- New crate: gyandoot-license with: - Machine fingerprinting (CPU, MAC, Disk, hostname) - Ed25519 JWT signing and verification - Agent counting with RAII guards - AES-256-GCM encrypted license cache - Anti-tamper binary integrity checks - Debugger detection - Added to workspace Cargo.toml - Added 'gyandoot license' CLI subcommand (activate, status, deactivate, fingerprint)
119 lines
3.3 KiB
Rust
119 lines
3.3 KiB
Rust
//! Anti-Tamper & Debugger Detection
|
|
//!
|
|
//! Prevents the binary from being modified or debugged.
|
|
//! - Binary integrity hash verification
|
|
//! - Debugger detection (ptrace)
|
|
//! - String obfuscation helpers
|
|
|
|
use crate::LicenseError;
|
|
use sha2::{Sha256, Digest};
|
|
|
|
/// Compute SHA-256 hash of the current binary
|
|
pub fn current_binary_hash() -> String {
|
|
let exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("unknown"));
|
|
if let Ok(data) = std::fs::read(&exe_path) {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(&data);
|
|
hex::encode(hasher.finalize())
|
|
} else {
|
|
"unknown_binary".to_string()
|
|
}
|
|
}
|
|
|
|
/// Verify binary integrity against an expected hash
|
|
pub fn verify_binary(expected_hash: &str) -> Result<(), LicenseError> {
|
|
let actual = current_binary_hash();
|
|
if actual != expected_hash {
|
|
return Err(LicenseError::BinaryTampered);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Detect if a debugger is attached
|
|
pub fn detect_debugger() -> bool {
|
|
// Linux: check if being ptraced
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
// Try ptrace(PTRACE_TRACEME, ...) — if it fails, we're being debugged
|
|
let result = unsafe { libc::ptrace(libc::PTRACE_TRACEME, 0, 0, 0) };
|
|
if result == -1 {
|
|
return true; // Being traced
|
|
}
|
|
// Detach
|
|
unsafe { libc::ptrace(libc::PTRACE_DETACH, 0, 0, 0) };
|
|
}
|
|
|
|
// Check for common debugger environment variables
|
|
let debug_env_vars = [
|
|
"DEBUG", "DEBUGGER", "JAVA_DEBUG", "PYTHONDEBUG",
|
|
"TRACE", "TRACEME", "LD_PRELOAD",
|
|
];
|
|
for var in &debug_env_vars {
|
|
if std::env::var(var).is_ok() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for /proc/self/status TracerPid
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
if let Ok(content) = std::fs::read_to_string("/proc/self/status") {
|
|
for line in content.lines() {
|
|
if line.starts_with("TracerPid:") {
|
|
let pid = line.trim_start_matches("TracerPid:").trim();
|
|
if pid != "0" {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Run all anti-tamper checks at startup
|
|
pub fn run_startup_checks(expected_binary_hash: &str) -> Result<(), LicenseError> {
|
|
// Check 1: Debugger
|
|
if detect_debugger() {
|
|
return Err(LicenseError::DebuggerDetected);
|
|
}
|
|
|
|
// Check 2: Binary integrity
|
|
verify_binary(expected_binary_hash)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Obfuscate a string literal (compile-time)
|
|
/// Usage: obfstr!("sensitive_string")
|
|
/// In production, use the `obfstr` crate for true compile-time obfuscation
|
|
#[macro_export]
|
|
macro_rules! obf {
|
|
($s:expr) => {{
|
|
let bytes: &[u8] = $s.as_bytes();
|
|
let mut obfuscated: Vec<u8> = bytes.iter().map(|b| b ^ 0xAA).collect();
|
|
let deobf: Vec<u8> = obfuscated.iter().map(|b| b ^ 0xAA).collect();
|
|
String::from_utf8(deobf).unwrap_or_default()
|
|
}};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_binary_hash_not_empty() {
|
|
let hash = current_binary_hash();
|
|
assert!(!hash.is_empty());
|
|
assert_eq!(hash.len(), 64);
|
|
}
|
|
|
|
#[test]
|
|
fn test_obf_macro() {
|
|
let original = "license.gyannetra.com";
|
|
let result = obf!(original);
|
|
assert_eq!(result, original);
|
|
}
|
|
}
|