Bomb lab主要涉及2个文件
- bomb:实验提供的可执行程序,需要反汇编和调试
- bomb.c:bomb的主函数源码
根据readme.pdf文档,就是有6段代码,需要自己反汇编bomb可执行文件,根据反汇编的文件,实现源文件里面的函数。
首先反汇编这个bomb文件
objdump -d ./bomb > bomb.dis
注:如果你忘记了这些寄存器都是干嘛的,请查看CSAPP 3.4章节的寄存器表,里面有写用途
phase1
首先反汇编中看到的phase_1代码如下
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp # 移动栈指针
400ee4: be 00 24 40 00 mov $0x402400,%esi # 将立即数传递到了rsi寄存器的低位esi
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> # 调用字符串函数
400eee: 85 c0 test %eax,%eax # 判断是否为0
400ef0: 74 05 je 400ef7 <phase_1+0x17> # 跳转到f7也就是爆炸下面的add指令
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> # 调用炸弹函数
400ef7: 48 83 c4 08 add $0x8,%rsp # 恢复栈
400efb: c3 retq
这里拷贝了一个立即数到esi,但是不知道这个是什么值,看到寄存器esi,那就是函数第一个参数的存储嘛,再看到第二个参数(phase_1里面没有,那就全局搜索phase_1),找到调用phase_1调用前有一个mov操作存放参数,这样就很清楚了。
400e37: 48 89 c7 mov %rax,%rdi #将第二个参数放到rdi寄存器
400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1>
接下来就是查看这个二进制值是多少,因为是字符串比较,将其以字符串形式打印出来,可以使用gdb查看下二进制值。
gdb bomb
x/s 0x402400
那就很明显了,最终phase_1代码如下。
void phase_1(char *input)
{
if(string_not_equal(input,"Border relations with Canada have never been better.")==0)
{
explode_bomb();
}
return ;
}
字符串为
Border relations with Canada have never been better.
那么对于bomb输入的字符串如下,phase1通过
phase_2
再看到phase2,这次比较复杂了,好像是各种的控制语句(一堆jump, cmp)。
0000000000400da0 <main>:
... ...
400e49: e8 c2 fc ff ff callq 400b10 <puts@plt>
400e4e: e8 4b 06 00 00 callq 40149e <read_line>
400e53: 48 89 c7 mov %rax,%rdi
400e56: e8 a1 00 00 00 callq 400efc <phase_2>
0000000000400efc <phase_2>:
# 保存现场
400efc: 55 push %rbp # callee saved 当前函数指针栈帧基地址, 等下跳转到其他函数
400efd: 53 push %rbx # callee saved
# 保存现场
400efe: 48 83 ec 28 sub $0x28,%rsp # 移动栈指针, 这里分配内存空间
400f02: 48 89 e6 mov %rsp,%rsi #
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> #读取字符串到数组里面, 从这里就知道前面是分配了6个元素大小空间
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) #将数组第一个元素和1比较, 这里是double word,就是4个字节, 那就是一个int[6]数组
400f0e: 74 20 je 400f30 <phase_2+0x34> # 如果相等就跳转
400f10: e8 25 05 00 00 callq 40143a <explode_bomb> # 不相等爆炸
400f15: eb 19 jmp 400f30 <phase_2+0x34>
phase_2+0x1b:
400f17: 8b 43 fc mov -0x4(%rbx),%eax # array[i-1]
400f1a: 01 c0 add %eax,%eax # 自加
400f1c: 39 03 cmp %eax,(%rbx) #比较array[i]和array[i-1]
400f1e: 74 05 je 400f25 <phase_2+0x29>
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>
phase_2+0x29: # if跳转
400f25: 48 83 c3 04 add $0x4,%rbx # array[i]
400f29: 48 39 eb cmp %rbp,%rbx #比较是否超过了数组范围
400f2c: 75 e9 jne 400f17 <phase_2+0x1b> #循环跳转
400f2e: eb 0c jmp 400f3c <phase_2+0x40> #结束
phase_2+0x34:
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx #保存了数组的第一个元素的地址
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp #偏移24字节,也就是数组的末尾
400f3a: eb db jmp 400f17 <phase_2+0x1b>
phase_2+0x40: #结束函数,恢复现场
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
read_six_number
phase2调用了read_six_number,简单看一下。就是将rsi一直做偏移,把地址存放到了寄存器,然后把他存到了分配的连续数组里面,一直在lea和mov。
000000000040145c <read_six_numbers>:
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp)
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
401480: be c3 25 40 00 mov $0x4025c3,%esi
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax
401492: 7f 05 jg 401499 <read_six_numbers+0x3d>
401494: e8 a1 ff ff ff callq 40143a <explode_bomb>
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
转换为C
phase2汇编转换为C代码如下
void phase_2(char *input)
{
int array[6];
read_six_number(input, array);
if(array[0]!=1)
{
explode_bomb();
}
for(int i=1;i<6;i++)
{
array[i-1]+=array[i-1];
if(array[i]!=array[i-1])
{
explode_bomb();
}
}
return ;
}
测试
这里为了方便测试,将输入写到txt文件里面,再输入到bomb程序。
Border relations with Canada have never been better.
1 2 4 8 16 32
已经通过phase2了,没问题。
phase3
phase3也是很多跳转,而且都是跳转到了同一个地址?
main:
400e65: e8 a6 fc ff ff callq 400b10 <puts@plt>
400e6a: e8 2f 06 00 00 callq 40149e <read_line>
400e6f: 48 89 c7 mov %rax,%rdi
400e72: e8 cc 00 00 00 callq 400f43 <phase_3>
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx #用于scanf输入
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx #用于scanf输入
400f51: be cf 25 40 00 mov $0x4025cf,%esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>
phase_3+0x27:
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) #前面是scanf的输入判断,这里开始是输入后比较
400f6f: 77 3c ja 400fad <phase_3+0x6a> #0x8(%rsp)比7大就跳转到explode
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax #拷贝变量
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) # 跳转到某个地方去了
# 下面是赋值然后又跳转到400fad,多个分支而且没有判断,应该是switch语句
phase_3+57:
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
phase_3+64:
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
phase_3+71:
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
phase_3+78:
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
phase_3+85:
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
phase_3+92:
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
phase_3+99:
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
phase_3+0x6a:
400fad: e8 88 04 00 00 callq 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
phase_3+118:
400fb9: b8 37 01 00 00 mov $0x137,%eax
phase_3+0x7b:
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax #比较变量和eax值是否相等
400fc2: 74 05 je 400fc9 <phase_3+0x86> #如果相等就explode
phase_3+0x86:
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
地址分析
里面的立即数地址拷贝到了寄存器中,下面分析一下
# %d %d字符串常量地址
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
# 0x402470地址存放的是一个跳表, 这些地址就是eax赋值的指令地址
(gdb) x/16a 0x402470
0x402470: 0x400f7c <phase_3+57> 0x400fb9 <phase_3+118>
0x402480: 0x400f83 <phase_3+64> 0x400f8a <phase_3+71>
0x402490: 0x400f91 <phase_3+78> 0x400f98 <phase_3+85>
0x4024a0: 0x400f9f <phase_3+92> 0x400fa6 <phase_3+99>
0x4024b0 <array.3449>: 0x737265697564616d 0x6c796276746f666e
0x4024c0: 0x7420756f79206f53 0x756f79206b6e6968
0x4024d0: 0x6f7473206e616320 0x6f62206568742070
0x4024e0: 0x206874697720626d 0x202c632d6c727463
转换为C
上面分析后实际上就是scanf输入后,通过swtich语句将变量跳转,最后再进行一次比较看是否相等。实际上两个输入变量满足小于等于7和case标签里面的值即可。
void phase_3(char *input)
{
int x,y,z;
if(scanf("%d %d",&x,&y)<=1)
{
explode_bomb();
}
if(x>7)
{
explode_bomb();
}
swtich(x)
{
case 0:
z = 0xcf; // 207
break;
case 1:
z = 0x137; // 311
break;
case 2:
z = 0x2c3; // 707
break;
case 3:
z = 0x100; // 256
break;
case 4:
z = 0x185; // 389
break;
case 5:
z = 0xce; // 206
break;
case 6:
z = 0x2aa; // 682
break;
case 7:
z = 0x147; // 327
break;
}
if(z!=y)
{
explode_bomb();
}
return ;
}
测试
多组数据都可以满足,只要x<=7,并且x对应的标签和输入y相同即可。
3 256
phase4
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
40101a: be cf 25 40 00 mov $0x4025cf,%esi
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035 <phase_4+0x29>
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
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>
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c>
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
401056: 74 05 je 40105d <phase_4+0x51>
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
转换为C
phase_4函数整体流程如下
void phase(char *input)
{
int x,y; // 0x8(%rsp) 0xc(%rsp)
int ret = sscanf("%d %d",&x,&y); //实际上ret是寄存器, 不占用内存, 优化后就是放在寄存器
if(ret!=2 || x>14)
{
explode_bomb();
}
ret = func4(x,0,14);
if(ret!=0 || y!=0) // 返回值和栈变量比较
{
explode_bomb();
}
return ;
}
func4
可以看到func4中递归调用了自己
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp # 分配8个字节
400fd2: 89 d0 mov %edx,%eax # 将第三个参数移动到eax
400fd4: 29 f0 sub %esi,%eax # edx - esi, 结果存放到eax
400fd6: 89 c1 mov %eax,%ecx # eax存到ecx
400fd8: c1 e9 1f shr $0x1f,%ecx # ecx右移31位
400fdb: 01 c8 add %ecx,%eax # eax+ecx -> eax
400fdd: d1 f8 sar %eax # eax算术右移1位
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx #ecx存放rax+rsi内存地址的值
400fe2: 39 f9 cmp %edi,%ecx # 比较ecx和edi
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx # edx = rcx - 1
400fe9: e8 e0 ff ff ff callq 400fce <func4> # 递归调用func4
400fee: 01 c0 add %eax,%eax # eax = 2*eax
400ff0: eb 15 jmp 401007 <func4+0x39>
func4+0x24:
400ff2: b8 00 00 00 00 mov $0x0,%eax # eax = 0 返回值为0
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39> # ecx > edi? 跳转返回函数值
400ffb: 8d 71 01 lea 0x1(%rcx),%esi # 设置参数, 准备继续调用func4
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax #eax = rax+rax+1
func4+0x39:
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
根据func4的汇编代码翻译为C代码如下,实际上就是使用二分法找到x。
int func4(int x,int y,int z)
{
int tmp = z - y; // 400fd2 ~ 400fd4
tmp = (tmp + tmp >>31)>>1; // 400fd6 ~ 400fdd
int cx = tmp + y; //
if(cx<=x)
{
if(cx>=x) return 0; // 400ff2~400ff9 -> 401007~40100b
return 2*func4(x,tmp+1,z)+1; // 400ffb~40100b
}
return 2*func4(x,y,tmp-1)+1; // 400fe6~400ff0 -> 401007~40100b
}
测试
根据上面转换好的C代码,现在来输入测试。首先第一个输入值需要小于14的整数,第二个输入值从phase_4的汇编代码来看,它必须是为0的。所以输入值可以为
0 0
1 0
3 0
7 0
phase5
前面硬啃汇编代码问题不大,但是这里开始啃不下去了(前面尝试硬啃熟悉下汇编指令和常用寄存器),必须GDB单步调试看寄存器和内存才知道其中几段指令在干嘛。首先看到整个phase5的汇编代码。
Dump of assembler code for function phase_5:
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq
首先是输入部分,这里如果读取的字符串长度不够6,会直接bomb结束,所以input要满足6个字符才能继续。
Dump of assembler code for function phase_5:
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
循环
往下面看,看到里面的jump结构,可以看到41是关键的循环部分,这里就比较复杂了。
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
进入到循环位置(+41)
首先查看下rbx存放了什么字符串,好像是输入的字符串
再打印下内存,确定了是自己输入的字符串。
走几次循环后发现,rax是计数器,然后将输入字符串和循环变量的偏移值得到的字符存到rcx,rcx数据又存放到edx,edx和0xf进行与运算,得到低4位放到edx。最后从[0x4024b0+rdx]偏移得到的字节拷贝到edx。最后把edx的值又放到内存里面**M[rsp+rax+0x10]**里面。
查看下这**M[rsp+rax+0x10]**里面存放了什么内容,有点奇怪,输入的abcdef,一顿操作后变成了aduier。
再看一下$0x4024b0里面的地址存放的内容,这里的字符串内容如下,后面存放的So you think...应该是ctrl-c接收信号中断后输出的字符串。
那么就是这maduiersnfotvbyl这16个字符,因为前面是通过取得edx低4位的数值然后作为偏移又去取出这个字符串的某个字符。
字符串比较
再看到循环结束后的指令
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp) # '\0'赋值到字符串末尾
0x00000000004010b3 <+81>: mov $0x40245e,%esi # 拷贝地址值到esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi #将偏移地址放到rdi作为参数传递
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal> #字符串比较
0x00000000004010c2 <+96>: test %eax,%eax # test 返回值
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb> # 不相等就爆炸
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq
查看地址0x40245e的内容,是一个字符串。后面就比较清楚了,在前面操作的字符串保存在数组里面了,现在比较这6个字符是否等于"flyers",如果是,那么这个phase_5就通过了。
现在的问题就是如何输入值从字符串maduiersnfotvbyl取出flyters
- f对应的offset是9
- l对应的offset是15
- y对应的offset是14
- e对应的offset是5
- r对应的offset是6
- s对应的offset是7
**那么只要输入的ASCII字符的低4位(模16)对应的是这么几个offset即可,比如
IONEFG
测试
输入上面的字符串后,没问题通过。
phase6
phase6接收了6个数字,尝试输入6个数字后,直接一直单步,发现里面有非常多的循环,而且有嵌套循环。
0x00000000004010f4 <+0>: push %r14
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13 # r13 = rsp
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14 # r14 = rsp
0x000000000040110e <+26>: mov $0x0,%r12d # r12 = 0
0x0000000000401114 <+32>: mov %r13,%rbp # rbp = r13
0x0000000000401117 <+35>: mov 0x0(%r13),%eax
0x000000000040111b <+39>: sub $0x1,%eax # eax = eax - 1
0x000000000040111e <+42>: cmp $0x5,%eax # 大于5则结束
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d # r12 += 1
0x000000000040112c <+56>: cmp $0x6,%r12d # r12==6?
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx # rbx = r12
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax # rax = 4*rax + rsp 将每个数字都拷贝到rax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) # rax 和 rbp是否相等? if(num[i]==num[j])
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb> # 相等则bomb
0x0000000000401145 <+81>: add $0x1,%ebx # rbx += 1, j++
0x0000000000401148 <+84>: cmp $0x5,%ebx # rbx < 5 ?
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13 # r13 += 4, 就是rsp移动了4字节
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> # 又回到了32,是双重循环
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi # rsi = 0
0x0000000000401158 <+100>: mov %r14,%rax # rax = r14
0x000000000040115b <+103>: mov $0x7,%ecx # rcx = 7
# 单层循环将数组的每个元素赋值为 7 - nums[i]
0x0000000000401160 <+108>: mov %ecx,%edx # rdx = rcx
0x0000000000401162 <+110>: sub (%rax),%edx # rdx -= *rax
0x0000000000401164 <+112>: mov %edx,(%rax) *rax = edx #
0x0000000000401166 <+114>: add $0x4,%rax # rax += 4
0x000000000040116a <+118>: cmp %rsi,%rax # rax是否为0
0x000000000040116d <+121>: jne 0x401160 <phase_6+108> #这里是一个单层循环
0x000000000040116f <+123>: mov $0x0,%esi # rsi = 0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
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)
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
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>
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
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)
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>
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq
循环拆分
检测输入
首先是第一个循环,检查6个输入值是否唯一并且在[1,6]范围内。
0x000000000040110e <+26>: mov $0x0,%r12d # r12 = 0
0x0000000000401114 <+32>: mov %r13,%rbp # rbp = r13
0x0000000000401117 <+35>: mov 0x0(%r13),%eax # rax = M[r13]
0x000000000040111b <+39>: sub $0x1,%eax # eax = eax - 1
0x000000000040111e <+42>: cmp $0x5,%eax # 大于5则结束
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>]
0x0000000000401128 <+52>: add $0x1,%r12d # r12 += 1, 元素计数
0x000000000040112c <+56>: cmp $0x6,%r12d # r12==6?
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov %r12d,%ebx # rbx = r12
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax # rax = 4*rax + rsp 将每个数字都拷贝到rax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) # rax 和 M[rbp]是否相等?
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb> # 相等则bomb
0x0000000000401145 <+81>: add $0x1,%ebx # rbx += 1, j++
0x0000000000401148 <+84>: cmp $0x5,%ebx # rbx <= 5 ?
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add $0x4,%r13 # r13 += 4, 就是rsp移动了4字节
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> # 又回到了+32,是双重循环, 此时是下一个元素
修改输入值
第二循环是一个单层循环,这个循环主要就是修改每个数组元素array[i] = 7 - array[i]。
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi # rsi = 0
0x0000000000401158 <+100>: mov %r14,%rax # rax = r14
0x000000000040115b <+103>: mov $0x7,%ecx # rcx = 7
# 单层循环将数组的每个元素赋值为 7 - nums[i]
0x0000000000401160 <+108>: mov %ecx,%edx # rdx = rcx
0x0000000000401162 <+110>: sub (%rax),%edx # rdx -= *rax
0x0000000000401164 <+112>: mov %edx,(%rax) *rax = edx #
0x0000000000401166 <+114>: add $0x4,%rax # rax += 4
0x000000000040116a <+118>: cmp %rsi,%rax # rax是否为0
0x000000000040116d <+121>: jne 0x401160 <phase_6+108> #这里是一个单层循环
首先是将rsp的偏移0x18位置赋值到rsi,此时的r14的地址就是rsp中数组元素的首地址(下图是输入了1,2,3,4,5,6)。
循环后结束发现,数组元素确实变成了7-x形式,在这里就是刚好顺序反过来了。
构造节点数组
第三个循环,这是一个双重循环,这里一个地址值很重要,0x6032d0。
0x000000000040116f <+123>: mov $0x0,%esi # rsi = 0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
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 #将地址放到edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) # 将地址放到栈内存中
0x000000000040118d <+153>: add $0x4,%rsi # 偏移量+4
0x0000000000401191 <+157>: cmp $0x18,%rsi # 是否超过元素个数范围
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183> # 是就跳出
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx # 将数组元素值取出来放到rcx
0x000000000040119a <+166>: cmp $0x1,%ecx # 判断元素值是否为1
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> #如果小于等于1
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx # 把另一段内存地址放到了rdx中
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
这里面涉及到了链表节点,而且不止一个,是一个数组,每个节点元素占了16个字节,包括指针域和整型值。
调试过程中,130内部是链表节点的操作,不断将next域的地址赋给当前节点变量变量,可以看到不断stepi执行,rdx指向的节点是在变化的。
再看到这个内部循环的控制变量,eax肯定是for循环中的循环控制变量,因为进入130循环前都是将它赋值为1。内部循环结束后是跳转到了148偏移位置,这里开始将rdx的地址放到M[rsp+rsi*2+0x20]的位置,这个地方应该是分配的节点数组。
在后面的代码实际上就是外部循环的开头,更新rsi指针
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) # 将地址放到栈内存中
0x000000000040118d <+153>: add $0x4,%rsi # 偏移量+4
0x0000000000401191 <+157>: cmp $0x18,%rsi # 是否超过元素个数范围
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183> # 是就跳出
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx # 将数组元素值取出来放到rcx
0x000000000040119a <+166>: cmp $0x1,%ecx # 判断元素值是否为1
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> #如果小于等于1
构造完的链表结构如下,构造出来的内容就是指针域的对应,但是会发现还没有碰这个内存里面的指针节点时,他前面的一些内容已经初始化好了,刚好有123456(跟后续的操作有关)。
搬运全局内存到栈内存中
下面的部分就是将链表节点从bss段搬运到栈内存中。
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx #node3 地址存放到rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax #node3 的指针域地址存放到rax,也就是node4的地址
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi # rsi指向保存的链表节点地址末尾, 0x50是前面分配的栈大小
0x00000000004011ba <+198>: mov %rbx,%rcx
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) # 栈指向的最后一个节点地址复制到前一个节点的地址位置
0x00000000004011c4 <+208>: add $0x8,%rax # 移动到下一个节点
0x00000000004011c8 <+212>: cmp %rsi,%rax # 是否到达链表末尾,是否遍历完成
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx # 更新rcx指向下一个节点
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
单步调试到222后,查看栈的内存,将节点指针移动到了栈中。而且顺序不太一样,实际上就是根据之前的节点内存图片里面的数值(39c, 263, 1dd, 1bb, 14c, 0a8)重新排序链接扔到了栈里面。
检查链表值大小
最后就是检查链表中每一个节点是否比下一个节点值要大。
0x00000000004011da <+230>: mov $0x5,%ebp
0x00000000004011df <+235>: mov 0x8(%rbx),%rax # rax指向头指针
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx) #比较节点值
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx # 访问next指针
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq
总结
- bomb lab把x86_64常用特殊寄存器在课本那一页多翻,多写课本上的几个练习题还是没太大问题的,如果一些指令忘记了回去翻ppt或书,最好了个相应的练习题
- 给自己多点耐心,最后两题我写了差不多一个周日
- 最后,phase6题目花的时间是前面5道题时间的几倍(麻了)