Memory Process reading/Writing & Pattern Scans (Array of bytes)

Post your working scripts, libraries and tools for AHK v1.1 and older
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Memory Process reading/Writing & Pattern Scans (Array of bytes)

28 Dec 2013, 03:02

Last edited by RHCP on 25 Jul 2016, 21:57, edited 4 times in total.
ciantic
Posts: 19
Joined: 24 Oct 2015, 15:39
Contact:

Re: Memory Process reading/Writing

02 Nov 2015, 03:31

I get total emptiness if I run:

Code: Select all

explorerExe := new memory("ahk_exe explorer.exe")
msgbox % explorerExe.BaseAddress
Should this also work for explorer.exe?

Edit: I'm using Windows 10, 64 bit.

I've made a in-memory patch for explorer.exe and tested it using x64dbg, and now I'm thinking best way to apply it without x64dbg. But since I need to also call GetWindowLongPtrW within the explorer.exe process context, I wonder if this is at all possible with AHK, because I'm not sure if I can use CreateRemoteThread from AHK at all, it may require me to write C++ which makes the patch more rigid.
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Re: Memory Process reading/Writing

02 Nov 2015, 07:44

Code: Select all

#Include <classMemory>

if (_ClassMemory.__Class != "_ClassMemory")
{
    msgbox class memory not correctly installed. 
    ExitApp
}
explorerExe := new _ClassMemory("ahk_exe explorer.exe")
SetFormat, IntegerFast, H ; View the addresses in hex
msgbox % explorerExe.BaseAddress ; This works for me on explorer. For some applications it will not be correct. getmodulebaseAddress() always seems to work. (but its bitness dependant)
    . "`n" explorerExe.getmodulebaseAddress() ; fails if AHK is 32 and target is 64 bit. When ahk is 64 bit this will work with both 64 and 32 bit target apps


The description in the original post is outdated.

It's possible to call CreateRemoteThread in AHK and consequently call functions in remote processes. I've done it before, but only as a test.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: Memory Process reading/Writing

16 Jan 2016, 11:25

First of all thanks for great script!

This works fine:

Code: Select all

stringAdress := vlc.processPatternScan( ,, 0x30, 0x30, 0x3a, 0x30, 0x34)
But I need to use instead of aAOBPattern* a variable with hex number. Something like:

Code: Select all

bbb := 0x30, 0x30, 0x3a, 0x30, 0x34

stringAdress := vlc.processPatternScan( ,, bbb)
OR

Code: Select all

bbb := 0x30303a3034

stringAdress := vlc.processPatternScan( ,, bbb)
How can I do it?
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Re: Memory Process reading/Writing

16 Jan 2016, 12:24

There are a few ways, but it depends on what that number represents.

The found address is being stored as 'stringAdress' so this kinda indicates that youre searching for a string i.e. '00:04'.
I'm guessing you're trying to search for a changing string?


A neater method:

Code: Select all

AOB := stringToAOBPattern("00:04", "UTF-8") 
stringAdress := vlc.processPatternScan( ,, AOB*)

stringToAOBPattern(string, encoding := "UTF-8", insertNullTerminator := False)
{
	AOBPattern := []
	encodingSize := (encoding = "utf-16" || encoding = "cp1200") ? 2 : 1
	requiredSize := StrPut(string, encoding) * encodingSize - (insertNullTerminator ? 0 : encodingSize)
	VarSetCapacity(buffer, requiredSize)
	StrPut(string, &buffer, StrLen(string) + (insertNullTerminator ?  1 : 0), encoding)	
	loop, % requiredSize
		AOBPattern.Insert(NumGet(buffer, A_Index-1, "UChar"))
	return AOBPattern
}

Code: Select all

pattern := hexStrToAOBPattern("30303a3034")
; hexString is a string of hex bytes (2-digits) without the '0x' hex prefix.
; eg.
; DEADBEEF
; A byte can be denoted wild by using two question marks (or any other character that isn't a hex number)
; DEAD??EF - the third byte  is wild
hexStrToAOBPattern(hexString)
{
	AOBPattern := []
	length := StrLen(hexString)
	if !length || Mod(length, 2)
		return -1 ; no str or string is not an even number of characters - 2 characters per byte
	loop, % length/2
	{
		value := "0x" SubStr(hexString, 1 + 2 * (A_index-1), 2)
		if (value + 0 = "")
			value := "?"
		AOBPattern.Insert(value)
	}
	return AOBPattern
}
Last edited by RHCP on 16 Jan 2016, 13:30, edited 1 time in total.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: Memory Process reading/Writing

16 Jan 2016, 13:23

Thank you :) It works great with strings.

How to instead of string 00:04 search for some hex pattern. I mean something like this:


Code: Select all

AOB := hexToAOBPattern(0xA734dFFF345643C) 
patternAdress := vlc.processPatternScan( ,, AOB*)
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Re: Memory Process reading/Writing

16 Jan 2016, 13:34

I edited the post above, hexStrToAOBPattern() does exactly that. Don't use the hex prefix.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: Memory Process reading/Writing

17 Jan 2016, 11:21

Here is my code:

Code: Select all

#include classMemory.ahk


vlc := new _ClassMemory("ahk_exe vlc.exe", "", hProcessCopy)

stringAdress := vlc.processPatternScan( ,, 0x30, 0x30, 0x3a, 0x30, 0x34)

SetFormat, IntegerFast, hex
stringAdress += 0  ; Sets Var (which previously contained 11) to be 0xb.
stringAdress .= ""  ; Necessary due to the "fast" mode.
SetFormat, IntegerFast, d

MsgBox, % stringAdress



pattern := hexStrToAOBPattern("30303a3034")

SetFormat, IntegerFast, hex
pattern += 0  ; Sets Var (which previously contained 11) to be 0xb.
pattern .= ""  ; Necessary due to the "fast" mode.
SetFormat, IntegerFast, d

MsgBox, % pattern


; hexString is a string of hex bytes (2-digits) without the '0x' hex prefix.
; eg.
; DEADBEEF
; A byte can be denoted wild by using two question marks (or any other character that isn't a hex number)
; DEAD??EF - the third byte  is wild
hexStrToAOBPattern(hexString)
{
	AOBPattern := []
	length := StrLen(hexString)
	if !length || Mod(length, 2)
		return -1 ; no str or string is not an even number of characters - 2 characters per byte
	loop, % length/2
	{
		value := "0x" SubStr(hexString, 1 + 2 * (A_index-1), 2)
		if (value + 0 = "")
			value := "?"
		AOBPattern.Insert(value)
	}
	return AOBPattern
}






; AOB is object!
AOB := stringToAOBPattern("00:04", "UTF-8") 
stringAdress2 := vlc.processPatternScan( ,, AOB*)
stringToAOBPattern(string, encoding := "UTF-8", insertNullTerminator := False)
{
	AOBPattern := []
	encodingSize := (encoding = "utf-16" || encoding = "cp1200") ? 2 : 1
	requiredSize := StrPut(string, encoding) * encodingSize - (insertNullTerminator ? 0 : encodingSize)
	VarSetCapacity(buffer, requiredSize)
	StrPut(string, &buffer, StrLen(string) + (insertNullTerminator ?  1 : 0), encoding)	
	loop, % requiredSize
		AOBPattern.Insert(NumGet(buffer, A_Index-1, "UChar"))
	return AOBPattern
}




SetFormat, IntegerFast, hex
stringAdress2 += 0  ; Sets Var (which previously contained 11) to be 0xb.
stringAdress2 .= ""  ; Necessary due to the "fast" mode.
SetFormat, IntegerFast, d

MsgBox, % stringAdress2

MsgBox, % stringAdress and MsgBox, % stringAdress2 are giving me correct result. But MsgBox, % pattern gives 0x0 . What I am doing wrong?
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Re: Memory Process reading/Writing

17 Jan 2016, 22:54

Code: Select all

pattern := hexStrToAOBPattern("30303a3034")
 
SetFormat, IntegerFast, hex
pattern += 0  ; Sets Var (which previously contained 11) to be 0xb.
pattern .= ""  ; Necessary due to the "fast" mode.
SetFormat, IntegerFast, d
 
MsgBox, % pattern
That is from your code.

hexStrToAOBPattern() is returning an object, like you did in the other examples, you need to pass that object to a pattern scan method.

Code: Select all

pattern := hexStrToAOBPattern("30303a3034")
stringAdress3 := vlc.processPatternScan( ,, pattern*)
You can't display objects via a msgbox. Consider downloading HotkeyIt's ObjTree function, It allows you to see the contents and layout of an object/array - I use it all the time.
https://autohotkey.com/board/topic/6483 ... ts-easily/

Cheers.
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: Memory Process reading/Writing

18 Jan 2016, 13:29

Thanks :) Works great! :)
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
loter
Posts: 38
Joined: 26 May 2016, 00:35

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

09 Aug 2016, 19:33

Really good script.
Thank you very much
User avatar
WAZAAAAA
Posts: 88
Joined: 13 Jan 2015, 19:48

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

23 Oct 2016, 11:51

I have built 3 game trainer tools around classMemory, thank you RHCP.

For those who would like to have a fully working basic example of the memory read and write functions, take a look at my stuff https://autohotkey.com/boards/viewtopic.php?&t=24155
YOU'RE NOT ALEXANDER
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

24 Oct 2016, 08:29

Suggestion for EnumProcessModulesEx

Instand of loop to get the size you can call it twice:

Code: Select all

; Initial call to get the size needed
DllCall("psapi\EnumProcessModulesEx", "ptr", hProcess, "ptr", 0, "uint", 0, "uint*", size, "uint", 0x03)

; Allocate space for use with DllCall
cb := VarSetCapacity(hModule, size, 0)

; Second call to get the data we want
DllCall("psapi\EnumProcessModulesEx", "ptr", hProcess, "ptr", &hModule, "uint", cb, "uint*", size, "uint", 0x03)
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
WAZAAAAA
Posts: 88
Joined: 13 Jan 2015, 19:48

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

19 Jul 2017, 08:22

Hello RHCP

I can't seem to be able to loop stringToPattern() searches while the targeted program does not exist yet. I can do that easily with read() so that my memory tool will pick up the right memory addresses as soon as the targeted program is detected. This way I'm not forcing the user to run the target program BEFORE the memory tool, thus making it easier to use.

Here's an example. If I first launch Notepad, type Thisisatest and launch the script, the AOB is correctly found:

Code: Select all

#Include classMemory.ahk
#SingleInstance Force
TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
myAOBstringpattern := "Thisisatest"
ConvertBase(InputBase, OutputBase, number)
{
	static u := A_IsUnicode ? "_wcstoui64" : "_strtoui64"
	static v := A_IsUnicode ? "_i64tow"    : "_i64toa"
	VarSetCapacity(s, 65, 0)
	value := DllCall("msvcrt.dll\" u, "Str", number, "UInt", 0, "UInt", InputBase, "CDECL Int64")
	DllCall("msvcrt.dll\" v, "Int64", value, "Str", s, "UInt", OutputBase, "CDECL")
	return s
}

Loop
{
	myAOBscan := TargetProcess.stringToPattern(myAOBstringpattern, "UTF-16")
	myAOBaddressdec := TargetProcess.processPatternScan(,, myAOBscan*)
	if (myAOBaddressdec > 0) ;AOB found, stop scanning and convert it from decimal to hexadecimal
	{
		myAOBaddresshex := "0x" ConvertBase(10, 16, myAOBaddressdec)
		MsgBox, found AOB at: %myAOBaddresshex%
		ExitApp
	}
	else ;AOB not found, continue scanning
	{
		Sleep,500
	}
}
Launching Notepad, launching the script and finally typing Thisisatest also works.
But if I first launch the script, then Notepad and type Thisisatest, the AOB will never be found even though the search is looped. Is there any workaround for this? I want to make my scripts as easy as possible for users.


Also, just a suggestion, I think classMemory should convert the found AOB address from decimal to hexadecimal AUTOMATICALLY. I mean the rest of the stuff I worked with in classMemory outputted addresses in hex, so why does this one in particular have to be dec?
YOU'RE NOT ALEXANDER
RHCP
Posts: 202
Joined: 30 Sep 2013, 10:59

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

20 Jul 2017, 04:45

I can't seem to be able to loop stringToPattern() searches while the targeted program does not exist yet. I can do that easily with read()
When you mention read(), I assume you're refering to some ReadMemory function in another library.... replacing the AOB scan in your code with a _classMemory.read() definitely wont work.

The target process must exist when you call "new _ClassMemory()".
Notes:
If the target process exits and then starts again (or restarts) you will need to free the derived object and then use the new operator to create a new object i.e.
calc := [] ; or calc := "" ; free the object. This is actually optional if using the line below, as the line below would free the previous derived object calc prior to initialising the new copy.
calc := new _ClassMemory("ahk_exe calc.exe") ; Create a new derived object to read calc's memory.

This is a simple approach. Just wait for the target to exist before doing any memory stuff. Exit the script when the target closes.

Code: Select all

#Include classMemory.ahk
#SingleInstance Force
#Persistent

process, wait, notepad.exe ; wait indefinitely for process to exist
settimer, exist, 100 ; when process exits close the script

TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
myAOBstringpattern := "Thisisatest"
Loop
{
	myAOBscan := TargetProcess.stringToPattern(myAOBstringpattern, "UTF-16")
	; My notepad is 64 bits, so need to increase the endAddress parameter to find this pattern

	myAOBaddress := TargetProcess.processPatternScan(, TargetProcess.isTarget64bit ? 0x7FFFFFFFFFF : 0x7FFFFFFF, myAOBscan*)

	if (myAOBaddress > 0) 
		MsgBox, found AOB at: %myAOBaddress%
	else
		Sleep,500
}
return 

; when process exits close the script
exist: 
if !WinExist("ahk_exe notepad.exe")
	ExitApp

; alternatively
;Process, exist, notepad.exe
;if !ErrorLevel
;	ExitApp
return
This is a better solution. It allows the user to exit/restart the target.
When checking if the target exists or not you want to use a relatively low interval, otherwise it's possible for the target process to restart with the script missing this. You could check the PID for extra-protection, or use other means - but this is simple and works well for most purposes.

Code: Select all

#Include classMemory.ahk
#SingleInstance Force
#Persistent

settimer, setMemory, 50
return 

doScans:
myAOBscan := TargetProcess.stringToPattern("Thisisatest", "UTF-16")
myAOBaddress := TargetProcess.processPatternScan(, TargetProcess.isTarget64bit ? 0x7FFFFFFFFFF : 0x7FFFFFFF, myAOBscan*)
if (myAOBaddress > 0) 
{
	SetFormat, IntegerFast, H
	myAOBaddress += 0
	myAOBaddress .= ""
	SetFormat, IntegerFast, D
	msgbox patterns found at %myAOBaddress% 
}
return 


setMemory:
; if TargetProcess hasn;t been set and the process exists - open a handle
if !IsObject(TargetProcess) && WinExist("ahk_exe notepad.exe")
{
	TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
	SetTimer, doScans, 100 ; enable all memory reading timers
}
else if !WinExist("ahk_exe notepad.exe")
{
	settimer, doScans, off ; disable all memory reading timers 
	TargetProcess := "" ; destroy the memory object
}
return
This works like the script above, but it's a safer approach that relies on isHandleValid() - this will always detect if the target has closed (no chance of missing it). You have to update class memory to the latest version (vr 2.8).

Code: Select all

#Include classMemory.ahk
#SingleInstance Force
#Persistent

settimer, setMemory, 50
msgbox This requires ClassMemory version 2.8 or greater!
return 


doScans:
myAOBscan := TargetProcess.stringToPattern("Thisisatest", "UTF-16")
myAOBaddress := TargetProcess.processPatternScan(,, myAOBscan*)
if (myAOBaddress > 0) 
{
    SetFormat, IntegerFast, H
    myAOBaddress += 0
    myAOBaddress .= ""
    SetFormat, IntegerFast, D
    msgbox patterns found at %myAOBaddress% 
}
return 


setMemory:
; If new _ClassMemory() has never been called then TargetProcess.isHandleValid() will return null (as TargetProcess isn't an object yet)
; If new _ClassMemory() has been successfully called, then isHandleValid() will return false after notepad closes or restarts
; otherwise it returns true
if TargetProcess.isHandleValid()
    return 
else if WinExist("ahk_exe notepad.exe")
{
    TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
    SetTimer, doScans, 100 ; enable all memory reading timers
}
else ; handle is invalid and target process doesn't exist
{
    settimer, doScans, off ; disable all memory reading timers 
}
return
I think classMemory should convert the found AOB address from decimal to hexadecimal AUTOMATICALLY.
That is a valid point and I did consider it when writing the functions. I elected not to for the simple fact that the vast majority of times that these functions are called they will not be outputting values for people to read. When debugging, its easy to place the thread into hex mode via setformat. And when outputting found addresses for some other purpose you will often want it in a specific format - perhaps with or without the '0x' prefix and/or left zero padded which would could negate any default conversion.
I mean the rest of the stuff I worked with in classMemory outputted addresses in hex
None of the functions in this class convert dec to hex or vice versa.
KusochekDobra
Posts: 38
Joined: 25 Apr 2016, 18:00

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

08 Aug 2017, 16:47

This is Great work!!! Thank You very much for so awesome, simple and beautiful solution!!!
Дай Вам Бог здоровья и долгих и интересных дней жизни! =)
User avatar
WAZAAAAA
Posts: 88
Joined: 13 Jan 2015, 19:48

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

10 Oct 2017, 08:57

RHCP wrote:When you mention read(), I assume you're refering to some ReadMemory function in another library.... replacing the AOB scan in your code with a _classMemory.read() definitely wont work.
No I mean the read method described in your library. classMemory is the only library I've ever used for memory related stuff in AHK. From your documentation:

Code: Select all

    Commonly used methods:
        read()
None of the functions in this class convert dec to hex or vice versa.
yeah my bad I meant every time I use read() and write() I feed it addresses written in hex not dec like this:
PX_distance := TargetProcess.read(0x00FB79A0 + TargetProcess.BaseAddress, "UFloat")
but I understand I made no sense since the dec version of 0x00FB79A0 (16480672) would work too there.

Anyway, thanks for the help on the AOB scans and for isHandleValid, they worked great, this is the looped code I've been using on my own tool:
Spoiler


Moving on to another matter, I can't get suspend() and resume() to work through classMemory. This is the code I've been trying:

Code: Select all

#Include classMemory.ahk

;request admin rights
full_command_line := DllCall("GetCommandLine", "str")
if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
{
	try
	{
		if A_IsCompiled
			RunWait *RunAs "%A_ScriptFullPath%" /restart
		else
			RunWait *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
	}
}
if not A_IsAdmin
{
	MsgBox, Administrator rights not found. The program might not work.
}
;SeDebugPrivilege just in case
_ClassMemory.setSeDebugPrivilege()

$*F1::
TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
TargetProcess.suspend()
return
$*F2::
TargetProcess := new _ClassMemory("ahk_exe notepad.exe", "", hProcessCopy)
TargetProcess.resume()
return
It doesn't suspend or resume the process, what am I doing wrong?
I would also like to recommend you to somehow implement multiple methods of process suspension, as they have different pros and cons.

METHOD 1:
NtSuspendProcess/ZwSuspendProcess and NtResumeProcess/ZwResumeProcess. This is the one your classMemory uses but I can't get it to work.
Working example with a function that checks weather a process has been suspended or not before attempting to suspend:

Code: Select all

;request admin rights
full_command_line := DllCall("GetCommandLine", "str")
if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
{
	try
	{
		if A_IsCompiled
			RunWait *RunAs "%A_ScriptFullPath%" /restart
		else
			RunWait *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
	}
}
if not A_IsAdmin
{
	MsgBox, Administrator rights not found. The program might not work.
}

;enable SeDebugPrivilege just in case
Process, Exist  ; sets ErrorLevel to the PID of this running script
; Get the handle of this script with PROCESS_QUERY_INFORMATION (0x0400)
h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", ErrorLevel, "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")
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

;suspension-related functions
SuspendProcess(pid) {
	hProcess := DllCall("OpenProcess", "UInt", 0x1F0FFF, "Int", 0, "Int", pid)
	If (hProcess) {
		DllCall("ntdll.dll\NtSuspendProcess", "Int", hProcess)
		DllCall("CloseHandle", "Int", hProcess)
	}
}
ResumeProcess(pid) {
	hProcess := DllCall("OpenProcess", "UInt", 0x1F0FFF, "Int", 0, "Int", pid)
	If (hProcess) {
		DllCall("ntdll.dll\NtResumeProcess", "Int", hProcess)
		DllCall("CloseHandle", "Int", hProcess)
	}
}
ProcessIsSuspended(pid, ByRef isPartiallySuspended := 0) {
	static initialBufferSize := 0x4000, cbSYSTEM_THREAD_INFORMATION := A_PtrSize == 8 ? 80 : 64
	static SystemProcessInformation := 5, STATUS_BUFFER_TOO_SMALL := 0xC0000023, STATUS_INFO_LENGTH_MISMATCH := 0xC0000004
	static Waiting := 5, Suspended := 5
	static UniqueProcessIdOffset := A_PtrSize == 8 ? 80 : 68, NumberOfThreadsOffset := 4, ThreadsArrayOffset := A_PtrSize == 8 ? 256 : 184
	static ThreadStateOffset := A_PtrSize == 8 ? 68 : 52, WaitReasonOffset := A_PtrSize == 8 ? 72 : 56
	bufferSize := initialBufferSize
	
	VarSetCapacity(ProcessBuffer, bufferSize)
	
	Loop {
		status := DllCall("ntdll\NtQuerySystemInformation", "UInt", SystemProcessInformation, "Ptr", &ProcessBuffer, "UInt", bufferSize, "UInt*", bufferSize, "UInt")
		if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) {
			VarSetCapacity(ProcessBuffer, bufferSize)
		}
		else {
			break
		}
	}
	
	if (status < 0)	{
		return False
	}
	
	if (bufferSize <= 0x100000) initialBufferSize := bufferSize
		
	isSuspended := pid > 0
	isPartiallySuspended := False
	ThisEntryOffset := 0
	
	Loop {
		if (NumGet(ProcessBuffer, ThisEntryOffset + UniqueProcessIdOffset, "Ptr") == pid) {
			Loop % NumGet(ProcessBuffer, ThisEntryOffset + NumberOfThreadsOffset, "UInt") {
				ThisThreadsOffset := ThisEntryOffset + ThreadsArrayOffset + (cbSYSTEM_THREAD_INFORMATION * (A_Index - 1))
				ThreadState := NumGet(ProcessBuffer, ThisThreadsOffset + ThreadStateOffset, "UInt")
				WaitReason := NumGet(ProcessBuffer, ThisThreadsOffset + WaitReasonOffset, "UInt")
				if (ThreadState != Waiting || WaitReason != Suspended) {
					isSuspended := False
				} else {
					isPartiallySuspended := True
				}
			}
			return isSuspended
		}
	} until (!(NextEntryOffset := NumGet(ProcessBuffer, ThisEntryOffset, "UInt")), ThisEntryOffset += NextEntryOffset)
	return -1
}

$*F1::
Process, Exist, notepad.exe
pid := ErrorLevel
if !ProcessIsSuspended(pid)
{
	SuspendProcess(pid)
}
else
{
	ResumeProcess(pid)
}
return
CONS:
- if you send the suspend command to a process like 3 times in a row, you also need to resume it the same amount of times (or more) to actually resume it, so there needs to be a way to properly check the current suspension status of a process beforehand (provided in the above example)

METHOD 2:
DebugActiveProcess and DebugActiveProcessStop
example:

Code: Select all

;request admin rights
full_command_line := DllCall("GetCommandLine", "str")
if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
{
	try
	{
		if A_IsCompiled
			RunWait *RunAs "%A_ScriptFullPath%" /restart
		else
			RunWait *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
	}
}
if not A_IsAdmin
{
	MsgBox, Administrator rights not found. The program might not work.
}

;enable SeDebugPrivilege just in case
Process, Exist  ; sets ErrorLevel to the PID of this running script
; Get the handle of this script with PROCESS_QUERY_INFORMATION (0x0400)
h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", ErrorLevel, "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")
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

$*F1::
Process, Exist, notepad.exe
pid := ErrorLevel
DllCall("DebugActiveProcess", "UInt", pid)
return
$*F2::
Process, Exist, notepad.exe
pid := ErrorLevel
DllCall("DebugActiveProcessStop", "UInt", pid)
return
PROS:
- can suspend multiple times without the need to resume the same amount of times unlike METHOD 1

METHOD 3:
SuspendThread/Wow64SuspendThread and ResumeThread/Wow64ResumeThread. Suspending every thread of a process should result in the same outcome as the previous methods.
I don't have a ready to use code for this, I did it with Process Hacker, but these resources may be useful
https://autohotkey.com/board/topic/2124 ... endthread/
https://autohotkey.com/boards/viewtopic.php?t=19323
https://autohotkey.com/boards/viewtopic.php?t=24055
PROS:
- this method can work where METHOD 1 and 2 fail with some protected processes, bypassing "Access denied" errors
Image
Black Desert Online for example is protected by anticheat XIGNCODE, and this has been the only suspension method that worked.
CONS:
looks complicated for multi-threaded processes
Last edited by WAZAAAAA on 11 Oct 2017, 02:03, edited 3 times in total.
YOU'RE NOT ALEXANDER
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

10 Oct 2017, 09:22

For suspend and resume see here:
https://autohotkey.com/boards/viewtopic ... 012#p96012

(A 64-bit application can suspend a WOW64 thread using the Wow64SuspendThread function.)
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
Galaxis
Posts: 73
Joined: 04 Feb 2016, 20:09

Re: Memory Process reading/Writing & Pattern Scans (Array of bytes)

27 Jun 2019, 15:34

RHCP wrote:
28 Jun 2019, 00:18
The first string is unicode.
Try:

Code: Select all

Exile := poe.readString(0x7FFB69C3AE78,, encoding := "UTF-16")
What is this supposed to do, besides change the English text to look like Japanese characters instead? UFT-8 I understand as English, and reads perfectly for what I'm trying to accomplish.

It still refuses to show text beyond the first encountered null.

This is my code for the AOB scan to find the bytes for Hotbar.dat

Code: Select all

Hotbar:= XIV.hexStringtoPattern("48 4F 54 42 41 52 2E 44 41 54 00 00 01 00 00 00 70")
I'll save the address starting at HOTBAR.DAT. and then read subsequent text up to 1000 characters
which will cover all the skills/keybinds on the hotbars.
Reading all these hotkeys, quite simply informs the program I'm writing, what all the keybinds are and what to press for said abilities.

In this example here, I simply want to read all the Abilities on my Final Fantasy 14 hotbar?
Image

Lol, I don't understand why that Length size is there is null completely negates it.


AHK will absolutely display the string HOTBAR.DAT with the readString(address,, encoding :="UTF-8") function. Japanese if its UTF-16.
However, it will not read beyond the 0 byte after that.

So what's the point of specifying length?

Is it possible to read all the hotkey text, even across the encountered nulls between the skills?
AOB scan isn't optimal, because it won't read changes to the hotbar in real-time.
People change hotkeys and move skills around all the time. The pattern won't be the same, and the bot will press wrong buttons.


What other function ignores nulls, reads between a range of addresses, and obeys specified byte length?
AOB scans take too damn long, because I have to write AOB patters for every single spell/skill keybind in Final Fantasy.

@rommmcek
Currently, I am using an increment of =+1 from the base address to read all the bytes individually and parse each character/letter in order, and paste all that text file, and read from file.

But that's so exhausting. If only it can read it all.
A readstring function that ignores the nulls and obeys the length would be simple, if it exists.

@RHCP
Any solution you can give just to read text across the range of 2 specified addresses would be invaluable. Please HALP! Thanks

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: Google [Bot] and 122 guests