i am currently learning more about ctypes
and its functions and what I'm trying to do is to create a script to WriteProcessMemory
into notepad via its PID(process ID)
. However, when i tried to execute my script, notepad just instantly crashes. I am following the tutorial from this, which i think is the same as the "Gray Hat for Hacking Python" book. By right, the shell code executed is supposed to create a message box.
Here is my code.
import os
import colorama
from colorama import Fore, Back, Style
import win32com.client
from ctypes import *
from ctypes import wintypes
import ctypes
from ctypes.wintypes import BOOL
from ctypes.wintypes import DWORD
from ctypes.wintypes import HANDLE
from ctypes.wintypes import LPVOID
from ctypes.wintypes import LPCVOID
from ctypes.wintypes import LPCWSTR
colorama.init()
kernel32 = ctypes.WinDLL('Kernel32', use_last_error=True)
LPCSTR = LPCTSTR = ctypes.c_char_p
LPDWORD = PDWORD = ctypes.POINTER(DWORD)
class _SECURITY_ATTRIBUTES(ctypes.Structure):
_fields_ = [('nLength', DWORD),
('lpSecurityDescriptor', LPVOID),
('bInheritHandle', BOOL),]
SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES
LPSECURITY_ATTRIBUTES = ctypes.POINTER(_SECURITY_ATTRIBUTES)
LPTHREAD_START_ROUTINE = LPVOID
OpenProcess = kernel32.OpenProcess
OpenProcess.restype = HANDLE
OpenProcess.argtypes = (DWORD, BOOL, DWORD)
VirtualAllocEx = kernel32.VirtualAllocEx
VirtualAllocEx.restype = LPVOID
VirtualAllocEx.argtypes = (HANDLE, LPVOID, ctypes.c_size_t, DWORD, DWORD)
ReadProcessMemory = kernel32.ReadProcessMemory
ReadProcessMemory.restype = BOOL
ReadProcessMemory.argtypes = (HANDLE, LPCVOID, LPVOID, DWORD, DWORD)
WriteProcessMemory = kernel32.WriteProcessMemory
WriteProcessMemory.restype = BOOL
WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, ctypes.c_int)
CreateRemoteThread = kernel32.CreateRemoteThread
CreateRemoteThread.restype = HANDLE
CreateRemoteThread.argtypes = (HANDLE, LPSECURITY_ATTRIBUTES, ctypes.c_size_t , LPTHREAD_START_ROUTINE, LPVOID, DWORD, ctypes.c_ulong)
GetLastError = kernel32.GetLastError
GetLastError.restype = DWORD
GetLastError.argtypes = ()
GetModuleHandle = kernel32.GetModuleHandleA
GetModuleHandle.restype = HANDLE
GetModuleHandle.argtypes = (LPCWSTR,)
GetProcAddress = kernel32.GetProcAddress
GetProcAddress.restype = LPVOID
GetProcAddress.argtypes = (HANDLE, LPCWSTR)
# https://www.aldeid.com/wiki/Process-Security-and-Access-Rights
PROCESS_VM_READ = 0x0010 # Required to read memory in a process using ReadProcessMemory.
PROCESS_VM_WRITE = 0x0020
PROCESS_VM_OPERATION = 0x0008 # Required to write to memory in a process using WriteProcessMemory.
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_CREATE_THREAD = 0x0002
PROCESS_ALL_ACCESS = (PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD) #0x1F0FFF
print(Fore.RED + 'Retrieving PIDs...')
WMI= win32com.client.GetObject('winmgmts:')
processes = WMI.ExecQuery('SELECT * from win32_process')
print(Fore.GREEN)
process_list = [i.Properties_('ProcessId').Value for i in processes] # list of available processes
for process in processes:
print(process.Properties_('ProcessId').Value , " - " , process.Properties_('Name').Value)
PID = int(input('Enter the PID of the process '))
# https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
process_handle = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, PID) # creating the handle
if not process_handle:
print ("Couldn't acquire a handle to PID: %s" % PID)
shellcode = "C:\\Users\\User\\Desktop\\py\\injector\\hello-world-x64.dll"
# https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
memory_alloc = kernel32.VirtualAllocEx(process_handle,0, len(shellcode), (0x1000 | 0x2000), 0x40) # allocating memory to the process
write = kernel32.WriteProcessMemory(process_handle, memory_alloc, shellcode, len(shellcode), 0)
ModuleHandle = kernel32.GetModuleHandleA('kernel32.dll')
LoadLibraryA = kernel32.GetProcAddress(ModuleHandle,"LoadLibraryA")
if not kernel32.CreateRemoteThread(process_handle, None, 0, LoadLibraryA, memory_alloc, 0, 0):
print("Failed injection..")
print("ModuleHandle : ", ModuleHandle)
print("LoadLibrary : ", LoadLibraryA)
print("process handle : ", process_handle)
print("VirtualAllocEx : ",memory_alloc)
print("WriteProcessMemory : ",write)
print(ctypes.GetLastError())
I've tried printing the return values and apparently the one that is giving me error is ModuleHandle
and LoadLibrary
which is returning a None value. But according to microsoft:
If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get extended error information, call GetLastError. The function fails if the requested write operation crosses into an area of the process that is inaccessible.
I've also tried the GetLastError()
method which returned 6, which upon googling, it refers to "invalid handler".
If it helps, my OS, notepad, VScode(my ide), python(3.6.8) are all 64bit. I apologise for the messy code and please feel free to correct me as i am a complete beginner in this area.
Edit
Here is the image of my printed outputs. I have also tried LoadLibraryW
and GetModuleHandleW
but it didnt work either, my notepad just crashes. The dll that im using is a generic DLL file that just spawns a messagebox saying "Hello World"
Okay, there are many mistakes I've made writing this script and @eryksun has given many brilliant answers in the comment section. And i am writing this answer to consolidate what I've learnt.
1) I should've used kernel32 = ctypes.WinDLL('Kernel32', use_last_error=True)
instead of kernel32 = ctypes.windll.kernel32
. This avoids conflicts with other modules that use windll
. It also enables protection for the thread's LastErrorValue
. In this case, use ctypes.get_last_error()
and ctypes.set_last_error(err)
instead of directly calling WinAPI GetLastError
and SetLastError
.
2) WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, ctypes.c_int)
was set wrongly. The argtypes according to the documentation, it should be:
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
where the last 2 arguments should be [HANDLE, LPVOID, LPCVOID, SIZE_T, PSIZE_T]
.
3) PROCESS_ALL_ACCESS
should only take in the necessary rights needed by CreateRemoteThread
and WriteProcessMemory
which are PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION
.
4) Instead of allocating the size of the bytes I am passing to be len(shellcode)
, it should be (len(shellcode) + 1) * WCHAR_SIZE
.
In general you should factor in the null terminator when copying a string, e.g. use len(dll_path) + 1. In this case you're committing a new page of memory (4 KiB on x86 and x64 systems), which is initially all zeros.
If you're using Python 3, then the DLL path is passed as a wide-character, null-terminated string, which explains the need for plus 1 (the trailing null character) and the need to multiply by WCHAR_SIZE (two bytes per character).
WriteProcessMemory is just writing the path as a string in the address space of the target process. This is passed as a parameter to LoadLibraryW (the native "W" version for a wide-character Unicode string), which is called in the target process on a new remote thread. Notice that LoadLibraryW is at the same address in the target process as our current process, because kernel32.dll is always mapped at its preferred base address, and it's always loaded in a Windows process. That's not necessarily the case for most other DLLs.
5) GetModuleHandleA
and LoadLibraryA
should be replaced with their respective W function. Moreover, The GetModuleHandleW
and GetProcAddress
steps are unnecessary. ctypes already does this for you. Just use kernel32.LoadLibraryW. This depends on kernel32.dll always being mapped to the same base address in each process, which I think is true for existing versions of Windows.
DOS and Windows 9x used codepages to encode strings (i.e. a mapping between ordinal values and characters) for a given locale, such as 437 for the U.S., 850 for Western Europe, and 1252 in Windows for both regions. OTOH, Windows NT (1993) was based on Unicode (i.e. one character set that supports all written languages), and it needed to provide compatibility with Windows 9x applications that didn't support Unicode.
NT defined two codepages per locale, an OEM (DOS) codepage and an ANSI (Windows) codepage. Functions had a [W]ide-character version (e.g. GetModuleHandleW) and a wrapped [A]NSI version (e.g. GetModuleHandleA). The wrapper translated between ANSI and Unicode and called the wide-character function. At the API level, the headers defined one or the other, depending on whether UNICODE was defined. Additionally this system defined TCHAR string types such as LPTSTR that are mapped to either the narrow type such as LPSTR or the wide-character type such as LPWSTR
Since Windows XP, all supported versions of Windows have been based on Windows NT, and many new functions added to the API are only available in wide-character versions (e.g. GetLocaleInfoEx; note the lack of a "W" suffix in this case because there's only one version). The old ANSI API and TCHAR types are a legacy now. I recommend only using the native wide-character API with Unicode strings.
Props to @eryksun for being so helpful!