/ Reverse

Pico CTF : A Thing Called the Stack 逆向题Writeup

A Thing Called the Stack

(注:一道非常简单的逆向题)

逆向 60分

题目描述:你能找出代码运行完毕后esp的值和保存的返回地址的差异吗?(假设是32位系统)结果使用16进制提交,并去除多余的0,例如2015的16进制是0x000007df但你需要把它转化成0x7df。

链接

assembly.s

foo:
    pushl %ebp
    mov %esp, %ebp
    pushl %edi
    pushl %esi
    pushl %ebx
    sub $0x12c, %esp
    movl $0x1, (%esp)
    movl $0x2, 0x4(%esp)
    movl $0x3, 0x8(%esp)
    movl $0x4, 0xc(%esp)

解答

为了理解这个文件,我们首先需要理解x86的调用约定(Calling Convention)是怎样的。
当一个子过程想调用其它过程,并且期望其过程返回的时候,它就会调用call指令。call指令就像一种特殊的jmp指令,除了修改IP之外,他还将返回值压栈。所以,当子过程完成调用之后,为它所分配的栈帧就会消失,为了达到这样的效果,它可以简单的调用ret指令,来将返回地址出栈并且跳往该地址。

在我们的例子中,假设foo被另一个子过程调用(bar),在foo开始的栈帧就会变成像这样:

高地址               | 参数 3     |
                    | 参数 2     |
                    | 参数 1     |
低地址               | 返回地址   |    <= %esp

注意在调用该过程之前,参数被压栈的顺序是逆序。另外注意到栈地址是从高往低生长的。
现在,我们可以开始一步步调试foo,并且一边想想栈帧的分布会是什么样子。首先,我们看到foo设置了他自身的栈帧(这是子过程调用的标准流程)。

pushl %ebp
mov %esp, %ebp

这两条指令运行完毕以后,栈帧长这样:

高地址               | 参数 3          |
                    | 参数 2          |
                    | 参数 1          |
                    | 返回地址        |
                    | 保存的栈基址指针 |     <= %esp

现在,我们遇到了3个连续的push指令:

pushl %edi
pushl %esi
pushl %ebx

栈帧现在长这样:

高地址              | 参数 3          |
                    | 参数 2         |
                    | 参数 1         |
                    | 返回地址        |
                    | 保存的栈基址指针 |
                    | %edi           |
                    | %esi           |
                    | %ebx           |    <= %esp

在赛题描述中说了是32位系统,那么这表示每个栈上的值占用4字节大小。因此,每次我们压栈,都会将栈指针ESP的值减0x4. 因此在这个时候,保存的返回地址和ESP的差值是: 4次压栈*4字节 = 0xf.
这时候我们执行一个sub指令:

sub $0x12c, %esp

这时候栈帧长这样

Higher addresses    | 参数 3     |
                    | 参数 2     |
                    | 参数 1     |
                    | 返回地址     |
                    | 储存的栈基址指针 |
                    | %edi        |
                    | %esi        |
                    | %ebx        |
                    ...
                    < 0x12b 省略 >
                    ...
                    |            |    <= %esp

现在ESP和返回地址储存点的地址差是 0xf + 0x12c = 0x13c

下面4个 mov指令并不更改esp。 %esp两端的小括号表示解引用。 mov指令将值写入%esp寄存器中存储的地址,而不是写入%esp本身。
因此,最终的offset没有变化,我们就得到了flag。


原作者, hgarrereyn, 出处 https://hgarrereyn.gitbooks.io/