diff --git a/tests/test_registry.py b/tests/test_registry.py index 411fe38..c092e80 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -34,16 +34,46 @@ def test_registry_set_get_simple_values(value): # TODO: test with other registry type (the stranges ones) @pytest.mark.parametrize("value, type", [ - (0x11223344, gdef.REG_DWORD), - (0x1122334455667788, gdef.REG_QWORD), + (0x11223344, gdef.REG_DWORD), # same as gdef.REG_DWORD_LITTLE_ENDIAN + (0x11223344, gdef.REG_DWORD_BIG_ENDIAN), + (0x1122334455667788, gdef.REG_QWORD), # same as gdef.REG_QWORD_LITTLE_ENDIAN ("", gdef.REG_SZ), + ("Hello world", gdef.REG_SZ), + ("Hello world %path%", gdef.REG_EXPAND_SZ), (["AAAA", "BBBB", "CCCC"], gdef.REG_MULTI_SZ), + # Binary format and associated ("123\x00123" + "".join(chr(c) for c in range(256)), gdef.REG_BINARY), + ("Hello world", gdef.REG_LINK), + ("", gdef.REG_NONE), + ("Not really None :)\x11\x22\x00ABCD", gdef.REG_NONE), + ("Test-Unknown-format", 0x11223344), # Unknown registry type + ("Test-Unknown-format\x00\x01\xff\xfe Lol", 0xffffffff), # Unknown registry type ]) def test_registry_set_get_simple_values_with_types(value, type): basekeytest["tst2"] = (value, type) assert basekeytest["tst2"].value == value + +@pytest.mark.parametrize("value, type", [ + # "\xff\xd8".decode("utf-16") -> UnicodeDecodeError + ("\xff\xd8", gdef.REG_MULTI_SZ), + # Is NOT valid UTF-16 (len == 33) + ("Hello\x00World\x00This is not unicode\x00\x00", gdef.REG_MULTI_SZ), + # Is valid UTF-16 (len == 40) + # Should the decoding be completly different ? + ("Hello\x00World\x00This is not really unicode\x00\x00", gdef.REG_MULTI_SZ), +]) +def test_registry_badly_encoded_values(value, type): + # Bypass any encoding logic to setup bad key + keyname = "bad_encoding" + buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(value) + windows.winproxy.RegSetValueExW(basekeytest.phkey, keyname, 0, type, buffer, len(buffer)) + # Not the best decoded value + # But should not crash + assert basekeytest[keyname] + + + UNICODE_PATH_NAME = u'\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624b' UNICODE_RU_STRING = u"\u0441\u0443\u043a\u0430\u0020\u0431\u043b\u044f\u0442\u044c" # CYKA BLYAT in Cyrillic @@ -51,7 +81,13 @@ UNICODE_RU_STRING = u"\u0441\u0443\u043a\u0430\u0020\u0431\u043b\u044f\u0442\u04 # But was the cause a special bug / reimplem due to _winreg using ANSI functions # So create a special test with a very identifiable name / bug cause -@pytest.mark.parametrize("unistr", ['\u52a9' * 126, UNICODE_PATH_NAME, UNICODE_RU_STRING, u""]) +@pytest.mark.parametrize("unistr", [ + u'c:\\users\\hakril\\appdata\\local\\temp\\test_unicode_\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624bdbqsm3', + '\u52a9' * 126, + UNICODE_PATH_NAME, + UNICODE_RU_STRING, + u"" +]) def test_registry_unicode_string_value(unistr): basekeytest["tst3"] = unistr assert basekeytest["tst3"].value == unistr diff --git a/windows/winobject/registry.py b/windows/winobject/registry.py index 1962530..f0745ea 100644 --- a/windows/winobject/registry.py +++ b/windows/winobject/registry.py @@ -2,7 +2,7 @@ import sys import ctypes import itertools import struct -from collections import namedtuple +from collections import namedtuple, defaultdict import windows from windows.dbgprint import dbgprint @@ -51,6 +51,15 @@ def Reg2Py_DWORD(buffer, size): def Py2Reg_DWORD(obj): return struct.pack("I", obj) + + def Reg2Py_BINARY(buffer, size): return str(bytearray(buffer[:size])) @@ -74,14 +83,18 @@ def Py2Reg_SZ(obj): def Reg2Py_Multi_SZ(buffer, size): if not size: return [] + # Simple path rawstr = "".join([chr(c) for c in buffer[:size]]) - if rawstr[-4:] != "\x00\x00\x00\x00": # 2 UNICODE NULL bytes (4 \x00) - rawstr += "\x00\x00\x00\x00" - # Decode as utf-16 to get multiple unicode string sepated by NULL BYTE - unistr = rawstr.decode(WENCODING) - # Remove final \x00 - unistr = unistr[:-2] # 2 UTF-16 NULL BITS (was 4 bits in encoded) - return unistr.split(u"\x00") # Return as list of string + try: + unistr = rawstr.decode(WENCODING) + return unistr.rstrip(u"\x00").split(u"\x00") + except UnicodeDecodeError as e: + pass + # Complexe-path + # This is not some valide UTF-16 + # Try our best to extract some stuff from raw + return rawstr.rstrip("\x00").split("\x00") + def Py2Reg_Multi_SZ(obj): # Work on encoded values (to prevent str/unicode errors) @@ -91,19 +104,33 @@ def Py2Reg_Multi_SZ(obj): # Add UTF-16 NULL byte for final string + final UTF-16 \x00 (4 \x00) return uni_str + "\x00\x00\x00\x00" - - DECODE_METHOD = 0 ENCODE_METHOD = 1 -ENCODE_DECODE_METHODS = { + +KNOWN_ENCODE_DECODE_METHODS = { gdef.REG_SZ: (Reg2Py_SZ, Py2Reg_SZ), + gdef.REG_EXPAND_SZ: (Reg2Py_SZ, Py2Reg_SZ), gdef.REG_MULTI_SZ: (Reg2Py_Multi_SZ, Py2Reg_Multi_SZ), - gdef.REG_BINARY: (Reg2Py_BINARY, Py2Reg_BINARY), gdef.REG_DWORD: (Reg2Py_DWORD, Py2Reg_DWORD), + gdef.REG_DWORD_BIG_ENDIAN: (Reg2Py_DWORD_BIG_ENDIAN, Py2Reg_DWORD_BIG_ENDIAN), gdef.REG_QWORD: (Reg2Py_QWORD, Py2Reg_QWORD), + # Binary formats + gdef.REG_LINK: (Reg2Py_BINARY, Py2Reg_BINARY), # TESTING + gdef.REG_BINARY: (Reg2Py_BINARY, Py2Reg_BINARY), + gdef.REG_NONE: (Reg2Py_BINARY, Py2Reg_BINARY), } +# All unknown format are seens as binary data +UNKNOWM_FORMAT = (Reg2Py_BINARY, Py2Reg_BINARY) +ENCODE_DECODE_METHODS = defaultdict(lambda: UNKNOWM_FORMAT, KNOWN_ENCODE_DECODE_METHODS) + +def decode_registry_buffer(type, buffer, size): + try: + return ENCODE_DECODE_METHODS[type][DECODE_METHOD](buffer, size) + except UnicodeDecodeError as e: + # Best effort if any decoding error happen + return "".join(chr(c) for c in buffer[:size]) KeyValue = namedtuple("KeyValue", ["name", "value", "type"]) @@ -178,7 +205,7 @@ class PyHKey(object): for i in itertools.count(): name_size.value = default_name_size winproxy.RegEnumKeyExW(self.phkey, i, name_buffer, name_size, None, None, None, None) - res.append(name_buffer.value) + res.append(name_buffer[:name_size.value]) # Will allow key name with \x00 inside return [PyHKey(self, n) for n in res] def get_key_size_info(self): @@ -223,7 +250,7 @@ class PyHKey(object): keyname = ctypes.create_unicode_buffer(namesize.value) datasize = gdef.DWORD(max_data_size) databuffer = windows.utils.BUFFER(gdef.BYTE, nbelt=datasize.value)() - vobj = ENCODE_DECODE_METHODS[value_type.value][DECODE_METHOD](databuffer, datasize.value) + vobj = decode_registry_buffer(value_type.value, databuffer, datasize.value) res.append(KeyValue(keyname.value, vobj, value_type.value)) return res @@ -260,7 +287,7 @@ class PyHKey(object): size.value *= 2 buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=size.value)() continue - vobj = ENCODE_DECODE_METHODS[type.value][DECODE_METHOD](buffer, size.value) + vobj = decode_registry_buffer(type.value, buffer, size.value) return KeyValue(value_name, vobj, type.value) def _guess_value_type(self, value):