实验内容及操作步骤:
1.实验题目分析和准备
首先分析bomb.c的C语言代码,我们可以得知本实验共分为六个部分外加一个隐藏关卡,每一部分都要求输入一个字符串,如果输入的字符串不正确则会触发炸弹爆炸实验失败,因此本实验的主要目标就是找到这几个合法的字符串,但是我们会发现无法直接阅读C语言的函数,不能从C语言代码中获得信息。但是题目中还有一个可执行文件,因此我们能想到可以反汇编可执行文件从反汇编出的汇编代码中获得操作的信息从而解决问题。
本实验在linux环境下进行,方便使用用gdb调试方法进行实验,因为本次实验反汇编出的汇编代码很长,在命令行中有时会显示不全,因此我们可以通过指令objdump -d bomb > bomb.txt 可以将反汇编出的汇编代码导入到一个txt文件,然后进行分析。
2.实验步骤和分析过程
首先,我们分析主函数的汇编代码发现,在主函数中依次调用了phase1到phase6这几个函数,由此我们可以推断,这六个函数应该是和我们要找的六个字符串相关,因此我们要分析这六个函数的汇编代码。
接下来进行这六个函数的汇编代码的分析:
Phase1:
08048b50
8048b50: 83 ec 1c sub $0x1c,%esp
8048b53: c7 44 24 04 8c a1 04 movl $0x804a18c,0x4(%esp)
8048b5a: 08
8048b5b: 8b 44 24 20 mov 0x20(%esp),%eax
8048b5f: 89 04 24 mov %eax,(%esp)
8048b62: e8 5d 04 00 00 call 8048fc4
8048b67: 85 c0 test %eax,%eax
8048b69: 74 05 je 8048b70
8048b6b: e8 66 05 00 00 call 80490d6
8048b70: 83 c4 1c add $0x1c,%esp
8048b73: c3 ret
分析:
分析汇编代码我们可以发现当调用了
那么可以利用gdb调试来查看0x804a18c这个地址内的内容:
查看到果然是一个字符串:
When a problem comes along, you must zip it!
再看前面的代码mov 0x20(%esp),%eax mov %eax,(%esp)两部分是吧我们输入的字符串作为参数传递给
因此这一题我们输入的字符串应该是:When a problem comes along, you must zip it!
验证:(正确进入下一关!)
Phase2:
08048b74
8048b74: 56 push %esi
8048b75: 53 push %ebx
8048b76: 83 ec 34 sub $0x34,%esp
8048b79: 8d 44 24 18 lea 0x18(%esp),%eax
8048b7d: 89 44 24 04 mov %eax,0x4(%esp)
8048b81: 8b 44 24 40 mov 0x40(%esp),%eax
8048b85: 89 04 24 mov %eax,(%esp)
8048b88: e8 7e 06 00 00 call 804920b
8048b8d: 83 7c 24 18 01 cmpl $0x1,0x18(%esp)
8048b92: 74 05 je 8048b99
8048b94: e8 3d 05 00 00 call 80490d6
8048b99: 8d 5c 24 1c lea 0x1c(%esp),%ebx
8048b9d: 8d 74 24 30 lea 0x30(%esp),%esi
8048ba1: 8b 43 fc mov -0x4(%ebx),%eax
8048ba4: 01 c0 add %eax,%eax
8048ba6: 39 03 cmp %eax,(%ebx)
8048ba8: 74 05 je 8048baf
8048baa: e8 27 05 00 00 call 80490d6
8048baf: 83 c3 04 add $0x4,%ebx
8048bb2: 39 f3 cmp %esi,%ebx
8048bb4: 75 eb jne 8048ba1
8048bb6: 83 c4 34 add $0x34,%esp
8048bb9: 5b pop %ebx
8048bba: 5e pop %esi
8048bbb: c3 ret
分析:
代码的开头部分是栈帧的创建过程和参数传递过程,我们可以注意到开头调用了
接下来的lea 0x1c(%esp),%ebx和mov -0x4(%ebx),%eax两条指令先把第二个数的地址存放进ebx,然后又把第一个数放进eax,接着add %eax,%eax指令相当于把,eax中的数据乘2,cmp %eax,(%ebx)和ebx的地址中的数据比较,也就是和输入的第2个数比较,如果不相等的话就会调用函数
因此如果不想引爆炸弹,我们输入的6个数中,后一个数必须是前一个数的2倍,并且第一个数是1,那么很容易就能得到了6个数分别是:1 2 4 8 16 32
因此这一题的答案是: 1 2 4 8 16 32
验证:(正确进入下一关!)
Phase3:
总的代码部分太长省略粘贴,只粘贴了重要的部分。
分析:
8048bbf: 8d 44 24 1c lea 0x1c(%esp),%eax
8048bc3: 89 44 24 0c mov %eax,0xc(%esp)
8048bc7: 8d 44 24 18 lea 0x18(%esp),%eax
8048bcb: 89 44 24 08 mov %eax,0x8(%esp)
8048bcf: c7 44 24 04 7f a3 04 movl $0x804a37f,0x4(%esp)
8048bd6: 08
8048bd7: 8b 44 24 30 mov 0x30(%esp),%eax
8048bdb: 89 04 24 mov %eax,(%esp)
8048bde: e8 8d fc ff ff call 8048870 <__isoc99_sscanf@plt>
8048be3: 83 f8 01 cmp $0x1,%eax
8048be6: 7f 05 jg 8048bed
8048be8: e8 e9 04 00 00 call 80490d6
8048bed: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)
8048bf2: 77 3c ja 8048c30
在汇编代码的开头部分,有一个指令movl $0x804a37f,0x4(%esp),我们在命令行gdb调试中输入指令x/s $0x804a37f查看,可以得到"%d %d",由此显示出这一题我们输入的应该是两个数字。开头的地址传送部分都是给函数<__isoc99_sscanf@plt>传送参数,把我们输入的数据写入栈帧,函数在调用<__isoc99_sscanf@plt>函数后,接着函数调用了而cmp $0x1,%eax表明输入参数多于1个。
并且cmpl $0x7,0x18(%esp)指令也告诉我们,我们输入的第一个数字必须小于等于7,否则就会引爆炸弹。
8048bf4: 8b 44 24 18 mov 0x18(%esp),%eax
8048bf8: ff 24 85 ec a1 04 08 jmp *0x804a1ec(,%eax,4)
8048bff: b8 dc 03 00 00 mov $0x3dc,%eax
8048c04: eb 3b jmp 8048c41
8048c06: b8 ab 03 00 00 mov $0x3ab,%eax
8048c0b: eb 34 jmp 8048c41
8048c0d: b8 76 03 00 00 mov $0x376,%eax
8048c12: eb 2d jmp 8048c41
8048c14: b8 71 03 00 00 mov $0x371,%eax
8048c19: eb 26 jmp 8048c41
8048c1b: b8 eb 00 00 00 mov $0xeb,%eax
8048c20: eb 1f jmp 8048c41
8048c22: b8 ac 02 00 00 mov $0x2ac,%eax
8048c27: eb 18 jmp 8048c41
8048c29: b8 75 03 00 00 mov $0x375,%eax
8048c2e: eb 11 jmp 8048c41
8048c30: e8 a1 04 00 00 call 80490d6
8048c35: b8 00 00 00 00 mov $0x0,%eax
8048c3a: eb 05 jmp 8048c41
8048c3c: b8 c1 00 00 00 mov $0xc1,%eax
8048c41: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048c45: 74 05 je 8048c4c
接着向下查看代码, mov 0x18(%esp),%eax把我们输入的第一个参数传递给eax;然后看到jmp *0x804a1ec(,%eax,4),这是switch跳转语句,即跳转到以地址*0x804a1ec为基址的跳转表中。我们可以查看这个跳转表中的地址元素,在gdb调试中输入x/8x 0x804a1ec可以查看到其中储存的地址。
我们可以对应这其中储存的地址找到代码中相应的指令,例如当eax为0时,指向的地址是0x8048bff,在代码中对应指令mov $0x3dc,%eax;jmp 8048c41
当然这道题的答案不唯一,从0到7一共有8种可能的答案,分别对应着跳转表中的8个地址,8个条指令,也对应着8组数据。
(0 988) (1 193) (2 939) (3 886) (4 881) (5 235) (6 684) (7 885)
在此我们就验证第一个答案:0 988
验证:(正确进入下一关!)
Phase4:
总的代码部分太长省略粘贴,只粘贴了重要的部分。
Phase4的代码前面部分和phase3很相似,也是调用了<__isoc99_sscanf@plt>函数传送参数,并且通过查看0x804a37f和指令cmp $0x2,%eax,可以知道也是输入了两个整数。
8048cd9: 8b 44 24 18 mov 0x18(%esp),%eax
8048cdd: 83 f8 01 cmp $0x1,%eax
8048ce0: 7e 05 jle 8048ce7
8048ce2: 83 f8 04 cmp $0x4,%eax
8048ce5: 7e 05 jle 8048cec
从这部分代码我们可以知道输入的其中一个数必须大于1小于等于4.
8048cec: 8b 44 24 18 mov 0x18(%esp),%eax
8048cf0: 89 44 24 04 mov %eax,0x4(%esp)
8048cf4: c7 04 24 09 00 00 00 movl $0x9,(%esp)
8048cfb: e8 50 ff ff ff call 8048c50
这几行代码显示了在调用func4函数前进行的参数传递过程,将输入的一个数字和数字9传递给func4,进入func4函数。最后将函数的返回值和我们输入的第一个数进行比较,正确时才成功。
08048c50
8048c50: 83 ec 1c sub $0x1c,%esp
8048c53: 89 5c 24 10 mov %ebx,0x10(%esp)
8048c57: 89 74 24 14 mov %esi,0x14(%esp)
8048c5b: 89 7c 24 18 mov %edi,0x18(%esp)
8048c5f: 8b 74 24 20 mov 0x20(%esp),%esi
8048c63: 8b 5c 24 24 mov 0x24(%esp),%ebx
8048c67: 85 f6 test %esi,%esi
8048c69: 7e 2b jle 8048c96
8048c6b: 83 fe 01 cmp $0x1,%esi
8048c6e: 74 2b je 8048c9b
8048c70: 89 5c 24 04 mov %ebx,0x4(%esp)
8048c74: 8d 46 ff lea -0x1(%esi),%eax
8048c77: 89 04 24 mov %eax,(%esp)
8048c7a: e8 d1 ff ff ff call 8048c50
8048c7f: 8d 3c 18 lea (%eax,%ebx,1),%edi
8048c82: 89 5c 24 04 mov %ebx,0x4(%esp)
8048c86: 83 ee 02 sub $0x2,%esi
8048c89: 89 34 24 mov %esi,(%esp)
8048c8c: e8 bf ff ff ff call 8048c50
8048c91: 8d 1c 07 lea (%edi,%eax,1),%ebx
8048c94: eb 05 jmp 8048c9b
8048c96: bb 00 00 00 00 mov $0x0,%ebx
8048c9b: 89 d8 mov %ebx,%eax
8048c9d: 8b 5c 24 10 mov 0x10(%esp),%ebx
8048ca1: 8b 74 24 14 mov 0x14(%esp),%esi
8048ca5: 8b 7c 24 18 mov 0x18(%esp),%edi
8048ca9: 83 c4 1c add $0x1c,%esp
8048cac: c3 ret
首先看func4部分的代码我们可以发现它在函数内部又调用了它本身,因此这是个递归函数。接着来判断这个函数的内部结构。我们对传入的两个参数暂时称作 x和y第一次进入时,x=9,y=a(也就是我们输入的数)。
从指令test %esi,%esi;jle 8048c96
剩下的部分也就是参数x大于1的时候,首先指令lea -0x1(%esi),%eax以及后面的指令我们可以看出,参数x把自己减1后又作为一个参数x-1和y传入下一层递归func4,并把返回值和y相加,传到edi,接着后面sub $0x2,%esi指令显示参数x把自己减2后又作为一个参数x-2和y传入下一层递归func4,并把返回值和之前的edi中储存的值相加传给ebx然后作为返回值返回。
以上就是这个func4函数递归的整个过程。整理一下我们可以用c语言来表示这个过程。
这就是这个函数递归的过程。由此可以很容易的写出对应的解,同样本题也不只有一个答案,这里我们验证的解是:176 2(只要第二个参数在1到4的范围内通过函数我们就能得到第一个参数也就能得到正确的解)
验证:(正确进入下一关!)(在这一关还有一个隐藏关卡的开启按钮后面会提到)
Phase5:
总的代码部分太长省略粘贴,只粘贴了重要的部分。
分析:
8048d0f: 53 push %ebx
8048d10: 83 ec 28 sub $0x28,%esp
8048d13: 8b 5c 24 30 mov 0x30(%esp),%ebx
8048d17: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048d1d: 89 44 24 1c mov %eax,0x1c(%esp)
8048d21: 31 c0 xor %eax,%eax
8048d23: 89 1c 24 mov %ebx,(%esp)
8048d26: e8 80 02 00 00 call 8048fab
8048d2b: 83 f8 06 cmp $0x6,%eax
8048d2e: 74 05 je 8048d35
代码的开头部分还是栈帧的准备阶段和输入函数的调用。从call 8048fab
8048d35: b8 00 00 00 00 mov $0x0,%eax
8048d3a: 0f be 14 03 movsbl (%ebx,%eax,1),%edx
8048d3e: 83 e2 0f and $0xf,%edx
8048d41: 0f b6 92 0c a2 04 08 movzbl 0x804a20c(%edx),%edx
8048d48: 88 54 04 15 mov %dl,0x15(%esp,%eax,1)
8048d4c: 83 c0 01 add $0x1,%eax
8048d4f: 83 f8 06 cmp $0x6,%eax
8048d52: 75 e6 jne 8048d3a
这一段的代码是一个循环,首先对第一个字符and $0xf,%edx取出来它的二进制表示的低4位,然后用基址变址寻址从0x804a20c(%edx)中找到相应的字符传到edx,在取低8位也就是正好一个char类型的字符传到0x15(%esp,%eax,1)这样的一个地址中去。然后循环对6个字符都进行这个操作。至此我们可能还看不出什么头绪,但是得到了一个字符串。往下继续看。
8048d54: c6 44 24 1b 00 movb $0x0,0x1b(%esp)
8048d59: c7 44 24 04 e2 a1 04 movl $0x804a1e2,0x4(%esp)
8048d60: 08
8048d61: 8d 44 24 15 lea 0x15(%esp),%eax
8048d65: 89 04 24 mov %eax,(%esp)
8048d68: e8 57 02 00 00 call 8048fc4
8048d6d: 85 c0 test %eax,%eax
8048d6f: 74 05 je 8048d76
这一部分调用了
这个内存中储存的字符串是 flames 也就是说前面通过计算变换得到的字符串也应该是flames,因此我们可以到这推回去,推测我们输入的字符串是什么,首先查看地址0x804a20c 里面存放的数据
可以看到前面有一串字符,因为flames是通过基址变址寻址得到的,因此通过这个字符串我们就能得到他们对应的偏移分别是9 15 1 0 5 7,而这些偏移正是通过我们输入的字符的ASCII码的后四位得到的,因此他们对应的二进制分别是1001 1111 0001 0000 0101 0111,然后从ASCII码表中找出对应的低位能对应上的字母即可,同样这一题的答案也是不唯一的。
在此我选取验证的字符串是:yoapuw
验证:(正确进入下一关!)
Phase6:
总的代码部分太长省略粘贴,只粘贴了重要的部分。
分析:
开始部分依然是栈帧的准备阶段,从函数
8048dab: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax
8048daf: 83 e8 01 sub $0x1,%eax
8048db2: 83 f8 05 cmp $0x5,%eax
从这几行可以看出输入的数字小于等于6,接着是一段循环代码:
8048dbc: 83 c6 01 add $0x1,%esi
8048dbf: 83 fe 06 cmp $0x6,%esi
8048dc2: 74 33 je 8048df7
8048dc4: 89 f3 mov %esi,%ebx
8048dc6: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax
8048dca: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4)
8048dce: 75 05 jne 8048dd5
8048dd0: e8 01 03 00 00 call 80490d6
8048dd5: 83 c3 01 add $0x1,%ebx
8048dd8: 83 fb 05 cmp $0x5,%ebx
8048ddb: 7e e9 jle 8048dc6
8048ddd: eb cc jmp 8048dab
这一段代码的功能就是通过循环判断这6个数字都是相互不相等的, mov 0x10(%esp,%ebx,4),%eax和 cmp %eax,0xc(%esp,%esi,4)两条指令通过不断地循环,把当前的数和之前的数进行比较必须都不相同才不会爆炸,经过6次循环之后,可以判断这六个数都互不相同。
8048ddf: 8b 52 08 mov 0x8(%edx),%edx
8048de2: 83 c0 01 add $0x1,%eax
8048de5: 39 c8 cmp %ecx,%eax
8048de7: 75 f6 jne 8048ddf
8048de9: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4)
8048ded: 83 c3 01 add $0x1,%ebx
8048df0: 83 fb 06 cmp $0x6,%ebx
8048df3: 75 07 jne 8048dfc
8048df5: eb 1c jmp 8048e13
8048df7: bb 00 00 00 00 mov $0x0,%ebx
8048dfc: 89 de mov %ebx,%esi
8048dfe: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx
8048e02: b8 01 00 00 00 mov $0x1,%eax
8048e07: ba 3c c1 04 08 mov $0x804c13c,%edx
8048e0c: 83 f9 01 cmp $0x1,%ecx
8048e0f: 7f ce jg 8048ddf
8048e11: eb d6 jmp 8048de9
这一部分代码也是一个循环,根据我们输入的6个数字,从首地址$0x804c13c开始,根据输入的数字的不同在内存中查找相应的存储单元中的数据存到栈空间内,通过查看内存的内容我们可以发现,这些单元的数据全部都是地址形式的。其实循环里面对于%ebx的迭代操作,很像链表的操作,把最初的那个赋给%edx的值看作是链表的头节点的地址,然后每一个节点都有一个指针域指向下一个节点,那么这个迭代过程就是在节点之间移动。将这六个节点的地址按照我们输入的数字的顺序,已经存入栈帧中间了。
8048e13: 8b 5c 24 28 mov 0x28(%esp),%ebx
8048e17: 8b 44 24 2c mov 0x2c(%esp),%eax
8048e1b: 89 43 08 mov %eax,0x8(%ebx)
8048e1e: 8b 54 24 30 mov 0x30(%esp),%edx
8048e22: 89 50 08 mov %edx,0x8(%eax)
8048e25: 8b 44 24 34 mov 0x34(%esp),%eax
8048e29: 89 42 08 mov %eax,0x8(%edx)
8048e2c: 8b 54 24 38 mov 0x38(%esp),%edx
8048e30: 89 50 08 mov %edx,0x8(%eax)
8048e33: 8b 44 24 3c mov 0x3c(%esp),%eax
8048e37: 89 42 08 mov %eax,0x8(%edx)
8048e3a: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)
这部分的代码是把节点中的数据和地址都按照我们输入的数据存放在了栈帧中。例如我们输入的第一个数字如果是4 那么此时栈帧中的第一个节点就是值为390的节点。
8048e41: be 05 00 00 00 mov $0x5,%esi
8048e46: 8b 43 08 mov 0x8(%ebx),%eax
8048e49: 8b 10 mov (%eax),%edx
8048e4b: 39 13 cmp %edx,(%ebx)
8048e4d: 7d 05 jge 8048e54
8048e4f: e8 82 02 00 00 call 80490d6
8048e54: 8b 5b 08 mov 0x8(%ebx),%ebx
8048e57: 83 ee 01 sub $0x1,%esi
8048e5a: 75 ea jne 8048e46
这部分代码是一个比较排序的循环,mov 0x8(%ebx),%eax;mov (%eax),%edx;cmp %edx,(%ebx) 这三个指令可以看出每次都取前一个节点的值和后一个比较,前一个节点的值必须大于后一个节点才不会爆炸。也就是说我们前面在输入6个数字进行排序的时候,内存中存储的值大的必须排在前面。通过查看内存的数据我们知道,6个值分别是16进制的67 1b9 12e 390 136 2b5 ,因此在输入数据时必须保证大的在前面,所以输入的顺序为4 6 2 5 3 1
这也就是这一题的答案,输入数字为:4 6 2 5 3 1
验证:(通过!)
至此前面的六关已经全部通过:
隐藏的关卡
Phase_defused:
因为当六关过了之后就直接退出了程序,没有进入其他关卡,的所以证明这个隐藏的关卡一定还有其他的触发机关才能触发,首先我们发现,在C语言的代码中每一个关卡后面都有一个phase_defused函数,但是在之前的解题中并没有使用到,因此我们查看这个函数的代码,发现其中有一个函数
0804925b
804925b: 81 ec 8c 00 00 00 sub $0x8c,%esp
8049261: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8049267: 89 44 24 7c mov %eax,0x7c(%esp)
804926b: 31 c0 xor %eax,%eax
804926d: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
8049274: 75 72 jne 80492e8
通过这一段代码我们可以看出出现了一个cmpl $0x6,0x804c3cc指令,因为正好是6,推测可能是和我们通过的关卡数有关,如果没有通过6关应该不会触发这个隐藏关卡。
8049276: 8d 44 24 2c lea 0x2c(%esp),%eax
804927a: 89 44 24 10 mov %eax,0x10(%esp)
804927e: 8d 44 24 28 lea 0x28(%esp),%eax
8049282: 89 44 24 0c mov %eax,0xc(%esp)
8049286: 8d 44 24 24 lea 0x24(%esp),%eax
804928a: 89 44 24 08 mov %eax,0x8(%esp)
804928e: c7 44 24 04 85 a3 04 movl $0x804a385,0x4(%esp)
8049295: 08
8049296: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
804929d: e8 ce f5 ff ff call 8048870 <__isoc99_sscanf@plt>
80492a2: 83 f8 03 cmp $0x3,%eax
80492a5: 75 35 jne 80492dc
前面这一部分代码传递了两个立即数地址给<__isoc99_sscanf@plt>函数,查看第一个地址中存放的
由此可以看出我们输入的数据应该是两个整数和一个字符串。
第二个地址提供了输入数据的地址位置,我们推测应该是在之前的某一个关卡中触发的,因此这个输入的地址应该和其中一个关卡的数据输入地址相同 ,于是我们查找每一个关卡数据输入的地址,正好发现第四关的数据输入的地址和这个地址相同。都是0x804c4d0.由此可以判断这个关卡在第四关触发。
80492a7: c7 44 24 04 8e a3 04 movl $0x804a38e,0x4(%esp)
80492ae: 08
80492af: 8d 44 24 2c lea 0x2c(%esp),%eax
80492b3: 89 04 24 mov %eax,(%esp)
80492b6: e8 09 fd ff ff call 8048fc4
80492bb: 85 c0 test %eax,%eax
80492bd: 75 1d jne 80492dc
80492bf: c7 04 24 54 a2 04 08 movl $0x804a254,(%esp)
80492c6: e8 35 f5 ff ff call 8048800
80492cb: c7 04 24 7c a2 04 08 movl $0x804a27c,(%esp)
80492d2: e8 29 f5 ff ff call 8048800
80492d7: e8 d7 fb ff ff call 8048eb3
这一段代码显示程序从地址0x804a38e中取了一个字符串和我们输入的字符串比较,相同时才能触发隐藏关卡,于是查看内存0x804a38e中的值是 DrEvil
因此在第四关的答案后输入 DrEvil 就可在最后进入隐藏关卡,经过验证正确
secret_phase:
总的代码部分太长省略粘贴,只粘贴了重要的部分。
08048eb3
8048eb3: 53 push %ebx
8048eb4: 83 ec 18 sub $0x18,%esp
8048eb7: e8 41 02 00 00 call 80490fd
8048ebc: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048ec3: 00
8048ec4: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048ecb: 00
8048ecc: 89 04 24 mov %eax,(%esp)
8048ecf: e8 0c fa ff ff call 80488e0
前面是栈帧准备的阶段,然后是给函数
8048ed4: 89 c3 mov %eax,%ebx
8048ed6: 8d 40 ff lea -0x1(%eax),%eax
8048ed9: 3d e8 03 00 00 cmp $0x3e8,%eax
8048ede: 76 05 jbe 8048ee5
8048ee0: e8 f1 01 00 00 call 80490d6
8048ee5: 89 5c 24 04 mov %ebx,0x4(%esp)
8048ee9: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp)
8048ef0: e8 6d ff ff ff call 8048e62
8048ef5: 83 f8 03 cmp $0x3,%eax
8048ef8: 74 05 je 8048eff
8048efa: e8 d7 01 00 00 call 80490d6
这一部分代码可以看出strtol函数的返回值不能大于0x3e8,否则就会爆炸。然后把0x804c088和刚才输入的数当做参数传送给func7,进入func7函数。而且返回值要等于3.
08048e62
8048e62: 53 push %ebx
8048e63: 83 ec 18 sub $0x18,%esp
8048e66: 8b 54 24 20 mov 0x20(%esp),%edx
8048e6a: 8b 4c 24 24 mov 0x24(%esp),%ecx
8048e6e: 85 d2 test %edx,%edx
8048e70: 74 37 je 8048ea9
8048e72: 8b 1a mov (%edx),%ebx
8048e74: 39 cb cmp %ecx,%ebx
8048e76: 7e 13 jle 8048e8b
8048e78: 89 4c 24 04 mov %ecx,0x4(%esp)
8048e7c: 8b 42 04 mov 0x4(%edx),%eax
8048e7f: 89 04 24 mov %eax,(%esp)
8048e82: e8 db ff ff ff call 8048e62
8048e87: 01 c0 add %eax,%eax
8048e89: eb 23 jmp 8048eae
8048e8b: b8 00 00 00 00 mov $0x0,%eax
8048e90: 39 cb cmp %ecx,%ebx
8048e92: 74 1a je 8048eae
8048e94: 89 4c 24 04 mov %ecx,0x4(%esp)
8048e98: 8b 42 08 mov 0x8(%edx),%eax
8048e9b: 89 04 24 mov %eax,(%esp)
8048e9e: e8 bf ff ff ff call 8048e62
8048ea3: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax
8048ea7: eb 05 jmp 8048eae
8048ea9: b8 ff ff ff ff mov $0xffffffff,%eax
8048eae: 83 c4 18 add $0x18,%esp
8048eb1: 5b pop %ebx
8048eb2: c3 ret
分析:
这又是一个递归函数,可以看出当我们传入的值和传入的地址储存的值相同时返回的是0;当地址内的值大于输入的数时,将当前的地加4,里面储存的地址作为一个新的地址,和输入的值一同传入下一个func7函数,并返回他的2倍;当地址内的值小于输入的数时,将当前的地加8,里面储存的地址作为一个新的地址,和输入的值一同传入下一个func7函数,并返回他的2倍加1。
等价的C语言代码如下:
类似于一棵搜索二叉树,因为最终要返回的值是3,所以第二层递归时一定是2*1+1的情况,因此通过gdb调试查看,得到第二次的地址为0x0804c0a8,然后第三层同理应该是2*0+1,所以第三层的地址为0x0804c0d0,而且因为这一层返回值为0,所以这个地址单元内的值就是,我们输入的那个数,gdb查看得到0x6b转化为十进制是107,因此这个关卡输入的数据是 107
经过验证正确:
至此,本实验所有的关卡及隐藏关卡都已经完成!
收获与体会:
通过这次实验,对于Linux系统的一些操作命令有了一些了解和掌握,学习了如何使用gdb这个强大的工具进行调试,以及加深了对于汇编语言的熟悉。
本次实验花费的时间较长,明显能感觉到对汇编语言的阅读能力和要求很高,难度也是循序渐进的,在做的时候必须思路清晰不能混乱,尤其是到了后面比较复杂的时候更不能乱。
比较有效的方法就是一步一步的来,最好能列一个表对寄存器的数据实时更新,这样看起来方便,也能对分析汇编代码有帮助。
本次实验遇到困难主要是在递归的部分,可能还是对递归的机器及表达不够熟悉,在phase4上花费了一些时间,最终还是克服了,也能写出它的C语言代码,还是很有成就感的。
经过这几个题目的洗礼对汇编语言也有了更深的理解,在阅读的时候也能更加的流畅,加深了对程序底层的理解,也能熟练地运用gdb调试工具进行代码的调试,内存数据和寄存器数据的查看,收获很大