DF-3083 / craft.py
#!/usr/bin/env python3 """DF-3083 craft (host): build a small clean ext2 image, then flip ONE byte of a directory entry's rec_len (16 -> 17, so rec_len % 4 != 0) in dir 'd'. Usage: craft.py [workdir] """ import os, struct, subprocess, sys def main(w='/tmp/opencode/e2work'): os.makedirs(w, exist_ok=True) img = os.path.join(w, 'mangle.img') for f in (img,): if os.path.exists(f): os.unlink(f) subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024', '-I', '128', '-O', '^metadata_csum', img, '8192'], check=True) for i in range(1, 6): with open(os.path.join(w, f'g{i}'), 'w') as f: f.write('x') cmds = os.path.join(w, 'mcmds.txt') with open(cmds, 'w') as f: f.write('mkdir d\ncd /d\n') for i in range(1, 6): f.write(f'write g{i} file{i}\n') subprocess.run(['debugfs', '-w', '-f', cmds, img], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.run(['e2fsck', '-fy', img], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) data = bytearray(open(img, 'rb').read()) BS = 1024 sb = bytes(data[1024:2048]) ipg = struct.unpack('<I', sb[40:44])[0] grp, idx = (12 - 1) // ipg, (12 - 1) % ipg # dir d is inode 12 base = 2048 + grp * 32 # group desc table (1k blocks) itbl = struct.unpack('<I', data[base + 8:base + 12])[0] ino = bytes(data[itbl * BS + idx * 128: itbl * BS + idx * 128 + 128]) dblk = struct.unpack('<I', ino[40:44])[0] * BS # i_block[0] o = 0 while o < BS: rec, nlen = struct.unpack('<HB', data[dblk + o + 4:dblk + o + 7]) name = bytes(data[dblk + o + 8:dblk + o + 8 + nlen]).decode() if name == 'file3': data[dblk + o + 4] = (rec + 1) & 0xff print(f'patched {name}: rec_len {rec} -> {rec + 1} (odd)') break if rec == 0: break o += rec open(img, 'wb').write(bytes(data)) print('OK:', img) if __name__ == '__main__': main(sys.argv[1] if len(sys.argv) > 1 else '/tmp/opencode/e2work') |