参考资料
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 call 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 call 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 ret
观察到:将0x402400写到寄存器%esi中,随后调用了strings_not_equal(判断字符串是否不想等),如果返回值%eax是0,则跳转到400ef7,进行栈恢复并且结束phase_1函数。如果返回值不是0,则会执行explode_bomb函数,炸弹就爆炸了。
以上是phase_1函数的逻辑,注意到$rsi是传递第二个参数的,而%rdx是传递第一个参数的。%rdx很有可能就是我们输入的内容,随后判断输入的内容是否与内存中0x402400的内容相同,如果不同则会爆炸。
所以我们需要得到内存中$0x402400的值.
得到是:
Border relations with Canada have never been better.
抱着学习与验证的目的,我们看一下函数string_not_equal在汇编层面是如何实现的:
首先看一下string_not_equal调用的string_length函数:
000000000040131b <string_length>:
40131b: 80 3f 00 cmpb $0x0,(%rdi) # 判断%rdi保存的内存地址中内存第一个数是否为0, 也就是'\0', 字符串的结尾
40131e: 74 12 je 401332 <string_length+0x17> # 如果相等,跳转到401332: 将0 -> %eax (返回0)
401320: 48 89 fa mov %rdi,%rdx # 参数1 -> %rdx
401323: 48 83 c2 01 add $0x1,%rdx # %rdx + 1: 字符串第一个字符内存位置+1:为了看字符串的下一个元素
401327: 89 d0 mov %edx,%eax # +1后的子串的内存起始位置 -> %eax
401329: 29 f8 sub %edi,%eax # eax的值 - edi的值 -> %eax (+1后的子串在的内存起始位置-原字符串内存中的起始位置) 当前计算的字符串的长度
40132b: 80 3a 00 cmpb $0x0,(%rdx) # 继续看%rdx (地址+1后的内存中是否为'\0')
40132e: 75 f3 jne 401323 <string_length+0x8> # 如果不是0 跳转到401332,继续上面的判断
401330: f3 c3 repz ret
401332: b8 00 00 00 00 mov $0x0,%eax
401337: c3 ret
可以汇编中是如何计算字符串长度的,不断将字符串首个内存地址+1,判断是否为0,长度计算方法是不断+1后的内存地址减去原字符串首个内存地址。还是很巧妙的,这样做可以省内存,不需要放一个而外的变量存地址。
0000000000401338 <strings_not_equal>:
401338: 41 54 push %r12 # push保存 被调用者保存
40133a: 55 push %rbp
40133b: 53 push %rbx
40133c: 48 89 fb mov %rdi,%rbx # 参数1 -> %rbx中
40133f: 48 89 f5 mov %rsi,%rbp # 参数2 -> %rbp
401342: e8 d4 ff ff ff call 40131b <string_length> # 获取参数1的字符串长度
401347: 41 89 c4 mov %eax,%r12d # 参数1长度 -> %r12d
40134a: 48 89 ef mov %rbp,%rdi # 参数2 -> %rdi (为了后续调用string_length %rdi是过程中用来传递参数的)
40134d: e8 c9 ff ff ff call 40131b <string_length> # 获取参数2的字符串长度
401352: ba 01 00 00 00 mov $0x1,%edx # 1 -> %edx
401357: 41 39 c4 cmp %eax,%r12d # 判断参数1长度与参数2长度
40135a: 75 3f jne 40139b <strings_not_equal+0x63> # 如果不相等 跳转到40139b:返回%edx的值,也就是1
40135c: 0f b6 03 movzbl (%rbx),%eax # 参数1内存位置的值 -> %eax (字节零扩展到双字)
40135f: 84 c0 test %al,%al # 参数1字节部分 &
401361: 74 25 je 401388 <strings_not_equal+0x50> # 如果参数1前四个字符为0 '\0'也就是空字符串 跳转到401388 返回0 (其实就是代表已经比较完了,所以返回0代表两个字符串相等)
401363: 3a 45 00 cmp 0x0(%rbp),%al # 对比参数2 首个字符与 参数1首个字符
401366: 74 0a je 401372 <strings_not_equal+0x3a> # 相等则跳转到401372
401368: eb 25 jmp 40138f <strings_not_equal+0x57>
40136a: 3a 45 00 cmp 0x0(%rbp),%al # 对比当前内存位置的参数1与参数1是否相等
40136d: 0f 1f 00 nopl (%rax) # nopl 并没有实际执行任何有意义的操作,只是用于填充空间或对齐代码。
401370: 75 24 jne 401396 <strings_not_equal+0x5e> # 如果发现不相等的跳转到位置,直接返回1
401372: 48 83 c3 01 add $0x1,%rbx # 参数1内存位置+1
401376: 48 83 c5 01 add $0x1,%rbp # 参数2内存位置+1
40137a: 0f b6 03 movzbl (%rbx),%eax # 参数1内存位置的值 -> %eax (字节扩展到双字)
40137d: 84 c0 test %al,%al # 判断是否为\0
40137f: 75 e9 jne 40136a <strings_not_equal+0x32> # 如果不是跳转到40136a 继续对比是否相等
401381: ba 00 00 00 00 mov $0x0,%edx # 如果是\n 返回0
401386: eb 13 jmp 40139b <strings_not_equal+0x63>
401388: ba 00 00 00 00 mov $0x0,%edx # 0 -> edx
40138d: eb 0c jmp 40139b <strings_not_equal+0x63>
40138f: ba 01 00 00 00 mov $0x1,%edx
401394: eb 05 jmp 40139b <strings_not_equal+0x63>
401396: ba 01 00 00 00 mov $0x1,%edx
40139b: 89 d0 mov %edx,%eax
40139d: 5b pop %rbx # 栈恢复
40139e: 5d pop %rbp
40139f: 41 5c pop %r12
4013a1: c3 ret
也比较明确,就是先判断两个字符串的长度是否相等,再逐个字符进行判断。
Phase_2
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp # 栈指针
400f02: 48 89 e6 mov %rsp,%rsi # rsp -> rsi
400f05: e8 52 05 00 00 call 40145c <read_six_numbers> # 读六个数
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) # M[rsp] 是否等于1
400f0e: 74 20 je 400f30 <phase_2+0x34> # 如果等于跳转到400f30 不等于爆炸
400f10: e8 25 05 00 00 call 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax # M[rbx-0x4] -> eax;第一次进来:也M[rsp+0x4-0x4] -> eax, eax是1.第二次进来:400f25行已经将rbx+4乐,所以最后是M[rsp+0x4]其实就是第2个参数;相应的后面就是第3、4、5、6个参数
400f1a: 01 c0 add %eax,%eax # eax=2*eax, 第一次循环进来:2;第二次进来,上一次是2,两倍就是4;往后每次都是两倍,所以六个数分别对应1 2 4 8 16 32
400f1c: 39 03 cmp %eax,(%rbx) # eax是否等于M[rbx];第一次循环:2是否等于M[rsp+0x4]
400f1e: 74 05 je 400f25 <phase_2+0x29> # 相等跳到0x4
400f20: e8 15 05 00 00 call 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx # rbx+4 -> rbx
400f29: 48 39 eb cmp %rbp,%rbx # 对比rbx是否与rbp相等,注意rbp等于rsp+0x18,十进制是rsp+24
400f2c: 75 e9 jne 400f17 <phase_2+0x1b> # 不相等就跳转到400f17,到这里就很明确了这里是为了依次判断六个数是否等于预期的值.
400f2e: eb 0c jmp 400f3c <phase_2+0x40> # 相等就结束函数
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx # rsp+0x4 -> rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp # rsp+0x18 -> rbp
400f3a: eb db jmp 400f17 <phase_2+0x1b> # 跳转
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 ret
入手可以看到首先要读取六个数,随后判断M[%rsp]的值是否等于1,不等于1直接爆炸了。等于1的话跳转到相应位置,相应位置上%rbx的位置存%rsp+0x4,%rbp存+0x18,对应十进制是+4与+24。随后得到%eax的值是1,然后扩大两倍判断。rsp的值相较最开始分别加,4 8 12 16 20 24,对应循环五次,每次都扩充两倍,所以输入的值应该是1 2 4 8 16 32。从最开始的1每次扩充2倍。
继续看一下read_six_numbers函数是如何做到读取六个参数的
000000000040145c <read_six_numbers>:
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx # rsi -> rdx; 参数1 -> rdx
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx # rsi+0x4 -> rcx
401467: 48 8d 46 14 lea 0x14(%rsi),%rax # rsi+0x14 -> rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) # rsi+0x14 -> M[%rsp+0x8]
401470: 48 8d 46 10 lea 0x10(%rsi),%rax # rsi+0x10 -> rax
401474: 48 89 04 24 mov %rax,(%rsp) # rsi+0x10 -> M[%rsp]
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 # rsi+0xc -> %r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 # rsi+0x8 -> %r8
401480: be c3 25 40 00 mov $0x4025c3,%esi # 0x4025c3 -> %rsi
401485: b8 00 00 00 00 mov $0x0,%eax # 0 -> %eax
40148a: e8 61 f7 ff ff call 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax # 5 -> %eax
401492: 7f 05 jg 401499 <read_six_numbers+0x3d> # 如果上面sscanf返回值>5 返回函数,如果<= 直接爆炸
401494: e8 a1 ff ff ff call 40143a <explode_bomb>
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 ret
注意sscanf函数:
int sscanf(const char *str, const char *format, ...);
str
:指向要读取的字符串。format
:指定输入格式控制。...
:可变数量的额外参数,用于存储从str
中按照format
指定的格式提取出的数据。- 返回值:成功返回成功匹配并复制的数据项个数
根据 x86-64 ABI,sscanf
函数的参数依次通过以下寄存器传递:
%rdi
:指向要解析的字符串(str
)。%rsi
:格式化字符串(format
)。%rdx
:指向第一个输出变量的指针(用于存储解析到的值)。%rcx
:指向第二个输出变量的指针(如果有)。%r8
:指向第三个输出变量的指针(如果有)。%r9
:指向第四个输出变量的指针(如果有)。
Phase_3
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # sscanf中存储第2个变量的地址
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # sscanf中存储第1个变量的地址
400f51: be cf 25 40 00 mov $0x4025cf,%esi # 格式化字符串的地址 -> %esi
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff call 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax # 判断sscanf的返回值
400f63: 7f 05 jg 400f6a <phase_3+0x27> # 如果成功读入的数小于1则爆炸
400f65: e8 d0 04 00 00 call 40143a <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) # 参数1-0x7
400f6f: 77 3c ja 400fad <phase_3+0x6a> # 如果参数1大于0x7,跳转到400fad(爆炸)
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax # 参数1 -> %eax
400f75: ff 24 c5 70 24 40 00 jmp *0x402470(,%rax,8) # 这应该是一个跳转表 0x402470+8*rax,这里是用gdb,输入1之后发现跳转到400fb9,对应参数2的值应该是311.当然选择其他分支会有不同的答案
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe <phase_3+0x7b>
400fad: e8 88 04 00 00 call 40143a <explode_bomb>
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe <phase_3+0x7b>
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax
400fc2: 74 05 je 400fc9 <phase_3+0x86>
400fc4: e8 71 04 00 00 call 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 ret
注意phase_2中提到的sscanf函数的特征,中间用gdb看了一下0x4025cf,也就是对应sscanf函数的第二个参数:格式化字符串。发现是要输入两个数字。
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
后面发现第一个参数的值不能大于7, 我这里选择输入一个1,然后使用gdb根据跳转位置,判断第一个参数输入1之后第二个参数的值应该是0x137,也就是十进制的311。
答案:
1 311
Phase_4
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # rsp+0xc -> rcx 第2个参数
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # rsp+0x8 -> rdx 第1个参数
40101a: be cf 25 40 00 mov $0x4025cf,%esi # sscanf的format
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff call 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) # 参数1-0xe
401033: 76 05 jbe 40103a <phase_4+0x2e> # 如果参数1<=0xe 跳转到40103a,否则就爆炸
401035: e8 00 04 00 00 call 40143a <explode_bomb>
40103a: ba 0e 00 00 00 mov $0xe,%edx # 0xe->edx
40103f: be 00 00 00 00 mov $0x0,%esi # 0->rsi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi # 参数1 -> edi
401048: e8 81 ff ff ff call 400fce <func4> # 调用func4
40104d: 85 c0 test %eax,%eax # test func4的返回值
40104f: 75 07 jne 401058 <phase_4+0x4c> # 不等于0就爆炸
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) # 参数2不等于0就爆炸
401056: 74 05 je 40105d <phase_4+0x51>
401058: e8 dd 03 00 00 call 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 ret
首先观察phase_4函数,读入两个参数要求参数1≤14,并且将参数1、0、14作为参数调用fun4函数,并且要求fun4返回的值为0,而且参数2也要为0。接下来继续看fun4函数
0000000000400fce <func4>:
# phase_4调用此函数时: %rdi:参数1 %rsi:0 %rdx:0xe
# 并且要保证此函数返回的值是0,也就是最后%rax的值
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax # rdx(0xe) -> eax
400fd4: 29 f0 sub %esi,%eax # eax=0xe-0=0xe
400fd6: 89 c1 mov %eax,%ecx # eax -> ecx
400fd8: c1 e9 1f shr $0x1f,%ecx # ecx=ecx>>1f(十进制31) 逻辑右移
400fdb: 01 c8 add %ecx,%eax # eax=eax+ecx
400fdd: d1 f8 sar %eax # 算术右移 默认1位 eax=eax/2
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx # rax+rsi -> ecx
400fe2: 39 f9 cmp %edi,%ecx # ecx-edi
400fe4: 7e 0c jle 400ff2 <func4+0x24> # ecx-edi<=0 跳转到 400ff2
400fe6: 8d 51 ff lea -0x1(%rcx),%edx # edx=rcx-1
400fe9: e8 e0 ff ff ff call 400fce <func4> # 重复运行上面内容
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax # 0 -> eax
400ff7: 39 f9 cmp %edi,%ecx # ecx-edi
400ff9: 7d 0c jge 401007 <func4+0x39> # exc-edi>=0 结束函数运行,【这是我们的目标】 %rax为0并且函数返回
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff call 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 ret
看每条汇编指令可以看到我们的目标在400ff9,上一条将%eax设置为0并且返回。顺着以上的内容,根据两条判断语句可以确定参数1的值可以选择7.
所以答案是:7 0
进一步,尝试将汇编还原成C
// 按照汇编命令逻辑逐步还原
int func4(int edi, int esi, int edx)
{
int eax = edx;
eax = eax - esi;
int ecx = eax;
ecx = ecx >> 31;
eax = eax + ecx;
eax = eax >> 1;
ecx=eax+esi;
if (ecx-edi) <= 0
{
eax = 0;
if (ecx - edi >= 0)
return eax
esi = ecx + 1
return 2 * func4(edi, esi, edx)
}
edx = ecx-1
return 2 * func4(edi, esi, edx) + 1
}
// 按照高级语言的逻辑
int func4(int edi, int esi, int edx)
{
int eax = edx - esi;
eax = ((eax >> 31) + eax) >> 1;
int ecx = eax + esi;
if (ecx > edi)
return 2 * func4(edi, esi, ecx-1)
else if(ecx < edi)
return 2 * func4(edi, ecx+1, edx) + 1
else //我们需要的分支
return 0
}
Phase_5
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx # 参数1 -> rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax # 金丝雀保护机制
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp) # 金丝雀保护机制
401078: 31 c0 xor %eax,%eax # eax异或 清零操作
40107a: e8 9c 02 00 00 call 40131b <string_length> # 获取参数1长度
40107f: 83 f8 06 cmp $0x6,%eax # 字符串参数1的长度不是6就爆炸
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 call 40143a <explode_bomb>
401089: eb 47 jmp 4010d2 <phase_5+0x70> # 跳转执行eax清零操作
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # M[rdx+rax]->ecx 字节零扩展 (gdb验证后是字符串的每个字符)
40108f: 88 0c 24 mov %cl,(%rsp) # cl -> M[rsp];上一步从内存中读内容 (字符移动到rsp内存位置)
401092: 48 8b 14 24 mov (%rsp),%rdx # M[rsp] -> rdx;M[从内存中读的数] -> rdx (字符 -> rdx)
401096: 83 e2 0f and $0xf,%edx # 高位清0 保留后8位
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # M[rdx+0x4024b0] -> rdx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # dl -> M[rsp+rax+0x10]
4010a4: 48 83 c0 01 add $0x1,%rax # rax=rax+1
4010a8: 48 83 f8 06 cmp $0x6,%rax # 对比rax与6
4010ac: 75 dd jne 40108b <phase_5+0x29> # 不想等则跳转 (也就是i=0;i<6;i++)
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) # M[rsp+0x16] -> 0 (这一步是添加字符串的结尾\0)
4010b3: be 5e 24 40 00 mov $0x40245e,%esi # 0x40245e -> esi
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # rsp+0x10 -> rdi
4010bd: e8 76 02 00 00 call 401338 <strings_not_equal> # 判断rdi与rsi是否相等 (到这里与固定值对比的不是input,input应该是保存在M[rsp],对比的是M[rsp+0x10],往前推一步就是M[rdx+0x4024b0] -> rdx ,而rdx是我们输入的每个字符,那就是控制从0x4024b0开始多少个位置的字符与固定值对比!) (还有一点要注意,对于输入的字符有一个&0xf的操作,问题就变成了 ? & 0xf = [0x40245e处字符串中index])
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9 <phase_5+0x77> # 不相等就爆炸 相等就结束函数
4010c6: e8 6f 03 00 00 call 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 # rax=0
4010d7: eb b2 jmp 40108b <phase_5+0x29>
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 call 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 ret
首先就是获取参数1的长度,如果长度不是6就直接爆炸,提醒我们这次输入是一个字符串,而且长度要为6.
40108b开始之后是核心部分,注意到在进入核心部分之前(上一行跳转指令)寄存器rax的值已经是0了,并且在401067中rbx的值与rsi值相同,所以40108b的操作就是去内存中读取输入字符串的字符,具体哪个字符随着rax在后续加1(4010a4)决定。这里可以先看一下4010a4-4010ac,还是很明确的,就是循环六次,按顺序依次操作字符串中的字符。
接下来就看,后续指令拿字符做了什么事情:
两件事情:将输入的字符串保存到rsp对应内存地址中,将M[字符&0xf + 0x4024b0]的字符复制到M[rsp+rax+0x10]。
循环结束后,对比0x40245e与M[rsp+0x10]处的字符串是否相等。
分析完以上就很明确了,其实将输入字符串的每个字符与地址0x4024b0相加,获取新地址的字符,六个字符计算出新地址(0x4024b0处的字符串下标为输入字符串字符的新字符串)组成的新字符串与地址0x40245e是否相同.
接下来查看两个地址的字符串:
(gdb) x/s 0x40245e
0x40245e: "flyers"
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
(gdb)
所以,要保证输入的字符串的每个字符对应下标分别为9、15、14、5、6、7。还要注意到有个&0xf的操作,可以查看ascii表后找对应的,也可以运行下面代码:
#include <stdio.h>
int main()
{
for (char c = 'a'; c <= 'z'; c++)
printf("%c: %d\n",c, c & 0xf);
return 0;
}
找到对应的字符:
ionefg
phase_6
00000000004010f4 <phase_6>:
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
401100: 49 89 e5 mov %rsp,%r13
401103: 48 89 e6 mov %rsp,%rsi
401106: e8 51 03 00 00 call 40145c <read_six_numbers>
# 以上内容读六个数,保存在 M[%rsp] - M[%rsp + 0x14]
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> # input-1是否<=5,>5就爆炸
401123: e8 12 03 00 00 call 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 call 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>
# 以上内容判定每个数-1后是否小于5 并且与其他的数不相同
# 这里猜测答案应该是1-6的某一种排列方式
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx # 依次取每个数用7减掉
401164: 89 10 mov %edx,(%rax) # 保存到输入原来地址
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>
# 以上内容将每个参数改为:n[i] = 7 - input[i]
40116f: be 00 00 00 00 mov $0x0,%esi # esi=0
401174: eb 21 jmp 401197 <phase_6+0xa3> # goto L0
# L3
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx # rdx=M[rdx+0x8] -> rdx=node->next
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax # 循环n[0]-1次
40117f: 75 f5 jne 401176 <phase_6+0x82> # 如果n[i]不是1 遍历到第n[i]个节点
401181: eb 05 jmp 401188 <phase_6+0x94>
# L1
401183: ba d0 32 60 00 mov $0x6032d0,%edx # list的首地址
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) # M[rsp + 2*rsi + 0x20]=rdx
40118d: 48 83 c6 04 add $0x4,%rsi # rsi += 0x4
401191: 48 83 fe 18 cmp $0x18,%rsi #
401195: 74 14 je 4011ab <phase_6+0xb7> # 循环结束 goto L2
# L0
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx # ecx=M[rsp+rsi]
40119a: 83 f9 01 cmp $0x1,%ecx # if n[i]==1
40119d: 7e e4 jle 401183 <phase_6+0x8f> # goto L1
40119f: b8 01 00 00 00 mov $0x1,%eax # 【n[0]!=1分支】 eax=1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx # edx=list首地址
4011a9: eb cb jmp 401176 <phase_6+0x82> # goto L3
# 以上部分就是按照n[]的数字在rsp+20开始依次存放对应的列表节点
# L2
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx # rbx=M[rsp+0x20] node[n[0]]
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 (rcx=node[n[0]])
# L4
4011bd: 48 8b 10 mov (%rax),%rdx # rdx = M[rax]; (rdx=node[i] i from 1 to 5)
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) # M[rcx+0x8]=rdx; M[M[rsp+0x20]+0x8]=rdx (node[j]->next=node[i])
4011c4: 48 83 c0 08 add $0x8,%rax # rax = rax+0x8
4011c8: 48 39 f0 cmp %rsi,%rax #
4011cb: 74 05 je 4011d2 <phase_6+0xde> # 遍历6个节点 (rax=0x28; rax<0x50; rax+=0x8)
4011cd: 48 89 d1 mov %rdx,%rcx # rcx=rdx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> # goto L4
# 遍历node[n[i]]->next = node[n[i+1]]完成重排列表
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) # M[n[6]]->next = 0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp # ebp=5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax # rax=M[rbx+0x8] (rax=node[i]->next)
4011e3: 8b 00 mov (%rax),%eax # eax=M[rax] (eax=node[i+1])
4011e5: 39 03 cmp %eax,(%rbx) # node > node->next
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> #
4011e9: e8 4c 02 00 00 call 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx # rbx = M[rbx+8]
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>
4011f7: 48 83 c4 50 add $0x50,%rsp
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 ret
# 以上内容就是判断我们重排的列表每是否按照节点value递减排列的
这个做的让人头皮发麻…
但总要去看的,一段一段去看:
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
401100: 49 89 e5 mov %rsp,%r13
401103: 48 89 e6 mov %rsp,%rsi
401106: e8 51 03 00 00 call 40145c <read_six_numbers>
# 以上内容读六个数,保存在 M[%rsp] - M[%rsp + 0x14]
这段调用read_six_numbers读取六个数
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> # input-1是否<=5,>5就爆炸
401123: e8 12 03 00 00 call 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 call 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>
# 以上内容判定每个数-1后是否小于5 并且与其他的数不相同
# 这里猜测答案应该是1-6的某一种排列方式
这段是两重循环,首先判断每个数是是否≤6 并且还要保证输入的六个数不相同
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi
401158: 4c 89 f0 mov %r14,%rax
40115b: b9 07 00 00 00 mov $0x7,%ecx
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx # 依次取每个数用7减掉
401164: 89 10 mov %edx,(%rax) # 保存到输入原来地址
401166: 48 83 c0 04 add $0x4,%rax
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>
# 以上内容将每个参数改为:n[i] = 7 - input[i]
上面这段比较简单,将输入的值变为7-input[i]
40116f: be 00 00 00 00 mov $0x0,%esi # esi=0
401174: eb 21 jmp 401197 <phase_6+0xa3> # goto L0
# L3
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx # rdx=M[rdx+0x8] -> rdx=node->next
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax # 循环n[0]-1次
40117f: 75 f5 jne 401176 <phase_6+0x82> # 如果n[i]不是1 遍历到第n[i]个节点
401181: eb 05 jmp 401188 <phase_6+0x94>
# L1
401183: ba d0 32 60 00 mov $0x6032d0,%edx # list的首地址
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) # M[rsp + 2*rsi + 0x20]=rdx
40118d: 48 83 c6 04 add $0x4,%rsi # rsi += 0x4
401191: 48 83 fe 18 cmp $0x18,%rsi #
401195: 74 14 je 4011ab <phase_6+0xb7> # 循环结束 goto L2
# L0
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx # ecx=M[rsp+rsi]
40119a: 83 f9 01 cmp $0x1,%ecx # if n[i]==1
40119d: 7e e4 jle 401183 <phase_6+0x8f> # goto L1
40119f: b8 01 00 00 00 mov $0x1,%eax # 【n[0]!=1分支】 eax=1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx # edx=list首地址
4011a9: eb cb jmp 401176 <phase_6+0x82> # goto L3
# 以上部分就是按照n[]的数字在rsp+20开始依次存放对应的列表节点
这段比较复杂,我们先看一下直接移动的地址存放的是什么:
(gdb) x/24x 0x6032d0
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000
(gdb) x/24d 0x6032d0
0x6032d0 <node1>: 332 1 6304480 0
0x6032e0 <node2>: 168 2 6304496 0
0x6032f0 <node3>: 924 3 6304512 0
0x603300 <node4>: 691 4 6304528 0
0x603310 <node5>: 477 5 6304544 0
0x603320 <node6>: 443 6 0 0
结合node,猜测应该是一个链表。每个结构体占16字节。第一个值是value(int 4字节)、第二个是key(int 4字节)、指针(8字节),按照小端法,高位8字节是指向下一个节点的地址.
struct node
{
int value;
int key;
struct node* next;
};
再顺着下面的指令走,判断以上内容就是按照我们输入的顺序在重排链表(放置到对应的栈位置实现)
# L2
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx # rbx=M[rsp+0x20] node[n[0]]
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 (rcx=node[n[0]])
# L4
4011bd: 48 8b 10 mov (%rax),%rdx # rdx = M[rax]; (rdx=node[i] i from 1 to 5)
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) # M[rcx+0x8]=rdx; M[M[rsp+0x20]+0x8]=rdx (node[j]->next=node[i])
4011c4: 48 83 c0 08 add $0x8,%rax # rax = rax+0x8
4011c8: 48 39 f0 cmp %rsi,%rax #
4011cb: 74 05 je 4011d2 <phase_6+0xde> # 遍历6个节点 (rax=0x28; rax<0x50; rax+=0x8)
4011cd: 48 89 d1 mov %rdx,%rcx # rcx=rdx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> # goto L4
# 遍历node[n[i]]->next = node[n[i+1]]完成重排列表
改变next指针位置完成最终的排列
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) # M[n[6]]->next = 0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp # ebp=5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax # rax=M[rbx+0x8] (rax=node[i]->next)
4011e3: 8b 00 mov (%rax),%eax # eax=M[rax] (eax=node[i+1])
4011e5: 39 03 cmp %eax,(%rbx) # node > node->next
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> #
4011e9: e8 4c 02 00 00 call 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx # rbx = M[rbx+8]
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb>
4011f7: 48 83 c4 50 add $0x50,%rsp
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 ret
# 以上内容就是判断我们重排的列表每是否按照节点value递减排列的
最后一步判断,排列的顺序应该是按照value递减的
0x6032d0 <node1>: 332 1 6304480 0
0x6032e0 <node2>: 168 2 6304496 0
0x6032f0 <node3>: 924 3 6304512 0
0x603300 <node4>: 691 4 6304528 0
0x603310 <node5>: 477 5 6304544 0
0x603320 <node6>: 443 6 0 0
所以n的顺序应该是3 4 5 6 1 2
对应的输入就应该是 4 3 2 1 6 5