47

Rootkit隐藏进程和端口检测

 5 years ago
source link: https://www.freebuf.com/articles/system/198769.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

*本文作者:zhouqiao,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

一、引言

Rootkit是一种特殊的恶意软件,它的功能是在安装目标上隐藏自身及指定的文件、进程和网络链接等信息,比较多见到的是Rootkit一般都和木马、后门等其他恶意程序结合使用。

例如:inetd或者login,为攻击者提供后门;隐藏攻击者的目录和进程的程序,ps、netstat等常见命令。

rootkit检测也成为主机安全一项重要功能,针对rootkit中最常见隐藏进程、端口检测,主要分为两种检测思路,一种基于内核内存分析,一种基于应用层分析。

基于内存分析Rootkit检测可参考Rootkit检测,该方案缺点是需要增加内核模块,风险高,检测效果最好。

本文介绍第二种方案,unhide在应用层发现隐藏进程、端口,该方案风险小,可集成到主机安全agent中。

二、应用层隐藏进程检测

2.1 进程隐藏和检测方式

进程隐藏两种方式:

1) 替换ps命令,在读取/proc/pid目录时,过滤掉需隐藏进程信息

2)加载内核模块,通过拦截proc文件系统的回调函数,过滤掉需隐藏进程信息

检测核心思想:

通过libc系统函数盲测进程pid的存活状态,再根据ps结果对比差异,判断该pid是隐藏进程。

unhide提供如下19种检测方式,大致可分为四类:一类通过procfs下的进程目录信息,第二类通过系统调用函数, 第三类通过前两类组合方式,第四类通过爆力破解(不推荐)。

tab_test[TST_PROC].func = checkproc;

   tab_test[TST_CHDIR].func = checkchdir;

   tab_test[TST_OPENDIR].func = checkopendir;

   tab_test[TST_READDIR].func = checkreaddir;

   tab_test[TST_GETPRIO].func = checkgetpriority;

   tab_test[TST_GETPGID].func = checkgetpgid;

   tab_test[TST_GETSID].func = checkgetsid;

   tab_test[TST_GETAFF].func = checksched_getaffinity;

   tab_test[TST_GETPARM].func = checksched_getparam;

   tab_test[TST_GETSCHED].func = checksched_getscheduler;

   tab_test[TST_RR_INT].func = checksched_rr_get_interval;

   tab_test[TST_KILL].func = checkkill;

   tab_test[TST_NOPROCPS].func = checkallnoprocps;

   tab_test[TST_BRUTE].func = brute;

   tab_test[TST_REVERSE].func = checkallreverse;

   tab_test[TST_QUICKONLY].func = checkallquick;

   tab_test[TST_SYS_INFO].func = checksysinfo;

   tab_test[TST_SYS_INFO2].func = checksysinfo2;

   tab_test[TST_SYS_INFO3].func = checksysinfo3;

从名字上可以看出,unhide使用chdir,opendir,readdir,getpriority,getpgid,getsid,sched_getaffinity,kill,sysinfo等libc函数。

下面分别从三类中各选择一种检测方式分析。

2.2 procfs检测

抽取checkreaddir检测分析:

1)遍历/proc目录, 子文件名如果是数字,代表是一个进程pid
2)读取进程状态task目录(/proc/pid/task), 获取各子线程号
3) 通过ps命令查找是否存在该线程
4)对比差异,不存在判断为隐藏进程
1)procdir = opendir("/proc");

2)dirproc = readdir(procdir));

3)taskdir = opendir(task);

4) dir = readdir(taskdir)

5) checkps(procpids,PS_THREAD)

该类方法能检测出第一种隐藏进程方式,对第二种无效。

2.3 syscall检测

抽取checkgetsid检测分析:

max_pid通过读取 /proc/sys/kernel/pid_max获取:

1)从1到max_pid遍历进程, 通过getsid返回值和错误码探测进程存活状态
2)通过ps命令查找是否存在该进程
3)再次通过getsid确认进程存活状态,防止在执行ps这时间内,进程退出了
4)对比差异,不存在判断为隐藏进程
1)ret = getsid(syspids)

2)checkps(syspids,PS_PROC | PS_THREAD);

3)ret = getsid(syspids)

该类方法都能检测上述两种隐藏进程方式。

2.4 compund检测

抽取checkallquick检测分析:

1)从1到max_pid遍历进程

2) 通过kill返回值和错误码探测进程存活状态

3) 通过getpriority返回值和错误码探测进程存活状态

4) 通过getpgid返回值和错误码探测进程存活状态

5) 通过getsid返回值和错误码探测进程存活状态

6) 通过sched_getaffinity返回值和错误码探测进程存活状态

7) 通过sched_getparam返回值和错误码探测进程存活状态

8) 通过sched_getscheduler返回值和错误码探测进程存活状态

9) 通过sched_rr_get_interval返回值和错误码探测进程存活状态

10) 通过chdir,opendir读取进程目录(/proc/pid)

11) 通过ps命令查找是否存在该进程

12)再次通过kill确认进程存活状态,防止在执行ps这时间内,进程退出

13)对比差异,只有进程不存在(found=0)或者进程经过11项检测(found == 11)认为是正常的,其余都判断为隐藏进程

ret = kill(syspids, 0);

ret = getpriority(PRIO_PROCESS, syspids);

ret = getpgid(syspids);

ret = getsid(syspids);

ret = sched_getaffinity(syspids, sizeof(cpu_set_t), &mask);

ret = sched_getparam(syspids, &param);

ret = sched_getscheduler(syspids);

statusproc = stat(directory, &buffer);

statusdir = chdir(directory);

dir_fd = opendir(directory) ;

checkps(syspids,PS_PROC | PS_THREAD)

ret = kill(syspids, 0);

if (found_killbefore == found_killafter) {

     if ( ! ((found_killbefore == 0 && found == 0) ||

             (found_killbefore == 1 && found == 11)) ) {

        printbadpid(syspids);

     }

三、应用层隐藏端口检测

核心思想:通过libc系统函数bind,listen盲测端口

3.1 tcp隐藏端口检测

1)从1到65535遍历端口

2) 创建一个基于tcp协议SOCK_STREAM的socket

3) 通过bind返回值和错误码探测端口状态

4) 如果被占用,通过listen 错误码是EADDRINUSE确定端口占用

5) 通过ss或netstat命令过滤tcp协议,查看端口情况

6)对比差异,确认该端口为隐藏端口

socket_desc=socket(AF_INET,SOCK_STREAM,0);

bind(socket_desc,(struct sockaddr *)&address,sizeof(address));

listen(socket_desc,1);

if(EADDRINUSE == errno) {

    checkoneport(i, tcpcommand, TCP);

}

3.2 udp隐藏端口检测

相比tcp, udp使用SOCK_DGRAM的socket, 缺少listen这步,其余检测步骤类似

socket_desc=socket(AF_INET,SOCK_DGRAM,0);

bind(socket_desc,(struct sockaddr *)&address,sizeof(address));

if(EADDRINUSE == errno) {

    checkoneport(u, udpcommand, UDP);

}

四、结论

本文提供的通过应用层方式检测rootkit中最常见的隐藏进程和端口,风险性小,可无缝集成到主机安全agent中。

*本文作者:zhouqiao,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK