0x00 CheckYourKey
一道安卓逆向
先扔jeb里面看看,找到MainActivity
简单分析一下上面的java代码
可以看到关键判断函数为ooxx
但ooxx函数为空
再分析一下,可以看到static代码块中多了一条语句:
1
| System.loadLibrary("CheckYourKey");
|
在Android开发中,原生代码一般用C\C++编写,然后编译为一个动态链接库,即 .so文件 。
loadLibrary
的作用就是加载这个动态链接库,使后面的代码能成功调用到对应的原生函数。
静态代码块的执行时机非常早、比构造函数、onCreate都要早,在类加载的时候就被调用。库加载并非一定要在当前类、static块中!!!
加载库还有其他方法,例如使用 System.load(String)
方法,其传入链接库的具体路径;甚至有的是在Native层中使用dlopen、mmap等方式来进行加载,相当于自己实现一个loadlibrary。其最终目的都是为了将代码加载入内存中 。
Android编译后的Apk文件 其实质上是一个Zip压缩包 ,打开后在其lib 目录中可以看到被 loadLibrary
加载的库。
如本题,将附件的文件后缀改为zip并打开
查看lib文件夹找到关键.so文件
将.so文件拖入IDA中进行静态分析
- 在Android系统中,对链接库进行加载的程序叫做linker,文件路径为
/system/bin/linker
。linker加载so的时候会依次调用其init_array中的函数来执行开发者的初始化代码,可在IDA中按shift+f7
打开Segmentation视图,若有.init_array
项,那么其中的函数就会被依次执行,这些函数都没有参数。
linker中加载so的函数叫做dlopen
,而loadLibrary跟load其实也是基于dlopen,但其添加了一个回调就是JNI_OnLoad,只要在代码中定义一个名为JNI_OnLoad的函数,dlopen完成之后就会将其调用。JNI_OnLoad的定义如下:
1
| jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
IDA查看默认JNI函数入口 Java_com_ctf_CheckYourKey_MainActivity_ooxx
反编译查看伪代码找到关键加密代码段
发现进行了两次加密 分别是sub_F7DC函数base58加密和sub_13788函数base64加密
但是解密出来不对
因此我们在IDA中查找JNI_OnLoad函数并进行分析
简单分析可知 JNI_onLoad函数将sub_8965注册为 ooxx
查看sub_8965定位到关键代码段
可以看出sub_8965函数将输入的字符串先后进行了三次加密
1.sub_FB40:
标准 AES-128-ECB
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 58 59 60 61 62 63 64 65 66
| __int64 __fastcall sub_FB40(__int64 a1, __int64 a2, __int64 a3) { __int64 v3;
sub_FBA8(a3, a1); qword_412C0 = a3; qword_412C8 = a2; v3 = sub_FC20(); return sub_FEC8(v3); }
__int64 __fastcall sub_FBA8(__int64 result, __int64 a2) { unsigned __int8 i;
for ( i = 0; i < 0x10u; ++i ) *(_BYTE *)(result + i) = *(_BYTE *)(a2 + i); return result; }
void sub_FC20() { unsigned __int8 v0; unsigned int j; unsigned int i; unsigned __int8 v3; unsigned __int8 v4; unsigned __int8 v5; unsigned __int8 v6; __int64 v7;
v7 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40); for ( i = 0; i <= 3; ++i ) { byte_412D0[4 * i] = *(_BYTE *)(qword_412C8 + 4 * i); byte_412D0[(4 * i) | 1] = *(_BYTE *)(qword_412C8 + ((4 * i) | 1)); byte_412D0[(4 * i) | 2] = *(_BYTE *)(qword_412C8 + ((4 * i) | 2)); byte_412D0[(4 * i) | 3] = *(_BYTE *)(qword_412C8 + ((4 * i) | 3)); } while ( i <= 0x2B ) { for ( j = 0; j <= 3; ++j ) *(&v3 + j) = byte_412D0[4 * i - 4 + j]; if ( (i & 3) == 0 ) { v0 = v3; v3 = v4; v4 = v5; v5 = v6; v6 = v0; v3 = sub_1005C(v3); v4 = sub_1005C(v4); v5 = sub_1005C(v5); v6 = sub_1005C(v6); v3 ^= byte_33C10[i >> 2]; } byte_412D0[4 * i] = byte_412D0[4 * i - 16] ^ v3; byte_412D0[(4 * i) | 1] = byte_412D0[(4 * i - 16) | 1] ^ v4; byte_412D0[(4 * i) | 2] = byte_412D0[(4 * i - 16) | 2] ^ v5; byte_412D0[(4 * i) | 3] = byte_412D0[(4 * i - 16) | 3] ^ v6; ++i; } _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)); }
|
2.sub_F7DC:
标准base58
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
| unsigned __int8 *__fastcall sub_F7DC(__int64 a1, unsigned __int64 a2) { unsigned __int64 v2; __int64 v3; bool v5; unsigned __int64 v6; div_t v7; unsigned __int64 v8; unsigned __int64 v9; int v10; unsigned __int64 v11; unsigned __int64 j; unsigned __int64 v13; unsigned __int8 *v14; unsigned __int64 i;
for ( i = 0LL; !*(_BYTE *)(a1 + i); ++i ) ; v14 = (unsigned __int8 *)malloc((unsigned int)(138 * a2 / 0x64) + 2); memset(v14, 0, 138 * a2 / 0x64 + 2); for ( j = 0LL; j < i; ++j ) { v2 = j; v14[v2] = *(_BYTE *)off_41008; } v11 = 0LL; while ( j < a2 ) { v3 = j++; v10 = *(unsigned __int8 *)(a1 + v3); v9 = 0LL; v8 = 138 * a2 / 0x64 + 1; while ( 1 ) { if ( v10 || (v5 = 0, v9 < v11) ) v5 = v8 != 0; if ( !v5 ) break; v7 = div(v10 + (v14[--v8] << 8), 58); v14[v8] = v7.rem; v10 = LOBYTE(v7.quot); ++v9; } v11 = v9; } v13 = i; v6 = 138 * a2 / 0x64 + 1 - v11 - i; while ( v13 < i + v11 ) { v14[v13] = *((_BYTE *)off_41008 + v14[v13 + v6]); ++v13; } if ( v6 ) v14[v13] = 0; return v14; }
|
3.sun_13788:
变表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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| __int64 __fastcall sub_13788(__int64 a1, unsigned int a2, __int64 a3) { __int64 v3; __int64 v4; __int64 v5; __int64 v6; __int64 v7; __int64 v8; __int64 v9; __int64 v10; __int64 v11; unsigned __int8 v13; unsigned __int8 v14; unsigned int v15; unsigned int v16; unsigned int v17; unsigned int v18; unsigned int i; int v20;
v20 = 0; v13 = 0; v15 = 0; for ( i = 0; i < a2; ++i ) { v14 = *(_BYTE *)(a1 + i); if ( v20 ) { if ( v20 == 1 ) { v20 = 2; v4 = v15++; *(_BYTE *)(a3 + v4) = byte_33F0F[(16 * (v13 & 3)) | ((int)v14 >> 4) & 0xF]; } else { v20 = 0; v5 = v15; v16 = v15 + 1; *(_BYTE *)(a3 + v5) = byte_33F0F[(4 * (v13 & 0xF)) | ((int)v14 >> 6) & 3]; v6 = v16; v15 = v16 + 1; *(_BYTE *)(a3 + v6) = byte_33F0F[v14 & 0x3F]; } } else { v20 = 1; v3 = v15++; *(_BYTE *)(a3 + v3) = byte_33F0F[((int)v14 >> 2) & 0x3F]; } v13 = v14; } if ( v20 == 1 ) { v7 = v15; v17 = v15 + 1; *(_BYTE *)(a3 + v7) = byte_33F0F[16 * (v13 & 3)]; v8 = v17++; *(_BYTE *)(a3 + v8) = 61; v9 = v17; v15 = v17 + 1; *(_BYTE *)(a3 + v9) = 61; } else if ( v20 == 2 ) { v10 = v15; v18 = v15 + 1; *(_BYTE *)(a3 + v10) = byte_33F0F[4 * (v13 & 0xF)]; v11 = v18; v15 = v18 + 1; *(_BYTE *)(a3 + v11) = 61; } *(_BYTE *)(a3 + v15) = 0; return v15; }
|
最后按顺序逆回去就可以得到flag
0x01 huowang
迷宫题 要求最短路径
常规查壳,64位无壳elf文件
参考资料
checkyourkey
Write-up/RCTF.md at main · b3f0re-team/Write-up · GitHub
[原创]Android逆向新手答疑解惑篇——JNI与动态注册-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com