Initial commit: noxis JVM bytecode obfuscator
Workspace of 9 crates (noxis, noxis-core, noxis-classfile, noxis-analysis, noxis-passes, noxis-runtime, noxis-jar, noxis-verify, noxis-structure-check). AGPL-3.0-only license, Rust 2024 edition, unsafe_code denied.
This commit is contained in:
commit
a641e69455
140 changed files with 17197 additions and 0 deletions
112
crates/noxis-core/src/mapping.rs
Normal file
112
crates/noxis-core/src/mapping.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//! Обратная таблица маппингов: деобфускация имён и stack-trace translate.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Ключ метода: `owner/name+descriptor`.
|
||||
pub fn method_key(owner: &str, name: &str, descriptor: &str) -> String {
|
||||
format!("{owner}/{name}{descriptor}")
|
||||
}
|
||||
|
||||
/// Ключ поля: `owner/name:descriptor`.
|
||||
pub fn field_key(owner: &str, name: &str, descriptor: &str) -> String {
|
||||
format!("{owner}/{name}:{descriptor}")
|
||||
}
|
||||
|
||||
/// Таблицы «оригинал → обфусцированное».
|
||||
///
|
||||
/// Нужна по двум причинам: (1) перевод stack trace'ей обратно в читаемые имена
|
||||
/// (аналог ZKM Stack Trace Translate), (2) аудит — что именно поменялось.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
|
||||
pub struct Mapping {
|
||||
/// internal name класса → новый internal name
|
||||
pub classes: BTreeMap<String, String>,
|
||||
/// `owner/name+desc` → новое имя метода (владелец остаётся прежним по ключу)
|
||||
pub methods: BTreeMap<String, String>,
|
||||
/// `owner/name:desc` → новое имя поля
|
||||
pub fields: BTreeMap<String, String>,
|
||||
/// имя локальной переменной/параметра в методе → новое (по номеру слота)
|
||||
pub locals: BTreeMap<String, String>,
|
||||
/// оригинальный путь ресурса → новый
|
||||
pub resources: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
/// Новое имя класса, если он переименован.
|
||||
pub fn class(&self, from: &str) -> Option<&str> {
|
||||
self.classes.get(from).map(String::as_str)
|
||||
}
|
||||
|
||||
/// Новое имя метода.
|
||||
pub fn method(&self, owner: &str, name: &str, descriptor: &str) -> Option<&str> {
|
||||
self.methods
|
||||
.get(&method_key(owner, name, descriptor))
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// Новое имя поля.
|
||||
pub fn field(&self, owner: &str, name: &str, descriptor: &str) -> Option<&str> {
|
||||
self.fields
|
||||
.get(&field_key(owner, name, descriptor))
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// Число переименований всех видов.
|
||||
pub fn len(&self) -> usize {
|
||||
self.classes.len() + self.methods.len() + self.fields.len() + self.locals.len()
|
||||
}
|
||||
|
||||
/// Пустая ли таблица.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Человекочитаемая таблица для файла маппингов и deobfuscator'а.
|
||||
pub fn to_text(&self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("# classes\n");
|
||||
for (from, to) in &self.classes {
|
||||
out.push_str(&format!("{from} -> {to}\n"));
|
||||
}
|
||||
out.push_str("\n# methods\n");
|
||||
for (from, to) in &self.methods {
|
||||
out.push_str(&format!("{from} -> {to}\n"));
|
||||
}
|
||||
out.push_str("\n# fields\n");
|
||||
for (from, to) in &self.fields {
|
||||
out.push_str(&format!("{from} -> {to}\n"));
|
||||
}
|
||||
out.push_str("\n# resources\n");
|
||||
for (from, to) in &self.resources {
|
||||
out.push_str(&format!("{from} -> {to}\n"));
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_lookup() {
|
||||
let mut m = Mapping::default();
|
||||
m.classes.insert("com/example/Main".into(), "a/B".into());
|
||||
m.methods
|
||||
.insert(method_key("a/B", "run", "()V"), "x".into());
|
||||
assert_eq!(m.class("com/example/Main"), Some("a/B"));
|
||||
assert_eq!(m.method("a/B", "run", "()V"), Some("x"));
|
||||
assert!(m.method("a/B", "other", "()V").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_report_contains_sections() {
|
||||
let mut m = Mapping::default();
|
||||
m.classes.insert("A".into(), "B".into());
|
||||
let text = m.to_text();
|
||||
assert!(text.contains("# classes"));
|
||||
assert!(text.contains("A -> B"));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue