Sunday, July 16, 2017

Anti-Antidebugging WinDbg Scripts

In this post I would like to share some scripts for WinDbg that they were useful for me while I was reversing malware with antidebug tricks. In the future I would like to write additional scripts related to this issue, and I will update this post to have all together. I hope these scripts will be useful for you too.
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:


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.



The script:


$$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.




The script:


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