From 7be24e5795d51f895418295da3ba59e55c369e95 Mon Sep 17 00:00:00 2001 From: Adam Sindelar Date: Sun, 25 Oct 2015 17:11:46 +0100 Subject: [PATCH] Fixed 'ifconfig' and 'arp' for 10.10.5 (and probably 10.11). BUG= R=scudette@gmail.com Review URL: https://codereview.appspot.com/274040043. --- .../rekall/plugins/darwin/networking.py | 161 ++++++++++++++---- .../rekall/plugins/overlays/darwin/darwin.py | 22 ++- .../rekall/plugins/renderers/darwin.py | 25 +++ rekall-core/rekall/ui/text.py | 7 +- .../osx/MacPmem/MacPmemTest/rangemap_tests.c | 4 + 5 files changed, 179 insertions(+), 40 deletions(-) diff --git a/rekall-core/rekall/plugins/darwin/networking.py b/rekall-core/rekall/plugins/darwin/networking.py index b1cfedd7f..ea42942a2 100644 --- a/rekall-core/rekall/plugins/darwin/networking.py +++ b/rekall-core/rekall/plugins/darwin/networking.py @@ -20,6 +20,7 @@ "Michael Cohen ", "Adam Sindelar ") +from rekall import obj from rekall import plugin from rekall import registry @@ -87,7 +88,7 @@ def methods(cls): @registry.classproperty @registry.memoize - def table_header(cls): + def table_header(cls): # pylint: disable=no-self-argument header = [dict(name="Socket", cname="socket", type="socket", width=60)] for method in cls.methods(): header.append(dict(name=method, cname=method, width=12)) @@ -106,41 +107,123 @@ def collect(self): yield row -class DarwinArp(common.AbstractDarwinCommand): - """Show information about arp tables.""" +class DarwinGetArpListHead(common.AbstractDarwinParameterHook): + """ - __name = "arp" + One version of arp_init looks like this: - def render(self, renderer): - renderer.table_header( - [("IP Addr", "ip_addr", "20"), - ("MAC Addr", "mac", "18"), - ("Interface", "interface", "9"), - ("Sent", "sent", "8"), - ("Recv", "recv", "8"), - ("Time", "timestamp", "24"), - ("Expires", "expires", "8"), - ("Delta", "delta", "8")]) + void + arp_init(void) + { + VERIFY(!arpinit_done); + + LIST_INIT(&llinfo_arp); // <-- This is the global we want. + + llinfo_arp_zone = zinit(sizeof (struct llinfo_arp), + LLINFO_ARP_ZONE_MAX * sizeof (struct llinfo_arp), 0, + LLINFO_ARP_ZONE_NAME); + if (llinfo_arp_zone == NULL) + panic("%s: failed allocating llinfo_arp_zone", __func__); + + zone_change(llinfo_arp_zone, Z_EXPAND, TRUE); + zone_change(llinfo_arp_zone, Z_CALLERACCT, FALSE); + + arpinit_done = 1; + } + + Disassembled, the first few instructions look like this: + 0x0 55 PUSH RBP + 0x1 4889e5 MOV RBP, RSP + 0x4 803d65e9400001 CMP BYTE [RIP+0x40e965], 0x1 + 0xb 7518 JNZ 0xff80090a7f95 + 0xd 488d3dee802900 LEA RDI, [RIP+0x2980ee] + 0x14 488d35f5802900 LEA RSI, [RIP+0x2980f5] + 0x1b baf3000000 MOV EDX, 0xf3 + + # This is a call to kernel!panic (later kernel!assfail): + 0x20 e80b6c1400 CALL 0xff80091eeba0 + + # This is where it starts initializing the linked list: + 0x25 48c70548e94000000000 MOV QWORD [RIP+0x40e948], 0x0 + 00 + 0x30 488d0d0e812900 LEA RCX, [RIP+0x29810e] + """ + name = "disassembled_llinfo_arp" + + PANIC_FUNCTIONS = (u"__kernel__!_panic", u"__kernel__!_assfail") - arp_cache = self.profile.get_constant_object( - "_llinfo_arp", + def calculate(self): + resolver = self.session.address_resolver + arp_init = resolver.get_constant_object("__kernel__!_arp_init", + target="Function") + instructions = iter(arp_init.Decompose(20)) + + # Walk down to the CALL mnemonic and use the address resolver to + # see if it calls one of the panic functions. + for instruction in instructions: + # Keep spinning until we get to the first CALL. + if instruction.mnemonic != "CALL": + continue + + # This is absolute: + target = instruction.operands[0].value + _, names = resolver.get_nearest_constant_by_address(target) + if not names: + return obj.NoneObject("Could not find CALL in arp_init.") + + if names[0] not in self.PANIC_FUNCTIONS: + return obj.NoneObject( + "CALL was to %r, which is not on the PANIC list." + % names) + + # We verified it's the right CALL. MOV should be right after it, + # so let's just grab it. + mov_instruction = next(instructions) + if mov_instruction.mnemonic != "MOV": + return obj.NoneObject("arp_init code changed.") + + offset = (mov_instruction.operands[0].disp + + mov_instruction.address + + mov_instruction.size) + address = self.session.profile.Object(type_name="address", + offset=offset) + + llinfo_arp = self.session.profile.Object( + type_name="llinfo_arp", + offset=address.v()) + + if llinfo_arp.isvalid: + return llinfo_arp.obj_offset + + return obj.NoneObject("llinfo_arp didn't validate.") + + +class DarwinArp(common.AbstractDarwinProducer): + """Show information about arp tables.""" + + name = "arp" + type_name = "rtentry" + + def collect(self): + llinfo_arp = self.session.address_resolver.get_constant_object( + "__kernel__!_llinfo_arp", target="Pointer", target_args=dict(target="llinfo_arp")) - while arp_cache: - entry = arp_cache.la_rt + if not llinfo_arp: + # Must not have it in the profile. Try asking the session hook + # for the address. + offset = self.session.GetParameter("disassembled_llinfo_arp") + if not offset: + self.session.logging.error( + "Could not find the address of llinfo_arp.") + return - renderer.table_row( - entry.source_ip, - entry.dest_ip, - entry.name, - entry.sent, - entry.rx, - entry.base_calendartime, - entry.rt_expire, - entry.delta) + llinfo_arp = self.session.profile.Object( + type_name="llinfo_arp", offset=offset) - arp_cache = arp_cache.la_le.le_next + for arp_hit in llinfo_arp.walk_list("la_le.le_next"): + yield [arp_hit.la_rt] class DarwinRoute(common.AbstractDarwinCommand): @@ -263,14 +346,24 @@ class DarwinIfnetHook(common.AbstractDarwinParameterHook): https://github.com/opensource-apple/xnu/blob/10.9/bsd/net/if_var.h#L816 """ - name = "ifnet" + name = "ifconfig" + + # ifnet_head is the actual extern holding ifnets and seems to be an + # improvement over dlil_ifnet_head, which is a static and used only in the + # dlil (stands for data link interface, I think?) module. + IFNET_HEAD_NAME = ("_ifnet_head", "_dlil_ifnet_head") def calculate(self): - ifnet_head = self.session.profile.get_constant_object( - "_dlil_ifnet_head", - target="Pointer", - target_args=dict( - target="ifnet")) + ifnet_head = obj.NoneObject("No ifnet global names given.") + for name in self.IFNET_HEAD_NAME: + ifnet_head = self.session.profile.get_constant_object( + name, + target="Pointer", + target_args=dict( + target="ifnet")) + + if ifnet_head: + break return [x.obj_offset for x in ifnet_head.walk_list("if_link.tqe_next")] diff --git a/rekall-core/rekall/plugins/overlays/darwin/darwin.py b/rekall-core/rekall/plugins/overlays/darwin/darwin.py index 516d5fcfe..8d3e82bd1 100644 --- a/rekall-core/rekall/plugins/overlays/darwin/darwin.py +++ b/rekall-core/rekall/plugins/overlays/darwin/darwin.py @@ -588,6 +588,15 @@ def __iter__(self): return self.list_of_type(self.obj_parent.obj_type, self.obj_name) +class llinfo_arp(obj.Struct): + @property + def isvalid(self): + try: + return self.la_rt.rt_llinfo.v() == self.obj_offset + except AttributeError: + return False + + class queue_entry(basic.ListMixIn, obj.Struct): """A queue_entry is an externalized linked list. @@ -758,7 +767,7 @@ def fill_socketinfo(self): https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/NKEConceptual/control/control.html """ domain = self.so_proto.pr_domain.dom_family - type = self.so_proto.pr_type + type_name = self.so_proto.pr_type protocol = self.so_proto.pr_protocol si = {"soi_kind": "SOCKINFO_GENERIC"} @@ -796,7 +805,7 @@ def fill_socketinfo(self): inp.inp_dependladdr.inp6_local.m("__u6_addr") ) - if (type == "SOCK_STREAM" + if (type_name == "SOCK_STREAM" and (protocol == 0 or protocol == "IPPROTO_TCP") and inp.inp_ppcb != None): @@ -978,7 +987,8 @@ def _get_address_obj(self): return addr - def __unicode__(self): + @property + def address(self): result = "" addr = self._get_address_obj() if addr: @@ -990,6 +1000,9 @@ def __unicode__(self): return str(result) + def __unicode__(self): + return self.address + class vm_map_entry(obj.Struct): def find_vnode_object(self): @@ -1500,7 +1513,8 @@ def Initialize(cls, profile): # Support both forms with and without _class suffix. OSDictionary=OSDictionary, OSDictionary_class=OSDictionary, OSOrderedSet=OSOrderedSet, OSOrderedSet_class=OSOrderedSet, - fileproc=fileproc, session=session, cnode=cnode) + fileproc=fileproc, session=session, cnode=cnode, + llinfo_arp=llinfo_arp) profile.add_enums(**darwin_enums) profile.add_overlay(darwin_overlay) profile.add_constants(default_text_encoding="utf8") diff --git a/rekall-core/rekall/plugins/renderers/darwin.py b/rekall-core/rekall/plugins/renderers/darwin.py index b5565a7de..d35757698 100644 --- a/rekall-core/rekall/plugins/renderers/darwin.py +++ b/rekall-core/rekall/plugins/renderers/darwin.py @@ -20,6 +20,7 @@ from rekall.ui import json_renderer +from rekall.ui import text from rekall.plugins.renderers import base_objects from rekall.plugins.renderers import data_export @@ -121,6 +122,30 @@ class Socket_TextObjectRenderer(base_objects.StructTextRenderer): ] +class Rtentry_TextObjectRenderer(base_objects.StructTextRenderer): + renders_type = "rtentry" + + COLUMNS = [ + dict(name="IP Address", cname="source_ip", type="sockaddr", + width=18), + dict(name="Mac Address", cname="dest_ip", type="sockaddr", + width=18), + dict(name="Interface", cname="name", align="c"), + dict(name="Sent", cname="sent", width=8, align="r"), + dict(name="Received", cname="rx", width=8, align="r"), + dict(name="Time", cname="base_calendartime", width=30, align="c"), + dict(name="Expires", cname="rt_expire", align="r"), + dict(name="Delta", cname="delta", align="r") + ] + + +class Sockaddr_TextObjectRenderer(text.TextObjectRenderer): + renders_type = "sockaddr" + + def render_full(self, target, **_): + return text.Cell(target.address) + + class Zone_TextObjectRenderer(base_objects.StructTextRenderer): renders_type = "zone" COLUMNS = [ diff --git a/rekall-core/rekall/ui/text.py b/rekall-core/rekall/ui/text.py index 1f0f9a5ab..165dd391e 100644 --- a/rekall-core/rekall/ui/text.py +++ b/rekall-core/rekall/ui/text.py @@ -404,8 +404,11 @@ def render_address(self, target, width=None, **options): self.format_address(int(target), **options), width=width) - render_compact = render_full - render_value = render_full + def render_compact(self, *args, **kwargs): + return self.render_full(*args, **kwargs) + + def render_value(self, *args, **kwargs): + return self.render_full(*args, **kwargs) def render_row(self, target, style=None, **options): """Render the target suitably. diff --git a/tools/osx/MacPmem/MacPmemTest/rangemap_tests.c b/tools/osx/MacPmem/MacPmemTest/rangemap_tests.c index 986c7b76e..af77120ab 100644 --- a/tools/osx/MacPmem/MacPmemTest/rangemap_tests.c +++ b/tools/osx/MacPmem/MacPmemTest/rangemap_tests.c @@ -74,6 +74,9 @@ int test_rangemap() { // Test filling the rangemap out of sequence. int test_rangemap_nonsequential() { + // Disable error logging because we cause them on purpose. + PmemLogLevel previous_level = pmem_logging_level; + pmem_logging_level = kPmemFatal; pmem_rangemap *r = pmem_rangemap_make(0x100); // Starting the map past zero should work. @@ -91,5 +94,6 @@ int test_rangemap_nonsequential() { return -3; } + pmem_logging_level = previous_level; return 0; }