#![allow(dead_code)]
use crate::opcodes::{stack, arithmetic};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum SubstitutionLevel {
None,
#[default]
Light,
Heavy,
}
struct SubstRng {
state: u64,
}
impl SubstRng {
fn new(seed: u64) -> Self {
Self { state: seed ^ 0xDEADBEEF }
}
fn next(&mut self) -> u64 {
self.state = self.state.wrapping_mul(0x5851F42D4C957F2D).wrapping_add(0x14057B7EF767814F);
self.state
}
fn choice(&mut self, max: usize) -> usize {
if max == 0 { return 0; }
(self.next() % max as u64) as usize
}
fn should_substitute(&mut self, probability: u8) -> bool {
(self.next() % 100) < probability as u64
}
}
pub fn generate_subst_seed(fn_name: &str, build_seed: &[u8]) -> u64 {
let mut hasher = DefaultHasher::new();
fn_name.hash(&mut hasher);
build_seed.hash(&mut hasher);
"substitution-v1".hash(&mut hasher);
hasher.finish()
}
pub struct InstructionSubstituter {
rng: SubstRng,
level: SubstitutionLevel,
output: Vec<u8>,
}
impl InstructionSubstituter {
pub fn new(seed: u64, level: SubstitutionLevel) -> Self {
Self {
rng: SubstRng::new(seed),
level,
output: Vec::new(),
}
}
pub fn substitute(&mut self, bytecode: &[u8]) -> Vec<u8> {
if self.level == SubstitutionLevel::None {
return bytecode.to_vec();
}
self.output.clear();
let mut i = 0;
while i < bytecode.len() {
let opcode = bytecode[i];
let substituted = self.try_substitute(opcode, &bytecode[i..]);
if let Some((replacement, consumed)) = substituted {
self.output.extend_from_slice(&replacement);
i += consumed;
} else {
self.output.push(opcode);
i += 1;
}
}
self.output.clone()
}
fn try_substitute(&mut self, opcode: u8, _remaining: &[u8]) -> Option<(Vec<u8>, usize)> {
let prob = match self.level {
SubstitutionLevel::None => return None,
SubstitutionLevel::Light => 30, SubstitutionLevel::Heavy => 70, };
if !self.rng.should_substitute(prob) {
return None;
}
match opcode {
arithmetic::ADD => Some((self.substitute_add(), 1)),
arithmetic::SUB => Some((self.substitute_sub(), 1)),
arithmetic::XOR => Some((self.substitute_xor(), 1)),
arithmetic::INC => Some((self.substitute_inc(), 1)),
arithmetic::DEC => Some((self.substitute_dec(), 1)),
arithmetic::NOT => Some((self.substitute_not(), 1)),
arithmetic::AND => Some((self.substitute_and(), 1)),
arithmetic::OR => Some((self.substitute_or(), 1)),
_ => None,
}
}
fn substitute_add(&mut self) -> Vec<u8> {
match self.rng.choice(2) {
0 => {
vec![
stack::PUSH_IMM8, 0, arithmetic::ADD, arithmetic::ADD,
]
}
_ => {
vec![arithmetic::ADD]
}
}
}
fn substitute_sub(&mut self) -> Vec<u8> {
match self.rng.choice(2) {
0 => {
vec![arithmetic::SUB]
}
_ => {
vec![arithmetic::SUB]
}
}
}
fn substitute_xor(&mut self) -> Vec<u8> {
vec![arithmetic::XOR]
}
fn substitute_inc(&mut self) -> Vec<u8> {
match self.rng.choice(3) {
0 => {
vec![stack::PUSH_IMM8, 1, arithmetic::ADD]
}
1 => {
vec![stack::PUSH_IMM8, 0xFF, arithmetic::SUB]
}
_ => {
vec![arithmetic::INC]
}
}
}
fn substitute_dec(&mut self) -> Vec<u8> {
match self.rng.choice(3) {
0 => {
vec![stack::PUSH_IMM8, 1, arithmetic::SUB]
}
1 => {
vec![stack::PUSH_IMM8, 0xFF, arithmetic::ADD]
}
_ => {
vec![arithmetic::DEC]
}
}
}
fn substitute_not(&mut self) -> Vec<u8> {
match self.rng.choice(2) {
0 => {
let max_val = u64::MAX;
let mut result = vec![stack::PUSH_IMM];
result.extend_from_slice(&max_val.to_le_bytes());
result.push(arithmetic::XOR);
result
}
_ => {
vec![arithmetic::NOT]
}
}
}
fn substitute_and(&mut self) -> Vec<u8> {
vec![arithmetic::AND]
}
fn substitute_or(&mut self) -> Vec<u8> {
vec![arithmetic::OR]
}
}
pub fn apply_substitution(
bytecode: &[u8],
fn_name: &str,
level: SubstitutionLevel,
) -> Vec<u8> {
let seed = generate_subst_seed(fn_name, b"instruction-subst");
let mut substituter = InstructionSubstituter::new(seed, level);
substituter.substitute(bytecode)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcodes::exec;
#[test]
fn test_no_substitution() {
let bytecode = vec![
stack::PUSH_IMM8, 10,
stack::PUSH_IMM8, 20,
arithmetic::ADD,
exec::HALT,
];
let result = apply_substitution(&bytecode, "test", SubstitutionLevel::None);
assert_eq!(result, bytecode);
}
#[test]
fn test_deterministic_substitution() {
let bytecode = vec![
stack::PUSH_IMM8, 5,
arithmetic::INC,
exec::HALT,
];
let r1 = apply_substitution(&bytecode, "same_func", SubstitutionLevel::Heavy);
let r2 = apply_substitution(&bytecode, "same_func", SubstitutionLevel::Heavy);
assert_eq!(r1, r2, "Substitution should be deterministic");
}
#[test]
fn test_different_functions_different_substitutions() {
let bytecode = vec![
stack::PUSH_IMM8, 42,
arithmetic::INC,
arithmetic::INC,
arithmetic::INC,
arithmetic::DEC,
exec::HALT,
];
let r1 = apply_substitution(&bytecode, "func_a", SubstitutionLevel::Heavy);
let r2 = apply_substitution(&bytecode, "func_b", SubstitutionLevel::Heavy);
assert!(!r1.is_empty());
assert!(!r2.is_empty());
}
#[test]
fn test_inc_substitution_produces_valid_bytecode() {
let bytecode = vec![
stack::PUSH_IMM8, 100,
arithmetic::INC,
exec::HALT,
];
let result = apply_substitution(&bytecode, "inc_test", SubstitutionLevel::Heavy);
assert_eq!(result[0], stack::PUSH_IMM8);
assert_eq!(result[1], 100);
assert_eq!(*result.last().unwrap(), exec::HALT);
}
#[test]
fn test_dec_substitution_produces_valid_bytecode() {
let bytecode = vec![
stack::PUSH_IMM8, 50,
arithmetic::DEC,
exec::HALT,
];
let result = apply_substitution(&bytecode, "dec_test", SubstitutionLevel::Heavy);
assert_eq!(result[0], stack::PUSH_IMM8);
assert_eq!(*result.last().unwrap(), exec::HALT);
}
}