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
152
crates/noxis-classfile/src/model/builder.rs
Normal file
152
crates/noxis-classfile/src/model/builder.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
//! Сборка классов «с нуля»: junk-классы, рантайм-декодеры, аннотации-шум.
|
||||
|
||||
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version};
|
||||
|
||||
/// Строитель класса без бинарного представления на входе.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClassBuilder {
|
||||
version: Version,
|
||||
access_flags: u16,
|
||||
this_class: String,
|
||||
super_class: Option<String>,
|
||||
interfaces: Vec<String>,
|
||||
fields: Vec<Field>,
|
||||
methods: Vec<Method>,
|
||||
attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl ClassBuilder {
|
||||
/// Новый класс `public` с указанным суперклассом (`None` = `java/lang/Object`,
|
||||
/// кроме самого `java/lang/Object` — у него суперкласса нет).
|
||||
pub fn new(this_class: impl Into<String>, super_class: Option<&str>) -> Self {
|
||||
let this_class = this_class.into();
|
||||
let super_class = match super_class {
|
||||
Some(name) => Some(name.to_string()),
|
||||
None if this_class == "java/lang/Object" => None,
|
||||
None => Some("java/lang/Object".to_string()),
|
||||
};
|
||||
Self {
|
||||
version: Version::JAVA_21,
|
||||
access_flags: 0x0021, // ACC_PUBLIC | ACC_SUPER
|
||||
this_class,
|
||||
super_class,
|
||||
interfaces: Vec::new(),
|
||||
fields: Vec::new(),
|
||||
methods: Vec::new(),
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Задать версию классового файла.
|
||||
pub fn version(mut self, version: Version) -> Self {
|
||||
self.version = version;
|
||||
self
|
||||
}
|
||||
|
||||
/// Задать флаги доступа (перезаписывает `ACC_PUBLIC|ACC_SUPER`).
|
||||
pub fn access_flags(mut self, flags: u16) -> Self {
|
||||
self.access_flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить интерфейс.
|
||||
pub fn interface(mut self, name: impl Into<String>) -> Self {
|
||||
self.interfaces.push(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить поле.
|
||||
pub fn field(mut self, name: impl Into<String>, descriptor: impl Into<String>) -> Self {
|
||||
self.fields.push(Field {
|
||||
access_flags: 0x0001, // ACC_PUBLIC
|
||||
name: name.into(),
|
||||
descriptor: descriptor.into(),
|
||||
attributes: Vec::new(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить метод без тела (`abstract`/`native` должны выставить флаги сами).
|
||||
pub fn method(
|
||||
mut self,
|
||||
access_flags: u16,
|
||||
name: impl Into<String>,
|
||||
descriptor: impl Into<String>,
|
||||
) -> Self {
|
||||
self.methods.push(Method {
|
||||
access_flags,
|
||||
name: name.into(),
|
||||
descriptor: descriptor.into(),
|
||||
code: None,
|
||||
attributes: Vec::new(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить метод с телом.
|
||||
pub fn method_with_code(
|
||||
mut self,
|
||||
access_flags: u16,
|
||||
name: impl Into<String>,
|
||||
descriptor: impl Into<String>,
|
||||
code: Code,
|
||||
) -> Self {
|
||||
self.methods.push(Method {
|
||||
access_flags,
|
||||
name: name.into(),
|
||||
descriptor: descriptor.into(),
|
||||
code: Some(code),
|
||||
attributes: Vec::new(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить атрибут уровня класса.
|
||||
pub fn attribute(mut self, attribute: Attribute) -> Self {
|
||||
self.attributes.push(attribute);
|
||||
self
|
||||
}
|
||||
|
||||
/// Собрать класс.
|
||||
pub fn build(self) -> ClassFile {
|
||||
ClassFile {
|
||||
version: self.version,
|
||||
access_flags: self.access_flags,
|
||||
this_class: self.this_class,
|
||||
super_class: self.super_class,
|
||||
interfaces: self.interfaces,
|
||||
fields: self.fields,
|
||||
methods: self.methods,
|
||||
attributes: self.attributes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Пустое тело метода (`return`), для junk-методов.
|
||||
pub fn empty_code(max_locals: u16) -> Code {
|
||||
Code {
|
||||
max_stack: 0,
|
||||
max_locals,
|
||||
insns: vec![crate::insn::Insn::Return(None)],
|
||||
exception_table: Vec::new(),
|
||||
frames: Vec::new(),
|
||||
attributes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn builds_class_with_method() {
|
||||
let class = ClassBuilder::new("pkg/A", None)
|
||||
.field("f", "I")
|
||||
.method_with_code(0x0009, "m", "()V", empty_code(0))
|
||||
.build();
|
||||
assert_eq!(class.this_class, "pkg/A");
|
||||
assert_eq!(class.fields.len(), 1);
|
||||
assert!(class.method("m", "()V").unwrap().has_code());
|
||||
}
|
||||
}
|
||||
236
crates/noxis-classfile/src/model/mod.rs
Normal file
236
crates/noxis-classfile/src/model/mod.rs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
//! Структура «программы» — единица работы пайплайна.
|
||||
|
||||
pub mod builder;
|
||||
pub mod version;
|
||||
|
||||
pub use version::Version;
|
||||
|
||||
use crate::format::StackMapFrame;
|
||||
use crate::insn::{Const, Insn, Label};
|
||||
|
||||
/// Неизвестные/специфичные атрибуты хранятся байтами — round-trip без потерь.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Attribute {
|
||||
SourceFile(String),
|
||||
LineNumberTable(Vec<LineNumber>),
|
||||
LocalVariableTable(Vec<LocalVariable>),
|
||||
Signature(String),
|
||||
Exceptions(Vec<String>),
|
||||
Deprecated,
|
||||
Synthetic,
|
||||
InnerClasses(Vec<InnerClass>),
|
||||
/// `ConstantValue` поля (`static final`): значение из пула.
|
||||
ConstantValue(Const),
|
||||
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы.
|
||||
BootstrapMethods(Vec<BootstrapMethod>),
|
||||
/// `EnclosingMethod`: класс и опциональный метод, содержащие локальный класс.
|
||||
EnclosingMethod {
|
||||
class: String,
|
||||
method: Option<(String, String)>,
|
||||
},
|
||||
/// `MethodParameters`: имена формальных параметров (`None` — безымянный).
|
||||
MethodParameters(Vec<MethodParameter>),
|
||||
/// `Record`: компоненты record-класса.
|
||||
Record(Vec<RecordComponent>),
|
||||
/// `LocalVariableTypeTable`: сигнатуры generic-локальных (как LVT).
|
||||
LocalVariableTypeTable(Vec<LocalVariableType>),
|
||||
/// Всё остальное: имя + сырые байты `info` (осторожно с индексами пула).
|
||||
Raw {
|
||||
name: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct LineNumber {
|
||||
/// Метка начала (байты вычисляет writer — переживает вставки пассов).
|
||||
pub start: Label,
|
||||
pub line: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LocalVariable {
|
||||
/// Метки диапазона видимости (writer пересчитает в байты).
|
||||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub name: String,
|
||||
pub descriptor: String,
|
||||
pub index: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct InnerClass {
|
||||
pub inner: String,
|
||||
pub outer: Option<String>,
|
||||
pub inner_name: Option<String>,
|
||||
pub access_flags: u16,
|
||||
}
|
||||
|
||||
/// Один `bootstrap`-метод: ссылка вида `MethodHandle` + аргументы-константы.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BootstrapMethod {
|
||||
/// Номер `reference_kind` (JVMS §5.4.3.5).
|
||||
pub kind: u8,
|
||||
pub owner: String,
|
||||
pub name: String,
|
||||
pub descriptor: String,
|
||||
pub itf: bool,
|
||||
pub args: Vec<Const>,
|
||||
}
|
||||
|
||||
/// Формальный параметр метода (`MethodParameters`).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MethodParameter {
|
||||
pub name: Option<String>,
|
||||
pub access_flags: u16,
|
||||
}
|
||||
|
||||
/// Компонент `Record`: поле record-класса с атрибутами.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RecordComponent {
|
||||
pub name: String,
|
||||
pub descriptor: String,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
/// Строка `LocalVariableTypeTable` (как `LocalVariable`, но с сигнатурой).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LocalVariableType {
|
||||
/// Метки диапазона видимости (writer пересчитает в байты).
|
||||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub name: String,
|
||||
pub signature: String,
|
||||
pub index: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Field {
|
||||
pub access_flags: u16,
|
||||
pub name: String,
|
||||
pub descriptor: String,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Method {
|
||||
pub access_flags: u16,
|
||||
pub name: String,
|
||||
pub descriptor: String,
|
||||
pub code: Option<Code>,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
/// `native`/`abstract` тела не имеют.
|
||||
pub fn has_code(&self) -> bool {
|
||||
self.code.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Тело метода.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Code {
|
||||
pub max_stack: u16,
|
||||
pub max_locals: u16,
|
||||
/// Маркеры меток + инструкции; ветвления указывают на [`Label`].
|
||||
pub insns: Vec<Insn>,
|
||||
pub exception_table: Vec<ExceptionHandler>,
|
||||
pub frames: Vec<StackMapFrame>,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl Code {
|
||||
/// Все метки, объявленные в теле метода.
|
||||
pub fn labels(&self) -> Vec<Label> {
|
||||
self.insns
|
||||
.iter()
|
||||
.filter_map(|i| match i {
|
||||
Insn::Mark(l) => Some(*l),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExceptionHandler {
|
||||
/// `None` — catch-all (`finally`).
|
||||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub handler: Label,
|
||||
/// Внутреннее имя класса или `None` для `any`.
|
||||
pub catch_type: Option<String>,
|
||||
}
|
||||
|
||||
/// Один `.class` в нормализованном виде.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ClassFile {
|
||||
pub version: Version,
|
||||
pub access_flags: u16,
|
||||
pub this_class: String,
|
||||
pub super_class: Option<String>,
|
||||
pub interfaces: Vec<String>,
|
||||
pub fields: Vec<Field>,
|
||||
pub methods: Vec<Method>,
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl ClassFile {
|
||||
/// Ищет метод по имени и дескриптору.
|
||||
pub fn method(&self, name: &str, descriptor: &str) -> Option<&Method> {
|
||||
self.methods
|
||||
.iter()
|
||||
.find(|m| m.name == name && m.descriptor == descriptor)
|
||||
}
|
||||
|
||||
/// Ищет метод по имени и дескриптору (мутирующий доступ).
|
||||
pub fn method_mut(&mut self, name: &str, descriptor: &str) -> Option<&mut Method> {
|
||||
self.methods
|
||||
.iter_mut()
|
||||
.find(|m| m.name == name && m.descriptor == descriptor)
|
||||
}
|
||||
|
||||
/// Признак `ACC_INTERFACE`.
|
||||
pub fn is_interface(&self) -> bool {
|
||||
self.access_flags & 0x0200 != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Ресурс внутри архива (JSON, properties, картинки, ...).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Resource {
|
||||
pub path: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Вся входная программа: классы + ресурсы.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct Program {
|
||||
pub classes: Vec<ClassFile>,
|
||||
pub resources: Vec<Resource>,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Класс по внутреннему имени (`pkg/Class`).
|
||||
pub fn class(&self, internal_name: &str) -> Option<&ClassFile> {
|
||||
self.classes.iter().find(|c| c.this_class == internal_name)
|
||||
}
|
||||
|
||||
/// Класс по внутреннему имени (мутирующий доступ).
|
||||
pub fn class_mut(&mut self, internal_name: &str) -> Option<&mut ClassFile> {
|
||||
self.classes
|
||||
.iter_mut()
|
||||
.find(|c| c.this_class == internal_name)
|
||||
}
|
||||
|
||||
/// Ресурс по пути внутри архива.
|
||||
pub fn resource(&self, path: &str) -> Option<&Resource> {
|
||||
self.resources.iter().find(|r| r.path == path)
|
||||
}
|
||||
|
||||
/// Пустая ли программа.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.classes.is_empty() && self.resources.is_empty()
|
||||
}
|
||||
}
|
||||
26
crates/noxis-classfile/src/model/version.rs
Normal file
26
crates/noxis-classfile/src/model/version.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Версия классового файла (major/minor).
|
||||
|
||||
/// Версия классового файла.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Version {
|
||||
pub minor: u16,
|
||||
pub major: u16,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// Java 8 (52).
|
||||
pub const JAVA_8: Version = Version {
|
||||
minor: 0,
|
||||
major: 52,
|
||||
};
|
||||
/// Java 17 (61).
|
||||
pub const JAVA_17: Version = Version {
|
||||
minor: 0,
|
||||
major: 61,
|
||||
};
|
||||
/// Java 21 (65).
|
||||
pub const JAVA_21: Version = Version {
|
||||
minor: 0,
|
||||
major: 65,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue