mod codegen;
mod driver;
mod emit;
mod error;
mod lex;
mod obfuscation;
mod parse;
mod tacky;
mod typecheck;
use std::path::PathBuf;
use std::process;
use clap::Parser;
use driver::Stage;
use obfuscation::{ObfuscationConfig, OpsecPolicy};
#[derive(Parser)]
#[command(
name = "ferrugocc",
about = "An experimental C compiler and obfuscating compiler written in Rust",
long_about = "FerrugoCC compiles a practical subset of C to x86_64 assembly (SysV ABI).\n\
Requires gcc for preprocessing (gcc -E) and assembling/linking.\n\
With --fobfuscate, applies 16 obfuscation passes instead of optimization."
)]
struct Cli {
#[arg(long)]
lex: bool,
#[arg(long)]
parse: bool,
#[arg(long)]
validate: bool,
#[arg(long)]
tacky: bool,
#[arg(long)]
codegen: bool,
#[arg(short = 'S')]
emit_asm: bool,
#[arg(long = "fobfuscate")]
obfuscate: bool,
#[arg(long = "obf-level", default_value_t = 3, value_parser = clap::value_parser!(u8).range(1..=4))]
obf_level: u8,
#[arg(long = "obf-no-cff")]
obf_no_cff: bool,
#[arg(long = "obf-no-strings")]
obf_no_strings: bool,
#[arg(long = "obf-no-anti-disasm")]
obf_no_anti_disasm: bool,
#[arg(long = "obf-no-indirect-calls")]
obf_no_indirect_calls: bool,
#[arg(long = "obf-junk-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_junk_freq: Option<u64>,
#[arg(long = "obf-pred-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_pred_freq: Option<u64>,
#[arg(long = "obf-no-arith-subst")]
obf_no_arith_subst: bool,
#[arg(long = "obf-no-reg-shuffle")]
obf_no_reg_shuffle: bool,
#[arg(long = "obf-no-stack-frame")]
obf_no_stack_frame: bool,
#[arg(long = "obf-no-instr-subst")]
obf_no_instr_subst: bool,
#[arg(long = "obf-no-func-inline")]
obf_no_func_inline: bool,
#[arg(long = "obf-no-func-outline")]
obf_no_func_outline: bool,
#[arg(long = "obf-no-vm-virtualize")]
obf_no_vm_virtualize: bool,
#[arg(long = "obf-no-lib-obfuscate")]
obf_no_lib_obfuscate: bool,
#[arg(long = "obf-no-opsec")]
obf_no_opsec: bool,
#[arg(long = "obf-no-opsec-warn")]
obf_no_opsec_warn: bool,
#[arg(long = "obf-no-strip")]
obf_no_strip: bool,
#[arg(long = "opsec-policy", value_enum, default_value = "warn")]
opsec_policy: OpsecPolicy,
#[arg(long = "opsec-audit")]
opsec_audit: bool,
#[arg(long = "obf-arith-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_arith_freq: Option<u64>,
#[arg(long = "obf-reg-shuffle-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_reg_shuffle_freq: Option<u64>,
#[arg(long = "obf-stack-padding")]
obf_stack_padding: Option<usize>,
#[arg(long = "obf-stack-fake-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_stack_fake_freq: Option<u64>,
#[arg(long = "obf-instr-subst-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_instr_subst_freq: Option<u64>,
#[arg(long = "obf-inline-freq", value_parser = clap::value_parser!(u64).range(1..))]
obf_inline_freq: Option<u64>,
#[arg(long = "obf-outline-min-block", value_parser = clap::value_parser!(u64).range(1..))]
obf_outline_min_block: Option<u64>,
#[arg(short = 'D', value_name = "MACRO")]
defines: Vec<String>,
#[arg(short = 'U', value_name = "MACRO")]
undefs: Vec<String>,
#[arg(short = 'c')]
compile_only: bool,
#[arg(short = 'o', value_name = "FILE")]
output: Option<PathBuf>,
#[arg(required = true)]
sources: Vec<PathBuf>,
}
fn main() {
let cli = Cli::parse();
let stage = if cli.lex {
Stage::Lex
} else if cli.parse {
Stage::Parse
} else if cli.validate {
Stage::Validate
} else if cli.tacky {
Stage::Tacky
} else if cli.codegen {
Stage::Codegen
} else if cli.emit_asm {
Stage::EmitAsm
} else {
Stage::Full
};
let obf_config = if cli.obfuscate {
let mut config = ObfuscationConfig::from_level(cli.obf_level);
if cli.obf_no_cff {
config.cff = false;
}
if cli.obf_no_strings {
config.string_encryption = false;
}
if cli.obf_no_anti_disasm {
config.anti_disassembly = false;
}
if cli.obf_no_indirect_calls {
config.indirect_calls = false;
}
if cli.obf_no_arith_subst {
config.arith_subst = false;
}
if cli.obf_no_reg_shuffle {
config.reg_shuffle = false;
}
if cli.obf_no_stack_frame {
config.stack_frame_obf = false;
}
if cli.obf_no_instr_subst {
config.instr_subst = false;
}
if cli.obf_no_func_inline {
config.func_inline = false;
}
if cli.obf_no_func_outline {
config.func_outline = false;
}
if cli.obf_no_vm_virtualize {
config.vm_virtualize = false;
}
if cli.obf_no_lib_obfuscate {
config.lib_obfuscate = false;
}
config.opsec_policy = cli.opsec_policy;
config.opsec_audit = cli.opsec_audit;
if cli.obf_no_opsec {
config.opsec = false;
config.opsec_warn = false;
config.opsec_strip = false;
config.opsec_audit = false;
config.opsec_policy = OpsecPolicy::Warn;
}
if cli.obf_no_opsec_warn {
config.opsec_warn = false;
}
if cli.obf_no_strip {
config.opsec_strip = false;
}
if let Some(freq) = cli.obf_junk_freq {
config.junk_freq = freq as usize;
}
if let Some(freq) = cli.obf_pred_freq {
config.pred_freq = freq as usize;
}
if let Some(freq) = cli.obf_arith_freq {
config.arith_freq = freq as usize;
}
if let Some(freq) = cli.obf_reg_shuffle_freq {
config.reg_shuffle_freq = freq as usize;
}
if let Some(n) = cli.obf_stack_padding {
config.stack_frame_padding = n;
}
if let Some(freq) = cli.obf_stack_fake_freq {
config.stack_frame_fake_freq = freq as usize;
}
if let Some(freq) = cli.obf_instr_subst_freq {
config.instr_subst_freq = freq as usize;
}
if let Some(freq) = cli.obf_inline_freq {
config.func_inline_freq = freq as usize;
}
if let Some(n) = cli.obf_outline_min_block {
config.func_outline_min_block = n as usize;
}
Some(config)
} else {
None
};
if let Err(e) = driver::run_multi(
&cli.sources,
stage,
obf_config,
driver::PreprocessMode::External,
&cli.defines,
&cli.undefs,
cli.compile_only,
cli.output.as_deref(),
) {
eprintln!("ferrugocc: {e}");
process::exit(1);
}
}