structure-check: gate counts code lines, docs and comments in English
Gate ignores blank and full-line comment lines so the 250-line limit measures code bulk, not documentation weight; docs/README.md synced. Translate code comments to English in classfile, core config, namegen, rename assign, constants forms. Expand Plausible word bank with client-ecosystem terms (Module, Setting, Mixin, ...). cargo fmt clean, check.sh green.
This commit is contained in:
parent
ec28ec82e3
commit
39178420e4
42 changed files with 532 additions and 437 deletions
|
|
@ -1,14 +1,13 @@
|
|||
//! Схемы разбиения констант и opaque-предикаты.
|
||||
//! Constant-splitting schemes and opaque predicates.
|
||||
//!
|
||||
//! Целые константы вне `iconst`-диапазона заменяются тройками (`x^k`,
|
||||
//! `a+b`, сдвиг); opaque-предикат — всегда истинное условие с мёртвой
|
||||
//! ветвью из одного `nop` (валидно, не исполняется, ломает линейный
|
||||
//! дисассемблирование).
|
||||
//! Integer constants outside the `iconst` range are replaced by triples
|
||||
//! (`x^k`, `a+b`, shift); an opaque predicate is an always-true condition with
|
||||
//! a one-`nop` dead branch (valid, never executed, breaks linear disassembly).
|
||||
|
||||
use noxis_classfile::{Arith, BranchCond, Cond, Const, Insn, Prim};
|
||||
use noxis_core::Rng;
|
||||
|
||||
/// Схема разбиения (имена из `ConstantsConfig::schemes`).
|
||||
/// Splitting scheme (names from `ConstantsConfig::schemes`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum Scheme {
|
||||
Xor,
|
||||
|
|
@ -27,7 +26,7 @@ impl Scheme {
|
|||
}
|
||||
}
|
||||
|
||||
/// Разбить `int`-константу на вычисляющую её тройку.
|
||||
/// Split an `int` constant into a triple that computes it.
|
||||
pub(super) fn encrypt_int(value: i32, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> {
|
||||
let (first, second) = match scheme {
|
||||
Scheme::Xor => {
|
||||
|
|
@ -50,7 +49,7 @@ pub(super) fn encrypt_int(value: i32, scheme: Scheme, rng: &mut Rng) -> Vec<Insn
|
|||
}
|
||||
}
|
||||
|
||||
/// Разбить `long`-константу (те же три схемы на `l*`-опкодах).
|
||||
/// Split a `long` constant (the same three schemes on `l*` opcodes).
|
||||
pub(super) fn encrypt_long(value: i64, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> {
|
||||
let (first, second) = match scheme {
|
||||
Scheme::Xor => {
|
||||
|
|
@ -73,7 +72,7 @@ pub(super) fn encrypt_long(value: i64, scheme: Scheme, rng: &mut Rng) -> Vec<Ins
|
|||
}
|
||||
}
|
||||
|
||||
/// `(a, b, op)` как три инструкции.
|
||||
/// `(a, b, op)` as three instructions.
|
||||
fn binary(first: i32, second: i32, op: Arith, prim: Prim) -> Vec<Insn> {
|
||||
vec![
|
||||
Insn::Const(Const::Int(first)),
|
||||
|
|
@ -93,7 +92,7 @@ fn binary_long(first: i64, second: i64, op: Arith) -> Vec<Insn> {
|
|||
]
|
||||
}
|
||||
|
||||
/// `v = ((v >> 1) << 1) | (v & 1)` — работает для любых, включая отрицательные.
|
||||
/// `v = ((v >> 1) << 1) | (v & 1)` — works for any value, including negatives.
|
||||
fn shift_form(value: i32) -> Vec<Insn> {
|
||||
vec![
|
||||
Insn::Const(Const::Int(value >> 1)),
|
||||
|
|
@ -110,7 +109,7 @@ fn shift_form(value: i32) -> Vec<Insn> {
|
|||
]
|
||||
}
|
||||
|
||||
/// То же для `long` (величина сдвига — всегда `int`!).
|
||||
/// The same for `long` (the shift amount is always an `int`!).
|
||||
fn shift_form_long(value: i64) -> Vec<Insn> {
|
||||
vec![
|
||||
Insn::Const(Const::Long(value >> 1)),
|
||||
|
|
@ -127,13 +126,18 @@ fn shift_form_long(value: i64) -> Vec<Insn> {
|
|||
]
|
||||
}
|
||||
|
||||
/// Opaque-предикат `(C*C + C) & 1 == 0` (истина для любого `C`):
|
||||
/// `[pred, ifne DEAD, goto MERGE, DEAD: nop, MERGE:]`, дальше — оригинал.
|
||||
/// Opaque predicate `(C*C + C) & 1 == 0` (true for any `C`).
|
||||
///
|
||||
/// Метки выдаёт вызывающий код (свободные номера метода).
|
||||
/// Stack-neutral and leaves control flow unchanged: the computed value is
|
||||
/// consumed by a branch that never fires; the dead `DEAD: nop` branch only
|
||||
/// bloats linear disassembly.
|
||||
///
|
||||
/// Pattern: `[pred..., ifne DEAD, goto MERGE, DEAD: nop, MERGE:]`.
|
||||
/// Labels come from the calling code (free method numbers).
|
||||
pub(super) fn opaque_true(dead: u32, merge: u32, rng: &mut Rng) -> Vec<Insn> {
|
||||
let constant = rng.next_u64() as i32;
|
||||
vec![
|
||||
// P = ((C*C) + C) & 1 — even, so always 0.
|
||||
Insn::Const(Const::Int(constant)),
|
||||
Insn::Dup,
|
||||
Insn::Binary {
|
||||
|
|
@ -150,6 +154,7 @@ pub(super) fn opaque_true(dead: u32, merge: u32, rng: &mut Rng) -> Vec<Insn> {
|
|||
prim: Prim::Int,
|
||||
op: Arith::And,
|
||||
},
|
||||
// P != 0 never holds — the branch into DEAD is not taken, value popped.
|
||||
Insn::Branch(BranchCond::Zero(Cond::Ne), dead),
|
||||
Insn::Goto(merge),
|
||||
Insn::Mark(dead),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Алфавиты, ключевые слова и запрещённые имена.
|
||||
//! Alphabets, keywords and forbidden names.
|
||||
//!
|
||||
//! Списки вынесены отдельно: к ним обращаются и генератор, и пасс
|
||||
//! переименования (резервирование keep-имён).
|
||||
//! Kept in their own file because both the generator and the rename pass
|
||||
//! reach into them (reserving keep-listed names before generation starts).
|
||||
|
||||
/// Жёсткие ключевые слова Java: имя класса/метода не может им совпадать.
|
||||
/// Hard Java keywords: a class/method name can never collide with these.
|
||||
pub(super) const KEYWORDS: &[&str] = &[
|
||||
"abstract",
|
||||
"assert",
|
||||
|
|
@ -66,8 +66,8 @@ pub(super) const KEYWORDS: &[&str] = &[
|
|||
"yield",
|
||||
];
|
||||
|
||||
/// Имена, которые не стоит затирать: мешают читать/логировать и ловятся
|
||||
/// инструментами до любой деобфускации.
|
||||
/// Names not worth overwriting: they'd hurt readability/logging and get
|
||||
/// flagged by tooling before any deobfuscation even starts.
|
||||
pub(super) const AVOID_CLASS: &[&str] = &[
|
||||
"Object",
|
||||
"String",
|
||||
|
|
@ -101,9 +101,9 @@ pub(super) const AVOID_MEMBER: &[&str] = &[
|
|||
|
||||
pub(super) const LATIN_LOWER: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
|
||||
pub(super) const LATIN_UPPER: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
/// Визуально неразличимые символы — ядро стиля `IlO0`.
|
||||
/// Visually indistinguishable characters: the core of the `IlO0` style.
|
||||
pub(super) const CONFUSABLE: &[u8] = b"IlO01";
|
||||
/// Unicode-буквы, валидные как Java-идентификаторы (Cyrillic/Greek/CJK).
|
||||
/// Unicode letters valid in a Java identifier (Cyrillic/Greek/CJK).
|
||||
pub(super) const UNICODE_LETTERS: &[char] = &[
|
||||
'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'у', 'ф',
|
||||
'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'λ', 'μ', 'π',
|
||||
|
|
@ -118,15 +118,58 @@ pub(super) const DECOY_RIGHT: &[&str] = &[
|
|||
"ripple",
|
||||
];
|
||||
|
||||
/// Стиль `Plausible`: домен-нейтральные существительные для классов/полей —
|
||||
/// похожи на обычный прикладной код, не привязаны к конкретному проекту.
|
||||
/// `Plausible` style word bank. Mixes domain-neutral software nouns with
|
||||
/// terms that are boring, structural scaffolding in the client/mod
|
||||
/// ecosystem noxis is actually used on (see
|
||||
/// `/storage/project/jvm/LoVisual/ref/clients/*`: `Module`, `Setting`,
|
||||
/// `Event`, `Mixin`, `Manager`, `Component`, `Accessor`, `Hud`, `Overlay`,
|
||||
/// `Theme`, `Timer`, `Animation`, `Rotation` all show up dozens of times
|
||||
/// across real client codebases). Deliberately excludes anything that
|
||||
/// itself reads as a "feature flag" worth an analyst's attention (no
|
||||
/// `Esp`/`Aimbot`/`KillAura`/`Chams`): a decoy name that announces
|
||||
/// "interesting code lives here" defeats the point of blending in.
|
||||
pub(super) const PLAUSIBLE_NOUN: &[&str] = &[
|
||||
"Texture", "Event", "State", "Render", "Asset", "Config", "Session", "Cache", "Buffer",
|
||||
"Socket", "Stream", "Widget", "Layout", "Theme", "Profile", "Metric", "Token", "Handle",
|
||||
"Context", "Registry", "Request", "Response", "Payload", "Resource", "Snapshot", "Pipeline",
|
||||
"Queue", "Slot", "Frame", "Entry",
|
||||
"Texture",
|
||||
"Event",
|
||||
"State",
|
||||
"Render",
|
||||
"Asset",
|
||||
"Config",
|
||||
"Session",
|
||||
"Cache",
|
||||
"Buffer",
|
||||
"Socket",
|
||||
"Stream",
|
||||
"Widget",
|
||||
"Layout",
|
||||
"Theme",
|
||||
"Profile",
|
||||
"Metric",
|
||||
"Token",
|
||||
"Handle",
|
||||
"Context",
|
||||
"Registry",
|
||||
"Request",
|
||||
"Response",
|
||||
"Payload",
|
||||
"Resource",
|
||||
"Snapshot",
|
||||
"Pipeline",
|
||||
"Queue",
|
||||
"Slot",
|
||||
"Frame",
|
||||
"Entry",
|
||||
"Module",
|
||||
"Setting",
|
||||
"Overlay",
|
||||
"Hud",
|
||||
"Component",
|
||||
"Accessor",
|
||||
"Timer",
|
||||
"Animation",
|
||||
"Rotation",
|
||||
];
|
||||
/// Суффиксы ролей для классов (`NounSuffix`, напр. `TextureCache`).
|
||||
/// Role suffixes for classes (`NounSuffix`, e.g. `TextureCache`).
|
||||
pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[
|
||||
"Manager",
|
||||
"Handler",
|
||||
|
|
@ -148,8 +191,10 @@ pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[
|
|||
"Processor",
|
||||
"Context",
|
||||
"Listener",
|
||||
"Mixin",
|
||||
"Component",
|
||||
];
|
||||
/// Глаголы для методов/полей (`verbNoun`, напр. `resolveToken`).
|
||||
/// Verbs for methods/fields (`verbNoun`, e.g. `resolveToken`).
|
||||
pub(super) const PLAUSIBLE_VERB: &[&str] = &[
|
||||
"get", "set", "update", "process", "handle", "compute", "resolve", "load", "build", "parse",
|
||||
"fetch", "apply", "validate", "clear", "reset", "sync", "flush", "bind", "attach", "detach",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Генерация имён: стили, анти-ключевые слова, уникальность в scope.
|
||||
//! Name generation: styles, anti-keyword checks, uniqueness within a scope.
|
||||
|
||||
pub mod alphabet;
|
||||
#[cfg(test)]
|
||||
|
|
@ -13,18 +13,18 @@ use self::alphabet::{
|
|||
LATIN_UPPER, PLAUSIBLE_NOUN, PLAUSIBLE_SUFFIX, PLAUSIBLE_VERB, UNICODE_LETTERS,
|
||||
};
|
||||
|
||||
/// Генератор имён с гарантированной уникальностью в пределах одного scope.
|
||||
/// Name generator with guaranteed uniqueness within a single scope.
|
||||
pub struct NameGenerator {
|
||||
rng: Rng,
|
||||
style: NameStyle,
|
||||
used: HashSet<String>,
|
||||
/// Счётчик-страховка, если случайные варианты исчерпаны.
|
||||
/// Fallback counter for when the random pool is exhausted.
|
||||
fallback: u64,
|
||||
avoid_class: bool,
|
||||
}
|
||||
|
||||
impl NameGenerator {
|
||||
/// Генератор для указанного стиля.
|
||||
/// Generator for the given style.
|
||||
pub fn new(rng: Rng, style: NameStyle) -> Self {
|
||||
Self {
|
||||
rng,
|
||||
|
|
@ -35,18 +35,18 @@ impl NameGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
/// Режим «имена классов»: дополнительно избегаем имён из JDK/аннотаций.
|
||||
/// "Class name" mode: additionally avoid names from the JDK/annotations.
|
||||
pub fn for_classes(mut self) -> Self {
|
||||
self.avoid_class = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Занять конкретное имя (защита от коллизий с keep-именами).
|
||||
/// Reserve a specific name (protects against collisions with keep-listed names).
|
||||
pub fn reserve(&mut self, name: impl Into<String>) {
|
||||
self.used.insert(name.into());
|
||||
}
|
||||
|
||||
/// Следующее свободное имя.
|
||||
/// The next free name.
|
||||
pub fn next_name(&mut self) -> String {
|
||||
for _ in 0..64 {
|
||||
let candidate = self.candidate();
|
||||
|
|
@ -55,7 +55,7 @@ impl NameGenerator {
|
|||
return candidate;
|
||||
}
|
||||
}
|
||||
// Страховка: детерминированный счётчик, гарантированно уникален.
|
||||
// Fallback: a deterministic counter, guaranteed unique.
|
||||
loop {
|
||||
self.fallback += 1;
|
||||
let candidate = format!("{}{}", self.style_prefix(), self.fallback);
|
||||
|
|
@ -95,7 +95,7 @@ impl NameGenerator {
|
|||
};
|
||||
let idx = self.rng.below(alpha.len() as u64) as usize;
|
||||
let ch = alpha[idx] as char;
|
||||
// Первый символ — только буква (не цифра).
|
||||
// First character must be a letter, not a digit.
|
||||
if i == 0 && !ch.is_ascii_alphabetic() {
|
||||
out.push('a');
|
||||
} else {
|
||||
|
|
@ -123,9 +123,9 @@ impl NameGenerator {
|
|||
}
|
||||
name
|
||||
}
|
||||
// Маскируется под обычный прикладной код, а не под мусор:
|
||||
// классы — `NounSuffix` (`TextureCache`), члены — `verbNoun`
|
||||
// (`resolveToken`) camelCase.
|
||||
// Blends in as ordinary application code instead of obvious
|
||||
// noise: classes get `NounSuffix` (`TextureCache`), members get
|
||||
// camelCase `verbNoun` (`resolveToken`).
|
||||
NameStyle::Plausible => {
|
||||
let noun = PLAUSIBLE_NOUN[self.rng.below(PLAUSIBLE_NOUN.len() as u64) as usize];
|
||||
if self.avoid_class {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Тесты генератора имён: уникальность, валидность, форма по стилю.
|
||||
//! Name generator tests: uniqueness, validity, per-style shape.
|
||||
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ fn names_are_unique_and_valid() {
|
|||
let mut seen = HashSet::new();
|
||||
for _ in 0..500 {
|
||||
let name = names.next_name();
|
||||
assert!(seen.insert(name.clone()), "повтор имени {name}");
|
||||
assert!(seen.insert(name.clone()), "duplicate name {name}");
|
||||
assert!(!KEYWORDS.contains(&name.as_str()));
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_alphabetic()));
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ fn plausible_class_names_look_like_noun_suffix() {
|
|||
for _ in 0..50 {
|
||||
let name = names.next_name();
|
||||
assert!(name.starts_with(|c: char| c.is_ascii_uppercase()), "{name}");
|
||||
// Два слова подряд слиплись в CamelCase без разделителей.
|
||||
// Two words run together in CamelCase with no separator.
|
||||
assert!(
|
||||
name.chars().filter(|c| c.is_ascii_uppercase()).count() >= 2,
|
||||
"{name}"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
//! Назначение новых имён: карты, семейства виртуальных методов,
|
||||
//! пул пакетов и финальное ширинирование доступа.
|
||||
//! Assigning new names: maps, virtual method families, the package pool,
|
||||
//! and final access widening.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
|
|
@ -10,11 +10,11 @@ use noxis_core::{Config, Context, NameStyle, Rng};
|
|||
use super::{ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC, ACC_STATIC};
|
||||
use crate::namegen::NameGenerator;
|
||||
|
||||
/// `(owner, name, descriptor)` — как в JVM-ссылке.
|
||||
/// `(owner, name, descriptor)` — matches a JVM reference.
|
||||
pub(super) type MemberKey = (String, String, String);
|
||||
|
||||
/// Карты переименований одного прогона: `declared` — где объявлено,
|
||||
/// `alias` — наследники, которые наследуют это имя без своего объявления.
|
||||
/// Rename maps for one run: `declared` is where a member was declared,
|
||||
/// `alias` is subclasses that inherit the name without declaring it themselves.
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct RenameMaps {
|
||||
pub(super) classes: BTreeMap<String, String>,
|
||||
|
|
@ -41,7 +41,7 @@ impl RenameMaps {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
/// Псевдоними: наследник вызывает член, объявленный в предке.
|
||||
/// Aliases: a subclass calls a member declared on a parent class.
|
||||
pub(super) fn expand(&mut self, hierarchy: &ClassHierarchy, owners: &[String]) {
|
||||
let methods: Vec<(MemberKey, String)> = self
|
||||
.methods
|
||||
|
|
@ -85,8 +85,8 @@ impl RenameMaps {
|
|||
}
|
||||
}
|
||||
|
||||
/// Переименование виртуальных методов «семейством»: одно имя на всю иерархию,
|
||||
/// включая мостовые методы с ковариантным возвратом.
|
||||
/// Rename virtual methods as a "family": one name for the whole hierarchy,
|
||||
/// including bridge methods with covariant returns.
|
||||
pub(super) fn rename_virtual_families(
|
||||
program: &Program,
|
||||
analysis: &Analysis,
|
||||
|
|
@ -94,8 +94,8 @@ pub(super) fn rename_virtual_families(
|
|||
cfg: &Config,
|
||||
ctx: &mut Context,
|
||||
) -> usize {
|
||||
// Семейство = (имя, параметры): возврат игнорируем — так захватываем
|
||||
// ковариантные возвраты и мостовые методы (`ACC_BRIDGE`) разом.
|
||||
// Family = (name, params): the return type is ignored, so covariant
|
||||
// returns and bridge methods (`ACC_BRIDGE`) are captured together.
|
||||
let mut families: BTreeMap<(String, String), Vec<MemberKey>> = BTreeMap::new();
|
||||
let mut locked_families: BTreeSet<(String, String)> = BTreeSet::new();
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ pub(super) fn rename_virtual_families(
|
|||
renamed
|
||||
}
|
||||
|
||||
/// `(<params>)<ret>` → `(<params>)`, либо `None`, если дескриптор кривой.
|
||||
/// `(<params>)<ret>` → `(<params>)`, or `None` if the descriptor is malformed.
|
||||
fn params_of(descriptor: &str) -> Option<String> {
|
||||
let close = descriptor.find(')')?;
|
||||
if !descriptor.starts_with('(') {
|
||||
|
|
@ -172,7 +172,8 @@ pub(super) fn simple_name(internal: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Пул случайных пакетов (2–3 сегмента); при выключенном разбросе — один пустой.
|
||||
/// Pool of random packages (2-3 segments); a single empty one when spreading
|
||||
/// is turned off.
|
||||
pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
|
||||
if !cfg.rename.spread_packages {
|
||||
return vec![String::new()];
|
||||
|
|
@ -194,7 +195,7 @@ pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Переписать дескриптор/сигнатуру: `Lpkg/Class;` → новое имя класса.
|
||||
/// Rewrite a descriptor/signature: `Lpkg/Class;` → the new class name.
|
||||
pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String {
|
||||
if !desc.contains('L') {
|
||||
return desc.to_string();
|
||||
|
|
@ -220,7 +221,7 @@ pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>)
|
|||
out
|
||||
}
|
||||
|
||||
/// Расширить доступ до `public`, чтобы переезд в другой пакет не ломал вызовы.
|
||||
/// Widen access to `public` so moving to a different package doesn't break calls.
|
||||
pub(super) fn widen_all(program: &mut Program) {
|
||||
for class in program.classes.iter_mut() {
|
||||
class.access_flags = widen(class.access_flags);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue