Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ You should have received a copy of the GNU General Public License
along with de4dot. If not, see <http://www.gnu.org/licenses/>.
*/

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) {
Expand All @@ -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;
}

Expand All @@ -52,7 +59,7 @@ protected override bool DecodeStoredLength() {
return false;
input.DropBits(16);

uncomprLen ^= magic;
uncomprLen ^= _magic;

return true;
}
Expand Down
27 changes: 25 additions & 2 deletions de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef

public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef decrypterType, Action<MethodDef> 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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -94,6 +103,18 @@ static bool GetXorKey2(MethodDef method, out int xorKey) {
return false;
}

static bool GetXorKeyArgument(IList<Instruction> 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)
Expand All @@ -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;

Expand Down
91 changes: 88 additions & 3 deletions de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,31 @@ You should have received a copy of the GNU General Public License
along with de4dot. If not, see <http://www.gnu.org/licenses/>.
*/

using System.Collections.Generic;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip.Compression;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using de4dot.blocks;

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);
Expand Down Expand Up @@ -113,5 +119,84 @@ static MethodDef FindInitHeaderMethod2(TypeDef nested) {

return null;
}

/// <summary>
/// Analyzes a chain of "if (this.blockType == x)" checks in the initHeader method.
/// </summary>
/// <param name="method">initHeader method</param>
/// <returns>An array with 3 values corresponding to STORED_BLOCK, STATIC_TREES and DYN_TREES; null if analysis failed.</returns>
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<int>();
var current = start;
var visited = new HashSet<Instruction>();

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;
}

/// <summary>
/// Determines the size (in bits) of the block type. This does not include the "last block" bit.
/// </summary>
/// <param name="method">initHeader method</param>
/// <returns>Number of bits or null</returns>
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;
}
}
}
85 changes: 81 additions & 4 deletions de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand All @@ -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);
Expand Down
Loading
Loading