本篇文章参考了:http://www.cnblogs.com/remlostime/archive/2011/05/21/2052708.html大神的文章,有时候没思路了会来看一下,但是保证本文的每个阶段都是自己独立思考后总结写出来的。
Phase_1
对于phase1,我们只要关注一下红色框两行的代码,分别是将内存0x8049678的处的字符串A和ebp+0x8处的字符串B作为参数来调用strings_not_equal子程序,那么这里的逻辑也很明了——要想知道要求我们输入的字符串,只要查看内存0x8049678处的字符串就可以了:
读到phase_4终于想明白为啥DWORD PTR [ebp+0x8]代表的是子程序接受的参数了,子程序调用的模型如下图所示:
ebp+0x4是跨越ebp的4个字节,再加4是跨越返回地址的4个单元,就是这样
Phase_2
首先我们还是关注红色的部分,phase_2将eax和[ebp+0x8]这两处的值作为参数传递给<read_six_numbers>子程序,在这里:push eax是因为phase_2在子程序调用后还要用到eax的值,而调用的子程序恰好也要用到eax的值,为了维护eax,caller选择将其压栈,这也是保存eax的一种方法;而[ebp+0x8]处的值则是程序从键盘上读取的我们的输入,作为调用phase_2的参数,而对于这个参数的安放,是整个phase_2最关键的地方!
这两行是read_six_numbers子程序内部的两行代码,首先ebp+0x8处是传递给该子程序的参数,即phase_2的ebp+0x8,即我们键盘输入的6个数。然后该proc用sscanf从该输入中读取数据——放到哪里呢?
没错,就是phase_2子程序的绿色一行,如果不加以严谨的测试,我们只通过看代码,黄色的一行设置esi的指定位置,以及后续的循环过程,都能帮助我们推测出read_six_numbers的sscanf函数就是将输入的字符串读取到了[ebp-0x20]这个位置
如果最科学的做法应该是这样的:我们在绿线的那行设置断点,然后执行程序,然后对第2个函数的参数输入23 x x x x x,x是随机一个整数(不超过int范围),然后我们再观察[ebp-0x20]处的DWORD,就会发现这个值是23——这就证明了我们上面的猜测~
Phase_3
首先看刚开始的红色部分,都是作为调用sscanf函数的参数来压栈的,首先第一个红框不难看出是用来存储参数,那么那个内存地址里面是啥呢?观察一下就可以明了:
没错,就是传递sscanf函数所需要的字符串格式的参数,然后我们从[ebp+0x8]所指向的内存中来读取我们所需要的内容到相应的位置即可
还有一点需要注意,就是绿色框的switch选择语句,我们可以通过相应的指令显示出该内存地址后面的所有16进制内容:
那么到底该选择哪一个呢,我们再看下面的黄色语句,他告诉我们其中一个参数的值是必须要小于等于5的,也就是eax*4<=20,即我们最多可以选择到0x8048bcc这条内存地址,然后从第一个地址到底6个地址任选一条来运算,求得的答案都可以通过phase_3,后面的算数过程没什么难度,就不细说了。
Phase_4:
同phase_3查看内存0x804996b处的值,可以看到是“%d”,可见sscanf是input读取一个数字的,这点通过后面的返回值eax也可以看出来。
黄色框说明我们输入的这个数一定是个正数。
绿色的框告诉我们func4(input)的返回值是144D(0x90),思路也很显而易见,我们关注func4函数的返回值即可
这是func4函数的disassembly-code,首先棕色的框可以看出每次调用子程序用ebx来保存参数的值
然后就是判断参数的值,如果大于1,就调转到偏移地址为20的地方,反之则跳转到50偏移地址处,中间具体的运算过程在下面给出:
func(1):1
return 1
func(2):2
esi = 0
esi += func(1) = 0+1 = 1
ebx = 2-2 = 0 NO
return esi+1 = 1+1 = 2
func(3):3
esi = 0
esi += func(2) = 0+2 = 2
ebx = 3-2 = 1 NO
return esi+1 = 2+1 = 3
func(4):5
esi = 0
esi += func(3) = 0+3 = 3
ebx = 4-2 = 2 OK
esi += func(2-1) = 3+1 = 4
ebx = 2-2 = 0 NO
return esi+1 = 4+1 = 5
func(5):8
esi = 0
esi += func(4) = 0+5 = 5
ebx = 5-2 = 3 OK
esi += func(3-1) = 5+2 = 7
ebx = 3-2 = 1 NO
return esi+1 = 7+1 = 8
func(6):13
esi = 0
esi += func(5) = 0+8 = 8
ebx = 6-2 = 4 OK
esi += func(4-1) = 8+3 = 11
ebx = 4-2 = 2 OK
esi += func(2-1) = 11+1 = 12
ebx = 2-2 = 0 NO
return esi+1 = 12+1 = 13
func(7):21
esi = 0
esi += func(6) = 0+13 = 13
ebx = 7-2 = 5 OK
esi += func(5-1) = 13+5 = 18
ebx = 5-2 = 3 OK
esi += func(3-1) = 18+2 = 20
ebx = 3-2 = 1 NO
return esi+1 = 20+1 = 21
func(8):34
esi = 0
esi += func(7) = 0+21 = 21
ebx = 8-2 = 6 OK
esi += func(6-1) = 21+8 = 29
ebx = 6-2 = 4 OK
esi += func(4-1) = 29+3 = 32
ebx = 4-2 = 2 OK
esi += func(2-1) = 32+1 = 33
ebx = 2-2 = 0 NO
return esi+1 = 33+1 = 34
func(9):55
esi = 0
esi += func(8) = 0+34 = 34
ebx = 9-2 = 7 OK
esi += func(7-1) = 34+13 = 47
ebx = 7-2 = 5 OK
esi += func(5-1) = 47+5 = 52
ebx = 5-2 = 3 OK
esi += func(3-1) = 52+2 = 54
ebx = 3-2 = 1 NO
return esi+1 = 54+1 = 55
func(10):11
esi = 0
esi += func(9) = 0+55 = 55
ebx = 10-2 = 8 OK
esi += func(8-1) = 55+21 = 76
ebx = 8-2 = 6 OK
esi += func(6-1) = 76+8 = 84
ebx = 6-2 = 4 OK
esi += func(4-1) = 84+3 = 87
ebx = 4-2 = 2 OK
esi += func(2-1) = 87+1 = 88
ebx = 2-2 = 0 NO
return esi+1 = 88+1 = 89
func(11):
esi = 0
esi += func(10) = 0+89 = 89
ebx = 11-2 = 9 OK
esi += func(9-1) = 89+34 = 123
ebx = 9-2 = 7 OK
esi += func(7-1) = 123+13 = 136
ebx = 7-2 = 5 OK
esi += func(5-1) = 136+5 = 141
ebx = 5-2 = 3 OK
esi += func(3-1) = 141+2 = 143
ebx = 3-2 = 1 NO
return esi+1 = 143+1 = 144
一定要注意每次调用结束后esi的值都还要变回调用前的,也就是一直是0,这儿被坑了好久。。
Phase_5:
前面没什么好说的,绿色部分是循环体,作用是把phase_5的参数经过变换后转移到ecx所指向的地址中,edx作为循环变量从1到6
很容易得知题目给我们的提示是转换后的字符串等于内存0x80496c2起始的内容,我们查看这个内容
在这里转换后的字符没什么用,转换后的ASCII码是我们破解这层炸弹的关键,而每个ASCII码的获得如图一红色方框所示:
先将原操作数的ASCII码存到eax中,然后保留其后四位,再将后四位当成偏移地址来寻找相应的新的ASCII码
根据0x80496c2中的6个数字,我们可以反过来分别推出原来6个char的ASCII码的后四位,然后我们可以测出abcdef的ASCII码的后四位,发现是从0001开始增长的,这样可以列出a-z的后四位ASCII码,然后将相应的字符带入即可(此题有4种答案都是正确的)
转换后 al 转换前
116/t 1101 m
105/i 0000 p
116/t 1101 m
97/a 0101 e/u
110/n 1011 k
115/s 0001 a/q
Phase_6:
这题是对我智商的侮辱,我拒绝贴出来。
secrete_phase:
这个炸弹折腾了能有一个周,今天下午把第6个拆出来了,感觉对汇编的兴趣并不是太大,就没再做这个,不过我参考的文章里有对secrete_phase的详解释,大家可以去看。