简介 BombLab属于《深入理解计算机系统》第三章程序的机器级表示,主要通过练习加深汇编知识的相关记忆。上一篇中介绍了前三关的具体解答,本文继续解答后三关和隐藏关。继续闯关获取第四个字符串 我们在通过前面的步骤成功获取了前三个字符串,此时我们可以开始获取第四个字符串。让我们重新运行程序(gdbbomb),在phase4入口处打上断点,然后运行程序使其进入函数phase4,并获取phase4汇编代码:。。。Breakpoint1,0x000000000040100cinphase4()获取函数phase4的汇编代码(gdb)disasDumpofassemblercodeforfunctionphase4:0x40100c0:sub0x18,rsp0x4010104:lea0xc(rsp),rcx0x4010159:lea0x8(rsp),rdx0x40101a14:mov0x4025cf,esi0x40101f19:mov0x0,eax0x40102424:callq0x400bf0isoc99sscanfplt0x40102929:cmp0x2,eax0x40102c32:jne0x401035phase4410x40102e34:cmpl0xe,0x8(rsp)0x40103339:jbe0x40103aphase4460x40103541:callq0x40143aexplodebomb0x40103a46:mov0xe,edx0x40103f51:mov0x0,esi0x40104456:mov0x8(rsp),edi0x40104860:callq0x400fcefunc40x40104d65:testeax,eax0x40104f67:jne0x401058phase4760x40105169:cmpl0x0,0xc(rsp)0x40105674:je0x40105dphase4810x40105876:callq0x40143aexplodebomb0x40105d81:add0x18,rsp0x40106185:retqEndofassemblerdump。 0x40100c00x40102c32:和前面类似,主要是通过sscanf获取输入字符串的两个32位有符号整数,假设分别存储在a(0x8(rsp))和b(0xc(rsp))中。如果没有成功获取两个整数,那么就会引爆炸弹,无法继续运行。 0x40102e340x40103339:主要是判断0a14是否成立(注意跳转使用的是jbe指令,所以是把a当作无符号数时必须小于等于14,即a必须非负且小于等于14),成立则跳转至0x40103a46继续运行;不成立则会引爆炸弹,无法继续运行。 0x40103a460x40104860:主要是调用函数func4,该函数签名大致为intfunc4(inti,intj,intk)。我们调用语句大致为intcfunc4(a,0,14);。 0x40104d650x40104f67:判断c0是否成立,不成立则引爆炸弹,成立时继续运行。此时我们确定func4函数的调用结果必须为0,从而反推出第一个数(a)的值。 0x401051690x40105674:判断b0是否成立,不成立则引爆炸弹,成立时继续运行并可顺利结束。此时我们已经确定了第二个数(b)为0。 此时我们已经确认了第二个数的值,还需确认第一个数的值。我们需要给func4入口处打上断点,并运行至func4入口处,查看func4的汇编代码:在函数func4入口处打上断点(gdb)breakfunc4Breakpoint2at0x400fce继续运行至func4处打上断点(gdb)continueContinuing。Breakpoint2,0x0000000000400fceinfunc4()获取函数func4的汇编代码(gdb)disasDumpofassemblercodeforfunctionfunc4:0x400fce0:sub0x8,rsp0x400fd24:movedx,eax0x400fd46:subesi,eax0x400fd68:moveax,ecx0x400fd810:shr0x1f,ecx0x400fdb13:addecx,eax0x400fdd15:sareax0x400fdf17:lea(rax,rsi,1),ecx0x400fe220:cmpedi,ecx0x400fe422:jle0x400ff2func4360x400fe624:lea0x1(rcx),edx0x400fe927:callq0x400fcefunc40x400fee32:addeax,eax0x400ff034:jmp0x401007func4570x400ff236:mov0x0,eax0x400ff741:cmpedi,ecx0x400ff943:jge0x401007func4570x400ffb45:lea0x1(rcx),esi0x400ffe48:callq0x400fcefunc40x40100353:lea0x1(rax,rax,1),eax0x40100757:add0x8,rsp0x40100b61:retqEndofassemblerdump。 这一段代码比较繁琐,而且涉及到递归调用,直接根据每块指令看不太容易理解,所以直接可以先直译出相关的C代码:inffunc4(inti,intj,intk){0x400fd24:movedx,eax0x400fd46:subesi,eaxintlkj;0x400fd68:moveax,ecx0x400fd810:shr0x1f,ecx由于shr是逻辑右移,而C语言是算术右移动,所以最后还要再与上0x1这一句相当于取符号位intm(l31)0x1;0x400fdb13:addecx,eax0x400fdd15:sareaxl(lm)1;0x400fdf17:lea(rax,rsi,1),ecxmlj;0x400fe220:cmpedi,ecx0x400fe422:jle0x400ff2func436if(li){0x400ff741:cmpedi,ecx0x400ff943:jge0x401007func457if(li){0x400ff236:mov0x0,eaxreturn0;}else{0x400ffb45:lea0x1(rcx),esi0x400ffe48:callq0x400fcefunc40x40100353:lea0x1(rax,rax,1),eaxreturn2func4(i,j1,k)1}}else{0x400fe624:lea0x1(rcx),edx0x400fe927:callq0x400fcefunc40x400fee32:addeax,eaxreturn2func4(i,j,k1)}} 直译后的代码还是有点难懂(难怪main。c里要问数学好不好),但是我们知道这个函数需要返回0才行,找到对应的分支语句,可以反推出il,i就是我们需要确定的值,而l只与j,k有关,并且第一次调用func4时j0,k14,根据函数最开始的语句可以算出l7,所以i的值也为7,并且满足0i14,所以i7是一个合法的解。(已经获取了func4的函数,所以可以直接枚举i014,可得合法的解为:0,1,3,7) 至此我们已经推断出了字符串中两个数字的值分别为7和0,那么第四个字符串为:70。获取第五个字符串 我们在通过前面的步骤成功获取了前四个字符串,此时我们可以开始获取第五个字符串。让我们重新运行程序(gdbbomb),在phase5入口处打上断点,然后运行程序使其进入函数phase5,并获取phase5汇编代码:。。。Breakpoint1,0x0000000000401062inphase5()获取函数phase5的汇编代码(gdb)disasDumpofassemblercodeforfunctionphase5:0x4010620:pushrbx0x4010631:sub0x20,rsp0x4010675:movrdi,rbx0x40106a8:movfs:0x28,rax0x40107317:movrax,0x18(rsp)0x40107822:xoreax,eax0x40107a24:callq0x40131bstringlength0x40107f29:cmp0x6,eax0x40108232:je0x4010d2phase51120x40108434:callq0x40143aexplodebomb0x40108939:jmp0x4010d2phase51120x40108b41:movzbl(rbx,rax,1),ecx0x40108f45:movcl,(rsp)0x40109248:mov(rsp),rdx0x40109652:and0xf,edx0x40109955:movzbl0x4024b0(rdx),edx0x4010a062:movdl,0x10(rsp,rax,1)0x4010a466:add0x1,rax0x4010a870:cmp0x6,rax0x4010ac74:jne0x40108bphase5410x4010ae76:movb0x0,0x16(rsp)0x4010b381:mov0x40245e,esi0x4010b886:lea0x10(rsp),rdi0x4010bd91:callq0x401338stringsnotequal0x4010c296:testeax,eax0x4010c498:je0x4010d9phase51190x4010c6100:callq0x40143aexplodebomb0x4010cb105:nopl0x0(rax,rax,1)0x4010d0110:jmp0x4010d9phase51190x4010d2112:mov0x0,eax0x4010d7117:jmp0x40108bphase5410x4010d9119:mov0x18(rsp),rax0x4010de124:xorfs:0x28,rax0x4010e7133:je0x4010eephase51400x4010e9135:callq0x400b30stackchkfailplt0x4010ee140:add0x20,rsp0x4010f2144:poprbx0x4010f3145:retqEndofassemblerdump。 0x40106a80x40107317:看到movfs:0x28,rax就想起了这是栈破坏检测的相关代码(P199),主要是检测栈是否被破坏,也说明了我们刚刚定义了一个数组,并且这个数组在后面会作为参数传入一个函数中 0x401078220x40108232:主要判断我们输入的字符串的长度是否为6,不为6则引爆炸弹,否则继续运行后续代码 0x4010d21120x4010d7117:将寄存器eax赋值为0(书中P123提到movl指令以寄存器作为目的时,它会把该寄存器的高位4字节设置为0,因此没必要使用movq0x0,rax) 0x40108b410x4010ae76:这一段是循环,主要是遍历输入字符串的6个字符ch,并取该字符的低4位作为一个下标index,然后将相对地址0x4024b0偏移为index的一个字节赋值给我们定义的数组中的对应位置(假设输入字符串为s,栈中数组的定义语句为chard〔7〕;,地址0x4024b0对应的字符数组为chs,那么这一段循环的意思为d〔i〕chs〔s〔i〕0xf〕;)0x40108b41:使用了MOVZ类指令,这类指令会把目的中剩余的字节填充为0;与其对应的MOVS类指令会通过符号扩展填充剩余的字节(即将源操作数的最高位进行赋值填充到目的中剩余的字节)P123print(char)0x4024b016:查看地址0x4024b0开始的8个字符为maduiersnfotvbyl0x4010ae76:d〔6〕,即把我们栈中定义的字符数组最后设置为0,表示其代表的字符串长度为6 0x4010b3810x4010c498:判断字符串d与以地址0x40245e开始的字符串是否相等,不相等则引爆炸弹,相等则继续运行校验栈是否被破坏,然后通过第五个字符串的验证print(char)0x40245e7:查看地址0x40245e开始的7个字符为flyers(注意最后一个没有显示) 综上可知:我们需要生成的字符串d为flyers,可用于生成目标字符串的字母表为maduiersnfotvbyl,然后我们可以反推出我们可输入的字符的低4位,然后对照manascii指令输出的表找出对应哪些可输入的字符。 i d〔i〕 s〔i〕0xf 可输入字符 0hrf 0x9 )9IYiy 1hrl 0xf ?Oo 2hry 0xe 。Nn 3hre 0x5 5EUeu 4hrr 0x6 6FVfv 5hrs 0x7 7GWgw 第五个字符串每个位置任意选取对应的可输入字符即可通过第五个校验。获取第六个字符串 我们在通过前面的步骤成功获取了前五个字符串,此时我们可以开始获取第六个字符串。让我们重新运行程序(gdbbomb),在phase6入口处打上断点,然后运行程序使其进入函数phase6,并获取phase6汇编代码:。。。Breakpoint1,0x00000000004010f4inphase6()(gdb)disasDumpofassemblercodeforfunctionphase6:0x4010f40:pushr140x4010f62:pushr130x4010f84:pushr120x4010fa6:pushrbp0x4010fb7:pushrbx0x4010fc8:sub0x50,rsp0x40110012:movrsp,r130x40110315:movrsp,rsi0x40110618:callq0x40145creadsixnumbers0x40110b23:movrsp,r140x40110e26:mov0x0,r12d0x40111432:movr13,rbp0x40111735:mov0x0(r13),eax0x40111b39:sub0x1,eax0x40111e42:cmp0x5,eax0x40112145:jbe0x401128phase6520x40112347:callq0x40143aexplodebomb0x40112852:add0x1,r12d0x40112c56:cmp0x6,r12d0x40113060:je0x401153phase6950x40113262:movr12d,ebx0x40113565:movslqebx,rax0x40113868:mov(rsp,rax,4),eax0x40113b71:cmpeax,0x0(rbp)0x40113e74:jne0x401145phase6810x40114076:callq0x40143aexplodebomb0x40114581:add0x1,ebx0x40114884:cmp0x5,ebx0x40114b87:jle0x401135phase6650x40114d89:add0x4,r130x40115193:jmp0x401114phase6320x40115395:lea0x18(rsp),rsi0x401158100:movr14,rax0x40115b103:mov0x7,ecx0x401160108:movecx,edx0x401162110:sub(rax),edx0x401164112:movedx,(rax)0x401166114:add0x4,rax0x40116a118:cmprsi,rax0x40116d121:jne0x401160phase61080x40116f123:mov0x0,esi0x401174128:jmp0x401197phase61630x401176130:mov0x8(rdx),rdx0x40117a134:add0x1,eax0x40117d137:cmpecx,eax0x40117f139:jne0x401176phase61300x401181141:jmp0x401188phase61480x401183143:mov0x6032d0,edx0x401188148:movrdx,0x20(rsp,rsi,2)0x40118d153:add0x4,rsi0x401191157:cmp0x18,rsi0x401195161:je0x4011abphase61830x401197163:mov(rsp,rsi,1),ecx0x40119a166:cmp0x1,ecx0x40119d169:jle0x401183phase61430x40119f171:mov0x1,eax0x4011a4176:mov0x6032d0,edx0x4011a9181:jmp0x401176phase61300x4011ab183:mov0x20(rsp),rbx0x4011b0188:lea0x28(rsp),rax0x4011b5193:lea0x50(rsp),rsi0x4011ba198:movrbx,rcx0x4011bd201:mov(rax),rdx0x4011c0204:movrdx,0x8(rcx)0x4011c4208:add0x8,rax0x4011c8212:cmprsi,rax0x4011cb215:je0x4011d2phase62220x4011cd217:movrdx,rcx0x4011d0220:jmp0x4011bdphase62010x4011d2222:movq0x0,0x8(rdx)0x4011da230:mov0x5,ebp0x4011df235:mov0x8(rbx),rax0x4011e3239:mov(rax),eax0x4011e5241:cmpeax,(rbx)0x4011e7243:jge0x4011eephase62500x4011e9245:callq0x40143aexplodebomb0x4011ee250:mov0x8(rbx),rbx0x4011f2254:sub0x1,ebp0x4011f5257:jne0x4011dfphase62350x4011f7259:add0x50,rsp0x4011fb263:poprbx0x4011fc264:poprbp0x4011fd265:popr120x4011ff267:popr130x401201269:popr140x401203271:retqEndofassemblerdump。 第一眼看到这么长的汇编代码有点懵,但大概一看没有太复杂的逻辑,还有前面遇到过的函数,所以还是冷静下来继续分析,由于拆开很难看懂,所以还是需要直译成C代码再研究(部分判断转换成等价的语句,减少缩进)。为了细化每一块的功能,将把源代码拆成多部分进行分析。 0x401100120x40110618:调用函数readsixnumbers从输入的字符串读入6个数字,并按顺序存储在nums中 直译的C代码如下:voidphase6(chars){intap,bp,endp;intc,d,e;structNodenextpp,endpp;structNodenodep,curp;structNodearr〔6〕;intnums〔6〕;0x40110012:movrsp,r13apnums;0x40110315:movrsp,rsi0x40110618:callq0x40145creadsixnumbersreadsixnumbers(s,nums);。。。} 0x40110b230x40114d89:主要是判断nums中的数字是否全都满足0nums〔i〕6且互不相等 直译的C代码如下:。。。0x40110b23:movrsp,r14bpnums;0x40110e26:mov0x0,r12dc0;whilt(true){0x40111432:movr13,rbp0x40111735:mov0x0(r13),eax0x40111b39:sub0x1,eax0x40111e42:cmp0x5,eax0x40112145:jbe0x401128phase652if((unsigned)(ap1)5)){0x40112347:callq0x40143aexplodebombexplodebomb();}0x40112852:add0x1,r12dc1;0x40112c56:cmp0x6,r12d0x40113060:je0x401153phase695if(c6){break;}0x40113262:movr12d,ebxdc;判断每一个数字是否与后面的某个数字相等,存在一个相等时则引爆炸弹do{0x40113565:movslqebx,rax0x40113868:mov(rsp,rax,4),eax0x40113b71:cmpeax,0x0(rbp)0x40113e74:jne0x401145phase681if(nums〔d〕ap){0x40114076:callq0x40143aexplodebombexplodebomb();}0x40114581:add0x1,ebxd1;0x40114884:cmp0x5,ebx0x40114b87:jle0x401135phase665}while(d5);0x40114d89:add0x4,r13ap1;}。。。} 0x401153950x40116d121:重新计算每个数组中的数字:nums〔i〕7nums〔i〕 直译的C代码如下:。。。0x40115395:lea0x18(rsp),rsiendpnums6;0x401158100:movr14,rax由于bp还未使用过,所以相当于apnums;apbp;0x40115b103:mov0x7,ecxc7;do{0x401160108:movecx,edx0x401162110:sub(rax),edx0x401164112:movedx,(rax)apcap;0x401166114:add0x4,raxap1;0x40116a118:cmprsi,rax0x40116d121:jne0x401160phase6108}while(ap!endp);。。。 0x40116f1230x4011a9181:这一段有点绕,第一次看的时候还丢了一段代码,导致后面怎么都对不上。主要还是缺少类型信息,不知道某些地址对应的数据类型,导致推理走了歧路。 这一段代码中出现了一个地址0x6032d0,我们可以使用x14g0x6032d0查看里面存储的数据:(gdb)x14g0x6032d00x6032d0node1:0x000000010000014c0x00000000006032e00x6032e0node2:0x00000002000000a80x00000000006032f00x6032f0node3:0x000000030000039c0x00000000006033000x603300node4:0x00000004000002b30x00000000006033100x603310node5:0x00000005000001dd0x00000000006033200x603320node6:0x00000006000001bb0x00000000000000000x603330:0x00000000000000000x0000000000000000 这里面存储了6个Node类型的数据,可以发现后面的数字是一个地址,且为后面一个Node的地址,因此可以推导出Node类型及预先定义的一个Node链表:structNode{longval;structNodenext;};省略相关的初始化语句structNodenodes〔6〕; 有了以上信息,我们就能懂得这部分汇编代码的含义了:将链表的第nums〔c〕个Node赋值给arr〔c〕,即:arr〔c〕nodes〔nums〔c〕〕 直译的C代码如下:。。。0x40116f123:mov0x0,esic0;do{0x401174128:jmp0x401197phase61630x401197163:mov(rsp,rsi,1),ecxdnums〔c〕;0x40119a166:cmp0x1,ecx0x40119d169:jle0x401183phase6143第一次看到后面是1感觉有点奇怪,看到后面有add0x4,rsi和movrdx,0x20(rsp,rsi,2)才明白这里是在共用一个下标(这里转换成对应的C语句)这里就是找到第nums〔c〕个Nodeif(d1){0x401183143:mov0x6032d0,edxnodepnodes〔0〕;}else{0x40119f171:mov0x1,eaxe1;0x4011a4176:mov0x6032d0,edxnodepnodes;0x4011a9181:jmp0x401176phase6130do{0x401176130:mov0x8(rdx),rdxnodepnodepnext;0x40117a134:add0x1,eaxe1;0x40117d137:cmpecx,eax0x40117f139:jne0x401176phase6130}while(e!d);}0x401188148:movrdx,0x20(rsp,rsi,2)arr〔c〕nodep;0x401191157:cmp0x18,rsi0x401195161:je0x4011abphase6183}while(c!6);。。。 0x4011ab1830x4011d2222:主要是将原链表中的Node按照目前在arr的顺序重新连起来,即:arr〔i〕nextarr〔i1〕 直译的C代码如下:。。。0x4011ab183:mov0x20(rsp),rbxnodeparr〔0〕;0x4011b0188:lea0x28(rsp),raxnextpparr1;0x4011b5193:lea0x50(rsp),rsiendpparr6;0x4011ba198:movrbx,rcxcurpnextpp;while(true){0x4011bd201:mov(rax),rdx0x4011c0204:movrdx,0x8(rcx)curpnextnextpp;0x4011c4208:add0x8,raxnextpp1;0x4011c8212:cmprsi,rax0x4011cb215:je0x4011d2phase6222if(nextppendpp){break;}0x4011cd217:movrdx,rcxcurpnextpp;}0x4011d2222:movq0x0,0x8(rdx)nodegpnextNULL;。。。 0x4011da2300x4011f5257:这一段主要是判断新链表中的值val强转成32位有符号整型后是不是严格递减的。。。0x4011da230:mov0x5,ebpc5;do{0x4011df235:mov0x8(rbx),rax0x4011e3239:mov(rax),eax【注意】这里和下面都是使用寄存器eax,即使用了低32位等于将val的值转成了int类型intd(int)(nodepnextval)0x4011e5241:cmpeax,(rbx)0x4011e7243:jge0x4011eephase6250if(d(int)(nodepval)){0x4011e9245:callq0x40143aexplodebombexplodebomb();}0x4011ee250:mov0x8(rbx),rbxnodepnodepnext;0x4011f2254:sub0x1,ebpc1;0x4011f5257:jne0x4011dfphase6235}while(c!0);} 至此我们已经知道phase6中每一部分的逻辑,串起来就是:先从输入的字符串中读入6个在范围〔1,6〕内且互不相同数字,按顺序放入nums数组中;再对数组中的每个数字执行以下操作:nums〔i〕7nums〔i〕;然后将预先定义的一个链表nodes按照新顺序重新连起来,将原来的第nums〔i〕个节点放在第i个位置;最后判断新顺序下链表节点中的值强转成32位有符号整型后是否为严格递减,严格递减的通过本关,否则引爆炸弹。 原链表中的数字顺序为:0x14c0x0a80x39c0x2b30x1dd0x1bb 可以通过数字数组nums{3,4,5,6,1,2}将其转换为降序顺序,由于前面还执行了nums〔i〕7nums〔i〕,所以实际读取的数字数组为:nums{4,3,2,1,6,5},对应的输入字符串为:432165进入隐藏关 至此我们已经通过全部六关,但main。c最后还有一段注释提示我们可能忽略了什么: Wow,theygotit!Butisntsomething。。。missing?Perhapssomethingtheyoverlooked?Muahahahaha! 我们再回看以下前面六关中main函数内的代码,可以发现每一关都是先调用readline函数读取输入字符串,然后传入每关的校验函数phasex,再然后调用了同一个方法phasedefused,最后在提示通过了当前关。 回忆一下我们的校验函数,里面都直接对输入的字符串进行了校验,不需要后面再进行处理,也就是后面的phasedefused可能另有奥秘(最开始我还以为这个函数是进行后续校验的),所以我们先看看这个函数究竟做了什么事情。Breakpoint1,0x00000000004015c4inphasedefused()查看phasedefused的汇编代码(gdb)disasDumpofassemblercodeforfunctionphasedefused:0x4015c40:sub0x78,rsp0x4015c84:movfs:0x28,rax0x4015d113:movrax,0x68(rsp)0x4015d618:xoreax,eax0x4015d820:cmpl0x6,0x202181(rip)0x603760numinputstrings0x4015df27:jne0x40163fphasedefused1230x4015e129:lea0x10(rsp),r80x4015e634:lea0xc(rsp),rcx0x4015eb39:lea0x8(rsp),rdx0x4015f044:mov0x402619,esi0x4015f549:mov0x603870,edi0x4015fa54:callq0x400bf0isoc99sscanfplt0x4015ff59:cmp0x3,eax0x40160262:jne0x401635phasedefused1130x40160464:mov0x402622,esi0x40160969:lea0x10(rsp),rdi0x40160e74:callq0x401338stringsnotequal0x40161379:testeax,eax0x40161581:jne0x401635phasedefused1130x40161783:mov0x4024f8,edi0x40161c88:callq0x400b10putsplt0x40162193:mov0x402520,edi0x40162698:callq0x400b10putsplt0x40162b103:mov0x0,eax0x401630108:callq0x401242secretphase0x401635113:mov0x402558,edi0x40163a118:callq0x400b10putsplt0x40163f123:mov0x68(rsp),rax0x401644128:xorfs:0x28,rax0x40164d137:je0x401654phasedefused1440x40164f139:callq0x400b30stackchkfailplt0x401654144:add0x78,rsp0x401658148:retqEndofassemblerdump。 0x401630108:callq0x401242这一句看起来会调用一个隐藏关的函数,所以我们需要先破解当前函数使得我们可以进入隐藏关。 0x4015d8200x4015df27:判断0x202181(rip)中的值是否为6,不为6则直接返回当前函数,否则会执行函数体。通过注释0x603760我们可以判断出这个值记录的是输入的字符串的个数,只有当输入6个字符串后才有可能进入隐藏关,即我们必须通过前六关。 0x4015e1290x40160262:从0x603870开始的字符串读入3个参数,前两个是有符号整型,第三个是字符串,如果未成功读入3个参数,则直接返回xs0x402619:查看模式串为dds,从而可以得出三个变量的类型分别为:int,int,charxs0x603870:当我们打上断点,在通过第六关后进入phasedefused,运行这个命令可得字符串70,这个看起来是第四关的字符串,那么就需要我们将第四关的字符串替换为70DrEvil,这样我们就能成功进入隐藏关 0x401604640x40161581:判断刚刚读入的第三个字符串是否等于DrEvil,不等则直接返回xs0x402622:可知第三个字符串需要和DrEvil相等 0x401617830x40163a118:输出两端话提示我们进入隐藏关,然后通过隐藏关后再提示我们成功拆除炸弹破解隐藏关 main。c中开始可以使用两种方式读入字符串,第一种是使用标准输入,第二种是从文件中读入,我们可以将字符串都存入in。txt中,然后通过运行(gdb)runin。txt避免多次重复输入字符串直接查看0x401242所在函数的汇编代码(gdb)disas0x401242Dumpofassemblercodeforfunctionsecretphase:0x4012420:pushrbx0x4012431:callq0x40149ereadline0x4012486:mov0xa,edx0x40124d11:mov0x0,esi0x40125216:movrax,rdi0x40125519:callq0x400bd0strtolplt0x40125a24:movrax,rbx0x40125d27:lea0x1(rax),eax0x40126030:cmp0x3e8,eax0x40126535:jbe0x40126csecretphase420x40126737:callq0x40143aexplodebomb0x40126c42:movebx,esi0x40126e44:mov0x6030f0,edi0x40127349:callq0x401204fun70x40127854:cmp0x2,eax0x40127b57:je0x401282secretphase640x40127d59:callq0x40143aexplodebomb0x40128264:mov0x402438,edi0x40128769:callq0x400b10putsplt0x40128c74:callq0x4015c4phasedefused0x40129179:poprbx0x40129280:retqEndofassemblerdump。 0x40124310x40125519:读入一个字符串并将其按照10进制对待转换成一个长整型num 0x40125a240x40126737:判断(unsigned)(num1)0x3e8是否成立,不成立则引爆炸弹 0x40126c420x40128c74:调用函数fun7(0x6030f0,num),判断返回值是否为2,是则提示成功拆除炸弹,不是则引爆炸弹直接查看0x401204所在函数的汇编代码(gdb)disas0x401204Dumpofassemblercodeforfunctionfun7:0x4012040:sub0x8,rsp0x4012084:testrdi,rdi0x40120b7:je0x401238fun7520x40120d9:mov(rdi),edx0x40120f11:cmpesi,edx0x40121113:jle0x401220fun7280x40121315:mov0x8(rdi),rdi0x40121719:callq0x401204fun70x40121c24:addeax,eax0x40121e26:jmp0x40123dfun7570x40122028:mov0x0,eax0x40122533:cmpesi,edx0x40122735:je0x40123dfun7570x40122937:mov0x10(rdi),rdi0x40122d41:callq0x401204fun70x40123246:lea0x1(rax,rax,1),eax0x40123650:jmp0x40123dfun7570x40123852:mov0xffffffff,eax0x40123d57:add0x8,rsp0x40124161:retqEndofassemblerdump。 0x40120840x40120b7:判断第一个参数是否为0,是则直接返回0xffffffff,不是则继续运行 现在基本可以判断第一个参数是一个指针,刚刚的语句在判断指针是否为空,目前还需要判断这个指针指向的数据类型。(gdb)x64xg0x6030f00x6030f0n1:0x240x6031100x603100n116:0x6031300x00x603110n21:0x80x6031900x603120n2116:0x6031500x00x603130n22:0x320x6031700x603140n2216:0x6031b00x00x603150n32:0x160x6032700x603160n3216:0x6032300x00x603170n33:0x2d0x6031d00x603180n3316:0x6032900x00x603190n31:0x60x6031f00x6031a0n3116:0x6032500x00x6031b0n34:0x6b0x6032100x6031c0n3416:0x6032b00x00x6031d0n45:0x280x00x6031e0n4516:0x00x00x6031f0n41:0x10x00x603200n4116:0x00x00x603210n47:0x630x00x603220n4716:0x00x00x603230n44:0x230x00x603240n4416:0x00x00x603250n42:0x70x00x603260n4216:0x00x00x603270n43:0x140x00x603280n4316:0x00x00x603290n46:0x2f0x00x6032a0n4616:0x00x00x6032b0n48:0x3e90x00x6032c0n4816:0x00x00x6032d0node1:0x10000014c0x6032e00x6032e0node2:0x2000000a80x6032f0 看起来这个数据类型占32字节,第一个存储一个数,后面两个存储指针,分别指向相同的类型,所以可以猜测这是一个二叉树节点。通过0x40120d9:mov(rdi),edx可以猜测第一个字段是整型,而后面两个字段分别是左右子节点,剩余的字节未使用。因此这个节点的定义大致如下:typeTreeNode{intval;structTreeNodeleft,right;}; 现在我们可以直译出func7对应的C代码了:intfunc7(TreeNoderoot,intnum){0x4012084:testrdi,rdi0x40120b7:je0x401238fun752if(rootNULL){return0xffffffff;}0x40120f11:cmpesi,edx0x40121113:jle0x401220fun728if(rootvalnum){0x40122533:cmpesi,edx0x40122735:je0x40123dfun757if(rootvalnum){return0;}else{0x40122937:mov0x10(rdi),rdi0x40122d41:callq0x401204fun70x40123246:lea0x1(rax,rax,1),eaxreturn2fun7(rootright,num)1}}else{0x40121315:mov0x8(rdi),rdi0x40121719:callq0x401204fun70x40121c24:addeax,eaxreturn2fun7(rootleft);}} 至此我们知道了fun7的代码,并且需要fun7(0x6030f0,num)的返回值为2,为了方便计算,需要将树中的每个值画出来 这是一个四层的满二叉树,根据递归的计算方式和所需的最终返回值可以继续推导: 第一层:节点值为0x24,需要返回2,那么就需要走return2fun7(rootleft)这个逻辑,限定了num0x24 第二层:节点值为0x8,需要返回1,那么需要走return2fun7(rootright,num)1这个逻辑,限定了num0x8 第三层:节点值为0x16,需要返回0,有两个逻辑可走,限定了num0x16走return0这个逻辑:限定了num0x16走return2fun7(rootleft)这个逻辑,限定了num0x16 第四层:节点值为0x14,需要返回0,那么需要走return0这个逻辑,限定了num0x14 综上可知,隐藏关有两个解:0x14和0x16,对应的十进制数分别为:20和22小结 前五关都是平铺直述的各种简单语句,让我信心大增,内心OS:就这?第六关立刻教我做人,来来回回很久怎么翻译都不对(主要就是因为类型没推断出来,导致后续的推导不自洽),看来类型还是很重要的,如果没有推断出类型,那很容易误入歧途。 这些关卡强化了汇编的相关记忆(基本指令的具体含义、gdb的各种基本命令以及常用的寄存器的含义),中后期基本不需要再翻书查阅了。