Skip to content

Instantly share code, notes, and snippets.

@caiorss
Created December 4, 2020 22:18
Show Gist options
  • Save caiorss/339b00fc8ab1b3d1d46ed9167ccbaeeb to your computer and use it in GitHub Desktop.
Save caiorss/339b00fc8ab1b3d1d46ed9167ccbaeeb to your computer and use it in GitHub Desktop.
Ptrace attach sample code for tracing system calls of running processes
#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, &regs);
// 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