oletools_rs 0.1.0

Rust port of oletools — analysis tools for Microsoft Office files (VBA macros, DDE, OLE objects, RTF exploits)
Documentation
//! VBA keyword tables for malware detection.
//!
//! Ported from oletools/olevba.py — contains AutoExec triggers,
//! suspicious keywords, and IOC patterns.

use std::sync::LazyLock;

use regex::Regex;

/// Type of finding when scanning VBA code.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FindingType {
    /// Auto-execution trigger (macro runs automatically).
    AutoExec,
    /// Suspicious keyword (potentially malicious API call).
    Suspicious,
    /// Indicator of Compromise (URL, IP, executable name).
    Ioc,
    /// Hex-encoded string (may hide payload).
    HexString,
    /// Base64-encoded string (may hide payload).
    Base64String,
    /// Dridex-style string obfuscation.
    Dridex,
    /// VBA stomping (P-code doesn't match source).
    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"),
        }
    }
}

/// A single keyword entry: (keyword_or_pattern, description).
pub struct KeywordEntry {
    pub keyword: &'static str,
    pub description: &'static str,
}

/// AutoExec keyword entries — triggers that cause macros to run automatically.
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" },
];

/// AutoExec regex patterns.
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"),
    ]
});

/// Suspicious keyword entries — potentially malicious API calls and patterns.
pub static SUSPICIOUS_KEYWORDS: &[KeywordEntry] = &[
    // Shell & execution
    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" },

    // Object creation
    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" },

    // File system operations
    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" },

    // Network / download
    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" },

    // Process manipulation
    KeywordEntry { keyword: "Environ", description: "May read environment variables" },
    KeywordEntry { keyword: "PowerShell", description: "May run PowerShell commands" },
    KeywordEntry { keyword: "AppActivate", description: "May activate another application" },

    // Registry
    KeywordEntry { keyword: "RegRead", description: "May read registry" },
    KeywordEntry { keyword: "RegWrite", description: "May write to registry" },
    KeywordEntry { keyword: "RegDelete", description: "May delete registry key" },

    // Obfuscation / evasion
    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" },

    // DDE
    KeywordEntry { keyword: "DDEInitiate", description: "May start DDE link" },
    KeywordEntry { keyword: "DDEExecute", description: "May execute DDE command" },

    // Other suspicious
    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" },

    // Macro settings
    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" },

    // Persistence
    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" },
];

/// Suspicious regex patterns.
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());
    }
}