GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — PythonMemoryModule/pythonmemorymodule/windows/injection.py at main · freeide/PythonMemoryModule Expand file tree/ Copy pathinjection.pyMore file actions 438 lines (390 loc) · 18.3 KB / Copy pathinjection.pyFile metadata and controls438 lines (390 loc) · 18.3 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 import struct import ctypes import os import sys import windows import windows.utils as utils import windows.generated_def as gdef from .native_exec import simple_x86 as x86 from .native_exec import simple_x64 as x64 from windows.generated_def import STATUS_THREAD_IS_TERMINATING from windows.native_exec.nativeutils import GetProcAddress64, GetProcAddress32 from windows.dbgprint import dbgprint class InjectionFailedError(WindowsError): pass def get_kernel32_dll_name(): # Our injected shellcode search for 'kernel32.dll' with a strcmp # The BaseDllName of k32 might be 'KERNEL32.DLL' or 'kernel32.dll' on different system32 # We base the name on our own loaded kernel32 k32 = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"] assert len(k32) == 1 k32name = k32[0].BaseDllName.str return (k32name + "\x00").encode("utf-16-le") def perform_manual_getproc_loadlib_32(target, dll_name): dll = get_kernel32_dll_name() api = "LoadLibraryA\x00" dll_to_load = dll_name + "\x00" RemoteManualLoadLibray = x86.MultipleInstr() code = RemoteManualLoadLibray code += x86.Mov("ECX", x86.mem("[ESP + 4]")) code += x86.Push(x86.mem("[ECX + 4]")) code += x86.Push(x86.mem("[ECX]")) code += x86.Call(":FUNC_GETPROCADDRESS32") code += x86.Push(x86.mem("[ECX + 8]")) code += x86.Call("EAX") # LoadLibrary code += x86.Pop("ECX") code += x86.Pop("ECX") code += x86.Ret() RemoteManualLoadLibray += GetProcAddress32 with target.allocated_memory(0x1000) as addr: addr2 = addr + len(dll) addr3 = addr2 + len(api) addr4 = addr3 + len(dll_to_load) target.write_memory(addr, dll) target.write_memory(addr2, api) target.write_memory(addr3, dll_to_load) target.write_qword(addr4, addr) target.write_qword(addr4 + 4, addr2) target.write_qword(addr4 + 0x8, addr3) t = target.execute(RemoteManualLoadLibray.get_code(), addr4) t.wait() if not t.exit_code: raise InjectionFailedError("Injection of <{0}> failed".format(dll_name)) return True def perform_manual_getproc_loadlib_64(target, dll_name): dll = get_kernel32_dll_name() api = "LoadLibraryA\x00" dll_to_load = dll_name + "\x00" RemoteManualLoadLibray = x64.MultipleInstr() code = RemoteManualLoadLibray code += x64.Mov("R15", "RCX") code += x64.Mov("RCX", x64.mem("[R15 + 0]")) code += x64.Mov("RDX", x64.mem("[R15 + 8]")) code += x64.Call(":FUNC_GETPROCADDRESS64") code += x64.Mov("RCX", x64.mem("[R15 + 0x10]")) code += x64.Push("RCX") code += x64.Push("RCX") code += x64.Push("RCX") code += x64.Call("RAX") # LoadLibrary code += x64.Pop("RCX") code += x64.Pop("RCX") code += x64.Pop("RCX") code += x64.Ret() RemoteManualLoadLibray += GetProcAddress64 with target.allocated_memory(0x1000) as addr: addr2 = addr + len(dll) addr3 = addr2 + len(api) addr4 = addr3 + len(dll_to_load) target.write_memory(addr, dll) target.write_memory(addr2, api) target.write_memory(addr3, dll_to_load) target.write_qword(addr4, addr) target.write_qword(addr4 + 8, addr2) target.write_qword(addr4 + 0x10, addr3) t = target.execute(RemoteManualLoadLibray.get_code(), addr4) t.wait() if not t.exit_code: raise InjectionFailedError("Injection of <{0}> failed".format(dll_name)) return True def generate_simple_LoadLibraryW_64(load_libraryW, remote_store): code = RemoteLoadLibrayStub = x64.MultipleInstr() code += x64.Mov("RAX", load_libraryW) code += (x64.Push("RDI") * 5) # Prepare stack code += x64.Call("RAX") code += (x64.Pop("RDI") * 5) # Clean stack code += x64.Mov(x64.deref(remote_store), "RAX") code += x64.Ret() return RemoteLoadLibrayStub.get_code() def perform_manual_getproc_loadlib(target, *args, **kwargs): if target.bitness == 32: return perform_manual_getproc_loadlib_32(target, *args, **kwargs) return perform_manual_getproc_loadlib_64(target, *args, **kwargs) def load_dll_in_remote_process(target, dll_path): rpeb = target.peb if rpeb.Ldr: # LDR est parcourable, ca va etre deja plus simple.. modules = rpeb.modules if any(mod.fullname.lower() == dll_path.lower() for mod in modules): # DLL already loaded dbgprint("DLL already present in target", "DLLINJECT") return False k32 = [mod for mod in modules if mod.name.lower() == "kernel32.dll"] if k32: # We have kernel32 \o/ k32 = k32[0] try: load_libraryW = k32.pe.exports["LoadLibraryW"] except KeyError: raise ValueError("Kernel32 have no export <LoadLibraryA> (wtf)") with target.allocated_memory(0x1000) as addr: if target.bitness == 32: target.write_memory(addr, (dll_path + "\x00").encode('utf-16le')) t = target.create_thread(load_libraryW, addr) t.wait() module_baseaddr = t.exit_code else: # For 64b target we need a special stub as the return value of # load_libraryW does not fit in t.exit_code (DWORD) retval_addr = addr target.write_ptr(retval_addr, 0) addr += ctypes.sizeof(ctypes.c_ulonglong) full_dll_name = (dll_path + "\x00").encode('utf-16le') target.write_memory(addr, full_dll_name) param_addr = addr addr += len(full_dll_name) shellcode_addr = addr shellcode = generate_simple_LoadLibraryW_64(load_libraryW, retval_addr) target.write_memory(shellcode_addr, shellcode) t = target.create_thread(shellcode_addr, param_addr) t.wait() module_baseaddr = target.read_ptr(retval_addr) if not module_baseaddr: raise InjectionFailedError(u"Injection of <{0}> failed".format(dll_path)) dbgprint("DLL Injected via LoadLibray", "DLLINJECT") # Cannot return the full return value of load_libraryW in 64b target.. (exit_code is a DWORD) return module_baseaddr # Hardcore mode # We don't have k32 or PEB->Ldr # Go inject a GetProcAddress(LoadLib) + LoadLib shellcode :D dbgprint("DLL Via manual getproc / loadlib", "DLLINJECT") if target.bitness == 32: return perform_manual_getproc_loadlib_32(target, dll_path) return perform_manual_getproc_loadlib_64(target, dll_path) python_function_32_bits = {} def generate_python_exec_shellcode_32(target, PyDll): pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0] base = pymodule.baseaddr if not python_function_32_bits: Py_exports = pymodule.pe.exports python_function_32_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base python_function_32_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base python_function_32_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base python_function_32_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base python_function_32_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base python_function_32_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base python_function_32_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base Py_exports = python_function_32_bits PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base Py_IsInitialized = Py_exports["Py_IsInitialized"] + base PyGILState_Release = Py_exports["PyGILState_Release"] + base PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base Py_Initialize = Py_exports["Py_Initialize"] + base PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base code = x86.MultipleInstr() code += x86.Mov('EAX', Py_IsInitialized) code += x86.Call('EAX') code += x86.Mov("EDI", "EAX") code += x86.Cmp("EAX", 0) code += x86.Jnz(":DO_ENSURE") code += x86.Mov('EAX', Py_Initialize) code += x86.Call('EAX') # https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads code += x86.Mov('EAX', PyEval_InitThreads) code += x86.Call('EAX') code += x86.Label(":DO_ENSURE") code += x86.Mov('EAX', PyGILState_Ensure) code += x86.Call('EAX') code += x86.Push('EAX') # Get the string to execute from parameters code += x86.Mov("EAX", x86.mem("[ESP + 0x8]")) code += x86.Push('EAX') code += x86.Mov('EAX', PyRun_SimpleString) code += x86.Call('EAX') code += x86.Mov("ESI", "EAX") code += x86.Mov('EAX', PyGILState_Release) code += x86.Call('EAX') code += x86.Pop('EAX') code += x86.Cmp("EDI", 0) code += x86.Jnz(":RETURN") # If PyEval_InitThreads was called (init done in this thread) # We must release the GIL code += x86.Mov('EAX', PyEval_SaveThread) code += x86.Call('EAX') code += x86.Label(":RETURN") code += x86.Mov("EAX", "ESI") code += x86.Pop("EDI") code += x86.Ret() return code.get_code() python_function_64_bits = {} def generate_python_exec_shellcode_64(target, PyDll): pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0] base = pymodule.baseaddr if not python_function_64_bits: Py_exports = pymodule.pe.exports python_function_64_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base python_function_64_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base python_function_64_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base python_function_64_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base python_function_64_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base python_function_64_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base python_function_64_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base Py_exports = python_function_64_bits PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base Py_IsInitialized = Py_exports["Py_IsInitialized"] + base PyGILState_Release = Py_exports["PyGILState_Release"] + base PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base Py_Initialize = Py_exports["Py_Initialize"] + base PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4) Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4) code = x64.MultipleInstr() # Do stack alignement code += x64.Push('RCX') code += Reserve_space_for_call code += x64.Mov('RAX', Py_IsInitialized) code += x64.Call('RAX') code += x64.Mov("RDI", "RAX") code += x64.Cmp("RAX", 0) code += x64.Jnz(":DO_ENSURE") code += x64.Mov('RAX', Py_Initialize) code += x64.Call('RAX') # https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads code += x64.Mov('RAX', PyEval_InitThreads) code += x64.Call('RAX') code += x64.Label(":DO_ENSURE") code += x64.Mov('RAX', PyGILState_Ensure) code += x64.Call('RAX') code += x64.Mov('R15', 'RAX') code += x64.Mov("RCX", x64.mem("[RSP + 0x20]")) code += x64.Mov('RAX', PyRun_SimpleString) code += x64.Call('RAX') code += x64.Mov('RCX', 'R15') code += x64.Mov('R15', 'RAX') code += x64.Mov('RAX', PyGILState_Release) code += x64.Call('RAX') code += x64.Cmp("RDI", 0) code += x64.Jnz(":RETURN") # If PyEval_InitThreads was called (init done in this thread) # We must release the GIL code += x64.Mov('RAX', PyEval_SaveThread) code += x64.Call('RAX') code += x64.Label(":RETURN") code += Clean_space_for_call # Remove stack alignement code += x64.Pop('RCX') code += x64.Mov("RAX", "R15") code += x64.Ret() return code.get_code() def inject_python_command(target, code_injected, PYDLL): """Postulate: PYDLL is already loaded in target process""" PYCODE = code_injected + "\x00" # TODO: free this (how ? when ?) remote_python_code_addr = target.virtual_alloc(len(PYCODE)) target.write_memory(remote_python_code_addr, PYCODE) shellcode_addr = getattr(target, "_execute_python_shellcode", None) if shellcode_addr is not None: return shellcode_addr, remote_python_code_addr if target.bitness == 32: shellcode_generator = generate_python_exec_shellcode_32 else: shellcode_generator = generate_python_exec_shellcode_64 shellcode = shellcode_generator(target, PYDLL) shellcode_addr = target.virtual_alloc(len(shellcode)) target.write_memory(shellcode_addr, shellcode) target._execute_python_shellcode = shellcode_addr return shellcode_addr, remote_python_code_addr def get_dll_name_from_python_version(): version = sys.version_info return "python{v.major}{v.minor}.dll".format(v=version) def find_python_dll_to_inject(target_bitness): pydll_name = get_dll_name_from_python_version() if windows.current_process.bitness == target_bitness: # We can inject our own DLL pymodules = [m for m in windows.current_process.peb.modules if m.name == pydll_name] assert len(pymodules) == 1 return pymodules[0].fullname # Okay, so we need to find the DLL to inject. # Problem is, for py3 the DLL is not un system32, so we need for search for it # Simpler solution is the registry # Add a check using %PATH% ? assert windows.system.bitness == 64, "How can we have process of different bitness on 32b system ?" if sys.version_info.major == 2: # Python2 DLL are located in system32/syswow64 # We know that we are looking to DLL of the other bitness if windows.current_process.bitness == 32: # We need to check that the real system32\pythonXX.dll exists systempath = "sysnative" else: # We need to check that the wow64 system32\pythonXX.dll exists systempath = "syswow64" if os.path.exists(os.path.join(os.environ["windir"], systempath, pydll_name)): # In any way (32b ou 64b) the target process will load system32\pydll # If the target is 32b the wow64 layer will translate it return os.path.join(os.environ["windir"], "system32", pydll_name) # If not found this way -> may mean we have a install only for a user, give registry a try # Python 3 dll must be located using the registry for base_key in "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER": # Open the registry in 64b view regardless of current process bitness regbase = windows.system.registry(base_key, gdef.KEY_WOW64_64KEY | gdef.KEY_READ) # we cannot use sys.winver as we are looking for the OTHER version # But from Python <PCbuild/python.props> it looks like format is # {Major}.{Minor}{-32}(for 32b build) # This code do not handle -test version winver_base = sys.winver[:3] # major-minor if target_bitness == 64: pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base)] else: pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base + "-32"), regbase(r"SOFTWARE\WOW6432Node\Python\PythonCore")(winver_base + "-32")] for pyinstallkey in pyinstallkeys: if not pyinstallkey.exists: continue try: pyinstallpath = pyinstallkey("InstallPath")[""].value final_path = os.path.join(pyinstallpath, pydll_name) assert os.path.exists(final_path), "Could not find <{0}> pydll referenced from registry".format(final_path) return final_path except WindowsError as e: if e.winerror != gdef.ERROR_FILE_NOT_FOUND: raise # Not found continue # Could not find a valid installation raise ValueError("Could not find a path for python-dll <{0}>({1}bits)".format(sys.winver, target_bitness)) def execute_python_code(process, code): # Cache the value ? py_dll_name = get_dll_name_from_python_version() pydll_path = find_python_dll_to_inject(process.bitness) if sys.version_info.major == 3: # FOr py3, we may have a per-user install. # Meaning that the vcruntime140.dll will not be in the injected process path # Find it & load-it as well, it should be in the same directory as pythonxx.dll vc_runtime_dll = os.path.join(os.path.dirname(pydll_path), "vcruntime140.dll") load_dll_in_remote_process(process, vc_runtime_dll) # Try to inject the vcrunt load_dll_in_remote_process(process, pydll_path) shellcode, pythoncode = inject_python_command(process, code, py_dll_name) t = process.create_thread(shellcode, pythoncode) return t retrieve_exc = r""" import traceback import sys addr = {0} txt = "".join(traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback)) import ctypes size = ctypes.c_uint.from_address(addr) size.value = len(txt) buff = (ctypes.c_char * len(txt)).from_address(addr + ctypes.sizeof(ctypes.c_uint)) buff[:] = txt.encode() """ def retrieve_last_exception_data(process): with process.allocated_memory(0x1000) as mem: execute_python_code(process, retrieve_exc.format(mem)).wait() size = struct.unpack("<I", process.read_memory(mem, ctypes.sizeof(ctypes.c_uint)))[0] data = process.read_memory(mem + ctypes.sizeof(ctypes.c_uint), size) return data class RemotePythonError(Exception): pass def safe_execute_python(process, code): t = execute_python_code(process, code) t.wait() # Wait terminaison of the thread if t.exit_code == 0: return True if t.exit_code == STATUS_THREAD_IS_TERMINATING or process.is_exit: raise WindowsError("{0} died during execution of python command".format(process)) if t.exit_code != 0xffffffff: raise ValueError("Unknown exit code {0}".format(hex(t.exit_code))) data = retrieve_last_exception_data(process) raise RemotePythonError(data) |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |