CS:APP二进制炸弹phase4

写在前面

OK,已经完成一半任务了,继续吧。let's go!!!


分析

首先找到调用函数phase_4的代码:

CS:APP二进制炸弹phase4_第1张图片

反汇编函数phase_4如下:

CS:APP二进制炸弹phase4_第2张图片

跟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反汇编如下:

CS:APP二进制炸弹phase4_第3张图片

第一眼瞅过去,函数内部尽然有调用自己,原来这是个递归函数。通过刚才的分析,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。

知道了这几个生疏的汇编指令,应该没什么问题的。好,继续下一个阶段吧。

你可能感兴趣的:(汇编语言,工具学习)