bomb实验是一个比较恶心的实验,因为你要被汇编玩死的。我刚开始做的时候对汇编还不怎么了解,可以说我的汇编知识全是从这个bomb实验学的。现在过去好久了,现在实验课要验收,写篇博客来巩固一下。希望能帮到有需要的人。实验文件如下:
实验介绍我简单说一下:就是一共有七个关卡(包含一个隐藏的关卡),每一关都有特定的通关密码,你需要找出这个通关密码,通关运行./bomb,输入你找到的密码,正确则到下一关,否则炸弹爆炸,讲真一开始我以为它会把我的电脑弄爆炸,吓死都有,原来就是输出个字符串。(小声比比)废话不多说,开始的破解。
1. Phase_1
原理设计:
(1)通过反汇编指令得到汇编代码并导入到txt文档中:
Objdump -d bomb > 1.txt
(2)分析通关条件:
首先要知道在整个过程中栈的变化,那么要了解栈的运行原理;其次要看下寄存器相关信息,因此需要了解相关gdb调试命令,比如查看某一地址的内容:x/s 0x80adce;要找到通关的条件,首先找到它要你输入的参数,再分析如何避开它调用引爆函数,就能找到通关条件。如何避开一个又一个炸弹呢?引爆炸弹必须要有条件,注意避开这些条件即可防止炸弹引爆了。
实现与分析:
汇编代码如下:
08048f61 :
8048f61: 55 push %ebp
8048f62: 89 e5 mov %esp,%ebp
8048f64: 83 ec 18 sub $0x18,%esp //这里对esp-24,开栈操作
8048f67: c7 44 24 04 5c a1 04 movl $0x804a15c,0x4(%esp) // 将这个地址的内容存放到esp+4的位置
8048f6e: 08
8048f6f: 8b 45 08 mov 0x8(%ebp),%eax //esp+8,从调用函数处取第一个参数放到eax寄存器中
8048f72: 89 04 24 mov %eax,(%esp) //传送到esp寄存器
8048f75: e8 31 00 00 00 call 8048fab //入口函数, 判断字符串是否相等(判断esp+4和esp+8的字符串是否相等)
8048f7a: 85 c0 test %eax,%eax //当两个字符串相等时返回eax=0
8048f7c: 74 05 je 8048f83 //是否引爆炸弹的条件(eax=0跳到leave,否则引爆炸弹,因此通关的条件是eax=0)
8048f7e: e8 4e 01 00 00 call 80490d1
8048f83: c9 leave //程序结束
8048f84: c3 ret
8048f85: 90 nop
8048f86: 90 nop
8048f87: 90 nop
8048f88: 90 nop
8048f89: 90 nop
8048f8a: 90 nop
8048f8b: 90 nop
8048f8c: 90 nop
8048f8d: 90 nop
8048f8e: 90 nop
8048f8f: 90 nop
画一下那个栈帧的情况:
思路分析
根据以上分析我们可知,程序先将我们输入的参数存放到ebp+8的位置,接着传送到esp中,然后函数在0x804a15c这个地址取值,放到esp+8中。最后传送到eax进行比较,如果相等,返回eax=0,跳到leave结束,不相等则返回1,同时调用引爆炸弹的函数。因此我们只要查看0x804a15c这个地址的内容即可通关!用GDB调试工具进行查看:
第一关通关密码为:
We have to stand with our North Korean allies.
通关成功!
2.Phase_2
原理设计
先明确题目输入的参数类型,即输入整数还是字符串等等。确定输入的参数后,再分析它进行了哪些操作,查看关键地址内容,同时要画出栈帧的变化情况,那么需要用GDB工具查看寄存器的值;其次是分析通关的条件,也就是在输入参数后它进行了哪些操作,一步一步推下来找到通关密码。避开炸弹,只要引爆炸弹的条件不成立即可。
实现与分析:
汇编代码如下:
08048d6a :
8048d6a: 55 push %ebp
8048d6b: 89 e5 mov %esp,%ebp
8048d6d: 56 push %esi
8048d6e: 53 push %ebx //esi和ebx为调用者保存寄存器,因为后面的循环用到了者两个寄存器,因此要压栈保存
8048d6f: 83 ec 30 sub $0x30,%esp //esp-48开栈操作
8048d72: 8d 45 e0 lea -0x20(%ebp),%eax
8048d75: 89 44 24 04 mov %eax,0x4(%esp)//以上两句将ebp-32处的地址借助eax传给esp+4处
8048d79: 8b 45 08 mov 0x8(%ebp),%eax
8048d7c: 89 04 24 mov %eax,(%esp)//以上两句将ebp+8处的地址传给esp
8048d7f: e8 87 03 00 00 call 804910b //输入6个数
8048d84: 83 7d e0 00 cmpl $0x0,-0x20(%ebp)
8048d88: 75 06 jne 8048d90 //引爆炸弹的条件,不为0则引爆炸弹。因此ebp-32处要为0,从前面看,这是存放输入参数的位置
8048d8a: 83 7d e4 01 cmpl $0x1,-0x1c(%ebp)
8048d8e: 74 05 je 8048d95 //引爆炸弹的条件,ebp-28处要为1,否则引爆炸弹,因此第二个参数可知为1
8048d90: e8 3c 03 00 00 call 80490d1
8048d95: 8d 5d e8 lea -0x18(%ebp),%ebx//ebp-24传给ebx(调用者帧保存)
8048d98: 8d 75 f8 lea -0x8(%ebp),%esi//ebp-8传给esi(调用者帧保存)以上两句是数据的初始化,ebp-24是第三个数的地址,而ebp-8是第七个数(理论上没有第七个数),但这样设置是为了作判断(猜想是结束循环,因为就六个数最多到ebp-12,如果读到ebp-8了那肯定读完了,那么要结束循环。)
8048d9b: 8b 43 fc mov -0x4(%ebx),%eax//ebx-4传给eax
8048d9e: 03 43 f8 add -0x8(%ebx),%eax//ebx-8+eax(也就是ebx前后两个数相加)
8048da1: 39 03 cmp %eax,(%ebx)//此时的ebx应该是ebx-24,也就是第三个数,此时的eax是两个数相加后的值(第一次循环的时候)
8048da3: 74 05 je 8048daa //相等则跳过炸弹
8048da5: e8 27 03 00 00 call 80490d1
8048daa: 83 c3 04 add $0x4,%ebx//ebx+=4,取值操作
8048dad: 39 f3 cmp %esi,%ebx //此时的ebx是ebx-20了,第四个数
8048daf: 75 ea jne 8048d9b //上数三句是作用是:如果我们的输入满足条件,那么继续循环上移动4位,也就是继续求两个数之和赋给第三个数。
8048db1: 83 c4 30 add $0x30,%esp
8048db4: 5b pop %ebx
8048db5: 5e pop %esi
8048db6: 5d pop %ebp
8048db7: c3 ret
栈图如下:
思路分析
到这里题目意思已经很明了,就是一个斐波那契数列的前六项。
结论
第二关的通关密码为:0 1 1 2 3 5
3.Phase_3
原理设计
先找到要输入的参数,通过GDB调试工具可知第三关要输入两个整数。要找到这两个数应该不难,画出栈帧的变化情况以及查看关键地址的内容。找到这两个整数即可找到通关条件,要避开炸弹只需让调用炸弹的条件不成立即可。
实现与分析:
汇编代码如下:
08048ea1 :
8048ea1: 55 push %ebp
8048ea2: 89 e5 mov %esp,%ebp
8048ea4: 83 ec 28 sub $0x28,%esp//esp-40
8048ea7: 8d 45 f0 lea -0x10(%ebp),%eax//ebp-16赋给eax
8048eaa: 89 44 24 0c mov %eax,0xc(%esp)//将ebp-16赋给esp+12
8048eae: 8d 45 f4 lea -0xc(%ebp),%eax//ebp-12赋给eax
8048eb1: 89 44 24 08 mov %eax,0x8(%esp)//ebp-12赋给esp+8
8048eb5: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp)//GDB查看发现是两个%d%d,说明要输入的参数是两个整数。
8048ebc: 08
8048ebd: 8b 45 08 mov 0x8(%ebp),%eax
8048ec0: 89 04 24 mov %eax,(%esp)//ebp+8赋给esp
8048ec3: e8 78 f9 ff ff call 8048840 <__isoc99_sscanf@plt>//读入函数,将你输入的参数按指定格式读到相应的地址
8048ec8: 83 f8 01 cmp $0x1,%eax //eax接受ssacnf函数的返回值,说明sscanf函数的返回值是参数个数,如果大于1跳过炸弹,否则引爆炸弹。
8048ecb: 7f 05 jg 8048ed2 //jg是大于
8048ecd: e8 ff 01 00 00 call 80490d1
8048ed2: 83 7d f4 07 cmpl $0x7,-0xc(%ebp)
8048ed6: 77 6b ja 8048f43 //以上两句说明ebp-12不能超过7,否则就引爆炸弹。
8048ed8: 8b 45 f4 mov -0xc(%ebp),%eax //ebp-12赋给eax,观察下面的代码得知对ebp-12的值有不同的操作,而且very多jmp,猜想这是一个switch跳转表。
8048edb: ff 24 85 a0 a1 04 08 jmp *0x804a1a0(,%eax,4) // 间接跳转,跳到*0x804a1a0这个地址,这是第三关的核心代码!
8048ee2: b8 00 00 00 00 mov $0x0,%eax //eax清零
8048ee7: eb 53 jmp 8048f3c
8048ee9: b8 00 00 00 00 mov $0x0,%eax
8048eee: 66 90 xchg %ax,%ax
8048ef0: eb 45 jmp 8048f37
8048ef2: b8 00 00 00 00 mov $0x0,%eax
8048ef7: eb 39 jmp 8048f32
8048ef9: b8 00 00 00 00 mov $0x0,%eax
8048efe: 66 90 xchg %ax,%ax
8048f00: eb 2b jmp 8048f2d
8048f02: b8 00 00 00 00 mov $0x0,%eax
8048f07: eb 1f jmp 8048f28
8048f09: b8 00 00 00 00 mov $0x0,%eax
8048f0e: 66 90 xchg %ax,%ax
8048f10: eb 11 jmp 8048f23 //以上代码是switch的跳转表
8048f12: b8 14 03 00 00 mov $0x314,%eax //将这个地址的值取出赋给eax(跳转表的初始值,通过jmp到不同的地方计算出多组值),eax=788(十进制)
8048f17: eb 05 jmp 8048f1e
8048f19: b8 00 00 00 00 mov $0x0,%eax
8048f1e: 2d 5a 03 00 00 sub $0x35a,%eax
//计算到此处eax=788-0x35a=-70(第一次跳转的时候,也就是case 0 的情况)
8048f23: 05 ef 02 00 00 add $0x2ef,%eax
8048f28: 2d 16 02 00 00 sub $0x216,%eax
8048f2d: 05 16 02 00 00 add $0x216,%eax
8048f32: 2d 16 02 00 00 sub $0x216,%eax
8048f37: 05 16 02 00 00 add $0x216,%eax
8048f3c: 2d 16 02 00 00 sub $0x216,%eax
//算到这里停止,此时eax=147(case 0)
8048f41: eb 0a jmp 8048f4d
8048f43: e8 89 01 00 00 call 80490d1
8048f48: b8 00 00 00 00 mov $0x0,%eax
8048f4d: 83 7d f4 05 cmpl $0x5,-0xc(%ebp)//ebp-12要小于等于5
8048f51: 7f 05 jg 8048f58 //ebp-12如果大于5就引爆炸弹,从前面看参数个数存放在ebp-12处(旧ebp-12的值为6),说明输入的参数个数最多为6组(下标从0开始)。所以推知第三关有六组答案。
8048f53: 3b 45 f0 cmp -0x10(%ebp),%eax
8048f56: 74 05 je 8048f5d
8048f58: e8 74 01 00 00 call 80490d1
8048f5d: c9 leave
8048f5e: 66 90 xchg %ax,%ax
8048f60: c3 ret
栈帧数据记录:
思路分析
第三关要输入两个%d%d,不妨设为参数A,B,ebp-12处的值为参数A,存放在esp+8处。而ebp-20处的值为参数B,存放在esp+12处。接下来是查看switch跳转表,这也是最麻烦的地方。它出了一个地址指针*0x804a1a0,用GDB查看发现它指向0x8048f12。在源码找到这个地址,发现mov了一个$0x314。先分析参数A,刚开始的时候A<=6(下标从0开始数),但后面发现ebp-12必须要小于等于5,所以参数A只能从0-5取值了。那么A=0的时候跳转到哪呢?当然就是上面说的那个啦!在源码备注中我给出了计算过程这里不再复述。当A=1的时候也是类似的计算过程,直到计算完5组答案,然而0 147已经可以通过了,剩下的算下去也没太大意义了。也就是说一共有六种case,每个case有一定的计算过程,前面分析到有个地方的值不可以大于7,因此第三关就是在0x314的基础上,根据不同的case计算出不同的值。我算了case0的,其它的也是类似计算。
实验结论
第三关通关密码:0 147
4.Phase_4
原理设计
老规矩先找到要输入的参数,用GDB调试工具发现要输入两个整数。第四关用到了函数递归,因此要了解递归函数的栈帧变化情况。通过输入的两个参数以及一系列操作推出通关条件是输入两个符合要求的整数。如何避免一个又一个炸弹呢?引爆炸弹的函数不成立即可。
实现与分析
汇编代码如下:
08048e2e :
8048e2e: 55 push %ebp
8048e2f: 89 e5 mov %esp,%ebp
8048e31: 83 ec 28 sub $0x28,%esp
8048e34: 8d 45 f0 lea -0x10(%ebp),%eax
8048e37: 89 44 24 0c mov %eax,0xc(%esp)//将ebp-16处的地址赋给esp+12,记为参数2
8048e3b: 8d 45 f4 lea -0xc(%ebp),%eax
8048e3e: 89 44 24 08 mov %eax,0x8(%esp)//将ebp-12处的地址赋给esp+8,记为参数1
8048e42: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp)//GDB查看发现是%d%d,推知第四关也要输入两个整数。
8048e49: 08
8048e4a: 8b 45 08 mov 0x8(%ebp),%eax
8048e4d: 89 04 24 mov %eax,(%esp)//ebp+8的值赋给esp
8048e50: e8 eb f9 ff ff call 8048840 <__isoc99_sscanf@plt>//上一关已经说明,返回参数的个数
8048e55: 83 f8 02 cmp $0x2,%eax//判断输入的参数是否为2个
8048e58: 75 0c jne 8048e66 //不为2个则引爆炸弹
8048e5a: 8b 45 f4 mov -0xc(%ebp),%eax//ebp-12赋给eax
8048e5d: 85 c0 test %eax,%eax//判断ebp-12是否大于等于0
8048e5f: 78 05 js 8048e66 //若不大于等于0则引爆炸弹
8048e61: 83 f8 0e cmp $0xe,%eax//判断ebp-12是否小于14,若大于14引爆炸弹,由此推知参数1的范围是0-14。
8048e64: 7e 05 jle 8048e6b
8048e66: e8 66 02 00 00 call 80490d1
8048e6b: c7 44 24 08 0e 00 00 movl $0xe,0x8(%esp)//14赋给esp+8
8048e72: 00
8048e73: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)//0赋给esp+4
8048e7a: 00
8048e7b: 8b 45 f4 mov -0xc(%ebp),%eax
8048e7e: 89 04 24 mov %eax,(%esp)//ebp-12也就是参数1赋给esp
8048e81: e8 da fc ff ff call 8048b60 //开始递归,传递了三个参数:14,0,参数1
8048e86: 83 f8 01 cmp $0x1,%eax //eax是返回值且必须为1,否则引爆炸弹
8048e89: 75 06 jne 8048e91
8048e8b: 83 7d f0 01 cmpl $0x1,-0x10(%ebp)//判断ebp-16是否为1,也就是参数2必须为1。
8048e8f: 74 0c je 8048e9d 参数2为1则结束,否则引爆炸弹。
8048e91: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e98: e8 34 02 00 00 call 80490d1
8048e9d: c9 leave
8048e9e: 66 90 xchg %ax,%ax
8048ea0: c3 ret
汇编代码如下:
08048b60 :
8048b60: 55 push %ebp
8048b61: 89 e5 mov %esp,%ebp
8048b63: 83 ec 18 sub $0x18,%esp//esp-24,开栈操作
8048b66: 89 5d f8 mov %ebx,-0x8(%ebp)//ebx赋给ebp-8
8048b69: 89 75 fc mov %esi,-0x4(%ebp)//esi赋给ebp-4,前面说了ebx和esi是调用保存寄存器
8048b6c: 8b 55 08 mov 0x8(%ebp),%edx//ebp+8赋给edx,记为参数A
8048b6f: 8b 45 0c mov 0xc(%ebp),%eax//ebp+12赋给eax,记为参数B
8048b72: 8b 5d 10 mov 0x10(%ebp),%ebx//ebp+16赋给ebx,记为参数C
8048b75: 89 d9 mov %ebx,%ecx//ebx赋给ecx
8048b77: 29 c1 sub %eax,%ecx//C-B
8048b79: 89 ce mov %ecx,%esi//将ecx赋给esi
8048b7b: c1 ee 1f shr $0x1f,%esi//对esi进行逻辑右移31位
8048b7e: 8d 0c 0e lea (%esi,%ecx,1),%ecx//按照这个寻址方式存放在ecx里面
8048b81: d1 f9 sar %ecx //算术右移,相当于 C/2,默认移动一位
8048b83: 01 c1 add %eax,%ecx//将递归的返回值加倍
8048b85: 39 d1 cmp %edx,%ecx//若C>A,则B=0,进入递归,否则跳到8048ba0继续执行。
8048b87: 7e 17 jle 8048ba0 //如果C<=A,跳到8048ba0继续执行,否则顺序执行
8048b89: 83 e9 01 sub $0x1,%ecx//C=C-1
8048b8c: 89 4c 24 08 mov %ecx,0x8(%esp)//将C-1放到%esp+8
8048b90: 89 44 24 04 mov %eax,0x4(%esp)//将B放到%esp+4
8048b94: 89 14 24 mov %edx,(%esp)//edx赋给esp
8048b97: e8 c4 ff ff ff call 8048b60 //进入递归
8048b9c: 01 c0 add %eax,%eax//B=B+1
8048b9e: eb 20 jmp 8048bc0
8048ba0: b8 00 00 00 00 mov $0x0,%eax//将B重置为0
8048ba5: 39 d1 cmp %edx,%ecx//将A,C进行比较,若A>=C跳到8048bc0继续执行,递归终止
8048ba7: 7d 17 jge 8048bc0
8048ba9: 89 5c 24 08 mov %ebx,0x8(%esp)//将C转存到esp
8048bad: 83 c1 01 add $0x1,%ecx//C=C+1
8048bb0: 89 4c 24 04 mov %ecx,0x4(%esp)
8048bb4: 89 14 24 mov %edx,(%esp)
8048bb7: e8 a4 ff ff ff call 8048b60 //进入递归
8048bbc: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax//递归返回值加倍后再加1
8048bc0: 8b 5d f8 mov -0x8(%ebp),%ebx//ebp-8赋给ebx
8048bc3: 8b 75 fc mov -0x4(%ebp),%esi
8048bc6: 89 ec mov %ebp,%esp
8048bc8: 5d pop %ebp
8048bc9: c3 ret
思路分析:
从上面可以推知第四关要输入两个数,第一个数的范围在0-14,第二个数确定为1。从fun4函数可知,第一次调用的时候是将14,0,参数1作为参数传入,fun4类似于一个二分操作,在0-14中,通过二分不断逼近参数1,经过分析发现一共三组答案:8 1,9 1,11 1。也可以用比较笨的方法,就是直接输入0-14之间的数,没有引爆的都是符合的。
结论
第四关通关密码为:8 1 , 9 1 , 11 1 。输入三组中任意一组都可以。
5.Phase_5
原理设计
老规矩找到要输入的参数类型,用GDB调试工具查看发现要输入两个%d%d。接下来分析输入参数后进行的一系列操作一步一步推得通关条件。调用引爆函数前均有一个条件,不满足这些条件即可避开炸弹。
实现与分析
汇编代码如下:
08048db8 :
8048db8: 55 push %ebp
8048db9: 89 e5 mov %esp,%ebp
8048dbb: 56 push %esi
8048dbc: 53 push %ebx//esi和ebx为调用者寄存器,接下来的循环用到了,故压栈保存。
8048dbd: 83 ec 20 sub $0x20,%esp//esp-32
8048dc0: 8d 45 f0 lea -0x10(%ebp),%ea
8048dc3: 89 44 24 0c mov %eax,0xc(%esp)//ebp-16赋给esp+12,记为参数2
8048dc7: 8d 45 f4 lea -0xc(%ebp),%eax
8048dca: 89 44 24 08 mov %eax,0x8(%esp)//ebp-12赋给esp+8,记为参数1
8048dce: c7 44 24 04 3e a2 04 movl $0x804a23e,0x4(%esp)//跟前面关卡一样,还是两个%d%d
8048dd5: 08
8048dd6: 8b 45 08 mov 0x8(%ebp),%eax
8048dd9: 89 04 24 mov %eax,(%esp)//ebp+8赋给esp
8048ddc: e8 5f fa ff ff call 8048840 <__isoc99_sscanf@plt>//前面说两遍了!
8048de1: 83 f8 01 cmp $0x1,%eax//参数个数大于1,否则炸!
8048de4: 7f 05 jg 8048deb
8048de6: e8 e6 02 00 00 call 80490d1
8048deb: 8b 45 f4 mov -0xc(%ebp),%eax//ebp-12(参数1)赋给eax
8048dee: 83 e0 0f and $0xf,%eax //把eax和低四位为1的数作位与操作,同时传回eax
8048df1: 89 45 f4 mov %eax,-0xc(%ebp)//eax赋给ebp-12
8048df4: 83 f8 0f cmp $0xf,%eax
8048df7: 74 29 je 8048e22 //判断参数1是否等于15,若等于15则引爆炸弹,猜想输入的第一个参数要小于15。此时的eax是按位与后的eax,也就是00000000 00000000 00000000 00001111,说白了第一个参数不能大于等于15。
8048df9: b9 00 00 00 00 mov $0x0,%ecx//清零
8048dfe: ba 00 00 00 00 mov $0x0,%edx//清零
8048e03: bb c0 a1 04 08 mov $0x804a1c0,%ebx//传了个地址,用GDB查看发现是一个数组
8048e08: 83 c2 01 add $0x1,%edx //edx+1(是记录循环次数的)
8048e0b: 8b 04 83 mov (%ebx,%eax,4),%eax//从数组读数,每次跳四个字节(一个int四字节),也就是一个数组位置。
8048e0e: 01 c1 add %eax,%ecx//ecx+=eax
8048e10: 83 f8 0f cmp $0xf,%eax //判断eax是否大于15
8048e13: 75 f3 jne 8048e08 //不等于15则跳到8048e08,也就是继续循环,以上是循环部分。
8048e15: 89 45 f4 mov %eax,-0xc(%ebp)//把完成循环后的eax传给ebp-12
8048e18: 83 fa 0f cmp $0xf,%edx//判断循环的次数是否够15次,加上exd=0那一次循环,共进行了16次循环。
8048e1b: 75 05 jne 8048e22 //不够15次引爆炸弹
8048e1d: 39 4d f0 cmp %ecx,-0x10(%ebp)//把完成循环的ecx与参数2进行比较
8048e20: 74 05 je 8048e27 //若参数2与ecx不相等则顺序执行引爆炸弹,否则跳过炸弹。从而推知ecx是通过循环累加得到的。
8048e22: e8 aa 02 00 00 call 80490d1
8048e27: 83 c4 20 add $0x20,%esp
8048e2a: 5b pop %ebx
8048e2b: 5e pop %esi
8048e2c: 5d pop %ebp
8048e2d: c3 ret
思路分析:
从上面的分析可知,第四关是对数组进行操作前一次取数组的值作为下一次取数的下标,并不断累加,因此我们要推出第一次取了数组那个值即可通关。用GDB查看发现是存储了16个元素的数组:[10,2,14,7,8,12,15,11,0,4,1,13,3,9,6,5]。从循环最开始前看,eax就是参数1的底四位且不能为1111,所以eax的取值只能是0-14。以上是循环开始前的条件,那么循环结束的条件呢?很明显是eax=15,那这个15究竟从何而来呢?当然是从数组里面读来的,按照mov (%ebx,%eax,4),%eax这个寻址方式得来的,15对应的上个值是6(就表示上一次数组取的值是6,然后下一次取a[6],也就是15,然后下一次取a[15])。依此类推,得到第一个取的值为5,数组累加得到的值为120,但进入循环前ecx已经为5了,故减去5,那么通关密码为 5 115。
6.Phase_6
原理设计
老规矩先找到要输入的参数,发现要输入六个数。然后分析读入六个数后的操作,第六关用到了链表,要了解链表的特性(分为数据域和指针域),用GDB查看关键地址内容,一步一步即可推得通关条件,避开炸弹的方法就是让调用炸弹函数的条件不成立就好了。给点心里准备:第六关是一个链表,汇编代码又长又臭!
实现与分析
汇编代码按如下:
08048c89 :
8048c89: 55 push %ebp
8048c8a: 89 e5 mov %esp,%ebp
8048c8c: 57 push %edi
8048c8d: 56 push %esi
8048c8e: 53 push %ebx //以上三个为调用者保存寄存器,后面的循环用到,故压栈保存(寄存器数量有限,要将原来的状态保存,因为要复原)
8048c8f: 83 ec 5c sub $0x5c,%esp//esp-92
8048c92: 8d 45 d0 lea -0x30(%ebp),%eax
8048c95: 89 44 24 04 mov %eax,0x4(%esp) //ebp-48赋给esp+4
8048c99: 8b 45 08 mov 0x8(%ebp),%eax
8048c9c: 89 04 24 mov %eax,(%esp)//ebp+8赋给esp
8048c9f: e8 67 04 00 00 call 804910b //读入6个数
8048ca4: be 00 00 00 00 mov $0x0,%esi//清零
8048ca9: 8d 7d d0 lea -0x30(%ebp),%edi//ebp-48赋给edi
8048cac: 8b 04 b7 mov (%edi,%esi,4),%eax//edi+4esi赋给eax,当前esi=0,亦即把ebp-48赋给eax,对于不同的esi,就是从ebp-48向上找第esi个数赋给eax。
8048caf: 83 e8 01 sub $0x1,%eax//eax-=1
8048cb2: 83 f8 05 cmp $0x5,%eax
8048cb5: 76 05 jbe 8048cbc //将eax-1与5比较,如果小于等于则继续,否则引爆炸弹,jbe是无符号数操作,故推知eax的范围为1-6。
8048cb7: e8 15 04 00 00 call 80490d1
8048cbc: 83 c6 01 add $0x1,%esi//esi+=1(取第二个数)
8048cbf: 83 fe 06 cmp $0x6,%esi
8048cc2: 74 22 je 8048ce6 //判断取的数是否超过6个,若大于6个引爆炸弹。从而推知循环结束的条件是esi=6。
8048cc4: 8d 1c b7 lea (%edi,%esi,4),%ebx//edi+4esi赋给ebx,当前esi=1,edi=ebp-48,故本次将ebp-44赋给ebx(下一次应该是ebp-40)到这里就知道这是个线性的结构
8048cc7: 89 75 b4 mov %esi,-0x4c(%ebp)//esi赋给ebp-76,此时esi=1
8048cca: 8b 44 b7 fc mov -0x4(%edi,%esi,4),%eax//将edi+4esi-4赋给eax,综合上面两句来看,应该是将ebx的下一个地址赋给eax
8048cce: 3b 03 cmp (%ebx),%eax
8048cd0: 75 05 jne 8048cd7
8048cd2: e8 fa 03 00 00 call 80490d1 //eax和ebx的值相等则引爆炸弹,证明ebx和它的下一个值不相等。
8048cd7: 83 45 b4 01 addl $0x1,-0x4c(%ebp)//ebp-76+1,此时变为2
8048cdb: 83 c3 04 add $0x4,%ebx//ebx+=4(读取下一个地址的内容)
8048cde: 83 7d b4 05 cmpl $0x5,-0x4c(%ebp)
8048ce2: 7e e6 jle 8048cca
8048ce4: eb c6 jmp 8048cac //ebp-76的值小于等于5跳到8048cca,大于5则跳到8048cac,由此分析这是一个嵌套循环。(思路分析处有详细分析)
8048ce6: bb 00 00 00 00 mov $0x0,%ebx//清零,ebx=0
8048ceb: 8d 7d d0 lea -0x30(%ebp),%edi//ebp-48赋给edi
8048cee: eb 16 jmp 8048d06 //直接跳到8048d06号地址
8048cf0: 8b 52 08 mov 0x8(%edx),%edx//edx+8赋给edx
8048cf3: 83 c0 01 add $0x1,%eax //eax+=1
8048cf6: 39 c8 cmp %ecx,%eax
8048cf8: 75 f6 jne 8048cf0 //若ecx不等于eax,跳回8048cf0,直到两者相等才继续。意思就是匹配节点
8048cfa: 89 54 b5 b8 mov %edx,-0x48(%ebp,%esi,4)//edx赋给ebp+4esi-48,也就是第esi个参数
8048cfe: 83 c3 01 add $0x1,%ebx//ebx+=1,记录取的结点个数
8048d01: 83 fb 06 cmp $0x6,%ebx//是否取完6个结点的内容
8048d04: 74 16 je 8048d1c //等于6则跳到8048d1,否则顺序执行
8048d06: 89 de mov %ebx,%esi //ebx赋给esi
8048d08: 8b 0c 9f mov (%edi,%ebx,4),%ecx//寻址操作
8048d0b: ba c4 c0 04 08 mov $0x804c0c4,%edx//用GDB查看发现它是链表表头的首地址,推知第六关是对链表进行操作
8048d10: b8 01 00 00 00 mov $0x1,%eax //eax=1
8048d15: 83 f9 01 cmp $0x1,%ecx
8048d18: 7f d6 jg 8048cf0
8048d1a: eb de jmp 8048cfa //ecx大于1则跳到8048cf0,否则跳到8048cfa
//下面的内容画重点~
8048d1c: 8b 5d b8 mov -0x48(%ebp),%ebx //ebp-72的值传给ebx
8048d1f: 8b 45 bc mov -0x44(%ebp),%eax //ebp-68的值传给eax
8048d22: 89 43 08 mov %eax,0x8(%ebx) // eax传给ebx+8,此时ebx=ebp-72,故eax传给ebp-64
8048d25: 8b 55 c0 mov -0x40(%ebp),%edx //ebp-64传给edx
8048d28: 89 50 08 mov %edx,0x8(%eax)//edx传给eax+8,即ebp-60
8048d2b: 8b 45 c4 mov -0x3c(%ebp),%eax //ebp-60传给eax
8048d2e: 89 42 08 mov %eax,0x8(%edx)//eax传给edx+8,即ebp-56
8048d31: 8b 55 c8 mov -0x38(%ebp),%edx //ebp-56传给edx
8048d34: 89 50 08 mov %edx,0x8(%eax) //edx传给eax+8,即ebp-52
8048d37: 8b 45 cc mov -0x34(%ebp),%eax //ebp-52传给eax
8048d3a: 89 42 08 mov %eax,0x8(%edx)//eax传给edx+8,即ebp-48
8048d3d: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax) //eax+8,即ebp-44为0
8048d44: be 00 00 00 00 mov $0x0,%esi //esi=0
8048d49: 8b 43 08 mov 0x8(%ebx),%eax //ebx+8传给eax,即ebp-64传给eax
8048d4c: 8b 13 mov (%ebx),%edx
8048d4e: 3b 10 cmp (%eax),%edx
8048d50: 7d 05 jge 8048d57 //eax必须大于edx,结合上面推知第五关是对链表进行排序操作。
8048d52: e8 7a 03 00 00 call 80490d1
8048d57: 8b 5b 08 mov 0x8(%ebx),%ebx//ebx+8传给ebx
8048d5a: 83 c6 01 add $0x1,%esi//esi+1
8048d5d: 83 fe 05 cmp $0x5,%esi
8048d60: 75 e7 jne 8048d49 //若esi不等于5,跳回循环中。
8048d62: 83 c4 5c add $0x5c,%esp
8048d65: 5b pop %ebx
8048d66: 5e pop %esi
8048d67: 5f pop %edi
8048d68: 5d pop %ebp
8048d69: c3 ret
思路分析
这个函数真是又长又臭!真的最难的一个了!幸好查看了0x804c0c4这个地址的内容,发现它显示node,网上查了一下表示链表。然后分析上面变来变去的寄存器发现这是一个交换结点的过程,后来推知这是对链表进行降序排序。起初我猜想链表是存储了两个信息的,一个是数据大小,另一个是指向下一个节点的地址,后来查看发现还存放了节点的序号,从输入参数限制小于6推知第六关的通关密码为排好序后的节点的下标顺序!通过GDB查看六个节点的值分别为:
下一个节点的地址在上一个节点已经给出。
每个结点存储的值依次为:1a7, 6c ,155,187,3bd,255。从大到校排序后:3bd,255,1a7,187,155,6c。那么节点顺序就是:5,6,1,4,3,2。(因为输入的六个数不能大于6,所以不可能输入结点存储的值。)
结论
第六关的通关密码为:5,6,1,4,3,2
7.Secret_phase
原理设计
首先是找到入口函数,那么先找到哪里调用了这个secre_phase函数,查看发现在phase_defused调用了这个函数。接着分析发现secre_phase还调用了一个fun7函数,经过一系列分析发现开启隐藏关卡的条件是在输入两个%d%d的关卡后再输入一个%s,通过第六关就会提示你成功开启了隐藏关卡。然后分析通关条件,发现是让你输入的是一个十进制数,然后作判断,相等则通关,否则引爆炸弹。避开炸弹的方法是让引爆炸弹的条件不成立即可。
实现与分析
由于秘密关卡关联的函数有点多,分析较难,故引用日志并加以修改。
08049014 :
8049014: 55 push %ebp
8049015: 89 e5 mov %esp,%ebp
8049017: 81 ec 88 00 00 00 sub $0x88,%esp
804901d: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8049023: 89 45 f4 mov %eax,-0xc(%ebp)
8049026: 31 c0 xor %eax,%eax
8049028: 83 3d d0 c3 04 08 06 cmpl $0x6,0x804c3d0
804902f: 0f 85 86 00 00 00 jne 80490bb
8049035: 8d 45 a4 lea -0x5c(%ebp),%eax
//先是做一个判断,如果你前面六个关卡已经通关那么开启隐藏关卡,否则跳到80490bb结束,接下来发现到调用密码关卡函数前movl了一连串的立即数:
8049038: 89 44 24 10 mov %eax,0x10(%esp)
804903c: 8d 45 9c lea -0x64(%ebp),%eax
804903f: 89 44 24 0c mov %eax,0xc(%esp)
8049043: 8d 45 a0 lea -0x60(%ebp),%eax
8049046: 89 44 24 08 mov %eax,0x8(%esp)
804904a: c7 44 24 04 00 a2 04 movl $0x804a200,0x4(%esp)
8049051: 08
8049052: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
8049059: e8 e2 f7 ff ff call 8048840 <__isoc99_sscanf@plt>
804905e: 83 f8 03 cmp $0x3,%eax
8049061: 75 44 jne 80490a7
8049063: c7 44 24 04 09 a2 04 movl $0x804a209,0x4(%esp)
804906a: 08
804906b: 8d 45 a4 lea -0x5c(%ebp),%eax
804906e: 89 04 24 mov %eax,(%esp)
8049071: e8 35 ff ff ff call 8048fab
8049076: 85 c0 test %eax,%eax
8049078: 75 2d jne 80490a7
804907a: c7 44 24 04 dc a2 04 movl $0x804a2dc,0x4(%esp)
8049081: 08
8049082: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8049089: e8 e2 f7 ff ff call 8048870 <__printf_chk@plt>
804908e: c7 44 24 04 04 a3 04 movl $0x804a304,0x4(%esp)
8049095: 08
8049096: c7 04 24 01 00 00 00 movl $0x1,(%esp)
804909d: e8 ce f7 ff ff call 8048870 <__printf_chk@plt>
80490a2: e8 74 fb ff ff call 8048c1b
80490a7: c7 44 24 04 3c a3 04 movl $0x804a33c,0x4(%esp)
80490ae: 08
80490af: c7 04 24 01 00 00 00 movl $0x1,(%esp)
80490b6: e8 b5 f7 ff ff call 8048870 <__printf_chk@plt>
80490bb: 8b 45 f4 mov -0xc(%ebp),%eax
80490be: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
80490c5: 74 05 je 80490cc
80490c7: e8 e4 f6 ff ff call 80487b0 <__stack_chk_fail@plt>
80490cc: c9 leave
80490cd: 8d 76 00 lea 0x0(%esi),%esi
80490d0: c3 ret
用GBD查看这些地址存储的内容:
先输入两个整数再输入字符串,就能开启密码关卡,而前面要输入两个整数的关卡只有3,4,5。所以应该是要在这三关中任意一关输入DrEvil就能激活秘密关卡。下面测试一下:(我测试过了只能在第三关输入才能激活密码关卡)
接下来回去看密码关卡的代码:
08048c1b :
8048c1b: 55 push %ebp
8048c1c: 89 e5 mov %esp,%ebp
8048c1e: 53 push %ebx
8048c1f: 83 ec 14 sub $0x14,%esp
8048c22: e8 df 05 00 00 call 8049206
8048c27: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048c2e: 00
8048c2f: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048c36: 00
8048c37: 89 04 24 mov %eax,(%esp)
8048c3a: e8 71 fc ff ff call 80488b0
8048c3f: 89 c3 mov %eax,%ebx
8048c41: 8d 40 ff lea -0x1(%eax),%eax
8048c44: 3d e8 03 00 00 cmp $0x3e8,%eax
8048c49: 76 05 jbe 8048c50
8048c4b: e8 81 04 00 00 call 80490d1
8048c50: 89 5c 24 04 mov %ebx,0x4(%esp)
8048c54: c7 04 24 78 c1 04 08 movl $0x804c178,(%esp)
8048c5b: e8 6a ff ff ff call 8048bca
8048c60: 83 f8 05 cmp $0x5,%eax
8048c63: 74 05 je 8048c6a
8048c65: e8 67 04 00 00 call 80490d1
8048c6a: c7 44 24 04 34 a1 04 movl $0x804a134,0x4(%esp)
8048c71: 08
8048c72: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048c79: e8 f2 fb ff ff call 8048870 <__printf_chk@plt>
8048c7e: e8 91 03 00 00 call 8049014
8048c83: 83 c4 14 add $0x14,%esp
8048c86: 5b pop %ebx
8048c87: 5d pop %ebp
8048c88: c3 ret
call 8049206 ,表明程序先读入一行,随后返回值%eax作为函数的参数之一,另外两个参数分别
是0xa和0x0,也就是我们需要输入一个十进制数。由lea -0x1(%eax),%eax 和cmp $0x3e8,%eax 这两句知输入的十进制数要小于等
于1001。随后将所输入的数作为 的参数之一。另外一个参数来自 0x804c178,查看为0x24。
接下来发现调用了fun7函数。找到它的代码:
08048bca :
8048bca: 55 push %ebp
8048bcb: 89 e5 mov %esp,%ebp
8048bcd: 53 push %ebx
8048bce: 83 ec 14 sub $0x14,%esp
8048bd1: 8b 55 08 mov 0x8(%ebp),%edx
8048bd4: 8b 4d 0c mov 0xc(%ebp),%ecx
8048bd7: b8 ff ff ff ff mov $0xffffffff,%eax
8048bdc: 85 d2 test %edx,%edx
8048bde: 74 35 je 8048c15
8048be0: 8b 1a mov (%edx),%ebx
8048be2: 39 cb cmp %ecx,%ebx
8048be4: 7e 13 jle 8048bf9
8048be6: 89 4c 24 04 mov %ecx,0x4(%esp)
8048bea: 8b 42 04 mov 0x4(%edx),%eax
8048bed: 89 04 24 mov %eax,(%esp)
8048bf0: e8 d5 ff ff ff call 8048bca
8048bf5: 01 c0 add %eax,%eax
8048bf7: eb 1c jmp 8048c15
8048bf9: b8 00 00 00 00 mov $0x0,%eax
8048bfe: 39 cb cmp %ecx,%ebx
8048c00: 74 13 je 8048c15
8048c02: 89 4c 24 04 mov %ecx,0x4(%esp)
8048c06: 8b 42 08 mov 0x8(%edx),%eax
8048c09: 89 04 24 mov %eax,(%esp)
8048c0c: e8 b9 ff ff ff call 8048bca
8048c11: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax
8048c15: 83 c4 14 add $0x14,%esp
8048c18: 5b pop %ebx
8048c19: 5d pop %ebp
8048c1a: c3 ret
思路分析:
首先看mov 0x8(%ebp),%edx,mov 0xc(%ebp),%ecx。不妨设为参数A,B(B是自己输入的参数)。这步操作是传参操作,test %edx,%edx是递归终止的时候,返回%edx=0。接下来是进入递归:这里有两个判断,如果A,那么将地址A+8作为地址进入递归。如果A>B,以地址A+4作为地址进入递归。代码add %eax ,%eax将递归返回值加倍。代码add $0x14,%esp是将返回值再加一。在调用完
A*2+1=5 -->A=2 即有*A
A*2=2 -->A=1 有*A>B
A*2+1=1 -->A=0 即有*A
也就是说在这三次递归中两次执行了“若AB,将(A+4)作为地址进入递归”系列代码。使用GDB查询储存值:
最后一次得到的值是0x2f,化为十进制是32+15=47。下面来验证一下:
秘密关卡调用的函数太多了,分析起来很麻烦,我也是参考了下别人的做法,写得不是太好,若有问题欢迎留言指出,转载务必注明出处谢谢。
完成于2018.6.29