use std::sync::LazyLock;
use regex::Regex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FindingType {
AutoExec,
Suspicious,
Ioc,
HexString,
Base64String,
Dridex,
VbaStomp,
}
impl std::fmt::Display for FindingType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FindingType::AutoExec => write!(f, "AutoExec"),
FindingType::Suspicious => write!(f, "Suspicious"),
FindingType::Ioc => write!(f, "IOC"),
FindingType::HexString => write!(f, "Hex String"),
FindingType::Base64String => write!(f, "Base64 String"),
FindingType::Dridex => write!(f, "Dridex"),
FindingType::VbaStomp => write!(f, "VBA Stomping"),
}
}
}
pub struct KeywordEntry {
pub keyword: &'static str,
pub description: &'static str,
}
pub static AUTOEXEC_KEYWORDS: &[KeywordEntry] = &[
KeywordEntry { keyword: "AutoExec", description: "Runs when Word starts" },
KeywordEntry { keyword: "AutoOpen", description: "Runs when document is opened" },
KeywordEntry { keyword: "AutoClose", description: "Runs when document is closed" },
KeywordEntry { keyword: "AutoExit", description: "Runs when Word exits" },
KeywordEntry { keyword: "AutoNew", description: "Runs when new document is created" },
KeywordEntry { keyword: "Document_Open", description: "Runs when document is opened" },
KeywordEntry { keyword: "Document_Close", description: "Runs when document is closed" },
KeywordEntry { keyword: "Document_BeforeClose", description: "Runs before document is closed" },
KeywordEntry { keyword: "Document_Change", description: "Runs when document changes" },
KeywordEntry { keyword: "Document_New", description: "Runs when new document is created" },
KeywordEntry { keyword: "Document_BeforePrint", description: "Runs before printing" },
KeywordEntry { keyword: "Document_BeforeSave", description: "Runs before saving" },
KeywordEntry { keyword: "DocumentOpen", description: "Runs when document is opened" },
KeywordEntry { keyword: "DocumentBeforeClose", description: "Runs before document is closed" },
KeywordEntry { keyword: "DocumentChange", description: "Runs when document changes" },
KeywordEntry { keyword: "NewDocument", description: "Runs when new document is created" },
KeywordEntry { keyword: "Workbook_Open", description: "Runs when workbook is opened" },
KeywordEntry { keyword: "Workbook_Activate", description: "Runs when workbook is activated" },
KeywordEntry { keyword: "Workbook_BeforeClose", description: "Runs before workbook is closed" },
KeywordEntry { keyword: "Workbook_BeforeSave", description: "Runs before saving" },
KeywordEntry { keyword: "Workbook_Deactivate", description: "Runs when workbook is deactivated" },
KeywordEntry { keyword: "Workbook_AddinInstall", description: "Runs when add-in is installed" },
KeywordEntry { keyword: "Worksheet_Activate", description: "Runs when worksheet is activated" },
KeywordEntry { keyword: "Worksheet_Calculate", description: "Runs when worksheet is calculated" },
KeywordEntry { keyword: "Worksheet_Change", description: "Runs when worksheet changes" },
KeywordEntry { keyword: "Worksheet_FollowHyperlink", description: "Runs when hyperlink is clicked" },
KeywordEntry { keyword: "Worksheet_SelectionChange", description: "Runs when selection changes" },
KeywordEntry { keyword: "Worksheet_BeforeDoubleClick", description: "Runs before double-click" },
KeywordEntry { keyword: "Worksheet_BeforeRightClick", description: "Runs before right-click" },
KeywordEntry { keyword: "Sheet_Activate", description: "Runs when sheet is activated" },
KeywordEntry { keyword: "UserForm_Activate", description: "Runs when UserForm is activated" },
KeywordEntry { keyword: "UserForm_Initialize", description: "Runs when UserForm initializes" },
];
pub static AUTOEXEC_REGEX: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
vec![
(Regex::new(r"(?i)\w+_Painted\b").unwrap(), "Runs on paint event"),
(Regex::new(r"(?i)\w+_Click\b").unwrap(), "Runs on click event"),
(Regex::new(r"(?i)\w+_Change\b").unwrap(), "Runs on change event"),
(Regex::new(r"(?i)\w+_GotFocus\b").unwrap(), "Runs on focus event"),
(Regex::new(r"(?i)\w+_LostFocus\b").unwrap(), "Runs on lost focus event"),
(Regex::new(r"(?i)\w+_Resize\b").unwrap(), "Runs on resize event"),
(Regex::new(r"(?i)\w+_Layout\b").unwrap(), "Runs on layout event"),
(Regex::new(r"(?i)\w+_MouseMove\b").unwrap(), "Runs on mouse move"),
(Regex::new(r"(?i)\w+_Timer\b").unwrap(), "Runs on timer event"),
(Regex::new(r"(?i)Sub\s+main\b").unwrap(), "Main subroutine"),
(Regex::new(r"(?i)\w+_ContentControlOnEnter\b").unwrap(), "Runs on content control"),
]
});
pub static SUSPICIOUS_KEYWORDS: &[KeywordEntry] = &[
KeywordEntry { keyword: "Shell", description: "May run an executable" },
KeywordEntry { keyword: "vbNormalFocus", description: "May run an executable" },
KeywordEntry { keyword: "vbHide", description: "May run an executable in hidden window" },
KeywordEntry { keyword: "vbMinimizedFocus", description: "May run an executable minimized" },
KeywordEntry { keyword: "vbMaximizedFocus", description: "May run an executable maximized" },
KeywordEntry { keyword: "vbNormalNoFocus", description: "May run an executable" },
KeywordEntry { keyword: "vbMinimizedNoFocus", description: "May run an executable minimized" },
KeywordEntry { keyword: "WScript.Shell", description: "May run an executable" },
KeywordEntry { keyword: "Wscript.Run", description: "May run an executable" },
KeywordEntry { keyword: "ShellExecute", description: "May run an executable" },
KeywordEntry { keyword: "CreateObject", description: "May create OLE object" },
KeywordEntry { keyword: "GetObject", description: "May get reference to OLE object" },
KeywordEntry { keyword: "CallByName", description: "May call any method dynamically" },
KeywordEntry { keyword: "FileCopy", description: "May copy a file" },
KeywordEntry { keyword: "CopyFile", description: "May copy a file" },
KeywordEntry { keyword: "Kill", description: "May delete a file" },
KeywordEntry { keyword: "CreateTextFile", description: "May create a text file" },
KeywordEntry { keyword: "Open", description: "May open a file" },
KeywordEntry { keyword: "Write", description: "May write to a file" },
KeywordEntry { keyword: "Put", description: "May write to a file" },
KeywordEntry { keyword: "Output", description: "May write to a file" },
KeywordEntry { keyword: "Print #", description: "May write to a file" },
KeywordEntry { keyword: "Binary", description: "May read/write binary file" },
KeywordEntry { keyword: "FileSaveAs", description: "May save file to disk" },
KeywordEntry { keyword: "MkDir", description: "May create directory" },
KeywordEntry { keyword: "SaveToFile", description: "May save content to file" },
KeywordEntry { keyword: "Scripting.FileSystemObject", description: "May access file system" },
KeywordEntry { keyword: "MSXML2.XMLHTTP", description: "May download content from URL" },
KeywordEntry { keyword: "Microsoft.XMLHTTP", description: "May download content from URL" },
KeywordEntry { keyword: "MSXML2.ServerXMLHTTP", description: "May download content from URL" },
KeywordEntry { keyword: "InternetExplorer.Application", description: "May open URL in browser" },
KeywordEntry { keyword: "URLDownloadToFile", description: "May download file from URL" },
KeywordEntry { keyword: "ADODB.Stream", description: "May read/write binary data" },
KeywordEntry { keyword: "Net.WebClient", description: "May download content (.NET)" },
KeywordEntry { keyword: "SendKeys", description: "May send keystrokes" },
KeywordEntry { keyword: "Environ", description: "May read environment variables" },
KeywordEntry { keyword: "PowerShell", description: "May run PowerShell commands" },
KeywordEntry { keyword: "AppActivate", description: "May activate another application" },
KeywordEntry { keyword: "RegRead", description: "May read registry" },
KeywordEntry { keyword: "RegWrite", description: "May write to registry" },
KeywordEntry { keyword: "RegDelete", description: "May delete registry key" },
KeywordEntry { keyword: "StrReverse", description: "May reverse strings to obfuscate" },
KeywordEntry { keyword: "Chr", description: "May build strings char by char" },
KeywordEntry { keyword: "ChrB", description: "May build strings byte by byte" },
KeywordEntry { keyword: "ChrW", description: "May build strings from Unicode" },
KeywordEntry { keyword: "Asc", description: "May get character code" },
KeywordEntry { keyword: "Replace", description: "May replace strings for obfuscation" },
KeywordEntry { keyword: "Mid", description: "May extract substring" },
KeywordEntry { keyword: "Left", description: "May extract left substring" },
KeywordEntry { keyword: "Right", description: "May extract right substring" },
KeywordEntry { keyword: "Hex", description: "May convert to hex string" },
KeywordEntry { keyword: "CLng", description: "May convert to Long integer" },
KeywordEntry { keyword: "CByte", description: "May convert to Byte" },
KeywordEntry { keyword: "Xor", description: "May use XOR for encoding/decoding" },
KeywordEntry { keyword: "DDEInitiate", description: "May start DDE link" },
KeywordEntry { keyword: "DDEExecute", description: "May execute DDE command" },
KeywordEntry { keyword: "Lib", description: "May call external DLL function" },
KeywordEntry { keyword: "Declare", description: "May declare external DLL function" },
KeywordEntry { keyword: "Private Declare", description: "May declare external DLL function" },
KeywordEntry { keyword: "VirtualAlloc", description: "May allocate memory (shellcode)" },
KeywordEntry { keyword: "RtlMoveMemory", description: "May copy memory (shellcode)" },
KeywordEntry { keyword: "CreateThread", description: "May create thread (shellcode)" },
KeywordEntry { keyword: "EnumSystemLanguageGroupsA", description: "May execute shellcode via callback" },
KeywordEntry { keyword: "NtAllocateVirtualMemory", description: "May allocate memory (shellcode)" },
KeywordEntry { keyword: "WriteProcessMemory", description: "May write to process memory" },
KeywordEntry { keyword: "CreateRemoteThread", description: "May inject code into process" },
KeywordEntry { keyword: "VirtualProtect", description: "May change memory protection" },
KeywordEntry { keyword: "Application.MacroOptions", description: "May hide macro from menus" },
KeywordEntry { keyword: "Application.EnableEvents", description: "May disable event handling" },
KeywordEntry { keyword: "Application.DisplayAlerts", description: "May suppress alerts" },
KeywordEntry { keyword: "Application.ScreenUpdating", description: "May suppress screen updates" },
KeywordEntry { keyword: "ThisDocument.SaveAs", description: "May save document with macros" },
KeywordEntry { keyword: "ActiveDocument.SaveAs", description: "May save document with macros" },
KeywordEntry { keyword: "StartupPath", description: "May modify startup folder" },
KeywordEntry { keyword: "NormalTemplate", description: "May modify Normal.dotm" },
KeywordEntry { keyword: "AddFromFile", description: "May import module from file" },
];
pub static SUSPICIOUS_REGEX: LazyLock<Vec<(Regex, &'static str)>> = LazyLock::new(|| {
vec![
(Regex::new(r"(?i)\bShell\s*\(").unwrap(), "May run an executable (Shell function call)"),
(Regex::new(r"(?i)\bCreateObject\s*\(").unwrap(), "May create OLE object"),
(Regex::new(r"(?i)\bGetObject\s*\(").unwrap(), "May get reference to OLE object"),
(Regex::new(r"(?i)\bDeclare\s+(PtrSafe\s+)?(?:Sub|Function)\b").unwrap(), "May declare external DLL function"),
(Regex::new(r#"(?i)\bLib\s+""#).unwrap(), "May call external DLL function"),
(Regex::new(r"(?i)\.Run\s").unwrap(), "May run a command"),
(Regex::new(r"(?i)\.Exec\s*\(").unwrap(), "May execute a command"),
(Regex::new(r"(?i)\bOpen\s+.*\bFor\s+(Output|Append|Binary)\b").unwrap(), "May write to a file"),
(Regex::new(r#"(?i)(?:^|\s)(?:cmd|cmd\.exe|command|powershell|powershell\.exe|mshta|cscript|wscript)(?:\s|$|")"#).unwrap(), "May execute system command"),
(Regex::new(r"(?i)(?:HKCU|HKLM|HKEY_)").unwrap(), "May access Windows registry"),
]
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autoexec_keywords_not_empty() {
assert!(AUTOEXEC_KEYWORDS.len() >= 30);
}
#[test]
fn test_suspicious_keywords_not_empty() {
assert!(SUSPICIOUS_KEYWORDS.len() >= 60);
}
#[test]
fn test_finding_type_display() {
assert_eq!(FindingType::AutoExec.to_string(), "AutoExec");
assert_eq!(FindingType::Suspicious.to_string(), "Suspicious");
assert_eq!(FindingType::Ioc.to_string(), "IOC");
}
#[test]
fn test_autoexec_regex_matches() {
let code = "Sub Button1_Click()";
let matches: Vec<_> = AUTOEXEC_REGEX
.iter()
.filter(|(re, _)| re.is_match(code))
.collect();
assert!(!matches.is_empty());
}
#[test]
fn test_suspicious_regex_matches() {
let code = r#"CreateObject("WScript.Shell")"#;
let matches: Vec<_> = SUSPICIOUS_REGEX
.iter()
.filter(|(re, _)| re.is_match(code))
.collect();
assert!(!matches.is_empty());
}
}