gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 00:55:24 24 4
gpt4 key购买 nike

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

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

我的印象是 LEAVE 指令做了:

MOV (E)SP, (E)BP

POP (E)BP

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

MOV RBP, QWORD PTR [RSP]

我认为它只是通过在执行该指令前后观察所有寄存器的内容来做到这一点。 LEAVEQ 似乎只是 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;
}

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

调试 session 的摘录如下:

(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 之后中断。

(gdb) break *0x00000000004005e1

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

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

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

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

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

0x7fffffffda90: 0xffffdab0      0x00007fff      0x00400610      0x00000000

紧接在 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 设置为此地址:

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

但 RIP 显然是:

rip            0x4005ef 0x4005ef <vulnerable+131>

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

最佳答案

这两条指令完全按照您的期望进行操作。您已经用 0x41 覆盖了之前的堆栈帧,所以当您点击 leaveq 时,您正在执行此操作:

mov rsp, rbp
pop rpb

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

mov rbp, [rsp]
add rsp,1

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

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

关于linux - 64 位上的堆栈驻留缓冲区溢出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11506799/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com