User-Mode Driver Emulation: Buffered Code Execution (BCE) Matt Conover Principal Software Engineer,...
-
Upload
cameron-davis -
Category
Documents
-
view
228 -
download
0
Transcript of User-Mode Driver Emulation: Buffered Code Execution (BCE) Matt Conover Principal Software Engineer,...
User-Mode Driver Emulation:Buffered Code Execution (BCE)
Matt Conover
Principal Software Engineer, Symantec Research Labs
2
Project Goal
• Develop a way to observe the interaction between a suspicious kernel driver and the kernel
• Use the observed interactions as the basis for detection
• This tool is used by anti-virus analysts to analyze and debug a rootkit driver
– A usual kernel debugger must debug the whole kernel
– Our tool can isolate the rootkit and analyze only the rootkit
3
Project Implementation Strategies
• We will use a virtual machine (Windows guest in VMWare)
– In the virtual machine, we will use our tool to execute the rootkit driver and observe its behavior
• Our tool can record the rootkit’s interactions with the kernel, so we can learn the rootkit’s behavior
– Includes using kernel APIs, reading from kernel memory, writing to kernel memory, and using privileged instructions.
• How can this be accomplished?
– Emulation
– Debugging (single-step, breakpoints, etc.)
– Dynamic translation (rewriting code)
4
Strategy #1: Emulation
• Utilizes user-mode emulation of the driver
– Privileged instructions and kernel-mode APIs are emulated
– Doesn’t interact with kernel
• Requires emulating the kernel-mode APIs
• Symantec has tried this approach
– RAID 2007, “A Forced Sampled Execution Approach to Kernel Rootkit Identification”
• Norman Sandbox uses this approach
• Too much work!
5
Strategy #2: Debugger
• Use kernel-mode debugger (such as Microsoft’s WinDbg)
• Use breakpoints or single-stepping to control the rootkit
• This approach is not very reliable
• The rootkit and debugger are running at the same privilege level
• It’s easy for the rootkit to detect or disable the debugger (for example, check KdDebuggerPresent variable)
6
Strategy #3: Dynamic Translation
• The rootkit can be run natively but using translated code.
– This is the technique used by VMWare and QEMU
• Rather than transfer control the driver’s entry point:
• We copy (translate) the instructions into our own code buffer
• Execute the code from the buffer, which will return control to our translator
• We repeat this process for each instruction/code block until the program finishes
7
Strategy #3: Dynamic Translation
TranslateBuffer:
switch to “guest” context
InstructionToBeExecuted
save changes to “guest context”
RunProgram:
while (ProgramRunning):
if (VirtualizeInstruction(Guest.EIP)) continue
memcpy(TranslateBuffer.InstructionToBeExecuted,
Guest.EIP, get_ilen(Guest.EIP))
call TranslateBuffer
8
Strategy #3: Dynamic Translation
• Certain instructions can be specially handled:
call GetEIP <- should push virtual return address
GetEIP: pop eax
– EAX should contain the original (virtual) address, not the translated code’s address
• We also need to prevent the rootkit from reading/writing certain addresses
– Don’t let it detect our code
– Don’t let it do some action that will cause us to lose control
9
Strategy #3: Dynamic Translation
• This seems the best trade-off
– Although some instructions need to be specially handled,
– Execution can be native
– We can more easily hide our presence
– We can improve the performance using caching
10
Questions?
11
Our Approach: Buffered Code Execution (BCE)
• We use a form of dynamic translation
– That’s why we call this project “BCE”
• Run the rootkit at ring 3, but make the rootkit think it is ring 0
– We can keep control of the rootkit, while still observing its original behavior
– We don’t need to emulate instructions, we just need to handle faults
– In other words, we will need to handle privileged instructions and privileged memory access specially (proxy them to ring 0)
12
Our Approach: Buffered Code Execution (BCE)
• How it works
– We make the rootkit’s own stack and code pages user-mode (ring 3) accessible We just directly execute all rootkit’s instructions.
– As long as it doesn’t interact with the kernel, the code runs without a problem
13
Our Approach:Buffered Code Execution (BCE)
• If the rootkit tries to read/write/call a kernel-mode address…
– It will produce an access violation (since ring 3 code can’t access the kernel directly)
• When we catch this access violation
– We execute the faulting instruction at ring 0
– Then we continue execution at the next instruction at ring 3
• Result
– We can just let the code execute and wait for an access violation! Then we record the interesting event and continue execution
– We can easily observe the rootkits key behaviors without too much noise or effort
14
Address Space View
Kernel Address Space
BCE Region:
Rootkit Driver Rootkit Stack
0xFFFFFFFF
0x80000000NTOSKRNL
BCE
Ring3 Accessible
BCE
Non-Executable Pages
BCEK
Code Buffer
15
Address Space View
Kernel Address Space (shared)BCE Driver
Region
0xFFFFFFFF
0x80000000
A.exe
BCEK
BCE Driver Region
B.exe
BCEK
0x00000000
User Address Space (unique per process)
BCEK driver is always present in all processes
0x7FFFFFF
16
Questions?
17
BCE Version 1
• The first version of BCE was a user-mode process (BCE.exe) which ran the rootkit from user-mode:
– When the rootkit tries to access kernel-memory, it causes an access violation (ring 0 cannot access ring 3)
– Use Win32 Structured Exception Handling (SEH) to detect the faults
• How does it work?
– By recording these faults, we can detect when the rootkit is trying interacting with the kernel
– We will specially handle these faults, so that the rootkit works correctly
– This way, the rootkit will work correctly even though it’s running in user-mode
18
BCE Version 1
• STATUS_ACCESS_VIOLATION
– Kernel rootkit tried to read or write to kernel memory
MOV EAX, DWORD PTR [KeServiceDescriptorTable]
– Kernel rootkit tried to call a kernel-mode API
CALL PsGetCurrentProcessId
• STATUS_PRIVILEGED_INSTRUCTION
– Kernel rootkit used a privileged instruction:
IN, OUT
LIDT, LGDT, LTR
RDMSR, WRMSR
MOV CRx, ...
19
Hurdles to Overcome:
• The approach I have just mentioned, in fact, doesn’t work very well….
• What’s the problem?
• Kernel-mode callbacks…
• To fix this problem, we need to use a different architecture
• First, let’s discuss what a kernel-mode callback is
20
Hurdles to Overcome: Kernel-Mode Callbacks
• What’s a callback? Any kind of function pointer…
– System call handler
– Interrupt handler
– PsCreateSystemThread, PsSetCreateProcessNotifyRoutine, etc.
21
Hurdles to Overcome: Kernel-Mode Callbacks
• Callbacks are asynchronous, they can happen at any time
• Imagine the rootkit replaces an interrupt handler
• When the interrupt happens, if our user-mode process is not the current process, the result is unpredictable
22
Callback Problem
RootKit.sys NTOSKRNL
IDT[0x03]
Set
IDT[0x03] =
0x404037
CPU
0x404037:
Int3Handler
Arbitrary.exe
_asm int 3
0x404037:
???
User-Mode Address = Process-Specific
3
4
21
23
Hurdles to Overcome: Losing Control
• We must prevent the kernel from directly calling rootkit code, since it will run at ring 0 without protection.
• Example:
PsCreateSystemThread(…, RootkitFunc)
The kernel will call RootkitFunc directly, it will break our sandbox!
24
Questions?
25
Ring3 Emulation of an Instruction
Kernel Address Space
0xFFFFFFFF
0x80000000
BCE Buffer (ring 3)
Rootkit Stack
Rootkit Driver
ESP
EIP
Rootkit Context (ring3)
BCEK (ring0)#1 Read into BCE Buffer
#2 Jump to BCE Buffer
#3 BCE Buffer returns to BCEK
26
Example
TID 3224 -> eax BB40E64E ebx 11111111 ecx BB40E64E edx 80549FA0
TID 3224 -> esi 00000000 edi 00000000
TID 3224 -> esp 81563070 ebp 81563070 eflags 00000246
TID 3224 <- FF809FCC mov <eax>, 0xFF801780
TID 3224 -> eax FF801780 ebx 11111111 ecx BB40E64E edx 80549FA0
TID 3224 -> esi 00000000 edi 00000000
TID 3224 -> esp 81563070 ebp 81563070 eflags 00000246
TID 3224 <- FF809FD1 shr eax, 0x08
TID 3224 -> eax 00FF8017 ebx 11111111 ecx BB40E64E edx 80549FA0
TID 3224 -> esi 00000000 edi 00000000
TID 3224 -> esp 81563070 ebp 81563070 eflags 00000207
TID 3224 <- FF809FD4 xor eax, dword ptr ds:[edx]
27
Handling Kernel Function Calls:Calling a kernel API
Kernel Address Space
0xFFFFFFFF
0x80000000
Rootkit Stack
Rootkit Driver
BCEK
#1: Rootkit calls PsGetCurrentProcessId (caused a GPF)
GPF Handler
BCE Buffer (ring 3)
28
Handling Kernel Function Calls: BCEK proxies the called kernel API
Kernel Address Space
0xFFFFFFFF
Rootkit Stack
Rootkit Driver
BCEK
0x80000000
GPF Handler
#2: GPF is handled by BCEK, “returns” to BCEK
BCE Buffer (ring 3)
29
Handling Kernel Function Calls: Calling the actual kernel API
Kernel Address Space
0xFFFFFFFF
Rootkit Stack
Rootkit Driver
BCEK
0x80000000
GPF Handler
#3: BCEK calls API (ring0), API returns to BCEK, BCEK saves context
NTOSKRNL
BCE Buffer (ring 3)
30
Handling Kernel Function Calls: Return to ring3 emulation
Kernel Address Space
0xFFFFFFFF
Rootkit Stack
Rootkit Driver
BCEK
0x80000000
GPF Handler
#4: Repeat the process on the next instruction
NTOSKRNL
BCE Buffer (ring 3)
31
API Demo
[APIPROXY] TID 1704 API 0x8052702E (DbgPrint)
BCE Context 0x81D5A000:
host_prev_eip 0xFF80052C
host_prev_len 6
host_eip 0xFF800532
host_esp 0x81D7A068
Parameters: FF800540 FF6DE04C 00000000 8899AABB
Return address 0xFF8004A6
32
BCE Solving Callback Problem:Kernel calls a rootkit function
Kernel Address Space
0xFFFFFFFF
0x80000000
Rootkit DriverNX Handler
Interrupt Handlers
NTOSKRNL
#1 Kernel makes call into rootkit driver
#2 Causes NX fault (rootkit driver code is non-executable)
33
BCE Solving Callback Problem:Control passed to BCEK
Kernel Address Space
0xFFFFFFFF
0x80000000
Rootkit DriverNX Handler
Interrupt Handlers
NTOSKRNL
#3 NX handler saves caller information (NTOSKRNL)
BCE Buffer (ring 3)
BCEK (ring0)
ESP
EIP
#4 NX handler “returns” to BCEK callback handler
34
BCE Solving Callback Problem:BCEK emulates the rootkit function
Kernel Address Space
0xFFFFFFFF
0x80000000
Rootkit DriverNX Handler
Interrupt Handlers
NTOSKRNL
#5 BCEK emulates the callback code
BCE Buffer (ring 3)
BCEK (ring0)
35
BCE Solving Callback Problem:BCEK returns to kernel
Kernel Address Space
0xFFFFFFFF
0x80000000
Rootkit DriverNX Handler
Interrupt Handlers
NTOSKRNL
#6 BCEK reads in saved caller information
BCE Buffer (ring 3)
BCEK (ring0)
ESP
EIP
#7 BCEK returns to the original caller (NTOSKRNL)
36
Callback Demo: Before
[APIPROXY] TID 420 API 0x805C5BF6 (PsCreateSystemThread)
BCE Context 0x81D54000:
host_prev_eip 0xFF809614
host_prev_len 6
host_eip 0xFF80961A
host_esp 0x81D74048
Parameters: 81D74068 10000000 00000000 00000000
Return address 0xFF80961A
37
Callback Demo: After
[BCENX] IRQL 0, thread 0x81ECD2D8, FaultAddress 0xFF809290, ErrorCode 0x00000011, CS:EIP 0x08:0xFF809290
[HandleCallback] TID 224 @ 0xFF809290: callback (stack 0xF7872DB0)
eax 81ECD2D8 ebx 00000000 ecx 00000000 edx 821CC9FA
esi 81ECD2D8 edi 00000000 ebp F7872DDC
[bce_new_context] +R3 PDE 0xC0602080-0xC0602080
[bce_new_context] +R3 PTE 0xC0410910-0xC0410A10
[bce_run] Calling bce_thread_init 0xF702C7E8 (EIP FF809290, ESP 82142080)
38
Future Direction
• Working!!!
• Detection
– Rootkits can detect our software because the rootkit runs more slowly than expected. Try to make our software harder to detect
• Performance
– No caching is implemented, instructions are translated one-by-one. We need to use caching to improve performance
39
Conclusion
• We have a very effective and unique tool that can observe rootkit behavior
• The technology is lightweight
– It’s just a single driver and user-mode program
– It can run on any vanilla Windows machine
– It doesn’t require hardware virtualization (it just needs NX)
40
Conclusion
• The rootkit cannot breakout of its sandbox
– Without the activity first being recorded
• The technology is can generic
– It can be used to run any kind of driver from ring 3
• We don’t prevent the rootkit from doing its behavior, we just make sure we can observe it
– That is, we don’t prevent it from doing something bad!
– Therefore, this should only be used from a virtual machine when using it with unknown, possibly malicious drivers