├── .gitignore ├── .travis.yml ├── COPYING ├── Cargo.toml ├── README.md ├── src └── lib.rs └── tests └── read-and-write.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2015 Torrie Fischer 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ptrace" 3 | version = "0.1.0" 4 | authors = ["Torrie Fischer "] 5 | description = "Rust bindings for the POSIX ptrace API" 6 | repository = "http://github.com/codius/rust-ptrace" 7 | license = "MIT" 8 | readme = "README.md" 9 | 10 | [lib] 11 | name = "ptrace" 12 | 13 | [dependencies] 14 | bitflags = "0.1" 15 | posix-ipc = "0.0" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust ptrace 2 | ============== 3 | 4 | A rust crate that provides safe bindings to the POSIX ptrace() API. 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[allow(unstable)] 2 | extern crate libc; 3 | extern crate "posix-ipc" as ipc; 4 | #[macro_use] 5 | extern crate bitflags; 6 | 7 | use std::os; 8 | use std::ptr; 9 | use std::default::Default; 10 | use std::vec::Vec; 11 | use std::mem; 12 | use std::iter; 13 | use std::num::FromPrimitive; 14 | use std::cmp::min; 15 | 16 | pub type Address = u64; 17 | pub type Word = u64; 18 | 19 | #[derive(Copy)] 20 | pub enum Action { 21 | Allow, 22 | Kill 23 | } 24 | 25 | #[derive(Debug, Copy)] 26 | pub enum Request { 27 | TraceMe = 0, 28 | PeekText = 1, 29 | PeekData = 2, 30 | PeekUser = 3, 31 | PokeText = 4, 32 | PokeData = 5, 33 | PokeUser = 6, 34 | Continue = 7, 35 | Kill = 8, 36 | SingleStep = 9, 37 | GetRegs = 12, 38 | SetRegs = 13, 39 | Attach = 16, 40 | Detatch = 17, 41 | SetOptions = 0x4200, 42 | Seize = 0x4206 43 | } 44 | 45 | #[derive(Copy, Debug, FromPrimitive)] 46 | pub enum Event { 47 | Fork = 1, 48 | VFork = 2, 49 | Clone = 3, 50 | Exec = 4, 51 | VForkDone = 5, 52 | Exit = 6, 53 | Seccomp = 7, 54 | Stop = 128 55 | } 56 | 57 | impl Event { 58 | pub fn from_wait_status(st: i32) -> Option { 59 | let e: Option = FromPrimitive::from_i32(((st >> 8) & !5) >> 8); 60 | return e; 61 | } 62 | } 63 | 64 | #[derive(Copy, Default, Debug)] 65 | pub struct Registers { 66 | pub r15: Word, 67 | pub r14: Word, 68 | pub r13: Word, 69 | pub r12: Word, 70 | pub rbp: Word, 71 | pub rbx: Word, 72 | pub r11: Word, 73 | pub r10: Word, 74 | pub r9: Word, 75 | pub r8: Word, 76 | pub rax: Word, 77 | pub rcx: Word, 78 | pub rdx: Word, 79 | pub rsi: Word, 80 | pub rdi: Word, 81 | pub orig_rax: Word, 82 | pub rip: Word, 83 | pub cs: Word, 84 | pub eflags: Word, 85 | pub rsp: Word, 86 | pub ss: Word, 87 | pub fs_base: Word, 88 | pub gs_base: Word, 89 | pub ds: Word, 90 | pub es: Word, 91 | pub fs: Word, 92 | pub gs: Word 93 | } 94 | 95 | bitflags! { 96 | flags Options: u32 { 97 | const SysGood = 1, 98 | const TraceFork = 1 << 1, 99 | const TraceVFork = 1 << 2, 100 | const TraceClone = 1 << 3, 101 | const TraceExec = 1 << 4, 102 | const TraceVForkDone = 1 << 5, 103 | const TraceExit = 1 << 6, 104 | const TraceSeccomp = 1 << 7, 105 | const ExitKill = 1 << 20 106 | } 107 | } 108 | 109 | pub fn setoptions(pid: libc::pid_t, opts: Options) -> Result { 110 | unsafe { 111 | raw (Request::SetOptions, pid, ptr::null_mut(), opts.bits as *mut 112 | libc::c_void) 113 | } 114 | } 115 | 116 | pub fn getregs(pid: libc::pid_t) -> Result { 117 | let mut buf: Registers = Default::default(); 118 | let buf_mut: *mut Registers = &mut buf; 119 | 120 | match unsafe { 121 | raw (Request::GetRegs, pid, ptr::null_mut(), buf_mut as *mut libc::c_void) 122 | } { 123 | Ok(_) => Ok(buf), 124 | Err(e) => Err(e) 125 | } 126 | } 127 | 128 | pub fn setregs(pid: libc::pid_t, regs: &Registers) -> Result { 129 | unsafe { 130 | let buf: *mut libc::c_void = mem::transmute(regs); 131 | raw (Request::SetRegs, pid, ptr::null_mut(), buf) 132 | } 133 | } 134 | 135 | pub fn seize(pid: libc::pid_t) -> Result { 136 | unsafe { 137 | raw (Request::Seize, pid, ptr::null_mut(), ptr::null_mut()) 138 | } 139 | } 140 | 141 | pub fn attach(pid: libc::pid_t) -> Result { 142 | unsafe { 143 | raw (Request::Attach, pid, ptr::null_mut(), ptr::null_mut()) 144 | } 145 | } 146 | 147 | pub fn release(pid: libc::pid_t, signal: ipc::signals::Signal) -> Result { 148 | unsafe { 149 | raw (Request::Detatch, pid, ptr::null_mut(), (signal as u32) as *mut libc::c_void) 150 | } 151 | } 152 | 153 | pub fn cont(pid: libc::pid_t, signal: ipc::signals::Signal) -> Result { 154 | unsafe { 155 | raw (Request::Continue, pid, ptr::null_mut(), (signal as u32) as *mut libc::c_void) 156 | } 157 | } 158 | 159 | pub fn traceme() -> Result { 160 | unsafe { 161 | raw (Request::TraceMe, 0, ptr::null_mut(), ptr::null_mut()) 162 | } 163 | } 164 | 165 | unsafe fn raw(request: Request, 166 | pid: libc::pid_t, 167 | addr: *mut libc::c_void, 168 | data: *mut libc::c_void) -> Result { 169 | let v = ptrace (request as libc::c_int, pid, addr, data); 170 | match v { 171 | -1 => Result::Err(os::errno()), 172 | _ => Result::Ok(v) 173 | } 174 | } 175 | 176 | extern { 177 | fn ptrace(request: libc::c_int, 178 | pid: libc::pid_t, 179 | addr: *mut libc::c_void, 180 | data: *mut libc::c_void) -> libc::c_long; 181 | } 182 | 183 | #[derive(Copy, Debug)] 184 | pub struct Syscall { 185 | pub args: [Word; 6], 186 | pub call: u64, 187 | pub pid: libc::pid_t, 188 | pub returnVal: Word 189 | } 190 | 191 | impl Syscall { 192 | pub fn from_pid(pid: libc::pid_t) -> Result { 193 | match getregs (pid) { 194 | Ok(regs) => 195 | Ok(Syscall { 196 | pid: pid, 197 | call: regs.orig_rax, 198 | args: [regs.rdi, regs.rsi, regs.rdx, regs.rcx, regs.r8, regs.r9], 199 | returnVal: 0 200 | }), 201 | Err(e) => Err(e) 202 | } 203 | } 204 | 205 | pub fn write(&self) -> Result { 206 | match getregs(self.pid) { 207 | Ok(mut regs) => { 208 | regs.rdi = self.args[0]; 209 | regs.rsi = self.args[1]; 210 | regs.rdx = self.args[2]; 211 | regs.rcx = self.args[3]; 212 | regs.r8 = self.args[4]; 213 | regs.r9 = self.args[5]; 214 | regs.orig_rax = self.call; 215 | regs.rax = self.returnVal; 216 | setregs(self.pid, ®s) 217 | }, 218 | Err(e) => Err(e) 219 | } 220 | } 221 | } 222 | 223 | #[derive(Copy)] 224 | pub struct Reader { 225 | pub pid: libc::pid_t 226 | } 227 | 228 | #[derive(Copy)] 229 | pub struct Writer { 230 | pub pid: libc::pid_t 231 | } 232 | 233 | impl Writer { 234 | pub fn new(pid: libc::pid_t) -> Self { 235 | Writer { 236 | pid: pid 237 | } 238 | } 239 | 240 | pub fn poke_data(&self, address: Address, data: Word) -> Result { 241 | match unsafe { 242 | raw (Request::PokeData, self.pid, address as *mut libc::c_void, data as *mut libc::c_void) 243 | } { 244 | Err(e) => Err(e), 245 | Ok(r) => Ok(r as Word) 246 | } 247 | } 248 | 249 | pub fn write_object(&self, address: Address, data: &T) -> Result<(), usize> { 250 | let mut buf = Vec::with_capacity(mem::size_of::()); 251 | unsafe { 252 | let tptr: *const T = data; 253 | let p: *const u8 = mem::transmute(tptr); 254 | for i in range(0, buf.capacity()) { 255 | buf.push(*p.offset(i as isize)); 256 | } 257 | } 258 | 259 | Ok(()) 260 | } 261 | 262 | pub fn write_data(&self, address: Address, buf: &Vec) -> Result<(), usize> { 263 | // The end of our range 264 | let max_addr = address + buf.len() as Address; 265 | // The last word we can completely overwrite 266 | let align_end = max_addr - (max_addr % mem::size_of::() as Address); 267 | for write_addr in iter::range_step(address, align_end, mem::size_of::() as Address) { 268 | let mut d: Word = 0; 269 | let buf_idx = (write_addr - address) as usize; 270 | for word_idx in iter::range(0, mem::size_of::()) { 271 | d = set_byte(d, word_idx, buf[buf_idx + word_idx]); 272 | } 273 | match self.poke_data(write_addr, d) { 274 | Ok(_) => {}, 275 | Err(e) => return Err(e) 276 | } 277 | } 278 | // Handle a partial word overwrite 279 | if max_addr > align_end { 280 | let buf_start = buf.len() - (max_addr - align_end) as usize; 281 | let r = Reader::new(self.pid); 282 | let mut d = match r.peek_data(align_end) { 283 | Ok(v) => v, 284 | Err(e) => return Err(e) 285 | }; 286 | for word_idx in iter::range(0, mem::size_of::()-2) { 287 | let buf_idx = buf_start + word_idx; 288 | d = set_byte(d, word_idx, buf[buf_idx]); 289 | } 290 | match self.poke_data(align_end, d) { 291 | Ok(_) => {}, 292 | Err(e) => return Err(e) 293 | } 294 | } 295 | Ok(()) 296 | } 297 | } 298 | 299 | impl Reader { 300 | pub fn new(pid: libc::pid_t) -> Reader { 301 | Reader { 302 | pid: pid 303 | } 304 | } 305 | 306 | pub fn peek_data(&self, address: Address) -> Result { 307 | let l; 308 | unsafe { 309 | l = raw (Request::PeekData, self.pid, address as *mut libc::c_void, ptr::null_mut()) 310 | } 311 | match l { 312 | Result::Err(e) => Result::Err(e), 313 | _ => Result::Ok(l.unwrap() as Word) 314 | } 315 | } 316 | 317 | pub fn read_string(&self, address: Address) -> Result, usize> { 318 | let mut end_of_str = false; 319 | let mut buf: Vec = Vec::with_capacity(1024); 320 | let max_addr = address + buf.capacity() as Address; 321 | let align_end = max_addr - (max_addr % mem::size_of::() as Address); 322 | 'finish: for read_addr in iter::range_step(address, align_end, mem::size_of::() as Address) { 323 | let d; 324 | match self.peek_data(read_addr) { 325 | Ok(v) => d = v, 326 | Err(e) => return Err(e) 327 | } 328 | for word_idx in iter::range(0, mem::size_of::()) { 329 | let chr = get_byte(d, word_idx); 330 | if chr == 0 { 331 | end_of_str = true; 332 | break 'finish; 333 | } 334 | buf.push (chr); 335 | } 336 | } 337 | if !end_of_str { 338 | let d; 339 | match self.peek_data(align_end) { 340 | Ok(v) => d = v, 341 | Err(e) => return Err(e) 342 | } 343 | for word_idx in range(0, mem::size_of::()) { 344 | let chr = get_byte(d, word_idx); 345 | if chr == 0 { 346 | break; 347 | } 348 | buf.push (chr); 349 | } 350 | } 351 | return Ok(buf); 352 | } 353 | } 354 | 355 | fn get_byte(d: Word, byte_idx: usize) -> u8 { 356 | assert!(byte_idx < mem::size_of::() * 8); 357 | ((d >> (byte_idx * 8)) & 0xff) as u8 358 | } 359 | 360 | fn set_byte(d: Word, byte_idx: usize, value: u8) -> Word { 361 | assert!(byte_idx < mem::size_of::() * 8); 362 | let shift = mem::size_of::() * 8 * byte_idx; 363 | let mask = (0xff << shift); 364 | (d & !mask) | (((value as Word) << shift) & mask) 365 | } 366 | 367 | #[test] 368 | pub fn test_set_byte() { 369 | assert_eq!(set_byte(0, 0, 0), 0); 370 | assert_eq!(set_byte(0xffffffffffff, 0, 0xff), 0xffffffffffff); 371 | assert_eq!(set_byte(0xffffffffffff, 0, 0), 0xffffffffff00); 372 | assert_eq!(set_byte(0xffffffffffff, 0, 0xaa), 0xffffffffffaa); 373 | assert_eq!(set_byte(0xffffffffffff, 1, 0x00), 0xffffffff00ff); 374 | assert_eq!(set_byte(0xffffffffffff, 4, 0xaa), 0xffaaffffffff); 375 | } 376 | 377 | #[test] 378 | pub fn test_get_byte() { 379 | assert_eq!(get_byte(0, 0), 0); 380 | assert_eq!(get_byte(0xffffffffffff, 0), 0xff); 381 | assert_eq!(get_byte(0xffffffffffff, 8), 0xff); 382 | assert_eq!(get_byte(0xffffffffffaa, 0), 0xaa); 383 | assert_eq!(get_byte(0x0123456789ab, 1), 0x89); 384 | assert_eq!(get_byte(0x0123456789ab, 4), 0x23); 385 | } 386 | -------------------------------------------------------------------------------- /tests/read-and-write.rs: -------------------------------------------------------------------------------- 1 | #![feature(libc, std_misc)] 2 | extern crate libc; 3 | extern crate ptrace; 4 | extern crate "posix-ipc" as ipc; 5 | 6 | use std::ffi::CString; 7 | use std::os; 8 | use std::ptr; 9 | 10 | #[test] 11 | fn test_attach_detach() { 12 | let pid = fork_and_halt(); 13 | assert!(match ptrace::attach(pid) { Ok(_) => true, _ => false }); 14 | unsafe { waitpid(pid, ptr::null_mut(), 0) }; 15 | assert!(match ptrace::release(pid, ipc::signals::Signal::None) { Ok(_) => true, _ => false }); 16 | } 17 | 18 | #[test] 19 | fn test_read() { 20 | let (buf_addr, pid) = fork_with_buffer("foobar"); 21 | let reader = ptrace::Reader::new(pid); 22 | match reader.peek_data(unsafe { buf_addr.offset(3) } as u64) { 23 | Ok(v) => assert_eq!((v & 0xff) as u8, 'b' as u8), 24 | Err(_) => panic!("Error while reading: {:?}", os::last_os_error()) 25 | } 26 | } 27 | 28 | #[test] 29 | fn test_read_string() { 30 | let (buf_addr, pid) = fork_with_buffer("foobar"); 31 | let reader = ptrace::Reader::new(pid); 32 | match reader.read_string(buf_addr as u64) { 33 | Ok(v) => 34 | assert_eq!(v, vec!('f' as u8, 'o' as u8, 'o' as u8, 'b' as u8, 'a' as u8, 'r' as u8)), 35 | Err(_) => 36 | panic!("Error while reading string: {:?}", os::last_os_error()) 37 | } 38 | } 39 | 40 | #[test] 41 | fn test_write() { 42 | let (buf_addr, pid) = fork_with_buffer("foobar"); 43 | let foo_word = 0x0123456789abcdef; 44 | 45 | let writer = ptrace::Writer::new(pid); 46 | match writer.poke_data(buf_addr as u64, foo_word) { 47 | Ok(_) => { 48 | let reader = ptrace::Reader::new(pid); 49 | let v = reader.peek_data(buf_addr as u64).ok().expect("Could not read back word"); 50 | assert_eq!(v, foo_word); 51 | }, 52 | Err(_) => 53 | panic!("Error while writing char: {:?}", os::last_os_error()) 54 | } 55 | } 56 | 57 | #[test] 58 | // Test that we only overwrite the first few bytes, nothing more or less. 59 | fn test_write_small_buf() { 60 | use std::str; 61 | let (buf_addr, pid) = fork_with_buffer("foobar and then some"); 62 | let writer = ptrace::Writer::new(pid); 63 | let buf = vec!('F' as u8, 'O' as u8, 'O' as u8, 'B' as u8, 'A' as u8, 'R' as u8); 64 | match writer.write_data(buf_addr as u64, &buf) { 65 | Ok(_) => { 66 | let reader = ptrace::Reader::new(pid); 67 | let v = reader.read_string(buf_addr as u64).ok().expect("Could not read back buffer"); 68 | assert_eq!(str::from_utf8(v.as_slice()), Ok("FOOBAR and then some")); 69 | }, 70 | Err(_) => 71 | panic!("Error while writing buffer: {:?}", os::last_os_error()) 72 | } 73 | } 74 | 75 | #[test] 76 | // Test that we only overwrite the first few words, nothing more or less. 77 | fn test_write_large_buf() { 78 | use std::str; 79 | let s = "foo bar baz frob fritz friddle"; 80 | let (buf_addr, pid) = fork_with_buffer(s); 81 | let writer = ptrace::Writer::new(pid); 82 | let mut buf: Vec = Vec::new(); 83 | buf.push_all("FRIDDLE FRITZ FROB BAZ BAR FOO".as_bytes()); 84 | match writer.write_data(buf_addr as u64, &buf) { 85 | Ok(_) => { 86 | let reader = ptrace::Reader::new(pid); 87 | let v = reader.read_string(buf_addr as u64).ok().expect("Could not read back buffer"); 88 | assert_eq!(str::from_utf8(v.as_slice()), str::from_utf8(buf.as_slice())); 89 | }, 90 | Err(_) => 91 | panic!("Error while writing buffer: {:?}", os::last_os_error()) 92 | } 93 | } 94 | 95 | fn fork_with_buffer(buf: &str) -> (*const libc::c_char, libc::c_int) { 96 | let buf = CString::from_slice(buf.as_bytes()); 97 | let buf_addr: *const libc::c_char = buf.as_ptr(); 98 | let pid = fork_and_halt(); 99 | ptrace::attach(pid).ok().expect("Could not attach to child"); 100 | unsafe { waitpid(pid, ptr::null_mut(), 0) }; 101 | return (buf_addr, pid); 102 | } 103 | 104 | fn fork_and_halt() -> libc::c_int { 105 | match unsafe { fork() } { 106 | 0 => { 107 | loop { 108 | unsafe { raise(19) }; 109 | } 110 | }, 111 | v => v 112 | } 113 | } 114 | 115 | extern "C" { 116 | fn fork() -> libc::pid_t; 117 | fn raise(signal: libc::c_int) -> libc::c_int; 118 | fn waitpid(pid: libc::pid_t, status: *mut libc::c_int, options: libc::c_int) -> libc::c_int; 119 | } 120 | --------------------------------------------------------------------------------