|
| 1 | +import sys, pydoc, struct |
| 2 | +import volatility.utils as utils |
| 3 | +import volatility.registry as registry |
| 4 | +import volatility.obj as obj |
| 5 | +import volatility.win32.modules as modules |
| 6 | +import volatility.win32.tasks as tasks |
| 7 | +import volatility.plugins.ssdt as ssdt |
| 8 | +import volatility.plugins.taskmods as taskmods |
| 9 | +import volatility.plugins.modscan as modscan |
| 10 | +import volatility.plugins.malware.malfind as malfind |
| 11 | +import volatility.debug as debug |
| 12 | +import volatility.plugins.malware.threads as threads |
| 13 | + |
| 14 | +enumerated_processes = {} |
| 15 | +kernel_modules = {} |
| 16 | + |
| 17 | +class CallstackItem(): |
| 18 | + """ a class that describes an item retreived from the callstack""" |
| 19 | + def __init__(self, ret_address, frame_address): |
| 20 | + self.ret_address = ret_address |
| 21 | + self.frame_address = frame_address |
| 22 | + self.owning_module_name = None |
| 23 | + self.owning_module_address = None |
| 24 | + self.function = None |
| 25 | + self.comment = "" |
| 26 | + |
| 27 | +class ThreadModule(): |
| 28 | + """ a class that describes a loaded module""" |
| 29 | + def __init__(self, ldr_object): |
| 30 | + self.ldr_object = ldr_object |
| 31 | + self.functions = None |
| 32 | + |
| 33 | +class ModuleFunction(): |
| 34 | + """ a class that describes an exported function""" |
| 35 | + def __init__(self, name, address): |
| 36 | + self.name = name |
| 37 | + self.address = address |
| 38 | + |
| 39 | +class ThreadCallstack(): |
| 40 | + """ a class that describes a threads callstack""" |
| 41 | + def __init__(self, thread): |
| 42 | + self.callstack = [] |
| 43 | + self.eip = False |
| 44 | + self.bit32 = True |
| 45 | + self.thread = thread |
| 46 | + self.mal_pattern = [] |
| 47 | + |
| 48 | + def add_callstack_item(self, item): |
| 49 | + # adds a CallstackItem to the threads list |
| 50 | + self.callstack.append(item) |
| 51 | + return True |
| 52 | + |
| 53 | + def set_bits(self, s): |
| 54 | + if s == "32": |
| 55 | + self.bits32 = True |
| 56 | + elif s == "64": |
| 57 | + self.bits32 = False |
| 58 | + |
| 59 | +def parse_callstack(thread, bits32, config): |
| 60 | + # extract the threads callstack by walking saved frames |
| 61 | + |
| 62 | + ## ---------- 0x00001234 |
| 63 | + ## /--| ebp | |
| 64 | + ## | ---------- |
| 65 | + ## | |ret_addr| |
| 66 | + ## | ---------- |
| 67 | + ## | | data | |
| 68 | + ## | ---------- |
| 69 | + ## | | data | |
| 70 | + ## | ---------- |
| 71 | + ## \->| ebp |-----\ |
| 72 | + ## ---------- | |
| 73 | + ## |ret_addr| | |
| 74 | + ## ---------- | |
| 75 | + ## | data | | |
| 76 | + ## ---------- | |
| 77 | + ## | data | | |
| 78 | + ## ---------- | |
| 79 | + ## | ebp |<----/ |
| 80 | + ## ---------- |
| 81 | + ## |ret_addr| |
| 82 | + ## ---------- 0x0000125c |
| 83 | + |
| 84 | + # for each ebp that is retreived the function saves the |
| 85 | + # data at [ebp+4] which is the return address and continues |
| 86 | + # to the next ebp. |
| 87 | + |
| 88 | + # this function does not work for 64bit since building frames on the stack by pushing |
| 89 | + # rbp is not a must in the _fastcall |
| 90 | + |
| 91 | + thread_callstack = ThreadCallstack(thread) |
| 92 | + trapframe = thread.Tcb.TrapFrame.dereference_as("_KTRAP_FRAME") |
| 93 | + current_ebp = 0 |
| 94 | + call_address = 0 |
| 95 | + ip_added = False |
| 96 | + |
| 97 | + if bits32: |
| 98 | + pointer_size = 4 |
| 99 | + unpack_size = "I" |
| 100 | + else: |
| 101 | + pointer_size = 8 |
| 102 | + unpack_size = "Q" |
| 103 | + |
| 104 | + if trapframe: |
| 105 | + address_space = thread.owning_process().get_process_address_space() |
| 106 | + if bits32: |
| 107 | + thread_callstack.set_bits("32") |
| 108 | + current_ebp = trapframe.Ebp |
| 109 | + thread_callstack.add_callstack_item(CallstackItem(trapframe.Eip, trapframe.Eip)) |
| 110 | + thread_callstack.eip = True |
| 111 | + |
| 112 | + |
| 113 | + while current_ebp: |
| 114 | + if address_space.is_valid_address(current_ebp) and address_space.is_valid_address(current_ebp + pointer_size): |
| 115 | + call_address = struct.unpack(unpack_size, address_space.zread(current_ebp + pointer_size, pointer_size))[0] |
| 116 | + current_ebp = struct.unpack(unpack_size, address_space.zread(current_ebp, pointer_size))[0] |
| 117 | + |
| 118 | + if len(thread_callstack.callstack) > 1: |
| 119 | + if current_ebp == thread_callstack.callstack[-1].frame_address: |
| 120 | + break |
| 121 | + if call_address: |
| 122 | + thread_callstack.add_callstack_item(CallstackItem(call_address, current_ebp)) |
| 123 | + else: |
| 124 | + call_address = None |
| 125 | + current_ebp = None |
| 126 | + thread_callstack.callstack = parse_callstack_items_address(thread, thread_callstack.callstack, config) |
| 127 | + else: |
| 128 | + |
| 129 | + thread_callstack.set_bits("64") |
| 130 | + current_ebp = trapframe.Rbp |
| 131 | + thread_callstack.add_callstack_item(CallstackItem(trapframe.Rip, trapframe.Rip)) |
| 132 | + thread_callstack.eip = True |
| 133 | + |
| 134 | + while current_ebp: |
| 135 | + if address_space.is_valid_address(current_ebp) and address_space.is_valid_address(current_ebp + pointer_size): |
| 136 | + call_address = struct.unpack(unpack_size, address_space.zread(current_ebp + pointer_size, pointer_size))[0] |
| 137 | + current_ebp = struct.unpack(unpack_size, address_space.zread(current_ebp, pointer_size))[0] |
| 138 | + |
| 139 | + if len(thread_callstack.callstack) > 1: |
| 140 | + if current_ebp == thread_callstack.callstack[-1].frame_address: |
| 141 | + break |
| 142 | + if call_address: |
| 143 | + thread_callstack.callstack.add_callstack_item(CallstackItem(call_address, current_ebp)) |
| 144 | + else: |
| 145 | + call_address = None |
| 146 | + current_ebp = None |
| 147 | + thread_callstack.callstack = parse_callstack_items_address(thread, thread_callstack.callstack, config) |
| 148 | + |
| 149 | + return thread_callstack |
| 150 | + |
| 151 | +def parse_callstack_items_address(thread, callstack, config): |
| 152 | + # for each callstack item eg. 0x7f801234 figure out in |
| 153 | + # which module and function the address resides |
| 154 | + |
| 155 | + modules = get_thread_modules(thread, config) |
| 156 | + |
| 157 | + for item in callstack: |
| 158 | + current_module = None |
| 159 | + current_function = None |
| 160 | + |
| 161 | + for mod in modules: |
| 162 | + |
| 163 | + if mod.ldr_object.DllBase < item.ret_address: |
| 164 | + if item.ret_address > mod.ldr_object.DllBase and item.ret_address < (mod.ldr_object.DllBase + mod.ldr_object.SizeOfImage): |
| 165 | + current_module = mod |
| 166 | + else: |
| 167 | + break |
| 168 | + |
| 169 | + if current_module: |
| 170 | + item.owning_module_name = current_module.ldr_object.BaseDllName |
| 171 | + item.owning_module_address = current_module.ldr_object.DllBase |
| 172 | + |
| 173 | + for function in current_module.functions: |
| 174 | + if function.address < item.ret_address: |
| 175 | + current_function = function |
| 176 | + else: |
| 177 | + break |
| 178 | + else: |
| 179 | + item.owning_module_name = "Unknown" |
| 180 | + item.owning_module_address = 0 |
| 181 | + |
| 182 | + if current_function: |
| 183 | + item.function = current_function |
| 184 | + else: |
| 185 | + item.function = ModuleFunction("Unknown", item.ret_address) |
| 186 | + return callstack |
| 187 | + |
| 188 | +def get_module_exports(thread, mod): |
| 189 | + # retreive exports from module |
| 190 | + |
| 191 | + functions = [] |
| 192 | + if mod: |
| 193 | + for _, f, n in mod.exports(): |
| 194 | + if n: |
| 195 | + functions.append(ModuleFunction(str(n), mod.DllBase + f)) |
| 196 | + else: |
| 197 | + functions.append(ModuleFunction("Unknown", mod.DllBase + f)) |
| 198 | + return functions |
| 199 | + |
| 200 | +def get_thread_modules(thread, config): |
| 201 | + # get the loaded modules of the process containing the thread |
| 202 | + # this function also pays respect to already gathered modules |
| 203 | + # for increased performance |
| 204 | + |
| 205 | + global kernel_modules |
| 206 | + global enumerated_processes |
| 207 | + |
| 208 | + thread_modules = [] |
| 209 | + user_modules = [] |
| 210 | + |
| 211 | + addr_space = utils.load_as(config) |
| 212 | + system_range = tasks.get_kdbg(addr_space).MmSystemRangeStart.dereference_as("Pointer") |
| 213 | + |
| 214 | + if len(kernel_modules) == 0: |
| 215 | + for mod in modules.lsmod(addr_space): |
| 216 | + if mod: |
| 217 | + thread_modules.append(ThreadModule(mod)) |
| 218 | + thread_modules[-1].functions = get_module_exports(thread, thread_modules[-1].ldr_object) |
| 219 | + thread_modules[-1].functions = sorted(thread_modules[-1].functions, key = lambda item: item.address) |
| 220 | + thread_modules = sorted(thread_modules, key = lambda item: item.ldr_object.DllBase) |
| 221 | + kernel_modules = thread_modules |
| 222 | + else: |
| 223 | + pass |
| 224 | + |
| 225 | + owning_process = thread.owning_process() |
| 226 | + if not owning_process.is_valid(): |
| 227 | + owner = None |
| 228 | + else: |
| 229 | + try: |
| 230 | + user_modules = enumerated_processes[owning_process.obj_offset] |
| 231 | + except KeyError: |
| 232 | + for mod in owning_process.get_load_modules(): |
| 233 | + if mod: |
| 234 | + user_modules.append(ThreadModule(mod)) |
| 235 | + user_modules[-1].functions = get_module_exports(thread, user_modules[-1].ldr_object) |
| 236 | + user_modules[-1].functions = sorted(user_modules[-1].functions, key = lambda item: item.address) |
| 237 | + user_modules = sorted(user_modules, key = lambda item: item.ldr_object.DllBase) |
| 238 | + enumerated_processes[owning_process.obj_offset] = user_modules |
| 239 | + |
| 240 | + thread_modules = user_modules + kernel_modules |
| 241 | + return thread_modules |
| 242 | + |
| 243 | + |
| 244 | +class Callstacks(taskmods.DllList): |
| 245 | + """ this is the plugin class for callstacks """ |
| 246 | + def __init__(self, config, *args, **kwargs): |
| 247 | + taskmods.DllList.__init__(self, config, *args, **kwargs) |
| 248 | + self.pidlist = None |
| 249 | + if self._config.PID is not None: |
| 250 | + try: |
| 251 | + self.pidlist = map(int, self._config.PID.split(',')) |
| 252 | + except ValueError: |
| 253 | + return |
| 254 | + |
| 255 | + self.bits32 = None |
| 256 | + |
| 257 | + def calculate(self): |
| 258 | + thread_start_module = None |
| 259 | + thread_start_function = None |
| 260 | + # Checks that subclass AbstractThreadCheck |
| 261 | + checks = registry.get_plugin_classes(threads.AbstractThreadCheck) |
| 262 | + |
| 263 | + addr_space = utils.load_as(self._config) |
| 264 | + |
| 265 | + # Are we on x86 or x64. Save this for render_text |
| 266 | + self.bits32 = addr_space.profile.metadata.\ |
| 267 | + get("memory_model", "32bit") == "32bit" |
| 268 | + |
| 269 | + seen_threads = dict() |
| 270 | + |
| 271 | + # Gather threads by list traversal of active/linked processes |
| 272 | + for task in taskmods.DllList(self._config).calculate(): |
| 273 | + for thread in task.ThreadListHead.\ |
| 274 | + list_of_type("_ETHREAD", "ThreadListEntry"): |
| 275 | + seen_threads[thread.obj_vm.vtop(thread.obj_offset)] = (False, thread) |
| 276 | + |
| 277 | + # Now scan for threads and save any that haven't been seen |
| 278 | + for thread in modscan.ThrdScan(self._config).calculate(): |
| 279 | + if not seen_threads.has_key(thread.obj_offset): |
| 280 | + seen_threads[thread.obj_offset] = (True, thread) |
| 281 | + |
| 282 | + # Keep a record of processes whose DLLs we've already enumerated |
| 283 | + process_dll_info = {} |
| 284 | + |
| 285 | + for _offset, (found_by_scanner, thread) in seen_threads.items(): |
| 286 | + if self.pidlist: |
| 287 | + if not thread.attached_process().UniqueProcessId in self.pidlist: |
| 288 | + continue |
| 289 | + thread_callstack = parse_callstack(thread, self.bits32, self._config) |
| 290 | + |
| 291 | + yield thread, addr_space, \ |
| 292 | + thread_start_function, thread_callstack |
| 293 | + |
| 294 | + def render_text(self, outfd, data): |
| 295 | + for thread, addr_space, \ |
| 296 | + thread_start_function, thread_callstack in data: |
| 297 | + |
| 298 | + s = "\n------\n\n" |
| 299 | + |
| 300 | + s += "ETHREAD: {0:#010x} Pid: {1} Tid: {2}\n".format( |
| 301 | + thread.obj_offset, |
| 302 | + thread.Cid.UniqueProcess, thread.Cid.UniqueThread) |
| 303 | + |
| 304 | + s += "Owning Process: {0}\n".format( |
| 305 | + thread.owning_process().ImageFileName) |
| 306 | + |
| 307 | + s += "Attached Process: {0}\n".format( |
| 308 | + thread.attached_process().ImageFileName) |
| 309 | + |
| 310 | + s += "Thread Flags: {0}\n".format(str(thread.CrossThreadFlags)) |
| 311 | + |
| 312 | + |
| 313 | + |
| 314 | + |
| 315 | + if len(thread_callstack.callstack) > 0: |
| 316 | + |
| 317 | + s += "\nCallstack:\n" |
| 318 | + if thread_callstack.eip: |
| 319 | + s += "\t{0:<8} {3:<8} {1:<8} {2}\n".format("No.", "RetAddr", "Function", "Ebp") |
| 320 | + s += "\t{0:<8} 0x{5:08x} 0x{1:08x} {2}!{3}+0x{4:<8x}\n".format("[eip]", thread_callstack.callstack[0].function.address, |
| 321 | + thread_callstack.callstack[0].owning_module_name, thread_callstack.callstack[0].function.name, |
| 322 | + thread_callstack.callstack[0].ret_address - thread_callstack.callstack[0].function.address, 0) |
| 323 | + thread_callstack.callstack.remove(thread_callstack.callstack[0]) |
| 324 | + |
| 325 | + i = 0 |
| 326 | + for item in thread_callstack.callstack: |
| 327 | + s += "\t{0:<8} 0x{5:08x} 0x{1:08x} {2}!{3}+0x{4:<8x}\n".format("[" + str(i) + "]", item.function.address, |
| 328 | + item.owning_module_name, item.function.name, |
| 329 | + item.ret_address - item.function.address, item.frame_address) |
| 330 | + i += 1 |
| 331 | + else: |
| 332 | + s += "Couldn't acquire threads _KTRAP_FRAME\n" |
| 333 | + |
| 334 | + outfd.write("{0}\n".format(s)) |
| 335 | + |
0 commit comments