mirror of
https://github.com/project-dorado/dorado-hd.git
synced 2026-10-11 21:10:01 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
Reference in New Issue
Block a user