写在前面
OK,已经完成一半任务了,继续吧。let's go!!!
分析
首先找到调用函数phase_4的代码:
反汇编函数phase_4如下:
跟phase_3同样的套路,先从我们的输入中获取两个int型整数。我们命令为num1和num2。
接着判断sscanf函数的返回值,如果不等于2则触发炸弹,否则继续。
接下来的几条指令看出,num1必须小于等于14,否则触发炸弹,难道本阶段又跟phase_3一样,存在多组答案? 不得而知,继续。
接下来调用函数func4,参数分别通过寄存器%edi,%esi和%edx传递,值分别为num1,0和14。
test %eax,%eax用于检测函数func4的返回值是否为0,如果不为0,跳转到0x0000000000401058处,触发炸弹,否则继续。
cmpl $0x0,0xc(%rsp)用于检测num2是否为0,为0的话,跳转到0x000000000040105d处,函数结束,一切OK,否则触发炸弹。所以可以断定输入的num2必须为0。
现在来看函数func4反汇编如下:
第一眼瞅过去,函数内部尽然有调用自己,原来这是个递归函数。通过刚才的分析,func4接受3个参数,并且都是int型的,所以func4的原型应该是int func4(int a, int b, int c);
mov %edx,%eax,将参数c存储在寄存器%eax中。
sub %esi,%eax,用%eax中的值减去%esi,结果存在寄存器%eaxz中,即c - b的值存储在寄存器%eax中。
为了直观,我们将%eax定义为一个局部变量int x,即int x = c - b;
mov %eax,%ecx,shr $0x1f,%ecx,add %ecx,%eax,sar %eax 这几句整合起来就是x = (x>>31 + x) >> 1。
lea (%rax,%rsi,1),%ecx,这句%rsi,意思是%ecx = %rax + %rsi * 1,其中%rax就是我们刚刚求得的x,%rsi是参数b。因此%ecx = (x>>31 + x) >> 1 + b; 我们将寄存器%ecx定义一个局部变量,名为tmp,即int tmp = (x>>31 + x) >> 1 + b;将x带进去就是int tmp = ((c - b) >> 31 + (c -b)) >> 1 + b;
好,得到了tmp的值,就好办了,因为我们发现后面的汇编一直在使用tmp的值与参数a在比较。继续分析。
cmp %edi,%ecx,就是用参数a和tmp值比较,即tmp - a,如果tmp小于等于a,则跳转到0x0000000000400ff2处执行。我根据接下来的汇编,将func4用C实现,大概如下:
// %edi %esi %edx
static int func4(int a, int b, int c)
{
int tmp = (((c - b) + ((c - b) >> 31)) >> 1) + b;
if (tmp <= a) {
if (tmp <= a) {
return (0);
} else {
return func4(a, tmp + 1, c) * 2 + 1;
}
} else {
return func4(a, b, tmp - 1) * 2;
}
}
发现没有,这里的逻辑很奇怪! 在tmp小于等于a后,紧接着判断tmp是否大于等a,很显然这里应该是判断tmp是否等于a的情况,所以代码修改如下:
// %edi %esi %edx
static int func4(int a, int b, int c)
{
int tmp = (((c - b) + ((c - b) >> 31)) >> 1) + b;
if (tmp <= a) {
if (tmp == a) {
return (0);
} else {
return func4(a, tmp + 1, c) * 2 + 1;
}
} else {
return func4(a, b, tmp - 1) * 2;
}
}
从前面的分析得到,只要func4最终的返回值是0就成功了。那我们先着眼于代码中的return 0语句吧。假设第一次调用func4,参数分别为num1,0和14,计算tmp得到7,那么如果num1等于7,就会走到return 0那里,函数返回0,bingo!因此num1是7,num2是0时就能过这一个phase。试试,果然可以。
但是本phase应该还有其他答案,不然不会大费周折的搞什么递归了。我们可以写个测试程序,输出最后的所有答案,程序如下:
#include
#include
// %edi %esi %edx
static int func4(int a, int b, int c)
{
int tmp = (((c - b) + ((c - b) >> 31)) >> 1) + b;
if (tmp <= a) {
if (tmp == a) {
return (0);
} else {
return func4(a, tmp + 1, c) * 2 + 1;
}
} else {
return func4(a, b, tmp - 1) * 2;
}
}
int main(int argc, const char *argv[])
{
int i, result;
for (i = 0; i < 14; ++i) {
result = func4(i, 0, 14);
if (result == 0) {
printf("%d\n", i);
}
}
return 0;
}
程序输出0 1 3 7,因此本阶段的答案有4组,分别为(0,0)、(1,0)、(3,0)、(7,0)。
本阶段的C程序源码完整如下:
// %edi %esi %edx
static int func4(int a, int b, int c)
{
int tmp = (((c - b) + ((c - b) >> 31)) >> 1) + b;
if (tmp <= a) {
if (tmp == a) {
return (0);
} else {
return func4(a, tmp + 1, c) * 2 + 1;
}
} else {
return func4(a, b, tmp - 1) * 2;
}
}
void phase_4(const char *input)
{
// 0x8(%rsp) 0xc(%rsp)
int num1, num2;
// %rdi %rsi %rdx %rcx
int result = sscanf(input, "%d %d", &num1, &num2);
if (result != 2) {
explode_bomb();
}
if (num1 > 0xe) {
explode_bomb();
}
// %edi %esi %edx
result = func4(num1, 0, 0xe);
if (result != 0) {
explode_bomb();
}
if (num2 != 0) {
explode_bomb();
}
}
总结:
本阶段的func4汇编代码有点晦涩难懂,其中shr和sar分别为逻辑右移和算术右移,并且sar %eax的意思是%eax中的值右移一位,即除以2。
知道了这几个生疏的汇编指令,应该没什么问题的。好,继续下一个阶段吧。