ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

通过ptrace跟踪进程2

2021-06-11 11:33:55  阅读:222  来源: 互联网

标签:PTRACE pid exit ptrace printf 进程 NULL reg 跟踪


相关连接

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 运行结果

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

标签:PTRACE,pid,exit,ptrace,printf,进程,NULL,reg,跟踪
来源: https://www.cnblogs.com/bunner/p/14874439.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有