内存空间布局/奇怪的内存(堆栈)行为C / ASM?

时间:2015-07-05 12:50:32

标签: c pointers memory assembly stack

当玩弄内存以更好地理解进程内存布局和幕后操作时,我完全无法理解它。想象一下以下代码:

#include <stdio.h>
#include <string.h>

int main(int argc,char **argv) {
    char buf[32];
    strcpy(buf,argv[1]);
    return 0;
}

转自IDAdec而非hex):

var_30= dword ptr -30h
var_2C= dword ptr -2Ch
var_20= dword ptr -20h
arg_4= dword ptr  0Ch

结束

push    ebp
mov     ebp, esp
and     esp, 4294967280
sub     esp, 48
call    sub_401920
mov     eax, [ebp+12]
add     eax, 4
mov     eax, [eax]
mov     [esp+4], eax
lea     eax, [esp+16]
mov     [esp], eax
call    strcpy
mov     eax, 0
leave
retn

我的解释:

  • 1)将EBP推入堆栈
  • 2)将ESPEBP
  • 对齐
  • 3)and esp, 4294967280编译器模式可能会被忽略(?)
  • 4)从ESP中减去48个字节,分配大小为48个字节
  • N)我使用的编译器低效地按16字节的块分配内存,即如果你有一个整数,它将分配16个字节,如果你超过16,它将使用32,48,64等
  • 5)调用与(3)中的编译器模式相关的函数可能会被忽略(?)

由于EBP+0x0-0x3存储EBP指针和EBP+0x4-0x7返回地址,我们可以看看这里发生了什么。

  • 1)将指针argv移动到EAX
  • 2)将4个字节添加到EAX(现指向EBP+12+4
  • 3)将指针EBP+12+4移动到EAX
  • N)EBP+12+4等于argv[1]
  • 4)将argv[1]的指针移到堆栈ESP+4
  • 5)???
  • 6)在ESP + 0(?)
  • 上存储buf[32]的内容

尽管如此,如果回答的话,非常感谢,问题不在于ASM,而在于:

根据我的理解,此函数的堆栈框架应如下所示:

[   ] < ESP+0x0-0x3 
[   ]
[   ]
[   ]
[   ]
[   ]
[   ]
[   ]
[   ]
[   ]
[   ]
[   ] < ESP+0x2C-0x2F
[EBP] < EBP+0x0-0x3
[RET] < EBP+0x4-0x7
[ARG]

ARG(EBP + 0x8 +)包含函数的参数。

混乱

  • 当我使用44字节的数据A作为用户输入时,它导致堆栈溢出,而堆栈上的argv[1]指针只有4个其他字节,所以单字节在哪里来自?

  • EBP是4个字节,因为它是一个指针但是当我使用45个字节的数据时,整个EBP已被A覆盖。

  • EIP(afaik)通过覆盖EBP+0x3-0x7(RET)来控制,并且大小也是4个字节。但是,46个字节的数据导致EIP被A的中途重写,47个字节的3/4和48个字节完全用{A}覆盖EIP

最后,有没有理由为什么发送一个太大的缓冲区EIP不再被重写?那是因为它开始覆盖先前的堆栈帧导致更早的崩溃过程吗?

1 个答案:

答案 0 :(得分:3)

让我们分析一下代码

push    ebp
mov     ebp, esp

这是一个标准的序言。

and     esp, 0ffffffff0h 

清除ESP的低半字节(4位)。在最坏的情况下,这会将堆栈指针降低15个字节,最好是什么都没有 但是,此操作将堆栈对齐在16字节边界上。最近这种行为有所增加(当我开始查看反汇编的二进制文件时,没有编译器对齐堆栈)并且这是因为越来越多地使用 SSE AVX 指令。

sub     esp, 30h

这里分配了本地变量的空间。理论上你有32个字节的本地,所以20h字节。 编译器在这里做了一些非常敏捷的东西。它注意到strcpy需要两个4字节的参数。因此,它不是使用两个push指令而是分配空间 对于那个params直接在这里。 为了保持堆栈对齐,它需要达到16的倍数。它不能简单地保留28h字节,而是保留30h字节。为了对齐堆栈指针,浪费8个字节并不是一个很大的损失 所以分配的空间是

EBP         <-- Old Frame Pointer (Saved EBP)
...
EBP - 20h   <-- Start of 32 byte array (Up to EBP-01h included)
EBP - 24h   <-- Unused
EBP - 28h   <-- Unused
EBP - 2ch   <-- strcpy source ptr
EBP - 30h   <-- strcpy destination ptr

在这张图片中,我故意省略了序言开头的堆栈对齐操作,以便有明确的偏移并且为了清晰起见
下一条指令是

call    sub_401920

很难说没有完全反汇编的符号,但这可能是CRT初始化。在GCC汇编的来源中称为__main

main需要两个参数:argcargvEBP上方的内存布局是:

...
EBP + 0ch    <-- argv
EBP + 08h    <-- argc
EBP + 04h    <-- Return address
EBP          <-- Previous Frame Pointer (Saved EBP)
EBP - 04h    <-- Locals (Array)
EBP - 08h    <-- Locals (Array)
...

下一条说明只需加载argv[1]

mov     eax, [ebp+0ch]        ;<-- argv ptr
add     eax, 4                ;<-- &argv[1]
mov     eax, [eax]            ;<-- argv[1]
mov     [esp+4], eax          ;<-- Like a push

请记住,当执行最后一条指令时,堆栈指针就是 在当地以下

...
EBP - 20h   <-- Start of 32 byte array (Up to EBP-01h included)
EBP - 24h   <-- Unused
EBP - 28h   <-- Unused
EBP - 2ch   <-- ESP+04h (strcpy source ptr)
EBP - 30h   <-- STACK POINTER (strcpy destination ptr)

目的地

同样的事情
lea     eax, [esp+10h]    ;Pointer to ebp-20h (EAX = ebp-20h)
mov     [esp], eax        ;Like a push
call    strcpy

最后是标准的epilog

mov     eax, 0
leave
retn

现在是时候了解堆栈对齐的全貌。保存EBP寄存器后,将堆栈对齐降低堆栈指针。引用本地变量主要是通过ESP完成的。

      +---------+
      |  argv   |  EBP + 0ch
      +---------+
      |  argc   |  EBP + 08h
      +---------+
      | ret adr |  EBP + 04h
      +---------+
EBP ->| Old EBP |  EBP
      +---------+
      | Unused  |  EBP - 04h    \
          ...                    > Variable length (min: 0, max = 0fh)
      | Unused  |  ESP + 30h    /
      +---------+
      |  Array  |  
          ...
      |  Array  |  ESP + 10h 
      +---------+
      | Unused  |  ESP + 0ch
      +---------+
      | Unused  |  ESP + 08h
      +---------+
      | src ptr |  ESP + 04h
      +---------+
ESP ->| dst ptr |  ESP
      +---------+

关于您的最终问题,无法确定性地回答。
如果编译器没有对齐堆栈,答案将​​是:

  1. 当您输入44个字节时,您将其标记为EBP-20h,因此有超过12个字节。首先覆盖旧帧指针,然后覆盖返回地址,然后覆盖argc值。

  2. EBP是4个字节,因为它是32位寄存器。使用45个字节,您将覆盖保存在堆栈中的旧帧指针(EBP)。见上文。

  3. 您开始使用37个字节的数据覆盖返回地址(需要40个字节才能完全覆盖)。

  4. 然而,通过对齐堆栈指针,您实际上将ESP降低了变量(理论上)的数据量,因此上面的数字必须添加0到15之间的变量。例如它似乎你的情况下的对齐在最后一个问题中将堆栈指针降低了7个字节。