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