The Rootkit Mac OS
The Rootkit Mac OS
Rootkit Hunter is my favorite tool to use on the Mac for finding rootkits. It’s relatively easy to use and the output is very easy to understand. Firstly, go to the download page and click on the green download button. Go ahead and double-click on the.tar.gz file to unpack it. A common rootkit definition is a type of malware program that enables cyber criminals to gain access to and infiltrate data from machines without being detected. It covers software toolboxes designed to infect computers, give the attacker remote control, and remain hidden for a long period of time.
Kernel rootkit is considered the most dangerous malware that may infect computers. Operating at ring 0, the highest privilege level in the system, this super malware has unrestricted power to control the whole machine, thus can defeat all the defensive and monitoring mechanisms. Unfortunately, dynamic analysis solutions for kernel rootkits are severely lacking; indeed, most existing dynamic tools are just built for userspace code (ring 3), but not for Operating System (OS) level. This limitation forces security researchers to turn to static analysis, which however proved to be very tricky & time consuming. In this research, I use Qiling Framework as the main emulator ( give him stars on Github 😁 ).
This research is a part of our publication at BlackHat USA 2020. The presentation is now available here.
First, let see how a rootkit is loaded into the kernel. The macOS kernel is officially known as XNU, which is a hybrid kernel combined from Mach kernel and BSD kernel. The kernel format also belongs to the MachO executable file. The kernel usually exposes its interface, a.k.a KPI, to let users use its functionality, and all of them are implemented inside the kernel code. Like other operating systems, macOS needs drivers to control devices, they are called Kernel Extensions or Kexts. The kernel extension is a bundle of files, and the kernel loads it from external space. Some interesting information can be gathered from Info.plist
inside the bundle. In general, rootkit plays a role as a driver then it gets full power features from KPI.
In order to emulate the driver, I decided to load both kernel and kernel extensions together. Because the kernel is also a MachO executable binary, as well as the main component of KEXT, so I can load all of their Segment64 to emulator engine. Now I can access all implemented code of KPIs from the kernel. Besides, like a normal application, I also have to resolve local symbols and some other dynamic symbols of KEXT. Note that kernel releases its KPIs through some dependencies, so It is necessary to create junk code as a kind of indirection calling.
Next, a driver will run from its initial function, and this entry address can be extracted from the binary symbol, for IOKit driver is ::start
method, and for the generic driver is the address stored in __realmain
symbol. Before emulating the driver, some objects/context under kernel space should be initialized. I setup mac_policy_list
by allocating a new object in the emulator engine and fill the address in target on kernel space. The same thing I will do for allproc
symbol on kernel space. I also create some vnode
and credential
objects. Then I run emulation for preprocessing such as ::attach
, ::probe
, or kmod_info
. Finally, I can go into the entry of the driver.
Regarding instrumentation, I map all KPIs exported from kernel to user-defined methods. It helps to simplify some features or just pass through and use the native function.
On the other hand, I also hook thread-related KPIs to disable multi-thread functionality. I give the driver a chance to interact with the real machine with some specific KPIs. For example, getattrlistbulk
is a function to retrieve every entry in a directory ( which is called inside the application ls
). So I just scan all files and folders in the directory and pack them using vfs_attr_pack
function from the kernel. To emulate syscall, I just find the sysent
symbol on loaded kernel space, assign arguments to registers and run the entry address.
In some cases, we may want to call a native KPI from the hooked KPI. Normally, we have to save the current state and run another emulator. But it may screw up in some complicated situations. So I have a workaround here: I create a junk code and push its address to stack as saved rip. This junk code has three main missions: prepare arguments to registers and stacks, clear stack after calling, and jump to native KPI directly.
What Is Rootkit Scan
Under kernel, there are many events and callbacks that need to be emulated. So I build an Event Management System to listen to a register request from the driver and emulate the interaction from the user. That means I hook into KPI used to register callbacks to create a new event on EMS, then when user wants to trigger callbacks, I just emulate the corresponding address from EMS. In details,
The Rootkit Mac Os 11
Event | Hook function to register | Hook function to unregister | Additional parameters when trigger event |
---|---|---|---|
SYSCTL | sysctl_register_oid() | sysctl_unregister_oid() | sysctlbyname_args objects |
Network Kernel Extension | ctl_register() | ctl_deregister() | socket object and mbuf data |
Network Filter | sflt_register() | sflt_unregister() | raw network packet |
MAC Policy | mac_policy_register() | mac_policy_unregister() | |
KAuth | kauth_listen_scope() | kauth_unlisten_scope() |
The Rootkit Mac Os Catalina
I have some demonstration about emulating a famous rootkit on MacOS: Rubilyn
The Rootkit Mac OS