From f1d440440c27e411f826b7220933ce8adb2dc235 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 16 Jan 2026 11:27:00 -0500 Subject: [PATCH 01/24] Benchmarking code --- plugins/volatility/volglue3.py | 230 +++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 plugins/volatility/volglue3.py diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py new file mode 100644 index 0000000..8c180ed --- /dev/null +++ b/plugins/volatility/volglue3.py @@ -0,0 +1,230 @@ +# This section sets up the volatility environment +# and is invoked by the plugin when it loads this +# script as a module +# +# It should be refactored so that the setup +# is done as a function call to make it cleaner +# and take the profile as an argument +import os +import json +import shutil +import hashlib +import tempfile +import traceback + +# import volatility3 +from volatility3.cli import text_renderer +from volatility3.plugins.windows import psscan, pslist, svcscan, netscan, vadinfo +from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer +from volatility3.framework import automagic, contexts, interfaces, plugins + + +def filter_invalid_ascii(strobj): + return strobj.encode("utf-8", "replace") + + +def is_interesting(eprocess, filter_data): + check_pid = int(eprocess.UniqueProcessId) + check_asid = int(eprocess.Pcb.DirectoryTableBase) + + for (pid, tid, asid) in filter_data["threads"]: + if check_pid == int(pid) and check_asid == int(asid): + return True + return False + + +def hash_file(filepath, *algorithms): + with open(filepath, "rb") as fobj: + while True: + chunk = fobj.read(4096) + if not chunk: + break + + for a in algorithms: + a.update(chunk) + + return {a.name: a.hexdigest().lower() for a in algorithms} + +def socket_visitor(node, accumulator): + if node.values: + proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] + sdata = { + "pid": pid, + "owner": owner, + "proto": proto, + "local_addr": laddr, + "local_port": lport, + "remote_addr": raddr, + "remote_port": rport, + "state": state, + } + accumulator.append(sdata) + return accumulator + +def pslist_visitor(node, accumulator): + if node.values: + pid, ppid, img_name, offset = node.values[0:4] + proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) + if img_name == "services.exe": + print(offset) + breakpoint() + sdata = { + "pid": int(pid), + "ppid": int(ppid), + "asid": int(proc.Pcb.DirectoryTableBase), + "ImagePathName": img_name, + } + accumulator.append(sdata) + return accumulator + + +def svcscan_visitor(node, accumulator): + if node.values: + print(node) + breakpoint() + + return accumulator + +def driverscan_visitor(node, accumulator): + if node.values: + print(node) + offset, start = node.values[:2] + name = node.values[-1] + drv_data = { + "offset": int(offset), + "start": int(start), + "name": str(name), + } + accumulator.append(drv_data) + breakpoint() + + return accumulator + + +def get_memory_hashes(filter_data): + + runner = vadinfo.VADDump(config) + + results = [] + for proc in runner.calculate(): + addr = proc.get_process_address_space() + + if not addr: + continue + + if is_interesting(proc, filter_data): + + for vad, _ in proc.get_vads( + vad_filter=lambda v: v.Length < pow(2, 30), skip_max_commit=True + ): + path = os.path.join( + config.DUMP_DIR, + "{}_{}.{}".format(vad.Start, vad.End, proc.UniqueProcessId), + ) + + runner.dump_vad(path, vad, addr) + + result = { + "pid": int(proc.UniqueProcessId), + "start": int(vad.Start), + "end": int(vad.End), + } + result.update(hash_file(path, hashlib.sha256())) + results.append(result) + + shutil.rmtree(config.DUMP_DIR) + + return results + + +def get_pslist(): + """List all the tasks that aren't hidden, unlinked, etc""" + config_path = "plugins.PsList" + automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=None) + # breakpoint() + treegrid = constructed.run() + pslist_data = [] + treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=pslist_data) + return pslist_data + # text_renderer.PrettyTextRenderer().render(treegrid) + +def get_psscan(): + """List all the tasks including hidden, unlinked, etc""" + config_path = "plugins.PsScan" + automagics = automagic.choose_automagic(available_automagics, psscan.PsScan) + constructed = plugins.construct_plugin(ctx, automagics, psscan.PsScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + psscan_data = [] + treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=psscan_data) + return psscan_data + # text_renderer.PrettyTextRenderer().render(treegrid) + +def get_svcscan(): + """List all of the system services""" + config_path = "plugins.SvcScan" + automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) + constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + svcscan_data = [] + treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) + driverscan_data = get_driverscan() + return svcscan_data + + +def get_driverscan(): + from volatility3.plugins.windows import driverscan + config_path = "plugins.DriverScan" + automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) + constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + driverscan_data = [] + treegrid.visit(node=None, function=driverscan_visitor, initial_accumulator=driverscan_data) + return driverscan_data + + +def get_sockets(): + """List all of the sockets that have not been unlinked or hidden""" + + # This only works for Vista and later + config_path = "plugins.NetScan" + automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) + constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + socket_data = [] + treegrid.visit(node=None, function=socket_visitor, initial_accumulator=socket_data) + return socket_data + + +def run(location, filterfile): + """Returns a list of the processes as a JSON string + + This analysis demonstrates that volatility can be successfully + invoked and that data can be serialized as JSON data and + returned to the plugin. + + """ + print("Volatility version: %r" % volatility3.framework.constants.VERSION) + try: + with open(filterfile, "rb") as fobj: + filter_data = json.load(fobj) + + analysis_results = { + "pslist": get_pslist(), + "svcscan": get_svcscan(), + "sockets": get_sockets(), + "process_hashes": get_process_hashes(filter_data), + "memory_hashes": get_memory_hashes(filter_data), + } + except Exception as err: + analysis_results = {"error": traceback.format_exc(err)} + + json_str = json.dumps(analysis_results, indent=1) + return json_str + +if __name__ == "__main__": + image = "mymem.dd" + ctx = contexts.Context() + ctx.config["automagic.LayerStacker.single_location"] = f"file:{image}" + available_automagics = automagic.available(ctx) + print(get_driverscan()) From a5f1950d9803596915b76ba3716413ef20d43012 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 16 Jan 2026 15:25:05 -0500 Subject: [PATCH 02/24] Commented breakpoints --- plugins/volatility/volglue3.py | 39 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 8c180ed..c073d50 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -12,7 +12,7 @@ import tempfile import traceback -# import volatility3 +import volatility3 from volatility3.cli import text_renderer from volatility3.plugins.windows import psscan, pslist, svcscan, netscan, vadinfo from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer @@ -68,26 +68,39 @@ def pslist_visitor(node, accumulator): if img_name == "services.exe": print(offset) breakpoint() - sdata = { + pdata = { "pid": int(pid), "ppid": int(ppid), "asid": int(proc.Pcb.DirectoryTableBase), "ImagePathName": img_name, } - accumulator.append(sdata) + accumulator.append(pdata) return accumulator def svcscan_visitor(node, accumulator): if node.values: - print(node) - breakpoint() - + # print(node) + offset = node.values[0] + pid = node.values[2] + state = node.values[4] + name, display_name = node.values[6:8] + + pid = str(pid) if type(pid) == volatility3.framework.renderers.NotApplicableValue else int(pid) + + svc_data = { + "ServiceName": name, + "DisplayName": display_name, + "State": state, + "Pid": pid, + "offset": int(offset), + } + accumulator.append(svc_data) + # breakpoint() return accumulator def driverscan_visitor(node, accumulator): if node.values: - print(node) offset, start = node.values[:2] name = node.values[-1] drv_data = { @@ -96,8 +109,7 @@ def driverscan_visitor(node, accumulator): "name": str(name), } accumulator.append(drv_data) - breakpoint() - + # breakpoint() return accumulator @@ -169,6 +181,12 @@ def get_svcscan(): svcscan_data = [] treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) driverscan_data = get_driverscan() + + for i in range(len(svcscan_data)): + for drv in driverscan_data: + if svcscan_data[i]["offset"] == drv["offset"]: + svcscan_data[i]["DriverName"] = drv["name"] + continue return svcscan_data @@ -186,7 +204,6 @@ def get_driverscan(): def get_sockets(): """List all of the sockets that have not been unlinked or hidden""" - # This only works for Vista and later config_path = "plugins.NetScan" automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) @@ -227,4 +244,4 @@ def run(location, filterfile): ctx = contexts.Context() ctx.config["automagic.LayerStacker.single_location"] = f"file:{image}" available_automagics = automagic.available(ctx) - print(get_driverscan()) + print(get_svcscan()) From 0ffc8d3895502033cd021659f9eb80f58ad3e9a3 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 16 Jan 2026 15:47:26 -0500 Subject: [PATCH 03/24] Explained location of import --- plugins/volatility/volglue3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index c073d50..0168ef4 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -191,7 +191,7 @@ def get_svcscan(): def get_driverscan(): - from volatility3.plugins.windows import driverscan + from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file config_path = "plugins.DriverScan" automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) From b135e208fbafabfe12553dbdd77e93c8349ad5ff Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Thu, 5 Feb 2026 16:14:55 -0500 Subject: [PATCH 04/24] Matched driver with service --- plugins/volatility/volglue3.py | 136 ++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 46 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 0168ef4..d5849ad 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -14,7 +14,7 @@ import volatility3 from volatility3.cli import text_renderer -from volatility3.plugins.windows import psscan, pslist, svcscan, netscan, vadinfo +from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer from volatility3.framework import automagic, contexts, interfaces, plugins @@ -45,6 +45,83 @@ def hash_file(filepath, *algorithms): return {a.name: a.hexdigest().lower() for a in algorithms} + +def get_process_hashes(): + config_path = "plugins.PEDump" + automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) + breakpoint() + constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + breakpoint() + + results = [] + for proc in runner.calculate(): + addr = proc.get_process_address_space() + + vtop_check = False + invalid = any( + [ + addr is None, + proc.Peb is None, + ] + ) + if proc.Peb: + vtop_check = addr.vtop(proc.Peb.ImageBaseAddress) is None + + if invalid: + if vtop_check: + continue + + if is_interesting(proc, filter_data): + name = str(proc.ImageFileName) + + runner.dump_pe(addr, proc.Peb.ImageBaseAddress, name) + + dumped_file = os.path.join(config.DUMP_DIR, name) + + result = { + "pid": int(proc.UniqueProcessId), + "base": int(proc.Peb.ImageBaseAddress), + } + result.update(hash_file(dumped_file, hashlib.sha256())) + results.append(result) + + return results + + +def get_memory_hashes(filter_data): + breakpoint() + runner = vadinfo.VADDump() + + results = [] + for proc in runner.calculate(): + addr = proc.get_process_address_space() + + if not addr: + continue + + if is_interesting(proc, filter_data): + + for vad, _ in proc.get_vads( + vad_filter=lambda v: v.Length < pow(2, 30), skip_max_commit=True + ): + path = os.path.join( + config.DUMP_DIR, + "{}_{}.{}".format(vad.Start, vad.End, proc.UniqueProcessId), + ) + + runner.dump_vad(path, vad, addr) + + result = { + "pid": int(proc.UniqueProcessId), + "start": int(vad.Start), + "end": int(vad.End), + } + result.update(hash_file(path, hashlib.sha256())) + results.append(result) + + return results + def socket_visitor(node, accumulator): if node.values: proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] @@ -86,14 +163,14 @@ def svcscan_visitor(node, accumulator): state = node.values[4] name, display_name = node.values[6:8] - pid = str(pid) if type(pid) == volatility3.framework.renderers.NotApplicableValue else int(pid) + pid = -1 if type(pid) == volatility3.framework.renderers.NotApplicableValue else int(pid) svc_data = { "ServiceName": name, "DisplayName": display_name, + "DriverName": '', "State": state, "Pid": pid, - "offset": int(offset), } accumulator.append(svc_data) # breakpoint() @@ -102,10 +179,10 @@ def svcscan_visitor(node, accumulator): def driverscan_visitor(node, accumulator): if node.values: offset, start = node.values[:2] + servicekey = node.values[3] name = node.values[-1] drv_data = { - "offset": int(offset), - "start": int(start), + "servicekey": servicekey, "name": str(name), } accumulator.append(drv_data) @@ -113,42 +190,6 @@ def driverscan_visitor(node, accumulator): return accumulator -def get_memory_hashes(filter_data): - - runner = vadinfo.VADDump(config) - - results = [] - for proc in runner.calculate(): - addr = proc.get_process_address_space() - - if not addr: - continue - - if is_interesting(proc, filter_data): - - for vad, _ in proc.get_vads( - vad_filter=lambda v: v.Length < pow(2, 30), skip_max_commit=True - ): - path = os.path.join( - config.DUMP_DIR, - "{}_{}.{}".format(vad.Start, vad.End, proc.UniqueProcessId), - ) - - runner.dump_vad(path, vad, addr) - - result = { - "pid": int(proc.UniqueProcessId), - "start": int(vad.Start), - "end": int(vad.End), - } - result.update(hash_file(path, hashlib.sha256())) - results.append(result) - - shutil.rmtree(config.DUMP_DIR) - - return results - - def get_pslist(): """List all the tasks that aren't hidden, unlinked, etc""" config_path = "plugins.PsList" @@ -182,11 +223,10 @@ def get_svcscan(): treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) driverscan_data = get_driverscan() - for i in range(len(svcscan_data)): + for svc in svcscan_data: for drv in driverscan_data: - if svcscan_data[i]["offset"] == drv["offset"]: - svcscan_data[i]["DriverName"] = drv["name"] - continue + if svc["ServiceName"] == drv["servicekey"]: + svc["DriverName"] = drv["name"] return svcscan_data @@ -206,6 +246,7 @@ def get_sockets(): config_path = "plugins.NetScan" automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) + breakpoint() constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) treegrid = constructed.run() socket_data = [] @@ -244,4 +285,7 @@ def run(location, filterfile): ctx = contexts.Context() ctx.config["automagic.LayerStacker.single_location"] = f"file:{image}" available_automagics = automagic.available(ctx) - print(get_svcscan()) + svcs = get_svcscan() + + # with open("svcs3.json", 'w') as f: + # json.dump(svcs, f, indent=None, separators=(',\n', ': ')) From 46856b0ff7614f149646d33c0862dccb38deaab6 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Mon, 9 Feb 2026 09:46:36 -0500 Subject: [PATCH 05/24] Refactored for panda code --- plugins/CMakeLists.txt | 2 +- plugins/volatility/CMakeLists.txt | 22 ++--- plugins/volatility/volatility.cc | 141 ++++++++---------------------- plugins/volatility/volglue3.py | 59 +++++++------ 4 files changed, 82 insertions(+), 142 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3f5a2cd..ff42719 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -70,4 +70,4 @@ add_subdirectory(callstack) add_subdirectory(apicall_tracer) add_subdirectory(memory_regions) add_subdirectory(pmemdump) -#add_subdirectory(volatility) +add_subdirectory(volatility) diff --git a/plugins/volatility/CMakeLists.txt b/plugins/volatility/CMakeLists.txt index f2963a2..8c58464 100644 --- a/plugins/volatility/CMakeLists.txt +++ b/plugins/volatility/CMakeLists.txt @@ -2,7 +2,7 @@ set(PANDA_PLUGIN_NAME "volatility") set(PLUGIN_TARGET "panda_${PANDA_PLUGIN_NAME}") # The volatility plugin requires linking against python -find_package(PythonLibs 2.7 REQUIRED) +find_package(PythonLibs 3.8 REQUIRED) if (NOT PYTHONLIBS_FOUND) message(FATAL_ERROR "Could not find python libraries. Is python-dev installed?") endif() @@ -20,14 +20,14 @@ set(LINK_LIBS_X86_64 ${LINK_LIBS} panda_ipanda-x86_64) set(TARGET_DEPS_I386 panda_ipanda-i386) set(TARGET_DEPS_X86_64 panda_ipanda-x86_64) -add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_I386}/volglue.py - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py ${PANDA_PLUGIN_DIR_I386}/volglue.py - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py) -add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_X86_64}/volglue.py - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py ${PANDA_PLUGIN_DIR_X86_64}/volglue.py - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py) -add_custom_target(volglue-script ALL - DEPENDS ${PANDA_PLUGIN_DIR_I386}/volglue.py ${PANDA_PLUGIN_DIR_X86_64}/volglue.py) +add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_I386}/volglue3.py + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py ${PANDA_PLUGIN_DIR_I386}/volglue3.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py) +add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py) +add_custom_target(volglue3-script ALL + DEPENDS ${PANDA_PLUGIN_DIR_I386}/volglue3.py ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py) add_i386_plugin(${PLUGIN_TARGET} SRC_FILES LINK_LIBS_I386) @@ -35,5 +35,5 @@ add_x86_64_plugin(${PLUGIN_TARGET} SRC_FILES LINK_LIBS_X86_64) add_dependencies(${PLUGIN_TARGET}-i386 ${TARGET_DEPS_I386}) add_dependencies(${PLUGIN_TARGET}-x86_64 ${TARGET_DEPS_X86_64}) -install(FILES ${PANDA_PLUGIN_DIR_I386}/volglue.py DESTINATION lib/panda/i386) -install(FILES ${PANDA_PLUGIN_DIR_X86_64}/volglue.py DESTINATION lib/panda/x86_64) +install(FILES ${PANDA_PLUGIN_DIR_I386}/volglue3.py DESTINATION lib/panda/i386) +install(FILES ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py DESTINATION lib/panda/x86_64) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index c522988..7b41a7d 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -38,7 +38,7 @@ char g_profile[512] = {0}; #define SOCKET_PATH_FMT "/tmp/panda%d.sock" char g_location[512] = "file://\0"; char g_filter_path[512] = {0}; -const char g_script_name[] = "/volglue.py"; +const char g_script_name[] = "/volglue3.py"; // Globals std::shared_ptr os_manager; @@ -79,32 +79,33 @@ bool log_analysis_results(CPUState* env, const char* data); int run_volatility_analysis(CPUState* env) { // Convert global strings to python strings - PyObject* pprofile_str = PyString_FromString(g_profile); - PyObject* plocation_str = PyString_FromString(g_location); - PyObject* pfilter_str = PyString_FromString(g_filter_path); + // PyObject* pprofile_str = PyUnicode_FromString(g_profile); + PyObject* plocation_str = PyUnicode_FromString(g_location); + fprintf(stdout, "Location: %s\n", g_location); + // PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); - PyObject* pargs = PyTuple_New(3); + PyObject* pargs = PyTuple_New(1); // Mildly concerned about death-by-oom - if (!pprofile_str || !plocation_str || !pfilter_str || !pargs) { + if (!plocation_str) { fprintf(stderr, "[%s] Failed to allocate args\n", __FILE__); - Py_XDECREF(pprofile_str); + // Py_XDECREF(pprofile_str); Py_XDECREF(plocation_str); - Py_XDECREF(pfilter_str); + // Py_XDECREF(pfilter_str); Py_XDECREF(pargs); } // Add these strings to an argument object - PyTuple_SetItem(pargs, 0, pprofile_str); - PyTuple_SetItem(pargs, 1, plocation_str); - PyTuple_SetItem(pargs, 2, pfilter_str); + PyTuple_SetItem(pargs, 0, plocation_str); + // PyTuple_SetItem(pargs, 1, pfilter_str); - // Call run(profile, location) + // Call run(location) PyObject* pvalue = PyObject_CallObject(g_pfunc, pargs); if (pvalue) { // The function returned a value successfully - if (PyString_Check(pvalue)) { - const char* json_str = PyString_AsString(pvalue); + if (PyUnicode_Check(pvalue)) { + const char* json_str = PyUnicode_AsUTF8(pvalue); + fprintf(stdout, "%s\n", json_str); if (log_analysis_results(env, json_str)) { fprintf(stderr, "[%s] Failed to record result!\n", __FILE__); } @@ -189,8 +190,8 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) free_process(g_current_process); g_current_process = kosi_get_current_process(kosi); - g_targeted = g_filter->thread_check(process_get_pid(g_current_process), - process_get_asid(g_current_process)); + // g_targeted = g_filter->thread_check(process_get_pid(g_current_process), + // process_get_asid(g_current_process)); g_check_for_process = false; } @@ -203,15 +204,15 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) auto asid = process_get_asid(g_current_process); auto tid = kosi_get_current_tid(kosi); - if (!g_filter->thread_check(pid, asid, tid)) { - return; - } + // if (!g_filter->thread_check(pid, asid, tid)) { + // return; + // } run_volatility_analysis(env); // remove the thread now that we've handled it and make // the next bb refresh state info - g_filter->remove_thread(pid, asid, tid); + // g_filter->remove_thread(pid, asid, tid); g_check_for_process = true; return; @@ -289,11 +290,11 @@ bool init_plugin(void* self) panda_arg_list* vol_args = panda_get_args("volatility"); output_path = panda_parse_string(vol_args, "output", "volatility.panda"); - panda_arg_list* filter_args = panda_get_args("filter"); - filter_path = panda_parse_string(filter_args, "file", ""); - strncpy(g_filter_path, filter_path, sizeof(g_filter_path) - 1); - g_filter.reset(new InstrumentationFilter(g_filter_path)); - panda_free_args(filter_args); + // panda_arg_list* filter_args = panda_get_args("filter"); + // filter_path = panda_parse_string(filter_args, "file", ""); + // strncpy(g_filter_path, filter_path, sizeof(g_filter_path) - 1); + // g_filter.reset(new InstrumentationFilter(g_filter_path)); + // panda_free_args(filter_args); if (init_avro(output_path)) { return false; @@ -302,84 +303,10 @@ bool init_plugin(void* self) // Read arguments const char* profile_arg = panda_os_name; - const char* profile = nullptr; // volatility profile + // const char* profile = nullptr; // volatility profile if (!profile_arg) { fprintf(stderr, "[%s] The -os flag is required\n", __FILE__); return false; - } else if (strcasecmp(profile_arg, "windows-64-vistasp0") == 0) { - profile = "VistaSP0x64"; - } else if (strcasecmp(profile_arg, "windows-32-vistasp0") == 0) { - profile = "VistaSP0x86"; - } else if (strcasecmp(profile_arg, "windows-64-vistasp1") == 0) { - profile = "VistaSP1x64"; - } else if (strcasecmp(profile_arg, "windows-32-vistasp1") == 0) { - profile = "VistaSP1x86"; - } else if (strcasecmp(profile_arg, "windows-64-vistasp2") == 0) { - profile = "VistaSP2x64"; - } else if (strcasecmp(profile_arg, "windows-32-vistasp2") == 0) { - profile = "VistaSP2x86"; - } else if (strcasecmp(profile_arg, "windows-64-10x64sp0") == 0) { - profile = "Win10x64"; - } else if (strcasecmp(profile_arg, "windows-32-10x86sp0") == 0) { - profile = "Win10x86"; - } else if (strcasecmp(profile_arg, "windows-32-2003sp0") == 0) { - profile = "Win2003SP0x86"; - } else if (strcasecmp(profile_arg, "windows-64-2003sp1") == 0) { - profile = "Win2003SP1x64"; - } else if (strcasecmp(profile_arg, "windows-32-2003sp1") == 0) { - profile = "Win2003SP1x86"; - } else if (strcasecmp(profile_arg, "windows-64-2003sp2") == 0) { - profile = "Win2003SP2x64"; - } else if (strcasecmp(profile_arg, "windows-32-2003sp2") == 0) { - profile = "Win2003SP2x86"; - } else if (strcasecmp(profile_arg, "windows-64-2008r2sp0") == 0) { - profile = "Win2008R2SP0x64"; - } else if (strcasecmp(profile_arg, "windows-64-2008r2sp1") == 0) { - profile = "Win2008R2SP1x64"; - } else if (strcasecmp(profile_arg, "windows-64-2008sp1") == 0) { - profile = "Win2008SP1x64"; - } else if (strcasecmp(profile_arg, "windows-32-2008sp1") == 0) { - profile = "Win2008SP1x86"; - } else if (strcasecmp(profile_arg, "windows-64-2008sp2") == 0) { - profile = "Win2008SP2x64"; - } else if (strcasecmp(profile_arg, "windows-32-2008sp2") == 0) { - profile = "Win2008SP2x86"; - } else if (strcasecmp(profile_arg, "windows-64-2012r2sp0") == 0) { - profile = "Win2012R2x64"; - } else if (strcasecmp(profile_arg, "windows-64-2012sp0") == 0) { - profile = "Win2012x64"; - } else if (strcasecmp(profile_arg, "windows-64-7sp0") == 0) { - profile = "Win7SP0x64"; - } else if (strcasecmp(profile_arg, "windows-32-7sp0") == 0) { - profile = "Win7SP0x86"; - } else if (strcasecmp(profile_arg, "windows-64-7sp1") == 0) { - profile = "Win7SP1x64"; - } else if (strcasecmp(profile_arg, "windows-32-7sp1") == 0) { - profile = "Win7SP1x86"; - } else if (strcasecmp(profile_arg, "windows-64-81sp0") == 0) { - profile = "Win81U1x64"; - } else if (strcasecmp(profile_arg, "windows-32-81sp0") == 0) { - profile = "Win81U1x86"; - } else if (strcasecmp(profile_arg, "windows-64-8sp0") == 0) { - profile = "Win8SP0x64"; - } else if (strcasecmp(profile_arg, "windows-32-8sp0") == 0) { - profile = "Win8SP0x86"; - } else if (strcasecmp(profile_arg, "windows-64-8sp1") == 0) { - profile = "Win8SP1x64"; - } else if (strcasecmp(profile_arg, "windows-32-8sp1") == 0) { - profile = "Win8SP1x86"; - } else if (strcasecmp(profile_arg, "windows-64-xpsp1") == 0) { - profile = "WinXPSP1x64"; - } else if (strcasecmp(profile_arg, "windows-64-xpsp2") == 0) { - profile = "WinXPSP2x64"; - } else if (strcasecmp(profile_arg, "windows-32-xpsp2") == 0) { - profile = "WinXPSP2x86"; - } else if (strcasecmp(profile_arg, "windows-32-xpsp3") == 0) { - profile = "WinXPSP3x86"; - } - if (!profile) { - fprintf(stderr, "[%s] Unrecognized profile\n", __FILE__); - return false; } char* socket_path = (char*)calloc(512, 1); @@ -392,7 +319,7 @@ bool init_plugin(void* self) const char* python_script = panda_parse_string(vol_args, "script", g_script_path); - strncpy(g_profile, profile, sizeof(g_profile) - 1); + // strncpy(g_profile, profile, sizeof(g_profile) - 1); panda_free_args(vol_args); panda_cb pcb; @@ -403,7 +330,7 @@ bool init_plugin(void* self) // This hack can be avoided by working with PANDA // to expose the python shared library - dlopen("libpython2.7.so", RTLD_LAZY | RTLD_GLOBAL); + dlopen("libpython3.8.so", RTLD_LAZY | RTLD_GLOBAL); char* script_contents = read_script(python_script); if (!script_contents) { @@ -411,15 +338,19 @@ bool init_plugin(void* self) return false; } - Py_SetProgramName(g_program_name); + Py_SetProgramName((wchar_t*) g_program_name); Py_Initialize(); + PyRun_SimpleString("import sys; from pathlib import Path"); + PyRun_SimpleString("sys.path.append(f'{Path.home()}/.pyenv/versions/vol3/lib/python3.8/site-packages')"); + PyRun_SimpleString("sys.path.append(f'{Path.home()}/volatility3')"); // Load the program as a code object - pcode = Py_CompileString((char*)script_contents, "volglue.py", Py_file_input); + pcode = Py_CompileString(script_contents, "volglue3.py", Py_file_input); CHECK_OR_DIE(pcode, "Failed to compile python program!\n", cleanup); // Load the code object into a module - pmodule = PyImport_ExecCodeModule(g_module_name, pcode); + // fprintf(stdout, "Module name: %s\n", g_module_name); + pmodule = PyImport_ExecCodeModule("gluemod", pcode); CHECK_OR_DIE(pmodule, "Failed to load as module!\n", cleanup); // Extract the entry point of our new module diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index d5849ad..8514274 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -19,6 +19,10 @@ from volatility3.framework import automagic, contexts, interfaces, plugins +ctx = contexts.Context() + + + def filter_invalid_ascii(strobj): return strobj.encode("utf-8", "replace") @@ -49,10 +53,10 @@ def hash_file(filepath, *algorithms): def get_process_hashes(): config_path = "plugins.PEDump" automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) - breakpoint() + # breakpoint() constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=None) treegrid = constructed.run() - breakpoint() + # breakpoint() results = [] for proc in runner.calculate(): @@ -90,7 +94,7 @@ def get_process_hashes(): def get_memory_hashes(filter_data): - breakpoint() + # breakpoint() runner = vadinfo.VADDump() results = [] @@ -142,9 +146,9 @@ def pslist_visitor(node, accumulator): if node.values: pid, ppid, img_name, offset = node.values[0:4] proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) - if img_name == "services.exe": - print(offset) - breakpoint() + # if img_name == "services.exe": + # print(offset) + # breakpoint() pdata = { "pid": int(pid), "ppid": int(ppid), @@ -190,7 +194,7 @@ def driverscan_visitor(node, accumulator): return accumulator -def get_pslist(): +def get_pslist(available_automagics): """List all the tasks that aren't hidden, unlinked, etc""" config_path = "plugins.PsList" automagics = automagic.choose_automagic(available_automagics, pslist.PsList) @@ -213,7 +217,7 @@ def get_psscan(): return psscan_data # text_renderer.PrettyTextRenderer().render(treegrid) -def get_svcscan(): +def get_svcscan(available_automagics): """List all of the system services""" config_path = "plugins.SvcScan" automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) @@ -246,7 +250,7 @@ def get_sockets(): config_path = "plugins.NetScan" automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) - breakpoint() + # breakpoint() constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) treegrid = constructed.run() socket_data = [] @@ -254,7 +258,7 @@ def get_sockets(): return socket_data -def run(location, filterfile): +def run(location): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -262,17 +266,21 @@ def run(location, filterfile): returned to the plugin. """ - print("Volatility version: %r" % volatility3.framework.constants.VERSION) + # print("Volatility version: %r" % volatility3.framework.constants.VERSION) + print("Location:", location) + ctx.config["automagic.LayerStacker.single_location"] = location + available_automagics = automagic.available(ctx) + # breakpoint() try: - with open(filterfile, "rb") as fobj: - filter_data = json.load(fobj) + # with open(filterfile, "rb") as fobj: + # filter_data = json.load(fobj) analysis_results = { - "pslist": get_pslist(), - "svcscan": get_svcscan(), - "sockets": get_sockets(), - "process_hashes": get_process_hashes(filter_data), - "memory_hashes": get_memory_hashes(filter_data), + "pslist": get_pslist(available_automagics), + "svcscan": get_svcscan(available_automagics), + # "sockets": get_sockets(), + # "process_hashes": get_process_hashes(filter_data), + # "memory_hashes": get_memory_hashes(filter_data), } except Exception as err: analysis_results = {"error": traceback.format_exc(err)} @@ -281,11 +289,12 @@ def run(location, filterfile): return json_str if __name__ == "__main__": - image = "mymem.dd" - ctx = contexts.Context() - ctx.config["automagic.LayerStacker.single_location"] = f"file:{image}" - available_automagics = automagic.available(ctx) - svcs = get_svcscan() + import argparse + + parser = argparse.ArgumentParser(description="Test analysis") + parser.add_argument("--location", default="file:mymem.dd") + args = parser.parse_args() + print(run(args.location)) + - # with open("svcs3.json", 'w') as f: - # json.dump(svcs, f, indent=None, separators=(',\n', ': ')) +### Must end with this comment \ No newline at end of file From bcd3717735a6f1215166569aa6b9bd3766052c8f Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Tue, 17 Mar 2026 12:06:30 -0400 Subject: [PATCH 06/24] Dump memory of each translation block and analyze that --- plugins/volatility/volatility.cc | 71 +++++++++++++++++++++++--- plugins/volatility/volglue3.py | 87 +++++++++++++++++++++++++++++--- 2 files changed, 143 insertions(+), 15 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 7b41a7d..f400f7a 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -36,7 +36,7 @@ char g_profile[512] = {0}; // Constants #define SOCKET_PATH_FMT "/tmp/panda%d.sock" -char g_location[512] = "file://\0"; +char g_location[512] = "file:\0"; char g_filter_path[512] = {0}; const char g_script_name[] = "/volglue3.py"; @@ -71,6 +71,56 @@ void uninit_plugin(void*); int run_volatility_analysis(CPUState* env); bool log_analysis_results(CPUState* env, const char* data); +static const uint8_t _zero_block[1024] = {0}; +static void actually_dump_physical_memory(FILE* out, size_t len) +{ + hwaddr addr = 0; + uint8_t block[sizeof(_zero_block)]; + + if (!out) + return; + + while (len != 0) + { + size_t l = sizeof(block); + if (l > len) + l = len; + if (panda_physical_memory_read(addr, block, l) == MEMTX_OK) + fwrite(block, 1, l, out); + else + fwrite(_zero_block, 1, l, out); + addr += l; + len -= l; + } +} + +static void dump_memory(char* filename, char* register_filename, uint64_t pmem_len){ + FILE* out = fopen(filename, "wb"); + + if (pmem_len == 0){ + // dump all memory if not specified as arg + pmem_len = ram_size; + } + + actually_dump_physical_memory(out, pmem_len); + fclose(out); + if (register_filename) + { + if ((out = fopen(register_filename, "w")) != NULL) + { + CPUState* cpu; + CPU_FOREACH(cpu) + { + fprintf(out, "CPU#%d\n", cpu->cpu_index); + cpu_dump_state(cpu, out, fprintf, CPU_DUMP_FPU); + } + fclose(out); + } + } + + panda_replay_end(); +} + /** * Run the volatility analysis, passing the desired profile and args * as python strings. Stores the results in the panda log or writes them @@ -80,6 +130,7 @@ int run_volatility_analysis(CPUState* env) { // Convert global strings to python strings // PyObject* pprofile_str = PyUnicode_FromString(g_profile); + dump_memory("mem.ram", "mem.regs.txt", 0); PyObject* plocation_str = PyUnicode_FromString(g_location); fprintf(stdout, "Location: %s\n", g_location); // PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); @@ -314,8 +365,8 @@ bool init_plugin(void* self) fprintf(stderr, "[%s] Failed to allocate memory for socket path\n", __FILE__); return false; } - sprintf(socket_path, SOCKET_PATH_FMT, getpid()); - strncat(g_location, socket_path, sizeof(g_location) - 1); + // sprintf(socket_path, SOCKET_PATH_FMT, getpid()); + strncat(g_location, "mem.ram", sizeof(g_location) - 1); const char* python_script = panda_parse_string(vol_args, "script", g_script_path); @@ -364,10 +415,10 @@ bool init_plugin(void* self) fprintf(stdout, "Successfully initialized python routines.\n"); - if (!start_memory_server(socket_path)) { - fprintf(stderr, "[%s] Failed to start memory server!\n", __FILE__); - goto cleanup; - } + // if (!start_memory_server(socket_path)) { + // fprintf(stderr, "[%s] Failed to start memory server!\n", __FILE__); + // goto cleanup; + // } if (script_contents) { free(script_contents); @@ -399,14 +450,18 @@ bool init_plugin(void* self) pmodule = NULL; Py_XDECREF(g_pfunc); g_pfunc = NULL; + unlink("mem.ram"); + unlink("mem.regs.txt"); return false; } void uninit_plugin(void* self) { - stop_memory_server(); + // stop_memory_server(); Py_XDECREF(g_pfunc); g_pfunc = NULL; Py_Finalize(); teardown_avro(); + unlink("mem.ram"); + unlink("mem.regs.txt"); } diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 8514274..d4c1954 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -7,7 +7,9 @@ # and take the profile as an argument import os import json +import logging import shutil +import subprocess import hashlib import tempfile import traceback @@ -19,8 +21,69 @@ from volatility3.framework import automagic, contexts, interfaces, plugins -ctx = contexts.Context() +import socket +from typing import Optional + +vollog = logging.getLogger(__name__) + +class UnixSocketFileHandler(interfaces.plugins.FileHandlerInterface): + def __init__(self, socket_path: str, filename: str) -> None: + """Initializes the UnixSocketFileHandler.""" + super().__init__(filename) + self.socket_path = socket_path + self.sock: Optional[socket.socket] = None + self.file: Optional[socket.SocketIO] = None + + def open(self): + """Connects to the existing Unix domain socket and wraps it in a file-like interface.""" + try: + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(self.socket_path) + # Wrap the socket in a file-like interface + self.file = self.sock.makefile(mode="rwb") + except FileNotFoundError: + raise Exception(f"Socket file not found: {self.socket_path}") + except PermissionError: + raise Exception(f"Permission denied for socket: {self.socket_path}") + except Exception as e: + raise Exception(f"Failed to connect to Unix domain socket: {e}") + + def read(self, buffer_size: int = 1024) -> bytes: + """Reads data from the socket.""" + if self.file is None: + raise Exception("Socket is not connected") + return self.file.read(buffer_size) + + def write(self, data: bytes): + """Writes data to the socket.""" + if self.file is None: + raise Exception("Socket is not connected") + self.file.write(data) + self.file.flush() + + def close(self): + """Closes the socket connection.""" + if self.file is not None: + self.file.close() + self.file = None + if self.sock is not None: + self.sock.close() + self.sock = None + + @staticmethod + def sanitize_filename(filename: str) -> str: + """Sanitizes the filename to ensure only a specific allow list of characters is allowed through.""" + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^#~," + result = "" + for char in filename: + if char in allowed: + result += char + else: + result += "_" # change unwanted chars to an underscore + return result + +ctx = contexts.Context() def filter_invalid_ascii(strobj): @@ -194,10 +257,12 @@ def driverscan_visitor(node, accumulator): return accumulator -def get_pslist(available_automagics): +def get_pslist(available_automagics, socket_path): """List all the tasks that aren't hidden, unlinked, etc""" config_path = "plugins.PsList" automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + # breakpoint() + # constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=lambda filename: UnixSocketFileHandler(socket_path, filename)) constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=None) # breakpoint() treegrid = constructed.run() @@ -225,7 +290,7 @@ def get_svcscan(available_automagics): treegrid = constructed.run() svcscan_data = [] treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) - driverscan_data = get_driverscan() + driverscan_data = get_driverscan(available_automagics) for svc in svcscan_data: for drv in driverscan_data: @@ -234,7 +299,7 @@ def get_svcscan(available_automagics): return svcscan_data -def get_driverscan(): +def get_driverscan(available_automagics): from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file config_path = "plugins.DriverScan" automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) @@ -245,7 +310,7 @@ def get_driverscan(): return driverscan_data -def get_sockets(): +def get_sockets(available_automagics): """List all of the sockets that have not been unlinked or hidden""" config_path = "plugins.NetScan" @@ -268,17 +333,25 @@ def run(location): """ # print("Volatility version: %r" % volatility3.framework.constants.VERSION) print("Location:", location) + # socat_command = f"socat -u UNIX-CONNECT:{location[6:]} - > mem.dd" + # output = subprocess.run(socat_command, check=True, shell=True) + # print("Output:", output) ctx.config["automagic.LayerStacker.single_location"] = location + # ctx.config["automagic.QemuSuspend.single_location"] = location available_automagics = automagic.available(ctx) + # socket_path = location[6:] + # breakpoint() + # unix_socket_handler = UnixSocketFileHandler(socket_path, location[12:]) + # unix_socket_handler.open() # breakpoint() try: # with open(filterfile, "rb") as fobj: # filter_data = json.load(fobj) analysis_results = { - "pslist": get_pslist(available_automagics), + "pslist": get_pslist(available_automagics, location), "svcscan": get_svcscan(available_automagics), - # "sockets": get_sockets(), + # "sockets": get_sockets(available_automagics), # "process_hashes": get_process_hashes(filter_data), # "memory_hashes": get_memory_hashes(filter_data), } From 7cf53f65fe772f43e01d694fa97c293162162ef0 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Tue, 17 Mar 2026 15:13:35 -0400 Subject: [PATCH 07/24] Handled unreadable values for socket --- plugins/volatility/volglue3.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index d4c1954..0e01fbc 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -192,16 +192,24 @@ def get_memory_hashes(filter_data): def socket_visitor(node, accumulator): if node.values: proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] + state = "STATELESS" if state == "" else state + laddr = "::" if type(laddr) == volatility3.framework.renderers.UnreadableValue else laddr + pid = -1 if type(pid) == volatility3.framework.renderers.UnreadableValue else int(pid) + owner = "" if type(owner) == volatility3.framework.renderers.UnreadableValue else owner + raddr = "::" if type(raddr) == volatility3.framework.renderers.UnreadableValue else raddr + sdata = { "pid": pid, "owner": owner, "proto": proto, "local_addr": laddr, - "local_port": lport, + "local_port": int(lport), "remote_addr": raddr, "remote_port": rport, "state": state, } + # print(sdata) + # breakpoint() accumulator.append(sdata) return accumulator @@ -332,12 +340,7 @@ def run(location): """ # print("Volatility version: %r" % volatility3.framework.constants.VERSION) - print("Location:", location) - # socat_command = f"socat -u UNIX-CONNECT:{location[6:]} - > mem.dd" - # output = subprocess.run(socat_command, check=True, shell=True) - # print("Output:", output) ctx.config["automagic.LayerStacker.single_location"] = location - # ctx.config["automagic.QemuSuspend.single_location"] = location available_automagics = automagic.available(ctx) # socket_path = location[6:] # breakpoint() @@ -351,7 +354,7 @@ def run(location): analysis_results = { "pslist": get_pslist(available_automagics, location), "svcscan": get_svcscan(available_automagics), - # "sockets": get_sockets(available_automagics), + "sockets": get_sockets(available_automagics), # "process_hashes": get_process_hashes(filter_data), # "memory_hashes": get_memory_hashes(filter_data), } From ffc1a2fcfc7d4be5892e058764719b846f58f345 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Wed, 18 Mar 2026 10:47:17 -0400 Subject: [PATCH 08/24] Properly sourced venv --- plugins/volatility/volatility.cc | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index f400f7a..bcfaea6 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -50,6 +50,7 @@ bool g_check_for_process = true; bool g_targeted = true; static PyObject* g_pfunc = NULL; +PyConfig config; #define CHECK_OR_DIE(_obj, _emsg, _elabel) \ do { \ @@ -389,11 +390,24 @@ bool init_plugin(void* self) return false; } + const char* venv_path_cstr = std::getenv("VIRTUAL_ENV"); + std::string venv_path(venv_path_cstr); + std::string exec_path = venv_path + "/bin/python"; + std::wstring w_venv_path(venv_path.begin(), venv_path.end()); + std::wstring w_exec(exec_path.begin(), exec_path.end()); + Py_SetProgramName((wchar_t*) g_program_name); - Py_Initialize(); - PyRun_SimpleString("import sys; from pathlib import Path"); - PyRun_SimpleString("sys.path.append(f'{Path.home()}/.pyenv/versions/vol3/lib/python3.8/site-packages')"); - PyRun_SimpleString("sys.path.append(f'{Path.home()}/volatility3')"); + PyConfig_InitPythonConfig(&config); + PyConfig_SetString(&config, &config.executable, w_exec.c_str()); + Py_InitializeFromConfig(&config); + // Py_Initialize(); + // std::string cmd = "sys.path.append('"; + // cmd += venv_path; + // cmd += "/lib/python3.8/site-packages')"; + // std::cout << cmd << std::endl; + // PyRun_SimpleString("import sys; from pathlib import Path"); + // PyRun_SimpleString(cmd.c_str()); + // PyRun_SimpleString("sys.path.append(f'{Path.home()}/volatility3')"); // Load the program as a code object pcode = Py_CompileString(script_contents, "volglue3.py", Py_file_input); @@ -450,6 +464,7 @@ bool init_plugin(void* self) pmodule = NULL; Py_XDECREF(g_pfunc); g_pfunc = NULL; + PyConfig_Clear(&config); unlink("mem.ram"); unlink("mem.regs.txt"); return false; @@ -460,6 +475,7 @@ void uninit_plugin(void* self) // stop_memory_server(); Py_XDECREF(g_pfunc); g_pfunc = NULL; + PyConfig_Clear(&config); Py_Finalize(); teardown_avro(); unlink("mem.ram"); From e159bcda43458cff4709a179c99d9a69c5cf8886 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Wed, 1 Apr 2026 15:37:11 -0400 Subject: [PATCH 09/24] Filter code --- plugins/volatility/volatility.cc | 40 +++++++++++++------------------- plugins/volatility/volglue3.py | 16 +++++++------ 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index bcfaea6..386ecb2 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -131,25 +131,25 @@ int run_volatility_analysis(CPUState* env) { // Convert global strings to python strings // PyObject* pprofile_str = PyUnicode_FromString(g_profile); - dump_memory("mem.ram", "mem.regs.txt", 0); + dump_memory("mem.ram", "mem.regs.txt", 100); PyObject* plocation_str = PyUnicode_FromString(g_location); fprintf(stdout, "Location: %s\n", g_location); - // PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); + PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); - PyObject* pargs = PyTuple_New(1); + PyObject* pargs = PyTuple_New(2); // Mildly concerned about death-by-oom if (!plocation_str) { fprintf(stderr, "[%s] Failed to allocate args\n", __FILE__); // Py_XDECREF(pprofile_str); Py_XDECREF(plocation_str); - // Py_XDECREF(pfilter_str); + Py_XDECREF(pfilter_str); Py_XDECREF(pargs); } // Add these strings to an argument object PyTuple_SetItem(pargs, 0, plocation_str); - // PyTuple_SetItem(pargs, 1, pfilter_str); + PyTuple_SetItem(pargs, 1, pfilter_str); // Call run(location) PyObject* pvalue = PyObject_CallObject(g_pfunc, pargs); @@ -242,8 +242,8 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) free_process(g_current_process); g_current_process = kosi_get_current_process(kosi); - // g_targeted = g_filter->thread_check(process_get_pid(g_current_process), - // process_get_asid(g_current_process)); + g_targeted = g_filter->thread_check(process_get_pid(g_current_process), + process_get_asid(g_current_process)); g_check_for_process = false; } @@ -256,15 +256,15 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) auto asid = process_get_asid(g_current_process); auto tid = kosi_get_current_tid(kosi); - // if (!g_filter->thread_check(pid, asid, tid)) { - // return; - // } + if (!g_filter->thread_check(pid, asid, tid)) { + return; + } run_volatility_analysis(env); // remove the thread now that we've handled it and make // the next bb refresh state info - // g_filter->remove_thread(pid, asid, tid); + g_filter->remove_thread(pid, asid, tid); g_check_for_process = true; return; @@ -342,11 +342,11 @@ bool init_plugin(void* self) panda_arg_list* vol_args = panda_get_args("volatility"); output_path = panda_parse_string(vol_args, "output", "volatility.panda"); - // panda_arg_list* filter_args = panda_get_args("filter"); - // filter_path = panda_parse_string(filter_args, "file", ""); - // strncpy(g_filter_path, filter_path, sizeof(g_filter_path) - 1); - // g_filter.reset(new InstrumentationFilter(g_filter_path)); - // panda_free_args(filter_args); + panda_arg_list* filter_args = panda_get_args("filter"); + filter_path = panda_parse_string(filter_args, "file", ""); + strncpy(g_filter_path, filter_path, sizeof(g_filter_path) - 1); + g_filter.reset(new InstrumentationFilter(g_filter_path)); + panda_free_args(filter_args); if (init_avro(output_path)) { return false; @@ -400,14 +400,6 @@ bool init_plugin(void* self) PyConfig_InitPythonConfig(&config); PyConfig_SetString(&config, &config.executable, w_exec.c_str()); Py_InitializeFromConfig(&config); - // Py_Initialize(); - // std::string cmd = "sys.path.append('"; - // cmd += venv_path; - // cmd += "/lib/python3.8/site-packages')"; - // std::cout << cmd << std::endl; - // PyRun_SimpleString("import sys; from pathlib import Path"); - // PyRun_SimpleString(cmd.c_str()); - // PyRun_SimpleString("sys.path.append(f'{Path.home()}/volatility3')"); // Load the program as a code object pcode = Py_CompileString(script_contents, "volglue3.py", Py_file_input); diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 0e01fbc..a223abc 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -94,7 +94,7 @@ def is_interesting(eprocess, filter_data): check_pid = int(eprocess.UniqueProcessId) check_asid = int(eprocess.Pcb.DirectoryTableBase) - for (pid, tid, asid) in filter_data["threads"]: + for (pid, tid, asid) in filter_data["thread_whitelist"]: if check_pid == int(pid) and check_asid == int(asid): return True return False @@ -113,9 +113,11 @@ def hash_file(filepath, *algorithms): return {a.name: a.hexdigest().lower() for a in algorithms} -def get_process_hashes(): +def get_process_hashes(available_automagics, filter_data): config_path = "plugins.PEDump" - automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) + # dump = pedump.PEDump(ctx, config_path, file_name=args.location) + # breakpoint() + automagics = automagic.choose_automagic(available_automagics, pedump.PEDump, base=0) # breakpoint() constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=None) treegrid = constructed.run() @@ -331,7 +333,7 @@ def get_sockets(available_automagics): return socket_data -def run(location): +def run(location, filterfile): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -348,14 +350,14 @@ def run(location): # unix_socket_handler.open() # breakpoint() try: - # with open(filterfile, "rb") as fobj: - # filter_data = json.load(fobj) + with open(filterfile, "rb") as fobj: + filter_data = json.load(fobj) analysis_results = { "pslist": get_pslist(available_automagics, location), "svcscan": get_svcscan(available_automagics), "sockets": get_sockets(available_automagics), - # "process_hashes": get_process_hashes(filter_data), + # "process_hashes": get_process_hashes(available_automagics, filter_data), # "memory_hashes": get_memory_hashes(filter_data), } except Exception as err: From 0275c0c2c077f4d5cb0b0b67d55cfea25c6345a5 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Wed, 1 Apr 2026 15:37:35 -0400 Subject: [PATCH 10/24] Socket exists check --- plugins/volatility/memory-server.cc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/volatility/memory-server.cc b/plugins/volatility/memory-server.cc index a48cf9e..28f39e7 100644 --- a/plugins/volatility/memory-server.cc +++ b/plugins/volatility/memory-server.cc @@ -28,6 +28,19 @@ extern "C" { #define SUCCESS_CODE 0x79 #define FAILURE_CODE 0x77 +#include + +int file_exists_access(const char *filename) { + // F_OK tests for existence of the file + if (access(filename, F_OK) == 0) { + return 1; // File exists + } else { + fprintf(stderr, "Error checking file existence: %s (errno: %d)\n", filename, errno); + return 0; // File does not exist or an error occurred + } +} + + struct __attribute__((__packed__)) request { uint64_t type; // {QUIT, READ, QUERY_SIZE}_MESSAGE, ... rest reserved uint64_t address; // address to read from @@ -299,6 +312,15 @@ static int setup_socket(char* path, struct sockaddr_un* address, fprintf(stderr, "[%s] QemuMemoryAccess: bind failed\n", __FILE__); return -2; } + if (!file_exists_access(path)) { + fprintf(stdout, "%s not exists\n", path); + exit(1); + } + if (chmod(path, 0777) != 0) { + fprintf(stderr, "Failed to set permissions for %s\n", path); + exit(1); + } + if (listen(socket_fd, 0) != 0) { fprintf(stderr, "[%s] QemuMemoryAccess: listen failed\n", __FILE__); return -3; From 107d1a89e153dd7c99c5ffeb944338a8b75df911 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Wed, 8 Apr 2026 10:00:34 -0400 Subject: [PATCH 11/24] dump_memory_tb --- plugins/volatility/volatility.cc | 42 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 386ecb2..4ae79a4 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -122,6 +122,29 @@ static void dump_memory(char* filename, char* register_filename, uint64_t pmem_l panda_replay_end(); } +static void dump_memory_tb(char* filename, hwaddr start_addr, size_t len) { + FILE* out = fopen(filename, "wb"); + if (!out) { + fprintf(stderr, "Failed to open file for memory dump: %s\n", filename); + return; + } + + uint8_t block[1024]; // Adjust block size as needed + while (len != 0) { + size_t l = sizeof(block); + if (l > len) + l = len; + if (panda_physical_memory_read(start_addr, block, l) == MEMTX_OK) + fwrite(block, 1, l, out); + else + fwrite(_zero_block, 1, l, out); + start_addr += l; + len -= l; + } + + fclose(out); +} + /** * Run the volatility analysis, passing the desired profile and args * as python strings. Stores the results in the panda log or writes them @@ -131,7 +154,7 @@ int run_volatility_analysis(CPUState* env) { // Convert global strings to python strings // PyObject* pprofile_str = PyUnicode_FromString(g_profile); - dump_memory("mem.ram", "mem.regs.txt", 100); + // dump_memory("mem.ram", "mem.regs.txt", 100); PyObject* plocation_str = PyUnicode_FromString(g_location); fprintf(stdout, "Location: %s\n", g_location); PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); @@ -248,9 +271,14 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) g_check_for_process = false; } - if (!g_targeted) { - return; - } + // if (!g_targeted) { + // printf("In not gtargeted\n"); + // return; + // } + + hwaddr tb_start_addr = tb->pc; + size_t tb_length = tb->size; + // fprintf(stdout, "PC: %lu, Size: %d\n", tb_start_addr, tb_length); auto pid = process_get_pid(g_current_process); auto asid = process_get_asid(g_current_process); @@ -259,7 +287,7 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) if (!g_filter->thread_check(pid, asid, tid)) { return; } - + dump_memory_tb("mem.ram", tb_start_addr, tb_length); run_volatility_analysis(env); // remove the thread now that we've handled it and make @@ -458,7 +486,7 @@ bool init_plugin(void* self) g_pfunc = NULL; PyConfig_Clear(&config); unlink("mem.ram"); - unlink("mem.regs.txt"); + // unlink("mem.regs.txt"); return false; } @@ -471,5 +499,5 @@ void uninit_plugin(void* self) Py_Finalize(); teardown_avro(); unlink("mem.ram"); - unlink("mem.regs.txt"); + // unlink("mem.regs.txt"); } From b6e6052ed1c5ae24127e21ac01b75dbcfec5fcfc Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Sun, 12 Apr 2026 13:04:56 -0400 Subject: [PATCH 12/24] Implemented PandaFileHandler --- plugins/volatility/filter.cc | 2 +- plugins/volatility/volatility.cc | 116 ++++++++++++++---- plugins/volatility/volglue3.py | 198 ++++++++++++++++++++----------- 3 files changed, 224 insertions(+), 92 deletions(-) diff --git a/plugins/volatility/filter.cc b/plugins/volatility/filter.cc index a5f39ee..754c9f8 100644 --- a/plugins/volatility/filter.cc +++ b/plugins/volatility/filter.cc @@ -23,7 +23,7 @@ InstrumentationFilter::InstrumentationFilter(const char* filter_file) filter_document.ParseStream(is); // threads (pid, tid, asid) - rapidjson::Value::ConstMemberIterator itr = filter_document.FindMember("threads"); + rapidjson::Value::ConstMemberIterator itr = filter_document.FindMember("thread_whitelist"); if (itr != filter_document.MemberEnd()) { assert(itr->value.IsArray()); diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 4ae79a4..8238a7c 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -36,6 +36,7 @@ char g_profile[512] = {0}; // Constants #define SOCKET_PATH_FMT "/tmp/panda%d.sock" +#define TARGET_PAGE_SIZE 1024 char g_location[512] = "file:\0"; char g_filter_path[512] = {0}; const char g_script_name[] = "/volglue3.py"; @@ -145,6 +146,84 @@ static void dump_memory_tb(char* filename, hwaddr start_addr, size_t len) { fclose(out); } +void panda_memsavep(char* filename) { + FILE* f = fopen(filename, "wb"); + if (!f) return; + + uint8_t mem_buf[TARGET_PAGE_SIZE]; + uint8_t zero_buf[TARGET_PAGE_SIZE]; + memset(zero_buf, 0, TARGET_PAGE_SIZE); + int res; + ram_addr_t addr; + for (addr = 0; addr < ram_size; addr += TARGET_PAGE_SIZE) { + res = panda_physical_memory_rw(addr, mem_buf, TARGET_PAGE_SIZE, 0); + if (res == -1) { // I/O. Just fill page with zeroes. + fwrite(zero_buf, TARGET_PAGE_SIZE, 1, f); + } + else { + fwrite(mem_buf, TARGET_PAGE_SIZE, 1, f); + } + } + fclose(f); +} + +static PyObject* pandamem_read_physical(PyObject* self, PyObject* args) { + unsigned long long addr; + unsigned long long size; + + if (!PyArg_ParseTuple(args, "KK", &addr, &size)) { + return NULL; + } + + // Limit single read size to prevent excessive allocation + if (size > 100 * 1024 * 1024) { + PyErr_SetString(PyExc_ValueError, "Read size too large (max 100MB)"); + return NULL; + } + + uint8_t* buffer = (uint8_t*)malloc(size); + if (!buffer) { + return PyErr_NoMemory(); + } + + int res = panda_physical_memory_rw(addr, buffer, size, 0); + + if (res == 0) { + PyObject* bytes = PyBytes_FromStringAndSize((char*) buffer, size); + free(buffer); + return bytes; + } else { + memset(buffer, 0, size); + PyObject* bytes = PyBytes_FromStringAndSize((char*)buffer, size); + free(buffer); + return bytes; + } +} + +static PyObject* pandamem_get_ram_size(PyObject* self, PyObject* args) { + return PyLong_FromUnsignedLongLong(ram_size); +} + +static PyMethodDef PandaMemoryMethods[] = { + {"read_physical", pandamem_read_physical, METH_VARARGS, + "Read physical memory: read_physical(addr, size) -> bytes"}, + {"get_ram_size", pandamem_get_ram_size, METH_NOARGS, + "Get total RAM size: get_ram_size() -> int"}, + {NULL, NULL, NULL, NULL} +}; + +static struct PyModuleDef pandamemmodule = { + PyModuleDef_HEAD_INIT, + "pandamem", + "PANDA direct memory access module", + -1, + PandaMemoryMethods +}; + +PyMODINIT_FUNC PyInit_pandamem(void) { + return PyModule_Create(&pandamemmodule); +} + /** * Run the volatility analysis, passing the desired profile and args * as python strings. Stores the results in the panda log or writes them @@ -153,26 +232,20 @@ static void dump_memory_tb(char* filename, hwaddr start_addr, size_t len) { int run_volatility_analysis(CPUState* env) { // Convert global strings to python strings - // PyObject* pprofile_str = PyUnicode_FromString(g_profile); - // dump_memory("mem.ram", "mem.regs.txt", 100); - PyObject* plocation_str = PyUnicode_FromString(g_location); - fprintf(stdout, "Location: %s\n", g_location); + // PyObject* plocation_str = PyUnicode_FromString(g_location); PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); - PyObject* pargs = PyTuple_New(2); + PyObject* pargs = PyTuple_New(1); // Mildly concerned about death-by-oom - if (!plocation_str) { + if (!pfilter_str) { fprintf(stderr, "[%s] Failed to allocate args\n", __FILE__); - // Py_XDECREF(pprofile_str); - Py_XDECREF(plocation_str); Py_XDECREF(pfilter_str); Py_XDECREF(pargs); } // Add these strings to an argument object - PyTuple_SetItem(pargs, 0, plocation_str); - PyTuple_SetItem(pargs, 1, pfilter_str); + PyTuple_SetItem(pargs, 0, pfilter_str); // Call run(location) PyObject* pvalue = PyObject_CallObject(g_pfunc, pargs); @@ -267,17 +340,17 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) g_current_process = kosi_get_current_process(kosi); g_targeted = g_filter->thread_check(process_get_pid(g_current_process), process_get_asid(g_current_process)); - + g_check_for_process = false; } - // if (!g_targeted) { - // printf("In not gtargeted\n"); - // return; - // } + if (!g_targeted) { + // printf("In not gtargeted\n"); + return; + } - hwaddr tb_start_addr = tb->pc; - size_t tb_length = tb->size; + // hwaddr tb_start_addr = tb->pc; + // size_t tb_length = tb->size; // fprintf(stdout, "PC: %lu, Size: %d\n", tb_start_addr, tb_length); auto pid = process_get_pid(g_current_process); @@ -287,7 +360,8 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) if (!g_filter->thread_check(pid, asid, tid)) { return; } - dump_memory_tb("mem.ram", tb_start_addr, tb_length); + // dump_memory_tb("mem.ram", tb_start_addr, tb_length); + // panda_memsavep("mem.ram"); run_volatility_analysis(env); // remove the thread now that we've handled it and make @@ -421,9 +495,9 @@ bool init_plugin(void* self) const char* venv_path_cstr = std::getenv("VIRTUAL_ENV"); std::string venv_path(venv_path_cstr); std::string exec_path = venv_path + "/bin/python"; - std::wstring w_venv_path(venv_path.begin(), venv_path.end()); std::wstring w_exec(exec_path.begin(), exec_path.end()); + PyImport_AppendInittab("pandamem", PyInit_pandamem); Py_SetProgramName((wchar_t*) g_program_name); PyConfig_InitPythonConfig(&config); PyConfig_SetString(&config, &config.executable, w_exec.c_str()); @@ -485,8 +559,6 @@ bool init_plugin(void* self) Py_XDECREF(g_pfunc); g_pfunc = NULL; PyConfig_Clear(&config); - unlink("mem.ram"); - // unlink("mem.regs.txt"); return false; } @@ -498,6 +570,4 @@ void uninit_plugin(void* self) PyConfig_Clear(&config); Py_Finalize(); teardown_avro(); - unlink("mem.ram"); - // unlink("mem.regs.txt"); } diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index a223abc..4bba484 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -14,6 +14,9 @@ import tempfile import traceback +import urllib +from urllib.request import BaseHandler + import volatility3 from volatility3.cli import text_renderer from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo @@ -24,67 +27,123 @@ import socket from typing import Optional -vollog = logging.getLogger(__name__) - -class UnixSocketFileHandler(interfaces.plugins.FileHandlerInterface): - def __init__(self, socket_path: str, filename: str) -> None: - """Initializes the UnixSocketFileHandler.""" - super().__init__(filename) - self.socket_path = socket_path - self.sock: Optional[socket.socket] = None - self.file: Optional[socket.SocketIO] = None - - def open(self): - """Connects to the existing Unix domain socket and wraps it in a file-like interface.""" - try: - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.sock.connect(self.socket_path) - # Wrap the socket in a file-like interface - self.file = self.sock.makefile(mode="rwb") - except FileNotFoundError: - raise Exception(f"Socket file not found: {self.socket_path}") - except PermissionError: - raise Exception(f"Permission denied for socket: {self.socket_path}") - except Exception as e: - raise Exception(f"Failed to connect to Unix domain socket: {e}") - - def read(self, buffer_size: int = 1024) -> bytes: - """Reads data from the socket.""" - if self.file is None: - raise Exception("Socket is not connected") - return self.file.read(buffer_size) - - def write(self, data: bytes): - """Writes data to the socket.""" - if self.file is None: - raise Exception("Socket is not connected") - self.file.write(data) - self.file.flush() - - def close(self): - """Closes the socket connection.""" - if self.file is not None: - self.file.close() - self.file = None - if self.sock is not None: - self.sock.close() - self.sock = None - - @staticmethod - def sanitize_filename(filename: str) -> str: - """Sanitizes the filename to ensure only a specific allow list of characters is allowed through.""" - allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^#~," - result = "" - for char in filename: - if char in allowed: - result += char - else: - result += "_" # change unwanted chars to an underscore - return result +import pandamem +vollog = logging.getLogger(__name__) +DEBUG = False ctx = contexts.Context() +class PandaFile(object): + """ + Constructs a file that volatility can't ignore + to back by PANDA physical memory + """ + + def __init__(self, length): + self.pos = 0 + self.length = length + self.closed = False + self.mode = "rb" + self.name = "panda://memory" + self.classname = type(self).__name__ + self.x86_32 = (length <= 0xffffffff) + + def readable(self): + return self.closed + + def read(self, size=1): + if self.x86_32: + addr = self.pos & 0xfffffff + else: + addr = self.pos + + data = pandamem.read_physical(addr, size) + + if DEBUG: + print(self.classname+": Reading " + str(size)+" bytes from "+hex(self.pos)) + + self.pos += size + return data + + def peek(self, size=1): + return pandamem.read_physical(addr, size) + + def seek(self, pos, whence=0): + if whence == 0: + self.pos = pos + elif whence == 1: + self.pos += pos + else: + self.pos = self.length - pos + if self.pos > self.length: + print(self.classname+": We've gone off the deep end") + if DEBUG: + print(self.classname+" Seeking to address "+hex(self.pos)) + + def tell(self): + return self.pos + + def close(self): + self.closed = True + +class PandaFileHandler(BaseHandler): + def default_open(self, req): + if 'panda://' in req.full_url or 'panda.panda' in req.full_url: + length = pandamem.get_ram_size() + + if length > 0xc0000000: + length += 0x40000000 # 3GB hole + if DEBUG: + print(type(self).__name__ + ": initializing PandaFile with length="+hex(length)) + + return PandaFile(length=length) + + return None + + def file_close(self): + return True + +def setup_panda_handler(): + """ + Register the panda:// URL handler with urllib. + This allows Volatility to open "panda://memory" as if it were a file. + """ + opener = urllib.request.build_opener(PandaFileHandler()) + urllib.request.install_opener(opener) + vollog.info("[PandaFileHandler] Registered panda:// protocol handler") + +def test_file_behavior(): + print(f"\n{'='*60}") + print(f"Testing pandamem module...") + print(f"{'='*60}") + ram_size = pandamem.get_ram_size() + print(f"Ram size: {ram_size}") + # TEST 1: Can we read from address 0? + print("TEST 1: Reading 16 bytes from address 0x0...") + try: + data = pandamem.read_physical(0, 16) + print(f" SUCCESS: {data.hex()}") + except Exception as e: + print(f" FAILED: {e}") + return json.dumps({"error": f"pandamem.read_physical failed: {e}"}) + + # TEST 2: Can we read from address 0x1000 (typical page)? + print("TEST 2: Reading 4096 bytes from address 0x1000...") + try: + data = pandamem.read_physical(0x1000, 4096) + print(f" SUCCESS: {data.hex()}") + except Exception as e: + print(f" FAILED: {e}") + + # TEST 3: Can we read a larger chunk? + print("TEST 3: Reading 4096 bytes (one page)...") + try: + data = pandamem.read_physical(0, 4096) + print(f" SUCCESS: Read {len(data)} bytes") + except Exception as e: + print(f" FAILED: {e}") + print(f"{'='*60}\n") def filter_invalid_ascii(strobj): return strobj.encode("utf-8", "replace") @@ -267,12 +326,12 @@ def driverscan_visitor(node, accumulator): return accumulator -def get_pslist(available_automagics, socket_path): +def get_pslist(available_automagics): """List all the tasks that aren't hidden, unlinked, etc""" config_path = "plugins.PsList" automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + print(f"[AUTOMAGICS]: {automagics}") # breakpoint() - # constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=lambda filename: UnixSocketFileHandler(socket_path, filename)) constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=None) # breakpoint() treegrid = constructed.run() @@ -333,7 +392,7 @@ def get_sockets(available_automagics): return socket_data -def run(location, filterfile): +def run(location): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -342,19 +401,22 @@ def run(location, filterfile): """ # print("Volatility version: %r" % volatility3.framework.constants.VERSION) + setup_panda_handler() + + test_file_behavior() + + location = "panda://memory" ctx.config["automagic.LayerStacker.single_location"] = location + # Build automagics + print("Running automagic to build layers...") available_automagics = automagic.available(ctx) - # socket_path = location[6:] - # breakpoint() - # unix_socket_handler = UnixSocketFileHandler(socket_path, location[12:]) - # unix_socket_handler.open() - # breakpoint() + try: - with open(filterfile, "rb") as fobj: - filter_data = json.load(fobj) + # with open(filterfile, "rb") as fobj: + # filter_data = json.load(fobj) analysis_results = { - "pslist": get_pslist(available_automagics, location), + "pslist": get_pslist(available_automagics), "svcscan": get_svcscan(available_automagics), "sockets": get_sockets(available_automagics), # "process_hashes": get_process_hashes(available_automagics, filter_data), From e75e1b72bf89d8b9e13f6252ebfb3162a7f52624 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Mon, 13 Apr 2026 13:28:12 -0400 Subject: [PATCH 13/24] Debug information --- plugins/volatility/volatility.cc | 9 ++- plugins/volatility/volglue3.py | 103 ++++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 24 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 8238a7c..8356fe4 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -360,8 +360,7 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) if (!g_filter->thread_check(pid, asid, tid)) { return; } - // dump_memory_tb("mem.ram", tb_start_addr, tb_length); - // panda_memsavep("mem.ram"); + run_volatility_analysis(env); // remove the thread now that we've handled it and make @@ -565,6 +564,12 @@ bool init_plugin(void* self) void uninit_plugin(void* self) { // stop_memory_server(); + fprintf(stdout, "\n[VOLATILITY] Running analysis at end of replay...\n"); + + // CPUState* env = first_cpu; + // if (env) { + // run_volatility_analysis(env); + // } Py_XDECREF(g_pfunc); g_pfunc = NULL; PyConfig_Clear(&config); diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 4bba484..603d580 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -22,7 +22,7 @@ from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer from volatility3.framework import automagic, contexts, interfaces, plugins - +from volatility3.framework.layers.physical import FileLayer import socket from typing import Optional @@ -67,7 +67,7 @@ def read(self, size=1): return data def peek(self, size=1): - return pandamem.read_physical(addr, size) + return pandamem.read_physical(self.pos, size) def seek(self, pos, whence=0): if whence == 0: @@ -129,12 +129,12 @@ def test_file_behavior(): return json.dumps({"error": f"pandamem.read_physical failed: {e}"}) # TEST 2: Can we read from address 0x1000 (typical page)? - print("TEST 2: Reading 4096 bytes from address 0x1000...") - try: - data = pandamem.read_physical(0x1000, 4096) - print(f" SUCCESS: {data.hex()}") - except Exception as e: - print(f" FAILED: {e}") + # print("TEST 2: Reading 4096 bytes from address 0x1000...") + # try: + # data = pandamem.read_physical(0x1000, 4096) + # print(f" SUCCESS: {data.hex()}") + # except Exception as e: + # print(f" FAILED: {e}") # TEST 3: Can we read a larger chunk? print("TEST 3: Reading 4096 bytes (one page)...") @@ -328,17 +328,68 @@ def driverscan_visitor(node, accumulator): def get_pslist(available_automagics): """List all the tasks that aren't hidden, unlinked, etc""" - config_path = "plugins.PsList" - automagics = automagic.choose_automagic(available_automagics, pslist.PsList) - print(f"[AUTOMAGICS]: {automagics}") - # breakpoint() - constructed = plugins.construct_plugin(ctx, automagics, pslist.PsList, config_path, progress_callback=None, open_method=None) - # breakpoint() - treegrid = constructed.run() - pslist_data = [] - treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=pslist_data) - return pslist_data - # text_renderer.PrettyTextRenderer().render(treegrid) + print("\n" + "="*60) + print("[get_pslist] Starting") + print("="*60) + + try: + config_path = "plugins.PsList" + + print("[get_pslist] Choosing automagic...") + automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + print(f"[get_pslist] Chosen {len(automagics)} automagics") + ctx.config['plugins.PsList.PsList.kernel.layer_name'] = 'panda://memory' + ctx.config['plugins.PsList.PsList.kernel.symbol_table_name'] = 'symbol_table_name1' + print("[get_pslist] Constructing plugin...") + constructed = plugins.construct_plugin( + ctx, automagics, pslist.PsList, config_path, + progress_callback=None, open_method=None + ) + + print(f"[get_pslist] Config after construct:") + print(f" - layer_name: {ctx.config.get('plugins.PsList.kernel.layer_name', 'NOT SET')}") + print(f" - symbol_table: {ctx.config.get('plugins.PsList.kernel.symbol_table_name', 'NOT SET')}") + + print("[get_pslist] Running plugin...") + treegrid = constructed.run() + + print("[get_pslist] Extracting data...") + pslist_data = [] + treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=pslist_data) + + print(f"[get_pslist] SUCCESS: Found {len(pslist_data)} processes") + print("="*60 + "\n") + + return pslist_data + + except Exception as e: + print(f"\n[get_pslist] EXCEPTION CAUGHT: {type(e).__name__}") + print(f"[get_pslist] Error message: {str(e)}") + + # Try to extract unsatisfied requirements + if hasattr(e, 'unsatisfied'): + print(f"[get_pslist] Unsatisfied requirements:") + for req in e.unsatisfied: + # breakpoint() + print(f" - {req}") + + # Also check what's in the context + print(f"[get_pslist] Context config keys:") + for key in sorted(ctx.config.keys()): + print(f" - {key}: {ctx.config[key]}") + + print(f"[get_pslist] Context layers:") + for layer_name in ctx.layers.keys(): + print(f" - {layer_name}") + + print(f"[get_pslist] Context symbol tables:") + for symbol in ctx.symbol_space.keys(): + print(f" - {symbol}") + + print("\n" + traceback.format_exc()) + print("="*60 + "\n") + + return {"error": str(e), "traceback": traceback.format_exc()} def get_psscan(): """List all the tasks including hidden, unlinked, etc""" @@ -406,7 +457,9 @@ def run(location): test_file_behavior() location = "panda://memory" - ctx.config["automagic.LayerStacker.single_location"] = location + config_path = "automagic.LayerStacker.single_location" + ctx.config[config_path] = location + # breakpoint() # Build automagics print("Running automagic to build layers...") available_automagics = automagic.available(ctx) @@ -422,8 +475,14 @@ def run(location): # "process_hashes": get_process_hashes(available_automagics, filter_data), # "memory_hashes": get_memory_hashes(filter_data), } - except Exception as err: - analysis_results = {"error": traceback.format_exc(err)} + except Exception as e: + print(f"ERROR: {e}") + print(traceback.format_exc()) + print(dict(ctx.config)) + analysis_results = {"error": str(e), + "error_type": type(e).__name__, + "traceback": traceback.format_exc() + } json_str = json.dumps(analysis_results, indent=1) return json_str From b6e071e52138ac666c8dca3667c90b7231378f8d Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Mon, 13 Apr 2026 15:53:15 -0400 Subject: [PATCH 14/24] Switched to file://tmp/panda.panda --- plugins/volatility/volglue3.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 603d580..f868322 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -338,8 +338,6 @@ def get_pslist(available_automagics): print("[get_pslist] Choosing automagic...") automagics = automagic.choose_automagic(available_automagics, pslist.PsList) print(f"[get_pslist] Chosen {len(automagics)} automagics") - ctx.config['plugins.PsList.PsList.kernel.layer_name'] = 'panda://memory' - ctx.config['plugins.PsList.PsList.kernel.symbol_table_name'] = 'symbol_table_name1' print("[get_pslist] Constructing plugin...") constructed = plugins.construct_plugin( ctx, automagics, pslist.PsList, config_path, @@ -454,9 +452,9 @@ def run(location): # print("Volatility version: %r" % volatility3.framework.constants.VERSION) setup_panda_handler() - test_file_behavior() + # test_file_behavior() - location = "panda://memory" + location = "file:///tmp/panda.panda" config_path = "automagic.LayerStacker.single_location" ctx.config[config_path] = location # breakpoint() @@ -481,7 +479,7 @@ def run(location): print(dict(ctx.config)) analysis_results = {"error": str(e), "error_type": type(e).__name__, - "traceback": traceback.format_exc() + "traceback": traceback.format_exc(e) } json_str = json.dumps(analysis_results, indent=1) From a7b4a0fbb7b7adbd3c6cfa30fa36308ce7772ed1 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Mon, 13 Apr 2026 15:53:45 -0400 Subject: [PATCH 15/24] Switched analysis point --- plugins/volatility/volatility.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 8356fe4..9abbda0 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -344,10 +344,10 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) g_check_for_process = false; } - if (!g_targeted) { - // printf("In not gtargeted\n"); - return; - } + // if (!g_targeted) { + // // printf("In not gtargeted\n"); + // return; + // } // hwaddr tb_start_addr = tb->pc; // size_t tb_length = tb->size; @@ -564,7 +564,7 @@ bool init_plugin(void* self) void uninit_plugin(void* self) { // stop_memory_server(); - fprintf(stdout, "\n[VOLATILITY] Running analysis at end of replay...\n"); + // fprintf(stdout, "\n[VOLATILITY] Running analysis at end of replay...\n"); // CPUState* env = first_cpu; // if (env) { From a296c8053c9c4fe9f6bce9c4898ac8758a231061 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Thu, 16 Apr 2026 09:31:05 -0400 Subject: [PATCH 16/24] Debug code --- plugins/volatility/volatility.cc | 16 +++--- plugins/volatility/volglue3.py | 89 +++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 28 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 9abbda0..38897db 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -176,8 +176,8 @@ static PyObject* pandamem_read_physical(PyObject* self, PyObject* args) { } // Limit single read size to prevent excessive allocation - if (size > 100 * 1024 * 1024) { - PyErr_SetString(PyExc_ValueError, "Read size too large (max 100MB)"); + if (size > ram_size) { + PyErr_SetString(PyExc_ValueError, "Read size too large (max 2GB)"); return NULL; } @@ -225,8 +225,8 @@ PyMODINIT_FUNC PyInit_pandamem(void) { } /** - * Run the volatility analysis, passing the desired profile and args - * as python strings. Stores the results in the panda log or writes them + * Run the volatility analysis, passing the filter as a + * python string. Stores the results in the panda log or writes them * to stderr */ int run_volatility_analysis(CPUState* env) @@ -344,10 +344,10 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) g_check_for_process = false; } - // if (!g_targeted) { - // // printf("In not gtargeted\n"); - // return; - // } + if (!g_targeted) { + // printf("In not gtargeted\n"); + return; + } // hwaddr tb_start_addr = tb->pc; // size_t tb_length = tb->size; diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index f868322..1120349 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -26,11 +26,12 @@ import socket from typing import Optional +from pathlib import Path import pandamem vollog = logging.getLogger(__name__) -DEBUG = False +DEBUG = True ctx = contexts.Context() @@ -45,7 +46,7 @@ def __init__(self, length): self.length = length self.closed = False self.mode = "rb" - self.name = "panda://memory" + self.name = "/tmp/panda.panda" self.classname = type(self).__name__ self.x86_32 = (length <= 0xffffffff) @@ -62,6 +63,11 @@ def read(self, size=1): if DEBUG: print(self.classname+": Reading " + str(size)+" bytes from "+hex(self.pos)) + + file_path = Path(f'/data/small_{hex(self.pos)}.dd') + if not file_path.exists(): + with open(file_path, 'wb') as f: + f.write(data) self.pos += size return data @@ -278,9 +284,6 @@ def pslist_visitor(node, accumulator): if node.values: pid, ppid, img_name, offset = node.values[0:4] proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) - # if img_name == "services.exe": - # print(offset) - # breakpoint() pdata = { "pid": int(pid), "ppid": int(ppid), @@ -345,8 +348,8 @@ def get_pslist(available_automagics): ) print(f"[get_pslist] Config after construct:") - print(f" - layer_name: {ctx.config.get('plugins.PsList.kernel.layer_name', 'NOT SET')}") - print(f" - symbol_table: {ctx.config.get('plugins.PsList.kernel.symbol_table_name', 'NOT SET')}") + # breakpoint() + print(ctx.config) print("[get_pslist] Running plugin...") treegrid = constructed.run() @@ -402,19 +405,65 @@ def get_psscan(): def get_svcscan(available_automagics): """List all of the system services""" - config_path = "plugins.SvcScan" - automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) - constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - svcscan_data = [] - treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) - driverscan_data = get_driverscan(available_automagics) + print("\n" + "="*60) + print("[get_svcscan] Starting") + print("="*60) + + try: + config_path = "plugins.SvcScan" + + print("[get_svcscan] Choosing automagic...") + automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) + print(f"[get_svcscan] Chosen {len(automagics)} automagics") + print("[get_svcscan] Constructing plugin...") + constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) + print(f"[get_svcscan] Config after construct:") + print(ctx.config) + + print("[get_svcscan] Running plugin...") + treegrid = constructed.run() + + print("[get_svcscan] Extracting data...") + svcscan_data = [] + treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) + print(f"[get_svcscan] SUCCESS: Found {len(svcscan_data)} services") + print("="*60 + "\n") - for svc in svcscan_data: - for drv in driverscan_data: - if svc["ServiceName"] == drv["servicekey"]: - svc["DriverName"] = drv["name"] - return svcscan_data + driverscan_data = get_driverscan(available_automagics) + + for svc in svcscan_data: + for drv in driverscan_data: + if svc["ServiceName"] == drv["servicekey"]: + svc["DriverName"] = drv["name"] + return svcscan_data + except Exception as e: + print(f"\n[get_svcscan] EXCEPTION CAUGHT: {type(e).__name__}") + print(f"[get_svcscan] Error message: {str(e)}") + + # Try to extract unsatisfied requirements + if hasattr(e, 'unsatisfied'): + print(f"[get_svcscan] Unsatisfied requirements:") + for req in e.unsatisfied: + # breakpoint() + print(f" - {req}") + + # Also check what's in the context + print(f"[get_svcscan] Context config keys:") + for key in sorted(ctx.config.keys()): + print(f" - {key}: {ctx.config[key]}") + + print(f"[get_svcscan] Context layers:") + for layer_name in ctx.layers.keys(): + print(f" - {layer_name}") + + print(f"[get_svcscan] Context symbol tables:") + for symbol in ctx.symbol_space.keys(): + print(f" - {symbol}") + + print("\n" + traceback.format_exc()) + print("="*60 + "\n") + + return {"error": str(e), "traceback": traceback.format_exc()} def get_driverscan(available_automagics): @@ -454,7 +503,7 @@ def run(location): # test_file_behavior() - location = "file:///tmp/panda.panda" + location = "file:/tmp/panda.panda" config_path = "automagic.LayerStacker.single_location" ctx.config[config_path] = location # breakpoint() From 801e8d42dc5d9ed7f60b071c6d9e8f369406103e Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 24 Apr 2026 14:55:00 -0400 Subject: [PATCH 17/24] Got hashing working --- plugins/volatility/volglue3.py | 270 ++++++++++++++++++++++----------- 1 file changed, 181 insertions(+), 89 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 1120349..7ca3343 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -1,10 +1,7 @@ # This section sets up the volatility environment # and is invoked by the plugin when it loads this # script as a module -# -# It should be refactored so that the setup -# is done as a function call to make it cleaner -# and take the profile as an argument + import os import json import logging @@ -18,8 +15,8 @@ from urllib.request import BaseHandler import volatility3 -from volatility3.cli import text_renderer -from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo +from volatility3.cli import CommandLine, text_renderer +from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo, vadwalk from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer from volatility3.framework import automagic, contexts, interfaces, plugins from volatility3.framework.layers.physical import FileLayer @@ -28,10 +25,10 @@ from typing import Optional from pathlib import Path -import pandamem +# import pandamem vollog = logging.getLogger(__name__) -DEBUG = True +DEBUG = False ctx = contexts.Context() @@ -60,14 +57,17 @@ def read(self, size=1): addr = self.pos data = pandamem.read_physical(addr, size) + # print(len(data)) + # breakpoint() + # mem_buf.extend(data) if DEBUG: print(self.classname+": Reading " + str(size)+" bytes from "+hex(self.pos)) - file_path = Path(f'/data/small_{hex(self.pos)}.dd') - if not file_path.exists(): - with open(file_path, 'wb') as f: - f.write(data) + # file_path = Path(f'/data/small_{hex(self.pos)}.dd') + # if not file_path.exists(): + # with open(file_path, 'wb') as f: + # f.write(data) self.pos += size return data @@ -95,13 +95,14 @@ def close(self): class PandaFileHandler(BaseHandler): def default_open(self, req): - if 'panda://' in req.full_url or 'panda.panda' in req.full_url: + if 'panda.panda' in req.full_url: length = pandamem.get_ram_size() if length > 0xc0000000: length += 0x40000000 # 3GB hole if DEBUG: print(type(self).__name__ + ": initializing PandaFile with length="+hex(length)) + # breakpoint() return PandaFile(length=length) @@ -179,82 +180,154 @@ def hash_file(filepath, *algorithms): def get_process_hashes(available_automagics, filter_data): - config_path = "plugins.PEDump" - # dump = pedump.PEDump(ctx, config_path, file_name=args.location) - # breakpoint() - automagics = automagic.choose_automagic(available_automagics, pedump.PEDump, base=0) - # breakpoint() - constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - # breakpoint() + print("\n" + "="*60) + print("[get_process_hashes] Starting") + print("="*60) results = [] - for proc in runner.calculate(): - addr = proc.get_process_address_space() - - vtop_check = False - invalid = any( - [ - addr is None, - proc.Peb is None, - ] - ) - if proc.Peb: - vtop_check = addr.vtop(proc.Peb.ImageBaseAddress) is None - - if invalid: - if vtop_check: - continue - - if is_interesting(proc, filter_data): - name = str(proc.ImageFileName) - - runner.dump_pe(addr, proc.Peb.ImageBaseAddress, name) + try: + config_path = "plugins.PEDump" + ctx.config["plugins.PEDump.PEDump.base"] = 0x400000 + ctx.config["plugins.PEDump.PEDump.pid"] = [x[0] for x in filter_data['thread_whitelist']] + # print(filter_data) + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) + constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=FileHandler) + + print(f"[get_process_hashes] Config after construct:") + # breakpoint() + # print(ctx.config) + + print("[get_process_hashes] Running plugin...") + treegrid = constructed.run() + proc_hash_data = [] + treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) + + print(f"[get_process_hashes] SUCCESS: Found {len(proc_hash_data)} processes") + print("="*60 + "\n") - dumped_file = os.path.join(config.DUMP_DIR, name) + return proc_hash_data + + except Exception as e: + print(f"\n[get_process_hashes] EXCEPTION CAUGHT: {type(e).__name__}") + print(f"[get_process_hashes] Error message: {str(e)}") + + # Try to extract unsatisfied requirements + if hasattr(e, 'unsatisfied'): + print(f"[get_process_hashes] Unsatisfied requirements:") + for req in e.unsatisfied: + # breakpoint() + print(f" - {req}") + + # Also check what's in the context + print(f"[get_process_hashes] Context config keys:") + for key in sorted(ctx.config.keys()): + print(f" - {key}: {ctx.config[key]}") + + print(f"[get_process_hashes] Context layers:") + for layer_name in ctx.layers.keys(): + print(f" - {layer_name}") + + print(f"[get_process_hashes] Context symbol tables:") + for symbol in ctx.symbol_space.keys(): + print(f" - {symbol}") + + print("\n" + traceback.format_exc()) + print("="*60 + "\n") - result = { - "pid": int(proc.UniqueProcessId), - "base": int(proc.Peb.ImageBaseAddress), - } - result.update(hash_file(dumped_file, hashlib.sha256())) - results.append(result) + return {"error": str(e), "traceback": traceback.format_exc()} - return results +def process_hashes_visitor(node, accumulator, cmd): + if node.values: + result = { + "pid": int(node.values[0]), + "base": ctx.config["plugins.PEDump.PEDump.base"], + } + dumped_file = f"{cmd.output_dir}/{node.values[2]}" + result.update(hash_file(dumped_file, hashlib.sha256())) + accumulator.append(result) + return accumulator +def vadinfo_visitor(node, accumulator): + if node.values: + startvpn, endvpn = node.values[3:5] + fileoutput = node.values[-1] + accumulator[(int(startvpn), int(endvpn))] = fileoutput + # breakpoint() + return accumulator -def get_memory_hashes(filter_data): - # breakpoint() - runner = vadinfo.VADDump() +def get_memory_hashes(available_automagics, filter_data): + print("\n" + "="*60) + print("[get_memory_hashes] Starting") + print("="*60) results = [] - for proc in runner.calculate(): - addr = proc.get_process_address_space() + try: + config_path = "plugins.VadInfo" + ctx.config["plugins.VadInfo.VadInfo.pid"] = [x[0] for x in filter_data['thread_whitelist']] + ctx.config["plugins.VadInfo.VadInfo.dump"] = True + # print(filter_data) + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, vadinfo.VadInfo) + constructed = plugins.construct_plugin(ctx, automagics, vadinfo.VadInfo, config_path, progress_callback=None, open_method=FileHandler) + + print(f"[get_memory_hashes] Config after construct:") + # print(ctx.config) + print("[get_memory_hashes] Running vadinfo plugin...") + vadinfo_data = {} + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: vadinfo_visitor(node, acc), initial_accumulator=vadinfo_data) - if not addr: - continue + config_path = "plugins.VadWalk" + ctx.config["plugins.VadWalk.VadWalk.pid"] = [x[0] for x in filter_data['thread_whitelist']] + mem_hash_data = [] + automagics = automagic.choose_automagic(available_automagics, vadwalk.VadWalk) + constructed = plugins.construct_plugin(ctx, automagics, vadwalk.VadWalk, config_path, progress_callback=None, open_method=None) - if is_interesting(proc, filter_data): + print(f"[get_memory_hashes] Config after vadwalk construct:") + # breakpoint() + # print(ctx.config) + + print("[get_memory_hashes] Running vadwalk plugin...") + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) + + print(f"[get_memory_hashes] SUCCESS: Found {len(mem_hash_data)} processes") + print("="*60 + "\n") - for vad, _ in proc.get_vads( - vad_filter=lambda v: v.Length < pow(2, 30), skip_max_commit=True - ): - path = os.path.join( - config.DUMP_DIR, - "{}_{}.{}".format(vad.Start, vad.End, proc.UniqueProcessId), - ) + return mem_hash_data + + except Exception as e: + print(f"\n[get_memory_hashes] EXCEPTION CAUGHT: {type(e).__name__}") + print(f"[get_memory_hashes] Error message: {str(e)}") + + + print("\n" + traceback.format_exc()) + print("="*60 + "\n") + + return {"error": str(e), "traceback": traceback.format_exc()} - runner.dump_vad(path, vad, addr) - result = { - "pid": int(proc.UniqueProcessId), - "start": int(vad.Start), - "end": int(vad.End), - } - result.update(hash_file(path, hashlib.sha256())) - results.append(result) +def memory_hashes_visitor(node, accumulator, cmd, vadinfo_data): + if node.values: + # print(vadinfo_data) + start = int(node.values[-3]) + end = int(node.values[-2]) + result = { + "pid": int(node.values[0]), + "start": start, + "end": end + } + # breakpoint() + dumped_file = f"{cmd.output_dir}/{vadinfo_data[(start, end)]}" + result.update(hash_file(dumped_file, hashlib.sha256())) + accumulator.append(result) + return accumulator - return results def socket_visitor(node, accumulator): if node.values: @@ -280,10 +353,21 @@ def socket_visitor(node, accumulator): accumulator.append(sdata) return accumulator -def pslist_visitor(node, accumulator): +def pslist_visitor(node, accumulator, filter_data): if node.values: pid, ppid, img_name, offset = node.values[0:4] proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) + if is_interesting(proc, filter_data): + # breakpoint() + result = { + "pid": int(pid), + "base": int(proc.get_peb().ImageBaseAddress) + } + # result.update(hash_file(, hashlib.sha256())) + # accumulator[1].append(result) + # for dumps in os.listdir(cmd.output_dir): + + # breakpoint() pdata = { "pid": int(pid), "ppid": int(ppid), @@ -329,7 +413,7 @@ def driverscan_visitor(node, accumulator): return accumulator -def get_pslist(available_automagics): +def get_pslist(available_automagics, filter_data): """List all the tasks that aren't hidden, unlinked, etc""" print("\n" + "="*60) print("[get_pslist] Starting") @@ -339,6 +423,11 @@ def get_pslist(available_automagics): config_path = "plugins.PsList" print("[get_pslist] Choosing automagic...") + # ctx.config[f"{config_path}.PsList.pid"] = [x[0] for x in filter_data['thread_whitelist']] + # cmd = CommandLine() + # cmd.output_dir = tempfile.mkdtemp() + # FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, pslist.PsList) print(f"[get_pslist] Chosen {len(automagics)} automagics") print("[get_pslist] Constructing plugin...") @@ -349,15 +438,15 @@ def get_pslist(available_automagics): print(f"[get_pslist] Config after construct:") # breakpoint() - print(ctx.config) + # print(ctx.config) print("[get_pslist] Running plugin...") treegrid = constructed.run() print("[get_pslist] Extracting data...") pslist_data = [] - treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=pslist_data) - + treegrid.visit(node=None, function=lambda node, acc: pslist_visitor(node, acc, filter_data), initial_accumulator=pslist_data) + # breakpoint() print(f"[get_pslist] SUCCESS: Found {len(pslist_data)} processes") print("="*60 + "\n") @@ -411,6 +500,7 @@ def get_svcscan(available_automagics): try: config_path = "plugins.SvcScan" + print(f"[Location] {ctx.config['automagic.LayerStacker.single_location']}") print("[get_svcscan] Choosing automagic...") automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) @@ -418,7 +508,7 @@ def get_svcscan(available_automagics): print("[get_svcscan] Constructing plugin...") constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) print(f"[get_svcscan] Config after construct:") - print(ctx.config) + # print(ctx.config) print("[get_svcscan] Running plugin...") treegrid = constructed.run() @@ -490,7 +580,7 @@ def get_sockets(available_automagics): return socket_data -def run(location): +def run(location, filterfile): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -502,25 +592,26 @@ def run(location): setup_panda_handler() # test_file_behavior() - - location = "file:/tmp/panda.panda" + # memory_file = "mem.ram" + # location = f"file:{memory_file}" config_path = "automagic.LayerStacker.single_location" ctx.config[config_path] = location + ctx.config["config.output_dir"] = "here" # breakpoint() # Build automagics print("Running automagic to build layers...") available_automagics = automagic.available(ctx) try: - # with open(filterfile, "rb") as fobj: - # filter_data = json.load(fobj) + with open(filterfile, "rb") as fobj: + filter_data = json.load(fobj) analysis_results = { - "pslist": get_pslist(available_automagics), + "pslist": get_pslist(available_automagics, filter_data), "svcscan": get_svcscan(available_automagics), "sockets": get_sockets(available_automagics), - # "process_hashes": get_process_hashes(available_automagics, filter_data), - # "memory_hashes": get_memory_hashes(filter_data), + "process_hashes": get_process_hashes(available_automagics, filter_data), + "memory_hashes": get_memory_hashes(available_automagics, filter_data), } except Exception as e: print(f"ERROR: {e}") @@ -539,8 +630,9 @@ def run(location): parser = argparse.ArgumentParser(description="Test analysis") parser.add_argument("--location", default="file:mymem.dd") + parser.add_argument("--filter", default="aprog-x64-tracefilter.json") args = parser.parse_args() - print(run(args.location)) + print(run(args.location, args.filter)) ### Must end with this comment \ No newline at end of file From 8cfba8c607a74e43c39b5c1c846470d951080399 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 24 Apr 2026 15:47:01 -0400 Subject: [PATCH 18/24] Working pipeline with good output but exceptions --- plugins/volatility/volatility.cc | 115 +++---------------------------- plugins/volatility/volglue3.py | 29 +++----- 2 files changed, 19 insertions(+), 125 deletions(-) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index 38897db..a8cc0f8 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -22,7 +22,6 @@ extern "C" { #include "osi/windows/wintrospection.h" #include "filter.h" -#include "memory-server.h" // Globals to be set by configs, eventually char g_program_name[] = "volatility_plugin"; @@ -31,13 +30,10 @@ char g_func_name[] = "run"; char g_script_path[4096] = {0}; -// Globals to be calculated by the plugin, eventually char g_profile[512] = {0}; // Constants -#define SOCKET_PATH_FMT "/tmp/panda%d.sock" #define TARGET_PAGE_SIZE 1024 -char g_location[512] = "file:\0"; char g_filter_path[512] = {0}; const char g_script_name[] = "/volglue3.py"; @@ -73,78 +69,6 @@ void uninit_plugin(void*); int run_volatility_analysis(CPUState* env); bool log_analysis_results(CPUState* env, const char* data); -static const uint8_t _zero_block[1024] = {0}; -static void actually_dump_physical_memory(FILE* out, size_t len) -{ - hwaddr addr = 0; - uint8_t block[sizeof(_zero_block)]; - - if (!out) - return; - - while (len != 0) - { - size_t l = sizeof(block); - if (l > len) - l = len; - if (panda_physical_memory_read(addr, block, l) == MEMTX_OK) - fwrite(block, 1, l, out); - else - fwrite(_zero_block, 1, l, out); - addr += l; - len -= l; - } -} - -static void dump_memory(char* filename, char* register_filename, uint64_t pmem_len){ - FILE* out = fopen(filename, "wb"); - - if (pmem_len == 0){ - // dump all memory if not specified as arg - pmem_len = ram_size; - } - - actually_dump_physical_memory(out, pmem_len); - fclose(out); - if (register_filename) - { - if ((out = fopen(register_filename, "w")) != NULL) - { - CPUState* cpu; - CPU_FOREACH(cpu) - { - fprintf(out, "CPU#%d\n", cpu->cpu_index); - cpu_dump_state(cpu, out, fprintf, CPU_DUMP_FPU); - } - fclose(out); - } - } - - panda_replay_end(); -} - -static void dump_memory_tb(char* filename, hwaddr start_addr, size_t len) { - FILE* out = fopen(filename, "wb"); - if (!out) { - fprintf(stderr, "Failed to open file for memory dump: %s\n", filename); - return; - } - - uint8_t block[1024]; // Adjust block size as needed - while (len != 0) { - size_t l = sizeof(block); - if (l > len) - l = len; - if (panda_physical_memory_read(start_addr, block, l) == MEMTX_OK) - fwrite(block, 1, l, out); - else - fwrite(_zero_block, 1, l, out); - start_addr += l; - len -= l; - } - - fclose(out); -} void panda_memsavep(char* filename) { FILE* f = fopen(filename, "wb"); @@ -188,7 +112,7 @@ static PyObject* pandamem_read_physical(PyObject* self, PyObject* args) { int res = panda_physical_memory_rw(addr, buffer, size, 0); - if (res == 0) { + if (res == MEMTX_OK) { PyObject* bytes = PyBytes_FromStringAndSize((char*) buffer, size); free(buffer); return bytes; @@ -231,8 +155,10 @@ PyMODINIT_FUNC PyInit_pandamem(void) { */ int run_volatility_analysis(CPUState* env) { + // Dump RAM at this moment + panda_memsavep("mem.ram"); + // Convert global strings to python strings - // PyObject* plocation_str = PyUnicode_FromString(g_location); PyObject* pfilter_str = PyUnicode_FromString(g_filter_path); PyObject* pargs = PyTuple_New(1); @@ -247,7 +173,7 @@ int run_volatility_analysis(CPUState* env) // Add these strings to an argument object PyTuple_SetItem(pargs, 0, pfilter_str); - // Call run(location) + // Call run(filter) PyObject* pvalue = PyObject_CallObject(g_pfunc, pargs); if (pvalue) { // The function returned a value successfully @@ -345,13 +271,9 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) } if (!g_targeted) { - // printf("In not gtargeted\n"); return; } - // hwaddr tb_start_addr = tb->pc; - // size_t tb_length = tb->size; - // fprintf(stdout, "PC: %lu, Size: %d\n", tb_start_addr, tb_length); auto pid = process_get_pid(g_current_process); auto asid = process_get_asid(g_current_process); @@ -362,6 +284,7 @@ void before_block_exec(CPUState* env, TranslationBlock* tb) } run_volatility_analysis(env); + unlink("mem.ram"); // remove the thread now that we've handled it and make // the next bb refresh state info @@ -446,6 +369,7 @@ bool init_plugin(void* self) panda_arg_list* filter_args = panda_get_args("filter"); filter_path = panda_parse_string(filter_args, "file", ""); strncpy(g_filter_path, filter_path, sizeof(g_filter_path) - 1); + fprintf(stdout, "[Filter file]: %s\n", g_filter_path); g_filter.reset(new InstrumentationFilter(g_filter_path)); panda_free_args(filter_args); @@ -456,23 +380,13 @@ bool init_plugin(void* self) // Read arguments const char* profile_arg = panda_os_name; - // const char* profile = nullptr; // volatility profile if (!profile_arg) { fprintf(stderr, "[%s] The -os flag is required\n", __FILE__); return false; } - char* socket_path = (char*)calloc(512, 1); - if (!socket_path) { - fprintf(stderr, "[%s] Failed to allocate memory for socket path\n", __FILE__); - return false; - } - // sprintf(socket_path, SOCKET_PATH_FMT, getpid()); - strncat(g_location, "mem.ram", sizeof(g_location) - 1); - const char* python_script = panda_parse_string(vol_args, "script", g_script_path); - // strncpy(g_profile, profile, sizeof(g_profile) - 1); panda_free_args(vol_args); panda_cb pcb; @@ -507,7 +421,6 @@ bool init_plugin(void* self) CHECK_OR_DIE(pcode, "Failed to compile python program!\n", cleanup); // Load the code object into a module - // fprintf(stdout, "Module name: %s\n", g_module_name); pmodule = PyImport_ExecCodeModule("gluemod", pcode); CHECK_OR_DIE(pmodule, "Failed to load as module!\n", cleanup); @@ -522,11 +435,6 @@ bool init_plugin(void* self) fprintf(stdout, "Successfully initialized python routines.\n"); - // if (!start_memory_server(socket_path)) { - // fprintf(stderr, "[%s] Failed to start memory server!\n", __FILE__); - // goto cleanup; - // } - if (script_contents) { free(script_contents); } @@ -558,21 +466,16 @@ bool init_plugin(void* self) Py_XDECREF(g_pfunc); g_pfunc = NULL; PyConfig_Clear(&config); + unlink("mem.ram"); return false; } void uninit_plugin(void* self) { - // stop_memory_server(); - // fprintf(stdout, "\n[VOLATILITY] Running analysis at end of replay...\n"); - - // CPUState* env = first_cpu; - // if (env) { - // run_volatility_analysis(env); - // } Py_XDECREF(g_pfunc); g_pfunc = NULL; PyConfig_Clear(&config); Py_Finalize(); teardown_avro(); + unlink("mem.ram"); } diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index 7ca3343..c3fb48b 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -20,6 +20,7 @@ from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer from volatility3.framework import automagic, contexts, interfaces, plugins from volatility3.framework.layers.physical import FileLayer +from volatility3.framework.layers.linear import LinearlyMappedLayer import socket from typing import Optional @@ -449,7 +450,7 @@ def get_pslist(available_automagics, filter_data): # breakpoint() print(f"[get_pslist] SUCCESS: Found {len(pslist_data)} processes") print("="*60 + "\n") - + LinearlyMappedLayer.read.cache_clear() return pslist_data except Exception as e: @@ -481,16 +482,6 @@ def get_pslist(available_automagics, filter_data): return {"error": str(e), "traceback": traceback.format_exc()} -def get_psscan(): - """List all the tasks including hidden, unlinked, etc""" - config_path = "plugins.PsScan" - automagics = automagic.choose_automagic(available_automagics, psscan.PsScan) - constructed = plugins.construct_plugin(ctx, automagics, psscan.PsScan, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - psscan_data = [] - treegrid.visit(node=None, function=pslist_visitor, initial_accumulator=psscan_data) - return psscan_data - # text_renderer.PrettyTextRenderer().render(treegrid) def get_svcscan(available_automagics): """List all of the system services""" @@ -525,6 +516,7 @@ def get_svcscan(available_automagics): for drv in driverscan_data: if svc["ServiceName"] == drv["servicekey"]: svc["DriverName"] = drv["name"] + LinearlyMappedLayer.read.cache_clear() return svcscan_data except Exception as e: print(f"\n[get_svcscan] EXCEPTION CAUGHT: {type(e).__name__}") @@ -552,7 +544,7 @@ def get_svcscan(available_automagics): print("\n" + traceback.format_exc()) print("="*60 + "\n") - + LinearlyMappedLayer.read.cache_clear() return {"error": str(e), "traceback": traceback.format_exc()} @@ -580,7 +572,7 @@ def get_sockets(available_automagics): return socket_data -def run(location, filterfile): +def run(filterfile): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -589,15 +581,14 @@ def run(location, filterfile): """ # print("Volatility version: %r" % volatility3.framework.constants.VERSION) - setup_panda_handler() + # setup_panda_handler() # test_file_behavior() - # memory_file = "mem.ram" - # location = f"file:{memory_file}" + memory_file = "mem.ram" + location = f"file:{memory_file}" config_path = "automagic.LayerStacker.single_location" ctx.config[config_path] = location - ctx.config["config.output_dir"] = "here" - # breakpoint() + # Build automagics print("Running automagic to build layers...") available_automagics = automagic.available(ctx) @@ -632,7 +623,7 @@ def run(location, filterfile): parser.add_argument("--location", default="file:mymem.dd") parser.add_argument("--filter", default="aprog-x64-tracefilter.json") args = parser.parse_args() - print(run(args.location, args.filter)) + print(run(args.filter)) ### Must end with this comment \ No newline at end of file From 0c3a4354920f906a5cde2519f75811a77e542479 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 24 Apr 2026 16:29:39 -0400 Subject: [PATCH 19/24] Removed debug code and rearranged --- plugins/volatility/volglue3.py | 433 +++++++++------------------------ 1 file changed, 114 insertions(+), 319 deletions(-) diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py index c3fb48b..be3b26f 100644 --- a/plugins/volatility/volglue3.py +++ b/plugins/volatility/volglue3.py @@ -153,19 +153,6 @@ def test_file_behavior(): print(f" FAILED: {e}") print(f"{'='*60}\n") -def filter_invalid_ascii(strobj): - return strobj.encode("utf-8", "replace") - - -def is_interesting(eprocess, filter_data): - check_pid = int(eprocess.UniqueProcessId) - check_asid = int(eprocess.Pcb.DirectoryTableBase) - - for (pid, tid, asid) in filter_data["thread_whitelist"]: - if check_pid == int(pid) and check_asid == int(asid): - return True - return False - def hash_file(filepath, *algorithms): with open(filepath, "rb") as fobj: @@ -181,64 +168,21 @@ def hash_file(filepath, *algorithms): def get_process_hashes(available_automagics, filter_data): - print("\n" + "="*60) - print("[get_process_hashes] Starting") - print("="*60) + config_path = "plugins.PEDump" + ctx.config["plugins.PEDump.PEDump.base"] = 0x400000 + ctx.config["plugins.PEDump.PEDump.pid"] = [x[0] for x in filter_data['thread_whitelist']] + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) + constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=FileHandler) - results = [] - try: - config_path = "plugins.PEDump" - ctx.config["plugins.PEDump.PEDump.base"] = 0x400000 - ctx.config["plugins.PEDump.PEDump.pid"] = [x[0] for x in filter_data['thread_whitelist']] - # print(filter_data) - cmd = CommandLine() - cmd.output_dir = tempfile.mkdtemp() - FileHandler = cmd.file_handler_class_factory() - automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) - constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=FileHandler) - - print(f"[get_process_hashes] Config after construct:") - # breakpoint() - # print(ctx.config) - - print("[get_process_hashes] Running plugin...") - treegrid = constructed.run() - proc_hash_data = [] - treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) - - print(f"[get_process_hashes] SUCCESS: Found {len(proc_hash_data)} processes") - print("="*60 + "\n") + treegrid = constructed.run() + proc_hash_data = [] + treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) - return proc_hash_data - - except Exception as e: - print(f"\n[get_process_hashes] EXCEPTION CAUGHT: {type(e).__name__}") - print(f"[get_process_hashes] Error message: {str(e)}") - - # Try to extract unsatisfied requirements - if hasattr(e, 'unsatisfied'): - print(f"[get_process_hashes] Unsatisfied requirements:") - for req in e.unsatisfied: - # breakpoint() - print(f" - {req}") - - # Also check what's in the context - print(f"[get_process_hashes] Context config keys:") - for key in sorted(ctx.config.keys()): - print(f" - {key}: {ctx.config[key]}") - - print(f"[get_process_hashes] Context layers:") - for layer_name in ctx.layers.keys(): - print(f" - {layer_name}") - - print(f"[get_process_hashes] Context symbol tables:") - for symbol in ctx.symbol_space.keys(): - print(f" - {symbol}") - - print("\n" + traceback.format_exc()) - print("="*60 + "\n") + return proc_hash_data - return {"error": str(e), "traceback": traceback.format_exc()} def process_hashes_visitor(node, accumulator, cmd): if node.values: @@ -251,71 +195,44 @@ def process_hashes_visitor(node, accumulator, cmd): accumulator.append(result) return accumulator + +def get_memory_hashes(available_automagics, filter_data): + config_path = "plugins.VadInfo" + ctx.config["plugins.VadInfo.VadInfo.pid"] = [x[0] for x in filter_data['thread_whitelist']] + ctx.config["plugins.VadInfo.VadInfo.dump"] = True + + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, vadinfo.VadInfo) + constructed = plugins.construct_plugin(ctx, automagics, vadinfo.VadInfo, config_path, progress_callback=None, open_method=FileHandler) + + vadinfo_data = {} + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: vadinfo_visitor(node, acc), initial_accumulator=vadinfo_data) + + config_path = "plugins.VadWalk" + ctx.config["plugins.VadWalk.VadWalk.pid"] = [x[0] for x in filter_data['thread_whitelist']] + mem_hash_data = [] + automagics = automagic.choose_automagic(available_automagics, vadwalk.VadWalk) + constructed = plugins.construct_plugin(ctx, automagics, vadwalk.VadWalk, config_path, progress_callback=None, open_method=None) + + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) + + return mem_hash_data + + def vadinfo_visitor(node, accumulator): if node.values: startvpn, endvpn = node.values[3:5] fileoutput = node.values[-1] accumulator[(int(startvpn), int(endvpn))] = fileoutput - # breakpoint() return accumulator -def get_memory_hashes(available_automagics, filter_data): - print("\n" + "="*60) - print("[get_memory_hashes] Starting") - print("="*60) - - results = [] - try: - config_path = "plugins.VadInfo" - ctx.config["plugins.VadInfo.VadInfo.pid"] = [x[0] for x in filter_data['thread_whitelist']] - ctx.config["plugins.VadInfo.VadInfo.dump"] = True - # print(filter_data) - cmd = CommandLine() - cmd.output_dir = tempfile.mkdtemp() - FileHandler = cmd.file_handler_class_factory() - automagics = automagic.choose_automagic(available_automagics, vadinfo.VadInfo) - constructed = plugins.construct_plugin(ctx, automagics, vadinfo.VadInfo, config_path, progress_callback=None, open_method=FileHandler) - - print(f"[get_memory_hashes] Config after construct:") - # print(ctx.config) - print("[get_memory_hashes] Running vadinfo plugin...") - vadinfo_data = {} - treegrid = constructed.run() - treegrid.visit(node=None, function=lambda node, acc: vadinfo_visitor(node, acc), initial_accumulator=vadinfo_data) - - config_path = "plugins.VadWalk" - ctx.config["plugins.VadWalk.VadWalk.pid"] = [x[0] for x in filter_data['thread_whitelist']] - mem_hash_data = [] - automagics = automagic.choose_automagic(available_automagics, vadwalk.VadWalk) - constructed = plugins.construct_plugin(ctx, automagics, vadwalk.VadWalk, config_path, progress_callback=None, open_method=None) - - print(f"[get_memory_hashes] Config after vadwalk construct:") - # breakpoint() - # print(ctx.config) - - print("[get_memory_hashes] Running vadwalk plugin...") - treegrid = constructed.run() - treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) - - print(f"[get_memory_hashes] SUCCESS: Found {len(mem_hash_data)} processes") - print("="*60 + "\n") - - return mem_hash_data - - except Exception as e: - print(f"\n[get_memory_hashes] EXCEPTION CAUGHT: {type(e).__name__}") - print(f"[get_memory_hashes] Error message: {str(e)}") - - - print("\n" + traceback.format_exc()) - print("="*60 + "\n") - - return {"error": str(e), "traceback": traceback.format_exc()} - def memory_hashes_visitor(node, accumulator, cmd, vadinfo_data): if node.values: - # print(vadinfo_data) start = int(node.values[-3]) end = int(node.values[-2]) result = { @@ -323,52 +240,33 @@ def memory_hashes_visitor(node, accumulator, cmd, vadinfo_data): "start": start, "end": end } - # breakpoint() dumped_file = f"{cmd.output_dir}/{vadinfo_data[(start, end)]}" result.update(hash_file(dumped_file, hashlib.sha256())) accumulator.append(result) return accumulator -def socket_visitor(node, accumulator): - if node.values: - proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] - state = "STATELESS" if state == "" else state - laddr = "::" if type(laddr) == volatility3.framework.renderers.UnreadableValue else laddr - pid = -1 if type(pid) == volatility3.framework.renderers.UnreadableValue else int(pid) - owner = "" if type(owner) == volatility3.framework.renderers.UnreadableValue else owner - raddr = "::" if type(raddr) == volatility3.framework.renderers.UnreadableValue else raddr - - sdata = { - "pid": pid, - "owner": owner, - "proto": proto, - "local_addr": laddr, - "local_port": int(lport), - "remote_addr": raddr, - "remote_port": rport, - "state": state, - } - # print(sdata) - # breakpoint() - accumulator.append(sdata) - return accumulator +def get_pslist(available_automagics, filter_data): + """List all the tasks that aren't hidden, unlinked, etc""" + + config_path = "plugins.PsList" + automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + constructed = plugins.construct_plugin( + ctx, automagics, pslist.PsList, config_path, + progress_callback=None, open_method=None + ) + + treegrid = constructed.run() + + pslist_data = [] + treegrid.visit(node=None, function=lambda node, acc: pslist_visitor(node, acc, filter_data), initial_accumulator=pslist_data) + return pslist_data + def pslist_visitor(node, accumulator, filter_data): if node.values: pid, ppid, img_name, offset = node.values[0:4] proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) - if is_interesting(proc, filter_data): - # breakpoint() - result = { - "pid": int(pid), - "base": int(proc.get_peb().ImageBaseAddress) - } - # result.update(hash_file(, hashlib.sha256())) - # accumulator[1].append(result) - # for dumps in os.listdir(cmd.output_dir): - - # breakpoint() pdata = { "pid": int(pid), "ppid": int(ppid), @@ -379,9 +277,31 @@ def pslist_visitor(node, accumulator, filter_data): return accumulator +def get_svcscan(available_automagics): + """List all of the system services""" + + config_path = "plugins.SvcScan" + automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) + constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) + existing_hive_layer = next((layer_name for layer_name in ctx.layers.keys() if "hive" in layer_name), None) + if existing_hive_layer: + return + + treegrid = constructed.run() + + svcscan_data = [] + treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) + + driverscan_data = get_driverscan(available_automagics) + for svc in svcscan_data: + for drv in driverscan_data: + if svc["ServiceName"] == drv["servicekey"]: + svc["DriverName"] = drv["name"] + return svcscan_data + + def svcscan_visitor(node, accumulator): if node.values: - # print(node) offset = node.values[0] pid = node.values[2] state = node.values[4] @@ -397,9 +317,19 @@ def svcscan_visitor(node, accumulator): "Pid": pid, } accumulator.append(svc_data) - # breakpoint() return accumulator + +def get_driverscan(available_automagics): + from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file + config_path = "plugins.DriverScan" + automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) + constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + driverscan_data = [] + treegrid.visit(node=None, function=driverscan_visitor, initial_accumulator=driverscan_data) + return driverscan_data + def driverscan_visitor(node, accumulator): if node.values: offset, start = node.values[:2] @@ -410,167 +340,42 @@ def driverscan_visitor(node, accumulator): "name": str(name), } accumulator.append(drv_data) - # breakpoint() return accumulator -def get_pslist(available_automagics, filter_data): - """List all the tasks that aren't hidden, unlinked, etc""" - print("\n" + "="*60) - print("[get_pslist] Starting") - print("="*60) - - try: - config_path = "plugins.PsList" - - print("[get_pslist] Choosing automagic...") - # ctx.config[f"{config_path}.PsList.pid"] = [x[0] for x in filter_data['thread_whitelist']] - # cmd = CommandLine() - # cmd.output_dir = tempfile.mkdtemp() - # FileHandler = cmd.file_handler_class_factory() - - automagics = automagic.choose_automagic(available_automagics, pslist.PsList) - print(f"[get_pslist] Chosen {len(automagics)} automagics") - print("[get_pslist] Constructing plugin...") - constructed = plugins.construct_plugin( - ctx, automagics, pslist.PsList, config_path, - progress_callback=None, open_method=None - ) - - print(f"[get_pslist] Config after construct:") - # breakpoint() - # print(ctx.config) - - print("[get_pslist] Running plugin...") - treegrid = constructed.run() - - print("[get_pslist] Extracting data...") - pslist_data = [] - treegrid.visit(node=None, function=lambda node, acc: pslist_visitor(node, acc, filter_data), initial_accumulator=pslist_data) - # breakpoint() - print(f"[get_pslist] SUCCESS: Found {len(pslist_data)} processes") - print("="*60 + "\n") - LinearlyMappedLayer.read.cache_clear() - return pslist_data - - except Exception as e: - print(f"\n[get_pslist] EXCEPTION CAUGHT: {type(e).__name__}") - print(f"[get_pslist] Error message: {str(e)}") - - # Try to extract unsatisfied requirements - if hasattr(e, 'unsatisfied'): - print(f"[get_pslist] Unsatisfied requirements:") - for req in e.unsatisfied: - # breakpoint() - print(f" - {req}") - - # Also check what's in the context - print(f"[get_pslist] Context config keys:") - for key in sorted(ctx.config.keys()): - print(f" - {key}: {ctx.config[key]}") - - print(f"[get_pslist] Context layers:") - for layer_name in ctx.layers.keys(): - print(f" - {layer_name}") - - print(f"[get_pslist] Context symbol tables:") - for symbol in ctx.symbol_space.keys(): - print(f" - {symbol}") - - print("\n" + traceback.format_exc()) - print("="*60 + "\n") - - return {"error": str(e), "traceback": traceback.format_exc()} - - -def get_svcscan(available_automagics): - """List all of the system services""" - print("\n" + "="*60) - print("[get_svcscan] Starting") - print("="*60) - - try: - config_path = "plugins.SvcScan" - print(f"[Location] {ctx.config['automagic.LayerStacker.single_location']}") - - print("[get_svcscan] Choosing automagic...") - automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) - print(f"[get_svcscan] Chosen {len(automagics)} automagics") - print("[get_svcscan] Constructing plugin...") - constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) - print(f"[get_svcscan] Config after construct:") - # print(ctx.config) - - print("[get_svcscan] Running plugin...") - treegrid = constructed.run() - - print("[get_svcscan] Extracting data...") - svcscan_data = [] - treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) - print(f"[get_svcscan] SUCCESS: Found {len(svcscan_data)} services") - print("="*60 + "\n") - - driverscan_data = get_driverscan(available_automagics) - - for svc in svcscan_data: - for drv in driverscan_data: - if svc["ServiceName"] == drv["servicekey"]: - svc["DriverName"] = drv["name"] - LinearlyMappedLayer.read.cache_clear() - return svcscan_data - except Exception as e: - print(f"\n[get_svcscan] EXCEPTION CAUGHT: {type(e).__name__}") - print(f"[get_svcscan] Error message: {str(e)}") - - # Try to extract unsatisfied requirements - if hasattr(e, 'unsatisfied'): - print(f"[get_svcscan] Unsatisfied requirements:") - for req in e.unsatisfied: - # breakpoint() - print(f" - {req}") - - # Also check what's in the context - print(f"[get_svcscan] Context config keys:") - for key in sorted(ctx.config.keys()): - print(f" - {key}: {ctx.config[key]}") - - print(f"[get_svcscan] Context layers:") - for layer_name in ctx.layers.keys(): - print(f" - {layer_name}") - - print(f"[get_svcscan] Context symbol tables:") - for symbol in ctx.symbol_space.keys(): - print(f" - {symbol}") - - print("\n" + traceback.format_exc()) - print("="*60 + "\n") - LinearlyMappedLayer.read.cache_clear() - return {"error": str(e), "traceback": traceback.format_exc()} - - -def get_driverscan(available_automagics): - from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file - config_path = "plugins.DriverScan" - automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) - constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - driverscan_data = [] - treegrid.visit(node=None, function=driverscan_visitor, initial_accumulator=driverscan_data) - return driverscan_data - - def get_sockets(available_automagics): """List all of the sockets that have not been unlinked or hidden""" config_path = "plugins.NetScan" automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) - # breakpoint() constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) treegrid = constructed.run() socket_data = [] treegrid.visit(node=None, function=socket_visitor, initial_accumulator=socket_data) return socket_data +def socket_visitor(node, accumulator): + if node.values: + proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] + state = "STATELESS" if state == "" else state + laddr = "::" if type(laddr) == volatility3.framework.renderers.UnreadableValue else laddr + pid = -1 if type(pid) == volatility3.framework.renderers.UnreadableValue else int(pid) + owner = "" if type(owner) == volatility3.framework.renderers.UnreadableValue else owner + raddr = "::" if type(raddr) == volatility3.framework.renderers.UnreadableValue else raddr + + sdata = { + "pid": pid, + "owner": owner, + "proto": proto, + "local_addr": laddr, + "local_port": int(lport), + "remote_addr": raddr, + "remote_port": rport, + "state": state, + } + accumulator.append(sdata) + return accumulator + def run(filterfile): """Returns a list of the processes as a JSON string @@ -580,17 +385,13 @@ def run(filterfile): returned to the plugin. """ - # print("Volatility version: %r" % volatility3.framework.constants.VERSION) - # setup_panda_handler() - # test_file_behavior() memory_file = "mem.ram" location = f"file:{memory_file}" config_path = "automagic.LayerStacker.single_location" ctx.config[config_path] = location # Build automagics - print("Running automagic to build layers...") available_automagics = automagic.available(ctx) try: @@ -604,14 +405,8 @@ def run(filterfile): "process_hashes": get_process_hashes(available_automagics, filter_data), "memory_hashes": get_memory_hashes(available_automagics, filter_data), } - except Exception as e: - print(f"ERROR: {e}") - print(traceback.format_exc()) - print(dict(ctx.config)) - analysis_results = {"error": str(e), - "error_type": type(e).__name__, - "traceback": traceback.format_exc(e) - } + except Exception as err: + analysis_results = {"error": traceback.format_exc(err)} json_str = json.dumps(analysis_results, indent=1) return json_str From f3ee206a0a7ac6170e033eb7271e14d81d35a3ad Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 24 Apr 2026 16:42:19 -0400 Subject: [PATCH 20/24] Moved code into volglue.py and updated necessary files --- plugins/volatility/CMakeLists.txt | 20 +- plugins/volatility/volatility.cc | 4 +- plugins/volatility/volglue.py | 556 ++++++++++++++++++------------ 3 files changed, 356 insertions(+), 224 deletions(-) diff --git a/plugins/volatility/CMakeLists.txt b/plugins/volatility/CMakeLists.txt index 8c58464..37cfab2 100644 --- a/plugins/volatility/CMakeLists.txt +++ b/plugins/volatility/CMakeLists.txt @@ -20,14 +20,14 @@ set(LINK_LIBS_X86_64 ${LINK_LIBS} panda_ipanda-x86_64) set(TARGET_DEPS_I386 panda_ipanda-i386) set(TARGET_DEPS_X86_64 panda_ipanda-x86_64) -add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_I386}/volglue3.py - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py ${PANDA_PLUGIN_DIR_I386}/volglue3.py - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py) -add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue3.py) -add_custom_target(volglue3-script ALL - DEPENDS ${PANDA_PLUGIN_DIR_I386}/volglue3.py ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py) +add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_I386}/volglue.py + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py ${PANDA_PLUGIN_DIR_I386}/volglue.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py) +add_custom_command(OUTPUT ${PANDA_PLUGIN_DIR_X86_64}/volglue.py + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py ${PANDA_PLUGIN_DIR_X86_64}/volglue.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/volglue.py) +add_custom_target(volglue-script ALL + DEPENDS ${PANDA_PLUGIN_DIR_I386}/volglue.py ${PANDA_PLUGIN_DIR_X86_64}/volglue.py) add_i386_plugin(${PLUGIN_TARGET} SRC_FILES LINK_LIBS_I386) @@ -35,5 +35,5 @@ add_x86_64_plugin(${PLUGIN_TARGET} SRC_FILES LINK_LIBS_X86_64) add_dependencies(${PLUGIN_TARGET}-i386 ${TARGET_DEPS_I386}) add_dependencies(${PLUGIN_TARGET}-x86_64 ${TARGET_DEPS_X86_64}) -install(FILES ${PANDA_PLUGIN_DIR_I386}/volglue3.py DESTINATION lib/panda/i386) -install(FILES ${PANDA_PLUGIN_DIR_X86_64}/volglue3.py DESTINATION lib/panda/x86_64) +install(FILES ${PANDA_PLUGIN_DIR_I386}/volglue.py DESTINATION lib/panda/i386) +install(FILES ${PANDA_PLUGIN_DIR_X86_64}/volglue.py DESTINATION lib/panda/x86_64) diff --git a/plugins/volatility/volatility.cc b/plugins/volatility/volatility.cc index a8cc0f8..7644e0b 100644 --- a/plugins/volatility/volatility.cc +++ b/plugins/volatility/volatility.cc @@ -35,7 +35,7 @@ char g_profile[512] = {0}; // Constants #define TARGET_PAGE_SIZE 1024 char g_filter_path[512] = {0}; -const char g_script_name[] = "/volglue3.py"; +const char g_script_name[] = "/volglue.py"; // Globals std::shared_ptr os_manager; @@ -417,7 +417,7 @@ bool init_plugin(void* self) Py_InitializeFromConfig(&config); // Load the program as a code object - pcode = Py_CompileString(script_contents, "volglue3.py", Py_file_input); + pcode = Py_CompileString(script_contents, "volglue.py", Py_file_input); CHECK_OR_DIE(pcode, "Failed to compile python program!\n", cleanup); // Load the code object into a module diff --git a/plugins/volatility/volglue.py b/plugins/volatility/volglue.py index 2b90b27..be3b26f 100644 --- a/plugins/volatility/volglue.py +++ b/plugins/volatility/volglue.py @@ -1,55 +1,157 @@ # This section sets up the volatility environment # and is invoked by the plugin when it loads this # script as a module -# -# It should be refactored so that the setup -# is done as a function call to make it cleaner -# and take the profile as an argument + import os import json +import logging import shutil +import subprocess import hashlib import tempfile import traceback -import volatility -import volatility.conf as conf -import volatility.registry as registry -import volatility.commands as commands -import volatility.addrspace as addrspace -import volatility.plugins.taskmods as taskmods -import volatility.plugins.malware.svcscan as svcscan -import volatility.plugins.netscan as netscan -import volatility.plugins.procdump as procdump -import volatility.plugins.vadinfo as vadinfo - -# Volatility initialization code -registry.PluginImporter() -config = conf.ConfObject() -registry.register_global_options(config, commands.Command) -registry.register_global_options(config, addrspace.BaseAddressSpace) - +import urllib +from urllib.request import BaseHandler -def maybe_add_image(obj, proc): - if proc.Peb: - obj["ImagePathName"] = str(proc.Peb.ProcessParameters.ImagePathName).encode( - "utf-8", "replace" - ) - return obj +import volatility3 +from volatility3.cli import CommandLine, text_renderer +from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo, vadwalk +from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer +from volatility3.framework import automagic, contexts, interfaces, plugins +from volatility3.framework.layers.physical import FileLayer +from volatility3.framework.layers.linear import LinearlyMappedLayer +import socket +from typing import Optional +from pathlib import Path -def filter_invalid_ascii(strobj): - return strobj.encode("utf-8", "replace") +# import pandamem +vollog = logging.getLogger(__name__) +DEBUG = False -def is_interesting(eprocess, filter_data): - check_pid = int(eprocess.UniqueProcessId) - check_asid = int(eprocess.Pcb.DirectoryTableBase) +ctx = contexts.Context() - for (pid, tid, asid) in filter_data["threads"]: - if check_pid == int(pid) and check_asid == int(asid): - return True - return False +class PandaFile(object): + """ + Constructs a file that volatility can't ignore + to back by PANDA physical memory + """ + + def __init__(self, length): + self.pos = 0 + self.length = length + self.closed = False + self.mode = "rb" + self.name = "/tmp/panda.panda" + self.classname = type(self).__name__ + self.x86_32 = (length <= 0xffffffff) + + def readable(self): + return self.closed + + def read(self, size=1): + if self.x86_32: + addr = self.pos & 0xfffffff + else: + addr = self.pos + + data = pandamem.read_physical(addr, size) + # print(len(data)) + # breakpoint() + # mem_buf.extend(data) + + if DEBUG: + print(self.classname+": Reading " + str(size)+" bytes from "+hex(self.pos)) + + # file_path = Path(f'/data/small_{hex(self.pos)}.dd') + # if not file_path.exists(): + # with open(file_path, 'wb') as f: + # f.write(data) + + self.pos += size + return data + + def peek(self, size=1): + return pandamem.read_physical(self.pos, size) + + def seek(self, pos, whence=0): + if whence == 0: + self.pos = pos + elif whence == 1: + self.pos += pos + else: + self.pos = self.length - pos + if self.pos > self.length: + print(self.classname+": We've gone off the deep end") + if DEBUG: + print(self.classname+" Seeking to address "+hex(self.pos)) + + def tell(self): + return self.pos + + def close(self): + self.closed = True + +class PandaFileHandler(BaseHandler): + def default_open(self, req): + if 'panda.panda' in req.full_url: + length = pandamem.get_ram_size() + + if length > 0xc0000000: + length += 0x40000000 # 3GB hole + if DEBUG: + print(type(self).__name__ + ": initializing PandaFile with length="+hex(length)) + # breakpoint() + + return PandaFile(length=length) + + return None + + def file_close(self): + return True + +def setup_panda_handler(): + """ + Register the panda:// URL handler with urllib. + This allows Volatility to open "panda://memory" as if it were a file. + """ + opener = urllib.request.build_opener(PandaFileHandler()) + urllib.request.install_opener(opener) + vollog.info("[PandaFileHandler] Registered panda:// protocol handler") + +def test_file_behavior(): + print(f"\n{'='*60}") + print(f"Testing pandamem module...") + print(f"{'='*60}") + ram_size = pandamem.get_ram_size() + print(f"Ram size: {ram_size}") + # TEST 1: Can we read from address 0? + print("TEST 1: Reading 16 bytes from address 0x0...") + try: + data = pandamem.read_physical(0, 16) + print(f" SUCCESS: {data.hex()}") + except Exception as e: + print(f" FAILED: {e}") + return json.dumps({"error": f"pandamem.read_physical failed: {e}"}) + + # TEST 2: Can we read from address 0x1000 (typical page)? + # print("TEST 2: Reading 4096 bytes from address 0x1000...") + # try: + # data = pandamem.read_physical(0x1000, 4096) + # print(f" SUCCESS: {data.hex()}") + # except Exception as e: + # print(f" FAILED: {e}") + + # TEST 3: Can we read a larger chunk? + print("TEST 3: Reading 4096 bytes (one page)...") + try: + data = pandamem.read_physical(0, 4096) + print(f" SUCCESS: Read {len(data)} bytes") + except Exception as e: + print(f" FAILED: {e}") + print(f"{'='*60}\n") def hash_file(filepath, *algorithms): @@ -65,187 +167,217 @@ def hash_file(filepath, *algorithms): return {a.name: a.hexdigest().lower() for a in algorithms} -def get_process_hashes(config, filter_data): - config.DUMP_DIR = tempfile.mkdtemp() - - runner = procdump.ProcDump(config) - - results = [] - for proc in runner.calculate(): - addr = proc.get_process_address_space() - - vtop_check = False - invalid = any( - [ - addr is None, - proc.Peb is None, - ] - ) - if proc.Peb: - vtop_check = addr.vtop(proc.Peb.ImageBaseAddress) is None - - if invalid: - if vtop_check: - continue - - if is_interesting(proc, filter_data): - name = str(proc.ImageFileName) - - runner.dump_pe(addr, proc.Peb.ImageBaseAddress, name) - - dumped_file = os.path.join(config.DUMP_DIR, name) - - result = { - "pid": int(proc.UniqueProcessId), - "base": int(proc.Peb.ImageBaseAddress), - } - result.update(hash_file(dumped_file, hashlib.sha256())) - results.append(result) - - shutil.rmtree(config.DUMP_DIR) - - return results - - -def get_memory_hashes(config, filter_data): - config.DUMP_DIR = tempfile.mkdtemp() - - runner = vadinfo.VADDump(config) +def get_process_hashes(available_automagics, filter_data): + config_path = "plugins.PEDump" + ctx.config["plugins.PEDump.PEDump.base"] = 0x400000 + ctx.config["plugins.PEDump.PEDump.pid"] = [x[0] for x in filter_data['thread_whitelist']] + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) + constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=FileHandler) - results = [] - for proc in runner.calculate(): - addr = proc.get_process_address_space() + treegrid = constructed.run() + proc_hash_data = [] + treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) - if not addr: - continue + return proc_hash_data - if is_interesting(proc, filter_data): - for vad, _ in proc.get_vads( - vad_filter=lambda v: v.Length < pow(2, 30), skip_max_commit=True - ): - path = os.path.join( - config.DUMP_DIR, - "{}_{}.{}".format(vad.Start, vad.End, proc.UniqueProcessId), - ) - - runner.dump_vad(path, vad, addr) - - result = { - "pid": int(proc.UniqueProcessId), - "start": int(vad.Start), - "end": int(vad.End), - } - result.update(hash_file(path, hashlib.sha256())) - results.append(result) - - shutil.rmtree(config.DUMP_DIR) - - return results +def process_hashes_visitor(node, accumulator, cmd): + if node.values: + result = { + "pid": int(node.values[0]), + "base": ctx.config["plugins.PEDump.PEDump.base"], + } + dumped_file = f"{cmd.output_dir}/{node.values[2]}" + result.update(hash_file(dumped_file, hashlib.sha256())) + accumulator.append(result) + return accumulator + + +def get_memory_hashes(available_automagics, filter_data): + config_path = "plugins.VadInfo" + ctx.config["plugins.VadInfo.VadInfo.pid"] = [x[0] for x in filter_data['thread_whitelist']] + ctx.config["plugins.VadInfo.VadInfo.dump"] = True + + cmd = CommandLine() + cmd.output_dir = tempfile.mkdtemp() + FileHandler = cmd.file_handler_class_factory() + automagics = automagic.choose_automagic(available_automagics, vadinfo.VadInfo) + constructed = plugins.construct_plugin(ctx, automagics, vadinfo.VadInfo, config_path, progress_callback=None, open_method=FileHandler) + + vadinfo_data = {} + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: vadinfo_visitor(node, acc), initial_accumulator=vadinfo_data) + + config_path = "plugins.VadWalk" + ctx.config["plugins.VadWalk.VadWalk.pid"] = [x[0] for x in filter_data['thread_whitelist']] + mem_hash_data = [] + automagics = automagic.choose_automagic(available_automagics, vadwalk.VadWalk) + constructed = plugins.construct_plugin(ctx, automagics, vadwalk.VadWalk, config_path, progress_callback=None, open_method=None) + + treegrid = constructed.run() + treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) + + return mem_hash_data + + +def vadinfo_visitor(node, accumulator): + if node.values: + startvpn, endvpn = node.values[3:5] + fileoutput = node.values[-1] + accumulator[(int(startvpn), int(endvpn))] = fileoutput + return accumulator + + +def memory_hashes_visitor(node, accumulator, cmd, vadinfo_data): + if node.values: + start = int(node.values[-3]) + end = int(node.values[-2]) + result = { + "pid": int(node.values[0]), + "start": start, + "end": end + } + dumped_file = f"{cmd.output_dir}/{vadinfo_data[(start, end)]}" + result.update(hash_file(dumped_file, hashlib.sha256())) + accumulator.append(result) + return accumulator -def get_pslist(config): +def get_pslist(available_automagics, filter_data): """List all the tasks that aren't hidden, unlinked, etc""" - p = taskmods.PSList(config) - pdata = [] - for proc in p.calculate(): - obj = { - "pid": int(proc.UniqueProcessId), - "ppid": int(proc.InheritedFromUniqueProcessId), + + config_path = "plugins.PsList" + automagics = automagic.choose_automagic(available_automagics, pslist.PsList) + constructed = plugins.construct_plugin( + ctx, automagics, pslist.PsList, config_path, + progress_callback=None, open_method=None + ) + + treegrid = constructed.run() + + pslist_data = [] + treegrid.visit(node=None, function=lambda node, acc: pslist_visitor(node, acc, filter_data), initial_accumulator=pslist_data) + return pslist_data + + +def pslist_visitor(node, accumulator, filter_data): + if node.values: + pid, ppid, img_name, offset = node.values[0:4] + proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) + pdata = { + "pid": int(pid), + "ppid": int(ppid), "asid": int(proc.Pcb.DirectoryTableBase), + "ImagePathName": img_name, } - obj = maybe_add_image(obj, proc) - pdata.append(obj) - return pdata + accumulator.append(pdata) + return accumulator -def get_svcscan(config): +def get_svcscan(available_automagics): """List all of the system services""" - scanner = svcscan.SvcScan(config) - aggregate_data = [] - for service in scanner.calculate(): - sdata = { - "ServiceName": str(service.ServiceName.dereference()), - "DisplayName": str(service.DisplayName.dereference()), - "DriverName": str(service.DriverName.dereference()), - "State": str(service.State), - "Pid": int(service.ServiceProcess.dereference().ProcessId), - } - aggregate_data.append(sdata) - return aggregate_data - - -SOCKETS_PROFILES = [ - "VistaSP0x64", - "VistaSP0x86", - "VistaSP1x64", - "VistaSP1x86", - "VistaSP2x64", - "VistaSP2x86", - "Win7SP0x64", - "Win7SP0x86", - "Win7SP1x64", - "Win7SP1x86", - "Win2008R2SP0x64", - "Win2008R2SP1x64", - "Win2008SP1x64", - "Win2008SP1x86", - "Win2008SP2x64", - "Win2008SP2x86", - "Win8SP0x64", - "Win8SP0x86", - "Win8SP1x64", - "Win8SP1x86", - "Win81U1x64", - "Win81U1x86", - "Win2012R2x64", - "Win2012x64", - "Win10x64", - "Win10x86", -] - - -def get_sockets(config): - """List all of the sockets that have not been unlinked or hidden""" + config_path = "plugins.SvcScan" + automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) + constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) + existing_hive_layer = next((layer_name for layer_name in ctx.layers.keys() if "hive" in layer_name), None) + if existing_hive_layer: + return + + treegrid = constructed.run() + + svcscan_data = [] + treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) + + driverscan_data = get_driverscan(available_automagics) + for svc in svcscan_data: + for drv in driverscan_data: + if svc["ServiceName"] == drv["servicekey"]: + svc["DriverName"] = drv["name"] + return svcscan_data + + +def svcscan_visitor(node, accumulator): + if node.values: + offset = node.values[0] + pid = node.values[2] + state = node.values[4] + name, display_name = node.values[6:8] + + pid = -1 if type(pid) == volatility3.framework.renderers.NotApplicableValue else int(pid) + + svc_data = { + "ServiceName": name, + "DisplayName": display_name, + "DriverName": '', + "State": state, + "Pid": pid, + } + accumulator.append(svc_data) + return accumulator + + +def get_driverscan(available_automagics): + from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file + config_path = "plugins.DriverScan" + automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) + constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() + driverscan_data = [] + treegrid.visit(node=None, function=driverscan_visitor, initial_accumulator=driverscan_data) + return driverscan_data + +def driverscan_visitor(node, accumulator): + if node.values: + offset, start = node.values[:2] + servicekey = node.values[3] + name = node.values[-1] + drv_data = { + "servicekey": servicekey, + "name": str(name), + } + accumulator.append(drv_data) + return accumulator - # This only works for Vista and later - if config.PROFILE not in SOCKETS_PROFILES: - return [] +def get_sockets(available_automagics): + """List all of the sockets that have not been unlinked or hidden""" - scanner = netscan.Netscan(config) + config_path = "plugins.NetScan" + automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) + constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) + treegrid = constructed.run() socket_data = [] + treegrid.visit(node=None, function=socket_visitor, initial_accumulator=socket_data) + return socket_data - for socket in scanner.calculate(): - netobj, proto, laddr, lport, raddr, rport, state = socket - - if netobj.Owner != None: - pid = str(netobj.Owner.UniqueProcessId) - owner = filter_invalid_ascii(netobj.Owner.ImageFileName) - else: - pid = "-1" - owner = "Unknown" +def socket_visitor(node, accumulator): + if node.values: + proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] + state = "STATELESS" if state == "" else state + laddr = "::" if type(laddr) == volatility3.framework.renderers.UnreadableValue else laddr + pid = -1 if type(pid) == volatility3.framework.renderers.UnreadableValue else int(pid) + owner = "" if type(owner) == volatility3.framework.renderers.UnreadableValue else owner + raddr = "::" if type(raddr) == volatility3.framework.renderers.UnreadableValue else raddr sdata = { - "pid": str(pid), - "owner": str(owner), - "proto": str(proto), - "local_addr": str(laddr), - "local_port": str(lport), - "remote_addr": str(raddr), - "remote_port": str(rport), - "state": str(state), + "pid": pid, + "owner": owner, + "proto": proto, + "local_addr": laddr, + "local_port": int(lport), + "remote_addr": raddr, + "remote_port": rport, + "state": state, } - socket_data.append(sdata) - - return socket_data - + accumulator.append(sdata) + return accumulator -# TODO add a pool tag scanner to look for unlinked processes? - -def run(profile, location, filterfile): +def run(filterfile): """Returns a list of the processes as a JSON string This analysis demonstrates that volatility can be successfully @@ -253,20 +385,25 @@ def run(profile, location, filterfile): returned to the plugin. """ - print("Volatility version: %r" % volatility.constants.VERSION) - try: - config.PROFILE = profile - config.LOCATION = location + memory_file = "mem.ram" + location = f"file:{memory_file}" + config_path = "automagic.LayerStacker.single_location" + ctx.config[config_path] = location + + # Build automagics + available_automagics = automagic.available(ctx) + + try: with open(filterfile, "rb") as fobj: filter_data = json.load(fobj) analysis_results = { - "pslist": get_pslist(config), - "svcscan": get_svcscan(config), - "sockets": get_sockets(config), - "process_hashes": get_process_hashes(config, filter_data), - "memory_hashes": get_memory_hashes(config, filter_data), + "pslist": get_pslist(available_automagics, filter_data), + "svcscan": get_svcscan(available_automagics), + "sockets": get_sockets(available_automagics), + "process_hashes": get_process_hashes(available_automagics, filter_data), + "memory_hashes": get_memory_hashes(available_automagics, filter_data), } except Exception as err: analysis_results = {"error": traceback.format_exc(err)} @@ -274,19 +411,14 @@ def run(profile, location, filterfile): json_str = json.dumps(analysis_results, indent=1) return json_str - -# This stub is here to make it easier to test. Use pmemsave -# in a QEMU monitor to write a sample image to /tmp/memshot -# and then run this script as `python volglue.py` to -# see if it works. if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Test analysis") - parser.add_argument("--location", default="file:///tmp/memshot") - parser.add_argument("--profile", default="Win7SP1x64") + parser.add_argument("--location", default="file:mymem.dd") + parser.add_argument("--filter", default="aprog-x64-tracefilter.json") args = parser.parse_args() - print(run(args.profile, args.location)) + print(run(args.filter)) -### Must end with this comment +### Must end with this comment \ No newline at end of file From 0ab22a8950015b94dc04dd975d32b7e9bdc01adb Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Fri, 24 Apr 2026 16:43:48 -0400 Subject: [PATCH 21/24] Removed volglue3.py --- plugins/volatility/volglue3.py | 424 --------------------------------- 1 file changed, 424 deletions(-) delete mode 100644 plugins/volatility/volglue3.py diff --git a/plugins/volatility/volglue3.py b/plugins/volatility/volglue3.py deleted file mode 100644 index be3b26f..0000000 --- a/plugins/volatility/volglue3.py +++ /dev/null @@ -1,424 +0,0 @@ -# This section sets up the volatility environment -# and is invoked by the plugin when it loads this -# script as a module - -import os -import json -import logging -import shutil -import subprocess -import hashlib -import tempfile -import traceback - -import urllib -from urllib.request import BaseHandler - -import volatility3 -from volatility3.cli import CommandLine, text_renderer -from volatility3.plugins.windows import pedump, psscan, pslist, svcscan, netscan, vadinfo, vadwalk -from volatility3.framework.interfaces.context import ModuleInterface, ModuleContainer -from volatility3.framework import automagic, contexts, interfaces, plugins -from volatility3.framework.layers.physical import FileLayer -from volatility3.framework.layers.linear import LinearlyMappedLayer - -import socket -from typing import Optional -from pathlib import Path - -# import pandamem - -vollog = logging.getLogger(__name__) -DEBUG = False - -ctx = contexts.Context() - -class PandaFile(object): - """ - Constructs a file that volatility can't ignore - to back by PANDA physical memory - """ - - def __init__(self, length): - self.pos = 0 - self.length = length - self.closed = False - self.mode = "rb" - self.name = "/tmp/panda.panda" - self.classname = type(self).__name__ - self.x86_32 = (length <= 0xffffffff) - - def readable(self): - return self.closed - - def read(self, size=1): - if self.x86_32: - addr = self.pos & 0xfffffff - else: - addr = self.pos - - data = pandamem.read_physical(addr, size) - # print(len(data)) - # breakpoint() - # mem_buf.extend(data) - - if DEBUG: - print(self.classname+": Reading " + str(size)+" bytes from "+hex(self.pos)) - - # file_path = Path(f'/data/small_{hex(self.pos)}.dd') - # if not file_path.exists(): - # with open(file_path, 'wb') as f: - # f.write(data) - - self.pos += size - return data - - def peek(self, size=1): - return pandamem.read_physical(self.pos, size) - - def seek(self, pos, whence=0): - if whence == 0: - self.pos = pos - elif whence == 1: - self.pos += pos - else: - self.pos = self.length - pos - if self.pos > self.length: - print(self.classname+": We've gone off the deep end") - if DEBUG: - print(self.classname+" Seeking to address "+hex(self.pos)) - - def tell(self): - return self.pos - - def close(self): - self.closed = True - -class PandaFileHandler(BaseHandler): - def default_open(self, req): - if 'panda.panda' in req.full_url: - length = pandamem.get_ram_size() - - if length > 0xc0000000: - length += 0x40000000 # 3GB hole - if DEBUG: - print(type(self).__name__ + ": initializing PandaFile with length="+hex(length)) - # breakpoint() - - return PandaFile(length=length) - - return None - - def file_close(self): - return True - -def setup_panda_handler(): - """ - Register the panda:// URL handler with urllib. - This allows Volatility to open "panda://memory" as if it were a file. - """ - opener = urllib.request.build_opener(PandaFileHandler()) - urllib.request.install_opener(opener) - vollog.info("[PandaFileHandler] Registered panda:// protocol handler") - -def test_file_behavior(): - print(f"\n{'='*60}") - print(f"Testing pandamem module...") - print(f"{'='*60}") - ram_size = pandamem.get_ram_size() - print(f"Ram size: {ram_size}") - # TEST 1: Can we read from address 0? - print("TEST 1: Reading 16 bytes from address 0x0...") - try: - data = pandamem.read_physical(0, 16) - print(f" SUCCESS: {data.hex()}") - except Exception as e: - print(f" FAILED: {e}") - return json.dumps({"error": f"pandamem.read_physical failed: {e}"}) - - # TEST 2: Can we read from address 0x1000 (typical page)? - # print("TEST 2: Reading 4096 bytes from address 0x1000...") - # try: - # data = pandamem.read_physical(0x1000, 4096) - # print(f" SUCCESS: {data.hex()}") - # except Exception as e: - # print(f" FAILED: {e}") - - # TEST 3: Can we read a larger chunk? - print("TEST 3: Reading 4096 bytes (one page)...") - try: - data = pandamem.read_physical(0, 4096) - print(f" SUCCESS: Read {len(data)} bytes") - except Exception as e: - print(f" FAILED: {e}") - print(f"{'='*60}\n") - - -def hash_file(filepath, *algorithms): - with open(filepath, "rb") as fobj: - while True: - chunk = fobj.read(4096) - if not chunk: - break - - for a in algorithms: - a.update(chunk) - - return {a.name: a.hexdigest().lower() for a in algorithms} - - -def get_process_hashes(available_automagics, filter_data): - config_path = "plugins.PEDump" - ctx.config["plugins.PEDump.PEDump.base"] = 0x400000 - ctx.config["plugins.PEDump.PEDump.pid"] = [x[0] for x in filter_data['thread_whitelist']] - cmd = CommandLine() - cmd.output_dir = tempfile.mkdtemp() - FileHandler = cmd.file_handler_class_factory() - automagics = automagic.choose_automagic(available_automagics, pedump.PEDump) - constructed = plugins.construct_plugin(ctx, automagics, pedump.PEDump, config_path, progress_callback=None, open_method=FileHandler) - - treegrid = constructed.run() - proc_hash_data = [] - treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) - - return proc_hash_data - - -def process_hashes_visitor(node, accumulator, cmd): - if node.values: - result = { - "pid": int(node.values[0]), - "base": ctx.config["plugins.PEDump.PEDump.base"], - } - dumped_file = f"{cmd.output_dir}/{node.values[2]}" - result.update(hash_file(dumped_file, hashlib.sha256())) - accumulator.append(result) - return accumulator - - -def get_memory_hashes(available_automagics, filter_data): - config_path = "plugins.VadInfo" - ctx.config["plugins.VadInfo.VadInfo.pid"] = [x[0] for x in filter_data['thread_whitelist']] - ctx.config["plugins.VadInfo.VadInfo.dump"] = True - - cmd = CommandLine() - cmd.output_dir = tempfile.mkdtemp() - FileHandler = cmd.file_handler_class_factory() - automagics = automagic.choose_automagic(available_automagics, vadinfo.VadInfo) - constructed = plugins.construct_plugin(ctx, automagics, vadinfo.VadInfo, config_path, progress_callback=None, open_method=FileHandler) - - vadinfo_data = {} - treegrid = constructed.run() - treegrid.visit(node=None, function=lambda node, acc: vadinfo_visitor(node, acc), initial_accumulator=vadinfo_data) - - config_path = "plugins.VadWalk" - ctx.config["plugins.VadWalk.VadWalk.pid"] = [x[0] for x in filter_data['thread_whitelist']] - mem_hash_data = [] - automagics = automagic.choose_automagic(available_automagics, vadwalk.VadWalk) - constructed = plugins.construct_plugin(ctx, automagics, vadwalk.VadWalk, config_path, progress_callback=None, open_method=None) - - treegrid = constructed.run() - treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) - - return mem_hash_data - - -def vadinfo_visitor(node, accumulator): - if node.values: - startvpn, endvpn = node.values[3:5] - fileoutput = node.values[-1] - accumulator[(int(startvpn), int(endvpn))] = fileoutput - return accumulator - - -def memory_hashes_visitor(node, accumulator, cmd, vadinfo_data): - if node.values: - start = int(node.values[-3]) - end = int(node.values[-2]) - result = { - "pid": int(node.values[0]), - "start": start, - "end": end - } - dumped_file = f"{cmd.output_dir}/{vadinfo_data[(start, end)]}" - result.update(hash_file(dumped_file, hashlib.sha256())) - accumulator.append(result) - return accumulator - - -def get_pslist(available_automagics, filter_data): - """List all the tasks that aren't hidden, unlinked, etc""" - - config_path = "plugins.PsList" - automagics = automagic.choose_automagic(available_automagics, pslist.PsList) - constructed = plugins.construct_plugin( - ctx, automagics, pslist.PsList, config_path, - progress_callback=None, open_method=None - ) - - treegrid = constructed.run() - - pslist_data = [] - treegrid.visit(node=None, function=lambda node, acc: pslist_visitor(node, acc, filter_data), initial_accumulator=pslist_data) - return pslist_data - - -def pslist_visitor(node, accumulator, filter_data): - if node.values: - pid, ppid, img_name, offset = node.values[0:4] - proc = ctx.object("symbol_table_name1!_EPROCESS", "layer_name", offset) - pdata = { - "pid": int(pid), - "ppid": int(ppid), - "asid": int(proc.Pcb.DirectoryTableBase), - "ImagePathName": img_name, - } - accumulator.append(pdata) - return accumulator - - -def get_svcscan(available_automagics): - """List all of the system services""" - - config_path = "plugins.SvcScan" - automagics = automagic.choose_automagic(available_automagics, svcscan.SvcScan) - constructed = plugins.construct_plugin(ctx, automagics, svcscan.SvcScan, config_path, progress_callback=None, open_method=None) - existing_hive_layer = next((layer_name for layer_name in ctx.layers.keys() if "hive" in layer_name), None) - if existing_hive_layer: - return - - treegrid = constructed.run() - - svcscan_data = [] - treegrid.visit(node=None, function=svcscan_visitor, initial_accumulator=svcscan_data) - - driverscan_data = get_driverscan(available_automagics) - for svc in svcscan_data: - for drv in driverscan_data: - if svc["ServiceName"] == drv["servicekey"]: - svc["DriverName"] = drv["name"] - return svcscan_data - - -def svcscan_visitor(node, accumulator): - if node.values: - offset = node.values[0] - pid = node.values[2] - state = node.values[4] - name, display_name = node.values[6:8] - - pid = -1 if type(pid) == volatility3.framework.renderers.NotApplicableValue else int(pid) - - svc_data = { - "ServiceName": name, - "DisplayName": display_name, - "DriverName": '', - "State": state, - "Pid": pid, - } - accumulator.append(svc_data) - return accumulator - - -def get_driverscan(available_automagics): - from volatility3.plugins.windows import driverscan # Imported here because circular import error when at top of file - config_path = "plugins.DriverScan" - automagics = automagic.choose_automagic(available_automagics, driverscan.DriverScan) - constructed = plugins.construct_plugin(ctx, automagics, driverscan.DriverScan, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - driverscan_data = [] - treegrid.visit(node=None, function=driverscan_visitor, initial_accumulator=driverscan_data) - return driverscan_data - -def driverscan_visitor(node, accumulator): - if node.values: - offset, start = node.values[:2] - servicekey = node.values[3] - name = node.values[-1] - drv_data = { - "servicekey": servicekey, - "name": str(name), - } - accumulator.append(drv_data) - return accumulator - - -def get_sockets(available_automagics): - """List all of the sockets that have not been unlinked or hidden""" - - config_path = "plugins.NetScan" - automagics = automagic.choose_automagic(available_automagics, netscan.NetScan) - constructed = plugins.construct_plugin(ctx, automagics, netscan.NetScan, config_path, progress_callback=None, open_method=None) - treegrid = constructed.run() - socket_data = [] - treegrid.visit(node=None, function=socket_visitor, initial_accumulator=socket_data) - return socket_data - -def socket_visitor(node, accumulator): - if node.values: - proto, laddr, lport, raddr, rport, state, pid, owner = node.values[1:9] - state = "STATELESS" if state == "" else state - laddr = "::" if type(laddr) == volatility3.framework.renderers.UnreadableValue else laddr - pid = -1 if type(pid) == volatility3.framework.renderers.UnreadableValue else int(pid) - owner = "" if type(owner) == volatility3.framework.renderers.UnreadableValue else owner - raddr = "::" if type(raddr) == volatility3.framework.renderers.UnreadableValue else raddr - - sdata = { - "pid": pid, - "owner": owner, - "proto": proto, - "local_addr": laddr, - "local_port": int(lport), - "remote_addr": raddr, - "remote_port": rport, - "state": state, - } - accumulator.append(sdata) - return accumulator - - -def run(filterfile): - """Returns a list of the processes as a JSON string - - This analysis demonstrates that volatility can be successfully - invoked and that data can be serialized as JSON data and - returned to the plugin. - - """ - - memory_file = "mem.ram" - location = f"file:{memory_file}" - config_path = "automagic.LayerStacker.single_location" - ctx.config[config_path] = location - - # Build automagics - available_automagics = automagic.available(ctx) - - try: - with open(filterfile, "rb") as fobj: - filter_data = json.load(fobj) - - analysis_results = { - "pslist": get_pslist(available_automagics, filter_data), - "svcscan": get_svcscan(available_automagics), - "sockets": get_sockets(available_automagics), - "process_hashes": get_process_hashes(available_automagics, filter_data), - "memory_hashes": get_memory_hashes(available_automagics, filter_data), - } - except Exception as err: - analysis_results = {"error": traceback.format_exc(err)} - - json_str = json.dumps(analysis_results, indent=1) - return json_str - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Test analysis") - parser.add_argument("--location", default="file:mymem.dd") - parser.add_argument("--filter", default="aprog-x64-tracefilter.json") - args = parser.parse_args() - print(run(args.filter)) - - -### Must end with this comment \ No newline at end of file From cfde17c9f21d9c28141c13dc4a3c45a760109b9c Mon Sep 17 00:00:00 2001 From: Suhaas Narayan Date: Thu, 30 Apr 2026 15:27:37 -0400 Subject: [PATCH 22/24] Add code to remove tempfiles after being dumped --- plugins/volatility/volglue.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/volatility/volglue.py b/plugins/volatility/volglue.py index be3b26f..ba4386a 100644 --- a/plugins/volatility/volglue.py +++ b/plugins/volatility/volglue.py @@ -180,6 +180,7 @@ def get_process_hashes(available_automagics, filter_data): treegrid = constructed.run() proc_hash_data = [] treegrid.visit(node=None, function=lambda node, acc: process_hashes_visitor(node, acc, cmd), initial_accumulator=proc_hash_data) + shutil.rmtree(cmd.output_dir) return proc_hash_data @@ -219,6 +220,7 @@ def get_memory_hashes(available_automagics, filter_data): treegrid = constructed.run() treegrid.visit(node=None, function=lambda node, acc: memory_hashes_visitor(node, acc, cmd, vadinfo_data), initial_accumulator=mem_hash_data) + shutil.rmtree(cmd.output_dir) return mem_hash_data From 1add716ac845d937e59f2d0ff3dcb9df5856f714 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan <40607921+Swashbuckler1@users.noreply.github.com> Date: Fri, 1 May 2026 11:51:58 -0400 Subject: [PATCH 23/24] Edits for volatility3 refactor --- plugins/volatility/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/volatility/README.md b/plugins/volatility/README.md index 9f0a982..f17e9bf 100644 --- a/plugins/volatility/README.md +++ b/plugins/volatility/README.md @@ -6,10 +6,10 @@ either co-located with the plugin library. Results are stored as an `avro` recor This plugin takes in a PANDA recording and outputs an avro record file,`volatility.panda` containing a list of processes information (process hashes, socket information, etc) -* A sample filter inupt file is require, it can be crafted as follows: +* A sample filter input file is required, it can be crafted as follows: ``` { - "threads": [ + "thread_whitelist": [ [ pid, tid1, @@ -30,7 +30,7 @@ This plugin takes in a PANDA recording and outputs an avro record file,`volatili ## Usage ### Running manually -`volatility` plugin takes two arguments, `-os`, which asks for the type of operating system that the recording is used, and `--panda-arg filter=FILE.txt` to pass in the filter file to plugin. An example invocation: (NOTE: you need to have `RECORDING-rr-nondet.log`, `RECORDING-rr-snp`, and `filter.txt` in the path) +`volatility` plugin takes two arguments, `-os`, which asks for the type of operating system that the recording is used, and `--panda-arg filter=FILE.txt` to pass in the filter file to plugin. An example invocation: ```bash panda-system-i386 -m 2048 -replay /path/to/RECORDING -panda 'volatility' -os windows-32-7sp1 --panda-arg filter:file=filter.txt From c39554a2f5aa8ef4edb7045c37078822097ab412 Mon Sep 17 00:00:00 2001 From: Suhaas Narayan <40607921+Swashbuckler1@users.noreply.github.com> Date: Fri, 1 May 2026 12:02:57 -0400 Subject: [PATCH 24/24] fixed typos --- plugins/volatility/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/volatility/README.md b/plugins/volatility/README.md index f17e9bf..08d6e1f 100644 --- a/plugins/volatility/README.md +++ b/plugins/volatility/README.md @@ -30,10 +30,10 @@ This plugin takes in a PANDA recording and outputs an avro record file,`volatili ## Usage ### Running manually -`volatility` plugin takes two arguments, `-os`, which asks for the type of operating system that the recording is used, and `--panda-arg filter=FILE.txt` to pass in the filter file to plugin. An example invocation: +`volatility` plugin takes two arguments, `-os`, which asks for the type of operating system that the recording is used, and `--panda-arg filter:file=filter.json` to pass in the filter file to plugin. An example invocation: ```bash -panda-system-i386 -m 2048 -replay /path/to/RECORDING -panda 'volatility' -os windows-32-7sp1 --panda-arg filter:file=filter.txt +panda-system-i386 -m 2048 -replay /path/to/RECORDING -panda 'volatility' -os windows-32-7sp1 --panda-arg filter:file=filter.json ``` * To view the result avro record, we can use `jq` (a command line JSON processor for better visualization)