0x00 app1
下载文件,拖入AndroidKiller,查看AndroidManifest.xml文件,找到入口:
查看java源码,找到关键点,输入的内容要和versionName逐位与versionCode异或的结果相等。
在BuildConfig中找到版本信息,就可以写脚本了。
name = "X<cP[?PHNB<P?aj"
code = 15
flag = ''
for i in range(len(name)):
flag += chr(ord(name[i]) ^ code)
print(flag)
0x01 app2
第二题比起第一题就难了,首先看onClick,对c和d进行了非空判断,结合将该apk在模拟器里运行,不难得知c和d就是我们进行的两个输入。然后将其拼接了起来,传给了SecondActivity这个活动,接下来查看SecondActivity。
private EditText c;
private EditText d;
public void onClick(View paramView)
{
switch (paramView.getId())
{
default:
return;
case 2131165187:
}
if ((this.c.getText().length() == 0) || (this.d.getText().length() == 0))
{
Toast.makeText(this, "不能为空", 1).show();
return;
}
paramView = this.c.getText().toString();
String str = this.d.getText().toString();
Log.e("test", paramView + " test2 = " + str);
Intent localIntent = new Intent(this, SecondActivity.class);
localIntent.putExtra("ili", paramView);
localIntent.putExtra("lil", str);
startActivity(localIntent);
}
查看onCreate(),这里有一句 Encryto.doRawData(this, paramBundle + str).equals("VEIzd/V2UPYNdn/bxH3Xig==")
,将我们传入的数据进行了加密,然后和 "VEIzd/V2UPYNdn/bxH3Xig=="
相等,于是我们跟进查看class Encryto。
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130903041);
Object localObject = getIntent();
paramBundle = ((Intent)localObject).getStringExtra("ili");
String str = ((Intent)localObject).getStringExtra("lil");
if (Encryto.doRawData(this, paramBundle + str).equals("VEIzd/V2UPYNdn/bxH3Xig=="))
{
((Intent)localObject).setAction("android.test.action.MoniterInstallService");
((Intent)localObject).setClass(this, MoniterInstallService.class);
((Intent)localObject).putExtra("company", "tencent");
((Intent)localObject).putExtra("name", "hacker");
((Intent)localObject).putExtra("age", 18);
startActivity((Intent)localObject);
startService((Intent)localObject);
}
localObject = getSharedPreferences("test", 0).edit();
((SharedPreferences.Editor)localObject).putString("ilil", paramBundle);
((SharedPreferences.Editor)localObject).putString("lili", str);
((SharedPreferences.Editor)localObject).commit();
}
在class Encryto中,我们发现他加载了library,于是我们只能去看看so文件了。
static
{
System.loadLibrary("JNIEncrypt");
}
反编译后,查看函数 doRawData()
,这里把 "thisisatestkey=="
赋值给了v10,然后调用了 AES_128_ECB_PKCS5Padding_Encrypt(v4, (int)v10)
,推测是128位的AES加密,模式是ECB,填充方式是PKCS5Padding,密钥就是v10。
int __cdecl doRawData(int a1, int a2, int a3, int a4)
{
char *v4; // esi
int result; // eax
char *v6; // esi
size_t v7; // eax
int v8; // [esp+0h] [ebp-2Ch]
int (__cdecl *v9)(int, char *, size_t); // [esp+0h] [ebp-2Ch]
char v10[20]; // [esp+4h] [ebp-28h] BYREF
unsigned int v11; // [esp+18h] [ebp-14h]
v11 = __readgsdword(0x14u);
if ( checkSignature(a1, a2, a3) == 1 )
{
strcpy(v10, "thisisatestkey==");
v4 = (char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a4, 0);
v8 = AES_128_ECB_PKCS5Padding_Encrypt(v4, (int)v10);
(*(void (__cdecl **)(int, int, char *))(*(_DWORD *)a1 + 680))(a1, a4, v4);
result = (*(int (__cdecl **)(int, int))(*(_DWORD *)a1 + 668))(a1, v8);
}
else
{
v6 = UNSIGNATURE[0];
v9 = *(int (__cdecl **)(int, char *, size_t))(*(_DWORD *)a1 + 652);
v7 = strlen(UNSIGNATURE[0]);
result = v9(a1, v6, v7);
}
return result;
}
将之前得到的那串字符串与刚才得到的密钥进行aes解密,得到aimagetencent,但是提交flag不对,,,在wp的帮助下发现密文在一个没有使用过的class里,FileDataActivity.class里找到9YuQ2dk8CSaCe7DTAmaqAA==
,解密得到flag。
0x02 easy-apk
在MainActivity的Onclick()中,对我们的输入进行加密后等于5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=
,跟进查看Base64New().Base64Encode()
,是一个变换了码表的base64加密。
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130968603);
((Button)findViewById(2131427446)).setOnClickListener(new View.OnClickListener()
{
public void onClick(View paramView)
{
paramView = ((EditText)MainActivity.this.findViewById(2131427445)).getText().toString();
if (new Base64New().Base64Encode(paramView.getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="))
{
Toast.makeText(MainActivity.this, "验证通过!", 1).show();
return;
}
Toast.makeText(MainActivity.this, "验证失败!", 1).show();
}
});
}
熟悉了base64原理之后就可以写脚本了:
b64_table = ['v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a',
'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+',
'/']
def encode(text):
bit_list = ''
new_bit = []
base = ''
for i in text:
bit_list += '{:0>8}'.format(bin(ord(i)).replace('0b', ''))
for i in range(0, len(bit_list), 6):
new_bit.append('{:0<6}'.format(bit_list[i:i + 6]))
for i in new_bit:
base += b64_table[int(i, 2)]
if len(text) % 3 == 1:
base += '=='
if len(text) % 3 == 2:
base += '='
return base
def decode(text):
new_bit = []
bit_list = ''
flag = []
for i in text:
if i == '=':
break
new_bit.append(b64_table.index(i))
for i in new_bit:
bit_list += '{:0>6}'.format(bin(i).replace('0b', ''))
for i in range(0, len(bit_list), 8):
flag.append('{:0>8}'.format(bit_list[i:i + 8]))
for i in flag:
print(chr(int(i, 2)), end='')
decode('5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=')
好久没怎么写代码了,水平又退步了,麻了。
0x03 easyjni
这题onCreate()方法里,获取了我们的输入,然后传给方法a(),判断返回值,返回值为true就成功。
protected void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130968603);
findViewById(2131427446).setOnClickListener(new View.OnClickListener(this)
{
public void onClick(View paramView)
{
paramView = (EditText)((MainActivity)this.a).findViewById(2131427445);
if (MainActivity.a(MainActivity.this, paramView.getText().toString()))
{
Toast.makeText(this.a, "You are right!", 1).show();
return;
}
Toast.makeText(this.a, "You are wrong! Bye~", 1).show();
}
});
}
然后我们来看看方法a,new了一个对象a,然后将返回值传给方法ncheck(),native()是so文件里的函数,等下看看so文件。a对象的方法a()应该就是换了码表的base64加密。
private boolean a(String paramString)
{
try
{
boolean bool = ncheck(new a().a(paramString.getBytes()));
return bool;
}
catch (java.lang.Exception paramString)
{
}
return false;
}
private native boolean ncheck(String paramString);
然后看看so文件里的 Java_com_a_easyjni_MainActivity_ncheck()
,首先判断了传入长度是否为32,然后执行以下函数:
for ( i = 0; i != 16; ++i )
{
v7 = &text[i];
text[i] = input[i + 16];
v8 = input[i];
v7[16] = v8;
}
这里我改了一下一些变量名,input就是传入的内容,也就是我们输入的内容base64编码后的内容,然后不难看出这是将input前16位和后16位调换了顺序,然后存入text中。
然后是如下代码:
v9 = 0;
do
{
v10 = v9 < 30;
v13 = text[v9];
text[v9] = text[v9 + 1];
text[v9 + 1] = v13;
v9 += 2;
}while ( v10 );
result = memcmp(text, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
把之前得到的内容每两位调换顺序,然后得到"MbT3sQgX039i3gAQOoMQFPskB1Bsc7",现在我们可以写脚本了,将上一题的脚本改改就能用了。
b64_table = ['i', '5', 'j', 'L', 'W', '7', 'S', '0', 'G', 'X', '6', 'u', 'f', '1', 'c', 'v', '3', 'n', 'y', '4', 'q',
'8', 'e', 's', '2', 'Q', '+', 'b', 'd', 'k', 'Y', 'g', 'K', 'O', 'I', 'T', '/', 't', 'A', 'x', 'U', 'r',
'F', 'l', 'V', 'P', 'z', 'h', 'm', 'o', 'w', '9', 'B', 'H', 'C', 'M', 'D', 'p', 'E', 'a', 'J', 'R', 'Z',
'N']
def encode(text):
bit_list = ''
new_bit = []
base = ''
for i in text:
bit_list += '{:0>8}'.format(bin(ord(i)).replace('0b', ''))
for i in range(0, len(bit_list), 6):
new_bit.append('{:0<6}'.format(bit_list[i:i + 6]))
for i in new_bit:
base += b64_table[int(i, 2)]
if len(text) % 3 == 1:
base += '=='
if len(text) % 3 == 2:
base += '='
return base
def decode(text):
new_bit = []
bit_list = ''
flag = []
for i in text:
if i == '=':
break
new_bit.append(b64_table.index(i))
for i in new_bit:
bit_list += '{:0>6}'.format(bin(i).replace('0b', ''))
for i in range(0, len(bit_list), 8):
flag.append('{:0>8}'.format(bit_list[i:i + 8]))
for i in flag:
print(chr(int(i, 2)), end='')
def neck(text):
cipher_text = ''
for i in range(0, 31, 2):
cipher_text += text[i + 1] + text[i]
cipher_text = cipher_text[16:] + cipher_text[:16]
print(cipher_text)
return cipher_text
decode(neck('MbT3sQgX039i3g==AQOoMQFPskB1Bsc7'))
0x04 easy-so
这题还是先看onclick()方法,调用了cyberpeace对象的Check String()方法,然后Cyberpeace加载了so里的库函数cyberpeace,接下来就去看so文件了。
public void onClick(View paramView)
{
if (cyberpeace.CheckString(((EditText)MainActivity.this.findViewById(2131165233)).getText().toString()) == 1)
{
Toast.makeText(MainActivity.this, "验证通过!", 1).show();
return;
}
Toast.makeText(MainActivity.this, "验证失败!", 1).show();
}
public class cyberpeace
{
static
{
System.loadLibrary("cyberpeace");
}
public static native int CheckString(String paramString);
}
拖入ida后,分析了半天我们可以发现逻辑和上一题一样,先将前16位和后16位交换位置,然后每两位互换位置,用上一题的脚本就可以了:
def neck(text):
cipher_text = ''
for i in range(0, 31, 2):
cipher_text += text[i + 1] + text[i]
cipher_text = cipher_text[16:] + cipher_text[:16]
return cipher_text
print(neck('f72c5a36569418a20907b55be5bf95ad'))
0x05 app3
这题给了一个ab格式的文件,开始还以为下载错了,百度了一下,是android的备份数据格式。ab文件一般分两种:
未加密,文件前面有24字节文件头,文件头包含none标志,文件头之后就是数据
加密,文件头包含AES-256标志
使用winhex或者其他二进制编辑器打开该文件,发现了none字样,显然是未加密文件。
使用Android backup extractor进行解析:
java -jar abe.jar unpack app3.ab app3.tar
得到压缩文件,进行解压,拿到了apk文件以及数据库文件。
└─com.example.yaphetshan.tencentwelcome
│ Encryto.db
│ _manifest
│
├─a
│ base.apk
│
└─db
Demo.db
先尝试用DB Browser for SQLite打开,发现被加密了,那就来看看apk文件。
还是先看MainActivity()的onClick()方法,获取我们的输入后开启了AnotherActivity活动,但是里面没啥东西。
再来看看onCreate()方法,调用了a()方法,实例化对象a,将flag存在数据库中。然后给v0的name和password字段分别赋值为'Stranger'和'123465',接下来实例化了一个a包下的a对象并初始化。然后调用a对象的a()方法,传入两个参数。
SQLiteDatabase.loadLibs(this);
this.b = new a(this, "Demo.db", null, 1);
ContentValues v0 = new ContentValues();
v0.put("name", "Stranger");
v0.put("password", Integer.valueOf(0x1E240));
com.example.yaphetshan.tencentwelcome.a.a v1 = new com.example.yaphetshan.tencentwelcome.a.a();
String v2 = v1.a(v0.getAsString("name"), v0.getAsString("password"));
this.a = this.b.getWritableDatabase(v1.a(v2 + v1.b(v2, v0.getAsString("password"))).substring(0, 7));
this.a.insert("TencentMicrMsg", null, v0);
}
a()方法将传入的两个参数分别截取前四位然后拼合后返回,也就是说v2值为'Stra1234'。
public class a {
private String a;
public a() {
this.a = "yaphetshan";
}
public String a(String arg3) {
new b();
return b.b(arg3 + this.a);
}
public String a(String arg4, String arg5) {
return arg4.substring(0, 4) + arg5.substring(0, 4);
}
public String b(String arg2, String arg3) {
new b();
return b.a(arg2);
}
}
然后我们跟进查看一下getWritableDatabase()方法是干什么的,这里的关键代码为:
if(this.mName == null) {
v0_1 = SQLiteDatabase.create(null, arg7);
}
会判断有没有该数据库,没有则用传入的参数作为密码创建一个数据库。那我们只要知道传入参数是多少就可以了。也就是 v1.a(v2 + v1.b(v2, v0.getAsString("password"))).substring(0, 7)
。
v1也就是a包下的对象a的实例化,b方法传入两个参数,但是只对第一个参数进行操作,将其作为参数传入方法a,也就是说参数就为:b(("Stra1234"+a("Stra1234")+"yaphetshan"))substring(0,7)
,就可以写脚本了:
package app;
import java.security.MessageDigest;
public class App {
public static void main(String[] args) {
String two = ("Stra1234"+a("Stra1234")+"yaphetshan");
System.out.println(b(two).substring(0,7));
}
public static final String a(String arg9) {
int v0 = 0;
char[] v2 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] v1 = arg9.getBytes();
MessageDigest v3 = MessageDigest.getInstance("MD5");
v3.update(v1);
byte[] v3_1 = v3.digest();
char[] v5 = new char[v3_1.length * 2];
int v1_1 = 0;
while (v0 < v3_1.length) {
int v6 = v3_1[v0];
int v7 = v1_1 + 1;
v5[v1_1] = v2[v6 >>> 4 & 15];
v1_1 = v7 + 1;
v5[v7] = v2[v6 & 15];
++v0;
}
return new String(v5);
} catch (Exception v0_1) {
return null;
}
}
public static final String b(String arg9) {
int v0 = 0;
char[] v2 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] v1 = arg9.getBytes();
MessageDigest v3 = MessageDigest.getInstance("SHA-1");
v3.update(v1);
byte[] v3_1 = v3.digest();
char[] v5 = new char[v3_1.length * 2];
int v1_1 = 0;
while (v0 < v3_1.length) {
int v6 = v3_1[v0];
int v7 = v1_1 + 1;
v5[v1_1] = v2[v6 >>> 4 & 15];
v1_1 = v7 + 1;
v5[v7] = v2[v6 & 15];
++v0;
}
return new String(v5);
} catch (Exception v0_1) {
return null;
}
}
}
拿到密码ae56f99
,用DB Browser for SQLite打开Encryto.db文件,拿到base64编码后的flag,解码得到flag。
0x06 easyjava
先看看源码,读取输入后调用了一个布尔类型的方法对我们的输入进行检查,必须以flag{
开头,且以 }
结尾。然后截取了flag大括号里的内容,接下来实例化了两个对象,逐个读取截取的flag字符,然后将这三个值作为参数传递给了一个函数,我们将其重命名为enc。然后将截取的flag字符作为参数先后调用b类的encB方法和a类的encA方法。
private static char enc(String arg1, b arg2, a arg3) {
return arg3.encA(arg2.encB(arg1));
}
private static Boolean b(String arg8) {
int v0 = 0;
if(!arg8.startsWith("flag{")) { // 必须以flag{开头
return Boolean.valueOf(false);
}
if(!arg8.endsWith("}")) { // 以}结尾
return Boolean.valueOf(false);
}
String v2 = arg8.substring(5, arg8.length() - 1); // flag大括号里包裹的内容
b v4 = new b(((int)2));
a v5 = new a(((int)3));
StringBuilder v3 = new StringBuilder();
int v1 = 0;
while(v0 < v2.length()) {
v3.append(MainActivity.enc(v2.charAt(v0) + "", v4, v5));
if(((int)(((int)(((int)v4.b()) / 25)))) > v1 && ((int)(((int)(((int)v4.b()) / 25)))) >= 1) {
++v1;
}
++v0;
}
return Boolean.valueOf(v3.toString().equals("wigwrkaugala"));
}
然后先来看看b类的encB方法,查找截取的flag字符在字符串stringB中出现的位置,然后该值在arrayList中出现的位置,然后调用方法a来对该对象的一些值进行处理。
这里我有个疑问,按照代码所写,应该是先去掉arraylist的首元素,然后将现在的首元素也就是之前的第二个元素加到末尾,但是这样写解密代码并不对,按照首元素移动到末位就对了。
然后将stringB的首字符移动到末尾。
public Integer encB(String arg5) {
int v0 = 0;
Integer v1 = (int)0;
if(b.stringB.contains(arg5.toLowerCase())) {
while(v0 < b.arrayList.size() - 1) {
if(b.arrayList.get(v0) == ((int)b.stringB.indexOf(arg5))) {
v1 = (int)v0;
}
++v0;
}
}
else {
v1 = arg5.contains(" ") ? ((int)-10) : ((int)-1);
}
b.a();
return v1;
}
public static void a() {
b.arrayList.remove(0);
b.arrayList.add(Integer.valueOf(((Integer)b.arrayList.get(0)).intValue()));
b.stringB = b.stringB + "" + b.stringB.charAt(0);
b.stringB = b.stringB.substring(1, 27);
b.intD = (int)(((int)b.intD) + 1);
}
然后看看a对象的encA方法,判断参数在arrayList中出现的位置,然后返回该值在stringA对应下标的值,并返回。这里同样也有一个方法对该对象的一些属性进行修改,但是对我们的加解密没有影响就不过多赘述了。
public char encA(Integer arg5) {
int v0 = 0;
Integer v1 = (int)0;
if(((int)arg5) == -10) {
a.a();
return " ".charAt(0);
}
while(v0 < a.arrayList.size() - 1) {
if(a.arrayList.get(v0) == arg5) {
v1 = (int)v0;
}
++v0;
}
a.a();
return a.stringA.charAt(v1.intValue());
}
加密过程说完了,这里a和b类实例化的时候,对对象的arraylist进行了初始化,可以不用看他的逻辑,直接写java代码来获得生成的arraylist。
import java.util.ArrayList;
public class App {
public static ArrayList arrayList;
static String stringB;
Integer[] intArrayC;
static Integer intD;
static {
App.arrayList = new ArrayList();
App.stringB = "abcdefghijklmnopqrstuvwxyz";
App.intD = (int)0;
}
public static void main(String[] args){
App a = new App(3);
System.out.println(arrayList);
}
public App(int arg8) { // arg8 = 3
this.intArrayC = new Integer[]{7, 14, 16, 21, 4, 24, ((int)25), ((int)20), ((int)5), ((int)15), ((int)9), ((int)17), ((int)6), ((int)13), ((int)3), ((int)18), ((int)12), ((int)10), ((int)19), ((int)0), ((int)22), ((int)2), ((int)11), ((int)23), ((int)1), ((int)8)};
int v0;
for(v0 = (int)arg8; v0 < this.intArrayC.length; ++v0) {
arrayList.add(intArrayC[v0]);
}
int v0_1;
for(v0_1 = 0; v0_1 < ((int)arg8); ++v0_1) {
App.arrayList.add(this.intArrayC[v0_1]);
}
}
}
import java.util.ArrayList;
public class AppB {
public static ArrayList arrayList;
static String stringB;
Integer[] intArrayC;
static Integer intD;
static {
AppB.arrayList = new ArrayList();
AppB.stringB = "abcdefghijklmnopqrstuvwxyz";
AppB.intD = (int)0;
}
public static void main(String[] args) {
AppB appB = new AppB(2);
System.out.println(arrayList);
}
public AppB(Integer arg9) {
this.intArrayC = new Integer[]{((int)8), ((int)25), ((int)17), ((int)23), ((int)7), ((int)22), ((int)1), ((int)16), ((int)6), ((int)9), ((int)21), ((int)0), ((int)15), ((int)5), ((int)10), ((int)18), ((int)2), ((int)24), ((int)4), ((int)11), ((int)3), ((int)14), ((int)19), ((int)12), ((int)20), ((int)13)};
int v0;
for(v0 = (int)arg9; v0 < this.intArrayC.length; ++v0) {
AppB.arrayList.add(this.intArrayC[v0]);
}
int v0_1;
for(v0_1 = 0; v0_1 < ((int)arg9); ++v0_1) {
AppB.arrayList.add(this.intArrayC[v0_1]);
}
}
}
解密脚本:
text = "wigwrkaugala"
list_a = [21, 4, 24, 25, 20, 5, 15, 9, 17, 6, 13, 3, 18, 12, 10, 19, 0, 22, 2, 11, 23, 1, 8, 7, 14, 16]
list_b = [17, 23, 7, 22, 1, 16, 6, 9, 21, 0, 15, 5, 10, 18, 2, 24, 4, 11, 3, 14, 19, 12, 20, 13, 8, 25]
stringA = "abcdefghijklmnopqrstuvwxyz"
stringB = "abcdefghijklmnopqrstuvwxyz"
int_a = int_b = 0
flag = ''
for i in text:
arg5 = list_a[stringA.index(i)]
flag += stringB[list_b[arg5]]
int_a += 1
list_b.append(list_b[0])
del list_b[0]
stringB += stringB[0]
stringB = stringB[1:27]
print(flag)
0x07 RememberOther
读取输入后调用checkSN()方法对输入进行检查,其实要使注册码等于用户名的md5值得奇数位,然后出现一个successed md5,其实可以直接用jeb看到。。。
private boolean checkSN(String arg12, String arg13) {
try {
if(arg12.length() != 0 || arg13.length() != 0) {
if(arg12 == null || arg12.length() == 0) {
return false;
}
if(arg13 == null || arg13.length() != 16) {
return false;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(arg12.getBytes());
String hexstr = MainActivity.toHexString(digest.digest(), "");
StringBuilder sb = new StringBuilder();
int i;
for(i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
boolean v9 = sb.toString().equalsIgnoreCase(arg13);
if(!v9) {
return false;
}
}
return true;
}
catch(NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
}
将md5值进行解密,得到YOU_KNOW_,然后由于Word文档里写了 不懂安卓,所以就只是和安卓扯了扯边,,,Have fun~
,所以还要在后面加一个ANDROID,,,而且还没有flag{}包裹,极其没有营养。。。
0x08 Ph0en1x-100
这题更多考察的是对so文件的分析,看看onGoClick()
方法,读取输入后执行encrypt()
方法,然后与getflag()
方法返回值同时作为参数执行getSecret()
方法,也就是说放我们的输入执行了encrypt()
方法后等于getflag()
方法的返回值就可以了,这两个方法我们可以在so文件中看到。
public void onGoClick(View arg5) {
String sInput = this.etFlag.getText().toString();
if(this.getSecret(this.getFlag()).equals(this.getSecret(this.encrypt(sInput)))) {
Toast.makeText(this, "Success", 1).show();
return;
}
Toast.makeText(this, "Failed", 1).show();
}
将so文件拖入ida后,可以看到都是诸如(*(_DWORD *)a1 + 676)
这样的代码,只需要对着a1按下y键 然后输入 JNIEnv*
修改参数类型就可以了。
可以看到 encrypt()
函数在这将a1初始化到s中,然后逐一访问s中的元素,接着每位减一,然后返回,逻辑很简单。
jstring __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(JNIEnv *a1, int a2, int a3)
{
size_t v3; // esi
const char *s; // edi
v3 = 0;
for ( s = (*a1)->GetStringUTFChars(a1, a3, 0); v3 < strlen(s); --s[v3++] )
;
return (*a1)->NewStringUTF(a1, s);
}
在 getFlag()
函数中,这里v2是v7地址+2,也就是说此时v2对应的地址就是v7的第三个字节处,也就是0x47。
然后在do...while循环中,v3等于v2,然后v2进行自减,也就是说v2会对v7与v6的26个字节进行逆向遍历。
然后让v5的字符逐个与之进行异或并存入他的地址。
jstring __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_getFlag(JNIEnv *a1)
{
int v1; // esi
char *v2; // edi
char v3; // al
jstring result; // eax
char v5[14]; // [esp+26h] [ebp-46h] BYREF
_DWORD v6[9]; // [esp+34h] [ebp-38h] BYREF
int v7; // [esp+58h] [ebp-14h] BYREF
unsigned int v8; // [esp+5Ch] [ebp-10h]
v1 = 38;
v2 = (char *)&v7 + 2;
v6[0] = 0x4C42362E;
v6[1] = 0x3AE0BF5F;
v8 = __readgsdword(0x14u);
v6[2] = 0x6320C3A8;
v6[3] = 0x1CC0B789;
v6[4] = 0x28C2441D;
v6[5] = 0xE02ED7F;
v6[6] = 0x988F665D;
v6[7] = 0x16D0B7B5;
v6[8] = 0xFBF8834D;
v7 = 0x474301;
strcpy(v5, "Hello Ph0en1x");
do
{
v3 = *v2--;
v2[1] = (v5[v1 % 13] ^ (v3 + 1 - *v2)) - 1;
--v1;
}
while ( v1 );
LOBYTE(v6[0]) = (LOBYTE(v6[0]) ^ 0x48) - 1;
result = (*a1)->NewStringUTF(a1, v6);
if ( __readgsdword(0x14u) != v8 )
sub_4B0();
return result;
}
有了这些就可以写脚本了,先获取这些十六进制数的单个字节,然后逐个进行异或即可。
text = [0x474301, 0xFBF8834D, 0x16D0B7B5, 0x988F665D,
0xE02ED7F, 0x28C2441D, 0x1CC0B789, 0x6320C3A8,
0x3AE0BF5F, 0x4C42362E]
flag_list = []
for i in text:
content = str(hex(i)).replace('0x', '').rjust(8, '0')
for j in range(0, 8, 2):
if hex(int('0x' + content[j:j + 2], 16)) != '0x0':
flag_list.append(int(hex(int('0x' + content[j:j + 2], 16)), 16))
flag_list = list(reversed(flag_list))
v5 = "Hello Ph0en1x"
v1 = len(flag_list) - 1
for i in range(v1, 0, -1):
v3 = flag_list[i]
v2 = flag_list[i - 1]
flag_list[i] = chr(ord(v5[i % 13]) ^ (v3 + 1 - v2) & 255)
flag_list[0] = chr(flag_list[0] ^ 0x48 & 255)
flag = ''.join(flag_list)
print(flag)
0x09 黑客精神
先来看看MainActivity的onCreate()方法,会先判断MyApp.m是否为0,0的话就是未注册,1就是已注册。然后如果m为0,就会调用doRegister()来进行注册,否则调用MyApp.work()。
@Override // android.app.Activity
public void onCreate(Bundle arg6) {
String str2;
super.onCreate(arg6);
this.setContentView(0x7F04001A); // layout:activity_main
Log.d("com.gdufs.xman m=", "Xman");
this.getApplication();
int m = MyApp.m;
if(m == 0) {
str2 = "未注册";
}
else {
str2 = m == 1 ? "已注册" : "已混乱";
}
this.setTitle("Xman" + str2);
this.btn1 = (Button)this.findViewById(0x7F0B0054); // id:button1
this.btn1.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View arg5) {
MainActivity.this.getApplication();
if(MyApp.m == 0) {
MainActivity.this.doRegister();
return;
}
((MyApp)MainActivity.this.getApplication()).work();
Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
}
});
}
先来看看doRegister(),如果我们点击注册,会调用一个新的Activity,也就是 "com.gdufs.xman.RegActivity"
,然后在RegActivity中,读取了我们的输入,然后调用MyApp的saveSN()方法。如果点击不玩了就会退出。
public void doRegister() {
new AlertDialog.Builder(this).setTitle("注册").setMessage("Flag就在前方!").setPositiveButton("注册", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface arg5, int arg6) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.gdufs.xman", "com.gdufs.xman.RegActivity"));
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
}
}).setNegativeButton("不玩了", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface arg2, int arg3) {
Process.killProcess(Process.myPid());
}
}).show();
}
接下来来看看MyApp的内容,onCreate()会调用initSN(),然后initSN()、saveSN()、work()都是库函数,那接下来久去看看so文件了。
public class MyApp extends Application {
public static int m;
static {
MyApp.m = 0;
System.loadLibrary("myjni");
}
public native void initSN() {
}
@Override // android.app.Application
public void onCreate() {
this.initSN();
Log.d("com.gdufs.xman m=", String.valueOf(MyApp.m));
super.onCreate();
}
public native void saveSN(String arg1) {
}
public native void work() {
}
}
拖入ida后,没有发现上述提到的几个函数,可以shift+F12,找到这几个函数对应的字符串,双击,然后点击地址,按下x键,就可以找到引用他们的地方,如图,n1、n2、n3就是这几个函数了,按n进行改名就可以了。
在initSN()中,会打开"/sdcard/reg.dat"文件,读取内容,然后判断是否与字符串"EoPAoY62@ElRD"相等,那么我们接下来就要去找这个文件中的字符串在哪进行修改了。
int __fastcall initSN(int a1)
{
int v2; // r0
int v3; // r4
int v4; // r0
int v5; // r7
const char *v6; // r5
int v8; // r0
int v9; // r1
v2 = j_fopen("/sdcard/reg.dat", "r+");
v3 = v2;
if ( !v2 )
{
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v2, 0, 2);
v5 = j_ftell(v3);
v6 = (const char *)j_malloc(v5 + 1);
if ( !v6 )
{
j_fclose(v3);
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v3, 0, 0);
j_fread(v6, v5, 1, v3);
v6[v5] = 0;
if ( !j_strcmp(v6, "EoPAoY62@ElRD") )
{
v8 = a1;
v9 = 1;
}
else
{
v8 = a1;
v9 = 0;
}
setValue(v8, v9);
return j_fclose(v3);
}
在saveSN()中,他会先打开"/sdcard/reg.dat"文件,然后对我们的输入进行一次加密,然后存入"/sdcard/reg.dat"文件中。有了加密过程,有了加密后的字符串,我们就可以写脚本了。
int __fastcall saveSN(JNIEnv *a1, int a2, int a3)
{
int v5; // r7
const char *v7; // r6
const char *v8; // r5
int v9; // r4
int v10; // r0
char v11; // r2
signed int v12; // [sp+8h] [bp-38h]
char v13[20]; // [sp+10h] [bp-30h] BYREF
v5 = j_fopen("/sdcard/reg.dat", "w+");
if ( !v5 )
return j___android_log_print(3, "com.gdufs.xman", byte_2E5A);
strcpy(v13, "W3_arE_whO_we_ARE");
v7 = (*a1)->GetStringUTFChars(a1, a3, 0);
v8 = v7;
v12 = j_strlen(v7);
v9 = 2016;
while ( 1 )
{
v10 = v8 - v7;
if ( v8 - v7 >= v12 )
break;
if ( v10 % 3 == 1 )
{
v9 = (v9 + 5) % 16;
v11 = v13[v9 + 1];
}
else if ( v10 % 3 == 2 )
{
v9 = (v9 + 7) % 15;
v11 = v13[v9 + 2];
}
else
{
v9 = (v9 + 3) % 13;
v11 = v13[v9 + 3];
}
*v8++ ^= v11;
}
j_fputs(v7, v5);
return j_fclose(v5);
}
解密代码:
str1 = "W3_arE_whO_we_ARE"
target = "EoPAoY62@ElRD"
v12 = len(target)
v8 = 0
v9 = 2016
flag = ''
while (1):
v10 = v8
if v8 >= v12:
break
if v10 % 3 == 1:
v9 = (v9 + 5) % 16
v11 = str1[v9 + 1]
elif v10 % 3 == 2:
v9 = (v9 + 7) % 15
v11 = str1[v9 + 2]
else:
v9 = (v9 + 3) % 13
v11 = str1[v9 + 3]
flag += chr((ord(target[v8]) ^ ord(v11)))
v8 += 1
print(flag)
将其输入app中,再次打开,点击提示:输入即是flag,格式为xman{......}!
成功!
0x0a easy-dex
(待更新)
Comments | NOTHING