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。

 

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

 

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

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

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

 

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

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

4)函数栈帧变为:

 

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

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

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

 

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

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

7、1147-1150行:为调用strings_not_equal做准备:

1)1147行:0x804a794  --> esp + 4

2)1149-1150行:sscanf函数读出的str  -->esp

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

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

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

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

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

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

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

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

 

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

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

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

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

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

从上面可以看出:

1)0x804c808:每输入一次,该地方的值加1;

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

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

1.3 secret_phase分析

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

 

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

 

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

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

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

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

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

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

此时的函数栈帧是:

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

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

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

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

int  fun7(void* ptr, int num)

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

1.4 fun7函数分析

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

 

 

 1、693-696行:初始化函数栈帧,此时函数栈帧为:

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

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

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

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

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

1)702行:将ecx放置到esp+4,即将num放置到esp+4

2)703-704行:将edx + 4地址的内容,放置到esp(edx此时为0x804c0c0)

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

4)706行:将3)的返回结果乘以2。

6、708-716行:当(*ptr <= num)时,运行这段代码。

1)708行:0  --> eax

2)709行:比较ecx与ebx,其中ecx为num,ebx为0x804c0c0地址的内容(*ptr)

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

4)711-716行:如果ebx != ecx,即*ptr < num时:

a:711行:ecx  --> esp +4,即esp+4为num

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

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

d:715行:eax = eax + eax * 1 + 1;eax为fun7返回结果,即将上一步的返回结果乘以2加1。

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

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;

}

}

注:

1、ptr的类型尚未知,需要进一步分析,这里暂时以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地址的内容。可以通过两种方法单独分析或者二者结合分析。

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

 

 

从上面可以看出:

1)0x804c0c0开始的12个字节:0x24、804c0cc、804c0d8

2)0x804c0cc开始的12个字节:0x08、804c0fc、804c0e4

3)0x804c0d8开始的12个字节:0x32、804c0f0、804c108

4)0x804c0e4开始的12个字节:0x16、804c150、804c138

5)0x804c0f0开始的12个字节:0x2d、804c114、804c15c

6)0x804c0fc开始的12个字节:0x06、804c120、804c144

7)0x804c108开始的12个字节:0x6b、804c12c、804c168

8)0x804c114开始的12个字节:0x28、0000000、0000000

9)0x804c120开始的12个字节:0x01、0000000、0000000

10)0x804c12c开始的12个字节:0x63、000000、0000000

11)0x804c138开始的12个字节:0x23、0000000、0000000

12)0x804c144开始的12个字节:0x07、0000000、0000000

13)0x8c150开始的12个字节:0x14、0000000、0000000

14)0x8c15c开始的12个字节:0x2f、0000000、0000000

15)0x8c168开始的12个字节:0x3e9、0000000、0000000

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

 

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

 

 

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

1.5.3 二叉树

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

 

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

 

1.5 secret_phase结果分析

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

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,回退到上一层,如果该层是上一层的右子树,则将返回值*2加1(也相当于左移一位,然后加1),如果该层是上一层的左子树,则将返回值*2(左移一位)。

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

 

 

 

例如:

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

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

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

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

 

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

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