3

我有一个看起来像这样的函数:

int bof(char *str)
{
    char buffer[12];

    strcpy(buffer, str);

    return 1;
}

我试图覆盖它的返回地址。我发现我可以通过使用例如memcpy(buffer+24, "\x15\xf1\xff\xbf", 4). 我不明白为什么我需要访问buffer + 24. 我对 C 内存模型的理解告诉我,这个函数执行时的堆栈应该是这样的

bottom of                                                            top of
memory                                                               memory
           buffer(12)     sfp(4)   ret(4)   str(4)
<------   [            ][       ][       ][       ] --->

top of                                                            bottom of
stack                                                                 stack

这表明我应该将 ret 地址从缓冲区 + 16 开始。额外的 8 个字节从哪里来?

顺便说一句,我在 32 位系统上运行它。

4

2 回答 2

3

检查 ISO C 99 和更早的标准,您会发现没有 C 内存模型之类的东西。每个编译器都可以自由使用它喜欢的任何模型来满足功能规范。一个简单的例子是编译器可以使用或省略帧指针。它还可以为返回值分配空间或使用寄存器。

当我在我的机器上编译这段代码时,我得到

_bof:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    8(%ebp), %eax
    movl    %eax, 4(%esp)
    leal    -20(%ebp), %eax
    movl    %eax, (%esp)
    call    _strcpy
    movl    $1, %eax
    leave
    ret

leal指令正在计算缓冲区的起始位置bp-20。在序言之后,堆栈帧如下所示:

[ bp+8: str ] [bp+4: rtn address ] [bp: saved bp] [ bp-20: buf ] ...

所以看起来你必须写 28 个字节来改变这里的返回地址。我的猜测是编译器试图在段落边界上对齐堆栈帧。这里的完整帧包括 strcpy arg 设置是 48 字节,即 3 段。段落对齐有助于总线和缓存性能。

于 2012-11-06T05:36:26.410 回答
2

这不是 C 内存模型或 C 函数调用堆栈布局。它只是您知道的特定编译器和硬件架构的实现。在 ARM 32 位 CPU 中,函数调用参数 1~4 不入栈,它使用 r0~r3 传递参数,如果有超过 4 个参数,则将其他参数入栈。函数返回地址可能也不需要推送到堆栈,因为有一个特殊的寄存器 LR 来保存返回地址。

于 2012-11-06T07:01:42.820 回答