I'm want to automate dragging a file onto an external application. I want to do this by sending a message to the target application rather than trying something error prone, like moving the mouse.
As a test case, I'm trying to do this with notepad.exe. I'm running this test code in anaconda as the administrator, but it fails on VirtualProtectEx with error code 87. Which seems to mean an invalid parameter, but I'm totally lost as to what's invalid.
What am I doing wrong?
import os
import ctypes
import struct
import win32con
import win32process
import win32gui
GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx
VirtualFreeEx = ctypes.windll.kernel32.VirtualFreeEx
OpenProcess = ctypes.windll.kernel32.OpenProcess
WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory
ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory
VirtualProtectEx = ctypes.windll.kernel32.VirtualProtectEx
VirtualProtectEx.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.LPVOID, ctypes.c_size_t, ctypes.wintypes.DWORD, ctypes.wintypes.LPVOID]
VirtualProtectEx.restype = ctypes.wintypes.BOOL
def dragFileToWnd(filename, hwnd):
# Pack the data
filepath = bytes(filename + "\0\0", encoding="GBK")
packed_filedata = struct.pack("iiiii" + str(len(filepath)) + "s",*[0x14, 0x0A, 0x0A, 00, 00, filepath])
buffer = ctypes.create_string_buffer(packed_filedata)
# Find window handles for the specific thread
thread_id, pid = win32process.GetWindowThreadProcessId(hwnd)
pid = ctypes.c_uint(pid)
# Attach to process
hProcHnd = OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid)
# Allocate memory in target process
mem_addr = VirtualAllocEx(hProcHnd, 0, len(buffer), win32con.MEM_RESERVE|win32con.MEM_COMMIT, win32con.PAGE_READWRITE)
# Apply for protected memory access?
# WriteProcessMemory returns a 998 error, that I *think* VirtualProtectEx
# will fix, but VirtualProtectEx returns error 87.
oldprotect = ctypes.c_ulong()
protect_result = VirtualProtectEx(hProcHnd, mem_addr, len(buffer), win32con.PAGE_READWRITE, ctypes.byref(oldprotect))
if not protect_result:
print('VirtualProtectEx Failed with error code', ctypes.windll.kernel32.GetLastError())
VirtualFreeEx(hProcHnd, mem_addr, 0, win32con.MEM_RELEASE)
return
# Write filenames to target process
# As I understand it, we must write our filenames in the target processes' memory
# or the drop will fail / memory address won't be vaild.
bytes_written = ctypes.c_ulong(0)
WriteProcessMemory(hProcHnd, mem_addr, buffer, len(buffer), ctypes.byref(bytes_written))
# Restore memory access
VirtualProtectEx(hProcHnd, mem_addr, len(buffer), oldprotect.value, ctypes.byref(oldprotect))
# Send ancient drag-drop message from windows 3.1 era, and hope the application accepts it
# Is there a better way? That doesn't involve injecting memory?
print("Bytes bytes_written:", bytes_written.value)
if bytes_written.value > 0:
win32gui.SendMessage(hwnd, win32con.WM_DROPFILES, mem_addr)
else:
print('Drop failed with error code', ctypes.windll.kernel32.GetLastError())
VirtualFreeEx(hProcHnd, mem_addr, 0, win32con.MEM_RELEASE)
# Test it while an empty notepad is open
filepath = f"{os.environ['userprofile']}\Desktop\log.txt"
hwnd = win32gui.FindWindow("Notepad", "Untitled - Notepad")
dragFileToWnd(filepath, hwnd)
VirtualProtectEx Failed with error code 87
Related
I am trying to get the 64-bit memory address of a variable, using a pointer created with CheatEngine.
Here are the pointers:
from ReadWriteMemory import ReadWriteMemory
from numpy import int64
import win32api
import win32process
import win32con
import ctypes as c
rwm = ReadWriteMemory()
process = rwm.get_process_by_name("NAME.exe")
process.open()
def get_process_by_namee(process_name):
"""Finds the process id of the given
process name and returns the process id and its base address."""
process_name = process_name.lower()
# Enumerate all processes
processes = win32process.EnumProcesses()
for process_id in processes:
# If process_id is the same as this program, skip it
if process_id == -1:
continue
# Try to read the process memory
try:
h_process = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, True, process_id)
# Try to read the modules of the process
try:
# modules is an array of base addresses of each module
modules = win32process.EnumProcessModules(h_process)
for base_address in modules:
# Get the name of the module
name = str(win32process.GetModuleFileNameEx(h_process, base_address))
# Compare it to the name of your program
if name.lower().find(process_name) != -1:
return process_id, base_address
finally:
win32api.CloseHandle(h_process)
except:
pass
# this is the prefered imagebase + specific offset for the variable
baseaddress = c.c_uint64(0x00007FF7BBE80000 + 0x0218F138)
boostPointer1 = process.get_pointer(c.c_uint64(baseaddress), offsets=[0x3F0, 0x0, 0x60, 0x178, 0x0, 0xC0, 0xA8C])
boostPointer2 = process.get_pointer(c.c_uint64(0x1B8CC0FFC24))
boost_count = process.read(boostPointer1)
print (boost_count)
When i create a pointer in CheatEngine with the offsets and stuff of boostPointer1, i get the address, which i put into boostpointer2 (0x1B8CC0FFC24)
However, when i read boostPointer1 and boostPointer2, i get different results, leading to a different memory spot being read. Why does boostPointer1 return a different Pointer, than boostPointer2, and how can this be fixed?
I'm trying to read another process' memory in Python and I have the static address of the program and all the offsets. I'm using the win32api to do this. I can already read a process' memory with an address without offsets but I don't know how to use offsets.
I've already tried the script in this answer but it returns -1. I've changed the PROCESS_ALL_ACCESS to win32con.PROCESS_VM_READ and even then it returns -1.
How do I use the offsets with ReadProcessMemory?
Here is the code I'm using:
import win32api
import win32process
import win32con
import ctypes
import ctypes.wintypes as wintypes
def get_process_by_name(process_name):
"""Find the process id of the given
process name and returns the process id."""
process_name = process_name.lower()
# Enumerate all processes
processes = win32process.EnumProcesses()
for process_id in processes:
# If process_id is the same as this program, skip it
if process_id == -1:
continue
# Try to read the process memory
try:
p_handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, True, process_id)
# Try to read the modules of the process
try:
modules = win32process.EnumProcessModules(p_handle)
for module_id in modules:
name = str(win32process.GetModuleFileNameEx(p_handle, module_id))
if name.lower().find(process_name) != -1:
return process_id
finally:
win32api.CloseHandle(p_handle)
except:
pass
def read_process_memory(process_id, address, offsets, size_of_data=4):
p_handle = ctypes.windll.kernel32.OpenProcess(win32con.PROCESS_VM_READ, False, p_id)
data = ctypes.c_uint(size_of_data)
bytesRead = ctypes.c_uint(size_of_data)
current_address = address
if offsets:
# Do something to the offsets
ctypes.windll.kernel32.ReadProcessMemory(p_handle, current_address, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(bytesRead))
else:
ctypes.windll.kernel32.ReadProcessMemory(p_handle, current_address, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(bytesRead))
# Close the handle to the process
ctypes.windll.kernel32.CloseHandle(p_handle)
return data.value
p_id = get_process_by_name("program.exe")
# Without offsets it works fine
address = 0x2ADB1818
val = read_process_memory(p_id, address, None)
print(val)
# Does not point to the correct address
address = 0x00571160
offsets = [0xD84, 0x1B8, 0x38, 0x5C, 0x24, 0xF4, 0x1D08]
for offset in offsets:
address += offset
val = read_process_memory(p_id, address, offsets)
print(val)
I've figured out what I was missing. I've been interpreting the addresses wrong. They are pointers to the addresses and so, when using offsets I need to read them, and add them to one another to get access to the value I want to read. Also, I needed to use the base address of the program, for which I just needed to return the value of the module.
Here is the above script with the necessary changes to read a process' memory with offsets:
import win32api
import win32process
import win32con
import ctypes
def get_process_by_name(process_name):
"""Finds the process id of the given
process name and returns the process id and its base address."""
process_name = process_name.lower()
# Enumerate all processes
processes = win32process.EnumProcesses()
for process_id in processes:
# If process_id is the same as this program, skip it
if process_id == -1:
continue
# Try to read the process memory
try:
h_process = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, True, process_id)
# Try to read the modules of the process
try:
# modules is an array of base addresses of each module
modules = win32process.EnumProcessModules(h_process)
for base_address in modules:
# Get the name of the module
name = str(win32process.GetModuleFileNameEx(h_process, base_address))
# Compare it to the name of your program
if name.lower().find(process_name) != -1:
return process_id, base_address
finally:
win32api.CloseHandle(h_process)
except:
pass
def read_process_memory(process_id, address, offsets=[]):
"""Read a process' memory based on its process id, address and offsets.
Returns the address without offsets and the value."""
# The handle to the program's process
# This will allow to use ReadProcessMemory
h_process = ctypes.windll.kernel32.OpenProcess(win32con.PROCESS_VM_READ, False, p_id)
# This is a pointer to the data you want to read
# Use `data.value` to get the value at this pointer
# In this case, this value is an Integer with 4 bytes
data = ctypes.c_uint(0)
# Size of the variable, it usually is 4 bytes
bytesRead = ctypes.c_uint(0)
# Starting address
current_address = address
if offsets:
# Append a new element to the offsets array
# This will allow you to get the value at the last offset
offsets.append(None)
for offset in offsets:
# Read the memory of current address using ReadProcessMemory
ctypes.windll.kernel32.ReadProcessMemory(h_process, current_address, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(bytesRead))
# If current offset is `None`, return the value of the last offset
if not offset:
return current_address, data.value
else:
# Replace the address with the new data address
current_address = data.value + offset
else:
# Just read the single memory address
ctypes.windll.kernel32.ReadProcessMemory(h_process, current_address, ctypes.byref(data), ctypes.sizeof(data), ctypes.byref(bytesRead))
# Close the handle to the process
ctypes.windll.kernel32.CloseHandle(h_process)
# Return a pointer to the value and the value
# The pointer will be used to write to the memory
return current_address, data.value
# Open the process
p_id, base_address = get_process_by_name("program.exe")
# The static address needs the program base_address
address = base_address + 0x00571160
offsets = [0xD84, 0x1B8, 0x38, 0x5C, 0x24, 0xF4, 0x1D08]
pointer_value, value = read_process_memory(p_id, address, offsets)
print(f"(Static Address) Value: {value}")
# Re-reading the memory with the last pointer
pointer_value, value = read_process_memory(p_id, pointer_value, None)
print(f"(Dynamic Address) Value: {value}")
I am trying to create a memory scanner. similar to Cheat Engine. but only for extract information.
I know how to get the pid (in this case is "notepad.exe"). But I don't have any Idea about how to know wicht especific adress belong to the program that I am scanning.
Trying to looking for examples. I could see someone it was trying to scan every adress since one point to other. But it's to slow. Then I try to create a batch size (scan a part of memory and not one by one each adress). The problem is if the size is to short. still will take a long time. and if it is to long, is possible to lose many adress who are belong to the program. Because result from ReadMemoryScan is False in the first Adress, but It can be the next one is true. Here is my example.
import ctypes as c
from ctypes import wintypes as w
import psutil
from sys import stdout
write = stdout.write
import numpy as np
def get_client_pid(process_name):
pid = None
for proc in psutil.process_iter():
if proc.name() == process_name:
pid = int(proc.pid)
print(f"Found '{process_name}' PID = ", pid,f" hex_value = {hex(pid)}")
break
if pid == None:
print('Program Not found')
return pid
pid = get_client_pid("notepad.exe")
if pid == None:
sys.exit()
k32 = c.WinDLL('kernel32', use_last_error=True)
OpenProcess = k32.OpenProcess
OpenProcess.argtypes = [w.DWORD,w.BOOL,w.DWORD]
OpenProcess.restype = w.HANDLE
ReadProcessMemory = k32.ReadProcessMemory
ReadProcessMemory.argtypes = [w.HANDLE,w.LPCVOID,w.LPVOID,c.c_size_t,c.POINTER(c.c_size_t)]
ReadProcessMemory.restype = w.BOOL
GetLastError = k32.GetLastError
GetLastError.argtypes = None
GetLastError.restype = w.DWORD
CloseHandle = k32.CloseHandle
CloseHandle.argtypes = [w.HANDLE]
CloseHandle.restype = w.BOOL
processHandle = OpenProcess(0x10, False, int(pid))
# addr = 0x0FFFFFFFFFFF
data = c.c_ulonglong()
bytesRead = c.c_ulonglong()
start = 0x000000000000
end = 0x7fffffffffff
batch_size = 2**13
MemoryData = np.zeros(batch_size, 'l')
Size = MemoryData.itemsize*MemoryData.size
index = 0
Data_address = []
for c_adress in range(start,end,batch_size):
result = ReadProcessMemory(processHandle,c.c_void_p(c_adress), MemoryData.ctypes.data,
Size, c.byref(bytesRead))
if result: # Save adress
Data_address.extend(list(range(c_adress,c_adress+batch_size)))
e = GetLastError()
CloseHandle(processHandle)
I decided from 0x000000000000 to 0x7fffffffffff Because cheat engine scan this size. I am still a begginer with this kind of this about memory scan. maybe there are things that I can do to improve the efficiency.
I suggest you take advantage of existing python libraries that can analyse Windows 10 memory.
I'm no specialist but I've found Volatility. Seems to be pretty useful for your problem.
For running that tool you need Python 2 (Python 3 won't work).
For running python 2 and 3 in the same Windows 10 machine, follow this tutorial (The screenshots are in Spanish but it can easily be followed).
Then see this cheat sheet with main commands. You can dump the memory and then operate on the file.
Perhaps this leads you to the solution :) At least the most basic command pslist dumps all the running processes addresses.
psutil has proc.memory_maps()
pass the result as map to this function
TargetProcess eaxample 'Calculator.exe'
def get_memSize(self,TargetProcess,map):
for m in map:
if TargetProcess in m.path:
memSize= m.rss
break
return memSize
if you use this function, it returns the memory size of your Target Process
my_pid is the pid for 'Calculator.exe'
def getBaseAddressWmi(self,my_pid):
PROCESS_ALL_ACCESS = 0x1F0FFF
processHandle = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, my_pid)
modules = win32process.EnumProcessModules(processHandle)
processHandle.close()
base_addr = modules[0] # for me it worked to select the first item in list...
return base_addr
to get the base address of your prog
so you search range is from base_addr to base_addr + memSize
Using the python watchdog file system events watching library I noticed that when being used under Windows Server 2003 it entered into "Polling Mode" thus stoping using asynchronous OS notification and, therefore, heavily reducing system performance under big amount of file changes.
I traced the problem to watchdog/observers/winapi.py file where CancelIoEx system call is used in order to stop ReadDirectoryChangesW call lock when the user wants to stop monitoring the watched directory or file:
(winapi.py)
CancelIoEx = ctypes.windll.kernel32.CancelIoEx
CancelIoEx.restype = ctypes.wintypes.BOOL
CancelIoEx.errcheck = _errcheck_bool
CancelIoEx.argtypes = (
ctypes.wintypes.HANDLE, # hObject
ctypes.POINTER(OVERLAPPED) # lpOverlapped
)
...
...
...
def close_directory_handle(handle):
try:
CancelIoEx(handle, None) # force ReadDirectoryChangesW to return
except WindowsError:
return
The problem with CancelIoEx call is that it is not available until Windows Server 2008:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363792(v=vs.85).aspx
One possible alternative is to change close_directory_handle in order to make it create a mock file within the monitored directory, thus unlocking the thread waiting for ReadDirectoryChangesW to return.
However, I noticed that CancelIo system call is in fact available in Windows Server 2003:
Cancels all pending input and output (I/O) operations that are issued
by the calling thread for the specified file. The function does not
cancel I/O operations that other threads issue for a file handle. To
cancel I/O operations from another thread, use the CancelIoEx
function.
But calling CancelIo won't affect the waiting thread.
Do you have any idea on how to solve this problem?
May be threading.enumerate() could be used issue a signal to be handled by each thread being CancelIo called from these handlers?
The natural approach is to implement a completion routine and call to ReadDirectoryChangesW using its overlapped mode. The following example shows the way to do that:
RDCW_CALLBACK_F = ctypes.WINFUNCTYPE(None, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.POINTER(OVERLAPPED))
First, create a WINFUNCTYPE factory which will be used to generate (callable from Windows API) C like functions from python methods. In this case, no return value and 3 parameters corresponding to
VOID CALLBACK FileIOCompletionRoutine(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
);
FileIOCompletionRoutine header.
The callback reference as well as the overlapped structure need to be added to ReadDirectoryChangesW arguments list:
ReadDirectoryChangesW = ctypes.windll.kernel32.ReadDirectoryChangesW
ReadDirectoryChangesW.restype = ctypes.wintypes.BOOL
ReadDirectoryChangesW.errcheck = _errcheck_bool
ReadDirectoryChangesW.argtypes = (
ctypes.wintypes.HANDLE, # hDirectory
LPVOID, # lpBuffer
ctypes.wintypes.DWORD, # nBufferLength
ctypes.wintypes.BOOL, # bWatchSubtree
ctypes.wintypes.DWORD, # dwNotifyFilter
ctypes.POINTER(ctypes.wintypes.DWORD), # lpBytesReturned
ctypes.POINTER(OVERLAPPED), # lpOverlapped
RDCW_CALLBACK_F # FileIOCompletionRoutine # lpCompletionRoutine
)
From here, we are ready to perform the overlapped system call.
This is a simple call bacl just usefult to test that everything works fine:
def dir_change_callback(dwErrorCode,dwNumberOfBytesTransfered,p):
print("dir_change_callback! PID:" + str(os.getpid()))
print("CALLBACK THREAD: " + str(threading.currentThread()))
Prepare and perform the call:
event_buffer = ctypes.create_string_buffer(BUFFER_SIZE)
nbytes = ctypes.wintypes.DWORD()
overlapped_read_dir = OVERLAPPED()
call2pass = RDCW_CALLBACK_F(dir_change_callback)
hand = get_directory_handle(os.path.abspath("/test/"))
def docall():
ReadDirectoryChangesW(hand, ctypes.byref(event_buffer),
len(event_buffer), False,
WATCHDOG_FILE_NOTIFY_FLAGS,
ctypes.byref(nbytes),
ctypes.byref(overlapped_read_dir), call2pass)
print("Waiting!")
docall()
If you load and execute all this code into a DreamPie interactive shell you can check the system call is done and that the callback executes thus printing the thread and pid numbers after the first change done under c:\test directory. Besides, you will notice those are the same than the main thread and process: Despite the event is raised by a separated thread, the callback runs in the same process and thread as our main program thus providing an undesired behaviour:
lck = threading.Lock()
def dir_change_callback(dwErrorCode,dwNumberOfBytesTransfered,p):
print("dir_change_callback! PID:" + str(os.getpid()))
print("CALLBACK THREAD: " + str(threading.currentThread()))
...
...
...
lck.acquire()
print("Waiting!")
docall()
lck.acquire()
This program will lock the main thread and the callback will never execute.
I tried many synchronization tools, even Windows API semaphores always getting the same behaviour so, finally, I decided to implement the ansynchronous call using the synchronous configuration for ReadDirectoryChangesW within a separate process managed and synchronized using multiprocessing python library:
Calls to get_directory_handle won't return the handle number given by windows API but one managed by winapi library, for that I implemented a handle generator:
class FakeHandleFactory():
_hl = threading.Lock()
_next = 0
#staticmethod
def next():
FakeHandleFactory._hl.acquire()
ret = FakeHandleFactory._next
FakeHandleFactory._next += 1
FakeHandleFactory._hl.release()
return ret
Each generated handle has to be globally associated with a file system path:
handle2file = {}
Each call to read_directory_changes will now generate ReadDirectoryRequest (derived from multiprocessing.Process) object:
class ReadDirectoryRequest(multiprocessing.Process):
def _perform_and_wait4request(self, path, recursive, event_buffer, nbytes):
hdl = CreateFileW(path, FILE_LIST_DIRECTORY, WATCHDOG_FILE_SHARE_FLAGS,
None, OPEN_EXISTING, WATCHDOG_FILE_FLAGS, None)
#print("path: " + path)
aux_buffer = ctypes.create_string_buffer(BUFFER_SIZE)
aux_n = ctypes.wintypes.DWORD()
#print("_perform_and_wait4request! PID:" + str(os.getpid()))
#print("CALLBACK THREAD: " + str(threading.currentThread()) + "\n----------")
try:
ReadDirectoryChangesW(hdl, ctypes.byref(aux_buffer),
len(event_buffer), recursive,
WATCHDOG_FILE_NOTIFY_FLAGS,
ctypes.byref(aux_n), None, None)
except WindowsError as e:
print("!" + str(e))
if e.winerror == ERROR_OPERATION_ABORTED:
nbytes = 0
event_buffer = []
else:
nbytes = 0
event_buffer = []
# Python 2/3 compat
nbytes.value = aux_n.value
for i in xrange(self.int_class(aux_n.value)):
event_buffer[i] = aux_buffer[i]
CloseHandle(hdl)
try:
self.lck.release()
except:
pass
def __init__(self, handle, recursive):
buffer = ctypes.create_string_buffer(BUFFER_SIZE)
self.event_buffer = multiprocessing.Array(ctypes.c_char, buffer)
self.nbytes = multiprocessing.Value(ctypes.wintypes.DWORD, 0)
targetPath = handle2file.get(handle, None)
super(ReadDirectoryRequest, self).__init__(target=self._perform_and_wait4request, args=(targetPath, recursive, self.event_buffer, self.nbytes))
self.daemon = True
self.lck = multiprocessing.Lock()
self.result = None
try:
self.int_class = long
except NameError:
self.int_class = int
if targetPath is None:
self.result = ([], -1)
def CancelIo(self):
try:
self.result = ([], 0)
self.lck.release()
except:
pass
def read_changes(self):
#print("read_changes! PID:" + str(os.getpid()))
#print("CALLBACK THREAD: " + str(threading.currentThread()) + "\n----------")
if self.result is not None:
raise Exception("ReadDirectoryRequest object can be used only once!")
self.lck.acquire()
self.start()
self.lck.acquire()
self.result = (self.event_buffer, self.int_class(self.nbytes.value))
return self.result
This class specifies Process providing a process which perform the system call and waits until (or):
A change event has been raised.
The main thread cancels the request by calling to the ReadDirectoryRequest object CancelIo method.
Note that:
get_directory_handle
close_directory_handle
read_directory_changes
Roles are now to manage requests. For that, thread locks and auxiliary data structures are needed:
rqIndexLck = threading.Lock() # Protects the access to `rqIndex`
rqIndex = {} # Maps handles to request objects sets.
get_directory_handle
def get_directory_handle(path):
rqIndexLck.acquire()
ret = FakeHandleFactory.next()
handle2file[ret] = path
rqIndexLck.release()
return ret
close_directory_handle
def close_directory_handle(handle):
rqIndexLck.acquire()
rqset4handle = rqIndex.get(handle, None)
if rqset4handle is not None:
for rq in rqset4handle:
rq.CancelIo()
del rqIndex[handle]
if handle in handle2file:
del handle2file[handle]
rqIndexLck.release()
And last but not least: read_directory_changes
def read_directory_changes(handle, recursive):
rqIndexLck.acquire()
rq = ReadDirectoryRequest(handle, recursive)
set4handle = None
if handle in rqIndex:
set4handle = rqIndex[handle]
else:
set4handle = set()
rqIndex[handle] = set4handle
set4handle.add(rq)
rqIndexLck.release()
ret = rq.read_changes()
rqIndexLck.acquire()
if rq in set4handle:
set4handle.remove(rq)
rqIndexLck.release()
return ret
I have an application with a ListView ('SysListView32') control, from which I would like to extract data. The control has 4 columns, only textual data.
I have been playing around the following lines (found online somewhere):
VALUE_LENGTH = 256
bufferlength_int=struct.pack('i', VALUE_LENGTH)
count = win32gui.SendMessage(TargetHwnd, commctrl.LVM_GETITEMCOUNT, 0, 0)
for ItemIndex in range(count):
valuebuffer = array.array('c',bufferlength_int + " " * (VALUE_LENGTH - len(bufferlength_int)))
ListItems = win32gui.SendMessage(TargetHwnd, commctrl.LVM_GETITEMTEXT, ItemIndex, valuebuffer)
[The above code may not be entirely executable, as I stripped it from irrelevant stuff. but the gist is certainly here.]
This seems to run ok but I must be doing something wrong - I get all sorts of mostly-zeroed data buffers in return, and none of the actual text contents I was looking for.
Any suggestions?
Thanks,
Yonatan
Well, it turns out I was wrong on several points there. However it is possible to do by allocating memory inside the target process, constructing the required struct (LVITEM) there, sending the message and reading back the result from the buffer allocated in said process.
For the sake of completeness, I attach a code example for reading SysListView32 items from a foreign process, given a window handle of the control.
from win32con import PAGE_READWRITE, MEM_COMMIT, MEM_RESERVE, MEM_RELEASE,\
PROCESS_ALL_ACCESS
from commctrl import LVM_GETITEMTEXT, LVM_GETITEMCOUNT
import struct
import ctypes
import win32api
import win32gui
GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId
VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx
VirtualFreeEx = ctypes.windll.kernel32.VirtualFreeEx
OpenProcess = ctypes.windll.kernel32.OpenProcess
WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory
ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory
memcpy = ctypes.cdll.msvcrt.memcpy
def readListViewItems(hwnd, column_index=0):
# Allocate virtual memory inside target process
pid = ctypes.create_string_buffer(4)
p_pid = ctypes.addressof(pid)
GetWindowThreadProcessId(hwnd, p_pid) # process owning the given hwnd
hProcHnd = OpenProcess(PROCESS_ALL_ACCESS, False, struct.unpack("i",pid)[0])
pLVI = VirtualAllocEx(hProcHnd, 0, 4096, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)
pBuffer = VirtualAllocEx(hProcHnd, 0, 4096, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)
# Prepare an LVITEM record and write it to target process memory
lvitem_str = struct.pack('iiiiiiiii', *[0,0,column_index,0,0,pBuffer,4096,0,0])
lvitem_buffer = ctypes.create_string_buffer(lvitem_str)
copied = ctypes.create_string_buffer(4)
p_copied = ctypes.addressof(copied)
WriteProcessMemory(hProcHnd, pLVI, ctypes.addressof(lvitem_buffer), ctypes.sizeof(lvitem_buffer), p_copied)
# iterate items in the SysListView32 control
num_items = win32gui.SendMessage(hwnd, LVM_GETITEMCOUNT)
item_texts = []
for item_index in range(num_items):
win32gui.SendMessage(hwnd, LVM_GETITEMTEXT, item_index, pLVI)
target_buff = ctypes.create_string_buffer(4096)
ReadProcessMemory(hProcHnd, pBuffer, ctypes.addressof(target_buff), 4096, p_copied)
item_texts.append(target_buff.value)
VirtualFreeEx(hProcHnd, pBuffer, 0, MEM_RELEASE)
VirtualFreeEx(hProcHnd, pLVI, 0, MEM_RELEASE)
win32api.CloseHandle(hProcHnd)
return item_texts
If the control is in the same process as your code, it should work. If it's in a different process (as "another application" suggests), then this doesn't work (or at least it shouldn't). Check the error codes, you should get something along the lines of "permission denied": Applications can't see into each others memory.