Linux 64位上的堆栈驻留缓冲区溢出?

Linux 64位上的堆栈驻留缓冲区溢出?,linux,gdb,64-bit,stack-overflow,callstack,Linux,Gdb,64 Bit,Stack Overflow,Callstack,我正在研究一些与安全相关的东西,现在我正在玩我自己的堆栈。我所做的应该非常简单,我甚至没有尝试执行堆栈,只是为了表明我可以控制64位系统上的指令指针。我关闭了所有我知道的保护机制,只是为了能够使用它(NX位,ASLR,也使用-fno-stack-protector-z execstack编译)。 我对64位汇编没有太多的经验,在花了一些时间搜索和试验我自己之后,我想知道是否有人能对我正在经历的一个问题提供一些启示 我有一个程序(下面的源代码),它只将字符串复制到堆栈驻留缓冲区中,而不进行边界检查

我正在研究一些与安全相关的东西,现在我正在玩我自己的堆栈。我所做的应该非常简单,我甚至没有尝试执行堆栈,只是为了表明我可以控制64位系统上的指令指针。我关闭了所有我知道的保护机制,只是为了能够使用它(NX位,ASLR,也使用-fno-stack-protector-z execstack编译)。 我对64位汇编没有太多的经验,在花了一些时间搜索和试验我自己之后,我想知道是否有人能对我正在经历的一个问题提供一些启示

我有一个程序(下面的源代码),它只将字符串复制到堆栈驻留缓冲区中,而不进行边界检查。然而,当我用一系列0x41覆盖时,我希望看到RIP被设置为0x4141,相反,我发现我的RBP被设置为这个值。我确实得到了一个分段错误,但RIP不会在RET指令执行时更新为这个(非法)值,即使RSP设置为合法值。我甚至在GDB中验证过,在RET指令之前的RSP上有一个包含一系列0x41的可读取内存

我的印象是,请假指示确实:

MOV(东)SP(东)BP

波普(E)BP

但是,在64位上,“leveq”指令似乎可以执行(类似于):

MOV RBP,QWORD PTR[RSP]

我认为它只是通过在执行该指令之前和之后观察所有寄存器的内容来实现的。LEVEQ似乎只是RET指令的上下文相关名称(GDB的反汇编程序给出了它),因为它仍然只是一个0xC9

RET指令似乎与RBP寄存器有关,也许是去引用它?我的印象是RET做了(类似于):

MOV RIP,QWORD PTR[RSP]

然而就像我提到的,它似乎取消了对RBP的引用,我认为它这样做是因为当没有其他寄存器似乎包含非法值时,我得到了一个分段错误

程序的源代码:

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

int vuln_function(int argc,char *argv[])
{
    char buffer[512];

    for(int i = 0; i < 512; i++) {
        buffer[i] = 0x42;
    }

    printf("The buffer is at %p\n",buffer);

    if(argc > 1) {
        strcpy(buffer,argv[1]);
    }

    return 0;
}    

int main(int argc,char *argv[])
{
    vuln_function(argc,argv);

    return 0;
}
程序以650 0x41作为参数执行,这足以覆盖堆栈上的返回地址

(gdb) run `perl -e 'print "A"x650'`
我在内存中搜索返回地址0x00400610(通过查看main的反汇编找到)

我用x/200x检查了内存,得到了一个很好的概述,由于它的大小,我在这里省略了它,但是我可以清楚地看到0x42,它表示缓冲区的合法大小,以及返回地址

0x7fffffffda90: 0xffffdab0      0x00007fff      0x00400610      0x00000000
strcpy()之后的新断点:

但RIP显然是:

rip            0x4005ef 0x4005ef <vulnerable+131>
rip 0x4005ef 0x4005ef

为什么RIP没有像我期望的那样得到更新?LEVEQ和RETQ在64位上真正做什么?简言之,我在这里遗漏了什么?我试着在编译时省略编译器参数,只是为了看看它是否有任何区别,它似乎没有任何区别。

这两条指令正是您所期望的。您已经用
0x41
覆盖了上一个堆栈帧,因此当您点击
leaveq
时,您正在执行以下操作:

mov rsp, rbp
pop rpb
现在
rsp
指向了
rbp
以前的位置。但是,您已经覆盖了该内存区域,因此当您执行
pop rbp
时,硬件基本上就是这样做的

mov rbp, [rsp]
add rsp,1
但是
[rsp]
现在有
0x41
。因此,这就是为什么您看到
rbp
被该值填充的原因


至于为什么
rip
没有像您预期的那样设置,这是因为
ret
正在将
rip
设置为
0x41
,然后在指令获取时生成异常(页面错误)。在这种情况下,我不会依赖GDB来展示正确的东西。您应该尝试用程序文本段中的有效地址覆盖返回值,您可能不会看到这种奇怪的行为

x32上EIP 0×4141崩溃的原因是,当程序将先前保存的EIP值从堆栈中弹出并返回EIP时,CPU会尝试在内存地址0×4141处执行指令,从而导致segfault。(它必须在执行课程之前获取页面)

现在,在x64执行期间,当程序将先前保存的RIP值弹出回RIP寄存器时,内核将尝试在内存地址0×4141处执行指令。首先,由于标准形式寻址,任何虚拟地址的位48到63必须是位47的副本(以类似于符号扩展的方式),否则处理器将引发异常。如果这不是问题-内核在调用页面错误处理程序之前进行额外检查,因为最大用户空间地址是0x00007FFFFFFFF

综上所述,在x32体系结构中,地址在没有任何“验证”的情况下传递给页面错误处理程序,页面错误处理程序尝试加载页面,从而触发内核发送程序SEGFULT,但x64没有做到这一点

测试它,用0×00004141覆盖RIP,您将看到期望值被放置在RIP中,因为内核通过预检查,然后调用页面错误处理程序,就像x32一样(当然,这会导致程序崩溃)。

由“kch”和“import os.boom.headshot”给出的答案不太正确

实际情况是,RET指令将弹出到RIP中的堆栈(0x4141)上的值包含处理器“非规范”地址范围内的地址。这会导致CPU生成一般保护故障(GPF)中断,而不是内核预检查生成的故障。GPF反过来触发内核在RIP实际更新之前报告分段错误,这就是您在GDB中看到的

大多数现代CPU仅提供48位地址范围,该地址范围在较高的
(gdb) break *0x00000000004005e9
(gdb) set disassemble-next-line on
(gdb) si
19 }
=> 0x00000000004005ee <vulnerable+130>:  c9     leave  
   0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x7fffffffda90   0x7fffffffda90
rsp            0x7fffffffd870   0x7fffffffd870
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ee 0x4005ee <vulnerable+130>

   0x00000000004005ee <vulnerable+130>:  c9     leave  
=> 0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x4141414141414141       0x4141414141414141
rsp            0x7fffffffda98   0x7fffffffda98
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ef 0x4005ef <vulnerable+131>
(gdb) si

Program received signal SIGSEGV, Segmentation fault.
   0x00000000004005ee <vulnerable+130>:  c9     leave  
=> 0x00000000004005ef <vulnerable+131>:  c3     ret    
(gdb) i r
rax            0x0      0
rbx            0x0      0
rcx            0x4141414141414141       4702111234474983745
rdx            0x414141 4276545
rsi            0x7fffffffe17a   140737488347514
rdi            0x7fffffffdb00   140737488345856
rbp            0x4141414141414141       0x4141414141414141
rsp            0x7fffffffda98   0x7fffffffda98
r8             0x1      1
r9             0x270    624
r10            0x6      6
r11            0x7ffff7b9fff0   140737349550064
r12            0x400410 4195344
r13            0x7fffffffdb90   140737488346000
r14            0x0      0
r15            0x0      0
rip            0x4005ef 0x4005ef <vulnerable+131>
(gdb) x/4x 0x7fffffffda90
0x7fffffffda90: 0x41414141      0x41414141      0x41414141      0x41414141
(gdb) x/4x $rsp          
0x7fffffffda98: 0x41414141      0x41414141      0x41414141      0x41414141
rip            0x4005ef 0x4005ef <vulnerable+131>
mov rsp, rbp
pop rpb
mov rbp, [rsp]
add rsp,1