rootkit detection via kernel code tunneling...– rootkits can easily interfere with the tracing...
TRANSCRIPT
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