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。
其余部分待续。

浙公网安备 33010602011771号