pub mod asm_ast;
pub mod generator;
pub mod regalloc;
pub use asm_ast::{AsmFunction, AsmProgram};
use crate::error::Result;
use crate::obfuscation::ObfuscationConfig;
use crate::tacky::tacky_ast::TackyProgram;
use asm_ast::{AsmBinaryOp, AsmType, AsmUnaryOp, Instruction, Operand, Reg};
pub fn generate(
program: &TackyProgram,
obf_config: Option<&ObfuscationConfig>,
) -> Result<AsmProgram> {
let (results, static_vars, static_constants) = generator::generate(program)?;
let mut functions = Vec::new();
for result in results {
let alloc = regalloc::allocate_registers(result.func.instructions, &result.var_types);
let fixed_instructions = regalloc::fixup_instructions(
alloc.instructions,
alloc.spill_bytes,
&alloc.callee_saved_used,
);
functions.push(AsmFunction {
name: result.func.name,
instructions: fixed_instructions,
global: result.func.global,
});
}
if let Some(config) = obf_config {
if config.stack_frame_obf {
obfuscate_stack_frame(
&mut functions,
config.stack_frame_padding,
config.stack_frame_fake_freq,
);
}
if config.reg_shuffle {
register_shuffle(&mut functions, config.reg_shuffle_freq);
}
if config.instr_subst {
instruction_substitution(&mut functions, config.instr_subst_freq);
}
if config.anti_disassembly {
insert_anti_disassembly(&mut functions);
}
if config.indirect_calls {
let local_names: std::collections::HashSet<String> =
functions.iter().map(|f| f.name.clone()).collect();
indirect_calls(&mut functions, &local_names);
}
}
Ok(AsmProgram {
functions,
static_vars,
static_constants,
})
}
fn obfuscate_stack_frame(functions: &mut [AsmFunction], num_fake_slots: usize, freq: usize) {
let num_fake_slots = if num_fake_slots == 0 {
4
} else {
num_fake_slots
};
let freq = if freq == 0 { 8 } else { freq };
let padding_size = (num_fake_slots * 8 + 15) & !15;
for func in functions {
let alloc_size = match find_allocate_stack(&func.instructions) {
Some(size) => size,
None => continue,
};
let min_offset = find_min_stack_offset(&func.instructions);
if min_offset >= 0 {
continue; }
for instr in func.instructions.iter_mut() {
match instr {
Instruction::AllocateStack(n) if *n == alloc_size => {
*n += padding_size;
}
Instruction::DeallocateStack(n) if *n == alloc_size => {
*n += padding_size;
}
_ => {}
}
}
let fake_offsets: Vec<i32> = (1..=num_fake_slots as i32)
.map(|i| min_offset - 8 * i)
.collect();
let mut new_instrs = Vec::new();
let mut counter: usize = 0;
let mut pattern: usize = 0;
let mut slot_idx: usize = 0;
let mut written_slots: Vec<i32> = Vec::new();
let orig = func.instructions.clone();
for (i, instr) in orig.iter().enumerate() {
new_instrs.push(instr.clone());
counter += 1;
if counter < freq {
continue;
}
let next = orig.get(i + 1);
if !is_safe_shuffle_point(instr, next) {
continue;
}
let offset = fake_offsets[slot_idx % num_fake_slots];
match pattern % 3 {
0 => {
let src_reg = extract_source_reg(instr);
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: Operand::Register(src_reg),
dst: Operand::Stack(offset),
});
if !written_slots.contains(&offset) {
written_slots.push(offset);
}
}
1 => {
let src_reg = extract_source_reg(instr);
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(src_reg),
dst: Operand::Stack(offset),
});
if !written_slots.contains(&offset) {
written_slots.push(offset);
}
}
2 => {
if written_slots.is_empty() {
let src_reg = extract_source_reg(instr);
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: Operand::Register(src_reg),
dst: Operand::Stack(offset),
});
if !written_slots.contains(&offset) {
written_slots.push(offset);
}
} else {
let read_offset = written_slots[slot_idx % written_slots.len()];
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Longword,
src: Operand::Stack(read_offset),
dst: Operand::Register(Reg::R10),
});
}
}
_ => unreachable!(),
}
counter = 0;
pattern += 1;
slot_idx += 1;
}
func.instructions = new_instrs;
}
}
fn find_allocate_stack(instructions: &[Instruction]) -> Option<usize> {
for instr in instructions {
if let Instruction::AllocateStack(n) = instr {
return Some(*n);
}
}
None
}
fn find_min_stack_offset(instructions: &[Instruction]) -> i32 {
let mut min_offset: i32 = 0;
for instr in instructions {
let check = |op: &Operand| {
if let Operand::Stack(off) = op {
*off
} else {
0
}
};
let offset = match instr {
Instruction::Mov { src, dst, .. } => check(src).min(check(dst)),
Instruction::Binary { src, dst, .. } => check(src).min(check(dst)),
Instruction::Cmp { src, dst, .. } => check(src).min(check(dst)),
Instruction::Unary { operand, .. } => check(operand),
Instruction::Idiv { operand, .. } => check(operand),
Instruction::Div { operand, .. } => check(operand),
Instruction::SetCC { operand, .. } => check(operand),
Instruction::Push(op) => check(op),
Instruction::Movsx { src, dst } => check(src).min(check(dst)),
Instruction::MovsxByte { src, dst, .. } => check(src).min(check(dst)),
Instruction::MovZeroExtend { src, dst } => check(src).min(check(dst)),
Instruction::MovZeroExtendByte { src, dst, .. } => check(src).min(check(dst)),
Instruction::Truncate { src, dst } => check(src).min(check(dst)),
Instruction::Cvtsi2sd { src, dst, .. } => check(src).min(check(dst)),
Instruction::Cvttsd2si { src, dst, .. } => check(src).min(check(dst)),
Instruction::Lea { src, dst } => check(src).min(check(dst)),
_ => 0,
};
if offset < min_offset {
min_offset = offset;
}
}
min_offset
}
fn insert_anti_disassembly(functions: &mut [AsmFunction]) {
for func in functions {
let mut new_instrs = Vec::new();
for instr in &func.instructions {
new_instrs.push(instr.clone());
if matches!(instr, Instruction::Jmp(_) | Instruction::JmpIndirect(_, _)) {
new_instrs.push(Instruction::RawBytes(vec![0xE8]));
}
}
func.instructions = new_instrs;
}
}
fn indirect_calls(functions: &mut [AsmFunction], local_names: &std::collections::HashSet<String>) {
for func in functions {
let mut new_instrs = Vec::new();
for instr in &func.instructions {
match instr {
Instruction::Call(name) if local_names.contains(name) => {
new_instrs.push(Instruction::Lea {
src: Operand::Data(name.clone()),
dst: Operand::Register(Reg::R10),
});
new_instrs.push(Instruction::CallIndirect(Operand::Register(Reg::R10)));
}
_ => new_instrs.push(instr.clone()),
}
}
func.instructions = new_instrs;
}
}
fn register_shuffle(functions: &mut [AsmFunction], freq: usize) {
let freq = if freq == 0 { 5 } else { freq };
for func in functions {
let mut new_instrs = Vec::new();
let mut counter: usize = 0;
let mut pattern: usize = 0;
let orig = &func.instructions;
for (i, instr) in orig.iter().enumerate() {
new_instrs.push(instr.clone());
counter += 1;
if counter < freq {
continue;
}
let next = orig.get(i + 1);
if !is_safe_shuffle_point(instr, next) {
continue;
}
let src_reg = extract_source_reg(instr);
let src = Operand::Register(src_reg);
match pattern % 3 {
0 => {
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src,
dst: Operand::Register(Reg::R10),
});
}
1 => {
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src,
dst: Operand::Register(Reg::R10),
});
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::R10),
dst: Operand::Register(Reg::R11),
});
}
2 => {
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: src.clone(),
dst: Operand::Register(Reg::R10),
});
new_instrs.push(Instruction::Mov {
asm_type: AsmType::Quadword,
src: Operand::Register(Reg::R10),
dst: src,
});
}
_ => unreachable!(),
}
counter = 0;
pattern += 1;
}
func.instructions = new_instrs;
}
}
fn instruction_substitution(functions: &mut [AsmFunction], freq: usize) {
let freq = if freq == 0 { 4 } else { freq };
for func in functions {
let mut new_instrs = Vec::new();
let mut counter: usize = 0;
let mut pattern_idx: usize = 0;
let orig = &func.instructions;
for (i, instr) in orig.iter().enumerate() {
counter += 1;
if counter < freq {
new_instrs.push(instr.clone());
continue;
}
let next = orig.get(i + 1);
if !is_safe_subst_point(instr, next) {
new_instrs.push(instr.clone());
continue;
}
let pat = pattern_idx % 4;
let substituted = match (pat, instr) {
(
0,
Instruction::Binary {
asm_type,
op: AsmBinaryOp::Add,
src: Operand::Imm(n),
dst,
},
) if *asm_type != AsmType::Double => {
let neg_n = -(*n);
if neg_n >= i32::MIN as i64 && neg_n <= i32::MAX as i64 {
new_instrs.push(Instruction::Binary {
asm_type: *asm_type,
op: AsmBinaryOp::Sub,
src: Operand::Imm(neg_n),
dst: dst.clone(),
});
true
} else {
false
}
}
(
1,
Instruction::Binary {
asm_type,
op: AsmBinaryOp::Sub,
src: Operand::Imm(n),
dst,
},
) if *asm_type != AsmType::Double => {
let neg_n = -(*n);
if neg_n >= i32::MIN as i64 && neg_n <= i32::MAX as i64 {
new_instrs.push(Instruction::Binary {
asm_type: *asm_type,
op: AsmBinaryOp::Add,
src: Operand::Imm(neg_n),
dst: dst.clone(),
});
true
} else {
false
}
}
(
2,
Instruction::Unary {
asm_type,
op: AsmUnaryOp::Neg,
operand,
},
) if *asm_type != AsmType::Double => {
new_instrs.push(Instruction::Unary {
asm_type: *asm_type,
op: AsmUnaryOp::Not,
operand: operand.clone(),
});
new_instrs.push(Instruction::Binary {
asm_type: *asm_type,
op: AsmBinaryOp::Add,
src: Operand::Imm(1),
dst: operand.clone(),
});
true
}
(
3,
Instruction::Mov {
asm_type,
src: Operand::Imm(n),
dst,
},
) if *asm_type != AsmType::Double && *n != 0 => {
let k = ((pattern_idx * 7 + 3) % 127 + 1) as i64;
let n_plus_k = *n + k;
if n_plus_k >= i32::MIN as i64 && n_plus_k <= i32::MAX as i64 {
new_instrs.push(Instruction::Mov {
asm_type: *asm_type,
src: Operand::Imm(n_plus_k),
dst: dst.clone(),
});
new_instrs.push(Instruction::Binary {
asm_type: *asm_type,
op: AsmBinaryOp::Sub,
src: Operand::Imm(k),
dst: dst.clone(),
});
true
} else {
false
}
}
_ => false,
};
if substituted {
counter = 0;
pattern_idx += 1;
} else {
new_instrs.push(instr.clone());
}
}
func.instructions = new_instrs;
}
}
fn is_safe_subst_point(current: &Instruction, next: Option<&Instruction>) -> bool {
if matches!(current, Instruction::Cmp { .. } | Instruction::SetCC { .. }) {
return false;
}
if matches!(
current,
Instruction::Push(_)
| Instruction::Pop(_)
| Instruction::AllocateStack(_)
| Instruction::DeallocateStack(_)
| Instruction::Ret
) {
return false;
}
if let Some(next_instr) = next {
if matches!(next_instr, Instruction::Label(_)) {
return false;
}
if matches!(
next_instr,
Instruction::JmpCC(..) | Instruction::SetCC { .. }
) {
return false;
}
}
true
}
fn is_safe_shuffle_point(current: &Instruction, next: Option<&Instruction>) -> bool {
if matches!(current, Instruction::Cmp { .. } | Instruction::SetCC { .. }) {
return false;
}
if matches!(
current,
Instruction::Push(_)
| Instruction::Pop(_)
| Instruction::AllocateStack(_)
| Instruction::DeallocateStack(_)
| Instruction::Ret
) {
return false;
}
if let Some(next_instr) = next {
if matches!(next_instr, Instruction::Label(_)) {
return false;
}
if reads_r10_or_r11(next_instr) {
return false;
}
if matches!(
next_instr,
Instruction::Call(_) | Instruction::CallIndirect(_)
) {
return false;
}
}
true
}
fn extract_source_reg(instr: &Instruction) -> Reg {
let reg = match instr {
Instruction::Mov {
dst: Operand::Register(r),
..
} => Some(*r),
Instruction::Binary {
dst: Operand::Register(r),
..
} => Some(*r),
Instruction::Lea {
dst: Operand::Register(r),
..
} => Some(*r),
_ => None,
};
match reg {
Some(r) if is_valid_shuffle_source(r) => r,
_ => Reg::AX,
}
}
fn is_valid_shuffle_source(r: Reg) -> bool {
!matches!(
r,
Reg::R10
| Reg::R11
| Reg::SP
| Reg::BP
| Reg::XMM0
| Reg::XMM1
| Reg::XMM2
| Reg::XMM3
| Reg::XMM4
| Reg::XMM5
| Reg::XMM6
| Reg::XMM7
| Reg::XMM8
| Reg::XMM9
| Reg::XMM10
| Reg::XMM11
| Reg::XMM12
| Reg::XMM13
| Reg::XMM14
| Reg::XMM15
)
}
fn reads_r10_or_r11(instr: &Instruction) -> bool {
let check = |op: &Operand| matches!(op, Operand::Register(Reg::R10 | Reg::R11));
match instr {
Instruction::Mov { src, .. } => check(src),
Instruction::Binary { src, dst, .. } => check(src) || check(dst),
Instruction::Cmp { src, dst, .. } => check(src) || check(dst),
Instruction::Unary { operand, .. } => check(operand),
Instruction::Idiv { operand, .. } => check(operand),
Instruction::Div { operand, .. } => check(operand),
Instruction::SetCC { operand, .. } => check(operand),
Instruction::Push(op) => check(op),
Instruction::Movsx { src, dst } => check(src) || check(dst),
Instruction::MovsxByte { src, dst, .. } => check(src) || check(dst),
Instruction::MovZeroExtend { src, dst } => check(src) || check(dst),
Instruction::MovZeroExtendByte { src, dst, .. } => check(src) || check(dst),
Instruction::Truncate { src, dst } => check(src) || check(dst),
Instruction::Cvtsi2sd { src, dst, .. } => check(src) || check(dst),
Instruction::Cvttsd2si { src, dst, .. } => check(src) || check(dst),
Instruction::Lea { src, dst } => check(src) || check(dst),
Instruction::CallIndirect(op) => check(op),
_ => false,
}
}