BombLab phase-6 & secret_phase

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Gease_Gg/article/details/83384259

导航

BombLab Phase-1 & Phase-2

BombLab Phase-3 & Phase-4 &Phase-5


Phase_6

  • Phase_6栈帧图

  • Read_six_numbers和sscanf栈帧

由于我们之前在第二次实验已经详细分析过了这两个函数的栈帧,这里我们就不多赘述了,我们知道他们俩的功能是为了读取我们输入的六个数字。

  • 反汇编phase_6函数。

1.

常规的开栈操作,我们看到一共开了92个字节空间。

2.

我们看到这边又是调用了read_six_numbers这个函数,这个函数我们在之前的实验中遇到过,是读取六个数。而我们看到这里参数是ebp-0x30这个地址,和ebp+8这个我们输入的字符串首地址,那我们就知道了ebp-0x30是一个数组的首地址,保存了我们输入的六个数。

 

3.

我们可以用这几句来替代:

Esi = 0;

Edi = 数组首地址;

Eax = 数组首元素;

Eax = eax – 1;

If eax <=5 跳转+51

Else 爆炸

如此,我们看出,当前获得的元素必须要<=6,不然就爆炸了。

 

4.

Esi的值增加1,

然后和6比较,如果等于6,跳转到+93,我们先假设没有跳转,往下继续看;

 

5.

 

Ebx = 下标为esi的元素的地址;  

//看到这里我们又恍然明白,前面esi=6时会跳转,正好六个数都被遍历完了循环结束的跳转,所以接下来肯定存在一个循环。

Ebp-0x4c保存esi的值;

Eax=下标为esi的后面一个元素值;

If(eax!=(ebx)) //其实就是数组中一个元素和他后面一个元素比较

       跳转到 +78

Else

       爆炸;

这个阶段就说明了前后相邻元素必定不能相等,相等就爆炸;

 

6.

我们看看跳到+78发生了什么

我们们说过 ebp-0x4c保存的当前元素的下标的一个临时变量;

临时变量+1;  //其实也就是下标+1;

Ebx=ebx+4;   //地址+4,ebx变成下一个元素地址

If(下标值<=5)

       跳转到 +65;  //说明比较没有结束,轮到下一个元素了

Else

       跳转到 +35;   //回到35,说明内层循环已经走完,开始下一轮外层循环

 

7.

当我们循环完毕后,会跳转到另一个地方

这一段,我们看到是在判断数组的元素有没有判断完,完毕之后,我们来到下一阶段+93

 

阶段一总结:

所以,综合起来看,这一阶段这其实是一个2层的for循环,目的是用来保证数组中的任意两个数都不相等,

且输入的每个元素都要<=6

否则就会就爆炸。  

分析完这个阶段我们来到下一个阶段。

我们看到下一个阶段会跳转到 +93,那么我们去+93看看;

(下一阶段我们称为阶段二)


8.

Ebx = 0;

Edi=数组首地址;

无条件跳转 +124的地方,那么我们可以看出这两句是初始化语句,我们再去看看+125。

 

9.

Esi = ebx;

Ecx = 数组中下表为 ebx 的元素值;

Edx = 内存为0x804c0c4中的值;

Eax = 1;

If( ecx > 1 )

       跳转到+103       //可以看出,又是一个循环;

              Else

                     跳转到+113       //eax=ecx会进入另一个分支

首先,我们还是看看 0x804c0c4 地址里放了什么东西:

从数据上,我们暂时看不出什么东西,但是我们可以注意到左边的尖括号里面的node1,这提示我们作者这个变量的取名为node1,我们可以猜测这是一个结点

 

分析完有用信息,那我们先去看看到 +103是什么情况把;

10.

这里汇编我们可以看到终止条件是ecx=eax,如果ecx!=eax,则edx在循环里面每次改边一次并且edx=edx+8,这我们可以猜想很多,edx里面存放的是地址,edx+8里面存放的也是地址,联想到之前我们说的node1,很可能这是一个链表结构!

所以到这,我们知道最后的edx取值取决于你输入的ecx。

假设我们已经eax=ecx,我们来到下一步。

11.

这几句代码,是将我们edx里面存放的内容,放到栈中的一个位置保存,然后ebx加1,如果ebx!=6,我们发现又回到了+125的位置,又开始了对下一个元素的操作,这说明这也是一个两层循环,只有ebx=6之后才会来到之后一个阶段。

 

阶段二总结:

这个阶段仍然是一个两层循环,目的在于将我们从链表中取到的结点地址放入栈中保存,将六个都取完之后,到下一个阶段。

好,我们开始进入阶段三分析。


12

我们发现这边是一系列的赋值,但从阶段二中,我们知道这些位置保存的都是一些结点的地址值,我们先看看前几句:

Ebx=第一个结点地址;

Eax=第二个结点地址;

Ebx+8=eax;

这几句很关键!!节点一的地址+8里面存放的是取出的第二个结点的起始地址!!这是在做链表的构建!

之后我们再看,意义是一样的,剩下的代码又将

结点2地址+8=结点3

结点3地址+8=结点4

········

结点6地址+8=0.   //(0就是指针空)

所以,这些代码是将我们取出的六个结点又依此串联重组起来了!!!

这样做又是想干嘛呢,我们再往后看。

13.

Esi=0 //结合前面经验,esi可能又是下标

Ebx存放的第一个结点的地址,ebx+8是第二个元素地址。

Eax=第二个结点地址;

Edx=第一个结点存放的值;

If(第一个结点存放值>=第二个结点存放值)

              跳转到+206

Else

       爆炸

这里说明取到的当前要>=后面一个结点值,我们假设成立,来到+206

Ebx=该节点下一个指向结点的地址;

Esi1

If(esi!=5)

       跳转到+192      //说明是一个循环,开始下一轮循环

Else

       结束,弹栈返回;

 

阶段三总结:

可以看出esi是循环计数变量,要对里面的的五个数进行判断,一共5轮,判断结点保存的六个数中相邻的数是不是都比后面一个数大,一言以蔽之,就是判断结点里面的值是不是非递增的!

-------------------------------------------------------------------------------------------------------------------------

Phase_6总结:

总结一下前面所有的限制条件

        1. 输入的数必须<=6
        2. 输入的数两两不同
        3. 最后取出结点的数必须非递增
        4. 输入的每个元素都会决定你取出哪个结点

 

所以,现在唯一问题在于结点里面保存的是什么值,之后一切都可解决,那我们就查看一下:

结点1信息(已知地址0x804c0c4):

第二个结点:

第三个:

第四个:

第五个:

第六个:

 

所以一开始这个链表是这样的:

我们只要按值递减顺序输入他们的编号就可以了:

所以答案应该是

5 6 1 4 3 2

成功!


Secret_phase

上课提示有一个隐藏关,并且在phase_defused中,作为一种锻炼,我们还是继续深入看看

  • Phase_defuse栈帧

1.这是调用sscanf函数前的栈帧情况:

2.这是调用strings_not_equal前的栈帧

3.这是调用secret_phase前的栈帧

1.

Phase_defuse函数汇编分析

我们看到在+142看到了secret_phase,那我们来看下怎么触发。

 

2.

常规的开栈操作,不过不同的这边多了一个哨兵值的概念,具体是用来干什么的,相见“遇到的问题模块”,但是我们可以提前知道它的作用是用来检查栈是否被破坏的。

 

3.

这里,我们看到将一个地址值与6比较,我们查看这个地址值,发现:

这是地址指向num_input_strings,很明显这是一个变量,保存的意义是什么我们还要再去看?细想我们之前六个phase都没遇到过这个地址,那么现在它唯一可能出现的地方就是read_line这个函数了,我们去看一下:

这两个对于0x804c3d0的操作都很简单,就是让0x804c3d0里的值加1

我们大致可以猜测,这个变量存放的是我们输入的字符串的数量,也就是我们当前闯完了几关;

我们调试测试以下:

当我们通过第一关后,nums_input_strings变量的变化情况:

第二关的变化情况:

··········

我们可以看到的确如我们所说的样子,这个记录了你闯关的关数。

 

那么回到phase_defuse里面,当num_input_strings!=6,这个函数就会跳转到+167

 

4.

跳转到+167,不管你的栈是否有没有被破坏,函数都会结束,不会进入隐藏关,所以隐藏关一定是在6关都闯完后才触发

 

5.

那么假设我们闯完了6关,接下来:

这一系列操作我们已经很熟悉了,就是压栈调用函数的操作,可以看出是在为了sscanf函数准备。

一共五个参数,我们知道第二个参数是格式控制串,第一个是输入的字符串地址,剩下三个是保存的地方,我们查看以下格式控制串,发现:

从中,我们知道,这个读取字符串前两个位数字,最后一个为字符串。

但是第一个参数0x804c4d0是什么呢,其实我们可以不妨调试观察以下:

我们再六次答案输入完毕之后,看下这个地址存放的是什么:

观察三个阶段,我们发现当我们在phase_4输入答案后,这个地址的字符串突然就变成了我们输入的字符串,并且之后也没有改变,换其他数据测试,也是如此,所以我们确定这个地址保存的是我们第四个阶段输入的答案。

 

6.

输入完毕之后,我们发现它又将提取出来的第三个字符串和0x804a209地址的字符串做了比较,略去中间的printf,我们发现之后就可以进入隐藏关了。

所以我们查看一下0x804a209里面存放的内容:

那我们尝试下加入这个字符串:

没错,我们终于触发了隐藏关;

------------------------------------------------------------------------------------------------------------------------------------

  • 隐藏关secret_phase汇编分析

Secret_phase栈帧:(我们给出最关键的调用fun7前的栈帧)

那我们现在进入隐藏关,看看怎么才能解决;

 

1.

开了20个字节的栈

 

2.

调用了read_line函数,是在读取我们输入的答案。

然后,传入了三个参数,eax(输入字符串的地址),0a,给了strtol函数,这个函数也是一个动态链接库的函数,我们查看它的功能:

Strtol有三个参数,第一个是要转化的字符串的地址,第二个是结束符标志,0表明是‘\0’,第三个是转化的数的进制,a说明最后是10进制。

 

3.

Eaxstrtol的返回值,也就是我们输入的数字,然后又放在了ebx中。

然后eax的值自减1,

If(eax<=1000)

       跳转到+53

Else

       爆炸

 

4.

我们先看看跳转到+53是什么情况:

前面两句明显是为调用func7函数而传入的两个参数,

参数1=0x804c178

 

这个地址里面存了一个数是36

参数2=ebx   //即我们输入的那个数字

 

5.

这里说明如果fun7返回值如果不是5就是爆炸。

 

6.

如果炸弹没有爆炸,说明你输入信息正确了,那么这段语句我们都不用想,肯定是printf祝贺你的话,我们就不看了。

 

阶段总结:

目标:

1.要输入一个数,它<=1001;

2.输入的这个数要作为参数传进fun7,最后要是fun7的返回值为5

------------------------------------------------------------------------------------------------------------------------------------

  • fun7的汇编分析

fun7(层数太多,我们暂且给出两层)

 

1.

常规开栈,开了20个字节。并且将

参数1赋值给edx,参数2赋值给ecx;将eax赋值为-1

所以现在   edx=0x804c178  ecx=我们输入的数字;eax=-1;

 

2.

如果edx=0,跳转到+75;我们知道edx存放的是地址,所以这个0代指的是null指针的意思。

 

3.

跳转到+75函数就返回了。

 

4.如果edx=null

edx地址的值存放到ebx中;

If(ebx<=ecx)   //将我们输入的值和这个结点的值进行比较

       跳转到+47

Else

       下一步

 

5.

如果跳转到+47, ebx<=ecx

eax置为0

If(ebx==ecx)

       函数返回

Else

       下一条指令;

 

6.

ecx传入esp+4,将edx+8地址值传给esp位置,根据第六题的经验,我们轻易猜测到这是一个链式结构,将结点指向的下一个结点地址传给了fun7进行了递归调用。

 

7.

返回值eax=2*eax+1;

8.

回到最开始的分支,如果没有跳到+47,即如果ebx>ecx

 

ecx的值放在esp+4位置,取edx+4的值给eax,可见edx+4也是一个地址,将eax放入栈底,栈底两个数作为fun7的两个参数又进行了一次递归调用,调用完毕后,返回值*2,函数结束。

------------------------------------------------------------------------------------------------------------------------------------

Fun7函数总结

我们用伪代码来总结下函数:

Int Fun7(node* addr, int input){

       If(addr==null)  return -1;

       If(*addr <= input){

       If(*addr == input ) return 0;

       Else 2*fun7(addr+8,input)+1;

}

Else 2*fun7(addr+4,input);

}

这个过程类似于BST的搜索,但是在返回值处理上有一点细微的差别。我们的目标是让这个函数返回值为5,那我们只能逆着来推了。

       返回值为5,那么它肯定是根据 2*2+1得到的,也就是说下一层的fun返回值为2

       返回值为2,那么肯定是2*1得到,也即是说下一层fun返回值为1

       返回值为1,那么肯定是2*0+1得到,说明下一层返回值为0

       返回值为0,说明是此时结点值=输入值,那么我们只要找到这个结点值就可了。

       顺着上面的思路,我们可以看出,这个二叉树的遍历顺序是

        --->  --->  ,我们来找一下最后一个节点的值:

      

       所以,最后一个结点值为0x2f,47,我们输入进去:

      

       完成!

四、遇到的问题及解决方法

       关于mov %gs::0x14,%eax

这段代码一开始不是很理解意思,查询了博客:https://blog.csdn.net/swordmanwk/article/details/42086557

了解到这个语句是栈的保护机制,可以检测栈是否被破坏。

我们根据phase_defused函数来讲解它是如何检查并实现保护的:

这两句,他是将段地址偏移为20的一个值存入了ebp-0xc的位置。

当我们进行完了一系列的操作后,他会检查一遍:

它将ebp-0xc位置的值取出,然后和%gs::20做抑或,结果为0表示栈没有被破坏,不为0,表示这个值被更改,栈被破坏,执行stack_chk这个函数。

工作原理就是如此,具体细节还没有找到更深入的介绍,还在继续学习。

 

猜你喜欢

转载自blog.csdn.net/Gease_Gg/article/details/83384259
今日推荐