start, reverse guard, cli-frontend for server and client

This commit is contained in:
2026-01-01 18:54:36 +03:00
commit 5fbb32d243
30 changed files with 4700 additions and 0 deletions

14
ostp-guard/Cargo.toml Normal file
View 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"

View 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
View 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 &registry_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
}

View 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
View 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
View 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");
}
}