feat(re): kernel module reconstruction and ZCSTFS/keyvault findings

- Add scripts/reconstruct_nk_modules.py to rebuild the kernel-only modules
  (zcstfs.dll, keyvault.dll, DwXfer.dll, zcblock.dll, zpartstream.dll) from
  NK.bin into the external corpus.
- Update the module inventory and API reference: 114/114 modules, 67,399
  functions decompiled, 4,236 exports.
- Correct the ZCP inventory and asset notes: the payload is a ZCSTFS volume
  (plaintext for the runtime), and marketplace keys are per-package AES keys
  unwrapped from an RSA-2048 blob by a device-provisioned keypack.
This commit is contained in:
John Smith
2026-09-11 03:26:21 -04:00
parent 9fceea6341
commit b8403621da
5 changed files with 348 additions and 5 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ shipped by Dorado-HD.
| NX header | `0x000`–`0x1F0` | u32 fields; magic `4e 58` ("NX") at `0x32` behind a small version word (`07 d2`/`07 d3`); SHA-1 + GUID fingerprints | | NX header | `0x000`–`0x1F0` | u32 fields; magic `4e 58` ("NX") at `0x32` behind a small version word (`07 d2`/`07 d3`); SHA-1 + GUID fingerprints |
| Authenticode signature | `0x1F0` (hdr[4]), len hdr[7] (≈0x1DE0–0x1DEF) | PKCS#7 SignedData: `Microsoft Zune Publisher` ← `Microsoft Home Entertainment PCA`, MS Time-Stamp PCA countersignature | | Authenticode signature | `0x1F0` (hdr[4]), len hdr[7] (≈0x1DE0–0x1DEF) | PKCS#7 SignedData: `Microsoft Zune Publisher` ← `Microsoft Home Entertainment PCA`, MS Time-Stamp PCA countersignature |
| Record manifest | `0x2800…` | `EXEC` records: lang `0x0409`, package GUID, `<App>.exe`, platform `Zune.v3.1`; `TITL`: UTF-16LE title + description | | Record manifest | `0x2800…` | `EXEC` records: lang `0x0409`, package GUID, `<App>.exe`, platform `Zune.v3.1`; `TITL`: UTF-16LE title + description |
| Payload | after manifest | AES-ECB encrypted (16-byte-aligned identical ciphertext blocks; unique block tail per package). Marketplace DRM keys were device-delivered and are unrecoverable. | | Payload | after manifest | A **ZCSTFS volume** (STFS-derived mini filesystem). Format version 1 (`runtimeZune.v3.1.zcp`) is plaintext; marketplace packages AES-ECB encrypt the volume with a per-package key that arrives RSA-2048-wrapped at header `0xF0` and is unwrapped on-device from a provisioned keypack. Keys are not derivable from the package; see `dorado-emu/docs/zcp-decryption.md`. |
**Verdict:** binaries and XNA content are not extractable. The manifest **Verdict:** binaries and XNA content are not extractable. The manifest
(facts only) is inventoried below; each app is re-implemented as (facts only) is inventoried below; each app is re-implemented as
+91
View File
@@ -3635,3 +3635,94 @@ metadata, not Microsoft source. Raw decompilation stays out-of-tree.
| `DllRegisterServer` | `0x41B580DC` | | `DllRegisterServer` | `0x41B580DC` |
| `DllUnregisterServer` | `0x41B580DC` | | `DllUnregisterServer` | `0x41B580DC` |
## `zcblock.dll` — 20 exports
| Export | VA |
|---|---|
| `DSK_Close` | `0xC0C11290` |
| `DSK_Deinit` | `0xC0C11278` |
| `DSK_IOControl` | `0xC0C11298` |
| `DSK_Init` | `0xC0C111E8` |
| `DSK_Open` | `0xC0C1128C` |
| `DSK_PowerDown` | `0xC0C11398` |
| `DSK_PowerUp` | `0xC0C11398` |
| `DSK_Read` | `0xC0C11398` |
| `DSK_Seek` | `0xC0C11398` |
| `DSK_Write` | `0xC0C11398` |
| `ZCB_Close` | `0xC0C11290` |
| `ZCB_Deinit` | `0xC0C11278` |
| `ZCB_IOControl` | `0xC0C11298` |
| `ZCB_Init` | `0xC0C111E8` |
| `ZCB_Open` | `0xC0C1128C` |
| `ZCB_PowerDown` | `0xC0C11398` |
| `ZCB_PowerUp` | `0xC0C11398` |
| `ZCB_Read` | `0xC0C11398` |
| `ZCB_Seek` | `0xC0C11398` |
| `ZCB_Write` | `0xC0C11398` |
## `zcstfs.dll` — 29 exports
| Export | VA |
|---|---|
| `FSD_CloseFile` | `0xC0C2D6B0` |
| `FSD_CreateDirectoryW` | `0xC0C2BA74` |
| `FSD_CreateFileW` | `0xC0C2CB38` |
| `FSD_DeleteAndRenameFileW` | `0xC0C2C60C` |
| `FSD_DeleteFileW` | `0xC0C2C134` |
| `FSD_DeviceIoControl` | `0xC0C2D504` |
| `FSD_FindClose` | `0xC0C2CA84` |
| `FSD_FindFirstFileW` | `0xC0C2C628` |
| `FSD_FindNextFileW` | `0xC0C2C944` |
| `FSD_FlushFileBuffers` | `0xC0C2D2D8` |
| `FSD_GetDiskFreeSpaceW` | `0xC0C2C58C` |
| `FSD_GetFileAttributesW` | `0xC0C2BD90` |
| `FSD_GetFileInformationByHandle` | `0xC0C2D230` |
| `FSD_GetFileSize` | `0xC0C2D198` |
| `FSD_GetFileTime` | `0xC0C2D32C` |
| `FSD_MountDisk` | `0xC0C2B590` |
| `FSD_MoveFileW` | `0xC0C2C2A8` |
| `FSD_Notify` | `0xC0C2C608` |
| `FSD_ReadFile` | `0xC0C2CD6C` |
| `FSD_ReadFileWithSeek` | `0xC0C2CE4C` |
| `FSD_RegisterFileSystemFunction` | `0xC0C2C60C` |
| `FSD_RemoveDirectoryW` | `0xC0C2BC00` |
| `FSD_SetEndOfFile` | `0xC0C2D490` |
| `FSD_SetFileAttributesW` | `0xC0C2BF74` |
| `FSD_SetFilePointer` | `0xC0C2D0A4` |
| `FSD_SetFileTime` | `0xC0C2D3C0` |
| `FSD_UnmountDisk` | `0xC0C2B8B0` |
| `FSD_WriteFile` | `0xC0C2CF38` |
| `FSD_WriteFileWithSeek` | `0xC0C2CFE4` |
## `keyvault.dll` — 5 exports
| Export | VA |
|---|---|
| `KEY_Close` | `0xC0B36BA4` |
| `KEY_Deinit` | `0xC0B36B28` |
| `KEY_IOControl` | `0xC0B3AA88` |
| `KEY_Init` | `0xC0B36B74` |
| `KEY_Open` | `0xC0B36BA0` |
## `zpartstream.dll` — 8 exports
| Export | VA |
|---|---|
| `Close` | `0xC0AE14DC` |
| `Deinit` | `0xC0AE12D0` |
| `IOControl` | `0xC0AE1300` |
| `Init` | `0xC0AE112C` |
| `Open` | `0xC0AE1068` |
| `Read` | `0xC0AE1308` |
| `Seek` | `0xC0AE13E8` |
| `Write` | `0xC0AE1378` |
## `DwXfer.dll` — 5 exports
| Export | VA |
|---|---|
| `DWX_Close` | `0xC0733F4C` |
| `DWX_Deinit` | `0xC0733F38` |
| `DWX_IOControl` | `0xC07342AC` |
| `DWX_Init` | `0xC07336E0` |
| `DWX_Open` | `0xC0733F48` |
+2 -2
View File
@@ -21,7 +21,7 @@ inventory only.
| `nvmm_h264dec.axf`, `nvmm_mpeg4dec.axf`, `nvmm_adtsdec.axf`, `nvmm_jpegdec.axf`, `nvmm_jpegenc.axf` | Tegra APX 2600 codec firmware images (H.264, MPEG-4, AAC-ADTS, JPEG decode/encode). | | `nvmm_h264dec.axf`, `nvmm_mpeg4dec.axf`, `nvmm_adtsdec.axf`, `nvmm_jpegdec.axf`, `nvmm_jpegenc.axf` | Tegra APX 2600 codec firmware images (H.264, MPEG-4, AAC-ADTS, JPEG decode/encode). |
| `nvddk_audiomixer_core.axf`, `nvrm_avp.axf` | Audio-mixer core and the Tegra AVP (audio/video processor) firmware. | | `nvddk_audiomixer_core.axf`, `nvrm_avp.axf` | Audio-mixer core and the Tegra AVP (audio/video processor) firmware. |
| `msadpcm.dll` | Microsoft ADPCM codec (WinCE). | | `msadpcm.dll` | Microsoft ADPCM codec (WinCE). |
| `runtimeZune.v3.1.zcp` | The **XNA Game Studio 3.1 runtime** package (4.3 MB). NX container flagged `encrypted = True` with no readable manifest — i.e. marketplace DRM (AES-ECB); it is the same container family handled by `dorado-emu/Dorado.Containers`. | | `runtimeZune.v3.1.zcp` | The **XNA Game Studio 3.1 runtime** package (4.3 MB). NX format version 1: a **plaintext ZCSTFS volume** holding ten runtime assemblies. It parses and extracts with no keys (`dorado-emu` M1.5). |
| `TVOut.exe` | Small Windows CE ARM PE (TV-out helper). | | `TVOut.exe` | Small Windows CE ARM PE (TV-out helper). |
## Networking / radio firmware ## Networking / radio firmware
@@ -51,7 +51,7 @@ inventory only.
## What this pass did not recover ## What this pass did not recover
- `runtimeZune.v3.1.zcp` payload contents (marketplace DRM; no public key). - Device-provisioned keypacks (the RSA-2048 private key that unwraps marketplace `.zcp` content keys); not present in the firmware cab or PC installer.
- `zuneroots.p7b` (PGP-encrypted). - `zuneroots.p7b` (PGP-encrypted).
- Field semantics of the `XuiTouchSettings` struct (see - Field semantics of the `XuiTouchSettings` struct (see
`zune-hd-touch-settings.md` §4 — no PDB/symbol data exists in the corpus). `zune-hd-touch-settings.md` §4 — no PDB/symbol data exists in the corpus).
+13 -2
View File
@@ -4,14 +4,17 @@ Generated from the out-of-tree `zune-hd-disassembly/` corpus by
`scripts/ghidra_corpus.py` (rizin symbol recovery + full Ghidra decompilation). `scripts/ghidra_corpus.py` (rizin symbol recovery + full Ghidra decompilation).
Raw decompilation/symbols stay external; **this table is synthesized metadata only.** Raw decompilation/symbols stay external; **this table is synthesized metadata only.**
**Coverage:** 109/109 reconstructed ARM32 PE modules analysed; 66,598 functions **Coverage:** 114/114 reconstructed ARM32 PE modules analysed; 67,399 functions
decompiled; 4,169 named exports and 10,525 imports recovered. decompiled; 4,236 named exports and 10,731 imports recovered. The kernel-only
modules (`zcstfs.dll`, `keyvault.dll`, `DwXfer.dll`, `zcblock.dll`,
`zpartstream.dll`) are rebuilt from `NK.bin` by `scripts/reconstruct_nk_modules.py`.
| Module | Bytes | Exports | Functions | Role | | Module | Bytes | Exports | Functions | Role |
|---|---:|---:|---:|---| |---|---:|---:|---:|---|
| `AAXSDKWin.dll` | 113,152 | 74 | 360 | Audible/AAX audio SDK (spoken-word playback) | | `AAXSDKWin.dll` | 113,152 | 74 | 360 | Audible/AAX audio SDK (spoken-word playback) |
| `ASAFilter.dll` | 39,936 | 5 | 193 | System DLL | | `ASAFilter.dll` | 39,936 | 5 | 193 | System DLL |
| `Dw.exe` | 60,928 | 0 | 260 | Native executable | | `Dw.exe` | 60,928 | 0 | 260 | Native executable |
| `DwXfer.dll` | 19,456 | 5 | 108 | Dump/transfer driver (`DWX1:`); keyvault routes secure transfers through it |
| `IECEExt.dll` | 11,776 | 43 | 117 | System DLL | | `IECEExt.dll` | 11,776 | 43 | 117 | System DLL |
| `MtpSvc.dll` | 375,296 | 5 | 1,585 | MTP device service (USB media transfer) | | `MtpSvc.dll` | 375,296 | 5 | 1,585 | MTP device service (USB media transfer) |
| `ZWmtStreamer.dll` | 390,144 | 5 | 1,485 | WMA/WMV streaming (PlayReady/WMDRM) | | `ZWmtStreamer.dll` | 390,144 | 5 | 1,485 | WMA/WMV streaming (PlayReady/WMDRM) |
@@ -38,6 +41,7 @@ decompiled; 4,169 named exports and 10,525 imports recovered.
| `iphlpapi.dll` | 51,200 | 62 | 270 | System DLL | | `iphlpapi.dll` | 51,200 | 62 | 270 | System DLL |
| `jscript.dll` | 709,120 | 4 | 2,010 | JScript engine (zie.exe) | | `jscript.dll` | 709,120 | 4 | 2,010 | JScript engine (zie.exe) |
| `jsproxy.dll` | 18,432 | 92 | 85 | System DLL | | `jsproxy.dll` | 18,432 | 92 | 85 | System DLL |
| `keyvault.dll` | 118,272 | 5 | 358 | Key/keypack driver (`KEY1:`): RSA key import, `.zcp` content-key unwrap, provisioning |
| `libEGL.dll` | 74,240 | 36 | 320 | OpenGL ES / EGL stack | | `libEGL.dll` | 74,240 | 36 | 320 | OpenGL ES / EGL stack |
| `libGLESv2.dll` | 263,680 | 143 | 931 | OpenGL ES / EGL stack | | `libGLESv2.dll` | 263,680 | 143 | 931 | OpenGL ES / EGL stack |
| `libKD.dll` | 84,992 | 173 | 544 | Kernel/user debug + crypto helpers | | `libKD.dll` | 84,992 | 173 | 544 | Kernel/user debug + crypto helpers |
@@ -101,6 +105,8 @@ decompiled; 4,169 named exports and 10,525 imports recovered.
| `xuidll.dll` | 465,408 | 529 | 2,288 | Xbox UI (XUI) runtime ported to WinCE — layout, animation, touch dispatch | | `xuidll.dll` | 465,408 | 529 | 2,288 | Xbox UI (XUI) runtime ported to WinCE — layout, animation, touch dispatch |
| `zam_serv.dll` | 93,184 | 5 | 532 | Zune Application Manager (XNA mini-app lifecycles) | | `zam_serv.dll` | 93,184 | 5 | 532 | Zune Application Manager (XNA mini-app lifecycles) |
| `zcab.dll` | 50,176 | 12 | 252 | CAB decompression helper | | `zcab.dll` | 50,176 | 12 | 252 | CAB decompression helper |
| `zcblock.dll` | 5,120 | 20 | 29 | Block-device shim exposing ZCab volumes (`ZCB_*`) |
| `zcstfs.dll` | 77,312 | 29 | 269 | ZCSTFS volume driver — mounts `.zcp` at `\gametitle` / `\gamert` / `\xnaa` |
| `zconfig_serv.dll` | 35,328 | 5 | 207 | Device configuration service | | `zconfig_serv.dll` | 35,328 | 5 | 207 | Device configuration service |
| `zcontent_serv.dll` | 56,832 | 5 | 228 | Media library indexing + metadata/search service | | `zcontent_serv.dll` | 56,832 | 5 | 228 | Media library indexing + metadata/search service |
| `zcredentials_serv.dll` | 15,360 | 5 | 118 | Credential/DRM key service | | `zcredentials_serv.dll` | 15,360 | 5 | 118 | Credential/DRM key service |
@@ -114,6 +120,7 @@ decompiled; 4,169 named exports and 10,525 imports recovered.
| `zmassive.dll` | 87,552 | 19 | 572 | Massive (audio/marketplace) client library | | `zmassive.dll` | 87,552 | 19 | 572 | Massive (audio/marketplace) client library |
| `zmedia_serv.dll` | 273,920 | 5 | 1,221 | Core media playback engine (pipeline, queue, EQ) | | `zmedia_serv.dll` | 273,920 | 5 | 1,221 | Core media playback engine (pipeline, queue, EQ) |
| `znet_serv.dll` | 492,544 | 5 | 2,046 | Networking service (download, marketplace transport) | | `znet_serv.dll` | 492,544 | 5 | 2,046 | Networking service (download, marketplace transport) |
| `zpartstream.dll` | 5,632 | 8 | 38 | Partition stream helper |
| `zrender.dll` | 211,968 | 3 | 614 | High-level scene/texture rendering engine | | `zrender.dll` | 211,968 | 3 | 614 | High-level scene/texture rendering engine |
| `zserial.dll` | 19,456 | 18 | 114 | Serial/device identity helper | | `zserial.dll` | 19,456 | 18 | 114 | Serial/device identity helper |
| `zsplash.exe` | 18,944 | 0 | 107 | Animated startup bootsplash | | `zsplash.exe` | 18,944 | 0 | 107 | Animated startup bootsplash |
@@ -129,6 +136,10 @@ decompiled; 4,169 named exports and 10,525 imports recovered.
- **zd3d.dll** (237 fns, 62 exports) — D3DX/`D3DXMatrix*` helpers over Direct3D-Mobile. - **zd3d.dll** (237 fns, 62 exports) — D3DX/`D3DXMatrix*` helpers over Direct3D-Mobile.
- **zmedia_serv.dll** (1,221 fns) / **zhud_serv.dll** (1,167 fns) — playback + HUD. - **zmedia_serv.dll** (1,221 fns) / **zhud_serv.dll** (1,167 fns) — playback + HUD.
- **znet_serv.dll** (2,046 fns) — networking/marketplace transport. - **znet_serv.dll** (2,046 fns) — networking/marketplace transport.
- **zcstfs.dll** (269 fns) — the ZCSTFS filesystem: `FSD_MountDisk`, hash/chain
tables, `0x4000`-byte blocks, AES-256 volume decrypt via `KEY1:`.
- **keyvault.dll** (358 fns) — keypacks (`KEY_IOControl`), RSA private-key import
and content-key unwrap (`0x220054` / `0x220020`), fused provisioning.
- **shdoclc.dll** — resource-only (0 code functions). - **shdoclc.dll** — resource-only (0 code functions).
> Raw per-function decompiled C and the symbol/string dumps live only in the external > Raw per-function decompiled C and the symbol/string dumps live only in the external
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""Reconstruct kernel-only PE modules from the Zune HD NK.bin image.
The main ``disassemble_zune_hd.py`` pipeline rebuilds modules from the
``EXT.bin`` ROM assets. Some high-value modules live only in the kernel image
(``NK.bin``) and are not in the ROMHDR module table of ``EXT.bin``:
``zcstfs.dll`` (the ZCSTFS volume driver), ``keyvault.dll`` (the key/keypack
driver), ``zcblock.dll``, ``zpartstream.dll`` and ``DwXfer.dll``.
This script flattens ``NK.bin`` (B000FF), locates the named module entries in
the kernel module table, reconstructs an importable ARM32 PE for each, and
writes it to the external corpus ``modules/`` directory. Output stays in the
git-ignored corpus; never commit it.
Usage:
python3 scripts/reconstruct_nk_modules.py
python3 scripts/reconstruct_nk_modules.py --modules zcstfs.dll,keyvault.dll
python3 scripts/reconstruct_nk_modules.py --list
"""
import argparse
import os
import struct
import sys
BASE_DIR = os.environ.get(
"ZUNE_HD_DISASSEMBLY_DIR",
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "zune-hd-disassembly")),
)
NK_PATH = os.path.join(BASE_DIR, "firmware", "NK.bin")
MODULES_DIR = os.path.join(BASE_DIR, "modules")
DEFAULT_MODULES = [
"zcstfs.dll",
"keyvault.dll",
"zcblock.dll",
"zpartstream.dll",
"DwXfer.dll",
]
HASH = 0x7FFFFFFF
def flatten_nk(path):
"""Return (image_start, raw) for a B000FF NK image."""
with open(path, "rb") as f:
if f.read(7) != b"B000FF\n":
raise ValueError("NK.bin is not a B000FF image")
image_start, image_length = struct.unpack("<II", f.read(8))
raw = bytearray(image_length)
while True:
header = f.read(12)
if len(header) < 12:
break
address, length, checksum = struct.unpack("<III", header)
if address == 0 and checksum == 0:
break
offset = address - image_start
raw[offset : offset + length] = f.read(length)
return image_start, raw
def find_module_entries(raw, image_start):
"""Index the kernel module table by module name."""
entries = {}
for offset in range(len(raw) - 32):
attrs, ft_low, ft_high, size, name_ptr, e32_off, o32_off, load_off = struct.unpack_from(
"<IIIIIIII", raw, offset
)
name_offset = (name_ptr & HASH) - image_start
if not (0 <= name_offset < len(raw)):
continue
end = raw.find(b"\0", name_offset)
name = raw[name_offset:end]
if not (1 <= len(name) < 64) or not all(32 <= c < 127 for c in name):
continue
decoded = name.decode("ascii")
if not (decoded.endswith(".dll") or decoded.endswith(".exe")):
continue
e32_rva = (e32_off & HASH) - image_start
o32_rva = (o32_off & HASH) - image_start
if not (0 <= e32_rva < len(raw) - 28 and 0 <= o32_rva < len(raw) - 24 and size < 0x800000):
continue
entries.setdefault(decoded, (offset, e32_off, o32_off, ft_low))
return entries
def reconstruct_pe(raw, image_start, e32_off, o32_off, ft_low):
"""Rebuild a standard PE from a Windows CE e32_rom/o32_rom module pair."""
e32_rva = (e32_off & HASH) - image_start
(
objcnt,
imgflags,
entryrva,
vbase,
subsysmaj,
subsysmin,
stackmax,
vsize,
subsys,
_,
) = struct.unpack_from("<HHIIHHIIHH", raw, e32_rva)
o32_rva = (o32_off & HASH) - image_start
sections = []
for index in range(objcnt):
vsz, rva, psz, dataptr, _, flags = struct.unpack_from(
"<IIIIII", raw, o32_rva + index * 24
)
data_offset = (dataptr & HASH) - image_start
sections.append((vsz, rva, psz, flags, bytes(raw[data_offset : data_offset + psz])))
raw_dirs = []
for index in range(1, 9):
rva, size = struct.unpack_from("<II", raw, e32_rva + 28 + index * 8)
raw_dirs.append((rva, size))
data_dirs = [
raw_dirs[0],
raw_dirs[1],
raw_dirs[2],
raw_dirs[3],
(0, 0),
raw_dirs[5],
raw_dirs[6],
]
while len(data_dirs) < 16:
data_dirs.append((0, 0))
valid_dirs = []
for rva, size in data_dirs:
if rva == 0 or size == 0:
valid_dirs.append((0, 0))
continue
in_section = any(s[1] <= rva < s[1] + s[0] for s in sections)
valid_dirs.append((rva, size) if in_section else (0, 0))
data_dirs = valid_dirs
dos_header = bytearray(0x80)
dos_header[0:2] = b"MZ"
struct.pack_into("<I", dos_header, 0x3C, 0x80)
file_header = struct.pack(
"<4sHHIIIHH", b"PE\0\0", 0x01C2, objcnt, ft_low, 0, 0, 224, imgflags
)
code_size = sections[0][2] if sections else 0
init_data_size = sum(s[2] for s in sections[1:]) if len(sections) > 1 else 0
optional_fixed = struct.pack(
"<HBBIIIIIIIIIHHHHHHIIIIHHIIIIII",
0x010B,
8,
0,
code_size,
init_data_size,
0,
entryrva,
sections[0][1] if sections else 0,
sections[1][1] if objcnt > 1 else 0,
vbase,
0x1000,
0x200,
subsysmaj,
subsysmin,
0,
0,
subsysmaj,
subsysmin,
0,
vsize,
0x400,
0,
subsys,
0,
stackmax,
0x1000,
0x100000,
0x1000,
0,
16,
)
optional = optional_fixed + b"".join(struct.pack("<II", r, s) for r, s in data_dirs)
section_names = [b".text\0\0\0", b".data\0\0\0", b".rdata\0\0", b".reloc\0\0", b".rsrc\0\0\0"]
section_headers = bytearray()
file_offset = 0x400
placed = []
for index, (vsz, rva, psz, flags, data) in enumerate(sections):
name = section_names[index] if index < len(section_names) else f".sec{index}\0\0".encode()
aligned = (psz + 0x1FF) & ~0x1FF
section_headers += struct.pack(
"<8sIIIIIIHHI", name, vsz, rva, aligned, file_offset, 0, 0, 0, 0, flags
)
placed.append((file_offset, data))
file_offset += aligned
buffer = bytearray(file_offset)
headers = dos_header + file_header + optional + section_headers
buffer[0 : len(headers)] = headers
for offset, data in placed:
buffer[offset : offset + len(data)] = data
return bytes(buffer)
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--modules", help="Comma-separated module names (default: the built-in list).")
parser.add_argument("--list", action="store_true", help="List the requested modules without writing files.")
args = parser.parse_args()
if not os.path.isfile(NK_PATH):
print(f"[!] NK image not found: {NK_PATH}", file=sys.stderr)
return 1
image_start, raw = flatten_nk(NK_PATH)
entries = find_module_entries(raw, image_start)
requested = [m.strip() for m in args.modules.split(",")] if args.modules else DEFAULT_MODULES
os.makedirs(MODULES_DIR, exist_ok=True)
written = 0
for module in requested:
if module not in entries:
print(f"[!] {module}: not found in the kernel module table")
continue
_, e32_off, o32_off, ft_low = entries[module]
if args.list:
print(f"[*] {module}: kernel module present")
continue
pe = reconstruct_pe(raw, image_start, e32_off, o32_off, ft_low)
destination = os.path.join(MODULES_DIR, module)
with open(destination, "wb") as handle:
handle.write(pe)
written += 1
print(f"[*] {module}: {len(pe):,} bytes -> {destination}")
if not args.list:
print(f"[*] Reconstructed {written} kernel module(s) into the external corpus.")
return 0
if __name__ == "__main__":
raise SystemExit(main())