diff --git a/README.md b/README.md
index 76e15e0..4d592b6 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,61 @@
+
+
# noxis
Обфускатор JVM/Java-байткода на Rust.
-Читает JAR, переписывает классовые файлы и ресурсы, собирает обратно —
-детерминированно, с маппингами для stack trace и с проверкой, что выход
-действительно загружается настоящей JVM.
+
+
+
+
+
+
+
+---
+
+Читает JAR, переписывает классовые файлы и ресурсы, собирает обратно: детерминированно,
+с маппингами для stack trace и с проверкой, что выход действительно загружается настоящей JVM.
## Статус
-В работе. Ядро построено, часть техник реализована, часть — по плану.
+Ядро построено, большая часть техник реализована.
| блок | состояние |
|---|---|
| IR классовых файлов, метки вместо смещений | ✅ |
| конфиг, детерминированный PRNG, маппинги, отчёт | ✅ |
| анализ: иерархия, entrypoint'ы, keep-правила | ✅ |
-| переименование всех символов | ✅ |
-| схемы шифрования строк (salt, nonce, MAC) | ✅ |
-| JAR: чтение/запись, ресурсы, срез подписей | ✅ |
-| гейт структурных правил при компиляции | ✅ |
-| пассы: rename, resources, debug | ✅ |
-| spike backend'а `ristretto_classfile` | ✅ |
-| `format::parse`/`write` + кадры `StackMapTable` | ✅ |
-| пассы: strings, constants, noise (+ декодер v49) | ✅ |
-| пасс flow | 🚧 |
-| верификация и метрики | 🚧 |
-| CLI | 🚧 |
+| `format::parse`/`write` + кадры `StackMapTable` (backend `ristretto_classfile`) | ✅ |
+| пассы: rename, resources, strings, constants, noise, debug | ✅ |
+| верификация: red-паттерны, `java -Xverify:all`, метрики | ✅ |
+| CLI (`obfuscate` / `analyze` / `patterns`) | ✅ |
+| пасс flow (кадры готовы, схем флаттенинга пока нет) | 🚧 |
-Подробности — [`docs/architecture.md`](docs/architecture.md),
-план — [`todo.md`](todo.md).
+Подробности в [`docs/architecture.md`](docs/architecture.md), план в [`todo.md`](todo.md).
+
+## Быстрый старт
+
+```sh
+./scripts/check.sh # fmt + clippy -D warnings + тесты
+
+# обфускация jar: конвейер, маппинги, JSON-отчёт
+cargo run -p noxis -- obfuscate input.jar -o output.jar --seed 42
+
+# что внутри jar: классы, entrypoint'ы, keep-правила (без изменений)
+cargo run -p noxis -- analyze input.jar
+
+# красный список паттернов: показать или прогнать по jar'у
+cargo run -p noxis -- patterns --list
+cargo run -p noxis -- patterns --input output.jar
+```
+
+`obfuscate` по умолчанию включает все готовые пассы. Флаги `--no-rename`,
+`--no-strings`, `--no-constants`, `--no-noise`, `--no-resources`, `--no-debug-info`
+выключают их по отдельности. `--no-flow` честно падает с ошибкой: пасса пока нет.
+
+Одно `cargo build` уже гоняет проверку структуры кода: `.rs`-файл длиннее
+250 строк или директория с больше чем 4 `.rs`-файлами ломают сборку.
+Документация (`docs/**`) под это правило не попадает. Правила в [`docs/README.md`](docs/README.md).
## Структура
@@ -36,75 +63,56 @@
noxis/
├── Cargo.toml workspace, lints, release-профиль
├── scripts/check.sh structure → fmt → clippy → test
-├── docs/ ← архитектура, техники, стек, уроки
+├── docs/ архитектура, техники, стек, уроки
├── info/ исходный материал разбора (вне правил)
└── crates/
- ├── noxis-classfile/ IR + парсер/запись + StackMapTable
- ├── noxis-core/ конфиг, seed, маппинги, контракт Pass
- ├── noxis-analysis/ иерархия, entrypoint'ы, keep
- ├── noxis-passes/ сами трансформации + конвейер
- ├── noxis-runtime/ схемы шифрования, ABI декодера
- ├── noxis-jar/ ZIP, ресурсы, срез подписей
- ├── noxis-verify/ red-паттерны, java -Xverify (план)
- ├── noxis/ CLI (план)
+ ├── noxis-classfile/ IR + парсер/запись + StackMapTable
+ ├── noxis-core/ конфиг, seed, маппинги, контракт Pass
+ ├── noxis-analysis/ иерархия, entrypoint'ы, keep
+ ├── noxis-passes/ сами трансформации + конвейер
+ ├── noxis-runtime/ схемы шифрования, ABI декодера
+ ├── noxis-jar/ ZIP, ресурсы, срез подписей
+ ├── noxis-verify/ red-паттерны, java -Xverify, метрики
+ ├── noxis/ CLI: obfuscate, analyze, patterns
└── noxis-structure-check/ автогейт структуры
```
-## Быстрый старт
-
-```sh
-./scripts/check.sh # fmt + clippy -D warnings + тесты
-```
-
-Одно `cargo build` уже гоняет проверку структуры проекта: если файл
-длиннее 250 строк или в директории больше 4 файлов, сборка падает.
-Правила — в [`docs/README.md`](docs/README.md).
-
## Документация
| документ | что внутри |
|---|---|
| [`docs/README.md`](docs/README.md) | индекс и структурные правила проекта |
| [`docs/architecture.md`](docs/architecture.md) | слои, крейты, IR, конвейер |
-| [`docs/techniques.md`](docs/techniques.md) | как работает каждая техника: плюсы и минусы |
+| [`docs/techniques.md`](docs/techniques.md) | как работает каждая техника, плюсы и минусы |
| [`docs/stack.md`](docs/stack.md) | почему свой IR, backend, `StackMapTable`, PRNG |
| [`docs/learnings/lessons.md`](docs/learnings/lessons.md) | уроки разбора: чего делать нельзя |
| [`docs/learnings/reference.md`](docs/learnings/reference.md) | разбор референсного обфускатора |
| [`docs/learnings/priorities.md`](docs/learnings/priorities.md) | что улучшаем и в каком порядке |
+| [`docs/learnings/ai-resistance.md`](docs/learnings/ai-resistance.md) | новые техники (MBA, виртуализация) и устойчивость к LLM-анализу |
-Исходный материал разбора — [`info/README.md`](info/README.md).
-
-## Принципы
-
-- **KISS / DRY / SOLID.** Файл — одна ответственность. Не больше 250 строк,
- не больше 4 файлов в директории.
-- **Детерминированность.** Один seed → байт-в-байт одинаковый артефакт.
-- **Ничего не ломаем.** Техника, которая может дать `VerifyError`,
- включается только после верификатора.
-- **Измеримо.** Защита без метрик — это мнение, а не свойство.
+Исходный материал разбора в [`info/README.md`](info/README.md).
## Лицензия
-AGPL-3.0-only — см. [`LICENSE`](LICENSE).
+AGPL-3.0-only, см. [`LICENSE`](LICENSE).
-Проект остаётся полностью открытым: его можно изучать, использовать
-(в том числе коммерчески) и изменять. Условие AGPL — любые производные
-работы, которые распространяются или доступны пользователям по сети,
-обязаны публиковаться под той же лицензией. Это защищает `noxis` от
-закрытых форков, которые перепродают его как готовый продукт.
+Проект остаётся полностью открытым: его можно изучать, использовать (в том числе
+коммерчески) и изменять. Условие AGPL: любые производные работы, которые
+распространяются или доступны пользователям по сети, обязаны публиковаться под
+той же лицензией. Это защищает `noxis` от закрытых форков, которые перепродают
+его как готовый продукт.
-Сторонние зависимости и их лицензии — в [`NOTICE.md`](NOTICE.md).
+Сторонние зависимости и их лицензии в [`NOTICE.md`](NOTICE.md).
## Ответственное использование
-`noxis` — инструмент двойного назначения. Он одинаково полезен для
-защиты собственного байткода и способен усложнять анализ вредоносного
-ПО. Лицензия это не регламентирует, но это норма сообщества проекта:
+`noxis` это инструмент двойного назначения, одинаково полезный для защиты
+собственного байткода и способен усложнять анализ вредоносного ПО. Лицензия
+это не регламентирует, но это норма сообщества проекта:
-- используйте обфускацию, чтобы защищать **свой** код и код тех, кто
- вас об этом попросил;
-- не применяйте инструмент для упаковки вредоносов, обхода средств
- защиты и антивирусов или иной деятельности, запрещённой законом.
+- используйте обфускацию, чтобы защищать **свой** код и код тех, кто вас об этом попросил;
+- не применяйте инструмент для упаковки вредоносов, обхода средств защиты
+ и антивирусов или иной деятельности, запрещённой законом.
-Нарушение этих норм не влечёт юридических последствий по лицензии, но
-приведёт к отказу в поддержке и закрытию issue/PR в репозитории.
+Нарушение этих норм не влечёт юридических последствий по лицензии, но приведёт
+к отказу в поддержке и закрытию issue/PR в репозитории.
diff --git a/crates/noxis-structure-check/src/lib.rs b/crates/noxis-structure-check/src/lib.rs
index 347afdc..7cde776 100644
--- a/crates/noxis-structure-check/src/lib.rs
+++ b/crates/noxis-structure-check/src/lib.rs
@@ -76,4 +76,16 @@ mod tests {
let found = violations(&tmp.0);
assert!(found.is_empty(), "{found:?}");
}
+
+ #[test]
+ fn ignores_markdown_size_and_count() {
+ // Правила — только про Rust-код: `.md` не лимитирован ни по
+ // строкам, ни в счётчике файлов директории.
+ let tmp = TempDir::new("md");
+ for index in 0..super::MAX_DIR_FILES + 3 {
+ fs::write(tmp.0.join(format!("doc{index}.md")), "line\n".repeat(300)).expect("write");
+ }
+ let found = violations(&tmp.0);
+ assert!(found.is_empty(), "{found:?}");
+ }
}
diff --git a/crates/noxis-structure-check/src/rules.rs b/crates/noxis-structure-check/src/rules.rs
index 8cf1ada..d6b9b9a 100644
--- a/crates/noxis-structure-check/src/rules.rs
+++ b/crates/noxis-structure-check/src/rules.rs
@@ -1,31 +1,32 @@
//! Project structure rules, invoked from every crate's `build.rs`.
//!
-//! Rules:
-//! * every `.rs` and `.md` file is at most [`MAX_FILE_LINES`] lines long;
-//! * every directory inside a crate `src/` or the workspace `docs/` holds at
-//! most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
+//! Rules apply to Rust code only, not to documentation:
+//! * every `.rs` file is at most [`MAX_FILE_LINES`] lines long;
+//! * every directory inside a crate's `src/`, `tests/` or `benches/` holds
+//! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
//!
-//! Deliberately excluded: `Cargo.lock`, `.gitignore`, the legacy `info/` tree,
-//! and crate roots (they always own `Cargo.toml` plus `build.rs`).
+//! Deliberately excluded: `Cargo.lock`, `.gitignore`, `docs/`, the legacy
+//! `info/` tree, and crate roots (they always own `Cargo.toml` plus
+//! `build.rs`).
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
-/// Maximum number of lines in a source (`rs`) or documentation (`md`) file.
+/// Maximum number of lines in a Rust source file.
pub const MAX_FILE_LINES: usize = 250;
/// Maximum number of files per directory; subdirectories are not counted.
pub const MAX_DIR_FILES: usize = 4;
/// Extensions subject to [`MAX_FILE_LINES`].
-const LINE_LIMITED_EXTS: [&str; 2] = ["rs", "md"];
+const LINE_LIMITED_EXTS: [&str; 1] = ["rs"];
/// Directory names the scan never descends into.
const SKIPPED_DIRS: [&str; 2] = ["target", ".git"];
-/// Scans the calling crate's `src/`/`tests/`/`benches/` plus the workspace
-/// `docs/` tree and panics with a report on the first violation.
+/// Scans the calling crate's `src/`/`tests/`/`benches/` and panics with a
+/// report on the first violation.
///
/// # Panics
///
@@ -35,13 +36,13 @@ pub fn check() {
let mut violations = Vec::new();
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы —
- // иначе правило «250 строк» обходится выносом кода в тесты.
- let mut roots = vec![manifest.join("src"), manifest.join("tests")];
- roots.push(manifest.join("benches"));
-
- if let Some(root) = workspace_root(&manifest) {
- roots.push(root.join("docs"));
- }
+ // иначе правило «250 строк» обходится выносом кода в тесты. `docs/` вне
+ // гейта: правила — только про Rust-код.
+ let roots = [
+ manifest.join("src"),
+ manifest.join("tests"),
+ manifest.join("benches"),
+ ];
for root in &roots {
watch(root);
@@ -64,8 +65,9 @@ pub fn check() {
/// Re-runs the build script whenever a checked tree changes.
///
-/// Without this cargo only invalidates `build.rs` on crate-local changes, so
-/// editing `docs/` would never be re-validated.
+/// Without an explicit `rerun-if-changed`, cargo falls back to its default
+/// (rerun on any change anywhere in the package); naming the trees keeps the
+/// gate from firing on unrelated edits.
fn watch(root: &Path) {
if root.is_dir() {
println!("cargo:rerun-if-changed={}", root.display());
@@ -101,6 +103,10 @@ pub fn scan(root: &Path, violations: &mut Vec) {
subdirs.push(path);
continue;
}
+ let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
+ if !LINE_LIMITED_EXTS.contains(&ext) {
+ continue;
+ }
files += 1;
check_line_limit(&path, violations);
}
@@ -118,10 +124,6 @@ pub fn scan(root: &Path, violations: &mut Vec) {
}
fn check_line_limit(path: &Path, violations: &mut Vec) {
- let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
- if !LINE_LIMITED_EXTS.contains(&ext) {
- return;
- }
let lines = count_lines(path);
if lines > MAX_FILE_LINES {
violations.push(format!(
@@ -149,17 +151,3 @@ fn manifest_dir() -> PathBuf {
None => panic!("CARGO_MANIFEST_DIR is unset; run this check from a build script"),
}
}
-
-/// Walks up from `start` until a manifest declaring `[workspace]` is found.
-fn workspace_root(start: &Path) -> Option {
- let mut current = Some(start);
- while let Some(dir) = current {
- if let Ok(text) = fs::read_to_string(dir.join("Cargo.toml"))
- && text.lines().any(|line| line.trim() == "[workspace]")
- {
- return Some(dir.to_path_buf());
- }
- current = dir.parent();
- }
- None
-}
diff --git a/docs/README.md b/docs/README.md
index 7236664..6f18984 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -12,22 +12,24 @@
| [`learnings/lessons.md`](learnings/lessons.md) | уроки из разбора EvoVis: что защита сделала плохо и чего мы не делаем |
| [`learnings/reference.md`](learnings/reference.md) | разбор референсного обфускатора (Java/ASM) — что берём, что нет |
| [`learnings/priorities.md`](learnings/priorities.md) | что улучшим: стратегия и приоритеты |
+| [`learnings/ai-resistance.md`](learnings/ai-resistance.md) | новые техники обфускации и устойчивость к LLM-анализу |
План выполнения — [`../todo.md`](../todo.md). Исходный материал разбора —
[`../info/`](../info/README.md).
## Структурные правила проекта
-Правила действуют на **весь наш код и документацию**: `crates/**` и `docs/**`.
-Не учитываются: `Cargo.lock`, `.gitignore`, `info/`, корень крейта
+Правила — **только про Rust-код**: `crates/*/src`, `crates/*/tests`,
+`crates/*/benches`. Документация (`docs/**`, корневые `.md`) и `info/` вне
+гейта: писать подробно там, где это документация, а не ограничивать объём
+искусственно. Не учитываются также `Cargo.lock`, `.gitignore`, корень крейта
(в нём всегда лежат `Cargo.toml` + `build.rs`).
-1. **Не больше 250 строк** в одном `.rs` или `.md` файле.
-2. **Не больше 4 файлов** в одной директории. Подпапки не ограничены:
+1. **Не больше 250 строк** в одном `.rs` файле.
+2. **Не больше 4 `.rs`-файлов** в одной директории. Подпапки не ограничены:
`src/` может содержать `lib.rs` и три модуля-директории, каждая из которых
в свою очередь содержит не больше четырёх файлов.
-Проверяются: `crates/*/src`, `crates/*/tests`, `crates/*/benches` и `docs/`.
`tests/` и `benches/` не исключены намеренно — иначе правило
«250 строк» обходится выносом кода в тесты.
@@ -45,11 +47,11 @@ fn main() {
}
```
-`noxis-structure-check` находит корень воркспейста, обходит деревья `src/`,
-`tests/` и `benches/` всех крейтов плюс `docs/`, и **падает со стопкой ошибок**,
-если лимит нарушен. Поэтому `cargo build` не соберёт нарушение — специально
-запускать проверку не нужно. Деревья регистрируются через
-`cargo:rerun-if-changed`, так что правка `docs/` тоже перезапускает гейт.
+`noxis-structure-check` обходит деревья `src/`, `tests/` и `benches/` **этого
+крейта** и **падает со стопкой ошибок**, если лимит нарушен. Поэтому
+`cargo build` не соберёт нарушение — специально запускать проверку не нужно.
+Деревья регистрируются через `cargo:rerun-if-changed`, чтобы гейт не
+срабатывал на правки вне проверяемых путей.
Остальное, что не привязано к компиляции, собирается одним скриптом:
diff --git a/docs/architecture.md b/docs/architecture.md
index 108456e..91a31ad 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -120,9 +120,9 @@ Rename → Resources → Strings → Constants → Flow → Noise → DebugInfo
| этап | что |
|---|---|
-| **готово** | IR, модель, конфиг, seed/PRNG, маппинги, анализ, keep, переименование, шифры строк, гейт правил, `format::parse`/`write` поверх ристретто, вывод кадров `StackMapTable` |
-| **P2** | пассы строк/констант/шума/потока, генератор декодера, CLI |
-| **P2** | генерация класса-декодера, вывод мастер-секрета, остальные пассы |
-| **P3** | CLI, метрики, интеграция с `java -Xverify:all` |
+| **готово** | IR, модель, конфиг, seed/PRNG, маппинги, анализ, keep, гейт правил, `format::parse`/`write` поверх ристретто, вывод кадров `StackMapTable` |
+| **готово** | пассы rename/resources/strings/constants/noise/debug, генератор декодера, вывод мастер-секрета в рантайме |
+| **готово** | `noxis-verify` (red-паттерны, `java -Xverify:all`, метрики), CLI (`obfuscate`/`analyze`/`patterns`) |
+| **план** | схемы пасса `flow` (кадры для них уже считаются), фикстуры и regression-тесты |
Детали выбора — [`stack.md`](stack.md), план — [`../todo.md`](../todo.md).
diff --git a/docs/learnings/ai-resistance.md b/docs/learnings/ai-resistance.md
new file mode 100644
index 0000000..1e3881f
--- /dev/null
+++ b/docs/learnings/ai-resistance.md
@@ -0,0 +1,167 @@
+# Устойчивость к ИИ-анализу и новые техники (октябрь 2026)
+
+Полный разбор двух источников по запросу: что конкретно работает против LLM
+при реверсе кода и что из этого переносится на JVM-байткод. Плюс отдельно:
+что нового в классической обфускации (MBA, виртуализация).
+
+Короткий вывод: специальной "защиты от нейросетей" как отдельного свойства
+кода не существует. Есть два разных, непересекающихся приёма, которые
+работают по разным причинам, и переносится на наш проект только один из них.
+
+## 1. Elastic Security Labs: агент с IDA Pro против Tigress (апрель 2026)
+
+[«The Cost of Understanding»](https://www.elastic.co/security-labs/llm-reversing-vs-llm-obfuscation) —
+Claude Opus 4.6 с доступом к IDA Pro (через MCP) реверсил crackme,
+обфусцированный [Tigress](https://tigress.wtf/), затем команда сама собрала
+("vibecode"-ила) три защиты, нацеленные именно на модель. Это ближе всего
+к нашей реальной угрозе: не чат с текстом исходника, а агент, читающий
+бинарник/байткод инструментами.
+
+### Benchmark на Tigress
+
+22 цели, 4 уровня сложности: без трансформаций → одиночные → пары → тяжёлые
+комбинации. Трансформации: `EncodeArithmetic`, `EncodeLiterals` (MBA),
+`Flatten` (control-flow flattening), `JIT`/`JitDynamic` (XTEA-шифрование кода
+с рантайм-расшифровкой), `Virtualize` (byte-code VM, варианты
+indirect/regs и switch/stack).
+
+- Opus решил ~40% из 20 валидных задач. Успех стоил в среднем **$2.39**,
+ провал — **$4.83**. Ни одна задача уровня "многослойные комбинации" не
+ была решена.
+- **`JIT` (XTEA + рантайм-расшифровка) оказался самой сложной одиночной
+ трансформацией** — модель понимала структуру движка, но не смогла
+ восстановить ключевые константы. Это ровно наш подход в `Strings`
+ (ключ выводится в рантайме, не лежит в вызове), только применённый ко
+ **всему коду метода**, а не к строкам.
+- На самых жёстких комбинациях цена и время выросли в ~4.5× и ~4.3×, но
+ задачи, которые Opus уже умел решать, он решил всё равно. **Flatten + MBA
+ било по модели сильнее, чем Virtualize + MBA** — авторы объясняют это тем,
+ что флаттенинг сильнее раздувает размер функции.
+
+### Три кастомные защиты, нацеленные на агента
+
+| защита | идея | результат |
+|---|---|---|
+| Matryoshka Wall | crackme внутри 100 000 вложенных слоёв ChaCha20 (+ подделка под Salsa20) | v1 — агент сдался не расшифровав ($1.50); v2 (каждый ключ зависит от предыдущего слоя) — исчерпал бюджет $10 за 56 минут, 0 успехов |
+| Double Fond | патч open-source крипто-библиотеки: скрытый загрузчик через изменённую lookup-таблицу, шифрованные указатели, RWX-сегмент | v7 — 1/5 у "осведомлённого" агента (12 мин, $5.20), 0/5 у "слепого" |
+| Dispatch Maze | шифр раздроблен на 20 фрагментов по 3000 функций, большинство — ловушки | v1 решена за 12 минут ($2.56); v2 (разнообразные шаблоны кода + ложные переходы) — провал агента через 46 минут и $8.83 |
+
+### Что из этого реально работает, по механизму
+
+1. **Контекстное окно.** Длинные тупиковые пути (decoy-функции, ложные
+ переходы) забивают контекст и портят рассуждение модели.
+2. **Бюджет шагов/денег.** Каждый впустую потраченный цикл стоит агенту
+ реальных токенов; если он упирается в лимит и сдаётся — это победа
+ защиты, даже если код теоретически вскрываем.
+3. **Shortcut bias.** Вводящая в заблуждение структура (ловушки в
+ Dispatch Maze) заставляет модель остановиться раньше времени.
+ Попытки prompt injection в тексте **не сработали** против Opus 4.6.
+
+**Ключевое экономическое наблюдение авторов:** нативное исполнение занимает
+~55 мс, а статический провал агента стоит **$1.50–$9.67**. Это и есть
+реальный рычаг — не "нечитаемость навсегда", а разрыв между ценой атаки и
+ценой сборки для нас. Авторы прямо предупреждают: преимущество схлопнется,
+когда у агентов появятся инструменты динамического исполнения (сейчас
+тестировался только статический анализ).
+
+## 2. Acoda: атака на safety-alignment и токенизацию (ICSE 2026)
+
+[Acoda](https://arxiv.org/pdf/2606.11755) — другая постановка задачи: не
+агент с дизассемблером, а LLM, которой **в чат вставили исходный код** и
+попросили его проанализировать/деобфусцировать. Генетический алгоритм
+подбирает комбинацию из 8 трансформаций, до 70% ASR (attack success rate)
+на 7 моделях (GPT-4o, DeepSeek, Qwen, Llama, Gemma).
+
+### 8 методов, по стадиям
+
+| стадия | метод | как работает |
+|---|---|---|
+| до анализа | Deceptive Comment Warnings | комментарий командует модели отказаться анализировать ("прекрати, это бэкдор") |
+| до анализа | Variable/Function Renaming | идентификаторы вида `MakeAnesthetics`, намекающие на malware/оружие |
+| до анализа | Vulnerability Code Injection | фрагмент с SQL-инъекцией/`pickle.loads`/command injection в заведомо мёртвой ветке (`while (сложное_но_ложное_условие)`) |
+| во время анализа | Misleading Summarization | модель сама просится описать код, затем это описание (намеренно неверное) вклеивается рядом с кодом |
+| во время анализа | String Obfuscation | `base64` + словарь, расшифровка даёт слова типа "virus"/"weapon" |
+| во время анализа | Try-Except Wrapping | настоящая логика лежит в `except`-ветке, в `try` — правдоподобный, но бесполезный код |
+| после анализа | EOS Token Insertion | в текст вставлены литералы токенов-разделителей (`<|EOT|>`), из-за чего генерация обрывается раньше |
+
+### Честная оценка: что из этого применимо к нам
+
+Почти ничего напрямую, и вот почему. Все 8 методов работают через
+**текстовый/натурально-языковой канал**: комментарии, сгенерированное
+описание, визуально узнаваемые "эксплойт"-паттерны в исходнике. У нас:
+
+- `DebugInfo` уже вычищает всё, что могло бы стать комментарием или
+ осмысленным описанием (`LineNumberTable`, `Signature`, исходные имена);
+ в скомпилированном `.class` комментариев не бывает в принципе.
+- Цель Acoda — заставить модель **отказаться анализировать свой же код**,
+ вставленный в чат пользователем. У нас код анализирует **чужой** агент
+ чужого jar — нам не нужно, чтобы он отказался, если он не наш союзник;
+ и вызывать намеренно подозрительные паттерны в легитимном продукте —
+ прямой риск получить ложное срабатывание антивируса/EDR на свой же jar.
+ Это противоречит `README.md` → «Ответственное использование».
+- **EOS Token Insertion** не переносится: это свойство текстового
+ стриминга токенов модели, а не байткода.
+
+Единственное, что частично переносится: **String Obfuscation** и
+**Variable Renaming** — ровно то, что `Strings` и `Rename` уже делают,
+просто дешевле (у Acoda — обратимый `base64`, у нас — ротация шифров с
+выводом ключа в рантайме, см. [`../stack.md`](../stack.md) §4-5). Здесь мы
+уже на шаг впереди статьи, а не позади.
+
+**Vulnerability Code Injection** как идея (мёртвый код, похожий на уязвимый
+паттерн) пересекается с нашим `Noise`, но внедрять **реально похожие на
+эксплойт сигнатуры** в выдаваемый клиентам jar — плохая идея для легального
+продукта защиты: это именно то, от чего предостерегает
+[`info/evo-weaknesses.md`](../../info/evo-weaknesses.md) с другой стороны
+(не подставлять защищённый продукт под чужие red-листы).
+
+## 3. Что добавить в noxis практически
+
+Экономический рычаг из Elastic (цена агента > цены нашей сборки) ложится на
+архитектуру без переделок, конкретные пункты — в [`../../todo.md`](../../todo.md)
+§7:
+
+1. **Вложенные слои шифрования строк** (аналог Matryoshka Wall) — расширить
+ `StringsConfig` полем "глубина", decoder разворачивает N слоёв вместо
+ одного. Цена агента растёт с глубиной почти линейно, цена сборки — нет.
+2. **Больше decoy-точек ветвления ключа** (аналог Dispatch Maze) — наш
+ декодер уже собирает мастер-секрет из `k1^k2` (`stack.md` §4); можно
+ поднять число частей и раскидать их сборку по случайным junk-методам
+ из `Noise`, а не держать в одном месте.
+3. **MBA вместо простых тождеств в `Constants`** — то же решение, что ловит
+ и символьный исполнитель, и SMT-решатель, и, по данным Elastic,
+ "Flatten + MBA" бьёт сильнее, чем просто Flatten.
+4. **Не делать:** имитацию вредоносных паттернов, prompt injection в
+ строках, EOS-токены. Либо не переносится на байткод, либо создаёт риск
+ ложного срабатывания защитных систем на легитимном продукте.
+
+Критерий "сработало/не сработало" для всего этого — как и для остальных
+техник: прогон конкретного агента на конкретной сборке, а не вера в
+методичку. Эффект устаревает с каждой новой версией модели.
+
+## 4. Классическая обфускация: что нового, помимо LLM
+
+- [Систематическое исследование control-flow обфускации в Java](https://arxiv.org/pdf/1809.11037)
+ называет **виртуализацию** (table interpretation) самой мощной техникой:
+ декомпилятор не восстанавливает структуру, потому что исходного байткода
+ метода в классе физически больше нет, есть только интерпретатор и таблица
+ операций. Коммерческие инструменты (например,
+ [Promon Shield](https://promon.io/security-news/virtual-machine-obfuscation-sdk-protection))
+ уже уносят это на уровень нативной библиотеки.
+- **Для нас это ломает собственное допущение** из
+ [`../stack.md`](../stack.md) §3: `noxis-verify` верифицирует метод через
+ `java -Xverify:all`, потому что это *обычный* байткод с валидным
+ `StackMapTable`. Виртуализированный метод — уже не обычный байткод,
+ верификатор проверит только обвязку интерпретатора, не тело. Big-ticket
+ item, не для ближайшего спринта.
+- **MBA** укладывается без архитектурных изменений — см. выше, §3.
+
+## Источники
+
+- [Elastic Security Labs — The Cost of Understanding: LLM-Driven Reverse Engineering vs Iterative LLM Obfuscation](https://www.elastic.co/security-labs/llm-reversing-vs-llm-obfuscation)
+- [Acoda: Adversarial Code Obfuscation for Defending against LLM-based Analysis (arXiv 2606.11755, ICSE-Companion '26)](https://arxiv.org/pdf/2606.11755)
+- [A Systematic Study on Static Control Flow Obfuscation Techniques in Java (arXiv 1809.11037)](https://arxiv.org/pdf/1809.11037)
+- [Promon Shield: Virtual Machine obfuscation SDK protection](https://promon.io/security-news/virtual-machine-obfuscation-sdk-protection)
+- [CoTDeceptor: Adversarial Code Obfuscation Against CoT-Enhanced LLM Code Agents (arXiv 2512.21250)](https://arxiv.org/pdf/2512.21250)
+- [Deconstructing Obfuscation: a four-dimensional framework for evaluating LLM deobfuscation (arXiv 2505.19887)](https://arxiv.org/pdf/2505.19887)
+- [Tigress — диверсифицирующий обфускатор для C, использованный в бенчмарке Elastic](https://tigress.wtf/)
diff --git a/docs/learnings/lessons.md b/docs/learnings/lessons.md
index ae1fee1..0fe7e1e 100644
--- a/docs/learnings/lessons.md
+++ b/docs/learnings/lessons.md
@@ -11,14 +11,14 @@
| № | провал EvoVis | наше решение | пасс | статус |
|---|---|---|---|---|
-| 1 | ключ лежит рядом с шифротекстом | вывод мастер-секрета в декодере, ключ из `master‖salt` | Strings | частично |
+| 1 | ключ лежит рядом с шифротекстом | вывод мастер-секрета в декодере, ключ из `master‖salt` | Strings | **готово** |
| 2 | переименованы типы, но не члены | rename полей/методов + семейства virtual | Rename | **готово** |
-| 3 | отладочная информация нетронута | strip + ложный `SourceFile`/номера строк | DebugInfo | план |
-| 4 | шаблонный flattening | 4 схемы CFG + мешанина внутри метода | Flow | план |
-| 5 | opaque-предикаты = `A+B` | ротация тождеств + предикаты от состояния | Constants | план |
-| 6 | junk-классы вне графа reachability | `weave_reachable` — обязательная вплетённость | Noise | план |
+| 3 | отладочная информация нетронута | strip + ложный `SourceFile`/номера строк | DebugInfo | **готово** (`SourceFile`; номера строк — план) |
+| 4 | шаблонный flattening | 4 схемы CFG + мешанина внутри метода | Flow | план (кадры готовы) |
+| 5 | opaque-предикаты = `A+B` | ротация тождеств + предикаты от состояния | Constants | **готово** (int/long) |
+| 6 | junk-классы вне графа reachability | `weave_reachable` — обязательная вплетённость | Noise | **готово** (без аннотаций) |
| 7 | имена говорят «я защита» | все артефакты получают данные псевдонимы | Rename | **готово** |
-| 8 | вторичные компоненты без защиты | весь jar, а не только основной класс | Resources | план |
+| 8 | вторичные компоненты без защиты | весь jar, а не только основной класс | Resources | **готово** |
| 9 | шифр без MAC и без per-string salt | salt на строку, уникальный nonce, MAC | Strings | **готово** |
## 1. Ключ рядом с шифротекстом
diff --git a/docs/learnings/priorities.md b/docs/learnings/priorities.md
index d3fb815..57352c7 100644
--- a/docs/learnings/priorities.md
+++ b/docs/learnings/priorities.md
@@ -17,9 +17,9 @@
| пункт | что это | пасс | статус |
|---|---|---|---|
| A1 | переименование **всех** символов: классы, поля, методы, локальные переменные, параметры, record-компоненты, enum-константы, `SourceFile` | Rename | ✅ готово |
-| A2 | вычистить `LineNumberTable`/`LocalVariableTable`/`SourceFile`/`InnerClasses`/`Signature`/`MethodParameters` | DebugInfo | план |
-| A3 | никаких сигнатурных имён у защитного кода, защитный код смешан с обычным | Rename + Noise | ✅ / план |
-| A4 | обфусцировать **весь** архив, включая ресурсы, mixin-JSON, access widener, service loader'ы | Resources | план |
+| A2 | вычистить `LineNumberTable`/`LocalVariableTable`/`SourceFile`/`InnerClasses`/`Signature`/`MethodParameters` | DebugInfo | ✅ готово |
+| A3 | никаких сигнатурных имён у защитного кода, защитный код смешан с обычным | Rename + Noise | ✅ готово |
+| A4 | обфусцировать **весь** архив, включая ресурсы, mixin-JSON, access widener, service loader'ы | Resources | ✅ готово |
| — | маппинг в отчёт (иначе не читаем свои же stack trace) | Rename | ✅ готово |
Это самый дешёвый блок и одновременно **самый большой утечка** в разобранном
@@ -30,49 +30,51 @@
| пункт | что это | пасс | статус |
|---|---|---|---|
-| B1 | ключ на строку + ротация 3–5 схем + MAC + уникальный nonce | Strings | ✅ схемы готовы |
-| B2 | **вывод** ключа в рантайме, а не константа в вызове | Strings + генератор декодера | ⛔ блокер |
-| B3 | константы — не тождества, а вычисления, разнесённые по блокам | Constants | план |
-| B4 | **ложная** отладочная информация: чужие имена переменных и строк | DebugInfo | план |
+| B1 | ключ на строку + ротация 3–5 схем + MAC + уникальный nonce | Strings | ✅ готово |
+| B2 | **вывод** ключа в рантайме, а не константа в вызове | Strings + генератор декодера | ✅ готово |
+| B3 | константы — не тождества, а вычисления, разнесённые по блокам | Constants | ✅ готово (int/long) |
+| B4 | **ложная** отладочная информация: чужие имена переменных и строк | DebugInfo | ✅ `SourceFile` |
B2 — главное отличие от разобранного образца: у него ключ был константным
выражением в вызове и расшифровывался статически, без JVM. Мастер-секрет
-должен собираться в рантайме из нескольких мест. Это требует сначала
-собрать класс-декодер (P2) — потому и блокер, а не «сделаем потом».
+теперь собирается в рантайме из двух частей (`k1 ^ k2`) декодером класса —
+см. [`../stack.md`](../stack.md) §4. B4 пока покрывает только `SourceFile`;
+ложные номера строк (`fake_line_numbers`) — см. [`../../todo.md`](../../todo.md) §3.6.
## P2 — контрольный поток
| пункт | что это | статус |
|---|---|---|
| C1 | **несколько схем** флаттенинга вместо одной, с мешаниной внутри метода | план |
-| C2 | верификатор обязателен: ошибка CFG = `VerifyError` | план |
-| C3 | реальный объём ≠ раздувание: бюджет на размер и время | план |
+| C2 | верификатор обязателен: ошибка CFG = `VerifyError` | ✅ готово (кадры + `java -Xverify:all`) |
+| C3 | реальный объём ≠ раздувание: бюджет на размер и время | ✅ размер; время — план |
-Приоритет здесь строго подчинён: **C2 раньше C1**. Включать Flow без
-проверки кадров `StackMapTable` — это гарантированный битый jar.
-Подробности: [`../stack.md`](../stack.md) §3.
+Приоритет здесь строго подчинён: **C2 раньше C1**. Кадры `StackMapTable`
+и приёмка `java -Xverify:all` уже готовы (см. [`../stack.md`](../stack.md)
+§3) — единственное, чего не хватает, это сами схемы флаттенинга (C1).
## P3 — измеримость (метрики, без которых ничего не доказано)
-Обязательный блок, иначе фраза «обфускация есть» ничего не означает:
+Обязательный блок, иначе фраза «обфускация есть» ничего не означает.
+Крейт `noxis-verify` реализован — вот что из списка уже цифра, а что ещё мнение:
-1. **Утечки имён.** Список эталонных осмысленных идентификаторов;
- если хоть один остался в `strings`/константах — тест падает.
-2. **Читаемость декомпилята.** Прогон JADX/CFR → доля распознанных структур
- (try/catch, switch, циклы), доля классов с восстановленными именами.
-3. **Противник.** Минимальный скрипт-деобфускатор (свёртка констант,
- regex-паттерны). Если он снимает больше порога — техника провалена.
- **Красный список паттернов держим в репозитории.**
-4. **Семантика.** Эталонные программы до и после — 100% совпадение.
-5. **Бюджеты.** Размер ≤ N%, время исполнения ≤ M%, отдельно — бюджет
- раскрытия строк (кэш, чтобы не платить на каждый вызов).
-6. **Стабильность между сборками.** Имена и схемы **меняются** от билда
- к билду: у разобранного образца шифр был один на все версии — готовое
- окно для обучения по частотам.
+1. **Утечки имён.** ✅ `collect_names`/`find_leaks` — сверка идентификаторов
+ с учётом keep-списка.
+2. **Читаемость декомпилята.** ⬜ Прогон JADX/CFR → доля распознанных
+ структур — не начато.
+3. **Противник.** ✅ `scan_secrets` + `RED_PATTERNS` держим в репозитории;
+ ⬜ полноценный скрипт-деобфускатор (свёртка констант) как тест — план.
+4. **Семантика.** ⬜ Эталонные программы до/после — не начато
+ (сейчас семантику подтверждает только `java -Xverify:all`).
+5. **Бюджеты.** ✅ размер (`inflation_ratio`/`check_inflation`);
+ ⬜ время исполнения — не начато.
+6. **Стабильность между сборками.** ✅ `mappings_changed` — маппинги
+ обязаны отличаться между прогонами с разным seed.
-Это блок `noxis-verify`: красные паттерны + `java -Xverify:all` +
-метрики. Единственное место, где «нам кажется защищённым» заменяется
-цифрой.
+Итог: `noxis-verify` закрывает красные паттерны, утечки имён,
+`java -Xverify:all`, бюджет размера и стабильность маппингов. Остаются
+декомпилят-метрика, бюджет времени и эталонная семантика — три пункта,
+где «нам кажется защищённым» всё ещё мнение, а не цифра.
## P4 — долгий горизонт (пока не делаем)
@@ -105,12 +107,14 @@ writer).
## Порядок выполнения
-Смотрите [`../../todo.md`](../../todo.md). Кратко:
+Смотрите [`../../todo.md`](../../todo.md). P0–P3 и P3-измеримость уже
+готовы; единственный крупный пункт, который остался, — схемы `Flow` (C1),
+потому что верификатор (C2) для них уже есть:
```text
-P0 DebugInfo + Resources → снимает главные утечки, почти бесплатно
-P1 Strings (B2) + Constants → ключ выводится, схемы ротируются
-P1 backend (ristretto) + frames
-P2 Flow (C1) после верификатора
-P3 метрики (F) и красные паттерны
+✅ P0 DebugInfo + Resources → главные утечки сняты
+✅ P1 Strings (B2) + Constants → ключ выводится, схемы ротируются
+✅ P1 backend (ristretto) + frames
+⬜ P2 Flow (C1) — единственное, что ждёт реализации; верификатор готов
+✅ P3 метрики, красные паттерны, CLI
```
diff --git a/docs/techniques.md b/docs/techniques.md
index 7359eba..dfde703 100644
--- a/docs/techniques.md
+++ b/docs/techniques.md
@@ -130,7 +130,10 @@ opaque-предикаты — условия, истинность которы
- Слишком «чистый» класс (`LineNumberTable` есть, а `SourceFile` = `X.java`,
которого нет) — тоже признак.
-**Статус: не реализован.**
+**Статус: готово** (strip + `spoof_source_file`, детерминированная подмена
+от мастер-сида). Не реализовано: `fake_line_numbers` (ложные номера строк,
+а не только `SourceFile`) и `strip_source_file` (полное удаление вместо
+подмены) — см. [`todo.md`](../todo.md) §3.6.
## 7. Ресурсы (`Resources`)
@@ -147,7 +150,10 @@ opaque-предикаты — условия, истинность которы
трогать нельзя.
- Промах в патче → тихо неработающий refmap, ловится только запуском.
-**Статус: не реализован (крейт `noxis-jar` — план).**
+**Статус: готово** (патч текстовых ресурсов по маппингу, включая
+`fabric.mod.json`/`*.mixins.json`/`META-INF/services/*`; срез подписей
+в `noxis-jar`). Не реализовано: типизированный патч бинарных/специфичных
+форматов (refmap, access widener) сверх общего boundary-replace.
## Сводная таблица
@@ -155,11 +161,11 @@ opaque-предикаты — условия, истинность которы
|---|---|---|---|---|
| Rename | ★★★★★ | низкий | низкая | готово |
| Strings | ★★★★ | средний | средняя | готово |
-| Resources | ★★★★★ | низкий | низкая | план |
-| DebugInfo | ★★★ | низкий | низкая | план |
+| Resources | ★★★★★ | низкий | низкая | готово |
+| DebugInfo | ★★★ | низкий | низкая | готово |
| Constants | ★★ | низкий | низкая | готово (int/long) |
| Noise | ★ | низкий | раздувание | готово (без аннотаций) |
-| Flow | ★★★★ | **высокий** | высокая | план, ждёт frames |
+| Flow | ★★★★ | **высокий** | высокая | план, кадры готовы |
Вывод, который стоит держать в голове: **Rename + Resources + DebugInfo**
дают большую часть защиты за небольшую цену. Flow — единственная техника,