mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
447 lines
22 KiB
Python
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()))
|