├── .gitignore ├── MANIFEST.in ├── tests ├── reg_samples │ ├── issue22.hive │ ├── UNICODE_TESTS │ └── REORGANIZED_VALUES_TESTS ├── test_parse.py ├── fixtures.py ├── test_reorganized_values.py ├── test_large_data.py ├── test_issue22.py ├── test_unicode_names.py ├── test_issue26.py ├── test_parse_timestamp.py └── RegTester.py ├── testing ├── reg_samples │ ├── new_log_1 │ │ ├── SYSTEM │ │ ├── SYSTEM.LOG1 │ │ └── SYSTEM.LOG2 │ ├── new_log_2 │ │ ├── SYSTEM │ │ ├── SYSTEM.LOG1 │ │ └── SYSTEM.LOG2 │ ├── new_log_1_reversed │ │ ├── SYSTEM │ │ ├── SYSTEM.LOG1 │ │ └── SYSTEM.LOG2 │ ├── new_log_2_reversed │ │ ├── SYSTEM │ │ ├── SYSTEM.LOG1 │ │ └── SYSTEM.LOG2 │ └── new_log_1_reversed_and_invalid_checksum │ │ ├── SYSTEM │ │ ├── SYSTEM.LOG1 │ │ └── SYSTEM.LOG2 └── TransactionLogFiles.py ├── documentation ├── TheWindowsNTRegistryFileFormat.pdf └── WinReg.txt ├── samples ├── printall.py ├── list_nk_class_names.py ├── list_value_types.py ├── timeline.py ├── list_services.py ├── shelltypes.py ├── regfetch.py ├── test_dump.py ├── findkey.py ├── reg_export.py ├── copyclean.py ├── mount.py ├── amcache.py ├── forensicating.py └── regview.py ├── .travis.yml ├── setup.py ├── Registry ├── __init__.py ├── RegistryLog.py ├── SettingsParse.py └── Registry.py ├── CONTRIBUTORS.TXT ├── CHANGELOG.TXT ├── README.MD └── LICENSE.TXT /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | *# 4 | .#* 5 | *.pyc 6 | build/* -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.TXT CONTRIBUTORS.TXT LICENSE.TXT README.MD 2 | 3 | -------------------------------------------------------------------------------- /tests/reg_samples/issue22.hive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/tests/reg_samples/issue22.hive -------------------------------------------------------------------------------- /tests/reg_samples/UNICODE_TESTS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/tests/reg_samples/UNICODE_TESTS -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1/SYSTEM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1/SYSTEM -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2/SYSTEM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2/SYSTEM -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1/SYSTEM.LOG1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1/SYSTEM.LOG1 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1/SYSTEM.LOG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1/SYSTEM.LOG2 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2/SYSTEM.LOG1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2/SYSTEM.LOG1 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2/SYSTEM.LOG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2/SYSTEM.LOG2 -------------------------------------------------------------------------------- /tests/reg_samples/REORGANIZED_VALUES_TESTS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/tests/reg_samples/REORGANIZED_VALUES_TESTS -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed/SYSTEM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed/SYSTEM -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2_reversed/SYSTEM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2_reversed/SYSTEM -------------------------------------------------------------------------------- /documentation/TheWindowsNTRegistryFileFormat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/documentation/TheWindowsNTRegistryFileFormat.pdf -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed/SYSTEM.LOG1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed/SYSTEM.LOG1 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed/SYSTEM.LOG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed/SYSTEM.LOG2 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2_reversed/SYSTEM.LOG1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2_reversed/SYSTEM.LOG1 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_2_reversed/SYSTEM.LOG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_2_reversed/SYSTEM.LOG2 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM -------------------------------------------------------------------------------- /tests/test_parse.py: -------------------------------------------------------------------------------- 1 | import Registry 2 | 3 | from fixtures import * 4 | 5 | 6 | def test_file_type(hive): 7 | assert hive._regf.file_type() == Registry.RegistryParse.FileType.FILE_TYPE_PRIMARY 8 | -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM.LOG1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM.LOG1 -------------------------------------------------------------------------------- /testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM.LOG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williballenthin/python-registry/HEAD/testing/reg_samples/new_log_1_reversed_and_invalid_checksum/SYSTEM.LOG2 -------------------------------------------------------------------------------- /tests/fixtures.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | import pytest 4 | 5 | from Registry import Registry 6 | 7 | 8 | @pytest.fixture 9 | def hive(): 10 | path = os.path.join(os.path.dirname(__file__), "reg_samples", "issue22.hive") 11 | return Registry.Registry(path) 12 | -------------------------------------------------------------------------------- /samples/printall.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import unicode_literals 3 | 4 | import sys 5 | from Registry import * 6 | 7 | def rec(key, depth=0): 8 | print("\t" * depth + key.path()) 9 | for subkey in key.subkeys(): 10 | rec(subkey, depth + 1) 11 | 12 | reg = Registry.Registry(sys.argv[1]) 13 | rec(reg.root()) 14 | 15 | -------------------------------------------------------------------------------- /samples/list_nk_class_names.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import unicode_literals 3 | 4 | import sys 5 | from Registry import * 6 | 7 | def rec(key): 8 | 9 | if key._nkrecord.has_classname(): 10 | print("%s : %s" % (key.path(), key._nkrecord.classname())) 11 | 12 | for subkey in key.subkeys(): 13 | rec(subkey) 14 | 15 | reg = Registry.Registry(sys.argv[1]) 16 | rec(reg.root()) 17 | 18 | -------------------------------------------------------------------------------- /samples/list_value_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import unicode_literals 3 | 4 | import sys 5 | from Registry import * 6 | 7 | def rec(key): 8 | for value in key.values(): 9 | print("%s : %s : %s" % (key.path(), value.name(), value.value_type_str())) 10 | 11 | for subkey in key.subkeys(): 12 | rec(subkey) 13 | 14 | reg = Registry.Registry(sys.argv[1]) 15 | rec(reg.root()) 16 | 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | python: 2.7 7 | 8 | - os: linux 9 | python: 3.5 10 | 11 | install: 12 | - pip install --upgrade pip pytest pytest-cov 13 | - pip install -e . 14 | 15 | before_script: 16 | # stop the build if there are Python syntax errors or undefined names 17 | #- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics 18 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 19 | #- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 20 | 21 | script: 22 | #- find . -name \*.py -exec pep8 --ignore=E501 {} \; 23 | - py.test ./tests/ -v --cov=Registry 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | from Registry import _version_ 5 | 6 | setup(name='python-registry', 7 | version=_version_, 8 | description='Read access to Windows Registry files.', 9 | author='Willi Ballenthin', 10 | author_email='willi.ballenthin@gmail.com', 11 | url='https://github.com/williballenthin/python-registry', 12 | license='Apache License (2.0)', 13 | packages=['Registry'], 14 | classifiers = ["Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Operating System :: OS Independent", 17 | "License :: OSI Approved :: Apache Software License"], 18 | install_requires=['enum-compat'] 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /tests/test_reorganized_values.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import six 5 | import unittest 6 | from datetime import datetime 7 | 8 | from Registry import Registry 9 | 10 | class TestReorganizedValues(unittest.TestCase): 11 | def setUp(self): 12 | self.path = os.path.join(os.path.dirname(__file__), 'reg_samples', 'REORGANIZED_VALUES_TESTS') 13 | 14 | def test_access_bits(self): 15 | root = Registry.Registry(self.path).root() 16 | for key in root.subkeys(): 17 | assert(key._nkrecord.access_bits() == 2) 18 | 19 | def test_timestamp(self): 20 | timestamp = Registry.Registry(self.path)._regf.reorganized_timestamp() 21 | self.assertEqual(datetime(2016, 7, 17, 10, 40, 0, 41865), timestamp) 22 | 23 | # Run Tests 24 | if __name__ == '__main__': 25 | unittest.main(verbosity=2) 26 | -------------------------------------------------------------------------------- /tests/test_large_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import hashlib 4 | import os 5 | import unittest 6 | 7 | from Registry import Registry 8 | 9 | 10 | class TestRegistryLargeData(unittest.TestCase): 11 | def setUp(self): 12 | self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'testing', 'reg_samples', 'new_log_1', 'SYSTEM') 13 | 14 | def test_large_data(self): 15 | root = Registry.Registry(self.path).root() 16 | value = root.find_key(r'ControlSet001\Control\ProductOptions').value('ProductPolicy') 17 | data = value.raw_data() 18 | self.assertEqual(bytes, type(data)) 19 | self.assertEqual(23712, len(data)) 20 | self.assertEqual('7e96f10f77e1c44771d7045c24b94024', hashlib.md5(data).hexdigest()) 21 | 22 | # Run Tests 23 | if __name__ == '__main__': 24 | unittest.main(verbosity=2) 25 | -------------------------------------------------------------------------------- /Registry/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of python-registry. 2 | # 3 | # Copyright 2011 Will Ballenthin 4 | # while at Mandiant 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | _version_ = '1.3.1' 19 | 20 | __all__ = [ 21 | 'Registry', 22 | 'RegistryParse', 23 | 'RegistryLog', 24 | 'SettingsParse' 25 | ] 26 | -------------------------------------------------------------------------------- /tests/test_issue22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import unittest 5 | 6 | from Registry import Registry 7 | 8 | EXPECTED_MD5 = "0f8f1276f2a4fafc03b2a31775898800" 9 | REG_KEY = "TimeZoneKeyName" 10 | REG_EXPECTED_VALUE = u"W. Europe Standard Time" 11 | 12 | 13 | class TestIssue22(unittest.TestCase): 14 | def setUp(self): 15 | self.path = os.path.join(os.path.dirname(__file__), "reg_samples", "issue22.hive") 16 | 17 | import hashlib 18 | md5 = hashlib.md5() 19 | with open(self.path, 'rb') as file: 20 | md5.update(file.read()) 21 | 22 | self.assertEqual(md5.hexdigest(), EXPECTED_MD5, \ 23 | "Please use the SYSTEM hive with MD5 %s, got %s" % (EXPECTED_MD5, md5.hexdigest())) 24 | 25 | def test_regsz_value(self): 26 | reg = Registry.Registry(self.path) 27 | reg_key = reg.root() 28 | reg_val = reg_key.value(REG_KEY) 29 | self.assertEqual(reg_val.value(), REG_EXPECTED_VALUE, \ 30 | "Expected: %s Got: %s (length: %d)" % (REG_EXPECTED_VALUE, reg_val.value(), len(reg_val.value()))) 31 | 32 | 33 | if __name__ == "__main__": 34 | unittest.main(verbosity=2) 35 | -------------------------------------------------------------------------------- /CONTRIBUTORS.TXT: -------------------------------------------------------------------------------- 1 | Many thanks to the following contributors who submitted patches, bug reports, and great ideas: 2 | 3 | - @3ev0: implemented Python3 support 4 | - @hiddenillusion: provided example scripts 5 | - @reidrac: submitted Debian packaging support 6 | - @jallmantalbot: submitting a patch for better detection and handling of malformed Unicode 7 | - @jallmantalbot: submitting a patch for a more correct Unicode code page for key/value names 8 | - Joel Schneider: suggesting that "Windows-1252" is the correct Unicode code page for NKRecord and VKRecord fields. 9 | - @woanware: 10 | - submitting a patch for hive detection 11 | - researching and submitting a patch for value types on Vista+ and RegDateTime support 12 | - @tonycpsu: 13 | - Add BCD hive type 14 | - @NiKiZe: 15 | - REGFBlock Checksum and validation 16 | - RegTester updates 17 | - @BridgeyTheGeek 18 | - identify issue in data size parsing 19 | - @sbv-csis 20 | - identify path cycle issue and suggest fix 21 | - @msuhanov 22 | - transaction log files (new format) support 23 | - various improvements to the primary files parser 24 | - John Ormonde and Tim Heaney 25 | - identifing and providing a fix for regf file type detection 26 | -------------------------------------------------------------------------------- /CHANGELOG.TXT: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | - [DEPRECATED] records() in HBINBlock, use the more correct cells() instead, by @NiKiZe 3 | - fix bug in parsing of resident values with length zero, reported and patched by @BridgeyTheGeek 4 | - fix handling of path cycles, reported and patched by @sbv-csis 5 | 6 | 1.1.0 7 | - add raw_data method 8 | - fix testing scripts and methods 9 | - add amcache.hve parsing script 10 | - add script for mounting hive as file system via FUSE 11 | - many fixes, including checksum calculations, by @NiKiZe 12 | - fixes to RegDateTime parsing by @woanware 13 | 14 | 1.0.4 15 | - correctly handle value types on Vista+ that require a DEVPROP_MASK_TYPE mask, thanks to @woanware 16 | - support the new RegDateTime value type, used for instance in some USBSTOR values, thanks to @woanware 17 | 18 | 1.0.3 19 | - use setuptools over distutils 20 | 21 | 1.0.2 22 | - can now fetch hive name from a Registry hive, thanks to @woanware, @jallmantalbot 23 | - can now guess the Registry hive type (NTUSER, USRCLASS, etc) from the hive name, thanks to @woanware 24 | - better handling of key/value names through use of Windows-1252 encoding, thanks to @jallmantalbot and Joel Schnieder 25 | 26 | 1.0.1 27 | - better detection and handling of malformed Unicode, thanks to @jallmantalbot 28 | 29 | 1.0.0 30 | - implemented Python3 support thanks to @3ev0 31 | -------------------------------------------------------------------------------- /tests/test_unicode_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import six 5 | import unittest 6 | 7 | from Registry import Registry 8 | 9 | 10 | class TestRegistryUnicode(unittest.TestCase): 11 | def setUp(self): 12 | self.path = os.path.join(os.path.dirname(__file__), 'reg_samples', 'UNICODE_TESTS') 13 | 14 | @classmethod 15 | def is_correct_string(cls, data): 16 | return (isinstance(data, six.text_type) 17 | and (data == u"" 18 | or data.startswith(u"ASCII") 19 | or data.startswith(u'UNICODE_JUMBLE_{H~\u2591\xf4\xab}'))) 20 | 21 | def test_decoding(self): 22 | root = Registry.Registry(self.path).root() 23 | for key in root.subkeys(): 24 | self.assertTrue(self.is_correct_string(key.name()), key.name()) 25 | for value in key.values(): 26 | self.assertTrue(self.is_correct_string(value.name()), value.name()) 27 | val = value.value() 28 | if isinstance(val, list): 29 | for item in val: 30 | self.assertTrue(self.is_correct_string(item), item) 31 | else: 32 | self.assertTrue(self.is_correct_string(val), val) 33 | 34 | # Run Tests 35 | if __name__ == '__main__': 36 | unittest.main(verbosity=2) 37 | -------------------------------------------------------------------------------- /tests/test_issue26.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | 5 | from Registry.RegistryParse import decode_utf16le 6 | 7 | 8 | # here are the test cases written out with their explanations: 9 | # 10 | # 61 00 --> 61 --> "a" 11 | # 61 62 --> 61 62 --> "扡" 12 | # 61 62 00 00 --> 61 62 --> "扡" 13 | # 61 00 61 00 00 00 --> 61 00 61 00 --> "aa" 14 | # 61 00 61 62 00 00 --> 61 00 61 62 --> "a扡" 15 | # 61 00 61 00 00 --> 61 00 61 00 --> "aa" 16 | # 61 00 61 62 00 --> 61 00 61 62 --> "a扡" 17 | 18 | 19 | class TestIssue26(unittest.TestCase): 20 | def test_utf16le_kanji_with_nulls(self): 21 | self.assertEqual(decode_utf16le(b"\x61\x00"), u"a") 22 | self.assertEqual(decode_utf16le(b"\x61\x62"), u"扡") 23 | self.assertEqual(decode_utf16le(b"\x61\x62\x00\x00"), u"扡") 24 | self.assertEqual(decode_utf16le(b"\x61\x00\x61\x00\x00\x00"), u"aa") 25 | self.assertEqual(decode_utf16le(b"\x61\x00\x61\x62\x00\x00"), u"a扡") 26 | self.assertEqual(decode_utf16le(b"\x61\x00\x61\x00\x00"), u"aa") 27 | self.assertEqual(decode_utf16le(b"\x61\x00\x61\x62\x00"), u"a扡") 28 | self.assertEqual(decode_utf16le(b"W\x00.\x00 \x00E\x00u\x00r\x00o\x00p\x00e\x00 \x00S\x00t\x00a\x00n\x00d\x00a\x00r\x00d\x00 \x00T\x00i\x00m\x00e\x00\x00\x00"), \ 29 | u"W. Europe Standard Time") 30 | 31 | 32 | if __name__ == "__main__": 33 | unittest.main(verbosity=2) 34 | -------------------------------------------------------------------------------- /samples/timeline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import os 7 | import calendar 8 | 9 | import argparse 10 | from Registry import Registry 11 | 12 | 13 | def guess_hive_name(path): 14 | for i in range(len(path)): 15 | rpath = path[-(i + 1):].lower() 16 | for guess in ["ntuser", "software", "system", 17 | "userdiff", "sam", "default"]: 18 | if guess in rpath: 19 | return guess.upper() 20 | 21 | 22 | def main(): 23 | parser = argparse.ArgumentParser( 24 | description="Timeline Windows Registry key timestamps") 25 | parser.add_argument("--bodyfile", action="store_true", 26 | help="Output in the Bodyfile 3 format") 27 | parser.add_argument("registry_hives", type=str, nargs="+", 28 | help="Path to the Windows Registry hive to process") 29 | args = parser.parse_args() 30 | 31 | def rec(key, visitor): 32 | try: 33 | visitor(key.timestamp(), key.path()) 34 | except ValueError: 35 | pass 36 | for subkey in key.subkeys(): 37 | rec(subkey, visitor) 38 | 39 | for filename in args.registry_hives: 40 | basename = os.path.basename(filename) 41 | reg = Registry.Registry(filename) 42 | 43 | if args.bodyfile: 44 | def visitor(timestamp, path): 45 | try: 46 | print("0|[Registry %s] %s|0|0|0|0|0|%s|0|0|0" % \ 47 | (basename, path, int(calendar.timegm(timestamp.timetuple())))) 48 | except UnicodeDecodeError: 49 | pass 50 | 51 | rec(reg.root(), visitor) 52 | else: 53 | items = [] 54 | rec(reg.root(), lambda a, b: items.append((a, b))) 55 | for i in sorted(items, key=lambda x: x[0]): 56 | print("%s\t[Registry %s]%s" % (i[0], basename, i[1])) 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /samples/list_services.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011 Will Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | from __future__ import unicode_literals 21 | 22 | import sys 23 | from Registry import Registry 24 | 25 | 26 | def usage(): 27 | return " USAGE:\n\t%s " % sys.argv[0] 28 | 29 | 30 | def main(): 31 | if len(sys.argv) != 2: 32 | print(usage()) 33 | sys.exit(-1) 34 | 35 | registry = Registry.Registry(sys.argv[1]) 36 | select = registry.open("Select") 37 | current = select.value("Current").value() 38 | services = registry.open("ControlSet00%d\\Services" % (current)) 39 | for service in services.subkeys(): 40 | try: 41 | display_name = service.value("DisplayName").value() 42 | except: 43 | display_name = "???" 44 | 45 | try: 46 | description = service.value("Description").value() 47 | except: 48 | description = "???" 49 | 50 | try: 51 | image_path = service.value("ImagePath").value() 52 | except: 53 | image_path = "???" 54 | 55 | try: 56 | dll = service.subkey("Parameters").value("ServiceDll").value() 57 | except: 58 | dll = "???" 59 | print('%s, %s, "%s", "%s", "%s"' % (service.name(), display_name, image_path, dll, description)) 60 | 61 | 62 | if __name__ == '__main__': 63 | main() -------------------------------------------------------------------------------- /samples/shelltypes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011 Will Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | from __future__ import print_function 21 | from __future__ import unicode_literals 22 | 23 | import re, sys, datetime, time 24 | from Registry import Registry 25 | 26 | def get_shellbags(registry): 27 | shellbags = [] 28 | # TODO try both Shell and ShellNoRoam 29 | try: 30 | # Windows XP NTUSER.DAT location 31 | windows = registry.open("Software\\Microsoft\\Windows\\ShellNoRoam") 32 | except Registry.RegistryKeyNotFoundException: 33 | try: 34 | # Windows 7 UsrClass.dat location 35 | windows = registry.open("Local Settings\\Software\\Microsoft\\Windows\\Shell") 36 | except Registry.RegistryKeyNotFoundException: 37 | print("Unable to find shellbag key.") 38 | sys.exit(-1) 39 | bagmru = windows.subkey("BagMRU") 40 | 41 | def shellbag_rec(key, bag_prefix): 42 | for value in key.values(): 43 | if not re.match(r"\d+", value.name()): 44 | continue 45 | mru_type = ord(value.value()[2:3]) 46 | print("%s %s" % (hex(mru_type), bag_prefix + "\\" + value.name())) 47 | 48 | shellbag_rec(key.subkey(value.name()), bag_prefix + "\\" + value.name()) 49 | 50 | shellbag_rec(bagmru, "") 51 | 52 | def usage(): 53 | return " USAGE:\n\t%s " % sys.argv[0] 54 | 55 | if __name__ == '__main__': 56 | if len(sys.argv) != 2: 57 | print(usage()) 58 | sys.exit(-1) 59 | 60 | get_shellbags(Registry.Registry(sys.argv[1])) 61 | -------------------------------------------------------------------------------- /samples/regfetch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011 Will Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | from __future__ import unicode_literals 21 | 22 | import os 23 | import sys 24 | from Registry import Registry 25 | 26 | 27 | def usage(): 28 | return " USAGE:\n\t%s []" % sys.argv[0] 29 | 30 | 31 | if __name__ == '__main__': 32 | if len(sys.argv) != 4 and len(sys.argv) != 3: 33 | print(usage()) 34 | sys.exit(-1) 35 | 36 | # this is wild, on Windows, redirection of the stream may be in text mode 37 | # so line ending characters may quietly be inserted into binary data! 38 | if sys.platform == "win32": 39 | import msvcrt 40 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 41 | 42 | registry = Registry.Registry(sys.argv[1]) 43 | 44 | try: 45 | if sys.argv[2].startswith(registry.root().name()): 46 | key = registry.open(sys.argv[2].partition("\\")[2]) 47 | else: 48 | key = registry.open(sys.argv[2]) 49 | except Registry.RegistryKeyNotFoundException: 50 | print("Specified key not found") 51 | sys.exit(-1) 52 | 53 | if len(sys.argv) == 4: 54 | if sys.argv[3] == "default": 55 | sys.argv[3] = "(default)" 56 | 57 | value = key.value(sys.argv[3]).value() 58 | if isinstance(value, str): 59 | sys.stdout.write(value) 60 | elif isinstance(value, bytes): 61 | sys.stdout.buffer.write(value) 62 | else: 63 | raise ValueError("unexpected value type: " + str(type(value))) 64 | if len(sys.argv) == 3: 65 | print("Subkeys") 66 | for subkey in key.subkeys(): 67 | print(" - {}".format(subkey.name())) 68 | 69 | print("Values") 70 | for value in key.values(): 71 | print(" - {}".format(value.name())) 72 | 73 | -------------------------------------------------------------------------------- /samples/test_dump.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import unicode_literals 3 | 4 | import sys 5 | from Registry import * 6 | 7 | 8 | def format_total_keys(total_keys): 9 | return "total_keys|{total_keys}".format(total_keys=total_keys) 10 | 11 | 12 | def format_total_values(total_values): 13 | return "total_values|{total_values}".format(total_values=total_values) 14 | 15 | 16 | def format_key(key): 17 | return "key|{path}|{ts}".format( 18 | path=key.path(), 19 | ts=key.timestamp().isoformat(chr(ord("T"))) + "Z") 20 | 21 | 22 | def format_value(key, value): 23 | try: 24 | h = " ".join(["%02X" % (ord(c)) for c in value.raw_data()]) 25 | except RegistryParse.UnknownTypeException: 26 | h = "UNKNOWN_TYPE_SO_UNKNOWN_DATA" 27 | return "value|{path}|{name}|{type}|{hex}".format( 28 | path=key.path(), 29 | name=value.name(), 30 | type=value.value_type(), 31 | hex=h) 32 | 33 | 34 | def handle_key(key): 35 | print(format_key(key)) 36 | 37 | 38 | def handle_value(key, value): 39 | print(format_value(key, value)) 40 | 41 | 42 | class RegistryExplorer(object): 43 | def __init__(self, root): 44 | self._root = root 45 | 46 | def handle_pre(self): 47 | pass 48 | 49 | def handle_key(self, key): 50 | raise NotImplementedException() 51 | 52 | def handle_value(self, key, value): 53 | raise NotImplementedException() 54 | 55 | def handle_post(self): 56 | pass 57 | 58 | def _rec(self, key): 59 | self.handle_key(key) 60 | for value in key.values(): 61 | self.handle_value(key, value) 62 | map(self._rec, key.subkeys()) 63 | 64 | def go(self): 65 | self.handle_pre() 66 | self._rec(self._root) 67 | self.handle_post() 68 | 69 | 70 | class TestDumper(RegistryExplorer): 71 | def __init__(self, *args, **kwargs): 72 | super(TestDumper, self).__init__(*args, **kwargs) 73 | self._key_count = 0 74 | self._value_count = 0 75 | 76 | def handle_key(self, key): 77 | self._key_count += 1 78 | try: 79 | print(format_key(key)) 80 | except UnicodeEncodeError: 81 | pass 82 | except UnicodeDecodeError: 83 | pass 84 | 85 | def handle_value(self, key, value): 86 | self._value_count += 1 87 | try: 88 | print(format_value(key, value)) 89 | except UnicodeEncodeError: 90 | pass 91 | except UnicodeDecodeError: 92 | pass 93 | 94 | 95 | def handle_post(self): 96 | print(format_total_keys(self._key_count)) 97 | print(format_total_values(self._value_count)) 98 | 99 | 100 | reg = Registry.Registry(sys.argv[1]) 101 | TestDumper(reg.root()).go() 102 | -------------------------------------------------------------------------------- /samples/findkey.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011 Will Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | # Find all Registry paths, value names, and values that 21 | # contain the given string. 22 | # 23 | # python findkey.py 24 | # 25 | 26 | from __future__ import print_function 27 | from __future__ import unicode_literals 28 | 29 | import sys 30 | 31 | import argparse 32 | from Registry import Registry 33 | 34 | 35 | def main(): 36 | parser = argparse.ArgumentParser( 37 | description="Search for a string in a Windows Registry hive") 38 | parser.add_argument("registry_hive", type=str, 39 | help="Path to the Windows Registry hive to process") 40 | parser.add_argument("query", type=str, 41 | help="Query for which to search") 42 | parser.add_argument("-i", action="store_true", dest="case_insensitive", 43 | help="Query for which to search") 44 | args = parser.parse_args() 45 | 46 | paths = [] 47 | value_names = [] 48 | values = [] 49 | 50 | 51 | def rec(key, depth, needle): 52 | for value in key.values(): 53 | if (args.case_insensitive and needle in value.name().lower()) or needle in value.name(): 54 | value_names.append((key.path(), value.name())) 55 | sys.stdout.write("n") 56 | sys.stdout.flush() 57 | try: 58 | if (args.case_insensitive and needle in str(value.value()).lower()) or needle in str(value.value()): 59 | values.append((key.path(), value.name())) 60 | sys.stdout.write("v") 61 | sys.stdout.flush() 62 | except UnicodeEncodeError: 63 | pass 64 | except UnicodeDecodeError: 65 | pass 66 | 67 | for subkey in key.subkeys(): 68 | if needle in subkey.name(): 69 | paths.append(subkey.path()) 70 | sys.stdout.write("p") 71 | sys.stdout.flush() 72 | rec(subkey, depth + 1, needle) 73 | 74 | reg = Registry.Registry(args.registry_hive) 75 | needle = args.query 76 | if args.case_insensitive: 77 | needle = needle.lower() 78 | 79 | rec(reg.root(), 0, needle) 80 | print("") 81 | 82 | print("[Paths]") 83 | for path in paths: 84 | print(" - %s" % (path)) 85 | if len(paths) == 0: 86 | print(" (none)") 87 | print("") 88 | 89 | print("[Value Names]") 90 | for pair in value_names: 91 | print(" - %s : %s" % (pair[0], pair[1])) 92 | if len(value_names) == 0: 93 | print(" (none)") 94 | print("") 95 | 96 | print("[Values]") 97 | for pair in values: 98 | print(" - %s : %s" % (pair[0], pair[1])) 99 | if len(values) == 0: 100 | print(" (none)") 101 | print("") 102 | 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 2 | python-registry 3 | =============== 4 | 5 | Introduction 6 | ------------ 7 | python-registry is a pure Python library that provides read-only 8 | access to Windows NT Registry files. 9 | These include NTUSER.DAT, userdiff, and SAM. The interface is two-fold: 10 | a high-level interface suitable for most tasks, and a low level 11 | set of parsing objects and methods which may be used for advanced 12 | study of the Windows NT Registry. This library is compatible with 13 | Windows,Linux, and MacOS, making it accessible and functional 14 | across all major operating systems. 15 | 16 | Usage 17 | ----- 18 | 19 | Most users will find the `Registry.Registry` module most appropriate. 20 | The module exposes three classes: the `Registry`, the `RegistryKey`, 21 | and the `RegistryValue`. The `Registry` organizes parsing and access 22 | to the Windows Registry file. The `RegistryKey` is a convenient 23 | interface into the tree-like structure of the Windows NT Registry. 24 | A `RegistryKey` may have children `RegistryKeys`, and may also have 25 | values associated with it. A `RegistryValue` can be thought of as 26 | the tuple (name, datatype, value) associated with a `RegistryKey`. 27 | python-registry supports all major datatypes, such as `RegSZ`, 28 | `RegDWord`, and `RegBin`. 29 | 30 | To open a Windows Registry file, its this easy: 31 | 32 | 33 | import sys 34 | from Registry import Registry 35 | 36 | reg = Registry.Registry(sys.argv[1]) 37 | 38 | 39 | Print all keys in a Registry 40 | 41 | 42 | def rec(key, depth=0): 43 | print "\t" * depth + key.path() 44 | 45 | for subkey in key.subkeys(): 46 | rec(subkey, depth + 1) 47 | 48 | rec(reg.root()) 49 | 50 | 51 | Find a key and print all string values 52 | 53 | 54 | try: 55 | key = reg.open("SOFTWARE\\Microsoft\\Windows\\Current Version\\Run") 56 | except Registry.RegistryKeyNotFoundException: 57 | print "Couldn't find Run key. Exiting..." 58 | sys.exit(-1) 59 | 60 | for value in [v for v in key.values() \ 61 | if v.value_type() == Registry.RegSZ or \ 62 | v.value_type() == Registry.RegExpandSZ]: 63 | print "%s: %s" % (value.name(), value.value()) 64 | 65 | 66 | Advanced users who wish to study the structure of the Windows 67 | Registry may find the `Registry.RegistryParse` module useful. 68 | This module implements all known structures of the Windows Registry. 69 | 70 | Wanted 71 | ------ 72 | - Bug reports. 73 | - Feedback. 74 | 75 | python-registry was originally developed to scratch one of 76 | the author's itches. Now he hopes it can be of use to 77 | someone outside of his lonely NYC apartment. 78 | 79 | 80 | License 81 | ------- 82 | As of version 0.2.0, python-registry is released under the Apache 2.0 license. 83 | Before that, python-registry was released under the GPLv3. 84 | 85 | 86 | Sources 87 | ------- 88 | Nearly all structure definitions used in python-registry 89 | came from one of two sources: 90 | 1) WinReg.txt, by B.H., which may be accessed at: 91 | http://pogostick.net/~pnh/ntpasswd/WinReg.txt 92 | 2) The Windows NT Registry File Format version 0.4, by 93 | Timothy D. Morgan, which may be accessed at: 94 | https://docs.google.com/viewer?url=http%3A%2F%2Fsentinelchicken.com%2Fdata%2FTheWindowsNTRegistryFileFormat.pdf 95 | Copies of these resources are included in the 96 | `documentation/` directory of the python-registry source. 97 | 98 | 99 | The source directory for python-registry contains a `sample/` 100 | subdirectory that contains small programs that use python-registry. 101 | For example, `regview.py` is a read-only clone of Microsoft Window's 102 | Regedit, implemented in a few hundred lines. 103 | -------------------------------------------------------------------------------- /testing/TransactionLogFiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys 5 | from StringIO import StringIO 6 | from Registry import Registry, RegistryLog 7 | 8 | def print_test_testAAAA_testBBBB(reg): 9 | try: 10 | reg.root().find_key('testAAAA') 11 | print('testAAAA found!') 12 | except Exception: 13 | print('testAAAA not found!') 14 | 15 | try: 16 | reg.root().find_key('testBBBB') 17 | print('testBBBB found!') 18 | except Exception: 19 | print('testBBBB not found!') 20 | 21 | def print_test_fdenytsconnections(reg): 22 | val = reg.root().find_key('ControlSet001\\Control\\Terminal Server').value('fDenyTSConnections').value() 23 | print('fDenyTSConnections = ' + str(val)) 24 | 25 | if len(sys.argv) != 4: 26 | print('You need to specify 3 files to test!') 27 | sys.exit(255) 28 | 29 | primary_filepath = sys.argv[1] 30 | log1_filepath = sys.argv[2] 31 | log2_filepath = sys.argv[3] 32 | 33 | primary = StringIO() 34 | with open(primary_filepath, 'rb') as f: 35 | primary.write(f.read()) 36 | 37 | primary.seek(0) 38 | log1 = RegistryLog.RegistryLog(primary, log1_filepath) 39 | primary.seek(0) 40 | log2 = RegistryLog.RegistryLog(primary, log2_filepath) 41 | primary.seek(0) 42 | 43 | reg = Registry.Registry(primary) 44 | 45 | # Run the tests for the first time 46 | print_test_testAAAA_testBBBB(reg) 47 | print_test_fdenytsconnections(reg) 48 | 49 | r = reg._regf.recovery_required() 50 | if not (r.recover_header or r.recover_data): 51 | print('Recovery not required!') 52 | sys.exit(0) 53 | 54 | if not r.recover_header: 55 | print('Current hbins size: ' + str(reg._regf.hbins_size())) 56 | 57 | print('Header recovery: ' + str(r.recover_header)) 58 | print('Data recovery: ' + str(r.recover_data)) 59 | 60 | apply_first = False 61 | apply_second = False 62 | logs_count = 0 63 | 64 | if log1.is_eligible_log(): 65 | logs_count += 1 66 | apply_first = True 67 | if log2.is_eligible_log(): 68 | logs_count += 1 69 | apply_second = True 70 | 71 | print('Eligible log files count: ' + str(logs_count)) 72 | 73 | if logs_count == 1: 74 | if apply_first: 75 | print('Applying the first log') 76 | seqnum = log1.recover_hive() 77 | print('Finishing with sequence number = ' + str(seqnum)) 78 | elif apply_second: 79 | print('Applying the second log') 80 | seqnum = log2.recover_hive() 81 | print('Finishing with sequence number = ' + str(seqnum)) 82 | else: 83 | print('Bug!') 84 | elif logs_count == 2: 85 | first_then_second = log1.is_starting_log(log2) 86 | if (not r.recover_header) and first_then_second: 87 | print('Applying the first log') 88 | seqnum = log1.recover_hive() 89 | print('Finishing with sequence number = ' + str(seqnum)) 90 | print('Applying the second log') 91 | seqnum = log2.recover_hive_continue(seqnum + 1) 92 | print('Finishing with sequence number = ' + str(seqnum)) 93 | elif (not r.recover_header): 94 | print('Applying the second log') 95 | seqnum = log2.recover_hive() 96 | print('Finishing with sequence number = ' + str(seqnum)) 97 | print('Applying the first log') 98 | seqnum = log1.recover_hive_continue(seqnum + 1) 99 | print('Finishing with sequence number = ' + str(seqnum)) 100 | else: 101 | if first_then_second: 102 | print('Applying the second log') 103 | seqnum = log2.recover_hive() 104 | print('Finishing with sequence number = ' + str(seqnum)) 105 | else: 106 | print('Applying the first log') 107 | seqnum = log1.recover_hive() 108 | print('Finishing with sequence number = ' + str(seqnum)) 109 | 110 | primary.seek(0) 111 | reg = Registry.Registry(primary) 112 | 113 | # Run the tests again 114 | print_test_testAAAA_testBBBB(reg) 115 | print_test_fdenytsconnections(reg) 116 | 117 | # Print the final values of the updated REGF block 118 | print('hive_sequence1 = ' + str(reg._regf.hive_sequence1())) 119 | print('hive_sequence2 = ' + str(reg._regf.hive_sequence2())) 120 | 121 | r = reg._regf.recovery_required() 122 | if not (r.recover_header or r.recover_data): 123 | print('Recovery not required!') 124 | print('Current hbins size: ' + str(reg._regf.hbins_size())) 125 | 126 | else: 127 | print('Recovery is required! Bug!') 128 | print('REGF block checksum written: ' + str(reg._regf.checksum())) 129 | print('REGF block checksum calculated: ' + str(reg._regf.calculate_checksum())) 130 | -------------------------------------------------------------------------------- /tests/test_parse_timestamp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from datetime import datetime 5 | from decimal import ROUND_HALF_EVEN 6 | 7 | import pytest 8 | from Registry.RegistryParse import parse_timestamp, parse_windows_timestamp 9 | 10 | 11 | def test_parse_windows_timestamp(): 12 | tests = { 13 | # Rounding error in old floating point calculation, which gave 2016-7-14 10:40:00.041864) 14 | 131132256000418650: datetime(2016, 7, 17, 10, 40, 0, 41865), 15 | # This actually rounds up to microseconds=041866 using 64-bit floating point arithmetic 16 | 131132256000418654: datetime(2016, 7, 17, 10, 40, 0, 41865), 17 | # Unix epoch 18 | 116444736000000000: datetime(1970, 1, 1, 0, 0, 0, 0), 19 | # Rounding up to next second 20 | 116444736009999996: datetime(1970, 1, 1, 0, 0, 1, 0), 21 | # Rounding the last digit which doesn't fit into datetime.microseconds 22 | 116444736000000006: datetime(1970, 1, 1, 0, 0, 0, 1), 23 | # round up to even 24 | 116444736000000015: datetime(1970, 1, 1, 0, 0, 0, 2), 25 | # round down to even 26 | 116444736000000005: datetime(1970, 1, 1, 0, 0, 0, 0), 27 | } 28 | 29 | for timestamp, expected in tests.items(): 30 | actual = parse_windows_timestamp(timestamp) 31 | assert expected == actual 32 | 33 | 34 | # HFS timestamps are seconds + 65535ths of seconds since 1 Jan 1904 35 | HFS_EPOCH = datetime(1904, 1, 1) 36 | HFS_RESOLUTION = 65535 37 | 38 | # Mac absolute timestamps are seconds since 1 Jan 2001 39 | MAC_EPOCH = datetime(2001, 1, 1) 40 | MAC_RESOLUTION = 1 41 | 42 | # NTFS timestamps are hundreds of nanoseconds since 1 Jan 1601 43 | NTFS_EPOCH = datetime(1601, 1, 1) 44 | NTFS_RESOLUTION = int(1e7) 45 | 46 | # UNIX timestamps are seconds since 1 Jan 1970 47 | UNIX_EPOCH = datetime(1970, 1, 1) 48 | UNIX_RESOLUTION = 1 49 | 50 | HFS_TESTS = { 51 | # least HFS timestamp 52 | 0: datetime(1904, 1, 1, 0, 0, 0, 0), 53 | # least nonzero HFS timestamp 54 | 1: datetime(1904, 1, 1, 0, 0, 0, 15), 55 | 65535: datetime(1904, 1, 1, 0, 0, 1), 56 | 136496402790465: datetime(1969, 12, 31, 11, 59, 59), 57 | 136496402856000: datetime(1969, 12, 31, 12, 0, 0), 58 | 136499233968000: datetime(1970, 1, 1, 0, 0, 0), 59 | 233401598681175: datetime(2016, 11, 8, 20, 1, 45), 60 | 233401598707098: datetime(2016, 11, 8, 20, 1, 45, 395560), 61 | # greatest "low" timestamp 62 | 281470681743360: datetime(2040, 2, 6, 6, 28, 16), 63 | 514872280424535: datetime(2152, 12, 16, 2, 30, 1), 64 | # greatest HFS timestamp representable as a datetime 65 | 16743219016895999: datetime(9999, 12, 31, 23, 59, 59, 999985) 66 | } 67 | 68 | MAC_TESTS = { 69 | # least Mac absolute timestamp 70 | 0: datetime(2001, 1, 1, 0, 0, 0), 71 | # least nonzero Mac absolute timestamp 72 | 1: datetime(2001, 1, 1, 0, 0, 1), 73 | 307828812: datetime(2010, 10, 3, 20, 0, 12), 74 | } 75 | 76 | NTFS_TESTS = { 77 | # least NTFS timestamp 78 | 0: datetime(1601, 1, 1, 0, 0, 0, 0), 79 | # least nonzero NTFS timestamp 80 | 1: datetime(1601, 1, 1, 0, 0, 0, 0), 81 | # least nonzero NTFS timestamp which doesn't round to the epoch 82 | 10: datetime(1601, 1, 1, 0, 0, 0, 1), 83 | 131467743999999999: datetime(2017, 8, 9, 17, 46, 40), 84 | # greatest NTFS timestamp representable as a datetime 85 | 2650467743999999994: datetime(9999, 12, 31, 23, 59, 59, 999999) 86 | } 87 | 88 | UNIX_TESTS = { 89 | # least signed 32-bit UNIX timestamp 90 | -2147483648: datetime(1901, 12, 13, 20, 45, 52), 91 | # least nonnegative UNIX timestamp 92 | 0: datetime(1970, 1, 1, 0, 0, 0), 93 | # least nonzero UNIX timestamp 94 | 1: datetime(1970, 1, 1, 0, 0, 1), 95 | 1516799714: datetime(2018, 1, 24, 13, 15, 14), 96 | # greatest signed 32-bit UNIX timestamp 97 | 2147483647: datetime(2038, 1, 19, 3, 14, 7) 98 | } 99 | 100 | TEST_SETS = [ 101 | (HFS_TESTS, HFS_RESOLUTION, HFS_EPOCH, ROUND_HALF_EVEN), 102 | (MAC_TESTS, MAC_RESOLUTION, MAC_EPOCH, ROUND_HALF_EVEN), 103 | (NTFS_TESTS, NTFS_RESOLUTION, NTFS_EPOCH, ROUND_HALF_EVEN), 104 | (UNIX_TESTS, UNIX_RESOLUTION, UNIX_EPOCH, ROUND_HALF_EVEN) 105 | ] 106 | 107 | TEST_CONFIGS = [] 108 | for tests, resolution, epoch, mode in TEST_SETS: 109 | for tics, expected in tests.items(): 110 | TEST_CONFIGS.append((expected, tics, resolution, epoch, mode)) 111 | 112 | 113 | @pytest.mark.parametrize('expected,tics,resolution,epoch,mode', TEST_CONFIGS) 114 | def test_parse_timestamp(expected, tics, resolution, epoch, mode): 115 | actual = parse_timestamp(tics, resolution, epoch, mode=mode) 116 | assert expected == actual 117 | -------------------------------------------------------------------------------- /samples/reg_export.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2015 Willi Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | from __future__ import unicode_literals 21 | 22 | import sys 23 | from Registry import Registry 24 | 25 | stdout = sys.stdout 26 | if hasattr(stdout, 'buffer'): 27 | stdout = stdout.buffer 28 | 29 | def usage(): 30 | return " USAGE:\n\t%s []" % sys.argv[0] 31 | 32 | 33 | 34 | def reg_format_header(): 35 | """ 36 | @rtype: byte string 37 | """ 38 | return u"\ufeffWindows Registry Editor Version 5.00\r\n\r\n".encode("utf-16le") 39 | 40 | 41 | def reg_format_value_sz(value): 42 | """ 43 | @rtype: str 44 | """ 45 | return "\"{value}\"".format(value=value.value()) 46 | 47 | 48 | def reg_format_value_dword(value): 49 | """ 50 | @rtype: str 51 | """ 52 | return "dword:%08x" % (value.value()) 53 | 54 | 55 | def reg_format_value_bin(value): 56 | """ 57 | result should look like the following (after the '='): 58 | "ProductLocalizedName"=hex:40,00,25,00,50,00,72,00,6f,00,67,00,72,00,61,00,\ 59 | 6d,00,46,00,69,00,6c,00,65,00,73,00,25,00,5c,00,57,00,69,00,6e,00,64,00,6f,\ 60 | 00,77,00,73,00,20,00,44,00,65,00,66,00,65,00,6e,00,64,00,65,00,72,00,5c,00,\ 61 | 45,00,70,00,70,00,4d,00,61,00,6e,00,69,00,66,00,65,00,73,00,74,00,2e,00,64,\ 62 | 00,6c,00,6c,00,2c,00,2d,00,31,00,30,00,30,00,30,00,00,00 63 | 64 | so we: 65 | - format into one big line of hex 66 | - search for places to split, at about 80 chars or less 67 | - split, with the former receiving a backslash, and the latter getting the 68 | prefixed whitespace 69 | 70 | if the type of value is RegBin, then we use the type prefix "hex:", 71 | otherwise, the type prefix is "hex(%d):" where %d is the value_type constant. 72 | eg. RegExpandSZ is "hex(3)" 73 | 74 | @rtype: str 75 | """ 76 | ret = [] 77 | 78 | s = ",".join(["%02x" % (ord(c)) for c in value.value()]) 79 | 80 | if value.value_type() == Registry.RegBin: 81 | s = "hex:" + s 82 | else: 83 | s = "hex(%d):" % (value.value_type()) + s 84 | 85 | # there might be an off by one error in here somewhere... 86 | name_len = len(value.name()) + 2 + 1 # name + 2 * '"' + '=' 87 | split_index = 80 - name_len 88 | while len(s) > 0: 89 | if len(s) > split_index: 90 | # split on a comma 91 | while s[split_index] != ",": 92 | split_index -= 1 93 | ret.append(s[:split_index + 1] + "\\") 94 | s = " " + s[split_index + 1:] 95 | else: 96 | ret.append(s) 97 | s = "" 98 | split_index = 80 99 | 100 | return "\r\n".join(ret) 101 | 102 | 103 | def reg_format_value(value): 104 | return { 105 | Registry.RegSZ: reg_format_value_sz, 106 | Registry.RegExpandSZ: reg_format_value_bin, 107 | Registry.RegBin: reg_format_value_bin, 108 | Registry.RegDWord: reg_format_value_dword, 109 | }[value.value_type()](value) 110 | 111 | 112 | def reg_format_key_values(registry, prefix, key, values): 113 | """ 114 | @rtype: byte string 115 | """ 116 | ret = [] 117 | path = key.path().partition("\\")[2] # remove root key name ("$$$PROTO_HIV") 118 | ret.append(u"[{prefix}\{path}]".format(prefix=prefix, path=path)) 119 | for value in values: 120 | ret.append("\"{name}\"={value}".format(name=value.name(), 121 | value=reg_format_value(value))) 122 | ret.append("\r\n") 123 | return u"\r\n".join(ret).encode("utf-16le") 124 | 125 | 126 | def main(hive, prefix, keyname, *valuenames): 127 | """ 128 | @param prefix: something like "HKEY_LOCAL_MACHINE" to prepend to formatted key names. 129 | """ 130 | registry = Registry.Registry(hive) 131 | 132 | key = None 133 | try: 134 | if keyname.startswith(registry.root().name()): 135 | key = registry.open(keyname.partition("\\")[2]) 136 | else: 137 | key = registry.open(keyname) 138 | except Registry.RegistryKeyNotFoundException: 139 | print("Error: Specified key not found") 140 | sys.exit(-1) 141 | 142 | values = [] 143 | if len(valuenames) != 0: 144 | for valuename in valuenames: 145 | if valuename == "default": 146 | valuename = "(default)" 147 | 148 | values.append(key.value(valuename)) 149 | else: 150 | values = [v for v in key.values()] 151 | 152 | stdout.write(reg_format_header()) 153 | stdout.write(reg_format_key_values(registry, prefix, key, values)) 154 | 155 | 156 | if __name__ == '__main__': 157 | if len(sys.argv) < 4: 158 | print(usage()) 159 | sys.exit(-1) 160 | 161 | main(*sys.argv[1:]) 162 | 163 | -------------------------------------------------------------------------------- /samples/copyclean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of python-registry. 5 | # It tries to copy only used parts of hive file, 6 | # skipping leftover data in unused parts. 7 | # 8 | # Copyright 2015 Christian Nilsson 9 | # Copyright 2011 Will Ballenthin 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | 23 | from __future__ import print_function 24 | 25 | import sys 26 | import struct 27 | import logging 28 | 29 | from Registry import RegistryParse 30 | 31 | g_logger = logging.getLogger(__file__) 32 | 33 | 34 | class Copy(RegistryParse.REGFBlock): 35 | """ 36 | Parsing of file as a Windows Registry file. 37 | """ 38 | def __init__(self, filelikeobject, writer): 39 | """ 40 | Constructor. 41 | Arguments: 42 | - `filelikeobject`: A file-like object with a .read() method. 43 | If a Python string is passed, it is interpreted as a filename, 44 | and the corresponding file is opened. 45 | """ 46 | try: 47 | self._buf = filelikeobject.read() 48 | except AttributeError: 49 | with open(filelikeobject, "rb") as f: 50 | self._buf = f.read() 51 | self._buflen = len(self._buf) 52 | self._writer = writer 53 | 54 | super(Copy, self).__init__(self._buf, 0, False) 55 | 56 | def pack_dword(self, offset, *data): 57 | """ 58 | write little-endian DWORDs (4 bytes) to the relative offset. 59 | Arguments: 60 | - `offset`: The relative offset from the start of the block. 61 | - `data`: The data to be written, can be multiple 62 | """ 63 | g_logger.debug("write dword 0x%08x at offset 0x%08x" % (data[0], self._offset + offset)) 64 | #return struct.pack_into(str(" " % (sys.argv[0]) 118 | 119 | if __name__ == '__main__': 120 | if len(sys.argv) != 3: 121 | print(usage()) 122 | sys.exit(-1) 123 | 124 | # TODO add parsing of commandline to set log level 125 | g_logger.setLevel(logging.DEBUG) 126 | 127 | f = open(sys.argv[1], "rb") 128 | fw = open(sys.argv[2], "wb") 129 | 130 | copy = Copy(f, fw) 131 | # TODO add checksum and sequence check, exit if incorrect, 132 | # but make it possible to override from the commandline with -f 133 | copy.copy() 134 | 135 | f.close() 136 | fw.close() 137 | -------------------------------------------------------------------------------- /samples/mount.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import os 4 | import sys 5 | import stat 6 | import errno 7 | import inspect 8 | import calendar 9 | 10 | from fuse import FUSE, FuseOSError, Operations, fuse_get_context 11 | 12 | from Registry import Registry 13 | 14 | 15 | PERMISSION_ALL_READ = int("444", 8) 16 | 17 | 18 | def unixtimestamp(ts): 19 | """ 20 | unixtimestamp converts a datetime.datetime to a UNIX timestamp. 21 | @type ts: datetime.datetime 22 | @rtype: int 23 | """ 24 | return calendar.timegm(ts.utctimetuple()) 25 | 26 | 27 | def log(func): 28 | """ 29 | log is a decorator that logs the a function call with its 30 | parameters and return value. 31 | """ 32 | def inner(*args, **kwargs): 33 | func_name = inspect.stack()[3][3] 34 | if func_name == "_wrapper": 35 | func_name = inspect.stack()[2][3] 36 | (uid, gid, pid) = fuse_get_context() 37 | pre = "(%s: UID=%d GID=%d PID=%d ARGS=(%s) KWARGS=(%s))" % ( 38 | func_name, uid, gid, pid, 39 | ", ".join(map(str, list(args)[1:])), str(**kwargs)) 40 | try: 41 | ret = func(*args, **kwargs) 42 | post = " +--> %s" % (str(ret)) 43 | sys.stderr.write("%s\n%s\n" % (pre, post)) 44 | return ret 45 | except Exception as e: 46 | post = " +--> %s" % (str(e)) 47 | sys.stderr.write("%s\n%s" % (pre, post)) 48 | raise e 49 | return inner 50 | 51 | 52 | class FH(object): 53 | """ 54 | FH is a class used to represent a file handle. 55 | """ 56 | def __init__(self, fh, data): 57 | super(FH, self).__init__() 58 | self._fh = fh 59 | self._data = data 60 | 61 | def get_fh(self): 62 | return self._fh 63 | 64 | def get_data(self): 65 | return self._data 66 | 67 | def get_size(self): 68 | return len(self._data) 69 | 70 | 71 | class EntryNotFoundError(Exception): 72 | pass 73 | 74 | 75 | class RegFuseOperations(Operations): 76 | """ 77 | RegFuseOperations is a FUSE driver for Registry hives. 78 | """ 79 | def __init__(self, root, reg): 80 | self._root = root 81 | self._reg = reg 82 | self._opened_files = {} # dict(int --> FH subclass) 83 | 84 | def _get_entry(self, path): 85 | if path == "/" or path == "": 86 | return self._reg.root() 87 | path = path.lstrip("/\\").replace("/", "\\") 88 | 89 | try: 90 | return self._reg.open(path) 91 | except Registry.RegistryKeyNotFoundException: 92 | key, _, value = path.rpartition("\\") 93 | parent = self._reg.open(key) 94 | try: 95 | return parent.value(value) 96 | except Registry.RegistryValueNotFoundException: 97 | raise EntryNotFoundError() 98 | 99 | def _is_directory(self, entry): 100 | return isinstance(entry, Registry.RegistryKey) 101 | 102 | def _is_file(self, entry): 103 | return isinstance(entry, Registry.RegistryValue) 104 | 105 | # Filesystem methods 106 | # ================== 107 | #@log 108 | def getattr(self, path, fh=None): 109 | (uid, gid, pid) = fuse_get_context() 110 | 111 | working_path = path 112 | 113 | try: 114 | entry = self._get_entry(path) 115 | except EntryNotFoundError: 116 | return errno.ENOENT 117 | 118 | if self._is_directory(entry): 119 | mode = (stat.S_IFDIR | PERMISSION_ALL_READ) 120 | nlink = 2 121 | ts = unixtimestamp(entry.timestamp()) 122 | size = 0 123 | else: 124 | mode = (stat.S_IFREG | PERMISSION_ALL_READ) 125 | nlink = 1 126 | ts = 0 127 | size = len(entry.raw_data()) 128 | 129 | return { 130 | "st_atime": 0, 131 | "st_ctime": 0, 132 | #"st_crtime": unixtimestamp(record.standard_information().created_time()), 133 | "st_mtime": ts, 134 | "st_size": size, 135 | "st_uid": uid, 136 | "st_gid": gid, 137 | "st_mode": mode, 138 | "st_nlink": nlink, 139 | } 140 | 141 | #@log 142 | def readdir(self, path, fh): 143 | try: 144 | entry = self._get_entry(path) 145 | except EntryNotFoundError: 146 | return errno.ENOENT 147 | 148 | if not self._is_directory(entry): 149 | return 150 | 151 | # can't be a generator, since we *return* ENOENT above (not yield) 152 | ret = [".", ".."] 153 | 154 | for key in entry.subkeys(): 155 | ret.append(key.name()) 156 | 157 | for value in entry.values(): 158 | ret.append(value.name()) 159 | 160 | return ret 161 | 162 | @log 163 | def readlink(self, path): 164 | return path 165 | 166 | @log 167 | def statfs(self, path): 168 | return dict((key, 0) for key in ('f_bavail', 'f_bfree', 169 | 'f_blocks', 'f_bsize', 'f_favail', 170 | 'f_ffree', 'f_files', 'f_flag', 171 | 'f_frsize', 'f_namemax')) 172 | 173 | @log 174 | def chmod(self, path, mode): 175 | return errno.EROFS 176 | 177 | @log 178 | def chown(self, path, uid, gid): 179 | return errno.EROFS 180 | 181 | @log 182 | def mknod(self, path, mode, dev): 183 | return errno.EROFS 184 | 185 | @log 186 | def rmdir(self, path): 187 | return errno.EROFS 188 | 189 | @log 190 | def mkdir(self, path, mode): 191 | return errno.EROFS 192 | 193 | @log 194 | def unlink(self, path): 195 | return errno.EROFS 196 | 197 | @log 198 | def symlink(self, target, name): 199 | return errno.EROFS 200 | 201 | @log 202 | def rename(self, old, new): 203 | return errno.EROFS 204 | 205 | @log 206 | def link(self, target, name): 207 | return errno.EROFS 208 | 209 | @log 210 | def utimens(self, path, times=None): 211 | return errno.EROFS 212 | 213 | # File methods 214 | # ============ 215 | 216 | def _get_available_fh(self): 217 | """ 218 | _get_available_fh returns an unused fh 219 | The caller must be careful to handle race conditions. 220 | @rtype: int 221 | """ 222 | for i in xrange(65534): 223 | if i not in self._opened_files: 224 | return i 225 | 226 | @log 227 | def open(self, path, flags): 228 | if flags & os.O_WRONLY > 0: 229 | return errno.EROFS 230 | if flags & os.O_RDWR > 0: 231 | return errno.EROFS 232 | 233 | # TODO(wb): race here on fh used/unused 234 | fh = self._get_available_fh() 235 | 236 | try: 237 | entry = self._get_entry(path) 238 | except EntryNotFoundError: 239 | return errno.ENOENT 240 | 241 | data = entry.raw_data() 242 | self._opened_files[fh] = FH(fh, data) 243 | 244 | return fh 245 | 246 | #@log 247 | def read(self, path, length, offset, fh): 248 | return self._opened_files[fh].get_data()[offset:offset + length] 249 | 250 | @log 251 | def flush(self, path, fh): 252 | return "" 253 | 254 | @log 255 | def release(self, path, fh): 256 | del self._opened_files[fh] 257 | 258 | @log 259 | def create(self, path, mode, fi=None): 260 | return errno.EROFS 261 | 262 | @log 263 | def write(self, path, buf, offset, fh): 264 | return errno.EROFS 265 | 266 | @log 267 | def truncate(self, path, length, fh=None): 268 | return errno.EROFS 269 | 270 | @log 271 | def fsync(self, path, fdatasync, fh): 272 | return errno.EPERM 273 | 274 | 275 | def main(hivepath, mountpoint): 276 | r = Registry.Registry(hivepath) 277 | handler = RegFuseOperations(mountpoint, r) 278 | FUSE(handler, mountpoint, foreground=True) 279 | 280 | if __name__ == '__main__': 281 | main(sys.argv[1], sys.argv[2]) 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /samples/amcache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # This file is part of python-registry. 3 | # 4 | # Copyright 2015 Will Ballenthin 5 | # while at Mandiant Exe 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | import sys 19 | import logging 20 | import datetime 21 | from collections import namedtuple 22 | 23 | import argparse 24 | import csv 25 | from Registry import Registry 26 | from Registry.RegistryParse import parse_windows_timestamp as _parse_windows_timestamp 27 | 28 | 29 | g_logger = logging.getLogger("amcache") 30 | Field = namedtuple("Field", ["name", "getter"]) 31 | 32 | 33 | def make_value_getter(value_name): 34 | """ return a function that fetches the value from the registry key """ 35 | def _value_getter(key): 36 | try: 37 | return key.value(value_name).value() 38 | except Registry.RegistryValueNotFoundException: 39 | return None 40 | return _value_getter 41 | 42 | 43 | def make_windows_timestamp_value_getter(value_name): 44 | """ 45 | return a function that fetches the value from the registry key 46 | as a Windows timestamp. 47 | """ 48 | f = make_value_getter(value_name) 49 | def _value_getter(key): 50 | try: 51 | return parse_windows_timestamp(f(key) or 0) 52 | except ValueError: 53 | return datetime.datetime.min 54 | return _value_getter 55 | 56 | 57 | def parse_unix_timestamp(qword): 58 | return datetime.datetime.fromtimestamp(qword) 59 | 60 | 61 | def parse_windows_timestamp(qword): 62 | try: 63 | return _parse_windows_timestamp(qword) 64 | except ValueError: 65 | return datetime.datetime.min 66 | 67 | 68 | def make_unix_timestamp_value_getter(value_name): 69 | """ 70 | return a function that fetches the value from the registry key 71 | as a UNIX timestamp. 72 | """ 73 | f = make_value_getter(value_name) 74 | def _value_getter(key): 75 | try: 76 | return parse_unix_timestamp(f(key) or 0) 77 | except ValueError: 78 | return datetime.datetime.min 79 | return _value_getter 80 | 81 | 82 | UNIX_TIMESTAMP_ZERO = parse_unix_timestamp(0) 83 | WINDOWS_TIMESTAMP_ZERO = parse_windows_timestamp(0) 84 | 85 | 86 | # via: http://www.swiftforensics.com/2013/12/amcachehve-in-windows-8-goldmine-for.html 87 | #Product Name UNICODE string 88 | #============================================================================== 89 | #0 Product Name UNICODE string 90 | #1 Company Name UNICODE string 91 | #2 File version number only UNICODE string 92 | #3 Language code (1033 for en-US) DWORD 93 | #4 SwitchBackContext QWORD 94 | #5 File Version UNICODE string 95 | #6 File Size (in bytes) DWORD 96 | #7 PE Header field - SizeOfImage DWORD 97 | #8 Hash of PE Header (unknown algorithm) UNICODE string 98 | #9 PE Header field - Checksum DWORD 99 | #a Unknown QWORD 100 | #b Unknown QWORD 101 | #c File Description UNICODE string 102 | #d Unknown, maybe Major & Minor OS version DWORD 103 | #f Linker (Compile time) Timestamp DWORD - Unix time 104 | #10 Unknown DWORD 105 | #11 Last Modified Timestamp FILETIME 106 | #12 Created Timestamp FILETIME 107 | #15 Full path to file UNICODE string 108 | #16 Unknown DWORD 109 | #17 Last Modified Timestamp 2 FILETIME 110 | #100 Program ID UNICODE string 111 | #101 SHA1 hash of file 112 | 113 | 114 | # note: order here implicitly orders CSV column ordering cause I'm lazy 115 | FIELDS = [ 116 | Field("path", make_value_getter("15")), 117 | Field("sha1", make_value_getter("101")), 118 | Field("size", make_value_getter("6")), 119 | Field("file_description", make_value_getter("c")), 120 | Field("source_key_timestamp", lambda key: key.timestamp()), 121 | Field("created_timestamp", make_windows_timestamp_value_getter("12")), 122 | Field("modified_timestamp", make_windows_timestamp_value_getter("11")), 123 | Field("modified_timestamp2", make_windows_timestamp_value_getter("17")), 124 | Field("linker_timestamp", make_unix_timestamp_value_getter("f")), 125 | Field("product", make_value_getter("0")), 126 | Field("company", make_value_getter("1")), 127 | Field("pe_sizeofimage", make_value_getter("7")), 128 | Field("version_number", make_value_getter("2")), 129 | Field("version", make_value_getter("5")), 130 | Field("language", make_value_getter("3")), 131 | Field("header_hash", make_value_getter("8")), 132 | Field("pe_checksum", make_value_getter("9")), 133 | Field("id", make_value_getter("100")), 134 | Field("switchbackcontext", make_value_getter("4")), 135 | ] 136 | 137 | 138 | ExecutionEntry = namedtuple("ExecutionEntry", map(lambda e: e.name, FIELDS)) 139 | 140 | 141 | def parse_execution_entry(key): 142 | return ExecutionEntry(**dict((e.name, e.getter(key)) for e in FIELDS)) 143 | 144 | 145 | 146 | class NotAnAmcacheHive(Exception): 147 | pass 148 | 149 | 150 | def parse_execution_entries(registry): 151 | try: 152 | volumes = registry.open("Root\\File") 153 | except Registry.RegistryKeyNotFoundException: 154 | raise NotAnAmcacheHive() 155 | 156 | ret = [] 157 | for volumekey in volumes.subkeys(): 158 | for filekey in volumekey.subkeys(): 159 | ret.append(parse_execution_entry(filekey)) 160 | return ret 161 | 162 | 163 | TimelineEntry = namedtuple("TimelineEntry", ["timestamp", "type", "entry"]) 164 | 165 | 166 | def main(argv=None): 167 | if argv is None: 168 | argv = sys.argv 169 | 170 | parser = argparse.ArgumentParser( 171 | description="Parse program execution entries from the Amcache.hve Registry hive") 172 | parser.add_argument("registry_hive", type=str, 173 | help="Path to the Amcache.hve hive to process") 174 | parser.add_argument("-v", action="store_true", dest="verbose", 175 | help="Enable verbose output") 176 | parser.add_argument("-t", action="store_true", dest="do_timeline", 177 | help="Output in simple timeline format") 178 | args = parser.parse_args(argv[1:]) 179 | 180 | if args.verbose: 181 | logging.basicConfig(level=logging.DEBUG) 182 | else: 183 | logging.basicConfig(level=logging.INFO) 184 | 185 | if sys.platform == "win32": 186 | import os, msvcrt 187 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 188 | 189 | r = Registry.Registry(args.registry_hive) 190 | 191 | try: 192 | ee = parse_execution_entries(r) 193 | except NotAnAmcacheHive: 194 | g_logger.error("doesn't appear to be an Amcache.hve hive") 195 | return 196 | 197 | if args.do_timeline: 198 | entries = [] 199 | for e in ee: 200 | for t in ["source_key_timestamp", "created_timestamp", "modified_timestamp", 201 | "modified_timestamp2", "linker_timestamp"]: 202 | ts = getattr(e, t) 203 | if ts == UNIX_TIMESTAMP_ZERO: 204 | continue 205 | if ts == WINDOWS_TIMESTAMP_ZERO: 206 | continue 207 | if ts == datetime.datetime.min: 208 | continue 209 | 210 | entries.append(TimelineEntry(ts, t, e)) 211 | w = csv.writer(sys.stdout, delimiter="|", quotechar="\"", 212 | quoting=csv.QUOTE_MINIMAL, encoding="utf-8") 213 | w.writerow(["timestamp", "timestamp_type", "path", "sha1"]) 214 | for e in sorted(entries, key=lambda e: e.timestamp): 215 | w.writerow([e.timestamp, e.type, e.entry.path, e.entry.sha1]) 216 | else: 217 | w = csv.writer(sys.stdout, delimiter="|", quotechar="\"", 218 | quoting=csv.QUOTE_MINIMAL, encoding="utf-8") 219 | w.writerow(map(lambda e: e.name, FIELDS)) 220 | for e in ee: 221 | w.writerow(map(lambda i: getattr(e, i.name), FIELDS)) 222 | 223 | 224 | if __name__ == "__main__": 225 | main(argv=sys.argv) 226 | -------------------------------------------------------------------------------- /Registry/RegistryLog.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011, 2012 Willi Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | from __future__ import absolute_import 21 | 22 | from ctypes import c_uint32 23 | from struct import pack 24 | from Registry import RegistryParse 25 | 26 | class RegistryLog(object): 27 | """ 28 | A class for parsing and applying a Windows Registry transaction log file. 29 | """ 30 | def __init__(self, filelikeobject_primary, filelikeobject_log): 31 | """ 32 | Constructor. 33 | Arguments: 34 | - `filelikeobject_primary`: A file-like object with .read(), .write() and .seek() methods. 35 | This object shall be writable, it will receive the contents of the recovered hive. 36 | - `filelikeobject_log`: A file-like object with a .read() method. 37 | If a Python string is passed, it is interpreted as a filename, 38 | and the corresponding file is opened. 39 | """ 40 | try: 41 | self._log_buf = filelikeobject_log.read() 42 | except AttributeError: 43 | with open(filelikeobject_log, "rb") as f: 44 | self._log_buf = f.read() 45 | 46 | self._regf = RegistryParse.REGFBlock(self._log_buf, 0, False) 47 | 48 | if self._regf.is_old_transaction_log_file(): 49 | raise RegistryParse.NotSupportedException("Old transaction log files are not supported") 50 | 51 | if self._regf.is_primary_file(): 52 | raise RegistryParse.ParseException("Cannot load a primary file as a transaction log file") 53 | 54 | if not self._regf.is_new_transaction_log_file(): 55 | raise RegistryParse.NotSupportedException("Unknown file type") 56 | 57 | if self._regf.clustering_factor() != 1: 58 | raise RegistryParse.NotSupportedException("Clustering factor not equal to 1 is not supported") 59 | 60 | recover = self._regf.recovery_required() 61 | if recover.recover_header or recover.recover_data: 62 | raise RegistryParse.NotSupportedException("This transaction log file requires self-healing") 63 | 64 | self._primary_buf = filelikeobject_primary.read(512) 65 | self._primary = filelikeobject_primary 66 | 67 | self._primary_regf = RegistryParse.REGFBlock(self._primary_buf, 0, False) 68 | self._hive_flags = None 69 | self._hive_sequence = None 70 | self._hbins_size = None 71 | 72 | def reload_primary_regf(self): 73 | """Fill the _primary_buf and _primary_regf variables again.""" 74 | self._primary.seek(0) 75 | self._primary_buf = self._primary.read(512) 76 | self._primary_regf = RegistryParse.REGFBlock(self._primary_buf, 0, False) 77 | 78 | def latest_hive_flags(self): 79 | """Return the latest hive flags. At present, only one bit mask (0x1) is used.""" 80 | return self._hive_flags 81 | 82 | def latest_hbins_size(self): 83 | """Return the latest hbins_size.""" 84 | return self._hbins_size 85 | 86 | def latest_hive_sequence(self): 87 | """Return the latest hive_sequence1 (the same as hive_sequence2 after recovery).""" 88 | return self._hive_sequence 89 | 90 | def first_log_sequence(self): 91 | """Returns the first log sequence number.""" 92 | return self._regf.hive_sequence2() 93 | 94 | def is_eligible_log(self): 95 | """Check if this log is eligible for the primary file.""" 96 | if not self._primary_regf.validate_checksum(): 97 | return True 98 | 99 | if self.first_log_sequence() >= self._primary_regf.hive_sequence2(): 100 | return True 101 | 102 | return False 103 | 104 | def is_starting_log(self, another_log_file): 105 | """ 106 | When the dual-logging scheme is used, check if this log shall be applied first. 107 | Another RegistryLog instance is checked against this one. 108 | """ 109 | another_seqnum = another_log_file.first_log_sequence() 110 | this_seqnum = self.first_log_sequence() 111 | if this_seqnum >= another_seqnum: 112 | delta = this_seqnum - another_seqnum 113 | starting = False 114 | else: 115 | delta = another_seqnum - this_seqnum 116 | starting = True 117 | 118 | if c_uint32(delta).value <= 0x7FFFFFFF: 119 | return starting 120 | else: 121 | # Sequence numbers did overflow. 122 | return not starting 123 | 124 | def write_dirty_page(self, dirty_page_reference, dirty_page): 125 | """Write a dirty page to the primary file.""" 126 | offset_primary = dirty_page_reference.offset() + self._primary_regf.first_hbin_offset() 127 | size = dirty_page_reference.size() 128 | dirty_data = dirty_page.data() 129 | self._primary.seek(offset_primary) 130 | self._primary.write(dirty_data) 131 | 132 | def update_regf_header(self, hive_sequence, hbins_size, hive_flags): 133 | """ 134 | Update the REGF block of the primary file with the new hive_sequence1, hive_sequence2, hbins_size, and hive_flags. 135 | Recalculate the checksum and reload the REGF block. 136 | """ 137 | 138 | def pack_dword(num): 139 | return pack(str(" 12 | # 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | # 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | # 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | from __future__ import print_function 31 | from __future__ import unicode_literals 32 | 33 | from datetime import datetime, timedelta 34 | import binascii 35 | import struct 36 | 37 | REG_COMPOSITE_TYPE = 0x100 38 | # When used in registry to denote value type, the REG_COMPOSITE_TYPE is or'd with one of the 39 | # values below. Example: RegUInt8 will be (REG_COMPOSITE_TYPE | RegUint8) 40 | # In the serialized ApplicationDataCompositeValue stream, the REG_COMPOSITE_TYPE is not present. 41 | RegUint8 = 0x001 42 | RegInt16 = 0x002 43 | RegUint16 = 0x003 44 | RegInt32 = 0x004 45 | RegUint32 = 0x005 46 | RegInt64 = 0x006 47 | RegUint64 = 0x007 48 | RegFloat = 0x008 # aka Single 49 | RegDouble = 0x009 50 | RegUnicodeChar = 0x00A 51 | RegBoolean = 0x00B 52 | RegUnicodeString = 0x00C 53 | RegCompositeValue = 0x00D # Application Data Composite Value (Dictionary Object) 54 | RegDateTimeOffset = 0x00E # Date as FILETIME 55 | RegTimeSpan = 0x00F # Span in 100ns ticks 56 | RegGUID = 0x010 57 | RegUnk111 = 0x011 58 | RegUnk112 = 0x012 59 | RegUnk113 = 0x013 60 | RegBytesArray = 0x014 61 | RegInt16Array = 0x015 62 | RegUint16Array = 0x016 63 | RegInt32Array = 0x017 64 | RegUInt32Array = 0x018 65 | RegInt64Array = 0x019 66 | RegUInt64Array = 0x01A 67 | RegFloatArray = 0x01B 68 | RegDoubleArray = 0x01C 69 | RegUnicodeCharArray = 0x01D 70 | RegBooleanArray = 0x01E 71 | RegUnicodeStringArray = 0x01F 72 | 73 | def parse_windows_timestamp(qword): 74 | # see http://integriography.wordpress.com/2010/01/16/using-phython-to-parse-and-present-windows-64-bit-timestamps/ 75 | return datetime.utcfromtimestamp(float(qword) * 1e-7 - 11644473600 ) 76 | 77 | def ReadUnicodeStringArray(buf): 78 | """Read a buffer containing an array of struct { int size; wchar string[size]; } 79 | Returns a list of utf8 encoded strings 80 | """ 81 | strings = [] 82 | buf_len = len(buf) 83 | pos = 0 84 | while pos < buf_len: 85 | item_byte_len = struct.unpack_from(str(" 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | from __future__ import print_function 21 | from __future__ import unicode_literals 22 | 23 | import sys, struct 24 | from Registry import Registry 25 | 26 | class Value(object): 27 | def __init__(self, name, data_type, data): 28 | self.name = name 29 | self.data_type = data_type 30 | self.data = data 31 | 32 | class Key(object): 33 | def __init__(self, name): 34 | self.name = name 35 | self.values = [] 36 | 37 | def parse(f): 38 | t = f.read() 39 | h = t.partition("\n")[0] 40 | if "\xff\xfe\x57\x00\x69\x00" in h: # Windows Registry Editor 5.00 41 | raise "THIS ISNT SUPPORTED YET" 42 | try: 43 | t = t.decode("utf16") 44 | print("Decoded input file with UTF16 decoder") 45 | except: 46 | raise 47 | elif "Windows Registry Editor Version 5.00" in h: 48 | t = t.decode("iso-8859-1", "replace") 49 | print("Decoded input file with ASCII decoder") 50 | elif "REGEDIT4" in h: # Regedit 51 | t = t.decode("iso-8859-1", "replace") 52 | print("Decoded input file with ASCII decoder") 53 | else: 54 | print("Unable to parse header") 55 | sys.exit(-1) 56 | 57 | lines = t.split("\n") 58 | 59 | current_key = False 60 | current_value = False 61 | keys = [] 62 | 63 | print("Found " + str(len(lines)) + " lines") 64 | 65 | line_count = 0 66 | for line in [l.rstrip('\r') for l in lines[1:]]: 67 | line_count += 1 68 | 69 | if len(line.lstrip(" ")) < 2: 70 | if current_value: 71 | current_key.values.append(current_value) 72 | current_value = False 73 | keys.append(current_key) 74 | current_key = False 75 | continue 76 | 77 | if current_key: 78 | if current_value: 79 | real_data = line.lstrip(" ") 80 | real_data = real_data.replace("\\", "") 81 | 82 | for c in real_data.split(","): 83 | try: 84 | current_value.data += chr(int(c, 16)) 85 | except ValueError: 86 | continue 87 | 88 | else: 89 | (name, _, data) = line.partition("=") 90 | 91 | # strip exactly one " mark from either side of the name 92 | if name[0] == '"': 93 | name = name[1:] 94 | if name[-1] == '"': 95 | name = name[:-1] 96 | 97 | if name == "@": 98 | name = "(default)" 99 | 100 | if ":" in data and data[0] != '"': 101 | real_data = data.partition(":")[2].rstrip("\\") # strip off trailing \ if it exists 102 | try: 103 | if real_data[-1] == '\\': 104 | real_data = real_data[:-2] 105 | except IndexError: 106 | real_data = "" 107 | data_type = data.partition(":")[0] 108 | else: 109 | real_data = data 110 | data_value = data.rstrip("\r\n") # strip off one " from both sides 111 | 112 | if data_value[0] == '"': 113 | data_value = data_value[1:] 114 | if data_value[-1] == '"': 115 | data_value = data_value[:-1] 116 | 117 | data_value = data_value.replace('\\"', '"') 118 | data_value = data_value.replace('\\\\', '\\') 119 | 120 | data_type = "string" 121 | 122 | if "word" in data_type: 123 | data_value = int(real_data, 16) 124 | 125 | if "hex" in data_type: 126 | data_value = "" 127 | for c in real_data.split(","): 128 | try: 129 | data_value += chr(int(c, 16)) 130 | except ValueError: 131 | continue 132 | 133 | print_value = data_value 134 | if "word" in data_type: 135 | print_value = str(print_value) 136 | elif "hex" in data_type: 137 | print_value = print_value.decode("ascii", "replace") + "" 138 | 139 | v = Value(name, data_type, data_value) 140 | 141 | if data[-1] == "\\": 142 | current_value = v 143 | else: 144 | current_key.values.append(v) 145 | else: 146 | name = line.lstrip("[").partition("]")[0] 147 | current_key = Key(name) 148 | 149 | return keys 150 | 151 | def key_long_str(key): 152 | """ 153 | Prints a long listing of a Registry Key 154 | """ 155 | ret = "" 156 | ret += str(key) + "\n" 157 | 158 | for s in key.subkeys(): 159 | ret += "\tsubkey: %s\n" % (s.name()) 160 | 161 | for v in key.values(): 162 | ret += "\tvalue: %s\n" % (v.name()) 163 | 164 | return ret 165 | 166 | def usage(): 167 | return " USAGE:\n\t%s <.reg file> " % (sys.argv[0]) 168 | 169 | if __name__ == '__main__': 170 | if len(sys.argv) != 3: 171 | print(usage()) 172 | sys.exit(-1) 173 | 174 | f = open(sys.argv[1]) 175 | keys = parse(f) 176 | print("Parsed .reg file") 177 | 178 | r = Registry.Registry(sys.argv[2]) 179 | print("Parsed Registry file") 180 | 181 | not_found_keys = 0 182 | incorrect_data = 0 183 | not_found_values = 0 184 | 185 | for k in [k for k in keys if k]: 186 | try: 187 | rk = r.open(k.name.partition("\\")[2]) 188 | for v in k.values: 189 | if v.name == ".Default": 190 | v.name = "" 191 | try: 192 | rv = rk.value(v.name) 193 | 194 | if rv.value_type() == Registry.RegSZ or \ 195 | rv.value_type() == Registry.RegExpandSZ: 196 | rvv = rv.value().decode("utf8") 197 | 198 | try: 199 | if rvv[-1] == '\x00': 200 | rvv = rvv[:-1] 201 | if rvv[-1] == '\x00': 202 | rvv = rvv[:-1] 203 | except IndexError: 204 | pass 205 | 206 | vv = unicode(v.data).partition('\x00')[0] 207 | 208 | if not rvv == vv: 209 | print("DATA VALUE INCORRECT: " + k.name + ":" + v.name) 210 | print(" " + rk.path() + ":" + rv.name()) 211 | print(key_long_str(rk)) 212 | print() 213 | 214 | print("|%s|" % (rvv)) 215 | print(rvv.__class__.__name__) 216 | print(len(rvv)) 217 | print(list(rvv)) 218 | print() 219 | 220 | print("|%s|" % (vv)) 221 | print(vv.__class__.__name__) 222 | print(len(vv)) 223 | print(list(vv)) 224 | print() 225 | 226 | incorrect_data += 1 227 | 228 | elif rv.value_type() == Registry.RegMultiSZ: 229 | vv = v.data.decode("utf16").split('\x00') 230 | try: 231 | rvv = map(lambda x: x.decode("utf8"), rv.value()) 232 | except: 233 | print("UNABLE TO DECODE UTF8") 234 | print("Path", rk.path()) 235 | print("Name", rv.name()) 236 | print("Value", rv.value()) 237 | print() 238 | 239 | raise 240 | 241 | for vvv in vv: 242 | if vvv not in rvv: 243 | print("RegMultiSZ DATA VALUE MISSING: " + vvv) 244 | print("Path", rk.path()) 245 | print("Name", rv.name()) 246 | print("Value", rv.value()) 247 | print() 248 | 249 | print("reg data:", list(v.data)) 250 | print("Decoded reg value:", vv) 251 | print("Decoded Hive value:", rvv) 252 | print() 253 | 254 | incorrect_data += 1 255 | 256 | elif rv.value_type() == Registry.RegDWord: 257 | vv = v.data 258 | 259 | rvv = rv.value() 260 | if not rvv == vv: 261 | print("DWORD INCORRECT: " + str(vv) + " != " + str(rvv)) 262 | print(list(vv)) 263 | print("Path", rk.path()) 264 | print("Name", rv.name()) 265 | print("Value", rv.value()) 266 | print() 267 | 268 | incorrect_data += 1 269 | 270 | elif rv.value_type() == Registry.RegQWord: 271 | vv = struct.unpack(" 0: 303 | print("Unable to find %d keys" % (not_found_keys)) 304 | else: 305 | print("Found all keys") 306 | 307 | if not_found_values > 0: 308 | print("Unable to find %d values" % (not_found_values)) 309 | else: 310 | print("Found all values") 311 | 312 | if incorrect_data > 0: 313 | print("%d incorrect data values" % (incorrect_data)) 314 | else: 315 | print("All supported data values correct") 316 | 317 | 318 | -------------------------------------------------------------------------------- /samples/forensicating.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # created by Glenn P. Edwards Jr. 4 | # http://hiddenillusion.blogspot.com 5 | # @hiddenillusion 6 | # Version 0.1 7 | # Date: 07-23-2013 8 | # (while at FireEye) 9 | 10 | from __future__ import print_function 11 | from __future__ import unicode_literals 12 | 13 | import os 14 | import re 15 | import sys 16 | import time 17 | try: 18 | from Registry import Registry 19 | except ImportError: 20 | print("[!] Python-Registry not found") 21 | 22 | """ 23 | Some repetitively used functions 24 | """ 25 | 26 | def control_set_check(sys_reg): 27 | """ 28 | Determine which Control Set the system was using 29 | """ 30 | registry = Registry.Registry(sys_reg) 31 | key = registry.open("Select") 32 | for v in key.values(): 33 | if v.name() == "Current": 34 | return v.value() 35 | 36 | def arch_check(sys_reg): 37 | """ 38 | Architecture Check 39 | """ 40 | registry = Registry.Registry(sys_reg) 41 | key = registry.open("ControlSet00%s\\Control\\Session Manager\\Environment" % control_set_check(sys_reg)) 42 | for v in key.values(): 43 | if v.name() == "PROCESSOR_ARCHITECTURE": 44 | return v.value() 45 | 46 | def windir_check(sys_reg): 47 | """ 48 | Locate the Windows directory 49 | """ 50 | registry = Registry.Registry(sys_reg) 51 | key = registry.open("ControlSet00%s\\Control\\Session Manager\\Environment" % control_set_check(sys_reg)) 52 | for v in key.values(): 53 | if v.name() == "windir": 54 | return v.value() 55 | 56 | def os_check(soft_reg): 57 | """ 58 | Determine the Operating System 59 | """ 60 | registry = Registry.Registry(soft_reg) 61 | key = registry.open("Microsoft\\Windows NT\\CurrentVersion") 62 | for v in key.values(): 63 | if v.name() == "ProductName": 64 | return v.value() 65 | 66 | def users_sids(soft_reg): 67 | ''' 68 | Return a list of subkeys containing the users SIDs 69 | ''' 70 | sid_list = [] 71 | registry = Registry.Registry(soft_reg) 72 | key = registry.open("Microsoft\\Windows NT\\CurrentVersion\\ProfileList") 73 | for v in key.subkeys(): 74 | sid_list.append(v.name()) 75 | 76 | return sid_list 77 | 78 | def sid_to_user(sid_list, soft_reg): 79 | ''' 80 | Return a list which maps SIDs to usernames 81 | ''' 82 | # Grab the users profiles path based on the above SIDs 83 | mapping_list = [] 84 | registry = Registry.Registry(soft_reg) 85 | for sid in sid_list: 86 | k = registry.open("Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s" % sid) 87 | for v in k.values(): 88 | if v.name() == "ProfileImagePath": 89 | mapping_list.append("{0:20} : {1}".format(v.value().rpartition('\\')[2],sid)) 90 | 91 | return mapping_list 92 | 93 | def users_paths(soft_reg, sid_list): 94 | ''' 95 | Return a list of the profile paths for users on the system 96 | ''' 97 | # Grab the users profiles path based on their SIDs 98 | users_paths_list = [] 99 | registry = Registry.Registry(soft_reg) 100 | for sid in sid_list: 101 | k = registry.open("Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%s" % sid) 102 | for v in k.values(): 103 | if v.name() == "ProfileImagePath": 104 | users_paths_list.append(v.value()) 105 | 106 | return users_paths_list 107 | 108 | 109 | def user_reg_locs(user_path_locs): 110 | """ 111 | Returns the full path to each users NTUSER.DAT hive 112 | """ 113 | user_ntuser_list = [] 114 | for p in user_path_locs: 115 | if re.match('.*(Users|Documents and Settings).*', p): 116 | user_reg = os.path.join(p, "NTUSER.DAT") 117 | user_ntuser_list.append(user_reg) 118 | 119 | return user_ntuser_list 120 | 121 | """ 122 | Leverage the above functions and do something cool with them 123 | """ 124 | def env_settings(sys_reg): 125 | """ 126 | Environment Settings 127 | """ 128 | results = [] 129 | sys_architecture = [] 130 | registry = Registry.Registry(sys_reg) 131 | print(("=" * 51) + "\n[+] Environment Settings\n" + ("=" * 51)) 132 | key = registry.open("ControlSet00%s\\Control\\Session Manager\\Environment" % control_set_check(sys_reg)) 133 | for v in key.values(): 134 | if v.name() == "PROCESSOR_ARCHITECTURE": 135 | sys_architecture = v.value() 136 | results.append("[-] Architecture.....: " + str(v.value())) 137 | if v.name() == "NUMBER_OF_PROCESSORS": 138 | results.append("[-] Processors.......: " + str(v.value())) 139 | if v.name() == "TEMP": 140 | results.append("[-] Temp path........: " + str(v.value())) 141 | if v.name() == "TMP": 142 | results.append("[-] Tmp path.........: " + str(v.value())) 143 | for line in results: 144 | print(line) 145 | 146 | def tz_settings(sys_reg): 147 | """ 148 | Time Zone Settings 149 | """ 150 | results = [] 151 | current_control_set = "ControlSet00%s" % control_set_check(sys_reg) 152 | k = "%s\\Control\\TimeZoneInformation" % current_control_set 153 | registry = Registry.Registry(sys_reg) 154 | key = registry.open(k) 155 | results.append(("=" * 51) + "\nTime Zone Settings\n" + ("=" * 51)) 156 | print("[-] Checking %s based on 'Select' settings" % current_control_set) 157 | results.append("[+] %s" % k) 158 | results.append("---------------------------------------") 159 | for v in key.values(): 160 | if v.name() == "ActiveTimeBias": 161 | results.append("[-] ActiveTimeBias: %s" % v.value()) 162 | if v.name() == "Bias": 163 | results.append("[-] Bias...: %s" % v.value()) 164 | if v.name() == "TimeZoneKeyName": 165 | results.append("[-] Time Zone Name...: %s" % str(v.value())) 166 | 167 | return results 168 | 169 | def os_settings(sys_reg, soft_reg): 170 | """ 171 | Installed Operating System information 172 | """ 173 | results = [] 174 | registry = Registry.Registry(soft_reg) 175 | os_dict = {} 176 | key = registry.open("Microsoft\\Windows NT\\CurrentVersion") 177 | for v in key.values(): 178 | if v.name() == "ProductName": 179 | os_dict['ProductName'] = v.value() 180 | if v.name() == "ProductId": 181 | os_dict['ProductId'] = v.value() 182 | if v.name() == "CSDVersion": 183 | os_dict['CSDVersion'] = v.value() 184 | if v.name() == "PathName": 185 | os_dict['PathName'] = v.value() 186 | if v.name() == "InstallDate": 187 | os_dict['InstallDate'] = time.strftime('%a %b %d %H:%M:%S %Y (UTC)', time.gmtime(v.value())) 188 | if v.name() == "RegisteredOrganization": 189 | os_dict['RegisteredOrganization'] = v.value() 190 | if v.name() == "RegisteredOwner": 191 | os_dict['RegisteredOwner'] = v.value() 192 | 193 | print(("=" * 51) + "\n[+] Operating System Information\n" + ("=" * 51)) 194 | print("[-] Product Name.....: %s" % os_dict['ProductName']) 195 | print("[-] Product ID.......: %s" % os_dict['ProductId']) 196 | print("[-] CSDVersion.......: %s" % os_dict['CSDVersion']) 197 | print("[-] Path Name........: %s" % os_dict['PathName'] ) 198 | print("[-] Install Date.....: %s" % os_dict['InstallDate'] ) 199 | print("[-] Registered Org...: %s" % os_dict['RegisteredOrganization']) 200 | print("[-] Registered Owner : %s" % os_dict['RegisteredOwner']) 201 | 202 | def network_settings(sys_reg, soft_reg): 203 | """ 204 | Network Settings 205 | """ 206 | nic_names = [] 207 | results_dict = {} 208 | nic_list = [] 209 | nics_dict = {} 210 | int_list = [] 211 | registry = Registry.Registry(soft_reg) 212 | key = registry.open("Microsoft\\Windows NT\\CurrentVersion\\NetworkCards") 213 | print(("=" * 51) + "\n[+] Network Adapters\n" + ("=" * 51)) 214 | 215 | # Populate the subkeys containing the NICs information 216 | for v in key.subkeys(): 217 | nic_list.append(v.name()) 218 | 219 | for nic in nic_list: 220 | k = registry.open("Microsoft\\Windows NT\\CurrentVersion\\NetworkCards\\%s" % nic) 221 | for v in k.values(): 222 | if v.name() == "Description": 223 | desc = v.value() 224 | nic_names.append(desc) 225 | if v.name() == "ServiceName": 226 | guid = v.value() 227 | nics_dict['Description'] = desc 228 | nics_dict['ServiceName'] = guid 229 | 230 | reg = Registry.Registry(sys_reg) 231 | key2 = reg.open("ControlSet00%s\\services\\Tcpip\\Parameters\\Interfaces" % control_set_check(sys_reg)) 232 | # Populate the subkeys containing the interfaces GUIDs 233 | for v in key2.subkeys(): 234 | int_list.append(v.name()) 235 | 236 | def guid_to_name(g): 237 | for k,v in nics_dict.items(): 238 | ''' 239 | k = ServiceName, Description 240 | v = GUID, Adapter name 241 | ''' 242 | if v == g: 243 | return nics_dict['Description'] 244 | 245 | # Grab the NICs info based on the above list 246 | for i in int_list: 247 | print("[-] Interface........: %s" % guid_to_name(i)) 248 | print("[-] GUID.............: %s" % i) 249 | key3 = reg.open("ControlSet00%s\\services\\Tcpip\\Parameters\\Interfaces\\%s" % (control_set_check(sys_reg), i)) 250 | for v in key3.values(): 251 | if v.name() == "Domain": 252 | results_dict['Domain'] = v.value() 253 | if v.name() == "IPAddress": 254 | # Sometimes the IP would end up in a list here so just doing a little check 255 | ip = v.value() 256 | results_dict['IPAddress'] = ip[0] 257 | if v.name() == "DhcpIPAddress": 258 | results_dict['DhcpIPAddress'] = v.value() 259 | if v.name() == "DhcpServer": 260 | results_dict['DhcpServer'] = v.value() 261 | if v.name() == "DhcpSubnetMask": 262 | results_dict['DhcpSubnetMask'] = v.value() 263 | 264 | # Just to avoid key errors and continue to do because not all will have these fields 265 | if not 'Domain' in results_dict: 266 | results_dict['Domain'] = "N/A" 267 | if not 'IPAddress' in results_dict: 268 | results_dict['IPAddress'] = "N/A" 269 | if not 'DhcpIPAddress' in results_dict: 270 | results_dict['DhcpIPAddress'] = "N/A" 271 | if not 'DhcpServer' in results_dict: 272 | results_dict['DhcpServer'] = "N/A" 273 | if not 'DhcpSubnetMask' in results_dict: 274 | results_dict['DhcpSubnetMask'] = "N/A" 275 | 276 | print("[-] Domain...........: %s" % results_dict['Domain']) 277 | print("[-] IP Address.......: %s" % results_dict['IPAddress']) 278 | print("[-] DHCP IP..........: %s" % results_dict['DhcpIPAddress']) 279 | print("[-] DHCP Server......: %s" % results_dict['DhcpServer']) 280 | print("[-] DHCP Subnet......: %s" % results_dict['DhcpSubnetMask']) 281 | print("\n" ) 282 | 283 | def users_info(soft_reg): 284 | """ 285 | Populating all of the user accounts 286 | ref: http://support.microsoft.com/kb/154599 287 | """ 288 | results = [] 289 | results_dict = {} 290 | registry = Registry.Registry(soft_reg) 291 | 292 | results.append("{0:20} : {1}".format("Username", "SID")) 293 | results.append("---------------------------------------") 294 | 295 | for l in sid_to_user(users_sids(soft_reg), soft_reg): 296 | results.append(l) 297 | 298 | print(("=" * 51) + "\n[+] User Accounts\n" + ("=" * 51)) 299 | for line in results: 300 | print(line) 301 | 302 | 303 | if __name__ == "__main__": 304 | """ 305 | Print out all of the information 306 | """ 307 | import sys 308 | sys_reg = sys.argv[1] 309 | soft_reg = sys.argv[2] 310 | print("[+] SYSTEM hive: %s" % sys_reg) 311 | print("[+] SOFTWARE hive: %s" % soft_reg) 312 | print("[+] The system's Control Set is :",control_set_check(sys_reg)) 313 | print("[+] The system's Architecture is:",arch_check(sys_reg)) 314 | tz_settings(sys_reg) 315 | env_settings(sys_reg) 316 | os_settings(sys_reg, soft_reg) 317 | network_settings(sys_reg, soft_reg) 318 | users_info(soft_reg) 319 | user_reg_locs(users_paths(soft_reg, users_sids(soft_reg))) 320 | -------------------------------------------------------------------------------- /samples/regview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011 Will Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | from __future__ import unicode_literals 21 | 22 | import sys 23 | import os 24 | import wx 25 | from Registry import Registry 26 | 27 | ID_FILE_OPEN = wx.NewId() 28 | ID_FILE_SESSION_SAVE = wx.NewId() 29 | ID_FILE_SESSION_OPEN = wx.NewId() 30 | ID_TAB_CLOSE = wx.NewId() 31 | ID_FILE_EXIT = wx.NewId() 32 | ID_HELP_ABOUT = wx.NewId() 33 | 34 | 35 | def nop(*args, **kwargs): 36 | pass 37 | 38 | 39 | def basename(path): 40 | if "/" in path: 41 | path = path.split("/")[-1] 42 | if "\\" in path: 43 | path = path.split("\\")[-1] 44 | return path 45 | 46 | 47 | def _expand_into(dest, src): 48 | vbox = wx.BoxSizer(wx.VERTICAL) 49 | vbox.Add(src, 1, wx.EXPAND | wx.ALL) 50 | dest.SetSizer(vbox) 51 | 52 | 53 | def _format_hex(data): 54 | """ 55 | see http://code.activestate.com/recipes/142812/ 56 | """ 57 | byte_format = {} 58 | for c in xrange(256): 59 | if c > 126: 60 | byte_format[c] = '.' 61 | elif len(repr(chr(c))) == 3 and chr(c): 62 | byte_format[c] = chr(c) 63 | else: 64 | byte_format[c] = '.' 65 | 66 | def format_bytes(s): 67 | return "".join([byte_format[ord(c)] for c in s]) 68 | 69 | def dump(src, length=16): 70 | N = 0 71 | result = '' 72 | while src: 73 | s, src = src[:length], src[length:] 74 | hexa = ' '.join(["%02X" % ord(x) for x in s]) 75 | s = format_bytes(s) 76 | result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) 77 | N += length 78 | return result 79 | return dump(data) 80 | 81 | 82 | class DataPanel(wx.Panel): 83 | """ 84 | Displays the contents of a Registry value. 85 | Shows a text string where appropriate, or a hex dump. 86 | """ 87 | def __init__(self, *args, **kwargs): 88 | super(DataPanel, self).__init__(*args, **kwargs) 89 | self._sizer = wx.BoxSizer(wx.VERTICAL) 90 | self.SetSizer(self._sizer) 91 | 92 | def display_value(self, value): 93 | self._sizer.Clear() 94 | data_type = value.value_type() 95 | 96 | if data_type == Registry.RegSZ or \ 97 | data_type == Registry.RegExpandSZ or \ 98 | data_type == Registry.RegDWord or \ 99 | data_type == Registry.RegQWord: 100 | view = wx.TextCtrl(self, style=wx.TE_MULTILINE) 101 | view.SetValue(unicode(value.value())) 102 | 103 | elif data_type == Registry.RegMultiSZ: 104 | view = wx.ListCtrl(self, style=wx.LC_LIST) 105 | for string in value.value(): 106 | view.InsertStringItem(view.GetItemCount(), string) 107 | 108 | elif data_type == Registry.RegBin or \ 109 | data_type == Registry.RegNone: 110 | view = wx.TextCtrl(self, style=wx.TE_MULTILINE) 111 | font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Courier') 112 | view.SetFont(font) 113 | view.SetValue(_format_hex(value.value())) 114 | 115 | else: 116 | view = wx.TextCtrl(self, style=wx.TE_MULTILINE) 117 | view.SetValue(unicode(value.value())) 118 | 119 | self._sizer.Add(view, 1, wx.EXPAND) 120 | self._sizer.Layout() 121 | 122 | def clear_value(self): 123 | self._sizer.Clear() 124 | self._sizer.Add(wx.Panel(self, -1), 1, wx.EXPAND) 125 | self._sizer.Layout() 126 | 127 | 128 | class ValuesListCtrl(wx.ListCtrl): 129 | """ 130 | Shows a list of values associated with a Registry key. 131 | """ 132 | def __init__(self, *args, **kwargs): 133 | super(ValuesListCtrl, self).__init__(*args, **kwargs) 134 | self.InsertColumn(0, "Value name") 135 | self.InsertColumn(1, "Value type") 136 | self.SetColumnWidth(1, 100) 137 | self.SetColumnWidth(0, 300) 138 | self.values = {} 139 | 140 | def clear_values(self): 141 | self.DeleteAllItems() 142 | self.values = {} 143 | 144 | def add_value(self, value): 145 | n = self.GetItemCount() 146 | self.InsertStringItem(n, value.name()) 147 | self.SetStringItem(n, 1, value.value_type_str()) 148 | self.values[value.name()] = value 149 | 150 | def get_value(self, valuename): 151 | return self.values[valuename] 152 | 153 | 154 | class RegistryTreeCtrl(wx.TreeCtrl): 155 | """ 156 | Treeview control that displays the Registry key structure. 157 | """ 158 | def __init__(self, *args, **kwargs): 159 | super(RegistryTreeCtrl, self).__init__(*args, **kwargs) 160 | self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnExpandKey) 161 | 162 | def add_registry(self, registry): 163 | """ 164 | Add the registry to the control as the (a?) root element. 165 | """ 166 | root_key = registry.root() 167 | root_item = self.AddRoot(root_key.name()) 168 | self.SetPyData(root_item, {"key": root_key, 169 | "has_expanded": False}) 170 | 171 | if len(root_key.subkeys()) > 0: 172 | self.SetItemHasChildren(root_item) 173 | 174 | def delete_registry(self): 175 | """ 176 | Removes all elements from the control. 177 | """ 178 | self.DeleteAllItems() 179 | 180 | def select_path(self, path): 181 | """ 182 | Take a Registry key path separated by back slashes and select 183 | that key. The path should not contain the root key name. 184 | If the key is not found, the most specific ancestor key is selected. 185 | """ 186 | parts = path.split("\\") 187 | node = self.GetRootItem() 188 | 189 | for part in parts: 190 | self._extend(node) 191 | (node, cookie) = self.GetFirstChild(node) 192 | 193 | cont = True 194 | while node and cont: 195 | key = self.GetPyData(node)["key"] 196 | if key.name() == part: 197 | self.SelectItem(node) 198 | cont = False 199 | else: 200 | node = self.GetNextSibling(node) 201 | 202 | def _extend(self, item): 203 | """ 204 | Lazily parse and add children items to the tree. 205 | """ 206 | if self.GetPyData(item)["has_expanded"]: 207 | return 208 | 209 | key = self.GetPyData(item)["key"] 210 | 211 | for subkey in key.subkeys(): 212 | subkey_item = self.AppendItem(item, subkey.name()) 213 | self.SetPyData(subkey_item, {"key": subkey, 214 | "has_expanded": False}) 215 | 216 | if len(subkey.subkeys()) > 0: 217 | self.SetItemHasChildren(subkey_item) 218 | 219 | self.GetPyData(item)["has_expanded"] = True 220 | 221 | def OnExpandKey(self, event): 222 | item = event.GetItem() 223 | if not item.IsOk(): 224 | item = self.GetSelection() 225 | 226 | if not self.GetPyData(item)["has_expanded"]: 227 | self._extend(item) 228 | 229 | 230 | class RegistryFileView(wx.Panel): 231 | """ 232 | A three-paned display of the RegistryTreeCtrl, ValueListCtrl, and DataPanel. 233 | """ 234 | def __init__(self, parent, registry, filename): 235 | super(RegistryFileView, self).__init__(parent, -1, size=(800, 600)) 236 | self._filename = filename 237 | 238 | vsplitter = wx.SplitterWindow(self, -1) 239 | panel_left = wx.Panel(vsplitter, -1) 240 | self._tree = RegistryTreeCtrl(panel_left, -1) 241 | _expand_into(panel_left, self._tree) 242 | 243 | hsplitter = wx.SplitterWindow(vsplitter, -1) 244 | panel_top = wx.Panel(hsplitter, -1) 245 | panel_bottom = wx.Panel(hsplitter, -1) 246 | 247 | self._value_list_view = ValuesListCtrl(panel_top, -1, style=wx.LC_REPORT) 248 | self._data_view = DataPanel(panel_bottom, -1) 249 | 250 | _expand_into(panel_top, self._value_list_view) 251 | _expand_into(panel_bottom, self._data_view) 252 | 253 | hsplitter.SplitHorizontally(panel_top, panel_bottom) 254 | vsplitter.SplitVertically(panel_left, hsplitter) 255 | 256 | # give enough space in the data display for the hex output 257 | vsplitter.SetSashPosition(325, True) 258 | _expand_into(self, vsplitter) 259 | self.Centre() 260 | 261 | self._value_list_view.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnValueSelected) 262 | self._tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnKeySelected) 263 | 264 | self._tree.add_registry(registry) 265 | 266 | def OnKeySelected(self, event): 267 | item = event.GetItem() 268 | if not item.IsOk(): 269 | item = self._tree.GetSelection() 270 | 271 | key = self._tree.GetPyData(item)["key"] 272 | 273 | parent = self.GetParent() 274 | while parent: 275 | try: 276 | parent.SetStatusText(key.path()) 277 | except AttributeError: 278 | pass 279 | parent = parent.GetParent() 280 | 281 | self._data_view.clear_value() 282 | self._value_list_view.clear_values() 283 | for value in key.values(): 284 | self._value_list_view.add_value(value) 285 | 286 | def OnValueSelected(self, event): 287 | item = event.GetItem() 288 | 289 | value = self._value_list_view.get_value(item.GetText()) 290 | self._data_view.display_value(value) 291 | 292 | def filename(self): 293 | """ 294 | Return the filename of the current Registry file as a string. 295 | """ 296 | return self._filename 297 | 298 | def selected_path(self): 299 | """ 300 | Return the Registry key path of the currently selected item. 301 | """ 302 | item = self._tree.GetSelection() 303 | if item: 304 | return self._tree.GetPyData(item)["key"].path() 305 | return False 306 | 307 | def select_path(self, path): 308 | """ 309 | Select a Registry key path specified as a string in the relevant panes. 310 | """ 311 | self._tree.select_path(path) 312 | 313 | 314 | class RegistryFileViewer(wx.Frame): 315 | """ 316 | The main RegView GUI application. 317 | """ 318 | def __init__(self, parent, files): 319 | super(RegistryFileViewer, self).__init__(parent, -1, "Registry File Viewer", size=(800, 600)) 320 | self.CreateStatusBar() 321 | 322 | menu_bar = wx.MenuBar() 323 | file_menu = wx.Menu() 324 | _open = file_menu.Append(ID_FILE_OPEN, '&Open File') 325 | self.Bind(wx.EVT_MENU, self.menu_file_open, _open) 326 | file_menu.AppendSeparator() 327 | _session_save = file_menu.Append(ID_FILE_SESSION_SAVE, '&Save Session') 328 | self.Bind(wx.EVT_MENU, self.menu_file_session_save, _session_save) 329 | _session_open = file_menu.Append(ID_FILE_SESSION_OPEN, '&Open Session') 330 | self.Bind(wx.EVT_MENU, self.menu_file_session_open, _session_open) 331 | file_menu.AppendSeparator() 332 | _exit = file_menu.Append(ID_FILE_EXIT, 'E&xit Program') 333 | self.Bind(wx.EVT_MENU, self.menu_file_exit, _exit) 334 | menu_bar.Append(file_menu, "&File") 335 | 336 | tab_menu = wx.Menu() 337 | _close = tab_menu.Append(ID_TAB_CLOSE, '&Close') 338 | self.Bind(wx.EVT_MENU, self.menu_tab_close, _close) 339 | menu_bar.Append(tab_menu, "&Tab") 340 | 341 | help_menu = wx.Menu() 342 | _about = help_menu.Append(ID_HELP_ABOUT, '&About') 343 | self.Bind(wx.EVT_MENU, self.menu_help_about, _about) 344 | menu_bar.Append(help_menu, "&Help") 345 | self.SetMenuBar(menu_bar) 346 | 347 | p = wx.Panel(self) 348 | self._nb = wx.Notebook(p) 349 | 350 | for filename in files: 351 | self._open_registry_file(filename) 352 | 353 | sizer = wx.BoxSizer(wx.VERTICAL) 354 | sizer.Add(self._nb, 1, wx.EXPAND) 355 | p.SetSizer(sizer) 356 | self.Layout() 357 | 358 | def _open_registry_file(self, filename): 359 | """ 360 | Open a Registry file by filename into a new tab and return the window. 361 | """ 362 | with open(filename, "rb") as f: 363 | registry = Registry.Registry(f) 364 | view = RegistryFileView(self._nb, registry=registry, filename=filename) 365 | self._nb.AddPage(view, basename(filename)) 366 | return view 367 | # TODO handle error 368 | 369 | def menu_file_open(self, evt): 370 | dialog = wx.FileDialog(None, "Choose Registry File", "", "", "*", wx.OPEN) 371 | if dialog.ShowModal() != wx.ID_OK: 372 | return 373 | filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename()) 374 | self._open_registry_file(filename) 375 | 376 | def menu_file_exit(self, evt): 377 | sys.exit(0) 378 | 379 | def menu_file_session_open(self, evt): 380 | self._nb.DeleteAllPages() 381 | 382 | dialog = wx.FileDialog(None, "Open Session File", "", "", "*", wx.OPEN) 383 | if dialog.ShowModal() != wx.ID_OK: 384 | return 385 | filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename()) 386 | with open(filename, "rb") as f: 387 | t = f.read() 388 | 389 | lines = t.split("\n") 390 | 391 | if len(lines) % 2 != 1: # there is a trailing newline 392 | self.SetStatusText("Malformed session file!") 393 | return 394 | 395 | while len(lines) > 1: 396 | filename = lines.pop(0) 397 | path = lines.pop(0) 398 | 399 | view = self._open_registry_file(filename) 400 | view.select_path(path.partition("\\")[2]) 401 | 402 | self.SetStatusText("Opened session") 403 | 404 | def menu_file_session_save(self, evt): 405 | dialog = wx.FileDialog(None, "Save Session File", "", "", "*", wx.SAVE) 406 | if dialog.ShowModal() != wx.ID_OK: 407 | return 408 | filename = os.path.join(dialog.GetDirectory(), dialog.GetFilename()) 409 | with open(filename, "wb") as f: 410 | for i in range(0, self._nb.GetPageCount()): 411 | page = self._nb.GetPage(i) 412 | f.write(page.filename() + "\n") 413 | 414 | path = page.selected_path() 415 | if path: 416 | f.write(path) 417 | f.write("\n") 418 | self.SetStatusText("Saved session") 419 | # TODO handle error 420 | 421 | def menu_tab_close(self, evt): 422 | self._nb.RemovePage(self._nb.GetSelection()) 423 | 424 | def menu_help_about(self, evt): 425 | wx.MessageBox("regview.py, a part of `python-registry`\n\nhttp://www.williballenthin.com/registry/", "info") 426 | 427 | 428 | if __name__ == '__main__': 429 | app = wx.App(False) 430 | 431 | filenames = [] 432 | filenames = sys.argv[1:] 433 | 434 | frame = RegistryFileViewer(None, filenames) 435 | frame.Show() 436 | app.MainLoop() 437 | -------------------------------------------------------------------------------- /Registry/Registry.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | # This file is part of python-registry. 4 | # 5 | # Copyright 2011, 2012 Willi Ballenthin 6 | # while at Mandiant 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | from __future__ import print_function 20 | 21 | import sys 22 | import ntpath 23 | from enum import Enum 24 | 25 | from . import RegistryParse 26 | 27 | RegSZ = 0x0001 28 | RegExpandSZ = 0x0002 29 | RegBin = 0x0003 30 | RegDWord = 0x0004 31 | RegMultiSZ = 0x0007 32 | RegQWord = 0x000B 33 | RegNone = 0x0000 34 | RegBigEndian = 0x0005 35 | RegLink = 0x0006 36 | RegResourceList = 0x0008 37 | RegFullResourceDescriptor = 0x0009 38 | RegResourceRequirementsList = 0x000A 39 | RegFileTime = 0x0010 40 | 41 | DEVPROP_MASK_TYPE = 0x00000FFF 42 | 43 | class HiveType(Enum): 44 | UNKNOWN = "" 45 | NTUSER = "ntuser.dat" 46 | SAM = "sam" 47 | SECURITY = "security" 48 | SOFTWARE = "software" 49 | SYSTEM = "system" 50 | USRCLASS = "usrclass.dat" 51 | BCD = "bcd" 52 | COMPONENTS = "components" 53 | DEFAULT = "default" 54 | SCHEMA = "schema.dat" 55 | SETTINGS = "settings.dat" 56 | 57 | 58 | class RegistryKeyHasNoParentException(RegistryParse.RegistryStructureDoesNotExist): 59 | """ 60 | """ 61 | def __init__(self, value): 62 | """ 63 | Constructor. 64 | Arguments: 65 | - `value`: A string description. 66 | """ 67 | super(RegistryKeyHasNoParentException, self).__init__(value) 68 | 69 | def __str__(self): 70 | return "Registry key has no parent key: %s" % (self._value) 71 | 72 | 73 | class RegistryKeyNotFoundException(RegistryParse.RegistryStructureDoesNotExist): 74 | """ 75 | """ 76 | def __init__(self, value): 77 | """ 78 | 79 | Arguments: 80 | - `value`: 81 | """ 82 | super(RegistryKeyNotFoundException, self).__init__(value) 83 | 84 | def __str__(self): 85 | return "Registry key not found: %s" % (self._value) 86 | 87 | class RegistryValueNotFoundException(RegistryParse.RegistryStructureDoesNotExist): 88 | """ 89 | """ 90 | def __init__(self, value): 91 | """ 92 | 93 | Arguments: 94 | - `value`: 95 | """ 96 | super(RegistryValueNotFoundException, self).__init__(value) 97 | 98 | def __str__(self): 99 | return "Registry value not found: %s" % (self._value) 100 | 101 | class RegistryValue(object): 102 | """ 103 | This is a high level structure for working with the Windows Registry. 104 | It represents the 3-tuple of (name, type, value) associated with 105 | a registry value. 106 | """ 107 | def __init__(self, vkrecord): 108 | self._vkrecord = vkrecord 109 | 110 | def __repr__(self): 111 | return 'RegistryValue(name="{0}", value="{1}", type="{2}")'.format(self.name(), self.value(), self.value_type_str()) 112 | 113 | def name(self): 114 | """ 115 | Get the name of the value as a string. 116 | The name of the default value is returned as "(default)". 117 | """ 118 | if self._vkrecord.has_name(): 119 | return self._vkrecord.name() 120 | else: 121 | return "(default)" 122 | 123 | def value_type(self): 124 | """ 125 | Get the type of the value as an integer constant. 126 | 127 | One of: 128 | - RegSZ = 0x0001 129 | - RegExpandSZ = 0x0002 130 | - RegBin = 0x0003 131 | - RegDWord = 0x0004 132 | - RegMultiSZ = 0x0007 133 | - RegQWord = 0x000B 134 | - RegNone = 0x0000 135 | - RegBigEndian = 0x0005 136 | - RegLink = 0x0006 137 | - RegResourceList = 0x0008 138 | - RegFullResourceDescriptor = 0x0009 139 | - RegResourceRequirementsList = 0x000A 140 | - RegUint8 = 0x101 141 | - RegInt16 = 0x102 142 | - RegUint16 = 0x103 143 | - RegInt32 = 0x104 144 | - RegUint32 = 0x105 145 | - RegInt64 = 0x106 146 | - RegUint64 = 0x107 147 | - RegFloat = 0x108 148 | - RegDouble = 0x109 149 | - RegUnicodeChar = 0x10A 150 | - RegBoolean = 0x10B 151 | - RegUnicodeString = 0x10C 152 | - RegCompositeValue = 0x10D 153 | - RegDateTimeOffset = 0x10E 154 | - RegTimeSpan = 0x10F 155 | - RegGUID = 0x110 156 | - RegUnk111 = 0x111 157 | - RegUnk112 = 0x112 158 | - RegUnk113 = 0x113 159 | - RegBytesArray = 0x114 160 | - RegInt16Array = 0x115 161 | - RegUint16Array = 0x116 162 | - RegInt32Array = 0x117 163 | - RegUInt32Array = 0x118 164 | - RegInt64Array = 0x119 165 | - RegUInt64Array = 0x11A 166 | - RegFloatArray = 0x11B 167 | - RegDoubleArray = 0x11C 168 | - RegUnicodeCharArray = 0x11D 169 | - RegBooleanArray = 0x11E 170 | - RegUnicodeStringArray = 0x11F 171 | """ 172 | return self._vkrecord.data_type() 173 | 174 | def value_type_str(self): 175 | """ 176 | Get the type of the value as a string. 177 | 178 | One of: 179 | - RegSZ 180 | - RegExpandSZ 181 | - RegBin 182 | - RegDWord 183 | - RegMultiSZ 184 | - RegQWord 185 | - RegNone 186 | - RegBigEndian 187 | - RegLink 188 | - RegResourceList 189 | - RegFullResourceDescriptor 190 | - RegResourceRequirementsList 191 | - RegUint8 192 | - RegInt16 193 | - RegUint16 194 | - RegInt32 195 | - RegUint32 196 | - RegInt64 197 | - RegUint64 198 | - RegFloat 199 | - RegDouble 200 | - RegUnicodeChar 201 | - RegBoolean 202 | - RegUnicodeString 203 | - RegCompositeValue 204 | - RegDateTimeOffset 205 | - RegTimeSpan 206 | - RegGUID 207 | - RegUnk111 208 | - RegUnk112 209 | - RegUnk113 210 | - RegBytesArray 211 | - RegInt16Array 212 | - RegUint16Array 213 | - RegInt32Array 214 | - RegUInt32Array 215 | - RegInt64Array 216 | - RegUInt64Array 217 | - RegFloatArray 218 | - RegDoubleArray 219 | - RegUnicodeCharArray 220 | - RegBooleanArray 221 | - RegUnicodeStringArray 222 | """ 223 | return self._vkrecord.data_type_str() 224 | 225 | def value(self, overrun=0): 226 | return self._vkrecord.data(overrun) 227 | 228 | def raw_data(self, overrun=0): 229 | return self._vkrecord.raw_data(overrun) 230 | 231 | def timestamp(self): 232 | """ 233 | Get the last modified timestamp as a Python datetime. Only valid for 234 | AppContainer settings.dat reg hive 235 | """ 236 | return self._vkrecord.timestamp() 237 | 238 | 239 | class RegistryKey(object): 240 | """ 241 | A high level structure for use in traversing the Windows Registry. 242 | A RegistryKey is a node in a tree-like structure. 243 | A RegistryKey may have a set of values associated with it, 244 | as well as a last modified timestamp. 245 | """ 246 | def __init__(self, nkrecord): 247 | """ 248 | 249 | Arguments: 250 | - `NKRecord`: 251 | """ 252 | self._nkrecord = nkrecord 253 | 254 | def __str__(self): 255 | return "Registry Key %s with %d values and %d subkeys" % \ 256 | (self.path(), len(self.values()), len(self.subkeys())) 257 | 258 | def __repr__(self): 259 | return 'RegistryKey(name="{0}", path="{1}")'.format(self.name(), self.path()) 260 | 261 | 262 | def __getitem__(self, key): 263 | return self.value(key) 264 | 265 | def timestamp(self): 266 | """ 267 | Get the last modified timestamp as a Python datetime. 268 | """ 269 | return self._nkrecord.timestamp() 270 | 271 | def name(self): 272 | """ 273 | Get the name of the key as a string. 274 | 275 | For example, "Windows" if the key path were 276 | /{hive name}/SOFTWARE/Microsoft/Windows 277 | See RegistryKey.path() to get the complete key name. 278 | """ 279 | return self._nkrecord.name() 280 | 281 | def path(self): 282 | """ 283 | Get the full path of the RegistryKey as a string. 284 | For example, "/{hive name}/SOFTWARE/Microsoft/Windows" 285 | """ 286 | return self._nkrecord.path() 287 | 288 | def parent(self): 289 | """ 290 | Get the parent RegistryKey of this key, or raise 291 | RegistryKeyHasNoParentException if it does not exist (for example, 292 | the root key has no parent). 293 | """ 294 | # there may be a memory inefficiency here, since we create 295 | # a new RegistryKey from the NKRecord parent key, rather 296 | # than using the parent of this instance, if it exists. 297 | try: 298 | return RegistryKey(self._nkrecord.parent_key()) 299 | except RegistryParse.ParseException: 300 | raise RegistryKeyHasNoParentException(self.name()) 301 | 302 | def subkeys(self): 303 | """ 304 | Return a list of all subkeys. 305 | Each element in the list is a RegistryKey. 306 | If the key has no subkeys, the empty list is returned. 307 | """ 308 | if self._nkrecord.subkey_number() == 0: 309 | return [] 310 | 311 | l = self._nkrecord.subkey_list() 312 | return [RegistryKey(k) for k in l.keys()] 313 | 314 | def subkey(self, name): 315 | """ 316 | Return the subkey with a given name as a RegistryKey. 317 | Raises RegistryKeyNotFoundException if the subkey with 318 | the given name does not exist. 319 | """ 320 | if self._nkrecord.subkey_number() == 0: 321 | raise RegistryKeyNotFoundException(self.path() + "\\" + name) 322 | 323 | for k in self._nkrecord.subkey_list().keys(): 324 | if k.name().lower() == name.lower(): 325 | return RegistryKey(k) 326 | raise RegistryKeyNotFoundException(self.path() + "\\" + name) 327 | 328 | def values(self): 329 | """ 330 | Return a list containing the values associated with this RegistryKey. 331 | Each element of the list will be a RegistryValue. 332 | If there are no values associated with this RegistryKey, then the 333 | empty list is returned. 334 | """ 335 | try: 336 | return [RegistryValue(v) for v in self._nkrecord.values_list().values()] 337 | except RegistryParse.RegistryStructureDoesNotExist: 338 | return [] 339 | 340 | def value(self, name): 341 | """ 342 | Return the value with the given name as a RegistryValue. 343 | Raises RegistryValueNotFoundExceptiono if the value with 344 | the given name does not exist. 345 | """ 346 | if name == "(default)": 347 | name = "" 348 | try: 349 | for v in self._nkrecord.values_list().values(): 350 | if v.name().lower() == name.lower(): 351 | return RegistryValue(v) 352 | except RegistryParse.RegistryStructureDoesNotExist: 353 | raise RegistryValueNotFoundException(self.path() + " : " + name) 354 | raise RegistryValueNotFoundException(self.path() + " : " + name) 355 | 356 | def find_key(self, path): 357 | """ 358 | Perform a search for a RegistryKey with a specific path. 359 | """ 360 | if len(path) == 0: 361 | return self 362 | 363 | (immediate, _, future) = path.partition("\\") 364 | return self.subkey(immediate).find_key(future) 365 | 366 | def values_number(self): 367 | """ 368 | Return the number of values associated with this key 369 | """ 370 | return self._nkrecord.values_number() 371 | 372 | def subkeys_number(self): 373 | """ 374 | Return the number of subkeys associated with this key 375 | """ 376 | return self._nkrecord.subkey_number() 377 | 378 | 379 | class Registry(object): 380 | """ 381 | A class for parsing and reading from a Windows Registry file. 382 | """ 383 | def __init__(self, filelikeobject): 384 | """ 385 | Constructor. 386 | Arguments: 387 | - `filelikeobject`: A file-like object with a .read() method. 388 | If a Python string is passed, it is interpreted as a filename, 389 | and the corresponding file is opened. 390 | """ 391 | try: 392 | self._buf = filelikeobject.read() 393 | except AttributeError: 394 | with open(filelikeobject, "rb") as f: 395 | self._buf = f.read() 396 | self._regf = RegistryParse.REGFBlock(self._buf, 0, False) 397 | 398 | def __repr__(self): 399 | return 'Registry(hive_name="{0}", hive_type="{1}")'.format(self.hive_name(), self.hive_type()) 400 | 401 | def hive_name(self): 402 | """Returns the internal file name""" 403 | return self._regf.hive_name() 404 | 405 | def hive_type(self): 406 | """Returns the hive type""" 407 | temp = self.hive_name() 408 | temp = temp.replace('\\??\\', '') 409 | temp = ntpath.basename(temp) 410 | 411 | if temp.lower() == HiveType.NTUSER.value: 412 | return HiveType.NTUSER 413 | elif temp.lower() == HiveType.SAM.value: 414 | return HiveType.SAM 415 | elif temp.lower() == HiveType.SECURITY.value: 416 | return HiveType.SECURITY 417 | elif temp.lower() == HiveType.SOFTWARE.value: 418 | return HiveType.SOFTWARE 419 | elif temp.lower() == HiveType.SYSTEM.value: 420 | return HiveType.SYSTEM 421 | elif temp.lower() == HiveType.USRCLASS.value: 422 | return HiveType.USRCLASS 423 | elif temp.lower() == HiveType.BCD.value: 424 | return HiveType.BCD 425 | elif temp.lower() == HiveType.COMPONENTS.value: 426 | return HiveType.COMPONENTS 427 | elif temp.lower() == HiveType.DEFAULT.value: 428 | return HiveType.DEFAULT 429 | elif temp.lower() == HiveType.SCHEMA.value: 430 | return HiveType.SCHEMA 431 | elif temp.lower() == HiveType.SETTINGS.value: 432 | return HiveType.SETTINGS 433 | else: 434 | return HiveType.UNKNOWN 435 | 436 | def root(self): 437 | """ 438 | Return the first RegistryKey in the hive. 439 | """ 440 | return RegistryKey(self._regf.first_key()) 441 | 442 | def open(self, path): 443 | """ 444 | Return a RegistryKey by full path. 445 | Subkeys are separated by the backslash character ('\'). 446 | A trailing backslash may or may not be present. 447 | The hive name should not be included. 448 | """ 449 | # is the first registry key always the root? 450 | # are there any other keys at this 451 | # level? is this the name of the hive? 452 | return RegistryKey(self._regf.first_key()).find_key(path) 453 | 454 | def print_all(key): 455 | if len(key.subkeys()) == 0: 456 | print(key.path()) 457 | else: 458 | for k in key.subkeys(): 459 | print_all(k) 460 | 461 | if __name__ == '__main__': 462 | r = Registry(sys.argv[1]) 463 | print_all(r.root()) 464 | -------------------------------------------------------------------------------- /documentation/WinReg.txt: -------------------------------------------------------------------------------- 1 | Agreement: 2 | ========== 3 | 4 | The author of this document will not be responsible for any damage and/or 5 | license violation that may occur. The information within this document is 6 | provided "as is" without warranty of any kind... 7 | This information was "collected" during sleepless nights, and is NOT 8 | officially released by Microsoft! It shall give you a peek at the Windows(tm) 9 | internals to give you a chance to recover from corrupted data. 10 | 11 | The author has nothing to do with Microsoft, except that he uses their 12 | products... 13 | 14 | If you don't agree with this, stop reading this document, and delete it at 15 | once! 16 | 17 | 18 | History: 19 | ======== 20 | 21 | What is the registry? Where did it came from? Two questions, which I will try to 22 | answer here. The registry is a database (at least microsoft thinks so:) 23 | which contains configuration information about the system. 24 | It mainly is a memory dump which is saved to one or more files on the windows 25 | host drive. It is loaded every system-boot and remains resident until 26 | shutdown. Since parts of it are not used during normal operation it will be 27 | swapped out very soon. The registry appeared with windows 3.?? (sorry, I can't 28 | remember any earlier version :-), where it was used for file associations and 29 | the "OLE" functions (the conection between ole-id's and the applications). 30 | This is a critical information and since the registry has (almost) NO 31 | CHECKSUM information (!), it sometimes gets corrupted. This is the main 32 | reason for this doc. 33 | 34 | Using windows 3.x, almost every configuration was done using good old ".INI"- 35 | files, which were readable but slow and limited in size (64k). In windows 95 36 | (and NT), the registry was used instead of these files. So, to edit a 37 | particular setting, you would have to run the application which manages these 38 | settings. :( but what if this app won't start? MS included a tool named 39 | REGEDIT in windows 3.?? and 95, and a REGEDT32 in windows NT. You can use 40 | these apps to edit ALL contents of the registry (in windows NT the registry 41 | supports security, as well as it provides the security for the whole system!) 42 | 43 | An application can open a "key", write values (variables) to it and fill them 44 | with data. Each key represents also a value called "default" and can contain 45 | any number of sub-keys. This will form a tree-structure as you can see at 46 | the left half of REGEDIT. (note: REGEDIT from windows 3.?? has to be started 47 | with /V or /Y, I can't remember now) 48 | 49 | 50 | Where can I find the registry??? 51 | ================================ 52 | 53 | That differs for each windows-version: 54 | 55 | Version File(s) Contents 56 | 3.1x REG.DAT Complete windows 3.?? Registry 57 | 58 | 95 SYSTEM.DAT System-values (HKEY_LOCAL_MACHINE) 59 | USER.DAT User-values (HKEY_USERS) 60 | 61 | NT SYSTEM32\CONFIG\SAM SAM-part of the registry (=NT Security) 62 | SYSTEM32\CONFIG\SOFTWARE Software-Specific part 63 | (HKEY_LOCAL_MACHINE\SOFTWARE) 64 | SYSTEM32\CONFIG\SYSTEM System-specific part 65 | (HKEY_LOCAL_MACHINE\System) 66 | PROFILES\%USERNAME%\NTUSER.DAT User-Specific part 67 | (HKEY_CURRENT_USER\{S-1-xxx...}) 68 | PROFILES\%USERNAME%\NTUSER.MAN like NTUSER.DAT but a 69 | MANDATORY-profile 70 | 71 | If you are using a ROAMING-profile with windows NT, NTUSER.xxx can be on 72 | a network-share as well... 73 | 74 | 75 | 76 | Terms 77 | ===== 78 | 79 | The registry consists of the following elements: 80 | 81 | Hive: strating point of the structure. The name of an hive starts 82 | with the "HKEY_"-prefix. Can be seen as a "drive" in a file 83 | system. 84 | 85 | Hive name Beschreibung 3.1 95 NT4 86 | HKEY_CLASSES_ROOT Points to the "class" key in 87 | the "HKEY_LOCAL_MACHINE" hive, 88 | the only hive in windows 3.?? X X X 89 | 90 | HKEY_CURRENT_USER Information and settings valid 91 | for the currently logged in 92 | user. (Points to the correct X X 93 | key under "HKEY_USERS") 94 | 95 | HKEY_CURRENT_CONFIG Settings for the currently 96 | active hardware profile. 97 | Points to "HKEY_LOCAL_MACHINE\ X X 98 | CONTROL\CONTROLSETxxx 99 | 100 | HKEY_USERS Contains all currently active 101 | user settings. Since NT is a 102 | single user system, there 103 | will be only one key (the S-ID X X 104 | of the active user), and a 105 | ".DEFUALT" key (The settings 106 | for the CTRL-ALT-DEL environment) 107 | 108 | HKEY_LOCALMACHINE All local settings X X 109 | 110 | HKEY_DYN_DATA As the name says, here you'll find X 111 | dynamic data (CPU-usage,...) 112 | 113 | 114 | Key: A key to the registry can be seen as a directory in a file 115 | system. 116 | Value: can be seen as the registrys "file" 117 | Data: is the actual setting, can be seen as the contents of a 118 | file 119 | 120 | 121 | Windows 3.x 122 | =========== 123 | 124 | This registry is the easiest one. It consists of 3 blocks, which are not 125 | "signed" at all: 126 | 127 | Block Position Size 128 | Header 0 32 Bytes 129 | Navigation-Info 0x00000020 ??? 130 | Data-Block ??? ??? 131 | 132 | The "???" marked values can be read from the header. 133 | 134 | Header 135 | ====== 136 | 137 | Offset Size Description 138 | 0x0000 8 Byte ASCII-Text: "SHCC3.10" 139 | 0x0008 D-Word ? 140 | 0x000C D-Word ? (always equal the D-Word at 0x0008) 141 | 0x0010 D-Word Number of entrys in the navigation-block 142 | 0x0014 D-Word Offset of the data-block 143 | 0x0018 D-Word Size of the data-block 144 | 0x001C Word ? 145 | 0x001E Word ? 146 | 147 | Values marked "?" are not important for a read-access, and therefore unknown 148 | to me... 149 | 150 | Navigation-Block 151 | ================ 152 | 153 | This is where chaos rules! It consists of two different, 8 byte long blocks: 154 | 155 | * Navigation-Info-Record, 156 | * Text-Info-Record 157 | 158 | The first record in the navigation block is a navigation info record. 159 | 160 | Navigation-Info-Record 161 | 162 | Offset Size Contents 163 | 0x00 Word Next Key (same level) 164 | 0x02 Word First Sub-Key (one level deeper) 165 | 0x04 Word Text-Info-Record Key-Namens 166 | 0x06 Word Text-Info-Record Key-Value (default) 167 | 168 | The values are the locical number of the block inside the file: 169 | 170 | offset=blocksize*blocknumber+headersize 171 | 172 | since 2 of this values are constant: 173 | 174 | offset=8*blocknumber+0x20 175 | 176 | 177 | Text-Info-Record 178 | ================ 179 | 180 | 181 | Offset Size Contents 182 | 0x00 Word ? 183 | 0x02 Word number of references to this text 184 | 0x04 Word Text-length 185 | 0x06 Word Offset of the text-string inside the data-block 186 | 187 | To get the text-offset inside the file you have to add this offset to the 188 | data-offset inside the header. 189 | 190 | Data-Block 191 | ========== 192 | 193 | The data-block only consists of a collection of text-strings. Right in front 194 | of every text is a word which may or may not have a meaning. The offset in 195 | the text-info record points directly to the text, the text-size has to be 196 | defined in the text-info record too. 197 | 198 | 199 | Windows 95 200 | ========== 201 | 202 | the Windows95-Registry Files: 203 | 204 | inside the windows-directory (default: C:\WINDOWS) are 2 files which are 205 | loaded to form the registry: 206 | 207 | SYSTEM.DAT 208 | 209 | and 210 | 211 | USER.DAT 212 | 213 | This files are mapped to the following hives: 214 | 215 | HKEY_LOCAL_MACHINE in SYSTEM.DAT 216 | 217 | and 218 | 219 | HKEY_USERS in USER.DAT 220 | 221 | 222 | 223 | The file structure: 224 | =================== 225 | 226 | 227 | Both files have the same structure. Each of them consists of 3 blocks where 228 | 1 of these blocks can be repeated. 229 | Every block has a 4 byte long signature to help identify its contents. 230 | 231 | ID Block-contents Max. size 232 | CREG Header 32 Bytes @ Offset 0 233 | RGKN Directory information 234 | (Tree-structure) ??? @ Offset 32 235 | RGDB The real data 236 | (Values and data) max. 65535 Bytes an Offset ?? 237 | 238 | these blocks are "sticked together" with no space between them, but always 239 | a multiple of 16 in size. 240 | 241 | the CREG-Block 242 | ============== 243 | 244 | Offset Size Inhalt 245 | 0x00000000 D-Word ASCII-"CREG" = 0x47455243 246 | 0x00000008 D-Word Offset of 1st RGDB-block 247 | 0x00000010 D-Word # of RGDB-blocks 248 | 249 | all other values are not needed to read the registry... 250 | 251 | 252 | the RGKN-Block 253 | ============== 254 | 255 | I assume that RGKN stands for ReGistry-Key-Navigation. This block contains 256 | the information needed to built the tree-structure of the registry. This 257 | block will be larger then 65536 bytes (0xFFFF)! 258 | 259 | All offset-values are RELATIVE to the RGKN-block! 260 | 261 | Offset Size Contents 262 | 0x00000000 D-Word ASCII-"RGKN" = 0x4E4B4752 263 | 0x00000004 D-Word Size of the RGKN-block in bytes 264 | 0x00000008 D-Word Rel. Offset of the root-record 265 | 0x00000020 ???? Tree-Records (often the 1st Record) 266 | 267 | the Tree-Record 268 | =============== 269 | 270 | The tree-record is a "complete" registry-key. It contains the "hash"-info 271 | for the real data stored in this key. 272 | 273 | Offset Size Contents 274 | 0x0000 D-Word Always 0 275 | 0x0004 D-Word Hash of the key-name 276 | 0x0008 D-Word Always -1 (0xFFFFFFFF) 277 | 0x000C D-Word Offset of the owner (parent)-records 278 | 0x0010 D-Word Offset of the 1st sub-sey record 279 | 0x0014 D-Word Offset of the next record in this level 280 | 0x0018 D-Word ID-number of the real key 281 | 282 | the 1st entry in a "usual" registry file is a nul-entry with subkeys: the 283 | hive itself. It looks the same like other keys. Even the ID-number can 284 | be any value. 285 | 286 | The "hash"-value is a value representing the key's name. Windows will not 287 | search for the name, but for a matching hash-value. if it finds one, it 288 | will compare the actual string info, otherwise continue with the next key. 289 | 290 | End of list-pointers are filled with -1 (0xFFFFFFFF) 291 | 292 | 293 | The ID-field has the following format: 294 | 295 | Bits 31..16: Number of the corresponding RGDB-blocks 296 | Bits 15..0: continuous number inside this RGDB-block. 297 | 298 | 299 | 300 | The hash-method: 301 | ================ 302 | 303 | you are looking for the key: Software\Microsoft 304 | 305 | first you take the first part of the string and convert it to upper case 306 | 307 | SOFTWARE 308 | 309 | The "\" is used as a seperator only and has no meaning here. 310 | Next you initialize a D-Word with 0 and add all ASCII-values of the string 311 | which are smaller than 0x80 (128) to this D-Word. 312 | 313 | SOFTWARE = 0x0000026B 314 | 315 | Now you can start looking for this hash-value in the tree-record. 316 | If you want to modify key names, also modify the hash-values, since they 317 | cannot be found again (although they would be displayed in REGEDIT) 318 | 319 | the RGDB-Block 320 | ============== 321 | 322 | Header: 323 | 324 | Offset Size Contents 325 | 0x0000 D-Word ASCII-"RGDB" = 0x42444752 326 | 0x0004 D-Word Size of this RGDB-block 327 | 0x0020 ???? RGDB Records 328 | 329 | 330 | RGDB-Record (Key-Information) 331 | ============================= 332 | 333 | Offset Size Contents 334 | 0x0000 D-Word record length in bytes 335 | 0x0004 D-Word ID-number 336 | 0x0008 D-Word ??? Size ??? 337 | 0x000C Word text length of key name 338 | 0x000E Word Number of values inside this key 339 | 0x0010 D-Word always 0 340 | 0x0014 ???? Key-name 341 | 0x???? ???? Values 342 | 343 | The first size (record length) can be used to find the next record. 344 | The second size value is only correct if the key has at least one value, 345 | otherwise it is a little lower. 346 | 347 | The key-name is not 0-terminated, its length is defined by the key- 348 | text length field. The values are stored as records. 349 | 350 | 351 | Value-Record 352 | ============ 353 | 354 | Offset Size Contents 355 | 0x0000 D-Word Type of data 356 | 0x0004 D-Word always 0 357 | 0x0008 Word length of value-name 358 | 0x000A Word length of value-data 359 | 0x000C ???? value-name 360 | 0x???? ???? data 361 | 362 | Data-Types 363 | ========== 364 | 365 | value Contents 366 | 0x00000001 RegSZ - 0-terminated string (sometimes without the 0!) 367 | 0x00000003 RegBin - binary value (a simple data-block) 368 | 0x00000004 RegDWord - D-Word (always 4 bytes in size) 369 | 370 | 371 | 372 | Windows NT (Version 4.0) 373 | ======================== 374 | 375 | Whoever thought that the registry of windows 95 and windows nt are similar 376 | will be surprised! They only look much the same, but have completely other 377 | structures! 378 | Since the RGDB-blocks in the windows 95 registry are not larger than 379 | 0xFFFF, we can see that it is optimized for a 16-bit OS... 380 | Windows NT stores its registry in a page-oriented format with blocks 381 | of 4kb (4096 = 0x1000 bytes) 382 | 383 | The windows NT registry has 2 different blocks, where one can occure many 384 | times... 385 | 386 | the "regf"-Block 387 | ================ 388 | 389 | "regf" is obviosly the abbreviation for "Registry file". "regf" is the 390 | signature of the header-block which is always 4kb in size, although only 391 | the first 64 bytes seem to be used and a checksum is calculated over 392 | the first 0x200 bytes only! 393 | 394 | Offset Size Contents 395 | 0x00000000 D-Word ID: ASCII-"regf" = 0x66676572 396 | 0x00000004 D-Word ???? 397 | 0x00000008 D-Word ???? Always the same value as at 0x00000004 398 | 0x0000000C Q-Word last modify date in WinNT date-format 399 | 0x00000014 D-Word 1 400 | 0x00000018 D-Word 3 401 | 0x0000001C D-Word 0 402 | 0x00000020 D-Word 1 403 | 0x00000024 D-Word Offset of 1st key record 404 | 0x00000028 D-Word Size of the data-blocks (Filesize-4kb) 405 | 0x0000002C D-Word 1 406 | 0x000001FC D-Word Sum of all D-Words from 0x00000000 to 0x000001FB 407 | 408 | I have analyzed more registry files (from multiple machines running 409 | NT 4.0 german version) and could not find an explanation for the values 410 | marked with ???? the rest of the first 4kb page is not important... 411 | 412 | 413 | the "hbin"-Block 414 | ================ 415 | 416 | I don't know what "hbin" stands for, but this block is always a multiple 417 | of 4kb in size. 418 | 419 | Inside these hbin-blocks the different records are placed. The memory- 420 | management looks like a C-compiler heap management to me... 421 | 422 | 423 | hbin-Header 424 | =========== 425 | 426 | Offset Size Contents 427 | 0x0000 D-Word ID: ASCII-"hbin" = 0x6E696268 428 | 0x0004 D-Word Offset from the 1st hbin-Block 429 | 0x0008 D-Word Offset to the next hbin-Block 430 | 0x001C D-Word Block-size 431 | 432 | The values in 0x0008 and 0x001C should be the same, so I don't know 433 | if they are correct or swapped... 434 | 435 | From offset 0x0020 inside a hbin-block data is stored with the following 436 | format: 437 | 438 | 439 | Offset Size Contents 440 | 0x0000 D-Word Data-block size 441 | 0x0004 ???? Data 442 | 443 | If the size field is negative (bit 31 set), the corresponding block 444 | is free and has a size of -blocksize! 445 | The data is stored as one record per block. Block size is a multiple 446 | of 4 and the last block reaches the next hbin-block, leaving no room. 447 | 448 | 449 | Records in the hbin-blocks 450 | ========================== 451 | 452 | 453 | nk-Record 454 | 455 | The nk-record can be treated as a kombination of tree-record and 456 | key-record of the win 95 registry. 457 | 458 | lf-Record 459 | 460 | The lf-record is the counterpart to the RGKN-record (the hash-function) 461 | 462 | vk-Record 463 | 464 | The vk-record consists information to a single value. 465 | 466 | sk-Record 467 | 468 | sk (? Security Key ?) is the ACL of the registry. 469 | 470 | Value-Lists 471 | 472 | The value-lists contain information about which values are inside a 473 | sub-key and don't have a header. 474 | 475 | Datas 476 | 477 | The datas of the registry are (like the value-list) stored without a 478 | header. 479 | 480 | 481 | All offset-values are relative to the first hbin-block and point to the block- 482 | size field of the record-entry. to get the file offset, you have to add 483 | the header size (4kb) and the size field (4 bytes)... 484 | 485 | the nk-Record 486 | ============= 487 | 488 | Offset Size Contents 489 | 0x0000 Word ID: ASCII-"nk" = 0x6B6E 490 | 0x0002 Word for the root-key: 0x2C, otherwise 0x20 491 | 0x0004 Q-Word write-date/time in windows nt notation 492 | 0x0010 D-Word Offset of Owner/Parent key 493 | 0x0014 D-Word number of sub-Keys 494 | 0x001C D-Word Offset of the sub-key lf-Records 495 | 0x0024 D-Word number of values 496 | 0x0028 D-Word Offset of the Value-List 497 | 0x002C D-Word Offset of the sk-Record 498 | 0x0030 D-Word Offset of the Class-Name 499 | 0x0044 D-Word Unused (data-trash) 500 | 0x0048 Word name-length 501 | 0x004A Word class-name length 502 | 0x004C ???? key-name 503 | 504 | the Value-List 505 | ============== 506 | 507 | Offset Size Contents 508 | 0x0000 D-Word Offset 1st Value 509 | 0x0004 D-Word Offset 2nd Value 510 | 0x???? D-Word Offset nth Value 511 | 512 | To determine the number of values, you have to look at the 513 | owner-nk-record! 514 | 515 | Der vk-Record 516 | ============= 517 | 518 | Offset Size Contents 519 | 0x0000 Word ID: ASCII-"vk" = 0x6B76 520 | 0x0002 Word name length 521 | 0x0004 D-Word length of the data 522 | 0x0008 D-Word Offset of Data 523 | 0x000C D-Word Type of value 524 | 0x0010 Word Flag 525 | 0x0012 Word Unused (data-trash) 526 | 0x0014 ???? Name 527 | 528 | If bit 0 of the flag-word is set, a name is present, otherwise the 529 | value has no name (=default) 530 | If the data-size is lower 5, the data-offset value is used to store 531 | the data itself! 532 | 533 | 534 | The data-types 535 | ============== 536 | 537 | Wert Beteutung 538 | 0x0001 RegSZ: character string (in UNICODE!) 539 | 0x0002 ExpandSZ: string with "%var%" expanding (UNICODE!) 540 | 0x0003 RegBin: raw-binary value 541 | 0x0004 RegDWord: Dword 542 | 0x0007 RegMultiSZ: multiple strings, seperated with 0 543 | (UNICODE!) 544 | 545 | The "lf"-record 546 | =============== 547 | 548 | Offset Size Contents 549 | 0x0000 Word ID: ASCII-"lf" = 0x666C 550 | 0x0002 Word number of keys 551 | 0x0004 ???? Hash-Records 552 | 553 | Hash-Record 554 | =========== 555 | 556 | Offset Size Contents 557 | 0x0000 D-Word Offset of corresponding "nk"-Record 558 | 0x0004 D-Word ASCII: the first 4 characters of the key-name, 559 | padded with 0's. Case sensitiv! 560 | 561 | Keep in mind, that the value at 0x0004 is used for checking the 562 | data-consistency! If you change the key-name you have to change the 563 | hash-value too! 564 | 565 | The "sk"-block 566 | ============== 567 | 568 | (due to the complexity of the SAM-info, not clear jet) 569 | 570 | Offset Size Contents 571 | 0x0000 Word ID: ASCII-"sk" = 0x6B73 572 | 0x0002 Word Unused 573 | 0x0004 D-Word Offset of previous "sk"-Record 574 | 0x0008 D-Word Offset of next "sk"-Record 575 | 0x000C D-Word usage-counter 576 | 0x0010 D-Word Size of "sk"-record in bytes 577 | ???? 578 | ???? ???? Security and auditing settings... 579 | ???? 580 | 581 | The usage counter counts the number of references to this 582 | "sk"-record. You can use one "sk"-record for the entire registry! 583 | 584 | 585 | Windows nt date/time format 586 | =========================== 587 | 588 | The time-format is a 64-bit integer which is incremented every 589 | 0,0000001 seconds by 1 (I don't know how accurate it realy is!) 590 | It starts with 0 at the 1st of january 1601 0:00! All values are 591 | stored in GMT time! The time-zone is important to get the real 592 | time! 593 | 594 | 595 | 596 | Common values for win95 and win-nt 597 | ================================== 598 | 599 | Offset values marking an "end of list", are either 0 or -1 (0xFFFFFFFF). 600 | If a value has no name (length=0, flag(bit 0)=0), it is treated as the 601 | "Default" entry... 602 | If a value has no data (length=0), it is displayed as empty. 603 | 604 | 605 | 606 | simplyfied win-3.?? registry: 607 | ============================= 608 | 609 | 610 | 611 | +-----------+ 612 | | next rec. |---+ +-----> +------------+ 613 | | first sub | | | | Usage cnt. | 614 | | name | | +--> +------------+ | | length | 615 | | value | | | | next rec. | | | text |-------> +-------+ 616 | +-----------+ | | | name rec. |--+ +------------+ | xxxxx | 617 | +------------+ | | value rec. |--------> +------------+ +-------+ 618 | v | +------------+ | Usage cnt. | 619 | +-----------+ | | length | 620 | | next rec. | | | text |-------> +-------+ 621 | | first sub |------+ +------------+ | xxxxx | 622 | | name | +-------+ 623 | | value | 624 | +-----------+ 625 | 626 | 627 | 628 | Greatly simplyfied structure of the nt-registry: 629 | ================================================ 630 | 631 | 632 | +-------------------------------------------------------------------------+ 633 | v | 634 | +---------------+ +-------------> +-----------+ +------> +---------+ | 635 | | "nk" | | | lf-rec. | | | nk-rec. | | 636 | | ID | | | # of keys | | | parent |---+ 637 | | Date | | | 1st key |--+ | .... | 638 | | parent | | +-----------+ +---------+ 639 | | suk-keys |-------+ 640 | | values |---------------------> +----------+ 641 | | SK-rec. |---------------+ | 1. value |--> +----------+ 642 | | class |--+ | +----------+ | vk-rec. | 643 | +---------------+ | | | .... | 644 | v | | data |--> +-------+ 645 | +------------+ | +----------+ | xxxxx | 646 | | Class name | | +-------+ 647 | +------------+ | 648 | v 649 | +---------+ +---------+ 650 | +-----> | next sk |---> | Next sk |--+ 651 | | +---| prev sk | <---| prev sk | | 652 | | | | .... | | ... | | 653 | | | +---------+ +---------+ | 654 | | | ^ | 655 | | +--------------------+ | 656 | +------------------------------------+ 657 | 658 | -------------------------------------------------------------------------------- 659 | 660 | Hope this helps.... (Although it was "fun" for me to uncover this things, 661 | it took me several sleepless nights ;) 662 | 663 | B.D. 664 | --------------------------------------------------------------------------------