; http://winprogger.com/getmodulefilenameex-enumprocessmodulesex-failures-in-wow64/
; http://stackoverflow.com/questions/3801517/how-to-enum-modules-in-a-64bit-process-from-a-32bit-wow-process
; Method: getModuleBaseAddress(module := "", byRef aModuleInfo := "")
; Parameters:
; moduleName - The file name of the module/dll to find e.g. "calc.exe", "GDI32.dll", "Bass.dll" etc
; If no module (null) is specified, the address of the base module - main()/process will be returned
; e.g. for calc.exe the following two method calls are equivalent getModuleBaseAddress() and getModuleBaseAddress("calc.exe")
; aModuleInfo - (Optional) A module Info object is returned in this variable. If method fails this variable is made blank.
; This object contains the keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint
; Return Values:
; Positive integer - The module's base/load address (success).
; -1 - Module not found
; -3 - EnumProcessModulesEx failed
; -4 - The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process. Or the target process has been closed.
; Notes: A 64 bit AHK can enumerate the modules of a target 64 or 32 bit process.
; A 32 bit AHK can only enumerate the modules of a 32 bit process
; This method requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ access rights. These are included by default with this class.
getModuleBaseAddress(moduleName := "", byRef aModuleInfo := "")
{
aModuleInfo := ""
if (moduleName = "")
moduleName := this.GetModuleFileNameEx(0, True) ; main executable module of the process - get just fileName no path
if r := this.getModules(aModules, True) < 0
return r ; -4, -3
return aModules.HasKey(moduleName) ? (aModules[moduleName].lpBaseOfDll, aModuleInfo := aModules[moduleName]) : -1
; no longer returns -5 for failed to get module info
}
; Method: getModuleFromAddress(address, byRef aModuleInfo)
; Finds the module in which the address resides.
; Parameters:
; address The address of interest.
;
; aModuleInfo (Optional) An unquoted variable name. If the module associated with the address is found,
; a moduleInfo object will be stored in this variable. This object has the
; following keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint.
; If the address is not found to reside inside a module, the passed variable is
; made blank/null.
; offsetFromModuleBase (Optional) Stores the relative offset from the module base address
; to the specified address. If the method fails then the passed variable is set to blank/empty.
; Return Values:
; 1 Success - The address is contained within a module.
; -1 The specified address does not reside within a loaded module.
; -3 EnumProcessModulesEx failed.
; -4 The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process.
getModuleFromAddress(address, byRef aModuleInfo, byRef offsetFromModuleBase := "")
{
aModuleInfo := offsetFromModule := ""
if result := this.getmodules(aModules) < 0
return result ; error -3, -4
for k, module in aModules
{
if (address >= module.lpBaseOfDll && address < module.lpBaseOfDll + module.SizeOfImage)
return 1, aModuleInfo := module, offsetFromModuleBase := address - module.lpBaseOfDll
}
return -1
}
; SeDebugPrivileges is required to read/write memory in some programs.
; This only needs to be called once when the script starts,
; regardless of the number of programs being read (or if the target programs restart)
; Call this before attempting to call any other methods in this class
; i.e. call _ClassMemory.setSeDebugPrivilege() at the very start of the script.
setSeDebugPrivilege(enable := True)
{
h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", DllCall("GetCurrentProcessId"), "Ptr")
; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
DllCall("Advapi32.dll\OpenProcessToken", "Ptr", h, "UInt", 32, "PtrP", t)
VarSetCapacity(ti, 16, 0) ; structure of privileges
NumPut(1, ti, 0, "UInt") ; one entry in the privileges array...
; Retrieves the locally unique identifier of the debug privilege:
DllCall("Advapi32.dll\LookupPrivilegeValue", "Ptr", 0, "Str", "SeDebugPrivilege", "Int64P", luid)
NumPut(luid, ti, 4, "Int64")
if enable
NumPut(2, ti, 12, "UInt") ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
; Update the privileges of this process with the new access token:
r := DllCall("Advapi32.dll\AdjustTokenPrivileges", "Ptr", t, "Int", false, "Ptr", &ti, "UInt", 0, "Ptr", 0, "Ptr", 0)
DllCall("CloseHandle", "Ptr", t) ; close this access token handle to save memory
DllCall("CloseHandle", "Ptr", h) ; close this process handle to save memory
return r
}
; Method: isTargetProcess64Bit(PID, hProcess := "", currentHandleAccess := "")
; Determines if a process is 64 bit.
; Parameters:
; PID The Process ID of the target process. If required this is used to open a temporary process handle.
; hProcess (Optional) A handle to the process, as returned by openProcess() i.e. [derivedObject].hProcess
; currentHandleAccess (Optional) The dwDesiredAccess value used when opening the process handle which has been
; passed as the hProcess parameter. If specifying hProcess, you should also specify this value.
; Return Values:
; True The target application is 64 bit.
; False The target application is 32 bit.
; Null The method failed.
; Notes:
; This is an internal method which is called when the new operator is used. It is used to set the pointer type for 32/64 bit applications so the pointer methods will work.
; This operation requires a handle with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
; If the currentHandleAccess parameter does not contain these rights (or not passed) or if the hProcess (process handle) is invalid (or not passed)
; a temporary handle is opened to perform this operation. Otherwise if hProcess and currentHandleAccess appear valid
; the passed hProcess is used to perform the operation.
isTargetProcess64Bit(PID, hProcess := "", currentHandleAccess := "")
{
if !A_Is64bitOS
return False
; If insufficient rights, open a temporary handle
else if !hProcess || !(currentHandleAccess & (this.aRights.PROCESS_QUERY_INFORMATION | this.aRights.PROCESS_QUERY_LIMITED_INFORMATION))
closeHandle := hProcess := this.openProcess(PID, this.aRights.PROCESS_QUERY_INFORMATION)
if (hProcess && DllCall("IsWow64Process", "Ptr", hProcess, "Int*", Wow64Process))
result := !Wow64Process
return result, closeHandle ? this.CloseHandle(hProcess) : ""
}
/*
_Out_ PBOOL Wow64Proces value set to:
True if the process is running under WOW64 - 32bit app on 64bit OS.
False if the process is running under 32-bit Windows!
False if the process is a 64-bit application running under 64-bit Windows.
*/
; Method: suspend() / resume()
; Notes:
; These are undocumented Windows functions which suspend and resume the process. Here be dragons.
; The process handle must have PROCESS_SUSPEND_RESUME access rights.
; That is, you must specify this when using the new operator, as it is not included.
; Some people say it requires more rights and just use PROCESS_ALL_ACCESS, however PROCESS_SUSPEND_RESUME has worked for me.
; Suspending a process manually can be quite helpful when reversing memory addresses and pointers, although it's not at all required.
; As an unorthodox example, memory addresses holding pointers are often stored in a slightly obfuscated manner i.e. they require bit operations to calculate their
; true stored value (address). This obfuscation can prevent Cheat Engine from finding the true origin of a pointer or links to other memory regions. If there
; are no static addresses between the obfuscated address and the final destination address then CE wont find anything (there are ways around this in CE). One way around this is to
; suspend the process, write the true/deobfuscated value to the address and then perform your scans. Afterwards write back the original values and resume the process.
suspend()
{
return DllCall("ntdll\NtSuspendProcess", "Ptr", this.hProcess)
}
resume()
{
return DllCall("ntdll\NtResumeProcess", "Ptr", this.hProcess)
}
; Method: getModules(byRef aModules, useFileNameAsKey := False)
; Stores the process's loaded modules as an array of (object) modules in the aModules parameter.
; Parameters:
; aModules An unquoted variable name. The loaded modules of the process are stored in this variable as an array of objects.
; Each object in this array has the following keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint.
; useFileNameAsKey When true, the file name e.g. GDI32.dll is used as the lookup key for each module object.
; Return Values:
; Positive integer The size of the aModules array. (Success)
; -3 EnumProcessModulesEx failed.
; -4 The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process.
getModules(byRef aModules, useFileNameAsKey := False)
{
if (A_PtrSize = 4 && this.IsTarget64bit)
return -4 ; AHK is 32bit and target process is 64 bit, this function wont work
aModules := []
if !moduleCount := this.EnumProcessModulesEx(lphModule)
return -3
loop % moduleCount
{
this.GetModuleInformation(hModule := numget(lphModule, (A_index - 1) * A_PtrSize), aModuleInfo)
aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
filePath := aModuleInfo.name
SplitPath, filePath, fileName
aModuleInfo.fileName := fileName
if useFileNameAsKey
aModules[fileName] := aModuleInfo
else aModules.insert(aModuleInfo)
}
return moduleCount
}
getEndAddressOfLastModule(byRef aModuleInfo := "")
{
if !moduleCount := this.EnumProcessModulesEx(lphModule)
return -3
hModule := numget(lphModule, (moduleCount - 1) * A_PtrSize)
if this.GetModuleInformation(hModule, aModuleInfo)
return aModuleInfo.lpBaseOfDll + aModuleInfo.SizeOfImage
return -5
}
; lpFilename [out]
; A pointer to a buffer that receives the fully qualified path to the module.
; If the size of the file name is larger than the value of the nSize parameter, the function succeeds
; but the file name is truncated and null-terminated.
; If the buffer is adequate the string is still null terminated.
GetModuleFileNameEx(hModule := 0, fileNameNoPath := False)
{
; ANSI MAX_PATH = 260 (includes null) - unicode can be ~32K.... but no one would ever have one that size
; So just give it a massive size and don't bother checking. Most coders just give it MAX_PATH size anyway
VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
DllCall("psapi\GetModuleFileNameEx"
, "Ptr", this.hProcess
, "Ptr", hModule
, "Str", lpFilename
, "Uint", 2048 / (A_IsUnicode ? 2 : 1))
if fileNameNoPath
SplitPath, lpFilename, lpFilename ; strips the path so = GDI32.dll
return lpFilename
}
; dwFilterFlag
; LIST_MODULES_DEFAULT 0x0
; LIST_MODULES_32BIT 0x01
; LIST_MODULES_64BIT 0x02
; LIST_MODULES_ALL 0x03
; If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option
; is ignored and the function provides the same results as the EnumProcessModules function.
EnumProcessModulesEx(byRef lphModule, dwFilterFlag := 0x03)
{
lastError := A_LastError
size := VarSetCapacity(lphModule, 4)
loop
{
DllCall("psapi\EnumProcessModulesEx"
, "Ptr", this.hProcess
, "Ptr", &lphModule
, "Uint", size
, "Uint*", reqSize
, "Uint", dwFilterFlag)
if ErrorLevel
return 0
else if (size >= reqSize)
break
else size := VarSetCapacity(lphModule, reqSize)
}
; On first loop it fails with A_lastError = 0x299 as its meant to
; might as well reset it to its previous version
DllCall("SetLastError", "UInt", lastError)
return reqSize // A_PtrSize ; module count ; sizeof(HMODULE) - enumerate the array of HMODULEs
}
GetModuleInformation(hModule, byRef aModuleInfo)
{
VarSetCapacity(MODULEINFO, A_PtrSize * 3), aModuleInfo := []
return DllCall("psapi\GetModuleInformation"
, "Ptr", this.hProcess
, "Ptr", hModule
, "Ptr", &MODULEINFO
, "UInt", A_PtrSize * 3)
, aModuleInfo := { lpBaseOfDll: numget(MODULEINFO, 0, "Ptr")
, SizeOfImage: numget(MODULEINFO, A_PtrSize, "UInt")
, EntryPoint: numget(MODULEINFO, A_PtrSize * 2, "Ptr") }
}
; Method: hexStringToPattern(hexString)
; Converts the hex string parameter into an array of bytes pattern (AOBPattern) that
; can be passed to the various pattern scan methods i.e. modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan()
;
; Parameters:
; hexString - A string of hex bytes. The '0x' hex prefix is optional.
; Bytes can optionally be separated using the space or tab characters.
; Each byte must be two characters in length i.e. '04' or '0x04' (not '4' or '0x4')
; ** Unlike the other methods, wild card bytes MUST be denoted using '??' (two question marks)**
;
; Return Values:
; Object Success - The returned object contains the AOB pattern.
; -1 An empty string was passed.
; -2 Non hex character present. Acceptable characters are A-F, a-F, 0-9, ?, space, tab, and 0x (hex prefix).
; -3 Non-even wild card character count. One of the wild card bytes is missing a '?' e.g. '?' instead of '??'.
; -4 Non-even character count. One of the hex bytes is probably missing a character e.g. '4' instead of '04'.
;
; Examples:
; pattern := hexStringToPattern("DEADBEEF02")
; pattern := hexStringToPattern("0xDE0xAD0xBE0xEF0x02")
; pattern := hexStringToPattern("DE AD BE EF 02")
; pattern := hexStringToPattern("0xDE 0xAD 0xBE 0xEF 0x02")
;
; This will mark the third byte as wild:
; pattern := hexStringToPattern("DE AD ?? EF 02")
; pattern := hexStringToPattern("0xDE 0xAD ?? 0xEF 0x02")
;
; The returned pattern can then be passed to the various pattern scan methods, for example:
; pattern := hexStringToPattern("DE AD BE EF 02")
; memObject.processPatternScan(,, pattern*) ; Note the '*'
hexStringToPattern(hexString)
{
AOBPattern := []
hexString := RegExReplace(hexString, "(\s|0x)")
StringReplace, hexString, hexString, ?, ?, UseErrorLevel
wildCardCount := ErrorLevel
if !length := StrLen(hexString)
return -1 ; no str
else if RegExMatch(hexString, "[^0-9a-fA-F?]")
return -2 ; non hex character and not a wild card
else if Mod(wildCardCount, 2)
return -3 ; non-even wild card character count
else if Mod(length, 2)
return -4 ; non-even character count
loop, % length/2
{
value := "0x" SubStr(hexString, 1 + 2 * (A_index-1), 2)
AOBPattern.Insert(value + 0 = "" ? "?" : value)
}
return AOBPattern
}
; Method: stringToPattern(string, encoding := "UTF-8", insertNullTerminator := False)
; Converts a text string parameter into an array of bytes pattern (AOBPattern) that
; can be passed to the various pattern scan methods i.e. modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan()
;
; Parameters:
; string The text string to convert.
; encoding This refers to how the string is stored in the program's memory.
; UTF-8 and UTF-16 are common. Refer to the AHK manual for other encoding types.
; insertNullTerminator Includes the null terminating byte(s) (at the end of the string) in the AOB pattern.
; This should be set to 'false' unless you are certain that the target string is null terminated and you are searching for the entire string or the final part of the string.
;
; Return Values:
; Object Success - The returned object contains the AOB pattern.
; -1 An empty string was passed.
;
; Examples:
; pattern := stringToPattern("This text exists somewhere in the target program!")
; memObject.processPatternScan(,, pattern*) ; Note the '*'
stringToPattern(string, encoding := "UTF-8", insertNullTerminator := False)
{
if !length := StrLen(string)
return -1 ; no str
AOBPattern := []
encodingSize := (encoding = "utf-16" || encoding = "cp1200") ? 2 : 1
requiredSize := StrPut(string, encoding) * encodingSize - (insertNullTerminator ? 0 : encodingSize)
VarSetCapacity(buffer, requiredSize)
StrPut(string, &buffer, length + (insertNullTerminator ? 1 : 0), encoding)
loop, % requiredSize
AOBPattern.Insert(NumGet(buffer, A_Index-1, "UChar"))
return AOBPattern
}
; Method: modulePatternScan(module := "", aAOBPattern*)
; Scans the specified module for the specified array of bytes
; Parameters:
; module - The file name of the module/dll to search e.g. "calc.exe", "GDI32.dll", "Bass.dll" etc
; If no module (null) is specified, the executable file of the process will be used.
; e.g. for calc.exe it would be the same as calling modulePatternScan(, aAOBPattern*) or modulePatternScan("calc.exe", aAOBPattern*)
; aAOBPattern* A variadic list of byte values i.e. the array of bytes to find.
; Wild card bytes should be indicated by passing a non-numeric value eg "?".
; Return Values:
; Positive int Success. The memory address of the found pattern.
; Null Failed to find or retrieve the specified module. ErrorLevel is set to the returned error from getModuleBaseAddress()
; refer to that method for more information.
; 0 The pattern was not found inside the module
; -9 VirtualQueryEx() failed
; -10 The aAOBPattern* is invalid. No bytes were passed
modulePatternScan(module := "", aAOBPattern*)
{
MEM_COMMIT := 0x1000, MEM_MAPPED := 0x40000, MEM_PRIVATE := 0x20000
, PAGE_NOACCESS := 0x01, PAGE_GUARD := 0x100
if (result := this.getModuleBaseAddress(module, aModuleInfo)) <= 0
return "", ErrorLevel := result ; failed
if !patternSize := this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
return -10 ; no pattern
; Try to read the entire module in one RPM()
; If fails with access (-1) iterate the modules memory pages and search the ones which are readable
if (result := this.PatternScan(aModuleInfo.lpBaseOfDll, aModuleInfo.SizeOfImage, patternMask, AOBBuffer)) >= 0
return result ; Found / not found
; else RPM() failed lets iterate the pages
address := aModuleInfo.lpBaseOfDll
endAddress := address + aModuleInfo.SizeOfImage
loop
{
if !this.VirtualQueryEx(address, aRegion)
return -9
if (aRegion.State = MEM_COMMIT
&& !(aRegion.Protect & (PAGE_NOACCESS | PAGE_GUARD)) ; can't read these areas
;&& (aRegion.Type = MEM_MAPPED || aRegion.Type = MEM_PRIVATE) ;Might as well read Image sections as well
&& aRegion.RegionSize >= patternSize
&& (result := this.PatternScan(address, aRegion.RegionSize, patternMask, AOBBuffer)) > 0)
return result
} until (address += aRegion.RegionSize) >= endAddress
return 0
}
; Method: addressPatternScan(startAddress, sizeOfRegionBytes, aAOBPattern*)
; Scans a specified memory region for an array of bytes pattern.
; The entire memory area specified must be readable for this method to work,
; i.e. you must ensure the area is readable before calling this method.
; Parameters:
; startAddress The memory address from which to begin the search.
; sizeOfRegionBytes The numbers of bytes to scan in the memory region.
; aAOBPattern* A variadic list of byte values i.e. the array of bytes to find.
; Wild card bytes should be indicated by passing a non-numeric value eg "?".
; Return Values:
; Positive integer Success. The memory address of the found pattern.
; 0 Pattern not found
; -1 Failed to read the memory region.
; -10 An aAOBPattern pattern. No bytes were passed.
addressPatternScan(startAddress, sizeOfRegionBytes, aAOBPattern*)
{
if !this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
return -10
return this.PatternScan(startAddress, sizeOfRegionBytes, patternMask, AOBBuffer)
}
; Method: processPatternScan(startAddress := 0, endAddress := "", aAOBPattern*)
; Scan the memory space of the current process for an array of bytes pattern.
; To use this in a loop (scanning for multiple occurrences of the same pattern),
; simply call it again passing the last found address + 1 as the startAddress.
; Parameters:
; startAddress - The memory address from which to begin the search.
; endAddress - The memory address at which the search ends.
; Defaults to 0x7FFFFFFF for 32 bit target processes.
; Defaults to 0xFFFFFFFF for 64 bit target processes when the AHK script is 32 bit.
; Defaults to 0x7FFFFFFFFFF for 64 bit target processes when the AHK script is 64 bit.
; 0x7FFFFFFF and 0x7FFFFFFFFFF are the maximum process usable virtual address spaces for 32 and 64 bit applications.
; Anything higher is used by the system (unless /LARGEADDRESSAWARE and 4GT have been modified).
; Note: The entire pattern must be occur inside this range for a match to be found. The range is inclusive.
; aAOBPattern* - A variadic list of byte values i.e. the array of bytes to find.
; Wild card bytes should be indicated by passing a non-numeric value eg "?".
; Return Values:
; Positive integer - Success. The memory address of the found pattern.
; 0 The pattern was not found.
; -1 VirtualQueryEx() failed.
; -2 Failed to read a memory region.
; -10 The aAOBPattern* is invalid. (No bytes were passed)
processPatternScan(startAddress := 0, endAddress := "", aAOBPattern*)
{
address := startAddress
if endAddress is not integer
endAddress := this.isTarget64bit ? (A_PtrSize = 8 ? 0x7FFFFFFFFFF : 0xFFFFFFFF) : 0x7FFFFFFF
MEM_COMMIT := 0x1000, MEM_MAPPED := 0x40000, MEM_PRIVATE := 0x20000
PAGE_NOACCESS := 0x01, PAGE_GUARD := 0x100
if !patternSize := this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
return -10
while address <= endAddress ; > 0x7FFFFFFF - definitely reached the end of the useful area (at least for a 32 target process)
{
if !this.VirtualQueryEx(address, aInfo)
return -1
if A_Index = 1
aInfo.RegionSize -= address - aInfo.BaseAddress
if (aInfo.State = MEM_COMMIT)
&& !(aInfo.Protect & (PAGE_NOACCESS | PAGE_GUARD)) ; can't read these areas
;&& (aInfo.Type = MEM_MAPPED || aInfo.Type = MEM_PRIVATE) ;Might as well read Image sections as well
&& aInfo.RegionSize >= patternSize
&& (result := this.PatternScan(address, aInfo.RegionSize, patternMask, AOBBuffer))
{
if result < 0
return -2
else if (result + patternSize - 1 <= endAddress)
return result
else return 0
}
address += aInfo.RegionSize
}
return 0
}
; Method: rawPatternScan(byRef buffer, sizeOfBufferBytes := "", aAOBPattern*)
; Scans a binary buffer for an array of bytes pattern.
; This is useful if you have already dumped a region of memory via readRaw()
; Parameters:
; buffer The binary buffer to be searched.
; sizeOfBufferBytes The size of the binary buffer. If null or 0 the size is automatically retrieved.
; startOffset The offset from the start of the buffer from which to begin the search. This must be >= 0.
; aAOBPattern* A variadic list of byte values i.e. the array of bytes to find.
; Wild card bytes should be indicated by passing a non-numeric value eg "?".
; Return Values:
; >= 0 The offset of the pattern relative to the start of the haystack.
; -1 Not found.
; -2 Parameter incorrect.
rawPatternScan(byRef buffer, sizeOfBufferBytes := "", startOffset := 0, aAOBPattern*)
{
if !this.getNeedleFromAOBPattern(patternMask, AOBBuffer, aAOBPattern*)
return -10
if (sizeOfBufferBytes + 0 = "" || sizeOfBufferBytes <= 0)
sizeOfBufferBytes := VarSetCapacity(buffer)
if (startOffset + 0 = "" || startOffset < 0)
startOffset := 0
return this.bufferScanForMaskedPattern(&buffer, sizeOfBufferBytes, patternMask, &AOBBuffer, startOffset)
}
; Method: getNeedleFromAOBPattern(byRef patternMask, byRef needleBuffer, aAOBPattern*)
; Converts an array of bytes pattern (aAOBPattern*) into a binary needle and pattern mask string
; which are compatible with patternScan() and bufferScanForMaskedPattern().
; The modulePatternScan(), addressPatternScan(), rawPatternScan(), and processPatternScan() methods
; allow you to directly search for an array of bytes pattern in a single method call.
; Parameters:
; patternMask - (output) A string which indicates which bytes are wild/non-wild.
; needleBuffer - (output) The array of bytes passed via aAOBPattern* is converted to a binary needle and stored inside this variable.
; aAOBPattern* - (input) A variadic list of byte values i.e. the array of bytes from which to create the patternMask and needleBuffer.
; Wild card bytes should be indicated by passing a non-numeric value eg "?".
; Return Values:
; The number of bytes in the binary needle and hence the number of characters in the patternMask string.
getNeedleFromAOBPattern(byRef patternMask, byRef needleBuffer, aAOBPattern*)
{
patternMask := "", VarSetCapacity(needleBuffer, aAOBPattern.MaxIndex())
for i, v in aAOBPattern
patternMask .= (v + 0 = "" ? "?" : "x"), NumPut(round(v), needleBuffer, A_Index - 1, "UChar")
return round(aAOBPattern.MaxIndex())
}
; The handle must have been opened with the PROCESS_QUERY_INFORMATION access right
VirtualQueryEx(address, byRef aInfo)
{
if (aInfo.__Class != "_ClassMemory._MEMORY_BASIC_INFORMATION")
aInfo := new this._MEMORY_BASIC_INFORMATION()
return aInfo.SizeOfStructure = DLLCall("VirtualQueryEx"
, "Ptr", this.hProcess
, "Ptr", address
, "Ptr", aInfo.pStructure
, "Ptr", aInfo.SizeOfStructure
, "Ptr")
}
/*
// The c++ function used to generate the machine code
int scan(unsigned char* haystack, unsigned int haystackSize, unsigned char* needle, unsigned int needleSize, char* patternMask, unsigned int startOffset)
{
for (unsigned int i = startOffset; i <= haystackSize - needleSize; i++)
{
for (unsigned int j = 0; needle[j] == haystack[i + j] || patternMask[j] == '?'; j++)
{
if (j + 1 == needleSize)
return i;
}
}
return -1;
}
*/
; Method: PatternScan(startAddress, sizeOfRegionBytes, patternMask, byRef needleBuffer)
; Scans a specified memory region for a binary needle pattern using a machine code function
; If found it returns the memory address of the needle in the processes memory.
; Parameters:
; startAddress - The memory address from which to begin the search.
; sizeOfRegionBytes - The numbers of bytes to scan in the memory region.
; patternMask - This string indicates which bytes must match and which bytes are wild. Each wildcard byte must be denoted by a single '?'.
; Non wildcards can use any other single character e.g 'x'. There should be no spaces.
; With the patternMask 'xx??x', the first, second, and fifth bytes must match. The third and fourth bytes are wild.
; needleBuffer - The variable which contains the binary needle. This needle should consist of UChar bytes.
; Return Values:
; Positive integer The address of the pattern.
; 0 Pattern not found.
; -1 Failed to read the region.
patternScan(startAddress, sizeOfRegionBytes, byRef patternMask, byRef needleBuffer)
{
if !this.readRaw(startAddress, buffer, sizeOfRegionBytes)
return -1
if (offset := this.bufferScanForMaskedPattern(&buffer, sizeOfRegionBytes, patternMask, &needleBuffer)) >= 0
return startAddress + offset
else return 0
}
; Method: bufferScanForMaskedPattern(byRef hayStack, sizeOfHayStackBytes, byRef patternMask, byRef needle)
; Scans a binary haystack for binary needle against a pattern mask string using a machine code function.
; Parameters:
; hayStackAddress - The address of the binary haystack which is to be searched.
; sizeOfHayStackBytes The total size of the haystack in bytes.
; patternMask - A string which indicates which bytes must match and which bytes are wild. Each wildcard byte must be denoted by a single '?'.
; Non wildcards can use any other single character e.g 'x'. There should be no spaces.
; With the patternMask 'xx??x', the first, second, and fifth bytes must match. The third and fourth bytes are wild.
; needleAddress - The address of the binary needle to find. This needle should consist of UChar bytes.
; startOffset - The offset from the start of the haystack from which to begin the search. This must be >= 0.
; Return Values:
; >= 0 Found. The pattern begins at this offset - relative to the start of the haystack.
; -1 Not found.
; -2 Invalid sizeOfHayStackBytes parameter - Must be > 0.
; Notes:
; This is a basic function with few safeguards. Incorrect parameters may crash the script.
bufferScanForMaskedPattern(hayStackAddress, sizeOfHayStackBytes, byRef patternMask, needleAddress, startOffset := 0)
{
static p
if !p
{
if A_PtrSize = 4
p := this.MCode("1,x86:8B44240853558B6C24182BC5568B74242489442414573BF0773E8B7C241CBB010000008B4424242BF82BD8EB038D49008B54241403D68A0C073A0A740580383F750B8D0C033BCD74174240EBE98B442424463B74241876D85F5E5D83C8FF5BC35F8BC65E5D5BC3")
else
p := this.MCode("1,x64:48895C2408488974241048897C2418448B5424308BF2498BD8412BF1488BF9443BD6774A4C8B5C24280F1F800000000033C90F1F400066660F1F840000000000448BC18D4101418D4AFF03C80FB60C3941380C18740743803C183F7509413BC1741F8BC8EBDA41FFC2443BD676C283C8FF488B5C2408488B742410488B7C2418C3488B5C2408488B742410488B7C2418418BC2C3")
}
if (needleSize := StrLen(patternMask)) + startOffset > sizeOfHayStackBytes
return -1 ; needle can't exist inside this region. And basic check to prevent wrap around error of the UInts in the machine function
if (sizeOfHayStackBytes > 0)
return DllCall(p, "Ptr", hayStackAddress, "UInt", sizeOfHayStackBytes, "Ptr", needleAddress, "UInt", needleSize, "AStr", patternMask, "UInt", startOffset, "cdecl int")
return -2
}
; Notes:
; Other alternatives for non-wildcard buffer comparison.
; Use memchr to find the first byte, then memcmp to compare the remainder of the buffer against the needle and loop if it doesn't match
; The function FindMagic() by Lexikos uses this method.
; Use scanInBuf() machine code function - but this only supports 32 bit ahk. I could check if needle contains wild card and AHK is 32bit,
; then call this function. But need to do a speed comparison to see the benefits, but this should be faster. Although the benefits for
; the size of the memory regions be dumped would most likely be inconsequential as it's already extremely fast.
MCode(mcode)
{
static e := {1:4, 2:1}, c := (A_PtrSize=8) ? "x64" : "x86"
if !regexmatch(mcode, "^([0-9]+),(" c ":|.*?," c ":)([^,]+)", m)
return
if !DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", 0, "uint*", s, "ptr", 0, "ptr", 0)
return
p := DllCall("GlobalAlloc", "uint", 0, "ptr", s, "ptr")
; if (c="x64") ; Virtual protect must always be enabled for both 32 and 64 bit. If DEP is set to all applications (not just systems), then this is required
DllCall("VirtualProtect", "ptr", p, "ptr", s, "uint", 0x40, "uint*", op)
if DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", p, "uint*", s, "ptr", 0, "ptr", 0)
return p
DllCall("GlobalFree", "ptr", p)
return
}
; This link indicates that the _MEMORY_BASIC_INFORMATION32/64 should be based on the target process
; http://stackoverflow.com/questions/20068219/readprocessmemory-on-a-64-bit-proces-always-returns-error-299
; The msdn documentation is unclear, and suggests that a debugger can pass either structure - perhaps there is some other step involved.
; My tests seem to indicate that you must pass _MEMORY_BASIC_INFORMATION i.e. structure is relative to the AHK script bitness.
; Another post on the net also agrees with my results.
; Notes:
; A 64 bit AHK script can call this on a target 64 bit process. Issues may arise at extremely high memory addresses as AHK does not support UInt64 (but these addresses should never be used anyway).
; A 64 bit AHK can call this on a 32 bit target and it should work.
; A 32 bit AHk script can call this on a 64 bit target and it should work providing the addresses fall inside the 32 bit range.
class _MEMORY_BASIC_INFORMATION
{
__new()
{
if !this.pStructure := DllCall("GlobalAlloc", "UInt", 0, "Ptr", this.SizeOfStructure := A_PtrSize = 8 ? 48 : 28, "Ptr")
return ""
return this
}
__Delete()
{
DllCall("GlobalFree", "Ptr", this.pStructure)
}
; For 64bit the int64 should really be unsigned. But AHK doesn't support these
; so this won't work correctly for higher memory address areas
__get(key)
{
static aLookUp := A_PtrSize = 8
? { "BaseAddress": {"Offset": 0, "Type": "Int64"}
, "AllocationBase": {"Offset": 8, "Type": "Int64"}
, "AllocationProtect": {"Offset": 16, "Type": "UInt"}
, "RegionSize": {"Offset": 24, "Type": "Int64"}
, "State": {"Offset": 32, "Type": "UInt"}
, "Protect": {"Offset": 36, "Type": "UInt"}
, "Type": {"Offset": 40, "Type": "UInt"} }
: { "BaseAddress": {"Offset": 0, "Type": "UInt"}
, "AllocationBase": {"Offset": 4, "Type": "UInt"}
, "AllocationProtect": {"Offset": 8, "Type": "UInt"}
, "RegionSize": {"Offset": 12, "Type": "UInt"}
, "State": {"Offset": 16, "Type": "UInt"}
, "Protect": {"Offset": 20, "Type": "UInt"}
, "Type": {"Offset": 24, "Type": "UInt"} }
if aLookUp.HasKey(key)
return numget(this.pStructure+0, aLookUp[key].Offset, aLookUp[key].Type)
}
__set(key, value)
{
static aLookUp := A_PtrSize = 8
? { "BaseAddress": {"Offset": 0, "Type": "Int64"}
, "AllocationBase": {"Offset": 8, "Type": "Int64"}
, "AllocationProtect": {"Offset": 16, "Type": "UInt"}
, "RegionSize": {"Offset": 24, "Type": "Int64"}
, "State": {"Offset": 32, "Type": "UInt"}
, "Protect": {"Offset": 36, "Type": "UInt"}
, "Type": {"Offset": 40, "Type": "UInt"} }
: { "BaseAddress": {"Offset": 0, "Type": "UInt"}
, "AllocationBase": {"Offset": 4, "Type": "UInt"}
, "AllocationProtect": {"Offset": 8, "Type": "UInt"}
, "RegionSize": {"Offset": 12, "Type": "UInt"}
, "State": {"Offset": 16, "Type": "UInt"}
, "Protect": {"Offset": 20, "Type": "UInt"}
, "Type": {"Offset": 24, "Type": "UInt"} }
if aLookUp.HasKey(key)
{
NumPut(value, this.pStructure+0, aLookUp[key].Offset, aLookUp[key].Type)
return value
}
}
Ptr()
{
return this.pStructure
}
sizeOf()
{
return this.SizeOfStructure
}
}
}