本文接 深入理解计算机系统(CSAPP)课程实验bomb程序炸弹实验日志(phase_5)写。关注bomb程序炸弹实验的最后一个关卡phase_6。
找到phase_6的代码,比前面几关都要长很多:
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
8048c92: 8d 45 d0 lea -0x30(%ebp),%eax
8048c95: 89 44 24 04 mov %eax,0x4(%esp)
8048c99: 8b 45 08 mov 0x8(%ebp),%eax
8048c9c: 89 04 24 mov %eax,(%esp)
8048c9f: e8 67 04 00 00 call 804910b
8048ca4: be 00 00 00 00 mov $0x0,%esi
8048ca9: 8d 7d d0 lea -0x30(%ebp),%edi
8048cac: 8b 04 b7 mov (%edi,%esi,4),%eax
8048caf: 83 e8 01 sub $0x1,%eax
8048cb2: 83 f8 05 cmp $0x5,%eax
8048cb5: 76 05 jbe 8048cbc
8048cb7: e8 15 04 00 00 call 80490d1
8048cbc: 83 c6 01 add $0x1,%esi
8048cbf: 83 fe 06 cmp $0x6,%esi
8048cc2: 74 22 je 8048ce6
8048cc4: 8d 1c b7 lea (%edi,%esi,4),%ebx
8048cc7: 89 75 b4 mov %esi,-0x4c(%ebp)
8048cca: 8b 44 b7 fc mov -0x4(%edi,%esi,4),%eax
8048cce: 3b 03 cmp (%ebx),%eax
8048cd0: 75 05 jne 8048cd7
8048cd2: e8 fa 03 00 00 call 80490d1
8048cd7: 83 45 b4 01 addl $0x1,-0x4c(%ebp)
8048cdb: 83 c3 04 add $0x4,%ebx
8048cde: 83 7d b4 05 cmpl $0x5,-0x4c(%ebp)
8048ce2: 7e e6 jle 8048cca
8048ce4: eb c6 jmp 8048cac
8048ce6: bb 00 00 00 00 mov $0x0,%ebx
8048ceb: 8d 7d d0 lea -0x30(%ebp),%edi
8048cee: eb 16 jmp 8048d06
8048cf0: 8b 52 08 mov 0x8(%edx),%edx
8048cf3: 83 c0 01 add $0x1,%eax
8048cf6: 39 c8 cmp %ecx,%eax
8048cf8: 75 f6 jne 8048cf0
8048cfa: 89 54 b5 b8 mov %edx,-0x48(%ebp,%esi,4)
8048cfe: 83 c3 01 add $0x1,%ebx
8048d01: 83 fb 06 cmp $0x6,%ebx
8048d04: 74 16 je 8048d1c
8048d06: 89 de mov %ebx,%esi
8048d08: 8b 0c 9f mov (%edi,%ebx,4),%ecx
8048d0b: ba c4 c0 04 08 mov $0x804c0c4,%edx
8048d10: b8 01 00 00 00 mov $0x1,%eax
8048d15: 83 f9 01 cmp $0x1,%ecx
8048d18: 7f d6 jg 8048cf0
8048d1a: eb de jmp 8048cfa
8048d1c: 8b 5d b8 mov -0x48(%ebp),%ebx
8048d1f: 8b 45 bc mov -0x44(%ebp),%eax
8048d22: 89 43 08 mov %eax,0x8(%ebx)
8048d25: 8b 55 c0 mov -0x40(%ebp),%edx
8048d28: 89 50 08 mov %edx,0x8(%eax)
8048d2b: 8b 45 c4 mov -0x3c(%ebp),%eax
8048d2e: 89 42 08 mov %eax,0x8(%edx)
8048d31: 8b 55 c8 mov -0x38(%ebp),%edx
8048d34: 89 50 08 mov %edx,0x8(%eax)
8048d37: 8b 45 cc mov -0x34(%ebp),%eax
8048d3a: 89 42 08 mov %eax,0x8(%edx)
8048d3d: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)
8048d44: be 00 00 00 00 mov $0x0,%esi
8048d49: 8b 43 08 mov 0x8(%ebx),%eax
8048d4c: 8b 13 mov (%ebx),%edx
8048d4e: 3b 10 cmp (%eax),%edx
8048d50: 7d 05 jge 8048d57
8048d52: e8 7a 03 00 00 call 80490d1
8048d57: 8b 5b 08 mov 0x8(%ebx),%ebx
8048d5a: 83 c6 01 add $0x1,%esi
8048d5d: 83 fe 05 cmp $0x5,%esi
8048d60: 75 e7 jne 8048d49
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
8048caf到8048cb7的代码说明,第一个数的值要小于等于6。否则将引爆炸弹。
从0x8048cbc开始,%esi的值开始从1往6递增,直到等于6时才跳转到0x8048ce6,否则将执行8048cc4。这里应该是双层循环的外层。
8048cc4将当前元素的地址传入寄存器ebx保存,并将当前的偏移量(数组的下标)保存到-0x4c(%ebp)位置暂存起来,这里应该是内层循环的起点。再把数组的下一个元素,也就是-0x4(%edi,%esi,4)位置的元素放入寄存器eax并将两者比较,如果不相等则跳转到0x8048cd7位置,否则将引爆炸弹。即第一个数不能和第二个数相等。
再看0x8048cd7位置,将前面暂存在-0x4c(%ebp)位置的值取出并加1,然后%ebx加4,指向数组的下一个元素。
0x8048cde位置,将5和-0x4c(%ebp)位置的数,也就是内层循环的控制条件进行比较,当小于等于5时,说明内层循环还未结束,跳转至0x8048cca继续比较当前元素和下一个元素,需要满足的条件同样是两个元素不相等,直到内层循环结束,0x8048ce4跳转至0x8048cac处,重新开始外层循环。
至此可以分析出第一部分代码的意思是要求输入的6个数据彼此都不能相等。所以这6个数据应该是大于0而小于等于6的。
当0x8048cc2处的跳转条件满足时,将结束整个循环,跳转至0x8048ce6处。
0x8048ce6开始执行一个新的操作,将寄存器ebx的值清零,同样将地址-0x30(%ebp)传给%edi,然后直接跳转至0x8048d06处。
0x8048d06开始先是将%ebx(也就是0)赋给%esi,使%esi获得初始值0,再将(%edi,%ebx,4)位置的数取出,存入%ecx中,将立即数0x804c0c4存入寄存器%edx中。
0x8048d10开始将%eax的值变为1,将%ecx中的值(也就是内存中(%edi,%ebx,4)位置的数)和1做比较,如果大于1,则跳转至0x8048cf0,否则跳转至0x8048cfa。
转向8048cf0位置,从8048cf0到8048cf8是一个循环,每循环一次,就更新一次%edx中的值,更新为内存地址为0x8(%edx)中的值,从这样的操作可以发现,地址的下一个元素还是地址,由此可以联想到的结构就是链表。直到%eax中的值和%ecx中的值相等为止。如果相等,就来到0x8048cfa位置。
0x8048cfa处开始是一个mov指令,将前面那个循环产生的最终的%edx中的值存入-0x48(%ebp,%esi,4)位置,若为第一轮操作,就是-0x48(%ebp),若为第二轮,就是-0x44(%ebp),依此次序每次向上递增4个单元。(因为%esi,%ebx的值是变化的)
随后%ebx加1,再和6做比较,不相等则继续从0x8048d06重新开始,若相等,也就是6轮循环结束,则跳转至0x8048d1c位置。
可以发现,这个6轮的循环总是会执行0x8048cfa开始的这一小部分代码,也就是说会有6组不同的%edx被传入内存-0x48(%ebp)开始向后的6个空间。
接下来对0x8048ce6开始的这部分代码进行调试,可以清楚地看到,寄存器%ecx中的值经历了6个不同的值。而这6个值正是我们在调试的时候输入的。
并且,按不同的顺序输入6个数,每次存入对应内存位置的地址也不相同。
六轮循环结束后,跳转至8048d1c位置。
与前面的代码实现的功能做一个类比可以发现,这里一连串的mov指令应该是将链表中的元素做一个新的赋值映射。
同时,0x8048d44开始的比较+循环结构实现的功能是判断链表元素是否是一个递增的序列。
再次进入gdb调试,查看地址0x804c0c4(%edx)中的内容。
然后每次将%edx偏移0x8,查看后会发现,首先edx中是内容,然后edx+0x8内的内容是一个地址,然后再查看这个地址,地址内的内容是一个内容,然后在将这个地址偏移0x8得到的地址内的内容又是一个地址,可以看出来,这是一个典型的链表结构。链表就是不同的node节点,每个node节点在内存中存在不同的位置,不连续的位置中。每个node由内容和next指针构成,每个next指针内的值是一个指向下一个node节点的地址,最后一个node的next指向null。
这样结合前面的分析就可以知道,我们输入的6个数实际上就是不同的node节点数,根据我输入的节点号,把节点内的内容重新进行排序,排成递增序列。
将节点值按递增顺序排列,则节点号的顺序应该为:
5,6,1,4,3,2
测试一下这个答案:
提示炸弹已解除。