summaryrefslogtreecommitdiff
path: root/hook
diff options
context:
space:
mode:
authorGravatar Matthew Wozniak <me@woz.blue> 2025-08-11 18:17:40 -0400
committerGravatar Matthew Wozniak <me@woz.blue> 2025-08-11 20:46:00 -0400
commit89055b019e2e2d49f8813d3578f6bc338326ca47 (patch)
treecdd097e134454937ccd35324b55d4055c2c2f4ed /hook
downloadrt2-89055b019e2e2d49f8813d3578f6bc338326ca47.tar.gz
rt2-89055b019e2e2d49f8813d3578f6bc338326ca47.zip
intial commitHEADmaster
Diffstat (limited to 'hook')
-rw-r--r--hook/LICENSE12
-rw-r--r--hook/build.zig21
-rw-r--r--hook/build.zig.zon12
-rw-r--r--hook/src/Hook.zig237
-rw-r--r--hook/src/HookManager.zig80
-rw-r--r--hook/src/mem.zig199
-rw-r--r--hook/src/utils.zig198
-rw-r--r--hook/src/x86.zig670
-rw-r--r--hook/src/zhook.zig9
9 files changed, 1438 insertions, 0 deletions
diff --git a/hook/LICENSE b/hook/LICENSE
new file mode 100644
index 0000000..ec7482e
--- /dev/null
+++ b/hook/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2025 evanlin96069
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
diff --git a/hook/build.zig b/hook/build.zig
new file mode 100644
index 0000000..6e13e7c
--- /dev/null
+++ b/hook/build.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const mod = b.addModule("hook", .{
+ .root_source_file = b.path("src/zhook.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+ if (target.result.os.tag == .windows)
+ mod.linkSystemLibrary("kernel32", .{});
+
+ const mod_tests = b.addTest(.{
+ .root_module = mod,
+ });
+ const run_mod_tests = b.addRunArtifact(mod_tests);
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&run_mod_tests.step);
+}
diff --git a/hook/build.zig.zon b/hook/build.zig.zon
new file mode 100644
index 0000000..f9c531b
--- /dev/null
+++ b/hook/build.zig.zon
@@ -0,0 +1,12 @@
+.{
+ .name = .hook,
+ .version = "0.0.0",
+ .fingerprint = 0xa45843556d3689d1, // Changing this has security and trust implications.
+ .minimum_zig_version = "0.15.0-dev.1149+4e6a04929",
+ .dependencies = .{},
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ },
+}
diff --git a/hook/src/Hook.zig b/hook/src/Hook.zig
new file mode 100644
index 0000000..7a8ca06
--- /dev/null
+++ b/hook/src/Hook.zig
@@ -0,0 +1,237 @@
+const std = @import("std");
+const builtin = @import("builtin");
+
+const x86 = @import("x86.zig");
+const utils = @import("utils.zig");
+
+const loadValue = @import("mem.zig").loadValue;
+
+const Hook = @This();
+
+const HookType = enum {
+ vmt,
+ detour,
+};
+
+const Rel32Patch = struct {
+ offset: u32, // offset into the trampoline
+ dest: u32, // absolute address rel32 points to
+ orig: u32, // original data in the instruction
+};
+
+// Windows doesn't use PIC
+const PICPatch = switch (builtin.os.tag) {
+ .linux => struct {
+ offset: u32, // offset into the original function
+ orig: u32, // original data in the instruction
+ },
+ .windows => void,
+ else => @compileError("Unsupported OS"),
+};
+
+const HookData = union(HookType) {
+ const HookVMTResult = struct {
+ vt: [*]*const anyopaque,
+ index: u32,
+ };
+
+ const HookDetourResult = struct {
+ func: [*]u8,
+ trampoline: []u8,
+ rel32_patch: ?Rel32Patch = null,
+ pic_patch: ?PICPatch = null,
+ };
+
+ vmt: HookVMTResult,
+ detour: HookDetourResult,
+};
+
+orig: ?*const anyopaque,
+data: HookData,
+
+pub fn hookVMT(vt: [*]*const anyopaque, index: usize, target: *const anyopaque) !Hook {
+ const orig: *const anyopaque = vt[index];
+ const entry_ptr: [*]u8 = @ptrCast(vt + index);
+
+ const bytes = std.mem.toBytes(target);
+ try utils.patchCode(entry_ptr, &bytes, 0b001); // restore to read-only
+
+ return Hook{
+ .orig = orig,
+ .data = .{
+ .vmt = .{
+ .vt = vt,
+ .index = index,
+ },
+ },
+ };
+}
+
+// Trampoline memory must have rwx permissions
+pub fn hookDetour(func: *anyopaque, target: *const anyopaque, trampoline: []u8) !Hook {
+ var mem: [*]u8 = @ptrCast(func);
+
+ // Hook the underlying thing if the function jmp immediately.
+ while (mem[0] == x86.Opcode.Op1IW.jmpiw) {
+ const offset = loadValue(u32, mem + 1);
+ mem = @ptrFromInt(@intFromPtr(mem + 5) +% offset);
+ }
+
+ var rel32_patch: ?Rel32Patch = null;
+ var pic_patch: ?PICPatch = null;
+
+ var len: usize = 0;
+ while (true) : (len += try x86.x86_len(mem + len)) {
+ if (len >= 5) break;
+
+ // No checks for rel16 at all. I don't think we will encounter them.
+ const op0 = mem[len];
+ switch (op0) {
+ x86.Opcode.Op1I8.jmpi8,
+ x86.Opcode.Op1I8.jcxz,
+ x86.Opcode.Op1I8.jo,
+ x86.Opcode.Op1I8.jno,
+ x86.Opcode.Op1I8.jb,
+ x86.Opcode.Op1I8.jnb,
+ x86.Opcode.Op1I8.jz,
+ x86.Opcode.Op1I8.jnz,
+ x86.Opcode.Op1I8.jna,
+ x86.Opcode.Op1I8.ja,
+ x86.Opcode.Op1I8.js,
+ x86.Opcode.Op1I8.jns,
+ x86.Opcode.Op1I8.jp,
+ x86.Opcode.Op1I8.jnp,
+ x86.Opcode.Op1I8.jl,
+ x86.Opcode.Op1I8.jnl,
+ x86.Opcode.Op1I8.jng,
+ x86.Opcode.Op1I8.jg,
+ => {
+ // TODO: Make it rel32 jump in the trampoline
+ return error.BadInstruction;
+ },
+
+ x86.Opcode.Op1IW.jmpiw,
+ x86.Opcode.Op1IW.call,
+ => {
+ const offset = loadValue(u32, mem + len + 1);
+ rel32_patch = .{
+ .offset = len + 1,
+ .dest = @intFromPtr(mem + len + 5) +% offset,
+ .orig = offset,
+ };
+
+ if (op0 == x86.Opcode.Op1IW.call and builtin.os.tag == .linux) {
+ // Look for PIC pattern:
+ // call __i686.get_pc_thunk.reg
+ // add reg, imm32
+ if (utils.matchPIC(mem + len)) |off| {
+ const imm32 = loadValue(u32, mem + len + off);
+ pic_patch = .{
+ .offset = len + off,
+ .orig = imm32,
+ };
+ }
+ }
+ },
+
+ x86.Opcode.op2_byte => {
+ const op1 = mem[len + 1];
+ switch (op1) {
+ x86.Opcode.Op2IW.joii,
+ x86.Opcode.Op2IW.jnoii,
+ x86.Opcode.Op2IW.jbii,
+ x86.Opcode.Op2IW.jnbii,
+ x86.Opcode.Op2IW.jzii,
+ x86.Opcode.Op2IW.jnzii,
+ x86.Opcode.Op2IW.jnaii,
+ x86.Opcode.Op2IW.jaii,
+ x86.Opcode.Op2IW.jsii,
+ x86.Opcode.Op2IW.jnsii,
+ x86.Opcode.Op2IW.jpii,
+ x86.Opcode.Op2IW.jnpii,
+ x86.Opcode.Op2IW.jlii,
+ x86.Opcode.Op2IW.jnlii,
+ x86.Opcode.Op2IW.jngii,
+ x86.Opcode.Op2IW.jgii,
+ => {
+ const offset = loadValue(u32, mem + len + 2);
+ rel32_patch = .{
+ .offset = len + 2,
+ .dest = @intFromPtr(mem + len + 6) +% offset,
+ .orig = offset,
+ };
+ },
+ else => {},
+ }
+ },
+ else => {},
+ }
+ }
+
+ const trampoline_size = len + 5;
+ if (trampoline.len < trampoline_size) {
+ return error.OutOfTrampoline;
+ }
+
+ @memcpy(trampoline[0..len], mem);
+ trampoline[len] = x86.Opcode.Op1IW.jmpiw;
+ const jmp1_offset: *align(1) u32 = @ptrCast(trampoline.ptr + len + 1);
+ jmp1_offset.* = @intFromPtr(mem + len) -% @intFromPtr(trampoline.ptr + len + 5);
+
+ if (rel32_patch) |r| {
+ const rel_patch: *align(1) u32 = @ptrCast(trampoline.ptr + r.offset);
+ rel_patch.* = r.dest -% (@intFromPtr(trampoline.ptr + r.offset + 4));
+ }
+
+ var detour: [5]u8 = undefined;
+ detour[0] = x86.Opcode.Op1IW.jmpiw;
+ const jmp2_offset: *align(1) u32 = @ptrCast(&detour[1]);
+ jmp2_offset.* = @intFromPtr(target) -% @intFromPtr(mem + 5);
+
+ try utils.patchCode(mem, detour[0..], 0b101);
+
+ if (builtin.os.tag == .linux) {
+ if (pic_patch) |p| {
+ const delta: u32 = @intFromPtr(trampoline.ptr) -% @intFromPtr(mem);
+ const new_value: u32 = p.orig -% delta;
+
+ const bytes = std.mem.toBytes(new_value);
+ try utils.patchCode(mem + p.offset, &bytes, 0b101);
+ }
+ }
+
+ return Hook{
+ .orig = trampoline.ptr,
+ .data = .{ .detour = .{
+ .func = mem,
+ .trampoline = trampoline[0..trampoline_size],
+ .rel32_patch = rel32_patch,
+ .pic_patch = pic_patch,
+ } },
+ };
+}
+
+pub fn unhook(self: *Hook) !void {
+ const orig = self.orig orelse return;
+ switch (self.data) {
+ .vmt => |v| {
+ const entry_ptr: [*]u8 = @ptrCast(v.vt + v.index);
+ const bytes = std.mem.toBytes(orig);
+ try utils.patchCode(entry_ptr, &bytes, 0b001); // restore to read-only
+ },
+ .detour => |v| {
+ if (v.rel32_patch) |r| {
+ const orig_patch: *align(1) u32 = @ptrCast(v.trampoline.ptr + r.offset);
+ orig_patch.* = r.orig;
+ }
+ try utils.patchCode(v.func, v.trampoline[0 .. v.trampoline.len - 5], 0b101);
+ if (builtin.os.tag == .linux) {
+ if (v.pic_patch) |p| {
+ const bytes = std.mem.toBytes(p.orig);
+ try utils.patchCode(v.func + p.offset, &bytes, 0b101);
+ }
+ }
+ },
+ }
+ self.orig = null;
+}
diff --git a/hook/src/HookManager.zig b/hook/src/HookManager.zig
new file mode 100644
index 0000000..259fc3e
--- /dev/null
+++ b/hook/src/HookManager.zig
@@ -0,0 +1,80 @@
+const std = @import("std");
+const builtin = @import("builtin");
+
+const Hook = @import("Hook.zig");
+const mem = @import("mem.zig");
+const utils = @import("utils.zig");
+
+const HookManager = @This();
+
+hooks: std.ArrayList(Hook),
+exec_page: []u8,
+
+pub fn init(alloc: std.mem.Allocator) !HookManager {
+ // create our exectuable page to store trampolines
+ const page_size = std.heap.page_size_min;
+ const exec_page: []u8 = try alloc.alignedAlloc(u8, .fromByteUnits(page_size), page_size);
+ if (builtin.os.tag == .windows) {
+ var oldprotect: u32 = undefined;
+ try std.os.windows.VirtualProtect(exec_page.ptr, page_size, std.os.windows.PAGE_EXECUTE_READWRITE, &oldprotect);
+ } else if (builtin.os.tag == .linux) {
+ const PROT = std.os.linux.PROT;
+ try std.os.linux.mprotect(exec_page.ptr, page_size, PROT.EXEC | PROT.WRITE | PROT.READ);
+ } else {
+ @compileError("unsupported os");
+ }
+ return HookManager{
+ .hooks = std.ArrayList(Hook).init(alloc),
+ .exec_page = exec_page[0..],
+ };
+}
+
+pub fn deinit(self: *HookManager) usize {
+ var count: usize = 0;
+ for (self.hooks.items) |*hook| {
+ hook.unhook() catch continue;
+ count += 1;
+ }
+
+ self.hooks.deinit();
+ return count;
+}
+
+pub fn findAndHook(self: *HookManager, T: type, module: []const u8, patterns: []const []const ?u8, target: *const anyopaque) !T {
+ const match = mem.scanUniquePatterns(module, patterns) orelse {
+ return error.PatternNotFound;
+ };
+
+ return self.hookDetour(T, match.ptr, target);
+}
+
+pub fn hookVMT(self: *HookManager, vt: [*]*const anyopaque, index: usize, target: anytype) !@TypeOf(target) {
+ var hook = try Hook.hookVMT(vt, index, target);
+ errdefer hook.unhook() catch {};
+
+ try self.hooks.append(hook);
+
+ return @ptrCast(hook.orig.?);
+}
+
+pub fn hookDetour(self: *HookManager, func: anytype, target: @TypeOf(func)) !@TypeOf(func) {
+ var hook = try Hook.hookDetour(@constCast(func), target, self.exec_page);
+ errdefer hook.unhook() catch {};
+
+ try self.hooks.append(hook);
+
+ self.exec_page = self.exec_page[hook.data.detour.trampoline.len..];
+
+ return @ptrCast(hook.orig.?);
+}
+
+pub fn hookSymbol(self: *HookManager, module_name: []const u8, func: [:0]const u8, target: anytype) !@TypeOf(target) {
+ comptime std.debug.assert(@typeInfo(@TypeOf(target)) == .pointer);
+
+ var module = try std.DynLib.open(module_name);
+ // this just decreases refcount by 1, we don't ever hook things that aren't
+ // already loaded so it doesn't matter
+ defer module.close();
+ const func_ptr = module.lookup(@TypeOf(target), func) orelse return error.NoSuchSymbol;
+ return self.hookDetour(func_ptr, target);
+}
diff --git a/hook/src/mem.zig b/hook/src/mem.zig
new file mode 100644
index 0000000..62aec00
--- /dev/null
+++ b/hook/src/mem.zig
@@ -0,0 +1,199 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const testing = std.testing;
+
+const utils = @import("utils.zig");
+
+const isHex = utils.isHex;
+const makeHex = utils.makeHex;
+
+pub fn makePattern(comptime str: []const u8) []const ?u8 {
+ return comptime blk: {
+ @setEvalBranchQuota(10000);
+ var it = std.mem.splitSequence(u8, str, " ");
+ var pat: []const ?u8 = &.{};
+
+ while (it.next()) |byte| {
+ if (byte.len != 2) {
+ @compileError("Each byte should be 2 characters");
+ }
+ if (byte[0] == '?') {
+ if (byte[1] != '?') {
+ @compileError("The second question mark is missing");
+ }
+ pat = pat ++ .{null};
+ } else if (isHex(byte[0])) {
+ if (!isHex(byte[1])) {
+ @compileError("The second hex digit is missing");
+ }
+ const n = try std.fmt.parseInt(u8, byte, 16);
+ pat = pat ++ .{n};
+ } else {
+ @compileError("Only hex digits, spaces and question marks are allowed");
+ }
+ }
+ break :blk pat;
+ };
+}
+
+pub fn makePatterns(comptime arr: anytype) []const []const ?u8 {
+ return comptime blk: {
+ var patterns: []const []const ?u8 = &.{};
+ for (arr) |str| {
+ const pat: []const []const ?u8 = &.{makePattern(str)};
+ patterns = patterns ++ pat;
+ }
+ break :blk patterns;
+ };
+}
+
+pub fn scanFirst(mem: []const u8, pattern: []const ?u8) ?usize {
+ if (mem.len < pattern.len) {
+ return null;
+ }
+
+ var offset: usize = 0;
+ outer: while (offset < mem.len - pattern.len + 1) : (offset += 1) {
+ for (pattern, 0..) |byte, j| {
+ if (byte) |b| {
+ if (b != mem[offset + j]) {
+ continue :outer;
+ }
+ }
+ }
+ return offset;
+ }
+
+ return null;
+}
+
+pub fn scanUnique(mem: []const u8, pattern: []const ?u8) ?usize {
+ if (scanFirst(mem, pattern)) |offset| {
+ if (scanFirst(mem[offset + pattern.len ..], pattern) != null) {
+ return null;
+ }
+ return offset;
+ }
+
+ return null;
+}
+
+pub const MatchedPattern = struct {
+ index: usize,
+ ptr: [*]const u8,
+};
+
+pub fn scanAllPatterns(mem: []const u8, patterns: []const []const ?u8, data: *std.ArrayList(MatchedPattern)) !void {
+ for (patterns, 0..) |pattern, i| {
+ var base: usize = 0;
+ while (scanFirst(mem[base..], pattern)) |offset| {
+ try data.append(MatchedPattern{
+ .index = i,
+ .ptr = mem.ptr + base + offset,
+ });
+ base += offset + pattern.len;
+ }
+ }
+}
+
+pub fn scanUniquePatterns(mem: []const u8, patterns: []const []const ?u8) ?MatchedPattern {
+ var match: ?MatchedPattern = null;
+ for (patterns, 0..) |pattern, i| {
+ if (scanFirst(mem, pattern)) |offset| {
+ if (scanFirst(mem[offset + pattern.len ..], pattern) != null) {
+ return null;
+ }
+
+ if (match != null) {
+ return null;
+ }
+
+ match = .{
+ .index = i,
+ .ptr = mem.ptr + offset,
+ };
+ }
+ }
+
+ return match;
+}
+
+test "Scan first pattern" {
+ const mem = makeHex("F6 05 12 34 56 78 12");
+
+ // Match at the start
+ const test_pattern1 = makePattern("F6 05 12");
+ const result1 = scanFirst(mem, test_pattern1);
+ try testing.expect(result1 != null);
+ if (result1) |offset| {
+ try testing.expectEqual(0, offset);
+ }
+
+ // Match at the middle
+ const test_pattern2 = makePattern("12 34 56");
+ const result2 = scanFirst(mem, test_pattern2);
+ try testing.expect(result2 != null);
+ if (result2) |offset| {
+ try testing.expectEqual(2, offset);
+ }
+
+ // Match at the end
+ const test_pattern3 = makePattern("56 78 12");
+ const result3 = scanFirst(mem, test_pattern3);
+ try testing.expect(result3 != null);
+ if (result3) |offset| {
+ try testing.expectEqual(4, offset);
+ }
+}
+
+test "Scan unique patterns" {
+ const mem = makeHex("F6 05 12 34 56 78 12");
+ const test_patterns = makePatterns(.{
+ "00 00 ?? ?? 12",
+ "12 ?? 56",
+ "F6 05 00 34",
+ });
+
+ const result = scanUniquePatterns(mem, test_patterns);
+ try testing.expect(result != null);
+ if (result) |r| {
+ try testing.expectEqual(1, r.index);
+ try testing.expectEqual(mem.ptr + 2, r.ptr);
+ }
+}
+
+test "Scan unique patterns with multiple matches" {
+ const mem = makeHex("12 34 56 12 34 56 78 9A BC DE");
+
+ const test_patterns1 = makePatterns(.{
+ "12 34 56", // Non-unique match
+ });
+ try testing.expect(scanUniquePatterns(mem, test_patterns1) == null);
+
+ const test_patterns2 = makePatterns(.{
+ "12 ?? ?? 12", // Unique match
+ "9A BC DE", // Unique match
+ });
+ try testing.expect(scanUniquePatterns(mem, test_patterns2) == null);
+
+ const test_patterns3 = makePatterns(.{
+ "12 34 56", // Non-unique match
+ "9A BC DE", // Unique match
+ });
+ try testing.expect(scanUniquePatterns(mem, test_patterns3) == null);
+}
+
+pub fn loadValue(T: type, ptr: [*]const u8) T {
+ const val: *align(1) const T = @ptrCast(ptr);
+ return val.*;
+}
+
+pub fn setValue(T: type, ptr: [*]u8, value: T) void {
+ const val: *align(1) T = @ptrCast(ptr);
+ val.* = value;
+}
+
+test "Load value from memory" {
+ const mem = makeHex("E9 B1 9A 78 56"); // jmp
+ try testing.expectEqual(0x56789AB1, loadValue(u32, mem.ptr + 1));
+}
diff --git a/hook/src/utils.zig b/hook/src/utils.zig
new file mode 100644
index 0000000..57fdfec
--- /dev/null
+++ b/hook/src/utils.zig
@@ -0,0 +1,198 @@
+const std = @import("std");
+const w = std.os.windows;
+const builtin = @import("builtin");
+
+const x86 = @import("x86.zig");
+const mem = @import("mem.zig");
+
+const MODULEINFO = extern struct {
+ lpBaseOfDll: w.LPVOID,
+ SizeOfImage: w.DWORD,
+ EntryPoint: w.LPVOID,
+};
+
+extern "kernel32" fn FlushInstructionCache(hProcess: w.HANDLE, lpBaseAddress: w.LPCVOID, dwSize: w.SIZE_T) callconv(.winapi) w.BOOL;
+extern "kernel32" fn GetModuleHandleW(lpModuleName: ?w.LPCWSTR) callconv(.winapi) w.HMODULE;
+extern "kernel32" fn GetModuleInformation(hProcess: w.HANDLE, hModule: w.HMODULE, lpmodinfo: *MODULEINFO, cb: w.DWORD) callconv(.winapi) w.BOOL;
+extern "kernel32" fn GetCurrentProcess() callconv(.winapi) w.HANDLE;
+
+pub inline fn isHex(c: u8) bool {
+ return (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F');
+}
+
+pub fn makeHex(comptime str: []const u8) []const u8 {
+ return comptime blk: {
+ @setEvalBranchQuota(10000);
+ var it = std.mem.splitSequence(u8, str, " ");
+ var pat: []const u8 = &.{};
+
+ while (it.next()) |byte| {
+ if (byte.len != 2) {
+ @compileError("Each byte should be 2 characters");
+ }
+ if (isHex(byte[0])) {
+ if (!isHex(byte[1])) {
+ @compileError("The second hex digit is missing");
+ }
+ const n = try std.fmt.parseInt(u8, byte, 16);
+ pat = pat ++ .{n};
+ } else {
+ @compileError("Only hex digits are allowed");
+ }
+ }
+ break :blk pat;
+ };
+}
+
+pub fn patchCode(addr: [*]u8, data: []const u8, restore_protect: u32) !void {
+ if (builtin.os.tag == .windows) {
+ var old_protect: w.DWORD = undefined;
+
+ try w.VirtualProtect(addr, data.len, w.PAGE_EXECUTE_READWRITE, &old_protect);
+ @memcpy(addr, data);
+
+ _ = FlushInstructionCache(GetCurrentProcess(), addr, data.len);
+ try w.VirtualProtect(addr, data.len, old_protect, &old_protect);
+ } else {
+ const page_size = std.heap.page_size_min;
+ const addr_int = @intFromPtr(addr);
+ const page_start = addr_int & ~(page_size - 1);
+ const page_end = addr_int + data.len;
+ const page_len = (page_end - page_start + page_size - 1) & ~(page_size - 1);
+
+ const prot_all = 0b111; // rwx
+
+ if (std.c.mprotect(@ptrFromInt(page_start), page_len, prot_all) != 0)
+ return error.MProtectWritable;
+
+ @memcpy(addr, data);
+
+ if (std.c.mprotect(@ptrFromInt(page_start), page_len, restore_protect) != 0)
+ return error.MProtectRestore;
+ }
+}
+
+// Windows: Return entire module memory
+// Linux: Return the code segment memory
+pub fn getModule(comptime module_name: []const u8) ?[]const u8 {
+ return switch (builtin.os.tag) {
+ .windows => getModuleWindows(module_name),
+ .linux => getModuleLinux(module_name, 0b101) catch return null,
+ else => @compileError("getModule is not available for this target"),
+ };
+}
+
+// Return the entire module memory
+pub fn getEntireModule(comptime module_name: []const u8) ?[]const u8 {
+ return switch (builtin.os.tag) {
+ .windows => getModuleWindows(module_name),
+ .linux => getModuleLinux(module_name, 0) catch return null,
+ else => @compileError("getModule is not available for this target"),
+ };
+}
+
+fn getModuleWindows(comptime module_name: []const u8) ?[]const u8 {
+ const dll_name = module_name ++ ".dll";
+ const path_w = std.unicode.utf8ToUtf16LeStringLiteral(dll_name);
+ const dll = w.GetModuleHandleW(path_w) orelse return null;
+ var info: w.MODULEINFO = undefined;
+ if (GetModuleInformation(GetCurrentProcess(), dll, &info, @sizeOf(MODULEINFO)) == 0) {
+ return null;
+ }
+ const module: [*]const u8 = @ptrCast(dll);
+ return module[0..info.SizeOfImage];
+}
+
+fn getModuleLinux(comptime module_name: []const u8, permission: u32) !?[]const u8 {
+ const file_name = module_name ++ ".so";
+
+ const allocator = std.heap.page_allocator;
+ var file = try std.fs.openFileAbsolute("/proc/self/maps", .{ .mode = .read_only });
+ defer file.close();
+ var reader = file.reader();
+
+ var base: usize = 0;
+ var end: usize = 0;
+ var found = false;
+
+ while (try reader.readUntilDelimiterOrEofAlloc(allocator, '\n', 4096)) |line| {
+ defer allocator.free(line);
+
+ // Example format:
+ // de228000-de229000 r--p 00000000 00:29 1026008 /usr/lib/libstdc++.so.6.0.33
+
+ if (!std.mem.endsWith(u8, line, file_name)) {
+ if (found) break;
+ continue;
+ }
+
+ const pos = line.len - file_name.len;
+ if (line[pos - 1] != '/' and line[pos - 1] != ' ') continue;
+
+ const dash = std.mem.indexOfScalar(u8, line, '-') orelse continue;
+ const space = std.mem.indexOfScalarPos(u8, line, dash + 1, ' ') orelse continue;
+
+ const perms_start = space + 1;
+ if (line.len < perms_start + 4) continue;
+ const read = line[perms_start];
+ const write = line[perms_start + 1];
+ const exec = line[perms_start + 2];
+ if (permission & 0b001 != 0 and read == '-') continue;
+ if (permission & 0b010 != 0 and write == '-') continue;
+ if (permission & 0b100 != 0 and exec == '-') continue;
+
+ const start_hex = line[0..dash];
+ const end_hex = line[dash + 1 .. space];
+
+ const start_addr = try std.fmt.parseInt(usize, start_hex, 16);
+ const end_addr = try std.fmt.parseInt(usize, end_hex, 16);
+
+ if (!found) {
+ base = start_addr;
+ end = end_addr;
+ found = true;
+ } else if (start_addr == end) {
+ end = end_addr;
+ } else {
+ break;
+ }
+ }
+
+ if (!found) return null;
+
+ const size = end - base;
+ const ptr: [*]const u8 = @ptrFromInt(base);
+ return ptr[0..size];
+}
+
+// Match call + add pattern
+// If matched, inst + len will be the start of the imm32
+pub fn matchPIC(inst: [*]const u8) ?u32 {
+ if (inst[0] != x86.Opcode.Op1.call) return null;
+ if (inst[5] == x86.Opcode.Op1.alumiw) {
+ const modrm = inst[6];
+ // mod must be 0b11 (register operand)
+ if ((modrm & 0b1100_0000) != 0b1100_0000) return null;
+ // reg/opcode must be 0b000 (ADD)
+ if ((modrm & 0b0011_1000) != 0b0000_0000) return null;
+
+ // rm should not be 0b100 (ESP)
+ // Although it's rare, compiler occasionally uses EBP for PIC
+ const rm = modrm & 0b0000_0111;
+ if (rm == 0b100) return null;
+ return 7;
+ } else if (inst[5] == x86.Opcode.Op1.addeaxi) {
+ return 6;
+ }
+ return null;
+}
+
+const GOT_pattern = mem.makePattern("E8 ?? ?? ?? ?? 05 ?? ?? ?? ?? 8D 80");
+
+pub fn findGOTAddr(module: []const u8) ?u32 {
+ if (mem.scanFirst(module, GOT_pattern)) |offset| {
+ const imm32 = mem.loadValue(u32, module.ptr + offset + 6);
+ return @intFromPtr(module.ptr + offset + 5) +% imm32;
+ }
+ return null;
+}
diff --git a/hook/src/x86.zig b/hook/src/x86.zig
new file mode 100644
index 0000000..60b9666
--- /dev/null
+++ b/hook/src/x86.zig
@@ -0,0 +1,670 @@
+const std = @import("std");
+const testing = std.testing;
+
+const makeHex = @import("utils.zig").makeHex;
+
+pub const Opcode = struct {
+ pub const Prefixes = struct {
+ pub const es = 0x26;
+ pub const cs = 0x2E;
+ pub const ss = 0x36;
+ pub const ds = 0x3E;
+ pub const fs = 0x64;
+ pub const gs = 0x65;
+ pub const opsz = 0x66;
+ pub const adsz = 0x67;
+ pub const lock = 0xF0;
+ pub const repn = 0xF2;
+ pub const rep = 0xF3;
+ };
+
+ pub const Op1No = struct {
+ pub const pushes = 0x06;
+ pub const popes = 0x07;
+ pub const pushcs = 0x0E;
+ pub const pushss = 0x16;
+ pub const popss = 0x17;
+ pub const pushds = 0x1E;
+ pub const popds = 0x1F;
+ pub const daa = 0x27;
+ pub const das = 0x2F;
+ pub const aaa = 0x37;
+ pub const aas = 0x3F;
+ pub const inceax = 0x40;
+ pub const incecx = 0x41;
+ pub const incedx = 0x42;
+ pub const incebx = 0x43;
+ pub const incesp = 0x44;
+ pub const incebp = 0x45;
+ pub const incesi = 0x46;
+ pub const incedi = 0x47;
+ pub const deceax = 0x48;
+ pub const dececx = 0x49;
+ pub const decedx = 0x4A;
+ pub const decebx = 0x4B;
+ pub const decesp = 0x4C;
+ pub const decebp = 0x4D;
+ pub const decesi = 0x4E;
+ pub const decedi = 0x4F;
+ pub const pusheax = 0x50;
+ pub const pushecx = 0x51;
+ pub const pushedx = 0x52;
+ pub const pushebx = 0x53;
+ pub const pushesp = 0x54;
+ pub const pushebp = 0x55;
+ pub const pushesi = 0x56;
+ pub const pushedi = 0x57;
+ pub const popeax = 0x58;
+ pub const popecx = 0x59;
+ pub const popedx = 0x5A;
+ pub const popebx = 0x5B;
+ pub const popesp = 0x5C;
+ pub const popebp = 0x5D;
+ pub const popesi = 0x5E;
+ pub const popedi = 0x5F;
+ pub const pusha = 0x60;
+ pub const popa = 0x61;
+ pub const nop = 0x90;
+ pub const xchgecxeax = 0x91;
+ pub const xchgedxeax = 0x92;
+ pub const xchgebxeax = 0x93;
+ pub const xchgespeax = 0x94;
+ pub const xchgebpeax = 0x95;
+ pub const xchgesieax = 0x96;
+ pub const xchgedieax = 0x97;
+ pub const cwde = 0x98;
+ pub const cdq = 0x99;
+ pub const wait = 0x9B;
+ pub const pushf = 0x9C;
+ pub const popf = 0x9D;
+ pub const sahf = 0x9E;
+ pub const lahf = 0x9F;
+ pub const movs8 = 0xA4;
+ pub const movsw = 0xA5;
+ pub const cmps8 = 0xA6;
+ pub const cmpsw = 0xA7;
+ pub const stos8 = 0xAA;
+ pub const stosd = 0xAB;
+ pub const lods8 = 0xAC;
+ pub const lodsd = 0xAD;
+ pub const scas8 = 0xAE;
+ pub const scasd = 0xAF;
+ pub const ret = 0xC3;
+ pub const leave = 0xC9;
+ pub const retf = 0xCB;
+ pub const int3 = 0xCC;
+ pub const into = 0xCE;
+ pub const xlat = 0xD7;
+ pub const cmc = 0xF5;
+ pub const clc = 0xF8;
+ pub const stc = 0xF9;
+ pub const cli = 0xFA;
+ pub const sti = 0xFB;
+ pub const cld = 0xFC;
+ pub const std = 0xFD;
+ };
+ pub const Op1I8 = struct {
+ pub const addali = 0x04;
+ pub const orali = 0x0C;
+ pub const adcali = 0x14;
+ pub const sbbali = 0x1C;
+ pub const andali = 0x24;
+ pub const subali = 0x2C;
+ pub const xorali = 0x34;
+ pub const cmpali = 0x3C;
+ pub const pushi8 = 0x6A;
+ pub const testali = 0xA8;
+ pub const jo = 0x70;
+ pub const jno = 0x71;
+ pub const jb = 0x72;
+ pub const jnb = 0x73;
+ pub const jz = 0x74;
+ pub const jnz = 0x75;
+ pub const jna = 0x76;
+ pub const ja = 0x77;
+ pub const js = 0x78;
+ pub const jns = 0x79;
+ pub const jp = 0x7A;
+ pub const jnp = 0x7B;
+ pub const jl = 0x7C;
+ pub const jnl = 0x7D;
+ pub const jng = 0x7E;
+ pub const jg = 0x7F;
+ pub const movali = 0xB0;
+ pub const movcli = 0xB1;
+ pub const movdli = 0xB2;
+ pub const movbli = 0xB3;
+ pub const movahi = 0xB4;
+ pub const movchi = 0xB5;
+ pub const movdhi = 0xB6;
+ pub const movbhi = 0xB7;
+ pub const int = 0xCD;
+ pub const amx = 0xD4;
+ pub const adx = 0xD5;
+ pub const loopnz = 0xE0;
+ pub const loopz = 0xE1;
+ pub const loop = 0xE2;
+ pub const jcxz = 0xE3;
+ pub const jmpi8 = 0xEB;
+ };
+ pub const Op1IW = struct {
+ pub const addeaxi = 0x05;
+ pub const oreaxi = 0x0D;
+ pub const adceaxi = 0x15;
+ pub const sbbeaxi = 0x1D;
+ pub const andeaxi = 0x25;
+ pub const subeaxi = 0x2D;
+ pub const xoreaxi = 0x35;
+ pub const cmpeaxi = 0x3D;
+ pub const pushiw = 0x68;
+ pub const testeaxi = 0xA9;
+ pub const moveaxi = 0xB8;
+ pub const movecxi = 0xB9;
+ pub const movedxi = 0xBA;
+ pub const movebxi = 0xBB;
+ pub const movespi = 0xBC;
+ pub const movebpi = 0xBD;
+ pub const movesii = 0xBE;
+ pub const movedii = 0xBF;
+ pub const call = 0xE8;
+ pub const jmpiw = 0xE9;
+ };
+ pub const Op1IWI = struct {
+ pub const movalii = 0xA0;
+ pub const moveaxii = 0xA1;
+ pub const moviial = 0xA2;
+ pub const moviieax = 0xA3;
+ };
+ pub const Op1I16 = struct {
+ pub const reti16 = 0xC2;
+ pub const retfi16 = 0xCA;
+ };
+ pub const Op1Mrm = struct {
+ pub const addmr8 = 0x00;
+ pub const addmrw = 0x01;
+ pub const addrm8 = 0x02;
+ pub const addrmw = 0x03;
+ pub const ormr8 = 0x08;
+ pub const ormrw = 0x09;
+ pub const orrm8 = 0x0A;
+ pub const orrmw = 0x0B;
+ pub const adcmr8 = 0x10;
+ pub const adcmrw = 0x11;
+ pub const adcrm8 = 0x12;
+ pub const adcrmw = 0x13;
+ pub const sbbmr8 = 0x18;
+ pub const sbbmrw = 0x19;
+ pub const sbbrm8 = 0x1A;
+ pub const sbbrmw = 0x1B;
+ pub const andmr8 = 0x20;
+ pub const andmrw = 0x21;
+ pub const andrm8 = 0x22;
+ pub const andrmw = 0x23;
+ pub const submr8 = 0x28;
+ pub const submrw = 0x29;
+ pub const subrm8 = 0x2A;
+ pub const subrmw = 0x2B;
+ pub const xormr8 = 0x30;
+ pub const xormrw = 0x31;
+ pub const xorrm8 = 0x32;
+ pub const xorrmw = 0x33;
+ pub const cmpmr8 = 0x38;
+ pub const cmpmrw = 0x39;
+ pub const cmprm8 = 0x3A;
+ pub const cmprmw = 0x3B;
+ pub const arpl = 0x63;
+ pub const testmr8 = 0x84;
+ pub const testmrw = 0x85;
+ pub const xchgmr8 = 0x86;
+ pub const xchgmrw = 0x87;
+ pub const movmr8 = 0x88;
+ pub const movmrw = 0x89;
+ pub const movrm8 = 0x8A;
+ pub const movrmw = 0x8B;
+ pub const movms = 0x8C;
+ pub const lea = 0x8D;
+ pub const movsm = 0x8E;
+ pub const popm = 0x8F;
+ pub const shiftm18 = 0xD0;
+ pub const shiftm1w = 0xD1;
+ pub const shiftmcl8 = 0xD2;
+ pub const shiftmclw = 0xD3;
+ pub const fltblk1 = 0xD8;
+ pub const fltblk2 = 0xD9;
+ pub const fltblk3 = 0xDA;
+ pub const fltblk4 = 0xDB;
+ pub const fltblk5 = 0xDC;
+ pub const fltblk6 = 0xDD;
+ pub const fltblk7 = 0xDE;
+ pub const fltblk8 = 0xDF;
+ pub const miscm8 = 0xFE;
+ pub const miscmw = 0xFF;
+ };
+ pub const Op1MrmI8 = struct {
+ pub const imulmi8 = 0x6B;
+ pub const alumi8 = 0x80;
+ pub const alumi8x = 0x82;
+ pub const alumi8s = 0x83;
+ pub const shiftmi8 = 0xC0;
+ pub const shiftmiw = 0xC1;
+ pub const movmi8 = 0xC6;
+ };
+ pub const Op1MrmIW = struct {
+ pub const imulmiw = 0x69;
+ pub const alumiw = 0x81;
+ pub const movmiw = 0xC7;
+ };
+
+ pub const Op1Extra = struct {
+ const enter = 0xC8;
+ const crazy8 = 0xF6;
+ const crazyw = 0xF7;
+ };
+
+ pub const Op2No = struct {
+ pub const rdtsc = 0x31;
+ pub const rdpmd = 0x33;
+ pub const sysenter = 0x34;
+ pub const pushfs = 0xA0;
+ pub const popfs = 0xA1;
+ pub const cpuid = 0xA2;
+ pub const pushgs = 0xA8;
+ pub const popgs = 0xA9;
+ pub const rsm = 0xAA;
+ pub const bswapeax = 0xC8;
+ pub const bswapecx = 0xC9;
+ pub const bswapedx = 0xCA;
+ pub const bswapebx = 0xCB;
+ pub const bswapesp = 0xCC;
+ pub const bswapebp = 0xCD;
+ pub const bswapesi = 0xCE;
+ pub const bswapedi = 0xCF;
+ pub const emms = 0x77;
+ };
+ pub const Op2IW = struct {
+ pub const joii = 0x80;
+ pub const jnoii = 0x81;
+ pub const jbii = 0x82;
+ pub const jnbii = 0x83;
+ pub const jzii = 0x84;
+ pub const jnzii = 0x85;
+ pub const jnaii = 0x86;
+ pub const jaii = 0x87;
+ pub const jsii = 0x88;
+ pub const jnsii = 0x89;
+ pub const jpii = 0x8A;
+ pub const jnpii = 0x8B;
+ pub const jlii = 0x8C;
+ pub const jnlii = 0x8D;
+ pub const jngii = 0x8E;
+ pub const jgii = 0x8F;
+ };
+ pub const Op2Mrm = struct {
+ pub const nop = 0x0D;
+ pub const hints1 = 0x18;
+ pub const hints2 = 0x19;
+ pub const hints3 = 0x1A;
+ pub const hints4 = 0x1B;
+ pub const hints5 = 0x1C;
+ pub const hints6 = 0x1D;
+ pub const hints7 = 0x1E;
+ pub const hints8 = 0x1F;
+ pub const cmovo = 0x40;
+ pub const cmovno = 0x41;
+ pub const cmovb = 0x42;
+ pub const cmovnb = 0x43;
+ pub const cmovz = 0x44;
+ pub const cmovnz = 0x45;
+ pub const cmovna = 0x46;
+ pub const cmova = 0x47;
+ pub const cmovs = 0x48;
+ pub const cmovns = 0x49;
+ pub const cmovp = 0x4A;
+ pub const cmovnp = 0x4B;
+ pub const cmovl = 0x4C;
+ pub const cmovnl = 0x4D;
+ pub const cmovng = 0x4E;
+ pub const cmovg = 0x4F;
+ pub const seto = 0x90;
+ pub const setno = 0x91;
+ pub const setb = 0x92;
+ pub const setnb = 0x93;
+ pub const setz = 0x94;
+ pub const setnz = 0x95;
+ pub const setna = 0x96;
+ pub const seta = 0x97;
+ pub const sets = 0x98;
+ pub const setns = 0x99;
+ pub const setp = 0x9A;
+ pub const setnp = 0x9B;
+ pub const setl = 0x9C;
+ pub const setnl = 0x9D;
+ pub const setng = 0x9E;
+ pub const setg = 0x9F;
+ pub const btmr = 0xA3;
+ pub const shldmrcl = 0xA5;
+ pub const bts = 0xAB;
+ pub const shrdmrcl = 0xAD;
+ pub const misc = 0xAE;
+ pub const imul = 0xAF;
+ pub const cmpxchg8 = 0xB0;
+ pub const cmpxchgw = 0xB1;
+ pub const movzx8 = 0xB6;
+ pub const movzxw = 0xB7;
+ pub const popcnt = 0xB8;
+ pub const btcrm = 0xBB;
+ pub const bsf = 0xBC;
+ pub const bsr = 0xBD;
+ pub const movsx8 = 0xBE;
+ pub const movsxw = 0xBF;
+ pub const xaddrm8 = 0xC0;
+ pub const xaddrmw = 0xC1;
+ pub const cmpxchg64 = 0xC7;
+ pub const movrm128 = 0x10;
+ pub const movmr128 = 0x11;
+ pub const movlrm = 0x12;
+ pub const movlmr = 0x13;
+ pub const unpckl = 0x14;
+ pub const unpckh = 0x15;
+ pub const movhrm = 0x16;
+ pub const movhmr = 0x17;
+ pub const movarm = 0x28;
+ pub const movamr = 0x29;
+ pub const cvtif64 = 0x2A;
+ pub const movnt = 0x2B;
+ pub const cvtft64 = 0x2C;
+ pub const cvtfi64 = 0x2D;
+ pub const ucomi = 0x2E;
+ pub const comi = 0x2F;
+ pub const movmsk = 0x50;
+ pub const sqrt = 0x51;
+ pub const rsqrt = 0x52;
+ pub const rcp = 0x53;
+ pub const and_ = 0x54;
+ pub const andn = 0x55;
+ pub const or_ = 0x56;
+ pub const xor = 0x57;
+ pub const add = 0x58;
+ pub const mul = 0x59;
+ pub const cvtff128 = 0x5A;
+ pub const cvtfi128 = 0x5B;
+ pub const sub = 0x5C;
+ pub const div = 0x5D;
+ pub const min = 0x5E;
+ pub const max = 0x5F;
+ pub const punpcklbw = 0x60;
+ pub const punpcklbd = 0x61;
+ pub const punpckldq = 0x62;
+ pub const packsswb = 0x63;
+ pub const pcmpgtb = 0x64;
+ pub const pcmpgtw = 0x65;
+ pub const pcmpgtd = 0x66;
+ pub const packuswb = 0x67;
+ pub const punpckhbw = 0x68;
+ pub const punpckhwd = 0x69;
+ pub const punpckhdq = 0x6A;
+ pub const packssdw = 0x6B;
+ pub const punpcklqdq = 0x6C;
+ pub const punpckhqdq = 0x6D;
+ pub const movdrm = 0x6E;
+ pub const movqrm = 0x6F;
+ pub const pcmpeqb = 0x74;
+ pub const pcmpeqw = 0x75;
+ pub const pcmpeqd = 0x76;
+ pub const movdmr = 0x7E;
+ pub const movqmr = 0x7F;
+ pub const movnti = 0xC3;
+ pub const addsub = 0xD0;
+ pub const psrlw = 0xD1;
+ pub const psrld = 0xD2;
+ pub const psrlq = 0xD3;
+ pub const paddq = 0xD4;
+ pub const pmullw = 0xD5;
+ pub const movqrr = 0xD6;
+ pub const pmovmskb = 0xD7;
+ pub const psubusb = 0xD8;
+ pub const psubusw = 0xD9;
+ pub const pminub = 0xDA;
+ pub const pand = 0xDB;
+ pub const paddusb = 0xDC;
+ pub const paddusw = 0xDD;
+ pub const pmaxub = 0xDE;
+ pub const pandn = 0xDF;
+ pub const pavgb = 0xE0;
+ pub const psraw = 0xE1;
+ pub const psrad = 0xE2;
+ pub const pavgw = 0xE3;
+ pub const pmulhuw = 0xE4;
+ pub const pmulhw = 0xE5;
+ pub const cvtq = 0xE6;
+ pub const movntq = 0xE7;
+ pub const psubsb = 0xE8;
+ pub const psubsw = 0xE9;
+ pub const pminsb = 0xEA;
+ pub const pminsw = 0xEB;
+ pub const paddsb = 0xEC;
+ pub const paddsw = 0xED;
+ pub const pmaxsw = 0xEE;
+ pub const pxor = 0xEF;
+ pub const lddqu = 0xF0;
+ pub const psllw = 0xF1;
+ pub const pslld = 0xF2;
+ pub const psllq = 0xF3;
+ pub const pmuludq = 0xF4;
+ pub const pmaddwd = 0xF5;
+ pub const psabdw = 0xF6;
+ pub const maskmovq = 0xF7;
+ pub const psubb = 0xF8;
+ pub const psubw = 0xF9;
+ pub const psubd = 0xFA;
+ pub const psubq = 0xFB;
+ pub const paddb = 0xFC;
+ pub const paddw = 0xFD;
+ pub const paddd = 0xFE;
+ };
+ pub const Op2MrmI8 = struct {
+ pub const shldmri = 0xA4;
+ pub const shrdmri = 0xAC;
+ pub const btxmi = 0xBA;
+ pub const pshuf = 0x70;
+ pub const pswi = 0x71;
+ pub const psdi = 0x72;
+ pub const psqi = 0x73;
+ pub const cmpsi = 0xC2;
+ pub const pinsrw = 0xC4;
+ pub const pextrw = 0xC5;
+ pub const shuf = 0xC6;
+ };
+
+ pub const op2_byte = 0x0F;
+ pub const op3_1 = 0x38;
+ pub const op3_2 = 0x3A;
+ pub const op3dnow = 0x0F;
+};
+
+// Constructs a ModRM byte
+pub fn modrm(mod: u8, reg: u8, rm: u8) u8 {
+ return mod << 6 | reg << 3 | rm;
+}
+
+fn mrmsib(b: [*]const u8, address_len: usize) usize {
+ if (address_len == 4 or b[0] & 0xC0 != 0) {
+ const sib: usize = if (address_len == 4 and b[0] < 0xC0 and (b[0] & 7) == 4) 1 else 0;
+ if ((b[0] & 0xC0) == 0x40) {
+ return 2 + sib;
+ }
+ if ((b[0] & 0xC0) == 0x00) {
+ if ((b[0] & 7) != 5) {
+ if (sib == 1 and (b[1] & 7) == 5) {
+ return if (b[0] & 0x40 != 0) 3 else 6;
+ }
+ return 1 + sib;
+ }
+ return 1 + address_len + sib;
+ }
+ if ((b[0] & 0xC0) == 0x80) {
+ return 1 + address_len + sib;
+ }
+ }
+ if (address_len == 2 and (b[0] & 0xC7) == 0x06) {
+ return 3;
+ }
+ return 1;
+}
+
+fn is_field(comptime T: type, byte: u8) bool {
+ @setEvalBranchQuota(100000);
+ for (@typeInfo(T).@"struct".decls) |decl| {
+ const decl_ptr = &@field(T, decl.name);
+ if (decl_ptr.* == byte) {
+ return true;
+ }
+ }
+ return false;
+}
+
+pub fn x86_len(address: [*]const u8) !usize {
+ var b = address;
+
+ var prefix_len: usize = 0;
+ var operand_len: usize = 4;
+ var address_len: usize = 4;
+
+ // prefixes
+ while (prefix_len < 14 and switch (b[0]) {
+ inline 0x00...0xFF => |byte| blk: {
+ break :blk comptime is_field(Opcode.Prefixes, byte);
+ },
+ }) : ({
+ prefix_len += 1;
+ b += 1;
+ }) {
+ if (b[0] == Opcode.Prefixes.opsz) {
+ operand_len = 2;
+ } else if (b[0] == Opcode.Prefixes.adsz) {
+ address_len = 2;
+ }
+ }
+
+ // opcode
+ return switch (b[0]) {
+ Opcode.op2_byte => switch (b[1]) {
+ Opcode.op3_1,
+ Opcode.op3_2,
+ Opcode.op3dnow,
+ => error.UnsupportedInstruction,
+ inline else => |byte| blk: {
+ if (comptime is_field(Opcode.Op2No, byte)) {
+ break :blk prefix_len + 2;
+ }
+ if (comptime is_field(Opcode.Op2IW, byte)) {
+ break :blk prefix_len + 2 + operand_len;
+ }
+ if (comptime is_field(Opcode.Op2Mrm, byte)) {
+ break :blk prefix_len + 2 + mrmsib(b + 2, address_len);
+ }
+ if (comptime is_field(Opcode.Op2MrmI8, byte)) {
+ operand_len = 1;
+ break :blk prefix_len + 2 + operand_len + mrmsib(b + 2, address_len);
+ }
+ break :blk error.UnsupportedInstruction;
+ },
+ },
+ inline else => |byte| blk: {
+ if (comptime is_field(Opcode.Op1No, byte)) {
+ break :blk prefix_len + 1;
+ }
+ if (comptime is_field(Opcode.Op1I8, byte)) {
+ operand_len = 1;
+ break :blk prefix_len + 1 + operand_len;
+ }
+ if (comptime is_field(Opcode.Op1IW, byte)) {
+ break :blk prefix_len + 1 + operand_len;
+ }
+ if (comptime is_field(Opcode.Op1IWI, byte)) {
+ break :blk prefix_len + 1 + address_len;
+ }
+ if (comptime is_field(Opcode.Op1I16, byte)) {
+ break :blk prefix_len + 3;
+ }
+ if (comptime is_field(Opcode.Op1Mrm, byte)) {
+ break :blk prefix_len + 1 + mrmsib(b + 1, address_len);
+ }
+ if (comptime is_field(Opcode.Op1MrmI8, byte)) {
+ operand_len = 1;
+ break :blk prefix_len + 1 + operand_len + mrmsib(b + 1, address_len);
+ }
+ if (comptime is_field(Opcode.Op1MrmIW, byte)) {
+ break :blk prefix_len + 1 + operand_len + mrmsib(b + 1, address_len);
+ }
+ if (byte == Opcode.Op1Extra.enter) {
+ break :blk prefix_len + 4;
+ }
+ if (byte == Opcode.Op1Extra.crazy8 or byte == Opcode.Op1Extra.crazyw) {
+ if (byte == Opcode.Op1Extra.crazy8) {
+ operand_len = 1;
+ }
+
+ if ((b[1] & 0x38) >= 0x10) {
+ operand_len = 0;
+ }
+
+ break :blk prefix_len + 1 + operand_len + mrmsib(b + 1, address_len);
+ }
+ break :blk error.UnsupportedInstruction;
+ },
+ };
+}
+
+test "Simple x86 instruction lengths" {
+ const nop = makeHex("90");
+ try testing.expectEqual(1, try x86_len(nop.ptr));
+ const push_eax = makeHex("50");
+ try testing.expectEqual(1, try x86_len(push_eax.ptr));
+ const mov_eax = makeHex("B8 78 56 34 12");
+ try testing.expectEqual(5, try x86_len(mov_eax.ptr));
+ const add_mem_eax = makeHex("00 00");
+ try testing.expectEqual(2, try x86_len(add_mem_eax.ptr));
+ const mov_ax = makeHex("66 B8 34 12");
+ try testing.expectEqual(4, try x86_len(mov_ax.ptr));
+ const add_mem_disp32 = makeHex("00 80 78 56 34 12");
+ try testing.expectEqual(6, try x86_len(add_mem_disp32.ptr));
+ const add_eax_imm = makeHex("05 78 56 34 12");
+ try testing.expectEqual(5, try x86_len(add_eax_imm.ptr));
+}
+
+test "The \"crazy\" instructions should be given correct lengths" {
+ const test8 = makeHex("F6 05 12 34 56 78 12");
+ try testing.expectEqual(7, try x86_len(test8.ptr));
+ const test16 = makeHex("66 F7 05 12 34 56 78 12");
+ try testing.expectEqual(9, try x86_len(test16.ptr));
+ const test32 = makeHex("F7 05 12 34 56 78 12 34 56 78");
+ try testing.expectEqual(10, try x86_len(test32.ptr));
+ const not8 = makeHex("F6 15 12 34 56 78");
+ try testing.expectEqual(6, try x86_len(not8.ptr));
+ const not16 = makeHex("66 F7 15 12 34 56 78");
+ try testing.expectEqual(7, try x86_len(not16.ptr));
+ const not32 = makeHex("F7 15 12 34 56 78");
+ try testing.expectEqual(6, try x86_len(not32.ptr));
+}
+
+test "SIB bytes should be decoded correctly" {
+ const fstp = makeHex("D9 1C 24");
+ try testing.expectEqual(3, try x86_len(fstp.ptr));
+}
+
+test "mov AL, moff8 instructions should be decoded correctly" {
+ const mov_moff8_al = makeHex("A2 DA 78 B4 0D");
+ try testing.expectEqual(5, try x86_len(mov_moff8_al.ptr));
+ const mov_al_moff8 = makeHex("A0 28 DF 5C 66");
+ try testing.expectEqual(5, try x86_len(mov_al_moff8.ptr));
+}
+
+test "16-bit MRM instructions should be decoded correctly" {
+ const fiadd_off16 = makeHex("67 DA 06 DF 11");
+ try testing.expectEqual(5, try x86_len(fiadd_off16.ptr));
+ const fld_tword = makeHex("67 DB 2E 99 C4");
+ try testing.expectEqual(5, try x86_len(fld_tword.ptr));
+ const add_off16_bl = makeHex("67 00 1E F5 BB");
+ try testing.expectEqual(5, try x86_len(add_off16_bl.ptr));
+}
diff --git a/hook/src/zhook.zig b/hook/src/zhook.zig
new file mode 100644
index 0000000..82e184b
--- /dev/null
+++ b/hook/src/zhook.zig
@@ -0,0 +1,9 @@
+pub const x86 = @import("x86.zig");
+pub const mem = @import("mem.zig");
+pub const Hook = @import("Hook.zig");
+pub const HookManager = @import("HookManager.zig");
+pub const utils = @import("utils.zig");
+
+test {
+ @import("std").testing.refAllDecls(@This());
+}