当玩弄内存以更好地理解进程内存布局和幕后操作时,我完全无法理解它。想象一下以下代码:
#include <stdio.h>
#include <string.h>
int main(int argc,char **argv) {
char buf[32];
strcpy(buf,argv[1]);
return 0;
}
转自IDA
(dec
而非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
我的解释:
EBP
推入堆栈ESP
与EBP
and esp, 4294967280
编译器模式可能会被忽略(?)ESP
中减去48个字节,分配大小为48个字节由于EBP+0x0-0x3
存储EBP
指针和EBP+0x4-0x7
返回地址,我们可以看看这里发生了什么。
argv
移动到EAX
EAX
(现指向EBP+12+4
)EBP+12+4
移动到EAX
EBP+12+4
等于argv[1]
argv[1]
的指针移到堆栈ESP+4
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
不再被重写?那是因为它开始覆盖先前的堆栈帧导致更早的崩溃过程吗?
答案 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
需要两个参数:argc
和argv
。 EBP
上方的内存布局是:
...
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
+---------+
关于您的最终问题,无法确定性地回答。
如果编译器没有对齐堆栈,答案将是:
当您输入44个字节时,您将其标记为EBP-20h
,因此有超过12个字节。首先覆盖旧帧指针,然后覆盖返回地址,然后覆盖argc
值。
EBP
是4个字节,因为它是32位寄存器。使用45个字节,您将覆盖保存在堆栈中的旧帧指针(EBP
)。见上文。
您开始使用37个字节的数据覆盖返回地址(需要40个字节才能完全覆盖)。
然而,通过对齐堆栈指针,您实际上将ESP
降低了变量(理论上)的数据量,因此上面的数字必须添加0到15之间的变量。例如它似乎你的情况下的对齐在最后一个问题中将堆栈指针降低了7个字节。