当前位置: 首页 > 面试题库 >

堆栈常驻缓冲区在64位上溢出?

仲智
2023-03-14
问题内容

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

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

我给人的印象是LEAVE指令可以做到:

MOV(E)SP,(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填充缓冲区的合法部分,这使得在溢出之前在调试器中很容易看到它的位置。

调试会话的摘录如下:

(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(通过查看主程序的反汇编找到了该地址)。

(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没有像您期望的那样进行设置,那是因为ret将设置rip0x41,然后在指令提取时生成异常(页面错误)。在这种情况下,我不会依靠GDB来显示正确的内容。您应该尝试使用程序文本段中的有效地址覆盖返回值,并且您可能不会看到这种奇怪的行为。



 类似资料:
  • 虚拟机安装:Ubuntu 12.04(x86) 这个帖子是最简单的漏洞开发教程系列,在互联网上你可以找到很多关于它的文章。尽管它丰富和熟悉,我更喜欢自己写博客文章,因为它将作为我未来许多职位的先决条件! 什么是缓冲区溢出? 将源缓冲区复制到目标缓冲区可能导致溢出 1、源字符串长度大于目标字符串长度。 2、不进行大小检查。 缓冲区溢出有两种类型: 1、基于堆栈的缓冲区溢出 - 这里的目标缓冲区位于堆

  • 缓冲区溢出的漏洞随着冯·诺依曼 1 构架的出 现就已经开始出现了。 在1988年随着莫里斯互联网蠕虫的广泛传播他们开始声名狼藉。不幸的是, 同样的这种攻击一直持续到今天。 到目前为止,大部分的缓冲区溢出的攻击都是基于摧毁栈的方式。 大部分现代计算机系统使用栈来给进程传递参数并且存储局部变量。 栈是一种在进程映象内存的高地址内的后进先出(LIFO)的缓冲区。 当程序调用一个函数时一个新的“栈帧”会被

  • 当程序试图在临时数据存储区域(缓冲区)中存储比预期要容纳的更多数据时,会出现缓冲区溢出。由于创建缓冲区以包含有限数量的数据,因此额外信息可能溢出到相邻缓冲区中,从而破坏其中保存的有效数据。 示例 这是缓冲区溢出的经典示例。它演示了一个简单的缓冲区溢出,它是由第一个依赖外部数据来控制其行为的场景引起的。无法限制用户输入的数据量,程序的行为取决于用户放入的字符数。 动手实践 第1步 - 我们需要使用姓

  • 我正在尝试正确地使用ByteBuffer和BigEndian字节顺序格式。。 我有几个字段,我试图把它存储在Cassandra数据库之前放在一个单一的ByteBuffer中。 我将要写入Cassandra的字节数组由三个字节数组组成,如下所述- 现在,我需要快速压缩attributeValue数据,然后再将其存储在Cassandra中- 现在,我将编写,和snappy压缩的一起组成一个单字节数组,

  • (使用Java 15.0+) 我正在用这些实现一个堆栈 如何检查堆栈是否下溢?从Overflow boolean变量中,我们知道如果一个数字不能用8位来表示,就会导致溢出。但是,如果数字不能以这种方式表示,我们如何检查呢?我还认为应该有更多的情况下堆栈溢出,像jumpz或jumpn导致通过指令的无限循环。

  • 问题内容: 现在,Stack Overflow使用redis,它们是否以相同的方式处理缓存失效?即散列到查询字符串+名称的身份列表(我想这个名称是某种用途或对象类型的名称)。 也许他们然后直接通过id(从一堆数据库索引中绕过,而是使用效率更高的聚集索引)直接从缓存中检索缺少的单个项。那会很聪明(杰夫提到的补液?)。 现在,我正在努力寻找一种简洁地解决所有问题的方法。在我自己进行初次切割之前,是否有