diff --git a/README.md b/README.md index 3c2215b..d481381 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ lives in [`web/`](web/). | Kind | Description | | --- | --- | -| `Exe` | Crackproof-protected executable (PE32+ and PE32). | +| `NativeExe` | Crackproof-protected native executable (PE32+ and PE32). | +| `ManagedExe` | Protected .NET executable (has a CLR data directory). | | `NativeDll` | Protected native (unmanaged) DLL. | | `ManagedDll` | Protected .NET assembly (has a CLR data directory). | | `._` companion | Stub + external encrypted payload layout, spliced automatically. | diff --git a/docs/design.md b/docs/design.md index 15a35f0..eec8d5b 100644 --- a/docs/design.md +++ b/docs/design.md @@ -48,17 +48,18 @@ src/ Detection is content-based (`unpacker::detect`), never extension-based: the key table is derived from the file header and checked against the format -magic, then the PE characteristics classify the input as EXE, native DLL, or -managed DLL. +magic, then the PE characteristics classify the input as EXE or DLL and the +CLR data directory splits each into native vs managed (`NativeExe` / +`ManagedExe` / `NativeDll` / `ManagedDll`). `unpack_auto` then dispatches: -- `Exe` → the EXE pipeline (handles both PE32+ and PE32). Managed EXEs take - the same path: their import-string table is null (imports are the CLR - bootstrap stub), the entry point comes from the protected header (the - config block stores 0 for managed images), and the COR20 header, BSJB - metadata stream, and CLR resources are restored verbatim from the protected - file, mirroring the managed-DLL restore. +- `NativeExe` / `ManagedExe` → the EXE pipeline (handles both PE32+ and + PE32). Managed EXEs take the same path: their import-string table is null + (imports are the CLR bootstrap stub), the entry point comes from the + protected header (the config block stores 0 for managed images), and the + COR20 header, BSJB metadata stream, and CLR resources are restored verbatim + from the protected file, mirroring the managed-DLL restore. - `NativeDll` / `ManagedDll` → the DLL pipeline first; on failure, the EXE pipeline as a fallback. Two DLL layouts exist in the wild: an older layout the DLL pipeline parses, and a newer one that protects DLLs with the diff --git a/src/unpacker/mod.rs b/src/unpacker/mod.rs index e4b9c0a..e133173 100644 --- a/src/unpacker/mod.rs +++ b/src/unpacker/mod.rs @@ -55,7 +55,8 @@ pub(crate) fn is_supported_magic(magic: u32) -> bool { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Kind { - Exe, + NativeExe, + ManagedExe, NativeDll, ManagedDll, } @@ -109,7 +110,7 @@ fn key_table(input: &[u8]) -> Option<[u32; 8]> { /// /// Routing: `keys[1]` must be the Crackproof magic (`KONN`). /// The PE IMAGE_FILE_DLL characteristic distinguishes EXE vs DLL; -/// the CLR data-directory RVA further distinguishes ManagedDll from NativeDll. +/// the CLR data-directory RVA distinguishes managed from native for both. pub fn detect(input: &[u8]) -> Option { let keys = key_table(input)?; let magic = keys[1]; @@ -132,21 +133,15 @@ pub fn detect(input: &[u8]) -> Option { let chars = (input[chars_offset as usize] as u16) | ((input[chars_offset as usize + 1] as u16) << 8); let is_dll = (chars & 0x2000) != 0; - if !is_dll { - return Some(Detected { - kind: Kind::Exe, - magic, - }); - } - // DLL: determine managed vs native via CLR data-directory RVA. + // Managed vs native via the CLR data-directory RVA. // peOff + 24 = start of optional header. The data directories start at a // magic-dependent offset within it: PE32 (0x10B) at +96, PE32+ (0x20B) at // +112. Using the PE32+ offset on a PE32 image reads the wrong dword and - // can mis-flag a native DLL as managed. + // can mis-flag a native image as managed. // // `get_u16`/`get_u32` index unchecked, so every read past the already- // checked Characteristics word must be bounds-checked first: a truncated - // DLL (e.g. `e_lfanew` pointing at len-24) would otherwise panic here, + // file (e.g. `e_lfanew` pointing at len-24) would otherwise panic here, // and this detector runs on the folder scan threads where a panic aborts // the whole run. let opt_magic_off = pe_off.wrapping_add(24) as usize; @@ -165,10 +160,11 @@ pub fn detect(input: &[u8]) -> Option { return None; } let clr_rva = primitives::get_u32(input, clr_rva_offset); - let kind = if clr_rva != 0 { - Kind::ManagedDll - } else { - Kind::NativeDll + let kind = match (is_dll, clr_rva != 0) { + (false, false) => Kind::NativeExe, + (false, true) => Kind::ManagedExe, + (true, false) => Kind::NativeDll, + (true, true) => Kind::ManagedDll, }; Some(Detected { kind, magic }) } @@ -184,7 +180,7 @@ pub fn unpack_auto(input: &[u8]) -> Result<(Kind, Vec), UnpackError> { pub fn unpack_auto_v(input: &[u8], verbose: bool) -> Result<(Kind, Vec), UnpackError> { let detected = detect(input).ok_or(UnpackError::NotCrackproof)?; let out = match detected.kind { - Kind::Exe => unpack_exe_v(input, verbose)?, + Kind::NativeExe | Kind::ManagedExe => unpack_exe_v(input, verbose)?, Kind::NativeDll | Kind::ManagedDll => { // Two Crackproof DLL layouts exist. The older one (the byte-identical // DLL goldens) follows the pipeline in `dll.rs`. Newer builds protect diff --git a/web/app.js b/web/app.js index a5c9df6..24cbabe 100644 --- a/web/app.js +++ b/web/app.js @@ -45,7 +45,8 @@ const files = new Map(); const rowEls = new Map(); const KIND_LABEL = { - exe: 'protected EXE', + 'native-exe': 'protected native EXE', + 'managed-exe': 'protected managed EXE', 'native-dll': 'protected native DLL', 'managed-dll': 'protected managed DLL', metadata: 'il2cpp metadata', @@ -323,7 +324,8 @@ async function unpackModule(name, entry) { let comp = compEntry ? await read(compEntry.file) : undefined; let r = await runUnpack(input, comp, false); - if (!r.ok && r.trap && entry.kind !== 'exe') { + const isExe = entry.kind === 'native-exe' || entry.kind === 'managed-exe'; + if (!r.ok && r.trap && !isExe) { // The DLL-routing probe trapped (panics can't be caught in wasm). Retry // once with the forced-EXE pipeline in a fresh worker — this mirrors the // CLI's dll-first/exe-fallback outcome for EXE-shell-layout DLLs. diff --git a/web/src/lib.rs b/web/src/lib.rs index 4b98583..976f4c1 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -24,7 +24,8 @@ pub struct UnpackResult { #[wasm_bindgen] impl UnpackResult { - /// Detected module kind: `"exe"`, `"native-dll"`, or `"managed-dll"`. + /// Detected module kind: `"native-exe"`, `"managed-exe"`, `"native-dll"`, + /// or `"managed-dll"`. #[wasm_bindgen(getter)] pub fn kind(&self) -> String { self.kind.clone() @@ -102,7 +103,8 @@ impl MetadataResult { fn kind_str(kind: senbei::unpacker::Kind) -> &'static str { match kind { - senbei::unpacker::Kind::Exe => "exe", + senbei::unpacker::Kind::NativeExe => "native-exe", + senbei::unpacker::Kind::ManagedExe => "managed-exe", senbei::unpacker::Kind::NativeDll => "native-dll", senbei::unpacker::Kind::ManagedDll => "managed-dll", } @@ -110,8 +112,9 @@ fn kind_str(kind: senbei::unpacker::Kind) -> &'static str { /// Classify a file's bytes without unpacking. /// -/// Returns `"exe"`, `"native-dll"`, `"managed-dll"`, `"metadata"` (an il2cpp -/// `global-metadata.dat`), or `undefined` for anything unrecognized. +/// Returns `"native-exe"`, `"managed-exe"`, `"native-dll"`, `"managed-dll"`, +/// `"metadata"` (an il2cpp `global-metadata.dat`), or `undefined` for +/// anything unrecognized. #[wasm_bindgen] pub fn detect(input: &[u8]) -> Option { if senbei::metadata::is_metadata(input) {