Rough Draft, can dump memory via ptrace

This commit is contained in:
2026-01-19 14:09:43 -06:00
parent 711f4ed019
commit 656d26c21d
+209
View File
@@ -0,0 +1,209 @@
// Copyright (C) 2026 William Welna (wwelna@occultusterra.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following condition.
// * The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// In addition, the following restrictions apply:
// * The software, either in source or compiled binary form, with or without any
// modification, may not be used with or incorporated into any other software
// that used an Artificial Intelligence (AI) model and/or Large Language Model
// (LLM) to generate any portion of that other software's source code, binaries,
// or artwork.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
const std = @import("std");
const clap = @import("clap");
var gpa = std.heap.DebugAllocator(.{}){};
const gpa_allocator = gpa.allocator();
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer_wrapper = std.fs.File.stdout().writer(&stdout_buffer);
const stdout: *std.io.Writer = &stdout_writer_wrapper.interface;
const MapEntry = struct {
start_addr: usize,
end_addr: usize,
readable: bool,
writable: bool,
executable: bool,
is_private: bool,
offset: usize,
dev_major: u8,
dev_minor: u8,
inode: usize,
path: []const u8,
chunk: ?[]u8,
};
const Error = error{
readMemError,
ptraceAttach,
ptraceDetach,
ptracePeek,
};
// This use to work in a time long ago but doesn't anymore (needs CAP_SYS_PTRACE), etc. so PTRACE it is
// pub fn _broken_grabChunk(pid: std.os.linux.pid_t, start_addr: usize, end_addr: usize) ![]u8 {
// var open_buffer: [256]u8 = undefined;
// var read_buffer: [1024]u8 = undefined;
// const size: usize = end_addr - start_addr;
// var mem = try std.fs.cwd().openFile(try std.fmt.bufPrint(&open_buffer, "/proc/{any}/mem", .{pid}), .{ .mode = .read_only });
// defer mem.close();
// var frb = mem.reader(&read_buffer);
// var reader = &frb.interface;
// try mem.seekTo(start_addr);
// const ret = try gpa_allocator.alloc(u8, size);
// errdefer gpa_allocator.free(ret);
// try reader.readSliceAll(ret);
// std.debug.print("[{d}] Scanning Memory Block {x} of size {d} via /proc/\n", .{ pid, start_addr, size });
// if (ret.len != size) return Error.readMemError else return ret;
// }
pub fn grabChunk(pid: std.os.linux.pid_t, start_addr: usize, end_addr: usize) ![]u8 {
const size: usize = end_addr - start_addr;
const ret = try gpa_allocator.alloc(u8, size);
errdefer gpa_allocator.free(ret);
try stdout.print("[{d}] Scanning Memory Block {x} of size {d}\n", .{ pid, start_addr, size });
try stdout.flush();
var call_count = size / @sizeOf(usize);
var addr = start_addr;
while (call_count > 0) : (call_count -= 1) {
const word = try peek(pid, addr);
const copy_len = @min(@sizeOf(usize), end_addr - addr);
const offset = (addr - start_addr);
@memcpy(ret[offset..(copy_len + offset)], std.mem.asBytes(&word)[0..copy_len]);
addr += @sizeOf(usize);
}
return ret;
}
pub fn openmap(pid: std.os.linux.pid_t) !std.ArrayList(MapEntry) {
var open_buffer: [256]u8 = undefined;
var read_buffer: [1024]u8 = undefined;
var ret: std.ArrayList(MapEntry) = .empty;
errdefer ret.deinit(gpa_allocator);
var map = try std.fs.cwd().openFile(try std.fmt.bufPrint(&open_buffer, "/proc/{any}/maps", .{pid}), .{ .mode = .read_only });
defer map.close();
var frb = map.reader(&read_buffer);
var reader = &frb.interface;
while (reader.takeDelimiterInclusive('\n')) |line| {
var it = std.mem.splitAny(u8, line, " ");
const addr_range_str = it.next().?;
const perms_str = it.next().?;
const offset_str = it.next().?;
const dev_str = it.next().?;
const inode_str = it.next().?;
const path = it.rest();
const path_dup = try gpa_allocator.dupe(u8, path);
errdefer gpa_allocator.free(path_dup);
var addr_it = std.mem.splitAny(u8, addr_range_str, "-");
const start_addr = try std.fmt.parseInt(usize, addr_it.next().?, 16);
const end_addr = try std.fmt.parseInt(usize, addr_it.next().?, 16);
var dev_it = std.mem.splitAny(u8, dev_str, ":");
try ret.append(gpa_allocator, .{
.start_addr = start_addr,
.end_addr = end_addr,
.readable = if (perms_str[0] == 'r') true else false,
.writable = if (perms_str[1] == 'w') true else false,
.executable = if (perms_str[2] == 'x') true else false,
.is_private = if (perms_str[3] == 'p') true else false,
.offset = try std.fmt.parseInt(usize, offset_str, 16),
.dev_major = @truncate(try std.fmt.parseInt(usize, dev_it.next().?, 16)),
.dev_minor = @truncate(try std.fmt.parseInt(usize, dev_it.next().?, 16)),
.inode = try std.fmt.parseInt(usize, inode_str, 10),
.path = path_dup,
.chunk = if (perms_str[1] == 'w') try grabChunk(pid, start_addr, end_addr) else null,
});
//break;
} else |err| switch (err) {
error.EndOfStream => {},
else => {
return err;
},
}
return ret;
}
pub fn closemap(map: *std.ArrayList(MapEntry)) void {
for (map.items) |e| {
gpa_allocator.free(e.path);
if (e.chunk) |v| gpa_allocator.free(v);
}
map.deinit(gpa_allocator);
}
pub fn attach(pid: std.os.linux.pid_t) !void {
const ret = std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0);
if (ret != 0) {
std.debug.print("[{d}] PTRACE.ATTACH Error: {d}\n", .{ pid, ret });
return Error.ptraceAttach;
}
var status: u32 = undefined;
_ = std.os.linux.waitpid(pid, &status, 0);
}
pub fn detach(pid: std.os.linux.pid_t) !void {
const ret = std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0);
if (ret != 0) {
std.debug.print("[{d}] PTRACE.DETACH Error: {d}\n", .{ pid, ret });
return Error.ptraceDetach;
}
}
// pub fn cont(pid: std.os.linux.pid_t) !void {
// const ret = std.os.linux.ptrace(std.os.linux.PTRACE.CONT, pid, 0, 0, 0);
// if (ret != 0) return Error.ptraceDetach;
// }
pub fn peek(pid: std.os.linux.pid_t, addr: u64) !usize {
const word = std.os.linux.ptrace(std.os.linux.PTRACE.PEEKDATA, pid, addr, 0, 0);
return word;
}
pub fn dopid(pid: std.os.linux.pid_t) !void {
try attach(pid);
var map = try openmap(pid);
defer closemap(&map);
try detach(pid);
}
pub fn main() !void {
defer _ = gpa.deinit();
// TODO: Make this actually work
try dopid(3140335);
try stdout.flush();
}