CMU 15-213 Lab2 Bomb Lab
课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html
课程资料:https://github.com/EugeneLiu/translationCSAPP
课程视频:https://www.bilibili.com/video/av31289365/
这一部分回顾CSAPP的Bomb Lab。
x86-64参考资料:
https://web.stanford.edu/class/archive/cs/cs107/cs107.1166/guide_x86-64.html
https://cs.brown.edu/courses/cs033/docs/guides/x64_cheatsheet.pdf
首先进行利用反汇编命令得到方便阅读的代码:
objdump -d bomb
phase_1
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp
400ee4: be 00 24 40 00 mov $0x402400,%esi
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
edi存储参数1,为string的第一个字符的地址;esi存储参数2,也为string的第一个字符的地址,注意esi的地址为0x402400,所以利用如下操作即可:
gdb bomb
print (char *) 0x402400
得到结果为:
$1 = 0x402400 "Border relations with Canada have never been better."
所以这部分的结果为:
Border relations with Canada have never been better.
phase_2
这部分先简单介绍gdb的使用方法。
首先运行如下命令:
gdb bomb
然后设置断点:
break read_six_numbers
利用如下命令逐条查阅:
stepi
stepi 4
finish
查看寄存器的值:
print /x $rbx
print *(int *) $rbx
print (char *) 0xbfff890
接下来正式讨论phase_2函数。
首先阅读read_six_numbers函数,逐条调试之后可以得到如下命:
401480: be c3 25 40 00 mov $0x4025c3,%esi
对应的字符串为:
"%d %d %d %d %d %d"
说明输入为6个整数,然后尝试如下整数:
1 2 3 4 5 6
然后回到phase_2函数,如下命令检查整数读取是否正确:
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
如果通过检查,则进入如下命令:
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx #rbx=&(rsp+0x4)
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp #rbp=&(rsp+0x18)
打印上述寄存器可以得到rsp表示第一个参数的地址,rbx表示第二个参数的地址,rbp存储某个特殊地址(不对应某个具体的参数,大于第5个参数的地址,小于第6个参数的地址)。
然后运行如下代码
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
400f1c: 39 03 cmp %eax,(%rbx)
上述代码首先将rax设置为rbx前一个位置的地址,在当前情形即rax存储第一个参数的地址;第二步将rax对应的参数乘以2,然后和rbx存储的参数比较——即判断第二个参数是否是第一个参数的两倍,如果通过上述检验则进入第三步:
400f25: 48 83 c3 04 add $0x4,%rbx
400f29: 48 39 eb cmp %rbp,%rbx
第一步更新rbx的位置,对于当前情形rbx表示第三个参数的地址,第二步将rbx和rbp比较,这说明rbp起到上界的作用,如果通过上述测试则回到400f17地址的命令。
总结:上述代码是判断输入是否为以2为公比的等比级数,所以答案为:
1 2 4 8 16 32
phase_3
首先阅读如下汇编码:
400f51: be cf 25 40 00 mov $0x4025cf,%esi
打印0x4025cf处的信息:
print (char *) 0x4025cf
得到如下结果:
1 = 0x4025cf "%d %d"
说明首先读取两个数字,即我们的输入为$d_1\ d_2$的形式,这两个参数分别利用如下命令保存:
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
注意到如下命令:
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
该命令将第3个参数($d_1$)与0x7比较,这表示$d_1\le 7$。
接着如下命令根据$d_1$进行跳转:
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
利用如下命令打印0x402470的内容:
print /x *0x402470
0x400f7c
该地址对应如下汇编码:
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
上述代码的效果是让rax存储$\text{0xcf}=207$。
所以
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
实际上是进行条件分支,分支中rax寄存器中存储值。
上述分支最后都会跳转到400fbe:
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
注意到0xc(%rsp)对应第四个参数($d_2$),所以上述命令是将rax寄存器的值和第四个参数($d_2$)进行比较,以上述例子来说,假设$d_1=0$,那么$d_2=\text{0xcf}=207$,所以一个合理的结果为
0 207
phase_4
首先阅读如下汇编码:
400f51: be cf 25 40 00 mov $0x4025cf,%esi
打印0x4025cf处的信息:
print (char *) 0x4025cf
得到如下结果:
1 = 0x4025cf "%d %d"
说明首先读取两个数字,即我们的输入为$d_1\ d_2$的形式,这两个参数分别利用如下命令保存:
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
然后如下命令比较$d_1$和$14$的大小:
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
如果小于等于$14$则调用函数func4,命令如下:
40103a: ba 0e 00 00 00 mov $0xe,%edx
40103f: be 00 00 00 00 mov $0x0,%esi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi
401048: e8 81 ff ff ff callq 400fce <func4>
func4的三个参数分别为$d_1,0,14$,接着利用如下命令判断返回值是否为$0$:
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
接下来进入函数func4,如下命令判断$d_1$和$(0+14)/2=7$的大小:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx
400fdb: 01 c8 add %ecx,%eax
400fdd: d1 f8 sar %eax
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
如果$d_1$小于等于$7$,则利用如下命令进行递归,注意此时参数为$d_1,0,6$:
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>
从上述命令中不难看出func4是类似二分查找的函数。
接着看后续代码:
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
由于我们需要返回值为$0$,下列命令会将返回值赋值为$0$:
400ff2: b8 00 00 00 00 mov $0x0,%eax
如果进行了递归,则如下命令会使得返回值不为$0$:
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
而不进行递归的情形有如下命令决定(比较$d_1$和$7$):
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
不难看出唯一满足上述条件的$d_1$应该为$7$。
接下来回到phase_4,如下命令判断$d_2$是否为$0$:
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
所以输入为
7 0
phase_5
代码第一部分是读取长度为6的字符串:
40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2 <phase_5+0x70>
然后进行跳转并进行初始化:
4010d2: b8 00 00 00 00 mov $0x0,%eax
4010d7: eb b2 jmp 40108b <phase_5+0x29>
如下部分是进行6次循环:
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
循环后进行比较:
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
打印出常量处的值:
print (char *) 0x40245e
$16 = 0x40245e "flyers"
说明是将0x10(%rsp)的字符串和上述字符进行比较,为了后续讨论方便,假设我们的输入为”flyers”。
接着分析循环处的代码,首先打印常量处的值,得到字符串$s_1$:
print (char *) 0x4024b0
$15 = 0x4024b0 <array> "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
然后打印rbx处的字符:
print (char *) $rbx
$3 = 0x6038c0 <input_strings+320> "flyers"
说明rbx存储输入字符串的首地址,于是如下命令是取输入字符串的第rax个位置的地址:
(%rbx,%rax,1),%ecx
如下命令将字符串第rax个位置的值(ascii码)存储到rdx:
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
如下命令将ascii码模16(取末4位)得到偏移位置:
401096: 83 e2 0f and $0xf,%edx
如下命令表示取$s_1$的第rax个字符:
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
最后将值该字符复制到0x10(%rsp)+rax的位置上:
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
代码分析完毕,上述任务可以用如下python代码完成:
s1 = "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
s2 = "flyers"
#计算索引的值
index = []
for s in s2:
index.append(s1.find(s))
#找到对应的字符
res = ""
for i in index:
#ascii码范围
for j in range(97, 123):
if j % 16 == i:
res += chr(j)
break
print(res)
最终结果为
ionefg
phase_6
phase_6个人感觉非常难,花了大半天才解决,下面逐步分析。
首先读取6个数并判断第一个数是否小于等于6:
401106: e8 51 03 00 00 callq 40145c <read_six_numbers>
40110b: 49 89 e6 mov %rsp,%r14 #r14=rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d #r12d=0x0
401114: 4c 89 ed mov %r13,%rbp #rbp=r13(第一个数)
401117: 41 8b 45 00 mov 0x0(%r13),%eax #rax=r13+0x0(第一个数)
40111b: 83 e8 01 sub $0x1,%eax #rax=rax-0x1
40111e: 83 f8 05 cmp $0x5,%eax #rax-0x5(判断输入是否为6个数字)
401121: 76 05 jbe 401128 <phase_6+0x34> #小于等于
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
然后判断每个整数是否两两不同:
401114: 4c 89 ed mov %r13,%rbp #rbp=r13(第一个数)
401117: 41 8b 45 00 mov 0x0(%r13),%eax #rax=r13+0x0(第一个数)
40111b: 83 e8 01 sub $0x1,%eax #rax=rax-0x1
40111e: 83 f8 05 cmp $0x5,%eax #rax-0x5(判断输入是否为6个数字)
401121: 76 05 jbe 401128 <phase_6+0x34> #小于等于
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d #r12d=r12d+0x1(r12d计数器)
40112c: 41 83 fc 06 cmp $0x6,%r12d #r12d-0x6
401130: 74 21 je 401153 <phase_6+0x5f> #为0则跳转
401132: 44 89 e3 mov %r12d,%ebx #rbx=r12d(计数器)
401135: 48 63 c3 movslq %ebx,%rax #rax=ebx(带符号32转64)
401138: 8b 04 84 mov (%rsp,%rax,4),%eax #rax=rsp+4*rax(输入的第rax个数)
40113b: 39 45 00 cmp %eax,0x0(%rbp) #rbp+0x0-eax(比较)
40113e: 75 05 jne 401145 <phase_6+0x51> #不相等则跳转
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx #rbx=rbx+0x1(计数器)
401148: 83 fb 05 cmp $0x5,%ebx #rbx-0x5
40114b: 7e e8 jle 401135 <phase_6+0x41> #小于等于则跳转
40114d: 49 83 c5 04 add $0x4,%r13 #r13=r13+0x4
401151: eb c1 jmp 401114 <phase_6+0x20> #跳转
然后将输入的每个值变成$7-x$得到$d_1,d_2,d_3,d_4,d_5$
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi #rsi=rsp+0x18(0x18=24)
401158: 4c 89 f0 mov %r14,%rax #rax=r14(第一个参数,6)
40115b: b9 07 00 00 00 mov $0x7,%ecx #rcx=0x7
401160: 89 ca mov %ecx,%edx #rdx=ecx
401162: 2b 10 sub (%rax),%edx #rdx=rdx-rax(做差,1)
401164: 89 10 mov %edx,(%rax) #*rax=edx(第一个参数变成1)
401166: 48 83 c0 04 add $0x4,%rax #rax=rax+0x4(第二个参数)
40116a: 48 39 f0 cmp %rsi,%rax #rax-rsi(当前值-阈值)
40116d: 75 f1 jne 401160 <phase_6+0x6c> #不相等则跳转(进行循环)
后续部分出现常量0x6032d0,打印附近的值可得:
(gdb) print *(int *) 0x6032d0
$72 = 332
(gdb) print *(int *) (0x6032d0+0x8)
$73 = 6304480
(gdb) print *(int *) (0x6032d0+0x8*2)
$74 = 168
(gdb) print *(int *) (0x6032d0+0x8*3)
$75 = 6304496
(gdb) print *(int *) (0x6032d0+0x8*4)
$76 = 924
(gdb) print *(int *) (0x6032d0+0x8*5)
$77 = 6304512
(gdb) print *(int *) (0x6032d0+0x8*6)
$78 = 691
(gdb) print *(int *) (0x6032d0+0x8*7)
$79 = 6304528
(gdb) print *(int *) (0x6032d0+0x8*8)
$80 = 477
(gdb) print *(int *) (0x6032d0+0x8*9)
$81 = 6304544
(gdb) print *(int *) (0x6032d0+0x8*10)
$82 = 443
(gdb) print *(int *) (0x6032d0+0x8*11)
$48 = 6304464
然后如下代码将$\text{0x6032d0+0x8(2i-1)}$处的值赋值为$\text{0x6032d0+0x8}(2d_i-2)$处的值:
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx #rdx=rdx+0x8(这部分加16)
40117a: 83 c0 01 add $0x1,%eax #rax=rax+0x1
40117d: 39 c8 cmp %ecx,%eax #rax-rcx(循环rcx-1次,rsi为索引)
40117f: 75 f5 jne 401176 <phase_6+0x82> #不相等则跳转
401181: eb 05 jmp 401188 <phase_6+0x94>
401183: ba d0 32 60 00 mov $0x6032d0,%edx #rdx=$0x6032d0(此处的值为332)
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) #rsp+2*rsi+0x20=rdx
40118d: 48 83 c6 04 add $0x4,%rsi #rsi=rsi+0x4
401191: 48 83 fe 18 cmp $0x18,%rsi #rsi-0x18
401195: 74 14 je 4011ab <phase_6+0xb7> #为0则调用
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx #rcx=rsp+rsi*1(rcx表示第k个数)
40119a: 83 f9 01 cmp $0x1,%ecx #rcx-0x1(第二个数-1)
40119d: 7e e4 jle 401183 <phase_6+0x8f> #小于等于则跳转
40119f: b8 01 00 00 00 mov $0x1,%eax #rax=0x1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx #rdx=$0x6032d0
4011a9: eb cb jmp 401176 <phase_6+0x82> #跳转
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx #rbx=rsp+0x20
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax #rax=rsp+0x28
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi #rsi=rsp+0x50(上界)
4011ba: 48 89 d9 mov %rbx,%rcx #rcx=rbx
4011bd: 48 8b 10 mov (%rax),%rdx #rdx=*rax(R[rax])
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) #rcx+0x8=rdx(rcx后一个位置取rdx)
4011c4: 48 83 c0 08 add $0x8,%rax #rax=rax+0x8(这一步加了16)
4011c8: 48 39 f0 cmp %rsi,%rax #rax-rsi
4011cb: 74 05 je 4011d2 <phase_6+0xde> #相等则跳转
4011cd: 48 89 d1 mov %rdx,%rcx #rcx=rdx(更新rcx)
4011d0: eb eb jmp 4011bd <phase_6+0xc9> #跳转
最后一部分判断$\text{0x6032d0+0x8*(2i-1)}(i=0,\ldots,5)$是否按降序排列:
4011da: bd 05 00 00 00 mov $0x5,%ebp #rbp=0x5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax #rax=rbx+0x8(rbx的位置应该存储值,加8为地址)
4011e3: 8b 00 mov (%rax),%eax #rax=*rax
4011e5: 39 03 cmp %eax,(%rbx) #*rbx-rax
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> #大于等于则跳转
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx #rbx=rbx+0x8
4011f2: 83 ed 01 sub $0x1,%ebp #rbp=rbp-0x1
4011f5: 75 e8 jne 4011df <phase_6+0xeb> #不相等则跳转
注意$\text{0x6032d0+0x8*}(2i-2)$的值为
即$d_i$为
原始的输入为$7-d_i$
即
4 3 2 1 6 5