Rootkit detection via Kernel Code Tunneling...– Rootkits can easily interfere with the tracing...
Transcript of Rootkit detection via Kernel Code Tunneling...– Rootkits can easily interfere with the tracing...
Rootkit detection via Kernel Code Tunneling
Mihai Chiriac BitDefender
Talk outline
• Introduction • DBI – user mode • DBI – kernel mode • Analysis – rootkit detection • Analysis - cleaning! • Conclusions
“Rootkits are sophisticated tools that allow pieces of malware to stay hidden, once they
are installed on a system, by subverting standard operating system functionality”
File System
Storage Stack
Detection methods
• “Bounds” check -IDT, MSRs, SDT entries, etc normally point inside ntoskrnl.exe - IRP_MJ_* functions point inside the driver
Detection methods
• “Branch” check
Problems
• “Trampoline attack” – small piece of code (few instructions) – stored in module’s unused areas – difficult to analyze statically
Detection methods
• Cross-view
Problems
• Needs an untainted “low level” view – difficult to obtain (miniport!)
• Signature attack – turn off filtering during RK scan
• Other attacks – piggyback an existing process – use a custom file system
A solution…
• Single-step through the code flow • Done by J. K. Rutkowski
– “Execution path analysis”, 2003 – Uses TF, hooks int1, counts instructions
• However…. – Instruction count varies widely – Rootkits can easily interfere with the tracing
process
Dynamic Binary Instrumentation
Code Generation
• Translates entire Basic Blocks • Most instructions are copied 1:1 • Offensive instructions are logged
– We can remove or replace them… • “Garbage” instructions are logged
– Needed during analysis… • How about branches?
Code Generation • .4017F7 43 inc ebx • .4017F8 83 7D CC 00 cmp byte ptr [ebp-34], 00
• .4017FC 89 5D B8 mov dword ptr [ebp-48], ebx
• .4017FF 0F 8C E0 07 00 00 jl 401FE5
• .401805
•
• .3370000 43 inc ebx
• .3370001 83 7D CC 00 cmp byte ptr [ebp-34], 00
• .3370005 89 5D B8 mov dword ptr [ebp-48], ebx
• .3370008 0F 8C ?? ?? ?? ?? jl __branch_taken
• __fall_through:
• JUMP_TO_VM (401805)
• __branch_taken:
• JUMP_TO_VM (401FE5) •
Code Generation
• JUMP_TO_VM (new EIP) – Has to save environment – Cannot push data unto the stack (stack
pollution) – Has to find the successor (or translate it!) – Has to restore the environment – Has to jump to the new EIP
Code Generation • .3370000 43 inc ebx • .3370001 83 7D CC 00 cmp byte ptr [ebp-34], 00
• .3370005 89 5D B8 mov dword ptr [ebp-48], ebx
• .3370008 0F 8C ?? ?? ?? ?? jl __branch_taken
• __fall_through:
• xchg esp, dword ptr [__shadow_stack]
• pushf
• pushad
• COMPUTE_JUMP (401805)
• popad
• popfd
• xchg esp, dword ptr [__shadow_stack]
• jmp dword ptr [__shadow_eip]
• __branch_taken:
• […] •
Code Generation
• COMPUTE_JUMP (new EIP) – Expensive operation – Saves and restores gen-purpose registers
and flags for every control transfer… – Finds the successor, or even translates it – Cache the results = direct link!
Code Generation
• .3370000 43 inc ebx
• .3370001 83 7D CC 00 cmp byte ptr [ebp-34], 00
• .3370005 89 5D B8 mov dword ptr [ebp-48], ebx
• .3370008 0F 8C ?? ?? ?? ?? jl __branch_taken
• __fall_through:
• jmp dword ptr [_BB.cache_fall_through]
• __branch_taken:
• jmp dword ptr [_BB.cache_branch_taken]
Code Generation
• Indirect control transfer – Cannot compute “target” at translation time – Cannot link directly – Have to generate special linking code… – Very expensive L
.405B17 FF 24 95 20 5B 40 00 jmp dword ptr [405B20+edx*4]
Code Generation • SPILL_EAX • SPILL_ECX • mov eax, dword ptr [405B20+edx*4] • lea ecx, dword ptr [eax - _real_address_1] • jecxz _1 • […] • // no match, so COMPUTE_JUMP (eax) • _1: • RESTORE_EAX • RESTORE_ECX • jmp _translated_address_1 • […]
Asynchronous Tasks
• Exceptions - 1 – Common anti-debug technique – Sometimes used as a covert control
transfer method – evades analysis – Need to be properly handled
Asynchronous Tasks
• Exceptions - 2 – Monitor KiUserExceptionDispatcher,
enforce monitoring – Inspect CONTEXT.Eip,
EXCEPTION_RECORD.ExceptionAddress
Asynchronous Tasks
• Exceptions – 3
• if (ExceptionAddress in Code Cache) { ExceptionAddress := FindRealAddress() return CONTINUE;
}
• if (Eip in Code Cache) { Eip := FindRealAddress()
return CONTINUE; }
Asynchronous Tasks
• APCs – Hook KiUserAPCDispatcher
• GUI related callbacks – Hook KeUserModeCallback
• Inspect and “fix” parameters – The app will never know
Self Modifying Code
• Enforce W^X at translation time… • If executing from a RW page, make it
RO, but remember its original flags • Extend KiUserExceptionDispatcher
monitoring • Hook NtProtectVirtualMemory and
NtQueryVirtualMemory
Self Modifying Code
• if (ExceptionCode = Write to RO Memory) {
• // SMC? • DeleteBlocksFromPage(); • RemoveProtection(); • Return HANDLED; • }
DEMO User-mode instrumentation
Kernel Mode DBI
• Has to run at any IRQL – Design a custom memory manager – Remove all concurrent access control – New engine instance, if we want to
instrument multiple threads – Code generation engine should NOT crash J
Kernel Mode DBI
• Detecting abnormal execution flow • Exceptions? DRx ?
– Hook the IDT ? – We’re exposing ourselves L – Compatibility problems with 64-bit
machines
Kernel Mode DBI
• Detecting Self Modifying Code – Normal method is very complex – A rootkit may directly modify page
attributes… – Or modify the WP bit in CR0 ! – Hey, aren’t these offensive instructions? J
Kernel Mode DBI
• Detecting Self Modifying Code – Remove direct block linking – Make sure everything goes through a
“basic block integrity checker” – Simple CRC – Obviously slower L
Kernel Mode DBI
• Detecting Self Modifying Code – Problems with basic blocks that modify
themselves – Have to modify beyond the pre-fetch queue – Hard to detect this behavior at translation
time
Analysis session
• Let’s read the MBR J
• void ReadMBR0() • { • li.LowPart = 0;
• li.HighPart = 0;
• dwStatus = ZwReadFile (hDisk, NULL, NULL, NULL, &ioBlock, pBuf, 512, &li, NULL);
• }
Analysis session
• Let’s read the MBR J
• DWORD __declspec(noinline) _cdecl_0 (void *f) • { • Stopper (_ReturnAddress());
• AddBlock (Translate (f));
• ret = ((_fn_cdecl_0) (pBlock->pCode)) ();
• return ret;
• }
Analysis session
• A normal disk operation… – File system filter drivers – File system driver – Volume, Partition managers – Class driver – Port, Miniport, Hardware
A clean system
• A normal disk operation… - entire log in the whitepaper appendix!
• 82A4CEE8 | ntkrnlpa.exe ZwReadFile • 82C2F105 | ntkrnlpa.exe ObReferenceObjectByHandle • 82ABE05A | ntkrnlpa.exe IoGetRelatedDeviceObject • 82A9CD9A | ntkrnlpa.exe IoGetAttachedDevice • 82AC1ABB | ntkrnlpa.exe IoAllocateIrp • 82A47458 | ntkrnlpa.exe IofCallDriver • 8AA06306 | fltmgr.sys (8AA00000)+00006306
• 8ABE06DA | fileinfo.sys (8ABD9000)+000076DA
A clean system 8AD2B222 | partmgr.sys (8AD2A000)+00001222 8AFAB39F | CLASSPNP.SYS (8AFA7000)+0000439F
8B1E75C2 | disk.sys (8B1E6000)+000015C2
8AFAB4A1 | CLASSPNP.SYS (8AFA7000)+000044A1
8ACA54AA | ACPI.sys (8AC9C000)+000094AA
8ABBC44E | ataport.SYS (8ABB6000)+0000644E
8ADAA006 | intelide.sys (8ADA9000)+00001006
8ABC4B0A | ataport.SYS (8ABB6000)+0000EB0A
8ADB115C | PCIIDEX.SYS (8ADB0000)+0000115C 82E1F874 | halmacpi.dll (82E1B000)+00004874
8ADB1056 | PCIIDEX.SYS (8ADB0000)+00001056
A clean system 8ABC008C | ataport.SYS (8ABB6000)+0000A08C 8ADED438 | atapi.sys (8ADEC000)+00001438
8ACA53B8 | ACPI.sys (8AC9C000)+000093B8
8AFAB5A4 | CLASSPNP.SYS (8AFA7000)+000045A4
8AD2B230 | partmgr.sys (8AD2A000)+00001230
8AA0620C | fltmgr.sys (8AA00000)+0000620C
82A4E487 | ntkrnlpa.exe (82A0B000)+00043487
82A4CEF9 | ntkrnlpa.exe (82A0B000)+00041EF9
B1C36492 | KLUP.sys (B1C35000)+00001492
A clean system
• We have the entire execution path • We’ve logged all instructions…
– No suspicious control transfers – No suspicious basic blocks – No “garbage” or “offensive” instructions – All basic blocks belong to legally loaded
modules
Infected system • 86BDE574 | CLASSPNP.SYS (86BDA000)+00004574 • 831B3EB6 | lsi_scsi.sys (8319F000)+00014EB6(T) • 855A2F61 | ??? (00000000)+855A2F61
• 855A2FD9 | ??? (00000000)+855A2FD9
• 855A2FED | ??? (00000000)+855A2FED
• 855A30D8 | ??? (00000000)+855A30D8
• 855A310A | ??? (00000000)+855A310A
• 855A3150 | ??? (00000000)+855A3150
• […] • 831C14B1 | storport.sys (831B9000)+000084B1
Infected system
• Infection “hints” – Executing code outside of the code section
(s) – Mismatch between the in-memory and on-
disk images of lsi_scsi.sys – Execution of orphaned code – code that
does not belong to a legally loaded module
Detection
• Select APIs to instrument – Try to cover as much code as possible – ~10 functions are enough! – Registry – Storage – Processes / Threads – Network
Detection
• Registry – ZwEnumerateKey – ZwEnumerateValueKey – ZwCreateKey – …?
Detection
• Storage system – ZwCreateFile – ZwReadFile – ZwWriteFile – ZwQueryDirectoryFile – … ?
Detection
• Processes / threads – ZwQuerySystemInformation – ZwQueryInformationProcess – ZwQueryInformationThread – …?
Detection
• Network operations – Not tested yet – Probably use Windows Sockets Kernel? – Instrument ZwDeviceIoControlFile on
\Device\Afd ? – On our TODO list
System disinfection
• Booting from a clean disk is the safe way to go
• But in some cases, live system disinfection may be valuable – High availability environments?
• Rebooting is a nuisance for most users
System disinfection
• A typical hook…
int Hook (QWORD Sector, BYTE *pData)
{ if (Sector == Rootkit_Sector)
{
memset (pData, 0, 512);
return 1;
} return OriginalFunction (Sector, pData);
}
System disinfection
• A typical hook (disarmed)
int Hook (QWORD Sector, BYTE *pData)
{ if (Sector == Rootkit_Sector)
{
NOP }
return OriginalFunction (Sector, pData);
}
System disinfection
• We could just patch the code, BUT… – TDL3 checks its own code for patches,
using a second thread – Not very elegant – it’s a last resort – Prone to race conditions – Need to find atomic operations to “disarm”
the hook – After all, we’re patching live code J
System disinfection
• Pairs of signatures – “original” malware code – “disarmed” malware code
• And during code generation… – When encountering “original” code… – …translate the “disarmed” code! – We’ll then execute the “disarmed” path!
System disinfection
• Virus body is never modified – But our translated copy is disarmed
• We can use the full range of detection and remediation technologies
DEMO Kernel-mode instrumentation
Conclusions
• Strong detection technique – Need to cover as many code paths as
possible • Interesting disinfection capabilities
– Rootkit binaries change many times/week – Hook code changes much less often – Signatures may live much longer
Conclusions
• Interesting way of detecting ROP-based rootkits?
• Rootkits have to modify control flow – Detect DKOM-based malware using other
techniques – Same for virtualization-based rootkits L
Conclusions
• “Shadow Walker” – BH 2005, by Sherri Sparks & Jamie Butler – ITLB / DTLB de-synchronization
• We’ll execute the clean code path! – as read by the Code Generator (via the
DTLB) – No infection “hints”, but we can use any
“classic” detection technology
Future work
• 64 bit code instrumentation – An absolute must, considering 64-bit TDL4,
and it’s just the beginning! • Network code analysis
– Still in development – Probably with WSK