看见的看不见的
瞬间的永恒的
青草长啊大雪飘扬
——月亮之上
完整代码见: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,我们输入的内容作为参数1400ee9: 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)=1400f10: 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*=2400f1c: 39 03 cmp %eax,(%rbx) ;if((rbx)==eax)400f1e: 74 05 je 400f25 <phase_2+0x29> ;goto 400f25400f20: e8 15 05 00 00 callq 40143a <explode_bomb> ;else bomb!400f25: 48 83 c3 04 add $0x4,%rbx ;rbx=rbx+0x4400f29: 48 39 eb cmp %rbp,%rbx ;if(rbx!=rbp)400f2c: 75 e9 jne 400f17 <phase_2+0x1b> ;goto 400f17:mov -0x4(%rbx),%eax400f2e: eb 0c jmp 400f3c <phase_2+0x40> ;goto 400f3c return400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx ;rbx=rsp+0x4400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp ;rbp=rsp+0x18400f3a: eb db jmp 400f17 <phase_2+0x1b> ;goto 400f17400f3c: 48 83 c4 28 add $0x28,%rsp400f40: 5b pop %rbx400f41: 5d pop %rbp400f42: 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;elsereturn ;}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=0400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> ;读入输入400f60: 83 f8 01 cmp $0x1,%eax ;if(eax>1)吧 所以eax必为1400f63: 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=0x400f7c400f7c: b8 cf 00 00 00 mov $0xcf,%eax ;eax=0xcf400f81: eb 3b jmp 400fbe <phase_3+0x7b> ;跳转到400fbe400f83: b8 c3 02 00 00 mov $0x2c3,%eax ;eax=0x2c3400f88: eb 34 jmp 400fbe <phase_3+0x7b> 400f8a: b8 00 01 00 00 mov $0x100,%eax400f8f: eb 2d jmp 400fbe <phase_3+0x7b>400f91: b8 85 01 00 00 mov $0x185,%eax400f96: eb 26 jmp 400fbe <phase_3+0x7b>400f98: b8 ce 00 00 00 mov $0xce,%eax400f9d: eb 1f jmp 400fbe <phase_3+0x7b>400f9f: b8 aa 02 00 00 mov $0x2aa,%eax400fa4: eb 18 jmp 400fbe <phase_3+0x7b>400fa6: b8 47 01 00 00 mov $0x147,%eax400fab: 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,%eax400fb7: eb 05 jmp 400fbe <phase_3+0x7b>400fb9: b8 37 01 00 00 mov $0x137,%eax400fbe: 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 ;栈指针自减8400fd2: 89 d0 mov %edx,%eax ;eax=edx=14400fd4: 29 f0 sub %esi,%eax ;eax=eax-esi esi=0400fd6: 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+eax400fdd: d1 f8 sar %eax ;把eax算数右移一位400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx ;ecx=rax+rsi400fe2: 39 f9 cmp %edi,%ecx ;if(ecx<=edi)400fe4: 7e 0c jle 400ff2 <func4+0x24> ;goto 400ff2400fe6: 8d 51 ff lea -0x1(%rcx),%edx ;else edx=rcx-1400fe9: e8 e0 ff ff ff callq 400fce <func4> ;goto 400fce 开始递归了 为了跳出递归,就有ecx=edi400fee: 01 c0 add %eax,%eax ;eax=2*eax400ff0: eb 15 jmp 401007 <func4+0x39> ;goto 401007 返回400ff2: b8 00 00 00 00 mov $0x0,%eax ;eax=0400ff7: 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+1400ffe: e8 cb ff ff ff callq 400fce <func4> ;goto 400fce 又开始递归了他奶奶滴401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax ;eax=2*rax+1401007: 48 83 c4 08 add $0x8,%rsp ;恢复栈指针40100b: c3 retq ;返回0才行
000000000040100c <phase_4>:40100c: 48 83 ec 18 sub $0x18,%rsp ;栈指针自减24401010: 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=0401024: 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必然等于2401033: 76 05 jbe 40103a <phase_4+0x2e> ;goto 40403a 所以rdx<=0xe401035: e8 00 04 00 00 callq 40143a <explode_bomb> ;else bomb!40103a: ba 0e 00 00 00 mov $0xe,%edx ;edx=0xe40103f: be 00 00 00 00 mov $0x0,%esi ;esi=0401044: 8b 7c 24 08 mov 0x8(%rsp),%edi ;edi=edx=0xe rdx不是刚刚被赋值了吗? mov指令和lea指令的区别401048: e8 81 ff ff ff callq 400fce <func4> ;goto func440104d: 85 c0 test %eax,%eax ;if(eax!=0) 所以eax=040104f: 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=num2401056: 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,%rsp401061: 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 把输入的参数地址传递到rbx40106a: 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)=rax401078: 31 c0 xor %eax,%eax ;将eax清040107a: e8 9c 02 00 00 callq 40131b <string_length>40107f: 83 f8 06 cmp $0x6,%eax ;if(eax==6) 所以字符串长度为6=eax401082: 74 4e je 4010d2 <phase_5+0x70> ;goto 4010d2401084: 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 40108b4010ae: 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> ;bomb4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) ;什么意思?4010d0: eb 07 jmp 4010d9 <phase_5+0x77> ;goto 4010d94010d2: b8 00 00 00 00 mov $0x0,%eax ;eax=04010d7: eb b2 jmp 40108b <phase_5+0x29> ;goto 40108b4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax4010e5: 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,%rsp4010f2: 5b pop %rbx4010f3: 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;} elseboom!}elsegoto 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 把栈的起始地址传递给r13401103: 48 89 e6 mov %rsp,%rsi ;rsi=rsp 把栈的起始地址传递给rsi401106: e8 51 03 00 00 callq 40145c <read_six_numbers> ;读入六个数,两个函数通过rsi进行参数传递40110b: 49 89 e6 mov %rsp,%r14 ;r14=rsp40110e: 41 bc 00 00 00 00 mov $0x0,%r12d ;r12d=0401114: 4c 89 ed mov %r13,%rbp ;rbp=r13 把栈的起始地址传递给rbp401117: 41 8b 45 00 mov 0x0(%r13),%eax ;eax=(r13) 获取首地址的数据40111b: 83 e8 01 sub $0x1,%eax ;eax-=0x140111e: 83 f8 05 cmp $0x5,%eax ;if(eax<=0x5)401121: 76 05 jbe 401128 <phase_6+0x34> ;goto 401128:add $0x1,%r12d401123: e8 12 03 00 00 callq 40143a <explode_bomb> ;else boom!401128: 41 83 c4 01 add $0x1,%r12d ;r12d+=0x140112c: 41 83 fc 06 cmp $0x6,%r12d ;if(r12d==0x6)401130: 74 21 je 401153 <phase_6+0x5f> ;goto 401153:lea 0x18(%rsp),%rsi401132: 44 89 e3 mov %r12d,%ebx ;else ebx=r12d401135: 48 63 c3 movslq %ebx,%rax ;rax=ebx401138: 8b 04 84 mov (%rsp,%rax,4),%eax ;eax=rsp+rax*440113b: 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+=0x1401148: 83 fb 05 cmp $0x5,%ebx ;if(ebx<=0x5)40114b: 7e e8 jle 401135 <phase_6+0x41> ;goto 401135:movslq %ebx,%rax40114d: 49 83 c5 04 add $0x4,%r13 ;else r13+=0x4401151: eb c1 jmp 401114 <phase_6+0x20> ;goto 401114: mov %r13,%rbp401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi ;rsi=(rsp+0x18)401158: 4c 89 f0 mov %r14,%rax ;rax=r1440115b: b9 07 00 00 00 mov $0x7,%ecx ;ecx=0x7401160: 89 ca mov %ecx,%edx ;edx=ecx401162: 2b 10 sub (%rax),%edx ;edx-=(rax)401164: 89 10 mov %edx,(%rax) ;(rax)=edx401166: 48 83 c0 04 add $0x4,%rax ;rax+=0x440116a: 48 39 f0 cmp %rsi,%rax ;if(rax!=rsi)40116d: 75 f1 jne 401160 <phase_6+0x6c> ;goto 401160:mov %ecx,%edx40116f: be 00 00 00 00 mov $0x0,%esi ;else esi=0401174: eb 21 jmp 401197 <phase_6+0xa3> ;goto 401197:ecx=rsp+rsi*1401176: 48 8b 52 08 mov 0x8(%rdx),%rdx ;rdx=(rdx+0x8)40117a: 83 c0 01 add $0x1,%eax ;eax+=0x140117d: 39 c8 cmp %ecx,%eax ;if(eax!=ecx)40117f: 75 f5 jne 401176 <phase_6+0x82> ;goto 401176401181: eb 05 jmp 401188 <phase_6+0x94> ;else goto 101188401183: 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*240118d: 48 83 c6 04 add $0x4,%rsi ;rsi+=0x4401191: 48 83 fe 18 cmp $0x18,%rsi ;if(rsi==0x18)401195: 74 14 je 4011ab <phase_6+0xb7> ;goto 4011ab:0x20(%rsp),%rbx401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ;这是数组的形式吧 ecx=rsp+rsi*140119a: 83 f9 01 cmp $0x1,%ecx ;if(ecx<=0x1)40119d: 7e e4 jle 401183 <phase_6+0x8f> ;goto 401183:mov $0x6032d0,%edx40119f: b8 01 00 00 00 mov $0x1,%eax ;eax=0x14011a4: 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),%rdx4011ab: 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=rbx4011bd: 48 8b 10 mov (%rax),%rdx ;rdx=(rax) rax里面存的是地址而不是普通数据了4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) ;(rcx+0x8)=rdx4011c4: 48 83 c0 08 add $0x8,%rax ;rax+=0x84011c8: 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=rdx4011d0: eb eb jmp 4011bd <phase_6+0xc9> ;goto 4011bd: mov (%rax),%rdx4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) ;(rdx+8)=04011d9: 00 4011da: bd 05 00 00 00 mov $0x5,%ebp ;ebp=0x54011df: 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)>=eax4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> ;else boom4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx ;rbx=(rbx+0x8)4011f2: 83 ed 01 sub $0x1,%ebp ;ebp-=0x14011f5: 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 %rbx4011fc: 5d pop %rbp4011fd: 41 5c pop %r124011ff: 41 5d pop %r13401201: 41 5e pop %r14401203: 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+0x4401467: 48 8d 46 14 lea 0x14(%rsi),%rax ;rax=rsi+0x1440146b: 48 89 44 24 08 mov %rax,0x8(%rsp) ;(rsp+0x8)=rax401470: 48 8d 46 10 lea 0x10(%rsi),%rax ;rax=rsi+0x10401474: 48 89 04 24 mov %rax,(%rsp) ;(rsp)=rax401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 ;(rsi+0xc)=%r940147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 ;(rsi+0x8)=%r8401480: be c3 25 40 00 mov $0x4025c3,%esi ;esi=0x4025c3401485: b8 00 00 00 00 mov $0x0,%eax ;eax=040148a: 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=6401494: e8 a1 ff ff ff callq 40143a <explode_bomb> ;else bomb!401499: 48 83 c4 18 add $0x18,%rsp40149d: 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);elsegoto 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,%rsp401208: 48 85 ff test %rdi,%rdi ;if(rdi==0)40120b: 74 2b je 401238 <fun7+0x34> ;goto 401238: eax=-1,准备return40120d: 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 401220401213: 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*eax40121e: eb 1d jmp 40123d <fun7+0x39> ;return 401220: b8 00 00 00 00 mov $0x0,%eax ;eax=0401225: 39 f2 cmp %esi,%edx ;if(edx==esi)401227: 74 14 je 40123d <fun7+0x39> ;goto 40123d:return401229: 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+1401236: eb 05 jmp 40123d <fun7+0x39> ;return 401238: b8 ff ff ff ff mov $0xffffffff,%eax ;eax=-140123d: 48 83 c4 08 add $0x8,%rsp 401241: c3 retq
0000000000401242 <secret_phase>:401242: 53 push %rbx401243: e8 56 02 00 00 callq 40149e <read_line>401248: ba 0a 00 00 00 mov $0xa,%edx ;edx=1040124d: be 00 00 00 00 mov $0x0,%esi ;esi=0401252: 48 89 c7 mov %rax,%rdi ;rdi=rax401255: e8 76 f9 ff ff callq 400bd0 <strtol@plt>40125a: 48 89 c3 mov %rax,%rbx ;rbx=rax40125d: 8d 40 ff lea -0x1(%rax),%eax ;eax=rax-1401260: 3d e8 03 00 00 cmp $0x3e8,%eax ;if(eax<=1000)401265: 76 05 jbe 40126c <secret_phase+0x2a> ;goto 40126c401267: e8 ce 01 00 00 callq 40143a <explode_bomb> ;else boom!40126c: 89 de mov %ebx,%esi ;esi=ebx40126e: 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=$0x6030f0401278: 83 f8 02 cmp $0x2,%eax ;if(eax==2) 所以func返回的eax=240127b: 74 05 je 401282 <secret_phase+0x40> ;goto 40127d40127d: 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 %rbx401292: c3 retq 401293: 90 nop401294: 90 nop401295: 90 nop401296: 90 nop401297: 90 nop401298: 90 nop401299: 90 nop40129a: 90 nop40129b: 90 nop40129c: 90 nop40129d: 90 nop40129e: 90 nop40129f: 90 nop
00000000004015c4 <phase_defused>:4015c4: 48 83 ec 78 sub $0x78,%rsp ;rdp-=1204015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax4015cf: 00 00 4015d1: 48 89 44 24 68 mov %rax,0x68(%rsp) ;rsp[104]=rax4015d6: 31 c0 xor %eax,%eax ;eax=04015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings>4015df: 75 5e jne 40163f <phase_defused+0x7b> ;如果输入数据的个数不等于6 return4015e1: 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) 如果输入数据个数不为3401602: 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=0401630: e8 0d fc ff ff callq 401242 <secret_phase> ;goto secret_phase401635: 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,%rax40164b: 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,%rsp401658: c3 retq 401659: 90 nop40165a: 90 nop40165b: 90 nop40165c: 90 nop40165d: 90 nop40165e: 90 nop40165f: 90 nop