介绍:这是一个“拆弹”游戏,玩家要在反汇编中找到每一关的拆弹密码,一共有6个phases。 该Lab的下载地址:http://csapp.cs.cmu.edu/3e/labs.html。
警告:本文含有剧透内容,想要玩Lab2的慎看!
首先先看一下phase_4的汇编
000000000040100c <phase_4>:
40100c: sub $0x18,%rsp
401010: lea 0xc(%rsp),%rcx
401015: lea 0x8(%rsp),%rdx
40101a: mov $0x4025cf,%esi
40101f: mov $0x0,%eax
401024: callq 400bf0 <__isoc99_sscanf@plt>=
401029: cmp $0x2,%eax
40102c: jne 401035 <phase_4+0x29>
40102e: cmpl $0xe,0x8(%rsp)
401033: jbe 40103a <phase_4+0x2e>
401035: callq 40143a <explode_bomb>
40103a: mov $0xe,%edx
40103f: mov $0x0,%esi
401044: mov 0x8(%rsp),%edi
401048: callq 400fce <func4>
40104d: test %eax,%eax
40104f: jne 401058 <phase_4+0x4c>
401051: cmpl $0x0,0xc(%rsp)
401056: je 40105d <phase_4+0x51>
401058: callq 40143a <explode_bomb>
40105d: add $0x18,%rsp
401061: retq
通过汇编分析可知,第7行处调用了sscanf(rdi, *0x4025cf, rsp+0x8, rsp+0xc)
,其中*0x4025cf即"%d %d",将两个数读入到了栈上(后用N1,N2代指这两个数)。
接着在第11行处可以分析出N1<=0xe。
16行又是一个函数调用(参数个数未知,估计是3个),func4(rdi,0,0xE)
.
通过后面的代码可以推测出func4的返回值为0且N2为0。所以现在的目标就是找到N1使func4(N1,0,0xE)==0。
然后来看看func4吧:
0000000000400fce <func4>: ;edi=N1, esi=0, edx=0xE
400fce: sub $0x8,%rsp
400fd2: mov %edx,%eax ;eax=arg3
400fd4: sub %esi,%eax ;eax-=arg2
400fd6: mov %eax,%ecx ;ecx=eax=arg3-arg2
400fd8: shr $0x1f,%ecx ;ecx>>=0x1f (logic)
400fdb: add %ecx,%eax ;eax+=ecx
400fdd: sar %eax ;eax/=2
400fdf: lea (%rax,%rsi,1),%ecx ;ecx=rax+rsi
400fe2: cmp %edi,%ecx
400fe4: jle 400ff2 <func4+0x24> ;ecx<=edi -> 400ff2
400fe6: lea -0x1(%rcx),%edx ;edx=rcx-1
400fe9: callq 400fce <func4>
400fee: add %eax,%eax
400ff0: jmp 401007 <func4+0x39>
400ff2: mov $0x0,%eax
400ff7: cmp %edi,%ecx
400ff9: jge 401007 <func4+0x39>
400ffb: lea 0x1(%rcx),%esi
400ffe: callq 400fce <func4>
401003: lea 0x1(%rax,%rax,1),%eax
401007: add $0x8,%rsp
40100b: retq
然后将其手动翻译成等价的伪C代码:
func4(edi=N1, esi=0, edx=0xE){
eax=edx;
eax-=esi;
ecx=eax=edx-esi;
ecx>>=0x1f (logic);
eax+=ecx;
eax/=2;
ecx=rax+rsi;
if(ecx<=edi) goto 400ff2;
edx=rcx-1;
func4(edi,esi,edx);
eax*=2;
goto 401007;
400ff2:
eax=0;
if(ecx>=edi) goto 401007;
esi=rcx+1;
func4(edi,esi,edx);
eax=rax+rax+1;
401007:
return eax;
}
func4(N1,0,0xE);
然后再稍微整(魔)理(改)下:
func4(edi=N1, esi=0, edx=0xE){
ret=(edx-esi+sign(edx-esi))/2; //sign(ecx)是ecx的符号位
ecx=ret+esi;
if(ecx>edi){
return func4(N1,esi,ecx-1)*2;
}
if(ecx<edi){
return func4(N1,ecx+1,edx)*2+1;
}
return 0;
}
func4(N1,0,0xE);
这里发现中间的代码是两个if,然后前面的移位其实是在取符号位。 可以看出这一段代码其实是类似于二分查找的东西,那么想让返回值为0的话,需要ecx>=edi。edi直接取0试了一下,竟然过了。 所以N1=0,N2=0。 phase4的答案为"0 0"。