Bomblab——二进制炸弹实验
学校:湖南大学
班级:智能
姓名:诸神
【实验目的】
理解汇编语言,学会使用gdb调试。
【实验原理】
二进制炸弹是作为一个目标代码文件提供给学生们的程序,运行时,它提示用户输入6个不同的字符串。如果其中任何一个不正确,炸弹就会“爆炸”:打印出一条错误信息。学生通过反汇编和逆向工程来确定是哪六个字符串,从而解除它们。
【实验过程】
一、反汇编bomb,并输出到txt文件。
打开终端,用objdump –d bomb > 1.txt反汇编bomb并且将汇编代码储存到文件1.txt中。
二、拆炸弹
1、 首先大致浏览代码,发现它有一个main函数,phase_1到phase_6六个函数,一个secret_phase函数,表明我们要从phase_1到phase_6的代码中得到答案,除了六个关卡之外还有一个隐藏关卡。
2、 Phase_1
我们从代码中可以看到当<strings_not_equal>返回值%eax!=0时,炸弹会炸。再向上看,有立即数$0x804a15c,这里是当做地址用。故此地址储存的字符串即为通关密码。在gdb调试中调用指令查看,得到通关密码:
3、 phase_2
首先注意到phase_2调用了函数<read_six_numbers>读入六个数。紧接着5条指令:
8048d84: 83 7d e0 00 cmpl $0x0,-0x20(%ebp)
8048d88: 75 06 jne 8048d90 <phase_2+0x26>
8048d8a: 83 7d e4 01 cmpl $0x1,-0x1c(%ebp)
8048d8e: 74 05 je 8048d95 <phase_2+0x2b>
8048d90: e8 3c 03 00 00 call 80490d1 <explode_bomb>
是说第一个数为0,第二个数为1,否则bomb。接下来是一个循环,主要指令是add -0x8(%ebx),%eax,就是后一个数时它前两个数之和,很容易联想到斐波切纳数列。即输入斐波切纳数列前六个数即可:0 1 1 2 3 5。
4、 phase_3
注意到代码movl $0x804a23e,0x4(%esp),用gdb查看:x/s 0x804a23e,得到"%d %d",
即要输入两个数。而cmp $0x1,%eax表明输入参数必须多于1个。再往下到达cmpl $0x7,-0xc(%ebp),即输入的第一个参数值必须小于等于7。然后看到jmp *0x8049920(,%eax,4),这是典型的switch跳转语句,即跳转到以地址*0x8049920为基址的跳转表中。在结束指令之前发现cmpl $0x5,-0xc(%ebp),表明输入的第一个参数值必须要小于等于5。用gdb查看跳转表:
按照跳转表计算,得到共计6个答案:0 147,1 -641,2 217,3 -534,4 0,5 -534。
5、 phase_4
同样由movl $0x804a23e,0x4(%esp)知道要输入两个数。由test %eax,%eax知输入的第一个数为非负数,由cmp $0xe,%eax知这个数还小于14。将这个数还有0,e作为func4的初始参数进行递归运算。由cmp $0x1,%eax知返回值%eax要等于1。由cmpl $0x1,-0x10(%ebp)知输入的第二个数为1。
<func4>代码:
08048b60<func4>:
8048b60: 55 push %ebp
8048b61: 89e5 mov %esp,%ebp
8048b63: 83ec 18 sub $0x18,%esp
8048b66: 895d f8 mov %ebx,-0x8(%ebp)
8048b69: 8975 fc mov %esi,-0x4(%ebp)
8048b6c: 8b55 08 mov 0x8(%ebp),%edx //第一个参数A
8048b6f: 8b45 0c mov 0xc(%ebp),%eax //第二个参数B
8048b72: 8b5d 10 mov 0x10(%ebp),%ebx //第三个参数C
8048b75: 89d9 mov %ebx,%ecx
8048b77: 29c1 sub %eax,%ecx
8048b79: 89ce mov %ecx,%esi
8048b7b: c1ee 1f shr $0x1f,%esi
8048b7e: 8d0c 0e lea (%esi,%ecx,1),%ecx
8048b81: d1f9 sar %ecx //C/2
8048b83: 01c1 add %eax,%ecx //在此处将递归返回值加倍
8048b85: 39d1 cmp %edx,%ecx
8048b87: 7e17 jle 8048ba0 <func4+0x40>
//若C>A,C=C-1,进入递归
8048b89: 83e9 01 sub $0x1,%ecx
8048b8c: 894c 24 08 mov %ecx,0x8(%esp)
8048b90: 8944 24 04 mov %eax,0x4(%esp)
8048b94: 8914 24 mov %edx,(%esp)
8048b97: e8c4 ff ff ff call 8048b60<func4>
8048b9c: 01c0 add %eax,%eax
8048b9e: eb20 jmp 8048bc0 <func4+0x60>
8048ba0: b800 00 00 00 mov $0x0,%eax //B=0
8048ba5: 39d1 cmp %edx,%ecx
8048ba7: 7d17 jge 8048bc0 <func4+0x60>
//若C<A,则B=C+1,C=14,进入递归
8048ba9: 895c 24 08 mov %ebx,0x8(%esp)
8048bad: 83c1 01 add $0x1,%ecx
8048bb0: 894c 24 04 mov %ecx,0x4(%esp)
8048bb4: 8914 24 mov %edx,(%esp)
8048bb7: e8a4 ff ff ff call 8048b60 <func4>
//在此处将递归返回值加倍后再加1
8048bbc: 8d44 00 01 lea 0x1(%eax,%eax,1),%eax
//若C=A,则递归终止
8048bc0: 8b5d f8 mov -0x8(%ebp),%ebx
8048bc3: 8b75 fc mov -0x4(%ebp),%esi
8048bc6: 89ec mov %ebp,%esp
8048bc8: 5d pop %ebp
8048bc9: c3 ret
因为递归返回值为1,故最外层返回值为1,它里面一返回值为0,进行倒推,可得出3个答案:8 1,9 1,11 1。
6、 phase_5
同样由movl $0x804a23e,0x4(%esp)知道要输入两个数。由cmp $0xf,%eax知第一个数小于15。然后有一个立即数$0x804a1c0。再往下是个循环,在其中看到mov (%ebx,%eax,
4),%eax,这是典型的数组的使用,把上次循环读取的值做为这次循环元素的下标。用指令add %eax,%ecx实现每次读取的值的累加,由cmp $0xf,%eax知最后取出的数应该为15。mov %eax,-0xc(%ebp),累加值应于输入的第二个数相等。cmp $0xf,%edx,知循环15次。
用gdb查看地址中储存的数组:
倒推15次得到12,故应该输入5,累加值为115,故答案为5 115.
7、 phase_6
首先我们注意到它先调用了函数<read_six_numbers>,要我们输入六个数。紧接着是一个循环,其中sub $0x1,%eax,cmp $0x5,%eax对其进行判断,即6个数都不大于6。再往下走,我们看到立即数$0x804c0c4,这里当做地址使用。上面的指令也是对储存在地址里的值进行赋值。可用gdb查看其中储存的值:
这个是很典型的链表的使用,,每个节点储存了两个int型,还有指向下一个节点的指针,即地址。所以题目是对链表的节点进行的操作。
再往下一段,是地址的传递,不改变我们输入的值,先跳过。再往下又是一个循环,其中关键指令mov 0x8(%ebx),%eax,mov (%ebx),%edx,cmp (%eax),%edx,知前一个值要小于等于后一个值。这样我们就知道题目的大概要求了,通过我们输入的六个值对对链表的值进行排序,使他们呈递减数列。
接着用gdb查看链表储存的值:
得到所有的值:1a7、6c、155、187、3bd、255,按从大到小排序:3bd>255>1a7>187>155>6c。那么我们就能得到答案:5 6 1 4 3 2。
8、 secret_phase
1) 入口
首先我们找调用<secret_phase>函数的地方,发现它在<phase_defused>函数里。查看函数<phase_defused>的代码,首先注意到cmpl $0x6,0x804c3d0,若不等于6则直接离开,猜测是判断你是否完成了6道题。当6道题全部完成时,才会继续执行。再往下,到调用函数<secret_phase>之前发现一系列立即数:$0x804a200、$0x804a209、$0x804a2dc、$0x804a304。用gdb查看其内容:
首先我们看到%d %d %s,表示输入两个整数一个字符串,接着是DrEvil。我们注意到在使用$0x804a209之后调用了<strings_not_equal>函数,相同则继续执行。让我们自然的想到我们要在3、4、5关其中某一关的答案后加上字符串DrEvil。在调用<strings_not_equal>函数之前 我们发现指令lea -0x5c(%ebp),%eax,其中0x5c(%ebp)即是我们要输入DrEvil的地址。我们可以经过计算知道应在第四关输入,不过太麻烦我没有使用。我是使用了简单无脑的方法,依次在3、4、5关输入DrEvil看其是否能进入隐藏关从而知到入口在第四关。
2) 解题
<secret_phase>代码:
08048c1b <secret_phase>:
8048c1b: 55 push %ebp
8048c1c: 89e5 mov %esp,%ebp
8048c1e: 53 push %ebx
8048c1f: 83ec 14 sub $0x14,%esp
8048c22: e8df 05 00 00 call 8049206<read_line>
8048c27: c744 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048c2e: 00
8048c2f: c744 24 04 00 00 00 movl $0x0,0x4(%esp)
8048c36: 00
8048c37: 8904 24 mov %eax,(%esp)
8048c3a: e871 fc ff ff call 80488b0<strtol@plt>
8048c3f: 89c3 mov %eax,%ebx
8048c41: 8d40 ff lea -0x1(%eax),%eax
8048c44: 3de8 03 00 00 cmp $0x3e8,%eax
8048c49: 7605 jbe 8048c50 <secret_phase+0x35>
8048c4b: e881 04 00 00 call 80490d1<explode_bomb>
8048c50: 895c 24 04 mov %ebx,0x4(%esp)
8048c54: c704 24 78 c1 04 08 movl $0x804c178,(%esp)
8048c5b: e86a ff ff ff call 8048bca <fun7>
8048c60: 83f8 05 cmp $0x5,%eax
8048c63: 7405 je 8048c6a <secret_phase+0x4f>
8048c65: e867 04 00 00 call 80490d1<explode_bomb>
8048c6a: c744 24 04 34 a1 04 movl $0x804a134,0x4(%esp)
8048c71: 08
8048c72: c7 04 24 01 00 00 00 movl $0x1,(%esp)
8048c79: e8f2 fb ff ff call 8048870<__printf_chk@plt>
8048c7e: e891 03 00 00 call 8049014<phase_defused>
8048c83: 83c4 14 add $0x14,%esp
8048c86: 5b pop %ebx
8048c87: 5d pop %ebp
8048c88: c3 ret
首先一句call 8049206 <read_line>,表明程序先读入一行,随后返回值%eax作为函数<strtol@plt>的参数之一,另外两个参数分别是0xa和0x0,也就是我们需要输入一个十进制数。由lea -0x1(%eax),%eax 和cmp $0x3e8,%eax 这两句知输入的十进制数要小于等于1001。随后将所输入的数作为<fun7> 的参数之一。另外一个参数来自 0x804c178,查看为0x24。
<fun7>代码:
08048bca <fun7>:
8048bca: 55 push %ebp
8048bcb: 89e5 mov %esp,%ebp
8048bcd: 53 push %ebx
8048bce: 83ec 14 sub $0x14,%esp
8048bd1: 8b55 08 mov 0x8(%ebp),%edx //第一个参数A
8048bd4: 8b4d 0c mov 0xc(%ebp),%ecx //第二个参数B 即输入
8048bd7: b8ff ff ff ff mov $0xffffffff,%eax
8048bdc: 85d2 test %edx,%edx //递归终止,返回%edx=0
8048bde: 7435 je 8048c15 <fun7+0x4b>
8048be0: 8b1a mov (%edx),%ebx
8048be2: 39cb cmp %ecx,%ebx
8048be4: 7e13 jle 8048bf9 <fun7+0x2f>
//若*A>b,将(A+4)作为地址进入递归
8048be6: 894c 24 04 mov %ecx,0x4(%esp)
8048bea: 8b42 04 mov 0x4(%edx),%eax
8048bed: 8904 24 mov %eax,(%esp)
8048bf0: e8d5 ff ff ff call 8048bca<fun7>
8048bf5: 01c0 add %eax,%eax //在此处将递归返回值加倍
8048bf7: eb1c jmp 8048c15 <fun7+0x4b>
8048bf9: b800 00 00 00 mov $0x0,%eax
8048bfe: 39cb cmp %ecx,%ebx
8048c00: 7413 je 8048c15 <fun7+0x4b>
//若*A<B,将(A+8)作为地址进入递归
8048c02: 894c 24 04 mov %ecx,0x4(%esp)
8048c06: 8b42 08 mov 0x8(%edx),%eax
8048c09: 8904 24 mov %eax,(%esp)
8048c0c: e8b9 ff ff ff call 8048bca<fun7>
//在此处将递归返回值加倍后在加1
8048c11: 8d44 00 01 lea 0x1(%eax,%eax,1),%eax
8048c15: 83c4 14 add $0x14,%esp
8048c18: 5b pop %ebx
8048c19: 5d pop %ebp
8048c1a: c3 ret
在调用完<fun7>之后,紧跟着cmp $0x5,%eax,即返回值必须为5。<fun7>分析如上,为递归函数,与第四题十分相似。递归最深处的返回值肯定为0,最外层返回值为5,可得出如下反递归过程:
A*2+1=5 - ->A=2 即有*A<B
A*2=2 -->A=1 有*A>B
A*2+1=1 - ->A=0 即有*A<B
也就是说在这三次递归中两次执行了“若*A<B将(A+8)作为地址进入递归”系列代码,一次执行了“若*A>b,将(A+4)作为地址进入递归”系列代码。使用gdb查询储存值:
最后得到0x2f,即使我们要输入的十进制值47。
9、 测试(其中3题4题不止一个答案)
【实验总结】
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX