From e2c2bdc2364faa5339213f5a49ae0235a7392c85 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Thu, 12 Mar 2015 18:25:49 +0100 Subject: [PATCH] Add support for 64bits injection + hooks --- __init__.py | 4 +- generated_def/windef.py | 2 + generated_def/winfuncs.py | 14 +- generated_def/winstructs.py | 161 +++++++++++- hooks.py | 10 +- injection.py | 103 +++++--- k32testing.py | 13 +- native_exec/native_function.py | 147 ++++++++++- native_exec/simple_x86.py | 48 +++- pe_parse.py | 457 +++++++++++++-------------------- remote_callback.py | 15 +- utils.py | 42 +-- winobject.py | 107 +++++++- 13 files changed, 729 insertions(+), 394 deletions(-) diff --git a/__init__.py b/__init__.py index d7aed84..631ba8b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,6 @@ -from utils import * +import k32testing +from winobject import System, CurrentProcess +from utils import VirtualProtected system = System() current_process = CurrentProcess() diff --git a/generated_def/windef.py b/generated_def/windef.py index a47c78a..8a2a528 100644 --- a/generated_def/windef.py +++ b/generated_def/windef.py @@ -13,6 +13,8 @@ class Flag(long): NULL = Flag("NULL", 0) MAX_PATH = Flag("MAX_PATH", 260) ANYSIZE_ARRAY = Flag("ANYSIZE_ARRAY", 1) +IMAGE_SIZEOF_SHORT_NAME = Flag("IMAGE_SIZEOF_SHORT_NAME", 8) +IMAGE_NUMBEROF_DIRECTORY_ENTRIES = Flag("IMAGE_NUMBEROF_DIRECTORY_ENTRIES", 16) WARMING_NOT_SAME_FLAG_FOR_WINXP = Flag("WARMING_NOT_SAME_FLAG_FOR_WINXP", 0) PROCESS_ALL_ACCESS = Flag("PROCESS_ALL_ACCESS", 0x001F0FFF) THREAD_ALL_ACCESS = Flag("THREAD_ALL_ACCESS", 0x001F03FF) diff --git a/generated_def/winfuncs.py b/generated_def/winfuncs.py index 7292c5d..23b0f60 100644 --- a/generated_def/winfuncs.py +++ b/generated_def/winfuncs.py @@ -3,7 +3,7 @@ from ctypes import * from ctypes.wintypes import * from winstructs import * -functions = ['ExitProcess', 'GetLastError', 'GetCurrentProcess', 'CreateFileA', 'CreateFileW', 'NtQuerySystemInformation', 'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualQuery', 'GetModuleFileNameA', 'GetModuleFileNameW', 'CreateRemoteThread', 'VirtualProtect', 'CreateProcessA', 'CreateProcessW', 'GetThreadContext', 'SetThreadContext', 'OpenThread', 'OpenProcess', 'CloseHandle', 'ReadProcessMemory', 'WriteProcessMemory', 'CreateToolhelp32Snapshot', 'Thread32First', 'Thread32Next', 'Process32First', 'Process32Next', 'Process32FirstW', 'Process32NextW', 'GetProcAddress', 'LoadLibraryA', 'LoadLibraryW', 'OpenProcessToken', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW', 'AdjustTokenPrivileges', 'FindResourceA', 'FindResourceW', 'SizeofResource', 'LoadResource', 'LockResource'] +functions = ['ExitProcess', 'GetLastError', 'GetCurrentProcess', 'CreateFileA', 'CreateFileW', 'NtQuerySystemInformation', 'VirtualAlloc', 'VirtualAllocEx', 'VirtualProtect', 'VirtualQuery', 'GetModuleFileNameA', 'GetModuleFileNameW', 'CreateRemoteThread', 'VirtualProtect', 'CreateProcessA', 'CreateProcessW', 'GetThreadContext', 'SetThreadContext', 'OpenThread', 'OpenProcess', 'CloseHandle', 'ReadProcessMemory', 'WriteProcessMemory', 'CreateToolhelp32Snapshot', 'Thread32First', 'Thread32Next', 'Process32First', 'Process32Next', 'Process32FirstW', 'Process32NextW', 'GetProcAddress', 'LoadLibraryA', 'LoadLibraryW', 'OpenProcessToken', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW', 'AdjustTokenPrivileges', 'FindResourceA', 'FindResourceW', 'SizeofResource', 'LoadResource', 'LockResource', 'GetVersionExA', 'GetVersionExW', 'GetVersion'] # ExitProcess(uExitCode): ExitProcessPrototype = WINFUNCTYPE(VOID, UINT) @@ -173,3 +173,15 @@ LoadResourceParams = ((1, 'hModule'), (1, 'hResInfo')) LockResourcePrototype = WINFUNCTYPE(LPVOID, HGLOBAL) LockResourceParams = ((1, 'hResData'),) +# GetVersionExA(lpVersionInformation): +GetVersionExAPrototype = WINFUNCTYPE(BOOL, LPOSVERSIONINFOA) +GetVersionExAParams = ((1, 'lpVersionInformation'),) + +# GetVersionExW(lpVersionInformation): +GetVersionExWPrototype = WINFUNCTYPE(BOOL, LPOSVERSIONINFOW) +GetVersionExWParams = ((1, 'lpVersionInformation'),) + +# GetVersion(): +GetVersionPrototype = WINFUNCTYPE(DWORD) +GetVersionParams = () + diff --git a/generated_def/winstructs.py b/generated_def/winstructs.py index 84ecc8e..c34bcee 100644 --- a/generated_def/winstructs.py +++ b/generated_def/winstructs.py @@ -18,10 +18,11 @@ CHAR = c_char FARPROC = PVOID HGLOBAL = PVOID HRESULT = c_long +ULONGLONG = c_ulonglong PHANDLE = POINTER(HANDLE) VOID = DWORD -structs = ['_LIST_ENTRY', '_PEB_LDR_DATA', '_LSA_UNICODE_STRING', '_RTL_USER_PROCESS_PARAMETERS', '_PEB', '_SECURITY_ATTRIBUTES', '_SYSTEM_VERIFIER_INFORMATION', '_LDR_DATA_TABLE_ENTRY', '_PEB_LDR_DATA', '_IMAGE_FILE_HEADER', '_IMAGE_DATA_DIRECTORY', '_IMAGE_OPTIONAL_HEADER', '_IMAGE_NT_HEADERS', '_MEMORY_BASIC_INFORMATION', '_STARTUPINFOA', '_STARTUPINFOW', '_PROCESS_INFORMATION', '_FLOATING_SAVE_AREA', '_CONTEXT', 'tagPROCESSENTRY32W', 'tagPROCESSENTRY32', 'tagTHREADENTRY32', '_LUID', '_LUID_AND_ATTRIBUTES', '_TOKEN_PRIVILEGES'] +structs = ['_LIST_ENTRY', '_PEB_LDR_DATA', '_LSA_UNICODE_STRING', '_RTL_USER_PROCESS_PARAMETERS', '_PEB', '_SECURITY_ATTRIBUTES', '_SYSTEM_VERIFIER_INFORMATION', '_LDR_DATA_TABLE_ENTRY', '_PEB_LDR_DATA', '_IMAGE_FILE_HEADER', '_IMAGE_DATA_DIRECTORY', '_IMAGE_SECTION_HEADER', '_IMAGE_OPTIONAL_HEADER64', '_IMAGE_OPTIONAL_HEADER', '_IMAGE_NT_HEADERS64', '_IMAGE_NT_HEADERS', '_IMAGE_IMPORT_DESCRIPTOR', '_MEMORY_BASIC_INFORMATION', '_STARTUPINFOA', '_STARTUPINFOW', '_PROCESS_INFORMATION', '_FLOATING_SAVE_AREA', '_CONTEXT', 'tagPROCESSENTRY32W', 'tagPROCESSENTRY32', 'tagTHREADENTRY32', '_LUID', '_LUID_AND_ATTRIBUTES', '_TOKEN_PRIVILEGES', '_OSVERSIONINFOA', '_OSVERSIONINFOW', '_OSVERSIONINFOEXA', '_OSVERSIONINFOEXW'] enums = ['_SYSTEM_INFORMATION_CLASS'] @@ -269,6 +270,60 @@ class _IMAGE_DATA_DIRECTORY(Structure): IMAGE_DATA_DIRECTORY = _IMAGE_DATA_DIRECTORY PIMAGE_DATA_DIRECTORY = POINTER(_IMAGE_DATA_DIRECTORY) +# Struct _IMAGE_SECTION_HEADER definitions +class _IMAGE_SECTION_HEADER(Structure): + _fields_ = [ + ("Name", BYTE * IMAGE_SIZEOF_SHORT_NAME), + ("VirtualSize", DWORD), + ("VirtualAddress", DWORD), + ("SizeOfRawData", DWORD), + ("PointerToRawData", DWORD), + ("PointerToRelocations", DWORD), + ("PointerToLinenumbers", DWORD), + ("NumberOfRelocations", WORD), + ("NumberOfLinenumbers", WORD), + ("Characteristics", DWORD), + ] +PIMAGE_SECTION_HEADER = POINTER(_IMAGE_SECTION_HEADER) +IMAGE_SECTION_HEADER = _IMAGE_SECTION_HEADER + +# Struct _IMAGE_OPTIONAL_HEADER64 definitions +class _IMAGE_OPTIONAL_HEADER64(Structure): + _fields_ = [ + ("Magic", WORD), + ("MajorLinkerVersion", BYTE), + ("MinorLinkerVersion", BYTE), + ("SizeOfCode", DWORD), + ("SizeOfInitializedData", DWORD), + ("SizeOfUninitializedData", DWORD), + ("AddressOfEntryPoint", DWORD), + ("BaseOfCode", DWORD), + ("ImageBase", ULONGLONG), + ("SectionAlignment", DWORD), + ("FileAlignment", DWORD), + ("MajorOperatingSystemVersion", WORD), + ("MinorOperatingSystemVersion", WORD), + ("MajorImageVersion", WORD), + ("MinorImageVersion", WORD), + ("MajorSubsystemVersion", WORD), + ("MinorSubsystemVersion", WORD), + ("Win32VersionValue", DWORD), + ("SizeOfImage", DWORD), + ("SizeOfHeaders", DWORD), + ("CheckSum", DWORD), + ("Subsystem", WORD), + ("DllCharacteristics", WORD), + ("SizeOfStackReserve", ULONGLONG), + ("SizeOfStackCommit", ULONGLONG), + ("SizeOfHeapReserve", ULONGLONG), + ("SizeOfHeapCommit", ULONGLONG), + ("LoaderFlags", DWORD), + ("NumberOfRvaAndSizes", DWORD), + ("DataDirectory", IMAGE_DATA_DIRECTORY * IMAGE_NUMBEROF_DIRECTORY_ENTRIES), + ] +PIMAGE_OPTIONAL_HEADER64 = POINTER(_IMAGE_OPTIONAL_HEADER64) +IMAGE_OPTIONAL_HEADER64 = _IMAGE_OPTIONAL_HEADER64 + # Struct _IMAGE_OPTIONAL_HEADER definitions class _IMAGE_OPTIONAL_HEADER(Structure): _fields_ = [ @@ -302,20 +357,42 @@ class _IMAGE_OPTIONAL_HEADER(Structure): ("SizeOfHeapCommit", DWORD), ("LoaderFlags", DWORD), ("NumberOfRvaAndSizes", DWORD), - ("DataDirectory", PIMAGE_DATA_DIRECTORY), + ("DataDirectory", IMAGE_DATA_DIRECTORY * IMAGE_NUMBEROF_DIRECTORY_ENTRIES), ] -PIMAGE_OPTIONAL_HEADER = POINTER(_IMAGE_OPTIONAL_HEADER) -IMAGE_OPTIONAL_HEADER = _IMAGE_OPTIONAL_HEADER +PIMAGE_OPTIONAL_HEADER32 = POINTER(_IMAGE_OPTIONAL_HEADER) +IMAGE_OPTIONAL_HEADER32 = _IMAGE_OPTIONAL_HEADER + +# Struct _IMAGE_NT_HEADERS64 definitions +class _IMAGE_NT_HEADERS64(Structure): + _fields_ = [ + ("Signature", DWORD), + ("FileHeader", IMAGE_FILE_HEADER), + ("OptionalHeader", IMAGE_OPTIONAL_HEADER64), + ] +PIMAGE_NT_HEADERS64 = POINTER(_IMAGE_NT_HEADERS64) +IMAGE_NT_HEADERS64 = _IMAGE_NT_HEADERS64 # Struct _IMAGE_NT_HEADERS definitions class _IMAGE_NT_HEADERS(Structure): _fields_ = [ ("Signature", DWORD), ("FileHeader", IMAGE_FILE_HEADER), - ("OptionalHeader", IMAGE_OPTIONAL_HEADER), + ("OptionalHeader", IMAGE_OPTIONAL_HEADER32), ] -PIMAGE_NT_HEADERS = POINTER(_IMAGE_NT_HEADERS) -IMAGE_NT_HEADERS = _IMAGE_NT_HEADERS +IMAGE_NT_HEADERS32 = _IMAGE_NT_HEADERS +PIMAGE_NT_HEADERS32 = POINTER(_IMAGE_NT_HEADERS) + +# Struct _IMAGE_IMPORT_DESCRIPTOR definitions +class _IMAGE_IMPORT_DESCRIPTOR(Structure): + _fields_ = [ + ("OriginalFirstThunk", DWORD), + ("TimeDateStamp", DWORD), + ("ForwarderChain", DWORD), + ("Name", DWORD), + ("FirstThunk", DWORD), + ] +IMAGE_IMPORT_DESCRIPTOR = _IMAGE_IMPORT_DESCRIPTOR +PIMAGE_IMPORT_DESCRIPTOR = POINTER(_IMAGE_IMPORT_DESCRIPTOR) # Struct _MEMORY_BASIC_INFORMATION definitions class _MEMORY_BASIC_INFORMATION(Structure): @@ -519,3 +596,73 @@ class _TOKEN_PRIVILEGES(Structure): TOKEN_PRIVILEGES = _TOKEN_PRIVILEGES PTOKEN_PRIVILEGES = POINTER(_TOKEN_PRIVILEGES) +# Struct _OSVERSIONINFOA definitions +class _OSVERSIONINFOA(Structure): + _fields_ = [ + ("dwOSVersionInfoSize", DWORD), + ("dwMajorVersion", DWORD), + ("dwMinorVersion", DWORD), + ("dwBuildNumber", DWORD), + ("dwPlatformId", DWORD), + ("szCSDVersion", CHAR * 128), + ] +POSVERSIONINFOA = POINTER(_OSVERSIONINFOA) +OSVERSIONINFOA = _OSVERSIONINFOA +LPOSVERSIONINFOA = POINTER(_OSVERSIONINFOA) + +# Struct _OSVERSIONINFOW definitions +class _OSVERSIONINFOW(Structure): + _fields_ = [ + ("dwOSVersionInfoSize", DWORD), + ("dwMajorVersion", DWORD), + ("dwMinorVersion", DWORD), + ("dwBuildNumber", DWORD), + ("dwPlatformId", DWORD), + ("szCSDVersion", WCHAR * 128), + ] +RTL_OSVERSIONINFOW = _OSVERSIONINFOW +PRTL_OSVERSIONINFOW = POINTER(_OSVERSIONINFOW) +LPOSVERSIONINFOW = POINTER(_OSVERSIONINFOW) +POSVERSIONINFOW = POINTER(_OSVERSIONINFOW) +OSVERSIONINFOW = _OSVERSIONINFOW + +# Struct _OSVERSIONINFOEXA definitions +class _OSVERSIONINFOEXA(Structure): + _fields_ = [ + ("dwOSVersionInfoSize", DWORD), + ("dwMajorVersion", DWORD), + ("dwMinorVersion", DWORD), + ("dwBuildNumber", DWORD), + ("dwPlatformId", DWORD), + ("szCSDVersion", CHAR * 128), + ("wServicePackMajor", WORD), + ("wServicePackMinor", WORD), + ("wSuiteMask", WORD), + ("wProductType", BYTE), + ("wReserved", BYTE), + ] +OSVERSIONINFOEXA = _OSVERSIONINFOEXA +POSVERSIONINFOEXA = POINTER(_OSVERSIONINFOEXA) +LPOSVERSIONINFOEXA = POINTER(_OSVERSIONINFOEXA) + +# Struct _OSVERSIONINFOEXW definitions +class _OSVERSIONINFOEXW(Structure): + _fields_ = [ + ("dwOSVersionInfoSize", DWORD), + ("dwMajorVersion", DWORD), + ("dwMinorVersion", DWORD), + ("dwBuildNumber", DWORD), + ("dwPlatformId", DWORD), + ("szCSDVersion", WCHAR * 128), + ("wServicePackMajor", WORD), + ("wServicePackMinor", WORD), + ("wSuiteMask", WORD), + ("wProductType", BYTE), + ("wReserved", BYTE), + ] +PRTL_OSVERSIONINFOEXW = POINTER(_OSVERSIONINFOEXW) +LPOSVERSIONINFOEXW = POINTER(_OSVERSIONINFOEXW) +OSVERSIONINFOEXW = _OSVERSIONINFOEXW +POSVERSIONINFOEXW = POINTER(_OSVERSIONINFOEXW) +RTL_OSVERSIONINFOEXW = _OSVERSIONINFOEXW + diff --git a/hooks.py b/hooks.py index 8dc23f4..6af1fa8 100644 --- a/hooks.py +++ b/hooks.py @@ -4,9 +4,8 @@ import native_exec import ctypes import windows.generated_def.winfuncs as winfuncs import windows.generated_def.windef as windef +from windows.generated_def.winstructs import * -DWORD = ctypes.wintypes.DWORD -HANDLE = ctypes.wintypes.HANDLE class Callback(object): def __init__(self, *types): @@ -37,6 +36,7 @@ for func in winfuncs.functions: CallBackDeclaration.__name__ = callback_name add_callback_to_module(CallBackDeclaration()) + class IATHook(object): @@ -55,7 +55,6 @@ class IATHook(object): self.realfunction = ctypes.WINFUNCTYPE(*types)(IAT_entry.nonhookvalue) self.is_enable = False - def transform_arguments(self, types): res = [] for type in types: @@ -66,12 +65,12 @@ class IATHook(object): return res def enable(self): - with utils.VirtualProtected(self.entry.addr, 0x4, windef.PAGE_EXECUTE_READWRITE): + with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), windef.PAGE_EXECUTE_READWRITE): self.entry.value = self.stub self.is_enable = True def disable(self): - with utils.VirtualProtected(self.entry.addr, 0x4, windef.PAGE_EXECUTE_READWRITE): + with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), windef.PAGE_EXECUTE_READWRITE): self.entry.value = self.entry.nonhookvalue self.is_enable = False @@ -85,6 +84,7 @@ class IATHook(object): adapted_args.append(ctypes.c_char_p((value))) else: adapted_args.append(value) + def real_function(*args): if args == (): args = adapted_args diff --git a/injection.py b/injection.py index bf51f12..1ff2185 100644 --- a/injection.py +++ b/injection.py @@ -1,35 +1,88 @@ import sys import os - import utils -from native_exec.simple_x86 import * +import windows -RPython = os.getcwd() + r'\..\RPython' -sys.path.append(RPython) -#import master - -def generate_python_exec_shellcode(PYDLL_addr, PyInit, PyRun, PYCODE_ADDR): +# 32 to 32 injection +def generate_python_exec_shellcode_32(PYDLL_addr, PyInit, PyRun, PYCODE_ADDR): + from native_exec.simple_x86 import * LoadLibraryA = utils.get_func_addr('kernel32', 'LoadLibraryA') GetProcAddress = utils.get_func_addr('kernel32', 'GetProcAddress') code = MultipleInstr() + # Load python27.dll code += Push_X(PYDLL_addr) code += Mov_EAX_X(LoadLibraryA) code += Call_EAX() + # Get PyInit function into pythondll code += Push_EAX() code += Pop_EDI() code += Push_X(PyInit) code += Push_EDI() code += Mov_EBX_X(GetProcAddress) code += Call_EBX() + # Call PyInit code += Call_EAX() + # Get PyRun function into pythondll code += Push_X(PyRun) code += Push_EDI() code += Call_EBX() + # Call PyInit with python code to exec code += Push_X(PYCODE_ADDR) code += Call_EAX() - code += Int3() + code += Pop_EDI() + code += Ret() return code.get_code() + +# 64 to 64 injection +def generate_python_exec_shellcode_64(PYDLL_addr, PyInit, PyRun, PYCODE_ADDR): + from native_exec.simple_x64 import * + LoadLibraryA = utils.get_func_addr('kernel32', 'LoadLibraryA') + GetProcAddress = utils.get_func_addr('kernel32', 'GetProcAddress') + + Reserve_space_for_call = MultipleInstr([Push_RDI()] * 4) + Clean_space_for_call = MultipleInstr([Pop_RDI()] * 4) + + code = MultipleInstr() + # Do stack alignement + code += Push_RAX() + # Load python27.dll + code += Mov_RCX_X(PYDLL_addr) + code += Mov_RAX_X(LoadLibraryA) + code += Reserve_space_for_call + code += Call_RAX() + code += Clean_space_for_call + code += Push_RAX() + code += Pop_RCX() + # Save RCX + code += Push_RCX() + # Align stack + code += Push_RDI() + # Get PyInit function into pythondll + code += Reserve_space_for_call + code += Mov_RDX_X(PyInit) + code += Mov_RBX_X(GetProcAddress) + code += Call_RBX() + # Call PyInit + code += Call_RAX() + code += Clean_space_for_call + # Remove Stack align + code += Pop_RDI() + # Restore pythondll base into rcx + code += Pop_RCX() + # Get PyRun function into pythondll + code += Mov_RDX_X(PyRun) + code += Reserve_space_for_call + code += Call_RBX() + # Call PyInit with python code to exec + code += Mov_RCX_X(PYCODE_ADDR) + code += Call_RAX() + code += Clean_space_for_call + # Remove stack alignement + code += Pop_RAX() + code += Ret() + return code.get_code() + def inject_python_command(process, code_injected, PYDLL="python27.dll\x00"): PyInitT = "Py_Initialize\x00" @@ -57,36 +110,20 @@ def inject_python_command(process, code_injected, PYDLL="python27.dll\x00"): SHELLCODE_ADDR = remote_addr - shellcode = generate_python_exec_shellcode(PYDLL_addr, PyInitT_ADDR, Pyrun_ADDR, PYCODE_ADDR) + if process.bitness == 32: + shellcode = generate_python_exec_shellcode_32(PYDLL_addr, PyInitT_ADDR, Pyrun_ADDR, PYCODE_ADDR) + else: + shellcode = generate_python_exec_shellcode_64(PYDLL_addr, PyInitT_ADDR, Pyrun_ADDR, PYCODE_ADDR) process.write_memory(SHELLCODE_ADDR, shellcode) return SHELLCODE_ADDR + def execute_python_code(process, code): + print("me = {0}".format(windows.current_process.bitness)) + print("him = {0}".format(process.bitness)) + if windows.current_process.bitness != process.bitness: + raise NotImplementedError("Cannot perform 32 <-> 64 injection") shellcode_remote_addr = inject_python_command(process, code) return process.create_thread(shellcode_remote_addr, 0) - -remote_slave_launcher = """ -import sys -import ctypes - -sys.path.append(r'{0}') -import slave - -sys.path.append(r'{1}') -import windows - -name_pool = {{'ctypes' : ctypes, '__import__' : __import__, 'windows' : windows}} -s = slave.RemotePythonSlave.create(name_pool) -slave.debug_run(s) -""".format(RPython, os.getcwd()) - - -def launch_remote_slave(process): - import master - execute_python_code(process, remote_slave_launcher) - m = master.RemotePython.create() - return m - - \ No newline at end of file diff --git a/k32testing.py b/k32testing.py index 0bc39c4..41875c2 100644 --- a/k32testing.py +++ b/k32testing.py @@ -62,14 +62,17 @@ class ApiProxy(object): doc = doc if doc else "" python_proxy.__doc__ = doc + "\nErrcheck:\n " + self.error_check.__doc__ params_name = [param[1] for param in params] - def check_arguments(*args): + def perform_call(*args): if len(params_name) != len(args): + print("ERROR:") + print("Expected params: {0}".format(params_name)) + print("Just Got params: {0}".format(args)) raise ValueError("I do not have all parameters: how is that possible ?") for param_name, param_value in zip(params_name, args): if param_value is NeededParameter: raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.func_name, param_name)) return c_prototyped(*args) - setattr(python_proxy, "ctypes_function", check_arguments) + setattr(python_proxy, "ctypes_function", perform_call) return python_proxy class Kernel32Proxy(ApiProxy): @@ -123,6 +126,10 @@ GetCurrentProcess = TransparentKernel32Proxy("GetCurrentProcess") def VirtualAlloc(lpAddress=0, dwSize=NeededParameter, flAllocationType=MEM_COMMIT, flProtect=PAGE_EXECUTE_READWRITE): return VirtualAlloc.ctypes_function(lpAddress, dwSize, flAllocationType, flProtect) +@Kernel32Proxy("VirtualAllocEx") +def VirtualAllocEx(hProcess, lpAddress=0, dwSize=NeededParameter, flAllocationType=MEM_COMMIT, flProtect=PAGE_EXECUTE_READWRITE): + return VirtualAllocEx.ctypes_function(hProcess, lpAddress, dwSize, flAllocationType, flProtect) + @Kernel32Proxy("CreateRemoteThread") def CreateRemoteThread(hProcess=NeededParameter, lpThreadAttributes=None, dwStackSize=0, lpStartAddress=NeededParameter, lpParameter=NeededParameter, dwCreationFlags=0, lpThreadId=None): @@ -193,7 +200,7 @@ def WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize=None, lpNumberOf """Computer nSize with len(lpBuffer) if not given""" if nSize is None: nSize = len(lpBuffer) - return WriteProcessMemory.ctypes_function(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead) + return WriteProcessMemory.ctypes_function(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten) @Kernel32Proxy("CreateToolhelp32Snapshot") def CreateToolhelp32Snapshot(dwFlags, th32ProcessID=0): diff --git a/native_exec/native_function.py b/native_exec/native_function.py index 718cf80..8837eb6 100644 --- a/native_exec/native_function.py +++ b/native_exec/native_function.py @@ -2,6 +2,9 @@ import ctypes import mmap import platform +import windows +import windows.k32testing as k32api + class PyObj(ctypes.Structure): _fields_ = [("ob_refcnt", ctypes.c_size_t), ("ob_type", ctypes.c_void_p)] #must be cast @@ -36,7 +39,7 @@ class Win32MyMap(MyMap): #access = mmap.ACCESS_READ | mmap.ACCESS_WRITE #return cls(-1, size, access=access) access = mmap.ACCESS_READ | mmap.ACCESS_WRITE - addr = ctypes.windll.kernel32.VirtualAlloc(0, size, 0x1000, 0x40) + addr = k32api.VirtualAlloc(0, size, 0x1000, 0x40) new_map = (ctypes.c_char * size).from_address(addr) new_map.addr = addr @@ -95,15 +98,13 @@ allocator = CustomAllocator() def get_functions(): - # Windows only with python27.dll | improve this + # Windows only with python27.dll | improve this ? import sys - - sys.path.append(r"C:\Users\hakril\Documents\Work\Python_Injection") - import windows - PyGILState_Ensure = windows.get_func_addr('python27', 'PyGILState_Ensure') - PyObject_CallObject = windows.get_func_addr('python27', 'PyObject_CallObject') - PyGILState_Release = windows.get_func_addr('python27', 'PyGILState_Release') + + PyGILState_Ensure = windows.utils.get_func_addr('python27', 'PyGILState_Ensure') + PyObject_CallObject = windows.utils.get_func_addr('python27', 'PyObject_CallObject') + PyGILState_Release = windows.utils.get_func_addr('python27', 'PyGILState_Release') return [PyGILState_Ensure, PyObject_CallObject, PyGILState_Release] def analyse_callback(callback): @@ -114,16 +115,17 @@ def analyse_callback(callback): raise ValueError("Need a ctypes PyCFuncPtr") return obj_id -from simple_x86 import * + # For windows 32 bits with stdcall def generate_stub(callback): + from simple_x86 import * obj_id = analyse_callback(callback) - c_callback = ctypes.c_ulong.from_address(id(callback._objects['0']) + 0xc).value + c_callback = ctypes.c_ulong.from_address(id(callback._objects['0']) + 3 * ctypes.sizeof(ctypes.c_void_p)).value gstate_save_addr = allocator.reserve_int() return_addr_save_addr = allocator.reserve_int() - + save_ebx = allocator.reserve_int() save_ecx = allocator.reserve_int() save_edx = allocator.reserve_int() @@ -155,6 +157,7 @@ def generate_stub(callback): code += Mov_EAX_X(c_callback) code += Call_EAX() + # TODO : Remove this and setup callback as WinFuncType for i in range(len(callback.argtypes)): code += Pop_EBX() @@ -181,12 +184,132 @@ def generate_stub(callback): code += Mov_EDI_DX(save_edi) code += Ret() return code + +# For windows 32 bits with stdcall +def generate_stub_64(callback): + from simple_x64 import * + obj_id = analyse_callback(callback) + + c_callback = ctypes.c_ulong.from_address(id(callback._objects['0']) + 3 * ctypes.sizeof(ctypes.c_void_p)).value + gstate_save_addr = allocator.reserve_int() + return_addr_save_addr = allocator.reserve_int() + + Reserve_space_for_call = MultipleInstr([Push_RDI()] * 4) + Clean_space_for_call = MultipleInstr([Pop_RDI()] * 4) + + save_rbx = allocator.reserve_int() + save_rcx = allocator.reserve_int() + save_rdx = allocator.reserve_int() + save_rsi = allocator.reserve_int() + save_rdi = allocator.reserve_int() + + ensure, objcall, release = get_functions() + print("save_rbx = {0}".format(hex(save_rbx))) + + ### Shellcode ### + code = MultipleInstr() + + code += Mov_RAX_X(save_rbx) + code += Mov_DRAX_RBX() + + code += Mov_RAX_X(save_rcx) + code += Mov_DRAX_RCX() + + code += Mov_RAX_X(save_rdx) + code += Mov_DRAX_RDX() + + code += Mov_RAX_X(save_rsi) + code += Mov_DRAX_RSI() + + code += Mov_RAX_X(save_rdi) + code += Mov_DRAX_RDI() + + # need to save other registers ? + + code += Mov_RAX_X(ensure) + code += Reserve_space_for_call + code += Call_RAX() + code += Clean_space_for_call + code += Mov_DX_RAX(gstate_save_addr) + #Save real return addr (for good argument parsing by the callback) + code += Pop_RAX() + code += Mov_DX_RAX(return_addr_save_addr) + # Restore parameters for real function call + code += Mov_RAX_X(save_rbx) + code += Mov_RBX_DRAX() + code += Mov_RAX_X(save_rcx) + code += Mov_RCX_DRAX() + code += Mov_RAX_X(save_rdx) + code += Mov_RDX_DRAX() + code += Mov_RAX_X(save_rsi) + code += Mov_RSI_DRAX() + code += Mov_RAX_X(save_rdi) + code += Mov_DRAX_RDI() + code += Reserve_space_for_call + # Call python code + code += Mov_RAX_X(c_callback) + code += Clean_space_for_call + code += Call_RAX() + code += Mov_RAX_DX(return_addr_save_addr) + code += Push_RAX() + code += Mov_RAX_X(release) + code += Reserve_space_for_call + code += Call_RAX() + code += Clean_space_for_call + # Restore registers + code += Mov_RAX_X(save_rbx) + code += Mov_RBX_DRAX() + code += Mov_RAX_X(save_rcx) + code += Mov_RCX_DRAX() + code += Mov_RAX_X(save_rdx) + code += Mov_RDX_DRAX() + code += Mov_RAX_X(save_rsi) + code += Mov_RSI_DRAX() + code += Mov_RAX_X(save_rdi) + code += Mov_DRAX_RDI() + code += Ret() + + + ###### CODE 32 %%%%%%%% + # + ##Save real return addr (for good argument parsing by the callback) + # + ## TODO : Remove this and setup callback as WinFuncType + #for i in range(len(callback.argtypes)): + # code += Pop_EBX() + # + ## Restore real return value + #code += Mov_EBX_DX(return_addr_save_addr) + #code += Push_EBX() + # + ## Save return value + #code += Push_EAX() + #code += Mov_EBX_DX(gstate_save_addr) + #code += Push_EBX() + # + #code += Mov_EAX_X(release) + #code += Call_EAX() + # + ## Discard `release` argument + #code += Pop_EAX() + ## Restore return value + #code += Pop_EAX() + #code += Mov_EBX_DX(save_ebx) + #code += Mov_ECX_DX(save_ecx) + #code += Mov_EDX_DX(save_edx) + #code += Mov_ESI_DX(save_esi) + #code += Mov_EDI_DX(save_edi) + #code += Ret() + return code def generate_callback_stub(callback, types): func_type = ctypes.CFUNCTYPE(*types) c_callable = func_type(callback) - stub = generate_stub(c_callable) + if windows.current_process.bitness == 32: + stub = generate_stub(c_callable) + else: + stub = generate_stub_64(c_callable) stub_addr = allocator.write_code(stub.get_code()) generate_callback_stub.l.append((stub, c_callable)) return stub_addr diff --git a/native_exec/simple_x86.py b/native_exec/simple_x86.py index 7460ad1..774bde9 100644 --- a/native_exec/simple_x86.py +++ b/native_exec/simple_x86.py @@ -1,5 +1,6 @@ import struct import sys +# This code should really be rewritten.. this_module = sys.modules[__name__] @@ -164,10 +165,48 @@ class Mov_DX_Reg(object): generate_reg_modr(Mov_DX_Reg) +def generate_reg_indirect_modr_byte(reg_dst_bits, reg_src_bits): + # reg, [reg] or [reg], reg + return "00{0}{1}".format(reg_dst_bits, reg_src_bits) + +def generate_reg_reg_deref(instr_cls, src_first=True): + "generate the Mov_Reg_DReg and Mov_DReg_Reg" + for reg_src_name, reg_src_bits in reg_opcode.items(): + for reg_dst_name, reg_dst_bits in reg_opcode.items(): + if reg_dst_name in ("EBP", "ESP") or reg_src_name in ("EBP", "ESP"): + # Not same encoding -> Not implemented + continue + class Reg_DReg_instruction(X86Instruction): + mnemo = instr_cls.mnemo.format(reg_dst_name, reg_src_name) + name = instr_cls.name.format(reg_dst_name, reg_src_name) + if src_first: + modr_code = generate_reg_indirect_modr_byte(reg_src_bits, reg_dst_bits) + else: + modr_code = generate_reg_indirect_modr_byte(reg_dst_bits, reg_src_bits) + code = instr_cls.instruction_bits + chr(int(modr_code, 2)).encode("hex") + Reg_DReg_instruction.__name__ = Reg_DReg_instruction.name + add_instruction(Reg_DReg_instruction.__name__, Reg_DReg_instruction) + + +class Mov_Reg_DReg(object): + name = 'Mov_{0}_D{1}' + mnemo = 'mov [{0}], {1}' + instruction_bits = '8B' + +generate_reg_reg_deref(Mov_Reg_DReg, False) + +class Mov_DReg_Reg(object): + name = 'Mov_D{0}_{1}' + mnemo = 'mov {0}, [{1}]' + instruction_bits = '89' + +generate_reg_reg_deref(Mov_DReg_Reg, True) def generate_reg_reg_modr_byte(reg_dst_bits, reg_src_bits): + # reg, reg return "11{0}{1}".format(reg_src_bits, reg_dst_bits) + def generate_reg_reg_modr(instr_cls): for reg_src_name, reg_src_bits in reg_opcode.items(): @@ -209,13 +248,18 @@ class JNZ(OneBindX86Instruction): def get_code(self): return super(JNZ, self).get_code() + self.instr_block.get_code() + + class MultipleInstr(object): - def __init__(self): - self.instrs = [] + def __init__(self, init_instrs=()): + self.instrs = list(init_instrs) def __iadd__(self, value): + if type(value) == MultipleInstr: + self.instrs.extend(value.instrs) + return self self.instrs.append(value) return self diff --git a/pe_parse.py b/pe_parse.py index dacbbd5..d0cdaee 100644 --- a/pe_parse.py +++ b/pe_parse.py @@ -1,200 +1,27 @@ import ctypes -from generated_def.winstructs import BYTE, WORD, DWORD, PVOID, CHAR - +import windows +from generated_def.winstructs import * +# This must go to windefs IMAGE_DIRECTORY_ENTRY_EXPORT = 0 IMAGE_DIRECTORY_ENTRY_IMPORT = 1 - IMAGE_ORDINAL_FLAG32 = 0x80000000 +IMAGE_ORDINAL_FLAG64 = 0x8000000000000000 + + +def RedefineCtypesStruct(struct, replacement): + class NewStruct(ctypes.Structure): + _fields_ = transform_ctypes_fields(struct, replacement_) + NewStruct.__name__ = struct.__name__ + return NewStruct + +def transform_ctypes_fields(struct, replacement): + return [(name, replacement.get(name, type)) for name, type in struct._fields_] def PEFile(baseaddr): - class PEFile(object): - def __init__(self): - self.baseaddr = baseaddr - - def get_DOS_HEADER(self): - return IMAGE_DOS_HEADER.from_address(baseaddr) - - def get_NT_HEADER(self): - return self.get_DOS_HEADER().get_NT_HEADER() - - def get_OptionalHeader(self): - return self.get_NT_HEADER().OptionalHeader - - def get_DataDirectory(self): - return self.get_OptionalHeader().DataDirectory - - def get_DataDirectoryAddr(self, number): - return self.get_DataDirectory()[number] - - def get_IMPORT_DESCRIPTORS(self): - return self.get_OptionalHeader().get_IMPORT_DESCRIPTORS() - - def get_EXPORT_DIRECTORY(self): - return self.get_OptionalHeader().get_EXPORT_DIRECTORY() - - @property - def exports(self): - return self.get_EXPORT_DIRECTORY().get_exports() - - @property - def IAT(self): - res = {} - for import_descriptor in self.get_OptionalHeader().get_IMPORT_DESCRIPTORS(): - res.setdefault(import_descriptor.Name.str.lower(),[]).extend(import_descriptor.get_IAT()) - return res - - # TODO: get imports by parsing other modules exports if no INT - @property - def imports(self): - res = {} - for import_descriptor in self.get_OptionalHeader().get_IMPORT_DESCRIPTORS(): - INT = import_descriptor.get_INT() - IAT = import_descriptor.get_IAT() - if INT is not None: - for iat_entry, (ord, name) in zip(IAT, INT): - iat_entry.name = name - iat_entry.ord = ord - res.setdefault(import_descriptor.Name.str.lower(),[]).extend(IAT) - return res - - current_pe = PEFile() - - class IMAGE_DOS_HEADER(ctypes.Structure): - _fields_ = [ - ("e_magic", CHAR * 2), - ("e_cblp", WORD), - ("e_cp", WORD), - ("e_crlc", WORD), - ("e_cparhdr", WORD), - ("e_minalloc", WORD), - ("e_maxalloc", WORD), - ("e_ss", WORD), - ("e_sp", WORD), - ("e_csum", WORD), - ("e_ip", WORD), - ("e_cs", WORD), - ("e_lfarlc", WORD), - ("e_ovno", WORD), - ("e_res", WORD * 4), - ("e_oemid", WORD), - ("e_oeminfo", WORD), - ("e_res2", WORD * 10), - ("e_lfanew", DWORD), - ] - - def get_NT_HEADER(self): - return IMAGE_NT_HEADERS.from_address(baseaddr + self.e_lfanew) - - - class IMAGE_FILE_HEADER(ctypes.Structure): - _fields_ = [ - ("Machine", WORD), - ("NumberOfSections", WORD), - ("TimeDateStamp", DWORD), - ("PointerToSymbolTable", DWORD), - ("NumberOfSymbols", DWORD), - ("SizeOfOptionalHeader", WORD), - ("Characteristics", WORD), - ] - - - class IMAGE_DATA_DIRECTORY(ctypes.Structure): - _fields_ = [ - ("VirtualAddress", DWORD), - ("Size", DWORD), - ] - - class IMAGE_OPTIONAL_HEADER(ctypes.Structure): - _fields_ = [ - ("Magic", WORD), - ("MajorLinkerVersion", BYTE), - ("MinorLinkerVersion", BYTE), - ("SizeOfCode", DWORD), - ("SizeOfInitializedData", DWORD), - ("SizeOfUninitializedData", DWORD), - ("AddressOfEntryPoint", DWORD), - ("BaseOfCode", DWORD), - ("BaseOfData", DWORD), - ("ImageBase", DWORD), - ("SectionAlignment", DWORD), - ("FileAlignment", DWORD), - ("MajorOperatingSystemVersion", WORD), - ("MinorOperatingSystemVersion", WORD), - ("MajorImageVersion", WORD), - ("MinorImageVersion", WORD), - ("MajorSubsystemVersion", WORD), - ("MinorSubsystemVersion", WORD), - ("Win32VersionValue", DWORD), - ("SizeOfImage", DWORD), - ("SizeOfHeaders", DWORD), - ("CheckSum", DWORD), - ("Subsystem", WORD), - ("DllCharacteristics", WORD), - ("SizeOfStackReserve", DWORD), - ("SizeOfStackCommit", DWORD), - ("SizeOfHeapReserve", DWORD), - ("SizeOfHeapCommit", DWORD), - ("LoaderFlags", DWORD), - ("NumberOfRvaAndSizes", DWORD), - ("DataDirectory", IMAGE_DATA_DIRECTORY * 16), - ] - - def tst(self): - import_descriptor_rva = self.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - if import_descriptor_rva == 0: - return [] - import_descriptor_addr = baseaddr + import_descriptor_rva - res = [] - current_import_descriptor = IMPORT_DESCRIPTOR.from_address(import_descriptor_addr) - return current_import_descriptor - - def get_IMPORT_DESCRIPTORS(self): - import_descriptor_rva = self.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - if import_descriptor_rva == 0: - return [] - import_descriptor_addr = baseaddr + import_descriptor_rva - res = [] - current_import_descriptor = IMPORT_DESCRIPTOR.from_address(import_descriptor_addr) - while current_import_descriptor.FirstThunk.value != 0: - res.append(current_import_descriptor) - import_descriptor_addr += ctypes.sizeof(IMPORT_DESCRIPTOR) - current_import_descriptor = IMPORT_DESCRIPTOR.from_address(import_descriptor_addr) - return res - - def get_EXPORT_DIRECTORY(self): - export_directory_rva = self.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress - if export_directory_rva == 0: - return None - export_directory_addr = baseaddr + export_directory_rva - return IMAGE_EXPORT_DIRECTORY.from_address(export_directory_addr) - - - class IMAGE_NT_HEADERS(ctypes.Structure): - _fields_ = [ - ("Signature", CHAR * 2), - ("FileHeader", IMAGE_FILE_HEADER), - ("OptionalHeader", IMAGE_OPTIONAL_HEADER), - ] - - - class IMAGE_SECTION_HEADER(ctypes.Structure): - _fields_ = [ - ("Name", BYTE * 8), - ("VirtualSize", DWORD), - ("VirtualAddress", DWORD), - ("SizeOfRawData", DWORD), - ("PointerToRawData", DWORD), - ("PointerToRelocations", DWORD), - ("PointerToLinenumbers", DWORD), - ("NumberOfRelocations", WORD), - ("NumberOfLinenumbers", WORD), - ("Characteristics", DWORD), - ] - class RVA(DWORD): @property def addr(self): @@ -202,8 +29,7 @@ def PEFile(baseaddr): def __repr__(self): return "".format(self.value, hex(self.addr)) - - # Good idea ? + class StringRVa(RVA): @property def str(self): @@ -214,11 +40,23 @@ def PEFile(baseaddr): def __int__(self): return self.value - - + + + class IMPORT_BY_NAME(ctypes.Structure): + _fields_ = [ + ("Hint", WORD), + ("Name", BYTE) + ] + + class THUNK_DATA(ctypes.Union): + _fields_ = [ + ("Ordinal", PVOID), + ("AddressOfData", PVOID) + ] + class IATEntry(ctypes.Structure): _fields_ = [ - ("value", DWORD)] + ("value", PVOID)] @classmethod def create(cls, addr, ord, name): @@ -246,102 +84,157 @@ def PEFile(baseaddr): self.hook.disable() self.hook = None return True + + class PEFile(object): + def __init__(self): + self.baseaddr = baseaddr + def get_DOS_HEADER(self): + return IMAGE_DOS_HEADER.from_address(baseaddr) - - class IMPORT_DESCRIPTOR(ctypes.Structure): - _fields_ = [ - ("OriginalFirstThunk", RVA), - ("TimeDateStamp", DWORD), - ("ForwarderChain", DWORD), - ("Name", StringRVa), - ("FirstThunk", RVA)] - - - def get_INT(self): - if not self.OriginalFirstThunk.value: - return None - int_addr = self.OriginalFirstThunk.addr - int_entry = THUNK_DATA.from_address(int_addr) + def get_NT_HEADER(self): + return self.get_DOS_HEADER().get_NT_HEADER() + + def get_OptionalHeader(self): + return self.get_NT_HEADER().OptionalHeader + + def get_DataDirectory(self): + return self.get_OptionalHeader().DataDirectory + + def get_IMPORT_DESCRIPTORS(self): + import_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_IMPORT] + if import_datadir.VirtualAddress == 0: + print("get_IMPORT_DESCRIPTORS : VirtualAddress == 0") + return [] + import_descriptor_addr = RVA(import_datadir.VirtualAddress).addr + current_import_descriptor = self.IMAGE_IMPORT_DESCRIPTOR.from_address(import_descriptor_addr) res = [] - while int_entry.Ordinal: - if int_entry.Ordinal & IMAGE_ORDINAL_FLAG32: - res += [(int_entry.Ordinal & 0x7fffffff, None)] - else: - import_by_name = IMPORT_BY_NAME.from_address(baseaddr + int_entry.AddressOfData) - name = ctypes.c_char_p(ctypes.addressof(import_by_name) + IMPORT_BY_NAME.Name.offset).value - res.append((import_by_name.Hint, name)) - int_addr += ctypes.sizeof(THUNK_DATA) + while current_import_descriptor.FirstThunk: + res.append(current_import_descriptor) + import_descriptor_addr += ctypes.sizeof(self.IMAGE_IMPORT_DESCRIPTOR) + current_import_descriptor = self.IMAGE_IMPORT_DESCRIPTOR.from_address(import_descriptor_addr) + return res + + + #def get_EXPORT_DIRECTORY(self): + # return self.get_OptionalHeader().get_EXPORT_DIRECTORY() + + #@property + #def exports(self): + # return self.get_EXPORT_DIRECTORY().get_exports() + + # TODO: get imports by parsing other modules exports if no INT + @property + def imports(self): + res = {} + for import_descriptor in self.get_IMPORT_DESCRIPTORS(): + INT = import_descriptor.get_INT() + IAT = import_descriptor.get_IAT() + if INT is not None: + for iat_entry, (ord, name) in zip(IAT, INT): + iat_entry.name = name + iat_entry.ord = ord + res.setdefault(import_descriptor.Name.str.lower(),[]).extend(IAT) + return res + + # Will be usable as `self.IMPORT_DESCRIPTOR` + class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): + _fields_ = transform_ctypes_fields(IMAGE_IMPORT_DESCRIPTOR, {"Name" : StringRVa, "OriginalFirstThunk" : RVA, "FirstThunk" : RVA}) + + def get_INT(self): + if not self.OriginalFirstThunk.value: + return None + int_addr = self.OriginalFirstThunk.addr int_entry = THUNK_DATA.from_address(int_addr) - return res - - - #def get_IAT(self): - # iat_addr = self.FirstThunk.addr - # iat_entry = THUNK_DATA.from_address(iat_addr) - # res = [] - # INT_iter = iter(self.get_INT()) - # while iat_entry.Ordinal: - # try: - # ordinal, name = next(INT_iter) #Should never StopIteration as IAT and INT are the same size - # except StopIteration: - # raise ValueError("Int shorter than IAT") - # res.append(IATEntry.create(iat_addr, ordinal, name)) - # iat_addr += ctypes.sizeof(THUNK_DATA) - # iat_entry = THUNK_DATA.from_address(iat_addr) - # return res - - def get_IAT(self): - iat_addr = self.FirstThunk.addr - iat_entry = THUNK_DATA.from_address(iat_addr) - res = [] - while iat_entry.Ordinal: - res.append(IATEntry.create(iat_addr, -1, "??")) - iat_addr += ctypes.sizeof(THUNK_DATA) + res = [] + while int_entry.Ordinal: + if int_entry.Ordinal & IMAGE_ORDINAL_FLAG32: + res += [(int_entry.Ordinal & 0x7fffffff, None)] + else: + import_by_name = IMPORT_BY_NAME.from_address(baseaddr + int_entry.AddressOfData) + name = ctypes.c_char_p(ctypes.addressof(import_by_name) + IMPORT_BY_NAME.Name.offset).value + res.append((import_by_name.Hint, name)) + int_addr += ctypes.sizeof(THUNK_DATA) + int_entry = THUNK_DATA.from_address(int_addr) + return res + + def get_IAT(self): + iat_addr = self.FirstThunk.addr iat_entry = THUNK_DATA.from_address(iat_addr) - return res - - - - class IMPORT_BY_NAME(ctypes.Structure): + res = [] + while iat_entry.Ordinal: + res.append(IATEntry.create(iat_addr, -1, "??")) + iat_addr += ctypes.sizeof(THUNK_DATA) + iat_entry = THUNK_DATA.from_address(iat_addr) + return res + + current_pe = PEFile() + + class IMAGE_DOS_HEADER(ctypes.Structure): _fields_ = [ - ("Hint", WORD), - ("Name", BYTE) - ] - - class THUNK_DATA(ctypes.Union): - _fields_ = [ - ("Ordinal", DWORD), - ("AddressOfData", DWORD) - ] - - class IMAGE_EXPORT_DIRECTORY(ctypes.Structure): - _fields_ = [ - ("Characteristics", DWORD), - ("TimeDateStamp", DWORD), - ("MajorVersion", WORD), - ("MinorVersion", WORD), - ("Name", StringRVa), - ("Base", DWORD), - ("NumberOfFunctions", DWORD), - ("NumberOfNames", DWORD), - ("AddressOfFunctions", RVA), - ("AddressOfNames", RVA), - ("AddressOfNameOrdinals", RVA), + ("e_magic", CHAR * 2), + ("e_cblp", WORD), + ("e_cp", WORD), + ("e_crlc", WORD), + ("e_cparhdr", WORD), + ("e_minalloc", WORD), + ("e_maxalloc", WORD), + ("e_ss", WORD), + ("e_sp", WORD), + ("e_csum", WORD), + ("e_ip", WORD), + ("e_cs", WORD), + ("e_lfarlc", WORD), + ("e_ovno", WORD), + ("e_res", WORD * 4), + ("e_oemid", WORD), + ("e_oeminfo", WORD), + ("e_res2", WORD * 10), + ("e_lfanew", DWORD), ] - def get_exports(self): - NameOrdinals = (WORD * self.NumberOfNames).from_address(self.AddressOfNameOrdinals.addr) - NameOrdinals = list(NameOrdinals) - Functions = (RVA * self.NumberOfFunctions).from_address(self.AddressOfFunctions.addr) - Names = (StringRVa * self.NumberOfNames).from_address(self.AddressOfNames.addr) - res = [] - for nb,func in enumerate(Functions): - if nb in NameOrdinals: - name = Names[NameOrdinals.index(nb)] - else: - name = None - res.append((nb, func, name)) - return res - + def get_NT_HEADER(self): + if windows.current_process.bitness == 32: + return IMAGE_NT_HEADERS32.from_address(baseaddr + self.e_lfanew) + return IMAGE_NT_HEADERS64.from_address(baseaddr + self.e_lfanew) + + # + # def get_EXPORT_DIRECTORY(self): + # export_directory_rva = self.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + # if export_directory_rva == 0: + # return None + # export_directory_addr = baseaddr + export_directory_rva + # return IMAGE_EXPORT_DIRECTORY.from_address(export_directory_addr) + # + + + #class IMAGE_EXPORT_DIRECTORY(ctypes.Structure): + # _fields_ = [ + # ("Characteristics", DWORD), + # ("TimeDateStamp", DWORD), + # ("MajorVersion", WORD), + # ("MinorVersion", WORD), + # ("Name", StringRVa), + # ("Base", DWORD), + # ("NumberOfFunctions", DWORD), + # ("NumberOfNames", DWORD), + # ("AddressOfFunctions", RVA), + # ("AddressOfNames", RVA), + # ("AddressOfNameOrdinals", RVA), + # ] + # + # def get_exports(self): + # NameOrdinals = (WORD * self.NumberOfNames).from_address(self.AddressOfNameOrdinals.addr) + # NameOrdinals = list(NameOrdinals) + # Functions = (RVA * self.NumberOfFunctions).from_address(self.AddressOfFunctions.addr) + # Names = (StringRVa * self.NumberOfNames).from_address(self.AddressOfNames.addr) + # res = [] + # for nb,func in enumerate(Functions): + # if nb in NameOrdinals: + # name = Names[NameOrdinals.index(nb)] + # else: + # name = None + # res.append((nb, func, name)) + # return res + # return current_pe \ No newline at end of file diff --git a/remote_callback.py b/remote_callback.py index 979708f..a4cbd87 100644 --- a/remote_callback.py +++ b/remote_callback.py @@ -2,6 +2,7 @@ import ctypes import ctypes.wintypes from windows.hooks import * +# Example of callback for IAT hooks @Callback(ctypes.c_ulong, ctypes.c_ulong) def exit_callback(x, real_function): @@ -11,20 +12,6 @@ def exit_callback(x, real_function): return real_function(1234) return 0 -tt = False - -DWORD = ctypes.wintypes.DWORD - -@Callback(DWORD, DWORD , DWORD, DWORD, DWORD) -def valloc_callback(*args, **kwargs): - global tt - real_function = kwargs['real_function'] - print("Try to virtual alloc with {0}".format([hex(x) for x in args])) - tt = not tt - if tt: - return real_function() - return 0 - @CreateFileACallback def createfile_callback(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, real_function): print("Trying to open {0}".format(lpFileName)) diff --git a/utils.py b/utils.py index e818992..e36ae64 100644 --- a/utils.py +++ b/utils.py @@ -4,7 +4,7 @@ import generated_def.windef as windef import winobject import copy import native_exec -import generated_def.winstructs as winstructs +from generated_def.winstructs import * # Function resolution ! @@ -19,7 +19,6 @@ def get_func_addr(dll_name, func_name): def enumerate_processes(): process_entry = winobject.WinProcess() - #process_entry = winstructs.PROCESSENTRY32() process_entry.dwSize = ctypes.sizeof(process_entry) snap = kernel32proxy.CreateToolhelp32Snapshot(windef.TH32CS_SNAPPROCESS, 0) kernel32proxy.Process32First(snap, process_entry) @@ -40,33 +39,18 @@ def enumerate_threads(): threads.append(copy.copy(thread_entry)) return threads -class System(object): - - @property - def processes(self): - return enumerate_processes() - - @property - def threads(self): - return enumerate_threads() - -class CurrentProcess(object): - get_peb = None +def is_wow_64(hProcess): + try: + fnIsWow64Process = get_func_addr("kernel32.dll", "IsWow64Process") + except kernel32proxy.Kernel32Error: + return False + IsWow64Process = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(BOOL))(fnIsWow64Process) + Wow64Process = BOOL() + res = IsWow64Process(hProcess, ctypes.byref(Wow64Process)) + if res: + return bool(Wow64Process) + raise ctypes.WinError() - get_peb_32_code = '64a130000000c3'.decode('hex') - - def get_peb_builtin(self): - if self.get_peb is not None: - return self.get_peb - get_peb = native_exec.create_function(self.get_peb_32_code, [winstructs.PVOID]) - self.get_peb = get_peb - return get_peb - - @property - def peb(self): - return winobject.PEB.from_address(self.get_peb_builtin()()) - - class VirtualProtected(object): def __init__(self, addr, size, new_protect): if (addr % 0x1000): @@ -76,7 +60,7 @@ class VirtualProtected(object): self.new_protect = new_protect def __enter__(self): - self.old_protect = winstructs.DWORD() + self.old_protect = DWORD() kernel32proxy.VirtualProtect(self.addr, self.size, self.new_protect, ctypes.byref(self.old_protect)) return self diff --git a/winobject.py b/winobject.py index 960e5ca..0d9166b 100644 --- a/winobject.py +++ b/winobject.py @@ -1,7 +1,11 @@ import ctypes +import os + +import windows import utils import k32testing as kernel32proxy import injection +import native_exec from generated_def.winstructs import * import pe_parse @@ -22,7 +26,28 @@ class AutoHandle(object): if hasattr(self, "_handle"): kernel32proxy.CloseHandle(self._handle) - +class System(object): + + @property + def processes(self): + return utils.enumerate_processes() + + @property + def threads(self): + return utils.enumerate_threads() + + @property + def bitness(self): + if os.environ["PROCESSOR_ARCHITECTURE"].lower() != "x86": + return 64 + if "PROCESSOR_ARCHITEW6432" in os.environ: + return 64 + return 32 + + +# May have a common class with WinProcess for is_wow_64 and stuff + + class WinThread(THREADENTRY32, AutoHandle): @property def tid(self): @@ -41,6 +66,39 @@ class WinThread(THREADENTRY32, AutoHandle): def __repr__(self): return '<{0} {1} owner "{2}" at {3}>'.format(self.__class__.__name__, self.tid, self.owner.name, hex(id(self))) + +class CurrentProcess(object): + get_peb = None + get_peb_32_code = '64a130000000c3'.decode('hex') + + # mov rax,QWORD PTR gs:0x60 + # ret + get_peb_64_code = "65488B042560000000C3".decode('hex') + + def get_peb_builtin(self): + if self.get_peb is not None: + return self.get_peb + if self.bitness == 32: + get_peb = native_exec.create_function(self.get_peb_32_code, [PVOID]) + else: + get_peb = native_exec.create_function(self.get_peb_64_code, [PVOID]) + self.get_peb = get_peb + return get_peb + + @property + def peb(self): + return PEB.from_address(self.get_peb_builtin()()) + + @property + def bitness(self): + """Return 32 or 64""" + import platform + bits = platform.architecture()[0] + return int(bits[:2]) + + @property + def is_wow_64(self): + return utils.is_wow_64(kernel32proxy.GetCurrentProcess()) class WinProcess(PROCESSENTRY32, AutoHandle): is_pythondll_injected = 0 @@ -63,6 +121,18 @@ class WinProcess(PROCESSENTRY32, AutoHandle): def __repr__(self): return '<{0} "{1}" pid {2} at {3}>'.format(self.__class__.__name__, self.name, self.pid, hex(id(self))) + + @property + def is_wow_64(self): + return utils.is_wow_64(self.handle) + + @property + def bitness(self): + if windows.system.bitness == 32: + return 32 + if self.is_wow_64: + return 32 + return 64 def virtual_alloc(self, size): return kernel32proxy.VirtualAllocEx(self.handle, dwSize=size) @@ -73,7 +143,35 @@ class WinProcess(PROCESSENTRY32, AutoHandle): def read_memory(self, addr, size): return kernel32proxy.ReadProcessMemory(self.handle, addr, nSize=size) +## CreateRemoteThread(hProcess, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId): +#CreateRemoteThreadPrototype = WINFUNCTYPE(HANDLE, HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD) + + def NtCreateThreadEx(self, addr, param): + print("CALLING SPECIAL NtCreateThreadEx") + NtCreateThreadExAddr = utils.get_func_addr("ntdll.dll", "NtCreateThreadEx") + NtCreateThreadEx = WINFUNCTYPE(HRESULT, PHANDLE, LPSECURITY_ATTRIBUTES, PVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID, BOOL, DWORD, DWORD, DWORD, PVOID)(NtCreateThreadExAddr) + thread_handle = HANDLE() + res = NtCreateThreadEx(byref(thread_handle), None, None, self.handle, addr, param, False, 0, 0, 0, None) + print("RES = {0}".format(hex(res & 0xffffffff))) + if res: + raise WinError() + + def RtlCreateUserThread(self, addr, param): + print("CALLING SPECIAL RtlCreateUserThread") + RtlCreateUserThreadAddr = utils.get_func_addr("ntdll.dll", "RtlCreateUserThread") + RtlCreateUserThread = WINFUNCTYPE(HRESULT, HANDLE, LPSECURITY_ATTRIBUTES, BOOL, ULONG, PULONG, PULONG, PVOID, PVOID, PHANDLE, PVOID)(RtlCreateUserThreadAddr) + thread_handle = HANDLE() + tmp1 = DWORD() + tmp2 = DWORD() + res = RtlCreateUserThread(self.handle, None, False, 0, None, None, addr, param, None, None) + print("RES = {0}".format(hex(res & 0xffffffff))) + if res: + raise WinError() + + def create_thread(self, addr, param): + if windows.current_process.bitness == 32 and self.bitness == 64: + raise NotImplementedError("Injection 32 to 64") return kernel32proxy.CreateRemoteThread(hProcess=self.handle, lpStartAddress=addr, lpParameter=param) def load_library(self, dll_path): @@ -87,12 +185,11 @@ class WinProcess(PROCESSENTRY32, AutoHandle): self.write_memory(x, code) return self.create_thread(x, 0) - def get_remote_python(self): - return injection.launch_remote_slave(self) + def execute_python(self, pycode): + return injection.execute_python_code(self, pycode) class LoadedModule(LDR_DATA_TABLE_ENTRY): - @property def baseaddr(self): return self.DllBase @@ -135,8 +232,8 @@ class PEB(PEB): # This or changing the __repr__ of LSA_UNICODE_STRING raw_cmd = self.ProcessParameters.contents.CommandLine return WinUnicodeString.from_address(ctypes.addressof(raw_cmd)) + @property - def modules(self): res = [] list_entry_ptr = ctypes.cast(self.Ldr.contents.InMemoryOrderModuleList.Flink, LIST_ENTRY_PTR)