CSAPP Lab 2 Binary Bomb 二进制炸弹

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,炸弹解除。

头铁硬刚到凌晨三点,累成傻狗。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值