【问题标题】:Modify return address on stack修改栈上的返回地址
【发布时间】:2012-09-19 18:14:54
【问题描述】:

我查看了缓冲区溢出漏洞的基础知识,并试图了解堆栈是如何工作的。为此,我想编写一个简单的程序,将返回地址的地址更改为某个值。谁能帮我弄清楚基指针的大小以获取第一个参数的偏移量?

void foo(void)
{
    char ret;
    char *ptr;

    ptr = &ret; //add some offset value here 
    *ptr = 0x00;
}

int main(int argc, char **argv)
{
    foo();

    return 1;
}

生成的汇编代码如下:

    .file   "test.c"
    .text
    .globl  foo 
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16 
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    -9(%rbp), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movb    $0, (%rax)
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret 
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16 
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    call    foo 
    movl    $1, %eax
    leave
    .cfi_def_cfa 7, 8
    ret 
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.7.1 20120721 (prerelease)"
    .section    .note.GNU-stack,"",@progbits

foo 帧段的相关部分应如下所示:

[char ret] [基指针] [返回地址]

我有第一个的位置,它的大小只有 1 个字节。它是否只比http://insecure.org/stf/smashstack.html 中提到的基指针或字的大小更远了 1 个字节?以及如何知道基指针的大小?

【问题讨论】:

    标签: c assembly stack buffer-overflow


    【解决方案1】:

    您无法在 vanilla C 中执行此操作,您无法控制编译器如何布置堆栈帧。

    在 x86-64 中,返回地址应为 %rbp + 8。您可以使用一些内联程序集来获得它(gcc 语法):

    uint64_t returnaddr;
    asm("mov 8(%%rbp),%0" : "=r"(returnaddr) : : );
    

    同样用于设置。

    即使这有点粗略,因为您不知道编译器是否会设置%rbp。 YMMV。

    【讨论】:

    • +1 重新编译总是可以移动变量。仅用于 OP 的测试,在这种特殊情况下,返回地址为 &ret + 17,只要该函数中的局部变量不改变,它就不太可能改变。
    • @fliX 在程序集中,指令leaq -9(%rbp), %rax 正在获取堆栈上的地址。由于它是字节对齐的,并且是唯一的地址计算,所以它必须是ret 所在的位置。取那个 9,并为之前的 64 位基指针加上 8,得到 17。
    • 为什么使用chars? ints 不是更适合指针地址吗?
    【解决方案2】:

    您的基指针很可能只是一个指针,因此它的大小为 sizeof(int*)。 但是在变量ret 和基指针之间还有另一个值。 我会假设它的值是一个寄存器(eax?)。如果您想要一个无限循环,这将导致类似以下内容:

    void foo(void)
    {
        char ret;
        char *ptr;
    
        ptr = (char*)(&ret) + (sizeof(ret)  + 2*sizeof(int*)) ;
        *(int*)ptr -= 0x0c;
    }
    

    假设它具有指针大小(对于其他指令集可能会有所不同)而被修改的返回目标。通过递减它,返回目标被设置为foo的调用点之前的一个点。

    【讨论】:

      【解决方案3】:

      您似乎使用的是 64 位架构,因为 RBP 和 RSP 寄存器都是 64 位长的。如果将ptr 声明为char*,则必须将其递增8 次才能在堆栈中移动。 相反,您可以将其声明为uint64_t *。此数据类型通常在<stdint.h> 中可用。

      但是,堆栈帧定义会因目标体系结构甚至编译器行为和优化而异。不过,如果你在做实验也没关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-28
        • 2016-01-10
        • 2017-04-04
        • 2014-04-21
        • 2010-12-14
        • 1970-01-01
        • 2012-04-21
        • 1970-01-01
        相关资源
        最近更新 更多