mirror of
https://github.com/Momoko-Ayase/Senbei.git
synced 2026-09-19 03:57:59 -04:00
Split the Exe kind into NativeExe / ManagedExe
The detector already used the CLR data-directory RVA to split DLLs into NativeDll / ManagedDll; EXEs were a single undifferentiated Exe kind. Apply the same CLR check to EXEs so callers can tell a protected .NET executable from a native one without unpacking. Routing is unchanged: both EXE kinds go to the EXE pipeline. - CLI per-file lines and the run log now print NativeExe / ManagedExe (the kind comes from the same Debug formatting as the DLL variants). - The web API's detect()/unpack_file() kind strings become 'native-exe' / 'managed-exe'; the web UI gains matching labels, and the trap-retry guard (DLL-probe recovery) keys off both EXE kinds. Golden corpus unchanged (35/35 byte-identical); kind is classification only and never affects output bytes.
This commit is contained in:
@@ -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. |
|
||||
|
||||
+9
-8
@@ -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
|
||||
|
||||
+12
-16
@@ -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<Detected> {
|
||||
let keys = key_table(input)?;
|
||||
let magic = keys[1];
|
||||
@@ -132,21 +133,15 @@ pub fn detect(input: &[u8]) -> Option<Detected> {
|
||||
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<Detected> {
|
||||
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<u8>), UnpackError> {
|
||||
pub fn unpack_auto_v(input: &[u8], verbose: bool) -> Result<(Kind, Vec<u8>), 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
|
||||
|
||||
+4
-2
@@ -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.
|
||||
|
||||
+7
-4
@@ -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<String> {
|
||||
if senbei::metadata::is_metadata(input) {
|
||||
|
||||
Reference in New Issue
Block a user