课程主页: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