Grapl: A Graph Platform for Detection and Response
Edge
Node
Node
Node
Edge
Edge
Manage from reality because that's the prepared Defenders Mindset
https://github.com/BloodHoundAD/BloodHound
[
{
'ppid': 100,
'pid': 250,
'action': 'started',
'time': '2019-02-24 18:21:25'
},
{
'ppid': 250,
'pid': 350,
'action': 'started',
'time': '2019-02-24 18:22:31'
}
]
[
{
'pid': 350,
'action': 'connected_to',
'domain': 'evil.com',
'time': '2019-02-24 18:24:27'
},
{
'pid': 350,
'action': 'created_file',
'path': 'downloads/evil.exe',
'time': '2019-02-24 18:28:31'
}
]
seen_at: '2019-02-24 18:21:25'
created_at: '2019-02-24 18:21:25'
created_at: '2019-02-24 18:22:31'
seen_at: '2019-02-24 18:24:27'
seen_at: '2019-02-24 18:28:31'
seen_at: '2019-02-24 18:22:31'
seen_at: '2019-02-24 18:24:27'
seen_at: '2019-02-24 18:28:31'
Grapl
Graph Analytics Platform
for Detection, Forensics, and Incident Response
- Consumes logs
- Turn logs into subgraphs
- Merge subgraphs into master graph
- Executes code against master graph to find scary patterns
- Scopes engagements through graph expansion
{
"host_id": "cobrien-mac",
"parent_pid": 3,
"pid": 4,
"image_name": "word.exe",
"create_time": 600,
}
{
"host_id": "cobrien-mac",
"parent_pid": 4,
"pid": 5,
"image_name": "payload.exe",
"create_time": 650,
}
word.exe
payload.exe
ssh.exe
/secret/file
evil.com
explorer.exe
payload.exe
word.exe
Process {
image_name: str,
image_path: str,
pid: int,
create_time: int,
terminate_time: int,
last_seen_time: int,
children: [Process],
binary: File,
created_files: [File],
read_files: [File],
written_files: [File],
deleted_files: [File],
outbound: [OutboundConnection],
inbound: [InboundConnection]
}
File {
path: str,
create_time: int,
delete_time: int,
last_seen_time: int,
}
OutboundConnection {
port: int,
created: int,
ended: int,
bound_connection: InboundConnection
}
InboundConnection {
port: int,
created: int,
ended: int,
}
User {
assets: [Asset],
reports_to: User,
created: int,
ended: int,
}
Asset {
processes: [Process],
}
Email {
from: User,
to: User,
subject: str,
body: str
}
ExternalIp {
port: int,
address: str,
}
{
'pid': 100,
'type': 'process_start',
'timestamp': 1551753726,
'image': '/usr/bin/program',
}
{
'pid': 100,
'type': 'file_read',
'path': '/home/.cache/file',
'timestamp': 1551754726
}
{
'pid': 100,
'type': 'process_terminate',
'timestamp': 1551755726,
'image': '/usr/bin/program',
}
pid: 100
created: 1551753726
terminated: 1551755726
image: '/usr/bin/program'
node_key: <uuid>
pid 250
C - Process Create Event
C - 0
0 10 20 30 40 50 60 70 80
{
'pid': 250,
'created_at': 30
}
C - 1
ID - 0
{
'pid': 250,
'created_at': 50
}
ID - 1
pid 250
C - Process Create Event
C - 0
0 10 20 30 40 50 60 70 80
{
'pid': 250,
'seen_at': 30
}
C - 1
ID - 0
pid 250
C - Process Create Event
0 10 20 30 40 50 60 70 80
{
'pid': 250,
'seen_at': 30
}
C - 1
G - Process Create Event (Guessed)
G - 0
ID - 0
pid 250
C - Process Create Event
0 10 20 30 40 50 60 70 80
{
'pid': 250,
'seen_at': 20
}
C - 1
G - Process Create Event (Guessed)
G - 0
G - 0
ID - 0
{
'type': 'Process Create',
'image': 'C:\Program Files\Microsoft Office\winword.exe',
'pid': 1200,
'ppid': 1080
}
{
'type': 'Process Create',
'image': 'C:\Windows\WindowsPowershell\v1.0\powershell.exe',
'pid': 2590,
'ppid': 1200,
}
word.exe
powershell.exe
word.exe
payload.exe
wmiexec.exe
/secret/file
evil.com
explorer.exe
word.exe
payload.exe
word.exe
evil.com
word.exe talking to non-whitelisted domain
word.exe spawning child process
cmd.exe
payload.exe
wmiexec.exe
cmd.exe
wmiexec from non standard grandparent process
# Malware names itself 'svchost.exe' in order to look legitimate
def signature_graph(node_key) -> str:
child = Process() \
.with_image_name(contains="svchost.exe") \
.with_node_key(eq=node_key)
parent = Process() \
.with_image_name(contains=Not("services.exe"))
return parent.with_child(child).to_query()
def _analyzer(client: DgraphClient, graph: Subgraph, sender: Connection):
hits = analyze_by_node_key(client, graph, signature_graph)
for hit in hits:
sender.send(ExecutionHit.from_parent_child(hit))
sender.send(ExecutionComplete())
{
'pid': 250,
'ppid': 150,
'created_at': 1551565127,
'hash': 'acbd18db4cc2f85cedef654fccc4a4d8',
'image_name': '/home/user/downloads/evil.sh'
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
{
~~: ~~
~~: ~~
}
Search Window
pid collision
{
~~: ~~
~~: ~~
}
Alert
cmd.exe
svchost.exe
wmiexec.exe
/secret/file
evil.com
engagement = Engagement(engagement_id)
svchost = engagement.get_by_image_name(contains="svchost.exe")
parent = svchost.get_parent()
parent.get_connections()
svchost.get_read_files()
svchost.get_children_files()
raw-logs
sysmon-log-parser
generic-log-parser
unidentified-subgraphs
node-identifier
graph-merger
raw-logs
sysmon-log-parser
generic-log-parser
custom-parser
let parent = ProcessDescriptionBuilder::default()
.host_id(process_start.computer)
.state(ProcessState::Existing)
.pid(process_start.ppid)
.last_seen_timestamp(timestamp)
.build()?;
let child = ProcessDescriptionBuilder::default()
.host_id(process_start.computer)
.image_path(process_start.image)
.image_name(get_image_name(&process_start.image)?)
.state(ProcessState::Created)
.pid(process_start.pid)
.created_timestamp(timestamp)
.build()?;
{
'action': 'Process Create',
'computer': 'winuser-laptop',
'ppid': 100,
'pid: 150,
'image': 'C:\\Users\\Demo\\Downloads\\foo.exe',
'timestamp': 1551578297
}
let child_exe = FileDescriptionBuilder::default()
.hostt_id(process_start.computer)
.state(FileState::Existing)
.last_seen_timestamp(timestamp)
.path(process_start.image)
.build()?;
{
'action': 'Process Create',
'computer': 'winuser-laptop',
'ppid': 100,
'pid: 150,
'image': 'C:\\Users\\Demo\\Downloads\\foo.exe',
'timestamp': 1551578297
}
let mut graph = GraphDescription::new(
timestamp
);
graph.add_edge("bin_file",
child.clone_key(),
child_exe.clone_key()
);
graph.add_node(child_exe);
graph.add_edge("children",
parent.clone_key(),
child.clone_key());
graph.add_node(parent);
graph.add_node(child);
Automated scoping
https://www.princeton.edu/~pmittal/publications/priotracker-ndss18
$ git clone git@github.com:insanitybit/grapl.git
$ cd ./grapl/grapl-cdk/
$ npm install -g
$ <your editor> ./.env
HISTORY_DB_USERNAME=username
HISTORY_DB_PASSWORD=password
BUCKET_PREFIX="<unique identifier>"
GRAPH_DB_KEY_NAME=<name of SSH key, if debug mode is enabled, to SSH to graphdb>
$ ssh ubuntu@<master_graph/engagement_graph>
$ curl https://get.dgraph.io -sSf > setup.sh
$ <inspect setup.sh>
$ ./setup.sh
$ dgraph zero &
$ dgraph alpha --zero localhost:5080 &
$ dgraph-ratel &
# ./deploy_all.sh
$ cd ../grapl/
$ python ./gen-raw-logs.py <your bucket prefix>
Questions
Grapl: A Graph Platform for Detection and Response
By Colin
Grapl: A Graph Platform for Detection and Response
- 3,083