看雪CTF2017第6题WP

这是看雪CTF2017比赛的第6题。题目地址:传送门

APK直接拖入JEB,发现函数名混淆了,很长的标点什么的,还有就是字串加密了,看起来MainActivity有个文本框和一个按钮,点击按钮后,将文本作为参数调用utils.checkutils加载了一个so库,check为native函数。只不过so库名字加密了,不用管,因为看了下只有一个so库。

ida加载so文件,发现了check函数,发现好多花,JNI_Onloadcheck根本没法看。看了下字串,发现有几个可疑的字串,就研究了下,这几个字串都是通过unhex加异或解码的,大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
5DDF101B20158266 0x2A, 0xBE, 0x79, 0x6F, 0x50, 0x7C, 0xE6, 0x66 waitpid
93581A05791938828C421C 0xC3, 0xC, 0x48, 0x44, 0x3A, 0x5C, 0x67, 0xC1 PTRACE_CONT
5229C2BD6437C15E526793F96430D0595026 0x31, 0x48, 0xB6, 0x9D, 0x4B, 0x47, 0xB3, 0x31 cat /proc/%d/wchan
13B82D685939526D 0x61, 0xB8, 0x2D, 0x68, 0x59, 0x39, 0x52, 0x6D r
1410CEB874A13B500B358D 0x67, 0x69, 0xBD, 0xE7, 0x11, 0xD1, 0x54, 0x3C sys_epoll\0
D79D76C575AC5DAFC8B835 0xA4, 0xE4, 5, 0x9A, 0x10, 0xDC, 0x32, 0xC3 sys_epoll\0
71897FAB1C51CCD475927D964F 1, 0xFD, 0xD, 0xCA, 0x7F, 0x34, 0x93, 0xA7 ptrace_stop\0
304B53EAF184220D345051D7A2 0x40, 0x3F, 0x21, 0x8B, 0x92, 0xE1, 0x7D, 0x7E
4B1D178C542EF29A1042118047 0x64,0x6D,0x65,0xE3,0x37, 1,0x9C,0xFF /proc/net/tcp
221DD6112A3D27F7 0x50,0x1D,0xD6,0x11,0x2A,0x3D,0x27,0xF7 r
973C7EAA8C8B94DD9D390AA2FD 0xA7, 0xC,0x4E,0x9A,0xBC,0xBB,0xA4,0xED 00000000:5D8A
12FA6FDF29F96CD212E77CC039 0x3D,0x8A,0x1D,0xB0,0x4A,0xD6,0x49,0xB6 /proc/%d/maps
6C600DAFA9FE35C66C7D1EB0B9 0x43,0x10,0x7F,0xC0,0xCA,0xD1,0x10,0xA2 /proc/%d/maps

前面是原始字串,后面为异或表,最后是解码的字串。
查看调用位置,发现几个类似反调试的函数,一个是通过ptrace反附加,一个是通过检查ida的远程调试端口反调试,一个是通过inotify监控maps文件反调试。我尝试进行了更改,前面为偏移,后面为更改情况:

1
2
3
4016 0446 -> 0024
41F2 0120 -> 0020
45AA 50BB -> 50B3

在异或表前后还有很多类似的表,据此发现很多的unhex_xor的操作函数,数量很大。但并非找到调用的地方,反调试函数也没有找到调用的地方。陷入僵局。
无法继续看混淆,发现规律,形式单一,并且中间似乎没有和真实指令有关的东西,所以粗暴替换成NOP,加上前面的unhex_xor一起搞了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def scan(patt):
pattern = patt
addr = MinEA()
l = []
for i in range(0,MaxEA()):
addr = idc.FindBinary(addr, SEARCH_DOWN|SEARCH_NEXT, pattern)
if addr != idc.BADADDR:
l.append(addr)
return l
def makefunc(l):
for addr in l:
MakeFunction(addr,BADADDR)
oldname = GetFunctionName(addr)
MakeName(addr,'unhex_xor_'+hex(addr)[2:-1])
print 'make function at:'+hex(addr)
def junk(l):
for addr in l:
for i in xrange(43):
PatchWord(addr+i*2,0xbf00)
l = scan('2D E9 F0 47 03 AF 04 46 65 1E')
makefunc(l)
l = scan('13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47 FF E7 B1 B5 82 B0 12 46 02 B0 00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40')
junk(l)

搞完查看伪代码,发现似乎path有问题,看代码:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
int __fastcall check(JNIEnv *a1, int a2, int a3)
{
JNIEnv *v3; // r9@1
void *v4; // r8@1
signed int v5; // r4@3
int v6; // r1@3
int v7; // r5@4
int v8; // r6@4
_WORD *v9; // r4@5
int v10; // r3@5
__int16 v11; // r0@6
int count; // r4@12
const char *input; // r0@12
char *v14; // r0@12
signed int v15; // r0@15
_WORD v16[32]; // [sp+0h] [bp-54h]@1
int v17; // [sp+40h] [bp-14h]@2
int v18; // [sp+44h] [bp-10h]@1
_BYTE savedregs[20]; // [sp+54h] [bp+0h]@2
v3 = a1;
v4 = (void *)a3;
v18 = _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] = 0xFFFF0011;
*(_DWORD *)&v16[20] = 0xFFFF0011;
*(_DWORD *)&v16[22] = 0x10FFFF;
*(_DWORD *)&v16[24] = 0xFFFF0001;
*(_DWORD *)&v16[26] = 0x30010;
*(_DWORD *)&v16[28] = 0x21FFFF;
*(_DWORD *)&v16[30] = 0xA;
if ( inputcount_20040 >= 6 )
((void (__cdecl *)(int, signed int, unsigned int, int *, signed int, signed int, signed int, _BYTE *, int))loop_286C)(
inputcount_20040,
0x10FFFF,
0xFFFF0001,
&v17,
0x30010,
0x21FFFF,
0xA,
savedregs,
a3);
++inputcount_20040;
v16[1] = 0x4A; // Jyu3CJlVDSGQ
v16[2] = 0x79;
v16[3] = 0x75;
v16[4] = 0x33;
v16[5] = 0x43;
v16[6] = 0x4A;
v16[7] = 0x6C;
v16[8] = 0x56;
v16[9] = 0x44;
v16[10] = 0x53;
v16[11] = 0x47;
v16[12] = 0x51;
v5 = 0;
v6 = 0;
while ( 1 )
{
v7 = v16[v5];
v8 = v5 + 3;
if ( v7 != 0xFFFFFFFF )
{
v9 = &v16[v5];
v10 = v9[1];
if ( (unsigned __int16)v10 == 0xFFFF )
{
check_20020[2 * v6++] = v16[v7];
}
else
{
v5 = v9[2];
v11 = v16[v10] - v16[v7];
v16[v10] = v11;
if ( v11 <= 0 )
goto LABEL_11;
}
}
v5 = v8;
LABEL_11:
if ( v5 <= -1 )
{
sub_19FC();
count = 0;
input = (*v3)->GetStringUTFChars(v3, v4, 0);
sub_19DA8(input); // important
while ( check_20020[count] == (unsigned __int8)v14[count] )
{
if ( ++count == 24 )
{
v15 = 1;
goto LABEL_17;
}
}
sub_27C8(check_20020, v14);
v15 = 0;
LABEL_17:
if ( _stack_chk_guard == *(_DWORD *)&v16[18] )
JUMPOUT(__CS__, v18);
_stack_chk_fail(v15);
}
}
}

变量名不知怎么乱了。check_20020[count] == (unsigned __int8)v14[count]似乎就是最终的比较,24位,check_20020的赋值在此处和另一个函数sub_19FC中,分别写check_20020的奇数位和偶数位。似乎sub_19DA8为加密函数,最后来验证。

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
void __fastcall sub_19DA8(_BYTE *a1)
{
int v1; // r9@1
int v2; // r0@1
int v3; // r8@1
_BYTE *v4; // r0@1
int v5; // t1@2
unsigned int v6; // r6@3
int v7; // r5@3
_BYTE *v8; // r0@3
int v9; // t1@4
int v10; // [sp+0h] [bp-418h]@3
int v11; // [sp+408h] [bp-10h]@1
v1 = (int)a1;
v11 = _stack_chk_guard;
sub_1A31C(); // 密钥
v3 = v2;
v4 = (_BYTE *)v1;
do
v5 = *v4++;
while ( v5 );
v6 = (unsigned int)&v4[~v1];
v7 = (int)malloc((size_t)&v4[~v1 + 1]);
_aeabi_memclr();
sub_55E4((int)&v10, 8, v3);
wow_467E(&v10, v6, v1, v7);
v8 = (_BYTE *)v7;
do
v9 = *v8++;
while ( v9 );
enbase_5AFC(v7, (int)&v8[-v7 - 1]); // base
JUMPOUT(_stack_chk_guard, v11, j___stack_chk_fail);
}

经过时间的研究与猜测,sub_1A31C()check_20020取值手法一样,怀疑是密钥,sub_55E4像是密钥扩展什么的,里面开头有这么一段:

1
2
3
4
5
6
do
{
*(_DWORD *)(v3 + 4 * v4) = v4;
++v4;
}
while ( v4 != 256 );

再加上后面及加密部分的难懂的代码,推理加密算法为RC4。
enbase_5AFC为base64编码,64个变换表太显眼了,还有个解码的函数,似乎没有地方调用。
分析加猜测,算法出来了,现在就是验证及校验码的拼接顺序,密钥的取值顺序,其值都是硬编码的明文,目前能得到校验码的可能相关值为Jyu3CJlVDSGQPjpeyjk6mmH=,密钥的相关值为199310124853!,通过以上代码我没有弄出来准确值,代码解析还是有问题的。只好试着动态,在关键点下断找数据。

重新打开so文件,只patch反调试的地方,使用jarjarsigner进行文件替换和签名,push到模拟器进行ida动态调试,意外出现了,在正常指令的地方如MOV R3,R3出现非法指令的异常,又费了很长时间,无法解决了似乎,另外arm虚拟机太慢了,坑死了。带着遗憾过了最后一晚上的攻击机会。最后听说没有反调试,可以直接调试,没时间试,不知道什么情况。

眼看情况快到了,一直在想参数的确定问题,24位base64比较值,两串相关字串就是24位,有可能是直接奇偶拼接,没有更换顺序。而key只有8位,现在是13位,又是怎么取的呢,难道和前面一样,按顺序取?
最后找朋友帮忙动态确定猜想,果然。

1
2
check_20020 = 'JPyjup3eCyJjlkV6DmSmGHQ='
key = `19931012`

懒得写py脚本了,用软件解了得到:madebyericky94528

说明:本文首发于看雪论坛。

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
,