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: file:///root/vol2.3/rubilyn.vmem 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.
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.