linux ptrace注入

源代码:
https://github.com/haidragon/linux-inject
注入的代码中动态加载所用到的函数。代码为调用 dlopen启动一个动态库。
原理:
通过 ptrace 附加他,到目标内存中找一块可以执行区域,把代码注入到那里。同时保存原来的数据。用于执行完后恢复现场。关键点是注入的代码最后一个字节为 int 3.产生一个异常。这个异常的作用用来恢复现场。

命令为 到目录下 make
另外开一个终端到相同目录下
运行 ./sample-target
第一个终端运行:
sudo ./inject -n sample-target sample-library.so

关键代码

#include 
#include 
#include 
#include 
#include 
#include 

#include "utils.h"
#include "ptrace.h"
// 这是将实际注入目标进程的代码。
// 此代码负责将共享库加载到目标中进程的地址空间。首先,它调用malloc()来分配缓冲区。
// 保存要加载的库的文件名。
// 然后,它调用__libc_dlopen_mode(),libc实现dlopen(),加载所需的共享库。最后,它调用free()来释放包含库名称。
// 每次需要给注射器传递目标进程的中相关信息,原理是用的int $3异常
void injectSharedLibrary(long mallocaddr, long freeaddr, long dlopenaddr)
{
    // here are the assumptions I'm making about what data will be located
    // where at the time the target executes this code:
    //
    //   rdi = address of malloc() in target process
    //   rsi = address of free() in target process
    //   rdx = address of __libc_dlopen_mode() in target process
    //   rcx = size of the path to the shared library we want to load

    // save addresses of free() and __libc_dlopen_mode() on the stack for later use
    asm(
        // rsi is going to contain the address of free(). it's going to get wiped
        // out by the call to malloc(), so save it on the stack for later
        "push %rsi \n"
        // same thing for rdx, which will contain the address of _dl_open()
        "push %rdx"
    );

    // call malloc() from within the target process
    asm(
        // save previous value of r9, because we're going to use it to call malloc()
        "push %r9 \n"
        // now move the address of malloc() into r9
        "mov %rdi,%r9 \n"
        // choose the amount of memory to allocate with malloc() based on the size
        // of the path to the shared library passed via rcx
        "mov %rcx,%rdi \n"
        // now call r9; malloc()
        "callq *%r9 \n"
        // after returning from malloc(), pop the previous value of r9 off the stack
        "pop %r9 \n"
        // break in so that we can see what malloc() returned
        "int $3"
    );

    // call __libc_dlopen_mode() to load the shared library
    asm(
        // get the address of __libc_dlopen_mode() off of the stack so we can call it
        "pop %rdx \n"
        // as before, save the previous value of r9 on the stack
        "push %r9 \n"
        // copy the address of __libc_dlopen_mode() into r9
        "mov %rdx,%r9 \n"
        // 1st argument to __libc_dlopen_mode(): filename = the address of the buffer returned by malloc()
        "mov %rax,%rdi \n"
        // 2nd argument to __libc_dlopen_mode(): flag = RTLD_LAZY
        "movabs $1,%rsi \n"
        // call __libc_dlopen_mode()
        "callq *%r9 \n"
        // restore old r9 value
        "pop %r9 \n"
        // break in so that we can see what __libc_dlopen_mode() returned
        "int $3"
    );

    // call free() to free the buffer we allocated earlier.
    //
    // Note: I found that if you put a nonzero value in r9, free() seems to
    // interpret that as an address to be freed, even though it's only
    // supposed to take one argument. As a result, I had to call it using a
    // register that's not used as part of the x64 calling convention. I
    // chose rbx.
    asm(
        // at this point, rax should still contain our malloc()d buffer from earlier.
        // we're going to free it, so move rax into rdi to make it the first argument to free().
        "mov %rax,%rdi \n"
        // pop rsi so that we can get the address to free(), which we pushed onto the stack a while ago.
        "pop %rsi \n"
        // save previous rbx value
        "push %rbx \n"
        // load the address of free() into rbx
        "mov %rsi,%rbx \n"
        // zero out rsi, because free() might think that it contains something that should be freed
        "xor %rsi,%rsi \n"
        // break in so that we can check out the arguments right before making the call
        "int $3 \n"
        // call free()
        "callq *%rbx \n"
        // restore previous rbx value
        "pop %rbx"
    );

    // we already overwrote the RET instruction at the end of this function
    // with an INT 3, so at this point the injector will regain control of
    // the target's execution.
}

//仅仅用作计算偏移
void injectSharedLibrary_end()
{
}

int main(int argc, char** argv)
{
    if(argc < 4)
    {
        usage(argv[0]);
        return 1;
    }
    //./jnject -n exec libxxxx.so
    char* command = argv[1];
    char* commandArg = argv[2];
    char* libname = argv[3];
    //转换成绝对路径
    char* libPath = realpath(libname, NULL);
    char* processName = NULL;
    pid_t target = 0;
    if(!libPath)
    {
        fprintf(stderr, "can't find file \"%s\"\n", libname);
        return 1;
    }
    //名称
    if(!strcmp(command, "-n"))
    {
        //要注入的进程名
        processName = commandArg;
        //通过进程名称找到 pid
        target = findProcessByName(processName);
        if(target == -1)
        {
            fprintf(stderr, "doesn't look like a process named \"%s\" is running right now\n", processName);
            return 1;
        }

        printf("targeting process \"%s\" with pid %d\n", processName, target);
    }
    //pid
    else if(!strcmp(command, "-p"))
    {
        target = atoi(commandArg);
        printf("targeting process with pid %d\n", target);
    }
    else
    {
        usage(argv[0]);
        return 1;
    }

    int libPathLength = strlen(libPath) + 1;
    //获取 pid
    int mypid = getpid();
    //获取lib 基址
    long mylibcaddr = getlibcaddr(mypid);
    //获取相关函数地址
    long mallocAddr = getFunctionAddress("malloc");
    long freeAddr = getFunctionAddress("free");
    long dlopenAddr = getFunctionAddress("__libc_dlopen_mode");
    //计算相对偏移
    long mallocOffset = mallocAddr - mylibcaddr;
    long freeOffset = freeAddr - mylibcaddr;
    long dlopenOffset = dlopenAddr - mylibcaddr;
    //获取目标进程
    long targetLibcAddr = getlibcaddr(target);
    //获取目标进程中的函数绝对地址
    long targetMallocAddr = targetLibcAddr + mallocOffset;
    long targetFreeAddr = targetLibcAddr + freeOffset;
    long targetDlopenAddr = targetLibcAddr + dlopenOffset;
    //系统
    struct user_regs_struct oldregs, regs;
    memset(&oldregs, 0, sizeof(struct user_regs_struct));
    memset(®s, 0, sizeof(struct user_regs_struct));
    //附加
    ptrace_attach(target);
    //获取寄存器
    ptrace_getregs(target, &oldregs);
    memcpy(®s, &oldregs, sizeof(struct user_regs_struct));
    //找一个可以执行的段
    long addr = freespaceaddr(target) + sizeof(long);
    //现在我们有一个复制代码的地址,将目标的RIP设置为它。
    //我们必须提前2字节,因为RIP是由当前指令的大小递增的,并且在开始注入函数时的指令总是恰好是2字节长。
    regs.rip = addr + 2;
    //通过加载它们将参数传递给我的函数注入
    //进入正确的寄存器。请注意,这肯定只会起作用。
    //x64,因为它依赖于x64调用约定,其中
    //参数通过寄存器RDI、RSI、RDX、RCX、R8和R9传递。
    //在CujSudidLabValuy()中查看注释以获取更多细节。
    regs.rdi = targetMallocAddr;
    regs.rsi = targetFreeAddr;
    regs.rdx = targetDlopenAddr;
    regs.rcx = libPathLength;
    //设置寄存器的值
    ptrace_setregs(target, ®s);
    //计算函数大小
    size_t injectSharedLibrary_size = (intptr_t)injectSharedLibrary_end - (intptr_t)injectSharedLibrary;
    //也可以找出RET指令在哪里结束
    intptr_t injectSharedLibrary_ret = (intptr_t)findRet(injectSharedLibrary_end) - (intptr_t)injectSharedLibrary;

    // 备份我们想要修改的地址所使用的任何数据
    char* backup = malloc(injectSharedLibrary_size * sizeof(char));
    ptrace_read(target, addr, backup, injectSharedLibrary_size);

    // 设置一个缓冲区来保存我们将要注入目标进程的代码
    char* newcode = malloc(injectSharedLibrary_size * sizeof(char));
    memset(newcode, 0, injectSharedLibrary_size * sizeof(char));

    // 拷贝代码injectSharedLibrary()
    memcpy(newcode, injectSharedLibrary, injectSharedLibrary_size - 1);
    // 覆盖最后一个字节为INT 3用于产生异常
    newcode[injectSharedLibrary_ret] = INTEL_INT3_INSTRUCTION;

    //注入
    ptrace_write(target, addr, newcode, injectSharedLibrary_size);

    //让目标运行我们的注入代码。
    ptrace_cont(target);

    //此时,目标应该运行malloc()。检查它的返回值,看看它是否成功,如果没有,就保释出来。
    struct user_regs_struct malloc_regs;
    memset(&malloc_regs, 0, sizeof(struct user_regs_struct));
    ptrace_getregs(target, &malloc_regs);
    //malloc()的返回值
    unsigned long long targetBuf = malloc_regs.rax;
    if(targetBuf == 0)
    {
        fprintf(stderr, "malloc() failed to allocate memory\n");
        //恢复现场
        restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
        free(backup);
        free(newcode);
        return 1;
    }
    //写入路径名称 作为__libc_dlopen_mode 的参数
    ptrace_write(target, targetBuf, libPath, libPathLength);
    //继续执行目标 call  __libc_dlopen_mode.
    //重新运行 eip已经指向下一条
    ptrace_cont(target);
    //检查 rax 看dlopen是否调用成功
    struct user_regs_struct dlopen_regs;
    memset(&dlopen_regs, 0, sizeof(struct user_regs_struct));
    ptrace_getregs(target, &dlopen_regs);
    unsigned long long libAddr = dlopen_regs.rax;
    //如果 rax返回0 说明__libc_dlopen_mode加载失败,因此直接   恢复原有的状态
    if(libAddr == 0)
    {
        fprintf(stderr, "__libc_dlopen_mode() failed to load %s\n", libname);
        restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
        free(backup);
        free(newcode);
        return 1;
    }
    //检查动态库
    if(checkloaded(target, libname))
    {
        printf("\"%s\" successfully injected\n", libname);
    }
    else
    {
        fprintf(stderr, "could not inject \"%s\"\n", libname);
    }
    //再检查下返回值
    ptrace_cont(target);
    //恢复原有的状态
    restoreStateAndDetach(target, addr, backup, injectSharedLibrary_size, oldregs);
    free(backup);
    free(newcode);

    return 0;
}

utils.c

#include 
#include 
#include 
#include 
#include 
#include 

#include "utils.h"

/*
 * findProcessByName()
 *
 * Given the name of a process, try to find its PID by searching through /proc
 * and reading /proc/[pid]/exe until we find a process whose name matches the
 * given process.
 *
 * args:
 * - char* processName: name of the process whose pid to find
 *
 * returns:
 * - a pid_t containing the pid of the process (or -1 if not found)
 *
 */
//通过进程名称找到pid
pid_t findProcessByName(char* processName)
{
    if(processName == NULL)
    {
        return -1;
    }

    struct dirent *procDirs;
    //打开一个目录
    DIR *directory = opendir("/proc/");

    if (directory)
    {
        while ((procDirs = readdir(directory)) != NULL)
        {
            if (procDirs->d_type != DT_DIR)
                continue;
            //转换成 pid
            pid_t pid = atoi(procDirs->d_name);

            int exePathLen = 10 + strlen(procDirs->d_name) + 1;
            char* exePath = malloc(exePathLen * sizeof(char));

            if(exePath == NULL)
            {
                continue;
            }
            //proc/[pid]/exe为实际运行程序的符号链接
            sprintf(exePath, "/proc/%s/exe", procDirs->d_name);
            exePath[exePathLen-1] = '\0';

            char* exeBuf = malloc(PATH_MAX * sizeof(char));
            if(exeBuf == NULL)
            {
                free(exePath);
                continue;
            }
            //找出符号链接所指向的位置
            ssize_t len = readlink(exePath, exeBuf, PATH_MAX - 1);

            if(len == -1)
            {
                free(exePath);
                free(exeBuf);
                continue;
            }

            exeBuf[len] = '\0';

            char* exeName = NULL;
            char* exeToken = strtok(exeBuf, "/");
            while(exeToken)
            {
                exeName = exeToken;
                exeToken = strtok(NULL, "/");
            }

            if(strcmp(exeName, processName) == 0)
            {
                free(exePath);
                free(exeBuf);
                closedir(directory);
                return pid;
            }

            free(exePath);
            free(exeBuf);
        }

        closedir(directory);
    }

    return -1;
}

/*
 * freespaceaddr()
 *
 * Search the target process' /proc/pid/maps entry and
 * 找到一个可执行的内存区域,我们可以使用它来运行代码。
 *
 * args:
 * - pid_t pid: pid of process to inspect

 */

long freespaceaddr(pid_t pid)
{
    FILE *fp;
    char filename[30];
    char line[850];
    long addr;
    char str[20];
    char perms[5];
    sprintf(filename, "/proc/%d/maps", pid);
    fp = fopen(filename, "r");
    if(fp == NULL)
        exit(1);
    while(fgets(line, 850, fp) != NULL)
    {
        //格式化接收
        sscanf(line, "%lx-%*lx %s %*s %s %*d", &addr, perms, str);
        //查找字符 表示找到了可能执行的段
        if(strstr(perms, "x") != NULL)
        {
            break;
        }
    }
    fclose(fp);
    return addr;
}

/*
 * getlibcaddr()
 *
 * Gets the base address of libc.so inside a process by reading /proc/pid/maps.
 *
 * args:
 * - pid_t pid: the pid of the process whose libc.so base address we should
 *   find
 * 
 * returns:
 * - a long containing the base address of libc.so inside that process
 *
 */
//获取动态库的加载基址
long getlibcaddr(pid_t pid)
{
    FILE *fp;
    char filename[30];
    char line[850];
    long addr;
    char perms[5];
    char* modulePath;
    sprintf(filename, "/proc/%d/maps", pid);
    fp = fopen(filename, "r");
    if(fp == NULL)
        exit(1);
    while(fgets(line, 850, fp) != NULL)
    {
        sscanf(line, "%lx-%*lx %*s %*s %*s %*d", &addr);
        if(strstr(line, "libc-") != NULL)
        {
            break;
        }
    }
    fclose(fp);
    return addr;
}

/*
 * checkloaded()
 *
 * Given a process ID and the name of a shared library, check whether that
 * process has loaded the shared library by reading entries in its
 * /proc/[pid]/maps file.
 *
 * args:
 * - pid_t pid: the pid of the process to check
 * - char* libname: the library to search /proc/[pid]/maps for
 *
 * returns:
 * - an int indicating whether or not the library has been loaded into the
 *   process (1 = yes, 0 = no)
 *
 */
//检查是否存在动态库
int checkloaded(pid_t pid, char* libname)
{
    FILE *fp;
    char filename[30];
    char line[850];
    long addr;
    char perms[5];
    char* modulePath;
    sprintf(filename, "/proc/%d/maps", pid);
    fp = fopen(filename, "r");
    if(fp == NULL)
        exit(1);
    while(fgets(line, 850, fp) != NULL)
    {
        sscanf(line, "%lx-%*lx %*s %*s %*s %*d", &addr);
        if(strstr(line, libname) != NULL)
        {
            fclose(fp);
            return 1;
        }
    }
    fclose(fp);
    return 0;
}

/*
 * getFunctionAddress()
 *
 * Find the address of a function within our own loaded copy of libc.so.
 *
 * args:
 * - char* funcName: name of the function whose address we want to find
 *
 * returns:
 * - a long containing the address of that function
 *
 */

long getFunctionAddress(char* funcName)
{
    void* self = dlopen("libc.so.6", RTLD_LAZY);
    void* funcAddr = dlsym(self, funcName);
    return (long)funcAddr;
}

unsigned char* findRet(void* endAddr)
{
    unsigned char* retInstAddr = endAddr;
    while(*retInstAddr != INTEL_RET_INSTRUCTION)
    {
        retInstAddr--;
    }
    return retInstAddr;
}

//接收参数
void usage(char* name)
{
    printf("usage: %s [-n process-name] [-p pid] [library-to-inject]\n", name);
}

ptrace.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "ptrace.h"

/*
 * ptrace_attach()
 *
 * Use ptrace() to attach to a process. This requires calling waitpid() to
 * determine when the process is ready to be traced.
 *
 * args:
 * - int pid: pid of the process to attach to
 *
 */

void ptrace_attach(pid_t target)
{
    int waitpidstatus;

    if(ptrace(PTRACE_ATTACH, target, NULL, NULL) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_ATTACH) failed\n");
        exit(1);
    }

    if(waitpid(target, &waitpidstatus, WUNTRACED) != target)
    {
        fprintf(stderr, "waitpid(%d) failed\n", target);
        exit(1);
    }
}

/*
 * ptrace_detach()
 *
 * Detach from a process that is being ptrace()d. Unlike ptrace_cont(), this
 * completely ends our relationship with the target process.
 *
 * args:
 * - int pid: pid of the process to detach from. this process must already be
 *   ptrace()d by us in order for this to work.
 *
 */

void ptrace_detach(pid_t target)
{
    if(ptrace(PTRACE_DETACH, target, NULL, NULL) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_DETACH) failed\n");
        exit(1);
    }
}

/*
 * ptrace_getregs()
 *
 * Use ptrace() to get a process' current register state.  Uses REG_TYPE
 * preprocessor macro in order to allow for both ARM and x86/x86_64
 * functionality.
 *
 * args:
 * - int pid: pid of the target process
 * - struct REG_TYPE* regs: a struct (either user_regs_struct or user_regs,
 *   depending on architecture) to store the resulting register data in
 *
 */

void ptrace_getregs(pid_t target, struct REG_TYPE* regs)
{
    if(ptrace(PTRACE_GETREGS, target, NULL, regs) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_GETREGS) failed\n");
        exit(1);
    }
}

/*
 * ptrace_cont()
 *
 * Continue the execution of a process being traced using ptrace(). Note that
 * this is different from ptrace_detach(): we still retain control of the
 * target process after this call.
 *
 * args:
 * - int pid: pid of the target process
 *
 */
//重新运行 eip已经指向下一条
void ptrace_cont(pid_t target)
{
    struct timespec* sleeptime = malloc(sizeof(struct timespec));

    sleeptime->tv_sec = 0;
    sleeptime->tv_nsec = 5000000;

    if(ptrace(PTRACE_CONT, target, NULL, NULL) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_CONT) failed\n");
        exit(1);
    }

    nanosleep(sleeptime, NULL);

    // 确保目标进程在停止后接收SIGLAFT。
    checktargetsig(target);
}

/*
 * ptrace_setregs()
 *
 * Use ptrace() to set the target's register state.
 *
 * args:
 * - int pid: pid of the target process
 * - struct REG_TYPE* regs: a struct (either user_regs_struct or user_regs,
 *   depending on architecture) containing the register state to be set in the
 *   target process
 *
 */

void ptrace_setregs(pid_t target, struct REG_TYPE* regs)
{
    if(ptrace(PTRACE_SETREGS, target, NULL, regs) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_SETREGS) failed\n");
        exit(1);
    }
}

/*
 * ptrace_getsiginfo()
 *
 * Use ptrace() to determine what signal was most recently raised by the target
 * process. This is primarily used for to determine whether the target process
 * has segfaulted.
 *
 * args:
 * - int pid: pid of the target process
 *
 * returns:
 * - a siginfo_t containing information about the most recent signal raised by
 *   the target process
 *
 */

siginfo_t ptrace_getsiginfo(pid_t target)
{
    siginfo_t targetsig;
    if(ptrace(PTRACE_GETSIGINFO, target, NULL, &targetsig) == -1)
    {
        fprintf(stderr, "ptrace(PTRACE_GETSIGINFO) failed\n");
        exit(1);
    }
    return targetsig;
}

/*
 * ptrace_read()
 *
 * Use ptrace() to read the contents of a target process' address space.
 *
 * args:
 * - int pid: pid of the target process
 * - unsigned long addr: the address to start reading from
 * - void *vptr: a pointer to a buffer to read data into
 * - int len: the amount of data to read from the target
 *
 */

void ptrace_read(int pid, unsigned long addr, void *vptr, int len)
{
    int bytesRead = 0;
    int i = 0;
    long word = 0;
    long *ptr = (long *) vptr;

    while (bytesRead < len)
    {
        word = ptrace(PTRACE_PEEKTEXT, pid, addr + bytesRead, NULL);
        if(word == -1)
        {
            fprintf(stderr, "ptrace(PTRACE_PEEKTEXT) failed\n");
            exit(1);
        }
        bytesRead += sizeof(word);
        ptr[i++] = word;
    }
}

/*
 * ptrace_write()
 *
 * Use ptrace() to write to the target process' address space.
 *
 * args:
 * - int pid: pid of the target process
 * - unsigned long addr: the address to start writing to
 * - void *vptr: a pointer to a buffer containing the data to be written to the
 *   target's address space
 * - int len: the amount of data to write to the target
 *
 */

void ptrace_write(int pid, unsigned long addr, void *vptr, int len)
{
    int byteCount = 0;
    long word = 0;

    while (byteCount < len)
    {
        memcpy(&word, vptr + byteCount, sizeof(word));
        word = ptrace(PTRACE_POKETEXT, pid, addr + byteCount, word);
        if(word == -1)
        {
            fprintf(stderr, "ptrace(PTRACE_POKETEXT) failed\n");
            exit(1);
        }
        byteCount += sizeof(word);
    }
}

/*
 * checktargetsig()
 *
 * Check what signal was most recently returned by the target process being
 * ptrace()d. We expect a SIGTRAP from the target process, so raise an error
 * and exit if we do not receive that signal. The most likely non-SIGTRAP
 * signal for us to receive would be SIGSEGV.
 *
 * args:
 * - int pid: pid of the target process
 *
 */

void checktargetsig(int pid)
{
    // check the signal that the child stopped with.
    siginfo_t targetsig = ptrace_getsiginfo(pid);

    // if it wasn't SIGTRAP, then something bad happened (most likely a
    // segfault).
    if(targetsig.si_signo != SIGTRAP)
    {
        fprintf(stderr, "instead of expected SIGTRAP, target stopped with signal %d: %s\n", targetsig.si_signo, strsignal(targetsig.si_signo));
        fprintf(stderr, "sending process %d a SIGSTOP signal for debugging purposes\n", pid);
        ptrace(PTRACE_CONT, pid, NULL, SIGSTOP);
        exit(1);
    }
}

/*
 * restoreStateAndDetach()
 *
 * Once we're done debugging a target process, restore the process' backed-up
 * data and register state and let it go on its merry way.
 *
 * args:
 * - pid_t target: pid of the target process
 * - unsigned long addr: address within the target's address space to write
 *   backed-up data to
 * - void* backup: a buffer pointing to the backed-up data
 * - int datasize: the amount of backed-up data to write
 * - struct REG_TYPE oldregs: backed-up register state to restore
 *
 */

void restoreStateAndDetach(pid_t target, unsigned long addr, void* backup, int datasize, struct REG_TYPE oldregs)
{
    ptrace_write(target, addr, backup, datasize);
    ptrace_setregs(target, &oldregs);
    ptrace_detach(target);
}

转载于:https://blog.51cto.com/haidragon/2135226

你可能感兴趣的:(linux ptrace注入)