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'

Categories

Resources