CTF2017 第六题 Ericky-apk
初步分析
- 拿到 apk 直接丢到 jeb,发现 java 部分直接调用了 so 提供的 check 函数
- ida 打开 so,发现花指令:成对指令+跳转,多跟了几个后发现形式是固定的(不是随机产生)
- 这是开始手动过花指令的思路,原谅我放这么大的图
脱花指令
手动过了十几个后,发现的确有挺多,还是写个 IDA 脚本过吧
import idaapi
import idautils
import keystone
text_seg = idaapi.get_segm_by_name('.text')
next_addr = text_seg.startEA
asm = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
while True:
# 找 PUSH.W {R4-R10,LR}
# POP.W {R4-R10,LR}
curr = idaapi.find_binary(next_addr, text_seg.endEA,
"2D E9 F0 47 BD E8 F0 47", 0, SEARCH_DOWN)
if curr == idaapi.BADADDR:
break
b1 = idautils.DecodeInstruction(curr + 8)
# 下一条是 B跳转
if (b1.get_canon_mnem() == "B" and
# 花指令块最后一定是 SUB.W R1, R1, #1
idaapi.get_many_bytes(curr + 0x62, 4).encode("hex") == "a1f10101"):
# 用 ks 汇编跳转指令,ida 内置的不支持该平台
op, cnt = asm.asm("B " + hex(curr + 0x66), curr)
assert cnt == 1
idaapi.patch_byte(curr, op[0])
idaapi.patch_byte(curr + 1, op[1])
# 中间 NOP 调
idaapi.patch_many_bytes(curr + 2, "\x00\xbf" * 50)
next_addr = curr + 0x66
else:
next_addr = curr + 8
后续修复
- check 开头有个无限循环要 NOP,要不 IDA 分析不出
- 有堆栈保护的函数找最近
BLX __stack_chk_fail
设置为函数尾 - 没有堆栈保护的函数找最近的
POP {xxx-xxx, PC}
(xxx-xxx 和函数开头对应,堆栈平衡) 设置为函数尾
分析代码
其实可以不导出 check 函数的,在 JNI_OnLoad 内就一个 RegisterNatives 少了很多看头
signed int __fastcall check(JNIEnv *env, int a2, int src)
{
JNIEnv *env_; // r9@1
void *src_; // r8@1
signed int v5; // r4@3
int v6; // r1@3
int v7; // r5@4
int v8; // r6@4
__int16 *v9; // r4@5
int v10; // r3@5
__int16 v11; // r0@6
int i; // r4@12
const char *src_str; // r0@12
_BYTE *data; // r1@12
signed int result; // r0@15
__int16 v16[32]; // [sp+0h] [bp-54h]@1
int v17; // [sp+44h] [bp-10h]@1
env_ = env;
src_ = (void *)src;
v17 = _stack_chk_guard;
*(_DWORD *)v16 = 0x11000F;
*(_DWORD *)&v16[2] = 0x11FFFF;
*(_DWORD *)&v16[4] = 0xFFFFFFFF;
*(_DWORD *)&v16[6] = 0x10010;
*(_DWORD *)&v16[8] = 0x10FFFF;
*(_DWORD *)&v16[10] = 0xFFFF0003;
*(_DWORD *)&v16[12] = 0xF000F;
*(_DWORD *)&v16[14] = 0;
*(_DWORD *)&v16[16] = 0xFFFFF;
*(_DWORD *)&v16[18] = -0xFFEFu;
*(_DWORD *)&v16[20] = -0xFFEFu;
*(_DWORD *)&v16[22] = 0x10FFFF;
*(_DWORD *)&v16[24] = 0xFFFF0001;
*(_DWORD *)&v16[26] = 0x30010;
*(_DWORD *)&v16[28] = 0x21FFFF;
*(_DWORD *)&v16[30] = 0xA;
if ( try_count >= 6 )
{
while ( 1 )
;
}
v5 = 0;
++try_count;
v16[17] = 'J'; // 其实可能加密的时候参数设置错误才导致密文是明文的
// 就是 v16[15] == 0 导致的
v16[18] = 'y';
v16[19] = 'u';
v16[20] = '3';
v16[21] = 'C';
v6 = 0;
v16[22] = 'J';
v16[23] = 'l';
v16[24] = 'V';
v16[25] = 'D';
v16[26] = 'S';
v16[27] = 'G';
v16[28] = 'Q';
do
{
v7 = v16[v5];
v8 = v5 + 3;
if ( v7 == -1 )
goto LABEL_10;
v9 = &v16[v5];
v10 = v9[1];
if ( (unsigned __int16)v10 == 0xFFFF )
{
dest_buf[2 * v6++] = v16[v7];
LABEL_10:
v5 = v8;
continue;
}
v5 = v9[2];
v11 = v16[v10] - v16[v7];
v16[v10] = v11;
if ( v11 > 0 )
goto LABEL_10;
}
while ( v5 > -1 ); // 前面解码后 dest_buf = J_y_u_3_C_J_l_V_D_S_G_Q_
dest_part2(); // 这里面和上面的算法一样
// 最后 dest_buf = JPyjup3eCyJjlkV6DmSmGHQ=
i = 0;
src_str = (*env_)->GetStringUTFChars(env_, src_, 0);
data = string_encode(src_str);
while ( dest_buf[i] == data[i] )
{
if ( ++i == 24 )
{
result = 1;
goto LABEL_17;
}
}
sub_27C8(dest_buf, data); // 干扰的比较
result = 0;
LABEL_17:
if ( _stack_chk_guard != v17 )
_stack_chk_fail(result, _stack_chk_guard - v17);
return result;
}
_BYTE *__cdecl string_encode(_BYTE *src)
{
_BYTE *src_; // r9@1
_BYTE *key; // r8@1
_BYTE *v3; // r0@1
int v4; // t1@2
unsigned int srclen; // r6@3
_BYTE *dst; // r5@3
_BYTE *dst_; // r0@3
int v8; // t1@4
_BYTE *result; // r0@5
int rc4; // [sp+0h] [bp-418h]@3
int v11; // [sp+408h] [bp-10h]@1
src_ = src;
v11 = _stack_chk_guard;
key = get_key(); // 还是之前的算法返回 199310124853,但只要前面 8 个字节
v3 = src_;
do
v4 = *v3++;
while ( v4 );
srclen = (unsigned int)&v3[~(unsigned int)src_];
dst = malloc((size_t)&v3[~(unsigned int)src_ + 1]);
_aeabi_memclr();
rc4_init(&rc4, 8, key); // 这个点进去看到 256 就知道是 rc4 了(考试要考)
rc4_crypt(&rc4, srclen, src_, dst);
dst_ = dst;
do
v8 = *dst_++;
while ( v8 );
result = base64_encode((int)dst, (int)&dst_[-(signed int)dst - 1]);
if ( _stack_chk_guard != v11 )
_stack_chk_fail(result, _stack_chk_guard - v11);
return result;
}
结果
从代码看出,整个流程就是将输入的字符串 rc4 加密后 base64,与内置的结果对比,python 一句出结果
ARC4.new("19931012").decrypt(base64.b64decode("JPyjup3eCyJjlkV6DmSmGHQ="))
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课