CSAPP lab2 二进制拆弹 binary bombs secret_phase(完结撒花?)

给出对应于7个阶段的7篇博客

phase_1  https://www.cnblogs.com/wkfvawl/p/10632044.html
phase_2  https://www.cnblogs.com/wkfvawl/p/10636214.html
phase_3  https://www.cnblogs.com/wkfvawl/p/10651205.html
phase_4  https://www.cnblogs.com/wkfvawl/p/10672680.html
phase_5  https://www.cnblogs.com/wkfvawl/p/10703941.html
phase_6  https://www.cnblogs.com/wkfvawl/p/10742405.html
secret_phase  https://www.cnblogs.com/wkfvawl/p/10745307.html

 

1.1 打开隐藏关

根据前面的提示,在第四阶段输入特殊字符,会引发隐藏关,但是我们在第四阶段的分析中,并没有找到相关代码。

实际上,我们每个阶段通过之后,都调用了phase_defused函数,可以分析里面代码,看看是否在里面。(分析证明,确实在里面)。

1.2 phase_defused分析

asm.txt中寻找phase_defused,可以找到该函数的入口地址0x80495d4

 

11127-1132行:准备函数栈帧,并调用send_msg函数(send_msg是向服务器发送信息,也就是是否通过某一阶段,会向服务器发送信息,send_msg不做进一步分析)。此时函数栈帧如下:

 

21132-1133行:0x804c808地址的内容与6比较,如果不等,则跳转到8049679 <phase_defused+0xa5>1163行(8049679 <phase_defused+0xa5>):判断栈是否溢出,如果没有,正常返回。(注意:esp+0x7c处存放的“哨兵”))(问题:0x804c808地址内容是什么?通过objdump --start-address=0x804c808 -s bomb找不到该地址内容

31135-1143行:如果相等,则继续从1135行执行。这些代码实际上是在为调用sscanf函数准备参数:

11141行:0x804a78b放入到esp + 4。使用objdump --start-address=0x804a78b -s bomb,可以查看该地址内容(这是个字符串常量,在只读数据段中):"%d %d %s",可以判断,要读入的数据应该包含两个整数,一个字符串。

 

21143行:0x804c910放入到esp。根据前面各个阶段的分析,0x804c910应该存放的是要读入的字符串。(0x804c910是什么?使用objdump --start-address=0x804c910 -s bomb找不到该地址内容

31135-1140行:准备返回读出的数据:两个整数,一个字符串。

4)函数栈帧变为:

 

5)从以上函数栈帧可以看出,读出的数据最终放置在esp+24d1)、esp+28d2)、esp+2cstr)的地方。sscanf函数返回结果存储在eax中(读出的数据个数)。

41145 - 1146行:如果读出的数据不是3,则跳转到8049661 <phase_defused+0x8d>(第1159行)

51159-1168行:显示了0x804a640以及0x804a66c地址的内容,然后退出了(puts函数:在屏幕上打印)。使用objdump --start-address=0x804a640 -s bomb可以查看这两个地址的内容,如下图所示:

 

显然,这就是我们看到的通过某个阶段的信息,但这里我们仍然没有打开隐藏关。

6、如果读出的数据是3个,继续从1147行开始执行。

71147-1150行:为调用strings_not_equal做准备:

11147行:0x804a794  --> esp + 4

21149-1150行:sscanf函数读出的str  -->esp

3)显然,strings_not_equal是要比较前述两个字符串,0x804a794的内容(objdump --start-address=0x804a794 -s bomb):

显然,这个要比较的字符串是:DrEvil

81152-1153行:判断以上两个字符串是否相等(相等,eax返回为0),如果不等,跳转到8049661 <phase_defused+0x8d>(第1159行,显示一些信息后,退出函数,参见前面分析),如果相等,继续执行1154行。

91154行:显示一些信息后,调用secret_path函数。

1)显示0x804a5e0/0x804a608地址显示的内容,使用objdump --start-address=0x804a5e0 -s bomb查看这些地址内容:

显然,这些信息是说找到了隐藏的关。

21158行:调用secret_phase函数,显然,这个函数就是隐藏关!!

10、现在的问题是,第1144行输入的字符串0x804c910是什么(根据前面的分析,这个字符串中应该包含"%d %d %s",其中第三个字符串应该是" DrEvil ",才能打开隐藏关)?第1133行和6进行比较的0x804c808是什么?这两个地址的内容通过objdump --start-address=0x804c910 -s bomb并不能查看出来。可以使用objdump -D bomb将所有的section全部打印出来进行分析:

 

从上图可以看出,这两个地址的内容,是放置在bss段中(未初始化的全局变量以及静态变量):

10x804c808:对应的全局变量名称为num_input_strings,顾名思义,这个名字是用户输入的字符串的个数,对于bomb来说,每一关需要输入一个一个字符串(read_line函数),可以猜测这个变量保存了输入的字符串的个数,因为每通一关,输入一个字符串,该变量也可以说明是通过了几关。(实际上,可以继续分析read_line函数,在该函数中,每执行一次,会将0x804c808处的内容加1,这里不展开分析了)。

因此,我们可以判断:将0x804c808处的内容与6相比较,应是判断是否已经通过了第6关,如果已经通过了第6关,才判断是否打开隐藏关。

20x804c910这个是在0x804c820 + 0xf0的地址,也即(input_strings+0xf0),从这里大致可以判断,input_strings应该是保存每次输入的数组。根据解题提示,在第四关输入一个特殊的字符串,可以打开隐藏关,而第四关要求我们输入的是%d %d”(根据第四关解题,应输入"12 43"),为了打开隐藏关,应该输入"12 43 DrEvil"。(DrEvil为作者名字,太自恋了)

为验证以上猜测,可以使用gdb bomb跟踪bomb的执行:

从上面可以看出:

10x804c808:每输入一次,该地方的值加1

20x804c910:正是第四次输入之后,该字符串的首地址。

以上分析,可以说明我们的判断是正确的。

1.3 secret_phase分析

asm.txt中寻找secret_phase,可以找到secret_phase函数入口地址。

 

1723-731行:准备函数栈帧,调用read_line函数,读取用户输入,用户输入放在ax中。然后调用strtol将用户输入转换成十进制数,此时函数栈帧为:

 

strtol(input, 0, 10)input转换成十进制整数。strtol的原型参见相关c语言资料。

2732-736行:将strtol函数传递给ebx,将返回结果减一,然后与0x3e8(十进制1000)进行比较,如果小于则跳转到8049082 <secret_phase+0x32>(第737行),否则引爆炸弹。

注意,这里使用的是jbe,是无符号整数跳转指令。如果输入的数小于0,减1之后是一个很大的无符号数,因此也会大于1000,因此此段代码用于判断输入的整数是否大于等于1,小于等于1001

3737-739行:为调用8048fff <fun7>函数准备函数栈帧。显然,fun7函数包括两个参数:

1)用户输入的整数放置到esp+4(注意:第732行,将用户输入的整数传递给了ebx

20x804c0c0放置到esp。(0x804c0c0应该是某个地址,但0x804c0c0放置的是什么,将在后文分析)

此时的函数栈帧是:

fun7函数返回结果放置在eax中。

4740-742行:判断fun7返回的结果是否是3,如果是,则跳转到804909c <secret_phase+0x4c>(第743行),否则引爆炸弹;

5743-747行:应该是显示某些信息,然后退出函数,这不是我们关心的内容。

目前的关键是fun7函数。根据前面分析,函数fun7的原型应该是:

int  fun7(void* ptr, int num)

注:因尚不清楚0x804c0c0指向的内容是什么,以一个指向void的指针代替。

1.4 fun7函数分析

asm.txt中继续寻找fun7,可以找到fun7的入口。

 

 

 1693-696行:初始化函数栈帧,此时函数栈帧为:

此时,edx的内容为ptr = 0x804c0c0ecx为用户输入的整数num(后文均以num代替)。

2697-698行:判断edx是否为0,即0x804c0c0ptr指针)是否为0,如是,则跳转到8049046 <fun7+0x47>717行,实际上就是返回了0xffffffff,即-1),否则继续执行。

3699-700行:将edx指向的地址的内容放置到ebx(即0x804c0c0指向的地址的内容),比较ecx是否与ebx,即num0x804c0c0地址的内容相比较。

4701行:如0x804c0c0地址的内容(*ptr)小于等于num,则跳转到8049028 <fun7+0x29>(第708行)

5702-707行:如果0x804c0c0地址的内容(*ptr)大于num,则:

1702行:将ecx放置到esp+4,即将num放置到esp+4

2703-704行:将edx + 4地址的内容,放置到espedx此时为0x804c0c0

3705行:调用fun7fun7(*(ptr + 4), num)显然这是一个递归调用。

4706行:将3)的返回结果乘以2

6708-716行:当*ptr <= num时,运行这段代码。

1708行:0  --> eax

2709行:比较ecxebx,其中ecxnumebx0x804c0c0地址的内容(*ptr

3710行:如果ebx==ecx,则跳转到804904b <fun7+0x4c>(即结束,返回eax的值,此时eax的值为0,即返回0

4711-716行:如果ebx != ecx,即*ptr < num

a711行:ecx  --> esp +4,即esp+4num

b712-713行:将edx + 8的内容放置到esp,此时edx的内容为0x804c0c0

c714行:调用fun7fun7(*(ptr + 8), num)显然这是一个递归调用。

d715行:eax = eax + eax * 1 + 1eaxfun7返回结果,即将上一步的返回结果乘以21

7、综合以上分析,可以初步写出fun7C语言代码:

int fun7(void* ptr, int num)

{

if(ptr == null){

return -1;

}

if(*ptr > num){

return 2 * fun7(*(ptr + 4), num);

}else if(*ptr == num){

return 0;

}else{

return 2 * fun7(*(ptr + 8), num) + 1;

}

}

注:

1ptr的类型尚未知,需要进一步分析,这里暂时以void代替。但根据代码的分析:

a*ptr + 4),即ptr +4 的地址的内容,也应该是和ptr同类型的指针;

b*ptr + 8),即ptr +8的地址的内容,也应该是和ptr同类型的指针;

c*ptr),这应该是一个整型。

因此,ptr指向的类型应该是一个构成二叉树的一个节点。

1.5 二叉树分析

1.5.1 初步分析

根据前面分析,ptr应该是一个指向二叉树的一个节点,其定义类似:

struct nodeTree{

int   val;//用于与num比较,应该是一个整数

struct node* left;

struct node* right;

}

如果:struct nodeTree *ptr;

则:*(ptr+4) = ptr->left*(ptr+8) = ptr->right

1.5.2 0x804c0c0地址内容分析

证实以上猜测的最好方法是分析0x804c0c0地址的内容。可以通过两种方法单独分析或者二者结合分析。

方式1objdump --start-address=0x804c0c0 -s bomb > s0x804c0c0.txt,然后查看该txt文件。

 

 

从上面可以看出:

10x804c0c0开始的12个字节:0x24804c0cc804c0d8

20x804c0cc开始的12个字节:0x08804c0fc804c0e4

30x804c0d8开始的12个字节:0x32804c0f0804c108

40x804c0e4开始的12个字节:0x16804c150804c138

50x804c0f0开始的12个字节:0x2d804c114804c15c

60x804c0fc开始的12个字节:0x06804c120804c144

70x804c108开始的12个字节:0x6b804c12c804c168

80x804c114开始的12个字节:0x2800000000000000

90x804c120开始的12个字节:0x0100000000000000

100x804c12c开始的12个字节:0x630000000000000

110x804c138开始的12个字节:0x2300000000000000

120x804c144开始的12个字节:0x0700000000000000

130x8c150开始的12个字节:0x1400000000000000

140x8c15c开始的12个字节:0x2f00000000000000

150x8c168开始的12个字节:0x3e900000000000000

通过以上分析,显然这是一棵二叉树,后面的leftright都为null的情况,代表他们都是叶子节点。

 

方式2objdump --start-address=0x804c0c0 -D bomb > D0x804c0c0.txt,然后查看该txt文件。

 

 

显然,这种方式看到的结果和第一种方式是一样的,但这种方式比第一种方式更容易理解。我们可以看出来一个一个节点,以及它们在程序中定义的名称。

1.5.3 二叉树

通过以上的分析,我们可以得出一个二叉树,如下图所示(上面的16进制均转换成了十进制)。

 

显然,这还是一棵平衡二叉树,而且左子树的值比右子树的值要小,可以用于搜索某个值。

 

1.5 secret_phase结果分析

根据前面分析,secret_phase要求用户输入一个1 ~ 1001的数,fun7搜索这个二叉树,要求fun7返回3,下面来分析fun7返回值的含义。根据以上分析,重写fun7C语言代码。

struct nodeTree{

int   val;//用于与num比较,应该是一个整数

struct node* left;

struct node* right;

}

int fun7(struct nodeTree* ptr, int num)

{

if(ptr == null){

return -1;

}

if(ptr->val  >  num){

return fun7(ptr->left, num)  * 2;

}else if(ptr->val == num){

return 0;

}else{

return  fun7(ptr->right, num)  * 2 + 1;

}

}

从代码可以看出,给定一个值后,fun7对这个二叉树进行搜索,在某一层上搜索到这个值,返回0,回退到上一层,如果该层是上一层的右子树,则将返回值*21(也相当于左移一位,然后加1),如果该层是上一层的左子树,则将返回值*2(左移一位)。

因此,对于本题来讲,相当于是对二叉树的每个节点进行了编号,左子树为0,右子树为1,如下图所示:

 

 

 

例如:

1)搜索1fun7  = 000b = 0,搜索7fun7 = 100b = 4

2)搜索20fun7 = 010b = 2,搜索35fun7= 110b = 6

3)搜索40fun7 = 001b = 1,搜索47fun7 = 101b = 5

4)搜索99fun7= 011b = 3,搜索1001fun7= 111b = 7

 

因此,对于本题来说,要使fun7 = 3,则输入的数应该是99。这就是答案!!

posted @ 2019-04-21 15:25  王陸  阅读(541)  评论(0编辑  收藏  举报