Lab 2 Binary Bomb(二进制炸弹)
传说中的二进制炸弹实验,广为流传,学csapp的人对这个lab耳熟能详。
实验内容是通过反汇编一个linux可执行文件,得到它的汇编格式代码,通过分析这个汇编代码,得出解除炸弹需要输入的字符串。输入每个炸弹对应的正确字符串,就能解除这个炸弹,一共六个炸弹。
具体描述是,有6个不同的样例,对每个样例,只有输入正确的字符串才能成功通过,输入错误的字符串就会失败(表现为这个炸弹爆炸,扣作业分)。通过反汇编得到汇编代码,分析能够得到正确字符串,运行时输入即可。
初始准备
推荐使用vscode作为文本编辑工具,窗口拆分无比好用!用其他文本工具做这种开十几个窗口来回切换的实验简直是噩梦!
到CSAPP课程官网下载文件包和实验说明材料。
需要下载的有一个bomblab.pdf,一个readme文档需要自己把网页上文字复制到一个文件中保存(其实看过后才知道这个readme是给老师看了自己配置炸弹的,对学生来说不看都行),一个bomb.tar压缩包。
其实压缩包里面就三个文件,一个linux可执行程序bomb,一个炸弹的C代码概况描述,一个readme(里面就一句话,"This is an x86-64 bomb for self-study students. ")。
所以,能用到的只有三个文件,即writeup文档(就是bomblab.pdf),压缩包里的bomb和bomb.c
仔细阅读bomblab.pdf,发现需要具备gdb调试和objdump反汇编技能,可以之前学,也可以现用现查。
为开始试验,首先将bomb可执行程序文件反汇编。大概objdump的应用在这个实验里会这一句就行了。全部操作均需要在linux下完成!!!
objdump -d bomb > disassemby_version
这样,反汇编代码就到了disassemby_version文件中,下面研究它即可。
同时,为了观察程序如何运作,需要会gdb单步调试和状态查看。这都能再写一个博客了,见。
要进行gdb调试,只需要下面这条指令,但这时还未开始
gdb bomb
如果需要开始,需要输入r。指定当前启动程序的读取文件,只需要使用下面的格式.
r filename
filename中填写用来解除炸弹的字符串。
炸弹1
对照反汇编代码和bomb.c,发现main函数内结构还是容易理解的。反汇编代码中,phase_1部分代码如下。
400e1e: bf 38 23 40 00 mov $0x402338,%edi
400e23: e8 e8 fc ff ff callq 400b10 <puts@plt>
400e28: bf 78 23 40 00 mov $0x402378,%edi
400e2d: e8 de fc ff ff callq 400b10 <puts@plt>
400e32: e8 67 06 00 00 callq 40149e <read_line>
400e37: 48 89 c7 mov %rax,%rdi
400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1>
400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused>
最开始两个movcall组合,用gdb追踪地址丢失了,看也没看明白是在干什么,但是看到汇编文件开头从0到26代码段中参数,这俩puts@plt也转到了那里,猜测这和phase_1上面的两个printf打印有关,所以忽略。
400e32处调用read_line指令,这个函数体太长了,没细看,但是应该意为把输入字符串读入内存某个单元。
400e37处,是在保存环境或设置初始变量,之后调用phase_1函数。
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 callq 401338 <strings_not_equal>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq
先栈指针下移8字节,可能是保存出一些安全空间吧。400ee4应该是设置参数,在下面的callq里判断是否两字符串相等。400eee是test一下eax的值,如果eax为0,则test后zf=0,je判定成立,跳转,返回main函数,通过。如果zf=1,test不通过,炸弹就炸了。
这里关键显然在字符串判等函数。调到判等函数的汇编代码,发现判等函数先使用了字符串长度计算函数string_length,而字符串长度计算函数节选如下,在第一行就使用了rdi,与常量0判断,结合下面跳转位置401332,可知这是在判断源是否是空字符串。这说明rdi存放字符串初始地址。
000000000040131b <string_length>:
40131b: 80 3f 00 cmpb $0x0,(%rdi)
40131e: 74 12 je 401332 <string_length+0x17>
401320: 48 89 fa mov %rdi,%rdx
401323: 48 83 c2 01 add $0x1,%rdx
查看字符串判等函数,发现通过改动rdi,就能选择两个不同字符串,从而判断它们是否相等。后面炸弹5还要用到。
那么,找到这两个字符串所在的位置,其中一个是输入字符串,另一个就肯定是正确字符串。
判等函数strings_not_equal中,一个字符串最终通过rsi->rbp->rdi,另一个是main函数时最初的rdi(因为可以看到rdi一直未被改变)。查看第一个字符串,发现是在400ee4通过将0x402400常量传递到esi,即rsi低32位得到的。所以,判断一个字符串在0x402400处,gdb调试命令查看该处字符串,发现如下。
(gdb) x/4s 0x402400
0x402400: "Border relations with Canada have never been better."
0x402435: ""
0x402436: ""
0x402437: ""
不管我们输入是什么,"Border relations with Canada have never been better."这个字符串都不变,所以肯定是那个系统预设的正确串,炸弹1解除。
或者,回到main函数,能看到最初rdi是read_line的返回结果(这么说,因为read_line函数调用后面就是将rax->rdi,而rax在函数调用后一般是作为函数的参数返回的),所以指向的肯定是输入字符串,那另一个肯定是预设字符串。
其实看到phase_1中的callq之上的esi参数设置,直接猜这是答案地址,都能猜对,但是很不严谨。
炸弹2
有了1的例子,现在学聪明了,一开始该忽略的忽略,rdi传输入字符串地址。
0000000000400efc <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
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
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>
400f17: 8b 43 fc mov -0x4(%rbx),%eax
400f1a: 01 c0 add %eax,%eax
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
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 <phase_2+0x1b>
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%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 retq
400f05有意思,调用读6个数函数。之前说是字符串,这里居然成了6个数。我粗略一看,没整明白它具体是怎么读这6个数的,看起来在栈里,但是随便输入6个数,用x指令一看,果然从低到高在栈里存着,每个数占4字节,看来是int型。
那就不用管函数具体实现了,反正结果是在栈里存了输入的数。
下面400f0a是个突破口。比较常数1和栈顶元素是否相等,不等则炸?那直接知道第一个输入数字应该是1,被暂存到栈顶处。
接着看,如果相等,则到400f30,rbx暂存栈顶上4字节处地址;之后rbp暂存栈顶上24字节处地址。之前知道,一个数4字节,6个数正好12字节。
之后跳到400f17,rbx之下4字节内容赋给eax,再eax=eax+eax,之后比较eax和rbx指向的值是否相等,不等则炸。之前知道,rbx=rsp+0x4,现在又-0x4,那还是表示栈顶rsp处,此时知道,栈顶处正确值应该为1,那这样操作eax应该为1+1=2。则第二个数为2。
不炸,则跳到400f25,rbx又上移4字节,比较是否超过了rbp的界限,超了就结束,说明均正确,炸弹2解除。没超就继续判断剩余的字,就是再跳回到400f17。此时由于rbx已上移4字节,再下移4字节就回到了原来的位置,也就是正确时,栈中此处应为2,赋给eax,继续eax=eax+eax,eax=4,比较现在上移后的rbx指向的值是否为4,是则继续循环这个过程,否则炸。
这个循环不太好理解,可以自行画个图便于理解,注意,这里一个数字占4个字节,即1个字。
最后结果是每次都×2,第一个数为1,那输入如下序列。
1 2 4 8 16 32
炸弹2解除。
炸弹3
开始碰到了一个问题,就是line 389,地址400f5b处引用__isoc99_sscanf这个函数,不知道它操作的具体细节。经过试验后发现,它能够保存我们输入的字符串的前两个整型数,如果输入字符就直接终止。
输入浮点数,栈中会存一些莫名其妙的东西,不是IEEE754格式的对应浮点数,应该也是错的。
输入两个整型数后,会接连存在栈顶之上8字节处、12字节处。即rsp+8,、rsp+c。
其实测出了要输入什么信息,这题就算做得差不多了。下面是完整的炸弹3的代码。
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
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>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)
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 callq 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 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
400f60会检查是否输入了两个整型,如果不是,原地爆炸。
如果是整型,跳到400f63,检查是否输入的第一个数小于等于7,如果超了,照样爆炸。
如果没超,会按照输入的第一个参数,决定下面400f75间接跳转的位置。但是这个跳转位置超过了反汇编代码的范围,应该在include但没给出的库中。
但我们不需要知道。我输入的第一个数是1,在400f75之后单步调试,我们看不到的程序段计算出,会跳转到400fb9处(这是通过单步调试显示当前运行的代码地址看到的)。
那就解决了。400fb9附近两行在比较0x137和我们输入的第二个参数的大小关系,相等则炸弹解除,不等则原地爆炸。
0x137=311,则第一个参数输入1时,第二个参数输入311,炸弹3解除。
如上所述,我没有试第一个参数输入其他情况,理论上说有8种答案,感兴趣可以依次作答,但是只要给出一个就满分了。
炸弹4
如果炸弹3、4一起做,会发现开头的6行读输入的代码完全一样,说明也是读入两个整型数,写入到栈顶之上8、12字节处。
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
之后40102c检查是不是输入了两个参数,如果不是两个参数,会直接爆炸。
如果是两个参数,40102e会将第一个参数与0xe相比较,如果小于等于0xe(十进制14)就跳转,大于0xe就原地爆炸。
下面就是40103a了。到这里,还是不能确定要输入的两个参数到底是什么。
连续三行将edx=0xe、esi=0x0、edi=0x8+rsp(即输入的第一个数据)初始化赋值,下面要调用函数func4了。func4代码如下。
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax
400fd6: 89 c1 mov %eax,%ecx
400fd8: c1 e9 1f shr $0x1f,%ecx
400fdb: 01 c8 add %ecx,%eax
400fdd: d1 f8 sar %eax
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx
400fe2: 39 f9 cmp %edi,%ecx
400fe4: 7e 0c jle 400ff2 <func4+0x24>
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4>
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax
400ff7: 39 f9 cmp %edi,%ecx
400ff9: 7d 0c jge 401007 <func4+0x39>
400ffb: 8d 71 01 lea 0x1(%rcx),%esi
400ffe: e8 cb ff ff ff callq 400fce <func4>
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
前面400fce~400fdb操作都比较简单,无非是几个计算。但是400fdd那里sar只带一个操作数是什么意思?自己第一个数输入了个14,输出是7,并且上网上查,确定这样sar只带一个操作数相当于逻辑右移1位,即sar %eax等价于sar $1,%eax.
下面400fdf在做ecx=rsi+rax。条件判定是如果ecx<=edi就跳转。也就是说,一顿操作之后,如果ecx<=输入的第一个数,就会跳转。如果不跳转,后面会知道,eax未清零就返回,炸弹会爆炸。
跳转到400ff2会先清零eax,之后比较if (ecx>=edi),成立就返回,不成立就esi=rcx,循环调用func4,直到满足条件或其他情况终止。
总的来说,func4结构这个样子
//设输入的第一个数为n1
一顿操作计算ecx、eax
begin:
if (ecx <= n1)
goto 400ff2;
else
edx= ecx - 1
goto begin;
400ff2:
eax=0;
if (ecx >= n1)
return;
else
esi = ecx + 1;
goto begin;
显然,计算出进入第一个if时的ecx,如果输入的第一个数n1等于ecx,那么两次判定后会直接返回,func4其他部分如何运作的就不需要知道了。
手算得ecx=0x7,那第一个参数输入7即可。
func4调用完,回到40104d,直接测eax是不是0,不是0就炸。这解释了之前为什么eax要清零。
最后,401051比较第二个参数和0x0,不等也炸,说明第二个参数是0.
综上,输入7 0,炸弹4解除。
炸弹5
40106a读了个不知道是啥的东西到rax中,应该是fs指向的段内某内容。查看修改后的rax,rax=0x7f7328cb0a3fed00,不知道这是个什么东西。
之后调用计算字符串长度的函数,长度应该保存在eax中,40107f将eax和0x6相比较,不等则炸,说明输入字符串长度为6。
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx ;此处rdi赋给rdx,可能为首字符地址
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
40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 callq 40143a <explode_bomb>
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx ;此处rax=0,移动后ecx中为第一个输入字符
40108f: 88 0c 24 mov %cl,(%rsp) ;第一个字符低8位移至栈顶元素
401092: 48 8b 14 24 mov (%rsp),%rdx ;第一个字符低8位移至rdx
401096: 83 e2 0f and $0xf,%edx ;取第一个字符的低4位保存在rdx
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx ;取1个字节到edx中
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) ;将低8位也就是字符本身到rsp上一定位置
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)
4010b3: be 5e 24 40 00 mov $0x40245e,%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>
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
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 callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
后面见注释,最后发现会以输入字符的后4位作为密码表,从内存中一个固定地方读出数据,和另一个固不变的字符串相比较。固定的字符串内容为"flyers",输入的字符后四位需要查密码表,转换成flyers才行。
**最终结果是ionefg,炸弹5解除。**详细解释全做完再补。
炸弹6
难死我了,最后一个拆除花了我7个小时。具体不说了,见注释吧,分块说。把注释后代码复制到vscode或notepad++、gedit啥的读起来更方便。
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 ;现在r13中是栈顶位置
401103: 48 89 e6 mov %rsp,%rsi
401106: e8 51 03 00 00 callq 40145c <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 ;首次执行时,rbp中是栈顶位置
401117: 41 8b 45 00 mov 0x0(%r13),%eax ;取第i个数到eax
40111b: 83 e8 01 sub $0x1,%eax ;eax=ni-1
40111e: 83 f8 05 cmp $0x5,%eax ;比较eax和5
401121: 76 05 jbe 401128 <phase_6+0x34> ;如果ni<=6就跳转到隔一行,否则爆炸
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d ;之前r12d是0,现在是1.r12d是r12的低32位
40112c: 41 83 fc 06 cmp $0x6,%r12d ;比较r12d是否等于6,等于则跳转
401130: 74 21 je 401153 <phase_6+0x5f> ;现在显然不跳转,r12d才是1
401132: 44 89 e3 mov %r12d,%ebx
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax ;将下个参数ni调到eax
40113b: 39 45 00 cmp %eax,0x0(%rbp) ;如果n2=n1,爆炸,
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=0x2
401148: 83 fb 05 cmp $0x5,%ebx ;判断ebx<=5是否成立
40114b: 7e e8 jle 401135 <phase_6+0x41> ;如果ebx<=5,跳回到401135
40114d: 49 83 c5 04 add $0x4,%r13 ;如果ebx>5,r13=r13+4,现在指向栈中下个数
401151: eb c1 jmp 401114 <phase_6+0x20> ;跳回到401114
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi ;上述代码最后确保,ni<=6且互不相等
401158: 4c 89 f0 mov %r14,%rax ;rsi存栈中第7个数的地址,r14、rax现在存栈顶指针
40115b: b9 07 00 00 00 mov $0x7,%ecx ;ecx=7
401160: 89 ca mov %ecx,%edx ;edx=7
401162: 2b 10 sub (%rax),%edx ;edx=edx-ni
401164: 89 10 mov %edx,(%rax) ;ni’=7-ni
401166: 48 83 c0 04 add $0x4,%rax ;rax指向下一个数
40116a: 48 39 f0 cmp %rsi,%rax ;看看是否超过了6个数的范围
40116d: 75 f1 jne 401160 <phase_6+0x6c> ;如此循环处理全部6个数
40116f: be 00 00 00 00 mov $0x0,%esi
401174: eb 21 jmp 401197 <phase_6+0xa3> ;初始先设esi=0
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx
40117a: 83 c0 01 add $0x1,%eax
40117d: 39 c8 cmp %ecx,%eax ;自引ni'-1次,正好相当于每次在地址中加16个字节
40117f: 75 f5 jne 401176 <phase_6+0x82>
401181: eb 05 jmp 401188 <phase_6+0x94> ;五六行自引,改变rdx
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) ;rdx写到栈中,相当于第9个数及往后,以8字节写
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi ;是否6个参数都这样处理完了。
401195: 74 14 je 4011ab <phase_6+0xb7>
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ;到这里,ecx=ni‘=7-ni
40119a: 83 f9 01 cmp $0x1,%ecx ;若ecx<=1,就是说ni>=6就跳转到401183
40119d: 7e e4 jle 401183 <phase_6+0x8f> ;若ni<6,就继续运行
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82> ;如果ni<6,就跳转到401176
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx ;上面操作把ni>=6的都对应写64位0x6032d0,到对应栈中i+8个数位置上,ni<6的都循环自引得到一个rdx,写到对应栈中i+8个数的位置上
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax ;相当于又转化了一次,记为ni‘’,rbx=n1‘’,即设第i个输入数字使得rsp+20地址往后读入的值为ni‘’
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi ;这是6个数的结尾处地址,存在rsi中,rax=&n2‘’
4011ba: 48 89 d9 mov %rbx,%rcx ;rcx=n1‘’=0x603320
4011bd: 48 8b 10 mov (%rax),%rdx ;rdx=n2‘’
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) ;将n2‘’写进地址rcx+8处,即(n1‘’+8)=n2‘’,循环完成(n2''+8)=n3'',(n3''+8)=n4'',(n4''+8)=n5'',(n5''+8)=n6''
4011c4: 48 83 c0 08 add $0x8,%rax
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov %rdx,%rcx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> ;这个循环数值也是地址,最终改变0x603320处的一系列单元
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) ;(n6''+8)=0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax ;rax=(n1''+8)
4011e3: 8b 00 mov (%rax),%eax ;eax=(n2'')
4011e5: 39 03 cmp %eax,(%rbx) ;需令(n1'')>=(n2''),循环执行这一步,说明只要从ni''地址处取出的元素是按降序排列的,炸弹就拆了
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> ;那么最终是个排序问题,让存最大值的地址在前即可
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> ;4 3 2 1 6 5
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
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
大体就是这样,先是一个双重循环,再是全部被7减,再是按差用循环链表转换地址,再是按地址取数到栈,再是双重指针写数据,最后识别出满足要求需要是个降序序列。
最后输入4 3 2 1 6 5,炸弹解除。
头铁硬刚到凌晨三点,累成傻狗。