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 {
    node_key: string ,
    asset_id: string ,
    process_id: int ,
    process_guid: string ,
    created_timestamp: int ,
    terminated_timestamp: int ,
    last_seen_timestamp: int ,
    process_name: string ,
    process_command_line: string ,
    process_integrity_level: string ,
    operating_system: string ,
    process_path: File ,
    children: [Process] ,
    created_files: [File] ,
    deleted_files: [File] ,
    read_files: [File] ,
    wrote_files: [File] ,
}
File {
    node_key: string ,
    asset_id: string ,
    created_timestamp: int ,
    deleted_timestamp: int ,
    last_seen_timestamp: int ,
    file_name: string ,
    file_path: string ,
    file_extension: string ,
    file_mime_type: string ,
    file_size: int ,
    file_version: string ,
    file_description: string ,
    file_product: string ,
    file_company: string ,
    file_directory: string ,
    file_inode: int ,
    file_hard_links: int ,
    md5_hash: string ,
    sha1_hash: string ,
    sha256_hash: string ,
}
OutboundConnection {
    port: int,
    created: int,
    ended: int,
    bound_connection: InboundConnection
}
InboundConnection {
    port: int,
    created: int,
    ended: int,
}
Asset {
    operating_system: string,
    processes: [Process],
}
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

{
  '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

word.exe

payload.exe

evil.com

Process with network access creates file, executes child from it


from pydgraph import DgraphClient, DgraphClientStub

from grapl_analyzer.entity_views import SubgraphView

def _analyzer(client: DgraphClient, graph: SubgraphView, sender: Any):
    
    # Detect processes that delete their parent processes' binary file

    for process in graph.process_iter():
        parent = process.get_parent()
        if not parent: continue

        deleted_files = process.get_deleted_files()
        if not deleted_files: continue

        bin_file = parent.get_bin_file()
        
        for deleted_file in deleted_files:
            if deleted_file.path == bin_file.path:
                sender.send(process)
from pydgraph import DgraphClient, DgraphClientStub

from grapl_analyzer.counters import ParentChildCounter

from grapl_analyzer.entity_views import SubgraphView

def _analyzer(client: DgraphClient, graph: SubgraphView, sender: Any):
    counter = ParentChildCounter(client)

    # Detect unique parent-child process combinations, where we have seen
    # the child process at least once before

    for process in graph.process_iter():
        children = process.get_children() or []

        for child in children:
            combo_count = counter.get_count_for(
                process.image_name,
                child.image_name,
                excluding=process.node_key
            )

            child_image_count = counter.get_count_for(child.image_name, 
                                                excluding=process.node_key)

            if combo_count >= Seen.Once and child_image_count == Seen.Many:
                sender.send(process)
            
{
    'pid': 250,
    'ppid': 150,
    'created_at': 1551565127,
    'hash': 'acbd18db4cc2f85cedef654fccc4a4d8',
    'image_name': '/home/user/downloads/evil.sh'
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}
{
  ~~: ~~
  ~~: ~~
}

Search Window

pid collision

{
  ~~: ~~
  ~~: ~~
}

Alert

engagement = Engagement(engagement_id)
suspect_file = FileView(node_key="node key from engagement")
creator = suspect_file.get_creator()
spawned_procs = suspect_file.get_spawned_from()
engagement.add_processes(spawned_procs)

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)
        .process_id(process_start.ppid)
        .last_seen_timestamp(timestamp)
        .build()?;

    let child = ProcessDescriptionBuilder::default()
        .host_id(process_start.computer)
        .process_name(get_image_name(&process_start.image)?)
        .state(ProcessState::Created)
        .process_id(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)
        .file_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);

$ git clone git@github.com:insanitybit/grapl.git
$ cd ./grapl/grapl-cdk/
$ npm install -g
$ <your editor> ./.env

BUCKET_PREFIX="<unique identifier>"
$ ./deploy_all.sh
cd /path/to/grapl/
python ./gen-raw-logs.py <bucket prefix>

Questions