Skip to content

Commit 974d254

Browse files
authored
Merge pull request #27 from GDATAAdvancedAnalytics/update-babel
Babel.NET: Fix string and resource decryption for v10/v11
2 parents 88b7662 + f89d423 commit 974d254

5 files changed

Lines changed: 296 additions & 25 deletions

File tree

de4dot.code/deobfuscators/Babel_NET/BabelInflater.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,25 @@ You should have received a copy of the GNU General Public License
1717
along with de4dot. If not, see <http://www.gnu.org/licenses/>.
1818
*/
1919

20+
using System;
2021
using ICSharpCode.SharpZipLib;
2122
using ICSharpCode.SharpZipLib.Zip.Compression;
2223

2324
namespace de4dot.code.deobfuscators.Babel_NET {
2425
class BabelInflater : Inflater {
25-
int magic;
26+
static readonly int[] DefaultMapping = new int[] { 1, 5, 6 }; // STORED_BLOCK (0), STATIC_TREES (1), DYN_TREES (2)
27+
readonly int _typeSize;
28+
readonly int _magic;
29+
readonly int[] _blockTypeMapping;
2630

27-
public BabelInflater(bool noHeader, int magic) : base(noHeader) => this.magic = magic;
31+
public BabelInflater(bool noHeader, int typeSize, int magic, int[] blockTypeMapping) : base(noHeader) {
32+
_typeSize = typeSize;
33+
_magic = magic;
34+
_blockTypeMapping = blockTypeMapping ?? DefaultMapping;
35+
}
2836

2937
protected override bool ReadHeader(ref bool isLastBlock, out int blockType) {
30-
const int numBits = 4;
38+
int numBits = 1 + _typeSize;
3139

3240
int type = input.PeekBits(numBits);
3341
if (type < 0) {
@@ -38,12 +46,11 @@ protected override bool ReadHeader(ref bool isLastBlock, out int blockType) {
3846

3947
if ((type & 1) != 0)
4048
isLastBlock = true;
41-
switch (type >> 1) {
42-
case 1: blockType = STORED_BLOCK; break;
43-
case 5: blockType = STATIC_TREES; break;
44-
case 6: blockType = DYN_TREES; break;
45-
default: throw new SharpZipBaseException("Unknown block type: " + type);
46-
}
49+
50+
blockType = Array.IndexOf(_blockTypeMapping, type >> 1);
51+
if (blockType == -1)
52+
throw new SharpZipBaseException("Unknown block type: " + type);
53+
4754
return true;
4855
}
4956

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

55-
uncomprLen ^= magic;
62+
uncomprLen ^= _magic;
5663

5764
return true;
5865
}

de4dot.code/deobfuscators/Babel_NET/BabelUtils.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef
3737

3838
public static EmbeddedResource FindEmbeddedResource(ModuleDefMD module, TypeDef decrypterType, Action<MethodDef> fixMethod) {
3939
foreach (var method in decrypterType.Methods) {
40-
if (!DotNetUtils.IsMethod(method, "System.String", "()"))
40+
if (!DotNetUtils.IsMethod(method, "System.String", "()") && !DotNetUtils.IsMethod(method, "System.String", "(System.Int32)"))
4141
continue;
4242
if (!method.IsStatic)
4343
continue;
@@ -66,9 +66,18 @@ static EmbeddedResource FindEmbeddedResource2(ModuleDefMD module, MethodDef meth
6666
if (!GetXorKey2(method, out int xorKey))
6767
return null;
6868

69+
int xorKeyArg = 0;
70+
foreach (var methodCaller in method.DeclaringType.Methods) {
71+
if (!methodCaller.HasBody) continue;
72+
73+
var insns = methodCaller.Body.Instructions;
74+
if (GetXorKeyArgument(methodCaller.Body.Instructions, ref xorKeyArg))
75+
break;
76+
}
77+
6978
var sb = new StringBuilder(encryptedString.Length);
7079
foreach (var c in encryptedString)
71-
sb.Append((char)(c ^ xorKey));
80+
sb.Append((char)(c ^ xorKey ^ xorKeyArg));
7281
return DotNetUtils.GetResource(module, sb.ToString()) as EmbeddedResource;
7382
}
7483

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

106+
static bool GetXorKeyArgument(IList<Instruction> insns, ref int xorKey) {
107+
for (var i = 0; i < insns.Count - 4; i++) {
108+
if (insns[i].OpCode == OpCodes.Ldsfld && insns[i + 1].IsLdcI4()
109+
&& insns[i + 2].OpCode == OpCodes.Callvirt
110+
&& insns[i + 3].OpCode == OpCodes.Callvirt) {
111+
xorKey = insns[i + 1].GetLdcI4Value();
112+
return true;
113+
}
114+
}
115+
return false;
116+
}
117+
97118
public static bool FindRegisterMethod(TypeDef type, out MethodDef regMethod, out MethodDef handler) {
98119
foreach (var method in type.Methods) {
99120
if (!method.IsStatic || method.Body == null)
@@ -114,6 +135,8 @@ public static bool FindRegisterMethod(TypeDef type, out MethodDef regMethod, out
114135
handler = DotNetUtils.GetMethod(type, handlerRef);
115136
if (handler == null)
116137
continue;
138+
if (handler.Body != null && handler.Body.Instructions.Count == 4 && handler.Body.Instructions[2].OpCode.Code == Code.Call)
139+
handler = (MethodDef)handler.Body.Instructions[2].Operand;
117140
if (handler.Body == null || handler.Body.ExceptionHandlers.Count != 1)
118141
continue;
119142

de4dot.code/deobfuscators/Babel_NET/InflaterCreator.cs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,31 @@ You should have received a copy of the GNU General Public License
1717
along with de4dot. If not, see <http://www.gnu.org/licenses/>.
1818
*/
1919

20+
using System.Collections.Generic;
21+
using System.Linq;
2022
using ICSharpCode.SharpZipLib.Zip.Compression;
2123
using dnlib.DotNet;
2224
using dnlib.DotNet.Emit;
2325
using de4dot.blocks;
2426

2527
namespace de4dot.code.deobfuscators.Babel_NET {
2628
class InflaterCreator {
27-
public static Inflater Create(MethodDef method, bool noHeader) => Create(FindInflaterType(method), noHeader);
29+
public static Inflater Create(MethodDef method, ISimpleDeobfuscator deobfuscator, bool noHeader) => Create(FindInflaterType(method), deobfuscator, noHeader);
2830

29-
public static Inflater Create(TypeDef inflaterType, bool noHeader) {
31+
public static Inflater Create(TypeDef inflaterType, ISimpleDeobfuscator deobfuscator, bool noHeader) {
3032
if (inflaterType == null)
3133
return CreateNormal(noHeader);
3234
var initHeaderMethod = FindInitHeaderMethod(inflaterType);
3335
if (initHeaderMethod == null)
3436
return CreateNormal(noHeader, "Could not find inflater init header method");
37+
deobfuscator.Deobfuscate(initHeaderMethod);
3538
var magic = GetMagic(initHeaderMethod);
3639
if (!magic.HasValue)
3740
return CreateNormal(noHeader);
38-
return new BabelInflater(noHeader, magic.Value);
41+
42+
var mapping = GetBlockTypeMapping(initHeaderMethod);
43+
var size = GetTypeSize(initHeaderMethod);
44+
return new BabelInflater(noHeader, size ?? 3, magic.Value, mapping);
3945
}
4046

4147
static Inflater CreateNormal(bool noHeader) => CreateNormal(noHeader, null);
@@ -113,5 +119,84 @@ static MethodDef FindInitHeaderMethod2(TypeDef nested) {
113119

114120
return null;
115121
}
122+
123+
/// <summary>
124+
/// Analyzes a chain of "if (this.blockType == x)" checks in the initHeader method.
125+
/// </summary>
126+
/// <param name="method">initHeader method</param>
127+
/// <returns>An array with 3 values corresponding to STORED_BLOCK, STATIC_TREES and DYN_TREES; null if analysis failed.</returns>
128+
static int[] GetBlockTypeMapping(MethodDef method) {
129+
if (!method.HasBody || !method.Body.HasInstructions)
130+
return null;
131+
132+
var instrs = method.Body.Instructions;
133+
var blocks = new List<(Instruction Ldarg, Instruction Target, int Value)>();
134+
for (int i = 0; i < instrs.Count - 3; i++) {
135+
var i0 = instrs[i]; // load "this"
136+
var i1 = instrs[i + 1]; // field load for blockType
137+
var i2 = instrs[i + 2]; // type value to check against
138+
var i3 = instrs[i + 3]; // bne to next check
139+
140+
if (!i0.IsLdarg()) continue;
141+
if (i1.OpCode.Code != Code.Ldfld) continue;
142+
if (!i2.IsLdcI4()) continue;
143+
if (i3.OpCode.Code is not (Code.Bne_Un or Code.Bne_Un_S)) continue;
144+
if (i3.Operand is not Instruction target) continue;
145+
146+
blocks.Add((i0, target, i2.GetLdcI4Value()));
147+
}
148+
149+
if (blocks.Count != 3)
150+
return null;
151+
152+
// Each block starts with ldarg; create a mapping ldarg -> block
153+
var map = blocks.ToDictionary(b => b.Ldarg);
154+
155+
// Find start of chain (where ldarg is not a target of any other)
156+
var start = blocks.FirstOrDefault(b => !blocks.Any(x => x.Target == b.Ldarg));
157+
if (start.Ldarg == null)
158+
return null;
159+
160+
var result = new List<int>();
161+
var current = start;
162+
var visited = new HashSet<Instruction>();
163+
164+
while (true) {
165+
if (!visited.Add(current.Ldarg))
166+
return null; // cycle, shouldn't happen
167+
168+
result.Add(current.Value);
169+
170+
if (!map.TryGetValue(current.Target, out var next))
171+
break; // last bne.un ends the chain
172+
173+
current = next;
174+
}
175+
176+
return result.Count == 3 ? result.ToArray() : null;
177+
}
178+
179+
/// <summary>
180+
/// Determines the size (in bits) of the block type. This does not include the "last block" bit.
181+
/// </summary>
182+
/// <param name="method">initHeader method</param>
183+
/// <returns>Number of bits or null</returns>
184+
static int? GetTypeSize(MethodDef method) {
185+
if (method == null || method.Body == null)
186+
return null;
187+
var instrs = method.Body.Instructions;
188+
for (int i = 0; i < instrs.Count - 6; i++) {
189+
if (!instrs[i].IsLdarg()) continue;
190+
if (!instrs[i + 1].IsLdarg()) continue;
191+
if (instrs[i + 2].OpCode != OpCodes.Ldfld) continue;
192+
if (!instrs[i + 3].IsLdcI4()) continue;
193+
if (instrs[i + 4].OpCode != OpCodes.Callvirt) continue;
194+
if (instrs[i + 5].OpCode != OpCodes.Stfld) continue;
195+
196+
return instrs[i + 3].GetLdcI4Value();
197+
}
198+
199+
return null;
200+
}
116201
}
117202
}

de4dot.code/deobfuscators/Babel_NET/ResourceDecrypter.cs

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ class Decrypter3 : IDecrypter {
147147
ModuleDefMD module;
148148
Inflater inflater;
149149

150-
public Decrypter3(ModuleDefMD module, MethodDef decryptMethod) {
150+
public Decrypter3(ModuleDefMD module, MethodDef decryptMethod, ISimpleDeobfuscator deobfuscator) {
151151
this.module = module;
152-
inflater = InflaterCreator.Create(decryptMethod, true);
152+
inflater = InflaterCreator.Create(decryptMethod, deobfuscator, true);
153153
}
154154

155155
public byte[] Decrypt(byte[] encryptedData) {
@@ -199,6 +199,80 @@ bool GetKeyIv(byte[] headerData, out byte[] key, out byte[] iv) {
199199
}
200200
}
201201

202+
// v10/v11
203+
class Decrypter4 : IDecrypter {
204+
ModuleDefMD module;
205+
Inflater inflater;
206+
207+
public Decrypter4(ModuleDefMD module, MethodDef decryptMethod, ISimpleDeobfuscator deobfuscator) {
208+
this.module = module;
209+
inflater = InflaterCreator.Create(decryptMethod, deobfuscator, true);
210+
}
211+
212+
public byte[] Decrypt(byte[] encryptedData) {
213+
int index = 0;
214+
ParseHeader(GetHeaderData(encryptedData, ref index, out var iv),
215+
out var key,
216+
out var flag,
217+
out var cipherType);
218+
bool isEncrypted = (flag & 2) != 0;
219+
bool isCompressed = (flag & 1) != 0;
220+
221+
byte[] data = new byte[encryptedData.Length - index];
222+
Array.Copy(encryptedData, index, data, 0, encryptedData.Length - index);
223+
224+
if (isEncrypted) {
225+
if (cipherType == 1)
226+
data = DeobUtils.DesDecrypt(data, 0, data.Length, key, iv);
227+
else if (cipherType is 2 or 4)
228+
data = DeobUtils.AesDecrypt(data, key, iv);
229+
else if (cipherType == 3)
230+
data = DeobUtils.Des3Decrypt(data, key, iv);
231+
else
232+
throw new Exception($"Unsupported cipher type {cipherType}");
233+
}
234+
235+
if (isCompressed) {
236+
data = DeobUtils.Inflate(data, inflater);
237+
}
238+
239+
return data;
240+
}
241+
242+
byte[] GetHeaderData(byte[] encryptedData, ref int index, out byte[] iv) {
243+
var headerData = new byte[BitConverter.ToUInt16(encryptedData, index)];
244+
Array.Copy(encryptedData, index + 2, headerData, 0, headerData.Length);
245+
index += headerData.Length + 2;
246+
247+
iv = new byte[encryptedData[index++]];
248+
Array.Copy(encryptedData, index, iv, 0, iv.Length);
249+
index += iv.Length;
250+
for (int i = 0; i < headerData.Length; i++)
251+
headerData[i] ^= iv[i % iv.Length];
252+
253+
return headerData;
254+
}
255+
256+
void ParseHeader(byte[] headerData, out byte[] key, out byte flag, out byte cipherType) {
257+
var reader = new BinaryReader(new MemoryStream(headerData));
258+
259+
/*var license =*/ reader.ReadString();
260+
flag = reader.ReadByte();
261+
cipherType = reader.ReadByte();
262+
byte pubKeyOffset = reader.ReadByte();
263+
264+
key = reader.ReadBytes(reader.ReadByte());
265+
if (pubKeyOffset < 64) {
266+
if (reader.BaseStream.Position < reader.BaseStream.Length)
267+
throw new Exception("Expected end of header");
268+
}
269+
else {
270+
Array.Copy(module.Assembly.PublicKey.Data, pubKeyOffset + 12, key, 0, key.Length);
271+
//key[5] |= 0x80;
272+
}
273+
}
274+
}
275+
202276
public MethodDef DecryptMethod {
203277
set {
204278
if (value == null)
@@ -222,7 +296,8 @@ public static MethodDef FindDecrypterMethod(MethodDef method) {
222296
var calledMethod = instr.Operand as MethodDef;
223297
if (calledMethod == null || !calledMethod.IsStatic || calledMethod.Body == null)
224298
continue;
225-
if (!DotNetUtils.IsMethod(calledMethod, "System.IO.MemoryStream", "(System.IO.Stream)"))
299+
if (!DotNetUtils.IsMethod(calledMethod, "System.IO.MemoryStream", "(System.IO.Stream)")
300+
&& !DotNetUtils.IsMethod(calledMethod, "System.IO.Stream", "(System.IO.Stream)"))
226301
continue;
227302

228303
return calledMethod;
@@ -238,8 +313,10 @@ public byte[] Decrypt(byte[] encryptedData) {
238313
}
239314

240315
IDecrypter CreateDecrypter(byte[] encryptedData) {
316+
if (decryptMethod != null && DeobUtils.HasInteger(decryptMethod, 64))
317+
return new Decrypter4(module, decryptMethod, simpleDeobfuscator);
241318
if (decryptMethod != null && DeobUtils.HasInteger(decryptMethod, 6))
242-
return new Decrypter3(module, decryptMethod);
319+
return new Decrypter3(module, decryptMethod, simpleDeobfuscator);
243320
if (IsV30(encryptedData))
244321
return new Decrypter1(module);
245322
return new Decrypter2(module);

0 commit comments

Comments
 (0)