2012-05-31 21:38:51 +08:00
|
|
|
|
/*
|
2014-03-12 05:15:43 +08:00
|
|
|
|
Copyright (C) 2011-2014 de4dot@gmail.com
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
|
|
|
|
This file is part of de4dot.
|
|
|
|
|
|
|
|
|
|
de4dot is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
de4dot is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
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 System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2012-12-20 09:06:09 +08:00
|
|
|
|
using dnlib.IO;
|
|
|
|
|
using dnlib.DotNet;
|
|
|
|
|
using dnlib.DotNet.Emit;
|
2012-05-31 21:38:51 +08:00
|
|
|
|
using de4dot.blocks;
|
|
|
|
|
|
|
|
|
|
namespace de4dot.code.deobfuscators.CodeFort {
|
|
|
|
|
class AssemblyDecrypter {
|
2012-11-08 14:43:57 +08:00
|
|
|
|
ModuleDefMD module;
|
2012-05-31 21:38:51 +08:00
|
|
|
|
EmbeddedResource assemblyEncryptedResource;
|
|
|
|
|
PasswordInfo embedPassword;
|
2012-11-02 22:57:11 +08:00
|
|
|
|
MethodDef embedInitMethod;
|
|
|
|
|
MethodDef embedResolverMethod;
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
|
|
|
|
public class AssemblyInfo {
|
|
|
|
|
public readonly byte[] data;
|
|
|
|
|
public readonly EmbeddedResource resource;
|
|
|
|
|
public readonly string asmFullName;
|
|
|
|
|
public readonly string asmSimpleName;
|
|
|
|
|
public readonly string extension;
|
|
|
|
|
|
|
|
|
|
public AssemblyInfo(byte[] data, EmbeddedResource resource, string asmFullName, string asmSimpleName, string extension) {
|
|
|
|
|
this.data = data;
|
|
|
|
|
this.resource = resource;
|
|
|
|
|
this.asmFullName = asmFullName;
|
|
|
|
|
this.asmSimpleName = asmSimpleName;
|
|
|
|
|
this.extension = extension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string ToString() {
|
|
|
|
|
return asmFullName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool EncryptedDetected {
|
|
|
|
|
get { return assemblyEncryptedResource != null; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-01 18:01:45 +08:00
|
|
|
|
public bool MainAssemblyHasAssemblyResolver {
|
|
|
|
|
get { return embedInitMethod != null; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-31 21:38:51 +08:00
|
|
|
|
public bool Detected {
|
2012-06-01 18:01:45 +08:00
|
|
|
|
get { return EncryptedDetected || MainAssemblyHasAssemblyResolver; }
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-02 22:57:11 +08:00
|
|
|
|
public TypeDef Type {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
get { return embedInitMethod != null ? embedInitMethod.DeclaringType : null; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-02 22:57:11 +08:00
|
|
|
|
public MethodDef InitMethod {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
get { return embedInitMethod; }
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-08 14:43:57 +08:00
|
|
|
|
public AssemblyDecrypter(ModuleDefMD module) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
this.module = module;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-08 14:43:57 +08:00
|
|
|
|
public AssemblyDecrypter(ModuleDefMD module, AssemblyDecrypter oldOne) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
this.module = module;
|
|
|
|
|
this.embedPassword = oldOne.embedPassword;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
public void Find() {
|
|
|
|
|
if (FindEncrypted())
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
FindEmbedded();
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static readonly string[] encryptedRequiredLocals = new string[] {
|
|
|
|
|
"System.Byte",
|
|
|
|
|
"System.Byte[]",
|
|
|
|
|
"System.Int32",
|
|
|
|
|
"System.IO.BinaryReader",
|
|
|
|
|
"System.IO.MemoryStream",
|
|
|
|
|
"System.IO.Stream",
|
|
|
|
|
"System.Object[]",
|
|
|
|
|
"System.Reflection.Assembly",
|
|
|
|
|
"System.Type[]",
|
|
|
|
|
};
|
2013-01-19 20:03:57 +08:00
|
|
|
|
bool FindEncrypted() {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
var ep = module.EntryPoint;
|
|
|
|
|
if (ep == null || ep.Body == null)
|
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (!DotNetUtils.IsMethod(ep, "System.Void", "(System.String[])"))
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var initMethod = CheckCalledMethods(ep);
|
|
|
|
|
if (initMethod == null || !new LocalTypes(initMethod).All(encryptedRequiredLocals))
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var resource = GetResource();
|
2012-05-31 21:38:51 +08:00
|
|
|
|
if (resource == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
assemblyEncryptedResource = resource;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
MethodDef CheckCalledMethods(MethodDef method) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
int calls = 0;
|
2012-11-02 22:57:11 +08:00
|
|
|
|
TypeDef type = null;
|
|
|
|
|
MethodDef initMethod = null;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
foreach (var calledMethod in DotNetUtils.GetCalledMethods(module, method)) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
calls++;
|
|
|
|
|
if (type != null && calledMethod.DeclaringType != type)
|
|
|
|
|
return null;
|
|
|
|
|
type = calledMethod.DeclaringType;
|
|
|
|
|
if (initMethod == null)
|
|
|
|
|
initMethod = calledMethod;
|
|
|
|
|
}
|
|
|
|
|
if (calls != 2)
|
|
|
|
|
return null;
|
|
|
|
|
return initMethod;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
EmbeddedResource GetResource() {
|
|
|
|
|
return DotNetUtils.GetResource(module, "_") as EmbeddedResource;
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
bool FindEmbedded() {
|
|
|
|
|
return FindEmbedded(DotNetUtils.GetModuleTypeCctor(module)) ||
|
|
|
|
|
FindEmbedded(module.EntryPoint);
|
2012-06-01 18:40:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
bool FindEmbedded(MethodDef method) {
|
2012-06-01 18:40:16 +08:00
|
|
|
|
if (method == null || method.Body == null)
|
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
foreach (var calledMethod in DotNetUtils.GetCalledMethods(module, method)) {
|
|
|
|
|
var resolver = CheckInitMethod(calledMethod);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
if (resolver == null)
|
|
|
|
|
continue;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (!CheckType(calledMethod.DeclaringType))
|
2012-05-31 21:38:51 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
embedInitMethod = calledMethod;
|
|
|
|
|
embedResolverMethod = resolver;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
MethodDef CheckInitMethod(MethodDef method) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
if (method == null || !method.IsStatic || method.Body == null)
|
|
|
|
|
return null;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (!DotNetUtils.IsMethod(method, "System.Void", "()"))
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var resolver = DeobUtils.GetResolveMethod(method);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
if (resolver == null || resolver.DeclaringType != method.DeclaringType)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return resolver;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
bool CheckType(TypeDef type) {
|
|
|
|
|
if (DotNetUtils.GetMethod(type, "System.Byte[]", "(System.Byte[],System.String,System.String,System.Int32,System.String,System.Int32)") == null)
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (DotNetUtils.GetMethod(type, "System.String", "(System.String)") == null)
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (DotNetUtils.GetMethod(type, "System.Byte[]", "(System.Reflection.Assembly,System.String)") == null)
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
if (DotNetUtils.GetMethod(type, "System.Void", "(System.IO.Stream,System.IO.Stream)") == null)
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
public byte[] Decrypt() {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
if (assemblyEncryptedResource == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2012-11-20 14:53:54 +08:00
|
|
|
|
assemblyEncryptedResource.Data.Position = 0;
|
2012-11-08 17:16:58 +08:00
|
|
|
|
var reader = new BinaryReader(assemblyEncryptedResource.Data.CreateStream());
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var encryptedData = DeobUtils.Gunzip(reader.BaseStream, reader.ReadInt32());
|
2012-05-31 21:38:51 +08:00
|
|
|
|
reader = new BinaryReader(new MemoryStream(encryptedData));
|
|
|
|
|
var serializedData = reader.ReadBytes(reader.ReadInt32());
|
|
|
|
|
for (int i = 0; i < serializedData.Length; i++)
|
|
|
|
|
serializedData[i] ^= 0xAD;
|
|
|
|
|
var encryptedAssembly = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position));
|
|
|
|
|
|
|
|
|
|
var passwordFinder = new PasswordFinder(serializedData);
|
|
|
|
|
PasswordInfo mainAsmPassword;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
passwordFinder.Find(out mainAsmPassword, out embedPassword);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
return Decrypt(mainAsmPassword, encryptedAssembly);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
static byte[] Decrypt(PasswordInfo password, byte[] data) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
const int iterations = 2;
|
|
|
|
|
const int numBits = 0x100;
|
|
|
|
|
var key = new Rfc2898DeriveBytes(password.passphrase, Encoding.UTF8.GetBytes(password.salt), iterations).GetBytes(numBits / 8);
|
2013-01-19 20:03:57 +08:00
|
|
|
|
return DeobUtils.AesDecrypt(data, key, Encoding.UTF8.GetBytes(password.iv));
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
static byte[] Gunzip(byte[] data) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
var reader = new BinaryReader(new MemoryStream(data));
|
2013-01-19 20:03:57 +08:00
|
|
|
|
return DeobUtils.Gunzip(reader.BaseStream, reader.ReadInt32());
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
public List<AssemblyInfo> GetAssemblyInfos(ISimpleDeobfuscator simpleDeobfuscator, IDeobfuscator deob) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
var infos = new List<AssemblyInfo>();
|
|
|
|
|
|
|
|
|
|
if (embedResolverMethod != null) {
|
2013-01-19 20:03:57 +08:00
|
|
|
|
simpleDeobfuscator.Deobfuscate(embedResolverMethod);
|
|
|
|
|
simpleDeobfuscator.DecryptStrings(embedResolverMethod, deob);
|
|
|
|
|
embedPassword = GetEmbedPassword(embedResolverMethod);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (embedPassword == null)
|
|
|
|
|
return infos;
|
|
|
|
|
|
|
|
|
|
foreach (var rsrc in module.Resources) {
|
|
|
|
|
var resource = rsrc as EmbeddedResource;
|
|
|
|
|
if (resource == null)
|
|
|
|
|
continue;
|
2012-11-08 14:43:57 +08:00
|
|
|
|
if (!Regex.IsMatch(resource.Name.String, "^cfd_([0-9a-f]{2})+_$"))
|
2012-05-31 21:38:51 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var asmData = Decrypt(embedPassword, Gunzip(resource.Data.ReadAllBytes()));
|
2012-11-08 14:43:57 +08:00
|
|
|
|
var mod = ModuleDefMD.Load(asmData);
|
2013-01-19 20:03:57 +08:00
|
|
|
|
infos.Add(new AssemblyInfo(asmData, resource, mod.Assembly.FullName, mod.Assembly.Name.String, DeobUtils.GetExtension(mod.Kind)));
|
2012-05-31 21:38:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return infos;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
static PasswordInfo GetEmbedPassword(MethodDef method) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
var instrs = method.Body.Instructions;
|
|
|
|
|
for (int i = 0; i < instrs.Count - 3; i++) {
|
|
|
|
|
int index = i;
|
|
|
|
|
|
|
|
|
|
var ldstr1 = instrs[index++];
|
|
|
|
|
if (ldstr1.OpCode.Code != Code.Ldstr)
|
|
|
|
|
continue;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var passphrase = GetString(ldstr1, instrs, ref index);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
|
|
|
|
var ldstr2 = instrs[index++];
|
|
|
|
|
if (ldstr2.OpCode.Code != Code.Ldstr)
|
|
|
|
|
continue;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var salt = GetString(ldstr2, instrs, ref index);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
|
|
|
|
var ldci4 = instrs[index++];
|
2012-11-08 14:43:57 +08:00
|
|
|
|
if (!ldci4.IsLdcI4())
|
2012-05-31 21:38:51 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var ldstr3 = instrs[index++];
|
|
|
|
|
if (ldstr3.OpCode.Code != Code.Ldstr)
|
|
|
|
|
continue;
|
2013-01-19 20:03:57 +08:00
|
|
|
|
var iv = GetString(ldstr3, instrs, ref index);
|
2012-05-31 21:38:51 +08:00
|
|
|
|
|
|
|
|
|
return new PasswordInfo(passphrase, salt, iv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2013-01-19 20:03:57 +08:00
|
|
|
|
static string GetString(Instruction ldstr, IList<Instruction> instrs, ref int index) {
|
2012-05-31 21:38:51 +08:00
|
|
|
|
var s = (string)ldstr.Operand;
|
|
|
|
|
if (index >= instrs.Count)
|
|
|
|
|
return s;
|
|
|
|
|
var call = instrs[index];
|
|
|
|
|
if (call.OpCode.Code != Code.Call && call.OpCode.Code != Code.Callvirt)
|
|
|
|
|
return s;
|
|
|
|
|
index++;
|
2012-11-08 14:43:57 +08:00
|
|
|
|
var calledMethod = call.Operand as IMethod;
|
|
|
|
|
if (calledMethod.Name.String == "ToUpper")
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return s.ToUpper();
|
2012-11-08 14:43:57 +08:00
|
|
|
|
if (calledMethod.Name.String == "ToLower")
|
2012-05-31 21:38:51 +08:00
|
|
|
|
return s.ToLower();
|
|
|
|
|
throw new ApplicationException(string.Format("Unknown method {0}", calledMethod));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|