Skip to content

Instantly share code, notes, and snippets.

@cmeslay
Last active April 15, 2026 12:24
Show Gist options
  • Select an option

  • Save cmeslay/9ff499776ca2c2d84890c30a16001c20 to your computer and use it in GitHub Desktop.

Select an option

Save cmeslay/9ff499776ca2c2d84890c30a16001c20 to your computer and use it in GitHub Desktop.
Script used to automatically decrypt strings in some Covenant samples of APT28
# pip install pythonnet (works with python < 3.14)
# You can test this script with the following file: 69609e89b04d8d27dc47bda2971376cfd760abb40ffe325f00d0cf3303be8906
# https://bazaar.abuse.ch/sample/69609e89b04d8d27dc47bda2971376cfd760abb40ffe325f00d0cf3303be8906/
import clr,sys
import binascii
import re
# Add dlls to reverse .NET. You could find them in REPythonNET-MCP projects (into the `dll/`` directory)
clr.AddReference("dnlib")
import base64
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
from dnlib.DotNet.Writer import ModuleWriterOptions
module = dnlib.DotNet.ModuleDefMD.Load("/tmp/input.dll")
signature = ["nop", "ldloc.0", "ldarg.0", "ldloc.1", "ldelem.u1", "ldsfld", "ldloc.1", "ldsfld", "callvirt", "rem", "callvirt", "xor", "conv.u1", "callvirt", "nop", "nop", "ldloc.1", "ldc.i4.1", "add", "stloc.1", "ldloc.1", "ldarg.0", "ldlen", "conv.i4", "clt", "stloc.2", "ldloc.2", "brtrue.s"]
def has_config_pattern(method):
if method.HasBody:
if len(method.Body.Instructions) >= len(signature):
ins = [x.OpCode.Name for x in method.Body.Instructions]
if len(signature) > len(ins):
return False
for i in range(len(ins) - len(signature) + 1):
if ins[i:i+len(signature)] == signature:
return True
return False
def xor_with_repeating_key(data: bytes, key: bytes) -> bytes:
"""
XOR data with key
"""
if not key:
raise ValueError("Key empty")
return bytes(data[i] ^ key[i % len(key)] for i in range(len(data)))
def decrypt_str(encrypted_str, key):
tmp = binascii.a2b_base64(encrypted_str)
tmp = xor_with_repeating_key(tmp, key.encode("utf-8"))
return tmp.decode("utf-8")
XorFunction = ""
KeyName = ""
KeyClassName = ""
for t in module.GetTypes():
for m in t.Methods:
if not m.HasBody:
continue
if has_config_pattern(m):
XorFunction = m.Name
for instr in m.Body.Instructions:
if instr.OpCode.Name == "ldsfld":
KeyName = instr.Operand.Name
KeyClassName = instr.Operand.get_DeclaringType()
break
print("XorFunction: {}".format(XorFunction))
print("KeyName {}".format(KeyName))
print("KeyClassName {}".format(KeyClassName))
# find the class in the assembly
decryption_class = next(t for t in module.GetTypes() if t.FullName == KeyClassName.FullName)
# find the class in the constructor
cctor = next((m for m in decryption_class.Methods if m.Name == ".cctor"), None)
# Find the key initialization and get the value
found = False
for instr in cctor.Body.Instructions:
if found and instr and instr.OpCode == OpCodes.Stsfld and instr.Operand.Name == KeyName:
break
elif instr.OpCode == OpCodes.Ldstr:
found = True
key = instr.Operand
else:
found = False
print("KeyValue : {}".format(key))
# Find the calling function inside the class:
DecryptionFunction = ""
for m in decryption_class.Methods:
for instr in m.Body.Instructions:
if instr.OpCode.Name == "call":
if instr.Operand.Name == XorFunction:
DecryptionFunction = m.Name
print("DecryptionFunction: {}".format(DecryptionFunction))
# Now we can find calls to the decryption function:
# - modify the opcode of the previous instruction by the decrypted string
# - modify the opcode of the current instruction (call) by a nop
previous_instr = None
for t in module.GetTypes():
for m in t.Methods:
if not m.HasBody:
continue
for instr in m.Body.Instructions:
if instr.OpCode.Name == "ldstr":
previous_instr = instr
elif instr.OpCode.Name == "call" and instr.Operand.Name == DecryptionFunction:
decrypted_str = decrypt_str(previous_instr.Operand, key)
print("{} decrypted to {}".format(previous_instr.Operand, decrypted_str))
previous_instr.Operand = decrypted_str
instr.OpCode = OpCodes.Nop
else:
previous_instr = None
opts = ModuleWriterOptions(module)
opts.Logger = None
module.Write("/tmp/output.dll", opts)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment