Android Re 入门 必备工具: IDA : 反编译.so文件
AndroidKiller: 反编译apk文件及再次编译为apk
jd-gui : 将.jar文件反编译为java代码
dex2jar: 反编译.dex文件
apktool: 能够反编译及回编译apk
夜神模拟器: 运行apk文件
例题 1 [easy-so] 下载
来源:攻防世界
使用夜神模拟器运行该apk文件, 发现需要输入flag
zip解压apk文件, 用 dex2jar反编译 classes.dex文件得到classes-dex2jar.jar,
1 d2j-dex2jar.bat classes.dex
再用jd-gui打开classes-dex2jar.jar反编译为java代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MainActivity extends AppCompatActivity { protected void onCreate (Bundle paramBundle) { super .onCreate(paramBundle); setContentView(2131296283 ); ((Button)findViewById(2131165218 )).setOnClickListener(new View .OnClickListener() { public void onClick (View param1View) { if (cyberpeace.CheckString(((EditText)MainActivity.this .findViewById(2131165233 )).getText().toString()) == 1 ) { Toast.makeText((Context)MainActivity.this , ", 1).show(); return; } Toast.makeText((Context)MainActivity.this, " , 1 ).show(); } }); } }
在这里可以发现, cyberpeace加载了’cyberpeace’动态库, CheckString函数是native层的, 这个函数的实现就在libcyberpeace.so文件中
1 2 3 4 5 6 7 8 9 10 package com.testjava.jack.pingan2; public class cyberpeace { static { System.loadLibrary("cyberpeace"); } public static native int CheckString(String paramString); }
使用ida打开lib/x86/libcyberpeace.so文件, 找到 _BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3)函数, 该函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 _BOOL4 __cdecl Java_com_testjava_jack_pingan2_cyberpeace_CheckString(int a1, int a2, int a3) { const char *get_str; // ST1C_4 size_t len; // edi char *str; // esi size_t i; // edi char v7; // al char v8; // al size_t v9; // edi char v10; // al get_str = (const char *)(*(int (__cdecl **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0); // 从java层获取所输入的字符串 len = strlen(get_str); str = (char *)malloc(len + 1); memset(&str[len], 0, len != -1); memcpy(str, get_str, len); if ( strlen(str) >= 2 ) // 加密1 { i = 0; do { v7 = str[i]; str[i] = str[i + 16]; str[i++ + 16] = v7; } while ( i < strlen(str) >> 1 ); } // 加密2 v8 = *str; if ( *str ) { *str = str[1]; str[1] = v8; if ( strlen(str) >= 3 ) { v9 = 2; do { v10 = str[v9]; str[v9] = str[v9 + 1]; str[v9 + 1] = v10; v9 += 2; } while ( v9 < strlen(str) ); } } return strcmp(str, "f72c5a36569418a20907b55be5bf95ad") == 0; // 与字符串作比较 }
从以上发现, 对我们所输入的字符串进行了加密, 然后再与f72c5a36569418a20907b55be5bf95ad进行比较. 现在只需逆一下以上代码即可得到flag, exp代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h> #include <string.h> int main(void) { char str[] = "f72c5a36569418a20907b55be5bf95ad"; int i, v7, v8, v9, v10; v8 = *str; if ( *str ) { *str = str[1]; str[1] = v8; if ( strlen(str) >= 3 ) { v9 = 2; do { v10 = str[v9]; str[v9] = str[v9 + 1]; str[v9 + 1] = v10; v9 += 2; } while ( v9 < strlen(str) ); } } // 交换 if ( strlen(str) >= 2 ) { i = 0; do { v7 = str[i]; str[i] = str[i + 16]; str[i++ + 16] = v7; } while ( i < strlen(str) >> 1 ); } printf("%s", str); return 0; }
运行以上代码即可获取flag
例题 2 [app2] 下载
来源:攻防世界
对输入的账号和密码在SecondActivity类中进行加密判断, 而加密调用了native层的加密函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903041); Intent intent = getIntent(); String str1 = intent.getStringExtra("ili"); String str2 = intent.getStringExtra("lil"); if (Encryto.doRawData(this, str1 + str2).equals("VEIzd/V2UPYNdn/bxH3Xig==")) { intent.setAction("android.test.action.MoniterInstallService"); intent.setClass((Context)this, MoniterInstallService.class); intent.putExtra("company", "tencent"); intent.putExtra("name", "hacker"); intent.putExtra("age", 18); startActivity(intent); startService(intent); } SharedPreferences.Editor editor = getSharedPreferences("test", 0).edit(); editor.putString("ilil", str1); editor.putString("lili", str2); editor.commit(); }
IDA反编译doRawData函数, 因为a为对象, 选择a按下y 键 然后输入 JNIEnv*就可以显示对象的函数调用, 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int __cdecl doRawData(JNIEnv *a1, int a2, int a3, int a4) { char *v4; // esi const char *v5; // ST10_4 int result; // eax char *v7; // esi jstring (*v8)(JNIEnv *, const jchar *, jsize); // ST10_4 size_t v9; // eax int v10; // [esp+4h] [ebp-28h] int v11; // [esp+8h] [ebp-24h] int v12; // [esp+Ch] [ebp-20h] int v13; // [esp+10h] [ebp-1Ch] char v14; // [esp+14h] [ebp-18h] unsigned int v15; // [esp+18h] [ebp-14h] v15 = __readgsdword(0x14u); if ( checkSignature((int)a1, a2, a3) == 1 ) { v14 = 0; v13 = 0x3D3D7965; v12 = 0x6B747365; v11 = 0x74617369; v10 = 0x73696874; v4 = (char *)(*a1)->GetStringUTFChars(a1, (jstring)a4, 0); v5 = (const char *)AES_128_ECB_PKCS5Padding_Encrypt(v4, (int)&v10); (*a1)->ReleaseStringUTFChars(a1, (jstring)a4, v4); result = (int)(*a1)->NewStringUTF(a1, v5); } else { v7 = UNSIGNATURE[0]; v8 = (*a1)->NewString; v9 = strlen(UNSIGNATURE[0]); result = (int)v8(a1, (const jchar *)v7, v9); } return result; }
可以发现, 加密方式为aes加密, key 为 v10中的内容.为thisisatestkey==
对VEIzd/V2UPYNdn/bxH3Xig== 解密为aimagetencent, 发现提交flag错误, 重新找另一个字符串,在FileDataActivity类中找到如下.
1 2 3 4 5 6 7 8 9 10 public class FileDataActivity extends a { private TextView c; protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130903042); this.c = (TextView)findViewById(2131165184); this.c.setText(Encryto.decode(this, "9YuQ2dk8CSaCe7DTAmaqAA==")); } }
调用了decode函数, 而decode函数与doRawData实现一样, 直接与之前一样的AES ecb解密, 得到flag
Ph0en1x-100 下载
来源:攻防世界
程序流程, 输入flag
jd-gui反编译如下:
1 2 3 4 5 6 7 8 9 10 11 12 public void onGoClick(View paramView) { paramView = this.etFlag.getText().toString(); if (getSecret(getFlag()).equals(getSecret(encrypt(paramView)))) { Toast.makeText(this, "Success", 1).show(); } for (;;) { return; Toast.makeText(this, "Failed", 1).show(); } }
getFlag函数与encrypt函数是native层
1 2 3 4 5 6 7 8 static { System.loadLibrary("phcm"); } public native String encrypt(String paramString); public native String getFlag();
反编译libphcm.so文件
1 2 3 4 5 6 7 8 9 10 int __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(JNIEnv *a1, int a2, int a3) { size_t i; // esi const char *s; // edi i = 0; for ( s = (*a1)->GetStringUTFChars(a1, (jstring)a3, 0); i < strlen(s); --s[i++] ) ; return (*a1)->NewStringUTF(a1, s); }
以上加密就是对字符串中的每个字符-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 int __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_getFlag(JNIEnv *a1) { signed int v1; // esi char *v2; // edi char v3; // al int result; // eax int v5; // [esp+26h] [ebp-46h] int v6; // [esp+2Ah] [ebp-42h] int v7; // [esp+2Eh] [ebp-3Eh] __int16 v8; // [esp+32h] [ebp-3Ah] int v9; // [esp+34h] [ebp-38h] int v10; // [esp+38h] [ebp-34h] int v11; // [esp+3Ch] [ebp-30h] int v12; // [esp+40h] [ebp-2Ch] int v13; // [esp+44h] [ebp-28h] int v14; // [esp+48h] [ebp-24h] int v15; // [esp+4Ch] [ebp-20h] int v16; // [esp+50h] [ebp-1Ch] int v17; // [esp+54h] [ebp-18h] int v18; // [esp+58h] [ebp-14h] unsigned int v19; // [esp+5Ch] [ebp-10h] v1 = 38; v2 = (char *)&v18 + 2; v9 = 1279407662; v10 = 987807583; v19 = __readgsdword(0x14u); v11 = 1663091624; v12 = 482391945; v13 = 683820061; v14 = 235072895; v15 = 2559534685; v16 = 382777269; v17 = 4227367757; v18 = 4670209; v5 = 1819043144; v6 = 1750081647; v7 = 829318448; v8 = 120; do { v3 = *v2--; v2[1] = (*((_BYTE *)&v5 + v1-- % 13) ^ (v3 + 1 - *v2)) - 1; } while ( v1 ); LOBYTE(v9) = (v9 ^ 0x48) - 1; result = (int)(*a1)->NewStringUTF(a1, (const char *)&v9); if ( __readgsdword(0x14u) != v19 ) sub_4B0(); return result; }
这个加密稍微有点复杂. 若想获取flag, 先对上面这给逆出来, 在对encrypt加密加密函数给再逆出来即可获得flag, 但是, 我写了一个c脚本, 上面这个有问题, 主要是LOBYTE(v9) = (v9 ^ 0x48) - 1;这个语句不好写.换另一种思路.动态调试(瞎弄半天, 啥也没弄出来), 再换另一种, 就是使用Android killer修改smali源码, 将其getFlag函数的字符串给打印出来, 只需将打印失败逻辑添加一下getFlag函数, 将getFlag字符串覆盖为打印失败的字符串, 复制上面调用getFlag的即可, 再更变一下变量, 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 .line 37 :cond_0 const-string v1, "Failed" invoke-virtual {p0}, Lcom/ph0en1x/android_crackme/MainActivity;->getFlag()Ljava/lang/String; move-result-object v1 // getFlag()函数的返回值,(字符串) invoke-static {p0, v1, v3}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; move-result-object v1 invoke-virtual {v1}, Landroid/widget/Toast;->show()V
然后点击Android->编译, 即可再次编译为apk文件, 安装再nox中运行, 随便输入就会出现ekfz@q2^x/t^fn0mF^6/^rb
qanqntfg^E`hq|
再次让每个字符+1就可得到flag
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <string.h> int main(void) { char flag[] = "ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|"; for(int i = 0; i < strlen(flag); ++i) { putchar(++flag[i]); } return 0; }
app1 下载
来源:攻防世界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public void onClick(View paramView) { for (;;) { try { str = this.this$0.text.getText().toString(); PackageInfo localPackageInfo = this.this$0.getPackageManager().getPackageInfo("com.example.yaphetshan.tencentgreat", 16384); paramView = localPackageInfo.versionName; int i = localPackageInfo.versionCode; j = 0; if ((j >= str.length()) || (j >= paramView.length())) { continue; } if (str.charAt(j) != (paramView.charAt(j) ^ i)) { Toast.makeText(this.this$0, "再接再励~", 1).show(); return; } } catch (PackageManager.NameNotFoundException paramView) { String str; int j; Toast.makeText(this.this$0, "不要玩小聪明", 1).show(); continue; } j++; continue; if (str.length() != paramView.length()) { continue; } Toast.makeText(this.this$0, "恭喜开启芝麻之门", 1).show(); } }
在BuildConfig class中找到versionName和versionCode
1 2 3 4 5 6 7 8 9 10 11 12 package com.example.yaphetshan.tencentgreat; public final class BuildConfig { public static final String APPLICATION_ID = "com.example.yaphetshan.tencentgreat"; public static final String BUILD_TYPE = "debug"; public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String FLAVOR = ""; public static final int VERSION_CODE = 15; public static final String VERSION_NAME = "X<cP[?PHNB<P?aj"; }
解密脚本
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <string.h> int main(void) { char name[] = "X<cP[?PHNB<P?aj"; for(int i = 0; i < strlen(name); ++i) putchar(name[i] ^15); return 0; }
easyjni 下载
来源:攻防世界
反编译java如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class MainActivity$1 implements View.OnClickListener { MainActivity$1(MainActivity paramMainActivity, Context paramContext) {} public void onClick(View paramView) { paramView = (EditText)((MainActivity)this.a).findViewById(2131427445); if (MainActivity.a(this.b, paramView.getText().toString())) { Toast.makeText(this.a, "You are right!", 1).show(); } for (;;) { return; Toast.makeText(this.a, "You are wrong! Bye~", 1).show(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MainActivity extends c { static { System.loadLibrary("native"); } private boolean a(String paramString) { try { a locala = new com/a/easyjni/a; locala.<init>(); bool = ncheck(locala.a(paramString.getBytes())); return bool; } catch (Exception paramString) { for (;;) { boolean bool = false; } } } private native boolean ncheck(String paramString);
从以上代码可以看到, 对输入的数据先进行base64加密, 然后调用native层的ncheck函数判断是否正确.
ida打开libnative.so文件, 反编译ncheck函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 signed int __fastcall Java_com_a_easyjni_MainActivity_ncheck(JNIEnv *a1, int a2, int a3) { int v3; // r8 JNIEnv *v4; // r5 int v5; // r8 const char *str; // r6 int i; // r0 char *v8; // r2 char v9; // r1 int v10; // r0 bool v11; // nf unsigned __int8 v12; // vf int v13; // r1 signed int result; // r0 char en_str[32]; // [sp+3h] [bp-35h] char v16; // [sp+23h] [bp-15h] int v17; // [sp+28h] [bp-10h] v17 = v3; v4 = a1; v5 = a3; str = (const char *)((int (__fastcall *)(JNIEnv *, int, _DWORD))(*a1)->GetStringUTFChars)(a1, a3, 0); if ( strlen(str) == 32 ) { i = 0; do { v8 = &en_str[i]; en_str[i] = str[i + 16]; v9 = str[i++]; v8[16] = v9; } while ( i != 16 ); ((void (__fastcall *)(JNIEnv *, int, const char *))(*v4)->ReleaseStringUTFChars)(v4, v5, str); v10 = 0; do { v12 = __OFSUB__(v10, 30); v11 = v10 - 30 < 0; v16 = en_str[v10]; en_str[v10] = en_str[v10 + 1]; en_str[v10 + 1] = v16; v10 += 2; } while ( v11 ^ v12 ); v13 = memcmp(en_str, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u); result = 0; if ( !v13 ) result = 1; } else { ((void (__fastcall *)(JNIEnv *, int, const char *))(*v4)->ReleaseStringUTFChars)(v4, v5, str); result = 0; } return result; }
以上代码经过了两次加密, 先进行str[i]与str[i + 16]的交换, 再进行str[i]与str[i + 1]进行交换.
解密脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> void decode() { char code[] = "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7"; for(int i = 0; i < 32; i += 2) { char ch = code[i]; code[i] = code[i + 1]; code[i + 1] = ch; } //printf("%s", code); for(int i = 0; i < 16; i ++) { char ch = code[i]; code[i] = code[i + 16]; code[i + 16] = ch; } printf("%s", code); } void show_arr() { char arr[] = { 105, 53, 106, 76, 87, 55, 83, 48, 71, 88, 54, 117, 102, 49, 99, 118, 51, 110, 121, 52, 113, 56, 101, 115, 50, 81, 43, 98, 100, 107, 89, 103, 75, 79, 73, 84, 47, 116, 65, 120, 85, 114, 70, 108, 86, 80, 122, 104, 109, 111, 119, 57, 66, 72, 67, 77, 68, 112, 69, 97, 74, 82, 90, 78, 0 }; printf("%s\n", arr); } int main(void) { show_arr(); decode(); return 0; }
解密base64
1 2 3 4 5 6 7 8 9 10 import base64 import string str1 = "QAoOQMPFks1BsB7cbM3TQsXg30i9g3==" string1 = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN" string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" print (b'code: ' + base64.b64decode(str1.translate(str.maketrans(string1,string2))))
easy apk 下载
来源: 攻防世界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MainActivity$1 implements View.OnClickListener { MainActivity$1(MainActivity paramMainActivity) {} public void onClick(View paramView) { paramView = ((EditText)this.this$0.findViewById(2131427445)).getText().toString(); if (new Base64New().Base64Encode(paramView.getBytes()).equals("5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=")) { Toast.makeText(this.this$0, "验证通过!", 1).show(); } for (;;) { return; Toast.makeText(this.this$0, "验证失败!", 1).show(); } } }
base64加密如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Base64New { private static final char[] Base64ByteToStr = { 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 }; private static final int RANGE = 255; private static byte[] StrToBase64Byte = new byte['?']; public String Base64Encode(byte[] paramArrayOfByte) { StringBuilder localStringBuilder = new StringBuilder(); for (int i = 0; i <= paramArrayOfByte.length - 1; i += 3) { byte[] arrayOfByte = new byte[4]; int j = 0; int k = 0; if (k <= 2) { if (i + k <= paramArrayOfByte.length - 1) { arrayOfByte[k] = ((byte)(byte)((paramArrayOfByte[(i + k)] & 0xFF) >>> k * 2 + 2 | j)); } for (j = (byte)(((paramArrayOfByte[(i + k)] & 0xFF) << (2 - k) * 2 + 2 & 0xFF) >>> 2);; j = 64) { k++; break; arrayOfByte[k] = ((byte)j); } } arrayOfByte[3] = ((byte)j); j = 0; if (j <= 3) { if (arrayOfByte[j] <= 63) { localStringBuilder.append(Base64ByteToStr[arrayOfByte[j]]); } for (;;) { j++; break; localStringBuilder.append('='); } } } return localStringBuilder.toString(); } }
以上加密是对输入进行了一个base64换表的加密
解密脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import base64 import string base_table = [ 118, 119, 120, 114, 115, 116, 117, 111, 112, 113, 51, 52, 53, 54, 55, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 121, 122, 48, 49, 50, 80, 81, 82, 83, 84, 75, 76, 77, 78, 79, 90, 97, 98, 99, 100, 85, 86, 87, 88, 89, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 56, 57, 43, 47 ]; string1 = '' code = '5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs=' for i in range(len(base_table)): string1 += chr(base_table[i]) string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" print(base64.b64decode(code.translate(str.maketrans(string1,string2))))