导航
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=该节点下一个指向结点的地址;
Esi加1;
If(esi!=5)
跳转到+192 //说明是一个循环,开始下一轮循环
Else
结束,弹栈返回;
阶段三总结:
可以看出esi是循环计数变量,要对里面的的五个数进行判断,一共5轮,判断结点保存的六个数中相邻的数是不是都比后面一个数大,一言以蔽之,就是判断结点里面的值是不是非递增的!
-------------------------------------------------------------------------------------------------------------------------
Phase_6总结:
总结一下前面所有的限制条件
-
-
-
- 输入的数必须<=6
- 输入的数两两不同
- 最后取出结点的数必须非递增
- 输入的每个元素都会决定你取出哪个结点
-
-
所以,现在唯一问题在于结点里面保存的是什么值,之后一切都可解决,那我们就查看一下:
结点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(输入字符串的地址),0,a,给了strtol函数,这个函数也是一个动态链接库的函数,我们查看它的功能:
Strtol有三个参数,第一个是要转化的字符串的地址,第二个是结束符标志,0表明是‘\0’,第三个是转化的数的进制,a说明最后是10进制。
3.
Eax是strtol的返回值,也就是我们输入的数字,然后又放在了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这个函数。
工作原理就是如此,具体细节还没有找到更深入的介绍,还在继续学习。