Skip to main content

lurk_cli/arch/
mod.rs

1use crate::syscall_info::{SyscallArg, SyscallArgs};
2use byteorder::{LittleEndian, WriteBytesExt};
3use libc::{c_long, c_ulonglong, user_regs_struct};
4use nix::sys::ptrace;
5use nix::sys::ptrace::Options;
6use nix::unistd::Pid;
7use std::ffi::c_void;
8use syscalls::Sysno;
9
10#[cfg(any(target_arch = "aarch64", feature = "aarch64"))]
11pub mod aarch64;
12// #[cfg(any(target_arch = "arm", feature = "arm"))]
13// pub mod arm;
14// #[cfg(any(target_arch = "mips", feature = "mips"))]
15// pub mod mips;
16// #[cfg(any(target_arch = "mips64", feature = "mips64"))]
17// pub mod mips64;
18// #[cfg(any(target_arch = "powerpc", feature = "powerpc"))]
19// pub mod powerpc;
20// #[cfg(any(target_arch = "powerpc64", feature = "powerpc64"))]
21// pub mod powerpc64;
22#[cfg(any(target_arch = "riscv64", feature = "riscv64"))]
23pub mod riscv64;
24// #[cfg(any(target_arch = "s390x", feature = "s390x"))]
25// pub mod s390x;
26// #[cfg(any(target_arch = "sparc", feature = "sparc"))]
27// pub mod sparc;
28// #[cfg(any(target_arch = "sparc64", feature = "sparc64"))]
29// pub mod sparc64;
30// #[cfg(any(target_arch = "x86", feature = "x86"))]
31// pub mod x86;
32#[cfg(any(target_arch = "x86_64", feature = "x86_64"))]
33pub mod x86_64;
34
35#[cfg(target_arch = "aarch64")]
36pub use aarch64::*;
37// #[cfg(target_arch = "arm")]
38// pub use arm::*;
39// #[cfg(target_arch = "mips")]
40// pub use mips::*;
41// #[cfg(target_arch = "mips64")]
42// pub use mips64::*;
43// #[cfg(target_arch = "powerpc")]
44// pub use powerpc::*;
45// #[cfg(target_arch = "powerpc64")]
46// pub use powerpc64::*;
47#[cfg(target_arch = "riscv64")]
48pub use riscv64::*;
49// #[cfg(target_arch = "s390x")]
50// pub use s390x::*;
51// #[cfg(target_arch = "sparc")]
52// pub use sparc::*;
53// #[cfg(target_arch = "sparc64")]
54// pub use sparc64::*;
55// #[cfg(target_arch = "x86")]
56// pub use x86::*;
57#[cfg(target_arch = "x86_64")]
58pub use x86_64::*;
59
60#[derive(Debug, Copy, Clone)]
61pub enum SyscallArgType {
62    // Integer can be used to represent int, fd and size_t
63    Int,
64    // String can be used to represent *buf
65    Str,
66    // Array of strings, e.g. argv or envp
67    StrArray,
68    // Address can be used to represent *statbuf
69    Addr,
70}
71
72pub fn read_string(pid: Pid, address: c_ulonglong) -> String {
73    let mut string = String::new();
74    // Move 8 bytes up each time for next read.
75    let mut count = 0;
76    let word_size = 8;
77
78    'done: loop {
79        let address = unsafe { (address as *mut c_void).offset(count) };
80
81        let res: c_long = match ptrace::read(pid, address) {
82            Ok(c_long) => c_long,
83            Err(_) => {
84                // If we can't read the memory at the address, return the pointer
85                // as a hex string so callers can still see a useful value.
86                return format!("{:#x}", address as usize);
87            }
88        };
89
90        let mut bytes: Vec<u8> = vec![];
91        bytes.write_i64::<LittleEndian>(res).unwrap_or_else(|err| {
92            panic!("Failed to write {res} as i64 LittleEndian: {err}");
93        });
94        for b in bytes {
95            if b == 0 {
96                break 'done;
97            }
98            string.push(b as char);
99        }
100
101        count += word_size;
102    }
103
104    string
105}
106
107pub fn ptrace_init_options(pid: Pid) -> nix::Result<()> {
108    ptrace::setoptions(
109        pid,
110        Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT | Options::PTRACE_O_TRACEEXEC,
111    )
112}
113
114pub fn ptrace_init_options_fork(pid: Pid) -> nix::Result<()> {
115    ptrace::setoptions(
116        pid,
117        Options::PTRACE_O_TRACESYSGOOD
118            | Options::PTRACE_O_TRACEEXIT
119            | Options::PTRACE_O_TRACEEXEC
120            | Options::PTRACE_O_TRACEFORK
121            | Options::PTRACE_O_TRACEVFORK
122            | Options::PTRACE_O_TRACECLONE,
123    )
124}
125
126#[allow(clippy::cast_sign_loss)]
127#[must_use]
128// SAFTEY: In get_register_data we make sure that the syscall number will never be negative.
129pub fn parse_args(pid: Pid, syscall: Sysno, registers: user_regs_struct) -> SyscallArgs {
130    SYSCALLS
131        .get(syscall.id() as usize)
132        .and_then(|option| option.as_ref())
133        .map_or_else(
134            || SyscallArgs(vec![]),
135            |(_, args)| {
136                SyscallArgs(
137                    args.iter()
138                        .filter_map(Option::as_ref)
139                        .enumerate()
140                        .map(|(idx, arg_type)| map_arg(pid, registers, idx, *arg_type))
141                        .collect(),
142                )
143            },
144        )
145}
146
147fn map_arg(pid: Pid, registers: user_regs_struct, idx: usize, arg: SyscallArgType) -> SyscallArg {
148    let value = get_arg_value(registers, idx);
149    match arg {
150        SyscallArgType::Int => SyscallArg::Int(value as i64),
151        SyscallArgType::Str => SyscallArg::Str(read_string(pid, value)),
152        SyscallArgType::StrArray => {
153            SyscallArg::StrVec(read_string_array(pid, value), Some(value as usize))
154        }
155        SyscallArgType::Addr => SyscallArg::Addr(value as usize),
156    }
157}
158
159pub fn read_string_array(pid: Pid, address: c_ulonglong) -> Vec<String> {
160    let mut vec = Vec::new();
161    if address == 0 {
162        return vec;
163    }
164
165    // pointer size on x86_64 is 8 bytes; read pointers until NULL
166    let mut offset = 0isize;
167    // safety limit to avoid infinite loops on corrupt pointers
168    for _ in 0..1024 {
169        let ptr_addr = unsafe { (address as *mut c_void).offset(offset) };
170        let res: c_long = match ptrace::read(pid, ptr_addr) {
171            Ok(v) => v,
172            Err(_) => break,
173        };
174        let mut bytes: Vec<u8> = vec![];
175
176        bytes.write_i64::<LittleEndian>(res).ok();
177        let mut ptr_value: c_ulonglong = 0;
178        for (i, b) in bytes.iter().enumerate() {
179            ptr_value |= c_ulonglong::from(*b) << (i * 8);
180        }
181        if ptr_value == 0 {
182            break;
183        }
184        vec.push(read_string(pid, ptr_value));
185        offset += std::mem::size_of::<c_ulonglong>() as isize;
186    }
187
188    vec
189}
190
191/// Count pointers in a NULL-terminated `char * const *` array without attempting
192/// to dereference the strings. This is more robust when the tracee's memory
193/// cannot be fully read, but the pointer array itself is accessible.
194pub fn read_string_array_count(pid: Pid, address: c_ulonglong) -> usize {
195    if address == 0 {
196        return 0;
197    }
198
199    let mut count = 0usize;
200    let mut offset = 0isize;
201    for _ in 0..1024 {
202        let ptr_addr = unsafe { (address as *mut c_void).offset(offset) };
203        let res: c_long = match ptrace::read(pid, ptr_addr) {
204            Ok(v) => v,
205            Err(_) => break,
206        };
207        // reconstruct pointer value from bytes
208        let mut bytes: Vec<u8> = vec![];
209        bytes.write_i64::<LittleEndian>(res).ok();
210        let mut ptr_value: c_ulonglong = 0;
211        for (i, b) in bytes.iter().enumerate() {
212            ptr_value |= c_ulonglong::from(*b) << (i * 8);
213        }
214        if ptr_value == 0 {
215            break;
216        }
217        count += 1;
218        offset += std::mem::size_of::<c_ulonglong>() as isize;
219    }
220
221    count
222}
223
224pub fn escape_to_string(buf: &Vec<u8>) -> String {
225    let mut string = String::new();
226    for c in buf {
227        let code = *c;
228        if (0x20..=0x7f).contains(&code) {
229            if code == b'\\' {
230                string.push_str("\\\\");
231            } else {
232                string.push(char::from(code));
233            }
234        } else {
235            string.push_str(format!("\\{c:x}").as_str());
236        }
237    }
238    string
239}