CSAPP bomblab记录(未完待续)

CSAPP bomblab 记录

这是一个有趣的反汇编以及读懂汇编语言的 lab,很有挑战性也很有难度。需要运用 gdb 调试工具完成 lab。

主要用到的工具是 gdb ,使用 “gdb bomb” 指令进入 gdb 工具

以下是常用指令:

b [*address] : 在 address 处打断点,当用 continue 的时候程序会停在下一个断点处。
d [id] : 删除第 id 个断点。
i r [reg] : 查询寄存器 reg 中的值
x/[type] [address] : 查询地址 address 中的值
十进制即为 x/o

phase1

这个 phase 是简单的指令运用。

   0x0000000000400ee0 <+0>:	sub    $0x8,%rsp
=> 0x0000000000400ee4 <+4>:	mov    $0x402400,%esi #move了一个地址,之后进行了比较 string 是否相等,打印一下这个地址的值得到一句话。即为答案。
   0x0000000000400ee9 <+9>:	call   0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:	test   %eax,%eax
   0x0000000000400ef0 <+16>:	je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:	call   0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:	add    $0x8,%rsp
   0x0000000000400efb <+27>:	ret

phase2

while 循环的运用。
这个先随便输入 6 个数,然后每次 stepi 去查 %rbx 所存地址中的值和 %eax 中存储的值,然后注意到 <+30> 这个地方是累加操作。而 %eax 又是读的上一个 %rbx 的地址, %rbx 每次加 4 ,读到下一个数,因此下一个数必须是上一个数的 2 倍。

   0x0000000000400efc <+0>:	push   %rbp
   0x0000000000400efd <+1>:	push   %rbx
   0x0000000000400efe <+2>:	sub    $0x28,%rsp #分配栈空间
   0x0000000000400f02 <+6>:	mov    %rsp,%rsi
   0x0000000000400f05 <+9>:	call   0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp) #第一个值要是1
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:	call   0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:	jmp    0x400f30 <phase_2+52>
=> 0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:	add    %eax,%eax #可以发现这里每次是 %eax 倍增,搞个几次后发现是1 2 4 8 16 32
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx)
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:	call   0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:	add    $0x4,%rbx
   0x0000000000400f29 <+45>:	cmp    %rbp,%rbx
   0x0000000000400f2c <+48>:	jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:	jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:	lea    0x4(%rsp),%rbx
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:	add    $0x28,%rsp
   0x0000000000400f40 <+68>:	pop    %rbx
   0x0000000000400f41 <+69>:	pop    %rbp
   0x0000000000400f42 <+70>:	ret

phase3

这个 phase 主要是条件控制,通过跳转实现 switch 的功能

先简单观察,尝试几次,发现要读入两个数字 x , y ,读入后看到底下有一个 x 与 7 比较,是 unsigned 类型的比较,因此 x 是 0 - 7 间的数。

那么输入正确的第一个参数后,去查询其跳转的位置,可以看到读入 %eax 中的数字,计算出来后即可得到正确答案。

   0x0000000000400f43 <+0>:	sub    $0x18,%rsp
   0x0000000000400f47 <+4>:	lea    0xc(%rsp),%rcx
   0x0000000000400f4c <+9>:	lea    0x8(%rsp),%rdx
   0x0000000000400f51 <+14>:	mov    $0x4025cf,%esi
   0x0000000000400f56 <+19>:	mov    $0x0,%eax
   0x0000000000400f5b <+24>:	call   0x400bf0 <__isoc99_sscanf@plt> #调用sscanf读数,返回值就是参数数量,但是实测3个数%eax也是2
   0x0000000000400f60 <+29>:	cmp    $0x1,%eax #在这里卡了很久,这个就是参数小于等于1直接炸。
   0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39>
   0x0000000000400f65 <+34>:	call   0x40143a <explode_bomb>
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp)
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106>
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax #这个地方是你读入的第一个参数
   0x0000000000400f75 <+50>:	jmp    *0x402470(,%rax,8) #根据你读入的,按比例寻址跳转,跳到*(0x402470+8*rax)
   0x0000000000400f7c <+57>:	mov    $0xcf,%eax
   0x0000000000400f81 <+62>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f83 <+64>:	mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:	mov    $0x100,%eax
   0x0000000000400f8f <+76>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:	mov    $0x185,%eax
   0x0000000000400f96 <+83>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:	mov    $0xce,%eax
   0x0000000000400f9d <+90>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:	mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fa6 <+99>:	mov    $0x147,%eax
   0x0000000000400fab <+104>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:	call   0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:	mov    $0x0,%eax
   0x0000000000400fb7 <+116>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:	mov    $0x137,%eax
   0x0000000000400fbe <+123>:	cmp    0xc(%rsp),%eax #这个地方的第一个参数是你读入的第二个参数,实现比较
   0x0000000000400fc2 <+127>:	je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:	call   0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:	add    $0x18,%rsp
   0x0000000000400fcd <+138>:	ret

phase4

通过 func4 函数的调用,学习了递归对应的汇编指令,

主函数对应部分:

   0x000000000040100c <+0>:	sub    $0x18,%rsp   #分配栈空间
   0x0000000000401010 <+4>:	lea    0xc(%rsp),%rcx #
   0x0000000000401015 <+9>:	lea    0x8(%rsp),%rdx
   0x000000000040101a <+14>:	mov    $0x4025cf,%esi #输出地址中的内容,可以发现是 "%d %d",即应该输入两个整数。
   0x000000000040101f <+19>:	mov    $0x0,%eax
   0x0000000000401024 <+24>:	call   0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:	cmp    $0x2,%eax  #不是两个参数就直接炸
   0x000000000040102c <+32>:	jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:	cmpl   $0xe,0x8(%rsp) #第一个参数小于14
   0x0000000000401033 <+39>:	jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:	call   0x40143a <explode_bomb>
   0x000000000040103a <+46>:	mov    $0xe,%edx #这三个寄存器是参数 x=14, y=0, z=arg2
   0x000000000040103f <+51>:	mov    $0x0,%esi
   0x0000000000401044 <+56>:	mov    0x8(%rsp),%edi
=> 0x0000000000401048 <+60>:	call   0x400fce <func4> #调用 func4
   0x000000000040104d <+65>:	test   %eax,%eax  #result of func4
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:	cmpl   $0x0,0xc(%rsp) 检查第二个参数是否为0
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:	call   0x40143a <explode_bomb>
   0x000000000040105d <+81>:	add    $0x18,%rsp
   0x0000000000401061 <+85>:	ret

从 <+69> 可以看出第二个参数必须为 0,而第一个参数通过寄存器 %edi 传入 func4 中,需要满足 func4 的返回值是 0 且从 <+34> 处可以看出其本身小于 14。

func4 函数对应部分如下:

=> 0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # res=x
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # res-=y
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # t=res
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx # t>>=31
   0x0000000000400fdb <+13>:	add    %ecx,%eax # res+=t
   0x0000000000400fdd <+15>:	sar    %eax #这里sar正常情况下应该是有两个参数,一个参数是默认右移一位,即res>>=1
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx # t=res+y
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx # t-z<=0 -> <+36>
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx # x=t-1
   0x0000000000400fe9 <+27>:	call   0x400fce <func4> #递归调用,那么 x, y, z 已经更新。
   0x0000000000400fee <+32>:	add    %eax,%eax #res*=2,这里 %eax 是上次的 func4 所返回值
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax   #res=0
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx   #t>=z ? -> <+57>
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi #y=t+1;
   0x0000000000400ffe <+48>:	call   0x400fce <func4> #递归调用
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax  #res=res*2+1
   0x0000000000401007 <+57>:	add    $0x8,%rsp #释放栈空间,结束
   0x000000000040100b <+61>:	ret

这个 func4 的逻辑有些混乱,这也是汇编语言难读的原因之一,那么把它写成 C 语言的形式

int func4(int x,int y,int z)
{
   int res=x-y;
   res=(res+(res>>31))>>1;
   int t=res+y;
   if(z==t)return 0;
   else if(z<t)return func4(x,t+1,z)*2+1;
   else return func4(t-1,y,z)*2;
}

第一次调用的为 func4(14,0,arg2)

那么比较难搞的就是这个 res 和 t 究竟是在算什么,再考察,发现 res=(res+(res>>31))>>1 是 res 除以 2 并向负无穷舍入的操作。再加上最开始 y 是比 x 小,那么这个东西显然就是在 [x, y] 上进行二分,只不过写的比较奇怪罢了。 t 在这里就是二分时的 mid

那么由于返回值必须是 0,那么必须是每次都要保证 t>z,就是在二分的时候要么命中目标要么在左侧,于是手算一下,答案为 (7,0),(3,0),(1,0)。

phase6

这个部分的汇编代码很长。

先来看第一部分

   0x0000000000401100 <+12>:	mov    %rsp,%r13
   0x0000000000401103 <+15>:	mov    %rsp,%rsi
   0x0000000000401106 <+18>:	call   0x40145c <read_six_numbers>
   0x000000000040110b <+23>:	mov    %rsp,%r14
   0x000000000040110e <+26>:	mov    $0x0,%r12d
   0x0000000000401114 <+32>:	mov    %r13,%rbp
   0x0000000000401117 <+35>:	mov    0x0(%r13),%eax
   0x000000000040111b <+39>:	sub    $0x1,%eax
   0x000000000040111e <+42>:	cmp    $0x5,%eax
   0x0000000000401121 <+45>:	jbe    0x401128 <phase_6+52>  #这个位置打断点,可以发现第 x 次的 %eax 是你读入的第 x 个参数。
   0x0000000000401123 <+47>:	call   0x40143a <explode_bomb>

这个部分就是读入 6 个整数,然后分别存放在 %rsp 到 %rsp+20 里边。输出一下对应地址里的值可以很容易发现这件事情。

再来看第二部分

   0x0000000000401128 <+52>:	add    $0x1,%r12d #第一次来到这个地方的时候 %r12d 中的数是 0 ,这就是一个 for 循环里的 i 的作用
   0x000000000040112c <+56>:	cmp    $0x6,%r12d  # for(int i=1;i<6;i++)
   0x0000000000401130 <+60>:	je     0x401153 <phase_6+95>
   0x0000000000401132 <+62>:	mov    %r12d,%ebx #ebx=i
   0x0000000000401135 <+65>:	movslq %ebx,%rax #rax=ebx
   0x0000000000401138 <+68>:	mov    (%rsp,%rax,4),%eax #rax=arg(i) ,读相应的内存,参见上边
   0x000000000040113b <+71>:	cmp    %eax,0x0(%rbp)     #输出一下 %rbp 可以发现是第 x 个参数,所以这是个排列
   0x000000000040113e <+74>:	jne    0x401145 <phase_6+81>
   0x0000000000401140 <+76>:	call   0x40143a <explode_bomb>
   0x0000000000401145 <+81>:	add    $0x1,%ebx   #ebx+=1
   0x0000000000401148 <+84>:	cmp    $0x5,%ebx
   0x000000000040114b <+87>:	jle    0x401135 <phase_6+65>
   0x000000000040114d <+89>:	add    $0x4,%r13
   0x0000000000401151 <+93>:	jmp    0x401114 <phase_6+32>

前两部分就是个循环判断输入参数合法性,可以发现是检测输入序列是否是排列(没有数大于 6,没有两数重复),写成 C 语言代码如下:

for(int i=1;i<=6;i++)
{
   if(a[i]>6)explode_bomb();
   for(int j=i+1;j<=6;j++)
   {
      if(a[i]==a[j])explode_bomb();
   }
}

第三部分如下:

   0x0000000000401153 <+95>:	lea    0x18(%rsp),%rsi
   0x0000000000401158 <+100>:	mov    %r14,%rax
   0x000000000040115b <+103>:	mov    $0x7,%ecx
   0x0000000000401160 <+108>:	mov    %ecx,%edx
   0x0000000000401162 <+110>:	sub    (%rax),%edx
   0x0000000000401164 <+112>:	mov    %edx,(%rax)
   0x0000000000401166 <+114>:	add    $0x4,%rax
   0x000000000040116a <+118>:	cmp    %rsi,%rax
   0x000000000040116d <+121>:	jne    0x401160 <phase_6+108>
   0x000000000040116f <+123>:	mov    $0x0,%esi
   0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>

这个部分比较简单,就是把每个 arg 变为 7-arg,地址不变。

   0x000000000040116f <+123>:	mov    $0x0,%esi
   0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>

   #这几行是一个独立的模块,用来计算 (7-argi)*8+0x6032d0

   0x0000000000401176 <+130>:	mov    0x8(%rdx),%rdx
   0x000000000040117a <+134>:	add    $0x1,%eax
   0x000000000040117d <+137>:	cmp    %ecx,%eax
   0x000000000040117f <+139>:	jne    0x401176 <phase_6+130>


   0x0000000000401181 <+141>:	jmp    0x401188 <phase_6+148>
   0x0000000000401183 <+143>:	mov    $0x6032d0,%edx
   0x0000000000401188 <+148>:	mov    %rdx,0x20(%rsp,%rsi,2) # 把 %rdx 即 (7-argi)*8+0x6032d0 放到 %rsp+0x20+i*8 的地址中。
   0x000000000040118d <+153>:	add    $0x4,%rsi
   0x0000000000401191 <+157>:	cmp    $0x18,%rsi             #rsi<24
   0x0000000000401195 <+161>:	je     0x4011ab <phase_6+183>
   0x0000000000401197 <+163>:	mov    (%rsp,%rsi,1),%ecx     #最开始先跳转到这里,然后读出第 i 个参数即 7-argi 。
   0x000000000040119a <+166>:	cmp    $0x1,%ecx
   0x000000000040119d <+169>:	jle    0x401183 <phase_6+143>
   0x000000000040119f <+171>:	mov    $0x1,%eax
   0x00000000004011a4 <+176>:	mov    $0x6032d0,%edx
   0x00000000004011a9 <+181>:	jmp    0x401176 <phase_6+130>

那么 0x6032d0 这个地址,通过查询发现是长成这样的

0x6032d0 <node1>:	0x000000010000014c	0x00000000006032e0
0x6032e0 <node2>:	0x00000002000000a8	0x00000000006032f0
0x6032f0 <node3>:	0x000000030000039c	0x0000000000603300
0x603300 <node4>:	0x00000004000002b3	0x0000000000603310
0x603310 <node5>:	0x00000005000001dd	0x0000000000603320
0x603320 <node6>:	0x00000006000001bb	0x0000000000000000

是一个链表。但是目前还不知道有什么用,总之这个部分是把这些地址存入了栈中的一段连续位置。

第四部分

   0x00000000004011ab <+183>:	mov    0x20(%rsp),%rbx
   0x00000000004011b0 <+188>:	lea    0x28(%rsp),%rax     #rax是地址
   0x00000000004011b5 <+193>:	lea    0x50(%rsp),%rsi     #这个是上界
   0x00000000004011ba <+198>:	mov    %rbx,%rcx           #第 i 轮 %rcx 是第 i 个存放在上述地址中的数。%rax 为其后继,但是 %rax 存的是一个地址
   0x00000000004011bd <+201>:	mov    (%rax),%rdx         #取出下一个数,放进 %rcx
   0x00000000004011c0 <+204>:	mov    %rdx,0x8(%rcx)      #把 node i 中指向下一个节点的地址改了,改成 %rax 中的地址即下一个数
   0x00000000004011c4 <+208>:	add    $0x8,%rax
   0x00000000004011c8 <+212>:	cmp    %rsi,%rax
   0x00000000004011cb <+215>:	je     0x4011d2 <phase_6+222>
   0x00000000004011cd <+217>:	mov    %rdx,%rcx
   0x00000000004011d0 <+220>:	jmp    0x4011bd <phase_6+201>
   0x00000000004011d2 <+222>:	movq   $0x0,0x8(%rdx)

写成 C 语言代码

for(int i=1;i<6;i++)
{
   node[(7-argi)*8+0x6032d0].next=&node[(7-arg(i+1))*8+0x6032d0];
}
node[6].next=0;

最后这一块

   0x00000000004011da <+230>:	mov    $0x5,%ebp
   0x00000000004011df <+235>:	mov    0x8(%rbx),%rax
   0x00000000004011e3 <+239>:	mov    (%rax),%eax
   0x00000000004011e5 <+241>:	cmp    %eax,(%rbx)
   0x00000000004011e7 <+243>:	jge    0x4011ee <phase_6+250>  #比大小,内存大的地方数字要小,比的是 node 的第一个值
   0x00000000004011e9 <+245>:	call   0x40143a <explode_bomb>
   0x00000000004011ee <+250>:	mov    0x8(%rbx),%rbx
   0x00000000004011f2 <+254>:	sub    $0x1,%ebp
   0x00000000004011f5 <+257>:	jne    0x4011df <phase_6+235>

即链表重新连接后,要降序排列,否则爆炸。
那么观察最初链表中的值,可得,栈中序列是 3 4 5 6 1 2. (*8)+0x0x6032d0

那么原始序列就是 4 3 2 1 6 5。

其余部分待续。

posted @ 2025-07-04 01:30  shao0320  阅读(30)  评论(2)    收藏  举报
****************************************** 页脚Html代码 ******************************************