From 0efd62e629da71c8ce054ed979f33d33d884f554 Mon Sep 17 00:00:00 2001 From: YagUber Date: Sun, 27 Sep 2026 18:32:03 -0500 Subject: [PATCH] feat(reactor): Add support for Brotli compressed resources --- de4dot.code/de4dot.code.csproj | 1 + de4dot.code/deobfuscators/DeobUtils.cs | 40 ++++++++- .../dotNET_Reactor/v4/ResourceResolver.cs | 81 +++++++++++++------ 3 files changed, 95 insertions(+), 27 deletions(-) diff --git a/de4dot.code/de4dot.code.csproj b/de4dot.code/de4dot.code.csproj index a9063c1b..cdf69bb0 100644 --- a/de4dot.code/de4dot.code.csproj +++ b/de4dot.code/de4dot.code.csproj @@ -27,6 +27,7 @@ + diff --git a/de4dot.code/deobfuscators/DeobUtils.cs b/de4dot.code/deobfuscators/DeobUtils.cs index 69ae031c..e756aa63 100644 --- a/de4dot.code/deobfuscators/DeobUtils.cs +++ b/de4dot.code/deobfuscators/DeobUtils.cs @@ -25,13 +25,36 @@ You should have received a copy of the GNU General Public License using dnlib.DotNet; using dnlib.DotNet.Emit; using ICSharpCode.SharpZipLib.Zip.Compression; +#if NETFRAMEWORK +using BrotliSharpLib; +#endif using de4dot.blocks; namespace de4dot.code.deobfuscators { public static class DeobUtils { public static void DecryptAndAddResources(ModuleDef module, string encryptedName, Func decryptResource) { Logger.v("Decrypting resources, name: {0}", Utils.ToCsharpString(encryptedName)); - var decryptedResourceData = decryptResource(); + AddDecryptedResources(module, decryptResource()); + } + + public static void DecryptAndAddResources(ModuleDef module, string encryptedName, Func> decryptResources) { + Logger.v("Decrypting resources, name: {0}", Utils.ToCsharpString(encryptedName)); + BadImageFormatException lastException = null; + foreach (var decryptedResourceData in decryptResources()) { + try { + AddDecryptedResources(module, decryptedResourceData); + return; + } + catch (BadImageFormatException ex) { + lastException = ex; + } + } + if (lastException != null) + throw lastException; + throw new BadImageFormatException(); + } + + static void AddDecryptedResources(ModuleDef module, byte[] decryptedResourceData) { if (decryptedResourceData == null) throw new ApplicationException("decryptedResourceData is null"); var resourceModule = ModuleDefMD.Load(decryptedResourceData); @@ -163,6 +186,21 @@ public static byte[] Inflate(byte[] data, int start, int len, Inflater inflater) return memStream.ToArray(); } + public static byte[] BrotliDecompress(byte[] data) { +#if NETFRAMEWORK + return Brotli.DecompressBuffer(data, 0, data.Length); +#else + using (var input = new MemoryStream(data, writable: false)) { + using (var brotli = new BrotliStream(input, CompressionMode.Decompress)) { + using (var output = new MemoryStream()) { + brotli.CopyTo(output); + return output.ToArray(); + } + } + } +#endif + } + public static byte[] Gunzip(Stream input, int decompressedSize) { using (var gzip = new GZipStream(input, CompressionMode.Decompress)) { var decompressed = new byte[decompressedSize]; diff --git a/de4dot.code/deobfuscators/dotNET_Reactor/v4/ResourceResolver.cs b/de4dot.code/deobfuscators/dotNET_Reactor/v4/ResourceResolver.cs index 9b06bbb2..c82ad177 100644 --- a/de4dot.code/deobfuscators/dotNET_Reactor/v4/ResourceResolver.cs +++ b/de4dot.code/deobfuscators/dotNET_Reactor/v4/ResourceResolver.cs @@ -151,34 +151,63 @@ MethodDef FindInitMethod(ISimpleDeobfuscator simpleDeobfuscator) { public EmbeddedResource MergeResources() { if (encryptedResource.Resource == null) return null; - DeobUtils.DecryptAndAddResources(module, encryptedResource.Resource.Name.String, () => { - byte[] decrypted; - try { - decrypted = encryptedResource.Decrypt(); - } - catch (Exception ex) { - Logger.e("Resource decryption failed:\n{0}", ex); - return null; - } + byte[] decrypted; + try { + decrypted = encryptedResource.Decrypt(); + } + catch (Exception ex) { + Logger.e("Resource decryption failed:\n{0}", ex); + return null; + } + DeobUtils.DecryptAndAddResources(module, encryptedResource.Resource.Name.String, + () => GetDecompressedResources(decrypted)); + return encryptedResource.Resource; + } - if (decrypted.Length < 64) - throw new Exception("Decrypted resource data has length " + decrypted.Length); - if (decrypted[0] == 0x4D && decrypted[1] == 0x5A && decrypted[62] == 0 && decrypted[63] == 0) - return decrypted; + static IEnumerable GetDecompressedResources(byte[] decrypted) { + if (decrypted.Length >= 2 && decrypted[0] == 0x4D && decrypted[1] == 0x5A) + yield return decrypted; - try { - return QuickLZ.Decompress(decrypted); - } - catch { - try { - return DeobUtils.Inflate(decrypted, true); - } - catch { - return null; - } - } - }); - return encryptedResource.Resource; + if (TryQuickLZDecompress(decrypted, out var decompressed)) + yield return decompressed; + if (TryInflate(decrypted, out var inflated)) + yield return inflated; + + if (TryBrotliDecompress(decrypted, out var brotli)) + yield return brotli; + } + + static bool TryQuickLZDecompress(byte[] data, out byte[] decompressed) { + try { + decompressed = QuickLZ.Decompress(data); + return true; + } + catch { + decompressed = null; + return false; + } + } + + static bool TryInflate(byte[] data, out byte[] inflated) { + try { + inflated = DeobUtils.Inflate(data, true); + return true; + } + catch { + inflated = null; + return false; + } + } + + static bool TryBrotliDecompress(byte[] data, out byte[] brotli) { + try { + brotli = DeobUtils.BrotliDecompress(data); + return true; + } + catch { + brotli = null; + return false; + } } } }