64位堆栈驻留缓冲区溢出? [英] Stack resident buffer overflow on 64-bit?

查看:25
本文介绍了64位堆栈驻留缓冲区溢出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm studying some security related things and right now I'm playing around with my own stack. What I'm doing should be very trivial, I'm not even trying to execute the stack, simply to show that I can get control over the instruction pointer on my 64-bit system. I have turned off all protection mechanisms I'm aware of just to be able to play with it (NX-bit, ASLR, also compiling with -fno-stack-protector -z execstack). I don't have that much experience with 64-bit assembly and after spending some time searching and experimenting myself I'm wondering if anyone could shed some light on an issue I'm experiencing.

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

I have a program (source code below) which simply copies a string into a stack resident buffer with no bounds checking. However when I overwrite with a series of 0x41 I'm expecting to see the RIP be set to 0x4141414141414141, instead I'm finding that my RBP gets set to this value. I do get a segmentation fault, but RIP does not get updated to this (illegal) value at the execution of the RET instruction, even if RSP is set to a legal value. I have even verified in GDB that there is readlable memory containing a series of 0x41's at RSP immediately prior to the RET instruction.

我的印象是 LEAVE 指令做了:

I was under the impression that the LEAVE instruction did:

MOV (E)SP, (E)BP

MOV (E)SP, (E)BP

POP (E)BP

但是在 64 位上,LEAVEQ"指令似乎可以(类似于):

However on 64-bit, the "LEAVEQ" instruction seems to do (similar to):

MOV RBP,QWORD PTR [RSP]

MOV RBP, QWORD PTR [RSP]

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

I'm thinking it does this simply from observing the contents of all registers before and after execution of this instruction. LEAVEQ seems to be just a context dependent name of the RET instruction though (which GDB's disassembler gives it), as it is still just a 0xC9.

RET 指令似乎对 RBP 寄存器做了一些事情,也许是取消引用它?我的印象是 RET 做了(类似于):

And the RET instruction seems to do something with the RBP register, perhaps dereferencing it? I was under the impression that RET did (similar to):

MOV RIP,QWORD PTR [RSP]

MOV RIP, QWORD PTR [RSP]

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

However like I mentioned, it seems to dereference RBP, I'm thinking it does this because I get a segmentation fault when no other register seems to contain an illegal value.

程序源代码:

#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
",buffer);

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

    return 0;
}    

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

    return 0;
}

for 循环只是用来用 0x42 填充缓冲区的合法部分,这使得在溢出之前在调试器中很容易看到它在哪里.

The for loop is just there to fill the legal part of the buffer with 0x42, which makes it easy to see in the debugger where it is, before the overflow.

调试会话摘录如下:

(gdb) disas vulnerable
Dump of assembler code for function vulnerable:
   0x000000000040056c <+0>:     push   rbp
   0x000000000040056d <+1>:     mov    rbp,rsp
   0x0000000000400570 <+4>:     sub    rsp,0x220
   0x0000000000400577 <+11>:    mov    DWORD PTR [rbp-0x214],edi
   0x000000000040057d <+17>:    mov    QWORD PTR [rbp-0x220],rsi
   0x0000000000400584 <+24>:    mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040058b <+31>:    jmp    0x40059e <vulnerable+50>
   0x000000000040058d <+33>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400590 <+36>:    cdqe   
   0x0000000000400592 <+38>:    mov    BYTE PTR [rbp+rax*1-0x210],0x42
   0x000000000040059a <+46>:    add    DWORD PTR [rbp-0x4],0x1
   0x000000000040059e <+50>:    cmp    DWORD PTR [rbp-0x4],0x1ff
   0x00000000004005a5 <+57>:    jle    0x40058d <vulnerable+33>
   0x00000000004005a7 <+59>:    lea    rax,[rbp-0x210]
   0x00000000004005ae <+66>:    mov    rsi,rax
   0x00000000004005b1 <+69>:    mov    edi,0x40070c
   0x00000000004005b6 <+74>:    mov    eax,0x0
   0x00000000004005bb <+79>:    call   0x4003d8 <printf@plt>
   0x00000000004005c0 <+84>:    cmp    DWORD PTR [rbp-0x214],0x1
   0x00000000004005c7 <+91>:    jle    0x4005e9 <vulnerable+125>
   0x00000000004005c9 <+93>:    mov    rax,QWORD PTR [rbp-0x220]
   0x00000000004005d0 <+100>:   add    rax,0x8
   0x00000000004005d4 <+104>:   mov    rdx,QWORD PTR [rax]
   0x00000000004005d7 <+107>:   lea    rax,[rbp-0x210]
   0x00000000004005de <+114>:   mov    rsi,rdx
   0x00000000004005e1 <+117>:   mov    rdi,rax
   0x00000000004005e4 <+120>:   call   0x4003f8 <strcpy@plt>
   0x00000000004005e9 <+125>:   mov    eax,0x0
   0x00000000004005ee <+130>:   leave  
   0x00000000004005ef <+131>:   ret    

我在调用 strcpy() 之前就中断了,但是在缓冲区被 0x42 填充之后.

I break right before the call to strcpy(), but after the buffer has been filled with 0x42's.

(gdb) break *0x00000000004005e1

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

The program is executed with 650 0x41's as argument, this should be plenty to overwrite the return address on the stack.

(gdb) run `perl -e 'print "A"x650'`

我在内存中搜索返回地址 0x00400610(我是通过查看 main 的反汇编找到的).

I search the memory for the return address 0x00400610 (which I found from looking at the disassembly of main).

(gdb) find $rsp, +1024, 0x00400610
0x7fffffffda98
1 pattern found.

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

I examine the memory with x/200x and get a nice overview which I have omitted here because of its size, but I can clearly see the 0x42 that denote the legal size of the buffer, and the return address.

0x7fffffffda90: 0xffffdab0      0x00007fff      0x00400610      0x00000000

strcpy() 之后的新断点:

New breakpoint just after strcpy():

(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>

我确认返回地址已被覆盖,并且我应该期望看到 RIP 被设置为该地址:

I verify that the return address has been overwritten and I should have expected to see RIP get set to this address:

(gdb) x/4x 0x7fffffffda90
0x7fffffffda90: 0x41414141      0x41414141      0x41414141      0x41414141
(gdb) x/4x $rsp          
0x7fffffffda98: 0x41414141      0x41414141      0x41414141      0x41414141

然而 RIP 显然是:

Yet RIP is clearly:

rip            0x4005ef 0x4005ef <vulnerable+131>

为什么 RIP 没有像我预期的那样更新?LEAVEQ 和 RETQ 在 64 位上真正做了什么?简而言之,我在这里缺少什么?我试图在编译时省略编译器参数只是为了看看它是否有任何区别,它似乎没有任何区别.

Why has not RIP gotten updated as I'm expecting? What does LEAVEQ and RETQ really do on 64-bit? In short, what am I missing here? I have tried to omit the compiler arguments when compiling just to see if it makes any difference, it doesn't seem to make any difference.

推荐答案

这两条指令完全符合您的预期.你已经用 0x41 覆盖了之前的堆栈帧,所以当你点击 leaveq 时,你正在这样做:

Those two instructions are doing exactly what you expect them to do. You have overwritten the previous stack frame with 0x41's so when you hit the leaveq, you are doing this:

mov rsp, rbp
pop rpb

现在 rsp 指向 rbp 之前的位置.但是,您已经覆盖了该内存区域,因此当您执行 pop rbp 时,硬件实际上是在执行此操作

Now rsp points to where rbp did before. However, you have overwritten that region of memory, so when you do the pop rbp, the hardware is essentially doing this

mov rbp, [rsp]
add rsp,1

但是 [rsp] 现在有 0x41 的.这就是为什么您会看到 rbp 被该值填充.

But [rsp] now has 0x41's. So this is why you're seeing rbp get filled with that value.

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

As for why rip isn't getting set like you expect, it's because ret is setting the rip to 0x41 and then generating an exception (page fault) on the instruction fetch. I wouldn't rely on GDB to show the right thing in this case. You should try overwriting the return value with a valid address within the program's text segment and you likely won't see this weird behavior.

这篇关于64位堆栈驻留缓冲区溢出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆