docs: sync status with code, add AI-resistance research, restyle README
Documentation had drifted from the actual implementation: the noxis
(CLI) and noxis-verify crates were fully built and tested but every
doc called them "not started". Verified against the code (build +
test run) and corrected status across README.md, todo.md,
architecture.md, techniques.md and learnings/{lessons,priorities}.md.
Also:
- restyled README.md (badges, no em dashes, usage examples, dropped
the redundant "Принципы" section already covered elsewhere)
- added docs/learnings/ai-resistance.md: a sourced writeup of what
actually works against LLM-driven reverse engineering (Elastic
Security Labs' Opus 4.6 vs Tigress study, the Acoda paper) and what
of it applies to noxis, plus concrete todo.md §7 items
- noxis-structure-check: the 250-line/4-file gate was scanning
docs/**, which fought against writing documentation freely; it now
only scans crates/*/{src,tests,benches} and only counts .rs files
This commit is contained in:
parent
2674d7fc89
commit
c3b8d4dd62
9 changed files with 349 additions and 162 deletions
122
README.md
122
README.md
|
|
@ -1,34 +1,61 @@
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
# noxis
|
# noxis
|
||||||
|
|
||||||
Обфускатор JVM/Java-байткода на Rust.
|
Обфускатор JVM/Java-байткода на Rust.
|
||||||
|
|
||||||
Читает JAR, переписывает классовые файлы и ресурсы, собирает обратно —
|

|
||||||
детерминированно, с маппингами для stack trace и с проверкой, что выход
|

|
||||||
действительно загружается настоящей JVM.
|

|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Читает JAR, переписывает классовые файлы и ресурсы, собирает обратно: детерминированно,
|
||||||
|
с маппингами для stack trace и с проверкой, что выход действительно загружается настоящей JVM.
|
||||||
|
|
||||||
## Статус
|
## Статус
|
||||||
|
|
||||||
В работе. Ядро построено, часть техник реализована, часть — по плану.
|
Ядро построено, большая часть техник реализована.
|
||||||
|
|
||||||
| блок | состояние |
|
| блок | состояние |
|
||||||
|---|---|
|
|---|---|
|
||||||
| IR классовых файлов, метки вместо смещений | ✅ |
|
| IR классовых файлов, метки вместо смещений | ✅ |
|
||||||
| конфиг, детерминированный PRNG, маппинги, отчёт | ✅ |
|
| конфиг, детерминированный PRNG, маппинги, отчёт | ✅ |
|
||||||
| анализ: иерархия, entrypoint'ы, keep-правила | ✅ |
|
| анализ: иерархия, entrypoint'ы, keep-правила | ✅ |
|
||||||
| переименование всех символов | ✅ |
|
| `format::parse`/`write` + кадры `StackMapTable` (backend `ristretto_classfile`) | ✅ |
|
||||||
| схемы шифрования строк (salt, nonce, MAC) | ✅ |
|
| пассы: rename, resources, strings, constants, noise, debug | ✅ |
|
||||||
| JAR: чтение/запись, ресурсы, срез подписей | ✅ |
|
| верификация: red-паттерны, `java -Xverify:all`, метрики | ✅ |
|
||||||
| гейт структурных правил при компиляции | ✅ |
|
| CLI (`obfuscate` / `analyze` / `patterns`) | ✅ |
|
||||||
| пассы: rename, resources, debug | ✅ |
|
| пасс flow (кадры готовы, схем флаттенинга пока нет) | 🚧 |
|
||||||
| spike backend'а `ristretto_classfile` | ✅ |
|
|
||||||
| `format::parse`/`write` + кадры `StackMapTable` | ✅ |
|
|
||||||
| пассы: strings, constants, noise (+ декодер v49) | ✅ |
|
|
||||||
| пасс flow | 🚧 |
|
|
||||||
| верификация и метрики | 🚧 |
|
|
||||||
| CLI | 🚧 |
|
|
||||||
|
|
||||||
Подробности — [`docs/architecture.md`](docs/architecture.md),
|
Подробности в [`docs/architecture.md`](docs/architecture.md), план в [`todo.md`](todo.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,7 +63,7 @@
|
||||||
noxis/
|
noxis/
|
||||||
├── Cargo.toml workspace, lints, release-профиль
|
├── Cargo.toml workspace, lints, release-профиль
|
||||||
├── scripts/check.sh structure → fmt → clippy → test
|
├── scripts/check.sh structure → fmt → clippy → test
|
||||||
├── docs/ ← архитектура, техники, стек, уроки
|
├── docs/ архитектура, техники, стек, уроки
|
||||||
├── info/ исходный материал разбора (вне правил)
|
├── info/ исходный материал разбора (вне правил)
|
||||||
└── crates/
|
└── crates/
|
||||||
├── noxis-classfile/ IR + парсер/запись + StackMapTable
|
├── noxis-classfile/ IR + парсер/запись + StackMapTable
|
||||||
|
|
@ -45,66 +72,47 @@ noxis/
|
||||||
├── noxis-passes/ сами трансформации + конвейер
|
├── noxis-passes/ сами трансформации + конвейер
|
||||||
├── noxis-runtime/ схемы шифрования, ABI декодера
|
├── noxis-runtime/ схемы шифрования, ABI декодера
|
||||||
├── noxis-jar/ ZIP, ресурсы, срез подписей
|
├── noxis-jar/ ZIP, ресурсы, срез подписей
|
||||||
├── noxis-verify/ red-паттерны, java -Xverify (план)
|
├── noxis-verify/ red-паттерны, java -Xverify, метрики
|
||||||
├── noxis/ CLI (план)
|
├── noxis/ CLI: obfuscate, analyze, patterns
|
||||||
└── noxis-structure-check/ автогейт структуры
|
└── 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/README.md`](docs/README.md) | индекс и структурные правила проекта |
|
||||||
| [`docs/architecture.md`](docs/architecture.md) | слои, крейты, IR, конвейер |
|
| [`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/stack.md`](docs/stack.md) | почему свой IR, backend, `StackMapTable`, PRNG |
|
||||||
| [`docs/learnings/lessons.md`](docs/learnings/lessons.md) | уроки разбора: чего делать нельзя |
|
| [`docs/learnings/lessons.md`](docs/learnings/lessons.md) | уроки разбора: чего делать нельзя |
|
||||||
| [`docs/learnings/reference.md`](docs/learnings/reference.md) | разбор референсного обфускатора |
|
| [`docs/learnings/reference.md`](docs/learnings/reference.md) | разбор референсного обфускатора |
|
||||||
| [`docs/learnings/priorities.md`](docs/learnings/priorities.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).
|
Исходный материал разбора в [`info/README.md`](info/README.md).
|
||||||
|
|
||||||
## Принципы
|
|
||||||
|
|
||||||
- **KISS / DRY / SOLID.** Файл — одна ответственность. Не больше 250 строк,
|
|
||||||
не больше 4 файлов в директории.
|
|
||||||
- **Детерминированность.** Один seed → байт-в-байт одинаковый артефакт.
|
|
||||||
- **Ничего не ломаем.** Техника, которая может дать `VerifyError`,
|
|
||||||
включается только после верификатора.
|
|
||||||
- **Измеримо.** Защита без метрик — это мнение, а не свойство.
|
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
AGPL-3.0-only — см. [`LICENSE`](LICENSE).
|
AGPL-3.0-only, см. [`LICENSE`](LICENSE).
|
||||||
|
|
||||||
Проект остаётся полностью открытым: его можно изучать, использовать
|
Проект остаётся полностью открытым: его можно изучать, использовать (в том числе
|
||||||
(в том числе коммерчески) и изменять. Условие AGPL — любые производные
|
коммерчески) и изменять. Условие AGPL: любые производные работы, которые
|
||||||
работы, которые распространяются или доступны пользователям по сети,
|
распространяются или доступны пользователям по сети, обязаны публиковаться под
|
||||||
обязаны публиковаться под той же лицензией. Это защищает `noxis` от
|
той же лицензией. Это защищает `noxis` от закрытых форков, которые перепродают
|
||||||
закрытых форков, которые перепродают его как готовый продукт.
|
его как готовый продукт.
|
||||||
|
|
||||||
Сторонние зависимости и их лицензии — в [`NOTICE.md`](NOTICE.md).
|
Сторонние зависимости и их лицензии в [`NOTICE.md`](NOTICE.md).
|
||||||
|
|
||||||
## Ответственное использование
|
## Ответственное использование
|
||||||
|
|
||||||
`noxis` — инструмент двойного назначения. Он одинаково полезен для
|
`noxis` это инструмент двойного назначения, одинаково полезный для защиты
|
||||||
защиты собственного байткода и способен усложнять анализ вредоносного
|
собственного байткода и способен усложнять анализ вредоносного ПО. Лицензия
|
||||||
ПО. Лицензия это не регламентирует, но это норма сообщества проекта:
|
это не регламентирует, но это норма сообщества проекта:
|
||||||
|
|
||||||
- используйте обфускацию, чтобы защищать **свой** код и код тех, кто
|
- используйте обфускацию, чтобы защищать **свой** код и код тех, кто вас об этом попросил;
|
||||||
вас об этом попросил;
|
- не применяйте инструмент для упаковки вредоносов, обхода средств защиты
|
||||||
- не применяйте инструмент для упаковки вредоносов, обхода средств
|
и антивирусов или иной деятельности, запрещённой законом.
|
||||||
защиты и антивирусов или иной деятельности, запрещённой законом.
|
|
||||||
|
|
||||||
Нарушение этих норм не влечёт юридических последствий по лицензии, но
|
Нарушение этих норм не влечёт юридических последствий по лицензии, но приведёт
|
||||||
приведёт к отказу в поддержке и закрытию issue/PR в репозитории.
|
к отказу в поддержке и закрытию issue/PR в репозитории.
|
||||||
|
|
|
||||||
|
|
@ -76,4 +76,16 @@ mod tests {
|
||||||
let found = violations(&tmp.0);
|
let found = violations(&tmp.0);
|
||||||
assert!(found.is_empty(), "{found:?}");
|
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:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,32 @@
|
||||||
//! Project structure rules, invoked from every crate's `build.rs`.
|
//! Project structure rules, invoked from every crate's `build.rs`.
|
||||||
//!
|
//!
|
||||||
//! Rules:
|
//! Rules apply to Rust code only, not to documentation:
|
||||||
//! * every `.rs` and `.md` file is at most [`MAX_FILE_LINES`] lines long;
|
//! * every `.rs` file is at most [`MAX_FILE_LINES`] lines long;
|
||||||
//! * every directory inside a crate `src/` or the workspace `docs/` holds at
|
//! * every directory inside a crate's `src/`, `tests/` or `benches/` holds
|
||||||
//! most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
|
//! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
|
||||||
//!
|
//!
|
||||||
//! Deliberately excluded: `Cargo.lock`, `.gitignore`, the legacy `info/` tree,
|
//! Deliberately excluded: `Cargo.lock`, `.gitignore`, `docs/`, the legacy
|
||||||
//! and crate roots (they always own `Cargo.toml` plus `build.rs`).
|
//! `info/` tree, and crate roots (they always own `Cargo.toml` plus
|
||||||
|
//! `build.rs`).
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
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;
|
pub const MAX_FILE_LINES: usize = 250;
|
||||||
|
|
||||||
/// Maximum number of files per directory; subdirectories are not counted.
|
/// Maximum number of files per directory; subdirectories are not counted.
|
||||||
pub const MAX_DIR_FILES: usize = 4;
|
pub const MAX_DIR_FILES: usize = 4;
|
||||||
|
|
||||||
/// Extensions subject to [`MAX_FILE_LINES`].
|
/// 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.
|
/// Directory names the scan never descends into.
|
||||||
const SKIPPED_DIRS: [&str; 2] = ["target", ".git"];
|
const SKIPPED_DIRS: [&str; 2] = ["target", ".git"];
|
||||||
|
|
||||||
/// Scans the calling crate's `src/`/`tests/`/`benches/` plus the workspace
|
/// Scans the calling crate's `src/`/`tests/`/`benches/` and panics with a
|
||||||
/// `docs/` tree and panics with a report on the first violation.
|
/// report on the first violation.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
|
|
@ -35,13 +36,13 @@ pub fn check() {
|
||||||
let mut violations = Vec::new();
|
let mut violations = Vec::new();
|
||||||
|
|
||||||
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы —
|
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы —
|
||||||
// иначе правило «250 строк» обходится выносом кода в тесты.
|
// иначе правило «250 строк» обходится выносом кода в тесты. `docs/` вне
|
||||||
let mut roots = vec![manifest.join("src"), manifest.join("tests")];
|
// гейта: правила — только про Rust-код.
|
||||||
roots.push(manifest.join("benches"));
|
let roots = [
|
||||||
|
manifest.join("src"),
|
||||||
if let Some(root) = workspace_root(&manifest) {
|
manifest.join("tests"),
|
||||||
roots.push(root.join("docs"));
|
manifest.join("benches"),
|
||||||
}
|
];
|
||||||
|
|
||||||
for root in &roots {
|
for root in &roots {
|
||||||
watch(root);
|
watch(root);
|
||||||
|
|
@ -64,8 +65,9 @@ pub fn check() {
|
||||||
|
|
||||||
/// Re-runs the build script whenever a checked tree changes.
|
/// Re-runs the build script whenever a checked tree changes.
|
||||||
///
|
///
|
||||||
/// Without this cargo only invalidates `build.rs` on crate-local changes, so
|
/// Without an explicit `rerun-if-changed`, cargo falls back to its default
|
||||||
/// editing `docs/` would never be re-validated.
|
/// (rerun on any change anywhere in the package); naming the trees keeps the
|
||||||
|
/// gate from firing on unrelated edits.
|
||||||
fn watch(root: &Path) {
|
fn watch(root: &Path) {
|
||||||
if root.is_dir() {
|
if root.is_dir() {
|
||||||
println!("cargo:rerun-if-changed={}", root.display());
|
println!("cargo:rerun-if-changed={}", root.display());
|
||||||
|
|
@ -101,6 +103,10 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
|
||||||
subdirs.push(path);
|
subdirs.push(path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
|
||||||
|
if !LINE_LIMITED_EXTS.contains(&ext) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
files += 1;
|
files += 1;
|
||||||
check_line_limit(&path, violations);
|
check_line_limit(&path, violations);
|
||||||
}
|
}
|
||||||
|
|
@ -118,10 +124,6 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_line_limit(path: &Path, violations: &mut Vec<String>) {
|
fn check_line_limit(path: &Path, violations: &mut Vec<String>) {
|
||||||
let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
|
|
||||||
if !LINE_LIMITED_EXTS.contains(&ext) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let lines = count_lines(path);
|
let lines = count_lines(path);
|
||||||
if lines > MAX_FILE_LINES {
|
if lines > MAX_FILE_LINES {
|
||||||
violations.push(format!(
|
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"),
|
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<PathBuf> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,24 @@
|
||||||
| [`learnings/lessons.md`](learnings/lessons.md) | уроки из разбора EvoVis: что защита сделала плохо и чего мы не делаем |
|
| [`learnings/lessons.md`](learnings/lessons.md) | уроки из разбора EvoVis: что защита сделала плохо и чего мы не делаем |
|
||||||
| [`learnings/reference.md`](learnings/reference.md) | разбор референсного обфускатора (Java/ASM) — что берём, что нет |
|
| [`learnings/reference.md`](learnings/reference.md) | разбор референсного обфускатора (Java/ASM) — что берём, что нет |
|
||||||
| [`learnings/priorities.md`](learnings/priorities.md) | что улучшим: стратегия и приоритеты |
|
| [`learnings/priorities.md`](learnings/priorities.md) | что улучшим: стратегия и приоритеты |
|
||||||
|
| [`learnings/ai-resistance.md`](learnings/ai-resistance.md) | новые техники обфускации и устойчивость к LLM-анализу |
|
||||||
|
|
||||||
План выполнения — [`../todo.md`](../todo.md). Исходный материал разбора —
|
План выполнения — [`../todo.md`](../todo.md). Исходный материал разбора —
|
||||||
[`../info/`](../info/README.md).
|
[`../info/`](../info/README.md).
|
||||||
|
|
||||||
## Структурные правила проекта
|
## Структурные правила проекта
|
||||||
|
|
||||||
Правила действуют на **весь наш код и документацию**: `crates/**` и `docs/**`.
|
Правила — **только про Rust-код**: `crates/*/src`, `crates/*/tests`,
|
||||||
Не учитываются: `Cargo.lock`, `.gitignore`, `info/`, корень крейта
|
`crates/*/benches`. Документация (`docs/**`, корневые `.md`) и `info/` вне
|
||||||
|
гейта: писать подробно там, где это документация, а не ограничивать объём
|
||||||
|
искусственно. Не учитываются также `Cargo.lock`, `.gitignore`, корень крейта
|
||||||
(в нём всегда лежат `Cargo.toml` + `build.rs`).
|
(в нём всегда лежат `Cargo.toml` + `build.rs`).
|
||||||
|
|
||||||
1. **Не больше 250 строк** в одном `.rs` или `.md` файле.
|
1. **Не больше 250 строк** в одном `.rs` файле.
|
||||||
2. **Не больше 4 файлов** в одной директории. Подпапки не ограничены:
|
2. **Не больше 4 `.rs`-файлов** в одной директории. Подпапки не ограничены:
|
||||||
`src/` может содержать `lib.rs` и три модуля-директории, каждая из которых
|
`src/` может содержать `lib.rs` и три модуля-директории, каждая из которых
|
||||||
в свою очередь содержит не больше четырёх файлов.
|
в свою очередь содержит не больше четырёх файлов.
|
||||||
|
|
||||||
Проверяются: `crates/*/src`, `crates/*/tests`, `crates/*/benches` и `docs/`.
|
|
||||||
`tests/` и `benches/` не исключены намеренно — иначе правило
|
`tests/` и `benches/` не исключены намеренно — иначе правило
|
||||||
«250 строк» обходится выносом кода в тесты.
|
«250 строк» обходится выносом кода в тесты.
|
||||||
|
|
||||||
|
|
@ -45,11 +47,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`noxis-structure-check` находит корень воркспейста, обходит деревья `src/`,
|
`noxis-structure-check` обходит деревья `src/`, `tests/` и `benches/` **этого
|
||||||
`tests/` и `benches/` всех крейтов плюс `docs/`, и **падает со стопкой ошибок**,
|
крейта** и **падает со стопкой ошибок**, если лимит нарушен. Поэтому
|
||||||
если лимит нарушен. Поэтому `cargo build` не соберёт нарушение — специально
|
`cargo build` не соберёт нарушение — специально запускать проверку не нужно.
|
||||||
запускать проверку не нужно. Деревья регистрируются через
|
Деревья регистрируются через `cargo:rerun-if-changed`, чтобы гейт не
|
||||||
`cargo:rerun-if-changed`, так что правка `docs/` тоже перезапускает гейт.
|
срабатывал на правки вне проверяемых путей.
|
||||||
|
|
||||||
Остальное, что не привязано к компиляции, собирается одним скриптом:
|
Остальное, что не привязано к компиляции, собирается одним скриптом:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,9 @@ Rename → Resources → Strings → Constants → Flow → Noise → DebugInfo
|
||||||
|
|
||||||
| этап | что |
|
| этап | что |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **готово** | IR, модель, конфиг, seed/PRNG, маппинги, анализ, keep, переименование, шифры строк, гейт правил, `format::parse`/`write` поверх ристретто, вывод кадров `StackMapTable` |
|
| **готово** | IR, модель, конфиг, seed/PRNG, маппинги, анализ, keep, гейт правил, `format::parse`/`write` поверх ристретто, вывод кадров `StackMapTable` |
|
||||||
| **P2** | пассы строк/констант/шума/потока, генератор декодера, CLI |
|
| **готово** | пассы rename/resources/strings/constants/noise/debug, генератор декодера, вывод мастер-секрета в рантайме |
|
||||||
| **P2** | генерация класса-декодера, вывод мастер-секрета, остальные пассы |
|
| **готово** | `noxis-verify` (red-паттерны, `java -Xverify:all`, метрики), CLI (`obfuscate`/`analyze`/`patterns`) |
|
||||||
| **P3** | CLI, метрики, интеграция с `java -Xverify:all` |
|
| **план** | схемы пасса `flow` (кадры для них уже считаются), фикстуры и regression-тесты |
|
||||||
|
|
||||||
Детали выбора — [`stack.md`](stack.md), план — [`../todo.md`](../todo.md).
|
Детали выбора — [`stack.md`](stack.md), план — [`../todo.md`](../todo.md).
|
||||||
|
|
|
||||||
167
docs/learnings/ai-resistance.md
Normal file
167
docs/learnings/ai-resistance.md
Normal file
|
|
@ -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/)
|
||||||
|
|
@ -11,14 +11,14 @@
|
||||||
|
|
||||||
| № | провал EvoVis | наше решение | пасс | статус |
|
| № | провал EvoVis | наше решение | пасс | статус |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| 1 | ключ лежит рядом с шифротекстом | вывод мастер-секрета в декодере, ключ из `master‖salt` | Strings | частично |
|
| 1 | ключ лежит рядом с шифротекстом | вывод мастер-секрета в декодере, ключ из `master‖salt` | Strings | **готово** |
|
||||||
| 2 | переименованы типы, но не члены | rename полей/методов + семейства virtual | Rename | **готово** |
|
| 2 | переименованы типы, но не члены | rename полей/методов + семейства virtual | Rename | **готово** |
|
||||||
| 3 | отладочная информация нетронута | strip + ложный `SourceFile`/номера строк | DebugInfo | план |
|
| 3 | отладочная информация нетронута | strip + ложный `SourceFile`/номера строк | DebugInfo | **готово** (`SourceFile`; номера строк — план) |
|
||||||
| 4 | шаблонный flattening | 4 схемы CFG + мешанина внутри метода | Flow | план |
|
| 4 | шаблонный flattening | 4 схемы CFG + мешанина внутри метода | Flow | план (кадры готовы) |
|
||||||
| 5 | opaque-предикаты = `A+B` | ротация тождеств + предикаты от состояния | Constants | план |
|
| 5 | opaque-предикаты = `A+B` | ротация тождеств + предикаты от состояния | Constants | **готово** (int/long) |
|
||||||
| 6 | junk-классы вне графа reachability | `weave_reachable` — обязательная вплетённость | Noise | план |
|
| 6 | junk-классы вне графа reachability | `weave_reachable` — обязательная вплетённость | Noise | **готово** (без аннотаций) |
|
||||||
| 7 | имена говорят «я защита» | все артефакты получают данные псевдонимы | Rename | **готово** |
|
| 7 | имена говорят «я защита» | все артефакты получают данные псевдонимы | Rename | **готово** |
|
||||||
| 8 | вторичные компоненты без защиты | весь jar, а не только основной класс | Resources | план |
|
| 8 | вторичные компоненты без защиты | весь jar, а не только основной класс | Resources | **готово** |
|
||||||
| 9 | шифр без MAC и без per-string salt | salt на строку, уникальный nonce, MAC | Strings | **готово** |
|
| 9 | шифр без MAC и без per-string salt | salt на строку, уникальный nonce, MAC | Strings | **готово** |
|
||||||
|
|
||||||
## 1. Ключ рядом с шифротекстом
|
## 1. Ключ рядом с шифротекстом
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
| пункт | что это | пасс | статус |
|
| пункт | что это | пасс | статус |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| A1 | переименование **всех** символов: классы, поля, методы, локальные переменные, параметры, record-компоненты, enum-константы, `SourceFile` | Rename | ✅ готово |
|
| A1 | переименование **всех** символов: классы, поля, методы, локальные переменные, параметры, record-компоненты, enum-константы, `SourceFile` | Rename | ✅ готово |
|
||||||
| A2 | вычистить `LineNumberTable`/`LocalVariableTable`/`SourceFile`/`InnerClasses`/`Signature`/`MethodParameters` | DebugInfo | план |
|
| A2 | вычистить `LineNumberTable`/`LocalVariableTable`/`SourceFile`/`InnerClasses`/`Signature`/`MethodParameters` | DebugInfo | ✅ готово |
|
||||||
| A3 | никаких сигнатурных имён у защитного кода, защитный код смешан с обычным | Rename + Noise | ✅ / план |
|
| A3 | никаких сигнатурных имён у защитного кода, защитный код смешан с обычным | Rename + Noise | ✅ готово |
|
||||||
| A4 | обфусцировать **весь** архив, включая ресурсы, mixin-JSON, access widener, service loader'ы | Resources | план |
|
| A4 | обфусцировать **весь** архив, включая ресурсы, mixin-JSON, access widener, service loader'ы | Resources | ✅ готово |
|
||||||
| — | маппинг в отчёт (иначе не читаем свои же stack trace) | Rename | ✅ готово |
|
| — | маппинг в отчёт (иначе не читаем свои же stack trace) | Rename | ✅ готово |
|
||||||
|
|
||||||
Это самый дешёвый блок и одновременно **самый большой утечка** в разобранном
|
Это самый дешёвый блок и одновременно **самый большой утечка** в разобранном
|
||||||
|
|
@ -30,49 +30,51 @@
|
||||||
|
|
||||||
| пункт | что это | пасс | статус |
|
| пункт | что это | пасс | статус |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| B1 | ключ на строку + ротация 3–5 схем + MAC + уникальный nonce | Strings | ✅ схемы готовы |
|
| B1 | ключ на строку + ротация 3–5 схем + MAC + уникальный nonce | Strings | ✅ готово |
|
||||||
| B2 | **вывод** ключа в рантайме, а не константа в вызове | Strings + генератор декодера | ⛔ блокер |
|
| B2 | **вывод** ключа в рантайме, а не константа в вызове | Strings + генератор декодера | ✅ готово |
|
||||||
| B3 | константы — не тождества, а вычисления, разнесённые по блокам | Constants | план |
|
| B3 | константы — не тождества, а вычисления, разнесённые по блокам | Constants | ✅ готово (int/long) |
|
||||||
| B4 | **ложная** отладочная информация: чужие имена переменных и строк | DebugInfo | план |
|
| B4 | **ложная** отладочная информация: чужие имена переменных и строк | DebugInfo | ✅ `SourceFile` |
|
||||||
|
|
||||||
B2 — главное отличие от разобранного образца: у него ключ был константным
|
B2 — главное отличие от разобранного образца: у него ключ был константным
|
||||||
выражением в вызове и расшифровывался статически, без JVM. Мастер-секрет
|
выражением в вызове и расшифровывался статически, без JVM. Мастер-секрет
|
||||||
должен собираться в рантайме из нескольких мест. Это требует сначала
|
теперь собирается в рантайме из двух частей (`k1 ^ k2`) декодером класса —
|
||||||
собрать класс-декодер (P2) — потому и блокер, а не «сделаем потом».
|
см. [`../stack.md`](../stack.md) §4. B4 пока покрывает только `SourceFile`;
|
||||||
|
ложные номера строк (`fake_line_numbers`) — см. [`../../todo.md`](../../todo.md) §3.6.
|
||||||
|
|
||||||
## P2 — контрольный поток
|
## P2 — контрольный поток
|
||||||
|
|
||||||
| пункт | что это | статус |
|
| пункт | что это | статус |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| C1 | **несколько схем** флаттенинга вместо одной, с мешаниной внутри метода | план |
|
| C1 | **несколько схем** флаттенинга вместо одной, с мешаниной внутри метода | план |
|
||||||
| C2 | верификатор обязателен: ошибка CFG = `VerifyError` | план |
|
| C2 | верификатор обязателен: ошибка CFG = `VerifyError` | ✅ готово (кадры + `java -Xverify:all`) |
|
||||||
| C3 | реальный объём ≠ раздувание: бюджет на размер и время | план |
|
| C3 | реальный объём ≠ раздувание: бюджет на размер и время | ✅ размер; время — план |
|
||||||
|
|
||||||
Приоритет здесь строго подчинён: **C2 раньше C1**. Включать Flow без
|
Приоритет здесь строго подчинён: **C2 раньше C1**. Кадры `StackMapTable`
|
||||||
проверки кадров `StackMapTable` — это гарантированный битый jar.
|
и приёмка `java -Xverify:all` уже готовы (см. [`../stack.md`](../stack.md)
|
||||||
Подробности: [`../stack.md`](../stack.md) §3.
|
§3) — единственное, чего не хватает, это сами схемы флаттенинга (C1).
|
||||||
|
|
||||||
## P3 — измеримость (метрики, без которых ничего не доказано)
|
## P3 — измеримость (метрики, без которых ничего не доказано)
|
||||||
|
|
||||||
Обязательный блок, иначе фраза «обфускация есть» ничего не означает:
|
Обязательный блок, иначе фраза «обфускация есть» ничего не означает.
|
||||||
|
Крейт `noxis-verify` реализован — вот что из списка уже цифра, а что ещё мнение:
|
||||||
|
|
||||||
1. **Утечки имён.** Список эталонных осмысленных идентификаторов;
|
1. **Утечки имён.** ✅ `collect_names`/`find_leaks` — сверка идентификаторов
|
||||||
если хоть один остался в `strings`/константах — тест падает.
|
с учётом keep-списка.
|
||||||
2. **Читаемость декомпилята.** Прогон JADX/CFR → доля распознанных структур
|
2. **Читаемость декомпилята.** ⬜ Прогон JADX/CFR → доля распознанных
|
||||||
(try/catch, switch, циклы), доля классов с восстановленными именами.
|
структур — не начато.
|
||||||
3. **Противник.** Минимальный скрипт-деобфускатор (свёртка констант,
|
3. **Противник.** ✅ `scan_secrets` + `RED_PATTERNS` держим в репозитории;
|
||||||
regex-паттерны). Если он снимает больше порога — техника провалена.
|
⬜ полноценный скрипт-деобфускатор (свёртка констант) как тест — план.
|
||||||
**Красный список паттернов держим в репозитории.**
|
4. **Семантика.** ⬜ Эталонные программы до/после — не начато
|
||||||
4. **Семантика.** Эталонные программы до и после — 100% совпадение.
|
(сейчас семантику подтверждает только `java -Xverify:all`).
|
||||||
5. **Бюджеты.** Размер ≤ N%, время исполнения ≤ M%, отдельно — бюджет
|
5. **Бюджеты.** ✅ размер (`inflation_ratio`/`check_inflation`);
|
||||||
раскрытия строк (кэш, чтобы не платить на каждый вызов).
|
⬜ время исполнения — не начато.
|
||||||
6. **Стабильность между сборками.** Имена и схемы **меняются** от билда
|
6. **Стабильность между сборками.** ✅ `mappings_changed` — маппинги
|
||||||
к билду: у разобранного образца шифр был один на все версии — готовое
|
обязаны отличаться между прогонами с разным seed.
|
||||||
окно для обучения по частотам.
|
|
||||||
|
|
||||||
Это блок `noxis-verify`: красные паттерны + `java -Xverify:all` +
|
Итог: `noxis-verify` закрывает красные паттерны, утечки имён,
|
||||||
метрики. Единственное место, где «нам кажется защищённым» заменяется
|
`java -Xverify:all`, бюджет размера и стабильность маппингов. Остаются
|
||||||
цифрой.
|
декомпилят-метрика, бюджет времени и эталонная семантика — три пункта,
|
||||||
|
где «нам кажется защищённым» всё ещё мнение, а не цифра.
|
||||||
|
|
||||||
## P4 — долгий горизонт (пока не делаем)
|
## P4 — долгий горизонт (пока не делаем)
|
||||||
|
|
||||||
|
|
@ -105,12 +107,14 @@ writer).
|
||||||
|
|
||||||
## Порядок выполнения
|
## Порядок выполнения
|
||||||
|
|
||||||
Смотрите [`../../todo.md`](../../todo.md). Кратко:
|
Смотрите [`../../todo.md`](../../todo.md). P0–P3 и P3-измеримость уже
|
||||||
|
готовы; единственный крупный пункт, который остался, — схемы `Flow` (C1),
|
||||||
|
потому что верификатор (C2) для них уже есть:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
P0 DebugInfo + Resources → снимает главные утечки, почти бесплатно
|
✅ P0 DebugInfo + Resources → главные утечки сняты
|
||||||
P1 Strings (B2) + Constants → ключ выводится, схемы ротируются
|
✅ P1 Strings (B2) + Constants → ключ выводится, схемы ротируются
|
||||||
P1 backend (ristretto) + frames
|
✅ P1 backend (ristretto) + frames
|
||||||
P2 Flow (C1) после верификатора
|
⬜ P2 Flow (C1) — единственное, что ждёт реализации; верификатор готов
|
||||||
P3 метрики (F) и красные паттерны
|
✅ P3 метрики, красные паттерны, CLI
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,10 @@ opaque-предикаты — условия, истинность которы
|
||||||
- Слишком «чистый» класс (`LineNumberTable` есть, а `SourceFile` = `X.java`,
|
- Слишком «чистый» класс (`LineNumberTable` есть, а `SourceFile` = `X.java`,
|
||||||
которого нет) — тоже признак.
|
которого нет) — тоже признак.
|
||||||
|
|
||||||
**Статус: не реализован.**
|
**Статус: готово** (strip + `spoof_source_file`, детерминированная подмена
|
||||||
|
от мастер-сида). Не реализовано: `fake_line_numbers` (ложные номера строк,
|
||||||
|
а не только `SourceFile`) и `strip_source_file` (полное удаление вместо
|
||||||
|
подмены) — см. [`todo.md`](../todo.md) §3.6.
|
||||||
|
|
||||||
## 7. Ресурсы (`Resources`)
|
## 7. Ресурсы (`Resources`)
|
||||||
|
|
||||||
|
|
@ -147,7 +150,10 @@ opaque-предикаты — условия, истинность которы
|
||||||
трогать нельзя.
|
трогать нельзя.
|
||||||
- Промах в патче → тихо неработающий refmap, ловится только запуском.
|
- Промах в патче → тихо неработающий refmap, ловится только запуском.
|
||||||
|
|
||||||
**Статус: не реализован (крейт `noxis-jar` — план).**
|
**Статус: готово** (патч текстовых ресурсов по маппингу, включая
|
||||||
|
`fabric.mod.json`/`*.mixins.json`/`META-INF/services/*`; срез подписей
|
||||||
|
в `noxis-jar`). Не реализовано: типизированный патч бинарных/специфичных
|
||||||
|
форматов (refmap, access widener) сверх общего boundary-replace.
|
||||||
|
|
||||||
## Сводная таблица
|
## Сводная таблица
|
||||||
|
|
||||||
|
|
@ -155,11 +161,11 @@ opaque-предикаты — условия, истинность которы
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Rename | ★★★★★ | низкий | низкая | готово |
|
| Rename | ★★★★★ | низкий | низкая | готово |
|
||||||
| Strings | ★★★★ | средний | средняя | готово |
|
| Strings | ★★★★ | средний | средняя | готово |
|
||||||
| Resources | ★★★★★ | низкий | низкая | план |
|
| Resources | ★★★★★ | низкий | низкая | готово |
|
||||||
| DebugInfo | ★★★ | низкий | низкая | план |
|
| DebugInfo | ★★★ | низкий | низкая | готово |
|
||||||
| Constants | ★★ | низкий | низкая | готово (int/long) |
|
| Constants | ★★ | низкий | низкая | готово (int/long) |
|
||||||
| Noise | ★ | низкий | раздувание | готово (без аннотаций) |
|
| Noise | ★ | низкий | раздувание | готово (без аннотаций) |
|
||||||
| Flow | ★★★★ | **высокий** | высокая | план, ждёт frames |
|
| Flow | ★★★★ | **высокий** | высокая | план, кадры готовы |
|
||||||
|
|
||||||
Вывод, который стоит держать в голове: **Rename + Resources + DebugInfo**
|
Вывод, который стоит держать в голове: **Rename + Resources + DebugInfo**
|
||||||
дают большую часть защиты за небольшую цену. Flow — единственная техника,
|
дают большую часть защиты за небольшую цену. Flow — единственная техника,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue