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:
Loki 2026-10-08 22:01:03 +02:00
commit a641e69455
140 changed files with 17197 additions and 0 deletions

View file

@ -0,0 +1,136 @@
//! Типы операндов инструкций: константы, примитивы, условия, вызовы.
/// Идентификатор метки внутри одного метода.
pub type Label = u32;
/// Константа для `ldc`/`ldc_w`/`ldc2_w`.
#[derive(Debug, Clone, PartialEq)]
pub enum Const {
Int(i32),
Long(i64),
Float(f32),
Double(f64),
String(String),
/// Внутреннее имя класса (`java/lang/String`).
Class(String),
MethodType(String),
MethodHandle {
kind: u8,
owner: String,
name: String,
descriptor: String,
itf: bool,
},
/// `CONSTANT_Dynamic`: имя, дескриптор, индекс bootstrap-метода.
Dynamic {
name: String,
descriptor: String,
bootstrap: u16,
},
}
/// Размер операнда/результата арифметики.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Prim {
Int,
Long,
Float,
Double,
}
/// Арифметическая/битовая операция; тип берётся из [`Prim`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arith {
Add,
Sub,
Mul,
Div,
Rem,
Neg,
And,
Or,
Xor,
Shl,
Shr,
Ushr,
}
/// Условие перехода.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cond {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
/// Вид условного перехода (соответствует семействам `if*`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchCond {
/// `if<cond>` относительно нуля.
Zero(Cond),
/// `if_icmp<cond>`.
Int(Cond),
/// `if_acmp<eq|ne>`.
Ref { eq: bool },
/// `ifnull` / `ifnonnull`.
Null { is_null: bool },
}
/// Вид вызова.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvokeKind {
Virtual,
Special,
Static,
Interface,
}
/// Конверсия типов (`i2l`, `f2b`, ... — JVMS §6.5.i2<b|c|s|l|f|d>).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Convert {
I2L,
I2F,
I2D,
I2B,
I2C,
I2S,
L2I,
L2F,
L2D,
F2I,
F2L,
F2D,
D2I,
D2L,
D2F,
}
/// Тип элемента массива для `newarray`/`*aload`/`*astore`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrayKind {
Boolean,
Char,
Float,
Double,
Byte,
Short,
Int,
Long,
Reference,
}
/// Тип слота локальной переменной для `*load*`/`*store*`.
///
/// Хранится явно: выбор опкода (`iload` vs `aload`) — подсказка верификатору,
/// потерять её при round-trip нельзя.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotKind {
Int,
Long,
Float,
Double,
Ref,
}

View file

@ -0,0 +1,164 @@
//! Инструкции IR: смещения отсутствуют, ветвления адресуют [`Label`].
//!
//! Это принципиально: любой пасс, меняющий порядок/длину инструкций, не обязан
//! пересчитывать jump-офсеты — это делает writer один раз в конце пайплайна.
mod kinds;
pub use kinds::{
Arith, ArrayKind, BranchCond, Cond, Const, Convert, InvokeKind, Label, Prim, SlotKind,
};
/// Мнемоника одной инструкции.
#[derive(Debug, Clone, PartialEq)]
pub enum Insn {
/// Маркер метки. Ветвления указывают на [`Label`], writer вычисляет смещения.
Mark(Label),
Nop,
AconstNull,
Const(Const),
/// Загрузка локальной переменной (`iload`, `aload`, ...).
Load {
slot: u16,
kind: SlotKind,
},
/// Сохранение локальной переменной.
Store {
slot: u16,
kind: SlotKind,
},
/// `iinc` — увеличение локальной переменной на константу.
Iinc {
index: u16,
delta: i32,
},
/// Загрузка элемента массива.
ArrayLoad(ArrayKind),
/// Сохранение элемента массива.
ArrayStore(ArrayKind),
Pop,
Pop2,
Dup,
DupX1,
DupX2,
Dup2,
Dup2X1,
Dup2X2,
Swap,
/// `iadd`, `lmul`, `lneg`, ... — тип + операция.
Binary {
prim: Prim,
op: Arith,
},
/// Сравнение длин (`lcmp`) и плавающих (`fcmpl`/`fcmpg`).
Cmp {
prim: Prim,
nan_great: bool,
},
/// Конверсия типов (`i2l`, `d2f`, ...).
Convert(Convert),
Branch(BranchCond, Label),
Goto(Label),
/// `jsr` — вызов подпрограммы (только классы версии ≤ 50).
Jsr(Label),
/// `ret` — возврат из подпрограммы (`jsr`/`ret` парны).
Ret(u16),
TableSwitch {
default: Label,
low: i32,
high: i32,
targets: Vec<Label>,
},
LookupSwitch {
default: Label,
pairs: Vec<(i32, Label)>,
},
/// `return`/`ireturn`/...; `None` — пустой возврат.
Return(Option<Prim>),
/// `areturn` — возврат ссылки (у [`Prim`] нет ссылочного типа).
AReturn,
GetStatic {
owner: String,
name: String,
descriptor: String,
},
PutStatic {
owner: String,
name: String,
descriptor: String,
},
GetField {
owner: String,
name: String,
descriptor: String,
},
PutField {
owner: String,
name: String,
descriptor: String,
},
Invoke {
kind: InvokeKind,
owner: String,
name: String,
descriptor: String,
itf: bool,
},
InvokeDynamic {
name: String,
descriptor: String,
bootstrap: u16,
},
New(String),
NewArray(ArrayKind),
ANewArray(String),
/// `multianewarray` — массив любого измерения; `dims` — сколько измерений со стека.
MultiANewArray {
descriptor: String,
dims: u8,
},
ArrayLength,
AThrow,
CheckCast(String),
InstanceOf(String),
MonitorEnter,
MonitorExit,
}
impl Insn {
/// Метки, на которые ссылается инструкция (для построения CFG).
pub fn targets(&self) -> Vec<Label> {
match self {
Insn::Branch(_, label) | Insn::Goto(label) | Insn::Jsr(label) => vec![*label],
Insn::TableSwitch {
default,
targets,
low: _,
high: _,
} => {
let mut all = Vec::with_capacity(targets.len() + 1);
all.push(*default);
all.extend(targets.iter().copied());
all
}
Insn::LookupSwitch { default, pairs } => {
let mut all = Vec::with_capacity(pairs.len() + 1);
all.push(*default);
all.extend(pairs.iter().map(|(k, l)| {
let _ = k;
*l
}));
all
}
_ => Vec::new(),
}
}
/// Незакодированный переход (для вставки opaque-предикатов и перестройки CFG).
pub fn branch_target_mut(&mut self) -> Option<&mut Label> {
match self {
Insn::Branch(_, label) | Insn::Goto(label) => Some(label),
_ => None,
}
}
}