AgentTesla
Some dot net exploration featuring agenttesla
Overview
Our goal is to write a static strings table decryptor for this AgentTesla variant. The strings are stored in a giant INT array so our goals are twofold.
- statically identify .NET code sections using opcodes and extract some values from the code
- statically identify the location of large int arrays in .NET binaries
The obfuscator used is called obfuscar and it looks to be responsible for the strings encryption ref.
Note: After our analysis it was brought to my attention two existing string decryption solutions already exist for this obfuscator. They are listed below.
Samples (unpacked)
-
20f4ec03549be469302c0fcb5f55307680fd189aa733f90eb59cb2fbc34317cc
malshare -
cb3afdb1e17d5bdaf641e633434ac71855e5dcfdd21d66a565f0dc9844d30030
malshare
References
data=[152,155,209,208,215,214,129,224,239,142,196,197,134,239,236,159,215,214,130,202,205,198,197,196,203,236,253,252,233,211,208,234,194,195,215,228,227,208,0xff,254,190]
out = ''
for i in range(len(data)):
out += chr(data[i] ^ (i & 0xff) ^ 170)
out
Locate The Array
Byte Matching Regex To Locate The Array
We could possibly locate the array based on the fact that it is huge! Not many giant arrays in this malware.
04 20 B1 2E 00 00 //ldc.i4 11953
8D 2B 00 00 01 // newarr [mscorlib]System.Byte
The drawbacks are that we will still need to parse the .NET to find the token with the data offset to the actual array being passed to RuntimeHelpers::InitializeArray
.
.NET Parsing To Locate The Array
TARGET_PATH = '/tmp/at.bin'
file_data = open(TARGET_PATH,'rb').read()
import pefile
import sys, struct, clr
clr.AddReference("System.Memory")
from System.Reflection import Assembly, MethodInfo, BindingFlags
from System import Type
DNLIB_PATH = '/tmp/dnlib.dll'
clr.AddReference(DNLIB_PATH)
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
module = dnlib.DotNet.ModuleDefMD.Load(TARGET_PATH)
for mtype in module.GetTypes():
if not mtype.HasMethods:
continue
for method in mtype.Methods:
if not method.HasBody:
continue
if not method.Body.HasInstructions:
continue
if len(method.Body.Instructions) < 20:
continue
key_set = False
block_set = False
for ptr in range(20):
if "RuntimeHelpers::InitializeArray" in method.Body.Instructions[ptr].ToString():
arr_inst = method.Body.Instructions[ptr-1]
break
dir(arr_inst)
dir(arr_inst.Operand)
print(hex(arr_inst.Operand.RVA))
print(hex(arr_inst.Operand.GetFieldSize()))
pe = pefile.PE(data=file_data, fast_load=True)
hex(pe.get_offset_from_rva(0x260b8))
target_module = dnlib.DotNet.ModuleDefMD.Load(TARGET_PATH)
def pct_ascii(s):
return len([c for c in s if c < 128 or c == 0]) / len(s)
def decrypt(data, key):
out = []
for i in range(len(data)):
out.append((data[i] ^ i ^ key) & 0xff)
return bytes(out)
def get_strings_table(target_module, pe):
out = []
keys = []
for mtype in target_module.GetTypes():
if not mtype.HasMethods:
continue
for method in mtype.Methods:
# The string decryption happens in a constructor
if not method.IsConstructor:
continue
if not method.HasBody:
continue
if not method.Body.HasInstructions:
continue
if len(method.Body.Instructions) < 30:
continue
key_set = False
block_set = False
key_flag = False
for ptr in range(30):
if "RuntimeHelpers::InitializeArray" in method.Body.Instructions[ptr].ToString():
arr_inst = method.Body.Instructions[ptr-1]
arr_rva = arr_inst.Operand.RVA
arr_size = arr_inst.Operand.GetFieldSize()
out.append(pe.get_data(arr_rva, arr_size))
key_flag = True
if key_flag:
if "xor" in method.Body.Instructions[ptr].ToString() and "ldc.i4" in method.Body.Instructions[ptr - 1].ToString():
keys.append(method.Body.Instructions[ptr - 1].Operand)
if len(out) == 0:
return None
arr_data = max(out, key=len)
# For each key try to decrypt and save the one that
# decrypt to valid ascii
ptxt_data = None
for key in keys:
tmp_out = decrypt(arr_data, key)
if pct_ascii(tmp_out) > 0.8:
ptxt_data = tmp_out
break
return ptxt_data
strings_table = get_strings_table(target_module, pe)
strings_table
# 0x0001F0CB 202D080000 */ IL_0014: ldc.i4 2093 // offset
# 0x0001F0D0 1F09 */ IL_0019: ldc.i4.s 9 // size
# 0x0001F0D2 282D020006 */ IL_001B: call string
# offset,size
str_offsets = []
for mtype in target_module.GetTypes():
if not mtype.HasMethods:
continue
for method in mtype.Methods:
# The get string functions are public and return a string
if not method.IsPublic:
continue
if method.ReturnType.ToString() != "System.String":
continue
if not method.HasBody:
continue
if not method.Body.HasInstructions:
continue
if len(method.Body.Instructions) < 10:
continue
key_set = False
block_set = False
key_flag = False
for ptr in range(10):
if "call System.String" in method.Body.Instructions[ptr].ToString():
if "ldc" in method.Body.Instructions[ptr-1].ToString() and \
"ldc" in method.Body.Instructions[ptr-2].ToString() and \
"ldc" in method.Body.Instructions[ptr-3].ToString():
if method.Body.Instructions[ptr-1].Operand is None:
str_size = int(method.Body.Instructions[ptr-1].ToString().split('.')[-1])
else:
str_size = method.Body.Instructions[ptr-1].Operand
if method.Body.Instructions[ptr-2].Operand is None:
str_offset = int(method.Body.Instructions[ptr-2].ToString().split('.')[-1])
else:
str_offset = method.Body.Instructions[ptr-2].Operand
str_offsets.append((str_offset, str_size))
str_offsets
strings = []
for offset_info in str_offsets:
strings.append(strings_table[offset_info[0]:offset_info[0]+offset_info[1]])
strings