Detection rules › Elastic

Potential API Call via ROP Cleanup Gadget

Source
github.com/elastic/protections-artifacts

Identifies Windows Native API calls where ROP cleanup gadgets are present in trailing bytes, and the call originates from an unusual or undetermined Microsoft-signed module. This behavior may indicate post-execution ROP cleanup after in-memory code execution or stealthy memory manipulation. The detection focuses on ROP teardown patterns, differentiating them from standard compiler epilogues.

MITRE ATT&CK coverage

TacticTechniques
Stealth

Rule body

[rule]
description = """
Identifies Windows Native API calls where ROP cleanup gadgets are present in trailing bytes, and the call originates
from an unusual or undetermined Microsoft-signed module. This behavior may indicate post-execution ROP cleanup after
in-memory code execution or stealthy memory manipulation. The detection focuses on ROP teardown patterns,
differentiating them from standard compiler epilogues.
"""
id = "6c8baa0b-7dd6-43b9-acab-06e21e93beb4"
license = "Elastic License v2"
name = "Potential API Call via ROP Cleanup Gadget"
os_list = ["windows"]
reference = ["https://github.com/klezVirus/Moonwalk--"]
version = "1.0.1"

query = '''
api where process.Ext.api.behaviors == "image_rop" and process.Ext.api.behaviors == "proxy_call" and
 process.Ext.api.name in ("VirtualAlloc", "VirtualProtect", "WriteProcessMemory", "SetThreadContext", "SuspendThread", "VirtualProtectEx") and
 not process.thread.Ext.call_stack_final_user_module.name in ("Kernel", "Unbacked") and
 process.thread.Ext.call_stack_summary like "ntdll.dll|kernelbase.dll|*" and process.thread.Ext.call_stack_final_user_module.name == "Undetermined" and
 _arraysearch(process.thread.Ext.call_stack, $entry, $entry.callsite_trailing_bytes regex """5[8-9a-f]c3.+""")
'''

min_endpoint_version = "8.10.0"
optional_actions = []
[[actions]]
action = "kill_process"
field = "process.entity_id"
state = 0
tree = true

[[threat]]
framework = "MITRE ATT&CK"
[[threat.technique]]
id = "T1055"
name = "Process Injection"
reference = "https://attack.mitre.org/techniques/T1055/"


[threat.tactic]
id = "TA0005"
name = "Defense Evasion"
reference = "https://attack.mitre.org/tactics/TA0005/"

[internal]
min_endpoint_version = "8.10.0"

Stages and Predicates

Stage 1: api

api where process.Ext.api.behaviors == "image_rop" and process.Ext.api.behaviors == "proxy_call" and
 process.Ext.api.name in ("VirtualAlloc", "VirtualProtect", "WriteProcessMemory", "SetThreadContext", "SuspendThread", "VirtualProtectEx") and
 not process.thread.Ext.call_stack_final_user_module.name in ("Kernel", "Unbacked") and
 process.thread.Ext.call_stack_summary like "ntdll.dll|kernelbase.dll|*" and process.thread.Ext.call_stack_final_user_module.name == "Undetermined" and
 _arraysearch(process.thread.Ext.call_stack, $entry, $entry.callsite_trailing_bytes regex """5[8-9a-f]c3.+""")

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
process.Ext.api.behaviorseq
  • image_rop corpus 3 (elastic 3)
  • proxy_call corpus 4 (elastic 4)
field:"process.Ext.api.behaviors" kind:eq
process.Ext.api.namein
  • SetThreadContext corpus 4 (elastic 4)
  • SuspendThread
  • VirtualAlloc corpus 4 (elastic 4)
  • VirtualProtect corpus 19 (elastic 19)
  • VirtualProtectEx
  • WriteProcessMemory corpus 14 (elastic 14)
field:"process.Ext.api.name" kind:in
process.thread.Ext.call_stack_final_user_module.nameeq
  • Undetermined corpus 4 (elastic 4)
field:"process.thread.Ext.call_stack_final_user_module.name" kind:eq value:"Undetermined"
process.thread.Ext.call_stack_summarywildcard
  • ntdll.dll|kernelbase.dll|* corpus 4 (elastic 4)
field:"process.thread.Ext.call_stack_summary" kind:wildcard value:"ntdll.dll|kernelbase.dll|*"