srounet / Pymem

A python library for windows, providing the needed functions to start working on your own with memory editing.
MIT License
303 stars 45 forks source link

`pymem.process.inject_dll` returns local handle instead of remote #109

Open y0umu opened 1 year ago

y0umu commented 1 year ago

Describe the bug It seems pymem.process.inject_dll should return the handle of the injected dll. But it is using GetModuleHandleW to search for that handle locally (i.e. in the python.exe process?). I can confirm with Process Explore and x64dbg that the dll is already in the memory of the target process. But pymem.process.inject_dll always gives me None.

If I am wrong please point it out.

Your Environment

  1. Python 3.10.11
  2. Windows 11 64bit (22H2, 22621.1848)
  3. pymem version 1.12.0

Expected behavior pymem.process.inject_dll correctly yields the the address of injected dll by searching the remote process.

Traceback Sorry I don't have a traceback.

Additional context May be the following code could serve as a workaround.

tgt_ps = subprocess.Popen([r'simple_adder\x64\Debug\simple_adder.exe'])

pm = pymem.Pymem('simple_adder.exe') 
helper_path = ctypes.util.find_library(r'D:\lab\python-dll-injection\simple_adder\x64\Debug\helper.dll')
helper_path = bytes(helper_path, 'utf-8')

_helper_address = pymem.process.inject_dll(pm.process_handle, helper_path)  # current _helper_address is useless, it refers to something in the memory of python.exe if not None

print(f'_helper_address={_helper_address}')

# workaround
helper = pymem.process.module_from_name(pm.process_handle, 'helper.dll')

if helper is None:
    raise ValueError('helper.dll failed to load into target memory.')

tgt_ps.kill()
y0umu commented 1 year ago

I think I can illustrate more with this example:

import ctypes
import subprocess
import os
import pymem

local_helper = ctypes.CDLL(ctypes.util.find_library(r'D:\xzc\lab\pyWeKi\python-dll-injection\simple_adder\x64\Debug\helper.dll'))
h_helper = ctypes.windll.kernel32.GetModuleHandleA(b'helper.dll')
print(f'h_helper={h_helper}')

tgt_ps = subprocess.Popen([r'simple_adder\x64\Debug\simple_adder.exe'])

pm = pymem.Pymem('simple_adder.exe') 
helper_path  = ctypes.util.find_library(r'D:\xzc\lab\pyWeKi\python-dll-injection\simple_adder\x64\Debug\helper.dll')
helper_path = bytes(helper_path, 'utf-8')

_helper_address = pymem.process.inject_dll(pm.process_handle, helper_path)

print(f'_helper_address=0x{_helper_address:x}')

tgt_ps.kill()

address_returned_by_inject_dll process_explore_examine

D:\xzc\lab\pyWeKi\python-dll-injection is a directory junction if you have to ask.

StarrFox commented 6 months ago

this is definitely not intended behavior, seems inject_dll also gets LoadLibraryA from the local process? not sure though

Harding-Stardust commented 4 months ago

this is definitely not intended behavior, seems inject_dll also gets LoadLibraryA from the local process? not sure though

To get the address to LoadLibraryA from the local process is OK. A DLL loaded into different processes will have the same base address for each reboot. And a module handle is just the DLL base address.

However the problem is the https://github.com/srounet/Pymem/blob/ea2c215dcd1427d76b604b5faa1b39fe4ba89d69/pymem/process.py#L70 This will try to get the handle from the local process and if the DLL is not loaded in the local process, then this will fail and return None. The solution is to use the workaround @y0umu mention:

module_address = pymem.process.module_from_name(handle, dll_name)
y0umu commented 4 months ago

If this is the only problematic line: https://github.com/srounet/Pymem/blob/ea2c215dcd1427d76b604b5faa1b39fe4ba89d69/pymem/process.py#L70

Maybe we can pull request and patch it.

StarrFox commented 4 months ago

this is definitely not intended behavior, seems inject_dll also gets LoadLibraryA from the local process? not sure though

To get the address to LoadLibraryA from the local process is OK. A DLL loaded into different processes will have the same base address for each reboot. And a module handle is just the DLL base address.

However the problem is the

https://github.com/srounet/Pymem/blob/ea2c215dcd1427d76b604b5faa1b39fe4ba89d69/pymem/process.py#L70

This will try to get the handle from the local process and if the DLL is not loaded in the local process, then this will fail and return None. The solution is to use the workaround @y0umu mention:

module_address = pymem.process.module_from_name(handle, dll_name)

I see, I was unsure if the LoadLibraryA usage was alright

also if the suggested change fixes this issue a pr with it would be great