From 6d2a5fbfaa9bf0294bbf719bda2677b69126b020 Mon Sep 17 00:00:00 2001 From: Andrew Case Date: Fri, 28 Dec 2018 15:14:28 -0600 Subject: [PATCH] add mac_netstat and supporting networking code --- volatility/framework/automagic/mac.py | 38 ++++++ volatility/framework/renderers/conversion.py | 92 +++++++++++++- volatility/framework/symbols/mac/__init__.py | 4 + .../symbols/mac/extensions/__init__.py | 116 +++++++++++++++++- 4 files changed, 245 insertions(+), 5 deletions(-) diff --git a/volatility/framework/automagic/mac.py b/volatility/framework/automagic/mac.py index 7e3f4617b..2def7f15c 100644 --- a/volatility/framework/automagic/mac.py +++ b/volatility/framework/automagic/mac.py @@ -222,3 +222,41 @@ class MacUtilities(object): def virtual_to_physical_address(cls, addr: int) -> int: """Converts a virtual mac address to a physical one (does not account of ASLR)""" return addr - 0xffffff8000000000 + + @classmethod + def files_descriptors_for_process(cls, config: interfaces.configuration.HierarchicalDict, + context: interfaces.context.ContextInterface, + task: interfaces.objects.ObjectInterface): + + num_fds = task.p_fd.fd_lastfile + nfiles = task.p_fd.fd_nfiles + + if nfiles > num_fds: + num_fds = nfiles + + if num_fds > 4096: + num_fds = 1024 + + file_type = config["darwin"] + constants.BANG + 'fileproc' + + try: + table_addr = task.p_fd.fd_ofiles.dereference() + except exceptions.PagedInvalidAddressException: + return + + fds = objects.utility.array_of_pointers(table_addr, count = num_fds, subtype = file_type, context = context) + + for fd_num, f in enumerate(fds): + if f != 0: + try: + ftype = f.f_fglob.get_fg_type() + except exceptions.PagedInvalidAddressException: + continue + + if ftype == 'DTYPE_VNODE': + vnode = f.f_fglob.fg_data.dereference().cast("vnode") + path = vnode.full_path() + else: + path = "<%s>" % ftype.replace("DTYPE_", "").lower() + + yield f, path, fd_num diff --git a/volatility/framework/renderers/conversion.py b/volatility/framework/renderers/conversion.py index c4a429435..8818d7e29 100644 --- a/volatility/framework/renderers/conversion.py +++ b/volatility/framework/renderers/conversion.py @@ -18,7 +18,7 @@ # specific language governing rights and limitations under the License. # -import datetime +import datetime, socket, struct from typing import Union from volatility.framework import interfaces, renderers @@ -65,3 +65,93 @@ def round(addr: int, align: int, up: bool = False) -> int: if up: return (addr + (align - (addr % align))) return (addr - (addr % align)) + +""" +For vol3 devs: + +convert_ipv4 && convert_ipv6 are slightly modified versions of their +counterparts from vol2: + + https://github.com/volatilityfoundation/volatility/blob/master/volatility/utils.py#L84 + +Furthermore, vol2 used as overlay for ip addresses that made the conversion string based: + + https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/overlays/basic.py#L156 + +by using struct.pack with the given format string on data that was then gathered through .v(): + + https://github.com/volatilityfoundation/volatility/blob/aa6b960c1077e447bda9d64df507ec02f8fcc958/volatility/obj.py#L439 + +.v() for IP addresses would do obj_vm.read(), which returned a string, and struct.pack was called on it. + +This doesn't translate very well to vol3, since vol3 does have overlays so the plugins instead are retreiving the raw integers +from memory. That is why convert_ip4 takes a 32 bit integer as its input and convert_ipv6 takes an array of shorts. +This code has only been tested on Mac so far, but since the modified functions cleanly replace evaluation of data that used to +be done by the overlays that plugins for every OS used, then I don't expect issues when vol3 linux and windows plugins use them + +The other thing to note about these functions, is that when you struct.pack("I",...) as convert_ip4 does, and then you try to enumerate it, +Python3 will treat each element as an 'int' and *not* a string. That means any code that calls ord() will fail, so those calls were removed +when porting over the vol2 and python2 version. +""" +def convert_ipv4(ip_as_integer): + ip_str = struct.pack("> 8) | ((port_as_integer & 0xff) << 8) + +def convert_network_four_tuple(family, four_tuple): + """ + Converts the connection four_tuple: + (source ip, + source port, + dest ip, + dest port) + + into their string equivalents. + IP addresses are expected as a tuple of unsigned shorts + Ports are converted to proper endianess as well + """ + + if family == socket.AF_INET: + ret = (convert_ipv4(four_tuple[0]), + convert_port(four_tuple[1]), + convert_ipv4(four_tuple[2]), + convert_port(four_tuple[3])) + elif family == socket.AF_INET6: + ret = (convert_ipv6(four_tuple[0]), + convert_port(four_tuple[1]), + convert_ipv6(four_tuple[2]), + convert_port(four_tuple[3])) + else: + ret = None + + return ret + + diff --git a/volatility/framework/symbols/mac/__init__.py b/volatility/framework/symbols/mac/__init__.py index 152d80fb3..b0a9bd738 100644 --- a/volatility/framework/symbols/mac/__init__.py +++ b/volatility/framework/symbols/mac/__init__.py @@ -34,3 +34,7 @@ class MacKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class('vnode', extensions.vnode) self.set_type_class('vm_map_entry', extensions.vm_map_entry) self.set_type_class('vm_map_object', extensions.vm_map_object) + self.set_type_class('socket', extensions.socket) + self.set_type_class('inpcb', extensions.inpcb) + + diff --git a/volatility/framework/symbols/mac/extensions/__init__.py b/volatility/framework/symbols/mac/extensions/__init__.py index ce7faf53d..0d9d0a614 100644 --- a/volatility/framework/symbols/mac/extensions/__init__.py +++ b/volatility/framework/symbols/mac/extensions/__init__.py @@ -24,6 +24,7 @@ from volatility.framework import constants from volatility.framework import exceptions, interfaces from volatility.framework.symbols import generic from volatility.framework.objects import utility +from volatility.framework.renderers import conversion class proc(generic.GenericIntelProcess): @@ -126,7 +127,7 @@ class vnode(generic.GenericIntelProcess): return if vname: - ret.append(utility.pointer_to_string(vname)) + ret.append(utility.pointer_to_string(vname, 255)) if int(vnodeobj.v_flag) & 0x000001 != 0 and int(vnodeobj.v_mount) != 0: if int(vnodeobj.v_mount.mnt_vnodecovered) != 0: @@ -145,11 +146,11 @@ class vnode(generic.GenericIntelProcess): elements.reverse() for e in elements: - files.append(e.decode("utf-8")) + files.append(e.encode("utf-8")) - ret = "/".join(files) + ret = b"/".join(files) if ret: - ret = "/" + ret + ret = b"/" + ret return ret @@ -277,3 +278,110 @@ class vm_map_entry(generic.GenericIntelProcess): ret = None return ret + +class socket(generic.GenericIntelProcess): + def get_inpcb(self): + try: + ret = self.so_pcb.dereference().cast("inpcb") + except exceptions.PagedInvalidAddressException: + ret = None + + return ret + + def get_family(self): + return self.so_proto.pr_domain.dom_family + + def get_protocol_as_string(self): + proto = self.so_proto.pr_protocol + + if proto == 6: + ret = "TCP" + elif proto == 17: + ret = "UDP" + else: + ret = "" + + return ret + + def get_state(self): + ret = "" + + if self.so_proto.pr_protocol == 6: + inpcb = self.get_inpcb() + if inpcb is not None: + ret = inpcb.get_tcp_state() + + return ret + + def get_connection_info(self): + inpcb = self.get_inpcb() + + if inpcb is None: + ret = None + elif self.get_family() == 2: + ret = inpcb.get_ipv4_info() + else: + ret = inpcb.get_ipv6_info() + + return ret + + def get_converted_connection_info(self): + vals = self.get_connection_info() + + if vals: + ret = conversion.convert_network_four_tuple(self.get_family(), vals) + else: + ret = None + + return ret + +class inpcb(generic.GenericIntelProcess): + + def get_tcp_state(self): + tcp_states = ( + "CLOSED", + "LISTEN", + "SYN_SENT", + "SYN_RECV", + "ESTABLISHED", + "CLOSE_WAIT", + "FIN_WAIT1", + "CLOSING", + "LAST_ACK", + "FIN_WAIT2", + "TIME_WAIT") + + try: + tcpcb = self.inp_ppcb.dereference().cast("tcpcb") + except exceptions.PagedInvalidAddressException: + return "" + + state_type = tcpcb.t_state + if state_type and state_type < len(tcp_states): + state = tcp_states[state_type] + else: + state = "" + + return state + + def get_ipv4_info(self): + lip = self.inp_dependladdr.inp46_local.ia46_addr4.s_addr + lport = self.inp_lport + + rip = self.inp_dependfaddr.inp46_foreign.ia46_addr4.s_addr + rport = self.inp_fport + + return [lip, lport, rip, rport] + + def get_ipv6_info(self): + lip = self.inp_dependladdr.inp6_local.member(attr = '__u6_addr').member(attr = '__u6_addr16') + lport = self.inp_lport + + rip = self.inp_dependfaddr.inp6_foreign.member(attr = '__u6_addr').member(attr = '__u6_addr16') + rport = self.inp_fport + + return [lip, lport, rip, rport] + + + +