oletools_rs 0.1.0

Rust port of oletools — analysis tools for Microsoft Office files (VBA macros, DDE, OLE objects, RTF exploits)
Documentation
//! CVE detection for OLE objects embedded in RTF documents.
//!
//! Detects known exploits based on class name and file extension:
//! - CVE-2017-0199: OLE2Link class (HTA handler exploit)
//! - CVE-2017-11882: Equation Editor class (buffer overflow)

use std::sync::LazyLock;

use regex::Regex;

use crate::oleobj::ole_object::OleObject;

/// A detected CVE in an OLE object.
#[derive(Debug, Clone)]
pub struct CveDetection {
    /// CVE identifier.
    pub cve_id: String,
    /// Description of the vulnerability.
    pub description: String,
    /// Class name that triggered the detection.
    pub class_name: String,
}

/// Known dangerous file extensions (executable types).
static RE_EXECUTABLE_EXT: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"(?i)\.(bat|cmd|com|cpl|dll|exe|hta|inf|js|jse|lnk|msi|msp|mst|ocx|pif|ps1|reg|scr|sct|vb|vbe|vbs|wsc|wsf|wsh)$").unwrap()
});

/// Detect CVEs for a parsed OLE object.
pub fn detect_cves(obj: &OleObject) -> Vec<CveDetection> {
    let mut detections = Vec::new();

    // CVE-2017-0199: OLE2Link class
    if obj.is_ole2link() {
        detections.push(CveDetection {
            cve_id: "CVE-2017-0199".to_string(),
            description: "OLE2Link object detected — may exploit HTA handler".to_string(),
            class_name: obj.class_name.clone(),
        });
    }

    // CVE-2017-11882: Equation Editor
    if obj.is_equation() {
        detections.push(CveDetection {
            cve_id: "CVE-2017-11882".to_string(),
            description: "Equation Editor object detected — potential buffer overflow"
                .to_string(),
            class_name: obj.class_name.clone(),
        });
    }

    detections
}

/// Check if a filename has a dangerous executable extension.
pub fn is_executable_extension(filename: &str) -> bool {
    RE_EXECUTABLE_EXT.is_match(filename)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_ole_object(class_name: &str) -> OleObject {
        OleObject {
            ole_version: 0x501,
            format_id: 2,
            class_name: class_name.to_string(),
            topic_name: String::new(),
            item_name: String::new(),
            data_size: 0,
            data: Vec::new(),
        }
    }

    #[test]
    fn test_detect_cve_2017_0199() {
        let obj = make_ole_object("OLE2Link");
        let cves = detect_cves(&obj);
        assert_eq!(cves.len(), 1);
        assert_eq!(cves[0].cve_id, "CVE-2017-0199");
    }

    #[test]
    fn test_detect_cve_2017_11882() {
        let obj = make_ole_object("Equation.3");
        let cves = detect_cves(&obj);
        assert_eq!(cves.len(), 1);
        assert_eq!(cves[0].cve_id, "CVE-2017-11882");
    }

    #[test]
    fn test_detect_equation_case_insensitive() {
        let obj = make_ole_object("EQUATION.3");
        let cves = detect_cves(&obj);
        assert_eq!(cves.len(), 1);
        assert_eq!(cves[0].cve_id, "CVE-2017-11882");
    }

    #[test]
    fn test_no_cve_clean_object() {
        let obj = make_ole_object("Package");
        let cves = detect_cves(&obj);
        assert!(cves.is_empty());
    }

    #[test]
    fn test_executable_extensions() {
        assert!(is_executable_extension("payload.exe"));
        assert!(is_executable_extension("script.vbs"));
        assert!(is_executable_extension("test.HTA"));
        assert!(is_executable_extension("file.PS1"));
        assert!(is_executable_extension("dropper.SCR"));
        assert!(!is_executable_extension("document.docx"));
        assert!(!is_executable_extension("image.png"));
        assert!(!is_executable_extension("noext"));
    }

    #[test]
    fn test_executable_bat_cmd() {
        assert!(is_executable_extension("run.bat"));
        assert!(is_executable_extension("run.cmd"));
        assert!(is_executable_extension("lib.dll"));
    }
}