看见的看不见的
瞬间的永恒的
青草长啊大雪飘扬
——月亮之上
完整代码见:CSAPP/bomb at main · SnowLegend-star/CSAPP (github.com)
01 字符串比较
简单的把输入的字符串和地址“0x402400”内早已存储的字符串相比较。如果两个字符串相等则函数返回,否则炸弹爆炸。
这里有一个困扰我很久的疑惑——我之前一直以为每个寄存器自身就是代表一个地址,同时寄存器自己内部存储着一个数据。就比如%eax=0xFFFF ABCD,那(%eax)也是对寄存器内部操作从而把存储在它里面的数据给提取出来。这种想法实在是大错特错!所谓寄存器,访问它直接用这个寄存器的名字即可,寄存器的名字并不是一个无用的标签,而是相当于可以直接访问寄存器的一个引用。至于寄存器它自己存储的那个数字,可以是地址也可以是一个数据。如mov (%eax),%esi就是访问存储在0xFFFF ABCD这个地址里面的数据传递给%esi,是要访问内存的。而mov %eax,%esi就是直接令%esi=0xFFFF ABCD。
值得一提的是,一般%eax里面存储的是可以直接拿来使用的数据而非地址,%rsp作为栈帧寄存器,一般存储的数字表示一个地址。
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp ;rsp-=8 准备腾出空间
400ee4: be 00 24 40 00 mov $0x402400,%esi ;把这个地址存入esi,我们输入的内容作为参数1
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> ;如果字符串不相等 *(esi)="Border relations with Canada have never been better."
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17> ;if(eax==0)
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> ;bomb!
400ef7: 48 83 c4 08 add $0x8,%rsp ;else return ;
400efb: c3 retq
02循环
lea和mov两条指令的区别。给出如下两个例子
lea (%eax,%ebx,1),%esi
mov (%ebx,%ebp,1),%eax
在lea指令中,“()”并不是进行地址引用的意思,而是单纯拿来对括号里的三元组进行运算,即经过lea命令后,%esi=%eax+%ebx。但是在mov命令中,“()”表示的是取地址的意思,即经过mov命令后,% eax=(%ebx+%ebp)。
这题主要就是要理解输入的6个数组元素整齐存在于刚才开辟的栈空间中,用rbx来遍历这个栈空间。
0000000000400efc <phase_2>:
400efc: 55 push %rbp ;
400efd: 53 push %rbx ;
400efe: 48 83 ec 28 sub $0x28,%rsp ;rsp-0x28=rsp,准备腾出空间
400f02: 48 89 e6 mov %rsp,%rsi ;rsi=rsp 现在把栈的首地址传递给rsi,一会儿<read_six_numbers>会调用rsi这个参数
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> ;读入6个数字
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) ;if((rsp)==1)
400f0e: 74 20 je 400f30 <phase_2+0x34> ;goto 400f30:lea 0x4(%rsp),%rbx 所以(rsp)=1
400f10: e8 25 05 00 00 callq 40143a <explode_bomb> ;else boom!
400f15: eb 19 jmp 400f30 <phase_2+0x34> ;这句话能运行得到吗
400f17: 8b 43 fc mov -0x4(%rbx),%eax ;eax=(rbx-0x4)
400f1a: 01 c0 add %eax,%eax ;eax*=2
400f1c: 39 03 cmp %eax,(%rbx) ;if((rbx)==eax)
400f1e: 74 05 je 400f25 <phase_2+0x29> ;goto 400f25
400f20: e8 15 05 00 00 callq 40143a <explode_bomb> ;else bomb!
400f25: 48 83 c3 04 add $0x4,%rbx ;rbx=rbx+0x4
400f29: 48 39 eb cmp %rbp,%rbx ;if(rbx!=rbp)
400f2c: 75 e9 jne 400f17 <phase_2+0x1b> ;goto 400f17:mov -0x4(%rbx),%eax
400f2e: eb 0c jmp 400f3c <phase_2+0x40> ;goto 400f3c return
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx ;rbx=rsp+0x4
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp ;rbp=rsp+0x18
400f3a: eb db jmp 400f17 <phase_2+0x1b> ;goto 400f17
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
汇编代码改写如下
int phase_2(){
rsi=rsp;
if((rsp)==1){
rbx=rsp+0x4;
rbp=rsp+0x18;
eax=rbx-0x4;
eax*=2;
if((rbx)==eax){
rbx=rbx+0x4;
if(rbx!=rbp)
goto line9;
else
return ;
}
else boom!
}
else
boom!
}
03 条件与分支(switch)
这题的关键点在于认识到“jmpq *0x402470(,%rax,8)”是一个跳转表,从而理解下面的那几条cmp命令相当于switch的几条case。
这题和上一题的输入对我还是有很大的启发效果的,那就是不用再纠结到底是用哪个寄存器来存储输入的数据了,直接看函数开始的%rsp自减操作和紧跟着的push操作。本质上就是抓住一点,输入的参数说到底还是存储在刚才开辟的栈空间站中。
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp ;栈指针减24,腾出空间
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ;
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ;传入这个栈的两个参数地址从(rsp+0x8)开始
400f51: be cf 25 40 00 mov $0x4025cf,%esi ;给esi传入内容 "%d %d" (可以强制类型转换为一个字符串)
400f56: b8 00 00 00 00 mov $0x0,%eax ;eax=0
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> ;读入输入
400f60: 83 f8 01 cmp $0x1,%eax ;if(eax>1)吧 所以eax必为1
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) ;比较(rsp+8)这个地址里面存储的数据和0x7的大小 if((rsp+8)>0x7)
400f6f: 77 3c ja 400fad <phase_3+0x6a> ;跳转到400fad 即引爆炸弹
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax ;否则eax=(rsp+8) 所以exa的值是小于等于0x7的
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) ;我们发现*0x402470的值为4198268 跳转到4198268+rax*8 十进制的4198268=0x400f7c
400f7c: b8 cf 00 00 00 mov $0xcf,%eax ;eax=0xcf
400f81: eb 3b jmp 400fbe <phase_3+0x7b> ;跳转到400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax ;eax=0x2c3
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 ;if(eax==(rsp+0xc))
400fc2: 74 05 je 400fc9 <phase_3+0x86> ;跳转到400fc9 所以eax必然=(rsp+0xc)
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb> ;否则爆炸 所以eax=(rsp+0xc)
400fc9: 48 83 c4 18 add $0x18,%rsp ;恢复栈指针
400fcd: c3 retq
04 递归调用和栈
这题的麻烦点我感觉在于phase_4和func4之间传递的参数。我们假设函数A调用函数B。如果函数B中突然就出现了用一个参数寄存器Ri 来给其他寄存器赋值,或者用一个还没在函数B中进行赋值的寄存器Rj 和一个数相比较,且Ri 和Rj 都在A中被赋值过,那么Ri 和Rj 大概率就是在两个函数间传递的参数。
0000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp ;栈指针自减8
400fd2: 89 d0 mov %edx,%eax ;eax=edx=14
400fd4: 29 f0 sub %esi,%eax ;eax=eax-esi esi=0
400fd6: 89 c1 mov %eax,%ecx ;ecx=eax=edx-esi
400fd8: c1 e9 1f shr $0x1f,%ecx ;逻辑右移ecx31位,即判断ecx的符号位 汇编的右移会改变寄存器存储的值,这一段和c语言的不同
400fdb: 01 c8 add %ecx,%eax ;eax=ecx+eax
400fdd: d1 f8 sar %eax ;把eax算数右移一位
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx ;ecx=rax+rsi
400fe2: 39 f9 cmp %edi,%ecx ;if(ecx<=edi)
400fe4: 7e 0c jle 400ff2 <func4+0x24> ;goto 400ff2
400fe6: 8d 51 ff lea -0x1(%rcx),%edx ;else edx=rcx-1
400fe9: e8 e0 ff ff ff callq 400fce <func4> ;goto 400fce 开始递归了 为了跳出递归,就有ecx=edi
400fee: 01 c0 add %eax,%eax ;eax=2*eax
400ff0: eb 15 jmp 401007 <func4+0x39> ;goto 401007 返回
400ff2: b8 00 00 00 00 mov $0x0,%eax ;eax=0
400ff7: 39 f9 cmp %edi,%ecx ;if(ecx>=edi)
400ff9: 7d 0c jge 401007 <func4+0x39> ;goto 401007 返回
400ffb: 8d 71 01 lea 0x1(%rcx),%esi ;else esi=rcx+1
400ffe: e8 cb ff ff ff callq 400fce <func4> ;goto 400fce 又开始递归了他奶奶滴
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax ;eax=2*rax+1
401007: 48 83 c4 08 add $0x8,%rsp ;恢复栈指针
40100b: c3 retq ;返回0才行
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp ;栈指针自减24
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ;rcx 这个空间存num1 这两句lea有什么作用呢
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ;这个空间存num2 给两个变量rcx和rdx腾出空间 不是给两个变量腾出空间
40101a: be cf 25 40 00 mov $0x4025cf,%esi ;往esi里面传入数据 “%d %d”
40101f: b8 00 00 00 00 mov $0x0,%eax ;eax=0
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> ;从stdin接收数据
401029: 83 f8 02 cmp $0x2,%eax ;if(eax!=0x2) 这里eax里面存储的是刚刚从函数400bfo传来的scanf的输出参数
40102c: 75 07 jne 401035 <phase_4+0x29> ;boom!
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) ;else if(rdx=<0xe) 所以eax必然等于2
401033: 76 05 jbe 40103a <phase_4+0x2e> ;goto 40403a 所以rdx<=0xe
401035: e8 00 04 00 00 callq 40143a <explode_bomb> ;else bomb!
40103a: ba 0e 00 00 00 mov $0xe,%edx ;edx=0xe
40103f: be 00 00 00 00 mov $0x0,%esi ;esi=0
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi ;edi=edx=0xe rdx不是刚刚被赋值了吗? mov指令和lea指令的区别
401048: e8 81 ff ff ff callq 400fce <func4> ;goto func4
40104d: 85 c0 test %eax,%eax ;if(eax!=0) 所以eax=0
40104f: 75 07 jne 401058 <phase_4+0x4c> ;goto 401058 boom!
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) ;else if(rcx==0) 所以rcx=0=num2
401056: 74 05 je 40105d <phase_4+0x51> ;goto 40105d恢复栈指针
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
汇编代码改写如下
//x=edx=14,y=esi=0,z=ecx,val=eax,k=edi
int func4(int x,int y,int k){
val=x;
val=val-y;
z=val;
z=z>>31;
val=val+z;
val=val>>1;
z=val+y;
if(z>k){
x=z-1;
func4();
val=val*2;
return val;
}
else{
val=0;
if(z<k){
y=z+1;
func4();
val=val*2+1;
}
else return val;
}
}
05 指针与数组
刚才忽然悟了一个东西,之前的几个phase里面上来就是对esi这个寄存器进行操作,搞得我就很疑惑esi不是一般作为传递第二个参数的寄存器吗,为什么不用edi这个寄存器呢?而且我们的输入又是从什么地方被接受到这个phase的呢?
后来我在一篇题解里看到了这样一句话,“(phase_1)将0x402400赋值到%esi寄存器,根据X86-64的参数传递规则,我们知道%esi寄存器保存的是函数调用的第二个参数。第一个参数就是我们通过标准输入/指定的输入设备文件输入的数据,这里我们简单输入“1234”,存放在%rax指向的地址处”这句话看下来我对为什么第一个直接操作esi似懂非懂。昨晚在知乎上看到了一篇题解,也提到了我们输入的内容是作为第一个参数传入的。我这才恍然大悟,原来rdi只是没有被显示地使用啊,汇编里面的函数参数传递还是一般遵循相应寄存器保存第几个参数这一规律的。我们可以看看bomb.c文件,可以发现每个函数都会有形如“phase_i(input)”这种函数调用,这就更证实了第一个传递的参数是我们的输入内容,而bomb.asm里面的esi保存的参数则更像是隐式参数,是通过内存中预先存好的值来进行传递的。而且通过观察phase_2~4,我们可以发现函数都是调用了函数“__isoc99_sscanf@plt”来读入输入内容,在这里就得用宏观的眼光审视这个函数了——要牢记输入的内容会按序存储在栈帧上,更准确地说是存储在rsp刚才开辟的空间里面,也就是用rsp可以顺序访问到输入内容,而不用去想我们的输入到底是通过哪个寄存器来传递的。
在本题中,通过“mov %dl,0x10(%rsp,%rax,1)”我们可以判断出处理后的输入内容按顺序存储在以“rsp+0x10”为起始的地址中。由分析可知这一关通过取我们输入六个字符的ASCII码的低四位作为索引值,查找maduiersnfotvbyl里的字符,最后返回的字符串应该是flyers。
maduiersnfotvbyl中f为第9位,l为第15位,y第14位,e第5位,r第6位,s第7位
即我们需要输入6个字符,使它们ASCII码低四位分别是:1001, 1111, 1110, 0101, 0110, 0111。查看ASCII表可找到对应字符,a的ASCII码为01100001,因此,其中一种解码可为ionuvw;ionefg;9?>567
0000000000401062 <phase_5>:
401062: 53 push %rbx ;先保存rbx rbx存放着输入的字符串的地址
401063: 48 83 ec 20 sub $0x20,%rsp ;栈指针腾出32byte,是准备开辟数组了吗
401067: 48 89 fb mov %rdi,%rbx ;rbx=rdi 把输入的参数地址传递到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) ;(rsp+0x18)=rax
401078: 31 c0 xor %eax,%eax ;将eax清0
40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax ;if(eax==6) 所以字符串长度为6=eax
401082: 74 4e je 4010d2 <phase_5+0x70> ;goto 4010d2
401084: e8 b1 03 00 00 callq 40143a <explode_bomb> ;else bomb!
401089: eb 47 jmp 4010d2 <phase_5+0x70> ;goto 4010d2 这句话不是多此一举吗
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx ;进行0扩展字节到双字,但是rbx不是四字吗?这是(%rbx,%rax,1)才是操作源数,是双字的 ecx=rbx+rax还是ecx=(rbx+rax)呢 后者
40108f: 88 0c 24 mov %cl,(%rsp) ;(rsp)=cl 假设数组名字是A,此处应该是A[0]=cl cl是ecx的低8位
401092: 48 8b 14 24 mov (%rsp),%rdx ;rdx=(rsp)
401096: 83 e2 0f and $0xf,%edx ;将edx和F相与
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx ;edx=(0x4024b0+rdx) 0x4024b0 对应字符串:maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) ;rsp+rax*1+0x10=dl dl是edx的低8位
4010a4: 48 83 c0 01 add $0x1,%rax ;rax=rax+1;
4010a8: 48 83 f8 06 cmp $0x6,%rax ;if(eax!=0x6)
4010ac: 75 dd jne 40108b <phase_5+0x29> ;goto 40108b
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) ;(rsp+0x16)=0; 是不是字符串的结尾
4010b3: be 5e 24 40 00 mov $0x40245e,%esi ;esi="flyers"
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi ;rdi=(rsp+0x10)
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax ;if(rax==0)
4010c4: 74 13 je 4010d9 <phase_5+0x77> ;
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb> ;bomb
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) ;什么意思?
4010d0: eb 07 jmp 4010d9 <phase_5+0x77> ;goto 4010d9
4010d2: b8 00 00 00 00 mov $0x0,%eax ;eax=0
4010d7: eb b2 jmp 40108b <phase_5+0x29> ;goto 40108b
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
phase_05改写如下
int phase_5(){
char *A;
A= 0x4024b0;
string str;
cin>>str;
eax=srt.length();
if(eax!=6)
bomb!
else{
eax=0;
L1:
ecx=rbx+rax*1;
(rsp)=cl;
rdx=(rsp);
edx=edx&1111;
edx=A[rdx];
rsp+rax*1+0x10=dl;
rax++;
if(eax==0x6){
(rsp+0x16)=0;
esi="flyers";
rdi=0x10+rsp;
if(eax==0){
rax=rsp+0x18;
}
else
boom!
}
else
goto L1;
}
}
06 链表与结构体
这题可谓是极尽恶心,折磨得我晕头转向。解决这题最好的方法是把代码分成几部分来看,但是把phase_6的代码分块对代码阅读的能力要求又极高,新手不完成这个phase基本分不好块,有“循环论证”那感觉的哈哈哈哈。
处理这题我的方法是先照例给每行指令写好注释,相当于通读一遍代码,然后把用if-else、for等分支循环改写代码,让代码初步变得直观。这个时候就可以去休息会儿了——tmd改完代码一百来行,还有一堆goto语句,真的再看一眼就会爆炸……OK,我们继续看改好后的代码。可以很明显地发现代码的第一部分由两层嵌套循环组成,旨在判定输入的数字都不大于6且互不相同。这里有个小技巧,就是自己跟着循环走一两遍,把rsp+4*i看成num[i]这种数组形式,之后循环要表达什么就较为容易理解了。第二部分则是形如“num[i]=7-num[i]”。
第三部分是对链表进行操作。我们使用命令“print (char*)0x6032d0”发现输出结果是“<node1> "L\001"”,看到node1要迅速反应出来这有可能是一个链表或者树,接着使用“x/66 0x6032d0”一探究竟,发现这块连续的内存大概率保存的是链表。
通过分析node的结构,可以猜测它抽象为结构体可以表示为
struct node{
int value;
int number;
node* next;
}
然后就到了一个最晦涩的循环部分,该循环根据输入数将链表中对应的第“num[i]”个结点的地址复制到 0x20(%rsp) 开始的栈中。
第四部分是要求在rsp中排序好的链表是按照值降序排列的,通过比较node[i].value,可以发现node[3]>node[4]>node[5]>node[6]>node[1]>node[2],又因为这个顺序,是经过了numx = 0x7 - numx 则原输入数据应该是4 3 2 1 6 5
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 ;rsp-=90,准备腾出空间
401100: 49 89 e5 mov %rsp,%r13 ;r13=rsp 把栈的起始地址传递给r13
401103: 48 89 e6 mov %rsp,%rsi ;rsi=rsp 把栈的起始地址传递给rsi
401106: e8 51 03 00 00 callq 40145c <read_six_numbers> ;读入六个数,两个函数通过rsi进行参数传递
40110b: 49 89 e6 mov %rsp,%r14 ;r14=rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d ;r12d=0
401114: 4c 89 ed mov %r13,%rbp ;rbp=r13 把栈的起始地址传递给rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax ;eax=(r13) 获取首地址的数据
40111b: 83 e8 01 sub $0x1,%eax ;eax-=0x1
40111e: 83 f8 05 cmp $0x5,%eax ;if(eax<=0x5)
401121: 76 05 jbe 401128 <phase_6+0x34> ;goto 401128:add $0x1,%r12d
401123: e8 12 03 00 00 callq 40143a <explode_bomb> ;else boom!
401128: 41 83 c4 01 add $0x1,%r12d ;r12d+=0x1
40112c: 41 83 fc 06 cmp $0x6,%r12d ;if(r12d==0x6)
401130: 74 21 je 401153 <phase_6+0x5f> ;goto 401153:lea 0x18(%rsp),%rsi
401132: 44 89 e3 mov %r12d,%ebx ;else ebx=r12d
401135: 48 63 c3 movslq %ebx,%rax ;rax=ebx
401138: 8b 04 84 mov (%rsp,%rax,4),%eax ;eax=rsp+rax*4
40113b: 39 45 00 cmp %eax,0x0(%rbp) ;if(eax!=(rbp))
40113e: 75 05 jne 401145 <phase_6+0x51> ;goto 401145 所以eax!=(rbp)
401140: e8 f5 02 00 00 callq 40143a <explode_bomb> ;else boom!
401145: 83 c3 01 add $0x1,%ebx ;ebx+=0x1
401148: 83 fb 05 cmp $0x5,%ebx ;if(ebx<=0x5)
40114b: 7e e8 jle 401135 <phase_6+0x41> ;goto 401135:movslq %ebx,%rax
40114d: 49 83 c5 04 add $0x4,%r13 ;else r13+=0x4
401151: eb c1 jmp 401114 <phase_6+0x20> ;goto 401114: mov %r13,%rbp
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi ;rsi=(rsp+0x18)
401158: 4c 89 f0 mov %r14,%rax ;rax=r14
40115b: b9 07 00 00 00 mov $0x7,%ecx ;ecx=0x7
401160: 89 ca mov %ecx,%edx ;edx=ecx
401162: 2b 10 sub (%rax),%edx ;edx-=(rax)
401164: 89 10 mov %edx,(%rax) ;(rax)=edx
401166: 48 83 c0 04 add $0x4,%rax ;rax+=0x4
40116a: 48 39 f0 cmp %rsi,%rax ;if(rax!=rsi)
40116d: 75 f1 jne 401160 <phase_6+0x6c> ;goto 401160:mov %ecx,%edx
40116f: be 00 00 00 00 mov $0x0,%esi ;else esi=0
401174: eb 21 jmp 401197 <phase_6+0xa3> ;goto 401197:ecx=rsp+rsi*1
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx ;rdx=(rdx+0x8)
40117a: 83 c0 01 add $0x1,%eax ;eax+=0x1
40117d: 39 c8 cmp %ecx,%eax ;if(eax!=ecx)
40117f: 75 f5 jne 401176 <phase_6+0x82> ;goto 401176
401181: eb 05 jmp 401188 <phase_6+0x94> ;else goto 101188
401183: ba d0 32 60 00 mov $0x6032d0,%edx ;edx=0x6032d0 *0x6032d0=<node1> "L\001"
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) ;rdx=0x20+rsp+rsi*2
40118d: 48 83 c6 04 add $0x4,%rsi ;rsi+=0x4
401191: 48 83 fe 18 cmp $0x18,%rsi ;if(rsi==0x18)
401195: 74 14 je 4011ab <phase_6+0xb7> ;goto 4011ab:0x20(%rsp),%rbx
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ;这是数组的形式吧 ecx=rsp+rsi*1
40119a: 83 f9 01 cmp $0x1,%ecx ;if(ecx<=0x1)
40119d: 7e e4 jle 401183 <phase_6+0x8f> ;goto 401183:mov $0x6032d0,%edx
40119f: b8 01 00 00 00 mov $0x1,%eax ;eax=0x1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx ;edx=0x6032d0 *0x6032d0="L\001" 用0x6032d0查看!
4011a9: eb cb jmp 401176 <phase_6+0x82> ;goto 401176:mov 0x8(%rdx),%rdx
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx ;rbx=(rsp+0x20)
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
4011bd: 48 8b 10 mov (%rax),%rdx ;rdx=(rax) rax里面存的是地址而不是普通数据了
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) ;(rcx+0x8)=rdx
4011c4: 48 83 c0 08 add $0x8,%rax ;rax+=0x8
4011c8: 48 39 f0 cmp %rsi,%rax ;if(rax!=rsi)
4011cb: 74 05 je 4011d2 <phase_6+0xde> ;goto 4011d2:movq $0x0,0x8(%rdx)
4011cd: 48 89 d1 mov %rdx,%rcx ;else rcx=rdx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> ;goto 4011bd: mov (%rax),%rdx
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) ;(rdx+8)=0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp ;ebp=0x5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax ;rax=(rbx+0x8)
4011e3: 8b 00 mov (%rax),%eax ;eax=(rax)
4011e5: 39 03 cmp %eax,(%rbx) ;if((rbx)>=eax)
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> ;goto 4011ee 所以(rbx)>=eax
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> ;else boom
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx ;rbx=(rbx+0x8)
4011f2: 83 ed 01 sub $0x1,%ebp ;ebp-=0x1
4011f5: 75 e8 jne 4011df <phase_6+0xeb> ;if(ebp!=0) goto 4011df:mov 0x8(%rbx),%rax
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
000000000040145c <read_six_numbers>:
40145c: 48 83 ec 18 sub $0x18,%rsp ;rsp自减来腾出空间
401460: 48 89 f2 mov %rsi,%rdx ;rdx=rsi 这里的rsi是由pahse_2传递过来的
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx ;rcx=rsi+0x4
401467: 48 8d 46 14 lea 0x14(%rsi),%rax ;rax=rsi+0x14
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) ;(rsp+0x8)=rax
401470: 48 8d 46 10 lea 0x10(%rsi),%rax ;rax=rsi+0x10
401474: 48 89 04 24 mov %rax,(%rsp) ;(rsp)=rax
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 ;esi=0x4025c3
401485: b8 00 00 00 00 mov $0x0,%eax ;eax=0
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax ;if(eax>5)
401492: 7f 05 jg 401499 <read_six_numbers+0x3d> ;return 所以eax=6
401494: e8 a1 ff ff ff callq 40143a <explode_bomb> ;else bomb!
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
可以把汇编改写为类似c语言的格式
int phase_6(){
r13=rsp;
rsi=rsp;
//读入六个数,这六个数在(rsp)~(rsp+0x18)
r14=rsp;
r12d=0;
rbp=r13;
eax=(r13);
eax-=0x1;
if(eax<=0x5){
r12d+=0x1;
if(r12d!=0x6){
ebx=r12d;
rax=ebx;
eax=rsp+rax*4;
if(eax!=(rbp)){
ebx+=0x1;
if(ebx>0x5){
r13+=0x4;
goto line94:rbp=r13;
}
else{
goto line101:rax=ebx;
}
}
else boom!
}
else{
rsi=(rsp+0x18);
rax=r14;
ecx=0x7;
ecx=edx;
edx-=(rax);
(rax)=edx;
rax+=0x4;
if(rax==rsi){
esi=0;
ecx=rsp+rsi*1;
if(ecx>0x1){
eax=0x1;
edx=0x6032d0;
rdx=(rdx+0x8);
eax+=0x1;
if(eax==ecx){
rdx=0x20+rsp+rsi*2;
rsi+=0x4;
if(rsi==0x18)
goto line149:rbx=(rsp+0x20);
else
goto line125:ecx=rsp+rsi*1;
}
else{
goto
}
}
else{
edx=0x6032d0;
rdx=0x20+rsp+rsi*2;
rsi+=0x4;
if(rsi!=0x18){
goto line125:ecx=rsp+rsi*1;
}
else{
rbx=(rsp+0x20);
rax=(rsp+0x28);
rsi=(rsp+0x50);
rcx=rbx;
rdx=(rax); rax里面存的是地址而不是普通数据了;
(rcx+0x8)=rdx;
rax+=0x8;
if(rax==rsi){
rcx=rdx;
goto line141:rdx=(rax);
}
else{
(rdx+8)=0;
ebp=0x5;
rax=(rbx+0x8);
eax=(rax);
if((rbx)>=eax){
rbx=(rbx+0x8);
ebp-=0x1;
if(ebp!=0)
goto line151:rax=(rbx+0x8);
}
else
bomb!
}
}
}
}
else{
ecx=edx;
edx-=(rax);
(rax)=edx;
rax+=0x4;
goto line123;
}
}
}
else
boom!
}
07 Secret_Phase
首先要发现secret_phase不像前面6个phase,它属于一个被其他调用的函数,类似于phase1~6调用的func函数。这里还有个小坑,进入gdb模式后直接“print (char*)0x603870”,会发现输出“<input_strings+240> ""”,这说明要把解决前面几个phase的答案输入后再打断点才能输出这里应有的内容。其实这里有个取巧的地方,“print (char*)0x402619”得到输出“%d %d %s”。说明输入两个整形和一个字符串才可以进入这个phase,而前面6个phase只有phase_4的答案是输入两个整形。再结合“print (char*)0x0x402622”得到“DrEvil”,可以猜测进入这个阶段完整的答案应该是“7 0 DrEvil”。
进如secret_phase后,发现 “print (char*)0x6030f0”得到“<n1>"$"”,猜测这个地址存储的又是链表的形式,而且好像是一棵二叉树,画出对应的二叉树。
从退出func7后eax的值必须为2可以知道func7只能返回2。在func7里面“mov 0x10(%rdi),%rdi”比较直观,是进入右节点。但是“mov 0x8(%rdi),%rdi”就让人有些费解了,这时候直接“print *0x6030f8”查看这个地址的内容是“6304016即0x603110”,不要自己瞎想,发现这句话的意思是进入左节点。递归过程不再赘述,还是转化成if-else等语句再自行判断。
总的来说有了phase_6的经验,这题难度比phase_6略低,不过相较于其他phase还是难不少的。
可使用命令 touch answers.txt 新建名为answers的.txt文件,将所有拆弹密码写入该文件,然后用命令 ./bomb answers.txt 运行bomb可执行文件 (之前的每一关调试结束后,也可以将新的拆弹密码写入.txt文件,用这个方法验证是否爆炸,就不用每次重复输入之前关卡的拆弹密码了)。
0000000000401204 <fun7>:
401204: 48 83 ec 08 sub $0x8,%rsp
401208: 48 85 ff test %rdi,%rdi ;if(rdi==0)
40120b: 74 2b je 401238 <fun7+0x34> ;goto 401238: eax=-1,准备return
40120d: 8b 17 mov (%rdi),%edx ;else edx=(rdi)
40120f: 39 f2 cmp %esi,%edx ;if(edx<=esi)
401211: 7e 0d jle 401220 <fun7+0x1c> ;goto 401220
401213: 48 8b 7f 08 mov 0x8(%rdi),%rdi ;else rdi=(rdi+8)
401217: e8 e8 ff ff ff callq 401204 <fun7> ;开始递归
40121c: 01 c0 add %eax,%eax ;eax=2*eax
40121e: eb 1d jmp 40123d <fun7+0x39> ;return
401220: b8 00 00 00 00 mov $0x0,%eax ;eax=0
401225: 39 f2 cmp %esi,%edx ;if(edx==esi)
401227: 74 14 je 40123d <fun7+0x39> ;goto 40123d:return
401229: 48 8b 7f 10 mov 0x10(%rdi),%rdi ;else rdi=(rdi+16) ;又是节点的结构是吗
40122d: e8 d2 ff ff ff callq 401204 <fun7> ;开始递归
401232: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax ;eax=rax*2+1
401236: eb 05 jmp 40123d <fun7+0x39> ;return
401238: b8 ff ff ff ff mov $0xffffffff,%eax ;eax=-1
40123d: 48 83 c4 08 add $0x8,%rsp
401241: c3 retq
0000000000401242 <secret_phase>:
401242: 53 push %rbx
401243: e8 56 02 00 00 callq 40149e <read_line>
401248: ba 0a 00 00 00 mov $0xa,%edx ;edx=10
40124d: be 00 00 00 00 mov $0x0,%esi ;esi=0
401252: 48 89 c7 mov %rax,%rdi ;rdi=rax
401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt>
40125a: 48 89 c3 mov %rax,%rbx ;rbx=rax
40125d: 8d 40 ff lea -0x1(%rax),%eax ;eax=rax-1
401260: 3d e8 03 00 00 cmp $0x3e8,%eax ;if(eax<=1000)
401265: 76 05 jbe 40126c <secret_phase+0x2a> ;goto 40126c
401267: e8 ce 01 00 00 callq 40143a <explode_bomb> ;else boom!
40126c: 89 de mov %ebx,%esi ;esi=ebx
40126e: bf f0 30 60 00 mov $0x6030f0,%edi ;(edi)=<n1>"$",edi=$0x6030f0 反应出这是个连续存储的链表,用x/140 0x6030f0查看所以节点
401273: e8 8c ff ff ff callq 401204 <fun7> ;edi=$0x6030f0
401278: 83 f8 02 cmp $0x2,%eax ;if(eax==2) 所以func返回的eax=2
40127b: 74 05 je 401282 <secret_phase+0x40> ;goto 40127d
40127d: e8 b8 01 00 00 callq 40143a <explode_bomb> ;else boom!
401282: bf 38 24 40 00 mov $0x402438,%edi ;(edi)="Wow! You've defused the secret stage!"
401287: e8 84 f8 ff ff callq 400b10 <puts@plt>
40128c: e8 33 03 00 00 callq 4015c4 <phase_defused>
401291: 5b pop %rbx
401292: c3 retq
401293: 90 nop
401294: 90 nop
401295: 90 nop
401296: 90 nop
401297: 90 nop
401298: 90 nop
401299: 90 nop
40129a: 90 nop
40129b: 90 nop
40129c: 90 nop
40129d: 90 nop
40129e: 90 nop
40129f: 90 nop
00000000004015c4 <phase_defused>:
4015c4: 48 83 ec 78 sub $0x78,%rsp ;rdp-=120
4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4015cf: 00 00
4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp) ;rsp[104]=rax
4015d6: 31 c0 xor %eax,%eax ;eax=0
4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings>
4015df: 75 5e jne 40163f <phase_defused+0x7b> ;如果输入数据的个数不等于6 return
4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 ;r8=rsp[16]
4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ;rcx=rsp[12]
4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ;rdx=rsp[8]
4015f0: be 19 26 40 00 mov $0x402619,%esi ;"%d %d %s",输入的格式为“整形 整形 字符串”
4015f5: bf 70 38 60 00 mov $0x603870,%edi ;input_strings+240> ""
4015fa: e8 f1 f5 ff ff callq 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax ;if(eax!=3) 如果输入数据个数不为3
401602: 75 31 jne 401635 <phase_defused+0x71> ;goto 401635 结束
401604: be 22 26 40 00 mov $0x402622,%esi ;else esi="DrEvil"
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi ;rdi=rsp[16]
40160e: e8 25 fd ff ff callq 401338 <strings_not_equal>
401613: 85 c0 test %eax,%eax ;if(eax!=0)
401615: 75 1e jne 401635 <phase_defused+0x71> ;goto 401635 结束
401617: bf f8 24 40 00 mov $0x4024f8,%edi ;else (edi)="Curses, you've found the secret phase!"
40161c: e8 ef f4 ff ff callq 400b10 <puts@plt>
401621: bf 20 25 40 00 mov $0x402520,%edi ;(edi)="But finding it and solving it are quite different..."
401626: e8 e5 f4 ff ff callq 400b10 <puts@plt>
40162b: b8 00 00 00 00 mov $0x0,%eax ;eax=0
401630: e8 0d fc ff ff callq 401242 <secret_phase> ;goto secret_phase
401635: bf 58 25 40 00 mov $0x402558,%edi ;"Congratulations! You've defused the bomb!"
40163a: e8 d1 f4 ff ff callq 400b10 <puts@plt>
40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax ;准备return了
401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
40164b: 00 00
40164d: 74 05 je 401654 <phase_defused+0x90>
40164f: e8 dc f4 ff ff callq 400b30 <__stack_chk_fail@plt>
401654: 48 83 c4 78 add $0x78,%rsp
401658: c3 retq
401659: 90 nop
40165a: 90 nop
40165b: 90 nop
40165c: 90 nop
40165d: 90 nop
40165e: 90 nop
40165f: 90 nop