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 |
|
| 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. |
|
| `NativeDll` | Protected native (unmanaged) DLL. |
|
||||||
| `ManagedDll` | Protected .NET assembly (has a CLR data directory). |
|
| `ManagedDll` | Protected .NET assembly (has a CLR data directory). |
|
||||||
| `._` companion | Stub + external encrypted payload layout, spliced automatically. |
|
| `._` 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
|
Detection is content-based (`unpacker::detect`), never extension-based: the
|
||||||
key table is derived from the file header and checked against the format
|
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
|
magic, then the PE characteristics classify the input as EXE or DLL and the
|
||||||
managed DLL.
|
CLR data directory splits each into native vs managed (`NativeExe` /
|
||||||
|
`ManagedExe` / `NativeDll` / `ManagedDll`).
|
||||||
|
|
||||||
`unpack_auto` then dispatches:
|
`unpack_auto` then dispatches:
|
||||||
|
|
||||||
- `Exe` → the EXE pipeline (handles both PE32+ and PE32). Managed EXEs take
|
- `NativeExe` / `ManagedExe` → the EXE pipeline (handles both PE32+ and
|
||||||
the same path: their import-string table is null (imports are the CLR
|
PE32). Managed EXEs take the same path: their import-string table is null
|
||||||
bootstrap stub), the entry point comes from the protected header (the
|
(imports are the CLR bootstrap stub), the entry point comes from the
|
||||||
config block stores 0 for managed images), and the COR20 header, BSJB
|
protected header (the config block stores 0 for managed images), and the
|
||||||
metadata stream, and CLR resources are restored verbatim from the protected
|
COR20 header, BSJB metadata stream, and CLR resources are restored verbatim
|
||||||
file, mirroring the managed-DLL restore.
|
from the protected file, mirroring the managed-DLL restore.
|
||||||
- `NativeDll` / `ManagedDll` → the DLL pipeline first; on failure, the EXE
|
- `NativeDll` / `ManagedDll` → the DLL pipeline first; on failure, the EXE
|
||||||
pipeline as a fallback. Two DLL layouts exist in the wild: an older layout
|
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
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Exe,
|
NativeExe,
|
||||||
|
ManagedExe,
|
||||||
NativeDll,
|
NativeDll,
|
||||||
ManagedDll,
|
ManagedDll,
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ fn key_table(input: &[u8]) -> Option<[u32; 8]> {
|
|||||||
///
|
///
|
||||||
/// Routing: `keys[1]` must be the Crackproof magic (`KONN`).
|
/// Routing: `keys[1]` must be the Crackproof magic (`KONN`).
|
||||||
/// The PE IMAGE_FILE_DLL characteristic distinguishes EXE vs DLL;
|
/// 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> {
|
pub fn detect(input: &[u8]) -> Option<Detected> {
|
||||||
let keys = key_table(input)?;
|
let keys = key_table(input)?;
|
||||||
let magic = keys[1];
|
let magic = keys[1];
|
||||||
@@ -132,21 +133,15 @@ pub fn detect(input: &[u8]) -> Option<Detected> {
|
|||||||
let chars =
|
let chars =
|
||||||
(input[chars_offset as usize] as u16) | ((input[chars_offset as usize + 1] as u16) << 8);
|
(input[chars_offset as usize] as u16) | ((input[chars_offset as usize + 1] as u16) << 8);
|
||||||
let is_dll = (chars & 0x2000) != 0;
|
let is_dll = (chars & 0x2000) != 0;
|
||||||
if !is_dll {
|
// Managed vs native via the CLR data-directory RVA.
|
||||||
return Some(Detected {
|
|
||||||
kind: Kind::Exe,
|
|
||||||
magic,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// DLL: determine managed vs native via CLR data-directory RVA.
|
|
||||||
// peOff + 24 = start of optional header. The data directories start at a
|
// peOff + 24 = start of optional header. The data directories start at a
|
||||||
// magic-dependent offset within it: PE32 (0x10B) at +96, PE32+ (0x20B) at
|
// 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
|
// +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-
|
// `get_u16`/`get_u32` index unchecked, so every read past the already-
|
||||||
// checked Characteristics word must be bounds-checked first: a truncated
|
// 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
|
// and this detector runs on the folder scan threads where a panic aborts
|
||||||
// the whole run.
|
// the whole run.
|
||||||
let opt_magic_off = pe_off.wrapping_add(24) as usize;
|
let opt_magic_off = pe_off.wrapping_add(24) as usize;
|
||||||
@@ -165,10 +160,11 @@ pub fn detect(input: &[u8]) -> Option<Detected> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let clr_rva = primitives::get_u32(input, clr_rva_offset);
|
let clr_rva = primitives::get_u32(input, clr_rva_offset);
|
||||||
let kind = if clr_rva != 0 {
|
let kind = match (is_dll, clr_rva != 0) {
|
||||||
Kind::ManagedDll
|
(false, false) => Kind::NativeExe,
|
||||||
} else {
|
(false, true) => Kind::ManagedExe,
|
||||||
Kind::NativeDll
|
(true, false) => Kind::NativeDll,
|
||||||
|
(true, true) => Kind::ManagedDll,
|
||||||
};
|
};
|
||||||
Some(Detected { kind, magic })
|
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> {
|
pub fn unpack_auto_v(input: &[u8], verbose: bool) -> Result<(Kind, Vec<u8>), UnpackError> {
|
||||||
let detected = detect(input).ok_or(UnpackError::NotCrackproof)?;
|
let detected = detect(input).ok_or(UnpackError::NotCrackproof)?;
|
||||||
let out = match detected.kind {
|
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 => {
|
Kind::NativeDll | Kind::ManagedDll => {
|
||||||
// Two Crackproof DLL layouts exist. The older one (the byte-identical
|
// Two Crackproof DLL layouts exist. The older one (the byte-identical
|
||||||
// DLL goldens) follows the pipeline in `dll.rs`. Newer builds protect
|
// 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 rowEls = new Map();
|
||||||
|
|
||||||
const KIND_LABEL = {
|
const KIND_LABEL = {
|
||||||
exe: 'protected EXE',
|
'native-exe': 'protected native EXE',
|
||||||
|
'managed-exe': 'protected managed EXE',
|
||||||
'native-dll': 'protected native DLL',
|
'native-dll': 'protected native DLL',
|
||||||
'managed-dll': 'protected managed DLL',
|
'managed-dll': 'protected managed DLL',
|
||||||
metadata: 'il2cpp metadata',
|
metadata: 'il2cpp metadata',
|
||||||
@@ -323,7 +324,8 @@ async function unpackModule(name, entry) {
|
|||||||
let comp = compEntry ? await read(compEntry.file) : undefined;
|
let comp = compEntry ? await read(compEntry.file) : undefined;
|
||||||
let r = await runUnpack(input, comp, false);
|
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
|
// 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
|
// 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.
|
// CLI's dll-first/exe-fallback outcome for EXE-shell-layout DLLs.
|
||||||
|
|||||||
+7
-4
@@ -24,7 +24,8 @@ pub struct UnpackResult {
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl UnpackResult {
|
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)]
|
#[wasm_bindgen(getter)]
|
||||||
pub fn kind(&self) -> String {
|
pub fn kind(&self) -> String {
|
||||||
self.kind.clone()
|
self.kind.clone()
|
||||||
@@ -102,7 +103,8 @@ impl MetadataResult {
|
|||||||
|
|
||||||
fn kind_str(kind: senbei::unpacker::Kind) -> &'static str {
|
fn kind_str(kind: senbei::unpacker::Kind) -> &'static str {
|
||||||
match kind {
|
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::NativeDll => "native-dll",
|
||||||
senbei::unpacker::Kind::ManagedDll => "managed-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.
|
/// Classify a file's bytes without unpacking.
|
||||||
///
|
///
|
||||||
/// Returns `"exe"`, `"native-dll"`, `"managed-dll"`, `"metadata"` (an il2cpp
|
/// Returns `"native-exe"`, `"managed-exe"`, `"native-dll"`, `"managed-dll"`,
|
||||||
/// `global-metadata.dat`), or `undefined` for anything unrecognized.
|
/// `"metadata"` (an il2cpp `global-metadata.dat`), or `undefined` for
|
||||||
|
/// anything unrecognized.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn detect(input: &[u8]) -> Option<String> {
|
pub fn detect(input: &[u8]) -> Option<String> {
|
||||||
if senbei::metadata::is_metadata(input) {
|
if senbei::metadata::is_metadata(input) {
|
||||||
|
|||||||
Reference in New Issue
Block a user