As I learned in Native Hook using java.lang.foreign in JDK23, the third return value of SetWindowsHookEX,
"lParam", is
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
So I wrote a code to read it.
public static MemorySegment hookProc(int code, MemorySegment wParam, MemorySegment lParam) {
if (code >= 0) {
System.out.println("wParam address: " + wParam.address() + "\tlParam address: " + lParam.address());
long vkCode = lParam.get(ValueLayout.JAVA_INT, 0);
long scanCode = lParam.get(ValueLayout.JAVA_INT, 4);
long flags = lParam.get(ValueLayout.JAVA_INT, 8);
long time = lParam.get(ValueLayout.JAVA_INT, 12);
long dwExtraInfo = lParam.get(ValueLayout.JAVA_LONG, 16);
When I try to run it with the original code, a java.lang.IndexOutOfBoundsException occurs.
What should I do?
Error
Hook set successfully
wParam address: 256 lParam address: 400736383368
java.lang.IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ address: 0x5d4dbff188, byteSize: 0 }; new offset = 0; new length = 4
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.outOfBoundException(AbstractMemorySegmentImpl.java:439)
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.apply(AbstractMemorySegmentImpl.java:420)
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.apply(AbstractMemorySegmentImpl.java:70)
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:98)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:124)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:448)
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.checkBounds(AbstractMemorySegmentImpl.java:409)
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.checkAccess(AbstractMemorySegmentImpl.java:369)
at java.base/jdk.internal.foreign.LayoutPath.checkEnclosingLayout(LayoutPath.java:288)
at java.base/jdk.internal.foreign.AbstractMemorySegmentImpl.get(AbstractMemorySegmentImpl.java:777)
at hook_test.hookProc(hook_test.java:46)
at hook_test.main(hook_test.java:38)
Unrecoverable uncaught exception encountered. The VM will now exit
Full code
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class hook_test {
private static final int WH_KEYBOARD_LL = 13;
private static final int WM_KEYDOWN = 0x0100;
private static MemorySegment hook;
private static MethodHandle callNextHookEx;
public static void main(String[] args) throws Throwable {
System.loadLibrary("user32");
Linker linker = Linker.nativeLinker();
SymbolLookup user32Lookup = SymbolLookup.loaderLookup();
MethodHandle setWindowsHookEx = linker.downcallHandle(user32Lookup.find("SetWindowsHookExA").orElseThrow(), FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
callNextHookEx = linker.downcallHandle(user32Lookup.find("CallNextHookEx").orElseThrow(), FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS));
MethodHandle getMessage = linker.downcallHandle(user32Lookup.find("GetMessageA").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
MethodHandle unhookWindowsHookEx = linker.downcallHandle(user32Lookup.find("UnhookWindowsHookEx").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
MethodHandle hookProcHandle = MethodHandles.lookup().findStatic(hook_test.class, "hookProc", MethodType.methodType(MemorySegment.class, int.class, MemorySegment.class, MemorySegment.class));
MemorySegment hookProcAddress = linker.upcallStub(hookProcHandle, FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS), Arena.ofAuto());
hook = (MemorySegment) setWindowsHookEx.invoke(WH_KEYBOARD_LL, hookProcAddress, MemorySegment.NULL, 0);
if (hook.equals(MemorySegment.NULL)) {
System.out.println("Failed to set hook");
return;
} else
System.out.println("Hook set successfully");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
if (!hook.equals(MemorySegment.NULL))
unhookWindowsHookEx.invoke(hook);
} catch (Throwable t) {
System.err.println(t.getMessage());
}
}));
try (Arena arena = Arena.ofAuto()) {
MemorySegment msg = arena.allocate(28);
while ((int) getMessage.invoke(msg, MemorySegment.NULL, 0, 0) != 0) {
}
}
}
public static MemorySegment hookProc(int code, MemorySegment wParam, MemorySegment lParam) {
if (code >= 0) {
System.out.println("wParam address: " + wParam.address() + "\tlParam address: " + lParam.address());
long vkCode = lParam.get(ValueLayout.JAVA_INT, 0);
long scanCode = lParam.get(ValueLayout.JAVA_INT, 4);
long flags = lParam.get(ValueLayout.JAVA_INT, 8);
long time = lParam.get(ValueLayout.JAVA_INT, 12);
long dwExtraInfo = lParam.get(ValueLayout.JAVA_LONG, 16);
System.out.printf("vkCode: %d, scanCode: %d, flags: %d, time: %d, dwExtraInfo: %d\n", vkCode, scanCode, flags, time, dwExtraInfo);
if (wParam != MemorySegment.NULL && wParam.byteSize() >= ValueLayout.JAVA_INT.byteSize()) {
long wParamValue = wParam.get(ValueLayout.JAVA_INT, 0);
if (wParamValue == WM_KEYDOWN)
System.out.printf("int code:%s\tlong wParam:%s\n", code, wParamValue);
} else
System.err.println("Invalid wParam segment size or null segment.");
}
try {
return (MemorySegment) callNextHookEx.invoke(hook, code, wParam, lParam);
} catch (Throwable t) {
System.err.println(t.getMessage());
return MemorySegment.NULL;
}
}
}
environment
Oracle OpenJDK 23.0.1
23(Preview)
IntelliJ IDEA 2024.2.1 (Community Edition)
Build #IC-242.21829.142, built on August 29, 2024
Runtime version: 21.0.3+13-b509.11 amd64 (JCEF 122.1.9)
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Toolkit: sun.awt.windows.WToolkit
Windows 11.0
GC: G1 Young Generation, G1 Concurrent GC, G1 Old Generation
Memory: 2014M
Cores: 12
Registry:
debugger.attach.dialog.enabled=true
documentation.show.toolbar=true
ide.experimental.ui=true
i18n.locale=
terminal.new.ui.show.promotion=false
org.toml.json.schema=false
Non-Bundled Plugins:
color.scheme.Eclipse Dark Theme (1.4)
Lombook Plugin (242.20224.331)
org.teavm.idea (0.10.1)
Kotlin: 242.21829.142-IJ
int b = 24;
MemorySegment surroundingSegment = MemorySegment.ofAddress(lParam.address()).reinterpret(b);
String[] NativeRawMemoryStrings = new String[b];
println("Surrounding memory PID:" + getMyPID() + "," + Integer.toHexString(getMyPID()) + "\tAddress:" + lParam.address() + "," + Long.toHexString(lParam.address()) + "\t(hex):");
for (int i = 0; i < b; i++) {
NativeRawMemoryStrings[i] = format("%02X ", surroundingSegment.get(ValueLayout.JAVA_BYTE, i));
}
long wParam_Long = wParam.address();
String vkCode16_Str = (NativeRawMemoryStrings[3] + NativeRawMemoryStrings[2] + NativeRawMemoryStrings[1] + NativeRawMemoryStrings[0]).replaceAll("\\s+", "");
String scanCode16_Str = (NativeRawMemoryStrings[7] + NativeRawMemoryStrings[6] + NativeRawMemoryStrings[5] + NativeRawMemoryStrings[4]).replaceAll("\\s+", "");
String flag16_Str = (NativeRawMemoryStrings[11] + NativeRawMemoryStrings[10] + NativeRawMemoryStrings[9] + NativeRawMemoryStrings[8]).replaceAll("\\s+", "");
String time16_Str = (NativeRawMemoryStrings[15] + NativeRawMemoryStrings[14] + NativeRawMemoryStrings[13] + NativeRawMemoryStrings[12]).replaceAll("\\s+", "");
String dwExtraInfo16_64_Str = (NativeRawMemoryStrings[23] + NativeRawMemoryStrings[22] + NativeRawMemoryStrings[21] + NativeRawMemoryStrings[20] + NativeRawMemoryStrings[19] + NativeRawMemoryStrings[18] + NativeRawMemoryStrings[17] + NativeRawMemoryStrings[16]).replaceAll("\\s+", "");
String dwExtraInfo16_32_Str = (NativeRawMemoryStrings[19] + NativeRawMemoryStrings[18] + NativeRawMemoryStrings[17] + NativeRawMemoryStrings[16]).replaceAll("\\s+", "");