pub const MZ_MAGIC: [u8; 2] = *b"MZ";
pub const PE_SIGNATURE: [u8; 4] = *b"PE\0\0";
pub const MACHINE_AMD64: u16 = 0x8664;
pub const MACHINE_I386: u16 = 0x014C;
pub const MACHINE_ARM64: u16 = 0xAA64;
pub const SUSPICIOUS_IMPORT_NAMES: &[&str] = &[
"VirtualAlloc",
"VirtualAllocEx",
"VirtualProtect",
"VirtualProtectEx",
"WriteProcessMemory",
"ReadProcessMemory",
"CreateRemoteThread",
"CreateRemoteThreadEx",
"NtCreateThreadEx",
"RtlCreateUserThread",
"SetThreadContext",
"GetThreadContext",
"SuspendThread",
"ResumeThread",
"QueueUserAPC",
"NtQueueApcThread",
"OpenProcess",
"NtOpenProcess",
"AdjustTokenPrivileges",
"OpenProcessToken",
"DuplicateTokenEx",
"LoadLibraryA",
"LoadLibraryW",
"LoadLibraryExA",
"LoadLibraryExW",
"GetProcAddress",
"IsDebuggerPresent",
"CheckRemoteDebuggerPresent",
"NtQueryInformationProcess",
"OutputDebugStringA",
"OutputDebugStringW",
"CryptEncrypt",
"CryptDecrypt",
"CryptGenKey",
"CryptImportKey",
"BCryptEncrypt",
"BCryptDecrypt",
"BCryptGenerateSymmetricKey",
"FindFirstFileA",
"FindFirstFileW",
"FindFirstFileExA",
"FindFirstFileExW",
"DeleteFileA",
"DeleteFileW",
"MoveFileExA",
"MoveFileExW",
"ShellExecuteA",
"ShellExecuteW",
"ShellExecuteExA",
"ShellExecuteExW",
"WinExec",
"CreateProcessA",
"CreateProcessW",
"RegSetValueExA",
"RegSetValueExW",
"RegCreateKeyExA",
"RegCreateKeyExW",
"InternetOpenA",
"InternetOpenW",
"InternetConnectA",
"InternetConnectW",
"HttpSendRequestA",
"HttpSendRequestW",
"URLDownloadToFileA",
"URLDownloadToFileW",
"WSAStartup",
"WSAConnect",
"WSASend",
"WSARecv",
"connect",
"send",
"recv",
];
pub const PACKED_SECTION_NAMES: &[&str] = &[
"UPX0",
"UPX1",
"UPX2",
".upx0",
".upx1",
".aspack",
".adata",
".packed",
".shrink",
"MPRESS1",
"MPRESS2",
".petite",
".nsp0",
".nsp1",
".nsp2", ".themida",
".winlicen",
"PESHiELD",
"_winzip_",
"ASProtect",
".enigma1",
".enigma2",
".vmp0",
".vmp1", ".obsidium",
"Exe32Pack",
];
pub const AV_EXCLUSION_PATH_FRAGMENTS: &[&str] = &[
"Exclusions\\Paths",
"Exclusions\\Extensions",
"Exclusions\\Processes",
"Exclusions\\IpAddresses",
"Windows Defender\\Exclusions",
"Microsoft\\Windows Defender",
"Kaspersky Lab\\AVP",
"KasperskyLab",
"Symantec\\Symantec Endpoint Protection",
"Norton AntiVirus",
"McAfee\\DesktopProtection",
"McAfee\\Endpoint Security",
"TrendMicro",
"OfficeScanNT",
"SophosSAV",
"Sophos\\Sophos Anti-Virus",
"ESET\\ESET Security",
"Bitdefender",
"bd_ie",
"Malwarebytes",
"VIPRE",
"SentinelOne",
"AddDynamicSignature",
"RemoveDynamicSignature",
"DisableRealtimeMonitoring",
"SubmitSamplesConsent",
"MpCmdRun",
"ExcludeFromScan",
"ExclusionPath",
"SecurityCenter",
"AntiVirusOverride",
"FirewallDisableNotify",
];
pub const QWCRYPT_PE_STRING_IOCS: &[&str] = &[
".qwCrypt",
"rbcw",
"ADNotificationManager",
"excludeVM", "HyperV",
"ZAM64", "zamguard",
"workers.dev",
"cloudflare",
];
pub const ANTI_DEBUG_IMPORT_NAMES: &[&str] = &[
"IsDebuggerPresent",
"CheckRemoteDebuggerPresent",
"NtQueryInformationProcess", "ZwQueryInformationProcess",
"NtSetInformationThread", "ZwSetInformationThread",
"SetUnhandledExceptionFilter",
"UnhandledExceptionFilter",
"RaiseException",
"GetTickCount",
"GetTickCount64",
"QueryPerformanceCounter",
"timeGetTime",
"CloseHandle",
"OutputDebugStringA",
"OutputDebugStringW",
"CreateToolhelp32Snapshot",
"Process32First",
"Process32Next",
"Module32First",
"Module32Next",
"FindWindowA",
"FindWindowW",
"FindWindowExA",
"FindWindowExW",
"EnumWindows",
"BlockInput",
];
pub const PROCESS_HOLLOWING_APIS: &[&str] = &[
"NtUnmapViewOfSection",
"ZwUnmapViewOfSection",
"NtMapViewOfSection",
"ZwMapViewOfSection",
"NtCreateSection",
"ZwCreateSection",
"VirtualAllocEx",
"WriteProcessMemory",
"SetThreadContext",
"GetThreadContext",
"ResumeThread",
"NtResumeThread",
"ZwResumeThread",
];
pub const RANSOMWARE_STRING_PATTERNS: &[&str] = &[
".encrypted",
".locked",
".enc",
".crypt",
".crypted",
".locky",
".zepto",
".cerber",
".wncry", ".wnry",
".ryuk",
".conti",
".hive",
".lockbit",
".qwCrypt", "HOW_TO_DECRYPT",
"DECRYPT_FILES",
"RECOVER_FILES",
"README_DECRYPT",
"YOUR_FILES_ARE_ENCRYPTED",
"IMPORTANT_README",
"restore_files",
"How to Decrypt Files",
"All your files",
"Your personal ID",
"unique identifier",
"bitcoin",
"Bitcoin",
"monero",
"Monero",
" BTC",
" XMR",
"wallet address",
"Bitcoin address",
"send payment",
".onion",
"tor2web",
"torproject.org",
"ransom",
"decrypt",
"decryption key",
"decryptor",
"pay within",
"deadline",
];
pub const PERSISTENCE_STRING_PATTERNS: &[&str] = &[
"CurrentVersion\\Run",
"CurrentVersion\\RunOnce",
"CurrentVersion\\RunServices",
"CurrentVersion\\RunServicesOnce",
"Winlogon\\Userinit",
"Winlogon\\Shell",
"CurrentControlSet\\Services",
"schtasks /create",
"schtasks.exe",
"Task Scheduler",
"\\Microsoft\\Windows\\TaskScheduler",
"\\Start Menu\\Programs\\Startup",
"\\Roaming\\Microsoft\\Windows\\Start Menu",
"__EventFilter",
"__EventConsumer",
"CommandLineEventConsumer",
"ROOT\\subscription",
"InprocServer32",
"LocalServer32",
"AppInit_DLLs",
"Image File Execution Options",
"Security Packages",
"Authentication Packages",
"UserInitMprLogonScript",
"BootExecute",
];
pub const NETWORK_C2_PATTERNS: &[&str] = &[
"http://",
"https://",
"ftp://",
".onion",
"tor2web",
"User-Agent:",
"Content-Type: application/",
"Authorization: Bearer",
"Authorization: Basic",
"X-Forwarded-For:",
"POST /",
"/beacon",
"/checkin",
"/gate.php",
"/config.php",
"/panel/",
"/upload",
"/download",
"/command",
"/tasks",
"/results",
"/implant",
"/stager",
"workers.dev",
"dns.google",
"cloudflare-dns.com",
"doh.pub",
"powershell -enc",
"powershell -e ",
"powershell -EncodedCommand",
"cmd.exe /c ",
"meterpreter",
"reverse_tcp",
"reverse_https",
"shellcode",
"payload.dll",
];
pub const CREDENTIAL_PATTERNS: &[&str] = &[
"password=",
"Password=",
"passwd=",
"pass=",
"pwd=",
"secret=",
"api_key=",
"apikey=",
"api-key=",
"API_KEY=",
"token=",
"access_token",
"refresh_token",
"auth_token",
"Authorization: Basic",
"Authorization: Bearer",
"AKIA", "aws_secret_access_key",
"GOOGLE_APPLICATION_CREDENTIALS",
"client_secret",
"client_id",
"Data Source=",
"User ID=",
"Password;",
"Integrated Security=False",
"-----BEGIN",
"BEGIN RSA PRIVATE",
"BEGIN PRIVATE KEY",
"BEGIN CERTIFICATE",
"ssh-rsa ",
"id_rsa",
"eyJhbGciOi", ];
const DOS_E_LFANEW_OFFSET: usize = 0x3C;
const COFF_NUMBER_OF_SECTIONS_OFFSET: usize = 6;
const COFF_SIZE_OF_OPTIONAL_HEADER_OFFSET: usize = 0x14;
const OPT_MAGIC_PE32: u16 = 0x010B;
const OPT_MAGIC_PE32PLUS: u16 = 0x020B;
const PE32_DATA_DIR_OFFSET: usize = 96;
const PE32PLUS_DATA_DIR_OFFSET: usize = 112;
const SECTION_ENTRY_SIZE: usize = 40;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CoffHeader {
pub number_of_sections: u16,
pub size_of_optional_header: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OptionalHeaderMagic {
Pe32,
Pe32Plus,
}
impl OptionalHeaderMagic {
#[must_use]
pub fn data_directory_offset(self) -> usize {
match self {
OptionalHeaderMagic::Pe32 => PE32_DATA_DIR_OFFSET,
OptionalHeaderMagic::Pe32Plus => PE32PLUS_DATA_DIR_OFFSET,
}
}
}
fn read_u16_le(bytes: &[u8], offset: usize) -> Option<u16> {
let end = offset.checked_add(2)?;
let slice = bytes.get(offset..end)?;
Some(u16::from_le_bytes(slice.try_into().ok()?))
}
fn read_u32_le(bytes: &[u8], offset: usize) -> Option<u32> {
let end = offset.checked_add(4)?;
let slice = bytes.get(offset..end)?;
Some(u32::from_le_bytes(slice.try_into().ok()?))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionEntry {
pub name: Vec<u8>,
pub virtual_size: u32,
pub virtual_address: u32,
}
#[must_use]
pub fn parse_dos_header(bytes: &[u8]) -> Option<u32> {
if bytes.get(..2)? != MZ_MAGIC {
return None;
}
read_u32_le(bytes, DOS_E_LFANEW_OFFSET)
}
#[must_use]
pub fn parse_coff_header(bytes: &[u8]) -> Option<CoffHeader> {
if bytes.get(..4)? != PE_SIGNATURE {
return None;
}
Some(CoffHeader {
number_of_sections: read_u16_le(bytes, COFF_NUMBER_OF_SECTIONS_OFFSET)?,
size_of_optional_header: read_u16_le(bytes, COFF_SIZE_OF_OPTIONAL_HEADER_OFFSET)?,
})
}
#[must_use]
pub fn parse_optional_header_magic(bytes: &[u8]) -> Option<OptionalHeaderMagic> {
match read_u16_le(bytes, 0)? {
OPT_MAGIC_PE32 => Some(OptionalHeaderMagic::Pe32),
OPT_MAGIC_PE32PLUS => Some(OptionalHeaderMagic::Pe32Plus),
_ => None,
}
}
#[must_use]
pub fn parse_section_entry(bytes: &[u8]) -> Option<SectionEntry> {
let entry = bytes.get(..SECTION_ENTRY_SIZE)?;
let raw_name = entry.get(..8)?;
let name_len = raw_name
.iter()
.position(|&b| b == 0)
.unwrap_or(raw_name.len());
Some(SectionEntry {
name: raw_name.get(..name_len)?.to_vec(),
virtual_size: read_u32_le(entry, 8)?,
virtual_address: read_u32_le(entry, 0xC)?,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn anti_debug_import_names_not_empty() {
assert!(!ANTI_DEBUG_IMPORT_NAMES.is_empty());
}
#[test]
fn anti_debug_contains_core_apis() {
assert!(ANTI_DEBUG_IMPORT_NAMES.contains(&"IsDebuggerPresent"));
assert!(ANTI_DEBUG_IMPORT_NAMES.contains(&"CheckRemoteDebuggerPresent"));
assert!(ANTI_DEBUG_IMPORT_NAMES.contains(&"NtQueryInformationProcess"));
assert!(ANTI_DEBUG_IMPORT_NAMES.contains(&"QueryPerformanceCounter"));
}
#[test]
fn anti_debug_no_duplicates() {
let mut sorted = ANTI_DEBUG_IMPORT_NAMES.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), ANTI_DEBUG_IMPORT_NAMES.len(), "no duplicates");
}
#[test]
fn process_hollowing_apis_not_empty() {
assert!(!PROCESS_HOLLOWING_APIS.is_empty());
}
#[test]
fn process_hollowing_contains_unmap_and_resume() {
assert!(PROCESS_HOLLOWING_APIS.contains(&"NtUnmapViewOfSection"));
assert!(PROCESS_HOLLOWING_APIS.contains(&"WriteProcessMemory"));
assert!(PROCESS_HOLLOWING_APIS.contains(&"SetThreadContext"));
assert!(PROCESS_HOLLOWING_APIS.contains(&"ResumeThread"));
}
#[test]
fn ransomware_patterns_not_empty() {
assert!(!RANSOMWARE_STRING_PATTERNS.is_empty());
}
#[test]
fn ransomware_patterns_cover_extensions_and_payment() {
assert!(RANSOMWARE_STRING_PATTERNS.contains(&".encrypted"));
assert!(RANSOMWARE_STRING_PATTERNS.contains(&".wncry"));
assert!(RANSOMWARE_STRING_PATTERNS.contains(&"bitcoin"));
assert!(RANSOMWARE_STRING_PATTERNS.contains(&".onion"));
assert!(RANSOMWARE_STRING_PATTERNS.contains(&"HOW_TO_DECRYPT"));
}
#[test]
fn persistence_patterns_not_empty() {
assert!(!PERSISTENCE_STRING_PATTERNS.is_empty());
}
#[test]
fn persistence_patterns_cover_key_techniques() {
assert!(PERSISTENCE_STRING_PATTERNS.contains(&"CurrentVersion\\Run"));
assert!(PERSISTENCE_STRING_PATTERNS.contains(&"CurrentControlSet\\Services"));
assert!(PERSISTENCE_STRING_PATTERNS.contains(&"AppInit_DLLs"));
assert!(PERSISTENCE_STRING_PATTERNS.contains(&"__EventFilter"));
}
#[test]
fn network_c2_patterns_not_empty() {
assert!(!NETWORK_C2_PATTERNS.is_empty());
}
#[test]
fn network_c2_patterns_cover_http_and_c2() {
assert!(NETWORK_C2_PATTERNS.contains(&"http://"));
assert!(NETWORK_C2_PATTERNS.contains(&"https://"));
assert!(NETWORK_C2_PATTERNS.contains(&".onion"));
assert!(NETWORK_C2_PATTERNS.contains(&"User-Agent:"));
assert!(NETWORK_C2_PATTERNS.contains(&"meterpreter"));
}
#[test]
fn av_exclusion_covers_malwarebytes() {
assert!(
AV_EXCLUSION_PATH_FRAGMENTS.contains(&"Malwarebytes"),
"QWCrypt explicitly excludes Malwarebytes from its AV kill list"
);
}
#[test]
fn av_exclusion_covers_vipre() {
assert!(
AV_EXCLUSION_PATH_FRAGMENTS.contains(&"VIPRE"),
"QWCrypt explicitly excludes VIPRE Security"
);
}
#[test]
fn av_exclusion_covers_sentinelone() {
assert!(
AV_EXCLUSION_PATH_FRAGMENTS.contains(&"SentinelOne"),
"QWCrypt explicitly excludes SentinelOne endpoint protection"
);
}
#[test]
fn network_c2_patterns_cover_cloudflare_workers() {
assert!(
NETWORK_C2_PATTERNS.contains(&"workers.dev"),
"workers.dev is the Cloudflare Workers C2 infrastructure abused by RedCurl/QWCrypt"
);
}
#[test]
fn ransomware_patterns_cover_qwcrypt_extension() {
assert!(
RANSOMWARE_STRING_PATTERNS.contains(&".qwCrypt"),
".qwCrypt is the file extension appended by QWCrypt ransomware"
);
}
#[test]
fn credential_patterns_not_empty() {
assert!(!CREDENTIAL_PATTERNS.is_empty());
}
#[test]
fn credential_patterns_cover_passwords_tokens_and_pem() {
assert!(CREDENTIAL_PATTERNS.contains(&"password="));
assert!(CREDENTIAL_PATTERNS.contains(&"api_key="));
assert!(CREDENTIAL_PATTERNS.contains(&"AKIA"));
assert!(CREDENTIAL_PATTERNS.contains(&"-----BEGIN"));
}
fn dos_header(e_lfanew: u32) -> Vec<u8> {
let mut buf = vec![0u8; 0x40];
buf[0..2].copy_from_slice(&MZ_MAGIC);
buf[0x3C..0x40].copy_from_slice(&e_lfanew.to_le_bytes());
buf
}
fn coff_header(number_of_sections: u16, size_of_optional_header: u16) -> Vec<u8> {
let mut buf = vec![0u8; 24];
buf[0..4].copy_from_slice(&PE_SIGNATURE);
buf[6..8].copy_from_slice(&number_of_sections.to_le_bytes());
buf[0x14..0x16].copy_from_slice(&size_of_optional_header.to_le_bytes());
buf
}
#[test]
fn dos_header_returns_e_lfanew() {
assert_eq!(parse_dos_header(&dos_header(0x80)), Some(0x80));
assert_eq!(parse_dos_header(&dos_header(0xF0)), Some(0xF0));
}
#[test]
fn dos_header_rejects_missing_mz_magic() {
let mut buf = dos_header(0x80);
buf[0] = b'X';
assert_eq!(parse_dos_header(&buf), None);
}
#[test]
fn dos_header_rejects_short_buffer() {
assert_eq!(parse_dos_header(b"MZ"), None);
assert_eq!(parse_dos_header(&[0u8; 0x3F]), None);
assert_eq!(parse_dos_header(&[]), None);
}
#[test]
fn coff_header_parses_section_count_and_opt_size() {
let parsed = parse_coff_header(&coff_header(7, 0xF0)).expect("valid COFF");
assert_eq!(parsed.number_of_sections, 7);
assert_eq!(parsed.size_of_optional_header, 0xF0);
}
#[test]
fn coff_header_rejects_bad_signature() {
let mut buf = coff_header(2, 0xF0);
buf[1] = b'X'; assert_eq!(parse_coff_header(&buf), None);
}
#[test]
fn coff_header_rejects_short_buffer() {
assert_eq!(parse_coff_header(&PE_SIGNATURE), None);
assert_eq!(parse_coff_header(&[]), None);
}
#[test]
fn optional_header_magic_classifies_pe32_and_pe32plus() {
let pe32 = 0x010Bu16.to_le_bytes();
let pe32plus = 0x020Bu16.to_le_bytes();
assert_eq!(
parse_optional_header_magic(&pe32),
Some(OptionalHeaderMagic::Pe32)
);
assert_eq!(
parse_optional_header_magic(&pe32plus),
Some(OptionalHeaderMagic::Pe32Plus)
);
}
#[test]
fn optional_header_magic_rejects_unknown_and_short() {
assert_eq!(parse_optional_header_magic(&0x0000u16.to_le_bytes()), None);
assert_eq!(parse_optional_header_magic(&[0x0B]), None);
assert_eq!(parse_optional_header_magic(&[]), None);
}
#[test]
fn data_directory_offset_matches_spec() {
assert_eq!(OptionalHeaderMagic::Pe32.data_directory_offset(), 96);
assert_eq!(OptionalHeaderMagic::Pe32Plus.data_directory_offset(), 112);
}
#[test]
fn section_entry_parses_name_vsize_and_vaddr() {
let mut buf = vec![0u8; 40];
buf[0..5].copy_from_slice(b".text");
buf[8..12].copy_from_slice(&0x1234u32.to_le_bytes()); buf[0xC..0x10].copy_from_slice(&0x1000u32.to_le_bytes()); let entry = parse_section_entry(&buf).expect("valid entry");
assert_eq!(entry.name, b".text");
assert_eq!(entry.virtual_size, 0x1234);
assert_eq!(entry.virtual_address, 0x1000);
}
#[test]
fn section_entry_strips_nul_padding_only() {
let mut buf = vec![0u8; 40];
buf[0..8].copy_from_slice(b".rdata12");
let entry = parse_section_entry(&buf).expect("valid entry");
assert_eq!(entry.name, b".rdata12");
}
#[test]
fn section_entry_rejects_short_buffer() {
assert_eq!(parse_section_entry(&[0u8; 39]), None);
assert_eq!(parse_section_entry(&[]), None);
}
}