Package rekall :: Package plugins :: Package darwin :: Module networking
[frames] | no frames]

Source Code for Module rekall.plugins.darwin.networking

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright 2013 Google Inc. All Rights Reserved. 
  4  # 
  5  # This program is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or (at 
  8  # your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  # General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 18   
 19  __author__ = ( 
 20      "Michael Cohen <scudette@google.com>", 
 21      "Adam Sindelar <adam.sindelar@gmail.com>") 
 22   
 23  from rekall import obj 
 24  from rekall import plugin 
 25  from rekall_lib import registry 
 26   
 27  from rekall.plugins.darwin import common 
28 29 30 -class DarwinUnpListCollector(common.AbstractDarwinProducer):
31 """Walks the global list of sockets in uipc_usrreq.""" 32 33 name = "unp_sockets" 34 type_name = "socket" 35
36 - def collect(self):
37 for head_const in ["_unp_dhead", "_unp_shead"]: 38 lhead = self.session.profile.get_constant_object( 39 head_const, 40 target="unp_head") 41 42 for unp in lhead.lh_first.walk_list("unp_link.le_next"): 43 yield [unp.unp_socket]
44
45 46 -class DarwinSocketsFromHandles(common.AbstractDarwinProducer):
47 """Looks up handles that point to a socket and collects the socket.""" 48 49 name = "open_sockets" 50 type_name = "socket" 51
52 - def collect(self):
53 for fileproc in self.session.plugins.collect("fileproc"): 54 if fileproc.fg_type == "DTYPE_SOCKET": 55 yield [fileproc.autocast_fg_data()]
56
57 58 -class DarwinNetstat(common.AbstractDarwinCommand):
59 """Prints all open sockets we know about, from any source. 60 61 Netstat will display even connections that lsof doesn't know about, because 62 they were either recovered from an allocation zone, or found through a 63 secondary mechanism (like system call handler cache). 64 65 On the other hand, netstat doesn't know the file descriptor or, really, the 66 process that owns the connection (although it does know the PID of the last 67 process to access the socket.) 68 69 Netstat will also tell you, in the style of psxview, if a socket was only 70 found using some of the methods available. 71 """ 72 73 name = "netstat" 74 75 @classmethod
76 - def methods(cls):
77 """Return the names of available socket enumeration methods.""" 78 # Find all the producers that collect procs and inherit from 79 # AbstractDarwinCachedProducer. 80 methods = [] 81 for subclass in common.AbstractDarwinProducer.classes.itervalues(): 82 # We look for a plugin which is a producer and a darwin command. 83 if (issubclass(subclass, common.AbstractDarwinCommand) and 84 issubclass(subclass, plugin.Producer) and 85 subclass.type_name == "socket"): 86 methods.append(subclass.name) 87 methods.sort() 88 89 return methods
90 91 @registry.classproperty 92 @registry.memoize
93 - def table_header(cls): # pylint: disable=no-self-argument
94 header = [dict(name="socket", type="socket", width=60)] 95 for method in cls.methods(): 96 header.append(dict(name=method, width=12)) 97 98 return plugin.PluginHeader(*header)
99
100 - def collect(self):
101 methods = self.methods() 102 103 for socket in sorted(self.session.plugins.collect("socket"), 104 key=lambda socket: socket.last_pid): 105 row = [socket] 106 for method in methods: 107 row.append(method in socket.obj_producers) 108 109 yield row
110
111 112 -class DarwinGetArpListHead(common.AbstractDarwinParameterHook):
113 """ 114 115 One version of arp_init looks like this: 116 117 void 118 arp_init(void) 119 { 120 VERIFY(!arpinit_done); 121 122 LIST_INIT(&llinfo_arp); // <-- This is the global we want. 123 124 llinfo_arp_zone = zinit(sizeof (struct llinfo_arp), 125 LLINFO_ARP_ZONE_MAX * sizeof (struct llinfo_arp), 0, 126 LLINFO_ARP_ZONE_NAME); 127 if (llinfo_arp_zone == NULL) 128 panic("%s: failed allocating llinfo_arp_zone", __func__); 129 130 zone_change(llinfo_arp_zone, Z_EXPAND, TRUE); 131 zone_change(llinfo_arp_zone, Z_CALLERACCT, FALSE); 132 133 arpinit_done = 1; 134 } 135 136 Disassembled, the first few instructions look like this: 137 0x0 55 PUSH RBP 138 0x1 4889e5 MOV RBP, RSP 139 0x4 803d65e9400001 CMP BYTE [RIP+0x40e965], 0x1 140 0xb 7518 JNZ 0xff80090a7f95 141 0xd 488d3dee802900 LEA RDI, [RIP+0x2980ee] 142 0x14 488d35f5802900 LEA RSI, [RIP+0x2980f5] 143 0x1b baf3000000 MOV EDX, 0xf3 144 145 # This is a call to kernel!panic (later kernel!assfail): 146 0x20 e80b6c1400 CALL 0xff80091eeba0 147 148 # This is where it starts initializing the linked list: 149 0x25 48c70548e94000000000 MOV QWORD [RIP+0x40e948], 0x0 150 00 151 0x30 488d0d0e812900 LEA RCX, [RIP+0x29810e] 152 """ 153 name = "disassembled_llinfo_arp" 154 155 PANIC_FUNCTIONS = (u"__kernel__!_panic", u"__kernel__!_assfail") 156
157 - def calculate(self):
158 resolver = self.session.address_resolver 159 arp_init = resolver.get_constant_object("__kernel__!_arp_init", 160 target="Function") 161 instructions = iter(arp_init.Decompose(20)) 162 163 # Walk down to the CALL mnemonic and use the address resolver to 164 # see if it calls one of the panic functions. 165 for instruction in instructions: 166 # Keep spinning until we get to the first CALL. 167 if instruction.mnemonic != "CALL": 168 continue 169 170 # This is absolute: 171 target = instruction.operands[0].value 172 _, names = resolver.get_nearest_constant_by_address(target) 173 if not names: 174 return obj.NoneObject("Could not find CALL in arp_init.") 175 176 if names[0] not in self.PANIC_FUNCTIONS: 177 return obj.NoneObject( 178 "CALL was to %r, which is not on the PANIC list." 179 % names) 180 181 # We verified it's the right CALL. MOV should be right after it, 182 # so let's just grab it. 183 mov_instruction = next(instructions) 184 if mov_instruction.mnemonic != "MOV": 185 return obj.NoneObject("arp_init code changed.") 186 187 offset = (mov_instruction.operands[0].disp 188 + mov_instruction.address 189 + mov_instruction.size) 190 address = self.session.profile.Object(type_name="address", 191 offset=offset) 192 193 llinfo_arp = self.session.profile.Object( 194 type_name="llinfo_arp", 195 offset=address.v()) 196 197 if llinfo_arp.isvalid: 198 return llinfo_arp.obj_offset 199 200 return obj.NoneObject("llinfo_arp didn't validate.")
201
202 203 -class DarwinArp(common.AbstractDarwinProducer):
204 """Show information about arp tables.""" 205 206 name = "arp" 207 type_name = "rtentry" 208
209 - def collect(self):
210 llinfo_arp = self.session.address_resolver.get_constant_object( 211 "__kernel__!_llinfo_arp", 212 target="Pointer", 213 target_args=dict(target="llinfo_arp")) 214 215 if not llinfo_arp: 216 # Must not have it in the profile. Try asking the session hook 217 # for the address. 218 offset = self.session.GetParameter("disassembled_llinfo_arp") 219 if not offset: 220 self.session.logging.error( 221 "Could not find the address of llinfo_arp.") 222 return 223 224 llinfo_arp = self.session.profile.Object( 225 type_name="llinfo_arp", offset=offset) 226 227 for arp_hit in llinfo_arp.walk_list("la_le.le_next"): 228 yield [arp_hit.la_rt]
229
230 231 -class DarwinRoute(common.AbstractDarwinCommand):
232 """Show routing table.""" 233 234 __name = "route" 235 236 RNF_ROOT = 2 237
238 - def rn_walk_tree(self, h):
239 """Walks the radix tree starting from the header h. 240 241 This function is taken from 242 xnu-2422.1.72/bsd/net/radix.c: rn_walk_tree() 243 244 Which is why it does not conform to the style guide. 245 246 Note too that the darwin source code abuses C macros: 247 248 #define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey 249 #define rn_key rn_u.rn_leaf.rn_Key 250 #define rn_mask rn_u.rn_leaf.rn_Mask 251 #define rn_offset rn_u.rn_node.rn_Off 252 #define rn_left rn_u.rn_node.rn_L 253 #define rn_right rn_u.rn_node.rn_R 254 255 And then the original code does: 256 rn = rn.rn_left 257 258 So we replace these below. 259 """ 260 rn = h.rnh_treetop 261 262 seen = set() 263 264 # First time through node, go left */ 265 while rn.rn_bit >= 0: 266 rn = rn.rn_u.rn_node.rn_L 267 268 while rn and rn not in seen: 269 base = rn 270 271 seen.add(rn) 272 273 # If at right child go back up, otherwise, go right 274 while (rn.rn_parent.rn_u.rn_node.rn_R == rn and 275 not rn.rn_flags & self.RNF_ROOT): 276 rn = rn.rn_parent 277 278 # Find the next *leaf* to start from 279 rn = rn.rn_parent.rn_u.rn_node.rn_R 280 while rn.rn_bit >= 0: 281 rn = rn.rn_u.rn_node.rn_L 282 283 next = rn 284 285 # Process leaves 286 while True: 287 rn = base 288 if not rn: 289 break 290 291 base = rn.rn_u.rn_leaf.rn_Dupedkey 292 if not rn.rn_flags & self.RNF_ROOT: 293 yield rn 294 295 rn = next 296 if rn.rn_flags & self.RNF_ROOT: 297 return
298
299 - def render(self, renderer):
300 renderer.table_header( 301 [("Source IP", "source", "20"), 302 ("Dest IP", "dest", "20"), 303 ("Interface", "interface", "9"), 304 ("Sent", "sent", "8"), 305 ("Recv", "recv", "8"), 306 ("Time", "timestamp", "24"), 307 ("Expires", "expires", "8"), 308 ("Delta", "delta", "8")]) 309 310 route_tables = self.profile.get_constant_object( 311 "_rt_tables", 312 target="Array", 313 target_args=dict( 314 count=32, 315 target="Pointer", 316 target_args=dict( 317 target="radix_node_head"))) 318 319 for node in self.rn_walk_tree(route_tables[2]): 320 rentry = node.dereference_as("rtentry") 321 322 renderer.table_row( 323 rentry.source_ip, 324 rentry.dest_ip, 325 rentry.name, 326 rentry.sent, rentry.rx, 327 rentry.base_calendartime, 328 rentry.rt_expire, 329 rentry.delta)
330
331 332 -class DarwinIfnetHook(common.AbstractDarwinParameterHook):
333 """Walks the global list of interfaces. 334 335 The head of the list of network interfaces is a kernel global [1]. 336 The struct we use [2] is just the public part of the data [3]. Addresses 337 are related to an interface in a N:1 relationship [4]. AF-specific data 338 is a normal sockaddr struct. 339 340 References: 341 1: 342 https://github.com/opensource-apple/xnu/blob/10.9/bsd/net/dlil.c#L254 343 2: 344 https://github.com/opensource-apple/xnu/blob/10.9/bsd/net/if_var.h#L528 345 3: 346 https://github.com/opensource-apple/xnu/blob/10.9/bsd/net/dlil.c#L188 347 4: 348 https://github.com/opensource-apple/xnu/blob/10.9/bsd/net/if_var.h#L816 349 """ 350 351 name = "ifconfig" 352 353 # ifnet_head is the actual extern holding ifnets and seems to be an 354 # improvement over dlil_ifnet_head, which is a static and used only in the 355 # dlil (stands for data link interface, I think?) module. 356 IFNET_HEAD_NAME = ("_ifnet_head", "_dlil_ifnet_head") 357
358 - def calculate(self):
359 ifnet_head = obj.NoneObject("No ifnet global names given.") 360 for name in self.IFNET_HEAD_NAME: 361 ifnet_head = self.session.profile.get_constant_object( 362 name, 363 target="Pointer", 364 target_args=dict( 365 target="ifnet")) 366 367 if ifnet_head: 368 break 369 370 return [x.obj_offset for x in ifnet_head.walk_list("if_link.tqe_next")]
371
372 373 -class DarwinIfnetCollector(common.AbstractDarwinCachedProducer):
374 name = "ifconfig" 375 type_name = "ifnet"
376
377 378 -class DarwinIPFilters(common.AbstractDarwinCommand):
379 """Check IP Filters for hooks.""" 380 381 __name = "ip_filters" 382
383 - def render(self, renderer):
384 renderer.table_header([ 385 ("Context", "context", "10"), 386 ("Filter", "filter", "16"), 387 ("Handler", "handler", "[addrpad]"), 388 ("Symbol", "symbol", "20")]) 389 390 resolver = self.session.address_resolver 391 for list_name in ["_ipv4_filters", "_ipv6_filters"]: 392 filter_list = self.profile.get_constant_object( 393 list_name, target="ipfilter_list") 394 395 for item in filter_list.tqh_first.walk_list("ipf_link.tqe_next"): 396 filter = item.ipf_filter 397 name = filter.name.deref() 398 handler = filter.ipf_input.deref() 399 renderer.table_row("INPUT", name, handler, 400 resolver.format_address(handler)) 401 402 handler = filter.ipf_output.deref() 403 renderer.table_row("OUTPUT", name, handler, 404 resolver.format_address(handler)) 405 406 handler = filter.ipf_detach.deref() 407 renderer.table_row("DETACH", name, handler, 408 resolver.format_address(handler))
409