Python3 get process base-address from PID - python
I am trying to get the base-address of a process in Windows (64-bit), with Python3, assuming to know the PID.
I looked over all questions here on stack, but the solutions are old/not working.
I assume to have the PID of the process in a variable called pid.
One of the many pieces of code I tried is
PROCESS_ALL_ACCESS = 0x1F0FFF
processHandle = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
modules = win32process.EnumProcessModules(processHandle)
fileName = win32process.GetModuleFileNameEx(processHandle, modules[0])
base_address = win32api.GetModuleHandle(fileName)
processHandle.close()
But I get error on GetModuleHandle: 'Impossible to find the specified module'.
Thank you for the help.
According to [MS.Docs]: GetModuleHandleW function (emphasis is mine):
Retrieves a module handle for the specified module. The module must have been loaded by the calling process.
That means that it will work fine for the current process, but for any other one you'd get Undefined Behavior, because you try retrieving:
The .dll (or .exe) name from the other process (GetModuleFileNameEx call)
The handle for the name at previous step (GetModuleHandle call) but in the current process (if loaded), which makes no sense
Although there's no clear documentation on this topic (or at least I couldn't find any), the handle is the base address. This is a principle that you also rely on (calling GetModuleHandle), but you can use the values returned by EnumProcessModules directly (look at the example below, the values are the same).
If you want to be rigorous, you could use [MS.Docs]: GetModuleInformation function. Unfortunately, that's not exported by PyWin32, and an alternative is using [Python 3.Docs]: ctypes - A foreign function library for Python.
code00.py:
#!/usr/bin/env python3
import sys
import win32api as wapi
import win32process as wproc
import win32con as wcon
import ctypes as ct
from ctypes import wintypes as wt
import traceback as tb
class MODULEINFO(ct.Structure):
_fields_ = [
("lpBaseOfDll", ct.c_void_p),
("SizeOfImage", wt.DWORD),
("EntryPoint", ct.c_void_p),
]
get_module_information_func_name = "GetModuleInformation"
GetModuleInformation = getattr(ct.WinDLL("kernel32"), get_module_information_func_name, getattr(ct.WinDLL("psapi"), get_module_information_func_name))
GetModuleInformation.argtypes = [wt.HANDLE, wt.HMODULE, ct.POINTER(MODULEINFO)]
GetModuleInformation.restype = wt.BOOL
def get_base_address_original(process_handle, module_handle):
module_file_name = wproc.GetModuleFileNameEx(process_handle, module_handle)
print(" File for module {0:d}: {1:s}".format(module_handle, module_file_name))
module_base_address = wapi.GetModuleHandle(module_file_name)
return module_base_address
def get_base_address_new(process_handle, module_handle):
module_info = MODULEINFO()
res = GetModuleInformation(process_handle.handle, module_handle, ct.byref(module_info))
print(" Result: {0:}, Base: {1:d}, Size: {2:d}".format(res, module_info.lpBaseOfDll, module_info.SizeOfImage))
if not res:
print(" {0:s} failed: {1:d}".format(get_module_information_func_name, getattr(ct.WinDLL("kernel32"), "GetLastError")()))
return module_info.lpBaseOfDll
def main(*argv):
pid = int(argv[0]) if argv and argv[0].isdecimal() else wapi.GetCurrentProcessId()
print("Working on pid {0:d}".format(pid))
process_handle = wapi.OpenProcess(wcon.PROCESS_ALL_ACCESS, False, pid)
print("Process handle: {0:d}".format(process_handle.handle))
module_handles = wproc.EnumProcessModules(process_handle)
print("Loaded modules: {0:}".format(module_handles))
module_index = 0 # 0 - the executable itself
module_handle = module_handles[module_index]
get_base_address_funcs = [
#get_base_address_original, # Original behavior moved in a function
get_base_address_new,
]
for get_base_address in get_base_address_funcs:
print("\nAttempting {0:s}".format(get_base_address.__name__))
try:
module_base_address = get_base_address(process_handle, module_handle)
print(" Base address: 0x{0:016X} ({1:d})".format(module_base_address, module_base_address))
except:
tb.print_exc()
process_handle.close()
#input("\nPress ENTER to exit> ")
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
Output:
e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Working on pid 59608
Process handle: 452
Loaded modules: (140696816713728, 140714582343680, 140714572513280, 140714535354368, 140714547544064, 140713592946688, 140714443341824, 140714557898752, 140714556325888, 140714550362112, 140714414964736, 140714562486272, 140714532798464, 140714555473920, 140714548592640, 140714533322752, 140714531946496, 140714553769984, 140714555670528, 140714558750720, 140714581426176, 140714556129280, 140714546036736, 140714518052864, 140714532601856, 140714524737536, 140714210361344, 1797128192, 140714574151680, 140714535026688, 140714557046784, 140714538172416, 140714531291136, 140714530963456, 140714530766848, 140714530832384, 1796931584, 140714561044480, 140714573299712, 140714215014400, 140714529849344, 1798438912, 140714559995904, 140714167042048)
Attempting get_base_address_new
Result: 1, Base: 140696816713728, Size: 110592
Base address: 0x00007FF687C80000 (140696816713728)
Done.
e:\Work\Dev\StackOverflow\q059610466>:: Attempting to run with Task Manager pid
e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py 22784
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Working on pid 22784
Process handle: 480
Loaded modules: (140699900903424, 140714582343680, 140714572513280, 140714535354368, 140714547544064, 140714573299712, 140714531946496, 140714550362112, 140714562486272, 140714532798464, 140714530963456, 140714530766848, 140714556981248, 140714557898752, 140714556325888, 140714555473920, 140714365222912, 140714548592640, 140714496753664, 140714533322752, 140714553769984, 140714574151680, 140714535026688, 140714557046784, 140714538172416, 140714581426176, 140714558750720, 140714531291136, 140714530832384, 140714546036736, 140714444521472, 140714567467008, 140714532601856, 140714468966400, 140714452385792, 140714267115520, 140714510843904, 140714478731264, 140713698263040, 140714510254080, 140714556129280, 140714565435392, 140714110091264, 140714491379712, 140714455007232, 140714514382848, 140714459529216, 140714281140224, 140714370859008, 140714471260160, 140714566746112, 140713839362048, 140714555670528, 140714171695104, 140714508615680, 140714514841600, 140714029154304, 140714036625408, 140714329636864, 140714447011840, 140714434691072, 140714470866944, 140714561044480, 140714520870912, 140714469883904, 140714494787584, 140714293592064, 140713999335424, 140714400743424, 140714497605632, 140714502193152, 140714197254144, 140714415030272, 140714035576832, 140714065854464, 140714513006592, 140714529652736, 140714512809984, 140714495049728, 140714038657024, 140714371448832, 140714421911552, 140714325966848, 140714196074496, 140714057924608, 140714058317824, 140714064281600, 140714058121216, 140714519756800, 140714327539712, 140714311614464, 140714501079040, 140714546167808, 140714531422208, 140714531553280, 140714557767680, 140714518052864, 140714524737536, 140714167631872, 140714528669696, 140714331865088, 140714310369280, 140714310238208, 140714520018944, 140714458939392, 2018133999616, 140714401988608, 2018141863936, 140714514644992, 140714454810624, 140714294640640)
Attempting get_base_address_new
Result: 1, Base: 140699900903424, Size: 1105920
Base address: 0x00007FF73F9D0000 (140699900903424)
Done.
Update #0
According to [MS.Docs]: MODULEINFO structure (Remarks section, emphasis still mine):
The load address of a module is the same as the HMODULE value.
So, things seem to be pretty straightforward.
code01.py:
#!/usr/bin/env python3
import sys
import win32api as wapi
import win32process as wproc
import win32con as wcon
def main(*argv):
pid = int(argv[0]) if argv and argv[0].isdecimal() else wapi.GetCurrentProcessId()
print("Working on pid {0:d}".format(pid))
process_handle = wapi.OpenProcess(wcon.PROCESS_ALL_ACCESS, False, pid)
print(" Process handle: {0:d}".format(process_handle.handle))
module_handles = wproc.EnumProcessModules(process_handle)
module_handles_count = len(module_handles)
print(" Loaded modules count: {0:d}".format(module_handles_count))
module_index = 0 # 0 - the executable itself
if module_index > module_handles_count:
module_index = 0
module_handle = module_handles[module_index]
module_file_name = wproc.GetModuleFileNameEx(process_handle, module_handle)
print(" File [{0:s}] (index {1:d}) is loaded at address 0x{2:016X} ({3:d})".format(module_file_name, module_index, module_handle, module_handle))
process_handle.close()
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
Output:
e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Working on pid 7184
Process handle: 456
Loaded modules count: 43
File [e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe] (index 0) is loaded at address 0x00007FF687C80000 (140696816713728)
Done.
e:\Work\Dev\StackOverflow\q059610466>:: Attempting to run with Task Manager pid
e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py 22784
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Working on pid 22784
Process handle: 624
Loaded modules count: 111
File [C:\WINDOWS\system32\taskmgr.exe] (index 0) is loaded at address 0x00007FF73F9D0000 (140699900903424)
Done.
Related
Catch events from printer in Windows
The printer has a print queue where are documents prepared for printing that were sent to the printer from a few computers. I'd like to write a code in Python that runs on the server and checks for printer events. Especially when the document has finished successfully printing, I'd like to catch this event and get information about the document document name number of pages format (A4, A3, etc.) was it colorful or black and white time of finish printing Could you help me bounce off? I already studied this question but I can't figure out what I need from it. I tried this code but ends with error message: Traceback (most recent call last): File "...recipe-305690-1.py", line 195, in <module> prt.EnumJobs(pJob, prt.pcbNeeded) File "...recipe-305690-1.py", line 164, in EnumJobs ret = ws.EnumJobsA(self.OpenPrinter(), ctypes.ArgumentError: argument 5: <class 'OverflowError'>: int too long to convert
[GitHub]: ActiveState/code - (master) code/recipes/Python/305690_Enumerate_printer_job/recipe-305690.py (that raises ctypes.ArgumentError in your case) is ancient, some parts of it only worked by luck, and some of them never worked (as the flow didn't reach them). I submitted [GitHub]: ActiveState/code - Fixes and updates which addresses the major problems (there are some left, but the code is working). Starting from that, I tried to tailor it for this question, and address the items (partially at least). Since the question is tagged for [GitHub]: mhammond/pywin32 - pywin32, I used it in order to have (much) shorter (and Python friendlier) code. code00.py: #!/usr/bin/env python import sys import time import win32con as wcon import win32print as wprn from pprint import pprint as pp JOB_INFO_RAW_KEYS_DICT = { # Can comment uninteresting ones "JobId": "Id", "Position": "Index", "pPrinterName": "Printer", "pUserName": "User", "pDocument": "Document", "TotalPages": "Pages", "Submitted": "Created", } def generate_constant_strings(header, mod=None): header_len = len(header) ret = {} for k, v in (globals() if mod is None else mod.__dict__).items(): if k.startswith(header): ret[v] = k[header_len:].capitalize() return ret JOBSTATUS_DICT = generate_constant_strings("JOB_STATUS_", mod=wcon) DMCOLOR_DICT = generate_constant_strings("DMCOLOR_", mod=wcon) DMPAPER_DICT = generate_constant_strings("DMPAPER_", mod=wcon) def printer_jobs(name, level=2): p = wprn.OpenPrinter(wprn.GetDefaultPrinter() if name is None else name, None) jobs = wprn.EnumJobs(p, 0, -1, level) wprn.ClosePrinter(p) return jobs def job_data(job, raw_keys_dict=JOB_INFO_RAW_KEYS_DICT): ret = {} for k, v in job.items(): if k in raw_keys_dict: ret[raw_keys_dict[k]] = v ret["Format"] = DMPAPER_DICT.get(job["pDevMode"].PaperSize) ret["Color"] = DMCOLOR_DICT.get(job["pDevMode"].Color) ret["Status"] = JOBSTATUS_DICT.get(job["Status"]) return ret def main(*argv): printer_name = None interval = 3 while 1: try: jobs = printer_jobs(printer_name) for job in jobs: data = job_data(job) pp(data, indent=2, sort_dicts=0) time.sleep(interval) except KeyboardInterrupt: break if __name__ == "__main__": print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform)) rc = main(*sys.argv[1:]) print("\nDone.") sys.exit(rc) Output: [cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q070103258]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32 { 'Id': 9, 'Printer': 'WF-7610 Series(Network)', 'User': 'cfati', 'Document': '*Untitled - Notepad', 'Index': 1, 'Pages': 1, 'Created': pywintypes.datetime(2021, 12, 3, 22, 52, 22, 923000, tzinfo=TimeZoneInfo('GMT Standard Time', True)), 'Format': 'A4', 'Color': 'Color', 'Status': None} { 'Id': 13, 'Printer': 'WF-7610 Series(Network)', 'User': 'cfati', 'Document': 'e:\\Work\\Dev\\StackOverflow\\q070103258\\code00.py', 'Index': 2, 'Pages': 4, 'Created': pywintypes.datetime(2021, 12, 3, 23, 10, 40, 430000, tzinfo=TimeZoneInfo('GMT Standard Time', True)), 'Format': 'A3', 'Color': 'Monochrome', 'Status': None} Done. Grapically: Notes: Data is retrieved using [MS.Docs]: EnumJobs function (mainly from [MS.Docs]: JOB_INFO_2 structure) As I specified, some of the requested items are not present (finish printing time), and won't be be using this approach. For those ones, [SO]: How to catch printer event in python (#ErykSun's answer) can be used (I tried it for some events, and it works). Unfortunately PyWin32 does not wrap all the print APIs, so printer / job notification functions family (e.g. FindFirstPrinterChangeNotification) should still be called via [Python.Docs]: ctypes - A foreign function library for Python Print jobs from other computers: this is a whole different animal. OpenPrinter function opens the printer on the local machine (by default), and only gets the local queue. I'm not sure how (or whether it's possible) to get all of them. I think that code should not need changes, only setting the computer as a print server (and pass that name as OpenPrinter's 1st argument) from [MS.Docs]: Configure Print and Document Services (but this is an untested assumption)
The problem with the code sample is that it is expecting #164: FirstJob = c_ulong(0) #Start from this job But the verion of the ws = WinDLL("winspool.drv") you are using expects an Int per the error message. My recommendation is to use something like C# as handling this kind of thing is easier and better documentented. https://www.codeproject.com/Articles/51085/Monitor-jobs-in-a-printer-queue-NET
How to implement NetUserEnum function in Python using only ctypes?
I'm looking to use the NetUserEnum() function in the Win32 API using Python but would like to use ctypes instead of a library that already performs this function. I have already done this with GlobalMemoryStatusEx() and GetSystemPowerStatus() but I am struggling with NetUserEnum(). I think I am getting tripped up trying to supply LPBYTE *bufptr to the function. I'm not sure how to properly structure the information and call the function correctly. Any help is appreciated.
As [SO]: How to create a Minimal, Reproducible Example (reprex (mcve)) recommends, you should post your current work together with the problem that prevents you from going further. Before anything else, the references: [Python.Docs]: ctypes - A foreign function library for Python [MS.docs]: NetUserEnum function (lmaccess.h) Yes, that LPBYTE *bufptr argument can be tricky for someone unfamiliar with CTypes (and also processing it afterwards). code00.py: #!/usr/bin/env python import sys import ctypes as ct from ctypes import wintypes as wt DLL_NAME = "netapi32.dll" FILTER_NORMAL_ACCOUNT = 0x0002 MAX_PREFERRED_LENGTH = wt.DWORD(-1) NERR_Success = 0 NET_API_STATUS = wt.DWORD class USER_INFO_0(ct.Structure): _fields_ = [ ("usri0_name", wt.LPWSTR), ] def user_info_type(level): if level == 0: return USER_INFO_0 # elif ... else: print("Invalid user info level") def print_user_info(idx, user_info): print("\nUser {:d}:".format(idx)) if isinstance(user_info, USER_INFO_0): print(" Name: {:s}".format(user_info.usri0_name)) # elif ... else: print("Invalid data") def main(*argv): netapi32 = ct.WinDLL(DLL_NAME) NetUserEnum = netapi32.NetUserEnum NetUserEnum.argtypes = (wt.LPCWSTR, wt.DWORD, wt.DWORD, ct.POINTER(wt.LPBYTE), wt.DWORD, wt.LPDWORD, wt.LPDWORD, wt.PDWORD) NetUserEnum.restype = NET_API_STATUS NetApiBufferFree = netapi32.NetApiBufferFree NetApiBufferFree.argtypes = (wt.LPVOID,) NetApiBufferFree.restype = NET_API_STATUS info_level = 0 filter_flag = FILTER_NORMAL_ACCOUNT buf = wt.LPBYTE() buf_len = MAX_PREFERRED_LENGTH read, total = wt.DWORD(0), wt.DWORD(0) resume_handle = None res = NetUserEnum(None, info_level, filter_flag, ct.byref(buf), buf_len, ct.byref(read), ct.byref(total), resume_handle) print("\n{0:s} returned: {1:d}".format(NetUserEnum.__name__, res)) if res != NERR_Success: return -1 print("{:d} (out of {:d}) entries read".format(read.value, total.value)) UserInfoArray = user_info_type(info_level) * read.value users = UserInfoArray.from_address(ct.addressof(buf.contents)) for idx, user in enumerate(users): print_user_info(idx, user) res = NetApiBufferFree(buf) if res != NERR_Success: print("Error freing buffer: {:d}".format(res)) if __name__ == "__main__": print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform)) rc = main(*sys.argv[1:]) print("\nDone.") sys.exit(rc) Notes: This is an minimum minimorum example to get you past your current problem. There are certain aspects that need to be taken into consideration : If you want more details about each user, use the appropriate USER_INFO_* structure. You should define it (as I did for USER_INFO_0), and also handle it (in the wrapper functions that I wrote: user_info_type, print_user_info), and (obviously) set the appropriate level (info_level) This minimal example is OK for a regular computer. But, if you attempt to run it in a domain (with tenths of thousands of users) you might experience some memory issues. In that case, you should set a decent buf_size (maybe 16 MiBs) and call NetUserEnum (and also NetApiBufferFree) in a loop, relying on resume_handle, while it keeps returning ERROR_MORE_DATA Make sure the user that runs this program has enough privileges Output: cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q066185045]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 64bit on win32 NetUserEnum returned: 0 6 (out of 6) entries read User 0: Name: Administrator User 1: Name: cfati User 2: Name: DefaultAccount User 3: Name: Guest User 4: Name: test User 5: Name: WDAGUtilityAccount Done.
Compare two IPv6 addresses for equality
I need to compare two IP version6, if there are equal. It is not a simple string comparison because the same address can be written in multiple ways. I can't use third-party packages. For example: 2041:0000:140F:0000:0000:0000:875B:131B 2041:0000:140F::875B:131B 2041:0:140F::875B:131B and from windows ip config can be: 2041:0:140F::875B:131B%11
If you are on Python 3.3+ you can use the standard library module ipaddress: Python 3.7.5 (default, Dec 15 2019, 17:54:26) [GCC 9.2.1 20190827 (Red Hat 9.2.1-1)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ipaddress >>> a=ipaddress.ip_address('2041:0000:140F:0000:0000:0000:875B:131B') >>> b=ipaddress.ip_address('2041:0000:140F::875B:131B') >>> c=ipaddress.ip_address('2041:0:140F::875B:131B') >>> a==b==c True
you can try with socket library : import socket ip1 = "2041:0000:140F:0000:0000:0000:875B:131B" ip2 = "2041:0000:140F::875B:131B" ip3 = "2041:0000:140F::875B:131B" if socket.inet_pton(socket.AF_INET6, ip1) == socket.inet_pton(socket.AF_INET6, ip2) == socket.inet_pton(socket.AF_INET6, ip3): print ("match") Output:
For example: import sys def extend_ipv6(src): parts = src.split(":") if '%' in parts[-1]: parts[-1], _ = parts[-1].split('%', 2) n_parts = len(parts) out = list() for part in parts: if len(part) == 0: for i in range(8-n_parts+1): out.append("0000") else: out.append("0"*(4-len(part))+part) return ":".join(out) def main(): for arg in sys.argv[1:]: print(extend_ipv6(arg)) if __name__ == "__main__": main() python3 do.py 2041:0000:140F:0000:0000:0000:875B:131B 2041:0000:140F::875B:131B 2041:0:140F::875B:131B 2041:0:140F::875B:131B%11 2041:0000:140F:0000:0000:0000:875B:131B 2041:0000:140F:0000:0000:0000:875B:131B 2041:0000:140F:0000:0000:0000:875B:131B 2041:0000:140F:0000:0000:0000:875B:131B will be this solution enough for your task?
Ctypes- pass the address of an integer to WriteProcessMemory
I want to write to address1 the value of number1 that is an integer. I tried to use addressof but it still doesn't work. what is the equivalent of & from c++? windll.kernel32.WriteProcessMemory.argtypes = [c_void_p, c_void_p, c_char_p, c_int, c_void_p] windll.kernel32.WriteProcessMemory.restype = c_void_p address1 = 0xa9a010 number1 = 0x140000000 lpNumberOfBytesWritten = c_size_t(0) if windll.kernel32.WriteProcessMemory( hProcess, c_char_p(address1), addressof(c_char_p(number1)), sizeof(c_void_p), byref(lpNumberOfBytesWritten)) == 0: error() The desired output is *address1=0x140000000 but it contains other value
Listing [Python 3.Docs]: ctypes - A foreign function library for Python. There is a number of problems (besides the question lacking some info): When working with ctypes functions (e.g. ctypes.addressof), you should consider that they operate on ctypes (not Python) types. Therefore, number must be converted to a ctypes type (that should fit its value). Creating a pointer from its value is a recipe for disaster Generally, importing everything in the current namespace is not a good idea ([SO]: What is the reason for using a wildcard import? (#CristiFati's answer)) Your [MS.Docs]: WriteProcessMemory function definition is a bit wrong (not critical), but it's best to use existing aliases (for readability's sake) Here's a working variant. code00.py: #!/usr/bin/env python3 import sys import ctypes from ctypes import wintypes def main(): kernel32 = ctypes.WinDLL("kernel32.dll") GetCurrentProcess = kernel32.GetCurrentProcess GetCurrentProcess.argtypes = [] GetCurrentProcess.restype = wintypes.HANDLE WriteProcessMemory = kernel32.WriteProcessMemory WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] WriteProcessMemory.restypes = wintypes.BOOL buf = ctypes.create_string_buffer(b"0123456789") print("Buffer INITIAL contents: [{0:}]".format(buf.value)) number = ctypes.c_ulonglong(0x4847464544434241) # 8 bytes: ASCII codes H .. A address = ctypes.addressof(buf) # Mimic your address - which is the above buffer's bytes_to_write = ctypes.sizeof(number) bytes_written = ctypes.c_size_t(0) print("Attempting to write ({0:d} bytes) number {1:d} (0x{2:016X}) to address {3:} (0x{4:016X}) ...".format(bytes_to_write, number.value, number.value, address, address)) res = WriteProcessMemory(GetCurrentProcess(), address, ctypes.addressof(number), bytes_to_write, ctypes.byref(bytes_written)) if res: print("Wrote {0:d} bytes".format(bytes_written.value)) print("Buffer FINAL contents: [{0:}]".format(buf.value)) if __name__ == "__main__": print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform)) main() print("\nDone.") Output: [cfati#CFATI-5510-0:e:\Work\Dev\StackOverflow\q057768711]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32 Buffer INITIAL contents: [b'0123456789'] Attempting to write (8 bytes) number 5208208757389214273 (0x4847464544434241) to address 1807564046992 (0x000001A4DB368290) ... Wrote 8 bytes Buffer FINAL contents: [b'ABCDEFGH89'] Done. Notes: As seen, the memory contents at the specified address was successfully written The only "abnormal" thing is that the number had the ASCII codes ['H' .. 'A'], but in the buffer they appear reversed. That is because my PC is Little-endian. If you care to take a look, I covered this topic in [SO]: Python struct.pack() behavior (#CristiFati's answer)
First, sizeof(c_void_p) = 4; Because 0x140000000 exceeds four bytes, it will be truncated. You can see that under the same environment, the results of 0x40000000 and 0x140000000 are the same. You need to change sizeof(c_void_p) to sizeof(c_longlong) Second, According to WriteProcessMemory's function prototype BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten ); You can see the type of lpBuffer is VOID* So you need to change windll.kernel32.WriteProcessMemory.argtypes = [c_void_p, c_void_p, c_char_p, c_int, c_void_p] to windll.kernel32.WriteProcessMemory.argtypes = [c_void_p, c_void_p, c_void_p, c_int, c_void_p] Finally, this is the modified code. windll.kernel32.WriteProcessMemory.argtypes = [c_void_p, c_void_p, c_void_p, c_int, c_void_p] windll.kernel32.WriteProcessMemory.restype = c_void_p address1 = 0xa9a010 number1 = 0x140000000 lpNumberOfBytesWritten = c_size_t(0) if windll.kernel32.WriteProcessMemory( hProcess, c_char_p(address1), addressof(c_longlong(number1)), sizeof(c_longlong), byref(lpNumberOfBytesWritten)) == 0: error() Note: When checking * address1 at hProcess, you should also pay attention to the type and use long type to check address1.
How to use SHGetFileInfo with SHGFI_PIDL in python
I'm trying to retrieve file information (specifically info about the icon) using SHGetFileInfo. In reality, I don't have the full path of the file, I only have the pidl. The following code returns (0L, (0, 0, 0, '', '')) and my question is why. from win32com.shell import shell, shellcon def get_info(): desktop = shell.SHGetDesktopFolder() eaten, desktop_pidl, attr = desktop.ParseDisplayName(None, None, r"C:\Users\Ella\Desktop") return shell.SHGetFileInfo(desktop_pidl, 0, shellcon.SHGFI_PIDL | shellcon.SHGFI_SYSICONINDEX | shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME) On the other hand, the code bellow does work for some reason (it uses full path instead of pidl): from win32com.shell import shell, shellcon def get_info2(): return shell.SHGetFileInfo(r"C:\Users\Ella\Desktop", 0, shellcon.SHGFI_SYSICONINDEX | shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME) Thanks!
You've uncovered a bug in PySHGetFileInfo. If SHGFI_PIDL is set in flags, it calls PyObject_AsPIDL and stores the result to pidl_or_name, but it mistakenly passes name to SHGetFileInfo, which in this case is the initial NULL value. See below for more details. You asked how to set a breakpoint on shell32!SHGetFileInfoW. There's no simple answer to that. Instead allow me to share an overview of what I did to test this. Hopefully this will at least get you started. Test environment: 64-bit Windows 7 SP1 (6.1.7601) Windows SDK 7.1 (ensure the debuggers are installed) Visual Studio 2010 SP1 Visual C++ 2010 SP1 Compiler Update Python 3.4 (and debug files) Mercurial (hg.exe, not TortoiseHg) Set up the shell environment. "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.Cmd" set MSSdk=%WindowsSDKDir% set SYMDIR=C:\Symbols set SYMSRV=http://msdl.microsoft.com/download/symbols set _NT_SYMBOL_PATH=symsrv*symsrv.dll*%SYMDIR%*%SYMSRV% path C:\Program Files\Debugging Tools for Windows (x64);%PATH% path C:\Program Files\Mercurial;%PATH% Create a Python virtual environment. py -3.4 -m venv --symlinks test venv doesn't link the .pdb files, so grab those manually in a for loop. set PYDIR="%ProgramW6432%\Python34" set CMD=mklink "test\Scripts\%~nxf" "%f" for /R %PYDIR% %f in (*.pdb) do #%CMD% Activate the virtual environment. test\Scripts\activate Clone the PyWin32 repo. Build and install version 219. set HGSRV=http://pywin32.hg.sourceforge.net hg clone %HGSRV%/hgroot/pywin32/pywin32 cd pywin32 hg up b219 I edited setup.py to comment out everything related to building win32com.mapi. My setup didn't even have the required headers, and when I obtained them there were problems building the extension for WIN64. Build and install the package. python setup3.py install Run Python under the console debugger, cdb.exe. >cdb -xi ld python Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: python Symbol search path is: symsrv*symsrv.dll*C:\Symbols* http://msdl.microsoft.com/download/symbols Executable search path is: (d50.1174): Break instruction exception - code 80000003 (first chance) ntdll!LdrpDoDebuggerBreak+0x30: 00000000`770bcb70 cc int 3 0:000> bp shell32!SHGetFileInfoW 0:000> g Python 3.4.2 (v3.4.2:ab2c023a9432, Oct 6 2014, 22:16:31) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. The option -xi ld in the above command line sets a filter to ignore printing loaded modules. There are lots of tutorials and 'cheat sheets' online for using Microsoft's debuggers such as WinDbg, cdb, and kd. The debuggers all use the same engine, so they support a common set of debugging commands. The attached debugger has a breakpoint set on shell32!SHGetFileInfoW. When the breakpoint is triggered, the debugger grabs the console. One of the few redeeming features of the Windows console is its per-application input history and aliases. This makes it convenient to recall commands when bouncing in and out of the debugger and debuggee in the same console window. >>> import os >>> from win32com.shell import shell, shellcon >>> print(shell.__file__) C:\Temp\test\lib\site-packages\win32comext\shell\shell.pyd >>> path = os.path.expanduser(r'~\Desktop\desktop.ini') >>> pidl = shell.SHParseDisplayName(path, 0, None)[0] >>> flags = (shellcon.SHGFI_PIDL | ... shellcon.SHGFI_SYSICONINDEX | ... shellcon.SHGFI_ICON | ... shellcon.SHGFI_DISPLAYNAME) >>> shell.SHGetFileInfo(pidl, 0, flags) Breakpoint 0 hit SHELL32!SHGetFileInfoW: 000007fe`fd692290 fff3 push rbx 0:000> k 5 *** WARNING: Unable to verify checksum for C:\Temp\test\lib\site-packages\win32comext\shell\shell.pyd Child-SP RetAddr Call Site 00000000`003ff2d8 00000000`5f44c5e8 SHELL32!SHGetFileInfoW 00000000`003ff2e0 00000000`5f5af8bd shell!PySHGetFileInfo+0xf8 00000000`003ff610 00000000`5f62385b python34!PyCFunction_Call+0x12d 00000000`003ff640 00000000`5f625c89 python34!call_function+0x2ab 00000000`003ff6a0 00000000`5f62770c python34!PyEval_EvalFrameEx+0x2279 0:000> r rcx rcx=0000000000000000 0:000> g (0, (0, 0, 0, '', '')) In the Windows x64 ABI, the first argument of a function is passed in register rcx. We know from the SHGetFileInfo docs that this should be the PIDL, but actually NULL is being passed. Clearly this is a bug. The stack trace lays the blame on shell!PySHGetFileInfo. Here's a snippet of the problematic code: if (flags & SHGFI_PIDL) { ok = PyObject_AsPIDL(obName, &pidl, FALSE); pidl_or_name = (TCHAR *)pidl; } else { ok = PyWinObject_AsTCHAR(obName, &name, FALSE); pidl_or_name = name; } if (!ok) return NULL; SHFILEINFO info; memset(&info, 0, sizeof(info)); info.dwAttributes = info_attrs; PY_INTERFACE_PRECALL; DWORD_PTR dw = SHGetFileInfo(name, attr, &info, sizeof(info), flags); The mistake is passing name as the first argument instead of pidl_or_name. The question is tagged ctypes. IMO, using ctypes is worth it if doing so eliminates a large dependency such as PyWin32. I wouldn't normally use ctypes by itself for a COM-based API. The comtypes package builds on ctypes if you want to try that. In this case directly calling COM methods can be avoided by instead calling SHParseDisplayName. Other than using HRESULT return codes, it's pretty much like any other Win32 API. import types as _types import ctypes as _ctypes from ctypes import wintypes as _wtypes _mtypes = _types.ModuleType('_mtypes') _ole32 = _ctypes.WinDLL('ole32') _shell32 = _ctypes.WinDLL('shell32') _user32 = _ctypes.WinDLL('user32') try: from win32com.shell import shell as _shell except ImportError: _shell = None try: from win32com.shell import shellcon except ImportError: shellcon = _types.ModuleType('shellcon') shellcon.SHGFI_LARGEICON = 0x00000 shellcon.SHGFI_SMALLICON = 0x00001 shellcon.SHGFI_OPENICON = 0x00002 shellcon.SHGFI_SHELLICONSIZE = 0x00004 shellcon.SHGFI_PIDL = 0x00008 shellcon.SHGFI_USEFILEATTRIBUTES = 0x00010 shellcon.SHGFI_ICON = 0x00100 shellcon.SHGFI_DISPLAYNAME = 0x00200 shellcon.SHGFI_TYPENAME = 0x00400 shellcon.SHGFI_ATTRIBUTES = 0x00800 shellcon.SHGFI_ICONLOCATION = 0x01000 shellcon.SHGFI_EXETYPE = 0x02000 shellcon.SHGFI_SYSICONINDEX = 0x04000 shellcon.SHGFI_LINKOVERLAY = 0x08000 shellcon.SHGFI_SELECTED = 0x10000 shellcon.SHGFI_ATTR_SPECIFIED = 0x20000 try: import win32con except ImportError: win32con = _types.ModuleType('win32con') win32con.MAX_PATH = 260 win32con.FILE_ATTRIBUTE_READONLY = 0x00001 win32con.FILE_ATTRIBUTE_HIDDEN = 0x00002 win32con.FILE_ATTRIBUTE_SYSTEM = 0x00004 win32con.FILE_ATTRIBUTE_DIRECTORY = 0x00010 win32con.FILE_ATTRIBUTE_ARCHIVE = 0x00020 win32con.FILE_ATTRIBUTE_DEVICE = 0x00040 win32con.FILE_ATTRIBUTE_NORMAL = 0x00080 win32con.FILE_ATTRIBUTE_TEMPORARY = 0x00100 win32con.FILE_ATTRIBUTE_ATOMIC_WRITE = 0x00200 win32con.FILE_ATTRIBUTE_SPARSE_FILE = 0x00200 win32con.FILE_ATTRIBUTE_REPARSE_POINT = 0x00400 win32con.FILE_ATTRIBUTE_XACTION_WRITE = 0x00400 win32con.FILE_ATTRIBUTE_COMPRESSED = 0x00800 win32con.FILE_ATTRIBUTE_OFFLINE = 0x01000 win32con.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x02000 win32con.FILE_ATTRIBUTE_ENCRYPTED = 0x04000 win32con.FILE_ATTRIBUTE_VIRTUAL = 0x10000 _mtypes.CData = _ctypes.Array.__bases__[0] _mtypes.PPIDLIST_ABSOLUTE = _ctypes.POINTER(_ctypes.c_void_p) _mtypes.SFGAOF = _wtypes.ULONG _mtypes.PSFGAOF = _ctypes.POINTER(_mtypes.SFGAOF) _ole32.CoInitialize.restype = _ctypes.HRESULT # checked _ole32.CoInitialize.argtypes = (_ctypes.c_void_p,) _ole32.CoUninitialize.restype = None _ole32.CoUninitialize.argtypes = () _ole32.CoTaskMemFree.restype = None _ole32.CoTaskMemFree.argtypes = (_ctypes.c_void_p,) _user32.DestroyIcon.argtypes = (_wtypes.HICON,) _shell32.SHParseDisplayName.restype = _ctypes.HRESULT # checked _shell32.SHParseDisplayName.argtypes = ( _wtypes.LPCWSTR, # pszName, _In_ _ctypes.c_void_p, # pbc, _In_opt_ _mtypes.PPIDLIST_ABSOLUTE, # ppidl, _Out_ _mtypes.SFGAOF, # sfgaoIn, _In_ _mtypes.PSFGAOF) # psfgaoOut, _Out_opt_ class SHFILEINFO(_ctypes.Structure): _fields_ = (('hIcon', _wtypes.HICON), ('iIcon', _ctypes.c_int), ('dwAttributes', _wtypes.DWORD), ('szDisplayName', _wtypes.WCHAR * win32con.MAX_PATH), ('szTypeName', _wtypes.WCHAR * 80)) _mtypes.SHFILEINFO = SHFILEINFO _mtypes.PSHFILEINFO = _ctypes.POINTER(SHFILEINFO) _shell32.SHGetFileInfoW.restype = _ctypes.c_void_p _shell32.SHGetFileInfoW.argtypes = ( _wtypes.LPVOID, # pszPath, _In_ _wtypes.DWORD, # dwFileAttributes, _mtypes.PSHFILEINFO, # psfi, _Inout_ _wtypes.UINT, # cbFileInfo, _wtypes.UINT) # uFlags def SHGetFileInfo(pidl, attributes=0, flags=0): if _shell is not None: if not isinstance(pidl, (str, bytes, _mtypes.CData)): pidl = _shell.PIDLAsString(pidl) finfo = SHFILEINFO() _ole32.CoInitialize(None) try: retval = _shell32.SHGetFileInfoW(pidl, attributes, _ctypes.byref(finfo), _ctypes.sizeof(finfo), flags) finally: _ole32.CoUninitialize() if not retval: if flags != shellcon.SHGFI_EXETYPE: raise _ctypes.WinError() return retval, finfo Example: if __name__ == '__main__': import os path = os.path.expanduser(r'~\Desktop\desktop.ini') pidl = _shell.SHParseDisplayName(path, 0)[0] assert isinstance(pidl, list) flags = (shellcon.SHGFI_PIDL | shellcon.SHGFI_ICON | shellcon.SHGFI_DISPLAYNAME | shellcon.SHGFI_TYPENAME | shellcon.SHGFI_ATTRIBUTES | shellcon.SHGFI_SYSICONINDEX) hImageList, finfo = SHGetFileInfo(pidl, 0, flags) print('hImageList:', hImageList) for name, typ in finfo._fields_: print(name, ': ', ascii(getattr(finfo, name)), sep='') if finfo.hIcon: _user32.DestroyIcon(finfo.hIcon) Output: hImageList: 4411024 hIcon: 10617107 iIcon: 7 dwAttributes: 1078497655 szDisplayName: 'desktop.ini' szTypeName: 'Configuration settings'