diff options
author | 2025-08-11 18:17:40 -0400 | |
---|---|---|
committer | 2025-08-11 20:46:00 -0400 | |
commit | 89055b019e2e2d49f8813d3578f6bc338326ca47 (patch) | |
tree | cdd097e134454937ccd35324b55d4055c2c2f4ed /hook | |
download | rt2-master.tar.gz rt2-master.zip |
Diffstat (limited to 'hook')
-rw-r--r-- | hook/LICENSE | 12 | ||||
-rw-r--r-- | hook/build.zig | 21 | ||||
-rw-r--r-- | hook/build.zig.zon | 12 | ||||
-rw-r--r-- | hook/src/Hook.zig | 237 | ||||
-rw-r--r-- | hook/src/HookManager.zig | 80 | ||||
-rw-r--r-- | hook/src/mem.zig | 199 | ||||
-rw-r--r-- | hook/src/utils.zig | 198 | ||||
-rw-r--r-- | hook/src/x86.zig | 670 | ||||
-rw-r--r-- | hook/src/zhook.zig | 9 |
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()); +} |