Files
2017-08-18 16:03:50 -05:00

447 lines
22 KiB
Python

# Volatility
# Copyright (C) 2007-2013 Volatility Foundation
#
# This file is part of Volatility.
#
# Volatility is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License Version 2 as
# published by the Free Software Foundation. You may not use, modify or
# distribute this program under any other version of the GNU General
# Public License.
#
# Volatility is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
#
"""
@author: Cem Gurkok
@license: GNU General Public License 2.0
@contact: cemgurkok@gmail.com
@organization:
"""
import volatility.obj as obj
import volatility.plugins.mac.common as common
import volatility.debug as debug
import volatility.utils as utils
import volatility.plugins.mac.pstasks as mac_tasks
from volatility.renderers import TreeGrid
from volatility.renderers.basic import Address
## http://hte.sourceforge.net/doxygenized-0.8.0pre1/machostruc_8h-source.html
## documentation for thread state, registry, launch cmd etc
thread_overlay = {
"thread": [ None, {
"options": [None, ['Flags', {'target': 'int', 'bitmap': {
"TH_OPT_INTMASK": 0,# interrupt / abort level
"TH_OPT_INTMASK": 1,# interrupt / abort level
"TH_OPT_VMPRIV": 2, # may allocate reserved memory
"TH_OPT_DTRACE": 3, # executing under dtrace_probe
"TH_OPT_SYSTEM_CRITICAL": 4, # Thread must always be allowed to run, even under heavy load
"TH_OPT_PROC_CPULIMIT": 5, # Thread has a task-wide CPU limit applied to it
"TH_OPT_PRVT_CPULIMIT": 6 # Thread has a thread-private CPU limit applied to it
}}]],
"state": [None, ['Flags', {'target': 'int', 'bitmap': {
"TH_WAIT": 0,
"TH_SUSP": 1,
"TH_RUN": 2,
"TH_UNINT": 3,
"TH_TERMINATE": 4,
"TH_TERMINATE2": 5,
"TH_IDLE": 6, # kAppleProfileTriggerClientThreadModeIdle
"TH_IDLE_N": 6 << 16 # kAppleProfileTriggerClientThreadModeNotIdle, !TH_IDLE
}}]],
"sched_mode": [None, ['Flags', {'target': 'int', 'bitmap': {
"TH_MODE_REALTIME": 0, # /* time constraints supplied */
"TH_MODE_TIMESHARE": 1, # /* use timesharing algorithm */
"TH_MODE_FAILSAFE": 2, # /* fail-safe has tripped */
"TH_MODE_PROMOTED": 3, # /* sched pri has been promoted */
"TH_MODE_ABORT": 4, # /* abort interruptible waits */
"TH_MODE_ABORTSAFELY": 5, # /* ... but only those at safe point */
# "TH_MODE_ISABORTED": (TH_MODE_ABORT | TH_MODE_ABORTSAFELY)
"TH_MODE_DEPRESS": 6, # /* normal depress yield */
"TH_MODE_POLLDEPRESS": 7, # /* polled depress yield */
# "TH_MODE_ISDEPRESSED": (TH_MODE_DEPRESS | TH_MODE_POLLDEPRESS)
}}]],
"ast": [None, ['Flags', {'target': 'int', 'bitmap': { # Asynchronous System Traps
# AST_NONE , no bits set
"AST_HALT": 0,
"AST_TERMINATE": 1,
"AST_BLOCK": 2,
"AST_UNUSED": 3,
"AST_QUANTUM": 4,
"AST_APC": 5, # /* migration APC hook */
"AST_URGENT": 6
}}]],
}]
}
class queue_entry(obj.CType):
# needed a separate walk_list function for threads since the original was task specific
def thread_walk_list(self, list_head):
n = self.next.dereference_as("thread")
while n and n.obj_offset != list_head:
yield n
n = n.task_threads.next.dereference_as("thread")
p = self.prev.dereference_as("thread")
while p and p.obj_offset != list_head:
yield p
p = p.task_threads.prev.dereference_as("thread")
def walk_list(self, list_head):
n = self.next.dereference_as("task")
while n and n.obj_offset != list_head:
yield n
n = n.tasks.next.dereference_as("task")
p = self.prev.dereference_as("task")
while p and p.obj_offset != list_head:
yield p
p = p.tasks.prev.dereference_as("task")
class MacObjectClasses2(obj.ProfileModification):
conditions = {'os': lambda x: x == 'mac'}
before = ['BasicObjectClasses']
def modification(self, profile):
profile.object_classes.update({
'queue_entry' : queue_entry
})
class MacObjectClasses4(obj.ProfileModification):
conditions = {'os': lambda x: x == 'mac'}
before = ['BasicObjectClasses']
def modification(self, profile):
profile.merge_overlay(thread_overlay)
# https://www.opensource.apple.com/source/xnu/xnu-124.1/osfmk/mach/vm_statistics.h
dict_alias = {
1: "VM_MEMORY_MALLOC",
2: "VM_MEMORY_MALLOC_SMALL",
3: "VM_MEMORY_MALLOC_LARGE",
4: "VM_MEMORY_MALLOC_HUGE",
5: "VM_MEMORY_SBRK",
6: "VM_MEMORY_REALLOC",
7: "VM_MEMORY_MALLOC_TINY",
8: "VM_MEMORY_MALLOC_LARGE_REUSABLE",
9: "VM_MEMORY_MALLOC_LARGE_REUSED",
10: "VM_MEMORY_ANALYSIS_TOOL",
20: "VM_MEMORY_MACH_MSG",
21: "VM_MEMORY_IOKIT",
30: "VM_MEMORY_STACK",
31: "VM_MEMORY_GUARD",
32: "VM_MEMORY_SHARED_PMAP",
33: "VM_MEMORY_DYLIB",
34: "VM_MEMORY_OBJC_DISPATCHERS",
35: "VM_MEMORY_UNSHARED_PMAP",
40: "VM_MEMORY_APPKIT",
41: "VM_MEMORY_FOUNDATION",
42: "VM_MEMORY_COREGRAPHICS",
43: "VM_MEMORY_CORESERVICES",
44: "VM_MEMORY_JAVA",
50: "VM_MEMORY_ATS",
51: "VM_MEMORY_LAYERKIT",
52: "VM_MEMORY_CGIMAGE",
53: "VM_MEMORY_TCMALLOC",
54: "VM_MEMORY_COREGRAPHICS_DATA",
55: "VM_MEMORY_COREGRAPHICS_SHARED",
56: "VM_MEMORY_COREGRAPHICS_FRAMEBUFFERS",
57: "VM_MEMORY_COREGRAPHICS_BACKINGSTORES",
60: "VM_MEMORY_DYLD",
61: "VM_MEMORY_DYLD_MALLOC",
62: "VM_MEMORY_SQLITE",
63: "VM_MEMORY_JAVASCRIPT_CORE",
64: "VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR",
65: "VM_MEMORY_JAVASCRIPT_JIT_REGISTER_FILE",
66: "VM_MEMORY_GLSL",
67: "VM_MEMORY_OPENCL",
68: "VM_MEMORY_COREIMAGE",
69: "VM_MEMORY_WEBCORE_PURGEABLE_BUFFERS",
70: "VM_MEMORY_IMAGEIO",
71: "VM_MEMORY_COREPROFILE",
72: "VM_MEMORY_ASSETSD",
240: "VM_MEMORY_APPLICATION_SPECIFIC_1",
241: "VM_MEMORY_APPLICATION_SPECIFIC_2",
242: "VM_MEMORY_APPLICATION_SPECIFIC_3",
243: "VM_MEMORY_APPLICATION_SPECIFIC_4",
244: "VM_MEMORY_APPLICATION_SPECIFIC_5",
245: "VM_MEMORY_APPLICATION_SPECIFIC_6",
246: "VM_MEMORY_APPLICATION_SPECIFIC_7",
247: "VM_MEMORY_APPLICATION_SPECIFIC_8",
248: "VM_MEMORY_APPLICATION_SPECIFIC_9",
249: "VM_MEMORY_APPLICATION_SPECIFIC_10",
250: "VM_MEMORY_APPLICATION_SPECIFIC_11",
251: "VM_MEMORY_APPLICATION_SPECIFIC_12",
252: "VM_MEMORY_APPLICATION_SPECIFIC_13",
253: "VM_MEMORY_APPLICATION_SPECIFIC_14",
254: "VM_MEMORY_APPLICATION_SPECIFIC_15",
255: "VM_MEMORY_APPLICATION_SPECIFIC_16"
}
class mac_threads(mac_tasks.mac_tasks):
""" List Process Threads """
def get_active_threads(self):
threads = {}
real_ncpus = obj.Object("int", offset = self.addr_space.profile.get_symbol("_real_ncpus"), vm = self.addr_space)
cpu_data_ptrs = obj.Object(theType = 'Array', offset = self.addr_space.profile.get_symbol("_cpu_data_ptr"), vm = self.addr_space, targetType = "unsigned long long", count = real_ncpus)
for i in range(0, real_ncpus):
cpu_data = obj.Object('cpu_data', offset = cpu_data_ptrs[i], vm = self.addr_space)
threads[i] = cpu_data.cpu_active_thread
return threads
def is_thread_active(self, thread, active_threads):
for active_thread in active_threads.values():
if active_thread.v() == thread.v():
return True
return False
def get_stack_map(self, proc, proc_threads, bit_string):
proc_addrspace = proc.get_process_address_space()
vm_map_slide = 0
vm_map_start = 0
stack_thread_id = None
maps = []
for map in proc.get_proc_maps():
vm_map_start = map.links.start
map_type = str(dict_alias.get(int(map.range_alias()), "UNKNOWN"))
map_path = map.get_path()
# see if map is a STACK (not a STACK_GUARD), if so which thread it belongs to
# VM_MEMORY_STACK
if map_type == "VM_MEMORY_STACK" and map.get_perms() != "---":
for thread in proc_threads:
# 64bit thread
if "64" in bit_string:
# isf: interrupt stack frame
thread_sp = thread.machine.iss.uss.ss_64.isf.rsp
# 32 bit thread
else:
thread_sp = thread.machine.iss.uss.ss_32.uesp
if map.links.start <= thread_sp and thread_sp <= map.links.end:
stack_thread_id = thread.thread_id
map_path = "thread id {0}".format(thread.thread_id)
break
if "thread" in map_path:
# Based on the vmmap command:
# current map is a stack marked as thread, then mark previous map with thread id if stack
prev_proc, prev_map, prev_map_path = maps.pop()
if str(dict_alias.get(int(prev_map.range_alias()), "UNKNOWN")) == "VM_MEMORY_STACK" and prev_map.get_perms() != "---" and "thread" not in prev_map_path:
prev_map_path = "thread id {0}".format(stack_thread_id)
maps.append((prev_proc, prev_map, prev_map_path))
else:
# if previous map is a stack marked as thread, then mark current map with thread id
prev_proc, prev_map, prev_map_path = maps.pop()
if str(dict_alias.get(int(prev_map.range_alias()), "UNKNOWN")) == "VM_MEMORY_STACK" and prev_map.get_perms() != "---" and "thread" in prev_map_path:
map_path = "thread id {0}".format(stack_thread_id)
maps.append((prev_proc, prev_map, prev_map_path))
elif map_type != "VM_MEMORY_STACK":
stack_thread_id = None
maps.append((proc, map, map_path))
return maps
def get_thread_registers(self, thread, bit_string):
# http://www.opensource.apple.com/source/xnu/xnu-2050.18.24/osfmk/i386/pcb.c
registers_64 = ['rdi','rsi','rdx','rbp','rbx','rcx','rax','cr2','r8','r9','r10','r11','r12','r13','r14','r15','gs','fs']
registers_32 = ['edi','esi','edx','ebp','ebx','uesp','ecx','eax','eip','cr2','gs','cs','fs','es','ds']
registers = {}
if "64" in bit_string:
registers['rsp'] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_64.isf, 'rsp'))
registers['rip'] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_64.isf, 'rip'))
registers['ss'] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_64.isf, 'ss'))
registers['trapno'] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_64.isf, 'trapno'))
# check if trap function/sysent is known or hooked
trapfn_addr = getattr(thread.machine.iss.uss.ss_64.isf, 'trapfn')
if trapfn_addr == 0:
trapfn_name = ''
else:
trapfn_name = self.addr_space.profile.get_symbol_by_address('kernel', trapfn_addr)
if trapfn_name == '':
trapfn = "UNKNOWN function at {0}".format(trapfn_addr)
else:
trapfn = "{0} at {1:#10x}".format(trapfn_name, trapfn_addr)
registers['trapfn'] = trapfn
for reg in registers_64:
registers[reg] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_64, reg))
else:
for reg in registers_32:
if hasattr(thread.machine, "iss"):
registers[reg] = "{0:#10x}".format(getattr(thread.machine.iss.uss.ss_32, reg))
else:
registers[reg] = ""
return registers
def calculate(self):
common.set_plugin_members(self)
for proc in mac_tasks.mac_tasks(self._config).calculate():
bit_string = str(proc.task.map.pmap.pm_task_map or '')[9:]
# get proc args and arg address
args = proc.get_arguments()
args_addr = proc.user_stack - proc.p_argslen
# get threads
qentry = proc.task.threads
seen_threads = []
thread_list = []
active_threads = self.get_active_threads()
for thread in qentry.thread_walk_list(qentry.obj_offset):
if thread.obj_offset not in seen_threads:
seen_threads.append(thread.obj_offset)
thread_list.append(thread)
# get proc maps
maps = self.get_stack_map(proc, thread_list, bit_string)
# get thread stack start and size
for thread in thread_list:
stack_start = 0
stack_size = 0
thread_args = ""
registers = {}
is_active = "NO"
dtraced = "NO"
debugged = "NO"
uid = "NONE"
for proc, map, map_path in maps:
if "thread id {0}".format(thread.thread_id) in map_path:
if stack_start == 0 or stack_start > map.links.start:
stack_start = map.links.start
stack_size += map.links.end - map.links.start
# find thread with args, which probably is main thread
if map.links.start < args_addr < map.links.end:
thread_args = args
# kernel_stack process
# thread stack information is empty for kernel threads
if str(proc.p_pid) == "0":
stack_start = thread.kernel_stack
registers = self.get_thread_registers(thread, bit_string)
if self.is_thread_active(thread, active_threads):
is_active = "YES"
# check if thread is being hardware debugged, ids = x86_debug_state64
if thread.machine.ids != 0:
debugged = "YES"
# check if dtrace probe is applied
if "TH_OPT_DTRACE" in str(thread.options):
dtraced = "YES"
#get thread User ID
#if thread.uthread != 0:
#uid = thread.uthread.dereference_as('uthread').uu_context.vc_ucred.cr_posix.cr_uid
yield proc, thread, stack_start, stack_size, thread_args, registers, is_active, dtraced, debugged, uid
proc = proc.p_list.le_next.dereference()
self.get_active_threads()
def unified_output(self, data):
return TreeGrid([("Offset", Address),
("Pid", int),
("Tid", int),
("UID", str),
("State", str),
("Is Active?", str),
("Options", str),
("Priority", int),
("Startup Addr", Address),
("Stack Start Addr", Address),
("Stack Size (bytes)", int),
("HW Debugged",str),
("DTraced", str),
("Arguments", str),
], self.generator(data))
def generator(self, data):
for proc, thread, stack_start, stack_size, args, registers, is_active, dtraced, debugged, uid in data:
if not thread.is_valid():
continue
yield (0, [
Address(thread.v()),
int(proc.p_pid),
int(thread.thread_id),
str(uid),
str(thread.state),
str(is_active),
str(thread.options),
int(thread.sched_pri),
Address(thread.continuation),
Address(stack_start),
int(stack_size),
str(debugged),
str(dtraced),
str(args),
])
def render_text(self, outfd, data):
self.table_header(outfd, [("Offset", "[addrpad]"),
("Pid", "8"),
("Tid", "8"),
("UID", "8"),
("State", "30"),
("Is Active?","<10"),
("Options", "30"),
("Priority", "8"),
("Startup Addr", "[addrpad]"),
("Stack Start Addr", "[addrpad]"),
("Stack Size (bytes)", "<18"),
("HW Debugged","<11"),
("DTraced","<7"),
("Arguments", "")
])
for proc, thread, stack_start, stack_size, args, registers, is_active, dtraced, debugged, uid in data:
if not thread.is_valid():
continue
self.table_row(outfd, thread.v(),
str(proc.p_pid),
str(thread.thread_id),
str(uid),
str(thread.state),
is_active,
str(thread.options),
str(thread.sched_pri),
thread.continuation,
stack_start,
stack_size,
debugged,
dtraced,
args
)
#for reg in registers:
# outfd.write("\t{0:<10} {1:}\n".format(reg, registers[reg].strip()))