diff --git a/speakeasy/windows/common.py b/speakeasy/windows/common.py index d139c76..cddbd87 100644 --- a/speakeasy/windows/common.py +++ b/speakeasy/windows/common.py @@ -67,7 +67,7 @@ # Blank header used for a 64-bit PE header EMPTY_PE_64 = DOS_HEADER + b'PE\x00\x00d\x86\x00\x00ABCD\x00\x00\x00\x00\x00\x00\x00\x00' \ b'\xf0\x00\x03\x10\x0b\x02\x08\x00\x04\x00\x00\x00\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@' \ + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@' \ b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x06\x00' \ b'\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xb8' \ b'\x01\x00\x00\x00\x00\x00\x00AAAA\x02\x00\x00\x04\x00\x00\x10' \ diff --git a/speakeasy/windows/win32.py b/speakeasy/windows/win32.py index e524265..258c261 100644 --- a/speakeasy/windows/win32.py +++ b/speakeasy/windows/win32.py @@ -86,13 +86,15 @@ def add_vectored_exception_handler(self, first, handler): """ Add a vectored exception handler that will be executed on an exception """ - self.veh_handlers.append(handler) + if handler not in self.veh_handlers: + self.veh_handlers.append(handler) def remove_vectored_exception_handler(self, handler): """ Remove a vectored exception handler """ - self.veh_handlers.remove(handler) + if handler in self.veh_handlers: + self.veh_handlers.remove(handler) def get_processes(self): if len(self.processes) <= 1: @@ -232,7 +234,7 @@ def load_module(self, path=None, data=None, first_time_setup=True): pass self.mem_map(pe.image_size, base=base, - tag='emu.module.%s' % (self.mod_name)) + tag='emu.module.%s' % (self.mod_name)) self.modules.append((pe, ranges, emu_path)) self.mem_write(pe.base, pe.mapped_image) @@ -392,7 +394,7 @@ def run_module(self, module, all_entrypoints=False, emulate_children=False): child = self.child_processes.pop(0) child.pe = self.load_module(data=child.pe_data, - first_time_setup=False) + first_time_setup=False) self.prepare_module_for_emulation(child.pe, all_entrypoints) self.command_line = child.cmdline diff --git a/speakeasy/windows/winemu.py b/speakeasy/windows/winemu.py index 4fc3bca..6a37630 100644 --- a/speakeasy/windows/winemu.py +++ b/speakeasy/windows/winemu.py @@ -1762,8 +1762,7 @@ def init_module(self, modconf={}, name='none', emu_path='', default_base=None): mod.decoy_path = modconf.get('path', emu_path) or (name + '.dll') # Reserve memory for the module - res, size = self.get_valid_ranges(mod.image_size, - base) + res, size = self.get_valid_ranges(mod.image_size, base) mod.decoy_base = res mod.name = modconf.get('name', name) self.mem_reserve(size, base=res, tag='emu.module.%s' % (mod.name), diff --git a/speakeasy/winenv/api/api.py b/speakeasy/winenv/api/api.py index ebad068..e17e6a2 100644 --- a/speakeasy/winenv/api/api.py +++ b/speakeasy/winenv/api/api.py @@ -155,7 +155,7 @@ def mem_copy(self, dst, src, n): return self.emu.mem_copy(dst, src, n) def read_mem_string(self, addr, width, max_chars=0): - string = self.emu.read_mem_string(addr, width=width) + string = self.emu.read_mem_string(addr, width=width, max_chars=max_chars) return string def mem_string_len(self, addr, width): @@ -165,14 +165,14 @@ def read_ansi_string(self, addr): ans = ntos.STRING(self.emu.get_ptr_size()) ans = self.mem_cast(ans, addr) - string = self.emu.read_mem_string(ans.Buffer, width=1) + string = self.emu.read_mem_string(ans.Buffer, width=1, max_chars=ans.Length) return string def read_unicode_string(self, addr): us = ntos.UNICODE_STRING(self.emu.get_ptr_size()) us = self.mem_cast(us, addr) - string = self.emu.read_mem_string(us.Buffer, width=2) + string = self.emu.read_mem_string(us.Buffer, width=2, max_chars=us.Length // 2) return string def read_wide_string(self, addr, max_chars=0): diff --git a/speakeasy/winenv/api/kernelmode/ntoskrnl.py b/speakeasy/winenv/api/kernelmode/ntoskrnl.py index ec86fff..1914722 100644 --- a/speakeasy/winenv/api/kernelmode/ntoskrnl.py +++ b/speakeasy/winenv/api/kernelmode/ntoskrnl.py @@ -43,7 +43,7 @@ def get_current_irql(self): def set_current_irql(self, irql): return self.emu.set_current_irql(irql) - + def win_perms_to_emu_perms(self, win_perms): """ Maps Windows permissions to emulator engine permissions @@ -600,14 +600,14 @@ def ZwQuerySystemInformation(self, emu, argv, ctx={}): size = len(out) self.mem_write(sysinfo, out) nts = ddk.STATUS_SUCCESS - + elif sysclass == ddk.SYSTEM_INFORMATION_CLASS.SystemCodeIntegrityInformation: if sysinfo and syslen >= 8: class_len = (8).to_bytes(4, "little") flags = (1).to_bytes(4, "little") self.mem_write(sysinfo, class_len + flags) nts = ddk.STATUS_SUCCESS - + elif sysclass == ddk.SYSTEM_INFORMATION_CLASS.SystemProcessInformation: procs = emu.get_processes() for proc in procs: diff --git a/speakeasy/winenv/api/usermode/advapi32.py b/speakeasy/winenv/api/usermode/advapi32.py index 2ccd07e..faef5a9 100644 --- a/speakeasy/winenv/api/usermode/advapi32.py +++ b/speakeasy/winenv/api/usermode/advapi32.py @@ -523,6 +523,19 @@ def RegisterServiceCtrlHandler(self, emu, argv, ctx={}): return self.service_status_handle + @apihook('RegisterServiceCtrlHandlerEx', argc=3) + def RegisterServiceCtrlHandlerEx(self, emu, argv, ctx={}): + ''' + SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerExA( + LPCSTR lpServiceName, + LPHANDLER_FUNCTION_EX lpHandlerProc, + LPVOID lpContext + ); + ''' + lpServiceName, lpHandlerProc, lpContext = argv + + return self.RegisterServiceCtrlHandler(self, emu, [lpServiceName, lpHandlerProc], ctx) + @apihook('SetServiceStatus', argc=2) def SetServiceStatus(self, emu, argv, ctx={}): ''' diff --git a/speakeasy/winenv/api/usermode/kernel32.py b/speakeasy/winenv/api/usermode/kernel32.py index 8e0d581..30dc5f1 100644 --- a/speakeasy/winenv/api/usermode/kernel32.py +++ b/speakeasy/winenv/api/usermode/kernel32.py @@ -1673,10 +1673,10 @@ def IsProcessorFeaturePresent(self, emu, argv, ctx={}): );''' rv = 1 - ''' + ''' Not all the features must return 1, because those can represent a feature which can be unavailable - for your processor. For example PF_FLOATING_POINT_PRECISION_ERRATA is a Pentium instructions - which doesn't exist on new ones, and malware developers are using it to see if they are in an + for your processor. For example PF_FLOATING_POINT_PRECISION_ERRATA is a Pentium instructions + which doesn't exist on new ones, and malware developers are using it to see if they are in an emulated environment or not. To get the correct value you just need write an app to check all the fatures, something like: @@ -1730,10 +1730,10 @@ def IsProcessorFeaturePresent(self, emu, argv, ctx={}): 40:{"name":"PF_AVX2_INSTRUCTIONS_AVAILABLE","return":1}, 41:{"name":"PF_AVX512F_INSTRUCTIONS_AVAILABLE","return":0}, } - + rv = lookup[argv[0]]["return"] argv[0] = lookup[argv[0]]["name"] - + return rv @apihook('lstrcmpi', argc=2) @@ -3225,7 +3225,7 @@ def MapViewOfFile(self, emu, argv, ctx={}): fname = ntpath.basename(f.get_path()) fname = fname.replace('.', '_') - + # If the call to CreateFileMapping (done before calling this API) # has beed done with SEC_IMAGE protection, the mapping is not # done as a contigous stream of bytes, but it is mapped as @@ -3237,7 +3237,7 @@ def MapViewOfFile(self, emu, argv, ctx={}): base, size = emu.get_valid_ranges(pe.image_size) while base and base & 0xFFF: base, size = emu.get_valid_ranges(size) - + emu.mem_map(pe.image_size, base=base,tag='%s.%s.0x%x' % (tag_prefix, fname, base)) mapping.add_view(base, full_offset, size, access) self.mem_write(base, pe.mapped_image) @@ -3411,9 +3411,9 @@ def RemoveDirectory(self, emu, argv, ctx={}): if pn: target = self.read_mem_string(pn, cw) argv[0] = target - + return True - + @apihook('CopyFile', argc=3) def CopyFile(self, emu, argv, ctx={}): ''' @@ -3727,7 +3727,7 @@ def CloseHandle(self, emu, argv, ctx={}): emu.dec_ref(obj) return True return False - + @apihook('SetEndOfFile', argc=1) def SetEndOfFile(self, emu, argv, ctx={}): ''' @@ -5248,6 +5248,15 @@ def RtlZeroMemory(self, emu, argv, ctx={}): buf = b'\x00' * length self.mem_write(dest, buf) + @apihook('RtlMoveMemory', argc=3) + def RtlMoveMemory(self, emu, argv, ctx={}): + """ + void RtlMoveMemory(void* pvDest, const void *pSrc, size_t Length); + """ + dest, source, length = argv + buf = self.mem_read(source, length) + self.mem_write(dest, buf) + @apihook('QueryPerformanceFrequency', argc=1) def QueryPerformanceFrequency(self, emu, argv, ctx={}): """ @@ -5766,7 +5775,7 @@ def GetModuleFileNameExA(self, emu, argv, ctx={}): proc = self.get_object_from_handle(hProcess) if proc == None: - return + return filename = proc.get_process_path() @@ -5832,6 +5841,16 @@ def AddVectoredExceptionHandler(self, emu, argv, ctx={}): return Handler + @apihook('RemoveVectoredExceptionHandler', argc=1) + def RemoveVectoredExceptionHandler(self, emu, argv, ctx={}): + ''' + ULONG RemoveVectoredExceptionHandler( + PVOID Handle); + ''' + Handler = argv + emu.remove_vectored_exception_handler(Handler) + return 1 + @apihook("GetSystemDefaultUILanguage", argc=0) def GetSystemDefaultUILanguage(self, emu, argv, ctx={}): ''' @@ -5942,21 +5961,21 @@ def _lclose(self, emu, argv, ctx={}): def GetConsoleTitle(self, emu, argv, ctx={}): ''' DWORD WINAPI GetConsoleTitle( - _Out_ LPTSTR lpConsoleTitle, - _In_  DWORD  nSize - ); - ''' + _Out_ LPTSTR lpConsoleTitle, + _In_ DWORD nSize + ); + ''' lpConsoleTitle, nSize = argv cw = self.get_char_width(ctx) rv = False - + # TODO: consider enumeration logic temp_title = "explorer.exe" - - if cw == 2: - temp_title = temp_title.encode('utf-16le') + b'\x00\x00' - else: - temp_title = temp_title.encode('utf-8') + b'\x00' + + if cw == 2: + temp_title = temp_title.encode('utf-16le') + b'\x00\x00' + else: + temp_title = temp_title.encode('utf-8') + b'\x00' argv[0] = temp_title argv[1] = len(temp_title) @@ -6036,3 +6055,12 @@ def GetPhysicallyInstalledSystemMemory(self, emu, argv, ctx={}): @apihook('WTSGetActiveConsoleSessionId', argc=0) def WTSGetActiveConsoleSessionId(self, emu, argv, ctx={}): return emu.get_current_process().get_session_id() + + @apihook('WaitForSingleObjectEx', argc=3) + def WaitForSingleObjectEx(self, emu, argv, ctx={}): + return 0 # = WAIT_OBJECT_0 + + @apihook('GetProfileInt', argc=3) + def GetProfileInt(self, emu, argv, ctx={}): + _, _, nDefault = argv + return nDefault diff --git a/speakeasy/winenv/api/usermode/ntdll.py b/speakeasy/winenv/api/usermode/ntdll.py index 3371974..065853f 100644 --- a/speakeasy/winenv/api/usermode/ntdll.py +++ b/speakeasy/winenv/api/usermode/ntdll.py @@ -1,7 +1,7 @@ # Copyright (C) 2020 FireEye, Inc. All Rights Reserved. import os -import binascii +import binascii from .. import api @@ -37,6 +37,11 @@ def RtlGetLastWin32Error(self, emu, argv, ctx={}): return emu.get_last_error() + @apihook('RtlNtStatusToDosError', argc=1) + def RtlNtStatusToDosError(self, emu, argv, ctx={}): + '''ULONG RtlNtStatusToDosError(NTSTATUS Status);''' + return 0 + @apihook('RtlFlushSecureMemoryCache', argc=2) def RtlFlushSecureMemoryCache(self, emu, argv, ctx={}): '''DWORD RtlFlushSecureMemoryCache(PVOID arg0, PVOID arg1);''' @@ -146,7 +151,7 @@ def LdrGetProcedureAddress(self, emu, argv, ctx={}): fn = ntos.STRING(emu.get_ptr_size()) fn = self.mem_cast(fn, proc_name) - proc = self.read_mem_string(fn.Buffer, 1) + proc = self.read_mem_string(fn.Buffer, 1, max_chars=fn.Length) argv[1] = proc elif ordinal: @@ -175,6 +180,15 @@ def RtlZeroMemory(self, emu, argv, ctx={}): buf = b'\x00' * length self.mem_write(dest, buf) + @apihook('RtlMoveMemory', argc=3) + def RtlMoveMemory(self, emu, argv, ctx={}): + """ + void RtlMoveMemory(void* pvDest, const void *pSrc, size_t Length); + """ + dest, source, length = argv + buf = self.mem_read(source, length) + self.mem_write(dest, buf) + @apihook('NtSetInformationProcess', argc=4) def NtSetInformationProcess(self, emu, argv, ctx={}): """ @@ -237,7 +251,7 @@ def NtWaitForSingleObject(self, emu, argv, ctx={}): rv = ddk.STATUS_SUCCESS return rv - + @apihook('RtlComputeCrc32', argc=3) def RtlComputeCrc32(self, emu, argv, ctx={}): ''' @@ -258,9 +272,9 @@ def RtlComputeCrc32(self, emu, argv, ctx={}): def LdrFindResource_U(self, emu, argv, ctx={}): ''' pub unsafe extern "system" fn LdrFindResource_U( - DllHandle: PVOID, - ResourceInfo: PLDR_RESOURCE_INFO, - Level: ULONG, + DllHandle: PVOID, + ResourceInfo: PLDR_RESOURCE_INFO, + Level: ULONG, ResourceDataEntry: *mut PIMAGE_RESOURCE_DATA_ENTRY ) -> NTSTATUS @@ -280,7 +294,7 @@ def LdrFindResource_U(self, emu, argv, ctx={}): ''' DllHandle, ResourceInfo, Level, ResourceDataEntry = argv - # Reusing some functions from kernel32 module that are used to + # Reusing some functions from kernel32 module that are used to # handle the very similar function FindResourceA k32 = emu.api.mods.get('kernel32') @@ -313,15 +327,15 @@ def LdrFindResource_U(self, emu, argv, ctx={}): self.mem_write(ptr_data_entry+4, resource['size'].to_bytes(4, 'little')) return hnd - + @apihook('LdrAccessResource', argc=4) def LdrAccessResource(self, emu, argv, ctx={}): ''' - NTSTATUS NTAPI LdrAccessResource ( _In_ PVOID BaseAddress, - _In_ PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry, - _Out_opt_ PVOID * Resource, - _Out_opt_ PULONG Size - ) + NTSTATUS NTAPI LdrAccessResource ( _In_ PVOID BaseAddress, + _In_ PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry, + _Out_opt_ PVOID * Resource, + _Out_opt_ PULONG Size + ) ''' BaseAddress, ResourceDataEntry, Resource, Size = argv @@ -331,6 +345,6 @@ def LdrAccessResource(self, emu, argv, ctx={}): # Fill in the Resource struct self.mem_write(Size, size.to_bytes(4, 'little')) self.mem_write(Resource, offset.to_bytes(4, 'little')) - + return 0 diff --git a/speakeasy/winenv/api/usermode/sfc.py b/speakeasy/winenv/api/usermode/sfc.py new file mode 100644 index 0000000..7a07fd5 --- /dev/null +++ b/speakeasy/winenv/api/usermode/sfc.py @@ -0,0 +1,20 @@ +from .. import api + +import time + +class sfc(api.ApiHandler): + """ + Emulates functions from sfc.dll + """ + + name = 'sfc' + apihook = api.ApiHandler.apihook + impdata = api.ApiHandler.impdata + + def __init__(self, emu): + super(sfc, self).__init__(emu) + super(sfc, self).__get_hook_attrs__(self) + + @apihook('SfcIsFileProtected', argc=2) + def SfcIsFileProtected(self, emu, argv, ctx={}): + return False diff --git a/speakeasy/winenv/api/usermode/shell32.py b/speakeasy/winenv/api/usermode/shell32.py index e83422b..9c000ee 100644 --- a/speakeasy/winenv/api/usermode/shell32.py +++ b/speakeasy/winenv/api/usermode/shell32.py @@ -116,12 +116,12 @@ def ShellExecuteEx(self, emu, argv, ctx={}): sei_struct.lpFile, sei_struct.lpParameters, sei_struct.lpDirectory, 0 - ], + ], ctx ) - + return True - + @apihook('IsUserAnAdmin', argc=0, ordinal=680) def IsUserAnAdmin(self, emu, argv, ctx={}): """ diff --git a/speakeasy/winenv/api/usermode/shlwapi.py b/speakeasy/winenv/api/usermode/shlwapi.py index 1990b2f..0e2850a 100644 --- a/speakeasy/winenv/api/usermode/shlwapi.py +++ b/speakeasy/winenv/api/usermode/shlwapi.py @@ -6,6 +6,8 @@ from .. import api import speakeasy.winenv.arch as e_arch +MAX_PATH = 260 + class Shlwapi(api.ApiHandler): @@ -54,7 +56,7 @@ def PathIsRelative(self, emu, argv, ctx={}): argv[0] = pn return rv - + @apihook('StrStr', argc=2) def StrStr(self, emu, argv, ctx={}): ''' @@ -83,7 +85,7 @@ def StrStr(self, emu, argv, ctx={}): ret = 0 return ret - + @apihook('StrStrI', argc=2) def StrStrI(self, emu, argv, ctx={}): ''' @@ -293,7 +295,7 @@ def PathAppend(self, emu, argv, ctx={}): out += '\0' self.write_mem_string(out, pszPath, cw) return 1 - + @apihook('PathCanonicalize', argc=2) def PathCanonicalize(self, emu, argv, ctx={}): """ @@ -306,3 +308,64 @@ def PathCanonicalize(self, emu, argv, ctx={}): path = self.read_wide_string(pszPath) self.write_wide_string(path, pszBuf) return 1 + + @apihook('PathRemoveFileSpec', argc=1) + def PathRemoveFileSpec(self, emu, argv, ctx={}): + """ + BOOL PathRemoveFileSpec(LPTSTR pszPath); + """ + pszPath, = argv + cw = self.get_char_width(ctx) + s = self.read_mem_string(pszPath, cw) + idx = s.rfind('\\') + if idx == -1: + return 0 + + s = s[:idx] + self.write_mem_string(s, pszPath, cw) + return 1 + + @apihook('PathAddBackslash', argc=1) + def PathAddBackslash(self, emu, argv, ctx={}): + """ + LPTSTR PathAddBackslash(LPTSTR pszPath); + """ + pszPath, = argv + cw = self.get_char_width(ctx) + s = self.read_mem_string(pszPath, cw) + if not s.endswith('\\'): + s += '\\' + if len(s) > MAX_PATH: + return 0 + + self.write_mem_string(s, pszPath, cw) + return pszPath + + @apihook('PathRenameExtension', argc=2) + def PathRenameExtension(self, emu, argv, ctx={}): + """ + BOOL PathRenameExtension( + [in, out] LPSTR pszPath, + [in] LPCSTR pszExt + ); + """ + pszPath, pszExt = argv + + cw = self.get_char_width(ctx) + path = self.read_mem_string(pszPath, cw) + + ext = self.read_mem_string(pszExt, cw) + if not ext.startswith('.'): + return 0 + + i = path.rfind('.') + if i == -1: + path += ext + else: + path = path[:i] + ext + + if len(path) > MAX_PATH: + return 0 + + self.write_mem_string(path, pszPath, cw) + return 1