From 6166770e6f2a9176399488039912214361746eff Mon Sep 17 00:00:00 2001 From: saleh muhaysin Date: Sat, 27 Mar 2021 20:09:30 +0300 Subject: [PATCH 1/7] v2.0.10 --- VirualMachine/README.md | 3 +- app/__init__.py | 120 +- app/__version__ | 2 +- app/controllers/API_management.py | 87 + app/controllers/admin_management.py | 3 + app/controllers/case_management.py | 24 +- app/controllers/parser_management.py | 4 +- app/database/elkdb.py | 4 +- app/parsers/BrowserHistory/configuration.json | 2 +- .../CertUtilParser/CryptnetUrlCacheParser.py | 170 ++ app/parsers/CertUtilParser/__init__.py | 0 app/parsers/CertUtilParser/configuration.json | 1 + app/parsers/CertUtilParser/interface.py | 21 + .../IIS_Sites_Parser/Ichnaea_Sites_Parser.py | 33 + .../IIS_Sites_Parser/Ichnaea_interface.py | 15 + app/parsers/IIS_Sites_Parser/__init__.py | 0 .../IIS_Sites_Parser/configuration.json | 1 + .../Ichnaea_compare/Ichnaea_compare.py | 57 + .../Ichnaea_compare_interface.py | 24 + app/parsers/Ichnaea_compare/__init__.py | 0 .../Ichnaea_compare/configuration.json | 1 + .../Ichnaea_modules_Parser.py | 19 + .../Ichnaea_modules_interface.py | 15 + .../Ichnaea_modules_parser/__init__.py | 0 .../Ichnaea_modules_parser/configuration.json | 1 + app/parsers/JumpListLnk/configuration.json | 2 +- app/parsers/MFT_Parser/configuration.json | 2 +- app/parsers/MFT_Parser/mft_dump | Bin .../PowerShellHistory/configuration.json | 2 +- .../PyWMIPersistenceFinder/configuration.json | 2 +- app/parsers/RUA/configuration.json | 2 +- app/parsers/RecentFileCache/__init__.py | 0 .../RecentFileCache/configuration.json | 1 + app/parsers/RecentFileCache/interface.py | 53 + app/parsers/SCCM/configuration.json | 2 +- app/parsers/UsnJrnl_parser/configuration.json | 2 +- app/parsers/WER/configuration.json | 2 +- app/parsers/WinEvents/configuration.json | 2 +- app/parsers/WinEvents/evtx_dump | Bin app/parsers/csv_parser/configuration.json | 2 +- .../prefetch_parser/configuration.json | 2 +- .../recyclebin_parser/configuration.json | 2 +- app/parsers/regsk/configuration.json | 2 +- app/parsers/regsk/interface.py | 3 +- app/parsers/regsk/plugins/Amcache.py | 5 +- app/parsers/scheduled_task/configuration.json | 2 +- app/parsers/shellbags/configuration.json | 2 +- .../shimcache_parser/configuration.json | 2 +- app/parsers/srum_parser/configuration.json | 2 +- app/parsers/srum_parser/srum.py | 2 +- app/parsers/srum_parser/srum_interface.py | 3 + app/parsers/vol_Parser/LICENSE.txt | 46 + app/parsers/vol_Parser/MANIFEST.in | 6 + app/parsers/vol_Parser/README.md | 131 ++ app/parsers/vol_Parser/__init__.py | 0 app/parsers/vol_Parser/configuration.json | 1 + .../vol_Parser/development/__init__.py | 0 .../vol_Parser/development/centos-kernels.txt | 51 + .../vol_Parser/development/compare-vol.py | 274 +++ .../vol_Parser/development/debian-kernels.txt | 93 + .../vol_Parser/development/fedora-kernels.txt | 50 + .../vol_Parser/development/jsonschema.schema | 168 ++ .../development/mac-kdk/extract_kernel.sh | 50 + .../development/mac-kdk/generate_json.sh | 19 + .../development/mac-kdk/parse_pbzx2.py | 83 + .../development/pdbparse-to-json.py | 360 ++++ .../vol_Parser/development/schema_validate.py | 58 + .../development/stock-linux-json.py | 135 ++ app/parsers/vol_Parser/doc/Makefile | 24 + app/parsers/vol_Parser/doc/make.bat | 35 + .../vol_Parser/doc/source/_static/favicon.ico | Bin 0 -> 107414 bytes .../vol_Parser/doc/source/_static/vol.png | Bin 0 -> 9843 bytes app/parsers/vol_Parser/doc/source/basics.rst | 131 ++ .../vol_Parser/doc/source/complex-plugin.rst | 287 +++ app/parsers/vol_Parser/doc/source/conf.py | 326 ++++ app/parsers/vol_Parser/doc/source/index.rst | 33 + .../vol_Parser/doc/source/simple-plugin.rst | 234 +++ .../vol_Parser/doc/source/symbol-tables.rst | 53 + .../doc/source/using-as-a-library.rst | 244 +++ app/parsers/vol_Parser/doc/source/vol-cli.rst | 123 ++ app/parsers/vol_Parser/doc/source/vol2to3.rst | 80 + app/parsers/vol_Parser/interface.py | 48 + app/parsers/vol_Parser/mypy.ini | 4 + .../vol_Parser/parser_plugins/__init__.py | 20 + .../vol_Parser/parser_plugins/mem_FileScan.py | 8 + .../vol_Parser/parser_plugins/mem_ModScan.py | 9 + .../vol_Parser/parser_plugins/mem_Modules.py | 8 + .../parser_plugins/mem_ProcessPrivs.py | 7 + .../parser_plugins/mem_ProcessSID.py | 8 + .../vol_Parser/parser_plugins/mem_SSDT.py | 9 + .../vol_Parser/parser_plugins/mem_cmdline.py | 7 + .../vol_Parser/parser_plugins/mem_dlllist.py | 9 + .../vol_Parser/parser_plugins/mem_envars.py | 8 + .../vol_Parser/parser_plugins/mem_handles.py | 7 + .../vol_Parser/parser_plugins/mem_hiveList.py | 8 + .../vol_Parser/parser_plugins/mem_info.py | 12 + .../parser_plugins/mem_mutantScan.py | 8 + .../vol_Parser/parser_plugins/mem_netScan.py | 9 + .../vol_Parser/parser_plugins/mem_pslist.py | 16 + .../vol_Parser/parser_plugins/mem_symlink.py | 10 + .../parser_plugins/mem_timeliner.py | 7 + .../parser_plugins/mem_userAssist.py | 17 + .../vol_Parser/parser_plugins/mem_vadInfo.py | 12 + .../vol_Parser/parser_plugins/mem_virtMap.py | 10 + app/parsers/vol_Parser/setup.py | 41 + app/parsers/vol_Parser/vol.py | 10 + app/parsers/vol_Parser/vol.spec | 110 ++ app/parsers/vol_Parser/vol_Parser.py | 328 ++++ app/parsers/vol_Parser/volatility/__init__.py | 62 + .../vol_Parser/volatility/cli/__init__.py | 583 ++++++ .../volatility/cli/text_renderer.py | 365 ++++ .../vol_Parser/volatility/cli/volargparse.py | 83 + .../volatility/cli/volshell/__init__.py | 245 +++ .../volatility/cli/volshell/generic.py | 334 ++++ .../volatility/cli/volshell/linux.py | 66 + .../vol_Parser/volatility/cli/volshell/mac.py | 66 + .../volatility/cli/volshell/windows.py | 63 + .../volatility/framework/__init__.py | 134 ++ .../framework/automagic/__init__.py | 134 ++ .../framework/automagic/construct_layers.py | 67 + .../volatility/framework/automagic/linux.py | 156 ++ .../volatility/framework/automagic/mac.py | 206 +++ .../volatility/framework/automagic/pdbscan.py | 305 ++++ .../volatility/framework/automagic/stacker.py | 261 +++ .../framework/automagic/symbol_cache.py | 127 ++ .../framework/automagic/symbol_finder.py | 141 ++ .../volatility/framework/automagic/windows.py | 458 +++++ .../framework/configuration/__init__.py | 5 + .../framework/configuration/requirements.py | 426 +++++ .../framework/constants/__init__.py | 94 + .../framework/constants/linux/__init__.py | 13 + .../framework/constants/windows/__init__.py | 10 + .../volatility/framework/contexts/__init__.py | 352 ++++ .../volatility/framework/exceptions.py | 101 ++ .../framework/interfaces/__init__.py | 16 + .../framework/interfaces/automagic.py | 134 ++ .../framework/interfaces/configuration.py | 722 ++++++++ .../framework/interfaces/context.py | 228 +++ .../volatility/framework/interfaces/layers.py | 613 +++++++ .../framework/interfaces/objects.py | 337 ++++ .../framework/interfaces/plugins.py | 151 ++ .../framework/interfaces/renderers.py | 220 +++ .../framework/interfaces/symbols.py | 335 ++++ .../volatility/framework/layers/__init__.py | 3 + .../framework/layers/codecs/__init__.py | 4 + .../volatility/framework/layers/crash.py | 202 +++ .../volatility/framework/layers/elf.py | 86 + .../volatility/framework/layers/intel.py | 318 ++++ .../volatility/framework/layers/lime.py | 90 + .../volatility/framework/layers/linear.py | 72 + .../volatility/framework/layers/msf.py | 231 +++ .../volatility/framework/layers/physical.py | 185 ++ .../volatility/framework/layers/qemu.py | 236 +++ .../volatility/framework/layers/registry.py | 263 +++ .../volatility/framework/layers/resources.py | 230 +++ .../framework/layers/scanners/__init__.py | 60 + .../framework/layers/scanners/multiregexp.py | 30 + .../volatility/framework/layers/segmented.py | 142 ++ .../volatility/framework/layers/vmware.py | 157 ++ .../volatility/framework/objects/__init__.py | 764 ++++++++ .../volatility/framework/objects/templates.py | 106 ++ .../volatility/framework/objects/utility.py | 44 + .../volatility/framework/plugins/__init__.py | 54 + .../volatility/framework/plugins/banners.py | 50 + .../framework/plugins/configwriter.py | 51 + .../framework/plugins/frameworkinfo.py | 34 + .../volatility/framework/plugins/isfinfo.py | 130 ++ .../framework/plugins/layerwriter.py | 121 ++ .../framework/plugins/linux/__init__.py | 8 + .../framework/plugins/linux/bash.py | 109 ++ .../framework/plugins/linux/check_afinfo.py | 91 + .../framework/plugins/linux/check_creds.py | 63 + .../framework/plugins/linux/check_idt.py | 97 + .../framework/plugins/linux/check_modules.py | 75 + .../framework/plugins/linux/check_syscall.py | 177 ++ .../framework/plugins/linux/elfs.py | 64 + .../plugins/linux/keyboard_notifiers.py | 62 + .../framework/plugins/linux/lsmod.py | 75 + .../framework/plugins/linux/lsof.py | 62 + .../framework/plugins/linux/malfind.py | 80 + .../framework/plugins/linux/proc.py | 73 + .../framework/plugins/linux/pslist.py | 92 + .../framework/plugins/linux/pstree.py | 56 + .../framework/plugins/linux/tty_check.py | 80 + .../framework/plugins/mac/__init__.py | 0 .../volatility/framework/plugins/mac/bash.py | 111 ++ .../framework/plugins/mac/check_syscall.py | 66 + .../framework/plugins/mac/check_sysctl.py | 140 ++ .../framework/plugins/mac/check_trap_table.py | 61 + .../framework/plugins/mac/ifconfig.py | 52 + .../framework/plugins/mac/kauth_listeners.py | 59 + .../framework/plugins/mac/kauth_scopes.py | 76 + .../framework/plugins/mac/kevents.py | 174 ++ .../framework/plugins/mac/list_files.py | 175 ++ .../volatility/framework/plugins/mac/lsmod.py | 60 + .../volatility/framework/plugins/mac/lsof.py | 54 + .../framework/plugins/mac/malfind.py | 80 + .../volatility/framework/plugins/mac/mount.py | 60 + .../framework/plugins/mac/netstat.py | 115 ++ .../framework/plugins/mac/proc_maps.py | 54 + .../volatility/framework/plugins/mac/psaux.py | 103 ++ .../framework/plugins/mac/pslist.py | 290 +++ .../framework/plugins/mac/pstree.py | 73 + .../framework/plugins/mac/socket_filters.py | 72 + .../framework/plugins/mac/timers.py | 79 + .../framework/plugins/mac/trustedbsd.py | 77 + .../framework/plugins/mac/vfsevents.py | 65 + .../volatility/framework/plugins/timeliner.py | 218 +++ .../framework/plugins/windows/__init__.py | 8 + .../framework/plugins/windows/bigpools.py | 127 ++ .../framework/plugins/windows/cachedump.py | 131 ++ .../framework/plugins/windows/callbacks.py | 289 +++ .../framework/plugins/windows/cmdline.py | 88 + .../framework/plugins/windows/dlllist.py | 174 ++ .../framework/plugins/windows/driverirp.py | 74 + .../framework/plugins/windows/driverscan.py | 77 + .../framework/plugins/windows/envars.py | 201 +++ .../framework/plugins/windows/filescan.py | 63 + .../plugins/windows/getservicesids.py | 87 + .../framework/plugins/windows/getsids.py | 162 ++ .../framework/plugins/windows/handles.py | 351 ++++ .../framework/plugins/windows/hashdump.py | 297 +++ .../framework/plugins/windows/info.py | 206 +++ .../framework/plugins/windows/lsadump.py | 189 ++ .../framework/plugins/windows/malfind.py | 164 ++ .../framework/plugins/windows/memmap.py | 81 + .../framework/plugins/windows/modscan.py | 179 ++ .../framework/plugins/windows/modules.py | 179 ++ .../framework/plugins/windows/mutantscan.py | 66 + .../framework/plugins/windows/netscan.py | 351 ++++ .../framework/plugins/windows/poolscanner.py | 401 +++++ .../framework/plugins/windows/privileges.py | 99 + .../framework/plugins/windows/pslist.py | 217 +++ .../framework/plugins/windows/psscan.py | 168 ++ .../framework/plugins/windows/pstree.py | 98 + .../plugins/windows/registry/__init__.py | 8 + .../plugins/windows/registry/hivelist.py | 242 +++ .../plugins/windows/registry/hivescan.py | 76 + .../plugins/windows/registry/printkey.py | 199 ++ .../plugins/windows/registry/userassist.json | 103 ++ .../plugins/windows/registry/userassist.py | 254 +++ .../plugins/windows/sids_and_privileges.json | 612 +++++++ .../framework/plugins/windows/ssdt.py | 132 ++ .../framework/plugins/windows/strings.py | 131 ++ .../framework/plugins/windows/svcscan.py | 170 ++ .../framework/plugins/windows/symlinkscan.py | 80 + .../framework/plugins/windows/vadinfo.py | 216 +++ .../framework/plugins/windows/vadyarascan.py | 87 + .../framework/plugins/windows/verinfo.py | 169 ++ .../framework/plugins/windows/virtmap.py | 122 ++ .../volatility/framework/plugins/yarascan.py | 94 + .../framework/renderers/__init__.py | 383 ++++ .../framework/renderers/conversion.py | 111 ++ .../framework/renderers/format_hints.py | 50 + .../volatility/framework/symbols/__init__.py | 261 +++ .../framework/symbols/generic/__init__.py | 49 + .../framework/symbols/generic/qemu.json | 107 ++ .../volatility/framework/symbols/intermed.py | 687 +++++++ .../framework/symbols/linux/__init__.py | 259 +++ .../framework/symbols/linux/bash.py | 14 + .../framework/symbols/linux/bash32.json | 72 + .../framework/symbols/linux/bash64.json | 72 + .../framework/symbols/linux/elf.json | 967 ++++++++++ .../symbols/linux/extensions/__init__.py | 530 ++++++ .../symbols/linux/extensions/bash.py | 53 + .../framework/symbols/linux/extensions/elf.py | 269 +++ .../framework/symbols/mac/__init__.py | 212 +++ .../symbols/mac/extensions/__init__.py | 571 ++++++ .../volatility/framework/symbols/metadata.py | 41 + .../volatility/framework/symbols/native.py | 106 ++ .../framework/symbols/windows/__init__.py | 64 + .../symbols/windows/bigpools-vista-x64.json | 88 + .../symbols/windows/bigpools-vista-x86.json | 82 + .../symbols/windows/bigpools-win10-x64.json | 123 ++ .../symbols/windows/bigpools-win10-x86.json | 111 ++ .../symbols/windows/bigpools-x64.json | 71 + .../symbols/windows/bigpools-x86.json | 65 + .../symbols/windows/callbacks-x64.json | 150 ++ .../symbols/windows/callbacks-x86.json | 150 ++ .../framework/symbols/windows/crash.json | 418 +++++ .../framework/symbols/windows/crash64.json | 481 +++++ .../symbols/windows/extensions/__init__.py | 875 +++++++++ .../symbols/windows/extensions/kdbg.py | 36 + .../symbols/windows/extensions/network.py | 239 +++ .../symbols/windows/extensions/pe.py | 168 ++ .../symbols/windows/extensions/pool.py | 350 ++++ .../symbols/windows/extensions/registry.py | 301 ++++ .../symbols/windows/extensions/services.py | 128 ++ .../framework/symbols/windows/kdbg.json | 1025 +++++++++++ .../windows/netscan-vista-sp12-x64.json | 536 ++++++ .../symbols/windows/netscan-vista-x64.json | 536 ++++++ .../symbols/windows/netscan-vista-x86.json | 537 ++++++ .../windows/netscan-win10-14393-x86.json | 535 ++++++ .../windows/netscan-win10-15063-x64.json | 388 ++++ .../windows/netscan-win10-15063-x86.json | 535 ++++++ .../symbols/windows/netscan-win10-x64.json | 389 ++++ .../symbols/windows/netscan-win10-x86.json | 535 ++++++ .../symbols/windows/netscan-win7-x64.json | 535 ++++++ .../symbols/windows/netscan-win7-x86.json | 536 ++++++ .../symbols/windows/netscan-win8-x64.json | 536 ++++++ .../symbols/windows/netscan-win8-x86.json | 537 ++++++ .../symbols/windows/netscan-win81-x64.json | 536 ++++++ .../symbols/windows/netscan-win81-x86.json | 537 ++++++ .../framework/symbols/windows/pdb.json | 1598 +++++++++++++++++ .../framework/symbols/windows/pdb.py | 291 +++ .../framework/symbols/windows/pdbconv.py | 985 ++++++++++ .../framework/symbols/windows/pe.json | 931 ++++++++++ .../symbols/windows/poolheader-x64-win7.json | 79 + .../symbols/windows/poolheader-x64.json | 85 + .../symbols/windows/poolheader-x86.json | 85 + .../framework/symbols/windows/registry.json | 320 ++++ .../symbols/windows/services-vista-x64.json | 255 +++ .../symbols/windows/services-vista-x86.json | 255 +++ .../windows/services-win10-15063-x64.json | 255 +++ .../windows/services-win10-15063-x86.json | 248 +++ .../windows/services-win10-16299-x64.json | 255 +++ .../windows/services-win10-16299-x86.json | 248 +++ .../symbols/windows/services-win8-x64.json | 255 +++ .../symbols/windows/services-win8-x86.json | 248 +++ .../symbols/windows/services-xp-2003-x64.json | 245 +++ .../symbols/windows/services-xp-x86.json | 245 +++ .../framework/symbols/windows/versions.py | 118 ++ .../volatility/framework/symbols/wrappers.py | 27 + .../vol_Parser/volatility/plugins/__init__.py | 17 + .../volatility/plugins/linux/__init__.py | 19 + .../volatility/plugins/mac/__init__.py | 19 + .../volatility/plugins/windows/__init__.py | 19 + .../plugins/windows/registry/__init__.py | 19 + .../plugins/windows/registry/certificates.py | 70 + .../volatility/plugins/windows/statistics.py | 71 + .../vol_Parser/volatility/schemas/__init__.py | 82 + .../volatility/schemas/schema-0.1.0.json | 307 ++++ .../volatility/schemas/schema-2.0.0.json | 307 ++++ .../volatility/schemas/schema-2.1.0.json | 317 ++++ .../volatility/schemas/schema-4.0.0.json | 331 ++++ .../volatility/schemas/schema-4.1.0.json | 338 ++++ .../volatility/schemas/schema-6.0.0.json | 447 +++++ .../volatility/schemas/schema-6.1.0.json | 450 +++++ .../volatility/schemas/schema-6.2.0.json | 498 +++++ .../vol_Parser/volatility/symbols/__init__.py | 11 + ...15B12C74F0E177581B6B27DD4C5022C2-1.json.xz | Bin 0 -> 585376 bytes ...B16053724B46515388FDEA9D0470D02E-1.json.xz | Bin 0 -> 585272 bytes app/parsers/vol_Parser/volshell.py | 10 + app/parsers/vol_Parser/volshell.spec | 66 + app/static/Kuiper.js | 36 +- .../dist/tagsinput/bootstrap-tagsinput.js | 94 +- app/templates/admin/configuration.html | 1 + app/templates/case/browse_artifacts.html | 219 ++- app/templates/case/machines.html | 6 +- app/templates/case/upload_machines.html | 2 +- app/templates/login.html | 192 ++ cert/MyCertificate.crt | 49 +- cert/MyKey.key | 76 +- cert/cert.pem | 32 - cert/key.pem | 52 - configuration.yaml | 28 +- flask_simpleldap/__init__.py | 386 ++++ kuiper-nginx.conf | 26 + kuiper_install.sh | 38 +- requirements_2.7.txt | 1 + 360 files changed, 54953 insertions(+), 287 deletions(-) create mode 100644 app/controllers/API_management.py create mode 100644 app/parsers/CertUtilParser/CryptnetUrlCacheParser.py create mode 100644 app/parsers/CertUtilParser/__init__.py create mode 100644 app/parsers/CertUtilParser/configuration.json create mode 100644 app/parsers/CertUtilParser/interface.py create mode 100644 app/parsers/IIS_Sites_Parser/Ichnaea_Sites_Parser.py create mode 100644 app/parsers/IIS_Sites_Parser/Ichnaea_interface.py create mode 100644 app/parsers/IIS_Sites_Parser/__init__.py create mode 100644 app/parsers/IIS_Sites_Parser/configuration.json create mode 100644 app/parsers/Ichnaea_compare/Ichnaea_compare.py create mode 100644 app/parsers/Ichnaea_compare/Ichnaea_compare_interface.py create mode 100644 app/parsers/Ichnaea_compare/__init__.py create mode 100644 app/parsers/Ichnaea_compare/configuration.json create mode 100644 app/parsers/Ichnaea_modules_parser/Ichnaea_modules_Parser.py create mode 100644 app/parsers/Ichnaea_modules_parser/Ichnaea_modules_interface.py create mode 100644 app/parsers/Ichnaea_modules_parser/__init__.py create mode 100644 app/parsers/Ichnaea_modules_parser/configuration.json mode change 100755 => 100644 app/parsers/MFT_Parser/mft_dump create mode 100644 app/parsers/RecentFileCache/__init__.py create mode 100644 app/parsers/RecentFileCache/configuration.json create mode 100644 app/parsers/RecentFileCache/interface.py mode change 100755 => 100644 app/parsers/WinEvents/evtx_dump create mode 100644 app/parsers/vol_Parser/LICENSE.txt create mode 100644 app/parsers/vol_Parser/MANIFEST.in create mode 100644 app/parsers/vol_Parser/README.md create mode 100644 app/parsers/vol_Parser/__init__.py create mode 100644 app/parsers/vol_Parser/configuration.json create mode 100644 app/parsers/vol_Parser/development/__init__.py create mode 100644 app/parsers/vol_Parser/development/centos-kernels.txt create mode 100644 app/parsers/vol_Parser/development/compare-vol.py create mode 100644 app/parsers/vol_Parser/development/debian-kernels.txt create mode 100644 app/parsers/vol_Parser/development/fedora-kernels.txt create mode 100644 app/parsers/vol_Parser/development/jsonschema.schema create mode 100644 app/parsers/vol_Parser/development/mac-kdk/extract_kernel.sh create mode 100644 app/parsers/vol_Parser/development/mac-kdk/generate_json.sh create mode 100644 app/parsers/vol_Parser/development/mac-kdk/parse_pbzx2.py create mode 100644 app/parsers/vol_Parser/development/pdbparse-to-json.py create mode 100644 app/parsers/vol_Parser/development/schema_validate.py create mode 100644 app/parsers/vol_Parser/development/stock-linux-json.py create mode 100644 app/parsers/vol_Parser/doc/Makefile create mode 100644 app/parsers/vol_Parser/doc/make.bat create mode 100644 app/parsers/vol_Parser/doc/source/_static/favicon.ico create mode 100644 app/parsers/vol_Parser/doc/source/_static/vol.png create mode 100644 app/parsers/vol_Parser/doc/source/basics.rst create mode 100644 app/parsers/vol_Parser/doc/source/complex-plugin.rst create mode 100644 app/parsers/vol_Parser/doc/source/conf.py create mode 100644 app/parsers/vol_Parser/doc/source/index.rst create mode 100644 app/parsers/vol_Parser/doc/source/simple-plugin.rst create mode 100644 app/parsers/vol_Parser/doc/source/symbol-tables.rst create mode 100644 app/parsers/vol_Parser/doc/source/using-as-a-library.rst create mode 100644 app/parsers/vol_Parser/doc/source/vol-cli.rst create mode 100644 app/parsers/vol_Parser/doc/source/vol2to3.rst create mode 100644 app/parsers/vol_Parser/interface.py create mode 100644 app/parsers/vol_Parser/mypy.ini create mode 100644 app/parsers/vol_Parser/parser_plugins/__init__.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_FileScan.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_ModScan.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_Modules.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_ProcessPrivs.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_ProcessSID.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_SSDT.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_cmdline.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_dlllist.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_envars.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_handles.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_hiveList.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_info.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_mutantScan.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_netScan.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_pslist.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_symlink.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_timeliner.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_userAssist.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_vadInfo.py create mode 100644 app/parsers/vol_Parser/parser_plugins/mem_virtMap.py create mode 100644 app/parsers/vol_Parser/setup.py create mode 100644 app/parsers/vol_Parser/vol.py create mode 100644 app/parsers/vol_Parser/vol.spec create mode 100644 app/parsers/vol_Parser/vol_Parser.py create mode 100644 app/parsers/vol_Parser/volatility/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/cli/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/cli/text_renderer.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volargparse.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volshell/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volshell/generic.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volshell/linux.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volshell/mac.py create mode 100644 app/parsers/vol_Parser/volatility/cli/volshell/windows.py create mode 100644 app/parsers/vol_Parser/volatility/framework/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/construct_layers.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/linux.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/mac.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/pdbscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/stacker.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/symbol_cache.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/symbol_finder.py create mode 100644 app/parsers/vol_Parser/volatility/framework/automagic/windows.py create mode 100644 app/parsers/vol_Parser/volatility/framework/configuration/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/configuration/requirements.py create mode 100644 app/parsers/vol_Parser/volatility/framework/constants/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/constants/linux/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/constants/windows/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/contexts/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/exceptions.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/automagic.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/configuration.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/context.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/layers.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/objects.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/plugins.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/renderers.py create mode 100644 app/parsers/vol_Parser/volatility/framework/interfaces/symbols.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/codecs/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/crash.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/elf.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/intel.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/lime.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/linear.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/msf.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/physical.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/qemu.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/registry.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/resources.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/scanners/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/scanners/multiregexp.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/segmented.py create mode 100644 app/parsers/vol_Parser/volatility/framework/layers/vmware.py create mode 100644 app/parsers/vol_Parser/volatility/framework/objects/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/objects/templates.py create mode 100644 app/parsers/vol_Parser/volatility/framework/objects/utility.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/banners.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/configwriter.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/frameworkinfo.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/isfinfo.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/layerwriter.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/bash.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/check_afinfo.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/check_creds.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/check_idt.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/check_modules.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/check_syscall.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/elfs.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/keyboard_notifiers.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/lsmod.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/lsof.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/malfind.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/proc.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/pslist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/pstree.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/linux/tty_check.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/bash.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/check_syscall.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/check_sysctl.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/check_trap_table.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/ifconfig.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_listeners.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_scopes.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/kevents.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/list_files.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/lsmod.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/lsof.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/malfind.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/mount.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/netstat.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/proc_maps.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/psaux.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/pslist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/pstree.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/socket_filters.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/timers.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/trustedbsd.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/mac/vfsevents.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/timeliner.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/bigpools.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/cachedump.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/callbacks.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/cmdline.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/dlllist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/driverirp.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/driverscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/envars.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/filescan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/getservicesids.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/getsids.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/handles.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/hashdump.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/info.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/lsadump.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/malfind.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/memmap.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/modscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/modules.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/mutantscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/netscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/poolscanner.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/privileges.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/pslist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/psscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/pstree.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivelist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivescan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/printkey.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.json create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/sids_and_privileges.json create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/ssdt.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/strings.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/svcscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/symlinkscan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/vadinfo.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/vadyarascan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/verinfo.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/windows/virtmap.py create mode 100644 app/parsers/vol_Parser/volatility/framework/plugins/yarascan.py create mode 100644 app/parsers/vol_Parser/volatility/framework/renderers/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/renderers/conversion.py create mode 100644 app/parsers/vol_Parser/volatility/framework/renderers/format_hints.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/generic/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/generic/qemu.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/intermed.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/bash.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/bash32.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/bash64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/elf.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/bash.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/elf.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/mac/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/mac/extensions/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/metadata.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/native.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/crash.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/crash64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/kdbg.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/network.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pe.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pool.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/registry.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/services.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/kdbg.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-sp12-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-14393-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/pdbconv.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/pe.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64-win7.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/registry.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-2003-x64.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-x86.json create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/windows/versions.py create mode 100644 app/parsers/vol_Parser/volatility/framework/symbols/wrappers.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/linux/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/mac/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/windows/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/windows/registry/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/windows/registry/certificates.py create mode 100644 app/parsers/vol_Parser/volatility/plugins/windows/statistics.py create mode 100644 app/parsers/vol_Parser/volatility/schemas/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-0.1.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-2.0.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-2.1.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-4.0.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-4.1.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-6.0.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-6.1.0.json create mode 100644 app/parsers/vol_Parser/volatility/schemas/schema-6.2.0.json create mode 100644 app/parsers/vol_Parser/volatility/symbols/__init__.py create mode 100644 app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/15B12C74F0E177581B6B27DD4C5022C2-1.json.xz create mode 100644 app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/B16053724B46515388FDEA9D0470D02E-1.json.xz create mode 100644 app/parsers/vol_Parser/volshell.py create mode 100644 app/parsers/vol_Parser/volshell.spec create mode 100644 app/templates/login.html delete mode 100644 cert/cert.pem delete mode 100644 cert/key.pem create mode 100644 flask_simpleldap/__init__.py create mode 100644 kuiper-nginx.conf mode change 100755 => 100644 kuiper_install.sh diff --git a/VirualMachine/README.md b/VirualMachine/README.md index a838fcb6..bb4c9608 100644 --- a/VirualMachine/README.md +++ b/VirualMachine/README.md @@ -1,11 +1,10 @@ # Virtual Machine -Note: we recommend to use this VM machine for testing only, it is preferable to use the latests version from releases ## Download Link To download a virtual machine with Kuiper and all its dependencies installed, please visit the following link ``` -https://mega.nz/folder/OslmmaxL#7AUJEdU9hiAHHuD19uNF2w +https://mega.nz/#F!i9UjyaQQ!DK97l_CzzhCa-y_E1dPULQ ``` ## Credentials diff --git a/app/__init__.py b/app/__init__.py index bf38031c..d1f818ba 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,17 +2,21 @@ import yaml import inspect -from datetime import datetime +from datetime import datetime, timedelta -from flask import Flask +from flask import Flask, g , session, render_template from flask import request, redirect, url_for - - + +import urllib, json from celery import Celery from celery.bin import worker #from flask.ext.celery import Celery +# ldap authentication +from flask_simpleldap import LDAP, LDAPException + + app = Flask(__name__) @@ -23,8 +27,6 @@ app.config['UPLOADED_FILES_DEST'] = os.path.abspath( ''.join(y['Directories']['artifacts_upload']) ) # uploaded artifacts files app.config['UPLOADED_FILES_DEST_RAW'] = os.path.abspath( ''.join(y['Directories']['artifacts_upload_raw']) ) # uploaded artifacts raw files app.config['PARSER_PATH'] = os.path.abspath( ''.join(y['Directories']['app_parsers']) ) # parser folder - - app.config['CELERY_BROKER_URL'] = y['CELERY']['CELERY_BROKER_URL'] app.config['CELERY_RESULT_BACKEND'] = y['CELERY']['CELERY_RESULT_BACKEND'] app.config['CELERY_TASK_ACKS_LATE'] = y['CELERY']['CELERY_TASK_ACKS_LATE'] @@ -40,6 +42,9 @@ y['CELERY']['CELERY_WORKER_NAME'] = "celery@" + y['CELERY']['CELERY_WORKER_NAME'] +app.secret_key = y['Kuiper']['secret_key'] + + app.config['DB_NAME'] = y['MongoDB']['DB_NAME'] app.config['DB_IP'] = y['MongoDB']['DB_IP'] app.config['DB_PORT'] = y['MongoDB']['DB_PORT'] @@ -48,6 +53,19 @@ +app.config['LDAP_HOST'] = y['LDAP_auth']['LDAP_HOST'] +app.config['LDAP_PORT'] = y['LDAP_auth']['LDAP_PORT'] +app.config['LDAP_SCHEMA'] = y['LDAP_auth']['LDAP_SCHEMA'] +app.config['LDAP_USE_SSL'] = y['LDAP_auth']['LDAP_USE_SSL'] +app.config['LDAP_BASE_DN'] = y['LDAP_auth']['LDAP_BASE_DN'] +app.config['LDAP_USERNAME'] = y['LDAP_auth']['LDAP_USERNAME'] +app.config['LDAP_PASSWORD'] = y['LDAP_auth']['LDAP_PASSWORD'] + +app.config['LDAP_USER_OBJECT_FILTER'] = y['LDAP_auth']['LDAP_USER_OBJECT_FILTER'] + +if y['LDAP_auth']['enabled']: + ldap = LDAP(app) + # ===================== Logger - START ===================== # # class Logger to handle all Kuiper logs class Logger: @@ -105,11 +123,95 @@ def logger(self, level , type , message , reason=""): -from controllers import case_management,admin_management - +from controllers import case_management,admin_management,API_management # redirector to the actual home page @app.route('/') def home(): - return redirect(url_for('home_page')) \ No newline at end of file + return redirect(url_for('home_page')) + + +# ================= ldap authentication + +def is_authenticated(): + return 'last_visit' in session and 'user_id' in session + +@app.before_request +def before_request(): + g.user = None + if y['LDAP_auth']['enabled'] == False or request.full_path.startswith('/static') or request.full_path.startswith('/login') or request.full_path.startswith('/logout'): + return + + # check if the api token correct + if request.full_path.startswith('/api'): + request_str = urllib.unquote(request.data).decode('utf8') + request_json = json.loads(request_str)['data'] + + if request_json['api_token'] == y['Kuiper']['api_token']: + return + + if is_authenticated(): + session_expired = datetime.now() > session['last_visit'] + timedelta(minutes = 30) + if 'last_visit' not in session or session_expired: + return redirect(url_for('login', message="Session expired!", url=request.full_path)) + + session['last_visit'] = datetime.now() + return + + + return redirect(url_for('login', message=None)) + + + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if y['LDAP_auth']['enabled'] == False: + return redirect(url_for('home')) + + message = request.args.get('message' , None) + if is_authenticated() and message != "Session expired!": + return redirect(url_for('home')) + if request.method == 'POST': + user = request.form['user'] + passwd = request.form['passwd'] + try: + test = ldap.bind_user(user, passwd) + except LDAPException as e : + message = str(e) + return render_template('login.html' , msg=message ) + + if test is None or passwd == '': + return render_template('login.html' , msg='Invalid credentials') + else: + session['user_id'] = request.form['user'] + session['last_visit'] = datetime.now() + + url = request.args.get('url' , None) + if url is None: + return redirect(url_for('home')) + else: + return redirect(url) + + message = request.args.get('message' , None) + return render_template('login.html' , msg=message ) + +@app.route('/logout', methods=['GET']) +def logout(): + + if y['LDAP_auth']['enabled'] == False: + return redirect(url_for('home')) + + + try: + del session['user_id'] + del session['last_visit'] + except: + pass + + message = request.args.get('message' , None) + url = request.args.get('url' , None) + if message is not None and url is not None: + return redirect(url_for('login', message=message, url=request.full_path)) + return redirect(url_for('login')) \ No newline at end of file diff --git a/app/__version__ b/app/__version__ index a92991c5..ed85297d 100644 --- a/app/__version__ +++ b/app/__version__ @@ -1 +1 @@ -Kuiper:1.0.1 +Kuiper:2.0.10 diff --git a/app/controllers/API_management.py b/app/controllers/API_management.py new file mode 100644 index 00000000..86ae98d3 --- /dev/null +++ b/app/controllers/API_management.py @@ -0,0 +1,87 @@ +#/usr/bin/env python2 +# -*- coding: utf-8 -*- + +import json +import os +import urllib +import shutil +import yaml +import zipfile +import hashlib +import base64 + + +from flask import Flask +from flask import request, redirect, render_template, url_for, flash +from flask import jsonify + +from app import app + +import parser_management + +from app.database.dbstuff import * +from app.database.elkdb import * + +from werkzeug.utils import secure_filename +from bson import json_util +from bson.json_util import dumps + + + + + + + +# retrive all artifacts records using ajax +@app.route('/api/get_artifacts/', methods=["POST"]) +def api_get_artifacts(case_id): + if request.method == "POST": + + request_str = urllib.unquote(request.data).decode('utf8') + request_json = json.loads(request_str)['data'] + logger.logger(level=logger.DEBUG , type="api", message="Case["+case_id+"]: get artifacts request", reason=json.dumps(request_json)) + + # == from - to + body = { + "from": request_json['seq_num'] * 20000, + "size":20000, + + } + + # == query + if request_json['query'] != None: + request_json['query'] = request_json['query'].strip() + query = '*' if request_json['query'] == "" or request_json['query'] is None else request_json['query'] + body["query"] = { + "query_string" : { + "query" : '!(data_type:\"tag\") AND ' + query, + "default_field" : "catch_all" + } + } + + # == sort + if request_json['sort_by'] != None: + order = "asc" if request_json['sort_by']['order'] == 0 else "desc" + body["sort"] = {request_json['sort_by']['name'] : {"order" : order}} + else: + body["sort"] = {'Data.@timestamp' : {"order" : 'asc'}} + + + # == fields + if request_json['fields'] != None : + body['_source'] = request_json['fields'].split(",") + + logger.logger(level=logger.DEBUG , type="api", message="Case["+case_id+"]: Query artifacts", reason=json.dumps(body)) + + + + # request the data from elasticsearch + res = db_es.query( case_id, body ) + if res[0] == False: + logger.logger(level=logger.ERROR , type="api", message="Case["+case_id+"]: Failed query artifacts from dataabase", reason=res[1]) + return json.dumps({'res_total' : 0 , 'res_records' : [] , 'aggs' : []}) + + + + return json.dumps({'data': res[1] , 'total': res[1]['hits']['total']['value']}) + diff --git a/app/controllers/admin_management.py b/app/controllers/admin_management.py index 1eb74a6e..53e45917 100644 --- a/app/controllers/admin_management.py +++ b/app/controllers/admin_management.py @@ -180,6 +180,9 @@ def get_folder_size(start_path = '.'): # ================================================= + + + # =================== Cases ======================= #call admin page will all content from content_management @app.route('/admin/') diff --git a/app/controllers/case_management.py b/app/controllers/case_management.py index ee7face9..d6a184a6 100644 --- a/app/controllers/case_management.py +++ b/app/controllers/case_management.py @@ -162,13 +162,13 @@ def upload_file(file , case_id , machine=None , base64_name=None): if isUploadMachine: # if the option is upload machine - machine_name = source_filename.rstrip('.zip') + machine_name = source_filename.rstrip('.zip').replace("/" , "_") machine_id = case_id + "_" + machine_name else: # if upload artifacts machine_id = machine - + try: # ======= validate machine exists or not # check if machine already exists @@ -289,7 +289,7 @@ def upload_file(file , case_id , machine=None , base64_name=None): if isUploadMachine: create_m = db_cases.add_machine({ 'main_case' : case_id, - 'machinename' : machine_name + 'machinename' : machine_name.replace("/" , "_") }) if create_m[0] == False: # if creating the machine failed @@ -604,7 +604,7 @@ def main_upload_artifacts(main_case_id,machine_case_id): def add_machine(case_id): if request.method == 'POST': machine_details = { - 'machinename' :request.form['machinename'], + 'machinename' :request.form['machinename'].replace("/" , "_"), 'main_case' :case_id, 'ip' :request.form['ip'], @@ -647,8 +647,16 @@ def delete_machine(case_id , machines_list): if es_machine: logger.logger(level=logger.INFO , type="case", message="Case["+case_id+"]: Machine ["+machine+"] deleted") - # delete all records for the machines - + # delete all files + try: + files_folder = app.config["UPLOADED_FILES_DEST"] + "/" + case_id + "/" + machine + "/" + raw_folder = app.config["UPLOADED_FILES_DEST_RAW"] + "/" + case_id + "/" + machine + "/" + shutil.rmtree(files_folder, ignore_errors=True) + shutil.rmtree(raw_folder, ignore_errors=True) + logger.logger(level=logger.DEBUG , type="case", message="Case["+case_id+"]: deleted machine folders ["+machine+"]" , reason=files_folder + "," + raw_folder) + except Exception as e: + logger.logger(level=logger.ERROR , type="case", message="Case["+case_id+"]: Failed deleting machine folders ["+machine+"]" , reason=str(e)) + return redirect(url_for('all_machines',case_id=case_id)) @@ -1061,7 +1069,6 @@ def case_add_tag_ajax(case_id): record_id = None ajax_data = json.loads(ajax_str)['data'] - Data = { "tag" : ajax_data['time'] , "@timestamp" : ajax_data['time'] @@ -1096,8 +1103,7 @@ def case_add_tag_ajax(case_id): logger.logger(level=logger.INFO , type="case", message="Case["+case_id+"]: Tag created") return json.dumps({"result" : 'successful' , 'tag_id' : up[3][0]}) - - + return json.dumps({"result" : 'successful' , 'tag_id' : up[3][0]}) # =================== Alerts ======================= diff --git a/app/controllers/parser_management.py b/app/controllers/parser_management.py index c9df5152..9104d08a 100644 --- a/app/controllers/parser_management.py +++ b/app/controllers/parser_management.py @@ -637,8 +637,8 @@ def convert_json_fields_to_str(self, json_data): k = str(k) # replace "." and " " in field name with "_" (to avoid elasticsearch issue) - if "." in k or " " in k: - k_tmp = k.replace("." , "_").replace(" " , "_") + if "." in k or " " in k or '/' in k: + k_tmp = k.replace("." , "_").replace(" " , "_").replace("/" , "_") json_data[k_tmp] = json_data[k] del json_data[k] k = k_tmp diff --git a/app/database/elkdb.py b/app/database/elkdb.py index e6c7df51..f88bc1c0 100644 --- a/app/database/elkdb.py +++ b/app/database/elkdb.py @@ -311,8 +311,8 @@ def bulk_queue_push(self, data , case_id , source = None , machine = None , data di['_source']['Data'] = d['Data'] if kjson else d source = d['data_source'] if kjson else source - data_type = d['data_type'] if kjson else source - data_path = d['data_path'] if kjson else source + data_type = d['data_type'] if kjson else data_type + data_path = d['data_path'] if kjson else data_path if source is not None: di['_source']['data_source'] = source diff --git a/app/parsers/BrowserHistory/configuration.json b/app/parsers/BrowserHistory/configuration.json index 825193db..41ec287b 100644 --- a/app/parsers/BrowserHistory/configuration.json +++ b/app/parsers/BrowserHistory/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "WebCacheV01.dat,History,places.sqlite", "important_field": [{"path": "type", "name": "Type"}, {"path": "browser_name", "name": "Browser"}, {"path": "link", "name": "Link"}], "name": "Browser_History", "interface_function": "BrowserHistory_interface.auto_browser_history", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.222626", "parser_type_field": "web_browser", "action": "edit", "parser_folder": "BrowserHistory", "_id": "Browser_History", "description": "Parser the browser history for (IE, Firefox, Chrome)"}] \ No newline at end of file +[{"parser_files_categorization_values": "WebCacheV01.dat,History,places.sqlite", "important_field": [{"path": "type", "name": "Type"}, {"path": "browser_name", "name": "Browser"}, {"path": "link", "name": "Link"}], "name": "Browser_History", "interface_function": "BrowserHistory_interface.auto_browser_history", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.716164", "parser_type_field": "web_browser", "action": "edit", "parser_folder": "BrowserHistory", "_id": "Browser_History", "description": "Parser the browser history for (IE, Firefox, Chrome)"}] \ No newline at end of file diff --git a/app/parsers/CertUtilParser/CryptnetUrlCacheParser.py b/app/parsers/CertUtilParser/CryptnetUrlCacheParser.py new file mode 100644 index 00000000..677bbf50 --- /dev/null +++ b/app/parsers/CertUtilParser/CryptnetUrlCacheParser.py @@ -0,0 +1,170 @@ +# This is a parser for the certutil cache files +# +# The following is the structure of the cache files: +# 12 bytes unknown +# 4 bytes (uint32) urlSize +# 8 bytes (uint64) FILETIME +# 76 bytes unkown +# 4 bytes (uint32) hashSize +# 8 bytes unkown +# 4 bytes (uint32) file size +# urlSize utf-16-le URL string +# hashSize utf-16-le MD5 hash string (Sometimes) +# ========================================= +# For more details refere to my blog post : https://u0041.co/blog/post/3 + + +import struct +import os +from datetime import datetime, timedelta +import hashlib + + +__author__ = "AbdulRhman Alfaifi" +__version__ = "1.1" +__maintainer__ = "AbdulRhman Alfaifi" +__license__ = "GPL" +__status__ = "Development" + + +class CertutilCacheParser: + def __init__(self,filePath): + self.filePath = filePath + if not os.path.isfile(self.filePath): + raise FileNotFoundError("The cache file '"+self.filePath+"' not found.") + + # Not used anymore. I was using this before I found hashSize and urlSize. + def ReadUTF16String(self,file): + string = b"" + while True: + c = struct.unpack("s",file.read(1))[0] + c2 = struct.unpack("s",file.read(1))[0] + if c == b"\x00" and c2 == b"\x00": + break + string+=c + return string.decode() + + # https://stackoverflow.com/questions/2150739/iso-time-iso-8601-in-python + def FILETIMEToISO(self,ft): + us = (ft - 116444736000000000) // 10 + dtObj = datetime(1970, 1, 1) + timedelta(microseconds = us) + return dtObj.isoformat() + + def MD5(self,fname): + try: + hash_md5 = hashlib.md5() + with open(fname, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + except: + return "00000000000000000000000000000000" + + + def Parse(self,useContent=True): + parsedData = {} + cacheFile = open(self.filePath,"rb") + header = struct.unpack("<12xIQ76xI8xI",cacheFile.read(116)) + urlSize = header[0] + timestamp = self.FILETIMEToISO(header[1]) + hashSize = header[2] + fileSize = header[3] + # Read url string with the urlSize-1 (ignore null byte). + try: + url = b"".join(struct.unpack(str(urlSize)+"c",cacheFile.read(urlSize))).decode("utf-16-le")[0:-1] + except: + return None + + + # Read hash string with the hashSize-1 (ignore null byte) and remove double quotation. + try: + hash = b"".join(struct.unpack(str(hashSize)+"c",cacheFile.read(hashSize))).decode("utf-16-le").replace('"','')[0:-1] + except: + hash = "Not Found" + + + parsedData.update({ + "Timestamp": timestamp, + "URL": url, + "FileSize": fileSize, + "MetadataHash": hash, + "FullPath": cacheFile.name, + }) + + # Check if the file exsistes in the Content folder. If it does then calculate the file MD5 hash. + if useContent: + contentFilePath = os.path.dirname(cacheFile.name)+"/../Content/{os.path.basename(cacheFile.name)}" + md5 = self.MD5(contentFilePath) + parsedData.update({"MD5": md5}) + + return parsedData + + +if __name__ == "__main__": + import glob + import argparse + import sys + import json + import csv + + certutilCachePaths = [ + "C:\\Windows\\System32\\config\\systemprofile\\AppData\\LocalLow\\Microsoft\\CryptnetUrlCache\\Metadata", + "C:\\Windows\\SysWOW64\\config\\systemprofile\\AppData\\LocalLow\\Microsoft\\CryptnetUrlCache\\Metadata" + ] + glob.glob("C:\\Users\\*\\AppData\\LocalLow\\Microsoft\\CryptnetUrlCache\\MetaData") + + parser = argparse.ArgumentParser(description='CryptnetUrlCache Metadata Parser - Developded by AbdulRhman Alfaifi') + parser.add_argument("-f","--files",nargs='+',help='A list of files that contain certutil cache files') + parser.add_argument("-d","--dirs",nargs='+',help='A list of dirs that contain certutil cache files (default: all certutil cache paths)',default=certutilCachePaths) + parser.add_argument("-o","--output",help='The file path to write the output to (default: stdout)',default=sys.stdout) + parser.add_argument("--outputFormat",help='The output formate (default: csv)',default="csv", choices=["csv","json","jsonl"]) + parser.add_argument("--useContent",action='store_true',help='Try finding the cached file and calculate the MD5 hash for it',default=False) + parser.add_argument("--noHeaders",action='store_true',help='Don\'t print headers when using CSV as the output format',default=False) + + args = parser.parse_args() + + if args.outputFormat == "csv": + if isinstance(args.output,str): + f = open(args.output,"w") + results = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC,lineterminator="\n") + else: + results = csv.writer(args.output, quoting=csv.QUOTE_NONNUMERIC,lineterminator="\n") + if args.useContent and not args.noHeaders: + results.writerow(["Timestamp","URL","FileSize","MetadataHash","FullPath", "MD5"]) + elif not args.noHeaders: + results.writerow(["Timestamp","URL","FileSize","MetadataHash","FullPath"]) + elif args.outputFormat == "json": + results = [] + else: + if isinstance(args.output,str): + results = open(args.output,"w") + else: + results = args.output + files = [] + if args.files: + files = args.files + else: + for path in args.dirs: + for root,dirs,all_files in os.walk(path): + for file in all_files: + fullPath = os.path.join(root,file) + files.append(fullPath) + + for file in files: + res = CertutilCacheParser(file).Parse(useContent=args.useContent) + if res: + if args.outputFormat == "csv": + if args.useContent: + results.writerow([res.get("Timestamp"),res.get("URL"),res.get("FileSize"),res.get("MetadataHash"),res.get("FullPath"), res.get("MD5")]) + else: + results.writerow([res.get("Timestamp"),res.get("URL"),res.get("FileSize"),res.get("MetadataHash"),res.get("FullPath")]) + elif args.outputFormat == "json": + results.append(res) + else: + results.write(json.dumps(res)+"\n") + if args.outputFormat == "json": + if isinstance(args.output,str): + out = open(args.output,"w") + else: + out = args.output + out.write(json.dumps(results)) + \ No newline at end of file diff --git a/app/parsers/CertUtilParser/__init__.py b/app/parsers/CertUtilParser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/CertUtilParser/configuration.json b/app/parsers/CertUtilParser/configuration.json new file mode 100644 index 00000000..5551f782 --- /dev/null +++ b/app/parsers/CertUtilParser/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": "70000000", "important_field": [{"path": "URL", "name": "URL"}, {"path": "FileSize", "name": "FileSize"}], "name": "CertUtilParser", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "magic_number", "creation_time": "2021-03-26T11:15:13.877150", "parser_type_field": "os_general", "action": "edit", "parser_folder": "CertUtilParser", "_id": "CertUtilParser", "description": "certutil cache parser"}] \ No newline at end of file diff --git a/app/parsers/CertUtilParser/interface.py b/app/parsers/CertUtilParser/interface.py new file mode 100644 index 00000000..df05ce08 --- /dev/null +++ b/app/parsers/CertUtilParser/interface.py @@ -0,0 +1,21 @@ +import os +import sys +import subprocess +import json + +def auto_interface(file,parser): + try: + fullpath = os.path.abspath(file) + CurrentPath=os.path.dirname(os.path.abspath(__file__)) + proc = subprocess.Popen('python3 "'+ CurrentPath+'/CryptnetUrlCacheParser.py" -f "' + file + '" --useContent --outputFormat jsonl', shell=True ,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + res = json.loads(proc.stdout.read()) + res["@timestamp"] = res.pop("Timestamp") + return [res] + except Exception as e: + exc_type,exc_obj,exc_tb = sys.exc_info() + msg = "[-] [Error] " + str(parser) + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + print(msg) + return (None , msg) + +if __name__ == "__main__": + print(json.dumps(auto_interface("6BADA8974A10C4BD62CC921D13E43B18_711ED44619924BA6DC33E69F97E7FF63","CertUtilParser"))) \ No newline at end of file diff --git a/app/parsers/IIS_Sites_Parser/Ichnaea_Sites_Parser.py b/app/parsers/IIS_Sites_Parser/Ichnaea_Sites_Parser.py new file mode 100644 index 00000000..d77dd61c --- /dev/null +++ b/app/parsers/IIS_Sites_Parser/Ichnaea_Sites_Parser.py @@ -0,0 +1,33 @@ +import sys +import os +import xml.etree.ElementTree as ET +import json +from collections import OrderedDict + + +class Ichnaea: + def __init__(self, file): + + self.IIS_Sites = self.get_IIS_Sites_details(file) + + def get_IIS_Sites_details(self, files): + tree = ET.parse(files) + root = tree.getroot() + virDirs = [] + for site in root.findall('./system.applicationHost/sites/'): + for app in site.findall('./'): + try: + for virDir in app.findall('./'): + if virDir.tag == "virtualDirectory": + siteDict = site.attrib + appDict = app.attrib + virDirDict = virDir.attrib + siteDict = {'Site ' + k: v for k, v in siteDict.items()} + virDirDict["Virtual Path"] = appDict["path"] + virDirDict["path"] + virDirDict["Physical Path"] = virDirDict.pop("physicalPath") + siteDict.update(appDict) + siteDict.update(virDirDict) + virDirs.append(OrderedDict(siteDict)) + except Exception as e: + print(e) + return virDirs \ No newline at end of file diff --git a/app/parsers/IIS_Sites_Parser/Ichnaea_interface.py b/app/parsers/IIS_Sites_Parser/Ichnaea_interface.py new file mode 100644 index 00000000..1f58d826 --- /dev/null +++ b/app/parsers/IIS_Sites_Parser/Ichnaea_interface.py @@ -0,0 +1,15 @@ +import sys +import Ichnaea_Sites_Parser as sites + +def Ichnaea_interface(file, parser): + try: + sites_obj = sites.Ichnaea(file) + return_data = [] + if sites_obj.IIS_Sites is not None: + return_data = sites_obj.IIS_Sites + return return_data + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + msg = "[-] [Error] " + parser + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + return (None, msg) \ No newline at end of file diff --git a/app/parsers/IIS_Sites_Parser/__init__.py b/app/parsers/IIS_Sites_Parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/IIS_Sites_Parser/configuration.json b/app/parsers/IIS_Sites_Parser/configuration.json new file mode 100644 index 00000000..31a52672 --- /dev/null +++ b/app/parsers/IIS_Sites_Parser/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": "applicationHost.config", "important_field": [{"path": "Site_name", "name": "Site Name"}, {"path": "Virtual_Path", "name": "Virtual Path"}, {"path": "Physical_Path", "name": "Physical Path"}], "name": "IIS_Sites", "interface_function": "Ichnaea_interface.Ichnaea_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.918769", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "IIS_Sites_Parser", "_id": "IIS_Sites", "description": "Extract IIS Sites details from applicationHost.config file (This is part of Ichnaea script)"}] \ No newline at end of file diff --git a/app/parsers/Ichnaea_compare/Ichnaea_compare.py b/app/parsers/Ichnaea_compare/Ichnaea_compare.py new file mode 100644 index 00000000..c64f8567 --- /dev/null +++ b/app/parsers/Ichnaea_compare/Ichnaea_compare.py @@ -0,0 +1,57 @@ +import os +import sys +import time +import difflib + +# _____ ____ __ __ __ _ ____ _____ ____ +# (_ _) / ___) ( \ / ) / \ / ) ( ) / ___/ ( ) +# | | / / \ (__) / / /\ \ / / / /\ \ ( (__ / /\ \ +# | | ( ( ) __ ( ) ) ) ) ) ) ( (__) ) ) __) ( (__) ) +# | | ( ( ( ( ) ) ( ( ( ( ( ( ) ( ( ( ) ( +# _| |__ \ \___ ) )( ( / / \ \/ / / /\ \ \ \___ / /\ \ +# /_____( \____) /_/ \_\ (_/ \__/ /__( )__\ \____\ /__( )__\ + +#[*] Version 1.0 +#[*] Compare IIS ApplicationHost files + + +class Ichnaea: + def __init__(self, file_path, history_path): + self.extenstion = "applicationHost.config" + self.config_files = self.list_dir(file_path) + self.history_files = self.list_dir(history_path) + self.config_files + self.compareResult=[] + rfile = range(len(self.history_files)-1) + count = 0 + countnew = 1 + for count in rfile: + self.get_diff_files(self.history_files[count],self.history_files[countnew]) + count+=1 + countnew = count + 1 + + # list csv file in a given path + def list_dir(self, path): + configFiles = [] + # with disable_file_system_redirection(): + for root, dirs, files in os.walk(path): + configFiles += [os.path.join(root, f) for f in files if f.endswith(self.extenstion)] + configFiles.sort() + if not configFiles: + print("No config files were found") + return configFiles + + # compare file from list of files + def get_diff_files(self , pre , new): + text1 = open(pre).readlines() + text2 = open(new).readlines() + for comp in difflib.unified_diff(text1, text2): + if comp.startswith('++') or comp.startswith('---'): + continue + elif comp.startswith('-'): + dic_data= {'History Filename' : new, 'Modification Time' :time.strftime( "%Y-%m-%d %H:%M:%S" ,time.gmtime(os.path.getmtime(new))), 'Removed Lines' : comp.strip().strip("- ")} + dic_data['@timestamp'] = dic_data['Modification Time'] + self.compareResult.append(dic_data) + elif comp.startswith('+'): + dic_data= {'History Filename' : new, 'Modification Time' :time.strftime( "%Y-%m-%d %H:%M:%S" ,time.gmtime(os.path.getmtime(new))), 'Added Lines' : comp.strip().strip('+ ')} + dic_data['@timestamp'] = dic_data['Modification Time'] + self.compareResult.append(dic_data) \ No newline at end of file diff --git a/app/parsers/Ichnaea_compare/Ichnaea_compare_interface.py b/app/parsers/Ichnaea_compare/Ichnaea_compare_interface.py new file mode 100644 index 00000000..523dfe2c --- /dev/null +++ b/app/parsers/Ichnaea_compare/Ichnaea_compare_interface.py @@ -0,0 +1,24 @@ +import sys +import os +import Ichnaea_compare as compare + +def Ichnaea_compare_interface(file, parser): + try: + file = os.path.dirname(os.path.abspath(file)) + parentPath=os.path.relpath(os.path.join(os.path.join(os.path.join(os.path.join(file, os.pardir),os.pardir),os.pardir),os.pardir)) + historyPath= parentPath + "/inetpub/history" + compare_obj = compare.Ichnaea(file, historyPath) + return_data = [] + if compare_obj.compareResult is not None: + return_data = compare_obj.compareResult + return return_data + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + msg = "[-] [Error] " + parser + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + return (None, msg) + + + + + diff --git a/app/parsers/Ichnaea_compare/__init__.py b/app/parsers/Ichnaea_compare/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/Ichnaea_compare/configuration.json b/app/parsers/Ichnaea_compare/configuration.json new file mode 100644 index 00000000..8cbf2cae --- /dev/null +++ b/app/parsers/Ichnaea_compare/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": "applicationHost.config", "important_field": [{"path": "Added_Lines", "name": "Added_Lines"}, {"path": "Removed_Lines", "name": "Removed_Lines"}], "name": "IIS_Compare", "interface_function": "Ichnaea_compare_interface.Ichnaea_compare_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.913759", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "Ichnaea_compare", "_id": "IIS_Compare", "description": "Get all IIS configuration history files (applicationHost.config) and compare the files to show all IIS configuration modifications (This is part of Ichnaea script)"}] \ No newline at end of file diff --git a/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_Parser.py b/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_Parser.py new file mode 100644 index 00000000..13fb4ec9 --- /dev/null +++ b/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_Parser.py @@ -0,0 +1,19 @@ +import sys +import os +import xml.etree.ElementTree as ET +import json +from collections import OrderedDict + + +class Ichnaea: + def __init__(self, file): + + self.IIS_modules = self.get_IIS_modules_details(file) + + def get_IIS_modules_details(self, files): + tree = ET.parse(files) + root = tree.getroot() + glopalModules = [] + for module in root.findall('./system.webServer/globalModules/'): + glopalModules.append(module.attrib) + return glopalModules \ No newline at end of file diff --git a/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_interface.py b/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_interface.py new file mode 100644 index 00000000..21253b4e --- /dev/null +++ b/app/parsers/Ichnaea_modules_parser/Ichnaea_modules_interface.py @@ -0,0 +1,15 @@ +import sys +import Ichnaea_modules_Parser as modules + +def Ichnaea_modules_interface(file, parser): + try: + sites_obj = modules.Ichnaea(file) + return_data = [] + if sites_obj.IIS_modules is not None: + return_data = sites_obj.IIS_modules + return return_data + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + msg = "[-] [Error] " + parser + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + return (None, msg) \ No newline at end of file diff --git a/app/parsers/Ichnaea_modules_parser/__init__.py b/app/parsers/Ichnaea_modules_parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/Ichnaea_modules_parser/configuration.json b/app/parsers/Ichnaea_modules_parser/configuration.json new file mode 100644 index 00000000..af029883 --- /dev/null +++ b/app/parsers/Ichnaea_modules_parser/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": "applicationHost.config", "important_field": [{"path": "name", "name": "Name"}, {"path": "image", "name": "Image"}], "name": "IIS_Modules", "interface_function": "Ichnaea_modules_interface.Ichnaea_modules_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.108026", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "Ichnaea_modules_parser", "_id": "IIS_Modules", "description": "Extract IIS Modules details from applicationHost.config file (This is part of Ichnaea script)"}] \ No newline at end of file diff --git a/app/parsers/JumpListLnk/configuration.json b/app/parsers/JumpListLnk/configuration.json index db96d070..4f8b8dd5 100644 --- a/app/parsers/JumpListLnk/configuration.json +++ b/app/parsers/JumpListLnk/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": ".automaticDestinations-ms,.customDestinations-ms", "important_field": [{"path": "Local_Path", "name": "Local Path"}, {"path": "AppDesc", "name": "AppDesc"}], "name": "JumpList", "interface_function": "JumpListLnk_interface.JLParser_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.288348", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "JumpListLnk", "_id": "JumpList", "description": "The jump list store the recently opened files by application in taskbar"}, {"parser_files_categorization_values": ".lnk", "important_field": [{"path": "Local_Path", "name": "Local_Path"}, {"path": "Source_Name", "name": "Source_Name"}], "name": "LNK", "interface_function": "JumpListLnk_interface.JLParser_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.291100", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "JumpListLnk", "_id": "LNK", "description": "An LNK file is a shortcut or \"link\" used by Windows as a reference to an original file, folder, or application."}] \ No newline at end of file +[{"parser_files_categorization_values": ".automaticDestinations-ms,.customDestinations-ms", "important_field": [{"path": "Local_Path", "name": "Local Path"}, {"path": "AppDesc", "name": "AppDesc"}], "name": "JumpList", "interface_function": "JumpListLnk_interface.JLParser_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:14.103218", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "JumpListLnk", "_id": "JumpList", "description": "The jump list store the recently opened files by application in taskbar"}, {"parser_files_categorization_values": ".lnk", "important_field": [{"path": "Local_Path", "name": "Local_Path"}, {"path": "Source_Name", "name": "Source_Name"}], "name": "LNK", "interface_function": "JumpListLnk_interface.JLParser_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:14.105649", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "JumpListLnk", "_id": "LNK", "description": "An LNK file is a shortcut or \"link\" used by Windows as a reference to an original file, folder, or application."}] \ No newline at end of file diff --git a/app/parsers/MFT_Parser/configuration.json b/app/parsers/MFT_Parser/configuration.json index e8ea820e..3ff39171 100644 --- a/app/parsers/MFT_Parser/configuration.json +++ b/app/parsers/MFT_Parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "$MFT,$MFTMirr", "important_field": [{"path": "IsADirectory", "name": "Directory"}, {"path": "FullPath", "name": "Path"}], "name": "MFT_parser", "interface_function": "mft_interface.MFT_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-11-01T02:58:49.302787", "parser_type_field": "os_general", "action": "edit", "parser_folder": "MFT_Parser", "_id": "MFT_parser", "description": "Parse Master File Table (MFT)"}] \ No newline at end of file +[{"parser_files_categorization_values": "$MFT,$MFTMirr", "important_field": [{"path": "IsADirectory", "name": "Directory"}, {"path": "FullPath", "name": "Path"}], "name": "MFT_parser", "interface_function": "mft_interface.MFT_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.904694", "parser_type_field": "os_general", "action": "edit", "parser_folder": "MFT_Parser", "_id": "MFT_parser", "description": "Parse Master File Table (MFT)"}] \ No newline at end of file diff --git a/app/parsers/MFT_Parser/mft_dump b/app/parsers/MFT_Parser/mft_dump old mode 100755 new mode 100644 diff --git a/app/parsers/PowerShellHistory/configuration.json b/app/parsers/PowerShellHistory/configuration.json index daf08753..8a362af5 100644 --- a/app/parsers/PowerShellHistory/configuration.json +++ b/app/parsers/PowerShellHistory/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "ConsoleHost_history.txt", "important_field": [{"path": "command", "name": "Command"}], "name": "PowerShellHistory", "interface_function": "PowerShellHistory.PowerShellHistory", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.239967", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "PowerShellHistory", "_id": "PowerShellHistory", "description": "PowerShell Console History, this stores the powershell commands executed per user, but it does not show the time.."}] \ No newline at end of file +[{"parser_files_categorization_values": "ConsoleHost_history.txt", "important_field": [{"path": "command", "name": "Command"}], "name": "PowerShellHistory", "interface_function": "PowerShellHistory.PowerShellHistory", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.886324", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "PowerShellHistory", "_id": "PowerShellHistory", "description": "PowerShell Console History, this stores the powershell commands executed per user, but it does not show the time.."}] \ No newline at end of file diff --git a/app/parsers/PyWMIPersistenceFinder/configuration.json b/app/parsers/PyWMIPersistenceFinder/configuration.json index bed28ee3..30882c4e 100644 --- a/app/parsers/PyWMIPersistenceFinder/configuration.json +++ b/app/parsers/PyWMIPersistenceFinder/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "OBJECTS.DATA", "important_field": [{"path": "filter_name", "name": "filter_name"}, {"path": "binding_name", "name": "binding_name"}, {"path": "consumer_name", "name": "consumer_name"}], "name": "WMI_Persistence", "interface_function": "WMI_Persistence_Interface.auto_wmi_persistence", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.233737", "parser_type_field": "autostart_locations", "action": "edit", "parser_folder": "PyWMIPersistenceFinder", "_id": "WMI_Persistence", "description": "show the WMI persistence"}] \ No newline at end of file +[{"parser_files_categorization_values": "OBJECTS.DATA", "important_field": [{"path": "filter_name", "name": "filter_name"}, {"path": "binding_name", "name": "binding_name"}, {"path": "consumer_name", "name": "consumer_name"}], "name": "WMI_Persistence", "interface_function": "WMI_Persistence_Interface.auto_wmi_persistence", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.863393", "parser_type_field": "autostart_locations", "action": "edit", "parser_folder": "PyWMIPersistenceFinder", "_id": "WMI_Persistence", "description": "show the WMI persistence"}] \ No newline at end of file diff --git a/app/parsers/RUA/configuration.json b/app/parsers/RUA/configuration.json index 5d79231d..6951f035 100644 --- a/app/parsers/RUA/configuration.json +++ b/app/parsers/RUA/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "OBJECTS.DATA", "important_field": [{"path": "FolderPath", "name": "Folder"}, {"path": "LastUserName", "name": "Username"}, {"path": "ExplorerFileName", "name": "File Name"}], "name": "RUA", "interface_function": "rua_interface.RUA_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.260233", "parser_type_field": "program_execution", "action": "add", "parser_folder": "RUA", "_id": "RUA", "description": "Parser for Recently Used Application (RUA) from (OBJECTS.DATA) "}] \ No newline at end of file +[{"parser_files_categorization_values": "OBJECTS.DATA", "important_field": [{"path": "FolderPath", "name": "Folder"}, {"path": "LastUserName", "name": "Username"}, {"path": "ExplorerFileName", "name": "File Name"}], "name": "RUA", "interface_function": "rua_interface.RUA_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.896005", "parser_type_field": "program_execution", "action": "add", "parser_folder": "RUA", "_id": "RUA", "description": "Parser for Recently Used Application (RUA) from (OBJECTS.DATA) "}] \ No newline at end of file diff --git a/app/parsers/RecentFileCache/__init__.py b/app/parsers/RecentFileCache/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/RecentFileCache/configuration.json b/app/parsers/RecentFileCache/configuration.json new file mode 100644 index 00000000..6bbad3b9 --- /dev/null +++ b/app/parsers/RecentFileCache/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": ".bcf", "important_field": [{"path": "Path", "name": "Path"}], "name": "RecentFileCache", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.908116", "parser_type_field": "program_execution", "action": "add", "parser_folder": "RecentFileCache", "_id": "RecentFileCache", "description": "This is a parser for .bcf files (amcache for old Windows version)"}] \ No newline at end of file diff --git a/app/parsers/RecentFileCache/interface.py b/app/parsers/RecentFileCache/interface.py new file mode 100644 index 00000000..c8d8b90d --- /dev/null +++ b/app/parsers/RecentFileCache/interface.py @@ -0,0 +1,53 @@ + +''' +The MIT License (MIT) + +Copyright (c) 2015 Patrick Olsen + + +''' + +import struct +import os +import json +import argparse +import sys + + +def imain(file, parser): + try: + with open(file, "rb") as f: + # Offset + offset = 0x14 + # Go to beginning of file. + f.seek(0) + # Checking if signature is correct + if (f.read(1) != b'\xfe'): + raise Exception("Not RCF File") + # Read forward 0x14 (20). + f.seek(offset) + res = [] + while (True): + read = f.read(1) + if not read: + break + # Reading 3 bytes + f.read(3) + rl = struct.unpack('>B', read)[0] + fnlen = (rl + 1) * 2 + foundpath = f.read(fnlen).replace("\x00" , "") + P =foundpath.split("\\") + data = { + 'Folder': "\\".join(P[:-1]) + "\\", + 'Path':foundpath, + 'FileName': P[-1] + } + res.append(data) + return res + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + msg = "[-] [Error] " + parser + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + return (None , msg) + + diff --git a/app/parsers/SCCM/configuration.json b/app/parsers/SCCM/configuration.json index 01459e2a..68ae8d4a 100644 --- a/app/parsers/SCCM/configuration.json +++ b/app/parsers/SCCM/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "mtrmgr", "important_field": [{"path": "file", "name": "file"}, {"path": "event", "name": "event"}], "name": "sccm_parser", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "startswith", "creation_time": "2020-10-29T06:42:58.249644", "parser_type_field": "program_execution", "action": "add", "parser_folder": "SCCM", "_id": "sccm_parser", "description": "This is to parse sccm logs mgmtr"}] \ No newline at end of file +[{"parser_files_categorization_values": "mtrmgr", "important_field": [{"path": "file", "name": "file"}, {"path": "event", "name": "event"}], "name": "sccm_parser", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "startswith", "creation_time": "2021-03-26T11:15:13.891103", "parser_type_field": "program_execution", "action": "add", "parser_folder": "SCCM", "_id": "sccm_parser", "description": "This is to parse sccm logs mgmtr"}] \ No newline at end of file diff --git a/app/parsers/UsnJrnl_parser/configuration.json b/app/parsers/UsnJrnl_parser/configuration.json index 3a0ab891..0e476e0a 100644 --- a/app/parsers/UsnJrnl_parser/configuration.json +++ b/app/parsers/UsnJrnl_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "$J", "important_field": [{"path": "filename", "name": "File"}, {"path": "reason", "name": "Action"}], "name": "UsnJernl", "interface_function": "usn_interface.UsnJrnl_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.268603", "parser_type_field": "logging_information", "action": "add", "parser_folder": "UsnJrnl_parser", "_id": "UsnJernl", "description": "UsnJrnl is where all changes (creation, modification, changes, etc.) occurred on the system for files and folders stored "}] \ No newline at end of file +[{"parser_files_categorization_values": "$J", "important_field": [{"path": "filename", "name": "File"}, {"path": "reason", "name": "Action"}], "name": "UsnJernl", "interface_function": "usn_interface.UsnJrnl_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.911167", "parser_type_field": "logging_information", "action": "add", "parser_folder": "UsnJrnl_parser", "_id": "UsnJernl", "description": "UsnJrnl is where all changes (creation, modification, changes, etc.) occurred on the system for files and folders stored "}] \ No newline at end of file diff --git a/app/parsers/WER/configuration.json b/app/parsers/WER/configuration.json index 2aa0e6c5..0aff3819 100644 --- a/app/parsers/WER/configuration.json +++ b/app/parsers/WER/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "wer", "important_field": [{"path": "AppPath", "name": "AppPath"}, {"path": "hash", "name": "Hash"}], "name": "WER", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.237319", "parser_type_field": "program_execution", "action": "add", "parser_folder": "WER", "_id": "WER", "description": "The WER (Windows Error Reporting) service is designed to collect the debug information about system and third-party software failures in Windows"}] \ No newline at end of file +[{"parser_files_categorization_values": "wer", "important_field": [{"path": "AppPath", "name": "AppPath"}, {"path": "hash", "name": "Hash"}], "name": "WER", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.882361", "parser_type_field": "program_execution", "action": "add", "parser_folder": "WER", "_id": "WER", "description": "The WER (Windows Error Reporting) service is designed to collect the debug information about system and third-party software failures in Windows"}] \ No newline at end of file diff --git a/app/parsers/WinEvents/configuration.json b/app/parsers/WinEvents/configuration.json index 02f0891f..7e6754a7 100644 --- a/app/parsers/WinEvents/configuration.json +++ b/app/parsers/WinEvents/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": ".evtx", "important_field": [{"path": "Event.System.EventID.#text", "name": "Event ID"}, {"path": "Event.System.Computer", "name": "Computer"}, {"path": "Event.System.Channel", "name": "Channel"}], "name": "Events", "interface_function": "EventParser_Interface.Events_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.229994", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "WinEvents", "_id": "Events", "description": "parse windows events files (.evtx)"}] \ No newline at end of file +[{"parser_files_categorization_values": ".evtx", "important_field": [{"path": "Event.System.EventID.#text", "name": "Event ID"}, {"path": "Event.System.Computer", "name": "Computer"}, {"path": "Event.System.Channel", "name": "Channel"}], "name": "Events", "interface_function": "EventParser_Interface.Events_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.720345", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "WinEvents", "_id": "Events", "description": "parse windows events files (.evtx)"}] \ No newline at end of file diff --git a/app/parsers/WinEvents/evtx_dump b/app/parsers/WinEvents/evtx_dump old mode 100755 new mode 100644 diff --git a/app/parsers/csv_parser/configuration.json b/app/parsers/csv_parser/configuration.json index 4460d10a..f3b670b6 100644 --- a/app/parsers/csv_parser/configuration.json +++ b/app/parsers/csv_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "csv", "important_field": [{"path": "null.0", "name": "f1"}, {"path": "null.1", "name": "f2"}, {"path": "null.2", "name": "f3"}], "name": "csv", "interface_function": "csv_parser.auto_csv_parser", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.243340", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "csv_parser", "_id": "csv", "description": "CSV parser, parse any csv file with values separated by comma (,), the first row must contain the columns name, other rows contain the values "}] \ No newline at end of file +[{"parser_files_categorization_values": "csv", "important_field": [{"path": "null.0", "name": "f1"}, {"path": "null.1", "name": "f2"}, {"path": "null.2", "name": "f3"}], "name": "csv", "interface_function": "csv_parser.auto_csv_parser", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.888752", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "csv_parser", "_id": "csv", "description": "CSV parser, parse any csv file with values separated by comma (,), the first row must contain the columns name, other rows contain the values "}] \ No newline at end of file diff --git a/app/parsers/prefetch_parser/configuration.json b/app/parsers/prefetch_parser/configuration.json index 8fab2266..1ef34594 100644 --- a/app/parsers/prefetch_parser/configuration.json +++ b/app/parsers/prefetch_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "pf", "important_field": [{"path": "last_run", "name": "Last Run"}, {"path": "prefetch_file", "name": "File"}, {"path": "Run_Count", "name": "Run Count"}], "name": "Prefetch", "interface_function": "prefetch_interface.prefetch_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.297723", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "prefetch_parser", "_id": "Prefetch", "description": "Feature that stores specific data about the applications you run in order to help them start faster"}] \ No newline at end of file +[{"parser_files_categorization_values": "pf", "important_field": [{"path": "last_run", "name": "Last Run"}, {"path": "prefetch_file", "name": "File"}, {"path": "Run_Count", "name": "Run Count"}], "name": "Prefetch", "interface_function": "prefetch_interface.prefetch_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:14.114579", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "prefetch_parser", "_id": "Prefetch", "description": "Feature that stores specific data about the applications you run in order to help them start faster"}] \ No newline at end of file diff --git a/app/parsers/recyclebin_parser/configuration.json b/app/parsers/recyclebin_parser/configuration.json index d282d624..6b8e562a 100644 --- a/app/parsers/recyclebin_parser/configuration.json +++ b/app/parsers/recyclebin_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "$I", "important_field": [{"path": "Path", "name": "Path"}], "name": "Recyclebin", "interface_function": "recyclebin_interface.recyclebin_interface", "parser_files_categorization_type": "startswith", "creation_time": "2020-10-29T06:42:58.263085", "parser_type_field": "os_general", "action": "add", "parser_folder": "recyclebin_parser", "_id": "Recyclebin", "description": "parse deleted files (Recyclebin $I) and show its information"}] \ No newline at end of file +[{"parser_files_categorization_values": "$I", "important_field": [{"path": "Path", "name": "Path"}], "name": "Recyclebin", "interface_function": "recyclebin_interface.recyclebin_interface", "parser_files_categorization_type": "startswith", "creation_time": "2021-03-26T11:15:13.899561", "parser_type_field": "os_general", "action": "add", "parser_folder": "recyclebin_parser", "_id": "Recyclebin", "description": "parse deleted files (Recyclebin $I) and show its information"}] \ No newline at end of file diff --git a/app/parsers/regsk/configuration.json b/app/parsers/regsk/configuration.json index 85fe1953..8ffdb3fb 100644 --- a/app/parsers/regsk/configuration.json +++ b/app/parsers/regsk/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "hve,bcf", "important_field": [{"path": "Sha1", "name": "SHA1"}, {"path": "Path", "name": "Path"}], "name": "Amcache", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.279490", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Amcache", "description": "This will parse the binaries excuted and logged through Amcache hive."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Key_Timestamp", "name": "KeyTimestamp"}, {"path": "File", "name": "File"}], "name": "AppCompatFlags", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.283619", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "AppCompatFlags", "description": "Parse the application compatibility logs that stored into the registery under the AppCompantFlags key. "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "name", "name": "FileName"}], "name": "Bam", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.287833", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Bam", "description": "It parses the background applications created by Windows service that Controls activity applications. "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "ComputerName", "name": "ComputerName"}], "name": "ComputerName", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.308814", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ComputerName", "description": "parse the Computer Name"}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Dhcp_IP_Address", "name": "IPAddress"}, {"path": "Dhcp_Server", "name": "DHCPServer"}], "name": "DHCP", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.312670", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "DHCP", "description": "Parse all loged IP addresses through DHCP"}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Application_Name", "name": "Application"}, {"path": "Path", "name": "Path"}], "name": "InstalledApp", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.316094", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "InstalledApp", "description": "parse all installed applications in a machine."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "StubPath", "name": "StubPath"}, {"path": "LocalizedName", "name": "LocalizedName"}, {"path": "ComponentID", "name": "ComponentID"}], "name": "InstalledComponents", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.319244", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "InstalledComponents", "description": "parse all installed software of machine"}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "File_name", "name": "File_name"}, {"path": "path", "name": "path"}], "name": "LastVisitedMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.321584", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "LastVisitedMRU", "description": "Tracks the specific executable used by an application to open the files documented in the OpenSaveMRU key."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "name", "name": "name"}], "name": "LaunchTracing", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.324166", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "LaunchTracing", "description": "Parse list of app that can be traced."}, {"parser_files_categorization_values": "UsrClass.dat", "important_field": [{"path": "name", "name": "File"}], "name": "MuiCache", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.326700", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "MuiCache", "description": "Parse,view and edit the list of all MuiCache items of machine.When using a new application, Windows operating system automatically extract the application name from the version resource of the exe file, and stores it for using it later, in Registry key known as the 'MuiCache'. "}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "path", "name": "path"}], "name": "OpenSaveMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2020-10-29T06:42:58.329263", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "regsk", "_id": "OpenSaveMRU", "description": "parse the files names that have been opened or saved within a Windows shell dialog box."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Path", "name": "Path"}, {"path": "User", "name": "User"}], "name": "ProfileList", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.336652", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ProfileList", "description": "parse all users names from the System hive "}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Command", "name": "Command"}], "name": "RunMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.344177", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "RunMRU", "description": "Parse The history of most recently used commands from the Run command ."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "File_Name", "name": "File Name"}], "name": "ShellExtensions", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.354319", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ShellExtensions", "description": "it parses Shell Extensions that sometimes used for malwares persistence."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Application_Name", "name": "ApplicationName"}, {"path": "Accepted_TimeStamp", "name": "AcceptedTimeStamp"}], "name": "Sysinternals", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.363843", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Sysinternals", "description": "Parse all Sysinternals tool accepted date."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "IP_Address", "name": "IPAddress"}, {"path": "User_Name", "name": "UserName"}], "name": "TerminalServerClient", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.376627", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "TerminalServerClient", "description": "This will parse the IP the host connected to using RDP protocol "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Time_Zone", "name": "Time Zone"}], "name": "TimeZoneInformation", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.438979", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "TimeZoneInformation", "description": "Parse the Time Zone Information for the machine."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Url", "name": "Url"}], "name": "TypedUrls", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.565794", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "TypedUrls", "description": "Parses all Urls typed into the Browsers."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Publisher", "name": "Publisher"}, {"path": "Application_Name", "name": "Application"}, {"path": "name", "name": "Name"}], "name": "Uninstall", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.568250", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "Uninstall", "description": "parse all uninstalled software of machine"}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "name", "name": "File"}], "name": "UserAssist", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.570896", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "UserAssist", "description": "it shows what programs were recently executed on a system.UserAssist data will include information on whether an application was run from a shortcut (LNK file) or directly from the executable."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Key_Timestamp", "name": "KeyTimestamp"}, {"path": "Url", "name": "Word"}], "name": "WordWheelQuery", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.573594", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "WordWheelQuery", "description": "This will parse the record information about user searches into the WordWheel."}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Type", "name": "Type"}, {"path": "Name", "name": "Name"}, {"path": "ImagePath", "name": "ImagePath"}], "name": "Services", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.583565", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Services", "description": "Windows services created to run services (executables) in the background."}] \ No newline at end of file +[{"parser_files_categorization_values": "hve", "important_field": [{"path": "Sha1", "name": "SHA1"}, {"path": "Path", "name": "Path"}], "name": "Amcache", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.932954", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Amcache", "description": "This will parse the binaries excuted and logged through Amcache hive."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Key_Timestamp", "name": "KeyTimestamp"}, {"path": "File", "name": "File"}], "name": "AppCompatFlags", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.936864", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "AppCompatFlags", "description": "Parse the application compatibility logs that stored into the registery under the AppCompantFlags key. "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "name", "name": "FileName"}], "name": "Bam", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.940968", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Bam", "description": "It parses the background applications created by Windows service that Controls activity applications. "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "ComputerName", "name": "ComputerName"}], "name": "ComputerName", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.947415", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ComputerName", "description": "parse the Computer Name"}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Dhcp_IP_Address", "name": "IPAddress"}, {"path": "Dhcp_Server", "name": "DHCPServer"}], "name": "DHCP", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.957865", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "DHCP", "description": "Parse all loged IP addresses through DHCP"}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Application_Name", "name": "Application"}, {"path": "Path", "name": "Path"}], "name": "InstalledApp", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.964635", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "InstalledApp", "description": "parse all installed applications in a machine."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "StubPath", "name": "StubPath"}, {"path": "LocalizedName", "name": "LocalizedName"}, {"path": "ComponentID", "name": "ComponentID"}], "name": "InstalledComponents", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.969461", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "InstalledComponents", "description": "parse all installed software of machine"}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "File_name", "name": "File_name"}, {"path": "path", "name": "path"}], "name": "LastVisitedMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.981972", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "LastVisitedMRU", "description": "Tracks the specific executable used by an application to open the files documented in the OpenSaveMRU key."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "name", "name": "name"}], "name": "LaunchTracing", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.985498", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "LaunchTracing", "description": "Parse list of app that can be traced."}, {"parser_files_categorization_values": "UsrClass.dat", "important_field": [{"path": "name", "name": "File"}], "name": "MuiCache", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.990749", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "MuiCache", "description": "Parse,view and edit the list of all MuiCache items of machine.When using a new application, Windows operating system automatically extract the application name from the version resource of the exe file, and stores it for using it later, in Registry key known as the 'MuiCache'. "}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "path", "name": "path"}], "name": "OpenSaveMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.995584", "parser_type_field": "logging_information", "action": "edit", "parser_folder": "regsk", "_id": "OpenSaveMRU", "description": "parse the files names that have been opened or saved within a Windows shell dialog box."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Path", "name": "Path"}, {"path": "User", "name": "User"}], "name": "ProfileList", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.001529", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ProfileList", "description": "parse all users names from the System hive "}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Command", "name": "Command"}], "name": "RunMRU", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.011389", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "RunMRU", "description": "Parse The history of most recently used commands from the Run command ."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "File_Name", "name": "File Name"}], "name": "ShellExtensions", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.018152", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "ShellExtensions", "description": "it parses Shell Extensions that sometimes used for malwares persistence."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Application_Name", "name": "ApplicationName"}, {"path": "Accepted_TimeStamp", "name": "AcceptedTimeStamp"}], "name": "Sysinternals", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.021825", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Sysinternals", "description": "Parse all Sysinternals tool accepted date."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "IP_Address", "name": "IPAddress"}, {"path": "User_Name", "name": "UserName"}], "name": "TerminalServerClient", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.024919", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "TerminalServerClient", "description": "This will parse the IP the host connected to using RDP protocol "}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Time_Zone", "name": "Time Zone"}], "name": "TimeZoneInformation", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.030130", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "TimeZoneInformation", "description": "Parse the Time Zone Information for the machine."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Url", "name": "Url"}], "name": "TypedUrls", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.040557", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "TypedUrls", "description": "Parses all Urls typed into the Browsers."}, {"parser_files_categorization_values": "SOFTWARE", "important_field": [{"path": "Publisher", "name": "Publisher"}, {"path": "Application_Name", "name": "Application"}, {"path": "name", "name": "Name"}], "name": "Uninstall", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.055173", "parser_type_field": "os_general", "action": "edit", "parser_folder": "regsk", "_id": "Uninstall", "description": "parse all uninstalled software of machine"}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "name", "name": "File"}], "name": "UserAssist", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.062392", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "UserAssist", "description": "it shows what programs were recently executed on a system.UserAssist data will include information on whether an application was run from a shortcut (LNK file) or directly from the executable."}, {"parser_files_categorization_values": "NTUSER.DAT", "important_field": [{"path": "Key_Timestamp", "name": "KeyTimestamp"}, {"path": "Url", "name": "Word"}], "name": "WordWheelQuery", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.074731", "parser_type_field": "user_activities", "action": "edit", "parser_folder": "regsk", "_id": "WordWheelQuery", "description": "This will parse the record information about user searches into the WordWheel."}, {"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Type", "name": "Type"}, {"path": "Name", "name": "Name"}, {"path": "ImagePath", "name": "ImagePath"}], "name": "Services", "interface_function": "interface.auto_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:14.100401", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "regsk", "_id": "Services", "description": "Windows services created to run services (executables) in the background."}] \ No newline at end of file diff --git a/app/parsers/regsk/interface.py b/app/parsers/regsk/interface.py index 180efdf8..a1f70f59 100644 --- a/app/parsers/regsk/interface.py +++ b/app/parsers/regsk/interface.py @@ -13,8 +13,6 @@ def auto_interface(file,parser): cmd = 'python3 '+ CurrentPath+'/regsk.py -k -f "' + file.replace("$" , '\$') + '" -pl ' + parser proc = subprocess.Popen(cmd, shell=True ,stdin=None , stdout=subprocess.PIPE , stderr=subprocess.PIPE) res , err = proc.communicate() - #print res - #print err if err != "": raise Exception(err.split("\n")[-2]) @@ -45,3 +43,4 @@ def auto_interface(file,parser): + diff --git a/app/parsers/regsk/plugins/Amcache.py b/app/parsers/regsk/plugins/Amcache.py index 5c6df796..17620ba2 100644 --- a/app/parsers/regsk/plugins/Amcache.py +++ b/app/parsers/regsk/plugins/Amcache.py @@ -46,7 +46,10 @@ def run(self): "BinProductVersion" :"BinProductVersion", "Size" :"FileSize", "Language" :"Language", - "IsPeFile" :"IsPeFile" + "IsPeFile" :"IsPeFile", + "OriginalFileName" :"OriginalFileName", + "AppxPackageFullName":"AppxPackageFullName", + "AppxPackageRelativeId":"AppxPackageRelativeId" } record = OrderedDict([ ]) diff --git a/app/parsers/scheduled_task/configuration.json b/app/parsers/scheduled_task/configuration.json index 2573a631..d66c7838 100644 --- a/app/parsers/scheduled_task/configuration.json +++ b/app/parsers/scheduled_task/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "FFFE3C003F0078006D006C002000760065007200730069006F006E003D00220031002E0030002200200065006E0063006F00640069006E0067003D0022005500540046002D003100360022003F003E000D000A003C005400610073006B00", "important_field": [{"path": "triggers.0.Type", "name": "Trigger"}, {"path": "author", "name": "Author"}, {"path": "uri", "name": "uri"}], "name": "Scheduled_Task", "interface_function": "scheduled_task_interface.scheduled_task_interface", "parser_files_categorization_type": "magic_number", "creation_time": "2020-10-29T06:42:58.294435", "parser_type_field": "os_general", "action": "add", "parser_folder": "scheduled_task", "_id": "Scheduled_Task", "description": "Parse Windows scheduled tasks"}] \ No newline at end of file +[{"parser_files_categorization_values": "FFFE3C003F0078006D006C002000760065007200730069006F006E003D00220031002E0030002200200065006E0063006F00640069006E0067003D0022005500540046002D003100360022003F003E000D000A003C005400610073006B00", "important_field": [{"path": "triggers.0.Type", "name": "Trigger"}, {"path": "author", "name": "Author"}, {"path": "uri", "name": "uri"}], "name": "Scheduled_Task", "interface_function": "scheduled_task_interface.scheduled_task_interface", "parser_files_categorization_type": "magic_number", "creation_time": "2021-03-26T11:15:14.110999", "parser_type_field": "os_general", "action": "add", "parser_folder": "scheduled_task", "_id": "Scheduled_Task", "description": "Parse Windows scheduled tasks"}] \ No newline at end of file diff --git a/app/parsers/shellbags/configuration.json b/app/parsers/shellbags/configuration.json index e5624f16..63ba2c25 100644 --- a/app/parsers/shellbags/configuration.json +++ b/app/parsers/shellbags/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "NTUSER.DAT,UsrClass.dat", "important_field": [{"path": "path", "name": "Path"}], "name": "Shellbags", "interface_function": "shellbags_interface.auto_shellbags", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.257377", "parser_type_field": "user_activities", "action": "add", "parser_folder": "shellbags", "_id": "Shellbags", "description": "Shellbags store views, sizes and positions of a folder window when viewed through Windows Explorer"}] \ No newline at end of file +[{"parser_files_categorization_values": "NTUSER.DAT,UsrClass.dat", "important_field": [{"path": "path", "name": "Path"}], "name": "Shellbags", "interface_function": "shellbags_interface.auto_shellbags", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.893072", "parser_type_field": "user_activities", "action": "add", "parser_folder": "shellbags", "_id": "Shellbags", "description": "Shellbags store views, sizes and positions of a folder window when viewed through Windows Explorer"}] \ No newline at end of file diff --git a/app/parsers/shimcache_parser/configuration.json b/app/parsers/shimcache_parser/configuration.json index dea412ad..e66c9135 100644 --- a/app/parsers/shimcache_parser/configuration.json +++ b/app/parsers/shimcache_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Exec_Flag", "name": "Exec_Flag"}, {"path": "File_Name", "name": "File_Name"}, {"path": "Path", "name": "Path"}], "name": "Shimcache", "interface_function": "shimcache_interface.shimcache_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.177091", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "shimcache_parser", "_id": "Shimcache", "description": "Shimcache store application compatibility issues, help for identifying executable paths, executed or not, last modified, etc."}] \ No newline at end of file +[{"parser_files_categorization_values": "SYSTEM", "important_field": [{"path": "Exec_Flag", "name": "Exec_Flag"}, {"path": "File_Name", "name": "File_Name"}, {"path": "Path", "name": "Path"}], "name": "Shimcache", "interface_function": "shimcache_interface.shimcache_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.692721", "parser_type_field": "program_execution", "action": "edit", "parser_folder": "shimcache_parser", "_id": "Shimcache", "description": "Shimcache store application compatibility issues, help for identifying executable paths, executed or not, last modified, etc."}] \ No newline at end of file diff --git a/app/parsers/srum_parser/configuration.json b/app/parsers/srum_parser/configuration.json index 0ef6eb96..96ddf99d 100644 --- a/app/parsers/srum_parser/configuration.json +++ b/app/parsers/srum_parser/configuration.json @@ -1 +1 @@ -[{"parser_files_categorization_values": "SRUDB.dat", "important_field": [{"path": "SRUM_Type", "name": "Info Type"}, {"path": "UserName", "name": "Username"}, {"path": "App", "name": "Application"}], "name": "SRUM", "interface_function": "srum_interface.SRUM_interface", "parser_files_categorization_type": "file_name", "creation_time": "2020-10-29T06:42:58.226565", "parser_type_field": "program_execution", "action": "add", "parser_folder": "srum_parser", "_id": "SRUM", "description": "parse Windows System Resource Usage Monitor (SRUM)"}] \ No newline at end of file +[{"parser_files_categorization_values": "SRUDB.dat", "important_field": [{"path": "SRUM_Type", "name": "Info Type"}, {"path": "UserName", "name": "Username"}, {"path": "App", "name": "Application"}], "name": "SRUM", "interface_function": "srum_interface.SRUM_interface", "parser_files_categorization_type": "file_name", "creation_time": "2021-03-26T11:15:13.718321", "parser_type_field": "program_execution", "action": "add", "parser_folder": "srum_parser", "_id": "SRUM", "description": "parse Windows System Resource Usage Monitor (SRUM)"}] \ No newline at end of file diff --git a/app/parsers/srum_parser/srum.py b/app/parsers/srum_parser/srum.py index c17a8b34..bcb4164a 100644 --- a/app/parsers/srum_parser/srum.py +++ b/app/parsers/srum_parser/srum.py @@ -324,7 +324,7 @@ class SRUM_Parser(): def __init__(self , ese_file): self.ese_db = pyesedb.open(ese_file) - + print "here" self.GUID_tables = { 'SruDbIdMapTable' : 'SruDbIdMapTable', 'NetworkDataUsageMonitor' : '{973F5D5C-1D90-4944-BE8E-24B94231A174}', diff --git a/app/parsers/srum_parser/srum_interface.py b/app/parsers/srum_parser/srum_interface.py index c2b0d869..a541ce01 100644 --- a/app/parsers/srum_parser/srum_interface.py +++ b/app/parsers/srum_parser/srum_interface.py @@ -18,3 +18,6 @@ def SRUM_interface(file, parser): msg = "[-] [Error] " + parser + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) return (None , msg) + + +print SRUM_interface("/home/kuiper/kuiper/files/sr2008015/sr2008015_V28-0046R3-BC01/2020-12-18T15:15:44-V28-0046R3-BC01.zip//PhysicalDrive0_1/srum/Windows/System32/sru/SRUDB.dat" , 'SRUM') \ No newline at end of file diff --git a/app/parsers/vol_Parser/LICENSE.txt b/app/parsers/vol_Parser/LICENSE.txt new file mode 100644 index 00000000..96f22218 --- /dev/null +++ b/app/parsers/vol_Parser/LICENSE.txt @@ -0,0 +1,46 @@ +Volatility Software License +Version 1.0 dated October 3, 2019. +This license covers the Volatility software, Copyright 2019 Volatility Foundation. + +Software +The software referred to in this license includes the software named above, and any data (such as operating system profiles or configuration information), and documentation provided with the software. + +Purpose +This license gives you permission to use, share, and build with this software for free, but requires you to share source code for changes, additions, and software that you build with it. + +Acceptance +In order to receive this license, you must agree to its rules. The rules of this license are both obligations under that agreement and conditions to your license. You must not do anything with this software that triggers a rule that you cannot or will not follow. + +Copyright +Each contributor licenses you to do everything with this software that would otherwise infringe that contributor's copyright in it. + +Notices +You must ensure that everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license or a link to https://www.volatilityfoundation.org/license/vsl-v1.0. You must not remove any copyright notice in the Software. + +Patent +Each contributor licenses you to do everything with this software that would otherwise infringe any patent claims they can license or become able to license. + +Reliability +No contributor can revoke this license. + +Copyleft +If you make any Additions available to others, such as by providing copies of them or providing access to them over the Internet, you must make them publicly available, according to this paragraph. "Additions" includes changes or additions to the software, and any content or materials, including any training materials, you create that contain any portion of the software. "Additions" also includes any translations or ports of the software. "Additions" also includes any software designed to execute the software and parse its results, such as a wrapper written for the software, but does not include shell or execution menu software designed to execute software generally. When this license requires you to make Additions available: + +- You must publish all source code for software under this license, in the preferred form for making changes, through a freely accessible distribution system widely used for similar source code, so the developer and others can find and copy it. +- You must publish all data or content under this license, in a format customarily used to make changes to it, through a freely accessible distribution system, so the developer and others can find and copy it. +- You are responsible to ensure you have rights in Additions necessary to comply with this section. + +Contributing +If you contribute (or offer to contribute) any materials to Volatility Foundation for the software, such as by submitting a pull request to the repository for the software or related content run by Volatility Foundation, you agree to contribute them under the under the BSD 2-Clause Plus Patent License (in the case of software) or the Creative Commons Zero Public Domain Dedication (in the case of content), unless you clearly mark them "Not a Contribution." + +Trademarks +This license grants you no rights to any trademarks or service marks. + +Termination +If you violate any term of this license, your license ends immediately. + +No Liability +As far as the law allows, the software comes as is, without any warranty or condition, and no contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim. + +Versions +Volatility Foundation is the steward of this license and may publish new versions of this license with new version numbers. You may use the software under the version of this license under which you received the software, or, at your choice, any later version. diff --git a/app/parsers/vol_Parser/MANIFEST.in b/app/parsers/vol_Parser/MANIFEST.in new file mode 100644 index 00000000..4e5bbf6a --- /dev/null +++ b/app/parsers/vol_Parser/MANIFEST.in @@ -0,0 +1,6 @@ +prune development +include * .* +include doc/make.bat doc/Makefile +recursive-include doc/source * +recursive-include volatility *.json +recursive-exclude doc/source volatility*.rst diff --git a/app/parsers/vol_Parser/README.md b/app/parsers/vol_Parser/README.md new file mode 100644 index 00000000..1da1d4bd --- /dev/null +++ b/app/parsers/vol_Parser/README.md @@ -0,0 +1,131 @@ +# Volatility 3: The volatile memory extraction framework + +Volatility is the world’s most widely used framework for extracting digital +artifacts from volatile memory (RAM) samples. The extraction techniques are +performed completely independent of the system being investigated but offer +visibility into the runtime state of the system. The framework is intended +to introduce people to the techniques and complexities associated with +extracting digital artifacts from volatile memory samples and provide a +platform for further work into this exciting area of research. + +In 2019, the Volatility Foundation released a complete rewrite of the +framework, Volatility 3. The project was intended to address many of the +technical and performance challenges associated with the original +code base that became apparent over the previous 10 years. Another benefit +of the rewrite is that Volatility 3 could be released under a custom +license that was more aligned with the goals of the Volatility community, +the Volatility Software License (VSL). See the [LICENSE](LICENSE.txt) file for more details. + +## Requirements + +- Python 3.5.3 or later. +- Pefile 2017.8.1 or later. + +## Optional Dependencies + +- yara-python 3.8.0 or later. +- capstone 3.0.0 or later. + +## Downloading Volatility + +The latest stable version of Volatility will always be the master branch of the GitHub repository. You can get the latest version of the code using the following command: + +```shell +git clone https://github.com/volatilityfoundation/volatility3.git +``` + +## Quick Start + +1. Clone the latest version of Volatility from GitHub: + + ```shell + git clone https://github.com/volatilityfoundation/volatility3.git + ``` + +2. See available options: + + ```shell + python3 vol.py —h + ``` + +3. To get more information on a Windows memory sample and to make sure +Volatility supports that sample type, run +`python3 vol.py -f windows.info` + + Example: + + ```shell + python3 vol.py —f /home/user/samples/stuxnet.vmem windows.info + ``` + +4. Run some other plugins. The `-f` or `—-single-location` is not strictly +required, but most plugins expect a single sample. Some also +require/accept other options. Run `python3 vol.py -h` +for more information on a particular command. + +## Symbol Tables + +Symbol table packs for the various operating systems are available for download at: + + + + + +The hashes to verify whether any of the symbol pack files have downloaded successfully or have changed can be found at: + + + + + +Symbol tables zip files must be placed, as named, into the `volatility/symbols` directory (or just the symbols directory next to the executable file). + +Windows symbols that cannot be found will be queried, downloaded, generated and cached. Mac and Linux symbol tables must be manually produced by a tool such as [dwarf2json](https://github.com/volatilityfoundation/dwarf2json). + +Please note: These are representative and are complete up to the point of creation for Windows and Mac. Due to the ease of compiling Linux kernels and the inability to uniquely distinguish them, an exhaustive set of Linux symbol tables cannot easily be supplied. + +## Documentation + +The framework is documented through doc strings and can be built using sphinx. + +The latest generated copy of the documentation can be found at: + +## Licensing and Copyright + +Copyright (C) 2007-2020 Volatility Foundation + +All Rights Reserved + + + +## Bugs and Support + +If you think you've found a bug, please report it at: + + + +In order to help us solve your issues as quickly as possible, +please include the following information when filing a bug: + +- The version of Volatility you're using +- The operating system used to run Volatility +- The version of Python used to run Volatility +- The suspected operating system of the memory sample +- The complete command line you used to run Volatility + +For community support, please join us on Slack: + + + +## Contact + +For information or requests, contact: + +Volatility Foundation + +Web: + +Blog: + +Email: volatility (at) volatilityfoundation (dot) org + +Twitter: [@volatility](https://twitter.com/volatility) diff --git a/app/parsers/vol_Parser/__init__.py b/app/parsers/vol_Parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/vol_Parser/configuration.json b/app/parsers/vol_Parser/configuration.json new file mode 100644 index 00000000..5cefbf07 --- /dev/null +++ b/app/parsers/vol_Parser/configuration.json @@ -0,0 +1 @@ +[{"parser_files_categorization_values": ".mem", "important_field": [{"path": "Major_Minor", "name": "Version"}, {"path": "NtSystemRoot", "name": "SystemRoot"}], "name": "mem_info", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.724540", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_info", "description": "Get the OS information from memory dump"}, {"important_field": [{"path": "PID", "name": "PID"}, {"path": "Variable", "name": "Variable"}, {"path": "Value", "name": "Value"}], "parser_files_categorization_values": ".mem", "description": "List the environment variables for processes", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.734512", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_envars", "name": "mem_envars"}, {"important_field": [], "parser_files_categorization_values": ".mem", "description": "Get all opened files from the memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.759490", "parser_type_field": "memory", "action": "add", "parser_folder": "vol_Parser", "_id": "mem_FileScan", "name": "mem_FileScan"}, {"important_field": [{"path": "Process", "name": "Process"}, {"path": "Name", "name": "Name"}], "parser_files_categorization_values": ".mem", "description": "Get all processes and SID of user", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.765441", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_ProcessSID", "name": "mem_ProcessSID"}, {"important_field": [], "parser_files_categorization_values": ".mem", "description": "List all object handles from memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.773492", "parser_type_field": "memory", "action": "add", "parser_folder": "vol_Parser", "_id": "mem_handles", "name": "mem_handles"}, {"important_field": [{"path": "Name", "name": "Name"}, {"path": "Path", "name": "Path"}], "parser_files_categorization_values": ".mem", "description": "Scan for all modules from memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.776892", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_ModScan", "name": "mem_ModScan"}, {"important_field": [{"path": "Name", "name": "Name"}, {"path": "Path", "name": "Path"}], "parser_files_categorization_values": ".mem", "description": "Get all loaded modules", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.784791", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_Modules", "name": "mem_Modules"}, {"important_field": [{"path": "Name", "name": "Name"}], "parser_files_categorization_values": ".mem", "description": "List all mutant objects", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.788737", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_mutantScan", "name": "mem_mutantScan"}, {"important_field": [{"path": "PID", "name": "PID"}, {"path": "State", "name": "State"}, {"path": "ForeignAddr", "name": "ForeignAddr"}], "parser_files_categorization_values": ".mem", "description": "Scan for network sessions", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.796875", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_netScan", "name": "mem_netScan"}, {"important_field": [{"path": "PID", "name": "PID"}, {"path": "Privilege", "name": "Privilege"}], "parser_files_categorization_values": ".mem", "description": "Get all processes and assigned privs for it", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.799682", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_ProcessPrivs", "name": "mem_ProcessPrivs"}, {"important_field": [{"path": "FileFullPath", "name": "Path"}], "parser_files_categorization_values": ".mem", "description": "List all registry hives", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.803904", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_hiveList", "name": "mem_hiveList"}, {"important_field": [{"path": "Hive_Name", "name": "Hive_Name"}, {"path": "Name", "name": "Name"}], "parser_files_categorization_values": ".mem", "description": "Parse user assists from memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.806800", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_userAssist", "name": "mem_userAssist"}, {"important_field": [{"path": "From_Name", "name": "From_Name"}, {"path": "To_Name", "name": "To_Name"}], "parser_files_categorization_values": ".mem", "description": "List all symlink from memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.809857", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_symlink", "name": "mem_symlink"}, {"important_field": [{"path": "Process", "name": "Process"}, {"path": "File", "name": "File"}], "parser_files_categorization_values": ".mem", "description": "parse the Virtual Address Descriptor (VAD)", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.814655", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_vadInfo", "name": "mem_vadInfo"}, {"important_field": [{"path": "Region", "name": "Region"}, {"path": "Start_offset", "name": "Start_offset"}], "parser_files_categorization_values": ".mem", "description": "List the assigned region for each processes", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.822081", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_virtMap", "name": "mem_virtMap"}, {"important_field": [{"path": "Plugin", "name": "Plugin"}, {"path": "Description", "name": "Description"}], "parser_files_categorization_values": ".mem", "description": "Get multiple memory plugins that contains a time, and list them in timeline order", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.843241", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_timeliner", "name": "mem_timeliner"}, {"important_field": [{"path": "PID", "name": "PID"}, {"path": "ImageFileName", "name": "Name"}], "parser_files_categorization_values": ".mem", "description": "get all process list from volatility pslist plugin ", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.846103", "parser_type_field": "memory", "action": "add", "parser_folder": "vol_Parser", "_id": "mem_pslist", "name": "mem_pslist"}, {"important_field": [{"path": "Process", "name": "Process"}, {"path": "Args", "name": "Args"}], "parser_files_categorization_values": ".mem", "description": "Get command line from memory dump", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.849344", "parser_type_field": "memory", "action": "add", "parser_folder": "vol_Parser", "_id": "mem_cmdline", "name": "mem_cmdline"}, {"important_field": [{"path": "Process", "name": "Process"}, {"path": "Path", "name": "Path"}], "parser_files_categorization_values": ".mem", "description": "Get the loaded DLLs from memory", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.852691", "parser_type_field": "memory", "action": "add", "parser_folder": "vol_Parser", "_id": "mem_dlllist", "name": "mem_dlllist"}, {"important_field": [{"path": "Module", "name": "Module"}, {"path": "Symbol", "name": "Symbol"}], "parser_files_categorization_values": ".mem", "description": " Lists the system call table", "interface_function": "interface.imain", "parser_files_categorization_type": "extension", "creation_time": "2021-03-26T11:15:13.855525", "parser_type_field": "memory", "action": "edit", "parser_folder": "vol_Parser", "_id": "mem_SSDT", "name": "mem_SSDT"}] \ No newline at end of file diff --git a/app/parsers/vol_Parser/development/__init__.py b/app/parsers/vol_Parser/development/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/vol_Parser/development/centos-kernels.txt b/app/parsers/vol_Parser/development/centos-kernels.txt new file mode 100644 index 00000000..17280a51 --- /dev/null +++ b/app/parsers/vol_Parser/development/centos-kernels.txt @@ -0,0 +1,51 @@ +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190218180909/4.14.94-200.el7.x86_64/kernel-core-4.14.94-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190218180909/4.14.94-200.el7.x86_64/kernel-debuginfo-4.14.94-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190218180954/4.14.101-200.el7.x86_64/kernel-core-4.14.101-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190218180954/4.14.101-200.el7.x86_64/kernel-debuginfo-4.14.101-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190314091448/4.14.106-200.el7.x86_64/kernel-core-4.14.106-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190314091448/4.14.106-200.el7.x86_64/kernel-debuginfo-4.14.106-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190415175139/4.14.111-200.el7.x86_64/kernel-core-4.14.111-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190415175139/4.14.111-200.el7.x86_64/kernel-debuginfo-4.14.111-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190506131339/4.14.116-200.el7.x86_64/kernel-core-4.14.116-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190506131339/4.14.116-200.el7.x86_64/kernel-debuginfo-4.14.116-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190506131813/4.19.39-300.el7.x86_64/kernel-core-4.19.39-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190506131813/4.19.39-300.el7.x86_64/kernel-debuginfo-4.19.39-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190516234433/4.14.119-200.el7.x86_64/kernel-core-4.14.119-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190516234433/4.14.119-200.el7.x86_64/kernel-debuginfo-4.14.119-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190918210642/4.19.72-300.el7.x86_64/kernel-core-4.19.72-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20190918210642/4.19.72-300.el7.x86_64/kernel-debuginfo-4.19.72-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20191121160255/4.19.84-300.el7.x86_64/kernel-core-4.19.84-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20191121160255/4.19.84-300.el7.x86_64/kernel-debuginfo-4.19.84-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200109160334/4.19.94-300.el7.x86_64/kernel-core-4.19.94-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200109160334/4.19.94-300.el7.x86_64/kernel-debuginfo-4.19.94-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200217152238/4.19.104-300.el7.x86_64/kernel-core-4.19.104-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200217152238/4.19.104-300.el7.x86_64/kernel-debuginfo-4.19.104-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200316161817/4.19.110-300.el7.x86_64/kernel-core-4.19.110-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200316161817/4.19.110-300.el7.x86_64/kernel-debuginfo-4.19.110-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200330144235/4.19.113-300.el8.x86_64/kernel-core-4.19.113-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200330144235/4.19.113-300.el8.x86_64/kernel-debuginfo-4.19.113-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200330211837/4.19.113-300.el8.x86_64/kernel-core-4.19.113-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200330211837/4.19.113-300.el8.x86_64/kernel-debuginfo-4.19.113-300.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200522143231/5.4.42-200.el7.x86_64/kernel-core-5.4.42-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7-kernels.x86_64/kernel/20200522143231/5.4.42-200.el7.x86_64/kernel-debuginfo-5.4.42-200.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.02/kernel/20140529190808/3.10.0-121.el7.x86_64/kernel-3.10.0-121.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.02/kernel/20140529190808/3.10.0-121.el7.x86_64/kernel-debuginfo-3.10.0-121.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.03/kernel/20140609184350/3.10.0-121.el7.x86_64/kernel-3.10.0-121.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.03/kernel/20140609184350/3.10.0-121.el7.x86_64/kernel-debuginfo-3.10.0-121.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.04/kernel/20140612172658/3.10.0-123.el7.x86_64/kernel-3.10.0-123.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.04/kernel/20140612172658/3.10.0-123.el7.x86_64/kernel-debuginfo-3.10.0-123.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.04/kernel/20140619231033/3.10.0-123.el7.x86_64/kernel-3.10.0-123.el7.x86_64.rpm +https://buildlogs.centos.org/c7.00.04/kernel/20140619231033/3.10.0-123.el7.x86_64/kernel-debuginfo-3.10.0-123.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1611.01/kernel/20161117160457/3.10.0-514.el7.x86_64/kernel-3.10.0-514.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1611.01/kernel/20161117160457/3.10.0-514.el7.x86_64/kernel-debuginfo-3.10.0-514.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1708.00/kernel/20170822030048/3.10.0-693.el7.x86_64/kernel-3.10.0-693.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1708.00/kernel/20170822030048/3.10.0-693.el7.x86_64/kernel-debuginfo-3.10.0-693.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1804.00.x86_64/kernel/20180410150127/3.10.0-862.el7.x86_64/kernel-3.10.0-862.el7.centos.x86_64.rpm +https://buildlogs.centos.org/c7.1804.00.x86_64/kernel/20180410150127/3.10.0-862.el7.x86_64/kernel-debuginfo-3.10.0-862.el7.centos.x86_64.rpm +https://buildlogs.centos.org/c7.1810.00.x86_64/kernel/20181030130226/3.10.0-957.el7.x86_64/kernel-3.10.0-957.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1810.00.x86_64/kernel/20181030130226/3.10.0-957.el7.x86_64/kernel-debuginfo-3.10.0-957.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-3.10.0-1062.el7.x86_64.rpm +https://buildlogs.centos.org/c7.1908.00.x86_64/kernel/20190808101829/3.10.0-1062.el7.x86_64/kernel-debuginfo-3.10.0-1062.el7.x86_64.rpm +https://buildlogs.centos.org/c7.2003.00.x86_64/kernel/20200331233310/3.10.0-1127.el7.x86_64/kernel-3.10.0-1127.el7.x86_64.rpm +https://buildlogs.centos.org/c7.2003.00.x86_64/kernel/20200331233310/3.10.0-1127.el7.x86_64/kernel-debuginfo-3.10.0-1127.el7.x86_64.rpm + diff --git a/app/parsers/vol_Parser/development/compare-vol.py b/app/parsers/vol_Parser/development/compare-vol.py new file mode 100644 index 00000000..4e4f01b1 --- /dev/null +++ b/app/parsers/vol_Parser/development/compare-vol.py @@ -0,0 +1,274 @@ +import argparse +import csv +import hashlib +import os +import re +import shutil +import subprocess +import time +from dataclasses import dataclass, field +from typing import Dict, List + + +@dataclass +class VolatilityImage: + filepath: str = "" + vol2_profile: str = "" + vol2_imageinfo_time: float = None + vol2_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict) + vol3_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict) + rekall_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict) + + +@dataclass +class VolatilityPlugin: + name: str = "" + vol2_plugin_parameters: List[str] = field(default_factory = list) + vol3_plugin_parameters: List[str] = field(default_factory = list) + rekall_plugin_parameters: List[str] = field(default_factory = list) + + +class VolatilityTest: + short_name = "true" + long_name = "True" + + def __init__(self, path: str, output_directory: str) -> None: + self.path = path + self.output_directory = output_directory + + def result_titles(self) -> List[str]: + return [self.long_name] + + def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None: + pass + + def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> List[float]: + self.create_prerequisites(plugin, image, image_hash) + + # Volatility 2 Test + print("[*] Testing {} {} with image {}".format(self.short_name, plugin.name, image.filepath)) + os.chdir(self.path) + cmd = self.plugin_cmd(plugin, image) + start_time = time.perf_counter() + try: + completed = subprocess.run(cmd, cwd = self.path, capture_output = True, timeout = 420) + except subprocess.TimeoutExpired as excp: + completed = excp + end_time = time.perf_counter() + total_time = end_time - start_time + print(" Tested {} {} with image {}: {}".format(self.short_name, plugin.name, image.filepath, total_time)) + with open( + os.path.join(self.output_directory, '{}_{}_{}_stdout'.format(self.short_name, plugin.name, image_hash)), + "wb") as f: + f.write(completed.stdout) + if completed.stderr: + with open( + os.path.join(self.output_directory, '{}_{}_{}_stderr'.format(self.short_name, plugin.name, + image_hash)), "wb") as f: + f.write(completed.stderr) + return [total_time] + + def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage): + return ["true"] + + +class Volatility2Test(VolatilityTest): + short_name = "vol2" + long_name = "Volatility 2" + + def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage): + return ["python2", "-u", "vol.py", "-f", image.filepath, "--profile", image.vol2_profile + ] + plugin.vol2_plugin_parameters + image.vol2_plugin_parameters.get(plugin.name, []) + + def result_titles(self): + return [self.long_name, "Imageinfo", f"{self.long_name} + Imageinfo"] + + def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash) -> List[float]: + result = super().create_results(plugin, image, image_hash) + result += [image.vol2_imageinfo_time, result[0] + image.vol2_imageinfo_time] + return result + + def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash): + # Volatility 2 image info + if not image.vol2_profile: + print("[*] Testing {} imageinfo with image {}".format(self.short_name, image.filepath)) + os.chdir(self.path) + cmd = ["python2", "-u", "vol.py", "-f", image.filepath, "imageinfo"] + start_time = time.perf_counter() + vol2_completed = subprocess.run(cmd, cwd = self.path, capture_output = True) + end_time = time.perf_counter() + image.vol2_imageinfo_time = end_time - start_time + print(" Tested volatility2 imageinfo with image {}: {}".format(image.filepath, end_time - start_time)) + with open(os.path.join(self.output_directory, 'vol2_imageinfo_{}_stdout'.format(image_hash)), "wb") as f: + f.write(vol2_completed.stdout) + image.vol2_profile = re.search(b"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout)[1] + + +class RekallTest(VolatilityTest): + short_name = "rekall" + long_name = "Rekall" + + def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]: + if not plugin.rekall_plugin_parameters: + plugin.rekall_plugin_parameters = plugin.vol2_plugin_parameters + if not image.rekall_plugin_parameters: + image.rekall_plugin_parameters = image.vol2_plugin_parameters + return ["rekall", "-f", image.filepath] + plugin.rekall_plugin_parameters + image.rekall_plugin_parameters.get( + plugin.name, []) + + def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None: + shutil.rmtree('/home/mike/.rekall_cache/sessions') + + +class Volatility3Test(VolatilityTest): + short_name = "vol3" + long_name = "Volatility 3" + + def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]: + return [ + "python", + "-u", + "vol.py", + "-q", + "-f", + image.filepath, + ] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, []) + + +class Volatility3PyPyTest(VolatilityTest): + short_name = "pypy" + long_name = "Volatility 3 (PyPy)" + + def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]: + return [ + "pypy3", + "-u", + "vol.py", + "-q", + "-f", + image.filepath, + ] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, []) + + +class VolatilityTester: + + def __init__(self, + images: List[VolatilityImage], + plugins: List[VolatilityPlugin], + frameworks: List[str], + output_dir: str, + vol2_path: str = None, + vol3_path: str = None, + rekall_path = None): + self.images = images + self.plugins = plugins + if not vol2_path: + vol2_path = output_dir + if not vol3_path: + vol3_path = output_dir + if not rekall_path: + rekall_path = output_dir + available_tests = [ + Volatility3Test(vol3_path, output_dir), + Volatility3PyPyTest(vol3_path, output_dir), + Volatility2Test(vol2_path, output_dir), + RekallTest(rekall_path, output_dir) + ] + self.tests = [x for x in available_tests if x.short_name.lower() in frameworks] + self.csv_writer = None + print(f"[?] Vol2 path {vol2_path}") + print(f"[?] Vol3 path {vol3_path}") + print(f"[?] Rekall path {rekall_path}") + print("") + print(f"[?] Frameworks: {[x.long_name for x in self.tests]}") + + def run_tests(self): + with open("volatility-timings.csv", 'w') as csvfile: + self.csv_writer = csv.writer(csvfile) + titles = ["Image Hash", "Image Path", "Plugin Name"] + for test in self.tests: + titles += test.result_titles() + self.csv_writer.writerow(titles) + for image in self.images: + for plugin in self.plugins: + self.run_test(plugin, image) + + def run_test(self, plugin: VolatilityPlugin, image: VolatilityImage): + image_hash = hashlib.md5(bytes(image.filepath, "latin-1")).hexdigest() + + results = [] + for test in self.tests: + results += test.create_results(plugin, image, image_hash) + + self.csv_writer.writerow([image_hash, image.filepath, plugin.name] + results) + + +if __name__ == '__main__': + plugins = [ + VolatilityPlugin(name = "pslist", + vol2_plugin_parameters = ["pslist"], + vol3_plugin_parameters = ["windows.pslist"]), + VolatilityPlugin(name = "psscan", + vol2_plugin_parameters = ["psscan"], + vol3_plugin_parameters = ["windows.psscan"], + rekall_plugin_parameters = ["psscan", "--scan_kernel"]), + VolatilityPlugin(name = "driverscan", + vol2_plugin_parameters = ["driverscan"], + vol3_plugin_parameters = ["windows.driverscan"], + rekall_plugin_parameters = ["driverscan", "--scan_kernel"]), + VolatilityPlugin(name = "handles", + vol2_plugin_parameters = ["handles"], + vol3_plugin_parameters = ["windows.handles"]), + VolatilityPlugin(name = "modules", + vol2_plugin_parameters = ["modules"], + vol3_plugin_parameters = ["windows.modules"]), + VolatilityPlugin(name = "hivelist", + vol2_plugin_parameters = ["hivelist"], + vol3_plugin_parameters = ["registry.hivelist"], + rekall_plugin_parameters = ["hives"]), + VolatilityPlugin(name = "vadinfo", + vol2_plugin_parameters = ["vadinfo"], + vol3_plugin_parameters = ["windows.vadinfo"], + rekall_plugin_parameters = ["vad"]), + VolatilityPlugin(name = "modscan", + vol2_plugin_parameters = ["modscan"], + vol3_plugin_parameters = ["windows.modscan"], + rekall_plugin_parameters = ["modscan", "--scan_kernel"]), + VolatilityPlugin(name = "svcscan", + vol2_plugin_parameters = ["svcscan"], + vol3_plugin_parameters = ["windows.svcscan"], + rekall_plugin_parameters = ["svcscan"]), + VolatilityPlugin(name = "ssdt", vol2_plugin_parameters = ["ssdt"], vol3_plugin_parameters = ["windows.ssdt"]), + VolatilityPlugin(name = "printkey", + vol2_plugin_parameters = ["printkey", "-K", "Classes"], + vol3_plugin_parameters = ["registry.printkey", "--key", "Classes"], + rekall_plugin_parameters = ["printkey", "--key", "Classes"]) + ] + + parser = argparse.ArgumentParser() + parser.add_argument("--output-dir", type = str, default = os.getcwd(), help = "Directory to store all results") + parser.add_argument("--vol3path", + type = str, + default = os.path.join(os.getcwd(), 'volatility3'), + help = "Path ot the volatility 3 directory") + parser.add_argument("--vol2path", + type = str, + default = os.path.join(os.getcwd(), 'volatility'), + help = "Path to the volatility 2 directory") + parser.add_argument("--rekallpath", + type = str, + default = os.path.join(os.getcwd(), 'rekall'), + help = "Path to the rekall directory") + parser.add_argument("--frameworks", + nargs = "+", + type = str, + choices = [x.short_name.lower() for x in VolatilityTest.__subclasses__()], + default = [x.short_name.lower() for x in VolatilityTest.__subclasses__()], + help = "A comma separated list of frameworks to test") + parser.add_argument('images', metavar = 'IMAGE', type = str, nargs = '+', help = 'The list of images to compare') + args = parser.parse_args() + + vt = VolatilityTester([VolatilityImage(filepath = x) for x in args.images], plugins, + [x.lower() for x in args.frameworks], args.output_dir, args.vol2path, args.vol3path, + args.rekallpath) + vt.run_tests() diff --git a/app/parsers/vol_Parser/development/debian-kernels.txt b/app/parsers/vol_Parser/development/debian-kernels.txt new file mode 100644 index 00000000..8b5c3dfb --- /dev/null +++ b/app/parsers/vol_Parser/development/debian-kernels.txt @@ -0,0 +1,93 @@ +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-6-amd64-dbg_3.16.56-1+deb8u1_amd64.deb +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-6-amd64_3.16.56-1+deb8u1_amd64.deb +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-12-amd64-dbg_4.9.210-1_amd64.deb +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-12-amd64_4.9.210-1_amd64.deb +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-12-rt-amd64_4.9.210-1_amd64.deb +https://www.mirrorservice.org/sites/ftp.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-12-rt-amd64-dbg_4.9.210-1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-amd64_2.6.32-48squeeze6_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-amd64-dbg_2.6.32-48squeeze6_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-amd64_2.6.32-48squeeze20_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-amd64-dbg_2.6.32-48squeeze20_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-xen-amd64_2.6.32-48squeeze6_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-xen-amd64-dbg_2.6.32-48squeeze6_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-xen-amd64_2.6.32-48squeeze20_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux-2.6/linux-image-2.6.32-5-xen-amd64-dbg_2.6.32-48squeeze20_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.2.0-4-amd64_3.2.78-1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.2.0-4-amd64-dbg_3.2.78-1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.2.0-4-rt-amd64_3.2.78-1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.2.0-4-rt-amd64-dbg_3.2.78-1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-0.bpo.4-amd64_3.16.39-1+deb8u1~bpo70+1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-0.bpo.4-amd64-dbg_3.16.39-1+deb8u1~bpo70+1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-6-amd64_3.16.56-1+deb8u1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-3.16.0-6-amd64-dbg_3.16.56-1+deb8u1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-0.bpo.6-amd64_4.9.88-1+deb9u1~bpo8+1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-0.bpo.6-amd64-dbg_4.9.88-1+deb9u1~bpo8+1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-0.bpo.6-rt-amd64_4.9.88-1+deb9u1~bpo8+1_amd64.deb +http://archive.debian.org/debian/pool/main/l/linux/linux-image-4.9.0-0.bpo.6-rt-amd64-dbg_4.9.88-1+deb9u1~bpo8+1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-3.16.0-10-amd64_3.16.81-1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-3.16.0-10-amd64-dbg_3.16.81-1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-amd64_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-amd64-dbg_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-rt-amd64_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-rt-amd64-dbg_4.9.189-3+deb9u2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.6-amd64-dbg_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.6-amd64_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.6-cloud-amd64-dbg_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.6-cloud-amd64_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.6-rt-amd64-dbg_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.6-rt-amd64_4.19.67-2%2Bdeb10u2~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.8-amd64-dbg_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.8-amd64_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.8-cloud-amd64-dbg_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.8-cloud-amd64_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-0.bpo.8-rt-amd64-dbg_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-0.bpo.8-rt-amd64_4.19.98-1~bpo9%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-9-amd64-dbg_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-9-amd64_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-9-cloud-amd64-dbg_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-9-cloud-amd64_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-4.19.0-9-rt-amd64-dbg_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-4.19.0-9-rt-amd64_4.19.118-2_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.2-amd64_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.2-amd64-dbg_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.2-cloud-amd64_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.2-cloud-amd64-dbg_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.2-rt-amd64_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.2-rt-amd64-dbg_5.4.8-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.3-amd64_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.3-amd64-dbg_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.3-cloud-amd64_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.3-cloud-amd64-dbg_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.3-rt-amd64_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.3-rt-amd64-dbg_5.4.13-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.4-amd64_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.4-amd64-dbg_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.4-cloud-amd64_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.4-cloud-amd64-dbg_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.4.0-0.bpo.4-rt-amd64-dbg_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.4.0-0.bpo.4-rt-amd64_5.4.19-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.5.0-0.bpo.2-amd64-dbg_5.5.17-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.5.0-0.bpo.2-amd64_5.5.17-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.5.0-0.bpo.2-cloud-amd64-dbg_5.5.17-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.5.0-0.bpo.2-cloud-amd64_5.5.17-1~bpo10%2B1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.6.0-1-amd64-dbg_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.6.0-1-amd64_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.6.0-1-cloud-amd64-dbg_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.6.0-1-cloud-amd64_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.6.0-1-rt-amd64-dbg_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.6.0-1-rt-amd64_5.6.7-1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.7.0-rc5-amd64-dbg_5.7~rc5-1~exp1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.7.0-rc5-amd64_5.7~rc5-1~exp1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.7.0-rc5-cloud-amd64-dbg_5.7~rc5-1~exp1_amd64.deb +http://ftp.us.debian.org/debian/pool/main/l/linux-signed-amd64/linux-image-5.7.0-rc5-cloud-amd64_5.7~rc5-1~exp1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux-latest/linux-image-amd64-dbg_3.16+63+deb8u6_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux-latest/linux-image-amd64_3.16+63+deb8u6_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux-latest-4.9/linux-image-4.9-amd64-dbg_4.9+80+deb9u10~deb8u1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux-latest-4.9/linux-image-4.9-amd64_4.9+80+deb9u10~deb8u1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-rt-amd64-dbg_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-rt-amd64_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-amd64-dbg_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.9.0-11-amd64_4.9.189-3+deb9u2_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-3.16.0-10-amd64-dbg_3.16.81-1_amd64.deb +http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-3.16.0-10-amd64_3.16.81-1_amd64.deb + diff --git a/app/parsers/vol_Parser/development/fedora-kernels.txt b/app/parsers/vol_Parser/development/fedora-kernels.txt new file mode 100644 index 00000000..70f05b57 --- /dev/null +++ b/app/parsers/vol_Parser/development/fedora-kernels.txt @@ -0,0 +1,50 @@ +https://archives.fedoraproject.org/pub/archive/fedora/linux/core/5/x86_64/os/Fedora/RPMS/kernel-2.6.15-1.2054_FC5.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/core/5/x86_64/debug/kernel-debuginfo-2.6.15-1.2054_FC5.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/core/6/x86_64/os/Fedora/RPMS/kernel-2.6.18-1.2798.fc6.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/core/6/x86_64/debug/kernel-debuginfo-2.6.18-1.2798.fc6.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/7/x86_64/kernel-2.6.23.17-88.fc7.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/7/x86_64/debug/kernel-debuginfo-2.6.23.17-88.fc7.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/8/x86_64.newkey/kernel-2.6.26.8-57.fc8.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/8/x86_64.newkey/debug/kernel-debuginfo-2.6.26.8-57.fc8.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/9/x86_64.newkey/kernel-2.6.27.25-78.2.56.fc9.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/9/x86_64.newkey/debug/kernel-debuginfo-2.6.27.25-78.2.56.fc9.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/10/x86_64/kernel-2.6.27.41-170.2.117.fc10.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/10/x86_64/debug/kernel-debuginfo-2.6.27.41-170.2.117.fc10.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/11/x86_64/kernel-2.6.30.10-105.2.23.fc11.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/11/x86_64/debug/kernel-debuginfo-2.6.30.10-105.2.23.fc11.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/12/x86_64/kernel-2.6.32.26-175.fc12.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/12/x86_64/debug/kernel-debuginfo-2.6.32.26-175.fc12.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/13/x86_64/kernel-2.6.34.9-69.fc13.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/13/x86_64/debug/kernel-debuginfo-2.6.34.9-69.fc13.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/14/x86_64/kernel-2.6.35.14-106.fc14.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/14/x86_64/debug/kernel-debuginfo-2.6.35.14-106.fc14.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/15/x86_64/kernel-2.6.43.8-1.fc15.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/15/x86_64/debug/kernel-debuginfo-2.6.43.8-1.fc15.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/16/x86_64/kernel-3.6.11-4.fc16.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/16/x86_64/debug/kernel-debuginfo-3.6.11-4.fc16.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/17/x86_64/kernel-3.9.10-100.fc17.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/17/x86_64/debug/kernel-debuginfo-3.9.10-100.fc17.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/18/x86_64/kernel-3.11.10-100.fc18.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/18/x86_64/debug/kernel-debuginfo-3.11.10-100.fc18.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/19/x86_64/kernel-3.14.27-100.fc19.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/19/x86_64/debug/kernel-debuginfo-3.14.27-100.fc19.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/20/x86_64/kernel-3.19.8-100.fc20.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/20/x86_64/debug/kernel-debuginfo-3.19.8-100.fc20.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/21/x86_64/k/kernel-core-4.1.13-100.fc21.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/21/x86_64/debug/k/kernel-debuginfo-4.1.13-100.fc21.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/22/x86_64/k/kernel-core-4.4.14-200.fc22.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/22/x86_64/debug/k/kernel-debuginfo-4.4.14-200.fc22.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/23/x86_64/k/kernel-core-4.8.13-100.fc23.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/23/x86_64/debug/k/kernel-debuginfo-4.8.13-100.fc23.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/24/x86_64/k/kernel-core-4.11.12-100.fc24.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/24/x86_64/debug/k/kernel-debuginfo-4.11.12-100.fc24.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/25/x86_64/Packages/k/kernel-core-4.13.16-100.fc25.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/25/x86_64/debug/Packages/k/kernel-debuginfo-4.13.16-100.fc25.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/26/x86_64/Packages/k/kernel-core-4.16.11-100.fc26.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/26/x86_64/debug/Packages/k/kernel-debuginfo-4.16.11-100.fc26.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/27/x86_64/Packages/k/kernel-core-4.18.19-100.fc27.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/27/x86_64/debug/Packages/k/kernel-debuginfo-4.18.19-100.fc27.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/28/Everything/x86_64/Packages/k/kernel-core-5.0.16-100.fc28.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/28/Everything/x86_64/debug/Packages/k/kernel-debuginfo-5.0.16-100.fc28.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/29/Everything/x86_64/Packages/k/kernel-core-5.3.11-100.fc29.x86_64.rpm +https://archives.fedoraproject.org/pub/archive/fedora/linux/updates/29/Everything/x86_64/debug/Packages/k/kernel-debuginfo-5.3.11-100.fc29.x86_64.rpm diff --git a/app/parsers/vol_Parser/development/jsonschema.schema b/app/parsers/vol_Parser/development/jsonschema.schema new file mode 100644 index 00000000..85079d89 --- /dev/null +++ b/app/parsers/vol_Parser/development/jsonschema.schema @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": { "$ref": "#" }, + "then": { "$ref": "#" }, + "else": { "$ref": "#" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true +} diff --git a/app/parsers/vol_Parser/development/mac-kdk/extract_kernel.sh b/app/parsers/vol_Parser/development/mac-kdk/extract_kernel.sh new file mode 100644 index 00000000..21797a30 --- /dev/null +++ b/app/parsers/vol_Parser/development/mac-kdk/extract_kernel.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +UNPACK_DIR="Unpacked" +KERNEL_DIR="$(basename ${1})" +JSON_DIR="JSON" +DWARF2JSON="dwarf2json" + +echo "Operating on ${KERNEL_DIR}" +mkdir tmp +pushd tmp + + +7z x ../${1} +if [ -f "KernelDebugKit/mach_kernel" ]; then + echo "7Z unpack successful" + mv KernelDebugKit System +else + echo "XAR unpack required" + xar -x -f Kernel\ Debug\ Kit/KernelDebugKit.pkg + python2 ../parse_pbzx2.py KDK.pkg/Payload + if [ $? == 0 ]; then + xz -d KDK.pkg/*.xz + cat KDK.pkg/*.cpio > Payload\~ + else + 7z x Kernel\ Debug\ Kit/KernelDebugKit.pkg + fi + echo "CPIO unpacking Payload" + cpio -i < Payload\~ +fi + +mkdir -p "../${UNPACK_DIR}/${KERNEL_DIR}" + +if [ -f "System/Library/Kernels/kernel" ]; then + cp "System/Library/Kernels/kernel" "../${UNPACK_DIR}/${KERNEL_DIR}/kernel" + cp "System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/kernel" "../${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" +elif [ -f "System/mach_kernel.dSYM/Contents/Resources/DWARF/mach_kernel" ]; then + cp System/mach_kernel "../${UNPACK_DIR}/${KERNEL_DIR}/kernel" + cp System/mach_kernel.dSYM/Contents/Resources/DWARF/mach_kernel "../${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" +fi + +chmod -R ug+w "../${UNPACK_DIR}/${KERNEL_DIR}" + +${DWARF2JSON} +popd +rm -fr tmp + +${DWARF2JSON} mac --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIRECTORY}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz +if [ $? == 0 ]; then + ${DWARF2JSON} mac --arch i386 --macho "${UNPACK_DIR}/${KERNEL_DIR}/kernel.dSYM" --macho-symbols "${UNPACK_DIRECTORY}/${KERNEL_DIR}/kernel" | xz -9 > ${JSON_DIR}/${KERNEL_DIR}.json.xz +fi diff --git a/app/parsers/vol_Parser/development/mac-kdk/generate_json.sh b/app/parsers/vol_Parser/development/mac-kdk/generate_json.sh new file mode 100644 index 00000000..b09fd7ad --- /dev/null +++ b/app/parsers/vol_Parser/development/mac-kdk/generate_json.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +DWARF2JSON=$(which dwarf2json) +KDK_PATH="${1}" +JSON_DIR="${2}" + +for i in `ls ${KDK_PATH}`; +do + echo "${i}" + if [ -f "${KDK_PATH}/${i}/System/Library/Kernels/kernel" ]; then + echo "Library Kernels" + ${DWARF2JSON} mac --macho "${KDK_PATH}/${i}/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/kernel" --macho-symbols "${KDK_PATH}/${i}/System/Library/Kernels/kernel" > ${JSON_DIR}/${i}.json + elif [ -f "${KDK_PATH}/${i}/System/mach_kernel.dSYM/Contents/Resources/DWARF/mach_kernel" ]; then + echo "Mach kernel" + ${DWARF2JSON} mac --macho "${KDK_PATH}/${i}/System/mach_kernel.dSYM/Contents/Resources/DWARF/mach_kernel" --macho-symbols "${KDK_PATH}/${i}/System/mach_kernel" > "${JSON_DIR}/${i}.json" || ${DWARF2JSON} mac --arch i386 --macho "${KDK_PATH}/${i}/System/mach_kernel.dSYM/Contents/Resources/DWARF/mach_kernel" --macho-symbols "${KDK_PATH}/${i}/System/mach_kernel" > "${JSON_DIR}/${i}.json" + fi +done + + diff --git a/app/parsers/vol_Parser/development/mac-kdk/parse_pbzx2.py b/app/parsers/vol_Parser/development/mac-kdk/parse_pbzx2.py new file mode 100644 index 00000000..7ce9090d --- /dev/null +++ b/app/parsers/vol_Parser/development/mac-kdk/parse_pbzx2.py @@ -0,0 +1,83 @@ +# v2 pbzx stream handler +# My personal writeup on the differences here: https://gist.github.com/pudquick/29fcfe09c326a9b96cf5 +# +# Pure python reimplementation of .cpio.xz content extraction from pbzx file payload originally here: +# http://www.tonymacx86.com/general-help/135458-pbzx-stream-parser.html +# +# Cleaned up C version (as the basis for my code) here, thanks to Pepijn Bruienne / @bruienne +# https://gist.github.com/bruienne/029494bbcfb358098b41 + +import struct +import sys + + +def seekread(f, offset = None, length = 0, relative = True): + if offset is not None: + # offset provided, let's seek + f.seek(offset, [0, 1, 2][relative]) + if length: + return f.read(length) + + +def parse_pbzx(pbzx_path): + section = 0 + xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section) + f = open(pbzx_path, 'rb') + # pbzx = f.read() + # f.close() + magic = seekread(f, length = 4) + if magic != 'pbzx': + raise RuntimeError("Error: Not a pbzx file") + # Read 8 bytes for initial flags + flags = seekread(f, length = 8) + # Interpret the flags as a 64-bit big-endian unsigned int + flags = struct.unpack('>Q', flags)[0] + xar_f = open(xar_out_path, 'wb') + while flags & (1 << 24): + # Read in more flags + flags = seekread(f, length = 8) + flags = struct.unpack('>Q', flags)[0] + # Read in length + f_length = seekread(f, length = 8) + f_length = struct.unpack('>Q', f_length)[0] + xzmagic = seekread(f, length = 6) + if xzmagic != '\xfd7zXZ\x00': + # This isn't xz content, this is actually _raw decompressed cpio_ chunk of 16MB in size... + # Let's back up ... + seekread(f, offset = -6, length = 0) + # ... and split it out ... + f_content = seekread(f, length = f_length) + section += 1 + decomp_out = '%s.part%02d.cpio' % (pbzx_path, section) + g = open(decomp_out, 'wb') + g.write(f_content) + g.close() + # Now to start the next section, which should hopefully be .xz (we'll just assume it is ...) + xar_f.close() + section += 1 + new_out = '%s.part%02d.cpio.xz' % (pbzx_path, section) + xar_f = open(new_out, 'wb') + else: + f_length -= 6 + # This part needs buffering + f_content = seekread(f, length = f_length) + tail = seekread(f, offset = -2, length = 2) + xar_f.write(xzmagic) + xar_f.write(f_content) + if tail != 'YZ': + xar_f.close() + raise RuntimeError("Error: Footer is not xar file footer") + try: + f.close() + xar_f.close() + except IOError: + pass + + +def main(): + parse_pbzx(sys.argv[1]) + print("Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file") + + +if __name__ == '__main__': + main() diff --git a/app/parsers/vol_Parser/development/pdbparse-to-json.py b/app/parsers/vol_Parser/development/pdbparse-to-json.py new file mode 100644 index 00000000..88c8427f --- /dev/null +++ b/app/parsers/vol_Parser/development/pdbparse-to-json.py @@ -0,0 +1,360 @@ +import argparse +import binascii +import datetime +import json +import logging +import os +from typing import Dict, Union, Optional, Any, Set +from urllib import request + +import pdbparse +import pdbparse.undecorate + +logger = logging.getLogger(__name__) +logger.setLevel(1) + +if __name__ == '__main__': + console = logging.StreamHandler() + console.setLevel(1) + formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s') + console.setFormatter(formatter) + logger.addHandler(console) + + +class PDBRetreiver: + + def retreive_pdb(self, guid: str, file_name: str) -> Optional[str]: + logger.info("Download PDB file...") + file_name = ".".join(file_name.split(".")[:-1] + ['pdb']) + for sym_url in ['http://msdl.microsoft.com/download/symbols']: + url = sym_url + "/{}/{}/".format(file_name, guid) + + result = None + for suffix in [file_name[:-1] + '_', file_name]: + try: + logger.debug("Attempting to retrieve {}".format(url + suffix)) + result, _ = request.urlretrieve(url + suffix) + except request.HTTPError as excp: + logger.debug("Failed with {}".format(excp)) + if result: + logger.debug("Successfully written to {}".format(result)) + break + return result + + +class PDBConvertor: + ctype = { + "T_INT4": "int", + "T_INT8": "long long", + "T_LONG": "long", + "T_QUAD": "long long", + "T_RCHAR": "char", + "T_REAL32": "float", + "T_REAL64": "double", + "T_REAL80": "long double", + "T_SHORT": "short", + "T_UCHAR": "unsigned char", + "T_UINT4": "unsigned int", + "T_ULONG": "unsigned long", + "T_UQUAD": "unsigned long long", + "T_USHORT": "unsigned short", + "T_HRESULT": "HRESULT", + "T_WCHAR": "wchar", + "T_VOID": "void", + } + + ctype_python_types = { + "char": "char", + "unsigned char": "char", + "float": "float", + "double": "float", + "long double": "float", + "void": "void" + } + + base_type_size = { + "T_32PRCHAR": 4, + "T_32PUCHAR": 4, + "T_32PULONG": 4, + "T_32PUQUAD": 4, + "T_32PUSHORT": 4, + "T_32PLONG": 4, + "T_32PWCHAR": 4, + "T_32PVOID": 4, + "T_64PRCHAR": 8, + "T_64PUCHAR": 8, + "T_64PULONG": 8, + "T_64PUQUAD": 8, + "T_64PUSHORT": 8, + "T_64PLONG": 8, + "T_64PWCHAR": 8, + "T_64PVOID": 8, + "T_VOID": 0, + "T_INT4": 4, + "T_INT8": 8, + "T_LONG": 4, + "T_QUAD": 8, + "T_RCHAR": 1, + "T_REAL32": 4, + "T_REAL64": 8, + "T_REAL80": 10, + "T_SHORT": 2, + "T_UCHAR": 1, + "T_UINT4": 4, + "T_ULONG": 4, + "T_UQUAD": 8, + "T_USHORT": 2, + "T_WCHAR": 2, + "T_HRESULT": 4, + "PTR_64": 8, + "PTR_32": 4, + "PTR_NEAR32": 4, + "PTR_NEAR64": 8, + } + + def __init__(self, filename: str): + self._filename = filename + logger.info("Parsing PDB...") + self._pdb = pdbparse.parse(filename) + self._seen_ctypes = set([]) # type: Set[str] + + def lookup_ctype(self, ctype: str) -> str: + self._seen_ctypes.add(ctype) + return self.ctype[ctype] + + def lookup_ctype_pointers(self, ctype_pointer: str) -> Dict[str, Union[str, Dict[str, str]]]: + base_type = ctype_pointer.replace('32P', '').replace('64P', '') + if base_type == ctype_pointer: + # We raise a KeyError, because we've been asked about a type that isn't a pointer + raise KeyError + self._seen_ctypes.add(base_type) + return {"kind": "pointer", "subtype": {"kind": "base", "name": self.ctype[base_type]}} + + def read_pdb(self) -> Dict: + """Reads in the PDB file and forms essentially a python dictionary of necessary data""" + output = { + "user_types": self.read_usertypes(), + "enums": self.read_enums(), + "metadata": self.generate_metadata(), + "symbols": self.read_symbols(), + "base_types": self.read_basetypes() + } + return output + + def generate_metadata(self) -> Dict[str, Any]: + """Generates the metadata necessary for this object""" + dbg = self._pdb.STREAM_DBI + last_bytes = str(binascii.hexlify(self._pdb.STREAM_PDB.GUID.Data4), 'ascii')[-16:] + guidstr = u'{:08x}{:04x}{:04x}{}'.format(self._pdb.STREAM_PDB.GUID.Data1, self._pdb.STREAM_PDB.GUID.Data2, + self._pdb.STREAM_PDB.GUID.Data3, last_bytes) + pdb_data = { + "GUID": guidstr.upper(), + "age": self._pdb.STREAM_PDB.Age, + "database": "ntkrnlmp.pdb", + "machine_type": int(dbg.machine) + } + result = { + "format": "6.0.0", + "producer": { + "datetime": datetime.datetime.now().isoformat(), + "name": "pdbconv", + "version": "0.1.0" + }, + "windows": { + "pdb": pdb_data + } + } + return result + + def read_enums(self) -> Dict: + """Reads the Enumerations from the PDB file""" + logger.info("Reading enums...") + output = {} # type: Dict[str, Any] + stream = self._pdb.STREAM_TPI + for type_index in stream.types: + user_type = stream.types[type_index] + if (user_type.leaf_type == "LF_ENUM" and not user_type.prop.fwdref): + output.update(self._format_enum(user_type)) + return output + + def _format_enum(self, user_enum): + output = { + user_enum.name: { + 'base': self.lookup_ctype(user_enum.utype), + 'size': self._determine_size(user_enum.utype), + 'constants': dict([(enum.name, enum.enum_value) for enum in user_enum.fieldlist.substructs]) + } + } + return output + + def read_symbols(self) -> Dict: + """Reads the symbols from the PDB file""" + logger.info("Reading symbols...") + output = {} + + try: + sects = self._pdb.STREAM_SECT_HDR_ORIG.sections + omap = self._pdb.STREAM_OMAP_FROM_SRC + except AttributeError as e: + # In this case there is no OMAP, so we use the given section + # headers and use the identity function for omap.remap + sects = self._pdb.STREAM_SECT_HDR.sections + omap = None + + for sym in self._pdb.STREAM_GSYM.globals: + if not hasattr(sym, 'offset'): + continue + try: + virt_base = sects[sym.segment - 1].VirtualAddress + except IndexError: + continue + name, _, _ = pdbparse.undecorate.undecorate(sym.name) + if omap: + output[name] = {"address": omap.remap(sym.offset + virt_base)} + else: + output[name] = {"address": sym.offset + virt_base} + + return output + + def read_usertypes(self) -> Dict: + """Reads the user types from the PDB file""" + logger.info("Reading usertypes...") + output = {} + stream = self._pdb.STREAM_TPI + for type_index in stream.types: + user_type = stream.types[type_index] + if (user_type.leaf_type == "LF_STRUCTURE" and not user_type.prop.fwdref): + output.update(self._format_usertype(user_type, "struct")) + elif (user_type.leaf_type == "LF_UNION" and not user_type.prop.fwdref): + output.update(self._format_usertype(user_type, "union")) + return output + + def _format_usertype(self, usertype, kind) -> Dict: + """Produces a single usertype""" + fields = {} # type: Dict[str, Dict[str, Any]] + [fields.update(self._format_field(s)) for s in usertype.fieldlist.substructs] + return {usertype.name: {'fields': fields, 'kind': kind, 'size': usertype.size}} + + def _format_field(self, field) -> Dict[str, Dict[str, Any]]: + return {field.name: {"offset": field.offset, "type": self._format_kind(field.index)}} + + def _determine_size(self, field): + output = None + if isinstance(field, str): + output = self.base_type_size[field] + elif (field.leaf_type == "LF_STRUCTURE" or field.leaf_type == "LF_ARRAY" or field.leaf_type == "LF_UNION"): + output = field.size + elif field.leaf_type == "LF_POINTER": + output = self.base_type_size[field.ptr_attr.type] + elif field.leaf_type == "LF_MODIFIER": + output = self._determine_size(field.modified_type) + elif field.leaf_type == "LF_ENUM": + output = self._determine_size(field.utype) + elif field.leaf_type == "LF_BITFIELD": + output = self._determine_size(field.base_type) + elif field.leaf_type == "LF_MEMBER": + output = self._determine_size(field.index) + if output is None: + import pdb + pdb.set_trace() + raise ValueError("Unknown size for field: {}".format(field.name)) + return output + + def _format_kind(self, kind): + output = {} + if isinstance(kind, str): + try: + output = self.lookup_ctype_pointers(kind) + except KeyError: + try: + output = {'kind': 'base', 'name': self.lookup_ctype(kind)} + except KeyError: + output = {'kind': 'base', 'name': kind} + elif kind.leaf_type == 'LF_MODIFIER': + output = self._format_kind(kind.modified_type) + elif kind.leaf_type == 'LF_STRUCTURE': + output = {'kind': 'struct', 'name': kind.name} + elif kind.leaf_type == 'LF_UNION': + output = {'kind': 'union', 'name': kind.name} + elif kind.leaf_type == 'LF_BITFIELD': + output = { + 'kind': 'bitfield', + 'type': self._format_kind(kind.base_type), + 'bit_length': kind.length, + 'bit_position': kind.position + } + elif kind.leaf_type == 'LF_POINTER': + output = {'kind': 'pointer', 'subtype': self._format_kind(kind.utype)} + elif kind.leaf_type == 'LF_ARRAY': + output = { + 'kind': 'array', + 'count': kind.size // self._determine_size(kind.element_type), + 'subtype': self._format_kind(kind.element_type) + } + elif kind.leaf_type == 'LF_ENUM': + output = {'kind': 'enum', 'name': kind.name} + elif kind.leaf_type == 'LF_PROCEDURE': + output = {'kind': "function"} + else: + import pdb + pdb.set_trace() + return output + + def read_basetypes(self) -> Dict: + """Reads the base types from the PDB file""" + ptr_size = 4 + if "64" in self._pdb.STREAM_DBI.machine: + ptr_size = 8 + + output = {"pointer": {"endian": "little", "kind": "int", "signed": False, "size": ptr_size}} + for index in self._seen_ctypes: + output[self.ctype[index]] = { + "endian": "little", + "kind": self.ctype_python_types.get(self.ctype[index], "int"), + "signed": False if "_U" in index else True, + "size": self.base_type_size[index] + } + return output + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description = "Convertor for PDB files to Volatility 3 Intermediate Symbol Format") + parser.add_argument("-o", "--output", metavar = "OUTPUT", help = "Filename for data output", required = True) + file_group = parser.add_argument_group("file", description = "File-based conversion of PDB to ISF") + file_group.add_argument("-f", "--file", metavar = "FILE", help = "PDB file to translate to ISF") + data_group = parser.add_argument_group("data", description = "Convert based on a GUID and filename pattern") + data_group.add_argument("-p", "--pattern", metavar = "PATTERN", help = "Filename pattern to recover PDB file") + data_group.add_argument("-g", + "--guid", + metavar = "GUID", + help = "GUID + Age string for the required PDB file", + default = None) + data_group.add_argument("-k", + "--keep", + action = "store_true", + default = False, + help = "Keep the downloaded PDB file") + args = parser.parse_args() + + delfile = False + filename = None + if args.guid is not None and args.pattern is not None: + filename = PDBRetreiver().retreive_pdb(guid = args.guid, file_name = args.pattern) + delfile = True + elif args.file: + filename = args.file + else: + parser.error("No GUID/pattern or file provided") + + if not filename: + parser.error("No suitable filename provided or retrieved") + + convertor = PDBConvertor(filename) + + with open(args.output, "w") as f: + json.dump(convertor.read_pdb(), f, indent = 2, sort_keys = True) + + if args.keep: + print("Temporary PDB file: {}".format(filename)) + elif delfile: + os.remove(filename) diff --git a/app/parsers/vol_Parser/development/schema_validate.py b/app/parsers/vol_Parser/development/schema_validate.py new file mode 100644 index 00000000..00a74868 --- /dev/null +++ b/app/parsers/vol_Parser/development/schema_validate.py @@ -0,0 +1,58 @@ +import argparse +import json +import os +import sys + +# TODO: Rather nasty hack, when volatility's actually installed this would be unnecessary +sys.path += ".." + +import logging + +console = logging.StreamHandler() +console.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s') +console.setFormatter(formatter) + +logger = logging.getLogger("") +logger.addHandler(console) +logger.setLevel(logging.DEBUG) + +from volatility import schemas + +if __name__ == '__main__': + parser = argparse.ArgumentParser("Validates ") + parser.add_argument("-s", "--schema", dest = "schema", default = None) + parser.add_argument("filenames", metavar = "FILE", nargs = '+') + + args = parser.parse_args() + + schema = None + if args.schema: + with open(os.path.abspath(args.schema), 'r') as s: + schema = json.load(s) + + failures = [] + for filename in args.filenames: + try: + if os.path.exists(filename): + print("[?] Validating file: {}".format(filename)) + with open(filename, 'r') as t: + test = json.load(t) + + if args.schema: + result = schemas.valid(test, schema, False) + else: + result = schemas.validate(test, False) + + if result: + print("[+] Validation successful: {}".format(filename)) + else: + print("[-] Validation failed: {}".format(filename)) + failures.append(filename) + else: + print("[x] File not found: {}".format(filename)) + except Exception as e: + failures.append(filename) + print("[x] Exception occurred: {} ({})".format(filename, repr(e))) + + print("Failures", failures) diff --git a/app/parsers/vol_Parser/development/stock-linux-json.py b/app/parsers/vol_Parser/development/stock-linux-json.py new file mode 100644 index 00000000..51bcc7ec --- /dev/null +++ b/app/parsers/vol_Parser/development/stock-linux-json.py @@ -0,0 +1,135 @@ +import argparse +import lzma +import os +import subprocess +import tempfile +from typing import List, Dict, Optional + +import requests +import rpmfile +from debian import debfile + +DWARF2JSON = './dwarf2json' + + +class Downloader: + + def __init__(self, url_lists: List[List[str]]) -> None: + self.url_lists = url_lists + + def download_lists(self, keep = False): + for url_list in self.url_lists: + print("Downloading files...") + files_for_processing = self.download_list(url_list) + self.process_files(files_for_processing) + if not keep: + for fname in files_for_processing.values(): + if fname: + os.unlink(fname) + + def download_list(self, urls: List[str]) -> Dict[str, str]: + processed_files = {} + for url in urls: + print(" - Downloading {}".format(url)) + data = requests.get(url) + with tempfile.NamedTemporaryFile() as archivedata: + archivedata.write(data.content) + archivedata.seek(0) + if url.endswith('.rpm'): + processed_files[url] = self.process_rpm(archivedata) + elif url.endswith('.deb'): + processed_files[url] = self.process_deb(archivedata) + + return processed_files + + def process_rpm(self, archivedata) -> Optional[str]: + rpm = rpmfile.RPMFile(fileobj = archivedata) + member = None + extracted = None + for member in rpm.getmembers(): + if 'vmlinux' in member.name or 'System.map' in member.name: + print(" - Extracting {}".format(member.name)) + extracted = rpm.extractfile(member) + break + if not member or not extracted: + return None + with tempfile.NamedTemporaryFile(delete = False, + prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output: + print(" - Writing to {}".format(output.name)) + output.write(extracted.read()) + return output.name + + def process_deb(self, archivedata) -> Optional[str]: + deb = debfile.DebFile(fileobj = archivedata) + member = None + extracted = None + for member in deb.data.tgz().getmembers(): + if member.name.endswith('vmlinux') or 'System.map' in member.name: + print(" - Extracting {}".format(member.name)) + extracted = deb.data.get_file(member.name) + break + if not member or not extracted: + return None + with tempfile.NamedTemporaryFile(delete = False, + prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output: + print(" - Writing to {}".format(output.name)) + output.write(extracted.read()) + return output.name + + def process_files(self, named_files: Dict[str, str]): + """Runs the dwarf2json binary across the files""" + print("Processing Files...") + for i in named_files: + if named_files[i] is None: + print("FAILURE: None encountered for {}".format(i)) + return + args = [DWARF2JSON, 'linux'] + output_filename = 'unknown-kernel.json' + for named_file in named_files: + prefix = '--system-map' + if not 'System' in named_files[named_file]: + prefix = '--elf' + output_filename = './' + '-'.join((named_file.split('/')[-1]).split('-')[2:])[:-4] + '.json.xz' + args += [prefix, named_files[named_file]] + print(" - Running {}".format(args)) + proc = subprocess.run(args, capture_output = True) + + print(" - Writing to {}".format(output_filename)) + with lzma.open(output_filename, 'w') as f: + f.write(proc.stdout) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description = "Takes a list of URLs for Centos and downloads them") + parser.add_argument("-f", + "--file", + dest = 'filename', + metavar = "FILENAME", + help = "Filename to be read", + required = True) + parser.add_argument("-d", + "--dwarf2json", + dest = 'dwarfpath', + metavar = "PATH", + default = DWARF2JSON, + help = "Path to the dwarf2json binary", + required = True) + parser.add_argument("-k", + "--keep", + dest = 'keep', + action = 'store_true', + help = 'Keep extracted temporary files after completion', + default = False) + args = parser.parse_args() + + DWARF2JSON = args.dwarfpath + + with open(args.filename) as f: + lines = f.readlines() + + urls = [] + for i in range(len(lines) // 2): + urls += [[lines[2 * i].strip(), lines[(2 * i) + 1].strip()]] + + d = Downloader(urls) + d.download_lists(keep = args.keep) diff --git a/app/parsers/vol_Parser/doc/Makefile b/app/parsers/vol_Parser/doc/Makefile new file mode 100644 index 00000000..ffbbdf26 --- /dev/null +++ b/app/parsers/vol_Parser/doc/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Ensure we clean the autodoc files when we clean +clean: + -rm source/volatility.*.rst + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/app/parsers/vol_Parser/doc/make.bat b/app/parsers/vol_Parser/doc/make.bat new file mode 100644 index 00000000..543c6b13 --- /dev/null +++ b/app/parsers/vol_Parser/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/app/parsers/vol_Parser/doc/source/_static/favicon.ico b/app/parsers/vol_Parser/doc/source/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e2c921161c0b6f9d5b055b7225884da9edb61201 GIT binary patch literal 107414 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 literal 0 HcmV?d00001 diff --git a/app/parsers/vol_Parser/doc/source/_static/vol.png b/app/parsers/vol_Parser/doc/source/_static/vol.png new file mode 100644 index 0000000000000000000000000000000000000000..ed65b0ffca7b15d5a2f21fcbd2db60f3d3564069 GIT binary patch literal 9843 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, +which acts as a container for all the various layers and tables necessary to conduct memory analysis. + +Memory layers +------------- + +A memory layer is a body of data that can be accessed by requesting data at a specific address. Memory is seen as +sequential when accessed through sequential addresses, however, there is no obligation for the data to be stored +sequentially, and modern processors tend to store the memory in a paged format. Moreover, there is no need for the data +to be stored in an easily accessible format, it could be encoded or encrypted or more, it could be the combination of +two other sources. These are typically handled by programs that process file formats, or the memory manager of the +processor, but these are all translations (either in the geometric or linguistic sense) of the original data. + +In Volatility 3 this is represented by a directed graph, whose end nodes are +:py:class:`DataLayers ` and whose internal nodes are +specifically called a :py:class:`TranslationLayer `. +In this way, a raw memory image in the LiME file format and a page file can be +combined to form a single Intel virtual memory layer. When requesting addresses from the Intel layer, it will use the +Intel memory mapping algorithm, along with the address of the directory table base or page table map, to translate that +address into a physical address, which will then either be directed towards the swap layer or the LiME layer. Should it +be directed towards the LiME layer, the LiME file format algorithm will be translated to determine where within the file +the data is stored and that will be returned. + +.. note:: Volatility 2 had a similar concept, called address spaces, but these could only stack linearly one on top of another. + +The list of layers supported by volatility can be determined by running the `frameworkinfo` plugin. + +Templates and Objects +--------------------- + +Once we can address contiguous chunks of memory with a means to translate a virtual address (as seen by the programs) +into the actual data used by the processor, we can start pulling out +:py:class:`Objects ` by taking a +:py:class:`~volatility.framework.interfaces.objects.Template` and constructing +it on the memory layer at a specific offset. A :py:class:`~volatility.framework.interfaces.objects.Template` contains +all the information you can know about the structure of the object without actually being populated by any data. +As such a :py:class:`~volatility.framework.interfaces.objects.Template` can tell you the size of a structure and its +members, how far into the structure a particular member lives and potentially what various values in that field would +mean, but not what resides in a particular member. + +Using a :py:class:`~volatility.framework.interfaces.objects.Template` on a memory layer at a particular offset, an +:py:class:`Object ` can be constructed. In Volatility 3, once an +:py:class:`Object ` has been created, the data has been read from the +layer and is not read again. An object allows its members to be interrogated and in particular allows pointers to be +followed, providing easy access to the data contained in the object. + +.. note:: Volatility 2 would re-read the data which was useful for live memory forensics but quite inefficient for the + more common static memory analysis typically conducted. Volatility 3 requires that objects be manually reconstructed + if the data may have changed. Volatility 3 also constructs actual Python integers and floats whereas Volatility 2 + created proxy objects which would sometimes cause problems with type checking. + +Symbol Tables +------------- + +Most compiled programs know of their own templates, and define the structure (and location within the program) of these +templates as a :py:class:`Symbol `. A +:py:class:`Symbol ` is often an address and a template and can +be used to refer to either independently. Lookup tables of these symbols are often produced as debugging information +alongside the compilation of the program. Volatility 3 provides access to these through a +:py:class:`SymbolTable `, many of which can be collected +within a :py:class:`~volatility.framework.contexts.Context` as a :py:class:`SymbolSpace `. +A :py:class:`~volatility.framework.contexts.Context` can store only one :py:class:`~volatility.framework.symbols.SymbolSpace` +at a time, although a :py:class:`~volatility.framework.symbols.SymbolSpace` can store as +many :py:class:`~volatility.framework.symbols.SymbolTable` items as necessary. + +Volatility 3 uses the de facto naming convention for symbols of `module!symbol` to refer to them. It reads them from its +own JSON formatted file, which acts as a common intermediary between Windows PDB files, Linux DWARF files, other symbol +formats and the internal Python format that Volatility 3 uses to represent +a :py:class:`~volatility.framework.interfaces.objects.Template` or +a :py:class:`Symbol `. + +.. note:: Volatility 2's name for a :py:class:`~volatility.framework.symbols.SymbolSpace` was a profile, but it could + not differentiate between symbols from different modules and required special handling for 32-bit programs that + used Wow64 on Windows. This meant that all symbols lived in a single namespace with the possibility of symbol name + collisions. It read the symbols using a format called *vtypes*, written in Python code directly. + This made it less transferable or able to be used by other software. + +Plugins +------- + +A plugin acts as a means of requesting data from the user interface (and so the user) and then using it to carry out a +specific form of analysis on the :py:class:`Context ` +(containing whatever symbol tables and memory layers it may). The means of communication between the user interface and +the library is the configuration tree, which is used by components within the :py:class:`~volatility.framework.contexts.Context` +to store configurable data. After the plugin has been run, it then returns the results in a specific format known as a +:py:class:`~volatility.framework.interfaces.renderers.TreeGrid`. This ensures that the data can be handled by consumers of +the library, without knowing exactly what the data is or how it's formatted. + +Output Renderers +---------------- + +User interfaces can choose how best to present the output of the results to their users. The library always responds from +every plugin with a :py:class:`~volatility.framework.renderers.TreeGrid`, and the user interface can then determine how +best to display it. For the Command Line Interface, that might be via text output as a table, or it might output to an +SQLite database or a CSV file. For a web interface, the best output is probably as JSON where it could be displayed as +a table, or inserted into a database like Elastic Search and trawled using an existing frontend such as Kibana. + +The renderers only need to know how to process very basic types (booleans, strings, integers, bytes) and a few additional specific +ones (disassembly and various absent values). + +Configuration Tree +------------------ + +The configuration tree acts as the interface between the calling program and Volatility 3 library. Elements of the +library (such as a :py:class:`Plugin `, +a :py:class:`TranslationLayer `, +an :py:class:`Automagic `, etc.) can use the configuration +tree to inform the calling program of the options they require and/or optionally support, and allows the calling program +to provide that information when the library is then called. + +Automagic +--------- + +There are certain setup tasks that establish the context in a way favorable to a plugin before it runs, removing +several tasks that are repetitive and also easy to get wrong. These are called +:py:class:`Automagic `, since they do things like magically +taking a raw memory image and automatically providing the plugin with an appropriate Intel translation layer and an +accurate symbol table without either the plugin or the calling program having to specify all the necessary details. + +.. note:: Volatility 2 used to do this as well, but it wasn't a particularly modular mechanism, and was used only for + stacking address spaces (rather than identifying profiles), and it couldn't really be disabled/configured easily. + Automagics in Volatility 3 are a core component which consumers of the library can call or not at their discretion. diff --git a/app/parsers/vol_Parser/doc/source/complex-plugin.rst b/app/parsers/vol_Parser/doc/source/complex-plugin.rst new file mode 100644 index 00000000..85455b3b --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/complex-plugin.rst @@ -0,0 +1,287 @@ +Writing more advanced Plugins +============================= + +There are several common tasks you might wish to accomplish, there is a recommended means of achieving most of these +which are discussed below. + +Writing Reusable Methods +------------------------ +Classes which inherit from :py:class:`~volatility.framework.interfaces.plugins.PluginInterface` all have a :py:meth:`run()` method +which takes no parameters and will return a :py:class:`~volatility.framework.interfaces.renderers.TreeGrid`. Since most useful +functions are parameterized, to provide parameters to a plugin the `configuration` for the context must be appropriately manipulated. +There is scope for this, in order to run multiple plugins (see `Writing plugins that run other plugins`) but a much simpler method +is to provide a parameterized `classmethod` within the plugin, which will allow the method to yield whatever kind of output it will +generate and take whatever parameters it might need. + +This is how processes are listed, which is an often used function. The code lives within the +:py:class:`~volatility.plugins.windows.pslist.PsList` plugin but can be used by other plugins by providing the +appropriate parameters (see +:py:meth:`~volatility.plugins.windows.pslist.PsList.list_processes`). +It is up to the author of a plugin to validate that any required plugins are present and are the appropriate version. + +Writing plugins that run other plugins +-------------------------------------- + +Occasionally plugins will want to process the output from other plugins (for example, the timeliner plugin which runs all other +available plugins that feature a Timeliner interface). This can be achieved with the following example code: + +.. code-block:: python + + automagics = automagic.choose_automagic(automagic.available(self._context), plugin_class) + plugin = plugins.construct_plugin(self.context, automagics, plugin_class, self.config_path, + self._progress_callback, self.open) + +This code will first generate suitable automagics for running against the context. Unfortunately this must be re-run for +each plugin in order to populate the context's configuration correctly based on the plugin's requirements (which may vary +between plugins). Once the automagics have been constructed, the plugin can be instantiated using the helper function +:py:func:`~volatility.framework.plugins.construct_plugin` providing: + + * the base context (containing the configuration and any already loaded layers or symbol tables), + * the plugin class to run, + * the configuration path within the context for the plugin + * any callback to determine progress in lengthy operations + * an open method for the plugin to create files during the run + +With the constructed plugin, it can either be run by calling its +:py:meth:`~volatility.framework.interfaces.plugins.PluginInterface.run` method, or any other known method can +be invoked on it. + +Writing plugins that output files +--------------------------------- + +Every plugin can create files, but since the user interface must decide how to actually provide these files to the user, +an abstraction layer is used. + +The user interface specifies an open_method (which is actually a class constructor that can double as a python +ContextManager, so it can be used by the python `with` keyword). This is set on the plugin using +`plugin.set_open_method` and can then be called or accessed using `plugin.open(preferred_filename)`. There are no additional options +that can be set on the filename, and a :py:class:`~volatility.framework.interfaces.plugins.FileHandlerInterface` is the result. +This mimics an `IO[bytes]` object, which closely mimics a standard python file-like object. + +As such code for outputting to a file would be expected to look something like: + +.. code-block:: python + + with self.open(preferred_filename) as file_handle: + file_handle.write(data) + +Since self.open returns a ContextManager the file is closed automatically and thus committed for the UI to process as +necessary. If the file is not closed, the UI may not be able to properly process it and unexpected results may arise. +In certain instances you may receive a file_handle from another plugin's method, in which case the file is unlikely to be +closed to allow the preferred filename to be changed (or data to be added/modified, if necessary). + +Writing Scanners +---------------- + +Scanners are objects that adhere to the :py:class:`~volatility.framework.interfaces.layers.ScannerInterface`. They are +passed to the :py:meth:`~volatility.framework.interfaces.layers.TranslationLayerInterface.scan` method on layers which will +divide the provided range of sections (or the entire layer +if none are provided) and call the :py:meth:`~volatility.framework.interfaces.layers.ScannerInterface`'s call method +method with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner). +The offset of the chunk, within the layer, is also provided as a parameter. + +Scanners can technically maintain state, but it is not recommended since the ordering that the chunks are scanned is +not guaranteed. Scanners may be executed in parallel if they mark themselves as `thread_safe` although the threading +technique may be either standard threading or multiprocessing. Note, the only component of the scans which is +parallelized are those that go on within the scan method. As such, any processing carried out on the results yielded +by the scanner will be processed in serial. It should also be noted that generating the addresses to be scanned are +not iterated in parallel (in full, before the scanning occurs), meaning the smaller the sections to scan the quicker the +scan will run. + +Empirically it was found that scanners are typically not the most time intensive part of plugins (even those that do +extensive scanning) and so parallelism does not offer significant gains. As such, parallelism is not enabled by default +but interfaces can easily enable parallelism when desired. + +Writing/Using Intermediate Symbol Format Files +---------------------------------------------- + +It can occasionally be useful to create a data file containing the static structures that can create a +:py:class:`~volatility.framework.interfaces.objects.Template` to be instantiated on a layer. +Volatility has all the machinery necessary to construct these for you from properly formatted JSON data. + +The JSON format is documented by the JSON schema files located in schemas. These are versioned using standard .so +library versioning, so they may not increment as expected. Each schema lists an available version that can be used, +which specifies five different sections: + +* Base_types - These are the basic type names that will make up the native/primitive types +* User_types - These are the standard definitions of type structures, most will go here +* Symbols - These list offsets that are associated with specific names (and can be associated with specific type names) +* Enums - Enumerations that offer a number of choices +* Metadata - This is information about the generator, when the file was generated and similar + +Constructing an appropriate file, the file can be loaded into a symbol table as follows: + +.. code-block:: python + + table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'sub_path', 'filename') + +This code will load a JSON file from one of the standard symbol paths (volatility/symbols and volatility/framework/symbols) +under the additional directory sub_path, with a name matching filename.json +(the extension should not be included in the filename). + +The `sub_path` parameter acts as a filter, so that similarly named symbol tables for each operating system can be +addressed separately. The top level directories which sub_path filters are also checked as zipfiles to determine +any symbols within them. As such, group of symbol tables can be included in a single zip file. The filename for the +symbol tables should not contain an extension, as extensions for JSON (and compressed JSON files) will be tested to find +a match. + +Additional parameters exist, such as `native_types` which can be used to provide pre-populated native types. + +Another useful parameter is `table_mapping` which allows for type referenced inside the JSON (such as +`one_table!type_name`) would allow remapping of `one_table` to `another_table` by providing a dictionary as follows: + +.. code-block:: python + + table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'sub_path', 'filename', + table_mapping = {'one_table': 'another_table'}) + +The last parameter that can be used is called `class_types` which allows a particular structure to be instantiated on +a class other than :py:class:`~volatility.framework.objects.StructType`, allowing for additional methods to be defined +and associated with the type. + +The table name can then by used to access the constructed table from the context, such as: + +.. code-block:: python + + context.symbol_space[table_name] + +Writing new Translation Layers +------------------------------ + +Translation layers offer a way for data to be translated from a higher (domain) layer to a lower (range) layer. +The main method that must be overloaded for a translation layer is the `mapping` method. Usually this is a linear +mapping whereby a value at an offset in the domain maps directly to an offset in the range. + +Most new layers should inherit from :py:class:`~volatility.framework.layers.linear.LinearlyMappedLayer` where they +can define a mapping method as follows: + +.. code-block:: python + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + +This takes a (domain) offset and a length of block, and returns a sorted list of chunks that cover the requested amount +of data. Each chunk contains the following information, in order: + +**offset (domain offset)** + requested offset in the domain + +**chunk length** + the length of the data in the domain + +**mapped offset (range offset)** + where the data lives in the lower layer + +**mapped length** + the length of the data in the range + +**layer_name** + the layer that this data comes from + +An example (and the most common layer encountered in memory forensics) would be an Intel layer, which models the intel +page mapping system. Based on a series of tables stored within the layer itself, an intel layer can convert a virtual +address to a physical address. It should be noted that intel layers are surjective in that a single virtual address can +map to multiple physical addresses, but a single virtual address can only ever map to a single physical address. + +As a simple example, in a virtual layer which looks like `abracadabra` but maps to a physical layer that looks +like `abcdr`, requesting `mapping(5, 4)` would return: + +.. code-block:: python + + [(5,1,0,1, 'physical_layer'), + (6,1,3,1, 'physical_layer'), + (7,2,0,2, 'physical_layer') + ] + +This mapping mechanism allows for great flexibility in that chunks making up a virtual layer can come from multiple +different range layers, allowing for swap space to be used to construct the virtual layer, for example. Also, by +defining the mapping method, the read and write methods (which read and write into the domain layer) are defined for you +to write to the lower layers (which in turn can write to layers even lower than that) until eventually they arrive at a +DataLayer, such as a file or a buffer. + +This mechanism also allowed for some minor optimization in scanning such a layer, but should further control over the +scanning of layers be needed, please refer to the Layer Scanning page. + +Whilst it may seem as though some of the data seems redundant (the length values are always the same) this is not the +case for :py:class:`~volatility.framework.layers.segmented.NonLinearlySegmentedLayer`. These layers do not guarantee +that each domain address maps directly to a range address, and in fact can carry out processing on the data. These +layers are most commonly encountered as compression or encryption layers (whereby a domain address may map into a +chunk of the range, but not directly). In this instance, the mapping will likely define additional methods that can +take a chunk and process it from its original value into its final value (such as decompressing for read and compressing +for write). + +These methods are private to the class, and are used within the standard `read` and `write` methods of a layer. +A non-linear layer's mapping method should return the data required to be able to return the original data. As an +example, a run length encoded layer, whose domain data looks like `aaabbbbbcdddd` could be stored as `3a5b1c4d`. +The mapping method call for `mapping(5,4)` should return all the regions that encompass the data required. The layer +would return the following data: + +.. code-block:: python + + [(5, 4, 2, 4, 'rle layer')] + +It would then define `_decode` and `_encode` methods that could convert from one to the other. In the case of `read(5, 4)`, +the `_decode` method would be provided with the following parameters: + +.. code-block:: python + + data = "5b1c" + mapped_offset = 2 + offset = 5 + output_length = 4 + +This requires that the `_decode` method can unpack the encoding back to `bbbbbc` and also know that the decoded +block starts at 3, so that it can return just `bbbc`, as required. Such layers therefore typically need to keep much +more internal state, to keep track of which offset of encoded data relates to which decoded offset for both the mapping +and `_encode` and `_decode` methods. + +If the data processing produces known fixed length values, then it is possible to write an `_encode` method in much the +same way as the decode method. `_encode` is provided with the data to encode, the mapped_offset to write it to the lower +(range) layer, the original offset of the data in the higher (domain) layer and the value of the not yet encoded data +to write. The encoded result, regardless of length will be written over the current image at the mapped_offset. No +other changes or updates to tables, etc are carried out. + +`_encode` is much more difficult if the encoded data can be variable length, as it may involve rewriting most, if not +all of the data in the image. Such a situation is not currently supported with this API and it is strongly recommended +to raise NotImplementedError in this method. + +Communicating between layers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Layers can ask for information from lower layers using the `layer.metadata` lookup. In the following example, +a LayerStacker automagic that generates the intel TranslationLayer requests whether the base layer knows what the +`page_map_offset` value should be, a CrashDumpLayer would have that information. As such the TranslationLayer would +just lookup the `page_map_offset` value in the `base_layer.metadata` dictionary: + +.. code-block:: python + + if base_layer.metadata.get('page_layer_offset', None) is not None: + +Most layers will return `None`, since this is the default, but the CrashDumpLayer may know what the value should be, +so it therefore populates the `metadata` property. This is defined as a read-only mapping to ensure that every layer +includes data from every underlying layer. As such, CrashDumpLayer would actually specify this value by setting it +in the protected dictionary by `self._direct_metadata['page_map_offset']`. + +There is, unfortunately, no easy way to form consensus between a particular layer may want and what a particular layer +may be able to provide. At the moment, the main information that layers may populate are: + +* `os` with values of `Windows`, `Linux`, `Mac` or `unknown` +* `architecture` with values of `Intel32`, `Intel64` or `unknown` +* `pae` a boolean specifying whether the PAE mode is enabled for windows +* `page_map_offset` the value pointing to the intel page_map_offset + +Any value can be specified and used by layers but consideration towards ambiguity should be used to ensure that overly +generic names aren't used for something and then best describe something else that may be needed later on. + +.. note:: + + The data stored in metadata is *not* restored when constructed from a configuration, so metadata should only be + used as a temporary means of storing information to be used in constructing later objects and all information + required to recreate an object must be written through the requirements mechanism. + +Writing new Templates and Objects +--------------------------------- + + diff --git a/app/parsers/vol_Parser/doc/source/conf.py b/app/parsers/vol_Parser/doc/source/conf.py new file mode 100644 index 00000000..06218946 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/conf.py @@ -0,0 +1,326 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +# +# Volatility documentation build configuration file, created by +# sphinx-quickstart on Wed Apr 2 01:48:22 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import sys + +import sphinx.ext.apidoc + + +def setup(app): + volatility_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'volatility')) + + source_dir = os.path.abspath(os.path.dirname(__file__)) + sphinx.ext.apidoc.main(argv = ['-e', '-M', '-f', '-T', '-o', source_dir, volatility_directory]) + + # Go through the volatility.framework.plugins files and change them to volatility.plugins + for dir, _, files in os.walk(os.path.dirname(__file__)): + for filename in files: + if filename.startswith('volatility.framework.plugins') and filename != 'volatility.framework.plugins.rst': + # Change all volatility.framework.plugins to volatility.plugins in the file + # Rename the file + new_filename = filename.replace('volatility.framework.plugins', 'volatility.plugins') + + replace_string = b"Submodules\n----------\n\n.. toctree::\n\n" + submodules = replace_string + + # If file already exists, read out the subpackages entries from it add them to the new list + if os.path.exists(os.path.join(dir, new_filename)): + with open(os.path.join(dir, new_filename), 'rb') as newfile: + data = newfile.read() + index = data.find(replace_string) + if index > -1: + submodules = data[index:] + + with open(os.path.join(dir, new_filename), 'wb') as newfile: + with open(os.path.join(dir, filename), "rb") as oldfile: + line = oldfile.read() + correct_plugins = line.replace(b'volatility.framework.plugins', b'volatility.plugins') + correct_submodules = correct_plugins.replace(replace_string, submodules) + newfile.write(correct_submodules) + os.remove(os.path.join(dir, filename)) + elif filename == 'volatility.framework.rst': + with open(os.path.join(dir, filename), "rb") as contents: + lines = contents.readlines() + plugins_seen = False + with open(os.path.join(dir, filename), "wb") as contents: + for line in lines: + if b'volatility.framework.plugins' in line: + plugins_seen = True + if plugins_seen and line == b'': + contents.write(b' volatility.plugins') + contents.write(line) + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../..')) + +from volatility.framework import constants + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '2.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', + 'sphinx.ext.coverage', 'sphinx.ext.viewcode' +] + +try: + import sphinx_autodoc_typehints + + extensions.append('sphinx_autodoc_typehints') +except ImportError: + pass + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ['tools/templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Volatility 3' +copyright = '2012-2019, Volatility Foundation' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The full version, including alpha/beta/rc tags. +release = constants.PACKAGE_VERSION +# The short X.Y version. +version = ".".join(release.split('.')[0:2]) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True +add_module_names = False + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'default' +# html_theme = 'pydoctheme' +# html_theme_options = {'collapsiblesidebar': True} +# html_theme_path = ['tools'] +html_theme = 'sphinx_rtd_theme' +html_theme_options = {'logo_only': True} + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = '_static/vol.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = '_static/favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Volatilitydoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Volatility.tex', 'Volatility 3 Documentation', 'Volatility Foundation', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [('vol-cli', 'volatility', 'Volatility 3 Documentation', ['Volatility Foundation'], 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Volatility', 'Volatility 3 Documentation', 'Volatility Foundation', 'Volatility', + 'Memory forensics framework.', 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} + +# -- Autodoc options ------------------------------------------------------- + +# autodoc_member_order = 'groupwise' +autodoc_default_options = {'members': True, 'inherited-members': True, 'show-inheritance': True} +autoclass_content = 'both' diff --git a/app/parsers/vol_Parser/doc/source/index.rst b/app/parsers/vol_Parser/doc/source/index.rst new file mode 100644 index 00000000..c61e6e84 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/index.rst @@ -0,0 +1,33 @@ +Volatility 3 +============ + +This is the documentation for Volatility 3, the most advanced memory forensics +framework in the world. Like previous versions of the Volatility framework, +Volatility 3 is Open Source. + +:doc:`List of plugins ` + +Here are some guidelines for using Volatility 3 effectively: + +.. toctree:: + + basics + simple-plugin + vol2to3 + complex-plugin + using-as-a-library + symbol-tables + +Python Packages +=============== + +.. toctree:: + volatility + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/app/parsers/vol_Parser/doc/source/simple-plugin.rst b/app/parsers/vol_Parser/doc/source/simple-plugin.rst new file mode 100644 index 00000000..e857e830 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/simple-plugin.rst @@ -0,0 +1,234 @@ +How to Write a Simple Plugin +============================ + +This guide will step through how to construct a simple plugin using Volatility 3. + +The example plugin we'll use is :py:class:`~volatility.plugins.windows.dlllist.DllList`, which features the main traits +of a normal plugin, and reuses other plugins appropriately. + +Inherit from PluginInterface +---------------------------- + +The first step is to define a class that inherits from :py:class:`~volatility.framework.interfaces.plugins.PluginInterface`. +Volatility automatically finds all plugins defined under the various plugin directories by importing them and then +making use of any classes that inherit from :py:class:`~volatility.framework.interfaces.plugins.PluginInterface`. + +:: + + from volatility.framework import interfaces + + class DllList(interfaces.plugins.PluginInterface): + +The next step is to define the requirements of the plugin, these will be converted into options the user can provide +based on the User Interface. + +Define the plugin requirements +------------------------------ + +These requirements are the names of variables that will need to be populated in the configuration tree for the plugin +to be able to run properly. Any that are defined as optional need not necessarily be provided. + +:: + + @classmethod + def get_requirements(cls): + return [requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", + description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', + plugin = pslist.PsList, + version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True)] + + +This is a classmethod, because it is called before the specific plugin object has been instantiated (in order to know how +to instantiate the plugin). At the moment these requirements are fairly straightforward: + +:: + + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + +This requirement indicates that the plugin will operate on a single +:py:class:`TranslationLayer `. The name of the +loaded layer will appear in the plugin's configuration under the name ``primary``. Requirement values can be +accessed within the plugin through the plugin's `config` attribute (for example ``self.config['pid']``). + +.. note:: The name itself is dynamic depending on the other layers already present in the Context. Always use the value + from the configuration rather than attempting to guess what the layer will be called. + +Finally, this defines that the translation layer must be on the Intel Architecture. At the moment, this acts as a filter, +failing to be satisfied by memory images that do not match the architecture required. + +Most plugins will only operate on a single layer, but it is entirely possible for a plugin to request two different +layers, for example a plugin that carries out some form of difference or statistics against multiple memory images. + +This requirement (and the next two) are known as Complex Requirements, and user interfaces will likely not directly +request a value for this from a user. The value stored in the configuration tree for a +:py:class:`~volatility.framework.configuration.requirements.TranslationLayerRequirement` is +the string name of a layer present in the context's memory that satisfies the requirement. + +:: + + requirements.SymbolTableRequirement(name = "nt_symbols", + description = "Windows kernel symbols"), + +This requirement specifies the need for a particular +:py:class:`SymbolTable ` +to be loaded. This gets populated by various +:py:class:`Automagic ` as the nearest sibling to a particular +:py:class:`~volatility.framework.configuration.requirements.TranslationLayerRequirement`. +This means that if the :py:class:`~volatility.framework.configuration.requirements.TranslationLayerRequirement` +is satisfied and the :py:class:`Automagic ` can determine +the appropriate :py:class:`SymbolTable `, the +name of the :py:class:`SymbolTable ` will be stored in the configuration. + +This requirement is also a Complex Requirement and therefore will not be requested directly from the user. + +:: + + requirements.PluginRequirement(name = 'pslist', + plugin = pslist.PsList, + version = (1, 0, 0)), + +This requirement indicates that the plugin will make use of another plugin's code, and specifies the version requirements +on that plugin. The version is specified in terms of Semantic Versioning, meaning that to be compatible, the major +versions must be identical and the minor version must be equal to or higher than the one provided. This requirement +does not make use of any data from the configuration, even if it were provided, it is merely a functional check before +running the plugin. + +:: + + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + +The final requirement is a List Requirement, populated by integers. The description will be presented to the user to +describe what the value represents. The optional flag indicates that the plugin can function without the ``pid`` value +being defined within the configuration tree at all. + +Define the `run` method +----------------------- + +The run method is the primary method called on a plugin. It takes no parameters (these have been passed through the +context's configuration tree, and the context is provided at plugin initialization time) and returns an unpopulated +:py:class:`~volatility.framework.interfaces.renderers.TreeGrid` object. These are typically constructed based on a +generator that carries out the bulk of the plugin's processing. The +:py:class:`~volatility.framework.interfaces.renderers.TreeGrid` also specifies the column names and types +that will be output as part of the :py:class:`~volatility.framework.interfaces.renderers.TreeGrid`. + +:: + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), + ("Process", str), + ("Base", format_hints.Hex), + ("Size", format_hints.Hex), + ("Name", str), + ("Path", str)], + self._generator(pslist.PsList.list_processes(self.context, + self.config['primary'], + self.config['nt_symbols'], + filter_func = filter_func))) + +In this instance, the plugin constructs a filter (using the PsList plugin's *classmethod* for creating filters). +It checks the plugin's configuration for the ``pid`` value, and passes it in as a list if it finds it, or None if +it does not. The :py:func:`~volatility.plugins.windows.pslist.PsList.create_pid_filter` method accepts a list of process +identifiers that are included in the list. If the list is empty, all processes are returned. + +The next line specifies the columns by their name and type. The types are simple types (int, str, bytes, float, and bool) +but can also provide hints as to how the output should be displayed (such as a hexidecimal number, using +:py:class:`volatility.framework.renderers.format_hints.Hex`). +This indicates to user interfaces that the value should be displayed in a particular way, but does not guarantee that the value +will be displayed that way (for example, if it doesn't make sense to do so in a particular interface). + +Finally, the generator is provided. The generator accepts a list of processes, which is gathered using a different plugin, +the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin. That plugin features a *classmethod*, +so that other plugins can call it. As such, it takes all the necessary parameters rather than accessing them +from a configuration. Since it must be portable code, it takes a context, as well as the layer name, +symbol table and optionally a filter. In this instance we unconditionally +pass it the values from the configuration for the ``primary`` and ``nt_symbols`` requirements. This will generate a list +of :py:class:`~volatility.framework.symbols.windows.extensions.EPROCESS` objects, as provided by the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin, +and is not covered here but is used as an example for how to share code across plugins +(both as the provider and the consumer of the shared code). + +Define the generator +-------------------- +The :py:class:`~volatility.framework.interfaces.renderers.TreeGrid` can be populated without a generator, +but it is quite a common model to use. This is where the main processing for this plugin lives. + +:: + + def _generator(self, procs): + + for proc in procs: + + for entry in proc.load_order_modules(): + + BaseDllName = FullDllName = renderers.UnreadableValue() + try: + BaseDllName = entry.BaseDllName.get_string() + # We assume that if the BaseDllName points to an invalid buffer, so will FullDllName + FullDllName = entry.FullDllName.get_string() + except exceptions.InvalidAddressException: + pass + + yield (0, (proc.UniqueProcessId, + proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, + errors = 'replace'), + format_hints.Hex(entry.DllBase), format_hints.Hex(entry.SizeOfImage), + BaseDllName, FullDllName)) + +This iterates through the list of processes and for each one calls the :py:meth:`~volatility.framework.symbols.windows.extensions.EPROCESS.load_order_modules` method on it. This provides +a list of the loaded modules within the process. + +The plugin then defaults the ``BaseDllName`` and ``FullDllName`` variables to an :py:class:`~volatility.framework.renderers.UnreadableValue`, +which is a way of indicating to the user interface that the value couldn't be read for some reason (but that it isn't fatal). +There are currently four different reasons a value may be unreadable: + +* **Unreadble**: values which are empty because the data cannot be read +* **Unparsable**: values which are empty because the data cannot be interpreted correctly +* **NotApplicable**: values which are empty because they don't make sense for this particular entry +* **NotAvailable**: values which cannot be provided now (but might in a future run, via new symbols or an updated plugin) + +This is a safety provision to ensure that the data returned by the Volatility library is accurate and describes why +information may not be provided. + +The plugin then takes the process's ``BaseDllName`` value, and calls :py:meth:`~volatility.framework.symbols.windows.extensions.UNICODE_STRING.get_string` on it. All structure attributes, +as defined by the symbols, are directly accessible and use the case-style of the symbol library it came from (in Windows, +attributes are CamelCase), such as ``entry.BaseDllName`` in this instance. Any attribtues not defined by the symbol but added +by Volatility extensions cannot be properties (in case they overlap with the attributes defined in the symbol libraries) +and are therefore always methods and prepended with ``get_``, in this example ``BaseDllName.get_string()``. + +Finally, ``FullDllName`` is populated. These operations read from memory, and as such, the memory image may be unable to +read the data at a particular offset. This will cause an exception to be thrown. In Volatility 3, exceptions are thrown +as a means of communicating when something exceptional happens. It is the responsibility of the plugin developer to +appropriately catch and handle any non-fatal exceptions and otherwise allow the exception to be thrown by the user interface. + +In this instance, the :py:class:`~volatility.framework.exceptions.InvalidAddressException` class is caught, which is thrown +by any layer which cannot access an offset requested of it. Since we have already populated both values with ``UnreadableValue`` +we do not need to write code for the exception handler. + +Finally, we yield the record in the format required by the :py:class:`~volatility.framework.interfaces.renderers.TreeGrid`, +a tuple, listing the indentation level (for trees) and then the list of values for each column. +This plugin demonstrates casting a value ``ImageFileName`` to ensure it's returned +as a string with a specific maximum length, rather than its original type (potentially an array of characters, etc). +This is carried out using the :py:meth:`~volatility.framework.interfaces.objects.ObjectInterface.cast` method which takes a type (either a native type, such as string or pointer, or a +structure type defined in a :py:class:`SymbolTable ` +such as ``!_UNICODE``) and the parameters to that type. + +Since the cast value must populate a string typed column, it had to be a Python string (such as being cast to the native +type string) and could not have been a special Structure such as ``_UNICODE``. For the format hint columns, the format +hint type must be used to ensure the error checking does not fail. + + diff --git a/app/parsers/vol_Parser/doc/source/symbol-tables.rst b/app/parsers/vol_Parser/doc/source/symbol-tables.rst new file mode 100644 index 00000000..4e44e836 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/symbol-tables.rst @@ -0,0 +1,53 @@ +Creating New Symbol Tables +========================== + +This page details how symbol tables are located and used by Volatility, and documents the tools and methods that can be +used to make new symbol tables. + +How Volatility finds symbol tables +---------------------------------- + +All files are stored as JSON data, they can be in pure JSON files as ``.json``, or compressed as ``.json.gz`` or ``.json.xz``. +Volatility will automatically decompress them on use. It will also cache their contents (compressed) when used, located +under the user's home directory, in :file:`.cache/volatility3`, along with other useful data. The cache directory currently +cannot be altered. + +Symbol table JSON files live, by default, under the :file:`volatility/symbols`, underneath an operating system directory +(currently one of :file:`windows`, :file:`mac` or :file:`linux`). The symbols directory is configurable within the framework and can +usually be set within the user interface. + +These files can also be compressed into ZIP files, which Volatility will process in order to locate symbol files. +The ZIP file must be named after the appropriate operating system (such as `linux.zip`, `mac.zip` or `windows.zip`). +Inside the ZIP file, the directory structure should match the uncompressed operating system directory. + +Windows symbol tables +--------------------- + +For Windows systems, Volatility accepts a string made up of the GUID and Age of the required PDB file. It then +searches all files under the configured symbol directories under the windows subdirectory. Any that match the filename +pattern of :file:`/-.json` (or any compressed variant) will be used. If such a symbol table cannot be found, then +the associated PDB file will be downloaded from Microsoft's Symbol Server and converted into the appropriate JSON +format, and will be saved in the correct location. + +Windows symbol tables can be manually constructed from an appropriate PDB file. The primary tool for doing this +is built into Volatility 3, called :file:`pdbconv.py`. It can be run from the top-level Volatility path, using the +following command: + +:command:`PYTHONPATH="." python volatility/framework/symbols/windows/pdbconv.py` + +The :envvar:`PYTHONPATH` environment variable is not required if the Volatility library is installed in the system's library path +or a virtual environment. + +Mac/Linux symbol tables +----------------------- + +For Mac/Linux systems, both use the same mechanism for identification. JSON files live under the symbol directories, +under either the :file:`linux` or :file:`mac` directories. The generated files contain an identifying string (the operating system +banner), which Volatility's automagic can detect. Volatility caches the mapping between the strings and the symbol +tables they come from, meaning the precise file names don't matter and can be organized under any necessary hierarchy +under the operating system directory. + +Linux and Mac symbol tables can be generated from a DWARF file using a tool called `dwarf2json `_. Currently a kernel +with debugging symbols is the only suitable means for recovering all the information required by most Volatility plugins. +Once a kernel with debugging symbols/appropriate DWARF file has been located, `dwarf2json `_ will convert it into an +appropriate JSON file. diff --git a/app/parsers/vol_Parser/doc/source/using-as-a-library.rst b/app/parsers/vol_Parser/doc/source/using-as-a-library.rst new file mode 100644 index 00000000..abed6522 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/using-as-a-library.rst @@ -0,0 +1,244 @@ +Using Volatility 3 as a Library +=============================== + +This portion of the documentation discusses how to access the Volatility 3 framework from an external application. + +The general process of using volatility as a library is to as follows: + +1. :ref:`create_context` +2. (Optional) :ref:`available_plugins` +3. (Optional) :ref:`config_options` +4. :ref:`context_config` +5. (Optional) :ref:`use_automagic` +6. :ref:`run_plugin` +7. :ref:`render_treegrid` + +.. _create_context: + +Creating a context +------------------ + +First we make sure the volatility framework works the way we expect it (and is the version we expect). The +versioning used is semantic versioning, meaning any version with the same major number and a higher or equal +minor number will satisfy the requirement. An example is below since the CLI doesn't need any of the features +from versions 1.1 or 1.2: + +:: + + volatility.framework.require_interface_version(1, 0, 0) + +Contexts can be spun up quite easily, just construct one. It's not a singleton, so multiple contexts can be +constructed and operate independently, but be aware of which context you're handing where and make sure to use +the correct one. Typically once a context has been handed to a plugin, all objects will be created with a reference +to that context. + +:: + + ctx = contexts.Context() # Construct a blank context + +.. _available_plugins: + +Determine what plugins are available +------------------------------------ + +You can also interrogate the framework to see which plugins are available. First we have to try to load all +available plugins. The :py:func:`~volatility.framework.import_files` method will automatically use the module +paths for the provided module (in this case, volatility.plugins) and walk the directory (or directories) loading up +all python files. Any import failures will be provided in the failures return value, unless the second parameter is +False in which case the call will raise any exceptions encountered. Any additional directories containing plugins +should be added to the `__path__` attribute for the `volatility.plugins` module. The standard paths should generally +also be included, which can be found in `volatility.constants.PLUGINS_PATH`. + +:: + + volatility.plugins.__path__ = + constants.PLUGINS_PATH + failures = framework.import_files(volatility.plugins, True) + +Once the plugins have been imported, we can interrogate which plugins are available. The +:py:func:`~volatility.framework.list_plugins` call will +return a dictionary of plugin names and the plugin classes. + +:: + + plugin_list = framework.list_plugins() + +.. _config_options: + +Determine what configuration options a plugin requires +------------------------------------------------------ + +For each plugin class, we can call the classmethod `requirements` on it, which will return a list of objects that +adhere to the :py:class:`~volatility.framework.interfaces.configuration.RequirementInterface` method. The various +types of Requirement are split roughly in two, +:py:class:`~volatility.framework.interfaces.configuration.SimpleTypeRequirement` (such as integers, booleans, floats +and strings) and more complex requirements (such as lists, choices, multiple requirements, translation layer +requirements or symbol table requirements). A requirement just specifies a type of data and a name, and must be +combined with a configuration hierarchy to have meaning. + +List requirements are a list of simple types (integers, booleans, floats and strings), choices must match the available +options, multiple requirements needs all their subrequirements fulfilled and the other types require the names of +valid translation layers or symbol tables within the context, respectively. Luckily, each of these requirements can +tell you whether they've been fulfilled or not later in the process. For now, they can be used to ask the user to +fill in any parameters they made need to. Some requirements are optional, others are not. + +The plugin is essentially a multiple requirement. It should also be noted that automagic classes can have requirements +(as can translation layers). + +.. _context_config: + +Set the configuration in the context +------------------------------------ + +Once you know what requirements the plugin will need, you can populate them within the `context.config`. +The configuration is essentially a hierarchical tree of values, much like the windows registry. +Each plugin is instantiated at a particular branch within the hierarchy and will look for its configuration +options under that hierarchy (if it holds any configurable items, it will likely instantiate those at a point +underneaths its own branch). To set the hierarchy, you'll need to know where the configurables will be constructed. + +For this example, we'll assume plugins' base_config_path is set as `plugins`, and that automagics are configured under +the `automagic` tree. We'll see later how to ensure this matches up with the plugins and automagic when they're +constructed. Joining configuration options should always be carried out using +:py:func:`~volatility.framework.interfaces.configuration.path_join` +in case the separator value gets changed in the future. Configuration items can then be set as follows: + +:: + + config_path = path_join(base_config_path, plugin.__class__.__name__, ) + context.config['plugins..'] = value + +.. _use_automagic: + +Using automagic to complete the configuration +--------------------------------------------- + +Many of the options will require a lot of construction (layers on layers on layers). The automagic functionality +is there to help take some of that burden away. There are automagics designed to stack layers (such as compression and +file formats, as well as architectures) and automagics for determining critical information from windows, linux and mac +layers about the operating system. The list of available automagics can be found using: + +:: + + available_automagics = automagic.available(ctx) + +This again, will require that all automagic modules have been loaded but this should happen simply as part of importing +the `automagic` module. The available list will be pre-instantiated copies of the automagic with their configuration +path and context provided (based on `constants.AUTOMAGIC_CONFIG_PATH` and the automagic class name). + +A suitable list of automagics for a particular plugin (based on operating system) can be found using: + +:: + + automagics = automagic.choose_automagic(available_automagics, plugin) + +This will take the plugin module, extract the operating system (first level of the hierarchy) and then return just +the automagics which apply to the operating system. + +These automagics can then be run by providing the list, the context, the plugin to be run, the hierarchy name that +the plugin will be constructed on ('plugins' by default) and a progress_callback. This is a callable which takes +a percentage of completion and a description string and will be called throughout the process to indicate to the +user how much progress has been made. + +:: + + errors = automagic.run(automagics, context, plugin, base_config_path, progress_callback = progress_callback) + +Any exceptions that occur during the execution of the automagic will be returned as a list of exceptions. + +.. _run_plugin: + +Run the plugin +-------------- + +Firstly, we should check whether the plugin will be able to run (ie, whether the configuration options it needs +have been successfully set). We do this as follow (where plugin_config_path is the base_config_path (which defaults +to `plugins` and then the name of the class itself): + +:: + + unsatisfied = plugin.unsatisfied(context, plugin_config_path) + +If unsatisfied is an empty list, then the plugin has been given everything it requires. If not, it will be a +Dictionary of the hierarchy paths and their associated requirements that weren't satisfied. + +The plugin can then be instantiated with the context (containing the plugin's configuration) and the path that the +plugin can find its configuration at. A progress_callback can also be provided to give users feedback whilst the +plugin is running. Also, should the plugin produce files, an open_method can be set on the plugin, which will +be called whenever a plugin produces an auxiliary file. + +:: + + constructed = plugin(context, plugin_config_path, progress_callback = progress_callback) + constructed.set_open_method(file_handler) + +The file_handler must adhere to the :py:class:`~volatility.framework.interfaces.plugins.FileHandlerInterface`, +which represents an IO[bytes] object but also contains a `preferred_filename` attribute as a hint. + +All of this functionality has been condensed into a framework method called `construct_plugin` which will +take and run the automagics, and instantiate the plugin on the provided `base_config_path`. It also +accepts an optional progress_callback and an optional file_consumer. + +:: + + constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback, file_consumer) + +Finally the plugin can be run, and will return a :py:class:`~volatility.framework.interfaces.renderers.TreeGrid`. + +:: + + treegrid = constructed.run() + +.. _render_treegrid: + +Render the TreeGrid +------------------- + +The results are now in a structure of rows, with a hierarchy (allowing a row to be a child of another row). + +The TreeGrid can tell you what columns it contains, and the types of each column, but does not contain any data yet. +It must first be populated. This actually iterates through the results of the plugin, which may +have been provided as a generator, meaning this step may take the actual processing time, whilst the plugin +does the actual work. This can return an exception if one occurs during the running of the plugin. + +The results can be accessed either as the results are being processed, or by visiting the nodes in the tree +once it is fully populated. In either case, a visitor method will be required. The visitor method +should accept a :py:class:`~volatility.framework.interfaces.renderers.TreeNode` and an `accumulator`. It will +return an updated accumulator. + +When provided a :py:class:`~volatility.framework.interfaces.renderers.TreeNode`, it can be accessed as a dictionary +based on the column names that the treegrid contains. It should be noted that each column can contain only the +type specified in the `column.type` field (which can be a simple type like string, integer, float, bytes or +a more complex type, like a DateTime, a Disassembly or a descendant of +:py:class:`~volatility.framework.interfaces.renderers.BaseAbsentValue`). The various fields may also be wrapped in +`format_hints` designed to tell the user interface how to render the data. These hints can be things like Bin, Hex or +HexBytes, so that fields like offsets are displayed in hex form or so that bytes are displayed in their hex form rather +than their raw form. Descendants of :py:class:`~volatility.framework.interfaces.renderers.BaseAbsentValue` can currently +be one of +:py:class:`~volatility.framework.renderers.UnreadableValue`, +:py:class:`~volatility.framework.renderers.UnparsableValue`, +:py:class:`~volatility.framework.renderers.NotApplicableValue` or +:py:class:`~volatility.framework.renderers.NotAvailableValue`. These indicate that data could not be read from the +memory for some reason, could not be parsed properly, was not applicable or was not available. + +A simple text renderer (that returns output immediately) would appear as follows. This doesn't use +the accumulator, but instead uses print to directly produce the output. This is not recommended: + +:: + + for column in grid.columns: + print(column.name) + + def visitor(node, _accumulator): + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + print("*" * max(0, node.path_depth - 1), end = " ") + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + print(repr(node.values[column_index]), end = '\t') + + print('') + return None + + grid.populate(visitor, None) + +More complex examples of renderers can be found in the default CLI implementation, such as the +:py:class:`~volatility.cli.text_renderer.QuickTextRenderer` or the +:py:class:`~volatility.cli.text_renderer.PrettyTextRenderer`. diff --git a/app/parsers/vol_Parser/doc/source/vol-cli.rst b/app/parsers/vol_Parser/doc/source/vol-cli.rst new file mode 100644 index 00000000..06990687 --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/vol-cli.rst @@ -0,0 +1,123 @@ +:orphan: + +volatility manual page +====================== + +Synopsis +-------- + +**volatility** [-h] [-c CONFIG] [--parallelism [{processes,threads,off}]] + [-e EXTEND] [-p PLUGIN_DIRS] [-s SYMBOL_DIRS] [-v] [-l LOG] + [-o OUTPUT_DIR] [-q] [-r RENDERER] [-f FILE] + [--write-config] [--single-location SINGLE_LOCATION] + [--single-swap-locations SINGLE_SWAP_LOCATIONS] + ... + +Description +----------- + +Volatility is a program used to analyze memory images from a computer and +extract useful information from windows, linux and mac operating systems. +The framework is intended to introduce people to the techniques and +complexities associated with extracting digital artifacts from volatile +memory samples and provide a platform for further work into this exciting +area of research. + +The command line tool allows developers to distribute and easily use the +plugins of the framework against memory images of their choice. + +Plugins may define their own options, these are dynamic and therefore not +listed in this man page. Plugin options must be listed after the plugin +name. A list of the options for a specific plugin is available by running +"**volatility** --help". + +Options +------- + +-h, --help + Shows a help message that lists these options, and the available plugins. + If used after a plugin has been chosen, help will show any options which + that particular plugin can accept. + +-c CONFIG, --config CONFIG + Loads a JSON configuration from the CONFIG file + +--parallelism [{processes,threads,off}] + Enables parallelism (defaults to processes if no argument given). The + parallelism can be either off, or multithreaded (but due to python's GIL + still only takes up a single CPU) or multiprocessed (which spawns other + processes, but can use the whole of the CPU). Currently parallelism is + *experimental* and provides minimal benefits whilst still being developed + +-e EXTEND, --extend EXTEND + Extends an existing configuration with a single directive as specified by + EXTEND. Extensions must be of the form **configuration.item.name=value** + +-p PLUGIN_DIRS, --plugin-dirs PLUGIN_DIRS + Specified a semi-colon separated list of paths that contain directories + where plugins may be found. These paths are searched before the default + paths when loading python files for plugins. This can therefore be used + to override built-in plugins. NOTE: All python code within this directory + and any subdirectories will be evaluated during normal operation. + +-s SYMBOL_DIRS, --symbol-dirs SYMBOL_DIRS + SYMBOL_DIRS is a semi-colon separated list of paths that contain symbol + files or symbol zip packs. Symbols must be within a particular directory + structure if they depending on the operating system of the symbols, + whilst symbol packs must be in the root of the directory and named after + the after the operating system to which they apply. + +-v, --verbose + A flag which can be used multiple times, each time increasing the level of + detail in the logs produced. + +-l LOG, --log LOG + Writes all logs (even those not displayed on screen) to the file specified + by LOG. + +-o OUTPUT_DIR, --output-dir OUTPUT_DIR + Should volatility generate any files during its run (such as a `dump` + plugin), the files will be created in the OUTPUT_DIR directory. This + defaults to the current working directory. + +-q, --quiet + When present, this flag mutes the progress feedback for operations. This + can be beneficial when piping the output directly to a file or another + tool. This also removes the + +-r RENDERER, --renderer RENDERER + Specifies the output format in which to display results. The default is + the quick renderer, which produces output immediately at the cost of + spacing for columns. Pretty outputs the results at the end, but aligns + them all to column width. json and jsonl output JSON (or JSON lines) + format, which can be used directly in conjunction with -q. + +-f FILE, --file FILE + This takes the FILE value, and formats it as a file:// URL for use with + the --single-location field, which is the image that the automagic will + attempt to build upon, and can be considered the input for the program. + +--write-config + This flag specifies that volatility should write or overwrite a file + called config.json in the current directory. The file will contain + the necessary JSON configuration to recreate the environment that the + plugin was previously run in. This configuration *may* be accepted by + other plugins, but there's no guarantee that plugins use the same + configuration options. + +--single-location SINGLE_LOCATION + This specifies a URL which will be downloaded if necessary, and built + upon by the automagic and, since most plugins require a single memory + image, can be considered the input for the program. + +--single-swap-locations SINGLE_SWAP_LOCATIONS + A comma-separated list of swap files to be considered as part of the + memory image specified by the single-location or file parameters. + +**** + The name of the plugin to execute (these are usually categorized by + the operating system, such as `windows.pslist.PsList`). Any subtring + that uniquely matches the desired plugin name can be used. As such + `hivescan` would match `windows.registry.hivescan.HiveScan`, but + `pslist` is ambiguous because it could match `windows.pslist` or + `linux.pslist`. diff --git a/app/parsers/vol_Parser/doc/source/vol2to3.rst b/app/parsers/vol_Parser/doc/source/vol2to3.rst new file mode 100644 index 00000000..8e82ba8a --- /dev/null +++ b/app/parsers/vol_Parser/doc/source/vol2to3.rst @@ -0,0 +1,80 @@ +Changes between Volatility 2 and Volatility 3 +============================================= + +Library and Context +------------------- + +Volatility 3 has been designed from the ground up to be a library, this means the components are independent and all +state required to run a particular plugin at a particular time is self-contained in an object derived from +a :py:class:`~volatility.framework.interfaces.context.ContextInterface`. + +The context contains the two core components that make up Volatility, layers of data and the available symbols. + +Symbols and Types +----------------- + +Volatility 3 no longer uses profiles, it comes with an extensive library of +:py:class:`symbol tables `, and can generate new symbol +tables for most windows memory images, based on the memory image itself. This allows symbol tables to include specific +offsets for locations (symbol locations) based on that operating system in particular. This means it is easier and quicker +to identify structures within an operating system, by having known offsets for those structures provided by the official +debugging information. + +Object Model changes +-------------------- + +The object model has changed as well, objects now inherit directly from their Python counterparts, meaning an integer +object is actually a Python integer (and has all the associated methods, and can be used whereever a normal int could). +In Volatility 2, a complex proxy object was constructed which tried to emulate all the methods of the host object, but +ultimately it was a different type and could not be used in the same places (critically, it could make the ordering of +operations important, since a + b might not work, but b + a might work fine). + +Volatility 3 has also had significant speed improvements, where Volatility 2 was designed to allow access to live memory +images and situations in which the underlying data could change during the run of the plugin, in Volatility 3 the data +is now read once at the time of object construction, and will remain static, even if the underlying layer changes. +This was because live memory analysis was barely ever used, and this feature could cause a particular value to be +re-read many times over for no benefit (particularly since each re-read could result in many additional image reads +from following page table translations). + +Finally, in order to provide Volatility specific information without impact on the ability for structures to have members +with arbitrary names, all the metadata about the object (such as its layer or offset) have been moved to a read-only :py:meth:`~volatility.framework.interfaces.objects.ObjectInterface.vol` +dictionary. + +Further the distinction between a :py:class:`~volatility.framework.interfaces.objects.Template` (the thing that +constructs an object) and the :py:class:`Object ` itself has +been made more explicit. In Volatility 2, some information (such as size) could only be determined from a constructed object, +leading to instantiating a template on an empty buffer, just to determine the size. In Volatility 3, templates contain +information such as their size, which can be queried directly without constructing the object. + +Layer and Layer dependencies +---------------------------- +Address spaces in Volatility 2, are now more accurately referred to as +:py:class:`Translation Layers `, since each one typically sits +atop another and can translate addresses between the higher logical layer and the lower physical layer. Address spaces in +Volatility 2 were strictly limited to a stack, one on top of one other. In Volatility 3, layers can have multiple +"dependencies" (lower layers), which allows for the integration of features such as swap space. + +Automagic +--------- +In Volatility 2, we often tried to make this simpler for both users and developers. This resulted in something was +referred to as automagic, in that it was magic that happened automatically. We've now codified that more, so that the +automagic processes are clearly defined and can be enabled or disabled as necessary for any particular run. We also +included a stacker automagic to emulate the most common feature of Volatility 2, automatically stacking address spaces +(now translation layers) on top of each other. + +Searching and Scanning +---------------------- +Scanning is very similar to scanning in Volatility 2, a scanner object (such as a +:py:class:`~volatility.framework.layers.scanners.BytesScanner` or :py:class:`~volatility.framework.layers.scanners.RegExScanner`) is +primed with the data to be searched for, and the :py:meth:`~volatility.framework.interfaces.layers.DataLayerInterface.scan` method is called on the layer to be searched. + +Output Rendering +---------------- +This is extremely similar to Volatility 2, because we were developing it for Volatility 3 when we added it to Volatility 2. +We now require that all plugins produce output in a :py:class:`~volatility.framework.interfaces.renderers.TreeGrid` object, +which ensure that the library can be used regardless of which interface is driving it. An example web GUI is also available +called Volumetric which allows all the plugins that can be run from the command line to be run from a webpage, and offers +features such as automatic formatting and sorting of the data, which previously couldn't be provided easily from the CLI. + +There is also the ability to provide file output such that the user interface can provide a means to render or save those files. + diff --git a/app/parsers/vol_Parser/interface.py b/app/parsers/vol_Parser/interface.py new file mode 100644 index 00000000..112bb427 --- /dev/null +++ b/app/parsers/vol_Parser/interface.py @@ -0,0 +1,48 @@ + +import sys +import os +import subprocess +import ast +import json + + +# return json in a beautifier +def json_beautifier(js): + return json.dumps(js, indent=4, sort_keys=True) + +def imain(file, parser): + + try: + CurrentPath=os.path.dirname(os.path.abspath(__file__)) + + + cmd = 'python3 '+ CurrentPath+'/vol_Parser.py -k -f "' + file + '" -p ' + parser + proc = subprocess.Popen(cmd, shell=True ,stdin=None , stdout=subprocess.PIPE , stderr=subprocess.PIPE) + res , err = proc.communicate() + if err != "": + raise Exception(err.split("\n")[-2]) + + res = res.split('\n') + data = "" + for line in res: + if line.startswith('['): + data += line + if data == "": + return [] + d = [] + for i in ast.literal_eval(data): + if type(i) == dict: + if len(i.keys()): + d.append(i) + else: + continue + else: + d.append(json.loads(i) ) + return d + + + except Exception as e: + exc_type,exc_obj,exc_tb = sys.exc_info() + msg = "[-] [Error] " + str(parser) + " Parser: " + str(exc_obj) + " - Line No. " + str(exc_tb.tb_lineno) + return (None , msg) + diff --git a/app/parsers/vol_Parser/mypy.ini b/app/parsers/vol_Parser/mypy.ini new file mode 100644 index 00000000..6fb9f9ed --- /dev/null +++ b/app/parsers/vol_Parser/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +mypy_path = ./stubs +show_traceback = True +ignore_missing_imports = True diff --git a/app/parsers/vol_Parser/parser_plugins/__init__.py b/app/parsers/vol_Parser/parser_plugins/__init__.py new file mode 100644 index 00000000..d8f56a57 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/__init__.py @@ -0,0 +1,20 @@ +import parser_plugins.mem_info +import parser_plugins.mem_cmdline +import parser_plugins.mem_dlllist +import parser_plugins.mem_pslist +import parser_plugins.mem_envars +import parser_plugins.mem_FileScan +import parser_plugins.mem_ProcessSID +import parser_plugins.mem_handles +import parser_plugins.mem_ModScan +import parser_plugins.mem_Modules +import parser_plugins.mem_mutantScan +import parser_plugins.mem_netScan +import parser_plugins.mem_ProcessPrivs +import parser_plugins.mem_hiveList +import parser_plugins.mem_userAssist +import parser_plugins.mem_SSDT +import parser_plugins.mem_symlink +import parser_plugins.mem_vadInfo +import parser_plugins.mem_virtMap +import parser_plugins.mem_timeliner \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_FileScan.py b/app/parsers/vol_Parser/parser_plugins/mem_FileScan.py new file mode 100644 index 00000000..3a374784 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_FileScan.py @@ -0,0 +1,8 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_ModScan.py b/app/parsers/vol_Parser/parser_plugins/mem_ModScan.py new file mode 100644 index 00000000..9064a84b --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_ModScan.py @@ -0,0 +1,9 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Base"] = hex(res[i]["Base"]) + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_Modules.py b/app/parsers/vol_Parser/parser_plugins/mem_Modules.py new file mode 100644 index 00000000..3a374784 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_Modules.py @@ -0,0 +1,8 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_ProcessPrivs.py b/app/parsers/vol_Parser/parser_plugins/mem_ProcessPrivs.py new file mode 100644 index 00000000..571ced41 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_ProcessPrivs.py @@ -0,0 +1,7 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_ProcessSID.py b/app/parsers/vol_Parser/parser_plugins/mem_ProcessSID.py new file mode 100644 index 00000000..353d6c66 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_ProcessSID.py @@ -0,0 +1,8 @@ + + + +def imain(res): + print("here") + for i in range(len(res)): + del res[i]['__children'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_SSDT.py b/app/parsers/vol_Parser/parser_plugins/mem_SSDT.py new file mode 100644 index 00000000..023b6107 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_SSDT.py @@ -0,0 +1,9 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Address"] = hex(res[i]["Address"]) + return res + \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_cmdline.py b/app/parsers/vol_Parser/parser_plugins/mem_cmdline.py new file mode 100644 index 00000000..571ced41 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_cmdline.py @@ -0,0 +1,7 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_dlllist.py b/app/parsers/vol_Parser/parser_plugins/mem_dlllist.py new file mode 100644 index 00000000..faca8de6 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_dlllist.py @@ -0,0 +1,9 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + + res[i]['@timestamp'] = res[i]['LoadTime'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_envars.py b/app/parsers/vol_Parser/parser_plugins/mem_envars.py new file mode 100644 index 00000000..353d6c66 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_envars.py @@ -0,0 +1,8 @@ + + + +def imain(res): + print("here") + for i in range(len(res)): + del res[i]['__children'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_handles.py b/app/parsers/vol_Parser/parser_plugins/mem_handles.py new file mode 100644 index 00000000..571ced41 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_handles.py @@ -0,0 +1,7 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_hiveList.py b/app/parsers/vol_Parser/parser_plugins/mem_hiveList.py new file mode 100644 index 00000000..3a374784 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_hiveList.py @@ -0,0 +1,8 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_info.py b/app/parsers/vol_Parser/parser_plugins/mem_info.py new file mode 100644 index 00000000..95cdc0d6 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_info.py @@ -0,0 +1,12 @@ + + + +def imain(res): + data = {} + for r in res: + data[r['Variable']] = r['Value'] + + + data['@timestamp'] = data['SystemTime'].replace(" " , 'T') + + return [data] \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_mutantScan.py b/app/parsers/vol_Parser/parser_plugins/mem_mutantScan.py new file mode 100644 index 00000000..3a374784 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_mutantScan.py @@ -0,0 +1,8 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_netScan.py b/app/parsers/vol_Parser/parser_plugins/mem_netScan.py new file mode 100644 index 00000000..80aa80df --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_netScan.py @@ -0,0 +1,9 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]['@timestamp'] = res[i]['Created'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_pslist.py b/app/parsers/vol_Parser/parser_plugins/mem_pslist.py new file mode 100644 index 00000000..75aa5363 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_pslist.py @@ -0,0 +1,16 @@ + + + +def imain(res): + + for i in range(len(res)): + res[i]['@timestamp'] = res[i]['CreateTime'] + res[i]['Offset'] = hex(res[i]['Offset(V)']) + + if res[i]['Handles'] is None: + del res[i]['Handles'] + + del res[i]['Offset(V)'] + del res[i]['__children'] + + return res \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_symlink.py b/app/parsers/vol_Parser/parser_plugins/mem_symlink.py new file mode 100644 index 00000000..f4be9dbd --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_symlink.py @@ -0,0 +1,10 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]['@timestamp'] = res[i]['CreateTime'] + res[i]["Offset"] = hex(res[i]["Offset"]) + return res + \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_timeliner.py b/app/parsers/vol_Parser/parser_plugins/mem_timeliner.py new file mode 100644 index 00000000..e5eb0b52 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_timeliner.py @@ -0,0 +1,7 @@ + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]['@timestamp'] = res[i]['Created_Date'] + return res + diff --git a/app/parsers/vol_Parser/parser_plugins/mem_userAssist.py b/app/parsers/vol_Parser/parser_plugins/mem_userAssist.py new file mode 100644 index 00000000..c81bf680 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_userAssist.py @@ -0,0 +1,17 @@ + + + +def imain(res): + results = [] + for i in range(len(res)): + for child in res[i]['__children']: + results.append(child) + + for i in range(len(results)): + del results[i]['__children'] + del results[i]['Raw Data'] + + results[i]["Hive Offset"] = hex(results[i]["Hive Offset"]) + results[i]['@timestamp'] = results[i]['Last Updated'] + + return results \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_vadInfo.py b/app/parsers/vol_Parser/parser_plugins/mem_vadInfo.py new file mode 100644 index 00000000..428a8496 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_vadInfo.py @@ -0,0 +1,12 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["Offset"] = hex(res[i]["Offset"]) + res[i]["End_VPN"] = hex(res[i]["End_VPN"]) + res[i]["Parent"] = hex(res[i]["Parent"]) + res[i]["Start_VPN"] = hex(res[i]["Start_VPN"]) + return res + \ No newline at end of file diff --git a/app/parsers/vol_Parser/parser_plugins/mem_virtMap.py b/app/parsers/vol_Parser/parser_plugins/mem_virtMap.py new file mode 100644 index 00000000..9eb4a894 --- /dev/null +++ b/app/parsers/vol_Parser/parser_plugins/mem_virtMap.py @@ -0,0 +1,10 @@ + + + +def imain(res): + for i in range(len(res)): + del res[i]['__children'] + res[i]["End_offset"] = hex(res[i]["End_offset"]) + res[i]["Start_offset"] = hex(res[i]["Start_offset"]) + return res + \ No newline at end of file diff --git a/app/parsers/vol_Parser/setup.py b/app/parsers/vol_Parser/setup.py new file mode 100644 index 00000000..737deb4a --- /dev/null +++ b/app/parsers/vol_Parser/setup.py @@ -0,0 +1,41 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import setuptools + +from volatility.framework import constants + +setuptools.setup(name = "volatility", + description = "Memory forensics framework", + version = constants.PACKAGE_VERSION, + license = "VSL", + keywords = "volatility memory forensics framework windows linux volshell", + author = "Volatility Foundation", + author_email = "volatility@volatilityfoundation.org", + url = "https://volatilityfoundation.org/volatility/", + project_urls = { + "Bug Tracker": "https://github.com/volatilityfoundation/volatility3/issues", + "Documentation": "https://volatilityfoundation.org/volatility/docs/", + "Source Code": "https://github.com/volatilityfoundation/volatility3", + }, + include_package_data = True, + exclude_package_data = { + '': ['development', 'development.*'], + 'development': ['*'] + }, + packages = setuptools.find_packages(exclude = ["development", "development.*"]), + entry_points = { + 'console_scripts': [ + 'vol = volatility.cli:main', + 'volshell = volatility.cli.volshell:main', + ], + }, + install_requires = ["pefile"], + extras_require = { + 'jsonschema': ["jsonschema>=2.3.0"], + 'yara': ["yara-python>=3.8.0"], + 'crypto': ["pycryptodome>=3"], + 'disasm': ["capstone;platform_system=='Linux'", "capstone-windows;platform_system=='Windows'"], + 'doc': ["sphinx>=1.8.2", "sphinx_autodoc_typehints>=1.4.0", "sphinx-rtd-theme>=0.4.3"], + }) diff --git a/app/parsers/vol_Parser/vol.py b/app/parsers/vol_Parser/vol.py new file mode 100644 index 00000000..80291538 --- /dev/null +++ b/app/parsers/vol_Parser/vol.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import volatility.cli + +if __name__ == '__main__': + volatility.cli.main() diff --git a/app/parsers/vol_Parser/vol.spec b/app/parsers/vol_Parser/vol.spec new file mode 100644 index 00000000..6d7b6766 --- /dev/null +++ b/app/parsers/vol_Parser/vol.spec @@ -0,0 +1,110 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import os +import sys + +from PyInstaller.building.api import PYZ, EXE +from PyInstaller.building.build_main import Analysis +from PyInstaller.utils.hooks import collect_submodules, collect_data_files, collect_dynamic_libs + +block_cipher = None + +# NOTE: Issues with default pyinstaller build: +# jsonschema: +# - https://github.com/pyinstaller/pyinstaller/issues/4100 +# - https://github.com/pyinstaller/pyinstaller/pull/4168 + +binaries = [] +try: + import capstone + + binaries = collect_dynamic_libs('capstone') +except ImportError: + pass + +# Volatility must be findable in sys.path in order for collect_submodules to work +# This adds the current working directory, which should usually do the trick +sys.path.append(os.getcwd()) + +vol_analysis = Analysis(['vol.py'], + pathex = [], + binaries = binaries, + datas = collect_data_files('volatility.framework') + \ + collect_data_files('volatility.framework.automagic', include_py_files = True) + \ + collect_data_files('volatility.framework.plugins', include_py_files = True) + \ + collect_data_files('volatility.framework.layers', include_py_files = True) + \ + collect_data_files('volatility.schemas') + \ + collect_data_files('volatility.plugins', include_py_files = True), + hiddenimports = collect_submodules('volatility.framework.automagic') + \ + collect_submodules('volatility.framework.plugins') + \ + collect_submodules('volatility.framework.symbols'), + hookspath = [], + runtime_hooks = [], + excludes = [], + win_no_prefer_redirects = False, + win_private_assemblies = False, + cipher = block_cipher, + noarchive = False) +###### +# Multipackage spec files are broken in pyinstaller 3.0 (see bug 1527) +###### +# The following can be uncommented once multipackage spec files work again + +# volshell_analysis = Analysis(['volshell.py'], +# pathex = [], +# binaries = [], +# datas = collect_data_files('volatility.framework') + \ +# collect_data_files('volatility.framework.automagic', include_py_files = True) + \ +# collect_data_files('volatility.framework.plugins', include_py_files = True) + \ +# collect_data_files('volatility.framework.layers', include_py_files = True) + \ +# collect_data_files('volatility.cli', include_py_files = True) + \ +# collect_data_files('volatility.schemas') + \ +# collect_data_files('volatility.plugins', include_py_files = True), +# hiddenimports = collect_submodules('volatility.framework.automagic') + \ +# collect_submodules('volatility.framework.plugins') + \ +# collect_submodules('volatility.framework.symbols'), +# hookspath = [], +# runtime_hooks = [], +# excludes = [], +# win_no_prefer_redirects = False, +# win_private_assemblies = False, +# cipher = block_cipher, +# noarchive = False) + +# MERGE((vol_analysis, 'vol', 'vol'), (volshell_analysis, 'volshell', 'volshell')) + +vol_pyz = PYZ(vol_analysis.pure, vol_analysis.zipped_data, + cipher = block_cipher) +vol_exe = EXE(vol_pyz, + vol_analysis.scripts, + vol_analysis.binaries, + vol_analysis.zipfiles, + vol_analysis.datas, + [('u', None, 'OPTION')], + name = 'vol', + icon = os.path.join('doc', 'source', '_static', 'favicon.ico'), + debug = False, + bootloader_ignore_signals = False, + strip = False, + upx = True, + runtime_tmpdir = None, + console = True) + +# volshell_pyz = PYZ(volshell_analysis.pure, volshell_analysis.zipped_data, +# cipher = block_cipher) +# volshell_exe = EXE(volshell_pyz, +# volshell_analysis.scripts, +# volshell_analysis.binaries, +# volshell_analysis.zipfiles, +# volshell_analysis.datas, +# [('u', None, 'OPTION')], +# name = 'vol', +# icon = os.path.join('doc', 'source', '_static', 'favicon.ico'), +# debug = False, +# bootloader_ignore_signals = False, +# strip = False, +# upx = True, +# runtime_tmpdir = None, +# console = True) diff --git a/app/parsers/vol_Parser/vol_Parser.py b/app/parsers/vol_Parser/vol_Parser.py new file mode 100644 index 00000000..70e80f7d --- /dev/null +++ b/app/parsers/vol_Parser/vol_Parser.py @@ -0,0 +1,328 @@ + +import io +import os +from urllib import request +import json +import datetime +import argparse + +import volatility.plugins +from volatility.framework import automagic, constants, contexts, exceptions, interfaces, plugins, configuration +from volatility import framework + +from volatility import framework + +import volatility.framework.interfaces.renderers +from volatility.framework.interfaces.renderers import RenderOption +from volatility.framework.interfaces.configuration import * +from volatility.framework.automagic import stacker +from volatility.framework.renderers import format_hints + +from volatility.cli import text_renderer, volargparse + +import parser_plugins + + + +# return json in a beautifier +def json_beautifier(js): + return json.dumps(js, indent=4, sort_keys=True) + +# this dictionary contains the list of plugins information +plugin_info_dict = { + 'mem_info' : {'name': 'windows.info.Info' , 'function': parser_plugins.mem_info.imain}, + 'mem_cmdline' : {'name': 'windows.cmdline.CmdLine' , 'function': parser_plugins.mem_cmdline.imain}, + 'mem_dlllist' : {'name': 'windows.dlllist.DllList' , 'function': parser_plugins.mem_dlllist.imain}, + 'mem_pslist' : {'name': 'windows.pslist.PsList' , 'function': parser_plugins.mem_pslist.imain}, + 'mem_FileScan' : {'name': 'windows.filescan.FileScan' , 'function': parser_plugins.mem_FileScan.imain}, + 'mem_ProcessSID' : {'name': 'windows.getsids.GetSIDs' , 'function': parser_plugins.mem_ProcessSID.imain}, + 'mem_handles' : {'name': 'windows.handles.Handles' , 'function': parser_plugins.mem_handles.imain}, + 'mem_ModScan' : {'name': 'windows.modscan.ModScan' , 'function': parser_plugins.mem_ModScan.imain}, + 'mem_Modules' : {'name': 'windows.modules.Modules' , 'function': parser_plugins.mem_Modules.imain}, + 'mem_mutantScan' : {'name': 'windows.mutantscan.MutantScan' , 'function': parser_plugins.mem_mutantScan.imain}, + 'mem_netScan' : {'name': 'windows.netscan.NetScan' , 'function': parser_plugins.mem_netScan.imain}, + 'mem_ProcessPrivs' : {'name': 'windows.privileges.Privs' , 'function': parser_plugins.mem_ProcessPrivs.imain}, + 'mem_hiveList' : {'name': 'windows.registry.hivelist.HiveList' , 'function': parser_plugins.mem_hiveList.imain}, + 'mem_userAssist' : {'name': 'windows.registry.userassist.UserAssist' , 'function': parser_plugins.mem_userAssist.imain}, + 'mem_SSDT' : {'name': 'windows.ssdt.SSDT' , 'function': parser_plugins.mem_SSDT.imain}, + 'mem_symlink' : {'name': 'windows.symlinkscan.SymlinkScan' , 'function': parser_plugins.mem_symlink.imain}, + 'mem_vadInfo' : {'name': 'windows.vadinfo.VadInfo' , 'function': parser_plugins.mem_vadInfo.imain}, + 'mem_virtMap' : {'name': 'windows.virtmap.VirtMap' , 'function': parser_plugins.mem_virtMap.imain}, + 'mem_timeliner' : {'name': 'timeliner.Timeliner' , 'function': parser_plugins.mem_timeliner.imain}, + 'mem_envars' : {'name': 'windows.envars.Envars' , 'function': parser_plugins.mem_envars.imain} +} + + +class PrintedProgress(object): + """A progress handler that prints the progress value and the description + onto the command line.""" + + def __init__(self): + self._max_message_len = 0 + + def __call__(self, progress: Union[int, float], description: str = None): + """A simple function for providing text-based feedback. + + .. warning:: Only for development use. + + Args: + progress: Percentage of progress of the current procedure + """ + message = "\rProgress: {0: 7.2f}\t\t{1:}".format(round(progress, 2), description or '') + message_len = len(message) + self._max_message_len = max([self._max_message_len, message_len]) + +class Vol_Parser(): + + _type_renderers = { + format_hints.HexBytes: text_renderer.quoted_optional(text_renderer.hex_bytes_as_text), + volatility.framework.interfaces.renderers.Disassembly: text_renderer.quoted_optional(text_renderer.display_disassembly), + datetime.datetime: lambda x: x.isoformat() if not isinstance(x, volatility.framework.interfaces.renderers.BaseAbsentValue) else None, + volatility.framework.renderers.UnreadableValue: lambda x: None, + 'default': lambda x: x + } + + # this function to parse the TreeGrid object into json format + def render(self, grid: interfaces.renderers.TreeGrid): + outfd = sys.stdout + + final_output = ( + {}, []) # type: Tuple[Dict[str, List[interfaces.renderers.TreeNode]], List[interfaces.renderers.TreeNode]] + + + def visitor( + node: interfaces.renderers.TreeNode, + accumulator: Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]] + ) -> Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]]: + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + + acc_map, final_tree = accumulator + + node_dict = {'__children': []} # type: Dict[str, Any] + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + renderer = self._type_renderers.get(column.type, self._type_renderers['default']) + data = renderer(list(node.values)[column_index]) + if isinstance(data, interfaces.renderers.BaseAbsentValue): + data = None + node_dict[column.name] = data + + if node.parent: + acc_map[node.parent.path]['__children'].append(node_dict) + else: + final_tree.append(node_dict) + + acc_map[node.path] = node_dict + + return (acc_map, final_tree) + + if not grid.populated: + grid.populate(visitor, final_output) + else: + grid.visit(node = None, function = visitor, initial_accumulator = final_output) + return final_output[1] + + + # this is for output file handle + def file_handler_class_factory( self, direct = True): + output_dir = 'out' + + class CLIFileHandler(interfaces.plugins.FileHandlerInterface): + + def _get_final_filename(self): + """Gets the final filename""" + if output_dir is None: + raise TypeError("Output directory is not a string") + os.makedirs(output_dir, exist_ok = True) + + pref_name_array = self.preferred_filename.split('.') + filename, extension = os.path.join(output_dir, '.'.join(pref_name_array[:-1])), pref_name_array[-1] + output_filename = "{}.{}".format(filename, extension) + + counter = 1 + while os.path.exists(output_filename): + output_filename = "{}-{}.{}".format(filename, counter, extension) + counter += 1 + return output_filename + + class CLIMemFileHandler(io.BytesIO, CLIFileHandler): + def __init__(self, filename: str): + io.BytesIO.__init__(self) + CLIFileHandler.__init__(self, filename) + + def close(self): + # Don't overcommit + if self.closed: + return + + self.seek(0) + + output_filename = self._get_final_filename() + + with open(output_filename, "wb") as current_file: + current_file.write(self.read()) + self._committed = True + vollog.log(logging.INFO, "Saved stored plugin file: {}".format(output_filename)) + + super().close() + + class CLIDirectFileHandler(CLIFileHandler): + def __init__(self, filename: str): + fd, self._name = tempfile.mkstemp(suffix = '.vol3', prefix = 'tmp_', dir = output_dir) + self._file = io.open(fd, mode = 'w+b') + CLIFileHandler.__init__(self, filename) + for item in dir(self._file): + if not item.startswith('_') and not item in ['closed', 'close', 'mode', 'name']: + setattr(self, item, getattr(self._file, item)) + + def __getattr__(self, item): + return getattr(self._file, item) + + @property + def closed(self): + return self._file.closed + + @property + def mode(self): + return self._file.mode + + @property + def name(self): + return self._file.name + + def close(self): + """Closes and commits the file (by moving the temporary file to the correct name""" + # Don't overcommit + if self._file.closed: + return + + self._file.close() + output_filename = self._get_final_filename() + os.rename(self._name, output_filename) + + if direct: + return CLIDirectFileHandler + else: + return CLIMemFileHandler + + def __init__(self, image_path, plugin_name, plugins_path): + framework.require_interface_version(2, 0, 0) + + # import the plugin files + failures = framework.import_files(volatility.plugins, True) + + # load the framework plugins list + plugin_list = framework.list_plugins() + + plugin_info = self.getPlugin(plugin_name) + if plugin_info is None: + raise Exception("Plugin information for ["+plugin_name+"] not found") + plugin_info['obj'] = plugin_list[plugin_info['name']] + + # image path + file_name = os.path.abspath(image_path) + if not os.path.exists(file_name): + raise Exception("File does not exist: {}".format(file_name)) + + # set context + self.ctx = contexts.Context() # Construct a blank context + automagics = automagic.available(self.ctx) + automagics = automagic.choose_automagic(automagics, plugin_info['obj']) + + single_location = "file:" + request.pathname2url(file_name) + self.ctx.config['automagic.LayerStacker.single_location'] = single_location + + + # build plugin context + base_config_path = os.path.abspath(plugins_path) + progress_callback = PrintedProgress() + + + constructed = plugins.construct_plugin(self.ctx, automagics, plugin_info['obj'], base_config_path, progress_callback, self.file_handler_class_factory()) + + + + # run the plugin and render the results to json + treegrid = constructed.run() + + #renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)]) + #renderers['quick']().render(treegrid) + + #print(treegrid) + results = self.render(treegrid) + #print(results) + self.removeKeySpace(results) + + # go for the function that parse the json results to get clean output + self.results = plugin_info['function'](results) + + + + # this function return the plugin information from the dict + def getPlugin(self, plugin_name): + for p in plugin_info_dict: + if p == plugin_name: + return plugin_info_dict[p] + + return None + + # this will remove the space from all json keys, and replace it with "_" + def removeKeySpace(self, json_obj): + if isinstance(json_obj,list): + for i in range(len(json_obj)): + for k in json_obj[i].keys(): + if " " in k: + json_obj[i][k.replace(" " , "_")] = json_obj[i][k] + del json_obj[i][k] + + if isinstance(json_obj,dict): + for k in json_obj.keys(): + if " " in k: + json_obj[k.replace(" " , "_")] = json_obj[i][k] + del json_obj[k] + +def imain(file,parser): + try: + v = Vol_Parser(image_path=file , plugin_name=parser, plugins_path='./volatility/framework/plugins/') + + return v.results + + except Exception as e: + exc_type,exc_obj,exc_tb = sys.exc_info() + msg = "[-] [Error] " + str(parser) + " Parser: " + str(exc_obj) + ".."+str(e)+"- Line No. " + str(exc_tb.tb_lineno) + return (None , msg) + + + +def main(): + + parser = argparse.ArgumentParser(prog='vol_Parser.py', description="Run Volatility Parser.\n\n", epilog='Plugins List: ' + ', '.join(plugin_info_dict.keys()) ) + parser.add_argument("-f", "--file", action="store",help="dump file path") + parser.add_argument("-p", "--plugin", action="store", help="Select single plugin from plugins list") + parser.add_argument("-ls", "--list", action="store_true", help="list all plugins") + parser.add_argument("-k", "--kuiper", action="store_true", help="Enable kuiper output") + args = parser.parse_args() + + # if asked for plugin list + if args.list: + print("Volatility parser support the following plugins:") + for k in plugin_info_dict.keys(): + print( "\t" + k) + return + + if args.file is None or args.plugin is None: + print("Error: you should specify both dump file --file and the plugin --plugin") + return + + results = imain(args.file , args.plugin) + if len(results) and results[0] is None: + print("Error: " + results[1]) + return + + if args.kuiper: + print(results) + else: + print(json_beautifier(results)) + + +main() \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/__init__.py b/app/parsers/vol_Parser/volatility/__init__.py new file mode 100644 index 00000000..5b70678c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/__init__.py @@ -0,0 +1,62 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Volatility 3 - An open-source memory forensics framework""" +import inspect +import sys +from importlib import abc +from typing import List, TypeVar, Callable, Any, Optional + +_T = TypeVar("_T") +_S = TypeVar("_S") + + +class classproperty(property): + """Class property decorator. + + Note this will change the return type + """ + + def __init__(self, func: Callable[[_S], _T]) -> None: + self._func = func + super().__init__() + + def __get__(self, obj: Any, type: Optional[_S] = None) -> _T: + if type is not None: + return self._func(type) + raise TypeError("Classproperty was not applied properly") + + +class WarningFindSpec(abc.MetaPathFinder): + """Checks import attempts and throws a warning if the name shouldn't be + used.""" + + @staticmethod + def find_spec(fullname: str, path, target = None): + """Mock find_spec method that just checks the name, this must go + first.""" + if fullname.startswith("volatility.framework.plugins."): + warning = "Please do not use the volatility.framework.plugins namespace directly, only use volatility.plugins" + # Pyinstaller uses walk_packages to import, but needs to read the modules to figure out dependencies + # As such, we only print the warning when directly imported rather than from within walk_packages + if inspect.stack()[-2].function != 'walk_packages': + raise Warning(warning) + + +warning_find_spec = [WarningFindSpec()] # type: List[abc.MetaPathFinder] +sys.meta_path = warning_find_spec + sys.meta_path + +# We point the volatility.plugins __path__ variable at BOTH +# volatility/plugins +# volatility/framework/plugins +# in that order. +# +# This will allow our users to override any component of any plugin without monkey patching, +# but it also allows us to clear out the plugins directory to get back to proper functionality. +# This offered the greatest flexibility for users whilst allowing us to keep the core separate and clean. +# +# This means that all plugins should be imported as volatility.plugins (otherwise they'll be imported twice, +# once as volatility.plugins.NAME and once as volatility.framework.plugins.NAME). We therefore throw an error +# if anyone tries to import anything under the volatility.framework.plugins.* namespace +# +# The remediation is to only ever import form volatility.plugins instead. diff --git a/app/parsers/vol_Parser/volatility/cli/__init__.py b/app/parsers/vol_Parser/volatility/cli/__init__.py new file mode 100644 index 00000000..4f84ee30 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/__init__.py @@ -0,0 +1,583 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A CommandLine User Interface for the volatility framework. + +User interfaces make use of the framework to: + * determine available plugins + * request necessary information for those plugins from the user + * determine what "automagic" modules will be used to populate information the user does not provide + * run the plugin + * display the results +""" +import argparse +import inspect +import io +import json +import logging +import os +import sys +import tempfile +import traceback +from typing import Dict, Type, Union, Any +from urllib import parse, request + +import volatility.plugins +import volatility.symbols +from volatility import framework +from volatility.cli import text_renderer, volargparse +from volatility.framework import automagic, constants, contexts, exceptions, interfaces, plugins, configuration +from volatility.framework.automagic import stacker +from volatility.framework.configuration import requirements + +# Make sure we log everything + +vollog = logging.getLogger() +console = logging.StreamHandler() +console.setLevel(logging.WARNING) +formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s') +# Trim the console down by default +console.setFormatter(formatter) + + +class PrintedProgress(object): + """A progress handler that prints the progress value and the description + onto the command line.""" + + def __init__(self): + self._max_message_len = 0 + + def __call__(self, progress: Union[int, float], description: str = None): + """A simple function for providing text-based feedback. + + .. warning:: Only for development use. + + Args: + progress: Percentage of progress of the current procedure + """ + message = "\rProgress: {0: 7.2f}\t\t{1:}".format(round(progress, 2), description or '') + message_len = len(message) + self._max_message_len = max([self._max_message_len, message_len]) + sys.stderr.write(message + (' ' * (self._max_message_len - message_len)) + '\r') + + +class MuteProgress(PrintedProgress): + """A dummy progress handler that produces no output when called.""" + + def __call__(self, progress: Union[int, float], description: str = None): + pass + + +class CommandLine: + """Constructs a command-line interface object for users to run plugins.""" + + CLI_NAME = 'volatility' + + def __init__(self): + self.setup_logging() + self.output_dir = None + + @classmethod + def setup_logging(cls): + # Delay the setting of vollog for those that want to import volatility.cli (issue #241) + vollog.setLevel(1) + vollog.addHandler(console) + + def run(self): + """Executes the command line module, taking the system arguments, + determining the plugin to run and then running it.""" + + volatility.framework.require_interface_version(2, 0, 0) + + renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)]) + + parser = volargparse.HelpfulArgParser(add_help = False, + prog = self.CLI_NAME, + description = "An open-source memory forensics framework") + parser.add_argument( + "-h", + "--help", + action = "help", + default = argparse.SUPPRESS, + help = "Show this help message and exit, for specific plugin options use '{} --help'".format( + parser.prog)) + parser.add_argument("-c", + "--config", + help = "Load the configuration from a json file", + default = None, + type = str) + parser.add_argument("--parallelism", + help = "Enables parallelism (defaults to processes if no argument given)", + nargs = '?', + choices = ['processes', 'threads', 'off'], + const = 'processes', + default = None, + type = str) + parser.add_argument("-e", + "--extend", + help = "Extend the configuration with a new (or changed) setting", + default = None, + action = 'append') + parser.add_argument("-p", + "--plugin-dirs", + help = "Semi-colon separated list of paths to find plugins", + default = "", + type = str) + parser.add_argument("-s", + "--symbol-dirs", + help = "Semi-colon separated list of paths to find symbols", + default = "", + type = str) + parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count") + parser.add_argument("-l", + "--log", + help = "Log output to a file as well as the console", + default = None, + type = str) + parser.add_argument("-o", + "--output-dir", + help = "Directory in which to output any generated files", + default = os.getcwd(), + type = str) + parser.add_argument("-q", "--quiet", help = "Remove progress feedback", default = False, action = 'store_true') + parser.add_argument("-r", + "--renderer", + metavar = 'RENDERER', + help = "Determines how to render the output ({})".format(", ".join(list(renderers))), + default = "quick", + choices = list(renderers)) + parser.add_argument("-f", + "--file", + metavar = 'FILE', + default = None, + type = str, + help = "Shorthand for --single-location=file:// if single-location is not defined") + parser.add_argument("--write-config", + help = "Write configuration JSON file out to config.json", + default = False, + action = 'store_true') + parser.add_argument("--clear-cache", + help = "Clears out all short-term cached items", + default = False, + action = 'store_true') + + # We have to filter out help, otherwise parse_known_args will trigger the help message before having + # processed the plugin choice or had the plugin subparser added. + known_args = [arg for arg in sys.argv if arg != '--help' and arg != '-h'] + partial_args, _ = parser.parse_known_args(known_args) + + banner_output = sys.stdout + if renderers[partial_args.renderer].structured_output: + banner_output = sys.stderr + banner_output.write("Volatility 3 Framework {}\n".format(constants.PACKAGE_VERSION)) + + if partial_args.plugin_dirs: + volatility.plugins.__path__ = [os.path.abspath(p) + for p in partial_args.plugin_dirs.split(";")] + constants.PLUGINS_PATH + + if partial_args.symbol_dirs: + volatility.symbols.__path__ = [os.path.abspath(p) + for p in partial_args.symbol_dirs.split(";")] + constants.SYMBOL_BASEPATHS + + if partial_args.log: + file_logger = logging.FileHandler(partial_args.log) + file_logger.setLevel(1) + file_formatter = logging.Formatter(datefmt = '%y-%m-%d %H:%M:%S', + fmt = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + file_logger.setFormatter(file_formatter) + vollog.addHandler(file_logger) + vollog.info("Logging started") + if partial_args.verbosity < 3: + console.setLevel(30 - (partial_args.verbosity * 10)) + else: + console.setLevel(10 - (partial_args.verbosity - 2)) + + vollog.info("Volatility plugins path: {}".format(volatility.plugins.__path__)) + vollog.info("Volatility symbols path: {}".format(volatility.symbols.__path__)) + + # Set the PARALLELISM + if partial_args.parallelism == 'processes': + constants.PARALLELISM = constants.Parallelism.Multiprocessing + elif partial_args.parallelism == 'threads': + constants.PARALLELISM = constants.Parallelism.Threading + else: + constants.PARALLELISM = constants.Parallelism.Off + + if partial_args.clear_cache: + framework.clear_cache() + + # Do the initialization + ctx = contexts.Context() # Construct a blank context + failures = framework.import_files(volatility.plugins, + True) # Will not log as console's default level is WARNING + if failures: + parser.epilog = "The following plugins could not be loaded (use -vv to see why): " + \ + ", ".join(sorted(failures)) + vollog.info(parser.epilog) + automagics = automagic.available(ctx) + + plugin_list = framework.list_plugins() + + seen_automagics = set() + chosen_configurables_list = {} + for amagic in automagics: + if amagic in seen_automagics: + continue + seen_automagics.add(amagic) + if isinstance(amagic, interfaces.configuration.ConfigurableInterface): + self.populate_requirements_argparse(parser, amagic.__class__) + + subparser = parser.add_subparsers(title = "Plugins", + dest = "plugin", + description = "For plugin specific options, run '{} --help'".format( + self.CLI_NAME), + action = volargparse.HelpfulSubparserAction) + for plugin in sorted(plugin_list): + plugin_parser = subparser.add_parser(plugin, help = plugin_list[plugin].__doc__) + self.populate_requirements_argparse(plugin_parser, plugin_list[plugin]) + + ### + # PASS TO UI + ### + # Hand the plugin requirements over to the CLI (us) and let it construct the config tree + + # Run the argparser + args = parser.parse_args() + if args.plugin is None: + parser.error("Please select a plugin to run") + + vollog.log(constants.LOGLEVEL_VVV, "Cache directory used: {}".format(constants.CACHE_PATH)) + + plugin = plugin_list[args.plugin] + chosen_configurables_list[args.plugin] = plugin + base_config_path = "plugins" + plugin_config_path = interfaces.configuration.path_join(base_config_path, plugin.__name__) + + # Special case the -f argument because people use is so frequently + # It has to go here so it can be overridden by single-location if it's defined + # NOTE: This will *BREAK* if LayerStacker, or the automagic configuration system, changes at all + ### + if args.file: + file_name = os.path.abspath(args.file) + if not os.path.exists(file_name): + vollog.log(logging.INFO, "File does not exist: {}".format(file_name)) + else: + single_location = "file:" + request.pathname2url(file_name) + ctx.config['automagic.LayerStacker.single_location'] = single_location + + # UI fills in the config, here we load it from the config file and do it before we process the CL parameters + if args.config: + with open(args.config, "r") as f: + json_val = json.load(f) + ctx.config.splice(plugin_config_path, interfaces.configuration.HierarchicalDict(json_val)) + + # It should be up to the UI to determine which automagics to run, so this is before BACK TO THE FRAMEWORK + automagics = automagic.choose_automagic(automagics, plugin) + for amagic in automagics: + chosen_configurables_list[amagic.__class__.__name__] = amagic + + if ctx.config.get('automagic.LayerStacker.stackers', None) is None: + ctx.config['automagic.LayerStacker.stackers'] = stacker.choose_os_stackers(plugin) + self.output_dir = args.output_dir + + self.populate_config(ctx, chosen_configurables_list, args, plugin_config_path) + + if args.extend: + for extension in args.extend: + if '=' not in extension: + raise ValueError("Invalid extension (extensions must be of the format \"conf.path.value='value'\")") + address, value = extension[:extension.find('=')], json.loads(extension[extension.find('=') + 1:]) + ctx.config[address] = value + + ### + # BACK TO THE FRAMEWORK + ### + constructed = None + try: + progress_callback = PrintedProgress() + if args.quiet: + progress_callback = MuteProgress() + + constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback, + self.file_handler_class_factory()) + + if args.write_config: + vollog.debug("Writing out configuration data to config.json") + with open("config.json", "w") as f: + json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2) + except exceptions.UnsatisfiedException as excp: + self.process_unsatisfied_exceptions(excp) + parser.exit(1, "Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied])) + + try: + # Construct and run the plugin + if constructed: + renderers[args.renderer]().render(constructed.run()) + except (exceptions.VolatilityException) as excp: + self.process_exceptions(excp) + + def process_exceptions(self, excp): + """Provide useful feedback if an exception occurs during a run of a plugin.""" + # Ensure there's nothing in the cache + sys.stdout.write("\n\n") + sys.stdout.flush() + sys.stderr.flush() + + # Log the full exception at a high level for easy access + fulltrace = traceback.TracebackException.from_exception(excp).format(chain = True) + vollog.debug("".join(fulltrace)) + + if isinstance(excp, exceptions.InvalidAddressException): + general = "Volatility was unable to read a requested page:" + if isinstance(excp, exceptions.SwappedInvalidAddressException): + detail = "Swap error {} in layer {} ({})".format(hex(excp.invalid_address), excp.layer_name, excp) + caused_by = [ + "No suitable swap file having been provided (locate and provide the correct swap file)", + "An intentionally invalid page (operating system protection)" + ] + elif isinstance(excp, exceptions.PagedInvalidAddressException): + detail = "Page error {} in layer {} ({})".format(hex(excp.invalid_address), excp.layer_name, excp) + caused_by = [ + "Memory smear during acquisition (try re-acquiring if possible)", + "An intentionally invalid page lookup (operating system protection)", + "A bug in the plugin/volatility (re-run with -vvv and file a bug)" + ] + else: + detail = "{} in layer {} ({})".format(hex(excp.invalid_address), excp.layer_name, excp) + caused_by = [ + "The base memory file being incomplete (try re-acquiring if possible)", + "Memory smear during acquisition (try re-acquiring if possible)", + "An intentionally invalid page lookup (operating system protection)", + "A bug in the plugin/volatility (re-run with -vvv and file a bug)" + ] + elif isinstance(excp, exceptions.SymbolError): + general = "Volatility experienced a symbol-related issue:" + detail = "{}{}{}: {}".format(excp.table_name, constants.BANG, excp.symbol_name, excp) + caused_by = [ + "An invalid symbol table", + "A plugin requesting a bad symbol", + "A plugin requesting a symbol from the wrong table", + ] + elif isinstance(excp, exceptions.SymbolSpaceError): + general = "Volatility experienced an issue related to a symbol table:" + detail = "{}".format(excp) + caused_by = [ + "An invalid symbol table", "A plugin requesting a bad symbol", + "A plugin requesting a symbol from the wrong table" + ] + elif isinstance(excp, exceptions.LayerException): + general = "Volatility experienced a layer-related issue: {}".format(excp.layer_name) + detail = "{}".format(excp) + caused_by = ["A faulty layer implementation (re-run with -vvv and file a bug)"] + elif isinstance(excp, exceptions.MissingModuleException): + general = "Volatility could not import a necessary module: {}".format(excp.module) + detail = "{}".format(excp) + caused_by = ["A required python module is not installed (install the module and re-run)"] + else: + general = "Volatilty encountered an unexpected situation." + detail = "" + caused_by = [ + "Please re-run using with -vvv and file a bug with the output", "at {}".format(constants.BUG_URL) + ] + + # Code that actually renders the exception + output = sys.stderr + output.write(general + "\n") + output.write(detail + "\n\n") + for cause in caused_by: + output.write("\t* " + cause + "\n") + output.write("\nNo further results will be produced\n") + sys.exit(1) + + def process_unsatisfied_exceptions(self, excp): + """Provide useful feedback if an exception occurs during requirement fulfillment.""" + # Add a blank newline + print("") + translation_failed = False + symbols_failed = False + for config_path in excp.unsatisfied: + translation_failed = translation_failed or isinstance( + excp.unsatisfied[config_path], configuration.requirements.TranslationLayerRequirement) + symbols_failed = symbols_failed or isinstance(excp.unsatisfied[config_path], + configuration.requirements.SymbolTableRequirement) + + print("Unsatisfied requirement {}: {}".format(config_path, excp.unsatisfied[config_path].description)) + + if symbols_failed: + print("\nA symbol table requirement was not fulfilled. Please verify that:\n" + "\tYou have the correct symbol file for the requirement\n" + "\tThe symbol file is under the correct directory or zip file\n" + "\tThe symbol file is named appropriately or contains the correct banner\n") + if translation_failed: + print("\nA translation layer requirement was not fulfilled. Please verify that:\n" + "\tA file was provided to create this layer (by -f, --single-location or by config)\n" + "\tThe file exists and is readable\n" + "\tThe necessary symbols are present and identified by volatility") + + def populate_config(self, context: interfaces.context.ContextInterface, + configurables_list: Dict[str, Type[interfaces.configuration.ConfigurableInterface]], + args: argparse.Namespace, plugin_config_path: str) -> None: + """Populate the context config based on the returned args. + + We have already determined these elements must be descended from ConfigurableInterface + + Args: + context: The volatility context to operate on + configurables_list: A dictionary of configurable items that can be configured on the plugin + args: An object containing the arguments necessary + plugin_config_path: The path within the context's config containing the plugin's configuration + """ + vargs = vars(args) + for configurable in configurables_list: + for requirement in configurables_list[configurable].get_requirements(): + value = vargs.get(requirement.name, None) + + if value is not None: + if isinstance(requirement, requirements.URIRequirement): + if isinstance(value, str): + if not parse.urlparse(value).scheme: + if not os.path.exists(value): + raise FileNotFoundError( + "Non-existant file {} passed to URIRequirement".format(value)) + value = "file://" + request.pathname2url(os.path.abspath(value)) + if isinstance(requirement, requirements.ListRequirement): + if not isinstance(value, list): + raise TypeError("Configuration for ListRequirement was not a list: {}".format( + requirement.name)) + value = [requirement.element_type(x) for x in value] + if not inspect.isclass(configurables_list[configurable]): + config_path = configurables_list[configurable].config_path + else: + # We must be the plugin, so name it appropriately: + config_path = plugin_config_path + extended_path = interfaces.configuration.path_join(config_path, requirement.name) + context.config[extended_path] = value + + def file_handler_class_factory(self, direct = True): + output_dir = self.output_dir + + class CLIFileHandler(interfaces.plugins.FileHandlerInterface): + + def _get_final_filename(self): + """Gets the final filename""" + if output_dir is None: + raise TypeError("Output directory is not a string") + os.makedirs(output_dir, exist_ok = True) + + pref_name_array = self.preferred_filename.split('.') + filename, extension = os.path.join(output_dir, '.'.join(pref_name_array[:-1])), pref_name_array[-1] + output_filename = "{}.{}".format(filename, extension) + + counter = 1 + while os.path.exists(output_filename): + output_filename = "{}-{}.{}".format(filename, counter, extension) + counter += 1 + return output_filename + + class CLIMemFileHandler(io.BytesIO, CLIFileHandler): + def __init__(self, filename: str): + io.BytesIO.__init__(self) + CLIFileHandler.__init__(self, filename) + + def close(self): + # Don't overcommit + if self.closed: + return + + self.seek(0) + + output_filename = self._get_final_filename() + + with open(output_filename, "wb") as current_file: + current_file.write(self.read()) + self._committed = True + vollog.log(logging.INFO, "Saved stored plugin file: {}".format(output_filename)) + + super().close() + + class CLIDirectFileHandler(CLIFileHandler): + def __init__(self, filename: str): + fd, self._name = tempfile.mkstemp(suffix = '.vol3', prefix = 'tmp_', dir = output_dir) + self._file = io.open(fd, mode = 'w+b') + CLIFileHandler.__init__(self, filename) + for item in dir(self._file): + if not item.startswith('_') and not item in ['closed', 'close', 'mode', 'name']: + setattr(self, item, getattr(self._file, item)) + + def __getattr__(self, item): + return getattr(self._file, item) + + @property + def closed(self): + return self._file.closed + + @property + def mode(self): + return self._file.mode + + @property + def name(self): + return self._file.name + + def close(self): + """Closes and commits the file (by moving the temporary file to the correct name""" + # Don't overcommit + if self._file.closed: + return + + self._file.close() + output_filename = self._get_final_filename() + os.rename(self._name, output_filename) + + if direct: + return CLIDirectFileHandler + else: + return CLIMemFileHandler + + def populate_requirements_argparse(self, parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup], + configurable: Type[interfaces.configuration.ConfigurableInterface]): + """Adds the plugin's simple requirements to the provided parser. + + Args: + parser: The parser to add the plugin's (simple) requirements to + configurable: The plugin object to pull the requirements from + """ + if not issubclass(configurable, interfaces.configuration.ConfigurableInterface): + raise TypeError("Expected ConfigurableInterface type, not: {}".format(type(configurable))) + + # Construct an argparse group + + for requirement in configurable.get_requirements(): + additional = {} # type: Dict[str, Any] + if not isinstance(requirement, interfaces.configuration.RequirementInterface): + raise TypeError("Plugin contains requirements that are not RequirementInterfaces: {}".format( + configurable.__name__)) + if isinstance(requirement, interfaces.configuration.SimpleTypeRequirement): + additional["type"] = requirement.instance_type + if isinstance(requirement, requirements.IntRequirement): + additional["type"] = lambda x: int(x, 0) + if isinstance(requirement, requirements.BooleanRequirement): + additional["action"] = "store_true" + if "type" in additional: + del additional["type"] + elif isinstance(requirement, volatility.framework.configuration.requirements.ListRequirement): + additional["type"] = requirement.element_type + nargs = '*' if requirement.optional else '+' + additional["nargs"] = nargs + elif isinstance(requirement, volatility.framework.configuration.requirements.ChoiceRequirement): + additional["type"] = str + additional["choices"] = requirement.choices + else: + continue + parser.add_argument("--" + requirement.name.replace('_', '-'), + help = requirement.description, + default = requirement.default, + dest = requirement.name, + required = not requirement.optional, + **additional) + + +def main(): + """A convenience function for constructing and running the + :class:`CommandLine`'s run method.""" + CommandLine().run() diff --git a/app/parsers/vol_Parser/volatility/cli/text_renderer.py b/app/parsers/vol_Parser/volatility/cli/text_renderer.py new file mode 100644 index 00000000..828d4f77 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/text_renderer.py @@ -0,0 +1,365 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import datetime +import json +import logging +import random +import string +import sys +from functools import wraps +from typing import Any, List, Tuple, Dict + +from volatility.framework.interfaces.renderers import RenderOption +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + +try: + CAPSTONE_PRESENT = True + import capstone +except ImportError: + CAPSTONE_PRESENT = False + vollog.debug("Disassembly library capstone not found") + +from volatility.framework import interfaces, renderers + + +def hex_bytes_as_text(value: bytes) -> str: + """Renders HexBytes as text. + + Args: + value: A series of bytes to convert to text + + Returns: + A text representation of the hexadecimal bytes plus their ascii equivalents, separated by newline characters + """ + if not isinstance(value, bytes): + raise TypeError("hex_bytes_as_text takes bytes not: {}".format(type(value))) + ascii = [] + hex = [] + count = 0 + output = "" + for byte in value: + hex.append("{:02x}".format(byte)) + ascii.append(chr(byte) if 0x20 < byte <= 0x7E else ".") + if (count % 8) == 7: + output += "\n" + output += " ".join(hex[count - 7:count + 1]) + output += "\t" + output += "".join(ascii[count - 7:count + 1]) + count += 1 + return output + + +def multitypedata_as_text(value: format_hints.MultiTypeData) -> str: + """Renders the bytes as a string where possible, otherwise it displays hex data + + This attempts to convert the string based on its encoding and if no data's been lost due to the split on the null character, then it displays it as is + """ + if value.show_hex: + return hex_bytes_as_text(value) + string_representation = str(value, encoding = value.encoding, errors = 'replace') + if value.split_nulls and ((len(value) / 2 - 1) <= len(string_representation) <= (len(value) / 2)): + return "\n".join(string_representation.split("\x00")) + if len(string_representation) - 1 <= len(string_representation.split("\x00")[0]) <= len(string_representation): + return string_representation.split("\x00")[0] + return hex_bytes_as_text(value) + + +def optional(func): + @wraps(func) + def wrapped(x: Any) -> str: + if isinstance(x, interfaces.renderers.BaseAbsentValue): + if isinstance(x, renderers.NotApplicableValue): + return "N/A" + else: + return "-" + return func(x) + + return wrapped + + +def quoted_optional(func): + @wraps(func) + def wrapped(x: Any) -> str: + result = optional(func)(x) + if result == "-" or result == "N/A": + return "" + if isinstance(x, format_hints.MultiTypeData) and x.converted_int: + return "{}".format(result) + if isinstance(x, int) and not isinstance(x, (format_hints.Hex, format_hints.Bin)): + return "{}".format(result) + return "\"{}\"".format(result) + + return wrapped + + +def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str: + """Renders a disassembly renderer type into string format. + + Args: + disasm: Input disassembly objects + + Returns: + A string as rendererd by capstone where available, otherwise output as if it were just bytes + """ + + if CAPSTONE_PRESENT: + disasm_types = { + 'intel': capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32), + 'intel64': capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64), + 'arm': capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), + 'arm64': capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM) + } + output = "" + if disasm.architecture is not None: + for i in disasm_types[disasm.architecture].disasm(disasm.data, disasm.offset): + output += "\n0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str) + return output + return QuickTextRenderer._type_renderers[bytes](disasm.data) + + +class CLIRenderer(interfaces.renderers.Renderer): + """Class to add specific requirements for CLI renderers.""" + name = "unnamed" + structured_output = False + + +class QuickTextRenderer(CLIRenderer): + _type_renderers = { + format_hints.Bin: optional(lambda x: "0b{:b}".format(x)), + format_hints.Hex: optional(lambda x: "0x{:x}".format(x)), + format_hints.HexBytes: optional(hex_bytes_as_text), + format_hints.MultiTypeData: quoted_optional(multitypedata_as_text), + interfaces.renderers.Disassembly: optional(display_disassembly), + bytes: optional(lambda x: " ".join(["{0:02x}".format(b) for b in x])), + datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")), + 'default': optional(lambda x: "{}".format(x)) + } + + name = "quick" + + def get_render_options(self): + pass + + def render(self, grid: interfaces.renderers.TreeGrid) -> None: + """Renders each column immediately to stdout. + + This does not format each line's width appropriately, it merely tab separates each field + + Args: + grid: The TreeGrid object to render + """ + # TODO: Docstrings + # TODO: Improve text output + outfd = sys.stdout + + line = [] + for column in grid.columns: + # Ignore the type because namedtuples don't realize they have accessible attributes + line.append("{}".format(column.name)) + outfd.write("\n{}\n".format("\t".join(line))) + + def visitor(node: interfaces.renderers.TreeNode, accumulator): + accumulator.write("\n") + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + accumulator.write("*" * max(0, node.path_depth - 1) + ("" if (node.path_depth <= 1) else " ")) + line = [] + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + renderer = self._type_renderers.get(column.type, self._type_renderers['default']) + line.append(renderer(node.values[column_index])) + accumulator.write("{}".format("\t".join(line))) + accumulator.flush() + return accumulator + + if not grid.populated: + grid.populate(visitor, outfd) + else: + grid.visit(node = None, function = visitor, initial_accumulator = outfd) + + outfd.write("\n") + + +class CSVRenderer(CLIRenderer): + _type_renderers = { + format_hints.Bin: quoted_optional(lambda x: "0b{:b}".format(x)), + format_hints.Hex: quoted_optional(lambda x: "0x{:x}".format(x)), + format_hints.HexBytes: quoted_optional(hex_bytes_as_text), + format_hints.MultiTypeData: quoted_optional(multitypedata_as_text), + interfaces.renderers.Disassembly: quoted_optional(display_disassembly), + bytes: quoted_optional(lambda x: " ".join(["{0:02x}".format(b) for b in x])), + datetime.datetime: quoted_optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")), + 'default': quoted_optional(lambda x: "{}".format(x)) + } + + name = "csv" + structured_output = True + + def get_render_options(self): + pass + + def render(self, grid: interfaces.renderers.TreeGrid) -> None: + """Renders each row immediately to stdout. + + Args: + grid: The TreeGrid object to render + """ + outfd = sys.stdout + + line = ['"TreeDepth"'] + for column in grid.columns: + # Ignore the type because namedtuples don't realize they have accessible attributes + line.append("{}".format('"' + column.name + '"')) + outfd.write("{}".format(",".join(line))) + + def visitor(node: interfaces.renderers.TreeNode, accumulator): + accumulator.write("\n") + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + accumulator.write(str(max(0, node.path_depth - 1)) + ",") + line = [] + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + renderer = self._type_renderers.get(column.type, self._type_renderers['default']) + line.append(renderer(node.values[column_index])) + accumulator.write("{}".format(",".join(line))) + return accumulator + + if not grid.populated: + grid.populate(visitor, outfd) + else: + grid.visit(node = None, function = visitor, initial_accumulator = outfd) + + outfd.write("\n") + + +class PrettyTextRenderer(CLIRenderer): + _type_renderers = QuickTextRenderer._type_renderers + + name = "pretty" + + def get_render_options(self): + pass + + def render(self, grid: interfaces.renderers.TreeGrid) -> None: + """Renders each column immediately to stdout. + + This does not format each line's width appropriately, it merely tab separates each field + + Args: + grid: The TreeGrid object to render + """ + # TODO: Docstrings + # TODO: Improve text output + outfd = sys.stdout + + sys.stderr.write("Formatting...\n") + + display_alignment = ">" + column_separator = " | " + + tree_indent_column = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(20)) + max_column_widths = dict([(column.name, len(column.name)) for column in grid.columns]) + + def visitor( + node: interfaces.renderers.TreeNode, + accumulator: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]] + ) -> List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]]: + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + max_column_widths[tree_indent_column] = max(max_column_widths.get(tree_indent_column, 0), node.path_depth) + line = {} + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + renderer = self._type_renderers.get(column.type, self._type_renderers['default']) + data = renderer(node.values[column_index]) + max_column_widths[column.name] = max(max_column_widths.get(column.name, len(column.name)), + len("{}".format(data))) + line[column] = data + accumulator.append((node.path_depth, line)) + return accumulator + + final_output = [] # type: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]] + if not grid.populated: + grid.populate(visitor, final_output) + else: + grid.visit(node = None, function = visitor, initial_accumulator = final_output) + + # Always align the tree to the left + format_string_list = ["{0:<" + str(max_column_widths.get(tree_indent_column, 0)) + "s}"] + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + format_string_list.append("{" + str(column_index + 1) + ":" + display_alignment + + str(max_column_widths[column.name]) + "s}") + + format_string = column_separator.join(format_string_list) + "\n" + + column_titles = [""] + [column.name for column in grid.columns] + outfd.write(format_string.format(*column_titles)) + for (depth, line) in final_output: + outfd.write(format_string.format("*" * depth, *[line[column] for column in grid.columns])) + + +class JsonRenderer(CLIRenderer): + _type_renderers = { + format_hints.HexBytes: quoted_optional(hex_bytes_as_text), + interfaces.renderers.Disassembly: quoted_optional(display_disassembly), + bytes: optional(lambda x: " ".join(["{0:02x}".format(b) for b in x])), + datetime.datetime: lambda x: x.isoformat() if not isinstance(x, interfaces.renderers.BaseAbsentValue) else None, + 'default': lambda x: x + } + + name = 'JSON' + structured_output = True + + def get_render_options(self) -> List[RenderOption]: + pass + + def output_result(self, outfd, result): + """Outputs the JSON data to a file in a particular format""" + outfd.write(json.dumps(result, indent = 2, sort_keys = True)) + + def render(self, grid: interfaces.renderers.TreeGrid): + outfd = sys.stdout + + outfd.write("\n") + final_output = ( + {}, []) # type: Tuple[Dict[str, List[interfaces.renderers.TreeNode]], List[interfaces.renderers.TreeNode]] + + def visitor( + node: interfaces.renderers.TreeNode, accumulator: Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]] + ) -> Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]]: + # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case + acc_map, final_tree = accumulator + node_dict = {'__children': []} # type: Dict[str, Any] + for column_index in range(len(grid.columns)): + column = grid.columns[column_index] + renderer = self._type_renderers.get(column.type, self._type_renderers['default']) + data = renderer(list(node.values)[column_index]) + if isinstance(data, interfaces.renderers.BaseAbsentValue): + data = None + node_dict[column.name] = data + if node.parent: + acc_map[node.parent.path]['__children'].append(node_dict) + else: + final_tree.append(node_dict) + acc_map[node.path] = node_dict + + return (acc_map, final_tree) + + if not grid.populated: + grid.populate(visitor, final_output) + else: + grid.visit(node = None, function = visitor, initial_accumulator = final_output) + + self.output_result(outfd, final_output[1]) + + +class JsonLinesRenderer(JsonRenderer): + name = 'JSONL' + + def output_result(self, outfd, result): + """Outputs the JSON results as JSON lines""" + for line in result: + outfd.write(json.dumps(line, sort_keys = True)) + outfd.write("\n") diff --git a/app/parsers/vol_Parser/volatility/cli/volargparse.py b/app/parsers/vol_Parser/volatility/cli/volargparse.py new file mode 100644 index 00000000..79d59982 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volargparse.py @@ -0,0 +1,83 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import argparse +import gettext +import re + + +# This effectively overrides/monkeypatches the core argparse module to provide more helpful output around choices +# We shouldn't really steal a private member from argparse, but otherwise we're just duplicating code + +# HelpfulSubparserAction gives more information about the possible choices from a subparsed choice +# HelpfulArgParser gives the list of choices when no arguments are provided to a choice option whilst still using a METAVAR + + +class HelpfulSubparserAction(argparse._SubParsersAction): + """Class to either select a unique plugin based on a substring, or identify + the alternatives.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # We don't want the action self-check to kick in, so we remove the choices list, the check happens in __call__ + self.choices = None + + def __call__(self, parser, namespace, values, option_string = None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest != argparse.SUPPRESS: + setattr(namespace, self.dest, parser_name) + + matched_parsers = [name for name in self._name_parser_map if parser_name in name] + + if len(matched_parsers) < 1: + msg = 'invalid choice {} (choose from {})'.format(parser_name, ', '.join(self._name_parser_map)) + raise argparse.ArgumentError(self, msg) + if len(matched_parsers) > 1: + msg = 'plugin {} matches multiple plugins ({})'.format(parser_name, ', '.join(matched_parsers)) + raise argparse.ArgumentError(self, msg) + parser = self._name_parser_map[matched_parsers[0]] + setattr(namespace, 'plugin', matched_parsers[0]) + + # parse all the remaining options into the namespace + # store any unrecognized options on the object, so that the top + # level parser can decide what to do with them + + # In case this subparser defines new defaults, we parse them + # in a new namespace object and then update the original + # namespace for the relevant parts. + subnamespace, arg_strings = parser.parse_known_args(arg_strings, None) + for key, value in vars(subnamespace).items(): + setattr(namespace, key, value) + + if arg_strings: + vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, []) + getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) + + +class HelpfulArgParser(argparse.ArgumentParser): + + def _match_argument(self, action, arg_strings_pattern) -> int: + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: gettext.gettext('expected one argument'), + argparse.OPTIONAL: gettext.gettext('expected at most one argument'), + argparse.ONE_OR_MORE: gettext.gettext('expected at least one argument'), + } + msg = nargs_errors.get(action.nargs) + if msg is None: + msg = gettext.ngettext('expected %s argument', 'expected %s arguments', action.nargs) % action.nargs + if action.choices: + msg = "{} (from: {})".format(msg, ", ".join(action.choices)) + raise argparse.ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) diff --git a/app/parsers/vol_Parser/volatility/cli/volshell/__init__.py b/app/parsers/vol_Parser/volatility/cli/volshell/__init__.py new file mode 100644 index 00000000..4441d4b7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volshell/__init__.py @@ -0,0 +1,245 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import argparse +import json +import logging +import os +import sys +from urllib import request + +import volatility.plugins +import volatility.symbols +from volatility import cli, framework +from volatility.cli.volshell import generic, windows, linux, mac +from volatility.framework import automagic, constants, contexts, exceptions, interfaces, plugins + +# Make sure we log everything +vollog = logging.getLogger() +vollog.setLevel(0) +# Trim the console down by default +console = logging.StreamHandler() +console.setLevel(logging.WARNING) +formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s') +console.setFormatter(formatter) +vollog.addHandler(console) + + +class VolShell(cli.CommandLine): + """Program to allow interactive interaction with a memory image. + + This allows a memory image to be examined through an interactive + python terminal with all the volatility support calls available. + """ + + def __init__(self): + super().__init__() + self.output_dir = None + + def run(self): + """Executes the command line module, taking the system arguments, + determining the plugin to run and then running it.""" + sys.stdout.write("Volshell (Volatility 3 Framework) {}\n".format(constants.PACKAGE_VERSION)) + + framework.require_interface_version(2, 0, 0) + + parser = argparse.ArgumentParser(prog = 'volshell', + description = "A tool for interactivate forensic analysis of memory images") + parser.add_argument("-c", + "--config", + help = "Load the configuration from a json file", + default = None, + type = str) + parser.add_argument("-e", + "--extend", + help = "Extend the configuration with a new (or changed) setting", + default = None, + action = 'append') + parser.add_argument("-p", + "--plugin-dirs", + help = "Semi-colon separated list of paths to find plugins", + default = "", + type = str) + parser.add_argument("-s", + "--symbol-dirs", + help = "Semi-colon separated list of paths to find symbols", + default = "", + type = str) + parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count") + parser.add_argument("-o", + "--output-dir", + help = "Directory in which to output any generated files", + default = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')), + type = str) + parser.add_argument("-q", "--quiet", help = "Remove progress feedback", default = False, action = 'store_true') + parser.add_argument("--log", help = "Log output to a file as well as the console", default = None, type = str) + parser.add_argument("-f", + "--file", + metavar = 'FILE', + default = None, + type = str, + help = "Shorthand for --single-location=file:// if single-location is not defined") + parser.add_argument("--write-config", + help = "Write configuration JSON file out to config.json", + default = False, + action = 'store_true') + parser.add_argument("--clear-cache", + help = "Clears out all short-term cached items", + default = False, + action = 'store_true') + + # Volshell specific flags + os_specific = parser.add_mutually_exclusive_group(required = False) + os_specific.add_argument("-w", + "--windows", + default = False, + action = "store_true", + help = "Run a Windows volshell") + os_specific.add_argument("-l", "--linux", default = False, action = "store_true", help = "Run a Linux volshell") + os_specific.add_argument("-m", "--mac", default = False, action = "store_true", help = "Run a Mac volshell") + + # We have to filter out help, otherwise parse_known_args will trigger the help message before having + # processed the plugin choice or had the plugin subparser added. + known_args = [arg for arg in sys.argv if arg != '--help' and arg != '-h'] + partial_args, _ = parser.parse_known_args(known_args) + if partial_args.plugin_dirs: + volatility.plugins.__path__ = [os.path.abspath(p) + for p in partial_args.plugin_dirs.split(";")] + constants.PLUGINS_PATH + + if partial_args.symbol_dirs: + volatility.symbols.__path__ = [os.path.abspath(p) + for p in partial_args.symbol_dirs.split(";")] + constants.SYMBOL_BASEPATHS + + vollog.info("Volatility plugins path: {}".format(volatility.plugins.__path__)) + vollog.info("Volatility symbols path: {}".format(volatility.symbols.__path__)) + + if partial_args.log: + file_logger = logging.FileHandler(partial_args.log) + file_logger.setLevel(0) + file_formatter = logging.Formatter(datefmt = '%y-%m-%d %H:%M:%S', + fmt = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') + file_logger.setFormatter(file_formatter) + vollog.addHandler(file_logger) + vollog.info("Logging started") + + if partial_args.verbosity < 3: + console.setLevel(30 - (partial_args.verbosity * 10)) + else: + console.setLevel(10 - (partial_args.verbosity - 2)) + + if partial_args.clear_cache: + for cache_filename in glob.glob(os.path.join(constants.CACHE_PATH, '*.cache')): + os.unlink(cache_filename) + + # Do the initialization + ctx = contexts.Context() # Construct a blank context + failures = framework.import_files(volatility.plugins, + True) # Will not log as console's default level is WARNING + if failures: + parser.epilog = "The following plugins could not be loaded (use -vv to see why): " + \ + ", ".join(sorted(failures)) + vollog.info(parser.epilog) + automagics = automagic.available(ctx) + + # Initialize the list of plugins in case volshell needs it + framework.list_plugins() + + seen_automagics = set() + configurables_list = {} + for amagic in automagics: + if amagic in seen_automagics: + continue + seen_automagics.add(amagic) + if isinstance(amagic, interfaces.configuration.ConfigurableInterface): + self.populate_requirements_argparse(parser, amagic.__class__) + configurables_list[amagic.__class__.__name__] = amagic + + # We don't list plugin arguments, because they can be provided within python + volshell_plugin_list = {'generic': generic.Volshell, 'windows': windows.Volshell} + for plugin in volshell_plugin_list: + subparser = parser.add_argument_group(title = plugin.capitalize(), + description = "Configuration options based on {} options".format( + plugin.capitalize())) + self.populate_requirements_argparse(subparser, volshell_plugin_list[plugin]) + configurables_list[plugin] = volshell_plugin_list[plugin] + + ### + # PASS TO UI + ### + # Hand the plugin requirements over to the CLI (us) and let it construct the config tree + + # Run the argparser + args = parser.parse_args() + + vollog.log(constants.LOGLEVEL_VVV, "Cache directory used: {}".format(constants.CACHE_PATH)) + + plugin = generic.Volshell + if args.windows: + plugin = windows.Volshell + if args.linux: + plugin = linux.Volshell + if args.mac: + plugin = mac.Volshell + + base_config_path = "plugins" + plugin_config_path = interfaces.configuration.path_join(base_config_path, plugin.__name__) + + # Special case the -f argument because people use is so frequently + # It has to go here so it can be overridden by single-location if it's defined + # NOTE: This will *BREAK* if LayerStacker, or the automagic configuration system, changes at all + ### + if args.file: + file_name = os.path.abspath(args.file) + if not os.path.exists(file_name): + vollog.log(logging.INFO, "File does not exist: {}".format(file_name)) + else: + single_location = "file:" + request.pathname2url(file_name) + ctx.config['automagic.LayerStacker.single_location'] = single_location + + # UI fills in the config, here we load it from the config file and do it before we process the CL parameters + if args.config: + with open(args.config, "r") as f: + json_val = json.load(f) + ctx.config.splice(plugin_config_path, interfaces.configuration.HierarchicalDict(json_val)) + + self.populate_config(ctx, configurables_list, args, plugin_config_path) + + if args.extend: + for extension in args.extend: + if '=' not in extension: + raise ValueError("Invalid extension (extensions must be of the format \"conf.path.value='value'\")") + address, value = extension[:extension.find('=')], json.loads(extension[extension.find('=') + 1:]) + ctx.config[address] = value + + # It should be up to the UI to determine which automagics to run, so this is before BACK TO THE FRAMEWORK + automagics = automagic.choose_automagic(automagics, plugin) + self.output_dir = args.output_dir + + ### + # BACK TO THE FRAMEWORK + ### + try: + progress_callback = cli.PrintedProgress() + if args.quiet: + progress_callback = cli.MuteProgress() + + constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback, + self.file_handler_class_factory()) + + if args.write_config: + vollog.debug("Writing out configuration data to config.json") + with open("config.json", "w") as f: + json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2) + + # Construct and run the plugin + constructed.run() + except exceptions.VolatilityException as excp: + self.process_exceptions(excp) + parser.exit(1, "Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied])) + + +def main(): + """A convenience function for constructing and running the + :class:`CommandLine`'s run method.""" + VolShell().run() diff --git a/app/parsers/vol_Parser/volatility/cli/volshell/generic.py b/app/parsers/vol_Parser/volatility/cli/volshell/generic.py new file mode 100644 index 00000000..4dd30dc2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volshell/generic.py @@ -0,0 +1,334 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import binascii +import code +import io +import random +import string +import struct +import sys +from typing import Any, Dict, List, Optional, Tuple, Union, Type + +from volatility.cli import text_renderer +from volatility.framework import renderers, interfaces, objects, plugins, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import intel + +try: + import capstone + + has_capstone = True +except ImportError: + has_capstone = False + + +class Volshell(interfaces.plugins.PluginInterface): + """Shell environment to directly interact with a memory image.""" + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__current_layer = None # type: Optional[str] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]) + ] + + def run(self, additional_locals: Dict[str, Any] = None) -> interfaces.renderers.TreeGrid: + """Runs the interactive volshell plugin. + + Returns: + Return a TreeGrid but this is always empty since the point of this plugin is to run interactively + """ + + self._current_layer = self.config['primary'] + + # Try to enable tab completion + try: + import readline + except ImportError: + pass + else: + import rlcompleter + completer = rlcompleter.Completer(namespace = self._construct_locals_dict()) + readline.set_completer(completer.complete) + readline.parse_and_bind("tab: complete") + print("Readline imported successfully") + + # TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions + + mode = self.__module__.split('.')[-1] + mode = mode[0].upper() + mode[1:] + + banner = """ + Call help() to see available functions + + Volshell mode: {} + Current Layer: {} + """.format(mode, self.current_layer) + + sys.ps1 = "({}) >>> ".format(self.current_layer) + code.interact(banner = banner, local = self._construct_locals_dict()) + + return renderers.TreeGrid([("Terminating", str)], None) + + def help(self, *args): + """Describes the available commands""" + if args: + help(*args) + return + + variables = [] + print("\nMethods:") + for aliases, item in self.construct_locals(): + name = ", ".join(aliases) + if item.__doc__ and callable(item): + print("* {}".format(name)) + print(" {}".format(item.__doc__)) + else: + variables.append(name) + + print("\nVariables:") + for var in variables: + print(" {}".format(var)) + + def construct_locals(self) -> List[Tuple[List[str], Any]]: + """Returns a dictionary listing the functions to be added to the + environment.""" + return [(['dt', 'display_type'], self.display_type), (['db', 'display_bytes'], self.display_bytes), + (['dw', 'display_words'], self.display_words), (['dd', + 'display_doublewords'], self.display_doublewords), + (['dq', 'display_quadwords'], self.display_quadwords), (['dis', 'disassemble'], self.disassemble), + (['cl', 'change_layer'], self.change_layer), (['context'], self.context), (['self'], self), + (['dpo', 'display_plugin_output'], self.display_plugin_output), + (['gt', 'generate_treegrid'], self.generate_treegrid), (['rt', + 'render_treegrid'], self.render_treegrid), + (['ds', 'display_symbols'], self.display_symbols), (['hh', 'help'], self.help)] + + def _construct_locals_dict(self) -> Dict[str, Any]: + """Returns a dictionary of the locals """ + result = {} + for aliases, value in self.construct_locals(): + for alias in aliases: + result[alias] = value + return result + + def _read_data(self, offset, count = 128, layer_name = None): + """Reads the bytes necessary for the display_* methods""" + return self.context.layers[layer_name or self.current_layer].read(offset, count) + + def _display_data(self, offset: int, remaining_data: bytes, format_string: str = "B", ascii: bool = True): + """Display a series of bytes""" + chunk_size = struct.calcsize(format_string) + data_length = len(remaining_data) + remaining_data = remaining_data[:data_length - (data_length % chunk_size)] + + while remaining_data: + current_line, remaining_data = remaining_data[:16], remaining_data[16:] + + data_blocks = [current_line[chunk_size * i:chunk_size * (i + 1)] for i in range(16 // chunk_size)] + data_blocks = [x for x in data_blocks if x != b''] + valid_data = [("{:0" + str(2 * chunk_size) + "x}").format(struct.unpack(format_string, x)[0]) + for x in data_blocks] + padding_data = [" " * 2 * chunk_size for _ in range((16 - len(current_line)) // chunk_size)] + hex_data = " ".join(valid_data + padding_data) + + ascii_data = "" + if ascii: + connector = " " + if chunk_size < 2: + connector = "" + ascii_data = connector.join([self._ascii_bytes(x) for x in valid_data]) + + print(hex(offset), " ", hex_data, " ", ascii_data) + offset += 16 + + @staticmethod + def _ascii_bytes(bytes): + """Converts bytes into an ascii string""" + return "".join([chr(x) if 32 < x < 127 else '.' for x in binascii.unhexlify(bytes)]) + + @property + def current_layer(self): + return self._current_layer + + def change_layer(self, layer_name = None): + """Changes the current default layer""" + if not layer_name: + layer_name = self.config['primary'] + self._current_layer = layer_name + sys.ps1 = "({}) >>> ".format(self.current_layer) + + def display_bytes(self, offset, count = 128, layer_name = None): + """Displays byte values and ASCII characters""" + remaining_data = self._read_data(offset, count = count, layer_name = layer_name) + self._display_data(offset, remaining_data) + + def display_quadwords(self, offset, count = 128, layer_name = None): + """Displays quad-word values (8 bytes) and corresponding ASCII characters""" + remaining_data = self._read_data(offset, count = count, layer_name = layer_name) + self._display_data(offset, remaining_data, format_string = "Q") + + def display_doublewords(self, offset, count = 128, layer_name = None): + """Displays double-word values (4 bytes) and corresponding ASCII characters""" + remaining_data = self._read_data(offset, count = count, layer_name = layer_name) + self._display_data(offset, remaining_data, format_string = "I") + + def display_words(self, offset, count = 128, layer_name = None): + """Displays word values (2 bytes) and corresponding ASCII characters""" + remaining_data = self._read_data(offset, count = count, layer_name = layer_name) + self._display_data(offset, remaining_data, format_string = "H") + + def disassemble(self, offset, count = 128, layer_name = None, architecture = None): + """Disassembles a number of instructions from the code at offset""" + remaining_data = self._read_data(offset, count = count, layer_name = layer_name) + if not has_capstone: + print("Capstone not available - please install it to use the disassemble command") + else: + if isinstance(self.context.layers[layer_name or self.current_layer], intel.Intel32e): + architecture = 'intel64' + elif isinstance(self.context.layers[layer_name or self.current_layer], intel.Intel): + architecture = 'intel' + disasm_types = { + 'intel': capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32), + 'intel64': capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64), + 'arm': capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), + 'arm64': capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM) + } + if architecture is not None: + for i in disasm_types[architecture].disasm(remaining_data, offset): + print("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str)) + + def display_type(self, + object: Union[str, interfaces.objects.ObjectInterface, interfaces.objects.Template], + offset: int = None): + """Display Type describes the members of a particular object in alphabetical order""" + if not isinstance(object, (str, interfaces.objects.ObjectInterface, interfaces.objects.Template)): + print("Cannot display information about non-type object") + return + + if not isinstance(object, str): + # Mypy requires us to order things this way + volobject = object + elif offset is None: + # Str and no offset + volobject = self.context.symbol_space.get_type(object) + else: + # Str and offset + volobject = self.context.object(object, layer_name = self.current_layer, offset = offset) + + if offset is not None: + volobject = self.context.object(volobject.vol.type_name, layer_name = self.current_layer, offset = offset) + + if hasattr(volobject.vol, 'size'): + print("{} ({} bytes)".format(volobject.vol.type_name, volobject.vol.size)) + elif hasattr(volobject.vol, 'data_format'): + data_format = volobject.vol.data_format + print("{} ({} bytes, {} endian, {})".format(volobject.vol.type_name, data_format.length, + data_format.byteorder, + 'signed' if data_format.signed else 'unsigned')) + + if hasattr(volobject.vol, 'members'): + longest_member = longest_offset = longest_typename = 0 + for member in volobject.vol.members: + relative_offset, member_type = volobject.vol.members[member] + longest_member = max(len(member), longest_member) + longest_offset = max(len(hex(relative_offset)), longest_offset) + longest_typename = max(len(member_type.vol.type_name), longest_typename) + + for member in sorted(volobject.vol.members, key = lambda x: (volobject.vol.members[x][0], x)): + relative_offset, member_type = volobject.vol.members[member] + len_offset = len(hex(relative_offset)) + len_member = len(member) + len_typename = len(member_type.vol.type_name) + if isinstance(volobject, interfaces.objects.ObjectInterface): + # We're an instance, so also display the data + print(" " * (longest_offset - len_offset), hex(relative_offset), ": ", member, + " " * (longest_member - len_member), " ", + member_type.vol.type_name, " " * (longest_typename - len_typename), " ", + self._display_value(getattr(volobject, member))) + else: + print(" " * (longest_offset - len_offset), hex(relative_offset), ": ", member, + " " * (longest_member - len_member), " ", member_type.vol.type_name) + + @classmethod + def _display_value(self, value: Any) -> str: + if isinstance(value, objects.PrimitiveObject): + return repr(value) + elif isinstance(value, objects.Array): + return repr([self._display_value(val) for val in value]) + else: + return hex(value.vol.offset) + + def generate_treegrid(self, plugin: Type[interfaces.plugins.PluginInterface], + **kwargs) -> Optional[interfaces.renderers.TreeGrid]: + """Generates a TreeGrid based on a specific plugin passing in kwarg configuration values""" + path_join = interfaces.configuration.path_join + + # Generate a temporary configuration path + plugin_config_suffix = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32)) + plugin_path = path_join(self.config_path, plugin_config_suffix) + + # Populate the configuration + for name, value in kwargs.items(): + self.config[path_join(plugin_config_suffix, plugin.__name__, name)] = value + + try: + constructed = plugins.construct_plugin(self.context, [], plugin, plugin_path, None, NullFileHandler) + return constructed.run() + except exceptions.UnsatisfiedException as excp: + print("Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied])) + return None + + def render_treegrid(self, + treegrid: interfaces.renderers.TreeGrid, + renderer: Optional[interfaces.renderers.Renderer] = None) -> None: + """Renders a treegrid as produced by generate_treegrid""" + if renderer is None: + renderer = text_renderer.QuickTextRenderer() + renderer.render(treegrid) + + def display_plugin_output(self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs) -> None: + """Displays the output for a particular plugin (with keyword arguments)""" + treegrid = self.generate_treegrid(plugin, **kwargs) + if treegrid is not None: + self.render_treegrid(treegrid) + + def display_symbols(self, symbol_table: str = None): + """Prints an alphabetical list of symbols for a symbol table""" + if symbol_table is None: + print("No symbol table provided") + return + longest_offset = longest_name = 0 + + table = self.context.symbol_space[symbol_table] + for symbol_name in table.symbols: + symbol = table.get_symbol(symbol_name) + longest_offset = max(longest_offset, len(hex(symbol.address))) + longest_name = max(longest_name, len(symbol.name)) + + for symbol_name in sorted(table.symbols): + symbol = table.get_symbol(symbol_name) + len_offset = len(hex(symbol.address)) + print(" " * (longest_offset - len_offset), hex(symbol.address), " ", symbol.name) + + +class NullFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface): + """Null FileHandler that swallows files whole without consuming memory""" + + def __init__(self, preferred_name: str): + interfaces.plugins.FileHandlerInterface.__init__(self, preferred_name) + super().__init__() + + def writelines(self, lines): + """Dummy method""" + pass + + def write(self, data): + """Dummy method""" + return len(data) diff --git a/app/parsers/vol_Parser/volatility/cli/volshell/linux.py b/app/parsers/vol_Parser/volatility/cli/volshell/linux.py new file mode 100644 index 00000000..03c54bda --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volshell/linux.py @@ -0,0 +1,66 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Any, List, Tuple, Union + +from volatility.cli.volshell import generic +from volatility.framework import interfaces, constants +from volatility.framework.configuration import requirements +from volatility.plugins.linux import pslist + + +class Volshell(generic.Volshell): + """Shell environment to directly interact with a linux memory image.""" + + @classmethod + def get_requirements(cls): + return (super().get_requirements() + [ + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.IntRequirement(name = 'pid', description = "Process ID", optional = True) + ]) + + def change_task(self, pid = None): + """Change the current process and layer, based on a process ID""" + tasks = self.list_tasks() + for task in tasks: + if task.pid == pid: + process_layer = task.add_process_layer() + if process_layer is not None: + self.change_layer(process_layer) + return + print("Layer for task ID {} could not be constructed".format(pid)) + return + print("No task with task ID {} found".format(pid)) + + def list_tasks(self): + """Returns a list of task objects from the primary layer""" + # We always use the main kernel memory and associated symbols + return list(pslist.PsList.list_tasks(self.context, self.config['primary'], self.config['vmlinux'])) + + def construct_locals(self) -> List[Tuple[List[str], Any]]: + result = super().construct_locals() + result += [ + (['ct', 'change_task', 'cp'], self.change_task), + (['lt', 'list_tasks', 'ps'], self.list_tasks), + (['symbols'], self.context.symbol_space[self.config['vmlinux']]), + ] + if self.config.get('pid', None) is not None: + self.change_task(self.config['pid']) + return result + + def display_type(self, + object: Union[str, interfaces.objects.ObjectInterface, interfaces.objects.Template], + offset: int = None): + """Display Type describes the members of a particular object in alphabetical order""" + if isinstance(object, str): + if constants.BANG not in object: + object = self.config['vmlinux'] + constants.BANG + object + return super().display_type(object, offset) + + def display_symbols(self, symbol_table: str = None): + """Prints an alphabetical list of symbols for a symbol table""" + if symbol_table is None: + symbol_table = self.config['vmlinux'] + return super().display_symbols(symbol_table) diff --git a/app/parsers/vol_Parser/volatility/cli/volshell/mac.py b/app/parsers/vol_Parser/volatility/cli/volshell/mac.py new file mode 100644 index 00000000..d0ae4cb1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volshell/mac.py @@ -0,0 +1,66 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Any, List, Tuple, Union + +from volatility.cli.volshell import generic +from volatility.framework import constants, interfaces +from volatility.framework.configuration import requirements +from volatility.plugins.mac import pslist + + +class Volshell(generic.Volshell): + """Shell environment to directly interact with a mac memory image.""" + + @classmethod + def get_requirements(cls): + return (super().get_requirements() + [ + requirements.SymbolTableRequirement(name = "darwin", description = "Darwin kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.IntRequirement(name = 'pid', description = "Process ID", optional = True) + ]) + + def change_task(self, pid = None): + """Change the current process and layer, based on a process ID""" + tasks = self.list_tasks() + for task in tasks: + if task.p_pid == pid: + process_layer = task.add_process_layer() + if process_layer is not None: + self.change_layer(process_layer) + return + print("Layer for task ID {} could not be constructed".format(pid)) + return + print("No task with task ID {} found".format(pid)) + + def list_tasks(self): + """Returns a list of task objects from the primary layer""" + # We always use the main kernel memory and associated symbols + return list(pslist.PsList.list_tasks(self.context, self.config['primary'], self.config['darwin'])) + + def construct_locals(self) -> List[Tuple[List[str], Any]]: + result = super().construct_locals() + result += [ + (['ct', 'change_task', 'cp'], self.change_task), + (['lt', 'list_tasks', 'ps'], self.list_tasks), + (['symbols'], self.context.symbol_space[self.config['darwin']]), + ] + if self.config.get('pid', None) is not None: + self.change_task(self.config['pid']) + return result + + def display_type(self, + object: Union[str, interfaces.objects.ObjectInterface, interfaces.objects.Template], + offset: int = None): + """Display Type describes the members of a particular object in alphabetical order""" + if isinstance(object, str): + if constants.BANG not in object: + object = self.config['darwin'] + constants.BANG + object + return super().display_type(object, offset) + + def display_symbols(self, symbol_table: str = None): + """Prints an alphabetical list of symbols for a symbol table""" + if symbol_table is None: + symbol_table = self.config['darwin'] + return super().display_symbols(symbol_table) diff --git a/app/parsers/vol_Parser/volatility/cli/volshell/windows.py b/app/parsers/vol_Parser/volatility/cli/volshell/windows.py new file mode 100644 index 00000000..7f4c4fcc --- /dev/null +++ b/app/parsers/vol_Parser/volatility/cli/volshell/windows.py @@ -0,0 +1,63 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Any, List, Tuple, Union + +from volatility.cli.volshell import generic +from volatility.framework import interfaces, constants +from volatility.framework.configuration import requirements +from volatility.plugins.windows import pslist + + +class Volshell(generic.Volshell): + """Shell environment to directly interact with a windows memory image.""" + + @classmethod + def get_requirements(cls): + return (super().get_requirements() + [ + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.IntRequirement(name = 'pid', description = "Process ID", optional = True) + ]) + + def change_process(self, pid = None): + """Change the current process and layer, based on a process ID""" + processes = self.list_processes() + for process in processes: + if process.UniqueProcessId == pid: + process_layer = process.add_process_layer() + self.change_layer(process_layer) + return + print("No process with process ID {} found".format(pid)) + + def list_processes(self): + """Returns a list of EPROCESS objects from the primary layer""" + # We always use the main kernel memory and associated symbols + return list(pslist.PsList.list_processes(self.context, self.config['primary'], self.config['nt_symbols'])) + + def construct_locals(self) -> List[Tuple[List[str], Any]]: + result = super().construct_locals() + result += [ + (['cp', 'change_process'], self.change_process), + (['lp', 'list_processes', 'ps'], self.list_processes), + (['symbols'], self.context.symbol_space[self.config['nt_symbols']]), + ] + if self.config.get('pid', None) is not None: + self.change_process(self.config['pid']) + return result + + def display_type(self, + object: Union[str, interfaces.objects.ObjectInterface, interfaces.objects.Template], + offset: int = None): + """Display Type describes the members of a particular object in alphabetical order""" + if isinstance(object, str): + if constants.BANG not in object: + object = self.config['nt_symbols'] + constants.BANG + object + return super().display_type(object, offset) + + def display_symbols(self, symbol_table: str = None): + """Prints an alphabetical list of symbols for a symbol table""" + if symbol_table is None: + symbol_table = self.config['nt_symbols'] + return super().display_symbols(symbol_table) diff --git a/app/parsers/vol_Parser/volatility/framework/__init__.py b/app/parsers/vol_Parser/volatility/framework/__init__.py new file mode 100644 index 00000000..4c66329e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/__init__.py @@ -0,0 +1,134 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Volatility 3 framework.""" +# Check the python version to ensure it's suitable +# We currently require 3.5.3 since 3.5.1 has no typing.Type and 3.5.2 is broken for ''/delayed encapsulated types +import glob +import sys + +required_python_version = (3, 5, 3) +if (sys.version_info.major != required_python_version[0] or sys.version_info.minor < required_python_version[1] or + (sys.version_info.minor == required_python_version[1] and sys.version_info.micro < required_python_version[2])): + raise RuntimeError( + "Volatility framework requires python version {}.{}.{} or greater".format(*required_python_version)) + +import importlib +import inspect +import logging +import os +from typing import Any, Dict, Generator, List, Type, TypeVar + +from volatility.framework import constants, interfaces + +# ## +# +# SemVer version scheme +# +# Increment the: +# +# MAJOR version when you make incompatible API changes, +# MINOR version when you add functionality in a backwards compatible manner, and +# PATCH version when you make backwards compatible bug fixes. + + +def interface_version(): + """Provides the so version number of the library.""" + return constants.VERSION_MAJOR, constants.VERSION_MINOR, constants.VERSION_PATCH + + +vollog = logging.getLogger(__name__) + + +def require_interface_version(*args) -> None: + """Checks the required version of a plugin.""" + if len(args): + if args[0] != interface_version()[0]: + raise RuntimeError("Framework interface version {} is incompatible with required version {}".format( + interface_version()[0], args[0])) + if len(args) > 1: + if args[1] > interface_version()[1]: + raise RuntimeError( + "Framework interface version {} is an older revision than the required version {}".format( + ".".join([str(x) for x in interface_version()[0:1]]), ".".join([str(x) for x in args[0:2]]))) + + +class noninheritable(object): + + def __init__(self, value: Any, cls: Type) -> None: + self.default_value = value + self.cls = cls + + def __get__(self, obj: Any, type: Type = None) -> Any: + if type == self.cls: + if hasattr(self.default_value, '__get__'): + return self.default_value.__get__(obj, type) + return self.default_value + raise AttributeError + + +def hide_from_subclasses(cls: Type) -> Type: + cls.hidden = noninheritable(True, cls) + return cls + + +T = TypeVar('T') + + +def class_subclasses(cls: Type[T]) -> Generator[Type[T], None, None]: + """Returns all the (recursive) subclasses of a given class.""" + if not inspect.isclass(cls): + raise TypeError("class_subclasses parameter not a valid class: {}".format(cls)) + for clazz in cls.__subclasses__(): + # The typing system is not clever enough to realize that clazz has a hidden attr after the hasattr check + if not hasattr(clazz, 'hidden') or not clazz.hidden: # type: ignore + yield clazz + for return_value in class_subclasses(clazz): + yield return_value + + +def import_files(base_module, ignore_errors = False) -> List[str]: + """Imports all plugins present under plugins module namespace.""" + failures = [] + if not isinstance(base_module.__path__, list): + raise TypeError("[base_module].__path__ must be a list of paths") + vollog.log(constants.LOGLEVEL_VVVV, + "Importing from the following paths: {}".format(", ".join(base_module.__path__))) + for path in base_module.__path__: + for root, _, files in os.walk(path, followlinks = True): + # TODO: Figure out how to import pycache files + if root.endswith("__pycache__"): + continue + for f in files: + if (f.endswith(".py") or f.endswith(".pyc") or f.endswith(".pyo")) and not f.startswith("__"): + modpath = os.path.join(root[len(path) + len(os.path.sep):], f[:f.rfind(".")]) + module = modpath.replace(os.path.sep, ".") + if base_module.__name__ + "." + module not in sys.modules: + try: + importlib.import_module(base_module.__name__ + "." + module) + except ImportError as e: + vollog.debug(str(e)) + vollog.debug("Failed to import module {} based on file: {}".format( + base_module.__name__ + "." + module, modpath)) + failures.append(base_module.__name__ + "." + module) + if not ignore_errors: + raise + return failures + + +def list_plugins() -> Dict[str, Type[interfaces.plugins.PluginInterface]]: + plugin_list = {} + for plugin in class_subclasses(interfaces.plugins.PluginInterface): + plugin_name = plugin.__module__ + "." + plugin.__name__ + if plugin_name.startswith("volatility.plugins."): + plugin_name = plugin_name[len("volatility.plugins."):] + plugin_list[plugin_name] = plugin + return plugin_list + + +def clear_cache(complete = False): + glob_pattern = '*.cache' + if not complete: + glob_pattern = 'data_' + glob_pattern + for cache_filename in glob.glob(os.path.join(constants.CACHE_PATH, glob_pattern)): + os.unlink(cache_filename) diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/__init__.py b/app/parsers/vol_Parser/volatility/framework/automagic/__init__.py new file mode 100644 index 00000000..2541214a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/__init__.py @@ -0,0 +1,134 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Automagic modules allow the framework to populate configuration elements +that a user has not provided. + +Automagic objects accept a `context` and a `configurable`, and will make appropriate changes to the `context` in an +attempt to fulfill the requirements of the `configurable` object (or objects upon which that configurable may rely). + +Several pre-existing modules include one to stack layers on top of each other (allowing automatic detection and +loading of file format types) as well as a module to reconstruct layers based on their provided requirements. +""" + +import logging +import sys +import traceback +from typing import List, Type, Union + +from volatility.framework import class_subclasses, import_files, interfaces, constants +from volatility.framework.configuration import requirements + +vollog = logging.getLogger(__name__) + +windows_automagic = ['ConstructionMagic', 'LayerStacker', 'WintelHelper', 'KernelPDBScanner', 'WinSwapLayers'] + +linux_automagic = ['ConstructionMagic', 'LayerStacker', 'LinuxBannerCache', 'LinuxSymbolFinder'] + +mac_automagic = ['ConstructionMagic', 'LayerStacker', 'MacBannerCache', 'MacSymbolFinder'] + + +def available(context: interfaces.context.ContextInterface) -> List[interfaces.automagic.AutomagicInterface]: + """Returns an ordered list of all subclasses of + :class:`~volatility.framework.interfaces.automagic.AutomagicInterface`. + + The order is based on the priority attributes of the subclasses, in order to ensure the automagics are listed in + an appropriate order. + + Args: + context: The context that will contain any automagic configuration values. + """ + import_files(sys.modules[__name__]) + config_path = constants.AUTOMAGIC_CONFIG_PATH + return sorted([ + clazz(context, interfaces.configuration.path_join(config_path, clazz.__name__)) + for clazz in class_subclasses(interfaces.automagic.AutomagicInterface) + ], + key = lambda x: x.priority) + + +def choose_automagic( + automagics: List[interfaces.automagic.AutomagicInterface], + plugin: Type[interfaces.plugins.PluginInterface]) -> List[Type[interfaces.automagic.AutomagicInterface]]: + """Chooses which automagics to run, maintaining the order they were handed + in.""" + + plugin_category = "None" + plugin_categories = plugin.__module__.split('.') + lowest_index = len(plugin_categories) + + automagic_categories = {'windows': windows_automagic, 'linux': linux_automagic, 'mac': mac_automagic} + + for os in automagic_categories: + try: + if plugin_categories.index(os) < lowest_index: + lowest_index = plugin_categories.index(os) + plugin_category = os + except ValueError: + # The value wasn't found, try the next one + pass + + if plugin_category not in automagic_categories: + vollog.info("No plugin category detected") + return automagics + + vollog.info("Detected a {} category plugin".format(plugin_category)) + output = [] + for amagic in automagics: + if amagic.__class__.__name__ in automagic_categories[plugin_category]: + output += [amagic] + return output + + +def run(automagics: List[interfaces.automagic.AutomagicInterface], + context: interfaces.context.ContextInterface, + configurable: Union[interfaces.configuration.ConfigurableInterface, + Type[interfaces.configuration.ConfigurableInterface]], + config_path: str, + progress_callback: constants.ProgressCallback = None) -> List[traceback.TracebackException]: + """Runs through the list of `automagics` in order, allowing them to make + changes to the context. + + Args: + automagics: A list of :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` objects + context: The context (that inherits from :class:`~volatility.framework.interfaces.context.ContextInterface`) for modification + configurable: An object that inherits from :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface` + config_path: The path within the `context.config` for options required by the `configurable` + progress_callback: A function that takes a percentage (and an optional description) that will be called periodically + + This is where any automagic is allowed to run, and alter the context in order to satisfy/improve all requirements + + Returns a list of traceback objects that occurred during the autorun procedure + + Note: + The order of the `automagics` list is important. An `automagic` that populates configurations may be necessary + for an `automagic` that populates the context based on the configuration information. + """ + for automagic in automagics: + if not isinstance(automagic, interfaces.automagic.AutomagicInterface): + raise TypeError("Automagics must only contain AutomagicInterface subclasses") + + if (not isinstance(configurable, interfaces.configuration.ConfigurableInterface) + and not issubclass(configurable, interfaces.configuration.ConfigurableInterface)): + raise TypeError("Automagic operates on configurables only") + + # TODO: Fix need for top level config element just because we're using a MultiRequirement to group the + # configurable's config requirements + # configurable_class: Type[interfaces.configuration.ConfigurableInterface] + if isinstance(configurable, interfaces.configuration.ConfigurableInterface): + configurable_class = configurable.__class__ + else: + configurable_class = configurable + requirement = requirements.MultiRequirement(name = configurable_class.__name__) + for req in configurable.get_requirements(): + requirement.add_requirement(req) + + exceptions = [] + + for automagic in automagics: + try: + vollog.info("Running automagic: {}".format(automagic.__class__.__name__)) + automagic(context, config_path, requirement, progress_callback) + except Exception as excp: + exceptions.append(traceback.TracebackException.from_exception(excp)) + return exceptions diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/construct_layers.py b/app/parsers/vol_Parser/volatility/framework/automagic/construct_layers.py new file mode 100644 index 00000000..66288db6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/construct_layers.py @@ -0,0 +1,67 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""An automagic module to use configuration data to configure and then +construct classes that fulfill the descendants of a :class:`~volatility.framewo +rk.interfaces.configuration.ConfigurableInterface`.""" + +import logging +import sys +from typing import List + +from volatility import framework +from volatility.framework import constants +from volatility.framework import interfaces + +vollog = logging.getLogger(__name__) + + +class ConstructionMagic(interfaces.automagic.AutomagicInterface): + """Constructs underlying layers. + + Class to run through the requirement tree of the :class:`~volatility.framework.interfaces.configuration.ConfigurableInterface` + and from the bottom of the tree upwards, attempt to construct all + :class:`~volatility.framework.interfaces.configuration.ConstructableRequirementInterface` based classes. + + :warning: This `automagic` should run first to allow existing configurations to have been constructed for use by later automagic + """ + priority = 0 + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback = None, + optional = False) -> List[str]: + + # Make sure we import the layers, so they can reconstructed + framework.import_files(sys.modules['volatility.framework.layers']) + + result = [] # type: List[str] + if requirement.unsatisfied(context, config_path): + # Having called validate at the top level tells us both that we need to dig deeper + # but also ensures that TranslationLayerRequirements have got the correct subrequirements if their class is populated + + subreq_config_path = interfaces.configuration.path_join(config_path, requirement.name) + for subreq in requirement.requirements.values(): + try: + self(context, subreq_config_path, subreq, optional = optional or subreq.optional) + except Exception as e: + # We don't really care if this fails, it tends to mean the configuration isn't complete for that item + vollog.log(constants.LOGLEVEL_VVVV, "Construction Exception occurred: {}".format(e)) + invalid = subreq.unsatisfied(context, subreq_config_path) + # We want to traverse optional paths, so don't check until we've tried to validate + # We also don't want to emit a debug message when a parent is optional, hence the optional parameter + if invalid and not (optional or subreq.optional): + vollog.log(constants.LOGLEVEL_V, "Failed on requirement: {}".format(subreq_config_path)) + result.append(interfaces.configuration.path_join(subreq_config_path, subreq.name)) + if result: + return result + elif isinstance(requirement, interfaces.configuration.ConstructableRequirementInterface): + # We know all the subrequirements are filled, so let's populate + requirement.construct(context, config_path) + + if progress_callback is not None: + progress_callback(100, "Reconstruction finished") + + return [] diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/linux.py b/app/parsers/vol_Parser/volatility/framework/automagic/linux.py new file mode 100644 index 00000000..6f7781b4 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/linux.py @@ -0,0 +1,156 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Optional, Tuple, Type + +from volatility.framework import interfaces, constants +from volatility.framework.automagic import symbol_cache, symbol_finder +from volatility.framework.layers import intel, scanners +from volatility.framework.symbols import linux + +vollog = logging.getLogger(__name__) + + +class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 45 + exclusion_list = ['mac', 'windows'] + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempts to identify linux within this layer.""" + # Bail out by default unless we can stack properly + layer = context.layers[layer_name] + join = interfaces.configuration.path_join + + # Never stack on top of an intel layer + # FIXME: Find a way to improve this check + if isinstance(layer, intel.Intel): + return None + + linux_banners = LinuxBannerCache.load_banners() + # If we have no banners, don't bother scanning + if not linux_banners: + vollog.info("No Linux banners found - if this is a linux plugin, please check your symbol files location") + return None + + mss = scanners.MultiStringScanner([x for x in linux_banners if x is not None]) + for _, banner in layer.scan(context = context, scanner = mss, progress_callback = progress_callback): + dtb = None + vollog.debug("Identified banner: {}".format(repr(banner))) + + symbol_files = linux_banners.get(banner, None) + if symbol_files: + isf_path = symbol_files[0] + table_name = context.symbol_space.free_table_name('LintelStacker') + table = linux.LinuxKernelIntermedSymbols(context, + 'temporary.' + table_name, + name = table_name, + isf_url = isf_path) + context.symbol_space.append(table) + kaslr_shift, aslr_shift = cls.find_aslr(context, + table_name, + layer_name, + progress_callback = progress_callback) + + layer_class = intel.Intel # type: Type + if 'init_top_pgt' in table.symbols: + layer_class = intel.Intel32e + dtb_symbol_name = 'init_top_pgt' + elif 'init_level4_pgt' in table.symbols: + layer_class = intel.Intel32e + dtb_symbol_name = 'init_level4_pgt' + else: + dtb_symbol_name = 'swapper_pg_dir' + + dtb = cls.virtual_to_physical_address(table.get_symbol(dtb_symbol_name).address + kaslr_shift) + + # Build the new layer + new_layer_name = context.layers.free_layer_name("IntelLayer") + config_path = join("IntelHelper", new_layer_name) + context.config[join(config_path, "memory_layer")] = layer_name + context.config[join(config_path, "page_map_offset")] = dtb + context.config[join(config_path, LinuxSymbolFinder.banner_config_key)] = str(banner, 'latin-1') + + layer = layer_class(context, + config_path = config_path, + name = new_layer_name, + metadata = {'kaslr_value': aslr_shift}) + + if layer and dtb: + vollog.debug("DTB was found at: 0x{:0x}".format(dtb)) + return layer + return None + + @classmethod + def find_aslr(cls, + context: interfaces.context.ContextInterface, + symbol_table: str, + layer_name: str, + progress_callback: constants.ProgressCallback = None) \ + -> Tuple[int, int]: + """Determines the offset of the actual DTB in physical space and its + symbol offset.""" + init_task_symbol = symbol_table + constants.BANG + 'init_task' + init_task_json_address = context.symbol_space.get_symbol(init_task_symbol).address + swapper_signature = rb"swapper(\/0|\x00\x00)\x00\x00\x00\x00\x00\x00" + module = context.module(symbol_table, layer_name, 0) + address_mask = context.symbol_space[symbol_table].config.get('symbol_mask', None) + + task_symbol = module.get_type('task_struct') + comm_child_offset = task_symbol.relative_child_offset('comm') + + for offset in context.layers[layer_name].scan(scanner = scanners.RegExScanner(swapper_signature), + context = context, + progress_callback = progress_callback): + init_task_address = offset - comm_child_offset + init_task = module.object(object_type = 'task_struct', offset = init_task_address, absolute = True) + if init_task.pid != 0: + continue + elif init_task.has_member('state') and init_task.state.cast('unsigned int') != 0: + continue + + # This we get for free + aslr_shift = init_task.files.cast('long unsigned int') - module.get_symbol('init_files').address + kaslr_shift = init_task_address - cls.virtual_to_physical_address(init_task_json_address) + if address_mask: + aslr_shift = aslr_shift & address_mask + + if aslr_shift & 0xfff != 0 or kaslr_shift & 0xfff != 0: + continue + vollog.debug("Linux ASLR shift values determined: physical {:0x} virtual {:0x}".format( + kaslr_shift, aslr_shift)) + return kaslr_shift, aslr_shift + + # We don't throw an exception, because we may legitimately not have an ASLR shift, but we report it + vollog.debug("Scanners could not determine any ASLR shifts, using 0 for both") + return 0, 0 + + @classmethod + def virtual_to_physical_address(cls, addr: int) -> int: + """Converts a virtual linux address to a physical one (does not account + of ASLR)""" + if addr > 0xffffffff80000000: + return addr - 0xffffffff80000000 + return addr - 0xc0000000 + + +class LinuxBannerCache(symbol_cache.SymbolBannerCache): + """Caches the banners found in the Linux symbol files.""" + + os = "linux" + symbol_name = "linux_banner" + banner_path = constants.LINUX_BANNERS_PATH + + +class LinuxSymbolFinder(symbol_finder.SymbolFinder): + """Linux symbol loader based on uname signature strings.""" + + banner_config_key = "kernel_banner" + banner_cache = LinuxBannerCache + symbol_class = "volatility.framework.symbols.linux.LinuxKernelIntermedSymbols" + find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1] diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/mac.py b/app/parsers/vol_Parser/volatility/framework/automagic/mac.py new file mode 100644 index 00000000..5705bdd9 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/mac.py @@ -0,0 +1,206 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import struct +from typing import Optional + +from volatility.framework import interfaces, constants, layers +from volatility.framework.automagic import symbol_cache, symbol_finder +from volatility.framework.layers import intel, scanners +from volatility.framework.symbols import mac + +vollog = logging.getLogger(__name__) + + +class MacIntelStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 45 + exclusion_list = ['windows', 'linux'] + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempts to identify mac within this layer.""" + # Bail out by default unless we can stack properly + layer = context.layers[layer_name] + new_layer = None + join = interfaces.configuration.path_join + + # Never stack on top of an intel layer + # FIXME: Find a way to improve this check + if isinstance(layer, intel.Intel): + return None + + mac_banners = MacBannerCache.load_banners() + # If we have no banners, don't bother scanning + if not mac_banners: + vollog.info("No Mac banners found - if this is a mac plugin, please check your symbol files location") + return None + + mss = scanners.MultiStringScanner([x for x in mac_banners if x]) + for banner_offset, banner in layer.scan(context = context, scanner = mss, + progress_callback = progress_callback): + dtb = None + + symbol_files = mac_banners.get(banner, None) + if symbol_files: + isf_path = symbol_files[0] + table_name = context.symbol_space.free_table_name('MacintelStacker') + table = mac.MacKernelIntermedSymbols(context = context, + config_path = join('temporary', table_name), + name = table_name, + isf_url = isf_path) + context.symbol_space.append(table) + kaslr_shift = cls.find_aslr(context = context, + symbol_table = table_name, + layer_name = layer_name, + compare_banner = banner, + compare_banner_offset = banner_offset, + progress_callback = progress_callback) + + if kaslr_shift == 0: + vollog.log(constants.LOGLEVEL_VVV, "Invalid kalsr_shift found at offset: {}".format(banner_offset)) + continue + + bootpml4_addr = cls.virtual_to_physical_address(table.get_symbol("BootPML4").address + kaslr_shift) + + new_layer_name = context.layers.free_layer_name("MacDTBTempLayer") + config_path = join("automagic", "MacIntelHelper", new_layer_name) + context.config[join(config_path, "memory_layer")] = layer_name + context.config[join(config_path, "page_map_offset")] = bootpml4_addr + + layer = layers.intel.Intel32e(context, + config_path = config_path, + name = new_layer_name, + metadata = {'os': 'Mac'}) + + idlepml4_ptr = table.get_symbol("IdlePML4").address + kaslr_shift + idlepml4_str = layer.read(idlepml4_ptr, 4) + idlepml4_addr = struct.unpack(" int: + """Determines the offset of the actual DTB in physical space and its + symbol offset.""" + version_symbol = symbol_table + constants.BANG + 'version' + version_json_address = context.symbol_space.get_symbol(version_symbol).address + + version_major_symbol = symbol_table + constants.BANG + 'version_major' + version_major_json_address = context.symbol_space.get_symbol(version_major_symbol).address + version_major_phys_offset = cls.virtual_to_physical_address(version_major_json_address) + + version_minor_symbol = symbol_table + constants.BANG + 'version_minor' + version_minor_json_address = context.symbol_space.get_symbol(version_minor_symbol).address + version_minor_phys_offset = cls.virtual_to_physical_address(version_minor_json_address) + + if not compare_banner_offset or not compare_banner: + offset_generator = cls._scan_generator(context, layer_name, progress_callback) + else: + offset_generator = [(compare_banner_offset, compare_banner)] + + aslr_shift = 0 + + for offset, banner in offset_generator: + banner_major, banner_minor = [int(x) for x in banner[22:].split(b".")[0:2]] + + tmp_aslr_shift = offset - cls.virtual_to_physical_address(version_json_address) + + major_string = context.layers[layer_name].read(version_major_phys_offset + tmp_aslr_shift, 4) + major = struct.unpack(" int: + """Converts a virtual mac address to a physical one (does not account + of ASLR)""" + if addr > 0xffffff8000000000: + addr = addr - 0xffffff8000000000 + else: + addr = addr - 0xff8000000000 + + return addr + + @classmethod + def _scan_generator(cls, context, layer_name, progress_callback): + darwin_signature = rb"Darwin Kernel Version \d{1,3}\.\d{1,3}\.\d{1,3}: [^\x00]+\x00" + + for offset in context.layers[layer_name].scan(scanner = scanners.RegExScanner(darwin_signature), + context = context, + progress_callback = progress_callback): + + banner = context.layers[layer_name].read(offset, 128) + + idx = banner.find(b"\x00") + if idx != -1: + banner = banner[:idx] + + yield offset, banner + + +class MacBannerCache(symbol_cache.SymbolBannerCache): + """Caches the banners found in the Mac symbol files.""" + os = "mac" + symbol_name = "version" + banner_path = constants.MAC_BANNERS_PATH + + +class MacSymbolFinder(symbol_finder.SymbolFinder): + """Mac symbol loader based on uname signature strings.""" + + banner_config_key = 'kernel_banner' + banner_cache = MacBannerCache + find_aslr = MacIntelStacker.find_aslr + symbol_class = "volatility.framework.symbols.mac.MacKernelIntermedSymbols" diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/pdbscan.py b/app/parsers/vol_Parser/volatility/framework/automagic/pdbscan.py new file mode 100644 index 00000000..f46b110f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/pdbscan.py @@ -0,0 +1,305 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module for scanning translation layers looking for Windows PDB records +from loaded PE files. + +This module contains a standalone scanner, and also a :class:`~volatility.framework.interfaces.layers.ScannerInterface` +based scanner for use within the framework by calling :func:`~volatility.framework.interfaces.layers.DataLayerInterface.scan`. +""" +import logging +import math +import os +from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union + +from volatility.framework import constants, exceptions, interfaces, layers +from volatility.framework.configuration import requirements +from volatility.framework.layers import intel, scanners +from volatility.framework.symbols import native +from volatility.framework.symbols.windows.pdb import PDBUtility + +if __name__ == "__main__": + import sys + + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) + +vollog = logging.getLogger(__name__) + +ValidKernelType = Tuple[str, int, Dict[str, Optional[Union[bytes, str, int]]]] +KernelsType = Iterable[Dict[str, Any]] + + +class KernelPDBScanner(interfaces.automagic.AutomagicInterface): + """Windows symbol loader based on PDB signatures. + + An Automagic object that looks for all Intel translation layers and scans each of them for a pdb signature. + When found, a search for a corresponding Intermediate Format data file is carried out and if found an appropriate + symbol space is automatically loaded. + + Once a specific kernel PDB signature has been found, a virtual address for the loaded kernel is determined + by one of two methods. The first method assumes a specific mapping from the kernel's physical address to its + virtual address (typically the kernel is loaded at its physical location plus a specific offset). The second method + searches for a particular structure that lists the kernel module's virtual address, its size (not checked) and the + module's name. This value is then used if one was not found using the previous method. + """ + priority = 30 + max_pdb_size = 0x400000 + + def find_virtual_layers_from_req(self, context: interfaces.context.ContextInterface, config_path: str, + requirement: interfaces.configuration.RequirementInterface) -> List[str]: + """Traverses the requirement tree, rooted at `requirement` looking for + virtual layers that might contain a windows PDB. + + Returns a list of possible layers + + Args: + context: The context in which the `requirement` lives + config_path: The path within the `context` for the `requirement`'s configuration variables + requirement: The root of the requirement tree to search for :class:~`volatility.framework.interfaces.layers.TranslationLayerRequirement` objects to scan + progress_callback: Means of providing the user with feedback during long processes + + Returns: + A list of (layer_name, scan_results) + """ + sub_config_path = interfaces.configuration.path_join(config_path, requirement.name) + results = [] # type: List[str] + if isinstance(requirement, requirements.TranslationLayerRequirement): + # Check for symbols in this layer + # FIXME: optionally allow a full (slow) scan + # FIXME: Determine the physical layer no matter the virtual layer + virtual_layer_name = context.config.get(sub_config_path, None) + layer_name = context.config.get(interfaces.configuration.path_join(sub_config_path, "memory_layer"), None) + if layer_name and virtual_layer_name: + memlayer = context.layers[virtual_layer_name] + if isinstance(memlayer, intel.Intel): + results = [virtual_layer_name] + else: + for subreq in requirement.requirements.values(): + results += self.find_virtual_layers_from_req(context, sub_config_path, subreq) + return results + + def recurse_symbol_fulfiller(self, + context: interfaces.context.ContextInterface, + valid_kernel: ValidKernelType, + progress_callback: constants.ProgressCallback = None) -> None: + """Fulfills the SymbolTableRequirements in `self._symbol_requirements` + found by the `recurse_symbol_requirements`. + + This pass will construct any requirements that may need it in the context it was passed + + Args: + context: Context on which to operate + valid_kernel: A list of offsets where valid kernels have been found + """ + for sub_config_path, requirement in self._symbol_requirements: + # TODO: Potentially think about multiple symbol requirements in both the same and different levels of the requirement tree + # TODO: Consider whether a single found kernel can fulfill multiple requirements + if valid_kernel: + # TODO: Check that the symbols for this kernel will fulfill the requirement + virtual_layer, _kvo, kernel = valid_kernel + if not isinstance(kernel['pdb_name'], str) or not isinstance(kernel['GUID'], str): + raise TypeError("PDB name or GUID not a string value") + + PDBUtility.load_windows_symbol_table( + context = context, + guid = kernel['GUID'], + age = kernel['age'], + pdb_name = kernel['pdb_name'], + symbol_table_class = "volatility.framework.symbols.windows.WindowsKernelIntermedSymbols", + config_path = sub_config_path, + progress_callback = progress_callback) + else: + vollog.debug("No suitable kernel pdb signature found") + + def set_kernel_virtual_offset(self, context: interfaces.context.ContextInterface, + valid_kernel: ValidKernelType) -> None: + """Traverses the requirement tree, looking for kernel_virtual_offset + values that may need setting and sets it based on the previously + identified `valid_kernel`. + + Args: + context: Context on which to operate and provide the kernel virtual offset + valid_kernel: List of valid kernels and offsets + """ + if valid_kernel: + # Set the virtual offset under the TranslationLayer it applies to + virtual_layer, kvo, kernel = valid_kernel + kvo_path = interfaces.configuration.path_join(context.layers[virtual_layer].config_path, + 'kernel_virtual_offset') + context.config[kvo_path] = kvo + vollog.debug("Setting kernel_virtual_offset to {}".format(hex(kvo))) + + def get_physical_layer_name(self, context, vlayer): + return context.config.get(interfaces.configuration.path_join(vlayer.config_path, 'memory_layer'), None) + + def method_fixed_mapping(self, + context: interfaces.context.ContextInterface, + vlayer: layers.intel.Intel, + progress_callback: constants.ProgressCallback = None) -> Optional[ValidKernelType]: + # TODO: Verify this is a windows image + vollog.debug("Kernel base determination - testing fixed base address") + valid_kernel = None + virtual_layer_name = vlayer.name + physical_layer_name = self.get_physical_layer_name(context, vlayer) + + kernel_pdb_names = [bytes(name + ".pdb", "utf-8") for name in constants.windows.KERNEL_MODULE_NAMES] + kernels = PDBUtility.pdbname_scan(ctx = context, + layer_name = physical_layer_name, + page_size = vlayer.page_size, + pdb_names = kernel_pdb_names, + progress_callback = progress_callback) + for kernel in kernels: + # It seems the kernel is loaded at a fixed mapping (presumably because the memory manager hasn't started yet) + if kernel['mz_offset'] is None or not isinstance(kernel['mz_offset'], int): + # Rule out kernels that couldn't find a suitable MZ header + continue + if vlayer.bits_per_register == 64: + kvo = kernel['mz_offset'] + (31 << int(math.ceil(math.log2(vlayer.maximum_address + 1)) - 5)) + else: + kvo = kernel['mz_offset'] + (1 << (vlayer.bits_per_register - 1)) + try: + kvp = vlayer.mapping(kvo, 0) + if (any([(p == kernel['mz_offset'] and layer_name == physical_layer_name) + for (_, _, p, _, layer_name) in kvp])): + valid_kernel = (virtual_layer_name, kvo, kernel) + break + else: + vollog.debug("Potential kernel_virtual_offset did not map to expected location: {}".format( + hex(kvo))) + except exceptions.InvalidAddressException: + vollog.debug("Potential kernel_virtual_offset caused a page fault: {}".format(hex(kvo))) + return valid_kernel + + def _method_offset(self, + context: interfaces.context.ContextInterface, + vlayer: layers.intel.Intel, + pattern: bytes, + result_offset: int, + progress_callback: constants.ProgressCallback = None) -> Optional[ValidKernelType]: + """Method for finding a suitable kernel offset based on a module + table.""" + vollog.debug("Kernel base determination - searching layer module list structure") + valid_kernel = None # type: Optional[ValidKernelType] + # If we're here, chances are high we're in a Win10 x64 image with kernel base randomization + physical_layer_name = self.get_physical_layer_name(context, vlayer) + physical_layer = context.layers[physical_layer_name] + # TODO: On older windows, this might be \WINDOWS\system32\nt rather than \SystemRoot\system32\nt + results = physical_layer.scan(context, scanners.BytesScanner(pattern), progress_callback = progress_callback) + seen = set() # type: Set[int] + # Because this will launch a scan of the virtual layer, we want to be careful + for result in results: + # TODO: Identify the specific structure we're finding and document this a bit better + pointer = context.object("pdbscan!unsigned long long", + offset = (result + result_offset), + layer_name = physical_layer_name) + address = pointer & vlayer.address_mask + if address in seen: + continue + seen.add(address) + + valid_kernel = self.check_kernel_offset(context, vlayer, address, progress_callback) + + if valid_kernel: + break + return valid_kernel + + def method_module_offset(self, + context: interfaces.context.ContextInterface, + vlayer: layers.intel.Intel, + progress_callback: constants.ProgressCallback = None) -> ValidKernelType: + return self._method_offset(context, vlayer, b"\\SystemRoot\\system32\\nt", + -16 - int(vlayer.bits_per_register / 8), progress_callback) + + def method_kdbg_offset(self, + context: interfaces.context.ContextInterface, + vlayer: layers.intel.Intel, + progress_callback: constants.ProgressCallback = None) -> ValidKernelType: + return self._method_offset(context, vlayer, b"KDBG", 8, progress_callback) + + def check_kernel_offset(self, + context: interfaces.context.ContextInterface, + vlayer: layers.intel.Intel, + address: int, + progress_callback: constants.ProgressCallback = None) -> Optional[ValidKernelType]: + """Scans a virtual address.""" + # Scan a few megs of the virtual space at the location to see if they're potential kernels + + valid_kernel = None # type: Optional[ValidKernelType] + kernel_pdb_names = [bytes(name + ".pdb", "utf-8") for name in constants.windows.KERNEL_MODULE_NAMES] + + virtual_layer_name = vlayer.name + try: + if vlayer.read(address, 0x2) == b'MZ': + res = list( + PDBUtility.pdbname_scan(ctx = context, + layer_name = vlayer.name, + page_size = vlayer.page_size, + pdb_names = kernel_pdb_names, + progress_callback = progress_callback, + start = address, + end = address + self.max_pdb_size)) + if res: + valid_kernel = (virtual_layer_name, address, res[0]) + except exceptions.InvalidAddressException: + pass + return valid_kernel + + # List of methods to be run, in order, to determine the valid kernels + methods = [method_kdbg_offset, method_module_offset, method_fixed_mapping] + + def determine_valid_kernel(self, + context: interfaces.context.ContextInterface, + potential_layers: List[str], + progress_callback: constants.ProgressCallback = None) -> Optional[ValidKernelType]: + """Runs through the identified potential kernels and verifies their + suitability. + + This carries out a scan using the pdb_signature scanner on a physical layer. It uses the + results of the scan to determine the virtual offset of the kernel. On early windows implementations + there is a fixed mapping between the physical and virtual addresses of the kernel. On more recent versions + a search is conducted for a structure that will identify the kernel's virtual offset. + + Args: + context: Context on which to operate + potential_kernels: Dictionary containing `GUID`, `age`, `pdb_name` and `mz_offset` keys + progress_callback: Function taking a percentage and optional description to be called during expensive computations to indicate progress + + Returns: + A dictionary of valid kernels + """ + valid_kernel = None # type: Optional[ValidKernelType] + for virtual_layer_name in potential_layers: + vlayer = context.layers.get(virtual_layer_name, None) + if isinstance(vlayer, layers.intel.Intel): + for method in self.methods: + valid_kernel = method(self, context, vlayer, progress_callback) + if valid_kernel: + break + if not valid_kernel: + vollog.info("No suitable kernels found during pdbscan") + return valid_kernel + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> None: + if requirement.unsatisfied(context, config_path): + if "pdbscan" not in context.symbol_space: + context.symbol_space.append(native.NativeTable("pdbscan", native.std_ctypes)) + # TODO: check if this is a windows symbol requirement, otherwise ignore it + self._symbol_requirements = self.find_requirements(context, config_path, requirement, + requirements.SymbolTableRequirement) + potential_layers = self.find_virtual_layers_from_req(context = context, + config_path = config_path, + requirement = requirement) + for sub_config_path, symbol_req in self._symbol_requirements: + parent_path = interfaces.configuration.parent_path(sub_config_path) + if symbol_req.unsatisfied(context, parent_path): + valid_kernel = self.determine_valid_kernel(context, potential_layers, progress_callback) + if valid_kernel: + self.recurse_symbol_fulfiller(context, valid_kernel, progress_callback) + self.set_kernel_virtual_offset(context, valid_kernel) + + if progress_callback is not None: + progress_callback(100, "PDB scanning finished") diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/stacker.py b/app/parsers/vol_Parser/volatility/framework/automagic/stacker.py new file mode 100644 index 00000000..8c87d566 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/stacker.py @@ -0,0 +1,261 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""This module attempts to automatically stack layers. + +This automagic module fulfills :class:`~volatility.framework.interfaces.configuration.TranslationLayerRequirement` that are not already fulfilled, by attempting to +stack as many layers on top of each other as possible. The base/lowest layer is derived from the +"automagic.general.single_location" configuration path. Layers are then attempting in likely height order, and +once a layer successfully stacks on top of the existing layers, it is removed from the possible choices list +(so no layer type can exist twice in the layer stack). +""" + +import logging +import sys +import traceback +from typing import List, Optional, Tuple, Type + +from volatility import framework +from volatility.framework import interfaces, constants +from volatility.framework.automagic import construct_layers +from volatility.framework.configuration import requirements +from volatility.framework.layers import physical + +vollog = logging.getLogger(__name__) + + +class LayerStacker(interfaces.automagic.AutomagicInterface): + """Builds up layers in a single stack. + + This class mimics the volatility 2 style of stacking address spaces. It builds up various layers based on + separate :class:`~volatility.framework.interfaces.automagic.StackerLayerInterface` classes. These classes are + built up based on a `stack_order` class variable each has. + + This has a high priority to provide other automagic modules as complete a context/configuration tree as possible. + Upon completion it will re-call the :class:`~volatility.framework.automagic.construct_layers.ConstructionMagic`, + so that any stacked layers are actually constructed and added to the context. + """ + # Most important automagic, must happen first! + priority = 10 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._cached = None + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> Optional[List[str]]: + """Runs the automagic over the configurable.""" + + framework.import_files(sys.modules['volatility.framework.layers']) + + # Quick exit if we're not needed + if not requirement.unsatisfied(context, config_path): + return None + + # Bow out quickly if the UI hasn't provided a single_location + unsatisfied = self.unsatisfied(self.context, self.config_path) + if unsatisfied: + vollog.info("Unable to run LayerStacker, unsatisfied requirement: {}".format(unsatisfied)) + return list(unsatisfied) + if not self.config or not self.config.get('single_location', None): + raise ValueError("Unable to run LayerStacker, single_location parameter not provided") + + # Search for suitable requirements + self.stack(context, config_path, requirement, progress_callback) + + if progress_callback is not None: + progress_callback(100, "Stacking attempts finished") + return None + + def stack(self, context: interfaces.context.ContextInterface, config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback) -> None: + """Stacks the various layers and attaches these to a specific + requirement. + + Args: + context: Context on which to operate + config_path: Configuration path under which to store stacking data + requirement: Requirement that should have layers stacked on it + progress_callback: Function to provide callback progress + """ + # If we're cached, find Now we need to find where to apply the stack configuration + if self._cached: + top_layer_name, subconfig = self._cached + result = self.find_suitable_requirements(context, config_path, requirement, [top_layer_name]) + if result: + appropriate_config_path, layer_name = result + context.config.merge(appropriate_config_path, subconfig) + context.config[appropriate_config_path] = top_layer_name + return + self._cached = None + + new_context = context.clone() + location = self.config.get('single_location', None) + + # Setup the local copy of the resource + current_layer_name = context.layers.free_layer_name("FileLayer") + current_config_path = interfaces.configuration.path_join(config_path, "stack", current_layer_name) + + # This must be specific to get us started, setup the config and run + new_context.config[interfaces.configuration.path_join(current_config_path, "location")] = location + physical_layer = physical.FileLayer(new_context, current_config_path, current_layer_name) + new_context.add_layer(physical_layer) + + stacked_layers = self.stack_layer(new_context, current_layer_name, self.create_stackers_list(), + progress_callback) + + if stacked_layers is not None: + # Applies the stacked_layers to each requirement in the requirements list + result = self.find_suitable_requirements(new_context, config_path, requirement, stacked_layers) + if result: + path, layer = result + # splice in the new configuration into the original context + context.config.merge(path, new_context.layers[layer].build_configuration()) + + # Call the construction magic now we may have new things to construct + constructor = construct_layers.ConstructionMagic( + context, interfaces.configuration.path_join(self.config_path, "ConstructionMagic")) + constructor(context, config_path, requirement) + + # Stash the changed config items + self._cached = context.config.get(path, None), context.config.branch(path) + vollog.debug("Stacked layers: {}".format(stacked_layers)) + + @classmethod + def stack_layer(cls, + context: interfaces.context.ContextInterface, + initial_layer: str, + stack_set: List[Type[interfaces.automagic.StackerLayerInterface]] = None, + progress_callback: constants.ProgressCallback = None): + """Stacks as many possible layers on top of the initial layer as can be done. + + WARNING: This modifies the context provided and may pollute it with unnecessary layers + Recommended use is to: + 1. Pass in context.clone() instead of context + 2. When provided the layer list, choose the desired layer + 3. Build the configuration using layer.build_configuration() + 4. Merge the configuration into the original context with context.config.merge() + 5. Call Construction magic to reconstruct the layers from just the configuration + + Args: + context: The context on which to operate + initial_layer: The name of the initial layer within the context + stack_set: A list of StackerLayerInterface objects in the order they should be stacked + progress_callback: A function to report progress during the process + + Returns: + A list of layer names that exist in the provided context, stacked in order (highest to lowest) + """ + # Repeatedly apply "determine what this is" code and build as much up as possible + stacked = True + stacked_layers = [initial_layer] + if stack_set is None: + stack_set = list(framework.class_subclasses(interfaces.automagic.StackerLayerInterface)) + + for stacker_item in stack_set: + if not issubclass(stacker_item, interfaces.automagic.StackerLayerInterface): + raise TypeError("Stacker {} is not a descendent of StackerLayerInterface".format(stacker_item.__name__)) + + while stacked: + stacked = False + new_layer = None + stacker_cls = None + for stacker_cls in stack_set: + stacker = stacker_cls() + try: + vollog.log(constants.LOGLEVEL_VV, "Attempting to stack using {}".format(stacker_cls.__name__)) + new_layer = stacker.stack(context, initial_layer, progress_callback) + if new_layer: + context.layers.add_layer(new_layer) + vollog.log(constants.LOGLEVEL_VV, + "Stacked {} using {}".format(new_layer.name, stacker_cls.__name__)) + break + except Exception as excp: + # Stacking exceptions are likely only of interest to developers, so the lowest level of logging + fulltrace = traceback.TracebackException.from_exception(excp).format(chain = True) + vollog.log(constants.LOGLEVEL_VVV, "Exception during stacking: {}".format(str(excp))) + vollog.log(constants.LOGLEVEL_VVVV, "\n".join(fulltrace)) + else: + stacked = False + if new_layer and stacker_cls: + stacked_layers = [new_layer.name] + stacked_layers + initial_layer = new_layer.name + stacked = True + stack_set.remove(stacker_cls) + return stacked_layers + + def create_stackers_list(self) -> List[Type[interfaces.automagic.StackerLayerInterface]]: + """Creates the list of stackers to use based on the config option""" + stack_set = sorted(framework.class_subclasses(interfaces.automagic.StackerLayerInterface), + key = lambda x: x.stack_order) + stacker_list = self.config.get('stackers', []) + if len(stacker_list): + result = [] + for stacker in stack_set: + if stacker.__name__ in stacker_list: + result.append(stacker) + stack_set = result + return stack_set + + @classmethod + def find_suitable_requirements(cls, context: interfaces.context.ContextInterface, config_path: str, + requirement: interfaces.configuration.RequirementInterface, + stacked_layers: List[str]) -> Optional[Tuple[str, str]]: + """Looks for translation layer requirements and attempts to apply the + stacked layers to it. If it succeeds it returns the configuration path + and layer name where the stacked nodes were spliced into the tree. + + Returns: + A tuple of a configuration path and layer name for the top of the stacked layers + or None if suitable requirements are not found + """ + child_config_path = interfaces.configuration.path_join(config_path, requirement.name) + if isinstance(requirement, requirements.TranslationLayerRequirement): + if requirement.unsatisfied(context, config_path): + original_setting = context.config.get(child_config_path, None) + for layer_name in stacked_layers: + context.config[child_config_path] = layer_name + if not requirement.unsatisfied(context, config_path): + return child_config_path, layer_name + # Clean-up to restore the config + if original_setting: + context.config[child_config_path] = original_setting + else: + del context.config[child_config_path] + else: + return child_config_path, context.config.get(child_config_path, None) + for req_name, req in requirement.requirements.items(): + result = cls.find_suitable_requirements(context, child_config_path, req, stacked_layers) + if result: + return result + return None + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # This is not optional for the stacker to run, so optional must be marked as False + return [ + requirements.URIRequirement(name = "single_location", + description = "Specifies a base location on which to stack", + optional = True), + requirements.ListRequirement(name = "stackers", description = "List of stackers", optional = True) + ] + + +def choose_os_stackers(plugin): + """Identifies the stackers that should be run, based on the plugin (and thus os) provided""" + plugin_first_level = plugin.__module__.split('.')[2] + + # Ensure all stackers are loaded + framework.import_files(sys.modules['volatility.framework.layers']) + + result = [] + for stacker in sorted(framework.class_subclasses(interfaces.automagic.StackerLayerInterface), + key = lambda x: x.stack_order): + if plugin_first_level in stacker.exclusion_list: + continue + result.append(stacker.__name__) + return result diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/symbol_cache.py b/app/parsers/vol_Parser/volatility/framework/automagic/symbol_cache.py new file mode 100644 index 00000000..89d72718 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/symbol_cache.py @@ -0,0 +1,127 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import gc +import json +import logging +import os +import pickle +import urllib +import urllib.parse +import urllib.request +from typing import Dict, List, Optional + +from volatility.framework import constants, exceptions, interfaces +from volatility.framework.symbols import intermed + +vollog = logging.getLogger(__name__) + +BannersType = Dict[bytes, List[str]] + + +class SymbolBannerCache(interfaces.automagic.AutomagicInterface): + """Runs through all symbols tables and caches their banners.""" + + # Since this is necessary for ConstructionMagic, we set a lower priority + # The user would run it eventually either way, but running it first means it can be used that run + priority = 0 + + os = None # type: Optional[str] + symbol_name = "banner_name" # type: str + banner_path = None # type: Optional[str] + + @classmethod + def load_banners(cls) -> BannersType: + if not cls.banner_path: + raise ValueError("Banner_path not appropriately set") + banners = {} # type: BannersType + if os.path.exists(cls.banner_path): + with open(cls.banner_path, "rb") as f: + # We use pickle over JSON because we're dealing with bytes objects + banners.update(pickle.load(f)) + + # Remove possibilities that can't exist locally. + remove_banners = [] + for banner in banners: + for path in banners[banner]: + url = urllib.parse.urlparse(path) + if url.scheme == 'file' and not os.path.exists(urllib.request.url2pathname(url.path)): + vollog.log( + constants.LOGLEVEL_VV, "Removing cached path {} for banner {}: file does not exist".format( + path, str(banner or b'', 'latin-1'))) + banners[banner].remove(path) + # This is probably excessive, but it's here if we need it + # if url.scheme == 'jar': + # zip_file, zip_path = url.path.split("!") + # zip_file = urllib.parse.urlparse(zip_file).path + # if ((not os.path.exists(zip_file)) or (zip_path not in zipfile.ZipFile(zip_file).namelist())): + # vollog.log(constants.LOGLEVEL_VV, + # "Removing cached path {} for banner {}: file does not exist".format(path, banner)) + # banners[banner].remove(path) + + if not banners[banner]: + remove_banners.append(banner) + for remove_banner in remove_banners: + del banners[remove_banner] + return banners + + @classmethod + def save_banners(cls, banners): + + with open(cls.banner_path, "wb") as f: + pickle.dump(banners, f) + + def __call__(self, context, config_path, configurable, progress_callback = None): + """Runs the automagic over the configurable.""" + + # Bomb out if we're just the generic interface + if self.os is None: + return + + # We only need to be called once, so no recursion necessary + banners = self.load_banners() + + cacheables = list(intermed.IntermediateSymbolTable.file_symbol_url(self.os)) + + for banner in banners: + for json_file in banners[banner]: + if json_file in cacheables: + cacheables.remove(json_file) + + total = len(cacheables) + if total > 0: + vollog.info("Building {} caches...".format(self.os)) + for current in range(total): + if progress_callback is not None: + progress_callback(current * 100 / total, "Building {} caches".format(self.os)) + isf_url = cacheables[current] + + isf = None + try: + # Loading the symbol table will be very slow until it's been validated + isf = intermed.IntermediateSymbolTable(context, config_path, "temp", isf_url, validate = False) + + # We should store the banner against the filename + # We don't bother with the hash (it'll likely take too long to validate) + # but we should check at least that the banner matches on load. + banner = isf.get_symbol(self.symbol_name).constant_data + vollog.log(constants.LOGLEVEL_VV, "Caching banner {} for file {}".format(banner, isf_url)) + + bannerlist = banners.get(banner, []) + bannerlist.append(isf_url) + banners[banner] = bannerlist + except exceptions.SymbolError: + pass + except json.JSONDecodeError: + vollog.log(constants.LOGLEVEL_VV, "Caching file {} failed due to JSON error".format(isf_url)) + finally: + # Get rid of the loaded file, in case it sits in memory + if isf: + del isf + gc.collect() + + # Rewrite the cached banners each run, since writing is faster than the banner_cache validation portion + self.save_banners(banners) + + if progress_callback is not None: + progress_callback(100, "Built {} caches".format(self.os)) diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/symbol_finder.py b/app/parsers/vol_Parser/volatility/framework/automagic/symbol_finder.py new file mode 100644 index 00000000..50844d0f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/symbol_finder.py @@ -0,0 +1,141 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Any, Iterable, List, Tuple, Type, Optional, Callable + +from volatility.framework import interfaces, constants, layers, exceptions +from volatility.framework.automagic import symbol_cache +from volatility.framework.configuration import requirements +from volatility.framework.layers import scanners + +vollog = logging.getLogger(__name__) + + +class SymbolFinder(interfaces.automagic.AutomagicInterface): + """Symbol loader based on signature strings.""" + priority = 40 + + banner_config_key = "banner" # type: str + banner_cache = None # type: Optional[Type[symbol_cache.SymbolBannerCache]] + symbol_class = None # type: Optional[str] + find_aslr = None # type: Optional[Callable] + + def __init__(self, context: interfaces.context.ContextInterface, config_path: str) -> None: + super().__init__(context, config_path) + self._requirements = [] # type: List[Tuple[str, interfaces.configuration.RequirementInterface]] + self._banners = {} # type: symbol_cache.BannersType + + @property + def banners(self) -> symbol_cache.BannersType: + """Creates a cached copy of the results, but only it's been + requested.""" + if not self._banners: + if not self.banner_cache: + raise RuntimeError("Cache has not been properly defined for {}".format(self.__class__.__name__)) + self._banners = self.banner_cache.load_banners() + return self._banners + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> None: + """Searches for SymbolTableRequirements and attempt to populate + them.""" + + # Bomb out early if our details haven't been configured + if self.symbol_class is None: + return + + self._requirements = self.find_requirements( + context, + config_path, + requirement, (requirements.TranslationLayerRequirement, requirements.SymbolTableRequirement), + shortcut = False) + + for (sub_path, requirement) in self._requirements: + parent_path = interfaces.configuration.parent_path(sub_path) + + if (isinstance(requirement, requirements.SymbolTableRequirement) + and requirement.unsatisfied(context, parent_path)): + for (tl_sub_path, tl_requirement) in self._requirements: + tl_parent_path = interfaces.configuration.parent_path(tl_sub_path) + # Find the TranslationLayer sibling to the SymbolTableRequirement + if (isinstance(tl_requirement, requirements.TranslationLayerRequirement) + and tl_parent_path == parent_path): + if context.config.get(tl_sub_path, None): + self._banner_scan(context, parent_path, requirement, context.config[tl_sub_path], + progress_callback) + break + + def _banner_scan(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.ConstructableRequirementInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> None: + """Accepts a context, config_path and SymbolTableRequirement, with a + constructed layer_name and scans the layer for banners.""" + + # Bomb out early if there's no banners + if not self.banners: + return + + mss = scanners.MultiStringScanner([x for x in self.banners if x is not None]) + + layer = context.layers[layer_name] + + # Check if the Stacker has already found what we're looking for + if layer.config.get(self.banner_config_key, None): + banner_list = [(0, bytes(layer.config[self.banner_config_key], + 'raw_unicode_escape'))] # type: Iterable[Any] + else: + # Swap to the physical layer for scanning + # TODO: Fix this so it works for layers other than just Intel + layer = context.layers[layer.config['memory_layer']] + banner_list = layer.scan(context = context, scanner = mss, progress_callback = progress_callback) + + for _, banner in banner_list: + vollog.debug("Identified banner: {}".format(repr(banner))) + symbol_files = self.banners.get(banner, None) + if symbol_files: + isf_path = symbol_files[0] + vollog.debug("Using symbol library: {}".format(symbol_files[0])) + clazz = self.symbol_class + # Set the discovered options + path_join = interfaces.configuration.path_join + context.config[path_join(config_path, requirement.name, "class")] = clazz + context.config[path_join(config_path, requirement.name, "isf_url")] = isf_path + context.config[path_join(config_path, requirement.name, "symbol_mask")] = layer.address_mask + + # Set a default symbol_shift when attempt to determine it, + # so we can create the symbols which are used in finding the aslr_shift anyway + if not context.config.get(path_join(config_path, requirement.name, "symbol_shift"), None): + # Don't overwrite it if it's already been set, it will be manually refound if not present + prefound_kaslr_value = context.layers[layer_name].metadata.get('kaslr_value', 0) + context.config[path_join(config_path, requirement.name, "symbol_shift")] = prefound_kaslr_value + # Construct the appropriate symbol table + requirement.construct(context, config_path) + + # Apply the ASLR masking (only if we're not already shifted) + if self.find_aslr and not context.config.get(path_join(config_path, requirement.name, "symbol_shift"), + None): + unmasked_symbol_table_name = context.config.get(path_join(config_path, requirement.name), None) + if not unmasked_symbol_table_name: + raise exceptions.SymbolSpaceError("Symbol table could not be constructed") + if not isinstance(layer, layers.intel.Intel): + raise TypeError("Layer name {} is not an intel space") + aslr_shift = self.find_aslr(context, unmasked_symbol_table_name, layer.config['memory_layer']) + context.config[path_join(config_path, requirement.name, "symbol_shift")] = aslr_shift + context.symbol_space.clear_symbol_cache(unmasked_symbol_table_name) + + break + else: + if symbol_files: + vollog.debug("Symbol library path not found: {}".format(symbol_files[0])) + # print("Kernel", banner, hex(banner_offset)) + else: + vollog.debug("No existing banners found") + # TODO: Fallback to generic regex search? diff --git a/app/parsers/vol_Parser/volatility/framework/automagic/windows.py b/app/parsers/vol_Parser/volatility/framework/automagic/windows.py new file mode 100644 index 00000000..82caaca3 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/automagic/windows.py @@ -0,0 +1,458 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Module to identify the Directory Table Base and architecture of windows +memory images. + +This module contains a PageMapScanner that scans a physical layer to identify self-referential pointers. +All windows versions include a self-referential pointer in their Directory Table Base's top table, in order to +have a single offset that will allow manipulation of the page tables themselves. + +In older windows version the self-referential pointer was at a specific fixed index within the table, +which was different for each architecture. In very recent Windows versions, the self-referential pointer +index has been randomized, so a different heuristic must be used. In these versions of windows it was found +that the physical offset for the DTB was always within the range of 0x1a0000 to 0x1b0000. As such, a search +for any self-referential pointer within these pages gives a high probability of being an accurate DTB. + +The self-referential indices for older versions of windows are listed below: + + +--------------+-------+ + | Architecture | Index | + +==============+=======+ + | x86 | 0x300 | + +--------------+-------+ + | PAE | 0x3 | + +--------------+-------+ + | x64 | 0x1ED | + +--------------+-------+ +""" +import logging +import struct +from typing import Any, Generator, List, Optional, Tuple, Type + +from volatility.framework import interfaces, layers, constants +from volatility.framework.configuration import requirements +from volatility.framework.layers import intel + +vollog = logging.getLogger(__name__) + + +class DtbTest: + """This class generically contains the tests for a page based on a set of + class parameters. + + When constructed it contains all the information necessary to + extract a specific index from a page and determine whether it points + back to that page's offset. + """ + + def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None: + self.layer_type = layer_type + self.ptr_struct = ptr_struct + self.ptr_size = struct.calcsize(ptr_struct) + self.ptr_reference = ptr_reference + self.mask = mask + self.page_size = layer_type.page_size # type: int + + def _unpack(self, value: bytes) -> int: + return struct.unpack("<" + self.ptr_struct, value)[0] + + def __call__(self, data: bytes, data_offset: int, page_offset: int) -> Optional[Tuple[int, Any]]: + """Tests a specific page in a chunk of data to see if it contains a + self-referential pointer. + + Args: + data: The chunk of data that contains the page to be scanned + data_offset: Where, within the layer, the chunk of data lives + page_offset: Where, within the data, the page to be scanned starts + + Returns: + A valid DTB within this page (and an additional parameter for data) + """ + value = data[page_offset + (self.ptr_reference * self.ptr_size):page_offset + + ((self.ptr_reference + 1) * self.ptr_size)] + try: + ptr = self._unpack(value) + except struct.error: + return None + # The value *must* be present (bit 0) since it's a mapped page + # It's almost always writable (bit 1) + # It's occasionally Super, but not reliably so, haven't checked when/why not + # The top 3-bits are usually ignore (which in practice means 0 + # Need to find out why the middle 3-bits are usually 6 (0110) + if ptr != 0 and (ptr & self.mask == data_offset + page_offset) & (ptr & 0xFF1 == 0x61): + dtb = (ptr & self.mask) + return self.second_pass(dtb, data, data_offset) + return None + + def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]: + """Re-reads over the whole page to validate other records based on the + number of pages marked user vs super. + + Args: + dtb: The identified dtb that needs validating + data: The chunk of data that contains the dtb to be validated + data_offset: Where, within the layer, the chunk of data lives + + Returns: + A valid DTB within this page + """ + page = data[dtb - data_offset:dtb - data_offset + self.page_size] + usr_count, sup_count = 0, 0 + for i in range(0, self.page_size, self.ptr_size): + val = self._unpack(page[i:i + self.ptr_size]) + if val & 0x1: + sup_count += 0 if (val & 0x4) else 1 + usr_count += 1 if (val & 0x4) else 0 + # print(hex(dtb), usr_count, sup_count, usr_count + sup_count) + # We sometimes find bogus DTBs at 0x16000 with a very low sup_count and 0 usr_count + # I have a winxpsp2-x64 image with identical usr/sup counts at 0x16000 and 0x24c00 as well as the actual 0x3c3000 + if usr_count or sup_count > 5: + return dtb, None + return None + + +class DtbTest32bit(DtbTest): + + def __init__(self): + super().__init__(layer_type = layers.intel.WindowsIntel, + ptr_struct = "I", + ptr_reference = 0x300, + mask = 0xFFFFF000) + + +class DtbTest64bit(DtbTest): + + def __init__(self): + super().__init__(layer_type = layers.intel.WindowsIntel32e, + ptr_struct = "Q", + ptr_reference = 0x1ED, + mask = 0x3FFFFFFFFFF000) + + +class DtbTestPae(DtbTest): + + def __init__(self): + super().__init__(layer_type = layers.intel.WindowsIntelPAE, + ptr_struct = "Q", + ptr_reference = 0x3, + mask = 0x3FFFFFFFFFF000) + + def second_pass(self, dtb: int, data: bytes, data_offset: int) -> Optional[Tuple[int, Any]]: + """PAE top level directory tables contains four entries and the self- + referential pointer occurs in the second level of tables (so as not to + use up a full quarter of the space). This is very high in the space, + and occurs in the fourht (last quarter) second-level table. The + second-level tables appear always to come sequentially directly after + the real dtb. The value for the real DTB is therefore four page + earlier (and the fourth entry should point back to the `dtb` parameter + this function was originally passed. + + Args: + dtb: The identified self-referential pointer that needs validating + data: The chunk of data that contains the dtb to be validated + data_offset: Where, within the layer, the chunk of data lives + + Returns: + Returns the actual DTB of the PAE space + """ + dtb -= 0x4000 + # If we're not in something that the overlap would pick up + if dtb - data_offset >= 0: + pointers = data[dtb - data_offset + (3 * self.ptr_size):dtb - data_offset + (4 * self.ptr_size)] + val = self._unpack(pointers) + if (val & self.mask == dtb + 0x4000) and (val & 0xFFF == 0x001): + return dtb, None + return None + + +class DtbSelfReferential(DtbTest): + """A generic DTB test which looks for a self-referential pointer at *any* + index within the page.""" + + def __init__(self, layer_type: Type[layers.intel.Intel], ptr_struct: str, ptr_reference: int, mask: int) -> None: + super().__init__(layer_type = layer_type, ptr_struct = ptr_struct, ptr_reference = ptr_reference, mask = mask) + + def __call__(self, data: bytes, data_offset: int, page_offset: int) -> Optional[Tuple[int, int]]: + page = data[page_offset:page_offset + self.page_size] + if not page: + return None + ref_pages = set() + for ref in range(0, self.page_size, self.ptr_size): + ptr_data = page[ref:ref + self.ptr_size] + if len(ptr_data) == self.ptr_size: + ptr, = struct.unpack(self.ptr_struct, ptr_data) + if ((ptr & self.mask) == (data_offset + page_offset)) and (data_offset + page_offset > 0): + ref_pages.add(ref) + # The DTB is extremely unlikely to refer back to itself. so the number of reference should always be exactly 1 + if len(ref_pages) == 1: + return (data_offset + page_offset), ref_pages.pop() + return None + + +class DtbSelfRef32bit(DtbSelfReferential): + + def __init__(self): + super().__init__(layer_type = layers.intel.WindowsIntel, + ptr_struct = "I", + ptr_reference = 0x300, + mask = 0xFFFFF000) + + +class DtbSelfRef64bit(DtbSelfReferential): + + def __init__(self): + super().__init__(layer_type = layers.intel.WindowsIntel32e, + ptr_struct = "Q", + ptr_reference = 0x1ED, + mask = 0x3FFFFFFFFFF000) + + +class PageMapScanner(interfaces.layers.ScannerInterface): + """Scans through all pages using DTB tests to determine a dtb offset and + architecture.""" + overlap = 0x4000 + thread_safe = True + tests = [DtbTest64bit(), DtbTest32bit(), DtbTestPae()] + """The default tests to run when searching for DTBs""" + + def __init__(self, tests: List[DtbTest]) -> None: + super().__init__() + self.tests = tests + + def __call__(self, data: bytes, data_offset: int) -> Generator[Tuple[DtbTest, int], None, None]: + for test in self.tests: + for page_offset in range(0, len(data), 0x1000): + result = test(data, data_offset, page_offset) + if result is not None: + if result[0] < self.chunk_size: + yield (test, result[0]) + + +class WintelHelper(interfaces.automagic.AutomagicInterface): + """Windows DTB finder based on self-referential pointers. + + This class adheres to the :class:`~volatility.framework.interfaces.automagic.AutomagicInterface` interface + and both determines the directory table base of an intel layer if one hasn't been specified, and constructs + the intel layer if necessary (for example when reconstructing a pre-existing configuration). + + It will scan for existing TranslationLayers that do not have a DTB using the :class:`PageMapScanner` + """ + priority = 20 + tests = [DtbTest64bit(), DtbTest32bit(), DtbTestPae()] + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> None: + useful = [] + sub_config_path = interfaces.configuration.path_join(config_path, requirement.name) + if (isinstance(requirement, requirements.TranslationLayerRequirement) + and requirement.requirements.get("class", False) + and requirement.unsatisfied(context, config_path)): + class_req = requirement.requirements["class"] + + for test in self.tests: + if (test.layer_type.__module__ + "." + test.layer_type.__name__ == class_req.config_value( + context, sub_config_path)): + useful.append(test) + + # Determine if a class has been chosen + # Once an appropriate class has been chosen, attempt to determine the page_map_offset value + if ("memory_layer" in requirement.requirements + and not requirement.requirements["memory_layer"].unsatisfied(context, sub_config_path)): + # Only bother getting the DTB if we don't already have one + page_map_offset_path = interfaces.configuration.path_join(sub_config_path, "page_map_offset") + if not context.config.get(page_map_offset_path, None): + physical_layer_name = requirement.requirements["memory_layer"].config_value( + context, sub_config_path) + if not isinstance(physical_layer_name, str): + raise TypeError("Physical layer name is not a string: {}".format(sub_config_path)) + physical_layer = context.layers[physical_layer_name] + # Check lower layer metadata first + if physical_layer.metadata.get('page_map_offset', None): + context.config[page_map_offset_path] = physical_layer.metadata['page_map_offset'] + else: + hits = physical_layer.scan(context, PageMapScanner(useful), progress_callback) + for test, dtb in hits: + context.config[page_map_offset_path] = dtb + break + else: + return None + if isinstance(requirement, interfaces.configuration.ConstructableRequirementInterface): + requirement.construct(context, config_path) + else: + for subreq in requirement.requirements.values(): + self(context, sub_config_path, subreq) + + +class WindowsIntelStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 40 + exclusion_list = ['mac', 'linux'] + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempts to determine and stack an intel layer on a physical layer + where possible. + + Where the DTB scan fails, it attempts a heuristic of checking + for the DTB within a specific range. New versions of windows, + with randomized self-referential pointers, appear to always load + their dtb within a small specific range (`0x1a0000` and + `0x1b0000`), so instead we scan for all self-referential + pointers in that range, and ignore any that contain multiple + self-references (since the DTB is very unlikely to point to + itself more than once). + """ + base_layer = context.layers[layer_name] + if isinstance(base_layer, intel.Intel): + return None + if base_layer.metadata.get('os', None) not in ['Windows', 'Unknown']: + return None + layer = config_path = None + + # Check the metadata + if (base_layer.metadata.get('os', None) == 'Windows' and base_layer.metadata.get('page_map_offset')): + arch = base_layer.metadata.get('architecture', None) + if arch not in ['Intel32', 'Intel64']: + return None + # Set the layer type + layer_type = intel.WindowsIntel # type: Type + if arch == 'Intel64': + layer_type = intel.WindowsIntel32e + elif base_layer.metadata.get('pae', False): + layer_type = intel.WindowsIntelPAE + # Construct the layer + new_layer_name = context.layers.free_layer_name("IntelLayer") + config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name) + context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name + context.config[interfaces.configuration.path_join( + config_path, "page_map_offset")] = base_layer.metadata['page_map_offset'] + layer = layer_type(context, config_path = config_path, name = new_layer_name, metadata = {'os': 'Windows'}) + + # Check for the self-referential pointer + if layer is None: + hits = base_layer.scan(context, PageMapScanner(WintelHelper.tests)) + layer = None + config_path = None + for test, dtb in hits: + new_layer_name = context.layers.free_layer_name("IntelLayer") + config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name) + context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name + context.config[interfaces.configuration.path_join(config_path, "page_map_offset")] = dtb + layer = test.layer_type(context, + config_path = config_path, + name = new_layer_name, + metadata = {'os': 'Windows'}) + break + + # Fall back to a heuristic for finding the Windows DTB + if layer is None: + vollog.debug("Self-referential pointer not in well-known location, moving to recent windows heuristic") + # There is a very high chance that the DTB will live in this narrow segment, assuming we couldn't find it previously + hits = context.layers[layer_name].scan(context, + PageMapScanner([DtbSelfRef64bit()]), + sections = [(0x1a0000, 0x50000)], + progress_callback = progress_callback) + # Flatten the generator + hits = list(hits) + if hits: + # TODO: Decide which to use if there are multiple options + test, page_map_offset = hits[0] + new_layer_name = context.layers.free_layer_name("IntelLayer") + config_path = interfaces.configuration.path_join("IntelHelper", new_layer_name) + context.config[interfaces.configuration.path_join(config_path, "memory_layer")] = layer_name + context.config[interfaces.configuration.path_join(config_path, "page_map_offset")] = page_map_offset + # TODO: Need to determine the layer type (chances are high it's x64, hence this default) + layer = layers.intel.WindowsIntel32e(context, + config_path = config_path, + name = new_layer_name, + metadata = {'os': 'Windows'}) + if layer is not None and config_path: + vollog.debug("DTB was found at: 0x{:0x}".format(context.config[interfaces.configuration.path_join( + config_path, "page_map_offset")])) + return layer + + +class WinSwapLayers(interfaces.automagic.AutomagicInterface): + """Class to read swap_layers filenames from single-swap-layers, create the + layers and populate the single-layers swap_layers.""" + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> None: + """Finds translation layers that can have swap layers added.""" + path_join = interfaces.configuration.path_join + self._translation_requirement = self.find_requirements(context, + config_path, + requirement, + requirements.TranslationLayerRequirement, + shortcut = False) + for trans_sub_config, trans_req in self._translation_requirement: + if not isinstance(trans_req, requirements.TranslationLayerRequirement): + # We need this so the type-checker knows we're a TranslationLayerRequirement + continue + swap_sub_config, swap_req = self.find_swap_requirement(trans_sub_config, trans_req) + counter = 0 + swap_config = interfaces.configuration.parent_path(swap_sub_config) + + if swap_req and swap_req.unsatisfied(context, swap_config): + # See if any of them need constructing + for swap_location in self.config.get('single_swap_locations', []): + # Setup config locations/paths + current_layer_name = swap_req.name + str(counter) + current_layer_path = path_join(swap_sub_config, current_layer_name) + layer_loc_path = path_join(current_layer_path, "location") + layer_class_path = path_join(current_layer_path, "class") + counter += 1 + + # Fill in the config + if swap_location: + context.config[current_layer_path] = current_layer_name + context.config[layer_loc_path] = swap_location + context.config[layer_class_path] = 'volatility.framework.layers.physical.FileLayer' + + # Add the requirement + new_req = requirements.TranslationLayerRequirement(name = current_layer_name, + description = "Swap Layer", + optional = False) + swap_req.add_requirement(new_req) + + context.config[path_join(swap_sub_config, 'number_of_elements')] = counter + context.config[swap_sub_config] = True + + swap_req.construct(context, swap_config) + + @staticmethod + def find_swap_requirement(config: str, + requirement: requirements.TranslationLayerRequirement) \ + -> Tuple[str, Optional[requirements.LayerListRequirement]]: + """Takes a Translation layer and returns its swap_layer requirement.""" + swap_req = None + for req_name in requirement.requirements: + req = requirement.requirements[req_name] + if isinstance(req, requirements.LayerListRequirement) and req.name == 'swap_layers': + swap_req = req + continue + + swap_config = interfaces.configuration.path_join(config, 'swap_layers') + return swap_config, swap_req + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + """Returns the requirements of this plugin.""" + return [ + requirements.ListRequirement( + name = "single_swap_locations", + element_type = str, + min_elements = 0, + max_elements = 16, + description = "Specifies a list of swap layer URIs for use with single-location", + optional = True) + ] diff --git a/app/parsers/vol_Parser/volatility/framework/configuration/__init__.py b/app/parsers/vol_Parser/volatility/framework/configuration/__init__.py new file mode 100644 index 00000000..175dcf29 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/configuration/__init__.py @@ -0,0 +1,5 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework.configuration import requirements diff --git a/app/parsers/vol_Parser/volatility/framework/configuration/requirements.py b/app/parsers/vol_Parser/volatility/framework/configuration/requirements.py new file mode 100644 index 00000000..2b5042d4 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/configuration/requirements.py @@ -0,0 +1,426 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Contains standard Requirement types that all adhere to the :class:`~volatili +ty.framework.interfaces.configuration.RequirementInterface`. + +These requirement types allow plugins to request simple information +types (such as strings, integers, etc) as well as indicating what they +expect to be in the context (such as particular layers or symboltables). +""" +import abc +import logging +from typing import Any, ClassVar, List, Optional, Type, Dict, Tuple + +from volatility.framework import constants, interfaces + +vollog = logging.getLogger(__name__) + + +class MultiRequirement(interfaces.configuration.RequirementInterface): + """Class to hold multiple requirements. + + Technically the Interface could handle this, but it's an interface, + so this is a concrete implementation. + """ + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + return self.unsatisfied_children(context, config_path) + + +class BooleanRequirement(interfaces.configuration.SimpleTypeRequirement): + """A requirement type that contains a boolean value.""" + # Note, this must be a separate class in order to differentiate between Booleans and other instance requirements + + +class IntRequirement(interfaces.configuration.SimpleTypeRequirement): + """A requirement type that contains a single integer.""" + instance_type = int # type: ClassVar[Type] + + +class StringRequirement(interfaces.configuration.SimpleTypeRequirement): + """A requirement type that contains a single unicode string.""" + # TODO: Maybe add string length limits? + instance_type = str # type: ClassVar[Type] + + +class URIRequirement(StringRequirement): + """A requirement type that contains a single unicode string that is a valid + URI.""" + # TODO: Maybe a a check that to unsatisfied that the path really is a URL? + + +class BytesRequirement(interfaces.configuration.SimpleTypeRequirement): + """A requirement type that contains a byte string.""" + instance_type = bytes # type: ClassVar[Type] + + +class ListRequirement(interfaces.configuration.RequirementInterface): + """Allows for a list of a specific type of requirement (all of which must + be met for this requirement to be met) to be specified. + + This roughly correlates to allowing a number of arguments to follow a command line parameter, + such as a list of integers or a list of strings. + + It is distinct from a multi-requirement which stores the subrequirements in a dictionary, not a list, + and does not allow for a dynamic number of values. + """ + + def __init__(self, + element_type: Type[interfaces.configuration.SimpleTypes] = str, + max_elements: Optional[int] = 0, + min_elements: Optional[int] = None, + *args, + **kwargs) -> None: + """Constructs the object. + + Args: + element_type: The (requirement) type of each element within the list + max_elements; The maximum number of acceptable elements this list can contain + min_elements: The minimum number of acceptable elements this list can contain + """ + super().__init__(*args, **kwargs) + if not issubclass(element_type, interfaces.configuration.BasicTypes): + raise TypeError("ListRequirements can only be populated with simple InstanceRequirements") + self.element_type = element_type # type: Type + self.min_elements = min_elements or 0 # type: int + self.max_elements = max_elements # type: Optional[int] + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + """Check the types on each of the returned values and their number and + then call the element type's check for each one.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + default = None + value = self.config_value(context, config_path, default) + if not value and self.min_elements > 0: + vollog.log(constants.LOGLEVEL_V, "ListRequirement Unsatisfied - ListRequirement has non-zero min_elements") + return {config_path: self} + if value is None and not self.optional: + # We need to differentiate between no value and an empty list + vollog.log(constants.LOGLEVEL_V, "ListRequirement Unsatisfied - Value was not specified") + return {config_path: self} + elif value is None: + context.config[config_path] = [] + if not isinstance(value, list): + # TODO: Check this is the correct response for an error + raise TypeError("Unexpected config value found: {}".format(repr(value))) + if not (self.min_elements <= len(value)): + vollog.log(constants.LOGLEVEL_V, "TypeError - Too few values provided to list option.") + return {config_path: self} + if self.max_elements and not (len(value) < self.max_elements): + vollog.log(constants.LOGLEVEL_V, "TypeError - Too many values provided to list option.") + return {config_path: self} + if not all([isinstance(element, self.element_type) for element in value]): + vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.") + return {config_path: self} + return {} + + +class ChoiceRequirement(interfaces.configuration.RequirementInterface): + """Allows one from a choice of strings.""" + + def __init__(self, choices: List[str], *args, **kwargs) -> None: + """Constructs the object. + + Args: + choices: A list of possible string options that can be chosen from + """ + super().__init__(*args, **kwargs) + if not isinstance(choices, list) or any([not isinstance(choice, str) for choice in choices]): + raise TypeError("ChoiceRequirement takes a list of strings as choices") + self.choices = choices + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + """Validates the provided value to ensure it is one of the available + choices.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + value = self.config_value(context, config_path) + if value not in self.choices: + vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices") + return {config_path: self} + return {} + + +class ComplexListRequirement(MultiRequirement, + interfaces.configuration.ConfigurableRequirementInterface, + metaclass = abc.ABCMeta): + """Allows a variable length list of requirements.""" + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + """Validates the provided value to ensure it is one of the available + choices.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + ret_list = super().unsatisfied(context, config_path) + if ret_list: + return ret_list + if (self.config_value(context, config_path, None) is None + or self.config_value(context, interfaces.configuration.path_join(config_path, 'number_of_elements'))): + return {config_path: self} + return {} + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # This is not optional for the stacker to run, so optional must be marked as False + return [ + IntRequirement("number_of_elements", + description = "Determines how many layers are in this list", + optional = False) + ] + + @abc.abstractmethod + def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None: + """Method for constructing within the context any required elements + from subrequirements.""" + + @abc.abstractmethod + def new_requirement(self, index) -> interfaces.configuration.RequirementInterface: + """Builds a new requirement based on the specified index.""" + + def build_configuration(self, context: interfaces.context.ContextInterface, config_path: str, + _: Any) -> interfaces.configuration.HierarchicalDict: + result = interfaces.configuration.HierarchicalDict() + num_elem_config_path = interfaces.configuration.path_join(config_path, self.name, 'number_of_elements') + num_elements = context.config.get(num_elem_config_path, None) + if num_elements is not None: + result["number_of_elements"] = num_elements + for i in range(num_elements): + req = self.new_requirement(i) + self.add_requirement(req) + value_path = interfaces.configuration.path_join(config_path, self.name, req.name) + value = context.config.get(value_path, None) + if value is not None: + result.splice(req.name, context.layers[value].build_configuration()) + result[req.name] = value + return result + + +class LayerListRequirement(ComplexListRequirement): + """Allows a variable length list of layers that must exist.""" + + def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None: + """Method for constructing within the context any required elements + from subrequirements.""" + new_config_path = interfaces.configuration.path_join(config_path, self.name) + num_layers_path = interfaces.configuration.path_join(new_config_path, "number_of_elements") + number_of_layers = context.config[num_layers_path] + + # Build all the layers that can be built + for i in range(number_of_layers): + layer_req = self.requirements.get(self.name + str(i), None) + if layer_req is not None and isinstance(layer_req, TranslationLayerRequirement): + layer_req.construct(context, new_config_path) + + def new_requirement(self, index) -> interfaces.configuration.RequirementInterface: + """Constructs a new requirement based on the specified index.""" + return TranslationLayerRequirement(name = self.name + str(index), + description = "Layer for swap space", + optional = False) + + +class TranslationLayerRequirement(interfaces.configuration.ConstructableRequirementInterface, + interfaces.configuration.ConfigurableRequirementInterface): + """Class maintaining the limitations on what sort of translation layers are + acceptable.""" + + def __init__(self, + name: str, + description: str = None, + default: interfaces.configuration.ConfigSimpleType = None, + optional: bool = False, + oses: List = None, + architectures: List = None) -> None: + """Constructs a Translation Layer Requirement. + + The configuration option's value will be the name of the layer once it exists in the store + + Args: + name: Name of the configuration requirement + description: Description of the configuration requirement + default: A default value (should not be used for TranslationLayers) + optional: Whether the translation layer is required or not + oses: A list of valid operating systems which can satisfy this requirement + architectures: A list of valid architectures which can satisfy this requirement + """ + if oses is None: + oses = [] + if architectures is None: + architectures = [] + self.oses = oses + self.architectures = architectures + super().__init__(name, description, default, optional) + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + """Validate that the value is a valid layer name and that the layer + adheres to the requirements.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + value = self.config_value(context, config_path, None) + if isinstance(value, str): + if value not in context.layers: + vollog.log(constants.LOGLEVEL_V, "IndexError - Layer not found in memory space: {}".format(value)) + return {config_path: self} + if self.oses and context.layers[value].metadata.get('os', None) not in self.oses: + vollog.log(constants.LOGLEVEL_V, "TypeError - Layer is not the required OS: {}".format(value)) + return {config_path: self} + if (self.architectures + and context.layers[value].metadata.get('architecture', None) not in self.architectures): + vollog.log(constants.LOGLEVEL_V, "TypeError - Layer is not the required Architecture: {}".format(value)) + return {config_path: self} + return {} + + if value is not None: + vollog.log(constants.LOGLEVEL_V, + "TypeError - Translation Layer Requirement only accepts string labels: {}".format(repr(value))) + return {config_path: self} + + # TODO: check that the space in the context lives up to the requirements for arch/os etc + + ### NOTE: This validate method has side effects (the dependencies can change)!!! + + self._validate_class(context, interfaces.configuration.parent_path(config_path)) + vollog.log(constants.LOGLEVEL_V, "IndexError - No configuration provided: {}".format(config_path)) + return {config_path: self} + + def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None: + """Constructs the appropriate layer and adds it based on the class + parameter.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + + # Determine the layer name + name = self.name + counter = 2 + while name in context.layers: + name = self.name + str(counter) + counter += 1 + + args = {"context": context, "config_path": config_path, "name": name} + + if any( + [subreq.unsatisfied(context, config_path) for subreq in self.requirements.values() if not subreq.optional]): + return None + + obj = self._construct_class(context, config_path, args) + if obj is not None and isinstance(obj, interfaces.layers.DataLayerInterface): + context.add_layer(obj) + # This should already be done by the _construct_class method + # context.config[config_path] = obj.name + return None + + def build_configuration(self, context: interfaces.context.ContextInterface, _: str, + value: Any) -> interfaces.configuration.HierarchicalDict: + """Builds the appropriate configuration for the specified + requirement.""" + return context.layers[value].build_configuration() + + +class SymbolTableRequirement(interfaces.configuration.ConstructableRequirementInterface, + interfaces.configuration.ConfigurableRequirementInterface): + """Class maintaining the limitations on what sort of symbol spaces are + acceptable.""" + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + """Validate that the value is a valid within the symbol space of the + provided context.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + value = self.config_value(context, config_path, None) + if not isinstance(value, str) and value is not None: + vollog.log(constants.LOGLEVEL_V, + "TypeError - SymbolTableRequirement only accepts string labels: {}".format(repr(value))) + return {config_path: self} + if value and value in context.symbol_space: + # This is an expected situation, so return rather than raise + return {} + elif value: + vollog.log(constants.LOGLEVEL_V, "IndexError - Value not present in the symbol space: {}".format(value + or "")) + + ### NOTE: This validate method has side effects (the dependencies can change)!!! + + self._validate_class(context, interfaces.configuration.parent_path(config_path)) + vollog.log(constants.LOGLEVEL_V, "Symbol table requirement not yet fulfilled: {}".format(config_path)) + return {config_path: self} + + def construct(self, context: interfaces.context.ContextInterface, config_path: str) -> None: + """Constructs the symbol space within the context based on the + subrequirements.""" + config_path = interfaces.configuration.path_join(config_path, self.name) + # Determine the space name + name = context.symbol_space.free_table_name(self.name) + + args = {"context": context, "config_path": config_path, "name": name} + + if any( + [subreq.unsatisfied(context, config_path) for subreq in self.requirements.values() if not subreq.optional]): + return None + + # Fill out the parameter for class creation + if not isinstance(self.requirements["class"], interfaces.configuration.ClassRequirement): + raise TypeError("Class requirement is not of type ClassRequirement: {}".format( + repr(self.requirements["class"]))) + cls = self.requirements["class"].cls + node_config = context.config.branch(config_path) + for req in cls.get_requirements(): + if req.name in node_config.data and req.name != "class": + args[req.name] = node_config.data[req.name] + + obj = self._construct_class(context, config_path, args) + if obj is not None and isinstance(obj, interfaces.symbols.SymbolTableInterface): + context.symbol_space.append(obj) + return None + + def build_configuration(self, context: interfaces.context.ContextInterface, _: str, + value: Any) -> interfaces.configuration.HierarchicalDict: + """Builds the appropriate configuration for the specified + requirement.""" + return context.symbol_space[value].build_configuration() + + +class VersionRequirement(interfaces.configuration.RequirementInterface): + + def __init__(self, + name: str, + description: str = None, + default: bool = False, + optional: bool = False, + component: Type[interfaces.configuration.VersionableInterface] = None, + version: Optional[Tuple[int, ...]] = None) -> None: + super().__init__(name = name, description = description, default = default, optional = optional) + if component is None: + raise TypeError("Component cannot be None") + self._component = component + if version is None: + raise TypeError("Version cannot be None") + self._version = version + + def unsatisfied(self, context: interfaces.context.ContextInterface, + config_path: str) -> Dict[str, interfaces.configuration.RequirementInterface]: + # Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type + config_path = interfaces.configuration.path_join(config_path, self.name) + if len(self._version) > 0 and self._component.version[0] != self._version[0]: + return {config_path: self} + if len(self._version) > 1 and self._component.version[1] < self._version[1]: + return {config_path: self} + context.config[interfaces.configuration.path_join(config_path, self.name)] = True + return {} + + +class PluginRequirement(VersionRequirement): + + def __init__(self, + name: str, + description: str = None, + default: bool = False, + optional: bool = False, + plugin: Type[interfaces.plugins.PluginInterface] = None, + version: Optional[Tuple[int, ...]] = None) -> None: + super().__init__(name = name, + description = description, + default = default, + optional = optional, + component = plugin, + version = version) diff --git a/app/parsers/vol_Parser/volatility/framework/constants/__init__.py b/app/parsers/vol_Parser/volatility/framework/constants/__init__.py new file mode 100644 index 00000000..b39fc2b7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/constants/__init__.py @@ -0,0 +1,94 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Volatility 3 Constants. + +Stores all the constant values that are generally fixed throughout +volatility This includes default scanning block sizes, etc. +""" +import enum +import os.path +import sys +from typing import Optional, Callable + +import volatility.framework.constants.linux +import volatility.framework.constants.windows + +PLUGINS_PATH = [ + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "plugins")), + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "plugins")) +] +"""Default list of paths to load plugins from (volatility/plugins and volatility/framework/plugins)""" + +SYMBOL_BASEPATHS = [ + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "symbols")), + os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "symbols")) +] +"""Default list of paths to load symbols from (volatility/symbols and volatility/framework/symbols)""" + +ISF_EXTENSIONS = ['.json', '.json.xz', '.json.gz', '.json.bz2'] +"""List of accepted extensions for ISF files""" + +if hasattr(sys, 'frozen') and sys.frozen: + # Ensure we include the executable's directory as the base for plugins and symbols + PLUGINS_PATH = [os.path.abspath(os.path.join(os.path.dirname(sys.executable), 'plugins'))] + PLUGINS_PATH + SYMBOL_BASEPATHS = [os.path.abspath(os.path.join(os.path.dirname(sys.executable), 'symbols'))] + SYMBOL_BASEPATHS + +BANG = "!" +"""Constant used to delimit table names from type names when referring to a symbol""" + +# We use the SemVer 2.0.0 versioning scheme +VERSION_MAJOR = 2 # Number of releases of the library with a breaking change +VERSION_MINOR = 0 # Number of changes that only add to the interface +VERSION_PATCH = 0 # Number of changes that do not change the interface +VERSION_SUFFIX = "-beta.1" + +PACKAGE_VERSION = ".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]]) + VERSION_SUFFIX +"""The canonical version of the volatility package""" + +AUTOMAGIC_CONFIG_PATH = 'automagic' +"""The root section within the context configuration for automagic values""" + +LOGLEVEL_V = 9 +"""Logging level for a single -v""" +LOGLEVEL_VV = 8 +"""Logging level for -vv""" +LOGLEVEL_VVV = 7 +"""Logging level for -vvv""" +LOGLEVEL_VVVV = 6 +"""Logging level for -vvvv""" + +CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3") +"""Default path to store cached data""" + +if sys.platform == 'windows': + CACHE_PATH = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "volatility3") +os.makedirs(CACHE_PATH, exist_ok = True) + +LINUX_BANNERS_PATH = os.path.join(CACHE_PATH, "linux_banners.cache") +""""Default location to record information about available linux banners""" + +MAC_BANNERS_PATH = os.path.join(CACHE_PATH, "mac_banners.cache") +""""Default location to record information about available mac banners""" + +BUG_URL = "https://github.com/volatilityfoundation/volatility3/issues" + +ProgressCallback = Optional[Callable[[float, str], None]] +"""Type information for ProgressCallback objects""" + + +class Parallelism(enum.IntEnum): + """An enumeration listing the different types of parallelism applied to + volatility.""" + Off = 0 + Threading = 1 + Multiprocessing = 2 + + +PARALLELISM = Parallelism.Off +"""Default value to the parallelism setting used throughout volatility""" + +ISF_MINIMUM_SUPPORTED = (2, 0, 0) +"""The minimum supported version of the Intermediate Symbol Format""" +ISF_MINIMUM_DEPRECATED = (3, 9, 9) +"""The highest version of the ISF that's deprecated (usually higher than supported)""" diff --git a/app/parsers/vol_Parser/volatility/framework/constants/linux/__init__.py b/app/parsers/vol_Parser/volatility/framework/constants/linux/__init__.py new file mode 100644 index 00000000..c25ea0e2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/constants/linux/__init__.py @@ -0,0 +1,13 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Volatility 3 Linux Constants. + +Linux-specific values that aren't found in debug symbols +""" + +KERNEL_NAME = "__kernel__" + +# arch/x86/include/asm/page_types.h +PAGE_SHIFT = 12 +"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)""" diff --git a/app/parsers/vol_Parser/volatility/framework/constants/windows/__init__.py b/app/parsers/vol_Parser/volatility/framework/constants/windows/__init__.py new file mode 100644 index 00000000..37289759 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/constants/windows/__init__.py @@ -0,0 +1,10 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Volatility 3 Linux Constants. + +Windows-specific values that aren't found in debug symbols +""" + +KERNEL_MODULE_NAMES = ["ntkrnlmp", "ntkrnlpa", "ntkrpamp", "ntoskrnl"] +"""The list of names that kernel modules can have within the windows OS""" diff --git a/app/parsers/vol_Parser/volatility/framework/contexts/__init__.py b/app/parsers/vol_Parser/volatility/framework/contexts/__init__.py new file mode 100644 index 00000000..ba23847b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/contexts/__init__.py @@ -0,0 +1,352 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A `Context` maintains the accumulated state required for various plugins and +framework functions. + +This has been made an object to allow quick swapping and changing of +contexts, to allow a plugin to act on multiple different contexts +without them interfering eith each other. +""" +import functools +import hashlib +from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Union + +from volatility.framework import constants, interfaces, symbols, exceptions +from volatility.framework.objects import templates + + +class Context(interfaces.context.ContextInterface): + """Maintains the context within which to construct objects. + + The context object is the main method of carrying around state that's been constructed for the purposes of + investigating memory. It contains a symbol_space of all the symbols that can be accessed by plugins using the + context. It also contains the memory made up of data and translation layers, and it contains a factory method + for creating new objects. + + Other context objects can be constructed as long as they support the + :class:`~volatility.framework.interfaces.context.ContextInterface`. This is the primary context object to be used + in the volatility framework. It maintains the + """ + + def __init__(self) -> None: + """Initializes the context.""" + super().__init__() + self._symbol_space = symbols.SymbolSpace() + self._memory = interfaces.layers.LayerContainer() + self._config = interfaces.configuration.HierarchicalDict() + + # ## Symbol Space Functions + + @property + def config(self) -> interfaces.configuration.HierarchicalDict: + """Returns a mutable copy of the configuration, but does not allow the + whole configuration to be altered.""" + return self._config + + @config.setter + def config(self, value: interfaces.configuration.HierarchicalDict) -> None: + if not isinstance(value, interfaces.configuration.HierarchicalDict): + raise TypeError("Config must be of type HierarchicalDict") + self._config = value + + @property + def symbol_space(self) -> interfaces.symbols.SymbolSpaceInterface: + """The space of all symbols that can be accessed within this + context.""" + return self._symbol_space + + @property + def layers(self) -> interfaces.layers.LayerContainer: + """A LayerContainer object, allowing access to all data and translation + layers currently available within the context.""" + return self._memory + + # ## Translation Layer Functions + + def add_layer(self, layer: interfaces.layers.DataLayerInterface) -> None: + """Adds a named translation layer to the context. + + Args: + layer: The layer to be added to the memory + + Raises: + volatility.framework.exceptions.LayerException: if the layer is already present, or has + unmet dependencies + """ + self._memory.add_layer(layer) + + # ## Object Factory Functions + + def object(self, + object_type: Union[str, interfaces.objects.Template], + layer_name: str, + offset: int, + native_layer_name: Optional[str] = None, + **arguments) -> interfaces.objects.ObjectInterface: + """Object factory, takes a context, symbol, offset and optional + layername. + + Looks up the layername in the context, finds the object template based on the symbol, + and constructs an object using the object template on the layer at the offset. + + Args: + object_type: The name (or template) of the symbol type on which to construct the object. If this is a name, it should contain an explicit table name. + layer_name: The name of the layer on which to construct the object + offset: The offset within the layer at which the data used to create the object lives + native_layer_name: The name of the layer the object references (for pointers) if different to layer_name + + Returns: + A fully constructed object + """ + if not isinstance(object_type, interfaces.objects.Template): + try: + object_template = self._symbol_space.get_type(object_type) + except exceptions.SymbolError: + object_template = self._symbol_space.get_enumeration(object_type) + else: + if isinstance(object_type, templates.ReferenceTemplate): + object_type = self._symbol_space.get_type(object_type.vol.type_name) + object_template = object_type + # Ensure that if a pre-constructed type is provided we just instantiate it + arguments.update(object_template.vol) + + object_template = object_template.clone() + object_template.update_vol(**arguments) + return object_template(context = self, + object_info = interfaces.objects.ObjectInformation(layer_name = layer_name, + offset = offset, + native_layer_name = native_layer_name, + size = object_template.size)) + + def module(self, + module_name: str, + layer_name: str, + offset: int, + native_layer_name: Optional[str] = None, + size: Optional[int] = None) -> interfaces.context.ModuleInterface: + """Constructs a new os-independent module. + + Args: + module_name: The name of the module + layer_name: The layer within the context in which the module exists + offset: The offset at which the module exists in the layer + native_layer_name: The default native layer for objects constructed by the module + size: The size, in bytes, that the module occupys from offset location within the layer named layer_name + """ + if size: + return SizedModule(self, + module_name = module_name, + layer_name = layer_name, + offset = offset, + size = size, + native_layer_name = native_layer_name) + return Module(self, + module_name = module_name, + layer_name = layer_name, + offset = offset, + native_layer_name = native_layer_name) + + +def get_module_wrapper(method: str) -> Callable: + """Returns a symbol using the symbol_table_name of the Module.""" + + def wrapper(self, name: str) -> Callable: + if constants.BANG not in name: + name = self._module_name + constants.BANG + name + else: + raise ValueError("Cannot reference another module when calling {}".format(method)) + return getattr(self._context.symbol_space, method)(name) + + for entry in ['__annotations__', '__doc__', '__module__', '__name__', '__qualname__']: + proxy_interface = getattr(interfaces.context.ModuleInterface, method) + if hasattr(proxy_interface, entry): + setattr(wrapper, entry, getattr(proxy_interface, entry)) + + return wrapper + + +class Module(interfaces.context.ModuleInterface): + + def object(self, + object_type: str, + offset: int = None, + native_layer_name: Optional[str] = None, + absolute: bool = False, + **kwargs) -> 'interfaces.objects.ObjectInterface': + """Returns an object created using the symbol_table_name and layer_name + of the Module. + + Args: + object_type: Name of the type/enumeration (within the module) to construct + offset: The location of the object, ignored when symbol_type is SYMBOL + native_layer_name: Name of the layer in which constructed objects are made (for pointers) + absolute: whether the type's offset is absolute within memory or relative to the module + """ + if constants.BANG not in object_type: + object_type = self.symbol_table_name + constants.BANG + object_type + else: + raise ValueError("Cannot reference another module when constructing an object") + + if offset is None: + raise TypeError("Offset must not be None for non-symbol objects") + + if not absolute: + offset += self._offset + + # Ensure we don't use a layer_name other than the module's, why would anyone do that? + if 'layer_name' in kwargs: + del kwargs['layer_name'] + return self._context.object(object_type = object_type, + layer_name = self._layer_name, + offset = offset, + native_layer_name = native_layer_name or self._native_layer_name, + **kwargs) + + def object_from_symbol(self, + symbol_name: str, + native_layer_name: Optional[str] = None, + absolute: bool = False, + **kwargs) -> 'interfaces.objects.ObjectInterface': + """Returns an object based on a specific symbol (containing type and + offset information) and the layer_name of the Module. This will throw + a ValueError if the symbol does not contain an associated type, or if + the symbol name is invalid. It will throw a SymbolError if the symbol + cannot be found. + + Args: + symbol_name: Name of the symbol (within the module) to construct + native_layer_name: Name of the layer in which constructed objects are made (for pointers) + absolute: whether the symbol's address is absolute or relative to the module + """ + if constants.BANG not in symbol_name: + symbol_name = self.symbol_table_name + constants.BANG + symbol_name + else: + raise ValueError("Cannot reference another module when constructing an object") + + # Only set the offset if type is Symbol and we were given a name, not a template + symbol_val = self._context.symbol_space.get_symbol(symbol_name) + offset = symbol_val.address + + if not absolute: + offset += self._offset + + if symbol_val.type is None: + raise TypeError("Symbol {} has no associated type".format(symbol_val.name)) + + # Ensure we don't use a layer_name other than the module's, why would anyone do that? + if 'layer_name' in kwargs: + del kwargs['layer_name'] + + # Since type may be a template, we don't just call our own module method + return self._context.object(object_type = symbol_val.type, + layer_name = self._layer_name, + offset = offset, + native_layer_name = native_layer_name or self._native_layer_name, + **kwargs) + + get_symbol = get_module_wrapper('get_symbol') + get_type = get_module_wrapper('get_type') + get_enumeration = get_module_wrapper('get_enumeration') + has_symbol = get_module_wrapper('has_symbol') + has_type = get_module_wrapper('has_type') + has_enumeration = get_module_wrapper('has_enumeration') + + +class SizedModule(Module): + + def __init__(self, + context: interfaces.context.ContextInterface, + module_name: str, + layer_name: str, + offset: int, + size: int, + symbol_table_name: Optional[str] = None, + native_layer_name: Optional[str] = None) -> None: + super().__init__(context, + module_name = module_name, + layer_name = layer_name, + offset = offset, + native_layer_name = native_layer_name, + symbol_table_name = symbol_table_name) + self._size = size + + @property + def size(self) -> int: + """Returns the size of the module (0 for unknown size)""" + return self._size + + @property # type: ignore # FIXME: mypy #5107 + @functools.lru_cache() + def hash(self) -> str: + """Hashes the module for equality checks. + + The mapping should be sorted and should be quicker than reading + the data We turn it into JSON to make a common string and use a + quick hash, because collissions are unlikely + """ + layer = self._context.layers[self.layer_name] + if not isinstance(layer, interfaces.layers.TranslationLayerInterface): + raise TypeError("Hashing modules on non-TranslationLayers is not allowed") + return hashlib.md5(bytes(str(list(layer.mapping(self.offset, self.size, ignore_errors = True))), + 'utf-8')).hexdigest() + + def get_symbols_by_absolute_location(self, offset: int, size: int = 0) -> List[str]: + """Returns the symbols within this module that live at the specified + absolute offset provided.""" + if size < 0: + raise ValueError("Size must be strictly non-negative") + if offset > self._offset + self.size: + return [] + return list( + self._context.symbol_space.get_symbols_by_location(offset = offset - self._offset, + size = size, + table_name = self.symbol_table_name)) + + +class ModuleCollection: + """Class to contain a collection of SizedModules and reason about their + contents.""" + + def __init__(self, modules: List[SizedModule]) -> None: + self._modules = modules + + def deduplicate(self) -> 'ModuleCollection': + """Returns a new deduplicated ModuleCollection featuring no repeated + modules (based on data hash) + + All 0 sized modules will have identical hashes and are therefore + included in the deduplicated version + """ + new_modules = [] + seen = set() # type: Set[str] + for mod in self._modules: + if mod.hash not in seen or mod.size == 0: + new_modules.append(mod) + seen.add(mod.hash) # type: ignore # FIXME: mypy #5107 + return ModuleCollection(new_modules) + + @property + def modules(self) -> Dict[str, List[SizedModule]]: + """A name indexed dictionary of modules using that name in this + collection.""" + return self._generate_module_dict(self._modules) + + @classmethod + def _generate_module_dict(cls, modules: List[SizedModule]) -> Dict[str, List[SizedModule]]: + result = {} # type: Dict[str, List[SizedModule]] + for module in modules: + modlist = result.get(module.name, []) + modlist.append(module) + result[module.name] = modlist + return result + + def get_module_symbols_by_absolute_location(self, offset: int, size: int = 0) -> Iterable[Tuple[str, List[str]]]: + """Returns a tuple of (module_name, list_of_symbol_names) for each + module, where symbols live at the absolute offset in memory + provided.""" + if size < 0: + raise ValueError("Size must be strictly non-negative") + for module in self._modules: + if (offset <= module.offset + module.size) and (offset + size >= module.offset): + yield (module.name, module.get_symbols_by_absolute_location(offset, size)) diff --git a/app/parsers/vol_Parser/volatility/framework/exceptions.py b/app/parsers/vol_Parser/volatility/framework/exceptions.py new file mode 100644 index 00000000..c92b65af --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/exceptions.py @@ -0,0 +1,101 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A list of potential exceptions that volatility can throw. + +These include exceptions that can be thrown on errors by the symbol +space or symbol tables, and by layers when an address is invalid. The +:class:`PagedInvalidAddressException` contains information about the +size of the invalid page. +""" +from typing import Dict, Optional + +from volatility.framework import interfaces + + +class VolatilityException(Exception): + """Class to allow filtering of all VolatilityExceptions.""" + + +class PluginVersionException(VolatilityException): + """Class to allow determining that a required plugin has an invalid + version.""" + + +class PluginRequirementException(VolatilityException): + """Class to allow plugins to indicate that a requirement has not been + fulfilled.""" + + +class SymbolError(VolatilityException): + """Thrown when a symbol lookup has failed.""" + + def __init__(self, symbol_name: Optional[str], table_name: Optional[str], *args) -> None: + super().__init__(*args) + self.symbol_name = symbol_name + self.table_name = table_name + + +class LayerException(VolatilityException): + """Thrown when an error occurs dealing with memory and layers.""" + + def __init__(self, layer_name: str, *args) -> None: + super().__init__(*args) + self.layer_name = layer_name + + +class InvalidAddressException(LayerException): + """Thrown when an address is not valid in the layer it was requested.""" + + def __init__(self, layer_name: str, invalid_address: int, *args) -> None: + super().__init__(layer_name, *args) + self.invalid_address = invalid_address + + +class PagedInvalidAddressException(InvalidAddressException): + """Thrown when an address is not valid in the paged space in which it was + request. This is a subclass of InvalidAddressException and is only + thrown from a paged layer. In most circumstances :class:`InvalidAddressException` + is the correct exception to throw, since this will catch all invalid + mappings (including paged ones). + + Includes the invalid address and the number of bits of the address + that are invalid + """ + + def __init__(self, layer_name: str, invalid_address: int, invalid_bits: int, entry: int, *args) -> None: + super().__init__(layer_name, invalid_address, *args) + self.invalid_bits = invalid_bits + self.entry = entry + + +class SwappedInvalidAddressException(PagedInvalidAddressException): + """Thrown when an address is not valid in the paged layer in which it was + requested, but expected to be in an associated swap layer. + + Includes the swap lookup, as well as the invalid address and the bits of + the lookup that were invalid. + """ + + def __init__(self, layer_name: str, invalid_address: int, invalid_bits: int, entry: int, swap_offset: int, + *args) -> None: + super().__init__(layer_name, invalid_address, invalid_bits, entry, *args) + self.swap_offset = swap_offset + + +class SymbolSpaceError(VolatilityException): + """Thrown when an error occurs dealing with Symbolspaces and SymbolTables.""" + + +class UnsatisfiedException(VolatilityException): + + def __init__(self, unsatisfied: Dict[str, interfaces.configuration.RequirementInterface]) -> None: + super().__init__() + self.unsatisfied = unsatisfied + + +class MissingModuleException(VolatilityException): + + def __init__(self, module: str, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.module = module diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/__init__.py b/app/parsers/vol_Parser/volatility/framework/interfaces/__init__.py new file mode 100644 index 00000000..17fd6f01 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/__init__.py @@ -0,0 +1,16 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""The interfaces module contains the API interface for the core volatility +framework. + +These interfaces should help developers attempting to write components +for the main framework and help them understand how to use the internal +components of volatility to write plugins. +""" + +# Import the submodules we want people to be able to use without importing them themselves +# This will also avoid namespace issues, because people can use interfaces.layers to +# avoid clashing with the layers package +from volatility.framework.interfaces import renderers, configuration, context, layers, objects, plugins, symbols, \ + automagic diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/automagic.py b/app/parsers/vol_Parser/volatility/framework/interfaces/automagic.py new file mode 100644 index 00000000..ee4dad05 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/automagic.py @@ -0,0 +1,134 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Defines the automagic interfaces for populating the context before a plugin +runs. + +Automagic objects attempt to automatically fill configuration values +that a user has not filled. +""" +import logging +from abc import ABCMeta +from typing import Any, List, Optional, Tuple, Union, Type + +from volatility.framework import interfaces, constants +from volatility.framework.configuration import requirements + +vollog = logging.getLogger(__name__) + + +class AutomagicInterface(interfaces.configuration.ConfigurableInterface, metaclass = ABCMeta): + """Class that defines an automagic component that can help fulfill + `Requirements` + + These classes are callable with the following parameters: + + Args: + context: The context in which to store configuration data that the automagic might populate + config_path: Configuration path where the configurable's data under the context's config lives + configurable: The top level configurable whose requirements may need statisfying + progress_callback: An optional function accepting a percentage and optional description to indicate + progress during long calculations + + .. note:: + + The `context` provided here may be different to that provided during initialization. The `context` provided at + initialization should be used for local configuration of the automagic itself, the `context` provided during + the call is to be populated by the automagic. + """ + + priority = 10 + """An ordering to indicate how soon this automagic should be run""" + + def __init__(self, context: interfaces.context.ContextInterface, config_path: str, *args, **kwargs) -> None: + super().__init__(context, config_path) + for requirement in self.get_requirements(): + if not isinstance(requirement, (interfaces.configuration.SimpleTypeRequirement, + requirements.ChoiceRequirement, requirements.ListRequirement)): + raise TypeError( + "Automagic requirements must be a SimpleTypeRequirement, ChoiceRequirement or ListRequirement") + + def __call__(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement: interfaces.configuration.RequirementInterface, + progress_callback: constants.ProgressCallback = None) -> Optional[List[Any]]: + """Runs the automagic over the configurable.""" + return [] + + # TODO: requirement_type can be made UnionType[Type[T], Tuple[Type[T], ...]] + # once mypy properly supports Tuples in instance + + def find_requirements(self, + context: interfaces.context.ContextInterface, + config_path: str, + requirement_root: interfaces.configuration.RequirementInterface, + requirement_type: Union[Tuple[Type[interfaces.configuration.RequirementInterface], ...], + Type[interfaces.configuration.RequirementInterface]], + shortcut: bool = True) -> List[Tuple[str, interfaces.configuration.RequirementInterface]]: + """Determines if there is actually an unfulfilled `Requirement` + waiting. + + This ensures we do not carry out an expensive search when there is no need for a particular `Requirement` + + Args: + context: Context on which to operate + config_path: Configuration path of the top-level requirement + requirement_root: Top-level requirement whose subrequirements will all be searched + requirement_type: Type of requirement to find + shortcut: Only returns requirements that live under unsatisfied requirements + + Returns: + A list of tuples containing the config_path, sub_config_path and requirement identifying the unsatisfied `Requirements` + """ + sub_config_path = interfaces.configuration.path_join(config_path, requirement_root.name) + results = [] # type: List[Tuple[str, interfaces.configuration.RequirementInterface]] + recurse = not shortcut + if isinstance(requirement_root, requirement_type): + if recurse or requirement_root.unsatisfied(context, config_path): + results.append((sub_config_path, requirement_root)) + else: + recurse = True + if recurse: + for subreq in requirement_root.requirements.values(): + results += self.find_requirements(context, sub_config_path, subreq, requirement_type, shortcut) + return results + + +class StackerLayerInterface(metaclass = ABCMeta): + """Class that takes a lower layer and attempts to build on it. + + stack_order determines the order (from low to high) that stacking + layers should be attempted lower levels should have lower + `stack_orders` + """ + + stack_order = 0 + """The order in which to attempt stacking, the lower the earlier""" + exclusion_list = [] # type: List[str] + """The list operating systems/first-level plugin hierarchy that should exclude this stacker""" + + @classmethod + def stack(self, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + """Method to determine whether this builder can operate on the named + layer. If so, modify the context appropriately. + + Returns the name of any new layer stacked on top of this layer or None. The stacking is therefore strictly + linear rather than tree driven. + + Configuration options provided by the context are ignored, and defaults are to be used by this method + to build a space where possible. + + Args: + context: Context in which to construct the higher layer + layer_name: Name of the layer to stack on top of + progress_callback: A callback function to indicate progress through a scan (if one is necessary) + """ + + @classmethod + def stacker_slow_warning(cls): + vollog.warning( + "Reads to this layer are slow, it's recommended to use the layerwriter plugin once to produce a raw file") diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/configuration.py b/app/parsers/vol_Parser/volatility/framework/interfaces/configuration.py new file mode 100644 index 00000000..e19e12a0 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/configuration.py @@ -0,0 +1,722 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""The configuration module contains classes and functions for interacting with +the configuration and requirement trees. + +Volatility plugins can specify a list of requirements (which may have +subrequirements, thus forming a requirement tree). These requirement +trees can contain values, which are contained in a complementary +configuration tree. These two trees act as a protocol between the +plugins and users. The plugins provide requirements that must be +fulfilled, and the users provide configurations values that fulfill +those requirements. Where the user does not provide sufficient +configuration values, automagic modules may extend the configuration +tree themselves. +""" + +import collections.abc +import copy +import json +import logging +import random +import string +import sys +from abc import ABCMeta, abstractmethod +from typing import Any, ClassVar, Dict, Generator, List, Optional, Type, Union, Tuple + +from volatility import classproperty +from volatility.framework import constants, interfaces + +CONFIG_SEPARATOR = "." +"""Use to specify the separator between configuration hierarchies""" + +vollog = logging.getLogger(__name__) + +BasicTypes = (int, bool, bytes, str) +SimpleTypes = Union[int, bool, bytes, str] +ConfigSimpleType = Optional[Union[SimpleTypes, List[SimpleTypes]]] + + +def path_join(*args) -> str: + """Joins configuration paths together.""" + # If a path element (particularly the first) is empty, then remove it from the list + args = tuple([arg for arg in args if arg]) + return CONFIG_SEPARATOR.join(args) + + +def parent_path(value: str) -> str: + """Returns the parent configuration path from a configuration path.""" + return CONFIG_SEPARATOR.join(value.split(CONFIG_SEPARATOR)[:-1]) + + +def path_head(value: str) -> str: + """Return the top of the configuration path""" + return value.split(CONFIG_SEPARATOR)[-1] + + +def path_depth(path: str, depth: int = 1) -> str: + """Returns the `path` up to a certain depth. + + Note that `depth` can be negative (such as `-x`) and will return all + elements except for the last `x` components + """ + return path_join(path.split(CONFIG_SEPARATOR)[:depth]) + + +class HierarchicalDict(collections.abc.Mapping): + """The core of configuration data, it is a mapping class that stores keys + within itself, and also stores lower hierarchies.""" + + def __init__(self, + initial_dict: Dict[str, 'SimpleTypeRequirement'] = None, + separator: str = CONFIG_SEPARATOR) -> None: + """ + Args: + initial_dict: A dictionary to populate the HierachicalDict with initially + separator: A custom hierarchy separator (defaults to CONFIG_SEPARATOR) + """ + if not (isinstance(separator, str) and len(separator) == 1): + raise TypeError("Separator must be a one character string: {}".format(separator)) + self._separator = separator + self._data = {} # type: Dict[str, ConfigSimpleType] + self._subdict = {} # type: Dict[str, 'HierarchicalDict'] + if isinstance(initial_dict, str): + initial_dict = json.loads(initial_dict) + if isinstance(initial_dict, dict): + for k, v in initial_dict.items(): + self[k] = v + elif initial_dict is not None: + raise TypeError( + "Initial_dict must be a dictionary or JSON string containing a dictionary: {}".format(initial_dict)) + + def __eq__(self, other): + """Define equality between HierarchicalDicts""" + return dict(self) == dict(other) + + @property + def separator(self) -> str: + """Specifies the hierarchy separator in use in this HierarchyDict.""" + return self._separator + + @property + def data(self) -> Dict: + """Returns just the data-containing mappings on this level of the + Hierarchy.""" + return self._data.copy() + + def _key_head(self, key: str) -> str: + """Returns the first division of a key based on the dict separator, or + the full key if the separator is not present.""" + if self.separator in key: + return key[:key.index(self.separator)] + else: + return key + + def _key_tail(self, key: str) -> str: + """Returns all but the first division of a key based on the dict + separator, or None if the separator is not in the key.""" + if self.separator in key: + return key[key.index(self.separator) + 1:] + return '' + + def __iter__(self): + """Returns an iterator object that supports the iterator protocol.""" + return self.generator() + + def generator(self) -> Generator[str, None, None]: + """A generator for the data in this level and lower levels of this + mapping. + + Returns: + Returns each item in the top level data, and then all subkeys in a depth first order + """ + for key in self._data: + yield key + for subdict_key in self._subdict: + for key in self._subdict[subdict_key]: + yield subdict_key + self.separator + key + + def __getitem__(self, key): + """Gets an item, traversing down the trees to get to the final + value.""" + try: + if self.separator in key: + subdict = self._subdict[self._key_head(key)] + return subdict[self._key_tail(key)] + else: + return self._data[key] + except KeyError: + raise KeyError(key) + + def __setitem__(self, key: str, value: Any) -> None: + """Sets an item or creates a subdict and sets the item within that.""" + self._setitem(key, value) + + def _setitem(self, key: str, value: Any, is_data: bool = True) -> None: + """Set an item or appends a whole subtree at a key location.""" + if self.separator in key: + subdict = self._subdict.get(self._key_head(key), HierarchicalDict(separator = self.separator)) + subdict._setitem(self._key_tail(key), value, is_data) + self._subdict[self._key_head(key)] = subdict + else: + if is_data: + self._data[key] = self._sanitize_value(value) + else: + if not isinstance(value, HierarchicalDict): + raise TypeError( + "HierarchicalDicts can only store HierarchicalDicts within their structure: {}".format( + type(value))) + self._subdict[key] = value + + def _sanitize_value(self, value: Any) -> ConfigSimpleType: + """Method to ensure all values are standard values and not volatility + objects containing contexts.""" + if isinstance(value, bool): + return bool(value) + elif isinstance(value, int): + return int(value) + elif isinstance(value, str): + return str(value) + elif isinstance(value, bytes): + return bytes(value) + elif isinstance(value, list): + new_list = [] + for element in value: + element_value = self._sanitize_value(element) + if isinstance(element_value, list): + raise TypeError("Configuration list types cannot contain list types") + new_list.append(element_value) + return new_list + elif value is None: + return None + else: + raise TypeError("Invalid type stored in configuration") + + def __delitem__(self, key: str) -> None: + """Deletes an item from the hierarchical dict.""" + try: + if self.separator in key: + subdict = self._subdict[self._key_head(key)] + del subdict[self._key_tail(key)] + else: + del self._data[self._key_head(key)] + except KeyError: + raise KeyError(key) + + def __contains__(self, key: Any) -> bool: + """Determines whether the key is present in the hierarchy.""" + if self.separator in key: + try: + subdict = self._subdict[self._key_head(key)] + return self._key_tail(key) in subdict + except KeyError: + return False + else: + return key in self._data + + def __len__(self) -> int: + """Returns the length of all items.""" + return len(self._data) + sum([len(subdict) for subdict in self._subdict]) + + def branch(self, key: str) -> 'HierarchicalDict': + """Returns the HierarchicalDict housed under the key. + + This differs from the data property, in that it is directed by the `key`, and all layers under that key are + returned, not just those in that level. + + Higher layers are not prefixed with the location of earlier layers, so branching a hierarchy containing `a.b.c.d` + on `a.b` would return a hierarchy containing `c.d`, not `a.b.c.d`. + + Args: + key: The location within the hierarchy to return higher layers. + + Returns: + The HierarchicalDict underneath the specified key (not just the data at that key location in the tree) + """ + try: + if self.separator in key: + return self._subdict[self._key_head(key)].branch(self._key_tail(key)) + else: + return self._subdict[key] + except KeyError: + self._setitem(key = key, value = HierarchicalDict(separator = self.separator), is_data = False) + return HierarchicalDict() + + def splice(self, key: str, value: 'HierarchicalDict') -> None: + """Splices an existing HierarchicalDictionary under a specific key. + + This can be thought of as an inverse of :func:`branch`, although + `branch` does not remove the requested hierarchy, it simply + returns it. + """ + if not isinstance(key, str) or not isinstance(value, HierarchicalDict): + raise TypeError("Splice requires a string key and HierarchicalDict value") + self._setitem(key, value, False) + + def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False) -> None: + """Acts similarly to splice, but maintains previous values. + + If overwrite is true, then entries in the new value are used over those that exist within key already + + Args: + key: The location within the hierarchy at which to merge the `value` + value: HierarchicalDict to be merged under the key node + overwrite: A boolean defining whether the value will be overwritten if it already exists + """ + if not isinstance(key, str) or not isinstance(value, HierarchicalDict): + raise TypeError("Splice requires a string key and HierarchicalDict value") + for item in dict(value): + if self.get(key + self._separator + item, None) is not None: + if overwrite: + self[key + self._separator + item] = value[item] + else: + self[key + self._separator + item] = value[item] + + def clone(self) -> 'HierarchicalDict': + """Duplicates the configuration, allowing changes without affecting the + original. + + Returns: + A duplicate HierarchicalDict of this object + """ + return copy.deepcopy(self) + + def __str__(self) -> str: + """Turns the Hierarchical dict into a string representation.""" + return json.dumps(dict([(key, self[key]) for key in sorted(self.generator())]), indent = 2) + + +class RequirementInterface(metaclass = ABCMeta): + """Class that defines a requirement. + + A requirement is a means for plugins and other framework components to request specific configuration data. + Requirements can either be simple types (such as + :class:`~volatility.framework.configuration.requirements.SimpleTypeRequirement`, + :class:`~volatility.framework.configuration.requirements.IntRequirement`, + :class:`~volatility.framework.configuration.requirements.BytesRequirement` and + :class:`~volatility.framework.configuration.requirements.StringRequirement`) or complex types (such + as :class:`TranslationLayerRequirement`, :class:`SymbolTableRequirement` and :class:`ClassRequirement` + """ + + def __init__(self, + name: str, + description: str = None, + default: ConfigSimpleType = None, + optional: bool = False) -> None: + """ + + Args: + name: The name of the requirement + description: A short textual description of the requirement + default: The default value for the requirement if no value is provided + optional: Whether the requirement must be satisfied or not + """ + super().__init__() + if CONFIG_SEPARATOR in name: + raise ValueError("Name cannot contain the config-hierarchy divider ({})".format(CONFIG_SEPARATOR)) + self._name = name + self._description = description or "" + self._default = default + self._optional = optional + self._requirements = {} # type: Dict[str, RequirementInterface] + + def __repr__(self) -> str: + return "<" + self.__class__.__name__ + ": " + self.name + ">" + + @property + def name(self) -> str: + """The name of the Requirement. + + Names cannot contain CONFIG_SEPARATOR ('.' by default) since + this is used within the configuration hierarchy. + """ + return self._name + + @property + def description(self) -> str: + """A short description of what the Requirement is designed to affect or + achieve.""" + return self._description + + @property + def default(self) -> ConfigSimpleType: + """Returns the default value if one is set.""" + return self._default + + @property + def optional(self) -> bool: + """Whether the Requirement is optional or not.""" + return self._optional + + @optional.setter + def optional(self, value) -> None: + """Sets the optional value for a requirement.""" + self._optional = bool(value) + + def config_value(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + default: ConfigSimpleType = None) -> ConfigSimpleType: + """Returns the value for this Requirement from its config path. + + Args: + context: the configuration store to find the value for this requirement + config_path: the configuration path of the instance of the requirement to be recovered + default: a default value to provide if the requirement's configuration value is not found + """ + return context.config.get(config_path, default) + + # Child operations + @property + def requirements(self) -> Dict[str, 'RequirementInterface']: + """Returns a dictionary of all the child requirements, indexed by + name.""" + return self._requirements.copy() + + def add_requirement(self, requirement: 'RequirementInterface') -> None: + """Adds a child to the list of requirements. + + Args: + requirement: The requirement to add as a child-requirement + """ + self._requirements[requirement.name] = requirement + + def remove_requirement(self, requirement: 'RequirementInterface') -> None: + """Removes a child from the list of requirements. + + Args: + requirement: The requirement to remove as a child-requirement + """ + del self._requirements[requirement.name] + + def unsatisfied_children(self, context: 'interfaces.context.ContextInterface', + config_path: str) -> Dict[str, 'RequirementInterface']: + """Method that will validate all child requirements. + + Args: + context: the context containing the configuration data for this requirement + config_path: the configuration path of this instance of the requirement + + Returns: + A dictionary of full configuration paths for each unsatisfied child-requirement + """ + result = {} + for requirement in self.requirements.values(): + if not requirement.optional: + subresult = requirement.unsatisfied(context, path_join(config_path, self._name)) + result.update(subresult) + return result + + # Validation routines + @abstractmethod + def unsatisfied(self, context: 'interfaces.context.ContextInterface', + config_path: str) -> Dict[str, 'RequirementInterface']: + """Method to validate the value stored at config_path for the + configuration object against a context. + + Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid + + Args: + context: The context object containing the configuration for this requirement + config_path: The configuration path for this requirement to test satisfaction + + Returns: + A dictionary of configuration-paths to requirements that could not be satisfied + """ + + +class SimpleTypeRequirement(RequirementInterface): + """Class to represent a single simple type (such as a boolean, a string, an + integer or a series of bytes)""" + instance_type = bool # type: ClassVar[Type] + + def add_requirement(self, requirement: RequirementInterface): + """Always raises a TypeError as instance requirements cannot have + children.""" + raise TypeError("Instance Requirements cannot have subrequirements") + + def remove_requirement(self, requirement: RequirementInterface): + """Always raises a TypeError as instance requirements cannot have + children.""" + raise TypeError("Instance Requirements cannot have subrequirements") + + def unsatisfied(self, context: 'interfaces.context.ContextInterface', + config_path: str) -> Dict[str, RequirementInterface]: + """Validates the instance requirement based upon its + `instance_type`.""" + config_path = path_join(config_path, self.name) + + value = self.config_value(context, config_path, None) + if not isinstance(value, self.instance_type): + vollog.log( + constants.LOGLEVEL_V, + "TypeError - {} requirements only accept {} type: {}".format(self.name, self.instance_type.__name__, + repr(value))) + return {config_path: self} + return {} + + +class ClassRequirement(RequirementInterface): + """Requires a specific class. + + This is used as means to serialize specific classes for + :class:`TranslationLayerRequirement` and + :class:`SymbolTableRequirement` classes. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._cls = None + + @property + def cls(self) -> Type: + """Contains the actual chosen class based on the configuration value's + class name.""" + return self._cls + + def unsatisfied(self, context: 'interfaces.context.ContextInterface', + config_path: str) -> Dict[str, RequirementInterface]: + """Checks to see if a class can be recovered.""" + config_path = path_join(config_path, self.name) + + value = self.config_value(context, config_path, None) + self._cls = None + if value is not None and isinstance(value, str): + if "." in value: + # TODO: consider importing the prefix + module = sys.modules.get(value[:value.rindex(".")], None) + class_name = value[value.rindex(".") + 1:] + if hasattr(module, class_name): + self._cls = getattr(module, class_name) + else: + if value in globals(): + self._cls = globals()[value] + if self._cls is None: + return {config_path: self} + return {} + + +class ConstructableRequirementInterface(RequirementInterface): + """Defines a Requirement that can be constructed based on their own + requirements. + + This effectively offers a means for serializing specific python + types, to be reconstructed based on simple configuration data. Each + constructable records a `class` requirement, which indicates the + object that will be constructed. That class may have its own + requirements (which is why validation of a ConstructableRequirement + must happen after the class configuration value has been provided). + These values are then provided to the object's constructor by name + as arguments (as well as the standard `context` and `config_path` + arguments. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_requirement(ClassRequirement("class", "Class of the constructable requirement")) + self._current_class_requirements = set() + + @abstractmethod + def construct(self, context: 'interfaces.context.ContextInterface', config_path: str) -> None: + """Method for constructing within the context any required elements + from subrequirements. + + Args: + context: The context object containing the configuration data for the constructable + config_path: The configuration path for the specific instance of this constructable + """ + + def _validate_class(self, context: 'interfaces.context.ContextInterface', config_path: str) -> None: + """Method to check if the class Requirement is valid and if so populate + the other requirements (but no need to validate, since we're invalid + already) + + Args: + context: The context object containing the configuration data for the constructable + config_path: The configuration path for the specific instance of this constructable + """ + class_req = self.requirements['class'] + subreq_config_path = path_join(config_path, self.name) + if not class_req.unsatisfied(context, subreq_config_path) and isinstance(class_req, ClassRequirement): + # We have a class, and since it's validated we can construct our requirements from it + if issubclass(class_req.cls, ConfigurableInterface): + # In case the class has changed, clear out the old requirements + for old_req in self._current_class_requirements.copy(): + del self._requirements[old_req] + self._current_class_requirements.remove(old_req) + # And add the new ones + for requirement in class_req.cls.get_requirements(): + self._current_class_requirements.add(requirement.name) + self.add_requirement(requirement) + + def _construct_class(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + requirement_dict: Dict[str, object] = None) -> Optional['interfaces.objects.ObjectInterface']: + """Constructs the class, handing args and the subrequirements as + parameters to __init__""" + if self.requirements["class"].unsatisfied(context, config_path): + return None + + if not isinstance(self.requirements["class"], ClassRequirement): + return None + cls = self.requirements["class"].cls + + # These classes all have a name property + # We could subclass this out as a NameableInterface, but it seems a little excessive + # FIXME: We can't test this, because importing the other interfaces causes all kinds of import loops + # if not issubclass(cls, [interfaces.layers.TranslationLayerInterface, + # interfaces.symbols.SymbolTableInterface]): + # return None + + if requirement_dict is None: + requirement_dict = {} + + # Fulfillment must happen, exceptions happening here mean the requirements aren't correct + # and these need to be raised and fixed, rather than caught and ignored + obj = cls(**requirement_dict) + context.config[config_path] = obj.name + return obj + + +class ConfigurableRequirementInterface(RequirementInterface): + """Simple Abstract class to provide build_required_config.""" + + def build_configuration(self, context: 'interfaces.context.ContextInterface', config_path: str, + value: Any) -> HierarchicalDict: + """Proxies to a ConfigurableInterface if necessary.""" + + +class ConfigurableInterface(metaclass = ABCMeta): + """Class to allow objects to have requirements and read configuration data + from the context config tree.""" + + def __init__(self, context: 'interfaces.context.ContextInterface', config_path: str) -> None: + """Basic initializer that allows configurables to access their own + config settings.""" + super().__init__() + self._context = context + self._config_path = config_path + self._config_cache = None # type: Optional[HierarchicalDict] + + @property + def context(self) -> 'interfaces.context.ContextInterface': + """The context object that this configurable belongs to/configuration + is stored in.""" + return self._context + + @property + def config_path(self) -> str: + """The configuration path on which this configurable lives.""" + return self._config_path + + @config_path.setter + def config_path(self, value: str) -> None: + """The configuration path on which this configurable lives.""" + self._config_path = value + self._config_cache = None + + @property + def config(self) -> HierarchicalDict: + """The Hierarchical configuration Dictionary for this Configurable + object.""" + if not hasattr(self, "_config_cache") or self._config_cache is None: + self._config_cache = self._context.config.branch(self._config_path) + return self._config_cache + + def build_configuration(self) -> HierarchicalDict: + """Constructs a HierarchicalDictionary of all the options required to + build this component in the current context. + + Ensures that if the class has been created, it can be recreated + using the configuration built Inheriting classes must override + this to ensure any dependent classes update their configurations + too + """ + result = HierarchicalDict() + for req in self.get_requirements(): + value = self.config.get(req.name, None) + # Do not include the name of constructed classes + if value is not None and not isinstance(req, ConstructableRequirementInterface): + result[req.name] = value + if isinstance(req, ConfigurableRequirementInterface): + if value is not None: + result.splice(req.name, req.build_configuration(self.context, self.config_path, value)) + return result + + @classmethod + def get_requirements(cls) -> List[RequirementInterface]: + """Returns a list of RequirementInterface objects required by this + object.""" + return [] + + @classmethod + def unsatisfied(cls, context: 'interfaces.context.ContextInterface', + config_path: str) -> Dict[str, RequirementInterface]: + """Returns a list of the names of all unsatisfied requirements. + + Since a satisfied set of requirements will return [], it can be used in tests as follows: + + .. code-block:: python + + unmet = configurable.unsatisfied(context, config_path) + if unmet: + raise RuntimeError("Unsatisfied requirements: {}".format(unmet) + """ + result = {} + for requirement in cls.get_requirements(): + if not requirement.optional: + subresult = requirement.unsatisfied(context, config_path) + result.update(subresult) + return result + + @classmethod + def make_subconfig(cls, context: 'interfaces.context.ContextInterface', base_config_path: str, **kwargs) -> str: + """Convenience function to allow constructing a new randomly generated + sub-configuration path, containing each element from kwargs. + + Args: + context: The context in which to store the new configuration + base_config_path: The base configuration path on which to build the new configuration + kwargs: Keyword arguments that are used to populate the new configuration path + + Returns: + str: The newly generated full configuration path + """ + random_config_dict = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) + for _ in range(8)) + new_config_path = path_join(base_config_path, random_config_dict) + # TODO: Check that the new_config_path is empty, although it's not critical if it's not since the values are merged in + + # This should check that each k corresponds to a requirement and each v is of the appropriate type + # This would require knowledge of the new configurable itself to verify, and they should do validation in the + # constructor anyway, however, to prevent bad types getting into the config tree we just verify that v is a simple type + for k, v in kwargs.items(): + if not isinstance(v, (int, str, bool, float, bytes)): + raise TypeError("Config values passed to make_subconfig can only be simple types") + context.config[path_join(new_config_path, k)] = v + + return new_config_path + + +class VersionableInterface: + """A class that allows version checking so that plugins can request specific versions of components they made need + + This currently includes other Plugins and scanners, but may be extended in the future + + All version number should use semantic versioning + """ + _version = (0, 0, 0) # type: Tuple[int, int, int] + + @classproperty + def version(cls) -> Tuple[int, int, int]: + """The version of the current interface (classmethods available on the component). + + It is strongly recommended that Semantic Versioning be used (and the default version verification is defined that way): + + MAJOR version when you make incompatible API changes. + MINOR version when you add functionality in a backwards compatible manner. + PATCH version when you make backwards compatible bug fixes. + """ + return cls._version diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/context.py b/app/parsers/vol_Parser/volatility/framework/interfaces/context.py new file mode 100644 index 00000000..0dff023c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/context.py @@ -0,0 +1,228 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Defines an interface for contexts, which hold the core components that a +plugin will operate upon when running. + +These include a `memory` container which holds a series of forest of +layers, and a `symbol_space` which contains tables of symbols that can +be used to interpret data in a layer. The context also provides some +convenience functions, most notably the object constructor function, +`object`, which will construct a symbol on a layer at a particular +offset. +""" +import copy +from abc import ABCMeta, abstractmethod +from typing import Optional, Union + +from volatility.framework import interfaces + + +class ContextInterface(metaclass = ABCMeta): + """All context-like objects must adhere to the following interface. + + This interface is present to avoid import dependency cycles. + """ + + def __init__(self) -> None: + """Initializes the context with a symbol_space.""" + + # ## Symbol Space Functions + + @property + @abstractmethod + def config(self) -> 'interfaces.configuration.HierarchicalDict': + """Returns the configuration object for this context.""" + + @property + @abstractmethod + def symbol_space(self) -> 'interfaces.symbols.SymbolSpaceInterface': + """Returns the symbol_space for the context. + + This object must support the :class:`~volatility.framework.interfaces.symbols.SymbolSpaceInterface` + """ + + # ## Memory Functions + + @property + @abstractmethod + def layers(self) -> 'interfaces.layers.LayerContainer': + """Returns the memory object for the context.""" + raise NotImplementedError("LayerContainer has not been implemented.") + + def add_layer(self, layer: 'interfaces.layers.DataLayerInterface'): + """Adds a named translation layer to the context memory. + + Args: + layer: Layer object to be added to the context memory + """ + self.layers.add_layer(layer) + + # ## Object Factory Functions + + @abstractmethod + def object(self, + object_type: Union[str, 'interfaces.objects.Template'], + layer_name: str, + offset: int, + native_layer_name: str = None, + **arguments): + """Object factory, takes a context, symbol, offset and optional + layer_name. + + Looks up the layer_name in the context, finds the object template based on the symbol, + and constructs an object using the object template on the layer at the offset. + + Args: + object_type: Either a string name of the type, or a Template of the type to be constructed + layer_name: The name of the layer on which to construct the object + offset: The address within the layer at which to construct the object + native_layer_name: The layer this object references (should it be a pointer or similar) + + Returns: + A fully constructed object + """ + + def clone(self) -> 'ContextInterface': + """Produce a clone of the context (and configuration), allowing + modifications to be made without affecting any mutable objects in the + original. + + Memory constraints may become an issue for this function + depending on how much is actually stored in the context + """ + return copy.deepcopy(self) + + def module(self, + module_name: str, + layer_name: str, + offset: int, + native_layer_name: Optional[str] = None, + size: Optional[int] = None) -> 'ModuleInterface': + """Create a module object. + + A module object is associated with a symbol table, and acts like a context, but offsets locations by a known value + and looks up symbols, by default within the associated symbol table. It can also be sized should that information + be available. + + Args: + module_name: The name of the module + layer_name: The layer the module is associated with (which layer the module lives within) + offset: The initial/base offset of the module (used as the offset for relative symbols) + native_layer_name: The default native_layer_name to use when the module constructs objects + size: The size, in bytes, that the module occupys from offset location within the layer named layer_name + + Returns: + A module object + """ + + +class ModuleInterface(metaclass = ABCMeta): + """Maintains state concerning a particular loaded module in memory. + + This object is OS-independent. + """ + + def __init__(self, + context: ContextInterface, + module_name: str, + layer_name: str, + offset: int, + symbol_table_name: Optional[str] = None, + native_layer_name: Optional[str] = None) -> None: + """Constructs a new os-independent module. + + Args: + context: The context within which this module will exist + module_name: The name of the module + layer_name: The layer within the context in which the module exists + offset: The offset at which the module exists in the layer + symbol_table_name: The name of an associated symbol table + native_layer_name: The default native layer for objects constructed by the module + """ + self._context = context + self._module_name = module_name + self._layer_name = layer_name + self._offset = offset + self._native_layer_name = None + if native_layer_name: + self._native_layer_name = native_layer_name + self.symbol_table_name = symbol_table_name or self._module_name + super().__init__() + + @property + def name(self) -> str: + """The name of the constructed module.""" + return self._module_name + + @property + def offset(self) -> int: + """Returns the offset that the module resides within the layer of + layer_name.""" + return self._offset + + @property + def layer_name(self) -> str: + """Layer name in which the Module resides.""" + return self._layer_name + + @property + def context(self) -> ContextInterface: + """Context that the module uses.""" + return self._context + + @abstractmethod + def object(self, + object_type: str, + offset: int = None, + native_layer_name: Optional[str] = None, + absolute: bool = False, + **kwargs) -> 'interfaces.objects.ObjectInterface': + """Returns an object created using the symbol_table_name and layer_name + of the Module. + + Args: + object_type: The name of object type to construct (using the module's symbol_table) + offset: the offset (unless absolute is set) from the start of the module + native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction) + absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module + + Returns: + The constructed object + """ + + @abstractmethod + def object_from_symbol(self, + symbol_name: str, + native_layer_name: Optional[str] = None, + absolute: bool = False, + **kwargs) -> 'interfaces.objects.ObjectInterface': + """Returns an object created using the symbol_table_name and layer_name + of the Module. + + Args: + symbol_name: The name of a symbol (that must be present in the module's symbol table). The symbol's associated type will be used to construct an object at the symbol's offset. + native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction) + absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module + + Returns: + The constructed object + """ + + def get_type(self, name: str) -> 'interfaces.objects.Template': + """Returns a type from the module.""" + + def get_symbol(self, name: str) -> 'interfaces.symbols.SymbolInterface': + """Returns a symbol from the module.""" + + def get_enumeration(self, name: str) -> 'interfaces.objects.Template': + """Returns an enumeration from the module.""" + + def has_type(self, name: str) -> bool: + """Determines whether a type is present in the module.""" + + def has_symbol(self, name: str) -> bool: + """Determines whether a symbol is present in the module.""" + + def has_enumeration(self, name: str) -> bool: + """Determines whether an enumeration is present in the module.""" diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/layers.py b/app/parsers/vol_Parser/volatility/framework/interfaces/layers.py new file mode 100644 index 00000000..369384b9 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/layers.py @@ -0,0 +1,613 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Defines layers for containing data. + +One layer may combine other layers, map data based on the data itself, +or map a procedure (such as decryption) across another layer of data. +""" +import collections.abc +import functools +import logging +import math +import multiprocessing +import multiprocessing.managers +import threading +import traceback +import types +from abc import ABCMeta, abstractmethod +from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Union + +from volatility.framework import constants, exceptions, interfaces + +vollog = logging.getLogger(__name__) + +ProgressValue = Union['DummyProgress', multiprocessing.managers.ValueProxy] +IteratorValue = Tuple[List[Tuple[str, int, int]], int] + + +class ScannerInterface(interfaces.configuration.VersionableInterface, metaclass = ABCMeta): + """Class for layer scanners that return locations of particular values from + within the data. + + These are designed to be given a chunk of data and return a generator which yields + any found items. They should NOT perform complex/time-consuming tasks, these should + be carried out by the consumer of the generator on the items returned. + + They will be provided all *available* data (therefore not necessarily contiguous) + in ascending offset order, in chunks no larger than chunk_size + overlap where + overlap is the amount of data read twice once at the end of an earlier chunk and + once at the start of the next chunk. + + It should be noted that the scanner can maintain state if necessary. + Scanners should balance the size of chunk based on the amount of time + scanning the chunk will take (ie, do not set an excessively large chunksize + and try not to take a significant amount of time in the __call__ method). + + Scanners must NOT return results found *after* self.chunk_size (ie, entirely contained + within the overlap). It is the responsibility of the scanner not to return such + duplicate results. + + Scanners can mark themselves as thread_safe, if they do not require state + in either their own class or the context. This will allow the scanner to be run + in parallel against multiple blocks. + """ + thread_safe = False + + def __init__(self) -> None: + super().__init__() + self.chunk_size = 0x1000000 # Default to 16Mb chunks + self.overlap = 0x1000 # A page of overlap by default + self._context = None # type: Optional[interfaces.context.ContextInterface] + self._layer_name = None # type: Optional[str] + + @property + def context(self) -> Optional['interfaces.context.ContextInterface']: + return self._context + + @context.setter + def context(self, ctx: 'interfaces.context.ContextInterface') -> None: + """Stores the context locally in case the scanner needs to access the + layer.""" + self._context = ctx + + @property + def layer_name(self) -> Optional[str]: + return self._layer_name + + @layer_name.setter + def layer_name(self, layer_name: str) -> None: + """Stores the layer_name being scanned locally in case the scanner + needs to access the layer.""" + self._layer_name = layer_name + + @abstractmethod + def __call__(self, data: bytes, data_offset: int) -> Iterable[Any]: + """Searches through a chunk of data for a particular value/pattern/etc + Always returns an iterator of the same type of object (need not be a + volatility object) + + data is the chunk of data to search through data_offset is the + offset within the layer that the data being searched starts at + """ + + +class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metaclass = ABCMeta): + """A Layer that directly holds data (and does not translate it). + + This is effectively a leaf node in a layer tree. It directly + accesses a data source and exposes it within volatility. + """ + + _direct_metadata = collections.ChainMap({}, { + 'architecture': 'Unknown', + 'os': 'Unknown' + }) # type: collections.ChainMap + + def __init__(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context, config_path) + self._name = name + if metadata: + self._direct_metadata.update(metadata) + + # Standard attributes + + @property + def name(self) -> str: + """Returns the layer name.""" + return self._name + + @property + @abstractmethod + def maximum_address(self) -> int: + """Returns the maximum valid address of the space.""" + + @property + @abstractmethod + def minimum_address(self) -> int: + """Returns the minimum valid address of the space.""" + + @property + def address_mask(self) -> int: + """Returns a mask which encapsulates all the active bits of an address + for this layer.""" + return (1 << int(math.ceil(math.log2(self.maximum_address)))) - 1 + + @abstractmethod + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns a boolean based on whether the entire chunk of data (from + offset to length) is valid or not. + + Args: + offset: The address to start determining whether bytes are readable/valid + length: The number of bytes from offset of which to test the validity + + Returns: + Whether the bytes are valid and accessible + """ + + @abstractmethod + def read(self, offset: int, length: int, pad: bool = False) -> bytes: + """Reads an offset for length bytes and returns 'bytes' (not 'str') of + length size. + + If there is a fault of any kind (such as a page fault), an exception will be thrown + unless pad is set, in which case the read errors will be replaced by null characters. + + Args: + offset: The offset at which to being reading within the layer + length: The number of bytes to read within the layer + pad: A boolean indicating whether exceptions should be raised or bad bytes replaced with null characters + + Returns: + The bytes read from the layer, starting at offset for length bytes + """ + + @abstractmethod + def write(self, offset: int, data: bytes) -> None: + """Writes a chunk of data at offset. + + Any unavailable sections in the underlying bases will cause an exception to be thrown. + Note: Writes are not guaranteed atomic, therefore some data may have been written, even if an exception is thrown. + """ + + def destroy(self) -> None: + """Causes a DataLayer to close any open handles, etc. + + Systems that make use of Data Layers should call destroy when + they are done with them. This will close all handles, and make + the object unreadable (exceptions will be thrown using a + DataLayer after destruction) + """ + pass + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + """Returns a list of Requirement objects for this type of layer.""" + return super().get_requirements() + + @property + def dependencies(self) -> List[str]: + """A list of other layer names required by this layer. + + Note: + DataLayers must never define other layers + """ + return [] + + # ## General scanning methods + + def scan(self, + context: interfaces.context.ContextInterface, + scanner: ScannerInterface, + progress_callback: constants.ProgressCallback = None, + sections: Iterable[Tuple[int, int]] = None) -> Iterable[Any]: + """Scans a Translation layer by chunk. + + Note: this will skip missing/unmappable chunks of memory + + Args: + context: The context containing the data layer + scanner: The constructed Scanner object to be applied + progress_callback: Method that is called periodically during scanning to update progress + sections: A list of (start, size) tuples defining the portions of the layer to scan + + Returns: + The output iterable from the scanner object having been run against the layer + """ + if progress_callback is not None and not callable(progress_callback): + raise TypeError("Progress_callback is not callable") + + scanner.context = context + scanner.layer_name = self.name + + if sections is None: + sections = [(self.minimum_address, self.maximum_address - self.minimum_address)] + + sections = list(self._coalesce_sections(sections)) + + try: + progress = DummyProgress() # type: ProgressValue + scan_iterator = functools.partial(self._scan_iterator, scanner, sections) + scan_metric = self._scan_metric(scanner, sections) + if not scanner.thread_safe or constants.PARALLELISM == constants.Parallelism.Off: + progress = DummyProgress() + scan_chunk = functools.partial(self._scan_chunk, scanner, progress) + for value in scan_iterator(): + if progress_callback: + progress_callback(scan_metric(progress.value), + "Scanning {} using {}".format(self.name, scanner.__class__.__name__)) + yield from scan_chunk(value) + else: + progress = multiprocessing.Manager().Value("Q", 0) + parallel_module = multiprocessing # type: types.ModuleType + if constants.PARALLELISM == constants.Parallelism.Threading: + progress = DummyProgress() + parallel_module = threading + scan_chunk = functools.partial(self._scan_chunk, scanner, progress) + with parallel_module.Pool() as pool: + result = pool.map_async(scan_chunk, scan_iterator()) + while not result.ready(): + if progress_callback: + # Run the progress_callback + progress_callback(scan_metric(progress.value), + "Scanning {} using {}".format(self.name, scanner.__class__.__name__)) + # Ensures we don't burn CPU cycles going round in a ready waiting loop + # without delaying the user too long between progress updates/results + result.wait(0.1) + for result_value in result.get(): + yield from result_value + except Exception as e: + # We don't care the kind of exception, so catch and report on everything, yielding nothing further + vollog.debug("Scan Failure: {}".format(str(e))) + vollog.log(constants.LOGLEVEL_VVV, + "\n".join(traceback.TracebackException.from_exception(e).format(chain = True))) + + def _coalesce_sections(self, sections: Iterable[Tuple[int, int]]) -> Iterable[Tuple[int, int]]: + """Take a list of (start, length) sections and coalesce any adjacent + sections.""" + result = [] # type: List[Tuple[int, int]] + position = 0 + for (start, length) in sorted(sections): + if result and start <= position: + initial_start, _ = result.pop() + result.append((initial_start, (start + length) - initial_start)) + else: + result.append((start, length)) + position = start + length + + while result and result[0] < (self.minimum_address, 0): + first_start, first_length = result[0] + if first_start + first_length < self.minimum_address: + result = result[1:] + elif first_start < self.minimum_address: + result[0] = (self.minimum_address, (first_start + first_length) - self.minimum_address) + while result and result[-1] > (self.maximum_address, 0): + last_start, last_length = result[-1] + if last_start > self.maximum_address: + result.pop() + elif last_start + last_length > self.maximum_address: + result[1] = (last_start, self.maximum_address - last_start) + return result + + def _scan_iterator(self, scanner: 'ScannerInterface', sections: Iterable[Tuple[int, + int]]) -> Iterable[IteratorValue]: + """Iterator that indicates which blocks in the layer are to be read by + for the scanning. + + Returns a list of blocks (potentially in lower layers) that make + up this chunk contiguously. Chunks can be no bigger than + scanner.chunk_size + scanner.overlap DataLayers by default are + assumed to have no holes + """ + for section_start, section_length in sections: + offset, mapped_offset, length, layer_name = section_start, section_start, section_length, self.name + while length > 0: + chunk_size = min(length, scanner.chunk_size + scanner.overlap) + yield [(layer_name, mapped_offset, chunk_size)], offset + chunk_size + # It we've got more than the scanner's chunk_size, only move up by the chunk_size + if chunk_size > scanner.chunk_size: + chunk_size -= scanner.overlap + length -= chunk_size + mapped_offset += chunk_size + offset += chunk_size + + # We ignore the type due to the iterator_value, actually it only needs to match the output from _scan_iterator + def _scan_chunk(self, scanner: 'ScannerInterface', progress: 'ProgressValue', + iterator_value: IteratorValue) -> List[Any]: + data_to_scan, chunk_end = iterator_value + data = b'' + for layer_name, address, chunk_size in data_to_scan: + try: + data += self.context.layers[layer_name].read(address, chunk_size) + except exceptions.InvalidAddressException: + vollog.debug("Invalid address in layer {} found scanning {} at address {:x}".format( + layer_name, self.name, address)) + + progress.value = chunk_end + return list(scanner(data, chunk_end - len(data))) + + def _scan_metric(self, _scanner: 'ScannerInterface', sections: List[Tuple[int, int]]) -> Callable[[int], float]: + + if not sections: + raise ValueError("Sections have no size, nothing to scan") + last_section, last_length = sections[-1] + min_address, _ = sections[0] + max_address = last_section + last_length + + def _actual_scan_metric(value: int) -> float: + return max(0, ((value - min_address) * 100) / (max_address - min_address)) + + return _actual_scan_metric + + def build_configuration(self) -> interfaces.configuration.HierarchicalDict: + config = super().build_configuration() + + # Translation Layers are constructable, and therefore require a class configuration variable + config["class"] = self.__class__.__module__ + "." + self.__class__.__name__ + return config + + # ## Metadata methods + + @property + def metadata(self) -> Mapping: + """Returns a ReadOnly copy of the metadata published by this layer.""" + maps = [self.context.layers[layer_name].metadata for layer_name in self.dependencies] + return interfaces.objects.ReadOnlyMapping(collections.ChainMap({}, self._direct_metadata, *maps)) + + +class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta): + """Provides a layer that translates or transforms another layer or layers. + + Translation layers always depend on another layer (typically + translating offsets in a virtual offset space into a smaller + physical offset space). + """ + + @abstractmethod + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + """Returns a sorted iterable of (offset, sublength, mapped_offset, mapped_length, layer) + mappings. + + ignore_errors will provide all available maps with gaps, but + their total length may not add up to the requested length This + allows translation layers to provide maps of contiguous regions + in one layer + """ + return [] + + @property + @abstractmethod + def dependencies(self) -> List[str]: + """Returns a list of layer names that this layer translates onto.""" + return [] + + def _decode_data(self, data: bytes, mapped_offset: int, offset: int, output_length: int) -> bytes: + """Decodes any necessary data. Note, additional data may need to be read from the lower layer, such as lookup + tables or similar. The data provided to this layer is purely that data which encompasses the requested data + range. + + Args: + data: The bytes of data necessary for decoding + mapped_offset: The offset in the underlying layer where the data would begin + offset: The offset in the higher-layer where the data would begin + output_length: The expected length of the returned data + + Returns: + The data to be read from the underlying layer.""" + return data + + def _encode_data(self, layer_name: str, mapped_offset: int, offset: int, value: bytes) -> bytes: + """Encodes any necessary data. + + Args: + layer_name: The layer to write data back to + mapped_offset: The offset in the underlying layer where the data would begin + offset: The offset in the higher-layer where the data would begin + value: The new value to encode + + Returns: + The data to be rewritten at mapped_offset.""" + return value + + # ## Read/Write functions for mapped pages + + @functools.lru_cache(maxsize = 512) + def read(self, offset: int, length: int, pad: bool = False) -> bytes: + """Reads an offset for length bytes and returns 'bytes' (not 'str') of + length size.""" + current_offset = offset + output = b'' # type: bytes + for (layer_offset, sublength, mapped_offset, mapped_length, layer) in self.mapping(offset, + length, + ignore_errors = pad): + if not pad and layer_offset > current_offset: + raise exceptions.InvalidAddressException( + self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset)) + elif layer_offset > current_offset: + output += b"\x00" * (layer_offset - current_offset) + current_offset = layer_offset + # The layer_offset can be less than the current_offset in non-linearly mapped layers + # it does not suggest an overlap, but that the data is in an encoded block + if mapped_length > 0: + unprocessed_data = self._context.layers.read(layer, mapped_offset, mapped_length, pad) + processed_data = self._decode_data(unprocessed_data, mapped_offset, layer_offset, sublength) + if len(processed_data) != sublength: + raise ValueError("ProcessedData length does not match expected length of chunk") + output += processed_data + current_offset += sublength + return output + (b"\x00" * (length - len(output))) + + def write(self, offset: int, value: bytes) -> None: + """Writes a value at offset, distributing the writing across any + underlying mapping.""" + current_offset = offset + length = len(value) + for (layer_offset, sublength, mapped_offset, mapped_length, layer) in self.mapping(offset, length): + if layer_offset > current_offset: + raise exceptions.InvalidAddressException( + self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset)) + + value_chunk = value[layer_offset - offset:layer_offset - offset + sublength] + new_data = self._encode_data(layer, mapped_offset, layer_offset, value_chunk) + self._context.layers.write(layer, mapped_offset, new_data) + + current_offset += len(new_data) + + def _scan_iterator(self, + scanner: 'ScannerInterface', + sections: Iterable[Tuple[int, int]], + linear: bool = False) -> Iterable[IteratorValue]: + """Iterator that indicates which blocks in the layer are to be read by + for the scanning. + + Returns a list of blocks (potentially in lower layers) that make + up this chunk contiguously. Chunks can be no bigger than + scanner.chunk_size + scanner.overlap DataLayers by default are + assumed to have no holes + """ + for (section_start, section_length) in sections: + # For each section, split it into scan size chunks + for chunk_start in range(section_start, section_start + section_length, scanner.chunk_size): + # Shorten it, if we're at the end of the section + chunk_length = min(section_start + section_length - chunk_start, scanner.chunk_size + scanner.overlap) + + # Prev offset keeps track of the end of the previous subchunk + prev_offset = chunk_start + output = [] # type: List[Tuple[str, int, int]] + + # We populate the response based on subchunks that may be mapped all over the place + for mapped in self.mapping(chunk_start, chunk_length, ignore_errors = True): + # We don't bother with the other data in case the data's been processed by a lower layer + offset, sublength, mapped_offset, mapped_length, layer_name = mapped + + # We need to check if the offset is next to the end of the last one (contiguous) + if offset != prev_offset: + # Only yield if we've accumulated output + if len(output): + # Yield all the (joined) items so far + # and the ending point of that subchunk (where we'd gotten to previously) + yield output, prev_offset + output = [] + + # Shift the marker up to the end of what we just received and add it to the output + prev_offset = offset + sublength + + if not linear: + output += [(self.name, offset, sublength)] + else: + output += [(layer_name, mapped_offset, mapped_length)] + # If there's still output left, output it + if len(output): + yield output, prev_offset + + +class LayerContainer(collections.abc.Mapping): + """Container for multiple layers of data.""" + + def __init__(self) -> None: + self._layers = {} # type: Dict[str, DataLayerInterface] + + def read(self, layer: str, offset: int, length: int, pad: bool = False) -> bytes: + """Reads from a particular layer at offset for length bytes. + + Returns 'bytes' not 'str' + + Args: + layer: The name of the layer to read from + offset: Where to begin reading within the layer + length: How many bytes to read from the layer + pad: Whether to raise exceptions or return null bytes when errors occur + + Returns: + The result of reading from the requested layer + """ + return self[layer].read(offset, length, pad) + + def __eq__(self, other): + return dict(self) == dict(other) + + def write(self, layer: str, offset: int, data: bytes) -> None: + """Writes to a particular layer at offset for length bytes.""" + self[layer].write(offset, data) + + def add_layer(self, layer: DataLayerInterface) -> None: + """Adds a layer to memory model. + + This will throw an exception if the required dependencies are not met + + Args: + layer: the layer to add to the list of layers (based on layer.name) + """ + if layer.name in self._layers: + raise exceptions.LayerException(layer.name, "Layer already exists: {}".format(layer.name)) + if isinstance(layer, TranslationLayerInterface): + missing_list = [sublayer for sublayer in layer.dependencies if sublayer not in self._layers] + if missing_list: + raise exceptions.LayerException( + layer.name, "Layer {} has unmet dependencies: {}".format(layer.name, ", ".join(missing_list))) + self._layers[layer.name] = layer + + def del_layer(self, name: str) -> None: + """Removes the layer called name. + + This will throw an exception if other layers depend upon this layer + + Args: + name: The name of the layer to delete + """ + for layer in self._layers: + depend_list = [superlayer for superlayer in self._layers if name in self._layers[layer].dependencies] + if depend_list: + raise exceptions.LayerException( + self._layers[layer].name, + "Layer {} is depended upon: {}".format(self._layers[layer].name, ", ".join(depend_list))) + self._layers[name].destroy() + del self._layers[name] + + def free_layer_name(self, prefix: str = "layer") -> str: + """Returns an unused layer name to ensure no collision occurs when + inserting a layer. + + Args: + prefix: A descriptive string with which to prefix the layer name + + Returns: + A string containing a name, prefixed with prefix, not currently in use within the LayerContainer + """ + if prefix not in self: + return prefix + count = 1 + while "{}_{}".format(prefix, count) in self: + count += 1 + return "{}_{}".format(prefix, count) + + def __getitem__(self, name: str) -> DataLayerInterface: + """Returns the layer of specified name.""" + return self._layers[name] + + def __len__(self) -> int: + return len(self._layers) + + def __iter__(self): + return iter(self._layers) + + def check_cycles(self) -> None: + """Runs through the available layers and identifies if there are cycles + in the DAG.""" + # TODO: Is having a cycle check necessary? + raise NotImplementedError("Cycle checking has not yet been implemented") + + +class DummyProgress(object): + """A class to emulate Multiprocessing/threading Value objects.""" + + def __init__(self): + self.value = 0 diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/objects.py b/app/parsers/vol_Parser/volatility/framework/interfaces/objects.py new file mode 100644 index 00000000..88e6c061 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/objects.py @@ -0,0 +1,337 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Objects are the core of volatility, and provide pythonic access to +interpreted values of data from a layer.""" +import abc +import collections +import collections.abc +import logging +from typing import Any, Dict, List, Mapping, Optional + +from volatility.framework import constants, interfaces + +vollog = logging.getLogger(__name__) + + +class ReadOnlyMapping(collections.abc.Mapping): + """A read-only mapping of various values that offer attribute access as + well. + + This ensures that the data stored in the mapping should not be + modified, making an immutable mapping. + """ + + def __init__(self, dictionary: Mapping[str, Any]) -> None: + self._dict = dictionary + + def __getattr__(self, attr: str) -> Any: + """Returns the item as an attribute.""" + if attr == '_dict': + return super().__getattribute__(attr) + if attr in self._dict: + return self._dict[attr] + raise AttributeError("Object has no attribute: {}.{}".format(self.__class__.__name__, attr)) + + def __getitem__(self, name: str) -> Any: + """Returns the item requested.""" + return self._dict[name] + + def __iter__(self): + """Returns an iterator of the dictionary items.""" + return self._dict.__iter__() + + def __len__(self) -> int: + """Returns the length of the internal dictionary.""" + return len(self._dict) + + def __eq__(self, other): + return dict(self) == dict(other) + + +class ObjectInformation(ReadOnlyMapping): + """Contains common information useful/pertinent only to an individual + object (like an instance) + + This typically contains information such as the layer the object belongs to, the offset where it was constructed, + and if it is a subordinate object, its parent. + + This is primarily used to reduce the number of parameters passed to object constructors and keep them all together + in a single place. These values are based on the :class:`ReadOnlyMapping` class, to prevent their modification. + """ + + def __init__(self, + layer_name: str, + offset: int, + member_name: Optional[str] = None, + parent: Optional['ObjectInterface'] = None, + native_layer_name: Optional[str] = None, + size: Optional[int] = None): + """Constructs a container for basic information about an object. + + Args: + layer_name: Layer from which the data for the object will be read + offset: Offset within the layer at which the data for the object will be read + member_name: If the object was accessed as a member of a parent object, this was the name used to access it + parent: If the object was accessed as a member of a parent object, this is the parent object + native_layer_name: If this object references other objects (such as a pointer), what layer those objects live in + size: The size that the whole structure consumes in bytes + """ + super().__init__({ + 'layer_name': layer_name, + 'offset': offset, + 'member_name': member_name, + 'parent': parent, + 'native_layer_name': native_layer_name or layer_name, + 'size': size + }) + + +class ObjectInterface(metaclass = abc.ABCMeta): + """A base object required to be the ancestor of every object used in + volatility.""" + + def __init__(self, context: 'interfaces.context.ContextInterface', type_name: str, object_info: 'ObjectInformation', + **kwargs) -> None: + """Constructs an Object adhering to the ObjectInterface. + + Args: + context: The context associated with the object + type_name: The name of the type structure for the object + object_info: Basic information relevant to the object (layer, offset, member_name, parent, etc) + """ + # Since objects are likely to be instantiated often, + # we're reliant on type_checking to ensure correctness of context, offset and parent + # Everything else may be wrong, but that will get caught later on + + # Add an empty dictionary at the start to allow objects to add their own data to the vol object + # + # NOTE: + # This allows objects to MASSIVELY MESS with their own internal representation!!! + # Changes to offset, type_name, etc should NEVER be done + # + + # Normalize offsets + mask = context.layers[object_info.layer_name].address_mask + normalized_offset = object_info.offset & mask + + self._vol = collections.ChainMap({}, object_info, {'type_name': type_name, 'offset': normalized_offset}, kwargs) + self._context = context + + def __getattr__(self, attr: str) -> Any: + """Method for ensuring volatility members can be returned.""" + raise AttributeError + + @property + def vol(self) -> ReadOnlyMapping: + """Returns the volatility specific object information.""" + # Wrap the outgoing vol in a read-only proxy + return ReadOnlyMapping(self._vol) + + @abc.abstractmethod + def write(self, value: Any): + """Writes the new value into the format at the offset the object + currently resides at.""" + + def get_symbol_table_name(self) -> str: + """Returns the symbol table name for this particular object. + + Raises: + ValueError: If the object's symbol does not contain an explicit table + KeyError: If the table_name is not valid within the object's context + """ + if constants.BANG not in self.vol.type_name: + raise ValueError("Unable to determine table for symbol: {}".format(self.vol.type_name)) + table_name = self.vol.type_name[:self.vol.type_name.index(constants.BANG)] + if table_name not in self._context.symbol_space: + raise KeyError("Symbol table not found in context's symbol_space for symbol: {}".format(self.vol.type_name)) + return table_name + + def cast(self, new_type_name: str, **additional) -> 'ObjectInterface': + """Returns a new object at the offset and from the layer that the + current object inhabits. + + .. note:: If new type name does not include a symbol table, the + symbol table for the current object is used + """ + # TODO: Carefully consider the implications of casting and how it should work + if constants.BANG not in new_type_name: + symbol_table = self.vol['type_name'].split(constants.BANG)[0] + new_type_name = symbol_table + constants.BANG + new_type_name + object_template = self._context.symbol_space.get_type(new_type_name) + object_template = object_template.clone() + object_template.update_vol(**additional) + object_info = ObjectInformation(layer_name = self.vol.layer_name, + offset = self.vol.offset, + member_name = self.vol.member_name, + parent = self.vol.parent, + native_layer_name = self.vol.native_layer_name, + size = object_template.size) + return object_template(context = self._context, object_info = object_info) + + def has_member(self, member_name: str) -> bool: + """Returns whether the object would contain a member called + member_name. + + Args: + member_name: Name to test whether a member exists within the type structure + """ + return False + + def has_valid_member(self, member_name: str) -> bool: + """Returns whether the dereferenced type has a valid member. + + Args: + member_name: Name of the member to test access to determine if the member is valid or not + """ + if self.has_member(member_name): + # noinspection PyBroadException + try: + _ = getattr(self, member_name) + return True + except Exception: + pass + return False + + def has_valid_members(self, member_names: List[str]) -> bool: + """Returns whether the object has all of the members listed in member_names + + Args: + member_names: List of names to test as to members with those names validity + """ + return all([self.has_valid_member(member_name) for member_name in member_names]) + + class VolTemplateProxy(metaclass = abc.ABCMeta): + """A container for proxied methods that the ObjectTemplate of this + object will call. This is primarily to keep methods together for easy + organization/management, there is no significant need for it to be a + separate class. + + The methods of this class *must* be class methods rather than + standard methods, to allow for code reuse. Each method also + takes a template since the templates may contain the necessary + data about the yet-to-be-constructed object. It allows objects + to control how their templates respond without needing to write + new templates for each and every potental object type. + """ + _methods = [] # type: List[str] + + @classmethod + @abc.abstractmethod + def size(cls, template: 'Template') -> int: + """Returns the size of the template object.""" + + @classmethod + @abc.abstractmethod + def children(cls, template: 'Template') -> List['Template']: + """Returns the children of the template.""" + return [] + + @classmethod + @abc.abstractmethod + def replace_child(cls, template: 'Template', old_child: 'Template', new_child: 'Template') -> None: + """Substitutes the old_child for the new_child.""" + raise KeyError("Template does not contain any children to replace: {}".format(template.vol.type_name)) + + @classmethod + @abc.abstractmethod + def relative_child_offset(cls, template: 'Template', child: str) -> int: + """Returns the relative offset from the head of the parent data to + the child member.""" + raise KeyError("Template does not contain any children: {}".format(template.vol.type_name)) + + @classmethod + @abc.abstractmethod + def has_member(cls, template: 'Template', member_name: str) -> bool: + """Returns whether the object would contain a member called + member_name.""" + return False + + +class Template: + """Class for all Factories that take offsets, and data layers and produce + objects. + + This is effectively a class for currying object calls. It creates a callable that can be called with the following + parameters: + + Args: + context: The context containing the memory layers and symbols required to construct the object + object_info: Basic information about the object, see the ObjectInformation class for more information + + Returns: + The constructed object + + The keyword arguments handed to the constructor, along with the type_name are stored for later retrieval. + These will be access as `object.vol.` or `template.vol.` for each object and should contain + as least the basic information that each object will require before it is instantiated (so `offset` and `parent` + are explicitly not recorded here). This dictionary can be updated after construction, but any changes made + after that point will *not* be cloned. This is so that templates such as those for string objects may + contain different length limits, without affecting all other strings using the same template from a SymbolTable, + constructed at resolution time and then cached. + """ + + def __init__(self, type_name: str, **arguments) -> None: + """Stores the keyword arguments for later object creation.""" + # Allow the updating of template arguments whilst still in template form + super().__init__() + empty_dict = {} # type: Dict[str, Any] + self._vol = collections.ChainMap(empty_dict, arguments, {'type_name': type_name}) + + @property + def vol(self) -> ReadOnlyMapping: + """Returns a volatility information object, much like the + :class:`~volatility.framework.interfaces.objects.ObjectInformation` + provides.""" + return ReadOnlyMapping(self._vol) + + @property + def children(self) -> List['Template']: + """The children of this template (such as member types, sub-types and + base-types where they are relevant). + + Used to traverse the template tree. + """ + return [] + + @property + @abc.abstractmethod + def size(self) -> int: + """Returns the size of the template.""" + + @abc.abstractmethod + def relative_child_offset(self, child: str) -> int: + """Returns the relative offset of the `child` member from its parent + offset.""" + + @abc.abstractmethod + def replace_child(self, old_child: 'Template', new_child: 'Template') -> None: + """Replaces `old_child` with `new_child` in the list of children.""" + + @abc.abstractmethod + def has_member(self, member_name: str) -> bool: + """Returns whether the object would contain a member called + `member_name`""" + + def clone(self) -> 'Template': + """Returns a copy of the original Template as constructed (without + `update_vol` additions having been made)""" + clone = self.__class__(**self._vol.parents.new_child()) + return clone + + def update_vol(self, **new_arguments) -> None: + """Updates the keyword arguments with values that will **not** be + carried across to clones.""" + self._vol.update(new_arguments) + + def __getattr__(self, attr: str) -> Any: + """Exposes any other values stored in ._vol as attributes (for example, + enumeration choices)""" + if attr != '_vol': + if attr in self._vol: + return self._vol[attr] + raise AttributeError("{} object has no attribute {}".format(self.__class__.__name__, attr)) + + def __call__(self, context: 'interfaces.context.ContextInterface', + object_info: ObjectInformation) -> ObjectInterface: + """Constructs the object.""" diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/plugins.py b/app/parsers/vol_Parser/volatility/framework/interfaces/plugins.py new file mode 100644 index 00000000..15f45e7a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/plugins.py @@ -0,0 +1,151 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Plugins are the `functions` of the volatility framework. + +They are called and carry out some algorithms on data stored in layers +using objects constructed from symbols. +""" + +# Configuration interfaces must be imported separately, since we're part of interfaces and can't import ourselves +import logging +import os +from abc import ABCMeta, abstractmethod +from typing import List, Tuple, Type, IO + +from volatility import framework +from volatility.framework import exceptions, constants, interfaces + +vollog = logging.getLogger(__name__) + + +class FileHandlerInterface(IO[bytes]): + """Class for storing Files in the plugin as a means to output a file when necessary. + + This can be used as ContextManager that will close/produce the file automatically when exiting the context block + """ + + def __init__(self, filename: str) -> None: + """Creates a FileHandler + + Args: + filename: The requested name of the filename for the data + """ + self._preferred_filename = None + self.preferred_filename = filename + super().__init__() + + @property + def preferred_filename(self): + """The preferred filename to save the data to. + Until this file has been written, this value may not be the final filename the data is written to. + """ + return self._preferred_filename + + @preferred_filename.setter + def preferred_filename(self, filename): + """Sets the preferred filename""" + if self.closed: + raise IOError("FileHandler name cannot be changed once closed") + if not isinstance(filename, str): + raise TypeError("FileHandler preferred filenames must be strings") + if os.path.sep in filename: + raise ValueError("FileHandler filenames cannot contain path separators") + self._preferred_filename = filename + + @abstractmethod + def close(self): + """Method that commits the file and fixes the final filename for use""" + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None and exc_value is None and traceback is None: + self.close() + else: + vollog.warning("File {} could not be written: {}".format(self._preferred_filename, str(exc_value))) + self.close() + + +# +# Plugins +# - Take in relevant number of TranslationLayers (of specified type) +# - Outputs TreeGrid +# +# Should the plugin handle constructing the translation layers from the filenames or should the library have routines for it? +# Outwardly, the user specifies an OS, version, architecture triple and images. +# The UI checks the plugin against the OS/Version/Arch triple +# The UI constructs the TranslationLayers and names them according to the plugin's input layer names +# The UI constructs the appropriate default symbol spaces +# The plugin accepts the context and modifies as necessary +# The plugin runs and produces a TreeGrid output + + +class PluginInterface(interfaces.configuration.ConfigurableInterface, + interfaces.configuration.VersionableInterface, + metaclass = ABCMeta): + """Class that defines the basic interface that all Plugins must maintain. + + The constructor must only take a `context` and `config_path`, so + that plugins can be launched automatically. As such all + configuration information must be provided through the requirements + and configuration information in the context it is passed. + """ + + # Be careful with inheritance around this + _required_framework_version = (1, 0, 0) # type: Tuple[int, int, int] + """The _version variable is a quick way for plugins to define their current interface, it should follow SemVer rules""" + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + progress_callback: constants.ProgressCallback = None) -> None: + """ + + Args: + context: The context that the plugin will operate within + config_path: The path to configuration data within the context configuration data + progress_callback: A callable that can provide feedback at progress points + """ + super().__init__(context, config_path) + self._progress_callback = progress_callback or (lambda f, s: None) + # Plugins self validate on construction, it makes it more difficult to work with them, but then + # the validation doesn't need to be repeated over and over again by externals + if self.unsatisfied(context, config_path): + vollog.warning("Plugin failed validation") + raise exceptions.PluginRequirementException("The plugin configuration failed to validate") + # Populate any optional defaults + for requirement in self.get_requirements(): + if requirement.name not in self.config: + self.config[requirement.name] = requirement.default + + self._file_handler = FileHandlerInterface # type: Type[FileHandlerInterface] + + framework.require_interface_version(*self._required_framework_version) + + @property + def open(self): + """Returns a context manager and thus can be called like open""" + return self._file_handler + + def set_open_method(self, handler: Type[FileHandlerInterface]) -> None: + """Sets the file handler to be used by this plugin.""" + if not issubclass(handler, FileHandlerInterface): + raise ValueError("FileHandler must be a subclass of FileHandlerInterface") + self._file_handler = handler + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + """Returns a list of Requirement objects for this plugin.""" + return super().get_requirements() + + @abstractmethod + def run(self) -> interfaces.renderers.TreeGrid: + """Executes the functionality of the code. + + .. note:: This method expects `self.validate` to have been called to ensure all necessary options have been provided + + Returns: + A TreeGrid object that can then be passed to a Renderer. + """ diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/renderers.py b/app/parsers/vol_Parser/volatility/framework/interfaces/renderers.py new file mode 100644 index 00000000..54b9f922 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/renderers.py @@ -0,0 +1,220 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All plugins output a TreeGrid object which must then be rendered (eithe by a +GUI, or as text output, html output or in some other form. + +This module defines both the output format (:class:`TreeGrid`) and the +renderer interface which can interact with a TreeGrid to produce +suitable output. +""" + +import datetime +from abc import abstractmethod, ABCMeta +from collections import abc +from typing import Any, Callable, ClassVar, Generator, List, NamedTuple, Optional, TypeVar, Type, Tuple, Union + +Column = NamedTuple('Column', [('name', str), ('type', Any)]) + +RenderOption = Any + + +class Renderer(metaclass = ABCMeta): + """Class that defines the interface that all output renderers must + support.""" + + def __init__(self, options: Optional[List[RenderOption]] = None) -> None: + """Accepts an options object to configure the renderers.""" + # FIXME: Once the config option objects are in place, put the _type_check in place + + @abstractmethod + def get_render_options(self) -> List[RenderOption]: + """Returns a list of rendering options.""" + + @abstractmethod + def render(self, grid: 'TreeGrid') -> None: + """Takes a grid object and renders it based on the object's + preferences.""" + + +class ColumnSortKey(metaclass = ABCMeta): + ascending = True # type: bool + + @abstractmethod + def __call__(self, values: List[Any]) -> Any: + """The key function passed as a sort key to the TreeGrid's visit + function.""" + + +class TreeNode(abc.Sequence, metaclass = ABCMeta): + + def __init__(self, path, treegrid, parent, values): + """Initializes the TreeNode.""" + + @property + @abstractmethod + def values(self) -> List['BaseTypes']: + """Returns the list of values from the particular node, based on column + index.""" + + @property + @abstractmethod + def path(self) -> str: + """Returns a path identifying string. + + This should be seen as opaque by external classes, Parsing of + path locations based on this string are not guaranteed to remain + stable. + """ + + @property + @abstractmethod + def parent(self) -> Optional['TreeNode']: + """Returns the parent node of this node or None.""" + + @property + @abstractmethod + def path_depth(self) -> int: + """Return the path depth of the current node.""" + + @abstractmethod + def path_changed(self, path: str, added: bool = False) -> None: + """Updates the path based on the addition or removal of a node higher + up in the tree. + + This should only be called by the containing TreeGrid and + expects to only be called for affected nodes. + """ + + +class BaseAbsentValue(object): + """Class that represents values which are not present for some reason.""" + + +class Disassembly(object): + """A class to indicate that the bytes provided should be disassembled + (based on the architecture)""" + possible_architectures = ['intel', 'intel64', 'arm', 'arm64'] + + def __init__(self, data: bytes, offset: int = 0, architecture: str = 'intel64') -> None: + self.data = data + self.architecture = None + if architecture in self.possible_architectures: + self.architecture = architecture + if not isinstance(offset, int): + raise TypeError("Offset must be an integer type") + self.offset = offset + + +# We don't class these off a shared base, because the BaseTypes must only +# contain the types that the validator will accept (which would not include the base) + +_Type = TypeVar("_Type") +BaseTypes = Union[Type[int], Type[str], Type[float], Type[bytes], Type[datetime.datetime], Type[BaseAbsentValue], + Type[Disassembly]] +ColumnsType = List[Tuple[str, BaseTypes]] +VisitorSignature = Callable[[TreeNode, _Type], _Type] + + +class TreeGrid(object, metaclass = ABCMeta): + """Class providing the interface for a TreeGrid (which contains TreeNodes) + + The structure of a TreeGrid is designed to maintain the structure of the tree in a single object. + For this reason each TreeNode does not hold its children, they are managed by the top level object. + This leaves the Nodes as simple data carries and prevents them being used to manipulate the tree as a whole. + This is a data structure, and is not expected to be modified much once created. + + Carrying the children under the parent makes recursion easier, but then every node is its own little tree + and must have all the supporting tree functions. It also allows for a node to be present in several different trees, + and to create cycles. + """ + + base_types = (int, str, float, bytes, datetime.datetime, Disassembly) # type: ClassVar[Tuple] + + def __init__(self, columns: ColumnsType, generator: Generator) -> None: + """Constructs a TreeGrid object using a specific set of columns. + + The TreeGrid itself is a root element, that can have children but no values. + The TreeGrid does *not* contain any information about formatting, + these are up to the renderers and plugins. + + Args: + columns: A list of column tuples made up of (name, type). + generator: An iterable containing row for a tree grid, each row contains a indent level followed by the values for each column in order. + """ + + @staticmethod + @abstractmethod + def sanitize_name(text: str) -> str: + """Method used to sanitize column names for TreeNodes.""" + + @abstractmethod + def populate(self, + function: VisitorSignature = None, + initial_accumulator: Any = None, + fail_on_errors: bool = True) -> Optional[Exception]: + """Populates the tree by consuming the TreeGrid's construction + generator Func is called on every node, so can be used to create output + on demand. + + This is equivalent to a one-time visit. + """ + + @property + @abstractmethod + def populated(self) -> bool: + """Indicates that population has completed and the tree may now be + manipulated separately.""" + + @property + @abstractmethod + def columns(self) -> List[Column]: + """Returns the available columns and their ordering and types.""" + + @abstractmethod + def children(self, node: TreeNode) -> List[TreeNode]: + """Returns the subnodes of a particular node in order.""" + + @abstractmethod + def values(self, node: TreeNode) -> Tuple[BaseTypes, ...]: + """Returns the values for a particular node. + + The values returned are mutable, + """ + + @abstractmethod + def is_ancestor(self, node: TreeNode, descendant: TreeNode) -> bool: + """Returns true if descendent is a child, grandchild, etc of node.""" + + @abstractmethod + def max_depth(self) -> int: + """Returns the maximum depth of the tree.""" + + @staticmethod + def path_depth(node: TreeNode) -> int: + """Returns the path depth of a particular node.""" + return node.path_depth + + @abstractmethod + def visit(self, + node: Optional[TreeNode], + function: VisitorSignature, + initial_accumulator: _Type, + sort_key: ColumnSortKey = None) -> None: + """Visits all the nodes in a tree, calling function on each one. + + function should have the signature function(node, accumulator) and return new_accumulator + If accumulators are not needed, the function must still accept a second parameter. + + The order of that the nodes are visited is always depth first, however, the order children are traversed can + be set based on a sort_key function which should accept a node's values and return something that can be + sorted to receive the desired order (similar to the sort/sorted key). + + If node is None, then the root node is used. + + Args: + node: The initial node to be visited + function: The visitor to apply to the nodes under the initial node + initial_accumulator: An accumulator that allows data to be transfered between one visitor call to the next + sort_key: Information about the sort order of columns in order to determine the ordering of results + """ diff --git a/app/parsers/vol_Parser/volatility/framework/interfaces/symbols.py b/app/parsers/vol_Parser/volatility/framework/interfaces/symbols.py new file mode 100644 index 00000000..77d00337 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/interfaces/symbols.py @@ -0,0 +1,335 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Symbols provide structural information about a set of bytes.""" +import bisect +import collections.abc +from abc import abstractmethod, ABC +from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Mapping + +from volatility.framework import constants, exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import configuration, objects +from volatility.framework.interfaces.configuration import RequirementInterface + + +class SymbolInterface: + """Contains information about a named location in a program's memory.""" + + def __init__(self, + name: str, + address: int, + type: Optional[objects.Template] = None, + constant_data: Optional[bytes] = None) -> None: + """ + + Args: + name: Name of the symbol + address: Numeric address value of the symbol + type: Optional type structure information associated with the symbol + constant_data: Potential constant data the symbol points at + """ + self._name = name + if constants.BANG in self._name: + raise ValueError("Symbol names cannot contain the symbol differentiator ({})".format(constants.BANG)) + + # Scope can be added at a later date + self._location = None + self._address = address + self._type = type + self._constant_data = constant_data + + @property + def name(self) -> str: + """Returns the name of the symbol.""" + return self._name + + @property + def type_name(self) -> Optional[str]: + """Returns the name of the type that the symbol represents.""" + # Objects and ObjectTemplates should *always* get a type_name when they're constructed, so allow the IndexError + if self.type is None: + return None + return self.type.vol['type_name'] + + @property + def type(self) -> Optional[objects.Template]: + """Returns the type that the symbol represents.""" + return self._type + + @property + def address(self) -> int: + """Returns the relative address of the symbol within the compilation + unit.""" + return self._address + + @property + def constant_data(self) -> Optional[bytes]: + """Returns any constant data associated with the symbol.""" + return self._constant_data + + +class BaseSymbolTableInterface: + """The base interface, inherited by both NativeTables and SymbolTables. + + native_types is a NativeTableInterface used for native types for the particular loaded symbol table + table_mapping allows tables referenced by symbols to be remapped to a different table name if necessary + + Note: table_mapping is a rarely used feature (since symbol tables are typically self-contained) + """ + + def __init__(self, + name: str, + native_types: 'NativeTableInterface', + table_mapping: Optional[Dict[str, str]] = None, + class_types: Optional[Mapping[str, Type[objects.ObjectInterface]]] = None) -> None: + """ + + Args: + name: Name of the symbol table + native_types: The native symbol table used to resolve any base/native types + table_mapping: A dictionary mapping names of tables (which when present within the table will be changed to the mapped table) + class_types: A dictionary of types and classes that should be instantiated instead of Struct to construct them + """ + self.name = name + if table_mapping is None: + table_mapping = {} + self.table_mapping = table_mapping + self._native_types = native_types + self._sort_symbols = [] # type: List[Tuple[int, str]] + + # Set any provisioned class_types + if class_types: + for class_type in class_types: + self.set_type_class(class_type, class_types[class_type]) + + # ## Required Symbol functions + + def get_symbol(self, name: str) -> SymbolInterface: + """Resolves a symbol name into a symbol object. + + If the symbol isn't found, it raises a SymbolError exception + """ + raise NotImplementedError("Abstract property get_symbol not implemented by subclass.") + + @property + def symbols(self) -> Iterable[str]: + """Returns an iterator of the Symbol names.""" + raise NotImplementedError("Abstract property symbols not implemented by subclass.") + + # ## Required Type functions + + @property + def types(self) -> Iterable[str]: + """Returns an iterator of the Symbol type names.""" + raise NotImplementedError("Abstract property types not implemented by subclass.") + + def get_type(self, name: str) -> objects.Template: + """Resolves a symbol name into an object template. + + If the symbol isn't found it raises a SymbolError exception + """ + raise NotImplementedError("Abstract method get_type not implemented by subclass.") + + # ## Required Symbol enumeration functions + + @property + def enumerations(self) -> Iterable[Any]: + """Returns an iterator of the Enumeration names.""" + raise NotImplementedError("Abstract property enumerations not implemented by subclass.") + + # ## Native Type Handler + + @property + def natives(self) -> 'NativeTableInterface': + """Returns None or a NativeTable for handling space specific native + types.""" + return self._native_types + + @natives.setter + def natives(self, value: 'NativeTableInterface') -> None: + """Checks the natives value and then applies it internally. + + WARNING: This allows changing the underlying size of all the other types referenced in the SymbolTable + """ + self._native_types = value + + # ## Functions for overriding classes + + def set_type_class(self, name: str, clazz: Type[objects.ObjectInterface]) -> None: + """Overrides the object class for a specific Symbol type. + + Name *must* be present in self.types + + Args: + name: The name of the type to override the class for + clazz: The actual class to override for the provided type name + """ + raise NotImplementedError("Abstract method set_type_class not implemented yet.") + + def get_type_class(self, name: str) -> Type[objects.ObjectInterface]: + """Returns the class associated with a Symbol type.""" + raise NotImplementedError("Abstract method get_type_class not implemented yet.") + + def del_type_class(self, name: str) -> None: + """Removes the associated class override for a specific Symbol type.""" + raise NotImplementedError("Abstract method del_type_class not implemented yet.") + + # ## Convenience functions for location symbols + + def get_symbol_type(self, name: str) -> Optional[objects.Template]: + """Resolves a symbol name into a symbol and then resolves the symbol's + type.""" + type_name = self.get_symbol(name).type_name + if type_name is None: + return None + return self.get_type(type_name) + + def get_symbols_by_type(self, type_name: str) -> Iterable[str]: + """Returns the name of all symbols in this table that have type + matching type_name.""" + for symbol_name in self.symbols: + # This allows for searching with and without the table name (in case multiple tables contain + # the same symbol name and we've not specifically been told which one) + symbol = self.get_symbol(symbol_name) + if symbol.type_name is not None and (symbol.type_name == type_name or + (symbol.type_name.endswith(constants.BANG + type_name))): + yield symbol.name + + def get_symbols_by_location(self, offset: int, size: int = 0) -> Iterable[str]: + """Returns the name of all symbols in this table that live at a + particular offset.""" + if size < 0: + raise ValueError("Size must be strictly non-negative") + if not self._sort_symbols: + self._sort_symbols = sorted([(self.get_symbol(sn).address, sn) for sn in self.symbols]) + sort_symbols = self._sort_symbols + result = bisect.bisect_left(sort_symbols, (offset, "")) + while result < len(sort_symbols) and \ + (sort_symbols[result][0] >= offset and sort_symbols[result][0] <= offset + size): + yield sort_symbols[result][1] + result += 1 + + def clear_symbol_cache(self) -> None: + """Clears the symbol cache of this symbol table.""" + pass + + +class SymbolSpaceInterface(collections.abc.Mapping): + """An interface for the container that holds all the symbol-containing + tables for use within a context.""" + + def free_table_name(self, prefix: str = "layer") -> str: + """Returns an unused table name to ensure no collision occurs when + inserting a symbol table.""" + + @abstractmethod + def clear_symbol_cache(self, table_name: str) -> None: + """Clears the symbol cache for the specified table name. If no table + name is specified, the caches of all symbol tables are cleared.""" + + @abstractmethod + def get_symbols_by_type(self, type_name: str) -> Iterable[str]: + """Returns all symbols based on the type of the symbol.""" + + @abstractmethod + def get_symbols_by_location(self, offset: int, size: int = 0, table_name: Optional[str] = None) -> Iterable[str]: + """Returns all symbols that exist at a specific relative address.""" + + @abstractmethod + def get_type(self, type_name: str) -> objects.Template: + """Look-up a type name across all the contained symbol tables.""" + + @abstractmethod + def get_symbol(self, symbol_name: str) -> SymbolInterface: + """Look-up a symbol name across all the contained symbol tables.""" + + @abstractmethod + def get_enumeration(self, enum_name: str) -> objects.Template: + """Look-up an enumeration across all the contained symbol tables.""" + + @abstractmethod + def has_type(self, name: str) -> bool: + """Determines whether a type exists in the contained symbol tables.""" + + @abstractmethod + def has_symbol(self, name: str) -> bool: + """Determines whether a symbol exists in the contained symbol + tables.""" + + @abstractmethod + def has_enumeration(self, name: str) -> bool: + """Determines whether an enumeration choice exists in the contained + symbol tables.""" + + @abstractmethod + def append(self, value: BaseSymbolTableInterface) -> None: + """Adds a symbol_list to the end of the space.""" + + +class SymbolTableInterface(BaseSymbolTableInterface, configuration.ConfigurableInterface, ABC): + """Handles a table of symbols.""" + + # FIXME: native_types and table_mapping aren't recorded in the configuration + def __init__(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + name: str, + native_types: 'NativeTableInterface', + table_mapping: Optional[Dict[str, str]] = None, + class_types: Optional[Mapping[str, Type[objects.ObjectInterface]]] = None) -> None: + """Instantiates an SymbolTable based on an IntermediateSymbolFormat JSON file. This is validated against the + appropriate schema. + + Args: + context: The volatility context for the symbol table + config_path: The configuration path for the symbol table + name: The name for the symbol table (this is used in symbols e.g. table!symbol ) + isf_url: The URL pointing to the ISF file location + native_types: The NativeSymbolTable that contains the native types for this symbol table + table_mapping: A dictionary linking names referenced in the file with symbol tables in the context + class_types: A dictionary of type names and classes that override StructType when they are instantiated + """ + configuration.ConfigurableInterface.__init__(self, context, config_path) + BaseSymbolTableInterface.__init__(self, name, native_types, table_mapping, class_types = class_types) + + def build_configuration(self) -> 'configuration.HierarchicalDict': + config = super().build_configuration() + + # Symbol Tables are constructable, and therefore require a class configuration variable + config["class"] = self.__class__.__module__ + "." + self.__class__.__name__ + return config + + @classmethod + def get_requirements(cls) -> List[RequirementInterface]: + return super().get_requirements() + [ + requirements.IntRequirement(name = 'symbol_shift', description = 'Symbol Shift', optional = False), + requirements.IntRequirement( + name = 'symbol_mask', description = 'Address mask for symbols', optional = True, default = 0), + ] + + +class NativeTableInterface(BaseSymbolTableInterface): + """Class to distinguish NativeSymbolLists from other symbol lists.""" + + def get_symbol(self, name: str) -> SymbolInterface: + raise exceptions.SymbolError(name, self.name, "NativeTables never hold symbols") + + @property + def symbols(self) -> Iterable[str]: + return [] + + def get_enumeration(self, name: str) -> objects.Template: + raise exceptions.SymbolError(name, self.name, "NativeTables never hold enumerations") + + @property + def enumerations(self) -> Iterable[str]: + return [] + + +class MetadataInterface(object): + """Interface for accessing metadata stored within a symbol table.""" + + def __init__(self, json_data: Dict) -> None: + """Constructor that accepts json_data.""" + self._json_data = json_data diff --git a/app/parsers/vol_Parser/volatility/framework/layers/__init__.py b/app/parsers/vol_Parser/volatility/framework/layers/__init__.py new file mode 100644 index 00000000..e68d200f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/__init__.py @@ -0,0 +1,3 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# diff --git a/app/parsers/vol_Parser/volatility/framework/layers/codecs/__init__.py b/app/parsers/vol_Parser/volatility/framework/layers/codecs/__init__.py new file mode 100644 index 00000000..550161e6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/codecs/__init__.py @@ -0,0 +1,4 @@ +"""Codecs used for encoding or decoding data should live here + + +""" diff --git a/app/parsers/vol_Parser/volatility/framework/layers/crash.py b/app/parsers/vol_Parser/volatility/framework/layers/crash.py new file mode 100644 index 00000000..9f239fd1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/crash.py @@ -0,0 +1,202 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import struct +from typing import Tuple, Optional + +from volatility.framework import constants, exceptions, interfaces +from volatility.framework.layers import segmented +from volatility.framework.symbols import intermed + +vollog = logging.getLogger(__name__) + + +class WindowsCrashDumpFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying Crash file format.""" + + +class WindowsCrashDump32Layer(segmented.SegmentedLayer): + """A Windows crash format TranslationLayer. + + This TranslationLayer supports Microsoft complete memory dump files. + It currently does not support kernel or small memory dump files. + """ + + provides = {"type": "physical"} + + SIGNATURE = 0x45474150 + VALIDDUMP = 0x504d5544 + + crashdump_json = 'crash' + supported_dumptypes = [0x01] + dump_header_name = '_DUMP_HEADER' + + _magic_struct = struct.Struct(' None: + + # Construct these so we can use self.config + self._context = context + self._config_path = config_path + self._page_size = 0x1000 + self._base_layer = self.config["base_layer"] + + # Create a custom SymbolSpace + self._crash_table_name = intermed.IntermediateSymbolTable.create(context, self._config_path, 'windows', + self.crashdump_json) + # Check Header + hdr_layer = self._context.layers[self._base_layer] + hdr_offset = 0 + self.check_header(hdr_layer, hdr_offset) + + # Need to create a header object + header = self.context.object(self._crash_table_name + constants.BANG + self.dump_header_name, + offset = hdr_offset, + layer_name = self._base_layer) + + # Extract the DTB + self.dtb = int(header.DirectoryTableBase) + + self.dump_type = int(header.DumpType) + + # Verify that it is a supported format + if header.DumpType not in self.supported_dumptypes: + vollog.log(constants.LOGLEVEL_VVVV, "unsupported dump format 0x{:x}".format(header.DumpType)) + raise WindowsCrashDumpFormatException(name, "unsupported dump format 0x{:x}".format(header.DumpType)) + + super().__init__(context, config_path, name) + + def _load_segments(self) -> None: + """Loads up the segments from the meta_layer.""" + header = self.context.object(self._crash_table_name + constants.BANG + self.dump_header_name, + offset = 0, + layer_name = self._base_layer) + + segments = [] + + offset = self.headerpages + header.PhysicalMemoryBlockBuffer.Run.count = header.PhysicalMemoryBlockBuffer.NumberOfRuns + for x in header.PhysicalMemoryBlockBuffer.Run: + segments.append((x.BasePage * 0x1000, offset * 0x1000, x.PageCount * 0x1000, x.PageCount * 0x1000)) + # print("Segments {:x} {:x} {:x}".format(x.BasePage * 0x1000, + # offset * 0x1000, + # x.PageCount * 0x1000)) + offset += x.PageCount + + if len(segments) == 0: + raise WindowsCrashDumpFormatException(self.name, "No Crash segments defined in {}".format(self._base_layer)) + + self._segments = segments + + @classmethod + def check_header(cls, base_layer: interfaces.layers.DataLayerInterface, offset: int = 0) -> Tuple[int, int]: + # Verify the Window's crash dump file magic + + try: + header_data = base_layer.read(offset, cls._magic_struct.size) + except exceptions.InvalidAddressException: + raise WindowsCrashDumpFormatException(base_layer.name, + "Crashdump header not found at offset {}".format(offset)) + (signature, validdump) = cls._magic_struct.unpack(header_data) + + if signature != cls.SIGNATURE: + raise WindowsCrashDumpFormatException( + base_layer.name, "Bad signature 0x{:x} at file offset 0x{:x}".format(signature, offset)) + if validdump != cls.VALIDDUMP: + raise WindowsCrashDumpFormatException(base_layer.name, + "Invalid dump 0x{:x} at file offset 0x{:x}".format(validdump, offset)) + + return signature, validdump + + +class WindowsCrashDump64Layer(WindowsCrashDump32Layer): + """A Windows crash format TranslationLayer. + + This TranslationLayer supports Microsoft complete memory dump files. + It currently does not support kernel or small memory dump files. + """ + + VALIDDUMP = 0x34365544 + crashdump_json = 'crash64' + dump_header_name = '_DUMP_HEADER64' + supported_dumptypes = [0x1, 0x05] + headerpages = 2 + + def _load_segments(self) -> None: + """Loads up the segments from the meta_layer.""" + + segments = [] + + summary_header = self.context.object(self._crash_table_name + constants.BANG + "_SUMMARY_DUMP64", + offset = 0x2000, + layer_name = self._base_layer) + + if self.dump_type == 0x1: + header = self.context.object(self._crash_table_name + constants.BANG + self.dump_header_name, + offset = 0, + layer_name = self._base_layer) + + offset = self.headerpages + header.PhysicalMemoryBlockBuffer.Run.count = header.PhysicalMemoryBlockBuffer.NumberOfRuns + for x in header.PhysicalMemoryBlockBuffer.Run: + segments.append((x.BasePage * 0x1000, offset * 0x1000, x.PageCount * 0x1000, x.PageCount * 0x1000)) + offset += x.PageCount + + elif self.dump_type == 0x05: + summary_header.BufferLong.count = (summary_header.BitmapSize + 31) // 32 + previous_bit = 0 + start_position = 0 + # We cast as an int because we don't want to carry the context around with us for infinite loop reasons + mapped_offset = int(summary_header.HeaderSize) + current_word = None + for bit_position in range(len(summary_header.BufferLong) * 32): + if (bit_position % 32) == 0: + current_word = summary_header.BufferLong[bit_position // 32] + current_bit = (current_word >> (bit_position % 32)) & 1 + if current_bit != previous_bit: + if previous_bit == 0: + # Start + start_position = bit_position + else: + # Finish + length = (bit_position - start_position) * 0x1000 + segments.append((start_position * 0x1000, mapped_offset, length, length)) + mapped_offset += length + + # Finish it off + if bit_position == (len(summary_header.BufferLong) * 32) - 1 and current_bit == 1: + length = (bit_position - start_position) * 0x1000 + segments.append((start_position * 0x1000, mapped_offset, length, length)) + mapped_offset += length + + previous_bit = current_bit + else: + vollog.log(constants.LOGLEVEL_VVVV, "unsupported dump format 0x{:x}".format(self.dump_type)) + raise WindowsCrashDumpFormatException(self.name, "unsupported dump format 0x{:x}".format(self.dump_type)) + + if len(segments) == 0: + raise WindowsCrashDumpFormatException(self.name, "No Crash segments defined in {}".format(self._base_layer)) + + self._segments = segments + + +class WindowsCrashDumpStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 11 + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + for layer in [WindowsCrashDump32Layer, WindowsCrashDump64Layer]: + try: + layer.check_header(context.layers[layer_name]) + new_name = context.layers.free_layer_name(layer.__name__) + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = layer_name + return layer(context, new_name, new_name) + except WindowsCrashDumpFormatException: + pass + return None diff --git a/app/parsers/vol_Parser/volatility/framework/layers/elf.py b/app/parsers/vol_Parser/volatility/framework/layers/elf.py new file mode 100644 index 00000000..022fc496 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/elf.py @@ -0,0 +1,86 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +import struct +from typing import Optional + +from volatility.framework import exceptions, interfaces, constants +from volatility.framework.layers import segmented +from volatility.framework.symbols import intermed + +vollog = logging.getLogger(__name__) + + +class ElfFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying ELF file format.""" + + +class Elf64Layer(segmented.SegmentedLayer): + """A layer that supports the Elf64 format as documented at: http://ftp.openwatcom.org/devel/docs/elf-64-gen.pdf""" + _header_struct = struct.Struct(" None: + # Create a custom SymbolSpace + self._elf_table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'linux', 'elf') + + super().__init__(context, config_path, name) + + def _load_segments(self) -> None: + """Load the segments from based on the PT_LOAD segments of the Elf64 format""" + ehdr = self.context.object(self._elf_table_name + constants.BANG + "Elf64_Ehdr", + layer_name = self._base_layer, + offset = 0) + + segments = [] + + for pindex in range(ehdr.e_phnum): + phdr = self.context.object(self._elf_table_name + constants.BANG + "Elf64_Phdr", + layer_name = self._base_layer, + offset = ehdr.e_phoff + (pindex * ehdr.e_phentsize)) + # We only want PT_TYPES with valid sizes + if phdr.p_type.lookup() == "PT_LOAD" and phdr.p_filesz == phdr.p_memsz and phdr.p_filesz > 0: + # Cast these to ints to ensure the offsets don't need reconstructing + segments.append((int(phdr.p_paddr), int(phdr.p_offset), int(phdr.p_memsz), int(phdr.p_memsz))) + + if len(segments) == 0: + raise ElfFormatException(self.name, "No ELF segments defined in {}".format(self._base_layer)) + + self._segments = segments + + @classmethod + def _check_header(cls, base_layer: interfaces.layers.DataLayerInterface, offset: int = 0) -> bool: + try: + header_data = base_layer.read(offset, cls._header_struct.size) + except exceptions.InvalidAddressException: + raise ElfFormatException(base_layer.name, + "Offset 0x{:0x} does not exist within the base layer".format(offset)) + (magic, elf_class, elf_data_encoding, elf_version) = cls._header_struct.unpack(header_data) + if magic != cls.MAGIC: + raise ElfFormatException(base_layer.name, "Bad magic 0x{:x} at file offset 0x{:x}".format(magic, offset)) + if elf_class != cls.ELF_CLASS: + raise ElfFormatException(base_layer.name, "ELF class is not 64-bit (2): {:d}".format(elf_class)) + # Virtualbox uses an ELF version of 0, which isn't to specification, but is ok to deal with + return True + + +class Elf64Stacker(interfaces.automagic.StackerLayerInterface): + stack_order = 10 + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + try: + if not Elf64Layer._check_header(context.layers[layer_name]): + return None + except ElfFormatException as excp: + vollog.log(constants.LOGLEVEL_VVVV, "Exception: {}".format(excp)) + return None + new_name = context.layers.free_layer_name("Elf64Layer") + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = layer_name + + return Elf64Layer(context, new_name, new_name) diff --git a/app/parsers/vol_Parser/volatility/framework/layers/intel.py b/app/parsers/vol_Parser/volatility/framework/layers/intel.py new file mode 100644 index 00000000..544e6259 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/intel.py @@ -0,0 +1,318 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import collections +import functools +import logging +import math +import struct +from typing import Any, Dict, Iterable, List, Optional, Tuple + +from volatility import classproperty +from volatility.framework import exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.layers import linear + +vollog = logging.getLogger(__name__) + + +class Intel(linear.LinearlyMappedLayer): + """Translation Layer for the Intel IA32 memory mapping.""" + + _entry_format = " None: + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + self._base_layer = self.config["memory_layer"] + self._swap_layers = [] # type: List[str] + self._page_map_offset = self.config["page_map_offset"] + + # Assign constants + self._initial_position = min(self._maxvirtaddr, self._bits_per_register) - 1 + self._initial_entry = self._mask(self._page_map_offset, self._initial_position, 0) | 0x1 + self._entry_size = struct.calcsize(self._entry_format) + self._entry_number = self.page_size // self._entry_size + + # These can vary depending on the type of space + self._index_shift = int(math.ceil(math.log2(struct.calcsize(self._entry_format)))) + + @classproperty + def page_size(cls) -> int: + """Page size for the intel memory layers. + + All Intel layers work on 4096 byte pages + """ + return 1 << cls._page_size_in_bits + + @classproperty + def bits_per_register(cls) -> int: + """Returns the bits_per_register to determine the range of an + IntelTranslationLayer.""" + return cls._bits_per_register + + @classproperty + def minimum_address(cls) -> int: + return 0 + + @classproperty + def maximum_address(cls) -> int: + return (1 << cls._maxvirtaddr) - 1 + + @classproperty + def structure(cls) -> List[Tuple[str, int, bool]]: + return cls._structure + + @staticmethod + def _mask(value: int, high_bit: int, low_bit: int) -> int: + """Returns the bits of a value between highbit and lowbit inclusive.""" + high_mask = (1 << (high_bit + 1)) - 1 + low_mask = (1 << low_bit) - 1 + mask = (high_mask ^ low_mask) + # print(high_bit, low_bit, bin(mask), bin(value)) + return value & mask + + @staticmethod + def _page_is_valid(entry: int) -> bool: + """Returns whether a particular page is valid based on its entry.""" + return bool(entry & 1) + + def _translate(self, offset: int) -> Tuple[int, int, str]: + """Translates a specific offset based on paging tables. + + Returns the translated offset, the contiguous pagesize that the + translated address lives in and the layer_name that the address + lives in + """ + entry, position = self._translate_entry(offset) + + # Now we're done + if not self._page_is_valid(entry): + raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, + "Page Fault at entry {} in page entry".format(hex(entry))) + page = self._mask(entry, self._maxphyaddr - 1, position + 1) | self._mask(offset, position, 0) + + return page, 1 << (position + 1), self._base_layer + + def _translate_entry(self, offset): + """Translates a specific offset based on paging tables. + + Returns the translated entry value + """ + # Setup the entry and how far we are through the offset + # Position maintains the number of bits left to process + # We or with 0x1 to ensure our page_map_offset is always valid + position = self._initial_position + entry = self._initial_entry + + # Run through the offset in various chunks + for (name, size, large_page) in self._structure: + # Check we're valid + if not self._page_is_valid(entry): + raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, + "Page Fault at entry " + hex(entry) + " in table " + name) + # Check if we're a large page + if large_page and (entry & (1 << 7)): + # We're a large page, the rest is finished below + # If we want to implement PSE-36, it would need to be done here + break + # Figure out how much of the offset we should be using + start = position + position -= size + index = self._mask(offset, start, position + 1) >> (position + 1) + + # Grab the base address of the table we'll be getting the next entry from + base_address = self._mask(entry, self._maxphyaddr - 1, size + self._index_shift) + + table = self._get_valid_table(base_address) + if table is None: + raise exceptions.PagedInvalidAddressException(self.name, offset, position + 1, entry, + "Page Fault at entry " + hex(entry) + " in table " + name) + + # Read the data for the next entry + entry_data = table[(index << self._index_shift):(index << self._index_shift) + self._entry_size] + + # Read out the new entry from memory + entry, = struct.unpack(self._entry_format, entry_data) + + return entry, position + + @functools.lru_cache(1025) + def _get_valid_table(self, base_address: int) -> Optional[bytes]: + """Extracts the table, validates it and returns it if it's valid.""" + table = self._context.layers.read(self._base_layer, base_address, self.page_size) + + # If the table is entirely duplicates, then mark the whole table as bad + if (table == table[:self._entry_size] * self._entry_number): + return None + return table + + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns whether the address offset can be translated to a valid + address.""" + try: + # TODO: Consider reimplementing this, since calls to mapping can call is_valid + return all([ + self._context.layers[layer].is_valid(mapped_offset) + for _, _, mapped_offset, _, layer in self.mapping(offset, length) + ]) + except exceptions.InvalidAddressException: + return False + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + """Returns a sorted iterable of (offset, sublength, mapped_offset, mapped_length, layer) + mappings. + + This allows translation layers to provide maps of contiguous + regions in one layer + """ + if length == 0: + try: + mapped_offset, _, layer_name = self._translate(offset) + if not self._context.layers[layer_name].is_valid(mapped_offset): + raise exceptions.InvalidAddressException(layer_name = layer_name, invalid_address = mapped_offset) + except exceptions.InvalidAddressException: + if not ignore_errors: + raise + return + yield offset, length, mapped_offset, length, layer_name + return + while length > 0: + try: + chunk_offset, page_size, layer_name = self._translate(offset) + chunk_size = min(page_size - (chunk_offset % page_size), length) + if not self._context.layers[layer_name].is_valid(chunk_offset, chunk_size): + raise exceptions.InvalidAddressException(layer_name = layer_name, invalid_address = chunk_offset) + except (exceptions.PagedInvalidAddressException, exceptions.InvalidAddressException) as excp: + if not ignore_errors: + raise + # We can jump more if we know where the page fault failed + if isinstance(excp, exceptions.PagedInvalidAddressException): + mask = (1 << excp.invalid_bits) - 1 + else: + mask = (1 << self._page_size_in_bits) - 1 + length_diff = (mask + 1 - (offset & mask)) + length -= length_diff + offset += length_diff + else: + yield offset, chunk_size, chunk_offset, chunk_size, layer_name + length -= chunk_size + offset += chunk_size + + @property + def dependencies(self) -> List[str]: + """Returns a list of the lower layer names that this layer is dependent + upon.""" + return [self._base_layer] + self._swap_layers + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'memory_layer', optional = False), + requirements.LayerListRequirement(name = 'swap_layers', optional = True), + requirements.IntRequirement(name = 'page_map_offset', optional = False), + requirements.IntRequirement(name = 'kernel_virtual_offset', optional = True), + requirements.StringRequirement(name = 'kernel_banner', optional = True) + ] + + +class IntelPAE(Intel): + """Class for handling Physical Address Extensions for Intel + architectures.""" + + _entry_format = " bool: + """Returns whether a particular page is valid based on its entry. + + Windows uses additional "available" bits to store flags + These flags allow windows to determine whether a page is still valid + + Bit 11 is the transition flag, and Bit 10 is the prototype flag + + For more information, see Windows Internals (6th Ed, Part 2, pages 268-269) + """ + return bool((entry & 1) or ((entry & 1 << 11) and not entry & 1 << 10)) + + def _translate_swap(self, layer: Intel, offset: int, bit_offset: int): + try: + return super()._translate(offset) + except exceptions.PagedInvalidAddressException as excp: + entry = excp.entry + tbit = bool(entry & (1 << 11)) + pbit = bool(entry & (1 << 10)) + unknown_bit = bool(entry & (1 << 7)) + n = (entry >> 1) & 0xF + vbit = bool(entry & 1) + if (not tbit and not pbit and not vbit and unknown_bit) and ((entry >> bit_offset) != 0): + swap_offset = entry >> bit_offset << excp.invalid_bits + + if layer.config.get('swap_layers', False): + swap_layer_name = layer.config.get( + interfaces.configuration.path_join('swap_layers', 'swap_layers' + str(n)), None) + if swap_layer_name: + return swap_offset, 1 << excp.invalid_bits, swap_layer_name + raise exceptions.SwappedInvalidAddressException(layer_name = excp.layer_name, + invalid_address = excp.invalid_address, + invalid_bits = excp.invalid_bits, + entry = excp.entry, + swap_offset = swap_offset) + raise + + +### These must be full separate classes so that JSON configs re-create them properly + + +class WindowsIntel(WindowsMixin, Intel): + + def _translate(self, offset): + return self._translate_swap(self, offset, self._page_size_in_bits) + + +class WindowsIntelPAE(WindowsMixin, IntelPAE): + + def _translate(self, offset): + return self._translate_swap(self, offset, self._bits_per_register) + + +class WindowsIntel32e(WindowsMixin, Intel32e): + + def _translate(self, offset): + return self._translate_swap(self, offset, self._bits_per_register // 2) diff --git a/app/parsers/vol_Parser/volatility/framework/layers/lime.py b/app/parsers/vol_Parser/volatility/framework/layers/lime.py new file mode 100644 index 00000000..45e2ac9d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/lime.py @@ -0,0 +1,90 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import struct +from typing import Optional, Tuple + +from volatility.framework import exceptions, interfaces, constants +from volatility.framework.layers import segmented + + +class LimeFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying Lime file format.""" + + +class LimeLayer(segmented.SegmentedLayer): + """A Lime format TranslationLayer. + + Lime is generally used to store physical memory images where there + are large holes in the physical layer + """ + + MAGIC = 0x4c694d45 + VERSION = 1 + + # Magic[4], Version[4], Start[8], End[8], Reserved[8] + # XXX move this to a custom SymbolSpace? + _header_struct = struct.Struct(' None: + super().__init__(context, config_path, name) + + # The base class loads the segments on initialization, but otherwise this must to get the right min/max addresses + + def _load_segments(self) -> None: + base_layer = self._context.layers[self._base_layer] + base_maxaddr = base_layer.maximum_address + maxaddr = 0 + offset = 0 + header_size = self._header_struct.size + segments = [] + + while offset < base_maxaddr: + start, end = self._check_header(base_layer, offset) + + if start < maxaddr or end < start: + raise LimeFormatException( + self.name, "Bad start/end 0x{:x}/0x{:x} at file offset 0x{:x}".format(start, end, offset)) + + segment_length = end - start + 1 + segments.append((start, offset + header_size, segment_length, segment_length)) + maxaddr = end + offset = offset + header_size + segment_length + + if len(segments) == 0: + raise LimeFormatException(self.name, "No LiME segments defined in {}".format(self._base_layer)) + + self._segments = segments + + @classmethod + def _check_header(cls, base_layer: interfaces.layers.DataLayerInterface, offset: int = 0) -> Tuple[int, int]: + try: + header_data = base_layer.read(offset, cls._header_struct.size) + except exceptions.InvalidAddressException: + raise LimeFormatException(base_layer.name, + "Offset 0x{:0x} does not exist within the base layer".format(offset)) + (magic, version, start, end, reserved) = cls._header_struct.unpack(header_data) + if magic != cls.MAGIC: + raise LimeFormatException(base_layer.name, "Bad magic 0x{:x} at file offset 0x{:x}".format(magic, offset)) + if version != cls.VERSION: + raise LimeFormatException(base_layer.name, + "Unexpected version {:d} at file offset 0x{:x}".format(version, offset)) + return start, end + + +class LimeStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 10 + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + try: + LimeLayer._check_header(context.layers[layer_name]) + except LimeFormatException: + return None + new_name = context.layers.free_layer_name("LimeLayer") + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = layer_name + return LimeLayer(context, new_name, new_name) diff --git a/app/parsers/vol_Parser/volatility/framework/layers/linear.py b/app/parsers/vol_Parser/volatility/framework/layers/linear.py new file mode 100644 index 00000000..04f769fe --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/linear.py @@ -0,0 +1,72 @@ +import functools +from typing import List, Optional, Tuple, Iterable + +from volatility.framework import exceptions, interfaces +from volatility.framework.interfaces.layers import IteratorValue + + +class LinearlyMappedLayer(interfaces.layers.TranslationLayerInterface): + """Class to differentiate Linearly Mapped layers (where a => b implies that + a + c => b + c)""" + + ### Translation layer convenience function + + def translate(self, offset: int, ignore_errors: bool = False) -> Tuple[Optional[int], Optional[str]]: + mapping = list(self.mapping(offset, 0, ignore_errors)) + if len(mapping) == 1: + original_offset, _, mapped_offset, _, layer = mapping[0] + if original_offset != offset: + raise exceptions.LayerException(self.name, + "Layer {} claims to map linearly but does not".format(self.name)) + else: + if ignore_errors: + # We should only hit this if we ignored errors, but check anyway + return None, None + raise exceptions.InvalidAddressException(self.name, offset, + "Cannot translate {} in layer {}".format(offset, self.name)) + return mapped_offset, layer + + # ## Read/Write functions for mapped pages + # Redefine read here for speed reasons (so we don't call a processing method + + @functools.lru_cache(maxsize = 512) + def read(self, offset: int, length: int, pad: bool = False) -> bytes: + """Reads an offset for length bytes and returns 'bytes' (not 'str') of + length size.""" + current_offset = offset + output = [] # type: List[bytes] + for (offset, _, mapped_offset, mapped_length, layer) in self.mapping(offset, length, ignore_errors = pad): + if not pad and offset > current_offset: + raise exceptions.InvalidAddressException( + self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset)) + elif offset > current_offset: + output += [b"\x00" * (offset - current_offset)] + current_offset = offset + elif offset < current_offset: + raise exceptions.LayerException(self.name, "Mapping returned an overlapping element") + if mapped_length > 0: + output += [self._context.layers.read(layer, mapped_offset, mapped_length, pad)] + current_offset += mapped_length + recovered_data = b"".join(output) + return recovered_data + b"\x00" * (length - len(recovered_data)) + + def write(self, offset: int, value: bytes) -> None: + """Writes a value at offset, distributing the writing across any + underlying mapping.""" + current_offset = offset + length = len(value) + for (offset, _, mapped_offset, length, layer) in self.mapping(offset, length): + if offset > current_offset: + raise exceptions.InvalidAddressException( + self.name, current_offset, "Layer {} cannot map offset: {}".format(self.name, current_offset)) + elif offset < current_offset: + raise exceptions.LayerException(self.name, "Mapping returned an overlapping element") + self._context.layers.write(layer, mapped_offset, value[:length]) + value = value[length:] + current_offset += length + + def _scan_iterator(self, + scanner: 'interfaces.layers.ScannerInterface', + sections: Iterable[Tuple[int, int]], + linear: bool = True) -> Iterable[IteratorValue]: + return super()._scan_iterator(scanner, sections, linear) diff --git a/app/parsers/vol_Parser/volatility/framework/layers/msf.py b/app/parsers/vol_Parser/volatility/framework/layers/msf.py new file mode 100644 index 00000000..c030b535 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/msf.py @@ -0,0 +1,231 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import math +from typing import Optional, Dict, Any, List, Iterable, Tuple + +from volatility.framework import interfaces, constants, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import linear +from volatility.framework.objects import utility +from volatility.framework.symbols import intermed + + +class PDBFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying MSF file format.""" + + +class PdbMultiStreamFormat(linear.LinearlyMappedLayer): + _headers = { + "MSF_HDR": "Microsoft C/C++ program database 2.00\r\n\x1a\x4a\x47", + "BIG_MSF_HDR": "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53", + } + + def __init__(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context, config_path, name, metadata) + self._base_layer = self.config["base_layer"] + + self._pdb_symbol_table = intermed.IntermediateSymbolTable.create(context, self._config_path, 'windows', 'pdb') + response = self._check_header() + if response is None: + raise PDBFormatException(name, "Could not find a suitable header") + self._version, self._header = response + self._streams = {} # type: Dict[int, str] + + @property + def pdb_symbol_table(self) -> str: + return self._pdb_symbol_table + + def read_streams(self): + # Shortcut in case they've already been read + if self._streams: + return + + # Recover the root table, by recovering the root table index table... + module = self.context.module(self.pdb_symbol_table, self._base_layer, offset = 0) + entry_size = module.get_type("unsigned long").size + + root_table_num_pages = math.ceil(self._header.StreamInfo.StreamInfoSize / self._header.PageSize) + root_index_size = math.ceil((root_table_num_pages * entry_size) / self._header.PageSize) + root_index = module.object(object_type = "array", + offset = self._header.vol.size, + count = root_index_size, + subtype = module.get_type("unsigned long")) + root_index_layer_name = self.create_stream_from_pages("root_index", self._header.StreamInfo.StreamInfoSize, + [x for x in root_index]) + + module = self.context.module(self.pdb_symbol_table, root_index_layer_name, offset = 0) + root_pages = module.object(object_type = "array", + offset = 0, + count = root_table_num_pages, + subtype = module.get_type("unsigned long")) + root_layer_name = self.create_stream_from_pages("root", self._header.StreamInfo.StreamInfoSize, + [x for x in root_pages]) + + module = self.context.module(self.pdb_symbol_table, root_layer_name, offset = 0) + num_streams = module.object(object_type = "unsigned long", offset = 0) + stream_sizes = module.object(object_type = "array", + offset = entry_size, + count = num_streams, + subtype = module.get_type("unsigned long")) + + current_offset = (num_streams + 1) * entry_size + + for stream in range(num_streams): + list_size = math.ceil(stream_sizes[stream] / self.page_size) + if list_size == 0 or stream_sizes[stream] == 0xffffffff: + self._streams[stream] = None + else: + stream_page_list = module.object(object_type = "array", + offset = current_offset, + count = list_size, + subtype = module.get_type("unsigned long")) + current_offset += (list_size * entry_size) + self._streams[stream] = self.create_stream_from_pages("stream" + str(stream), stream_sizes[stream], + [x for x in stream_page_list]) + + def create_stream_from_pages(self, stream_name: str, maximum_size: int, pages: List[int]) -> str: + # Construct a root layer based on a number of pages + layer_name = self.name + "_" + stream_name + path_join = interfaces.configuration.path_join + config_path = path_join(self.config_path, stream_name) + self.context.config[path_join(config_path, 'base_layer')] = self.name + self.context.config[path_join(config_path, 'pages')] = pages + self.context.config[path_join(config_path, 'maximum_size')] = maximum_size + layer = PdbMSFStream(self.context, config_path, layer_name) + self.context.layers.add_layer(layer) + return layer_name + + def _check_header(self) -> Optional[Tuple[str, interfaces.objects.ObjectInterface]]: + """Verifies the header of the PDB file and returns the version of the + file.""" + for header in self._headers: + header_type = self.pdb_symbol_table + constants.BANG + header + current_header = self.context.object(header_type, self._base_layer, 0) + if utility.array_to_string(current_header.Magic) == self._headers[header]: + if not (current_header.PageSize < 0x100 or current_header.PageSize > (128 * 0x10000)): + return header, current_header + return None + + @property + def page_size(self): + return self._header.PageSize + + @property + def dependencies(self) -> List[str]: + """Returns a list of the lower layers that this layer is dependent + upon.""" + return [self._base_layer] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [requirements.TranslationLayerRequirement(name = 'base_layer', optional = False)] + + @property + def maximum_address(self) -> int: + return self.context.layers[self._base_layer].maximum_address + + @property + def minimum_address(self) -> int: + return self.context.layers[self._base_layer].minimum_address + + def is_valid(self, offset: int, length: int = 1) -> bool: + return self.context.layers[self._base_layer].is_valid(offset, length) + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + yield offset, length, offset, length, self._base_layer + + def get_stream(self, index) -> Optional['PdbMSFStream']: + self.read_streams() + if index not in self._streams: + raise PDBFormatException(self.name, "Stream not present") + if self._streams[index]: + layer = self.context.layers[self._streams[index]] + if isinstance(layer, PdbMSFStream): + return layer + return None + + +class PdbMSFStream(linear.LinearlyMappedLayer): + + def __init__(self, + context: 'interfaces.context.ContextInterface', + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context, config_path, name, metadata) + self._base_layer = self.config["base_layer"] + self._pages = self.config.get("pages", None) + self._pages_len = len(self._pages) + if not self._pages: + raise PDBFormatException(name, "Invalid/no pages specified") + if not isinstance(self._pdb_layer, PdbMultiStreamFormat): + raise TypeError("Base Layer must be a PdbMultiStreamFormat layer") + + @property + def pdb_symbol_table(self) -> Optional[str]: + layer = self._context.layers[self._base_layer] + if isinstance(layer, PdbMultiStreamFormat): + return layer.pdb_symbol_table + else: + return None + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ListRequirement(name = 'pages', element_type = int, min_elements = 1), + requirements.TranslationLayerRequirement(name = 'base_layer'), + requirements.IntRequirement(name = 'maximum_size') + ] + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + returned = 0 + page_size = self._pdb_layer.page_size + while length > 0: + page = math.floor((offset + returned) / page_size) + page_position = ((offset + returned) % page_size) + chunk_size = min(page_size - page_position, length) + if page >= self._pages_len: + if not ignore_errors: + raise exceptions.InvalidAddressException(layer_name = self.name, + invalid_address = offset + returned) + else: + yield offset + returned, chunk_size, (self._pages[page] * + page_size) + page_position, chunk_size, self._base_layer + returned += chunk_size + length -= chunk_size + + @property + def dependencies(self) -> List[str]: + return [self._base_layer] + + def is_valid(self, offset: int, length: int = 1) -> bool: + return self.context.layers[self._base_layer].is_valid(offset, length) + + @property + def minimum_address(self) -> int: + return 0 + + @property + def maximum_address(self) -> int: + return self.config.get('maximum_size', len(self._pages) * self._pdb_layer.page_size) + + @property + def _pdb_layer(self) -> PdbMultiStreamFormat: + if self._base_layer not in self._context.layers: + raise PDBFormatException(self._base_layer, + "No PdbMultiStreamFormat layer found: {}".format(self._base_layer)) + result = self._context.layers[self._base_layer] + if isinstance(result, PdbMultiStreamFormat): + return result + raise TypeError("Base layer is not PdbMultiStreamFormat") diff --git a/app/parsers/vol_Parser/volatility/framework/layers/physical.py b/app/parsers/vol_Parser/volatility/framework/layers/physical.py new file mode 100644 index 00000000..46bd7fc1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/physical.py @@ -0,0 +1,185 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import threading +from typing import Any, Dict, IO, List, Optional, Union + +from volatility.framework import exceptions, interfaces, constants +from volatility.framework.configuration import requirements +from volatility.framework.layers import resources + + +class BufferDataLayer(interfaces.layers.DataLayerInterface): + """A DataLayer class backed by a buffer in memory, designed for testing and + swift data access.""" + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + buffer: bytes, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + self._buffer = buffer + + @property + def maximum_address(self) -> int: + """Returns the largest available address in the space.""" + return len(self._buffer) - 1 + + @property + def minimum_address(self) -> int: + """Returns the smallest available address in the space.""" + return 0 + + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns whether the offset is valid or not.""" + return bool(self.minimum_address <= offset <= self.maximum_address + and self.minimum_address <= offset + length - 1 <= self.maximum_address) + + def read(self, address: int, length: int, pad: bool = False) -> bytes: + """Reads the data from the buffer.""" + if not self.is_valid(address, length): + invalid_address = address + if self.minimum_address < address <= self.maximum_address: + invalid_address = self.maximum_address + 1 + raise exceptions.InvalidAddressException(self.name, invalid_address, + "Offset outside of the buffer boundaries") + return self._buffer[address:address + length] + + def write(self, address: int, data: bytes): + """Writes the data from to the buffer.""" + self._buffer = self._buffer[:address] + data + self._buffer[address + len(data):] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # No real requirements (only the buffer). Need to figure out if there's a better way of representing this + return [ + requirements.BytesRequirement(name = 'buffer', + description = "The direct bytes to interact with", + optional = False) + ] + + +class DummyLock: + + def __enter__(self): + pass + + def __exit__(self, type, value, traceback): + pass + + +class FileLayer(interfaces.layers.DataLayerInterface): + """a DataLayer backed by a file on the filesystem.""" + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + + self._location = self.config["location"] + self._accessor = resources.ResourceAccessor() + self._file_ = None # type: Optional[IO[Any]] + self._size = None # type: Optional[int] + # Construct the lock now (shared if made before threading) in case we ever need it + self._lock = DummyLock() # type: Union[DummyLock, threading.Lock] + if constants.PARALLELISM == constants.Parallelism.Threading: + self._lock = threading.Lock() + # Instantiate the file to throw exceptions if the file doesn't open + _ = self._file + + @property + def location(self) -> str: + """Returns the location on which this Layer abstracts.""" + return self._location + + @property + def _file(self) -> IO[Any]: + """Property to prevent the initializer storing an unserializable open + file (for context cloning)""" + # FIXME: Add "+" to the mode once we've determined whether write mode is enabled + mode = "rb" + self._file_ = self._file_ or self._accessor.open(self._location, mode) + return self._file_ + + @property + def maximum_address(self) -> int: + """Returns the largest available address in the space.""" + # Zero based, so we return the size of the file minus 1 + if self._size: + return self._size + with self._lock: + orig = self._file.tell() + self._file.seek(0, 2) + self._size = self._file.tell() + self._file.seek(orig) + return self._size + + @property + def minimum_address(self) -> int: + """Returns the smallest available address in the space.""" + return 0 + + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns whether the offset is valid or not.""" + if length <= 0: + raise ValueError("Length must be positive") + return bool(self.minimum_address <= offset <= self.maximum_address + and self.minimum_address <= offset + length - 1 <= self.maximum_address) + + def read(self, offset: int, length: int, pad: bool = False) -> bytes: + """Reads from the file at offset for length.""" + if not self.is_valid(offset, length): + invalid_address = offset + if self.minimum_address < offset <= self.maximum_address: + invalid_address = self.maximum_address + 1 + raise exceptions.InvalidAddressException(self.name, invalid_address, + "Offset outside of the buffer boundaries") + + # TODO: implement locking for multi-threading + with self._lock: + self._file.seek(offset) + data = self._file.read(length) + + if len(data) < length: + if pad: + data += (b"\x00" * (length - len(data))) + else: + raise exceptions.InvalidAddressException( + self.name, offset + len(data), "Could not read sufficient bytes from the " + self.name + " file") + return data + + def write(self, offset: int, data: bytes) -> None: + """Writes to the file. + + This will technically allow writes beyond the extent of the file + """ + if not self.is_valid(offset, len(data)): + invalid_address = offset + if self.minimum_address < offset <= self.maximum_address: + invalid_address = self.maximum_address + 1 + raise exceptions.InvalidAddressException(self.name, invalid_address, + "Data segment outside of the " + self.name + " file boundaries") + with self._lock: + self._file.seek(offset) + self._file.write(data) + + def __getstate__(self) -> Dict[str, Any]: + """Do not store the open _file_ attribute, our property will ensure the + file is open when needed. + + This is necessary for multi-processing + """ + self._file_ = None + return self.__dict__ + + def destroy(self) -> None: + """Closes the file handle.""" + self._file.close() + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [requirements.StringRequirement(name = 'location', optional = False)] diff --git a/app/parsers/vol_Parser/volatility/framework/layers/qemu.py b/app/parsers/vol_Parser/volatility/framework/layers/qemu.py new file mode 100644 index 00000000..d5f92663 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/qemu.py @@ -0,0 +1,236 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import functools +import json +import math +from typing import Optional, Dict, Any, Tuple, List, Set + +from volatility.framework import interfaces, exceptions, constants +from volatility.framework.layers import segmented +from volatility.framework.symbols import intermed + + +class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer): + """A Qemu suspend-to-disk translation layer.""" + + QEVM_EOF = 0x00 + QEVM_SECTION_START = 0x01 + QEVM_SECTION_PART = 0x02 + QEVM_SECTION_END = 0x03 + QEVM_SECTION_FULL = 0x04 + QEVM_SUBSECTION = 0x05 + QEVM_VMDESCRIPTION = 0x06 + QEVM_CONFIGURATION = 0x07 + QEVM_SECTION_FOOTER = 0x7e + HASH_PTE_SIZE_64 = 16 + + SEGMENT_FLAG_COMPRESS = 0x02 + SEGMENT_FLAG_MEM_SIZE = 0x04 + SEGMENT_FLAG_PAGE = 0x08 + SEGMENT_FLAG_EOS = 0x10 + SEGMENT_FLAG_CONTINUE = 0x20 + SEGMENT_FLAG_XBZRLE = 0x40 + SEGMENT_FLAG_HOOK = 0x80 + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + self._qemu_table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'generic', 'qemu') + self._configuration = None + self._compressed = set() # type: Set[int] + self._current_segment_name = b'' + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + + @classmethod + def _check_header(cls, base_layer: interfaces.layers.DataLayerInterface, name: str = ''): + header = base_layer.read(0, 8) + if header[:4] != b'\x51\x45\x56\x4D': + raise exceptions.LayerException(name, 'No QEMU magic bytes') + if header[4:] != b'\x00\x00\x00\x03': + raise exceptions.LayerException(name, 'Unsupported QEMU version found') + + def _read_configuration(self, base_layer: interfaces.layers.DataLayerInterface, name: str) -> Any: + """Reads the JSON configuration from the end of the file""" + chunk_size = 0x4096 + data = b'' + for i in range(base_layer.maximum_address, base_layer.minimum_address, -chunk_size): + if i != base_layer.maximum_address: + data = base_layer.read(i, chunk_size) + data + if b'\x00' in data: + start = data.rfind(b'\x00') + data = data[data.find(b'{', start):] + return json.loads(data) + raise exceptions.LayerException(name, "Could not load JSON configuration from the end of the file") + + def _get_ram_segments(self, index: int, page_size: int) -> Tuple[List[Tuple[int, int, int, int]], int]: + """Recovers the new index and any sections of memory from a ram section""" + done = None + segments = [] + + base_layer = self.context.layers[self._base_layer] + + while not done: + addr = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long long', + offset = index, + layer_name = self._base_layer) + flags = addr & (page_size - 1) + page_size_bits = int(math.log(page_size, 2)) + addr = (addr >> page_size_bits) << page_size_bits + index += 8 + + if flags & self.SEGMENT_FLAG_MEM_SIZE: + namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', + offset = index, + layer_name = self._base_layer) + while namelen != 0: + # if base_layer.read(index + 1, namelen) == b'pc.ram': + # total_size = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned long long', + # offset = index + 1 + namelen, + # layer_name = self._base_layer) + index += 1 + namelen + 8 + namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', + offset = index, + layer_name = self._base_layer) + if flags & (self.SEGMENT_FLAG_COMPRESS | self.SEGMENT_FLAG_PAGE): + if not (flags & self.SEGMENT_FLAG_CONTINUE): + namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char', + offset = index, + layer_name = self._base_layer) + self._current_segment_name = base_layer.read(index + 1, namelen) + index += 1 + namelen + if flags & self.SEGMENT_FLAG_COMPRESS: + if self._current_segment_name == b'pc.ram': + segments.append((addr, index, page_size, 1)) + self._compressed.add(addr) + index += 1 + else: + if self._current_segment_name == b'pc.ram': + segments.append((addr, index, page_size, page_size)) + index += page_size + if flags & self.SEGMENT_FLAG_XBZRLE: + raise exceptions.LayerException(self.name, "XBZRLE compression not supported") + if flags & self.SEGMENT_FLAG_EOS: + done = True + return segments, index + + def _load_segments(self): + base_layer = self.context.layers[self._base_layer] + self._check_header(base_layer, self.name) + if not self._configuration: + self._configuration = self._read_configuration(base_layer, self.name) + section_byte = -1 + index = 8 + current_section_id = -1 + version_id = -1 + name = None + while section_byte != self.QEVM_EOF and index <= base_layer.maximum_address: + section_byte = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned char', + offset = index, + layer_name = self._base_layer) + index += 1 + if section_byte == self.QEVM_CONFIGURATION: + section_len = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + index += 4 + section_len + elif section_byte == self.QEVM_SECTION_START or section_byte == self.QEVM_SECTION_FULL: + section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + current_section_id = section_id + index += 4 + name_len = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned char', + offset = index, + layer_name = self._base_layer) + index += 1 + name = self.context.object(self._qemu_table_name + constants.BANG + 'string', + offset = index, + layer_name = self._base_layer, + max_length = name_len) + index += name_len + # instance_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + # offset = index, + # layer_name = self._base_layer) + index += 4 + version_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + index += 4 + # Read additional data + index = self.extract_data(index, name, version_id) + elif section_byte == self.QEVM_SECTION_PART or section_byte == self.QEVM_SECTION_END: + section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + current_section_id = section_id + index += 4 + # Read additional data + index = self.extract_data(index, name, version_id) + elif section_byte == self.QEVM_SECTION_FOOTER: + section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + index += 4 + if section_id != current_section_id: + raise exceptions.LayerException( + self._name, 'QEMU section footer mismatch: {} and {}'.format(current_section_id, section_id)) + elif section_byte == self.QEVM_EOF: + pass + else: + raise exceptions.LayerException(self._name, 'QEMU unknown section encountered: {}'.format(section_byte)) + + def extract_data(self, index, name, version_id): + if name == 'ram': + if version_id != 4: + raise exceptions.LayerException("QEMU unknown RAM version_id {}".format(version_id)) + new_segments, index = self._get_ram_segments(index, self._configuration.get('page_size', None) or 4096) + self._segments += new_segments + elif name == 'spapr/htab': + if version_id != 1: + raise exceptions.LayerException("QEMU unknown HTAB version_id {}".format(version_id)) + header = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long', + offset = index, + layer_name = self._base_layer) + index += 4 + if header == 0: + htab_index = -1 + htab_n_valid = 0 + htab_n_invalid = 0 + while htab_index != 0 and htab_n_valid != 0 and htab_n_invalid != 0: + htab = self.context.object(self._qemu_table_name + constants.BANG + 'htab', + offset = index, + layer_name = self._base_layer) + htab_index, htab_n_valid, htab_n_invalid = htab + index += 8 + (htab_n_valid * self.HASH_PTE_SIZE_64) + return index + + def _decode_data(self, data: bytes, mapped_offset: int, offset: int, output_length: int) -> bytes: + if mapped_offset in self._compressed: + return (data * 0x1000)[:output_length] + return data + + @functools.lru_cache(maxsize = 512) + def read(self, offset: int, length: int, pad: bool = False) -> bytes: + return super().read(offset, length, pad) + + +class QemuStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 10 + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + try: + QemuSuspendLayer._check_header(context.layers[layer_name]) + except exceptions.LayerException: + return None + new_name = context.layers.free_layer_name("QemuSuspendLayer") + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = layer_name + layer = QemuSuspendLayer(context, new_name, new_name) + cls.stacker_slow_warning() + return layer diff --git a/app/parsers/vol_Parser/volatility/framework/layers/registry.py b/app/parsers/vol_Parser/volatility/framework/layers/registry.py new file mode 100644 index 00000000..82128b4f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/registry.py @@ -0,0 +1,263 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union + +from volatility.framework import constants, exceptions, interfaces, objects +from volatility.framework.configuration import requirements +from volatility.framework.configuration.requirements import IntRequirement, TranslationLayerRequirement +from volatility.framework.exceptions import InvalidAddressException +from volatility.framework.layers import linear +from volatility.framework.symbols import intermed +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class RegistryFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying Registry file format.""" + + +class RegistryInvalidIndex(exceptions.LayerException): + """Thrown when an index that doesn't exist or can't be found occurs.""" + + +class RegistryHive(linear.LinearlyMappedLayer): + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + + self._base_layer = self.config["base_layer"] + self._hive_offset = self.config["hive_offset"] + self._table_name = self.config["nt_symbols"] + self._page_size = 1 << 12 + + self._reg_table_name = intermed.IntermediateSymbolTable.create(context, self._config_path, 'windows', + 'registry') + + cmhive = self.context.object(self._table_name + constants.BANG + "_CMHIVE", self._base_layer, self._hive_offset) + self._cmhive_name = cmhive.get_name() + self.hive = cmhive.Hive + + # TODO: Check the checksum + if self.hive.Signature != 0xbee0bee0: + raise RegistryFormatException( + self.name, "Registry hive at {} does not have a valid signature".format(self._hive_offset)) + + # Win10 17063 introduced the Registry process to map most hives. Check + # if it exists and update RegistryHive._base_layer + for proc in pslist.PsList.list_processes(self.context, self.config['base_layer'], self.config['nt_symbols']): + proc_name = proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace') + if proc_name == "Registry" and proc.InheritedFromUniqueProcessId == 4: + proc_layer_name = proc.add_process_layer() + self._base_layer = proc_layer_name + break + + self._base_block = self.hive.BaseBlock.dereference() + + self._minaddr = 0 + try: + self._hive_maxaddr_non_volatile = self.hive.Storage[0].Length + self._hive_maxaddr_volatile = self.hive.Storage[1].Length + self._maxaddr = 0x80000000 | self._hive_maxaddr_volatile + vollog.log(constants.LOGLEVEL_VVV, "Setting hive max address to {}".format(hex(self._maxaddr))) + except exceptions.InvalidAddressException: + self._hive_maxaddr_non_volatile = 0x7fffffff + self._hive_maxaddr_volatile = 0x7fffffff + self._maxaddr = 0x80000000 | self._hive_maxaddr_volatile + vollog.log(constants.LOGLEVEL_VVV, + "Exception when setting hive max address, using {}".format(hex(self._maxaddr))) + + def _get_hive_maxaddr(self, volatile): + return self._hive_maxaddr_volatile if volatile else self._hive_maxaddr_non_volatile + + def get_name(self) -> str: + return self._cmhive_name or "[NONAME]" + + @property + def hive_offset(self) -> int: + return self._hive_offset + + @property + def address_mask(self) -> int: + """Return a mask that allows for the volatile bit to be set.""" + return super().address_mask | 0x80000000 + + @property + def root_cell_offset(self) -> int: + """Returns the offset for the root cell in this hive.""" + try: + if self._base_block.Signature.cast("string", max_length = 4, encoding = "latin-1") == 'regf': + return self._base_block.RootCell + except InvalidAddressException: + pass + return 0x20 + + def get_cell(self, cell_offset: int) -> 'objects.StructType': + """Returns the appropriate Cell value for a cell offset.""" + # This would be an _HCELL containing CELL_DATA, but to save time we skip the size of the HCELL + cell = self._context.object(object_type = self._table_name + constants.BANG + "_CELL_DATA", + offset = cell_offset + 4, + layer_name = self.name) + return cell + + def get_node(self, cell_offset: int) -> 'objects.StructType': + """Returns the appropriate Node, interpreted from the Cell based on its + Signature.""" + cell = self.get_cell(cell_offset) + signature = cell.cast('string', max_length = 2, encoding = 'latin-1') + if signature == 'nk': + return cell.u.KeyNode + elif signature == 'sk': + return cell.u.KeySecurity + elif signature == 'vk': + return cell.u.KeyValue + elif signature == 'db': + # Big Data + return cell.u.ValueData + elif signature == 'lf' or signature == 'lh' or signature == 'ri': + # Fast Leaf, Hash Leaf, Index Root + return cell.u.KeyIndex + else: + # It doesn't matter that we use KeyNode, we're just after the first two bytes + vollog.debug("Unknown Signature {} (0x{:x}) at offset {}".format(signature, cell.u.KeyNode.Signature, + cell_offset)) + return cell + + def get_key(self, key: str, return_list: bool = False) -> Union[List[objects.StructType], objects.StructType]: + """Gets a specific registry key by key path. + + return_list specifies whether the return result will be a single + node (default) or a list of nodes from root to the current node + (if return_list is true). + """ + node_key = [self.get_node(self.root_cell_offset)] + if key.endswith("\\"): + key = key[:-1] + key_array = key.split('\\') + found_key = [] # type: List[str] + while key_array and node_key: + subkeys = node_key[-1].get_subkeys() + for subkey in subkeys: + # registry keys are not case sensitive so compare lowercase + # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724946(v=vs.85).aspx + if subkey.get_name().lower() == key_array[0].lower(): + node_key = node_key + [subkey] + found_key, key_array = found_key + [key_array[0]], key_array[1:] + break + else: + node_key = [] + if not node_key: + raise KeyError("Key {} not found under {}".format(key_array[0], '\\'.join(found_key))) + if return_list: + return node_key + return node_key[-1] + + def visit_nodes(self, + visitor: Callable[[objects.StructType], None], + node: Optional[objects.StructType] = None) -> None: + """Applies a callable (visitor) to all nodes within the registry tree + from a given node.""" + if not node: + node = self.get_node(self.root_cell_offset) + visitor(node) + for node in node.get_subkeys(): + self.visit_nodes(visitor, node) + + @staticmethod + def _mask(value: int, high_bit: int, low_bit: int) -> int: + """Returns the bits of a value between highbit and lowbit inclusive.""" + high_mask = (2 ** (high_bit + 1)) - 1 + low_mask = (2 ** low_bit) - 1 + mask = (high_mask ^ low_mask) + # print(high_bit, low_bit, bin(mask), bin(value)) + return value & mask + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + IntRequirement(name = 'hive_offset', + description = 'Offset within the base layer at which the hive lives', + default = 0, + optional = False), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + TranslationLayerRequirement(name = 'base_layer', + description = 'Layer in which the registry hive lives', + optional = False) + ] + + def _translate(self, offset: int) -> int: + """Translates a single cell index to a cell memory offset and the + suboffset within it.""" + + # Ignore the volatile bit when determining maxaddr validity + volatile = self._mask(offset, 31, 31) >> 31 + if offset & 0x7fffffff > self._get_hive_maxaddr(volatile): + raise RegistryInvalidIndex(self.name, "Mapping request for value greater than maxaddr") + + storage = self.hive.Storage[volatile] + dir_index = self._mask(offset, 30, 21) >> 21 + table_index = self._mask(offset, 20, 12) >> 12 + suboffset = self._mask(offset, 11, 0) >> 0 + + table = storage.Map.Directory[dir_index] + entry = table.Table[table_index] + return entry.get_block_offset() + suboffset + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + + if length < 0: + raise ValueError("Mapping length of RegistryHive must be positive or zero") + + # Return the translated offset without checking bounds within the HBIN. The check runs into + # issues when pages are swapped on large HBINs, and did not seem to find any errors on single page + # HBINs while dramatically slowing performance. + response = [] + current_offset = offset + remaining_length = length + chunk_size = self._page_size - (offset & (self._page_size - 1)) + while remaining_length > 0: + chunk_size = min(chunk_size, remaining_length, self._page_size) + try: + translated_offset = self._translate(current_offset) + response.append((current_offset, chunk_size, translated_offset, chunk_size, self._base_layer)) + except exceptions.LayerException: + if not ignore_errors: + raise + current_offset += chunk_size + remaining_length -= chunk_size + chunk_size = self._page_size + return response + + @property + def dependencies(self) -> List[str]: + """Returns a list of layer names that this layer translates onto.""" + return [self.config['base_layer']] + + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns a boolean based on whether the offset is valid or not.""" + try: + # Pass this to the lower layers for now + return all([ + self.context.layers[layer].is_valid(offset, length) + for (_, _, offset, length, layer) in self.mapping(offset, length) + ]) + except exceptions.InvalidAddressException: + return False + + @property + def minimum_address(self) -> int: + return self._minaddr + + @property + def maximum_address(self) -> int: + return self._maxaddr diff --git a/app/parsers/vol_Parser/volatility/framework/layers/resources.py b/app/parsers/vol_Parser/volatility/framework/layers/resources.py new file mode 100644 index 00000000..5be55e6d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/resources.py @@ -0,0 +1,230 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import bz2 +import contextlib +import gzip +import hashlib +import logging +import lzma +import os +import ssl +import urllib.parse +import urllib.request +import zipfile +from typing import Optional, Any +from urllib import error + +from volatility import framework +from volatility.framework import constants + +try: + import magic + + HAS_MAGIC = True +except ImportError: + HAS_MAGIC = False + +try: + # Import so that the handler is found by the framework.class_subclasses callc + import smb.SMBHandler # lgtm [py/unused-import] +except ImportError: + pass + +vollog = logging.getLogger(__name__) + + +# TODO: Type-annotating the ResourceAccessor.open method is difficult because HTTPResponse is not actually an IO[Any] type +# fix this + + +def cascadeCloseFile(new_fp, original_fp): + """Really horrible solution for ensuring files aren't left open + + Args: + new_fp: The file pointer constructed based on the original file pointer + original_fp: The original file pointer that should be closed when the new file pointer is closed, but isn't + """ + + def close(): + original_fp.close() + return new_fp.__class__.close(new_fp) + + new_fp.close = close + return new_fp + + +class ResourceAccessor(object): + """Object for openning URLs as files (downloading locally first if + necessary)""" + + list_handlers = True + + def __init__(self, + progress_callback: Optional[constants.ProgressCallback] = None, + context: Optional[ssl.SSLContext] = None) -> None: + """Creates a resource accessor. + + Note: context is an SSL context, not a volatility context + """ + self._progress_callback = progress_callback + self._context = context + self._handlers = list(framework.class_subclasses(urllib.request.BaseHandler)) + if self.list_handlers: + vollog.log(constants.LOGLEVEL_VVV, + "Available URL handlers: {}".format(", ".join([x.__name__ for x in self._handlers]))) + self.__class__.list_handlers = False + + def uses_cache(self, url: str) -> bool: + """Determines whether a URLs contents should be cached""" + parsed_url = urllib.parse.urlparse(url) + + return not parsed_url.scheme in ['file', 'jar'] + + # Current urllib.request.urlopen returns Any, so we do the same + def open(self, url: str, mode: str = "rb") -> Any: + """Returns a file-like object for a particular URL opened in mode. + + If the file is remote, it will be downloaded and locally cached + """ + urllib.request.install_opener(urllib.request.build_opener(*self._handlers)) + + try: + fp = urllib.request.urlopen(url, context = self._context) + except error.URLError as excp: + if excp.args: + # TODO: As of python3.7 this can be removed + unverified_retrieval = (hasattr(ssl, "SSLCertVerificationError") and isinstance( + excp.args[0], ssl.SSLCertVerificationError)) or (isinstance(excp.args[0], ssl.SSLError) and + excp.args[0].reason == "CERTIFICATE_VERIFY_FAILED") + if unverified_retrieval: + vollog.warning("SSL certificate verification failed: attempting UNVERIFIED retrieval") + non_verifying_ctx = ssl.SSLContext() + non_verifying_ctx.check_hostname = False + non_verifying_ctx.verify_mode = ssl.CERT_NONE + fp = urllib.request.urlopen(url, context = non_verifying_ctx) + else: + raise excp + else: + raise excp + + with contextlib.closing(fp) as fp: + # Cache the file locally + + if not self.uses_cache(url): + # ZipExtFiles (files in zips) cannot seek, so must be cached in order to use and/or decompress + curfile = urllib.request.urlopen(url, context = self._context) + else: + # TODO: find a way to check if we already have this file (look at http headers?) + block_size = 1028 * 8 + temp_filename = os.path.join( + constants.CACHE_PATH, + "data_" + hashlib.sha512(bytes(url, 'raw_unicode_escape')).hexdigest() + ".cache") + + if not os.path.exists(temp_filename): + vollog.debug("Caching file at: {}".format(temp_filename)) + + try: + content_length = fp.info().get('Content-Length', -1) + except AttributeError: + # If our fp doesn't have an info member, carry on gracefully + content_length = -1 + cache_file = open(temp_filename, "wb") + + count = 0 + block = fp.read(block_size) + while block: + count += len(block) + if self._progress_callback: + self._progress_callback(count * 100 / max(count, int(content_length)), + "Reading file {}".format(url)) + cache_file.write(block) + block = fp.read(block_size) + cache_file.close() + # Re-open the cache with a different mode + curfile = open(temp_filename, mode = "rb") + + # Determine whether the file is a particular type of file, and if so, open it as such + IMPORTED_MAGIC = False + if HAS_MAGIC: + stop = False + while not stop: + detected = None + try: + # Detect the content + detected = magic.detect_from_fobj(curfile) + IMPORTED_MAGIC = True + # This is because python-magic and file provide a magic module + # Only file's python has magic.detect_from_fobj + except (AttributeError, IOError): + pass + + if detected: + if detected.mime_type == 'application/x-xz': + curfile = cascadeCloseFile(lzma.LZMAFile(curfile, mode), curfile) + elif detected.mime_type == 'application/x-bzip2': + curfile = cascadeCloseFile(bz2.BZ2File(curfile, mode), curfile) + elif detected.mime_type == 'application/x-gzip': + curfile = cascadeCloseFile(gzip.GzipFile(fileobj = curfile, mode = mode), curfile) + if detected.mime_type in ['application/x-xz', 'application/x-bzip2', 'application/x-gzip']: + # Read and rewind to ensure we're inside any compressed file layers + curfile.read(1) + curfile.seek(0) + else: + stop = True + else: + stop = True + + if not IMPORTED_MAGIC: + # Somewhat of a hack, but prevents a hard dependency on the magic module + parsed_url = urllib.parse.urlparse(url) + url_path = parsed_url.path + stop = False + while not stop: + url_path_split = url_path.split(".") + url_path_list, extension = url_path_split[:-1], url_path_split[-1] + url_path = ".".join(url_path_list) + if extension == "xz": + curfile = cascadeCloseFile(lzma.LZMAFile(curfile, mode), curfile) + elif extension == "bz2": + curfile = cascadeCloseFile(bz2.BZ2File(curfile, mode), curfile) + elif extension == "gz": + curfile = cascadeCloseFile(gzip.GzipFile(fileobj = curfile, mode = mode), curfile) + else: + stop = True + + # Fallback in case the file doesn't exist + if curfile is None: + raise ValueError("URL does not reference an openable file") + return curfile + + +class JarHandler(urllib.request.BaseHandler): + """Handles the jar scheme for URIs. + + Reference used for the schema syntax: + http://docs.netkernel.org/book/view/book:mod:reference/doc:layer1:schemes:jar + + Actual reference (found from https://www.w3.org/wiki/UriSchemes/jar) seemed not to return: + http://developer.java.sun.com/developer/onlineTraining/protocolhandlers/ + """ + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the jar scheme.""" + if req.type == 'jar': + subscheme, remainder = req.full_url.split(":")[1], ":".join(req.full_url.split(":")[2:]) + if subscheme != 'file': + vollog.log(constants.LOGLEVEL_VVV, "Unsupported jar subscheme {}".format(subscheme)) + return None + + zipsplit = remainder.split("!") + if len(zipsplit) != 2: + vollog.log(constants.LOGLEVEL_VVV, + "Path did not contain exactly one fragment indicator: {}".format(remainder)) + return None + + zippath, filepath = zipsplit + return zipfile.ZipFile(zippath).open(filepath) + return None diff --git a/app/parsers/vol_Parser/volatility/framework/layers/scanners/__init__.py b/app/parsers/vol_Parser/volatility/framework/layers/scanners/__init__.py new file mode 100644 index 00000000..45208161 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/scanners/__init__.py @@ -0,0 +1,60 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import re +from typing import Generator, List, Tuple + +from volatility.framework.interfaces import layers +from volatility.framework.layers.scanners import multiregexp + + +class BytesScanner(layers.ScannerInterface): + thread_safe = True + + def __init__(self, needle: bytes) -> None: + super().__init__() + self.needle = needle + + def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]: + """Runs through the data looking for the needle, and yields all offsets + where the needle is found.""" + find_pos = data.find(self.needle) + while find_pos >= 0: + if find_pos < self.chunk_size: + yield find_pos + data_offset + find_pos = data.find(self.needle, find_pos + 1) + + +class RegExScanner(layers.ScannerInterface): + thread_safe = True + + def __init__(self, pattern: bytes, flags: int = 0) -> None: + super().__init__() + self.regex = re.compile(pattern, flags) + + def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]: + """Runs through the data looking for the needle, and yields all offsets + where the needle is found.""" + find_pos = self.regex.finditer(data) + for match in find_pos: + offset = match.start() + if offset < self.chunk_size: + yield offset + data_offset + + +class MultiStringScanner(layers.ScannerInterface): + thread_safe = True + + def __init__(self, patterns: List[bytes]) -> None: + super().__init__() + self._patterns = multiregexp.MultiRegexp() + for pattern in patterns: + self._patterns.add_pattern(pattern) + self._patterns.preprocess() + + def __call__(self, data: bytes, data_offset: int) -> Generator[Tuple[int, bytes], None, None]: + """Runs through the data looking for the needles.""" + for offset, pattern in self._patterns.search(data): + if offset < self.chunk_size: + yield offset + data_offset, pattern diff --git a/app/parsers/vol_Parser/volatility/framework/layers/scanners/multiregexp.py b/app/parsers/vol_Parser/volatility/framework/layers/scanners/multiregexp.py new file mode 100644 index 00000000..36c6410a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/scanners/multiregexp.py @@ -0,0 +1,30 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import re +from typing import Generator, List, Tuple + + +class MultiRegexp(object): + """Algorithm for multi-string matching.""" + + def __init__(self) -> None: + self._pattern_strings = [] # type: List[bytes] + self._regex = re.compile(b'') + + def add_pattern(self, pattern: bytes) -> None: + self._pattern_strings.append(pattern) + + def preprocess(self) -> None: + if not self._pattern_strings: + raise ValueError("No strings to compile into a regular expression") + self._regex = re.compile(b'|'.join(map(re.escape, self._pattern_strings))) + + def search(self, haystack: bytes) -> Generator[Tuple[int, bytes], None, None]: + if not isinstance(haystack, bytes): + raise TypeError("Search haystack must be a byte string") + if not self._regex.pattern: + raise ValueError("MultiRegexp cannot be used with an empty set of search strings") + for match in re.finditer(self._regex, haystack): + yield (match.start(0), match.group()) diff --git a/app/parsers/vol_Parser/volatility/framework/layers/segmented.py b/app/parsers/vol_Parser/volatility/framework/layers/segmented.py new file mode 100644 index 00000000..d2f80ec3 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/segmented.py @@ -0,0 +1,142 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +from abc import ABCMeta, abstractmethod +from bisect import bisect_right +from typing import Any, Dict, Iterable, List, Optional, Tuple + +from volatility.framework import exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.layers import linear + + +class NonLinearlySegmentedLayer(interfaces.layers.TranslationLayerInterface, metaclass = ABCMeta): + """A class to handle a single run-based layer-to-layer mapping. + + In the documentation "mapped address" or "mapped offset" refers to + an offset once it has been mapped to the underlying layer + """ + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + super().__init__(context = context, config_path = config_path, name = name, metadata = metadata) + + self._base_layer = self.config["base_layer"] + self._segments = [] # type: List[Tuple[int, int, int, int]] + self._minaddr = None # type: Optional[int] + self._maxaddr = None # type: Optional[int] + + self._load_segments() + + @abstractmethod + def _load_segments(self) -> None: + """Populates the _segments variable. + + Segments must be (address, mapped address, length) and must be + sorted by address when this method exits + """ + + def is_valid(self, offset: int, length: int = 1) -> bool: + """Returns whether the address offset can be translated to a valid + address.""" + try: + base_layer = self._context.layers[self._base_layer] + return all( + [base_layer.is_valid(mapped_offset) for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length)]) + except exceptions.InvalidAddressException: + return False + + def _find_segment(self, offset: int, next: bool = False) -> Tuple[int, int, int, int]: + """Finds the segment containing a given offset. + + Returns the segment tuple (offset, mapped_offset, length, mapped_length) + """ + + if not self._segments: + self._load_segments() + + # Find rightmost value less than or equal to x + i = bisect_right(self._segments, (offset, self.context.layers[self._base_layer].maximum_address)) + if i and not next: + segment = self._segments[i - 1] + if segment[0] <= offset < segment[0] + segment[2]: + return segment + if next: + if i < len(self._segments): + return self._segments[i] + raise exceptions.InvalidAddressException(self.name, offset, "Invalid address at {:0x}".format(offset)) + + def mapping(self, + offset: int, + length: int, + ignore_errors: bool = False) -> Iterable[Tuple[int, int, int, int, str]]: + """Returns a sorted iterable of (offset, length, mapped_offset, mapped_length, layer) + mappings.""" + done = False + current_offset = offset + while not done: + try: + # Search for the appropriate segment that contains the current_offset + logical_offset, mapped_offset, size, mapped_size = self._find_segment(current_offset) + # If it starts before the current_offset, bring the lower edge up to the right place + if current_offset > logical_offset: + difference = current_offset - logical_offset + logical_offset += difference + mapped_offset += difference + size -= difference + except exceptions.InvalidAddressException: + if not ignore_errors: + # If we're not ignoring errors, raise the invalid address exception + raise + try: + # Find the next valid segment after our current_offset + logical_offset, mapped_offset, size, mapped_size = self._find_segment(current_offset, next = True) + # We know that the logical_offset must be greater than current_offset so skip to that value + current_offset = logical_offset + # If it starts too late then we're done + if logical_offset > offset + length: + return + except exceptions.InvalidAddressException: + return + # Crop it to the amount we need left + chunk_size = min(size, length + offset - logical_offset) + yield logical_offset, chunk_size, mapped_offset, chunk_size, self._base_layer + current_offset += chunk_size + # Terminate if we've gone (or reached) our required limit + if current_offset >= offset + length: + done = True + + @property + def minimum_address(self) -> int: + if not self._segments: + raise ValueError("SegmentedLayer must contain some segments") + if self._minaddr is None: + mapped, _, _, _ = self._segments[0] + self._minaddr = mapped + return self._minaddr + + @property + def maximum_address(self) -> int: + if not self._segments: + raise ValueError("SegmentedLayer must contain some segments") + if self._maxaddr is None: + mapped, _, length, _ = self._segments[-1] + self._maxaddr = mapped + length + return self._maxaddr + + @property + def dependencies(self) -> List[str]: + """Returns a list of the lower layers that this layer is dependent + upon.""" + return [self._base_layer] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [requirements.TranslationLayerRequirement(name = 'base_layer', optional = False)] + + +class SegmentedLayer(NonLinearlySegmentedLayer, linear.LinearlyMappedLayer, metaclass = ABCMeta): + pass diff --git a/app/parsers/vol_Parser/volatility/framework/layers/vmware.py b/app/parsers/vol_Parser/volatility/framework/layers/vmware.py new file mode 100644 index 00000000..95eb7b75 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/layers/vmware.py @@ -0,0 +1,157 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import struct +from typing import Any, Dict, List, Optional + +from volatility.framework import interfaces, constants, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import physical, segmented, resources +from volatility.framework.symbols import native + + +class VmwareFormatException(exceptions.LayerException): + """Thrown when an error occurs with the underlying VMware vmem file format.""" + + +class VmwareLayer(segmented.SegmentedLayer): + header_structure = "<4sII" + group_structure = "64sQQ" + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + metadata: Optional[Dict[str, Any]] = None) -> None: + # Construct these so we can use self.config + self._context = context + self._config_path = config_path + self._page_size = 0x1000 + self._base_layer, self._meta_layer = self.config["base_layer"], self.config["meta_layer"] + # Then call the super, which will call load_segments (which needs the base_layer before it'll work) + super().__init__(context, config_path = config_path, name = name, metadata = metadata) + + def _load_segments(self) -> None: + """Loads up the segments from the meta_layer.""" + self._read_header() + + def _read_header(self) -> None: + """Checks the vmware header to make sure it's valid.""" + if "vmware" not in self._context.symbol_space: + self._context.symbol_space.append(native.NativeTable("vmware", native.std_ctypes)) + + meta_layer = self.context.layers.get(self._meta_layer, None) + header_size = struct.calcsize(self.header_structure) + data = meta_layer.read(0, header_size) + magic, unknown, groupCount = struct.unpack(self.header_structure, data) + if magic not in [b"\xD2\xBE\xD2\xBE"]: + raise VmwareFormatException(self.name, "Wrong magic bytes for Vmware layer: {}".format(repr(magic))) + + # TODO: Change certain structure sizes based on the version + # version = magic[1] & 0xf + + group_size = struct.calcsize(self.group_structure) + + groups = {} + for group in range(groupCount): + name, tag_location, _unknown = struct.unpack( + self.group_structure, meta_layer.read(header_size + (group * group_size), group_size)) + name = name.rstrip(b"\x00") + groups[name] = tag_location + memory = groups[b"memory"] + + tags_read = False + offset = memory + tags = {} + index_len = self._context.symbol_space.get_type("vmware!unsigned int").size + while not tags_read: + flags = ord(meta_layer.read(offset, 1)) + name_len = ord(meta_layer.read(offset + 1, 1)) + tags_read = (flags == 0) and (name_len == 0) + if not tags_read: + name = self._context.object("vmware!string", + layer_name = self._meta_layer, + offset = offset + 2, + max_length = name_len) + indicies_len = (flags >> 6) & 3 + indicies = [] + for index in range(indicies_len): + indicies.append( + self._context.object("vmware!unsigned int", + offset = offset + name_len + 2 + (index * index_len), + layer_name = self._meta_layer)) + data = self._context.object("vmware!unsigned int", + layer_name = self._meta_layer, + offset = offset + 2 + name_len + (indicies_len * index_len)) + tags[(name, tuple(indicies))] = (flags, data) + offset += 2 + name_len + (indicies_len * + index_len) + self._context.symbol_space.get_type("vmware!unsigned int").size + + if tags[("regionsCount", ())][1] == 0: + raise VmwareFormatException(self.name, "VMware VMEM is not split into regions") + for region in range(tags[("regionsCount", ())][1]): + offset = tags[("regionPPN", (region,))][1] * self._page_size + mapped_offset = tags[("regionPageNum", (region,))][1] * self._page_size + length = tags[("regionSize", (region,))][1] * self._page_size + self._segments.append((offset, mapped_offset, length, length)) + + @property + def dependencies(self) -> List[str]: + return [self._base_layer, self._meta_layer] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + """This vmware translation layer always requires a separate metadata + layer.""" + return [ + requirements.TranslationLayerRequirement(name = 'base_layer', optional = False), + requirements.TranslationLayerRequirement(name = 'meta_layer', optional = False) + ] + + +class VmwareStacker(interfaces.automagic.StackerLayerInterface): + stack_order = 20 + + @classmethod + def stack(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[interfaces.layers.DataLayerInterface]: + """Attempt to stack this based on the starting information.""" + memlayer = context.layers[layer_name] + if not isinstance(memlayer, physical.FileLayer): + return None + location = memlayer.location + if location.endswith(".vmem"): + vmss = location[:-5] + ".vmss" + vmsn = location[:-5] + ".vmsn" + current_layer_name = context.layers.free_layer_name("VmwareMetaLayer") + current_config_path = interfaces.configuration.path_join("automagic", "layer_stacker", "stack", + current_layer_name) + + try: + _ = resources.ResourceAccessor().open(vmss).read(10) + context.config[interfaces.configuration.path_join(current_config_path, "location")] = vmss + context.layers.add_layer(physical.FileLayer(context, current_config_path, current_layer_name)) + vmss_success = True + except IOError: + vmss_success = False + + if not vmss_success: + try: + _ = resources.ResourceAccessor().open(vmsn).read(10) + context.config[interfaces.configuration.path_join(current_config_path, "location")] = vmsn + context.layers.add_layer(physical.FileLayer(context, current_config_path, current_layer_name)) + vmsn_success = True + except IOError: + vmsn_success = False + + if not vmss_success and not vmsn_success: + return None + new_layer_name = context.layers.free_layer_name("VmwareLayer") + context.config[interfaces.configuration.path_join(current_config_path, "base_layer")] = layer_name + context.config[interfaces.configuration.path_join(current_config_path, "meta_layer")] = current_layer_name + new_layer = VmwareLayer(context, current_config_path, new_layer_name) + return new_layer + return None diff --git a/app/parsers/vol_Parser/volatility/framework/objects/__init__.py b/app/parsers/vol_Parser/volatility/framework/objects/__init__.py new file mode 100644 index 00000000..30bbcbe7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/objects/__init__.py @@ -0,0 +1,764 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import collections +import logging +import struct +from typing import Any, ClassVar, Dict, List, Iterable, Optional, Tuple, Type, Union as TUnion, overload + +from volatility.framework import interfaces, constants +from volatility.framework.objects import templates, utility + +vollog = logging.getLogger(__name__) + +DataFormatInfo = collections.namedtuple('DataFormatInfo', ['length', 'byteorder', 'signed']) + + +def convert_data_to_value(data: bytes, struct_type: Type[TUnion[int, float, bytes, str, bool]], + data_format: DataFormatInfo) -> TUnion[int, float, bytes, str, bool]: + """Converts a series of bytes to a particular type of value.""" + if struct_type == int: + return int.from_bytes(data, byteorder = data_format.byteorder, signed = data_format.signed) + if struct_type == bool: + struct_format = "?" + elif struct_type == float: + float_vals = "zzezfzzzd" + if data_format.length > len(float_vals) or float_vals[data_format.length] not in "efd": + raise ValueError("Invalid float size") + struct_format = ("<" if data_format.byteorder == 'little' else ">") + float_vals[data_format.length] + elif struct_type in [bytes, str]: + struct_format = str(data_format.length) + "s" + else: + raise TypeError("Cannot construct struct format for type {}".format(type(struct_type))) + + return struct.unpack(struct_format, data)[0] + + +def convert_value_to_data(value: TUnion[int, float, bytes, str, bool], struct_type: Type[TUnion[int, float, bytes, str, + bool]], + data_format: DataFormatInfo) -> bytes: + """Converts a particular value to a series of bytes.""" + if not isinstance(value, struct_type): + raise TypeError("Written value is not of the correct type for {}".format(struct_type.__class__.__name__)) + + if struct_type == int and isinstance(value, int): + # Doubling up on the isinstance is for mypy + return int.to_bytes(value, + length = data_format.length, + byteorder = data_format.byteorder, + signed = data_format.signed) + if struct_type == bool: + struct_format = "?" + elif struct_type == float: + float_vals = "zzezfzzzd" + if data_format.length > len(float_vals) or float_vals[data_format.length] not in "efd": + raise ValueError("Invalid float size") + struct_format = ("<" if data_format.byteorder == 'little' else ">") + float_vals[data_format.length] + elif struct_type in [bytes, str]: + struct_format = str(data_format.length) + "s" + else: + raise TypeError("Cannot construct struct format for type {}".format(type(struct_type))) + + return struct.pack(struct_format, value) + + +class Void(interfaces.objects.ObjectInterface): + """Returns an object to represent void/unknown types.""" + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + """Dummy size for Void objects. + + According to http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf, void is an incomplete type, + and therefore sizeof(void) should fail. However, we need to be able to construct voids to be able to + cast them, so we return a useless size. It shouldn't cause errors, but it also shouldn't be common, + it is logged at the lowest level. + """ + vollog.log(constants.LOGLEVEL_VVVV, "Void size requested") + return 0 + + def write(self, value: Any) -> None: + """Dummy method that does nothing for Void objects.""" + raise TypeError("Cannot write data to a void, recast as another object") + + +class Function(interfaces.objects.ObjectInterface): + """""" + + +class PrimitiveObject(interfaces.objects.ObjectInterface): + """PrimitiveObject is an interface for any objects that should simulate a + Python primitive.""" + _struct_type = int # type: ClassVar[Type] + + def __init__(self, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, data_format: DataFormatInfo) -> None: + super().__init__(context = context, type_name = type_name, object_info = object_info, data_format = data_format) + self._data_format = data_format + + def __new__(cls: Type, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + data_format: DataFormatInfo, + new_value: TUnion[int, float, bool, bytes, str] = None, + **kwargs) -> 'PrimitiveObject': + """Creates the appropriate class and returns it so that the native type + is inherited. + + The only reason the kwargs is added, is so that the inherriting types can override __init__ + without needing to override __new__ + + We also sneak in new_value, so that we don't have to do expensive (read: impossible) context reads + when unpickling. + """ + if new_value is None: + value = cls._unmarshall(context, data_format, object_info) + else: + value = new_value + result = cls._struct_type.__new__(cls, value) + # This prevents us having to go read a context layer when recreating after unpickling + # Mypy complains that result doesn't have a __new_value, but using setattr causes pycharm to complain further down + result.__new_value = value # type: ignore + return result + + def __getnewargs_ex__(self): + """Make sure that when pickling, all appropiate parameters for new are + provided.""" + kwargs = {} + for k, v in self._vol.maps[-1].items(): + if k not in ["context", "data_format", "object_info", "type_name"]: + kwargs[k] = v + kwargs['new_value'] = self.__new_value + return (self._context, self._vol.maps[-2]['type_name'], self._vol.maps[-3], self._data_format), kwargs + + @classmethod + def _unmarshall(cls, context: interfaces.context.ContextInterface, data_format: DataFormatInfo, + object_info: interfaces.objects.ObjectInformation) -> TUnion[int, float, bool, bytes, str]: + data = context.layers.read(object_info.layer_name, object_info.offset, data_format.length) + return convert_data_to_value(data, cls._struct_type, data_format) + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + """Returns the size of the templated object.""" + return template.vol.data_format.length + + def write(self, value: TUnion[int, float, bool, bytes, str]) -> None: + """Writes the object into the layer of the context at the current + offset.""" + data = convert_value_to_data(value, self._struct_type, self._data_format) + return self._context.layers.write(self.vol.layer_name, self.vol.offset, data) + + +# This must be int (and the _struct_type must be int) because bool cannot be inherited from: +# https://mail.python.org/pipermail/python-dev/2002-March/020822.html +# https://mail.python.org/pipermail/python-dev/2004-February/042537.html +class Boolean(PrimitiveObject, int): + """Primitive Object that handles boolean types.""" + _struct_type = int # type: ClassVar[Type] + + +class Integer(PrimitiveObject, int): + """Primitive Object that handles standard numeric types.""" + + +class Float(PrimitiveObject, float): + """Primitive Object that handles double or floating point numbers.""" + _struct_type = float # type: ClassVar[Type] + + +class Char(PrimitiveObject, int): + """Primitive Object that handles characters.""" + _struct_type = int # type: ClassVar[Type] + + +class Bytes(PrimitiveObject, bytes): + """Primitive Object that handles specific series of bytes.""" + _struct_type = bytes # type: ClassVar[Type] + + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + length: int = 1) -> None: + super().__init__(context = context, + type_name = type_name, + object_info = object_info, + data_format = DataFormatInfo(length, "big", False)) + self._vol['length'] = length + + def __new__(cls: Type, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + length: int = 1, + **kwargs) -> 'Bytes': + """Creates the appropriate class and returns it so that the native type + is inherritted. + + The only reason the kwargs is added, is so that the + inherriting types can override __init__ without needing to + override __new__ + """ + return cls._struct_type.__new__( + cls, cls._unmarshall(context, data_format = DataFormatInfo(length, "big", False), + object_info = object_info)) + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + return template.vol.length + + +class String(PrimitiveObject, str): + """Primitive Object that handles string values. + + Args: + max_length: specifies the maximum possible length that the string could hold within memory + (for multibyte characters, this will not be the maximum length of the string) + """ + _struct_type = str # type: ClassVar[Type] + + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + max_length: int = 1, + encoding: str = "utf-8", + errors: str = "strict") -> None: + super().__init__(context = context, + type_name = type_name, + object_info = object_info, + data_format = DataFormatInfo(max_length, "big", False)) + self._vol["max_length"] = max_length + self._vol['encoding'] = encoding + self._vol['errors'] = errors + + def __new__(cls: Type, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + max_length: int = 1, + encoding: str = "utf-8", + errors: str = "strict", + **kwargs) -> 'String': + """Creates the appropriate class and returns it so that the native type + is inherited. + + The only reason the kwargs is added, is so that the + inherriting types can override __init__ without needing to + override __new__ + """ + params = {} + if encoding: + params['encoding'] = encoding + if errors: + params['errors'] = errors + # Pass the encoding and error parameters to the string constructor to appropriately encode the string + value = cls._struct_type.__new__( + cls, + cls._unmarshall(context, data_format = DataFormatInfo(max_length, "big", False), object_info = object_info), + **params) + if value.find('\x00') >= 0: + value = value[:value.find('\x00')] + return value + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + """Returns the size of the templated object.""" + return template.vol.max_length + + +class Pointer(Integer): + """Pointer which points to another object.""" + + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + data_format: DataFormatInfo, + subtype: Optional[templates.ObjectTemplate] = None) -> None: + super().__init__(context = context, object_info = object_info, type_name = type_name, data_format = data_format) + self._vol['subtype'] = subtype + + @classmethod + def _unmarshall(cls, context: interfaces.context.ContextInterface, data_format: DataFormatInfo, + object_info: interfaces.objects.ObjectInformation) -> Any: + """Ensure that pointer values always fall within the domain of the + layer they're constructed on. + + If there's a need for all the data within the address, the + pointer should be recast. The "pointer" must always live within + the space (even if the data provided is invalid). + """ + length, endian, signed = data_format + if signed: + raise ValueError("Pointers cannot have signed values") + mask = context.layers[object_info.native_layer_name].address_mask + data = context.layers.read(object_info.layer_name, object_info.offset, length) + value = int.from_bytes(data, byteorder = endian, signed = signed) + return value & mask + + def dereference(self, layer_name: Optional[str] = None) -> interfaces.objects.ObjectInterface: + """Dereferences the pointer. + + Layer_name is identifies the appropriate layer within the + context that the pointer points to. If layer_name is None, it + defaults to the same layer that the pointer is currently + instantiated in. + """ + layer_name = layer_name or self.vol.native_layer_name + mask = self._context.layers[layer_name].address_mask + offset = self & mask + return self.vol.subtype(context = self._context, + object_info = interfaces.objects.ObjectInformation(layer_name = layer_name, + offset = offset, + parent = self, + size = self.vol.subtype.size)) + + def is_readable(self, layer_name: Optional[str] = None) -> bool: + """Determines whether the address of this pointer can be read from + memory.""" + layer_name = layer_name or self.vol.layer_name + return self._context.layers[layer_name].is_valid(self, self.vol.subtype.size) + + def __getattr__(self, attr: str) -> Any: + """Convenience function to access unknown attributes by getting them + from the subtype object.""" + if attr in ['vol', '_vol']: + raise AttributeError("Pointer not initialized before use") + return getattr(self.dereference(), attr) + + def has_member(self, member_name: str) -> bool: + """Returns whether the dereferenced type has this member.""" + return self._vol['subtype'].has_member(member_name) + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + return Integer.VolTemplateProxy.size(template) + + @classmethod + def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]: + """Returns the children of the template.""" + if 'subtype' in template.vol: + return [template.vol.subtype] + return [] + + @classmethod + def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: + """Substitutes the old_child for the new_child.""" + if 'subtype' in template.vol: + if template.vol.subtype == old_child: + template.update_vol(subtype = new_child) + + @classmethod + def has_member(cls, template: interfaces.objects.Template, member_name: str) -> bool: + return template.vol['subtype'].has_member(member_name) + + +class BitField(interfaces.objects.ObjectInterface, int): + """Object containing a field which is made up of bits rather than whole + bytes.""" + + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + base_type: interfaces.objects.Template, + start_bit: int = 0, + end_bit: int = 0) -> None: + super().__init__(context, type_name, object_info) + self._vol['base_type'] = base_type + self._vol['start_bit'] = start_bit + self._vol['end_bit'] = end_bit + + def __new__(cls, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + base_type: interfaces.objects.Template, + start_bit: int = 0, + end_bit: int = 0, + **kwargs) -> 'BitField': + value = base_type(context = context, object_info = object_info) + return int.__new__(cls, ((value & ((1 << end_bit) - 1)) >> start_bit)) # type: ignore + + def write(self, value): + raise NotImplementedError("Writing to BitFields is not yet implemented") + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + return template.vol.base_type.size + + @classmethod + def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]: + """Returns the children of the template.""" + if 'base_type' in template.vol: + return [template.vol.base_type] + return [] + + @classmethod + def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: + """Substitutes the old_child for the new_child.""" + if 'base_type' in template.vol: + if template.vol.base_type == old_child: + template.update_vol(base_type = new_child) + + +class Enumeration(interfaces.objects.ObjectInterface, int): + """Returns an object made up of choices.""" + + def __new__(cls, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, base_type: interfaces.objects.Template, + choices: Dict[str, int], **kwargs) -> 'Enumeration': + value = base_type(context = context, object_info = object_info) + return int.__new__(cls, value) # type: ignore + + def __init__(self, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, base_type: Integer, choices: Dict[str, + int]) -> None: + super().__init__(context, type_name, object_info) + self._inverse_choices = self._generate_inverse_choices(choices) + self._vol['choices'] = choices + + self._vol['base_type'] = base_type + + def __eq__(self, other): + """An enumeration must be equivalent to its value, even if the other value is not an enumeration""" + return int(self) == other + + def __hash__(self): + """Enumerations must be hashed as equivalent to their integer counterparts""" + return super().__hash__() + + @classmethod + def _generate_inverse_choices(cls, choices: Dict[str, int]) -> Dict[int, str]: + """Generates the inverse choices for the object.""" + inverse_choices = {} # type: Dict[int, str] + for k, v in choices.items(): + if v in inverse_choices: + # Technically this shouldn't be a problem, but since we inverse cache + # and can't map one value to two possibilities we throw an exception during build + # We can remove/work around this if it proves a common issue + raise ValueError("Enumeration value {} duplicated as {} and {}".format(v, k, inverse_choices[v])) + inverse_choices[v] = k + return inverse_choices + + def lookup(self, value: int = None) -> str: + """Looks up an individual value and returns the associated name.""" + if value is None: + return self.lookup(self) + if value in self._inverse_choices: + return self._inverse_choices[value] + raise ValueError("The value of the enumeration is outside the possible choices") + + @property + def description(self) -> str: + """Returns the chosen name for the value this object contains.""" + return self.lookup(self) + + @property + def choices(self) -> Dict[str, int]: + return self._vol['choices'] + + @property + def is_valid_choice(self) -> bool: + """Returns whether the value for the object is a valid choice""" + return self in self.choices.values() + + def __getattr__(self, attr: str) -> str: + """Returns the value for a specific name.""" + if attr in self._vol['choices']: + return self._vol['choices'][attr] + raise AttributeError("Unknown attribute {} for Enumeration {}".format(attr, self._vol['type_name'])) + + def write(self, value: bytes): + raise NotImplementedError("Writing to Enumerations is not yet implemented") + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + _methods = ['lookup'] + + @classmethod + def lookup(cls, template: interfaces.objects.Template, value: int) -> str: + """Looks up an individual value and returns the associated name.""" + _inverse_choices = Enumeration._generate_inverse_choices(template.vol['choices']) + if value in _inverse_choices: + return _inverse_choices[value] + raise ValueError("The value of the enumeration is outside the possible choices") + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + return template.vol['base_type'].size + + @classmethod + def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]: + """Returns the children of the template.""" + if 'base_type' in template.vol: + return [template.vol.base_type] + return [] + + @classmethod + def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: + """Substitutes the old_child for the new_child.""" + if 'base_type' in template.vol: + if template.vol.base_type == old_child: + template.update_vol(base_type = new_child) + + +class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence): + """Object which can contain a fixed number of an object type.""" + + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + count: int = 0, + subtype: templates.ObjectTemplate = None) -> None: + super().__init__(context = context, type_name = type_name, object_info = object_info) + self._vol['count'] = count + self._vol['subtype'] = subtype + self._vol['size'] = 0 + if subtype is not None: + self._vol['size'] = count * subtype.size + + # This overrides the little known Sequence.count(val) that returns the number of items in the list that match val + # Changing the name would be confusing (since we use count of an array everywhere else), so this is more important + @property + def count(self) -> int: + """Returns the count dynamically.""" + return self.vol.count + + @count.setter + def count(self, value: int) -> None: + """Sets the count to a specific value.""" + self._vol['count'] = value + self._vol['size'] = value * self._vol['subtype'].size + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + """Returns the size of the array, based on the count and the + subtype.""" + if 'subtype' not in template.vol and 'count' not in template.vol: + raise ValueError("Array ObjectTemplate must be provided a count and subtype") + return template.vol.get('subtype', None).size * template.vol.get('count', 0) + + @classmethod + def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]: + """Returns the children of the template.""" + if 'subtype' in template.vol: + return [template.vol.subtype] + return [] + + @classmethod + def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: + """Substitutes the old_child for the new_child.""" + if 'subtype' in template.vol: + if template.vol['subtype'] == old_child: + template.update_vol(subtype = new_child) + + @classmethod + def relative_child_offset(cls, template: interfaces.objects.Template, child: str) -> int: + """Returns the relative offset from the head of the parent data to + the child member.""" + if 'subtype' in template.vol and child == 'subtype': + return 0 + raise IndexError("Member not present in array template: {}".format(child)) + + @overload + def __getitem__(self, i: int) -> interfaces.objects.Template: + ... + + @overload + def __getitem__(self, s: slice) -> List[interfaces.objects.Template]: + ... + + def __getitem__(self, i): + """Returns the i-th item from the array.""" + result = [] # type: List[interfaces.objects.Template] + mask = self._context.layers[self.vol.layer_name].address_mask + # We use the range function to deal with slices for us + series = range(self.vol.count)[i] + return_list = True + if isinstance(series, int): + return_list = False + series = [series] + for index in series: + object_info = interfaces.objects.ObjectInformation( + layer_name = self.vol.layer_name, + offset = mask & (self.vol.offset + (self.vol.subtype.size * index)), + parent = self, + native_layer_name = self.vol.native_layer_name, + size = self.vol.subtype.size) + result += [self.vol.subtype(context = self._context, object_info = object_info)] + if not return_list: + return result[0] + return result + + def __len__(self) -> int: + """Returns the length of the array.""" + return self.vol.count + + def write(self, value) -> None: + raise NotImplementedError("Writing to Arrays is not yet implemented") + + +class AggregateType(interfaces.objects.ObjectInterface): + """Object which can contain members that are other objects. + + Keep the number of methods in this class low or very specific, since + each one could overload a valid member. + """ + + def __init__(self, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, size: int, + members: Dict[str, Tuple[int, interfaces.objects.Template]]) -> None: + super().__init__(context = context, + type_name = type_name, + object_info = object_info, + size = size, + members = members) + # self._check_members(members) + self._concrete_members = {} # type: Dict[str, Dict] + + def has_member(self, member_name: str) -> bool: + """Returns whether the object would contain a member called + member_name.""" + return member_name in self.vol.members + + class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): + + @classmethod + def size(cls, template: interfaces.objects.Template) -> int: + """Method to return the size of this type.""" + if template.vol.get('size', None) is None: + raise ValueError("ObjectTemplate not provided with a size") + return template.vol.size + + @classmethod + def children(cls, template: interfaces.objects.Template) -> List[interfaces.objects.Template]: + """Method to list children of a template.""" + return [member for _, member in template.vol.members.values()] + + @classmethod + def replace_child(cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: + """Replace a child elements within the arguments handed to the + template.""" + for member in template.vol.members.get('members', {}): + relative_offset, member_template = template.vol.members[member] + if member_template == old_child: + # Members will give access to the mutable members list, + # but in case that ever changes, do the update correctly + tmp_list = template.vol.members + tmp_list[member] = (relative_offset, new_child) + # If there's trouble with mutability, consider making update_vol return a clone with the changes + # (there will be a few other places that will be necessary) and/or making these part of the + # permanent dictionaries rather than the non-clonable ones + template.update_vol(members = tmp_list) + + @classmethod + def relative_child_offset(cls, template: interfaces.objects.Template, child: str) -> int: + """Returns the relative offset of a child to its parent.""" + retlist = template.vol.members.get(child, None) + if retlist is None: + raise IndexError("Member not present in template: {}".format(child)) + return retlist[0] + + @classmethod + def has_member(cls, template: interfaces.objects.Template, member_name: str) -> bool: + """Returns whether the object would contain a member called + member_name.""" + return member_name in template.vol.members + + @classmethod + def _check_members(cls, members: Dict[str, Tuple[int, interfaces.objects.Template]]) -> None: + # Members should be an iterable mapping of symbol names to tuples of (relative_offset, ObjectTemplate) + # An object template is a callable that when called with a context, offset, layer_name and type_name + + # We duplicate this code to avoid polluting the methodspace + agg_name = 'AggregateType' + for agg_type in AggregateTypes: + if isinstance(cls, agg_type): + agg_name = agg_type.__name__ + + assert isinstance(members, collections.abc.Mapping) + "{} members parameter must be a mapping: {}".format(agg_name, type(members)) + assert all([(isinstance(member, tuple) and len(member) == 2) for member in members.values()]) + "{} members must be a tuple of relative_offsets and templates".format(agg_name) + + def member(self, attr: str = 'member') -> object: + """Specifically named method for retrieving members.""" + return self.__getattr__(attr) + + def __getattr__(self, attr: str) -> Any: + """Method for accessing members of the type.""" + if attr in ['_concrete_members', 'vol']: + raise AttributeError("Object has not been properly initialized") + if attr in self._concrete_members: + return self._concrete_members[attr] + elif attr in self.vol.members: + mask = self._context.layers[self.vol.layer_name].address_mask + relative_offset, template = self.vol.members[attr] + if isinstance(template, templates.ReferenceTemplate): + template = self._context.symbol_space.get_type(template.vol.type_name) + object_info = interfaces.objects.ObjectInformation(layer_name = self.vol.layer_name, + offset = mask & (self.vol.offset + relative_offset), + member_name = attr, + parent = self, + native_layer_name = self.vol.native_layer_name, + size = template.size) + member = template(context = self._context, object_info = object_info) + self._concrete_members[attr] = member + return member + # We duplicate this code to avoid polluting the methodspace + agg_name = 'AggregateType' + for agg_type in AggregateTypes: + if isinstance(self, agg_type): + agg_name = agg_type.__name__ + raise AttributeError("{} has no attribute: {}.{}".format(agg_name, self.vol.type_name, attr)) + + def __dir__(self) -> Iterable[str]: + """Returns a complete list of members when dir is called.""" + return list(super().__dir__()) + list(self.vol.members) + + def write(self, value): + # We duplicate this code to avoid polluting the methodspace + agg_name = 'AggregateType' + for agg_type in AggregateTypes: + if isinstance(self, agg_type): + agg_name = agg_type.__name__ + raise TypeError( + "{}s cannot be written to directly, individual members must be written instead".format(agg_name)) + + +class StructType(AggregateType): + pass + + +class UnionType(AggregateType): + pass + + +class ClassType(AggregateType): + pass + + +AggregateTypes = {StructType: 'struct', UnionType: 'union', ClassType: 'class'} diff --git a/app/parsers/vol_Parser/volatility/framework/objects/templates.py b/app/parsers/vol_Parser/volatility/framework/objects/templates.py new file mode 100644 index 00000000..c19bc07e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/objects/templates.py @@ -0,0 +1,106 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import functools +import logging +from typing import Any, ClassVar, Dict, List, Type + +from volatility.framework import interfaces, exceptions, constants + +vollog = logging.getLogger(__name__) + + +class ObjectTemplate(interfaces.objects.Template): + """Factory class that produces objects that adhere to the Object interface + on demand. + + This is effectively a method of currying, but adds more structure to avoid abuse. + It also allows inspection of information that should already be known: + + * Type size + * Members + * etc + """ + + def __init__(self, object_class: Type[interfaces.objects.ObjectInterface], type_name: str, **arguments) -> None: + arguments['object_class'] = object_class + super().__init__(type_name = type_name, **arguments) + + proxy_cls = self.vol.object_class.VolTemplateProxy + for method_name in proxy_cls._methods: + setattr(self, method_name, functools.partial(getattr(proxy_cls, method_name), self)) + + @property + def size(self) -> int: + """Returns the children of the templated object (see :class:`~volatilit + y.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)""" + return self.vol.object_class.VolTemplateProxy.size(self) + + @property + def children(self) -> List[interfaces.objects.Template]: + """Returns the children of the templated object (see :class:`~volatilit + y.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)""" + return self.vol.object_class.VolTemplateProxy.children(self) + + def relative_child_offset(self, child: str) -> int: + """Returns the relative offset of a child of the templated object (see + :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTem + plateProxy`)""" + return self.vol.object_class.VolTemplateProxy.relative_child_offset(self, child) + + def replace_child(self, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template) -> None: + """Replaces `old_child` for `new_child` in the templated object's child + list (see :class:`~volatility.framework.interfaces.objects.ObjectInterf + ace.VolTemplateProxy`)""" + return self.vol.object_class.VolTemplateProxy.replace_child(self, old_child, new_child) + + def has_member(self, member_name: str) -> bool: + """Returns whether the object would contain a member called + member_name.""" + return self.vol.object_class.VolTemplateProxy.has_member(self, member_name) + + def __call__(self, context: interfaces.context.ContextInterface, + object_info: interfaces.objects.ObjectInformation) -> interfaces.objects.ObjectInterface: + """Constructs the object. + + Returns: an object adhereing to the :class:`~volatility.framework.interfaces.objects.ObjectInterface` + """ + arguments = {} # type: Dict[str, Any] + for arg in self.vol: + if arg != 'object_class': + arguments[arg] = self.vol[arg] + return self.vol.object_class(context = context, object_info = object_info, **arguments) + + +class ReferenceTemplate(interfaces.objects.Template): + """Factory class that produces objects based on a delayed reference type. + + Attempts to access any standard attributes of a resolved template will result in a + :class:`~volatility.framework.exceptions.SymbolError`. + """ + + @property + def children(self) -> List[interfaces.objects.Template]: + return [] + + def _unresolved(self, *args, **kwargs) -> Any: + """Referenced symbols must be appropriately resolved before they can + provide information such as size This is because the size request has + no context within which to determine the actual symbol structure.""" + type_name = self.vol.type_name.split(constants.BANG) + table_name = None + if len(type_name) == 2: + table_name = type_name[0] + symbol_name = type_name[-1] + raise exceptions.SymbolError( + symbol_name, table_name, + "Template contains no information about its structure: {}".format(self.vol.type_name)) + + size = property(_unresolved) # type: ClassVar[Any] + replace_child = _unresolved # type: ClassVar[Any] + relative_child_offset = _unresolved # type: ClassVar[Any] + has_member = _unresolved # type: ClassVar[Any] + + def __call__(self, context: interfaces.context.ContextInterface, object_info: interfaces.objects.ObjectInformation): + template = context.symbol_space.get_type(self.vol.type_name) + return template(context = context, object_info = object_info) diff --git a/app/parsers/vol_Parser/volatility/framework/objects/utility.py b/app/parsers/vol_Parser/volatility/framework/objects/utility.py new file mode 100644 index 00000000..e3795aa7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/objects/utility.py @@ -0,0 +1,44 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Optional, Union + +from volatility.framework import interfaces, objects, constants + + +def array_to_string(array: 'objects.Array', + count: Optional[int] = None, + errors: str = 'replace') -> interfaces.objects.ObjectInterface: + """Takes a volatility Array of characters and returns a string.""" + # TODO: Consider checking the Array's target is a native char + if count is None: + count = array.vol.count + if not isinstance(array, objects.Array): + raise TypeError("Array_to_string takes an Array of char") + + return array.cast("string", max_length = count, errors = errors) + + +def pointer_to_string(pointer: 'objects.Pointer', count: int, errors: str = 'replace'): + """Takes a volatility Pointer to characters and returns a string.""" + if not isinstance(pointer, objects.Pointer): + raise TypeError("pointer_to_string takes a Pointer") + if count < 1: + raise ValueError("pointer_to_string requires a positive count") + char = pointer.dereference() + return char.cast("string", max_length = count, errors = errors) + + +def array_of_pointers(array: interfaces.objects.ObjectInterface, count: int, + subtype: Union[str, interfaces.objects.Template], + context: interfaces.context.ContextInterface) -> interfaces.objects.ObjectInterface: + """Takes an object, and recasts it as an array of pointers to subtype.""" + symbol_table = array.vol.type_name.split(constants.BANG)[0] + if isinstance(subtype, str) and context is not None: + subtype = context.symbol_space.get_type(subtype) + if not isinstance(subtype, interfaces.objects.Template) or subtype is None: + raise TypeError("Subtype must be a valid template (or string name of an object template)") + subtype_pointer = context.symbol_space.get_type(symbol_table + constants.BANG + "pointer") + subtype_pointer.update_vol(subtype = subtype) + return array.cast("array", count = count, subtype = subtype_pointer) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/__init__.py b/app/parsers/vol_Parser/volatility/framework/plugins/__init__.py new file mode 100644 index 00000000..b00b4ea4 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/__init__.py @@ -0,0 +1,54 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All core generic plugins. + +These modules should only be imported from volatility.plugins NOT +volatility.framework.plugins +""" + +import logging +from typing import List, Type + +from volatility.framework import interfaces, automagic, exceptions, constants + +vollog = logging.getLogger(__name__) + + +def construct_plugin(context: interfaces.context.ContextInterface, + automagics: List[interfaces.automagic.AutomagicInterface], + plugin: Type[interfaces.plugins.PluginInterface], base_config_path: str, + progress_callback: constants.ProgressCallback, + open_method: Type[interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.PluginInterface: + """Constructs a plugin object based on the parameters. + + Clever magic figures out how to fulfill each requirement that might not be fulfilled + + Args: + context: The volatility context to operate on + automagics: A list of automagic modules to run to augment the context + plugin: The plugin to run + base_config_path: The path within the context's config containing the plugin's configuration + progress_callback: Callback function to provide feedback for ongoing processes + open_method: class to provide context manager for opening the file + + Returns: + The constructed plugin object + """ + errors = automagic.run(automagics, context, plugin, base_config_path, progress_callback = progress_callback) + # Plugins always get their configuration stored under their plugin name + plugin_config_path = interfaces.configuration.path_join(base_config_path, plugin.__name__) + + # Check all the requirements and/or go back to the automagic step + unsatisfied = plugin.unsatisfied(context, plugin_config_path) + if unsatisfied: + for error in errors: + error_string = [x for x in error.format_exception_only()][-1] + vollog.warning("Automagic exception occurred: {}".format(error_string[:-1])) + vollog.log(constants.LOGLEVEL_V, "".join(error.format(chain = True))) + raise exceptions.UnsatisfiedException(unsatisfied) + + constructed = plugin(context, plugin_config_path, progress_callback = progress_callback) + if open_method: + constructed.set_open_method(open_method) + return constructed diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/banners.py b/app/parsers/vol_Parser/volatility/framework/plugins/banners.py new file mode 100644 index 00000000..b7b439aa --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/banners.py @@ -0,0 +1,50 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import interfaces, renderers, layers +from volatility.framework.configuration import requirements +from volatility.framework.layers import scanners +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class Banners(interfaces.plugins.PluginInterface): + """Attempts to identify potential linux banners in an image""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [requirements.TranslationLayerRequirement(name = 'primary', description = 'Memory layer to scan')] + + def _generator(self): + layer = self.context.layers[self.config['primary']] + if isinstance(layer, layers.intel.Intel): + layer = self.context.layers[layer.config['memory_layer']] + for offset, banner in self.locate_banners(self.context, layer.name): + yield 0, (offset, banner) + + @classmethod + def locate_banners(cls, context: interfaces.context.ContextInterface, layer_name: str): + """Identifies banners from a memory image""" + layer = context.layers[layer_name] + for offset in layer.scan( + context = context, + scanner = scanners.RegExScanner(rb"(Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+")): + data = layer.read(offset, 0xfff) + data_index = data.find(b'\x00') + if data_index > 0: + data = data[:data_index].strip() + failed = [ + char for char in data + if char not in b' #()+,;/-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~' + ] + if not failed: + yield format_hints.Hex(offset), str(data, encoding = 'latin-1', errors = '?') + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Banner", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/configwriter.py b/app/parsers/vol_Parser/volatility/framework/plugins/configwriter.py new file mode 100644 index 00000000..3d44edfe --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/configwriter.py @@ -0,0 +1,51 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import json +import logging +from typing import List + +from volatility.framework import renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins + +vollog = logging.getLogger(__name__) + + +class ConfigWriter(plugins.PluginInterface): + """Runs the automagics and both prints and outputs configuration in the + output directory.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.BooleanRequirement(name = 'extra', + description = 'Outputs whole configuration tree', + default = False, + optional = True) + ] + + def _generator(self): + filename = "config.json" + config = dict(self.build_configuration()) + if self.config.get('extra', False): + vollog.debug("Outputting additional information, this will NOT work with the -c option") + config = dict(self.context.config) + filename = "config.extra" + try: + with self.open(filename) as file_data: + file_data.write(bytes(json.dumps(config, sort_keys = True, indent = 2), 'raw_unicode_escape')) + except Exception as excp: + vollog.warning("Unable to JSON encode configuration: {}".format(excp)) + + for k, v in config.items(): + yield (0, (k, json.dumps(v))) + + def run(self): + return renderers.TreeGrid([("Key", str), ("Value", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/frameworkinfo.py b/app/parsers/vol_Parser/volatility/framework/plugins/frameworkinfo.py new file mode 100644 index 00000000..52e85d0a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/frameworkinfo.py @@ -0,0 +1,34 @@ +from typing import List + +from volatility import framework +from volatility.framework import interfaces, renderers +from volatility.framework.interfaces import plugins + + +class FrameworkInfo(plugins.PluginInterface): + """Plugin to list the various modular components of Volatility""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [] + + def _generator(self): + categories = { + 'Automagic': interfaces.automagic.AutomagicInterface, + 'Requirement': interfaces.configuration.RequirementInterface, + 'Layer': interfaces.layers.DataLayerInterface, + 'LayerStacker': interfaces.automagic.StackerLayerInterface, + 'Object': interfaces.objects.ObjectInterface, + 'Plugin': interfaces.plugins.PluginInterface, + 'Renderer': interfaces.renderers.Renderer + } + + for category, module_interface in categories.items(): + yield (0, (category, )) + for clazz in framework.class_subclasses(module_interface): + yield (1, (clazz.__name__, )) + + def run(self): + return renderers.TreeGrid([("Data", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/isfinfo.py b/app/parsers/vol_Parser/volatility/framework/plugins/isfinfo.py new file mode 100644 index 00000000..41cecc1e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/isfinfo.py @@ -0,0 +1,130 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import base64 +import json +import logging +import os +import pathlib +import zipfile +from typing import List, Type, Any, Generator + +from volatility import schemas, symbols +from volatility.framework import interfaces, renderers, constants +from volatility.framework.automagic import mac, linux, symbol_cache +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.layers import resources + +vollog = logging.getLogger(__name__) + + +class IsfInfo(plugins.PluginInterface): + """Determines information about the currently available ISF files, or a specific one""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ListRequirement(name = 'filter', + description = 'String that must be present in the file URI to display the ISF', + optional = True, + default = []), + requirements.URIRequirement(name = 'isf', + description = "Specific ISF file to process", + default = None, + optional = True), + requirements.BooleanRequirement(name = 'validate', + description = 'Validate against schema if possible', + default = False, + optional = True) + ] + + @classmethod + def list_all_isf_files(cls) -> Generator[str, None, None]: + """Lists all the ISF files that can be found""" + for symbol_path in symbols.__path__: + for root, dirs, files in os.walk(symbol_path, followlinks = True): + for filename in files: + base_name = os.path.join(root, filename) + if filename.endswith('zip'): + with zipfile.ZipFile(base_name, 'r') as zfile: + for name in zfile.namelist(): + for extension in constants.ISF_EXTENSIONS: + # By ending with an extension (and therefore, not /), we should not return any directories + if name.endswith(extension): + yield "jar:file:" + str(pathlib.Path(base_name)) + "!" + name + + else: + for extension in constants.ISF_EXTENSIONS: + if filename.endswith(extension): + yield pathlib.Path(base_name).as_uri() + + def _get_banner(self, clazz: Type[symbol_cache.SymbolBannerCache], data: Any) -> str: + """Gets a banner from an ISF file""" + banner_symbol = data.get('symbols', {}).get(clazz.symbol_name, {}).get('constant_data', + renderers.NotAvailableValue()) + if not isinstance(banner_symbol, interfaces.renderers.BaseAbsentValue): + banner_symbol = str(base64.b64decode(banner_symbol), encoding = 'latin-1') + return banner_symbol + + def _generator(self): + if self.config.get('isf', None) is not None: + file_list = [self.config['isf']] + else: + file_list = list(self.list_all_isf_files()) + + # Filter the files + filtered_list = [] + if not len(self.config['filter']): + filtered_list = file_list + else: + for isf_file in file_list: + for filter_item in self.config['filter']: + if filter_item in isf_file: + filtered_list.append(isf_file) + + try: + import jsonschema + if not self.config['validate']: + raise ImportError # Act as if we couldn't import if validation is turned off + + def check_valid(data): + return "True" if schemas.validate(data, True) else "False" + except ImportError: + + def check_valid(data): + return "Unknown" + + # Process the filtered list + for entry in filtered_list: + num_types = num_enums = num_bases = num_symbols = 0 + windows_info = linux_banner = mac_banner = renderers.NotAvailableValue() + valid = "Unknown" + with resources.ResourceAccessor().open(url = entry) as fp: + try: + data = json.load(fp) + num_symbols = len(data.get('symbols', [])) + num_types = len(data.get('user_types', [])) + num_enums = len(data.get('enums', [])) + num_bases = len(data.get('base_types', [])) + + linux_banner = self._get_banner(linux.LinuxBannerCache, data) + mac_banner = self._get_banner(mac.MacBannerCache, data) + if not linux_banner and not mac_banner: + windows_info = os.path.splitext(os.path.basename(entry))[0] + valid = check_valid(data) + except (UnicodeDecodeError, json.decoder.JSONDecodeError): + vollog.warning("Invalid ISF: {}".format(entry)) + yield (0, (entry, valid, num_bases, num_types, num_symbols, num_enums, windows_info, linux_banner, + mac_banner)) + + # Try to open the file, load it as JSON, read the data from it + + def run(self): + return renderers.TreeGrid([("URI", str), ("Valid", str), + ("Number of base_types", int), ("Number of types", int), ("Number of symbols", int), + ("Number of enums", int), ("Windows info", str), ("Linux banner", str), + ("Mac banner", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/layerwriter.py b/app/parsers/vol_Parser/volatility/framework/plugins/layerwriter.py new file mode 100644 index 00000000..05d2c403 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/layerwriter.py @@ -0,0 +1,121 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List, Optional, Type + +from volatility.framework import renderers, interfaces, constants, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins + +vollog = logging.getLogger(__name__) + + +class LayerWriter(plugins.PluginInterface): + """Runs the automagics and writes out the primary layer produced by the stacker.""" + + default_block_size = 0x500000 + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.IntRequirement(name = 'block_size', + description = "Size of blocks to copy over", + default = cls.default_block_size, + optional = True), + requirements.BooleanRequirement(name = 'list', + description = 'List available layers', + default = False, + optional = True), + requirements.ListRequirement(name = 'layers', + element_type = str, + description = 'Names of layer to write', + default = None, + optional = True) + ] + + @classmethod + def write_layer( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + preferred_name: str, + open_method: Type[plugins.FileHandlerInterface], + chunk_size: Optional[int] = None, + progress_callback: Optional[constants.ProgressCallback] = None) -> Optional[plugins.FileHandlerInterface]: + """Produces a FileHandler from the named layer in the provided context or None on failure + + Args: + context: the context from which to read the memory layer + layer_name: the name of the layer to write out + preferred_name: a string with the preferred filename for hte file + chunk_size: an optional size for the chunks that should be written (defaults to 0x500000) + open_method: class for creating FileHandler context managers + progress_callback: an optional function that takes a percentage and a string that displays output + """ + + if layer_name not in context.layers: + raise exceptions.LayerException("Layer not found") + layer = context.layers[layer_name] + + if chunk_size is None: + chunk_size = cls.default_block_size + + file_handle = open_method(preferred_name) + for i in range(0, layer.maximum_address, chunk_size): + current_chunk_size = min(chunk_size, layer.maximum_address - i) + data = layer.read(i, current_chunk_size, pad = True) + file_handle.write(data) + if progress_callback: + progress_callback((i / layer.maximum_address) * 100, 'Writing layer {}'.format(layer_name)) + return file_handle + + def _generator(self): + if self.config['list']: + for name in self.context.layers: + yield 0, (name,) + else: + import pdb + pdb.set_trace() + # Choose the most recently added layer that isn't virtual + if self.config['layers'] is None: + self.config['layers'] = [] + for name in self.context.layers: + if not self.context.layers[name].metadata.get('mapped', False): + self.config['layers'] = [name] + + for name in self.config['layers']: + # Check the layer exists and validate the output file + if name not in self.context.layers: + yield 0, ('Layer Name {} does not exist'.format(name),) + else: + output_name = self.config.get('output', ".".join([name, "raw"])) + try: + file_handle = self.write_layer(self.context, + name, + output_name, + self.open, + self.config.get('block_size', self.default_block_size), + progress_callback = self._progress_callback) + file_handle.close() + except IOError as excp: + yield 0, ('Layer cannot be written to {}: {}'.format(self.config['output_name'], excp),) + + yield 0, ('Layer has been written to {}'.format(output_name),) + + def _generate_layers(self): + """List layer names from this run""" + for name in self.context.layers: + yield (0, (name,)) + + def run(self): + if self.config['list']: + return renderers.TreeGrid([("Layer name", str)], self._generate_layers()) + return renderers.TreeGrid([("Status", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/__init__.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/__init__.py new file mode 100644 index 00000000..24e0d3c1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/__init__.py @@ -0,0 +1,8 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All core linux plugins. + +These modules should only be imported from volatility.plugins NOT +volatility.framework.plugins +""" diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/bash.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/bash.py new file mode 100644 index 00000000..82fdd90d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/bash.py @@ -0,0 +1,109 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" + +import datetime +import struct +from typing import List + +from volatility.framework import constants, renderers, symbols, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.layers import scanners +from volatility.framework.objects import utility +from volatility.framework.symbols.linux.bash import BashIntermedSymbols +from volatility.plugins import timeliner +from volatility.plugins.linux import pslist + + +class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): + """Recovers bash command history from memory.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True) + ] + + def _generator(self, tasks): + is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["vmlinux"]) + if is_32bit: + pack_format = "I" + bash_json_file = "bash32" + else: + pack_format = "Q" + bash_json_file = "bash64" + + bash_table_name = BashIntermedSymbols.create(self.context, self.config_path, "linux", bash_json_file) + + ts_offset = self.context.symbol_space.get_type(bash_table_name + constants.BANG + + "hist_entry").relative_child_offset("timestamp") + + for task in tasks: + task_name = utility.array_to_string(task.comm) + if task_name not in ["bash", "sh", "dash"]: + continue + + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + proc_layer = self.context.layers[proc_layer_name] + + bang_addrs = [] + + # find '#' values on the heap + for address in proc_layer.scan(self.context, + scanners.BytesScanner(b"#"), + sections = task.get_process_memory_sections(heap_only = True)): + bang_addrs.append(struct.pack(pack_format, address)) + + history_entries = [] + + for address, _ in proc_layer.scan(self.context, + scanners.MultiStringScanner(bang_addrs), + sections = task.get_process_memory_sections(heap_only = True)): + hist = self.context.object(bash_table_name + constants.BANG + "hist_entry", + offset = address - ts_offset, + layer_name = proc_layer_name) + + if hist.is_valid(): + history_entries.append(hist) + + for hist in sorted(history_entries, key = lambda x: x.get_time_as_integer()): + yield (0, (task.pid, task_name, hist.get_time_object(), hist.get_command())) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("CommandTime", datetime.datetime), + ("Command", str)], + self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func))) + + def generate_timeline(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + for row in self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func)): + _depth, row_data = row + description = "{} ({}): \"{}\"".format(row_data[0], row_data[1], row_data[3]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[2]) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_afinfo.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_afinfo.py new file mode 100644 index 00000000..c3361b96 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_afinfo.py @@ -0,0 +1,91 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" +import logging +from typing import List + +from volatility.framework import exceptions, interfaces, contexts +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class Check_afinfo(plugins.PluginInterface): + """Verifies the operation function pointers of network protocols.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols") + ] + + # returns whether the symbol is found within the kernel (system.map) or not + def _is_known_address(self, handler_addr): + symbols = list(self.context.symbol_space.get_symbols_by_location(handler_addr)) + + return len(symbols) > 0 + + def _check_members(self, var_ops, var_name, members): + for check in members: + # redhat-specific garbage + if check.startswith("__UNIQUE_ID_rh_kabi_hide"): + continue + + if check == "write": + addr = var_ops.member(attr = 'write') + else: + addr = getattr(var_ops, check) + + if addr and addr != 0 and not self._is_known_address(addr): + yield check, addr + + def _check_afinfo(self, var_name, var, op_members, seq_members): + for hooked_member, hook_address in self._check_members(var.seq_fops, var_name, op_members): + yield var_name, hooked_member, hook_address + + # newer kernels + if var.has_member("seq_ops"): + for hooked_member, hook_address in self._check_members(var.seq_ops, var_name, seq_members): + yield var_name, hooked_member, hook_address + + # this is the most commonly hooked member by rootkits, so a force a check on it + elif not self._is_known_address(var.seq_show): + yield var_name, "show", var.seq_show + + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + op_members = vmlinux.get_type('file_operations').members + seq_members = vmlinux.get_type('seq_operations').members + + tcp = ("tcp_seq_afinfo", ["tcp6_seq_afinfo", "tcp4_seq_afinfo"]) + udp = ("udp_seq_afinfo", ["udplite6_seq_afinfo", "udp6_seq_afinfo", "udplite4_seq_afinfo", "udp4_seq_afinfo"]) + protocols = [tcp, udp] + + for (struct_type, global_vars) in protocols: + for global_var_name in global_vars: + # this will lookup fail for the IPv6 protocols on kernels without IPv6 support + try: + global_var = vmlinux.get_symbol(global_var_name) + except exceptions.SymbolError: + continue + + global_var = vmlinux.object(object_type = struct_type, offset = global_var.address) + + for name, member, address in self._check_afinfo(global_var_name, global_var, op_members, seq_members): + yield 0, (name, member, format_hints.Hex(address)) + + def run(self): + + return renderers.TreeGrid([("Symbol Name", str), ("Member", str), ("Handler Address", format_hints.Hex)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_creds.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_creds.py new file mode 100644 index 00000000..7d68bf64 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_creds.py @@ -0,0 +1,63 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from volatility.framework import interfaces, renderers, constants +from volatility.framework.configuration import requirements +from volatility.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +class Check_creds(interfaces.plugins.PluginInterface): + """Checks if any processes are sharing credential structures""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)) + ] + + def _generator(self): + # vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + type_task = self.context.symbol_space.get_type(self.config['vmlinux'] + constants.BANG + "task_struct") + + if not type_task.has_member("cred"): + raise TypeError( + "This plugin requires the task_struct structure to have a cred member. " + "This member is not present in the supplied symbol table. " + "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) + + creds = {} + + tasks = pslist.PsList.list_tasks(self.context, self.config['primary'], self.config['vmlinux']) + + for task in tasks: + + cred_addr = task.cred.dereference().vol.offset + + if not cred_addr in creds: + creds[cred_addr] = [] + + creds[cred_addr].append(task.pid) + + for (_, pids) in creds.items(): + if len(pids) > 1: + pid_str = "" + for pid in pids: + pid_str = pid_str + "{0:d}, ".format(pid) + pid_str = pid_str[:-2] + yield (0, [str(pid_str)]) + + def run(self): + return renderers.TreeGrid([("PIDs", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_idt.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_idt.py new file mode 100644 index 00000000..4da33e8d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_idt.py @@ -0,0 +1,97 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List + +from volatility.framework import interfaces, renderers, contexts, symbols +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import linux +from volatility.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +class Check_idt(interfaces.plugins.PluginInterface): + """ Checks if the IDT has been altered """ + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.VersionRequirement(name = 'linuxutils', component = linux.LinuxUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + modules = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['vmlinux']) + + handlers = linux.LinuxUtilities.generate_kernel_handler_info(self.context, self.config['primary'], + self.config['vmlinux'], modules) + + is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["vmlinux"]) + + idt_table_size = 256 + + address_mask = self.context.layers[self.config['primary']].address_mask + + # hw handlers + system call + check_idxs = list(range(0, 20)) + [128] + + if is_32bit: + if vmlinux.has_type("gate_struct"): + idt_type = "gate_struct" + else: + idt_type = "desc_struct" + else: + if vmlinux.has_type("gate_struct64"): + idt_type = "gate_struct64" + elif vmlinux.has_type("gate_struct"): + idt_type = "gate_struct" + else: + idt_type = "idt_desc" + + addrs = vmlinux.object_from_symbol("idt_table") + + table = vmlinux.object(object_type = 'array', + offset = addrs.vol.offset, + subtype = vmlinux.get_type(idt_type), + count = idt_table_size) + + for i in check_idxs: + ent = table[i] + + if not ent: + continue + + if hasattr(ent, "Address"): + idt_addr = ent.Address + else: + low = ent.offset_low + middle = ent.offset_middle + + if hasattr(ent, "offset_high"): + high = ent.offset_high + else: + high = 0 + + idt_addr = (high << 32) | (middle << 16) | low + + idt_addr = idt_addr & address_mask + + module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(self.context, handlers, idt_addr) + + yield (0, [format_hints.Hex(i), format_hints.Hex(idt_addr), module_name, symbol_name]) + + def run(self): + return renderers.TreeGrid([("Index", format_hints.Hex), ("Address", format_hints.Hex), ("Module", str), + ("Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_modules.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_modules.py new file mode 100644 index 00000000..470ce0a2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_modules.py @@ -0,0 +1,75 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List + +from volatility.framework import interfaces, renderers, exceptions, constants, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +class Check_modules(plugins.PluginInterface): + """Compares module list to sysfs info, if available""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def get_kset_modules(self, vmlinux): + + try: + module_kset = vmlinux.object_from_symbol("module_kset") + except exceptions.SymbolError: + module_kset = None + + if not module_kset: + raise TypeError( + "This plugin requires the module_kset structure. This structure is not present in the supplied symbol table. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) + + ret = {} + + kobj_off = self.context.symbol_space.get_type(self.config['vmlinux'] + constants.BANG + + 'module_kobject').relative_child_offset('kobj') + + for kobj in module_kset.list.to_list(vmlinux.name + constants.BANG + "kobject", "entry"): + + mod_kobj = vmlinux.object(object_type = "module_kobject", offset = kobj.vol.offset - kobj_off) + + mod = mod_kobj.mod + + name = utility.pointer_to_string(kobj.name, 32) + if kobj.name and kobj.reference_count() > 2: + ret[name] = mod + + return ret + + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + kset_modules = self.get_kset_modules(vmlinux) + + lsmod_modules = set( + str(utility.array_to_string(modules.name)) + for modules in lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['vmlinux'])) + + for mod_name in set(kset_modules.keys()).difference(lsmod_modules): + yield (0, (format_hints.Hex(kset_modules[mod_name]), str(mod_name))) + + def run(self): + return renderers.TreeGrid([("Module Address", format_hints.Hex), ("Module Name", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_syscall.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_syscall.py new file mode 100644 index 00000000..30843077 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/check_syscall.py @@ -0,0 +1,177 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" +import logging +from typing import List + +from volatility.framework import exceptions, interfaces, contexts +from volatility.framework import renderers, constants +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + +try: + import capstone + + has_capstone = True +except ImportError: + has_capstone = False + + +class Check_syscall(plugins.PluginInterface): + """Check system call table for hooks.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols") + ] + + def _get_table_size_next_symbol(self, table_addr, ptr_sz, vmlinux): + """Returns the size of the table based on the next symbol.""" + ret = 0 + + sym_table = self.context.symbol_space[vmlinux.name] + + sorted_symbols = sorted([(sym_table.get_symbol(sn).address, sn) for sn in sym_table.symbols]) + + sym_address = 0 + + for tmp_sym_address, sym_name in sorted_symbols: + if tmp_sym_address > table_addr: + sym_address = tmp_sym_address + break + + if sym_address > 0: + ret = int((sym_address - table_addr) / ptr_sz) + + return ret + + def _get_table_size_meta(self, vmlinux): + """returns the number of symbols that start with __syscall_meta__ this + is a fast way to determine the number of system calls, but not the most + accurate.""" + + return len( + [sym for sym in self.context.symbol_space[vmlinux.name].symbols if sym.startswith("__syscall_meta__")]) + + def _get_table_info_other(self, table_addr, ptr_sz, vmlinux): + table_size_meta = self._get_table_size_meta(vmlinux) + table_size_syms = self._get_table_size_next_symbol(table_addr, ptr_sz, vmlinux) + + sizes = [size for size in [table_size_meta, table_size_syms] if size > 0] + + table_size = min(sizes) + + return table_size + + def _get_table_info_disassembly(self, ptr_sz, vmlinux): + """Find the size of the system call table by disassembling functions + that immediately reference it in their first isntruction This is in the + form 'cmp reg,NR_syscalls'.""" + table_size = 0 + + if not has_capstone: + return table_size + + if ptr_sz == 4: + syscall_entry_func = "sysenter_do_call" + mode = capstone.CS_MODE_32 + else: + syscall_entry_func = "system_call_fastpath" + mode = capstone.CS_MODE_64 + + md = capstone.Cs(capstone.CS_ARCH_X86, mode) + + try: + func_addr = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + syscall_entry_func).address + except exceptions.SymbolError as e: + # if we can't find the disassemble function then bail and rely on a different method + return 0 + + data = self.context.layers.read(self.config['primary'], func_addr, 6) + + for (address, size, mnemonic, op_str) in md.disasm_lite(data, func_addr): + if mnemonic == 'CMP': + table_size = int(op_str.split(",")[1].strip()) & 0xffff + break + + return table_size + + def _get_table_info(self, vmlinux, table_name, ptr_sz): + table_sym = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + table_name) + + table_size = self._get_table_info_disassembly(ptr_sz, vmlinux) + + if table_size == 0: + table_size = self._get_table_info_other(table_sym.address, ptr_sz, vmlinux) + + if table_size == 0: + vollog.error("Unable to get system call table size") + return 0, 0 + + return table_sym.address, table_size + + # TODO - add finding and parsing unistd.h once cached file enumeration is added + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + ptr_sz = vmlinux.get_type("pointer").size + if ptr_sz == 4: + table_name = "32bit" + else: + table_name = "64bit" + + try: + table_info = self._get_table_info(vmlinux, "sys_call_table", ptr_sz) + except exceptions.SymbolError: + vollog.error("Unable to find the system call table. Exiting.") + return + + tables = [(table_name, table_info)] + + # this table is only present on 64 bit systems with 32 bit emulation + # enabled in order to support 32 bit programs and libraries + # if the symbol isn't there then the support isn't in the kernel and so we skip it + try: + ia32_symbol = self.context.symbol_space.get_symbol(vmlinux.name + constants.BANG + "ia32_sys_call_table") + except exceptions.SymbolError: + ia32_symbol = None + + if ia32_symbol != None: + ia32_info = self._get_table_info(vmlinux, "ia32_sys_call_table", ptr_sz) + tables.append(("32bit", ia32_info)) + + for (table_name, (tableaddr, tblsz)) in tables: + table = vmlinux.object(object_type = "array", + subtype = vmlinux.get_type("pointer"), + offset = tableaddr, + count = tblsz) + + for (i, call_addr) in enumerate(table): + if not call_addr: + continue + + symbols = list(self.context.symbol_space.get_symbols_by_location(call_addr)) + + if len(symbols) > 0: + sym_name = str(symbols[0].split(constants.BANG)[1]) if constants.BANG in symbols[0] else \ + str(symbols[0]) + else: + sym_name = "UNKNOWN" + + yield (0, (format_hints.Hex(tableaddr), table_name, i, format_hints.Hex(call_addr), sym_name)) + + def run(self): + + return renderers.TreeGrid([("Table Address", format_hints.Hex), ("Table Name", str), ("Index", int), + ("Handler Address", format_hints.Hex), ("Handler Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/elfs.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/elfs.py new file mode 100644 index 00000000..da9281ec --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/elfs.py @@ -0,0 +1,64 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" + +from typing import List + +from volatility.framework import renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.linux import pslist + + +class Elfs(plugins.PluginInterface): + """Lists all memory mapped ELF files for all processes.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + for task in tasks: + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + proc_layer = self.context.layers[proc_layer_name] + + name = utility.array_to_string(task.comm) + + for vma in task.mm.get_mmap_iter(): + hdr = proc_layer.read(vma.vm_start, 4, pad = True) + if not (hdr[0] == 0x7f and hdr[1] == 0x45 and hdr[2] == 0x4c and hdr[3] == 0x46): + continue + + path = vma.get_name(self.context, task) + + yield (0, (task.pid, name, format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), path)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex), + ("End", format_hints.Hex), ("File Path", str)], + self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/keyboard_notifiers.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/keyboard_notifiers.py new file mode 100644 index 00000000..7580b368 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/keyboard_notifiers.py @@ -0,0 +1,62 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from volatility.framework import interfaces, renderers, contexts, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import linux +from volatility.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +class Keyboard_notifiers(interfaces.plugins.PluginInterface): + """Parses the keyboard notifier call chain""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'linuxutils', component = linux.LinuxUtilities, version = (1, 0, 0)) + ] + + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + modules = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['vmlinux']) + + handlers = linux.LinuxUtilities.generate_kernel_handler_info(self.context, self.config['primary'], + self.config['vmlinux'], modules) + + try: + knl_addr = vmlinux.object_from_symbol("keyboard_notifier_list") + except exceptions.SymbolError: + knl_addr = None + + if not knl_addr: + raise TypeError( + "This plugin requires the keyboard_notifier_list structure. " + "This structure is not present in the supplied symbol table. " + "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) + + knl = vmlinux.object(object_type = "atomic_notifier_head", offset = knl_addr.vol.offset) + + for call_back in linux.LinuxUtilities.walk_internal_list(vmlinux, "notifier_block", "next", knl.head): + call_addr = call_back.notifier_call + + module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(self.context, handlers, call_addr) + + yield (0, [format_hints.Hex(call_addr), module_name, symbol_name]) + + def run(self): + return renderers.TreeGrid([("Address", format_hints.Hex), ("Module", str), ("Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsmod.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsmod.py new file mode 100644 index 00000000..ada3002a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsmod.py @@ -0,0 +1,75 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" + +import logging +from typing import List, Iterable + +from volatility.framework import contexts +from volatility.framework import exceptions, renderers, constants, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class Lsmod(plugins.PluginInterface): + """Lists loaded kernel modules.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols") + ] + + @classmethod + def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, + vmlinux_symbols: str) -> Iterable[interfaces.objects.ObjectInterface]: + """Lists all the modules in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + vmlinux_symbols: The name of the table containing the kernel symbols + + Yields: + The modules present in the `layer_name` layer's modules list + + This function will throw a SymbolError exception if kernel module support is not enabled. + """ + vmlinux = contexts.Module(context, vmlinux_symbols, layer_name, 0) + + modules = vmlinux.object_from_symbol(symbol_name = "modules").cast("list_head") + + table_name = modules.vol.type_name.split(constants.BANG)[0] + + for module in modules.to_list(table_name + constants.BANG + "module", "list"): + yield module + + def _generator(self): + try: + for module in self.list_modules(self.context, self.config['primary'], self.config['vmlinux']): + + mod_size = module.get_init_size() + module.get_core_size() + + mod_name = utility.array_to_string(module.name) + + yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size) + + except exceptions.SymbolError: + vollog.debug( + "The required symbol 'module' is not present in symbol table. Please check that kernel modules are enabled for the system under analysis." + ) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Name", str), ("Size", int)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsof.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsof.py new file mode 100644 index 00000000..1ea348b1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/lsof.py @@ -0,0 +1,62 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" +import logging +from typing import List + +from volatility.framework import renderers, interfaces, constants +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.symbols import linux +from volatility.plugins.linux import pslist + +vollog = logging.getLogger(__name__) + + +class Lsof(plugins.PluginInterface): + """Lists all memory maps for all processes.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'linuxutils', component = linux.LinuxUtilities, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + symbol_table = None + for task in tasks: + if symbol_table is None: + if constants.BANG not in task.vol.type_name: + raise ValueError("Task is not part of a symbol table") + symbol_table = task.vol.type_name.split(constants.BANG)[0] + + name = utility.array_to_string(task.comm) + pid = int(task.pid) + + for fd_num, _, full_path in linux.LinuxUtilities.files_descriptors_for_process( + self.context, symbol_table, task): + yield (0, (pid, name, fd_num, full_path)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("FD", int), ("Path", str)], + self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/malfind.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/malfind.py new file mode 100644 index 00000000..0da89025 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/malfind.py @@ -0,0 +1,80 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import List + +from volatility.framework import constants, interfaces +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.linux import pslist + + +class Malfind(interfaces.plugins.PluginInterface): + """Lists process memory ranges that potentially contain injected code.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _list_injections(self, task): + """Generate memory regions for a process that may contain injected + code.""" + + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + return + + proc_layer = self.context.layers[proc_layer_name] + + for vma in task.mm.get_mmap_iter(): + if vma.is_suspicious() and vma.get_name(self.context, task) != "[vdso]": + data = proc_layer.read(vma.vm_start, 64, pad = True) + yield vma, data + + def _generator(self, tasks): + # determine if we're on a 32 or 64 bit kernel + if self.context.symbol_space.get_type(self.config["vmlinux"] + constants.BANG + "pointer").size == 4: + is_32bit_arch = True + else: + is_32bit_arch = False + + for task in tasks: + process_name = utility.array_to_string(task.comm) + + for vma, data in self._list_injections(task): + if is_32bit_arch: + architecture = "intel" + else: + architecture = "intel64" + + disasm = interfaces.renderers.Disassembly(data, vma.vm_start, architecture) + + yield (0, (task.pid, process_name, format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), + vma.get_protection(), format_hints.HexBytes(data), disasm)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex), + ("End", format_hints.Hex), ("Protection", str), ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces.renderers.Disassembly)], + self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/proc.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/proc.py new file mode 100644 index 00000000..6b73f235 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/proc.py @@ -0,0 +1,73 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Linux's /proc file system.""" + +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.linux import pslist + + +class Maps(plugins.PluginInterface): + """Lists all memory maps for all processes.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + for task in tasks: + if not task.mm: + continue + + name = utility.array_to_string(task.comm) + + for vma in task.mm.get_mmap_iter(): + flags = vma.get_protection() + page_offset = vma.get_page_offset() + major = 0 + minor = 0 + inode = 0 + + if vma.vm_file != 0: + dentry = vma.vm_file.get_dentry() + if dentry != 0: + inode_object = dentry.d_inode + major = inode_object.i_sb.major + minor = inode_object.i_sb.minor + inode = inode_object.i_ino + + path = vma.get_name(self.context, task) + + yield (0, (task.pid, name, format_hints.Hex(vma.vm_start), format_hints.Hex(vma.vm_end), flags, + format_hints.Hex(page_offset), major, minor, inode, path)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), + ("Start", format_hints.Hex), ("End", format_hints.Hex), ("Flags", str), + ("PgOff", format_hints.Hex), ("Major", int), ("Minor", int), ("Inode", int), + ("File Path", str)], + self._generator( + pslist.PsList.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/pslist.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/pslist.py new file mode 100644 index 00000000..8a60d58c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/pslist.py @@ -0,0 +1,92 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Callable, Iterable, List, Any + +from volatility.framework import renderers, interfaces, contexts +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility + + +class PsList(interfaces.plugins.PluginInterface): + """Lists the processes present in a particular linux memory image.""" + + _required_framework_version = (2, 0, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + @classmethod + def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[Any], bool]: + """Constructs a filter function for process IDs. + + Args: + pid_list: List of process IDs that are acceptable (or None if all are acceptable) + + Returns: + Function which, when provided a process object, returns True if the process is to be filtered out of the list + """ + # FIXME: mypy #4973 or #2608 + pid_list = pid_list or [] + filter_list = [x for x in pid_list if x is not None] + if filter_list: + + def filter_func(x): + return x.pid not in filter_list + + return filter_func + else: + return lambda _: False + + def _generator(self): + for task in self.list_tasks(self.context, + self.config['primary'], + self.config['vmlinux'], + filter_func = self.create_pid_filter(self.config.get('pid', None))): + pid = task.pid + ppid = 0 + if task.parent: + ppid = task.parent.pid + name = utility.array_to_string(task.comm) + yield (0, (pid, ppid, name)) + + @classmethod + def list_tasks( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + vmlinux_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + vmlinux_symbols: The name of the table containing the kernel symbols + + Yields: + Process objects + """ + vmlinux = contexts.Module(context, vmlinux_symbols, layer_name, 0) + + init_task = vmlinux.object_from_symbol(symbol_name = "init_task") + + for task in init_task.tasks: + if not filter_func(task): + yield task + + def run(self): + return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/pstree.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/pstree.py new file mode 100644 index 00000000..c1e83295 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/pstree.py @@ -0,0 +1,56 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework.objects import utility +from volatility.plugins.linux import pslist + + +class PsTree(pslist.PsList): + """Plugin for listing processes in a tree based on their parent process + ID.""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._processes = {} + self._levels = {} + self._children = {} + + def find_level(self, pid): + """Finds how deep the pid is in the processes list.""" + seen = set([]) + seen.add(pid) + level = 0 + proc = self._processes.get(pid, None) + while proc is not None and proc.parent != 0 and proc.parent.pid not in seen: + ppid = int(proc.parent.pid) + + child_list = self._children.get(ppid, set([])) + child_list.add(proc.pid) + self._children[ppid] = child_list + proc = self._processes.get(ppid, None) + level += 1 + self._levels[pid] = level + + def _generator(self): + """Generates the.""" + for proc in self.list_tasks(self.context, self.config['primary'], self.config['vmlinux']): + self._processes[proc.pid] = proc + + # Build the child/level maps + for pid in self._processes: + self.find_level(pid) + + def yield_processes(pid): + proc = self._processes[pid] + row = (proc.pid, proc.parent.pid, utility.array_to_string(proc.comm)) + + yield (self._levels[pid] - 1, row) + for child_pid in self._children.get(pid, []): + yield from yield_processes(child_pid) + + for pid in self._levels: + if self._levels[pid] == 1: + yield from yield_processes(pid) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/linux/tty_check.py b/app/parsers/vol_Parser/volatility/framework/plugins/linux/tty_check.py new file mode 100644 index 00000000..90959d6a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/linux/tty_check.py @@ -0,0 +1,80 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List + +from volatility.framework import interfaces, renderers, exceptions, constants, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import linux +from volatility.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +class tty_check(plugins.PluginInterface): + """Checks tty devices for hooks""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "vmlinux", description = "Linux kernel symbols"), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'linuxutils', component = linux.LinuxUtilities, version = (1, 0, 0)) + ] + + def _generator(self): + vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) + + modules = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['vmlinux']) + + handlers = linux.LinuxUtilities.generate_kernel_handler_info(self.context, self.config['primary'], + self.config['vmlinux'], modules) + + try: + tty_drivers = vmlinux.object_from_symbol("tty_drivers") + except exceptions.SymbolError: + tty_drivers = None + + if not tty_drivers: + raise TypeError( + "This plugin requires the tty_drivers structure." + "This structure is not present in the supplied symbol table." + "This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) + + for tty in tty_drivers.to_list(vmlinux.name + constants.BANG + "tty_driver", "tty_drivers"): + + try: + ttys = utility.array_of_pointers(tty.ttys.dereference(), + count = tty.num, + subtype = vmlinux.name + constants.BANG + "tty_struct", + context = self.context) + except exceptions.PagedInvalidAddressException: + continue + + for tty_dev in ttys: + + if tty_dev == 0: + continue + + name = utility.array_to_string(tty_dev.name) + + recv_buf = tty_dev.ldisc.ops.receive_buf + + module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(self.context, handlers, recv_buf) + + yield (0, (name, format_hints.Hex(recv_buf), module_name, symbol_name)) + + def run(self): + return renderers.TreeGrid([("Name", str), ("Address", format_hints.Hex), ("Module", str), ("Symbol", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/__init__.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/bash.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/bash.py new file mode 100644 index 00000000..9c923d33 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/bash.py @@ -0,0 +1,111 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in mac's /proc file system.""" + +import datetime +import struct + +from volatility.framework import constants, renderers, symbols +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.layers import scanners +from volatility.framework.objects import utility +from volatility.framework.symbols.linux.bash import BashIntermedSymbols +from volatility.plugins import timeliner +from volatility.plugins.mac import pslist + + +class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface): + """Recovers bash command history from memory.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["darwin"]) + if is_32bit: + pack_format = "I" + bash_json_file = "bash32" + else: + pack_format = "Q" + bash_json_file = "bash64" + + bash_table_name = BashIntermedSymbols.create(self.context, self.config_path, "linux", bash_json_file) + + ts_offset = self.context.symbol_space.get_type(bash_table_name + constants.BANG + + "hist_entry").relative_child_offset("timestamp") + + for task in tasks: + task_name = utility.array_to_string(task.p_comm) + if task_name not in ["bash", "sh", "dash"]: + continue + + proc_layer_name = task.add_process_layer() + if proc_layer_name is None: + continue + + proc_layer = self.context.layers[proc_layer_name] + + bang_addrs = [] + + # find '#' values on the heap + for address in proc_layer.scan(self.context, + scanners.BytesScanner(b"#"), + sections = task.get_process_memory_sections(self.context, + self.config['darwin'], + rw_no_file = True)): + bang_addrs.append(struct.pack(pack_format, address)) + + history_entries = [] + + for address, _ in proc_layer.scan(self.context, + scanners.MultiStringScanner(bang_addrs), + sections = task.get_process_memory_sections(self.context, + self.config['darwin'], + rw_no_file = True)): + hist = self.context.object(bash_table_name + constants.BANG + "hist_entry", + offset = address - ts_offset, + layer_name = proc_layer_name) + + if hist.is_valid(): + history_entries.append(hist) + + for hist in sorted(history_entries, key = lambda x: x.get_time_as_integer()): + yield (0, (int(task.p_pid), task_name, hist.get_time_object(), hist.get_command())) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("CommandTime", datetime.datetime), + ("Command", str)], + self._generator( + list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func))) + + def generate_timeline(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + for row in self._generator( + list_tasks(self.context, self.config['primary'], self.config['darwin'], filter_func = filter_func)): + _depth, row_data = row + description = "{} ({}): \"{}\"".format(row_data[0], row_data[1], row_data[3]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[2]) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_syscall.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_syscall.py new file mode 100644 index 00000000..f1e0dc5d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_syscall.py @@ -0,0 +1,66 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Check_syscall(plugins.PluginInterface): + """Check system call table for hooks.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _generator(self): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + nsysent = kernel.object_from_symbol(symbol_name = "nsysent") + table = kernel.object_from_symbol(symbol_name = "sysent") + + # smear help + num_ents = min(nsysent, table.count) + if num_ents > 1024: + num_ents = 1024 + + for (i, ent) in enumerate(table): + try: + call_addr = ent.sy_call.dereference().vol.offset + except exceptions.InvalidAddressException: + continue + + if not call_addr or call_addr == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr) + + yield (0, (format_hints.Hex(table.vol.offset), "SysCall", i, format_hints.Hex(call_addr), module_name, + symbol_name)) + + def run(self): + return renderers.TreeGrid([("Table Address", format_hints.Hex), ("Table Name", str), ("Index", int), + ("Handler Address", format_hints.Hex), ("Handler Module", str), + ("Handler Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_sysctl.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_sysctl.py new file mode 100644 index 00000000..5923430b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_sysctl.py @@ -0,0 +1,140 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +import volatility +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Check_sysctl(plugins.PluginInterface): + """Check sysctl handlers for hooks.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _parse_global_variable_sysctls(self, kernel, name): + known_sysctls = { + "hostname": "hostname", + "nisdomainname": "domainname", + } + + var_str = "" + + if name in known_sysctls: + var_name = known_sysctls[name] + + try: + var_array = kernel.object_from_symbol(symbol_name = var_name) + except exceptions.SymbolError: + var_array = None + + if var_array is not None: + var_str = utility.array_to_string(var_array) + + return var_str + + def _process_sysctl_list(self, kernel, sysctl_list, recursive = 0): + if type(sysctl_list) == volatility.framework.objects.Pointer: + sysctl_list = sysctl_list.dereference().cast("sysctl_oid_list") + + sysctl = sysctl_list.slh_first + + if recursive != 0: + try: + sysctl = sysctl.oid_link.sle_next.dereference() + except exceptions.InvalidAddressException: + return + + while sysctl: + try: + name = utility.pointer_to_string(sysctl.oid_name, 128) + except exceptions.InvalidAddressException: + name = "" + + if len(name) == 0: + break + + ctltype = sysctl.get_ctltype() + + try: + arg1_ptr = sysctl.oid_arg1.dereference().vol.offset + except exceptions.InvalidAddressException: + arg1_ptr = 0 + + arg1 = sysctl.oid_arg1 + + if arg1 == 0 or arg1_ptr == 0: + val = self._parse_global_variable_sysctls(kernel, name) + elif ctltype == 'CTLTYPE_NODE': + if sysctl.oid_handler == 0: + for info in self._process_sysctl_list(kernel, sysctl.oid_arg1, recursive = 1): + yield info + + val = "Node" + + elif ctltype in ['CTLTYPE_INT', 'CTLTYPE_QUAD', 'CTLTYPE_OPAQUE']: + try: + val = str(arg1.dereference().cast("int")) + except exceptions.InvalidAddressException: + val = "-1" + + elif ctltype == 'CTLTYPE_STRING': + try: + val = utility.pointer_to_string(sysctl.oid_arg1, 64) + except exceptions.InvalidAddressException: + val = "" + else: + val = ctltype + + yield (sysctl, name, val) + + try: + sysctl = sysctl.oid_link.sle_next + except exceptions.InvalidAddressException: + break + + def _generator(self): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + sysctl_list = kernel.object_from_symbol(symbol_name = "sysctl__children") + + for sysctl, name, val in self._process_sysctl_list(kernel, sysctl_list): + try: + check_addr = sysctl.oid_handler + except exceptions.InvalidAddressException: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, check_addr) + + yield (0, (name, sysctl.oid_number, sysctl.get_perms(), format_hints.Hex(check_addr), val, module_name, + symbol_name)) + + def run(self): + return renderers.TreeGrid([("Name", str), ("Number", int), ("Perms", str), + ("Handler Address", format_hints.Hex), ("Value", str), ("Handler Module", str), + ("Handler Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_trap_table.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_trap_table.py new file mode 100644 index 00000000..99b509f4 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/check_trap_table.py @@ -0,0 +1,61 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List + +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Check_trap_table(plugins.PluginInterface): + """Check mach trap table for hooks.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + ] + + def _generator(self): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + table = kernel.object_from_symbol(symbol_name = "mach_trap_table") + + for i, ent in enumerate(table): + try: + call_addr = ent.mach_trap_function.dereference().vol.offset + except exceptions.InvalidAddressException: + continue + + if not call_addr or call_addr == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr) + + yield (0, (format_hints.Hex(table.vol.offset), "TrapTable", i, format_hints.Hex(call_addr), module_name, + symbol_name)) + + def run(self): + return renderers.TreeGrid([("Table Address", format_hints.Hex), ("Table Name", str), ("Index", int), + ("Handler Address", format_hints.Hex), ("Handler Module", str), + ("Handler Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/ifconfig.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/ifconfig.py new file mode 100644 index 00000000..8bcfe737 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/ifconfig.py @@ -0,0 +1,52 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +from volatility.framework import exceptions, renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.symbols import mac + + +class Ifconfig(plugins.PluginInterface): + """Lists loaded kernel modules""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)) + ] + + def _generator(self): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + try: + list_head = kernel.object_from_symbol(symbol_name = "ifnet_head") + except exceptions.SymbolError: + list_head = kernel.object_from_symbol(symbol_name = "dlil_ifnet_head") + + for ifnet in mac.MacUtilities.walk_tailq(list_head, "if_link"): + name = utility.pointer_to_string(ifnet.if_name, 32) + unit = ifnet.if_unit + prom = ifnet.if_flags & 0x100 == 0x100 # IFF_PROMISC + + sock_addr_dl = ifnet.sockaddr_dl() + if sock_addr_dl is None: + mac_addr = renderers.UnreadableValue() + else: + mac_addr = str(sock_addr_dl) + + for ifaddr in mac.MacUtilities.walk_tailq(ifnet.if_addrhead, "ifa_link"): + ip = ifaddr.ifa_addr.get_address() + + yield (0, ("{0}{1}".format(name, unit), ip, mac_addr, prom)) + + def run(self): + return renderers.TreeGrid([("Interface", str), ("IP Address", str), ("Mac Address", str), + ("Promiscuous", bool)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_listeners.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_listeners.py new file mode 100644 index 00000000..ec784f58 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_listeners.py @@ -0,0 +1,59 @@ +# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import renderers, interfaces, contexts +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod, kauth_scopes + + +class Kauth_listeners(interfaces.plugins.PluginInterface): + """ Lists kauth listeners and their status """ + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'kauth_scopes', + plugin = kauth_scopes.Kauth_scopes, + version = (1, 0, 0)) + ] + + def _generator(self): + """ + Enumerates the listeners for each kauth scope + """ + kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + for scope in kauth_scopes.Kauth_scopes.list_kauth_scopes(self.context, self.config['primary'], + self.config['darwin']): + + scope_name = utility.pointer_to_string(scope.ks_identifier, 128) + + for listener in scope.get_listeners(): + callback = listener.kll_callback + if callback == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, callback) + + yield (0, (scope_name, format_hints.Hex(listener.kll_idata), format_hints.Hex(callback), module_name, + symbol_name)) + + def run(self): + return renderers.TreeGrid([("Name", str), ("IData", format_hints.Hex), ("Callback Address", format_hints.Hex), + ("Module", str), ("Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_scopes.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_scopes.py new file mode 100644 index 00000000..9a9db690 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kauth_scopes.py @@ -0,0 +1,76 @@ +# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, Callable, Tuple + +from volatility.framework import renderers, interfaces, contexts +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + + +class Kauth_scopes(interfaces.plugins.PluginInterface): + """ Lists kauth scopes and their status """ + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + @classmethod + def list_kauth_scopes(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[Tuple[interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface]]: + """ + Enumerates the registered kauth scopes and yields each object + Uses smear-safe enumeration API + """ + + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + scopes = kernel.object_from_symbol("kauth_scopes") + + for scope in mac.MacUtilities.walk_tailq(scopes, "ks_link"): + yield scope + + def _generator(self): + kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + for scope in self.list_kauth_scopes(self.context, self.config['primary'], self.config['darwin']): + + callback = scope.ks_callback + if callback == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, callback) + + identifier = utility.pointer_to_string(scope.ks_identifier, 128) + + yield (0, (identifier, format_hints.Hex(scope.ks_idata), len([l for l in scope.get_listeners()]), + format_hints.Hex(callback), module_name, symbol_name)) + + def run(self): + return renderers.TreeGrid([("Name", str), ("IData", format_hints.Hex), ("Listeners", int), + ("Callback Address", format_hints.Hex), ("Module", str), ("Symbol", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/kevents.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kevents.py new file mode 100644 index 00000000..b3826ccf --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/kevents.py @@ -0,0 +1,174 @@ +# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, Callable, Tuple + +from volatility.framework import renderers, interfaces, exceptions, contexts +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.symbols import mac +from volatility.plugins.mac import pslist + + +class Kevents(interfaces.plugins.PluginInterface): + """ Lists event handlers registered by processes """ + + _required_framework_version = (2, 0, 0) + + event_types = { + 1: "EVFILT_READ", + 2: "EVFILT_WRITE", + 3: "EVFILT_AIO", + 4: "EVFILT_VNODE", + 5: "EVFILT_PROC", + 6: "EVFILT_SIGNAL", + 7: "EVFILT_TIMER", + 8: "EVFILT_MACHPORT", + 9: "EVFILT_FS", + 10: "EVFILT_USER", + 12: "EVFILT_VM" + } + + vnode_filters = [("NOTE_DELETE", 1), ("NOTE_WRITE", 2), ("NOTE_EXTEND", 4), ("NOTE_ATTRIB", 8), ("NOTE_LINK", 0x10), + ("NOTE_RENAME", 0x20), ("NOTE_REVOKE", 0x40)] + + proc_filters = [("NOTE_EXIT", 0x80000000), ("NOTE_EXITSTATUS", 0x04000000), ("NOTE_FORK", 0x40000000), + ("NOTE_EXEC", 0x20000000), ("NOTE_SIGNAL", 0x08000000), ("NOTE_REAP", 0x10000000)] + + timer_filters = [("NOTE_SECONDS", 1), ("NOTE_USECONDS", 2), ("NOTE_NSECONDS", 4), ("NOTE_ABSOLUTE", 8)] + + all_filters = { + 4: vnode_filters, # EVFILT_VNODE + 5: proc_filters, # EVFILT_PROC + 7: timer_filters # EVFILT_TIMER + } + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 2, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _parse_flags(self, filter_index, filter_flags): + if filter_flags == 0 or filter_index not in self.all_filters: + return "" + + context = [] + + filters = self.all_filters[filter_index] + for flag, index in filters: + if filter_flags & index == index: + context.append(flag) + + return ",".join(context) + + @classmethod + def _walk_klist_array(cls, kernel, fdp, array_pointer_member, array_size_member): + """ + Convience wrapper for walking an array of lists of kernel events + Handles invalid address references + """ + try: + klist_array_pointer = getattr(fdp, array_pointer_member) + array_size = getattr(fdp, array_size_member) + + klist_array = kernel.object(object_type = "array", + offset = klist_array_pointer, + count = array_size + 1, + subtype = kernel.get_type("klist")) + + except exceptions.InvalidAddressException: + return + + for klist in klist_array: + for kn in mac.MacUtilities.walk_slist(klist, "kn_link"): + yield kn + + @classmethod + def _get_task_kevents(cls, kernel, task): + """ + Enumerates event filters per task. + Uses smear-safe APIs throughout as these data structures + see a signifcant amount of smear + """ + fdp = task.p_fd + + for kn in cls._walk_klist_array(kernel, fdp, "fd_knlist", "fd_knlistsize"): + yield kn + + for kn in cls._walk_klist_array(kernel, fdp, "fd_knhash", "fd_knhashmask"): + yield kn + + try: + p_klist = task.p_klist + except exceptions.InvalidAddressException: + return + + for kn in mac.MacUtilities.walk_slist(p_klist, "kn_link"): + yield kn + + @classmethod + def list_kernel_events(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[Tuple[interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface]]: + """ + Returns the kernel event filters registered + + Return values: + A tuple of 3 elements: + 1) The name of the process that registered the filter + 2) The process ID of the process that registered the filter + 3) The object of the associated kernel event filter + """ + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + list_tasks = pslist.PsList.get_list_tasks(pslist.PsList.pslist_methods[0]) + + for task in list_tasks(context, layer_name, darwin_symbols, filter_func): + task_name = utility.array_to_string(task.p_comm) + pid = task.p_pid + + for kn in cls._get_task_kevents(kernel, task): + yield task_name, pid, kn + + def _generator(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + for task_name, pid, kn in self.list_kernel_events(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func): + + filter_index = kn.kn_kevent.filter * -1 + if filter_index in self.event_types: + filter_name = self.event_types[filter_index] + else: + continue + + try: + ident = kn.kn_kevent.ident + except exceptions.InvalidAddressException: + continue + + context = self._parse_flags(filter_index, kn.kn_sfflags) + + yield (0, (pid, task_name, ident, filter_name, context)) + + def run(self): + return renderers.TreeGrid([("PID", int), ("Process", str), ("Ident", int), ("Filter", str), ("Context", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/list_files.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/list_files.py new file mode 100644 index 00000000..a1a06211 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/list_files.py @@ -0,0 +1,175 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import Iterable, Optional + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import mount + +vollog = logging.getLogger(__name__) + + +class List_Files(plugins.PluginInterface): + """Lists all open file descriptors for all processes.""" + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Kernel Address Space', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"), + requirements.PluginRequirement(name = 'mount', plugin = mount.Mount, version = (1, 0, 0)), + ] + + @classmethod + def _vnode_name(cls, vnode: interfaces.objects.ObjectInterface) -> Optional[str]: + # roots of mount points have special name handling + if vnode.v_flag & 1 == 1: + v_name = vnode.full_path() + else: + try: + v_name = utility.pointer_to_string(vnode.v_name, 255) + except exceptions.InvalidAddressException: + v_name = None + + return v_name + + @classmethod + def _get_parent(cls, vnode): + parent = None + + # root entries do not have parents + # and parents of normal files can be smeared + try: + parent = vnode.v_parent + except exceptions.InvalidAddressException: + pass + + return parent + + @classmethod + def _add_vnode(cls, vnode, loop_vnodes): + """ + Adds the given vnode to loop_vnodes. + + loop_vnodes is key off the address of a vnode + and holds its name, parent address, and object + """ + + key = vnode + added = False + + if not key in loop_vnodes: + # We can't do anything with a no-name vnode + v_name = cls._vnode_name(vnode) + if v_name is None: + return added + + parent = cls._get_parent(vnode) + if parent: + parent_val = parent + else: + parent_val = None + + loop_vnodes[key] = (v_name, parent_val, vnode) + + added = True + + return added + + @classmethod + def _walk_vnode(cls, vnode, loop_vnodes): + """ + Iterates over the list of vnodes associated with the given one. + Also traverses the parent chain for the vnode and adds each one. + """ + while vnode: + if not cls._add_vnode(vnode, loop_vnodes): + break + + parent = cls._get_parent(vnode) + while parent: + cls._walk_vnode(parent, loop_vnodes) + parent = cls._get_parent(parent) + + try: + vnode = vnode.v_mntvnodes.tqe_next + except exceptions.InvalidAddressException: + break + + @classmethod + def _walk_vnodelist(cls, list_head, loop_vnodes): + for vnode in mac.MacUtilities.walk_tailq(list_head, "v_mntvnodes"): + cls._walk_vnode(vnode, loop_vnodes) + + @classmethod + def _walk_mounts(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + + loop_vnodes = {} + + # iterate each vnode source from each mount + list_mounts = mount.Mount.list_mounts(context, layer_name, darwin_symbols) + for mnt in list_mounts: + cls._walk_vnodelist(mnt.mnt_vnodelist, loop_vnodes) + cls._walk_vnodelist(mnt.mnt_workerqueue, loop_vnodes) + cls._walk_vnodelist(mnt.mnt_newvnodes, loop_vnodes) + + cls._walk_vnode(mnt.mnt_vnodecovered, loop_vnodes) + cls._walk_vnode(mnt.mnt_realrootvp, loop_vnodes) + cls._walk_vnode(mnt.mnt_devvp, loop_vnodes) + + return loop_vnodes + + @classmethod + def _build_path(cls, vnodes, vnode_name, parent_offset): + path = [vnode_name] + + while parent_offset in vnodes: + parent_name, parent_offset, _ = vnodes[parent_offset] + if parent_offset is None: + parent_offset = 0 + + path.insert(0, parent_name) + + if len(path) > 1: + path = "/".join(path) + else: + path = vnode_name + + if path.startswith("//"): + path = path[1:] + + return path + + @classmethod + def list_files(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + + vnodes = cls._walk_mounts(context, layer_name, darwin_symbols) + + for voff, (vnode_name, parent_offset, vnode) in vnodes.items(): + full_path = cls._build_path(vnodes, vnode_name, parent_offset) + + yield vnode, full_path + + def _generator(self): + for vnode, full_path in self.list_files(self.context, self.config['primary'], self.config['darwin']): + + yield (0, (format_hints.Hex(vnode), full_path)) + + def run(self): + return renderers.TreeGrid([("Address", format_hints.Hex), ("File Path", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsmod.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsmod.py new file mode 100644 index 00000000..129dbda9 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsmod.py @@ -0,0 +1,60 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Mac's lsmod command.""" +from volatility.framework import renderers, interfaces, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints + + +class Lsmod(plugins.PluginInterface): + """Lists loaded kernel modules.""" + + _required_framework_version = (2, 0, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel") + ] + + @classmethod + def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, darwin_symbols: str): + """Lists all the modules in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + + Returns: + A list of modules from the `layer_name` layer + """ + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + kmod_ptr = kernel.object_from_symbol(symbol_name = "kmod") + + # TODO - use smear-proof list walking API after dev release + kmod = kmod_ptr.dereference().cast("kmod_info") + while kmod != 0: + yield kmod + kmod = kmod.next + + def _generator(self): + for module in self.list_modules(self.context, self.config['primary'], self.config['darwin']): + + mod_name = utility.array_to_string(module.name) + mod_size = module.size + + yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Name", str), ("Size", int)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsof.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsof.py new file mode 100644 index 00000000..0307b221 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/lsof.py @@ -0,0 +1,54 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.symbols import mac +from volatility.plugins.mac import pslist + +vollog = logging.getLogger(__name__) + + +class Lsof(plugins.PluginInterface): + """Lists all open file descriptors for all processes.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Kernel Address Space', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + for task in tasks: + pid = task.p_pid + + for _, filepath, fd in mac.MacUtilities.files_descriptors_for_process(self.context, self.config['darwin'], + task): + if filepath and len(filepath) > 0: + yield (0, (pid, fd, filepath)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + return renderers.TreeGrid([("PID", int), ("File Descriptor", int), ("File Path", str)], + self._generator( + list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/malfind.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/malfind.py new file mode 100644 index 00000000..3a920a8a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/malfind.py @@ -0,0 +1,80 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import constants +from volatility.framework import interfaces +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.mac import pslist + + +class Malfind(interfaces.plugins.PluginInterface): + """Lists process memory ranges that potentially contain injected code.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _list_injections(self, task): + """Generate memory regions for a process that may contain injected + code.""" + + proc_layer_name = task.add_process_layer() + if proc_layer_name is None: + return + + proc_layer = self.context.layers[proc_layer_name] + + for vma in task.get_map_iter(): + if not vma.is_suspicious(self.context, self.config['darwin']): + data = proc_layer.read(vma.links.start, 64, pad = True) + yield vma, data + + def _generator(self, tasks): + # determine if we're on a 32 or 64 bit kernel + if self.context.symbol_space.get_type(self.config["darwin"] + constants.BANG + "pointer").size == 4: + is_32bit_arch = True + else: + is_32bit_arch = False + + for task in tasks: + process_name = utility.array_to_string(task.p_comm) + + for vma, data in self._list_injections(task): + if is_32bit_arch: + architecture = "intel" + else: + architecture = "intel64" + + disasm = interfaces.renderers.Disassembly(data, vma.links.start, architecture) + + yield (0, (task.p_pid, process_name, format_hints.Hex(vma.links.start), format_hints.Hex(vma.links.end), + vma.get_perms(), format_hints.HexBytes(data), disasm)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex), + ("End", format_hints.Hex), ("Protection", str), ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces.renderers.Disassembly)], + self._generator( + list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/mount.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/mount.py new file mode 100644 index 00000000..55193fac --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/mount.py @@ -0,0 +1,60 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""A module containing a collection of plugins that produce data typically +found in Mac's mount command.""" +from volatility.framework import renderers, interfaces, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.symbols import mac + + +class Mount(plugins.PluginInterface): + """A module containing a collection of plugins that produce data typically + foundin Mac's mount command""" + + _required_framework_version = (2, 0, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols") + ] + + @classmethod + def list_mounts(cls, context: interfaces.context.ContextInterface, layer_name: str, darwin_symbols: str): + """Lists all the mount structures in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + + Returns: + A list of mount structures from the `layer_name` layer + """ + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + list_head = kernel.object_from_symbol(symbol_name = "mountlist") + + for mount in mac.MacUtilities.walk_tailq(list_head, "mnt_list"): + yield mount + + def _generator(self): + for mount in self.list_mounts(self.context, self.config['primary'], self.config['darwin']): + vfs = mount.mnt_vfsstat + device_name = utility.array_to_string(vfs.f_mntonname) + mount_point = utility.array_to_string(vfs.f_mntfromname) + mount_type = utility.array_to_string(vfs.f_fstypename) + + yield 0, (device_name, mount_point, mount_type) + + def run(self): + return renderers.TreeGrid([("Device", str), ("Mount Point", str), ("Type", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/netstat.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/netstat.py new file mode 100644 index 00000000..dc3c0329 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/netstat.py @@ -0,0 +1,115 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Iterable, Callable, Tuple + +from volatility.framework import exceptions, renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import pslist + +vollog = logging.getLogger(__name__) + + +class Netstat(plugins.PluginInterface): + """Lists all network connections for all processes.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Kernel Address Space', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + @classmethod + def list_sockets(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[Tuple[interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface, + interfaces.objects.ObjectInterface]]: + """ + Returns the open socket descriptors of a process + + Return values: + A tuple of 3 elements: + 1) The name of the process that opened the socket + 2) The process ID of the processed that opened the socket + 3) The address of the associated socket structure + """ + # This is hardcoded, since a change in the default method would change the expected results + list_tasks = pslist.PsList.get_list_tasks(pslist.PsList.pslist_methods[0]) + for task in list_tasks(context, layer_name, darwin_symbols, filter_func): + + task_name = utility.array_to_string(task.p_comm) + pid = task.p_pid + + for filp, _, _ in mac.MacUtilities.files_descriptors_for_process(context, darwin_symbols, task): + try: + ftype = filp.f_fglob.get_fg_type() + except exceptions.InvalidAddressException: + continue + + if ftype != 'SOCKET': + continue + + try: + socket = filp.f_fglob.fg_data.dereference().cast("socket") + except exceptions.InvalidAddressException: + continue + + yield task_name, pid, socket + + def _generator(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + for task_name, pid, socket in self.list_sockets(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func): + + family = socket.get_family() + + if family == 1: + try: + upcb = socket.so_pcb.dereference().cast("unpcb") + path = utility.array_to_string(upcb.unp_addr.sun_path) + except exceptions.InvalidAddressException: + continue + + yield (0, (format_hints.Hex(socket.vol.offset), "UNIX", path, 0, "", 0, "", + "{}/{:d}".format(task_name, pid))) + + elif family in [2, 30]: + state = socket.get_state() + proto = socket.get_protocol_as_string() + + vals = socket.get_converted_connection_info() + + if vals: + (lip, lport, rip, rport) = vals + + yield (0, (format_hints.Hex(socket.vol.offset), proto, lip, lport, rip, rport, state, + "{}/{:d}".format(task_name, pid))) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Proto", str), ("Local IP", str), ("Local Port", int), + ("Remote IP", str), ("Remote Port", int), ("State", str), ("Process", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/proc_maps.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/proc_maps.py new file mode 100644 index 00000000..57a8d021 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/proc_maps.py @@ -0,0 +1,54 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.mac import pslist + + +class Maps(interfaces.plugins.PluginInterface): + """Lists process memory ranges that potentially contain injected code.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks): + for task in tasks: + process_name = utility.array_to_string(task.p_comm) + process_pid = task.p_pid + + for vma in task.get_map_iter(): + path = vma.get_path(self.context, self.config['darwin']) + if path == "": + path = vma.get_special_path() + + yield (0, (process_pid, process_name, format_hints.Hex(vma.links.start), + format_hints.Hex(vma.links.end), vma.get_perms(), path)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex), + ("End", format_hints.Hex), ("Protection", str), ("Map Name", str)], + self._generator( + list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/psaux.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/psaux.py new file mode 100644 index 00000000..2dca44da --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/psaux.py @@ -0,0 +1,103 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""In-memory artifacts from OSX systems.""" +from typing import Iterator, Tuple, Any, Generator, List + +from volatility.framework import exceptions, renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.plugins.mac import pslist + + +class Psaux(plugins.PluginInterface): + """Recovers program command line arguments.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + def _generator(self, tasks: Iterator[Any]) -> Generator[Tuple[int, Tuple[int, str, int, str]], None, None]: + for task in tasks: + proc_layer_name = task.add_process_layer() + if proc_layer_name is None: + continue + + proc_layer = self.context.layers[proc_layer_name] + + argsstart = task.user_stack - task.p_argslen + + if not proc_layer.is_valid(argsstart) or task.p_argslen == 0 or task.p_argc == 0: + continue + + # Add one because the first two are usually duplicates + argc = task.p_argc + 1 + + # smear protection + if argc > 1024: + continue + + task_name = utility.array_to_string(task.p_comm) + + args = [] # type: List[bytes] + + while argc > 0: + try: + arg = proc_layer.read(argsstart, 256) + except exceptions.InvalidAddressException: + break + + idx = arg.find(b'\x00') + if idx != -1: + arg = arg[:idx] + + argsstart += len(str(arg)) + 1 + + # deal with the stupid alignment (leading nulls) and arg duplication + if len(args) == 0: + while argsstart < task.user_stack: + try: + check = proc_layer.read(argsstart, 1) + except exceptions.InvalidAddressException: + break + + if check != b"\x00": + break + + argsstart = argsstart + 1 + + args.append(arg) + + # also check for initial duplicates since OS X is painful + elif arg != args[0]: + args.append(arg) + + argc = argc - 1 + + args_str = " ".join([s.decode("utf-8", errors = 'replace') for s in args]) + + yield (0, (task.p_pid, task_name, task.p_argc, args_str)) + + def run(self) -> renderers.TreeGrid: + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Argc", int), ("Arguments", str)], + self._generator( + list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/pslist.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/pslist.py new file mode 100644 index 00000000..23fba1ac --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/pslist.py @@ -0,0 +1,290 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Callable, Iterable, List, Dict + +from volatility.framework import renderers, interfaces, contexts, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.symbols import mac + +vollog = logging.getLogger(__name__) + + +class PsList(interfaces.plugins.PluginInterface): + """Lists the processes present in a particular mac memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + pslist_methods = ['tasks', 'allproc', 'process_group', 'sessions', 'pid_hash_table'] + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)), + requirements.ChoiceRequirement(name = 'pslist_method', + description = 'Method to determine for processes', + choices = cls.pslist_methods, + default = cls.pslist_methods[0], + optional = True), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True) + ] + + @classmethod + def get_list_tasks( + cls, method: str + ) -> Callable[[interfaces.context.ContextInterface, str, str, Callable[[int], bool]], + Iterable[interfaces.objects.ObjectInterface]]: + """Returns the list_tasks method based on the selector + + Args: + method: Must be one fo the available methods in get_task_choices + + Returns: + list_tasks method for listing tasks + """ + # Ensure method is one of the suitable choices + if method not in cls.pslist_methods: + method = cls.pslist_methods[0] + + if method == 'allproc': + list_tasks = cls.list_tasks_allproc + elif method == 'tasks': + list_tasks = cls.list_tasks_tasks + elif method == 'process_group': + list_tasks = cls.list_tasks_process_group + elif method == 'sessions': + list_tasks = cls.list_tasks_sessions + elif method == 'pid_hash_table': + list_tasks = cls.list_tasks_pid_hash_table + else: + raise ValueError("Impossible method choice chosen") + vollog.debug("Using method {}".format(method)) + + return list_tasks + + @classmethod + def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]: + + filter_func = lambda _: False + # FIXME: mypy #4973 or #2608 + pid_list = pid_list or [] + filter_list = [x for x in pid_list if x is not None] + if filter_list: + + def list_filter(x): + return x.p_pid not in filter_list + + filter_func = list_filter + return filter_func + + def _generator(self): + list_tasks = self.get_list_tasks(self.config.get('pslist_method', self.pslist_methods[0])) + + for task in list_tasks(self.context, + self.config['primary'], + self.config['darwin'], + filter_func = self.create_pid_filter(self.config.get('pid', None))): + pid = task.p_pid + ppid = task.p_ppid + name = utility.array_to_string(task.p_comm) + yield (0, (pid, ppid, name)) + + @classmethod + def list_tasks_allproc(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the processes in the primary layer based on the allproc method + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + filter_func: A function which takes a process object and returns True if the process should be ignored/filtered + + Returns: + The list of process objects from the processes linked list after filtering + """ + + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + kernel_layer = context.layers[layer_name] + + proc = kernel.object_from_symbol(symbol_name = "allproc").lh_first + + seen = {} # type: Dict[int, int] + while proc is not None and proc.vol.offset != 0: + if proc.vol.offset in seen: + vollog.log(logging.INFO, "Recursive process list detected (a result of non-atomic acquisition).") + break + else: + seen[proc.vol.offset] = 1 + + if kernel_layer.is_valid(proc.vol.offset, proc.vol.size) and not filter_func(proc): + yield proc + + try: + proc = proc.p_list.le_next.dereference() + except exceptions.InvalidAddressException: + break + + @classmethod + def list_tasks_tasks(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer based on the tasks queue + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + filter_func: A function which takes a task object and returns True if the task should be ignored/filtered + + Returns: + The list of task objects from the `layer_name` layer's `tasks` list after filtering + """ + + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + kernel_layer = context.layers[layer_name] + + queue_entry = kernel.object_from_symbol(symbol_name = "tasks") + + seen = {} # type: Dict[int, int] + for task in queue_entry.walk_list(queue_entry, "tasks", "task"): + if task.vol.offset in seen: + vollog.log(logging.INFO, "Recursive process list detected (a result of non-atomic acquisition).") + break + else: + seen[task.vol.offset] = 1 + + try: + proc = task.bsd_info.dereference().cast("proc") + except exceptions.InvalidAddressException: + continue + + if kernel_layer.is_valid(proc.vol.offset, proc.vol.size) and not filter_func(proc): + yield proc + + @classmethod + def list_tasks_sessions(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer using sessions + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + filter_func: A function which takes a task object and returns True if the task should be ignored/filtered + + Returns: + The list of task objects from the `layer_name` layer's `tasks` list after filtering + """ + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + table_size = kernel.object_from_symbol(symbol_name = "sesshash") + + sesshashtbl = kernel.object_from_symbol(symbol_name = "sesshashtbl") + + proc_array = kernel.object(object_type = "array", + offset = sesshashtbl, + count = table_size + 1, + subtype = kernel.get_type("sesshashhead")) + + for proc_list in proc_array: + for proc in mac.MacUtilities.walk_list_head(proc_list, "s_hash"): + if proc.s_leader.is_readable() and not filter_func(proc.s_leader): + yield proc.s_leader + + @classmethod + def list_tasks_process_group(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer using process groups + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + filter_func: A function which takes a task object and returns True if the task should be ignored/filtered + + Returns: + The list of task objects from the `layer_name` layer's `tasks` list after filtering + """ + + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + table_size = kernel.object_from_symbol(symbol_name = "pgrphash") + + pgrphashtbl = kernel.object_from_symbol(symbol_name = "pgrphashtbl") + + proc_array = kernel.object(object_type = "array", + offset = pgrphashtbl, + count = table_size + 1, + subtype = kernel.get_type("pgrphashhead")) + + for proc_list in proc_array: + for pgrp in mac.MacUtilities.walk_list_head(proc_list, "pg_hash"): + for proc in mac.MacUtilities.walk_list_head(pgrp.pg_members, "p_pglist"): + if not filter_func(proc): + yield proc + + @classmethod + def list_tasks_pid_hash_table(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + darwin_symbols: str, + filter_func: Callable[[int], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the tasks in the primary layer using the pid hash table + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + darwin_symbols: The name of the table containing the kernel symbols + filter_func: A function which takes a task object and returns True if the task should be ignored/filtered + + Returns: + The list of task objects from the `layer_name` layer's `tasks` list after filtering + """ + + kernel = contexts.Module(context, darwin_symbols, layer_name, 0) + + table_size = kernel.object_from_symbol(symbol_name = "pidhash") + + pidhashtbl = kernel.object_from_symbol(symbol_name = "pidhashtbl") + + proc_array = kernel.object(object_type = "array", + offset = pidhashtbl, + count = table_size + 1, + subtype = kernel.get_type("pidhashhead")) + + for proc_list in proc_array: + for proc in mac.MacUtilities.walk_list_head(proc_list, "p_hash"): + if not filter_func(proc): + yield proc + + def run(self): + return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/pstree.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/pstree.py new file mode 100644 index 00000000..7fda089f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/pstree.py @@ -0,0 +1,73 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.plugins.mac import pslist + + +class PsTree(plugins.PluginInterface): + """Plugin for listing processes in a tree based on their parent process + ID.""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._processes = {} + self._levels = {} + self._children = {} + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)) + ] + + def _find_level(self, pid): + """Finds how deep the pid is in the processes list.""" + seen = set([]) + seen.add(pid) + level = 0 + proc = self._processes.get(pid, None) + while proc is not None and proc.vol.offset != 0 and proc.p_ppid != 0 and proc.p_ppid not in seen: + ppid = int(proc.p_ppid) + child_list = self._children.get(ppid, set([])) + child_list.add(proc.p_pid) + self._children[ppid] = child_list + proc = self._processes.get(ppid, None) + level += 1 + self._levels[pid] = level + + def _generator(self): + """Generates the tree list of processes""" + list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0])) + + for proc in list_tasks(self.context, self.config['primary'], self.config['darwin']): + self._processes[proc.p_pid] = proc + + # Build the child/level maps + for pid in self._processes: + self._find_level(pid) + + def yield_processes(pid): + proc = self._processes[pid] + row = (proc.p_pid, proc.p_ppid, utility.array_to_string(proc.p_comm)) + + yield (self._levels[pid] - 1, row) + for child_pid in self._children.get(pid, []): + yield from yield_processes(child_pid) + + for pid in self._levels: + if self._levels[pid] == 1: + yield from yield_processes(pid) + + def run(self): + return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/socket_filters.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/socket_filters.py new file mode 100644 index 00000000..0fde476f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/socket_filters.py @@ -0,0 +1,72 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Socket_filters(plugins.PluginInterface): + """Enumerates kernel socket filters.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _generator(self): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + members_to_check = ["sf_unregistered", "sf_attach", "sf_detach", "sf_notify", "sf_getpeername", + "sf_getsockname", + "sf_data_in", "sf_data_out", "sf_connect_in", "sf_connect_out", "sf_bind", "sf_setoption", + "sf_getoption", "sf_listen", "sf_ioctl"] + + filter_list = kernel.object_from_symbol(symbol_name = "sock_filter_head") + + for filter_container in mac.MacUtilities.walk_tailq(filter_list, "sf_global_next"): + current_filter = filter_container.sf_filter + + filter_name = utility.pointer_to_string(current_filter.sf_name, count = 128) + + try: + filter_socket = filter_container.sf_entry_head.sfe_socket.vol.offset + except exceptions.InvalidAddressException: + filter_socket = 0 + + for member in members_to_check: + check_addr = current_filter.member(attr = member) + if check_addr == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, check_addr) + + yield (0, (format_hints.Hex(current_filter.vol.offset), filter_name, member, \ + format_hints.Hex(filter_socket), format_hints.Hex(check_addr), module_name, symbol_name)) + + def run(self): + return renderers.TreeGrid([("Filter", format_hints.Hex), ("Name", str), ("Member", str), + ("Socket", format_hints.Hex), ("Handler", format_hints.Hex), ("Module", str), + ("Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/timers.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/timers.py new file mode 100644 index 00000000..94502d80 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/timers.py @@ -0,0 +1,79 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Timers(plugins.PluginInterface): + """Check for malicious kernel timers.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _generator(self): + kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0) + + mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin']) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + real_ncpus = kernel.object_from_symbol(symbol_name = "real_ncpus") + + cpu_data_ptrs_ptr = kernel.get_symbol("cpu_data_ptr").address + + cpu_data_ptrs_addr = kernel.object(object_type = "pointer", + offset = cpu_data_ptrs_ptr, + subtype = kernel.get_type('long unsigned int')) + + cpu_data_ptrs = kernel.object(object_type = "array", + offset = cpu_data_ptrs_addr, + subtype = kernel.get_type('cpu_data'), + count = real_ncpus) + + for cpu_data_ptr in cpu_data_ptrs: + try: + queue = cpu_data_ptr.rtclock_timer.queue.head + except exceptions.InvalidAddressException: + break + + for timer in queue.walk_list(queue, "q_link", "call_entry"): + try: + handler = timer.func.dereference().vol.offset + except exceptions.InvalidAddressException: + continue + + if timer.has_member("entry_time"): + entry_time = timer.entry_time + else: + entry_time = -1 + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, handler) + + yield (0, (format_hints.Hex(handler), format_hints.Hex(timer.param0), format_hints.Hex(timer.param1), \ + timer.deadline, entry_time, module_name, symbol_name)) + + def run(self): + return renderers.TreeGrid([("Function", format_hints.Hex), ("Param 0", format_hints.Hex), + ("Param 1", format_hints.Hex), ("Deadline", int), ("Entry Time", int), + ("Module", str), ("Symbol", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/trustedbsd.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/trustedbsd.py new file mode 100644 index 00000000..73a313af --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/trustedbsd.py @@ -0,0 +1,77 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List, Iterator, Any + +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers, contexts +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import mac +from volatility.plugins.mac import lsmod + +vollog = logging.getLogger(__name__) + + +class Trustedbsd(plugins.PluginInterface): + """Checks for malicious trustedbsd modules""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"), + requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)) + ] + + def _generator(self, mods: Iterator[Any]): + kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0) + + handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods) + + policy_list = kernel.object_from_symbol(symbol_name = "mac_policy_list").cast("mac_policy_list") + + entries = kernel.object(object_type = "array", + offset = policy_list.entries.dereference().vol.offset, + subtype = kernel.get_type('mac_policy_list_element'), + count = policy_list.staticmax + 1) + + for i, ent in enumerate(entries): + # I don't know how this can happen, but the kernel makes this check all over the place + # the policy isn't useful without any ops so a rootkit can't abuse this + try: + mpc = ent.mpc.dereference() + ops = mpc.mpc_ops.dereference() + except exceptions.InvalidAddressException: + continue + + try: + ent_name = utility.pointer_to_string(mpc.mpc_name, 255) + except exceptions.InvalidAddressException: + ent_name = "N/A" + + for check in ops.vol.members: + call_addr = getattr(ops, check) + + if call_addr is None or call_addr == 0: + continue + + module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr) + + yield (0, (check, ent_name, format_hints.Hex(call_addr), module_name, symbol_name)) + + def run(self): + return renderers.TreeGrid([("Member", str), ("Policy Name", str), ("Handler Address", format_hints.Hex), + ("Handler Module", str), ("Handler Symbol", str)], + self._generator( + lsmod.Lsmod.list_modules(self.context, self.config['primary'], + self.config['darwin']))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/mac/vfsevents.py b/app/parsers/vol_Parser/volatility/framework/plugins/mac/vfsevents.py new file mode 100644 index 00000000..84d3adc5 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/mac/vfsevents.py @@ -0,0 +1,65 @@ +# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import renderers, interfaces, exceptions, contexts +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility + + +class VFSevents(interfaces.plugins.PluginInterface): + """ Lists processes that are filtering file system events """ + + _required_framework_version = (2, 0, 0) + + event_types = [ + "CREATE_FILE", "DELETE", "STAT_CHANGED", "RENAME", "CONTENT_MODIFIED", "EXCHANGE", "FINDER_INFO_CHANGED", + "CREATE_DIR", "CHOWN", "XATTR_MODIFIED", "XATTR_REMOVED", "DOCID_CREATED", "DOCID_CHANGED" + ] + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"), + ] + + def _generator(self): + """ + Lists the registered VFS event watching processes + Also lists which event(s) a process is registered for + """ + + kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0) + + watcher_table = kernel.object_from_symbol("watcher_table") + + for watcher in watcher_table: + if watcher == 0: + continue + + task_name = utility.array_to_string(watcher.proc_name) + task_pid = watcher.pid + + events = [] + + try: + event_array = kernel.object(object_type = "array", + offset = watcher.event_list, + count = 13, + subtype = kernel.get_type("unsigned char")) + + except exceptions.InvalidAddressException: + continue + + for i, event in enumerate(event_array): + if event == 1: + events.append(self.event_types[i]) + + if events != []: + yield (0, (task_name, task_pid, ",".join(events))) + + def run(self): + return renderers.TreeGrid([("Name", str), ("PID", int), ("Events", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/timeliner.py b/app/parsers/vol_Parser/volatility/framework/plugins/timeliner.py new file mode 100644 index 00000000..f0203ddf --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/timeliner.py @@ -0,0 +1,218 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import abc +import datetime +import enum +import io +import json +import logging +import traceback +from typing import Generator, Iterable, List, Optional, Tuple, Type + +from volatility import framework +from volatility.framework import renderers, automagic, interfaces, plugins, exceptions +from volatility.framework.configuration import requirements + +vollog = logging.getLogger(__name__) + + +class TimeLinerType(enum.IntEnum): + CREATED = 1 + MODIFIED = 2 + ACCESSED = 3 + CHANGED = 4 + + +class TimeLinerInterface(metaclass = abc.ABCMeta): + """Interface defining methods that timeliner will use to generate a body + file.""" + + @abc.abstractmethod + def generate_timeline(self) -> Generator[Tuple[str, TimeLinerType, datetime.datetime], None, None]: + """Method generates Tuples of (description, timestamp_type, timestamp) + + These need not be generated in any particular order, sorting + will be done later + """ + + +class Timeliner(interfaces.plugins.PluginInterface): + """Runs all relevant plugins that provide time related information and + orders the results by time.""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.timeline = {} + self.usable_plugins = None + self.automagics = None + + @classmethod + def get_usable_plugins(cls, selected_list: List[str] = None) -> List[Type]: + # Initialize for the run + plugin_list = list(framework.class_subclasses(TimeLinerInterface)) + + # Get the filter from the configuration + def passthrough(name: str, selected: List[str]) -> bool: + return True + + filter_func = passthrough + if selected_list: + + def filter_plugins(name: str, selected: List[str]) -> bool: + return any([s in name for s in selected]) + + filter_func = filter_plugins + else: + selected_list = [] + + return [plugin_class for plugin_class in plugin_list if filter_func(plugin_class.__name__, selected_list)] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.StringRequirement(name = 'plugins', + description = "Comma separated list of plugins to run", + optional = True, + default = None), + requirements.BooleanRequirement( + name = 'record-config', + description = "Whether to record the state of all the plugins once complete", + optional = True, + default = False), + requirements.ListRequirement(name = 'plugin-filter', + description = "Only run plugins featuring this substring", + element_type = str, + optional = True, + default = []), + requirements.BooleanRequirement(name = 'create-bodyfile', + description = "Whether to create a body file whilst producing results", + optional = True, + default = False) + ] + + def _sort_function(self, item): + data = item[1] + + def sortable(timestamp): + max_date = datetime.datetime(day = 1, month = 12, year = datetime.MAXYEAR) + if isinstance(timestamp, interfaces.renderers.BaseAbsentValue): + return max_date + return timestamp + + return [sortable(timestamp) for timestamp in data[2:]] + + def _generator(self, runable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]: + """Takes a timeline, sorts it and output the data from each relevant + row from each plugin.""" + # Generate the results for each plugin + data = [] + for plugin in runable_plugins: + plugin_name = plugin.__class__.__name__ + self._progress_callback((runable_plugins.index(plugin) * 100) // len(runable_plugins), + "Running plugin {}...".format(plugin_name)) + try: + vollog.log(logging.INFO, "Running {}".format(plugin_name)) + for (item, timestamp_type, timestamp) in plugin.generate_timeline(): + times = self.timeline.get((plugin_name, item), {}) + if times.get(timestamp_type, None) is not None: + vollog.debug("Multiple timestamps for the same plugin/file combination found: {} {}".format( + plugin_name, item)) + times[timestamp_type] = timestamp + self.timeline[(plugin_name, item)] = times + data.append((0, [ + plugin_name, item, + times.get(TimeLinerType.CREATED, renderers.NotApplicableValue()), + times.get(TimeLinerType.MODIFIED, renderers.NotApplicableValue()), + times.get(TimeLinerType.ACCESSED, renderers.NotApplicableValue()), + times.get(TimeLinerType.CHANGED, renderers.NotApplicableValue()) + ])) + except Exception: + vollog.log(logging.INFO, "Exception occurred running plugin: {}".format(plugin_name)) + vollog.log(logging.DEBUG, traceback.format_exc()) + for data_item in sorted(data, key = self._sort_function): + yield data_item + + # Write out a body file if necessary + if self.config.get('create-bodyfile', True): + with self.open("volatility.body") as file_data: + with io.TextIOWrapper(file_data, write_through = True) as fp: + for (plugin_name, item) in self.timeline: + times = self.timeline[(plugin_name, item)] + # Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime + + if self._any_time_present(times): + fp.write("|{} - {}||||||{}|{}|{}|{}\n".format( + plugin_name, self._sanitize_body_format(item), + self._text_format(times.get(TimeLinerType.ACCESSED, "")), + self._text_format(times.get(TimeLinerType.MODIFIED, "")), + self._text_format(times.get(TimeLinerType.CHANGED, "")), + self._text_format(times.get(TimeLinerType.CREATED, "")))) + + def _sanitize_body_format(self, value): + return value.replace("|", "_") + + def _any_time_present(self, times): + for time in TimeLinerType: + if not isinstance(times.get(time, renderers.NotApplicableValue), interfaces.renderers.BaseAbsentValue): + return True + return False + + def _text_format(self, value): + """Formats a value as text, in case it is an AbsentValue""" + if isinstance(value, interfaces.renderers.BaseAbsentValue): + return "" + if isinstance(value, datetime.datetime): + return int(value.timestamp()) + return value + + def run(self): + """Isolate each plugin and run it.""" + + # Use all the plugins if there's no filter + self.usable_plugins = self.usable_plugins or self.get_usable_plugins() + self.automagics = self.automagics or automagic.available(self._context) + plugins_to_run = [] + + filter_list = self.config['plugin-filter'] + # Identify plugins that we can run which output datetimes + for plugin_class in self.usable_plugins: + try: + automagics = automagic.choose_automagic(self.automagics, plugin_class) + + plugin = plugins.construct_plugin(self.context, automagics, plugin_class, self.config_path, + self._progress_callback, self.open) + + if isinstance(plugin, TimeLinerInterface): + if not len(filter_list) or any( + [filter in plugin.__module__ + '.' + plugin.__class__.__name__ for filter in filter_list]): + plugins_to_run.append(plugin) + except exceptions.UnsatisfiedException as excp: + # Remove the failed plugin from the list and continue + vollog.debug("Unable to satisfy {}: {}".format(plugin_class.__name__, excp.unsatisfied)) + continue + + if self.config.get('record-config', False): + total_config = {} + for plugin in plugins_to_run: + old_dict = dict(plugin.build_configuration()) + for entry in old_dict: + total_config[interfaces.configuration.path_join(plugin.__class__.__name__, entry)] = old_dict[entry] + + with self.open("config.json") as file_data: + with io.TextIOWrapper(file_data, write_through = True) as fp: + json.dump(total_config, fp, sort_keys = True, indent = 2) + + return renderers.TreeGrid(columns = [("Plugin", str), ("Description", str), ("Created Date", datetime.datetime), + ("Modified Date", datetime.datetime), ("Accessed Date", datetime.datetime), + ("Changed Date", datetime.datetime)], + generator = self._generator(plugins_to_run)) + + def build_configuration(self): + """Builds the configuration to save for the plugin such that it can be + reconstructed.""" + vollog.warning("Unable to record configuration data for the timeliner plugin") + return [] diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/__init__.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/__init__.py new file mode 100644 index 00000000..e7f81d48 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/__init__.py @@ -0,0 +1,8 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All core windows plugins. + +These modules should only be imported from volatility.plugins NOT +volatility.framework.plugins +""" diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/bigpools.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/bigpools.py new file mode 100644 index 00000000..434d88df --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/bigpools.py @@ -0,0 +1,127 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List, Optional, Tuple, Iterator + +from volatility.framework import interfaces, renderers, exceptions, symbols +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import configuration +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import extensions +from volatility.framework.symbols.windows import versions + +vollog = logging.getLogger(__name__) + + +class BigPools(interfaces.plugins.PluginInterface): + """List big page pools.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.StringRequirement(name = 'tags', + description = "Comma separated list of pool tags to filter pools returned", + optional = True, + default = None) + ] + + @classmethod + def list_big_pools(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + tags: Optional[list] = None): + """Returns the big page pool objects from the kernel PoolBigPageTable array. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + tags: An optional list of pool tags to filter big page pool tags by + + Yields: + A big page pool object + """ + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address + big_page_table = ntkrnlmp.object(object_type = "unsigned long long", offset = big_page_table_offset) + + big_page_table_size_offset = ntkrnlmp.get_symbol("PoolBigPageTableSize").address + big_page_table_size = ntkrnlmp.object(object_type = "unsigned long", offset = big_page_table_size_offset) + + try: + big_page_table_type = ntkrnlmp.get_type("_POOL_TRACKER_BIG_PAGES") + except exceptions.SymbolError: + # We have to manually load a symbol table + is_vista_or_later = versions.is_vista_or_later(context, symbol_table) + is_win10 = versions.is_win10(context, symbol_table) + if is_win10: + big_pools_json_filename = "bigpools-win10" + elif is_vista_or_later: + big_pools_json_filename = "bigpools-vista" + else: + big_pools_json_filename = "bigpools" + + if symbols.symbol_table_is_64bit(context, symbol_table): + big_pools_json_filename += "-x64" + else: + big_pools_json_filename += "-x86" + + new_table_name = intermed.IntermediateSymbolTable.create( + context = context, + config_path = configuration.path_join(context.symbol_space[symbol_table].config_path, "bigpools"), + sub_path = "windows", + filename = big_pools_json_filename, + table_mapping = {'nt_symbols': symbol_table}, + class_types = {'_POOL_TRACKER_BIG_PAGES': extensions.pool.POOL_TRACKER_BIG_PAGES}) + module = context.module(new_table_name, layer_name, offset = 0) + big_page_table_type = module.get_type("_POOL_TRACKER_BIG_PAGES") + + big_pools = ntkrnlmp.object(object_type = "array", + offset = big_page_table, + subtype = big_page_table_type, + count = big_page_table_size, + absolute = True) + + for big_pool in big_pools: + if big_pool.is_valid(): + if tags is None or big_pool.get_key() in tags: + yield big_pool + + def _generator(self) -> Iterator[Tuple[int, Tuple[int, str]]]: # , str, int]]]: + if self.config.get("tags"): + tags = [tag for tag in self.config["tags"].split(',')] + else: + tags = None + + for big_pool in self.list_big_pools(context = self.context, + layer_name = self.config["primary"], + symbol_table = self.config["nt_symbols"], + tags = tags): + + num_bytes = big_pool.get_number_of_bytes() + if not isinstance(num_bytes, interfaces.renderers.BaseAbsentValue): + num_bytes = format_hints.Hex(num_bytes) + + yield (0, (format_hints.Hex(big_pool.Va), big_pool.get_key(), big_pool.get_pool_type(), num_bytes)) + + def run(self): + return renderers.TreeGrid([ + ('Allocation', format_hints.Hex), + ('Tag', str), + ('PoolType', str), + ('NumberOfBytes', format_hints.Hex), + ], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/cachedump.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/cachedump.py new file mode 100644 index 00000000..5b303e26 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/cachedump.py @@ -0,0 +1,131 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from struct import unpack + +from Crypto.Cipher import ARC4, AES +from Crypto.Hash import HMAC + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.symbols.windows import versions +from volatility.plugins.windows import hashdump, lsadump +from volatility.plugins.windows.registry import hivelist + + +class Cachedump(interfaces.plugins.PluginInterface): + """Dumps lsa secrets from memory""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'lsadump', plugin = lsadump.Lsadump, version = (1, 0, 0)) + ] + + def get_nlkm(self, sechive, lsakey, is_vista_or_later): + return lsadump.Lsadump.get_secret_by_name(sechive, 'NL$KM', lsakey, is_vista_or_later) + + def decrypt_hash(self, edata, nlkm, ch, xp): + if xp: + hmac_md5 = HMAC.new(nlkm, ch) + rc4key = hmac_md5.digest() + rc4 = ARC4.new(rc4key) + data = rc4.encrypt(edata) + else: + # based on Based on code from http://lab.mediaservice.net/code/cachedump.rb + aes = AES.new(nlkm[16:32], AES.MODE_CBC, ch) + data = "" + for i in range(0, len(edata), 16): + buf = edata[i:i + 16] + if len(buf) < 16: + buf += (16 - len(buf)) * "\00" + data += aes.decrypt(buf) + return data + + def parse_cache_entry(self, cache_data): + (uname_len, domain_len) = unpack(" List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'ssdt', plugin = ssdt.SSDT, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'svcscan', plugin = svcscan.SvcScan, version = (1, 0, 0)) + ] + + @staticmethod + def create_callback_table(context: interfaces.context.ContextInterface, symbol_table: str, config_path: str) -> str: + """Creates a symbol table for a set of callbacks. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the kernel symbols + config_path: The configuration path within the context of the symbol table to create + + Returns: + The name of the constructed callback table + """ + native_types = context.symbol_space[symbol_table].natives + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + table_mapping = {"nt_symbols": symbol_table} + + if is_64bit: + symbol_filename = "callbacks-x64" + else: + symbol_filename = "callbacks-x86" + + return intermed.IntermediateSymbolTable.create(context, + config_path, + "windows", + symbol_filename, + native_types = native_types, + table_mapping = table_mapping) + + @classmethod + def list_notify_routines(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + callback_table_name: str) -> Iterable[Tuple[str, int, Optional[str]]]: + """Lists all kernel notification routines. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + callback_table_name: The nae of the table containing the callback symbols + + Yields: + A name, location and optional detail string + """ + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + is_vista_or_later = versions.is_vista_or_later(context = context, symbol_table = symbol_table) + full_type_name = callback_table_name + constants.BANG + "_GENERIC_CALLBACK" + + symbol_names = [("PspLoadImageNotifyRoutine", False), ("PspCreateThreadNotifyRoutine", True), + ("PspCreateProcessNotifyRoutine", True)] + + for symbol_name, extended_list in symbol_names: + + try: + symbol_offset = ntkrnlmp.get_symbol(symbol_name).address + except exceptions.SymbolError: + vollog.debug("Cannot find {}".format(symbol_name)) + continue + + if is_vista_or_later and extended_list: + count = 64 + else: + count = 8 + + fast_refs = ntkrnlmp.object(object_type = "array", + offset = symbol_offset, + subtype = ntkrnlmp.get_type("_EX_FAST_REF"), + count = count) + + for fast_ref in fast_refs: + try: + callback = fast_ref.dereference().cast(full_type_name) + except exceptions.InvalidAddressException: + continue + + if callback.Callback != 0: + yield symbol_name, callback.Callback, None + + @classmethod + def list_registry_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + callback_table_name: str) -> Iterable[Tuple[str, int, None]]: + """Lists all registry callbacks. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + callback_table_name: The nae of the table containing the callback symbols + + Yields: + A name, location and optional detail string + """ + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + full_type_name = callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK" + + try: + symbol_offset = ntkrnlmp.get_symbol("CmpCallBackVector").address + symbol_count_offset = ntkrnlmp.get_symbol("CmpCallBackCount").address + except exceptions.SymbolError: + vollog.debug("Cannot find CmpCallBackVector or CmpCallBackCount") + return + + callback_count = ntkrnlmp.object(object_type = "unsigned int", offset = symbol_count_offset) + + if callback_count == 0: + return + + fast_refs = ntkrnlmp.object(object_type = "array", + offset = symbol_offset, + subtype = ntkrnlmp.get_type("_EX_FAST_REF"), + count = callback_count) + + for fast_ref in fast_refs: + try: + callback = fast_ref.dereference().cast(full_type_name) + except exceptions.InvalidAddressException: + continue + + if callback.Function != 0: + yield "CmRegisterCallback", callback.Function, None + + @classmethod + def list_bugcheck_reason_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str, callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel bugcheck reason callbacks. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + callback_table_name: The nae of the table containing the callback symbols + + Yields: + A name, location and optional detail string + """ + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + list_offset = ntkrnlmp.get_symbol("KeBugCheckReasonCallbackListHead").address + except exceptions.SymbolError: + vollog.debug("Cannot find KeBugCheckReasonCallbackListHead") + return + + full_type_name = callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD" + callback_record = context.object(object_type = full_type_name, + offset = kvo + list_offset, + layer_name = layer_name) + + for callback in callback_record.Entry: + if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64): + continue + + try: + component = ntkrnlmp.object( + "string", absolute = True, offset = callback.Component, max_length = 64, errors = "replace" + ) # type: Union[interfaces.renderers.BaseAbsentValue, interfaces.objects.ObjectInterface] + except exceptions.InvalidAddressException: + component = renderers.UnreadableValue() + + yield "KeBugCheckReasonCallbackListHead", callback.CallbackRoutine, component + + @classmethod + def list_bugcheck_callbacks(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, + callback_table_name: str) -> Iterable[Tuple[str, int, str]]: + """Lists all kernel bugcheck callbacks. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + callback_table_name: The nae of the table containing the callback symbols + + Yields: + A name, location and optional detail string + """ + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address + except exceptions.SymbolError: + vollog.debug("Cannot find KeBugCheckCallbackListHead") + return + + full_type_name = callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD" + callback_record = context.object(full_type_name, offset = kvo + list_offset, layer_name = layer_name) + + for callback in callback_record.Entry: + + if not context.layers[layer_name].is_valid(callback.CallbackRoutine, 64): + continue + + try: + component = context.object(symbol_table + constants.BANG + "string", + layer_name = layer_name, + offset = callback.Component, + max_length = 64, + errors = "replace") + except exceptions.InvalidAddressException: + component = renderers.UnreadableValue() + + yield "KeBugCheckCallbackListHead", callback.CallbackRoutine, component + + def _generator(self): + + callback_table_name = self.create_callback_table(self.context, self.config["nt_symbols"], self.config_path) + + collection = ssdt.SSDT.build_module_collection(self.context, self.config['primary'], self.config['nt_symbols']) + + callback_methods = (self.list_notify_routines, self.list_bugcheck_callbacks, + self.list_bugcheck_reason_callbacks, self.list_registry_callbacks) + + for callback_method in callback_methods: + for callback_type, callback_address, callback_detail in callback_method(self.context, + self.config['primary'], + self.config['nt_symbols'], + callback_table_name): + + if callback_detail is None: + detail = renderers.NotApplicableValue() + else: + detail = callback_detail + + module_symbols = list(collection.get_module_symbols_by_absolute_location(callback_address)) + + if module_symbols: + for module_name, symbol_generator in module_symbols: + symbols_found = False + + # we might have multiple symbols pointing to the same location + for symbol in symbol_generator: + symbols_found = True + yield (0, (callback_type, format_hints.Hex(callback_address), module_name, + symbol.split(constants.BANG)[1], detail)) + + # no symbols, but we at least can report the module name + if not symbols_found: + yield (0, (callback_type, format_hints.Hex(callback_address), module_name, + renderers.NotAvailableValue(), detail)) + else: + # no module was found at the absolute location + yield (0, (callback_type, format_hints.Hex(callback_address), renderers.NotAvailableValue(), + renderers.NotAvailableValue(), detail)) + + def run(self): + + return renderers.TreeGrid([("Type", str), ("Callback", format_hints.Hex), ("Module", str), ("Symbol", str), + ("Detail", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/cmdline.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/cmdline.py new file mode 100644 index 00000000..83ac2aa0 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/cmdline.py @@ -0,0 +1,88 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import constants, exceptions, renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class CmdLine(interfaces.plugins.PluginInterface): + """Lists process command line arguments.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True) + ] + + @classmethod + def get_cmdline(cls, context: interfaces.context.ContextInterface, kernel_table_name: str, proc): + """Extracts the cmdline from PEB + + Args: + context: the context to operate upon + kernel_table_name: the name for the symbol table containing the kernel's symbols + proc: the process object + + Returns: + A string with the command line + """ + + proc_layer_name = proc.add_process_layer() + + peb = context.object(kernel_table_name + constants.BANG + "_PEB", + layer_name = proc_layer_name, + offset = proc.Peb) + result_text = peb.ProcessParameters.CommandLine.get_string() + + return result_text + + def _generator(self, procs): + + for proc in procs: + process_name = utility.array_to_string(proc.ImageFileName) + proc_id = "Unknown" + + try: + proc_id = proc.UniqueProcessId + result_text = self.get_cmdline(self.context, self.config["nt_symbols"], proc) + + except exceptions.SwappedInvalidAddressException as exp: + result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address) + + except exceptions.PagedInvalidAddressException as exp: + result_text = "Required memory at {0:#x} is not valid (process exited?)".format(exp.invalid_address) + + except exceptions.InvalidAddressException as exp: + result_text = "Process {}: Required memory at {:#x} is not valid (incomplete layer {}?)".format( + proc_id, exp.invalid_address, exp.layer_name) + + yield (0, (proc.UniqueProcessId, process_name, result_text)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Args", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/dlllist.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/dlllist.py new file mode 100644 index 00000000..60e86219 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/dlllist.py @@ -0,0 +1,174 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import datetime +import logging +import ntpath +from typing import List, Optional, Type + +from volatility.framework import exceptions, renderers, interfaces, constants +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints, conversion +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins import timeliner +from volatility.plugins.windows import pslist, info + +vollog = logging.getLogger(__name__) + + +class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Lists the loaded modules in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'info', component = info.Info, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed DLLs", + default = False, + optional = True) + ] + + @classmethod + def dump_pe(cls, + context: interfaces.context.ContextInterface, + pe_table_name: str, + dll_entry: interfaces.objects.ObjectInterface, + open_method: Type[interfaces.plugins.FileHandlerInterface], + layer_name: str = None, + prefix: str = '') -> Optional[interfaces.plugins.FileHandlerInterface]: + """Extracts the complete data for a process as a FileInterface + + Args: + context: the context to operate upon + pe_table_name: the name for the symbol table containing the PE format symbols + dll_entry: the object representing the module + layer_name: the layer that the DLL lives within + open_method: class for constructing output files + + Returns: + An open FileHandlerInterface object containing the complete data for the DLL or None in the case of failure + """ + try: + try: + name = dll_entry.FullDllName.get_string() + except exceptions.InvalidAddressException: + name = 'UnreadbleDLLName' + + if layer_name is None: + layer_name = dll_entry.vol.layer_name + + file_handle = open_method("{}{}.{:#x}.{:#x}.dmp".format(prefix, ntpath.basename(name), + dll_entry.vol.offset, dll_entry.DllBase)) + + dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset = dll_entry.DllBase, + layer_name = layer_name) + + for offset, data in dos_header.reconstruct(): + file_handle.seek(offset) + file_handle.write(data) + except (IOError, exceptions.VolatilityException, OverflowError, ValueError) as excp: + vollog.debug("Unable to dump dll at offset {}: {}".format(dll_entry.DllBase, excp)) + return None + return file_handle + + def _generator(self, procs): + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + + kuser = info.Info.get_kuser_structure(self.context, self.config['primary'], self.config['nt_symbols']) + nt_major_version = int(kuser.NtMajorVersion) + nt_minor_version = int(kuser.NtMinorVersion) + # LoadTime only applies to versions higher or equal to Window 7 (6.1 and higher) + dll_load_time_field = (nt_major_version > 6) or (nt_major_version == 6 and nt_minor_version >= 1) + for proc in procs: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + for entry in proc.load_order_modules(): + + BaseDllName = FullDllName = renderers.UnreadableValue() + try: + BaseDllName = entry.BaseDllName.get_string() + # We assume that if the BaseDllName points to an invalid buffer, so will FullDllName + FullDllName = entry.FullDllName.get_string() + except exceptions.InvalidAddressException: + pass + + if dll_load_time_field: + # Versions prior to 6.1 won't have the LoadTime attribute + # and 32bit version shouldn't have the Quadpart according to MSDN + try: + DllLoadTime = conversion.wintime_to_datetime(entry.LoadTime.QuadPart) + except exceptions.InvalidAddressException: + DllLoadTime = renderers.UnreadableValue() + else: + DllLoadTime = renderers.NotApplicableValue() + + file_output = "Disabled" + if self.config['dump']: + file_handle = self.dump_pe(self.context, + pe_table_name, + entry, + self.open, + proc_layer_name, + prefix = "pid.{}.".format(proc_id)) + file_output = "Error outputting file" + if file_handle: + file_handle.close() + file_output = file_handle.preferred_filename + + DllBase = SizeOfImage = 0 + try: + DllBase = entry.DllBase + SizeOfImage = entry.SizeOfImage # if the DllBase not detected, the size of file also will not be calculated + + except exceptions.InvalidAddressException: + pass + yield (0, (proc.UniqueProcessId, + proc.ImageFileName.cast("string", + max_length = proc.ImageFileName.vol.count, + errors = 'replace'), + format_hints.Hex(DllBase), + format_hints.Hex(SizeOfImage), BaseDllName, FullDllName, DllLoadTime, file_output)) + + def generate_timeline(self): + for row in self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'])): + _depth, row_data = row + if not isinstance(row_data[6], datetime.datetime): + continue + description = "DLL Load: Process {} {} Loaded {} ({}) Size {} Offset {}".format( + row_data[0], row_data[1], row_data[4], row_data[5], row_data[3], row_data[2]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[6]) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Base", format_hints.Hex), + ("Size", format_hints.Hex), ("Name", str), ("Path", str), + ("LoadTime", datetime.datetime), ("File output", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverirp.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverirp.py new file mode 100644 index 00000000..c77e0801 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverirp.py @@ -0,0 +1,74 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import constants +from volatility.framework import renderers, exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import ssdt, driverscan + +MAJOR_FUNCTIONS = [ + 'IRP_MJ_CREATE', 'IRP_MJ_CREATE_NAMED_PIPE', 'IRP_MJ_CLOSE', 'IRP_MJ_READ', 'IRP_MJ_WRITE', + 'IRP_MJ_QUERY_INFORMATION', 'IRP_MJ_SET_INFORMATION', 'IRP_MJ_QUERY_EA', 'IRP_MJ_SET_EA', 'IRP_MJ_FLUSH_BUFFERS', + 'IRP_MJ_QUERY_VOLUME_INFORMATION', 'IRP_MJ_SET_VOLUME_INFORMATION', 'IRP_MJ_DIRECTORY_CONTROL', + 'IRP_MJ_FILE_SYSTEM_CONTROL', 'IRP_MJ_DEVICE_CONTROL', 'IRP_MJ_INTERNAL_DEVICE_CONTROL', 'IRP_MJ_SHUTDOWN', + 'IRP_MJ_LOCK_CONTROL', 'IRP_MJ_CLEANUP', 'IRP_MJ_CREATE_MAILSLOT', 'IRP_MJ_QUERY_SECURITY', 'IRP_MJ_SET_SECURITY', + 'IRP_MJ_POWER', 'IRP_MJ_SYSTEM_CONTROL', 'IRP_MJ_DEVICE_CHANGE', 'IRP_MJ_QUERY_QUOTA', 'IRP_MJ_SET_QUOTA', + 'IRP_MJ_PNP' +] + + +class DriverIrp(interfaces.plugins.PluginInterface): + """List IRPs for drivers in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.PluginRequirement(name = 'ssdt', plugin = ssdt.SSDT, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'driverscan', plugin = driverscan.DriverScan, version = (1, 0, 0)), + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + ] + + def _generator(self): + + collection = ssdt.SSDT.build_module_collection(self.context, self.config['primary'], self.config['nt_symbols']) + + for driver in driverscan.DriverScan.scan_drivers(self.context, self.config['primary'], + self.config['nt_symbols']): + + try: + driver_name = driver.get_driver_name() + except (ValueError, exceptions.InvalidAddressException): + driver_name = renderers.NotApplicableValue() + + for i, address in enumerate(driver.MajorFunction): + module_symbols = collection.get_module_symbols_by_absolute_location(address) + + for module_name, symbol_generator in module_symbols: + symbols_found = False + + for symbol in symbol_generator: + symbols_found = True + yield (0, (format_hints.Hex(driver.vol.offset), driver_name, MAJOR_FUNCTIONS[i], + format_hints.Hex(address), module_name, symbol.split(constants.BANG)[1])) + + if not symbols_found: + yield (0, (format_hints.Hex(driver.vol.offset), driver_name, MAJOR_FUNCTIONS[i], + format_hints.Hex(address), module_name, renderers.NotAvailableValue())) + + def run(self): + + return renderers.TreeGrid([ + ("Offset", format_hints.Hex), + ("Driver Name", str), + ("IRP", str), + ("Address", format_hints.Hex), + ("Module", str), + ("Symbol", str), + ], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverscan.py new file mode 100644 index 00000000..e3d1bd5b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/driverscan.py @@ -0,0 +1,77 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import poolscanner + + +class DriverScan(interfaces.plugins.PluginInterface): + """Scans for drivers present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)), + ] + + @classmethod + def scan_drivers(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for drivers using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Dri\xf6', b'Driv']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self): + for driver in self.scan_drivers(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + driver_name = driver.get_driver_name() + except (ValueError, exceptions.InvalidAddressException): + driver_name = renderers.NotApplicableValue() + + try: + service_key = driver.DriverExtension.ServiceKeyName.String + except exceptions.InvalidAddressException: + service_key = renderers.NotApplicableValue() + + try: + name = driver.DriverName.String + except exceptions.InvalidAddressException: + name = renderers.NotApplicableValue() + + yield (0, (format_hints.Hex(driver.vol.offset), format_hints.Hex(driver.DriverStart), + format_hints.Hex(driver.DriverSize), service_key, driver_name, name)) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), + ("Start", format_hints.Hex), ("Size", format_hints.Hex), ("Service Key", str), + ("Driver Name", str), ("Name", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/envars.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/envars.py new file mode 100644 index 00000000..50f99659 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/envars.py @@ -0,0 +1,201 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +import logging +from typing import List + +from volatility.framework import renderers, interfaces, objects, exceptions, constants +from volatility.framework.configuration import requirements +from volatility.framework.layers import registry +from volatility.plugins.windows import pslist +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class Envars(interfaces.plugins.PluginInterface): + "Display process environment variables" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True), + requirements.BooleanRequirement(name = 'silent', + description = 'Suppress common and non-persistent variables', + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + def _get_silent_vars(self) -> List[str]: + """Enumerate persistent & common variables. + + This function collects the global (all users) and + user-specific environment variables from the + registry. Any variables in a process env block that + does not exist in the persistent list was explicitly + set with the SetEnvironmentVariable() API. + """ + + values = [] + + for hive in hivelist.HiveList.list_hives(context = self.context, + base_config_path = self.config_path, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + hive_offsets = None): + sys = False + ntuser = False + + ## The global variables + try: + key = hive.get_key('CurrentControlSet\\Control\\Session Manager\\Environment') + sys = True + except KeyError: + try: + key = hive.get_key('ControlSet001\\Control\\Session Manager\\Environment') + sys = True + except KeyError: + pass + if sys: + try: + for node in key.get_values(): + try: + value_node_name = node.get_name() + if value_node_name: + values.append(value_node_name) + except (exceptions.InvalidAddressException, registry.RegistryFormatException) as excp: + vollog.log( + constants.LOGLEVEL_VVV, + "Error while parsing global environment variables keys (some keys might be excluded)") + continue + except KeyError: + pass + + ## The user-specific variables + try: + key = hive.get_key('Environment') + ntuser = True + except KeyError: + pass + if ntuser: + try: + for node in key.get_values(): + try: + value_node_name = node.get_name() + if value_node_name: + values.append(value_node_name) + except (exceptions.InvalidAddressException, registry.RegistryFormatException) as excp: + vollog.log( + constants.LOGLEVEL_VVV, + "Error while parsing user environment variables keys (some keys might be excluded)") + continue + except KeyError: + pass + + ## The volatile user variables + try: + key = hive.get_key('Volatile Environment') + except KeyError: + continue + try: + for node in key.get_values(): + try: + value_node_name = node.get_name() + if value_node_name: + values.append(value_node_name) + except (exceptions.InvalidAddressException, registry.RegistryFormatException) as excp: + vollog.log( + constants.LOGLEVEL_VVV, + "Error while parsing volatile environment variables keys (some keys might be excluded)") + continue + except KeyError: + continue + + ## These are variables set explicitly but are + ## common enough to ignore safely. + values.extend([ + "ProgramFiles", + "CommonProgramFiles", + "SystemDrive", + "SystemRoot", + "ProgramData", + "PUBLIC", + "ALLUSERSPROFILE", + "COMPUTERNAME", + "SESSIONNAME", + "USERNAME", + "USERPROFILE", + "PROMPT", + "USERDOMAIN", + "AppData", + "CommonFiles", + "CommonDesktop", + "CommonProgramGroups", + "CommonStartMenu", + "CommonStartUp", + "Cookies", + "DesktopDirectory", + "Favorites", + "History", + "NetHood", + "PersonalDocuments", + "RecycleBin", + "StartMenu", + "Templates", + "AltStartup", + "CommonFavorites", + "ConnectionWizard", + "DocAndSettingRoot", + "InternetCache", + "windir", + "Path", + "HOMEDRIVE", + "PROCESSOR_ARCHITECTURE", + "NUMBER_OF_PROCESSORS", + "ProgramFiles(x86)", + "CommonProgramFiles(x86)", + "CommonProgramW6432", + "PSModulePath", + "PROCESSOR_IDENTIFIER", + "FP_NO_HOST_CHECK", + "LOCALAPPDATA", + "TMP", + "ProgramW6432", + ]) + + return values + + def _generator(self, data): + silent_vars = [] + if self.config.get('SILENT', None): + silent_vars = self._get_silent_vars() + + for task in data: + for var, val in task.environment_variables(): + if self.config.get('silent', None): + if var in silent_vars: + continue + yield (0, (int(task.UniqueProcessId), str(objects.utility.array_to_string(task.ImageFileName)), + hex(task.get_peb().ProcessParameters.Environment.vol.offset), str(var), str(val))) + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Block", str), ("Variable", str), ("Value", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/filescan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/filescan.py new file mode 100644 index 00000000..3f59e02e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/filescan.py @@ -0,0 +1,63 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import poolscanner + + +class FileScan(interfaces.plugins.PluginInterface): + """Scans for file objects present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)), + ] + + @classmethod + def scan_files(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for file objects using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of File objects as found from the `layer_name` layer based on File pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Fil\xe5', b'File']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self): + for fileobj in self.scan_files(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + file_name = fileobj.FileName.String + except exceptions.InvalidAddressException: + continue + + yield (0, (format_hints.Hex(fileobj.vol.offset), file_name, fileobj.Size)) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Name", str), ("Size", int)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/getservicesids.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/getservicesids.py new file mode 100644 index 00000000..0a5a9682 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/getservicesids.py @@ -0,0 +1,87 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 + +import hashlib +import json +import logging +import os +import struct +from typing import List + +from volatility.framework import renderers, interfaces, constants, exceptions +from volatility.framework.configuration import requirements +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +def createservicesid(svc) -> str: + """ Calculate the Service SID """ + uni = ''.join([c + '\x00' for c in svc]) + sha = hashlib.sha1(uni.upper().encode("utf-8")).digest() # pylint: disable-msg=E1101 + dec = list() + for i in range(5): + ## The use of struct here is OK. It doesn't make much sense + ## to leverage obj.Object inside this loop. + dec.append(struct.unpack(' List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + def _generator(self): + + # Go all over the hives + for hive in hivelist.HiveList.list_hives(context = self.context, + base_config_path = self.config_path, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + hive_offsets = None): + # Get ConrolSet\Services. + try: + services = hive.get_key(r"CurrentControlSet\Services") + except (KeyError, exceptions.InvalidAddressException): + try: + services = hive.get_key(r"ControlSet001\Services") + except (KeyError, exceptions.InvalidAddressException): + continue + + if services: + for s in services.get_subkeys(): + if s.get_name() not in self.servicesids.values(): + sid = createservicesid(s.get_name()) + yield (0, (sid, s.get_name())) + + def run(self): + return renderers.TreeGrid([("SID", str), ("Service", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/getsids.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/getsids.py new file mode 100644 index 00000000..5f7e2dcf --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/getsids.py @@ -0,0 +1,162 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 + +import json +import logging +import ntpath +import os +import re +from typing import List, Dict, Union + +from volatility.framework import renderers, interfaces, objects, exceptions, constants, layers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols.windows.extensions import registry +from volatility.plugins.windows import pslist +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +def find_sid_re(sid_string, sid_re_list) -> Union[str, interfaces.renderers.BaseAbsentValue]: + for reg, name in sid_re_list: + if reg.search(sid_string): + return name + return renderers.NotAvailableValue() + + +class GetSIDs(interfaces.plugins.PluginInterface): + """Print the SIDs owning each process""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for plugin_dir in constants.PLUGINS_PATH: + sids_json_file_name = os.path.join(plugin_dir, os.path.join("windows", "sids_and_privileges.json")) + if os.path.exists(sids_json_file_name): + break + else: + vollog.log(constants.LOGLEVEL_VVV, 'sids_and_privileges.json file is missing plugin error') + raise RuntimeError("The sids_and_privileges.json file missed from you plugin directory") + + # Get all the sids from the json file. + with open(sids_json_file_name, 'r') as file_handle: + sids_json_data = json.load(file_handle) + self.servicesids = sids_json_data['service sids'] + self.well_known_sids = sids_json_data['well known'] + + # Compile all the sids regex. + self.well_known_sid_re = [(re.compile(c_list[0]), c_list[1]) for c_list in sids_json_data['sids re']] + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + def lookup_user_sids(self) -> Dict[str, str]: + """ + Enumerate the registry for all the users. + + Returns: + An dictionary of {sid: user name} + """ + + key = "Microsoft\\Windows NT\\CurrentVersion\\ProfileList" + val = "ProfileImagePath" + + sids = {} + for hive in hivelist.HiveList.list_hives(context = self.context, + base_config_path = self.config_path, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + hive_offsets = None): + + try: + for subkey in hive.get_key(key).get_subkeys(): + sid = str(subkey.get_name()) + path = "" + for node in subkey.get_values(): + try: + value_node_name = node.get_name() or "(Default)" + except (exceptions.InvalidAddressException, layers.registry.RegistryFormatException) as excp: + continue + try: + value_data = node.decode_data() + if isinstance(value_data, int): + value_data = format_hints.MultiTypeData(value_data, encoding = 'utf-8') + elif registry.RegValueTypes.get(node.Type) == registry.RegValueTypes.REG_BINARY: + value_data = format_hints.MultiTypeData(value_data, show_hex = True) + elif registry.RegValueTypes.get(node.Type) == registry.RegValueTypes.REG_MULTI_SZ: + value_data = format_hints.MultiTypeData(value_data, + encoding = 'utf-16-le', + split_nulls = True) + else: + value_data = format_hints.MultiTypeData(value_data, encoding = 'utf-16-le') + if value_node_name == val: + path = str(value_data).replace('\\x00', '')[:-1] + user = ntpath.basename(path) + sids[sid] = user + except (ValueError, exceptions.InvalidAddressException, + layers.registry.RegistryFormatException) as excp: + continue + except (KeyError, exceptions.InvalidAddressException): + continue + + return sids + + def _generator(self, procs): + + user_sids = self.lookup_user_sids() + + # Go all over the process list, get the token + for task in procs: + # Make sure we have a valid token + try: + token = task.Token.dereference().cast("_TOKEN") + except exceptions.InvalidAddressException: + token = False + + if not token or not isinstance(token, interfaces.objects.ObjectInterface): + yield (0, [int(task.UniqueProcessId), str(task.ImageFileName), "Token unreadable", ""]) + continue + + # Go all over the sids and try to translate them with one of the tables we have + for sid_string in token.get_sids(): + if sid_string in self.well_known_sids: + sid_name = self.well_known_sids[sid_string] + elif sid_string in self.servicesids: + sid_name = self.servicesids[sid_string] + elif sid_string in user_sids: + sid_name = user_sids[sid_string] + else: + sid_name_re = find_sid_re(sid_string, self.well_known_sid_re) + if sid_name_re: + sid_name = sid_name_re + else: + sid_name = "" + + yield (0, (task.UniqueProcessId, objects.utility.array_to_string(task.ImageFileName), sid_string, + sid_name)) + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("SID", str), ("Name", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/handles.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/handles.py new file mode 100644 index 00000000..b32258bb --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/handles.py @@ -0,0 +1,351 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List, Optional, Dict + +from volatility.framework import constants, exceptions, renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + +try: + import capstone + + has_capstone = True +except ImportError: + has_capstone = False + + +class Handles(interfaces.plugins.PluginInterface): + """Lists process open handles.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._sar_value = None + self._type_map = None + self._cookie = None + self._level_mask = 7 + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)) + ] + + def _decode_pointer(self, value, magic): + """Windows encodes pointers to objects and decodes them on the fly + before using them. + + This function mimics the decoding routine so we can generate the + proper pointer values as well. + """ + + value = value & 0xFFFFFFFFFFFFFFF8 + value = value >> magic + # if (value & (1 << 47)): + # value = value | 0xFFFF000000000000 + + return value + + def _get_item(self, handle_table_entry, handle_value): + """Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a + process' handle table, determine where the corresponding object's + _OBJECT_HEADER can be found.""" + + virtual = self.config["primary"] + + try: + # before windows 7 + + if not self.context.layers[virtual].is_valid(handle_table_entry.Object): + return None + + fast_ref = handle_table_entry.Object.cast("_EX_FAST_REF") + object_header = fast_ref.dereference().cast("_OBJECT_HEADER") + object_header.GrantedAccess = handle_table_entry.GrantedAccess + except AttributeError: + # starting with windows 8 + + if handle_table_entry.LowValue == 0: + return None + + magic = self.find_sar_value() + # is this the right thing to raise here? + if magic is None: + return None + #if not has_capstone: + # raise AttributeError("Unable to find the SAR value for decoding handle table pointers") + #else: + # raise exceptions.MissingModuleException("capstone","Unable to find the SAR value for decoding handle table pointers") + + offset = self._decode_pointer(handle_table_entry.LowValue, magic) + # print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset)) + object_header = self.context.object(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER", + virtual, + offset = offset) + object_header.GrantedAccess = handle_table_entry.GrantedAccessBits + + object_header.HandleValue = handle_value + return object_header + + def find_sar_value(self): + """Locate ObpCaptureHandleInformationEx if it exists in the sample. + + Once found, parse it for the SAR value that we need to decode + pointers in the _HANDLE_TABLE_ENTRY which allows us to find the + associated _OBJECT_HEADER. + """ + + if self._sar_value is None: + + if not has_capstone: + return None + + virtual_layer_name = self.config['primary'] + kvo = self.context.layers[virtual_layer_name].config['kernel_virtual_offset'] + ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual_layer_name, offset = kvo) + + try: + func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address + except exceptions.SymbolError: + return None + + data = self.context.layers.read(virtual_layer_name, kvo + func_addr, 0x200) + if data is None: + return None + + md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) + + for (address, size, mnemonic, op_str) in md.disasm_lite(data, kvo + func_addr): + # print("{} {} {} {}".format(address, size, mnemonic, op_str)) + + if mnemonic.startswith("sar"): + # if we don't want to parse op strings, we can disasm the + # single sar instruction again, but we use disasm_lite for speed + self._sar_value = int(op_str.split(",")[1].strip(), 16) + break + + return self._sar_value + + @classmethod + def get_type_map(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> Dict[int, str]: + """List the executive object types (_OBJECT_TYPE) using the + ObTypeIndexTable or ObpObjectTypes symbol (differs per OS). This method + will be necessary for determining what type of object we have given an + object header. + + Note: + The object type index map was hard coded into profiles in previous versions of volatility. + It is now generated dynamically. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A mapping of type indicies to type names + """ + + type_map = {} # type: Dict[int, str] + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address + except exceptions.SymbolError: + table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address + + trans_layer = context.layers[layer_name] + + if not trans_layer.is_valid(kvo + table_addr): + return type_map + + ptrs = ntkrnlmp.object(object_type = "array", + offset = table_addr, + subtype = ntkrnlmp.get_type("pointer"), + count = 100) + + for i, ptr in enumerate(ptrs): # type: ignore + # the first entry in the table is always null. break the + # loop when we encounter the first null entry after that + if i > 0 and ptr == 0: + break + + try: + objt = ptr.dereference().cast(symbol_table + constants.BANG + "_OBJECT_TYPE") + type_name = objt.Name.String + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, + "Cannot access _OBJECT_HEADER Name at {0:#x}".format(objt.vol.offset)) + continue + + type_map[i] = type_name + + return type_map + + @classmethod + def find_cookie(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> Optional[interfaces.objects.ObjectInterface]: + """Find the ObHeaderCookie value (if it exists)""" + + try: + offset = context.symbol_space.get_symbol(symbol_table + constants.BANG + "ObHeaderCookie").address + except exceptions.SymbolError: + return None + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + return context.object(symbol_table + constants.BANG + "unsigned int", layer_name, offset = kvo + offset) + + def _make_handle_array(self, offset, level, depth = 0): + """Parse a process' handle table and yield valid handle table entries, + going as deep into the table "levels" as necessary.""" + virtual = self.config["primary"] + kvo = self.context.layers[virtual].config['kernel_virtual_offset'] + + ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual, offset = kvo) + + if level > 0: + subtype = ntkrnlmp.get_type("pointer") + count = 0x1000 / subtype.size + else: + subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY") + count = 0x1000 / subtype.size + + if not self.context.layers[virtual].is_valid(offset): + yield None + + table = ntkrnlmp.object(object_type = "array", + offset = offset, + subtype = subtype, + count = int(count), + absolute = True) + + layer_object = self.context.layers[virtual] + masked_offset = (offset & layer_object.maximum_address) + + for entry in table: + + if level > 0: + for x in self._make_handle_array(entry, level - 1, depth): + yield x + depth += 1 + else: + handle_multiplier = 4 + handle_level_base = depth * count * handle_multiplier + + handle_value = ((entry.vol.offset - masked_offset) / + (subtype.size / handle_multiplier)) + handle_level_base + + item = self._get_item(entry, handle_value) + + if item is None: + continue + try: + if item.TypeIndex != 0x0: + yield item + except AttributeError: + if item.Type.Name: + yield item + except exceptions.InvalidAddressException: + continue + + def handles(self, handle_table): + + try: + TableCode = handle_table.TableCode & ~self._level_mask + table_levels = handle_table.TableCode & self._level_mask + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception") + return None + + for handle_table_entry in self._make_handle_array(TableCode, table_levels): + yield handle_table_entry + + def _generator(self, procs): + + type_map = self.get_type_map(context = self.context, + layer_name = self.config["primary"], + symbol_table = self.config["nt_symbols"]) + + cookie = self.find_cookie(context = self.context, + layer_name = self.config["primary"], + symbol_table = self.config["nt_symbols"]) + + for proc in procs: + try: + object_table = proc.ObjectTable + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, + "Cannot access _EPROCESS.ObjectType at {0:#x}".format(proc.vol.offset)) + continue + + process_name = utility.array_to_string(proc.ImageFileName) + for entry in self.handles(object_table): + try: + if entry is None: + break + obj_type = entry.get_object_type(type_map, cookie) + + if obj_type is None: + continue + if obj_type == "File": + item = entry.Body.cast("_FILE_OBJECT") + obj_name = item.file_name_with_device() + elif obj_type == "Process": + item = entry.Body.cast("_EPROCESS") + obj_name = "{} Pid {}".format(utility.array_to_string(proc.ImageFileName), item.UniqueProcessId) + elif obj_type == "Thread": + item = entry.Body.cast("_ETHREAD") + obj_name = "Tid {} Pid {}".format(item.Cid.UniqueThread, item.Cid.UniqueProcess) + elif obj_type == "Key": + item = entry.Body.cast("_CM_KEY_BODY") + obj_name = item.get_full_key_name() + else: + try: + obj_name = entry.NameInfo.Name.String + except (ValueError, exceptions.InvalidAddressException): + obj_name = "" + + + + except (exceptions.InvalidAddressException): + vollog.log(constants.LOGLEVEL_VVV, + "Cannot access _OBJECT_HEADER at {0:#x}".format(entry.vol.offset)) + continue + + yield (0, (proc.UniqueProcessId, process_name, format_hints.Hex(entry.Body.vol.offset), + format_hints.Hex(entry.HandleValue), obj_type, format_hints.Hex(entry.GrantedAccess), + obj_name)) + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Offset", format_hints.Hex), + ("HandleValue", format_hints.Hex), ("Type", str), + ("GrantedAccess", format_hints.Hex), ("Name", str)], + self._generator( + pslist.PsList.list_processes(self.context, + self.config['primary'], + self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/hashdump.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/hashdump.py new file mode 100644 index 00000000..64e312bb --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/hashdump.py @@ -0,0 +1,297 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import binascii +import hashlib +import logging +from struct import unpack, pack +from typing import List, Tuple, Optional + +from Crypto.Cipher import ARC4, DES, AES +from Crypto.Hash import MD5 + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.symbols.windows.extensions import registry +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class Hashdump(interfaces.plugins.PluginInterface): + """Dumps user hashes from memory""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + odd_parity = [ + 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, + 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, + 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, + 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107, 107, 109, 109, 110, + 110, 112, 112, 115, 115, 117, 117, 118, 118, 121, 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, + 133, 134, 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148, 148, 151, 151, 152, 152, 155, + 155, 157, 157, 158, 158, 161, 161, 162, 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176, + 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191, 191, 193, 193, 194, 194, 196, 196, 199, + 199, 200, 200, 203, 203, 205, 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218, 218, 220, + 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233, 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, + 242, 244, 244, 247, 247, 248, 248, 251, 251, 253, 253, 254, 254 + ] + + # Permutation matrix for boot key + bootkey_perm_table = [0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7] + + # Constants for SAM decrypt algorithm + aqwerty = b"!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" + anum = b"0123456789012345678901234567890123456789\0" + antpassword = b"NTPASSWORD\0" + almpassword = b"LMPASSWORD\0" + lmkey = b"KGS!@#$%" + + empty_lm = b"\xaa\xd3\xb4\x35\xb5\x14\x04\xee\xaa\xd3\xb4\x35\xb5\x14\x04\xee" + empty_nt = b"\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0" + + @classmethod + def get_user_keys(cls, samhive: registry.RegistryHive) -> List[interfaces.objects.ObjectInterface]: + user_key_path = "SAM\\Domains\\Account\\Users" + + user_key = samhive.get_key(user_key_path) + if not user_key: + return [] + return [k for k in user_key.get_subkeys() if k.Name != "Names"] + + @classmethod + def get_bootkey(cls, syshive: registry.RegistryHive) -> Optional[bytes]: + cs = 1 + lsa_base = "ControlSet{0:03}".format(cs) + "\\Control\\Lsa" + lsa_keys = ["JD", "Skew1", "GBG", "Data"] + + lsa = syshive.get_key(lsa_base) + + if not lsa: + return None + + bootkey = '' + + for lk in lsa_keys: + key = syshive.get_key(lsa_base + '\\' + lk) + + class_data = syshive.read(key.Class + 4, key.ClassLength) + + if class_data is None: + return None + bootkey += class_data.decode('utf-16-le') + + bootkey_str = binascii.unhexlify(bootkey) + bootkey_scrambled = bytes([bootkey_str[cls.bootkey_perm_table[i]] for i in range(len(bootkey_str))]) + return bootkey_scrambled + + @classmethod + def get_hbootkey(cls, samhive: registry.RegistryHive, bootkey: bytes) -> Optional[bytes]: + sam_account_path = "SAM\\Domains\\Account" + + if not bootkey: + return None + + sam_account_key = samhive.get_key(sam_account_path) + if not sam_account_key: + return None + + sam_data = None + for v in sam_account_key.get_values(): + if v.get_name() == 'F': + sam_data = samhive.read(v.Data + 4, v.DataLength) + if not sam_data: + return None + + revision = sam_data[0x00] + if revision == 2: + md5 = hashlib.md5() + + md5.update(sam_data[0x70:0x80] + cls.aqwerty + bootkey + cls.anum) + rc4_key = md5.digest() + + rc4 = ARC4.new(rc4_key) + hbootkey = rc4.encrypt(sam_data[0x80:0xA0]) + return hbootkey + elif revision == 3: + # AES encrypted + iv = sam_data[0x78:0x88] + encryptedHBootKey = sam_data[0x88:0xA8] + cipher = AES.new(bootkey, AES.MODE_CBC, iv) + hbootkey = cipher.decrypt(encryptedHBootKey) + return hbootkey[:16] + return None + + @classmethod + def decrypt_single_salted_hash(cls, rid, hbootkey: bytes, enc_hash: bytes, lmntstr, salt: bytes) -> Optional[bytes]: + (des_k1, des_k2) = cls.sid_to_key(rid) + des1 = DES.new(des_k1, DES.MODE_ECB) + des2 = DES.new(des_k2, DES.MODE_ECB) + cipher = AES.new(hbootkey[:16], AES.MODE_CBC, salt) + obfkey = cipher.decrypt(enc_hash) + return des1.decrypt(obfkey[:8]) + des2.decrypt(obfkey[8:16]) + + @classmethod + def get_user_hashes(cls, user: registry.CM_KEY_NODE, samhive: registry.RegistryHive, + hbootkey: bytes) -> Tuple[bytes, bytes]: + ## Will sometimes find extra user with rid = NAMES, returns empty strings right now + try: + rid = int(str(user.get_name()), 16) + except ValueError: + return None + sam_data = None + for v in user.get_values(): + if v.get_name() == 'V': + sam_data = samhive.read(v.Data + 4, v.DataLength) + if not sam_data: + return None + + lm_offset = unpack(" Tuple[bytes, bytes]: + """Takes rid of a user and converts it to a key to be used by the DES cipher""" + bytestr1 = [sid & 0xFF, (sid >> 8) & 0xFF, (sid >> 16) & 0xFF, (sid >> 24) & 0xFF] + bytestr1 += bytestr1[0:3] + bytestr2 = [bytestr1[3]] + bytestr1[0:3] + bytestr2 += bytestr2[0:3] + return cls.sidbytes_to_key(bytes(bytestr1)), cls.sidbytes_to_key(bytes(bytestr2)) + + @classmethod + def sidbytes_to_key(cls, s: bytes) -> bytes: + """Builds final DES key from the strings generated in sid_to_key""" + key = [] + key.append(s[0] >> 1) + key.append(((s[0] & 0x01) << 6) | (s[1] >> 2)) + key.append(((s[1] & 0x03) << 5) | (s[2] >> 3)) + key.append(((s[2] & 0x07) << 4) | (s[3] >> 4)) + key.append(((s[3] & 0x0F) << 3) | (s[4] >> 5)) + key.append(((s[4] & 0x1F) << 2) | (s[5] >> 6)) + key.append(((s[5] & 0x3F) << 1) | (s[6] >> 7)) + key.append(s[6] & 0x7F) + for i in range(8): + key[i] = (key[i] << 1) + key[i] = cls.odd_parity[key[i]] + return bytes(key) + + @classmethod + def decrypt_single_hash(cls, rid, hbootkey, enc_hash: bytes, lmntstr): + (des_k1, des_k2) = cls.sid_to_key(rid) + des1 = DES.new(des_k1, DES.MODE_ECB) + des2 = DES.new(des_k2, DES.MODE_ECB) + md5 = MD5.new() + + md5.update(hbootkey[:0x10] + pack(" Optional[bytes]: + V = None + for v in user.get_values(): + if v.get_name() == 'V': + V = samhive.read(v.Data + 4, v.DataLength) + if not V: + return None + + name_offset = unpack(" len(V): + return None + + username = V[name_offset:name_offset + name_length] + return username + + # replaces the dump_hashes method in vol2 + def _generator(self, syshive: registry.RegistryHive, samhive: registry.RegistryHive): + if syshive is None: + vollog.debug("SYSTEM address is None: Did you use the correct profile?") + yield (0, (renderers.NotAvailableValue(), renderers.NotAvailableValue(), renderers.NotAvailableValue(), + renderers.NotAvailableValue())) + if samhive is None: + vollog.debug("SAM address is None: Did you use the correct profile?") + yield (0, (renderers.NotAvailableValue(), renderers.NotAvailableValue(), renderers.NotAvailableValue(), + renderers.NotAvailableValue())) + bootkey = self.get_bootkey(syshive) + hbootkey = self.get_hbootkey(samhive, bootkey) + if hbootkey: + for user in self.get_user_keys(samhive): + ret = self.get_user_hashes(user, samhive, hbootkey) + if ret: + lmhash, nthash = ret + + ## temporary fix to prevent UnicodeDecodeError backtraces + ## however this can cause truncated user names as a result + name = self.get_user_name(user, samhive) + if name is None: + name = renderers.NotAvailableValue() + else: + name = str(name, 'utf-16-le', errors = 'ignore') + + lmout = str(binascii.hexlify(lmhash or self.empty_lm), 'latin-1') + ntout = str(binascii.hexlify(nthash or self.empty_nt), 'latin-1') + rid = int(str(user.get_name()), 16) + yield (0, (name, rid, lmout, ntout)) + else: + raise ValueError("Hbootkey is not valid") + + def run(self): + offset = self.config.get('offset', None) + syshive = None + samhive = None + for hive in hivelist.HiveList.list_hives(self.context, + self.config_path, + self.config['primary'], + self.config['nt_symbols'], + hive_offsets = None if offset is None else [offset]): + + if hive.get_name().split('\\')[-1].upper() == 'SYSTEM': + syshive = hive + if hive.get_name().split('\\')[-1].upper() == 'SAM': + samhive = hive + + return renderers.TreeGrid([("User", str), ("rid", int), ("lmhash", str), ("nthash", str)], + self._generator(syshive, samhive)) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/info.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/info.py new file mode 100644 index 00000000..90ede538 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/info.py @@ -0,0 +1,206 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import time +from typing import List, Tuple, Iterable + +from volatility.framework import constants, interfaces, layers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import TreeGrid +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import extensions + + +class Info(plugins.PluginInterface): + """Show OS & kernel details of the memory sample being analyzed.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols") + ] + + @classmethod + def get_depends(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + index: int = 0) -> Iterable[Tuple[int, interfaces.layers.DataLayerInterface]]: + """List the dependencies of a given layer. + + Args: + context: The context to retrieve required layers from + layer_name: the name of the starting layer + index: the index/order of the layer + + Returns: + An iterable containing the levels and layer objects for all dependent layers + """ + layer = context.layers[layer_name] + yield index, layer + try: + for depends in layer.dependencies: + for j, dep in cls.get_depends(context, depends, index + 1): + yield j, context.layers[dep.name] + except AttributeError: + # FileLayer won't have dependencies + pass + + @classmethod + def get_kernel_module(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str): + """Returns the kernel module based on the layer and symbol_table""" + virtual_layer = context.layers[layer_name] + if not isinstance(virtual_layer, layers.intel.Intel): + raise TypeError("Virtual Layer is not an intel layer") + + kvo = virtual_layer.config["kernel_virtual_offset"] + + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + return ntkrnlmp + + @classmethod + def get_kdbg_structure(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str, + symbol_table: str) -> interfaces.objects.ObjectInterface: + """Returns the KDDEBUGGER_DATA64 structure for a kernel""" + ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + + native_types = context.symbol_space[symbol_table].natives + + kdbg_offset = ntkrnlmp.get_symbol("KdDebuggerDataBlock").address + + kdbg_table_name = intermed.IntermediateSymbolTable.create(context, + interfaces.configuration.path_join( + config_path, 'kdbg'), + "windows", + "kdbg", + native_types = native_types, + class_types = extensions.kdbg.class_types) + + kdbg = context.object(kdbg_table_name + constants.BANG + "_KDDEBUGGER_DATA64", + offset = ntkrnlmp.offset + kdbg_offset, + layer_name = layer_name) + + return kdbg + + @classmethod + def get_kuser_structure(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> interfaces.objects.ObjectInterface: + """Returns the _KUSER_SHARED_DATA structure for a kernel""" + virtual_layer = context.layers[layer_name] + if not isinstance(virtual_layer, layers.intel.Intel): + raise TypeError("Virtual Layer is not an intel layer") + + ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + + # this is a hard-coded address in the Windows OS + if virtual_layer.bits_per_register == 32: + kuser_addr = 0xFFDF0000 + else: + kuser_addr = 0xFFFFF78000000000 + + kuser = ntkrnlmp.object(object_type = "_KUSER_SHARED_DATA", + layer_name = layer_name, + offset = kuser_addr, + absolute = True) + + return kuser + + @classmethod + def get_version_structure(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> interfaces.objects.ObjectInterface: + """Returns the KdVersionBlock information from a kernel""" + ntkrnlmp = cls.get_kernel_module(context, layer_name, symbol_table) + + vers_offset = ntkrnlmp.get_symbol("KdVersionBlock").address + + vers = ntkrnlmp.object(object_type = "_DBGKD_GET_VERSION64", layer_name = layer_name, offset = vers_offset) + + return vers + + @classmethod + def get_ntheader_structure(cls, context: interfaces.context.ContextInterface, config_path: str, + layer_name: str) -> interfaces.objects.ObjectInterface: + """Gets the ntheader structure for the kernel of the specified layer""" + virtual_layer = context.layers[layer_name] + if not isinstance(virtual_layer, layers.intel.Intel): + raise TypeError("Virtual Layer is not an intel layer") + + kvo = virtual_layer.config["kernel_virtual_offset"] + + pe_table_name = intermed.IntermediateSymbolTable.create(context, + interfaces.configuration.path_join(config_path, 'pe'), + "windows", + "pe", + class_types = extensions.pe.class_types) + + dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset = kvo, + layer_name = layer_name) + + nt_header = dos_header.get_nt_header() + + return nt_header + + def _generator(self): + + layer_name = self.config['primary'] + symbol_table = self.config['nt_symbols'] + + kdbg = self.get_kdbg_structure(self.context, self.config_path, layer_name, symbol_table) + + yield (0, ("Kernel Base", hex(self.config["primary.kernel_virtual_offset"]))) + yield (0, ("DTB", hex(self.config["primary.page_map_offset"]))) + yield (0, ("Symbols", self.config["nt_symbols.isf_url"])) + + for i, layer in self.get_depends(self.context, "primary"): + yield (0, (layer.name, "{} {}".format(i, layer.__class__.__name__))) + + if kdbg.Header.OwnerTag == 0x4742444B: + + yield (0, ("KdDebuggerDataBlock", hex(kdbg.vol.offset))) + yield (0, ("NTBuildLab", kdbg.get_build_lab())) + yield (0, ("CSDVersion", str(kdbg.get_csdversion()))) + + vers = self.get_version_structure(self.context, layer_name, symbol_table) + + yield (0, ("KdVersionBlock", hex(vers.vol.offset))) + yield (0, ("Major/Minor", "{0}.{1}".format(vers.MajorVersion, vers.MinorVersion))) + yield (0, ("MachineType", str(vers.MachineType))) + + ntkrnlmp = self.get_kernel_module(self.context, layer_name, symbol_table) + + cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address + + cpu_count = ntkrnlmp.object(object_type = "unsigned int", layer_name = layer_name, offset = cpu_count_offset) + + yield (0, ("KeNumberProcessors", str(cpu_count))) + + kuser = self.get_kuser_structure(self.context, layer_name, symbol_table) + + yield (0, ("SystemTime", str(kuser.SystemTime.get_time()))) + yield (0, ("NtSystemRoot", + str(kuser.NtSystemRoot.cast("string", encoding = "utf-16", errors = "replace", max_length = 260)))) + yield (0, ("NtProductType", str(kuser.NtProductType.description))) + yield (0, ("NtMajorVersion", str(kuser.NtMajorVersion))) + yield (0, ("NtMinorVersion", str(kuser.NtMinorVersion))) + # yield (0, ("KdDebuggerEnabled", "True" if kuser.KdDebuggerEnabled else "False")) + # yield (0, ("SafeBootMode", "True" if kuser.SafeBootMode else "False")) + + nt_header = self.get_ntheader_structure(self.context, self.config_path, layer_name) + + yield (0, ("PE MajorOperatingSystemVersion", str(nt_header.OptionalHeader.MajorOperatingSystemVersion))) + yield (0, ("PE MinorOperatingSystemVersion", str(nt_header.OptionalHeader.MinorOperatingSystemVersion))) + + yield (0, ("PE Machine", str(nt_header.FileHeader.Machine))) + yield (0, ("PE TimeDateStamp", time.asctime(time.gmtime(nt_header.FileHeader.TimeDateStamp)))) + + def run(self): + + return TreeGrid([("Variable", str), ("Value", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/lsadump.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/lsadump.py new file mode 100644 index 00000000..a0b67485 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/lsadump.py @@ -0,0 +1,189 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from struct import unpack + +from Crypto.Cipher import ARC4, DES, AES +from Crypto.Hash import MD5, SHA256 + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.symbols.windows import versions +from volatility.plugins.windows import hashdump +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class Lsadump(interfaces.plugins.PluginInterface): + """Dumps lsa secrets from memory""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + @classmethod + def decrypt_aes(cls, secret, key): + """ + Based on code from http://lab.mediaservice.net/code/cachedump.rb + """ + sha = SHA256.new() + sha.update(key) + for _i in range(1, 1000 + 1): + sha.update(secret[28:60]) + aeskey = sha.digest() + + data = b"" + for i in range(60, len(secret), 16): + aes = AES.new(aeskey, AES.MODE_CBC, b'\x00' * 16) + buf = secret[i:i + 16] + if len(buf) < 16: + buf += (16 - len(buf)) * "\00" + data += aes.decrypt(buf) + + return data + + @classmethod + def get_lsa_key(cls, sechive, bootkey, vista_or_later): + if not bootkey: + return None + + if vista_or_later: + policy_key = 'PolEKList' + else: + policy_key = 'PolSecretEncryptionKey' + + enc_reg_key = sechive.get_key("Policy\\" + policy_key) + if not enc_reg_key: + return None + enc_reg_value = next(enc_reg_key.get_values()) + + if not enc_reg_value: + return None + + obf_lsa_key = sechive.read(enc_reg_value.Data + 4, enc_reg_value.DataLength) + + if not obf_lsa_key: + return None + if not vista_or_later: + md5 = MD5.new() + md5.update(bootkey) + for _i in range(1000): + md5.update(obf_lsa_key[60:76]) + rc4key = md5.digest() + + rc4 = ARC4.new(rc4key) + lsa_key = rc4.decrypt(obf_lsa_key[12:60]) + lsa_key = lsa_key[0x10:0x20] + else: + lsa_key = cls.decrypt_aes(obf_lsa_key, bootkey) + lsa_key = lsa_key[68:100] + return lsa_key + + @classmethod + def get_secret_by_name(cls, sechive, name, lsakey, is_vista_or_later): + try: + enc_secret_key = sechive.get_key("Policy\\Secrets\\" + name + "\\CurrVal") + except KeyError: + raise ValueError("Unable to read cache from memory") + + enc_secret_value = next(enc_secret_key.get_values()) + if not enc_secret_value: + return None + + enc_secret = sechive.read(enc_secret_value.Data + 4, enc_secret_value.DataLength) + if not enc_secret: + return None + + if not is_vista_or_later: + secret = cls.decrypt_secret(enc_secret[0xC:], lsakey) + else: + secret = cls.decrypt_aes(enc_secret, lsakey) + return secret + + @classmethod + def decrypt_secret(cls, secret, key): + """Python implementation of SystemFunction005. + + Decrypts a block of data with DES using given key. + Note that key can be longer than 7 bytes.""" + decrypted_data = b'' + j = 0 # key index + + for i in range(0, len(secret), 8): + enc_block = secret[i:i + 8] + block_key = key[j:j + 7] + des_key = hashdump.Hashdump.sidbytes_to_key(block_key) + des = DES.new(des_key, DES.MODE_ECB) + enc_block = enc_block + b"\x00" * int(abs(8 - len(enc_block)) % 8) + decrypted_data += des.decrypt(enc_block) + j += 7 + if len(key[j:j + 7]) < 7: + j = len(key[j:j + 7]) + + (dec_data_len, ) = unpack(" Iterable[Tuple[interfaces.objects.ObjectInterface, bytes]]: + """Generate memory regions for a process that may contain injected + code. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + kernel_layer_name: The name of the kernel layer from which to read the VAD protections + symbol_table: The name of the table containing the kernel symbols + proc: an _EPROCESS instance + + Returns: + An iterable of VAD instances and the first 64 bytes of data containing in that region + """ + proc_id = "Unknown" + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, + excp.layer_name)) + return + + proc_layer = context.layers[proc_layer_name] + + for vad in proc.get_vad_root().traverse(): + protection_string = vad.get_protection( + vadinfo.VadInfo.protect_values(context, kernel_layer_name, symbol_table), vadinfo.winnt_protections) + write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string + + # the write/exec check applies to everything + if not write_exec: + continue + + if (vad.get_private_memory() == 1 + and vad.get_tag() == "VadS") or (vad.get_private_memory() == 0 + and protection_string != "PAGE_EXECUTE_WRITECOPY"): + if cls.is_vad_empty(proc_layer, vad): + continue + + data = proc_layer.read(vad.get_start(), 64, pad = True) + yield vad, data + + def _generator(self, procs): + # determine if we're on a 32 or 64 bit kernel + is_32bit_arch = not symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"]) + + for proc in procs: + process_name = utility.array_to_string(proc.ImageFileName) + + for vad, data in self.list_injections(self.context, self.config["primary"], self.config["nt_symbols"], + proc): + + # if we're on a 64 bit kernel, we may still need 32 bit disasm due to wow64 + if is_32bit_arch or proc.get_is_wow64(): + architecture = "intel" + else: + architecture = "intel64" + + disasm = interfaces.renderers.Disassembly(data, vad.get_start(), architecture) + + file_output = "Disabled" + if self.config['dump']: + file_output = "Error outputting to file" + try: + file_handle = vadinfo.VadInfo.vad_dump(self.context, proc, vad, self.open) + file_handle.close() + file_output = file_handle.preferred_filename + except (exceptions.InvalidAddressException, OverflowError) as excp: + vollog.debug("Unable to dump PE with pid {0}.{1:#x}: {2}".format( + proc.UniqueProcessId, vad.get_start(), excp)) + + yield (0, (proc.UniqueProcessId, process_name, format_hints.Hex(vad.get_start()), + format_hints.Hex(vad.get_end()), vad.get_tag(), + vad.get_protection( + vadinfo.VadInfo.protect_values(self.context, self.config["primary"], + self.config["nt_symbols"]), + vadinfo.winnt_protections), vad.get_commit_charge(), vad.get_private_memory(), + file_output, format_hints.HexBytes(data), disasm)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Start VPN", format_hints.Hex), + ("End VPN", format_hints.Hex), ("Tag", str), ("Protection", str), + ("CommitCharge", int), ("PrivateMemory", int), ("File output", str), + ("Hexdump", format_hints.HexBytes), ("Disasm", interfaces.renderers.Disassembly)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/memmap.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/memmap.py new file mode 100644 index 00000000..627baca2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/memmap.py @@ -0,0 +1,81 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import exceptions, renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class Memmap(interfaces.plugins.PluginInterface): + """Prints the memory map""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.IntRequirement(name = 'pid', + description = "Process ID to include (all other processes are excluded)", + optional = True), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed memory segments", + default = False, + optional = True) + ] + + def _generator(self, procs): + for proc in procs: + pid = "Unknown" + + try: + pid = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + proc_layer = self.context.layers[proc_layer_name] + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format(pid, excp.invalid_address, + excp.layer_name)) + continue + + file_handle = self.open("pid.{}.dmp".format(pid)) + with file_handle as file_data: + + for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True): + offset, size, mapped_offset, mapped_size, maplayer = mapval + + file_output = "Disabled" + if self.config['dump']: + try: + data = proc_layer.read(offset, size, pad = True) + file_data.write(data) + file_output = file_handle.preferred_filename + except exceptions.InvalidAddressException: + file_output = "Error outputting to file" + vollog.debug("Unable to write {}'s address {} to {}".format( + proc_layer_name, offset, file_handle.preferred_filename)) + + yield (0, (format_hints.Hex(offset), format_hints.Hex(mapped_offset), format_hints.Hex(mapped_size), + format_hints.Hex(offset), file_output)) + offset += mapped_size + + def run(self): + filter_func = pslist.PsList.create_pid_filter([self.config.get('pid', None)]) + + return renderers.TreeGrid([("Virtual", format_hints.Hex), ("Physical", format_hints.Hex), + ("Size", format_hints.Hex), ("Offset", format_hints.Hex), ("File output", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/modscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/modscan.py new file mode 100644 index 00000000..3ff0f399 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/modscan.py @@ -0,0 +1,179 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import Iterable, List, Generator + +from volatility.framework import renderers, interfaces, exceptions, constants +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins.windows import poolscanner, dlllist, pslist + +vollog = logging.getLogger(__name__) + + +class ModScan(interfaces.plugins.PluginInterface): + """Scans for modules present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.VersionRequirement(name = 'poolerscanner', + component = poolscanner.PoolScanner, + version = (1, 0, 0)), + requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed modules", + default = False, + optional = True) + ] + + @classmethod + def scan_modules(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for modules using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of Driver objects as found from the `layer_name` layer based on Driver pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'MmLd']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + @classmethod + def get_session_layers(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + pids: List[int] = None) -> Generator[str, None, None]: + """Build a cache of possible virtual layers, in priority starting with + the primary/kernel layer. Then keep one layer per session by cycling + through the process list. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + pids: A list of process identifiers to include exclusively or None for no filter + + Returns: + A list of session layer names + """ + seen_ids = [] # type: List[interfaces.objects.ObjectInterface] + filter_func = pslist.PsList.create_pid_filter(pids or []) + + for proc in pslist.PsList.list_processes(context = context, + layer_name = layer_name, + symbol_table = symbol_table, + filter_func = filter_func): + proc_id = "Unknown" + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + + # create the session space object in the process' own layer. + # not all processes have a valid session pointer. + session_space = context.object(symbol_table + constants.BANG + "_MM_SESSION_SPACE", + layer_name = layer_name, + offset = proc.Session) + + if session_space.SessionId in seen_ids: + continue + + except exceptions.InvalidAddressException: + vollog.log( + constants.LOGLEVEL_VVV, + "Process {} does not have a valid Session or a layer could not be constructed for it".format( + proc_id)) + continue + + # save the layer if we haven't seen the session yet + seen_ids.append(session_space.SessionId) + yield proc_layer_name + + @classmethod + def find_session_layer(cls, context: interfaces.context.ContextInterface, session_layers: Iterable[str], + base_address: int): + """Given a base address and a list of layer names, find a layer that + can access the specified address. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + session_layers: A list of session layer names + base_address: The base address to identify the layers that can access it + + Returns: + Layer name or None if no layers that contain the base address can be found + """ + + for layer_name in session_layers: + if context.layers[layer_name].is_valid(base_address): + return layer_name + + return None + + def _generator(self): + session_layers = list(self.get_session_layers(self.context, self.config['primary'], self.config['nt_symbols'])) + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + + for mod in self.scan_modules(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + BaseDllName = mod.BaseDllName.get_string() + except exceptions.InvalidAddressException: + BaseDllName = "" + + try: + FullDllName = mod.FullDllName.get_string() + except exceptions.InvalidAddressException: + FullDllName = "" + + file_output = "Disabled" + if self.config['dump']: + + session_layer_name = self.find_session_layer(self.context, session_layers, mod.DllBase) + file_output = "Cannot find a viable session layer for {0:#x}".format(mod.DllBase) + if session_layer_name: + file_handle = dlllist.DllList.dump_pe(self.context, + pe_table_name, + mod, + self.open, + layer_name = session_layer_name) + file_output = "Error outputting file" + if file_handle: + file_output = file_handle.preferred_filename + + yield (0, (format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase), + format_hints.Hex(mod.SizeOfImage), BaseDllName, FullDllName, file_output)) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Base", format_hints.Hex), ("Size", format_hints.Hex), + ("Name", str), ("Path", str), ("File output", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/modules.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/modules.py new file mode 100644 index 00000000..df2c04fe --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/modules.py @@ -0,0 +1,179 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List, Iterable, Generator + +from volatility.framework import constants +from volatility.framework import exceptions, interfaces +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins.windows import pslist, dlllist + +vollog = logging.getLogger(__name__) + + +class Modules(interfaces.plugins.PluginInterface): + """Lists the loaded kernel modules.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 1, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed modules", + default = False, + optional = True) + ] + + def _generator(self): + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + + for mod in self.list_modules(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + BaseDllName = mod.BaseDllName.get_string() + except exceptions.InvalidAddressException: + BaseDllName = "" + + try: + FullDllName = mod.FullDllName.get_string() + except exceptions.InvalidAddressException: + FullDllName = "" + + file_output = "Disabled" + if self.config['dump']: + file_handle = dlllist.DllList.dump_pe(self.context, pe_table_name, mod, self.open) + file_output = "Error outputting file" + if file_handle: + file_handle.close() + file_output = file_handle.preferred_filename + + yield (0, (format_hints.Hex(mod.vol.offset), format_hints.Hex(mod.DllBase), + format_hints.Hex(mod.SizeOfImage), BaseDllName, FullDllName, file_output)) + + @classmethod + def get_session_layers(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + pids: List[int] = None) -> Generator[str, None, None]: + """Build a cache of possible virtual layers, in priority starting with + the primary/kernel layer. Then keep one layer per session by cycling + through the process list. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + pids: A list of process identifiers to include exclusively or None for no filter + + Returns: + A list of session layer names + """ + seen_ids = [] # type: List[interfaces.objects.ObjectInterface] + filter_func = pslist.PsList.create_pid_filter(pids or []) + + for proc in pslist.PsList.list_processes(context = context, + layer_name = layer_name, + symbol_table = symbol_table, + filter_func = filter_func): + proc_id = "Unknown" + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + + # create the session space object in the process' own layer. + # not all processes have a valid session pointer. + session_space = context.object(symbol_table + constants.BANG + "_MM_SESSION_SPACE", + layer_name = layer_name, + offset = proc.Session) + + if session_space.SessionId in seen_ids: + continue + + except exceptions.InvalidAddressException: + vollog.log( + constants.LOGLEVEL_VVV, + "Process {} does not have a valid Session or a layer could not be constructed for it".format( + proc_id)) + continue + + # save the layer if we haven't seen the session yet + seen_ids.append(session_space.SessionId) + yield proc_layer_name + + @classmethod + def find_session_layer(cls, context: interfaces.context.ContextInterface, session_layers: Iterable[str], + base_address: int): + """Given a base address and a list of layer names, find a layer that + can access the specified address. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + session_layers: A list of session layer names + base_address: The base address to identify the layers that can access it + + Returns: + Layer name or None if no layers that contain the base address can be found + """ + + for layer_name in session_layers: + if context.layers[layer_name].is_valid(base_address): + return layer_name + + return None + + @classmethod + def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> Iterable[interfaces.objects.ObjectInterface]: + """Lists all the modules in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of Modules as retrieved from PsLoadedModuleList + """ + + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + try: + # use this type if its available (starting with windows 10) + ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY") + except exceptions.SymbolError: + ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY") + + type_name = ldr_entry_type.type_name.split(constants.BANG)[1] + + list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address + list_entry = ntkrnlmp.object(object_type = "_LIST_ENTRY", offset = list_head) + reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks") + module = ntkrnlmp.object(object_type = type_name, offset = list_entry.vol.offset - reloff, absolute = True) + + for mod in module.InLoadOrderLinks: + yield mod + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex), ("Base", format_hints.Hex), ("Size", format_hints.Hex), + ("Name", str), ("Path", str), ("File output", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/mutantscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/mutantscan.py new file mode 100644 index 00000000..d9053c44 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/mutantscan.py @@ -0,0 +1,66 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import poolscanner + + +class MutantScan(interfaces.plugins.PluginInterface): + """Scans for mutexes present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)), + ] + + @classmethod + def scan_mutants(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for mutants using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of Mutant objects found by scanning memory for the Mutant pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Mut\xe1', b'Muta']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self): + for mutant in self.scan_mutants(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + name = mutant.get_name() + except (ValueError, exceptions.InvalidAddressException): + name = renderers.NotApplicableValue() + + yield (0, (format_hints.Hex(mutant.vol.offset), name)) + + def run(self): + return renderers.TreeGrid([ + ("Offset", format_hints.Hex), + ("Name", str), + ], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/netscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/netscan.py new file mode 100644 index 00000000..00eb8726 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/netscan.py @@ -0,0 +1,351 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +import logging +from typing import Iterable, List, Optional + +from volatility.framework import constants, exceptions, interfaces, renderers, symbols +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import network +from volatility.plugins import timeliner +from volatility.plugins.windows import info, poolscanner + +vollog = logging.getLogger(__name__) + + +class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Scans for network objects present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.VersionRequirement(name = 'poolscanner', + component = poolscanner.PoolScanner, + version = (1, 0, 0)), + requirements.VersionRequirement(name = 'info', component = info.Info, version = (1, 0, 0)), + requirements.BooleanRequirement( + name = 'include-corrupt', + description = + "Radically eases result validation. This will show partially overwritten data. WARNING: the results are likely to include garbage and/or corrupt data. Be cautious!", + default = False, + optional = True), + ] + + @staticmethod + def create_netscan_constraints(context: interfaces.context.ContextInterface, + symbol_table: str) -> List[poolscanner.PoolConstraint]: + """Creates a list of Pool Tag Constraints for network objects. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the symbols / types + + Returns: + The list containing the built constraints. + """ + + tcpl_size = context.symbol_space.get_type(symbol_table + constants.BANG + "_TCP_LISTENER").size + tcpe_size = context.symbol_space.get_type(symbol_table + constants.BANG + "_TCP_ENDPOINT").size + udpa_size = context.symbol_space.get_type(symbol_table + constants.BANG + "_UDP_ENDPOINT").size + + # ~ vollog.debug("Using pool size constraints: TcpL {}, TcpE {}, UdpA {}".format(tcpl_size, tcpe_size, udpa_size)) + + return [ + # TCP listener + poolscanner.PoolConstraint(b'TcpL', + type_name = symbol_table + constants.BANG + "_TCP_LISTENER", + size = (tcpl_size, None), + page_type = poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE), + # TCP Endpoint + poolscanner.PoolConstraint(b'TcpE', + type_name = symbol_table + constants.BANG + "_TCP_ENDPOINT", + size = (tcpe_size, None), + page_type = poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE), + # UDP Endpoint + poolscanner.PoolConstraint(b'UdpA', + type_name = symbol_table + constants.BANG + "_UDP_ENDPOINT", + size = (udpa_size, None), + page_type = poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE) + ] + + @classmethod + def determine_tcpip_version(cls, context: interfaces.context.ContextInterface, layer_name: str, + nt_symbol_table: str) -> str: + """Tries to determine which symbol filename to use for the image's tcpip driver. The logic is partially taken from the info plugin. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + nt_symbol_table: The name of the table containing the kernel symbols + + Returns: + The filename of the symbol table to use. + """ + + # while the failsafe way to determine the version of tcpip.sys would be to + # extract the driver and parse its PE header containing the versionstring, + # unfortunately that header is not guaranteed to persist within memory. + # therefore we determine the version based on the kernel version as testing + # with several windows versions has showed this to work out correctly. + + is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table) + + if is_64bit: + arch = "x64" + else: + arch = "x86" + + vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table) + + kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table) + + try: + vers_minor_version = int(vers.MinorVersion) + nt_major_version = int(kuser.NtMajorVersion) + nt_minor_version = int(kuser.NtMinorVersion) + except ValueError: + # vers struct exists, but is not an int anymore? + raise NotImplementedError("Kernel Debug Structure version format not supported!") + except: + # unsure what to raise here. Also, it might be useful to add some kind of fallback, + # either to a user-provided version or to another method to determine tcpip.sys's version + raise exceptions.VolatilityException( + "Kernel Debug Structure missing VERSION/KUSER structure, unable to determine Windows version!") + + vollog.debug("Determined OS Version: {}.{} {}.{}".format(kuser.NtMajorVersion, kuser.NtMinorVersion, + vers.MajorVersion, vers.MinorVersion)) + + if nt_major_version == 10 and arch == "x64": + # win10 x64 has an additional class type we have to include. + class_types = network.win10_x64_class_types + else: + # default to general class types + class_types = network.class_types + + # these versions are listed explicitly because symbol files differ based on + # version *and* architecture. this is currently the clearest way to show + # the differences, even if it introduces a fair bit of redundancy. + # furthermore, it is easy to append new versions. + if arch == "x86": + version_dict = { + (6, 0, 6000): "netscan-vista-x86", + (6, 0, 6001): "netscan-vista-x86", + (6, 0, 6002): "netscan-vista-x86", + (6, 0, 6003): "netscan-vista-x86", + (6, 1, 7600): "netscan-win7-x86", + (6, 1, 7601): "netscan-win7-x86", + (6, 1, 8400): "netscan-win7-x86", + (6, 2, 9200): "netscan-win8-x86", + (6, 3, 9600): "netscan-win81-x86", + (10, 0, 10240): "netscan-win10-x86", + (10, 0, 10586): "netscan-win10-x86", + (10, 0, 14393): "netscan-win10-14393-x86", + (10, 0, 15063): "netscan-win10-15063-x86", + (10, 0, 16299): "netscan-win10-15063-x86", + (10, 0, 17134): "netscan-win10-15063-x86", + (10, 0, 17763): "netscan-win10-15063-x86", + (10, 0, 18362): "netscan-win10-15063-x86", + (10, 0, 18363): "netscan-win10-15063-x86" + } + else: + version_dict = { + (6, 0, 6000): "netscan-vista-x64", + (6, 0, 6001): "netscan-vista-sp12-x64", + (6, 0, 6002): "netscan-vista-sp12-x64", + (6, 0, 6003): "netscan-vista-sp12-x64", + (6, 1, 7600): "netscan-win7-x64", + (6, 1, 7601): "netscan-win7-x64", + (6, 1, 8400): "netscan-win7-x64", + (6, 2, 9200): "netscan-win8-x64", + (6, 3, 9600): "netscan-win81-x64", + (10, 0, 10240): "netscan-win10-x64", + (10, 0, 10586): "netscan-win10-x64", + (10, 0, 14393): "netscan-win10-x64", + (10, 0, 15063): "netscan-win10-15063-x64", + (10, 0, 16299): "netscan-win10-15063-x64", + (10, 0, 17134): "netscan-win10-15063-x64", + (10, 0, 17763): "netscan-win10-15063-x64", + (10, 0, 18362): "netscan-win10-15063-x64", + (10, 0, 18363): "netscan-win10-15063-x64" + } + + # when determining the symbol file we have to consider the following cases: + # the determined version's symbol file is found by intermed.create -> proceed + # the determined version's symbol file is not found by intermed -> intermed will throw an exc and abort + # the determined version has no mapped symbol file -> if win10 use latest, otherwise throw exc + # windows version cannot be determined -> throw exc + filename = version_dict.get((nt_major_version, nt_minor_version, vers_minor_version)) + if not filename: + if nt_major_version == 10: + # NtMajorVersion of 10 without a match means a newer version than listed + # hence try the latest supported version. If this one throws an error, + # support has to be added manually. + win_10_versions = sorted([key for key in list(version_dict.keys()) if key[0] == 10]) + # as win10 MinorVersion counts upwards we can take the last entry + latest_version = win_10_versions[-1] + filename = version_dict.get(latest_version) + vollog.debug("Unable to find exact matching symbol file, going with latest: {}".format(filename)) + else: + raise NotImplementedError("This version of Windows is not supported: {}.{} {}.{}!".format( + nt_major_version, nt_minor_version, vers.MajorVersion, vers_minor_version)) + + vollog.debug("Determined symbol filename: {}".format(filename)) + + return filename, class_types + + @classmethod + def create_netscan_symbol_table(cls, context: interfaces.context.ContextInterface, layer_name: str, + nt_symbol_table: str, config_path: str) -> str: + """Creates a symbol table for TCP Listeners and TCP/UDP Endpoints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + nt_symbol_table: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files + + Returns: + The name of the constructed symbol table + """ + table_mapping = {"nt_symbols": nt_symbol_table} + + symbol_filename, class_types = cls.determine_tcpip_version( + context, + layer_name, + nt_symbol_table, + ) + + return intermed.IntermediateSymbolTable.create(context, + config_path, + "windows", + symbol_filename, + class_types = class_types, + table_mapping = table_mapping) + + @classmethod + def scan(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + nt_symbol_table: str, + netscan_symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for network objects using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + nt_symbol_table: The name of the table containing the kernel symbols + netscan_symbol_table: The name of the table containing the network object symbols (_TCP_LISTENER etc.) + + Returns: + A list of network objects found by scanning the `layer_name` layer for network pool signatures + """ + + constraints = cls.create_netscan_constraints(context, netscan_symbol_table) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, nt_symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self, show_corrupt_results: Optional[bool] = None): + """ Generates the network objects for use in rendering. """ + + netscan_symbol_table = self.create_netscan_symbol_table(self.context, self.config["primary"], + self.config["nt_symbols"], self.config_path) + + for netw_obj in self.scan(self.context, self.config['primary'], self.config['nt_symbols'], + netscan_symbol_table): + + vollog.debug("Found netw obj @ 0x{:2x} of assumed type {}".format(netw_obj.vol.offset, type(netw_obj))) + # objects passed pool header constraints. check for additional constraints if strict flag is set. + if not show_corrupt_results and not netw_obj.is_valid(): + continue + + if isinstance(netw_obj, network._UDP_ENDPOINT): + vollog.debug("Found UDP_ENDPOINT @ 0x{:2x}".format(netw_obj.vol.offset)) + + # For UdpA, the state is always blank and the remote end is asterisks + for ver, laddr, _ in netw_obj.dual_stack_sockets(): + yield (0, (format_hints.Hex(netw_obj.vol.offset), "UDP" + ver, laddr, netw_obj.Port, "*", 0, "", + netw_obj.get_owner_pid() or renderers.UnreadableValue(), netw_obj.get_owner_procname() + or renderers.UnreadableValue(), netw_obj.get_create_time() + or renderers.UnreadableValue())) + + elif isinstance(netw_obj, network._TCP_ENDPOINT): + vollog.debug("Found _TCP_ENDPOINT @ 0x{:2x}".format(netw_obj.vol.offset)) + if netw_obj.get_address_family() == network.AF_INET: + proto = "TCPv4" + elif netw_obj.get_address_family() == network.AF_INET6: + proto = "TCPv6" + else: + proto = "TCPv?" + + try: + state = netw_obj.State.description + except ValueError: + state = renderers.UnreadableValue() + + yield (0, (format_hints.Hex(netw_obj.vol.offset), proto, netw_obj.get_local_address() + or renderers.UnreadableValue(), netw_obj.LocalPort, netw_obj.get_remote_address() + or renderers.UnreadableValue(), netw_obj.RemotePort, state, netw_obj.get_owner_pid() + or renderers.UnreadableValue(), netw_obj.get_owner_procname() or renderers.UnreadableValue(), + netw_obj.get_create_time() or renderers.UnreadableValue())) + + # check for isinstance of tcp listener last, because all other objects are inherited from here + elif isinstance(netw_obj, network._TCP_LISTENER): + vollog.debug("Found _TCP_LISTENER @ 0x{:2x}".format(netw_obj.vol.offset)) + + # For TcpL, the state is always listening and the remote port is zero + for ver, laddr, raddr in netw_obj.dual_stack_sockets(): + yield (0, (format_hints.Hex(netw_obj.vol.offset), "TCP" + ver, laddr, netw_obj.Port, raddr, 0, + "LISTENING", netw_obj.get_owner_pid() or renderers.UnreadableValue(), + netw_obj.get_owner_procname() or renderers.UnreadableValue(), netw_obj.get_create_time() + or renderers.UnreadableValue())) + else: + # this should not happen therefore we log it. + vollog.debug("Found network object unsure of its type: {} of type {}".format(netw_obj, type(netw_obj))) + + def generate_timeline(self): + for row in self._generator(): + _depth, row_data = row + # Skip network connections without creation time + if not isinstance(row_data[9], datetime.datetime): + continue + row_data = [ + "N/A" if isinstance(i, renderers.UnreadableValue) or isinstance(i, renderers.UnparsableValue) else i + for i in row_data + ] + description = "Network connection: Process {} {} Local Address {}:{} " \ + "Remote Address {}:{} State {} Protocol {} ".format(row_data[7], row_data[8], + row_data[2], row_data[3], + row_data[4], row_data[5], + row_data[6], row_data[1]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[9]) + + def run(self): + show_corrupt_results = self.config.get('include-corrupt', None) + + return renderers.TreeGrid([ + ("Offset", format_hints.Hex), + ("Proto", str), + ("LocalAddr", str), + ("LocalPort", int), + ("ForeignAddr", str), + ("ForeignPort", int), + ("State", str), + ("PID", int), + ("Owner", str), + ("Created", datetime.datetime), + ], self._generator(show_corrupt_results = show_corrupt_results)) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/poolscanner.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/poolscanner.py new file mode 100644 index 00000000..c888c1f6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/poolscanner.py @@ -0,0 +1,401 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import enum +import logging +from typing import Dict, Generator, List, Optional, Tuple + +from volatility.framework import constants, interfaces, renderers, exceptions, symbols +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins, configuration +from volatility.framework.layers import scanners +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import extensions, versions +from volatility.plugins.windows import handles + +vollog = logging.getLogger(__name__) + + +# TODO: When python3.5 is no longer supported, make this enum.IntFlag +# Revisit the page_type signature of PoolConstraint once using enum.IntFlag +class PoolType(enum.IntEnum): + """Class to maintain the different possible PoolTypes The values must be + integer powers of 2.""" + + PAGED = 1 + NONPAGED = 2 + FREE = 4 + + +class PoolConstraint: + """Class to maintain tag/size/index/type information about Pool header + tags.""" + + def __init__(self, + tag: bytes, + type_name: str, + object_type: Optional[str] = None, + page_type: Optional[int] = None, + size: Optional[Tuple[Optional[int], Optional[int]]] = None, + index: Optional[Tuple[Optional[int], Optional[int]]] = None, + alignment: Optional[int] = 1, + skip_type_test: bool = False) -> None: + self.tag = tag + self.type_name = type_name + self.object_type = object_type + self.page_type = page_type + self.size = size + self.index = index + self.alignment = alignment + self.skip_type_test = skip_type_test + + +class PoolHeaderScanner(interfaces.layers.ScannerInterface): + + def __init__(self, module: interfaces.context.ModuleInterface, constraint_lookup: Dict[bytes, PoolConstraint], + alignment: int): + super().__init__() + self._module = module + self._constraint_lookup = constraint_lookup + self._alignment = alignment + + header_type = self._module.get_type('_POOL_HEADER') + self._header_offset = header_type.relative_child_offset('PoolTag') + self._subscanner = scanners.MultiStringScanner([c for c in constraint_lookup.keys()]) + + def __call__(self, data: bytes, data_offset: int): + for offset, pattern in self._subscanner(data, data_offset): + header = self._module.object(object_type = "_POOL_HEADER", + offset = offset - self._header_offset, + absolute = True) + constraint = self._constraint_lookup[pattern] + try: + # Size check + if constraint.size is not None: + if constraint.size[0]: + if (self._alignment * header.BlockSize) < constraint.size[0]: + continue + if constraint.size[1]: + if (self._alignment * header.BlockSize) > constraint.size[1]: + continue + + # Type check + if constraint.page_type is not None: + checks_pass = False + + if (constraint.page_type & PoolType.FREE) and header.is_free_pool(): + checks_pass = True + elif (constraint.page_type & PoolType.NONPAGED) and header.is_nonpaged_pool(): + checks_pass = True + elif (constraint.page_type & PoolType.PAGED) and header.is_paged_pool(): + checks_pass = True + + if not checks_pass: + continue + + if constraint.index is not None: + if constraint.index[0]: + if header.PoolIndex < constraint.index[0]: + continue + if constraint.index[1]: + if header.PoolIndex > constraint.index[1]: + continue + + except exceptions.InvalidAddressException: + # The tested object's header doesn't point to valid addresses, ignore it + continue + + # We found one that passed! + yield (constraint, header) + + +class PoolScanner(plugins.PluginInterface): + """A generic pool scanner plugin.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'handles', plugin = handles.Handles, version = (1, 0, 0)), + ] + + def _generator(self): + + symbol_table = self.config["nt_symbols"] + constraints = self.builtin_constraints(symbol_table) + + for constraint, mem_object, header in self.generate_pool_scan(self.context, self.config["primary"], + symbol_table, constraints): + # generate some type-specific info for sanity checking + if constraint.object_type == "Process": + name = mem_object.ImageFileName.cast("string", + max_length = mem_object.ImageFileName.vol.count, + errors = "replace") + elif constraint.object_type == "File": + try: + name = mem_object.FileName.String + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, "Skipping file at {0:#x}".format(mem_object.vol.offset)) + continue + else: + name = renderers.NotApplicableValue() + + yield (0, (constraint.type_name, format_hints.Hex(header.vol.offset), header.vol.layer_name, name)) + + @staticmethod + def builtin_constraints(symbol_table: str, tags_filter: List[bytes] = None) -> List[PoolConstraint]: + """Get built-in PoolConstraints given a list of pool tags. + + The tags_filter is a list of pool tags, and the associated + PoolConstraints are returned. If tags_filter is empty or + not supplied, then all builtin constraints are returned. + + Args: + symbol_table: The name of the symbol table to prepend to the types used + tags_filter: List of tags to return or None to return all + + Returns: + A list of well-known constructed PoolConstraints that match the provided tags + """ + + builtins = [ + # atom tables + PoolConstraint(b'AtmT', + type_name = symbol_table + constants.BANG + "_RTL_ATOM_TABLE", + size = (200, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # processes on windows before windows 8 + PoolConstraint(b'Pro\xe3', + type_name = symbol_table + constants.BANG + "_EPROCESS", + object_type = "Process", + size = (600, None), + skip_type_test = True, + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # processes on windows starting with windows 8 + PoolConstraint(b'Proc', + type_name = symbol_table + constants.BANG + "_EPROCESS", + object_type = "Process", + size = (600, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # files on windows before windows 8 + PoolConstraint(b'Fil\xe5', + type_name = symbol_table + constants.BANG + "_FILE_OBJECT", + object_type = "File", + size = (150, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # files on windows starting with windows 8 + PoolConstraint(b'File', + type_name = symbol_table + constants.BANG + "_FILE_OBJECT", + object_type = "File", + size = (150, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # mutants on windows before windows 8 + PoolConstraint(b'Mut\xe1', + type_name = symbol_table + constants.BANG + "_KMUTANT", + object_type = "Mutant", + size = (64, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # mutants on windows starting with windows 8 + PoolConstraint(b'Muta', + type_name = symbol_table + constants.BANG + "_KMUTANT", + object_type = "Mutant", + size = (64, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # drivers on windows before windows 8 + PoolConstraint(b'Dri\xf6', + type_name = symbol_table + constants.BANG + "_DRIVER_OBJECT", + object_type = "Driver", + size = (248, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # drivers on windows starting with windows 8 + PoolConstraint(b'Driv', + type_name = symbol_table + constants.BANG + "_DRIVER_OBJECT", + object_type = "Driver", + size = (248, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # kernel modules + PoolConstraint(b'MmLd', + type_name = symbol_table + constants.BANG + "_LDR_DATA_TABLE_ENTRY", + size = (76, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # symlinks on windows before windows 8 + PoolConstraint(b'Sym\xe2', + type_name = symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK", + object_type = "SymbolicLink", + size = (72, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # symlinks on windows starting with windows 8 + PoolConstraint(b'Symb', + type_name = symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK", + object_type = "SymbolicLink", + size = (72, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE), + # registry hives + PoolConstraint(b'CM10', + type_name = symbol_table + constants.BANG + "_CMHIVE", + size = (800, None), + page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE, + skip_type_test = True), + ] + + if not tags_filter: + return builtins + + return [constraint for constraint in builtins if constraint.tag in tags_filter] + + @classmethod + def generate_pool_scan(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + constraints: List[PoolConstraint]) \ + -> Generator[Tuple[ + PoolConstraint, interfaces.objects.ObjectInterface, interfaces.objects.ObjectInterface], None, None]: + """ + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + constraints: List of pool constraints used to limit the scan results + + Returns: + Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object + """ + + # get the object type map + type_map = handles.Handles.get_type_map(context = context, layer_name = layer_name, symbol_table = symbol_table) + + cookie = handles.Handles.find_cookie(context = context, layer_name = layer_name, symbol_table = symbol_table) + + is_windows_10 = versions.is_windows_10(context, symbol_table) + is_windows_8_or_later = versions.is_windows_8_or_later(context, symbol_table) + + # start off with the primary virtual layer + scan_layer = layer_name + + # switch to a non-virtual layer if necessary + if not is_windows_10: + scan_layer = context.layers[scan_layer].config['memory_layer'] + + if symbols.symbol_table_is_64bit(context, symbol_table): + alignment = 0x10 + else: + alignment = 8 + + for constraint, header in cls.pool_scan(context, scan_layer, symbol_table, constraints, alignment = alignment): + + mem_object = header.get_object(type_name = constraint.type_name, + use_top_down = is_windows_8_or_later, + executive = constraint.object_type is not None, + native_layer_name = 'primary', + kernel_symbol_table = symbol_table) + + if mem_object is None: + vollog.log(constants.LOGLEVEL_VVV, "Cannot create an instance of {}".format(constraint.type_name)) + continue + + if constraint.object_type is not None and not constraint.skip_type_test: + try: + if mem_object.get_object_header().get_object_type(type_map, cookie) != constraint.object_type: + continue + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, + "Cannot test instance type check for {}".format(constraint.type_name)) + continue + + yield constraint, mem_object, header + + @classmethod + def pool_scan(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + pool_constraints: List[PoolConstraint], + alignment: int = 8, + progress_callback: Optional[constants.ProgressCallback] = None) \ + -> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]: + """Returns the _POOL_HEADER object (based on the symbol_table template) + after scanning through layer_name returning all headers that match any + of the constraints provided. Only one constraint can be provided per + tag. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + pool_constraints: List of pool constraints used to limit the scan results + alignment: An optional value that all pool headers will be aligned to + progress_callback: An optional function to provide progress feedback whilst scanning + + Returns: + An Iterable of pool constraints and the pool headers associated with them + """ + # Setup the pattern + constraint_lookup = {} # type: Dict[bytes, PoolConstraint] + for constraint in pool_constraints: + if constraint.tag in constraint_lookup: + raise ValueError("Constraint tag is used for more than one constraint: {}".format(repr(constraint.tag))) + constraint_lookup[constraint.tag] = constraint + + pool_header_table_name = cls.get_pool_header_table(context, symbol_table) + module = context.module(pool_header_table_name, layer_name, offset = 0) + + # Run the scan locating the offsets of a particular tag + layer = context.layers[layer_name] + scanner = PoolHeaderScanner(module, constraint_lookup, alignment) + yield from layer.scan(context, scanner, progress_callback) + + @classmethod + def get_pool_header_table(cls, context: interfaces.context.ContextInterface, symbol_table: str) -> str: + """Returns the appropriate symbol_table containing a _POOL_HEADER type, even if the original symbol table + doesn't contain one. + + Args: + context: The context that the symbol tables does (or will) reside in + symbol_table: The expected symbol_table to contain the _POOL_HEADER type + """ + # Setup the pool header and offset differential + try: + context.symbol_space.get_type(symbol_table + constants.BANG + "_POOL_HEADER") + table_name = symbol_table + except exceptions.SymbolError: + # We have to manually load a symbol table + + if symbols.symbol_table_is_64bit(context, symbol_table): + is_win_7 = versions.is_windows_7(context, symbol_table) + if is_win_7: + pool_header_json_filename = "poolheader-x64-win7" + else: + pool_header_json_filename = "poolheader-x64" + else: + pool_header_json_filename = "poolheader-x86" + + # set the class_type to match the normal WindowsKernelIntermedSymbols + is_vista_or_later = versions.is_vista_or_later(context, symbol_table) + if is_vista_or_later: + class_type = extensions.pool.POOL_HEADER_VISTA + else: + class_type = extensions.pool.POOL_HEADER + + table_name = intermed.IntermediateSymbolTable.create(context = context, + config_path = configuration.path_join( + context.symbol_space[symbol_table].config_path, + "poolheader"), + sub_path = "windows", + filename = pool_header_json_filename, + table_mapping = {'nt_symbols': symbol_table}, + class_types = {'_POOL_HEADER': class_type}) + return table_name + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid([("Tag", str), ("Offset", format_hints.Hex), ("Layer", str), ("Name", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/privileges.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/privileges.py new file mode 100644 index 00000000..f24aa7be --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/privileges.py @@ -0,0 +1,99 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 + +import json +import logging +import os +from typing import List + +from volatility.framework import renderers, interfaces, objects, exceptions, constants +from volatility.framework.configuration import requirements +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class Privs(interfaces.plugins.PluginInterface): + """Lists process token privileges""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Find the sids json path (or raise error if its not in the plugin directory). + for plugin_dir in constants.PLUGINS_PATH: + sids_json_file_name = os.path.join(plugin_dir, os.path.join("windows", "sids_and_privileges.json")) + if os.path.exists(sids_json_file_name): + break + else: + vollog.log(constants.LOGLEVEL_VVV, 'sids_and_privileges.json file is missing plugin error') + raise RuntimeError("The sids_and_privileges.json file missed from you plugin directory") + + # Get service sids dictionary (we need only the service sids). + with open(sids_json_file_name, 'r') as file_handle: + temp_json = json.load(file_handle)['privileges'] + self.privilege_info = {int(priv_num): temp_json[priv_num] for priv_num in temp_json} + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + ] + + def _generator(self, procs): + + for task in procs: + try: + process_token = task.Token.dereference().cast("_TOKEN") + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, 'Skeep invalid token.') + continue + + for value, present, enabled, default in process_token.privileges(): + # Skip privileges whose bit positions cannot be + # translated to a privilege name + if not self.privilege_info.get(int(value)): + vollog.log(constants.LOGLEVEL_VVV, 'Skeep invalid privilege ({}).'.format(value)) + continue + + name, desc = self.privilege_info.get(int(value)) + + # Set the attributes + attributes = [] + if present: + attributes.append("Present") + if enabled: + attributes.append("Enabled") + if default: + attributes.append("Default") + + yield (0, [ + int(task.UniqueProcessId), + objects.utility.array_to_string(task.ImageFileName), + int(value), + str(name), ",".join(attributes), + str(desc) + ]) + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Value", int), ("Privilege", str), + ("Attributes", str), ("Description", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/pslist.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/pslist.py new file mode 100644 index 00000000..4cd17485 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/pslist.py @@ -0,0 +1,217 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +import logging +from typing import Callable, Iterable, List, Type + +from volatility.framework import renderers, interfaces, layers, constants +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins import timeliner + +vollog = logging.getLogger(__name__) + + +class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Lists the processes present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + PHYSICAL_DEFAULT = False + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.BooleanRequirement(name = 'physical', + description = 'Display physical offsets instead of virtual', + default = cls.PHYSICAL_DEFAULT, + optional = True), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process ID to include (all other processes are excluded)", + optional = True), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed processes", + default = False, + optional = True) + ] + + @classmethod + def process_dump( + cls, context: interfaces.context.ContextInterface, kernel_table_name: str, pe_table_name: str, + proc: interfaces.objects.ObjectInterface, + open_method: Type[interfaces.plugins.FileHandlerInterface]) -> interfaces.plugins.FileHandlerInterface: + """Extracts the complete data for a process as a FileHandlerInterface + + Args: + context: the context to operate upon + kernel_table_name: the name for the symbol table containing the kernel's symbols + pe_table_name: the name for the symbol table containing the PE format symbols + proc: the process object whose memory should be output + open_method: class to provide context manager for opening the file + + Returns: + An open FileHandlerInterface object containing the complete data for the process or None in the case of failure + """ + + file_handle = None + try: + proc_layer_name = proc.add_process_layer() + peb = context.object(kernel_table_name + constants.BANG + "_PEB", + layer_name = proc_layer_name, + offset = proc.Peb) + + dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset = peb.ImageBaseAddress, + layer_name = proc_layer_name) + file_handle = open_method("pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress)) + for offset, data in dos_header.reconstruct(): + file_handle.seek(offset) + file_handle.write(data) + except Exception as excp: + vollog.debug("Unable to dump PE with pid {}: {}".format(proc.UniqueProcessId, excp)) + + return file_handle + + @classmethod + def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[interfaces.objects.ObjectInterface], bool]: + """A factory for producing filter functions that filter based on a list + of process IDs. + + Args: + pid_list: A list of process IDs that are acceptable, all other processes will be filtered out + + Returns: + Filter function for passing to the `list_processes` method + """ + filter_func = lambda _: False + # FIXME: mypy #4973 or #2608 + pid_list = pid_list or [] + filter_list = [x for x in pid_list if x is not None] + if filter_list: + filter_func = lambda x: x.UniqueProcessId not in filter_list + return filter_func + + @classmethod + def create_name_filter(cls, name_list: List[str] = None) -> Callable[[interfaces.objects.ObjectInterface], bool]: + """A factory for producing filter functions that filter based on a list + of process names. + + Args: + name_list: A list of process names that are acceptable, all other processes will be filtered out + + Returns: + Filter function for passing to the `list_processes` method + """ + filter_func = lambda _: False + # FIXME: mypy #4973 or #2608 + name_list = name_list or [] + filter_list = [x for x in name_list if x is not None] + if filter_list: + filter_func = lambda x: utility.array_to_string(x.ImageFileName) not in filter_list + return filter_func + + @classmethod + def list_processes(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Lists all the processes in the primary layer that are in the pid + config option. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + filter_func: A function which takes an EPROCESS object and returns True if the process should be ignored/filtered + + Returns: + The list of EPROCESS objects from the `layer_name` layer's PsActiveProcessHead list after filtering + """ + + # We only use the object factory to demonstrate how to use one + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address + list_entry = ntkrnlmp.object(object_type = "_LIST_ENTRY", offset = ps_aph_offset) + + # This is example code to demonstrate how to use symbol_space directly, rather than through a module: + # + # ``` + # reloff = self.context.symbol_space.get_type( + # self.config['nt_symbols'] + constants.BANG + "_EPROCESS").relative_child_offset( + # "ActiveProcessLinks") + # ``` + # + # Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already + # having been present. Strictly, the value of the requirement should be joined with the BANG character + # defined in the constants file + reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset("ActiveProcessLinks") + eproc = ntkrnlmp.object(object_type = "_EPROCESS", offset = list_entry.vol.offset - reloff, absolute = True) + + for proc in eproc.ActiveProcessLinks: + if not filter_func(proc): + yield proc + + def _generator(self): + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + + memory = self.context.layers[self.config['primary']] + if not isinstance(memory, layers.intel.Intel): + raise TypeError("Primary layer is not an intel layer") + + for proc in self.list_processes(self.context, + self.config['primary'], + self.config['nt_symbols'], + filter_func = self.create_pid_filter(self.config.get('pid', None))): + + if not self.config.get('physical', self.PHYSICAL_DEFAULT): + offset = proc.vol.offset + else: + (_, _, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0] + + file_output = "Disabled" + if self.config['dump']: + file_handle = self.process_dump(self.context, self.config['nt_symbols'], pe_table_name, proc, + self.open) + file_output = "Error outputting file" + if file_handle: + file_handle.close() + file_output = str(file_handle.preferred_filename) + + yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId, + proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace'), + format_hints.Hex(offset), proc.ActiveThreads, proc.get_handle_count(), proc.get_session_id(), + proc.get_is_wow64(), proc.get_create_time(), proc.get_exit_time(), file_output)) + + def generate_timeline(self): + for row in self._generator(): + _depth, row_data = row + description = "Process: {} {} ({})".format(row_data[0], row_data[2], row_data[3]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[8]) + yield (description, timeliner.TimeLinerType.MODIFIED, row_data[9]) + + def run(self): + offsettype = "(V)" if not self.config.get('physical', self.PHYSICAL_DEFAULT) else "(P)" + + return renderers.TreeGrid([("PID", int), ("PPID", int), ("ImageFileName", str), + ("Offset{0}".format(offsettype), format_hints.Hex), ("Threads", int), + ("Handles", int), ("SessionId", int), ("Wow64", bool), + ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), + ("File output", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/psscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/psscan.py new file mode 100644 index 00000000..769b3e87 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/psscan.py @@ -0,0 +1,168 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +import logging +from typing import Iterable, Callable + +from volatility.framework import renderers, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins import timeliner +from volatility.plugins.windows import info +from volatility.plugins.windows import poolscanner +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Scans for processes present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 1, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'info', component = info.Info, version = (1, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process ID to include (all other processes are excluded)", + optional = True), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed processes", + default = False, + optional = True) + ] + + @classmethod + def scan_processes(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for processes using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of processes found by scanning the `layer_name` layer for process pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Pro\xe3', b'Proc']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + if not filter_func(mem_object): + yield mem_object + + @classmethod + def virtual_process_from_physical(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + proc: interfaces.objects.ObjectInterface) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """ Returns a virtual process from a physical addressed one + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + proc: the process object with phisical address + + Returns: + A process object on virtual address layer + + """ + + # We'll use the first thread to bounce back to the virtual process + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset("ThreadListEntry") + # Start out with the member offset + offsets = [tleoffset] + + # If (and only if) we're dealing with 64-bit Windows 7 SP1 + # then add the other commonly seen member offset to the list + kuser = info.Info.get_kuser_structure(context, layer_name, symbol_table) + nt_major_version = int(kuser.NtMajorVersion) + nt_minor_version = int(kuser.NtMinorVersion) + vers = info.Info.get_version_structure(context, layer_name, symbol_table) + build = vers.MinorVersion + bits = context.layers[layer_name].bits_per_register + version = (nt_major_version, nt_minor_version, build) + if version == (6, 1, 7601) and bits == 64: + offsets.append(tleoffset + 8) + + # Now we can try to bounce back + for ofs in offsets: + ethread = ntkrnlmp.object(object_type = "_ETHREAD", + offset = proc.ThreadListHead.Flink - ofs, + absolute = True) + + # Ask for the thread's process to get an _EPROCESS with a virtual address layer + virtual_process = ethread.owning_process() + # Sanity check the bounce. + # This compares the original offset with the new one (translated from virtual layer) + (_, _, ph_offset, _, _) = list(context.layers[layer_name].mapping(offset = virtual_process.vol.offset, + length = 0))[0] + if virtual_process and \ + proc.vol.offset == ph_offset: + return virtual_process + + def _generator(self): + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + for proc in self.scan_processes(self.context, + self.config['primary'], + self.config['nt_symbols'], + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))): + + file_output = "Disabled" + if self.config['dump']: + vproc = self.virtual_process_from_physical(self.context, self.config['primary'], + self.config['nt_symbols'], proc) + + file_handle = pslist.PsList.process_dump(self.context, self.config['nt_symbols'], pe_table_name, + vproc, self.open) + file_output = "Error outputting file" + if file_handle: + file_output = file_handle.preferred_filename + + yield (0, (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId, + proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, + errors = 'replace'), format_hints.Hex(proc.vol.offset), + proc.ActiveThreads, proc.get_handle_count(), proc.get_session_id(), proc.get_is_wow64(), + proc.get_create_time(), proc.get_exit_time(), file_output)) + + def generate_timeline(self): + for row in self._generator(): + _depth, row_data = row + description = "Process: {} {} ({})".format(row_data[0], row_data[2], row_data[3]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[8]) + yield (description, timeliner.TimeLinerType.MODIFIED, row_data[9]) + + def run(self): + return renderers.TreeGrid([("PID", int), ("PPID", int), ("ImageFileName", str), ("Offset", format_hints.Hex), + ("Threads", int), ("Handles", int), ("SessionId", int), ("Wow64", bool), + ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), + ("File output", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/pstree.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/pstree.py new file mode 100644 index 00000000..a30f22d5 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/pstree.py @@ -0,0 +1,98 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import datetime +from typing import Dict, Set + +from volatility.framework import objects, interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import pslist + + +class PsTree(interfaces.plugins.PluginInterface): + """Plugin for listing processes in a tree based on their parent process + ID.""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._processes = {} # type: Dict[int, interfaces.objects.ObjectInterface] + self._levels = {} # type: Dict[int, int] + self._children = {} # type: Dict[int, Set[int]] + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.BooleanRequirement(name = 'physical', + description = 'Display physical offsets instead of virtual', + default = pslist.PsList.PHYSICAL_DEFAULT, + optional = True), + requirements.VersionRequirement(name = 'pslist', + component = pslist.PsList, + version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process ID to include (all other processes are excluded)", + optional = True) + ] + + def find_level(self, pid: objects.Pointer) -> None: + """Finds how deep the pid is in the processes list.""" + seen = set([]) + seen.add(pid) + level = 0 + proc = self._processes.get(pid, None) + while proc is not None and proc.InheritedFromUniqueProcessId not in seen: + child_list = self._children.get(proc.InheritedFromUniqueProcessId, set([])) + child_list.add(proc.UniqueProcessId) + self._children[proc.InheritedFromUniqueProcessId] = child_list + proc = self._processes.get(proc.InheritedFromUniqueProcessId, None) + level += 1 + self._levels[pid] = level + + def _generator(self): + """Generates the Tree of processes.""" + for proc in pslist.PsList.list_processes(self.context, self.config['primary'], self.config['nt_symbols']): + + if not self.config.get('physical', pslist.PsList.PHYSICAL_DEFAULT): + offset = proc.vol.offset + else: + layer_name = self.config['primary'] + memory = self.context.layers[layer_name] + (_, _, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0] + + self._processes[proc.UniqueProcessId] = proc + + # Build the child/level maps + for pid in self._processes: + self.find_level(pid) + + def yield_processes(pid): + proc = self._processes[pid] + row = (proc.UniqueProcessId, proc.InheritedFromUniqueProcessId, + proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count, errors = 'replace'), + format_hints.Hex(offset), proc.ActiveThreads, proc.get_handle_count(), proc.get_session_id(), + proc.get_is_wow64(), proc.get_create_time(), proc.get_exit_time()) + + yield (self._levels[pid] - 1, row) + for child_pid in self._children.get(pid, []): + yield from yield_processes(child_pid) + + for pid in self._levels: + if self._levels[pid] == 1: + yield from yield_processes(pid) + + def run(self): + offsettype = "(V)" if not self.config.get('physical', pslist.PsList.PHYSICAL_DEFAULT) else "(P)" + + return renderers.TreeGrid([("PID", int), ("PPID", int), ("ImageFileName", str), + ("Offset{0}".format(offsettype), format_hints.Hex), ("Threads", int), + ("Handles", int), ("SessionId", int), ("Wow64", bool), + ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/__init__.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/__init__.py new file mode 100644 index 00000000..4939c775 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/__init__.py @@ -0,0 +1,8 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All core windows registry plugins. + +These modules should only be imported from volatility.plugins NOT +volatility.framework.plugins +""" diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivelist.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivelist.py new file mode 100644 index 00000000..81aa6b26 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivelist.py @@ -0,0 +1,242 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import Iterator, List, Tuple, Iterable, Optional + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import registry +from volatility.framework.renderers import format_hints +from volatility.plugins.windows.registry import hivescan + +vollog = logging.getLogger(__name__) + + +class HiveGenerator: + """Walks the registry HiveList linked list in a given direction and stores an invalid offset + if it's unable to fully walk the list""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, cmhive, forward = True): + self._cmhive = cmhive + self._forward = forward + self._invalid = None + + def __iter__(self): + for hive in self._cmhive.HiveList.to_list(self._cmhive.vol.type_name, "HiveList", forward = self._forward): + if not hive.is_valid(): + self._invalid = hive.vol.offset + return + yield hive + + @property + def invalid(self) -> Optional[int]: + return self._invalid + + +class HiveList(interfaces.plugins.PluginInterface): + """Lists the registry hives present in a particular memory image.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.StringRequirement(name = 'filter', + description = "String to filter hive names returned", + optional = True, + default = None), + requirements.PluginRequirement(name = 'hivescan', plugin = hivescan.HiveScan, version = (1, 0, 0)), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed registry hives", + default = False, + optional = True) + ] + + def _sanitize_hive_name(self, name: str) -> str: + return name.split('\\')[-1].replace(' ', '_').replace('.', '').replace('[', '').replace(']', '') + + def _generator(self) -> Iterator[Tuple[int, Tuple[int, str]]]: + chunk_size = 0x500000 + + for hive_object in self.list_hive_objects(context = self.context, + layer_name = self.config["primary"], + symbol_table = self.config["nt_symbols"], + filter_string = self.config.get('filter', None)): + + file_output = "Disabled" + if self.config['dump']: + # Construct the hive + hive = next( + self.list_hives(self.context, + self.config_path, + layer_name = self.config["primary"], + symbol_table = self.config["nt_symbols"], + hive_offsets = [hive_object.vol.offset])) + maxaddr = hive.hive.Storage[0].Length + hive_name = self._sanitize_hive_name(hive.get_name()) + + file_handle = self.open('registry.{}.{}.hive'.format(hive_name, hex(hive.hive_offset))) + with file_handle as file_data: + if hive._base_block: + hive_data = self.context.layers[hive.dependencies[0]].read(hive.hive.BaseBlock, 1 << 12) + else: + hive_data = '\x00' * (1 << 12) + file_data.write(hive_data) + + for i in range(0, maxaddr, chunk_size): + current_chunk_size = min(chunk_size, maxaddr - i) + data = hive.read(i, current_chunk_size, pad = True) + file_data.write(data) + # if self._progress_callback: + # self._progress_callback((i / maxaddr) * 100, 'Writing layer {}'.format(hive_name)) + file_output = file_handle.preferred_filename + + yield (0, (format_hints.Hex(hive_object.vol.offset), hive_object.get_name() or "", file_output)) + + @classmethod + def list_hives(cls, + context: interfaces.context.ContextInterface, + base_config_path: str, + layer_name: str, + symbol_table: str, + filter_string: Optional[str] = None, + hive_offsets: List[int] = None) -> Iterable[registry.RegistryHive]: + """Walks through a registry, hive by hive returning the constructed + registry layer name. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + base_config_path: The configuration path for any settings required by the new table + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + filter_string: An optional string which must be present in the hive name if specified + offset: An optional offset to specify a specific hive to iterate over (takes precedence over filter_string) + + Yields: + A registry hive layer name + """ + if hive_offsets is None: + try: + hive_offsets = [ + hive.vol.offset for hive in cls.list_hive_objects(context, layer_name, symbol_table, filter_string) + ] + except ImportError: + vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset") + raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset") + + for hive_offset in hive_offsets: + # Construct the hive + reg_config_path = cls.make_subconfig(context = context, + base_config_path = base_config_path, + hive_offset = hive_offset, + base_layer = layer_name, + nt_symbols = symbol_table) + + try: + hive = registry.RegistryHive(context, reg_config_path, name = 'hive' + hex(hive_offset)) + except exceptions.InvalidAddressException: + vollog.warning("Couldn't create RegistryHive layer at offset {}, skipping".format(hex(hive_offset))) + continue + context.layers.add_layer(hive) + yield hive + + @classmethod + def list_hive_objects(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + filter_string: str = None) -> Iterator[interfaces.objects.ObjectInterface]: + """Lists all the hives in the primary layer. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + filter_string: A string which must be present in the hive name if specified + + Returns: + The list of registry hives from the `layer_name` layer as filtered against using the `filter_string` + """ + + # We only use the object factory to demonstrate how to use one + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address + list_entry = ntkrnlmp.object(object_type = "_LIST_ENTRY", offset = list_head) + reloff = ntkrnlmp.get_type("_CMHIVE").relative_child_offset("HiveList") + cmhive = ntkrnlmp.object(object_type = "_CMHIVE", offset = list_entry.vol.offset - reloff, absolute = True) + + # Run through the list forwards + seen = set() + + hg = HiveGenerator(cmhive, forward = True) + for hive in hg: + if hive.vol.offset in seen: + vollog.debug("Hivelist found an already seen offset {} while " \ + "traversing forwards, this should not occur".format(hex(hive.vol.offset))) + break + seen.add(hive.vol.offset) + if filter_string is None or filter_string.lower() in str(hive.get_name() or "").lower(): + if context.layers[layer_name].is_valid(hive.vol.offset): + yield hive + + forward_invalid = hg.invalid + if forward_invalid: + vollog.debug("Hivelist failed traversing the list forwards at {}, traversing backwards".format( + hex(forward_invalid))) + hg = HiveGenerator(cmhive, forward = False) + for hive in hg: + if hive.vol.offset in seen: + vollog.debug("Hivelist found an already seen offset {} while " \ + "traversing backwards, list walking met in the middle".format(hex(hive.vol.offset))) + break + seen.add(hive.vol.offset) + if filter_string is None or filter_string.lower() in str(hive.get_name() or "").lower(): + if context.layers[layer_name].is_valid(hive.vol.offset): + yield hive + + backward_invalid = hg.invalid + + if backward_invalid and forward_invalid != backward_invalid: + # walking forward and backward did not stop at the same offset. they should if: + # 1) there are no invalid hives, walking forwards would reach the end and backwards is not necessary + # 2) there is one invalid hive, walking backwards would stop at the same place as forwards + # therefore, there must be more 2 or more invalid hives, so the middle of the list is not reachable + # by walking the list, so revert to scanning, and walk the list forwards and backwards from each + # found hive + vollog.debug("Hivelist failed traversing backwards at {}, a different " \ + "location from forwards, revert to scanning".format(hex(backward_invalid))) + for hive in hivescan.HiveScan.scan_hives(context, layer_name, symbol_table): + try: + if hive.HiveList.Flink: + start_hive_offset = hive.HiveList.Flink - reloff + + ## Now instantiate the first hive in virtual address space as normal + start_hive = ntkrnlmp.object(object_type = "_CMHIVE", + offset = start_hive_offset, + absolute = True) + for forward in (True, False): + for linked_hive in start_hive.HiveList.to_list(hive.vol.type_name, "HiveList", forward): + if not linked_hive.is_valid() or linked_hive.vol.offset in seen: + continue + seen.add(linked_hive.vol.offset) + if filter_string is None or filter_string.lower() in str(linked_hive.get_name() + or "").lower(): + if context.layers[layer_name].is_valid(linked_hive.vol.offset): + yield linked_hive + except exceptions.InvalidAddressException: + vollog.debug("InvalidAddressException when traversing hive {} found from scan, skipping".format( + hex(hive.vol.offset))) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid([("Offset", format_hints.Hex), ("FileFullPath", str), ("File output", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivescan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivescan.py new file mode 100644 index 00000000..466e2776 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/hivescan.py @@ -0,0 +1,76 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable + +from volatility.framework import renderers, interfaces, symbols +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols.windows import versions +from volatility.plugins.windows import poolscanner, bigpools + + +class HiveScan(interfaces.plugins.PluginInterface): + """Scans for registry hives present in a particular windows memory + image.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'bigpools', plugin = bigpools.BigPools, version = (1, 0, 0)), + ] + + @classmethod + def scan_hives(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for hives using the poolscanner module and constraints or bigpools module with tag. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures + """ + + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + is_windows_8_1_or_later = versions.is_windows_8_1_or_later(context = context, symbol_table = symbol_table) + + if is_windows_8_1_or_later and is_64bit: + kvo = context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + + for pool in bigpools.BigPools.list_big_pools(context, + layer_name = layer_name, + symbol_table = symbol_table, + tags = ["CM10"]): + cmhive = ntkrnlmp.object(object_type = "_CMHIVE", offset = pool.Va, absolute = True) + yield cmhive + + else: + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'CM10']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self): + for hive in self.scan_hives(self.context, self.config['primary'], self.config['nt_symbols']): + + yield (0, (format_hints.Hex(hive.vol.offset), )) + + def run(self): + return renderers.TreeGrid([("Offset", format_hints.Hex)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/printkey.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/printkey.py new file mode 100644 index 00000000..2fe3337c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/printkey.py @@ -0,0 +1,199 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +import logging +from typing import List, Sequence, Iterable, Tuple, Union + +from volatility.framework import objects, renderers, exceptions, interfaces, constants +from volatility.framework.configuration import requirements +from volatility.framework.layers.registry import RegistryHive, RegistryFormatException +from volatility.framework.renderers import TreeGrid, conversion, format_hints +from volatility.framework.symbols.windows.extensions.registry import RegValueTypes +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class PrintKey(interfaces.plugins.PluginInterface): + """Lists the registry keys under a hive or specific key value.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)), + requirements.IntRequirement(name = 'offset', description = "Hive Offset", default = None, optional = True), + requirements.StringRequirement(name = 'key', + description = "Key to start from", + default = None, + optional = True), + requirements.BooleanRequirement(name = 'recurse', + description = 'Recurses through keys', + default = False, + optional = True) + ] + + @classmethod + def key_iterator( + cls, + hive: RegistryHive, + node_path: Sequence[objects.StructType] = None, + recurse: bool = False + ) -> Iterable[Tuple[int, bool, datetime.datetime, str, bool, interfaces.objects.ObjectInterface]]: + """Walks through a set of nodes from a given node (last one in + node_path). Avoids loops by not traversing into nodes already present + in the node_path. + + Args: + hive: The registry hive to walk + node_path: The list of nodes that make up the + recurse: Traverse down the node tree or stay only on the same level + + Yields: + A tuple of results (depth, is_key, last write time, path, volatile, and the node). + """ + if not node_path: + node_path = [hive.get_node(hive.root_cell_offset)] + if not isinstance(node_path, list) or len(node_path) < 1: + vollog.warning("Hive walker was not passed a valid node_path (or None)") + return + node = node_path[-1] + key_path_items = [hive] + node_path[1:] + key_path = '\\'.join([k.get_name() for k in key_path_items]) + if node.vol.type_name.endswith(constants.BANG + '_CELL_DATA'): + raise RegistryFormatException(hive.name, "Encountered _CELL_DATA instead of _CM_KEY_NODE") + last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart) + + for key_node in node.get_subkeys(): + result = (len(node_path), True, last_write_time, key_path, key_node.get_volatile(), key_node) + yield result + + if recurse: + if key_node.vol.offset not in [x.vol.offset for x in node_path]: + try: + key_node.get_name() + except exceptions.InvalidAddressException as excp: + vollog.debug(excp) + continue + + yield from cls.key_iterator(hive, node_path + [key_node], recurse = recurse) + + for value_node in node.get_values(): + result = (len(node_path), False, last_write_time, key_path, node.get_volatile(), value_node) + yield result + + def _printkey_iterator(self, + hive: RegistryHive, + node_path: Sequence[objects.StructType] = None, + recurse: bool = False): + """Method that wraps the more generic key_iterator, to provide output + for printkey specifically. + + Args: + hive: The registry hive to walk + node_path: The list of nodes that make up the + recurse: Traverse down the node tree or stay only on the same level + + Yields: + The depth, and a tuple of results (last write time, hive offset, type, path, name, data and volatile) + """ + for depth, is_key, last_write_time, key_path, volatile, node in self.key_iterator(hive, node_path, recurse): + if is_key: + try: + key_node_name = node.get_name() + except (exceptions.InvalidAddressException, RegistryFormatException) as excp: + vollog.debug(excp) + key_node_name = renderers.UnreadableValue() + + yield (depth, (last_write_time, renderers.format_hints.Hex(hive.hive_offset), "Key", key_path, + key_node_name, renderers.NotApplicableValue(), volatile)) + else: + try: + value_node_name = node.get_name() or "(Default)" + except (exceptions.InvalidAddressException, RegistryFormatException) as excp: + vollog.debug(excp) + value_node_name = renderers.UnreadableValue() + + try: + value_type = RegValueTypes.get(node.Type).name + except (exceptions.InvalidAddressException, RegistryFormatException) as excp: + vollog.debug(excp) + value_type = renderers.UnreadableValue() + + if isinstance(value_type, renderers.UnreadableValue): + vollog.debug("Couldn't read registry value type, so data is unreadable") + value_data = renderers.UnreadableValue() # type: Union[interfaces.renderers.BaseAbsentValue, bytes] + else: + try: + value_data = node.decode_data() + + if isinstance(value_data, int): + value_data = format_hints.MultiTypeData(value_data, encoding = 'utf-8') + elif RegValueTypes.get(node.Type) == RegValueTypes.REG_BINARY: + value_data = format_hints.MultiTypeData(value_data, show_hex = True) + elif RegValueTypes.get(node.Type) == RegValueTypes.REG_MULTI_SZ: + value_data = format_hints.MultiTypeData(value_data, + encoding = 'utf-16-le', + split_nulls = True) + else: + value_data = format_hints.MultiTypeData(value_data, encoding = 'utf-16-le') + except (ValueError, exceptions.InvalidAddressException, RegistryFormatException) as excp: + vollog.debug(excp) + value_data = renderers.UnreadableValue() + + result = (depth, (last_write_time, renderers.format_hints.Hex(hive.hive_offset), value_type, key_path, + value_node_name, value_data, volatile)) + yield result + + def _registry_walker(self, + layer_name: str, + symbol_table: str, + hive_offsets: List[int] = None, + key: str = None, + recurse: bool = False): + + for hive in hivelist.HiveList.list_hives(self.context, + self.config_path, + layer_name = layer_name, + symbol_table = symbol_table, + hive_offsets = hive_offsets): + + try: + # Walk it + if key is not None: + node_path = hive.get_key(key, return_list = True) + else: + node_path = [hive.get_node(hive.root_cell_offset)] + for (x, y) in self._printkey_iterator(hive, node_path, recurse = recurse): + yield (x - len(node_path), y) + except (exceptions.InvalidAddressException, KeyError, RegistryFormatException) as excp: + if isinstance(excp, KeyError): + vollog.debug("Key '{}' not found in Hive at offset {}.".format(key, hex(hive.hive_offset))) + elif isinstance(excp, RegistryFormatException): + vollog.debug(excp) + elif isinstance(excp, exceptions.InvalidAddressException): + vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address))) + result = (0, (renderers.UnreadableValue(), format_hints.Hex(hive.hive_offset), "Key", + '?\\' + (key or ''), renderers.UnreadableValue(), renderers.UnreadableValue(), + renderers.UnreadableValue())) + yield result + + def run(self): + offset = self.config.get('offset', None) + + return TreeGrid(columns = [('Last Write Time', datetime.datetime), ('Hive Offset', format_hints.Hex), + ('Type', str), ('Key', str), ('Name', str), ('Data', format_hints.MultiTypeData), + ('Volatile', bool)], + generator = self._registry_walker(self.config['primary'], + self.config['nt_symbols'], + hive_offsets = None if offset is None else [offset], + key = self.config.get('key', None), + recurse = self.config.get('recurse', None))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.json b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.json new file mode 100644 index 00000000..10986e2b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.json @@ -0,0 +1,103 @@ +{ + "{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs", + "{054FAE61-4DD8-4787-80B6-090220C4B700}": "GameExplorer", + "{0762D272-C50A-4BB0-A382-697DCD729B80}": "%SystemDrive%\\Users", + "{0AC0837C-BBF8-452A-850D-79D08E667CA7}": "(My) Computer", + "{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}": "Sync Setup", + "{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}": "%PUBLIC%\\Music\\Sample Playlists", + "{1777F761-68AD-4D8A-87BD-30B759FA33DD}": "%USERPROFILE%\\Favorites", + "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}": "%USERPROFILE%\\Videos", + "{190337d1-b8ca-4121-a639-6d472d16972a}": "Search Results", + "{1A6FDBA2-F42D-4358-A798-B74D745926C5}": "%PUBLIC%\\RecordedTV.library-ms", + "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}": "%windir%\\system32", + "{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}": "%APPDATA%\\Microsoft\\Windows\\Libraries", + "{2112AB0A-C86A-4FFE-A368-0DE96E47012E}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Music.library-ms", + "{2400183A-6185-49FB-A2D8-4A392A602BA3}": "%PUBLIC%\\Videos", + "{289a9a43-be44-4057-a41b-587a76d7e7f9}": "Sync Results", + "{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}": "%windir%\\resources\\0409 (code page)", + "{2B0F765D-C0E9-4171-908E-08A611B84FF6}": "%APPDATA%\\Microsoft\\Windows\\Cookies", + "{2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39}": "%LOCALAPPDATA%\\Microsoft\\Windows Photo Gallery\\Original Images", + "{3214FAB5-9757-4298-BB61-92A9DEAA44FF}": "%PUBLIC%\\Music", + "{33E28130-4E1E-4676-835A-98395C3BC3BB}": "%USERPROFILE%\\Pictures", + "{352481E8-33BE-4251-BA85-6007CAEDCF9D}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Temporary Internet Files", + "{374DE290-123F-4565-9164-39C4925E467B}": "%USERPROFILE%\\Downloads", + "{3D644C9B-1FB8-4f30-9B45-F670235F79C0}": "%PUBLIC%\\Downloads", + "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}": "%APPDATA% (%USERPROFILE%\\AppData\\Roaming)", + "{43668BF8-C14E-49B2-97C9-747784D784B7}": "Sync Center", + "{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Libraries", + "{491E922F-5643-4AF4-A7EB-4E7A138D8174}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Videos.library-ms", + "{4BD8D571-6D19-48D3-BE97-422220080E43}": "%USERPROFILE%\\Music", + "{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}": "%USERPROFILE%\\Saved Games", + "{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}": "The Internet", + "{4bfefb45-347d-4006-a5be-ac0cb0567192}": "Conflicts", + "{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}": "Homegroup", + "{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch", + "{56784854-C6CB-462b-8169-88E350ACB882}": "%USERPROFILE%\\Contacts", + "{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}": "%LOCALAPPDATA%\\Programs", + "{5CE4A5E9-E4EB-479D-B89F-130C02886155}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\DeviceMetadataStore", + "{5E6C858F-0E22-4760-9AFE-EA3317B67173}": "%USERPROFILE% (%SystemDrive%\\Users\\%USERNAME%)", + "{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}": "%APPDATA%\\Microsoft\\Windows\\Start Menu", + "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}": "%ALLUSERSPROFILE% (%ProgramData%, %SystemDrive%\\ProgramData)", + "{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}": "%ProgramFiles%\\Common Files", + "{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}": "%USERPROFILE%\\Pictures\\Slide Shows", + "{6D809377-6AF0-444b-8957-A3773F02200E}": "%ProgramFiles%", + "{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}": "Network Connections", + "{724EF170-A42D-4FEF-9F26-B60E846FBA4F}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools", + "{76FC4E2D-D6AD-4519-A663-37BD56068185}": "Printers", + "{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Documents.library-ms", + "{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}": "%ProgramFiles%\\Windows Sidebar\\Gadgets", + "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}": "%ProgramFiles%", + "{7d1d3a04-debb-4115-95cf-2f29da2920da}": "%USERPROFILE%\\Searches", + "{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp", + "{82A74AEB-AEB4-465C-A014-D097EE346D63}": "Control Panel", + "{859EAD94-2E85-48AD-A71A-0969CB56A6CD}": "%PUBLIC%\\Videos\\Sample Videos", + "{8983036C-27C0-404B-8F08-102D10DCFD74}": "%APPDATA%\\Microsoft\\Windows\\SendTo", + "{8AD10C31-2ADB-4296-A8F7-E4701232C972}": "%windir%\\Resources", + "{905e63b6-c1bf-494e-b29c-65b732d3d21a}": "%ProgramFiles%", + "{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}": "%APPDATA%\\Microsoft\\Windows\\Printer Shortcuts", + "{98ec0e18-2098-4d44-8644-66979315a281}": "Microsoft Office Outlook", + "{9E3995AB-1F9C-4F13-B827-48B24B6C7174}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned", + "{9E52AB10-F80D-49DF-ACB8-4330F5687855}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Burn\\Burn", + "{A302545D-DEFF-464b-ABE8-61C8648D939B}": "Libraries", + "{A4115719-D62E-491D-AA7C-E74B8BE3B067}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu", + "{A520A1A4-1780-4FF6-BD18-167343C5AF16}": "%USERPROFILE%\\AppData\\LocalLow", + "{A63293E8-664E-48DB-A079-DF759E0509F7}": "%APPDATA%\\Microsoft\\Windows\\Templates", + "{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}": "%LOCALAPPDATA%\\Microsoft\\Windows Sidebar\\Gadgets", + "{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs", + "{A990AE9F-A03B-4E80-94BC-9912D7504104}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Pictures.library-ms", + "{AE50C081-EBD2-438A-8655-8A092E34987A}": "%APPDATA%\\Microsoft\\Windows\\Recent", + "{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}": "%PUBLIC%\\Music\\Sample Music", + "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}": "Desktop", + "{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}": "%PUBLIC%\\Pictures", + "{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}": "Recycle Bin", + "{B94237E7-57AC-4347-9151-B08C6C32D1F7}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Templates", + "{B97D20BB-F46A-4C97-BA10-5E3608430854}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp", + "{BCB5256F-79F6-4CEE-B725-DC34E402FD46}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\ImplicitAppShortcuts", + "{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}": "%LOCALAPPDATA%\\Programs\\Common", + "{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}": "%ALLUSERSPROFILE%\\OEM Links", + "{C4900540-2379-4C75-844B-64E6FAF8716B}": "%PUBLIC%\\Pictures\\Sample Pictures", + "{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}": "%PUBLIC%\\Desktop", + "{C5ABBF53-E17F-4121-8900-86626FC2C973}": "%APPDATA%\\Microsoft\\Windows\\Network Shortcuts", + "{C870044B-F49E-4126-A9C3-B52A1FF411E8}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Ringtones", + "{CAC52C1A-B53D-4edc-92D7-6B2E8AC19434}": "Games", + "{D0384E7D-BAC3-4797-8F14-CBA229B392B5}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools", + "{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}": "Network", + "{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}": "%windir%\\system32", + "{D9DC8A3B-B784-432E-A781-5A1130A75963}": "%LOCALAPPDATA%\\Microsoft\\Windows\\History", + "{DE92C1C7-837F-4F69-A3BB-86E631204A23}": "%USERPROFILE%\\Music\\Playlists", + "{DE974D24-D9C6-4D3E-BF91-F4455120B917}": "%ProgramFiles%\\Common Files", + "{DEBF2536-E1A8-4c59-B6A2-414586476AEA}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\GameExplorer", + "{DFDF76A2-C82A-4D63-906A-5644AC457385}": "%PUBLIC% (%SystemDrive%\\Users\\Public)", + "{E555AB60-153B-4D17-9F04-A5FE99FC15EC}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Ringtones", + "{ED4824AF-DCE4-45A8-81E2-FC7965083634}": "%PUBLIC%\\Documents", + "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}": "%LOCALAPPDATA% (%USERPROFILE%\\AppData\\Local)", + "{F38BF404-1D43-42F2-9305-67DE0B28FC23}": "%windir%", + "{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}": "%ProgramFiles%\\Common Files", + "{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}": "%windir%\\Fonts", + "{a305ce99-f527-492b-8b1a-7e76fa98d6e4}": "Installed Updates", + "{bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968}": "%USERPROFILE%\\Links", + "{de61d971-5ebc-4f02-a3a9-6c82895e5c04}": "Add or Remove Programs (Control Panel)", + "{df7266ac-9274-4867-8d55-3bd661de872d}": "Programs and Features", + "{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}": "Offline Files", + "{f3ce0f7c-4901-4acc-8648-d5d44b04ef8f}": "The user's full name" +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.py new file mode 100644 index 00000000..c18d92ed --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/registry/userassist.py @@ -0,0 +1,254 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import codecs +import datetime +import json +import logging +import os +from typing import Any, List, Tuple, Generator + +from volatility.framework import exceptions, renderers, constants, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.layers.physical import BufferDataLayer +from volatility.framework.layers.registry import RegistryHive +from volatility.framework.renderers import format_hints, conversion +from volatility.framework.symbols import intermed +from volatility.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class UserAssist(interfaces.plugins.PluginInterface): + """Print userassist registry keys and information.""" + + _required_framework_version = (2, 0, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._userassist_size = 0 + self._userassist_type_name = "_VOL_USERASSIST_TYPES_7" + self._reg_table_name = None + self._win7 = None + # taken from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx + self._folder_guids = json.load(open(os.path.join(os.path.dirname(__file__), "userassist.json"), "rb")) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.IntRequirement(name = 'offset', description = "Hive Offset", default = None, optional = True), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)) + ] + + def parse_userassist_data(self, reg_val): + """Reads the raw data of a _CM_KEY_VALUE and returns a dict of + userassist fields.""" + + item = { + "id": renderers.UnparsableValue(), + "count": renderers.UnparsableValue(), + "focus": renderers.UnparsableValue(), + "time": renderers.UnparsableValue(), + "lastupdated": renderers.UnparsableValue(), + "rawdata": renderers.UnparsableValue(), + } + + userassist_data = reg_val.decode_data() + + if userassist_data is None: + return item + + item["rawdata"] = userassist_data + + if self._win7 is None: + # if OS is still unknown at this point, return the default item which just has the rawdata + return item + + if len(userassist_data) < self._userassist_size: + return item + + userassist_layer_name = self.context.layers.free_layer_name("userassist_buffer") + buffer = BufferDataLayer(self.context, self._config_path, userassist_layer_name, userassist_data) + self.context.add_layer(buffer) + userassist_obj = self.context.object( + object_type = self._reg_table_name + constants.BANG + self._userassist_type_name, + layer_name = userassist_layer_name, + offset = 0) + + if self._win7: + item["id"] = renderers.NotApplicableValue() + item["count"] = int(userassist_obj.Count) + + seconds = (userassist_obj.FocusTime + 500) / 1000.0 + time = datetime.timedelta(seconds = seconds) if seconds > 0 else userassist_obj.FocusTime + item["focus"] = int(userassist_obj.FocusCount) + item["time"] = str(time) + + else: + item["id"] = int(userassist_obj.ID) + item["count"] = int(userassist_obj.CountStartingAtFive + if userassist_obj.CountStartingAtFive < 5 else userassist_obj.CountStartingAtFive - 5) + item["focus"] = renderers.NotApplicableValue() + item["time"] = renderers.NotApplicableValue() + + item["lastupdated"] = conversion.wintime_to_datetime(userassist_obj.LastUpdated.QuadPart) + + return item + + def _determine_userassist_type(self) -> None: + """Determine the userassist type and size depending on the OS + version.""" + + if self._win7 is True: + self._userassist_type_name = "_VOL_USERASSIST_TYPES_7" + elif self._win7 is False: + self._userassist_type_name = "_VOL_USERASSIST_TYPES_XP" + + self._userassist_size = self.context.symbol_space.get_type(self._reg_table_name + constants.BANG + + self._userassist_type_name).size + + def _win7_or_later(self) -> bool: + # TODO: change this if there is a better way of determining the OS version + # _KUSER_SHARED_DATA.CookiePad is in Windows 6.1 (Win7) and later + return self.context.symbol_space.get_type(self.config['nt_symbols'] + constants.BANG + + "_KUSER_SHARED_DATA").has_member('CookiePad') + + def list_userassist(self, hive: RegistryHive) -> Generator[Tuple[int, Tuple], None, None]: + """Generate userassist data for a registry hive.""" + + hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name() + + if self._win7 is None: + try: + self._win7 = self._win7_or_later() + except exceptions.SymbolError: + # self._win7 will be None and only registry value rawdata will be output + pass + + self._determine_userassist_type() + + userassist_node_path = hive.get_key("software\\microsoft\\windows\\currentversion\\explorer\\userassist", + return_list = True) + + if not userassist_node_path: + vollog.warning("list_userassist did not find a valid node_path (or None)") + return + + if not isinstance(userassist_node_path, list): + vollog.warning("userassist_node_path did not return a list as expected") + return + userassist_node = userassist_node_path[-1] + # iterate through the GUIDs under the userassist key + for guidkey in userassist_node.get_subkeys(): + # each guid key should have a Count key in it + for countkey in guidkey.get_subkeys(): + countkey_path = countkey.get_key_path() + countkey_last_write_time = conversion.wintime_to_datetime(countkey.LastWriteTime.QuadPart) + + # output the parent Count key + result = ( + 0, (renderers.format_hints.Hex(hive.hive_offset), hive_name, countkey_path, + countkey_last_write_time, "Key", renderers.NotApplicableValue(), renderers.NotApplicableValue(), + renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), + renderers.NotApplicableValue(), renderers.NotApplicableValue()) + ) # type: Tuple[int, Tuple[format_hints.Hex, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any]] + yield result + + # output any subkeys under Count + for subkey in countkey.get_subkeys(): + + subkey_name = subkey.get_name() + result = (1, ( + renderers.format_hints.Hex(hive.hive_offset), + hive_name, + countkey_path, + countkey_last_write_time, + "Subkey", + subkey_name, + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + renderers.NotApplicableValue(), + )) + yield result + + # output any values under Count + for value in countkey.get_values(): + + value_name = value.get_name() + try: + value_name = codecs.encode(value_name, "rot_13") + except UnicodeDecodeError: + pass + + if self._win7: + guid = value_name.split("\\")[0] + if guid in self._folder_guids: + value_name = value_name.replace(guid, self._folder_guids[guid]) + + userassist_data_dict = self.parse_userassist_data(value) + result = (1, ( + renderers.format_hints.Hex(hive.hive_offset), + hive_name, + countkey_path, + countkey_last_write_time, + "Value", + value_name, + userassist_data_dict["id"], + userassist_data_dict["count"], + userassist_data_dict["focus"], + userassist_data_dict["time"], + userassist_data_dict["lastupdated"], + format_hints.HexBytes(userassist_data_dict["rawdata"]), + )) + yield result + + def _generator(self): + + hive_offsets = None + if self.config.get('offset', None) is not None: + hive_offsets = [self.config.get('offset', None)] + + # get all the user hive offsets or use the one specified + for hive in hivelist.HiveList.list_hives(context = self.context, + base_config_path = self.config_path, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_string = 'ntuser.dat', + hive_offsets = hive_offsets): + try: + yield from self.list_userassist(hive) + continue + except exceptions.PagedInvalidAddressException as excp: + vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address))) + except exceptions.InvalidAddressException as excp: + vollog.debug("Invalid address identified in lower layer {}: {}".format( + excp.layer_name, excp.invalid_address)) + except KeyError: + vollog.debug("Key '{}' not found in Hive at offset {}.".format( + "software\\microsoft\\windows\\currentversion\\explorer\\userassist", hex(hive.hive_offset))) + + # yield UnreadableValues when an exception occurs for a given hive_offset + result = (0, (renderers.format_hints.Hex(hive.hive_offset), + hive.name if hive.name else renderers.UnreadableValue(), renderers.UnreadableValue(), + renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), + renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), + renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue())) + yield result + + def run(self): + self._reg_table_name = intermed.IntermediateSymbolTable.create(self.context, self._config_path, 'windows', + 'registry') + + return renderers.TreeGrid([("Hive Offset", renderers.format_hints.Hex), ("Hive Name", str), ("Path", str), + ("Last Write Time", datetime.datetime), ("Type", str), ("Name", str), ("ID", int), + ("Count", int), ("Focus Count", int), ("Time Focused", str), + ("Last Updated", datetime.datetime), ("Raw Data", format_hints.HexBytes)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/sids_and_privileges.json b/app/parsers/vol_Parser/volatility/framework/plugins/windows/sids_and_privileges.json new file mode 100644 index 00000000..0378699a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/sids_and_privileges.json @@ -0,0 +1,612 @@ +{ + "well known": { + "S-1-0": "Null Authority", + "S-1-0-0": "Nobody", + "S-1-1": "World Authority", + "S-1-1-0": "Everyone", + "S-1-2": "Local Authority", + "S-1-2-0": "Local (Users with the ability to log in locally)", + "S-1-2-1": "Console Logon (Users who are logged onto the physical console)", + "S-1-3": "Creator Authority", + "S-1-3-0": "Creator Owner", + "S-1-3-1": "Creator Group", + "S-1-3-2": "Creator Owner Server", + "S-1-3-3": "Creator Group Server", + "S-1-3-4": "Owner Rights", + "S-1-4": "Non-unique Authority", + "S-1-5": "NT Authority", + "S-1-5-1": "Dialup", + "S-1-5-2": "Network", + "S-1-5-3": "Batch", + "S-1-5-4": "Interactive", + "S-1-5-6": "Service", + "S-1-5-7": "Anonymous", + "S-1-5-8": "Proxy", + "S-1-5-9": "Enterprise Domain Controllers", + "S-1-5-10": "Principal Self", + "S-1-5-11": "Authenticated Users", + "S-1-5-12": "Restricted Code", + "S-1-5-13": "Terminal Server Users", + "S-1-5-14": "Remote Interactive Logon", + "S-1-5-15": "This Organization", + "S-1-5-17": "This Organization (Used by the default IIS user)", + "S-1-5-18": "Local System", + "S-1-5-19": "NT Authority", + "S-1-5-20": "NT Authority", + "S-1-5-32-544": "Administrators", + "S-1-5-32-545": "Users", + "S-1-5-32-546": "Guests", + "S-1-5-32-547": "Power Users", + "S-1-5-32-548": "Account Operators", + "S-1-5-32-549": "Server Operators", + "S-1-5-32-550": "Print Operators", + "S-1-5-32-551": "Backup Operators", + "S-1-5-32-552": "Replicators", + "S-1-5-32-554": "BUILTIN\\Pre-Windows 2000 Compatible Access", + "S-1-5-32-555": "BUILTIN\\Remote Desktop Users", + "S-1-5-32-556": "BUILTIN\\Network Configuration Operators", + "S-1-5-32-557": "BUILTIN\\Incoming Forest Trust Builders", + "S-1-5-32-558": "BUILTIN\\Performance Monitor Users", + "S-1-5-32-559": "BUILTIN\\Performance Log Users", + "S-1-5-32-560": "BUILTIN\\Windows Authorization Access Group", + "S-1-5-32-561": "BUILTIN\\Terminal Server License Servers", + "S-1-5-32-562": "BUILTIN\\Distributed COM Users", + "S-1-5-32-568": "BUILTIN\\IIS IUSRS", + "S-1-5-32-569": "Cryptographic Operators", + "S-1-5-32-573": "BUILTIN\\Event Log Readers", + "S-1-5-32-574": "BUILTIN\\Certificate Service DCOM Access", + "S-1-5-33": "Write Restricted", + "S-1-5-64-10": "NTLM Authentication", + "S-1-5-64-14": "SChannel Authentication", + "S-1-5-64-21": "Digest Authentication", + "S-1-5-80": "NT Service", + "S-1-5-86-1544737700-199408000-2549878335-3519669259-381336952": "WMI (Local Service)", + "S-1-5-86-615999462-62705297-2911207457-59056572-3668589837": "WMI (Network Service)", + "S-1-5-1000": "Other Organization", + "S-1-16-0": "Untrusted Mandatory Level", + "S-1-16-4096": "Low Mandatory Level", + "S-1-16-8192": "Medium Mandatory Level", + "S-1-16-8448": "Medium Plus Mandatory Level", + "S-1-16-12288": "High Mandatory Level", + "S-1-16-16384": "System Mandatory Level", + "S-1-16-20480": "Protected Process Mandatory Level", + "S-1-16-28672": "Secure Process Mandatory Level", + "S-1-5-21-0-0-0-496": "Compounded Authentication", + "S-1-5-21-0-0-0-497": "Claims Valid", + "S-1-5-32-575": "RDS Remote Application Services", + "S-1-5-32-576": "RDS Endpoint Servers", + "S-1-5-32-577": "RDS Management Servers", + "S-1-5-32-578": "Hyper-V Admins", + "S-1-5-32-579": "Access Control Assistance Ops", + "S-1-5-32-580": "Remote Management Users", + "S-1-5-65-1": "This Organization Certificate (Kerberos PAC)", + "S-1-5-84-0-0-0-0-0": "Usermode Drivers", + "S-1-5-113": "Local Account", + "S-1-5-114": "Local Account (Member of Administrators)", + "S-1-15-2-1": "Application Package Context", + "S-1-18-1": "Authentication Authority Asserted Identity", + "S-1-18-2": "Service Asserted Identity" + }, + "service sids": { + "S-1-5-80-3476726845-1218940557-3240126423-1396283824-3706223860": ".NET CLR Data", + "S-1-5-80-3749761688-76038143-2425834820-4129736068-309120712": ".NET CLR Networking", + "S-1-5-80-603392709-3706100282-1779817366-3290147925-2109454977": ".NET Data Provider for Oracle", + "S-1-5-80-1168016597-2140435647-491797002-352772175-817350590": ".NET Data Provider for SqlServer", + "S-1-5-80-255220978-1106536095-1636044468-311807000-281316439": ".NETFramework", + "S-1-5-80-799694863-4024754253-4060439485-3284853837-2852070736": "1394ohci", + "S-1-5-80-550892281-1246201444-2906082186-2301917840-2280485454": "ACPI", + "S-1-5-80-2750316143-92726786-3671103447-4285640526-595803658": "AcpiPmi", + "S-1-5-80-4277731759-3688284049-1726419820-405794046-874834352": "adp94xx", + "S-1-5-80-1668430318-2462354215-3771841206-4231263990-2365432302": "adpahci", + "S-1-5-80-1558789706-915067316-2610504951-4085128407-2746609837": "adpu320", + "S-1-5-80-2580340827-1408356417-1236233457-3361088231-1362281560": "adsi", + "S-1-5-80-1452425288-2709461340-3274533413-2407537074-986069024": "AeLookupSvc", + "S-1-5-80-958185937-3813565417-3041720555-255702914-2218388865": "AFD", + "S-1-5-80-1478021307-2683864309-2840291008-2654641652-1914939368": "agp440", + "S-1-5-80-2964793103-1312530465-1873688160-795174673-2945876561": "aic78xx", + "S-1-5-80-2387347252-3645287876-2469496166-3824418187-3586569773": "ALG", + "S-1-5-80-1587539839-2488332913-1287008632-3751426284-4220573165": "aliide", + "S-1-5-80-2808999507-317517852-2612044860-3916887390-3713671788": "amdagp", + "S-1-5-80-4100430975-1934021090-490597466-3817433801-2954987127": "amdide", + "S-1-5-80-2291534435-3322220689-2735625597-3465650106-1340236923": "AmdK8", + "S-1-5-80-4046459391-4016695280-780100908-1621843708-2839135617": "AmdPPM", + "S-1-5-80-1967003600-1747618720-202510732-1118110944-2056302645": "amdsata", + "S-1-5-80-3946629880-3877146532-1020811794-3209710663-3707805237": "amdsbs", + "S-1-5-80-2663151763-304964558-3327380674-1150567875-3378868591": "amdxata", + "S-1-5-80-4206070390-3011771559-4179333097-3486196663-2896243697": "AppID", + "S-1-5-80-2078495744-2416903469-4072184685-3943858305-976987417": "AppIDSvc", + "S-1-5-80-1345931346-2714066941-3624776837-1617505694-3927660246": "Appinfo", + "S-1-5-80-3213379692-3546485254-1309469428-3810262102-2442199571": "AppMgmt", + "S-1-5-80-2586396289-3967100905-3140788560-3910242148-3554126937": "arc", + "S-1-5-80-4275531960-1601664531-2254151532-3075236607-956726506": "arcsas", + "S-1-5-80-3772676405-1029441937-3739550121-1000989080-3364480489": "AsyncMac", + "S-1-5-80-3126347352-2401679295-1536073615-3396758597-3783091149": "atapi", + "S-1-5-80-1580948945-3239616721-2529237571-3761093093-1214243633": "AudioEndpointBuilder", + "S-1-5-80-2676549577-1911656217-2625096541-4178041876-1366760775": "Audiosrv", + "S-1-5-80-1058592404-331734164-3167594226-3910907650-1299295147": "AxInstSV", + "S-1-5-80-1401731874-3996074688-1963706087-3130220608-1140295258": "b06bdrv", + "S-1-5-80-528874604-3378394362-3426265968-3876211711-2956305666": "b57nd60x", + "S-1-5-80-2490514847-2461341327-10008697-1811907875-602803682": "BattC", + "S-1-5-80-2962817144-200689703-2266453665-3849882635-1986547430": "BDESVC", + "S-1-5-80-3186183977-1861961257-3523979229-167170737-1516062821": "Beep", + "S-1-5-80-1383147646-27650227-2710666058-1662982300-1023958487": "BFE", + "S-1-5-80-864916184-135290571-3087830041-1716922880-4237303741": "BITS", + "S-1-5-80-3199704608-2688121514-1535149675-608666402-3313731745": "blbdrive", + "S-1-5-80-26818074-245702967-483560604-1005139437-3076944027": "bowser", + "S-1-5-80-1926592986-1411939489-3259133927-4064956769-2216240612": "BrFiltLo", + "S-1-5-80-3843808474-1199403037-3395254522-1605808544-3221186762": "BrFiltUp", + "S-1-5-80-764937145-223273921-1726433829-265908364-3948077829": "Browser", + "S-1-5-80-3715020542-2003794336-3716799247-4001019941-1245790858": "Brserid", + "S-1-5-80-4014097382-2743177720-3750454595-1699596626-866516122": "BrSerWdm", + "S-1-5-80-1195671069-1048138941-897119314-1432864274-834752102": "BrUsbMdm", + "S-1-5-80-1736549233-1399426098-2600293700-2473969234-3259996387": "BrUsbSer", + "S-1-5-80-505608135-4274227953-3632766965-1888639892-3184055934": "BTHMODEM", + "S-1-5-80-1409084391-1870647740-2731517552-2815089321-2189562539": "BTHPORT", + "S-1-5-80-2586557155-168560303-1373426920-983201488-1499765686": "bthserv", + "S-1-5-80-3223837281-1527595016-2901219760-1358189227-808820507": "cdfs", + "S-1-5-80-364680967-1232085744-2960737863-915504889-2752576923": "cdrom", + "S-1-5-80-3256172449-2363790065-3617575471-4144056108-756904704": "CertPropSvc", + "S-1-5-80-4066704878-4231214995-2335031091-3527122690-1574766183": "circlass", + "S-1-5-80-1506673549-1532669541-769420574-1605323189-863873827": "CLFS", + "S-1-5-80-776041216-1751974135-1557427478-1892253070-796752000": "clr_optimization_v2.0.50727_32", + "S-1-5-80-452204072-1743664639-1560983493-2640850116-597529692": "CmBatt", + "S-1-5-80-979911607-31916023-2827320217-2656655436-259985251": "cmdide", + "S-1-5-80-3573738861-3694853854-361022443-2442358023-2743921644": "CNG", + "S-1-5-80-3960644792-2999129865-644014482-29643289-3842828219": "Compbatt", + "S-1-5-80-832194277-1022982267-2217674263-2896671990-3011983110": "CompositeBus", + "S-1-5-80-593875016-1044814911-1112741138-2143646632-2690613739": "COMSysApp", + "S-1-5-80-3158764370-1001901224-1854525633-1718604346-2756706540": "crcdisk", + "S-1-5-80-3747264324-1669729390-1715156009-1010652712-2439569381": "Crusoe", + "S-1-5-80-3020380856-1381845346-309829523-1810616773-418643442": "crypt32", + "S-1-5-80-242729624-280608522-2219052887-3187409060-2225943459": "CryptSvc", + "S-1-5-80-3601020880-2087999432-167179594-730776211-2997520967": "CSC", + "S-1-5-80-1987853863-1639573247-1110726908-1137832616-3599624523": "CscService", + "S-1-5-80-1564160128-141119064-743480990-78466790-746535033": "DCLocator", + "S-1-5-80-1601830629-990752416-3372939810-977361409-3075122917": "DcomLaunch", + "S-1-5-80-654447679-1163530548-981569129-3608673666-3128964045": "defragsvc", + "S-1-5-80-3837255464-839197112-3211601036-3795322556-2690640524": "DfsC", + "S-1-5-80-1267473060-1890374259-1137250836-544356534-2546457154": "DFSR", + "S-1-5-80-2940520708-3855866260-481812779-327648279-1710889582": "Dhcp", + "S-1-5-80-2142581517-3954605861-2373846864-2138305209-1019737370": "discache", + "S-1-5-80-1827140278-1118305254-4004251663-1512899043-4081885502": "Disk", + "S-1-5-80-859482183-879914841-863379149-1145462774-2388618682": "Dnscache", + "S-1-5-80-3787436395-2174616005-3003730137-1094982900-1570567328": "dot3svc", + "S-1-5-80-2970612574-78537857-698502321-558674196-1451644582": "DPS", + "S-1-5-80-338020179-181244551-1629881386-919369987-4169324252": "drmkaud", + "S-1-5-80-3820654016-1545322283-1804062181-1022271772-3696306321": "DXGKrnl", + "S-1-5-80-2212058837-3965059022-779215765-3282659977-917192320": "E1G60", + "S-1-5-80-3578261754-285310837-913589462-2834155770-667502746": "EapHost", + "S-1-5-80-2437473203-2648204866-3612751994-635271166-3967841232": "Ecache", + "S-1-5-80-1191957972-1903257272-3657591267-1787121440-2523964525": "ebdrv", + "S-1-5-80-730263862-4055390735-403826019-1175694336-1277635259": "EFS", + "S-1-5-80-567955335-3455378119-3305749985-2554534624-1867504835": "ehRecvr", + "S-1-5-80-3864065939-1897331054-469427076-3133256761-1570309435": "ehSched", + "S-1-5-80-2913099195-3001839937-1914692661-1563395363-459793767": "ehstart", + "S-1-5-80-3118383011-3159412168-3368304685-4081854189-1392756948": "elxstor", + "S-1-5-80-1436322865-2295268783-31549072-3549518694-69512146": "EmdCache", + "S-1-5-80-557382581-4103702789-1349398007-826115979-1301810884": "EMDMgmt", + "S-1-5-80-1580004045-3657569029-3054886754-3760858607-1347140441": "ErrDev", + "S-1-5-80-1163726475-4032819940-2637749356-1655080563-3495319901": "ESENT", + "S-1-5-80-880578595-1860270145-482643319-2788375705-1540778122": "eventlog", + "S-1-5-80-1772571935-1555666882-3369284645-1675012128-2386634627": "EventSystem", + "S-1-5-80-339744372-1785209941-194342311-2969164887-2874010346": "exfat", + "S-1-5-80-3825849991-4144931059-247537738-1429287757-2349637904": "fastfat", + "S-1-5-80-2117685068-4011115449-2646761356-2137676340-222423812": "Fax", + "S-1-5-80-678085088-615808128-1967178352-3804608619-208504977": "fdc", + "S-1-5-80-364023826-931424190-487969545-1024119571-74567675": "fdPHost", + "S-1-5-80-3215268152-2863950836-530904203-4246843131-2183915461": "FDResPub", + "S-1-5-80-3048209083-3162952562-941345871-1437532549-835501875": "FileInfo", + "S-1-5-80-1352441077-2188484239-1994186818-620473926-3758853310": "Filetrace", + "S-1-5-80-2678475722-3718149211-1393662077-3558562392-2203603517": "flpydisk", + "S-1-5-80-916285479-1714977700-1732101595-331036679-1735462769": "FltMgr", + "S-1-5-80-3655275221-2954682349-3644260495-855223267-1438849333": "FontCache", + "S-1-5-80-3782458156-2098404076-3767342964-3617937256-1389734963": "FontCache3.0.0.0", + "S-1-5-80-4244156434-496195918-1908400060-3754471672-3389379472": "FsDepends", + "S-1-5-80-1638897150-273717933-3197303335-567190659-606579740": "Fs_Rec", + "S-1-5-80-221025945-1494805562-2841517651-3196795133-192498206": "fvevol", + "S-1-5-80-1150850083-1108777032-2236282716-3985597815-2701820264": "gagp30kx", + "S-1-5-80-2024188204-2445810227-898691311-2942020084-762398166": "gpsvc", + "S-1-5-80-2384017851-2441776339-3346382083-2430645704-3475981877": "hcw85cir", + "S-1-5-80-2193151998-1100362924-2192368770-2985476713-896696503": "HDAudBus", + "S-1-5-80-1648434057-4219984261-1802816958-334501717-1769477291": "HidBatt", + "S-1-5-80-191977210-1053814073-2805336524-1775407748-120039257": "HidBth", + "S-1-5-80-498696395-104441048-3395182230-3082814586-1375447691": "HidIr", + "S-1-5-80-89818136-74175777-88572358-3912780041-2421659406": "hidserv", + "S-1-5-80-1586586559-167648910-1414982260-3863830924-1724542190": "HidUsb", + "S-1-5-80-1373701630-3910968185-3388013410-2492353-937432973": "hkmsvc", + "S-1-5-80-2291748755-1591405548-1905550586-2340871825-1258388485": "HpCISSs", + "S-1-5-80-4028305664-2774326660-44957573-2454826285-2129126537": "HomeGroupListener", + "S-1-5-80-2620923248-4247863784-3378508180-2659151310-2535246811": "HomeGroupProvider", + "S-1-5-80-3952044490-1864224763-1322162546-396143671-1619397437": "HpSAMD", + "S-1-5-80-3734987283-965611577-2130035942-3636592211-2616856863": "HTTP", + "S-1-5-80-970016657-3034632851-3048190821-4182690298-3323420226": "i2omp", + "S-1-5-80-3096896632-2411553352-2084109408-2930423838-4282791216": "hwpolicy", + "S-1-5-80-738727139-3255065492-2264176241-1836141076-1899426695": "i8042prt", + "S-1-5-80-1156567179-1019273932-444819734-1772733284-2107707318": "iaStorV", + "S-1-5-80-2984992224-2588614340-2167448307-2303456600-125847566": "idsvc", + "S-1-5-80-3218395955-317132717-2440444880-267201483-2700625476": "iirsp", + "S-1-5-80-698886940-375981264-2691324669-2937073286-3841916615": "IKEEXT", + "S-1-5-80-3217419572-1740605331-1127140686-2317006352-2064317000": "inetaccs", + "S-1-5-80-3664101217-2276051299-423734030-2746486177-2766044424": "intelide", + "S-1-5-80-817570274-767070440-2629795609-3336305482-1678804590": "intelppm", + "S-1-5-80-2506443892-94066030-1663014834-2885971264-4189966690": "IPBusEnum", + "S-1-5-80-2750735467-3008441591-3989401642-3215998983-1344927289": "IpFilterDriver", + "S-1-5-80-62724632-2456781206-3863850748-1496050881-1042387526": "iphlpsvc", + "S-1-5-80-1361160473-1867727628-1338406996-3302040194-2851723982": "IpInIp", + "S-1-5-80-2771164118-4094026282-2266286801-3306161409-3436440840": "IPMIDRV", + "S-1-5-80-2368102602-26431353-856636621-1497418614-482242802": "IPNAT", + "S-1-5-80-433158070-3235422099-1317741036-1922328546-1834106188": "IRENUM", + "S-1-5-80-1308614567-1511795785-2741360970-8197000-3264788676": "isapnp", + "S-1-5-80-1446792217-3918178545-2165441202-3760590537-1875255596": "iScsiPrt", + "S-1-5-80-2249099846-2157059493-1994460756-1924820827-2369096692": "iteatapi", + "S-1-5-80-750512324-770881543-4197932906-3645560491-3779161573": "iteraid", + "S-1-5-80-1974511938-2400693546-1685170019-203554928-1466978163": "kbdclass", + "S-1-5-80-3058542000-3285469617-40650340-3734485625-1920508542": "kbdhid", + "S-1-5-80-1206118541-1677721718-2423781911-3372378849-3903984073": "KeyIso", + "S-1-5-80-3810688523-3855579666-1860693470-2666993558-46302070": "KSecDD", + "S-1-5-80-638937566-1168471176-3064579757-2631269312-170126454": "KSecPkg", + "S-1-5-80-2818357584-3387065753-4000393942-342927828-138088443": "KtmRm", + "S-1-5-80-879696042-2351668846-370232824-2524288904-4023536711": "LanmanServer", + "S-1-5-80-719998295-2833700043-1566817583-4093942769-1414026312": "LanmanWorkstation", + "S-1-5-80-3356507721-3148410333-1453554623-2317622189-363686743": "ldap", + "S-1-5-80-1339741203-2503426401-303705627-250156843-1210515524": "lltdio", + "S-1-5-80-940647296-341435850-43817331-158078607-2483727905": "lltdsvc", + "S-1-5-80-172094073-716411664-54255058-185476446-2329512179": "lmhosts", + "S-1-5-80-1037107160-813189200-1860894220-2610408748-1807657940": "Lsa", + "S-1-5-80-973905250-3368826558-2408393701-2645888229-3042295110": "LSI_FC", + "S-1-5-80-3066312493-2787136058-3895654580-111488809-2262703568": "LSI_SAS", + "S-1-5-80-935126585-3333887566-2369146147-2658756633-3860083864": "LSI_SAS2", + "S-1-5-80-702453548-2563122194-4165184037-877730421-2039909086": "LSI_SCSI", + "S-1-5-80-381203785-1552481550-3565819581-4159540168-38965703": "luafv", + "S-1-5-80-3770938798-2726624435-2075025292-3280341113-3618470894": "Mcx2Svc", + "S-1-5-80-1503963800-3543347063-2443146678-2767313893-605308357": "megasas", + "S-1-5-80-4024713676-1017792628-381990976-3540878265-1306153904": "MegaSR", + "S-1-5-80-2799810402-4136494038-1094338311-2889966999-3154753985": "MMCSS", + "S-1-5-80-2005225957-2795451222-469338742-3947262705-2044891099": "Modem", + "S-1-5-80-4207690787-1085901060-2295361997-2227230598-1253819078": "monitor", + "S-1-5-80-675551267-1826535266-117093185-28668227-296166608": "mouclass", + "S-1-5-80-3854853272-3832246511-1244659077-3165440039-2262758429": "mouhid", + "S-1-5-80-3601998905-441174471-4117363912-32772110-2632366064": "mountmgr", + "S-1-5-80-4261667920-1220466518-1749771309-2316901739-273317064": "mpio", + "S-1-5-80-3142377179-3443479297-2149323391-1756545698-484011292": "mpsdrv", + "S-1-5-80-3088073201-1464728630-1879813800-1107566885-823218052": "MpsSvc", + "S-1-5-80-2250298043-1491746124-3447101336-2334414474-2555807208": "Mraid35x", + "S-1-5-80-2688027615-1506195528-3802338144-777155390-618458321": "MRxDAV", + "S-1-5-80-2162099894-1456621096-2119874347-3743340265-2368304946": "mrxsmb", + "S-1-5-80-2676550360-252586896-1701879715-2742386574-1171030092": "mrxsmb10", + "S-1-5-80-3970894941-767821303-4047113619-2738918178-2351404876": "mrxsmb20", + "S-1-5-80-276420989-3971400029-4249224515-3588854300-972083571": "msahci", + "S-1-5-80-827450036-3359053657-3286484322-221598818-2985401197": "msdsm", + "S-1-5-80-3960419045-2460139048-4046793004-1809597027-2250574426": "MSDTC", + "S-1-5-80-1515650939-3601430262-2496924429-640160050-3998290523": "MSDTC Bridge 3.0.0.0", + "S-1-5-80-3825916667-3375043415-3384654478-3177665693-2200644784": "Msfs", + "S-1-5-80-4064639957-1408283007-2091294018-2122350837-1986927883": "mshidkmdf", + "S-1-5-80-537088188-2896597613-2307397767-3752262660-2081934664": "msisadrv", + "S-1-5-80-917953661-2020045820-2727011118-2260243830-4032185929": "MSiSCSI", + "S-1-5-80-685333868-2237257676-1431965530-1907094206-2438021966": "msiserver", + "S-1-5-80-1314579368-1827054856-3801607513-4137797117-3785845944": "MSKSSRV", + "S-1-5-80-3515336427-2373706795-1189292716-3451446183-2383180522": "MSPCLOCK", + "S-1-5-80-2550581486-1497628998-1973453189-3108482975-2816921478": "MSPQM", + "S-1-5-80-4273119239-1126992662-2069961181-78804100-786965295": "MsRPC", + "S-1-5-80-2731410647-2404537004-1422510964-3385838496-1398925663": "MSSCNTRS", + "S-1-5-80-2379877105-2122874852-2028670630-1350450415-3977667049": "mssmbios", + "S-1-5-80-294111013-494549581-4136661504-3518049416-761106507": "MSTEE", + "S-1-5-80-772196467-3194495650-2141286422-1986870660-3602995159": "MTConfig", + "S-1-5-80-2851636321-923882121-3805946377-1773657562-2703951580": "Mup", + "S-1-5-80-2006800713-1441093265-249754844-3404434343-1444102779": "napagent", + "S-1-5-80-3451137062-797777108-3464068327-231871278-2024511519": "NativeWifiP", + "S-1-5-80-2183409222-222800135-1539000935-3109909370-1207982808": "NDIS", + "S-1-5-80-1310191460-362243386-72972191-123604350-1188038626": "NdisCap", + "S-1-5-80-3307576507-4040802919-832577921-47721884-821370673": "NdisTapi", + "S-1-5-80-2426641292-1095310648-1538795067-2456674997-547968854": "Ndisuio", + "S-1-5-80-3137956796-3050520361-1309400342-955303752-3583020413": "NdisWan", + "S-1-5-80-3999445478-1493703614-491198216-2250085872-3662815299": "NDProxy", + "S-1-5-80-298519744-3326885196-200884095-1345730765-1206919721": "NetBIOS", + "S-1-5-80-3481163626-3922336224-2171110286-845444925-873416656": "NetBT", + "S-1-5-80-1589317753-1926951874-3424712441-2302911845-2572860984": "Netlogon", + "S-1-5-80-2898649604-2335086160-1904548223-3761738420-3855444835": "Netman", + "S-1-5-80-3635958274-2059881490-2225992882-984577281-633327304": "netprofm", + "S-1-5-80-1773860938-1487242074-882566118-4272343956-2175834232": "NetTcpPortSharing", + "S-1-5-80-3739586395-593861784-2557645679-4197025642-341497066": "nfrd960", + "S-1-5-80-3141615172-2057878085-1754447212-2405740020-3916490453": "NlaSvc", + "S-1-5-80-1093399993-2276725296-2148262981-2274078422-4284582767": "Npfs", + "S-1-5-80-2310782386-4237065203-3688974353-390202159-3511571085": "nsi", + "S-1-5-80-4100249314-4086313984-28913695-873679419-2144728263": "nsiproxy", + "S-1-5-80-1664281202-2302623734-631624840-3461998672-2259661997": "NTDS", + "S-1-5-80-1256884789-1691082103-446998474-1367286246-1639025938": "Ntfs", + "S-1-5-80-2470698091-2858014709-2643764839-982706939-3434751516": "ntrigdigi", + "S-1-5-80-2407861648-785230825-3529290450-2326204529-1810679516": "Null", + "S-1-5-80-3495072887-919096479-2204902451-1048921326-800355041": "nvraid", + "S-1-5-80-3611874924-3178792031-3565391826-286563291-3680247785": "nvstor", + "S-1-5-80-2661219475-1923594960-1294537542-2454943126-82436970": "nv_agp", + "S-1-5-80-4169196349-563482612-2169411968-43761830-802868667": "NwlnkFlt", + "S-1-5-80-1643415749-1981533051-3884744798-2669202348-601031005": "NwlnkFwd", + "S-1-5-80-1196941233-2569882653-2923823926-962244991-4277418": "ohci1394", + "S-1-5-80-967499406-1694984581-2959056265-2481940682-939264259": "p2pimsvc", + "S-1-5-80-1971585524-2528565899-3324366483-1300752743-2325226580": "p2psvc", + "S-1-5-80-3473791808-4104434288-1928902041-1743473672-1277326840": "Parport", + "S-1-5-80-156989346-1343554423-902067029-1673992682-1866693543": "partmgr", + "S-1-5-80-4196153372-502005009-1971508045-3354250645-3015555128": "Parvdm", + "S-1-5-80-1948712186-1330865447-943413596-1669284603-1648638051": "PcaSvc", + "S-1-5-80-2069178898-4023461412-1711560041-390887617-271771820": "pci", + "S-1-5-80-4052642423-944120264-588619640-546327341-1110646568": "pciide", + "S-1-5-80-2795309555-3957969320-2916397881-2593713121-382316838": "pcmcia", + "S-1-5-80-59707871-3298565586-1716270302-948228651-1074156479": "pcw", + "S-1-5-80-1570874813-103103538-3327933986-104584388-2119773521": "PEAUTH", + "S-1-5-80-3124040864-3101396827-3094488734-3028845762-1939139329": "PeerDistSvc", + "S-1-5-80-4023986828-1464965280-3211893748-414212150-4115790068": "PerfDisk", + "S-1-5-80-2413971036-1590988147-3808667159-2204172745-1373631640": "PerfNet", + "S-1-5-80-3515570427-2977692895-3762163048-1504969852-99088878": "PerfOS", + "S-1-5-80-3544016446-4087985546-3773506770-1472693371-3235341583": "PerfProc", + "S-1-5-80-2661322625-712705077-2999183737-3043590567-590698655": "pla", + "S-1-5-80-1981970923-922788642-3535304421-2999920573-318732269": "PlugPlay", + "S-1-5-80-3141781312-1794533130-3616533224-2008760771-2116720301": "PNRPAutoReg", + "S-1-5-80-372467825-374176116-1198570892-3192490889-1232022613": "PNRPsvc", + "S-1-5-80-3044542841-3639452079-4096941652-1606687743-1256249853": "PolicyAgent", + "S-1-5-80-4126081702-1836807445-3803306975-1029803806-2479180530": "PortProxy", + "S-1-5-80-2343416411-2961288913-598565901-392633850-2111459193": "Power", + "S-1-5-80-3735226416-1729687437-1959510470-190511368-398645692": "PptpMiniport", + "S-1-5-80-3367479018-119754134-174380200-3035551807-2744700953": "Processor", + "S-1-5-80-2422153244-111630262-1029994140-3645224535-4078427153": "PROCEXP", + "S-1-5-80-3816717743-33564931-1112267079-3548917561-928358339": "ProfSvc", + "S-1-5-80-656433041-336319937-100815201-2263438610-4002557366": "ProtectedStorage", + "S-1-5-80-133730547-3458667493-930392497-3658715967-3359215708": "Psched", + "S-1-5-80-1010784341-3590640432-2144716203-2371202623-2111191834": "ql2300", + "S-1-5-80-3680784227-2138494325-1045417256-846249285-1494284974": "ql40xx", + "S-1-5-80-1659118645-3148100556-861291880-3953320898-4045657812": "QWAVE", + "S-1-5-80-3324762131-3390532780-137711907-1761928331-1932425801": "QWAVEdrv", + "S-1-5-80-951069737-1097907447-3199478753-2018050253-2083677786": "RasAcd", + "S-1-5-80-4022575210-2284560452-710265691-3594820739-387418549": "RasAgileVpn", + "S-1-5-80-1802467488-1541022566-2033325545-854566965-652742428": "RasAuto", + "S-1-5-80-1290287420-3502600185-382990664-1700026297-1337626153": "Rasl2tp", + "S-1-5-80-4176366874-305252471-2256717057-2714189771-3552532790": "RasMan", + "S-1-5-80-4122454071-3550668693-4211410744-1298358403-2272725717": "RasPppoe", + "S-1-5-80-1331337031-2474836174-2661672254-391271513-2096420174": "RasSstp", + "S-1-5-80-2489667-2470848582-3865645512-452901963-4178804252": "rdbss", + "S-1-5-80-3687944073-3313860148-3136628839-3387249243-1709534714": "rdpbus", + "S-1-5-80-2431288241-149984296-2543083935-4067350611-1975817884": "RDPCDD", + "S-1-5-80-981872547-3861006530-3984275202-4085961120-2027028908": "RDPDD", + "S-1-5-80-23661045-4033652049-3526044993-1401805078-1749661838": "RDPDR", + "S-1-5-80-3464459778-79086046-1894495498-3954672505-2750168721": "RDPENCDD", + "S-1-5-80-191927475-3325244020-2133763035-2511185485-3827563125": "RDPNP", + "S-1-5-80-1432111213-2818786930-2152807080-3377190559-901933699": "RDPREFMP", + "S-1-5-80-1857653372-1313752195-3783661666-502273730-1171188227": "RDPWD", + "S-1-5-80-3474873350-2412947251-3085823233-2315640422-3546857610": "rdyboost", + "S-1-5-80-1954729425-4294152082-187165618-318331177-3831297489": "RemoteAccess", + "S-1-5-80-2822507136-3601578665-1013168651-121944544-1825232178": "RemoteRegistry", + "S-1-5-80-521322694-906040134-3864710659-1525148216-3451224162": "RpcEptMapper", + "S-1-5-80-4056015446-1496461683-1723632270-3351149576-1119802320": "RpcLocator", + "S-1-5-80-979556362-403687129-3954533659-2335141334-1547273080": "RpcSs", + "S-1-5-80-25112808-303066962-2306571906-3820953744-554449017": "rspndr", + "S-1-5-80-3189092957-1825937568-2097962828-592273195-15751640": "s3cap", + "S-1-5-80-3453257571-682267348-3447719424-2810041157-893746920": "SamSs", + "S-1-5-80-2172748946-1139208647-3745649895-1734051075-2323558886": "sbp2port", + "S-1-5-80-1209419826-1829913269-3824447628-1153237837-3789837839": "SCardSvr", + "S-1-5-80-3145502940-3408664484-1477142494-2517801300-3177717725": "scfilter", + "S-1-5-80-4125092361-1567024937-842823819-2091237918-836075745": "Schedule", + "S-1-5-80-1691538513-4084330536-1620899472-1113280783-3554754292": "SCPolicySvc", + "S-1-5-80-2983134835-1185273323-1712700529-1489848661-2325612824": "SDRSVC", + "S-1-5-80-1722176216-3611007545-3657005850-3814612847-1080390000": "secdrv", + "S-1-5-80-1399994486-219206332-302438500-304602034-1537790326": "seclogon", + "S-1-5-80-4259241309-1822918763-1176128033-1339750638-3428293995": "SENS", + "S-1-5-80-3168472476-176724102-2968832672-2340942973-2241613192": "SensrSvc", + "S-1-5-80-1658387481-2925800327-3198882180-3147662777-2274689045": "Serenum", + "S-1-5-80-3562253942-857828347-2712713407-944836455-3636585461": "Serial", + "S-1-5-80-3369720968-4228855631-3683183521-2094993598-1022421131": "sermouse", + "S-1-5-80-675414407-775065359-1035864904-999747831-2072146957": "ServiceModelEndpoint 3.0.0.0", + "S-1-5-80-1904953591-2738210791-1061154185-3936071259-221446881": "ServiceModelOperation 3.0.0.0", + "S-1-5-80-297390187-2405189348-2222284465-2989988878-4218767654": "ServiceModelService 3.0.0.0", + "S-1-5-80-4022436659-1090538466-1613889075-870485073-3428993833": "SessionEnv", + "S-1-5-80-1220365695-3871163487-2301282001-885120026-718998505": "sffdisk", + "S-1-5-80-1593449009-2408870187-1077724223-1518188577-3728252823": "sffp_mmc", + "S-1-5-80-1659054941-531967795-1983128084-3748020815-2241757750": "sffp_sd", + "S-1-5-80-1407380289-3518059920-3931497022-2754447733-2222417609": "sfloppy", + "S-1-5-80-2009329905-444645132-2728249442-922493431-93864177": "SharedAccess", + "S-1-5-80-1690854464-3758363787-3981977099-3843555589-1401248062": "ShellHWDetection", + "S-1-5-80-2037654479-150732571-4235160932-1988269395-3027078133": "sisagp", + "S-1-5-80-2290943609-1211775869-3660739483-1432647055-1639441565": "SiSRaid2", + "S-1-5-80-1016766434-4163349990-2054491751-1265000292-413406215": "SiSRaid4", + "S-1-5-80-2119565420-4155874467-2934723793-509086461-374458824": "slsvc", + "S-1-5-80-429025866-4105586292-427562881-1309981334-1060966148": "SLUINotify", + "S-1-5-80-97513841-1071082959-3069755588-526311685-2961431215": "Smb", + "S-1-5-80-2400470686-1781479961-2091307112-2920730856-2901594176": "SMSvcHost 3.0.0.0", + "S-1-5-80-3964583643-2633443559-2834438935-3739664028-1580655619": "SNMPTRAP", + "S-1-5-80-2246094146-3761615012-3991572358-959820157-1291755210": "spldr", + "S-1-5-80-3951239711-1671533544-1416304335-3763227691-3930497994": "Spooler", + "S-1-5-80-123231216-2592883651-3715271367-3753151631-4175906628": "sppsvc", + "S-1-5-80-2105443381-1869407242-828286827-1344996006-2512971347": "sppuinotify", + "S-1-5-80-3318989984-2647182497-3022510041-1919214433-3551303480": "srv", + "S-1-5-80-1034188721-156321652-2901307485-3049929104-2850741453": "srv2", + "S-1-5-80-385674269-2427993094-4248660116-187565782-2803330530": "srvnet", + "S-1-5-80-486568272-975562994-1883531608-2732234258-332540751": "SSDPSRV", + "S-1-5-80-3435701886-799518250-3791383489-3228296122-2938884314": "SstpSvc", + "S-1-5-80-2502136977-515215333-1091199184-4078967732-698071891": "stexstor", + "S-1-5-80-3182985763-1431228038-2757062859-428472846-3914011746": "StiSvc", + "S-1-5-80-3877927215-2009774003-1789373229-1350139498-1490546062": "storflt", + "S-1-5-80-3355894222-2288616474-3163838539-1515771758-43395969": "StorSvc", + "S-1-5-80-2227193670-1472088527-4216801891-1255609005-3742950393": "storvsc", + "S-1-5-80-2499453150-1816575225-2698105218-861119070-2299588587": "swenum", + "S-1-5-80-1614360071-3471039648-1078047007-3707138327-1664821506": "swprv", + "S-1-5-80-3277458932-3608563558-2424252742-1006353051-3439664691": "Symc8xx", + "S-1-5-80-714262929-1152213303-426872964-3738532716-4000887735": "Sym_hi", + "S-1-5-80-73616012-2741736120-1450548080-3749295283-3869351969": "Sym_u3", + "S-1-5-80-2590341223-3996088049-3993122417-23640849-324535191": "SysMain", + "S-1-5-80-949921180-3923668869-394927020-528789358-3592448931": "TabletInputService", + "S-1-5-80-4230913304-2206818457-801678004-120036174-1892434133": "TapiSrv", + "S-1-5-80-4167276341-681140529-2035857140-584847688-708058301": "TBS", + "S-1-5-80-2869215396-3426808149-752611693-425565463-2833823703": "Tcpip", + "S-1-5-80-842221325-3630721446-2015653073-424833842-1069621030": "TCPIP6", + "S-1-5-80-1243767512-207181711-1639953288-846964026-179032965": "TCPIP6TUNNEL", + "S-1-5-80-183440435-3873164873-1814133288-2746138770-1127128543": "tcpipreg", + "S-1-5-80-517380867-1805075581-15937331-3649701458-2279870393": "TCPIPTUNNEL", + "S-1-5-80-1205525636-1316560639-1871536985-2915653626-3847227622": "TDPIPE", + "S-1-5-80-2653571336-860310240-1707811817-3246300807-2032786575": "TDTCP", + "S-1-5-80-1811008277-2130293716-2312968959-3698054739-726352487": "tdx", + "S-1-5-80-600900383-3940208308-3622757659-1160125390-3717916961": "TermDD", + "S-1-5-80-446051430-1559341753-4161941529-1950928533-810483104": "TermService", + "S-1-5-80-1189432293-2777010110-2640223427-1344437502-1956879817": "Themes", + "S-1-5-80-56840347-690487168-3179794702-1332568925-762031181": "THREADORDER", + "S-1-5-80-537470750-3688389562-3749243086-269898693-579266445": "TPAutoConnSvc", + "S-1-5-80-1495131930-2676463755-2136540566-1190107536-2533052015": "TPVCGateway", + "S-1-5-80-768763963-4214222998-2156221936-2953597973-713500239": "TrkWks", + "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464": "TrustedInstaller", + "S-1-5-80-602153688-1728218534-2156437410-2444491971-1703742505": "TSDDD", + "S-1-5-80-3250179172-3414919659-2784612865-1947102831-1832745880": "tssecsrv", + "S-1-5-80-3666930311-739912689-1101093007-1147922636-412121971": "tunmp", + "S-1-5-80-3579196564-3960183121-2393617881-1570124860-2153905208": "tunnel", + "S-1-5-80-3249175164-480052304-527258952-251146422-1017202920": "uagp35", + "S-1-5-80-4290168682-2694755981-2883756118-2205499398-4079537721": "udfs", + "S-1-5-80-2413584400-2834772909-3391057178-2993126719-4094614649": "UGatherer", + "S-1-5-80-900581847-2069635957-4095211819-2149323943-1216697729": "UGTHRSVC", + "S-1-5-80-997887591-2350776071-3817597635-4146973621-2526406719": "UI0Detect", + "S-1-5-80-4194149548-235381792-2829184477-3934495640-667433095": "uliagpkx", + "S-1-5-80-2051301031-3598501189-881763489-2611917303-2352103085": "uliahci", + "S-1-5-80-4294381996-3573690956-4084941264-2318251564-135754816": "UlSata", + "S-1-5-80-2849548708-3602852847-3953931013-1110249439-3333230880": "ulsata2", + "S-1-5-80-3018007626-163191633-622627787-1206491734-2917835273": "umbus", + "S-1-5-80-2029728201-2796881031-2302868875-2454600822-1203790938": "UmPass", + "S-1-5-80-2014626298-1656748749-3847481816-918933055-2469338456": "UmRdpService", + "S-1-5-80-448846144-1414373772-1578130625-718576682-2306699751": "upnphost", + "S-1-5-80-3724553804-53543757-2557641770-141295351-1687883918": "usb", + "S-1-5-80-4022141922-741376770-3260236731-1675477288-3792235576": "usbccgp", + "S-1-5-80-2601879200-4032607390-2815923362-3101623786-2213233685": "usbcir", + "S-1-5-80-1032545752-2203350250-1701939687-317337126-3231707909": "usbehci", + "S-1-5-80-676136802-2607101929-335774531-4135730467-913299484": "usbhub", + "S-1-5-80-3434778094-456680973-2488395463-338906152-1015349184": "usbohci", + "S-1-5-80-3620574345-1163766744-4010839292-3531329841-768311061": "usbprint", + "S-1-5-80-376233901-499118290-773318279-1925188704-297947815": "USBSTOR", + "S-1-5-80-2717376493-4290053016-2054941639-3048903775-1780974753": "usbuhci", + "S-1-5-80-2815190569-4075358141-1041947382-2198045348-980246365": "UxSms", + "S-1-5-80-2901324718-895851292-2096622302-170690027-1637913602": "VaultSvc", + "S-1-5-80-2236596344-777810374-464678914-301799185-133794676": "vdrvroot", + "S-1-5-80-2196396108-1448510645-203779624-3888580976-3789157697": "vds", + "S-1-5-80-1636345116-1749775499-167646407-1402041886-784684825": "vga", + "S-1-5-80-1604054522-1120073184-2766342441-3740248177-2194771659": "VgaSave", + "S-1-5-80-2349230263-3936233330-585165183-483748113-2063106807": "vhdmp", + "S-1-5-80-269018121-2628019534-3958128902-1689023713-3977233287": "viaagp", + "S-1-5-80-702914695-4281403409-954615538-3988029004-192649218": "ViaC7", + "S-1-5-80-3488702259-1115883433-1783531185-1350626685-2323838072": "viaide", + "S-1-5-80-3414199520-1924951526-579304523-1555932441-262361574": "vm3dmp", + "S-1-5-80-3316781363-2712907428-2579548995-1296955556-57435734": "VMAUDIO", + "S-1-5-80-394042835-174396444-3357755573-789530950-2357907384": "vmbus", + "S-1-5-80-3485585108-3288609388-3381644673-894183282-3425970148": "VMBusHID", + "S-1-5-80-2053731399-3564616636-592537298-4187980385-3071434599": "vmci", + "S-1-5-80-4081816966-3135276745-2345987325-2511854693-3099376874": "vmdebug", + "S-1-5-80-2844247271-1920892496-2185725435-2733799570-1491885128": "vmhgfs", + "S-1-5-80-2713566713-2012099321-1704287870-164250842-2950185051": "VMMEMCTL", + "S-1-5-80-616456234-2657522756-2692773202-1293725715-2143369223": "vmmouse", + "S-1-5-80-470576323-3739623512-411527224-1524486745-930631467": "vmrawdsk", + "S-1-5-80-994229404-1081919929-268374983-1858992150-4232923339": "VMTools", + "S-1-5-80-3615470141-4057994987-1930054357-1444440834-2714780835": "VMUpgradeHelper", + "S-1-5-80-3972256235-858188783-2536722634-3029314587-3393749697": "vmvss", + "S-1-5-80-1570634675-3893565091-22195573-2267868061-2898682217": "volmgr", + "S-1-5-80-2228288927-839465256-4097931996-4258784654-3424789253": "volmgrx", + "S-1-5-80-2161309226-1540144261-2901834345-3792977468-1183436922": "volsnap", + "S-1-5-80-1269120828-58111527-683397690-4062780901-3407528550": "vsmraid", + "S-1-5-80-3195062495-2862850656-3724129271-1847284719-4038691091": "VSS", + "S-1-5-80-4271242282-3170619077-2600330701-1558677754-1139114601": "vwifibus", + "S-1-5-80-4267341169-2882910712-659946508-2704364837-2204554466": "W32Time", + "S-1-5-80-989796750-4090848350-2040919084-978865222-2182970707": "W3SVC", + "S-1-5-80-1272828037-3321607953-1682131387-4084423848-3273467238": "WacomPen", + "S-1-5-80-145391760-3682396335-1395736941-2543690743-1822485816": "WANARP", + "S-1-5-80-3957613141-1606606214-622769385-3049525404-2510868034": "Wanarpv6", + "S-1-5-80-1549550529-11381693-4027442525-4081535042-2424139505": "wbengine", + "S-1-5-80-1577343513-2244782562-3500840712-2807016722-4230555396": "WbioSrvc", + "S-1-5-80-1555863574-1012459212-3842453055-37978308-1142448422": "wcncsvc", + "S-1-5-80-4064017820-1559943312-846267769-2219870576-1957141527": "WcsPlugInService", + "S-1-5-80-3405261312-3324525412-773550320-3159108954-1126011555": "Wd", + "S-1-5-80-2731089040-2526960094-3333867314-868407530-1311763772": "Wdf01000", + "S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420": "WdiServiceHost", + "S-1-5-80-3524758515-3090971750-345616940-2322499744-3530715838": "WdiSystemHost", + "S-1-5-80-324959683-3395802011-921526492-919036580-1730255754": "WebClient", + "S-1-5-80-4059739203-877974739-1245631912-527174227-2996563517": "Wecsvc", + "S-1-5-80-3594706986-2537596223-181334840-1741483385-1351671666": "wercplsupport", + "S-1-5-80-3299868208-4286319593-1091140620-3583751967-1732444380": "WerSvc", + "S-1-5-80-2019001281-2253379323-945087313-3738653069-3773415333": "WfpLwf", + "S-1-5-80-4016954646-3779912912-520790876-2627662839-2216516612": "WIMMount", + "S-1-5-80-1367312344-4235937835-3348187091-2947416599-1643272376": "win32dd", + "S-1-5-80-1913148863-3492339771-4165695881-2087618961-4109116736": "WinDefend", + "S-1-5-80-3760743496-293058752-544796799-945139227-648175845": "Windows Workflow Foundation 3.0.0.0", + "S-1-5-80-2455429942-3131183193-3617688776-595395669-3772047725": "WinHttpAutoProxySvc", + "S-1-5-80-3750560858-172214265-3889451188-1914796615-4100997547": "Winmgmt", + "S-1-5-80-569256582-2953403351-2909559716-1301513147-412116970": "WinRM", + "S-1-5-80-3758380775-581010763-2947690711-3499621892-3054972477": "Winsock", + "S-1-5-80-197470898-1564017914-2276667423-138762734-2890991316": "WinSock2", + "S-1-5-80-1428027539-3309602793-2678353003-1498846795-3763184142": "Wlansvc", + "S-1-5-80-404760553-4074834012-3606039051-2170089041-3496108291": "WmiAcpi", + "S-1-5-80-1672893355-2301755825-1450106782-2724904875-1401714515": "WmiApRpl", + "S-1-5-80-1851371743-411767070-3743290205-1090512353-603110601": "wmiApSrv", + "S-1-5-80-2375682873-768044350-3534595160-1005545032-2873800392": "WMPNetworkSvc", + "S-1-5-80-2153317275-3787551921-2333987345-3394040919-509713777": "WPCSvc", + "S-1-5-80-113310567-2163499630-2787090463-221477905-209227094": "WPDBusEnum", + "S-1-5-80-1339864866-2803517768-580965624-1158720225-1206284216": "ws2ifsl", + "S-1-5-80-3232712927-1625117661-2590453128-1738570065-3637376297": "wscsvc", + "S-1-5-80-117416528-2204451360-1913602512-1355018040-1234992034": "WSearch", + "S-1-5-80-1961591210-2878639619-2091680054-2529124376-3572759234": "WSearchIdxPi", + "S-1-5-80-1014140700-3308905587-3330345912-272242898-93311788": "wuauserv", + "S-1-5-80-69171120-2364612362-2758615892-3595098197-2063739924": "WudfPf", + "S-1-5-80-1839061227-813336325-324579571-4216704371-1399658985": "WUDFRd", + "S-1-5-80-2652678385-582572993-1835434367-1344795993-749280709": "wudfsvc", + "S-1-5-80-3981856537-581775623-1136376035-2066872258-409572886": "WwanSvc", + "S-1-5-80-2933569122-2468899862-1495779727-289297006-142656920": "xmlprov" + }, + "sids re":[ + ["S-1-5-[0-9-]+-500$", "Administrator"], + ["S-1-5-[0-9-]+-501$", "Guest"], + ["S-1-5-[0-9-]+-502$", "KRBTGT"], + ["S-1-5-[0-9-]+-512$", "Domain Admins"], + ["S-1-5-[0-9-]+-513$", "Domain Users"], + ["S-1-5-[0-9-]+-514$", "Domain Guests"], + ["S-1-5-[0-9-]+-515$", "Domain Computers"], + ["S-1-5-[0-9-]+-516$", "Domain Controllers"], + ["S-1-5-[0-9-]+-517$", "Cert Publishers"], + ["S-1-5-[0-9-]+-520$", "Group Policy Creator Owners"], + ["S-1-5-[0-9-]+-533$", "RAS and IAS Servers"], + ["S-1-5-5-[0-9]+-[0-9]+", "Logon Session"], + ["S-1-5-21-[0-9-]+-518$", "Schema Admins"], + ["S-1-5-21-[0-9-]+-519$", "Enterprise Admins"], + ["S-1-5-21-[0-9-]+-553$", "RAS Servers"], + ["S-1-5-21-[0-9-]+-498$", "Enterprise Read-Only Domain Controllers"], + ["S-1-5-21-[0-9-]+-521$", "Read-Only Domain Controllers"], + ["S-1-5-21-[0-9-]+-522$", "Cloneable Domain Controllers"], + ["S-1-5-21-[0-9-]+-525$", "Protected Users"], + ["S-1-5-21-[0-9-]+-553$", "Remote Access Services (RAS)"] + ], + "privileges":{ + "2": ["SeCreateTokenPrivilege", "Create a token object"], + "3": ["SeAssignPrimaryTokenPrivilege", "Replace a process-level token"], + "4": ["SeLockMemoryPrivilege", "Lock pages in memory"], + "5": ["SeIncreaseQuotaPrivilege", "Increase quotas"], + "6": ["SeMachineAccountPrivilege", "Add workstations to the domain"], + "7": ["SeTcbPrivilege", "Act as part of the operating system"], + "8": ["SeSecurityPrivilege", "Manage auditing and security log"], + "9": ["SeTakeOwnershipPrivilege", "Take ownership of files/objects"], + "10": ["SeLoadDriverPrivilege", "Load and unload device drivers"], + "11": ["SeSystemProfilePrivilege", "Profile system performance"], + "12": ["SeSystemtimePrivilege", "Change the system time"], + "13": ["SeProfileSingleProcessPrivilege", "Profile a single process"], + "14": ["SeIncreaseBasePriorityPrivilege", "Increase scheduling priority"], + "15": ["SeCreatePagefilePrivilege", "Create a pagefile"], + "16": ["SeCreatePermanentPrivilege", "Create permanent shared objects"], + "17": ["SeBackupPrivilege", "Backup files and directories"], + "18": ["SeRestorePrivilege", "Restore files and directories"], + "19": ["SeShutdownPrivilege", "Shut down the system"], + "20": ["SeDebugPrivilege", "Debug programs"], + "21": ["SeAuditPrivilege", "Generate security audits"], + "22": ["SeSystemEnvironmentPrivilege", "Edit firmware environment values"], + "23": ["SeChangeNotifyPrivilege", "Receive notifications of changes to files or directories"], + "24": ["SeRemoteShutdownPrivilege", "Force shutdown from a remote system"], + "25": ["SeUndockPrivilege", "Remove computer from docking station"], + "26": ["SeSyncAgentPrivilege", "Synch directory service data"], + "27": ["SeEnableDelegationPrivilege", "Enable user accounts to be trusted for delegation"], + "28": ["SeManageVolumePrivilege", "Manage the files on a volume"], + "29": ["SeImpersonatePrivilege", "Impersonate a client after authentication"], + "30": ["SeCreateGlobalPrivilege", "Create global objects"], + "31": ["SeTrustedCredManAccessPrivilege", "Access Credential Manager as a trusted caller"], + "32": ["SeRelabelPrivilege", "Modify the mandatory integrity level of an object"], + "33": ["SeIncreaseWorkingSetPrivilege", "Allocate more memory for user applications"], + "34": ["SeTimeZonePrivilege", "Adjust the time zone of the computer's internal clock"], + "35": ["SeCreateSymbolicLinkPrivilege", "Required to create a symbolic link"], + "36": ["SeDelegateSessionUserImpersonatePrivilege", "Obtain an impersonation token for another user in the same session."] + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/ssdt.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/ssdt.py new file mode 100644 index 00000000..539d2e0a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/ssdt.py @@ -0,0 +1,132 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import os +from typing import Any, Iterator, List, Tuple + +from volatility.framework import constants, interfaces +from volatility.framework import contexts +from volatility.framework import exceptions, symbols +from volatility.framework import renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import modules + + +class SSDT(plugins.PluginInterface): + """Lists the system call table.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'modules', plugin = modules.Modules, version = (1, 0, 0)), + ] + + @classmethod + def build_module_collection(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> contexts.ModuleCollection: + """Builds a collection of modules. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A Module collection of available modules based on `Modules.list_modules` + """ + + mods = modules.Modules.list_modules(context, layer_name, symbol_table) + context_modules = [] + + for mod in mods: + + try: + module_name_with_ext = mod.BaseDllName.get_string() + except exceptions.InvalidAddressException: + # there's no use for a module with no name? + continue + + module_name = os.path.splitext(module_name_with_ext)[0] + + symbol_table_name = None + if module_name in constants.windows.KERNEL_MODULE_NAMES: + symbol_table_name = symbol_table + + context_module = contexts.SizedModule(context, + module_name, + layer_name, + mod.DllBase, + mod.SizeOfImage, + symbol_table_name = symbol_table_name) + + context_modules.append(context_module) + + return contexts.ModuleCollection(context_modules) + + def _generator(self) -> Iterator[Tuple[int, Tuple[int, int, Any, Any]]]: + + layer_name = self.config['primary'] + collection = self.build_module_collection(self.context, self.config["primary"], self.config["nt_symbols"]) + + kvo = self.context.layers[layer_name].config['kernel_virtual_offset'] + ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = layer_name, offset = kvo) + + # this is just one way to enumerate the native (NT) service table. + # to do the same thing for the Win32K service table, we would need Win32K.sys symbol support + ## we could also find nt!KeServiceDescriptorTable (NT) and KeServiceDescriptorTableShadow (NT, Win32K) + service_table_address = ntkrnlmp.get_symbol("KiServiceTable").address + service_limit_address = ntkrnlmp.get_symbol("KiServiceLimit").address + service_limit = ntkrnlmp.object(object_type = "int", offset = service_limit_address) + + # on 32-bit systems the table indexes are 32-bits and contain pointers (unsigned) + # on 64-bit systems the indexes are also 32-bits but they're offsets from the + # base address of the table and can be negative, so we need a signed data type + is_kernel_64 = symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"]) + if is_kernel_64: + array_subtype = "long" + + def kvo_calulator(func: int) -> int: + return kvo + service_table_address + (func >> 4) + + find_address = kvo_calulator + else: + array_subtype = "unsigned long" + + def passthrough(func: int) -> int: + return func + + find_address = passthrough + + functions = ntkrnlmp.object(object_type = "array", + offset = service_table_address, + subtype = ntkrnlmp.get_type(array_subtype), + count = service_limit) + + for idx, function_obj in enumerate(functions): + + function = find_address(function_obj) + module_symbols = collection.get_module_symbols_by_absolute_location(function) + + for module_name, symbol_generator in module_symbols: + symbols_found = False + + for symbol in symbol_generator: + symbols_found = True + yield (0, (idx, format_hints.Hex(function), module_name, symbol.split(constants.BANG)[1])) + + if not symbols_found: + yield (0, (idx, format_hints.Hex(function), module_name, renderers.NotAvailableValue())) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid([("Index", int), ("Address", format_hints.Hex), ("Module", str), ("Symbol", str)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/strings.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/strings.py new file mode 100644 index 00000000..90fb82df --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/strings.py @@ -0,0 +1,131 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import re +from os import path +from typing import Dict, Generator, List, Set, Tuple + +from volatility.framework import interfaces, renderers, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import intel, resources, linear +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class Strings(interfaces.plugins.PluginInterface): + """Reads output from the strings command and indicates which process(es) each string belongs to.""" + + _required_framework_version = (2, 0, 0) + strings_pattern = re.compile(rb"(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)\n?") + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.URIRequirement(name = "strings_file", description = "Strings file") + ] + # TODO: Make URLRequirement that can accept a file address which the framework can open + + def run(self): + return renderers.TreeGrid([("String", str), ("Physical Address", format_hints.Hex), ("Result", str)], + self._generator()) + + def _generator(self) -> Generator[Tuple, None, None]: + """Generates results from a strings file.""" + revmap = self.generate_mapping(self.config['primary']) + + accessor = resources.ResourceAccessor() + strings_fp = accessor.open(self.config['strings_file'], "rb") + strings_size = path.getsize(strings_fp.file.name) + + line = strings_fp.readline() + last_prog = 0 + while line: + try: + offset, string = self._parse_line(line) + try: + revmap_list = [name + ":" + hex(offset) for (name, offset) in revmap[offset >> 12]] + except (IndexError, KeyError): + revmap_list = ["FREE MEMORY"] + yield (0, (str(string, 'latin-1'), format_hints.Hex(offset), ", ".join(revmap_list))) + except ValueError: + vollog.error("Strings file is in the wrong format") + return + line = strings_fp.readline() + prog = strings_fp.tell() / strings_size * 100 + if round(prog, 1) > last_prog: + last_prog = round(prog, 1) + self._progress_callback(prog, "Matching strings in memory") + + def _parse_line(self, line: bytes) -> Tuple[int, bytes]: + """Parses a single line from a strings file. + + Args: + line: bytes of the line of a strings file (an offset and a string) + + Returns: + Tuple of the offset and the string found at that offset + """ + + match = self.strings_pattern.search(line) + if not match: + raise ValueError("Strings file contains invalid strings line") + offset, string = match.group(1, 2) + return int(offset), string + + def generate_mapping(self, layer_name: str) -> Dict[int, Set[Tuple[str, int]]]: + """Creates a reverse mapping between virtual addresses and physical + addresses. + + Args: + layer_name: the layer to map against the string lines + + Returns: + A mapping of virtual offsets to strings and physical offsets + """ + layer = self._context.layers[layer_name] + reverse_map = dict() # type: Dict[int, Set[Tuple[str, int]]] + if isinstance(layer, intel.Intel): + # We don't care about errors, we just wanted chunks that map correctly + for mapval in layer.mapping(0x0, layer.maximum_address, ignore_errors = True): + offset, _, mapped_offset, mapped_size, maplayer = mapval + for val in range(mapped_offset, mapped_offset + mapped_size, 0x1000): + cur_set = reverse_map.get(mapped_offset >> 12, set()) + cur_set.add(("kernel", offset)) + reverse_map[mapped_offset >> 12] = cur_set + self._progress_callback((offset * 100) / layer.maximum_address, "Creating reverse kernel map") + + # TODO: Include kernel modules + + for process in pslist.PsList.list_processes(self.context, self.config['primary'], + self.config['nt_symbols']): + proc_id = "Unknown" + try: + proc_id = process.UniqueProcessId + proc_layer_name = process.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format( + proc_id, excp.invalid_address, excp.layer_name)) + continue + + proc_layer = self.context.layers[proc_layer_name] + if isinstance(proc_layer, linear.LinearlyMappedLayer): + for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True): + mapped_offset, _, offset, mapped_size, maplayer = mapval + for val in range(mapped_offset, mapped_offset + mapped_size, 0x1000): + cur_set = reverse_map.get(mapped_offset >> 12, set()) + cur_set.add(("Process {}".format(process.UniqueProcessId), offset)) + reverse_map[mapped_offset >> 12] = cur_set + # FIXME: make the progress for all processes, rather than per-process + self._progress_callback((offset * 100) / layer.maximum_address, + "Creating mapping for task {}".format(process.UniqueProcessId)) + + return reverse_map diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/svcscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/svcscan.py new file mode 100644 index 00000000..8c986663 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/svcscan.py @@ -0,0 +1,170 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List + +from volatility.framework import interfaces, renderers, constants, symbols, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import scanners +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import versions +from volatility.framework.symbols.windows.extensions import services +from volatility.plugins.windows import poolscanner, vadyarascan, pslist + +vollog = logging.getLogger(__name__) + + +class SvcScan(interfaces.plugins.PluginInterface): + """Scans for windows services.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.PluginRequirement(name = 'poolscanner', plugin = poolscanner.PoolScanner, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'vadyarascan', plugin = vadyarascan.VadYaraScan, version = (1, 0, 0)) + ] + + @staticmethod + def get_record_tuple(service_record: interfaces.objects.ObjectInterface): + return (format_hints.Hex(service_record.vol.offset), service_record.Order, service_record.get_pid(), + service_record.Start.description, service_record.State.description, service_record.get_type(), + service_record.get_name(), service_record.get_display(), service_record.get_binary()) + + @staticmethod + def create_service_table(context: interfaces.context.ContextInterface, symbol_table: str, config_path: str) -> str: + """Constructs a symbol table containing the symbols for services + depending upon the operating system in use. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of the table containing the kernel symbols + config_path: The configuration path for any settings required by the new table + + Returns: + A symbol table containing the symbols necessary for services + """ + native_types = context.symbol_space[symbol_table].natives + is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) + + if versions.is_windows_xp(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-xp-x86" + elif versions.is_xp_or_2003(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-xp-2003-x64" + elif versions.is_win10_16299_or_later(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-win10-16299-x64" + elif versions.is_win10_16299_or_later(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-win10-16299-x86" + elif versions.is_win10_up_to_15063(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-win8-x64" + elif versions.is_win10_up_to_15063(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-win8-x86" + elif versions.is_win10_15063(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-win10-15063-x64" + elif versions.is_win10_15063(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-win10-15063-x86" + elif versions.is_windows_8_or_later(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-win8-x64" + elif versions.is_windows_8_or_later(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-win8-x86" + elif versions.is_vista_or_later(context = context, symbol_table = symbol_table) and is_64bit: + symbol_filename = "services-vista-x64" + elif versions.is_vista_or_later(context = context, symbol_table = symbol_table) and not is_64bit: + symbol_filename = "services-vista-x86" + else: + raise NotImplementedError("This version of Windows is not supported!") + + return intermed.IntermediateSymbolTable.create(context, + config_path, + "windows", + symbol_filename, + class_types = services.class_types, + native_types = native_types) + + def _generator(self): + + service_table_name = self.create_service_table(self.context, self.config["nt_symbols"], self.config_path) + + relative_tag_offset = self.context.symbol_space.get_type(service_table_name + constants.BANG + + "_SERVICE_RECORD").relative_child_offset("Tag") + + filter_func = pslist.PsList.create_name_filter(["services.exe"]) + + is_vista_or_later = versions.is_vista_or_later(context = self.context, symbol_table = self.config["nt_symbols"]) + + if is_vista_or_later: + service_tag = b"serH" + else: + service_tag = b"sErv" + + seen = [] + + for task in pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func): + + proc_id = "Unknown" + try: + proc_id = task.UniqueProcessId + proc_layer_name = task.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, + excp.layer_name)) + continue + + layer = self.context.layers[proc_layer_name] + + for offset in layer.scan(context = self.context, + scanner = scanners.BytesScanner(needle = service_tag), + sections = vadyarascan.VadYaraScan.get_vad_maps(task)): + + if not is_vista_or_later: + service_record = self.context.object(service_table_name + constants.BANG + "_SERVICE_RECORD", + offset = offset - relative_tag_offset, + layer_name = proc_layer_name) + + if not service_record.is_valid(): + continue + + yield (0, self.get_record_tuple(service_record)) + else: + service_header = self.context.object(service_table_name + constants.BANG + "_SERVICE_HEADER", + offset = offset, + layer_name = proc_layer_name) + + if not service_header.is_valid(): + continue + + # since we walk the s-list backwards, if we've seen + # an object, then we've also seen all objects that + # exist before it, thus we can break at that time. + for service_record in service_header.ServiceRecord.traverse(): + if service_record in seen: + break + seen.append(service_record) + yield (0, self.get_record_tuple(service_record)) + + def run(self): + return renderers.TreeGrid([ + ('Offset', format_hints.Hex), + ('Order', int), + ('Pid', int), + ('Start', str), + ('State', str), + ('Type', str), + ('Name', str), + ('Display', str), + ('Binary', str), + ], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/symlinkscan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/symlinkscan.py new file mode 100644 index 00000000..74f5e116 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/symlinkscan.py @@ -0,0 +1,80 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +from typing import Iterable + +from volatility.framework import renderers, exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins import timeliner +from volatility.plugins.windows import poolscanner + + +class SymlinkScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Scans for links present in a particular windows memory image.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + ] + + @classmethod + def scan_symlinks(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str) -> \ + Iterable[interfaces.objects.ObjectInterface]: + """Scans for links using the poolscanner module and constraints. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + + Returns: + A list of symlink objects found by scanning memory for the Symlink pool signatures + """ + + constraints = poolscanner.PoolScanner.builtin_constraints(symbol_table, [b'Sym\xe2', b'Symb']) + + for result in poolscanner.PoolScanner.generate_pool_scan(context, layer_name, symbol_table, constraints): + + _constraint, mem_object, _header = result + yield mem_object + + def _generator(self): + for link in self.scan_symlinks(self.context, self.config['primary'], self.config['nt_symbols']): + + try: + from_name = link.get_link_name() + except (ValueError, exceptions.InvalidAddressException): + continue + + try: + to_name = link.LinkTarget.String + except exceptions.InvalidAddressException: + continue + + yield (0, (format_hints.Hex(link.vol.offset), link.get_create_time(), from_name, to_name)) + + def generate_timeline(self): + for row in self._generator(): + _depth, row_data = row + description = "Symlink: {} -> {}".format(row_data[2], row_data[3]) + yield (description, timeliner.TimeLinerType.CREATED, row_data[1]) + + def run(self): + return renderers.TreeGrid([ + ("Offset", format_hints.Hex), + ("CreateTime", datetime.datetime), + ("From Name", str), + ("To Name", str), + ], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadinfo.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadinfo.py new file mode 100644 index 00000000..a68bd717 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadinfo.py @@ -0,0 +1,216 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Callable, List, Generator, Iterable, Type, Optional + +from volatility.framework import renderers, interfaces, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.objects import utility +from volatility.framework.renderers import format_hints +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + +# these are from WinNT.h +winnt_protections = { + "PAGE_NOACCESS": 0x01, + "PAGE_READONLY": 0x02, + "PAGE_READWRITE": 0x04, + "PAGE_WRITECOPY": 0x08, + "PAGE_EXECUTE": 0x10, + "PAGE_EXECUTE_READ": 0x20, + "PAGE_EXECUTE_READWRITE": 0x40, + "PAGE_EXECUTE_WRITECOPY": 0x80, + "PAGE_GUARD": 0x100, + "PAGE_NOCACHE": 0x200, + "PAGE_WRITECOMBINE": 0x400, + "PAGE_TARGETS_INVALID": 0x40000000, +} + + +class VadInfo(interfaces.plugins.PluginInterface): + """Lists process memory ranges.""" + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + MAXSIZE_DEFAULT = 0 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._protect_values = None + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + # TODO: Convert this to a ListRequirement so that people can filter on sets of ranges + requirements.IntRequirement(name = 'address', + description = "Process virtual memory address to include " \ + "(all other address ranges are excluded). This must be " \ + "a base address, not an address within the desired range.", + optional = True), + requirements.ListRequirement(name = 'pid', + description = 'Filter on specific process IDs', + element_type = int, + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.BooleanRequirement(name = 'dump', + description = "Extract listed memory ranges", + default = False, + optional = True), + requirements.IntRequirement(name = 'maxsize', + description = "Maximum size for dumped VAD sections " \ + "(all the bigger sections will be ignored)", + default = cls.MAXSIZE_DEFAULT, + optional = True), + ] + + @classmethod + def protect_values(cls, context: interfaces.context.ContextInterface, layer_name: str, + symbol_table: str) -> Iterable[int]: + """Look up the array of memory protection constants from the memory + sample. These don't change often, but if they do in the future, then + finding them dynamically versus hard-coding here will ensure we parse + them properly. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + symbol_table: The name of the table containing the kernel symbols + """ + + kvo = context.layers[layer_name].config["kernel_virtual_offset"] + ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo) + addr = ntkrnlmp.get_symbol("MmProtectToValue").address + values = ntkrnlmp.object(object_type = "array", offset = addr, subtype = ntkrnlmp.get_type("int"), count = 32) + return values # type: ignore + + @classmethod + def list_vads(cls, proc: interfaces.objects.ObjectInterface, + filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \ + Generator[interfaces.objects.ObjectInterface, None, None]: + """Lists the Virtual Address Descriptors of a specific process. + + Args: + proc: _EPROCESS object from which to list the VADs + filter_func: Function to take a virtual address descriptor value and return True if it should be filtered out + + Returns: + A list of virtual address descriptors based on the process and filtered based on the filter function + """ + for vad in proc.get_vad_root().traverse(): + if not filter_func(vad): + yield vad + + @classmethod + def vad_dump(cls, + context: interfaces.context.ContextInterface, + proc: interfaces.objects.ObjectInterface, + vad: interfaces.objects.ObjectInterface, + open_method: Type[ + interfaces.plugins.FileHandlerInterface], + maxsize: int = MAXSIZE_DEFAULT) -> Optional[interfaces.plugins.FileHandlerInterface]: + """Extracts the complete data for Vad as a FileInterface. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + proc: an _EPROCESS instance + vad: The suspected VAD to extract (ObjectInterface) + open_method: class to provide context manager for opening the file + maxsize: Max size of VAD section (default MAXSIZE_DEFAULT) + + Returns: + An open FileInterface object containing the complete data for the process or None in the case of failure + """ + + try: + vad_start = vad.get_start() + vad_end = vad.get_end() + except AttributeError: + vollog.debug("Unable to find the starting/ending VPN member") + return + + if maxsize > 0 and (vad_end - vad_start) > maxsize: + vollog.debug("Skip VAD dump {0:#x}-{1:#x} due to maxsize limit".format(vad_start, vad_end)) + return + + proc_id = "Unknown" + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, + excp.layer_name)) + return None + + proc_layer = context.layers[proc_layer_name] + file_name = "pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc_id, vad_start, vad_end) + try: + file_handle = open_method(file_name) + chunk_size = 1024 * 1024 * 10 + offset = vad_start + while offset < vad_end: + to_read = min(chunk_size, vad_end - offset) + data = proc_layer.read(offset, to_read, pad = True) + if not data: + break + file_handle.write(data) + offset += to_read + + except Exception as excp: + vollog.debug("Unable to dump VAD {}: {}".format(file_name, excp)) + return + + return file_handle + + def _generator(self, procs): + + def passthrough(_: interfaces.objects.ObjectInterface) -> bool: + return False + + filter_func = passthrough + if self.config.get('address', None) is not None: + + def filter_function(x: interfaces.objects.ObjectInterface) -> bool: + return x.get_start() not in [self.config['address']] + + filter_func = filter_function + + for proc in procs: + process_name = utility.array_to_string(proc.ImageFileName) + + for vad in self.list_vads(proc, filter_func = filter_func): + + file_output = "Disabled" + if self.config['dump']: + file_handle = self.vad_dump(self.context, proc, vad, self.open) + file_output = "Error outputting file" + if file_handle: + file_handle.close() + file_output = file_handle.preferred_filename + + yield (0, (proc.UniqueProcessId, process_name, format_hints.Hex(vad.vol.offset), + format_hints.Hex(vad.get_start()), format_hints.Hex(vad.get_end()), vad.get_tag(), + vad.get_protection( + self.protect_values(self.context, self.config['primary'], self.config['nt_symbols']), + winnt_protections), vad.get_commit_charge(), vad.get_private_memory(), + format_hints.Hex(vad.get_parent()), vad.get_file_name(), file_output)) + + def run(self): + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Offset", format_hints.Hex), + ("Start VPN", format_hints.Hex), ("End VPN", format_hints.Hex), ("Tag", str), + ("Protection", str), ("CommitCharge", int), ("PrivateMemory", int), + ("Parent", format_hints.Hex), ("File", str), ("File output", str)], + self._generator( + pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadyarascan.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadyarascan.py new file mode 100644 index 00000000..799cdc4a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/vadyarascan.py @@ -0,0 +1,87 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Iterable, List, Tuple + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.plugins import yarascan +from volatility.plugins.windows import pslist + +vollog = logging.getLogger(__name__) + + +class VadYaraScan(interfaces.plugins.PluginInterface): + """Scans all the Virtual Address Descriptor memory maps using yara.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = "Memory layer for the kernel", + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.BooleanRequirement(name = "wide", + description = "Match wide (unicode) strings", + default = False, + optional = True), + requirements.StringRequirement(name = "yara_rules", + description = "Yara rules (as a string)", + optional = True), + requirements.URIRequirement(name = "yara_file", description = "Yara rules (as a file)", optional = True), + requirements.IntRequirement(name = "max_size", + default = 0x40000000, + description = "Set the maximum size (default is 1GB)", + optional = True), + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.VersionRequirement(name = 'yarascanner', component = yarascan.YaraScanner, + version = (2, 0, 0)), + requirements.ListRequirement(name = 'pid', + element_type = int, + description = "Process IDs to include (all other processes are excluded)", + optional = True) + ] + + def _generator(self): + + rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + + filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None)) + + for task in pslist.PsList.list_processes(context = self.context, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols'], + filter_func = filter_func): + layer_name = task.add_process_layer() + layer = self.context.layers[layer_name] + for offset, rule_name, name, value in layer.scan(context = self.context, + scanner = yarascan.YaraScanner(rules = rules), + sections = self.get_vad_maps(task)): + yield 0, (format_hints.Hex(offset), task.UniqueProcessId, rule_name, name, value) + + @staticmethod + def get_vad_maps(task: interfaces.objects.ObjectInterface) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses within a virtual address + descriptor tree. + + Args: + task: The EPROCESS object of which to traverse the vad tree + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ + vad_root = task.get_vad_root() + for vad in vad_root.traverse(): + end = vad.get_end() + start = vad.get_start() + yield (start, end - start) + + def run(self): + return renderers.TreeGrid([('Offset', format_hints.Hex), ('Pid', int), ('Rule', str), ('Component', str), + ('Value', bytes)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/verinfo.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/verinfo.py new file mode 100644 index 00000000..5338a317 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/verinfo.py @@ -0,0 +1,169 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import io +import logging +from typing import Generator, List, Tuple + +from volatility.framework import exceptions, renderers, constants, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.renderers import format_hints +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows.extensions import pe +from volatility.plugins.windows import pslist, modules, dlllist + +vollog = logging.getLogger(__name__) + +try: + import pefile +except ImportError: + vollog.info("Python pefile module not found, plugin (and dependent plugins) not available") + raise + + +class VerInfo(interfaces.plugins.PluginInterface): + """Lists version information from PE files.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + ## TODO: we might add a regex option on the name later, but otherwise we're good + ## TODO: and we don't want any CLI options from pslist, modules, or moddump + return [ + requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)), + requirements.PluginRequirement(name = 'modules', plugin = modules.Modules, version = (1, 0, 0)), + requirements.VersionRequirement(name = 'dlllist', component = dlllist.DllList, version = (2, 0, 0)), + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + ] + + @classmethod + def get_version_information(cls, context: interfaces.context.ContextInterface, pe_table_name: str, layer_name: str, + base_address: int) -> Tuple[int, int, int, int]: + """Get File and Product version information from PE files. + + Args: + context: volatility context on which to operate + pe_table_name: name of the PE table + layer_name: name of the layer containing the PE file + base_address: base address of the PE (where MZ is found) + """ + + if layer_name is None: + raise TypeError("Layer must be a string not None") + + pe_data = io.BytesIO() + + dos_header = context.object(pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset = base_address, + layer_name = layer_name) + + for offset, data in dos_header.reconstruct(): + pe_data.seek(offset) + pe_data.write(data) + + pe = pefile.PE(data = pe_data.getvalue(), fast_load = True) + pe.parse_data_directories([pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]]) + + if isinstance(pe.VS_FIXEDFILEINFO, list): + # pefile >= 2018.8.8 (estimated) + version_struct = pe.VS_FIXEDFILEINFO[0] + else: + # pefile <= 2017.11.5 (estimated) + version_struct = pe.VS_FIXEDFILEINFO + + major = version_struct.ProductVersionMS >> 16 + minor = version_struct.ProductVersionMS & 0xFFFF + product = version_struct.ProductVersionLS >> 16 + build = version_struct.ProductVersionLS & 0xFFFF + + pe_data.close() + + return major, minor, product, build + + def _generator(self, procs: Generator[interfaces.objects.ObjectInterface, None, None], + mods: Generator[interfaces.objects.ObjectInterface, None, None], session_layers: Generator[str, None, + None]): + """Generates a list of PE file version info for processes, dlls, and + modules. + + Args: + procs: of processes + mods: of modules + session_layers: of layers in the session to be checked + """ + + pe_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "windows", + "pe", + class_types = pe.class_types) + + for mod in mods: + try: + BaseDllName = mod.BaseDllName.get_string() + except exceptions.InvalidAddressException: + BaseDllName = renderers.UnreadableValue() + + session_layer_name = modules.Modules.find_session_layer(self.context, session_layers, mod.DllBase) + try: + (major, minor, product, build) = self.get_version_information(self._context, pe_table_name, + session_layer_name, mod.DllBase) + except (exceptions.InvalidAddressException, TypeError, AttributeError): + (major, minor, product, build) = [renderers.UnreadableValue()] * 4 + + # the pid and process are not applicable for kernel modules + yield (0, (renderers.NotApplicableValue(), renderers.NotApplicableValue(), format_hints.Hex(mod.DllBase), + BaseDllName, major, minor, product, build)) + + # now go through the process and dll lists + for proc in procs: + proc_id = "Unknown" + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug("Process {}: invalid address {} in layer {}".format(proc_id, excp.invalid_address, + excp.layer_name)) + continue + + for entry in proc.load_order_modules(): + + try: + BaseDllName = entry.BaseDllName.get_string() + except exceptions.InvalidAddressException: + BaseDllName = renderers.UnreadableValue() + + try: + DllBase = format_hints.Hex(entry.DllBase) + except exceptions.InvalidAddressException: + DllBase = renderers.UnreadableValue() + + try: + (major, minor, product, build) = self.get_version_information(self._context, pe_table_name, + proc_layer_name, entry.DllBase) + except (exceptions.InvalidAddressException, ValueError, AttributeError): + (major, minor, product, build) = [renderers.UnreadableValue()] * 4 + + yield (0, (proc.UniqueProcessId, + proc.ImageFileName.cast("string", + max_length = proc.ImageFileName.vol.count, + errors = "replace"), DllBase, BaseDllName, major, minor, product, + build)) + + def run(self): + procs = pslist.PsList.list_processes(self.context, self.config["primary"], self.config["nt_symbols"]) + + mods = modules.Modules.list_modules(self.context, self.config["primary"], self.config["nt_symbols"]) + + # populate the session layers for kernel modules + session_layers = modules.Modules.get_session_layers(self.context, self.config['primary'], + self.config['nt_symbols']) + + return renderers.TreeGrid([("PID", int), ("Process", str), ("Base", format_hints.Hex), ("Name", str), + ("Major", int), ("Minor", int), ("Product", int), ("Build", int)], + self._generator(procs, mods, session_layers)) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/windows/virtmap.py b/app/parsers/vol_Parser/volatility/framework/plugins/windows/virtmap.py new file mode 100644 index 00000000..5ce7d5d7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/windows/virtmap.py @@ -0,0 +1,122 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import List, Tuple, Dict, Generator + +from volatility.framework import interfaces, renderers, exceptions +from volatility.framework.configuration import requirements +from volatility.framework.layers import intel +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + + +class VirtMap(interfaces.plugins.PluginInterface): + """Lists virtual mapped sections.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # Since we're calling the plugin, make sure we have the plugin's requirements + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols") + ] + + def _generator(self, map): + for entry in sorted(map): + for (start, end) in map[entry]: + yield (0, (entry, format_hints.Hex(start), format_hints.Hex(end))) + + @classmethod + def determine_map(cls, module: interfaces.context.ModuleInterface) -> \ + Dict[str, List[Tuple[int, int]]]: + """Returns the virtual map from a windows kernel module.""" + layer = module.context.layers[module.layer_name] + if not isinstance(layer, intel.Intel): + raise + + result = {} # type: Dict[str, List[Tuple[int, int]]] + system_va_type = module.get_enumeration('_MI_SYSTEM_VA_TYPE') + large_page_size = (layer.page_size ** 2) // module.get_type("_MMPTE").size + + if module.has_symbol('MiVisibleState'): + symbol = module.get_symbol('MiVisibleState') + visible_state = module.object(object_type = 'pointer', + offset = symbol.address, + subtype = module.get_type('_MI_VISIBLE_STATE')).dereference() + if hasattr(visible_state, 'SystemVaRegions'): + for i in range(visible_state.SystemVaRegions.count): + lookup = system_va_type.lookup(i) + region_range = result.get(lookup, []) + region_range.append( + (visible_state.SystemVaRegions[i].BaseAddress, visible_state.SystemVaRegions[i].NumberOfBytes)) + result[lookup] = region_range + elif hasattr(visible_state, 'SystemVaType'): + system_range_start = module.object(object_type = "pointer", + offset = module.get_symbol("MmSystemRangeStart").address) + result = cls._enumerate_system_va_type(large_page_size, system_range_start, module, + visible_state.SystemVaType) + else: + raise exceptions.SymbolError(None, module.name, "Required structures not found") + elif module.has_symbol('MiSystemVaType'): + system_range_start = module.object(object_type = "pointer", + offset = module.get_symbol("MmSystemRangeStart").address) + symbol = module.get_symbol('MiSystemVaType') + array_count = (0xFFFFFFFF + 1 - system_range_start) // large_page_size + type_array = module.object(object_type = 'array', + offset = symbol.address, + count = array_count, + subtype = module.get_type('char')) + + result = cls._enumerate_system_va_type(large_page_size, system_range_start, module, type_array) + else: + raise exceptions.SymbolError(None, module.name, "Required structures not found") + + return result + + @classmethod + def _enumerate_system_va_type(cls, large_page_size: int, system_range_start: int, + module: interfaces.context.ModuleInterface, + type_array: interfaces.objects.ObjectInterface) -> Dict[str, List[Tuple[int, int]]]: + result = {} # type: Dict[str, List[Tuple[int, int]]] + system_va_type = module.get_enumeration('_MI_SYSTEM_VA_TYPE') + start = system_range_start + prev_entry = -1 + cur_size = large_page_size + for entry in type_array: + entry = system_va_type.lookup(entry) + if entry != prev_entry: + region_range = result.get(entry, []) + region_range.append((start, cur_size)) + result[entry] = region_range + start = start + cur_size + cur_size = large_page_size + else: + cur_size += large_page_size + prev_entry = entry + + return result + + @classmethod + def scannable_sections(cls, module: interfaces.context.ModuleInterface) -> Generator[Tuple[int, int], None, None]: + mapping = cls.determine_map(module) + for entry in mapping: + if 'Unused' not in entry: + for value in mapping[entry]: + yield value + + def run(self): + layer = self.context.layers[self.config['primary']] + module = self.context.module(self.config['nt_symbols'], + layer_name = layer.name, + offset = layer.config['kernel_virtual_offset']) + + return renderers.TreeGrid([("Region", str), ("Start offset", format_hints.Hex), + ("End offset", format_hints.Hex)], + self._generator(self.determine_map(module = module))) diff --git a/app/parsers/vol_Parser/volatility/framework/plugins/yarascan.py b/app/parsers/vol_Parser/volatility/framework/plugins/yarascan.py new file mode 100644 index 00000000..4e3abee2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/plugins/yarascan.py @@ -0,0 +1,94 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Iterable, Tuple, List, Dict, Any + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.layers import resources +from volatility.framework.renderers import format_hints + +vollog = logging.getLogger(__name__) + +try: + import yara +except ImportError: + vollog.info("Python Yara module not found, plugin (and dependent plugins) not available") + raise + + +class YaraScanner(interfaces.layers.ScannerInterface): + _version = (2, 0, 0) + + # yara.Rules isn't exposed, so we can't type this properly + def __init__(self, rules) -> None: + super().__init__() + self._rules = rules + + def __call__(self, data: bytes, data_offset: int) -> Iterable[Tuple[int, str, str, bytes]]: + for match in self._rules.match(data = data): + for offset, name, value in match.strings: + yield (offset + data_offset, match.rule, name, value) + + +class YaraScan(plugins.PluginInterface): + """Scans kernel memory using yara rules (string or file).""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = "Memory layer for the kernel", + architectures = ["Intel32", "Intel64"]), + requirements.BooleanRequirement(name = "insensitive", + description = "Makes the search case insensitive", + default = False, + optional = True), + requirements.BooleanRequirement(name = "wide", + description = "Match wide (unicode) strings", + default = False, + optional = True), + requirements.StringRequirement(name = "yara_rules", + description = "Yara rules (as a string)", + optional = True), + requirements.URIRequirement(name = "yara_file", description = "Yara rules (as a file)", optional = True), + requirements.IntRequirement(name = "max_size", + default = 0x40000000, + description = "Set the maximum size (default is 1GB)", + optional = True) + ] + + @classmethod + def process_yara_options(cls, config: Dict[str, Any]): + rules = None + if config.get('yara_rules', None) is not None: + rule = config['yara_rules'] + if rule[0] not in ["{", "/"]: + rule = '"{}"'.format(rule) + if config.get('case', False): + rule += " nocase" + if config.get('wide', False): + rule += " wide ascii" + rules = yara.compile(sources = {'n': 'rule r1 {{strings: $a = {} condition: $a}}'.format(rule)}) + elif config.get('yara_file', None) is not None: + rules = yara.compile(file = resources.ResourceAccessor().open(config['yara_file'], "rb")) + else: + vollog.error("No yara rules, nor yara rules file were specified") + return rules + + def _generator(self): + rules = self.process_yara_options(dict(self.config)) + + layer = self.context.layers[self.config['primary']] + for offset, rule_name, name, value in layer.scan(context = self.context, scanner = YaraScanner(rules = rules)): + yield 0, (format_hints.Hex(offset), rule_name, name, value) + + def run(self): + return renderers.TreeGrid([('Offset', format_hints.Hex), ('Rule', str), ('Component', str), ('Value', bytes)], + self._generator()) diff --git a/app/parsers/vol_Parser/volatility/framework/renderers/__init__.py b/app/parsers/vol_Parser/volatility/framework/renderers/__init__.py new file mode 100644 index 00000000..59517ba2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/renderers/__init__.py @@ -0,0 +1,383 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Renderers. + +Renderers display the unified output format in some manner (be it text +or file or graphical output +""" +import collections +import datetime +import logging +from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar, Union + +from volatility.framework import interfaces +from volatility.framework.interfaces import renderers + +vollog = logging.getLogger(__name__) + + +class UnreadableValue(interfaces.renderers.BaseAbsentValue): + """Class that represents values which are empty because the data cannot be + read.""" + + +class UnparsableValue(interfaces.renderers.BaseAbsentValue): + """Class that represents values which are empty because the data cannot be + interpreted correctly.""" + + +class NotApplicableValue(interfaces.renderers.BaseAbsentValue): + """Class that represents values which are empty because they don't make + sense for this node.""" + + +class NotAvailableValue(interfaces.renderers.BaseAbsentValue): + """Class that represents values which cannot be provided now (but might in + a future run) + + This might occur when information packed with volatility (such as + symbol information) is not available, but a future version or a + different run may later have that information available (ie, it + could be applicable, but we can't get it and it's not because it's + unreadable or unparsable). Unreadable and Unparsable should be used + in preference, and only if neither fits should this be used. + """ + + +class TreeNode(interfaces.renderers.TreeNode): + """Class representing a particular node in a tree grid.""" + + def __init__(self, path: str, treegrid: 'TreeGrid', parent: Optional['TreeNode'], + values: List[interfaces.renderers.BaseTypes]) -> None: + if not isinstance(treegrid, TreeGrid): + raise TypeError("Treegrid must be an instance of TreeGrid") + self._treegrid = treegrid + self._parent = parent + self._path = path + self._validate_values(values) + self._values = treegrid.RowStructure(*values) # type: ignore + + def __repr__(self) -> str: + return "".format(self.path, self._values) + + def __getitem__(self, item: Union[int, slice]) -> Any: + return self._treegrid.children(self).__getitem__(item) + + def __len__(self) -> int: + return len(self._treegrid.children(self)) + + def _validate_values(self, values: List[interfaces.renderers.BaseTypes]) -> None: + """A function for raising exceptions if a given set of values is + invalid according to the column properties.""" + if not (isinstance(values, collections.abc.Sequence) and len(values) == len(self._treegrid.columns)): + raise TypeError( + "Values must be a list of objects made up of simple types and number the same as the columns") + for index in range(len(self._treegrid.columns)): + column = self._treegrid.columns[index] + val = values[index] + if not isinstance(val, (column.type, interfaces.renderers.BaseAbsentValue)): + raise TypeError( + "Values item with index {} is the wrong type for column {} (got {} but expected {})".format( + index, column.name, type(val), column.type)) + # TODO: Consider how to deal with timezone naive/aware datetimes (and alert plugin uses to be precise) + # if isinstance(val, datetime.datetime): + # tznaive = val.tzinfo is None or val.tzinfo.utcoffset(val) is None + + @property + def values(self) -> List[interfaces.renderers.BaseTypes]: + """Returns the list of values from the particular node, based on column + index.""" + return self._values + + @property + def path(self) -> str: + """Returns a path identifying string. + + This should be seen as opaque by external classes, Parsing of + path locations based on this string are not guaranteed to remain + stable. + """ + return self._path + + @property + def parent(self) -> Optional['TreeNode']: + """Returns the parent node of this node or None.""" + return self._parent + + @property + def path_depth(self) -> int: + """Return the path depth of the current node.""" + return len(self.path.split(TreeGrid.path_sep)) + + def path_changed(self, path: str, added: bool = False) -> None: + """Updates the path based on the addition or removal of a node higher + up in the tree. + + This should only be called by the containing TreeGrid and + expects to only be called for affected nodes. + """ + components = self._path.split(TreeGrid.path_sep) + changed = path.split(TreeGrid.path_sep) + changed_index = len(changed) - 1 + if int(components[changed_index]) >= int(changed[-1]): + components[changed_index] = str(int(components[changed_index]) + (1 if added else -1)) + self._path = TreeGrid.path_sep.join(components) + + +def RowStructureConstructor(names: List[str]): + return collections.namedtuple("RowStructure", [TreeGrid.sanitize_name(name) for name in names]) + + +class TreeGrid(interfaces.renderers.TreeGrid): + """Class providing the interface for a TreeGrid (which contains TreeNodes) + + The structure of a TreeGrid is designed to maintain the structure of the tree in a single object. + For this reason each TreeNode does not hold its children, they are managed by the top level object. + This leaves the Nodes as simple data carries and prevents them being used to manipulate the tree as a whole. + This is a data structure, and is not expected to be modified much once created. + + Carrying the children under the parent makes recursion easier, but then every node is its own little tree + and must have all the supporting tree functions. It also allows for a node to be present in several different trees, + and to create cycles. + """ + + path_sep = "|" + + def __init__(self, columns: List[Tuple[str, interfaces.renderers.BaseTypes]], + generator: Optional[Iterable[Tuple[int, Tuple]]]) -> None: + """Constructs a TreeGrid object using a specific set of columns. + + The TreeGrid itself is a root element, that can have children but no values. + The TreeGrid does *not* contain any information about formatting, + these are up to the renderers and plugins. + + Args: + columns: A list of column tuples made up of (name, type). + generator: An iterable containing row for a tree grid, each row contains a indent level followed by the values for each column in order. + """ + self._populated = False + self._row_count = 0 + self._children = [] # type: List[TreeNode] + converted_columns = [] # type: List[interfaces.renderers.Column] + if len(columns) < 1: + raise ValueError("Columns must be a list containing at least one column") + for (name, column_type) in columns: + is_simple_type = issubclass(column_type, self.base_types) + if not is_simple_type: + raise TypeError("Column {}'s type is not a simple type: {}".format(name, + column_type.__class__.__name__)) + converted_columns.append(interfaces.renderers.Column(name, column_type)) + self.RowStructure = RowStructureConstructor([column.name for column in converted_columns]) + self._columns = converted_columns + if generator is None: + generator = [] + generator = iter(generator) + + self._generator = generator + + @staticmethod + def sanitize_name(text: str) -> str: + output = "" + for letter in text.lower(): + if letter != ' ': + output += (letter if letter in 'abcdefghiljklmnopqrstuvwxyz_0123456789' else '_') + return output + + def populate(self, + function: interfaces.renderers.VisitorSignature = None, + initial_accumulator: Any = None, + fail_on_errors: bool = True) -> Optional[Exception]: + """Populates the tree by consuming the TreeGrid's construction + generator Func is called on every node, so can be used to create output + on demand. + + This is equivalent to a one-time visit. + + Args: + function: The visitor to be called on each row of the treegrid + initial_accumulator: The initial value for an accumulator passed to the visitor to allow it to maintain state + fail_on_errors: A boolean defining whether exceptions should be caught or bubble up + """ + accumulator = initial_accumulator + if function is None: + + def function(_x: interfaces.renderers.TreeNode, _y: Any) -> Any: + return None + + if not self.populated: + try: + prev_nodes = [] # type: List[TreeNode] + for (level, item) in self._generator: + parent_index = min(len(prev_nodes), level) + parent = prev_nodes[parent_index - 1] if parent_index > 0 else None + treenode = self._append(parent, item) + prev_nodes = prev_nodes[0:parent_index] + [treenode] + if function is not None: + accumulator = function(treenode, accumulator) + self._row_count += 1 + except Exception as excp: + if fail_on_errors: + raise + vollog.debug("Exception during population: {}".format(excp)) + self._populated = True + return excp + self._populated = True + return None + + @property + def populated(self): + """Indicates that population has completed and the tree may now be + manipulated separately.""" + return self._populated + + @property + def columns(self) -> List[interfaces.renderers.Column]: + """Returns the available columns and their ordering and types.""" + return self._columns + + @property + def row_count(self) -> int: + """Returns the number of rows populated.""" + return self._row_count + + def children(self, node) -> List[interfaces.renderers.TreeNode]: + """Returns the subnodes of a particular node in order.""" + return [node for node, _ in self._find_children(node)] + + def _find_children(self, node): + """Returns the children list associated with a particular node. + + Returns None if the node does not exist + """ + children = self._children + try: + if node is not None: + for path_component in node.path.split(self.path_sep): + _, children = children[int(path_component)] + except IndexError: + return [] + return children + + def values(self, node): + """Returns the values for a particular node. + + The values returned are mutable, + """ + if node is None: + raise TypeError("Node must be a valid node within the TreeGrid") + return node.values + + def _append(self, parent, values): + """Adds a new node at the top level if parent is None, or under the + parent node otherwise, after all other children.""" + children = self.children(parent) + return self._insert(parent, len(children), values) + + def _insert(self, parent, position, values): + """Inserts an element into the tree at a specific position.""" + parent_path = "" + children = self._find_children(parent) + if parent is not None: + parent_path = parent.path + self.path_sep + newpath = parent_path + str(position) + tree_item = TreeNode(newpath, self, parent, values) + for node, _ in children[position:]: + self.visit(node, lambda child, _: child.path_changed(newpath, True), None) + children.insert(position, (tree_item, [])) + return tree_item + + def is_ancestor(self, node, descendant): + """Returns true if descendent is a child, grandchild, etc of node.""" + return descendant.path.startswith(node.path) + + def max_depth(self): + """Returns the maximum depth of the tree.""" + return self.visit(None, lambda n, a: max(a, self.path_depth(n)), 0) + + _T = TypeVar("_T") + + def visit(self, + node: Optional[interfaces.renderers.TreeNode], + function: Callable[[interfaces.renderers.TreeNode, _T], _T], + initial_accumulator: _T, + sort_key: Optional[interfaces.renderers.ColumnSortKey] = None): + """Visits all the nodes in a tree, calling function on each one. + + function should have the signature function(node, accumulator) and return new_accumulator + If accumulators are not needed, the function must still accept a second parameter. + + The order of that the nodes are visited is always depth first, however, the order children are traversed can + be set based on a sort_key function which should accept a node's values and return something that can be + sorted to receive the desired order (similar to the sort/sorted key). + + We use the private _find_children function so that we don't have to re-traverse the tree + for every node we descend further down + """ + if not self.populated: + self.populate() + + # Find_nodes is path dependent, whereas _visit is not + # So in case the function modifies the node's path, find the nodes first + children = self._find_children(node) + accumulator = initial_accumulator + # We split visit into two, so that we don't have to keep calling find_children to traverse the tree + if node is not None: + accumulator = function(node, initial_accumulator) + if children is not None: + if sort_key is not None: + sort_key_not_none = sort_key # Only necessary because of mypy + children = sorted(children, key = lambda x: sort_key_not_none(x[0].values)) + if not sort_key.ascending: + children = reversed(children) + accumulator = self._visit(children, function, accumulator, sort_key) + return accumulator + + def _visit(self, + list_of_children: List['TreeNode'], + function: Callable, + accumulator: _T, + sort_key: Optional[interfaces.renderers.ColumnSortKey] = None) -> _T: + """Visits all the nodes in a tree, calling function on each one.""" + if list_of_children is not None: + for n, children in list_of_children: + accumulator = function(n, accumulator) + if sort_key is not None: + sort_key_not_none = sort_key # Only necessary because of mypy + children = sorted(children, key = lambda x: sort_key_not_none(x[0].values)) + if not sort_key.ascending: + children = reversed(children) + accumulator = self._visit(children, function, accumulator, sort_key) + return accumulator + + +class ColumnSortKey(interfaces.renderers.ColumnSortKey): + + def __init__(self, treegrid: TreeGrid, column_name: str, ascending: bool = True) -> None: + _index = None + self._type = None + self.ascending = ascending + for i in range(len(treegrid.columns)): + column = treegrid.columns[i] + if column.name.lower() == column_name.lower(): + _index = i + self._type = column.type + if _index is None: + raise ValueError("Column not found in TreeGrid columns: {}".format(column_name)) + self._index = _index + + def __call__(self, values: List[Any]) -> Any: + """The key function passed as the sort key.""" + value = values[self._index] + if isinstance(value, interfaces.renderers.BaseAbsentValue): + if self._type == datetime.datetime: + value = datetime.datetime.min + elif self._type in [int, float]: + value = -1 + elif self._type == bool: + value = False + elif self._type in [str, renderers.Disassembly]: + value = "-" + elif self._type == bytes: + value = b"" + return value diff --git a/app/parsers/vol_Parser/volatility/framework/renderers/conversion.py b/app/parsers/vol_Parser/volatility/framework/renderers/conversion.py new file mode 100644 index 00000000..c570c124 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/renderers/conversion.py @@ -0,0 +1,111 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import datetime +import ipaddress +import socket +import struct +from typing import Union + +from volatility.framework import interfaces, renderers + + +def wintime_to_datetime(wintime: int) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]: + unix_time = wintime // 10000000 + if unix_time == 0: + return renderers.NotApplicableValue() + unix_time = unix_time - 11644473600 + try: + return datetime.datetime.utcfromtimestamp(unix_time) + # Windows sometimes throws OSErrors rather than ValueErrors when it can't convert a value + except (ValueError, OSError): + return renderers.UnparsableValue() + + +def unixtime_to_datetime(unixtime: int) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]: + ret = renderers.UnparsableValue() # type: Union[interfaces.renderers.BaseAbsentValue, datetime.datetime] + + if unixtime > 0: + try: + ret = datetime.datetime.utcfromtimestamp(unixtime) + except ValueError: + pass + + return ret + + +def round(addr: int, align: int, up: bool = False) -> int: + """Round an address up or down based on an alignment. + + Args: + addr: the address + align: the alignment value + up: Whether to round up or not + + Returns: + The aligned address + """ + + if addr % align == 0: + return addr + else: + if up: + return (addr + (align - (addr % align))) + return (addr - (addr % align)) + + +# For vol3 devs: +# +# convert_ipv4 && convert_ipv6 are slightly modified versions of their +# counterparts from vol2: +# +# https://github.com/volatilityfoundation/volatility/blob/master/volatility/utils.py#L84 +# +# Furthermore, vol2 used as overlay for ip addresses that made the conversion string based: +# +# https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/overlays/basic.py#L156 +# +# by using struct.pack with the given format string on data that was then gathered through .v(): +# +# https://github.com/volatilityfoundation/volatility/blob/aa6b960c1077e447bda9d64df507ec02f8fcc958/volatility/obj.py#L439 +# +# .v() for IP addresses would do obj_vm.read(), which returned a string, and struct.pack was called on it. +# +# This doesn't translate very well to vol3, since vol3 does have overlays so the plugins instead are retrieving the raw integers +# from memory. That is why convert_ip4 takes a 32 bit integer as its input and convert_ipv6 takes an array of shorts. +# This code has only been tested on Mac so far, but since the modified functions cleanly replace evaluation of data that used to +# be done by the overlays that plugins for every OS used, then I don't expect issues when vol3 linux and windows plugins use them + + +def convert_ipv4(ip_as_integer): + return str(ipaddress.IPv4Address(struct.pack("> 8) | ((port_as_integer & 0xff) << 8) + + +def convert_network_four_tuple(family, four_tuple): + """Converts the connection four_tuple: (source ip, source port, dest ip, + dest port) + + into their string equivalents. IP addresses are expected as a tuple + of unsigned shorts Ports are converted to proper endianess as well + """ + + if family == socket.AF_INET: + ret = (convert_ipv4(four_tuple[0]), convert_port(four_tuple[1]), convert_ipv4(four_tuple[2]), + convert_port(four_tuple[3])) + elif family == socket.AF_INET6: + ret = (convert_ipv6(four_tuple[0]), convert_port(four_tuple[1]), convert_ipv6(four_tuple[2]), + convert_port(four_tuple[3])) + else: + ret = None + + return ret diff --git a/app/parsers/vol_Parser/volatility/framework/renderers/format_hints.py b/app/parsers/vol_Parser/volatility/framework/renderers/format_hints.py new file mode 100644 index 00000000..91fe693f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/renderers/format_hints.py @@ -0,0 +1,50 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""The official list of format hints that text renderers and plugins can rely +upon existing within the framework. + +These hints allow a plugin to indicate how they would like data from a particular column to be represented. + +Text renderers should attempt to honour all hints provided in this module where possible +""" + + +class Bin(int): + """A class to indicate that the integer value should be represented as a + binary value.""" + + +class Hex(int): + """A class to indicate that the integer value should be represented as a + hexidecimal value.""" + + +class HexBytes(bytes): + """A class to indicate that the bytes should be display in an extended + format showing hexadecimal and ascii printable display.""" + + +class MultiTypeData(bytes): + """The contents are supposed to be a string, but may contain binary data.""" + + def __new__(cls, original, encoding: str = 'utf-16-le', split_nulls: bool = False, show_hex: bool = False): + if isinstance(original, int): + original = str(original).encode(encoding) + return super().__new__(cls, original) + + def __init__(self, original: bytes, encoding: str = 'utf-16-le', split_nulls: bool = False, show_hex: bool = False): + self.converted_int = False # type: bool + if isinstance(original, int): + self.converted_int = True + self.encoding = encoding + self.split_nulls = split_nulls + self.show_hex = show_hex + bytes.__init__(original) + + def __eq__(self, other): + return super(self) == super(other) and \ + self.converted_int == other.converted_int and \ + self.encoding == other.encoding and \ + self.split_nulls == other.split_nulls and \ + self.show_hex == other.show_hex diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/__init__.py new file mode 100644 index 00000000..6cbf4119 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/__init__.py @@ -0,0 +1,261 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import collections +import collections.abc +import enum +import logging +from typing import Any, Dict, Iterable, Iterator, TypeVar, List + +from volatility.framework import constants, exceptions, interfaces, objects + +vollog = logging.getLogger(__name__) + +SymbolSpaceReturnType = TypeVar("SymbolSpaceReturnType", interfaces.objects.Template, + interfaces.symbols.SymbolInterface, Dict[str, Any]) + + +class SymbolType(enum.Enum): + TYPE = 1 + SYMBOL = 2 + ENUM = 3 + + +class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): + """Handles an ordered collection of SymbolTables. + + This collection is ordered so that resolution of symbols can proceed + down through the ranks if a namespace isn't specified. + """ + + def __init__(self) -> None: + super().__init__() + self._dict = collections.OrderedDict() # type: Dict[str, interfaces.symbols.BaseSymbolTableInterface] + # Permanently cache all resolved symbols + self._resolved = {} # type: Dict[str, interfaces.objects.Template] + self._resolved_symbols = {} # type: Dict[str, interfaces.objects.Template] + + def clear_symbol_cache(self, table_name: str = None) -> None: + """Clears the symbol cache for the specified table name. If no table + name is specified, the caches of all symbol tables are cleared.""" + table_list = list() # type: List[interfaces.symbols.BaseSymbolTableInterface] + if table_name is None: + table_list = list(self._dict.values()) + else: + table_list.append(self._dict[table_name]) + for table in table_list: + table.clear_symbol_cache() + + def free_table_name(self, prefix: str = "layer") -> str: + """Returns an unused table name to ensure no collision occurs when + inserting a symbol table.""" + count = 1 + while prefix + str(count) in self: + count += 1 + return prefix + str(count) + + ### Symbol functions + + def get_symbols_by_type(self, type_name: str) -> Iterable[str]: + """Returns all symbols based on the type of the symbol.""" + for table in self._dict: + for symbol_name in self._dict[table].get_symbols_by_type(type_name): + yield table + constants.BANG + symbol_name + + def get_symbols_by_location(self, offset: int, size: int = 0, table_name: str = None) -> Iterable[str]: + """Returns all symbols that exist at a specific relative address.""" + table_list = self._dict.values() # type: Iterable[interfaces.symbols.BaseSymbolTableInterface] + if table_name is not None: + if table_name in self._dict: + table_list = [self._dict[table_name]] + else: + table_list = [] + for table in table_list: + for symbol_name in table.get_symbols_by_location(offset = offset, size = size): + yield table.name + constants.BANG + symbol_name + + ### Space functions + + def __len__(self) -> int: + """Returns the number of tables within the space.""" + return len(self._dict) + + def __getitem__(self, i: str) -> Any: + """Returns a specific table from the space.""" + return self._dict[i] + + def __iter__(self) -> Iterator[str]: + """Iterates through all available tables in the symbol space.""" + return iter(self._dict) + + def append(self, value: interfaces.symbols.BaseSymbolTableInterface) -> None: + """Adds a symbol_list to the end of the space.""" + if not isinstance(value, interfaces.symbols.BaseSymbolTableInterface): + raise TypeError(value) + if value.name in self._dict: + self.remove(value.name) + self._dict[value.name] = value + + def remove(self, key: str) -> None: + """Removes a named symbol_list from the space.""" + # Reset the resolved list, since we're removing some symbols + self._resolved = {} + del self._dict[key] + + ### Resolution functions + + class UnresolvedTemplate(objects.templates.ReferenceTemplate): + """Class to highlight when missing symbols are present. + + This class is identical to a reference template, but differentiable by its classname. + It will output a debug log to indicate when it has been instantiated and with what name. + + This class is designed to be output ONLY as part of the SymbolSpace resolution system. + Individual SymbolTables that cannot resolve a symbol should still return a SymbolError to + indicate this failure in resolution. + """ + + def __init__(self, type_name: str, **kwargs) -> None: + vollog.debug("Unresolved reference: {}".format(type_name)) + super().__init__(type_name = type_name, **kwargs) + + def _weak_resolve(self, resolve_type: SymbolType, name: str) -> SymbolSpaceReturnType: + """Takes a symbol name and resolves it with ReferentialTemplates.""" + if resolve_type == SymbolType.TYPE: + get_function = 'get_type' + elif resolve_type == SymbolType.SYMBOL: + get_function = 'get_symbol' + elif resolve_type == SymbolType.ENUM: + get_function = 'get_enumeration' + else: + raise TypeError("Weak_resolve called without a proper SymbolType") + + name_array = name.split(constants.BANG) + if len(name_array) == 2: + table_name = name_array[0] + component_name = name_array[1] + try: + return getattr(self._dict[table_name], get_function)(component_name) + except KeyError as e: + raise exceptions.SymbolError(component_name, table_name, + 'Type {} references missing Type/Symbol/Enum: {}'.format(name, e)) + raise exceptions.SymbolError(name, None, "Malformed name: {}".format(name)) + + def _iterative_resolve(self, traverse_list): + """Iteratively resolves a type, populating linked child + ReferenceTemplates with their properly resolved counterparts.""" + replacements = set() + # Whole Symbols that still need traversing + while traverse_list: + template_traverse_list, traverse_list = [self._resolved[traverse_list[0]]], traverse_list[1:] + # Traverse a single symbol looking for any ReferenceTemplate objects + while template_traverse_list: + traverser, template_traverse_list = template_traverse_list[0], template_traverse_list[1:] + for child in traverser.children: + if isinstance(child, objects.templates.ReferenceTemplate): + # If we haven't seen it before, subresolve it and also add it + # to the "symbols that still need traversing" list + if child.vol.type_name not in self._resolved: + traverse_list.append(child.vol.type_name) + try: + self._resolved[child.vol.type_name] = self._weak_resolve( + SymbolType.TYPE, child.vol.type_name) + except exceptions.SymbolError: + self._resolved[child.vol.type_name] = self.UnresolvedTemplate(child.vol.type_name) + # Stash the replacement + replacements.add((traverser, child)) + elif child.children: + template_traverse_list.append(child) + for (parent, child) in replacements: + parent.replace_child(child, self._resolved[child.vol.type_name]) + + def get_type(self, type_name: str) -> interfaces.objects.Template: + """Takes a symbol name and resolves it. + + This method ensures that all referenced templates (including + self-referential templates) are satisfied as ObjectTemplates + """ + # Traverse down any resolutions + if type_name not in self._resolved: + self._resolved[type_name] = self._weak_resolve(SymbolType.TYPE, type_name) # type: ignore + self._iterative_resolve([type_name]) + if isinstance(self._resolved[type_name], objects.templates.ReferenceTemplate): + table_name = None + index = type_name.find(constants.BANG) + if index > 0: + table_name, type_name = type_name[:index], type_name[index + 1:] + raise exceptions.SymbolError(type_name, table_name, "Unresolvable symbol requested: {}".format(type_name)) + return self._resolved[type_name] + + def get_symbol(self, symbol_name: str) -> interfaces.symbols.SymbolInterface: + """Look-up a symbol name across all the contained symbol spaces.""" + retval = self._weak_resolve(SymbolType.SYMBOL, symbol_name) + if symbol_name not in self._resolved_symbols and retval.type is not None: + self._resolved_symbols[symbol_name] = self._subresolve(retval.type) + if not isinstance(retval, interfaces.symbols.SymbolInterface): + table_name = None + index = symbol_name.find(constants.BANG) + if index > 0: + table_name, symbol_name = symbol_name[:index], symbol_name[index + 1:] + raise exceptions.SymbolError(symbol_name, table_name, "Unresolvable Symbol: {}".format(symbol_name)) + return retval + + def _subresolve(self, object_template: interfaces.objects.Template) -> interfaces.objects.Template: + """Ensure an ObjectTemplate doesn't contain any ReferenceTemplates""" + for child in object_template.children: + if isinstance(child, objects.templates.ReferenceTemplate): + new_child = self.get_type(child.vol.type_name) + else: + new_child = self._subresolve(child) + object_template.replace_child(old_child = child, new_child = new_child) + return object_template + + def get_enumeration(self, enum_name: str) -> interfaces.objects.Template: + """Look-up a set of enumeration choices from a specific symbol + table.""" + retval = self._weak_resolve(SymbolType.ENUM, enum_name) + if not isinstance(retval, interfaces.objects.Template): + table_name = None + index = enum_name.find(constants.BANG) + if index > 0: + table_name, enum_name = enum_name[:index], enum_name[index + 1:] + raise exceptions.SymbolError(enum_name, table_name, "Unresolvable Enumeration: {}".format(enum_name)) + return retval + + def _membership(self, member_type: SymbolType, name: str) -> bool: + """Test for membership of a component within a table.""" + + name_array = name.split(constants.BANG) + if len(name_array) == 2: + table_name = name_array[0] + component_name = name_array[1] + else: + return False + + if table_name not in self: + return False + table = self[table_name] + + if member_type == SymbolType.TYPE: + return component_name in table.types + elif member_type == SymbolType.SYMBOL: + return component_name in table.symbols + elif member_type == SymbolType.ENUM: + return component_name in table.enumerations + return False + + def has_type(self, name: str) -> bool: + return self._membership(SymbolType.TYPE, name) + + def has_symbol(self, name: str) -> bool: + return self._membership(SymbolType.SYMBOL, name) + + def has_enumeration(self, name: str) -> bool: + return self._membership(SymbolType.ENUM, name) + + +def symbol_table_is_64bit(context: interfaces.context.ContextInterface, symbol_table_name: str) -> bool: + """Returns a boolean as to whether a particular symbol table within a + context is 64-bit or not.""" + return context.symbol_space.get_type(symbol_table_name + constants.BANG + "pointer").size == 8 diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/generic/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/generic/__init__.py new file mode 100644 index 00000000..be0bfb59 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/generic/__init__.py @@ -0,0 +1,49 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import random +import string +from typing import Union + +from volatility.framework import objects, interfaces + + +class GenericIntelProcess(objects.StructType): + + def _add_process_layer(self, + context: interfaces.context.ContextInterface, + dtb: Union[int, interfaces.objects.ObjectInterface], + config_prefix: str = None, + preferred_name: str = None) -> str: + """Constructs a new layer based on the process's DirectoryTableBase.""" + + if config_prefix is None: + # TODO: Ensure collisions can't happen by verifying the config_prefix is empty + random_prefix = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) + for _ in range(8)) + config_prefix = interfaces.configuration.path_join("temporary", "_" + random_prefix) + + # Figure out a suitable name we can use for the new layer + if preferred_name is None: + preferred_name = context.layers.free_layer_name(prefix = self.vol.layer_name + "_Process") + else: + if preferred_name in context.layers: + preferred_name = context.layers.free_layer_name(prefix = preferred_name) + + # Copy the parent's config and then make suitable changes + parent_layer = context.layers[self.vol.layer_name] + parent_config = parent_layer.build_configuration() + # It's an intel layer, because we hardwire the "memory_layer" config option + # FIXME: this could be for other architectures if we don't hardwire this/these values + parent_config['memory_layer'] = parent_layer.config['memory_layer'] + parent_config['page_map_offset'] = dtb + + # Set the new configuration and construct the layer + config_path = interfaces.configuration.path_join(config_prefix, preferred_name) + context.config.splice(config_path, parent_config) + new_layer = parent_layer.__class__(context, config_path = config_path, name = preferred_name) + + # Add the constructed layer and return the name + context.layers.add_layer(new_layer) + return preferred_name diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/generic/qemu.json b/app/parsers/vol_Parser/volatility/framework/symbols/generic/qemu.json new file mode 100644 index 00000000..58faa6ee --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/generic/qemu.json @@ -0,0 +1,107 @@ +{ + "symbols": { + }, + "user_types": { + "qevm_header": { + "fields": { + "magic": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "version": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "qevm_htab": { + "fields": { + "index": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "n_valid": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "n_invalid": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 8 + } + }, + "enums": { + }, + "base_types": { + "unsigned char": { + "endian": "big", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "big", + "kind": "int", + "signed": false, + "size": 2 + }, + "long": { + "endian": "big", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "big", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "big", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "big", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "big", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "ikelos-by-hand", + "datetime": "2020-01-07T22:59:47" + }, + "format": "6.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/intermed.py b/app/parsers/vol_Parser/volatility/framework/symbols/intermed.py new file mode 100644 index 00000000..951659e0 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/intermed.py @@ -0,0 +1,687 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import base64 +import codecs +import copy +import json +import logging +import os +import pathlib +import zipfile +from abc import ABCMeta +from typing import Any, Dict, Generator, Iterable, List, Optional, Type, Tuple, Mapping + +import volatility.framework.layers.resources +from volatility import schemas, symbols +from volatility.framework import class_subclasses, constants, exceptions, interfaces, objects +from volatility.framework.configuration import requirements +from volatility.framework.symbols import native, metadata + +vollog = logging.getLogger(__name__) + + +# ## TODO +# +# All symbol tables should take a label to an object template +# +# Templates for subtypes etc should be looked up recursively just like anything else +# We therefore need a way to unroll rolled-up types +# Generate mangled names on the fly (prohibits external calling) +# +# Symbol list could be a dict with knowledge of its parent? +# Class split is arbitrary, it's an extension for developers +# Object template should contain both class and initial parameters +# +# +# *** Resolution should not happen in the resolve function +# It should only happen on access of contained types *** +# +# Recursive objects can be fixed by having caching the objects +# (however, they have to be built first!) +# +# Single hop resolution is probably the solution +# Could probably deal with it by having a property that caches +# for container types +# + + +def _construct_delegate_function(name: str, is_property: bool = False) -> Any: + def _delegate_function(self, *args, **kwargs): + if is_property: + return getattr(self._delegate, name) + return getattr(self._delegate, name)(*args, **kwargs) + + if is_property: + return property(_delegate_function) + return _delegate_function + + +class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): + """The IntermediateSymbolTable class reads a JSON file and conducts common + tasks such as validation, construction by looking up a JSON file from the + available files and ensuring the appropriate version of the schema and + proxy are chosen. + + The JSON format itself is made up of various groups (symbols, user_types, base_types, enums and metadata) + * Symbols link a name to a particular offset relative to the start of a section of memory + * Base types define the simplest primitive data types, these can make more complex structure + * User types define the more complex types by specifying members at a relative offset from the start of the type + * Enums can specify a list of names and values and a type inside which the numeric encoding will fit + * Metadata defines information about the originating file + + These are documented in JSONSchema JSON files located in volatility/schemas. + """ + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + isf_url: str, + native_types: interfaces.symbols.NativeTableInterface = None, + table_mapping: Optional[Dict[str, str]] = None, + validate: bool = True, + class_types: Optional[Mapping[str, Type[interfaces.objects.ObjectInterface]]] = None, + symbol_shift: int = 0, + symbol_mask: int = 0) -> None: + """Instantiates a SymbolTable based on an IntermediateSymbolFormat JSON file. This is validated against the + appropriate schema. The validation can be disabled by passing validate = False, but this should almost never be + done. + + Args: + context: The volatility context for the symbol table + config_path: The configuration path for the symbol table + name: The name for the symbol table (this is used in symbols e.g. table!symbol ) + isf_url: The URL pointing to the ISF file location + native_types: The NativeSymbolTable that contains the native types for this symbol table + table_mapping: A dictionary linking names referenced in the file with symbol tables in the context + validate: Determines whether the ISF file will be validated against the appropriate schema + class_types: A dictionary of type names and classes that override StructType when they are instantiated + symbol_shift: An offset by which to alter all returned symbols for this table + symbol_mask: An address mask used for all returned symbol offsets from this table (a mask of 0 disables masking) + """ + # Check there are no obvious errors + # Open the file and test the version + self._versions = dict([(x.version, x) for x in class_subclasses(ISFormatTable)]) + fp = volatility.framework.layers.resources.ResourceAccessor().open(isf_url) + reader = codecs.getreader("utf-8") + json_object = json.load(reader(fp)) # type: ignore + fp.close() + + # Validation is expensive, but we cache to store the hashes of successfully validated json objects + if validate and not schemas.validate(json_object): + raise exceptions.SymbolSpaceError("File does not pass version validation: {}".format(isf_url)) + + metadata = json_object.get('metadata', None) + + # Determine the delegate or throw an exception + self._delegate = self._closest_version(metadata.get('format', "0.0.0"), + self._versions)(context, config_path, name, json_object, native_types, + table_mapping) + if self._delegate.version < constants.ISF_MINIMUM_SUPPORTED: + raise RuntimeError("ISF version {} is no longer supported: {}".format(metadata.get('format', "0.0.0"), + isf_url)) + elif self._delegate.version < constants.ISF_MINIMUM_DEPRECATED: + vollog.warning("ISF version {} has been deprecated: {}".format(metadata.get('format', "0.0.0"), isf_url)) + + # Inherit + super().__init__(context, + config_path, + name, + native_types or self._delegate.natives, + table_mapping = table_mapping, + class_types = class_types) + + # Since we've been created with parameters, ensure our config is populated likewise + self.config['isf_url'] = isf_url + self.config['symbol_shift'] = symbol_shift + self.config['symbol_mask'] = symbol_mask + + @staticmethod + def _closest_version(version: str, versions: Dict[Tuple[int, int, int], Type['ISFormatTable']]) \ + -> Type['ISFormatTable']: + """Determines the highest suitable handler for specified version + format. + + An interface version such as Major.Minor.Patch means that Major + of the provider must be equal to that of the consumer, and the + provider (the JSON in this instance) must have a greater minor + (indicating that only additive changes have been made) than + the consumer (in this case, the file reader). + """ + major, minor, patch = [int(x) for x in version.split(".")] + supported_versions = [x for x in versions if x[0] == major and x[1] >= minor] + if not supported_versions: + raise ValueError( + "No Intermediate Format interface versions support file interface version: {}".format(version)) + return versions[max(supported_versions)] + + symbols = _construct_delegate_function('symbols', True) + types = _construct_delegate_function('types', True) + enumerations = _construct_delegate_function('enumerations', True) + metadata = _construct_delegate_function('metadata', True) + clear_symbol_cache = _construct_delegate_function('clear_symbol_cache') + get_type = _construct_delegate_function('get_type') + get_symbol = _construct_delegate_function('get_symbol') + get_enumeration = _construct_delegate_function('get_enumeration') + get_type_class = _construct_delegate_function('get_type_class') + set_type_class = _construct_delegate_function('set_type_class') + del_type_class = _construct_delegate_function('del_type_class') + + @classmethod + def file_symbol_url(cls, sub_path: str, filename: Optional[str] = None) -> Generator[str, None, None]: + """Returns an iterator of appropriate file-scheme symbol URLs that can + be opened by a ResourceAccessor class. + + Filter reduces the number of results returned to only those URLs + containing that string + """ + + # Check user-modifiable files first, then compressed ones + extensions = constants.ISF_EXTENSIONS + if filename is None: + filename = "*" + zip_match = filename + else: + # For zipfiles, the path separator is always "/", so we need to change the path + zip_match = "/".join(os.path.split(filename)) + + # Check user symbol directory first, then fallback to the framework's library to allow for overloading + vollog.log(constants.LOGLEVEL_VVVV, + "Searching for symbols in {}".format(", ".join(volatility.symbols.__path__))) + for path in volatility.symbols.__path__: + if not os.path.isabs(path): + path = os.path.abspath(os.path.join(__file__, path)) + for extension in extensions: + # Hopefully these will not be large lists, otherwise this might be slow + try: + for found in pathlib.Path(path).joinpath(sub_path).resolve().rglob(filename + extension): + yield found.as_uri() + except FileNotFoundError: + # If there's no linux symbols, don't cry about it + pass + + # Finally try looking in zip files + zip_path = os.path.join(path, sub_path + ".zip") + if os.path.exists(zip_path): + # We have a zipfile, so run through it and look for sub files that match the filename + with zipfile.ZipFile(zip_path) as zfile: + for name in zfile.namelist(): + for extension in extensions: + # By ending with an extension (and therefore, not /), we should not return any directories + if name.endswith(zip_match + extension) or (zip_match == "*" and name.endswith(extension)): + yield "jar:file:" + str(pathlib.Path(zip_path)) + "!" + name + + @classmethod + def create(cls, + context: interfaces.context.ContextInterface, + config_path: str, + sub_path: str, + filename: str, + native_types: Optional[interfaces.symbols.NativeTableInterface] = None, + table_mapping: Optional[Dict[str, str]] = None, + class_types: Optional[Mapping[str, Type[interfaces.objects.ObjectInterface]]] = None, + symbol_shift: int = 0, + symbol_mask: int = 0) -> str: + """Takes a context and loads an intermediate symbol table based on a + filename. + + Args: + context: The context that the current plugin is being run within + config_path: The configuration path for reading/storing configuration information this symbol table may use + sub_path: The path under a suitable symbol path (defaults to volatility/symbols and volatility/framework/symbols) to check + filename: Basename of the file to find under the sub_path + native_types: Set of native types, defaults to native types read from the intermediate symbol format file + table_mapping: a dictionary of table names mentioned within the ISF file, and the tables within the context which they map to + symbol_shift: An offset by which to alter all returned symbols for this table + symbol_mask: An address mask used for all returned symbol offsets from this table (a mask of 0 disables masking) + + Returns: + the name of the added symbol table + """ + urls = list(cls.file_symbol_url(sub_path, filename)) + if not urls: + raise FileNotFoundError("No symbol files found at provided filename: {}", filename) + table_name = context.symbol_space.free_table_name(filename) + table = cls(context = context, + config_path = config_path, + name = table_name, + isf_url = urls[0], + native_types = native_types, + table_mapping = table_mapping, + class_types = class_types, + symbol_shift = symbol_shift, + symbol_mask = symbol_mask) + context.symbol_space.append(table) + return table_name + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return super().get_requirements() + [ + requirements.StringRequirement( + "isf_url", description = "JSON file containing the symbols encoded in the Intermediate Symbol Format"), + ] + + +class ISFormatTable(interfaces.symbols.SymbolTableInterface, metaclass = ABCMeta): + """Provide a base class to identify all subclasses.""" + version = (0, 0, 0) + + def __init__(self, + context: interfaces.context.ContextInterface, + config_path: str, + name: str, + json_object: Any, + native_types: interfaces.symbols.NativeTableInterface = None, + table_mapping: Optional[Dict[str, str]] = None) -> None: + self._json_object = json_object + self._validate_json() + self.name = name + nt = native_types or self._get_natives() + if nt is None: + raise TypeError("Native table not provided") + nt.name = name + "_natives" + super().__init__(context, config_path, name, nt, table_mapping = table_mapping) + self._overrides = {} # type: Dict[str, Type[interfaces.objects.ObjectInterface]] + self._symbol_cache = {} # type: Dict[str, interfaces.symbols.SymbolInterface] + + def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: + """Determines the appropriate native_types to use from the JSON + data.""" + # TODO: Consider how to generate the natives entirely from the ISF + classes = {"x64": native.x64NativeTable, "x86": native.x86NativeTable} + for nc in sorted(classes): + native_class = classes[nc] + for base_type in self._json_object['base_types']: + try: + if self._json_object['base_types'][base_type]['length'] != native_class.get_type(base_type).size: + break + except TypeError: + # TODO: determine whether we should give voids a size - We don't give voids a length, whereas microsoft seemingly do + pass + else: + vollog.debug("Choosing appropriate natives for symbol library: {}".format(nc)) + return native_class.natives + return None + + # TODO: Check the format and make use of the other metadata + + def _validate_json(self) -> None: + if ('user_types' not in self._json_object or 'base_types' not in self._json_object + or 'metadata' not in self._json_object or 'symbols' not in self._json_object + or 'enums' not in self._json_object): + raise exceptions.SymbolSpaceError("Malformed JSON file provided") + + @property + def metadata(self) -> Optional[interfaces.symbols.MetadataInterface]: + """Returns a metadata object containing information about the symbol + table.""" + return None + + def clear_symbol_cache(self) -> None: + """Clears the symbol cache of the symbol table.""" + self._symbol_cache.clear() + + +class Version1Format(ISFormatTable): + """Class for storing intermediate debugging data as objects and classes.""" + version = (0, 0, 1) + + def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: + """Returns the location offset given by the symbol name.""" + # TODO: Add the ability to add/remove/change symbols after creation + # note that this should invalidate/update the cache + if self._symbol_cache.get(name, None): + return self._symbol_cache[name] + symbol = self._json_object['symbols'].get(name, None) + if not symbol: + raise exceptions.SymbolError(name, self.name, "Unknown symbol: {}".format(name)) + address = symbol['address'] + self.config.get('symbol_shift', 0) + if self.config.get('symbol_mask', 0): + address = address & self.config['symbol_mask'] + self._symbol_cache[name] = interfaces.symbols.SymbolInterface(name = name, address = address) + return self._symbol_cache[name] + + @property + def symbols(self) -> Iterable[str]: + """Returns an iterator of the symbol names.""" + return list(self._json_object.get('symbols', {})) + + @property + def enumerations(self) -> Iterable[str]: + """Returns an iterator of the available enumerations.""" + return list(self._json_object.get('enums', {})) + + @property + def types(self) -> Iterable[str]: + """Returns an iterator of the symbol type names.""" + return list(self._json_object.get('user_types', {})) + list(self.natives.types) + + def get_type_class(self, name: str) -> Type[interfaces.objects.ObjectInterface]: + return self._overrides.get(name, objects.AggregateType) + + def set_type_class(self, name: str, clazz: Type[interfaces.objects.ObjectInterface]) -> None: + if name not in self.types: + raise ValueError("Symbol type not in {} SymbolTable: {}".format(self.name, name)) + self._overrides[name] = clazz + + def del_type_class(self, name: str) -> None: + if name in self._overrides: + del self._overrides[name] + + def _interdict_to_template(self, dictionary: Dict[str, Any]) -> interfaces.objects.Template: + """Converts an intermediate format dict into an object template.""" + if not dictionary: + raise exceptions.SymbolSpaceError("Invalid intermediate dictionary: {}".format(dictionary)) + + type_name = dictionary['kind'] + if type_name == 'base': + type_name = dictionary['name'] + + if type_name in self.natives.types: + # The symbol is a native type + native_template = self.natives.get_type(self.name + constants.BANG + type_name) + + # Add specific additional parameters, etc + update = {} + if type_name == 'array': + update['count'] = dictionary['count'] + update['subtype'] = self._interdict_to_template(dictionary['subtype']) + elif type_name == 'pointer': + if dictionary.get('base', None): + base_type = self.natives.get_type(self.name + constants.BANG + dictionary['base']) + update['data_format'] = base_type.vol['data_format'] + update['subtype'] = self._interdict_to_template(dictionary['subtype']) + elif type_name == 'enum': + update = self._lookup_enum(dictionary['name']) + elif type_name == 'bitfield': + update = { + 'start_bit': dictionary['bit_position'], + 'end_bit': dictionary['bit_position'] + dictionary['bit_length'] + } + update['base_type'] = self._interdict_to_template(dictionary['type']) + # We do *not* call native_template.clone(), since it slows everything down a lot + # We require that the native.get_type method always returns a newly constructed python object + native_template.update_vol(**update) + return native_template + + # Otherwise + if dictionary['kind'] not in objects.AggregateTypes.values(): + raise exceptions.SymbolSpaceError("Unknown Intermediate format: {}".format(dictionary)) + + reference_name = dictionary['name'] + if constants.BANG not in reference_name: + reference_name = self.name + constants.BANG + reference_name + else: + reference_parts = reference_name.split(constants.BANG) + reference_name = (self.table_mapping.get(reference_parts[0], reference_parts[0]) + constants.BANG + + constants.BANG.join(reference_parts[1:])) + + return objects.templates.ReferenceTemplate(type_name = reference_name) + + def _lookup_enum(self, name: str) -> Dict[str, Any]: + """Looks up an enumeration and returns a dictionary of __init__ + parameters for an Enum.""" + lookup = self._json_object['enums'].get(name, None) + if not lookup: + raise exceptions.SymbolSpaceError("Unknown enumeration: {}".format(name)) + result = {"choices": copy.deepcopy(lookup['constants']), "base_type": self.natives.get_type(lookup['base'])} + return result + + def get_enumeration(self, enum_name: str) -> interfaces.objects.Template: + """Resolves an individual enumeration.""" + if constants.BANG in enum_name: + raise exceptions.SymbolError(enum_name, self.name, + "Enumeration for a different table requested: {}".format(enum_name)) + if enum_name not in self._json_object['enums']: + # Fall back to the natives table + raise exceptions.SymbolError(enum_name, self.name, + "Enumeration not found in {} table: {}".format(self.name, enum_name)) + curdict = self._json_object['enums'][enum_name] + base_type = self.natives.get_type(curdict['base']) + # The size isn't actually used, the base-type defines it. + return objects.templates.ObjectTemplate(type_name = self.name + constants.BANG + enum_name, + object_class = objects.Enumeration, + base_type = base_type, + choices = curdict['constants']) + + def get_type(self, type_name: str) -> interfaces.objects.Template: + """Resolves an individual symbol.""" + if constants.BANG in type_name: + index = type_name.find(constants.BANG) + table_name, type_name = type_name[:index], type_name[index + 1:] + raise exceptions.SymbolError( + type_name, table_name, + "Symbol for a different table requested: {}".format(table_name + constants.BANG + type_name)) + if type_name not in self._json_object['user_types']: + # Fall back to the natives table + return self.natives.get_type(self.name + constants.BANG + type_name) + curdict = self._json_object['user_types'][type_name] + members = {} + for member_name in curdict['fields']: + interdict = curdict['fields'][member_name] + member = (interdict['offset'], self._interdict_to_template(interdict['type'])) + members[member_name] = member + object_class = self.get_type_class(type_name) + if object_class == objects.AggregateType: + for clazz in objects.AggregateTypes: + if objects.AggregateTypes[clazz] == curdict['kind']: + object_class = clazz + return objects.templates.ObjectTemplate(type_name = self.name + constants.BANG + type_name, + object_class = object_class, + size = curdict['length'], + members = members) + + +class Version2Format(Version1Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (2, 0, 0) + + def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: + """Determines the appropriate native_types to use from the JSON + data.""" + classes = {"x64": native.x64NativeTable, "x86": native.x86NativeTable} + for nc in sorted(classes): + native_class = classes[nc] + for base_type in self._json_object['base_types']: + try: + if self._json_object['base_types'][base_type]['size'] != native_class.get_type(base_type).size: + break + except TypeError: + # TODO: determine whether we should give voids a size - We don't give voids a length, whereas microsoft seemingly do + pass + else: + vollog.debug("Choosing appropriate natives for symbol library: {}".format(nc)) + return native_class.natives + return None + + def get_type(self, type_name: str) -> interfaces.objects.Template: + """Resolves an individual symbol.""" + if constants.BANG in type_name: + index = type_name.find(constants.BANG) + table_name, type_name = type_name[:index], type_name[index + 1:] + raise exceptions.SymbolError( + type_name, table_name, + "Symbol for a different table requested: {}".format(table_name + constants.BANG + type_name)) + if type_name not in self._json_object['user_types']: + # Fall back to the natives table + if type_name in self.natives.types: + return self.natives.get_type(self.name + constants.BANG + type_name) + else: + raise exceptions.SymbolError(type_name, self.name, "Unknown symbol: {}".format(type_name)) + curdict = self._json_object['user_types'][type_name] + members = {} + for member_name in curdict['fields']: + interdict = curdict['fields'][member_name] + member = (interdict['offset'], self._interdict_to_template(interdict['type'])) + members[member_name] = member + object_class = self.get_type_class(type_name) + if object_class == objects.AggregateType: + for clazz in objects.AggregateTypes: + if objects.AggregateTypes[clazz] == curdict['kind']: + object_class = clazz + return objects.templates.ObjectTemplate(type_name = self.name + constants.BANG + type_name, + object_class = object_class, + size = curdict['size'], + members = members) + + +class Version3Format(Version2Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (2, 1, 0) + + def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: + """Returns the symbol given by the symbol name.""" + if self._symbol_cache.get(name, None): + return self._symbol_cache[name] + symbol = self._json_object['symbols'].get(name, None) + if not symbol: + raise exceptions.SymbolError(name, self.name, "Unknown symbol: {}".format(name)) + symbol_type = None + if 'type' in symbol: + symbol_type = self._interdict_to_template(symbol['type']) + + # Mask the addresses if necessary + address = symbol['address'] + self.config.get('symbol_shift', 0) + if self.config.get('symbol_mask', 0): + address = address & self.config['symbol_mask'] + self._symbol_cache[name] = interfaces.symbols.SymbolInterface(name = name, + address = address, + type = symbol_type) + return self._symbol_cache[name] + + +class Version4Format(Version3Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (4, 0, 0) + + format_mapping = { + 'int': objects.Integer, + 'float': objects.Float, + 'void': objects.Integer, + 'bool': objects.Boolean, + 'char': objects.Char + } + + def _get_natives(self) -> Optional[interfaces.symbols.NativeTableInterface]: + """Determines the appropriate native_types to use from the JSON + data.""" + native_dict = {} + base_types = self._json_object['base_types'] + for base_type in base_types: + # Void are ignored because voids are not a volatility primitive, they are a specific Volatility object + if base_type != 'void': + current = base_types[base_type] + # TODO: Fix up the typing of this, it bugs out because of the tuple assignment + if current['kind'] not in self.format_mapping: + raise ValueError("Unsupported base kind") + format_val = (current['size'], current['endian'], current['signed']) + object_type = self.format_mapping[current['kind']] + if base_type == 'pointer': + object_type = objects.Pointer + native_dict[base_type] = (object_type, format_val) + return native.NativeTable(name = "native", native_dictionary = native_dict) + + +class Version5Format(Version4Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (4, 1, 0) + + def get_symbol(self, name: str) -> interfaces.symbols.SymbolInterface: + """Returns the symbol given by the symbol name.""" + if self._symbol_cache.get(name, None): + return self._symbol_cache[name] + symbol = self._json_object['symbols'].get(name, None) + if not symbol: + raise exceptions.SymbolError(name, self.name, "Unknown symbol: {}".format(name)) + symbol_type = None + if 'type' in symbol: + symbol_type = self._interdict_to_template(symbol['type']) + symbol_constant_data = None + if 'constant_data' in symbol: + symbol_constant_data = base64.b64decode(symbol.get('constant_data')) + + # Mask the addresses if necessary + address = symbol['address'] + self.config.get('symbol_shift', 0) + if self.config.get('symbol_mask', 0): + address = address & self.config['symbol_mask'] + self._symbol_cache[name] = interfaces.symbols.SymbolInterface(name = name, + address = address, + type = symbol_type, + constant_data = symbol_constant_data) + return self._symbol_cache[name] + + +class Version6Format(Version5Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (6, 0, 0) + + @property + def metadata(self) -> Optional[interfaces.symbols.MetadataInterface]: + """Returns a MetadataInterface object.""" + if self._json_object.get('metadata', {}).get('windows'): + return metadata.WindowsMetadata(self._json_object['metadata']['windows']) + if self._json_object.get('metadata', {}).get('linux'): + return metadata.LinuxMetadata(self._json_object['metadata']['linux']) + return None + + +class Version7Format(Version6Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (6, 1, 0) + + +class Version8Format(Version7Format): + """Class for storing intermediate debugging data as objects and classes.""" + version = (6, 2, 0) + + def _process_fields(self, fields: Dict[str, Dict[str, Any]]) -> Dict[Any, Tuple[int, interfaces.objects.Template]]: + """For each type field, it walks its tree of subtypes, reducing the hierarchy to just one level. + It creates a tuple of offset and object templates for each field. + """ + members = {} + for new_offset, member_name, member_value in self._reduce_fields(fields): + member = (new_offset, self._interdict_to_template(member_value['type'])) + members[member_name] = member + return members + + def _reduce_fields(self, + fields: Dict[str, Dict[str, Any]], + parent_offset: int = 0) -> Generator[Tuple[int, str, Dict], None, None]: + """Reduce the fields bringing them one level up. It supports anonymous types such as structs or unions in any + level of depth.""" + for member_name, member_value in fields.items(): + new_offset = parent_offset + member_value.get('offset', 0) + if member_value.get('anonymous', False) and isinstance(member_value, dict): + # Gets the subtype from the json ISF and recursively reduce its fields + subtype = self._json_object['user_types'].get(member_value['type']['name'], {}) + yield from self._reduce_fields(subtype['fields'], new_offset) + else: + yield new_offset, member_name, member_value + + def get_type(self, type_name: str) -> interfaces.objects.Template: + """Resolves an individual symbol.""" + index = type_name.find(constants.BANG) + if index != -1: + table_name, type_name = type_name[:index], type_name[index + 1:] + raise exceptions.SymbolError( + type_name, table_name, + "Symbol for a different table requested: {}".format(table_name + constants.BANG + type_name)) + + type_definition = self._json_object['user_types'].get(type_name) + if type_definition is None: + # Fall back to the natives table + return self.natives.get_type(self.name + constants.BANG + type_name) + + members = self._process_fields(type_definition['fields']) + + object_class = self.get_type_class(type_name) + if object_class == objects.AggregateType: + for clazz in objects.AggregateTypes: + if objects.AggregateTypes[clazz] == type_definition['kind']: + object_class = clazz + return objects.templates.ObjectTemplate(type_name = self.name + constants.BANG + type_name, + object_class = object_class, + size = type_definition['size'], + members = members) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/linux/__init__.py new file mode 100644 index 00000000..d8c88d07 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/__init__.py @@ -0,0 +1,259 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +from typing import List, Tuple, Iterator + +from volatility.framework import exceptions, constants, interfaces, objects, contexts +from volatility.framework.objects import utility +from volatility.framework.symbols import intermed +from volatility.framework.symbols.linux import extensions +from volatility.framework.objects import utility + + +class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): + provides = {"type": "interface"} + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + # Set-up Linux specific types + self.set_type_class('file', extensions.struct_file) + self.set_type_class('list_head', extensions.list_head) + self.set_type_class('mm_struct', extensions.mm_struct) + self.set_type_class('super_block', extensions.super_block) + self.set_type_class('task_struct', extensions.task_struct) + self.set_type_class('vm_area_struct', extensions.vm_area_struct) + self.set_type_class('qstr', extensions.qstr) + self.set_type_class('dentry', extensions.dentry) + self.set_type_class('fs_struct', extensions.fs_struct) + self.set_type_class('files_struct', extensions.files_struct) + self.set_type_class('vfsmount', extensions.vfsmount) + self.set_type_class('kobject', extensions.kobject) + + if 'module' in self.types: + self.set_type_class('module', extensions.module) + + if 'mount' in self.types: + self.set_type_class('mount', extensions.mount) + + +class LinuxUtilities(interfaces.configuration.VersionableInterface): + """Class with multiple useful linux functions.""" + + _version = (1, 0, 0) + + # based on __d_path from the Linux kernel + @classmethod + def _do_get_path(cls, rdentry, rmnt, dentry, vfsmnt) -> str: + + ret_path = [] # type: List[str] + + while dentry != rdentry or vfsmnt != rmnt: + dname = dentry.path() + if dname == "": + break + + ret_path.insert(0, dname.strip('/')) + if dentry == vfsmnt.get_mnt_root() or dentry == dentry.d_parent: + if vfsmnt.get_mnt_parent() == vfsmnt: + break + + dentry = vfsmnt.get_mnt_mountpoint() + vfsmnt = vfsmnt.get_mnt_parent() + + continue + + parent = dentry.d_parent + dentry = parent + + # if we did not gather any valid dentrys in the path, then the entire file is + # either 1) smeared out of memory or 2) de-allocated and corresponding structures overwritten + # we return an empty string in this case to avoid confusion with something like a handle to the root + # directory (e.g., "/") + if not ret_path: + return "" + + ret_val = '/'.join([str(p) for p in ret_path if p != ""]) + + if ret_val.startswith(("socket:", "pipe:")): + if ret_val.find("]") == -1: + try: + inode = dentry.d_inode + ino = inode.i_ino + except exceptions.InvalidAddressException: + ino = 0 + + ret_val = ret_val[:-1] + ":[{0}]".format(ino) + else: + ret_val = ret_val.replace("/", "") + + elif ret_val != "inotify": + ret_val = '/' + ret_val + + return ret_val + + # method used by 'older' kernels + # TODO: lookup when dentry_operations->d_name was merged into the mainline kernel for exact version + @classmethod + def _get_path_file(cls, task, filp) -> str: + rdentry = task.fs.get_root_dentry() + rmnt = task.fs.get_root_mnt() + dentry = filp.get_dentry() + vfsmnt = filp.get_vfsmnt() + + return LinuxUtilities._do_get_path(rdentry, rmnt, dentry, vfsmnt) + + @classmethod + def _get_new_sock_pipe_path(cls, context, task, filp) -> str: + dentry = filp.get_dentry() + + sym_addr = dentry.d_op.d_dname + + symbol_table_arr = sym_addr.vol.type_name.split("!") + symbol_table = None + if len(symbol_table_arr) == 2: + symbol_table = symbol_table_arr[0] + + symbs = list(context.symbol_space.get_symbols_by_location(sym_addr, table_name = symbol_table)) + + if len(symbs) == 1: + sym = symbs[0].split(constants.BANG)[1] + + if sym == "sockfs_dname": + pre_name = "socket" + + elif sym == "anon_inodefs_dname": + pre_name = "anon_inode" + + elif sym == "pipefs_dname": + pre_name = "pipe" + + elif sym == "simple_dname": + pre_name = cls._get_path_file(task, filp) + + else: + pre_name = "".format(sym) + + ret = "{0}:[{1:d}]".format(pre_name, dentry.d_inode.i_ino) + + else: + ret = " {0:x}".format(sym_addr) + + return ret + + # a 'file' structure doesn't have enough information to properly restore its full path + # we need the root mount information from task_struct to determine this + @classmethod + def path_for_file(cls, context, task, filp) -> str: + try: + dentry = filp.get_dentry() + except exceptions.InvalidAddressException: + return "" + + if dentry == 0: + return "" + + dname_is_valid = False + + # TODO COMPARE THIS IN LSOF OUTPUT TO VOL2 + try: + if dentry.d_op and dentry.d_op.has_member("d_dname") and dentry.d_op.d_dname: + dname_is_valid = True + + except exceptions.InvalidAddressException: + dname_is_valid = False + + if dname_is_valid: + ret = LinuxUtilities._get_new_sock_pipe_path(context, task, filp) + else: + ret = LinuxUtilities._get_path_file(task, filp) + + return ret + + @classmethod + def files_descriptors_for_process(cls, context: interfaces.context.ContextInterface, symbol_table: str, + task: interfaces.objects.ObjectInterface): + + fd_table = task.files.get_fds() + if fd_table == 0: + return + + max_fds = task.files.get_max_fds() + + # corruption check + if max_fds > 500000: + return + + file_type = symbol_table + constants.BANG + 'file' + + fds = objects.utility.array_of_pointers(fd_table, count = max_fds, subtype = file_type, context = context) + + for (fd_num, filp) in enumerate(fds): + if filp != 0: + full_path = LinuxUtilities.path_for_file(context, task, filp) + + yield fd_num, filp, full_path + + @classmethod + def mask_mods_list(cls, context: interfaces.context.ContextInterface, layer_name: str, + mods: Iterator[interfaces.objects.ObjectInterface]) -> List[Tuple[str, int, int]]: + """ + A helper function to mask the starting and end address of kernel modules + """ + mask = context.layers[layer_name].address_mask + + return [(utility.array_to_string(mod.name), mod.get_module_base() & mask, + (mod.get_module_base() & mask) + mod.get_core_size()) for mod in mods] + + @classmethod + def generate_kernel_handler_info( + cls, context: interfaces.context.ContextInterface, layer_name: str, kernel_name: str, + mods_list: Iterator[interfaces.objects.ObjectInterface]) -> List[Tuple[str, int, int]]: + """ + A helper function that gets the beginning and end address of the kernel module + """ + + kernel = contexts.Module(context, kernel_name, layer_name, 0) + + mask = context.layers[layer_name].address_mask + + start_addr = kernel.object_from_symbol("_text") + start_addr = start_addr.vol.offset & mask + + end_addr = kernel.object_from_symbol("_etext") + end_addr = end_addr.vol.offset & mask + + return [(constants.linux.KERNEL_NAME, start_addr, end_addr)] + \ + LinuxUtilities.mask_mods_list(context, layer_name, mods_list) + + @classmethod + def lookup_module_address(cls, context: interfaces.context.ContextInterface, handlers: List[Tuple[str, int, int]], + target_address: int): + """ + Searches between the start and end address of the kernel module using target_address. + Returns the module and symbol name of the address provided. + """ + + mod_name = "UNKNOWN" + symbol_name = "N/A" + + for name, start, end in handlers: + if start <= target_address <= end: + mod_name = name + if name == constants.linux.KERNEL_NAME: + symbols = list(context.symbol_space.get_symbols_by_location(target_address)) + + if len(symbols): + symbol_name = symbols[0].split(constants.BANG)[1] if constants.BANG in symbols[0] else \ + symbols[0] + + break + + return mod_name, symbol_name + + @classmethod + def walk_internal_list(cls, vmlinux, struct_name, list_member, list_start): + while list_start: + list_struct = vmlinux.object(object_type = struct_name, offset = list_start.vol.offset) + yield list_struct + list_start = getattr(list_struct, list_member) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash.py b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash.py new file mode 100644 index 00000000..570d2cda --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash.py @@ -0,0 +1,14 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework.symbols import intermed +from volatility.framework.symbols.linux.extensions import bash + + +class BashIntermedSymbols(intermed.IntermediateSymbolTable): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.set_type_class('hist_entry', bash.hist_entry) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash32.json b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash32.json new file mode 100644 index 00000000..13212259 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash32.json @@ -0,0 +1,72 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "hist_entry": { + "fields": { + "timestamp": { + "type": { + "subtype": { + "count": 1024, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "kind": "pointer" + }, + "offset": 4 + }, + "line": { + "type": { + "subtype": { + "count": 1024, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "kind": "pointer" + }, + "offset": 0 + }, + "data": { + "type": { + "subtype": { + "kind": "struct", + "name": "void" + }, + "kind": "pointer" + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 12 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "/mnt/hgfs/volshared/vtypes_to_json.py", + "datetime": "2018-12-03T09:57:47.343730" + }, + "format": "4.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash64.json b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash64.json new file mode 100644 index 00000000..2127aa20 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/bash64.json @@ -0,0 +1,72 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "hist_entry": { + "fields": { + "timestamp": { + "type": { + "subtype": { + "count": 1024, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "kind": "pointer" + }, + "offset": 8 + }, + "line": { + "type": { + "subtype": { + "count": 1024, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "kind": "pointer" + }, + "offset": 0 + }, + "data": { + "type": { + "subtype": { + "kind": "struct", + "name": "void" + }, + "kind": "pointer" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "/mnt/hgfs/volshared/vtypes_to_json.py", + "datetime": "2018-12-03T09:55:34.880766" + }, + "format": "4.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/elf.json b/app/parsers/vol_Parser/volatility/framework/symbols/linux/elf.json new file mode 100644 index 00000000..76cd8a2e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/elf.json @@ -0,0 +1,967 @@ +{ + "symbols": { + }, + "user_types": { + "Elf": { + "fields": { + "e_ident": { + "offset": 0, + "type": { + "count": 16, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "Elf64_Ehdr": { + "fields": { + "e_ident": { + "offset": 0, + "type": { + "count": 16, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "e_type": { + "offset": 16, + "type": { + "kind": "enum", + "name": "EtypeEnum" + } + }, + "e_machine": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_version": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_entry": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "e_phoff": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "e_shoff": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "e_flags": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_ehsize": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_phentsize": { + "offset": 54, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_phnum": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shentsize": { + "offset": 58, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shnum": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shstrndx": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "Elf64_Shdr": { + "fields": { + "sh_name": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_type": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_flags": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "sh_addr": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "sh_offset": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "sh_size": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "sh_link": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_info": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_addralign": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "sh_entsize": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 64 + }, + "Elf64_Phdr": { + "fields": { + "p_type": { + "offset": 0, + "type": { + "kind": "enum", + "name": "PtypeEnum" + } + }, + "p_flags": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_offset": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "p_vaddr": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "p_paddr": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "p_filesz": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "p_memsz": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "p_align": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 56 + }, + "Elf64_Dyn": { + "fields": { + "d_tag": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "d_ptr": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 16 + }, + "Elf64_Note": { + "fields": { + "n_namesz": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "n_descsz": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "n_type": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + }, + "Elf64_Sym": { + "fields": { + "st_name": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "st_info": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "st_other": { + "offset": 5, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "st_shndx": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "st_value": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "st_size": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 24 + }, + "Elf64_LinkMap": { + "fields": { + "l_addr": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "l_name": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "l_ld": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "l_next": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "l_prev": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 40 + }, + "Elf64_Rel": { + "fields": { + "r_offset": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "r_info": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 16 + }, + "Elf64_Rela": { + "fields": { + "r_offset": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "r_info": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "r_addend": { + "offset": 16, + "type": { + "kind": "base", + "name": "long long" + } + } + }, + "kind": "struct", + "size": 24 + }, + "Elf32_Ehdr": { + "fields": { + "e_ident": { + "offset": 0, + "type": { + "count": 16, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "e_type": { + "offset": 16, + "type": { + "kind": "enum", + "name": "EtypeEnum" + } + }, + "e_machine": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_version": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_entry": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_phoff": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_shoff": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_flags": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "e_ehsize": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_phentsize": { + "offset": 42, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_phnum": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shentsize": { + "offset": 46, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shnum": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_shstrndx": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 52 + }, + "Elf32_Shdr": { + "fields": { + "sh_name": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_type": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_flags": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_addr": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_offset": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_size": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_link": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_info": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_addralign": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "sh_entsize": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 40 + }, + "Elf32_Phdr": { + "fields": { + "p_type": { + "offset": 0, + "type": { + "kind": "enum", + "name": "PtypeEnum" + } + }, + "p_offset": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_vaddr": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_paddr": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_filesz": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_memsz": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_flags": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "p_align": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 32 + }, + "Elf32_Dyn": { + "fields": { + "d_tag": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "d_ptr": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "Elf32_Note": { + "fields": { + "n_namesz": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "n_descsz": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "n_type": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + }, + "Elf32_Sym": { + "fields": { + "st_name": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "st_value": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "st_size": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "st_info": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "st_other": { + "offset": 13, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "st_shndx": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 16 + }, + "Elf32_LinkMap": { + "fields": { + "l_addr": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "l_name": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "l_ld": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "l_next": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "l_prev": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 20 + }, + "Elf32_Rel": { + "fields": { + "r_offset": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "r_info": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "Elf32_Rela": { + "fields": { + "r_offset": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "r_info": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "r_addend": { + "offset": 8, + "type": { + "kind": "base", + "name": "long" + } + } + }, + "kind": "struct", + "size": 12 + } + + }, + "enums": { + "EtypeEnum": { + "base": "unsigned short", + "constants": { + "ET_NONE": 0, + "ET_REL": 1, + "ET_EXEC": 2, + "ET_DYN": 3, + "ET_CORE": 4, + "ET_LOPROC": 65280, + "ET_HIPROC": 65535 + }, + "size": 2 + }, + "PtypeEnum": { + "base": "unsigned long", + "constants": { + "PT_NULL": 0, + "PT_LOAD": 1, + "PT_DYNAMIC": 2, + "PT_INTERP": 3, + "PT_NOTE": 4, + "PT_SHLIB": 5, + "PT_PHDR": 6, + "PT_TLS": 7, + "PT_LOOS": 1610612736, + "PT_HIOS": 1879048191, + "PT_LOWPROC": 1879048192, + "PT_HIPROC": 2147483647 + }, + "size": 4 + } + }, + "base_types": { + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "little", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "ikelos-by-hand", + "datetime": "2019-10-21T22:52:00" + }, + "format": "6.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/__init__.py new file mode 100644 index 00000000..bddb3c7a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/__init__.py @@ -0,0 +1,530 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import collections.abc +import logging +from typing import Generator, Iterable, Iterator, Optional, Tuple + +from volatility.framework import constants +from volatility.framework import exceptions, objects, interfaces, symbols +from volatility.framework.layers import linear +from volatility.framework.objects import utility +from volatility.framework.symbols import generic, linux +from volatility.framework.symbols import intermed +from volatility.framework.symbols.linux.extensions import elf + +vollog = logging.getLogger(__name__) + + +# Keep these in a basic module, to prevent import cycles when symbol providers require them + + +class module(generic.GenericIntelProcess): + + def get_module_base(self): + if self.has_member("core_layout"): + return self.core_layout.base + else: + return self.module_core + + def get_init_size(self): + if self.has_member("init_layout"): + return self.init_layout.size + + elif self.has_member("init_size"): + return self.init_size + + raise AttributeError("module -> get_init_size: Unable to determine .init section size of module") + + def get_core_size(self): + if self.has_member("core_layout"): + return self.core_layout.size + + elif self.has_member("core_size"): + return self.core_size + + raise AttributeError("module -> get_core_size: Unable to determine core size of module") + + def get_module_core(self): + if self.has_member("core_layout"): + return self.core_layout.base + elif self.has_member("module_core"): + return self.module_core + + raise AttributeError("module -> get_module_core: Unable to get module core") + + def get_module_init(self): + if self.has_member("init_layout"): + return self.init_layout.base + elif self.has_member("module_init"): + return self.module_init + + raise AttributeError("module -> get_module_core: Unable to get module init") + + def get_name(self): + """ Get the name of the module as a string """ + return utility.array_to_string(self.name) + + def _get_sect_count(self, grp): + """ Try to determine the number of valid sections """ + arr = self._context.object( + self.get_symbol_table().name + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = grp.attrs, + subtype = self._context.symbol_space.get_type(self.get_symbol_table().name + constants.BANG + "pointer"), + count = 25) + + idx = 0 + while arr[idx]: + idx = idx + 1 + + return idx + + def get_sections(self): + """ Get sections of the module """ + if self.sect_attrs.has_member("nsections"): + num_sects = self.sect_attrs.nsections + else: + num_sects = self._get_sect_count(self.sect_attrs.grp) + + arr = self._context.object(self.get_symbol_table().name + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = self.sect_attrs.attrs.vol.offset, + subtype = self._context.symbol_space.get_type(self.get_symbol_table().name + + constants.BANG + 'module_sect_attr'), + count = num_sects) + + for attr in arr: + yield attr + + def get_symbols(self): + if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table().name): + prefix = "Elf64_" + else: + prefix = "Elf32_" + + elf_table_name = intermed.IntermediateSymbolTable.create(self.context, + self.config_path, + "linux", + "elf", + native_types = None, + class_types = extensions.elf.class_types) + + syms = self._context.object( + self.get_symbol_table().name + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = self.section_symtab, + subtype = self._context.symbol_space.get_type(elf_table_name + constants.BANG + prefix + "Sym"), + count = self.num_symtab + 1) + if self.section_strtab: + for sym in syms: + sym.set_cached_strtab(self.section_strtab) + yield sym + + def get_symbol(self, wanted_sym_name): + """ Get value for a given symbol name """ + for sym in self.get_symbols(): + sym_name = sym.get_name() + sym_addr = sym.st_value + if wanted_sym_name == sym_name: + return sym_addr + + @property + def section_symtab(self): + if self.has_member("kallsyms"): + return self.kallsyms.symtab + elif self.has_member("symtab"): + return self.symtab + + raise AttributeError("module -> symtab: Unable to get symtab") + + @property + def num_symtab(self): + if self.has_member("kallsyms"): + return int(self.kallsyms.num_symtab) + elif self.has_member("num_symtab"): + return int(self.num_symtab) + + raise AttributeError("module -> num_symtab: Unable to determine number of symbols") + + @property + def section_strtab(self): + # Newer kernels + if self.has_member("kallsyms"): + return self.kallsyms.strtab + # Older kernels + elif self.has_member("strtab"): + return self.strtab + + raise AttributeError("module -> strtab: Unable to get strtab") + + +class task_struct(generic.GenericIntelProcess): + + def add_process_layer(self, config_prefix: str = None, preferred_name: str = None) -> Optional[str]: + """Constructs a new layer based on the process's DTB. + + Returns the name of the Layer or None. + """ + + parent_layer = self._context.layers[self.vol.layer_name] + try: + pgd = self.mm.pgd + except exceptions.InvalidAddressException: + return None + + if not isinstance(parent_layer, linear.LinearlyMappedLayer): + raise TypeError("Parent layer is not a translation layer, unable to construct process layer") + + dtb, layer_name = parent_layer.translate(pgd) + if not dtb: + return None + + if preferred_name is None: + preferred_name = self.vol.layer_name + "_Process{}".format(self.pid) + + # Add the constructed layer and return the name + return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) + + def get_process_memory_sections(self, heap_only: bool = False) -> Generator[Tuple[int, int], None, None]: + """Returns a list of sections based on the memory manager's view of + this task's virtual memory.""" + for vma in self.mm.get_mmap_iter(): + start = int(vma.vm_start) + end = int(vma.vm_end) + + if heap_only and not (start <= self.mm.brk and end >= self.mm.start_brk): + continue + else: + # FIXME: Check if this actually needs to be printed out or not + vollog.info("adding vma: {:x} {:x} | {:x} {:x}".format(start, self.mm.brk, end, self.mm.start_brk)) + + yield (start, end - start) + + +class fs_struct(objects.StructType): + + def get_root_dentry(self): + # < 2.6.26 + if self.has_member("rootmnt"): + return self.root + elif self.root.has_member("dentry"): + return self.root.dentry + + raise AttributeError("Unable to find the root dentry") + + def get_root_mnt(self): + # < 2.6.26 + if self.has_member("rootmnt"): + return self.rootmnt + elif self.root.has_member("mnt"): + return self.root.mnt + + raise AttributeError("Unable to find the root mount") + + +class mm_struct(objects.StructType): + + def get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Returns an iterator for the mmap list member of an mm_struct.""" + + if not self.mmap: + return + + yield self.mmap + + seen = {self.mmap.vol.offset} + link = self.mmap.vm_next + + while link != 0 and link.vol.offset not in seen: + yield link + seen.add(link.vol.offset) + link = link.vm_next + + +class super_block(objects.StructType): + # include/linux/kdev_t.h + MINORBITS = 20 + + @property + def major(self) -> int: + return self.s_dev >> self.MINORBITS + + @property + def minor(self) -> int: + return self.s_dev & ((1 << self.MINORBITS) - 1) + + +class vm_area_struct(objects.StructType): + perm_flags = { + 0x00000001: "r", + 0x00000002: "w", + 0x00000004: "x", + } + + extended_flags = { + 0x00000001: "VM_READ", + 0x00000002: "VM_WRITE", + 0x00000004: "VM_EXEC", + 0x00000008: "VM_SHARED", + 0x00000010: "VM_MAYREAD", + 0x00000020: "VM_MAYWRITE", + 0x00000040: "VM_MAYEXEC", + 0x00000080: "VM_MAYSHARE", + 0x00000100: "VM_GROWSDOWN", + 0x00000200: "VM_NOHUGEPAGE", + 0x00000400: "VM_PFNMAP", + 0x00000800: "VM_DENYWRITE", + 0x00001000: "VM_EXECUTABLE", + 0x00002000: "VM_LOCKED", + 0x00004000: "VM_IO", + 0x00008000: "VM_SEQ_READ", + 0x00010000: "VM_RAND_READ", + 0x00020000: "VM_DONTCOPY", + 0x00040000: "VM_DONTEXPAND", + 0x00080000: "VM_RESERVED", + 0x00100000: "VM_ACCOUNT", + 0x00200000: "VM_NORESERVE", + 0x00400000: "VM_HUGETLB", + 0x00800000: "VM_NONLINEAR", + 0x01000000: "VM_MAPPED_COP__VM_HUGEPAGE", + 0x02000000: "VM_INSERTPAGE", + 0x04000000: "VM_ALWAYSDUMP", + 0x08000000: "VM_CAN_NONLINEAR", + 0x10000000: "VM_MIXEDMAP", + 0x20000000: "VM_SAO", + 0x40000000: "VM_PFN_AT_MMAP", + 0x80000000: "VM_MERGEABLE", + } + + def _parse_flags(self, vm_flags, parse_flags) -> str: + """Returns an string representation of the flags in a + vm_area_struct.""" + + retval = "" + + for mask, char in parse_flags.items(): + if (vm_flags & mask) == mask: + retval = retval + char + else: + retval = retval + '-' + + return retval + + # only parse the rwx bits + def get_protection(self) -> str: + return self._parse_flags(self.vm_flags & 0b1111, vm_area_struct.perm_flags) + + # used by malfind + def get_flags(self) -> str: + return self._parse_flags(self.vm_flags, self.extended_flags) + + def get_page_offset(self) -> int: + if self.vm_file == 0: + return 0 + + return self.vm_pgoff << constants.linux.PAGE_SHIFT + + def get_name(self, context, task): + if self.vm_file != 0: + fname = linux.LinuxUtilities.path_for_file(context, task, self.vm_file) + elif self.vm_start <= task.mm.start_brk and self.vm_end >= task.mm.brk: + fname = "[heap]" + elif self.vm_start <= task.mm.start_stack and self.vm_end >= task.mm.start_stack: + fname = "[stack]" + elif self.vm_mm.context.has_member("vdso") and self.vm_start == self.vm_mm.context.vdso: + fname = "[vdso]" + else: + fname = "Anonymous Mapping" + + return fname + + # used by malfind + def is_suspicious(self): + ret = False + + flags_str = self.get_protection() + + if flags_str == "rwx": + ret = True + + elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0: + ret = True + + return ret + + +class qstr(objects.StructType): + + def name_as_str(self) -> str: + if self.has_member("len"): + str_length = self.len + 1 # Maximum length should include null terminator + else: + str_length = 255 + + try: + ret = objects.utility.pointer_to_string(self.name, str_length) + except (exceptions.InvalidAddressException, ValueError): + ret = "" + + return ret + + +class dentry(objects.StructType): + + def path(self) -> str: + return self.d_name.name_as_str() + + +class struct_file(objects.StructType): + + def get_dentry(self) -> interfaces.objects.ObjectInterface: + if self.has_member("f_dentry"): + return self.f_dentry + elif self.has_member("f_path"): + return self.f_path.dentry + else: + raise AttributeError("Unable to find file -> dentry") + + def get_vfsmnt(self) -> interfaces.objects.ObjectInterface: + if self.has_member("f_vfsmnt"): + return self.f_vfsmnt + elif self.has_member("f_path"): + return self.f_path.mnt + else: + raise AttributeError("Unable to find file -> vfs mount") + + +class list_head(objects.StructType, collections.abc.Iterable): + + def to_list(self, + symbol_type: str, + member: str, + forward: bool = True, + sentinel: bool = True, + layer: Optional[str] = None) -> Iterator[interfaces.objects.ObjectInterface]: + """Returns an iterator of the entries in the list.""" + layer = layer or self.vol.layer_name + + relative_offset = self._context.symbol_space.get_type(symbol_type).relative_child_offset(member) + + direction = 'prev' + if forward: + direction = 'next' + try: + link = getattr(self, direction).dereference() + except exceptions.InvalidAddressException: + return + + if not sentinel: + yield self._context.object(symbol_type, layer, offset = self.vol.offset - relative_offset) + + seen = {self.vol.offset} + while link.vol.offset not in seen: + + obj = self._context.object(symbol_type, layer, offset = link.vol.offset - relative_offset) + yield obj + + seen.add(link.vol.offset) + try: + link = getattr(link, direction).dereference() + except exceptions.InvalidAddressException: + break + + def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: + return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) + + +class files_struct(objects.StructType): + + def get_fds(self) -> interfaces.objects.ObjectInterface: + if self.has_member("fdt"): + return self.fdt.fd.dereference() + elif self.has_member("fd"): + return self.fd.dereference() + else: + raise AttributeError("Unable to find files -> file descriptors") + + def get_max_fds(self) -> interfaces.objects.ObjectInterface: + if self.has_member("fdt"): + return self.fdt.max_fds + elif self.has_member("max_fds"): + return self.max_fds + else: + raise AttributeError("Unable to find files -> maximum file descriptors") + + +class mount(objects.StructType): + + def get_mnt_sb(self): + if self.has_member("mnt"): + return self.mnt.mnt_sb + elif self.has_member("mnt_sb"): + return self.mnt_sb + else: + raise AttributeError("Unable to find mount -> super block") + + def get_mnt_root(self): + if self.has_member("mnt"): + return self.mnt.mnt_root + elif self.has_member("mnt_root"): + return self.mnt_root + else: + raise AttributeError("Unable to find mount -> mount root") + + def get_mnt_flags(self): + if self.has_member("mnt"): + return self.mnt.mnt_flags + elif self.has_member("mnt_flags"): + return self.mnt_flags + else: + raise AttributeError("Unable to find mount -> mount flags") + + def get_mnt_parent(self): + return self.mnt_parent + + def get_mnt_mountpoint(self): + return self.mnt_mountpoint + + +class vfsmount(objects.StructType): + + def is_valid(self): + return self.get_mnt_sb() != 0 and \ + self.get_mnt_root() != 0 and \ + self.get_mnt_parent() != 0 + + def _get_real_mnt(self): + table_name = self.vol.type_name.split(constants.BANG)[0] + mount_struct = "{0}{1}mount".format(table_name, constants.BANG) + offset = self._context.symbol_space.get_type(mount_struct).relative_child_offset("mnt") + + return self._context.object(mount_struct, self.vol.layer_name, offset = self.vol.offset - offset) + + def get_mnt_parent(self): + if self.has_member("mnt_parent"): + return self.mnt_parent + else: + return self._get_real_mnt().mnt_parent + + def get_mnt_mountpoint(self): + if self.has_member("mnt_mountpoint"): + return self.mnt_mountpoint + else: + return self._get_real_mnt().mnt_mountpoint + + def get_mnt_root(self): + return self.mnt_root + + +class kobject(objects.StructType): + + def reference_count(self): + refcnt = self.kref.refcount + if self.has_member("counter"): + ret = refcnt.counter + else: + ret = refcnt.refs.counter + + return ret diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/bash.py b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/bash.py new file mode 100644 index 00000000..c583b67a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/bash.py @@ -0,0 +1,53 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import exceptions +from volatility.framework import objects +from volatility.framework.objects import utility +from volatility.framework.renderers import conversion + + +class hist_entry(objects.StructType): + + def is_valid(self): + try: + cmd = self.get_command() + ts = utility.array_to_string(self.timestamp.dereference()) + except exceptions.InvalidAddressException: + return False + + if not cmd or len(cmd) == 0: + return False + + if not ts or len(ts) == 0: + return False + + # At this point in time, the epoc integer size will + # never be less than 10 characters, and the stamp is + # always preceded by a pound/hash character. + if len(ts) < 10 or str(ts)[0] != "#": + return False + + # The final check is to make sure the entire string + # is composed of numbers. Try to convert to an int. + try: + int(str(ts)[1:]) + except ValueError: + return False + + return True + + def get_time_as_integer(self): + # Get the string and remove the leading "#" from the timestamp + time_string = utility.array_to_string(self.timestamp.dereference())[1:] + # Convert the string into an integer (number of seconds) + return int(time_string) + + def get_time_object(self): + nsecs = self.get_time_as_integer() + # Build a timestamp object from the integer + return conversion.unixtime_to_datetime(nsecs) + + def get_command(self): + return utility.array_to_string(self.line.dereference()) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/elf.py b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/elf.py new file mode 100644 index 00000000..505132ea --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/linux/extensions/elf.py @@ -0,0 +1,269 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Dict, Tuple + +from volatility.framework import constants +from volatility.framework import objects, interfaces + + +class elf(objects.StructType): + ''' + Class used to create elf objects. It overrides the typename to `Elf32_` or `Elf64_`, + depending on the corresponding value on e_ident + ''' + + def __init__(self, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, size: int, + members: Dict[str, Tuple[int, interfaces.objects.Template]]) -> None: + + super().__init__(context = context, + type_name = type_name, + object_info = object_info, + size = size, + members = members) + + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + # We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46 + magic = self._context.object(symbol_table_name + constants.BANG + "unsigned long", + layer_name = layer_name, + offset = object_info.offset) + + # Check validity + if magic != 0x464c457f: + return None + + # We need to read the EI_CLASS (0x4 offset) + ei_class = self._context.object(symbol_table_name + constants.BANG + "unsigned char", + layer_name = layer_name, + offset = object_info.offset + 0x4) + + if ei_class == 1: + self._type_prefix = "Elf32_" + elif ei_class == 2: + self._type_prefix = "Elf64_" + else: + raise ValueError("Unsupported ei_class value {}".format(ei_class)) + + # Construct the full header + self._hdr = self._context.object(symbol_table_name + constants.BANG + self._type_prefix + "Ehdr", + layer_name = layer_name, + offset = object_info.offset) + self._offset = object_info.offset + + self._cached_symtab = None + self._cached_strtab = None + + def is_valid(self): + ''' + Determine whether it is a valid object + ''' + return self._type_prefix is not None and self._hdr is not None + + def __getattr__(self, name): + # Just redirect to the corresponding header + if name[0:2] == "e_" and name in dir(self._hdr): + return self._hdr.__getattr__(name) + else: + return self.__getattribute__(name) + + def __dir__(self): + return self._hdr.__dir__() + [ + "get_program_headers", "is_valid", "get_section_headers", "get_symbols", "__dir__" + ] + + def get_program_headers(self): + program_headers = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = self._offset + self.e_phoff, + subtype = self._context.symbol_space.get_type(self.get_symbol_table_name() + constants.BANG + + self._type_prefix + "Phdr"), + count = self.e_phnum) + + for prog_header in program_headers: + prog_header.parent_e_type = self.e_type + prog_header.parent_offset = self._offset + prog_header.type_prefix = self._type_prefix + yield prog_header + + def get_section_headers(self): + section_headers = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = self._offset + self.e_shoff, + subtype = self._context.symbol_space.get_type(self.get_symbol_table_name() + constants.BANG + + self._type_prefix + "Shdr"), + count = self.e_shnum) + return section_headers + + def _find_symbols(self): + dt_strtab = None + dt_symtab = None + dt_strent = None + + for phdr in self.get_program_headers(): + try: + # Find PT_DYNAMIC segment + if str(phdr.p_type.description) != 'PT_DYNAMIC': + continue + except ValueError: + # If the p_type value is outside the ones declared in the enumeration, an + # exception is raised + return None + + # This section contains pointers to the strtab, symtab, and strent sections + for dsec in phdr.dynamic_sections(): + if dsec.d_tag == 5: + dt_strtab = dsec.d_ptr + + elif dsec.d_tag == 6: + dt_symtab = dsec.d_ptr + + elif dsec.d_tag == 11: + # Size of the symtab symbol entry + dt_strent = dsec.d_ptr + + break + + if dt_strtab is None or dt_symtab is None or dt_strent is None: + return None + + self._cached_symtab = dt_symtab + self._cached_strtab = dt_strtab + + # Calculate number of symbol entries assuming that strtab follows symtab + if dt_symtab < dt_strtab: + self._cached_numsyms = (dt_strtab - dt_symtab) // dt_strent + else: + self._cached_numsyms = 1024 + + def get_symbols(self): + if self._cached_symtab is None: + self._find_symbols() + + if self._cached_symtab is None: + return + + symtab_arr = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name = self.vol.layer_name, + offset = self._cached_symtab, + subtype = self._context.symbol_space.get_type(self.get_symbol_table_name() + constants.BANG + + self._type_prefix + "Sym"), + count = self._cached_numsyms) + + for sym in symtab_arr: + sym.cached_strtab = self._cached_strtab + yield sym + + +class elf_sym(objects.StructType): + """ An elf symbol entry""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._cached_strtab = None + + @property + def cached_strtab(self): + return self._cached_strtab + + @cached_strtab.setter + def cached_strtab(self, cached_strtab): + self._cached_strtab = cached_strtab + + def get_name(self): + addr = self._cached_strtab + self.st_name + + # Just get the first 255 characters, it should be enough for a symbol name + name_bytes = self._context.layers[self.vol.layer_name].read(addr, 255, pad = True) + + if name_bytes: + idx = name_bytes.find(b"\x00") + if idx != -1: + name_bytes = name_bytes[:idx] + return name_bytes.decode('utf-8', errors = 'ignore') + else: + # If we cannot read the name from the address space, + # we return None. + return None + + +class elf_phdr(objects.StructType): + """ An elf program header """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent_e_type = None + self._parent_offset = None + self._type_prefix = None + + @property + def parent_e_type(self): + return self._parent_e_type + + @parent_e_type.setter + def parent_e_type(self, e_type): + self._parent_e_type = e_type + + @property + def parent_offset(self): + return self._parent_offset + + @parent_offset.setter + def parent_offset(self, offset): + self._parent_offset = offset + + @property + def type_prefix(self): + return self._type_prefix + + @type_prefix.setter + def type_prefix(self, prefix): + self._type_prefix = prefix + + def get_vaddr(self): + offset = self.__getattr__("p_vaddr") + + if self._parent_e_type == 3: # ET_DYN + offset = self._parent_offset + offset + + return offset + + def dynamic_sections(self): + # sanity check + try: + if str(self.p_type.description) != 'PT_DYNAMIC': + return None + except ValueError: + # If the value is outside the ones declared in the enumeration, an + # exception is raised + return None + + # the buffer of array starts at elf_base + our virtual address ( offset ) + arr_start = self.get_vaddr() + + symbol_table_name = self.get_symbol_table_name() + + rtsize = self._context.symbol_space.get_type(symbol_table_name + \ + constants.BANG + \ + self._type_prefix + "Dyn").size + + for i in range(256): + # use the real size + idx = i * rtsize + + dyn = self._context.object(symbol_table_name + constants.BANG + self._type_prefix + "Dyn", + layer_name = self.vol.layer_name, + offset = arr_start + idx) + + yield dyn + + if dyn.d_tag == 0: + break + + +class_types = {'Elf': elf, 'Elf64_Phdr': elf_phdr, 'Elf32_Phdr': elf_phdr, 'Elf32_Sym': elf_sym, 'Elf64_Sym': elf_sym} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/mac/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/mac/__init__.py new file mode 100644 index 00000000..4343a38f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/mac/__init__.py @@ -0,0 +1,212 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +from typing import Iterator, Any, Iterable, List, Tuple, Set + +from volatility.framework import interfaces, objects, exceptions, constants +from volatility.framework.symbols import intermed +from volatility.framework.symbols.mac import extensions + + +class MacKernelIntermedSymbols(intermed.IntermediateSymbolTable): + provides = {"type": "interface"} + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.set_type_class('proc', extensions.proc) + self.set_type_class('fileglob', extensions.fileglob) + self.set_type_class('vnode', extensions.vnode) + self.set_type_class('vm_map_entry', extensions.vm_map_entry) + self.set_type_class('vm_map_object', extensions.vm_map_object) + self.set_type_class('socket', extensions.socket) + self.set_type_class('inpcb', extensions.inpcb) + self.set_type_class('queue_entry', extensions.queue_entry) + self.set_type_class('ifnet', extensions.ifnet) + self.set_type_class('sockaddr_dl', extensions.sockaddr_dl) + self.set_type_class('sockaddr', extensions.sockaddr) + self.set_type_class('sysctl_oid', extensions.sysctl_oid) + self.set_type_class('kauth_scope', extensions.kauth_scope) + + +class MacUtilities(interfaces.configuration.VersionableInterface): + """Class with multiple useful mac functions.""" + """ + Version History: + 1.1.0 -> added walk_list_head API + 1.2.0 -> added walk_slist API + """ + _version = (1, 2, 0) + + @classmethod + def mask_mods_list(cls, context: interfaces.context.ContextInterface, layer_name: str, + mods: Iterator[Any]) -> List[Tuple[interfaces.objects.ObjectInterface, Any, Any]]: + """ + A helper function to mask the starting and end address of kernel modules + """ + mask = context.layers[layer_name].address_mask + + return [(objects.utility.array_to_string(mod.name), mod.address & mask, (mod.address & mask) + mod.size) + for mod in mods] + + @classmethod + def generate_kernel_handler_info( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + kernel, # ikelos - how to type this?? + mods_list: Iterator[Any]): + + try: + start_addr = kernel.object_from_symbol("vm_kernel_stext") + except exceptions.SymbolError: + start_addr = kernel.object_from_symbol("stext") + + try: + end_addr = kernel.object_from_symbol("vm_kernel_etext") + except exceptions.SymbolError: + end_addr = kernel.object_from_symbol("etext") + + mask = context.layers[layer_name].address_mask + + start_addr = start_addr & mask + end_addr = end_addr & mask + + return [("__kernel__", start_addr, end_addr)] + \ + MacUtilities.mask_mods_list(context, layer_name, mods_list) + + @classmethod + def lookup_module_address(cls, context: interfaces.context.ContextInterface, handlers: Iterator[Any], + target_address): + mod_name = "UNKNOWN" + symbol_name = "N/A" + + for name, start, end in handlers: + if start <= target_address <= end: + mod_name = name + if name == "__kernel__": + symbols = list(context.symbol_space.get_symbols_by_location(target_address)) + + if len(symbols) > 0: + symbol_name = str(symbols[0].split(constants.BANG)[1]) if constants.BANG in symbols[0] else \ + str(symbols[0]) + + break + + return mod_name, symbol_name + + @classmethod + def files_descriptors_for_process(cls, context: interfaces.context.ContextInterface, symbol_table_name: str, + task: interfaces.objects.ObjectInterface): + """Creates a generator for the file descriptors of a process + + Args: + symbol_table_name: The name of the symbol table associated with the process + context: + task: The process structure to enumerate file descriptors from + + Return: + A 3 element tuple is yielded for each file descriptor: + 1) The file's object + 2) The path referenced by the descriptor. + The path is either empty, the full path of the file in the file system, or the formatted name for sockets, pipes, etc. + 3) The file descriptor number + """ + + try: + num_fds = task.p_fd.fd_lastfile + except exceptions.InvalidAddressException: + num_fds = 1024 + + try: + nfiles = task.p_fd.fd_nfiles + except exceptions.InvalidAddressException: + nfiles = 1024 + + if nfiles > num_fds: + num_fds = nfiles + + if num_fds > 4096: + num_fds = 1024 + + file_type = symbol_table_name + constants.BANG + 'fileproc' + + try: + table_addr = task.p_fd.fd_ofiles.dereference() + except exceptions.InvalidAddressException: + return + + fds = objects.utility.array_of_pointers(table_addr, count = num_fds, subtype = file_type, context = context) + + for fd_num, f in enumerate(fds): + if f != 0: + try: + ftype = f.f_fglob.get_fg_type() + except exceptions.InvalidAddressException: + continue + + if ftype == 'VNODE': + vnode = f.f_fglob.fg_data.dereference().cast("vnode") + path = vnode.full_path() + elif ftype: + path = "<{}>".format(ftype.lower()) + + yield f, path, fd_num + + @classmethod + def _walk_iterable(cls, + queue: interfaces.objects.ObjectInterface, + list_head_member: str, + list_next_member: str, + next_member: str, + max_elements: int = 4096) -> Iterable[interfaces.objects.ObjectInterface]: + seen = set() # type: Set[int] + + try: + current = queue.member(attr = list_head_member) + except exceptions.InvalidAddressException: + return + + while current: + if current.vol.offset in seen: + break + + seen.add(current.vol.offset) + + if len(seen) == max_elements: + break + + if current.is_readable(): + yield current + + try: + current = current.member(attr = next_member).member(attr = list_next_member) + except exceptions.InvalidAddressException: + break + + @classmethod + def walk_tailq(cls, + queue: interfaces.objects.ObjectInterface, + next_member: str, + max_elements: int = 4096) -> Iterable[interfaces.objects.ObjectInterface]: + + for element in cls._walk_iterable(queue, "tqh_first", "tqe_next", next_member, max_elements): + yield element + + @classmethod + def walk_list_head(cls, + queue: interfaces.objects.ObjectInterface, + next_member: str, + max_elements: int = 4096) -> Iterable[interfaces.objects.ObjectInterface]: + + for element in cls._walk_iterable(queue, "lh_first", "le_next", next_member, max_elements): + yield element + + @classmethod + def walk_slist(cls, + queue: interfaces.objects.ObjectInterface, + next_member: str, + max_elements: int = 4096) -> Iterable[interfaces.objects.ObjectInterface]: + + for element in cls._walk_iterable(queue, "slh_first", "sle_next", next_member, max_elements): + yield element diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/mac/extensions/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/mac/extensions/__init__.py new file mode 100644 index 00000000..787e7b56 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/mac/extensions/__init__.py @@ -0,0 +1,571 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Generator, Iterable, Optional, Set, Tuple + +from volatility.framework import constants, objects, renderers +from volatility.framework import exceptions, interfaces +from volatility.framework.objects import utility +from volatility.framework.renderers import conversion +from volatility.framework.symbols import generic + + +class proc(generic.GenericIntelProcess): + + def get_task(self): + return self.task.dereference().cast("task") + + def add_process_layer(self, config_prefix: str = None, preferred_name: str = None) -> Optional[str]: + """Constructs a new layer based on the process's DTB. + + Returns the name of the Layer or None. + """ + parent_layer = self._context.layers[self.vol.layer_name] + + if not isinstance(parent_layer, interfaces.layers.TranslationLayerInterface): + raise TypeError("Parent layer is not a translation layer, unable to construct process layer") + + try: + dtb = self.get_task().map.pmap.pm_cr3 + except exceptions.InvalidAddressException: + return None + + if preferred_name is None: + preferred_name = self.vol.layer_name + "_Process{}".format(self.p_pid) + + # Add the constructed layer and return the name + return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) + + def get_map_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: + try: + task = self.get_task() + except exceptions.InvalidAddressException: + return + + try: + current_map = task.map.hdr.links.next + except exceptions.InvalidAddressException: + return + + seen = set() # type: Set[int] + + for i in range(task.map.hdr.nentries): + if not current_map or current_map.vol.offset in seen: + break + + yield current_map + seen.add(current_map.vol.offset) + current_map = current_map.links.next + + ###### + # ikelos: this breaks with multi threading on, but works with it disabled + # with multi threading on, it throws that same error about v4 pickle stuff that linux originally did + # the fix for linux was to call int() so that we were not returning vol objects. + # I call int() on these and the code works nearly 1-1 with the linux one so I am very confused + ###### + def get_process_memory_sections(self, + context: interfaces.context.ContextInterface, + config_prefix: str, + rw_no_file: bool = False) -> \ + Generator[Tuple[int, int], None, None]: + """Returns a list of sections based on the memory manager's view of + this task's virtual memory.""" + for vma in self.get_map_iter(): + start = int(vma.links.start) + end = int(vma.links.end) + + if rw_no_file: + if vma.get_perms() != "rw" or vma.get_path(context, config_prefix) != "": + if vma.get_special_path() != "[heap]": + continue + + yield (start, end - start) + + +class fileglob(objects.StructType): + + def get_fg_type(self): + ret = None + + if self.has_member("fg_type"): + ret = self.fg_type + elif self.fg_ops != 0: + try: + ret = self.fg_ops.fo_type + except exceptions.InvalidAddressException: + pass + + if ret: + ret = str(ret.description).replace("DTYPE_", "") + + return ret + + +class vm_map_object(objects.StructType): + + def get_map_object(self): + if self.has_member("vm_object"): + return self.vm_object + elif self.has_member("vmo_object"): + return self.vmo_object + + raise AttributeError("vm_map_object -> get_object") + + +class vnode(objects.StructType): + + def _do_calc_path(self, ret, vnodeobj, vname): + if vnodeobj is None: + return + + if vname: + ret.append(utility.pointer_to_string(vname, 255)) + + if int(vnodeobj.v_flag) & 0x000001 != 0 and int(vnodeobj.v_mount) != 0: + if int(vnodeobj.v_mount.mnt_vnodecovered) != 0: + self._do_calc_path(ret, vnodeobj.v_mount.mnt_vnodecovered, vnodeobj.v_mount.mnt_vnodecovered.v_name) + else: + try: + parent = vnodeobj.v_parent + parent_name = parent.v_name + except exceptions.InvalidAddressException: + return + + self._do_calc_path(ret, parent, parent_name) + + def full_path(self): + if self.v_flag & 0x000001 != 0 and self.v_mount != 0 and self.v_mount.mnt_flag & 0x00004000 != 0: + ret = b"/" + else: + elements = [] + files = [] + + self._do_calc_path(elements, self, self.v_name) + elements.reverse() + + for e in elements: + files.append(e.encode("utf-8")) + + ret = b"/".join(files) + if ret: + ret = b"/" + ret + + return ret.decode("utf-8") + + +class vm_map_entry(objects.StructType): + + def is_suspicious(self, context, config_prefix): + """Flags memory regions that are mapped rwx or that map an executable + not back from a file on disk.""" + ret = False + + perms = self.get_perms() + + if perms == "rwx": + ret = True + + elif perms == "r-x" and self.get_path(context, config_prefix) == "": + ret = True + + return ret + + def get_perms(self): + permask = "rwx" + perms = "" + + for (ctr, i) in enumerate([1, 3, 5]): + if (self.protection & i) == i: + perms = perms + permask[ctr] + else: + perms = perms + "-" + + return perms + + def get_range_alias(self): + if self.has_member("alias"): + ret = int(self.alias) + else: + ret = int(self.vme_offset) & 0xfff + + return ret + + def get_special_path(self): + check = self.get_range_alias() + + if 0 < check < 10: + ret = "[heap]" + elif check == 30: + ret = "[stack]" + else: + ret = "" + + return ret + + def get_path(self, context, config_prefix): + node = self.get_vnode(context, config_prefix) + + if type(node) == str and node == "sub_map": + ret = node + elif node: + path = [] + while node: + v_name = utility.pointer_to_string(node.v_name, 255) + path.append(v_name) + node = node.v_parent + path.reverse() + ret = "/" + "/".join(path) + else: + ret = "" + + return ret + + def get_object(self): + if self.has_member("vme_object"): + return self.vme_object + elif self.has_member("object"): + return self.object + + raise AttributeError("vm_map_entry -> get_object: Unable to determine object") + + def get_offset(self): + if self.has_member("vme_offset"): + return self.vme_offset + elif self.has_member("offset"): + return self.offset + + raise AttributeError("vm_map_entry -> get_offset: Unable to determine offset") + + def get_vnode(self, context, config_prefix): + if self.is_sub_map == 1: + return "sub_map" + + # based on find_vnode_object + vnode_object = self.get_object().get_map_object() + + found_end = False + + while not found_end: + try: + tmp_vnode_object = vnode_object.shadow.dereference() + except exceptions.InvalidAddressException: + break + + if tmp_vnode_object.vol.offset == 0: + found_end = True + else: + vnode_object = tmp_vnode_object + + try: + ops = vnode_object.pager.mo_pager_ops.dereference() + except exceptions.InvalidAddressException: + return None + + found = False + for sym in context.symbol_space.get_symbols_by_location(ops.vol.offset): + if sym.split(constants.BANG)[1] in ["vnode_pager_ops", "_vnode_pager_ops"]: + found = True + break + + if found: + vpager = context.object(config_prefix + constants.BANG + "vnode_pager", + layer_name = vnode_object.vol.layer_name, + offset = vnode_object.pager) + ret = vpager.vnode_handle + else: + ret = None + + return ret + + +class socket(objects.StructType): + + def get_inpcb(self): + try: + ret = self.so_pcb.dereference().cast("inpcb") + except exceptions.InvalidAddressException: + ret = None + + return ret + + def get_family(self): + return self.so_proto.pr_domain.dom_family + + def get_protocol_as_string(self): + proto = self.so_proto.pr_protocol + + if proto == 6: + ret = "TCP" + elif proto == 17: + ret = "UDP" + else: + ret = "" + + return ret + + def get_state(self): + ret = "" + + if self.so_proto.pr_protocol == 6: + inpcb = self.get_inpcb() + if inpcb is not None: + ret = inpcb.get_tcp_state() + + return ret + + def get_connection_info(self): + inpcb = self.get_inpcb() + + if inpcb is None: + ret = None + elif self.get_family() == 2: + ret = inpcb.get_ipv4_info() + else: + ret = inpcb.get_ipv6_info() + + return ret + + def get_converted_connection_info(self): + vals = self.get_connection_info() + + if vals: + ret = conversion.convert_network_four_tuple(self.get_family(), vals) + else: + ret = None + + return ret + + +class inpcb(objects.StructType): + + def get_tcp_state(self): + tcp_states = ("CLOSED", "LISTEN", "SYN_SENT", "SYN_RECV", "ESTABLISHED", "CLOSE_WAIT", "FIN_WAIT1", "CLOSING", + "LAST_ACK", "FIN_WAIT2", "TIME_WAIT") + + try: + tcpcb = self.inp_ppcb.dereference().cast("tcpcb") + except exceptions.InvalidAddressException: + return "" + + state_type = tcpcb.t_state + if state_type and state_type < len(tcp_states): + state = tcp_states[state_type] + else: + state = "" + + return state + + def get_ipv4_info(self): + try: + lip = self.inp_dependladdr.inp46_local.ia46_addr4.s_addr + except exceptions.InvalidAddressException: + return None + + lport = self.inp_lport + + try: + rip = self.inp_dependfaddr.inp46_foreign.ia46_addr4.s_addr + except exceptions.InvalidAddressException: + return None + + rport = self.inp_fport + + return [lip, lport, rip, rport] + + def get_ipv6_info(self): + try: + lip = self.inp_dependladdr.inp6_local.member(attr = '__u6_addr').member(attr = '__u6_addr32') + except exceptions.InvalidAddressException: + return None + + lport = self.inp_lport + + try: + rip = self.inp_dependfaddr.inp6_foreign.member(attr = '__u6_addr').member(attr = '__u6_addr32') + except exceptions.InvalidAddressException: + return None + + rport = self.inp_fport + + return [lip, lport, rip, rport] + + +class queue_entry(objects.StructType): + + def walk_list(self, + list_head: interfaces.objects.ObjectInterface, + member_name: str, + type_name: str, + max_size: int = 4096) -> Iterable[interfaces.objects.ObjectInterface]: + """ + Walks a queue in a smear-aware and smear-resistant manner + + smear is detected by: + - the max_size parameter sets an upper bound + - each seen entry is only allowed once + + attempts to work around smear: + - the list is walked in both directions to help find as many elements as possible + + Args: + list_head - the head of the list + member_name - the name of the embedded list member + type_name - the type of each element in the list + max_size - the maximum amount of elements that will be returned + + Returns: + Each instance of the queue cast as "type_name" type + """ + + yielded = 0 + + seen = set() + + for attr in ['next', 'prev']: + try: + n = getattr(self, attr).dereference().cast(type_name) + + while n is not None and n.vol.offset != list_head: + if n.vol.offset in seen: + break + + yield n + + seen.add(n.vol.offset) + + yielded = yielded + 1 + if yielded == max_size: + return + + n = getattr(n.member(attr = member_name), attr).dereference().cast(type_name) + + except exceptions.InvalidAddressException: + pass + + +class ifnet(objects.StructType): + + def sockaddr_dl(self): + if self.has_member("if_lladdr"): + try: + val = self.if_lladdr.ifa_addr.dereference().cast("sockaddr_dl") + except exceptions.InvalidAddressException: + val = None + else: + try: + val = self.if_addrhead.tqh_first.ifa_addr.dereference().cast("sockaddr_dl") + except exceptions.InvalidAddressException: + val = None + + return val + + +# this is used for MAC addresses +class sockaddr_dl(objects.StructType): + + def __str__(self): + ret = "" + + if self.sdl_alen > 14: + return ret + + for i in range(self.sdl_alen): + try: + e = self.sdl_data[self.sdl_nlen + i] + except IndexError: + break + + e = e.cast("unsigned char") + + ret = ret + "{:02X}:".format(e) + + if ret and ret[-1] == ":": + ret = ret[:-1] + + return ret + + +class sockaddr(objects.StructType): + + def get_address(self): + ip = "" + + family = self.sa_family + if family == 2: # AF_INET + addr_in = self.cast("sockaddr_in") + ip = conversion.convert_ipv4(addr_in.sin_addr.s_addr) + + elif family == 30: # AF_INET6 + addr_in6 = self.cast("sockaddr_in6") + ip = conversion.convert_ipv6(addr_in6.sin6_addr.member(attr = "__u6_addr").member(attr = "__u6_addr32")) + + elif family == 18: # AF_LINK + addr_dl = self.cast("sockaddr_dl") + ip = str(addr_dl) + + return ip + + +class sysctl_oid(objects.StructType): + + def get_perms(self) -> str: + """ + Returns the actions allowed on the node + + Args: None + + Returns: + A combination of: + R - readable + W - writeable + L - self handles locking + """ + ret = "" + + checks = [0x80000000, 0x40000000, 0x00800000] + perms = ["R", "W", "L"] + + for (i, c) in enumerate(checks): + if c & self.oid_kind: + ret = ret + perms[i] + else: + ret = ret + "-" + + return ret + + def get_ctltype(self) -> str: + """ + Returns the type of the sysctl node + + Args: None + + Returns: + One of: + CTLTYPE_NODE + CTLTYPE_INT + CTLTYPE_STRING + CTLTYPE_QUAD + CTLTYPE_OPAQUE + an empty string for nodes not in the above types + + Based on sysctl_sysctl_debug_dump_node + """ + types = {1: 'CTLTYPE_NODE', 2: 'CTLTYPE_INT', 3: 'CTLTYPE_STRING', 4: 'CTLTYPE_QUAD', 5: 'CTLTYPE_OPAQUE'} + + ctltype = self.oid_kind & 0xf + + if 0 < ctltype < 6: + ret = types[ctltype] + else: + ret = "" + + return ret + + +class kauth_scope(objects.StructType): + + def get_listeners(self): + for listener in self.ks_listeners: + if listener != 0 and listener.kll_callback != 0: + yield listener diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/metadata.py b/app/parsers/vol_Parser/volatility/framework/symbols/metadata.py new file mode 100644 index 00000000..444fb648 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/metadata.py @@ -0,0 +1,41 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Optional, Tuple + +from volatility.framework import interfaces + + +class WindowsMetadata(interfaces.symbols.MetadataInterface): + """Class to handle the metadata from a Windows symbol table.""" + + @property + def pe_version(self) -> Optional[Tuple]: + build = self._json_data.get('pe', {}).get('build', None) + revision = self._json_data.get('pe', {}).get('revision', None) + minor = self._json_data.get('pe', {}).get('minor', None) + major = self._json_data.get('pe', {}).get('major', None) + if revision is None or minor is None or major is None: + return None + if build is None: + return major, minor, revision + return major, minor, revision, build + + @property + def pe_version_string(self) -> Optional[str]: + if self.pe_version is None: + return None + return ".".join(self.pe_version) + + @property + def pdb_guid(self) -> Optional[str]: + return self._json_data.get('pdb', {}).get('GUID', None) + + @property + def pdb_age(self) -> Optional[int]: + return self._json_data.get('pdb', {}).get('age', None) + + +class LinuxMetadata(interfaces.symbols.MetadataInterface): + """Class to handle the etadata from a Linux symbol table.""" diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/native.py b/app/parsers/vol_Parser/volatility/framework/symbols/native.py new file mode 100644 index 00000000..26d12bd7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/native.py @@ -0,0 +1,106 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import copy +from typing import Any, Dict, Iterable, Optional, Type + +from volatility.framework import constants, interfaces, objects + + +class NativeTable(interfaces.symbols.NativeTableInterface): + """Symbol List that handles Native types.""" + + # FIXME: typing the native_dictionary as Tuple[interfaces.objects.ObjectInterface, str] throws many errors + def __init__(self, name: str, native_dictionary: Dict[str, Any]) -> None: + super().__init__(name, self) + self._native_dictionary = copy.deepcopy(native_dictionary) + self._overrides = {} # type: Dict[str, interfaces.objects.ObjectInterface] + for native_type in self._native_dictionary: + native_class, _native_struct = self._native_dictionary[native_type] + self._overrides[native_type] = native_class + # Create this once early, because it may get used a lot + self._types = set(self._native_dictionary).union( + {'enum', 'array', 'bitfield', 'void', 'string', 'bytes', 'function'}) + + def get_type_class(self, name: str) -> Type[interfaces.objects.ObjectInterface]: + ntype, _ = self._native_dictionary.get(name, (objects.Integer, None)) + return ntype + + @property + def types(self) -> Iterable[str]: + """Returns an iterator of the symbol type names.""" + return self._types + + def get_type(self, type_name: str) -> interfaces.objects.Template: + """Resolves a symbol name into an object template. + + This always construct a new python object, rather than using a + cached value otherwise changes made later may affect the cached + copy. Calling clone after every native type construction was + extremely slow. + """ + # NOTE: These need updating whenever the object init signatures change + prefix = "" + if constants.BANG in type_name: + name_split = type_name.split(constants.BANG) + if len(name_split) > 2: + raise ValueError("SymbolName cannot contain multiple {} separators".format(constants.BANG)) + table_name, type_name = name_split + prefix = table_name + constants.BANG + + additional = {} # type: Dict[str, Any] + obj = None # type: Optional[Type[interfaces.objects.ObjectInterface]] + if type_name == 'void' or type_name == 'function': + obj = objects.Void + elif type_name == 'array': + obj = objects.Array + additional = {"count": 0, "subtype": self.get_type('void')} + elif type_name == 'enum': + obj = objects.Enumeration + additional = {"base_type": self.get_type('void'), "choices": {}} + elif type_name == 'bitfield': + obj = objects.BitField + additional = {"start_bit": 0, "end_bit": 0, "base_type": self.get_type('void')} + elif type_name == 'string': + obj = objects.String + additional = {"max_length": 0} + elif type_name == 'bytes': + obj = objects.Bytes + additional = {"length": 0} + if obj is not None: + return objects.templates.ObjectTemplate(obj, type_name = prefix + type_name, **additional) + + _native_type, native_format = self._native_dictionary[type_name] + if type_name == 'pointer': + additional = {'subtype': self.get_type('void')} + return objects.templates.ObjectTemplate( + self.get_type_class(type_name), # pylint: disable=W0142 + type_name = prefix + type_name, + data_format = objects.DataFormatInfo(*native_format), + **additional) + + +std_ctypes = { + 'int': (objects.Integer, (4, "little", True)), + 'long': (objects.Integer, (4, "little", True)), + 'unsigned long': (objects.Integer, (4, "little", False)), + 'unsigned int': (objects.Integer, (4, "little", False)), + 'char': (objects.Integer, (1, "little", True)), + 'byte': (objects.Bytes, (1, "little", True)), + 'unsigned char': (objects.Integer, (1, "little", False)), + 'unsigned short int': (objects.Integer, (2, "little", False)), + 'unsigned short': (objects.Integer, (2, "little", False)), + 'unsigned be short': (objects.Integer, (2, "big", False)), + 'short': (objects.Integer, (2, "little", True)), + 'long long': (objects.Integer, (8, "little", True)), + 'unsigned long long': (objects.Integer, (8, "little", True)), + 'float': (objects.Float, (4, "little", True)), + 'double': (objects.Float, (8, "little", True)), + 'wchar': (objects.Integer, (2, "little", False)) +} +native_types = std_ctypes.copy() +native_types['pointer'] = (objects.Pointer, (4, "little", False)) +x86NativeTable = NativeTable("native", native_types) +native_types['pointer'] = (objects.Pointer, (8, "little", False)) +x64NativeTable = NativeTable("native", native_types) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/__init__.py new file mode 100644 index 00000000..42cd0bf7 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/__init__.py @@ -0,0 +1,64 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import volatility.framework.symbols.windows.extensions.pool +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import extensions +from volatility.framework.symbols.windows.extensions import registry, pool + + +class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + # Set-up windows specific types + self.set_type_class('_ETHREAD', extensions.ETHREAD) + self.set_type_class('_KTHREAD', extensions.KTHREAD) + self.set_type_class('_LIST_ENTRY', extensions.LIST_ENTRY) + self.set_type_class('_EPROCESS', extensions.EPROCESS) + self.set_type_class('_UNICODE_STRING', extensions.UNICODE_STRING) + self.set_type_class('_EX_FAST_REF', extensions.EX_FAST_REF) + self.set_type_class('_TOKEN', extensions.TOKEN) + self.set_type_class('_OBJECT_HEADER', pool.OBJECT_HEADER) + self.set_type_class('_FILE_OBJECT', extensions.FILE_OBJECT) + self.set_type_class('_DEVICE_OBJECT', extensions.DEVICE_OBJECT) + self.set_type_class('_CM_KEY_BODY', registry.CM_KEY_BODY) + self.set_type_class('_CMHIVE', registry.CMHIVE) + self.set_type_class('_CM_KEY_NODE', registry.CM_KEY_NODE) + self.set_type_class('_CM_KEY_VALUE', registry.CM_KEY_VALUE) + self.set_type_class('_HMAP_ENTRY', registry.HMAP_ENTRY) + self.set_type_class('_MMVAD_SHORT', extensions.MMVAD_SHORT) + self.set_type_class('_MMVAD', extensions.MMVAD) + self.set_type_class('_KSYSTEM_TIME', extensions.KSYSTEM_TIME) + self.set_type_class('_KMUTANT', extensions.KMUTANT) + self.set_type_class('_DRIVER_OBJECT', extensions.DRIVER_OBJECT) + self.set_type_class('_OBJECT_SYMBOLIC_LINK', extensions.OBJECT_SYMBOLIC_LINK) + self.set_type_class('_POOL_TRACKER_BIG_PAGES', pool.POOL_TRACKER_BIG_PAGES) + + # This doesn't exist in very specific versions of windows + try: + if self.get_type("_POOL_TRACKER_BIG_PAGES").has_member("PoolType"): + self.set_type_class('_POOL_HEADER', pool.POOL_HEADER_VISTA) + else: + self.set_type_class('_POOL_HEADER', pool.POOL_HEADER) + except ValueError: + pass + + # these don't exist in windows XP + try: + self.set_type_class('_MMADDRESS_NODE', extensions.MMVAD_SHORT) + except ValueError: + pass + + # these were introduced starting in windows 8 + try: + self.set_type_class('_MM_AVL_NODE', extensions.MMVAD_SHORT) + except ValueError: + pass + + # these were introduced starting in windows 7 + try: + self.set_type_class('_RTL_BALANCED_NODE', extensions.MMVAD_SHORT) + except ValueError: + pass diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x64.json new file mode 100644 index 00000000..da018a8b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x64.json @@ -0,0 +1,88 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Key": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PoolType": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfBytes": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 24 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPoolBase": 0, + "PagedPool": 1, + "NonPagedPoolBaseMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolBaseCacheAligned": 4, + "PagedPoolCacheAligned": 5, + "NonPagedPoolBaseCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "NonPagedPoolNx": 512, + "NonPagedPoolSessionNx": 544, + "NonPagedPoolNxCacheAligned": 516, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x86.json new file mode 100644 index 00000000..c7fdbb41 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-vista-x86.json @@ -0,0 +1,82 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Key": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PoolType": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfBytes": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPoolBase": 0, + "PagedPool": 1, + "NonPagedPoolBaseMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolBaseCacheAligned": 4, + "PagedPoolCacheAligned": 5, + "NonPagedPoolBaseCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "NonPagedPoolNx": 512, + "NonPagedPoolSessionNx": 544, + "NonPagedPoolNxCacheAligned": 516, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x64.json new file mode 100644 index 00000000..2556daf0 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x64.json @@ -0,0 +1,123 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Key": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Pattern": { + "offset": 12, + "type": { + "bit_length": 8, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "PoolType": { + "offset": 12, + "type": { + "bit_length": 8, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "enum", + "name": "_POOL_TYPE" + } + } + }, + "SlushSize": { + "offset": 12, + "type": { + "bit_length": 12, + "bit_position": 20, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "NumberOfBytes": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 24 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPoolBase": 0, + "PagedPool": 1, + "NonPagedPoolBaseMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolBaseCacheAligned": 4, + "PagedPoolCacheAligned": 5, + "NonPagedPoolBaseCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "NonPagedPoolNx": 512, + "NonPagedPoolSessionNx": 544, + "NonPagedPoolNxCacheAligned": 516, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x86.json new file mode 100644 index 00000000..1988e2b5 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-win10-x86.json @@ -0,0 +1,111 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Key": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Pattern": { + "offset": 8, + "type": { + "bit_length": 8, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "PoolType": { + "offset": 8, + "type": { + "bit_length": 8, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "enum", + "name": "_POOL_TYPE" + } + } + }, + "SlushSize": { + "offset": 8, + "type": { + "bit_length": 12, + "bit_position": 20, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "NumberOfBytes": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPoolBase": 0, + "PagedPool": 1, + "NonPagedPoolBaseMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolBaseCacheAligned": 4, + "PagedPoolCacheAligned": 5, + "NonPagedPoolBaseCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "NonPagedPoolNx": 512, + "NonPagedPoolSessionNx": 544, + "NonPagedPoolNxCacheAligned": 516, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x64.json new file mode 100644 index 00000000..f2be0d43 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x64.json @@ -0,0 +1,71 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Key": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPool": 0, + "PagedPool": 1, + "NonPagedPoolMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolCacheAligned": 4, + "PagedPoolAligned": 5, + "NonPagedPoolCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x86.json new file mode 100644 index 00000000..87d46bf0 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/bigpools-x86.json @@ -0,0 +1,65 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "dlassalle-by-hand", + "datetime": "2020-04-30T14:30:00.000000" + }, + "format": "6.2.0" + }, + "user_types": { + "_POOL_TRACKER_BIG_PAGES": { + "fields": { + "Va": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Key": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + } + }, + "symbols": { + }, + "enums": { + "_POOL_TYPE": { + "base": "unsigned long", + "constants": { + "NonPagedPool": 0, + "PagedPool": 1, + "NonPagedPoolMustSucceed": 2, + "DontUseThisType": 3, + "NonPagedPoolCacheAligned": 4, + "PagedPoolAligned": 5, + "NonPagedPoolCacheAlignedMustS": 6, + "MaxPoolType": 7, + "NonPagedPoolMustSucceedSession": 34, + "DontUseThisTypeSession": 35, + "NonPagedPoolSession": 32, + "PagedPoolSession": 33, + "NonPagedPoolCacheAlignedMustSSession": 38, + "PagedPoolCacheAlignedSession": 37, + "NonPagedPoolCacheAlignedSession": 36 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x64.json new file mode 100644 index 00000000..dbb6086d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x64.json @@ -0,0 +1,150 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_GENERIC_CALLBACK": { + "fields": { + "Callback": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + }, + "_KBUGCHECK_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 64 + }, + "_KBUGCHECK_REASON_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 64 + }, + "_EX_CALLBACK_ROUTINE_BLOCK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 64 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "mhl by hand", + "datetime": "2019-08-27T18:17:16.417006" + }, + "format": "4.0.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x86.json new file mode 100644 index 00000000..cf0cb8b6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/callbacks-x86.json @@ -0,0 +1,150 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_GENERIC_CALLBACK": { + "fields": { + "Callback": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 8 + }, + "_KBUGCHECK_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 32 + }, + "_KBUGCHECK_REASON_CALLBACK_RECORD": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + }, + "Component": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 28 + }, + "_EX_CALLBACK_ROUTINE_BLOCK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 4 + } + }, + "kind": "struct", + "size": 28 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "mhl by hand", + "datetime": "2019-08-27T18:17:16.417006" + }, + "format": "4.0.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash.json new file mode 100644 index 00000000..026a0c9c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash.json @@ -0,0 +1,418 @@ +{ + "symbols": { + }, + "user_types": { + "_DUMP_HEADER": { + "fields": { + "Signature": { + "offset": 0, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "ValidDump": { + "offset": 4, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "MajorVersion": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MinorVersion": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "DirectoryTableBase": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PfnDataBase": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PsLoadedModuleList": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PsActiveProcessHead": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MachineImageType": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberProcessors": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BugCheckCode": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BugCheckCodeParameter": { + "offset": 44, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "VersionUser": { + "offset": 60, + "type": { + "count": 32, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "PaeEnabled": { + "offset": 92, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "KdSecondaryVersion": { + "offset": 93, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "VersionUser2": { + "offset": 94, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "KdDebuggerDataBlock": { + "offset": 96, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PhysicalMemoryBlockBuffer": { + "offset": 100, + "type": { + "kind": "struct", + "name": "_PHYSICAL_MEMORY_DESCRIPTOR" + } + }, + "ContextRecord": { + "offset": 800, + "type": { + "count": 1200, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "Exception": { + "offset": 2000, + "type": { + "kind": "struct", + "name": "_EXCEPTION_RECORD32" + } + }, + "Comment": { + "offset": 2080, + "type": { + "count": 128, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "DumpType": { + "offset": 3976, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MiniDumpFields": { + "offset": 3980, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SecondaryDataState": { + "offset": 3984, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProductType": { + "offset": 3988, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SuiteMask": { + "offset": 3992, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WriterStatus": { + "offset": 3996, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "RequiredDumpSpace": { + "offset": 4000, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "SystemUpTime": { + "offset": 4024, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "SystemTime": { + "offset": 4032, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "reserved3": { + "offset": 4040, + "type": { + "count": 56, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + } + }, + "kind": "struct", + "size": 4096 + }, + "_EXCEPTION_RECORD32": { + "fields": { + "ExceptionCode": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "ExceptionFlags": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExceptionRecord": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExceptionAddress": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberParameters": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExceptionInformation": { + "offset": 20, + "type": { + "count": 15, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + } + }, + "kind": "struct", + "size": 80 + }, + "_PHYSICAL_MEMORY_DESCRIPTOR": { + "fields": { + "NumberOfPages": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfRuns": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Run": { + "offset": 8, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_PHYSICAL_MEMORY_RUN" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_PHYSICAL_MEMORY_RUN": { + "fields": { + "BasePage": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PageCount": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + } + }, + "enums": { + }, + "base_types": { + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "little", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.2", + "name": "awalters-by-hand", + "datetime": "2018-05-01T16:30:00" + }, + "format": "4.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash64.json new file mode 100644 index 00000000..3a713463 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/crash64.json @@ -0,0 +1,481 @@ +{ + "symbols": { + }, + "user_types": { + "_DUMP_HEADER64": { + "fields": { + "Signature": { + "offset": 0, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "ValidDump": { + "offset": 4, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "MajorVersion": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MinorVersion": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "DirectoryTableBase": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PfnDataBase": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PsLoadedModuleList": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PsActiveProcessHead": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MachineImageType": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberProcessors": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BugCheckCode": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BugCheckCodeParameter": { + "offset": 64, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "VersionUser": { + "offset": 96, + "type": { + "count": 32, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "KdDebuggerDataBlock": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PhysicalMemoryBlockBuffer": { + "offset": 136, + "type": { + "kind": "struct", + "name": "_PHYSICAL_MEMORY_DESCRIPTOR64" + } + }, + "ContextRecord": { + "offset": 840, + "type": { + "count": 3000, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "Exception": { + "offset": 3840, + "type": { + "kind": "struct", + "name": "_EXCEPTION_RECORD64" + } + }, + "DumpType": { + "offset": 3992, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "RequiredDumpSpace": { + "offset": 4000, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "SystemUpTime": { + "offset": 4008, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Comment": { + "offset": 4016, + "type": { + "count": 128, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "SystemTime": { + "offset": 4144, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "MiniDumpFields": { + "offset": 4152, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SecondaryDataState": { + "offset": 4156, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ProductType": { + "offset": 4160, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SuiteMask": { + "offset": 4164, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "WriterStatus": { + "offset": 4168, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Unused1": { + "offset": 4172, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "KdSecondaryVersion": { + "offset": 4173, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "Unused2": { + "offset": 4174, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 8192 + }, + "_SUMMARY_DUMP64": { + "fields": { + "Signature": { + "offset": 0, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "ValidDump": { + "offset": 4, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "DumpOptions": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "HeaderSize": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "BitmapSize": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "Pages": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "BufferLong": { + "offset": 56, + "type": { + "kind": "array", + "count": 1, + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "BufferChar": { + "offset": 56, + "type": { + "kind": "array", + "count": 1, + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + } + }, + "kind": "struct", + "size": 56 + }, + "_EXCEPTION_RECORD64": { + "fields": { + "ExceptionCode": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "ExceptionFlags": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExceptionRecord": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "ExceptionAddress": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "NumberParameters": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ExceptionInformation": { + "offset": 32, + "type": { + "count": 15, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long long" + } + } + } + }, + "kind": "struct", + "size": 152 + }, + "_PHYSICAL_MEMORY_DESCRIPTOR64": { + "fields": { + "NumberOfPages": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumberOfRuns": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Run": { + "offset": 16, + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_PHYSICAL_MEMORY_RUN64" + } + } + } + }, + "kind": "struct", + "size": 20 + }, + "_PHYSICAL_MEMORY_RUN64": { + "fields": { + "BasePage": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long long" + } + }, + "PageCount": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long long" + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "enums": { + }, + "base_types": { + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "little", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "ikelos-by-hand", + "datetime": "2020-09-10T00:20:00" + }, + "format": "6.2.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/__init__.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/__init__.py new file mode 100644 index 00000000..34cbae0d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/__init__.py @@ -0,0 +1,875 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import collections.abc +import datetime +import functools +import logging +from typing import Iterable, Iterator, Optional, Union + +from volatility.framework import constants, exceptions, interfaces, objects, renderers, symbols +from volatility.framework.layers import intel +from volatility.framework.renderers import conversion +from volatility.framework.symbols import generic +from volatility.framework.symbols.windows.extensions import pool, pe, kdbg + +vollog = logging.getLogger(__name__) + + +# Keep these in a basic module, to prevent import cycles when symbol providers require them + + +class KSYSTEM_TIME(objects.StructType): + """A system time structure that stores a high and low part.""" + + def get_time(self): + wintime = (self.High1Time << 32) | self.LowPart + return conversion.wintime_to_datetime(wintime) + + +class MMVAD_SHORT(objects.StructType): + """A class that represents process virtual memory ranges. + Each instance is a node in a binary tree structure and is pointed to + by VadRoot. + """ + + @functools.lru_cache(maxsize = None) + def get_tag(self): + vad_address = self.vol.offset + + # the offset is different on 32 and 64 bits + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + if not symbols.symbol_table_is_64bit(self._context, symbol_table_name): + vad_address -= 4 + else: + vad_address -= 12 + + try: + # TODO: instantiate a _POOL_HEADER and return PoolTag + bytesobj = self._context.object(symbol_table_name + constants.BANG + "bytes", + layer_name = self.vol.layer_name, + offset = vad_address, + native_layer_name = self.vol.native_layer_name, + length = 4) + + return bytesobj.decode() + except exceptions.InvalidAddressException: + return None + except UnicodeDecodeError: + return None + + def traverse(self, visited = None, depth = 0): + """Traverse the VAD tree, determining each underlying VAD node type by + looking up the pool tag for the structure and then casting into a new + object.""" + + # TODO: this is an arbitrary limit chosen based on past observations + if depth > 100: + vollog.log(constants.LOGLEVEL_VVV, "Vad tree is too deep, something went wrong!") + raise RuntimeError("Vad tree is too deep") + + if visited is None: + visited = set() + + vad_address = self.vol.offset + + if vad_address in visited: + vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!") + return + + visited.add(vad_address) + tag = self.get_tag() + + if tag in ["VadS", "VadF"]: + target = "_MMVAD_SHORT" + elif tag != None and tag.startswith("Vad"): + target = "_MMVAD" + elif depth == 0: + # the root node at depth 0 is allowed to not have a tag + # but we still want to continue and access its right & left child + target = None + else: + # any node other than the root that doesn't have a recognized tag + # is just garbage and we skip the node entirely + vollog.log(constants.LOGLEVEL_VVV, + "Skipping VAD at {} depth {} with tag {}".format(self.vol.offset, depth, tag)) + return + + if target: + vad_object = self.cast(target) + yield vad_object + + try: + for vad_node in self.get_left_child().dereference().traverse(visited, depth + 1): + yield vad_node + except exceptions.InvalidAddressException as excp: + vollog.log(constants.LOGLEVEL_VVV, "Invalid address on LeftChild: {0:#x}".format(excp.invalid_address)) + + try: + for vad_node in self.get_right_child().dereference().traverse(visited, depth + 1): + yield vad_node + except exceptions.InvalidAddressException as excp: + vollog.log(constants.LOGLEVEL_VVV, "Invalid address on RightChild: {0:#x}".format(excp.invalid_address)) + + def get_right_child(self): + """Get the right child member.""" + + if self.has_member("RightChild"): + return self.RightChild + + elif self.has_member("Right"): + return self.Right + + # this is for windows 8 and 10 + elif self.has_member("VadNode"): + if self.VadNode.has_member("RightChild"): + return self.VadNode.RightChild + if self.VadNode.has_member("Right"): + return self.VadNode.Right + + # also for windows 8 and 10 + elif self.has_member("Core"): + if self.Core.has_member("VadNode"): + if self.Core.VadNode.has_member("RightChild"): + return self.Core.VadNode.RightChild + if self.Core.VadNode.has_member("Right"): + return self.Core.VadNode.Right + + raise AttributeError("Unable to find the right child member") + + def get_left_child(self): + """Get the left child member.""" + + if self.has_member("LeftChild"): + return self.LeftChild + + elif self.has_member("Left"): + return self.Left + + # this is for windows 8 and 10 + elif self.has_member("VadNode"): + if self.VadNode.has_member("LeftChild"): + return self.VadNode.LeftChild + if self.VadNode.has_member("Left"): + return self.VadNode.Left + + # also for windows 8 and 10 + elif self.has_member("Core"): + if self.Core.has_member("VadNode"): + if self.Core.VadNode.has_member("LeftChild"): + return self.Core.VadNode.LeftChild + if self.Core.VadNode.has_member("Left"): + return self.Core.VadNode.Left + + raise AttributeError("Unable to find the left child member") + + def get_parent(self): + """Get the VAD's parent member.""" + + # this is for xp and 2003 + if self.has_member("Parent"): + return self.Parent + + # this is for vista through windows 7 + elif self.has_member("u1") and self.u1.has_member("Parent"): + return self.u1.Parent & ~0x3 + + # this is for windows 8 and 10 + elif self.has_member("VadNode"): + + if self.VadNode.has_member("u1"): + return self.VadNode.u1.Parent & ~0x3 + + elif self.VadNode.has_member("ParentValue"): + return self.VadNode.ParentValue & ~0x3 + + # also for windows 8 and 10 + elif self.has_member("Core"): + + if self.Core.VadNode.has_member("u1"): + return self.Core.VadNode.u1.Parent & ~0x3 + + elif self.Core.VadNode.has_member("ParentValue"): + return self.Core.VadNode.ParentValue & ~0x3 + + raise AttributeError("Unable to find the parent member") + + def get_start(self): + """Get the VAD's starting virtual address.""" + + if self.has_member("StartingVpn"): + + if self.has_member("StartingVpnHigh"): + return (self.StartingVpn << 12) | (self.StartingVpnHigh << 44) + else: + return self.StartingVpn << 12 + + elif self.has_member("Core"): + + if self.Core.has_member("StartingVpnHigh"): + return (self.Core.StartingVpn << 12) | (self.Core.StartingVpnHigh << 44) + else: + return self.Core.StartingVpn << 12 + + raise AttributeError("Unable to find the starting VPN member") + + def get_end(self): + """Get the VAD's ending virtual address.""" + + if self.has_member("EndingVpn"): + + if self.has_member("EndingVpnHigh"): + return (((self.EndingVpn + 1) << 12) | (self.EndingVpnHigh << 44)) - 1 + else: + return ((self.EndingVpn + 1) << 12) - 1 + + elif self.has_member("Core"): + if self.Core.has_member("EndingVpnHigh"): + return (((self.Core.EndingVpn + 1) << 12) | (self.Core.EndingVpnHigh << 44)) - 1 + else: + return ((self.Core.EndingVpn + 1) << 12) - 1 + + raise AttributeError("Unable to find the ending VPN member") + + def get_commit_charge(self): + """Get the VAD's commit charge (number of committed pages)""" + + if self.has_member("u1") and self.u1.has_member("VadFlags1"): + return self.u1.VadFlags1.CommitCharge + + elif self.has_member("u") and self.u.has_member("VadFlags"): + return self.u.VadFlags.CommitCharge + + elif self.has_member("Core"): + return self.Core.u1.VadFlags1.CommitCharge + + raise AttributeError("Unable to find the commit charge member") + + def get_private_memory(self): + """Get the VAD's private memory setting.""" + + if self.has_member("u1") and self.u1.has_member("VadFlags1") and self.u1.VadFlags1.has_member("PrivateMemory"): + return self.u1.VadFlags1.PrivateMemory + + elif self.has_member("u") and self.u.has_member("VadFlags") and self.u.VadFlags.has_member("PrivateMemory"): + return self.u.VadFlags.PrivateMemory + + elif self.has_member("Core"): + if (self.Core.has_member("u1") and self.Core.u1.has_member("VadFlags1") + and self.Core.u1.VadFlags1.has_member("PrivateMemory")): + return self.Core.u1.VadFlags1.PrivateMemory + + elif (self.Core.has_member("u") and self.Core.u.has_member("VadFlags") + and self.Core.u.VadFlags.has_member("PrivateMemory")): + return self.Core.u.VadFlags.PrivateMemory + + raise AttributeError("Unable to find the private memory member") + + def get_protection(self, protect_values, winnt_protections): + """Get the VAD's protection constants as a string.""" + + protect = None + + if self.has_member("u"): + protect = self.u.VadFlags.Protection + + elif self.has_member("Core"): + protect = self.Core.u.VadFlags.Protection + + try: + value = protect_values[protect] + except IndexError: + value = 0 + + names = [] + + for name, mask in winnt_protections.items(): + if value & mask != 0: + names.append(name) + + return "|".join(names) + + def get_file_name(self): + """Only long(er) vads have mapped files.""" + return renderers.NotApplicableValue() + + +class MMVAD(MMVAD_SHORT): + """A version of the process virtual memory range structure that contains + additional fields necessary to map files from disk.""" + + def get_file_name(self): + """Get the name of the file mapped into the memory range (if any)""" + + file_name = renderers.NotApplicableValue() + + try: + # this is for xp and 2003 + if self.has_member("ControlArea"): + file_name = self.ControlArea.FilePointer.FileName.get_string() + + # this is for vista through windows 7 + else: + file_name = self.Subsection.ControlArea.FilePointer.dereference().cast( + "_FILE_OBJECT").FileName.get_string() + + except exceptions.InvalidAddressException: + pass + + return file_name + + +class EX_FAST_REF(objects.StructType): + """This is a standard Windows structure that stores a pointer to an object + but also leverages the least significant bits to encode additional details. + When dereferencing the pointer, we need to strip off the extra bits. + """ + + def dereference(self) -> interfaces.objects.ObjectInterface: + + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + + # the mask value is different on 32 and 64 bits + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + if not symbols.symbol_table_is_64bit(self._context, symbol_table_name): + max_fast_ref = 7 + else: + max_fast_ref = 15 + + return self._context.object(symbol_table_name + constants.BANG + "pointer", + layer_name = self.vol.layer_name, + offset = self.Object & ~max_fast_ref, + native_layer_name = self.vol.native_layer_name) + + +class DEVICE_OBJECT(objects.StructType, pool.ExecutiveObject): + """A class for kernel device objects.""" + + def get_device_name(self) -> str: + header = self.get_object_header() + return header.NameInfo.Name.String # type: ignore + + +class DRIVER_OBJECT(objects.StructType, pool.ExecutiveObject): + """A class for kernel driver objects.""" + + def get_driver_name(self) -> str: + header = self.get_object_header() + return header.NameInfo.Name.String # type: ignore + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + return True + + +class OBJECT_SYMBOLIC_LINK(objects.StructType, pool.ExecutiveObject): + """A class for kernel link objects.""" + + def get_link_name(self) -> str: + header = self.get_object_header() + return header.NameInfo.Name.String # type: ignore + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + return True + + def get_create_time(self): + return conversion.wintime_to_datetime(self.CreationTime.QuadPart) + + +class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): + """A class for windows file objects.""" + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + return self.FileName.Length > 0 and self._context.layers[self.FileName.Buffer.vol.native_layer_name].is_valid( + self.FileName.Buffer) + + def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + name = renderers.UnreadableValue() # type: Union[str, interfaces.renderers.BaseAbsentValue] + + if self._context.layers[self.DeviceObject.vol.native_layer_name].is_valid(self.DeviceObject): + try: + name = "\\Device\\{}".format(self.DeviceObject.get_device_name()) + except ValueError: + pass + + try: + name += self.FileName.String + except (TypeError, exceptions.InvalidAddressException): + pass + + return name + + def access_string(self): + ## Make a nicely formatted ACL string + return (('R' if self.ReadAccess else '-') + ('W' if self.WriteAccess else '-') + + ('D' if self.DeleteAccess else '-') + ('r' if self.SharedRead else '-') + + ('w' if self.SharedWrite else '-') + ('d' if self.SharedDelete else '-')) + + +class KMUTANT(objects.StructType, pool.ExecutiveObject): + """A class for windows mutant objects.""" + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + return True + + def get_name(self) -> str: + """Get the object's name from the object header.""" + header = self.get_object_header() + return header.NameInfo.Name.String # type: ignore + + +class ETHREAD(objects.StructType): + """A class for executive thread objects.""" + + def owning_process(self, kernel_layer: str = None) -> interfaces.objects.ObjectInterface: + """Return the EPROCESS that owns this thread.""" + return self.ThreadsProcess.dereference(kernel_layer) + + def get_cross_thread_flags(self) -> str: + dictCrossThreadFlags = { + 'PS_CROSS_THREAD_FLAGS_TERMINATED': 0, + 'PS_CROSS_THREAD_FLAGS_DEADTHREAD': 1, + 'PS_CROSS_THREAD_FLAGS_HIDEFROMDBG': 2, + 'PS_CROSS_THREAD_FLAGS_IMPERSONATING': 3, + 'PS_CROSS_THREAD_FLAGS_SYSTEM': 4, + 'PS_CROSS_THREAD_FLAGS_HARD_ERRORS_DISABLED': 5, + 'PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION': 6, + 'PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG': 7, + 'PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG': 8 + } + + flags = self.CrossThreadFlags + stringCrossThreadFlags = '' + for flag in dictCrossThreadFlags: + if flags & 2 ** dictCrossThreadFlags[flag]: + stringCrossThreadFlags += '{} '.format(flag) + + return stringCrossThreadFlags[:-1] if stringCrossThreadFlags else stringCrossThreadFlags + + +class UNICODE_STRING(objects.StructType): + """A class for Windows unicode string structures.""" + + def get_string(self) -> interfaces.objects.ObjectInterface: + # We explicitly do *not* catch errors here, we allow an exception to be thrown + # (otherwise there's no way to determine anything went wrong) + # It's up to the user of this method to catch exceptions + return self.Buffer.dereference().cast("string", + max_length = self.Length, + errors = "replace", + encoding = "utf16") + + String = property(get_string) + + +class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): + """A class for executive kernel processes objects.""" + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + + try: + name = objects.utility.array_to_string(self.ImageFileName) + if not name or len(name) == 0 or name[0] == "\x00": + return False + + # The System/PID 4 process has no create time + if not (str(name) == "System" and self.UniqueProcessId == 4): + if self.CreateTime.QuadPart == 0: + return False + + ctime = self.get_create_time() + if not isinstance(ctime, datetime.datetime): + return False + + if not (1998 < ctime.year < 2030): + return False + + # NT pids are divisible by 4 + if self.UniqueProcessId % 4 != 0: + return False + + # check for all 0s besides the PCID entries + if isinstance(self.Pcb.DirectoryTableBase, objects.Array): + dtb = self.Pcb.DirectoryTableBase.cast("pointer") + else: + dtb = self.Pcb.DirectoryTableBase + + if dtb == 0: + return False + + # check for all 0s besides the PCID entries + if dtb & ~0xfff == 0: + return False + + ## TODO: we can also add the thread Flink and Blink tests if necessary + + except exceptions.InvalidAddressException: + return False + + return True + + def add_process_layer(self, config_prefix: str = None, preferred_name: str = None): + """Constructs a new layer based on the process's DirectoryTableBase.""" + + parent_layer = self._context.layers[self.vol.layer_name] + + if not isinstance(parent_layer, intel.Intel): + # We can't get bits_per_register unless we're an intel space (since that's not defined at the higher layer) + raise TypeError("Parent layer is not a translation layer, unable to construct process layer") + + # Presumably for 64-bit systems, the DTB is defined as an array, rather than an unsigned long long + dtb = 0 # type: int + if isinstance(self.Pcb.DirectoryTableBase, objects.Array): + dtb = self.Pcb.DirectoryTableBase.cast("unsigned long long") + else: + dtb = self.Pcb.DirectoryTableBase + dtb = dtb & ((1 << parent_layer.bits_per_register) - 1) + + if preferred_name is None: + preferred_name = self.vol.layer_name + "_Process{}".format(self.UniqueProcessId) + + # Add the constructed layer and return the name + return self._add_process_layer(self._context, dtb, config_prefix, preferred_name) + + def get_peb(self) -> interfaces.objects.ObjectInterface: + """Constructs a PEB object""" + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + + # add_process_layer can raise InvalidAddressException. + # if that happens, we let the exception propagate upwards + proc_layer_name = self.add_process_layer() + + proc_layer = self._context.layers[proc_layer_name] + if not proc_layer.is_valid(self.Peb): + raise exceptions.InvalidAddressException(proc_layer_name, self.Peb, + "Invalid address at {:0x}".format(self.Peb)) + + sym_table = self.vol.type_name.split(constants.BANG)[0] + peb = self._context.object("{}{}_PEB".format(sym_table, constants.BANG), + layer_name = proc_layer_name, + offset = self.Peb) + return peb + + def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Generator for DLLs in the order that they were loaded.""" + + try: + peb = self.get_peb() + for entry in peb.Ldr.InLoadOrderModuleList.to_list( + "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG), + "InLoadOrderLinks"): + yield entry + except exceptions.InvalidAddressException: + return + + def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Generator for DLLs in the order that they were initialized""" + + try: + peb = self.get_peb() + for entry in peb.Ldr.InInitializationOrderModuleList.to_list( + "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG), + "InInitializationOrderLinks"): + yield entry + except exceptions.InvalidAddressException: + return + + def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Generator for DLLs in the order that they appear in memory""" + + try: + peb = self.get_peb() + for entry in peb.Ldr.InMemoryOrderModuleList.to_list( + "{}{}_LDR_DATA_TABLE_ENTRY".format(self.get_symbol_table_name(), constants.BANG), + "InMemoryOrderLinks"): + yield entry + except exceptions.InvalidAddressException: + return + + def get_handle_count(self): + try: + if self.has_member("ObjectTable"): + if self.ObjectTable.has_member("HandleCount"): + return self.ObjectTable.HandleCount + + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, + "Cannot access _EPROCESS.ObjectTable.HandleCount at {0:#x}".format(self.vol.offset)) + + return renderers.UnreadableValue() + + def get_session_id(self): + try: + if self.has_member("Session"): + if self.Session == 0: + return renderers.NotApplicableValue() + + symbol_table_name = self.get_symbol_table_name() + kvo = self._context.layers[self.vol.native_layer_name].config['kernel_virtual_offset'] + ntkrnlmp = self._context.module(symbol_table_name, + layer_name = self.vol.native_layer_name, + offset = kvo, + native_layer_name = self.vol.native_layer_name) + session = ntkrnlmp.object(object_type = "_MM_SESSION_SPACE", offset = self.Session, absolute = True) + + if session.has_member("SessionId"): + return session.SessionId + + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVV, + "Cannot access _EPROCESS.Session.SessionId at {0:#x}".format(self.vol.offset)) + + return renderers.UnreadableValue() + + def get_create_time(self): + return conversion.wintime_to_datetime(self.CreateTime.QuadPart) + + def get_exit_time(self): + return conversion.wintime_to_datetime(self.ExitTime.QuadPart) + + def get_wow_64_process(self): + if self.has_member("Wow64Process"): + return self.Wow64Process + + elif self.has_member("WoW64Process"): + return self.WoW64Process + + raise AttributeError("Unable to find Wow64Process") + + def get_is_wow64(self): + try: + value = self.get_wow_64_process() + except AttributeError: + return False + + return value != 0 and value != None + + def get_vad_root(self): + + # windows 8 and 2012 (_MM_AVL_TABLE) + if self.VadRoot.has_member("BalancedRoot"): + return self.VadRoot.BalancedRoot + + # windows 8.1 and windows 10 (_RTL_AVL_TREE) + elif self.VadRoot.has_member("Root"): + return self.VadRoot.Root.dereference() # .cast("_MMVAD") + + else: + # windows xp and 2003 + return self.VadRoot.dereference().cast("_MMVAD") + + def environment_variables(self): + """Generator for environment variables. + + The PEB points to our env block - a series of null-terminated + unicode strings. Each string cannot be more than 0x7FFF chars. + End of the list is a quad-null. + """ + context = self._context + process_space = self.add_process_layer() + + try: + block = self.get_peb().ProcessParameters.Environment + try: + block_size = self.get_peb().ProcessParameters.EnvironmentSize + except AttributeError: # Windows XP + block_size = self.get_peb().ProcessParameters.Length + envars = context.layers[process_space].read(block, block_size).decode("utf-16-le", + errors = 'replace').split('\x00')[:-1] + except exceptions.InvalidAddressException: + return renderers.UnreadableValue() + + for envar in envars: + split_index = envar.find('=') + env = envar[:split_index] + var = envar[split_index + 1:] + + # Exlude parse problem with some types of env + if env and var: + yield env, var + + +class LIST_ENTRY(objects.StructType, collections.abc.Iterable): + """A class for double-linked lists on Windows.""" + + def to_list(self, + symbol_type: str, + member: str, + forward: bool = True, + sentinel: bool = True, + layer: Optional[str] = None) -> Iterator[interfaces.objects.ObjectInterface]: + """Returns an iterator of the entries in the list.""" + + layer = layer or self.vol.layer_name + + relative_offset = self._context.symbol_space.get_type(symbol_type).relative_child_offset(member) + + direction = 'Blink' + if forward: + direction = 'Flink' + + trans_layer = self._context.layers[layer] + + try: + trans_layer.is_valid(self.vol.offset) + link = getattr(self, direction).dereference() + except exceptions.InvalidAddressException: + return + + if not sentinel: + yield self._context.object(symbol_type, + layer, + offset = self.vol.offset - relative_offset, + native_layer_name = layer or self.vol.native_layer_name) + + seen = {self.vol.offset} + while link.vol.offset not in seen: + obj_offset = link.vol.offset - relative_offset + + try: + trans_layer.is_valid(obj_offset) + except exceptions.InvalidAddressException: + return + + obj = self._context.object(symbol_type, + layer, + offset = obj_offset, + native_layer_name = layer or self.vol.native_layer_name) + yield obj + + seen.add(link.vol.offset) + + try: + link = getattr(link, direction).dereference() + except exceptions.InvalidAddressException: + return + + def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: + return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) + + +class TOKEN(objects.StructType): + """A class for process etoken object.""" + + def get_sids(self) -> Iterable[str]: + """Yield a sid for the current token object.""" + + if self.UserAndGroupCount < 0xFFFF: + layer_name = self.vol.layer_name + kvo = self._context.layers[layer_name].config["kernel_virtual_offset"] + symbol_table = self.get_symbol_table_name() + ntkrnlmp = self._context.module(symbol_table, layer_name = layer_name, offset = kvo) + UserAndGroups = ntkrnlmp.object(object_type = "array", + offset = self.UserAndGroups.dereference().vol.get("offset") - kvo, + subtype = ntkrnlmp.get_type("_SID_AND_ATTRIBUTES"), + count = self.UserAndGroupCount) + for sid_and_attr in UserAndGroups: + try: + sid = sid_and_attr.Sid.dereference().cast("_SID") + # catch invalid pointers (UserAndGroupCount is too high) + if sid is None: + return + # this mimics the windows API IsValidSid + if sid.Revision & 0xF != 1 or sid.SubAuthorityCount > 15: + return + id_auth = "" + for i in sid.IdentifierAuthority.Value: + id_auth = i + SubAuthority = ntkrnlmp.object(object_type = "array", + offset = sid.SubAuthority.vol.offset - kvo, + subtype = ntkrnlmp.get_type("unsigned long"), + count = int(sid.SubAuthorityCount)) + yield "S-" + "-".join(str(i) for i in (sid.Revision, id_auth) + tuple(SubAuthority)) + except exceptions.InvalidAddressException: + vollog.log(constants.LOGLEVEL_VVVV, "InvalidAddressException while parsing for token sid") + + def privileges(self): + """Return a list of privileges for the current token object.""" + + try: + for priv_index in range(64): + yield (priv_index, bool(self.Privileges.Present & (2 ** priv_index)), + bool(self.Privileges.Enabled & (2 ** priv_index)), + bool(self.Privileges.EnabledByDefault & (2 ** priv_index))) + except AttributeError: # Windows XP + if self.PrivilegeCount < 1024: + # This is a pointer to an array of _LUID_AND_ATTRIBUTES + for luid in self.Privileges.dereference().cast( + "array", + count = self.PrivilegeCount, + subtype = self._context.symbol_space[self.get_symbol_table_name()].get_type( + "_LUID_AND_ATTRIBUTES")): + # The Attributes member is a flag + enabled = luid.Attributes & 2 != 0 + default = luid.Attributes & 1 != 0 + yield luid.Luid.LowPart, True, enabled, default + else: + vollog.log(constants.LOGLEVEL_VVVV, "Broken Token Privileges.") + + +class KTHREAD(objects.StructType): + """A class for thread control block objects.""" + + def get_state(self) -> str: + dictState = { + 0: 'Initialized', + 1: 'Ready', + 2: 'Running', + 3: 'Standby', + 4: 'Terminated', + 5: 'Waiting', + 6: 'Transition', + 7: 'DeferredReady', + 8: 'GateWait' + } + return dictState.get(self.State, renderers.NotApplicableValue()) + + def get_wait_reason(self) -> str: + dictWaitReason = { + 0: 'Executive', + 1: 'FreePage', + 2: 'PageIn', + 3: 'PoolAllocation', + 4: 'DelayExecution', + 5: 'Suspended', + 6: 'UserRequest', + 7: 'WrExecutive', + 8: 'WrFreePage', + 9: 'WrPageIn', + 10: 'WrPoolAllocation', + 11: 'WrDelayExecution', + 12: 'WrSuspended', + 13: 'WrUserRequest', + 14: 'WrEventPair', + 15: 'WrQueue', + 16: 'WrLpcReceive', + 17: 'WrLpcReply', + 18: 'WrVirtualMemory', + 19: 'WrPageOut', + 20: 'WrRendezvous', + 21: 'Spare2', + 22: 'Spare3', + 23: 'Spare4', + 24: 'Spare5', + 25: 'Spare6', + 26: 'WrKernel', + 27: 'WrResource', + 28: 'WrPushLock', + 29: 'WrMutex', + 30: 'WrQuantumEnd', + 31: 'WrDispatchInt', + 32: 'WrPreempted', + 33: 'WrYieldExecution', + 34: 'WrFastMutex', + 35: 'WrGuardedMutex', + 36: 'WrRundown', + 37: 'MaximumWaitReason' + } + return dictWaitReason.get(self.WaitReason, renderers.NotApplicableValue()) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/kdbg.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/kdbg.py new file mode 100644 index 00000000..6a98d62b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/kdbg.py @@ -0,0 +1,36 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.framework import constants +from volatility.framework import objects + + +class KDDEBUGGER_DATA64(objects.StructType): + + def get_build_lab(self): + """Returns the NT build lab string from the KDBG.""" + + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + + return self._context.object(symbol_table_name + constants.BANG + "string", + layer_name = layer_name, + offset = self.NtBuildLab, + max_length = 32, + errors = "replace") + + def get_csdversion(self): + """Returns the CSDVersion as an integer (i.e. Service Pack number)""" + + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + + csdresult = self._context.object(symbol_table_name + constants.BANG + "unsigned long", + layer_name = layer_name, + offset = self.CmNtCSDVersion) + + return (csdresult >> 8) & 0xffffffff + + +class_types = {'_KDDEBUGGER_DATA64': KDDEBUGGER_DATA64} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/network.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/network.py new file mode 100644 index 00000000..98881275 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/network.py @@ -0,0 +1,239 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +import socket +from typing import Dict, Tuple, List, Union + +from volatility.framework import exceptions +from volatility.framework import objects, interfaces +from volatility.framework.objects import Array +from volatility.framework.renderers import conversion + +vollog = logging.getLogger(__name__) + + +def inet_ntop(address_family: int, packed_ip: Union[List[int], Array]) -> str: + if address_family in [socket.AF_INET6, socket.AF_INET]: + try: + return socket.inet_ntop(address_family, bytes(packed_ip)) + except AttributeError: + raise RuntimeError("This version of python does not have socket.inet_ntop, please upgrade") + raise socket.error("[Errno 97] Address family not supported by protocol") + + +# Python's socket.AF_INET6 is 0x1e but Microsoft defines it +# as a constant value of 0x17 in their source code. Thus we +# need Microsoft's since that's what is found in memory. +AF_INET = 2 +AF_INET6 = 0x17 + +# String representations of INADDR_ANY and INADDR6_ANY +inaddr_any = inet_ntop(socket.AF_INET, [0] * 4) +inaddr6_any = inet_ntop(socket.AF_INET6, [0] * 16) + + +class _TCP_LISTENER(objects.StructType): + """Class for objects found in TcpL pools. + + This class serves as a base class for all pooled network objects. + + It exposes some functions which return sanity-checked members. Substructures referred to by a + pointer may appear valid at first glance but will throw an InvalidAddressException on access. + + This is not a problem when objects are validated via their `is_valid()` method, but when + scanning for semi-corrupted data this check will not be performed. + + Be mindful that most of those methods return `None` when they would access invalid data. + If you want to process the raw data access the attributes directly, e.g. + via `network_object.InetAF` instead of `network_object.get_address_family()`. + + """ + + MIN_CREATETIME_YEAR = 1950 + MAX_CREATETIME_YEAR = 2200 + + def __init__(self, context: interfaces.context.ContextInterface, type_name: str, + object_info: interfaces.objects.ObjectInformation, size: int, + members: Dict[str, Tuple[int, interfaces.objects.Template]]) -> None: + + super().__init__(context = context, + type_name = type_name, + object_info = object_info, + size = size, + members = members) + + def get_address_family(self): + try: + return self.InetAF.dereference().AddressFamily + + except exceptions.InvalidAddressException: + return None + + def get_owner(self): + try: + return self.member('Owner').dereference() + + except exceptions.InvalidAddressException: + return None + + def get_owner_pid(self): + if self.get_owner().is_valid(): + if self.get_owner().has_valid_member("UniqueProcessId"): + return self.get_owner().UniqueProcessId + + return None + + def get_owner_procname(self): + if self.get_owner().is_valid(): + if self.get_owner().has_valid_member("ImageFileName"): + return self.get_owner().ImageFileName.cast("string", + max_length = self.get_owner().ImageFileName.vol.count, + errors = "replace") + + return None + + def get_create_time(self): + dt_obj = conversion.wintime_to_datetime(self.CreateTime.QuadPart) + + if isinstance(dt_obj, interfaces.renderers.BaseAbsentValue): + return dt_obj + + # return None if the timestamp seems invalid + if not (self.MIN_CREATETIME_YEAR < dt_obj.year < self.MAX_CREATETIME_YEAR): + return None + else: + return dt_obj + + def get_in_addr(self): + try: + local_addr = self.LocalAddr.dereference() + + if local_addr.pData.dereference(): + inaddr = local_addr.inaddr + return inaddr + else: + return None + + except exceptions.InvalidAddressException: + return None + + def dual_stack_sockets(self): + """Handle Windows dual-stack sockets""" + + # If this pointer is valid, the socket is bound to + # a specific IP address. Otherwise, the socket is + # listening on all IP addresses of the address family. + + # Note the remote address is always INADDR_ANY or + # INADDR6_ANY for sockets. The moment a client + # connects to the listener, a TCP_ENDPOINT is created + # and that structure contains the remote address. + + inaddr = self.get_in_addr() + + if inaddr: + if self.get_address_family() == AF_INET: + yield "v4", inet_ntop(socket.AF_INET, inaddr.addr4), inaddr_any + elif self.get_address_family() == AF_INET6: + yield "v6", inet_ntop(socket.AF_INET6, inaddr.addr6), inaddr6_any + else: + yield "v4", inaddr_any, inaddr_any + if self.get_address_family() == AF_INET6: + yield "v6", inaddr6_any, inaddr6_any + + def is_valid(self): + + try: + if not self.get_address_family() in (AF_INET, AF_INET6): + return False + + except exceptions.InvalidAddressException: + return False + return True + + +class _TCP_ENDPOINT(_TCP_LISTENER): + """Class for objects found in TcpE pools""" + + def _ipv4_or_ipv6(self, inaddr): + + if self.get_address_family() == AF_INET: + return inet_ntop(socket.AF_INET, inaddr.addr4) + else: + return inet_ntop(socket.AF_INET6, inaddr.addr6) + + def get_local_address(self): + try: + inaddr = self.AddrInfo.dereference().Local.pData.dereference().dereference() + + return self._ipv4_or_ipv6(inaddr) + + except exceptions.InvalidAddressException: + return None + + def get_remote_address(self): + try: + inaddr = self.AddrInfo.dereference().Remote.dereference() + + return self._ipv4_or_ipv6(inaddr) + + except exceptions.InvalidAddressException: + return None + + def is_valid(self): + + if self.State not in self.State.choices.values(): + vollog.debug("invalid due to invalid tcp state {}".format(self.State)) + return False + + try: + if self.get_address_family() not in (AF_INET, AF_INET6): + vollog.debug("invalid due to invalid address_family {}".format(self.get_address_family())) + return False + + if not self.get_local_address() and (not self.get_owner() or self.get_owner().UniqueProcessId == 0 + or self.get_owner().UniqueProcessId > 65535): + vollog.debug("invalid due to invalid owner data") + return False + + except exceptions.InvalidAddressException: + vollog.debug("invalid due to invalid address access") + return False + + return True + + +class _UDP_ENDPOINT(_TCP_LISTENER): + """Class for objects found in UdpA pools""" + + +class _LOCAL_ADDRESS(objects.StructType): + + @property + def inaddr(self): + return self.pData.dereference().dereference() + + +class _LOCAL_ADDRESS_WIN10_UDP(objects.StructType): + + @property + def inaddr(self): + return self.pData.dereference() + + +win10_x64_class_types = { + '_TCP_ENDPOINT': _TCP_ENDPOINT, + '_TCP_LISTENER': _TCP_LISTENER, + '_UDP_ENDPOINT': _UDP_ENDPOINT, + '_LOCAL_ADDRESS': _LOCAL_ADDRESS, + '_LOCAL_ADDRESS_WIN10_UDP': _LOCAL_ADDRESS_WIN10_UDP +} + +class_types = { + '_TCP_ENDPOINT': _TCP_ENDPOINT, + '_TCP_LISTENER': _TCP_LISTENER, + '_UDP_ENDPOINT': _UDP_ENDPOINT, + '_LOCAL_ADDRESS': _LOCAL_ADDRESS +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pe.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pe.py new file mode 100644 index 00000000..f7805fa1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pe.py @@ -0,0 +1,168 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Generator, Tuple + +from volatility.framework import constants +from volatility.framework import objects, interfaces +from volatility.framework.renderers import conversion + + +class IMAGE_DOS_HEADER(objects.StructType): + + def get_nt_header(self) -> interfaces.objects.ObjectInterface: + """Carve out the NT header from this DOS header. This reflects on the + PE file's Machine type to create a 32- or 64-bit NT header structure. + + Returns: + <_IMAGE_NT_HEADERS> or <_IMAGE_NT_HEADERS64> instance + """ + + if self.e_magic != 0x5a4d: + raise ValueError("e_magic {0:04X} is not a valid DOS signature.".format(self.e_magic)) + + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + + nt_header = self._context.object(symbol_table_name + constants.BANG + "_IMAGE_NT_HEADERS", + layer_name = layer_name, + offset = self.vol.offset + self.e_lfanew) + + if nt_header.Signature != 0x4550: + raise ValueError("NT header signature {0:04X} is not a valid".format(nt_header.Signature)) + + # this checks if we need a PE32+ header + if nt_header.FileHeader.Machine == 34404: + nt_header = nt_header.cast("_IMAGE_NT_HEADERS64") + + return nt_header + + def replace_header_field(self, sect: interfaces.objects.ObjectInterface, header: bytes, + item: interfaces.objects.ObjectInterface, value: int) -> bytes: + """Replaces a member in an _IMAGE_SECTION_HEADER structure. + + Args: + sect: the section instance + header: raw data for the section + item: the member of the section to replace + value: new value for the member + + Returns: + The raw data with the replaced header field + """ + + member_size = self._context.symbol_space.get_type(item.vol.type_name).size + start = item.vol.offset - sect.vol.offset + newval = objects.convert_value_to_data(value, int, item.vol.data_format) + result = header[:start] + newval + header[start + member_size:] + return result + + def fix_image_base(self, raw_data: bytes, nt_header: interfaces.objects.ObjectInterface) -> bytes: + """Fix the _OPTIONAL_HEADER.ImageBase value (which is either an + unsigned long for 32-bit PE's or unsigned long long for 64-bit PE's) to + match the address where the PE file was carved out of memory. + + Args: + raw_data: a bytes object of the PE's data + nt_header: <_IMAGE_NT_HEADERS> or <_IMAGE_NT_HEADERS64> instance + + Returns: + patched with the correct address + """ + + image_base_offset = nt_header.OptionalHeader.ImageBase.vol.offset - self.vol.offset + image_base_type = nt_header.OptionalHeader.ImageBase.vol.type_name + member_size = self._context.symbol_space.get_type(image_base_type).size + newval = objects.convert_value_to_data(self.vol.offset, int, nt_header.OptionalHeader.ImageBase.vol.data_format) + return raw_data[:image_base_offset] + newval + raw_data[image_base_offset + member_size:] + + def reconstruct(self) -> Generator[Tuple[int, bytes], None, None]: + """This method generates the content necessary to reconstruct a PE file + from memory. It preserves slack space (similar to the old --memory) and + automatically fixes the ImageBase in the output PE file. + + Returns: + of ( offset, data) + """ + + nt_header = self.get_nt_header() + + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + + section_alignment = nt_header.OptionalHeader.SectionAlignment + + sect_header_size = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + + "_IMAGE_SECTION_HEADER").size + + size_of_image = nt_header.OptionalHeader.SizeOfImage + + # no legitimate PE is going to be larger than this + if size_of_image > (1024 * 1024 * 100): + raise ValueError("The claimed SizeOfImage is too large: {}".format(size_of_image)) + + read_layer = self._context.layers[layer_name] + + raw_data = read_layer.read(self.vol.offset, nt_header.OptionalHeader.SizeOfImage, pad = True) + + # fix the PE image base before yielding the initial view of the data + fixed_data = self.fix_image_base(raw_data, nt_header) + yield 0, fixed_data + + start_addr = nt_header.FileHeader.SizeOfOptionalHeader + \ + (nt_header.OptionalHeader.vol.offset - self.vol.offset) + + counter = 0 + for sect in nt_header.get_sections(): + + if sect.VirtualAddress > size_of_image: + raise ValueError("Section VirtualAddress is too large: {}".format(sect.VirtualAddress)) + + if sect.Misc.VirtualSize > size_of_image: + raise ValueError("Section VirtualSize is too large: {}".format(sect.Misc.VirtualSize)) + + if sect.SizeOfRawData > size_of_image: + raise ValueError("Section SizeOfRawData is too large: {}".format(sect.SizeOfRawData)) + + if sect is not None: + # It doesn't matter if this is too big, because it'll get overwritten by the later layers + sect_size = conversion.round(sect.Misc.VirtualSize, section_alignment, up = True) + sectheader = read_layer.read(sect.vol.offset, sect_header_size) + sectheader = self.replace_header_field(sect, sectheader, sect.PointerToRawData, sect.VirtualAddress) + sectheader = self.replace_header_field(sect, sectheader, sect.SizeOfRawData, sect_size) + sectheader = self.replace_header_field(sect, sectheader, sect.Misc.VirtualSize, sect_size) + + offset = start_addr + (counter * sect_header_size) + yield offset, sectheader + counter += 1 + + +class IMAGE_NT_HEADERS(objects.StructType): + + def get_sections(self) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """Iterate through the section headers for this PE file. + + Yields: + <_IMAGE_SECTION_HEADER> objects + """ + layer_name = self.vol.layer_name + symbol_table_name = self.get_symbol_table_name() + + sect_header_size = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + + "_IMAGE_SECTION_HEADER").size + start_addr = self.FileHeader.SizeOfOptionalHeader + self.OptionalHeader.vol.offset + + for i in range(self.FileHeader.NumberOfSections): + sect_addr = start_addr + (i * sect_header_size) + yield self._context.object(symbol_table_name + constants.BANG + "_IMAGE_SECTION_HEADER", + offset = sect_addr, + layer_name = layer_name) + + +class_types = { + '_IMAGE_DOS_HEADER': IMAGE_DOS_HEADER, + # the 32- and 64-bit extensions behave the same way, but the underlying structure is different + '_IMAGE_NT_HEADERS': IMAGE_NT_HEADERS, + '_IMAGE_NT_HEADERS64': IMAGE_NT_HEADERS +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pool.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pool.py new file mode 100644 index 00000000..192fef2b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/pool.py @@ -0,0 +1,350 @@ +import functools +import logging +import struct +from typing import Optional, Tuple, List, Dict, Union + +from volatility.framework import objects, interfaces, constants, symbols, exceptions, renderers +from volatility.framework.renderers import conversion + +vollog = logging.getLogger(__name__) + + +class POOL_HEADER(objects.StructType): + """A kernel pool allocation header. + + Exists at the base of the allocation and provides a tag that we can + scan for. + """ + + def get_object(self, + type_name: str, + use_top_down: bool, + executive: bool = False, + kernel_symbol_table: Optional[str] = None, + native_layer_name: Optional[str] = None) -> Optional[interfaces.objects.ObjectInterface]: + """Carve an object or data structure from a kernel pool allocation + + Args: + type_name: the data structure type name + native_layer_name: the name of the layer where the data originally lived + object_type: the object type (executive kernel objects only) + kernel_symbol_table: in case objects of a different symbol table are scanned for + + Returns: + An object as found from a POOL_HEADER + """ + + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + if constants.BANG in type_name: + symbol_table_name, type_name = type_name.split(constants.BANG)[0:2] + + # when checking for symbols from a table other than nt_symbols grab _OBJECT_HEADER from the kernel + # because symbol_table_name will be different from kernel_symbol_table. + if kernel_symbol_table: + object_header_type = self._context.symbol_space.get_type(kernel_symbol_table + constants.BANG + + "_OBJECT_HEADER") + else: + # otherwise symbol_table_name *is* the kernel symbol table, so just use that. + object_header_type = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + + "_OBJECT_HEADER") + + pool_header_size = self.vol.size + + # if there is no object type, then just instantiate a structure + if not executive: + mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, + layer_name = self.vol.layer_name, + offset = self.vol.offset + pool_header_size, + native_layer_name = native_layer_name) + return mem_object + + # otherwise we have an executive object in the pool + else: + if symbols.symbol_table_is_64bit(self._context, symbol_table_name): + alignment = 16 + else: + alignment = 8 + + # use the top down approach for windows 8 and later + if use_top_down: + body_offset = object_header_type.relative_child_offset('Body') + infomask_offset = object_header_type.relative_child_offset('InfoMask') + pointercount_offset = object_header_type.relative_child_offset('PointerCount') + pointercount_size = object_header_type.members['PointerCount'][1].size + optional_headers, lengths_of_optional_headers = self._calculate_optional_header_lengths( + self._context, symbol_table_name) + padding_available = None if 'PADDING_INFO' not in optional_headers else optional_headers.index( + 'PADDING_INFO') + max_optional_headers_length = sum(lengths_of_optional_headers) + + # define the starting and ending bounds for the scan + start_offset = self.vol.offset + pool_header_size + addr_limit = min(max_optional_headers_length, self.BlockSize * alignment) + + # A single read is better than lots of little one-byte reads. + # We're ok padding this, because the byte we'd check would be 0 which would only be valid if there + # were no optional headers in the first place (ie, if we read too much for headers that don't exist, + # but the bit we could read were valid) + infomask_data = self._context.layers[self.vol.layer_name].read(start_offset, + addr_limit + infomask_offset, + pad = True) + + # Addr stores the offset to the potential start of the OBJECT_HEADER from just after the POOL_HEADER + # It will always be aligned to a particular alignment + for addr in range(0, addr_limit, alignment): + infomask_value = infomask_data[addr + infomask_offset] + pointercount_value = int.from_bytes( + infomask_data[addr + pointercount_offset:addr + pointercount_offset + pointercount_size], + byteorder = 'little', + signed = True) + if not 0x1000000 > pointercount_value >= 0: + continue + + padding_present = False + optional_headers_length = 0 + for i in range(len(lengths_of_optional_headers)): + if infomask_value & (1 << i): + optional_headers_length += lengths_of_optional_headers[i] + if i == padding_available: + padding_present = True + + # PADDING_INFO is a special case (4 bytes that contain the total padding length) + padding_length = 0 + if padding_present: + # Read the four bytes from just before the next optional_headers_length minus the padding_info size + # + # --------------- + # POOL_HEADER + # --------------- + # + # start of PADDING_INFO + # --------------- + # End of other optional headers + # --------------- + # OBJECT_HEADER + # --------------- + if addr - optional_headers_length < 0: + continue + padding_length = struct.unpack( + "= padding_length > addr: + continue + + try: + mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, + layer_name = self.vol.layer_name, + offset = addr + body_offset + start_offset, + native_layer_name = native_layer_name) + + if mem_object.is_valid(): + return mem_object + + except (TypeError, exceptions.InvalidAddressException): + pass + + # use the bottom up approach for windows 7 and earlier + else: + type_size = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + type_name).size + rounded_size = conversion.round(type_size, alignment, up = True) + + mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, + layer_name = self.vol.layer_name, + offset = self.vol.offset + self.BlockSize * alignment - rounded_size, + native_layer_name = native_layer_name) + + try: + if mem_object.is_valid(): + return mem_object + except (TypeError, exceptions.InvalidAddressException): + return None + return None + + @classmethod + @functools.lru_cache() + def _calculate_optional_header_lengths(cls, context: interfaces.context.ContextInterface, + symbol_table_name: str) -> Tuple[List[str], List[int]]: + headers = [] + sizes = [] + for header in [ + 'CREATOR_INFO', 'NAME_INFO', 'HANDLE_INFO', 'QUOTA_INFO', 'PROCESS_INFO', 'AUDIT_INFO', 'EXTENDED_INFO', + 'HANDLE_REVOCATION_INFO', 'PADDING_INFO' + ]: + try: + type_name = "{}{}_OBJECT_HEADER_{}".format(symbol_table_name, constants.BANG, header) + header_type = context.symbol_space.get_type(type_name) + headers.append(header) + sizes.append(header_type.size) + except (AttributeError, exceptions.SymbolError): + # Some of these may not exist, for example: + # if build < 9200: PADDING_INFO else: AUDIT_INFO + # if build == 10586: HANDLE_REVOCATION_INFO else EXTENDED_INFO + # based on what's present and what's not, this list should be the right order and the right length + pass + return headers, sizes + + def is_free_pool(self): + return self.PoolType == 0 + + def is_paged_pool(self): + return self.PoolType % 2 == 0 and self.PoolType > 0 + + def is_nonpaged_pool(self): + return self.PoolType % 2 == 1 + + +class POOL_HEADER_VISTA(POOL_HEADER): + """A kernel pool allocation header, updated for Vista and later. + + Exists at the base of the allocation and provides a tag that we can + scan for. + """ + + def is_paged_pool(self): + return self.PoolType % 2 == 1 + + def is_nonpaged_pool(self): + return self.PoolType % 2 == 0 and self.PoolType > 0 + + +class POOL_TRACKER_BIG_PAGES(objects.StructType): + """A kernel big page pool tracker.""" + + pool_type_lookup = {} # type: Dict[str, str] + + def _generate_pool_type_lookup(self): + # Enumeration._generate_inverse_choices() raises ValueError because multiple enum names map to the same + # value in the kernel _POOL_TYPE so create a custom mapping here and take the first match + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + pool_type_enum = self._context.symbol_space.get_enumeration(symbol_table_name + constants.BANG + "_POOL_TYPE") + for k, v in pool_type_enum.choices.items(): + if v not in self.pool_type_lookup: + self.pool_type_lookup[v] = k + + def is_valid(self) -> bool: + return self.Key > 0 + # return self.Va > 0x1 + + def get_key(self) -> str: + """Returns the Key value as a 4 character string""" + tag_bytes = objects.convert_value_to_data(self.Key, int, objects.DataFormatInfo(4, "little", False)) + return "".join([chr(x) if 32 < x < 127 else '' for x in tag_bytes]) + + def get_pool_type(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + """Returns the enum name for the PoolType value on applicable systems""" + # Not applicable until Vista + if hasattr(self, 'PoolType'): + if not self.pool_type_lookup: + self._generate_pool_type_lookup() + return self.pool_type_lookup.get(self.PoolType, "Unknown choice {}".format(self.PoolType)) + else: + return renderers.NotApplicableValue() + + def get_number_of_bytes(self) -> Union[int, interfaces.renderers.BaseAbsentValue]: + """Returns the NumberOfBytes value on applicable systems""" + # Not applicable until Vista + try: + return self.NumberOfBytes + except AttributeError: + return renderers.NotApplicableValue() + + +class ExecutiveObject(interfaces.objects.ObjectInterface): + """This is used as a "mixin" that provides all kernel executive objects + with a means of finding their own object header.""" + + def get_object_header(self) -> 'OBJECT_HEADER': + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + body_offset = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + + "_OBJECT_HEADER").relative_child_offset("Body") + return self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", + layer_name = self.vol.layer_name, + offset = self.vol.offset - body_offset, + native_layer_name = self.vol.native_layer_name) + + +class OBJECT_HEADER(objects.StructType): + """A class for the headers for executive kernel objects, which contains + quota information, ownership details, naming data, and ACLs.""" + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + + # if self.InfoMask > 0x48: + # return False + + try: + if self.PointerCount > 0x1000000 or self.PointerCount < 0: + return False + except exceptions.InvalidAddressException: + return False + + return True + + def get_object_type(self, type_map: Dict[int, str], cookie: int = None) -> Optional[str]: + """Across all Windows versions, the _OBJECT_HEADER embeds details on + the type of object (i.e. process, file) but the way its embedded + differs between versions. + + This API abstracts away those details. + """ + + if self.vol.get('object_header_object_type', None) is not None: + return self.vol.object_header_object_type + + try: + # vista and earlier have a Type member + self._vol['object_header_object_type'] = self.Type.Name.String + except AttributeError: + # windows 7 and later have a TypeIndex, but windows 10 + # further encodes the index value with nt1!ObHeaderCookie + try: + type_index = ((self.vol.offset >> 8) ^ cookie ^ self.TypeIndex) & 0xFF + except (AttributeError, TypeError): + type_index = self.TypeIndex + + self._vol['object_header_object_type'] = type_map.get(type_index) + return self.vol.object_header_object_type + + @property + def NameInfo(self) -> interfaces.objects.ObjectInterface: + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + + try: + header_offset = self.NameInfoOffset + except AttributeError: + # http://codemachine.com/article_objectheader.html (Windows 7 and later) + name_info_bit = 0x2 + + layer = self._context.layers[self.vol.native_layer_name] + kvo = layer.config.get("kernel_virtual_offset", None) + + if kvo is None: + raise AttributeError("Could not find kernel_virtual_offset for layer: {}".format(self.vol.layer_name)) + + ntkrnlmp = self._context.module(symbol_table_name, layer_name = self.vol.layer_name, offset = kvo) + address = ntkrnlmp.get_symbol("ObpInfoMaskToOffset").address + calculated_index = self.InfoMask & (name_info_bit | (name_info_bit - 1)) + + header_offset = self._context.object(symbol_table_name + constants.BANG + "unsigned char", + layer_name = self.vol.native_layer_name, + offset = kvo + address + calculated_index) + + if header_offset == 0: + raise ValueError("Could not find _OBJECT_HEADER_NAME_INFO for object at {} of layer {}".format( + self.vol.offset, self.vol.layer_name)) + + header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER_NAME_INFO", + layer_name = self.vol.layer_name, + offset = self.vol.offset - header_offset, + native_layer_name = self.vol.native_layer_name) + return header diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/registry.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/registry.py new file mode 100644 index 00000000..cf15e6c1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/extensions/registry.py @@ -0,0 +1,301 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import enum +import logging +import struct +from typing import Optional, Iterable, Union + +from volatility.framework import constants, exceptions, objects, interfaces +from volatility.framework.layers.registry import RegistryHive, RegistryInvalidIndex, RegistryFormatException + +vollog = logging.getLogger(__name__) + +BIG_DATA_MAXLEN = 0x3fd8 + + +class RegValueTypes(enum.Enum): + REG_NONE = 0 + REG_SZ = 1 + REG_EXPAND_SZ = 2 + REG_BINARY = 3 + REG_DWORD = 4 + REG_DWORD_BIG_ENDIAN = 5 + REG_LINK = 6 + REG_MULTI_SZ = 7 + REG_RESOURCE_LIST = 8 + REG_FULL_RESOURCE_DESCRIPTOR = 9 + REG_RESOURCE_REQUIREMENTS_LIST = 10 + REG_QWORD = 11 + REG_UNKNOWN = 99999 + + # TODO: This _missing_() method can replace the get() method below + # if support for Python 3.6 is added in the future + # @classmethod + # def _missing_(cls, value): + # return cls(RegValueTypes.REG_UNKNOWN) + + @classmethod + def get(cls, value): + """An alternative method for using this enum when the value may be + unknown. + + This is used to support unknown value requests in Python <3.6. + """ + try: + return cls(value) + except ValueError: + return cls(RegValueTypes.REG_UNKNOWN) + + +class RegKeyFlags(enum.IntEnum): + KEY_IS_VOLATILE = 0x01 + KEY_HIVE_EXIT = 0x02 + KEY_HIVE_ENTRY = 0x04 + KEY_NO_DELETE = 0x08 + KEY_SYM_LINK = 0x10 + KEY_COMP_NAME = 0x20 + KEY_PREFEF_HANDLE = 0x40 + KEY_VIRT_MIRRORED = 0x80 + KEY_VIRT_TARGET = 0x100 + KEY_VIRTUAL_STORE = 0x200 + + +class HMAP_ENTRY(objects.StructType): + + def get_block_offset(self) -> int: + try: + return (self.PermanentBinAddress ^ (self.PermanentBinAddress & 0xf)) + self.BlockOffset + except AttributeError: + return self.BlockAddress + + +class CMHIVE(objects.StructType): + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + try: + return self.Hive.Signature == 0xbee0bee0 + except exceptions.InvalidAddressException: + return False + + def get_name(self) -> Optional[interfaces.objects.ObjectInterface]: + """Determine a name for the hive. + + Note that some attributes are unpredictably blank across + different OS versions while others are populated, so we check + all possibilities and take the first one that's not empty + """ + + for attr in ["FileFullPath", "FileUserName", "HiveRootPath"]: + try: + return getattr(self, attr).get_string() + except (AttributeError, exceptions.InvalidAddressException): + pass + + return None + + name = property(get_name) + + +class CM_KEY_BODY(objects.StructType): + """This represents an open handle to a registry key and is not tied to the + registry hive file format on disk.""" + + def _skip_key_hive_entry_path(self, kcb_flags): + """Win10 14393 introduced an extra path element that it skips over by + checking for Flags that contain KEY_HIVE_ENTRY.""" + + # _CM_KEY_BODY.Trans introduced in Win10 14393 + if hasattr(self, "Trans") and RegKeyFlags.KEY_HIVE_ENTRY & kcb_flags == RegKeyFlags.KEY_HIVE_ENTRY: + return True + + return False + + def get_full_key_name(self) -> str: + output = [] + kcb = self.KeyControlBlock + while kcb.ParentKcb: + if kcb.NameBlock.Name is None: + break + + if self._skip_key_hive_entry_path(kcb.Flags): + kcb = kcb.ParentKcb + if not kcb: + break + + output.append( + kcb.NameBlock.Name.cast("string", + encoding = "utf8", + max_length = kcb.NameBlock.NameLength, + errors = "replace")) + kcb = kcb.ParentKcb + return "\\".join(reversed(output)) + + +class CM_KEY_NODE(objects.StructType): + """Extension to allow traversal of registry keys.""" + + def get_volatile(self) -> bool: + if not isinstance(self._context.layers[self.vol.layer_name], RegistryHive): + raise ValueError("Cannot determine volatility of registry key without an offset in a RegistryHive layer") + return bool(self.vol.offset & 0x80000000) + + def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Returns a list of the key nodes.""" + hive = self._context.layers[self.vol.layer_name] + if not isinstance(hive, RegistryHive): + raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer") + for index in range(2): + # Use get_cell because it should *always* be a KeyIndex + subkey_node = hive.get_cell(self.SubKeyLists[index]).u.KeyIndex + yield from self._get_subkeys_recursive(hive, subkey_node) + + def _get_subkeys_recursive( + self, hive: RegistryHive, + node: interfaces.objects.ObjectInterface) -> Iterable[interfaces.objects.ObjectInterface]: + """Recursively descend a node returning subkeys.""" + # The keylist appears to include 4 bytes of key name after each value + # We can either double the list and only use the even items, or + # We could change the array type to a struct with both parts + try: + signature = node.cast('string', max_length = 2, encoding = 'latin-1') + except (exceptions.InvalidAddressException, RegistryFormatException): + return + + listjump = None + if signature == 'ri': + listjump = 1 + elif signature == 'lh' or signature == 'lf': + listjump = 2 + elif node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"): + yield node + else: + vollog.debug("Unexpected node type encountered when traversing subkeys: {}, signature: {}".format( + node.vol.type_name, signature)) + + if listjump: + node.List.count = node.Count * listjump + for subnode_offset in node.List[::listjump]: + if (subnode_offset & 0x7fffffff) > hive.maximum_address: + vollog.log(constants.LOGLEVEL_VVV, + "Node found with address outside the valid Hive size: {}".format(hex(subnode_offset))) + else: + try: + subnode = hive.get_node(subnode_offset) + except (exceptions.InvalidAddressException, RegistryFormatException): + vollog.log(constants.LOGLEVEL_VVV, + "Failed to get node at {}, skipping".format(hex(subnode_offset))) + continue + yield from self._get_subkeys_recursive(hive, subnode) + + def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Returns a list of the Value nodes for a key.""" + hive = self._context.layers[self.vol.layer_name] + if not isinstance(hive, RegistryHive): + raise TypeError("CM_KEY_NODE was not instantiated on a RegistryHive layer") + child_list = hive.get_cell(self.ValueList.List).u.KeyList + child_list.count = self.ValueList.Count + + try: + for v in child_list: + if v != 0: + try: + node = hive.get_node(v) + except (RegistryInvalidIndex, RegistryFormatException) as excp: + vollog.debug("Invalid address {}".format(excp)) + continue + if node.vol.type_name.endswith(constants.BANG + '_CM_KEY_VALUE'): + yield node + except (exceptions.InvalidAddressException, RegistryFormatException) as excp: + vollog.debug("Invalid address in get_values iteration: {}".format(excp)) + return + + def get_name(self) -> interfaces.objects.ObjectInterface: + """Gets the name for the current key node""" + namelength = self.NameLength + self.Name.count = namelength + return self.Name.cast("string", max_length = namelength, encoding = "latin-1") + + def get_key_path(self) -> str: + reg = self._context.layers[self.vol.layer_name] + if not isinstance(reg, RegistryHive): + raise TypeError("Key was not instantiated on a RegistryHive layer") + # Using the offset adds a significant delay (since it cannot be cached easily) + # if self.vol.offset == reg.get_node(reg.root_cell_offset).vol.offset: + if self.vol.offset == reg.root_cell_offset + 4: + # return the last part of the hive name for the root entry + return reg.get_name().split('\\')[-1] + return reg.get_node(self.Parent).get_key_path() + '\\' + self.get_name() + + +class CM_KEY_VALUE(objects.StructType): + """Extensions to extract data from CM_KEY_VALUE nodes.""" + + def get_name(self) -> interfaces.objects.ObjectInterface: + """Gets the name for the current key value""" + namelength = self.NameLength + self.Name.count = namelength + return self.Name.cast("string", max_length = namelength, encoding = "latin-1") + + def decode_data(self) -> Union[int, bytes]: + """Properly decodes the data associated with the value node""" + # Determine if the data is stored inline + datalen = self.DataLength + data = b"" + # Check if the data is stored inline + layer = self._context.layers[self.vol.layer_name] + if not isinstance(layer, RegistryHive): + raise TypeError("Key value was not instantiated on a RegistryHive layer") + + # If the high-bit is set + if datalen & 0x80000000: + # Remove the high bit + datalen = datalen & 0x7fffffff + if (0 > datalen or datalen > 4): + raise ValueError("Unable to read inline registry value with excessive length: {}".format(datalen)) + else: + data = layer.read(self.Data.vol.offset, datalen) + elif layer.hive.Version == 5 and datalen > 0x4000: + # We're bigdata + big_data = layer.get_node(self.Data) + # Oddly, we get a list of addresses, at which are addresses, which then point to data blocks + for i in range(big_data.Count): + # The value 4 should actually be unsigned-int.size, but since it's a file format that shouldn't change + # the direct value 4 can be used instead + block_offset = layer.get_cell(big_data.List + (i * 4)).cast("unsigned int") + if isinstance(block_offset, int) and block_offset < layer.maximum_address: + amount = min(BIG_DATA_MAXLEN, datalen) + data += layer.read(offset = layer.get_cell(block_offset).vol.offset, length = amount) + datalen -= amount + else: + # Suspect Data actually points to a Cell, + # but the length at the start could be negative so just adding 4 to jump past it + data = layer.read(self.Data + 4, datalen) + + self_type = RegValueTypes.get(self.Type) + if self_type == RegValueTypes.REG_DWORD: + if len(data) != struct.calcsize("L"): + raise ValueError("Size of data does not match the type of registry value {}".format(self.get_name())) + return struct.unpack(">L", data)[0] + if self_type == RegValueTypes.REG_QWORD: + if len(data) != struct.calcsize(" bool: + """Determine if the structure is valid.""" + if self.Order < 0 or self.Order > 0xFFFF: + return False + + try: + _ = self.State.description + _ = self.Start.description + except ValueError: + return False + + return True + + def get_pid(self) -> Union[int, interfaces.renderers.BaseAbsentValue]: + """Return the pid of the process, if any.""" + if self.State.description != "SERVICE_RUNNING" or "PROCESS" not in self.get_type(): + return renderers.NotApplicableValue() + + try: + return self.ServiceProcess.ProcessId + except exceptions.InvalidAddressException: + return renderers.UnreadableValue() + + def get_binary(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + """Returns the binary associated with the service.""" + if self.State.description != "SERVICE_RUNNING": + return renderers.NotApplicableValue() + + # depending on whether the service is for a process + # or kernel driver, the binary path is stored differently + try: + if "PROCESS" in self.get_type(): + return self.ServiceProcess.BinaryPath.dereference().cast("string", + encoding = "utf-16", + errors = "replace", + max_length = 512) + else: + return self.DriverName.dereference().cast("string", + encoding = "utf-16", + errors = "replace", + max_length = 512) + except exceptions.InvalidAddressException: + return renderers.UnreadableValue() + + def get_name(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + """Returns the service name.""" + try: + return self.ServiceName.dereference().cast("string", + encoding = "utf-16", + errors = "replace", + max_length = 512) + except exceptions.InvalidAddressException: + return renderers.UnreadableValue() + + def get_display(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: + """Returns the service display.""" + try: + return self.DisplayName.dereference().cast("string", + encoding = "utf-16", + errors = "replace", + max_length = 512) + except exceptions.InvalidAddressException: + return renderers.UnreadableValue() + + def get_type(self) -> str: + """Returns the binary types.""" + + SERVICE_TYPE_FLAGS = { + 'SERVICE_KERNEL_DRIVER': 1, + 'SERVICE_FILE_SYSTEM_DRIVER': 2, + 'SERVICE_ADAPTOR': 4, + 'SERVICE_RECOGNIZER_DRIVER': 8, + 'SERVICE_WIN32_OWN_PROCESS': 16, + 'SERVICE_WIN32_SHARE_PROCESS': 32, + 'SERVICE_INTERACTIVE_PROCESS': 256 + } + + type_flags = Flags(choices = SERVICE_TYPE_FLAGS) + return "|".join(type_flags(self.Type)) + + def traverse(self): + """Generator that enumerates other services.""" + + try: + if hasattr(self, "PrevEntry"): + yield self + # make sure we dereference these pointers, or the + # is_valid() checks will apply to the pointer and + # not the _SERVICE_RECORD object as intended. + rec = self.PrevEntry + while rec and rec.is_valid(): + yield rec + rec = rec.PrevEntry + else: + rec = self + while rec and rec.is_valid(): + yield rec + rec = rec.ServiceList.Blink.dereference() + except exceptions.InvalidAddressException: + return + + +class SERVICE_HEADER(objects.StructType): + """A service header structure.""" + + def is_valid(self) -> bool: + """Determine if the structure is valid.""" + try: + return self.ServiceRecord.is_valid() + except exceptions.InvalidAddressException: + return False + + +class_types = {'_SERVICE_RECORD': SERVICE_RECORD, '_SERVICE_HEADER': SERVICE_HEADER} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/kdbg.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/kdbg.json new file mode 100644 index 00000000..cc1df0ab --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/kdbg.json @@ -0,0 +1,1025 @@ +{ + "symbols": {}, + "enums": {}, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_DBGKD_DEBUG_DATA_HEADER64": { + "fields": { + "OwnerTag": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 16 + }, + "List": { + "type": { + "kind": "struct", + "name": "LIST_ENTRY64" + }, + "offset": 0 + }, + "Size": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 24 + }, + "_KDDEBUGGER_DATA64": { + "fields": { + "MmBadPagesDetected": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 800 + }, + "KeUserCallbackDispatcher": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 64 + }, + "MmPagedPoolCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 368 + }, + "FramePointer": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 52 + }, + "MmPfnDatabase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 192 + }, + "KiProcessorBlock": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 536 + }, + "SizeEThread": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 704 + }, + "MmPeakCommitment": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 600 + }, + "OffsetPrcbContext": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 824 + }, + "ThCallbackStack": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 48 + }, + "GdtLdt": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 768 + }, + "MmModifiedPageListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 408 + }, + "OffsetPrcbCurrentThread": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 692 + }, + "OffsetPrcbVendorString": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 698 + }, + "ObpTypeObjectType": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 160 + }, + "MmSystemCacheEnd": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 176 + }, + "MmDriverCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 352 + }, + "OffsetPrcbCpuType": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 696 + }, + "MmTotalCommitLimit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 328 + }, + "MmResidentAvailablePages": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 432 + }, + "OffsetEprocessPeb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 682 + }, + "PsActiveProcessHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 80 + }, + "SizeEProcess": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 680 + }, + "Gdt64R3CmTeb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 774 + }, + "MmFreePageListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 392 + }, + "SizePrcb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 688 + }, + "CmNtCSDVersion": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 616 + }, + "MmNumberOfPagingFiles": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 224 + }, + "KiNormalSystemCall": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 528 + }, + "OffsetKThreadBStoreLimit": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 678 + }, + "NextCallback": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 50 + }, + "IopNumTriageDumpDataBlocks": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 776 + }, + "MmProcessCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 360 + }, + "MmPhysicalMemoryBlock": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 624 + }, + "OffsetPcrInitialBStore": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 744 + }, + "SizePcr": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 736 + }, + "OffsetKThreadBStore": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 676 + }, + "MmTotalCommittedPages": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 336 + }, + "OffsetKThreadApcProcess": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 672 + }, + "GdtR3Data": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 764 + }, + "GdtR0Pcr": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 760 + }, + "GdtR3Teb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 766 + }, + "OffsetPcrContainedPrcb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 742 + }, + "Gdt64R3CmCode": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 772 + }, + "KeBugCheckCallbackListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 128 + }, + "MmZeroedPageSingleBitErrorsDetected": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 808 + }, + "OffsetPcrSelfPcr": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 738 + }, + "MmPagedPoolStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 288 + }, + "MmStandbyPageListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 400 + }, + "MmVirtualTranslationBase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 656 + }, + "GdtR0Data": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 758 + }, + "EtwpDebuggerData": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 816 + }, + "KdPrintBufferSize": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 720 + }, + "OffsetPrcbProcStateSpecialReg": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 754 + }, + "MmSystemParentTablePage": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 648 + }, + "MmSubsectionBase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 216 + }, + "OffsetKThreadKernelStack": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 668 + }, + "MmHighestUserAddress": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 456 + }, + "KdPrintCircularBufferEnd": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 488 + }, + "MmPagedPoolEnd": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 296 + }, + "MmAvailablePages": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 424 + }, + "KiBugcheckData": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 136 + }, + "MmVerifierData": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 584 + }, + "OffsetKThreadState": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 674 + }, + "MmNonPagedSystemStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 264 + }, + "MmSystemPtesStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 200 + }, + "KeLoaderBlock": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 728 + }, + "OffsetPrcbProcStateContext": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 700 + }, + "MmNonPagedPoolStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 272 + }, + "VfCrashDataBlock": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 792 + }, + "ExpNumberOfPagedPools": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 112 + }, + "MmSharedCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 344 + }, + "MmUnloadedDrivers": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 544 + }, + "GdtTss": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 770 + }, + "KernBase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 24 + }, + "KeTimeIncrement": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 120 + }, + "OffsetPrcbPcrPage": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 752 + }, + "OffsetPcrCurrentPrcb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 740 + }, + "NtBuildLab": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 520 + }, + "MmLastUnloadedDriver": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 552 + }, + "MmTriageActionTaken": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 560 + }, + "PspCidTable": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 88 + }, + "MmHighestPhysicalPage": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 240 + }, + "PsLoadedModuleList": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 72 + }, + "KdPrintWritePointer": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 496 + }, + "MmNonPagedPoolEnd": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 280 + }, + "OffsetPrcbNumber": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 702 + }, + "MmSystemCacheWs": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 184 + }, + "BreakpointWithStatus": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 32 + }, + "MmLowestPhysicalPage": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 232 + }, + "MmSystemPtesEnd": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 208 + }, + "MmModifiedNoWritePageListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 416 + }, + "MmExtendedCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 376 + }, + "OffsetPrcbDpcRoutine": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 690 + }, + "GdtR0Code": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 756 + }, + "OffsetEprocessParentCID": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 684 + }, + "MmZeroedPageListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 384 + }, + "MmAllocatedNonPagedPool": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 592 + }, + "MmUserProbeAddress": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 472 + }, + "MmSystemCacheStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 168 + }, + "Header": { + "type": { + "kind": "struct", + "name": "_DBGKD_DEBUG_DATA_HEADER64" + }, + "offset": 0 + }, + "OffsetKThreadNextProcessor": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 664 + }, + "ExpPagedPoolDescriptor": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 104 + }, + "MmNumberOfPhysicalPages": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 248 + }, + "OffsetPcrBStoreLimit": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 746 + }, + "MmPagedPoolInformation": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 304 + }, + "OffsetPrcbMhz": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 694 + }, + "MmSizeOfPagedPoolInBytes": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 320 + }, + "ObpRootDirectoryObject": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 152 + }, + "KdPrintCircularBuffer": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 480 + }, + "SavedContext": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 40 + }, + "KiCallUserMode": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 56 + }, + "MmTotalCommitLimitMaximum": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 608 + }, + "GdtR3Code": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 762 + }, + "MmPageSize": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 312 + }, + "IopErrorLogListHead": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 144 + }, + "KdPrintRolloverCount": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 504 + }, + "KdPrintCircularBufferPtr": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 712 + }, + "OffsetKThreadTeb": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 666 + }, + "MmLoadedUserImageList": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 512 + }, + "OffsetPcrStackLimit": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 750 + }, + "MmSessionBase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 632 + }, + "NonPagedPoolDescriptor": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 448 + }, + "OffsetEprocessDirectoryTableBase": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 686 + }, + "IopTriageDumpDataBlocks": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 784 + }, + "OffsetKThreadInitialStack": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 670 + }, + "KernelVerifier": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 576 + }, + "PoolTrackTable": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 440 + }, + "MmSessionSize": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 640 + }, + "OffsetPcrInitialStack": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 748 + }, + "MmMaximumNonPagedPoolInBytes": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 256 + }, + "MmSystemRangeStart": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 464 + }, + "ExpSystemResourcesList": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 96 + }, + "MmSpecialPoolTag": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 568 + } + }, + "kind": "struct", + "size": 832 + }, + "LIST_ENTRY64": { + "fields": { + "Flink": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 0 + }, + "Blink": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 16 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2018-05-22T22:47:20.182954" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-sp12-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-sp12-x64.json new file mode 100644 index 00000000..2034be6b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-sp12-x64.json @@ -0,0 +1,536 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 16, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 48, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 100, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 102, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 80, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 104 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 72 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 130 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 88, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 106, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 108 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 528, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 84, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 86, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 80, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 536 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 22 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T14:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x64.json new file mode 100644 index 00000000..8ff0aebe --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x64.json @@ -0,0 +1,536 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 16, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 48, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 100, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 102, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 80, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 104 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 72 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 130 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 88, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 106, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 108 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 520, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 84, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 86, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 80, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 528 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 22 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T14:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x86.json new file mode 100644 index 00000000..33b4182f --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-vista-x86.json @@ -0,0 +1,537 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 352, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 46, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 40, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 356 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 12 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-14393-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-14393-x86.json new file mode 100644 index 00000000..a0fc0dba --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-14393-x86.json @@ -0,0 +1,535 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 436, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 8, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 56, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 440 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x64.json new file mode 100644 index 00000000..c212ea6b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x64.json @@ -0,0 +1,388 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 128, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS_WIN10_UDP" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 120, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 132 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 64, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 40, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 116 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 624, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 616, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "AddrInfo": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 112, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 108, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 632 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 20 + }, + "_LOCAL_ADDRESS_WIN10_UDP": { + "fields": { + "pData": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 26 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x86.json new file mode 100644 index 00000000..08ba89db --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-15063-x86.json @@ -0,0 +1,535 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 460, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 8, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 56, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 464 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x64.json new file mode 100644 index 00000000..c8560cef --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x64.json @@ -0,0 +1,389 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 128, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS_WIN10_UDP" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 120, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 132 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 64, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 40, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 116 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 600, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 616, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "AddrInfo": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 112, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 108, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 624 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 20 + }, + "_LOCAL_ADDRESS_WIN10_UDP": { + "fields": { + "pData": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 4 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 26 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T11:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x86.json new file mode 100644 index 00000000..549eee05 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win10-x86.json @@ -0,0 +1,535 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 432, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 8, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 56, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 436 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x64.json new file mode 100644 index 00000000..872073dd --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x64.json @@ -0,0 +1,535 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 88, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 16, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 72, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 124, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 126, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 80, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 104, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 128 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 48, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 74, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 80, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 88, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 96 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 130 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 88, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 106, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 108 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 568, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 108, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 110, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 104, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 576 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 22 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T14:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x86.json new file mode 100644 index 00000000..891e7072 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win7-x86.json @@ -0,0 +1,536 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 44, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 36, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 74, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 40, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 52, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 76 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 42, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 44, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 48, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 52 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 372, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 58, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 52, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 376 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 8, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 12 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x64.json new file mode 100644 index 00000000..993c6a12 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x64.json @@ -0,0 +1,536 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 16, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 48, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 100, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 102, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 80, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 104 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 72 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 130 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 88, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 106, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 108 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 592, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 112, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 108, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 600 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 26 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T14:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x86.json new file mode 100644 index 00000000..d4f22677 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win8-x86.json @@ -0,0 +1,537 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 372, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 8, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 56, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 376 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x64.json new file mode 100644 index 00000000..80bea783 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x64.json @@ -0,0 +1,536 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 16, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 48, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 100, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 102, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 80, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 104 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 0, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + } + }, + "LocalPort": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 64, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 72 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 88, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 128, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 130 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 88, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 96, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 106, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 108 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 600, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 40, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 16, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 112, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 114, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 108, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 608 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 16, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 24 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 26 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 48 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-06-12T14:00:00" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x86.json new file mode 100644 index 00000000..b80599be --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/netscan-win81-x86.json @@ -0,0 +1,537 @@ +{ + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned be short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "big" + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "user_types": { + "_TCP_SYN_ENDPOINT": { + "fields": { + "Owner": { + "offset": 32, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SYN_OWNER" + } + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 8, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 24, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 28, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 40, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_TIMEWAIT_ENDPOINT": { + "fields": { + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "InetAF": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 30, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "LocalAddr": { + "offset": 32, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "RemoteAddress": { + "offset": 36, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 40 + }, + "_UDP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 48, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "InetAF": { + "offset": 20, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 74 + }, + "_TCP_LISTENER": { + "fields": { + "Owner": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 32, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "LocalAddr": { + "offset": 52, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + + } + }, + "InetAF": { + "offset": 56, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "Port": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_TCP_ENDPOINT": { + "fields": { + "Owner": { + "offset": 424, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + + } + }, + "CreateTime": { + "offset": 0, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + }, + "ListEntry": { + "offset": 20, + "type": { + "kind": "union", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "AddrInfo": { + "offset": 12, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ADDRINFO" + } + } + }, + "InetAF": { + "offset": 8, + "type":{ + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_INETAF" + } + + } + }, + "LocalPort": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "RemotePort": { + "offset": 62, + "type": { + "kind": "base", + "name": "unsigned be short" + } + }, + "State": { + "offset": 56, + "type": { + "kind": "enum", + "name": "TCPStateEnum" + } + } + }, + "kind": "struct", + "size": 428 + }, + "_LOCAL_ADDRESS": { + "fields": { + "pData": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_ADDRINFO": { + "fields": { + "Local": { + "offset": 0, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_LOCAL_ADDRESS" + } + } + }, + "Remote": { + "offset": 12, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_IN_ADDR" + } + } + } + }, + "kind": "struct", + "size": 16 + }, + "_IN_ADDR": { + "fields": { + "addr4": { + "offset": 0, + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + }, + "addr6": { + "offset": 0, + "type": { + "count": 16, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + } + } + }, + "kind": "struct", + "size": 6 + }, + "_INETAF": { + "fields": { + "AddressFamily": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 14 + }, + "_SYN_OWNER": { + "fields": { + "Process": { + "offset": 24, + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_EPROCESS" + } + } + } + }, + "kind": "struct", + "size": 14 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + } + }, + "enums": { + "TCPStateEnum": { + "base": "long", + "constants": { + "CLOSED": 0, + "LISTENING": 1, + "SYN_SENT": 2, + "SYN_RCVD": 3, + "ESTABLISHED": 4, + "FIN_WAIT1": 5, + "FIN_WAIT2": 6, + "CLOSE_WAIT": 7, + "CLOSING": 8, + "LAST_ACK": 9, + "TIME_WAIT": 12, + "DELETE_TCB": 13 + }, + "size": 4 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "japhlange-by-hand", + "datetime": "2020-05-29T19:28:34" + }, + "format": "6.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.json new file mode 100644 index 00000000..ffa98f1a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.json @@ -0,0 +1,1598 @@ +{ + "symbols": { + }, + "user_types": { + "pascal_string": { + "fields": { + "length": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "string": { + "offset": 1, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 2 + }, + "Type_Properties": { + "fields": { + "packed": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "constructors": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "overloaded_operators": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "nested_type": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 3, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "contains_nested": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 4, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "overloaded_assignment": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 5, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "overloaded_casting": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 6, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "forward_reference": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 7, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "scoped_definition": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "unique_name": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 9, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "sealed": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 10, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "hfa": { + "offset": 0, + "type": { + "bit_length": 2, + "bit_position": 11, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "intrinsic_type": { + "offset": 0, + "type": { + "bit_length": 1, + "bit_position": 12, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "mocom": { + "offset": 0, + "type": { + "bit_length": 2, + "bit_position": 13, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + } + }, + "kind": "struct", + "size": 2 + }, + "SI_PERSIST": { + "fields": { + "StreamInfoSize": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "mpspnpn": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "MSF_HDR": { + "fields": { + "Magic": { + "offset": 0, + "type": { + "count": 44, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "PageSize": { + "offset": 44, + "type": { + "kind": "base", + "name": "long" + } + }, + "FreePageMapNumber": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumPages": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "StreamInfo": { + "offset": 52, + "type": { + "kind": "struct", + "name": "SI_PERSIST" + } + } + }, + "kind": "struct", + "size": 60 + }, + "BIG_MSF_HDR": { + "fields": { + "Magic": { + "offset": 0, + "type": { + "count": 30, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "PageSize": { + "offset": 32, + "type": { + "kind": "base", + "name": "long" + } + }, + "FreePageMapNumber": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "NumPages": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "StreamInfo": { + "offset": 44, + "type": { + "kind": "struct", + "name": "SI_PERSIST" + } + } + }, + "kind": "struct", + "size": 52 + }, + "TPI_HEADER": { + "fields": { + "version": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "header_size": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "index_min": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "index_max": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "gprec_size": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "tpi_hash_stream": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "tpi_hash_pad_stream": { + "offset": 22, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "hash_key_size": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "hash_bucket_size": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "hash_values_offset": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "hash_values_size": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ti_off_offset": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ti_off_size": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "hash_adj_offset": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "hash_adj_size": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 56 + }, + "DBI_HEADER": { + "fields": { + "magic": { + "offset": 0, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "version": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "age": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "gssymStream": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "vers": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "pssymStream": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "pdbver": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "symrecStream": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "pdbver2": { + "offset": 22, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "module_size": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "secconSize": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "secmapSize": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "filinfSize": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "tsmapSize": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "mfcIndex": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "dbghdrSize": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ecinfoSize": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "flags": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "machine": { + "offset": 58, + "type": { + "kind": "enum", + "name": "MACHINE_TYPE" + } + }, + "reserved": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 64 + }, + "DBI_DBG_HEADER": { + "fields": { + "snFPO": { + "offset": 0, + "type": { + "kind": "base", + "name": "short" + } + }, + "snException": { + "offset": 2, + "type": { + "kind": "base", + "name": "short" + } + }, + "snFixup": { + "offset": 4, + "type": { + "kind": "base", + "name": "short" + } + }, + "snOmapToSrc": { + "offset": 6, + "type": { + "kind": "base", + "name": "short" + } + }, + "snOmapFromSrc": { + "offset": 8, + "type": { + "kind": "base", + "name": "short" + } + }, + "snSectionHdr": { + "offset": 10, + "type": { + "kind": "base", + "name": "short" + } + }, + "snTokenRidMap": { + "offset": 12, + "type": { + "kind": "base", + "name": "short" + } + }, + "snXdata": { + "offset": 14, + "type": { + "kind": "base", + "name": "short" + } + }, + "snPdata": { + "offset": 16, + "type": { + "kind": "base", + "name": "short" + } + }, + "snNewFPO": { + "offset": 18, + "type": { + "kind": "base", + "name": "short" + } + }, + "snSectionHdrOrig": { + "offset": 20, + "type": { + "kind": "base", + "name": "short" + } + } + }, + "kind": "struct", + "size": 22 + }, + "GLOBAL_SYMBOL": { + "fields": { + "length": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "leaf_type": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "symtype": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "segment": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 14, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 14 + }, + "IMAGE_SECTION_HEADER": { + "fields": { + "Characteristics": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Misc": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Name": { + "offset": 0, + "type": { + "count": 8, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "NumberOfLinenumbers": { + "offset": 34, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfRelocations": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "PointerToLinenumbers": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PointerToRawData": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PointerToRelocations": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfRawData": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "VirtualAddress": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 40 + }, + "LF_ARRAY": { + "fields": { + "element_type": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "indexing_type": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "size": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 10, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 10 + }, + "LF_BITFIELD": { + "fields": { + "underlying_type": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "length": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "position": { + "offset": 5, + "type": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "kind": "struct", + "size": 0 + }, + "LF_ENUM": { + "fields": { + "count": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "properties": { + "offset": 2, + "type": { + "kind": "struct", + "name": "Type_Properties" + } + }, + "subtype_index": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "fields": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "name": { + "offset": 12, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 12 + }, + "LF_ENUMERATE": { + "fields": { + "attributes": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "value": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 4, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 4 + }, + "LF_MEMBER": { + "fields": { + "attributes": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "field_type": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 8, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 8 + }, + "LF_MODIFIER": { + "fields": { + "subtype_index": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "constant": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "volatile": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 1, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "unaligned": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 2, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + } + }, + "kind": "struct", + "size": 6 + }, + "LF_POINTER": { + "fields": { + "subtype_index": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "pointer_type": { + "offset": 4, + "type": { + "bit_length": 5, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "mode": { + "offset": 4, + "type": { + "bit_length": 3, + "bit_position": 5, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "flat32": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "volatile": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 9, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "constant": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 10, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "unaligned": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 11, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "restricted": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 12, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "size": { + "offset": 4, + "type": { + "bit_length": 6, + "bit_position": 13, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "mocom": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 19, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "lref": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 20, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "rref": { + "offset": 4, + "type": { + "bit_length": 1, + "bit_position": 21, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + } + }, + "kind": "struct", + "size": 6 + }, + "LF_PROCEDURE": { + "fields": { + "return_type": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "attributes": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "parameter_count": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "argument_list": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 12 + }, + "LF_STRUCTURE": { + "fields": { + "count": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "properties": { + "offset": 2, + "type": { + "kind": "struct", + "name": "Type_Properties" + } + }, + "fields": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "derived_from": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "vtable_shape": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "size": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 18, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 18 + }, + "LF_UNION": { + "fields": { + "count": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "properties": { + "offset": 2, + "type": { + "kind": "struct", + "name": "Type_Properties" + } + }, + "fields": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "size": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "name": { + "offset": 10, + "type": { + "kind": "base", + "name": "string" + } + } + }, + "kind": "struct", + "size": 10 + }, + "OMAP_RECORD": { + "fields": { + "source_address": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "target_address": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "PDB_INFORMATION": { + "fields": { + "version": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "signature": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "age": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "GUID": { + "offset": 12, + "type": { + "count": 16, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + } + }, + "kind": "struct", + "size": 0 + } + }, + "enums": { + "MACHINE_TYPE": { + "base": "unsigned short", + "constants": { + "IMAGE_FILE_MACHINE_UNKNOWN": 0, + "IMAGE_FILE_MACHINE_I386": 332, + "IMAGE_FILE_MACHINE_R3000": 354, + "IMAGE_FILE_MACHINE_R4000": 358, + "IMAGE_FILE_MACHINE_R10000": 360, + "IMAGE_FILE_MACHINE_WCEMIPSV2": 361, + "IMAGE_FILE_MACHINE_ALPHA": 388, + "IMAGE_FILE_MACHINE_SH3": 418, + "IMAGE_FILE_MACHINE_SH3DSP": 419, + "IMAGE_FILE_MACHINE_SH3E": 420, + "IMAGE_FILE_MACHINE_SH4": 422, + "IMAGE_FILE_MACHINE_SH5": 424, + "IMAGE_FILE_MACHINE_ARM": 448, + "IMAGE_FILE_MACHINE_THUMB": 450, + "IMAGE_FILE_MACHINE_ARMNT": 452, + "IMAGE_FILE_MACHINE_AM33": 467, + "IMAGE_FILE_MACHINE_POWERPC": 496, + "IMAGE_FILE_MACHINE_POWERPCFP": 497, + "IMAGE_FILE_MACHINE_IA64": 512, + "IMAGE_FILE_MACHINE_MIPS16": 614, + "IMAGE_FILE_MACHINE_ALPHA64": 644, + "IMAGE_FILE_MACHINE_MIPSFPU": 870, + "IMAGE_FILE_MACHINE_MIPSFPU16": 1126, + "IMAGE_FILE_MACHINE_TRICORE": 1312, + "IMAGE_FILE_MACHINE_CEF": 3311, + "IMAGE_FILE_MACHINE_EBC": 3772, + "IMAGE_FILE_MACHINE_AMD64": 34404, + "IMAGE_FILE_MACHINE_M32R": 36929, + "IMAGE_FILE_MACHINE_CEE": 49390 + }, + "size": 2 + }, + "LEAF_TYPE": { + "base": "unsigned short", + "constants": { + "LF_MODIFIER_16t": 1, + "LF_POINTER_16t": 2, + "LF_ARRAY_16t": 3, + "LF_CLASS_16t": 4, + "LF_STRUCTURE_16t": 5, + "LF_UNION_16t": 6, + "LF_ENUM_16t": 7, + "LF_PROCEDURE_16t": 8, + "LF_MFUNCTION_16t": 9, + "LF_VTSHAPE": 10, + "LF_COBOL0_16t": 11, + "LF_COBOL1": 12, + "LF_BARRAY_16t": 13, + "LF_LABEL": 14, + "LF_NULL": 15, + "LF_NOTTRAN": 16, + "LF_DIMARRAY_16t": 17, + "LF_VFTPATH_16t": 18, + "LF_PRECOMP_16t": 19, + "LF_ENDPRECOMP": 20, + "LF_OEM_16t": 21, + "LF_TYPESERVER_ST": 22, + "LF_SKIP_16t": 512, + "LF_ARGLIST_16t": 513, + "LF_DEFARG_16t": 514, + "LF_LIST": 515, + "LF_FIELDLIST_16t": 516, + "LF_DERIVED_16t": 517, + "LF_BITFIELD_16t": 518, + "LF_METHODLIST_16t": 519, + "LF_DIMCONU_16t": 520, + "LF_DIMCONLU_16t": 521, + "LF_DIMVARU_16t": 522, + "LF_DIMVARLU_16t": 523, + "LF_REFSYM": 524, + "LF_BCLASS_16t": 1024, + "LF_VBCLASS_16t": 1025, + "LF_IVBCLASS_16t": 1026, + "LF_ENUMERATE_ST": 1027, + "LF_FRIENDFCN_16t": 1028, + "LF_INDEX_16t": 1029, + "LF_MEMBER_16t": 1030, + "LF_STMEMBER_16t": 1031, + "LF_METHOD_16t": 1032, + "LF_NESTTYPE_16t": 1033, + "LF_VFUNCTAB_16t": 1034, + "LF_FRIENDCLS_16t": 1035, + "LF_ONEMETHOD_16t": 1036, + "LF_VFUNCOFF_16t": 1037, + "LF_TI16_MAX": 4096, + "LF_MODIFIER": 4097, + "LF_POINTER": 4098, + "LF_ARRAY_ST": 4099, + "LF_CLASS_ST": 4100, + "LF_STRUCTURE_ST": 4101, + "LF_UNION_ST": 4102, + "LF_ENUM_ST": 4103, + "LF_PROCEDURE": 4104, + "LF_MFUNCTION": 4105, + "LF_COBOL0": 4106, + "LF_BARRAY": 4107, + "LF_DIMARRAY_ST": 4108, + "LF_VFTPATH": 4109, + "LF_PRECOMP_ST": 4110, + "LF_OEM": 4111, + "LF_ALIAS_ST": 4112, + "LF_OEM2": 4113, + "LF_SKIP": 4608, + "LF_ARGLIST": 4609, + "LF_DEFARG_ST": 4610, + "LF_FIELDLIST": 4611, + "LF_DERIVED": 4612, + "LF_BITFIELD": 4613, + "LF_METHODLIST": 4614, + "LF_DIMCONU": 4615, + "LF_DIMCONLU": 4616, + "LF_DIMVARU": 4617, + "LF_DIMVARLU": 4618, + "LF_BCLASS": 5120, + "LF_VBCLASS": 5121, + "LF_IVBCLASS": 5122, + "LF_FRIENDFCN_ST": 5123, + "LF_INDEX": 5124, + "LF_MEMBER_ST": 5125, + "LF_STMEMBER_ST": 5126, + "LF_METHOD_ST": 5127, + "LF_NESTTYPE_ST": 5128, + "LF_VFUNCTAB": 5129, + "LF_FRIENDCLS": 5130, + "LF_ONEMETHOD_ST": 5131, + "LF_VFUNCOFF": 5132, + "LF_NESTTYPEEX_ST": 5133, + "LF_MEMBERMODIFY_ST": 5134, + "LF_MANAGED_ST": 5135, + "LF_ST_MAX": 5376, + "LF_TYPESERVER": 5377, + "LF_ENUMERATE": 5378, + "LF_ARRAY": 5379, + "LF_CLASS": 5380, + "LF_STRUCTURE": 5381, + "LF_UNION": 5382, + "LF_ENUM": 5383, + "LF_DIMARRAY": 5384, + "LF_PRECOMP": 5385, + "LF_ALIAS": 5386, + "LF_DEFARG": 5387, + "LF_FRIENDFCN": 5388, + "LF_MEMBER": 5389, + "LF_STMEMBER": 5390, + "LF_METHOD": 5391, + "LF_NESTTYPE": 5392, + "LF_ONEMETHOD": 5393, + "LF_NESTTYPEEX": 5394, + "LF_MEMBERMODIFY": 5395, + "LF_MANAGED": 5396, + "LF_TYPESERVER2": 5397, + "LF_STRIDED_ARRAY": 5398, + "LF_HLSL": 5399, + "LF_MODIFIER_EX": 5400, + "LF_INTERFACE": 5401, + "LF_BINTERFACE": 5402, + "LF_VECTOR": 5403, + "LF_MATRIX": 5404, + "LF_VFTABLE": 5405, + "LF_FUNC_ID": 5633, + "LF_MFUNC_ID": 5634, + "LF_BUILDINFO": 5635, + "LF_SUBSTR_LIST": 5636, + "LF_STRING_ID": 5637, + "LF_UDT_SRC_LINE": 5638, + "LF_UDT_MOD_SRC_LINE": 5639, + "LF_CHAR": 32768, + "LF_SHORT": 32769, + "LF_USHORT": 32770, + "LF_LONG": 32771, + "LF_ULONG": 32772, + "LF_REAL32": 32773, + "LF_REAL64": 32774, + "LF_REAL80": 32775, + "LF_REAL128": 32776, + "LF_QUADWORD": 32777, + "LF_UQUADWORD": 32778, + "LF_REAL48": 32779, + "LF_COMPLEX32": 32780, + "LF_COMPLEX64": 32781, + "LF_COMPLEX80": 32782, + "LF_COMPLEX128": 32783, + "LF_VARSTRING": 32784, + "LF_OCTWORD": 32791, + "LF_UOCTWORD": 32792, + "LF_DECIMAL": 32793, + "LF_DATE": 32794, + "LF_UTF8STRING": 32795, + "LF_REAL16": 32796, + "LF_PAD0": 240, + "LF_PAD1": 241, + "LF_PAD2": 242, + "LF_PAD3": 243, + "LF_PAD4": 244, + "LF_PAD5": 245, + "LF_PAD6": 246, + "LF_PAD7": 247, + "LF_PAD8": 248, + "LF_PAD9": 249, + "LF_PAD10": 250, + "LF_PAD11": 251, + "LF_PAD12": 252, + "LF_PAD13": 253, + "LF_PAD14": 254, + "LF_PAD15": 255 + }, + "size": 2 + } + }, + "base_types": { + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "short": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 2 + }, + "long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "little", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "ikelos-by-hand", + "datetime": "2019-05-22T15:51:03" + }, + "format": "4.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.py new file mode 100644 index 00000000..dd8db5af --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdb.py @@ -0,0 +1,291 @@ +# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import binascii +import json +import logging +import lzma +import os +import struct +from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from urllib import request + +from volatility import symbols +from volatility.framework import constants, interfaces +from volatility.framework.configuration.requirements import SymbolTableRequirement +from volatility.framework.symbols import intermed +from volatility.framework.symbols.windows import pdbconv + +vollog = logging.getLogger(__name__) + + +class PDBUtility: + """Class to handle and manage all getting symbols based on MZ header""" + + @classmethod + def symbol_table_from_offset(cls, + context: interfaces.context.ContextInterface, + layer_name: str, + offset: int, + symbol_table_class: str, + config_path: str = None, + progress_callback: constants.ProgressCallback = None) -> Optional[str]: + """Produces the name of a symbol table loaded from the offset for an MZ header + + Args: + context: The context on which to operate + layer_name: The name of the (contiguous) layer within the context that contains the MZ file + offset: The offset in the layer at which the MZ file begins + symbol_table_class: The type of symbol class to construct the SymbolTable + config_path: New path for the produced symbol table configuration with the config tree + progress_callback: Callable called to update ongoing progress + + Returns: + None if no pdb information can be determined, else returned the name of the loaded symbols for the MZ + """ + result = cls.get_guid_from_mz(context, layer_name, offset) + if result is None: + return None + guid, age, pdb_name = result + if config_path is None: + config_path = interfaces.configuration.path_join('pdbutility', pdb_name.replace('.', '_')) + + return cls.load_windows_symbol_table(context, guid, age, pdb_name, symbol_table_class, config_path, + progress_callback) + + @classmethod + def load_windows_symbol_table(cls, + context: interfaces.context.ContextInterface, + guid: str, + age: int, + pdb_name: str, + symbol_table_class: str, + config_path: str = 'pdbutility', + progress_callback: constants.ProgressCallback = None): + """Loads (downlading if necessary) a windows symbol table""" + + filter_string = os.path.join(pdb_name.strip('\x00'), guid.upper() + "-" + str(age)) + + isf_path = False + # Take the first result of search for the intermediate file + for value in intermed.IntermediateSymbolTable.file_symbol_url("windows", filter_string): + isf_path = value + break + else: + # If none are found, attempt to download the pdb, convert it and try again + cls.download_pdb_isf(context, guid.upper(), age, pdb_name, progress_callback) + # Try again + for value in intermed.IntermediateSymbolTable.file_symbol_url("windows", filter_string): + isf_path = value + break + + if not isf_path: + vollog.debug("Required symbol library path not found: {}".format(filter_string)) + return None + + vollog.debug("Using symbol library: {}".format(filter_string)) + + # Set the discovered options + join = interfaces.configuration.path_join + context.config[join(config_path, "class")] = symbol_table_class + context.config[join(config_path, "isf_url")] = isf_path + parent_config_path = interfaces.configuration.parent_path(config_path) + requirement_name = interfaces.configuration.path_head(config_path) + + # Construct the appropriate symbol table + requirement = SymbolTableRequirement(name = requirement_name, description = "PDBUtility generated symbol table") + requirement.construct(context, parent_config_path) + return context.config[config_path] + + @classmethod + def get_guid_from_mz(cls, context: interfaces.context.ContextInterface, layer_name: str, + offset: int) -> Optional[Tuple[str, int, str]]: + """Takes the offset to an MZ header, locates any available pdb headers, and extracts the guid, age and pdb_name from them + + Args: + context: The context on which to operate + layer_name: The name of the (contiguous) layer within the context that contains the MZ file + offset: The offset in the layer at which the MZ file begins + + Returns: + A tuple of the guid, age and pdb_name, or None if no PDB record can be found + """ + try: + import pefile + except ImportError: + vollog.error("Get_guid_from_mz requires the following python module: pefile") + return None + + layer = context.layers[layer_name] + mz_sig = layer.read(offset, 2) + + # Check it is actually the MZ header + if mz_sig != b"MZ": + return None + + nt_header_start = ord(layer.read(offset + 0x3C, 1)) + optional_header_size = struct.unpack(' None: + """Attempts to download the PDB file, convert it to an ISF file and + save it to one of the symbol locations.""" + # Check for writability + filter_string = os.path.join(pdb_name, guid + "-" + str(age)) + for path in symbols.__path__: + + # Store any temporary files created by downloading PDB files + tmp_files = [] + potential_output_filename = os.path.join(path, "windows", filter_string + ".json.xz") + data_written = False + try: + os.makedirs(os.path.dirname(potential_output_filename), exist_ok = True) + with lzma.open(potential_output_filename, "w") as of: + # Once we haven't thrown an error, do the computation + filename = pdbconv.PdbRetreiver().retreive_pdb(guid + str(age), + file_name = pdb_name, + progress_callback = progress_callback) + if filename: + tmp_files.append(filename) + location = "file:" + request.pathname2url(tmp_files[-1]) + json_output = pdbconv.PdbReader(context, location, progress_callback).get_json() + of.write(bytes(json.dumps(json_output, indent = 2, sort_keys = True), 'utf-8')) + # After we've successfully written it out, record the fact so we don't clear it out + data_written = True + else: + vollog.warning("Symbol file could not be found on remote server" + (" " * 100)) + break + except PermissionError: + vollog.warning("Cannot write necessary symbol file, please check permissions on {}".format( + potential_output_filename)) + continue + finally: + # If something else failed, removed the symbol file so we don't pick it up in the future + if not data_written and os.path.exists(potential_output_filename): + os.remove(potential_output_filename) + # Clear out all the temporary file if we constructed one + for filename in tmp_files: + try: + os.remove(filename) + except PermissionError: + vollog.warning("Temporary file could not be removed: {}".format(filename)) + else: + vollog.warning("Cannot write downloaded symbols, please add the appropriate symbols" + " or add/modify a symbols directory that is writable") + + @classmethod + def pdbname_scan(cls, + ctx: interfaces.context.ContextInterface, + layer_name: str, + page_size: int, + pdb_names: List[bytes], + progress_callback: constants.ProgressCallback = None, + start: Optional[int] = None, + end: Optional[int] = None) -> Generator[Dict[str, Optional[Union[bytes, str, int]]], None, None]: + """Scans through `layer_name` at `ctx` looking for RSDS headers that + indicate one of four common pdb kernel names (as listed in + `self.pdb_names`) and returns the tuple (GUID, age, pdb_name, + signature_offset, mz_offset) + + .. note:: This is automagical and therefore not guaranteed to provide correct results. + + The UI should always provide the user an opportunity to specify the + appropriate types and PDB values themselves + """ + min_pfn = 0 + + if start is None: + start = ctx.layers[layer_name].minimum_address + if end is None: + end = ctx.layers[layer_name].maximum_address + + for (GUID, age, pdb_name, + signature_offset) in ctx.layers[layer_name].scan(ctx, + PdbSignatureScanner(pdb_names), + progress_callback = progress_callback, + sections = [(start, end - start)]): + mz_offset = None + sig_pfn = signature_offset // page_size + + for i in range(sig_pfn, min_pfn, -1): + if not ctx.layers[layer_name].is_valid(i * page_size, 2): + break + + data = ctx.layers[layer_name].read(i * page_size, 2) + if data == b'MZ': + mz_offset = i * page_size + break + min_pfn = sig_pfn + + yield { + 'GUID': GUID, + 'age': age, + 'pdb_name': str(pdb_name, "utf-8"), + 'signature_offset': signature_offset, + 'mz_offset': mz_offset + } + + +class PdbSignatureScanner(interfaces.layers.ScannerInterface): + """A :class:`~volatility.framework.interfaces.layers.ScannerInterface` + based scanner use to identify Windows PDB records. + + Args: + pdb_names: A list of bytestrings, used to match pdb signatures against the pdb names within the records. + + .. note:: The pdb_names must be a list of byte strings, unicode strs will not match against the data scanned + """ + overlap = 0x4000 + """The size of overlap needed for the signature to ensure data cannot hide between two scanned chunks""" + thread_safe = True + """Determines whether the scanner accesses global variables in a thread safe manner (for use with :mod:`multiprocessing`)""" + + _RSDS_format = struct.Struct("<16BI") + + def __init__(self, pdb_names: List[bytes]) -> None: + super().__init__() + self._pdb_names = pdb_names + + def __call__(self, data: bytes, data_offset: int) -> Generator[Tuple[str, Any, bytes, int], None, None]: + sig = data.find(b"RSDS") + while sig >= 0: + null = data.find(b'\0', sig + 4 + self._RSDS_format.size) + if null > -1: + if (null - sig - self._RSDS_format.size) <= 100: + name_offset = sig + 4 + self._RSDS_format.size + pdb_name = data[name_offset:null] + if pdb_name in self._pdb_names: + + ## this ordering is intentional due to mixed endianness in the GUID + (g3, g2, g1, g0, g5, g4, g7, g6, g8, g9, ga, gb, gc, gd, ge, gf, a) = \ + self._RSDS_format.unpack(data[sig + 4:name_offset]) + + guid = (16 * '{:02X}').format(g0, g1, g2, g3, g4, g5, g6, g7, g8, g9, ga, gb, gc, gd, ge, gf) + if sig < self.chunk_size: + yield (guid, a, pdb_name, data_offset + sig) + sig = data.find(b"RSDS", sig + 1) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdbconv.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdbconv.py new file mode 100644 index 00000000..4f183608 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pdbconv.py @@ -0,0 +1,985 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import binascii +import datetime +import json +import logging +import os +from bisect import bisect +from typing import Tuple, Dict, Any, Optional, Union, List +from urllib import request, error + +from volatility.framework import contexts, interfaces, constants +from volatility.framework.layers import physical, msf, resources + +vollog = logging.getLogger(__name__) + +primatives = { + 0x03: ("void", { + "endian": "little", + "kind": "void", + "signed": True, + "size": 0 + }), + 0x08: ("HRESULT", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 4 + }), + 0x10: ("char", { + "endian": "little", + "kind": "char", + "signed": True, + "size": 1 + }), + 0x20: ("unsigned char", { + "endian": "little", + "kind": "char", + "signed": False, + "size": 1 + }), + 0x68: ("int8", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 1 + }), + 0x69: ("uint8", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 1 + }), + 0x70: ("char", { + "endian": "little", + "kind": "char", + "signed": True, + "size": 1 + }), + 0x71: ("wchar", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 2 + }), + # 0x7a: ("rchar16", {}), + # 0x7b: ("rchar32", {}), + 0x11: ("short", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 2 + }), + 0x21: ("unsigned short", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 2 + }), + 0x72: ("short", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 2 + }), + 0x73: ("unsigned short", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 2 + }), + 0x12: ("long", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 4 + }), + 0x22: ("unsigned long", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 4 + }), + 0x74: ("int", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 4 + }), + 0x75: ("unsigned int", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 4 + }), + 0x13: ("long long", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 8 + }), + 0x23: ("unsigned long long", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 8 + }), + 0x76: ("long long", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 8 + }), + 0x77: ("unsigned long long", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 8 + }), + 0x14: ("int128", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 16 + }), + 0x24: ("uint128", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 16 + }), + 0x78: ("int128", { + "endian": "little", + "kind": "int", + "signed": True, + "size": 16 + }), + 0x79: ("uint128", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 16 + }), + 0x46: ("f16", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 2 + }), + 0x40: ("f32", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 4 + }), + 0x45: ("f32pp", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 4 + }), + 0x44: ("f48", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 6 + }), + 0x41: ("double", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 8 + }), + 0x42: ("f80", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 10 + }), + 0x43: ("f128", { + "endian": "little", + "kind": "float", + "signed": True, + "size": 16 + }) +} + +indirections = { + 0x100: ("pointer16", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 2 + }), + 0x400: ("pointer32", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 4 + }), + 0x600: ("pointer64", { + "endian": "little", + "kind": "int", + "signed": False, + "size": 8 + }) +} + + +class ForwardArrayCount: + + def __init__(self, size, element_type): + self.element_type = element_type + self.size = size + + +class PdbReader: + """Class to read Microsoft PDB files. + + This reads the various streams according to various sources as to how pdb should be read. + These sources include: + + https://docs.rs/crate/pdb/0.5.0/source/src/ + https://github.com/moyix/pdbparse + https://llvm.org/docs/PDB/index.html + https://github.com/Microsoft/microsoft-pdb/ + + In order to generate ISF files, we need the type stream (2), and the symbols stream (variable). + The MultiStream Format wrapper is handled as a volatility layer, which constructs sublayers for each stream. + The streams can then be read contiguously allowing the data to be accessed. + + Volatility's type system is strong when everything must be laid out in advance, but PDB data is reasonably dynamic, + particularly when it comes to names. We must therefore parse it after we've collected other information already. + This is in comparison to something such as Construct/pdbparse which can use just-parsed data to determine dynamically + sized data following. + """ + + def __init__(self, + context: interfaces.context.ContextInterface, + location: str, + progress_callback: constants.ProgressCallback = None) -> None: + self._layer_name, self._context = self.load_pdb_layer(context, location) + self._dbiheader = None # type: Optional[interfaces.objects.ObjectInterface] + if not progress_callback: + progress_callback = lambda x, y: None + self._progress_callback = progress_callback + self.types = [ + ] # type: List[Tuple[interfaces.objects.ObjectInterface, Optional[str], interfaces.objects.ObjectInterface]] + self.bases = {} # type: Dict[str, Any] + self.user_types = {} # type: Dict[str, Any] + self.enumerations = {} # type: Dict[str, Any] + self.symbols = {} # type: Dict[str, Any] + self._omap_mapping = [] # type: List[Tuple[int, int]] + self._sections = [] # type: List[interfaces.objects.ObjectInterface] + self.metadata = {"format": "6.1.0", "windows": {}} + + @property + def context(self): + return self._context + + @property + def pdb_layer_name(self): + return self._layer_name + + @classmethod + def load_pdb_layer(cls, context: interfaces.context.ContextInterface, + location: str) -> Tuple[str, interfaces.context.ContextInterface]: + """Loads a PDB file into a layer within the context and returns the + name of the new layer. + + Note: the context may be changed by this method + """ + physical_layer_name = context.layers.free_layer_name("FileLayer") + physical_config_path = interfaces.configuration.path_join("pdbreader", physical_layer_name) + + # Create the file layer + # This must be specific to get us started, setup the config and run + new_context = context.clone() + new_context.config[interfaces.configuration.path_join(physical_config_path, "location")] = location + + physical_layer = physical.FileLayer(new_context, physical_config_path, physical_layer_name) + new_context.add_layer(physical_layer) + + # Add on the MSF format layer + msf_layer_name = context.layers.free_layer_name("MSFLayer") + msf_config_path = interfaces.configuration.path_join("pdbreader", msf_layer_name) + new_context.config[interfaces.configuration.path_join(msf_config_path, "base_layer")] = physical_layer_name + msf_layer = msf.PdbMultiStreamFormat(new_context, msf_config_path, msf_layer_name) + new_context.add_layer(msf_layer) + + msf_layer.read_streams() + + return msf_layer_name, new_context + + def reset(self): + self.bases = {} + self.user_types = {} + self.enumerations = {} + self.symbols = {} + self._sections = [] + self._omap_mapping = [] + + def read_necessary_streams(self): + """Read streams to populate the various internal components for a PDB + table.""" + if not self.metadata['windows'].get('pdb', None): + self.read_pdb_info_stream() + if not self.user_types: + self.read_tpi_stream() + if not self.symbols: + self.read_symbol_stream() + + def read_tpi_stream(self) -> None: + """Reads the TPI type steam.""" + vollog.debug("Reading TPI") + tpi_layer = self._context.layers.get(self._layer_name + "_stream2", None) + if not tpi_layer: + raise ValueError("No TPI stream available") + module = self._context.module(module_name = tpi_layer.pdb_symbol_table, layer_name = tpi_layer.name, offset = 0) + header = module.object(object_type = "TPI_HEADER", offset = 0) + + # Check the header + if not (56 <= header.header_size < 1024): + raise ValueError("TPI Stream Header size outside normal bounds") + if header.index_min < 4096: + raise ValueError("Minimum TPI index is 4096, found: {}".format(header.index_min)) + if header.index_max < header.index_min: + raise ValueError("Maximum TPI index is smaller than minimum TPI index, found: {} < {} ".format( + header.index_max, header.index_min)) + + # Reset the state + self.types = [] + type_references = {} # type: Dict[str, int] + + offset = header.header_size + # Ensure we use the same type everywhere + length_type = "unsigned short" + length_len = module.get_type(length_type).size + type_index = 1 + while tpi_layer.maximum_address - offset > 0: + self._progress_callback(offset * 100 / tpi_layer.maximum_address, "Reading TPI layer") + length = module.object(object_type = length_type, offset = offset) + if not isinstance(length, int): + raise TypeError("Non-integer length provided") + offset += length_len + output, consumed = self.consume_type(module, offset, length) + leaf_type, name, value = output + for tag_type in ['unnamed', 'anonymous']: + if name == '<{}-tag>'.format(tag_type) or name == '__{}'.format(tag_type): + name = '__{}_'.format(tag_type) + hex(len(self.types) + 0x1000)[2:] + if name: + type_references[name] = len(self.types) + self.types.append((leaf_type, name, value)) + offset += length + type_index += 1 + # Since types can only refer to earlier types, assigning the name at this point is fine + + if tpi_layer.maximum_address - offset != 0: + raise ValueError("Type values did not fill the TPI stream correctly") + + self.process_types(type_references) + + def read_dbi_stream(self) -> None: + """Reads the DBI Stream.""" + vollog.debug("Reading DBI stream") + dbi_layer = self._context.layers.get(self._layer_name + "_stream3", None) + if not dbi_layer: + raise ValueError("No DBI stream available") + module = self._context.module(module_name = dbi_layer.pdb_symbol_table, layer_name = dbi_layer.name, offset = 0) + self._dbiheader = module.object(object_type = "DBI_HEADER", offset = 0) + + if not self._dbiheader: + raise ValueError("DBI Header could not be read") + + # Skip past sections we don't care about to get to the DBG header + dbg_hdr_offset = (self._dbiheader.vol.size + self._dbiheader.module_size + self._dbiheader.secconSize + + self._dbiheader.secmapSize + self._dbiheader.filinfSize + self._dbiheader.tsmapSize + + self._dbiheader.ecinfoSize) + self._dbidbgheader = module.object(object_type = "DBI_DBG_HEADER", offset = dbg_hdr_offset) + + self._sections = [] + self._omap_mapping = [] + + if self._dbidbgheader.snSectionHdrOrig != -1: + section_orig_layer_name = self._layer_name + "_stream" + str(self._dbidbgheader.snSectionHdrOrig) + consumed, length = 0, self.context.layers[section_orig_layer_name].maximum_address + while consumed < length: + section = self.context.object(dbi_layer.pdb_symbol_table + constants.BANG + "IMAGE_SECTION_HEADER", + offset = consumed, + layer_name = section_orig_layer_name) + self._sections.append(section) + consumed += section.vol.size + + if self._dbidbgheader.snOmapFromSrc != -1: + omap_layer_name = self._layer_name + "_stream" + str(self._dbidbgheader.snOmapFromSrc) + length = self.context.layers[omap_layer_name].maximum_address + data = self.context.layers[omap_layer_name].read(0, length) + # For speed we don't use the framework to read this (usually sizeable) data + for i in range(0, length, 8): + self._omap_mapping.append( + (int.from_bytes(data[i:i + 4], + byteorder = 'little'), int.from_bytes(data[i + 4:i + 8], byteorder = 'little'))) + elif self._dbidbgheader.snSectionHdr != -1: + section_layer_name = self._layer_name + "_stream" + str(self._dbidbgheader.snSectionHdr) + consumed, length = 0, self.context.layers[section_layer_name].maximum_address + while consumed < length: + section = self.context.object(dbi_layer.pdb_symbol_table + constants.BANG + "IMAGE_SECTION_HEADER", + offset = consumed, + layer_name = section_layer_name) + self._sections.append(section) + consumed += section.vol.size + + def read_symbol_stream(self): + """Reads in the symbol stream.""" + self.symbols = {} + + if not self._dbiheader: + self.read_dbi_stream() + + vollog.debug("Reading Symbols") + + symrec_layer = self._context.layers.get(self._layer_name + "_stream" + str(self._dbiheader.symrecStream), None) + if not symrec_layer: + raise ValueError("No SymRec stream available") + module = self._context.module(module_name = symrec_layer.pdb_symbol_table, + layer_name = symrec_layer.name, + offset = 0) + + offset = 0 + max_address = symrec_layer.maximum_address + + while offset < max_address: + self._progress_callback(offset * 100 / max_address, "Reading Symbol layer") + sym = module.object(object_type = "GLOBAL_SYMBOL", offset = offset) + leaf_type = module.object(object_type = "unsigned short", offset = sym.leaf_type.vol.offset) + name = None + address = None + if sym.segment < len(self._sections): + if leaf_type == 0x110e: + # v3 symbol (c-string) + name = self.parse_string(sym.name, False, sym.length - sym.vol.size + 2) + address = self._sections[sym.segment - 1].VirtualAddress + sym.offset + elif leaf_type == 0x1009: + # v2 symbol (pascal-string) + name = self.parse_string(sym.name, True, sym.length - sym.vol.size + 2) + address = self._sections[sym.segment - 1].VirtualAddress + sym.offset + else: + vollog.debug("Only v2 and v3 symbols are supported") + if name: + if self._omap_mapping: + address = self.omap_lookup(address) + stripped_name = self.name_strip(name) + self.symbols[stripped_name] = {"address": address} + if name != self.name_strip(name): + self.symbols[stripped_name]["linkage_name"] = name + offset += sym.length + 2 # Add on length itself + + def read_pdb_info_stream(self): + """Reads in the pdb information stream.""" + if not self._dbiheader: + self.read_dbi_stream() + + vollog.debug("Reading PDB Info") + pdb_info_layer = self._context.layers.get(self._layer_name + "_stream1", None) + if not pdb_info_layer: + raise ValueError("No PDB Info Stream available") + module = self._context.module(module_name = pdb_info_layer.pdb_symbol_table, + layer_name = pdb_info_layer.name, + offset = 0) + pdb_info = module.object(object_type = "PDB_INFORMATION", offset = 0) + + self.metadata['windows']['pdb'] = { + "GUID": self.convert_bytes_to_guid(pdb_info.GUID), + "age": pdb_info.age, + "database": "ntkrnlmp.pdb", + "machine_type": self._dbiheader.machine + } + + def convert_bytes_to_guid(self, original: bytes) -> str: + """Convert the bytes to the correct ordering for a GUID.""" + orig_guid_list = [x for x in original] + guid_list = [] + for i in [3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15]: + guid_list.append(orig_guid_list[i]) + return str(binascii.hexlify(bytes(guid_list)), "latin-1").upper() + + # SYMBOL HANDLING CODE + + def omap_lookup(self, address): + """Looks up an address using the omap mapping.""" + pos = bisect(self._omap_mapping, (address, -1)) + if self._omap_mapping[pos][0] > address: + pos -= 1 + + if not self._omap_mapping[pos][1]: + return 0 + return self._omap_mapping[pos][1] + (address - self._omap_mapping[pos][0]) + + def name_strip(self, name): + """Strips unnecessary components from the start of a symbol name.""" + new_name = name + + if new_name[:1] in ["_", "@", "\u007F"]: + new_name = new_name[1:] + + name_array = new_name.split("@") + if len(name_array) == 2: + if name_array[1].isnumeric() and name_array[0][0] != "?": + new_name = name_array[0] + else: + new_name = name + + return new_name + + def get_json(self): + """Returns the intermediate format JSON data from this pdb file.""" + self.read_necessary_streams() + + # Set the time/datestamp for the output + self.metadata["producer"] = { + "datetime": datetime.datetime.now().isoformat(), + "name": "volatility3", + "version": constants.PACKAGE_VERSION + } + + return { + "user_types": self.user_types, + "enums": self.enumerations, + "base_types": self.bases, + "symbols": self.symbols, + "metadata": self.metadata, + } + + def get_type_from_index(self, index: int) -> Union[List[Any], Dict[str, Any]]: + """Takes a type index and returns appropriate dictionary.""" + if index < 0x1000: + base_name, base = primatives[index & 0xff] + self.bases[base_name] = base + result = {"kind": "base", "name": base_name} # type: Union[List[Dict[str, Any]], Dict[str, Any]] + indirection = (index & 0xf00) + if indirection: + pointer_name, pointer_base = indirections[indirection] + if self.bases.get('pointer', None) and self.bases['pointer'] == pointer_base: + result = {"kind": "pointer", "subtype": result} + else: + self.bases[pointer_name] = pointer_base + result = {"kind": "pointer", "base": pointer_name, "subtype": result} + return result + else: + leaf_type, name, value = self.types[index - 0x1000] + result = {"kind": "struct", "name": name} + if leaf_type in [leaf_type.LF_MODIFIER]: + result = self.get_type_from_index(value.subtype_index) + elif leaf_type in [leaf_type.LF_ARRAY, leaf_type.LF_ARRAY_ST, leaf_type.LF_STRIDED_ARRAY]: + result = { + "count": ForwardArrayCount(value.size, value.element_type), + "kind": "array", + "subtype": self.get_type_from_index(value.element_type) + } + elif leaf_type in [leaf_type.LF_BITFIELD]: + result = { + "kind": "bitfield", + "type": self.get_type_from_index(value.underlying_type), + "bit_length": value.length, + "bit_position": value.position + } + elif leaf_type in [leaf_type.LF_POINTER]: + # Since we use the base['pointer'] to set the size for pointers, update it and check we don't get conflicts + size = self.get_size_from_index(index) + if self.bases.get("pointer", None) is None: + self.bases['pointer'] = {"endian": "little", "kind": "int", "signed": False, "size": size} + else: + if size != self.bases['pointer']['size']: + raise ValueError("Native pointers with different sizes!") + result = {"kind": "pointer", "subtype": self.get_type_from_index(value.subtype_index)} + elif leaf_type in [leaf_type.LF_PROCEDURE]: + return {"kind": "function"} + elif leaf_type in [leaf_type.LF_UNION]: + result = {"kind": "union", "name": name} + elif leaf_type in [leaf_type.LF_ENUM]: + result = {"kind": "enum", "name": name} + elif leaf_type in [leaf_type.LF_FIELDLIST]: + result = value + elif not name: + raise ValueError("No name for structure that should be named") + return result + + def get_size_from_index(self, index: int) -> int: + """Returns the size of the structure based on the type index + provided.""" + result = -1 + name = '' # type: Optional[str] + if index < 0x1000: + if (index & 0xf00): + _, base = indirections[index & 0xf00] + else: + _, base = primatives[index & 0xff] + result = base['size'] + else: + leaf_type, name, value = self.types[index - 0x1000] + if leaf_type in [ + leaf_type.LF_UNION, leaf_type.LF_CLASS, leaf_type.LF_CLASS_ST, leaf_type.LF_STRUCTURE, + leaf_type.LF_STRUCTURE_ST, leaf_type.LF_INTERFACE + ]: + if not value.properties.forward_reference: + result = value.size + elif leaf_type in [leaf_type.LF_ARRAY, leaf_type.LF_ARRAY_ST, leaf_type.LF_STRIDED_ARRAY]: + result = value.size + elif leaf_type in [leaf_type.LF_MODIFIER, leaf_type.LF_ENUM, leaf_type.LF_ARGLIST]: + result = self.get_size_from_index(value.subtype_index) + elif leaf_type in [leaf_type.LF_MEMBER]: + result = self.get_size_from_index(value.field_type) + elif leaf_type in [leaf_type.LF_BITFIELD]: + result = self.get_size_from_index(value.underlying_type) + elif leaf_type in [leaf_type.LF_POINTER]: + result = value.size + if not result: + if value.pointer_type == 0x0a: + return 4 + elif value.pointer_type == 0x0c: + return 8 + else: + raise ValueError("Pointer size could not be determined") + elif leaf_type in [leaf_type.LF_PROCEDURE]: + raise ValueError("LF_PROCEDURE size could not be identified") + else: + raise ValueError("Unable to determine size of leaf_type {}".format(leaf_type.lookup())) + if result <= 0: + raise ValueError("Invalid size identified: {} ({})".format(index, name)) + return result + + ### TYPE HANDLING CODE + + def process_types(self, type_references: Dict[str, int]) -> None: + """Reads the TPI and symbol streams to populate the reader's + variables.""" + + self.bases = {} + self.user_types = {} + self.enumerations = {} + + max_len = len(self.types) + for index in range(max_len): + self._progress_callback(index * 100 / max_len, "Processing types") + leaf_type, name, value = self.types[index] + if leaf_type in [ + leaf_type.LF_CLASS, leaf_type.LF_CLASS_ST, leaf_type.LF_STRUCTURE, leaf_type.LF_STRUCTURE_ST, + leaf_type.LF_INTERFACE + ]: + if not value.properties.forward_reference and name: + self.user_types[name] = { + "kind": "struct", + "size": value.size, + "fields": self.convert_fields(value.fields - 0x1000) + } + elif leaf_type in [leaf_type.LF_UNION]: + if not value.properties.forward_reference and name: + # Deal with UNION types + self.user_types[name] = { + "kind": "union", + "size": value.size, + "fields": self.convert_fields(value.fields - 0x1000) + } + elif leaf_type in [leaf_type.LF_ENUM]: + if not value.properties.forward_reference and name: + base = self.get_type_from_index(value.subtype_index) + if not isinstance(base, Dict): + raise ValueError("Invalid base type returned for Enumeration") + constants = self.get_type_from_index(value.fields) + if not isinstance(constants, list): + raise ValueError("Enumeration fields type not a list") + self.enumerations[name] = { + 'base': base['name'], + 'size': self.get_size_from_index(value.subtype_index), + 'constants': dict([(name, enum.value) for _, name, enum in constants]) + } + + # Re-run through for ForwardSizeReferences + self.user_types = self.replace_forward_references(self.user_types, type_references) + + def consume_type( + self, module: interfaces.context.ModuleInterface, offset: int, length: int + ) -> Tuple[Tuple[Optional[interfaces.objects.ObjectInterface], Optional[str], Union[ + None, List, interfaces.objects.ObjectInterface]], int]: + """Returns a (leaf_type, name, object) Tuple for a type, and the number + of bytes consumed.""" + leaf_type = self.context.object(module.get_enumeration("LEAF_TYPE"), + layer_name = module._layer_name, + offset = offset) + consumed = leaf_type.vol.base_type.size + remaining = length - consumed + + if leaf_type in [ + leaf_type.LF_CLASS, leaf_type.LF_CLASS_ST, leaf_type.LF_STRUCTURE, leaf_type.LF_STRUCTURE_ST, + leaf_type.LF_INTERFACE + ]: + structure = module.object(object_type = "LF_STRUCTURE", offset = offset + consumed) + name_offset = structure.name.vol.offset - structure.vol.offset + name, value, excess = self.determine_extended_value(leaf_type, structure.size, module, + remaining - name_offset) + structure.size = value + structure.name = name + consumed += remaining + result = leaf_type, name, structure + elif leaf_type in [leaf_type.LF_MEMBER, leaf_type.LF_MEMBER_ST]: + member = module.object(object_type = "LF_MEMBER", offset = offset + consumed) + name_offset = member.name.vol.offset - member.vol.offset + name, value, excess = self.determine_extended_value(leaf_type, member.offset, module, + remaining - name_offset) + member.offset = value + member.name = name + result = leaf_type, name, member + consumed += member.vol.size + len(name) + 1 + excess + elif leaf_type in [leaf_type.LF_ARRAY, leaf_type.LF_ARRAY_ST, leaf_type.LF_STRIDED_ARRAY]: + array = module.object(object_type = "LF_ARRAY", offset = offset + consumed) + name_offset = array.name.vol.offset - array.vol.offset + name, value, excess = self.determine_extended_value(leaf_type, array.size, module, remaining - name_offset) + array.size = value + array.name = name + result = leaf_type, name, array + consumed += remaining + elif leaf_type in [leaf_type.LF_ENUMERATE]: + enum = module.object(object_type = 'LF_ENUMERATE', offset = offset + consumed) + name_offset = enum.name.vol.offset - enum.vol.offset + name, value, excess = self.determine_extended_value(leaf_type, enum.value, module, remaining - name_offset) + enum.value = value + enum.name = name + result = leaf_type, name, enum + consumed += enum.vol.size + len(name) + 1 + excess + elif leaf_type in [leaf_type.LF_ARGLIST, leaf_type.LF_ENUM]: + enum = module.object(object_type = "LF_ENUM", offset = offset + consumed) + name_offset = enum.name.vol.offset - enum.vol.offset + name = self.parse_string(enum.name, leaf_type < leaf_type.LF_ST_MAX, size = remaining - name_offset) + enum.name = name + result = leaf_type, name, enum + consumed += remaining + elif leaf_type in [leaf_type.LF_UNION]: + union = module.object(object_type = "LF_UNION", offset = offset + consumed) + name_offset = union.name.vol.offset - union.vol.offset + name = self.parse_string(union.name, leaf_type < leaf_type.LF_ST_MAX, size = remaining - name_offset) + result = leaf_type, name, union + consumed += remaining + elif leaf_type in [leaf_type.LF_MODIFIER, leaf_type.LF_POINTER, leaf_type.LF_PROCEDURE]: + obj = module.object(object_type = leaf_type.lookup(), offset = offset + consumed) + result = leaf_type, None, obj + consumed += remaining + elif leaf_type in [leaf_type.LF_FIELDLIST]: + sub_length = remaining + sub_offset = offset + consumed + fields = [] + while length > consumed: + subfield, sub_consumed = self.consume_type(module, sub_offset, sub_length) + sub_consumed += self.consume_padding(module.layer_name, sub_offset + sub_consumed) + sub_length -= sub_consumed + sub_offset += sub_consumed + consumed += sub_consumed + fields.append(subfield) + result = leaf_type, None, fields + elif leaf_type in [leaf_type.LF_BITFIELD]: + bitfield = module.object(object_type = "LF_BITFIELD", offset = offset + consumed) + result = leaf_type, None, bitfield + consumed += remaining + else: + raise TypeError("Unhandled leaf_type: {}".format(leaf_type)) + + return result, consumed + + def consume_padding(self, layer_name: str, offset: int) -> int: + """Returns the amount of padding used between fields.""" + val = self.context.layers[layer_name].read(offset, 1) + if not ((val[0] & 0xf0) == 0xf0): + return 0 + return (int(val[0]) & 0x0f) + + def convert_fields(self, fields: int) -> Dict[Optional[str], Dict[str, Any]]: + """Converts a field list into a list of fields.""" + result = {} # type: Dict[Optional[str], Dict[str, Any]] + _, _, fields_struct = self.types[fields] + if not isinstance(fields_struct, list): + vollog.warning("Fields structure did not contain a list of fields") + return result + for field in fields_struct: + _, name, member = field + result[name] = {"offset": member.offset, "type": self.get_type_from_index(member.field_type)} + return result + + def replace_forward_references(self, types, type_references): + """Finds all ForwardArrayCounts and calculates them once + ForwardReferences have been resolved.""" + if isinstance(types, dict): + for k, v in types.items(): + types[k] = self.replace_forward_references(v, type_references) + elif isinstance(types, list): + new_types = [] + for v in types: + new_types.append(self.replace_forward_references(v, type_references)) + types = new_types + elif isinstance(types, ForwardArrayCount): + element_type = types.element_type + # If we're a forward array count, we need to do the calculation now after all the types have been processed + loop = True + while loop: + loop = False + if element_type > 0x1000: + _, name, toplevel_type = self.types[element_type - 0x1000] + # If there's no name, the original size is probably fine as long as we're not indirect (LF_MODIFIER) + if not name and isinstance( + toplevel_type, + interfaces.objects.ObjectInterface) and toplevel_type.vol.type_name.endswith('LF_MODIFIER'): + # We have check they don't point to a forward reference, so we go round again with the subtype + element_type = toplevel_type.subtype_index + loop = True + elif name: + # If there is a name, look it up so we're not using a reference but the real thing + element_type = type_references[name] + 0x1000 + return types.size // self.get_size_from_index(element_type) + return types + + # COMMON CODE + + @staticmethod + def parse_string(structure: interfaces.objects.ObjectInterface, + parse_as_pascal: bool = False, + size: int = 0) -> str: + """Consumes either a c-string or a pascal string depending on the + leaf_type.""" + if not parse_as_pascal: + name = structure.cast("string", max_length = size, encoding = "latin-1") + else: + name = structure.cast("pascal_string") + name = name.string.cast("string", max_length = name.length, encoding = "latin-1") + return str(name) + + def determine_extended_value(self, leaf_type: interfaces.objects.ObjectInterface, + value: interfaces.objects.ObjectInterface, module: interfaces.context.ModuleInterface, + length: int) -> Tuple[str, interfaces.objects.ObjectInterface, int]: + """Reads a value and potentially consumes more data to construct the + value.""" + excess = 0 + if value >= leaf_type.LF_CHAR: + sub_leaf_type = self.context.object(self.context.symbol_space.get_enumeration(leaf_type.vol.type_name), + layer_name = leaf_type.vol.layer_name, + offset = value.vol.offset) + # Set the offset at just after the previous size type + offset = value.vol.offset + value.vol.data_format.length + if sub_leaf_type in [leaf_type.LF_CHAR]: + value = module.object(object_type = 'char', offset = offset) + elif sub_leaf_type in [leaf_type.LF_SHORT]: + value = module.object(object_type = 'short', offset = offset) + elif sub_leaf_type in [leaf_type.LF_USHORT]: + value = module.object(object_type = 'unsigned short', offset = offset) + elif sub_leaf_type in [leaf_type.LF_LONG]: + value = module.object(object_type = 'long', offset = offset) + elif sub_leaf_type in [leaf_type.LF_ULONG]: + value = module.object(object_type = 'unsigned long', offset = offset) + else: + raise TypeError("Unexpected extended value type") + excess = value.vol.data_format.length + # Updated the consume/offset counters + name = module.object(object_type = "string", offset = value.vol.offset + value.vol.data_format.length) + name_str = self.parse_string(name, leaf_type < leaf_type.LF_ST_MAX, size = length - excess) + return name_str, value, excess + + +class PdbRetreiver: + + def retreive_pdb(self, + guid: str, + file_name: str, + progress_callback: constants.ProgressCallback = None) -> Optional[str]: + vollog.info("Download PDB file...") + file_name = ".".join(file_name.split(".")[:-1] + ['pdb']) + for sym_url in ['http://msdl.microsoft.com/download/symbols']: + url = sym_url + "/{}/{}/".format(file_name, guid) + + result = None + for suffix in [file_name, file_name[:-1] + '_']: + try: + vollog.debug("Attempting to retrieve {}".format(url + suffix)) + result = resources.ResourceAccessor(progress_callback).open(url + suffix) + except error.HTTPError as excp: + vollog.debug("Failed with {}".format(excp)) + if result: + break + if progress_callback is not None: + progress_callback(100, "Downloading {}".format(url + suffix)) + if result is None: + return None + return result.name + + +if __name__ == '__main__': + import argparse + + + class PrintedProgress(object): + """A progress handler that prints the progress value and the + description onto the command line.""" + + def __init__(self): + self._max_message_len = 0 + + def __call__(self, progress: Union[int, float], description: str = None): + """A simple function for providing text-based feedback. + + .. warning:: Only for development use. + + Args: + progress: Percentage of progress of the current procedure + """ + message = "\rProgress: {0: 7.2f}\t\t{1:}".format(round(progress, 2), description or '') + message_len = len(message) + self._max_message_len = max([self._max_message_len, message_len]) + print(message, end = (' ' * (self._max_message_len - message_len)) + '\r') + + + parser = argparse.ArgumentParser( + description = "Read PDB files and convert to Volatility 3 Intermediate Symbol Format") + parser.add_argument("-o", "--output", metavar = "OUTPUT", help = "Filename for data output", required = True) + file_group = parser.add_argument_group("file", description = "File-based conversion of PDB to ISF") + file_group.add_argument("-f", "--file", metavar = "FILE", help = "PDB file to translate to ISF") + data_group = parser.add_argument_group("data", description = "Convert based on a GUID and filename pattern") + data_group.add_argument("-p", "--pattern", metavar = "PATTERN", help = "Filename pattern to recover PDB file") + data_group.add_argument("-g", + "--guid", + metavar = "GUID", + help = "GUID + Age string for the required PDB file", + default = None) + data_group.add_argument("-k", + "--keep", + action = "store_true", + default = False, + help = "Keep the downloaded PDB file") + args = parser.parse_args() + + pg_cb = PrintedProgress() + + delfile = False + filename = None + if args.guid is not None and args.pattern is not None: + filename = PdbRetreiver().retreive_pdb(guid = args.guid, file_name = args.pattern, progress_callback = pg_cb) + delfile = True + elif args.file: + filename = args.file + else: + parser.error("No GUID/pattern or file provided") + + if not filename: + parser.error("No suitable filename provided or retrieved") + + ctx = contexts.Context() + if not os.path.exists(filename): + parser.error("File {} does not exists".format(filename)) + location = "file:" + request.pathname2url(filename) + + convertor = PdbReader(ctx, location, progress_callback = pg_cb) + + with open(args.output, "w") as f: + json.dump(convertor.get_json(), f, indent = 2, sort_keys = True) + + if args.keep: + print("Temporary PDB file: {}".format(filename)) + elif delfile: + os.remove(filename) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/pe.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pe.json new file mode 100644 index 00000000..b66612ac --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/pe.json @@ -0,0 +1,931 @@ +{ + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2018-05-22T22:47:20.182954" + }, + "format": "4.1.0" + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned long long": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": true, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "int", + "size": 1, + "signed": false, + "endian": "little" + } + }, + "symbols": {}, + "enums": {}, + "user_types": { + "_IMAGE_DATA_DIRECTORY": { + "fields": { + "Size": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "VirtualAddress": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + }, + "_IMAGE_DEBUG_DIRECTORY": { + "fields": { + "AddressOfRawData": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Characteristics": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "MajorVersion": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MinorVersion": { + "offset": 10, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "PointerToRawData": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfData": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TimeDateStamp": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Type": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 28 + }, + "_IMAGE_DOS_HEADER": { + "fields": { + "e_cblp": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cp": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cparhdr": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_crlc": { + "offset": 6, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_cs": { + "offset": 22, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_csum": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ip": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_lfanew": { + "offset": 60, + "type": { + "kind": "base", + "name": "long" + } + }, + "e_lfarlc": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_magic": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_maxalloc": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_minalloc": { + "offset": 10, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_oemid": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_oeminfo": { + "offset": 38, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ovno": { + "offset": 26, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_res": { + "offset": 28, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "e_res2": { + "offset": 40, + "type": { + "count": 10, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "e_sp": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "e_ss": { + "offset": 14, + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 64 + }, + "_IMAGE_FILE_HEADER": { + "fields": { + "Characteristics": { + "offset": 18, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Machine": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfSections": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfSymbols": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PointerToSymbolTable": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfOptionalHeader": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "TimeDateStamp": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 20 + }, + "_IMAGE_NT_HEADERS": { + "fields": { + "FileHeader": { + "offset": 4, + "type": { + "kind": "struct", + "name": "_IMAGE_FILE_HEADER" + } + }, + "OptionalHeader": { + "offset": 24, + "type": { + "kind": "struct", + "name": "_IMAGE_OPTIONAL_HEADER" + } + }, + "Signature": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 248 + }, + "_IMAGE_OPTIONAL_HEADER": { + "fields": { + "AddressOfEntryPoint": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BaseOfCode": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "BaseOfData": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "CheckSum": { + "offset": 64, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "DataDirectory": { + "offset": 96, + "type": { + "count": 16, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_IMAGE_DATA_DIRECTORY" + } + } + }, + "DllCharacteristics": { + "offset": 70, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "FileAlignment": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "ImageBase": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "LoaderFlags": { + "offset": 88, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Magic": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MajorImageVersion": { + "offset": 44, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MajorLinkerVersion": { + "offset": 2, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "MajorOperatingSystemVersion": { + "offset": 40, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MajorSubsystemVersion": { + "offset": 48, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MinorImageVersion": { + "offset": 46, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MinorLinkerVersion": { + "offset": 3, + "type": { + "kind": "base", + "name": "unsigned char" + } + }, + "MinorOperatingSystemVersion": { + "offset": 42, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "MinorSubsystemVersion": { + "offset": 50, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfRvaAndSizes": { + "offset": 92, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SectionAlignment": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfCode": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfHeaders": { + "offset": 60, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfHeapCommit": { + "offset": 84, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfHeapReserve": { + "offset": 80, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfImage": { + "offset": 56, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfInitializedData": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfStackCommit": { + "offset": 76, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfStackReserve": { + "offset": 72, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfUninitializedData": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Subsystem": { + "offset": 68, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "Win32VersionValue": { + "offset": 52, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 224 + }, + "__unnamed_46": { + "fields": { + "PhysicalAddress": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "VirtualSize": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "union", + "size": 4 + }, + "_IMAGE_SECTION_HEADER": { + "fields": { + "Characteristics": { + "offset": 36, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Misc": { + "offset": 8, + "type": { + "kind": "union", + "name": "__unnamed_46" + } + }, + "Name": { + "offset": 0, + "type": { + "count": 8, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + } + }, + "NumberOfLinenumbers": { + "offset": 34, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "NumberOfRelocations": { + "offset": 32, + "type": { + "kind": "base", + "name": "unsigned short" + } + }, + "PointerToLinenumbers": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PointerToRawData": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PointerToRelocations": { + "offset": 24, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "SizeOfRawData": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "VirtualAddress": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 40 + }, + "_IMAGE_NT_HEADERS64": { + "fields": { + "Signature": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 0 + }, + "FileHeader": { + "type": { + "kind": "struct", + "name": "_IMAGE_FILE_HEADER" + }, + "offset": 4 + }, + "OptionalHeader": { + "type": { + "kind": "struct", + "name": "_IMAGE_OPTIONAL_HEADER64" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 264 + }, + "_IMAGE_OPTIONAL_HEADER64": { + "fields": { + "Magic": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 0 + }, + "MajorLinkerVersion": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 2 + }, + "MinorLinkerVersion": { + "type": { + "kind": "base", + "name": "unsigned char" + }, + "offset": 3 + }, + "SizeOfCode": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 4 + }, + "SizeOfInitializedData": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 8 + }, + "SizeOfUninitializedData": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 12 + }, + "AddressOfEntryPoint": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 16 + }, + "BaseOfCode": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 20 + }, + "ImageBase": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 24 + }, + "SectionAlignment": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 32 + }, + "FileAlignment": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 36 + }, + "MajorOperatingSystemVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "MinorOperatingSystemVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 42 + }, + "MajorImageVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 44 + }, + "MinorImageVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 46 + }, + "MajorSubsystemVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 48 + }, + "MinorSubsystemVersion": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 50 + }, + "Win32VersionValue": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "SizeOfImage": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 56 + }, + "SizeOfHeaders": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 60 + }, + "CheckSum": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "Subsystem": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 68 + }, + "DllCharacteristics": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 70 + }, + "SizeOfStackReserve": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 72 + }, + "SizeOfStackCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 80 + }, + "SizeOfHeapReserve": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 88 + }, + "SizeOfHeapCommit": { + "type": { + "kind": "base", + "name": "unsigned long long" + }, + "offset": 96 + }, + "LoaderFlags": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 104 + }, + "NumberOfRvaAndSizes": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 108 + }, + "DataDirectory": { + "type": { + "count": 16, + "subtype": { + "kind": "struct", + "name": "_IMAGE_DATA_DIRECTORY" + }, + "kind": "array" + }, + "offset": 112 + } + }, + "kind": "struct", + "size": 240 + } + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64-win7.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64-win7.json new file mode 100644 index 00000000..99db2c12 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64-win7.json @@ -0,0 +1,79 @@ +{ + "metadata": { + "producer": { + "version": "0.1.5", + "name": "mhl-by-hand", + "datetime": "2018-10-03T16:53:00.478000" + }, + "format": "4.0.0" + }, + "user_types": { + "_POOL_HEADER": { + "fields": { + "BlockSize": { + "offset": 0, + "type": { + "bit_length": 8, + "bit_position": 16, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "PoolIndex": { + "offset": 0, + "type": { + "bit_length": 8, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "PoolTag": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PoolType": { + "offset": 0, + "type": { + "bit_length": 8, + "bit_position": 24, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned long" + } + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "symbols": { + }, + "enums": { + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "int": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64.json new file mode 100644 index 00000000..05a4507b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x64.json @@ -0,0 +1,85 @@ +{ + "metadata": { + "producer": { + "version": "0.1.5", + "name": "mhl-by-hand", + "datetime": "2018-10-03T16:53:00.478000" + }, + "format": "4.0.0" + }, + "user_types": { + "_POOL_HEADER": { + "fields": { + "BlockSize": { + "offset": 2, + "type": { + "bit_length": 8, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "PoolIndex": { + "offset": 0, + "type": { + "bit_length": 8, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "PoolTag": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PoolType": { + "offset": 2, + "type": { + "bit_length": 8, + "bit_position": 8, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "symbols": { + }, + "enums": { + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "int": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x86.json new file mode 100644 index 00000000..d117a8b2 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/poolheader-x86.json @@ -0,0 +1,85 @@ +{ + "metadata": { + "producer": { + "version": "0.1.5", + "name": "mhl-by-hand", + "datetime": "2018-10-03T16:53:00.478000" + }, + "format": "4.0.0" + }, + "user_types": { + "_POOL_HEADER": { + "fields": { + "BlockSize": { + "offset": 2, + "type": { + "bit_length": 9, + "bit_position": 0, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "PoolIndex": { + "offset": 0, + "type": { + "bit_length": 7, + "bit_position": 9, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + }, + "PoolTag": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "PoolType": { + "offset": 2, + "type": { + "bit_length": 7, + "bit_position": 9, + "kind": "bitfield", + "type": { + "kind": "base", + "name": "unsigned short" + } + } + } + }, + "kind": "struct", + "size": 16 + } + }, + "symbols": { + }, + "enums": { + }, + "base_types": { + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "int": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + } + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/registry.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/registry.json new file mode 100644 index 00000000..d140d7c9 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/registry.json @@ -0,0 +1,320 @@ +{ + "symbols": { + }, + "user_types": { + "__unnamed_114": { + "fields": { + "Next": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "UserData": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "union", + "size": 4 + }, + "__unnamed_115": { + "fields": { + "Last": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "u": { + "offset": 4, + "type": { + "kind": "union", + "name": "__unnamed_114" + } + } + }, + "kind": "struct", + "size": 8 + }, + "__unnamed_116": { + "fields": { + "u": { + "offset": 0, + "type": { + "kind": "union", + "name": "__unnamed_114" + } + } + }, + "kind": "struct", + "size": 4 + }, + "__unnamed_117": { + "fields": { + "NewCell": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_116" + } + }, + "OldCell": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_115" + } + } + }, + "kind": "union", + "size": 8 + }, + "_HBIN": { + "fields": { + "FileOffset": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Reserved1": { + "offset": 12, + "type": { + "count": 2, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "Signature": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Size": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "Spare": { + "offset": 28, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "TimeStamp": { + "offset": 20, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + } + }, + "kind": "struct", + "size": 32 + }, + "_HCELL": { + "fields": { + "Size": { + "offset": 0, + "type": { + "kind": "base", + "name": "long" + } + }, + "u": { + "offset": 4, + "type": { + "kind": "union", + "name": "__unnamed_117" + } + } + }, + "kind": "struct", + "size": 12 + }, + "_VOL_USERASSIST_TYPES_XP": { + "fields": { + "ID": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "CountStartingAtFive": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "LastUpdated": { + "offset": 8, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + } + }, + "kind": "struct", + "size": 16 + }, + "_VOL_USERASSIST_TYPES_7": { + "fields": { + "Count": { + "offset": 4, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FocusCount": { + "offset": 8, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "FocusTime": { + "offset": 12, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "LastUpdated": { + "offset": 60, + "type": { + "kind": "union", + "name": "_LARGE_INTEGER" + } + } + }, + "kind": "struct", + "size": 72 + }, + "_LARGE_INTEGER": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + }, + "QuadPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "long long" + } + }, + "u": { + "offset": 0, + "type": { + "kind": "struct", + "name": "__unnamed_2" + } + } + }, + "kind": "union", + "size": 8 + }, + "__unnamed_2": { + "fields": { + "HighPart": { + "offset": 4, + "type": { + "kind": "base", + "name": "long" + } + }, + "LowPart": { + "offset": 0, + "type": { + "kind": "base", + "name": "unsigned long" + } + } + }, + "kind": "struct", + "size": 8 + } + }, + "enums": { + }, + "base_types": { + "unsigned char": { + "endian": "little", + "kind": "char", + "signed": false, + "size": 1 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + }, + "long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 4 + }, + "char": { + "endian": "little", + "kind": "char", + "signed": true, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "long long": { + "endian": "little", + "kind": "int", + "signed": true, + "size": 8 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "ikelos-by-hand", + "datetime": "2017-09-04T22:45:22" + }, + "format": "4.0.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x64.json new file mode 100644 index 00000000..ed48f29a --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 16 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 24 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 120 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 16 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 40 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 0 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 76 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 52 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 40 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 48 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 120 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x86.json new file mode 100644 index 00000000..d31cb49b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-vista-x86.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 92 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 28 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 4 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 0 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 60 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 36 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 24 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 28 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 32 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 92 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x64.json new file mode 100644 index 00000000..a661eb87 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 32 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 64 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 232 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 36 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 76 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 232 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 72 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 232 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x86.json new file mode 100644 index 00000000..36b3b33e --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-15063-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 156 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 24 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 156 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 156 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x64.json new file mode 100644 index 00000000..1ee38fb5 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 40 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 64 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 232 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 36 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 76 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 232 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 72 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 232 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x86.json new file mode 100644 index 00000000..8949765b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win10-16299-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 20 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 156 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 24 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 156 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 156 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x64.json new file mode 100644 index 00000000..e90bb308 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 32 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 16 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 8 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 92 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 68 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 64 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 92 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x86.json new file mode 100644 index 00000000..c63363b8 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-win8-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 36 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 4 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 68 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 36 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 68 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-2003-x64.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-2003-x64.json new file mode 100644 index 00000000..c2408689 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-2003-x64.json @@ -0,0 +1,245 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 16 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 24 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 16 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 48 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 84 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 60 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 56 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 84 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-x86.json b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-x86.json new file mode 100644 index 00000000..546bce11 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/services-xp-x86.json @@ -0,0 +1,245 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 24 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 36 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 68 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 44 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 8 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 36 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 40 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 68 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} \ No newline at end of file diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/windows/versions.py b/app/parsers/vol_Parser/volatility/framework/symbols/windows/versions.py new file mode 100644 index 00000000..e1304262 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/windows/versions.py @@ -0,0 +1,118 @@ +import logging +from typing import Callable, Tuple, List, Optional + +from volatility.framework import interfaces, constants, exceptions + +vollog = logging.getLogger(__name__) + + +class OsDistinguisher: + """Distinguishes a symbol table as being above a particular version or + point. + + This will primarily check the version metadata first and foremost. + If that metadata isn't available then each item in the fallback_checks is tested. + If invert is specified then the result will be true if the version is less than that specified, or in the case of + fallback, if any of the fallback checks is successful. + + A fallback check is made up of: + * a symbol or type name + * a member name (implying that the value before was a type name) + * whether that symbol, type or member must be present or absent for the symbol table to be more above the required point + + Note: + Specifying that a member must not be present includes the whole type not being present too (ie, either will pass the test) + + Args: + version_check: Function that takes a 4-tuple version and returns whether whether the provided version is above a particular point + fallback_checks: A list of symbol/types/members of types, and whether they must be present to be above the required point + + Returns: + A function that takes a context and a symbol table name and determines whether that symbol table passes the distinguishing checks + """ + + def __init__(self, version_check: Callable[[Tuple[int, ...]], bool], fallback_checks: List[Tuple[str, Optional[str], + bool]]): + self._version_check = version_check + self._fallback_checks = fallback_checks + + # try the primary method based on the pe version in the ISF + def __call__(self, context: interfaces.context.ContextInterface, symbol_table: str) -> bool: + """ + + Args: + context: The context that contains the symbol table named `symbol_table` + symbol_table: Name of the symbol table within the context to distinguish the version of + + Returns: + True if the symbol table is of the required version + """ + + try: + pe_version = context.symbol_space[symbol_table].metadata.pe_version + major, minor, revision, build = pe_version + return self._version_check((major, minor, revision, build)) + except (AttributeError, ValueError, TypeError): + vollog.log(constants.LOGLEVEL_VVV, "Windows PE version data is not available") + + # fall back to the backup method, if necessary + for name, member, response in self._fallback_checks: + if member is None: + if (context.symbol_space.has_symbol(symbol_table + constants.BANG + name) + or context.symbol_space.has_type(symbol_table + constants.BANG + name)) != response: + return False + else: + try: + symbol_type = context.symbol_space.get_type(symbol_table + constants.BANG + name) + if symbol_type.has_member(member) != response: + return False + except exceptions.SymbolError: + if not response: + return False + + return True + + +is_windows_8_1_or_later = OsDistinguisher(version_check = lambda x: x >= (6, 3), + fallback_checks = [("_KPRCB", "PendingTickFlags", True)]) + +is_vista_or_later = OsDistinguisher(version_check = lambda x: x >= (6, 0), + fallback_checks = [("KdCopyDataBlock", None, True)]) + +is_win10 = OsDistinguisher(version_check = lambda x: (10, 0) <= x, + fallback_checks = [("ObHeaderCookie", None, True), ("_HANDLE_TABLE", "HandleCount", False)]) + +is_windows_xp = OsDistinguisher(version_check = lambda x: (5, 1) <= x < (5, 2), + fallback_checks = [("KdCopyDataBlock", None, False), + ("_HANDLE_TABLE", "HandleCount", True)]) + +is_xp_or_2003 = OsDistinguisher(version_check = lambda x: (5, 1) <= x < (6, 0), + fallback_checks = [("KdCopyDataBlock", None, False), + ("_HANDLE_TABLE", "HandleCount", True)]) + +is_win10_up_to_15063 = OsDistinguisher(version_check = lambda x: (10, 0) <= x < (10, 0, 15063), + fallback_checks = [("ObHeaderCookie", None, True), + ("_HANDLE_TABLE", "HandleCount", False), + ("_EPROCESS", "KeepAliveCounter", True)]) + +is_win10_15063 = OsDistinguisher(version_check = lambda x: x == (10, 0, 15063), + fallback_checks = [("ObHeaderCookie", None, True), + ("_HANDLE_TABLE", "HandleCount", False), + ("_EPROCESS", "KeepAliveCounter", False), + ("_EPROCESS", "ControlFlowGuardEnabled", True)]) + +is_win10_16299_or_later = OsDistinguisher(version_check = lambda x: x >= (10, 0, 16299), + fallback_checks = [("ObHeaderCookie", None, True), + ("_HANDLE_TABLE", "HandleCount", False), + ("_EPROCESS", "KeepAliveCounter", False), + ("_EPROCESS", "ControlFlowGuardEnabled", False)]) + +is_windows_10 = OsDistinguisher(version_check = lambda x: x >= (10, 0), + fallback_checks = [("ObHeaderCookie", None, True)]) + +is_windows_8_or_later = OsDistinguisher(version_check = lambda x: x >= (6, 2), + fallback_checks = [("_HANDLE_TABLE", "HandleCount", False)]) +# Technically, this is win7 or less +is_windows_7 = OsDistinguisher(version_check = lambda x: x == (6, 1), + fallback_checks = [("_OBJECT_HEADER", "TypeIndex", True), + ("_HANDLE_TABLE", "HandleCount", True)]) diff --git a/app/parsers/vol_Parser/volatility/framework/symbols/wrappers.py b/app/parsers/vol_Parser/volatility/framework/symbols/wrappers.py new file mode 100644 index 00000000..307da4ec --- /dev/null +++ b/app/parsers/vol_Parser/volatility/framework/symbols/wrappers.py @@ -0,0 +1,27 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import List, Mapping + +from volatility.framework import interfaces + + +class Flags: + """Object that converts an integer into a set of flags based on their + masks.""" + + def __init__(self, choices: Mapping[str, int]) -> None: + self._choices = interfaces.objects.ReadOnlyMapping(choices) + + @property + def choices(self) -> interfaces.objects.ReadOnlyMapping: + return self._choices + + def __call__(self, value: int) -> List[str]: + """Return the appropriate Flags.""" + result = [] + for k, v in self.choices.items(): + if value & v: + result.append(k) + return result diff --git a/app/parsers/vol_Parser/volatility/plugins/__init__.py b/app/parsers/vol_Parser/volatility/plugins/__init__.py new file mode 100644 index 00000000..8ecd9c94 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/__init__.py @@ -0,0 +1,17 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Defines the plugin architecture. + +This is the namespace for all volatility plugins, +and determines the path for loading plugins + +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. + +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. +""" +from volatility.framework import constants + +__path__ = constants.PLUGINS_PATH diff --git a/app/parsers/vol_Parser/volatility/plugins/linux/__init__.py b/app/parsers/vol_Parser/volatility/plugins/linux/__init__.py new file mode 100644 index 00000000..2d3e2386 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/linux/__init__.py @@ -0,0 +1,19 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All Linux-related plugins. + +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. + +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. + +When overriding the plugins directory, you must include a file like this in any subdirectories that may be necessary. +""" +import os +import sys + +# This is necessary to ensure the core plugins are available, whilst still be overridable +parent_module, module_name = ".".join(__name__.split(".")[:-1]), __name__.split(".")[-1] +__path__ = [os.path.join(x, module_name) for x in sys.modules[parent_module].__path__] diff --git a/app/parsers/vol_Parser/volatility/plugins/mac/__init__.py b/app/parsers/vol_Parser/volatility/plugins/mac/__init__.py new file mode 100644 index 00000000..3ac3f155 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/mac/__init__.py @@ -0,0 +1,19 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All Mac-related plugins. + +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. + +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. + +When overriding the plugins directory, you must include a file like this in any subdirectories that may be necessary. +""" +import os +import sys + +# This is necessary to ensure the core plugins are available, whilst still be overridable +parent_module, module_name = ".".join(__name__.split(".")[:-1]), __name__.split(".")[-1] +__path__ = [os.path.join(x, module_name) for x in sys.modules[parent_module].__path__] diff --git a/app/parsers/vol_Parser/volatility/plugins/windows/__init__.py b/app/parsers/vol_Parser/volatility/plugins/windows/__init__.py new file mode 100644 index 00000000..d74f4fcd --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/windows/__init__.py @@ -0,0 +1,19 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""All Windows OS plugins. + +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. + +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. + +When overriding the plugins directory, you must include a file like this in any subdirectories that may be necessary. +""" +import os +import sys + +# This is necessary to ensure the core plugins are available, whilst still be overridable +parent_module, module_name = ".".join(__name__.split(".")[:-1]), __name__.split(".")[-1] +__path__ = [os.path.join(x, module_name) for x in sys.modules[parent_module].__path__] diff --git a/app/parsers/vol_Parser/volatility/plugins/windows/registry/__init__.py b/app/parsers/vol_Parser/volatility/plugins/windows/registry/__init__.py new file mode 100644 index 00000000..8915cdfa --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/windows/registry/__init__.py @@ -0,0 +1,19 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Windows registry plugins. + +NOTE: This file is important for core plugins to run (which certain components such as the windows registry layers) +are dependent upon, please DO NOT alter or remove this file unless you know the consequences of doing so. + +The framework is configured this way to allow plugin developers/users to override any plugin functionality whether +existing or new. + +When overriding the plugins directory, you must include a file like this in any subdirectories that may be necessary. +""" +import os +import sys + +# This is necessary to ensure the core plugins are available, whilst still be overridable +parent_module, module_name = ".".join(__name__.split(".")[:-1]), __name__.split(".")[-1] +__path__ = [os.path.join(x, module_name) for x in sys.modules[parent_module].__path__] diff --git a/app/parsers/vol_Parser/volatility/plugins/windows/registry/certificates.py b/app/parsers/vol_Parser/volatility/plugins/windows/registry/certificates.py new file mode 100644 index 00000000..8fd57467 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/windows/registry/certificates.py @@ -0,0 +1,70 @@ +import struct +from typing import List, Iterator, Tuple + +from volatility.framework import interfaces, renderers +from volatility.framework.configuration import requirements +from volatility.framework.symbols.windows.extensions.registry import RegValueTypes +from volatility.plugins.windows.registry import hivelist, printkey + + +class Certificates(interfaces.plugins.PluginInterface): + """Lists the certificates in the registry's Certificate Store.""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]), + requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"), + requirements.PluginRequirement(name = 'hivelist', plugin = hivelist.HiveList, version = (1, 0, 0)), + requirements.PluginRequirement(name = 'printkey', plugin = printkey.PrintKey, version = (1, 0, 0)) + ] + + def parse_data(self, data: bytes) -> Tuple[str, bytes]: + name = renderers.NotAvailableValue() + certificate_data = renderers.NotAvailableValue() + while len(data) > 12: + ctype, clength = struct.unpack(" Iterator[Tuple[int, Tuple[str, str, str, str]]]: + for hive in hivelist.HiveList.list_hives(self.context, + base_config_path = self.config_path, + layer_name = self.config['primary'], + symbol_table = self.config['nt_symbols']): + + for top_key in [ + "Microsoft\\SystemCertificates", + "Software\\Microsoft\\SystemCertificates", + ]: + try: + # Walk it + node_path = hive.get_key(top_key, return_list = True) + for (depth, is_key, last_write_time, key_path, volatility, + node) in printkey.PrintKey.key_iterator(hive, node_path, recurse = True): + if not is_key and RegValueTypes.get(node.Type).name == "REG_BINARY": + name, certificate_data = self.parse_data(node.decode_data()) + unique_key_offset = key_path.index(top_key) + len(top_key) + 1 + reg_section = key_path[unique_key_offset:key_path.index("\\", unique_key_offset)] + key_hash = key_path[key_path.rindex("\\") + 1:] + + if not isinstance(certificate_data, interfaces.renderers.BaseAbsentValue): + with self.open("{} - {} - {}.crt".format(hex(hive.hive_offset), reg_section, + key_hash)) as file_data: + file_data.write(certificate_data) + yield (0, (top_key, reg_section, key_hash, name)) + except KeyError: + # Key wasn't found in this hive, carry on + pass + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid([("Certificate path", str), ("Certificate section", str), ("Certificate ID", str), + ("Certificate name", str)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/plugins/windows/statistics.py b/app/parsers/vol_Parser/volatility/plugins/windows/statistics.py new file mode 100644 index 00000000..2e637bf6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/plugins/windows/statistics.py @@ -0,0 +1,71 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List + +from volatility.framework import renderers, exceptions, interfaces +from volatility.framework.configuration import requirements +from volatility.framework.interfaces import plugins +from volatility.framework.layers import intel + +vollog = logging.getLogger(__name__) + + +class Statistics(plugins.PluginInterface): + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement(name = 'primary', + description = 'Memory layer for the kernel', + architectures = ["Intel32", "Intel64"]) + ] + + def _generator(self): + # Do mass mapping and determine the number of different layers and how many pages go to each one + layer = self.context.layers[self.config['primary']] + + page_count = swap_count = invalid_page_count = large_page_count = large_swap_count = large_invalid_count = other_invalid = 0 + + if isinstance(layer, intel.Intel): + page_addr = 0 + expected_page_size = 1 << layer.bits_per_register + + while page_addr < layer.maximum_address: + try: + _, _, _, page_size, layer_name = list(layer.mapping(page_addr, 2 * expected_page_size))[0] + if layer_name != layer.config['memory_layer']: + swap_count += 1 + else: + page_count += 1 + if page_size > expected_page_size: + large_page_count += 1 + except exceptions.SwappedInvalidAddressException as excp: + swap_count += 1 + page_size = (1 << excp.invalid_bits) + if page_size != expected_page_size: + large_swap_count += 1 + except exceptions.PagedInvalidAddressException as excp: + invalid_page_count += 1 + page_size = (1 << excp.invalid_bits) + if page_size != expected_page_size: + large_invalid_count += 1 + except exceptions.InvalidAddressException as excp: + other_invalid += 1 + page_size = expected_page_size + vollog.debug("A non-page lookup invalid address exception occurred at: {} in layer {}".format( + hex(excp.invalid_address), excp.layer_name)) + + page_addr += page_size + self._progress_callback((page_addr * 100) / layer.maximum_address, "Reading memory") + + yield (0, (page_count, large_page_count, swap_count, large_swap_count, invalid_page_count, large_invalid_count, + other_invalid)) + + def run(self): + return renderers.TreeGrid([("Valid pages (all)", int), ("Valid pages (large)", int), + ("Swapped Pages (all)", int), ("Swapped Pages (large)", int), + ("Invalid Pages (all)", int), ("Invalid Pages (large)", int), + ("Other Invalid Pages (all)", int)], self._generator()) diff --git a/app/parsers/vol_Parser/volatility/schemas/__init__.py b/app/parsers/vol_Parser/volatility/schemas/__init__.py new file mode 100644 index 00000000..0e140c7d --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/__init__.py @@ -0,0 +1,82 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import hashlib +import json +import logging +import os +from typing import Set, Any, Dict + +from volatility.framework import constants + +vollog = logging.getLogger(__name__) + +cached_validation_filepath = os.path.join(constants.CACHE_PATH, "valid_isf.hashcache") + + +def load_cached_validations() -> Set[str]: + """Loads up the list of successfully cached json objects, so we don't need + to revalidate them.""" + validhashes = set() # type: Set + if os.path.exists(cached_validation_filepath): + with open(cached_validation_filepath, "r") as f: + validhashes.update(json.load(f)) + return validhashes + + +def record_cached_validations(validations): + """Record the cached validations, so we don't need to revalidate them in + future.""" + with open(cached_validation_filepath, "w") as f: + json.dump(list(validations), f) + + +cached_validations = load_cached_validations() + + +def validate(input: Dict[str, Any], use_cache: bool = True) -> bool: + """Validates an input JSON file based upon.""" + format = input.get('metadata', {}).get('format', None) + if not format: + vollog.debug("No schema format defined") + return False + basepath = os.path.abspath(os.path.dirname(__file__)) + schema_path = os.path.join(basepath, 'schema-' + format + '.json') + if not os.path.exists(schema_path): + vollog.debug("Schema for format not found: {}".format(schema_path)) + return False + with open(schema_path, 'r') as s: + schema = json.load(s) + return valid(input, schema, use_cache) + + +def create_json_hash(input: Dict[str, Any], schema: Dict[str, Any]) -> str: + """Constructs the hash of the input and schema to create a unique + indentifier for a particular JSON file.""" + return hashlib.sha1(bytes(json.dumps((input, schema), sort_keys = True), 'utf-8')).hexdigest() + + +def valid(input: Dict[str, Any], schema: Dict[str, Any], use_cache: bool = True) -> bool: + """Validates a json schema.""" + input_hash = create_json_hash(input, schema) + if input_hash in cached_validations and use_cache: + return True + try: + import jsonschema + except ImportError: + vollog.info("Dependency for validation unavailable: jsonschema") + vollog.debug("All validations will report success, even with malformed input") + return True + + try: + vollog.debug("Validating JSON against schema...") + jsonschema.validate(input, schema) + cached_validations.add(input_hash) + vollog.debug("JSON validated against schema (result cached)") + except jsonschema.exceptions.SchemaError: + vollog.debug("Schema validation error", exc_info = True) + return False + + record_cached_validations(cached_validations) + return True diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-0.1.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-0.1.0.json new file mode 100644 index 00000000..2edafefd --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-0.1.0.json @@ -0,0 +1,307 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "element_metadata": { + "type": "object", + "properties": { + "format": { + "type": "string", + "pattern": "^0.[1-9]+.[0-9]+$" + }, + "source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + "element_enum": { + "properties": { + "length": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "length", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "length": { + "type": "integer" + } + }, + "required": [ + "length" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "length": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "length", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/type_base" + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-2.0.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-2.0.0.json new file mode 100644 index 00000000..e6aad3c9 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-2.0.0.json @@ -0,0 +1,307 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "element_metadata": { + "type": "object", + "properties": { + "format": { + "type": "string", + "pattern": "^2.[0-9]+.[0-9]+$" + }, + "source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + } + }, + "required": [ + "size" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "$ref": "#/definitions/type_base" + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-2.1.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-2.1.0.json new file mode 100644 index 00000000..f1188f9b --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-2.1.0.json @@ -0,0 +1,317 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "element_metadata": { + "type": "object", + "properties": { + "format": { + "type": "string", + "pattern": "^2.[1-9]+.[0-9]+$" + }, + "source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + } + }, + "required": [ + "size" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-4.0.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-4.0.0.json new file mode 100644 index 00000000..9337aa5c --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-4.0.0.json @@ -0,0 +1,331 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "element_metadata": { + "type": "object", + "properties": { + "format": { + "type": "string", + "pattern": "^4.[0-9]+.[0-9]+$" + }, + "source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-4.1.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-4.1.0.json new file mode 100644 index 00000000..66c66e29 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-4.1.0.json @@ -0,0 +1,338 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "element_metadata": { + "type": "object", + "properties": { + "format": { + "type": "string", + "pattern": "^4.[1-9]+.[0-9]+$" + }, + "source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "constant_data": { + "type": "string", + "media": { + "binaryEncoding": "base64", + "readOnly": true + } + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-6.0.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-6.0.0.json new file mode 100644 index 00000000..73be8ec1 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-6.0.0.json @@ -0,0 +1,447 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "metadata_producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + }, + "metadata_windows_pe": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "revision": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "major", + "minor", + "revision" + ] + }, + "metadata_windows_pdb": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "database": { + "type": "string" + }, + "machine_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "GUID", + "age", + "database", + "machine_type" + ] + }, + "metadata_windows": { + "type": "object", + "properties": { + "pe": { + "$ref": "#/definitions/metadata_windows_pe" + }, + "pdb": { + "$ref": "#/definitions/metadata_windows_pdb" + } + }, + "additionalProperties": false + }, + "metadata_linux": { + "type": "object", + "$comment": "Reserved for future use" + }, + "metadata_format": { + "type": "string", + "pattern": "^6.[0-9]+.[0-9]+$" + }, + "metadata_source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "element_metadata": { + "type": "object", + "oneOf": [ + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "windows": { + "$ref": "#/definitions/metadata_windows" + } + }, + "required": [ + "format", + "windows" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "linux": { + "$ref": "#/definitions/metadata_linux" + } + }, + "required": [ + "format", + "linux" + ], + "additionalProperties": false + } + ] + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "constant_data": { + "type": "string", + "media": { + "binaryEncoding": "base64", + "readOnly": true + } + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-6.1.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-6.1.0.json new file mode 100644 index 00000000..ace0a8a6 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-6.1.0.json @@ -0,0 +1,450 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "metadata_producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + }, + "metadata_windows_pe": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "revision": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "major", + "minor", + "revision" + ] + }, + "metadata_windows_pdb": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "database": { + "type": "string" + }, + "machine_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "GUID", + "age", + "database", + "machine_type" + ] + }, + "metadata_windows": { + "type": "object", + "properties": { + "pe": { + "$ref": "#/definitions/metadata_windows_pe" + }, + "pdb": { + "$ref": "#/definitions/metadata_windows_pdb" + } + }, + "additionalProperties": false + }, + "metadata_linux": { + "type": "object", + "$comment": "Reserved for future use" + }, + "metadata_format": { + "type": "string", + "pattern": "^6.[0-9]+.[0-9]+$" + }, + "metadata_source": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "element_metadata": { + "type": "object", + "oneOf": [ + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "windows": { + "$ref": "#/definitions/metadata_windows" + } + }, + "required": [ + "format", + "windows" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "linux": { + "$ref": "#/definitions/metadata_linux" + } + }, + "required": [ + "format", + "linux" + ], + "additionalProperties": false + } + ] + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "constant_data": { + "type": "string", + "media": { + "binaryEncoding": "base64", + "readOnly": true + } + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "base": { + "type": "string" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/schemas/schema-6.2.0.json b/app/parsers/vol_Parser/volatility/schemas/schema-6.2.0.json new file mode 100644 index 00000000..1f388005 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/schemas/schema-6.2.0.json @@ -0,0 +1,498 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "metadata_producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + } + }, + "metadata_windows_pe": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "revision": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "major", + "minor", + "revision" + ] + }, + "metadata_windows_pdb": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "database": { + "type": "string" + }, + "machine_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "GUID", + "age", + "database", + "machine_type" + ] + }, + "metadata_windows": { + "type": "object", + "properties": { + "pe": { + "$ref": "#/definitions/metadata_windows_pe" + }, + "pdb": { + "$ref": "#/definitions/metadata_windows_pdb" + } + }, + "additionalProperties": false + }, + "metadata_nix": { + "type": "object", + "properties": { + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/metadata_nix_item" + } + }, + "types": { + "type": "array", + "items": { + "$ref": "#/definitions/metadata_nix_item" + } + } + }, + "additionalProperties": false + }, + "metadata_format": { + "type": "string", + "pattern": "^6.[0-9]+.[0-9]+$" + }, + "metadata_nix_item": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "pattern": "^(dwarf|symtab|system-map)$" + }, + "name": { + "type": "string" + }, + "hash_type": { + "type": "string", + "pattern": "^(sha256)$" + }, + "hash_value": { + "type": "string", + "pattern": "^[a-fA-F0-9]+$" + } + }, + "additionalProperties": false + }, + "element_metadata": { + "type": "object", + "oneOf": [ + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + } + }, + "required": [ + "format" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "windows": { + "$ref": "#/definitions/metadata_windows" + } + }, + "required": [ + "format", + "windows" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "linux": { + "$ref": "#/definitions/metadata_nix" + } + }, + "required": [ + "format", + "linux" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "mac": { + "$ref": "#/definitions/metadata_nix" + } + }, + "required": [ + "format", + "mac" + ], + "additionalProperties": false + } + ] + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "constant_data": { + "type": "string", + "media": { + "binaryEncoding": "base64", + "readOnly": true + } + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + }, + "anonymous": { + "type": "boolean" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "base": { + "type": "string" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/app/parsers/vol_Parser/volatility/symbols/__init__.py b/app/parsers/vol_Parser/volatility/symbols/__init__.py new file mode 100644 index 00000000..e656d064 --- /dev/null +++ b/app/parsers/vol_Parser/volatility/symbols/__init__.py @@ -0,0 +1,11 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +"""Defines the symbols architecture. + +This is the namespace for all volatility symbols, and determines the +path for loading symbol ISF files +""" +from volatility.framework import constants + +__path__ = constants.SYMBOL_BASEPATHS diff --git a/app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/15B12C74F0E177581B6B27DD4C5022C2-1.json.xz b/app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/15B12C74F0E177581B6B27DD4C5022C2-1.json.xz new file mode 100644 index 0000000000000000000000000000000000000000..d86f6fc41eda8011286ae3a1167d5b94bc54282d GIT binary patch literal 585376 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ð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ℜG2c& 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�KOjZJ%8oqMr=B{7!y(0&ZtwCdLfx{iI(( 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%0D`y(*xsY`ao2!%$drnc{C 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≥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 z zNnEQIiVlErTNcwQTmXP{jMXGJ1gB@$*3hd>zb2f&Y= zT}Fq*sb*IT#i-h(&?e#Gk-fzNnt*Z3CWbPbN#bi-fgy)#RCfsMe?>D`?@y zY#U1G)mQl~@>d5iQS%I=uJj0?m6NFqEh=d4XS< zDJ=--AIr%;wJH1Wg{rwOIQO#y@5#jFiLr9am+bhx?Ovd%M)lLy0eIL5A2`rQ{CU_H ze9g~rKxF6&luOy)P?<3q}>;->#?3{VAK0RmEqW5r|zay>&@JcN%2#XPK1blj0V+CY)%f^=oyI+|` zBop1JwD~Vrg;%5;m0K#AV2kYulE1d5S{7A@tGXih% z5*S3a2eb42Bq{V+c=$571r+XkJ25W_ZrN(DtM_Q^=v%Ku^Y3xa4p z2)$S80rtBriw^U6I1@^vG|-EjSj_sp5?39OS}o=N^L?tI3$iC+IYm(KI0X||mh*@R zf`gkDh}h{yo+Wj!Y5{q~w$Nm;xhQ#ptvHIqR4ZQ&{E=yIpL+hF+$~8c8sq10iI$;d zm9HYA0@n(q16uNv=g8H}{Jp33qsLtce$E%2LagA*H} z`A-yZ+r^y|ortO4ik;1wL-UYSlu8jpr;jsC+i_w612-O>6=lqsx8pIT0J5ASC1o_a zWEMPRq-ngYc$i3}>h)_7G`JC3wmSb2(jl%I~R8+ zWy3Ep0R@ok5I8Vs&g#L-_q6kETHX0R3DcJ4!}gXaKTC-Ye|l{o1Y{D%I1s|9>hK4N zE@(mLpAGuJNQYEqZk?$3xx%eAgcWJw;^Wj{V0qMZPngNBN^w<@)Bu}nmMnqgL^cTU zuWZZ^d=-L@*%rfqn&}!j=pJ-^S3y0ngC~3AHHU=uWWtL`h$JM|5(IniGvrTyHfQ4i zX^p4|_M5vLaj}eX-hSs%4oT-h0NUl#$~zpcP`8f?p3YYm$T_C~@UK*MPkfev>f+M% zx$DT6bfM3_Qs}s0QkG)mrBj9|W7#FzYSg@LyYL*RJ}p2x7J*ElVLomp9rCZb?PpCm zeEm`nq5{k`NE7gQ#1_i*`Uax^2USfU;t|@wgfUQPbMOwU%P5$x$>)c6rWIvV&w_~? z>lxb$gKNGqckpLTU?Y||mQr^GQEm839jT{nSbW;(F$BK;VwsvOKQPM z{wtq|29d91Iu|2U^dt3nKhIwu{%y2?=rmH{Q@I_42p<6WmU&CeOgAVa-ltcx6x=T?3y5lB?&W0Tz87J$@kyYO;u zd?{JFy7(k=4B)sh-s17aq(X7ST8$35fJUb3NO`!>8i@#}8&TEL4giWWE08y?>sI_a zTAwFsIuQ^y=Wx=r#$F2j_>hK(#nO4SCc&8NQ-awpr&e0&JCUhb#|aIM4*Zd3wv}l# z7iTm$p0Os7KyCQWcu5byIzevzp@bCAwZQD&5Ah6!aZ9a_48rWrk}c<{rOGEd@!Q}j zY<2()>aA!h2-s@}+*bWaS&;B$8;!5KHBy#dp1wbfXFA11&^sTr-m&_N|zqH-eJkv{+d5^i3J^5I2?TA&D2J6ViG0)m+#{#$cJ}O#m)_-{jd60=} z)}5ieX+|xKllCzkm>J(>uT?W6tN}lb6GuIBrzOOkghwi;TG^nC7`(K zak5>_bM;4&(7nIeqiy5W{LP3Jy+3RH&oP&Q2ao?kQjfAw!`Ua%PtbXJ&(p8=1FUcM z;^a%!=76say4{`C7*LzdcVI%*a_2Wdebob6=J#@k@H}vQ4t3CF8Okmn>6^b(DsNO0 zgW2^SVfz7yDHOZ=IZj%p=Hg$uZSb{M&IUlYs7F@>{B!^T+MZPVx?E`bMa*Dy@rB=K z>$QN_b!q3*=bud$FIeJntfe#}%5FGJZOb!Q zcHDmq{0ON>TThttFMT2%`~s>gWUCIAi|^RhNd3ac6W?9X}T1soRTHu3i6_w%I;! zOy@A0j>woR1kRp_nKB(c<%GnSX?>ifOY{XC!}W<$hlo^>dt<|iWBFVIn6AcGUD6B5 zsCRTn7E5Lee*d#>94#h2pBk;4>Fk~2mnH0oi*VNpa1$r8+Y@P6|0P!_gY1;8KS{YVmWzE#oHm` z_gM?i(VGmNx?*kxNAdvuaBlSJ6>*Q%51vwkx(z3m@R?fdN|O|N%z(Pn$Qg1U%&;m` z`O(_1ob5Y)b}B8dJsxf{K#)k$>(BfL*LW?BBdI7W9aE*7alOCr^Np)mYXZM=C^%Ig z3#8Q~PX3VT2FA07ETrWuF%wGE@l>(~5KTbx0PwXuQwJ4M)awZUHcRix! zgA@BqGbDP#?0BRD4QV*GJ72=KEv!0vTTiy$cAds21Ph#O<%g%8_ zP19K!CySkofKj$*YvQ36sm)O_3l0g-Z8zJyRG@h%q)U_)?s;NBq`CqyPW^K}lgUFo zQP8IynDdseISUJnO%`D& zejsjzSAUb0yEHqf;_`0w&w!F`H&v%?RGCRe;))xkcg6mjr98w}G^6Tymmt+8NAcBF5 z7r%0>-|&tVM|~>~fXJQx4E0$XMkC}MxrfFrpdR{T(*Zp(l0*~vglLfJp_ek#GEWP9&w z`MA9=kz5u5Nx(|Kka}s{e0WEXHpWRu8z!e?1lDLFs;1e7;H6wGvHiVz%y&cpLEq-( zc|n0V=xWS7**ijziOjJx8H&u2e%O4$9M-wI;tVs%sV>uXREDjh92@W;{01*Sf0$R& z2uzSR(L#>Z7)`i%4kh^E=8)4%$Gmw8MWb2jieD*_l}YvAXO{Whdc&r~%xXxD6Rd0g zRZvzNZ<#KpmnOi<7uoB7vi9p4Mbf$eJmNSvvr6fK2(U*pSTL?G20D(!ZekR)6`uS3A+((b^?|06<#z~CkF^H zO53%L^X)ik1DxgAK>ksFk)?f%!Tscp!-T^Co@t@0$*^_$joH8HW0s3FJu0EHTJQeE zDdqP5paG(%i>@@v(k3Wy)_@n+6EbrYUL3A^P~Vl60nuVnXgx%Ss$I~af!3Regj!Xa z4&-SM5sGYb`W=@Rwup<{B78E1j2 zdVbS?G?UGn{$My8D9RiCW!m+-rK==21R=2(t6{8DJyJoO3?7**I%D2Fz|Ry29UrS| z*KuMJ@P|G%lH+I2A6Xdbi2{+wo}kL~O|a3$|*X2PNvQQk<~G!PWTXi8G;F&>FT{Bb-HWW3%4| zmPx~#@?-m|g2=35ubDO#bxGXL&p6r23StxFar~4qkIclT7&G-K-x44wrLe_x3e~;> zTGC@N=o`EzurbSrEF3+BZdzG@IEB6g&pQF1>|k5x%<}}vFo`&DfK!eJ7kK8A7eGk2 zdix<}a_%}_ynEsYR)h5wZ5nq`22R9q@-A*0^j&S2Ri>16AP(xvJ5HG(;@4@+dV{lF zW=ufN_J~X&TlWfLJn94B%MFq!dO!euC8pyE0Z7Fep+ZZdKyASj@(1@WTb zb&IMHSlTA9Zanu?2_w}F^r=15X#*;p-Fd0rE-j@q)euibu1En$k$dQ&vg&1l#cWnu z((pWmkE6tfK1{*Nb^7Eso>QTpHR|=)V%h^x=4{vKImaHBLxQ zTkSO1QN%sUO<>%5K`F|3^$IQYmym|u?IQ=@9u8x`29)^W&K!1L+w~W{;kX_$YzxM7 z#Zu0HBHkAb1CN!OZ|qr`Rv~Si?B_;xe`4qEjN&2Xzi(S?^eGG@};0oq!kmzni7=1M)F53V8hO{^M+p@~V=VhnG zbs8K9SHrgfYHL$W&ia_u-aeHq^@@a}PWQ*Fab={AL{yj!C?~UvGQ;Ow3V}X2>k6z3rt0D?lY*aY$ObBb zBDK&u1Yf6#9O0g@960D`nX$5XW%;&VZE69`5E! zI9yEaiwYZ}YK)n!q;7ci|H(Wb0A+^pP^9Mwt`+AV4}pyMU z2wbYT%`1ZbJh%zJ)tf+*8q~&?3f_1?;&UH+zeUjaN@H(nnlGSTkr&Q7@LC;Q6|(as zHj0v9-M45LPZvX|sLotWUUd&%Izmfywo~_1gspLl_`X{kQyUMIWNlE3UfC7QC+mHJ z18%^bQslKsfFq(yTHmM(`jW~b;=hA=3u@{3nynoG93XJz%8Yma0MksyutL&obw$_v z>d_p9bGCbb_1a&XqrG|4;i^~>XVDCXNbNc5SlwrSe|Y7!T78ncjvSOtkD8)5o%!TH zxFs=Odb_Sv!67~*ZrD`h2vFd6^Z1LuOEUrx1ZX18S(jKQWAh_4rR2{>G`GX$Ymb`g zi-77Dfuz7Pb~#cu?dO~`5WT}TC47mB3jJ7pY^%v!(XFZ+qXMY6vN_8lgG~BFW0?qH zbh!hT1PEB1t$8pcKMT0CR>`mY_$8HK;l;}#NH(GKl08)q5?Go=q?8TWt@iDX*P;<{ z<#CY1vQ`2r73sb>VR`FW2aWLFd7}S04Ng{(laGTpx1+0dICo7VI#&vv+ANVbx{|oN zi~Z&t2}<~}1$CNZDoL;hOMsR(fzk)U>2b7m6d zRTyh>lA1d&h(cn zO&c8O(2gtv>}F=3Ca$#aFjLrSn1C!>AVJBTN68LiryfBI0&pk1ZP#|;FQgAbPovU5 zlkvZruz&o&%m$IdP_ZopDud~7#~iFLoOtf|dy1toKYmB=$W8O=^uWX`3^Qqp=d;63 z4~zYZ9uFMBXq!dP%*Rc0yF~kA3Q53_vAOJbdAc46;{}R8HYfY^I=(c@bLnYi(rx4v zD>HxN^w@E9Z6eC>|6vCGKgZNBw$W9ts^XwIcLPLMXEahb?_V{d=5LDuz4 z{?MX0;p}Uewxa?FuYX$rtrrGb&iEfrJ&fVx6vm}wH26s`?h!pqT*95w0fh4%f7L(N zO>5-r9I|gNM$k1aWZ}^8Ux@S*4sZgZd^gR^Oo5eUK!x72{ylVU5PsX$%G>_kd$|MS z{8fc1@%c^!SAO~>+g4oSbqAZ}PA~$$K*b#*mI)6aOrMA2)?%Tl_#A^IsdwUew3oYC zbyx-+{bD;Ga0)oj9bKoCvI?Lgc}73|jP}nbJ{`MeXgOGMuefkd0E(XRVT&O|wMA<{ z1qmu=N<2)~+Uw_yQnQlWof38k3-&W23v7rno!sG;E4_!{@45z`iUt&9lM@HtY_;n{6QgMw);;Hz?O{XTLH}>4R0MsZyk)K~T?eMlp z0YaSxpuNQzq^4^XHED7pnA`Sud!)%9o8M~xhcDHl0JBP~s(O~zNOP_aGlkv@Zt3*- z`+LiSHu??X`_SaFbQUnKYe!{A_SVSUs0mXX3uE?xZYpm_e!6T|4`2*)JhRNvYo?k0V+oxpJ&! zh{k{(pc`74Mc>UTS&TXpcDi+bf_x86uH(-*L~Yy6AN;hWOY6s`o; zsw{PYTwPG{g%UQaGbP!T11DL7vamoMsLUrqBeqm81maLxt!Pc}Y+pL_3-urtgE&4+2Vy{t(ccS6)cwtiiG2^QJAI;0Cu(~?p|)IfE);o`{Xfo0pe&q_H|o}B zahRarJ8n^ha>xH4+n%^%TbK(#D$}s|NxGLGw}+M}2_%394_hr#HWi^!!1@!}W2gkRl9d+C!m9h9Xa>kdKx*>xoEk$`H=c=AC~#U4e+_w#%%v zF$JpJKr8I7U7ti|=Uh8;zOHIGTJDRPf?!TKJ> z;S0no!c~p5G_D9RQA@RFq5S{=^q@FJP(P(j=S?6@%5=^DaD`NkLs`fv+u3>w!n&g9PY`t> zTcdA~+=MZ=RcS0D`@b`oZwhd3_Q|CTD_`oz(%A)?4uV(;Uda8PAgs;&C^ff?9ybFs z4o%#U83WIO7w{Onts>fOArEVz)HE_El@xrk6julxvDH_EMv|^RR1fv8uiD>V%YB;w!B6$N;u+J{~473A$Z~=ugM>Z@>}NNbD9ODe~A~8qwEmkKxal zM|<$-iZ|*PJfh(WCNS((#HKAkWGJhQ{FxGj2|O%O^{3Y0UWo_Zs-Er#)dH$$<8vv9 zrLC3rZsTTprW zK7ACz_O(nZIi(&T&wZne*(VDbdHVR9SF@t02k`lYgG_AxE1G!pSdFl=&HD~qaCd&N z?F!$B>Ihv{+h75!2bSe*lNh{0y*jt6X(h~t8`ojYOK9CWQuC5qc(##jxQd?%_3-a> z=_>rfBG^xuTDLHLv}@6=((2;-dy~!hOzbV(hc4rPUiFy=u1r_)yD)~1E{<$1iRZm4u&h%<6GRN2JC|bel5oy=iyI$B!Z0Fln# z?*}ZO1ZS8lF<`_3$}Tm;(uUnPK2LusbT=tKcE{o3=JY3zt=+QpoS;;NVt^UuM+|=1 zLjGK+mGdDu;goaD*4LFSMkW!~LRv*vVqR~=etj9B^B-qNaMGOj4s5(lAR}X$D%d)m z2w3y6#b^0}JW{-RwkIn|3a6QIpz13UlH;w1(n$eQQ~gz6AF2$*>o)A)7w- z2auiHFoJfudIw!=Llt7*dKOW|!%RDwfhRl)?J6C`EoZAz+*-V&1RA}OUSQ_gL3}PN z&sOM(7GfH`67X4a5SqMg$J}7KcdjmaF(X)*t0hOWN_bCvJ@{(wsw{{`cS5|Vv)|M` z!^B6Pdke#iVhsA@@*Rj3IrIdMSharpVLdM-5*<-wgkY#)cEm zh5%`;cy2JsfgGSJ#3pgWLEV5@ZEI1whH4aKo@T2IpO!`nktmN17x`(D3EBRbn#}#|b*= z_}M@dG6|N90n#|a7}p_s9y@9I{T*nhy36j|EcO&foM+%WHO0F{8B5)1zt&&J{^Z48 zD&+Hu@bCj^$sKQ1m=e7g>*_hd!^I|O+i>EsKx1#-@+D>RZD}xJge#GJ#=S-Ea!ks) z4mB4`I^YF5$?pbrbT9UPs-y+=Ah(s}5W#ya`Wq1FO)`ZGnmiB+xz9$Ayokd$svMLc zaOA4i%ABmP?ZvkA%+??gk%V^C$}FV(;%BG*#Z=WE1+T(qf|cAEwl~OYTKEV zn-8>vQ{8Q0f9UBoo#7F;*mq+gW$FkAFB7DX{(uVy)$D%9FS_9cxfE#?d5Sp0R`0%= zg^x$Y*+=!d7C~Wp#IiG{k@Xu{mo9sy1{6p5tJ9*9b(b1K+d3ss7YOp!;pv(pCH*Q& z&qVEPS*&m6o+)}9+sP*X<8CZhfmv`t$~`)wnBpDXz|?QD??35f;I4+B4gEKCJfKPj zE^Atm%*&XK+;-Z*Dw4@JZDg&hP8dDa@%TYW@RK!A2RPz8KVJF#O#804>d$UkOaomk z$*ZDzS9+itTe{envOt-7i1M0kv_CF>uN6?gZ+YLli;2fQIG)O2D@k9BE(~lRgT5Y~ zPLf;;A?H@YlS&{4JNtuw6O3aCoJp1K9#D{^P5S4s+#fN-mEGi060G+Oe~28S4ki!D z?#|rrNfD9&Y7Pu(8 z=_XcIjl0~BFH9ZjX+jdQDQj2at{LGKNUi$NJ+C4+w{WAM^<>k1gumF=#KXZ`5S(9h zre~`mA;^{H3@LtxYyG0|lpZJ_03WoanH!K*j2NxXVfOvMuTDzi0(Ey3`@{im4S|mh zeX7;JQ7!W9%$Xv$K<7-LkfZ9WIO8zDB9e1%lNcK{*O}T0VmM;UG41E?2rW0;pyukf z_BWCKwMON6Ja0xXIfShH7PyrvFwspY4AXgC@qjL;N_tNW6)lnBG%> z1KJ$jOAO;cn`|5hv?Ihg5ZH<7tw2!x8t4V|^Kq>AVa>)27yw^o1eC0B`TkYpypj7)IWPh+*+3UjHFh zHCDRXSENZrr8Bv#z)g`> z0L&0SCaDW4IS+OrVA*c*-ZJ^QdZs|AQ3S>XvPUKO0FDtjs3taV$&^bI-IF$xEu`&M z7a~&ZeXO7I`Ls%;wLiNk#hJ4u1^uS%jycBnZvNWmkbzA&k4=ExQ*Rhz0J-a&MfwjZ zXj+D$$mKEBkCE?^$UqhGsP}W%cXBlg#)44+BXt8ZCQ~guFFN9QpcIOp8g3%JZBsb5 z%+ajoLO^J7<8(Ow)YmmaRSVD~Doeur{q)OM=tjR#b*H;;#+r`!v#U+AUMB8x*m*SI znlG{_AX5twp1P7lO?&mlqYl8w|NF%4v9b-Q37>|M`F#5e&)M@4CrcoBxhL>BT4;LT z(2rVXSlvOlMt9>M`c5CjE4SbJt-^Il+wR6aeIMS{?`lyA5w*~!W2;=YR8XpBjs*Bq z=TuhT!FjgqIs5siFHPHCN2f49Z~DdXF_{O`OPy}dR1l*)g;lCjNuIDWq%O>(IGJx~ zUfLz?2e3dz=cP(ANn3U6a!`?2|F!SWamtE#g_3(_YH{XPXdfS9AvXDn@|tBVV2o&Z zDq5}qbbtwr!I1JzAun#bZ<=xRyD=E19~wToF0A`DZVqV$*}f|w&wC9B6pq+?%z}m{ zifr>*?wPCkp_(Sk8Po@ET5tP2YVZO>XJ9AAN4CW*N;J*hlM1J#7r?B&F%NW{&DEh= zgU9I!!%WIuda2&YGz@M+4CKQ_Jz<;3=FG*7E2;=nOK)8cR%UQRVzmK;gpZ}jQt|`6 zV4L0|!)eTS4|{*|@8kI6$!zBdl`e{lCadiyg*OgIu5yt5%|`)Rqr~J&Fxmz6>EKLb zdHcJ%ZIgNQ6Qbm?Y_f1!K2l)A5Gm053NEoMeFYuYTOu)Rn+e!y77G{Q{P#s+8zGD1 zprOowJ@?Z@dAa5;_)8QWmY#=evYy{-FbrA1LSsb$#PnP)3`4IKp9fji2?vhyUVN2` z1)s37w9(kXQKC`R!ODZt^sn_qDaaQ_A)0kqNb>fgLUVEv;9TpL$}|DiXMV9_A@#lB)YIiF8Re~h3CJ7(t+J*_@i^CZHaTT71lEQSZ1 z6}R`5%d)E*uXgcB7rT!EO?-~~BEa~yd+UD6APhD$U4Znp&FfeYa|^;uYWm_oDPkvG zqR5rfccCy>i%OqbkB&TKYoO|<<$ne=(mSn9q+nD2L>cT3pWs4zQ~dZV1I>sr#B(Qk zc8>~uXmmm`u9cUl76jy1`)ew(n1l?LdUHH~z%A~ZPN%~y-${t$xMEQXWZw}t1U0&H zwY$6glpmaGgEMRu85yu*OY~LMN^%jMUb7)QFx=`R?8>#e@?BtwrEKpKZIxMAvZ(xp zhRyCyobogbvpNlT?;luwR-#cJEK&n&$=?6=?Jm=SD+JW-JHu^m*&$ZoEYv?7WfhP2 zsAnuibW*NU2tF*K>%hi)*#_Vf^ax?xBEDkl^=tj$S1j=BpwW4VXuc-xqvn1&Hau4} zd@4RV(}I%A+R9vQ8>U7mb>Fgl7w!v0>qPToAlW!a;^t{gdT$Mq&XEQ9@LLQ0;v>Wu zW<-{h(!!40qcQ))R%1$E3j(nh4hw)}sG(&wvMTy>J|T0l8T|%tfGU$+7Kxp34wd(a zI^XR?vul_sO{nNF7Nq`Z7)v+=T=|{5VQN{3FUg{nr%hB~BN9LvB!*%9 zCKV&I`#SCt=@sEl4WgHTys>&O0^aLmHzW}T1apKBZeL@%TA^;(;T2z9Pvz~I&rXh) zYK6mAK=gomvmzioJiq@=DjZYeCE-|_5QxaRt|yv^?~(+H6B@)mwAVE0yS{NF66W1D zLIgoqzYj2<$;8F?T8t|kyJ1QPd6Mix`C&r!Rfe~=At6JwDbrhsL43J<0O0!`MIcm=WY|lUuQu# z-rcv>WzS<@&)m{mU-EKblE;- z+`U;HxgPvN75+0yBIO^TrFZ`%N5&9q8sGh_G8RSeB2$n-*cWs6p$b60@PHw0=BupN zXVl-u&JSNI7b{Avr}2AbR+%#hD(|$d8X*nRU`{6VXPQt zQp!cj?M(Xd&l#`luNL^Py~(b~xy@*rv|uu6V= zQcow%sP#((dv&)}%NjC4#x)S|9vB3S2O~<8g%)M=) z%7bW^{cZ1LqB{-a$Vlb0mW;oh91Cr!^;+y--w%y=-z3bOx_f+PNB7x{BM=?~?EG`w zoy8TTcL?_!m{IDtRke=vTGLQd~qaY(tDl*}q;mh*~&B{b8b?RNEwwlTDYy=HTHTU-@`6P zKfVoT)`2$8oD%B8#u<+Y}w zUz#~v^7Z7ege$B1lC0Y?q6;ytUwIBhU@%pfl@ZmuTuD9s#PMKpd{QD{v4O3>T@>R) z4CIk=>RGYtCL1AiKm$(#sAf@GscXdCzrD55>xR^4x(w>n8lKQg+d-8hcw_Av>$?#3 zgkdM+X4&kmXjh`EO(->3%mXpyXPkFH`s~v~;lRU;6+2*hF*k^@Jur5cC?)a+#525QkbmYjJ0h)2 z3oSt+eHI$GNOSmHTY_6Jg?)Vgdmx0l&K$6re;TGO_n^@;@KkWl%q%>rZ#C6r>vQi2(T5T3! zf{T+rt?QuDwJMw*itW1<<=L>_s{5aNFOS;af0W6eij2qo*Qs>Mf6q&&zS<>OKmJT9 z2sq>|wU#b%(OMg!!#x?}YESlvO=5ScvINm_~iK z(J>54SW;a=f=P;$OZ@Ju6{T)#WORU}e8dq`6T)ca&4+pAx$;V#wvK_^ER)9Kn-LXL(8 z2B2Iu6oqmPY42Ja;Q{FU5|H$bnQb!+Ulx#cvPF8W3Ze7C#_;Ps^R5syR$Xlx)FSg? z;*mWa6zSND$Sfj`uEMOrwU>}v7q#Ve8MQa^^{O1BThm`>@m?^$LoI_Qr4w?OJsq#3 z1d(RerFbnoLY7f|Q*8V6Rh1F)Eqk6t)v>?E(_M+%q`ygOUriD*UcaH+|pBVa!=e&yA%~Hs{I=hef}W^?qf%|av%RY zrD#ZTDo%2#3GvP`1cw3JJ{-wVZ0l`DQ3;U(o>Apo#v z(jYSLg|ka;4?{(eZ3n;naQ{OHp|}2qj}_j-ind2`TKBF8(FlK{-SE5NJVItsqLI%y z1=bO5BlIzziW|2O^fyGh|+LKy{l% z85AIPA@|xqxi;ooFaVwUS4n4yQ`g|V9OJ4xi!E);W7N@HK#m>tDC`WHWye6JOR6Y1 zOfqA`k2Sx1pNVT-nV%8eqDMDQzZHr(0gCns=}Hu3kgkl7X5@3%ni6dnHAk?i@U4WG zFOH;5vFFuCMb?49@r|@8%xW}4$9Cvfje-_>;aOQ@6sX}~hSvH`r{?vNu))u_9;{`x zI=93UMy1T7pVwfTVr!F68#O7Lms|$rwIu%_lpu75+0Z5}*?#RT%~U&FX6UTBM&R++ ze2(4EZK_qH+=3DrnJ2<}RYB}8<_?{_?bh_eD4`;l^jsY}w~Yy+M@%&hW{XY#I>-Ku zo>CLsnmnU18$3S$HShr}i6!cvJ(%d;Sxdf3A6#Y-Bio?-(p5H%T>2UiD0&tgSuipt ziRii;#5;Ci`s9ygHjjXaG1DVoG`Mf&wxNd#+V+=Y>|#-hr}`saq4oyryA@UBW5E#?6bzS_fnE<=dN{>jr3bs{6{JZw*cMp zPIq4nH{ZN)J8nzbDKJS6TAWOrYeBm?*aL%e+mH+#<)E(dxdS+$x}+a7c+PCTqM;}| zzpw19A4jUWz4!o);x{T0`T#-_FJR%aRke!LyzdFS&@V14SV@Y?Zd(EfPP?o}Hl0)n zPlx>~_Y8})zeGq~ToC6L! z9=b`G*vd|{TqC^83D1{=sKOx7a#Rv6FfGW=3I3+M`&PF&Crv;*LuTyfEjUk z5vurD2y2y!-tSaFRa<~SwVxG;{zA9QzEC+h2VI6-J|@60{8pV>NuQH&1fcI$+IR4> zyZt$r#J9SM?qEUzj~yUXsPwIIEJ*xEFl=Q1`sSM-uI+9Ons~iHa!cXPRB^MTe~5HI z*Ech-R}@J6)g|F8BPe+-;} zK%py}{4ft=IXNoA9>kM4r*KgSDfHBY#C!)srHT5_pSn`~NnPxW3aDvp;E=l0)&I(3x<=lQpsEseWSJOEu{WNye`>!0i5 zJ#Pe>TjDYWR>{#vpIG1CH?`YXCmhJhvcy?6>veMbvz>O+`K{&_Fh$DWoP&f_oyt@` zQ5p|>sMlh?hF%&;b?zGIzZnR~JiTQ0;^8u7`iv^sAY_%hk1=!nkxLUseHdj^LY5ro zm$&R<%vs$}qaIQy1|aPNQB*Z@6ljv?fm7XWxI{OQq^b2w^()Gyp~Q>H;>I6~EY-N> zxUzzk=cRNI-aD%v#?czpgaua7w*b3b0UnicP@d28nctKNv?(6nY1e! za7te)W6~$=!lG8z83}L~S|~Q^iwi(s;{RYo!WCGOpW8kj9RS}ceW+aK>_<73mWCh{ z^N1ZNFuK2js1vU!Vi_crw&0m+OVvw*@@>+qFzi7YGNhExMK>c0xFWy(^Uu~TN<*G} zHN>b%aX0TKsL8AJLx-^#=FGCgn~`|ywkxb)=J*A8^R`W^h(8j=^#Jk;dI|D4(BOWL z=!gXu`An$(SFTEp*K(ZAB&-%7aujSI!EH*`4zsMuo8F~TqrUcvG>I+vB9k&uspmR4 zxd+e!aNdwMK^P{<-9^D3^%(GDVB46_Fl-m2e4)>1$HHgUo^5tG!<_ua$vEtjg-cXQMSfHVfVhGU)eI7}*Dl7s|YHmJZWKVx%IHh&!bcN7jkQQ9@ zAwZpPL1WTdc_q*3JCl9uwY$+lmSK*SA8p%$p92?6P)TX5(8HzZ=ybb7rJPv@Cqox1 z5+-xs@p2;vx9IiZ3Q99;@X--w1~=__t5BJn!+ky2%x_N9VW4q-^G^tv!L;U+3f}z) z7J!?saUqe||7VgEQzW0GA?~%0y{_RzaFxS+LqcLv2gECoV|w4rNoc`>&wM%jfhAjQ z5S~`0B1FE|DD+haanQYO6r1otHrdW3{AUqM(gWmM3J#z?=xq%owcA zSye;q@-UM*#CppMW?2%4+I*eCVS-7m1G>Z#-qRFGfWI{{D8&1N>(0{MjMzX=>HtH) zotc|vgTF?1A0tvOHt57-+0Z)|Mx5N5i}ae4KQp{Q$YO*}jwvUU61D!amt zU^c(je8h#fZ2oE}<2#5Vrr9V8io+sU5}7fa1p)$Z{f-6;a}v)97aE!Q2cI$DD$8fbW$spS(xpinv^PX#2$C9jy@P#;QZbcIl( zF_dB3!i8N@3HT`hI60H;?P3s^1A}f}Quy~L2Nn=91e~(a*CJ?JiBgJkGDHrgpyjj8 zK%`aqUb<5#5jvMPO6V@A$l(8*PdaH4`@*JX2qA%V+uB!8GOyd-g>2W3khQg)Bifu=OC9Owk!ZbPtZC=1mpVGB$k*Vb}i*P~e1-KN*HukL1-#6q}Ys`~7cFc=A zJ}=JIS2E24bP+@U&jWE!=lDz`b&&+#%mcEnXr=eCMgp83!41+$+}0SC2HwV4DZ!oI z9j>a}?Lo5%PxUu5ooG@3%R7w2j`^;g7B){D%VT$CjUQS93bsaCdni%oa@$0?V8~oe zPKH@yEED!SQb-7&)Kp>-j(dI=FrV*xbaw-7#>=(H|(dRszh zHuI_04xpp`{ZwsD$=s-BUCN#T>@_76UR%PTYBPdlI*m8jVl5Q0o@qu#NN-j>xs3s@ zUh8*IzKk8pkt>fP$5UVx=j=jYsPi2Yf#B5l1&y<2jHZskQdDqbJvL8wyVgdx^{y3M zvFVp8b68LITRI!o>{48TES7a@+}*>;2Z?i(UhEkA8kFbdc6-M)1uSeJsdYvs0c6`! z1LM|N_>P@^vur35R?gBl5hHbc3d<9|ExIs0&*WLJH0yjZ^NpEMwH78xn80ZQCeKJs zl}Ns^*MRD;M-x63z3;C{oZq-9c}!a@`@>`hUpdOo?17D&nz4z!=gf0~A%9H)L}=1x zj*_q1=Gp>`xq^EdWI@1NAMBaMhna6P%k5uCXQNm^``5q=@U5KB8526y}W4U<31z!tXoDS>ctpuU?T44EH z%fr5twO<|<1d#%t1Mw6La-LS+;MiA>1?z}a_TU5GOH=>k;sG;!h!v+Hmh=@Qtqzlh zM_}7q6>7HP-V=%u3fn?y(h3X}xjA|xeMG?N;X~B!Y@wZDbFA`BzktL`5KM@-tJgB< zlwZF_lpkde*Z2Q*Np0gA)9V+@937vxLW25$4bWVrzK^KqGT?2rSB0XU8kocw5c}CG zuL3$xnn2F`>f69U#%aZW39~Fk9#RHx5sE=7wZT$e(8`m1GJ1?#sDLgWaLV>Nh(!Rp zDSER?L9wCF3z|SpX5$4g<1uc3SbSujz@r_+nZ}~YUd@>T?0QEs>%M{#MK1+uZTJj6 z^0t)C7mmt3nOnA}!Nwspn?*D9%5AS}1k&ev3F4&nx4F$x?V`e&?cUodX5S;m(Qe0c zc)+!0?(LI0qhe?NXMl^X(ewM@o6O75)PJPxtQgzM`HSZZ9AS`eHP(8xBV8{CzV-)p zgE>7yKx_c)R{Fu_M{cGw`@ddKiLAAsuZX?Lqepp{jwZ8eP?uxAV4!d=uMavFz?Me& z<^|GkS6+wPP&r{Un5s(lV{G$`>qkq6N$6BrW-NaGl#;f$q1`(nCfIEqF7zj(&Yj9D z1fbY-cVzj|DnshD298nQmxnbj-W6FR2X|luQZ?F#w2%;wRPJqc%IlSMHKl1|p(_FL z-5)x5g4~qPmR_&a1?P<^>3)AJmH_zr%ABKzUS zB1V(0#>GAczyqdXj{;He#)s-I>+1q+B2aDi5eb)Q{Dk}RwKRC}pfY9Y%NoR#x=`HaR3 z`6uHZKTuW!Jod#!0JZ5+9-Yag&hdX$9Zv$|Dscm<4{^cG@yY;f(;~@UQrlSB^Lavn z@wppK<*?Xi`wQ~p)c~Y&<{AfTKGemU8lV*d3(0Wmg};tu z%)rsk{$)t*^3s_*CQgpn&r0HOX28Nh#n~?Z-?>~t9c++hN#vzJZPVSB0%uy(?5m9M z=0YWkGQSZsMamVA?+vovV2-<|0`oNSZeer%B<7@v(%0mA*x;W;E5M5fNqP z2#c7s7B_KvXO3V2%BaaV;7uAfo%9@Sej*t!)14Qjra3qxH(8$;oAf|mdzDGhFVnEbc(B?7=QT1qWqQ5+On8b~<)9<ZHD_(M<1F&I!%b?osAvNi8)>84DK1# z`ajeCBXvP3UX?5TYxnh-&A*bjLrg9o6}hS58S?t5`4N7a7AXCo zMqKfZK%fhIN#4&8Eh60Dlmhp-YnmfX0e5}{o{ z*DAt_Z3Ll4_>y+bp$H2BT?5RpAqu;Ex=#RPqChE_ysmIYkZT)vW7WGJok1mH0II8bUs7WlH1x8hS(9CTpt2qr ziSPJ!V`Lwc19seGYGp;cvOlmc;WZeWHBvi(fFcHo;8AN4#T#r0nY{iW7O>N*Cr3#Q*pKde2Auhzo2C8`jz(b8BhBv%K)fBH2{E%LXLVCA)i4Rl==ms zJ4JW&q!HM=hDW)_7l`9z!T-;R!n%D-yYQe<4{o=;3O>Tx+JUh48{z$@Sn&6h z1nmxaY8=P1LEzyNF4gm}z>$i`FpRSXl)?}sqH|&Fa~UManj4Hjv*d+)9n5h@6jI7` z*>dh%pb%9LqzAv+)MP+!h6U@ojZR7+aI$rtF^a6>Pnv6K7 zVCPRi^03`7?>2*|SJphkud@pCPsF(`2yQ7p6Jq9^N zOAvqI5ennBoLM{T8{GmE*D-SR$tCYM#CyUXPoSe(bmc>=6+sg0aT+PRHS~2ApvB!$ zhjm=0$d+m@B&Tt+q~PaXfm?Vh(2UB-nd~0TWj(wGHo#jhFxOQsUbg1S>1~cFn zQwPe4Tv!rfD8dbtwG{C1vK&j;@~0KfhY93K%KXOTL@ZW#y5Zwt*e0VsY#39FYZ@v@ zpE3Z2z9KK|%)T^w30C7q>NhZJu78$y-$9;N4B7PH8r48HLLTZ@0ZP((iWt)R){ zAr3^}Bm%iJnw|DSds~-Cu_bG0Rv)8z7wcBwi=F>Ls>bKR3xmkkGflTRSD=rL9xhQA!vrx7l=myA2O6ATBpoq8!)JLV0pzp5Uh6}x_HL)wtzIWFQQFLvp0^F$ z8NX)>9jkHH^uB!Lcij z01DdJS37S?Cgepx4RN_NPcJ{TwcUt31f{t0zU)F)HN>$_Ayx#gcxxBl4?aYY4rU)% z8SLM6u!MJnnuwE)I&aYfu_ZSd+{%Ad?sZdm3Yc9TzjMF(+7X{F<|ZJ@9JIWHnf?g! ze76Ln=!iz&^_MoO=4}{>Zc8^!>J1x9l+9fTQD2)2A37yk2u3kGg$!tWSKiW@&vyA`2qx`MM+>WOR- zfc1%~>l?vuS9g@Z%q2omZ{(;uEBgC40AZZ^PrFS*Vv)EwM3~$N$q1k{n=x$&Ga>Lc zfniV~OaVr+b;m^JB`G~TuSOEq>=X_k`N%dYk)*SSCLCwvKQ>M9tQ?`lgkNl}Na9w37|%@jTk^QLx!jN%%PCe{7Mhr2+Uh^UU+~-H^AfG4pTirxX@*?5 z>_Ojv5<(u8jJbVqU0dAUTTohqZ0{fSQew{hF@-@gciF~uM5DdjO2N;T8%KR6_5I(h z+iCvmsTo`dg$IM-1hw8#NopW4AsmMlB(9-7CMu}l<{mKlEo%HaE-<2?rgSe3lmg=Q zoNw)wqmcFsZyCq)n)W+CuG03JTI6XxzPSaK5dYLG2U!ZuJ{e;@Tc6%@MewMaf2aHI zSCf|uWebHX5^gF!)7QGNZh_QRqKZb$hBH9@KNQ$_HtP;8DQT#g0YrWEwoU+U|HL(- zfvlJ!ms4H}cSlq8d~qm@k%@vzPu9~X&Gp+hn#0Xcb5$$CN?FhkO$*Gv*IKvK+an-Y zU!?Ba6ecJm4l$K6Zp*~WV+03ds311SkQluLSp}#rac6oKKGs3$-2{7??OXu$DK5ur zY}1yxQFM-and-c*3=?{8V9{yRz-vP)MM0^g4{`v5Di1|UR-||BU~bi-GN!}l8MO~~ znNF!02pQ(0MDQ*AW@+f1#QWK|5g@)`qf=>N&|afGpNwK(`5DoNIwH?gs}&PXJ&i|z zN$@A>`RUHE4-`@PNNo9azA?oJdyv=2WZR|f8$V&Z4NN$`p^Aw?8r6MCO$-p_ovjRw z3i?JV-0t60C&q6l8_))tD8E;5a10w&djeJ6i~p=or8Y)Ed8drgmUl3+r0W{ju8s=2 z=Ghe70iYx$j(4#`7&UMz+>x1G>@#?_4cvkD`C0ca7~Mmi29GULfvWd^txoS( zw(3wVHZCTY`KUxiU9gok(sVc~f#K)QzP)a!#Fss4=D9J01-pUWrYy;j9N)R!Uo{P{ z=u;Fvf{sLuv}dnfJbr?P#>$*?DoxK=O&^w*)Y)7_+7HO4Fp}2!4QXSnS8BA(bQ)tF zob<|#I|oX{xuEPQF|<&C(wy|Ztz}l+N*0#ku*FYq56vtX>d<#62?A8Ewdw!*abht~ zZB8Eh(vY@Qr!Yf1%AcMfRa4Xs@3Fq>02%0 zhiA#<<~mjWp!Jsr;U~5R+D#@j2xMthe%VJb*ba)CefHhArjoR8#xveF^i3`jQpzI# z1t6!BKKR=YF{qtSpZF3`Mtk~ZMxb-W8nW)*g8*Yh^n7c&`XC+X!wI@O~ z>2h{813w08_fp^d=qkt;YR)V8$EeEW_k5-c#WKhx01{ehp>_IPSz+{M+>&V#;`y+h5 z{0UE@MlM?=M=tacgPM1~?f2(C++cd`?OdAr!Je>zg`sdRWtl;~vAhM7woD6P_14_r z1ljwvYSVJ>H4hK{{ZXwDAEgZ3h4V|R-Q_f(0sRxCPZc&U1;Kpg+>;56H|oZhv*K{0 zqw95ZQhWgEG#4~l<D^1O&G}i}k9d|2%UArC3faIp`G!Xx>1Mum!CWgR#v8~l6xKaf*njso5$^a@_9U>C zc5)G~``xhTuF!8BJlY_Xx>)#Uabm#qh3%?w`sdg-U10t8(nRjs?%*!hK+_@^+%;Rbs0!q;#|*-!A2 zg2OH(Aq&Ja{%_p3w^TgndMnO;Rr3^3c*n<-7dgZ1gCO9$stIkA_?F##VE19n_(_Br z8KYWkxO{p&f>4RgQb1Z2SpYwZeK4H_t&$9vLI4vJUs`(~6udRt#b-(-#(Tm-1=a#^ zvmB|^Y?fcVA_tCg*7{6cw%rc~Hk4vc0ylto5F+mXo)B8$gd_VxsMbA0b;jESEC4}E zrdn8r^!MlWSb;|pI`^XIcgXA{%9tSVv&0pSdR2G)8kNs;xk7lYp|0EmL!;6F>v4`* zrJ^s7;+b?;0t8&B?#c`iJqs9q-Ry~wvm5eK&s3k}k+#AVRC?$`wd7>5eMy1)YVYnt zeFZtMF(>?d%W!#2^&)RlDaXi+BRAxmQmBvIlFq%smb9>rk#6~nls?T@-rC@RIQJlsiOQ?+8T_)4tI9P<2n2F-t@Mt{7OPjsX6kPbuY2z7x23)6+0AJl81Js}>p~rR z4Jh-kjHj_ckF+8rArVndi+40pUPoyLGGC^&Jr-vL7NDBOaeD8@JiIHtk2ofx70!DI zqd5mxQb(8X>owCKYDQFXA#6VFnkMRltEF)i-5_4Xlbx7l-x_NLrJOI(nBI3lm2g;u zO?Ai@Pv)pZ@kUuU3g4DAqQat-A&7p!1KF03@ky$!?Ly0hg}o~-;ySv?OlA+VYT<+m zchf%#B{)v2*3}}9f>CcoRg=jSF%8oThjx64h~J`&_Oqo9EvOB+Dqw7*B~zsRdGm}w zsHstoBww*o8R9H0Pmj)~EqJ10svx;^kAMLV!DH7lphXq*64{@IU?y?EPY<+n)6;p5 zqW&WOMU?$_)zxezLufS`(HH5i2!TEg84dyI?{5!1D;a;(px!?lDWNy(a4m)Z%rq0>D78))D?#yS^XfV9CRI zZhsEc8PPdmOt+=&+eC09FP2QlPSw0S+jaJWO@01|#zrRXzyNr~vDoCuk^cBON0Jgk zDjU&C6-W{evdWpv*67(ud4|{{k@1caXRnF&m+A2&cUkW4%fYEAKWATsbkTrz3ha2N zUaC^8u4EuJ3wi{PS13WmU;VFW#hH*yV!T|wdasYr+l)p-FSOkrR${JqjJxydaVR&D z++I#oVZm=@FAcPkpyF4}E!CChhrkuK(&W_d6^WIz(xdnRwN9R@#$e1{Dqar$d)}Jnr=aDS5ckp30%T0 zx)OI8$&d<8Q?+*JC=y3ott)JT8xazClqU~C8^t4;h_;-WRqYbyHgqaKcX?nnB~!nc-q@WmS;yee$F2dKT6a}%&T`0v!B*+5#S5PEp2OEI?qD_khsmf7WBwg6he9f4rP(K2h`v;9w#{Fra#&^3G>jKe;=D=0y`Ea!$ z*>c^kk-u}6!cYT^YN746DH#`I1jQW)$5zo4^gQpb@nbZJv1?N$iIKspj4v`Vb{5dK z+UBOCwkgQsaLEOqXV}7hzhN+}k@Jx;e=fiPo1mo?^=#X0zbA-+f;OXlByR!zSBzBl z#0WqPaEFer2-aNKsnl#tkf19rPnJeO3!ZtZC+6O79htK_1bC$s0$nkkI1aZ4PH-fX zbu&gUElGKaY-$vpzMa)+%PhQW`Gc-?qCM91zc**Zk@zo85QRrJ05_vrB;*7 z0RI~a5Grn@U6#sAZt25Yl*ccM8suH zq5p(Uq{Olv?mbt{EUr)uhZ>3D2w0WE2wO@}=(A6kCo!t(&_S3@VeF=5x;vlg^C|}i^402c{LV57-Pvx&_S;jwN&d+7+?7retLrHDLFuExJ=hD}J zg7Gi5neewF6mMn*L?JPuY-IsObu`Pn#e)!qeCYd3#y0AR5PN2wC$7@5G0d$IAR22^ zMTzO8J{7SEbC1FsCP0sD%mHp`aFVGf%_|f5*LiXycwu3Cl0fUU120dIy%f;2eF8oc zDH8Ci2r+B^|6xBPj?r2J0t=YT-!E|ndStL!lI&Tj`Y=whQP;3*IGMJwuFJPFlAqlL|i%_5)eM<_k~OEjca0Nz|d=}wZY67C?{pmnMxOUp}HgXJptrERX>On$$p$Ue?V|Nc-As zCZ2T0XVB-{EUkEy_CugDc&INZTGW3bu@N(}92A#pY%Ao&{6)jogdjrq0E93dRTqrloF|q8g!e+4vQ?bJ^!B$b&@Doa z;Zg<$#xmC!X|G|P_Xcux$Y%$J%`{d!ex(f%Ws*Ydps_a1!`?;3uAxtr_`Ofx`QCr@ z%rX2H6N%yXoMMyZLmXICJkz@*PN76MC1#9F*>3qir&xI`7N;SoIoA#O$2tRBLYGTm zCM${-p1fXh=)$?Vu?);!TUqX1ugpgf@*v%-d6cU2&yH!l7PkZ8LJ?Eb6ao$9+8+b@ zGF+ms-G;Ryg0m^$6ro>rAeM_1Y|bXvdN-|(9Ffo9{5^Nq707j;Zhc^`L}OCZD1m{T z9};k0q@!LL@{<9mq=8O#&WtrT*J$(N5ZFf1=_{o%zl^oXAR|~=HujDsOd&sC1DtSl zQdZI0*HGXiZQNtZb|=N^ln2die*r0<#k1KiA10UBbm6-FGZC&Nu_#xQ6e9Eq112b( zbv&IP7g@S+N){-E2&=rI^apcFN`-7J?U4Y}W+7et<{QDs6((dW*Gks_GeFG07%RR@GGa;H2GN8^2BmjiB_LxLUevwV8}eDEbQ|1A6nuFiiwEc)?+Gv~xVEErYFrw@qG(9cOAfL^*a zIIY^2m@j_HUvUU)Ah_W8Y9Xf+t3)cOWctdW_q8(mYeXP)g#@#hx{@*(yqNVi`2^Mn zZTwn2$4Iy+L(e1xzc^!=V%V;4rOgJZJ++W3Cd}BR^SZn8&3n(e)dLt;eVti ziVSY|?8&M`^p7Y@=@)f_A{BpFbxXcZtb!rL6WZJPc$lbK;8XE8?pk9|ho>ya@$aZ3 z)WoR(A(wD6ae&hV8`SrAZQ2*#;<=apQjY{|{2_G%DbE;+c$CH`KOb#*KK8u{zAcwm zDc(i5f#0f(!VK6lmir${mkF*9X1^MU%Ke6(stNTh$O9y~4I-HlN`S1`ii5u)5zZy6 zsHgNu3YQ%d48cOS{7E>45zpbPzcO5v1rLu#QueJ$2_8kQ1!y0)$w40-Sp_Yo)xR(nuq1mYvk&KQ{1gTTlrGujT`>aV)m6iz zjNp<4Z!{t299-N6P#b~X#~HWqz^kH(l^N}R1K{uFL)-re0AFE4yC&De*tKz!McJ)Rnv~a3L|9K^2IY7N5^YW{j@QDRL=<3;@?R^Kw(&t8~e5ygf<)Lq5Vk6|Rn#72Fa)5;h=*Gfi9e_`L65_8P zJL(JO5DS!@K{$Zvx?`LWo@S4}yPsRq%l9LB2<}5zZ)yKfSaYQY4tF>RQP3P1o*r|% zAI7fI;&KIau33=#SeXmG^g({dtF{E1icBp{Z&=Gcw{&;ez*H)M>hT^5S7d&m_OQk{> z_sEz9vJASEDHNJAH{aR{H{n>n-19eqCPDr1bC7$PIN4A9UVdIYn)K~uvJSE~JN>{F zR5tNNa5vnu?>vM5|4tN+%4wVBgehM>IX3pBM^WwWCVRD1PK&?`1}7vqb4N2%di?KJ zGu!bnBxb8;ui0WJ*Vs_7IQ+jva5OW)X)_ZWuk0xvH!wx(<%+SiD$5{TE+u!??BnA{MI$W*nin|tM1l>v zYC{uK4z*qlZlcX(YVMj{e#=2|vfZ`GaapyU`7{0GhY`hDx!nfz_SHdlk~^i;SQivZ zPl}U)!4RLKxxew2ahFBw(L)9;Lv=&^dGkGAe5U0fJtEW_@bJO3? z$o0>PoFo1R@`a4X+DVe~>?cN)j#BN?7U+p`%tE;drpQNd+m!|4z6L2wmn!8K9G9%#ex!g|BW$#1YabAo+|8iazr>GO6)r z!I;i43FeGeT0J7M#n*gef}8?TEIWUiX;wI`sm!S?!lEw@WL#I@x45|NO(_u$!J)XN z$f&y+Ia-aH2vZkV*uXI_DAZGeZ)SA3qH7_EK8=>C1-p}I>gx#3{#H#}*DaE0xIcju zL$eX(*fUf0wx>+>yK{sUx&$+9c2cm>UL>VUOZ5#ZWNbXNcF_r$WURZ*7usoIZcDM(Sk3~%M`l&#ILyDtmqEm78+E6>D`$z z7E?n5#TQNtO`5uP*!CCeHn0@R)dVvS@vRBL?K-?Mn~quu^Ij9 z>%)c_$ABN#1x>GS=6}G4-#z84e;i-ys)@+LKnFZIuQ29`CZzQ==$lE)o~>pFkTv(q zxT7zDZwFdk*aYM4k!>@hfGN4^PGJWmow4rd)BhLGCKNiLC%^Z|MqyI%cRNO zK4jtR6@kIVAAuiYZSie+0A_#@_;tBlLu)7(!cO~ip&KmSR z;E+=i52P8%y)}l<&3bzL5)B1g+L=f=Xl+Y(7;~R(c76Sam2>qn;Y+dI=e7uW%29HE*GAG>{}|$=cmyRYI}Y@FBl`&yI``PN z{McRjMpq6So8@M8Sa)dHePO_@j5QC5r%^q|<_O7@LJHFW z;*H!u{r@}=3`H1?o5T>rgjP4mmjn_#z*|IMS5<=~ax3OmLYpy>3;+l11SUJ%6;PL1 zm?Yf(q6@qT{&Dd4;znYmN+)VSyg9KA>5cwYd%mjD*IC=1fmB_fsVdV;dlvzFh;4YF zfN+zfrdLEk)m3^2X6Z(U5j{nDC>449Hj1DK+!R5QSe|5Tz#$b@#XZ0&raKJY#lTb~4U;Ps|4E z7QDWitXo|33osRYHUyWZGN%8FGbjbfbg( z<^DrNPYLSiFXlhufj?7>^21OIwU+GSw9n7CD;}^xU9GaL z%BJShr;zE-t^7#@knLt%Fyv{0+*MAm;`24jakUYAMAuIODbrmC2x`2-y2mBGoz*`Czt4k>2eqouCvG6k0zj(P zZxT&WZ@SciT~_np1>gyO=Z|5#8r@<(D%8tTAhRa{=-R*44&oid=udRJfYQ?sL7gH} z-$kIaA}qm0zow^U^cx@fN>cf8Wy>TqFh|MTi&x7FR5%T-Z`B{YG0SgE<(1W!r@_&a zn{zU|c3(KUP6fSj8d$0n$CU%4Bl=h9ppT+XT{Hz+fi zA{WAQU9Y zx&!L`X(9Fq?1an(@2HYFy+XE~4ap;-p|9#V0w^pdN@|azx4ifbY1$64kxo`i3w&_;NpWXI~+OXAxN!GN#6|{Ii zl6SK3{))QrfWd3agUpvU$rvRWKdjHsr$%KS$SyTD9hR2eWM{FbruPWhwGn-B>7X^a ziCmIN3I&9e2;_w`>*L!4TlooQLd-}ofrP4 zM_7W7SE2AB<&RiQU1zf2UU{VDXGA8kfU%M0bDwr*cvY1FAS=QPzulLa}Ea zjyCSj>B)7qvI_aH0@IFbJvnvJ!p{cCwl!;qf$jYAsQB8fs6@)bol{7pS*^C@uaxK+d&8lKDdPvH&6_bGvl3mYn5Ws-+3ZG! z`JRg4U)oFuHNk)Mf$UtX!4Dn|h}}QsLt_}(j5CM-?36QhsESz(Rcm?)@S|-IO~qb= zD(6h1FcngMRGI@N8f4NdoIdF!gWQ;jZLwJeF)+AmF*ja-KsHPfs6cI${4Ix@A1AoO zI=dz<9TrJAXNy~i1ABI+8qc!>XW-^f5AMe~1@7-(RZ_^WN0c~4I`}rf7!q$F!Ut@} zK({R208A`;Wiq41gkZ>TQe>ayEk&nh>^~v*v=Q54s7ih077jPFRG;0R~Sym zmsK=+B*po|1EVzfEg04>d)NCn&WM^0SnV%NQd#Lb{oxIi*32=_>RCcAh>$eez`S4y$m%y)2xvfN&6>Ioz2D?EkG<->d z9lZ~4zv;y41@PQgs$1~g{<~j6*a|TwP^&!=3z_jLxU9!Xc%=3}ChGFb*3Qv@jLdjJ zd6k)bbgwu<8O4>oBS;J z`L_9$kzY8QX!-Xc+Is13dXe zQGf7^$)Y6|x0N+3P2 zw7X4|7jtFKq~nr!hO?llU?uc;Me)=5v@a#Y>I#V@DH%EPmPZcC`bQh;YbG;-R@gJdB!=%iO+)a($*Y25sgP&` z!&WOJi2y`#AA%V;3?NcqxfOacFfwo`lQIb+t;|6?=1awwmnYp4#^cWoy`LGsmnyimE9W1I|rm%n_MW7sB zOa$634eHV;|E^9<@5Cv|2%*CVf21CehZhmpUQMjJ7jy)E9&0-<^L+2y@wy0l!Yc}* zRiuUfvqa+B4k59+Ui%!Q6pQNL509gNx-(-(_^q;;LRb*iO+nnzr3OK`3kKEyI=ay8yWS<`j9uGz1nHiZUESZZ& zTxG`;Js67w3thp+!24KZAoR~SGLsen z=XhHWHq793l*qM%5;Ul~Z3AN4Bv{7=xno!+6w@e7)rc{Q?9+ErxrhMX%8F^~*dy_$ z^WYouxAD47Abq1tfSyWatS;RgK0I6d18y=oPy8ku_%rMpL|-e#+kF@JR99KHJId@@ zJJPKJv2FS!-$1av5K(Jg>UM5m5GWk@#;{Xw=2vaL5F&ZU1C+~ zLb#|vjTd{^yh24sc+q9~-5sWEl-PMGI3|7g=$oTTGFzEju*OlUb4=rdek*-c>nhD?yW$xo(`e*Q!fKP&)Tpkx;ZcDmc zE}@Sm)G7B)F7I>SmZqxROMWBRR7g*bey^0h6DC90IL{y9Kg7iCVWP^>#|uM00Hxc9 z1;@Rn|1dW7rIq6-)#g9zxEA89ka#w4Wwb#gdxxR^EC!!o^UFPCD0KJQQ;<84dt+4+ zmA9Wju~KE$ex9@YUo1H0U%z_R0M_(&VW_(e(A5;PJ_=a6-t!2s~T$+ zPG)jKEJCyAwQcIP{0W)x9ns*hwEX5P!1OOm=-%N5n=rSjUTU3$Qxa_MU0U+#& z;~R|^^zVPN1;3vyxr*Hs^yrqcA4Kf5HMA-#EdG^WXpfz?X6~_>Q+t-mJ9A-jbz^8e zXGJf=_rI4Af-8%lsaz707D|mIVbK_3y zqe%FfR{X+dQPkN3Sd~0Q&~7H2(m%Ko9RN)_D}&Hp7-%q8{31QJsQ>jUAbzr*j6#m! z5?qeT^R?a|8u%G7q4oeI%17Z*Xzs0KU_F0xYUdl~m7k9dN%zZhnD{I|qmwGDffq==}NVXd@71&4+xDPXW1qjd2vhTX$-JPe6+8+~U zyVQRoj_~Si3i+WPZ2^fluly^8W+JU`C`%9ec9iiO=OZYQEu2DrT$IIH%m$57$dFy+ zb@?$GofvP#+iu2ts_EPwxOBXIAw@C*^|(`?m!3(h?vc7_Lo8Y*<_{O^;01jN`f|zY`iJ zQ+@Pn{NYdfG7>Psizc4BTJBlm1#mPnbpFT2kA$S;60;8P$&FF|SjIquO;q@zT2oeOkpRS8VffQ9%mAz z{3D*|`eOpyyKn{dCH+enMapxEbuq8mL3S2-mrjM=SL-&j>fMQ z@@#$=KYmO3=iI3=)Uo**nin&Yn|+wFFOtYW1+hAGxVEQF*ve8zTu*hArHi1z5`bI< zolJi4v+>mjhICzaya{7H@Jw}g!ykHwrMFq|l9R6yrYX63qoyPP>uL?FnX-9{@yulb ziDobsZvxuzOsJS!RZ`!^#NCEd*L7jlIiSr3NANp34-fnwIW!T%J_^T0)5V?EVq2$n zua+JOAr^Pf1Y8dR^^=ONNxsb1EhLERQWab~ID`xVZtipkGI+}`BdRMvi^iSKOaC-M z;~L|SZ+2b@`G#QCi)S3*E&uF*qxt}XBy`HLBnwbehM%F>21AdNm!AhnNL?;}0A(ix z&(rkRCDF-eiY-FJFq`!*mwLk-F~UDlaa3h`!8uY|sjI?5ukJerNP%gFOPD11Zx%7kB$--dIuNJ!Q8cC#zNes@`S(I|2o*S}A@;CIorE&VXdz?k=p6 z_Z-D_9SG3ad}QL(jye?UPraEJlN?@tV?p|+|7=KNw>e2*B+Qmg1a91;)qCG3dyWmTe%miL^6$&>mfK`o+`M19QfxdmBl{(;NHZI zF{vRt5auWn&|Eu!o4lkua# zF%#g?A=9!=ydf!zG?UIbl)!v<#s$!AcjXpgds~Z&Mn(iINFgO0kND5oYr6Wb#sdw^ zk^Pn#-R@lGQ71KGNYMU-^jl0(KlBo6h1sTbctUeAY1&SJN6)++~$O5j z5cj$BK1{{^EPU2e=KpA*K+7-Jsyd*-EL!=_O{vcGkTy=v5$EQ?k<}6K&t{RBCBp0T{GkvW+R2H8BSsEKd4feHi5!o-!4RH25+YpSb+=*?p{vrh4Q2 zhVXf*O`p=#8nNQiy%66bP7|g<7Zhdt2)l4 z;;1dD@w!>zI8p^_YvMLP%Un;guPg|OMa$nwH5#m3@OLWtFB!~W()$?|@9<5)o`1^3BK^|H|^Cq-CvRqGuq=4XPP~xK}j~vh`?h zQGs!v{(}{_TK}L;gHzQUM8mTAX-L0ii8zsrDJF6veYs;TwXL8&l*V5J0HZAQ`JIg? zw~={hC^GU%e$_~_fzNX|Tl3z5gC>SR{3)0NSZ)o`Vuf5LqOd7Q)9~J~VdOv6&=jBR z6cd;|)aUA)B&R-(S_+mShtl=z+UF>M{ftR%c@TDSXu=EK-@Kx#7}EDHRpt;@uQZ1* zwr6*G6_nhlOsuzRz?>K_eQ#b?0muZ4^|RuA#t^Ib%7GM)4q_FiB!x+$g6La`tn#|4 zc2vDqdJ$NVY|ekEy2;wQyzRtD4H>M8=>5<0=;V)a6?*(Vs5v;mKH>TKPI^W2{Wss) z-QiAtDdcdd)&59-`U@@5e^|r}3BhsOPLkg+tc%(-#L|JSNn!NI4BsL8cg>yGV~7PO zK^&87Nt;cMP&_hs`K;HkdNUd*Q^6WOuCacnL>)f0Q$e(Y16&LK&!h@%t{K4G0jotl z&j)BqIbk!l2^OM)SUSK20RBO~LOWB}J_4C zWeZkMuE7ts7`wcS1JxCu(uSErx6RIEQR6%8&SfM%Yc)beheMME&s!qEM|DT=XR3d< z1p#>5=OnohFxM*$(n&1bqbY#vta-}-avPPpHPr6~{OvlQ+%=3Q3DT0S7QMV365mCRTN72W86j0vRGSgG|UIE zA_PRuFziMQRCMNlbZAMP(`LO zZwt@f3WXu@Nos!O@oxTg^qD+~5RI#o!57>yDoF~wNe-@4R7 zH51bbIaoJW;0O=|R}x{IAq;L1SG2^iWr`8FFj*f|A-I04AR`9)I)rt`*^#Bx^SeJ) zRK$u9=2JE0ThPN74W^Qu-prvkc~9(S?E=x#X5c(E*?Xs16VBJ-k;el_aKA_A5jSwI zt?yIvptg1oJSP#~Ka4cr9-AqlD*USn0_Pl^jyth04;d zqC*MD+UFthNk;VhM*o~;1$P_uIUYnh)lCjKdV)Qma;UovR1AL44yh@d zF5{3%AD2E13T)5x@!wTlb#)Jw_5!#9Vb&W3QLW##K1~0`K0JAk!Zob7&y8T2x#iOkDdNIU{JthrT7N0!)Z$d=*u*x|1D*3eyX0&RRv| zlj+@0e8$xnAfoQci@MX7kYl4(A2l-m7GRh` z!SThx5Fjaa*1Lw{8%ekq%a!3ZBhEA~-CJ)PPiLjpdR;yVk7}wUD17>To1B-SIIE~p z#dItL%G>2Y)^0C;Kxwg>cA%k0GD@2~I2}yeT;XdcnZUNv6YNDB(mwX5oJw9fJ%&YI^j>x8vwq(>G_ zfe4m1YD)f6C3U;Cqm`g_F}B_}j7}M?;+o}Cx-rLT{^1#KY%M;Wq3i8cZ#Kzz*gzy; zj?>pwsandq=}U`gnyHm$b?%Rj!+3=ErCcDsz8(7i9DpUKby@nsg-|b5tW_6iM%77S zxa69Tx!q??M>(2DBIoKj2;%)EheFk`D&59jA~_GwrYPSWS6ucN-yGnmGZGi;WOZeK zrwwP<)+u1@eC~pcfu(shy!V|&nHIU@r#!#KVJbDCU_s98~eHGy2AQcY7h{ijKIh zvGcj%k%GxG3;PsxEyc0rqA|wg+j!vErPOYCP>4A%z!#8uO>tp@rniRxz+Q|i@Z#^; zcK#o^@?DA=dg|slrcZBSm`-5rdY^O|)=8b7Tndi0{Gy%ed{nNi3;(mvceq^L4G{;sk5edabX)k+mr&aQ(6_YhRB*6Rg(wBpJ zZ0zW5@ofku2)O*PMpfyW`lQGqTi+>z`yF&r@wf#R2gHHYk0TNZ6ZDd&d9Yen7=suEoDQX75r zEJMz>NrvOKon90rL{%y+MZI@(CCBQT4%TMP#bk|*`EuPhu2|@-waTX=>+b5yWeFcD z6x7oQe|+mlR$T=4>tvNu4=dC+U(?yLQXd2Ny3EiqAeHxis3=45ltHR^sVS}w=OV4n zgj>1$+!;x{H20w9Ym|ep(3q+D;;NCdU~egRs>bdN2l7VarfwGd4v9(eG0 zmg(9>m@h|oOyh!M_DA$lh`sc^&|Eiy)u*Ck66e1$JBWAp~c)Aze0OSd^FEm8>*;qlPA^euhalB|a z(PSyL7ijW-R2W!0sM!o)=bJ3wJHHf;8?@mz=cae}u+d{9QyhdT{uN^ffrcc)M(aU> z1ba`dpHHynNE)(*beg;?k8EotK>DFo8EI=624Hg3(V z-rqQNYbjN!k6wVZl{RUWkpb~Mp3R_)uwy*5Q7ZOAM4iiy`wUDr{i{FXcQxT~`R;MnBS2Z_ecAruZ!*aFY zWxErwntEk%M52Hmpa^@O#gQy4I6EIVGa4?Q!wH^i;@OEAnW17b2|b<5ZO^>7lk#c> zoFfuwE9Hym(Hd7vA^2%&)_avrU(<4tC`1y@b;tPU!2ec3A^k?u_f8>g5*fYv7_jk3 zlpl2|x#6`z^qsyFRFP~hpd6hIT~0KVqrL~r(PE4DvVd-pY`7(d6n8Bg*Na)3JGBl= z(bvKUk&HnTFjjBM%7xr(Y+;04mdV~HQwNF|{n`m-)uX8!(-6~+TXJ9IAy|ZhtI#5% z84CRFQ65Fd6*B36(3mougKY_7qpUqmh9n}xemM6#+nut59@NY8;pfa(Z6t74Qnn!( z8YMXOwM-!i`Z+|D#UwJ^PN;&pP(EeFZDcfCxTn!<#F++Tx3`y9OjqHHc===g*wD*~ zd`me08swpLac(*9-22Sp`fm>CkRI{&y{Rh~t^{KAl%WT=lr9sqB9B10y62~J0q0`% zoTUGT0e0yxc{+Lh${}(F${>nuUTP^U4lJQ1a^jNZ<0hKqZ`KtSHQQu=Rhtz4geM(Y z>3oUB8V>NV&E3YyYVS|#kyhJr3Azt26RO0awM7L_%DHf&#y+3Q&L9m1%yRTrHv)_Z z!K+_eLB>?u&#bAA@wsL>7PjENR>Enmwj%~0(LG6+xcG_oi(Qz=RYH#7Wmmxl`zrW+ zoOJ5$xXBHFSlp@#r%QBLl^SSyWaO~By5*iw7oSWr^>OvceJrk+YXhSFjh}=Xhbc)> z(3hY6g}INGSy%1907g#!& z%N9nGA@lA0NI)2Xc3m6^e~PyjSKW;CHo5+^BEV5SDMiM(V`qT7_E07n{oVPyTp&@1 z!z`;5*WW~Xe9yP~p`Nfm{I=cze$Ac9u>|kA#=TCGwjJGaV9#!9KVpFicm`9X$Qh$CdIfVMm3l=x z2QIzZ2GPwz@dNJeNOx*$d8C@Dj{=1GX`Uh}c}-V58*D(rTQjY1Le0WL%?FF>8hb$q z_9D@5w6<5`Gt0w!6ER2B!%x@M5MQGgpVL-~?~Rk*1&n?FKzI}$3O5k1~ z>gC4Pc-g+oM=SbxxYyWCZG)t>Y8hBUL<+pJ*`RyrJOuHhZ{&%73@0Ak$IS`h3kL=T z)B`8C+=G8@ro7D{^*5=q047Lh;{t&D+@lb(RzpjDy*w9Vq>2#)C{kvAnRQ{=X3xjq zh!nYJwql!kinDhRE#EJ&d$N8FyPz}>^cZ%fcE6m}9Utg{q7^H9cAn>vzq%$alDpUJ1;qt1P9Kv8?^_i8p?c4>auh@IDq-acPn27a9!i(zYchijt(jO9uXb`B9 zs_}ks8&UdefmS)=+XvlKdGVnII929Z<4f^8P$CFv&5jzon=iP}=wAr2xFK+lPJEGl zZ!WhJ4j77M%NhiI`yvRwBT%=7o|pR6059H%s@aKx$#w4Z@7+rmQ3!)J2xaC?fF}+7 zIUw-vJ=|neJ!3Ba#g{@iPs{v^S)o#&7K(1qFpcIQCGd4QyfYWp0fw-l00E7c(I>9- z=Xx_rBRJ zmIGX}c&_cN`Xkr*j4NHcnQiGx(c(@1Vb4>H!7{gKSLSb`c;Omnfy9owV@~Cys&^aq zo_tijkUVB77LKv*N!?;a*!ccH7Xj|MV&m*r$^c zU@P^vn;W=xPkqIKHngT9x;w`}n&l)@ey{D~t=%KOM`;g%&u9x2dySdhO@gcq+~-aU z&=YYyfiqH@qUg91HaEG$p=~}#`kQ>cOJVi>fx-{Zhx9WJK-*4K7c!z+w$bz^&?S*%M`^#yZiaujq zT^G=zXgSm5o%{iVO{=<7b!DVNbJ}=?KO{z6UR9!0Vx0gH5$=f$A%5~LI&54oc91s( z%Qk1BC@xHzA*{sR(zan@ksni%dxspoztMKp#Ikr7*krp3iEBGy{0J;*9CkgV>q*rT zg;{jmxJ>Jd(*WDRtFF*{pvBdBjr%>_F>s^mAf|v8^=XhUe&ellxkO69XlQiGFn&l> z^}#zBw*q_vR-~|lt;n(w)bohnC6DEZk)(fh_h&%4a(s81z_@FsG!~wy( z5`qwLP)$hbK!907QpV6eV)TE!18?~=N@|Hs2SO+!``MQDw?>>-cS(PhnJXe%`t#OV zHCvs8s)PoF^d;0PT7qOy8%@>zq#SRW@lbHG6Py?D3DN^;IawGVFrSf$CBmmZ`y?tF zlbh8UbbU|IShC%5pHe9X7%={r-yrHG03EZ4m@sgs@#@ron36mQ5YH)roVEMmp!{rU zm4<7F6@gTD~kRvmdPn5SD$M^bgi6Z4_orB&saP z0lbzi8zSHvs{+19XN(2P>DB_Kt@*BJGU70W)r7m$YA=giGGAsJmOU&ybV|4xHXIV~muAWkl<~pZw5P|2?oV zmqW^(jd(5SCg7@P41btbzTz@A+;9{9%#7pJ4THAT^Halp-mc03ur;t2+5f4a+i(KHm_AQcG9l&oRbf*E85+fFtGJVzM8h8aWQ}QQe0BvL`7Qi z(QR&gp_YQ>y=v>R7+-IY^?S7Y%<24DMo9-Q&z)!Ed4oVJbSHjnx7EoEb38 z8Y$8dnn^jC!drtr{IYmuKsS~W_4u=pQL3YrqCo-BGz)7c^VZ6?7AMnCrYJ(2J8P;~ zmgj*CYJS!agg@mw!2~Q}4i;Yd)AB@VQ@$DtpLlK~5!qWq=3F6dQdnL;n?$~}E3bNL zkMGX=rmjzcS*l)@YaTobJ??$PVE@9fAKo-($N+TTK(PK=kdQ&ac*GM3ys9DSl!k0S zQi@dME%nY0YynLrAps2Lby7{c#bRkY3nIfnA8*YJ#OMHnkZH*wQ)uzszdq$I--n5^ zH@bWqNjI`SLVhuWg^Aoza>{WsR*2Jb#@@Iu? zn@OyUGH-Lj0HAIcnf}}Z@^i96MY6+P%t?{%bVwat-;TjhC?(k}`fo*{#<|^;Wdmxg z1yQGUYf8oH*_Xv;{}2f#X@**3whR__PpNj9hx10}>6At{8N@dmD+G=H1P4k%(zZsD zViRw4qFwgiiC-5{f|x)0U=x!IU|*8csK(_}OiB8rFMLcix|#C=o9D$(TBDI;T&!`A zTlIMMtx#VAr10|rwucF(HZ;#e!yV0POHB>{JR%cH=?T7c4hZom zmZ>FYexBjWk}T96gIhKm3m``KV$$ZXeZf&?o`T?}7&r8!9=BRwDKrwX37k54cmq$G z4@gBYn#UnKAEN2&{4XiC=8%H{6D;m3*zYEh3E~ojaP7<_UpE^x0Ni5C3Uz3FWVnCp zkek=SN%J7{=k}UiR7fDF-G`GIK%wq(gCaKPFr9+N-Pc7dk+oPNep7t}l9^VIbEld; z=EZI0>r5@omn_2N-@&*7uJ-vH>|-5)fcDYcVeV%5jUfYb{j@vMO)7Il{0Sg52P_kI zNw9kIaz9Me&SSdFPO9%Bc+1oyJfaIuaL^9|6@ z^`(nK?YOi=!2U~%e+H2Yo6wngCin37T9yFy3kwn_b;2Z;-A{8oi+M;s2E|En*Z;yt zq6d=Td*^6a1rP*KDLg4;sC)XOqSpf!jii_;+QO2zL({&80=GqXFL;AZ;|Ux|h?*xD z0DYGElH%fk?3q7kpgB*sJN>EzJNi|8{z@;=TM)!+qiThFi6`=Ix!aCH?_o|j8Mchf@Em#PTQ@FDSCA`8yHbB1sH3mb9 z)PC2zud2<)H90YKFG$lR%4c>_u5~6|ac)k`J1=p4%yga&KKI(vklQOaK_fuTImVWM zQhMh6YLBtlAkOWMROp>|LB=0Hj&C#Sf;^bY6ERCqJ_G`Y;6fNBGU-sPdi`Z(x$>I| z%CvI|2ZB58_-9p{8yjS7Hq7tjV?IVd_wK{u^~}nugqI5i7S2tl`om9ZAz;@iAishm zqmo-Ism-ANlQE+$OT8(g>bVu+XTE8SO~sMTn^&wPMQ{_JsT;k~iFd=c(t<7PkfZE8Ribz`PeGz6ca!W2QT6wm`hMZ=3U@L zDnQQx+8L~LhbAS|4tVQXHvJ6wA0%2dsLNC7uZ?*~K|8G)7}%#2Hi08Suj@pF7Q}ll z!+j_i|Kid6Itml4ZlVV-V%6GOi1g1ds{N_HFae8dTxc+i`nU9DN|x%=b@%=yz~fpA z!O(?Nf=HBmtgOofi6kH)JzAh>`1Q%MKSGlT?u=egA~PJd_Jl&vtl*UQydMv11%j}a zw^QeP41IjiUd@_0jPL-A((Ue$VV8#-XmX+EIb9`wz@T#Ct-)JT_M%aWnFgeRQ#ca8 zKJq*CFGPNgEY^!2LCEag6P0*fro4H1&Q521qQx1XxOPrASKQ*^G>Mp5BE9C< zc+bqJP`{v@GqzQ0i4^8O8`WD!=fx-okuKq%(mBQyjApnQQf4^iit!hf8TN|xt(unn z>{jnYBU93haMG=Vi#^_`N}Gbg&TC-@ZQu$w^hejx>*-#SSjP%{0v)qyXIga4Two!`s9U^L!glz2fTU*U6)my1>$epbSyw$de3Mj<0E zbIa`nR-!ToiE=k=DOSj17ZZ?Zr}P<{%lJV`m$$^ChKwh!4eoKG5EoxrJpn7nc_+$- z_2J}0_+`T}w2He_Y%CUgkx5(;JL(AGO8x5UEf8ce$xex_+Si$l9XhV90uITX#=EfU zfO8e}uirn`(EJ@^OyE4Zh}M<=v*9W`c_@;i=(Byu+S%E)RVl}dyYiRKv=iC#Sjkpe z-&ApJy^eI}MVGN}W|M9N4s=Gg^j|oV;<_z4@&!H>ec>+}2DiXLKsNgPrd3{M!q1V# zZBM9tYOzKcE%E#}eS8Zm>5m}`B8()jIa-PNRwO{gtJZUET;_U2A?i3+q-%J;JQU*8 zIIh8w42Q#iM@8r(3y9B;YQh`te2w1%k9quM5Qr<9r2ZCBp+fZ2%D2d(_CVaD;(vY9 zyMjvay(T$uCQxsHfOSsEL0#jFWb_s=U>yzhVk^dUODF+?PQ=Tj0RiJQ{cybFsx)!? zO^d6yt>}a3)W(!xfSr%nA)ZRa3pM9>hCPzLT^0$wzemr7Yq@TqfE73?y_6p1)v3{) zWynU>^vZ~}BFDTwy;W@c=o0oen8}g&cXvn=F)#qNks!3wdXj4>>A0T*jherg!7^=a zRR&)tZ~&BqB_7pGdo18sKh&jmF>WcO(*!tLuauDt?WRCkoY8H=+?a?AbT}s-EDMpS zquUIpuTB@Dg%nbnEm?(^+qgFi`ON74V(PmDEy>Nhe?^%=J($gLh=Vb!JBa6bu=W4Bbj4@_Ut;BH(NUW1;X*5Y08^5 zW||yzWf&Z3po;*lswl*N7eqstgS;!sy};vM+Lj`R|7TtDQWiw+)Y2Jh5Y*108#B&0 z81X3M12R+Q0 zl7Ex1s1Ah#(P)=uQ*L{z$HFP&Du)Ypf8bzXy8(iU)dvRqCMw4O51H`zda7Ql2 zVDtmN1f!rK4;{ihVr;~wq0|j1h1t!!U2<9=sy#>`o&`{0SUePkGj&CP|5cfXC8S_& zx+(#&@CaH@fHs7ng7efBm{31@UsN7Sbin)87G&u$)4Ngrf@lq<2!$n;jyzay(XDYn z7TkMts}H z*E|XOn}$kDCQi!SyzE_L$YM=tW0Atnw5-&E(b-P^Myz7{^`97PMuwjd*6SNW&l;=F zO{}5)GWoJW_SF?W7}YZdPb*Dz^-9N~v|@a3&Bf*xQTfwA%~W?}k!%|roZIwtD{Wrc z1phW){~Z!J<Q=IdOix#a`6SIdRcjbCt8ZcsXu6tYQ4A9`O2wH*l2ue8e*W zHBkQ)I5tzjiRxl&0OPK|iDF6}ee?@9_8Bv$%y5!Ry$DrD&~oVf4DblY%NQEHe=d?6 z>7pJ9%WJ`%J{YGAU^DqzalEbvrZ)YtY{0vxmh#jo_TspYjH9ZBHN>PG3nrm&-3H?z zIL!NGFFZE``}baQQ&I%c@Kc%CeO_tJ1B) zG;y;1&CqghLQ|rUxCYCn;v+M0-f>5_wMHVvqvB_j$(sahU={CWQ31(f7{)iS=C-74 z`{L6Eg4BKi^A59??ku$_LdwKx2;n{y)Oq2p>=#2$$@|sskgwtXD8`~CZz%lFuRGPFJI~y*Vu`x&d%1>DVW~Lye+jk6{0f{nKi%vA?3C0mh~RK`c6;4f_$Z6`qR({lKF;G;_$UeauUvui9y54#n30rw6?CsIte z5&9E`l>@cZb#E--^{IX6?KhlymRb$3cp##o2+9He?XxH6}=%8#MkZ_X2zlRz)O;l*G# zqdvfcOgxSq*NMD15L9Qj0%o-gGw|~W`)s~OR3G@Y12jnmPkzEU039uibB6+^qm#Hu zcjV;wb+KrUzmiw84Gz8`i}z?9>v)qIHJwSN0bb%kCmO-TAp}W&$Wky}!h;;`&Es3{ z2UAibnd&c*a032ZJWYJSZ^>9hqM!ATMaZ>d?b*uVpP5NN%&`|qw0jX{-r*@)K}}V%2GV4Eo9AQ)1Q48FHwVX zPq6EsQ4UoBR48xLTTBWN*B^BscM|u>2JMUPhMcRcGvILR+x8+tm}1S@Z$V`><2u*wZ!|$UIOO_)u}f z|Nan)a0L|f<)JMixYX?l@xXSs)Lou5bg;nYx^LE`YTov|tw}^3;!%PMFIObiaFg&m zi@A#6UeRuTtt{wDVb@FQsq`y2oJ2@I*gGU#u|8yFbl?^5_^a4mnbNau{@8R;Y;@Z> zka&rMB(F*qeqyxYGY-w4o2=UK%`NHcR$;XzvZ^zvu%oNWl*q+*M9+=sCy}De$Ms%6 zsfJCkM8T=%CvLFJhbnvn4BncLRkqk2*7$uLXqdfgph3SANQ9ARX5UHE< zK--v+;xr{s>Xj&sTa_*0($#!CsYyqY51o;%=?|yw=-g>SY~0)AL%>CJX9@C(84@dY z<`PuJ`MQYgU~}5BnFOBGq&qS?OPFMve+NFr2TgzNZStfuN9<~q7DxEq(XnQDc{?p1 zV|igOEkSP?l2p~qJcg?Md;m(xbQMTxd7-~DOCNCMlT$O;_it^b&)w72O>-*F?}#f>|!t6mQ|jClqGP z6kK)kZB>Ot68q37f~=!A-!F$w9%Wjk#X^Dx^6zOjAR7pAFSlW#tfi9;<#Pj7zcs~Q zV=tC2NHcs|DALZZ=t3-b{_*cfJK75|y|&Y~#Xr;i1Ytfq5(%BM_{^NSw! zTL+me!B>)SA4=FPM@!>l_`vqKwjhJ{QOncfv#bt(D5yxg)*2O z+W}METW_0$_3ruyN958R6VB-)yf^J$-1Ov#xxaQJk|Mw~$L=TQ_wrAi)9kz`a>*m4 zDjcl^=Ne`5s|}%)zx8qQ&xa!Wx9C-KZlFyEv^FO_SjX9YjE;u(-HUvHe?6+k0=f}L zZ`kHK1kToX`E8<|;>07sYc5KlMNX*A@B3IV&-T0C(qryelDjo!VQ|86nNfmeqCKA; z(n$w_Z!{V4Axjp@th_-UwfcdA-`PfPEK_3V22MKTV~ileskkcNoZk4kht223iyYd@ zaBwVDY!PW9mxbQTzvXjtXECH$wFREkEt{=H zyNlf7hmBxv=OdoGl9QAR;YuBPu9}!mZAr&WM&;9O(p17|Y$Ec57! zBHk^dN;;rn8>46_7^CZwgZhw#B~MN~l%3hZJcgZKP1-+r0dcS;#T|i)lu=yt?`0{V z;0dI?g!%C2XiZ3!wm&8DdxlKA6Nlr<0Okxr;1gZ5d(?4Lv#(6_SEzy! zyoaE4{HD(=?BGWUF1_UZQo*%+OtDvsrqmP=Mj_a>vbPykxOJsU_J*O%7<*Ts5?!=VhUsIQ`o z!uEG*4p5BxIVa?HC-p>ETv|sb%nK%YL)gy0-bz9a!M9t=X+PP%dLp1KcSQ(oJKOp$ zs}y0fRmBdc*%{iwN^lcjv}W4aoxfw5i-!JprGinV!>Z^4NG_v_u5-*a>w+z*m@DX~ zq7t0%>vsl9h3uBR@Ql(F@`;;|P+C>qY6|_Qs}BP-N2s5Yr_sv)cQiSUG?=ziHK)AV zHQ%c}If)=30hU(BYJDTOx%0MvsXMN?yfp{=Ly`YJ!_{U`9OJnH%K&1eKM*BG){QA+ zw55t2RTeP~ib^``e^09hti#Maf;3j(R9fG+D-rE5!>)o^H}`K?nO;QidMu9ShY9?7!<&@zo0`( zTc)jx!>Q-zY5f~Z!h{c1yblXst1wIV4&7Y5YcBzD+~1abJXN?GMdYpCXOJa9UI{wY!LNu1)xr zv%LZ^G|@u#$MN(F_n)!{JF(Ua=hL}9J{ZwNqa1D5ngUGxJ{WiG#_aE&o}q-e9#ei& zITN4+O_9zjZCcA?)&qw(`jh!hSrSdOnZzOZ{5Kf?{pbqPd1Ib3Z5mi&p_s$ zmuN?t1VwAAdnp7}fmOl%U_eJ|#RS)6Z zt;l?E&xIpC!i!f%QCnt@SZ{@2yq zcmn1hKO2%2z|XS_4X@mRMTTx>)rt!h^uxqoe(c`zG9bdVF_O%JzXB_3ab$zmqlcdeqj=C;LpXMI5)U0EL#;&)9MIvdU*c z1GWoY5eMwvYKHb0A)n=2d9~|N5ls-?Cn-&H(ZMXjTHNkONnHz4$so_PEPD_BeVeBM zjccPO3v(v*>yJU$T{^vDwdma;wv@JR&?q;{{>P1g((3=nz_iGQle6)%RkG5u-P|$< z;3QNP(>wN2m3D9ZYK^uC%2cVPF)HH=4p`lyl{>$zZ-u0>(=i13RMY$jx0Xb7Oo|=f zC8+>ro@RekV)!}g&PAICh%|4J_?#gw6BcX((s#eSi_)UF-A#o`G{VHhlLtL%p7l-h zUc5@(z?IJiBNNBwB1ertoy^yss4X6HGTKy;N<$u%b=>F@kCmQ~^Xr};eF!eKWD}c4 zRQWowwg<2MKL^HWf{n{DnkE&YlJ+Ie+aC8)rUf}I3f2nb>pGlQ5Jb*)O~2VZQK>6Q zc7~YuUN<8uKDm*WCZ^Nc`+kEAZVBmKb%UWk=;P-78`YBAwr&W;QlvHBMCB6C^{~+I zdoNf(^cK?xolXTj9H~wCnyVYZImuzDju$C66of~aGYm;G$wSwlFkysTx}84r{LHX6 z^pON#TBW+}+xeRXs16&wJ8i(b6!aigojyy!So{3zrQrAR|gd!Z^WD;QD&HRpv z!Jy|fl;8jPv;K7;K4EKLtfAz-HUIF8BKkDMOEpc(G?jpX87PiH*+S{!u3M+{6FmZL zG80?>Dl>;jX_^t(#c_d~hAt~8|NJzNYCX#%82-oc<=`Lp;DuF+Z-m9iS{hPno zXG!u&Jr>Wd2_{Y@ELaZ+YI+!w0$r5a&{vWA2BGykLL-P&`*ta9pxK03|IV# z6%DmIs|WaP>u`FfKl6<0-}z`^O%~kq70o4EinEYD+wrG=5}4wmmB^a;8 z0O@&)WL-*@0i)*b_nkgSJ&Qm6_=VCl>tDA{#z3kPJ6y)>x}~O|PoWvCWeeudD{0m> z-c681M&j=x%887qO(#az5~t$MQ6K^c#a=#Ntf)>cT*@Q81JDUy;6MeBO!2OnnhNct zEZToT`K{|L5zLSlL^bTLw(4Xx032v~9QErFED1?3>1L-+^zK>uxb-xlQ(Pa4-6quU zyHiztu7z{BoES*cJk2ZJXw$02YNybzd3IBwHmU-94BWN=_k1KGYXsp262)r%6&n$o z!Pbwbp$iQ=oA=`XY*W2z&6xI%WrClJ-Nn=wGLDm{TP) z&oe$PfeI}*#3t2vKsOpT@X{IWb~;a*CdQhI6r7q*q(_?H-p7*+z;9b*NK!by<|eZS zH@jPI2B*X{VN&FHqGQ+Q=51b9*244QFQR}?$`BBM%gXP_2&xyEg^0z+yJKA3azu_e z&-!T@>xB_n-9S+_b6mnOs?Eopt>&#wN1ZO1UOP#{)9fA$LiF7{FPe>Ye7MOs@ z6D5Y)_iWZlgzD{#(E7wTFGo_PF8qnyLcA8QByjl>C>^Lmwl+&4!cav-u2U*b_GR5p z6-g9RQ$aA-nJ>Wk-oCNjwqOsEvof`NZYeLxQCRu<7!qcOaiOMT$7Gv7l&}B1(%8W| z$f;8~vwnQ@70bre-M~AvJv8KOoonKqC#D)H%qldCuGwoPpF6LTHf}36PAz{JKR#jT zF@VGwZLkTS6i{_hm8UdoV;96^r4hG&QhY$q?P=s6cu<&E+=>gK_CRl1aRalPm5UfS z(^_`ulOe8^;4;jzqE58ou|qa`l~(MdiD~yNs>VCivb$8ut`RtL$b}J<4>gj<;Pn>})U|fe7)#unz)ok{{qSO%`98YG_)mj|WSInbk&wK_j zGf%UzEv|XS$xlvL(nq<&xVzWE&L+49F^Ln{4^S{|$5@fnToMk;X~f<){#<+^EM(^> zC}#PF|BNxYR)fW3&1ag%^_!KAWQ@W4mVDQw!gmrfl<6cZsRnwuOjZ%~$@W;Xa}y6v zY7BC)ZDbJP1!--zDo3HTJL;(iq)|j4;4!a282c@&=Wm+|=x79=R~hN`U*lNW&Y4f|0ktcS z4+xsRlzeqzir22ZwE<^8NRIQ965)g;cMysh@^Yc0Da}Q(Pj?Dy;qFc?d^EtHO$3~l zS(zAScAwlf_}pybC^LKT@|bq3TrCl!6qjM-J_v~z_vXKg9wVZqfoi)$-tN3MRvQEF z_o+wmQ~t`27{pqQkPb2R%Q8HnlR|FG$c3ti7tb7{AJ{R*il87kK4}oW#ajXldABfJ zC5)J3hrwK;qr*IV566rZIdM&1v;Du*R`8ti(ScJF^Xd$f8R9M|DgxeilU6ru&&^Ae zI6>v6Vudbb3;kX5*mC7FV{$w9S;AGJ44}D{`JuW)&Z#x9^-2rPmdUyI*1q(7c|HdJ zHUE01TSY6c#%%gYCAZq>le@>(`8h4VfbWsI)lbalPB_T5cgL2}WRefKPN4ufb-oGV z!yw^1SJ@(R8*`~qmO(;0>exR{ZMj*7%avDe?jC}qC8r}!oQ9Gi8!88|`X>;fRXCKV zvj67WLXepTB&(c_FsjQ-Bo4%cpf?vS;iKmURFZHc(vp zPm?7OzZkV7=t488>31_7)CFtLs4qXD)rBexu6ugYtWcJg_pH#4SJa6YoP>(im{Jy1 zR~p+`fCwJ0TQfO1g@wb~epV1Btce^yx8MN3H9c?6VLvc(ZqUT(Sd{cecm;`HcD*k@)$l-uawaG(0 z77{Q4EaF*>)1a*ThCx-)p`oUW6l%$P2F5}tg3+mZxrYdwsl8%Hl66(-WVz_LxANUE zi_554>l8yH1JgU&4bvdbkNV0?1QG3}hksu0dl3h?#}#}dNQsu}c<0wH&TjcZ4H3lgl*utJtA~iT?CN*Cc+Us%UE{d{$JG*n$EWf4 z{%nUqLL`v$E%Wyr`VIMKL8z#-crq@ElxLwY^Bcu(C7mw7d|M+Tz^H4^vuK8WQ}?~ zRcA#Hd8|D}j*9FG@&a(ytX=zI?Vi2_sXay$3#Zz8waPefDk;@YiVI?+^cvBCYWOBC z*Bt8Pvby)>u}AN1Pv-W2_UqrcqP-RF>;1E-9BY$hg;i5iRd$a($6aiF_!WwhM>mP} z)fJzp&5J9VX(XjqN?(anr$IgsE!Dk!v7I6 z#_fy{8?vCp{PXfu;dS2b!EdDQQ}#q0y}7`0M`VKd;{xV3Ly%V^Oj`IPgc$Rq(!mB& z`L*X#TWXm|2!Jl8O@7_kE|iGx!4(zEY+TX%CW~p2Q*ersq7|QDDaUJUsZXC7HjVmN z)=&$U!7BWsOEAY{DK4UFEjNb4RwCcz?~v2#U}3B z&DXD9|2zcbF$x;1u9Ea-BTa#%(A|0frk-&BGh)lqm2E8~@vBX2;s42F_+K^JkQX%# zm?Mw`4})mIi_6aYk5h32gv6+Nf&OMYT8N4&TJ=t|ixasnDiv#V;H{tZbRDr8tZs&h z=HN5R5}9<oc zbg~~28E&VJ5ovB^$0VqGh&$!|?*)g|K?|#LnuHVEl^5q=v38kdGmd8EDw$dN8`BrR1UuQkB(@Q^N0Kek_mhvQ-I2ux&S(YJCCT%FLD4ONLm}nC&k7Fk-l;E-cdgI>Qv8%o zXa8RkB?yD41vCB8zpHKPc|FcSN99k5p{36XR5~@K&}0%HCzy=v!(qhEW<%7D{<Qb6GVzJtwJaYW3+itqe$!&Y z=Pv_T#UEMV%InV0E^tq}JsDZryQC`}H%m@xc($wznK=nc;Vh%0v<8jH9rQ2V1{@8z z5pQrm*<1 zRduWC`iA8u^PK2s*D?@QJSx^%k zUl&lycd&g=YC1A1>|;`c!jdJ@T5sLt+0Qr zsyM`7CL6Xt1ytdIjR-_XL*_bf+ii#)8O~daJvc3rK+3(q1a4pcEv)?ulfHYccLZ7BFl4wC=T~7|tgE zw5IbUyOGoguq-%{VdJyIS5tc<0WF*44MIAgl9a0ngr!224H#5rx;#t$?22R5?>W)x zPN!HH<4DB&Y8{OHC1@FTevEtxBi99|0})}m%cVWnv9D&3mKH?XIG0!X+F!6_6brfP z>{)$M;Mi9)WOPTUMD58_j{CKg;{p($$9Uin#u;_Ljo{Y4<3pUIJBEF|Yut4kmPVFX zJBMtfawOhy`sCf@!Pr_-Eq85ZduB%LZAPzU0y40n*2nxM)=eRz9lDGHbq&7TZ^?*I zTJZ>vN~6f`U_65VVPjvB0=Jad;KN;a%%j#rT`f(u-+$}I?^&^t)!;?@V(Wbh%Gg79 z0UMi9dgkJ}+eLMN*yJZeYlm$%Km-KWJ0eubTNL=QcrX(?RE>PA zsk|efk%NwLe}!*0$SBI^iBBWD8s{ai?fiTh3M70YXwo+s< zJK=TtPGBY~fY9?xTYt80T^cmh8fFVIh0ot&oRo?5oweBM@(k@#HOItZGU@HiU6}_K z-0|6m!!QwPROc?pWU-JRFokp9C+;UE5BjlwBO(bO96nRMSS&0Hk+1F0d_OKjqXDme zwxqBN7LLy73u%4AIBJ^AT((nBtguZ7?d=^&7NwrNP54WOL+CXYUt-@tvjuy0x$?#q zsI9>J6t|0r6&->kc0c@qDL%kuAkhm?|_?m z+B~I66)REFrQ*OwD=eNUKR~Me&hrI|l$?oMZJPBHoV(|9AX3{v=zK{m zG&bk>OWJ9U8v*;;c#XwXiXgB&ub*EZyGJw)J>{+eowmP7M@ zSJ!j=0G8e1YQ>k`8&0Yks%U?HA_etjW1^|R>G7n6hU#+i_jh(RgeT$%QqO!8UAE=@ zS(S|NV(rTjNEKG3T2TV4(sVt=Kf#6o2h_0EZR7d^ba-o+XO^j`=9lr>*p=&XZRE+#YQ4Z7^VZ#oB;St9Vn}l+d~{|n9J(9 zWYW08b^xsV1}yqWYMbZ2k?yO#KX2#hUgPECzu2o@q0?%Y_OxHzoQb*=F!maB#VYEi z37o+{BB({(fIF6Kr1vRNK;a7})+4=EVBFI@qW}l{z9~1VKw?B(LP@Ao-^fW|$J+hiA<=ba(1>mt~7Ya*G;L(V61ra#qj42C=^)x!-fI9}MQNpqcj%ws%bsc_g z?$m9AVkT=rd+OX*yg`<8(t3n=zqB{GCMY{I9ljDB8@l8Y*~Hv~qyaqFE*ZH+Cb@`` z020&*y^pb2=%$}h&zFa1JPS=%Alc>&LPoq-nL;cofOEXj@z|0@mffZn!-{#D>P7n) z19Syp&`X!C&c&#k8{@N8Q-WZ%r;Or8<_aQAJP!fD-nR4%TDXifwH0KCD3pnth2QG* z;0oi@Sb;r5l%x9r6$a|P1U~Jl9nPm6`Exw*`DpyxYb`a@${GUl=0OKgQohraxVSv| zi`SCAxL+oT!5rzKI?}=4@D4Gc`^Pro>-v>(sKwfATh^r{17HehKtZ+}Or6EBW+goMU+b|ZA1knX$H>zF0OVf8M#e3VJB>kx zkELmwqNmEPt(^8raxyj^*lDE!LmBu6jj%Cx#+ZF2+TI(MXU_bClM@!F2ANS0%+=v#ce0Em7z;s~aHpQ=J*;ssV; zhH##MtC#sruR%ufhnhoYuxy;#q?aUI3%-#6#Uqx1U1*kyxFIuGWxSrK^SdxdvjBk8 zCX}GKhzn+z%~i_8CYua^Q8u2td&H1#TuNH2kJGkQdLvmop2qQwGumwP8lQ#2M`c?j zm~3hEL)bqQ9=G`&V&Ou#*BpCfZMqf4?~`J`Clc#}?WC|=k$q!;poSmh|BMhNsY`s0 z#BM`@_ADRD&+5(L+o+!t{O~bQ0`A}HB1)CzN=2aCL|d2@Fqqe1SuOHq&RoTkrxw9K02dLPGzlR@~|E*5bq@`@%>&p3d%98;v z32IDU#t&u9XgjmJgy|8mJP%ZVOW8#%vh~^z@`U+)4|)q^yW#v1dnuUeqT;k0)n&qk zN%t5oy;l^OdspMg$p*E@r3C%#5sIFGt3}Df!EeW*HaMr$9#%OhKYHv}>+2%O5#6z< zF7TX&)*XsPHo9f)I?@84Be*UZXoE2EM`KPf+cxiJmAVtYOyBoXND{tfAOiVH>>C zSbi5Z2~t79(bf(|+r+>={7NfYJ+M~?=A?S+u5pn01iNBAk_KXRZ$-A61*xuyUUFo# z3e#(d8Pa=saoY9+&wCY0ltPawmwdi~2fdERx49W0b494p;_QoH!VAoMC6+#FSu3y`qF>JEt$qnM! z%0r_YxMSP$-XqKk&>KRsmt?MxG0*&k{a337QB3kBTAQ?jUOOnz?69o~SftE^{4v!A z?y<4bJp?PKraDm_;k9jfo{K3F>X-D1o2cS`$|Us$45gek`%DfD*J@0Nu)2|D6(4R9 zS4}AZ4e(&SGU!Pf!74M+OT5?d8r8lktxer{Cv*Vs;GAQ#nG`}4)sh9vELIDgpR|Q~_WNZ0A)pbSQ*k+@$raQ;Zwr7E6qV>P>Qn}+ zIQ`latR!L|P~M^-L_89kPl6Q~)YEs=7akyjcE(D%Fbcm>M!fbFbbsdeFb)hJfgWH1 zktA0P(Px{}JUzzC_Z}q14+ZLm-{-#x%6>1t8Kx$Epgb|tIMMN#{4XsAPTR;K^7iWkp{ z_|dQjj2iqj3&3jg%a5-=emN~c{D=Pa2k=e9VIxeUSZ-~sQy@dO_XqjFMYc=If_1W9 zc$9};dO6Ipn3?adA(297v%ym@Ee---3qQg;4!XrWy6;ZC)_!x(vClW~z!zjX>;gN= z?SzxRmYkfMls~@F$QsQw;5-h~q5VOrddy}(khVXP68SL(fA6bG^Sh(e#0bmZVXy57YcSSPKg2Wzj z(r~!Dw;dRGkQ3}ir_IBBOhbkVjWCl#%$}L)-d%KnG_&QhO*tRf7ISm111E2pq=h>= zuXrV!YvD+i>B=WYLb}^ZK?054L-h+3nC=1zTHbNU3%S9oK4I1ZHpE0cM#+IINrY<{ z2de7w#bB9iqec!|(@u$@txA1RN8Lm8lbX6hv#*SLL$#C;1OSoP4}?Ub`j3oJF&@ZB zOLEe85Q}{0hz{Pf87=&wh4$m+6&}(Ts9o2sQYV(7V_5TZteR+k{o6+bXQGN*ubq6o zVu$7J5x9tz*MY52TU#U`fXjQ8zHdzX63^FTvGL-KD~=^Vhl>X7gL-GM2k5Y@#;a1% z2V84utU|%RFWrnba#w3ci#e3xaty{?UX}}mCf5`^2%d?p@Tucy(4hlRInYs9H*!$9 z#FFdWX;!4gq2hH~A)ly_(QW&f*jGyji~cKx4to%v$c&q%8=znc>pVu3CKLv5C*>K) zKwKZ6e8Ys0rA{(H5I&L*1;8qtM>2eKwrKf8CzHK&A@JK>BXy6t-C$B$m=JyVwoMZZ1Fmp@b9K^;4qAC4#y^ z2KM*)D|c1qHl3=V;Y;gMD9O%>D*xO_X+=2nsYh9ozqd27RXiAePk-7h3iuN2(}j1@ zzq>4&w6?j)K$G-w5nKTMULu(%!j<>1EF3_-EIt07D*5f-?ANE|%UMNr7YmI_Ct{Bj z&c8xkJy&C(su)=`mI^hI;0Di4>YjTXofq2 zJG4*$ML@d0n^+LaD%QqPSg=DEtV&56pfG9Q;rohnjMpFA5**B>jI5PdA!r=u zv%x`u3B4L3pPwp z@dm>{m4rz#$>^Sg{-E>Df5bS^wSbx3o#Rom5M|Vu1@j)QZlhAv^!?`4CG%F**~+D(M(X6{XpaiP?d&Im+uNfYrr+>4-bL7jf}gyp6uJfK(z1DwxneLKSJBv%A`mTW*g3#m z>7k29)x-ASb(1p=R!ihI=WJZ1)k}5j&iMy=i*uAfaKx&}aXeHhD<mtd{2f0xkc%fLTw5YhmOF0wu@E|n7_12YSTnwZ z^hn4)zMy)YyH}5*iYVC9KGqs83Tjz%9qs55e-<|iP|{p*p#v9WyKbrts0ugz1%w zA|2>)##_=Nes|PO#Ed=xm#qyOmIT!5I_vSzv7R221@n@&2ezaueI)QX5SKa1G=e5y zFX}ob77}#cF?Po_lj}*}Bw10oEA1-9kXcWSt`Hfx_WQ}kK5W5n*&V)QG2a(Wyt|Ij zs$=002JUIz)<5YoRel+ZR;I!r$?6EA4OpIf!3(c!s&}b;=1psa*_pFw)|l7c$zoO< zkQ_iAanQ(qsNR|muO%<$)Mg)2Pw!?LEBk;@YYuTq1_Zkpu)tR4uO(4BOqgeG!G|M6 z*8h5P>H=@;^MFosEAEZuyZ!%dPo*^wwN`bqfVX8il>b^~gjMzNrz4kUsT4`fMTbL8t`1xU zus10WSS-QiBl2UX8Qx?xjkRaV{-p@jKhmYw8BfuS$|;p1wU2kk{)3V{VkE!$03g=< z*rVK|&NhK=-lT<*+s$YgxiEhZ1lH!3s&P?9F8G(ZgGrJHp?^1~QMbY(3+IH&CoRJ~ z{tRXQHp+-lZD7!CP6%xYf0h8S}1=7&`5a_rb6>B#2>Cc#il@dbI0 zc79PLdjQ0V@(>NopVsWOyksMWwcttC5{Pm!YZ6RGit?fY3Z;yiWb%Mgm~%5swmLl( zCA)kNZ?XHuu9p4hY88ThK#)#9P$?jG3G3-p?{2KIY_21C$-wEG$?>sYD!L3$u=6!@ zM~*O2I%R0E37mX9{+3nq*aR$rn&kc+B?Xeq!RoaKfZ2gD0+WDcyJRURF``mw|EcUB z$Q1(p73;daId1NJCCo{{rWjW~(ocrAEuc9<%?QM=>RGhIV!HM-HfL@SPka{Kyn>?|ST{5H#lNGcyLsu`DWF54EC#d?KI0VMZArb1 zfS`I5621`ls%0%z4h+xT`W4*aZ;M~H;Onwq|9cGxS?vQ3h9Nzp4%-k(HxGzP>>|!# z#8(04Zi)kfPwTmM??pUZp`IA>>`wKVw@3% z4+kL$B+$BK)Y4S9v`q-|q0x7OBs3$pqO7~x-agy^q|?l8-I5O`>(;2LE=-hUD**6n ze>q=D6&lc9nnWax#d|g;^NH}qe1K~YW53K}$WOk?)FZl+40x(fiK>>EI{EhhxCKre++*?3uro~5WpO?f zH|I4v&2^REd^Vpz@LtN5Jfk`M7-TOx2*EfxBunchB>5Cr7k_@d%yj8}JzooAyHD*K z4af1W1RCqU&aJ;y{Y>ACU`=}`Q-)qDoUzg}Z6^s-t6u(rK_MgD;0utQN{C}N{nH6R z;?8B}SJ#a*Aju%IJYg9R4=8mwTpd)f#q-Jz)3z@l(4R`#2M$=1W8=$|Hb{oxdPC6q zK9z_k#}S;QNt%@$1X(HTMUKs0qR8*3B^7zMvHpH*&lr4^$bxAob?Nr7+yYf91zz)9 znZKhQ?KaM6Yw)8{zaxsE_R-G>j_YN|x59RkXq2>O><@u}kvZPt$K0T6)R?|rlkTpp z_H0zC7q?JbOrWtH)xgVDLi>0-T&pxJuD7NsWaJC2sCLinMjY`7zY3z604739DTb_A zKq=)bN0N1>`?gp5L}ZXMtIv$$K{4p+cNHAfi_bb6m>w5S>{t5iT+S$mKLBh2I`e_FAv!ytwX?@ADy z5U)(r=7(pdE8sgRz-t=Guu*7wRH!y=*l77>9+`~e#bp1xb05kiko}L}HlLAaR5C7f<92y0Z|;fD2=^84f?L&iP0=w+bwGn8 z@Yi;=$G(?^Sbf{QToN}u0v)Hx8_=)6SRSdIHTO;~^yGX(*4>E7cEkl}eCA8HSGkSW z`q|7I3Ns7RfNdovINuulb;XH=*HsbRBAI1TsAQu>xtvw~839*@+>jw;`CGWRJ0^lK ze-~d;GUW2UTuUUn-H_{)`lNi1b_F<#VSyzv&R6_NKT zfAu8dK>-tBxuX_gXN{!Qas+s=m8ff?HD4SY+!{gHG{j!P^LSL^db@8_{ z`o)KitQtmP+swcUbITMD7i&!vxL!7_P}jq28@W4J9X|JgF;(eu*!)PrGwm(Ehzsah zl%=I>xp9>^fo9;jJQVe|SG?0-r+m^eo*ND$tc(st2ZB<)TD4t{nBS0KxlG121pD(W zmth;CO&6b0Ylay6>L5;*Y8dv2gQ0{$>+easV5qTCp?Bl~f^^l%$rF|bD+epm(u-wvcw9<9OSm1Rv!18t5YsP@bhO4vKXY+>SPTNq?$pCVb?ThF}`lg zYE^&UG|0SMB3WeIaWuxh=-h9X_jDH~0J}qw4D`cjTbdEV&G8A{GLy&urX6is%clxW z0_p+a%2O#x)&0NUgnNVhv%#1FocDhz7ya{xa%;z(Z{vNBRhJCMW}ETSJx|kP0;%g5 zA9RczC07c$%mM{caEvpr?vZEZ(kdrjU;9WQh^JiQ4!nQN#2=F?m(sF0jwkQNW)bA# zJrjD{NN?n_S-*u71^7qek70}XaP_YZV_(ao?Klu%sDB6mIrT5y)xOul-&f=;f@eTC zxa=|VNT4%oFLq3&DvD6=I?9tyz;%g0+3yYi3_*j++?AXKo7DTAA5og8IfKCD)UtEe zNtseEt1qywDo0{?a0x0wkOlK5;1mCEJwWQBQu?`Y<2d)wvnvYcZ6xzZGZn;0n?UQZ z8-wt*=Xx{I6>S~~y}G=4B`~L7`nYf3Sp8u)L%fya`Q4(?VyEp0U;uQC)$tX7L4-`D zP8>R&ThyNF-h@)D(f-9ikM|0WF^##+NKx83ztM__QQwX0)n!fCe)PSWP*xX*brX^- zA`cUIo-=7>k#gx_YvKtFSvP;gZ<=LeL^+NnNz7-N4{4__u0Jb0kBk7bbGSoZ9eXup zYCd=Ml*RRKXo1J&q7q^4Wj2@%nr%=UKPj{7S7uO-$_|A*$H3%ndIdodBnsC2i!rD|Nc6qYp;FekwO=>tG9~U=1(Dta|$7#{* z#5rpla_$EshU1X4xqu8{ldVO$rF3nyY9+#U@{s2w+Zo{SdVe#K{0hDYf3x>X22L9v z)P=M&7s$++(?}XJkBN36cW_1#?A?}Ks`4HL8?_7`M*E%s3&jTTA&Z`?U7sL zY3va*dX#q@2-1}UMIKFs_aBTE+iPyvcN?WiSy3gQeCBKch>}6Eesq#FG|50cMWe@M z3lRZkY&XSIG94G7GX=~Y$B=k7?-L2j7SkT5ucwOs&mBk%WLo~y(@zMhh-k7yoanL^ zNAI#w6j+*sI79JGIoumBExmBHYN?R&8OZ3`eyeR7T~JZEr(d18$1@UdNlGxynY$|h z{#-#G=mh;6lT2zUQ#a1Ath67QR)wFA%1S@s0BAUG;{eal)mQ}@up2R64j$v+XIII5 zc+Um=NXg(jolD^}F#jlqLAh-oPeT_YAXL#HcbH|9Fx1#anzfj)6SBg#bHVd8%cgn# zFi-Og2nU#gEtJ(CmwFDO9;RCOv4$~1lT4cM9=%$89WM`*P#}@Kp^LK78GVV6UT0Kl z=+P}2;ar?Z)&hpSJ#^JF@_XN{6K?3ez%C_SiXA;(W9(>Z*;lWr|BEnL?*@2u`R>1{ zbC{avYU}@-L+g*RoR-H~u6UguE{Z_ajJo6S$7UW|9}sg0S|Y0xS_zsG4Bc(emU~5;)C|mS`?Ro1cFjDwlkv+ zIdszMT;mK`OcZXQNLC!$?EKBB5YEq66h#&_0d+rPfWEn!L|6r4#zC>gUlVZm`Hzv> z(bsNyx^djK#@il|A>3+Q6_)8PeS`po);zBYsfFVZq2gQlD5i%WjYb&oK1>%^=>5Jr z=A0DiA|44x;%h1E;61Q3bP46#Pt@|hK6JzZzX>W&|6~Ig#wH7 znz|X&ALu%MDu#RtkoHr(#Kvm&sH*OokV3E?LRlz;E>QrGfKSQ&j~wh!IjyKL;ZBo1 zcogIA!Z)(1f)EeG{sQ0+tO0rhTC#YEY8k!H6a9QRf^DO(b^{u>FZVCvO%S&#D5HMe zP5VlFt)&;G>$d{S8Ib0S8VJBG)k?e#?!QPjFzJ4299}<0fJQSC3>@(C6$iIG1CoR4 zkVc^2rZevUF3RomW1W4a4bw%9xK{!WfO$IayecHn0v!IDSKH3aO(ttHvc$P)1VO}{ z&`;RYr1KJ`fP+}y%IeY(sUunW_L$l2}5XUUH1WU zhbjwSJFf|98F+gz=XE%w0?bx)3}Rk>ryB!KtaX|nwvoaLa(YYD$66$;?h=`*BEbC0 z60O+KwAZZ6$tT^g0rZf6NA{m=aPedk^oi8$a>op08zK$GN-a!fJOJ;0%NEyHGBw}$YMd3u6iixeN-bZKr*!5 z7{Y`~twCrLw2+J}>3mEZ4_ruxLN69g8bV#zRZjC*6k;Kc=4W8kmR?7`^TJZhCNEB) zp=sIz(y{iiiFU1r`g#&tGf;jZ4iZpxGeRC`@~GzE$h&722{`&yYk8yl^w`M4i0mE9 zQb_}7Lo+Ef`ACEuLZ6g9>xoErJIRD_?rga%)0QuIkdlxzUxTwKH)8hsOtiCULdfLu z9mj}uQydY+t-}^R1433-zxcIGK+klw`NB9Ct~eiwJ=URckU=4zmwx@z^oaD7&yTXS zU)es@+jat?>PVJlfTn|r(st8(iFrxfY9#rwaFQCm+0E=$oWfnepVD&$tD`f6|lXUi^GO{*dj(HO~*eZff}S`?tCI3wGy1(TE-16@L1c( zD{L&`Q^YnKKopn}!8Qx79ZOY!&6Mv`yrT(lYbqHmb0%{e8=&?`<@LyA_CG%QNE}^o zy2fa^Jsk!Gdw%UDKX;XoH!~#CBSguNTdg7%AYN@!PAMk_QD#DW)gC&K91XNeoFSZT zWil;nDi)QU=Brdvsb9dx&i)u5UCuSCsy4*Hh|qmGXUO2g-Zu8d8~(-2t~7-HX)!B5 zCouDM+nff~>8fBKRG{KoC0VUue`j)3*mL@*oUCX?R(_5Yilig8a(U06-=@8VcO|4$ zMtrAtg3@{!ct zR!tB#+k@)Gf=(F-tY)}lyptB{*lqx8gB{8X#V!L0pc;rRj9 zZ$|S&%20^wInd~ZLy+Ov#A(QaPa7IV_vK-a;`TD6+6T-)RbmmjOI)kqm+W_2@R-6_3j48L{~UXg;Q=G%YOZv z(Oz+7BZKh2k!d4~Dn83{z{F|jc14+tcqG}Hki9w&)KCa}M~IEAZtHVD>`o}4opP#c z+h{glMFzw;W7?Jf0G!USrJ=E$Cxp>aL{zJROh6;|<)BGWb!h)oxUGba`i)I0BJqLn zr`F%$;uJ<%UY?AM4AdZg@jZmOoS1Ew9M$c&A|gR{SF_67{QEfzyqTkm*>G;4=RZ?T zCEoSLMpW+-S1A02h`V$5&X=|GkqN~YUAPKr7$60@f&_g?^945?{heudU@a7VwX=Cv zT5tI5nB|DkdBtITG;5qJeQXZA1SXm|n8VBLYl6)*r}gSR89~;0Nnd%CgFWO<4O-8O z##wy$re+l1q%*z#LEi@>^Dj%&UOw6IH%MTQ`SjG*GpTOu8^dD3nv^pS{{njwkbKRQsPt<&FOO^oorpXClR2=67>dyz z9BpcDA%ux~2{07Ek0T)BPBcC;&GF8-xx*Te$wy4eB63nV^JN8ZBIBsb0&f2~80Qx> zKR?903m4fq1R>svj*QTOuC_NX%T0zhQ@{7d^H%hekdCKgscSjvq<{5~v3pssbM#2HC4boARRi?JGbxi(?*`ywubi)1GTgkjCbgk>s@RJvV-arm4)F>MI|L){R8NXK z32T)`b@s2m9bXP;R!RT8gYB89_Kn6{z^aH_kr8sP_mUOfr)HLO2@e1}Tfl$5jp7<0 zlGSiZZq1n@%tt_{8%Fw#7NT|yTt6PE3Y&(NcfD8irLUm_b(={(8O8nmK`|7-8D-bz zPBO$?oagk(Ar0=@#U{Av6}BUK3iLN(Z}K1|MC1FnPKb+xO4^soYTStRpxVk${q&w6 ziNOU%WR1{P?Q1P(n(8}4vj4bKfz7veyM5=;3eXSwgPy<)6yz^fdYSM;QoJO6t$1)4 zCCx-w`|3oJL0ZCE=hBZDxvU#EgBWAWkNkxig=8&c^PAEb&C|8PSsL;_Ksk~g<--uey(m!d+hs z5hFkW_8=)51QR$}{8T4z0HnHE7~ebRSHOKclWP^wZ9v zEZR}A4kSt83r_y)Ow>&PftNAfqzhZizqKpvP18(@&!2d12X0+j=5AiX#N%Fs*lDG- z0t^70WbxuaMJZhqt{GiVY5hZTIUYiz|)Ujb}thQbAMhtzXjiPo9j2)zkeb}N5l*F>JvI}|`HnW!Q! z3n+os@s)&>mx=59^xuB{F<*Pbue51mDi|||q2n_5GjG4eb6HgRe!#f-6fZOU4Z(nB zUH7>cWYljUn+!nO;K_zJu@A6mi=F9I%c7q+_5ImFs4IHE_t0}WdXOc#dB#!>frFhk z-dki^BOz}G4IRsHSJ_%1kmPyPlJdmM7!&$Y1va<)8D_4y$SGD!pY+i-Ffir+|mAZg*x#c6GDkeW*WDMhsfQ%#Dyi=m<3tW;B*X6a`>L{h&~U*PEk2)QqJxqfal^M!@p# z^bD9lGmz2GQ|}ikVdN=5CcS?$svL;^4yl@k`5kfsAdAnU`a~N6negGZ7Av&KQ)R%;#3(z&b zqrv$=WASKo_r3lj-{BWrI_9KNhd&R*vL3@cpLu6*@aUi|F&cAuOPk=tPkF4T=1~y!?IUWo*J(9>if^_U)FYqPtM>c%G-zATlV`-yib~YgoI6bgJE=4+Ld_Tl` zl8Aj7#H*fUe)e8eD2PgjfmK}5Uv3q#0RpdTE970-IaP32%0(xEkzv2XcPamwmMu5P z0N>FO+Wd(Vdgb*Oyv(cnzqPAB9l+*tQwbU-W^>lfvD8PCdq5LMoGP$xT^9J@-be6) za7RyAc%Ngd`LKu;kkqa;S#Pl!`Kq`#s=PB#VXFa}QR8CJU?=*1*O^K@tCd~P)ll{p z)K7tjQH$Dy(NrvAY^^{;ZucQOD8*o|20UP%42QsXDqihL>jaqAw|DhRf`0@I4=mBP@b zq14tjVQdx%ZCREe9NG+$V<$sP5WrFE?@zo;WWdvn5MAlECbmSM>bk65EC+4el{u;i zl>o+bN6psH<-iH;mW7Oc(PczBUNn!8*b26$b!>T-8eWGiAm@yw`+&L@xjHtkC9dmO8T( z)S{NNV_<#b?^K;Bz?zk$8=`JuL{9GiAm9aVetM-TvW`lKDgzEJ@t zq-k>ISeQ&Tl-`(-6l+46u4Oh3pA@*&DYqB3upEM(OL zha4hil{`sZ|9i@}Z>XvO`hW3?58RM@!<)we$`Kh!+>LU8$7w@9o{IO9%5s|wa19>? z_?8zq4r9Si*%KYfig#MmSdc~!M$B$sx85$h%D43eGqunVTH!d2_)d~C*)m5QZX|{e zW^aN<90z4wlE0&~&{K5LYd-Zn+7Vc;IX2)4DOR+n5~KZfMoZ{T{1RLW6UaTb4!ln< z$21FEcI}+3b&jgSJ9vAOA9xgI)OIv#%DDFY=hCu_G-_#=r38OLX)-DUG~t)QF8?OOobwpyH_J_9WFD@IVE`Q99k+#-%ZZK*~r+?KH%(qqkxk9D2gf-2&mD3pgw&4ee zC^}IlSszh?1!xDm7JiSIq%N!rmMDyFn~l52e5c-diW|A znsNGN`QhIvAD18GeI5 zPTypd8msm_CQ-78)`13b)*=hNdMM|QW>%yaWx*=#Bz2~a{W~{C!Jkd3y;L%{slZj= zp6S$D>se?|6woPdeLouEr8`etXGv$=HJ;U?tjgZ7s0iXXQ}ub@$o<^wGQ$r;ib7)B z+1nsFa3n!>@_rD0P>wfBWjjy;Ezk-ymm$vZ{t;Nn8_tst7FW!;G^$5y@e1(-987nN((^*c~!qCU8HThU05H)lsOAa^Mqnf`k7lhdB{V^ zomIxDJF_g=h?bb_R^EAG6T?c1nZ2XrQoy()u@^>zrrAY*UY-uEw=GY-CONh;hGy?~ z?L%hs)-n}20;W9e2MZ`?ZKhfju4qtYeOw*4%}A|9qvVN^E4!!E`b2j8uh<=}RzVCj ze%4uWca5A-Ho?d%ZMZSISb2jx<9H?|(%@9l5++osZT;~84G(n~|+@j#m}qQw}QZ|^ey6LC5q zG}p57n!#Q=b7s&oJ|EGjws^lDcsUd7=+u#! zq3rEL&@#YAdkCLvpov8$%g{Pe3*FwsyBMLJean1_WDj^H#b4X(k)w1^tS zRfn77G;!_|Ow10hy|rZL$2;ddgxTb=B_cow`1&qQ{(qnEy8>`Es))g09TNqL)!L>m z0lRyt|Jk<=1h!W-Z`h*)+vU4=N4VXr~9^4&P<0n*=g#_`ps0{@CP#GVbqoN`QSe&w~h z65GFkvuNz4FB(RoUJrx)SRecq0zN~dJ(F3<2rpbpaWE|Dnb{>XqTgZ5V>9<>XMD88 zI7vP0ZpwA+^u=vv&iL2Nm0D6q4W2?J!UdXa@$d zn{sFD>oFOct+ z)PLaQDz78`Qp=Q8$<-Mos+#~r;-Zh`^ELpED05>R^3wJz=Ust9xxR0^t zUWxl+c*0PxEM%5{F3&LxUDQanQR*P^lJQp_QdaYm{tyX1SRmoq+ZoBeyV((~r?t%4 z;Y(qutShN52*(lb@woDz0BB5OCq zrXplqJPNO-`vNJlmV||Dly#D>V1ij)DXh*UWQDZ;Zh*4<6LCeC0Cf#_tSd&Q>ZB&7 zw3B=IYd2wa{UPJ*<1Qzq$qfPmxJOqDXm$oB48Bvn8?eO}G#@|Ef^{^2f6R+|NPnw0 zr#PEWZP&TwR*gT&;(UW=6ameOjA15im7K%6&ZbpDWQlGFZXBxNrS;j2^dWJhr*ON4 z65Ruj$JCuhs*mU9frskTCP#nHDwANhA^BeI&n_ z2w<|kNi8V`czbDXL8&PqMJ(OkoWa;As|BoQb>H}B{%U#+9Lf9WKhleMX*@oYj$OS6 z%@Rpa-pzxXOLIe^Zu(EbpnZocQQSLk%$MetP#T(5N!wt%7m*^=LZ$))ZXXJ&R|<4@ zYPIX7^gO#o(Mq+*8Khmlvkvksr)eVHs%v22EvRQXghLA}rdF222NLTjf@e!HBmD`+ zA&$8eS9lZXKsKh>ys~BlP$2D_tiA0wh?wOwds91kI|%N_D;tYOJfn!h9jdE_dOz4D zcxfJ%a9CMtB7!U*XH5|et$fM~`HNK=Lca~Kdg92$q2Z~Vi6WAKi`+$f-en!qm;b_~ z?8uL%-#{KHNWX=a%JM+!Br(#h;edGjpR?xOj6IS{tQc#Gn|8=d5BRQI0g{8^rdME*lzyJoB z+Nt6OmlheMzvE^cM+%Fx8f}J*oz#CZeMhv93jfVjgZAu~02|e$t^YXVfl1-AlvwgY zJ^@eN_Phs75Cb%i$cy3#d+rtNs;GZ+Xok~d(qZwveHo`3>}pvs}-SM=n@UdDoxwuMqkN6FN!kkmMe1o!7{%1 znk!3&wQ4G{;5xfyB&3tL=w%=xUt|Q3!B2Yic@hYGn$R2@6sv|}v~EwR?PJE~{RzwDPbY)PD>{kCyv)8~!8U9Yd;2YCtE-Z)LE}4bb!;C=SG_es0;jmB;SqLDagwq7Ht(jx9^lecK?(B>1Ro z?+mwH>V}=jO*-%dPx@}lYdQ=^5wfDS{+JsNu7Z#|q*Lm@8rDK)6K`5<2$&d3Cbe=> zj}x5gn?zf^cY$j{R}In}CSL%ryp>$V@w}Fy>c{8GHTSM(;Q4OCY@;i3Fe9s-NfnHT zNFHfM-76~ob+e$#g}hrAHYujmm0snCIq;vfpq$Ndup?omw#i|ZtRd1)ZvXgB8FX~! zUflcBaPO2?O2#H;?pPU&LrxuBe+fZN(Cr06A`DTC@-UWCcUTmxVprryL6?~OWVji8 z-&=Gaf;O0NCFpNneC&-%w%wG=^b=%0brpFJl~Y!{U6F1594aGbw2auDkYM|H?C z(qV3Dr_EKqgF{2NQ5#0l`69HMps`wRIKLEP85U!;P8^V?vH_ z!sOcC*!D%Ibg^Da1OwJKrJ|9a;JCqXqsowT7;!7C2)>;dC95qz6r6+0&qwLDk_u)#PUHa1rauTO85u-20@BrQN zspw~DGcWh@j#Ga{Ws)d&l;_)lF*-l*RZW3-A&~9=Sdyl>R5k8v7@40@Ef{@Va2qlq zT4K9TFbs$c{EipsL(-+Q^GL>okbG^GsVt<#LFW3U?O>msX`Pc(Cm4^(to0A{C7+j= zyf%=8yqd8dbpc15mEVemR$rHlePDmXSg5@f6dpDDM)uiIoyR@48T?rE-3DK0w015J zF{TR+(?16C8FG5{idfAJwfvuA7E^N>kMP-5M5-f4eduvXPO4}HvdA-e37h)*^-I(q z0xjd>@~gErlBs#dFnaC26}I@lcq=9>E+tqoa>dtOu*j($t=w7o{A3M&A~OxZRU=aG zU!&qR)(rxJyP6A@w7`i*lUjg8k#c2tlEXw;5GRBT1fN}f5^~U#VS+XK%@+xC$nOG4 zOgt<--Dm7mJ7EYzO0HPBi0&gFZdvj1$}y>&`c5qJI`*~oHLyXuwTG4&<=G5{NILNO z$fVu#Uo_mt;ZTAQ{O&j6;sJE?svXDijK8s)?wF?FJWs{WI>%i6u!q`TKY~(npj}jc zv5K=sApHPDy_-TEdQ(EO2-w6Go9Oiy-xd` zU%7d;2BmPv`lzcKTS*M2GI+O2Si>}yRWk^G$qPWy#qik&gkQy(D%{}Z!dUuorXV2$ z^!nnnqd=bZc)2j|{HVqZTjop$)|rP_z}hU*DaUNSAT%0ZQH|%|U-7LPb9r#Ah|fgN zT=;&Ft;pYL1y$l_H^-&X{vMeAN;Rvr?b|7}-#6&;8@Xc&;QSn)czd{2bi#)f_`S(~ zMpOI9q|$q9q_EH5bAN$E`u{5vN=dZkMGKIco$}j)zwpj){sAp&aG9bwzA1B^6LE-F z5{fk$2=JD>m)FiuBE?|A%6YZg1b`QCw#@JL*Xk@-d&v5@y1R$wmE!impae4&PEeUi zGI$hve`;y^RcZ;lljeY2H`IiaaZ@?{Y_htZ!8_w`D-j)L?) zVOy6F0Dez9&xub~$m_%^IE}SW7Q^)cXwjEgib{O$wx}#3i}c-Y^{HbCJ&Rfce<=$k zd-HPx$;RKm3sa4)1<#cGm}<$OH4eM1-X@JkebS+{t?hUQ>YnnEpz;BI@%Qw12NQ1a z;%I$bs0KUawe;efO>k`qfkMr#cfD{C6WQi4bU&+M$HF~H1~z0mL>pI z97S>9c{UGBZf z6NEFv7h@>;GF)ujd>@d)O5rCc!R@qp6oMgQ-bVxLSEUX5G67?D zsf}8YMS>to{48$eWWj&bF2YtpefMuv>=U#qaFnSrg>1^^j1vTL1!4&`@SHtqC~<9_ z6`z+0gO!iK^J;dSQEtm*^B*1z7WR&Qp>kV_TP9y4Wi4qpTh;~G@t2bQGqa`ab{P(d zK3*fIk5<*QXQZ`iNs+53kZ!0VQZrVddZ;T6i> zRVuGXnAwmuj?IHAzTSm!X`HD0^3(iYU04K5pYFfPVom~6wJ z2Cy6Gc5?4UEDc2)FFW@m{dGOSV%g6&Z0k*NQ*tV--9qX1M3$|LlPGpm(Q3x8efY{d zCXdOxZs%dn>lfH=u*0oFUvgB5{ZReat)z`)|?a-O)J9vgBvcZXuk z&%sRr`3uiy))aLTW(-7d8gJgITi4+5lu*P~8VM}3cb4OPxv99hzppEX(5G}{2K9b-#AL(oH-J|-X|Ua>Hcet=FwNAYlrwbe#f?P63vr9kpNz{Tg|v0a0TPjZ z#}smEwd=IMUyB#aK^v9l%`^Zonm+gT;=*f`ouxh084c$eMorgqV~gUqO|-GRO0`+r z;d&spQ!8^Fq8OHmI&Qspi#R5Q?PGDo$2<)zR#|nRMU*8=>Jv{kAWJ|;)Spg0Sk8>` zSPawENj86HYk59qOgsZ+ofW(V71$6Mu=*Df^ zTcT9r-tL`VI#I@Ji-&@3rzE*QeNwYe$7+Mvfr=$@zmUZNSK-m1Xx5s8K~*b*m1Z%9}+)itYEoV9Ab` zdcbto1%eQ65h=|U?@gg|VVYIjJ2$*wC&r&_$7(zFl-fe)GJFy1@M^Sckp>~1R?uRYQI3Hr*=x9hR&F0JG(ZXh|3prjh*Ku zNk(f=A_91UnLweytz66U#l&z>yN5LS$7h!re+@S;v94I%L;q=#&Rv3bc_@gRQ&FT) zd8Qcj@jZf3{fG=Ot|G9Id17Bw5c*X!$1mum6YIn~v|BZFO&B)>SOy6Sw&7$eN-C>0@#W1BcNFmz?R51n+ii@QIhq%ya;XXm0r)VOmI9ly12IQ>QYmexfDQd3A~m-UZuzAz-%{~ zH8LbG4EC=XORFNW@`L(zsNPbf8|Gs+t)J9V`GLz{Q6+YhxR^*rbh71ep1 zLFfB%pK(rSln85XNr?L0n!^}kgv1Ka-iisKDNyq28&qsaiP!E=>u(o)x%9|FKr2K! zT8=L^wNq2T6^21_dSoim1&HnCTPO##9D$MAsh3^5jf@-{z^7Fy+twlYKH?ZUOIo0n z>_gb%C*(t#aH}cW{Ji*=Boi51(C^d3x+Jv01`T{nwhb#3LYfxPZCG}o!-}N9hedwi zA*#XZicndKD^m?^KI`Z_g~8_t*_c@F6*<@D5Vr@_mqnG-(9p)ZCq)xS_Zu;d9FWrj z;trWOc#`nh39ZvHM)t>@_D)kp6-sXNC7)M^OA=XK3Ka4W0mBAsK~g?TL?c&9vuEyN z&dO7P7gT70GO*H2wl(dg-Am#f*@?taW*3|+QWkWxV4iXAP&HLKz@o+Fo^!x-^b#Q;`7slR@P`y`k=8kCSF2GxS(l7p_( z?Np~9vpV%m_;~ZiUYQu}D6h#_vx9F!?!c{p7NOSiU(G|Du=D8rfvZQWg6_)~R6C$` zgu-Q6rx6O-1hhzRnC?SF?A=&%W7KeyIJIQNgm=LR_g<=}&Wa_v07_ULxDKK*N$onNJ z9Cno-08TAG&pp$^ph}%7JHJ_eR|lWBlT$a+SD!rpqG1D2g2hIjG*bsargIzL2Hwdw6zaqx z(RN!*9LxZBA-wtmHrd%Hdc}kM6nv;I@Mu0DBp1**;2N@W&|cwZz$#zK%8eKpdgb;( z(vbmTDjAxSKu-uXh2|2+_o`)HYlw8WG?V|Wtf`UAxAexv#iEI-eIkQ+3+AoUz3Jp zVX9puRR6AR!mUG38?9#uVBeoMqCw+&82oAxAHC36ZB%7i}v5t7u48|0v0BSkO<{g{kN z8kW+_N}?T>b@5=js*ni=>CG0w^&+@bS-_>B6onPn)`CrXZGBYuZv)s$NKaKJK=lPx zs6S=LvlFTwC{P*{@Go4lw)wjHi=h591zL0iqCRFS+xmPOYqX&T{OT%fmtfn|g9GmHRnPalKxIIFe8-J)>n zU_oP1j@EUC()#9$*avLnA-_s8U39CyF8)0eRqlqt8poG%7vB?QrJ(y9)Kx$%Q2~0v zuogUZLXAW(Y2INDU*U)YV|6LI!-i8-a~cSke4!?YC)$2hl${J~A9AFa@DJA{8eg-b zrX)_f?;dOCZr|=rtkO{p{i6j=IQc5Q85D+G-ORmkqvE2W1W|(jA*{USd`#WD2=S9$ zUjpF~uwn|Pi*#}`;+l$;=D@TNv*+njGg(*l7{K%8l9s6F3w``3Z9*eJKVt83GhZoX zaZrWho{Ku4HVlZVTA1-X1=E=XYWGX#nZRwz(?*-o-NO9(*W1GuKJiwHK_R`I(uwHn z5u^)4ev(=F+h6-Fj%#o&E4v*67Ojga3vGnY z+<#x7rpT022;Z4a`>r_$So~jvY$IDecvfl~gHzjaeDP%36gc_OaEA7e4`#8OmBb?Y zGMW@lm+iV~&@Vo^_&!L5da9d)(?HG=A$P}=fb@G_TtT$Y2r*EQ&lQ0=nOhqFOEPmz z$P5c0g~G-35%bFpIJN6mdTnG=8>Tjelf!yAEgVM@Z}w&>r4fzqP7dQ=w`1-_Bj8lR ztZ;6>&&spovw4%}7W)K{+L6h{dtizIP5$eqBQx2NcOX6j0rk8Iz@9PJ2=yqZ1ut(J zJDoM3uSr@pe%{6swu&AX4~INOr1;#qG~e-sj3)crc`oHwxZr3QV4hpOC~Dh;lu+0Z zLY4d@hW3*Gf_heX0>vR&IOwZDdx$czGr?lM!=q-p=@%40K{?@)r9>~FZA*n1>c7GE zAS~>!Ywqh!_-T%j`~ai*9{i^peBU7j?qq|hLk)j5&7n-5<>wh666KE6a4XPvdgs7e z5sFX1MXU+l`J?vd;dMCcm5F=$ufkd53+L{-APwIerG^-;fmI|z3?f3v3Hx-(5qwJe z@R8C)Vp~wl?IK6_+~2D(4;f5{#1^1;I@s8%vS&}9)u z<;UZ^)EM>;fVHbLOxO^8gWaJ33YRmGv(1?jZQJYm1X71UxCXUr$fD%#ntlwAgCLOk zU6`P`B`ER`bZ+cvfD(PAX$tI&ba}S89{Gx3WmZ_=V_H3Njgrw~;+mhH=jgA^iJ8Yn z4vLTUOB~X;wcheK9XGitFjx(T@!e)k&}{y`2g#Vowg>s_V4z6P`M2qi&Pwcicpj%i zR`UgU>ZXfcE$yVUC&wr}DY`xnvmq>LC?jFWOKc~MY@;;@f8&AdKcf_9D>TGDH*&L& zFy`~H#>10vvU%h3;c&A>>u{fVhdyqd$K_e8xN1QfFCW~sBot;!{*b>PwtxwvFgpeF7jq)#HkFKO(qv$n@2h64N0y;d3<1c?rR z{Hyts+JjYVgfGjEQe7rqJ0Uf4`$-SgSAW0|)f(hRAn~953_P+uXmtbOQ><1>MxB%5 zn`|4Klp#vsQeaihK*Fq4i%>*=o*pfzZIs}|u^8}0JW;Z6{`vZX!}+yBy0cG?*_=J5(Y@kipl`b(kGh_JJz_;I z!K62B4}eCfb}{bK18FRI4A5v+B&n)YB+<-}=NN$?o7Xe&P!?5q7ke}bb59YQ=W-BA ztd0i`*lv|W8N$`OnrN&a!3-XChL*R}?GH0GhY&WeTWCMYcspgoy|BOOW*@l%MYN9} z3c}N@{igjL*@|>P9XGhfIGXrd{X@K_@KjaR)lWb$MEb9axpOs2vTYX1{Dvvw^_ay# zo>fYY&PvKF@pr0e;Fb`s<0ikH+z&ojIv7I*k*XbQ>0ZHl0Deg~$Md)|1DGUo&^0wj z>0COu7lv_GP}TdXAA0iQ2+!KMu$#3~hI-485ldwCSe2!b{?};3vG>=yii<6k)-EU` z?%;XOP-f&%WN7{TxE8lfp7!qyZb}%A^JkF;&He&6=nAD=b#(bwYT9Fe8vYnxXdR!# zho8X`$3STwzNQ!R9iiRPlTBWyz%@_HIkW_zJ3Zr1_cYU#l4p|e(@0Lw2+lb@1w_jb zINc~_KvU+R=uxy2>Q{fGurOR=da+OZrwdJ3dy_8uianvDzfedgYH;Mk83E%g= zEqdF!^+0dG#$qruH$MVEvLWWiflhCm6#sf@7-;qv87$Q{ROzP*a*$T~9yqh!Lh!DC z6lO|bm-HA}%BWA*NJc+qC6&vk6*x;3dZl9d!fP?5N$t2v<=1I=f1^UidH2gfGlEA~4^UA&QYvXG}F6=OXhy zZZ}(Hmx%1+b5_nyxBsaXtMr-M&n9PTa6iuVsx+#LE1J4Tncwr%iD*J{)dC=7AaNA zqPcP3a2h{^cN=#k=Rimxwr+>~Yi3f57T9C&mUx0D`0YN6aPwDgLBUqJ9!8oxa=SC? zu0>`4w1?f-0Ep2raGgXR>AkJ&XW2{weud3Sw`?M26jUSctw`T`a?=LSym5Lh0N|r{ z1)=0>=uZI>X+~lvu!#qrs3W=maEZ0kPaT*s1?)`|yLj&w1Dj1fOj2Z>0{GZ5d`bT# z-k;)Q0@bjv)oM(xw>nE9UpX_m8*2{5X!flcNY+T3NgDra-tt-0&WR-1K0`-E4YpAL z0m>w8n;K`0=9-8K?J|0(MIYwl%rq*{7GW_PQPq&~u6-X_*Sr5HQ+?z&6=&~h)}v=Z zxRU%UUGV|S{uTHgVIa6tT;^ldg^Fl1lg$v*@_hl@PWqx~a`}0ruM{q~@?tAXYVew| zraRPr==K)!H~=kYX_2IVHBIYh#BZ7)xs?Q9{!lV%*G;RTd89r{^%aaU{|Qpa7MJ8F z#IX#Wg;!GMb)lnv!iNF1rbPag7TCz@C`7S!tOM4S@y6SUe%xb{6Be41?AvFh2RrIQ zfPyo-X}K-vF2C*^S%~kp>c>D5*xP++YEXV~⋘MMOd$F`cutHP*$*=)u=&&;NxKP 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≺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 literal 0 HcmV?d00001 diff --git a/app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/B16053724B46515388FDEA9D0470D02E-1.json.xz b/app/parsers/vol_Parser/volatility/symbols/windows/ntkrnlmp.pdb/B16053724B46515388FDEA9D0470D02E-1.json.xz new file mode 100644 index 0000000000000000000000000000000000000000..9a7616b39cd07299561a19a1d0dd4769e84df395 GIT binary patch literal 585272 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

|lj@ld0CmR8=_wKj)r27@p}ea- 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 aJp9z>6j+Y0?g_1`nV9-WgMT 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%0DI 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≀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 literal 0 HcmV?d00001 diff --git a/app/parsers/vol_Parser/volshell.py b/app/parsers/vol_Parser/volshell.py new file mode 100644 index 00000000..4de304da --- /dev/null +++ b/app/parsers/vol_Parser/volshell.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from volatility.cli import volshell + +if __name__ == '__main__': + volshell.main() diff --git a/app/parsers/vol_Parser/volshell.spec b/app/parsers/vol_Parser/volshell.spec new file mode 100644 index 00000000..832332c2 --- /dev/null +++ b/app/parsers/vol_Parser/volshell.spec @@ -0,0 +1,66 @@ +# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import os +import sys + +from PyInstaller.building.api import PYZ, EXE +from PyInstaller.building.build_main import Analysis +from PyInstaller.utils.hooks import collect_submodules, collect_data_files, collect_dynamic_libs + +block_cipher = None + +# NOTE: Issues with default pyinstaller build: +# jsonschema: +# - https://github.com/pyinstaller/pyinstaller/issues/4100 +# - https://github.com/pyinstaller/pyinstaller/pull/4168 + +binaries = [] +try: + import capstone + + binaries = collect_dynamic_libs('capstone') +except ImportError: + pass + +# Volatility must be findable in sys.path in order for collect_submodules to work +# This adds the current working directory, which should usually do the trick +sys.path.append(os.getcwd()) + +vol_analysis = Analysis(['volshell.py'], + pathex = [], + binaries = binaries, + datas = collect_data_files('volatility.framework') + \ + collect_data_files('volatility.framework.automagic', include_py_files = True) + \ + collect_data_files('volatility.framework.plugins', include_py_files = True) + \ + collect_data_files('volatility.framework.layers', include_py_files = True) + \ + collect_data_files('volatility.cli', include_py_files = True) + \ + collect_data_files('volatility.schemas') + \ + collect_data_files('volatility.plugins', include_py_files = True), + hiddenimports = collect_submodules('volatility.framework.automagic') + \ + collect_submodules('volatility.framework.plugins') + \ + collect_submodules('volatility.framework.symbols'), + hookspath = [], + runtime_hooks = [], + excludes = [], + win_no_prefer_redirects = False, + win_private_assemblies = False, + cipher = block_cipher, + noarchive = False) +vol_pyz = PYZ(vol_analysis.pure, vol_analysis.zipped_data, + cipher = block_cipher) +vol_exe = EXE(vol_pyz, + vol_analysis.scripts, + vol_analysis.binaries, + vol_analysis.zipfiles, + vol_analysis.datas, + [('u', None, 'OPTION')], + name = 'volshell', + icon = os.path.join('doc', 'source', '_static', 'favicon.ico'), + debug = False, + bootloader_ignore_signals = False, + strip = False, + upx = True, + runtime_tmpdir = None, + console = True) diff --git a/app/static/Kuiper.js b/app/static/Kuiper.js index 1456aa00..7c06e1c0 100644 --- a/app/static/Kuiper.js +++ b/app/static/Kuiper.js @@ -1,5 +1,30 @@ var toast = $.toast; + + +// escape html strings +var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + function escapeHtml(string) { + return String(string).replace(/[&<>"'`=\/]/g, function (s) { + return entityMap[s]; + }); + } + + + + + + function toast_msg(msg, type = "info", header = "Information") { toast({ text: msg, @@ -32,8 +57,8 @@ function convert_json_to_list(json) { // it will take the json and the json_path and return the value function get_field_from_json_path(json, path) { - console.log(path) - console.log(json) + //console.log(path) + //console.log(json) if (path.split('.').length == 1) return (json == null) ? 'null' : json[path]; // if there is not end then return the value @@ -76,7 +101,7 @@ library.json = { function build_windows_event_table(record , container) { var open_badge = '' - console.log(record) + //console.log(record) var system = record['_source']['Data']["Event"]["System"] var eventid = get_field_from_json_path(system , "EventID.#text") var event_id_link = "http://www.eventid.net/display.asp?eventid=" + eventid; @@ -135,6 +160,7 @@ function build_windows_event_table(record , container) { } + // build simple table (record contains key and values) function build_simple_artifacts_table(records , container, searchable = true) { @@ -154,7 +180,7 @@ function build_simple_artifacts_table(records , container, searchable = true) { var search_plus = '' - html += '

'; + html += ''; @@ -183,7 +209,7 @@ function check_hash(records) { //data : JSON.stringify({'data': {"wanted_page":wanted_page , 'query' : decodeHtml('{{search_query}}') } }), data: JSON.stringify({ 'data': { "hash": records } }), success: function(result) { - console.log(result) + //console.log(result) d = result }, error: function(error) { diff --git a/app/static/dist/tagsinput/bootstrap-tagsinput.js b/app/static/dist/tagsinput/bootstrap-tagsinput.js index 3803f0a8..d314aae6 100644 --- a/app/static/dist/tagsinput/bootstrap-tagsinput.js +++ b/app/static/dist/tagsinput/bootstrap-tagsinput.js @@ -151,13 +151,18 @@ function escapeHtml(unsafe) { // add a tag element // options tag html //var html_tag_options = ' '; + + var html_tag_options = '\ - \ + \ \ '; - var $tag = $('' + html_tag_options + ' '+ htmlEncode(itemText) + ''); + + var itemID = itemValue.split("|")[0] + var item_text_element = ''+htmlEncode(itemText)+'' + var $tag = $('' + html_tag_options + ' '+ item_text_element + ''); $tag.data('item', item); self.findInputWrapper().before($tag); $tag.after(' '); @@ -258,8 +263,6 @@ function escapeHtml(unsafe) { refresh: function() { var self = this; $('.tag', self.$container).each(function() { - console.log('self_container'); - console.log($(this)); if(self.objectItems){ var $tag = $(this), @@ -282,17 +285,11 @@ function escapeHtml(unsafe) { } else { - console.log("update: "); var $tag = $(this), item = $tag.data('item'), itemText = self.options.itemText(item), tagClass = self.options.tagClass(item); - console.log($tag); - console.log(item); - console.log(itemText); - console.log(tagClass); - $tag.attr('class', null); $tag.addClass('tag ' + htmlEncode(tagClass)); @@ -428,10 +425,10 @@ function escapeHtml(unsafe) { } self.$container.on('click', $.proxy(function(event) { - if (! self.$element.attr('disabled')) { + if (! self.$element.attr('disabled') ) { self.$input.removeAttr('disabled'); } - self.$input.focus(); + //self.$input.focus(); }, self)); if (self.options.addOnBlur && self.options.freeInput) { @@ -480,7 +477,6 @@ function escapeHtml(unsafe) { } else{ self.remove(prev.data('item')); - //console.log('DONT remove b/c NOT sames name=value'); } // >>>> TRP 12/27/15 @@ -493,10 +489,6 @@ function escapeHtml(unsafe) { if (doGetCaretPosition($input[0]) === 0) { var next = $inputWrapper.next(); if (next.length) { - console.log('case=46, $input.val()'); - console.log($input.val()); - console.log("next.data('item')"); - console.log(next.data('item')); self.remove(next.data('item')); } } @@ -546,7 +538,6 @@ function escapeHtml(unsafe) { // Only attempt to add a tag if there is data in the field if (text.length !== 0) { - //console.log("text.length !== 0"+text); //self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text); ////TRP 12/24/15 //<<<<< TRP 12/24/15 @@ -557,8 +548,6 @@ function escapeHtml(unsafe) { if (beforeFreeInputItemAdd.cancel) return; - //console.log('beforeFreeInputItemAdd.item'); - //console.log(beforeFreeInputItemAdd.item); item2 = beforeFreeInputItemAdd.item; } @@ -582,13 +571,54 @@ function escapeHtml(unsafe) { }, self)); + // tag on click tag_itemText edit the content + self.$container.on('click', 'span[class=tag_itemText]', $.proxy(function(event){ + if (self.$element.attr('disabled')) + return; + + var itemElement = $(event.target).closest('.tag_itemText') + if (itemElement.attr('enable_edit') == 'true') + return; + + itemElement.attr('enable_edit' , 'true') + + var item = itemElement.closest('.tag').data('item'); + var itemContent = item.split("|")[1] + itemElement.html('') + var res = "" + itemElement.on('keypress' , function(event) { + if(event.keyCode == 13){ + + // this to prevent duplicate + if (itemElement.attr('enable_edit') == 'false') + return; + + var old_tag_content = itemElement.closest('.tag').data('item').split("|") + var input_field = itemElement.children()[0].value + + var new_tag_content = old_tag_content[0] + "|" + input_field + "|" + old_tag_content[2] + itemElement.html("") + + itemElement.attr('enable_edit' , 'false') + + + self.change_tag([itemElement.closest('.tag').data('item') , new_tag_content]) + self.$element.trigger($.Event('TagInputEdit', { item: item , html_item: itemElement })); + } + + }); + + //self.$element.trigger($.Event('tagOptions', { item: item , html_item: $(event.target).closest('.tag') })); + + }, self)); + + // tagOptions icon clicked self.$container.on('click' , '[data-role=options]' , $.proxy(function(event){ if (self.$element.attr('disabled')) { return; } var item = $(event.target).closest('.tag').data('item'); - console.log("=====" + item ) //$(event.target).closest('.tag').find('.dropdown-menu').dropdown('toggle'); //self.tagOptions(item , $(event.target).closest('.tag')); self.$element.trigger($.Event('tagOptions', { item: item , html_item: $(event.target).closest('.tag') })); @@ -663,17 +693,12 @@ function escapeHtml(unsafe) { change_tag: function(tags){ var oldt = tags[0]; var newt = tags[1]; - //console.log("change_tag: ") - //console.log(tags) - // get item container - //console.log( $('.tag', self.$container) ) - console.log("old" + oldt) var $item_container = $('.tag', self.$container).filter(function() { - console.log("found " + $(this).data('item')) return $(this).data('item') === oldt; }); + var item = $item_container.data('item'); var itemText =this.options.itemText(newt); var tagClass = this.options.tagClass(newt); @@ -682,8 +707,19 @@ function escapeHtml(unsafe) { $item_container.addClass('tag ' + htmlEncode(tagClass)); $item_container.data('item' , newt); - - $item_container.contents().filter( function(){return this.nodeType == 3; })[0].nodeValue = htmlEncode(itemText); + var itemID = newt.split("|")[0] + + var item_text_element = ''+htmlEncode(itemText)+'' + var item_container_tag_itemText = $item_container.contents().filter( function(){ + return $(this).hasClass('tag_itemText'); + })[0] + + $(item_container_tag_itemText).attr('enable_edit' , 'false') + $(item_container_tag_itemText).html(htmlEncode(itemText))//newt.split("|")[1])// + //$item_container.html(item_text_element) + //var item_container_tag_itemText = $item_container.contents().filter( function(){return this.nodeType == 3; }) + + //$item_container.contents().filter( function(){return this.nodeType == 3; })[0].html(item_text_element); // get and change the element values diff --git a/app/templates/admin/configuration.html b/app/templates/admin/configuration.html index c31e29e1..dc487705 100644 --- a/app/templates/admin/configuration.html +++ b/app/templates/admin/configuration.html @@ -118,6 +118,7 @@ + diff --git a/app/templates/case/browse_artifacts.html b/app/templates/case/browse_artifacts.html index f461ec3b..ed46c6a9 100644 --- a/app/templates/case/browse_artifacts.html +++ b/app/templates/case/browse_artifacts.html @@ -454,6 +454,23 @@

Record Details

padding: 0 5px ; } + + + + + +.event_record_click>td>.popover { + max-width: 150px; + width:150px; +} + +.event_record_click>td>.popover.right>.arrow:after { + border-right-color: #393e5a; +} +.event_record_click>td>.popover.right>.popover-content { + margin-right: 0px; +} + @@ -752,7 +769,7 @@

Record Details

// if the search type not simple, then build the elasticsearch query if(search_type != 'simple'){ search_json = build_elasticsearch_query_json(); - + console.log(search_json) // get the elasticsearch query string var search_es = build_search_query(search_json); } @@ -815,10 +832,44 @@

Record Details

// used to add a tag to the search query tags function add_search_query_input_tag(tag , option="contains"){ - if(tag.split('|').length == 1 && lock_adding_tag == false && search_type == 'tagsinput'){ + if(tag.split('|').length == 1 && lock_adding_tag == false && (search_type == 'tagsinput' || search_type == 'simple' )){ lock_adding_tag = true; // lock adding new item tags input - search_query_tagsinput.tagsinput('add' , search_query_tagsinput.val().length + "|" + tag + "|" + option ); // trigger change to search query tag input + if(search_type == 'tagsinput') + search_query_tagsinput.tagsinput('add' , search_query_tagsinput.val().length + "|" + tag + "|" + option ); // trigger change to search query tag input + else if(search_type == 'simple'){ + var old_search_query_simple = $('#search_query_simple').val() + console.log(old_search_query_simple) + var search_json = {"AND" : []} + + + var index_of_first_colon = tag.indexOf(':') + var col = tag.substring(0 , index_of_first_colon); + var val = tag.substring( index_of_first_colon +1 ); + var colmn_name = (col == "") ? "string" : col; // if column field not specified + var column_value = val; + var v = {}; + + switch(option){ + case "not_contains" : colmn_name = '!~' + colmn_name; break; + case "contains" : colmn_name = '=~' + colmn_name; break; + case "equal" : colmn_name = '==' + colmn_name; break; + case "not_equal" : colmn_name = '!=' + colmn_name; break; + } + + v[colmn_name] = column_value; + search_json["AND"].push( {"string": old_search_query_simple} ); + search_json["AND"].push( v ); + console.log(search_json) + + var search_es = build_search_query(search_json); + console.log(search_es) + + $('#search_query_simple').val( search_es ) + change_sample_query_search(search_es) + search_button_click() + } + lock_adding_tag = false; // disable lock adding new item tags input return true; } else{ @@ -925,28 +976,54 @@

Record Details

'; - + if( records[i]['_source']['Data']['@timestamp'] != null && records[i]['_source']['Data']['@timestamp'].indexOf('.') != -1) timestamp = timestamp.substring(0 , records[i]['_source']['Data']['@timestamp'].indexOf('.')) + new_tr +='
'; - new_tr +=''; + new_tr += ''; + - - new_tr +=''; + new_tr += ''; var date_type = records[i]['_source']['data_type'] var imp_rec = record_important_fields[date_type]; @@ -969,7 +1046,7 @@

Record Details

  • Sort
  • \ \ '; - td_details += ' ' + html_field_options + imp_rec[j][0]+' ' + field_value; + td_details += ' ' + html_field_options + imp_rec[j][0]+' ' + escapeHtml(field_value); } new_tr +='
    '; @@ -1136,7 +1213,7 @@

    Record Details

    rebuild_records(ajax_records); // build data type drop-down list - console.log(r) + //console.log(r) var list = "" if(r['res_total'] != 0){ @@ -1309,7 +1386,8 @@

    Record Details

    else key = key + ":" - + console.log("key: " + key) + console.log("val: " + val) // build the query string based on the operator switch(operator){ case "==": @@ -1324,12 +1402,16 @@

    Record Details

    case "!~": query_string = "!" + key + '*'+val+'*'; break; + default: + query_string = val; + break; } + console.log("query_string: " + query_string) terms.push(query_string) } } - + console.log(terms) return terms.join( ' ' + k + ' ' ); } return ""; // if empty there is no results @@ -1500,7 +1582,6 @@

    Record Details

    $("#search_query_simple").hide() } - search_button_click() // enable the simple search tags $('.bootstrap-tagsinput').removeAttr('disabled') @@ -1564,13 +1645,64 @@

    Record Details

    // set the position of the popover $(document).on('click' , '#sample_search_confirm_rule' , function(e){ $('.popover').css("left" , "-230px"); - $('.popover.bottom>.arrow').css('left' , "94%") + $('.popover.bottom>.arrow').css('left' , "94%"); }) + + + + }) +// if item clicked to be added to the search query (using fa-search-plus) +$("#table_events,#dialog_content,#artifacts_list_container,#record_details_block").on('click' , 'a.table_timestamp_field_button' , function(){ + + var id = $(this).data("id") + console.log("id") + console.log(id) + $(this).popover({ + placement : 'right', + title : 'timestamp', + html : true, + content : $('#table_timestamp_field_popover_' + id).html(), + }) + + + // set the position of the popover + //$('.popover').css("width" , "90px"); + //$('.popover.right>.arrow').css('border-right-color' , "#393e5a") + +}); +$("#table_events,#dialog_content,#artifacts_list_container,#record_details_block").on('click' , 'button.table_timestamp_field_popover_submit' , function(){ + var id = $(this).data("id") + var min = parseInt($('#table_timestamp_field_popover_min_' + id).val()) + var timestamp = $('#table_timestamp_field_popover_timestamp_' + id).val() + if(min == 0){ + t = "Data.@timestamp:"+timestamp + } else { + if(timestamp.indexOf("Z") == -1) + timestamp += "Z" + + var timestamp_parsed = new Date(timestamp) + + var start_date = new Date(timestamp) + var end_date = new Date(timestamp) + + start_date.setMinutes(timestamp_parsed.getMinutes() - min); + end_date.setMinutes(timestamp_parsed.getMinutes() + min); + + var start_date_str = start_date.toISOString().replace(".000Z", "") + var end_date_str = end_date.toISOString().replace(".000Z", "") + + t = "Data.@timestamp:["+start_date_str+" TO "+end_date_str+"]" + } + + console.log(t) + add_search_query_input_tag(t , 'equal'); + timestamp_range_search = search_query_tagsinput.val().length - 1; +}) @@ -1596,6 +1728,23 @@

    Record Details

    $popover.popover('toggle'); } }); + + + + $('.table_timestamp_field_button[data-toggle="popover"]').each(function () { + $popover = $(this); + + if (!$popover.is(e.target) && + $popover.has(e.target).length === 0 && + $('.popover').has(e.target).length === 0) + { + $popover.popover('hide'); + } else { + //fixes issue described above + $popover.popover('toggle'); + } + }); + }) @@ -1681,6 +1830,9 @@

    Record Details

    // on click open advanced search button $('#advance_search_dialog_opener_btn').click(function(){ reset_search_terms_func() + + search_button_click() + $('.bootstrap-tagsinput').attr('disabled' , 'disabled') $('#advance_search_dialog').fadeIn() if($('#advance_search_dialog').is(":visible")){ @@ -1695,19 +1847,24 @@

    Record Details

    // on click the simple search query button $('#simple_search_btn').click(function(){ - reset_search_terms_func() + + if($(this).hasClass('btn-primary')){ + var sample_query_search_content = $('#sample_query_search_content').text() // enable simple search - + reset_search_terms_func() + $(this).removeClass('btn-primary') $(this).addClass('btn-success') $("#search_query_simple").show() - $("#search_query_simple").val("") + $("#search_query_simple").val(sample_query_search_content) $('.bootstrap-tagsinput').hide() search_type = 'simple' $('.bootstrap-tagsinput').attr('disabled' , 'disabled') + search_button_click() + } }) @@ -1724,6 +1881,8 @@

    Record Details

    // if reset search terms clicked, remove all searches from advanced search and simple search (input tags) $("#reset_search_terms").on('click' , function(){ reset_search_terms_func() + + search_button_click() }) @@ -1797,6 +1956,11 @@

    Record Details

    +// when a tag input modified, it will trigger this action to submit the modified data +$('#search_query').on('TagInputEdit' , function( event ){ +search_button_click(); + +}); @@ -2000,6 +2164,7 @@

    Record Details

    // if the search type not simple, then build the elasticsearch query if(search_type != 'simple'){ var search_json = build_elasticsearch_query_json(); + // get the elasticsearch query string var search_es = build_search_query(search_json); } diff --git a/app/templates/case/machines.html b/app/templates/case/machines.html index e3595297..53f19ae6 100644 --- a/app/templates/case/machines.html +++ b/app/templates/case/machines.html @@ -228,7 +228,7 @@

    Processing Machine Back
    - +


    @@ -1175,12 +1175,12 @@

    ' + open_badge + " " + field + ' ' + search_plus + '' + value + '
    ' + open_badge + " " + field + ' ' + search_plus + '' + escapeHtml(value) + '
    '+ timestamp.replace('T' , ' ') +' \ - \ - \ - \ + \ + \ +
    \ + \ +
    \ + \ + \ + \ + \ +
    \ +
    \ + \
    '+ records[i]['_source']['data_type'] +' \ - \ - \ - \ - '+ records[i]['_source']['data_type'] +' \ + \ + \ + \ + \ + '+ records[i]['_source']['machinename'] +' \ - \ - \ - \ - '+ records[i]['_source']['machinename'] +' \ + \ + \ + \ + \ + '+ td_details + '