本帖最后由 HNHuangJingYU 于 2021-9-29 21:48 编辑
1. 嗯,拿到easy-dex.apk拖入jeb分析好家伙直接没有dex文件,嗯明白了,小型的脱壳题目,果断进入libnative.so,逆向前不知道什么是NativeActivity通过这里进行了简单了解下:https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff,了解到了两个重要函数ANativeActivity_onCreate、android_main大概就是页面的入口函数,通过IDA分析主要的核心代码在android_main里面
[C++] 纯文本查看 复制代码 int __fastcall android_main(_DWORD *application)
{
application_1 = application;
destLen = 0x100000;
dest = malloc(0x100000u);
v2 = dword_43A18;
v3 = malloc(dword_43A18);
_aeabi_memcpy(v3, &loc_7004, v2);
filename = -1651995194;
LOBYTE(filename) = 47; // //将47赋值给filename中检索低位字节
do
*(&filename + v4++) ^= 0xE9u; // //给filename填充数据
while ( v4 != 53 ); // //长度为52
v5 = 1;
LOBYTE(name) = 47; // //将47赋值给name中检索低位字节。
do
*(&name + v5++) ^= 0xE9u;
while ( v5 != 47 ); // //长度47
j_app_dummy();
_aeabi_memclr8(&application_2, 52);
*application_1 = &application_2; // //取application_2地址赋值给application_1[0]
application_1[1] = sub_29B8;
application_1[2] = sub_2B90; // func2
application_2 = application_1;
v27 = ASensorManager_getInstance(); // //application_2 取得 application_1 地址
v28 = ASensorManager_getDefaultSensor(); // //管理传感器实例对象
v6 = 0;
v29 = ASensorManager_createEventQueue(v27, application_1[7], 3, 0, 0);
v7 = application_1[5];
if ( v7 ) // application_1[5]是否存在
{
v8 = *(v7 + 4);
v9 = *(v7 + 8);
v31 = *v7;
v32 = v8;
v33 = v9;
}
_android_log_print(4, "FindMyDex", "Can you shake your phone 100 times in 10 seconds?");
v10 = 0;
do
{
while ( 1 )
{
v12 = 0;
if ( !v30 )
v12 = -1;
v13 = ALooper_pollAll(v12, 0, &v25, &v24);// 执行所有挂起的回调
if ( v13 >= 0 )
break;
if ( v30 )
{
v11 = v31 + 0.01;
if ( (v31 + 0.01) > 1.0 )
v11 = 0.0;
v31 = v11;
sub_2C14(&application_2);
}
}
if ( v24 )
(*(v24 + 8))(application_1);
if ( v13 == 3 && v28 )
{
while ( 1 )
{
do
{
if ( ASensorEventQueue_getEvents(v29, &v35, 1) < 1 )
goto LABEL_51;
}
while ( v36 != 1 );
if ( v10 & 1 )
{
if ( v37 >= -15.0 )
{
LABEL_30:
v14 = v10;
goto LABEL_31;
}
if ( v10 == 1 )
v6 = time(0);
v14 = v10 + 1;
}
else
{
if ( v37 <= 15.0 )
goto LABEL_30;
v14 = v10 + 1;
if ( v10 >= 0 )
_android_log_print(4, "FindMyDex", "Oh yeah~ You Got it~ %d times to go~", 99 - v10);
}
LABEL_31:
v10 = v14;
if ( (v14 - 1) <= 0x58 ) // 解密文件
{
v10 = v14;
v15 = v14 / 10;
if ( v14 % 10 == 9 )
{
v16 = dword_43A18;
v17 = dword_43A18 / 10;
v18 = (v15 + 1) * (dword_43A18 / 10);
if ( dword_43A18 / 10 * v15 < v18 )
{
v19 = &v3[v17 * v15];
do
{
--v17;
*v19++ ^= v14;
}
while ( v17 );
}
if ( v14 == 89 )
{
while ( v18 < v16 )
v3[v18++] ^= 0x59u;
}
v10 = v14 + 1;
}
}
if ( v14 == 100 )
{
if ( (time(0) - v6) > 9 )
{
_android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
_aeabi_memcpy(v3, &loc_7004, dword_43A18);
v10 = 0;
}
else
{
v20 = v6;
if ( uncompress(dest, &destLen, v3, dword_43A18) )// 解压缩文件
_android_log_print(5, "FindMyDex", "Dangerous operation detected.");
v21 = open(&filename, 577, 511);
if ( !v21 )
_android_log_print(5, "FindMyDex", "Something wrong with the permission.");
write(v21, dest, destLen); // 写入数据
close(v21);
free(dest);
free(v3);
if ( access(&name, 0) && mkdir(&name, 0x1FFu) )
_android_log_print(5, "FindMyDex", "Something wrong with the permission..");
sub_2368(application_1);
remove(&filename); // 删除文件
_android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
sub_2250(application_1);
v10 = 2147483648;
v6 = v20;
}
}
}
}
LABEL_51:
;
}
while ( !application_1[15] );
sub_2BDA(&application_2);
return _stack_chk_guard - v65;
} 2. 主要分为创建文件对传感器进行实例化和监听、对数据进行dex解密、解压缩、写数据、删除文件。
3. 分析解密文件处可知都是对dword_43A18数据段进行操作,所以先将这个段dump下来试试看,查看ida view得知dword_43A18位于.data里面,如图:
1
4. 通过0x43A18 - 0x3CA10 = 0x7008 定位到0x7008处,那么数据段就是从0x7004开始的那么就得到了dump需要的条件,那么运行脚本:
[Asm] 纯文本查看 复制代码 static main()
{
auto i,fp;
fp = fopen("d:\\dump","wb");
auto start = 0x7004;
auto size = 0x3CA10;
for(i=start;i<start+size;i++)
{
fputc(Byte(i),fp);
}
fp.close();
} 5. 在D盘找到demp文件,那么现在再进行解密。解密操作核心部分:
2
6. Java复现脚本(到了这里实在是卡住了第一次接触这种题,搜了下大佬思路,引用了大神的脚本,并对脚本进行了难理解的注释,算是做一丢丢贡献把,我也是第一次接触CTF,就当作一次学习的旅程吧)这里加了一个jar文件、注意下如图:
3
[Java] 纯文本查看 复制代码 @Test
public void demo() {
try {
FileInputStream fis = new FileInputStream("D://dump");
int length = 0x3ca10; //文件大小
byte[] b = new byte[length];
int tempchar;
int index = 0;
while ((tempchar = fis.read()) != -1) { //读取每个字节的数据存入 byte[] b
b[index] = (byte) tempchar;
index++;
}
int v15, v16, v17, v18, v19;
for (int v14 = 0; v14 < 90; v14++) { //这里是对应着C++中的 LABEL_31 那块部分解密逻辑
v15 = v14 / 10; //因为LABEL_31处于一个while ( 1 )的大逻辑死循环里面
if (v14 % 10 == 9) { //当v37满足-15<=v37<=15的时候 则v14 = v10 +1(V10的初始值为0)
v16 = length; //所以就可以得出这个for循环
v17 = length / 10;
v18 = (v15 + 1) * (length / 10);
if (length / 10 * v15 < v18) {
v19 = v17 * v15; //这里进行了变换 原句:v19 = &v3[v17 * v15];
do { //因为v3是一个占内存控件大小为0x3ca10的空数据地址 这里v19则是地址
--v17;
b[v19++] ^= v14; //原句 : *v19++ ^= v14; 可理解为: *v3[v17 * v15]取这块内存地址下的数据
}
while (v17 > 0);
}
if (v14 == 89) {
while (v18 < v16) { //到达这里解密算法就结束了
b[v18++] ^= 89;
}
}
}
}
fis.close();
FileOutputStream fos = new FileOutputStream("D://class.dex"); //进行文件写出
for (byte ch : unjzlib(b)) {
fos.write(ch);
}
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static byte[] unjzlib(byte[] object) {
byte[] data = null;
try {
ByteArrayInputStream in = new ByteArrayInputStream(object);
ZInputStream zIn = new ZInputStream(in); // qwb{TH3y_lo<e_EACh_OTH3r_FOrEUER}
byte[] buf = new byte[1024];
int num;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((num = zIn.read(buf, 0, buf.length)) != -1) {
baos.write(buf, 0, num);
}
data = baos.toByteArray();
baos.flush();
baos.close();
zIn.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return data;
} 7. 运行后就得到了完整的dex文件了路径在你写出文件的目录下,用jeb打开dex文件(jadx不行)一看b类好家伙这种算法逆向直接就给了我一大重击拳,求助大神的帖子后发现是一个第三方算法。先将MainActivity中的m转换为Base64因为twofish算法解密后是Base64。8.转换为Base64结果:iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N,去到网页在线twofish解密得到flag:qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}
|