nop patch for non rip relative instructions

This commit is contained in:
2025-11-21 15:19:05 +01:00
parent d8bf37baf6
commit be668b19cd
2 changed files with 25 additions and 25 deletions

View File

@@ -92,7 +92,6 @@ pub const FlickenId = enum(u64) {
/// The bytes are always empty, meaning that `bytes.len == 0`. /// The bytes are always empty, meaning that `bytes.len == 0`.
/// It also needs special handling when constructing the patches, because it's different for /// It also needs special handling when constructing the patches, because it's different for
/// each instruction. /// each instruction.
// TODO: implement the special handling
nop = 0, nop = 0,
_, _,
}; };
@@ -144,7 +143,9 @@ pub fn patchRegion(patcher: *Patcher, region: []align(page_size) u8) !void {
// Get where to patch. // Get where to patch.
var instruction_iterator = InstructionIterator.init(region); var instruction_iterator = InstructionIterator.init(region);
while (instruction_iterator.next()) |instruction| { while (instruction_iterator.next()) |instruction| {
const should_patch: bool = instruction.instruction.attributes & zydis.ZYDIS_ATTRIB_HAS_LOCK > 0; // TODO: handle RIP relative instructions/operands somehow.
// Maybe use `ZydisCalcAbsoluteAddress`?
const should_patch: bool = instruction.instruction.mnemonic == zydis.ZYDIS_MNEMONIC_SYSCALL;
if (should_patch) { if (should_patch) {
const offset = instruction.address - @intFromPtr(region.ptr); const offset = instruction.address - @intFromPtr(region.ptr);
const request: PatchRequest = .{ const request: PatchRequest = .{
@@ -197,18 +198,19 @@ pub fn patchRegion(patcher: *Patcher, region: []align(page_size) u8) !void {
{ {
// Apply patches. // Apply patches.
try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE); try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE);
defer { defer posix.mprotect(region, posix.PROT.READ | posix.PROT.EXEC) catch
log.info("mprotect region: {*}", .{region});
posix.mprotect(region, posix.PROT.READ | posix.PROT.EXEC) catch
@panic("patchRegion: mprotect back to R|X failed. Can't continue"); @panic("patchRegion: mprotect back to R|X failed. Can't continue");
}
// PERF: A set of the pages for the patches/flicken we made writable. This way we don't // PERF: A set of the pages for the patches/flicken we made writable. This way we don't
// repeatedly change call `mprotect` on the same page to switch it from R|W to R|X and back. // repeatedly change call `mprotect` on the same page to switch it from R|W to R|X and back.
// At the end we `mprotect` all pages in this set back to being R|X. // At the end we `mprotect` all pages in this set back to being R|X.
var pages_made_writable: std.AutoHashMapUnmanaged(u64, void) = .empty; var pages_made_writable: std.AutoHashMapUnmanaged(u64, void) = .empty;
for (patch_requests.items) |request| { for (patch_requests.items) |request| {
const flicken = patcher.flicken.entries.get(@intFromEnum(request.flicken)).value; const flicken: Flicken = if (request.flicken == .nop)
.{ .name = "nop", .bytes = request.bytes[0..request.size] }
else
patcher.flicken.entries.get(@intFromEnum(request.flicken)).value;
var pii = PatchInstructionIterator.init( var pii = PatchInstructionIterator.init(
request.bytes, request.bytes,
request.size, request.size,
@@ -260,7 +262,6 @@ pub fn patchRegion(patcher: *Patcher, region: []align(page_size) u8) !void {
}; };
assert(@as(u64, @intFromPtr(addr.ptr)) == page_addr); assert(@as(u64, @intFromPtr(addr.ptr)) == page_addr);
// `gop.value_ptr.* = {};` not needed because it's void. // `gop.value_ptr.* = {};` not needed because it's void.
log.info("mmap: {*}", .{addr.ptr});
} }
try pages_made_writable.put(arena, page_addr, {}); try pages_made_writable.put(arena, page_addr, {});
} }
@@ -276,44 +277,44 @@ pub fn patchRegion(patcher: *Patcher, region: []align(page_size) u8) !void {
const to = allocated_range.start; const to = allocated_range.start;
break :blk @intCast(to - from); break :blk @intCast(to - from);
}; };
@memset(request.bytes[0..pii.num_prefixes], nop);
request.bytes[pii.num_prefixes] = jump_rel32;
mem.writeInt(i32, request.bytes[pii.num_prefixes + 1 ..][0..4], jump_to_offset, .little);
// TODO: pad remaining with nops or int3
const jump_back_offset: i32 = blk: { const jump_back_offset: i32 = blk: {
const from = allocated_range.end; const from = allocated_range.end;
const to: i64 = @intCast(@intFromPtr(&request.bytes[request.size])); const to: i64 = @intCast(@intFromPtr(&request.bytes[request.size]));
break :blk @intCast(to - from); break :blk @intCast(to - from);
}; };
// The jumps have to be in the opposite direction.
assert(math.sign(jump_to_offset) * math.sign(jump_back_offset) < 0);
// Write to the trampoline first, because for the `nop` flicken `flicken.bytes`
// points to `request.bytes` which we overwrite in the next step.
@memcpy(flicken_addr, flicken.bytes); @memcpy(flicken_addr, flicken.bytes);
flicken_slice[flicken.bytes.len] = jump_rel32; flicken_slice[flicken.bytes.len] = jump_rel32;
const jump_back_location = flicken_slice[flicken.bytes.len + 1 ..][0..4]; const jump_back_location = flicken_slice[flicken.bytes.len + 1 ..][0..4];
mem.writeInt(i32, jump_back_location, jump_back_offset, .little); mem.writeInt(i32, jump_back_location, jump_back_offset, .little);
// The jumps have to be in the opposite direction. @memcpy(request.bytes[0..pii.num_prefixes], prefixes[0..pii.num_prefixes]);
assert(math.sign(jump_to_offset) * math.sign(jump_back_offset) < 0); request.bytes[pii.num_prefixes] = jump_rel32;
mem.writeInt(i32, request.bytes[pii.num_prefixes + 1 ..][0..4], jump_to_offset, .little);
// TODO: pad remaining with nops or int3
break; break;
} }
} }
// Change pages back to R|X. // Change pages back to R|X.
std.Thread.sleep(1000 * 1000 * 1000);
try printMaps();
var iter = pages_made_writable.keyIterator(); var iter = pages_made_writable.keyIterator();
const protection = posix.PROT.READ | posix.PROT.EXEC; const protection = posix.PROT.READ | posix.PROT.EXEC;
while (iter.next()) |page_addr| { while (iter.next()) |page_addr| {
const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr.*); const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr.*);
log.info("mprotect patch: {*}", .{ptr});
try posix.mprotect(ptr[0..page_size], protection); try posix.mprotect(ptr[0..page_size], protection);
} }
log.info("patchRegion: Finished applying patches", .{}); log.info("patchRegion: Finished applying patches", .{});
} }
try printMaps();
// TODO: statistics // TODO: statistics
} }
/// Only used for debugging.
fn printMaps() !void { fn printMaps() !void {
const path = "/proc/self/maps"; const path = "/proc/self/maps";
var reader = try std.fs.cwd().openFile(path, .{}); var reader = try std.fs.cwd().openFile(path, .{});
@@ -348,7 +349,6 @@ const PatchInstructionIterator = struct {
) PatchInstructionIterator { ) PatchInstructionIterator {
const patch_bytes = getPatchBytes(bytes, instruction_size, 0); const patch_bytes = getPatchBytes(bytes, instruction_size, 0);
var pli = PatchLocationIterator.init(patch_bytes, @intFromPtr(&bytes[5])); var pli = PatchLocationIterator.init(patch_bytes, @intFromPtr(&bytes[5]));
// TODO: handle null case which could only happen on negative offset
const valid_range = pli.next() orelse Range{ .start = 0, .end = 0 }; const valid_range = pli.next() orelse Range{ .start = 0, .end = 0 };
return .{ return .{
.bytes = bytes, .bytes = bytes,

View File

@@ -17,6 +17,7 @@ pub const std_options: std.Options = .{
.{ .scope = .disassembler, .level = .info }, .{ .scope = .disassembler, .level = .info },
.{ .scope = .patcher, .level = .debug }, .{ .scope = .patcher, .level = .debug },
.{ .scope = .patch_location_iterator, .level = .warn }, .{ .scope = .patch_location_iterator, .level = .warn },
.{ .scope = .flicker, .level = .info },
}, },
}; };
const page_size = std.heap.pageSize(); const page_size = std.heap.pageSize();
@@ -173,12 +174,11 @@ fn loadStaticElf(ehdr: elf.Header, file_reader: *std.fs.File.Reader) !usize {
hint, hint,
maxva - minva, maxva - minva,
posix.PROT.NONE, posix.PROT.NONE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true, .FIXED = !dynamic }, .{ .TYPE = .PRIVATE, .ANONYMOUS = true, .FIXED_NOREPLACE = !dynamic },
-1, -1,
0, 0,
); );
log.debug("Pre-flight reservation at: {*}, size: 0x{x}", .{ base.ptr, base.len }); log.debug("Pre-flight reservation at: {*}, size: 0x{x}", .{ base.ptr, base.len });
posix.munmap(base);
const flags = posix.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true, .FIXED = true }; const flags = posix.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true, .FIXED = true };
var phdrs = ehdr.iterateProgramHeaders(file_reader); var phdrs = ehdr.iterateProgramHeaders(file_reader);
@@ -194,8 +194,8 @@ fn loadStaticElf(ehdr: elf.Header, file_reader: *std.fs.File.Reader) !usize {
const base_for_dyn = if (dynamic) @intFromPtr(base.ptr) else 0; const base_for_dyn = if (dynamic) @intFromPtr(base.ptr) else 0;
start += base_for_dyn; start += base_for_dyn;
log.debug( log.debug(
" - phdr[{}]: mapping 0x{x} bytes at 0x{x} (vaddr=0x{x}, dyn_base=0x{x})", " - phdr[{}]: mapping 0x{x} - 0x{x} (vaddr=0x{x}, dyn_base=0x{x})",
.{ phdr_idx, size, start, phdr.p_vaddr, base_for_dyn }, .{ phdr_idx, start, start + size, phdr.p_vaddr, base_for_dyn },
); );
// NOTE: We can't use a single file-backed mmap for the segment, because p_memsz may be // NOTE: We can't use a single file-backed mmap for the segment, because p_memsz may be
// larger than p_filesz. This difference accounts for the .bss section, which must be // larger than p_filesz. This difference accounts for the .bss section, which must be