Source code for beagle.transformers.fireeye_ax_transformer

import email
import re
from typing import Optional, Tuple, Union

from beagle.common import split_path
from beagle.constants import HashAlgos
from beagle.nodes import URI, Domain, File, IPAddress, Process, RegistryKey
from beagle.transformers.base_transformer import Transformer


[docs]class FireEyeAXTransformer(Transformer): name = "FireEye AX"
[docs] def transform(self, event: dict) -> Optional[Tuple]: """Transformers the various events from the AX Report class. The only edge case is the network type, AX has multiple Nodes under one type when it comes to the network type. For example the following is a DNS event:: { "mode": "dns_query", "protocol_type": "udp", "hostname": "foobar", "qtype": "Host Address", "processinfo": { "imagepath": "C:\\ProgramData\\bloop\\some_proc.exe", "tainted": true, "md5sum": "....", "pid": 3020 }, "timestamp": 27648 } While the following is a TCP connection:: { "mode": "connect", "protocol_type": "tcp", "ipaddress": "192.168.199.123", "destination_port": 3333, "processinfo": { "imagepath": "C:\\ProgramData\\bloop\\some_proc.exe", "tainted": true, "md5sum": "...", "pid": 3020 }, "timestamp": 28029 } Both have the "network" event_type when coming from :py:class:`FireEyeAXReport` Parameters ---------- event : dict The current event to transform. Returns ------- Optional[Tuple] Tuple of nodes extracted from the event. """ event_type = event.get("event_type") if event_type == "process": return self.process_events(event) elif event_type == "network": if event["mode"] in ["dns_query_answer", "dns_query"]: return self.dns_events(event) elif event["mode"] == "connect": return self.conn_events(event) elif event["mode"] == "http_request": return self.http_requests(event) elif event_type == "file": return self.file_events(event) elif event_type == "regkey": return self.regkey_events(event) return None
[docs] def process_events(self, event: dict) -> Optional[Tuple[Process, File, Process, File]]: """Transformers events from the `process` entry. A single process entry looks like:: { "mode": string, "fid": dict, "parentname": string, "cmdline": string, "sha1sum": "string, "md5sum": string, "sha256sum": string, "pid": int, "filesize": int, "value": string, "timestamp": int, "ppid": int }, Parameters ---------- event : dict The input event. Returns ------- Optional[Tuple[Process, File, Process, File]] Parent and child processes, and the file nodes that represent their binaries. """ if event.get("mode") != "started": return None process_image, process_image_path = split_path(event["value"]) parent_image, parent_image_path = split_path(event["parentname"]) hashes = { HashAlgos.MD5: event.get("md5sum"), HashAlgos.SHA1: event.get("sha1sum"), HashAlgos.SHA256: event.get("sha256sum"), } proc = Process( process_image=process_image, process_image_path=process_image_path, command_line=event["cmdline"], process_id=int(event["pid"]), hashes={h: k for h, k in hashes.items() if k}, ) proc_file = proc.get_file_node() proc_file.file_of[proc] parent = Process( process_image=parent_image, process_image_path=parent_image_path, process_id=int(event["ppid"]), ) parent_file = parent.get_file_node() parent_file.file_of[parent] parent.launched[proc].append(timestamp=int(event["timestamp"])) return (proc, proc_file, parent, parent_file)
[docs] def dns_events( self, event: dict ) -> Union[Tuple[Process, File, Domain], Tuple[Process, File, Domain, IPAddress]]: """Transforms a single DNS event Example event:: { "mode": "dns_query", "protocol_type": "udp", "hostname": "foobar", "qtype": "Host Address", "processinfo": { "imagepath": "C:\\ProgramData\\bloop\\some_proc.exe", "tainted": true, "md5sum": "....", "pid": 3020 }, "timestamp": 27648 } Optionally, if the event is "dns_query_answer", we can also extract the response. Parameters ---------- event : dict source dns_query event Returns ------- Tuple[Process, File, Domain] Process and its image, and the domain looked up """ proc_info = event["processinfo"] process_image, process_image_path = split_path(proc_info["imagepath"]) proc = Process( process_id=int(proc_info["pid"]), process_image=process_image, process_image_path=process_image_path, ) proc_file = proc.get_file_node() proc_file.file_of[proc] domain = Domain(event["hostname"]) proc.dns_query_for[domain].append(timestamp=int(event["timestamp"])) if "ipaddress" in event: addr = IPAddress(event["ipaddress"]) domain.resolves_to[addr].append(timestamp=int(event["timestamp"])) return (proc, proc_file, domain, addr) else: return (proc, proc_file, domain)
[docs] def conn_events(self, event: dict) -> Tuple[Process, File, IPAddress]: """Transforms a single connection event Example event:: { "mode": "connect", "protocol_type": "tcp", "ipaddress": "199.168.199.123", "destination_port": 3333, "processinfo": { "imagepath": "C:\\ProgramData\\bloop\\some_proc.exe", "tainted": true, "md5sum": "....", "pid": 3020 }, "timestamp": 27648 } Parameters ---------- event : dict source dns_query event Returns ------- Tuple[Process, File, IPAddress] Process and its image, and the destination address """ proc_info = event["processinfo"] process_image, process_image_path = split_path(proc_info["imagepath"]) proc = Process( process_id=int(proc_info["pid"]), process_image=process_image, process_image_path=process_image_path, ) proc_file = proc.get_file_node() proc_file.file_of[proc] addr = IPAddress(event["ipaddress"]) proc.connected_to[addr].append( protocol=event["protocol_type"], timestamp=event["timestamp"], port=int(event["destination_port"]), ) return (proc, proc_file, addr)
[docs] def http_requests( self, event: dict ) -> Union[ Tuple[Process, File, IPAddress, URI, Domain], Tuple[Process, File, IPAddress, URI], Tuple[Process, File, IPAddress], ]: """Transforms a single `http_request` network event. A typical event looks like:: { "mode": "http_request", "protocol_type": "tcp", "ipaddress": "199.168.199.1", "destination_port": 80, "processinfo": { "imagepath": "c:\\Windows\\System32\\svchost.exe", "tainted": false, "md5sum": "1234", "pid": 1292 }, "http_request": "GET /some_route.crl HTTP/1.1~~Cache-Control: max-age = 900~~User-Agent: Microsoft-CryptoAPI/10.0~~Host: crl.microsoft.com~~~~", "timestamp": 433750 } Parameters ---------- event : dict The source `network` event with mode `http_request` Returns ------- Tuple[Node] [description] """ proc_info = event["processinfo"] process_image, process_image_path = split_path(proc_info["imagepath"]) proc = Process( process_id=int(proc_info["pid"]), process_image=process_image, process_image_path=process_image_path, ) proc_file = proc.get_file_node() proc_file.file_of[proc] addr = IPAddress(event["ipaddress"]) proc.connected_to[addr].append( timestamp=event["timestamp"], protocol=event["protocol_type"], port=event["destination_port"], ) try: url, header = event["http_request"].split("~~", 1) method, uri_path, _ = url.split(" ") uri = URI(uri_path) headers = dict(email.message_from_string(header.replace("~~", "\n")).items()) proc.http_request_to[uri].append(timestamp=event["timestamp"], method=method) if "Host" in headers: domain = Domain(headers["Host"]) # type: ignore domain.resolves_to[addr].append(timestamp=event["timestamp"]) uri.uri_of[domain] return (proc, proc_file, addr, uri, domain) else: return (proc, proc_file, addr, uri) except (ValueError, KeyError): return (proc, proc_file, addr)
[docs] def file_events( self, event: dict ) -> Union[Tuple[Process, File, File], Tuple[Process, File, File, File]]: """Transforms a file event Example file event:: { "mode": "created", "fid": { "ads": "", "content": 2533274790555891 }, "processinfo": { "imagepath": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "md5sum": "eb32c070e658937aa9fa9f3ae629b2b8", "pid": 2956 }, "ntstatus": "0x0", "value": "C:\\Users\\admin\\AppData\\Local\\Temp\\sy24ttkc.k25.ps1", "CreateOptions": "0x400064", "timestamp": 9494 } In 8.2.0 the `value` field became a dictionary when the mode is `failed`:: "values": { "value": "C:\\Users\\admin\\AppData\\Local\\Temp\\sy24ttkc.k25.ps1"" } Parameters ---------- event : dict The source event Returns ------- Tuple[Process, File, File] The process, the process' image, and the file written. """ proc_info = event["processinfo"] process_image, process_image_path = split_path(proc_info["imagepath"]) proc = Process( process_id=int(proc_info["pid"]), process_image=process_image, process_image_path=process_image_path, ) proc_file = proc.get_file_node() proc_file.file_of[proc] # 8.2.0 changes. if "values" in event: full_path = event["values"]["value"] else: full_path = event["value"] file_name, file_path = split_path(full_path) file_node = File(file_name=file_name, file_path=file_path) file_node.set_extension() if event["mode"] == "created": proc.wrote[file_node].append(timestamp=event["timestamp"]) elif event["mode"] == "deleted": proc.deleted[file_node].append(timestamp=event["timestamp"]) elif event["mode"] == "CopyFile": src_name, src_path = split_path(event["source"]) src_file = File(file_name=src_name, file_path=src_path) src_file.set_extension() src_file.copied_to[file_node].append(timestamp=event["timestamp"]) proc.copied[src_file].append(timestamp=event["timestamp"]) return (proc, proc_file, file_node, src_file) else: proc.accessed[file_node].append(timestamp=event["timestamp"]) return (proc, proc_file, file_node)
[docs] def regkey_events(self, event: dict) -> Tuple[Process, File, RegistryKey]: """Transforms a single registry key event Example event:: { "mode": "queryvalue", "processinfo": { "imagepath": "C:\\Users\\admin\\AppData\\Local\\Temp\\bar.exe", "tainted": True, "md5sum": "....", "pid": 1700, }, "value": "\\REGISTRY\\USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\\"ProxyOverride\"", "timestamp": 6203 }, Parameters ---------- event : dict source regkey event Returns ------- Tuple[Process, File, RegistrKey] Process and its image, and the registry key. """ proc_info = event["processinfo"] process_image, process_image_path = split_path(proc_info["imagepath"]) proc = Process( process_id=int(proc_info["pid"]), process_image=process_image, process_image_path=process_image_path, ) proc_file = proc.get_file_node() proc_file.file_of[proc] path, key_contents = re.match(r"(.*)\\\"(.*)", event["value"]).groups() # type: ignore key_contents = key_contents[:-1] # remove last quote. hive, reg_key_path = path.replace("\\REGISTRY\\", "").split("\\", 1) if '" = ' in key_contents: key, value = key_contents.split('" = ') else: key = key_contents value = None regkey = RegistryKey(hive=hive, key=key, key_path=reg_key_path, value=value) if event["mode"] == "added": proc.created_key[regkey].append(timestamp=event["timestamp"], value=value) elif event["mode"] == "setval": proc.changed_value[regkey].append(timestamp=event["timestamp"], value=value) elif event["mode"] in ["deleteval", "deleted"]: proc.deleted_key[regkey].append(timestamp=event["timestamp"], value=value) else: proc.read_key[regkey].append(timestamp=event["timestamp"], value=value) return (proc, proc_file, regkey)