Linux进程伪装(二):进程名&&命令行

admin 2024年11月4日23:20:46评论16 views字数 3904阅读13分0秒阅读模式

 前言

进程伪装在笔者看来是一种拖延战术,通过伪造恶意进程的相关信息,绕过一些基于进程静态信息的检测防御手段,使其被防守者初步判定为正常进程,混淆防守者视线,为扩大攻击战果尽可能的拖延时间。在Linux进程伪装系列文章的第一篇中,笔者分享了一种简单而特殊的进程伪装场景,通过修改可执行文件为内核线程名来实现进程名伪造。那么在实际攻防场景中,攻击者更多会在运行时修改进程的相关信息,达到进程伪装的目的。笔者通过本文和各位读者分享运行时修改进程名以及命令行的方式以及检测手段,希望有所帮助。本文较长,建议先收藏,细细阅读

正文

伪装进程的本质是让用户态无法获取真实的进程信息。通常情况下,从用户态获取进程相关信息的本质都是读取/proc/pid目录下的内容,进程名和命令行也不例外,包括这也是为什么mount -o bind /proc/pid这种方式能够实现进程隐藏。那么进程名&&命令行可以从/proc/pid的哪些文件字段中获取呢?

用户态获取进程名&&命令行

笔者使用strace追踪常用的查看进程信息的命令open调用,发现涉及到进程名&&命令行的文件有三个:stat、status、cmdline
Linux进程伪装(二):进程名&&命令行
cmdline文件展示了进程的命令行(以�分割),stat文件按照特定顺序展示进程信息(以空格分割),status以更可读的方式展示了进程信息的字段以及值。其中stat中的第二个字段和status中的Name字段显示了进程名,cmdline整个内容是命令行。
Linux进程伪装(二):进程名&&命令行
那么这些就是全部了吗?非也!/proc/[pid]/comm也是
Linux进程伪装(二):进程名&&命令行
总结一下上述信息:
    • 进程名:comm文件、stat文件的第二个字段、status文件的Name字段
    • 命令行:cmdline文件

内核相关的数据结构

/proc是个伪文件系统,向用户态提供查看内核部分数据结构的接口,大部分文件是只读的。虽然root用户对comm文件有write权限,但是也无法修改这个文件,而且ps命令等并没有读取这个文件,所以单独修改这个文件的意义不大,还是需要去修改内核数据结构中进程名和命令行对应的变量。
Linux进程伪装(二):进程名&&命令行
task_struct是Linux内核中用于管理进程的主要数据结构,其包含了进程的各种信息,也包括了进程名和命令行信息。进程名对应的是task_struct中的comm[TASK_COMM_LEN]变量。从源码来看,进程名是长度为16的不包含路径的可执行文件名,且最后一个字符为空字符,也就是说进程名的有效长度为15。当可执行文件名长度超过15后,进程名会进行截断处理。
Linux进程伪装(二):进程名&&命令行
Linux进程伪装(二):进程名&&命令行
另外如果通过默认程序来运行脚本或其他,那么进程名会是脚本名,命令行中会包含默认执行程序,如下所示:
Linux进程伪装(二):进程名&&命令行
同时通过查阅资料得知,comm、stat以及status中存储的进程名皆对应comm[TASK_COMM_LEN]变量:
Linux进程伪装(二):进程名&&命令行
命令行cmdline则对应着task_struct->mm_struct中的arg_start和arg_end,前者表示命令行参数的开始地址,后者表示结束地址,各参数之间以空字符分割,结尾也为空字符。

修改进程名

对于进程名的修改比较简单,Linux内核提供prctl系统调用PR_SET_NAME选项,可以用来设置进程的名称。例如如下代码:
#include <stdio.h>#include <sys/prctl.h>#include <unistd.h>
int main() { if (prctl(PR_SET_NAME, "shorter") != 0) { perror("prctl(PR_SET_NAME)"); return 1; } sleep(120); return 0;}
    编译运行后,可以看到此时进程名已经被修改为目标值shorter,cmdline值仍为原始值。
Linux进程伪装(二):进程名&&命令行

修改命令行

那么如何修改cmdline呢?有一种较为粗暴简单的方式,就是直接覆盖argv数组,这种方式的弊端,因为在内存空间中,命令行和环境变量的地址是紧邻着,如果命令行过长,会覆盖环境变量。例如将上述代码修改为如下:
#include <stdio.h>#include <sys/prctl.h>#include <unistd.h>#include <string.h>
int main(int argc, char *argv[]) { char *new = "sshd -D"; // 设置进程名称 if (prctl(PR_SET_NAME, "shorter") != 0) { perror("prctl(PR_SET_NAME)"); return 1; } int i; size_t rsize=0; for(i=0;i<argc;i++){ rsize+=strlen(argv[i])+1; } printf("rsize:%zu",rsize); size_t nsize=strlen(new)+1; if (nsize>rsize){ // 设置命令行 strncpy(argv[0],new,nsize+1); }else{ strncpy(argv[0],new,rsize); } sleep(120); return 0;}
像下图这种修改后命令行长度小于原始值的情况,无非是cmdline中用空字节补足长度而已,环境变量等不受影响。
Linux进程伪装(二):进程名&&命令行
但是像下图这种修改后命令行长度大于原始值,cmdline可以修改成功且显示正常,但是环境变量会被覆盖部分长度,这样也导致程序本身获取的环境变量可能出现错误。
Linux进程伪装(二):进程名&&命令行
笔者在探究查阅资料过程中,发现BPFDoor后门实现了伪装进程名和命令行的功能,感兴趣的可以去看一下代码。代码逻辑是在堆上分配新的内存空间,将原始的环境变量复制过去,并将对应的environ[i]指向新地址,确保通过environ[i]还能访问到正确的环境变量;然后将之前命令行参数和环境变量的内存空间清空,写入修改后的命令行,最后再使用prctl来修改进程名。这种方式确实可以既能一定程度上随意修改命令行,也可以保证能够获取到正确的环境变量。但是也有一个弊端,就是这种方式并未修改mm_struct中的env_start以及env_end,所以/proc/pid/environ文件输出的内容可能会出现大批量的空字符。
还有一种方式是通过prctl调用PR_SET_MM选项的PR_SET_MM_ARG_START以及PR_SET_MM_ARG_END,来重新设置mm_struct命令参数的地址空间,但是只有特权用户才可以调用成功:
Linux进程伪装(二):进程名&&命令行
demo如下所示,将命令行修改为new_program --first-argv first --second-argv second,将进程名修改为NewProcName:
#include <string.h>#include <unistd.h>#include <stdlib.h>#include <sys/prctl.h>#include <sys/mman.h>
int main(int argc, char **argv) { char * nm; char cmd[] = "new_program --first-argv first --second-argv second"; size_t cmd_size=strlen(cmd); nm = mmap(NULL, cmd_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); strncpy(nm, cmd, cmd_size); prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nm, 0, 0); prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nm + cmd_size + 1, 0, 0); prctl(PR_SET_NAME,"NewProcName"); sleep(120); return 0;}
Linux进程伪装(二):进程名&&命令行

如何检测

笔者尝试编写osquery规则,检测进程名、命令行以及可执行文件之间的关系,但是误报率居高不下,本质上还是因为很多正常进程本身也会存在这种行为,例如suricata、openresty、postgres等等,所以本次规则只能算是初步筛选一些可疑进程,还需要对于结果进行确认。
Linux进程伪装(二):进程名&&命令行
select pid,name,path,cmdline from processes where path <>'' and not (path like concat('%/',name) and (cmdline like concat(name,'%') or cmdline like concat('%/',name,'%'))) and (cmdline not like concat('%',name,'%') or path <> replace(split(cmdline,' ',0),'./',concat(cwd,'/')));

总结

本文介绍了Linux进程伪造中如何修改进程名和命令行以及对应的底层知识。进程名对应着task_struct数据结构中的comm[TASK_COMM_LEN],通过prctl系统调用可进行修改。命令行也可以通过prctl调用修改,但是需要特权用户。也可以通过覆盖原始命令行的形式进行修改,但是如果长度超过原始命令行,会覆盖环境变量,导致应用环境变量获取错误。在最后笔者也分享了对应的osquery检测规则,但是由于很多正常进程都会存在一些相似行为,所以检测结果中存在一定的误报,需要人工二次确认。

END
如果本文对你有用,欢迎点赞关注!
每周更新原创文章!

原文始发于微信公众号(风奕安全):Linux进程伪装(二):进程名&&命令行

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月4日23:20:46
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux进程伪装(二):进程名&&命令行https://cn-sec.com/archives/3353897.html

发表评论

匿名网友 填写信息