简介
实验提供了一个可执行文件bomb,并且埋下了6个"炸弹"。
需要通过反汇编查看bomb,找出6把钥匙字符串,破解6个炸弹。
- 反汇编命令:objdump -d xxx
一、阶段一
第一阶段,炸弹所在函数为phase1,其反汇编代码为:
400ee0: 48 83 ec 08 sub $0x8,%rsp //创建栈帧,大小为8个字节
400ee4: be 00 24 40 00 mov $0x402400,%esi //将立即数402400存储到%esi中作为参数
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> //调用strings_not_equal函数
400eee: 85 c0 test %eax,%eax //判断结果是否等于0
400ef0: 74 05 je 400ef7 <phase_1+0x17>//如果等于0,则跳转到400ef7处执行
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>//不等于0,即字符串不相等,爆炸
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
看了上面的汇编结合main函数的汇编,我们可以得知,传入strings_not_equal
函数进行比较的字符串为我们输入的字符串首地址和0x402400
这个地址处的字符串,如果得到的结果是等于0的,那么就跳过触发炸弹的语句,而如果为false,则触发炸弹。
因此,我们再看strings_not_equal
的汇编代码,查看具体的字符串比较逻辑。
// strings_not_equal函数
401338: 41 54 push %r12
40133a: 55 push %rbp
40133b: 53 push %rbx //保存被调用者保存寄存器,phase1函数并未存储值到这三个寄存器,不管
40133c: 48 89 fb mov %rdi,%rbx //将%rdi中保存的参数1放入%rbx中,该参数由main函数指定,其实就是我们输入的字符串的首地址。
40133f: 48 89 f5 mov %rsi,%rbp //将%rsi中的参数2(0x402400)放入基址寄存器%rbp,即破解炸弹字符串的首地址
401342: e8 d4 ff ff ff callq 40131b <string_length>//调用string_length,计算参数1的长度,即我们输入的字符串的长度
401347: 41 89 c4 mov %eax,%r12d //将返回的结果放入%r12d
40134a: 48 89 ef mov %rbp,%rdi //计算参数2的长度
40134d: e8 c9 ff ff ff callq 40131b <string_length>
401352: ba 01 00 00 00 mov $0x1,%edx //将参数2的长度存入%edx寄存器
401357: 41 39 c4 cmp %eax,%r12d //比较长度,如果不等,跳至40139b执行
40135a: 75 3f jne 40139b <strings_not_equal+0x63>
//如果长度相同,则比较每一个字符,%rbx保存的是我们输入的字符串的首地址
//%rbp保存的是破解字符串的首地址(0x402400)
40135c: 0f b6 03 movzbl (%rbx),%eax //取出输入的第一个字符
40135f: 84 c0 test %al,%al //判断是否等于0,即空串,等于跳401388
401361: 74 25 je 401388 <strings_not_equal+0x50>
401363: 3a 45 00 cmp 0x0(%rbp),%al //比较第一个字符,等于跳401372,不等跳转到40138f
401366: 74 0a je 401372 <strings_not_equal+0x3a>
401368: eb 25 jmp 40138f <strings_not_equal+0x57>
40136a: 3a 45 00 cmp 0x0(%rbp),%al
40136d: 0f 1f 00 nopl (%rax)
401370: 75 24 jne 401396 <strings_not_equal+0x5e>
401372: 48 83 c3 01 add $0x1,%rbx //读取我们输入的字符串的下一个字符
401376: 48 83 c5 01 add $0x1,%rbp //读取下一个字符
40137a: 0f b6 03 movzbl (%rbx),%eax //判断是否为结束字符
40137d: 84 c0 test %al,%al //不为0.即不为结束字符,跳转到40136a进行比较,形成循环
40137f: 75 e9 jne 40136a <strings_not_equal+0x32>
401381: ba 00 00 00 00 mov $0x0,%edx //%edx置0,如果循环中一直没有跳转到40138f,然后我们输入的字符串到达末尾,跳转到40139b
401386: eb 13 jmp 40139b <strings_not_equal+0x63>
401388: ba 00 00 00 00 mov $0x0,%edx //输出0
40138d: eb 0c jmp 40139b <strings_not_equal+0x63>
40138f: ba 01 00 00 00 mov $0x1,%edx //输出1
401394: eb 05 jmp 40139b <strings_not_equal+0x63>
401396: ba 01 00 00 00 mov $0x1,%edx //输出1
40139b: 89 d0 mov %edx,%eax //将edx中的内容当作返回值返回
40139d: 5b pop %rbx
40139e: 5d pop %rbp
40139f: 41 5c pop %r12
4013a1: c3 retq
通过上面的汇编代码,我们可以得知,我们输入的字符作为参数1,立即数0x402400作为参数2,传入该函数,首先判断这两个参数所表示的字符串的长度,如果长度不相等,则将参数2的长度作为返回值返回,此时phase再判断,结果不为0,爆炸,因此我们的目标就是输入一个字符串与起始地址为0x402400的字符串进行比较,比较结果为0,因此阶段1的答案为 0x402400地址处的字符串。
通过gdb调试bomb,查看0x402400处的内容,得到字符串:
/*
1. gdb bomb
2. x/s 0x402400
*/
"Border relations with Canada have never been better."
二、阶段二
phase2的汇编代码如下:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp //分配28个字节的空间用于栈帧
400f02: 48 89 e6 mov %rsp,%rsi //栈顶地址作为参数2
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>//调用read_six_numbers函数
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) //判断栈顶元素是否为1,此时栈顶存储数组的首地址,即数组的第一个元素
400f0e: 74 20 je 400f30 <phase_2+0x34> //如果等于1,跳转400f30
400f10: e8 25 05 00 00 callq 40143a <explode_bomb> //触发炸弹
400f15: eb 19 jmp 400f30 <phase_2+0x34> //跳转到400f30
400f17: 8b 43 fc mov -0x4(%rbx),%eax //循环开始,取当前元素的前一个元素
400f1a: 01 c0 add %eax,%eax //a[i-1]*2
400f1c: 39 03 cmp %eax,(%rbx) //判断当前元素是否为前一个元素的两倍
400f1e: 74 05 je 400f25 <phase_2+0x29>//如果是,则跳过炸弹
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>//否则引发爆炸
400f25: 48 83 c3 04 add $0x4,%rbx //%rbx向前推进
400f29: 48 39 eb cmp %rbp,%rbx //比较是否到达数组末尾,如果是则推出,否则继续
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40> //跳转到return语句
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx //将%rsp + 4的地址送入%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp //将%rsp + 24的地址送入%rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b> //跳转到400f17
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
结合上面的汇编代码,我们可以得知,首先分配28个字节作为栈帧,然后将我们输入的字符串作为第一个参数、栈顶指针%rsp作为第二个参数调用read_six_numbers
函数读取6个字符,即将我们的输入取前6个存放到一个字符数组中,而这个字符数组的首地址为phase2函数的栈顶指针。因此,6个元素相对栈顶的偏移为%rsp、%rsp+4一直到%rsp + 24
,然后判断第一个元素是否等于1,如果等于1,进行接下来的循环判断,否则触发炸弹。循环内部,判断当前数组元素a[i]是否为其前一个元素a[i-1]的两倍,直到最后一个元素。如果有任意一个元素不是前一个元素的两倍,则触发炸弹。
read_six_numbers
调用了8参数的sscanf,其中参数1为我们传入的字符串、参数2为输入模式,即"%d %d %d %d %d %d"
,然后将数组的6个元素分别放入%rdx,%rcx,%r8,%r9,以及在栈中存放两个。对解决此题的帮助不大,通过函数的名字即可知道大概的逻辑,此处不在列出。
因此本题的解码字符串也可以得到,即 1、2、4、8、16、32
三、阶段三
phase3的汇编代码如下所示:
400f43: 48 83 ec 18 sub $0x18,%rsp //申请24个比特的栈空间
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx//栈顶+12位置的地址作为第四个参数
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx//栈顶+8位置的地址作为第三个参数
400f51: be cf 25 40 00 mov $0x4025cf,%esi//地址0x4025cf作为第二个参数,内容为%d %d
400f56: b8 00 00 00 00 mov $0x0,%eax //将%rax置0
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
//前面汇编的意思为,申请24个比特,通过scanf函数读取两个整数,放入到栈顶+8和栈顶+12的位置
400f60: 83 f8 01 cmp $0x1,%eax //比较结果是否为1,
//scanf返回值存放的是读取的字符串的个数,phase2调用读取6个整数的scanf,返回值为6
400f63: 7f 05 jg 400f6a <phase_3+0x27> //如果大于1,跳转到400f6a
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> //触发炸弹,因为字符个数小于2
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) //比较读入的第一个int与7的大小
400f6f: 77 3c ja 400fad <phase_3+0x6a>//如果大于等于,跳转到400fad,触发炸弹
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax //将%rsp+8处的元素保存到%eax中
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)//跳转到M[8*%rax + 0x402470]的位置
//上一句为间接跳转,跳转到地址为M[8*%rax + 0x402470]内存单元存储的值的地方
//当%rax存储0时,M[0x402470]处存储的值为0x00400f7c,即跳转到地址为0x00400f7c的地方
//此时将0xcf转移到%eax中,即下一句汇编代码
400f7c: b8 cf 00 00 00 mov $0xcf,%eax //移动0xcf到%eax中
400f81: eb 3b jmp 400fbe <phase_3+0x7b> //跳转到400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax //移动0x2c3到%eax中
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax //移动0x100到%eax中
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax //移动0x185到%eax中
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax //移动0xce到%eax中
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax //移动0x2aa到%eax中
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax //移动0x147到%eax中
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 callq 40143a <explode_bomb> //爆炸
400fb2: b8 00 00 00 00 mov $0x0,%eax //%eax置为0
400fb7: eb 05 jmp 400fbe <phase_3+0x7b> //跳转400fbe
400fb9: b8 37 01 00 00 mov $0x137,%eax //%exa置为0x137
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax //%rsp+12位置的值与%eax中的比较
400fc2: 74 05 je 400fc9 <phase_3+0x86>//如果等于,跳转400fc9
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>//爆炸
400fc9: 48 83 c4 18 add $0x18,%rsp //返回
400fcd: c3 retq
从汇编代码可以得知,读入两个整数,且第一个数要不大于7,然后用第一个数*8 + 0x402470
得到一个内存地址1,然后将该内存地址1处存储的值作为一个地址2,跳转到地址2。然后执行地址2处的代码。
所以,这一阶段答案不止一种,而是有7种,当第一个数输入0,第二个数要输入0xcf,即207
输入1,第二个数要输入0x137
tips:
- 此处需要使用GDB进行调试,获取对应内存位置的值
- 通过x/w读取从
0x402470 + 8i
开始4个B的内容,当 i = 0时,输出 0x00400f7c - 然后执行0x400f7c处的代码
- x/s是将对应地址处的内容以字符串的形式输出
四、阶段四
phase4的汇编代码如下:
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 // "%d %d"放入到%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>
//上段汇编的逻辑为,申请24B栈空间,调用scanf函数读取两个整数,如果返回值不等于2,则跳转
//0x401035执行爆炸函数,读取的数放到 %rsp+8 和 %rsp+12 的位置
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) //比较输入的数和0xe的大小
401033: 76 05 jbe 40103a <phase_4+0x2e> //如果14小于等于,爆炸
401035: e8 00 04 00 00 callq 40143a <explode_bomb> //爆炸
//上段汇编,确保输入的第一个数要小于14
40103a: ba 0e 00 00 00 mov $0xe,%edx //将%edx置为14
40103f: be 00 00 00 00 mov $0x0,%esi //将%esi置为0
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi//将%edi置为 第一个输入的值
401048: e8 81 ff ff ff callq 400fce <func4> //调用func4
40104d: 85 c0 test %eax,%eax //判断结果是否为0
40104f: 75 07 jne 401058 <phase_4+0x4c> //不为0,跳转401058处爆炸
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) //比较输入的第二个数是否为0
401056: 74 05 je 40105d <phase_4+0x51> //等于0跳转40105d
401058: e8 dd 03 00 00 callq 40143a <explode_bomb> //执行爆炸
40105d: 48 83 c4 18 add $0x18,%rsp //返回
401061: c3 retq
从上段代码可知,我们需要输入两个数,第一个数需要小于14,且第二个数需要为0
然后将 输入的第一个数、0、14作为参数,调用func4,如果func4返回值不是0,则爆炸,否则再判断第二个数是否为0,所以接下来需要看func4的代码逻辑
func4的汇编代码如下:
//参数1 %rdi,设为x,参数2 %rsi,设为y,参数3 %rdx 设为z
400fce: 48 83 ec 08 sub $0x8,%rsp //申请8B的栈空间
400fd2: 89 d0 mov %edx,%eax //将14取出(参数3) z = 14
400fd4: 29 f0 sub %esi,%eax //%rsi存储的是参数2(0),14 - 0
400fd6: 89 c1 mov %eax,%ecx //保存到%ecx,等价将%ecx置为14,即temp = z
400fd8: c1 e9 1f shr $0x1f,%ecx //逻辑右移31位
400fdb: 01 c8 add %ecx,%eax //相加,相当于 temp + temp*2^-31
400fdd: d1 f8 sar %eax //算数右移--->res
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx //将地址%rsi + %rax保存到%ecx,相当于计算y + res
400fe2: 39 f9 cmp %edi,%ecx //比较x 与%ecx的大小
400fe4: 7e 0c jle 400ff2 <func4+0x24> //如果小于等于,跳转递归结束条件
400fe6: 8d 51 ff lea -0x1(%rcx),%edx //不小于,%ecx-1,继续递归
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax //将返回值*2
400ff0: eb 15 jmp 401007 <func4+0x39> //返回
400ff2: b8 00 00 00 00 mov $0x0,%eax //将%eax置为0
400ff7: 39 f9 cmp %edi,%ecx //比较x与%ecx的结果
400ff9: 7d 0c jge 401007 <func4+0x39> //如果大于等于
400ffb: 8d 71 01 lea 0x1(%rcx),%esi //将%rcx+1
400ffe: e8 cb ff ff ff callq 400fce <func4> //再次调用func4
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax //如果小于,则将结果*2 + 1后返回
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
由上面的func4的汇编代码可以大概看出,这个函数是一个递归函数,我们传入了3个参数,分别是我们输入的第一个数,0以及14,转换为C代码,大概是这个样子
//参数为x,y,z,其中y = 0,z = 14,函数名func4
int func4(int x,int y,int z){
int res= z - y;
unsigned int temp = res;
temp = temp >> 31;//取出z-y的最高位
res += temp;
res >>= 1;//算术右移1位
int var = y + res;
if(x < var){
z = var -1;
res = func4(x,y,z);
res *= 2;
return res;
}else{
//400fe4的跳转逻辑
res = 0;
if(var >= x){
return res;
}else{
res += 1;
return 2*func4(x,res,z)+1;
}
}
}
–看不太懂这代码啥意思。。。–
但是phase4的大体逻辑就是输入两个数,第二个要为0,第一个要使得func4(x)返回0
func4的逻辑看不懂但看网上大佬,使用暴力挨个测试,得到答案为第一个参数0 1 3 7
任选一个,第二个为0
暴力代码:
for (int i = 0; i <= 14; i++)
if (!func4(i, 0, 14))
cout << i << " ";
五、阶段五
phase5的汇编代码如下所示:
401062: 53 push %rbx //保存被调用者保存寄存器的值
401063: 48 83 ec 20 sub $0x20,%rsp //申请32B的栈空间
401067: 48 89 fb mov %rdi,%rbx //将我们输入的字符串首地址放入%rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax //%rax保存金丝雀值
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp) //将金丝雀值存放到%rsp+24的位置
401078: 31 c0 xor %eax,%eax //将%eax重新置为0
40107a: e8 9c 02 00 00 callq 40131b <string_length> //调用string_length函数
40107f: 83 f8 06 cmp $0x6,%eax //判断结果是否为6
401082: 74 4e je 4010d2 <phase_5+0x70> //如果等于6,跳转4010d2
401084: e8 b1 03 00 00 callq 40143a <explode_bomb> //否则爆炸
//上段汇编主要生成金丝雀值,保存到%rsp + 24的位置
//然后调用string_length函数,求我们输入的字符串的长度
//如果长度不为6,则爆炸,等于6,跳转到0x4010d2处执行
//疑问------401089这句话的作用是什么?爆炸执行完毕之后执行?这样长度不是6,继续执行会异常吧?
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx //%ecx = (%rbx + %rax)
//此时%ecx中存储的是temp,%rax中存储的是索引,%rbx为输入字符串首地址
//所以,相当于 temp = a[i];
40108f: 88 0c 24 mov %cl,(%rsp) //将该字符压入栈
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx //将刚入栈的字符低四位与0xf进行&操作
//下一局汇编代码取出%rdx + 0x4024b0处的值
//通过GDB查看得知(x/s 0x4024b0),此处为一个字符数组,值如下所示:
//"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx //取出某个字符,放入%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)//将该字符放入rsp+i+16的位置
4010a4: 48 83 c0 01 add $0x1,%rax //索引i++
4010a8: 48 83 f8 06 cmp $0x6,%rax //判断索引是否到达6
4010ac: 75 dd jne 40108b <phase_5+0x29> //不等于,跳至40108b
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) //如果到达最后,将0存入%rsp+22的位置
4010b3: be 5e 24 40 00 mov $0x40245e,%esi //"flyers"放入%esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9 <phase_5+0x77>
//4010b3---4010c4的逻辑为:判断%rsp + 16开始的字符串与”flyers“是否相等,如果相等则跳转4010d9
//否则爆炸
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9 <phase_5+0x77>
4010d2: b8 00 00 00 00 mov $0x0,%eax //将%eax置0
4010d7: eb b2 jmp 40108b <phase_5+0x29>//跳转到0x40108b执行
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax //释放栈空间,准备返回
4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax //判断金丝雀是否更改过
4010e5: 00 00
4010e7: 74 05 je 4010ee <phase_5+0x8c> //如果金丝雀值没被改,则返回
4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
阶段5的汇编代码大体逻辑就是,首先设置一个金丝雀,然后将我们输入的字符串,挨个取出,取其低四位,转换为int值之后作为索引,从"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
中取对应的字符,然后取完之后拼成一个字符串,判断是否和"flyers"
相等
因此我们输入的字符串的低四位的所对应的整数值是9、15、14、5、6、7,高四位无所谓,但最好处于ASCII码表中且属于英文字符。
因此,选择每位加64,得到 73=I 79=O 78=N 69=E 70=F 77=G
即 IONEFG
每位加96也可以,即 ionefg
六、阶段六
phase6的汇编代码如下:
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
//保存被调用者栈寄存器
4010fc: 48 83 ec 50 sub $0x50,%rsp
//分配90个字节
401100: 49 89 e5 mov %rsp,%r13
401103: 48 89 e6 mov %rsp,%rsi //以栈顶作为第二个参数调用read_six_numbers
401106: e8 51 03 00 00 callq 40145c <read_six_numbers>
//read_six_numbers向栈中存放了6个整数元素,偏移从0开始,假设数组为num
40110b: 49 89 e6 mov %rsp,%r14 //栈顶存%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d //将%r12d置为0,相当于i = 0
401114: 4c 89 ed mov %r13,%rbp
//%r13保存%rsp + 偏移量的值,首次执行为%rsp的地址,后面40114d每次将%rsp的偏移量+4,访问数组下一个元素
401117: 41 8b 45 00 mov 0x0(%r13),%eax //将当前元素放入%eax,即num[i]
40111b: 83 e8 01 sub $0x1,%eax //num[i]-1
40111e: 83 f8 05 cmp $0x5,%eax //与5进行比较
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 //i++--->i+1
40112c: 41 83 fc 06 cmp $0x6,%r12d //进行循环判断i<6,等于6跳转到401153
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx //不等于6,执行循环,将i放入%ebx,即j=i
401135: 48 63 c3 movslq %ebx,%rax //将j放到%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax //将%rsp+j*4处的元素放到%eax,即num[j]
40113b: 39 45 00 cmp %eax,0x0(%rbp)//比较num[i]与num[j]
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 //将%ebx + 1-->j+1
401148: 83 fb 05 cmp $0x5,%ebx//比较i是否等于5
40114b: 7e e8 jle 401135 <phase_6+0x41>//小于等于跳转到401135
40114d: 49 83 c5 04 add $0x4,%r13 //将%r13 + 4,相当于访问栈中数组下一个元素
401151: eb c1 jmp 401114 <phase_6+0x20>//跳回到401114
//上段代码的逻辑大概为一个循环,其中%r14保存栈顶,%r13记录当前数组元素,%r12d记录索引i
//然后i从0到5进行6次循环,判断每个数要小于6,否则爆炸,然后将i+1赋给j
//然后再内层循环5次,判断num[i]与num[j]的大小,如果相等则爆炸
//外层循环起始地址为401114,内层循环地址为401135
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi //将rsp+24处的地址赋给%rsi
401158: 4c 89 f0 mov %r14,%rax //将%r14赋给%rax,即栈顶%rsp
40115b: b9 07 00 00 00 mov $0x7,%ecx //将%ecx的置为7,temp = 7
401160: 89 ca mov %ecx,%edx //temp2 = temp
401162: 2b 10 sub (%rax),%edx //temp2 -= num[0]
401164: 89 10 mov %edx,(%rax) //num[0] = temp2
401166: 48 83 c0 04 add $0x4,%rax //rax指向下一个数组下一个元素
40116a: 48 39 f0 cmp %rsi,%rax //比较是否到达数组的末尾,即偏移量24处
40116d: 75 f1 jne 401160 <phase_6+0x6c> //不等于跳回401160继续
//上段代码也是一个循环,从nums[0]开始遍历整个数组,将数组中的元素都变为7-nums[i]
40116f: be 00 00 00 00 mov $0x0,%esi //将%esi置为0,即index = 0
401174: eb 21 jmp 401197 <phase_6+0xa3> //跳转到401197
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx //rdx+8
40117a: 83 c0 01 add $0x1,%eax //eax + 1
40117d: 39 c8 cmp %ecx,%eax //比较num[index]与eax的大小
40117f: 75 f5 jne 401176 <phase_6+0x82>//不等于跳401176
401181: eb 05 jmp 401188 <phase_6+0x94>//等于跳401188
401183: ba d0 32 60 00 mov $0x6032d0,%edx
//0x6032d0位置存储的是一个链表,可以通过GDB查询得到
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)//将%rdx元素放于%rsp+32的位置
40118d: 48 83 c6 04 add $0x4,%rsi //index + 4
401191: 48 83 fe 18 cmp $0x18,%rsi //判断index是否超24,即到达数组末尾
401195: 74 14 je 4011ab <phase_6+0xb7> //如果是,跳出循环
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx //将num[index]赋给%ecx
40119a: 83 f9 01 cmp $0x1,%ecx //比较是否小于1,跳转到401183
40119d: 7e e4 jle 401183 <phase_6+0x8f>
40119f: b8 01 00 00 00 mov $0x1,%eax //不小于1则将%eax置为1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx //将表头放到%edx中
4011a9: eb cb jmp 401176 <phase_6+0x82>//跳转到401176
//上段代码为设置一个偏移量index,此时index为0,即判断num[0]处的元素是否小于1,如果小于1则将链表首地址放置于%rsp+32位置处
//然后开始循环,结束条件为到达数组尾部
//每次循环首先index+4更新偏移量,即循环是从num[0]开始
//每次循环判断%rsp+index,即num[i]是否小于1,如果小于1,则将rsp + 2*index + 32处的值置为表头元素
//如果大于1,则设置一个temp = 1,然后判断,如果temp != num[i],则链表取下一个元素,直到temp=num[i]
//综上,这一段循环的逻辑为,从%rsp+32位置开始,设置栈内容为List-->get(num[i])
//假设上面的逻辑,使得我们在%rsp+32位置处新建了一个数组 num2,容量为6,每个元素8字节,存储的是链表节点的指针
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx //rbx = &num2[0]
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax //rax = &num2[1]
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi //rsi = &num2[5]
4011ba: 48 89 d9 mov %rbx,%rcx //rcx = rbx,即设置一个result = num2[0]
4011bd: 48 8b 10 mov (%rax),%rdx //rdx=nums[i],因为后面会更改%rax
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx)//将rdx得值设置到%rcx + 8的地方
4011c4: 48 83 c0 08 add $0x8,%rax //将rax+8指向下一个元素,即rax=num2[i++]
4011c8: 48 39 f0 cmp %rsi,%rax //比较num2[i++]是否等于num2[5]
4011cb: 74 05 je 4011d2 <phase_6+0xde>//如果等于说明到达数组末尾,跳转到4011d2
4011cd: 48 89 d1 mov %rdx,%rcx //没有到达末尾,result += num2[i]
4011d0: eb eb jmp 4011bd <phase_6+0xc9>//跳转回4011bd
//上段汇编的代码逻辑比较简单,即我们通过list创造了一个长度为6的数组,数组每个元素占8个字节
//设置一个result = 第一个元素,然后开始一个循环,累加数组的元素,直到数组末尾
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) //将数组后的8个字节设置为0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp //ebp = 5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax // rax = &num2[1]
4011e3: 8b 00 mov (%rax),%eax //rax = num2[1]的低4字节
4011e5: 39 03 cmp %eax,(%rbx) //比较前一个数组元素的低4字节
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
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>
4011f7: 48 83 c4 50 add $0x50,%rsp
//上段代码为比较num2数组,判断num2数组中的每个元素的低4字节是否比前一个元素的低4字节小,
//如果不小于则爆炸
4011fb: 5b pop %rbx
4011fc: 5d pop %rbp
4011fd: 41 5c pop %r12
4011ff: 41 5d pop %r13
401201: 41 5e pop %r14
401203: c3 retq
phase6的汇编代码比较长,首先取6个数,需要每个数都小于7
然后将每个数 x 转换为 7- x
然后按照新得到的数字(将新得到的数字作为链表索引)来重新排列整个链表,使得链表的元素的低4字节是从大到小排列的(汇编中的注释我用的一个新的数组num2,其实num2相当于一个指针数组,存储链表元素的指针)
所以我们需要输入一组数字,a,b,c,d,e,f
然后使得链表元素 按照 7-a,7-b,7-c,7-d,7-e,7-f的顺序排列,刚好低四字节是从大到小排列的。
我们首先需要查看0x6032d0处的链表。通过GDB查看,结果如下:
(gdb) x/12gx 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
按照低四字节从大到小排序,应该是 03 04 05 06 01 02
所以我们的输入应该是 a = 4,b = 3, c= 2, d= 1, e = 6, f = 5,即4 3 2 1 6 5