通过ptrace跟踪进程2
相关连接
- 项目地址 —— linux binary analysis: 《linux二进制分析》学习 (gitee.com)
- 上一篇博客 —— 通过ptrace跟踪进程 - bunner - 博客园 (cnblogs.com)
1. 任务环境和目标
1.1 实验机器
- Ubuntu 20.04 64位
1.2 任务目标
给定一个可执行文件或进程pid,其进程执行内容为:执行20次print_string
函数,要求本程序跟踪print_string
函数,并在目标进程每次执行print_string
函数时输出其寄存器内容
2. 原理
有关于上一个版本的trace的基本原理已在 通过ptrace跟踪进程 - bunner - 博客园 (cnblogs.com) 中介绍
2.1 ptrace
本版本中会涉及到PTRACE_ATTACH
参数
-
PTRACE_ATTACH
对于一个进程,我们无法通过
PTRACE_TRACEME
来跟踪它,只能通过PTRACE_ATTACH
参数来主动跟踪目标进程ptrace(PTRACE_ATTACH, pid, NULL, NULL);
且在我实验过程中,该操作需要
root
权限,即便我的目标进程不是root
的该参数会向目标进程发送SIGSTOP信号来终止进程
2.2 通过pid来获取可执行文件路径名称等信息
/proc/<pid>/cmdline
文件存储了我们需要的东西,它存储了执行可执行文件的完整命令,例如在本次实验中 ,该文件存储的是./test
3. 实现
3.1 程序流程
与上一个版本相比,本程序主要多了一个通过给定进程pid来对进程进行跟踪的操作,具体就是通过pid获取可执行文件路径,再通过PTACE_ATTACH
来跟踪目标进程
3.2 代码实现
- tracer_running
$ gcc tracer_running.c -o tracer_running
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <elf.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
typedef struct handle {
Elf64_Ehdr *ehdr;
Elf64_Phdr *phdr;
Elf64_Shdr *shdr;
uint8_t *mem;
char *symname;
Elf64_Addr symaddr;
struct user_regs_struct pt_reg;
char *exec;
} handle_t;
int global_pid;
Elf64_Addr lookup_symbol(handle_t *, const char *);
char *get_exe_name(int);
void sighandler(int);
#define EXE_MODE 0
#define PID_MOD 1
void cmd_process(int argc, char **argv, handle_t *h, int *pid, int *mode) {
int c;
while ((c = getopt(argc, argv, "p:e:f:")) != -1) {
switch (c) {
case 'p':
/* 给定进程pid模式, PID_MODE */
*pid = atoi(optarg);
h->exec = get_exe_name(*pid);
if (h->exec == NULL) {
printf("Unable to retrieve executable path for pid: %d\n", pid);
exit(-1);
}
*mode = PID_MOD;
break;
case 'e':
/* 给定可执行文件模式,EXE_MODE */
if ((h->exec = strdup(optarg)) == NULL) {
perror("strdup");
exit(-1);
}
*mode = EXE_MODE;
break;
case 'f':
if ((h->symname = strdup(optarg)) == NULL) {
perror("strdup");
exit(-1);
}
break;
default:
printf("Unknown option\n");
break;
}
}
}
int main(int argc, char **argv, char **envp) {
int fd, mode = 0;
handle_t h;
struct stat st;
long trap, orig;
int status, pid;
char *args[2];
printf("Usage: %s [-ep <exe>/<pid>] [f <fname>]\n", argv[0]);
memset(&h, 0, sizeof(handle_t));
cmd_process(argc, argv, &h, &pid, &mode);
printf("\nexec name: %s\n\n", h.exec);
if (h.symname == NULL) {
printf("Specifying a function name with -f option is required.\n");
exit(-1);
}
if (mode == EXE_MODE) {
args[0] = h.exec;
args[1] = NULL;
}
/**
* signal函数设置一个函数来处理指定的信号
* 这里当有SIGINT信号时,sighandler将捕获该信号并进行处理
* 其中SIGINT是中断信号,如键盘中断
*/
signal(SIGINT, sighandler);
if ((fd = open(h.exec, O_RDONLY)) < 0) {
perror("open");
exit(-1);
}
if (fstat(fd, &st) < 0) {
perror("fstat");
exit(-1);
}
h.mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (h.mem == MAP_FAILED) {
perror("mmap");
exit(-1);
}
h.ehdr = (Elf64_Ehdr *) h.mem;
h.phdr = (Elf64_Phdr *) (h.mem + h.ehdr->e_phoff);
h.shdr = (Elf64_Shdr *) (h.mem + h.ehdr->e_shoff);
// printf("ELF header:\n\n");
// printf("%-36s 0x%x\n", " Program Entry point:", h.ehdr->e_entry);
// printf("%-36s %d\n", " Start of Program header:", h.ehdr->e_phoff);
// printf("%-36s %d\n", " Start of Section header:", h.ehdr->e_shoff);
// printf("%-36s %d (bytes)\n", " Size of program header:", h.ehdr->e_phentsize);
// printf("%-36s %d\n", " Number of Program headers:", h.ehdr->e_phnum);
// printf("%-36s %d (bytes)\n", " Size of section header:", h.ehdr->e_shentsize);
// printf("%-36s %d\n", " Number of Section headers:", h.ehdr->e_shnum);
// printf("%-36s %d\n\n", " Section header string table index:", h.ehdr->e_shstrndx);
if (h.mem[0] != 0x7f || strncmp(&h.mem[1], "ELF", 3)) {
printf("%s is not an ELF file\n", h.exec);
exit(-1);
}
if (h.ehdr->e_type != ET_EXEC) {
printf("%s is not an ELF executable\n", h.exec);
}
if (h.ehdr->e_shnum == 0 || h.ehdr->e_shoff == 0 || h.ehdr->e_shstrndx == 0) {
printf("Section header table not found\n");
exit(-1);
}
if ((h.symaddr = lookup_symbol(&h, h.symname)) == NULL) {
printf("Unable to find symbol: %s not found in executable\n", h.symname);
exit(-1);
}
close(fd);
if (mode == EXE_MODE) {
/* EXE_MODE, 只给出可执行文件,需要本程序主动创建子程序并执行该程序 */
if ((pid = fork()) < 0) {
perror("fork");
exit(-1);
}
if (pid == 0) {
/* 子进程执行逻辑 */
if (ptrace(PTRACE_TRACEME, pid, NULL, NULL) < 0) {
perror("PTRACE_TRACEME");
exit(-1);
}
execve(h.exec, args, envp);
exit(0);
}
} else {
/**
* PID_MODE, 目标可执行文件已在运行,需要本进程主动ATTACH上该进程
* PTRACE_ATTACH参数会向被追踪进程发送SIGSTOP信号来终止进程
*/
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("PTRACE_ATTACH");
exit(-1);
}
}
/* 等待目标进程运行终止 */
wait(&status);
global_pid = pid;
printf("Begining analysis of pid of %d at %lx\n", pid, h.symaddr);
/* 保存目标地址处的原数据 */
orig = ptrace(PTRACE_PEEKTEXT, pid, h.symaddr, NULL);
/* 设置断点指令 */
trap = (orig & ~0xff) | 0xcc;
/* 将断点指令写入到目标地址中 */
if (ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0) {
perror("PTRACE_POKETEXT");
exit(-1);
}
trace:
/* 使进程继续执行 */
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT");
exit(-1);
}
wait(&status);
/* 若进程因为进入断点而运行终止 */
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
/* 获取寄存器数据 */
if (ptrace(PTRACE_GETREGS, pid, NULL, &h.pt_reg) < 0) {
perror("PTRACE_GETREGS");
exit(-1);
}
printf("\nExecutable %s (pid: %d) has hit breakpoint 0x%lx\n", h.exec, pid, h.symaddr);
printf("%%rcx: %llx\n", h.pt_reg.rcx);
printf("%%rdx: %llx\n", h.pt_reg.rdx);
printf("%%rbx: %llx\n", h.pt_reg.rbx);
printf("%%rax: %llx\n", h.pt_reg.rax);
printf("%%rdi: %llx\n", h.pt_reg.rdi);
printf("%%rsi: %llx\n", h.pt_reg.rsi);
printf("%%r8: %llx\n", h.pt_reg.r8);
printf("%%r9: %llx\n", h.pt_reg.r9);
printf("%%r10: %llx\n", h.pt_reg.r10);
printf("%%r11: %llx\n", h.pt_reg.r11);
printf("%%r12: %llx\n", h.pt_reg.r12);
printf("%%r13: %llx\n", h.pt_reg.r13);
printf("%%r14: %llx\n", h.pt_reg.r14);
printf("%%r15: %llx\n", h.pt_reg.r15);
printf("%%rsp: %llx\n", h.pt_reg.rsp);
printf("\n Please hit enter key to continue: ");
getchar();
/* 还原目标地址内数据 */
if (ptrace(PTRACE_POKETEXT, pid, h.symaddr, orig) < 0) {
perror("PTRACE_POKETEXT");
exit(-1);
}
/* 指令指针寄存器减1 */
h.pt_reg.rip = h.pt_reg.rip - 1;
/* 设置寄存器值,主要是让指令指针指到目标地址处 */
if (ptrace(PTRACE_SETREGS, pid, NULL, &h.pt_reg) < 0) {
perror("PTRACE_SETREGS");
exit(-1);
}
/* 单步执行 */
if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) < 0) {
perror("PTRACE_SINGLESTEP");
exit(-1);
}
/* 等待进程执行终止,主要就是单步执行后再次设置断点 */
wait(NULL);
if (ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0) {
perror("PTRACE_POKETEXT");
exit(-1);
}
goto trace;
}
if (WIFEXITED(status)) {
printf("\nCompleted tracing pid: %d\n", pid);
exit(0);
}
}
/**
* @brief 根据符号名获取符号地址
*/
Elf64_Addr lookup_symbol(handle_t *h, const char *symname) {
int i, j, NumOfSym;
char *strtab;
Elf64_Sym *symtab;
for (i = 0; i < h->ehdr->e_shnum; i++) {
/* 寻找符号表 */
if (h->shdr[i].sh_type == SHT_SYMTAB) {
/* 寻找字符串表,存储符号名 */
strtab = (char *) (h->mem + h->shdr[h->shdr[i].sh_link].sh_offset);
// printf("0x%x\n", h->shdr[29].sh_offset);
// for (j = 0; j < h->shdr[29].sh_size; j++) {
// if (strtab[j] >= 0x20 && strtab[j] <= 0x7e) {
// printf("%c", strtab[j]);
// }
// }
// puts("");
symtab = (Elf64_Sym *) (h->mem + h->shdr[i].sh_offset);
NumOfSym = h->shdr[i].sh_size / sizeof(Elf64_Sym);
for (j = 0; j < NumOfSym; j++) {
if (!strncmp(&strtab[symtab->st_name], symname, strlen(symname))) {
return symtab->st_value;
}
symtab++;
}
}
}
return 0;
}
/**
* @brief 去进程的cmdline中获取进程对应的可执行文件路径
*/
char *get_exe_name(int pid) {
char cmdline[255], path[512], *p;
int fd;
/* 将文件路径"/proc/pid/cmdline"写入到cmdline缓存中 */
snprintf(cmdline, 255, "/proc/%d/cmdline", pid);
if ((fd = open(cmdline, O_RDONLY)) < 0) {
perror("open");
exit(-1);
}
if (read(fd, path, 512) < 0) {
perror("read");
exit(-1);
}
if ((p = strdup(path)) == NULL) {
perror("strdup");
exit(-1);
}
return p;
}
/**
* @brief 捕获sig信号,并解除对目标进程的跟踪
*/
void sighandler(int sig) {
printf("Caught SIGINT: Detaching from %d\n", global_pid);
if (ptrace(PTRACE_DETACH, global_pid, NULL, NULL) < 0 && errno) {
perror("PTRACE_DETACH");
exit(-1);
}
exit(0);
}
- test.c
$ gcc -no-pie test.c -o test
#include <stdio.h>
#include <unistd.h>
void print_string(char *s) {
puts(s);
}
int main() {
int i, j, a, l = 20;
sleep(3);
while (l--) {
a = 0;
for (i = 0; i < 10000; i++) {
for (j = 0; j < 10000; j++) {
a++;
if (a % 100 == 0) a = 0;
}
}
print_string("Hello");
}
return 0;
}
3.3 运行结果
# 运行test,作为守护进程
./test &
$ sudo ./tracer_running -f print_string -p 6008
Usage: ./tracer_running [-ep <exe>/<pid>] [f <fname>]
exec name: ./test
Begining analysis of pid of 6008 at 401156
Executable ./test (pid: 6008) has hit breakpoint 0x401156
%rcx: 1
%rdx: 0
%rbx: 401220
%rax: 0
%rdi: 402004
%rsi: 8e12a0
%r8: 6
%r9: 7c
%r10: 7f1578293be0
%r11: 246
%r12: 401070
%r13: 7ffc855c32c0
%r14: 0
%r15: 0
%rsp: 7ffc855c31b8
Please hit enter key to continue:
Executable ./test (pid: 6008) has hit breakpoint 0x401156
%rcx: 1
%rdx: 0
%rbx: 401220
%rax: 0
%rdi: 402004
%rsi: 8e12a0
%r8: 6
%r9: 7c
%r10: 7f1578293be0
%r11: 246
%r12: 401070
%r13: 7ffc855c32c0
%r14: 0
%r15: 0
%rsp: 7ffc855c31b8
Please hit enter key to continue: ^CCaught SIGINT: Detaching from 6008
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!