看雪CTF2017第7题WP

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

拿到程序先运行了下(还是比较相信出题的大佬的),在win10系统中运行失败,放xp下运行正常,不过第二次点按钮会出错。此程序是窗口程序,输入错误注册码不提示任何信息。

拖入ida,发现入口代码被更改成错误代码,看导出除了start还有Tls_Callbase_0,原来用了Tls,代码如下:

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
.text:0040C120 push ebp
.text:0040C121 mov ebp, esp
.text:0040C123 sub esp, 10h
.text:0040C126 mov eax, ds:___security_cookie
.text:0040C12B xor eax, ebp
.text:0040C12D mov [ebp+var_4], eax
.text:0040C130 cmp [ebp+arg_4], 3
.text:0040C134 push ebx
.text:0040C135 push esi
.text:0040C136 push edi
.text:0040C137 jnz short loc_40C181
.text:0040C139 and [ebp+var_10], 0
.text:0040C13D pusha
.text:0040C13E mov eax, large fs:18h
.text:0040C144 mov eax, large fs:18h
.text:0040C14A mov eax, [eax+30h] ; peb
.text:0040C14D mov eax, [eax+8] ; base
.text:0040C150 mov ebx, [eax+3Ch]
.text:0040C153 add ebx, eax ; NT header
.text:0040C155 mov ebx, [ebx+28h]
.text:0040C158 add ebx, eax ; entry
.text:0040C15A mov [ebp+var_10], ebx
.text:0040C15D popa
.text:0040C15E lea eax, [ebp+var_C]
.text:0040C161 mov dword ptr [ebp+var_C], 0CC5874EBh
.text:0040C168 mov word ptr [ebp+var_C+4], 75E8h
.text:0040C16E push 6
.text:0040C170 push eax
.text:0040C171 push 0C8h
.text:0040C176 push [ebp+var_10]
.text:0040C179 call xor_410DD6
.text:0040C17E add esp, 10h
.text:0040C181
.text:0040C181 loc_40C181: ; CODE XREF: TlsCallback_0+17
.text:0040C181 mov ecx, [ebp+var_4]
.text:0040C184 pop edi
.text:0040C185 pop esi
.text:0040C186 xor ecx, ebp
.text:0040C188 pop ebx
.text:0040C189 call sub_413F5B
.text:0040C18E mov esp, ebp
.text:0040C190 pop ebp
.text:0040C191 retn 0Ch
.text:0040C191 TlsCallback_0 endp

先通过TEB获取自身两次,然后依次获取PEB、程序基址、PE的NT头、程序入口地址。然后调用xor_410DD6函数将入口入的0xc8个字节与eax指向的硬编码字串循环异或。直接patch。

由于是窗体程序,且没有出错弹窗,直接查看GetDlgItemTextA,只有一处调用,说明此处一定是验证的开始。直接看伪代码:

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
v17 = 144;
sub_413FD7();
v6 = v5;
sub_4156A0(&String, 0, 101);
v16 = 100;
v15 = &String;
v14 = 1000;
GetDlgItemTextA(*(HWND *)(v6 + 4), 1000, &String, 100);
sub_40FD2D((int)&v9, (int)&String); // memcpy?
v17 = 0;
v16 = 5;
v15 = (CHAR *)&v12;
v14 = 426;
v13 = sub_411B30;
xor_410DD6(); // 411B30处与PEDIY循环异或,共0x1AA字节
v12 = v7;
v11 = v7;
sub_40FD07((int)&v11, (int)&v9); // memcpy?
LOBYTE(v17) = 0;
sub_411B30(v4, v11, v12, (int)v13, v14, (int)v15, v16);// !!!
v16 = 5;
v15 = (CHAR *)&v12;
v14 = 426;
v13 = sub_411B30;
xor_410DD6(); // 再异或回去
sub_411825(v4); // 关键
v17 = -1;
sub_410AE3((int)&v9, 1, 0);
return sub_413F81(v17);

程序的传参方式受不了,特别是后面,看得有点蒙。
这里又出现了异或的这个函数,这次参数有
;地址是sub_411B30,异或字节数是426,异或串是PEDIY,此字距也是编码的,不过伪代码不知道为什么没显示出来。

异或完直接调用sub_411B30,然后还原sub_411B30
下面又看了下sub_411B30sub_411825,这时伪代码根本没法看了,完全看不出什么,而且里面还有些堆的申请释放等函数混杂在一起,静态看起来犹如天书。有一点看出来了,没有看到明显的比较或校验,sub_411825中使用了VirtualAlloc

sub_411B30顺便也patch了,而且把异或函数直接ret了,patch完了,程序再也不出错了。

1
2
3
4
5
6
7
8
def xor1(addr,off,length,ss):
for i in range(length):
ch = Byte(addr+off+i)
PatchByte( addr+off+i,ch^ord(ss[i%len(ss)]))
print "patched {:d} bytes in {:0>8x}".format(length,addr+off)
xor1(0x411B30,0,0x1AA,'PEDIY')
xor1(0x414422,0,200,'EB7458CCE875'.decode('hex'))

OD载入程序,没有停在入口,直接运行了,无妨,直接GetDlgItemTextA下断,输入确定后停到了预期位置。大概走了下,sub_411B30函数对输入进行异或还有置换操作,再进行异或移位。此为第一次分析的结果。
输入先进行异或

1
2
3
4
5
6
00411BCD |. /74 0C je short 7-不问少.00411BDB ;输入不为空
00411BCF |> |8031 CC /xor byte ptr ds:[ecx],0xCC ;按字节异或输入
00411BD2 |. |41 |inc ecx
00411BD3 |. |42 |inc edx
00411BD4 |. |3BD7 |cmp edx,edi
00411BD6 |.^|75 F7 \jnz short 7-不问少.00411BCF

异或后的输入进行查表置换,大概过程如代码示例:

1
2
3
4
5
for i in range(len(input)):
idx = table.index(input[i])
for j in xrange(i+1):
idx = (idx+idx/5+5)%64
output[i] = table[idx]

相应的程序代码如下:

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
00411BE9 |. 8B7D D8 mov edi,[local.10] ;输出
00411BEC |. 33C0 xor eax,eax
00411BEE |. 40 inc eax
00411BEF |. 897D EC mov [local.5],edi
00411BF2 |. 2BC7 sub eax,edi
00411BF4 |. C745 E4 05000>mov [local.7],0x5
00411BFB |. 8945 CC mov [local.13],eax
00411BFE |> FF75 E4 /push [local.7]
00411C01 |. 83FB 10 |cmp ebx,0x10
00411C04 |. 8D45 08 |lea eax,[arg.1]
00411C07 |. 0F4345 08 |cmovnb eax,[arg.1] ;异或后的输入
00411C0B |. 03C1 |add eax,ecx
00411C0D |. 50 |push eax
00411C0E |. FF76 04 |push dword ptr ds:[esi+0x4] ;表尾
00411C11 |. FF36 |push dword ptr ds:[esi] ;表头
00411C13 |. E8 34F3FFFF |call 7-不问少.00410F4C ;addr xor_input[i] in table
00411C18 |. 8BC8 |mov ecx,eax
00411C1A |. 83C4 10 |add esp,0x10
00411C1D |. 3B4E 04 |cmp ecx,dword ptr ds:[esi+0x4] ;如果没找到,NEXT
00411C20 |. 74 60 |je short 7-不问少.00411C82
00411C22 |. 8AC1 |mov al,cl
00411C24 |. 2A06 |sub al,byte ptr ds:[esi] ;index
00411C26 |. 8807 |mov byte ptr ds:[edi],al
00411C28 |. 75 05 |jnz short 7-不问少.00411C2F
00411C2A |. 33DB |xor ebx,ebx
00411C2C |. 43 |inc ebx
00411C2D |. EB 03 |jmp short 7-不问少.00411C32
00411C2F |> 0FBED8 |movsx ebx,al
00411C32 |> 837D E8 00 |cmp [local.6],0x0
00411C36 |. 881F |mov byte ptr ds:[edi],bl
00411C38 |. 7C 3E |jl short 7-不问少.00411C78
00411C3A |. 8B45 CC |mov eax,[local.13]
00411C3D |. 03C7 |add eax,edi
00411C3F |. 8945 D4 |mov [local.11],eax
00411C42 |> 33FF |/xor edi,edi
00411C44 |> 41 ||/inc ecx
00411C45 |. 3B4E 04 |||cmp ecx,dword ptr ds:[esi+0x4]
00411C48 |. 75 02 |||jnz short 7-不问少.00411C4C ;检查是否到表尾
00411C4A |. 8B0E |||mov ecx,dword ptr ds:[esi]
00411C4C |> 0FBEC3 |||movsx eax,bl
00411C4F |. 47 |||inc edi
00411C50 |. 99 |||cdq
00411C51 |. F77D E4 |||idiv [local.7]
00411C54 |. 6A 05 |||push 0x5
00411C56 |. 5A |||pop edx
00411C57 |. 03C2 |||add eax,edx
00411C59 |. 3BF8 |||cmp edi,eax ;index=index+index/5+5
00411C5B |.^ 75 E7 ||\jnz short 7-不问少.00411C44
00411C5D |. 8B7D EC ||mov edi,[local.5]
00411C60 |. 8AC1 ||mov al,cl
00411C62 |. 2A06 ||sub al,byte ptr ds:[esi]
00411C64 |. 8807 ||mov byte ptr ds:[edi],al
00411C66 |. 75 05 ||jnz short 7-不问少.00411C6D
00411C68 |. 33DB ||xor ebx,ebx
00411C6A |. 43 ||inc ebx
00411C6B |. EB 03 ||jmp short 7-不问少.00411C70
00411C6D |> 0FBED8 ||movsx ebx,al
00411C70 |> 836D D4 01 ||sub [local.11],0x1
00411C74 |. 881F ||mov byte ptr ds:[edi],bl
00411C76 |.^ 75 CA |\jnz short 7-不问少.00411C42 ;循环输入字节的序号次
00411C78 |> 0FBE0F |movsx ecx,byte ptr ds:[edi]
00411C7B |. 8B06 |mov eax,dword ptr ds:[esi]
00411C7D |. 8A0401 |mov al,byte ptr ds:[ecx+eax]
00411C80 |. 8807 |mov byte ptr ds:[edi],al ;置换出的字节
00411C82 |> 8B4D E8 |mov ecx,[local.6]
00411C85 |. 41 |inc ecx
00411C86 |. 47 |inc edi
00411C87 |. 894D E8 |mov [local.6],ecx
00411C8A |. 897D EC |mov [local.5],edi
00411C8D |. 3B4D D0 |cmp ecx,[local.12]
00411C90 |. 7D 08 |jge short 7-不问少.00411C9A
00411C92 |. 8B5D 1C |mov ebx,[arg.6]
00411C95 |.^ E9 64FFFFFF \jmp 7-不问少.00411BFE 下一个输入字节

转换表为:

1
2
3
4
0x89,0xBC,0x95,0xFC,0xFB,0xBA,0xED,0x9A,0xBB,0xAE,0xFE,0x99,0xA2,0x98,0xB9,0xF9
0x9F,0x84,0x9C,0xFD,0x83,0xAD,0xB6,0xA9,0xA5,0xF5,0x8C,0xA7,0x9E,0x96,0x8A,0xF4
0x85,0xBE,0xA8,0x8F,0x86,0xAF,0x88,0x9D,0x87,0xBF,0xFF,0xA1,0x8B,0x81,0xA0,0xAB
0x8E,0xBD,0xB5,0xAA,0x82,0x94,0xA4,0x8D,0xA3,0xF8,0xB4,0xFA,0x9B,0xA6,0xB8,0x80

我顺便看了下置换表对应的输入字符,字符范围为数字+大小字母+!+@,共64个。

sub_411B30中还调用了sub_410F75,对转换后的结果再进行变换,变换公式为(a^0xcc<<3)|(a^0xcc>>5)

1
2
3
4
5
6
7
8
9
10
11
00410F90 |> /8A0A /mov cl,byte ptr ds:[edx]
00410F92 |. |80F1 CC |xor cl,0xCC
00410F95 |. |8AC1 |mov al,cl
00410F97 |. |C0E1 03 |shl cl,0x3
00410F9A |. |C0F8 05 |sar al,0x5
00410F9D |. |0AC1 |or al,cl
00410F9F |. |8802 |mov byte ptr ds:[edx],al
00410FA1 |. |42 |inc edx
00410FA2 |. |47 |inc edi
00410FA3 |. |3BFE |cmp edi,esi
00410FA5 |.^\75 E9 \jnz short 7-不问少.00410F90

跟到这已经烦躁不行,有很多的堆函数什么的。所以就想直接看看整体过程。

然后直接进了sub_411825,静态的时候已经发现此处有VirtualAlloc,又翻看了下,没有明显弹窗的地方,此函数结束后就直接进入事件流程了。所以推测,弹窗在VirtualAlloc的空间的代码中。只跟了下流程,大概是:如果sub_411975函数返回真,则进行VirtualAlloc,拷贝411A9C处的0x94个字节数据,并循环与原始输入异或,最后执行代码。
只里有个细节,异或完成后,在复制过来的数据偏移0x61和0x8C处,分别加上了两个地址之间的dword偏移量,然后在数据后又加上了最后一次参加异或的输入后一位,后面就直接执行复制过来的数据了。据此猜测这两处是两个call,改的是操作数,也就是偏移,程序代码如下:

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
.text:004118A9 loc_4118A9: ; CODE XREF: sub_411825+5B
.text:004118A9 mov eax, [ecx+34h]
.text:004118AC mov [ebp-1Ch], eax
.text:004118AF add ecx, 24h
.text:004118B2 cmp dword ptr [ecx+14h], 10h
.text:004118B6 jb short loc_4118BA
.text:004118B8 mov ecx, [ecx]
.text:004118BA
.text:004118BA loc_4118BA: ; CODE XREF: sub_411825+91
.text:004118BA mov eax, esi
.text:004118BC xor edx, edx
.text:004118BE div dword ptr [ebp-1Ch]
.text:004118C1 mov al, [edx+ecx]
.text:004118C4 mov ecx, [ebp-14h]
.text:004118C7 xor [ecx], al
.text:004118C9 lea ecx, [edi+60h] ;virtual_addr+0x60
.text:004118CC mov edx, offset sub_411A9C
.text:004118D1 mov eax, edx
.text:004118D3 sub eax, ecx
.text:004118D5 add eax, 60h
.text:004118D8 add [ecx+1], eax ;eax=411A9C-virtual_addr
.text:004118DB lea ecx, [edi+8Bh] ;virtual_addr+0x8b
.text:004118E1 mov eax, [ecx+1]
.text:004118E4 sub eax, ecx
.text:004118E6 add edx, 8Bh
.text:004118EC add eax, edx
.text:004118EE mov [ecx+1], eax
.text:004118F1 call dword ptr [ebp-18h]

看到了这,我想偷个懒,尝试能不能直接通过代码的特点和规律,直接还原代码。先从入口,返回和那两个偏移入手。
原始的代码数据如下:

1
2
3
4
5
6
7
8
9
10
0x00 17 FC 82 19 BE 1C B8 60 21 69 1D 93 30 31 37 4B
0x10 08 BA A3 4F E3 F3 6A 33 41 47 95 EC 21 99 29 BF
0x20 75 C5 53 E8 58 39 4F 6B C1 12 B6 73 CC 31 88 35
0x30 E9 24 A5 F5 75 ED 75 1C 16 6A 1E E6 07 9A A9 36
0x40 A1 61 62 47 5B 39 F4 77 D0 F6 72 AF 3A 0A 6E 56
0x50 12 FA 2B A3 86 31 B8 42 12 1D 03 62 F6 74 DB 09
0x60 B0 2B B9 94 BD F4 AA 67 CC 31 B0 0F 24 01 64 0F
0x70 70 31 67 21 58 C6 5A 9B 7F 32 6E F8 0C 80 34 EC
0x80 69 69 79 32 30 68 BC 06 A8 0A 82 83 6D 53 6E 73
0x90 CA 91 0D A6 00 00 00 00 00 00 00 00 00 00 00 00

首先,入口通常为push ebp mov ebp,esp,对应机器码为55 8B EC,得到Bwn
再看返回,数据最后一位添加的不可能是C3,不在可输入范围,所以A6异或后就为C3C2,所以此处的异或字符为ed
再看两个call,B0 2B B9 94 BD6D 53 6E 73call的对应机器码是E8,所以B0对应的异或字符为X6D对应k,通常偏移是xx xx 00 00xx xx FF FF,所以94 BD对应Kb6E 73对应ns
似乎没法进行下去了。尝试多次也不见效。继续跟踪。这次跟踪重点是进VirtualAlloc的条件,即sub_411975函数。

反复跟踪,终于明白,转换后的输入先转成16进制,再将此转换成4进制,最后与一串常量比较,校验的函数为sub_41612A,主要校验是经过两轮,每轮比较0x20个四进制数。
常量串为:

1
2
3
4
5
02 02 00 03 00 00 02 03 00 01 02 03 02 00 00 02
02 00 02 02 02 03 02 03 01 00 02 02 02 01 02 03
02 02 02 01 02 03 02 03 02 01 00 03 02 02 02 02
02 02 00 03 01 02 02 03 02 00 00 02 02 02 02 02
00 00 00 02 01 00 02 02 02 03 00 02 01 00 00 03

那反算回来不就是key了。动手操作,发现我想到了,最后结果多处有多值。反变换脚本如下:

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
table = [0x89,0xBC,0x95,0xFC,0xFB,0xBA,0xED,0x9A,0xBB,0xAE,0xFE,0x99,0xA2,0x98,0xB9,0xF9,
0x9F,0x84,0x9C,0xFD,0x83,0xAD,0xB6,0xA9,0xA5,0xF5,0x8C,0xA7,0x9E,0x96,0x8A,0xF4,
0x85,0xBE,0xA8,0x8F,0x86,0xAF,0x88,0x9D,0x87,0xBF,0xFF,0xA1,0x8B,0x81,0xA0,0xAB,
0x8E,0xBD,0xB5,0xAA,0x82,0x94,0xA4,0x8D,0xA3,0xF8,0xB4,0xFA,0x9B,0xA6,0xB8,0x80]
check_table = [0x02,0x02,0x00,0x03,0x00,0x00,0x02,0x03,0x00,0x01,0x02,0x03,0x02,0x00,0x00,0x02,
0x02,0x00,0x02,0x02,0x02,0x03,0x02,0x03,0x01,0x00,0x02,0x02,0x02,0x01,0x02,0x03,
0x02,0x02,0x02,0x01,0x02,0x03,0x02,0x03,0x02,0x01,0x00,0x03,0x02,0x02,0x02,0x02,
0x02,0x02,0x00,0x03,0x01,0x02,0x02,0x03,0x02,0x00,0x00,0x02,0x02,0x02,0x02,0x02,
0x00,0x00,0x00,0x02,0x01,0x00,0x02,0x02,0x02,0x03,0x00,0x02,0x01,0x00,0x00,0x03]
l_check = []
for i in range(len(check_table)/4):
tmp = (check_table[4*i]*4+check_table[4*i+1])*16+check_table[4*i+2]*4+check_table[4*i+3]
l_check.append(tmp)
print hex(tmp)+',',
print '\n'
for i in range(len(l_check)):
tmp = (((l_check[i]<<5) +(l_check[i]>>3))&0xff)^0xcc
l_check[i] = tmp
print hex(tmp)+',',
print '\n'
count = 0
result = []
for i in l_check:
idx = table.index(i)
count += 1
for j in xrange(64):
index_t =j
for k in xrange(count):
index_t = (index_t+index_t/5+5)%64
if index_t == idx:
tmp = chr(table[j]^0xcc)
result.append(tmp)
print count,tmp

结果如下,前面是序号,后面为可能值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1 B
2 w j
3 n
4 d s
5 Y l A
6 b t
7 P i
8 H e
9 d c s
10 P i
11 y
12 2 5
13 0 g o
14 1 a
15 7 B 4
16 @ r K
17 J G X
18 9 I D
19 O
20 k F

还是上ida,用上面的xor1脚本函数,慢慢对。
结合前面已经分析的字串和此次的结果,整理下:
前三位是Bwn比较确定;输入应该是20位,那0x60,0x63,0x64处对应的异或数XkB应该是输入的第17、20、1位;0x8B,0x8E,0x8F处对应的异或数kns应该是输入的第20、3、4位。条件符合。所以此时的输入串为Bwns????????????X??k
但是反算结果中还有个别位是唯一解的,第11位,第19位,那输入为Bwns??????y?????X?Ok

再看看返回,猜测返回应该是C3而不是C2,因为我们的输入都大于0x20,一般返回中没有这么大的偏移,所以暂定为C2,对应输入为e,返回的前一个应该是pop ebp,字节码为5D,对应异或码为P,这两个分别对应输入的第7、8位。判定后对照反算结果,条件符合。所以此时已得到的输入为Bwns??Pe??y?????X?Ok

再往pop ebp上面看,上面一条应该是mov esp,ebp,与开头对应,保持栈平衡,机器码为8B E5,异或值为At,对应输入的第5、第6位,这样,开头的指令也比较正常。此时已得到的输入为BwnsAtPe??y?????X?Ok

下面就比较难看了。对照了下反解结果,第10位可选值有Pi,但是从程序的校验码来看,20个字符各不相同,因此第10位为i,所以此时已得到的输入为BwnsAtPe?iy?????X?Ok。已经出现MessageBoxA的偏移。

似乎没有特别明显的了,那从头开始试吧,第9位对应一个操作码,有三个可选值dcs
在函数开头,能影响一个push的值。选择d时,push了一个函数地址,一看是cookie检验的。所以此时已得到的输入为BwnsAtPediy?????X?Ok。出现了Pediy,呵呵。

411B1A出现mov fs:30320000h, ecx,一看偏移有问题,改成00,异或值为20,分别对应输入第12,13位。所以此时已得到的输入为BwnsAtPediy20???X?Ok

411AA6又出现mov eax, fs:4B373100h,同样偏移问题,得到17K
,应对输入的第14、15、16位。此时得到的输入已经成为BwnsAtPediy2017KX?Ok

最后把18都试下,9为正解。
所以正确输入为:BwnsAtPediy2017KX9Ok

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

×

纯属好玩

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

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

文章目录
,