C 将共享库注入到进程中

C 将共享库注入到进程中,c,linux,C,Linux,我刚刚开始学习Linux中的注入技术,并想编写一个简单的程序,将共享库注入到正在运行的进程中。(图书馆只会打印一个字符串。)然而,经过几个小时的研究,我找不到任何完整的例子。嗯,我确实发现我可能需要使用ptrace()来暂停进程并注入内容,但不确定如何将库加载到目标进程的内存空间中,以及C代码中的重新定位内容。有人知道共享库注入的好资源或工作示例吗?(当然,我知道可能有一些现有的库,比如hotpatch,我可以使用它们来简化注入,但这不是我想要的) 如果有人能写一些伪代码或者给我举个例子,我会很

我刚刚开始学习Linux中的注入技术,并想编写一个简单的程序,将共享库注入到正在运行的进程中。(图书馆只会打印一个字符串。)然而,经过几个小时的研究,我找不到任何完整的例子。嗯,我确实发现我可能需要使用ptrace()来暂停进程并注入内容,但不确定如何将库加载到目标进程的内存空间中,以及C代码中的重新定位内容。有人知道共享库注入的好资源或工作示例吗?(当然,我知道可能有一些现有的库,比如hotpatch,我可以使用它们来简化注入,但这不是我想要的)

如果有人能写一些伪代码或者给我举个例子,我会很感激的。谢谢

PS:我不是在问LD_预加载技巧

安德烈·佩尔在对原始问题的评论中提到的“LD_预加载技巧”其实不是技巧。它是在动态链接的流程中添加功能(或者更常见的是插入现有功能)的标准方法。它是Linux动态链接器提供的标准功能

Linux动态链接器由环境变量(和配置文件)控制
LD_PRELOAD
只是一个环境变量,它提供了应针对每个进程链接的动态库列表。(您还可以将库添加到
/etc/ld.so.preload
,在这种情况下,无论环境变量是
ld\u preload
,它都会自动为每个二进制文件加载。)

下面是一个例子,example.c:

如果随后运行任何(动态链接的)二进制文件,其完整路径为
libexample.so
列在
LD_PREALOD
环境变量中,则二进制文件将在正常输出之前将“I am load and running”输出到标准输出。比如说,

LD_PRELOAD=$PWD/libexample.so date
将输出如下内容

I am loaded and running.
Mon Jun 23 21:30:00 UTC 2014
注意,示例库中的
init()
函数是自动执行的,因为它被标记了;该属性表示函数将在
main()
之前执行

我的示例库对您来说可能很有趣——没有
printf()
等等,
wrerr()
处理
errno
——但我这样编写它有很好的理由

首先,
errno
是一个线程局部变量。如果运行某些代码,最初保存原始的
errno
值,并在返回之前恢复该值,则中断的线程将不会看到
errno
中的任何更改。(而且因为它是线程本地的,其他人也不会看到任何更改,除非您尝试一些愚蠢的操作,比如
&errno
)应该在进程其余部分没有注意到随机效果的情况下运行的代码,最好确保它以这种方式保持
errno
不变

wrerr()
函数本身是一个简单的函数,可以将字符串安全地写入标准错误。它是异步信号安全的(这意味着您可以在信号处理程序中使用它,与
printf()
等不同),并且除了保持不变的
errno
之外,它不会以任何方式影响进程其余部分的状态。简单地说,将字符串输出到标准错误是一种安全的方法。它也很简单,每个人都能理解

第二,并非所有进程都使用标准C I/O。例如,用Fortran编译的程序不使用标准C I/O。因此,如果您尝试使用标准C I/O,它可能会工作,也可能不会工作,甚至可能会混淆目标二进制文件。使用
wrerr()
函数可以避免所有这一切:它只会将字符串写入标准错误,而不会混淆过程的其余部分,不管它是用什么编程语言编写的——只要该语言的运行时不移动或关闭标准错误文件描述符(
STDERR\u FILENO==2


要在正在运行的进程中动态加载该库,您需要首先将
ptrace
附加到它,然后在下一个syscall条目(
ptrace\u SYSEMU
)之前停止它,以确保您在某个地方可以安全地执行dlopen调用

选中
/proc/PID/maps
以验证您是否在流程自己的代码中,而不是在共享库代码中。您可以执行
PTRACE\u SYSCALL
PTRACE\u SYSEMU
继续到下一个候选停车点。另外,请记住
wait()
,让子线程在连接到它之后实际停止,并将其连接到所有线程

停止时,使用
PTRACE\u GETREGS
获取寄存器状态,并使用
PTRACE\u PEEKTEXT
复制足够的代码,这样您就可以将其替换为
PTRACE\u POKETEXT
到一个位置无关的序列,该序列调用
dlopen(“/path/to/libexample.so”,RTLD\u NOW)
RTLD\u现在是在
/usr/include/../dlfcn.h
中为您的体系结构定义的整数常量,通常为2。由于路径名是常量字符串,您可以(临时)通过代码保存它;毕竟,函数调用需要一个指向它的指针

将用于重写某些现有代码的独立于位置的序列以系统调用结尾,这样就可以使用
PTRACE\u syscall
运行插入的系统调用(在循环中,直到它在插入的系统调用处结束),而无需单步执行。然后使用
PTRACE\u POKETEXT
将代码还原为其原始状态,最后使用
PTRACE\u SETREGS
将程序状态还原为其初始状态


考虑这个简单的程序,编译为say
target

#include <stdio.h>
int main(void)
{
    int c;
    while (EOF != (c = getc(stdin)))
        putc(c, stdout);
    return 0;
}
它需要两个参数:目标进程的pid和用于替换为注入的可执行代码的地址

两个神奇常量,
0x2c6f6c6c6548050fULL
0x0a21646c726f7720ULL
,只是x86-64上16个字节的本机表示形式

0F 05 "Hello, world!\n"
机智
#include <stdio.h>
int main(void)
{
    int c;
    while (EOF != (c = getc(stdin)))
        putc(c, stdout);
    return 0;
}
#define  _GNU_SOURCE
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    struct user_regs_struct oldregs, regs;
    unsigned long  pid, addr, save[2];
    siginfo_t      info;
    char           dummy;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s PID ADDRESS\n", argv[0]);
        fprintf(stderr, "\n");
        return 1;
    }

    if (sscanf(argv[1], " %lu %c", &pid, &dummy) != 1 || pid < 1UL) {
        fprintf(stderr, "%s: Invalid process ID.\n", argv[1]);
        return 1;
    }

    if (sscanf(argv[2], " %lx %c", &addr, &dummy) != 1) {
        fprintf(stderr, "%s: Invalid address.\n", argv[2]);
        return 1;
    }
    if (addr & 7) {
        fprintf(stderr, "%s: Address is not a multiple of 8.\n", argv[2]);
        return 1;
    }

    /* Attach to the target process. */
    if (ptrace(PTRACE_ATTACH, (pid_t)pid, NULL, NULL)) {
        fprintf(stderr, "Cannot attach to process %lu: %s.\n", pid, strerror(errno));
        return 1;
    }

    /* Wait for attaching to complete. */
    waitid(P_PID, (pid_t)pid, &info, WSTOPPED);

    /* Get target process (main thread) register state. */
    if (ptrace(PTRACE_GETREGS, (pid_t)pid, NULL, &oldregs)) {
        fprintf(stderr, "Cannot get register state from process %lu: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Save the 16 bytes at the specified address in the target process. */
    save[0] = ptrace(PTRACE_PEEKTEXT, (pid_t)pid, (void *)(addr + 0UL), NULL);
    save[1] = ptrace(PTRACE_PEEKTEXT, (pid_t)pid, (void *)(addr + 8UL), NULL);

    /* Replace the 16 bytes with 'syscall' (0F 05), followed by the message string. */
    if (ptrace(PTRACE_POKETEXT, (pid_t)pid, (void *)(addr + 0UL), (void *)0x2c6f6c6c6548050fULL) ||
        ptrace(PTRACE_POKETEXT, (pid_t)pid, (void *)(addr + 8UL), (void *)0x0a21646c726f7720ULL)) {
        fprintf(stderr, "Cannot modify process %lu code: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Modify process registers, to execute the just inserted code. */
    regs = oldregs;
    regs.rip = addr;
    regs.rax = SYS_write;
    regs.rdi = STDERR_FILENO;
    regs.rsi = addr + 2UL;
    regs.rdx = 14; /* 14 bytes of message, no '\0' at end needed. */
    if (ptrace(PTRACE_SETREGS, (pid_t)pid, NULL, &regs)) {
        fprintf(stderr, "Cannot set register state from process %lu: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Do the syscall. */
    if (ptrace(PTRACE_SINGLESTEP, (pid_t)pid, NULL, NULL)) {
        fprintf(stderr, "Cannot execute injected code to process %lu: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Wait for the client to execute the syscall, and stop. */
    waitid(P_PID, (pid_t)pid, &info, WSTOPPED);

    /* Revert the 16 bytes we modified. */
    if (ptrace(PTRACE_POKETEXT, (pid_t)pid, (void *)(addr + 0UL), (void *)save[0]) ||
        ptrace(PTRACE_POKETEXT, (pid_t)pid, (void *)(addr + 8UL), (void *)save[1])) {
        fprintf(stderr, "Cannot revert process %lu code modifications: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Revert the registers, too, to the old state. */
    if (ptrace(PTRACE_SETREGS, (pid_t)pid, NULL, &oldregs)) {
        fprintf(stderr, "Cannot reset register state from process %lu: %s.\n", pid, strerror(errno));
        ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL);
        return 1;
    }

    /* Detach. */
    if (ptrace(PTRACE_DETACH, (pid_t)pid, NULL, NULL)) {
        fprintf(stderr, "Cannot detach from process %lu: %s.\n", pid, strerror(errno));
        return 1;
    }

    fprintf(stderr, "Done.\n");
    return 0;
}
0F 05 "Hello, world!\n"