Created
December 4, 2020 22:18
-
-
Save caiorss/339b00fc8ab1b3d1d46ed9167ccbaeeb to your computer and use it in GitHub Desktop.
Ptrace attach sample code for tracing system calls of running processes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <string> | |
#include <sstream> | |
#include <cstring> // memcpy, memset, ... | |
#include <cassert> // assert() statement | |
#include <vector> | |
#include <functional> | |
#include <map> | |
// --- Unix-specific headers -----// | |
#include <unistd.h> | |
#include <sys/ptrace.h> | |
#include <sys/reg.h> | |
#include <sys/wait.h> | |
#include <sys/types.h> | |
#include <sys/syscall.h> | |
#include <sys/user.h> | |
#include <fcntl.h> | |
constexpr int FAILURE = -1; | |
enum class WaitResult | |
{ | |
STOPPED = 0 | |
, TERMINATED = 1 | |
}; | |
/** Ptrace wrapper that terminates the current process there is any error. */ | |
long _ptrace(int request, pid_t pid, void* addr, void* data); | |
WaitResult ptrace_wait_syscall( pid_t pid ); | |
std::string ptrace_get_string( pid_t pid, std::uintptr_t addr, std::size_t size ); | |
std::string ptrace_get_string_null( pid_t pid, std::uintptr_t addr ); | |
int main(int argc, char** argv) | |
{ | |
if(argc < 2) | |
{ | |
std::fprintf(stderr, " Usage: $ %s <PID> \n", argv[0]); | |
return EXIT_FAILURE; | |
} | |
// Next line: throws exception. | |
pid_t pid = std::stoi(argv[1]); | |
// Attempt to attach to process. | |
_ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); | |
std::fprintf(stderr, " [TRACE] Attached to process. Ok \n"); | |
// Fix PTRACE sigrap problem. That happens after attaching to child | |
// process with PTRACE_ATTACH and the child process receives a SIGTRAP signal. | |
// See: https://stackoverflow.com/questions/44630382 | |
_ptrace( PTRACE_SETOPTIONS, pid, nullptr, (void*) PTRACE_O_TRACECLONE); | |
// Makes possible to distinguish system-call related stops from other | |
// types of stops. | |
_ptrace( PTRACE_SETOPTIONS, pid, nullptr, (void*) PTRACE_O_TRACESYSGOOD); | |
// CPU registers (context) of the monitored process | |
struct user_regs_struct regs; | |
/// ========== Ptrace event loop ==============// | |
std::fprintf(stderr, " [TRACE] Start event loop. Ok. \n"); | |
for(;;) | |
{ | |
// Intercept system call entry | |
if( ptrace_wait_syscall(pid) == WaitResult::TERMINATED) | |
{ | |
std::fprintf(stderr, " [TRACE] (1) Monitored process has terminated. \n"); | |
break; | |
} | |
// Intercept system call exit | |
if( ptrace_wait_syscall(pid) == WaitResult::TERMINATED) | |
{ | |
std::fprintf(stderr, " [TRACE] (2) Monitored process has terminated. \n"); | |
break; | |
} | |
// Get CPU register for the current process. | |
_ptrace( PTRACE_GETREGS, pid, nullptr, ®s); | |
// Get system call number | |
int syscall = regs.orig_rax; | |
// System call => open() | |
// int openat( int dirfd // Register RDI (1st syscall arg.) | |
// , const char *pathname // Register RSI (2nd syscall arg.) | |
// , int flags // Register RSI (3rd syscall arg.) | |
// ); | |
if(syscall == SYS_openat) | |
{ | |
auto ptr = uintptr_t{regs.rsi}; | |
auto file = ptrace_get_string_null(pid, regs.rsi); | |
std::fprintf(stderr, " [TRACE] openat() system call found => file = %s \n", file.c_str()); | |
continue; | |
} | |
// => Note: Linux system calls for x64 is the System V x86-64/ calling convention. | |
// | |
// System call: write( int fd // register RDI (1st syscall argument) | |
// , void* buffer // register RSI (2nd syscall argument) | |
// , size_t size // register RDX (3rd syscall argument) | |
// ) | |
if(syscall == SYS_write) | |
{ | |
int write_fd = regs.rdi; // File descriptor of write system call | |
uintptr_t write_ptr = regs.rsi; // Pointer to buffer. | |
size_t write_size = regs.rdx; // Buffer size. | |
auto buffer_text = ptrace_get_string(pid, write_ptr, write_size); | |
std::fprintf( stderr | |
, " [TRACE] Write system (syscall = %d ) " | |
" fd = %d ; buffer_ptr = 0x%x ; size = %zu ; \n => buffer_text = '%s' \n\n" | |
, syscall | |
, write_fd, write_ptr, write_size, buffer_text.c_str()); | |
continue; | |
} | |
} // ====== End of ptrace event loop ====// | |
return 0; | |
} // --- End of main() ------- // | |
// ============================================== // | |
// I P L E M E N T A T I O N S // | |
//------------------------------------------------// | |
long | |
_ptrace(int request, pid_t pid, void* addr, void* data) | |
{ | |
long r = ptrace( (__ptrace_request) request, pid, addr, data); | |
if(r == -1){ | |
std::stringstream ss; | |
ss << " [PTRACE FAILURE] " | |
<< " ; errno = " << errno | |
<< " ; msg = '" << strerror(errno) | |
<< "' \n"; | |
throw std::runtime_error(ss.str()); | |
} | |
return r; | |
} | |
WaitResult | |
ptrace_wait_syscall(pid_t pid) | |
{ | |
long result; | |
int status; | |
for(;;){ | |
result = _ptrace(PTRACE_SYSCALL, pid, nullptr, nullptr); | |
waitpid(pid, &status, 0); | |
// This predicate holds when the monitored process | |
// has terminated. | |
if( WIFEXITED(status) ) | |
{ return WaitResult::TERMINATED; } | |
// Credits and further reading: | |
// => https://ops.tips/gists/using-c-to-inspect-linux-syscalls | |
if( WIFSTOPPED(status) && WSTOPSIG(status) & 0x80 ) | |
{ return WaitResult::STOPPED; } | |
} | |
} | |
std::string | |
ptrace_get_string(pid_t pid, std::uintptr_t addr, std::size_t size) | |
{ | |
constexpr size_t long_size = sizeof(long); | |
union ptrace_data { | |
long data; | |
char bytes[long_size]; | |
}; | |
int n = 0; | |
int steps = size / sizeof(long); | |
auto pdata = ptrace_data{}; | |
auto result = std::string(size + 1, 0); | |
char* paddr = &result[0]; | |
assert(result.length() == size + 1); | |
while(n < steps) | |
{ | |
pdata.data = _ptrace( PTRACE_PEEKDATA, pid | |
, reinterpret_cast<void*>(addr + n * long_size) | |
, nullptr | |
); | |
std::memcpy(paddr, pdata.bytes, long_size); | |
n++; | |
paddr = paddr + long_size; | |
} | |
// '%' integer remainder | |
steps = size % long_size; | |
if( steps != 0 ) { | |
pdata.data = _ptrace( PTRACE_PEEKDATA, pid | |
, reinterpret_cast<void*>(addr + n * long_size) | |
, nullptr | |
); | |
memcpy(paddr, pdata.bytes, steps); | |
} | |
result[size] = '\0'; | |
return result; | |
} | |
std::string | |
ptrace_get_string_null(pid_t pid, std::uintptr_t addr) | |
{ | |
constexpr size_t long_size = sizeof(long); | |
union ptrace_data { | |
long data; | |
char bytes[long_size]; | |
}; | |
int nread = 0; | |
auto pdata = ptrace_data{}; | |
auto result = std::string{""}; | |
std::memset(&pdata.bytes[0], 0x00, long_size * sizeof(char)); | |
for(;;) | |
{ | |
pdata.data = _ptrace( PTRACE_PEEKDATA | |
, pid | |
, reinterpret_cast<void*>(addr + nread) | |
, nullptr | |
); | |
result.resize(result.size() + long_size + 1); | |
char* pinit = &result[0]; | |
memcpy(pinit + nread, &pdata.data, 1 * sizeof(long)); | |
nread += long_size; | |
// If the null terminating character '\0' (0x00) is found | |
// , then exit the current loop. | |
for(size_t i = 0; i < long_size; i++) | |
if( pdata.bytes[i] == '\0' ) { goto exit_loop; } | |
} | |
exit_loop: | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment