首页
社区
课程
招聘
[原创]2015第2届移动安全挑战赛(MSC)Writeup
发表于: 2015-10-21 16:27 12899

[原创]2015第2届移动安全挑战赛(MSC)Writeup

2015-10-21 16:27
12899
此次比赛提交了一题,第二题到比赛结束时卡在了最后的aes算法那里,但是由于大小端字节序的问题,时间紧张没有解对,比赛结束后又再次继续做才得出了结果。

首先是第一题,简单说下思路吧,通过JEB来看,这个题调用大量用了反射,逻辑比较复杂,一开始就没有去分析算法,首先IDA直接动态调试dex,调试是成功了,但条指令的寄存器的值在IDA中却无法显示。于是换思路,打算借鉴第一届公布的writeup的思路来做,对虚拟机插桩,检测并输出反射的调用,希望得到算法大致逻辑。但是这个过程并不是很顺利,对虚拟机不是太熟,最后只打出了反射的ClassName和Method,而且由于大量反射调用,输出的结果比较混乱,对逻辑理解没有太多帮助。所以改变了思路,采用重打包加插Log的方式来输出逻辑的关键信息,最终问题解决。

第二题,重点说下。apk里额外加载了so,关键逻辑在so中实现,依据上一届的writeup经验,so中存在反调试,在init_array中做了手脚,并且多次检测了/proc/self/status中TracerPid来看是否被调试。一个比较暴力的做法是修改内核fs/proc/array.c的task_state函数,使进程无论是否被调试,都将该处TracerPid直接置为0。但是这里不打算这么做,在这之前并没有调试过so加载过程,想直接调试so的加载过程来看看整个流程细节是怎样的。于是首先看了linker的加载代码实现,init_array调用是在soinfo::CallConstructors()中进行的:
void soinfo::CallConstructors() {
  (略)
  TRACE("\"%s\": calling constructors", name);

  // DT_INIT should be called before DT_INIT_ARRAY if both are present.
  CallFunction("DT_INIT", init_func);
  CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
  (略)
  TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
  function();
  TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
  (略)
}

接下来用IDA进行动态调试,这里简单说下,具体细节可参考
http://drops.wooyun.org/mobile/5942
首先将ida的android_server push到模拟器并启动,执行adb forward tcp:23946 tcp:23946进行端口转发 
以debug模式启动目标进程:
adb shell am start -D -n k2015.a2/.Main
IDA远程附加进程,在附加前应设置调试选项选上"Suspend on load/unload library",这样就可以在加载libwbox时中断。附加后,此时程序还在等待状态,且so还未加载。
然后执行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700,程序就开始运行了,并在加载so时中断在了linker中。
此时可以在Module列表里找到libwbox.so对so内的关键函数Java_k2015_a2_Ch_ch进行断点。



如果需要调试init_array的过程,可以在当前中断的linker中开始进行调试,此时如果做了一些反调试手段,在这里都可以看到,并可以进行修改,可以直接动态调试修改,或者修改后可以重打包。



另外提一下,因为dex中存在对签名的验证过程,所以重打包时也需要处理一下dex中的检测逻辑,把关键的if判断修改即可。
对函数Java_k2015_a2_Ch_ch断点后,使程序正常执行,输入并点击Button,将触发断点中断,接下来即可进行单步调试。
简单说下函数逻辑如下:

Java_k2015_a2_Ch_ch () {
  1. 读取/proc/self/status检测TracerPid,如果检测不过则对代码段写操作造成内存异常
  2. 动态解密加密的第二层函数Function2
  3. 跳转到Function2执行,参数为输入字符串
  4. 动态恢复解密的第二层函数Function2
  5. 再次检测检测TracerPid
}
  
其中TracerPid的检测绕过,本来是直接NOP掉就可以了,但是继续调试下去会发现,在第二层、第三层的每层逻辑前后都再次进行检测,并且由于此时的代码是加密的,如果要patch再重打包比较麻烦。所以干脆不管他,直接调,到了这个地方出现异常时,忽略异常,直接右键set ip跳过这行写代码段指令即可。



动态解密的逻辑不用处理,直接在跳转的地方断点后跟进去即可。

进入第二层逻辑

Function2 () {
  1. 对字符串初处理,逐字节对每个字节加上对应的数组下标
  2. 读取/proc/self/status检测TracerPid,如果检测不过则对代码段写操作造成内存异常
  3. 动态解密加密的第二层函数Function3
  4. 跳转到Function3执行,参数为输入字符串
  5. 动态恢复解密的第二层函数Function3
  6. 再次检测检测TracerPid
}

Function3 () {
  1. 对字符串再处理,逐字节对每个字节加上一个固定值
  2. 动态解密加密的第二层函数Function4
  3. 读取/proc/self/status检测TracerPid,如果检测不过则对代码段写操作造成内存异常
  4. 跳转到Function4执行,对处理后的输入字符串进行编码,得到16字节输出字串
  5. 再次检测检测TracerPid
  6. 逐字节比对得到的16字节输出字串和一个固定的16字节字串,相等则验证通过
  7. 动态恢复解密的第二层函数Function4
}

关键逻辑就是Function5了,对应代码如下:
int __fastcall sub_AC38A000(int a1, int a2)
{
  signed int v2; // r5@1
  int *v3; // r12@1
  signed int v4; // r4@1
  unsigned int v5; // r0@1
  int v6; // r7@1
  int v7; // r0@4
  int *v8; // r10@5
  int v9; // r8@5
  int v10; // r8@5
  int v11; // r6@5
  int v12; // r5@5
  int v13; // r0@5
  unsigned int v14; // r4@7
  int v15; // r3@7
  unsigned int v16; // r5@7
  int v17; // r7@7
  unsigned int v18; // r2@7
  int v19; // r1@7
  unsigned int v20; // ST20_4@8
  unsigned __int8 v21; // ST08_1@8
  unsigned int v22; // ST0C_4@8
  int v23; // ST10_4@8
  int v24; // r7@8
  int v25; // r0@8
  unsigned int v26; // r8@8
  unsigned int v27; // r7@8
  int v28; // r0@8
  unsigned int v29; // r7@8
  unsigned int v30; // r5@8
  unsigned int v31; // r0@8
  int v32; // r0@8
  unsigned int v33; // r5@8
  unsigned int v34; // r0@8
  int v35; // r5@8
  unsigned int v36; // r6@8
  unsigned int v37; // r0@8
  unsigned int v38; // r0@8
  int v39; // r5@8
  unsigned int v40; // r0@8
  int v41; // r5@8
  unsigned int v42; // r6@8
  unsigned int v43; // r0@8
  int v44; // r10@8
  unsigned int v45; // r0@8
  int v46; // r5@8
  int v47; // r0@8
  unsigned int v48; // r5@8
  unsigned int v49; // r4@8
  int v50; // r0@8
  int v51; // r2@8
  unsigned int v52; // r0@8
  int v53; // r5@9
  int v54; // r4@9
  int v55; // r4@9
  int v56; // r4@9
  int v57; // r0@9
  int v58; // ST20_4@9
  int v59; // r4@9
  int v60; // r4@9
  int v61; // r4@9
  int v62; // r0@9
  int v63; // ST20_4@9
  int v64; // r4@9
  int v65; // r4@9
  int v66; // r4@9
  int v67; // r0@9
  int v68; // ST24_4@9
  int v69; // r4@9
  int v70; // r4@9
  int v71; // r4@9
  int v72; // r0@9
  int v74; // [sp+4h] [bp-F4h]@1
  int v75; // [sp+14h] [bp-E4h]@8
  int v76; // [sp+18h] [bp-E0h]@8
  int v77; // [sp+1Ch] [bp-DCh]@7
  int v78; // [sp+24h] [bp-D4h]@1
  int v79; // [sp+24h] [bp-D4h]@8
  int v80; // [sp+28h] [bp-D0h]@1
  int v81; // [sp+2Ch] [bp-CCh]@1
  int v82; // [sp+30h] [bp-C8h]@7
  int v83; // [sp+34h] [bp-C4h]@7
  int v84; // [sp+38h] [bp-C0h]@1

  v74 = a2;
  v78 = a1;
  v2 = 0x6BCDC67A;
  v3 = &v80;
  v4 = 0;
  v5 = (unsigned int)&v80 | 4;
  v81 = 0x6BCDC67A;
  *(_DWORD *)(v5 + 4) = 0x6B2B7C9D;
  *(_DWORD *)(v5 + 8) = 0x8DA459B1;
  v6 = 0xAB9D0680;
  v84 = 0xAB9D0680;
  while ( 1 )
  {
    if ( v4 & 3 )
    {
      v7 = (int)&v3[v4];
      v6 ^= v2;
    }
    else
    {
      v8 = v3;
      v9 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v6 >> 15) & 0x1FE));
      v10 = (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v6 >> 7) & 0x1FE)) << 16) & 0xFF0000 | (v9 << 24);
      v11 = v10 | (*(_WORD *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v6) << 8) & 0xFF00;
      v12 = (v11 | *(_BYTE *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v6 >> 23) & 0x1FE))) ^ v2;
      v13 = ((int (*)(void))unk_AC38A958)();
      v3 = v8;
      v6 = v12 ^ *(_DWORD *)(v13 + ((v4 + ((unsigned int)(v4 >> 31) >> 30)) & 0xFFFFFFFC));
      v7 = (int)&v8[v4];
    }
    *(_DWORD *)(v7 + 20) = v6;
    if ( v4 == 39 )
      break;
    v2 = v3[v4++ + 2];
  }
  v80 = 10;
  v77 = _byteswap_ulong(*(_DWORD *)(v78 + 12)) ^ v84;
  v14 = _byteswap_ulong(*(_DWORD *)(v78 + 8)) ^ v83;
  v15 = *(_BYTE *)(v78 + 2);
  v16 = _byteswap_ulong(*(_DWORD *)(v78 + 4)) ^ v82;
  v17 = (int)(v3 + 8);
  v18 = _byteswap_ulong(*(_DWORD *)v78) ^ v81;
  v19 = 0;
  do
  {
    v79 = v17;
    v20 = v18;
    v21 = v14;
    v22 = v16;
    v23 = v19;
    v24 = (v18 >> 23) & 0x1FE;
    v25 = ((int (*)(void))unk_AC38A750)();
    v26 = v16;
    v27 = ((*(_WORD *)(v25 + v24) & 0xFF) << 8) | (*(_WORD *)(v25 + v24) << 16) | *(_WORD *)(v25 + v24) & 0xFF ^ ((unsigned int)*(_WORD *)(v25 + v24) >> 8);
    v28 = ((int (*)(void))unk_AC38A750)() + ((v16 >> 15) & 0x1FE);
    v29 = (*(_WORD *)v28 & 0xFF | (*(_WORD *)v28 << 8) | ((*(_WORD *)v28 ^ ((unsigned int)*(_WORD *)v28 >> 8)) << 24)) ^ v27;
    v30 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + ((v14 >> 7) & 0x1FE));
    v31 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v77);
    v76 = v29 ^ *(_DWORD *)(v79 - 12) ^ (v30 | (((unsigned __int8)v30 ^ (v30 >> 8) | (v30 << 8)) << 16)) ^ (((((unsigned __int8)v31 << 8) | (v31 << 16) | (unsigned __int8)v31 ^ (v31 >> 8)) << 8) | (v31 >> 8));
    v32 = ((int (*)(void))unk_AC38A750)();
    v33 = ((*(_WORD *)(v32 + ((v26 >> 23) & 0x1FE)) & 0xFF) << 8) | (*(_WORD *)(v32 + ((v26 >> 23) & 0x1FE)) << 16) | *(_WORD *)(v32 + ((v26 >> 23) & 0x1FE)) & 0xFF ^ ((unsigned int)*(_WORD *)(v32 + ((v26 >> 23) & 0x1FE)) >> 8);
    v34 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + ((v14 >> 15) & 0x1FE));
    v35 = ((unsigned __int8)v34 | (v34 << 8) | ((v34 ^ (v34 >> 8)) << 24)) ^ v33;
    v36 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 7) & 0x1FE));
    v37 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v20);
    v75 = v35 ^ *(_DWORD *)(v79 - 8) ^ (v36 | (((unsigned __int8)v36 ^ (v36 >> 8) | (v36 << 8)) << 16)) ^ (((((unsigned __int8)v37 << 8) | (v37 << 16) | (unsigned __int8)v37 ^ (v37 >> 8)) << 8) | (v37 >> 8));
    v38 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + ((v14 >> 23) & 0x1FE));
    v39 = ((unsigned __int8)v38 << 8) | (v38 << 16) | (unsigned __int8)v38 ^ (v38 >> 8);
    v40 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 15) & 0x1FE));
    v41 = ((unsigned __int8)v40 | (v40 << 8) | ((v40 ^ (v40 >> 8)) << 24)) ^ v39;
    v42 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + ((v20 >> 7) & 0x1FE));
    v43 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v22);
    v44 = v41 ^ *(_DWORD *)(v79 - 4) ^ (v42 | (((unsigned __int8)v42 ^ (v42 >> 8) | (v42 << 8)) << 16)) ^ (((((unsigned __int8)v43 << 8) | (v43 << 16) | (unsigned __int8)v43 ^ (v43 >> 8)) << 8) | (v43 >> 8));
    v45 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 23) & 0x1FE));
    v46 = ((unsigned __int8)v45 << 8) | (v45 << 16) | (unsigned __int8)v45 ^ (v45 >> 8);
    v47 = ((int (*)(void))unk_AC38A750)();
    v48 = (*(_WORD *)(v47 + ((v20 >> 15) & 0x1FE)) & 0xFF | (*(_WORD *)(v47 + ((v20 >> 15) & 0x1FE)) << 8) | ((*(_WORD *)(v47 + ((v20 >> 15) & 0x1FE)) ^ ((unsigned int)*(_WORD *)(v47 + ((v20 >> 15) & 0x1FE)) >> 8)) << 24)) ^ v46;
    v49 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + ((v22 >> 7) & 0x1FE));
    v50 = ((int (*)(void))unk_AC38A750)();
    v17 = v79 + 16;
    v51 = v49 | (((unsigned __int8)v49 ^ (v49 >> 8) | (v49 << 8)) << 16);
    v14 = v44;
    v52 = *(_WORD *)(v50 + 2 * v21);
    v77 = v48 ^ *(_DWORD *)v79 ^ v51 ^ (((((unsigned __int8)v52 << 8) | (v52 << 16) | (unsigned __int8)v52 ^ (v52 >> 8)) << 8) | (v52 >> 8));
    v16 = v75;
    v19 = v23 + 1;
    v18 = v76;
  }
  while ( v23 + 1 < v80 - 1 );
  v53 = *(_DWORD *)(v79 + 4);
  v54 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v76 >> 23) & 0x1FE));
  v55 = (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v75 >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v54 << 24);
  v56 = v55 | (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v44 >> 7) & 0x1FE)) << 8) & 0xFF00;
  v57 = (v56 | *(_BYTE *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v77)) ^ v53;
  *(_BYTE *)v74 = BYTE3(v57);
  *(_BYTE *)(v74 + 1) = (unsigned int)v57 >> 16;
  *(_BYTE *)(v74 + 2) = BYTE1(v57);
  *(_BYTE *)(v74 + 3) = v57;
  v58 = *(_DWORD *)(v79 + 8);
  v59 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v75 >> 23) & 0x1FE));
  v60 = (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v44 >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v59 << 24);
  v61 = v60 | (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 7) & 0x1FE)) << 8) & 0xFF00;
  v62 = (v61 | *(_BYTE *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v76)) ^ v58;
  *(_BYTE *)(v74 + 4) = BYTE3(v62);
  *(_BYTE *)(v74 + 5) = (unsigned int)v62 >> 16;
  *(_BYTE *)(v74 + 6) = BYTE1(v62);
  *(_BYTE *)(v74 + 7) = v62;
  v63 = *(_DWORD *)(v79 + 12);
  v64 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v44 >> 23) & 0x1FE));
  v65 = (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v64 << 24);
  v66 = v65 | (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v76 >> 7) & 0x1FE)) << 8) & 0xFF00;
  v67 = (v66 | *(_BYTE *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v75)) ^ v63;
  *(_BYTE *)(v74 + 8) = BYTE3(v67);
  *(_BYTE *)(v74 + 9) = (unsigned int)v67 >> 16;
  *(_BYTE *)(v74 + 10) = BYTE1(v67);
  *(_BYTE *)(v74 + 11) = v67;
  v68 = *(_DWORD *)v17;
  v69 = *(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v77 >> 23) & 0x1FE));
  v70 = (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v76 >> 15) & 0x1FE)) << 16) & 0xFF0000 | (v69 << 24);
  v71 = v70 | (*(_WORD *)(((int (*)(void))unk_AC38A750)() + (((unsigned int)v75 >> 7) & 0x1FE)) << 8) & 0xFF00;
  v72 = (v71 | *(_BYTE *)(((int (*)(void))unk_AC38A750)() + 2 * (unsigned __int8)v44)) ^ v68;
  *(_BYTE *)(v74 + 12) = BYTE3(v72);
  *(_BYTE *)(v74 + 13) = (unsigned int)v72 >> 16;
  *(_BYTE *)(v74 + 14) = BYTE1(v72);
  *(_BYTE *)(v74 + 15) = v72;
  return 0;
}
  
对这段代码进行分析可以确定,这是一段标准aes加密算法,不过需要注意大小端的问题,最前面的4个DWORD合起来就是16字节的key了,不过需要注意的是大小端的问题,那标准的aes解密代码解出来是错误的。因此也要对整个过程调试跟踪,判断这个是aes算法并且对算法进行调试卡了较长时间,导致最后没有完成。解密代码写的比较挫,就不贴了。
分析过算法后,对内存中的比对16字节进行解密得到正确的输入如下:


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//