Rename things to ush for brevity. Add Tree system.

This commit is contained in:
Michael Mikovsky
2026-02-09 10:27:15 -07:00
parent ebeaa29d5b
commit 2a18639d84
86 changed files with 368 additions and 419 deletions
+505
View File
@@ -0,0 +1,505 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d28ed5f5f65056148fd25e1a596b5b6d9e772270abf9a9085d7cbfbf26c563"
dependencies = [
"hybrid-array",
]
[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
dependencies = [
"cipher",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
[[package]]
name = "hybrid-array"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0"
dependencies = [
"typenum",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"block-padding 0.3.3",
"generic-array",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "static_init"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bae1df58c5fea7502e8e352ec26b5579f6178e1fdb311e088580c980dee25ed"
dependencies = [
"bitflags 1.3.2",
"cfg_aliases 0.2.1",
"libc",
"parking_lot",
"parking_lot_core",
"static_init_macro",
"winapi",
]
[[package]]
name = "static_init_macro"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f"
dependencies = [
"cfg_aliases 0.1.1",
"memchr",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unshell-crypt"
version = "0.1.0"
dependencies = [
"aes",
"block-padding 0.4.1",
"cbc",
"getrandom",
"hex",
"hex-literal",
"regex",
"sha2",
]
[[package]]
name = "unshell-obfuscate"
version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"rand",
"static_init",
"syn 2.0.109",
"unshell-crypt",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.109",
]
+33
View File
@@ -0,0 +1,33 @@
[package]
name = "ush-obfuscate"
version.workspace = true
edition.workspace = true
authors.workspace = true
include.workspace = true
[features]
obfuscate = []
[lib]
proc-macro = true
[dependencies]
aes = "0.8.4"
block-padding = "0.4.1"
cbc = "0.1.2"
getrandom = "0.3.4"
hex = "0.4.3"
hex-literal = "1.1.0"
regex = "1.12.2"
sha2 = "0.10.9"
# unshell-crypt = {path = "../unshell-crypt"}
# Common
static_init = { workspace = true }
# Specific
quote = "1.0.42"
syn = {version = "2.0.109", features = ["full"]}
proc-macro2 = "1.0.103"
rand = "0.9.2"
+3
View File
@@ -0,0 +1,3 @@
# unshell-obfuscate
proc_macros that automatically obfuscate code in other projects
+3
View File
@@ -0,0 +1,3 @@
- Automate adding obfuscation to code
- Make random assembly use a distribution, not a random range, for more realistic code.
- Make the symbolic obfuscation actually symbolic, by creating a symbol table, not AES encryption.
+51
View File
@@ -0,0 +1,51 @@
pub fn decrypt_aes(input: &str, key_str: &str, iv: [u8; 16]) -> Result<String, String> {
// Hash the env key to get a 32-byte (256-bit) AES key
let mut key = hash(key_str.as_bytes());
let mut cipher_bytes = Base62::decode_full(input, &key).unwrap();
let salt = cipher_bytes.remove(0);
// XOR the salt bytes with the key bytes
// This replicates
for i in 0..32 {
key[i] ^= salt;
}
// Create buffer for result
let buf_len = cipher_bytes.len();
let mut buf: Vec<u8> = vec![0; buf_len];
buf[..cipher_bytes.len()].copy_from_slice(&cipher_bytes);
let pt = cbc::Decryptor::<aes::Aes256>::new(&key.into(), &iv.into())
.decrypt_padded_mut::<Pkcs7>(&mut buf)
.map_err(|_| "decryption failed".to_string())?;
Ok(String::from_utf8_lossy(pt).to_string())
}
pub fn decrypt_aes_lines(input: &str, key_str: &str, iv: [u8; 16]) -> String {
let mut decrypted_result = input.to_string();
let mut total_offset = 0;
// Split input by segments of base62 chars, denoted by two _'s, and attempt to decode
for aes_block in Regex::new(r"_([0-9a-zA-Z]*?)_").unwrap().find_iter(&input) {
let range = aes_block.range();
let aes_block = aes_block.as_str()[1..(aes_block.len() - 1)].to_string();
// If the decryption is successful, offset the current offset position
if let Ok(decrypted_block) = decrypt_aes(&aes_block, key_str, iv) {
let range = (range.start + total_offset as usize)..(range.end + total_offset as usize);
// Offset range by the difference between the decrypted block length and the original range length
total_offset += decrypted_block.len().clone() - (range.end - range.start);
decrypted_result.replace_range(range, &decrypted_block);
} else {
// If the decode is unsuccessful, leave the underscore-denoted region as is
continue;
}
}
decrypted_result
}
+53
View File
@@ -0,0 +1,53 @@
use crate::crypt::{base62::Base62, hash};
use aes::cipher::{BlockEncryptMut, KeyIvInit};
use cbc::cipher::block_padding::Pkcs7;
fn pkcs7_padded_length(input_len: usize) -> usize {
let block_size = 16;
((input_len / block_size) + 1) * block_size
}
pub fn encrypt_aes(plaintext: &str, key_str: &str, iv: [u8; 16]) -> String {
let plaintext = plaintext.as_bytes();
// Hash the env key to get a 32-byte (256-bit) AES key
let key = hash(key_str.as_bytes());
// Generate a psudo-random salt byte based on the plaintext
// I hope this does not break the encryption.
let mut salt = 0;
for byte in plaintext {
salt ^= byte;
}
let mut key_salted = key.clone();
// Salt the key by XORing the salt byte with all the key bytes.
// This ensures that the "hash" generated from the plaintext will
// make the encrypted result extremely different.
for i in 0..32 {
key_salted[i] ^= salt;
}
let buf_len = pkcs7_padded_length(plaintext.len());
let mut buf = vec![0u8; buf_len];
let pt_len = plaintext.len();
buf[..pt_len].copy_from_slice(&plaintext);
let mut ct = cbc::Encryptor::<aes::Aes256>::new(&key_salted.into(), &iv.into())
.encrypt_padded_mut::<Pkcs7>(&mut buf, pt_len)
.unwrap()
.to_vec();
// Add the salt byte to the key byte,
ct.insert(0, salt);
// Encode result in base62
Base62::encode_full(&ct, &key)
}
pub fn encrypt_aes_lines(plaintext: &str, key_str: &str, iv: [u8; 16]) -> String {
format!("_{}_", encrypt_aes(plaintext, key_str, iv))
}
+225
View File
@@ -0,0 +1,225 @@
use crate::crypt::{STATIC_BYTE_MAP, hash};
// Randomly mapped Base62 characters
pub struct Base62 {
charset: [char; 62],
}
pub const BASE62_CHARS: [char; 62] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z',
];
// Const for ratio
const ENCODING_RATIO: f64 = 8.0 / 5.954196310386875; // 8.0 / log2(62.0)
impl Base62 {
pub fn new(key: &[u8], nonce: usize) -> Self {
// Hash key again, for the chance that this random function can be used to derive the key
let key = hash(key);
let mut charset: [char; 62] = [0 as char; 62];
// Create a vector of indices from 0 to 61
let mut current_indicies = (0..62).map(|i| i as usize).collect::<Vec<usize>>();
// Loop through each byte in the key until all chars are filled
for i in 0..62 as usize {
let rand = STATIC_BYTE_MAP[(key[i as usize % key.len()] as usize + nonce) % 255];
let index_index = rand as usize % current_indicies.len();
let put_index = current_indicies.remove(index_index);
charset[put_index] = BASE62_CHARS[i];
}
return Self { charset };
}
// Convert character to base-62 value using custom charset
fn char_to_value(&self, ch: char) -> Result<u8, String> {
self.charset
.iter()
.position(|&c| c == ch)
.map(|pos| pos as u8)
.ok_or_else(|| format!("Invalid character for this charset: '{}'", ch))
}
/// Encodes a byte slice into a base-62 string using a custom character set
/// Supports arbitrary length input by using big integer arithmetic
pub fn encode(&self, data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
// Count leading zeros
let leading_zeros = data.iter().take_while(|&&b| b == 0).count();
// Skip leading zeros for conversion
let data = &data[leading_zeros..];
if data.is_empty() {
return self.charset[0].to_string().repeat(leading_zeros);
}
let mut result = Vec::new();
let mut num = data.to_vec();
// Convert to base-62 using division
while !is_zero(&num) {
let remainder = div_mod_62(&mut num);
result.push(self.charset[remainder]);
}
// Add leading zeros
for _ in 0..leading_zeros {
result.push(self.charset[0]);
}
// Reverse since we built it backwards
result.reverse();
result.into_iter().collect()
}
/// Decodes a base-62 string back into bytes using a custom character set
/// Supports arbitrary length output
pub fn decode(&self, encoded: &str) -> Result<Vec<u8>, String> {
if encoded.is_empty() {
return Ok(Vec::new());
}
// Count leading zeros (first character in charset)
let zero_char = self.charset[0];
let leading_zeros = encoded.chars().take_while(|&c| c == zero_char).count();
// Skip leading zeros for conversion
let encoded = &encoded[leading_zeros..];
if encoded.is_empty() {
return Ok(vec![0; leading_zeros]);
}
// Convert base-62 string to bytes using multiplication
let mut num = vec![0u8];
for ch in encoded.chars() {
let value = self.char_to_value(ch)?;
mul_add(&mut num, 62, value);
}
// Add leading zero bytes
let mut result = vec![0u8; leading_zeros];
result.append(&mut num);
Ok(result)
}
pub fn encode_full(data: &[u8], key: &[u8]) -> String {
// Predict the length of the encoded data
let length = predict_base62_len(data);
let base = Base62::new(&key, length % 255);
let encoded = base.encode(data);
// For the case that the encoded length is not equal to the predicted length
// The nonce must be derived from this length, so this needs to be ensured
//
// Re-encode with the correct length
if encoded.len() != length {
let len = encoded.len();
let base = Base62::new(&key, len % 255);
let encoded = base.encode(data);
assert_eq!(encoded.len(), len);
encoded
} else {
encoded
}
}
pub fn decode_full(data: &str, key: &[u8]) -> Result<Vec<u8>, String> {
let base = Base62::new(&key, data.len() % 255);
base.decode(data)
}
}
// Helper: Check if big integer (as bytes) is zero
fn is_zero(num: &[u8]) -> bool {
num.iter().all(|&b| b == 0)
}
// Helper: Divide big integer by 62 and return remainder
// Modifies num in place to be the quotient
fn div_mod_62(num: &mut Vec<u8>) -> usize {
let mut remainder = 0u16;
let mut all_zero = true;
for byte in num.iter_mut() {
let current = (remainder << 8) | (*byte as u16);
*byte = (current / 62) as u8;
remainder = current % 62;
if *byte != 0 {
all_zero = false;
}
}
// Remove leading zeros from quotient
if all_zero {
num.clear();
num.push(0);
} else {
let first_nonzero = num.iter().position(|&b| b != 0).unwrap_or(0);
if first_nonzero > 0 {
num.drain(0..first_nonzero);
}
}
remainder as usize
}
// Helper: Multiply big integer by 62 and add a value
// Modifies num in place
fn mul_add(num: &mut Vec<u8>, multiplier: u16, add: u8) {
let mut carry = add as u16;
for byte in num.iter_mut().rev() {
let product = (*byte as u16) * multiplier + carry;
*byte = (product & 0xFF) as u8;
carry = product >> 8;
}
// Add remaining carry bytes
while carry > 0 {
num.insert(0, (carry & 0xFF) as u8);
carry >>= 8;
}
}
/// Predicts the byte length of the decoded output given a base-62 encoded string
/// This calculates the length without performing the full decoding
pub fn predict_base62_len(input_bytes: &[u8]) -> usize {
if input_bytes.is_empty() {
return 0;
}
// 1. Count leading zero bytes.
let num_leading_zeros = input_bytes.iter().take_while(|&&b| b == 0).count();
// 2. Calculate length of the rest of the bytes.
let num_rest_bytes = input_bytes.len() - num_leading_zeros;
if num_rest_bytes == 0 {
// If all bytes were zeros, the length is just the number of zeros.
num_leading_zeros
} else {
// 3. Calculate the mathematical upper bound for the non-zero part.
// This is ceil(num_rest_bytes * 8_bits / log2(62))
// which is ceil(num_rest_bytes * log_62(256))
let rest_len = (num_rest_bytes as f64 * ENCODING_RATIO).ceil();
// 4. Total length is zeros + rest_len
num_leading_zeros + rest_len as usize
}
}
+34
View File
@@ -0,0 +1,34 @@
pub mod aes_encrypt;
#[allow(dead_code)]
pub mod base62;
pub const ENV_KEY_NAME: &str = "OBFUSCATION_KEY";
pub const BACKUP_ENV_KEY: &str = "OBFUSCATION_KEY_DO_NOT_USE";
pub const STATIC_IV: [u8; 16] = [
0x6d, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x69, 0x76, 0x5f, 0x30, 0x31, 0x32,
];
pub const STATIC_BYTE_MAP: [u8; 256] = [
58, 177, 23, 16, 227, 134, 93, 239, 201, 3, 74, 162, 228, 195, 126, 157, 136, 57, 98, 86, 175,
111, 71, 39, 205, 49, 139, 116, 143, 182, 250, 222, 59, 36, 18, 79, 37, 84, 190, 42, 7, 142,
167, 168, 105, 54, 218, 230, 203, 83, 52, 129, 144, 184, 41, 73, 29, 72, 128, 75, 160, 149, 20,
32, 207, 155, 131, 125, 199, 220, 56, 76, 94, 78, 247, 214, 165, 33, 19, 241, 69, 206, 172,
113, 225, 90, 150, 242, 107, 232, 8, 77, 100, 187, 240, 104, 31, 180, 53, 253, 63, 192, 252,
30, 140, 158, 1, 210, 24, 44, 243, 145, 197, 80, 202, 65, 196, 45, 51, 11, 55, 236, 186, 22,
224, 118, 200, 204, 153, 114, 117, 229, 47, 159, 96, 219, 234, 183, 13, 70, 81, 137, 46, 211,
254, 255, 127, 138, 246, 87, 61, 89, 189, 66, 208, 221, 85, 251, 188, 43, 248, 102, 146, 170,
132, 213, 178, 103, 62, 92, 27, 6, 38, 122, 185, 181, 215, 12, 179, 4, 169, 226, 209, 0, 112,
154, 120, 17, 101, 64, 194, 193, 212, 198, 121, 135, 99, 115, 244, 14, 133, 26, 156, 10, 5,
238, 163, 164, 25, 88, 95, 152, 40, 108, 216, 21, 109, 2, 123, 233, 237, 235, 119, 60, 82, 191,
68, 151, 161, 124, 48, 35, 249, 171, 50, 141, 166, 34, 15, 176, 97, 148, 147, 91, 9, 28, 223,
67, 130, 217, 231, 106, 245, 110, 173, 174,
];
use sha2::{Digest, Sha256};
pub fn hash(input: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(input);
hasher.finalize().into()
}
+97
View File
@@ -0,0 +1,97 @@
use syn::parse::{Parse, ParseStream};
use syn::{Expr, Lit, Token};
pub struct PrintlnArgs {
pub format_str: String,
pub args: Vec<Expr>,
}
impl Parse for PrintlnArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let format_expr: Expr = input.parse()?;
let format_str = match format_expr {
Expr::Lit(ref lit) => {
if let Lit::Str(ref s) = lit.lit {
s.value()
} else {
return Err(syn::Error::new_spanned(lit, "Expected string literal"));
}
}
_ => {
return Err(syn::Error::new_spanned(
format_expr,
"Expected string literal",
));
}
};
let mut args = Vec::new();
while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
args.push(input.parse()?);
}
Ok(PrintlnArgs { format_str, args })
}
}
#[derive(Debug)]
pub enum FormatSegment {
Static(String),
Dynamic(String, usize), // format spec, arg index
}
pub fn parse_format_string(fmt: &str) -> Vec<FormatSegment> {
let mut segments = Vec::new();
let mut current_static = String::new();
let mut chars = fmt.chars().peekable();
let mut arg_idx = 0;
while let Some(ch) = chars.next() {
if ch == '{' {
if chars.peek() == Some(&'{') {
chars.next();
current_static.push('{');
continue;
}
// Save current static segment
if !current_static.is_empty() {
segments.push(FormatSegment::Static(current_static.clone()));
current_static.clear();
}
// Parse format spec
let mut spec = String::new();
while let Some(&next_ch) = chars.peek() {
if next_ch == '}' {
chars.next();
break;
}
spec.push(chars.next().unwrap());
}
segments.push(FormatSegment::Dynamic(spec, arg_idx));
arg_idx += 1;
} else if ch == '}' {
if chars.peek() == Some(&'}') {
chars.next();
current_static.push('}');
} else {
current_static.push(ch);
}
} else {
current_static.push(ch);
}
}
if !current_static.is_empty() {
segments.push(FormatSegment::Static(current_static));
}
segments
}
+127
View File
@@ -0,0 +1,127 @@
#![feature(proc_macro_quote)]
#![feature(proc_macro_span)]
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
mod format_helper;
use format_helper::*;
mod crypt;
#[allow(dead_code, unused_imports)]
mod no_obfuscate;
#[allow(dead_code, unused_imports)]
mod obfuscate;
#[cfg(not(feature = "obfuscate"))]
use no_obfuscate as obs;
#[cfg(feature = "obfuscate")]
use obfuscate as obs;
// String obfuscation
#[proc_macro]
pub fn obs(input: TokenStream) -> TokenStream {
obs::xor(input)
}
#[proc_macro_attribute]
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
obs::aes_fn_name(_attr, item)
}
#[proc_macro]
pub fn symbol(input: TokenStream) -> TokenStream {
obs::aes_str(input)
}
#[proc_macro]
pub fn junk_asm(input: TokenStream) -> TokenStream {
obs::junk_asm(input)
}
//
#[proc_macro]
pub fn file_symbol(_input: TokenStream) -> TokenStream {
// Get the call site span to extract file information
let span = proc_macro::Span::call_site();
let source_file = span.source();
let file_path = source_file.file();
let line_num = source_file.line();
let concatted = format!("{}:{}", file_path, line_num);
// Return as a string literal
let output = quote! {
obfuscate::symbol!(#concatted)
};
// let output = quote! {
// #concatted
// };
output.into()
}
#[proc_macro]
pub fn format_obs(input: TokenStream) -> TokenStream {
let PrintlnArgs { format_str, args } = parse_macro_input!(input as PrintlnArgs);
let segments = parse_format_string(&format_str);
if segments.is_empty() {
return quote! {
print!("\n")
}
.into();
}
let mut parts = Vec::new();
for segment in segments {
match segment {
FormatSegment::Static(text) => {
parts.push(quote! {
obfuscate::symbol!(#text).to_string()
});
}
FormatSegment::Dynamic(spec, idx) => {
if idx >= args.len() {
return syn::Error::new(
proc_macro2::Span::call_site(),
format!("argument {} is missing", idx),
)
.to_compile_error()
.into();
}
let arg = &args[idx];
let fmt_spec = if spec.is_empty() {
quote! { "{}" }
} else {
let full_spec = format!("{{{}}}", spec);
quote! { #full_spec }
};
// quote! {
// println!(#fmt_spec, #arg);
// }
parts.push(quote! {
format!(#fmt_spec, #arg)
});
}
}
}
(quote! {
{
let mut string = String::new();
#(
string.push_str(&#parts);
)*
string
}
})
.into()
}
+27
View File
@@ -0,0 +1,27 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input};
pub fn xor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
(quote::quote! {
String::from(#input)
})
.into()
}
pub fn aes_fn_name(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
TokenStream::from(quote! {
#[unsafe(no_mangle)]
#func
})
}
pub fn aes_str(input: TokenStream) -> TokenStream {
input
}
pub fn junk_asm(_input: TokenStream) -> TokenStream {
TokenStream::new()
}
+18
View File
@@ -0,0 +1,18 @@
mod obs_junk_asm;
mod obs_xor;
mod sym_aes_strings;
pub use obs_junk_asm::junk_asm;
pub use obs_xor::xor;
pub use sym_aes_strings::*;
use crate::crypt::{BACKUP_ENV_KEY, ENV_KEY_NAME};
fn get_encryption_key() -> String {
std::env::var(ENV_KEY_NAME).unwrap_or({
println!("Using default encryption key!");
BACKUP_ENV_KEY.to_owned()
})
}
// pub fn
+234
View File
@@ -0,0 +1,234 @@
use proc_macro::TokenStream;
use quote::quote;
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use syn::{LitFloat, parse_macro_input};
// const MIN_TAGS: u32 = 1; // Maximum instructions per recursive block
// const MAX_TAGS: u32 = 22; // Maximum instructions per recursive block
// const MIN_INSTRUCTIONS: u32 = 1; // Maximum instructions per recursive block
// const MAX_INSTRUCTIONS: u32 = 22; // Maximum instructions per recursive block
// const MIN_JUMPS: u32 = 1;
// const MAX_JUMPS: u32 = 5;
const CHAIN_WEIGHT: f64 = 1.0;
const TAG_WEIGHT: f64 = 1.0;
const INST_WEIGHT: f64 = 3.0;
const JUMP_WEIGHT: f64 = 2.0;
// The full list of 64-bit registers in AT&T syntax (used by default in asm!)
const REGISTERS: &[&str] = &[
"%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13",
"%r14", "%r15",
];
// Conditional Jumps in AT&T syntax.
const COND_JUMPS: &[&str] = &[
"je", "jne", "jg", "jge", "jl", "jle", "ja", "jnb", "jc", "jnc", "jz", "jnz",
];
// Arithmetic/Logic operations with the 'q' (quad-word) suffix.
const ARITHITHMETIC_OPS: &[&str] = &["addq", "subq", "xorq", "andq", "orq"];
// --- Helper Functions for Modular Generation ---
/// Generates a unique label name for the given depth and ID.
fn generate_label(prefix: &str, depth: usize, id: usize) -> String {
format!(".L_{}_{}_{}", prefix, depth, id)
}
/// Generates a highly randomized, complex instruction using different addressing modes.
fn generate_complex_mutation(rng: &mut SmallRng) -> String {
let op = ARITHITHMETIC_OPS[rng.random_range(0..ARITHITHMETIC_OPS.len())];
match rng.random_range(0..3) {
// Pattern 0: Register-Immediate
// Example: "addq $0x1234, %rax"
0 => {
let reg = REGISTERS[rng.random_range(0..REGISTERS.len())];
let immediate = rng.random_range(1..=0xFFFF);
format!("\t{} ${}, {}", op, immediate, reg)
}
// Pattern 1: Register-Register
// Example: "xorq %rbx, %rcx"
1 => {
let reg_src = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_dst = REGISTERS[rng.random_range(0..REGISTERS.len())];
format!("\t{} {}, {}", op, reg_src, reg_dst)
}
// Pattern 2: LEA (Complex Address Calculation)
// Example: "leaq (%rax, %rbx, 4), %rcx"
2 => {
let reg_base = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_index = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg_dst = REGISTERS[rng.random_range(0..REGISTERS.len())];
let scale = 1 << rng.random_range(0..4); // Scale is 1, 2, 4, or 8
format!(
"\tleaq ({}, {}, {}), {}",
reg_base, reg_index, scale, reg_dst
)
}
_ => String::new(), // Should not happen
}
}
/// Generates a comparison followed by a conditional jump to a specific label.
fn generate_conditional_jump(rng: &mut SmallRng, label: &str) -> String {
let reg1 = REGISTERS[rng.random_range(0..REGISTERS.len())];
let reg2 = REGISTERS[rng.random_range(0..REGISTERS.len())];
let jump = COND_JUMPS[rng.random_range(0..COND_JUMPS.len())];
// Example: "cmpq %rdx, %rsi; jg .L_target_"
format!("\tcmpq {}, {}; {} {}\n", reg1, reg2, jump, label)
}
// --- The Core DAG Recursive Algorithm ---
fn generate_dag_block(weight: f64, rng: &mut SmallRng, total_count: usize) -> String {
let labels = (0..total_count)
.map(|i| {
(0..{
let mut n = 1;
while !rng.random_bool((weight.sqrt() * TAG_WEIGHT).min(1.)) {
n += 1;
}
n
})
.map(|j| generate_label("dag", i, j))
.collect::<Vec<String>>()
})
// .flatten()
.collect::<Vec<Vec<String>>>();
let mut assembly_block = String::new();
// 3. Instruction Loop and DAG construction
for i in 0..total_count {
let chain_labels = &labels[i];
let num_labels = chain_labels.len();
for j in 0..num_labels {
let current_label = &chain_labels[j];
assembly_block.push_str(&format!("{}:\n", current_label));
let mut inst_count = 1;
while !rng.random_bool((weight * INST_WEIGHT).min(1.)) {
inst_count += 1;
}
for _ in 0..inst_count {
assembly_block.push_str(&format!("{}\n", generate_complex_mutation(rng)));
}
// Conditional Forward Jump (Creates DAG edges)
if i < total_count - 1 {
let mut jump_count = 1;
while !rng.random_bool((weight.sqrt() * JUMP_WEIGHT).min(1.)) {
jump_count += 1;
}
for _ in 0..jump_count {
// Jump to a random label strictly ahead of the current one
let target_chain = if j + 1 < num_labels {
rng.random_range(i..total_count)
} else {
rng.random_range(i + 1..total_count)
};
let chain_labels = &labels[target_chain];
let target_index = if target_chain == i {
rng.random_range((j + 1)..num_labels)
} else {
rng.random_range(0..chain_labels.len())
};
let target_label = &chain_labels[target_index];
assembly_block.push_str(&generate_conditional_jump(rng, target_label));
}
// }
}
}
}
// 4. Backward Conditional Jump (Adds controlled cycles)
// Only at the end of the block, allowing a chance to loop back to an earlier instruction.
// if num_labels > 1 && rng.random_bool(weight * 3.) {
// let target_index = rng.random_range(0..num_labels as usize - 1);
// let target_label = &labels[target_index];
// assembly_block.push_str(&format!("{}\n", generate_complex_mutation(rng)));
// assembly_block.push_str(&generate_conditional_jump(rng, target_label));
// assembly_block.push_str("// Backward Conditional Jump to maintain short execution\n");
// }
assembly_block
}
pub fn junk_asm(input: TokenStream) -> TokenStream {
// 1. Parse the input (expecting an optional f32 weight)
let weight: f64 = if input.is_empty() {
None
} else {
match parse_macro_input!(input as LitFloat).base10_parse::<f64>() {
Ok(w) => Some(1. / (w + 1.)), // Move weight variable to be more
Err(_) => None,
}
}
.expect("Expected F64");
// let final_weight = input_weight.unwrap_or(WEIGHT);
// 2. Setup
let mut rng = SmallRng::from_os_rng();
let count = {
let mut n = 1;
while !rng.random_bool((weight.sqrt() * CHAIN_WEIGHT).min(1.)) {
n += 1;
}
n
};
// eeeeeeeeeeee
// 3. Generate Assembly
// let main_assembly = (0..count)
// .map(|i| generate_dag_block(weight, &mut rng, i, count))
// .into_iter()
// .collect::<String>();
let main_assembly = generate_dag_block(weight, &mut rng, count);
// println!("{}", main_assembly);
// 4. Wrap in `asm!`
let expanded = quote! {
// Output will replace the junk_asm!(...) call
{
// Execute the code using the standard `asm!` macro.
unsafe {
#[allow(named_asm_labels)]
core::arch::asm!(
// The generated junk code
// Note: We MUST use AT&T syntax (e.g., %rax, $100) due to options(att_syntax)
// The code is generated in AT&T syntax.
#main_assembly,
// Pass the simulated external address into a temporary register (%r15)
// This allows instructions to reference an "external" scope using memory reads/writes.
// in(reg) external_addr_ref,
// Clobber all general-purpose registers to force saving/restoring
clobber_abi("sysv64"),
// Correct options for non-volatile junk code
options(att_syntax, nomem, nostack, preserves_flags)
);
}
}
};
expanded.into()
}
// NOTE: To make the example runnable, the `src/main.rs` file would now call
// junk_asm!(0.2) or junk_asm!(). The instruction sizes and jump structure
// are now compliant with your requirements.
+52
View File
@@ -0,0 +1,52 @@
use getrandom::fill;
use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};
/// XOR encrypt strings
pub fn xor(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_str = lit_str.value();
// Handle empty strings explicitly
if original_str.is_empty() {
return TokenStream::from(quote! { String::new() });
}
// --- Obfuscated Branch Logic ---
// This code runs at compile-time
let str_bytes = original_str.as_bytes();
let len = str_bytes.len();
// 1. Generate a unique, random key for this string
let mut key = vec![0u8; len];
fill(&mut key).expect("Failed to get random bytes for XOR key");
// 2. XOR the string with the key
let mut obfuscated = Vec::with_capacity(len);
for i in 0..len {
obfuscated.push(str_bytes[i] ^ key[i]);
}
// 3. This is the code that will be injected into the user's binary
// It runs at *runtime* to decrypt the string.
let obfuscated_expansion = quote! {
{
// These static arrays are stored directly in your binary
static OBFUSCATED_DATA: [u8; #len] = [ #( #obfuscated ),* ];
static KEY_DATA: [u8; #len] = [ #( #key ),* ];
let mut decrypted = Vec::with_capacity(#len);
for i in 0..#len {
decrypted.push(OBFUSCATED_DATA[i] ^ KEY_DATA[i]);
}
// We can trust this since the source was a valid String literal
String::from_utf8(decrypted).unwrap()
}
};
TokenStream::from(obfuscated_expansion)
}
@@ -0,0 +1,44 @@
use crate::crypt::{BACKUP_ENV_KEY, ENV_KEY_NAME, STATIC_IV, aes_encrypt::encrypt_aes_lines};
use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input};
use crate::obfuscate::get_encryption_key;
/// Obfuscate function names by encrypting in AES
pub fn aes_fn_name(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the input function
let func = parse_macro_input!(item as ItemFn);
// Get the original function name
let fn_name = func.sig.ident.to_string();
// Generate the new, obfuscated name
let obfuscated_name = encrypt_aes_lines(&fn_name, &get_encryption_key(), STATIC_IV);
// Create a new string literal for the name
let new_name_lit = LitStr::new(&obfuscated_name, func.sig.ident.span());
// Re-build the function, but add #[no_mangle]
// and rename the *exported* symbol via #[export_name]
TokenStream::from(quote! {
#[unsafe(export_name = #new_name_lit)]
#func
})
}
/// Obfuscate strings by encrypting in AES
pub fn aes_str(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let lit_str = parse_macro_input!(input as LitStr);
let original_name = lit_str.value();
// Generate the exact same obfuscated name
let obfuscated_name = encrypt_aes_lines(&original_name, &get_encryption_key(), STATIC_IV);
// Expand to a static string literal
TokenStream::from(quote! {
#obfuscated_name
})
}