diff --git a/examples/src/windows/tls_multithread/CMakeLists.txt b/examples/src/windows/tls_multithread/CMakeLists.txt new file mode 100644 index 000000000..019e9f97a --- /dev/null +++ b/examples/src/windows/tls_multithread/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(x8664_tls_multithread C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_executable(x8664_tls_multithread main.c) + +if(MINGW) + target_compile_options(x8664_tls_multithread PRIVATE + -Os + -fno-asynchronous-unwind-tables + -fno-ident + -fno-stack-protector + ) + + target_link_options(x8664_tls_multithread PRIVATE + -nostdlib + -eEntryPoint + -s + -Wl,--gc-sections + ) + + target_link_libraries(x8664_tls_multithread PRIVATE kernel32 user32) +endif() \ No newline at end of file diff --git a/examples/src/windows/tls_multithread/main.c b/examples/src/windows/tls_multithread/main.c new file mode 100644 index 000000000..64e9a720a --- /dev/null +++ b/examples/src/windows/tls_multithread/main.c @@ -0,0 +1,111 @@ +#include + +static DWORD g_tlsIndex = TLS_OUT_OF_INDEXES; + +static volatile LONG g_hasError = 0; + +static DWORD StringLengthA(const char* str) { + DWORD len = 0; + while(str && str[len] != '\0') { + len++; + } + return len; +} + +static void PrintMessage(const char* message) { + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + if(hStdout != INVALID_HANDLE_VALUE && hStdout != NULL && message != NULL) { + DWORD bytesWritten; + WriteFile(hStdout, message, (DWORD)StringLengthA(message), &bytesWritten, NULL); + } +} + +DWORD WINAPI ThreadWorker(LPVOID lpParam) { + int threadId = (int)(UINT_PTR)lpParam; + void* expectedData = (void*)(UINT_PTR)(threadId + 100); + + void* initialData = TlsGetValue(g_tlsIndex); + if(GetLastError() != ERROR_SUCCESS || initialData != NULL) { + char buf[128]; + wsprintfA(buf, "[Thread %d] FAIL Initial TlsGetValue failed or not NULL.\n", threadId); + PrintMessage(buf); + InterlockedExchange(&g_hasError, 1); + return 1; + } + + if(!TlsSetValue(g_tlsIndex, expectedData)) { + char buf[128]; + wsprintfA(buf, "[Thread %d] FAIL TlsSetValue failed with error: %lu\n", threadId, GetLastError()); + PrintMessage(buf); + InterlockedExchange(&g_hasError, 1); + return 1; + } + + void* retrievedData = TlsGetValue(g_tlsIndex); + if(GetLastError() != ERROR_SUCCESS || retrievedData != expectedData) { + char buf[128]; + wsprintfA(buf, "[Thread %d] FAIL TlsGetValue returned unexpected data.\n", threadId); + PrintMessage(buf); + InterlockedExchange(&g_hasError, 1); + return 1; + } + + return 0; +} + +void EntryPoint(void) { + int exitCode = 0; + + g_tlsIndex = TlsAlloc(); + if(g_tlsIndex == TLS_OUT_OF_INDEXES) { + char buf[128]; + wsprintfA(buf, "FAIL TlsAlloc failed with error: %lu\n", GetLastError()); + PrintMessage(buf); + ExitProcess((UINT)-1); + } + + #define NUM_THREADS 5 + HANDLE threads[NUM_THREADS]; + + for(DWORD i = 0; i < NUM_THREADS; ++i) { + threads[i] = CreateThread( + NULL, + 0, + ThreadWorker, + (LPVOID)(UINT_PTR)i, + 0, + NULL + ); + + if(threads[i] == NULL) { + char buf[128]; + wsprintfA(buf, "FAIL CreateThread failed with error: %lu\n", GetLastError()); + PrintMessage(buf); + TlsFree(g_tlsIndex); + ExitProcess((UINT)-1); + } + } + + WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE); + + for(DWORD i = 0; i < NUM_THREADS; ++i) { + CloseHandle(threads[i]); + } + + if(!TlsFree(g_tlsIndex)) { + char buf[128]; + wsprintfA(buf, "FAIL TlsFree failed with error: %lu\n", GetLastError()); + PrintMessage(buf); + ExitProcess((UINT)-1); + } + + if(InterlockedCompareExchange(&g_hasError, 0, 0) != 0) { + PrintMessage("FAIL TLS test completed with errors.\n"); + exitCode = -1; + } else { + PrintMessage("SUCCESS All TLS operations succeeded across all threads.\n"); + exitCode = 0; + } + + ExitProcess((UINT)exitCode); +} \ No newline at end of file diff --git a/qiling/loader/pe.py b/qiling/loader/pe.py index f9ac1bb65..2191a627b 100644 --- a/qiling/loader/pe.py +++ b/qiling/loader/pe.py @@ -80,6 +80,7 @@ class Process: pe_image_address: int stack_address: int stack_size: int + tls_templates: List[bytes] dlls: MutableMapping[str, int] import_address_table: MutableMapping[str, Mapping] @@ -399,6 +400,13 @@ def load_dll(self, name: str, is_driver: bool = False) -> int: self.ql.log.debug(f'Init imports for {dll_name}') self.init_imports(dll, is_driver) + if not is_driver: + self.ql.log.debug(f'Init TLS for {dll_name}') + self.init_module_tls(dll) + + self.ql.os.thread_manager.init_tls(self.tls_templates) + self.ql.os.thread_manager.update_tls_address() + # calling DllMain is essential for dlls to initialize properly. however # DllMain of system libraries may fail due to incomplete or inaccurate # mock implementation. due to unicorn limitations, recovering from such @@ -493,6 +501,35 @@ def set_cmdline(self, name: bytes, address: int, memory: bytearray): return {"name": name, "address": address} + def init_module_tls(self, module: pefile.PE): + if hasattr(module, "DIRECTORY_ENTRY_TLS"): + # Not every module has a TLS directory entry, + # so we only bother with allocating a TLS block + # for the module if the module actually uses TLS. + + tls_struct = module.DIRECTORY_ENTRY_TLS.struct + + tls_data_block_size = tls_struct.EndAddressOfRawData - tls_struct.StartAddressOfRawData + + # The Windows loader places a zero-initialized block immediately + # after the data block, with size specified by SizeOfZeroFill. + # So the total size of the TLS block is the size of the data block + # plus the size of the zero-initialized block. + tls_zero_block_size = tls_struct.SizeOfZeroFill + + tls_data_block = bytes(self.ql.mem.read(tls_struct.StartAddressOfRawData, tls_data_block_size)) + tls_zero_block = bytes([0] for _ in range(tls_zero_block_size)) + tls_block = tls_data_block + tls_zero_block + + module_tls_index = len(self.tls_templates) + self.tls_templates.append(tls_block) + + tls_index_bytes = module_tls_index.to_bytes(4, byteorder='little') + + # The module expects to receive its index in the TLS vector + # at the location specified by AddressOfIndex + self.ql.mem.write(tls_struct.AddressOfIndex, tls_index_bytes) + def init_teb(self): teb_struct = make_teb(self.ql.arch.bits) @@ -825,6 +862,7 @@ def __init__(self, ql: Qiling, libcache: bool): self.ql = ql self.path = self.ql.path self.libcache = QlPeCache() if libcache else None + self.tls_templates = [] def run(self): self.init_dlls = ( @@ -927,10 +965,13 @@ def load(self, pe: Optional[pefile.PE]): self.init_peb() self.init_ldr_data() self.init_exports(pe) + self.init_module_tls(pe) # add image to ldr table self.add_ldr_data_table_entry(image_name) + self.ql.os.thread_manager.init_tls(self.tls_templates) + self.init_ki_user_shared_data() pe.parse_data_directories() @@ -1032,6 +1073,10 @@ def load(self, pe: Optional[pefile.PE]): for each in self.init_dlls: super().load_dll(each, self.is_driver) + # set the TLS address in the TEB to the main thread's + # TLS vector + self.ql.os.thread_manager.update_tls_address() + # move entry_point to ql.os self.ql.os.entry_point = self.entry_point self.init_sp = self.ql.arch.regs.arch_sp diff --git a/qiling/os/windows/dlls/kernel32/consoleapi.py b/qiling/os/windows/dlls/kernel32/consoleapi.py index 90f7fefab..a04f7dc63 100644 --- a/qiling/os/windows/dlls/kernel32/consoleapi.py +++ b/qiling/os/windows/dlls/kernel32/consoleapi.py @@ -5,6 +5,7 @@ from qiling import Qiling from qiling.os.windows.api import * +from qiling.os.windows.const import * from qiling.os.windows.fncc import * # BOOL WINAPI GetConsoleMode( @@ -17,3 +18,62 @@ }) def hook_GetConsoleMode(ql: Qiling, address: int, params): return 1 + +def _WriteConsole(ql: Qiling, address: int, params, unicode: bool) -> int: + hConsoleOutput = params["hConsoleOutput"] + lpBuffer = params["lpBuffer"] + nNumberOfCharsToWrite = params["nNumberOfCharsToWrite"] + lpNumberOfCharsWritten = params["lpNumberOfCharsWritten"] + + handle = ql.os.handle_manager.get(hConsoleOutput) + + if handle is None: + ql.os.last_error = ERROR_INVALID_HANDLE + return 0 + + character_size = 2 if unicode else 1 + encoding = 'utf-16le' if unicode else 'utf-8' + + text = ql.mem.read(lpBuffer, nNumberOfCharsToWrite * character_size).decode(encoding) + + if hConsoleOutput in (STD_OUTPUT_HANDLE, STD_ERROR_HANDLE): + ql.os.stats.log_string(text) + + written = handle.obj.write(bytes(text, encoding='utf-8')) + ql.mem.write_ptr(lpNumberOfCharsWritten, written, 4) + + return 1 + +# BOOL WriteConsoleW( +# _In_ HANDLE hConsoleOutput, +# _In_ const VOID *lpBuffer, +# _In_ DWORD nNumberOfCharsToWrite, +# _Out_opt_ LPDWORD lpNumberOfCharsWritten, +# _Reserved_ LPVOID lpReserved +# ); +@winsdkapi(cc=STDCALL, params={ + 'hConsoleOutput' : HANDLE, + 'lpBuffer' : LPCVOID, + 'nNumberOfCharsToWrite' : DWORD, + 'lpNumberOfCharsWritten' : LPDWORD, + 'lpReserved' : LPVOID +}) +def hook_WriteConsoleW(ql: Qiling, address: int, params): + return _WriteConsole(ql, address, params, True) + +# BOOL WriteConsoleA( +# _In_ HANDLE hConsoleOutput, +# _In_ const VOID *lpBuffer, +# _In_ DWORD nNumberOfCharsToWrite, +# _Out_opt_ LPDWORD lpNumberOfCharsWritten, +# _Reserved_ LPVOID lpReserved +# ); +@winsdkapi(cc=STDCALL, params={ + 'hConsoleOutput' : HANDLE, + 'lpBuffer' : LPCVOID, + 'nNumberOfCharsToWrite' : DWORD, + 'lpNumberOfCharsWritten' : LPDWORD, + 'lpReserved' : LPVOID +}) +def hook_WriteConsoleA(ql: Qiling, address: int, params): + return _WriteConsole(ql, address, params, False) \ No newline at end of file diff --git a/qiling/os/windows/dlls/kernel32/errhandlingapi.py b/qiling/os/windows/dlls/kernel32/errhandlingapi.py index ece53aa80..77eedcc79 100644 --- a/qiling/os/windows/dlls/kernel32/errhandlingapi.py +++ b/qiling/os/windows/dlls/kernel32/errhandlingapi.py @@ -42,6 +42,15 @@ def hook_GetLastError(ql: Qiling, address: int, params): 'dwErrCode' : DWORD }) def hook_SetLastError(ql: Qiling, address: int, params): + # TODO: Make this compatible with the new TLS emulation. + # Under the hood, the Windows implementation uses TLS to store last error + # codes. + # + # Alternatively, these hooks could be removed as the native implementations + # appear to work with the new TLS emulation, however any Qiling hooks that + # rely on setting ql.os.last_error would have to be rewritten to access the + # last error stored in TLS instead. + ql.os.last_error = params['dwErrCode'] # UINT SetErrorMode( diff --git a/qiling/os/windows/dlls/kernel32/processthreadsapi.py b/qiling/os/windows/dlls/kernel32/processthreadsapi.py index 0cda93c0f..2362c25eb 100644 --- a/qiling/os/windows/dlls/kernel32/processthreadsapi.py +++ b/qiling/os/windows/dlls/kernel32/processthreadsapi.py @@ -71,9 +71,7 @@ def hook_GetStartupInfoW(ql: Qiling, address: int, params): # DWORD TlsAlloc(); @winsdkapi(cc=STDCALL, params={}) def hook_TlsAlloc(ql: Qiling, address: int, params): - idx = ql.os.thread_manager.cur_thread.tls_index - ql.os.thread_manager.cur_thread.tls_index += 1 - ql.os.thread_manager.cur_thread.tls[idx] = 0 + idx = ql.os.thread_manager.tls_alloc() return idx @@ -86,11 +84,10 @@ def hook_TlsAlloc(ql: Qiling, address: int, params): def hook_TlsFree(ql: Qiling, address: int, params): idx = params['dwTlsIndex'] - if idx not in ql.os.thread_manager.cur_thread.tls: + if not ql.os.thread_manager.tls_free(idx): ql.os.last_error = ERROR_INVALID_PARAMETER return 0 - del (ql.os.thread_manager.cur_thread.tls[idx]) return 1 # LPVOID TlsGetValue( @@ -102,14 +99,15 @@ def hook_TlsFree(ql: Qiling, address: int, params): def hook_TlsGetValue(ql: Qiling, address: int, params): idx = params['dwTlsIndex'] - if idx not in ql.os.thread_manager.cur_thread.tls: - ql.os.last_error = ERROR_INVALID_PARAMETER - return 0 + value = ql.os.thread_manager.tls_get_value(idx) + + if value is None: + return ERROR_INVALID_PARAMETER # api explicity clears last error on success: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsgetvalue ql.os.last_error = 0 - return ql.os.thread_manager.cur_thread.tls[idx] + return value # BOOL TlsSetValue( # DWORD dwTlsIndex, @@ -121,12 +119,12 @@ def hook_TlsGetValue(ql: Qiling, address: int, params): }) def hook_TlsSetValue(ql: Qiling, address: int, params): idx = params['dwTlsIndex'] + value = params['lpTlsValue'] - if idx not in ql.os.thread_manager.cur_thread.tls: + if not ql.os.thread_manager.tls_set_value(idx, value): ql.os.last_error = ERROR_INVALID_PARAMETER return 0 - ql.os.thread_manager.cur_thread.tls[idx] = params['lpTlsValue'] return 1 # DWORD GetCurrentThreadId( @@ -199,9 +197,8 @@ def hook_CreateThread(ql: Qiling, address: int, params): ql.os.handle_manager.append(new_handle) # set lpThreadId - # FIXME: Temporary fix for the crash - # if lpThreadId != 0: - # ql.mem.write_ptr(lpThreadId, thread_id) + if lpThreadId != 0: + ql.mem.write_ptr(lpThreadId, new_thread.id) # set thread handle return new_handle.id @@ -344,3 +341,21 @@ def hook_OpenThreadToken(ql: Qiling, address: int, params): }) def hook_GetThreadTimes(ql: Qiling, address: int, params): return 1 + +# HRESULT SetThreadDescription( +# HANDLE hThread, +# PCWSTR lpThreadDescription +# ); +@winsdkapi(cc=STDCALL, params={ + 'hThread' : HANDLE, + 'lpThreadDescription' : PCWSTR, +}) +def hook_SetThreadDescription(ql: Qiling, address: int, params): + # The Rust runtime init calls this function but + # does not seem to depend on a full and correct implementation. + # + # The native implementation causes issues so we simply return nonzero + # indicating success. + # + # TODO: emulate this correctly, along with GetThreadDescription. + return 1 diff --git a/qiling/os/windows/thread.py b/qiling/os/windows/thread.py index 3c0d3bb5b..08cf354c4 100644 --- a/qiling/os/windows/thread.py +++ b/qiling/os/windows/thread.py @@ -3,12 +3,14 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # +import math from enum import Enum -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, Optional from qiling import Qiling from qiling.const import QL_ARCH, QL_HOOK_BLOCK from qiling.os.thread import QlThread +from qiling.loader.pe import QlLoaderPE if TYPE_CHECKING: from qiling.os.windows.windows import QlOsWindows @@ -30,8 +32,8 @@ def __init__(self, ql: Qiling, status: THREAD_STATUS = THREAD_STATUS.RUNNING): QlWindowsThread.ID += 1 self.status = status self.waitforthreads = [] - self.tls = {} - self.tls_index = 0 + self.tls_vector_addr = 0 + self.tls_initialized_indices = set() # create a new thread with context @classmethod @@ -40,6 +42,14 @@ def create(cls, ql: Qiling, stack_size: int, func_addr: int, func_params: int, s thread = cls(ql, status) + PAGE_SIZE = 4096 + + # According to Microsoft documentation: + # "The system rounds this value to the nearest page. + # If this parameter is zero, the new thread uses the default size for the executable." + # https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread + stack_size = int(math.ceil(max(stack_size, 1) / PAGE_SIZE)) * PAGE_SIZE + # create new stack new_stack = os.heap.alloc(stack_size) + stack_size @@ -68,6 +78,9 @@ def create(cls, ql: Qiling, stack_size: int, func_addr: int, func_params: int, s thread.saved_context = context + loader = cast('QlLoaderPE', ql.loader) + thread.init_tls(loader.tls_templates) + return thread def suspend(self) -> None: @@ -89,6 +102,79 @@ def waitfor(self, thread: 'QlWindowsThread') -> None: def has_waitfor(self) -> bool: return any(not thread.is_stop() for thread in self.waitforthreads) + def init_tls(self, tls_templates: list[bytes]): + """Initialize or reinitialize TLS storage for the thread. + + If the thread has a TLS slot which was previously initialized + in a prior call to `init_tls`, this TLS slot is skipped. + + Parameters: + - `tls_templates` - Array of per-module or allocated TLS blocks. + This array is cloned and used to initialize the thread's TLS vector. + """ + + # According to Microsoft documentation, the TLS vector has + # a minimum of 64 entries, but can be reallocated if more + # entries are needed. + # + # In practice most processes will only use a handful of entries, + # so for simplicity we will assume that the process will not + # need more than 64. + TLS_VECTOR_ENTRIES = 64 + TLS_ADDRESS_MIN = 0x800000 + TLS_VECTOR_SIZE = TLS_VECTOR_ENTRIES * self.ql.arch.pointersize + + if not self.tls_vector_addr: + self.tls_vector_addr = self.ql.mem.map_anywhere(TLS_VECTOR_SIZE, TLS_ADDRESS_MIN) + + self.ql.log.debug(f'Init TLS for thread ID={self.id}') + self.ql.log.debug(f'TLS vector at {self.tls_vector_addr:#x}') + else: + self.ql.log.debug(f'Reinit TLS for thread ID={self.id}') + + for module_tls_index, template in enumerate(tls_templates): + if module_tls_index in self.tls_initialized_indices: + self.ql.log.debug(f'Skipping TLS for module index={module_tls_index}; already initialized') + continue + + if len(template) > 0: + tls_block_addr = self.ql.mem.map_anywhere(len(template), TLS_ADDRESS_MIN) + + self.ql.log.debug(f'Module TLS index={module_tls_index}') + + self.ql.mem.write(tls_block_addr, template) + + self.ql.log.debug(f'Wrote TLS block at {self.tls_vector_addr:#x} ({len(template)} bytes)') + + # Write the pointer to the new TLS block to the thread's TLS vector + self.ql.mem.write_ptr(self.tls_vector_addr + module_tls_index * self.ql.arch.pointersize, tls_block_addr) + + else: + # If the template size is zero then this may be a TLS slot + # allocated with e.g. TlsAlloc. + self.ql.log.debug(f'Empty TLS index={module_tls_index}') + + # Zero-initialize the slot + self.ql.mem.write_ptr(self.tls_vector_addr + module_tls_index * self.ql.arch.pointersize, 0) + + self.tls_initialized_indices.add(module_tls_index) + + def update_tls_address(self): + """Update the `TlsAddress` pointer in the TEB to point + to the given thread's TLS. + + This method must be called before a thread resumes execution. + """ + loader = self.ql.loader + + if TYPE_CHECKING: + loader = cast('QlLoaderPE', loader) + + if not loader.is_driver: + loader.TEB.TlsAddress = self.tls_vector_addr + + self.ql.log.debug(f"Set TEB.TlsAddress={self.tls_vector_addr:#x} for thread ID {self.id}") + # Simple Thread Manager class QlWindowsThreadManagement: @@ -103,6 +189,10 @@ def __init__(self, ql: Qiling, os: 'QlOsWindows', cur_thread: QlWindowsThread): self.icount = 0 self.thread_ret_addr = os.heap.alloc(8) + # Index of the past-the-end TLS slot + self.tls_index = 0 + self.tls_allocated_indices = set() + # write nop to thread_ret_addr ql.mem.write(self.thread_ret_addr, b'\x90' * 8) @@ -125,7 +215,7 @@ def append(self, thread: QlWindowsThread): self.threads.append(thread) def do_schedule(self) -> bool: - need_schedule = self.cur_thread.is_stop() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0 + need_schedule = self.cur_thread.is_stop() or self.cur_thread.has_waitfor() or (self.icount % QlWindowsThreadManagement.TIME_SLICE) == 0 switched = False if need_schedule: @@ -140,9 +230,83 @@ def do_schedule(self) -> bool: self.cur_thread.suspend() next_thread.resume() + next_thread.update_tls_address() self.cur_thread = next_thread switched = True break return switched + + def init_tls(self, tls_templates: list[bytes]): + """Initialize or reinitialize TLS storage for all currently + running threads. + + This method should be called every time a new TLS template + is added by the loader, in order to update TLS vectors for + running threads. + + Parameters: + - `tls_templates` - Array of per-module or manually + allocated TLS blocks. This array is cloned and used + to initialize each thread's TLS vector. + """ + self.tls_index = len(tls_templates) - 1 + + for thread in self.threads: + thread.init_tls(tls_templates) + + def tls_alloc(self) -> int: + """Allocate a TLS slot. + + Returns the index of the new TLS slot. + """ + loader = cast('QlLoaderPE', self.ql.loader) + + current_tls_index = len(loader.tls_templates) + self.tls_allocated_indices.add(current_tls_index) + + loader.tls_templates.append(bytes()) + + self.init_tls(loader.tls_templates) + + return current_tls_index + + def tls_free(self, tls_index: int) -> bool: + """Free a TLS slot that was previously allocated. + + Returns `True` if the TLS slot was successfully + freed, `False` if the slot was not freed. + + Parameters: + - `tls_index` - Index of the TLS slot to be freed. + """ + if tls_index not in self.tls_allocated_indices: + return False + + self.tls_allocated_indices.remove(tls_index) + + return True + + def tls_get_value(self, idx: int) -> Optional[int]: + if idx not in self.tls_allocated_indices: + return None + + return self.ql.mem.read_ptr(self.cur_thread.tls_vector_addr + idx * self.ql.arch.pointersize) + + def tls_set_value(self, idx: int, value: int) -> bool: + if idx not in self.tls_allocated_indices: + return False + + self.ql.mem.write_ptr(self.cur_thread.tls_vector_addr + idx * self.ql.arch.pointersize, value) + + return True + + def update_tls_address(self): + """Update the `TlsAddress` pointer in the TEB to point + to the currently running thread's TLS. + + This method must be called before a thread resumes execution. + """ + if self.cur_thread is not None: + self.cur_thread.update_tls_address() \ No newline at end of file diff --git a/tests/test_windows_rust_x8664.py b/tests/test_windows_rust_x8664.py new file mode 100644 index 000000000..a772f01a3 --- /dev/null +++ b/tests/test_windows_rust_x8664.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys, unittest + +sys.path.append("..") +from qiling import Qiling +from qiling.const import QL_VERBOSE +from qiling.extensions import pipe + +class RustTests_x8664(unittest.TestCase): + + def test_rust_helloworld(self): + """This tests the default Rust Hello World program + compiled on 64-bit Windows. + """ + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_rust_helloworld.exe"], "../examples/rootfs/x8664_windows/", verbose=QL_VERBOSE.DEFAULT) + + ql.os.stdout = pipe.SimpleStringBuffer() + + ql.run() + + conout = ql.os.stdout.read().decode('utf-8') + + # The program's output will contain the "Hello, world!" + # string if there were no errors. + self.assertIn("Hello, world!", conout) + + del ql + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_windows_tls_x8664.py b/tests/test_windows_tls_x8664.py new file mode 100644 index 000000000..88006e3a2 --- /dev/null +++ b/tests/test_windows_tls_x8664.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +import sys, unittest + +sys.path.append("..") +from qiling import Qiling +from qiling.const import QL_VERBOSE +from qiling.extensions import pipe + +class TlsTests_x8664(unittest.TestCase): + + def test_tls_multithread(self): + """The program used in this test uses TlsAlloc to + allocate a new TLS slot, and then spawns multiple threads using + CreateThread. + + Each thread calls TlsSetValue and TlsGetValue using the allocated + TLS slot. + + The program waits on all threads using WaitForMultipleObjects, + and then calls TlsFree before exiting. + + Success of the test depends on a reasonably correct implementation + of TLS, as well as correct behavior of CreateThread + and thread waiting. + """ + ql = Qiling(["../examples/rootfs/x8664_windows/bin/x8664_tls_multithread.exe"], "../examples/rootfs/x8664_windows/", verbose=QL_VERBOSE.DEFAULT) + + ql.os.stdout = pipe.SimpleStringBuffer() + + ql.run() + + # The program is expected to return 0 on success + self.assertEqual(ql.os.exit_code, 0) + + conout = ql.os.stdout.read().decode('utf-8') + + # The program's output will contain SUCCESS + # if there were no errors. + self.assertIn("SUCCESS", conout) + + # The program will print lines containing + # FAIL if there are errors. + self.assertNotIn("FAIL", conout) + + del ql + +if __name__ == '__main__': + unittest.main() \ No newline at end of file