use std::collections::{HashMap, HashSet};
use super::tacky_ast::*;
use crate::error::CompileError;
use crate::obfuscation::{ObfuscationConfig, OpsecPolicy};
use crate::parse::ast::Type;
struct ObfCtx {
tmp_counter: usize,
label_counter: usize,
inline_counter: usize,
outline_counter: usize,
vm_counter: usize,
opsec_counter: usize,
}
impl ObfCtx {
fn new() -> Self {
ObfCtx {
tmp_counter: 0,
label_counter: 0,
inline_counter: 0,
outline_counter: 0,
vm_counter: 0,
opsec_counter: 0,
}
}
fn fresh_tmp(&mut self) -> String {
let name = format!("obf_tmp.{}", self.tmp_counter);
self.tmp_counter += 1;
name
}
fn fresh_label(&mut self) -> String {
let name = format!(".Lobf_{}", self.label_counter);
self.label_counter += 1;
name
}
}
pub fn obfuscate(
program: TackyProgram,
config: &ObfuscationConfig,
) -> crate::error::Result<TackyProgram> {
let mut program = program;
let mut ctx = ObfCtx::new();
if config.lib_obfuscate {
replace_library_functions(&mut program, &mut ctx);
}
if config.func_inline {
inline_functions(&mut program, &mut ctx, config.func_inline_freq);
}
for func in &mut program.functions {
if config.constant_encoding {
func.body = constant_encoding(
std::mem::take(&mut func.body),
&mut ctx,
&mut func.var_types,
);
}
if config.arith_subst {
func.body = arithmetic_substitution(
std::mem::take(&mut func.body),
&mut ctx,
&mut func.var_types,
config.arith_freq,
);
}
if config.junk_code {
func.body = junk_code_insertion(
std::mem::take(&mut func.body),
&mut ctx,
&mut func.var_types,
config.junk_freq,
);
}
if config.opaque_predicates {
func.body = opaque_predicates(
std::mem::take(&mut func.body),
&mut ctx,
&mut func.var_types,
config.pred_freq,
);
}
}
if config.func_outline {
outline_functions(&mut program, &mut ctx, config.func_outline_min_block);
}
if config.vm_virtualize {
vm_virtualize(&mut program, &mut ctx);
}
for func in &mut program.functions {
if config.cff {
func.body = control_flow_flattening(
std::mem::take(&mut func.body),
&mut ctx,
&mut func.var_types,
&mut program.static_vars,
config.cff_a,
config.cff_b,
);
}
}
if config.opsec_warn
&& let Err(count) = opsec_warn_strings(&program, config.opsec_policy)
{
return Err(CompileError::OpsecViolation(format!(
"{count} string violation(s) detected"
)));
}
if config.string_encryption {
string_encryption(&mut program, &mut ctx, config.string_key);
}
if config.opsec {
opsec_sanitize(
&mut program,
&mut ctx,
config.opsec_strip,
config.preserve_globals,
);
}
Ok(program)
}
fn replace_library_functions(program: &mut TackyProgram, ctx: &mut ObfCtx) {
const TARGET_FUNCTIONS: &[&str] = &[
"strlen", "strcmp", "strcpy", "memcpy", "memset", "memcmp", "strncmp", "strncpy", "strchr",
"strcat",
];
let mut needed: HashSet<String> = HashSet::new();
for func in &program.functions {
for instr in &func.body {
if let TackyInstruction::FunCall { name, .. } = instr
&& TARGET_FUNCTIONS.contains(&name.as_str())
{
needed.insert(name.clone());
}
}
}
if needed.is_empty() {
return;
}
let mut generated: HashMap<String, String> = HashMap::new(); let mut new_functions: Vec<TackyFunction> = Vec::new();
let mut needed_sorted: Vec<String> = needed.into_iter().collect();
needed_sorted.sort();
for name in &needed_sorted {
match name.as_str() {
"strlen" => {
let obf_name = "_obf_strlen".to_string();
new_functions.push(generate_strlen(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strcmp" => {
let obf_name = "_obf_strcmp".to_string();
new_functions.push(generate_strcmp(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strcpy" => {
let obf_name = "_obf_strcpy".to_string();
new_functions.push(generate_strcpy(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"memcpy" => {
let obf_name = "_obf_memcpy".to_string();
new_functions.push(generate_memcpy(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"memset" => {
let obf_name = "_obf_memset".to_string();
new_functions.push(generate_memset(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"memcmp" => {
let obf_name = "_obf_memcmp".to_string();
new_functions.push(generate_memcmp(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strncmp" => {
let obf_name = "_obf_strncmp".to_string();
new_functions.push(generate_strncmp(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strncpy" => {
let obf_name = "_obf_strncpy".to_string();
new_functions.push(generate_strncpy(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strchr" => {
let obf_name = "_obf_strchr".to_string();
new_functions.push(generate_strchr(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
"strcat" => {
let obf_name = "_obf_strcat".to_string();
new_functions.push(generate_strcat(ctx, &obf_name));
generated.insert(name.clone(), obf_name);
}
_ => {}
}
}
for func in &mut program.functions {
for instr in &mut func.body {
if let TackyInstruction::FunCall { name, .. } = instr
&& let Some(obf_name) = generated.get(name)
{
*name = obf_name.clone();
}
}
}
program.functions.extend(new_functions);
}
fn generate_strlen(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let p = "p".to_string();
let len = ctx.fresh_tmp(); let ptr = ctx.fresh_tmp(); let ch = ctx.fresh_tmp(); let ci = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let mut var_types = HashMap::new();
var_types.insert(p.clone(), Type::Pointer(Box::new(Type::Char)));
var_types.insert(len.clone(), Type::Long);
var_types.insert(ptr.clone(), Type::Pointer(Box::new(Type::Char)));
var_types.insert(ch.clone(), Type::Char);
var_types.insert(ci.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(len.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p.clone()),
index: TackyVal::Var(len.clone()),
scale: 1,
dst: TackyVal::Var(ptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: loop_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(len.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(len.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Var(len)),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![p],
body,
return_type: Type::Long,
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strcmp(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let p1 = "p1".to_string();
let p2 = "p2".to_string();
let idx = ctx.fresh_tmp(); let ptr1 = ctx.fresh_tmp(); let ptr2 = ctx.fresh_tmp(); let ch1 = ctx.fresh_tmp(); let ch2 = ctx.fresh_tmp(); let ci1 = ctx.fresh_tmp(); let ci2 = ctx.fresh_tmp(); let diff = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ret_diff = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(p1.clone(), ptr_char.clone());
var_types.insert(p2.clone(), ptr_char.clone());
var_types.insert(idx.clone(), Type::Long);
var_types.insert(ptr1.clone(), ptr_char.clone());
var_types.insert(ptr2.clone(), ptr_char);
var_types.insert(ch1.clone(), Type::Char);
var_types.insert(ch2.clone(), Type::Char);
var_types.insert(ci1.clone(), Type::Int);
var_types.insert(ci2.clone(), Type::Int);
var_types.insert(diff.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p1.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr1.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p2.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr2.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr1.clone()),
dst: TackyVal::Var(ch1.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr2.clone()),
dst: TackyVal::Var(ch2.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch1.clone()),
dst: TackyVal::Var(ci1.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch2.clone()),
dst: TackyVal::Var(ci2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(ci1.clone()),
right: TackyVal::Var(ci2.clone()),
dst: TackyVal::Var(diff.clone()),
},
TackyInstruction::JumpIfNotZero {
condition: TackyVal::Var(diff.clone()),
target: ret_diff.clone(),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci1.clone()),
target: loop_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(ret_diff),
TackyInstruction::Return(TackyVal::Var(diff)),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Constant(TackyConst::Int(0))),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![p1, p2],
body,
return_type: Type::Int,
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strcpy(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let dst = "p_dst".to_string();
let src = "p_src".to_string();
let idx = ctx.fresh_tmp(); let src_ptr = ctx.fresh_tmp(); let dst_ptr = ctx.fresh_tmp(); let ch = ctx.fresh_tmp(); let ci = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(dst.clone(), ptr_char.clone());
var_types.insert(src.clone(), ptr_char.clone());
var_types.insert(idx.clone(), Type::Long);
var_types.insert(src_ptr.clone(), ptr_char.clone());
var_types.insert(dst_ptr.clone(), ptr_char);
var_types.insert(ch.clone(), Type::Char);
var_types.insert(ci.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(src.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(src_ptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(src_ptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Var(ch.clone()),
dst_ptr: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: loop_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Var(dst.clone())),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![dst, src],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_memcpy(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let dst = "p_dst".to_string();
let src = "p_src".to_string();
let n = "p_n".to_string();
let idx = ctx.fresh_tmp(); let src_ptr = ctx.fresh_tmp(); let dst_ptr = ctx.fresh_tmp(); let byte = ctx.fresh_tmp(); let cmp = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(dst.clone(), ptr_char.clone());
var_types.insert(src.clone(), ptr_char.clone());
var_types.insert(n.clone(), Type::Long);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(src_ptr.clone(), ptr_char.clone());
var_types.insert(dst_ptr.clone(), ptr_char);
var_types.insert(byte.clone(), Type::Char);
var_types.insert(cmp.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop_end.clone(),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(src.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(src_ptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(src_ptr.clone()),
dst: TackyVal::Var(byte.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Var(byte.clone()),
dst_ptr: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Var(dst.clone())),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![dst, src, n],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_memset(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let s = "p_s".to_string();
let c = "p_c".to_string();
let n = "p_n".to_string();
let b = ctx.fresh_tmp(); let idx = ctx.fresh_tmp(); let dst_ptr = ctx.fresh_tmp(); let cmp = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(s.clone(), ptr_char.clone());
var_types.insert(c.clone(), Type::Int);
var_types.insert(n.clone(), Type::Long);
var_types.insert(b.clone(), Type::Char);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(dst_ptr.clone(), ptr_char);
var_types.insert(cmp.clone(), Type::Int);
let body = vec![
TackyInstruction::Truncate {
src: TackyVal::Var(c.clone()),
dst: TackyVal::Var(b.clone()),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop_end.clone(),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(s.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Var(b.clone()),
dst_ptr: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Var(s.clone())),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![s, c, n],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_memcmp(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let p1 = "p1".to_string();
let p2 = "p2".to_string();
let n = "p_n".to_string();
let idx = ctx.fresh_tmp();
let cmp = ctx.fresh_tmp();
let ptr1 = ctx.fresh_tmp();
let ptr2 = ctx.fresh_tmp();
let ch1 = ctx.fresh_tmp();
let ch2 = ctx.fresh_tmp();
let ci1 = ctx.fresh_tmp();
let ci2 = ctx.fresh_tmp();
let diff = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ret_diff = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(p1.clone(), ptr_char.clone());
var_types.insert(p2.clone(), ptr_char.clone());
var_types.insert(n.clone(), Type::Long);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(cmp.clone(), Type::Int);
var_types.insert(ptr1.clone(), ptr_char.clone());
var_types.insert(ptr2.clone(), ptr_char);
var_types.insert(ch1.clone(), Type::Char);
var_types.insert(ch2.clone(), Type::Char);
var_types.insert(ci1.clone(), Type::Int);
var_types.insert(ci2.clone(), Type::Int);
var_types.insert(diff.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop_end.clone(),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p1.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr1.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p2.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr2.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr1.clone()),
dst: TackyVal::Var(ch1.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr2.clone()),
dst: TackyVal::Var(ch2.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch1.clone()),
dst: TackyVal::Var(ci1.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch2.clone()),
dst: TackyVal::Var(ci2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(ci1.clone()),
right: TackyVal::Var(ci2.clone()),
dst: TackyVal::Var(diff.clone()),
},
TackyInstruction::JumpIfNotZero {
condition: TackyVal::Var(diff.clone()),
target: ret_diff.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(ret_diff),
TackyInstruction::Return(TackyVal::Var(diff)),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Constant(TackyConst::Int(0))),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![p1, p2, n],
body,
return_type: Type::Int,
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strncmp(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let p1 = "p1".to_string();
let p2 = "p2".to_string();
let n = "p_n".to_string();
let idx = ctx.fresh_tmp();
let cmp = ctx.fresh_tmp();
let ptr1 = ctx.fresh_tmp();
let ptr2 = ctx.fresh_tmp();
let ch1 = ctx.fresh_tmp();
let ch2 = ctx.fresh_tmp();
let ci1 = ctx.fresh_tmp();
let ci2 = ctx.fresh_tmp();
let diff = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let loop_end = ctx.fresh_label();
let ret_diff = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(p1.clone(), ptr_char.clone());
var_types.insert(p2.clone(), ptr_char.clone());
var_types.insert(n.clone(), Type::Long);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(cmp.clone(), Type::Int);
var_types.insert(ptr1.clone(), ptr_char.clone());
var_types.insert(ptr2.clone(), ptr_char);
var_types.insert(ch1.clone(), Type::Char);
var_types.insert(ch2.clone(), Type::Char);
var_types.insert(ci1.clone(), Type::Int);
var_types.insert(ci2.clone(), Type::Int);
var_types.insert(diff.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop_end.clone(),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p1.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr1.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(p2.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr2.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr1.clone()),
dst: TackyVal::Var(ch1.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr2.clone()),
dst: TackyVal::Var(ch2.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch1.clone()),
dst: TackyVal::Var(ci1.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch2.clone()),
dst: TackyVal::Var(ci2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(ci1.clone()),
right: TackyVal::Var(ci2.clone()),
dst: TackyVal::Var(diff.clone()),
},
TackyInstruction::JumpIfNotZero {
condition: TackyVal::Var(diff.clone()),
target: ret_diff.clone(),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci1.clone()),
target: loop_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(ret_diff),
TackyInstruction::Return(TackyVal::Var(diff)),
TackyInstruction::Label(loop_end),
TackyInstruction::Return(TackyVal::Constant(TackyConst::Int(0))),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![p1, p2, n],
body,
return_type: Type::Int,
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strncpy(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let dst = "p_dst".to_string();
let src = "p_src".to_string();
let n = "p_n".to_string();
let idx = ctx.fresh_tmp();
let cmp = ctx.fresh_tmp();
let src_ptr = ctx.fresh_tmp();
let dst_ptr = ctx.fresh_tmp();
let ch = ctx.fresh_tmp();
let ci = ctx.fresh_tmp();
let loop1_start = ctx.fresh_label();
let loop1_end = ctx.fresh_label();
let loop2_start = ctx.fresh_label();
let loop2_end = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(dst.clone(), ptr_char.clone());
var_types.insert(src.clone(), ptr_char.clone());
var_types.insert(n.clone(), Type::Long);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(cmp.clone(), Type::Int);
var_types.insert(src_ptr.clone(), ptr_char.clone());
var_types.insert(dst_ptr.clone(), ptr_char);
var_types.insert(ch.clone(), Type::Char);
var_types.insert(ci.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop1_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop2_end.clone(), },
TackyInstruction::AddPtr {
ptr: TackyVal::Var(src.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(src_ptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(src_ptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Var(ch.clone()),
dst_ptr: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: loop1_end.clone(), },
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop1_start),
TackyInstruction::Label(loop1_end.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop2_start.clone()),
TackyInstruction::Binary {
op: TackyBinaryOp::LessThan,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Var(n.clone()),
dst: TackyVal::Var(cmp.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(cmp.clone()),
target: loop2_end.clone(),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Constant(TackyConst::Char(0)),
dst_ptr: TackyVal::Var(dst_ptr.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop2_start),
TackyInstruction::Label(loop2_end),
TackyInstruction::Return(TackyVal::Var(dst.clone())),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![dst, src, n],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strchr(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let s = "p_s".to_string();
let c = "p_c".to_string();
let target = ctx.fresh_tmp(); let idx = ctx.fresh_tmp(); let ptr = ctx.fresh_tmp(); let ch = ctx.fresh_tmp(); let ci = ctx.fresh_tmp(); let ti = ctx.fresh_tmp(); let eq = ctx.fresh_tmp();
let loop_start = ctx.fresh_label();
let ret_found = ctx.fresh_label();
let ret_null = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(s.clone(), ptr_char.clone());
var_types.insert(c.clone(), Type::Int);
var_types.insert(target.clone(), Type::Char);
var_types.insert(idx.clone(), Type::Long);
var_types.insert(ptr.clone(), ptr_char);
var_types.insert(ch.clone(), Type::Char);
var_types.insert(ci.clone(), Type::Int);
var_types.insert(ti.clone(), Type::Int);
var_types.insert(eq.clone(), Type::Int);
let body = vec![
TackyInstruction::Truncate {
src: TackyVal::Var(c.clone()),
dst: TackyVal::Var(target.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(target.clone()),
dst: TackyVal::Var(ti.clone()),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Label(loop_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(s.clone()),
index: TackyVal::Var(idx.clone()),
scale: 1,
dst: TackyVal::Var(ptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(ptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Equal,
left: TackyVal::Var(ci.clone()),
right: TackyVal::Var(ti.clone()),
dst: TackyVal::Var(eq.clone()),
},
TackyInstruction::JumpIfNotZero {
condition: TackyVal::Var(eq.clone()),
target: ret_found.clone(),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: ret_null.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(idx.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(idx.clone()),
},
TackyInstruction::Jump(loop_start),
TackyInstruction::Label(ret_found),
TackyInstruction::Return(TackyVal::Var(ptr.clone())),
TackyInstruction::Label(ret_null),
TackyInstruction::Return(TackyVal::Constant(TackyConst::Long(0))),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![s, c],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn generate_strcat(ctx: &mut ObfCtx, name: &str) -> TackyFunction {
let dst = "p_dst".to_string();
let src = "p_src".to_string();
let di = ctx.fresh_tmp(); let si = ctx.fresh_tmp(); let dptr = ctx.fresh_tmp(); let sptr = ctx.fresh_tmp(); let ch = ctx.fresh_tmp(); let ci = ctx.fresh_tmp();
let find_start = ctx.fresh_label();
let find_end = ctx.fresh_label();
let copy_start = ctx.fresh_label();
let copy_end = ctx.fresh_label();
let ptr_char = Type::Pointer(Box::new(Type::Char));
let mut var_types = HashMap::new();
var_types.insert(dst.clone(), ptr_char.clone());
var_types.insert(src.clone(), ptr_char.clone());
var_types.insert(di.clone(), Type::Long);
var_types.insert(si.clone(), Type::Long);
var_types.insert(dptr.clone(), ptr_char.clone());
var_types.insert(sptr.clone(), ptr_char);
var_types.insert(ch.clone(), Type::Char);
var_types.insert(ci.clone(), Type::Int);
let body = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(di.clone()),
},
TackyInstruction::Label(find_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(di.clone()),
scale: 1,
dst: TackyVal::Var(dptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(dptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: find_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(di.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(di.clone()),
},
TackyInstruction::Jump(find_start),
TackyInstruction::Label(find_end.clone()),
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(si.clone()),
},
TackyInstruction::Label(copy_start.clone()),
TackyInstruction::AddPtr {
ptr: TackyVal::Var(src.clone()),
index: TackyVal::Var(si.clone()),
scale: 1,
dst: TackyVal::Var(sptr.clone()),
},
TackyInstruction::Load {
src_ptr: TackyVal::Var(sptr.clone()),
dst: TackyVal::Var(ch.clone()),
},
TackyInstruction::AddPtr {
ptr: TackyVal::Var(dst.clone()),
index: TackyVal::Var(di.clone()),
scale: 1,
dst: TackyVal::Var(dptr.clone()),
},
TackyInstruction::Store {
src: TackyVal::Var(ch.clone()),
dst_ptr: TackyVal::Var(dptr.clone()),
},
TackyInstruction::ZeroExtend {
src: TackyVal::Var(ch.clone()),
dst: TackyVal::Var(ci.clone()),
},
TackyInstruction::JumpIfZero {
condition: TackyVal::Var(ci.clone()),
target: copy_end.clone(),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(di.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(di.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(si.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(si.clone()),
},
TackyInstruction::Jump(copy_start),
TackyInstruction::Label(copy_end),
TackyInstruction::Return(TackyVal::Var(dst.clone())),
];
TackyFunction {
name: name.to_string(),
global: true,
params: vec![dst, src],
body,
return_type: Type::Pointer(Box::new(Type::Char)),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn inline_functions(program: &mut TackyProgram, ctx: &mut ObfCtx, freq: usize) {
let static_names: HashSet<String> = program
.static_vars
.iter()
.map(|v| v.name.clone())
.chain(program.static_constants.iter().map(|c| c.name.clone()))
.collect();
let callee_map: HashMap<String, TackyFunction> = program
.functions
.iter()
.map(|f| (f.name.clone(), f.clone()))
.collect();
for func in &mut program.functions {
let mut new_body = Vec::new();
let mut eligible_count = 0usize;
for instr in std::mem::take(&mut func.body) {
if let TackyInstruction::FunCall {
ref name,
ref args,
ref dst,
ref dst_type,
is_variadic: _,
} = instr
&& let Some(callee) = callee_map.get(name)
&& is_inline_eligible(callee, name, dst_type, &static_names)
{
eligible_count += 1;
if freq > 0 && eligible_count.is_multiple_of(freq) {
let prefix = format!("_inline_{}", ctx.inline_counter);
ctx.inline_counter += 1;
let end_label = format!("{}_end", prefix);
for (param, arg) in callee.params.iter().zip(args.iter()) {
let renamed_param = format!("{}_{}", prefix, param);
new_body.push(TackyInstruction::Copy {
src: arg.clone(),
dst: TackyVal::Var(renamed_param),
});
}
for callee_instr in &callee.body {
match callee_instr {
TackyInstruction::Return(val) => {
if !matches!(callee.return_type, Type::Void) {
new_body.push(TackyInstruction::Copy {
src: rename_val(val, &prefix, &static_names),
dst: dst.clone(),
});
}
new_body.push(TackyInstruction::Jump(end_label.clone()));
}
TackyInstruction::ReturnVoid => {
new_body.push(TackyInstruction::Jump(end_label.clone()));
}
_ => {
new_body.push(rename_instruction(
callee_instr,
&prefix,
&static_names,
dst,
&end_label,
&callee.return_type,
));
}
}
}
new_body.push(TackyInstruction::Label(end_label.clone()));
for (var_name, var_type) in &callee.var_types {
if !static_names.contains(var_name) {
let renamed = format!("{}_{}", prefix, var_name);
func.var_types.insert(renamed, var_type.clone());
}
}
continue;
}
}
new_body.push(instr);
}
func.body = new_body;
}
}
fn is_inline_eligible(
callee: &TackyFunction,
callee_name: &str,
dst_type: &Type,
static_names: &HashSet<String>,
) -> bool {
if callee_name == "main" {
return false;
}
if callee.body.is_empty() {
return false;
}
if callee.body.len() > 50 {
return false;
}
if matches!(dst_type, Type::Struct { .. }) {
return false;
}
if callee.is_variadic {
return false;
}
if is_directly_recursive(callee) {
return false;
}
if has_param_address_taken(callee, static_names) {
return false;
}
if callee.body.iter().any(|instr| {
matches!(instr, TackyInstruction::FunCall { name, .. } if name.starts_with("__call_expr."))
}) {
return false;
}
true
}
fn is_directly_recursive(func: &TackyFunction) -> bool {
func.body
.iter()
.any(|instr| matches!(instr, TackyInstruction::FunCall { name, .. } if name == &func.name))
}
fn has_param_address_taken(func: &TackyFunction, static_names: &HashSet<String>) -> bool {
let params: HashSet<&str> = func.params.iter().map(|s| s.as_str()).collect();
func.body.iter().any(|instr| {
if let TackyInstruction::GetAddress {
src: TackyVal::Var(name),
..
} = instr
{
!static_names.contains(name) && params.contains(name.as_str())
} else {
false
}
})
}
fn rename_val(val: &TackyVal, prefix: &str, static_names: &HashSet<String>) -> TackyVal {
match val {
TackyVal::Var(name) => {
if static_names.contains(name) {
val.clone()
} else {
TackyVal::Var(format!("{}_{}", prefix, name))
}
}
TackyVal::Constant(_) => val.clone(),
}
}
fn rename_label(label: &str, prefix: &str) -> String {
format!("{}_{}", prefix, label)
}
fn rename_instruction(
instr: &TackyInstruction,
prefix: &str,
static_names: &HashSet<String>,
call_dst: &TackyVal,
end_label: &str,
return_type: &Type,
) -> TackyInstruction {
let rv = |v: &TackyVal| rename_val(v, prefix, static_names);
let rl = |l: &str| rename_label(l, prefix);
match instr {
TackyInstruction::Return(val) => {
if matches!(return_type, Type::Void) {
TackyInstruction::Jump(end_label.to_string())
} else {
TackyInstruction::Copy {
src: rv(val),
dst: call_dst.clone(),
}
}
}
TackyInstruction::ReturnVoid => TackyInstruction::Jump(end_label.to_string()),
TackyInstruction::Unary { op, src, dst } => TackyInstruction::Unary {
op: *op,
src: rv(src),
dst: rv(dst),
},
TackyInstruction::Binary {
op,
left,
right,
dst,
} => TackyInstruction::Binary {
op: *op,
left: rv(left),
right: rv(right),
dst: rv(dst),
},
TackyInstruction::Copy { src, dst } => TackyInstruction::Copy {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::Jump(target) => TackyInstruction::Jump(rl(target)),
TackyInstruction::JumpIfZero { condition, target } => TackyInstruction::JumpIfZero {
condition: rv(condition),
target: rl(target),
},
TackyInstruction::JumpIfNotZero { condition, target } => TackyInstruction::JumpIfNotZero {
condition: rv(condition),
target: rl(target),
},
TackyInstruction::Label(name) => TackyInstruction::Label(rl(name)),
TackyInstruction::FunCall {
name,
args,
dst,
dst_type,
is_variadic,
} => TackyInstruction::FunCall {
name: name.clone(), args: args.iter().map(rv).collect(),
dst: rv(dst),
dst_type: dst_type.clone(),
is_variadic: *is_variadic,
},
TackyInstruction::SignExtend { src, dst } => TackyInstruction::SignExtend {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::ZeroExtend { src, dst } => TackyInstruction::ZeroExtend {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::Truncate { src, dst } => TackyInstruction::Truncate {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::IntToDouble { src, dst } => TackyInstruction::IntToDouble {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::DoubleToInt { src, dst } => TackyInstruction::DoubleToInt {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::UIntToDouble { src, dst } => TackyInstruction::UIntToDouble {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::DoubleToUInt { src, dst } => TackyInstruction::DoubleToUInt {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::FloatToDouble { src, dst } => TackyInstruction::FloatToDouble {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::DoubleToFloat { src, dst } => TackyInstruction::DoubleToFloat {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::IntToFloat { src, dst } => TackyInstruction::IntToFloat {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::FloatToInt { src, dst } => TackyInstruction::FloatToInt {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::UIntToFloat { src, dst } => TackyInstruction::UIntToFloat {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::FloatToUInt { src, dst } => TackyInstruction::FloatToUInt {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::GetAddress { src, dst } => TackyInstruction::GetAddress {
src: rv(src),
dst: rv(dst),
},
TackyInstruction::Load { src_ptr, dst } => TackyInstruction::Load {
src_ptr: rv(src_ptr),
dst: rv(dst),
},
TackyInstruction::Store { src, dst_ptr } => TackyInstruction::Store {
src: rv(src),
dst_ptr: rv(dst_ptr),
},
TackyInstruction::AddPtr {
ptr,
index,
scale,
dst,
} => TackyInstruction::AddPtr {
ptr: rv(ptr),
index: rv(index),
scale: *scale,
dst: rv(dst),
},
TackyInstruction::CopyToOffset { src, dst, offset } => TackyInstruction::CopyToOffset {
src: rv(src),
dst: if static_names.contains(dst) {
dst.clone()
} else {
format!("{}_{}", prefix, dst)
},
offset: *offset,
},
TackyInstruction::CopyFromOffset { src, offset, dst } => TackyInstruction::CopyFromOffset {
src: if static_names.contains(src) {
src.clone()
} else {
format!("{}_{}", prefix, src)
},
offset: *offset,
dst: rv(dst),
},
TackyInstruction::CopyStruct { src, dst, size } => TackyInstruction::CopyStruct {
src: rv(src),
dst: rv(dst),
size: *size,
},
TackyInstruction::JumpIndirect {
target,
possible_targets,
} => TackyInstruction::JumpIndirect {
target: rv(target),
possible_targets: possible_targets.iter().map(|l| rl(l)).collect(),
},
TackyInstruction::VaStart {
ap,
gp_offset_init,
fp_offset_init,
} => TackyInstruction::VaStart {
ap: rv(ap),
gp_offset_init: *gp_offset_init,
fp_offset_init: *fp_offset_init,
},
TackyInstruction::VaArg { ap, dst, arg_type } => TackyInstruction::VaArg {
ap: rv(ap),
dst: rv(dst),
arg_type: arg_type.clone(),
},
TackyInstruction::VaEnd => TackyInstruction::VaEnd,
}
}
fn outline_functions(program: &mut TackyProgram, ctx: &mut ObfCtx, min_block_size: usize) {
let mut new_functions: Vec<TackyFunction> = Vec::new();
const MAX_OUTLINES_PER_FUNC: usize = 30;
for func in &mut program.functions {
let mut new_body: Vec<TackyInstruction> = Vec::new();
let body = std::mem::take(&mut func.body);
let mut i = 0;
let mut outline_count = 0usize;
while i < body.len() {
if outline_count < MAX_OUTLINES_PER_FUNC
&& let Some(block_len) = find_outline_candidate(&body, i, min_block_size)
{
let block = &body[i..i + block_len];
if let Some((inputs, output_name, intermediates)) =
analyze_block(block, &body, i, &func.var_types)
{
if inputs.len() <= 6 {
let output_type = func
.var_types
.get(&output_name)
.cloned()
.unwrap_or(Type::Int);
let has_bad_type = matches!(
output_type,
Type::Float | Type::Double | Type::Struct { .. } | Type::Array(_, _)
) || inputs.iter().any(|name| {
let ty = func.var_types.get(name).unwrap_or(&Type::Int);
matches!(
ty,
Type::Float
| Type::Double
| Type::Struct { .. }
| Type::Array(_, _)
)
});
if !has_bad_type {
let outlined_func = build_outlined_function(
ctx,
block,
&inputs,
&output_name,
&intermediates,
&output_type,
&func.var_types,
);
let func_name = outlined_func.name.clone();
let call_args: Vec<TackyVal> = inputs
.iter()
.map(|name| TackyVal::Var(name.clone()))
.collect();
new_body.push(TackyInstruction::FunCall {
name: func_name,
args: call_args,
dst: TackyVal::Var(output_name),
dst_type: output_type,
is_variadic: false,
});
new_functions.push(outlined_func);
outline_count += 1;
i += block_len;
continue;
}
}
}
}
new_body.push(body[i].clone());
i += 1;
}
func.body = new_body;
}
program.functions.extend(new_functions);
}
fn find_outline_candidate(body: &[TackyInstruction], pos: usize, min_size: usize) -> Option<usize> {
let mut len = 0;
for instr in &body[pos..] {
match instr {
TackyInstruction::Copy { .. }
| TackyInstruction::Binary { .. }
| TackyInstruction::Unary { .. } => {
len += 1;
}
_ => break,
}
}
if len >= min_size { Some(len) } else { None }
}
fn analyze_block(
block: &[TackyInstruction],
full_body: &[TackyInstruction],
block_start: usize,
var_types: &HashMap<String, Type>,
) -> Option<(Vec<String>, String, HashSet<String>)> {
let mut inputs: Vec<String> = Vec::new();
let mut input_set: HashSet<String> = HashSet::new();
let mut written: HashSet<String> = HashSet::new();
for instr in block {
for src_val in instruction_sources(instr) {
if let TackyVal::Var(name) = src_val
&& !written.contains(name)
&& !input_set.contains(name)
{
inputs.push(name.clone());
input_set.insert(name.clone());
}
}
if let Some(dst_name) = instruction_dst_name(instr) {
written.insert(dst_name);
}
}
let output = instruction_dst_name(block.last()?)?;
let mut intermediates = written;
intermediates.remove(&output);
if !intermediates.is_empty() {
let block_end = block_start + block.len();
for (idx, instr) in full_body.iter().enumerate() {
if idx >= block_start && idx < block_end {
continue;
}
for operand in instruction_all_operands(instr) {
if let TackyVal::Var(name) = operand
&& intermediates.contains(name)
{
return None; }
}
match instr {
TackyInstruction::FunCall { name, .. } => {
if intermediates.contains(name) {
return None;
}
}
TackyInstruction::CopyToOffset { dst, .. } => {
if intermediates.contains(dst) {
return None;
}
}
TackyInstruction::CopyFromOffset { src, .. } => {
if intermediates.contains(src) {
return None;
}
}
_ => {}
}
}
}
let _ = var_types;
Some((inputs, output, intermediates))
}
fn instruction_sources(instr: &TackyInstruction) -> Vec<&TackyVal> {
match instr {
TackyInstruction::Copy { src, .. } => vec![src],
TackyInstruction::Unary { src, .. } => vec![src],
TackyInstruction::Binary { left, right, .. } => vec![left, right],
_ => vec![],
}
}
fn instruction_dst_name(instr: &TackyInstruction) -> Option<String> {
match instr {
TackyInstruction::Copy {
dst: TackyVal::Var(name),
..
}
| TackyInstruction::Unary {
dst: TackyVal::Var(name),
..
}
| TackyInstruction::Binary {
dst: TackyVal::Var(name),
..
} => Some(name.clone()),
_ => None,
}
}
fn instruction_all_operands(instr: &TackyInstruction) -> Vec<&TackyVal> {
match instr {
TackyInstruction::Return(val) => vec![val],
TackyInstruction::ReturnVoid => vec![],
TackyInstruction::Unary { src, dst, .. } => vec![src, dst],
TackyInstruction::Binary {
left, right, dst, ..
} => vec![left, right, dst],
TackyInstruction::Copy { src, dst } => vec![src, dst],
TackyInstruction::Jump(_) => vec![],
TackyInstruction::JumpIfZero { condition, .. }
| TackyInstruction::JumpIfNotZero { condition, .. } => vec![condition],
TackyInstruction::Label(_) => vec![],
TackyInstruction::FunCall { args, dst, .. } => {
let mut v: Vec<&TackyVal> = args.iter().collect();
v.push(dst);
v
}
TackyInstruction::SignExtend { src, dst }
| TackyInstruction::ZeroExtend { src, dst }
| TackyInstruction::Truncate { src, dst }
| TackyInstruction::IntToDouble { src, dst }
| TackyInstruction::DoubleToInt { src, dst }
| TackyInstruction::UIntToDouble { src, dst }
| TackyInstruction::DoubleToUInt { src, dst }
| TackyInstruction::FloatToDouble { src, dst }
| TackyInstruction::DoubleToFloat { src, dst }
| TackyInstruction::IntToFloat { src, dst }
| TackyInstruction::FloatToInt { src, dst }
| TackyInstruction::UIntToFloat { src, dst }
| TackyInstruction::FloatToUInt { src, dst } => vec![src, dst],
TackyInstruction::GetAddress { src, dst } => vec![src, dst],
TackyInstruction::Load { src_ptr, dst } => vec![src_ptr, dst],
TackyInstruction::Store { src, dst_ptr } => vec![src, dst_ptr],
TackyInstruction::AddPtr {
ptr, index, dst, ..
} => vec![ptr, index, dst],
TackyInstruction::CopyToOffset { src, .. } => vec![src],
TackyInstruction::CopyFromOffset { dst, .. } => vec![dst],
TackyInstruction::CopyStruct { src, dst, .. } => vec![src, dst],
TackyInstruction::JumpIndirect { target, .. } => vec![target],
TackyInstruction::VaStart { ap, .. } => vec![ap],
TackyInstruction::VaArg { ap, dst, .. } => vec![ap, dst],
TackyInstruction::VaEnd => vec![],
}
}
fn build_outlined_function(
ctx: &mut ObfCtx,
block: &[TackyInstruction],
inputs: &[String],
output_name: &str,
intermediates: &HashSet<String>,
output_type: &Type,
caller_var_types: &HashMap<String, Type>,
) -> TackyFunction {
let func_name = format!("_obf_outlined_{}", ctx.outline_counter);
ctx.outline_counter += 1;
let mut input_to_param: HashMap<String, String> = HashMap::new();
let mut params: Vec<String> = Vec::new();
let mut var_types: HashMap<String, Type> = HashMap::new();
for input_name in inputs {
let param_name = ctx.fresh_tmp();
let ty = caller_var_types
.get(input_name)
.cloned()
.unwrap_or(Type::Int);
var_types.insert(param_name.clone(), ty);
input_to_param.insert(input_name.clone(), param_name.clone());
params.push(param_name);
}
let mut intermediate_to_new: HashMap<String, String> = HashMap::new();
let mut intermediates_sorted: Vec<&String> = intermediates.iter().collect();
intermediates_sorted.sort();
for name in intermediates_sorted {
let new_name = ctx.fresh_tmp();
let ty = caller_var_types.get(name).cloned().unwrap_or(Type::Int);
var_types.insert(new_name.clone(), ty);
intermediate_to_new.insert(name.clone(), new_name);
}
let output_new = ctx.fresh_tmp();
var_types.insert(output_new.clone(), output_type.clone());
let output_init_copy: Option<TackyInstruction> =
input_to_param
.remove(output_name)
.map(|param_for_output| TackyInstruction::Copy {
src: TackyVal::Var(param_for_output),
dst: TackyVal::Var(output_new.clone()),
});
let rename = |val: &TackyVal| -> TackyVal {
match val {
TackyVal::Var(name) => {
if let Some(param) = input_to_param.get(name) {
TackyVal::Var(param.clone())
} else if let Some(new_name) = intermediate_to_new.get(name) {
TackyVal::Var(new_name.clone())
} else if name == output_name {
TackyVal::Var(output_new.clone())
} else {
val.clone()
}
}
TackyVal::Constant(_) => val.clone(),
}
};
let mut body: Vec<TackyInstruction> = Vec::new();
if let Some(init_copy) = output_init_copy {
body.push(init_copy);
}
for instr in block {
let new_instr = match instr {
TackyInstruction::Copy { src, dst } => TackyInstruction::Copy {
src: rename(src),
dst: rename(dst),
},
TackyInstruction::Unary { op, src, dst } => TackyInstruction::Unary {
op: *op,
src: rename(src),
dst: rename(dst),
},
TackyInstruction::Binary {
op,
left,
right,
dst,
} => TackyInstruction::Binary {
op: *op,
left: rename(left),
right: rename(right),
dst: rename(dst),
},
_ => instr.clone(),
};
body.push(new_instr);
}
body.push(TackyInstruction::Return(TackyVal::Var(output_new)));
TackyFunction {
name: func_name,
global: false,
params,
body,
return_type: output_type.clone(),
var_types,
is_variadic: false,
static_var_names: HashSet::new(),
has_sret: false,
}
}
fn string_encryption(program: &mut TackyProgram, ctx: &mut ObfCtx, key: u8) {
let mut encrypted_strings: Vec<(String, Vec<u8>, usize)> = Vec::new();
program.static_constants.retain(|sc| {
if let TackyStaticInit::StringInit(content, byte_len) = &sc.init {
let mut encrypted: Vec<u8> = content
.as_bytes()
.iter()
.map(|b| b.wrapping_add(key))
.collect();
encrypted.push(0u8.wrapping_add(key));
encrypted_strings.push((sc.name.clone(), encrypted, *byte_len));
false } else {
true }
});
if encrypted_strings.is_empty() {
return;
}
for (label, encrypted_bytes, byte_len) in &encrypted_strings {
program.static_vars.push(TackyStaticVar {
name: label.clone(),
global: false,
var_type: Type::Array(Box::new(Type::Char), *byte_len),
init: TackyStaticInit::ByteArrayInit(encrypted_bytes.clone()),
});
}
if let Some(main_func) = program.functions.iter_mut().find(|f| f.name == "main") {
let mut decrypt_instrs = Vec::new();
for (label, _, byte_len) in &encrypted_strings {
let base_ptr = ctx.fresh_tmp();
main_func
.var_types
.insert(base_ptr.clone(), Type::Pointer(Box::new(Type::Char)));
decrypt_instrs.push(TackyInstruction::GetAddress {
src: TackyVal::Var(label.clone()),
dst: TackyVal::Var(base_ptr.clone()),
});
for i in 0..*byte_len {
let byte_ptr = ctx.fresh_tmp();
let enc_byte = ctx.fresh_tmp();
let enc_int = ctx.fresh_tmp();
let dec_int = ctx.fresh_tmp();
let dec_byte = ctx.fresh_tmp();
main_func
.var_types
.insert(byte_ptr.clone(), Type::Pointer(Box::new(Type::Char)));
main_func.var_types.insert(enc_byte.clone(), Type::Char);
main_func.var_types.insert(enc_int.clone(), Type::Int);
main_func.var_types.insert(dec_int.clone(), Type::Int);
main_func.var_types.insert(dec_byte.clone(), Type::Char);
decrypt_instrs.push(TackyInstruction::AddPtr {
ptr: TackyVal::Var(base_ptr.clone()),
index: TackyVal::Constant(TackyConst::Int(i as i32)),
scale: 1,
dst: TackyVal::Var(byte_ptr.clone()),
});
decrypt_instrs.push(TackyInstruction::Load {
src_ptr: TackyVal::Var(byte_ptr.clone()),
dst: TackyVal::Var(enc_byte.clone()),
});
decrypt_instrs.push(TackyInstruction::SignExtend {
src: TackyVal::Var(enc_byte),
dst: TackyVal::Var(enc_int.clone()),
});
decrypt_instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(enc_int),
right: TackyVal::Constant(TackyConst::Int(key as i32)),
dst: TackyVal::Var(dec_int.clone()),
});
decrypt_instrs.push(TackyInstruction::Truncate {
src: TackyVal::Var(dec_int),
dst: TackyVal::Var(dec_byte.clone()),
});
decrypt_instrs.push(TackyInstruction::Store {
src: TackyVal::Var(dec_byte),
dst_ptr: TackyVal::Var(byte_ptr),
});
}
}
decrypt_instrs.append(&mut main_func.body);
main_func.body = decrypt_instrs;
}
}
fn constant_encoding(
instrs: Vec<TackyInstruction>,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let mut result = Vec::new();
for instr in instrs {
match &instr {
TackyInstruction::Copy {
src: TackyVal::Constant(c),
dst,
} => {
if let Some(encoded) = encode_constant(c, dst, ctx, var_types) {
result.extend(encoded);
continue;
}
result.push(instr);
}
_ => result.push(instr),
}
}
result
}
fn encode_constant(
c: &TackyConst,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Option<Vec<TackyInstruction>> {
match c {
TackyConst::Int(v) => Some(encode_int_constant(
*v as i64,
Type::Int,
dst,
ctx,
var_types,
|x| TackyConst::Int(x as i32),
)),
TackyConst::Long(v) => Some(encode_int_constant(
*v,
Type::Long,
dst,
ctx,
var_types,
TackyConst::Long,
)),
TackyConst::UInt(v) => Some(encode_int_constant(
*v as i64,
Type::UInt,
dst,
ctx,
var_types,
|x| TackyConst::UInt(x as u32),
)),
TackyConst::ULong(v) => Some(encode_int_constant(
*v as i64,
Type::ULong,
dst,
ctx,
var_types,
|x| TackyConst::ULong(x as u64),
)),
TackyConst::Char(v) => Some(encode_int_constant(
*v as i64,
Type::Char,
dst,
ctx,
var_types,
|x| TackyConst::Char(x as i8),
)),
TackyConst::UChar(v) => Some(encode_int_constant(
*v as i64,
Type::UChar,
dst,
ctx,
var_types,
|x| TackyConst::UChar(x as u8),
)),
TackyConst::Float(_) | TackyConst::Double(_) => None,
}
}
fn encode_int_constant<F>(
value: i64,
ty: Type,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
make_const: F,
) -> Vec<TackyInstruction>
where
F: Fn(i64) -> TackyConst,
{
let mut instrs = Vec::new();
if value == 0 {
let tmp_a = ctx.fresh_tmp();
var_types.insert(tmp_a.clone(), ty.clone());
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(make_const(7)),
dst: TackyVal::Var(tmp_a.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp_a.clone()),
right: TackyVal::Var(tmp_a),
dst: dst.clone(),
});
} else {
let (a, b, c) = decompose_value(value);
let tmp_a = ctx.fresh_tmp();
let tmp_b = ctx.fresh_tmp();
let tmp_mul = ctx.fresh_tmp();
var_types.insert(tmp_a.clone(), ty.clone());
var_types.insert(tmp_b.clone(), ty.clone());
var_types.insert(tmp_mul.clone(), ty.clone());
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(make_const(a)),
dst: TackyVal::Var(tmp_a.clone()),
});
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(make_const(b)),
dst: TackyVal::Var(tmp_b.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_a),
right: TackyVal::Var(tmp_b),
dst: TackyVal::Var(tmp_mul.clone()),
});
if c == 0 {
instrs.push(TackyInstruction::Copy {
src: TackyVal::Var(tmp_mul),
dst: dst.clone(),
});
} else {
let tmp_c = ctx.fresh_tmp();
var_types.insert(tmp_c.clone(), ty);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(make_const(c)),
dst: TackyVal::Var(tmp_c.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp_mul),
right: TackyVal::Var(tmp_c),
dst: dst.clone(),
});
}
}
instrs
}
fn decompose_value(value: i64) -> (i64, i64, i64) {
let factors = [7, 5, 3, 11, 13, 6, 9];
for &f in &factors {
if value % f == 0 && value / f != 1 && value / f != 0 {
return (f, value / f, 0);
}
}
let f = 7i64;
let q = value / f;
let r = value - f * q; if q != 0 {
(f, q, r)
} else {
(3, 1, value - 3)
}
}
fn arithmetic_substitution(
instrs: Vec<TackyInstruction>,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
freq: usize,
) -> Vec<TackyInstruction> {
let mut result = Vec::new();
let mut candidate_count = 0;
for instr in instrs {
match &instr {
TackyInstruction::Binary {
op,
left,
right,
dst,
} => {
let dst_is_obf = if let TackyVal::Var(name) = dst {
name.starts_with("obf_tmp.")
} else {
false
};
if !dst_is_obf {
match op {
TackyBinaryOp::Add => {
candidate_count += 1;
if candidate_count % freq == 0 {
let pattern = ctx.label_counter % 2;
ctx.label_counter += 1;
match pattern {
0 => result
.extend(arith_add_affine(left, right, dst, ctx, var_types)),
_ => result
.extend(arith_add_coeff(left, right, dst, ctx, var_types)),
}
continue;
}
}
TackyBinaryOp::Subtract => {
candidate_count += 1;
if candidate_count % freq == 0 {
let pattern = ctx.label_counter % 2;
ctx.label_counter += 1;
match pattern {
0 => result
.extend(arith_sub_affine(left, right, dst, ctx, var_types)),
_ => result
.extend(arith_sub_coeff(left, right, dst, ctx, var_types)),
}
continue;
}
}
_ => {}
}
}
result.push(instr);
}
_ => result.push(instr),
}
}
result
}
fn get_dst_type(dst: &TackyVal, var_types: &std::collections::HashMap<String, Type>) -> Type {
if let TackyVal::Var(name) = dst {
var_types.get(name).cloned().unwrap_or(Type::Int)
} else {
Type::Int
}
}
fn arith_add_affine(
left: &TackyVal,
right: &TackyVal,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let ty = get_dst_type(dst, var_types);
let k = ((ctx.label_counter as i64).wrapping_mul(0x9E37) ^ 0x1F2D) & 0x7FFF;
let k_const = make_typed_const(&ty, k);
let k_const2 = make_typed_const(&ty, k);
let tmp1 = ctx.fresh_tmp();
let tmp2 = ctx.fresh_tmp();
var_types.insert(tmp1.clone(), ty.clone());
var_types.insert(tmp2.clone(), ty);
vec![
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: left.clone(),
right: TackyVal::Constant(k_const),
dst: TackyVal::Var(tmp1.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: right.clone(),
right: TackyVal::Constant(k_const2),
dst: TackyVal::Var(tmp2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp1),
right: TackyVal::Var(tmp2),
dst: dst.clone(),
},
]
}
fn arith_add_coeff(
left: &TackyVal,
right: &TackyVal,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let ty = get_dst_type(dst, var_types);
let three = make_typed_const(&ty, 3);
let three2 = make_typed_const(&ty, 3);
let two = make_typed_const(&ty, 2);
let two2 = make_typed_const(&ty, 2);
let tmp1 = ctx.fresh_tmp(); let tmp2 = ctx.fresh_tmp(); let tmp3 = ctx.fresh_tmp(); let tmp4 = ctx.fresh_tmp(); let tmp5 = ctx.fresh_tmp(); let tmp6 = ctx.fresh_tmp(); for t in [&tmp1, &tmp2, &tmp3, &tmp4, &tmp5, &tmp6] {
var_types.insert(t.clone(), ty.clone());
}
vec![
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: left.clone(),
right: TackyVal::Constant(three),
dst: TackyVal::Var(tmp1.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: right.clone(),
right: TackyVal::Constant(three2),
dst: TackyVal::Var(tmp2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp1),
right: TackyVal::Var(tmp2),
dst: TackyVal::Var(tmp3.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: left.clone(),
right: TackyVal::Constant(two),
dst: TackyVal::Var(tmp4.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: right.clone(),
right: TackyVal::Constant(two2),
dst: TackyVal::Var(tmp5.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp4),
right: TackyVal::Var(tmp5),
dst: TackyVal::Var(tmp6.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp3),
right: TackyVal::Var(tmp6),
dst: dst.clone(),
},
]
}
fn arith_sub_affine(
left: &TackyVal,
right: &TackyVal,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let ty = get_dst_type(dst, var_types);
let k = ((ctx.label_counter as i64).wrapping_mul(0xA3B7) ^ 0x2E4C) & 0x7FFF;
let k_const = make_typed_const(&ty, k);
let k_const2 = make_typed_const(&ty, k);
let tmp1 = ctx.fresh_tmp();
let tmp2 = ctx.fresh_tmp();
var_types.insert(tmp1.clone(), ty.clone());
var_types.insert(tmp2.clone(), ty);
vec![
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: left.clone(),
right: TackyVal::Constant(k_const),
dst: TackyVal::Var(tmp1.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: right.clone(),
right: TackyVal::Constant(k_const2),
dst: TackyVal::Var(tmp2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp1),
right: TackyVal::Var(tmp2),
dst: dst.clone(),
},
]
}
fn arith_sub_coeff(
left: &TackyVal,
right: &TackyVal,
dst: &TackyVal,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let ty = get_dst_type(dst, var_types);
let three = make_typed_const(&ty, 3);
let three2 = make_typed_const(&ty, 3);
let two = make_typed_const(&ty, 2);
let two2 = make_typed_const(&ty, 2);
let tmp1 = ctx.fresh_tmp(); let tmp2 = ctx.fresh_tmp(); let tmp3 = ctx.fresh_tmp(); let tmp4 = ctx.fresh_tmp(); let tmp5 = ctx.fresh_tmp(); let tmp6 = ctx.fresh_tmp(); for t in [&tmp1, &tmp2, &tmp3, &tmp4, &tmp5, &tmp6] {
var_types.insert(t.clone(), ty.clone());
}
vec![
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: left.clone(),
right: TackyVal::Constant(three),
dst: TackyVal::Var(tmp1.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: right.clone(),
right: TackyVal::Constant(three2),
dst: TackyVal::Var(tmp2.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp1),
right: TackyVal::Var(tmp2),
dst: TackyVal::Var(tmp3.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: left.clone(),
right: TackyVal::Constant(two),
dst: TackyVal::Var(tmp4.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: right.clone(),
right: TackyVal::Constant(two2),
dst: TackyVal::Var(tmp5.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp4),
right: TackyVal::Var(tmp5),
dst: TackyVal::Var(tmp6.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp3),
right: TackyVal::Var(tmp6),
dst: dst.clone(),
},
]
}
fn make_typed_const(ty: &Type, value: i64) -> TackyConst {
match ty {
Type::Int => TackyConst::Int(value as i32),
Type::Long => TackyConst::Long(value),
Type::UInt => TackyConst::UInt(value as u32),
Type::ULong => TackyConst::ULong(value as u64),
Type::Char => TackyConst::Char(value as i8),
Type::UChar => TackyConst::UChar(value as u8),
_ => TackyConst::Int(value as i32),
}
}
fn junk_code_insertion(
instrs: Vec<TackyInstruction>,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
freq: usize,
) -> Vec<TackyInstruction> {
let mut result = Vec::new();
for (count, (i, instr)) in instrs.iter().enumerate().enumerate() {
if count > 0 && count % freq == 0 {
let next_is_label = instrs
.get(i)
.is_some_and(|next| matches!(next, TackyInstruction::Label(_)));
if !next_is_label {
result.extend(generate_junk(ctx, var_types));
}
}
result.push(instr.clone());
}
result
}
fn generate_junk(
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let tmp_x = ctx.fresh_tmp();
let tmp_y = ctx.fresh_tmp();
let tmp_z = ctx.fresh_tmp();
var_types.insert(tmp_x.clone(), Type::Int);
var_types.insert(tmp_y.clone(), Type::Int);
var_types.insert(tmp_z.clone(), Type::Int);
vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(0x1234)),
dst: TackyVal::Var(tmp_x.clone()),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(0x5678)),
dst: TackyVal::Var(tmp_y.clone()),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp_x),
right: TackyVal::Var(tmp_y),
dst: TackyVal::Var(tmp_z),
},
]
}
fn opaque_predicates(
instrs: Vec<TackyInstruction>,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
freq: usize,
) -> Vec<TackyInstruction> {
let mut result = Vec::new();
let mut candidate_count = 0;
for instr in instrs.iter() {
if is_value_producing(instr) {
candidate_count += 1;
if candidate_count % freq == 0 {
result.extend(wrap_with_opaque_predicate(instr.clone(), ctx, var_types));
continue;
}
}
result.push(instr.clone());
}
result
}
fn is_value_producing(instr: &TackyInstruction) -> bool {
matches!(
instr,
TackyInstruction::Copy { .. }
| TackyInstruction::Unary { .. }
| TackyInstruction::Binary { .. }
| TackyInstruction::SignExtend { .. }
| TackyInstruction::ZeroExtend { .. }
| TackyInstruction::Truncate { .. }
)
}
fn wrap_with_opaque_predicate(
real_instr: TackyInstruction,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
) -> Vec<TackyInstruction> {
let mut instrs = Vec::new();
let label_real = format!(".Lpred_{}", ctx.label_counter);
ctx.label_counter += 1;
let label_end = format!(".Lpred_{}", ctx.label_counter);
ctx.label_counter += 1;
let pattern = ctx.label_counter % 4;
let pred_var = match pattern {
0 => generate_predicate_0(ctx, var_types, &mut instrs),
1 => generate_predicate_1(ctx, var_types, &mut instrs),
2 => generate_predicate_2(ctx, var_types, &mut instrs),
3 => generate_predicate_3(ctx, var_types, &mut instrs),
_ => unreachable!(),
};
instrs.push(TackyInstruction::JumpIfZero {
condition: TackyVal::Var(pred_var),
target: label_real.clone(),
});
let tmp_fake = ctx.fresh_tmp();
var_types.insert(tmp_fake.clone(), Type::Int);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(0xDEAD)),
dst: TackyVal::Var(tmp_fake),
});
instrs.push(TackyInstruction::Jump(label_end.clone()));
instrs.push(TackyInstruction::Label(label_real));
instrs.push(real_instr);
instrs.push(TackyInstruction::Label(label_end));
instrs
}
fn generate_predicate_0(
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
instrs: &mut Vec<TackyInstruction>,
) -> String {
let tmp_x = ctx.fresh_tmp();
let tmp_x_plus_1 = ctx.fresh_tmp();
let tmp_prod = ctx.fresh_tmp();
let tmp_pred = ctx.fresh_tmp();
var_types.insert(tmp_x.clone(), Type::Int);
var_types.insert(tmp_x_plus_1.clone(), Type::Int);
var_types.insert(tmp_prod.clone(), Type::Int);
var_types.insert(tmp_pred.clone(), Type::Int);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(42)),
dst: TackyVal::Var(tmp_x.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp_x.clone()),
right: TackyVal::Constant(TackyConst::Int(1)),
dst: TackyVal::Var(tmp_x_plus_1.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x),
right: TackyVal::Var(tmp_x_plus_1),
dst: TackyVal::Var(tmp_prod.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Remainder,
left: TackyVal::Var(tmp_prod),
right: TackyVal::Constant(TackyConst::Int(2)),
dst: TackyVal::Var(tmp_pred.clone()),
});
tmp_pred
}
fn generate_predicate_1(
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
instrs: &mut Vec<TackyInstruction>,
) -> String {
let tmp_x = ctx.fresh_tmp();
let tmp_sq = ctx.fresh_tmp();
let tmp_sq_plus_1 = ctx.fresh_tmp();
let tmp_gt = ctx.fresh_tmp();
let tmp_pred = ctx.fresh_tmp();
var_types.insert(tmp_x.clone(), Type::Int);
var_types.insert(tmp_sq.clone(), Type::Int);
var_types.insert(tmp_sq_plus_1.clone(), Type::Int);
var_types.insert(tmp_gt.clone(), Type::Int);
var_types.insert(tmp_pred.clone(), Type::Int);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(17)),
dst: TackyVal::Var(tmp_x.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x.clone()),
right: TackyVal::Var(tmp_x),
dst: TackyVal::Var(tmp_sq.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp_sq),
right: TackyVal::Constant(TackyConst::Int(1)),
dst: TackyVal::Var(tmp_sq_plus_1.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::GreaterThan,
left: TackyVal::Var(tmp_sq_plus_1),
right: TackyVal::Constant(TackyConst::Int(0)),
dst: TackyVal::Var(tmp_gt.clone()),
});
instrs.push(TackyInstruction::Unary {
op: TackyUnaryOp::Not,
src: TackyVal::Var(tmp_gt),
dst: TackyVal::Var(tmp_pred.clone()),
});
tmp_pred
}
fn generate_predicate_2(
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
instrs: &mut Vec<TackyInstruction>,
) -> String {
let tmp_x = ctx.fresh_tmp();
let tmp_x1 = ctx.fresh_tmp();
let tmp_x1_sq = ctx.fresh_tmp();
let tmp_x_sq = ctx.fresh_tmp();
let tmp_sub1 = ctx.fresh_tmp();
let tmp_sub2 = ctx.fresh_tmp();
let tmp_2x = ctx.fresh_tmp();
let tmp_pred = ctx.fresh_tmp();
var_types.insert(tmp_x.clone(), Type::Int);
var_types.insert(tmp_x1.clone(), Type::Int);
var_types.insert(tmp_x1_sq.clone(), Type::Int);
var_types.insert(tmp_x_sq.clone(), Type::Int);
var_types.insert(tmp_sub1.clone(), Type::Int);
var_types.insert(tmp_sub2.clone(), Type::Int);
var_types.insert(tmp_2x.clone(), Type::Int);
var_types.insert(tmp_pred.clone(), Type::Int);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(13)),
dst: TackyVal::Var(tmp_x.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(tmp_x.clone()),
right: TackyVal::Constant(TackyConst::Int(1)),
dst: TackyVal::Var(tmp_x1.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x1.clone()),
right: TackyVal::Var(tmp_x1),
dst: TackyVal::Var(tmp_x1_sq.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x.clone()),
right: TackyVal::Var(tmp_x.clone()),
dst: TackyVal::Var(tmp_x_sq.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp_x1_sq),
right: TackyVal::Var(tmp_x_sq),
dst: TackyVal::Var(tmp_sub1.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp_sub1),
right: TackyVal::Constant(TackyConst::Int(1)),
dst: TackyVal::Var(tmp_sub2.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Constant(TackyConst::Int(2)),
right: TackyVal::Var(tmp_x),
dst: TackyVal::Var(tmp_2x.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp_sub2),
right: TackyVal::Var(tmp_2x),
dst: TackyVal::Var(tmp_pred.clone()),
});
tmp_pred
}
fn generate_predicate_3(
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
instrs: &mut Vec<TackyInstruction>,
) -> String {
let tmp_x = ctx.fresh_tmp();
let tmp_x_sq = ctx.fresh_tmp();
let tmp_x_cubed = ctx.fresh_tmp();
let tmp_diff = ctx.fresh_tmp();
let tmp_pred = ctx.fresh_tmp();
var_types.insert(tmp_x.clone(), Type::Int);
var_types.insert(tmp_x_sq.clone(), Type::Int);
var_types.insert(tmp_x_cubed.clone(), Type::Int);
var_types.insert(tmp_diff.clone(), Type::Int);
var_types.insert(tmp_pred.clone(), Type::Int);
instrs.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(7)),
dst: TackyVal::Var(tmp_x.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x.clone()),
right: TackyVal::Var(tmp_x.clone()),
dst: TackyVal::Var(tmp_x_sq.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: TackyVal::Var(tmp_x_sq),
right: TackyVal::Var(tmp_x.clone()),
dst: TackyVal::Var(tmp_x_cubed.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(tmp_x_cubed),
right: TackyVal::Var(tmp_x),
dst: TackyVal::Var(tmp_diff.clone()),
});
instrs.push(TackyInstruction::Binary {
op: TackyBinaryOp::Remainder,
left: TackyVal::Var(tmp_diff),
right: TackyVal::Constant(TackyConst::Int(3)),
dst: TackyVal::Var(tmp_pred.clone()),
});
tmp_pred
}
fn is_vm_eligible(func: &TackyFunction) -> bool {
if func.name == "main" {
return false;
}
if func.body.len() < 2 {
return false;
}
if func
.var_types
.values()
.any(|t| matches!(t, Type::Float | Type::Double))
{
return false;
}
for instr in &func.body {
match instr {
TackyInstruction::IntToDouble { .. }
| TackyInstruction::DoubleToInt { .. }
| TackyInstruction::UIntToDouble { .. }
| TackyInstruction::DoubleToUInt { .. }
| TackyInstruction::FloatToDouble { .. }
| TackyInstruction::DoubleToFloat { .. }
| TackyInstruction::IntToFloat { .. }
| TackyInstruction::FloatToInt { .. }
| TackyInstruction::UIntToFloat { .. }
| TackyInstruction::FloatToUInt { .. }
| TackyInstruction::CopyToOffset { .. }
| TackyInstruction::CopyFromOffset { .. }
| TackyInstruction::CopyStruct { .. } => return false,
_ => {}
}
}
true
}
fn vm_virtualize(program: &mut TackyProgram, ctx: &mut ObfCtx) {
let func_count = program.functions.len();
for fi in 0..func_count {
if !is_vm_eligible(&program.functions[fi]) {
continue;
}
let func = &program.functions[fi];
let original_body = func.body.clone();
let n = original_body.len();
if n == 0 {
continue;
}
let mut label_to_pc: HashMap<String, usize> = HashMap::new();
for (i, instr) in original_body.iter().enumerate() {
if let TackyInstruction::Label(name) = instr {
label_to_pc.insert(name.clone(), i);
}
}
let dispatch_label = ctx.fresh_label();
let handler_labels: Vec<String> = (0..n).map(|_| ctx.fresh_label()).collect();
let mut bc_bytes: Vec<u8> = Vec::new();
for i in 0..n {
bc_bytes.extend_from_slice(&(i as u32).to_le_bytes());
}
let bc_name = format!(".Lobf_vm_bc_{}", ctx.vm_counter);
program.static_vars.push(TackyStaticVar {
name: bc_name.clone(),
global: false,
var_type: Type::Array(Box::new(Type::UChar), bc_bytes.len()),
init: TackyStaticInit::ByteArrayInit(bc_bytes),
});
let jt_name = format!(".Lobf_vm_jt_{}", ctx.vm_counter);
program.static_vars.push(TackyStaticVar {
name: jt_name.clone(),
global: false,
var_type: Type::Array(Box::new(Type::Long), n),
init: TackyStaticInit::PointerArrayInit(handler_labels.clone()),
});
ctx.vm_counter += 1;
let var_types = &mut program.functions[fi].var_types;
let mut new_body: Vec<TackyInstruction> = Vec::new();
let pc_var = ctx.fresh_tmp();
let bc_ptr_var = ctx.fresh_tmp();
let jt_ptr_var = ctx.fresh_tmp();
var_types.insert(pc_var.clone(), Type::Long);
var_types.insert(bc_ptr_var.clone(), Type::Pointer(Box::new(Type::UChar)));
var_types.insert(jt_ptr_var.clone(), Type::Pointer(Box::new(Type::Long)));
new_body.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(0)),
dst: TackyVal::Var(pc_var.clone()),
});
new_body.push(TackyInstruction::GetAddress {
src: TackyVal::Var(bc_name),
dst: TackyVal::Var(bc_ptr_var.clone()),
});
new_body.push(TackyInstruction::GetAddress {
src: TackyVal::Var(jt_name),
dst: TackyVal::Var(jt_ptr_var.clone()),
});
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
new_body.push(TackyInstruction::Label(dispatch_label.clone()));
let fetch_ptr_var = ctx.fresh_tmp();
let handler_idx_var = ctx.fresh_tmp();
let handler_addr_ptr_var = ctx.fresh_tmp();
let handler_addr_var = ctx.fresh_tmp();
var_types.insert(fetch_ptr_var.clone(), Type::Pointer(Box::new(Type::Int)));
var_types.insert(handler_idx_var.clone(), Type::Int);
var_types.insert(
handler_addr_ptr_var.clone(),
Type::Pointer(Box::new(Type::Long)),
);
var_types.insert(handler_addr_var.clone(), Type::Long);
new_body.push(TackyInstruction::AddPtr {
ptr: TackyVal::Var(bc_ptr_var.clone()),
index: TackyVal::Var(pc_var.clone()),
scale: 4,
dst: TackyVal::Var(fetch_ptr_var.clone()),
});
new_body.push(TackyInstruction::Load {
src_ptr: TackyVal::Var(fetch_ptr_var),
dst: TackyVal::Var(handler_idx_var.clone()),
});
new_body.push(TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: TackyVal::Var(pc_var.clone()),
right: TackyVal::Constant(TackyConst::Long(1)),
dst: TackyVal::Var(pc_var.clone()),
});
let handler_idx_long_var = ctx.fresh_tmp();
var_types.insert(handler_idx_long_var.clone(), Type::Long);
new_body.push(TackyInstruction::SignExtend {
src: TackyVal::Var(handler_idx_var),
dst: TackyVal::Var(handler_idx_long_var.clone()),
});
new_body.push(TackyInstruction::AddPtr {
ptr: TackyVal::Var(jt_ptr_var.clone()),
index: TackyVal::Var(handler_idx_long_var),
scale: 8,
dst: TackyVal::Var(handler_addr_ptr_var.clone()),
});
new_body.push(TackyInstruction::Load {
src_ptr: TackyVal::Var(handler_addr_ptr_var),
dst: TackyVal::Var(handler_addr_var.clone()),
});
new_body.push(TackyInstruction::JumpIndirect {
target: TackyVal::Var(handler_addr_var),
possible_targets: handler_labels.clone(),
});
for (i, instr) in original_body.iter().enumerate() {
new_body.push(TackyInstruction::Label(handler_labels[i].clone()));
match instr {
TackyInstruction::Label(_) => {
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
}
TackyInstruction::Jump(target) => {
if let Some(&target_pc) = label_to_pc.get(target) {
new_body.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(target_pc as i64)),
dst: TackyVal::Var(pc_var.clone()),
});
}
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
}
TackyInstruction::JumpIfZero { condition, target } => {
if let Some(&target_pc) = label_to_pc.get(target) {
let skip = ctx.fresh_label();
new_body.push(TackyInstruction::JumpIfNotZero {
condition: condition.clone(),
target: skip.clone(),
});
new_body.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(target_pc as i64)),
dst: TackyVal::Var(pc_var.clone()),
});
new_body.push(TackyInstruction::Label(skip));
}
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
}
TackyInstruction::JumpIfNotZero { condition, target } => {
if let Some(&target_pc) = label_to_pc.get(target) {
let skip = ctx.fresh_label();
new_body.push(TackyInstruction::JumpIfZero {
condition: condition.clone(),
target: skip.clone(),
});
new_body.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Long(target_pc as i64)),
dst: TackyVal::Var(pc_var.clone()),
});
new_body.push(TackyInstruction::Label(skip));
}
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
}
TackyInstruction::Return(_) | TackyInstruction::ReturnVoid => {
new_body.push(instr.clone());
}
_ => {
new_body.push(instr.clone());
new_body.push(TackyInstruction::Jump(dispatch_label.clone()));
}
}
}
program.functions[fi].body = new_body;
}
}
fn control_flow_flattening(
instrs: Vec<TackyInstruction>,
ctx: &mut ObfCtx,
var_types: &mut std::collections::HashMap<String, Type>,
static_vars: &mut Vec<TackyStaticVar>,
cff_a: i32,
cff_b: i32,
) -> Vec<TackyInstruction> {
if instrs.is_empty() {
return instrs;
}
let blocks = split_into_blocks(&instrs);
if blocks.len() <= 1 {
return instrs;
}
let has_variadic_call = instrs.iter().any(|i| {
matches!(
i,
TackyInstruction::FunCall {
is_variadic: true,
..
}
)
});
if has_variadic_call {
return instrs;
}
let mut label_to_block: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for (i, block) in blocks.iter().enumerate() {
if let Some(TackyInstruction::Label(label)) = block.first() {
label_to_block.insert(label.clone(), i);
}
}
let state_var = ctx.fresh_tmp();
var_types.insert(state_var.clone(), Type::Int);
let dispatch_label = ctx.fresh_label();
let exit_label = ctx.fresh_label();
let mut result = Vec::new();
let block_labels: Vec<String> = (0..blocks.len()).map(|_| ctx.fresh_label()).collect();
let jt_name = format!(".Lobf_jt_{}", ctx.label_counter);
ctx.label_counter += 1;
static_vars.push(TackyStaticVar {
name: jt_name.clone(),
global: false,
var_type: Type::Array(Box::new(Type::Long), blocks.len()),
init: TackyStaticInit::PointerArrayInit(block_labels.clone()),
});
let encode = |index: usize| -> i32 { (index as i32).wrapping_mul(cff_a).wrapping_add(cff_b) };
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(0))),
dst: TackyVal::Var(state_var.clone()),
});
result.push(TackyInstruction::Jump(dispatch_label.clone()));
result.push(TackyInstruction::Label(dispatch_label.clone()));
let tmp_sub = ctx.fresh_tmp();
let decoded_index = ctx.fresh_tmp();
var_types.insert(tmp_sub.clone(), Type::Int);
var_types.insert(decoded_index.clone(), Type::Int);
result.push(TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: TackyVal::Var(state_var.clone()),
right: TackyVal::Constant(TackyConst::Int(cff_b)),
dst: TackyVal::Var(tmp_sub.clone()),
});
result.push(TackyInstruction::Binary {
op: TackyBinaryOp::Divide,
left: TackyVal::Var(tmp_sub),
right: TackyVal::Constant(TackyConst::Int(cff_a)),
dst: TackyVal::Var(decoded_index.clone()),
});
let jt_base = ctx.fresh_tmp();
var_types.insert(jt_base.clone(), Type::Pointer(Box::new(Type::Long)));
result.push(TackyInstruction::GetAddress {
src: TackyVal::Var(jt_name),
dst: TackyVal::Var(jt_base.clone()),
});
let jt_ptr = ctx.fresh_tmp();
var_types.insert(jt_ptr.clone(), Type::Pointer(Box::new(Type::Long)));
result.push(TackyInstruction::AddPtr {
ptr: TackyVal::Var(jt_base),
index: TackyVal::Var(decoded_index),
scale: 8,
dst: TackyVal::Var(jt_ptr.clone()),
});
let jt_addr = ctx.fresh_tmp();
var_types.insert(jt_addr.clone(), Type::Long);
result.push(TackyInstruction::Load {
src_ptr: TackyVal::Var(jt_ptr),
dst: TackyVal::Var(jt_addr.clone()),
});
result.push(TackyInstruction::JumpIndirect {
target: TackyVal::Var(jt_addr),
possible_targets: block_labels.clone(),
});
for (i, block) in blocks.iter().enumerate() {
result.push(TackyInstruction::Label(block_labels[i].clone()));
for instr in block {
match instr {
TackyInstruction::Label(_) => {
result.push(instr.clone());
}
TackyInstruction::Return(_) | TackyInstruction::ReturnVoid => {
result.push(instr.clone());
}
TackyInstruction::Jump(target) => {
if let Some(&target_block) = label_to_block.get(target) {
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(target_block))),
dst: TackyVal::Var(state_var.clone()),
});
result.push(TackyInstruction::Jump(dispatch_label.clone()));
} else {
result.push(instr.clone());
}
}
TackyInstruction::JumpIfZero { condition, target } => {
if let Some(&target_block) = label_to_block.get(target) {
let fallthrough_block = i + 1;
let tmp_is_zero = ctx.fresh_tmp();
var_types.insert(tmp_is_zero.clone(), Type::Int);
result.push(TackyInstruction::Binary {
op: TackyBinaryOp::Equal,
left: condition.clone(),
right: TackyVal::Constant(TackyConst::Int(0)),
dst: TackyVal::Var(tmp_is_zero.clone()),
});
result.push(TackyInstruction::JumpIfNotZero {
condition: TackyVal::Var(tmp_is_zero),
target: format!("{}_taken", block_labels[i]),
});
if fallthrough_block < blocks.len() {
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(fallthrough_block))),
dst: TackyVal::Var(state_var.clone()),
});
}
result.push(TackyInstruction::Jump(dispatch_label.clone()));
result.push(TackyInstruction::Label(format!(
"{}_taken",
block_labels[i]
)));
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(target_block))),
dst: TackyVal::Var(state_var.clone()),
});
result.push(TackyInstruction::Jump(dispatch_label.clone()));
} else {
result.push(instr.clone());
}
}
TackyInstruction::JumpIfNotZero { condition, target } => {
if let Some(&target_block) = label_to_block.get(target) {
let fallthrough_block = i + 1;
result.push(TackyInstruction::JumpIfNotZero {
condition: condition.clone(),
target: format!("{}_taken", block_labels[i]),
});
if fallthrough_block < blocks.len() {
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(fallthrough_block))),
dst: TackyVal::Var(state_var.clone()),
});
}
result.push(TackyInstruction::Jump(dispatch_label.clone()));
result.push(TackyInstruction::Label(format!(
"{}_taken",
block_labels[i]
)));
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(target_block))),
dst: TackyVal::Var(state_var.clone()),
});
result.push(TackyInstruction::Jump(dispatch_label.clone()));
} else {
result.push(instr.clone());
}
}
_ => {
result.push(instr.clone());
}
}
}
let last = block.last();
let is_terminator = last.is_some_and(|l| {
matches!(
l,
TackyInstruction::Jump(_)
| TackyInstruction::JumpIfZero { .. }
| TackyInstruction::JumpIfNotZero { .. }
| TackyInstruction::Return(_)
| TackyInstruction::ReturnVoid
)
});
if !is_terminator {
let next_block = i + 1;
if next_block < blocks.len() {
result.push(TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(encode(next_block))),
dst: TackyVal::Var(state_var.clone()),
});
result.push(TackyInstruction::Jump(dispatch_label.clone()));
}
}
}
result.push(TackyInstruction::Label(exit_label));
result
}
fn split_into_blocks(instrs: &[TackyInstruction]) -> Vec<Vec<TackyInstruction>> {
if instrs.is_empty() {
return vec![];
}
let mut blocks: Vec<Vec<TackyInstruction>> = Vec::new();
let mut current_block: Vec<TackyInstruction> = Vec::new();
let is_pred_label = |label: &str| label.starts_with(".Lpred_");
for instr in instrs {
match instr {
TackyInstruction::Label(label) if is_pred_label(label) => {
current_block.push(instr.clone());
}
TackyInstruction::Label(_) => {
if !current_block.is_empty() {
blocks.push(std::mem::take(&mut current_block));
}
current_block.push(instr.clone());
}
TackyInstruction::Jump(target) if is_pred_label(target) => {
current_block.push(instr.clone());
}
TackyInstruction::JumpIfZero { target, .. } if is_pred_label(target) => {
current_block.push(instr.clone());
}
TackyInstruction::JumpIfNotZero { target, .. } if is_pred_label(target) => {
current_block.push(instr.clone());
}
TackyInstruction::Jump(_)
| TackyInstruction::JumpIfZero { .. }
| TackyInstruction::JumpIfNotZero { .. }
| TackyInstruction::Return(_)
| TackyInstruction::ReturnVoid => {
current_block.push(instr.clone());
blocks.push(std::mem::take(&mut current_block));
}
_ => {
current_block.push(instr.clone());
}
}
}
if !current_block.is_empty() {
blocks.push(current_block);
}
blocks
}
fn opsec_warn_strings(
program: &TackyProgram,
policy: OpsecPolicy,
) -> std::result::Result<(), usize> {
let tag = match policy {
OpsecPolicy::Warn => "OPSEC WARNING",
OpsecPolicy::Deny => "OPSEC ERROR",
};
let mut violations: Vec<String> = Vec::new();
for sc in &program.static_constants {
if let TackyStaticInit::StringInit(content, _) = &sc.init {
let lower = content.to_lowercase();
if contains_ip_pattern(content) {
violations.push(format!(
"[{tag}] String literal may contain IP address: \"{}\"",
truncate_str(content, 60)
));
}
if content.contains("/home/")
|| content.contains("/tmp/")
|| content.contains("/etc/")
|| content.contains("C:\\")
|| content.contains("\\\\")
{
violations.push(format!(
"[{tag}] String literal may contain file path: \"{}\"",
truncate_str(content, 60)
));
}
if lower.contains("http://") || lower.contains("https://") || lower.contains("ftp://") {
violations.push(format!(
"[{tag}] String literal may contain URL: \"{}\"",
truncate_str(content, 60)
));
}
for keyword in &["debug", "todo", "fixme"] {
if lower.contains(keyword) {
violations.push(format!(
"[{tag}] String literal contains debug keyword \"{}\": \"{}\"",
keyword,
truncate_str(content, 60)
));
break;
}
}
for keyword in &[
"password",
"passwd",
"secret",
"api_key",
"token",
"credential",
] {
if lower.contains(keyword) {
violations.push(format!(
"[{tag}] String literal contains sensitive keyword \"{}\": \"{}\"",
keyword,
truncate_str(content, 60)
));
break;
}
}
}
}
for v in &violations {
eprintln!("{v}");
}
if policy == OpsecPolicy::Deny && !violations.is_empty() {
Err(violations.len())
} else {
Ok(())
}
}
fn contains_ip_pattern(s: &str) -> bool {
let bytes = s.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i].is_ascii_digit() {
let mut dots = 0;
let mut j = i;
let mut valid = true;
for _ in 0..4 {
if j >= len || !bytes[j].is_ascii_digit() {
valid = false;
break;
}
let start = j;
while j < len && bytes[j].is_ascii_digit() {
j += 1;
}
if j - start > 3 {
valid = false;
break;
}
dots += 1;
if dots < 4 {
if j >= len || bytes[j] != b'.' {
valid = false;
break;
}
j += 1; }
}
if valid && dots == 4 {
return true;
}
}
i += 1;
}
false
}
fn truncate_str(s: &str, max_len: usize) -> String {
if s.chars().count() <= max_len {
s.to_string()
} else {
let truncated: String = s.chars().take(max_len).collect();
format!("{truncated}...")
}
}
fn opsec_sanitize(
program: &mut TackyProgram,
ctx: &mut ObfCtx,
strip: bool,
preserve_globals: bool,
) {
let mut rename_map: HashMap<String, String> = HashMap::new();
let defined_funcs: HashSet<String> = program.functions.iter().map(|f| f.name.clone()).collect();
for func in &program.functions {
if func.name == "main" || (preserve_globals && func.global) {
continue;
}
let new_name = format!("_f{}", ctx.opsec_counter);
ctx.opsec_counter += 1;
rename_map.insert(func.name.clone(), new_name);
}
for sv in &program.static_vars {
if (preserve_globals && sv.global) || rename_map.contains_key(&sv.name) {
continue;
}
let new_name = format!("_v{}", ctx.opsec_counter);
ctx.opsec_counter += 1;
rename_map.insert(sv.name.clone(), new_name);
}
for sc in &program.static_constants {
if !rename_map.contains_key(&sc.name) {
let new_name = format!("_c{}", ctx.opsec_counter);
ctx.opsec_counter += 1;
rename_map.insert(sc.name.clone(), new_name);
}
}
let static_var_names: HashSet<String> = program
.static_vars
.iter()
.map(|sv| sv.name.clone())
.collect();
for func in &mut program.functions {
if let Some(new_name) = rename_map.get(&func.name) {
func.name = new_name.clone();
}
opsec_rename_body(&mut func.body, &rename_map, &defined_funcs);
for sv_name in static_var_names.iter() {
if let Some(new_name) = rename_map.get(sv_name)
&& let Some(ty) = func.var_types.remove(sv_name)
{
func.var_types.insert(new_name.clone(), ty);
}
}
}
for sv in &mut program.static_vars {
if let Some(new_name) = rename_map.get(&sv.name) {
sv.name = new_name.clone();
}
opsec_rename_init(&mut sv.init, &rename_map);
}
for sc in &mut program.static_constants {
if let Some(new_name) = rename_map.get(&sc.name) {
sc.name = new_name.clone();
}
}
if strip {
for func in &mut program.functions {
if func.name == "main" || (preserve_globals && func.global) {
continue;
}
func.global = false;
}
for sv in &mut program.static_vars {
if preserve_globals && sv.global {
continue;
}
sv.global = false;
}
}
}
fn opsec_rename_val(val: &mut TackyVal, rename_map: &HashMap<String, String>) {
if let TackyVal::Var(name) = val
&& let Some(new_name) = rename_map.get(name.as_str())
{
*name = new_name.clone();
}
}
fn opsec_rename_init(init: &mut TackyStaticInit, rename_map: &HashMap<String, String>) {
match init {
TackyStaticInit::PointerArrayInit(labels) => {
for label in labels {
if let Some(new_name) = rename_map.get(label.as_str()) {
*label = new_name.clone();
}
}
}
TackyStaticInit::ArrayInit(inits) => {
for sub in inits {
opsec_rename_init(sub, rename_map);
}
}
_ => {}
}
}
fn opsec_rename_body(
body: &mut [TackyInstruction],
rename_map: &HashMap<String, String>,
defined_funcs: &HashSet<String>,
) {
for instr in body.iter_mut() {
match instr {
TackyInstruction::Return(val) => {
opsec_rename_val(val, rename_map);
}
TackyInstruction::ReturnVoid => {}
TackyInstruction::Unary { src, dst, .. } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::Binary {
left, right, dst, ..
} => {
opsec_rename_val(left, rename_map);
opsec_rename_val(right, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::Copy { src, dst } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::Jump(_) => {}
TackyInstruction::JumpIfZero { condition, .. } => {
opsec_rename_val(condition, rename_map);
}
TackyInstruction::JumpIfNotZero { condition, .. } => {
opsec_rename_val(condition, rename_map);
}
TackyInstruction::Label(_) => {}
TackyInstruction::FunCall {
name, args, dst, ..
} => {
if defined_funcs.contains(name.as_str())
&& let Some(new_name) = rename_map.get(name.as_str())
{
*name = new_name.clone();
}
for arg in args {
opsec_rename_val(arg, rename_map);
}
opsec_rename_val(dst, rename_map);
}
TackyInstruction::SignExtend { src, dst }
| TackyInstruction::ZeroExtend { src, dst }
| TackyInstruction::Truncate { src, dst }
| TackyInstruction::IntToDouble { src, dst }
| TackyInstruction::DoubleToInt { src, dst }
| TackyInstruction::UIntToDouble { src, dst }
| TackyInstruction::DoubleToUInt { src, dst }
| TackyInstruction::FloatToDouble { src, dst }
| TackyInstruction::DoubleToFloat { src, dst }
| TackyInstruction::IntToFloat { src, dst }
| TackyInstruction::FloatToInt { src, dst }
| TackyInstruction::UIntToFloat { src, dst }
| TackyInstruction::FloatToUInt { src, dst } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::GetAddress { src, dst } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::Load { src_ptr, dst } => {
opsec_rename_val(src_ptr, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::Store { src, dst_ptr } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst_ptr, rename_map);
}
TackyInstruction::AddPtr {
ptr, index, dst, ..
} => {
opsec_rename_val(ptr, rename_map);
opsec_rename_val(index, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::CopyToOffset { src, dst, .. } => {
opsec_rename_val(src, rename_map);
if let Some(new_name) = rename_map.get(dst.as_str()) {
*dst = new_name.clone();
}
}
TackyInstruction::CopyFromOffset { src, dst, .. } => {
if let Some(new_name) = rename_map.get(src.as_str()) {
*src = new_name.clone();
}
opsec_rename_val(dst, rename_map);
}
TackyInstruction::CopyStruct { src, dst, .. } => {
opsec_rename_val(src, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::JumpIndirect { target, .. } => {
opsec_rename_val(target, rename_map);
}
TackyInstruction::VaStart { ap, .. } => {
opsec_rename_val(ap, rename_map);
}
TackyInstruction::VaArg { ap, dst, .. } => {
opsec_rename_val(ap, rename_map);
opsec_rename_val(dst, rename_map);
}
TackyInstruction::VaEnd => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn make_int_var(name: &str) -> TackyVal {
TackyVal::Var(name.to_string())
}
fn make_var_types(names: &[&str]) -> HashMap<String, Type> {
names.iter().map(|n| (n.to_string(), Type::Int)).collect()
}
#[test]
fn test_constant_encoding_replaces_int_constants() {
let instrs = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(42)),
dst: make_int_var("x"),
},
TackyInstruction::Return(make_int_var("x")),
];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["x"]);
let result = constant_encoding(instrs, &mut ctx, &mut var_types);
let has_direct_42 = result.iter().any(|i| matches!(i,
TackyInstruction::Copy { src: TackyVal::Constant(TackyConst::Int(42)), .. }
if !matches!(i, TackyInstruction::Copy { dst: TackyVal::Var(n), .. } if n.starts_with("obf_tmp."))
));
assert!(!has_direct_42, "constant 42 should be encoded");
let has_multiply = result.iter().any(|i| {
matches!(
i,
TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
..
}
)
});
assert!(has_multiply, "should contain a multiply operation");
}
#[test]
fn test_constant_encoding_zero_uses_subtract() {
let instrs = vec![TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(0)),
dst: make_int_var("x"),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["x"]);
let result = constant_encoding(instrs, &mut ctx, &mut var_types);
let has_subtract = result.iter().any(|i| {
matches!(
i,
TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
..
}
)
});
assert!(has_subtract, "zero should use a-a subtract pattern");
}
#[test]
fn test_constant_encoding_skips_double() {
let instrs = vec![TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Double(3.15)),
dst: make_int_var("x"),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["x"]);
let result = constant_encoding(instrs, &mut ctx, &mut var_types);
assert_eq!(result.len(), 1);
assert!(matches!(
&result[0],
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Double(_)),
..
}
));
}
#[test]
fn test_junk_code_increases_instruction_count() {
let instrs = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(1)),
dst: make_int_var("a"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(2)),
dst: make_int_var("b"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(3)),
dst: make_int_var("c"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(4)),
dst: make_int_var("d"),
},
TackyInstruction::Return(make_int_var("d")),
];
let original_len = instrs.len();
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c", "d"]);
let result = junk_code_insertion(instrs, &mut ctx, &mut var_types, 4);
assert!(
result.len() > original_len,
"junk code should increase instruction count"
);
}
#[test]
fn test_opaque_predicates_inserts_branches() {
let instrs = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(1)),
dst: make_int_var("a"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(2)),
dst: make_int_var("b"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(3)),
dst: make_int_var("c"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(4)),
dst: make_int_var("d"),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(5)),
dst: make_int_var("e"),
},
TackyInstruction::Return(make_int_var("e")),
];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c", "d", "e"]);
let result = opaque_predicates(instrs, &mut ctx, &mut var_types, 5);
let has_jump_if_zero = result
.iter()
.any(|i| matches!(i, TackyInstruction::JumpIfZero { .. }));
assert!(
has_jump_if_zero,
"opaque predicates should insert conditional branches"
);
}
#[test]
fn test_control_flow_flattening_adds_dispatch() {
let instrs = vec![
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(1)),
dst: make_int_var("x"),
},
TackyInstruction::JumpIfZero {
condition: make_int_var("x"),
target: ".L_else".to_string(),
},
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(10)),
dst: make_int_var("r"),
},
TackyInstruction::Jump(".L_end".to_string()),
TackyInstruction::Label(".L_else".to_string()),
TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(20)),
dst: make_int_var("r"),
},
TackyInstruction::Label(".L_end".to_string()),
TackyInstruction::Return(make_int_var("r")),
];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["x", "r"]);
let mut static_vars = Vec::new();
let result = control_flow_flattening(
instrs,
&mut ctx,
&mut var_types,
&mut static_vars,
37,
0xCAFE,
);
let has_dispatch_label = result.iter().any(|i| {
if let TackyInstruction::Label(l) = i {
l.starts_with(".Lobf_")
} else {
false
}
});
assert!(has_dispatch_label, "CFF should add dispatch labels");
let has_state_var = result.iter().any(|i| {
if let TackyInstruction::Copy {
dst: TackyVal::Var(n),
..
} = i
{
n.starts_with("obf_tmp.")
} else {
false
}
});
assert!(has_state_var, "CFF should use obf_tmp state variable");
assert!(
!static_vars.is_empty(),
"CFF should create jump table static var"
);
let jt_var = &static_vars[0];
assert!(
matches!(&jt_var.init, TackyStaticInit::PointerArrayInit(_)),
"jump table should use PointerArrayInit"
);
let has_jump_indirect = result
.iter()
.any(|i| matches!(i, TackyInstruction::JumpIndirect { .. }));
assert!(
has_jump_indirect,
"CFF should use JumpIndirect for dispatch"
);
let has_cafe = result.iter().any(|i| {
if let TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(v)),
..
} = i
{
*v == 0xCAFE_u16 as i32
} else {
false
}
});
assert!(has_cafe, "CFF should use encoded state values (0xCAFE)");
}
#[test]
fn test_opaque_predicate_diversification() {
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&[]);
for i in 0..4 {
ctx.label_counter = i;
let instr = TackyInstruction::Copy {
src: TackyVal::Constant(TackyConst::Int(42)),
dst: make_int_var("test_dst"),
};
let result = wrap_with_opaque_predicate(instr, &mut ctx, &mut var_types);
let has_jump = result
.iter()
.any(|i| matches!(i, TackyInstruction::JumpIfZero { .. }));
assert!(has_jump, "pattern {i} should generate JumpIfZero");
}
}
#[test]
fn test_arith_subst_expands_add() {
let instrs = vec![TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: make_int_var("a"),
right: make_int_var("b"),
dst: make_int_var("c"),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c"]);
let result = arithmetic_substitution(instrs, &mut ctx, &mut var_types, 1);
assert!(
result.len() >= 3,
"Add should be expanded to 3+ instructions, got {}",
result.len()
);
let has_original = result.len() == 1;
assert!(!has_original, "original Add should be replaced");
}
#[test]
fn test_arith_subst_expands_subtract() {
let instrs = vec![TackyInstruction::Binary {
op: TackyBinaryOp::Subtract,
left: make_int_var("a"),
right: make_int_var("b"),
dst: make_int_var("c"),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c"]);
let result = arithmetic_substitution(instrs, &mut ctx, &mut var_types, 1);
assert!(
result.len() >= 3,
"Subtract should be expanded to 3+ instructions, got {}",
result.len()
);
}
#[test]
fn test_arith_subst_skips_multiply() {
let instrs = vec![TackyInstruction::Binary {
op: TackyBinaryOp::Multiply,
left: make_int_var("a"),
right: make_int_var("b"),
dst: make_int_var("c"),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c"]);
let result = arithmetic_substitution(instrs, &mut ctx, &mut var_types, 1);
assert_eq!(result.len(), 1, "Multiply should not be expanded");
}
#[test]
fn test_arith_subst_skips_obf_tmp() {
let instrs = vec![TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: make_int_var("a"),
right: make_int_var("b"),
dst: TackyVal::Var("obf_tmp.0".to_string()),
}];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b"]);
var_types.insert("obf_tmp.0".to_string(), Type::Int);
let result = arithmetic_substitution(instrs, &mut ctx, &mut var_types, 1);
assert_eq!(result.len(), 1, "Add to obf_tmp should not be expanded");
}
#[test]
fn test_arith_subst_respects_frequency() {
let instrs = vec![
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: make_int_var("a"),
right: make_int_var("b"),
dst: make_int_var("c"),
},
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
left: make_int_var("c"),
right: make_int_var("a"),
dst: make_int_var("d"),
},
];
let mut ctx = ObfCtx::new();
let mut var_types = make_var_types(&["a", "b", "c", "d"]);
let result = arithmetic_substitution(instrs, &mut ctx, &mut var_types, 2);
assert!(
result.len() >= 4,
"only every 2nd Add should be expanded, got {} instructions",
result.len()
);
assert!(
matches!(
&result[0],
TackyInstruction::Binary {
op: TackyBinaryOp::Add,
..
}
),
"first Add should remain unchanged"
);
}
#[test]
fn test_state_encoding_consistency() {
let cff_a: i32 = 37;
let cff_b: i32 = 0xCAFE;
for i in 0i32..20 {
let encoded = i.wrapping_mul(cff_a).wrapping_add(cff_b);
let decoded = (encoded.wrapping_sub(cff_b)) / cff_a;
assert_eq!(
decoded, i,
"encode/decode should be consistent for index {i}"
);
}
}
}