首页 > *nix应用编程, *nix技术, 应用程序, 跟踪调试 > linux-inject:动态注入替换进程调用函数

linux-inject:动态注入替换进程调用函数

2018年3月4日 发表评论 阅读评论 333 次浏览

最近打算替换Linux下进程的调用函数,这有点类似于LD_PRELOAD环境变量所实现的功能,但是我需要动态的注入,Google了一下,找到几个玩意,linux-injectlinux-injector等。

这里试试linux-inject。

1,系统环境
CentOS 7.2 x86_64

2,下载linux-inject代码

# git clone https://github.com/gaffe23/linux-inject.git
# cd linux-inject
# yum search clang
# make

可能会遇到如下错误,第一个错误安装clang即可,第二个错误不用管(x64平台不用编译32位程序)。

make[1]: clang: Command not found
make[1]: *** [x86_64] Error 127

/usr/include/gnu/stubs.h:7:11: fatal error: 'gnu/stubs-32.h' file not found
# include <gnu/stubs-32.h>

3,运行demo
首先执行./sample-target

# ./sample-target 
sleeping...
sleeping...
...

新开一个终端,执行:./inject -n sample-target sample-library.so

# ./inject -n sample-target sample-library.so
targeting process "sample-target" with pid 27751
"sample-library.so" successfully injected

回到原终端窗口查看:

...
sleeping...
I just got loaded
sleeping...
...

上面是通过-n name指定sample-target进程,也可以通过-p PID的方式来进行指定。
另外,在新版本Linux 3.4中加入的Yama安全模块可能会阻止ptrace-based代码注入,如果测试失败,可以试试关闭这个安全模块:

# echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope # 允许普通用户注入代码到自己启动的进程中,root用户可以注入所有进程
# echo 2 | sudo tee /proc/sys/kernel/yama/ptrace_scope # 只允许root用户注入代码

4,代码&原理分析:
sample-library.c源码如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/ptrace.h>

/*
 * sleepfunc()
 *
 * The only purpose of this function is to output the message "sleeping..."
 * once a second to provide a more concrete idea of when the sample library
 * gets injected.
 *
 */

void sleepfunc()
{
	struct timespec* sleeptime = malloc(sizeof(struct timespec));

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

	while(1)
	{
		printf("sleeping...\n");
		nanosleep(sleeptime, NULL);
	}

	free(sleeptime);
}

/*
 * main()
 *
 * Call sleepfunc(), which loops forever.
 *
 */

int main()
{
	sleepfunc();
	return 0;
}

代码很简单,就是循环printf和sleep。

sample-library.c源码如下:

#include <stdio.h>
#include <dlfcn.h>

/*
 * hello()
 *
 * Hello world function exported by the sample library.
 *
 */

void hello()
{
	printf("I just got loaded\n");
}

/*
 * loadMsg()
 *
 * This function is automatically called when the sample library is injected
 * into a process. It calls hello() to output a message indicating that the
 * library has been loaded.
 *
 */

__attribute__((constructor))
void loadMsg()
{
	hello();
}

这个代码更简单,但值得注意的是__attribute__((constructor)),这个是gcc为函数提供的类型属性修饰关键字,其中constructor类似于构造函数,即在main函数执行之前或dlopen函数返回之前被调用。

另外一个注入程序inject对应三个源文件utils.c、ptrace.c、inject-x86_64.c,代码就不贴了,其核心思想是利用ptracedlopen向已运行进程中注入.so,执行注入后,该so被link到目标进程的地址空间中,从而目标进程就能“看到”该so中相关符号,比如函数等。同时,利用constructor构造函数能够在so被注入后立即执行的特性,就能进行一些初始化操作。

下面看一个具体的示例:

晕,没调完。。。说明两点:
1,下面的代码还有问题,仅供参考,函数替换肯定能做,但要做得没有问题(不会出现crash)就需要比较细致的功夫,具体可以参考后面的链接。
2,示例中采用__libc_dlopen_mode()是因为这几个函数来之标准C库(这里即glic),不用去链接libdl来使用dlopen(),比较省事。

/*
gcc -Wall -Wextra -Os -o my-sample-target1 my-sample-target1.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/ptrace.h>

void hello()
{
	printf("hello\n");
}

int main()
{
    struct timespec* sleeptime = malloc(sizeof(struct timespec));

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

    while(1)
    {
        hello();
        nanosleep(sleeptime, NULL);
    }

    free(sleeptime);

    return 0;
}

/*
gcc -Wall -Wextra -Os -D_GNU_SOURCE -shared -o my-sample-library1.so -fPIC my-sample-library1.c
*/

#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>

void* __libc_dlopen_mode(const char*, int);
void* __libc_dlsym(void*, const char*);
int   __libc_dlclose(void*);

void (*hello)();

void world()
{
    printf("world\n");
}

void function_replace(void *target, void *replacement)
{
    assert(((uintptr_t)target & 0x07) == 0); // 8-byte aligned?
    void *page = (void *)((uintptr_t)target & ~0xfff);
    mprotect(page, 4096, PROT_WRITE | PROT_EXEC);
    uint32_t rel = (char *)replacement - (char *)target - 5;
    union {
        uint8_t bytes[8];
        uint64_t value;
    } instruction = { {0xe9, rel >> 0, rel >> 8, rel >> 16, rel >> 24} };
    *(uint64_t *)target = instruction.value;
    mprotect(page, 4096, PROT_EXEC);
}

__attribute__((constructor))
void loadMsg()
{
	void *lib = RTLD_DEFAULT;
    *(void**)(&hello) = __libc_dlsym(lib, "hello");
    if (hello != NULL)
        function_replace(hello, world);
    else
        printf("find function hello failed.\n");
}

/*
gcc -std=c99 -Wall -Wextra -Os -D_GNU_SOURCE -shared -o my-sample-library.so -fPIC my-sample-library.c
*/

#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>

void perror(const char *s);
void* __libc_dlopen_mode(const char*, int);
void* __libc_dlsym(void*, const char*);
int   __libc_dlclose(void*);

int (*old_printf)(const char *format, ...);
int new_printf(const char *format, ...)
{
    va_list ap;

    old_printf("hello world.\n");

    va_start(ap, format);
    old_printf(fformat, ap);
    va_end(ap);
}

void* function_replace(void *target, void *replacement)
{
    assert(((uintptr_t)target & 0x07) == 0); // 8-byte aligned?
    void *page = (void *)((uintptr_t)target & ~0xfff);
    mprotect(page, 4096, PROT_WRITE | PROT_EXEC);
    uint32_t rel = (char *)replacement - (char *)target - 5;
    union {
        uint8_t bytes[8];
        uint64_t value;
    } instruction = { {0xe9, rel >> 0, rel >> 8, rel >> 16, rel >> 24} };
    *(uint64_t *)target = instruction.value;
    mprotect(page, 4096, PROT_EXEC);
}

__attribute__((constructor))
void loadMsg()
{
    void *clib;

    if ((clib = __libc_dlopen_mode("libc.so.6", RTLD_NOW)) == NULL) {
        perror("dlopen libc failed.\n");
        clib = RTLD_DEFAULT;
    }
    
    *(void**)(&old_printf) = __libc_dlsym(clib, "printf");
    
    old_printf = function_replace(old_printf, new_printf)

}

参考链接:

https://www.anquanke.com/post/id/83423

http://blog.csdn.net/hpp24/article/details/52125568

http://www.freebuf.com/articles/system/6388.html

http://packetstormsecurity.nl/mag/phrack/phrack59.tar.gz

http://www.blackhat.com/presentations/bh-europe-01/shaun-clowes/injectso3.ppt

ftp://tsx.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz

http://www.big.net.au/~silvio/lib-redirection.txt

http://online.securityfocus.com/data/library/subversiveld.pdf

https://stackoverflow.com/questions/24355344/inject-shared-library-into-a-process

https://lkubuntu.wordpress.com/2016/01/31/injecting-code-into-running-process-with-linux-inject/

https://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application

https://reverseengineering.stackexchange.com/questions/185/how-do-i-add-functionality-to-an-existing-binary-executable

https://www.linuxjournal.com/article/6210

https://stackoverflow.com/questions/3270281/can-gdb-make-a-function-pointer-point-to-another-location

https://stackoverflow.com/questions/9759880/automatically-executed-functions-when-loading-shared-libraries

https://stackoverflow.com/questions/29648919/hook-and-replace-export-function-in-the-loaded-elf-so-shared-library

http://nullprogram.com/blog/2016/03/31/

http://jbremer.org/x86-api-hooking-demystified/

https://blogs.msdn.microsoft.com/oldnewthing/20110921-00/?p=9583

https://lwn.net/Articles/620640/

http://conf.researchr.org/event/pldi-2016/pldi-2016-papers-living-on-the-edge-rapid-toggling-probes-with-cross-modification-on-x86

https://stackoverflow.com/questions/18811446/finding-a-symbol-in-my-own-process

转载请保留地址:http://www.lenky.info/archives/2018/03/2627http://lenky.info/?p=2627


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.