This blog was authored by Paul Rascagneres and Warren Mercer.

Introduction
.NET is an increasingly important component of the Microsoft ecosystem providing a shared framework for interoperability between different languages and hardware platforms. Many Microsoft tools, such as PowerShell, and other administrative functions rely on the .NET platform for their functionality. Obviously, this makes .NET an enticing language for malware developers too. Hence, malware researchers must also be familiar with the language and have the necessary skills to analyse malicious software that runs on the platform.

Analysis tools such as ILSpy help researchers decompile code from applications, but cannot be used to automate the analysis of many samples. In this article we will examine how to use WinDBG to analyse .NET applications using the SOS extension provided by Microsoft.

This article describes:

  • How to analyse PowerShell scripts by inserting a breakpoint in the .NET API.
  • How to easily create a script to automatically unpack .NET samples following analysis of the packer logic.
    Additionally, you can download a Python script (based on the WinDBG pykd extension) on our github to automate analysis of .NET. This script will be described in the article too.

SOS Extension
The SOS Extension provides .NET support for WinDBG. The extension provides a rich set of commands; in this article we will cover only a few that are useful for analysis..

Firstly, the SOS extension is not located in the same library, depending the version of .NET used. Before we are able to use the SOS extension we must load the library into WinDBG.

For .NET 4, the extension is located in CLR.dll and can be loaded with the following command:

.loadby sos clr

In .NET 2 and 3, the SOS extension is located is the mscorwks library:

.loadby sos mscorwks

Here are the commands used in this article:

  • !bpmd: this command is used to put breakpoints in managed code (.NET). The command takes two arguments. The first argument is the .NET dll where the function is located and the second is the function name.
  • !CLRStack: this command displays the CLR stack content. It is useful to identify the arguments of a .NET function.
  • !DumpObj: this command displays information on a specific object specified in an argument.
    In this article these 3 commands will be used to create a breakpoint within a specific .NET API, to get the arguments passed to the API, and display the contents.

Use Case #1: PowerShell Analysis
Few people realise that PowerShell can use the .NET framework. By examining .NET API usage, we can easily automate PowerShell analysis.

Example 1: Start-Process API
In this example, we will analyse the following PowerShell code:

PS> start-process notepad.exe

When you perform this task, PowerShell uses the Process.Start() API. So, we can breakpoint, this is where we stop the code execution on purpose, on this API (after loading the SOS extension):

0:011> .loadby sos clr

0:011> !bpmd system.dll System.Diagnostics.Process.Start
Found 6 methods in module 00007fff97581000...
breakpoint: bp 00007FFF977C96D9 [System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)]
breakpoint: bp 00007FFF97E8057D [System.Diagnostics.Process.Start(System.String, System.String)]
breakpoint: bp 00007FFF97E80539 [System.Diagnostics.Process.Start(System.String)]
breakpoint: bp 00007FFF97E804B6 [System.Diagnostics.Process.Start(System.String, System.String, breakpoint: bp 00007FFF977C72DA [System.Diagnostics.Process.Start()]
Adding pending breakpoints...

Once the breakpoint is set, we can enter the command 'g' to execute the PowerShell script. WinDBG will stop when the Start-Process is executed:

Breakpoint 0 hit
System_ni+0x2496d9:
00007fff`977c96d9 488d0d08711e00  lea     rcx,[System_ni+0x4307e8 (00007fff`979b07e8)]

The CLRStack command displays the argument provided to the Process.Start API. In our case, the argument is a System.Diagnostics.ProcessStartInfo object.

0:008> !CLRStack -p
OS Thread Id: 0x2d34 (8)
        Child SP               IP Call Site
000000a7f9ace700 00007fff977c96d9 System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)
    PARAMETERS:
        startInfo (<CLR reg>) = 0x0000028cbd5faa18

Finally the DumpObj command shows the contents of this object:

0:008> !DumpObj /d 0000028cbd5faa18
Name:        System.Diagnostics.ProcessStartInfo
MethodTable: 00007fff979ae380
EEClass:     00007fff975e29f0
Size:        144(0x90) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Fields:
              MT    Field   Offset                 Type VT     Attr   Value Name
00007fff9897de98  40027f3    8        System.String  0 instance 28cbd5fde18 fileName
00007fff9897de98  40027f4   10        System.String  0 instance 000 arguments
[...redacted...]
00007fff9897ad70  4002806   58 System.WeakReference  0 instance 000 weakParentProces
00007fff979af0a0  4002807   60 ....StringDictionary  0 instance 000 environmentVaria
00007fff982e5ec0  4002808   68 ...tring, mscorlib]]  0 instance 000 environment

The first field of the ProcessStartInfo object is a System.String object called filename. We can retrieve the contents of the object using DumpObj:

0:008> !DumpObj /d 0000028cbd5fde18
Name:        System.String
MethodTable: 00007fff9897de98
EEClass:     00007fff982d35f0
Size:        88(0x58) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      C:\WINDOWS\system32\notepad.exe

We can see that the filename string is the path to the notepad binary.

Example 2: DownloadFile API
In this second example, we will analyse the following code:

PS> $a = New-Object System.Net.WebClient
PS> $a.DownloadFile("http://blog.talosintelligence.com/","c:\users\lucifer\demo.txt")

The purpose of this code is to download a file and store it on the hard drive. This is a technique frequently used by malware to download a payload.

If this case, we must put on breakpoint on the DownloadFile AP and press 'g' to execute the PowerShelI:

0:008> .loadby sos clr
0:008> !bpmd system.dll System.Net.WebClient.DownloadFile
Found 2 methods in module 00007fff97581000...
MethodDesc = 00007fff976c1fe8
MethodDesc = 00007fff976c1ff8
Setting breakpoint: bp 00007FFF97DCAE0C [System.Net.WebClient.DownloadFile(System.Uri, System.String)]
Setting breakpoint: bp 00007FFF97DCADBC [System.Net.WebClient.DownloadFile(System.String, System.String)]
Adding pending breakpoints…
0:008> g

When the API is executed, WinDBG will automatically stop the execution of the PowerShell script:

Breakpoint 7 hit
System_ni+0x84adbc:
00007fff`97dcadbc 4885d2          test    rdx,rdx

In this case, we could use the CLRStack and DumpObj commands exactly as previously. Instead, we will get the value directly from the register (the first string is located in RDX+0xC and the second one in R8+0xC, as according to the Microsoft standard for memory location) :

0:008> du rdx+c
0000028c`bd53f13c  "http://blog.talosintelligence.co"
0000028c`bd53f17c  "m/"
0:008> du r8+c
0000028c`bd53f3b4  "c:\users\lucifer\desktop\demo.tx"
0000028c`bd53f3f4  "t"

Here is a snippet of the execution:

Use Case #2: .NET Unpack
Talos deals with packed malware samples on a daily basis. We recently identified a packed .NET executable that was being hosted on a Syrian government website: http://www[.]syriantax[.]gov[.]sy/css/igfxCUIService.exe. Initially we wondered if this was part of a targeted attack. After further research, we now believe that the website was compromised and used to deliver this malware. The malware turned out to be njRAT, a well-known public Remote Administration Tool that has been widely distributed for years. While finding njRAT is not particularly interesting, we thought that writing a blog post walking through the process of unpacking njRAT would be beneficial.

As such, this use case will explain how to deal with unknown .NET packers, using static analysis. We will also cover dynamic analysis using WinDBG as well as how to create a WinDBG script to automate the unpacking process for this type of packer.

Static Analysis
We started our analysis of this malware sample by using de4dot as it can quickly identify known packers. It is an open source analysis platform available here

C:> de4dot-x64.exe -d -r c:\to_test

de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com

Latest version and source code: https://github.com/0xd4d/de4dot
Detected Unknown Obfuscator (c:\to_test\21acd3457c1a58[...]1bfeeaf3c0cd79bfe)
Detected Unknown Obfuscator (c:\to_test\344ce133363f09[...]bbd2257a298484051)
Detected Unknown Obfuscator (c:\to_test\45c695e610d786[...]af65408fb6080300f)
Detected Unknown Obfuscator (c:\to_test\61653b2811fb7c[...]04f9807a775f25773)
Detected Unknown Obfuscator (c:\to_test\ac7bd77245bdf2[...]aee4d06563f057ca6)
Detected Unknown Obfuscator (c:\to_test\b607e87acdcb2e[...]d30eddddffbeec320)
Detected Unknown Obfuscator (c:\to_test\e93c0aed6bbb4a[...]6c2efe65942f83504)

In this section, we will also be using ILSpy an open source .NET decompiler available here.

XORed variant
Sample: 45c695e610d78178ec5ca6f4e1993afacf4e435b566cd2caf65408fb6080300f

The entry point of the packer is ob6eaGgG7Bht6B35c0.G9puOotvCiNCkEEPD9.XHh0nc9pu, we can identify this information from unpacking with ILSpy:


First, the packer decodes a Base64 encoded string (variable G9puOotvCiNCkEEPD9.EHQI8XHAH ). This decoded string is passed to the function G9puOotvCiNCkEEPD9.vovYCiNCk() along with a second argument that will function as the XOR key:


Scrolling through the output, we are able to identify the XOR operation with ILSpy by looking through the decompiled .NET executable by seeing the '^' function used we can determine this is XOR operation.


Finally the output of the function is passed as an argument to the function Assembly.Load(). This function is used to load .NET binaries.


The argument passed to Assembly.Load() is a byte array and contains a Windows binary (PE32). In this case, the unpacked malware is in the byte array.

AES variant
Sample: 21acd3457c1a589e117988fe0456e50ed627f051a97ccd11bfeeaf3c0cd79bfe

The logic contained within this variant of the packer is the same however instead of using XOR obfuscation, it uses AES encryption (also known as Rijndael):


Finally the decrypted data is then loaded into memory using the Assembly.Load() function.

Common points
While the algorithm used by each of the analysed samples is different, encoding versus encryption, the logic is exactly the same. If we can dump the byte array variable found in the argument of the Assembly.Load() function, we have the unpacked malware.

Dynamic Analysis with WinDBG

.NET Version 4
In order to perform dynamic analysis of .NET 4 samples, we need to obtain the WinDBG SOS extension available here. This extension allows for .NET 4 debugging using the Microsoft Debugger.

Let's execute the packed malware…

The first step is to stop the debugger execution when the CLRJIT library is loaded:

0:000> sxe ld clrjit
0:000> g
(dc0.1594): Unknown exception - code 04242420 (first chance)
ModLoad: 70fc0000 71040000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
eax=00000000 ebx=00800000 ecx=00000000 edx=00000000 esi=00000000 edi=0044e000
eip=7736e85c esp=006fe4fc ebp=006fe558 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206

ntdll!NtMapViewOfSection+0xc:
7736e85c c22800          ret     28h

We then load the WinDBG SOS extension to perform analysis on the .NET application (managed application):

0:000> .load "C:\\Psscor4\\x86\\x86\\psscor4.dll"

We now have new WinDBG commands related to .NET debugging available. We can set a breakpoint based on .NET API usage. In this case, we are interested in the Assembly.Load() API:

0:000> !bpmd mscorlib.dll System.Reflection.Assembly.Load
Found 8 methods in module 71041000...
MethodDesc = 71100b50
MethodDesc = 71100b7c
MethodDesc = 71100b88
MethodDesc = 71100b94
MethodDesc = 71100bb8
MethodDesc = 71100bd0
MethodDesc = 71100bdc
MethodDesc = 71100be8
Setting breakpoint: bp 71B29095 [System.Reflection.Assembly.Load(Byte[], Byte[], System.Security.Policy.Evidence)]
Setting breakpoint: bp 71B29037 [System.Reflection.Assembly.Load(Byte[], Byte[], System.Security.SecurityContextSource)]
Setting breakpoint: bp 71B28FFF [System.Reflection.Assembly.Load(Byte[], Byte[])]
Setting breakpoint: bp 71B28F9C [System.Reflection.Assembly.Load(Byte[])]
Setting breakpoint: bp 71395949 [System.Reflection.Assembly.Load(System.Reflection.AssemblyName, System.Security.Policy.Evidence)]
Setting breakpoint: bp 713F3479 [System.Reflection.Assembly.Load(System.Reflection.AssemblyName)]
Setting breakpoint: bp 71B28F3D [System.Reflection.Assembly.Load(System.String, System.Security.Policy.Evidence)]
Setting breakpoint: bp 713C880D [System.Reflection.Assembly.Load(System.String)]
Adding pending breakpoints...

(There is currently a bug in the extension, requiring the command to be executed twice)

The debugger will now stop the execution of the malware when the Assembly.Load() function is executed:

0:000> g

Breakpoint 3 hit
eax=00000000 ebx=006ff2dc ecx=026b30b8 edx=0000000a esi=026b30b8 edi=006ff250
eip=71b28f9c esp=006ff210 ebp=006ff218 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

mscorlib_ni+0xae8f9c:
71b28f9c e80368fdff      call    mscorlib_ni+0xabf7a4 (71aff7a4)

Obviously, we can use the CLRStack and DumpObj commands to get the arguments exactly as mentioned in the previous use case. In this example, we will only use the register content. The argument passed to Assembly.Load() is available on the Stack (ESP):

0:000> dp esp

006ff210  00000000 026b30b8 006ff238 009504ae
006ff220  00000000 00000000 00000000 00000000
006ff230  00000000 00000000 006ff244 7240ea56
006ff240  00a149a8 006ff298 724293ef 006ff2dc
006ff250  006ff288 725b24b0 006ff3b0 724293a8
006ff260  ecebc740 006ff404 006ff370 006ff324
006ff270  7246e611 006ff2dc 00000000 ecebc740
006ff280  006ff250 006ff370 006ff424 725b0890

The second value in the stack is a pointer to the byte array: 0x026b30b8.

0:000> dp 026b30b8 
026b30b8  71504448 00005e00 00905a4d 00000003
026b30c8  00000004 0000ffff 000000b8 00000000

0:000> db 026b30b8+8 L16
026b30c0  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
026b30d0  b8 00 00 00 00 00                                ......

The second argument 0x5e00 is the size of the byte array (in red), after which we can see the file header of the PE file starting with MZ: 0x4d 0x5a (in blue, but reversed due to the data being stored in little endian format). We can now dump the unpacked sample directly from within WinDBG:

.writemem C:\\unpacked_sample.exe 026b30b8+8 L00005e00

.NET Version 2 & 3
The dynamic analysis process for malware compiled with .NET Version 2 and 3 is the same. The difference is how the argument is passed to the Assembly.Load() API. In this case, the argument does not use the stack, it is stored in the ECX register instead:

0:000> dp ecx
024ba0b8  71504448 00005e00 00905a4d 00000003
024ba0c8  00000004 0000ffff 000000b8 00000000

0:000> db ecx+8 L16
024ba0c0  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
024ba0d0  b8 00 00 00 00 00     

The format is exactly the same as in the previous example the size of the array is in red and the binary to be loaded is in blue.

Automated Unpacking
Thanks to the analysis described previously, we can create a generic unpacker. You can find this WinDBG script in Appendix 2 for .NET versions 2, 3 and 4.

This script can be invoked using the following syntax:

"c:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" -c "$$>a< C:\unpack.script C:\unpacked_sample.exe" "c:\sample.exe"

Here is a screenshot of the script execution:


Python script
You can download a python script to automate .NET analysis on our github repository. The script needs the pykd extension in order to allow python execution in WinDBG. The script uses SOS commands previously mentioned in the article, the purpose is to have a better output. The configuration is at the beginning of the script:

dump_byte_array=1
dump_byte_array_path="c:\\path\\to\\directory\\"

bp_list = [ ["system.dll", "System.Diagnostics.Process.Start"],
            ["system.dll", "System.Net.WebClient.DownloadFile"],
            ["mscorlib.dll", "System.Reflection.Assembly.Load"]
          ]

The bp_list variable contains the list of breakpoint. In the example, the script will breakpoint on 3 .NET API (System.Diagnotics.Process.Start, System.Net.WebClient.Download.File and Sysyem.Reflection.Assembly.Load). The arguments of the 3 functions will be display in WinDBG.

If the dump_byte_array variable is set to 1, the script will automatically dump the byte array in provided in the argument on the analysed functions (where the breakpoints are in place). The dump will be located in the dump_byte_array_path directory.

The script allows text or json output. The output of the examples in this article is in text but we can switch in json by setting the JsonDebug variable to "True".

Example 1:
Here is the output of the script when the Assembly.Load function is called:

0:000> .loadby sos clr
0:000> .load pykd.dll
0:000> !py C:\Users\lucifer\NET_plugin.py
[.NET plugin] Beginning, setting breakpoints...
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0xb4fa65
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0xb4fa07
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0xb4f9cf
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0xb4f96c
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0x38a5a1
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0x3bda7d
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0xb4f90d
[.NET plugin] breakpoint: mscorlib.dll System.Reflection.Assembly.Load mscorlib_ni+0x3968dd
[.NET plugin] Let's go...

[.NET plugin] Breakpoint: System.Reflection.Assembly.Load(Byte[])
[.NET plugin] Argument 0: rawAssembly
[.NET plugin] !DumpObj /d 0x02f67e04
        Name:        System.Byte[]
        MethodTable: 6b5f60f8
        EEClass:     6b190878
        Size:        5644(0x160c) bytes
        Array:       Rank 1, Number of elements 5632, Type Byte (Print Array)
        Content:     MZ......................@...............................................!..L.!This program cannot
        Fields:
        None
        
        [.NET plugin] let's dump 0x02f67e04+8 Size:5644
        .writemem c:\users\lucifer\Desktop\dump_1496942775_0x02f67e04_5644.dmp 0x02f67e04+8 L5644

The content of the byte array in argument of Assembly.Load is automatically stored in c:\users\lucifer\Desktop\dump_1496942775_0x02f67e04_5644.dmp

Example 2:
Here is the output of the script on a PowerShell script that execute start-process:

[.NET plugin] Breakpoint: System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)
[.NET plugin] Argument 0: startInfo
[.NET plugin] !DumpObj /d 0x000001ad173cdb68
        Name:        System.Diagnostics.ProcessStartInfo
        MethodTable: 00007ffd7e3ee798
        EEClass:     00007ffd7e0229f0
        Size:        144(0x90) bytes
        File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
        Fields:
           MT    Field   Offset                 Type VT     Attr      Value Name
        07ffd69e969d0 40027fa   8     System.String  0 instance 01ad173d0f20 fileName
        07ffd69e969d0 40027fb   10    System.String  0 instance 00000000000 arguments
        07ffd69e969d0 40027fc   18    System.String  0 instance 1ad173d4bf8 directory
        07ffd69e969d0 40027fd   20    System.String  0 instance 000000000000 verb
        07ffd7e3c2a50 40027fe   78    System.Int32  1 instance          0 windowStyle
        07ffd69ea1fb0 40027ff   7c    System.Boolean  1 instance        0 errorDialog
        07ffd69eafc48 4002800   70    System.IntPtr  1 instance     0 errorDialogPare
        07ffd69ea1fb0 4002801   7d    System.Boolean  1 instance     1 useShellExecut
        07ffd69e969d0 4002802   28    System.String  0 instance 000000000000 userName
        07ffd69e969d0 4002803   30    System.String  0 instance 000000000000 domain
        07ffd69ea4068 4002804   38 ...rity.SecureString  0 instance 00000000 password
        07ffd69e969d0 4002805   40    System.String  0 instance 0 passwordInClearText
        07ffd69ea1fb0 4002806   7e    System.Boolean  1 instance,  1  loadUserProfile
        07ffd69ea1fb0 4002807   7f    System.Boolean  1 instance   0  redirectStandar
        07ffd69ea1fb0 4002808   80    System.Boolean  1 instance   0 redirectStandard
        07ffd69ea1fb0 4002809   81    System.Boolean  1 instance   0 redirectStandard
        07ffd69e9b048 400280a   48    System.Text.Encoding  0 instance 0 standardOutp
        07ffd69e9b048 400280b   50    System.Text.Encoding  0 instance 0 standardErro
        07ffd69ea1fb0 400280c   82    System.Boolean  1 instance   0 createNoWindow
        07ffd69eadec8 400280d   58 System.WeakReference  0 instance 0000 weakParentPr
        07ffd7e3ef4b8 400280e   60 ....StringDictionary  0 instance 0000 envVariables
        07ffd697a69f0 400280f   68 ...tring, mscorlib]]  0 instance 0000 environment
        
                [.NET plugin] !DumpObj /d 000001ad173d0f20
                Name:        System.String
                MethodTable: 00007ffd69e969d0
                EEClass:     00007ffd697950e0
                Size:        82(0x52) bytes
                File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
                String:      C:\WINDOWS\system32\calc.exe

The script displays the argument and the content of the interesting field (in the example the fileName string).

Example 3:
Here is the output on the script when the DownloadFile API is used in Powershell:

[.NET plugin] Breakpoint: System.Net.WebClient.DownloadFile(System.Uri, System.String)
[.NET plugin] Argument 1: address
[.NET plugin] !DumpObj /d 0x000001ad17315e78
        Name:        System.Uri
        MethodTable: 00007ffd7e3f4cf0
        EEClass:     00007ffd7dfc5fd0
        Size:        72(0x48) bytes
        File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
        Fields:
            MT    Field   Offset             Type VT     Attr            Value Name
        07ffd69e969d0 400040b 8   System.String  0 instance 000001ad172c5ea8 m_String
        07ffd69e969d0 400040c 10  System.String  0 instance 000000000 m_originalUnico
        07ffd7e3f51d8 400040d 18  System.UriParser  0 instance 001ad17032b40 m_Syntax
        07ffd69e969d0 400040e 20  System.String  0 instance 00000000000 m_DnsSafeHost
        07ffd7e3c2788 400040f 30  System.UInt64  1 instance 37615763456 m_Flags
        07ffd7e3f5590 4000410 28  System.Uri+UriInfo  0 instance 01ad17315f00 m_Info
        07ffd69ea1fb0 4000411 38  System.Boolean  1 instance          0 m_iriParsing
        07ffd69e969d0 40003fb 220 System.String  0   shared     static UriSchemeFile
        07ffd69e969d0 40003fc 228 System.String  0   shared     static UriSchemeFtp
        07ffd69e969d0 40003fd 230 System.String  0   shared     static UriSchemeGoph
        07ffd69e969d0 40003fe 238 System.String  0   shared     static UriSchemeHttp
        07ffd69e969d0 40003ff 240 System.String  0   shared     static UriSchemeHttps
        07ffd69e969d0 4000400 248 System.String  0   shared     static UriSchemeWs
        07ffd69e969d0 4000401 250 System.String  0   shared     static UriSchemeWss
        07ffd69e969d0 4000402 258 System.String  0   shared     static UriSchemeMail
        07ffd69e969d0 4000403 260 System.String  0   shared     static UriSchemeNews
        07ffd69e969d0 4000404 268 System.String  0   shared     static UriSchemeNntp
        07ffd69e969d0 4000405 270 System.String  0   shared     static UriSchemeNet
        07ffd69e969d0 4000406 278 System.String  0   shared     static UriSchemeNetP
        07ffd69e969d0 4000407 280 System.String  0   shared     static SchemeDelimit
        07ffd7e3b4bd0 4000412 288 ...etSecurityManager  0       static s_ManagerRef
        07ffd69e96fb0 4000413 290 System.Object  0   shared     static s_IntranetLock
        07ffd69ea1fb0 4000414 9c4 System.Boolean  1   shared    static s_ConfigInitia
        07ffd69ea1fb0 4000415 9c5 System.Boolean  1   shared    static s_ConfigInitia
        07ffd7e3afef8 4000416 9c0 System.Int32  1   shared      static s_IdnScope
        07ffd69ea1fb0 4000417 9c6 System.Boolean  1   shared    static s_IriParsing
        07ffd69e96fb0 4000418 298 System.Object  0   shared     static s_initLock
        07ffd69e97b20 400041c 2a0 System.Char[]  0   shared     static HexLowerChars
        07ffd69e97b20 400041d 2a8 System.Char[]  0   shared     static _WSchars
        
                [.NET plugin] !DumpObj /d 000001ad172c5ea8
                Name:        System.String
                MethodTable: 00007ffd69e969d0
                EEClass:     00007ffd697950e0
                Size:        94(0x5e) bytes
                File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
                String:      http://blog.talosintelligence.com/
                Fields:
                   MT    Field   Offset           Type VT     Attr         Value Name
               07ffd69e99310  400026f  8  System.Int32  1 instance  34 m_stringLength
               07ffd69e97b88  400027   c  System.Char   1 instance  68 m_firstChar
               07ffd69e969d0  4000274  90 System.String 0   shared  static Empty

[.NET plugin] Argument 2: fileName
[.NET plugin] !DumpObj /d 0x000001ad172c61c8
        Name:        System.String
        MethodTable: 00007ffd69e969d0
        EEClass:     00007ffd697950e0
        Size:        92(0x5c) bytes
        File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
        String:      c:\users\lucifer\desktop\demo.txt
        Fields:
             MT    Field   Offset                 Type VT     Attr      Value Name
        07ffd69e99310  400026f  8    System.Int32  1 instance       33 m_stringLength
        07ffd69e97b88  4000270  c    System.Char  1 instance        63 m_firstChar
        07ffd69e969d0  4000274  90   System.String  0   shared      static Empty


The first argument is a System.URI object. The object is automatically parsed and the relevant contents are displayed in WinDBG. In this case, the first field is displayed (the string m_string). This string contains the contacted URL. The second argument is a string which is displayed too.

Example 4:
Here is the output on the script in JSON (start-process execution):

0:020> .loadby sos clr
0:020> .load pykd
0:020> !py c:\Users\lucifer\DotNETPlugin.py
{
  "date": 1500306926,
  "bp": "System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)",
  "arguments": {
    "0": {
      "fields": {
        "0": {
          "Type": "System.String",
          "Name": "fileName",
          "string": "C:\\WINDOWS\\system32\\calc.exe"
        },
        "1": {
          "Type": "System.String",
          "Name": "arguments",
          "string": ""
        },
        "2": {
          "Type": "System.String",
          "Name": "directory",
          "string": "C:\\Users\\lucifer"
        },
        "3": {
          "Type": "System.String",
          "Name": "verb",
          "string": ""
        },
        [...redacted...]
        "20": {
          "Type": "....StringDictionary",
          "Name": "environmentVariables",
          "value": "0000000000000000"
        },
        "21": {
          "Type": "...tring,",
          "Name": "environment",
          "value": "instance"
        }
      },
      "name": "startInfo",
      "offset": "0x0000025c1c572170"
    }
  }
}


Conclusion
WinDBG is a really powerful tool provided by Microsoft. A lack of familiarity with the syntax and interface means that it can be overlooked as a malware analysis tool. With the right extension, it can easily be used for the analysis of managed code (.NET).

We hope that this article piques your curiosity and that you will think about WinDBG next time you will have to analyse managed code such .NET.

Appendix

IOCs

Packed samples SHA256

  • 21acd3457c1a589e117988fe0456e50ed627f051a97ccd11bfeeaf3c0cd79bfe
  • 344ce133363f005346210611d5abd2513934a32739bc6e1bbd2257a298484051
  • 45c695e610d78178ec5ca6f4e1993afacf4e435b566cd2caf65408fb6080300f
  • 61653b2811fb7c672584d00417cbc1a56c8372331f1913104f9807a775f25773
  • ac7bd77245bdf284d36ce1f9e2cb6a21d2dbd38aa1964dbaee4d06563f057ca6
  • b607e87acdcb2ef0f102298decc57ca3ea20fabbf02375fd30eddddffbeec320
  • e93c0aed6bbb4af734403e02d399c124f2d07f8e701fb716c2efe65942f83504

Unpacked samples SHA256

  • 35dee9106e4521e5adf295cc945355d72eb359d610230142e5dd4adda9678dee
  • b5ce02ee3dfccf28e86f737a6dde85e9d30ff0549ec611d115a1d575b5291c2e
  • d9a732dcf87764a87f17c95466f557fac33f041ac6f244dba006ba155d8e9aea
  • fe068ce56b258762c10cc66525c309e79026c0e44103ca9b223c51382722cb09

WinDBG scripts

Before .NET 4

sxe ld mscorjit
g
.loadby sos mscorwks
!bpmd mscorlib.dll System.Reflection.Assembly.Load
.echo "Weird bug... bp twice..."
!bpmd mscorlib.dll System.Reflection.Assembly.Load
g
r $t1 = ecx
.printf "Byte array: ";r $t1
r $t2 = poi($t1+4)
.printf "Size: ";r $t2
db $t1+8 L$t2
.echo "dump in the file: ${$arg1}"
.writemem ${$arg1} $t1+8 L$t2
.kill
q

.NET 4

sxe ld clrjit
g
.load "C:\\Psscor4\\x86\\x86\\psscor4.dll"
!bpmd mscorlib.dll System.Reflection.Assembly.Load
.echo "Weird bug... bp twice..."
!bpmd mscorlib.dll System.Reflection.Assembly.Load
g
r $t1 = poi(esp+4)
.printf "Byte array: ";r $t1
r $t2 = poi($t1+4)
.printf "Size: ";r $t2
db $t1+8 L$t2
.echo "dump in the file: ${$arg1}"
.writemem ${$arg1} $t1+8 L$t2
.kill
q