passes: emit strings as CONSTANT_Dynamic (condy) - BSM-resolved, invisible to concrete-execution deobfuscators

This commit is contained in:
Loki 2026-10-15 13:57:19 +02:00
parent 1ddc4afbfc
commit aac7571bbc
Signed by: boba
GPG key ID: 253067914055423B
10 changed files with 517 additions and 5 deletions

View file

@ -69,7 +69,14 @@ pub(super) fn build_rename(t: &Table) -> Res<RenameConfig> {
pub(super) fn build_strings(t: &Table) -> Res<StringsConfig> {
check_known_keys(
t,
&["schemes", "min_len", "split_above", "skip", "derive_key"],
&[
"schemes",
"min_len",
"split_above",
"skip",
"derive_key",
"condy",
],
"strings",
)?;
let mut c = StringsConfig::default();
@ -88,6 +95,9 @@ pub(super) fn build_strings(t: &Table) -> Res<StringsConfig> {
if let Some(v) = t.get("derive_key") {
c.derive_key = expect_bool(v, "strings.derive_key")?;
}
if let Some(v) = t.get("condy") {
c.condy = expect_bool(v, "strings.condy")?;
}
Ok(c)
}

View file

@ -62,3 +62,10 @@ fn parses_name_style_in_rename_section() {
let cfg = build_from("[rename]\nclass_style = \"il-o0\"\n").unwrap();
assert_eq!(cfg.rename.class_style, NameStyle::IlO0);
}
#[test]
fn parses_condy_flag_in_strings_section() {
let cfg = build_from("[strings]\ncondy = true\n").unwrap();
assert!(cfg.strings.condy);
assert!(!Config::default().strings.condy);
}

View file

@ -79,6 +79,12 @@ pub struct StringsConfig {
/// Derive the key from KDF(class, method, index) instead of placing it
/// next to the ciphertext.
pub derive_key: bool,
/// Emit strings as `CONSTANT_Dynamic` (condy) resolved by the decoder's
/// bootstrap method instead of direct `d(cipher, salt)` calls. A
/// concrete-execution deobfuscator cannot follow the condy resolution
/// (todo.md, 2026-10-14: SSVM made 0 changes on a condy-only class).
/// Requires classfile >= 55; older classes keep direct calls.
pub condy: bool,
}
impl Default for StringsConfig {
@ -89,6 +95,7 @@ impl Default for StringsConfig {
split_above: 64,
skip: Vec::new(),
derive_key: true,
condy: false,
}
}
}