Malware Analysis – Bandios – Part 2

Posted by

In the previous article, I analyzed the Bandios dropper. In this article, I’m going to analyze two of the dropped files, usp20.dll and iaStorE.sys. I recommend reading the previous article before this. Some of the information below is presented without any evidence as they were already covered in the previous article.

Note: Memory addresses mentioned in this article are just for reference. They may be different on your machine.

Code Analysis – usp20.dll

Loads KeyHook64.dll

usp20.dll is a small file. It creates a thread which loads KeyHook64.dll and then exits.

18000104A lea     r8, StartAddress               ; lpStartAddress
180001051 xor     r9d, r9d                       ; lpParameter
180001054 mov     [rsp+38h+threadId], rax        ; lpThreadId
180001059 xor     edx, edx                       ; dwStackSize
18000105B xor     ecx, ecx                       ; lpThreadAttributes
18000105D mov     [rsp+38h+dwCreationFlags], eax ; dwCreationFlags
180001061 call    cs:CreateThread
18000101F lea     rcx, LibFileName ; "KeyHook64.dll"
180001026 call    cs:LoadLibraryA

Analysis – iaStorE.sys

iaStorE.sys is a Windows driver file and runs in kernel-mode. Debugging such files is not possible with user-mode debuggers such as x64dbg, OllyDbg, etc. I’m going to use WinDbg to debug iaStorE.sys. There are prerequisite steps that need to be completed in order to setup a kernel debugging environment. I found this blog post to be an excellent resource.

Note: iaStorE.sys is a 64-bit driver. In x64 calling convention, the first argument is contained in rcx register, second argument in rdx register, third argument in r8 register, fourth argument in r9 register and following arguments on the stack in order.

WinDbg Debugging

Finding DriverEntry Address

The DriverEntry function in a driver file is equivalent to DLLMain in a DLL file. It is the first routine that is called when a driver is loaded. WinDbg‘s disassembler is not as sophisticated as IDA. It is not as straightforward to identify the DriverEntry function as it is in IDA. To make debugging easier, Microsoft provides a debug symbols server (you should already have a local copy from the kernel debugging environment setup). I’ll force reload it into WinDbg as follows:

kd> .reload /f 

The next step is to find the load address of iaStorE.sys driver in WinDbg. The base address of iaStorE.sys file when opened in IDA is 0x10000 and the DriverEntry function is located at 0x11064. The DriverEntry function is located at an offset of 0x1064 from the base address. This offset will remain the same even in WinDbg.

iaStorE.sys Base Address in IDA

To find the load address and step through, I had to break the moment iaStorE.sys was loaded. The following command ensures a break on module load:

kd> sxe ld dump_iaStorE.sys

Note: WinDbg displayed iaStorE.sys driver as dump_iaStorE.sys. I realized this when I was going over the list of loaded modules.

The g command allows Windows OS to resume execution. It will break when iaStorE.sys is loaded and when it does, we can use the lm command to list loaded modules along with their address range.

kd> g

0: kd> lm
start            end                module name
fffff80000b9b000 fffff80000ba5000   kdcom      (pdb symbols)          C:\ProgramData\dbg\sym\kdcom.pdb\9E6B09064E1049C8976FD3ABC6B079BB2\kdcom.pdb
fffff80002a10000 fffff80002a59000   hal        (pdb symbols)          C:\ProgramData\dbg\sym\hal.pdb\A085D08B9C5D4BFDBA48AC285BDA03F22\hal.pdb
fffff80002a59000 fffff80003040000   nt         (pdb symbols)          C:\ProgramData\dbg\sym\ntkrnlmp.pdb\2E37F962D699492CAAF3F9F4E9770B1D2\ntkrnlmp.pdb
fffff880016c8000 fffff880016d1000   dump_iaStorE   (no symbols)

We can see above that dump_iaStorE.sys is loaded at address 0xfffff880016c8000. From our previous offset calculation, the DriverEntry function should be located at 0xfffff880016c9064. We can disassemble instructions at an address using the u command. Comparing the instructions with those seen in IDA, we can say that the DriverEntry function is indeed at 0xfffff880016c9064.

0: kd> u 0xfffff880016c9064
fffff880016c9064 48895c2408      mov     qword ptr [rsp+8],rbx
fffff880016c9069 4889742410      mov     qword ptr [rsp+10h],rsi
fffff880016c906e 57              push    rdi
fffff880016c906f 4883ec60        sub     rsp,60h
fffff880016c9073 33f6            xor     esi,esi
fffff880016c9075 488bd9          mov     rbx,rcx
fffff880016c9078 483bce          cmp     rcx,rsi
fffff880016c907b 0f8482000000    je      dump_iaStorE+0x1103 (fffff880`016c9103)

The DriverEntry function as seen in IDA:

DriverEntry function in IDA

I then placed breakpoints on all function calls in the DriverEntry function and resumed execution.

0: kd> bl
      0 e Disable Clear  fffff880016c90d6     0001 (0001) dump_iaStorE+0x10d6      
      1 e Disable Clear  fffff880016c90b8     0001 (0001) dump_iaStorE+0x10b8
      2 e Disable Clear  fffff880016c91cf     0001 (0001) dump_iaStorE+0x11cf      
      3 e Disable Clear  fffff880016c91e1     0001 (0001) dump_iaStorE+0x11e1
      4 e Disable Clear  fffff880016c91f1     0001 (0001) dump_iaStorE+0x11f1      
      5 e Disable Clear  fffff880016c9202     0001 (0001) dump_iaStorE+0x1202
      6 e Disable Clear  fffff880016c9214     0001 (0001) dump_iaStorE+0x1214      
      7 e Disable Clear  fffff880016c9224     0001 (0001) dump_iaStorE+0x1224
      8 e Disable Clear  fffff880016c9235     0001 (0001) dump_iaStorE+0x1235      
      9 e Disable Clear  fffff880016c9247     0001 (0001) dump_iaStorE+0x1247
     10 e Disable Clear  fffff880016c9257     0001 (0001) dump_iaStorE+0x1257     
     11 e Disable Clear  fffff880016c925c     0001 (0001) dump_iaStorE+0x125c
     12 e Disable Clear  fffff880016c9281     0001 (0001) dump_iaStorE+0x1281

Rewrite Malicious Files

On resuming execution, breakpoint 2 is hit. Since symbols are loaded into WinDbg, the disassembly window shows that the called function is RtlInitUnicodeString.

0: kd> g
Breakpoint 2 hit
fffff880016c91cf ff15731e0000    call    qword ptr [dump_iaStorE+0x3048 (fffff880016cb048)] 

From MSDN docs, we know that the second argument to RtlInitUnicodeString contains the source string. The rdx register contains the second argument and in this case, points to a string, \SystemRoot\system32\KH.dat. From the previous article, we know that this file contains encoded data that can be xor-decoded into KeyHook64.dll.

0: kd> r rdx

The below extract from Microsoft’s Support website sheds more information on the \SystemRoot prefix seen above.

Kernel-mode device drivers refer to a file by its object name. This name is \DosDevices together with the full path of the file. For example, the object name of the C:\Windows\Example.txt file is \DosDevices\C:\Windows\Example.txt. Then the object name is encapsulated into an OBJECT_ATTRIBUTES structure by calling the InitializeObjectAttributes function.

Note If the device driver is loaded early, the \DosDevices namespace may not yet exist. Therefore, the \DosDevices namespace is inaccessible to the device driver because no drive letter is exposed. The only part of the file system that is guaranteed to be available is the \SystemRoot namespace. The \SystemRoot namespace is mapped to the folder where the operation system is installed. For example, this folder may be C:\Windows or D:\Winnt.

Another call is made to RtlInitUnicodeString with the rdx register pointing to a string, \SystemRoot\system32\KeyHook64.dll.

0: kd> g
Breakpoint 3 hit
fffff880016c91e1 ff15611e0000    call    qword ptr [dump_iaStorE+0x3048 (fffff880016cb048)]

0: kd> r rdx

The control flow then entered a user-defined function. I placed breakpoints on all functions that are called within the user-defined function:

0: kd> bp fffff880016c940a
0: kd> bp fffff880016c9470
0: kd> bp fffff880016c94bf
0: kd> bp fffff880016c954f 
0: kd> bp fffff880016c9580
0: kd> bp fffff880016c95a3
0: kd> bp fffff880016c95c3
0: kd> bp fffff880016c9605
0: kd> bp fffff880016c9661

With a call to ZwDeleteFile, the driver deletes the existing C:\Windows\system32\KeyHook64.dll. The prototype of ZwDeleteFile is as follows:


The first argument (rcx register) points to an OBJECT_ATTRIBUTES structure.

0: kd> r rcx

The prototype of the OBJECT_ATTRIBUTES structure is as follows:

typedef struct _OBJECT_ATTRIBUTES {
  ULONG           Length;
  HANDLE          RootDirectory;
  ULONG           Attributes;
  PVOID           SecurityDescriptor;
  PVOID           SecurityQualityOfService;

The third parameter points to an Unicode String structure that contains the name of the object for which a handle is to be opened.

0: kd> dd (rcx+0x10) L2
fffff880`009b03a8  009b0460 fffff880 

The prototype of the Unicode Structure is as follows:

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;

The third parameter points to a buffer containing a string of wide characters.

0: kd> dd (fffff880009b0460 + 0x8) L2
fffff880`009b0468  016cc110 fffff880

We can see that the deleted file is \SystemRoot\system32\KeyHook64.dll. It then creates the same file in the same location using ZwCreateFile. A following call to ZwCreateFile opens \SystemRoot\system32\KH.dat for reading.

0: kd> r r8

0: kd> dd (r8+0x10) L2
fffff880009b03a8  009b0450 fffff880

0: kd> dd (fffff880009b0450 + 0x8) L2
fffff880009b0458  016cc210 fffff880

A paged memory pool of size 0x8ab20 (same as mentioned in previous article) with tag named, ezlf is allocated using ExAllocatePoolWithTag. (According to the docs, the tag string is usually specified in reverse and thus, ezlf instead of flze.)

0: kd> r rcx

0: kd> r rdx

0: kd> r r8

0: kd> .formats r8
Evaluate expression:
  Hex:     00000000`666c7a65
  Decimal: 1718385253
  Octal:   0000000000014633075145
  Binary:  00000000 00000000 00000000 00000000 01100110 01101100 01111010 01100101
  Chars:   ....flze
  Time:    Fri Jun 14 13:14:13 2024
  Float:   low 2.79184e+023 high 0
  Double:  8.48995e-315 

The contents of KH.dat are read into the above allocated memory using ZwReadFile. The memory snap is shown below:

The above contents are xor-decoded using key 0xDD (same as mentioned in previous article) and then written back into \SystemRoot\system32\KeyHook64.dll using ZwWriteFile.

In the same manner, contents of UP.dat and MS.dat are xor-decoded and written into usp20.dll and spoolsr.exe respectively.

Run spoolsr.exe as Service

Changes are made to a newly created Windows Registry key, \registry\machine\SYSTEM\CurrentControlSet\services\spoolsr causing spoolsr.exe to be run on boot as a service with following characteristics:

  • it runs as a stand-alone process,
  • it is always loaded and run,
  • if it fails to load at boot, a warning is given but boot continues,
  • it runs from the binary, \SystemRoot\system32\spoolsr.exe,
  • it sets WOW64 = 1 to force spoolsr.exe into believing that it is running on a 32-bit system and thus, access system32 directory instead of SysWOW64.
  • it runs under LocalSystem (highest privilege) account.

Process Injection using AppInit_DLLs

The driver sets two subkey-values under the Registry key, \registry\machine\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows:

  • LoadAppInit_DLLs: <some DWORD Value>
  • AppInit_DLLs: usp20.dll

The above changes causes usp20.dll to be loaded into every process that loads User32.dll. And as we know, usp20.dll loads KeyHook64.dll.

To test the above, I started Chrome on an infected system and could see that KeyHook64.dll was loaded.

KeyHook64.dll Injected into Chrome

Persistence of Malicious Driver

The driver sets the DumpFilters subkey of Windows Registry key, \registry\machine\SYSTEM\CurrentControlSet\Control\CrashControl to ensure that the malicious driver is loaded on boot.

Thanks for reading!

In this article (2/3), I described my analysis for the Bandios rootkit driver, iaStorE.sys and one other DLL. In the next and final part, I’ll analyze KeyHook64.dll.

Thank you for reading! If you have any questions, leave them in the comments section below and I’ll get back to you as soon as I can!

Feature image credits:

Leave a Reply

Your email address will not be published.