#singleinstance force
; Run a 32 bit version of notepade and then start this script
; On 64 bit systems it can be found here C:\Windows\SysWOW64\notepad.exe
WinGet, targetPID, PID, ahk_exe notepad.exe
PROCESS_ALL_ACCESS := 0x1F0FFF
MEM_COMMIT := 0x1000
MEM_RELEASE := 0x8000
PAGE_EXECUTE_READWRITE := 64
; This function will be written to the remote process
; It takes a single argument, the address of a data structure.
; This data structure contains all the information on the function to call and the parameters.
; CreateRemoteThread will invoke this proxyCaller function which will then call the target function.
proxyCallerString := "558BEC83EC1C53568B75088B068B4E0C83650800578B7E088945EC8B46046A048945F88D46105B85C97E6C8B1083FA01750C03C38B108955FCFF75FCEB3483FA02751A03C38B1003C38955FC8B108955F4FF75F4FF75FC83450808EB1883FA03751703C3D900D95DF483EC04D945F4D91C24015D0803C3EB1B3BD3751703C3DD00DD5DF083EC08DD45F0DD1C248345080883C0084975948365E4008365E8008D45E48945FC83FF01750AFF55F88B5DFC8903EB2F83FF02750FFF55F88B5DFC890383C3048913EB1B83FF03750AFF55F88B5DFCD91BEB0C3BFB7508FF55F88B5DFCDD1B837DEC0175030365088B45E489068B45E86A0C894604585F5E5BC9C3"
; Convert the proxyCaller string to a machine code function stored at &proxyCaller
sizeProxyCaller := hexToCharArray(proxyCallerString, proxyCaller)
if !hProc := DllCall("OpenProcess", "UInt", PROCESS_ALL_ACCESS, "Int",0, "UInt", targetPID)
exit("failed to open process")
; Allocate room in remote process for our proxy caller function
If !pProxyFunction := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "Ptr", sizeProxyCaller, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
exit("Couldn't allocate memory for proxy function")
; Write the proxy caller function
if !DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pProxyFunction, "Ptr", &proxyCaller, "Ptr", sizeProxyCaller, "Ptr", 0)
exit("Couldn't write the function")
; Create some unicode strings and write them to notepad
titleSize := StrPutVar("This is a title", title, "UTF-16")
messageSize := StrPutVar("Hello from notepad!", message, "UTF-16")
If !pTitle := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "Ptr", titleSize, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
exit("Couldn't allocate memory")
if !DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pTitle, "Ptr", &title, "Ptr", titleSize, "Ptr", 0)
exit("Couldn't write")
If !pMessage := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "Ptr", messageSize, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
exit("Couldn't allocate memory")
if !DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pMessage, "Ptr", &message, "Ptr", messageSize, "Ptr", 0)
exit("Couldn't write")
; System functions are stored at the same addresses for each process (ignoring 32 vs 64 bit applications)
; So just find the address of the msgbox function in this script, as it will be the same in notepad
msgboxAddress := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "User32"), "AStr", "MessageBoxW")
; create a data structure which contains the function call type, function address, function return type, and each parameter/argument
; windows API use stdcall so function type is "". Pass pointers to the message and title strings i.e. pMessage and pTitle.
paramSize := createParams(parameters, "", msgboxAddress, returnType := "int", ["UInt", 0], ["UInt", pMessage], ["UInt", pTitle], ["UInt", 0x00001000 | 0x00000002])
; Allocate some room for this structure and write it to the remote process
If !pBufferParameters := DllCall("VirtualAllocEx", "Ptr", hProc, "Ptr", 0, "Ptr", paramSize, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
exit("Couldn't allocate memory for parameters")
if !DllCall("WriteProcessMemory", "Ptr", hProc, "Ptr", pBufferParameters, "Ptr", ¶meters, "Ptr", paramSize, "Ptr", 0)
exit("Couldn't write the parameters")
; create a remote thread and have it run our proxy/caller function and pass it the address of the parameter structure
if !hThread := DllCall("CreateRemoteThread", "Ptr", hProc, "UInt", 0, "UInt", 0, "Ptr", pProxyFunction, "Ptr", pBufferParameters, "UInt", 0, "UInt", 0)
exit("Couldn't start thread")
DllCall("WaitForSingleObject", "Ptr", hThread, "UInt", 0xFFFFFFFF)
if !DllCall("ReadProcessMemory", "Ptr", hProc, "Ptr", pBufferParameters, "int*", result, "UInt", 4, "Ptr",0)
exit("RPM failed")
msgbox % "Result: " result
exit()
hexToCharArray(hexString, byref var)
{
sizeBytes := strlen(hexString)//2
VarSetCapacity(var, sizeBytes)
loop, % sizeBytes
numput("0x" substr(hexString, A_Index * 2 - 1, 2), var, A_Index - 1, "UChar")
return sizeBytes
}
exit(msg := "")
{
global
if (msg != "")
msgbox % msg
if pProxyFunction
DllCall("VirtualFreeEx","Ptr",hProc,"Ptr", pProxyFunction,"Ptr", sizeProxyCaller, MEM_RELEASE)
if pBufferParameters
DllCall("VirtualFreeEx","Ptr",hProc,"Ptr", pBufferParameters,"Ptr", paramSize, MEM_RELEASE)
if pTitle
DllCall("VirtualFreeEx","Ptr",hProc,"Ptr", pTitle,"Ptr", paramSize, MEM_RELEASE)
if pMessage
DllCall("VirtualFreeEx","Ptr",hProc,"Ptr", pMessage,"Ptr", paramSize, MEM_RELEASE)
if hProc
DllCall("CloseHandle", "Ptr", hProc)
exitapp
}
/*
Param structure
// This structure is written to remote memory and holds the function address, type and parameters
// with which the function should be called
// offset value
// 0 functionType / calling convention
// 4 targetFunctionAddress
// 8 (Function) returnType
// 12 param count
// Params are listed in reverse order (right to left i.e. last to first)
// 16 lastParamType
// 20 lastParamValue (either 4 bytes or 8 bytes)
// 24 or 28 secondToLastParamType
// ......
*/
/*
// param/return types (the values the injected function expects)
int int32Type = 1;
int int64Type = 2;
int floatType = 3;
int doubleType = 4;
// functionType
int cdeclType = 1;
*/
; variable: The created structure is stored inside this variable
; functionType: Pass the string "cdecl" to call cdecl functions or leave it blank to call stdcall functions
; targetFunctionAddress: The address of the target function....
; returnType: can be any of the listed param types, as wells as void or null ("")
; params: Is variadic and accepts an object i.e. ["Int64", 5] means param type is Int64 with a value of 5
;
; returns the size of the structure
createParams(byRef variable, functionType, targetFunctionAddress, returnType, params*)
{
; These are used to lookup the IDs for the both the return types and the param types
static aTypeSize := { "": {ID: 0} ; void return type
, Void: {ID: 0} ; void return type
, Char: {size: 4, ID: 1}
, UChar: {size: 4, ID: 1}
, Short: {size: 4, ID: 1}
, UShort: {size: 4, ID: 1}
, Int: {size: 4, ID: 1}
, UInt: {size: 4, ID: 1}
, Int64: {size: 8, ID: 2}
, Float: {size: 4, ID: 3}
, Double: {size: 8, ID: 4}}
VarSetCapacity(variable, 16 + round(params.MaxIndex()) * 12, 0)
if instr(functionType, "cdecl")
NumPut(1, variable, 0, "Int") ; 1 = cdecl
NumPut(targetFunctionAddress, variable, 4, "UInt")
if !aTypeSize.hasKey(returnType)
return -1 ; invalid return type
NumPut(aTypeSize[returnType].ID, variable, 8, "Int") ; return type 0 = none, 1 32bit, 2 64bit
NumPut(round(params.MaxIndex()), variable, 12, "Int")
offset := 16
; params need to be written in reverse order (right to left)
while paramObj := params.Remove()
{
type := paramObj.1, value := paramObj.2
if !aTypeSize.hasKey(type)
return -2 ; invalid param type
NumPut(aTypeSize[type].ID, variable, offset, "Int"), offset += 4
NumPut(value, variable, offset, type), offset += aTypeSize[type].size
}
return offset ; size bytes
}
StrPutVar(string, ByRef var, encoding)
{
; Ensure capacity.
VarSetCapacity( var, StrPut(string, encoding)
; StrPut returns char count, but VarSetCapacity needs bytes.
* ((encoding="utf-16"||encoding="cp1200") ? 2 : 1) )
; Copy or convert the string.
return StrPut(string, &var, encoding) * ((encoding="utf-16"||encoding="cp1200") ? 2 : 1)
}