我正在学习一些安全相关的东西,现在我正在玩我自己的堆栈。我所做的应该是非常简单的,我甚至不试图执行堆栈,只是为了表明我可以控制64位系统上的指令指针。我已经关闭了我所知道的所有保护机制,只是为了能够使用它(NX-bit,ASLR,还使用-fno-stack-protector -z execstack编译)。 我对64位程序集没有那么多的经验,花了一些时间搜索和试验自己之后,我想知道是否有人能够解释我遇到的问题。在64位堆栈驻留缓冲区溢出?
我有一个程序(源代码如下),它只是将一个字符串复制到一个堆栈驻留缓冲区中,没有边界检查。但是,当我用一系列0x41覆盖时,我期望看到RIP被设置为0x4141414141414141,而我发现我的RBP被设置为这个值。我确实遇到了分段错误,但是在执行RET指令时,RIP不会更新为此(非法)值,即使RSP设置为合法值也是如此。我甚至在GDB中验证过,在RET指令之前,在RSP中有可读存储器包含一系列0x41。
我的印象是,leave指令做了下:
MOV(E)SP,(E)BP
POP(E)BP
但是在64位,在“ LEAVEQ”指令似乎做(类似):
MOV RBP,QWORD PTR [RSP]
我想它确实这只是从之前遵守所有寄存器的内容d执行此指令后。虽然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 <[email protected]>
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 <[email protected]>
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位上做了什么?总之,我在这里错过了什么?编译时我试图忽略编译器参数,看看它是否有任何区别,但似乎没有任何区别。
我在想知道leaveq如何“知道”释放先前分配的堆栈空间时发现了您的答案。你的解释清楚了。 SP更改为BP,删除任何堆栈分配,然后弹出一个新的BP,它在该函数启动时被弹出的位置。谢谢。 – suprjami 2014-09-13 15:05:42