diff --git a/de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs b/de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs
index 9cfdb886..6eb461aa 100644
--- a/de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs
+++ b/de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs
@@ -17,17 +17,25 @@ You should have received a copy of the GNU General Public License
along with de4dot. If not, see .
*/
+using System;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip.Compression;
namespace de4dot.code.deobfuscators.Babel_NET {
class BabelInflater : Inflater {
- int magic;
+ static readonly int[] DefaultMapping = new int[] { 1, 5, 6 }; // STORED_BLOCK (0), STATIC_TREES (1), DYN_TREES (2)
+ readonly int _typeSize;
+ readonly int _magic;
+ readonly int[] _blockTypeMapping;
- public BabelInflater(bool noHeader, int magic) : base(noHeader) => this.magic = magic;
+ public BabelInflater(bool noHeader, int typeSize, int magic, int[] blockTypeMapping) : base(noHeader) {
+ _typeSize = typeSize;
+ _magic = magic;
+ _blockTypeMapping = blockTypeMapping ?? DefaultMapping;
+ }
protected override bool ReadHeader(ref bool isLastBlock, out int blockType) {
- const int numBits = 4;
+ int numBits = 1 + _typeSize;
int type = input.PeekBits(numBits);
if (type < 0) {
@@ -38,12 +46,11 @@ protected override bool ReadHeader(ref bool isLastBlock, out int blockType) {
if ((type & 1) != 0)
isLastBlock = true;
- switch (type >> 1) {
- case 1: blockType = STORED_BLOCK; break;
- case 5: blockType = STATIC_TREES; break;
- case 6: blockType = DYN_TREES; break;
- default: throw new SharpZipBaseException("Unknown block type: " + type);
- }
+
+ blockType = Array.IndexOf(_blockTypeMapping, type >> 1);
+ if (blockType == -1)
+ throw new SharpZipBaseException("Unknown block type: " + type);
+
return true;
}
@@ -52,7 +59,7 @@ protected override bool DecodeStoredLength() {
return false;
input.DropBits(16);
- uncomprLen ^= magic;
+ uncomprLen ^= _magic;
return true;
}
diff --git a/de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs b/de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs
index dd669c87..26027b09 100644
--- a/de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs
+++ b/de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs
@@ -37,7 +37,7 @@ public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef
public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef decrypterType, Action fixMethod) {
foreach (var method in decrypterType.Methods) {
- if (!DotNetUtils.IsMethod(method, "System.String", "()"))
+ if (!DotNetUtils.IsMethod(method, "System.String", "()") && !DotNetUtils.IsMethod(method, "System.String", "(System.Int32)"))
continue;
if (!method.IsStatic)
continue;
@@ -66,9 +66,18 @@ static EmbeddedResource FindEmbeddedResource2(ModuleDefMD module, MethodDef meth
if (!GetXorKey2(method, out int xorKey))
return null;
+ int xorKeyArg = 0;
+ foreach (var methodCaller in method.DeclaringType.Methods) {
+ if (!methodCaller.HasBody) continue;
+
+ var insns = methodCaller.Body.Instructions;
+ if (GetXorKeyArgument(methodCaller.Body.Instructions, ref xorKeyArg))
+ break;
+ }
+
var sb = new StringBuilder(encryptedString.Length);
foreach (var c in encryptedString)
- sb.Append((char)(c ^ xorKey));
+ sb.Append((char)(c ^ xorKey ^ xorKeyArg));
return DotNetUtils.GetResource(module, sb.ToString()) as EmbeddedResource;
}
@@ -94,6 +103,18 @@ static bool GetXorKey2(MethodDef method, out int xorKey) {
return false;
}
+ static bool GetXorKeyArgument(IList insns, ref int xorKey) {
+ for (var i = 0; i < insns.Count - 4; i++) {
+ if (insns[i].OpCode == OpCodes.Ldsfld && insns[i + 1].IsLdcI4()
+ && insns[i + 2].OpCode == OpCodes.Callvirt
+ && insns[i + 3].OpCode == OpCodes.Callvirt) {
+ xorKey = insns[i + 1].GetLdcI4Value();
+ return true;
+ }
+ }
+ return false;
+ }
+
public static bool FindRegisterMethod(TypeDef type, out MethodDef regMethod, out MethodDef handler) {
foreach (var method in type.Methods) {
if (!method.IsStatic || method.Body == null)
@@ -114,6 +135,8 @@ public static bool FindRegisterMethod(TypeDef type, out MethodDef regMethod, out
handler = DotNetUtils.GetMethod(type, handlerRef);
if (handler == null)
continue;
+ if (handler.Body != null && handler.Body.Instructions.Count == 4 && handler.Body.Instructions[2].OpCode.Code == Code.Call)
+ handler = (MethodDef)handler.Body.Instructions[2].Operand;
if (handler.Body == null || handler.Body.ExceptionHandlers.Count != 1)
continue;
diff --git a/de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs b/de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs
index 91e1bc43..323da3be 100644
--- a/de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs
+++ b/de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs
@@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
along with de4dot. If not, see .
*/
+using System.Collections.Generic;
+using System.Linq;
using ICSharpCode.SharpZipLib.Zip.Compression;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
@@ -24,18 +26,22 @@ You should have received a copy of the GNU General Public License
namespace de4dot.code.deobfuscators.Babel_NET {
class InflaterCreator {
- public static Inflater Create(MethodDef method, bool noHeader) => Create(FindInflaterType(method), noHeader);
+ public static Inflater Create(MethodDef method, ISimpleDeobfuscator deobfuscator, bool noHeader) => Create(FindInflaterType(method), deobfuscator, noHeader);
- public static Inflater Create(TypeDef inflaterType, bool noHeader) {
+ public static Inflater Create(TypeDef inflaterType, ISimpleDeobfuscator deobfuscator, bool noHeader) {
if (inflaterType == null)
return CreateNormal(noHeader);
var initHeaderMethod = FindInitHeaderMethod(inflaterType);
if (initHeaderMethod == null)
return CreateNormal(noHeader, "Could not find inflater init header method");
+ deobfuscator.Deobfuscate(initHeaderMethod);
var magic = GetMagic(initHeaderMethod);
if (!magic.HasValue)
return CreateNormal(noHeader);
- return new BabelInflater(noHeader, magic.Value);
+
+ var mapping = GetBlockTypeMapping(initHeaderMethod);
+ var size = GetTypeSize(initHeaderMethod);
+ return new BabelInflater(noHeader, size ?? 3, magic.Value, mapping);
}
static Inflater CreateNormal(bool noHeader) => CreateNormal(noHeader, null);
@@ -113,5 +119,84 @@ static MethodDef FindInitHeaderMethod2(TypeDef nested) {
return null;
}
+
+ ///
+ /// Analyzes a chain of "if (this.blockType == x)" checks in the initHeader method.
+ ///
+ /// initHeader method
+ /// An array with 3 values corresponding to STORED_BLOCK, STATIC_TREES and DYN_TREES; null if analysis failed.
+ static int[] GetBlockTypeMapping(MethodDef method) {
+ if (!method.HasBody || !method.Body.HasInstructions)
+ return null;
+
+ var instrs = method.Body.Instructions;
+ var blocks = new List<(Instruction Ldarg, Instruction Target, int Value)>();
+ for (int i = 0; i < instrs.Count - 3; i++) {
+ var i0 = instrs[i]; // load "this"
+ var i1 = instrs[i + 1]; // field load for blockType
+ var i2 = instrs[i + 2]; // type value to check against
+ var i3 = instrs[i + 3]; // bne to next check
+
+ if (!i0.IsLdarg()) continue;
+ if (i1.OpCode.Code != Code.Ldfld) continue;
+ if (!i2.IsLdcI4()) continue;
+ if (i3.OpCode.Code is not (Code.Bne_Un or Code.Bne_Un_S)) continue;
+ if (i3.Operand is not Instruction target) continue;
+
+ blocks.Add((i0, target, i2.GetLdcI4Value()));
+ }
+
+ if (blocks.Count != 3)
+ return null;
+
+ // Each block starts with ldarg; create a mapping ldarg -> block
+ var map = blocks.ToDictionary(b => b.Ldarg);
+
+ // Find start of chain (where ldarg is not a target of any other)
+ var start = blocks.FirstOrDefault(b => !blocks.Any(x => x.Target == b.Ldarg));
+ if (start.Ldarg == null)
+ return null;
+
+ var result = new List();
+ var current = start;
+ var visited = new HashSet();
+
+ while (true) {
+ if (!visited.Add(current.Ldarg))
+ return null; // cycle, shouldn't happen
+
+ result.Add(current.Value);
+
+ if (!map.TryGetValue(current.Target, out var next))
+ break; // last bne.un ends the chain
+
+ current = next;
+ }
+
+ return result.Count == 3 ? result.ToArray() : null;
+ }
+
+ ///
+ /// Determines the size (in bits) of the block type. This does not include the "last block" bit.
+ ///
+ /// initHeader method
+ /// Number of bits or null
+ static int? GetTypeSize(MethodDef method) {
+ if (method == null || method.Body == null)
+ return null;
+ var instrs = method.Body.Instructions;
+ for (int i = 0; i < instrs.Count - 6; i++) {
+ if (!instrs[i].IsLdarg()) continue;
+ if (!instrs[i + 1].IsLdarg()) continue;
+ if (instrs[i + 2].OpCode != OpCodes.Ldfld) continue;
+ if (!instrs[i + 3].IsLdcI4()) continue;
+ if (instrs[i + 4].OpCode != OpCodes.Callvirt) continue;
+ if (instrs[i + 5].OpCode != OpCodes.Stfld) continue;
+
+ return instrs[i + 3].GetLdcI4Value();
+ }
+
+ return null;
+ }
}
}
diff --git a/de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs b/de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs
index 14893fd3..f00da1b8 100644
--- a/de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs
+++ b/de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs
@@ -147,9 +147,9 @@ class Decrypter3 : IDecrypter {
ModuleDefMD module;
Inflater inflater;
- public Decrypter3(ModuleDefMD module, MethodDef decryptMethod) {
+ public Decrypter3(ModuleDefMD module, MethodDef decryptMethod, ISimpleDeobfuscator deobfuscator) {
this.module = module;
- inflater = InflaterCreator.Create(decryptMethod, true);
+ inflater = InflaterCreator.Create(decryptMethod, deobfuscator, true);
}
public byte[] Decrypt(byte[] encryptedData) {
@@ -199,6 +199,80 @@ bool GetKeyIv(byte[] headerData, out byte[] key, out byte[] iv) {
}
}
+ // v10/v11
+ class Decrypter4 : IDecrypter {
+ ModuleDefMD module;
+ Inflater inflater;
+
+ public Decrypter4(ModuleDefMD module, MethodDef decryptMethod, ISimpleDeobfuscator deobfuscator) {
+ this.module = module;
+ inflater = InflaterCreator.Create(decryptMethod, deobfuscator, true);
+ }
+
+ public byte[] Decrypt(byte[] encryptedData) {
+ int index = 0;
+ ParseHeader(GetHeaderData(encryptedData, ref index, out var iv),
+ out var key,
+ out var flag,
+ out var cipherType);
+ bool isEncrypted = (flag & 2) != 0;
+ bool isCompressed = (flag & 1) != 0;
+
+ byte[] data = new byte[encryptedData.Length - index];
+ Array.Copy(encryptedData, index, data, 0, encryptedData.Length - index);
+
+ if (isEncrypted) {
+ if (cipherType == 1)
+ data = DeobUtils.DesDecrypt(data, 0, data.Length, key, iv);
+ else if (cipherType is 2 or 4)
+ data = DeobUtils.AesDecrypt(data, key, iv);
+ else if (cipherType == 3)
+ data = DeobUtils.Des3Decrypt(data, key, iv);
+ else
+ throw new Exception($"Unsupported cipher type {cipherType}");
+ }
+
+ if (isCompressed) {
+ data = DeobUtils.Inflate(data, inflater);
+ }
+
+ return data;
+ }
+
+ byte[] GetHeaderData(byte[] encryptedData, ref int index, out byte[] iv) {
+ var headerData = new byte[BitConverter.ToUInt16(encryptedData, index)];
+ Array.Copy(encryptedData, index + 2, headerData, 0, headerData.Length);
+ index += headerData.Length + 2;
+
+ iv = new byte[encryptedData[index++]];
+ Array.Copy(encryptedData, index, iv, 0, iv.Length);
+ index += iv.Length;
+ for (int i = 0; i < headerData.Length; i++)
+ headerData[i] ^= iv[i % iv.Length];
+
+ return headerData;
+ }
+
+ void ParseHeader(byte[] headerData, out byte[] key, out byte flag, out byte cipherType) {
+ var reader = new BinaryReader(new MemoryStream(headerData));
+
+ /*var license =*/ reader.ReadString();
+ flag = reader.ReadByte();
+ cipherType = reader.ReadByte();
+ byte pubKeyOffset = reader.ReadByte();
+
+ key = reader.ReadBytes(reader.ReadByte());
+ if (pubKeyOffset < 64) {
+ if (reader.BaseStream.Position < reader.BaseStream.Length)
+ throw new Exception("Expected end of header");
+ }
+ else {
+ Array.Copy(module.Assembly.PublicKey.Data, pubKeyOffset + 12, key, 0, key.Length);
+ //key[5] |= 0x80;
+ }
+ }
+ }
+
public MethodDef DecryptMethod {
set {
if (value == null)
@@ -222,7 +296,8 @@ public static MethodDef FindDecrypterMethod(MethodDef method) {
var calledMethod = instr.Operand as MethodDef;
if (calledMethod == null || !calledMethod.IsStatic || calledMethod.Body == null)
continue;
- if (!DotNetUtils.IsMethod(calledMethod, "System.IO.MemoryStream", "(System.IO.Stream)"))
+ if (!DotNetUtils.IsMethod(calledMethod, "System.IO.MemoryStream", "(System.IO.Stream)")
+ && !DotNetUtils.IsMethod(calledMethod, "System.IO.Stream", "(System.IO.Stream)"))
continue;
return calledMethod;
@@ -238,8 +313,10 @@ public byte[] Decrypt(byte[] encryptedData) {
}
IDecrypter CreateDecrypter(byte[] encryptedData) {
+ if (decryptMethod != null && DeobUtils.HasInteger(decryptMethod, 64))
+ return new Decrypter4(module, decryptMethod, simpleDeobfuscator);
if (decryptMethod != null && DeobUtils.HasInteger(decryptMethod, 6))
- return new Decrypter3(module, decryptMethod);
+ return new Decrypter3(module, decryptMethod, simpleDeobfuscator);
if (IsV30(encryptedData))
return new Decrypter1(module);
return new Decrypter2(module);
diff --git a/de4dot.code/deobfuscators/Babel_NET/StringDecrypter.cs b/de4dot.code/deobfuscators/Babel_NET/StringDecrypter.cs
index 999f50f4..d5cf8973 100644
--- a/de4dot.code/deobfuscators/Babel_NET/StringDecrypter.cs
+++ b/de4dot.code/deobfuscators/Babel_NET/StringDecrypter.cs
@@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
@@ -145,11 +146,11 @@ IDecrypterInfo CheckDecrypterType(TypeDef type) {
return null;
if (type.NestedTypes.Count > 2)
return null;
- if (type.Fields.Count > 1)
+ if (type.Fields.Count > 3)
return null;
foreach (var nested in type.NestedTypes) {
- var info = CheckNested(type, nested);
+ var info = CheckNested(type, nested) ?? CheckNested2(type, nested);
if (info != null)
return info;
}
@@ -277,11 +278,37 @@ IDecrypterInfo CheckNested(TypeDef type, TypeDef nested) {
return null;
}
+ IDecrypterInfo CheckNested2(TypeDef type, TypeDef nested) {
+ // 10.0/11.0, possibly earlier versions too
+ if (nested.HasProperties || nested.HasEvents)
+ return null;
+
+ var decrypterBuilderMethod = DotNetUtils.GetMethod(nested, "System.Reflection.Emit.MethodBuilder", "(System.Reflection.Emit.TypeBuilder)");
+ if (decrypterBuilderMethod == null)
+ return null;
+
+ var cctor = type.FindStaticConstructor();
+ if (cctor == null) return null;
+ resourceDecrypter.DecryptMethod = ResourceDecrypter.FindDecrypterMethod(cctor);
+
+ var decrypter = DotNetUtils.GetMethod(type, "System.String", "(System.Int32)");
+ if (decrypter is not { IsStatic: true })
+ return null;
+
+ simpleDeobfuscator.Deobfuscate(decrypterBuilderMethod);
+ return new DecrypterInfoV3(resourceDecrypter) {
+ Decrypter = decrypter,
+ OffsetCalcInstructions = GetOffsetCalcInstructions(decrypterBuilderMethod),
+ };
+ }
+
class ReflectionToDNLibMethodCreator {
MethodDef method;
List instructions = new List();
InstructionEmulator emulator;
int index;
+ private int _skipSwitchLoopIndex = -1;
+ private byte[] _xorValues;
class UserValue : UnknownValue {
public readonly object obj;
@@ -294,10 +321,41 @@ public override string ToString() {
}
public List Instructions => instructions;
+ public int ParsedValue { get; private set; }
public ReflectionToDNLibMethodCreator(MethodDef method) {
this.method = method;
emulator = new InstructionEmulator(method);
+ AnalyzeSwitch();
+ }
+
+ private void AnalyzeSwitch() {
+ var switchIns = method.Body.Instructions.FirstOrDefault(ins => ins.OpCode.Code == Code.Switch);
+ if (switchIns == null)
+ return;
+
+ for (int i = 0; i < method.Body.Instructions.IndexOf(switchIns); i++) {
+ if (method.Body.Instructions[i].OpCode.Code is Code.Bge or Code.Bge_S) {
+ _skipSwitchLoopIndex = method.Body.Instructions.IndexOf((Instruction)method.Body.Instructions[i].Operand);
+ break;
+ }
+ }
+ if (_skipSwitchLoopIndex == -1)
+ throw new Exception("decrypterBuilderMethod analysis failed");
+
+ int x = 0;
+ _xorValues = new byte[((Instruction[])switchIns.Operand).Length];
+ foreach (var targetIns in (Instruction[])switchIns.Operand) {
+ _xorValues[x++] = (byte)GetSwitchXor(method.Body.Instructions.IndexOf(targetIns));
+ }
+ }
+
+ private int GetSwitchXor(int caseStart) {
+ for (int i = caseStart; i < Math.Min(caseStart + 10, method.Body.Instructions.Count); i++) {
+ if (method.Body.Instructions[i].IsLdcI4())
+ return method.Body.Instructions[i].GetLdcI4Value();
+ }
+ throw new Exception("Ldc.i4 not found in switch case");
}
public bool Create() {
@@ -364,6 +422,20 @@ public bool Create() {
emulator.Push(new UserValue((IField)instr.Operand));
break;
+ case Code.Switch:
+ emulator.Pop();
+ if (_xorValues != null) {
+ // Directly handle the xoring logic here and skip it.
+ array = emulator.GetLocal(1);
+ if (array is UserValue { obj: byte[] b }) {
+ for (int i = 0; i < b.Length; i++) {
+ b[i] ^= _xorValues[i % _xorValues.Length];
+ }
+ }
+ index = _skipSwitchLoopIndex;
+ }
+ break;
+
default:
emulator.Emulate(instr);
break;
@@ -386,7 +458,8 @@ bool DoCall(Instruction instr) {
return true;
}
else if (fn == "System.Int32 System.Int32::Parse(System.String)") {
- emulator.Push(new Int32Value(int.Parse(((StringValue)emulator.Pop()).value)));
+ ParsedValue = int.Parse(((StringValue)emulator.Pop()).value);
+ emulator.Push(new Int32Value(ParsedValue));
return true;
}
else if (fn == "System.String[] System.String::Split(System.Char[])") {
@@ -449,8 +522,14 @@ static List GetOffsetCalcInstructions(MethodDef method) {
int endInstr = index - 1;
var transformInstructions = new List();
- for (int i = startInstr; i <= endInstr; i++)
- transformInstructions.Add(instrs[i]);
+ for (int i = startInstr; i <= endInstr; i++) {
+ if (instrs[i].IsLdloc() && creator.ParsedValue != 0) {
+ transformInstructions.Add(OpCodes.Ldc_I4.ToInstruction(creator.ParsedValue));
+ }
+ else
+ transformInstructions.Add(instrs[i]);
+ }
+
return transformInstructions;
}
@@ -533,7 +612,7 @@ public void Initialize() {
return;
if (decrypterInfo.NeedsResource) {
- encryptedResource = BabelUtils.FindEmbeddedResource(module, decrypterType);
+ encryptedResource = BabelUtils.FindEmbeddedResource(module, decrypterType, method => simpleDeobfuscator.Deobfuscate(method));
if (encryptedResource == null)
return;
}