diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a4c005 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +Logger.cs diff --git a/AgileStringDecryptor.csproj b/AgileStringDecryptor.csproj index c9fff14..9648706 100644 --- a/AgileStringDecryptor.csproj +++ b/AgileStringDecryptor.csproj @@ -2,11 +2,19 @@ Exe - netcoreapp3.1 + 9 + annotations + 1.1.0 + AgileStringDecryptor + net48 + + + + diff --git a/AgileStringDecryptor.sln.DotSettings.user b/AgileStringDecryptor.sln.DotSettings.user new file mode 100644 index 0000000..b65c917 --- /dev/null +++ b/AgileStringDecryptor.sln.DotSettings.user @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/ModuleDefExtensions.cs b/ModuleDefExtensions.cs new file mode 100644 index 0000000..71a5a03 --- /dev/null +++ b/ModuleDefExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using dnlib.DotNet; + +namespace AgileStringDecryptor { + internal static class ModuleDefExtensions { + public static IEnumerable EnumerateAllMethodDefs(this ModuleDefMD moduleDefMd) { + var methodTableLength = moduleDefMd.TablesStream.MethodTable.Rows; + // Get the length of the Method table. + for (uint rid = 1; rid <= methodTableLength; rid++) yield return moduleDefMd.ResolveMethod(rid); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index cab8c99..8a835fe 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -9,14 +8,14 @@ namespace AgileStringDecryptor { internal static class Program { - private static Module _module; - private static ModuleDefMD _moduleDefMd; - private static int _amount; + private static Module? _module; + private static ModuleDefMD? _moduleDefMd; + private static int _stringsAmount; + private static int _callsAmount; - private static void Main(string[] args) { + internal static void Main(string[] args) { Console.Title = "Agile String Decryptor by wwh1004 | Version : 6.x"; try { - // Load the assembly _module = Assembly.LoadFile(Path.GetFullPath(args[0])).ManifestModule; } catch { @@ -27,18 +26,84 @@ private static void Main(string[] args) { _moduleDefMd = ModuleDefMD.Load(args[0], new ModuleCreationOptions { TryToLoadPdbFromDisk = false }); - AgileDynamicStringDecryption(); + Action decryptString = DecryptString; + decryptString(); + Action fixProxyCall = FixProxyCall; + fixProxyCall(); SaveAs(Path.Combine( Path.GetDirectoryName(args[0]) ?? throw new InvalidOperationException("Failed to save this module !"), Path.GetFileNameWithoutExtension(args[0]) + "-StrDec" + Path.GetExtension(args[0]))); _moduleDefMd.Dispose(); - Console.WriteLine("[?] Decrypted : {0} strings", _amount); + Console.WriteLine($"[?] Fixed : {_callsAmount} calls"); + Console.WriteLine($"[?] Decrypted : {_stringsAmount} strings"); Console.WriteLine("[+] Done !"); Console.ReadLine(); } - private static void AgileDynamicStringDecryption() { - // Find namspace empty with class "" + internal static void FixProxyCall() { + var globalTypes = _moduleDefMd?.Types.Where(t => t.Namespace == string.Empty).ToArray(); + var decryptor = (globalTypes ?? throw new InvalidOperationException()).Single(t => t.Name.StartsWith("{", StringComparison.Ordinal) && + t.Name.EndsWith("}", StringComparison.Ordinal)).Methods.Single(m => !m.IsInstanceConstructor && m.Parameters.Count == 1); + + foreach (var typeDef in globalTypes) { + var cctor = typeDef.FindStaticConstructor(); + if(cctor is null || !cctor.Body.Instructions.Any(i => i.OpCode == OpCodes.Call && i.Operand == decryptor)) + continue; + switch (_module) { + case not null: { + foreach (var fieldInfo in _module.ResolveType(typeDef.MDToken.ToInt32()) + .GetFields(BindingFlags.NonPublic | BindingFlags.Static)!) { + var proxyFieldToken = fieldInfo.MetadataToken; + var proxyFieldDef = _moduleDefMd?.ResolveField((uint) proxyFieldToken - 0x4000000); + var realMethod = ((Delegate) fieldInfo.GetValue(null)!).Method; + + if (Utils.IsDynamicMethod(realMethod)) { + var dynamicMethodBodyReader = new DynamicMethodBodyReader(_moduleDefMd, realMethod); + dynamicMethodBodyReader.Read(); + var instructionList = dynamicMethodBodyReader.GetMethod().Body.Instructions; + ReplaceAllOperand(proxyFieldDef, instructionList[instructionList.Count - 2].OpCode, + (MemberRef) instructionList[instructionList.Count - 2].Operand); + } + else { + ReplaceAllOperand(proxyFieldDef, realMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, + (MemberRef) _moduleDefMd.Import(realMethod)); + } + } + + break; + } + } + } + } + + internal static void ReplaceAllOperand(FieldDef? proxyFieldDef, OpCode callOrCallvirt, MemberRef realMethod) { + if (proxyFieldDef is null) throw new ArgumentNullException(nameof(proxyFieldDef)); + if (realMethod is null) throw new ArgumentNullException(nameof(realMethod)); + + if (_moduleDefMd == null) return; + foreach (var method in _moduleDefMd.EnumerateAllMethodDefs()) { + if (!method.HasBody) continue; + var instr = method.Body.Instructions; + for (var i = 0; i < instr.Count; i++) { + if (instr[i].OpCode != OpCodes.Ldsfld || instr[i].Operand != proxyFieldDef) continue; + for (var j = i; j < instr.Count; j++) { + if (instr[j].OpCode.Code != Code.Call || !(instr[j].Operand is MethodDef) || + ((MethodDef) instr[j].Operand).DeclaringType != + ((TypeDefOrRefSig) proxyFieldDef.FieldType).TypeDefOrRef) continue; + instr[i].OpCode = OpCodes.Nop; + instr[i].Operand = null; + instr[j].OpCode = callOrCallvirt; + instr[j].Operand = realMethod; + _callsAmount++; + break; + } + } + } + } + + + internal static void DecryptString() { + // Find namespace empty with class "" var agileDotNetRt = _moduleDefMd.Types.First(t => t.Namespace == string.Empty && t.Name == ""); // Find a method in the class that has only one parameter with the parameter type String and the return value type String @@ -58,28 +123,22 @@ private static void AgileDynamicStringDecryption() { instr[i - 1].Operand = decryptor.Invoke(null, new[] { instr[i - 1].Operand }); - _amount++; + // The instruction corresponding to [i-1] is ldstr, + // we call the string decryptor method and replace the decrypted string back + _stringsAmount++; } } // remove decryption method from the assembly _moduleDefMd.Types.Remove(decryptionMethod.DeclaringType); - Console.WriteLine("[^] Removed junk : {0} class", decryptionMethod.DeclaringType); } - private static void SaveAs(string filePath) { - var opts = new ModuleWriterOptions(_moduleDefMd); - opts.MetadataOptions.Flags |= MetadataFlags.PreserveAll | MetadataFlags.KeepOldMaxStack; - opts.Logger = DummyLogger.NoThrowInstance; //this is just to prevent write methods from throwing error + internal static void SaveAs(string filePath) { + var opts = new ModuleWriterOptions(_moduleDefMd) { + MetadataOptions = {Flags = MetadataFlags.PreserveAll}, Logger = DummyLogger.NoThrowInstance + }; _moduleDefMd.Write(filePath, opts); } } - - internal static class ModuleDefExtensions { - public static IEnumerable EnumerateAllMethodDefs(this ModuleDefMD moduleDefMd) { - var methodTableLength = moduleDefMd.TablesStream.MethodTable.Rows; - for (uint rid = 1; rid <= methodTableLength; rid++) yield return moduleDefMd.ResolveMethod(rid); - } - } } // Ref : https://github.com/wwh1004/blog/ \ No newline at end of file diff --git a/Utils.cs b/Utils.cs new file mode 100644 index 0000000..9dd5a2e --- /dev/null +++ b/Utils.cs @@ -0,0 +1,20 @@ +using System; +using System.Reflection; + +namespace AgileStringDecryptor { + public static class Utils { + internal static bool IsDynamicMethod(MethodBase? methodBase) { + if (methodBase == null) + throw new ArgumentNullException(nameof(methodBase)); + + try { + var token = methodBase.MetadataToken; + } + catch (InvalidOperationException) { + return true; + } + return false; + + } + } +} \ No newline at end of file