2017-看雪CTF秋季赛-第二题

19-29-11.png

逆向入坑的试炼,跌跌撞撞地做完,也算是快速得积累了一些逆向的经验,很多地方不甚了了,望见谅

看雪的CTF都是一些逆向题,对于最近想要入门逆向的我很具有吸引力,正在自学win32汇编的我想来练手,看到第一题太简单了,故第二题也想一起做了,然后发现事情并没有那么简单(后来事实证明这个比赛不适合入门,2333)
进入正题

分析

PE分析软件告诉我这是一个未加壳的win32 console程序
(巧了,我正在学win32 汇编)

19-30-37.jpg

随直接丢IDA静态分析一下

19-33-41.jpg

主函数很简单,显示打印欢迎界面,然后初始化dword_41B034为2,接着调用三个函数,当三个函数进行完之后,若dword_41B034为0的话,就打印"You get it!\n"
那么先查看调用的第一个函数sub_401050

19-36-49.jpg看似很简单,一开始没注意,先理解为scanf一个字符串进来并返回了该字符串的地址
在看接下来的两个函数sub_401090sub_4010E0

19-39-50.jpg 19-40-00.jpg

看样子只要v1和v0满足这两个判断的要求就能将dword_41B034--两次,就能打印出"You get it!\n"

随后我就没啥好说的了,可以直接写脚本跑出来v0和v1就好了

陷入僵局

然后我看了一下别人的WP,发现事情果然没有那么简单

还是太naive啦

原来这两个条件是无解的

1
2
3
4
5 * (v1 - v0) + v1 == 0x8F503A42
17 * (v1 - v0) + v1 == 0xF3A94883
#两式相减
12*(v1-v0)=1683557953(奇数)

这就非常的尴尬了

还是太naive

再看第一个函数sub_401050的汇编代码

IDA反出来的代码不太利于理解,直接放Ollydbg的反汇编代码,等会反正会用到它

20-04-43.jpg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
sub esp,0xC
push ctf2017_.0041B0AC ; Coded by Fpc.\n\n
call ctf2017_.00413D42
add esp,0x4
push ctf2017_.0041B090 ; Please input your code:
call ctf2017_.00413D42
add esp,0x4
lea eax,dword ptr ss:[esp]
push eax
push ctf2017_.0041B08C ; %s
call ctf2017_.00413D73
lea eax,dword ptr ss:[esp+0x8]
add esp,0x14
retn

首先申请的0xc的栈空间push字符串地址,然后print出来,再将esp+4将栈顶指回原处,然后在地址0040106D处将栈顶push进栈然后将scanf的格式化字符%s的地址也push进栈,留着scanf备用
然后call scanf,执行完之后esp依然指向字符串%s地址处

20-13-17.jpg 20-13-38.jpg

然后esp+0x14即esp+20=esp+12(字符串长)+4(之前push的栈顶地址长度)+4(之前push的格式化字符地址长度)
指回到(0x0018FF40)之前调用函数sub_401050的地址,然后return回去

其实说了那么多就是想告诉读者
可以利用缓冲区溢出构造输入的字符串使得return 的地址是可控的

怎么利用?
当我们正正经经地输入12个字符如112233445566的时候,我们会刚好把申请的12字节缓冲区占满,但是我们如果输入112233445566aaa,此时aaa就会覆盖到return的地址上去,然后return 一下就可以执行对用地址的代码了

如果我们想直接跳到打印"You get it!\n"函数的地址

20-21-43.jpg

可以将字符串后三个字符构造为对应ascl码为0x2f 0x10 0x40的字符串就可以跳转到YouGotit

再次陷入僵局

然而事情还是没有那么简单
比赛规定了咱们只能输入数字和字母,而对应ASCLL码为0x2f 0x10 0x40的字符串并不是都是字母和数字
emmmmmmmm……
果然不适合入门

20-27-21.jpg

好戏才刚刚开始

通过观(kan)察(WP)我们注意到有一段很奇怪的数据段

20-30-52.jpg

然后还是通过观(kan)察(WP)发现这些数据居然都是代码,而且还是加了花的代码
(在IDA中按C键将数据转换成代码)

20-32-44.jpg

emmmmmm…….
这个花加得
果然不适合入门

20-33-51.jpg

该数据段的地址为0x00413131对应的ASCLL码字符串为11A
到此,我们可以看出这到题的思路就是构造缓冲区溢出,跳转到这个加了花的数据段上执行,然后分析得出flag

看到了大佬的WP中的一句话

可以看到很多跳转指令。熟悉的人一眼就明白。这里其实是花指令。而且是最简单的花指令。就是来回跳一跳,吓唬吓唬你而已。相信我。所有的花指令都是纸老虎。复杂的花指令可以用插件记录程序流程分析一下脱花。简单的花指令(就像本题中遇到的这种)根本不用鸟它。完全是战五渣。直接gang正面……

于是我充满信心,开始了我第一次去花之旅
打开Ollydbg开始动态调试
(恩,这是我第一次动态调试)
构造字符串11223344556611A使得咱们能给够跳转到0x00413131处执行

20-40-26.jpg

首先右键选择分析->从模块移除分析

20-42-45.jpg

然后代码就出来了
20-44-26.jpg

emmmmmm…..
这么多的花指令跳转,让我瑟瑟发抖

借助上面大佬的”教诲”壮胆,硬着头皮执行了下去
去花的原则是(当然我也不知道这样对不对,是不是一个好方法)

  • 上一步指令和跳转所依赖的标志位无关的直接跳过
  • 若是遇到依赖的标志位的跳转分支则下断点,选择其中一个跟进,不行的话就重新来,进入另一个分支

历经两天课下时间的动态调试,终于把花给去掉了
整理如下(可能与各位大佬整理的有出入):

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
;将输入的前12个字符分割为3个长度为4字节的子字符串S1,S2,S3
00413131 add esp,-0x10 ;栈顶指向输入的字符串“11223344556611A”
00413150 xor eax,eax ;将zf置零
00413184 mov dword ptr ds:[0x41B034],eax ;将关键判断字符赋值为0
004131BA pop eax ;eax=S1
004131EB mov ecx,eax ;ecx=S1
0041321F pop eax ;eax=S2
00413254 mov ebx,eax ;ebx=S2
00413289 pop eax ;eax=S3
004132B5 mov edx,eax ;edx=S3
004132E2 mov eax,ecx ;eax=S1
00413316 sub eax,ebx ;eax=S1-S2
if(zf==1){
;即S1==S2
00413306 lea edi,dword ptr ds:[ecx+0x6]
00413301 mov eax,0x28741C79
00413306 lea edi,dword ptr ds:[ecx+0x6]
;死循环
}
else{
;zf==0,即S1!=S2
00413349 shl eax,0x2 ;eax=(S1-S2)*4
00413380 add eax,ecx ;eax=(S1-S2)*4+S1
004133B5 add eax,edx ;eax=(S1-S2)*4+S1+S3
004133E9 sub eax,0xEAF917E2 ;BreakPoint,eax=(S1-S2)*4+S1+S3-0xEAF917E2
if(zf==0)
{
;即(S1-S2)*4+S1+S3!=0xEAF917E2
00413B1E pop eax ; ctf2017_.00413131
00413B4E xor eax,0x1210E
00413B1E pop eax ; ctf2017_.00413131
00413B4E xor eax,0x1210E
00413B83 xor eax,dword ptr ds:[0x41B034]
0040103F push ctf2017_.0041B038 ; ASCII "Bad register-code, keep trying.\n"
;打印上述字符并退出
}
else
{
;zf==1,eax=0
00413455 add eax,ecx ;eax=S1
00413489 sub eax,ebx ;eax=S1-S2
004134BF mov ebx,eax ;ebx=S1-S2
004134F3 shl eax,1 ;BreakPoint,eax=(S1-S2)*2
00413525 add eax,ebx ;eax=(S1-S2)*2+S1-S2
00413559 add eax,ecx ;eax=(S1-S2)*2+S1-S2+S1
0041358F mov ecx,eax ;ecx=(S1-S2)*2+S1-S2+S1
004135C3 add eax,edx ;eax=(S1-S2)*2+S1-S2+S1+S3
004135F7 sub eax,0xE8F508C8 ;BreakPoint,eax=(S1-S2)*2+S1-S2+S1+S3-0xE8F508C8
;jl{
00413B1E pop eax ; ctf2017_.00413131
00413B4E xor eax,0x1210E
00413B83 xor eax,dword ptr ds:[0x41B034]
0040103F push ctf2017_.0041B038 ; ASCII "Bad register-code, keep trying.\n"
打印上述字符并退出
}
;jge 且当zf=0
{
00413B1E pop eax ; ctf2017_.00413131
00413B4E xor eax,0x1210E
00413B83 xor eax,dword ptr ds:[0x41B034]
0040103F push ctf2017_.0041B038 ; ASCII "Bad register-code, keep trying.\n"
;打印上述字符并退出
}
;jge 且当zf=1
{
;即(S1-S2)*2+S1-S2+S1+S3=0xE8F508C8
0041365D mov eax,ecx ;eax=(S1-S2)*2+S1-S2+S1
004136A7 sub eax,edx ;BreakPoint,eax=(S1-S2)*2+S1-S2+S1-S3
004136D8 sub eax,0xC0A3C68 ;eax=(S1-S2)*2+S1-S2+S1-S3-0xC0A3C68
;je
;即(S1-S2)*2+S1-S2+S1-S3=0xC0A3C68
00413777 xor eax,0x8101
004137A9 mov edi,eax
004137E2 xor eax,eax
00413817 stos dword ptr es:[edi]
}
}

跳了无数次坑
这可真是个细心活

最后根据代码可以整理得三个方程

1
2
3
4
5
6
7
8
(S1-S2)*4+S1+S3=0xEAF917E2
(S1-S2)*2+S1-S2+S1+S3=0xE8F508C8
(S1-S2)*2+S1-S2+S1-S3=0xC0A3C68
#整理得
5*S1-4*S2+S3=0xEAF917E2
4*S1-3*S2+S3=0xE8F508C8
4*S1-3*S2-S3=0xC0A3C68

三条方程三个未知数,解得

1
2
3
S1="tsuJ"
S2="rof0"
S3="nuf0"

由于是小端存储,所以这12个字符应该是Just0for0fun

最后输入Just0for0fun11A即可

20-57-36.jpg

历经磨难,终于写了出来

这题可以说是非常的有意思了

给出题者点个赞

最后说一句,真的不适合入门!

感谢阅读



----- 感谢阅读 -----