start, reverse guard, cli-frontend for server and client
This commit is contained in:
14
ostp-guard/Cargo.toml
Normal file
14
ostp-guard/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "ostp-guard"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "OSTP Anti-Reverse Engineering & Protection Module"
|
||||
|
||||
[dependencies]
|
||||
rand.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3", features = ["debugapi", "processthreadsapi", "winnt", "sysinfoapi", "libloaderapi"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
228
ostp-guard/src/anti_debug.rs
Normal file
228
ostp-guard/src/anti_debug.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
//! Anti-Debugging Detection Module
|
||||
//!
|
||||
//! Detects if the process is being traced/debugged
|
||||
//! and takes evasive action without obvious crashes.
|
||||
|
||||
/// Check if any debugger is attached
|
||||
#[inline(never)]
|
||||
pub fn is_debugger_present() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
windows_debugger_check()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
unix_debugger_check()
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, unix)))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple Windows anti-debug techniques
|
||||
#[cfg(windows)]
|
||||
fn windows_debugger_check() -> bool {
|
||||
unsafe {
|
||||
// Method 1: IsDebuggerPresent API
|
||||
if winapi::um::debugapi::IsDebuggerPresent() != 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 2: CheckRemoteDebuggerPresent
|
||||
let mut is_debugged: i32 = 0;
|
||||
let process = winapi::um::processthreadsapi::GetCurrentProcess();
|
||||
if winapi::um::debugapi::CheckRemoteDebuggerPresent(process, &mut is_debugged) != 0 {
|
||||
if is_debugged != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: NtGlobalFlag check (PEB)
|
||||
// The NtGlobalFlag in PEB is set to 0x70 when debugged
|
||||
if check_peb_being_debugged() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 4: Timing check - debugger breakpoints cause delays
|
||||
if timing_check() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe fn check_peb_being_debugged() -> bool {
|
||||
// Access PEB through TEB
|
||||
// This is a low-level check that many debuggers don't hide
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
let peb: *const u8;
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"mov {}, gs:[0x60]",
|
||||
out(reg) peb,
|
||||
options(nostack, nomem)
|
||||
);
|
||||
}
|
||||
|
||||
if !peb.is_null() {
|
||||
// BeingDebugged flag at offset 0x2
|
||||
let being_debugged = unsafe { *peb.add(0x2) };
|
||||
if being_debugged != 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NtGlobalFlag at offset 0xBC (x64)
|
||||
let nt_global_flag = unsafe { *(peb.add(0xBC) as *const u32) };
|
||||
// FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS
|
||||
if nt_global_flag & 0x70 != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn timing_check() -> bool {
|
||||
use std::time::Instant;
|
||||
|
||||
// Simple operation that should be instant
|
||||
let start = Instant::now();
|
||||
|
||||
// Do some trivial work
|
||||
let mut x: u64 = 0;
|
||||
for i in 0..1000 {
|
||||
x = x.wrapping_add(i);
|
||||
}
|
||||
|
||||
// Prevent optimization
|
||||
std::hint::black_box(x);
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
// If this takes more than 100ms, likely being single-stepped
|
||||
elapsed.as_millis() > 100
|
||||
}
|
||||
|
||||
/// Linux/Unix anti-debug techniques
|
||||
#[cfg(unix)]
|
||||
fn unix_debugger_check() -> bool {
|
||||
// Method 1: ptrace self-attach trick
|
||||
if ptrace_check() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 2: Check /proc/self/status for TracerPid
|
||||
if proc_status_check() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 3: Check parent process name
|
||||
if parent_process_check() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 4: Timing check
|
||||
if timing_check_unix() {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ptrace_check() -> bool {
|
||||
// PTRACE_TRACEME = 0
|
||||
// If we're already being traced, this will fail
|
||||
unsafe {
|
||||
let result = libc::ptrace(libc::PTRACE_TRACEME, 0, 0, 0);
|
||||
if result == -1 {
|
||||
// Already being traced
|
||||
return true;
|
||||
}
|
||||
// Detach from ourselves
|
||||
libc::ptrace(libc::PTRACE_DETACH, 0, 0, 0);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn proc_status_check() -> bool {
|
||||
// Read /proc/self/status and check TracerPid
|
||||
if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
|
||||
for line in status.lines() {
|
||||
if line.starts_with("TracerPid:") {
|
||||
if let Some(pid_str) = line.split_whitespace().nth(1) {
|
||||
if let Ok(pid) = pid_str.parse::<i32>() {
|
||||
if pid != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn parent_process_check() -> bool {
|
||||
// Check if parent is a known debugger
|
||||
let debuggers = ["gdb", "lldb", "strace", "ltrace", "radare2", "r2", "ida", "x64dbg", "ollydbg"];
|
||||
|
||||
if let Ok(ppid_str) = std::fs::read_to_string("/proc/self/stat") {
|
||||
let parts: Vec<&str> = ppid_str.split_whitespace().collect();
|
||||
if parts.len() > 3 {
|
||||
if let Ok(ppid) = parts[3].parse::<i32>() {
|
||||
let parent_exe = format!("/proc/{}/exe", ppid);
|
||||
if let Ok(path) = std::fs::read_link(&parent_exe) {
|
||||
let name = path.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
for debugger in &debuggers {
|
||||
if name.contains(debugger) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn timing_check_unix() -> bool {
|
||||
use std::time::Instant;
|
||||
|
||||
let start = Instant::now();
|
||||
let mut x: u64 = 0;
|
||||
for i in 0..1000 {
|
||||
x = x.wrapping_add(i);
|
||||
}
|
||||
std::hint::black_box(x);
|
||||
|
||||
start.elapsed().as_millis() > 100
|
||||
}
|
||||
|
||||
/// Continuous background monitor (call from separate thread)
|
||||
pub fn start_background_monitor() -> std::thread::JoinHandle<()> {
|
||||
std::thread::spawn(|| {
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
|
||||
if is_debugger_present() {
|
||||
// Enter decoy mode silently
|
||||
crate::decoy_loop();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
315
ostp-guard/src/anti_vm.rs
Normal file
315
ostp-guard/src/anti_vm.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
//! Anti-VM and Sandbox Detection
|
||||
//!
|
||||
//! Detects common virtualization artifacts to prevent
|
||||
//! analysis in controlled environments.
|
||||
|
||||
/// Check if running in a virtual machine or sandbox
|
||||
#[inline(never)]
|
||||
pub fn is_virtual_machine() -> bool {
|
||||
// Check multiple indicators - any single check might be bypassed
|
||||
let checks = [
|
||||
check_vm_mac_addresses,
|
||||
check_vm_hardware_ids,
|
||||
check_vm_processes,
|
||||
check_vm_files,
|
||||
check_vm_registry,
|
||||
check_low_resources,
|
||||
];
|
||||
|
||||
// If more than 2 checks trigger, likely a VM
|
||||
let score: u32 = checks.iter()
|
||||
.map(|check| if check() { 1 } else { 0 })
|
||||
.sum();
|
||||
|
||||
score >= 2
|
||||
}
|
||||
|
||||
/// Check for known VM MAC address prefixes
|
||||
fn check_vm_mac_addresses() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Get network adapter info and check MAC prefixes
|
||||
// VMware: 00:0C:29, 00:50:56
|
||||
// VirtualBox: 08:00:27
|
||||
// Hyper-V: 00:15:5D
|
||||
// Parallels: 00:1C:42
|
||||
|
||||
// Simplified check via ipconfig output patterns
|
||||
if let Ok(output) = std::process::Command::new("ipconfig")
|
||||
.arg("/all")
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
let vm_macs = ["00-0c-29", "00-50-56", "08-00-27", "00-15-5d", "00-1c-42"];
|
||||
for mac in &vm_macs {
|
||||
if stdout.contains(mac) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("ip")
|
||||
.args(["link", "show"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
let vm_macs = ["00:0c:29", "00:50:56", "08:00:27", "00:15:5d", "00:1c:42"];
|
||||
for mac in &vm_macs {
|
||||
if stdout.contains(mac) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for VM-specific hardware IDs
|
||||
fn check_vm_hardware_ids() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Check WMI for VM indicators
|
||||
let vm_indicators = [
|
||||
"vmware", "virtualbox", "vbox", "qemu", "xen",
|
||||
"virtual", "hyperv", "parallels", "kvm"
|
||||
];
|
||||
|
||||
// Check computer name/model via WMI
|
||||
if let Ok(output) = std::process::Command::new("wmic")
|
||||
.args(["computersystem", "get", "model"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for indicator in &vm_indicators {
|
||||
if stdout.contains(indicator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check BIOS
|
||||
if let Ok(output) = std::process::Command::new("wmic")
|
||||
.args(["bios", "get", "serialnumber"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for indicator in &vm_indicators {
|
||||
if stdout.contains(indicator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// Check /sys/class/dmi/id/
|
||||
let dmi_paths = [
|
||||
"/sys/class/dmi/id/product_name",
|
||||
"/sys/class/dmi/id/sys_vendor",
|
||||
"/sys/class/dmi/id/board_vendor",
|
||||
];
|
||||
|
||||
let vm_indicators = [
|
||||
"vmware", "virtualbox", "vbox", "qemu", "xen",
|
||||
"virtual", "hyperv", "parallels", "kvm", "bochs"
|
||||
];
|
||||
|
||||
for path in &dmi_paths {
|
||||
if let Ok(content) = std::fs::read_to_string(path) {
|
||||
let lower = content.to_lowercase();
|
||||
for indicator in &vm_indicators {
|
||||
if lower.contains(indicator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for VM-related processes
|
||||
fn check_vm_processes() -> bool {
|
||||
let vm_processes = [
|
||||
"vmtoolsd", "vmwaretray", "vmwareuser", // VMware
|
||||
"vboxservice", "vboxtray", "vboxclient", // VirtualBox
|
||||
"xenservice", // Xen
|
||||
"qemu-ga", // QEMU
|
||||
"prl_tools", "prl_cc", // Parallels
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("tasklist").output() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for proc in &vm_processes {
|
||||
if stdout.contains(proc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("ps")
|
||||
.args(["aux"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for proc in &vm_processes {
|
||||
if stdout.contains(proc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for VM-specific files
|
||||
fn check_vm_files() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let vm_files = [
|
||||
r"C:\Windows\System32\drivers\vmmouse.sys",
|
||||
r"C:\Windows\System32\drivers\vmhgfs.sys",
|
||||
r"C:\Windows\System32\drivers\VBoxMouse.sys",
|
||||
r"C:\Windows\System32\drivers\VBoxGuest.sys",
|
||||
r"C:\Windows\System32\drivers\VBoxSF.sys",
|
||||
];
|
||||
|
||||
for file in &vm_files {
|
||||
if std::path::Path::new(file).exists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let vm_files = [
|
||||
"/usr/bin/vmtoolsd",
|
||||
"/usr/bin/VBoxService",
|
||||
"/usr/bin/VBoxClient",
|
||||
"/.dockerenv",
|
||||
"/run/.containerenv",
|
||||
];
|
||||
|
||||
for file in &vm_files {
|
||||
if std::path::Path::new(file).exists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check Windows registry for VM indicators
|
||||
fn check_vm_registry() -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Check via reg query
|
||||
let registry_keys = [
|
||||
r"HKLM\SOFTWARE\VMware, Inc.\VMware Tools",
|
||||
r"HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions",
|
||||
];
|
||||
|
||||
for key in ®istry_keys {
|
||||
if let Ok(output) = std::process::Command::new("reg")
|
||||
.args(["query", key])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for suspiciously low resources (sandbox indicator)
|
||||
fn check_low_resources() -> bool {
|
||||
// Sandboxes often have minimal resources
|
||||
|
||||
// Check CPU count
|
||||
let cpus = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
if cpus < 2 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check available disk space (simplified)
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("wmic")
|
||||
.args(["logicaldisk", "get", "size"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
// Very small disk = sandbox
|
||||
if let Some(size_str) = stdout.lines().nth(1) {
|
||||
if let Ok(size) = size_str.trim().parse::<u64>() {
|
||||
// Less than 50GB
|
||||
if size < 50_000_000_000 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for analysis tools
|
||||
pub fn check_analysis_tools() -> bool {
|
||||
let tools = [
|
||||
"wireshark", "fiddler", "burp", "charles", // Network
|
||||
"x64dbg", "x32dbg", "ollydbg", "windbg", // Debuggers
|
||||
"ida", "ida64", "ghidra", "radare2", "r2", // Disassemblers
|
||||
"procmon", "procexp", "processhacker", // Process monitors
|
||||
"pestudio", "die", "exeinfope", // PE analyzers
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("tasklist").output() {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for tool in &tools {
|
||||
if stdout.contains(tool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if let Ok(output) = std::process::Command::new("ps")
|
||||
.args(["aux"])
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_lowercase();
|
||||
for tool in &tools {
|
||||
if stdout.contains(tool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
231
ostp-guard/src/control_flow.rs
Normal file
231
ostp-guard/src/control_flow.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
//! Control Flow Obfuscation Helpers
|
||||
//!
|
||||
//! Makes decompiled code look like "spaghetti" by using:
|
||||
//! - State machines with non-sequential states
|
||||
//! - Opaque predicates
|
||||
//! - Indirect jumps via function pointers
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Opaque predicate that always returns true but is hard to analyze statically
|
||||
#[inline(never)]
|
||||
pub fn opaque_true() -> bool {
|
||||
// This is mathematically always true, but static analysis can't easily prove it
|
||||
let x: u32 = rand::random();
|
||||
let y: u32 = rand::random();
|
||||
|
||||
// (x^2 - y^2) == (x+y)(x-y) is always true
|
||||
let lhs = x.wrapping_mul(x).wrapping_sub(y.wrapping_mul(y));
|
||||
let rhs = x.wrapping_add(y).wrapping_mul(x.wrapping_sub(y));
|
||||
|
||||
lhs == rhs
|
||||
}
|
||||
|
||||
/// Opaque predicate that always returns false
|
||||
#[inline(never)]
|
||||
pub fn opaque_false() -> bool {
|
||||
let x: u32 = rand::random::<u32>() | 1; // Ensure odd
|
||||
// x^2 mod 4 is never 2 or 3 for any integer
|
||||
(x.wrapping_mul(x) % 4) >= 2 && false
|
||||
}
|
||||
|
||||
/// State machine for obfuscated control flow
|
||||
/// States are non-sequential random values
|
||||
pub struct ObfuscatedStateMachine {
|
||||
states: HashMap<u32, u32>, // current_state -> next_state
|
||||
actions: HashMap<u32, fn() -> bool>, // state -> action
|
||||
current: u32,
|
||||
final_state: u32,
|
||||
}
|
||||
|
||||
impl ObfuscatedStateMachine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
states: HashMap::new(),
|
||||
actions: HashMap::new(),
|
||||
current: 0,
|
||||
final_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a transition with obfuscated state values
|
||||
pub fn add_transition(&mut self, from: u32, to: u32, action: fn() -> bool) {
|
||||
// XOR state values to make them non-obvious
|
||||
let from_obf = from ^ 0xDEADBEEF;
|
||||
let to_obf = to ^ 0xCAFEBABE;
|
||||
|
||||
self.states.insert(from_obf, to_obf);
|
||||
self.actions.insert(from_obf, action);
|
||||
}
|
||||
|
||||
/// Set initial state
|
||||
pub fn set_start(&mut self, state: u32) {
|
||||
self.current = state ^ 0xDEADBEEF;
|
||||
}
|
||||
|
||||
/// Set final (success) state
|
||||
pub fn set_final(&mut self, state: u32) {
|
||||
self.final_state = state ^ 0xCAFEBABE;
|
||||
}
|
||||
|
||||
/// Execute state machine
|
||||
#[inline(never)]
|
||||
pub fn execute(&mut self) -> bool {
|
||||
let mut iterations = 0;
|
||||
const MAX_ITERATIONS: u32 = 100;
|
||||
|
||||
loop {
|
||||
iterations += 1;
|
||||
if iterations > MAX_ITERATIONS {
|
||||
return false; // Prevent infinite loops
|
||||
}
|
||||
|
||||
// Check if we reached final state
|
||||
if self.current == self.final_state {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get and execute action
|
||||
if let Some(action) = self.actions.get(&self.current) {
|
||||
if !action() {
|
||||
return false; // Action failed
|
||||
}
|
||||
}
|
||||
|
||||
// Transition to next state
|
||||
if let Some(&next) = self.states.get(&self.current) {
|
||||
self.current = next;
|
||||
} else {
|
||||
return false; // No transition defined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ObfuscatedStateMachine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro for obfuscated if-else chains
|
||||
/// Transforms obvious branching into state machine
|
||||
#[macro_export]
|
||||
macro_rules! obf_branch {
|
||||
($cond:expr => $then:expr ; $else:expr) => {{
|
||||
let selector: u32 = if $cond { 0xAAAAAAAA } else { 0x55555555 };
|
||||
let result_true = || { $then };
|
||||
let result_false = || { $else };
|
||||
|
||||
// Indirect call via computed index
|
||||
let funcs: [fn() -> _; 2] = [result_false, result_true];
|
||||
let idx = ((selector >> 31) & 1) as usize;
|
||||
|
||||
funcs[idx]()
|
||||
}};
|
||||
}
|
||||
|
||||
/// Indirect function call wrapper
|
||||
/// Makes static analysis harder by hiding call targets
|
||||
pub struct IndirectCaller<T, R> {
|
||||
funcs: Vec<fn(T) -> R>,
|
||||
}
|
||||
|
||||
impl<T: Clone, R> IndirectCaller<T, R> {
|
||||
pub fn new() -> Self {
|
||||
Self { funcs: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn register(&mut self, f: fn(T) -> R) -> usize {
|
||||
self.funcs.push(f);
|
||||
self.funcs.len() - 1
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn call(&self, index: usize, arg: T) -> Option<R> {
|
||||
// Add noise to confuse analysis
|
||||
let real_index = index ^ 0 ^ 0; // Looks suspicious but does nothing
|
||||
self.funcs.get(real_index).map(|f| f(arg.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone, R> Default for IndirectCaller<T, R> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Flatten control flow by converting to dispatch loop
|
||||
/// Usage: wrap your function body in this
|
||||
#[inline(never)]
|
||||
pub fn dispatch_loop<F, R>(blocks: &[F]) -> Option<R>
|
||||
where
|
||||
F: Fn() -> Option<(usize, Option<R>)>,
|
||||
{
|
||||
let mut current_block = 0;
|
||||
let mut iterations = 0;
|
||||
|
||||
loop {
|
||||
iterations += 1;
|
||||
if iterations > 1000 || current_block >= blocks.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match blocks[current_block]() {
|
||||
Some((_next, Some(result))) => return Some(result),
|
||||
Some((next, None)) => current_block = next,
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add random delays to confuse timing analysis
|
||||
#[inline(never)]
|
||||
pub fn timing_noise() {
|
||||
let delay = rand::random::<u64>() % 10;
|
||||
for _ in 0..delay {
|
||||
std::hint::black_box(rand::random::<u64>());
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert dead code that looks meaningful
|
||||
#[inline(never)]
|
||||
pub fn dead_code_insertion() -> u64 {
|
||||
// This code does nothing useful but looks like real work
|
||||
let mut accumulator = 0u64;
|
||||
let iterations = 10 + (rand::random::<u64>() % 10);
|
||||
|
||||
for i in 0..iterations {
|
||||
accumulator = accumulator.wrapping_add(i);
|
||||
accumulator = accumulator.wrapping_mul(0x5851F42D4C957F2D);
|
||||
accumulator ^= accumulator >> 33;
|
||||
}
|
||||
|
||||
// Return value is never used but prevents optimization
|
||||
std::hint::black_box(accumulator)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_opaque_true() {
|
||||
// Should always be true
|
||||
for _ in 0..100 {
|
||||
assert!(opaque_true());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_machine() {
|
||||
let mut sm = ObfuscatedStateMachine::new();
|
||||
|
||||
sm.add_transition(0, 1, || true);
|
||||
sm.add_transition(1, 2, || true);
|
||||
sm.set_start(0);
|
||||
sm.set_final(2);
|
||||
|
||||
assert!(sm.execute());
|
||||
}
|
||||
}
|
||||
61
ostp-guard/src/lib.rs
Normal file
61
ostp-guard/src/lib.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
//! OSTP Guard - Anti-Reverse Engineering Protection Module
|
||||
//!
|
||||
//! This module provides:
|
||||
//! - Compile-time string obfuscation (XOR encryption)
|
||||
//! - Runtime anti-debugging detection
|
||||
//! - Anti-VM/sandbox detection
|
||||
//! - Control flow obfuscation helpers
|
||||
|
||||
pub mod obfuscate;
|
||||
pub mod anti_debug;
|
||||
pub mod anti_vm;
|
||||
pub mod control_flow;
|
||||
|
||||
pub use obfuscate::*;
|
||||
pub use anti_debug::*;
|
||||
pub use anti_vm::*;
|
||||
pub use control_flow::*;
|
||||
|
||||
/// Error codes - obscure hex values instead of readable strings
|
||||
pub mod error_codes {
|
||||
pub const E_NET_TIMEOUT: u32 = 0xDEADC0DE;
|
||||
pub const E_AUTH_FAIL: u32 = 0xCAFEBABE;
|
||||
pub const E_HANDSHAKE: u32 = 0xBAADF00D;
|
||||
pub const E_CRYPTO: u32 = 0xFEEDFACE;
|
||||
pub const E_INTERNAL: u32 = 0xC0FFEE00;
|
||||
pub const E_BANNED: u32 = 0x0BADC0DE;
|
||||
}
|
||||
|
||||
/// Initialize all protection measures
|
||||
/// Call this at the start of main()
|
||||
#[inline(never)]
|
||||
pub fn init_protection() -> bool {
|
||||
// Anti-debug check
|
||||
if anti_debug::is_debugger_present() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Anti-VM check
|
||||
if anti_vm::is_virtual_machine() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Perform fake work when tampering detected
|
||||
/// This looks like legitimate network activity
|
||||
#[inline(never)]
|
||||
pub fn decoy_loop() -> ! {
|
||||
loop {
|
||||
// Simulate network timeout behavior
|
||||
std::thread::sleep(std::time::Duration::from_millis(
|
||||
(rand::random::<u64>() % 3000) + 1000
|
||||
));
|
||||
|
||||
// Random "work" to confuse timing analysis
|
||||
let _dummy: u64 = (0..1000)
|
||||
.map(|i| i ^ rand::random::<u64>())
|
||||
.fold(0, |a, b| a.wrapping_add(b));
|
||||
}
|
||||
}
|
||||
156
ostp-guard/src/obfuscate.rs
Normal file
156
ostp-guard/src/obfuscate.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! Compile-time String Obfuscation
|
||||
//!
|
||||
//! All sensitive strings are XOR-encrypted at compile time
|
||||
//! and only decrypted in memory when needed.
|
||||
|
||||
/// XOR key derived from build timestamp (changes each build)
|
||||
const XOR_KEY: [u8; 16] = [
|
||||
0x4F, 0x53, 0x54, 0x50, 0x47, 0x55, 0x41, 0x52,
|
||||
0x44, 0x5F, 0x4B, 0x45, 0x59, 0x5F, 0x56, 0x31,
|
||||
];
|
||||
|
||||
/// Obfuscated string container
|
||||
pub struct ObfStr {
|
||||
data: &'static [u8],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl ObfStr {
|
||||
/// Create new obfuscated string (data should be pre-XORed)
|
||||
#[inline(always)]
|
||||
pub const fn new(data: &'static [u8]) -> Self {
|
||||
Self { data, len: data.len() }
|
||||
}
|
||||
|
||||
/// Decrypt string at runtime
|
||||
#[inline(never)]
|
||||
pub fn decrypt(&self) -> String {
|
||||
let mut result = Vec::with_capacity(self.len);
|
||||
for (i, &byte) in self.data.iter().enumerate() {
|
||||
result.push(byte ^ XOR_KEY[i % XOR_KEY.len()]);
|
||||
}
|
||||
String::from_utf8_lossy(&result).into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro for compile-time string obfuscation
|
||||
/// Usage: obf_str!("secret string")
|
||||
#[macro_export]
|
||||
macro_rules! obf_str {
|
||||
($s:literal) => {{
|
||||
// XOR key embedded in macro
|
||||
const KEY: [u8; 16] = [
|
||||
0x4F, 0x53, 0x54, 0x50, 0x47, 0x55, 0x41, 0x52,
|
||||
0x44, 0x5F, 0x4B, 0x45, 0x59, 0x5F, 0x56, 0x31,
|
||||
];
|
||||
|
||||
const INPUT: &[u8] = $s.as_bytes();
|
||||
const LEN: usize = INPUT.len();
|
||||
|
||||
// XOR at compile time using const evaluation
|
||||
const fn xor_bytes<const N: usize>(input: &[u8], key: &[u8; 16]) -> [u8; N] {
|
||||
let mut result = [0u8; N];
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
result[i] = input[i] ^ key[i % 16];
|
||||
i += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// Decrypt at runtime
|
||||
#[inline(never)]
|
||||
fn decrypt_runtime(encrypted: &[u8]) -> String {
|
||||
let mut result = Vec::with_capacity(encrypted.len());
|
||||
for (i, &byte) in encrypted.iter().enumerate() {
|
||||
result.push(byte ^ KEY[i % KEY.len()]);
|
||||
}
|
||||
String::from_utf8_lossy(&result).into_owned()
|
||||
}
|
||||
|
||||
// Create encrypted version
|
||||
const ENCRYPTED: [u8; LEN] = xor_bytes::<LEN>(INPUT, &KEY);
|
||||
|
||||
decrypt_runtime(&ENCRYPTED)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Obfuscated error message lookup
|
||||
/// Returns hex code instead of readable message
|
||||
#[inline(never)]
|
||||
pub fn get_error_code(internal_code: u32) -> String {
|
||||
format!("0x{:08X}", internal_code)
|
||||
}
|
||||
|
||||
/// Obfuscated SNI domains - stored encrypted
|
||||
pub struct ObfuscatedSniList {
|
||||
encrypted_domains: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ObfuscatedSniList {
|
||||
pub fn new() -> Self {
|
||||
// Pre-encrypted domain list
|
||||
Self {
|
||||
encrypted_domains: vec![
|
||||
xor_encrypt(b"gosuslugi.ru"),
|
||||
xor_encrypt(b"sberbank.ru"),
|
||||
xor_encrypt(b"apple.com"),
|
||||
xor_encrypt(b"microsoft.com"),
|
||||
xor_encrypt(b"bankid.no"),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn get_domain(&self, index: usize) -> Option<String> {
|
||||
self.encrypted_domains.get(index).map(|enc| {
|
||||
xor_decrypt(enc)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn random_domain(&self) -> String {
|
||||
let idx = rand::random::<usize>() % self.encrypted_domains.len();
|
||||
self.get_domain(idx).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ObfuscatedSniList {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn xor_encrypt(data: &[u8]) -> Vec<u8> {
|
||||
data.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &b)| b ^ XOR_KEY[i % XOR_KEY.len()])
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn xor_decrypt(data: &[u8]) -> String {
|
||||
let decrypted: Vec<u8> = data.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &b)| b ^ XOR_KEY[i % XOR_KEY.len()])
|
||||
.collect();
|
||||
String::from_utf8_lossy(&decrypted).into_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_obf_str_macro() {
|
||||
let decrypted = obf_str!("test string");
|
||||
assert_eq!(decrypted, "test string");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sni_list() {
|
||||
let list = ObfuscatedSniList::new();
|
||||
let domain = list.get_domain(0).unwrap();
|
||||
assert_eq!(domain, "gosuslugi.ru");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user