Bomblab Writeup

0

用IDA分析这个bomb,先看main,发现先callinitialize_bomb这个函数,主要是用来获取hostname,检查是否在服务器端运行。所以先把这个函数调用nop掉。
接着分析,bomb一共6个关卡,和一个隐藏关。可以发现在每次过关后会调用phase_defused函数,里面首先调用了send_msg,这个是用来连接教师端,记录答题状态,所以也nop掉。
这样bomb就可以在本地linux运行调试了。

phase_1

int phase_1(int a1)
{
    int result;
    result = strings_not_equal(a1, "I can see Russia from my house!");
    if ( result )
        explode_bomb();
    return result;
}

就是一个字符串。

phase_2

int phase_2(int a1)
{
    int *v1;
    int v3;
    int v4;
    char v5;

    read_six_numbers(a1, &v3);
    if ( v3 || v4 != 1 )
        explode_bomb();
    v1 = &v3;
    do {
        if ( v1[2] != *v1 + v1[1] )
      	    explode_bomb();
        ++v1;
    } while ( (char *)v1 != &v5 );
    return 0;
}

这个是判断输入的六个数是否为斐波那契数列。

phase_3

int phase_3(int a1)
{
    signed int v1;
    int result;
    int v3;
    int v4;

    if ( __isoc99_sscanf(a1, "%d %d", &v3, &v4) <= 1 )
        explode_bomb();
    switch ( v3 )
    {
        case 1:
      	    v1 = 968;
      	    break;
    	case 2:
      	    v1 = 686;
      	    break;
    	case 3:
      	    v1 = 440;
      	    break;
    	case 4:
      	    v1 = 953;
      	    break;
    	case 5:
      	    v1 = 396;
      	    break;
    	case 6:
      	    v1 = 562;
      	    break;
    	case 7:
      	    v1 = 842;
      	    break;
    	case 0:
      	    v1 = 135;
      	    break;
    	default:
      	    explode_bomb();
      	    return result;
    }
    if ( v1 != v4 )
        explode_bomb();
    return 0;
}

输入的v1和v3满足其中一种case即可。

phase_4

int phase_4(int a1)
{
    int v1;
    int v3;
    int v4;

    if ( __isoc99_sscanf(a1, "%d %d", &v4, &v3) != 2 || (v3 - 2) > 2 )
        explode_bomb();
    v1 = func4(8, v3);
    if ( v1 != v4 )
        explode_bomb();
    return 0;
}

先判断输入的两个整数,第二个数要<=4,然后传参给func4返回值要等于输入的第一个数。

int func4(int a1, int a2)
{
    int result;
    int v3;

    if ( a1 <= 0 )
    {
        result = 0;
    }
    else
    {
        result = a2;
        if ( a1 != 1 )
    	{
      	    v3 = a2 + func4(a1 - 1, a2);
      	    result = v3 + func4(a1 - 2, a2);
    	}
    }
    return result;
}

写一个main调用一下即可。

int main(int argc, char *argv[]) {
    int a2 = 3;	     //a1是输入的第一个数 a2是输入的第二个数
    printf("a1: %d",func4(8,a2));
    return 0;
}

求出其中一个解是162 3

phase_5

int phase_5(int a1)
{
    int v1;
    int v2;
    int v3;
    int v5;
    int v6;

    if ( __isoc99_sscanf(a1, "%d %d", &v5, &v6) <= 1 )
        explode_bomb();
    v1 = v5 & 0xF;	//取二进制前四位,对于比较小的整数没有影响
    v5 = v1;
    if ( v1 == 15 )
    	goto LABEL_12;
    v2 = 0;
    v3 = 0;
    do {
    	++v3;
        v1 = array_3248[v1];
        v2 += v1;
    } while ( v1 != 15 );
    v5 = 15;
    if ( v3 != 15 || v2 != v6 )
LABEL_12:
        explode_bomb();
    return 0;
}

发现array_3248很可疑,看了下,是数据,整理一下即可。函数基本逻辑是输入的第一个数是array[15]的值,第二个数是array_3248所有值的和。由此写一个判断代码。

#include <stdio.h>
int main()
{
    int v1;
    int v2;
    int v3;
    int v5;
    int v6;
    int array[16];
    array[0]=10;
    array[1]=2;
    array[2]=14;
    array[3]=7;
    array[4]=8;
    array[5]=12;
    array[6]=15;
    array[7]=11;
    array[8]=0;
    array[9]=4;
    array[10]=1;
    array[11]=13;
    array[12]=3;
    array[13]=9;
    array[14]=6;
    array[15]=5;
	
    v5 = 5;		//输入的第一个数
    v6 = 115;		//输入的第二个数
	
    v1 = v5;
    if ( v1 == 15 )
        printf("error!");
    v2 = 0;
    v3 = 0;
    do {
        ++v3;
        v1 = array[v1];
        v2 += v1;
    } while ( v1 != 15 );
    if ( v3 != 15 || v2 != v6 )
        printf("Wrong!");
    else 
        printf("Correct!");
    return 0;
}

phase_6

int phase_6(int a1)
{
    int v1;
    signed int v2;
    signed int v3;
    _DWORD *v4;
    signed int v5;
    int v6;
    int v7;
    int v8;
    int *v9;
    int v10;
    int v11;
    signed int v12;
    int v14;
    int v15[6];
    int v16[5];
    char v17;

    read_six_numbers(a1, (int)v15);
    v2 = 0;
    while ( 1 )
    {
    	if ( (v15[v2] - 1) > 5 )
      	    explode_bomb(v1);
    	if ( ++v2 == 6 )
      	    break;
    	v3 = v2;
    	do {
      	    if ( *(&v14 + v2) == v15[v3] )
                explode_bomb(v1);
      	    ++v3;
    	} while ( v3 <= 5 );
    }

    v6 = 0;
    do {
    	v7 = v15[v6];
    	v5 = 1;
    	v4 = &node1;
    	if ( v7 > 1 )
    	{
      	    do {
                v4 = (_DWORD *)v4[2];
                ++v5;
      	    } while ( v5 != v7 );
    	}
    	v16[v6++] = (int)v4;
    } while ( v6 != 6 );

    v8 = v16[0];
    v9 = v16;
    v10 = v16[0];
    do {
        v11 = v9[1];
    	*(_DWORD *)(v10 + 8) = v11;
    	++v9;
    	v10 = v11;
    } while ( (char *)v9 != &v17 );
    *(_DWORD *)(v11 + 8) = 0;
    v12 = 5;
    do {
    	if ( *(_DWORD *)v8 > **(_DWORD **)(v8 + 8) )
      	    explode_bomb(v11);
    	v8 = *(_DWORD *)(v8 + 8);
    	--v12;
    } while ( v12 );
    return 0;
}

分析一下,第一段while就是循环判断了下输入的六个数是否都<=6,然后看第二段do while,里面有一个node1,看数据,下面还有node2一直到node6,原来是一个链表。结构如下:

typedef struct
{
    int data;	//节点的数据
    int order;	//这是序号,node1为1,node2为2
    node *next;
} node;

先是v4 = &node1,然后v16[v6++] = (int)v4,然后传给了v8,第三段do while是在判断数组中的数据是否递增。对6个node中的data递增排序,然后输入对应的order即可。

node1->data = 0x8A;
node2->data = 0x374;
node3->data = 0xE1;
node4->data = 0x26F;
node5->data = 0x47;
node6->data = 0x3A4;

所得序列为5 1 3 4 2 6。

secret_phase

先研究下如何进入secret_phase,看下每次的phase_defused函数:

int phase_defused()
{
    char v1;
    char v2;
    char v3;

    if ( num_input_strings == 6 )
    {
    	if ( __isoc99_sscanf(&unk_804D8F0, "%d %d %s", &v1, &v2, &v3) == 3 && !strings_not_equal(&v3, "DrEvil") )
    	{
      	    puts("Curses, you've found the secret phase!");
      	    puts("But finding it and solving it are quite different...");
      	    secret_phase();
    	}
    	puts("Congratulations! You've defused the bomb!");
    	puts("Your instructor has been notified and will verify your solution.");
    }
    return 0;
}

要输入"%d %d %s"的格式,看前几关的输入格式,phase_3phase_4phase_5都是"%d %d"的格式,干脆都在后面加上"DrEvil",第六关结束后顺利进入隐藏关卡。

int secret_phase()
{
    const char *v0;
    int v1;
    __int32 v2;
    int v3;

    v0 = (const char *)read_line();
    v2 = strtol(v0, 0, 10);
    if ( (unsigned int)(v2 - 1) > 0x3E8 )
        explode_bomb(v1);
    if ( fun7(&n1, v2) != 2 )
    	explode_bomb(v3);
    puts("Wow! You've defused the secret stage!");
    return phase_defused();
}

strtol(v0, 0, 10)是将输入的字符串转换成十进制。先判断v2要<=1001,然后传参给fun7,返回值必须为2。fun7函数如下:

int fun7(int a1, int a2)
{
    int result;

    if ( a1 )
    {
    	if ( *(_DWORD *)a1 <= a2 )
    	{
      	    result = 0;
      	    if ( *(_DWORD *)a1 != a2 )
                result = 2 * fun7(*(_DWORD *)(a1 + 8), a2) + 1;
    	}
    	else
    	{
      	    result = 2 * fun7(*(_DWORD *)(a1 + 4), a2);
    	}
    }
    else
    {
    	result = -1;
    }
    return result;
}

看了半天,原来fun7的第一个参数很重要,进入n1,发现这里有很多数据,整理之后如下:

n1 = 0x24;
n21 = 0x8;
n22 = 0x32;
n31 = 0x6;
n32 = 0x16;
n33 = 0x2D;
n34 = 0x6B;
n41 = 0x1;
n42 = 0x7;
n44 = 0x23;
n45 = 0x28;
n47 = 0x63;

是二叉树的结构,按照节点的命名规则来画二叉树,n1为根节点,n21为它的左孩子,n22为右孩子,以此类推。
于是改写一下fun7的代码:

int fun7(int a1, int a2)
{
    if ( a1->data == a2 )
      	return 0;

    else if ( a1->data < a2 )
        return 2 * fun7(a1->right, a2) + 1;

    else if ( a1->data > a2 )
    	return 2 * fun7(a1->left, a2);
}

要使返回值为2,a2应该等于n32data,即0x16 = 22,这样n1->left = n21n21->right = n322 * (2 * (2 * 0) + 1) = 2

通关截图:

Leave A Reply

苏ICP备16066660号-1

苏公网安备 32011502010432号