This is a series of code snippets that I have found useful when developing configuration extractors for dotnet-based malware.
Here are some examples where I have applied these snippets.
This page would not exist without the work of these folk. Much of this work is based on their blogs and scripts.
- RussianPanda - WhiteSnake Configuration Extractor
- N1ghtw0lf - XorString Decryptor
- OALabs - XorStringsNet
Enumerate DotNet File For Call Instruction
This snippet allows you to enumerate a dotnet exe/dll and locate all call instructions. You can replace the OpCodes.Call
with any instruction of your choosing.
Here is a list of all available instructions.
import clr
clr.AddReference("dnlib.dll")
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
filename = "yourfile.exe"
module = ModuleDefMD.Load(filename)
for types in module.GetTypes():
for method in types.Methods:
if method.HasBody:
for instr in method.Body.Instructions:
if instr.OpCode == OpCodes.Call:
result = "Something interesting"
Enumerate Method Signatures
This snippet allows you to hone in on specific functions/methods within a dotnet binary.
This particular snippet locates the first method/function that takes an integer value as an argument, and then returns a string.
Note that this uses the System.Reflection library and not the dnlib library as in other examples.
import clr
clr.AddReference("System")
module = System.Reflection.Assembly.LoadFile(filename)
for types in module.GetTypes():
for method in types.GetMethods():
params = method.GetParameters()
if params[0].ParameterType.FullName == "System.Int32":
if method.ReturnType.FullName == "System.String":
return method
Enumerating Method Instructions For a Known Pattern
This snippet enumerates a method for a known IL instruction pattern.
def has_config_sequence(method):
target_sequence = [OpCodes.Stsfld,OpCodes.Ldc_I4,OpCodes.Br_S,OpCodes.Call,OpCodes.Stsfld,OpCodes.Ret]
target_len = len(target_sequence)
for i in range(target_len):
target_sequence[i] = target_sequence[i].Name
if method.HasBody:
current_sequence = [instr.OpCode.Name for instr in method.Body.Instructions]
for i in range(1, target_len-1):
if target_sequence[-i] != current_sequence[-i]:
return False
return True
Invoke a Static Method
This snippet invokes a static
method with a single integer argument. This assumes that you have already located a method using something similar to the previous snippet.
import clr
clr.AddReference("System")
python_int = 55
dotnet_int = System.Int32(python_int)
module = <module found with previous snippet>
result = module.Invoke(None, (dotnet_int,))
Execute/Invoke a Method Via Metadata Token
A few notes
- Python Integers must be converted to "System.Int32" integers or you will have issues.
- This snippet resolves a method via it's metadata token, if you have resolved a method using the previous snippet, you can call
Invoke
directly on the returned value. - If the method you are invoking is
static
, then the first argument toInvoke
can beNone
. - For some reason arguments must be passed as an array, hence the
(dotnet_int,)
rather than justdotnet_int
import clr
clr.AddReference("System")
python_int = 55
dotnet_int = System.Int32(python_int)
token = 0x06000005
method = module.ManifestModule.ResolveMethod(token)
result = method.Invoke(None, (dotnet_int,))
Invoke a Generic Method
Invokes a static generic method that takes a single integer value as an argument.
- Assumes that you are expecting a string as a return value.
- Assumes the method is
Static
- Assumes one integer argument is expected
import clr
clr.AddReference("System")
method = <some method you have found using previous snippets>
concrete_method_str = method.MakeGenericMethod(clr.GetClrType(str))
python_int = 55
dotnet_int = System.Int32(python_int)
result = concrete_method_str.Invoke(None, (dotnet_int,))
Patch Anti-Debug Instructions with NOP Values
This snippet is a bit messy but possibly useful for someone.
It was used to patch all calls to GetExecutingAssembly
and GetCallingAssembly
which were followed by a Brfalse
or Callvirt
instruction.
This was a specific pattern used in a protector (I believe ConfuserEx) to prevent Invoke
calls. By patching the calls and surrounding instructions, the decryption methods could be invoked without failing the GetExecutingAssembly == GetCallingAssembly
anti-debug check.
import clr
clr.AddReference("dnlib.dll")
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
filename = "yourfile.exe"
module = ModuleDefMD.Load(filename)
def nop_executingAssembly(module):
nop_methods = ["GetExecutingAssembly","GetCallingAssembly"]
for types in module.GetTypes():
for method in types.Methods:
if method.HasBody:
for i in range(len(method.Body.Instructions)):
if method.Body.Instructions[i].OpCode == OpCodes.Call:
for nop_method in nop_methods:
if nop_method in str(method.Body.Instructions[i]):
method.Body.Instructions[i].OpCode = OpCodes.Nop
for j in range(1,4):
if method.Body.Instructions[i+j].OpCode == OpCodes.Callvirt:
method.Body.Instructions[i+j].OpCode = OpCodes.Nop
if method.Body.Instructions[i+j].OpCode == OpCodes.Brfalse:
method.Body.Instructions[i+j].OpCode = OpCodes.Nop
return dn_module
Argument Parsing
Obtain arguments from command line using flags/markers rather than the specific order.
This allows you to use args.file
instead of sys.argv[1]
and so on.
import argparse
parser = argparse.ArgumentParser(description="Extract config from AgentTesla Payloads")
parser.add_argument('-f', '--file', required=False, help="Path to agentTesla file")
parser.add_argument('-d', '--dnlib', required=False, default="dnlib.dll",help="Path to dnlib file")
parser.add_argument('--allstrings', required=False, default=False, help="print all strings for files, not just c2")
args = parser.parse_args()