diff --git a/README.md b/README.md index 4e2de1a..5d62406 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Checkpointing +## Requirements +This system was tested on linux with kernel version >= 6.11.5. ## Setup @@ -6,8 +8,9 @@ in `/get-tasks`: ``` make -make install +make install ``` +(use `make reinstall` to reinstall the kernel extension) in `/app`: @@ -21,8 +24,13 @@ in `/`: gcc hello-world.c -O3 -no-pie -fno-pic -static ``` -## Run +Anywhere: +``` +echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # disables ASLR +``` +## Run +### GDP & kernel extension in `/`: ``` @@ -40,3 +48,9 @@ sudo ./target/debug/app read sudo ./target/debug/app dump hello.proc sudo ./target/debug/app restore hello.proc ``` + +### ptrace only +in `/app` +``` +sudo ./run.sh +``` \ No newline at end of file diff --git a/app/run.sh b/app/run.sh new file mode 100755 index 0000000..04cbe90 --- /dev/null +++ b/app/run.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SESSION_NAME="runner" + +# Setup tmux session +tmux new-session -d -s $SESSION_NAME +tmux split-window -h -t $SESSION_NAME + +# Starts a.out in the second pane and retrieves the pid of the process +tmux send-keys -t $SESSION_NAME:0.1 "cd .." C-m +tmux send-keys -t $SESSION_NAME:0.1 "./a.out & echo \$! > /tmp/a_out_pid" C-m +sleep 2 + +A_OUT_PID=$(cat /tmp/a_out_pid) +rm -f /tmp/a_out_pid + +echo "PID of a.out: $A_OUT_PID" + +# starts the restore process using the pid found +tmux send-keys -t $SESSION_NAME:0.0 "./target/debug/app ptrace-restore $A_OUT_PID hello.proc" C-m + +# Attach to tmux and setup kill-session on exit +tmux set-option -t $SESSION_NAME destroy-unattached off +tmux set-option -t $SESSION_NAME remain-on-exit off +tmux set-hook -t $SESSION_NAME pane-exited "kill-session" + +tmux attach-session -t $SESSION_NAME \ No newline at end of file diff --git a/app/src/main.rs b/app/src/main.rs index 4798032..53442f6 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -7,10 +7,24 @@ use nix::{ use object::{Object, ObjectSymbol}; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::{env, ffi::CStr, fs::{self, File, OpenOptions}, io::{Read, Seek, Write}, os::fd::AsRawFd}; +use std::{ + env, + ffi::CStr, + fs::{self, File, OpenOptions}, + io::{Read, Seek, Write}, + os::fd::AsRawFd, +}; +use std::{ffi::c_void, io::BufRead}; const INT3: i64 = 0xcc; +#[derive(Debug, Serialize, Deserialize)] +struct Region { + name: String, + from: u64, + data: Vec, +} + #[repr(C)] #[derive(Debug)] struct ProbeRaw { @@ -42,9 +56,32 @@ struct ProbeRaw { } fn new_empty_raw_probe() -> ProbeRaw { - ProbeRaw { pid: 0, rax: 0, rbx: 0, rcx: 0, rdx: 0, r8: 0, r9: 0, r10: 0, r11: 0, r12: 0, r13: 0, - r14: 0, r15: 0, rip: 0, rsp: 0, rbp: 0, ss: 0, rsi: 0, rdi: 0, cs: 0, ds: 0, es: 0, fs: 0, - gs: 0, exe: [0; 4096], + ProbeRaw { + pid: 0, + rax: 0, + rbx: 0, + rcx: 0, + rdx: 0, + r8: 0, + r9: 0, + r10: 0, + r11: 0, + r12: 0, + r13: 0, + r14: 0, + r15: 0, + rip: 0, + rsp: 0, + rbp: 0, + ss: 0, + rsi: 0, + rdi: 0, + cs: 0, + ds: 0, + es: 0, + fs: 0, + gs: 0, + exe: [0; 4096], } } @@ -74,9 +111,9 @@ struct Probe { es: u64, fs: u64, gs: u64, + eflags: u64, exe: String, - stack_from: u64, - stack: Vec, + regions: Vec, } ioctl_readwrite!(read_regs, b'a', 1, ProbeRaw); @@ -104,6 +141,10 @@ enum Command { Restore { dump: String, }, + PtraceRestore { + pid: u64, + dump: String, + }, } fn main() { @@ -134,14 +175,101 @@ fn main() { Command::Restore { dump } => { restore_from_dump(dump); } + Command::PtraceRestore { pid, dump } => { + let pid = Pid::from_raw(pid as i32); + + // let stop_addr = 0x401504 as *mut libc::c_void; // stops in main loop for hello-world + // let stop_addr = 0x401650 as *mut libc::c_void; // stops in function body for hello-world-func.c + let stop_addr = 0x40150b as *mut libc::c_void; // stops in main loop for hello-world-func.c + + let call_instr = stop_with_ptrace(pid, stop_addr); + + dump_with_ptrace(pid, dump.clone()); + restore_from_dump(dump); + + ptrace::write(pid, stop_addr, call_instr) + .expect("failed to write back call instruction"); + ptrace::cont(pid, None).expect("failed to continue process"); + } } } +fn stop_with_ptrace(pid: Pid, stop_addr: *mut c_void) -> i64 { + // ptrace attache to the process + ptrace::attach(pid).expect("failed to seize process"); + waitpid(pid, None).unwrap(); + + println!("process attached !"); + + // replace the call instruction with an interrupt + let call_instr = ptrace::read(pid, stop_addr).expect("failed to read process"); + + println!("instruction read !"); + + ptrace::write(pid, stop_addr, INT3).expect("failed to write interrupt instruction"); + + // restart process and wait for interrupt + ptrace::cont(pid, None).expect("failed to continue process"); + waitpid(pid, None).unwrap(); + + call_instr as i64 +} + +fn dump_with_ptrace(pid: Pid, to: String) { + let exe = fs::read_link(format!("/proc/{}/exe", pid)) + .expect("failed to read exe name") + .into_os_string() + .into_string() + .expect("failed to convert exe name to string"); + + println!("exe: {}", exe); + + let regs = ptrace::getregs(pid).expect("Error when retrieving child process registers"); + + let data = Probe { + pid: pid.as_raw() as u64, + rax: regs.rax, + rbx: regs.rbx, + rcx: regs.rcx, + rdx: regs.rdx, + r8: regs.r8, + r9: regs.r9, + r10: regs.r10, + r11: regs.r11, + r12: regs.r12, + r13: regs.r13, + r14: regs.r14, + r15: regs.r15, + rip: regs.rip, + rsp: regs.rsp, + rbp: regs.rbp, + ss: regs.ss, + rsi: regs.rsi, + rdi: regs.rdi, + cs: regs.cs, + ds: regs.ds, + es: regs.es, + fs: regs.fs, + gs: regs.gs, + eflags: regs.eflags, + exe, + regions: vec![], + }; + + File::create(to) + .expect("Failed to create output file") + .write_all( + postcard::to_allocvec(&data) + .expect("Failed to serialize data") + .as_slice(), + ) + .expect("Failed to write to output file"); +} + fn dump_pid(pid: u64, to: String) { let mut data = read_regs_with_get_tasks(pid); - let stack_from = read_mem_region(Pid::from_raw(pid as i32), "stack", &mut data.stack); - data.stack_from = stack_from; + data.regions = dump_regions(pid); File::create(to) .expect("Failed to create output file") @@ -173,7 +301,7 @@ fn restore_from_dump(dump: String) { ptrace::traceme().expect("Failed to ptrace traceme"); println!("child process setup"); - println!("exe: {:#?}", dump.exe.as_ptr()); + println!("exe: {:#?}", dump.exe); // Stop itself for first setup phase raise(SIGSTOP); @@ -207,27 +335,44 @@ fn restore_from_dump(dump: String) { println!("status after child reached main: {:#?}", status); // Restores registers of the child - let mut regs = - ptrace::getregs(pid).expect("Error when retrieving child process registers"); - - regs.rax = dump.rax; regs.rbx = dump.rbx; regs.rcx = dump.rcx; regs.rdx = dump.rdx; - regs.r8 = dump.r8; regs.r9 = dump.r9; regs.r10 = dump.r10; regs.r11 = dump.r11; - regs.r12 = dump.r12; regs.r13 = dump.r13; regs.r14 = dump.r14; regs.r15 = dump.r15; - - regs.rip = dump.rip; regs.rsp = dump.rsp; regs.rbp = dump.rbp; regs.ss = dump.ss; - regs.rsi = dump.rsi; regs.rdi = dump.rdi; + let mut regs = ptrace::getregs(pid).expect("Error when retrieving child process registers"); + + regs.rax = dump.rax; + regs.rbx = dump.rbx; + regs.rcx = dump.rcx; + regs.rdx = dump.rdx; + regs.r8 = dump.r8; + regs.r9 = dump.r9; + regs.r10 = dump.r10; + regs.r11 = dump.r11; + regs.r12 = dump.r12; + regs.r13 = dump.r13; + regs.r14 = dump.r14; + regs.r15 = dump.r15; + + regs.rip = dump.rip; + regs.rsp = dump.rsp; + regs.rbp = dump.rbp; + regs.ss = dump.ss; + regs.rsi = dump.rsi; + regs.rdi = dump.rdi; regs.cs = dump.cs; - // TODO: find a way to get the real values in get-tasks.c or in another way - //regs.ds = dump.ds; regs.es = dump.es; regs.fs = dump.fs; regs.gs = dump.gs; regs.eflags = 0x202; + regs.ds = dump.ds; + regs.es = dump.es; + regs.fs = dump.fs; + regs.gs = dump.gs; + regs.eflags = dump.eflags; println!("registers restored: {:x} -> {:#?}", regs.rip, regs); ptrace::setregs(pid, regs).expect("Error when setting registers of child process."); // Restores stack of the child - println!("Write stack (from 0x{:x})", dump.stack_from); - write_mem_region(pid, dump.stack_from, &dump.stack); + for region in dump.regions { + println!("Write {} bytes at 0x{:x}", region.data.len(), region.from); + write_mem_region(pid, region.from, ®ion.data); + } ptrace::cont(pid, None).unwrap(); @@ -273,22 +418,7 @@ fn get_mem_region_limits(pid: Pid, region_name: &str) -> (usize, usize) { (region_from, region_to) } -fn read_mem_region(pid: Pid, region_name: &str, data: &mut Vec) -> u64 { - let (region_from, region_to) = get_mem_region_limits(pid, region_name); - - println!("Read {} ({:x}-{:x})", region_name, region_from, region_to); - - let mut mem = File::open(format!("/proc/{}/mem", pid)).expect("Failed to open mem file"); - mem.seek(std::io::SeekFrom::Start(region_from as u64)) - .unwrap(); - - data.resize(region_to - region_from, 0); - mem.read_exact(data.as_mut_slice()).unwrap(); - - region_from as u64 -} - -fn write_mem_region(pid: Pid, mut from: u64, data: &Vec) { +fn write_mem_region(pid: Pid, mut from: u64, data: &[u8]) { println!( "Write {} bytes in region 0x{:x} to 0x{:x}", data.len(), @@ -361,8 +491,49 @@ fn read_regs_with_get_tasks(pid: u64) -> Probe { es: data.es, fs: data.fs, gs: data.gs, + eflags: 0x202, exe, - stack_from: 0, - stack: vec![], + regions: vec![], + } +} + +fn dump_regions(pid: u64) -> Vec { + let map = File::open(format!("/proc/{}/maps", pid)).expect("Failed to open maps file"); + + let mut regions = vec![]; + + for line in std::io::BufReader::new(map).lines() { + let line = line.expect("Failed to read line"); + if let Some(region) = dump_region(&line, pid) { + regions.push(region); + } } + + regions +} + +fn dump_region(line: &str, pid: u64) -> Option { + let regex = Regex::new(r"^([0-9a-f]+)-([0-9a-f]+) [r-]([w-])[x-][p-].*\s(.*)").unwrap(); + let captures = regex.captures(line).unwrap(); + + let is_writable = captures.get(3).unwrap().as_str() == "w"; + let name = captures.get(4).unwrap().as_str().to_owned(); + if ["[vvar]", "[vdso]", "[vsyscall]"].contains(&name.as_str()) || !is_writable { + return None; + } + + let from = u64::from_str_radix(captures.get(1).unwrap().as_str(), 16).unwrap(); + let to = u64::from_str_radix(captures.get(2).unwrap().as_str(), 16).unwrap(); + + println!("Saving region {} (0x{:x}-0x{:x})", name, from, to); + + let mut file = File::open(format!("/proc/{}/mem", pid)).expect("Failed to open mem file"); + file.seek(std::io::SeekFrom::Start(from)) + .expect("Failed to seek in mem file"); + + let mut data = vec![0; (to - from) as usize]; + file.read_exact(data.as_mut_slice()) + .expect("Failed to read from mem file"); + + Some(Region { name, from, data }) } diff --git a/get-tasks/Makefile b/get-tasks/Makefile index a9d9cde..9f7eccd 100644 --- a/get-tasks/Makefile +++ b/get-tasks/Makefile @@ -6,6 +6,9 @@ all: clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean -install: all +install: + sudo insmod get-tasks.ko + +reinstall: all sudo rmmod get-tasks.ko sudo insmod get-tasks.ko diff --git a/hello-world-func.c b/hello-world-func.c new file mode 100644 index 0000000..15c8535 --- /dev/null +++ b/hello-world-func.c @@ -0,0 +1,13 @@ +#include + +int __attribute__ ((noinline)) time_2(int n) { + asm (""); + return 2 * n; +} + +int main() { + for (int i = 0; i < 10000000; i++) { + int i_mult = time_2(i); + printf("%d: Hello world!\n", i_mult); + } +}