Tuesday, June 11, 2013

MOVP II - 4.5 - Mac Volatility vs the Rubilyn Kernel Rootkit

In our final Month of Volatility Plugins post, we are going to demonstrate a number of plugins that can be used to detect kernel level OS X rootkits. To show these capabilities I am going to analyze a system that is infected with the rubilyn rootkit. I want to thank @osxreverser for providing me with the infected memory sample that I did this analysis on.


This rootkit was released on full disclosure last year, and claimed the following capabilities:

* works across multiple kernel versions (tested 11.0.0+)
* give root privileges to pid
* hide files / folders
* hide a process
* hide a user from 'who'/'w'
* hide a network port from netstat
* sysctl interface for userland control
* execute a binary with root privileges via magic ICMP ping 
The release notes claim to use system call hooking and DKOM to accomplish these goals. 

Detecting Hidden Processes

The first capability of Rubilyn that we will detect is the hiding of processes. If we run the mac_psxview plugin on our infected image, we immediately see a suspicious process:

$ python vol.py -f rubilyn.vmem --profile=MacLion_10_7_5_AMDx64 mac_psxview
Volatile Systems Volatility Framework 2.3_beta
Offset(P)          Name                    PID pslist parents pid_hash pgrp_hash_table session leaders task processes
------------------ -------------------- ------ ------ ------- -------- --------------- --------------- --------------
0xffffff80008d8d40 kernel_task               0 True   True    False    True            True            True
0xffffff8005ee4b80 launchd                   1 False  True    True     True            True            True
0xffffff8005ee4300 kextd                    10 True   True    True     True            True            True
0xffffff8005ee3ec0 UserEventAgent           11 True   False   True     True            True            True
0xffffff8005ee3640 notifyd                  12 True   False   True     True            True            True
0xffffff8005ee3200 mDNSResponder            13 True   False   True     True            True            True
0xffffff8005ee2dc0 opendirectoryd           14 True   False   True     True            True            True
0xffffff8005ee2980 diskarbitrationd         15 True   False   True     True            True            True
0xffffff8005ee2540 configd                  16 True   False   True     True            True            True
0xffffff8005ee2100 syslogd                  17 True   False   True     True            True            True

The launchd process of PID 1, appears in all columns but 'pslist'. This shows us that the rootkit was used to hide the particular process from the kernel's process list. In a real investigation this would be a process related to the attacker's activity (network listener, keylogger, etc), and we could then immediately focus our investigation on it. See our previous post from this week, MOVP 3.2 - Dumping, Scanning, and Searching Mac OSX Process Memory, to learn how to investigate individual processes.

System Call Hooking

Since the documentation claims to use system call hooking, it makes sense to check this. Volatility's mac_check_syscalls is able to determine hooks to the system call table and print the address of the hook. This reveals three hooked system calls:

$ python vol.py -f rubilyn.vmem --profile=MacLion_10_7_5_AMDx64 mac_check_syscalls | grep HOOK
Volatile Systems Volatility Framework 2.3_beta
SyscallTable       222 0xffffff7f807ff41d HOOKED
SyscallTable       344 0xffffff7f807ff2ee HOOKED
SyscallTable       397 0xffffff7f807ffa7e HOOKED

Looking up the system call table indexes for entries 222, 344, and 397 reveal that getdirentriesattr, getdirentries64, and write_nocancel are hooked. getdentires (get directory entries) are calls involved with reading of directories from active filesystems. These are almost always used to hide files and directories.

Reading the source code of the write_nocancel hook shows that it is looking for processes named 'grep', 'who', and 'netstat', and then filters out entries related to the rootkit. This will effectively hide data from these userland tools.

Custom Sysctl Handlers  

Rubilyn loads a kernel module, and if we use the mac_lsmod plugin, we see it listed as the first entry (note: modules are stored in the reverse order of when they loaded).

$ python vol.py -f rubilyn.vmem --profile=MacLion_10_7_5_AMDx64 mac_lsmod
Address                          Size   Refs   Version      Name
------------------ ------------------ -------- ------------ ----
0xffffff7f807fe000             0x5000    0     1            com.hackerfantastic.rubilyn
0xffffff7f8159d000             0xa000    0     0081.82.01   com.vmware.kext.vmhgfs
0xffffff7f80ad3000             0x6000    0     5.1.0        com.apple.driver.AppleUSBMergeNub
0xffffff7f80a7a000             0x8000    0     5.0.0        com.apple.iokit.IOUSBHIDDriver
0xffffff7f80a82000             0x6000    1     5.0.0        com.apple.driver.AppleUSBComposite

The mac_check_sysctl plugin lists all active sysctl entries and their handlers, and prints "OK" if the handler points to a known address within the kernel or a kernel module or prints "HOOKED" if is not found in these places.

In the case of Rubilyn, its handlers will be listed, but marked "OK" as the kernel module is still in the module list. Since we know the rootkit is malicious, we have two choices:

1) filter out the module in mac_lsmod so it appears hidden to other plugins
2) leverage mac_volshell to print out the sysctl handlers inside the module

Since 1) is fairly straightforward and many people have never used mac_volshell, I chose to do 2).

$ python vol.py -f rubilyn.vmem --profile=MacLion_10_7_5_AMDx64 mac_volshell
Volatile Systems Volatility Framework 2.3_beta
Current context: process kernel_task, pid=0 DTB=0x100000
Welcome to volshell! Current memory image is:
To get help, type 'hh()'
>>> import volatility.plugins.mac.check_sysctl as check_sysctl
>>> for (sysctl, name, val, _) in check_sysctl.mac_check_sysctl(self._config).calculate():
...     handler = sysctl.oid_handler
...     if 0xffffff7f807fe000 <= handler < 0xffffff7f807fe000+0x5000:
...         print "name: %s val: %s handler: %x" % (name, str(val), handler)
name: pid2 val: 0 handler: ffffff7f807ff14b
name: pid3 val: 0 handler: ffffff7f807ff1ed
name: dir val:  handler: ffffff7f807ff2aa
name: cmd val:  handler: ffffff7f807ff2bb
name: user val:  handler: ffffff7f807ff2cc
name: port val:  handler: ffffff7f807ff2dd
>>> dis(0xffffff7f807ff14b, 32)
0xffffff7f807ff14b 55                               PUSH RBP
0xffffff7f807ff14c 4889e5                           MOV RBP, RSP
0xffffff7f807ff14f 4157                             PUSH R15
0xffffff7f807ff151 4156                             PUSH R14
0xffffff7f807ff153 4154                             PUSH R12
0xffffff7f807ff155 53                               PUSH RBX
0xffffff7f807ff156 8b5720                           MOV EDX, [RDI+0x20]
0xffffff7f807ff159 488b7718                         MOV RSI, [RDI+0x18]
0xffffff7f807ff15d e8fea3d57f                       CALL 0xffffff8000559560
>>> dis(0xffffff7f807ff1ed, 32)
0xffffff7f807ff1ed 55                               PUSH RBP
0xffffff7f807ff1ee 4889e5                           MOV RBP, RSP
0xffffff7f807ff1f1 4157                             PUSH R15
0xffffff7f807ff1f3 4156                             PUSH R14
0xffffff7f807ff1f5 4155                             PUSH R13
0xffffff7f807ff1f7 4154                             PUSH R12
0xffffff7f807ff1f9 53                               PUSH RBX
0xffffff7f807ff1fa 50                               PUSH RAX
0xffffff7f807ff1fb 8b5720                           MOV EDX, [RDI+0x20]

In this output you can see that I import the mac_check_sysctl plugin. I then use it to enumerate every sysctl, and the generator gives me the sysctl structure, the name of entry, and the current value. I then use the information given from mac_lsmod about the Rubilyn module to filter out entries only belonging to the module. Since we have the handler address of each systcl handler, we could then reverse engineer the handler to see what it does (the 'dis' command).

The names of the discovered sysctl values seem to relate to the capabilities -- hiding ports & processes, running commands, and so on.

IP Filters 

The mac_ip_filters plugin lists any IP filters actived within the kernel. On a default system, there will be no output from this plugin.  It is likely that software firewalls will use this infrastructure to filter packets although we have not fully tested any. During our analysis of the rootkit, we see that it installs a handler for both incoming and outgoing packets:

# python vol.py -f rubilyn.vmem --profile=MacLion_10_7_5_AMDx64 mac_ip_filters
Volatile Systems Volatility Framework 2.3_beta
Context    Filter           Pointer            Status
---------- ---------------- ------------------ ------
INPUT      rubilyn          0xffffff7f807ff577 OK
OUTPUT     rubilyn          0xffffff7f807ff5ff OK
DETACH     rubilyn          0xffffff7f807ff607 OK

Analysis of the INPUT handler shows that it is used to implement an ICMP-based command & control backdoor. The OUTPUT and DETACH are stubs.

Escalation of Process Privleges  

The only capability not detected so far is the escalation of privileges of userland processes. The current version of Volatility is not able to detect this directly on Mac systems as we can on Linux. There are a number of indirect ways to detect this, such as bash history, strings/grep, and others, but those are not something that would be implemented as a plugin. Adding this capability to the next release of Volatility after 2.3 is on the TODO list.

Even with the current release of Volatility it would be hard for rootkits to hide this activity. Process privilege escalation is generally used when the rootkit lives long term in the kernel, but the attacker sparsely uses the infected machine. Upon logging into the system through an SSH or other backdoor, the attacker can then communicate with the kernel rootkit to elevate privileges, and the rouge communication channels (sysctl, system call table, etc) can be detected by Volatility.


We have demonstrated a number of ways to detect the Rubilyn kernel rootkit. The techniques shown will detect a wide range of other rootkits as well as there are only so many places that rootkits can utilize to accomplish their goals.

No comments:

Post a Comment