0% found this document useful (0 votes)
9 views77 pages

mas_4

Malware Analysis part 4
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views77 pages

mas_4

Malware Analysis part 4
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 77

https://exploitreversing.

com

Malware Analysis Series (MAS):


Article 4
by Alexandre Borges
release date: MAY/12/2022 | rev: A

1. Introduction
Welcome to the fourth article of MAS (Malware Analysis Series). After I have posted three articles that,
hopefully, provided you with relevant concepts, techniques and some new knowledge on malware
analysis, so let’s move forward to learn new and interesting aspects of other well-known malicious
Windows binaries available for downloading from public sandboxes such as Malware Bazaar, Triage,
Polyswarm, Malshare, Hybrid Analysis, Virus Total and other ones.
Just in case you haven’t read the previous articles, you can download them from:
▪ MAS_1: https://exploitreversing.com/2021/12/03/malware-analysis-series-mas-article-1/
▪ MAS_2: https://exploitreversing.com/2022/02/03/malware-analysis-series-mas-article-2/
▪ MAS_3: https://exploitreversing.com/2022/05/05/malware-analysis-series-mas-article-3/
Throughout this text I will refresh concepts explained in previous articles, but I don’t have any plan for
getting into details, so it’s recommended to read them once again just in case you need it. Of course, in
practical terms and along the time, many explained techniques, approaches and concepts will be repeated
over and over again to provide you with further experience about proposed topics.
In this fourth part of this series, we’ll scratch the surface of .NET malware analysis, which sometimes
might present difficulties for analysts due to several and different techniques and tricks. We have excellent
tools available for helping us such as dnSpy and ILSpy, which make an excellent job in decompiling code to
MSIL (Microsoft Intermediate Language) and offering an approximate code to the original in high-level
.NET language, but in some cases isn’t still enough due to customized encoding and encrypted data, which
force us to use different techniques to be able to proceed and tackle the binary.
I’ll try to provide a minimal theory about the subject to ensure you understand the basic information
required while reversing .NET code. No doubts, .NET malware analysis a quite extensive topic and we will
return to this subject in future articles of this series.
As you’ll during all analysis of managed code threats, most of the time will came up additional stages also
written in .NET, and some of them are protected with a packer, obfuscator or even a modern protector. At
end of day, our mission is handling each of these stages, decrypting them and moving forward to the next
one, until being able to find the final payload, which could not be so easy to get it.

Like binary malware threats, in .NET malware analysis we also search for persistence techniques, C2
communication, evasion techniques, data exfiltration, clear text URLs, credentials and all sort of IOCs
that might help us to identify similar threats. Certainly we will encounter a wide spectrum of challenges
1|Page
https://exploitreversing.com

and obstacles to analyze managed code (.NET code), and this task might be harder yet than native code
because it’s necessary to have much of knowledge learned from native binary analysis and know specific
concepts about .NET architecture and manage several issues (obfuscation and cryptography, as usual ) to
get good results from the analysis. As you will see, .NET malware threats are in everywhere and are
heavily used in many threat campaigns nowadays.

Now we’re ready to proceed to setup a lab environment and refresh key concepts.

2. Lab Setup
We’ll be using the following environment during this article and future articles focused on .NET reversing
and, this time, I’m going to focus only on .NET related tools: :

▪ DnSpy: it’s a .NET assembly editor and debugger, but this project was archived, unfortunately. You
can download/clone it from: https://github.com/dnSpy/dnSpy.

▪ DnSpyEx: This is the revival of the original dnSpy project and has been constantly updated:
https://github.com/dnSpyEx/dnSpy

▪ De4dot: it’s a .NET deobfuscator and unpacker. You can download/clone it from:
https://github.com/de4dot/de4dot. It’ uses dnlib (below) to read and write assemblies.
Additionally, de4dot is also available in many Linux distributions and to install it execute: apt get
install de4dot.

▪ dnlib: it’s a module used to manipulate (read/write) .NET assemblies. Clone it: git clone
https://github.com/0xd4d/dnlib

▪ ILSpy: It’s an open-source .NET assembly browser and decompiler. It can be downloaded/cloned
from: https://github.com/icsharpcode/ILSpy
We won’t use all of the mentioned tools in this article, but it would be recommended to install them on
their Windows virtual machines for future binary analysis. To any external de-obfuscator necessary during
the analysis, so I’m going to indicate the proper URL to download it.

3. .NET Concepts
Definitely learning programming in several languages such as C, C++ and C# is not quite critical to perform
reverse engineering, but certainly this knowledge takes you up to a next level and helps to acquire a better
understanding of the code before taking decisions during any analysis.
Thus, and based on this premise, I’ll review key concepts related to .NET programming in this section. Of
course, I won’t explain how to program any code, but I will only expose relevant concepts about .NET for
helping readers to become a bit more comfortable while analyzing .NET malware samples.

2|Page
https://exploitreversing.com

Probably we’ll find malware samples written in .NET Framework and .NET Core and, as you probably
already know, .NET code is a managed code, which needs a .NET runtime
(https://github.com/dotnet/runtime) to be executed. These. NET binaries are basically composed by MSIL
(Microsoft Intermediate Language) instructions and metadata. Of course, probably you rarely handle IL
(Intermediate Language) instructions (though it is required in some obfuscated samples) and, if you want,
you can list all .NET runtimes and SDKs by executing: dotnet --list-runtimes / dotnet --list-sdks.
While malware samples compiled in .NET Framework assemblies, which also contains metadata
(manifest), can be either .dll or .exe file, .NET Core samples are always compiled as a .dll file (usually
compiled using: dotnet <assembly>.dll). Another subtle difference is that .NET Core doesn’t use the GAC
(Global Assembly Cache) like .NET Framework used as a common installation directory for framework
libraries.
If you already analyzed .NET threats previously, so probably you also found encrypted payloads in
embedded .NET resources, which can be unpacked using distinguished approaches as dumping the
unpacked resource (a .NET module, for example) from the memory using common tools like dnSpy or
specific programs to accomplish the same task.
Similar to any native binary, a .NET malware threat might also unpack another .NET malware (a .dll module
or .exe file) or a native code to be injected into a running process, and this injected malicious binary could
be a downloader to the next stage, which can download a native or managed code and start the real
infection. Even worse, some .NET malicious payload are able to attack the own .NET runtime and
compromise the entire environment.
In a daily malware analysis job, you probably will find .NET malware samples obfuscated using well-known
obfuscators such ConfuserEx, .NET Reactor, Dotfuscator, babelfor.NET, Agile, and so on, or even a
customized protectors, so it could demand some time to unpack and de-obfuscate such sample due the
existence of so many distinguished approaches. Depending on used obfuscating techniques, you can wait
for different tricks such as:
▪ Methods signatures, fields and metadata renaming.
▪ Encrypted strings.
▪ Junk code
▪ Control Flow obfuscation.
▪ Cross-Reference obfuscation
▪ Obfuscated Implementation methods.
▪ Obfuscated/hidden cross references
Any .NET code (included malware binaries, of course) can interact with the system using class from
System.Diagnostics namespace such as Process, ProcessModule, ProcessThread,
ProcessThreadCollection, ProcessStartInfo, and so on. Furthermore, there’re different methods such as
Start( ), Kill( ), GetProcesses( ), GetCurrentProcess( ), GetProcessById( ) etc, which are applied to the
Process type mentioned in this paragraph and interact directly with a running system. As a programming
concepts, remember that to compile assemblies with System.Diagnostics namespace, programmers will
need System.Linq namespace, so that’s an additional clue about what readers should expect for.
.NET applications (composed by one or more assemblies) are hosted within an application domain, which
can be accessed using AppDomain.CurrentDomain static property. These assemblies can be accessed
3|Page
https://exploitreversing.com

using System.Reflection namespace and this is a critical stuff for malware analysts to learn because we’ll
find .NET Reflection methods being use in the most of the .NET malware samples.
A very short list of well-known methods from System, System.Reflection and also other namespaces,
which could be used by .NET malware threats, follows below and, as you’ll learn, these methods are
interesting targets to set up a breakpoint during dynamic analysis:
▪ Activator.CreateInstance: this method is used to create an instance of a specified type by using a
technique named “late binding”, which provides the possibility of creating an instance of a given
type and, better, invoking any of its member at runtime without having any pre-determined
reference to the member of given an external assembly in the code.
▪ Assembly.CreateInstance: this methods locates a type from this assembly and creates an instance.
▪ Assembly.GetExecutingAssembly: this method gets the assembly that contains the code that’s
currently executing.
▪ Assembly.GetEntryAssembly: this method gets the process executable in the default application
domain.
▪ Assembly. GetFile: this method returns a FileStream for the specified file in the file table of the
manifest of this assembly.
▪ Assembly.GetModule: this method gets the specified module in the given assembly.
▪ Assembly.GetType: this method gets the type given a string, for example.
▪ Assembly.Load: this method loads an assembly.
▪ Assembly.LoadFile: this method loads the content of an assembly file.
▪ Assembly.LoadFrom: this method loads the content of an assembly file.
▪ Assembly.LoadModule: this method loads the module internal to the given assembly.
▪ Assembly.GetLoadedModules: this method gets all the loaded modules that make part of the given
assembly.
▪ AssemblyDependencyResolver.ResolveAssemblyToPath: this method resolves a path to an
assembly given an assembly’s name.
▪ AppDomain.GetAssemblies: this method gets assemblies that have been loaded into the
application domain context.
▪ ConstructorInfo.Invoke: this method invokes the constructor given by the instance.
▪ System.Reflection.AssemblyName GetAssemblyName: this method gets the AssemblyName for a
given file.
▪ Module.GetField: this method returns a specified field.
▪ Module.GetFields: this method returns the global fields on a given module.
▪ Module.GetMethod: this method returns a method given a string name.
▪ Module.GetMethods: this method returns the global methods defined on the module.
▪ Module.IsResource: this method determines whether the given object is a resource or not.
▪ MethodBase.Invoke: this method invokes the method or constructor.
▪ ResourceManager class: it represents a resource manager, which offers access to culture
resources.
▪ Module.GetMethodImpl: this method returns an implementation of a method.
A .NET malware binary contains the following structure:
▪ File header

4|Page
https://exploitreversing.com

▪ Common Language Runtime (CLR) file header


▪ Manifest
▪ IL code (managed code)
▪ Embedded Resources
▪ Type metadata
I’ve only mentioned few classes (types) like Assembly and Module related to Reflection, but there are
many other such as AssemblyName, EventInfo, FieldInfo, MemberInfo, MethodInfo, PropertyInfo and so
on. At the same way, other type classes as System.Type offers properties (IsClass, IsArray, IsCOMObject,
IsEnum, …) and methods (GetMembers( ), GetType( ), GetMethods( ), GetProperties( ), GetFields( ),
InvokeMember( ), etc…) that could be used for getting information of the types that are returned by using
System.Reflection.
It’s suitable to explain that metadata are merely descriptors for structure components of the application
such as classes, delegates, interfaces, enumerations, structures and so on, and each type is referenced by
a TypeDef token that’s exactly a pointer to full metadata definition of the referenced type (TypeRef).
Furthermore, readers should remember that, when we talk about CLR (Common Language Runtime), we
are considering loaders and the JIT compiler.
Metadata is organized as a relational database by using cross-references and making viable to find classes
that each one comes from. How is metadata represented? It’s represented by named streams, which are
classified as metadata heap and metadata table.

slot 1: Method 1 - Classe A

slot 1: Class A -- methods at slot 1 slot 2: Method 2 - Classe A

slot 2: Class B -- methods at slot 3 slot 3: Method 1 - Classe B

slot 3: Class C -- methods at slot 5 slot 4: Method 2 - Classe B

slot 4: Class D -- methods at slot 6 slot 5: Method 1 - Classe C

slot 5: Class E -- methods at slot 8 slot 6: Method 1 - Classe D

slot 7: Method 2 - Classe D

slot 8: Method 1 - Classe E


[Figure 1] Structure of a classes and methods (metadata) organized in tables.
Remember that managed resources are included in the .text section and not .rsrc section.
Scratching the surface of .NET internals, metadata heap can be:
▪ GUID heap: contains objects of size equal to 16 bytes.
▪ String heap: contains strings.

5|Page
https://exploitreversing.com

▪ Blog heap: contains arbitrary binary objects aligned on 4-byte boundary.


There’re 6 possible named streams:
▪ #GUID: contains global unique identifiers.
▪ #Strings: contains names of classes, methods, and so on.
▪ #US: contains user defined strings.
▪ #~: contains compressed metadata stream.
▪ #-: contains uncompressed metadata stream.
▪ Blob: contains metadata from binary objects.
As a side note, compressed and uncompressed named streams are mutually exclusive.
About metadata tables, there’re more than 40 of them and it’d take so much time to cover all of them,
though some of them such as ImplMap, MethodImpl, MethodDef, ModuleRef, ManifestResource,
TypeRef, TypeDef, Field, Property, Member, MemberRef, Method and File table are very interesting for
our purpose. Both native file headers and CLR headers can be checked by using the following command
and visualized in the following pictures :
▪ File header: dumpbin /headers filename.dll
▪ CLR header: dumpbin /clrheader filename.dll

Note: in my system dumpbin.exe is located at: C:\Program Files (x86)\Microsoft Visual


Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\dumpbin.exe

[Figure 2] CLR Header for a usual .NET sample


6|Page
https://exploitreversing.com

DOS Header
PE Header
PE Header

Data Directories
(size and location of CLR header)
CLR Header
Section Headers

.text
CLR Data (includes MSIL and metadata)
(ILcode, metadata,
managed resources) .idata

.data
Native Code /
Data Remaining sections

[Figures 3] Header composition of a manage module.


In most of cases, .NET malware threats have one or more class constructor (.cctor( )) and instance
constructors (.ctor( )). The .cctor( ) class constructor is called/run before executing the main method, class
initializers or even getting to the entry point. While using tools such as dnSpy, you always should examine
them because .cctor( ) and .ctor( ) are one of preferred places to put [de]obfuscating .NET code.
There was the possibility of controlling the JIT by hijacking the ICorJitCompiler::getJit( ) +
ICorJitCompiler::compileMethod( ), which allowed us to manipulate the final resulting code , but this issue
was fixed and included into Windows Defender. Other advanced malware threats try to change the
runtime library (in IL code level) or even hooking it. If they are successful, so certainly it will be lethal for
many applications and, of course, compromise the entire system.
I am not going into deeper details on .NET internals details involving MSIL code because this knowledge is
not really required for understanding this article. Eventually, readers might get further information from
my slides on DEF CON USA 2019:
▪ https://exploitreversing.files.wordpress.com/2021/12/alexandreborges_defcon_2019-3.pdf

4. General Procedure
Certainly one of most common questions from professionals while examining .NET malware threats is:
what details and clues should I take note while analyzing a .NET sample?
Of course, there aren’t fixed rules here and some considerations should be taken:
▪ Determine whether the malware code is really a .NET code.

7|Page
https://exploitreversing.com

▪ Try to identify whether the malware is packed. Even the presence of embedded resources are a
fair indication that there might be some malicious code hidden (and obfuscated).
▪ Discover the real Entry Point (pay attention to .cctor and .ctor constructors).
▪ Examine the code and try to identify possible obfuscator’s presence.
▪ Tools such as de4dot (better editing capabilities when executed on PowerShell) and other
customized ones will help you to de-obfuscate the code.
▪ How do you plan to unpack it? You should consider a mix of static and dynamic approach.
▪ Most .NET malware are really large, so don’t try to analyze all of them line-by-line. Most of time, it
isn’t worth, though in few cases you don’t have another alternative (knowing C# could help you).
▪ If you use dynamic analysis (probably also using dnSpy), so try to set up breakpoints on critical
methods listed previously.
▪ While analyzing methods, pay attention to non-used parameters.
▪ While using dnSpy, debugger’s tabs such Local, Call Stack and Modules are incredibly useful.
▪ Remember that malicious modules are loaded anytime and you always can dump them from
memory.
▪ There’re .NET malware samples that result to a final .NET malware and other ones that result to a
native malicious binary. Therefore, don’t make any conclusion in advance.

5. Collecting .NET information


Certainly one of more outstanding approaches to collect information useful information about .NET
samples is by using System.Reflection namespace on PowerShell. As readers already know, there’re
dozens of excellent references about the topic on the Internet and I don’t have any plan to go into details,
but maybe a quick explanation might be useful.
PowerShell offers endless options to access and collect information by using .NET static and instance
methods, and every executed command demands to understand the method’s syntax to invoke methods
and property’s syntax to read/write properties.
Therefore, few well-known syntaxes are:
▪ [Class Name]::PropertyName
▪ $ObjectReference.PropertyName
▪ [Class Name]::MethodName(arguments list)
▪ $ObjectReference.MethodName(arguments list)
If you check the page 4, we have a short list of classes and methods that could be called using the referred
syntax examples above to discover useful information about a .NET malware or even executing a specific
method from the malware that might help us along a de-obfuscation process.
Any of next commands can be used with while collecting basic information of a .NET binary and, of course,
it’s necessary to adapt them to each case:
# List all loaded assemblies.
PS C:\ > [appdomain]::currentdomain.GetAssemblies() | ft Location | Select-Object -First 10
Location
------------
8|Page
https://exploitreversing.com

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.PowerShell.ConsoleHost\v4.0_3.0.0.0__31bf
3856ad364e35\Microsoft.PowerShell.ConsoleHost.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\Syste
m.Core.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3
856ad364e35\System.Management.Automation.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.Management.Infrastructure\v4.0_1.0.0.0__3
1bf3856ad364e35\Microsoft.Management.Infrastructure.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management\v4.0_4.0.0.0__b03f5f7f11d50a3a
\System.Management.dll
C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.DirectoryServices\v4.0_4.0.0.0__b03f5f7f11d50
a3a\System.DirectoryServices.dll

# Load an specific assembly (.NET malware).


PS C:\> $Malware_Assembly =
[System.Reflection.Assembly]::LoadFile("C:\Users\Administrator\Desktop\MAS\MAS_4\malware_dotne
t.bin")

# Get all loaded modules from a specific assembly.


PS C:\ > $LoadedModules = $Malware_Assembly.GetLoadedModules( )
PS C:\ > $LoadedModules

MDStreamVersion : 131072
FullyQualifiedName : C:\Users\Administrator\Desktop\MAS\MAS_4\malware_dotnet.bin
ModuleVersionId : 53d49999-e4ad-4b0b-be7a-8497530feeda
MetadataToken :1
ScopeName : WaitCallb.exe
Name : malware_dotnet.bin
Assembly : WaitCallb, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null
CustomAttributes : {}
ModuleHandle : System.ModuleHandle

# Get all modules from a specific assembly.


PS C:\ > $Malware_Assembly.GetModules()

MDStreamVersion : 131072
FullyQualifiedName : C:\Users\Administrator\Desktop\MAS\MAS_4\malware_dotnet.bin
ModuleVersionId : 53d49999-e4ad-4b0b-be7a-8497530feeda
MetadataToken :1
ScopeName : WaitCallb.exe
Name : malware_dotnet.bin
Assembly : WaitCallb, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null
CustomAttributes : {}
ModuleHandle : System.ModuleHandle

# Get the “FullName” property of the assembly.


9|Page
https://exploitreversing.com

PS C:\ > $Malware_Assembly.FullName


WaitCallb, Version=1.7.3.0, Culture=neutral, PublicKeyToken=null

# Get the Runtime Version of the assembly.


PS C:\ > $Malware_Assembly.ImageRuntimeVersion
v4.0.30319

# Get the entry-point method of the assembly.


PS C:\ > $Malware_Assembly.EntryPoint

Name : MapVisitor
DeclaringType : WaitCallb.Filter.GlobalValueFilter
ReflectedType : WaitCallb.Filter.GlobalValueFilter
MemberType : Method
MetadataToken : 100663315
Module : WaitCallb.exe
IsSecurityCritical : True
IsSecuritySafeCritical : False
IsSecurityTransparent : False
MethodHandle : System.RuntimeMethodHandle
Attributes : PrivateScope, Private, Static, HideBySig
CallingConvention : Standard
ReturnType : System.Void
ReturnTypeCustomAttributes : Void
ReturnParameter : Void
IsGenericMethod : False

# List all classes of the Assembly.
PS C:\ > $Malware_Assembly.GetModules().gettypes()|?{$_.isPublic -AND $_.isClass}

IsPublic IsSerial Name BaseType


-------- -------- ---------- ---------------
True False ReponseListState System.Windows.Forms.Form
True False MappingValueFilter System.Windows.Forms.Form
True False InterceptorExpressionMessage System.Windows.Window
True False Singleton System.Windows.Window
True False ObjectAttributePool System.Windows.Window
True False DicMethodAnnotation System.Windows.Application
True False OrderValueFilter System.Object
True False ParamsHelperRole System.Object
True False Definition System.Object
True False Tag System.Object
True False Getter System.Object
True False Pool System.Object
True False StubTokenizerImporter System.Object
True False MerchantExpressionMessage System.Object
True False MessageAttributePool Tourield.Messages.MerchantExpressionMessage
10 | P a g e
https://exploitreversing.com

True False Interceptor Tourield.Messages.MerchantExpressionMessage


True False Bridge System.Object

# List all resources’ names of the assembly.


PS C:\ > $Malware_Assembly.GetManifestResourceNames()

WaitCallb.g.resources
WaitCallb.States.ReponseListState.resources
WaitCallb.Filter.MappingValueFilter.resources
aR3nbf8dQp2feLmk31.lSfgApatkdxsVcGcrktoFd.resources
Tourield.Properties.Resources.resources

# Get Information of a given resource


PS C:\ >
$Malware_Assembly.GetManifestResourceStream("aR3nbf8dQp2feLmk31.lSfgApatkdxsVcGcrktoFd.reso
urces")

CanRead : True
CanSeek : True
CanWrite : False
Length : 5650
Capacity : 5650
Position :0
PositionPointer :
CanTimeout : False
ReadTimeout :

# List all referenced assembly by our loaded assembly.


PS C:\ > $Malware_Assembly.GetReferencedAssemblies()

Version Name
------- --------------------------------
4.0.0.0 mscorlib
4.0.0.0 PresentationFramework
4.0.0.0 System.Windows.Forms
4.0.0.0 System
4.0.0.0 System.Drawing
4.0.0.0 PresentationCore
4.0.0.0 System.Xaml
4.0.0.0 WindowsBase
4.0.0.0 System.Core

PS C:\ > $MyClass = $Malware_Assembly.GetModules().gettypes()|?{$_.Name.equals("Interceptor")}


# List declared methods for a given class.

PS C:\ > $MyClass.DeclaredMethods | Out-String -stream | Select-String "^Name”


11 | P a g e
https://exploitreversing.com

Name : InsertProcess
Name : RunProcess

# List public methods for a given class


PS C:\ > $MyClass.GetMethods() | Select-Object Name

Name
--------
Equals
GetHashCode
GetType
ToString

# List return non-public, instance methods.


PS C:\ > $MyClass.GetMethods([Reflection.BindingFlags]::NonPublic -bor
[Reflection.BindingFlags]::Instance) | Select-Object Name

Name
----
Finalize
MemberwiseClone

# List declared constructors for a given class.


PS C:\ > $MyClass.DeclaredConstructors | Out-String -stream | Select-String "^Name"
Name : .ctor

# List all member types for a given class.


PS C:\ > $MyClass.GetMembers() | ft memberType, Name -auto
Member Type Name
------------- ---------------
Method Equals
Method GetHashCode
Method GetType
Method ToString
Constructor .ctor
Field m_Merchant
Field _Server
Field _Listener
Field producer
Field database

# Get a list of public instance methods.


PS C:\ > $MyClass.GetMethods([Reflection.BindingFlags]::Public -bor [Reflection.BindingFlags]::Instance)
| Select-Object Name | ft -HideTableHeaders

Equals
GetHashCode
GetType
12 | P a g e
https://exploitreversing.com

ToString

# Get a list of non-public instance methods.


PS C:\ > $MyClass.GetMethods([Reflection.BindingFlags]::NonPublic -bor
[Reflection.BindingFlags]::Instance) | Select-Object Name | ft -HideTableHeaders

Finalize
MemberwiseClone

# Get a list of non-public static methods.


PS C:\ > $MyClass.GetMethods([Reflection.BindingFlags]::NonPublic -bor
[Reflection.BindingFlags]::Static) | Select-Object Name | ft -HideTableHeaders

InsertProcess
RunProcess

# Get a list of public static methods.


PS C:\ > $MyClass.GetMethods([Reflection.BindingFlags]::Public -bor [Reflection.BindingFlags]::Static) |
Select-Object Name | ft -HideTableHeaders

# Get a list of non-public instance fields.


PS C:\ > $MyClass.GetFields([Reflection.BindingFlags]::NonPublic -bor
[Reflection.BindingFlags]::Instance) | Select-Object Name | ft -HideTableHeaders

# Get a list of non-public static fields


PS C:\ > $MyClass.GetFields([Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Static) |
Select-Object Name | ft -HideTableHeaders

We’re also able to invoke any method of a .NET malware during our analysis, but we’re going to return to
this topic in next articles.
During .NET malware analysis we will encounter Dynamic Assemblies, which concept is quite different
from Static Assemblies. The latter are loaded from a file on disk while dynamic assemblies are created on
memory (at runtime) using a special naming space named System.Relfection.Emit that offers the
possibility of creating assemblies, modules, performing CIL implementation, etc, during runtime.
This System.Relfection.Emit namespace has several members such as:
▪ AssemblyBuilder: this class is used to create an assembly at runtime.
▪ TypeBuilder: this class to control the creation of interfaces, delegates, structures and, of course,
classes in a module.
▪ ModuleBuilder: this class is used to define a module within a given assembly.
▪ MethodBuilder: this class defines and represents a method/constructor.
▪ EnumBuilder: this class is used to create a .NET enumeration type.
It’s required to use ILGenerator class and its associated methods such as Emit, EmitCall, BeginScope,
DeclaredLocal and so on to emit raw CIL opcodes and, dynamically, make the entire assembly.

13 | P a g e
https://exploitreversing.com

Although this article isn’t about programming, further details that could help readers interested in learning
a bit more about the topic follow:
▪ System.Reflection.Emit NuGet package should be installed.

▪ System.Reflection and System.Reflection.Emit name spaces should be imported.

▪ You should use AssemblyName( ) constructor (from AssemblyName class) to describe an


assembly's unique identity (ex: MASassembly)

▪ Create an assembly: var mybuilder = AssemblyBuilder.DefineDynamicAssembly(varMASassembly,


AssemblyBuilderAccess.Run). Take care: varMASassembly would be a AssemblyName variable that
contains an assembly definition named “MASassembly”.

▪ Define the module’s name: ModuleBuilder mymodule =


mybuilder.DefineDynamicModule(“MASassembly”)

▪ Setup a public class named “MASclass”: TypeBuilder masClassExample =


mymodule.DefineType(“MASassembly.MASClass”, TypeAttributes.Public)
From this point onward, It’s possible to define .cctor( ), setup new variables and emit the code using
GetILGenerator( ) + Emit( ) methods.
The information above could also help you while analyzing .NET malware threats and, eventually, make
easier to detect instructions related to Dynamic Assembly, which is not a so well-known topic for many
professionals.
If you like to follow an operational approach, you might use the excellent Mono framework to get useful
information from a .NET binary.
To install it on Linux (REMnux / Ubuntu 20.04):
▪ sudo apt install gnupg ca-certificates
▪ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys
3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
▪ echo "deb https://download.mono-project.com/repo/ubuntu stable-focal main" | sudo tee
/etc/apt/sources.list.d/mono-official-stable.list
▪ sudo apt update
▪ sudo apt install mono-devel
▪ sudo apt install mono-complete
To install it on Windows:
▪ Download it from: https://download.mono-project.com/archive/6.12.0/windows-installer/mono-
6.12.0.107-x64-0.msi

▪ Add “C:\Program Files\Mono\bin” to the PATH environment variable.


Once it’s installed, we’re able to list metadata tables and additional information as shown below:
14 | P a g e
https://exploitreversing.com

[Figure 4] Gathering metadata table information from a .NET binary (part 1)


Of course, we’re able to get much more information from metadata tables and, as you see below, we can
download embedded resources easily:

[Figure 5] Gathering metadata table information from a .NET binary (part 2)

15 | P a g e
https://exploitreversing.com

[Figure 6] Gathering metadata table information from a .NET binary (part 3)


Quick observations follow:
▪ The ImplMap table seems to be empty. This effect might be a consequence of packers,
obfuscation, Dynamic Assembly or many other possible reasons.
▪ We were able to list all methods, interfaces, type definitions and manifest’s content.
▪ We were able to dump all managed resources.
▪ There’re other good information such as module name and exported types, which hold several
types’ entries defined within modules of assembly and exported to external assemblies.
All mentioned procedures are quite useful to collect first information from a given .NET malware before
starting the analysis itself and having an idea about what we should expect for. Of course, nothing replaces
the analysis of the code using static and mainly its dynamic analysis, and tools like dnSpy (or dnSpyEx) are
able to perform a great work.

6. Threat information
The sample that will be analyzing in this article is SHA 256:
▪ 7cb92356a0170028fabc20f0cb9736b149efab01824ab1173b3277340a6a2ec4
You can download the sample from Malware Bazaar:

16 | P a g e
https://exploitreversing.com

[Figure 7] Downloading sample from Malware Bazaar


Checking details about the sample on Malware Bazaar, we have:

[Figure 8] Gathering information about the sample from Malware Bazaar

17 | P a g e
https://exploitreversing.com

[Figure 9] Gathering information about the sample from Malware Bazaar (continuation)

Evaluating the given sample on Virus Total we also have:

18 | P a g e
https://exploitreversing.com

[Figure 10] Gathering information about the sample from Virus Total
There’s good information from Figures 7, 8, 9 and 10 that we could consider about this sample:
▪ Its “original name” seems to be tup.exe.
▪ Likely it performs code injection (WriteProcessMemory + SetThreadContext).
▪ It seems to “escalate privileges” during the execution (AdjustPrivilegeToken).
▪ It apparently uses hooking technique (SetWindowsHookEx).
▪ It enumerates processes (EnumeratesProcess) for, maybe, picking up one to inject code.
▪ WMI is used by the malware. Infinite probabilities: anti-vm, anti-debugging, and so on.
▪ A new process is launched, which might be a native one.
▪ A file is created.

19 | P a g e
https://exploitreversing.com

▪ The sample is the AgentTesla (or one of its variants) and written in .NET (mscoree.dll).
▪ The .text section entropy is too high (7.91), so maybe hiding or “packing” something. However,
remember: on .NET, the embedded resources make part of the .text section, so the high entropy
could be reflecting possible embedded resources.
It’s relevant to underscore that all considerations above are only possibilities and first information.
Remember that the malware is likely packed/obfuscated, so there’re many artifacts to be discovered.
To check possible existence of packers/obfuscators in a .NET malware, readers could use Exeinfo PE
(https://github.com/ExeinfoASL/ASL) or DiE (https://github.com/horsicq/Detect-It-Easy), which are great
tools to check existence of packers and obfuscators:

[Figure 11] Checking packers through Exeinfo PE and Detect It Easy (DiE)

20 | P a g e
https://exploitreversing.com

Both tools tell us a possible existence of .NET Reactor, though we need to confirm whether there is a
packer or not by analyzing the code.
A last tool that’s always recommended while analyzing .NET samples is pestudio
(https://www.winitor.com/download/). I’m using the free version of pestudio and the paid one has much
more features:

[Figure 12] Gathering information through pestudio


We’re able to collect several nice information from pestudio such as used blacklisted functions, libraries,
visualize first bytes of resources and dump them, list the manifest and so on. It’s worth, definitely.
21 | P a g e
https://exploitreversing.com

7. Analysis
That’s the start point of our analysis and comprehensive understanding of the threat. As you’ll remember
about .NET analysis, most of samples have embedded resources (managed resources), which might be a
binary (managed module or binary) to be unpacked in real time. From those ones, few of them work as a
simple downloader of an external resource that is the real malicious payload to be executed.
Nonetheless, that’s the crucial point. There’re three well-known approaches to unpack a .NET malware:
a. using an specialized debugger and assembly editor for .NET such as dnSpy / dnSpyEx and
proceeding manually doing the analysis.
b. using a native debugger and some associated tricks to do it semi-automatically.
c. using a specialized tool to accomplish this task automatically.
Actually, using the term “unpacking” could be imprecise in some cases because resource could be only
encoded (or even in plain text), but certainly we can continue using the term without any lost of meaning.
Due to motivation in highlighting few concepts presented in previous sections, we’re taking the first
approach and, in next articles, we’ll try the other two possibilities.
Although readers already know, remember that over any debugging session (even a managed one) the
system can and likely will be infected, so don’t forget to disable networking communications, disable
shared folder and, mainly, take a snapshot.
Thus, open the malware (mas_4.bin) on dnSpy and let’s make some notes about the sample:

[Figure 13] First view on dnSpy 32-bit


We have few considerations here:

22 | P a g e
https://exploitreversing.com

▪ There’re five embedded resources.


▪ The entry point is WaitCallb.Filter.GlobalValueFilter.MapVisitor.
▪ If readers open Type References, readers will see:
o Classes
o Enumerations
o Structures
o Delegations
▪ The Assembly Name is WaitCalib.
▪ The Module name is WaitCallb.exe.
▪ Two <Module> classes (<Module> @02000001 and <Module>{FFAD4D1F-94F7-4211-ACBA-
FABE281ED9F5}), which could contain a module initializer that’s a feature from CLR. At end, it
works as a constructor for the module. In general, static constructors of <Module> are executed
only once during the assembly loading, though classes have its own class constructors (.cctor).
There were are our first impressions and information that we were able to collect from dnSpy. Examining
the entry point, we have:

23 | P a g e
https://exploitreversing.com

[Figure 14] Entry Point Method: MapVisitor


According to the code above, there’re few interesting methods to analyze:
▪ Application.EnableVisualStyles( )
▪ Application.SetCompatibleTextRenderingDefault(false)
▪ RecordParam.SelectConfig()
▪ Application.Run(new ReponseListState())
Each one of these methods may take us to hundreds lines of code and, no doubts, it could take a quite long
time to analyze. Readers could notice there’s a variable (num) controlling the execution flow and, at start,
it’s set up to 4, so the first function to be executed is EnableVisualStyles( ), which gets the full path of the
own loaded Assembly. The method (EnableVisualStyles) calls Application.EnableVisualStylesInternal:

[Figure 15] EnableVisualStyles method

24 | P a g e
https://exploitreversing.com

As readers can verify, this method is using two arguments: text, which receives exactly the Assembly
Location (line 994) and 101. Going into this method, we have:

[Figure 16] EnableVisualStylesInternal method


According to the code above we learned that:
▪ its first argument is the name of Assembly file.
▪ its second argument is a native resource ID (in this case, it’s using 101).
▪ it’s using a very particular class named UnsafeNativeMethods and calling one of its methods
named CreateActivationContext( ).
The UnsafeNativeMethods class is used to access and call native methods and, as readers are able to
notice, the code is invoking CreateActivationContext( ) to create and setup data structures in memory
which will hold information that will be used to load specific DLL modules or COM object instance, for
example. Of course, there’re many functions associated with activation context such as ActivateActCtx( ),
QueryActCtxW( ), ReleaseActCtx( ) and so on.
Soon after Application.EnableVisualStyles( ) has been called, the num variable is set to 3 and other two
methods such as Application.SetCompatibleTextRenderingDefault( ) and RecordParam.SelectConfig( ) are
called, but there isn’t any really important on them to comment.
As the break instruction has been executed, so the next method to be called is Application.Run(new
ReponseListState()) (Figure 14 / line 47), which provide us with a clear path to follow over our analysis. A
remaining note about this entry point class (GlobalValueFilter) is that methods QueryProcess( ) and
SearchProcess( ) don’t do anything except returning “true”. The ReponseListState class has the following
instance constructor:

25 | P a g e
https://exploitreversing.com

[Figure 17] ReponseListState constructor called by Run( )


26 | P a g e
https://exploitreversing.com

Once again, we have a kind of state variable (num) that determines which piece of code will be executed
and, initially, it’s set to 6, so next methods to be invoked are RecordParam.SelectConfig( ) and
ReponseListState.PostProcess( ).
Before proceeding, we’re able to see several methods being called inside a for-loop:
▪ RecordParam.SelectConfig()
▪ ReponseListState.PostProcess( )
▪ Invoke(obj, parameters)
▪ GetMethod("InvalidCast")
▪ CompareVisitor( )
Anyway, as num variable has been set to 6, so the next methods to be executed are:
▪ RecordParam.SelectConfig (line 34)
▪ ReponseListState.PostProcess (line 36)
▪ And, num is will be set to 2 (line 35), and the execution will jump to IL_0C label.
Method SelectConfig( ) doesn’t do anything and PostProcess( ) only returns “false”, so the “continue”
instruction (line 40) executes and the code flows to IL_0C label anyway. Therefore, the next method to be
executed will be CompareVisitor( ), though an instance constructor (.ctor) is executed right before of it. If
the reader go inside CompareVisitor( ), there is a long switch case (17 cases) with many graphic-related
methods being executed and, apparently, there isn’t anything strange. However, the first impression is
wrong! The trigger to the second stage (another .NET module) is hidden exactly inside of this method
because, soon after it, there’s the instruction: this.Text = “Form 1” (line 258). The “Text” property is
associated to an accessor/mutator , which is overridden by other accessor/mutator on line 292:

[Figure 18] ResetVisitor( ) method being called within on overriding accessor (getter/setter)
27 | P a g e
https://exploitreversing.com

If readers are not used to working with dnSpy, it’s possible to get a list of methods that overrides, are
overridden, have dependencies (Uses) and dependents (Used by) through right clicking on any method
and choosing Analyze (CTRL+SHIFT+R). In this case, I showed the view from overriding mutator, but we
could have done the same analysis from the overwritten mutator’s point of view, as shown below:

[Figure 19] Pointer overriding


Actually WaitCallb.States.ReponseListState.set_Text(string) : void @06000005 method overrides
System.Windows.Forms.Form.set_Text(string) : void @0600234C (line 2260), which calls its base mutator
for property public virtual string Text on line 3784.
Once ResetVisitor( ) is called , the ResourceManager class is instantiated and the managed resource
“Vargo” is loaded into the array variable, which now contains the encoded .NET module (second stage)
that will be loaded and executed.
Before “Vargo” managed resource being decoded, the malware sets “text” variable to
"P7C455RF8EBCYHA8URJ585" (it’s the XOR key) on line 340 and num2 to 92182 (it’s the resource size) on
line 349. Finally, the decoder is called on line 323 from ResetVisitor( ) as shown below:

28 | P a g e
https://exploitreversing.com

[Figure 20] ResetVisitor method and the decoder of Vargo managed resource.
Of course, we can easily write a Python / PowerShell script to decode manually this managed resource, but
it isn’t worth because there could be many encrypted resources. Thus, let’s set up a breakpoint on line 323
(for example) and following a dynamic approach using a debugger, which is best approach to save time.
If you don’t know about hotkeys on dnSpy, the most important ones are:
▪ F11 for stepping-in
▪ F10 for stepping over
▪ SHIFT+F11 for stepping out
▪ F9 to set / clear a breakpoint
If you don’t want to use the hotkeys, so you can access the Debug menu and have access to the same
commands. Therefore, set the breakpoint on line 323 and start the debugging process. The debugger is
going to stop the execution exactly on line 323 before the managed resource being decoded, so it’s time to
wait a minute. Within this same method (ResetVisitor( )), there’s a critical instruction on line 370 that
really loads the Vargo managed resource:
▪ ReponseListState.DefineVisitor(Assembly.Load(array), 11);
We’ve listed the Assembly.Load( ) method on page 4 and at this point we can imagine that the Load
method will load the decoded resource (Vargo) and it will use methods from this new module. Therefore,
set up a breakpoint on the Load( ) method above and resume the execution. Just in case your dnSpy
environment doesn’t show the Modules window, so go to Debug → Windows → Modules as explained
below:

29 | P a g e
https://exploitreversing.com

[Figure 21] Enabling the Modules window

[Figure 22] Decoded array: pay attention to 0x4D, 0x5A bytes (MZ)

30 | P a g e
https://exploitreversing.com

Below I show you the list of modules before and after the new module (SharpStructures) being loaded:

[Figure 23] Loaded modules: before and after view


Readers can notice that at the last line the SharpStructures module is loaded InMemory (the column is
marking “yes”), so we can easily save this module by right clicking it and choosing “Save Module” as
SharpStructures.dll:

[Figure 24] Saving a module from memory on dnSpy


Remember, you should keep the debugger stopped on line 370 because first we’re going to check the
saved module.
As I mentioned previously, many .NET malware samples have several stages before revealing the main
payload and, usually, these intermediate stages are encrypted, so we should check them before
proceeding and one of recommended tools to accomplishing it is Exeinfo PE:
31 | P a g e
https://exploitreversing.com

[Figure 25] Extracted modules being checked by Exeinfo PE


The extracted module is obfuscated with SmartAssembly Obfuscator. To confirm that there’s code
obfuscation, verify the loaded module on dnSpy, as shown below:

[Figure 26] Obfuscated module


We are able notice several Unicode notations that also indicate the code is really obfuscated. Additionally,
on the left, readers can confirm that there’re attributes related to SmartAssembly.

32 | P a g e
https://exploitreversing.com

No doubts, we are able to de-obfuscate this code and there’re several available techniques and tools that
can be used accomplish this task, though the de4dot (https://github.com/de4dot/de4dot) is one of the
most recommended tools. Of course, de4dot is able to de-obfuscate / unpack many different types of .NET
malware samples, but not all of them and, in some cases, we need to search for a specific unpacker,
though is not a hard task. Anyway, let’s try to de-obfuscate the extracted module:

[Figure 27] de4dot output


After de-obfuscating the extracted module using de4dot we have:

[Figure 28] De-obfuscated method on dnSpy


Of course, it’s much better than code shown in Figure 26. There’re other ways to improve this code, which
is far from being perfect, but it’s enough to be analyzed for now. I’d like to highlight that it’d be possible to
proceed without manually cleaning it (as we did using de4dot) because the sample could have its own
decoding routine that make the job for us, but debugging it would be a bit more complicated.

33 | P a g e
https://exploitreversing.com

Returning to the malicious code, if we continue debugging after having extracted the second stage (a .NET
module) from memory, soon the MapVisitor( ) will be called and, so afterwards, it will call
ReponseListState( ) constructor from ResponseListState class. If you check the Stack windows, it confirms
our statement:

[Figure 29] Calling a method from the next stage


The GetMethod( ) function tries to get the InvalidCast method, which makes part of the new and
extracted .NET module (obfuscated, as readers already might expect for):

[Figure 30] InvalidCast( ) from the stage 2

34 | P a g e
https://exploitreversing.com

A well-known approach to manage cases like that is replacing the content of obfuscated module on
memory, before it being loaded, by our de-obfuscated one. It’s seems weird, but provide us good and
practical results because debugging it becomes easier than handling obfuscation issues. How can we do it?
There’re many options and, probably, it’s a matter of taste: some professionals prefer using a hexadecimal
editor + Notepad++ and other ones prefer using CyberChef. Personally, I prefer the latter one. Thus, we
need to stop the debug session (because the module was already loaded) and set up a breakpoint on the
instruction responsible for loading the second stage module that, in our case, it’s the one from Figure 22,
and on the first instruction calling a method from the second stage:
▪ ReponseListState.DefineVisitor(Assembly.Load(array), 11); (from ResetVisitor( ) method)

▪ methodInfo = ((Type)ReponseListState.param).GetMethod("InvalidCast"); (from


ReponseListState( ) method)
Using CyberChef (https://gchq.github.io/CyberChef/) we can load the cleaned version of the second stage
(resulting from de4dot.exe), use From Hex recipe and remove all spaces (None) as shown below:

[Figure 31] De-obfuscated and extracted module loaded onto CyberChef


Copy the hexadecimal content to the clipboard (fourth icon – marked on figure above).
Launch the dnSpy in debugging mode again (Debug → Start Debugging or only click on Play button). Don’t
forget: you should remember of setting the two breakpoints mentioned previously.
The debugger will stop at first instruction -- ReponseListState.DefineVisitor(Assembly.Load(array), 11); --
and, viewing the Modules window, readers will notice that the SharpStructures module (second stage) is
not loaded yet. In Locals window, right click on array variable, which holds the PE format content, go to
Show In Memory menu → Memory 1 and you’re going to see the next two screens:

35 | P a g e
https://exploitreversing.com

[Figure 32] Steps to visualize the memory of array variable

[Figure 33] Memory content of array variable


Put the cursor at beginning of the executable (4D 5A), right click → Paste Special → Paste. All content of
the cleaned module copied to clipboard from CyberChef will overwrite the memory region.
Proceed with the debugging session and the execution will stop at second breakpoint (methodInfo =
((Type)ReponseListState.param).GetMethod("InvalidCast"). Additionally, the cleaned module should have
been loaded and, when you visualize the InvalidCast( ) method then you will see the following image:

36 | P a g e
https://exploitreversing.com

[Figure 34] InvalidCast( ) method in the loaded and cleaned module


So far it’s everything going well. We’ve replaced the obfuscated module by a cleaned one on memory right
before it has been loaded. There few important points here:
▪ Readers should always look for any instance constructor (.ctor) and class constructor (.cctor)
before starting the analysis.
▪ Readers should set a breakpoint at start of the InvalidCast method (from
SharpStructures.Sorting.SortHelper class) to keep the control of the execution.
▪ It’s recommended take a snapshot of your virtual machine before proceeding.
▪ Expect for a new obfuscated code, as shown the Exeinfo PE, in our previously “cleaned” module:

[Figure 35] InvalidCast( ) method in the loaded and cleaned module

37 | P a g e
https://exploitreversing.com

Readers could also are asking how to find the right InvalidCast method because there’re two methods with
the same name, but that belong to different classes:
▪ SharpStructures.Main.SortHelper class
▪ SharpStructures.Sorting.SortHelper class
If you’re continue debugging (F10 – step over), the answer comes automatically in the FullName property:

[Figure 36] Finding the correct InvalidCast( ) method to set up a breakpoint


Therefore, set a breakpoint on the correct InvalidCast( ), keep debugging (F10 – step over) and the
“transition” to the InvalidCast( ) should occur on lines shown below:

[Figure 37] Transition to InvalidCast method from the replaced module


The actual transition is performed by the Invoke( ) method on the methodBase variable, which hold the
right InvalidCast( ) method: Invoke → Invoke → UnsafeInvokeInternal → InvalidCast.
The targeted SharpStructures.Sorting.SortHelper.InvalidCast method has the following instructions:

38 | P a g e
https://exploitreversing.com

[Figure 38] InvalidCast method


If readers analyze the first instructions, so interesting details will be found and we should consider them:
▪ A delay is established at first two instructions
▪ There’re a class named Class0, which contains relevant methods.
▪ Methods from Class0 that should be analyzed such as DemandResources, smethod_[1,4,5,6],
ConstructionResponse
▪ A final Exit(0) method.
▪ Many other “hidden” sub-methods under all of these mentioned methods.
Our analysis of the stage 2 starts now. The recommended step is to check the Class0 class to get first
information about its available methods and associated details:

39 | P a g e
https://exploitreversing.com

40 | P a g e
https://exploitreversing.com

[Figure 39] Class0 content


We have the following observations of the Class0 content (Figure 39):
▪ smethod_0 and smethod_1 are manipulating an array and they are identical.
▪ smethod_2 and smethod_4 are being used for invoking a method and they are identical.
▪ smethod_5 and smethod_7 are constructing a string and they are identical.
▪ smethod_3 and smethod_6 are loading an Assembly and they are identical.
According to our notes about InvalidCast( ), so we can assume (for while) that it’s:
▪ Constructing a string (smethod_5)
▪ Manipulating an array (smethod_1)
▪ Loading an assembly (smethod_6)
▪ Invoking a method from this assembly (smethod_4)
Returning to InvalidCast( ) method (Figure 38), another interesting method is DemandResources( ), which
has the following content:

[Figure 40] DemandResources method


The DemandResources( ) is instantiating the ResourceManager class, which provides access to resources,
for reading a given resource name resulting from the smethod_5( ).
Examining the ConstructionResponse( ) method (Figure 41 – next page), which is called on line 26 from
InvalidCast method (Figure 38), it provides us few details:
▪ It receives a byte-array from smethod_1( ).
▪ To those recovered bytes, it proposes a UTF-16 format that uses the big endian byte order.
▪ Performs a XOR operation using its last byte and the number 112.
▪ Allocates a new byte array (named array).
▪ Reads each of its bytes and does a double XOR operation.
▪ Resizes the resulting byte array.
▪ Returns the final array.
The content of ConstructionResponse( ) method is shown below:
41 | P a g e
https://exploitreversing.com

[Figure 41] ConstructionResponse method


So far we have an idea about what’s happening:
▪ A sequence of bytes is read (smethod_5) from a resource, which the respective name is given as
the return of smethod_1.
▪ All read bytes are decoded by the ConstructionResponse( ). The content of the resulting array is a
module (third stage).
▪ The resulting array is loaded by the smethod_6( ).
▪ All types (class, interface, array, value, enumeration and so on) are returned from the loaded
assembly, and one of them is chosen (a class).
▪ At same way, for the type returned with GetTypes( ), all public methods are returned using
GetMethods( ), and one of them is picked up.
▪ Finally, the chosen method is invoked by Invoke( ) method from smethod_4( ).
Therefore, a reasonable approach is:
▪ Setting a breakpoint (F9):
o on line 26 of InvalidCast( ) (Figure 38), when we will able to analyze the content of the
byte_ array and, probably, we will find a new module there.
o on line 27 of InvalidCast( ), where smethod_6( ) is being called.
o on Assembly.Load( ) within smethod_6( ).
o on line 30 before the discovered method to be invoked.
▪ Extracting the module loaded into byte_ array variable.
▪ Using Exeinfo PE or Die to check for the presence of any obfuscator/packer.
▪ If there’s an obfuscator/packer, trying to remove it using de4dot or any other deobfuscator.
▪ Discovering the name of the class (line 29) and method being invoked on line 30.
▪ Renaming the saved module and replacing it on memory.
42 | P a g e
https://exploitreversing.com

▪ Taking a snapshot of the virtual machine after having done this setup because you might want to
be able to repeat this procedure if it’s necessary.
Of course, it wouldn’t be necessary to set up four breakpoints and only executing the code using F10
(step-over) would be enough. Anyway, you can decide the best approach for you.
Therefore, we have the following breakpoints setup:

[Figure 42] InvalidCast method including breakpoints


Running the code we got the following information about the new stage loaded into byte_ array variable:

[Figure 43] PE Format file loaded into byte_ array

43 | P a g e
https://exploitreversing.com

For now, right click the _byte array → Save… . Choose a name (stage_3.bin, but we’re going to rename it
later) and save it. Use the Exeinfo PE or Die to check possible obfuscators/packers, as shown below:

[Figure 44] Checking packers/obfuscators presence on stage_3.bin


Further information about the execution:
▪ The name of the new loaded module is DotNetZipAdditionalPlatforms.dll and its version is
v2.0.50257.
▪ The type variable (a class) hold the string "LajJueXX7RvrQwTLPl.XcuCxUwDbNNwbx89AI"
(namespace + class), which is an obfuscation indicator.
▪ The method’s name being invoked is RrRUhxJmfM().

[Figure 45] New module (DotNetZipAdditionalPlatforms) loaded onto memory


As we’ve learned, the module loaded onto memory and extracted using dnSpy is obfuscated using .NET
Reactor. As most of these obfuscators use class constructors (.cctor) or instance constructors (.ctor) to
manipulate or even de-obfuscate/unpack some information, it’s worth to see the obfuscated version:

44 | P a g e
https://exploitreversing.com

[Figure 46] Method from third stage being called for the second stage
There’re many classes (not shown in this figure above), but each one has a respective .cctor( ) method.
Additionally, the XcuCxUwDbNNwbx89AI class has its own class constructor and an instance constructor
that calls the .ctor( ) constructor. Additionally, the function being called (RrRUhxJmfM) from the
InvalidCast( ) is also obfuscated and has several switch cases (not showed above).
Now we have some useful information, we can try to de-obfuscate the extracted module (the third stage)
to replace the obfuscated module loaded on memory by this one. Once again, we can try to use de4dot to
accomplish this job:

[Figure 45] Extracted module de-obfuscated by de4dot


This time the de-obfuscation process wasn’t been perfect, but you’ll see that it’s enough for our purposes.
We can repeat similar steps that we did previously to replace the obfuscated module on memory for the
de-obfuscated one, and the best approach to do it is manipulating the byte_ array variable before the
module being loaded by smethod_6.
Thus, let’s repeat the procedure once again:
45 | P a g e
https://exploitreversing.com

▪ De-obfuscate the saved module using de4dot.


▪ Open it up on CyberChef, pick up the ToHex recipe and don’t leave spaces (no spaces).
▪ Copy the result from CyberChef to the clipboard.
▪ Right the byte_ array variable → Show in Memory Windows → Memory 1
▪ Right click at start of the executable (MZ / 4D 5A) → Paste Special → Paste
▪ Continue debugging by using F10 (step-over) until the line 28 (after assembly has been loaded).
Check whether it was actually loaded.
▪ Proceed with the execution up to line 30 and collect information such as the class name and
method being called from the third stage.
After replacing the content of the byte_ array variable on memory and stepping-over the execution until
the line 30, we have the following scenario:

[Figure 47] Calling the de-obfuscated third module


From the picture above, we learned that:
▪ The type variable holds a class type.
▪ The class’s name is Class12, which belongs to the namespace ns0.
▪ The targeted method on lines 29 and 30 is smethod_10.
▪ The Class12 also has its own .cctor (class constructor).
▪ Few native APIs references have come up such as GetProcessAddress( ) and LoadLibrary( ), but
there’re other ones.
Therefore readers have to set up two breakpoints on:
▪ line 8 of the .cctor( ) class constructor.
▪ line 6 of the smethod_10( ).
Once readers have set up the breakpoints, so proceed the execution using F10 (step over).
46 | P a g e
https://exploitreversing.com

If everything goes smoothly, the execution will hit the breakpoint in .cctor( ). Welcome to the stage 3,
whose the first method being called has the content shown below:

47 | P a g e
https://exploitreversing.com

[Figure 48] Calling the de-obfuscated third module


There’re good points to underscore in the figure above:
▪ As the malware is executed on a 64-bit system, so its code retrieves the context of a WOW64
thread (32-bit thread) using Wow64GetThreadContext( ).
▪ The smethod_8 is using GetDelegateForFunctionPointer( ) to convert a native (unmanaged)
function pointer to a delegate, which can be cast to any delegate type.

48 | P a g e
https://exploitreversing.com

▪ About delegates in .NET, a delegate type is sort of object (data structure / class) that provides a
reference (as a pointer) to a method or list of methods that can be invoked anytime. Actually,
delegate type can be interpreted as a structure because it holds the address of a method (similar
a function pointer), its respective parameters and return type. Therefore, we could create a
delegate type to any function accepting two strings as arguments and returning another string, for
example. Furthermore, when we use delegate keyword to define a delegate type, we are creating a
class (data structure) to hold all necessary information to the delegate.
▪ Delegate types can be used to send notifications (as a callback) to the invoking function whether
any specific condition is triggered, but it is not the main purpose of the malware code.
▪ In our case, GetDelegateForFunctionPointer( ) is used for marshaling a pointer to a native function
into a delegate type that can be invoked inside the .NET code.
▪ Malware is performing code injection because the usage, through delegating, of native functions
such as VirtualAllocEx, WriteProcessMemory, SetThreadContext and ResumeThread.
▪ As readers know, CreateProcessA (via delegate) is being used to create a process, but we need to
get additional information about it.
▪ In fact, .cctor( ) in this sample is being used only to create delegates (references) to native
functions because, according to page 46, the stage 2 is really calling the smethod_10, which calls
many methods and many of them using these mentioned delegates.
Therefore, the recommended approach would be to:
▪ set up breakpoints on key functions / methods inside the smethod_10 (not within .cctor( )).
▪ filter relevant methods by using Analyze feature, which bring us methods being used by our
analyzed method and methods that use the analyzed method.
The .cctor uses Class12, which has 9 delegates, and you get them by using the Analyze feature:

[Figure 49] Calling the de-obfuscated third module


As mentioned, about smethod_10 (the real method being called from stage 2), there’re many methods
being invoked by it and we must filter the most relevant ones:
49 | P a g e
https://exploitreversing.com

[Figure 50] smethod_10: used methods


Finishing our quick analysis .cctor( ), our unique goal is to make a list of all delegates being created within
it because all of them will be used by smethod_10:

50 | P a g e
https://exploitreversing.com

▪ Class12.delegate0_0 → "ResumeThread"
▪ Class12.delegate1_0 → "Wow64SetThreadContext"
▪ Class12.delegate2_0 → "SetThreadContext"
▪ Class12.delegate3_0 → "Wow64GetThreadContext"
▪ Class12.delegate4_0 → "GetThreadContext"
▪ Class12.delegate5_0 → "VirtualAllocEx"
▪ Class12.delegate6_0 → “WriteProcessMemory"
▪ Class12.delegate7_0 → "ReadProcessMemory"
▪ Class12.delegate8_0 → "ZwUnmapViewOfSection"
▪ Class12.delegate9_0 → "CreateProcessA"
And we have a good surprise: all of delegates are being used in smethod_9, as shown below:

[Figure 51] All delegates being read by smethod_9( )


51 | P a g e
https://exploitreversing.com

It’s great! The smethod_9( ) is responsible for using several delegates related to native functions, but the
smethod_9 is not called directly by smethod_10 (the first method being executed after .cctor( )). Once
again, we should use Analyze feature to find out the sequence of calls:

[Figure 52] Sequence of methods up to calling smethod_9


From the figure above, we learned that: smethod_10 → smethod_12 → smethod_9.
To take control on the execution of these native functions, go to smethod_9( ) and set up a breakpoint on
every delegate being used, which should seem something similar to the image below:

[Figure 53] Breakpoints on all delegates being used in smethod_9


That’s great! Now, make sure you’ve set up a breakpoint on the first instruction of smethod_10 and
resume the debugging execution until hitting the smethod_10 by using the Play button.
As readers can check, the smethod_10( ) is really long and would take time to understand each piece of
code, so the general idea is to use the Analyze feature once again to understand which method contains
interesting code.

52 | P a g e
https://exploitreversing.com

Therefore, let’s do a quick analysis of each smethod_#, one by one. Starting in smethod_0( ), we have:

[Figure 54] smethod_0: some methods being used


The only the well-known FindWindow( ), which is used by many malware threats to detect tools being
used during analysis, is interesting, but there are also good indicators of sandbox detection code here:
▪ if ((int)Class1.FindWindow("Afx:400000:0", (IntPtr)0) != 0)
▪ bool flag2 = Operators.CompareString(string_0, "C:\\file.exe", false) != 0;
▪ num2 = ((((int)Class1.GetModuleHandle("SbieDll.dll") == 0)
▪ num2 = ((((int)Class1.GetModuleHandle("SbieDll.dll") == 0)
▪ bool flag6 = string_0.ToUpper().Contains("SAMPLE")
▪ bool flag7 = Operators.CompareString(stringBuilder.ToString().ToUpper(), "SANDBOX", false) ==
0;
▪ bool flag9 = Operators.CompareString(stringBuilder.ToString().ToUpper(), "MALWARE", false) ==
0;
▪ num2 = (string_0.ToUpper().Contains("SANDBOX")
▪ bool flag = string_0.ToUpper().Contains("\\VIRUS")
Analyzing methods being used within smethod_1( ), we have good indicators and artifacts:

[Figure 55] smethod_1: virtual machine detection

53 | P a g e
https://exploitreversing.com

The method HeGwfEiyF( ) is called several times and its strings, used as argument, tell us that its purpose is
virtual machine detection.
VMWARE:
▪ bool flag3 = Class1.HeGwfEiyF("SOFTWARE\\VMware, Inc.\\VMware Tools",
"InstallPath").ToUpper().Contains("C:\\PROGRAM FILES\\VMWARE\\VMWARE TOOLS\\");
▪ bool flag5 = Operators.CompareString(Class1.HeGwfEiyF("SOFTWARE\\VMware, Inc.\\VMware
Tools", ""), "noValueButYesKey", false) == 0
▪ bool flag6 = Class1.HeGwfEiyF("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 0\\Scsi Bus 0\\Target
Id 0\\Logical Unit Id 0", "Identifier").ToUpper().Contains("VMWARE");
▪ bool flag7 = Class1.HeGwfEiyF("SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-11CE-
BFC1-08002BE10318}\\0000\\Settings", "Device Description").ToUpper().Contains("VMWARE");
▪ bool flag8 = Class1.HeGwfEiyF("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 1\\Scsi Bus 0\\Target
Id 0\\Logical Unit Id 0", "Identifier").ToUpper().Contains("VMWARE");
▪ bool flag12 = Class1.HeGwfEiyF("SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-
11CE-BFC1-08002BE10318}\\0000", "DriverDesc").ToUpper().Contains("VMWARE");
▪ bool flag13 = Class1.HeGwfEiyF("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 2\\Scsi Bus 0\\Target
Id 0\\Logical Unit Id 0", "Identifier").ToUpper().Contains("VMWARE");
▪ num = (Class1.HeGwfEiyF("SYSTEM\\ControlSet001\\Services\\Disk\\Enum",
"0").ToUpper().Contains("vmware".ToUpper()) ? 4010111179U : 2868294220U);
▪ num4 = ((Operators.CompareString(managementObject["Description"].ToString(), "VMware
SVGA II", false) == 0)

VIRTUALBOX:

▪ bool flag10 = Class1.HeGwfEiyF("HARDWARE\\Description\\System",


"VideoBiosVersion").ToUpper().Contains("VIRTUALBOX");
▪ bool flag4 = Operators.CompareString(Class1.HeGwfEiyF("SOFTWARE\\Oracle\\VirtualBox Guest
Additions", ""), "noValueButYesKey", false) == 0;
▪ bool flag = Class1.HeGwfEiyF("HARDWARE\\Description\\System",
"SystemBiosVersion").ToUpper().Contains("VBOX");
▪ bool flag11 = Class1.HeGwfEiyF("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 0\\Scsi Bus 0\\Target
Id 0\\Logical Unit Id 0", "Identifier").ToUpper().Contains("VBOX");
▪ num4 = (((Operators.CompareString(managementObject["Description"].ToString(), "VM
Additions S3 Trio32/64",false) == 0)
▪ bool flag15 = Operators.CompareString(managementObject["Description"].ToString(),
"VirtualBox Graphics Adapter", false) == 0;
QEMU:
▪ num = ((!Class1.HeGwfEiyF("HARDWARE\\DEVICEMAP\\Scsi\\Scsi Port 0\\Scsi Bus 0\\Target Id
0\\Logical Unit Id 0", "Identifier").ToUpper().Contains("QEMU")) ? 3150288510U : 2854005095U);
▪ bool flag9 = !Class1.HeGwfEiyF("HARDWARE\\Description\\System",
"SystemBiosVersion").ToUpper().Contains("QEMU");

54 | P a g e
https://exploitreversing.com

WMI queries are involved in gathering system information to be able to check all details related to virtual
machines. Anyway, we can conclude that the main purpose of smethod_1( ) is detecting virtual machine
environments, so we might nullify any instruction invoking it. Nonetheless, you’ll see that it isn’t necessary
in this sample.
Next method, smethod_2( ), is very simple and only starts a thread given by smethod_3( ) by using the
instruction:
▪ new Thread(new ThreadStart(Class12.smethod_3));
Therefore, both are related to each other and there’s none additional facts about them.
The smethod_4( ) and smethod_5( ) manage ACLs through APIs such as:
▪ DirectorySecurity
▪ SetAccessControl
▪ SetAccessRuleProtection
▪ setAttributes
▪ FileSystemAccessRule
▪ AddAccessRule
The smethod_6 is a bit more interesting because it handles tasks using schtasks.exe and even temporary
file creation on disk to support it. The base64 string below represents only a XML template to define and
control an application using tags such as RunLevel, Triggers, Settings, StartWhenAvailable,
AllowStartOnDemand and so on, and it’s used by schtasks.exe during the scheduling of a task:

[Figure 56] smethod_6: base64 string used for persistence with schtasks.exe

55 | P a g e
https://exploitreversing.com

[Figure 57] smethod_6: using schtasks.exe for some persistence


We could easily decode strings on PowerShell, but let’s use CyberChef once again (choose recipes “From
Base64”) to decode and prove that we actually have a XML file:

[Figure 58] Decode XML from base64


The next method, smethod_7( ), is interesting because it suggests a download from the Internet:
56 | P a g e
https://exploitreversing.com

[Figure 59] smethod_7: supposedly downloads a file from the Internet


The smethod_8( ), which is invoked by .cctor( ) and not by smethod_10( ), use the well-known
LoadLibraryA( ) and GetProcAddress( ) to find native API addresses to be used through delegates:

[Figure 60] smethod_8: resolves native API addresses


The smethod_9( ) invokes the already mentioned native APIs by using delegates:

[Figure 61] smethod_9: invokes native APIs through delegations


The smethod_11( ) is quite relevant due to the fact it loads a new assembly, so we can set a breakpoint on
Assembly.Load( ) line because this new module might be the next stage or a support module (resources):

[Figure 62] smethod_11: loads a


new assembly

57 | P a g e
https://exploitreversing.com

The smethod_12( ) is a proxy method for the smethod_13( ), which invokes a member of the new loaded
assembly, but also provides the following lines of code (and strings) for our analysis:
▪ string text = Path.Combine(path, "RegSvcs.exe");
▪ string text = Path.Combine(path, "MSBuild.exe");
▪ string text = Path.Combine(path, "vbc.exe");

[Figure 63] smethod_12 and smethod_13: operation related to the new loaded module
There, as a summary of methods, we have:
▪ smethod_0: sandbox detection
▪ smethod_1: virtual machine detection
▪ smethod_2: starts a thread
▪ smethod_3: provides the application to be started as a thread
▪ smethod_4 and smethod_5: manages ACLs
▪ smethod_6: schedules new tasks with schtasks.exe
▪ smethod_7: supposedly downloads a file from the Internet
▪ smethod_8: resolves native API addresses
▪ smethod_9: involved with native API calls.
▪ smethod_10: the main method (dispatcher).
▪ smethod_11: loads a new assembly.
▪ smethod_12 and smethod_13: operations related method invocation.
We have to set up some breakpoints, and a list of few possible lines follows below:
▪ smethod_1: (line 488) Start of the loop

▪ smethod_3: (line 186)


o Process.Start(Class12.string_10)

▪ smethod_7: (line 583)


o webClient.DownloadFile(string_11, text)

▪ smethod_11: (line 1490)


o Assembly assembly = Assembly.Load(Class12.byte_0);
58 | P a g e
https://exploitreversing.com

▪ smethod_13:
o (line 1617) string path =
(string)typeof(RuntimeEnvironment).InvokeMember("GetRuntimeDirectory",
BindingFlags.InvokeMethod, null, null, null);
o (line 1633) string text = Path.Combine(path, "RegSvcs.exe");
o (line 1654) string text = Path.Combine(path, "MSBuild.exe");
o (line 1664) string text = Path.Combine(path, "vbc.exe");

▪ smethod_9:
o (line 776 / WriteProcessMemory) num6 = (((!Class12.delegate6_0(struct2.intptr_0, num10
+ num11, array, array.Length, ref num4)) ? 1777126585U : 974911055U) ^ num3 *
3593627777U)
o (line 803 / WriteProcessMemory) bool flag5 = !Class12.delegate6_0(struct2.intptr_0,
num13 + 8, bytes, 4, ref num4)
o (line 859 / VirtualAllocEx) int num10 = Class12.delegate5_0(struct2.intptr_0, num14,
length, 12288, 64)
o (line 867 / CreateProcessA) bool flag10 = !Class12.delegate9_0(string_11, string.Empty,
IntPtr.Zero, IntPtr.Zero, false, 134217732U, IntPtr.Zero, null, ref @struct, ref struct2)
o (line 880 / WriteProcessMemory) num6 = ((!Class12.delegate6_0(struct2.intptr_0, num10,
byte_1, bufferSize, ref num4)) ? 1884772482U : 172468949U);
o (line 895 / GetThreadContext) num6 = ((Class12.delegate4_0(struct2.intptr_1, array2) ?
1127022864U : 23477936U) ^ num3 * 3000738847U);
o (line 921 / ReadProcessMemory) bool flag6 = !Class12.delegate7_0(struct2.intptr_0, num13
+ 8, ref num16, 4, ref num4);
o (line 929 / Wow64GetThreadContext) bool flag11 = !Class12.delegate3_0(struct2.intptr_1,
array2)
o (line 941 / ZwUnmapViewOfSection) num6 = (((Class12.delegate8_0(struct2.intptr_0,
num16) != 0) ? 3120432759U : 2659671650U) ^ num3 * 1247483263U);
o (line 984 / SetThreadContext) bool flag13 = !Class12.delegate2_0(struct2.intptr_1, array2);
o (line 1035 / Wow64SetThreadContext) bool flag4 = !Class12.delegate1_0(struct2.intptr_1,
array2);
o (line 1045 / ResumeThread) bool flag12 = Class12.delegate0_0(struct2.intptr_1) == -1;
After setting the mentioned breakpoints readers should take a snapshot of the virtual machine just in case
to be necessary to start over.
Resuming the execution, few breakpoints will be hit and other ones don’t:
▪ smethod_13:
o (line 1617) @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\"
o (line 1633) @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegSvcs.exe"

▪ smethod_9:
o (line 867 / CreateProcess):
▪ lpApplicationName:
@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegSvcs.exe"
59 | P a g e
https://exploitreversing.com

▪ dwCreationFlags: 134217732U == 0x 0x08000004 == CREATE_SUSPENDED

o (line 895 / GetThreadContext): nothing important

o (line 921 / ReadProcessMemory):


▪ lpBuffer: 0x00C50000
▪ nSize: 4
▪ *lpNumberOfBytesRead: 4

o (line 859 / VirtualAllocEx):


▪ hProcess: 0x354 (handle to RegSvcs.exe)
▪ lpAddress: 0x00400000
▪ dwSize: 0x0003A000
▪ flProtect: 64 == 0x40 == PAGE_EXECUTE_READWRITE

o (line 880 / WriteProcessMemory):


▪ hProcess: 0x354 (handle to RegSvcs.exe)
▪ lpBaseAddress: 0x00400000
▪ lpBuffer: contains the executable to be injected
▪ nSize: 0x00000200

o (line 776 / WriteProcessMemory):


▪ hProcess: 0x354 (handle to RegSvcs.exe)
▪ lpBaseAddress: num10 + num11 = 0x00400000 + 0x00002000 = 0x00402000
▪ lpBuffer: contains the the second session of executable to be injected

o (line 776 / WriteProcessMemory):


▪ hProcess: 0x354 (handle to RegSvcs.exe)
▪ lpBaseAddress: num10 + num11 = 0x00400000 + 0x00036000 = 0x00436000
▪ lpBuffer: contains the the second session of executable to be injected

o (line 776 / WriteProcessMemory):


▪ hProcess: 0x354 (handle to RegSvcs.exe)
▪ lpBaseAddress: num10 + num11 = 0x00400000 + 0x00038000 = 0x00438000
▪ lpBuffer: contains the the second session of executable to be injected

o (line 984/ SetThreadContext): nothing important

o (line 1045/ ResumeThread): nothing important

I tried making things easier and wrote down some parameters (as shown above) during the debugging
execution for helping you to understand what’s happening over the stage_3.bin’s instructions.
Additionally, I left some API parameter as support stuff and, as you could notice, many breakpoints haven’t
been hit as we expected (nor not), and it looks like good:

60 | P a g e
https://exploitreversing.com

[Figure 64] CreateProcessA( ) – credits: Microsoft (MSDN)

[Figure 65] WriteProcessMemory( ) – credits: Microsoft (MSDN)


Additionally, I’ve run “C:\MAS_4>handle -p 4452 -a” command (from SysInternals) to reveal the process
associated to the the given handle:

[Figure 66] Handle command from SysInternals


61 | P a g e
https://exploitreversing.com

Based on information collected from the debugging session through those breakpoints, we can make some
considerations:
▪ The malware execute GetRuntimeDirectory( ) to find the current .NET Runtime directory.
▪ Depending on the result of GetRuntimeDirectory( ), which is related to .NET runtime version, the
malware loads one of available and legal applications. In my environment, It’s loaded RegSvcs.exe,
which is an installation tool for .NET services.
▪ The malware injects a malicious code into the loaded module (RegSvcs.exe). However, it doesn’t
do it at once to include the entire malicious code, but it does a section-by-section copy.
▪ Due to the fact that the malicious code is injected section-by-section, it isn’t practical to use dnSpy
to save each part of the code being injected because we would need to concatenate everything
later, and it isn’t worth to spend time doing it.
▪ The most recommended approach is to visualize memory addresses of the process (RegSvcs.exe)
and search for a RWX section, which likely starts at 0x400000. These both information can be
confirmed using collected parameters on line 859 (VirtualAllocEx) of page 60.

To save a memory region from Process Hacker tool, double-click the region, which confirms it’s an PE
executable and click on “Save…” button:

[Figure 67] Process Hacker: identify and save the injected code
If you open the saved binary in PE Bear to check Imports, so you’ll find everything messed up and,
apparently, there isn’t any useful information.
62 | P a g e
https://exploitreversing.com

The reason is that we dumped the binary from memory, so it’s in mapped format and we need to convert
it to unmapped format:

[Figure 68] PE Bear: messed Import table

[Figure 69] PE Bear: unaligned sections


Although readers already know how to do the transformation from mapped to unmapped format, and I
already explained it in previous articles, but it’s worth to repeat steps again:
63 | P a g e
https://exploitreversing.com

▪ At Section Hdrs tab, for each section, copy the Virtual Address to the Raw Address.
▪ Calculate the size of each section by subtracting the address of next section from the current one
and alters the Raw Size using the result.
▪ After you have changed the Raw Size, copy the Raw Size value to the Virtual Size field.
▪ Save the binary by right-clicking on the binary’s name (top-left) and provide it a new name.

[Figure 70] PE Bear: unaligned sections

[Figure 71] PE Bear: unaligned sections

64 | P a g e
https://exploitreversing.com

Once the saved binary has been fixed, search for possible packers/obfuscators using DiE and Exeinfo PE:

[Figure 72] DiE: checking packers and/or obfuscators

[Figure 73] Exeinfo PE: checking packers and/or obfuscators


It’s seems that our fourth stage is obfuscated using Obfuscar, which is one of many available packers for
.NET and we’ll proceed with our analysis using dnSpy and try to de-obfuscate it using de4dot or any
available deobfuscator. Anyway, before proceeding, it’s interesting to show you handles opened by this

65 | P a g e
https://exploitreversing.com

new stage because, apparently, it tries to communicate via network, according to \Device\Afd handle
name:

[Figure 74] Possible network communication based on handles related to WinSock.


Let’s start to analyze the fourth stage and, my first recommendation, is to take a snapshot of the virtual
machine to make possible to revert it whether something goes wrong.
As usual, we should try to open this stage on dnSpy because it is a .NET binary (remember: it imports
mscoree.dll) and check what’s happening:

[Figure 75] dnSpy: fourth stage

66 | P a g e
https://exploitreversing.com

According to the entry-point, namespaces, class names and methods, this stage seems to be also
obfuscated and, as we learned from Die and Exeinfo PE, apparently the packer is Obfuscar.
Readers can navigate to Entry Point or by clicking on last “A” on line 4 from last figure or right-clicking on
the Assembly name and choosing “Go to Entry Point”:

[Figure 76] dnSpy: going to entry point

[Figure 77] dnSpy: entry point of stage 4


Readers are also able to see the first effect of the obfuscation, where there’re classes such “b” and “C”,
and methods named at same way like “A( )” .
67 | P a g e
https://exploitreversing.com

Anyway, a method C.A( ) is called and, afterwards, an Application.Run( ) method is called. Going into the
A( ) method, we have:

[Figure 78] dnSpy: A.C.A( ) and A.C.A.A( ) methods


We have some methods names, but we don’t have any string. For example, on line 50, where we should
see a string, we see something like “4AE7E02E-291A-4676-9641-A6E499CD2831.aw()”, which seems to be
<class.method( )> and not a string. Clicking on the first one, the dnSpy take us to the following code:

[Figure 79] dnSpy: Part of the decryption routine

68 | P a g e
https://exploitreversing.com

The routine, partially shown in the figure above, contains a class containing a main method (private static
string <<EMPTY_NAME>>(int A_0, int A_1, int A_2)) and several methods calling a decryption routine.
Apparently, the malicious code dynamically decodes and build up a string table with 767 strings and, once
they are decoded then the malware picks up the string from there according to the given index. All this
process occurs in the .cctor( ), where is a long sequence of elements (11566 elements) and, at end, a for-
loop reading each one and decoding them using the own element index and a value (170).
Our first step is using de4dot, which doesn’t offer support for Obfuscar, to try de-obfuscate all possible
symbols:

[Figure 80] De-obfuscating possible symbols with de4dot


After using de4dot, we can open it on dnSpy and, though we see it has renamed some classes and so on,
strings weren’t decrypted yet, as shown below:

[Figure 81] Stage 4 after de-obfuscated by de4dot

69 | P a g e
https://exploitreversing.com

To decrypt strings we have two possible paths: we can use another de-obfuscator tool or try to do it using
other available options from de4dot. A working de-obfuscator for Obfuscar is
https://github.com/DarkObb/DeObfuscar-Static.
To use, reader should clone and compile it using Visual Studio 2019 or Visual Studio 2022, and building the
solution is clean and direct:

[Figure 82] Building DeObfuscar-Static using Visual Studio 2019/2022


Its usage is very simple and produces immediate results:

[Figure 83] De-obfuscating the fourth stage (already cleaned by de4dot) with DeObfuscar-Static
Opening stage_4_decrypted-Dec.exe on dnSpy, we have:

[Figure 84] De-obfuscated strings by DeObfuscar-Static


That’s perfect! Now we’re able to see strings where previously we saw <class>.<method>, so we can
analyze this fourth stage without further problems.
70 | P a g e
https://exploitreversing.com

Readers could ask about reasons of namespaces, classes and method haven’t been recovered neither using
de4dot nor DeObfuscar-Static tool. The cause is that, during the obfuscation process, all information
about names of namespace, classes, methods, and so on, were lost. However, it isn’t an issue for us
because everything else is present within the sample. Additionally, the whole
<PrivateImplementationDetails>{A78A1E33-EFB4-4B39-84DB-A2C18EC95E34} namespace could be
deleted without any problem because the malware won’t need it anymore, but it’s a personal decision.
Another approach would be use the own de4dot to de-obfuscate strings from this sample, but using non-
conventional options that usually works very well for several unknown obfuscators.
To understand what will do here, return to Figure 78 and pay attention to the following:
▪ 4AE7E02E-291A-4676-9641-A6E499CD2831.aw()
From this instruction, we have:
▪ 4AE7E02E-291A-4676-9641-A6E499CD2831 ➔ class
▪ aw() ➔ method
Therefore, as we mentioned previously, this class contains all methods used to decrypt scripts and, if we
can dynamic use them, so decrypting strings issue is solved. Checking the method above, we have:

[Figure 85] String decrypting methods


71 | P a g e
https://exploitreversing.com

There’re many decrypting methods and the most important part for us are their respective tokens, where
the first one is 0x060001F8 and the last one is 0x060004F6 (please, check the code).
The de4dot options we are looking for are reported in its help:
▪ --strtyp TYPE String decrypter type
▪ --strtok METHOD String decrypter method token or [type::][name][(args,...)]
In few words, de4dot provides us with options to dynamically call all decrypting methods by referring to
their respective tokens. Thus, the syntax to decrypt all strings is:
▪ de4dot --strtyp delegate –strtok <method token> –strtok <method token> --strtok…
The only issue is that there’re too many tokens (and methods associated) because, as we mentioned
previously, there’re 767 strings and, of course, the command line will be very long, but we can manage it.
Using Python + Jupyter Notebook, I wrote few line of code to generate our command line:

[Figure 86] Script to generate command line for decrypting strings

72 | P a g e
https://exploitreversing.com

As readers are able to see, the script only puts several parts of the necessary command together and
generates an output including all required tokens and options. As the Python range( ) function excludes
the last element, so I added one to include it. You should copy the whole command to a PowerShell
terminal and execute it there as shown below (I’m sorry for the dark background, please):

[Figure 87] Customize de4dot command line on PowerShell


Finally, open it on dnSpy:

[Figure 88] Stage 4 with decrypted strings through de4dot


73 | P a g e
https://exploitreversing.com

It has worked perfectly, again. There’re some comments that, eventually, could be useful here:
▪ Remember that de4dot is able to run Windows and Linux (apt install de4dot) and you could test
the command on these systems.
▪ Usually I prefer run commands and tools for .NET in recent version of Windows (10 or 11) to avoid
encountering any unexpected surprise. Once again, it’s a matter of personal preference.
▪ It’s recommended to compile your own version of de4dot because, usually, it will include updated
components.
▪ Prefer using PowerShell window because its editing capabilities are much better than Command
Prompt.
▪ If you have any problem while using de4dot to run the produced long command then you should
try a newer version of Windows system using your own compiled version.
From this point onward, finally there isn’t further obfuscation in the binary and it’s only a task of reading
code, analyzing APIs and structures, though you’ll find some encrypted data yet. I’m going to leave only
three pieces of code here, but readers really must parse several methods (there’re a lot of them) to learn
all capabilities offered by AgentTesla malware.
One of the possible approaches, mainly while analyzing obfuscated codes, would be to examine the Table
Streams to locate interesting functions, mainly native APIs, which provides an idea of some characteristics
of the malware. Of course, there’re other tables, but ImplMap table might help you here:

[Figure 89] ImplMap table


As reader can verify, there’re well known APIs which are used in native malwares such as
SetWindowsHookEx, CallNextHookEx, UnhookWindowsHookEx, GetKeyboardState, GetKeyboardLayout,
EnumProcessModules, SetClipboardViewer and so on.
It doesn’t mean that only these 42 APIs from ImplMap table are important, but maybe they could help you
providing a starting point. You could use CTRL+F and search for them in each class of this stage.

74 | P a g e
https://exploitreversing.com

You should always the notation on dnSpy: <namespace>.<class>.<subclass>.method( ), though in many


cases there isn’t any subclass.

[Figure 90] Establishing persistence

[Figure 91] Contacting an external website to upload information and/or download tools

[Figure 92] Downloading TorBrowser


Sincerely, I could comment dozens of lines and piece of code here because this trojan contains a wide
range of capabilities spread over dozens of methods, but I don’t think it’s necessary and it would be a bit
out of the context. As readers could notice, several clues came up using only three figures and exposed
that the malware uses Tor, contacts an external website to post information and eventually downloads
tools, and of course, uses classical persistence mechanisms.

75 | P a g e
https://exploitreversing.com

Finally, a question remains: can we use IDA Pro, which it’s used for analyzing native binaries, shellcodes,
raw files and UEFI firmware, to analyze a .NET (managed) malicious code? Of course, we can.
IDA Pro doesn’t show the high-level representation of the binary, but its IL (Intermediate Language)
interpretation, which already helped me understanding what was happening in the code over many
situations and, additionally, it has the well-known graph representation that makes easier to navigate
through the MSIL code:

[Figure 93] IDA Pro: view of the final .NET payload (AgentTesla)
Initially, you could think it wouldn’t be appropriate using IDA Pro to analyze managed code (.NET code)
because MSIL representation doesn’t seems too easy, but I’ve used it in many cases:
▪ To understand eventual obfuscation tricks.
▪ To quickly find all called natived APIs.
▪ To figure out the sequence of called functions using the graph mode.
Additionally, I have used IDA Pro to analyze final payloads and get quick directions of executed actions by
observing the function list, following code through the graph-mode and, as we’ve used in dnSpy,
performing text searches through ALT+T and CTRL+T. If you don’t know about MSIL then, once again, I
recommend you reading my slides from DEF CON USA 2019.
At the end of day, it’s a personal choice using tools and different approaches to analyze .NET malware
samples, but it’s always recommended to use any available tool that to make things clearer and faster.

76 | P a g e
https://exploitreversing.com

8. Conclusion
As I already mentioned previously, there’re dozens (or hundreds) of methods and functions to be analyzed,
which certainly would take many additional pages. We could, for example, have tracked a more complete
malware profile by:
▪ searching for other mechanisms of persistence
▪ collecting information from system
▪ studying hooks and keyloggers
▪ analyzing all network communications
My goal keep being to offer a review of malware analysis and, if it’s possible, helping reverse engineers to
learn something new, providing a guideline to follow and search for something when it’s necessary.
I could have chosen a more complex malware sample, but it wasn’t not the idea. The general context is to
explain key concepts, strategies, techniques and approaches used during malware analysis of different
threats and, in this scenario, proposing hard examples wouldn’t help anyone and it would be useless, in my
opinion.
This article certainly will have typos and errors, but it isn’t big deal. Soon I find them, I’ll release a new
revision of this document.

9. Acknowledgments
I’d like to publicly thank Ilfak Guilfanov (@ilfak) and Hex-Rays (@HexRaysSA) for supporting this project
by providing me with a personal license of the IDA Pro.
Although I haven’t used IDA Pro in this specific article, it doesn’t change anything because without having
the support from Ilfak and HexRays certainly I wouldn’t be able to write this series of articles.
As I promised him, I will keep writing this series of articles in the next months and years. Certainly, my
gratitude for his help is endless.
Once again: thank you for everything, Ilfak.

Just in case you want to keep in touch:


▪ Twitter: @ale_sp_brazil
▪ Blog: https://exploitreversing.com

Keep reversing and I see you at next time!


Alexandre Borges

77 | P a g e

You might also like