test whether another script's hotkeys are still working (trigger hotkeys in another script)

Get help with using AutoHotkey and its commands and hotkeys
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

test whether another script's hotkeys are still working (trigger hotkeys in another script)

06 Feb 2017, 14:59

I am looking for methods to test whether another script's
hotkeys are still working.
(Btw I am not looking to *communicate* with another script,
only to test whether its hotkeys are still working.)

Are there any ways to trigger a hotkey in another script
other than Send/SendRaw/SendInput/SendEvent?

Let's say all the scripts had the same hotkey, ~q,
could I direct a key press at a specific script,
so that only its hotkey was triggered?

Can a script reregister all its hotkeys,
without using Reload?

Code: Select all

~q::
MsgBox [%A_ThisHotkey%] %A_ScriptName%
Return

;==================================================

r::
Send q ;works
;SendRaw q ;works
;SendInput q ;works
;SendPlay q ;doesn't work
;SendEvent q ;works

;SendInput {VK51} ;works
;SendInput {SC010} ;works

;ControlSend, , q, A ;doesn't work (e.g. send to the AHK window)
;ControlSend, Edit1, q, A ;doesn't work (e.g. send to the AHK window)

;q is Chr(113)
;SendMessage, 0x102, 113, 1, , A ;WM_CHAR ;doesn't work
;SendMessage, 0x102, 113, 1, Edit1, A ;WM_CHAR ;doesn't work

;q is VK51 (where 51 is a hex number)
;SendMessage, 0x100, 0x51, 0x014B0001, , A ;WM_KEYDOWN ;doesn't work
;SendMessage, 0x101, 0x51, 0xC14B0001, , A ;WM_KEYUP ;doesn't work
Return
Last edited by jeeswg on 08 Feb 2017, 07:01, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Test whether another script's hotkeys are still working

07 Feb 2017, 15:54

What I'm about to suggest is not clean, but it's been a day and you've had no replies, so this feeling of dirtiness I have is assuaged somewhat. At the very least, you'll get a thread bump.
jeeswg wrote:Are there any ways to trigger a hotkey in another script
other than Send/SendRaw/SendInput/SendEvent?

Let's say all the scripts had the same hotkey, ~q,
could I direct a key press at a specific script,
so that only its hotkey was triggered?
I, erm, really wouldn't recommend this, but if AutoHotkey is using its hook for a hotkey, you can post 0x0400 (AHK_HOOK_HOTKEY = WM_USER) to the AutoHotkey window. The ID of the hotkey to put in wParam starts from zero. I've not the foggiest how to map each hook-using hotkey to the ID, nor how AutoHotkey decides which hotkey to put into the table first.

I was able do this in one script:

Code: Select all

#InstallKeybdHook
#UseHook On ; I know what $ does, but you can't be too sure
$F1::MsgBox 1
and

Code: Select all

DetectHiddenWindows On
PostMessage, 0x0400, 0, 1,, ahk_id %WindowIdIGotFromMyTaskmanagerBecauseI'mTooLazyToSearch% ahk_class AutoHotkey ; Although of course, you can match on the title name of each script, which should be different


in another. You should consider https://github.com/Lexikos/AutoHotkey_L ... .cpp#L2272 and given that I know nothing of how AutoHotkey works, I have no idea what other caveats you need to look out for.

For hotkeys that were registered using RegisterHotkey, I don't know how to trigger them. My attempts at posting WM_HOTKEY to AutoHotkey's main window failed. Maybe I wasn't performing the PostMessage correctly (one example I tried: https://autohotkey.com/boards/viewtopic.php?t=16787). I think the issue might've been that I don't know the IDs for the hotkey. As I understand it, this ID is globally used and stored inside the win32k.sys kernel driver. The only code I saw that was able to iterate the globally registered hotkeys did so by loading a driver... If there isn't an easier way of determining the ID sent to RegisterHotkey inside your AutoHotkey script that I'm not privy to, I would use a function hooking library like MinHook (I have a couple of examples on here that make use of it) to hijack RegisterHotkey and see what ID is used for a particular hotkey and store it away. (But you might need to convert your hotkeys to use the Hotkey command instead of declaring them directly to be sure the function is hooked first before the hotkeys are set. I didn't say I had good answers :-))
Can a script reregister all its hotkeys,
without using Reload?
You might be able to call Hotkey, but I have no idea if that actually works in practice.
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Test whether another script's hotkeys are still working

07 Feb 2017, 21:13

Thanks so much for this useful response.
I was unaware of this method.
Based on this post and others,
you clearly have a lot of interesting AutoHotkey knowledge!

From testing:

When using message 0x400 to trigger a hotkey
it appears that the type of hook does not matter:
$ doesn't matter,
#UseHook doesn't matter,
reg / k-hook / m-hook / 2-hooks doesn't matter.
All hotkeys can be triggered by this method.

For:
PostMessage, 0x400, %wParam%, , , ahk_id %hWnd%
The number wParam corresponds to the items in the list in
'View, Hotkeys and their methods'.
To trigger the first item: set wParam := 0,
to trigger the nth item: set wParam := n-1.

If a hotkey has context-sensitive variants,
the hotkey is still only listed once.
When you send the message, you must take into consideration,
the active window etcetera, because the #IfWin criteria
must be met.

If I could grab a list of a script's hotkeys remotely,
that would give a way to work out the required wParam.
Actually, putting the 'check hotkeys are working' hotkey
as the very first hotkey in my scripts would be sufficient for my needs,
and so I would thus use wParam := 0.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Test whether another script's hotkeys are still working

07 Feb 2017, 21:41

jeeswg wrote: Based on this post and others,
you clearly have a lot of interesting AutoHotkey knowledge!
Thanks! My usage of Acc in my personal scripts is pretty basic ("DoDefaultAction" and using it to retrieve the content of DirectUI controls that ControlGetText won't work on), so it's great to see your posts as something I can look at should I ever do more with the library :-)
When using message 0x400 to trigger a hotkey
it appears that the type of hook does not matter:
$ doesn't matter,
#UseHook doesn't matter,
reg / k-hook / m-hook / 2-hooks doesn't matter.
All hotkeys can be triggered by this method.
Nice work, thanks - I wouldn't have thought to look there and it's good to see that posting WM_HOTKEY can be done away with :oops:
The number wParam corresponds to the items in the list in
'View, Hotkeys and their methods'.
To trigger the first item: set wParam := 0,
to trigger the nth item: set wParam := n-1.

[...]

If I could grab a list of a script's hotkeys remotely,
that would give a way to work out the required wParam.
Actually, putting the 'check hotkeys are working' hotkey
as the very first hotkey in my scripts would be sufficient for my needs,
and so I would thus use wParam := 0.
I wouldn't know how to retrieve a hotkey's context, but if your needs become a little more advanced, you might be able to rig something up with ScriptInfo and a way to trigger that on demand (and send the results) - maybe with something like ObjRegisterActive or the fourth example from here might work for that...
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Test whether another script's hotkeys are still working

08 Feb 2017, 06:36

Looks like I've got everything I need now,
to trigger a hotkey by its label in any script.
Note: the hotkey will not trigger
unless the active window matches its #IfWin criteria.

ScriptInfo() gets info only from the current script,
but if you specify a different hWnd, it works
for external scripts too!
Which is what I did in JEE_AHKWinGetScriptInfo below.

Note: I don't know if anything can be done regarding *hotstrings*,
they do not appear in the ListHotkeys list,
although obviously you could change a hotstring from single line
to multiline, and add an obscure hotkey label above/below the hotstring label.

Code: Select all

;q:: ;trigger a hotkey in another script (or in the current script)
vHotkey := "w"
DetectHiddenWindows, % "On"
WinGet, hWnd, ID, %A_ScriptDir%\AutoHotkey.ahk ahk_class AutoHotkey
JEE_AHKWinTriggerHotkey(hWnd, vHotkey)
Return

;==================================================

;note: the hotkey will not trigger
;unless the active window matches its #IfWin criteria

JEE_AHKWinTriggerHotkey(hWnd, vHotkey)
{
if (hWnd = "")
hWnd := A_ScriptHwnd

vDHW := A_DetectHiddenWindows
DetectHiddenWindows, % "On"
vList := JEE_AHKWinGetScriptInfo(hWnd, "ListHotkeys")
vPos := InStr(vList, "`t" vHotkey "`r")
vList2 := SubStr(vList, 1, vPos)
StrReplace(vList2, "`n", "`n", vCount)
PostMessage, 0x400, % vCount-2, , , ahk_id %hWnd% ;WM_USER
DetectHiddenWindows, % vDHW
Return
}

;==================================================

;based on:
;ScriptInfo(): Get ListLines/ListVars/ListHotkeys/KeyHistory text - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?t=9656
;by lexikos

;Command can be one of: ListLines,ListVars,ListHotkeys,KeyHistory
JEE_AHKWinGetScriptInfo(hWnd, Command)
{
    if (hWnd = "")
    hWnd := A_ScriptHwnd

    hEdit := 0, pfn, bkp
    if !hEdit {
        hEdit := DllCall("GetWindow", "ptr", hWnd, "uint", 5, "ptr")
        user32 := DllCall("GetModuleHandle", "str", "user32.dll", "ptr")
        pfn := [], bkp := []
        for i, fn in ["SetForegroundWindow", "ShowWindow"] {
            pfn[i] := DllCall("GetProcAddress", "ptr", user32, "astr", fn, "ptr")
            DllCall("VirtualProtect", "ptr", pfn[i], "ptr", 8, "uint", 0x40, "uint*", 0)
            bkp[i] := NumGet(pfn[i], 0, "int64")
        }
    }

    if (A_PtrSize=8) {  ; Disable SetForegroundWindow and ShowWindow.
        NumPut(0x0000C300000001B8, pfn[1], 0, "int64")  ; return TRUE
        NumPut(0x0000C300000001B8, pfn[2], 0, "int64")  ; return TRUE
    } else {
        NumPut(0x0004C200000001B8, pfn[1], 0, "int64")  ; return TRUE
        NumPut(0x0008C200000001B8, pfn[2], 0, "int64")  ; return TRUE
    }

    static cmds := {ListLines:65406, ListVars:65407, ListHotkeys:65408, KeyHistory:65409}
    cmds[Command] ? DllCall("SendMessage", "ptr", hWnd, "uint", 0x111, "ptr", cmds[Command], "ptr", 0) : 0

    NumPut(bkp[1], pfn[1], 0, "int64")  ; Enable SetForegroundWindow.
    NumPut(bkp[2], pfn[2], 0, "int64")  ; Enable ShowWindow.

    ControlGetText, text,, ahk_id %hEdit%
    return text
}

;==================================================
Last edited by jeeswg on 08 Feb 2017, 12:19, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 11:19

You might want to fix the DllCall to SendMessage: it's still using A_ScriptHwnd. There's also an issue with running it on an AutoHotkey HWND not belonging to the caller - it can't stop the script window from showing (this is why I suggested adding ScriptInfo to the target scripts directly, although I guess if you know what you're doing with WriteProcessMemory - I don't - then that's an option instead)

EDIT: And, also, you might want to reconsider leaving hEdit static
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 12:25

(I fixed the second appearance of 'A_ScriptHwnd',
and the first appearance of 'static'.)

There are some examples here under JEE_TreeviewItemGetText
for WriteProcessMemory:
GUI COMMANDS: COMPLETE RETHINK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=25893

Although perhaps it's actually injecting a dll that would be required.
Personally I don't mind if the scripts pop up in this case,
because I want this for the occasional reloading of external scripts.
However it might be worth investigating to see if
external AHK windows can be successfully suppressed.

[EDIT:]
In my tests, it appeared that the external AHK windows were being suppressed, and it was getting the right text, but this was because the external Edit controls were already showing the hotkeys list.
Last edited by jeeswg on 27 May 2017, 12:24, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 18:25

jeeswg wrote: However it might be worth investigating to see if
external AHK windows can be successfully suppressed.
This was an exercise in pain, but this can disable the window remotely:

Code: Select all

#noenv
#singleinstance force

ScriptInfo(Command, hWnd := 0)
{
    if (!hWnd)
		hWnd := A_ScriptHwnd

	if (hWnd != A_ScriptHwnd) {
		prevDetectHiddenWindows := A_DetectHiddenWindows
		DetectHiddenWindows On
		hWnd := WinExist("ahk_id " . hWnd . " ahk_class AutoHotkey")
		if (!hWnd) {
			DetectHiddenWindows %prevDetectHiddenWindows%
			return ""
		}

		WinGet, PID, PID, ahk_id %hWnd%
		DetectHiddenWindows %prevDetectHiddenWindows%
		if (!(hProcess := DllCall("OpenProcess", "UInt", PROCESS_QUERY_INFORMATION := 0x0400 | PROCESS_VM_OPERATION := 0x0008 | PROCESS_VM_READ := 0x0010 | PROCESS_VM_WRITE := 0x0020, "Int", False, "UInt", PID, "Ptr")))
			return ""

		; LIST_MODULES_DEFAULT (0x00) is probably better here
		Loop {
			DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", 0, "UInt", 0, "UInt*", cbNeeded, "UInt", LIST_MODULES_ALL := 0x03)
			VarSetCapacity(hModules, cbNeeded, 0)
		} until (DllCall("psapi\EnumProcessModulesEx", "Ptr", hProcess, "Ptr", &hModules, "UInt", cbNeeded, "UInt*", cbNeeded, "UInt", LIST_MODULES_ALL := 0x03))			

		VarSetCapacity(modName, 524)
		Loop % cbNeeded / A_PtrSize {
			if (DllCall("psapi\GetModuleBaseName", "Ptr", hProcess, "Ptr", NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr"), "Str", modName, "UInt", 260))
				if (modName = "user32.dll") {
					user32 := NumGet(hModules, A_PtrSize * (A_Index - 1), "Ptr")
					break
				}
		}
	} else {
		user32 := DllCall("GetModuleHandle", "str", "user32.dll", "ptr")
	}

	if (!user32) {
		if (hProcess)
			LogonDesktop_CloseHandle(hProcess)
		return ""
	}

	hEdit := DllCall("GetWindow", "ptr", hWnd, "uint", 5, "ptr")
	pfn := [], bkp := []
	for i, fn in ["SetForegroundWindow", "ShowWindow"] {
		if (A_ScriptHwnd == hWnd) {
			pfn[i] := DllCall("GetProcAddress", "ptr", user32, "astr", fn, "ptr")
			DllCall("VirtualProtect", "ptr", pfn[i], "ptr", 8, "uint", 0x40, "uint*", 0)
			bkp[i] := NumGet(pfn[i], 0, "int64")
		} else {
			pfn[i] := ProcAddressFromRemoteProcess(hProcess, user32, fn, Magic)
			DllCall("VirtualProtectEx", "Ptr", hProcess, "Ptr", pfn[i], "ptr", 8, "uint", 0x40, "uint*", 0)
			DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", pfn[i], "Int64*", tmp, "UInt", 8, "UInt*", br)
			bkp[i] := tmp
		}
	}

	if (hWnd == A_ScriptHwnd) {
		if (A_PtrSize=8) {  ; Disable SetForegroundWindow and ShowWindow.
			NumPut(0x0000C300000001B8, pfn[1], 0, "int64")  ; return TRUE
			NumPut(0x0000C300000001B8, pfn[2], 0, "int64")  ; return TRUE
		} else {
			NumPut(0x0004C200000001B8, pfn[1], 0, "int64")  ; return TRUE
			NumPut(0x0008C200000001B8, pfn[2], 0, "int64")  ; return TRUE
		}
	} else {
		if (Magic == (IMAGE_NT_OPTIONAL_HDR64_MAGIC := 0x20b)) {
			DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[1], "Int64*", 0x0000C300000001B8, "UInt", 8, "UInt*", br)
			DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[2], "Int64*", 0x0000C300000001B8, "UInt", 8, "UInt*", br)
		} else {
			DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[1], "Int64*", 0x0004C200000001B8, "UInt", 8, "UInt*", br)
			DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[2], "Int64*", 0x0008C200000001B8, "UInt", 8, "UInt*", br)
		}
	}

    static cmds := {ListLines:65406, ListVars:65407, ListHotkeys:65408, KeyHistory:65409}
    cmds[Command] ? DllCall("SendMessage", "ptr", hWnd, "uint", 0x111, "ptr", cmds[Command], "ptr", 0) : 0

	if (hWnd == A_ScriptHwnd) {
		NumPut(bkp[1], pfn[1], 0, "int64")  ; Enable SetForegroundWindow.
		NumPut(bkp[2], pfn[2], 0, "int64")  ; Enable ShowWindow.
	} else {
		DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[1], "Int64*", bkp[1], "UInt", 8, "UInt*", br)
		DllCall("WriteProcessMemory", "Ptr", hProcess, "Ptr", pfn[2], "Int64*", bkp[2], "UInt", 8, "UInt*", br)
		LogonDesktop_CloseHandle(hProcess)
	}

    ControlGetText, text,, ahk_id %hEdit%
    return text
}

; Very little error checking. TBH, I'd be surprised if someone actually uses this, so...
ProcAddressFromRemoteProcess(hProcess, hModule, targetFuncName, ByRef Magic := 0)
{
	; MarkHC: https://www.unknowncheats.me/forum/1457119-post3.html
	IMAGE_DOS_SIGNATURE := 0x5A4D, IMAGE_NT_SIGNATURE := 0x4550
	if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule, "UShort*", header, "UInt", 2, "UInt*", br) && br == 2 && header == IMAGE_DOS_SIGNATURE) {
		if (DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+60, "Int*", e_lfanew, "UInt", 4, "UInt*", br) && DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew, "UInt*", Signature, "UInt", 4, "UInt*", br)) {
			if (Signature == IMAGE_NT_SIGNATURE) {
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24, "UShort*", Magic, "UInt", 2, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+e_lfanew+24+(Magic == (IMAGE_NT_OPTIONAL_HDR64_MAGIC := 0x20b) ? 112 : 96), "UInt*", exportTableRVA, "UInt", 4, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+20, "UInt*", NumberOfFunctions, "UInt", 4, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+24, "UInt*", NumberOfNames, "UInt", 4, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+28, "UInt*", AddressOfFunctions, "UInt", 4, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+32, "UInt*", AddressOfNames, "UInt", 4, "UInt*", br)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+exportTableRVA+36, "UInt*", AddressOfNameOrdinals, "UInt", 4, "UInt*", br)
				
				VarSetCapacity(functions, NumberOfFunctions * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfFunctions, "Ptr", &functions, "UInt", NumberOfFunctions * 4, "UInt*", br)
				VarSetCapacity(exports, NumberOfNames * 4)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNames, "Ptr", &exports, "UInt", NumberOfNames * 4, "UInt*", br)
				VarSetCapacity(ordinals, NumberOfNames * 2)
				DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+AddressOfNameOrdinals, "Ptr", &ordinals, "UInt", NumberOfNames * 2, "UInt*", br)
				
				Loop % NumberOfNames {
					addr := NumGet(exports, 4 * (A_Index - 1), "UInt")
					i := 0, funcName := ""
					while (true) {
						DllCall("ReadProcessMemory", "Ptr", hProcess, "Ptr", hModule+addr+i, "Int*", letter, "UInt", 1, "UInt*", br)
						if (!letter)
							break
						funcName .= Chr(letter)
						i += 1
					}
					if (funcName == targetFuncName) {
						ordinal := NumGet(ordinals, 2 * (A_Index - 1), "UShort")
						return NumGet(functions, 4 * ordinal, "UInt") + hModule
					}
				}
			}
		}
	}
	return 0
}

LogonDesktop_AdjustThisProcessPrivileges(privNames, ByRef PreviousState := 0) {
	sizeofTOKEN_PRIVILEGES := 16
	ret := False

	if (IsObject(privNames)) {
		if (!(privNamesOrPrevStateLen := privNames.SetCapacity(0)))
			return ret

		VarSetCapacity(TOKEN_PRIVILEGES, (sizeTp := 4 + ((sizeofTOKEN_PRIVILEGES - 4) * privNamesOrPrevStateLen)), 0)
		NumPut(privNamesOrPrevStateLen, TOKEN_PRIVILEGES,, "UInt")
		for priv, enabled in privNames {
			luidOffset := 4 + ((sizeofTOKEN_PRIVILEGES - 4) * (A_Index - 1))
			if (DllCall("Advapi32\LookupPrivilegeValueW", "Ptr", 0, "WStr", priv, "Ptr", &TOKEN_PRIVILEGES+luidOffset))
				if (enabled)
					NumPut(0x00000002, TOKEN_PRIVILEGES, luidOffset+8, "UInt")
		}
	} else {
		if (!privNames && !IsByRef(PreviousState))
			return ret
	}

	if (LogonDesktop_OpenProcessToken(_GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES := 0x0020 | TOKEN_QUERY := 0x0008 , hToken)) {
		psBr := IsByRef(PreviousState)
		if (privNames) {
			if (psBr)
				VarSetCapacity(PreviousState, sizeTp, 0)
			ret := DllCall("Advapi32\AdjustTokenPrivileges", "Ptr", hToken, "Int", False, "Ptr", &TOKEN_PRIVILEGES, "UInt", psBr ? 0 : sizeTp, "Ptr", psBr ? &PreviousState : 0, "UInt*", ReturnLength)
			if (!ret && psBr && ReturnLength && A_LastError == (ERROR_INSUFFICIENT_BUFFER := 122)) {
				VarSetCapacity(PreviousState, ReturnLength)
				ret := DllCall("Advapi32\AdjustTokenPrivileges", "Ptr", hToken, "Int", False, "Ptr", &TOKEN_PRIVILEGES, "UInt", ReturnLength, "Ptr", &PreviousState, "UInt*", ReturnLength)
			}
		} else {
			if (psBr && PreviousState)
				ret := DllCall("Advapi32\AdjustTokenPrivileges", "Ptr", hToken, "Int", False, "Ptr", &PreviousState, "UInt", 0, "Ptr", 0, "Ptr", 0)
		}
		LogonDesktop_CloseHandle(hToken)
	}

	return ret
}

_GetCurrentProcess() {
	return DllCall("GetCurrentProcess", "Ptr")
}

LogonDesktop_OpenProcessToken(ProcessHandle, DesiredAccess, ByRef TokenHandle) {
	return DllCall("Advapi32\OpenProcessToken", "Ptr", ProcessHandle, "UInt", DesiredAccess, "Ptr*", TokenHandle)
}

LogonDesktop_CloseHandle(hObject) {
	return DllCall("CloseHandle", "Ptr", hObject)
}

LogonDesktop_AdjustThisProcessPrivileges({"SeDebugPrivilege": True}, PreviousState)
DetectHiddenWindows, % "On"
WinGet, hWnd, ID, %A_ScriptDir%\New AutoHotkey Script.ahk
MsgBox % ScriptInfo("ListHotkeys", hWnd)
LogonDesktop_AdjustThisProcessPrivileges(0, PreviousState)
The following combinations work:

U64 ScriptInfo.ahk - U64 AutoHotkey process to remotely patch = OK
U64 - U32 = OK
U32 - U32 = OK
U32 - U64 = No
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 19:20

Well done, when I saw VirtualProtectEx I thought it might be doable without dll injection.
My problem was that GetModuleHandle and VirtualProtect
can only be used by the calling process.
You found EnumProcessModulesEx, I'm not sure how you found that,
and then you found VirtualProtectEx as well.
Omg, scrolling down it gets worse and worse,
I'm suffering just reading it.

I had thought perhaps the addresses for the functions,
could be established by reading the memory for the mapped dll files,
and doing a search with wOxxOm's InBuf (or something like that).
So that perhaps GetModuleHandle would not be needed.
I'm still working on it ... and will post code eventually.
(Btw it didn't matter what script was used I kept getting the
same addresses. Is it as simple as a copy of the dll verbatim
mapped to the address space?
I noticed 5 regions (not 1) corresponding to user32.dll.)
Nice to see it's doable.
Am I being over-optimistic somewhere,
in terms of getting some simple code.
Very well done on your code. It's horrible
when things get this complicated!
It's been one of the first time-stealers I've had
in a while!

I've learnt something new I think,
VirtualProtect changes the protection status of a page/pages,
rather than a region.
A bit unclear:
'Changes the protection on a region of committed pages
in the virtual address space of the calling process.'

Did my GUI examples help at all btw?
Many thanks, is this 'ScriptInfo Plus' and hotkey triggering capability
going to be to useful to you?

[EDIT:]
Oh my gosh and the 32/64 to 32/64 issue ... eek.
Will think about.

[EDIT 2:]
I did a read through, there was some stuff about privileges,
did you write to handle a situation where the internal script is not admin,
and the external script is admin (or something like that)?
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 20:16

Thanks,
jeeswg wrote: You found EnumProcessModulesEx, I'm not sure how you found that,
and then you found VirtualProtectEx as well.
The former I found out from reading StackOverflow and I knew of the latter's existence, but I just never had a reason to use it before...
Omg, scrolling down it gets worse and worse,
I'm suffering just reading it.
Yeah, it is really messy. Some of it is because of my coding "style" and I guess some of it is to do with it not being a simple task :-(
I had thought perhaps the addresses for the functions,
could be established by reading the memory for the mapped dll files,
and doing a search with wOxxOm's InBuf (or something like that).
So that perhaps GetModuleHandle would not be needed.
I'm still working on it ... and will post code eventually.
That would be interesting :thumbup:
(Btw it didn't matter what script was used I kept getting the
same addresses. Is it as simple as a copy of the dll verbatim
mapped to the address space?
I noticed 5 regions (not 1) corresponding to user32.dll.)
I also noticed user32 is always at the same base address no matter the AutoHotkey process. Maybe its location needs to be fixed, but honestly, I don't know why. At first, I was being lazy and just using the address from calling GetProcAddress in the same process, but I knew I'd have to go over the PE headers anyway if I was writing to a 32-bit process from a 64-bit one...
Very well done on your code. It's horrible
when things get this complicated!
It's been one of the first time-stealers I've had
in a while!
Thanks, I've never used ReadProcessMemory etc. before, so it was a learning experience!
VirtualProtect changes the protection status of a page/pages,
rather than a region. A bit unclear:
'Changes the protection on a region of committed pages
in the virtual address space of the calling process.'
Not actually knowing anything about OS internals, so odds are I'm wrong in all this (actual answers will be gratefully received), but I think a region contains pages. I think a committed page is memory that's actually available for use by the process. And VirtualProtect is needed because of DEP: if the area of memory has been marked as read-only and not executable, then writing to it and trying to run instructions from it will cause a crash AFAIK
Did my GUI examples help at all btw?
I did look for a bit to get an idea of the functions involved (thanks) and then I headed for MSDN and relied heavily on the lists of related functions there.
Many thanks, is this 'ScriptInfo Plus' and hotkey triggering capability
going to be to useful to you?
I might be able to incorporate it into a script I use to show ConEmu when I press the ThinkVantage key on my laptop - the only reliable way I found of showing ConEmu was to use AutoHotkey to Send the actual keyboard shortcut for it to display itself. The issue here is that if the active program is running with high integrity, then sending fails. The Lenovo program that listens for the ThinkVantage key press won't run an admin AutoHotkey instance/uiAccess enabled one (and I don't want to hook it to enable it in the token of the process it spawns), so I settled for having my always-running background AutoHotkey script perform the actual keypress, and the script the Lenovo service starts just posts a message to the background script (spawning so many process is slow :(). With this, I might be able to perform more stringent checks on determining whether my background script is hung.
Oh my gosh and the 32/64 to 32/64 issue ... eek.
Will think about.
Yeah, that would be interesting (if you actually have a need for a 32-bit process to write to the memory of a 64-bit one - I have to be honest, I personally didn't so I left it) - https://github.com/rwfpl/rewolf-wow64ext would probably help in the one scenario where this fails
I did a read through, there was some stuff about privileges,
did you write to handle a situation where the internal script is not admin,
and the external script is admin (or something like that)?
The privileges code I had already written for LogonDesktop (I couldn't find an AHK implementation that took advantage of AdjustTokenPrivileges' ability to manipulate more than one privilege and that stored the state of the token's privileges prior to me calling ATP), but now that you mention it, it can be removed to, going back to your point, make the code simpler. I put it in because I thought it might help in allowing a medium integrity process read and write the memory of a high integrity process (i.e. one that's elevated or has uiAccess abilities), but of course a limited token won't have SeDebugPrivilege present (duh :oops:)
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 20:59

Haha messy because of complexity, not because of style.

It's easy to loop through the regions and find
the User32 regions. The offsets where the 2 functions are
seem to be the same every time, but this would seem
reasonable as it is based on a dll that doesn't change.
The User32 regions are 'commit' and 'image' and GetMappedFileName
will reveal 'user32.dll'. Useful link for listing regions, use GetMappedFileName to get file names:
Read information about any application directly from RAM - Utilities - AutoHotkey Community
http://www.autohotkey.com/board/topic/3 ... -from-ram/

I think of it as regions (not pages) that are committed/reserved/free.
But I too could do with some lowdown.

Anecdotally, not definitely, I think that setting
a process to high priority, before reloading,
can help avoid the
'Could not close the previous instance of this script. Keep waiting?'
message, which is my least favourite thing in AutoHotkey.
Probably my one and only abiding negative about AHK,
which with these techniques I'm largely abolishing as a problem.

I've found that 32/64 to 32/64 communication,
isn't always particularly complicated (fingers crossed).

I hadn't really considered the IsAdmin issue, and
don't think I will. I never use IsAdmin scripts,
I even cloned RegEdit to avoid the 'pause, dark and beep'
moment. I also hope for a way to check for locked files
that doesn't use IsAdmin.
IsAdmin once a month for me probably.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

08 Feb 2017, 22:04

Btw is it possible to use VirtualProtect and WriteProcessMemory
to change 'AutoHotkeyGUI' in the address space (rather than the exe)?
I tried but it didn't seem to work.
Maybe because the user32.dll region was 'execute_read',
whereas the AutoHotkeyU32.exe region was 'readonly'.

A_LastError returned 998:
System Error Codes (500-999) (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

ERROR_NOACCESS998 (0x3E6)
Invalid access to memory location.

Or maybe it's because:
VirtualProtect function (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

For mapped views, this value must be compatible with the access protection specified when the view was mapped (see MapViewOfFile, MapViewOfFileEx, and MapViewOfFileExNuma).

Or maybe I'm just doing something wrong.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

09 Feb 2017, 13:24

jeeswg wrote:It's easy to loop through the regions and find
the User32 regions. The offsets where the 2 functions are
seem to be the same every time, but this would seem
reasonable as it is based on a dll that doesn't change.
The User32 regions are 'commit' and 'image' and GetMappedFileName
will reveal 'user32.dll'. Useful link for listing regions, use GetMappedFileName to get file names:
Read information about any application directly from RAM - Utilities - AutoHotkey Community
http://www.autohotkey.com/board/topic/3 ... -from-ram/
That's a good link, thanks for the info.
Anecdotally, not definitely, I think that setting
a process to high priority, before reloading,
can help avoid the
'Could not close the previous instance of this script. Keep waiting?'
message, which is my least favourite thing in AutoHotkey.
Probably my one and only abiding negative about AHK,
which with these techniques I'm largely abolishing as a problem.
I guess you could turn SingleInstance off, but I'm wondering what you're doing in your scripts to see that so often - the only time I ever I see the message is if I'm trying to run a single instance of a script that's already running elevated, but I expect that there.
I never use IsAdmin scripts,
I even cloned RegEdit to avoid the 'pause, dark and beep'
moment. I also hope for a way to check for locked files
that doesn't use IsAdmin.
IsAdmin once a month for me probably.
Heh, I have a total difference of opinion there; right now I have two AutoHotkey instances running as SYSTEM... In my case, overall, for my uses of AutoHotkey, running the scripts as admin where required is safer for me - I'd rather that than, say, allowing un-elevated me to open the WinRing0 driver that I use to make my microphone LED blink in lieu of an actual caps lock indicator.

Regarding locked files: IIRC, you don't have to be admin (Un-elevated and driverless Process Hacker's ability to find handles works quite well), it's just you won't be able to find out if a process with higher privileges is holding your file hostage.
jeeswg wrote:Btw is it possible to use VirtualProtect and WriteProcessMemory
to change 'AutoHotkeyGUI' in the address space (rather than the exe)?
I tried but it didn't seem to work.
Maybe because the user32.dll region was 'execute_read',
whereas the AutoHotkeyU32.exe region was 'readonly'.
Does VirtualProtect (or Ex if you primarily want to write to another process) succeed when you try it? Are you marking the same area you're writing to as PAGE_EXECUTE_READWRITE? Are you unprotecting an area of the same length of you're going to write? I don't see why it would fail, but this is all new to me (the only reason I understand what a C struct actually is is because of AutoHotkey) and I don't really know much here anyway.
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

16 Feb 2017, 16:56

Script to change the GUI command class name from 'AutoHotkeyGUI' to 'JEEGuiClass'.
The new class name must be a maximum of 13 characters long.
I did have a problem though, trying to change the class name multiple times, i.e. I can't have a new class name each time I create a GUI window.
It may or may not be possible.

Code: Select all

q:: ;create gui windows with custom class name
vClassNew := "JEEGuiClass"

;==================================================

if !vIsReady
{
	vIsReady := 1
	DetectHiddenWindows, On
	WinGet, vPID, PID, % "ahk_id " A_ScriptHwnd
	vClassOrig := "AutoHotkeyGUI"
	vPathExe := A_AhkPath
	SplitPath, vPathExe, vNameExe

	;==============================

	;find location of AutoHotkeyGUI string in the address space
	;PROCESS_QUERY_INFORMATION := 0x400
	;PROCESS_VM_READ := 0x10
	hProc := JEE_DCOpenProcess(0x410, 0, vPID)
	if !hProc
		return
	vSizeMBI := A_PtrSize=8?48:28
	VarSetCapacity(vMBI, vSizeMBI, 0)

	Loop
	{
		(A_Index = 1) ? (vAddress := 0) : (vAddress += vMbiRegionSize)
		;if (vAddress > 2**32-1) ;4GB - 1
		if (vAddress > 0x7ffffffffff)
			break
		if !DllCall("kernel32\VirtualQueryEx", Ptr,hProc, Ptr,vAddress, Ptr,&vMBI, UPtr,vSizeMBI, UPtr)
			break

		vMbiBaseAddress := NumGet(&vMBI, 0, "Ptr")
		vMbiAllocationBase := NumGet(&vMBI, A_PtrSize=8?8:4, "Ptr")
		vMbiAllocationProtect := NumGet(&vMBI, A_PtrSize=8?16:8, "UInt")
		vMbiRegionSize := NumGet(&vMBI, A_PtrSize=8?24:12, "UPtr")
		vMbiState := NumGet(&vMBI, A_PtrSize=8?32:16, "UInt")
		vMbiProtect := NumGet(&vMBI, A_PtrSize=8?36:20, "UInt")
		vMbiType := NumGet(&vMBI, A_PtrSize=8?40:24, "UInt")

		;note: path returned usually (always?) lacks the drive
		vPath := ""
		VarSetCapacity(vPath, 520)
		DllCall("psapi\GetMappedFileName", Ptr,hProc, Ptr,vMbiBaseAddress, Str,vPath, UInt,520, UInt)
		SplitPath, vPath, vName, vDir, vExt, vNameNoExt, vDrive

		if (vMbiState & 0x1000) ;MEM_COMMIT := 0x1000
		&& (vMbiType & 0x1000000) ;MEM_IMAGE := 0x1000000
		&& (vName = vNameExe)
		{
			VarSetCapacity(vData, vMbiRegionSize, 1)
			JEE_DCReadProcessMemory(hProc, vMbiBaseAddress, &vData, vMbiRegionSize, 0)

			vPos := (RegExMatch(vData, vClassOrig) - 1) * 2
			vAddress2 := vMbiBaseAddress+vPos
			if !(vPos = -2)
				break
		}
	}

	JEE_DCCloseHandle(hProc)

	;==============================

	vAddress := vAddress2
	MsgBox, % vAddress

	;PAGE_EXECUTE_READWRITE := 0x40
	DllCall("kernel32\VirtualProtect", Ptr,vAddress, UPtr,26, UInt,0x40, UIntP,0)

	;PROCESS_VM_WRITE := 0x20
	;PROCESS_VM_OPERATION := 0x8
	hProc := JEE_DCOpenProcess(0x28, 0, vPID)
	VarSetCapacity(vData, 26, 0)
	vData := vClassNew
	JEE_DCWriteProcessMemory(hProc, vAddress, &vData, 26, 0)
	JEE_DCCloseHandle(hProc)
}

Gui, New, hwndhGui
Gui, Add, Text, w300 h300
Gui, Show

WinGetClass, vWinClass, % "ahk_id " hGui
MsgBox, % "hWnd: " hGui "`r`nclass: " vWinClass
return

;==================================================

JEE_DCOpenProcess(vAccess, hInherit, vPID)
{
	return DllCall("kernel32\OpenProcess", UInt,vAccess, Int,hInherit, UInt,vPID, Ptr)
}
JEE_DCVirtualAllocEx(hProc, vAddress, vSize, vAllocType, vProtect)
{
	return DllCall("kernel32\VirtualAllocEx", Ptr,hProc, Ptr,vAddress, UPtr,vSize, UInt,vAllocType, UInt,vProtect, Ptr)
}
JEE_DCWriteProcessMemory(hProc, vBAddress, pBuf, vSize, vWritten)
{
	return DllCall("kernel32\WriteProcessMemory", Ptr,hProc, Ptr,vBAddress, Ptr,pBuf, UPtr,vSize, Ptr,vWritten)
}
JEE_DCReadProcessMemory(hProc, vBAddress, pBuf, vSize, vRead)
{
	return DllCall("kernel32\ReadProcessMemory", Ptr,hProc, Ptr,vBAddress, Ptr,pBuf, UPtr,vSize, Ptr,vRead)
}
JEE_DCVirtualFreeEx(hProc, vAddress, vSize, vFreeType)
{
	return DllCall("kernel32\VirtualFreeEx", Ptr,hProc, Ptr,vAddress, UPtr,vSize, UInt,vFreeType)
}
JEE_DCCloseHandle(hObject) ;e.g. hProc
{
	return DllCall("kernel32\CloseHandle", Ptr,hObject)
}

;==================================================
Last edited by jeeswg on 17 Feb 2019, 17:05, edited 2 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6648
Joined: 19 Dec 2016, 01:58
Location: UK

Re: test whether another script's hotkeys are still working (trigger hotkeys in another script)

27 May 2017, 12:31

So in summary it seems that you can use WM_USER := 0x400 to trigger a hotkey in another script.

If a hotkey has 'stopped working' (i.e. keypresses no longer trigger it), you can still trigger it using this method.

However, it seems that this method can't help you identify whether a hotkey has stopped working.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask For Help”

Who is online

Users browsing this forum: effel, flyingDman, paquirl, Rafio, rediffusion and 147 guests