From 3f02c07613f313fc65898bedec2cb563ecba6550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Keijzer?= Date: Tue, 16 May 2023 14:46:09 +0200 Subject: [PATCH 1/5] Fix wchar array encoding in structs --- qiling/os/struct.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qiling/os/struct.py b/qiling/os/struct.py index c723eb172..d8c713ffd 100644 --- a/qiling/os/struct.py +++ b/qiling/os/struct.py @@ -123,7 +123,14 @@ def __setattr__(self, name: str, value: Any) -> None: # transform value into field bytes and write them to memory fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value) - data = bytes(fvalue) + + # Decode wchar strings seperately to prevent double encoding + if (hasattr(ftype, '_type_') and ftype._type_ == ctypes.c_wchar): + data = b'' + for i in ftype(*value) if hasattr(ftype, '_length_') else ftype(value): + data += bytes(i, 'utf-16') + else: + data = bytes(fvalue) mem.write(address + field.offset, data) From cf82260fbe1e7e034ebcf36021147549ede0f9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Keijzer?= Date: Thu, 18 May 2023 12:05:22 +0200 Subject: [PATCH 2/5] Add wchar wrapper struct to fix UCS-4 encoding of unicode strings on Mac/Linux --- qiling/os/struct.py | 30 ++++++++++++++++++++---------- qiling/os/windows/structs.py | 20 ++++++++++---------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/qiling/os/struct.py b/qiling/os/struct.py index d8c713ffd..1951fb7c3 100644 --- a/qiling/os/struct.py +++ b/qiling/os/struct.py @@ -8,10 +8,23 @@ from typing import TYPE_CHECKING, Any, Type, Optional from qiling.const import QL_ENDIAN +from qiling.os.windows.const import MAX_PATH if TYPE_CHECKING: from qiling.os.memory import QlMemoryManager +class c_wchar_14(ctypes.c_ubyte * 28): + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + +class c_wchar_128(ctypes.c_ubyte * 256): + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + +class c_wchar_max_path(ctypes.c_ubyte * (MAX_PATH * 2)): + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + # the cache decorator is needed here not only for performance purposes, but also to make sure # the *same* class type is returned every time rather than creating another one with the same @@ -122,22 +135,19 @@ def __setattr__(self, name: str, value: Any) -> None: ftype = _fields[name] # transform value into field bytes and write them to memory - fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value) - - # Decode wchar strings seperately to prevent double encoding - if (hasattr(ftype, '_type_') and ftype._type_ == ctypes.c_wchar): - data = b'' - for i in ftype(*value) if hasattr(ftype, '_length_') else ftype(value): - data += bytes(i, 'utf-16') - else: - data = bytes(fvalue) + #fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value) + fvalue = ftype(value) + data = bytes(fvalue) mem.write(address + field.offset, data) # proceed to set the value to the structure in order to maintain consistency with ctypes.Structure # set attribute value - super().__setattr__(name, value) + if ftype == c_wchar_14 or ftype == c_wchar_128 or ftype == c_wchar_max_path: + super().__setattr__(name, ftype(value)) + else: + super().__setattr__(name, value) return VolatileStructRef() diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 71aed2d74..a872d09d0 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -9,16 +9,15 @@ from functools import lru_cache from qiling.os import struct +from qiling.os.struct import c_wchar_14, c_wchar_128, c_wchar_max_path from qiling.os.windows.const import MAX_PATH from qiling.os.windows.handle import Handle from qiling.exception import QlErrorNotImplemented from .wdk_const import IRP_MJ_MAXIMUM_FUNCTION, PROCESSOR_FEATURE_MAX - def make_teb(archbits: int): """Generate a TEB structure class. """ - native_type = struct.get_native_type(archbits) Struct = struct.get_aligned_struct(archbits) @@ -202,7 +201,7 @@ class KUSER_SHARED_DATA(struct.BaseStructEL): ('TimeZoneBias', KSYSTEM_TIME), ('ImageNumberLow', ctypes.c_uint16), ('ImageNumberHigh', ctypes.c_uint16), - ('NtSystemRoot', ctypes.c_wchar * MAX_PATH), + ('NtSystemRoot', c_wchar_max_path), ('MaxStackTraceDepth', ctypes.c_uint32), ('CryptoExponent', ctypes.c_uint32), ('TimeZoneId', ctypes.c_uint32), @@ -1050,7 +1049,7 @@ class PROCESS_BASIC_INFORMATION(Struct): def make_os_version_info(archbits: int, *, wide: bool): Struct = struct.get_aligned_struct(archbits) - char_type = (ctypes.c_wchar if wide else ctypes.c_char) + char_type = (c_wchar_128 if wide else ctypes.c_char * 128) class OSVERSIONINFO(Struct): _fields_ = ( @@ -1059,7 +1058,7 @@ class OSVERSIONINFO(Struct): ('dwMinorVersion', ctypes.c_uint32), ('dwBuildNumber', ctypes.c_uint32), ('dwPlatformId', ctypes.c_uint32), - ('szCSDVersion', char_type * 128) + ('szCSDVersion', char_type) ) return OSVERSIONINFO @@ -1069,7 +1068,7 @@ class OSVERSIONINFO(Struct): def make_os_version_info_ex(archbits: int, *, wide: bool): Struct = struct.get_aligned_struct(archbits) - char_type = (ctypes.c_wchar if wide else ctypes.c_char) + char_type = (c_wchar_128 if wide else ctypes.c_char * 128) class OSVERSIONINFOEX(Struct): _fields_ = ( @@ -1078,7 +1077,7 @@ class OSVERSIONINFOEX(Struct): ('dwMinorVersion', ctypes.c_uint32), ('dwBuildNumber', ctypes.c_uint32), ('dwPlatformId', ctypes.c_uint32), - ('szCSDVersion', char_type * 128), + ('szCSDVersion', char_type), ('wServicePackMajor', ctypes.c_uint16), ('wServicePackMinor', ctypes.c_uint16), ('wSuiteMask', ctypes.c_uint16), @@ -1521,7 +1520,8 @@ class OBJECT_ALL_TYPES_INFORMATION(Struct): def make_win32_find_data(archbits: int, *, wide: bool): Struct = struct.get_aligned_struct(archbits) - char_type = (ctypes.c_wchar if wide else ctypes.c_char) + filename = c_wchar_max_path if wide else ctypes.c_char * MAX_PATH + alternateFileName = c_wchar_14 if wide else ctypes.c_char * 14 class WIN32_FIND_DATA(Struct): _fields_ = ( @@ -1533,8 +1533,8 @@ class WIN32_FIND_DATA(Struct): ('nFileSizeLow', ctypes.c_uint32), ('dwReserved0', ctypes.c_uint32), ('dwReserved1', ctypes.c_uint32), - ('cFileName', char_type * MAX_PATH), - ('cAlternateFileName', char_type * 14), + ('cFileName', filename), + ('cAlternateFileName', alternateFileName), ('dwFileType', ctypes.c_uint32), ('dwCreatorType', ctypes.c_uint32), ('wFinderFlags', ctypes.c_uint16) From 4b957426665521f4fe10267cdbe32e676c763469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Keijzer?= Date: Thu, 18 May 2023 12:16:57 +0200 Subject: [PATCH 3/5] Move Wchar array wrapper classes to Windows/structs.py --- qiling/os/struct.py | 15 +-------------- qiling/os/windows/structs.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/qiling/os/struct.py b/qiling/os/struct.py index 1951fb7c3..92d56f311 100644 --- a/qiling/os/struct.py +++ b/qiling/os/struct.py @@ -8,23 +8,10 @@ from typing import TYPE_CHECKING, Any, Type, Optional from qiling.const import QL_ENDIAN -from qiling.os.windows.const import MAX_PATH if TYPE_CHECKING: from qiling.os.memory import QlMemoryManager -class c_wchar_14(ctypes.c_ubyte * 28): - def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) - -class c_wchar_128(ctypes.c_ubyte * 256): - def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) - -class c_wchar_max_path(ctypes.c_ubyte * (MAX_PATH * 2)): - def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) - # the cache decorator is needed here not only for performance purposes, but also to make sure # the *same* class type is returned every time rather than creating another one with the same @@ -144,7 +131,7 @@ def __setattr__(self, name: str, value: Any) -> None: # proceed to set the value to the structure in order to maintain consistency with ctypes.Structure # set attribute value - if ftype == c_wchar_14 or ftype == c_wchar_128 or ftype == c_wchar_max_path: + if hasattr(ftype, 'is_wrapper'): super().__setattr__(name, ftype(value)) else: super().__setattr__(name, value) diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index a872d09d0..3e9a4f8fd 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -9,12 +9,26 @@ from functools import lru_cache from qiling.os import struct -from qiling.os.struct import c_wchar_14, c_wchar_128, c_wchar_max_path from qiling.os.windows.const import MAX_PATH from qiling.os.windows.handle import Handle from qiling.exception import QlErrorNotImplemented from .wdk_const import IRP_MJ_MAXIMUM_FUNCTION, PROCESSOR_FEATURE_MAX +class c_wchar_14(ctypes.c_ubyte * 28): + is_wrapper = True + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + +class c_wchar_128(ctypes.c_ubyte * 256): + is_wrapper = True + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + +class c_wchar_max_path(ctypes.c_ubyte * (MAX_PATH * 2)): + is_wrapper = True + def __init__(self, string: str): + super().__init__(*[x for x in string.encode('utf-16le')]) + def make_teb(archbits: int): """Generate a TEB structure class. """ From 60328139141a36316f36c17f76daa8772918019b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Keijzer?= Date: Thu, 18 May 2023 12:35:08 +0200 Subject: [PATCH 4/5] Fix bug that would not resolve value of c_char_array type correctly --- qiling/os/struct.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/os/struct.py b/qiling/os/struct.py index 92d56f311..777443e06 100644 --- a/qiling/os/struct.py +++ b/qiling/os/struct.py @@ -122,8 +122,7 @@ def __setattr__(self, name: str, value: Any) -> None: ftype = _fields[name] # transform value into field bytes and write them to memory - #fvalue = ftype(*value) if hasattr(ftype, '_length_') else ftype(value) - fvalue = ftype(value) + fvalue = ftype(*value) if hasattr(ftype, '_length_') and not hasattr(ftype, 'is_wrapper') else ftype(value) data = bytes(fvalue) mem.write(address + field.offset, data) From 82d69d8147d56fd2b8a03c5cefd318a958a5ef8a Mon Sep 17 00:00:00 2001 From: Emperor-RE Date: Thu, 18 May 2023 18:27:35 +0200 Subject: [PATCH 5/5] Add null terminators to wchar wrapper classes as per Microsoft implementation --- qiling/os/windows/structs.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/qiling/os/windows/structs.py b/qiling/os/windows/structs.py index 3e9a4f8fd..6f157c0df 100644 --- a/qiling/os/windows/structs.py +++ b/qiling/os/windows/structs.py @@ -14,20 +14,26 @@ from qiling.exception import QlErrorNotImplemented from .wdk_const import IRP_MJ_MAXIMUM_FUNCTION, PROCESSOR_FEATURE_MAX -class c_wchar_14(ctypes.c_ubyte * 28): +class c_wchar_14(ctypes.c_ubyte * 30): is_wrapper = True def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) + null_terminated_bytes = [x for x in string.encode('utf-16le')] + null_terminated_bytes.extend([0, 0]) + super().__init__(*null_terminated_bytes) -class c_wchar_128(ctypes.c_ubyte * 256): +class c_wchar_128(ctypes.c_ubyte * 258): is_wrapper = True def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) + null_terminated_bytes = [x for x in string.encode('utf-16le')] + null_terminated_bytes.extend([0, 0]) + super().__init__(*null_terminated_bytes) -class c_wchar_max_path(ctypes.c_ubyte * (MAX_PATH * 2)): +class c_wchar_max_path(ctypes.c_ubyte * (MAX_PATH * 2 + 2)): is_wrapper = True def __init__(self, string: str): - super().__init__(*[x for x in string.encode('utf-16le')]) + null_terminated_bytes = [x for x in string.encode('utf-16le')] + null_terminated_bytes.extend([0, 0]) + super().__init__(*null_terminated_bytes) def make_teb(archbits: int): """Generate a TEB structure class.