Fix GetProcAddress issue on ARM64 (#5)

See [TeamTofuShop/segatools/issues/20](https://gitea.tendokyu.moe/TeamTofuShop/segatools/issues/20).

This PR adds `pe_is_arm64` for detecting ARM64 systems and `pe_get_arm64_real_proc` for retrieving the real thread address.

I'm still getting familiar with the project, so I’d really appreciate any feedback, suggestions, or corrections.

Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/capnhook/pulls/5
Co-authored-by: oldkingOK <oldkingok@qq.com>
Co-committed-by: oldkingOK <oldkingok@qq.com>
This commit is contained in:
oldkingOK
2026-08-10 15:38:40 +00:00
committed by Dniel97
parent 25be44b13f
commit e45e2ed352
3 changed files with 67 additions and 0 deletions
+62
View File
@@ -220,3 +220,65 @@ HRESULT pe_patch(void *dest, const void *src, size_t nbytes)
return S_OK; return S_OK;
} }
BOOL pe_is_arm64()
{
typedef BOOL (WINAPI *LPFN_IsWow64Process2)(HANDLE, USHORT *, USHORT *);
LPFN_IsWow64Process2 is_wow64_process2;
USHORT process_machine;
USHORT native_machine;
HMODULE k32;
BOOL is_arm64;
BOOL ok;
is_arm64 = FALSE;
k32 = GetModuleHandleW(L"kernel32.dll");
if (k32 == NULL) {
goto end;
}
is_wow64_process2 =
(LPFN_IsWow64Process2) GetProcAddress(k32, "IsWow64Process2");
if (is_wow64_process2 == NULL) {
goto end;
}
ok = is_wow64_process2(GetCurrentProcess(),
&process_machine,
&native_machine);
if (!ok) {
goto end;
}
is_arm64 = native_machine == IMAGE_FILE_MACHINE_ARM64;
end:
return is_arm64;
}
/* ARM64EC Fast-Forward Sequence thunks.
When x64 code calls GetProcAddress on an Arm64X hybrid PE, the returned
pointer is an x64 trampoline rather than the real ARM64EC function. Each
thunk is a canonical x64 prolog ending in a near JMP to the real target.
We read the JMP's displacement and skip past the thunk body.
Canonical prolog as of Win11 26H2 aarch64 (0xe bytes):
+0x0 488bc4 mov rax, rsp
+0x3 48895820 mov [rax+0x20], rbx
+0x7 55 push rbp
+0x8 5d pop rbp
+0x9 e9xxxxxxxx jmp rel32 ; +0xa = displacement */
#define X64_THUNK_JMP_DISP_OFFSET 0xa
#define X64_THUNK_SIZE 0xe
void *pe_thunk_resolve(FARPROC proc)
{
INT32 offset;
assert(proc != NULL);
memcpy(&offset, (BYTE *)proc + X64_THUNK_JMP_DISP_OFFSET, sizeof(offset));
return (BYTE *)proc + X64_THUNK_SIZE + offset;
}
+2
View File
@@ -25,3 +25,5 @@ HRESULT pe_iid_get_iat_entry(
void *pe_get_export(HMODULE pe, const char *name, uint16_t ord); void *pe_get_export(HMODULE pe, const char *name, uint16_t ord);
void *pe_get_entry_point(HMODULE pe); void *pe_get_entry_point(HMODULE pe);
HRESULT pe_patch(void *dest, const void *src, size_t nbytes); HRESULT pe_patch(void *dest, const void *src, size_t nbytes);
BOOL pe_is_arm64();
void *pe_thunk_resolve(FARPROC proc);
+3
View File
@@ -73,6 +73,9 @@ static HRESULT process_hijack_try_thread(
exe_entry = pe_get_entry_point(exe); exe_entry = pe_get_entry_point(exe);
ntstart = GetProcAddress(ntdll, "RtlUserThreadStart"); ntstart = GetProcAddress(ntdll, "RtlUserThreadStart");
if (pe_is_arm64()) {
ntstart = pe_thunk_resolve(ntstart);
}
if (ntstart == NULL) { if (ntstart == NULL) {
/* TODO Deal with WinXP, for the poor souls still stuck on that OS. /* TODO Deal with WinXP, for the poor souls still stuck on that OS.