First all, I am not an expert in Windows Kernel. If you find errors in the scripts or I am doing very crazy things that could destroy the debuggee machine, please tell me to fix them.
These scripts work with WinDbg (not local) Kernel Debugging. You need a machine running WinDbg, connected to another machine being debugged. In my case I work with a Windows host machine running WinDbg, and I debug a VMware machine (I use VirtualKD for the debugger connection because connection is much faster). But there are a lot of configurations.
I recommend these articles about setting the environment:
- VirtualKD – Installation
- Starting with Windows Kernel Exploitation – part 1 – setting up the lab
- Setting Up Kernel-Mode Debugging of a Virtual Machine Manually
Now, let’s explain each script.
Anti-rdtsc-trick script
- Parameters: $$>a<anti_antidebug_rdtsc.wdbg
Script:
There is a lot of information on internet about this trick. Here you can read pafish code using this trick:
There are tools that install a driver for skipping this trick. The WinDbg script works in a similar way, but it doesn’t need a driver and it works in x86 and x64 (not sure if there are tools for this working on 64 bits).
How it works: It enables flag 2 of cr4 (TSD Time Stamp Disable). In this way RDTSC is a privileged instruction. After that, it enables the option in Windbg for stopping when user mode exception occurs (gflag +sue +soe, gflags 0x20000001).
Then it enables capturing for 0xc0000096 exception (privileged instruction executed). In this way, when RDTSC is executed by an application, a exception will occur and windbg will catch the exception. In this moment, the script checks the ins code of RDTSC, 0x310f. If it is a RDTSC instruction, it skips the instruction, ip = ip+2. Finally it sets edx = 0, and eax = last_counter+1. Applications executing RDTSC will see an increment of 1 each RDTSC execution.
$$set rdtsc as priv instruction, then catch $$exceptions for priv instructions and skip $$rdtsc(eip=eip+2) and set edx:eax = last rdtsc $$returned value +1 $$use $t9 for counter r $t9 = 0 $$rdtsc = privileged instruction r cr4 = cr4 | 4 $$Stop on exception !gflag +soe $$Stop on unhandled user-mode exception !gflag +sue $$disable access violation (we have enabled exception $$in user mode, and access violation will cause lot of $$exceptions) sxd av $$we enable to catch privileged instructions execution $$(we have converted rdtsc in priv ins with cr4) $$in this moment we check if it is rdtsc, and in this case, $$we jump over the instruction and we set eax=0 edx=0 sxe -c ".if((poi(eip)&0x0000ffff)==0x310f){.printf \"rdtsc\r\n\";r eip = eip+2;r eax=@$t9;r edx=0;r $t9=@$t9+1; gh;}" c0000096
Script for renaming running process
- Parameters: $$>a<change_process_name.wdbg <main module of the process>
Script:
If we want a process will have a different name for toolhelp api for example, we need to modify EPROCESS->SeAuditProcessCreationInfo.ImageFileName:
The script receives as parameter the name of the main image of the process. It searchs processes with that imagename and it changes the name increasing +1 to the last letter. For example:
- $$>a<change_process_name.wdbg vmtoolsd.exe
In this case the scripts will rename the vmtoolsd.exe -> vmtoolse.exe. When a malware searchs for this process, it won’t find it. The renamed process continues working with no problem.
aS stage @$t19 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { r stage = 2 .printf "xxxx" .foreach (processes_tok { !process /m ${$arg1} 0 0 }) { .if($scmp("${processes_tok}","PROCESS")==0) { .if(${stage}==2) { $$stage==2 is used to skip the first apparition of $$PROCESS string in the results of !process 0 0 r stage = 0 } .else { r stage = 1 } } .elsif(${stage}==1) { .printf /D "<b>Renaming process ${processes_tok}</b>\n" r stage = 0 r $t4 = ${processes_tok} r $t0 = @@c++( ( ( nt!_EPROCESS * ) @$t4 )->SeAuditProcessCreationInfo.ImageFileName ) r $t1 = (poi @$t0)&0xffff r $t2 = (poi (@$t0+2))&0xffff r $t3 = (poi (@$t0+@@c++(#FIELD_OFFSET(nt!_UNICODE_STRING, Buffer)))) db ($t3 + $t1 - a) $$go to end of buffer of _UNICODE_STRING, and go back 0xa bytes. $$For example <filepath....><lastbyte>.exe. We locate on $$lastbyte, and we increase 1 the value of last byte $$For example <fullpath>\vmtoolsd.exe, will be modified to $$<fullpath>\vmtoolse.exe eb ($t3 + $t1 - a) ((poi($t3 + $t1 - a)&0xff)+1) !process @$t4 0 } } }
Script for renaming kernel objects
- Parameters: $$>a<change_object_name.wdbg <full object path + name>
Script:
This script renames a object in kernel.
First, it gets the address of the _OBJECT_HEADER struct associated to the object (it gets the address from the results of the !object command).
After getting _OBJECT_HEADER, it can get _OBJECT_HEADER_NAME_INFO structure at the address of _OBJECT_HEADER – 0x10 (in x86) or -0x20 (in x64):
We must modify the _UNICODE_STRING into the _OBJECT_HEADER_NAME_INFO for changing the object name.
A practical example, from pafish:
It tries to open a couple of devices. Really vmci is a device, and hgfs is a symboliclink to a device. Anyway, both are kernel objects, they have a _OBJECT_HEADER and a _OBJECT_HEADER_NAME_INFO.
We call the script:
- $$>a<change_object_name.wdbg \global??\hgfs -> new name \global??\agfs
- $$>a<change_object_name.wdbg \devices\vmci -> new name \devices\amci
When pafish tries to CreateFileA these devices, it fails and VM detection with this trick fails.
The script:
aS stage @$t19 aS x64arch $t18 aS objhnameinfodisp $t17 .block { .sympath "SRV*c:\symcache*http://msdl.microsoft.com/download/symbols"; .reload } .block { $$is x64? r x64arch = 0; r objhnameinfodisp = 0x10; .foreach( tok { .effmach } ) { .if($scmp("${tok}","x64")==0) { r x64arch = 1; r objhnameinfodisp = 0x20; .break; }; }; } r stage = 0 .foreach( tok { !object "${$arg1}" } ) { .printf "${tok}\r\n" .if(${stage}==1) { .echo ${tok} dt _OBJECT_HEADER ${tok} r $t0 = ${tok} dt _OBJECT_HEADER_NAME_INFO (@$t0-${objhnameinfodisp}) $$ $t0 -> OBJECT_HEADER_NAME_INFO r $t0 = @$t0 - ${objhnameinfodisp} $$ $t0 -> OBJECT_HEADER_NAME_INFO.UNICODE_STRING r $t0 = @$t0 + @@c++(#FIELD_OFFSET(_OBJECT_HEADER_NAME_INFO, Name)) $$ $t0 -> OBJECT_HEADER_NAME_INFO.UNICODE_STRING.Buffer r $t0 = @$t0 + @@c++(#FIELD_OFFSET(_UNICODE_STRING, Buffer)) db poi $t0 $$change the first letter for 'a' eb (poi $t0) 'a' .printf "--------------------\r\n" db poi $t0 .break } .if(${stage}==0) { .if($scmp("${tok}","ObjectHeader:")==0) { r stage = 1 } } }
To Be Continued...
For cases like registry keys (HKLM\SOFTWARE\VMware, Inc.\VMware Tools,…) or files (vmmouse.sys,…), the fast way to avoid the detection is to remove/rename the key or file being detected. VMware MAC address is used by pafish too, but vmware lets you to modify the MAC of the adapter. Etc… I mean some tricks can be skipped easier.
I would like to write other scripts for similar things like these. Devices or processes in memory, etc… things that maybe they are more difficult to hide.
If I code more scripts I will update this post to add them with the explanation.
I hope this information it is useful for you.
No comments:
Post a Comment