Skip to main content
Dr.Semu’s detection engine analyzes the collected behavior data using community-driven rules written in Lua or Python. Rules examine both static file properties and dynamic runtime behavior to classify samples.

Detection philosophy

From README.md:25-29:
After terminating the process, based on Dr.Semu rules we receive if the executable is detected as malware or not. Dr.Semu Rules/Detections
Dr.Semu focuses on behavior-based detection rather than signatures:
  • Behavioral patterns - What the malware does
  • API call sequences - How the malware interacts with Windows
  • File operations - Where the malware writes files
  • Static signatures - Byte patterns (not the primary focus)
This approach is effective against:
  • Polymorphic malware (changes signature each time)
  • Packed malware (code is encrypted until runtime)
  • Living-off-the-land attacks (uses legitimate system tools)

Rule languages

Lua and Python support

From README.md:31-33:
They are written in Python or LUA (located under dr_rules) and use dynamic information from the interception and static information about the sample. It’s trivial to add support of other languages.
Advantages:
  • Lightweight and fast
  • Easy to sandbox
  • Simple syntax for quick rules
  • Low memory footprint
Best for:
  • Quick pattern matching
  • Simple detection logic
  • Community contributions
Learn more →

Rule structure

Basic rule template

Every rule must implement a check() function:
function check(report_directory)
    local status = "CLEAN"
    
    -- Analysis logic here
    
    return status  -- or detection name like "Win32.Ransomware"
end

Return values

Rules return a verdict string:
  • "CLEAN" or b"CLEAN" - No malicious behavior detected
  • "DetectionName" - Malware detected, classification provided
Examples:
  • "Win32.Ransomware.WannaCry"
  • "Trojan.Downloader.Generic"
  • "Backdoor.RemoteAccess"

Data sources

Rules have access to three types of data:

1. Starter information

Contains metadata about the analysis session:
{
  "image_path": "C:\\samples\\malware.exe",
  "starter_pid": 4512,
  "explorer_pid": 3204,
  "sha_256": "a1b2c3d4e5f6..."
}
Generated by LauncherCLI.cpp:406-419:
starter_json["image_path"] = image_path_ascii.c_str();
starter_json["starter_pid"] = starter_proc_id;
starter_json["explorer_pid"] = explorer_pid;
starter_json["sha_256"] = file_sha2_ascii;

2. Dynamic behavior

JSON logs of all intercepted system calls, one file per process: Filename: {pid}.json Contents: Array of system call objects
[
  {
    "NtCreateFile": {
      "timestamp": "2024-03-03T14:32:10.123",
      "before": { "file_path": "C:\\ransom.txt" },
      "success": true
    }
  },
  {
    "NtWriteVirtualMemory": {
      "before": { "process_id": 5678 },
      "after": { "bytes_written": 4096 }
    }
  }
]

3. Static analysis

PE file analysis including imports, exports, sections, and resources: Filename: {sha256}.json Contents: Static file properties
{
  "generic": {
    "is_x86": true,
    "entry_point": "0x1000",
    "image_base": "0x400000"
  },
  "imports": ["kernel32.dll", "ws2_32.dll"],
  "sections": [".text", ".data", ".rsrc"]
}

Detection workflow

1

Sample execution completes

The target process and all child processes terminate. JSON reports are written to the report directory.
2

Detection engine launches

run_detections.exe is started with the report directory path (LauncherCLI.cpp:438-466).
3

Lua rules execute

All .lua files in dr_rules/ are loaded and executed:
// run_detections.cpp:51
auto verdict_string = lua_scan::lua_rules_verdict(
    report_directory, rules_directory, current_location, report_slot);
4

Python rules execute

If no detection from Lua, Python rules run:
// run_detections.cpp:54-58
if (verdict_string.empty() || verdict_string == "CLEAN") {
    verdict_string = python_rules_verdict(rules_directory.string(), 
                                           report_directory_ascii);
}
5

Verdict returned

The first non-CLEAN verdict is returned to the launcher:
// run_detections.cpp:60-66
if (verdict_string.empty()) {
    verdict_string = "NO DETECTIONS";
}

Example detections

WannaCry kill switch detection

From wannacry_url.lua:7-21:
function check(report_directory)
    local first_dynamic = utils.get_first_process_json(report_directory)
    
    if first_dynamic ~= nil then
        for index, win_func in pairs(first_dynamic) do
            if win_func.InternetOpenUrlW then
                local url = win_func.InternetOpenUrlW.before.url
                if url:find("iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com") then
                    return "Ransomware.WannaCry"
                end
            end
        end
    end
    
    return "CLEAN"
end
Detection logic:
  1. Load dynamic behavior for the first process
  2. Iterate through all system calls
  3. Find InternetOpenUrlW calls
  4. Check if URL matches WannaCry kill switch domain
  5. Return classification if matched

EICAR test detection

From dr_semu_eicar.py:8-23:
def check(report_directory):
    image_path, pid, sha_256 = dr_semu_utils.get_starter_details(report_directory)
    dynamic_info = dr_semu_utils.get_json_from_file(
        report_directory + b"\\" + str(pid).encode() + b".json")
    
    verdict = b"CLEAN"
    
    for win_func in dynamic_info:
        if "NtCreateUserProcess" in win_func:
            image_path = win_func["NtCreateUserProcess"]["before"]["image_path"]
            if image_path.lower().endswith("drsemu_eicar.exe"):
                return b"Win32.EICAR.Dr"
    
    return verdict
Detection logic:
  1. Extract starter details (path, PID, hash)
  2. Load dynamic behavior JSON
  3. Look for process creation calls
  4. Check if created process name contains “eicar”
  5. Return detection if matched

Common detection patterns

Ransomware indicators

-- File encryption
if win_func.NtWriteFile then
    local path = win_func.NtWriteFile.before.file_path
    if path:find("%.encrypted$") or path:find("%.locked$") then
        return "Ransomware.Generic"
    end
end

-- Mass file operations
local file_write_count = 0
for _, func in pairs(dynamic) do
    if func.NtWriteFile then
        file_write_count = file_write_count + 1
    end
end
if file_write_count > 100 then
    return "Ransomware.Suspected"
end

Credential theft

-- LSASS memory access
if win_func.NtOpenProcess then
    local target_pid = win_func.NtOpenProcess.before.process_id
    -- Check if target is lsass.exe
    if is_lsass_pid(target_pid) then
        return "Trojan.CredentialThief"
    end
end

-- SAM registry access
if win_func.NtOpenKey then
    local key = win_func.NtOpenKey.before.key_path
    if key:find("SAM\\SAM\\Domains\\Account") then
        return "Tool.PasswordDumper"
    end
end

Process injection

-- Remote thread creation
if win_func.NtWriteVirtualMemory then
    local target_pid = win_func.NtWriteVirtualMemory.before.process_id
    if target_pid ~= current_pid then
        return "Trojan.Injector"
    end
end

-- Process hollowing
if win_func.NtSetContextThread and win_func.NtWriteVirtualMemory then
    return "Trojan.ProcessHollowing"
end

Persistence mechanisms

-- Run key modification
if win_func.NtSetValueKey then
    local key = win_func.NtSetValueKey.before.key_path
    if key:find("CurrentVersion\\Run") then
        return "Trojan.Persistence"
    end
end

-- Scheduled task creation
if win_func.NtCreateUserProcess then
    local path = win_func.NtCreateUserProcess.before.image_path
    if path:find("schtasks.exe") then
        return "Trojan.TaskScheduler"
    end
end

Detection rule best practices

Be specific

Avoid false positives by checking multiple indicators, not just one suspicious action.

Use thresholds

Count operations (e.g., “100+ file writes”) rather than detecting single events.

Check context

Consider the full behavior sequence, not isolated actions.

Test thoroughly

Run rules against clean software and known malware to tune detection.
See Best Practices for comprehensive guidance.

Community rules

Dr.Semu supports community-contributed rules:
Official rule repository: DrSemu-Detections
To use community rules:
  1. Clone the repository:
    git clone https://github.com/secrary/DrSemu-Detections
    
  2. Copy rules to Dr.Semu:
    copy DrSemu-Detections\*.lua DrSemu\dr_rules\
    copy DrSemu-Detections\*.py DrSemu\dr_rules\
    
  3. Run analysis - rules are automatically loaded

Verdict reporting

The final verdict is communicated via mailslot:
// run_detections.cpp:65-66
const auto verdict_wide = std::wstring(verdict_string.begin(), verdict_string.end());
const auto slot_result = report_slot.write_slot(verdict_wide);
And displayed to the user:
// LauncherCLI.cpp:468
spdlog::critical(L"Verdict: {}", scan_result);
Example output:
Verdict: Ransomware.WannaCry
Or:
Verdict: CLEAN

Performance considerations

Rule execution overhead

  • Lua: ~10-50ms per rule
  • Python: ~50-200ms per rule (interpreter startup)
  • Total: Usually completes in < 1 second for typical rule sets

Optimization strategies

  1. Quick checks first - Put fast string checks before expensive operations
  2. Early return - Return as soon as malware is detected
  3. Lazy loading - Only parse JSON when needed
  4. Cache data - Store frequently accessed values in variables
Example:
-- Good: Quick check first
if not win_func.NtCreateFile then
    return "CLEAN"  -- Exit early
end
local path = win_func.NtCreateFile.before.file_path
if expensive_check(path) then
    return "Malware"
end

-- Bad: Expensive operation before quick check
local result = expensive_check(entire_json)
if not win_func.NtCreateFile then
    return "CLEAN"
end

See also

Writing Lua rules

Complete Lua rule development guide

Writing Python rules

Complete Python rule development guide

Rule examples

Real-world detection examples

JSON schema

Behavior report format reference

Build docs developers (and LLMs) love