Skip to content

Commit 33f89ff

Browse files
committed
Handle pie executables
1 parent c1312ca commit 33f89ff

File tree

8 files changed

+213
-18
lines changed

8 files changed

+213
-18
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,9 @@ memmap = "0.7.0"
2626
mach = "0.3"
2727
security-framework-sys = "1.0"
2828

29+
# Dependencies specific to Linux
30+
[target.'cfg(target_os="linux")'.dependencies]
31+
procfs = "0.8.0"
32+
2933
[dev-dependencies]
3034
rustyline = "6.2.0"

src/symbol/mod.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
use gimli::read::{EvaluationResult, Reader as _};
55
use object::{
6-
read::{Object, ObjectSection, Symbol},
6+
read::{Object, ObjectSection, ObjectSegment, Symbol},
77
SymbolKind,
88
};
99
use std::{
1010
borrow::Cow,
1111
collections::{BTreeMap, HashMap},
1212
fs::File,
13+
path::Path,
1314
rc::Rc,
1415
};
1516

@@ -223,7 +224,9 @@ mod inner {
223224
// todo: impl loader struct instead of taking 'path' as an argument.
224225
// It will be required to e.g. load coredumps, or external debug info, or to
225226
// communicate with rustc/lang servers.
226-
pub fn new(path: &str) -> Result<Dwarf, Box<dyn std::error::Error>> {
227+
pub fn new<P: AsRef<std::path::Path>>(
228+
path: P,
229+
) -> Result<Dwarf, Box<dyn std::error::Error>> {
227230
// Load ELF/Mach-O object file
228231
let file = File::open(path)?;
229232

@@ -277,3 +280,132 @@ impl Dwarf {
277280
self.rent(|parsed| parsed.get_var_address(name))
278281
}
279282
}
283+
284+
pub struct RelocatedDwarf(Vec<RelocatedDwarfEntry>);
285+
286+
struct RelocatedDwarfEntry {
287+
address_range: (u64, u64),
288+
file_range: (u64, u64),
289+
bias: u64,
290+
dwarf: Dwarf,
291+
}
292+
293+
impl std::fmt::Debug for RelocatedDwarfEntry {
294+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295+
f.debug_struct("RelocatedDwarfEntry")
296+
.field(
297+
"address_range",
298+
&format!(
299+
"{:016x}..{:016x}",
300+
self.address_range.0, self.address_range.1
301+
),
302+
)
303+
.field(
304+
"file_range",
305+
&format!("{:016x}..{:016x}", self.file_range.0, self.file_range.1),
306+
)
307+
.field("bias", &format!("{:016x}", self.bias))
308+
.field(
309+
"stated_range",
310+
&format!(
311+
"{:016x}..{:016x}",
312+
self.address_range.0 - self.bias,
313+
self.address_range.1 - self.bias
314+
),
315+
)
316+
.finish()
317+
}
318+
}
319+
320+
impl RelocatedDwarfEntry {
321+
fn from_file_and_offset(
322+
address: (u64, u64),
323+
file: &Path,
324+
offset: u64,
325+
) -> Result<Self, Box<dyn std::error::Error>> {
326+
match Dwarf::new(file) {
327+
Ok(dwarf) => {
328+
let (file_range, stated_address) = dwarf
329+
.rent(|parsed| {
330+
let object: &object::File = &parsed.object;
331+
object.segments().find_map(|segment: object::Segment| {
332+
// Sometimes the offset is just before the start file offset of the segment.
333+
if offset <= segment.file_range().0 {
334+
Some((
335+
segment.file_range(),
336+
segment.address() - (segment.file_range().0 - offset),
337+
))
338+
} else {
339+
None
340+
}
341+
})
342+
})
343+
.ok_or_else(|| {
344+
format!(
345+
"Couldn't find segment for `{}`+0x{:x}",
346+
file.display(),
347+
offset
348+
)
349+
})?;
350+
Ok(RelocatedDwarfEntry {
351+
address_range: address,
352+
file_range,
353+
bias: address.0 - stated_address,
354+
dwarf,
355+
})
356+
}
357+
Err(err) => Err(err),
358+
}
359+
}
360+
}
361+
362+
impl RelocatedDwarf {
363+
pub fn from_maps(
364+
maps: &[crate::target::MemoryMap],
365+
) -> Result<Self, Box<dyn std::error::Error>> {
366+
let vec: Result<Vec<_>, _> = maps
367+
.iter()
368+
.filter_map(|map| {
369+
map.backing_file.as_ref().map(|&(ref file, offset)| {
370+
RelocatedDwarfEntry::from_file_and_offset(map.address, file, offset)
371+
})
372+
})
373+
.collect();
374+
let vec = vec?;
375+
Ok(RelocatedDwarf(vec))
376+
}
377+
378+
pub fn get_symbol_address(&self, name: &str) -> Option<usize> {
379+
for entry in &self.0 {
380+
if let Some(addr) = entry.dwarf.get_symbol_address(name) {
381+
if addr as u64 + entry.bias >= entry.address_range.1 {
382+
continue;
383+
}
384+
return Some(addr + entry.bias as usize);
385+
}
386+
}
387+
None
388+
}
389+
390+
pub fn get_address_symbol(&self, addr: usize) -> Option<String> {
391+
for entry in &self.0 {
392+
if (addr as u64) < entry.address_range.0 || addr as u64 >= entry.address_range.1 {
393+
continue;
394+
}
395+
return entry.dwarf.get_address_symbol(addr - entry.bias as usize);
396+
}
397+
None
398+
}
399+
400+
pub fn get_var_address(&self, name: &str) -> Option<usize> {
401+
for entry in &self.0 {
402+
if let Some(addr) = entry.dwarf.get_var_address(name) {
403+
if addr as u64 + entry.bias >= entry.address_range.1 {
404+
continue;
405+
}
406+
return Some(addr + entry.bias as usize);
407+
}
408+
}
409+
None
410+
}
411+
}

src/target/linux.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ impl LinuxTarget {
123123
offset as _,
124124
)
125125
}
126+
127+
pub fn memory_maps(&self) -> Result<Vec<super::MemoryMap>, Box<dyn std::error::Error>> {
128+
Ok(procfs::process::Process::new(self.pid.as_raw())?
129+
.maps()?
130+
.into_iter()
131+
.map(|map| super::MemoryMap {
132+
address: map.address,
133+
backing_file: match map.pathname {
134+
procfs::process::MMapPath::Path(path) => Some((path, map.offset)),
135+
_ => None,
136+
},
137+
})
138+
.collect())
139+
}
126140
}
127141

128142
/// A single memory read operation.

src/target/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ pub use linux::*;
1212
mod macos;
1313
#[cfg(target_os = "macos")]
1414
pub use macos::*;
15+
16+
#[derive(Debug)]
17+
pub struct MemoryMap {
18+
/// Start and end range of the mapped memory.
19+
pub address: (u64, u64),
20+
/// The file and file offset backing the mapped memory if any.
21+
pub backing_file: Option<(std::path::PathBuf, u64)>,
22+
}

src/target/unix.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub trait UnixTarget {
1313
/// Continues execution of a debuggee.
1414
fn unpause(&self) -> Result<(), Box<dyn std::error::Error>> {
1515
ptrace::cont(self.pid(), None)?;
16+
waitpid(self.pid(), None).unwrap();
1617
Ok(())
1718
}
1819
}
@@ -32,6 +33,12 @@ pub(crate) fn launch(path: &str) -> Result<Pid, Box<dyn std::error::Error>> {
3233
ForkResult::Child => {
3334
ptrace::traceme()?;
3435

36+
// Disable ASLR
37+
#[cfg(target_os = "linux")]
38+
unsafe {
39+
libc::personality(0x0040000 /*ADDR_NO_RANDOMIZE*/);
40+
}
41+
3542
let path = CString::new(path)?;
3643
execv(&path, &[path.as_ref()])?;
3744

tests/readmem.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
mod test_utils;
44

55
#[cfg(target_os = "linux")]
6-
use headcrab::{symbol::Dwarf, target::LinuxTarget, target::UnixTarget};
6+
use headcrab::{symbol::RelocatedDwarf, target::LinuxTarget, target::UnixTarget};
77

88
static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello");
99

@@ -20,29 +20,29 @@ static MAC_DSYM_PATH: &str = concat!(
2020
fn read_memory() -> Result<(), Box<dyn std::error::Error>> {
2121
test_utils::ensure_testees();
2222

23-
#[cfg(target_os = "macos")]
24-
let debuginfo = Dwarf::new(MAC_DSYM_PATH)?;
25-
#[cfg(not(target_os = "macos"))]
26-
let debuginfo = Dwarf::new(BIN_PATH)?;
23+
let target = LinuxTarget::launch(BIN_PATH)?;
24+
25+
println!("{:#?}", target.memory_maps()?);
26+
let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?;
2727

2828
// Test that `a_function` resolves to a function.
29-
let addr = debuginfo.get_symbol_address("a_function");
30-
assert!(addr.is_some());
29+
let breakpoint_addr = debuginfo.get_symbol_address("breakpoint");
30+
assert!(breakpoint_addr.is_some());
3131

3232
// Test that the address of `a_function` and one byte further both resolves back to that symbol.
3333
assert_eq!(
3434
debuginfo
35-
.get_address_symbol(addr.unwrap())
35+
.get_address_symbol(breakpoint_addr.unwrap())
3636
.as_ref()
3737
.map(|name| &**name),
38-
Some("a_function")
38+
Some("breakpoint")
3939
);
4040
assert_eq!(
4141
debuginfo
42-
.get_address_symbol(addr.unwrap() + 1)
42+
.get_address_symbol(breakpoint_addr.unwrap() + 1)
4343
.as_ref()
4444
.map(|name| &**name),
45-
Some("a_function")
45+
Some("breakpoint")
4646
);
4747

4848
// Test that invalid addresses don't resolve to a symbol.
@@ -59,12 +59,38 @@ fn read_memory() -> Result<(), Box<dyn std::error::Error>> {
5959
None,
6060
);
6161

62+
// Write breakpoint to the `breakpoint` function.
63+
let mut pause_inst = 0 as libc::c_ulong;
64+
unsafe {
65+
target
66+
.read()
67+
.read(&mut pause_inst, breakpoint_addr.unwrap())
68+
.apply()?;
69+
}
70+
// pause (rep nop); ...
71+
assert_eq!(&pause_inst.to_ne_bytes()[0..2], &[0xf3, 0x90]);
72+
let mut breakpoint_inst = pause_inst.to_ne_bytes();
73+
// int3; nop; ...
74+
breakpoint_inst[0] = 0xcc;
75+
nix::sys::ptrace::write(
76+
target.pid(),
77+
breakpoint_addr.unwrap() as *mut _,
78+
libc::c_ulong::from_ne_bytes(breakpoint_inst) as *mut _,
79+
)?;
80+
81+
// Wait for the breakpoint to get hit.
82+
target.unpause().unwrap();
83+
84+
let ip = target.read_regs().unwrap().rip;
85+
assert_eq!(
86+
debuginfo.get_address_symbol(ip as usize).as_deref(),
87+
Some("breakpoint")
88+
);
89+
6290
let str_addr = debuginfo
6391
.get_var_address("STATICVAR")
6492
.expect("Expected static var has not been found in the target binary");
6593

66-
let target = LinuxTarget::launch(BIN_PATH)?;
67-
6894
// Read pointer
6995
let mut ptr_addr: usize = 0;
7096
unsafe {

tests/testees/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
CC = rustc
2-
CC_FLAGS = -g -C relocation-model=dynamic-no-pic
2+
CC_FLAGS = -g -Copt-level=2
33
SRCS = $(wildcard *.rs)
44
BINS = $(patsubst %.rs,%,$(SRCS))
55

tests/testees/hello.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ static STATICVAR: &str = "Hello, world!\n";
22

33
#[no_mangle]
44
#[inline(never)]
5-
fn a_function() {}
5+
fn breakpoint() {
6+
// This will be patched by the debugger to be a breakpoint
7+
unsafe { core::arch::x86_64::_mm_pause(); }
8+
}
9+
610

711
pub fn main() {
8-
a_function();
12+
breakpoint();
913
println!("{}", STATICVAR);
1014
}

0 commit comments

Comments
 (0)