Skip to content

Commit bb63dc6

Browse files
committed
Bump version, Improve control flow deobfuscator #16, Added boolean decryptor, Improve & fix cleaner, Update GUI.
1 parent 1c6652f commit bb63dc6

14 files changed

Lines changed: 441 additions & 45 deletions

File tree

NETReactorSlayer-x64.CLI/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
// You can specify all the values or you can default the Build and Revision Numbers
3232
// by using the '*' as shown below:
3333
// [assembly: AssemblyVersion("1.0.*")]
34-
[assembly: AssemblyVersion("3.0.0.0")]
35-
[assembly: AssemblyFileVersion("3.0.0.0")]
34+
[assembly: AssemblyVersion("3.1.0.0")]
35+
[assembly: AssemblyFileVersion("3.1.0.0")]

NETReactorSlayer.CLI/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
// You can specify all the values or you can default the Build and Revision Numbers
3232
// by using the '*' as shown below:
3333
// [assembly: AssemblyVersion("1.0.*")]
34-
[assembly: AssemblyVersion("3.0.0.0")]
35-
[assembly: AssemblyFileVersion("3.0.0.0")]
34+
[assembly: AssemblyVersion("3.1.0.0")]
35+
[assembly: AssemblyFileVersion("3.1.0.0")]

NETReactorSlayer.Core/DeobfuscatorContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ public bool Parse(string[] args)
9696
try
9797
{
9898
Module = AssemblyModule.Load();
99-
PEImage = new MyPEImage(DeobUtils.ReadModule(Module));
99+
ModuleBytes = DeobUtils.ReadModule(Module);
100+
PEImage = new MyPEImage(ModuleBytes);
100101
try { Assembly = Assembly.Load(SourcePath); } catch { Assembly = Assembly.UnsafeLoadFrom(SourcePath); }
101102
return true;
102103
}
@@ -138,6 +139,7 @@ public bool Parse(string[] args)
138139
IsNative = true;
139140
Process.Start(new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, $"--delete-native-image {Process.GetCurrentProcess().Id} \"{SourcePath}\"") { WindowStyle = ProcessWindowStyle.Hidden });
140141
Logger.Done("Native image unpacked.");
142+
ModuleBytes = DeobUtils.ReadModule(Module);
141143
return true;
142144
}
143145
else
@@ -241,6 +243,7 @@ public void Save()
241243
public static string SourcePath { get; set; }
242244
public static string DestPath { get; set; }
243245
public static string DestFileName { get; set; }
246+
public static byte[] ModuleBytes { get; set; }
244247
public static ModuleDefMD Module { get; set; }
245248
public static Assembly Assembly { get; set; }
246249
public static AssemblyModule AssemblyModule { get; set; }

NETReactorSlayer.Core/DeobfuscatorOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class DeobfuscatorOptions
2222
public Dictionary<string, IDeobfuscator> Dictionary = new Dictionary<string, IDeobfuscator>()
2323
{
2424
["decrypt-methods"] = new MethodDecryptor(),
25+
["decrypt-booleans"] = new BooleanDecryptor(),
2526
["deobfuscate-cflow"] = new ControlFlowDeobfuscator(),
2627
["anti-tamper"] = new AntiTamperPatcher(),
2728
["remove-ref-proxies"] = new ProxyCleaner(),
@@ -36,6 +37,7 @@ public class DeobfuscatorOptions
3637
public HashSet<IDeobfuscator> Stages = new HashSet<IDeobfuscator>()
3738
{
3839
new MethodDecryptor(),
40+
new BooleanDecryptor(),
3941
new ControlFlowDeobfuscator(),
4042
new AntiTamperPatcher(),
4143
new ProxyCleaner(),
@@ -50,6 +52,7 @@ public class DeobfuscatorOptions
5052
public readonly List<string> Arguments = new List<string>()
5153
{
5254
"--decrypt-methods", " Decrypt methods that encrypted by Necrobit (True)",
55+
"--decrypt-booleans", " Decrypt booleans that generated by control flow obfuscator (True)",
5356
"--deobfuscate-cflow", " Deobfuscate control flow (True)",
5457
"--anti-tamper", " Patch anti tamper (True)",
5558
"--remove-ref-proxies", " Remove reference proxies (True)",
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/*
2+
Copyright (C) 2021 CodeStrikers.org
3+
This file is part of NETReactorSlayer.
4+
NETReactorSlayer is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
NETReactorSlayer is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
GNU General Public License for more details.
12+
You should have received a copy of the GNU General Public License
13+
along with NETReactorSlayer. If not, see <http://www.gnu.org/licenses/>.
14+
*/
15+
using de4dot.blocks;
16+
using de4dot.blocks.cflow;
17+
using dnlib.DotNet;
18+
using dnlib.DotNet.Emit;
19+
using NETReactorSlayer.Core.Helper.De4dot;
20+
using System;
21+
using System.Collections.Generic;
22+
using System.Linq;
23+
using static NETReactorSlayer.Core.Deobfuscators.ResourceDecryptor;
24+
25+
namespace NETReactorSlayer.Core.Deobfuscators
26+
{
27+
class BooleanDecryptor : IDeobfuscator
28+
{
29+
private MethodDef decryptorMethod;
30+
private EmbeddedResource encryptedResource;
31+
public void Execute()
32+
{
33+
long count = 0L;
34+
Find();
35+
if (decryptorMethod == null || encryptedResource == null)
36+
{
37+
Logger.Warn("Couldn't find any encrypted boolean.");
38+
return;
39+
}
40+
Cleaner.ResourceToRemove.Add(encryptedResource);
41+
Cleaner.MethodsToPatch.Add(decryptorMethod);
42+
Deobfuscate(decryptorMethod);
43+
DnrDecrypterType decrypterType = GetDecrypterType(decryptorMethod, new string[0]);
44+
byte[] key = GetBytes(decryptorMethod, 32);
45+
if (decrypterType == DnrDecrypterType.V3)
46+
{
47+
V3 V3 = new V3(decryptorMethod);
48+
decryptedBytes = V3.Decrypt(encryptedResource);
49+
goto Continue;
50+
}
51+
byte[] iv = GetBytes(decryptorMethod, 16);
52+
if (IsNeedReverse(decryptorMethod))
53+
Array.Reverse(iv);
54+
if (UsesPublicKeyToken(decryptorMethod))
55+
{
56+
PublicKeyToken publicKeyToken = DeobfuscatorContext.Module.Assembly.PublicKeyToken;
57+
if (publicKeyToken != null && publicKeyToken.Data.Length != 0)
58+
{
59+
for (int z = 0; z < 8; z++)
60+
{
61+
iv[z * 2 + 1] = publicKeyToken.Data[z];
62+
}
63+
}
64+
}
65+
if (decrypterType == DnrDecrypterType.V1)
66+
{
67+
V1 V1 = new V1(iv, key);
68+
decryptedBytes = V1.Decrypt(encryptedResource);
69+
goto Continue;
70+
}
71+
else if (decrypterType == DnrDecrypterType.V2)
72+
{
73+
V2 V2 = new V2(iv, key, decryptorMethod);
74+
decryptedBytes = V2.Decrypt(encryptedResource);
75+
goto Continue;
76+
}
77+
Logger.Warn("Couldn't find any encrypted boolean.");
78+
return;
79+
Continue:
80+
if (decryptedBytes == null)
81+
{
82+
Logger.Error("Failed to decrypt booleans.");
83+
return;
84+
}
85+
foreach (TypeDef type in DeobfuscatorContext.Module.GetTypes().Where(x => x.HasMethods))
86+
{
87+
foreach (MethodDef method in type.Methods.Where(x => x.HasBody && x.Body.HasInstructions))
88+
{
89+
for (int i = 0; i < method.Body.Instructions.Count; i++)
90+
{
91+
try
92+
{
93+
if (!method.Body.Instructions[i].OpCode.Equals(OpCodes.Call) || !method.Body.Instructions[i - 1].IsLdcI4() || !method.Body.Instructions[i + 1].IsConditionalBranch())
94+
continue;
95+
if (method.Body.Instructions[i].Operand is IMethod iMethod && iMethod.MDToken.ToInt32() == decryptorMethod.MDToken.ToInt32())
96+
{
97+
int offset = method.Body.Instructions[i - 1].GetLdcI4Value();
98+
bool value = Decrypt(offset);
99+
if (value)
100+
method.Body.Instructions[i] = Instruction.CreateLdcI4(1);
101+
else
102+
method.Body.Instructions[i] = Instruction.CreateLdcI4(0);
103+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
104+
count += 1L;
105+
}
106+
}
107+
catch { }
108+
}
109+
}
110+
}
111+
if (count > 0L) Logger.Done((int)count + " Booleans decrypted.");
112+
else Logger.Warn("Couldn't find any encrypted boolean.");
113+
}
114+
public bool Decrypt(int offset) => DeobfuscatorContext.ModuleBytes[BitConverter.ToUInt32(decryptedBytes, offset)] == 0x80;
115+
116+
private void Find()
117+
{
118+
foreach (var type in DeobfuscatorContext.Module.Types.Where(x => x.BaseType != null && x.BaseType.FullName == "System.Object"))
119+
{
120+
var methodDef = DotNetUtils.GetMethod(type, "System.Boolean", "(System.Int32)");
121+
if (methodDef != null && GetEncryptedResource(methodDef) != null && GetDecrypterType(methodDef, new string[0]) != DnrDecrypterType.Unknown)
122+
{
123+
decryptorMethod = methodDef;
124+
encryptedResource = GetEncryptedResource(decryptorMethod);
125+
break;
126+
}
127+
}
128+
}
129+
130+
EmbeddedResource GetEncryptedResource(MethodDef method)
131+
{
132+
if (method == null || !method.HasBody || !method.Body.HasInstructions) return null;
133+
foreach (string s in DotNetUtils.GetCodeStrings(method))
134+
{
135+
if (DotNetUtils.GetResource(DeobfuscatorContext.Module, s) is EmbeddedResource resource) return resource;
136+
}
137+
return null;
138+
}
139+
140+
private DnrDecrypterType GetDecrypterType(MethodDef method, IList<string> additionalTypes)
141+
{
142+
if (method == null || !method.IsStatic || method.Body == null)
143+
{
144+
return DnrDecrypterType.Unknown;
145+
}
146+
if (additionalTypes == null)
147+
{
148+
additionalTypes = new string[0];
149+
}
150+
LocalTypes localTypes = new LocalTypes(method);
151+
if (V1.CouldBeResourceDecrypter(method, localTypes, additionalTypes))
152+
{
153+
return DnrDecrypterType.V1;
154+
}
155+
if (V2.CouldBeResourceDecrypter(localTypes, additionalTypes))
156+
{
157+
return DnrDecrypterType.V2;
158+
}
159+
if (V3.CouldBeResourceDecrypter(localTypes, additionalTypes))
160+
{
161+
return DnrDecrypterType.V3;
162+
}
163+
return DnrDecrypterType.Unknown;
164+
}
165+
166+
public void Deobfuscate(MethodDef method)
167+
{
168+
List<IBlocksDeobfuscator> list = new List<IBlocksDeobfuscator> { new MethodCallInliner(false) };
169+
SimpleDeobfuscatorFlags flags = 0;
170+
if (!(method == null || (!((flags & SimpleDeobfuscatorFlags.Force) > 0U) && Check(method, SimpleDeobFlags.HasDeobfuscated))))
171+
{
172+
Deobfuscate(method, delegate (Blocks blocks)
173+
{
174+
bool disableNewCFCode = (flags & SimpleDeobfuscatorFlags.DisableConstantsFolderExtraInstrs) > (SimpleDeobfuscatorFlags)0U;
175+
BlocksCflowDeobfuscator cflowDeobfuscator = new BlocksCflowDeobfuscator(list, disableNewCFCode);
176+
cflowDeobfuscator.Initialize(blocks);
177+
cflowDeobfuscator.Deobfuscate();
178+
});
179+
}
180+
}
181+
182+
void Deobfuscate(MethodDef method, Action<Blocks> handler)
183+
{
184+
if (method == null || !method.HasBody || !method.Body.HasInstructions) return;
185+
try
186+
{
187+
if (method.Body.Instructions.Any((Instruction instr) => instr.OpCode.Equals(OpCodes.Switch)))
188+
DeobfuscateEquations(method);
189+
Blocks blocks = new Blocks(method);
190+
handler(blocks);
191+
blocks.GetCode(out IList<Instruction> allInstructions, out IList<ExceptionHandler> allExceptionHandlers);
192+
DotNetUtils.RestoreBody(method, allInstructions, allExceptionHandlers);
193+
}
194+
catch
195+
{
196+
Logger.Warn("Couldn't deobfuscate " + method.FullName);
197+
}
198+
}
199+
200+
void DeobfuscateEquations(MethodDef method)
201+
{
202+
for (int i = 0; i < method.Body.Instructions.Count; i++)
203+
{
204+
if (method.Body.Instructions[i].IsBrtrue() && method.Body.Instructions[i + 1].OpCode.Equals(OpCodes.Pop) && method.Body.Instructions[i - 1].OpCode.Equals(OpCodes.Call))
205+
{
206+
if (method.Body.Instructions[i - 1].Operand is MethodDef methodDef)
207+
{
208+
IList<Instruction> methodDefInstr = methodDef.Body.Instructions;
209+
if (methodDef.ReturnType.FullName == "System.Boolean")
210+
{
211+
if (methodDefInstr[methodDefInstr.Count - 2].OpCode.Equals(OpCodes.Ldc_I4_0))
212+
{
213+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
214+
method.Body.Instructions[i].OpCode = OpCodes.Nop;
215+
}
216+
else
217+
{
218+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
219+
method.Body.Instructions[i].OpCode = OpCodes.Br_S;
220+
}
221+
}
222+
else
223+
{
224+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
225+
method.Body.Instructions[i].OpCode = OpCodes.Nop;
226+
}
227+
}
228+
}
229+
else
230+
{
231+
if (method.Body.Instructions[i].IsBrfalse() && method.Body.Instructions[i + 1].OpCode.Equals(OpCodes.Pop) && method.Body.Instructions[i - 1].OpCode.Equals(OpCodes.Call))
232+
{
233+
if (method.Body.Instructions[i - 1].Operand is MethodDef methodDef2)
234+
{
235+
IList<Instruction> methodDefInstr2 = methodDef2.Body.Instructions;
236+
if (methodDef2.ReturnType.FullName == "System.Boolean")
237+
{
238+
if (methodDefInstr2[methodDefInstr2.Count - 2].OpCode.Equals(OpCodes.Ldc_I4_0))
239+
{
240+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
241+
method.Body.Instructions[i].OpCode = OpCodes.Br_S;
242+
}
243+
else
244+
{
245+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
246+
method.Body.Instructions[i].OpCode = OpCodes.Nop;
247+
}
248+
}
249+
else
250+
{
251+
method.Body.Instructions[i - 1].OpCode = OpCodes.Nop;
252+
method.Body.Instructions[i].OpCode = OpCodes.Br_S;
253+
}
254+
}
255+
}
256+
}
257+
}
258+
}
259+
260+
bool Check(MethodDef method, SimpleDeobFlags flags)
261+
{
262+
if (method == null) return false;
263+
simpleDeobfuscatorFlags.TryGetValue(method, out SimpleDeobFlags oldFlags);
264+
simpleDeobfuscatorFlags[method] = (oldFlags | flags);
265+
return ((oldFlags & flags) == flags);
266+
}
267+
268+
[Flags] public enum SimpleDeobfuscatorFlags : uint { Force = 1U, DisableConstantsFolderExtraInstrs = 2U }
269+
270+
[Flags] enum SimpleDeobFlags { HasDeobfuscated = 1 }
271+
272+
readonly Dictionary<MethodDef, SimpleDeobFlags> simpleDeobfuscatorFlags = new Dictionary<MethodDef, SimpleDeobFlags>();
273+
274+
byte[] decryptedBytes = null;
275+
}
276+
}

0 commit comments

Comments
 (0)