Add SN string decrypter

This commit is contained in:
de4dot 2012-02-02 06:56:14 +01:00
parent 36b4806858
commit b3f17a27a3
7 changed files with 571 additions and 35 deletions

View File

@ -116,7 +116,6 @@
<Compile Include="deobfuscators\dotNET_Reactor\v4\MethodsDecrypter.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\v4\NativeFileDecrypter.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\v4\NativeImageUnpacker.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\QuickLZ.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\v4\ResourceResolver.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\v4\StringDecrypter.cs" />
<Compile Include="deobfuscators\dotNET_Reactor\v4\AntiStrongName.cs" />
@ -142,10 +141,14 @@
<Compile Include="deobfuscators\InlinedMethodsFinder.cs" />
<Compile Include="deobfuscators\ISimpleDeobfuscator.cs" />
<Compile Include="deobfuscators\MethodCollection.cs" />
<Compile Include="deobfuscators\QuickLZ.cs" />
<Compile Include="deobfuscators\RandomNameChecker.cs" />
<Compile Include="deobfuscators\Skater_NET\Deobfuscator.cs" />
<Compile Include="deobfuscators\Skater_NET\EnumClassFinder.cs" />
<Compile Include="deobfuscators\Skater_NET\StringDecrypter.cs" />
<Compile Include="deobfuscators\Spices_Net\Deobfuscator.cs" />
<Compile Include="deobfuscators\Spices_Net\QclzDecompressor.cs" />
<Compile Include="deobfuscators\Spices_Net\StringDecrypter.cs" />
<Compile Include="deobfuscators\StringCounts.cs" />
<Compile Include="deobfuscators\Operations.cs" />
<Compile Include="deobfuscators\ProxyDelegateFinderBase.cs" />

View File

@ -11,44 +11,21 @@
using System;
namespace de4dot.code.deobfuscators.dotNET_Reactor {
static class QuickLZ {
static int sig = 0x5A4C4351; // "QCLZ"
public static bool isCompressed(byte[] data) {
if (data.Length < 4)
return false;
return BitConverter.ToInt32(data, 0) == sig;
}
static uint read32(byte[] data, int index) {
namespace de4dot.code.deobfuscators {
class QuickLZBase {
protected static uint read32(byte[] data, int index) {
return BitConverter.ToUInt32(data, index);
}
// Can't use Array.Copy() when data overlaps so here's one that works
static void copy(byte[] src, int srcIndex, byte[] dst, int dstIndex, int size) {
protected static void copy(byte[] src, int srcIndex, byte[] dst, int dstIndex, int size) {
for (int i = 0; i < size; i++)
dst[dstIndex++] = src[srcIndex++];
}
static int[] indexInc = new int[] { 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 };
public static byte[] decompress(byte[] inData) {
int mode = BitConverter.ToInt32(inData, 4);
int compressedLength = BitConverter.ToInt32(inData, 8);
int decompressedLength = BitConverter.ToInt32(inData, 12);
bool isDataCompressed = BitConverter.ToInt32(inData, 16) == 1;
int headerLength = 32;
if (BitConverter.ToInt32(inData, 0) != sig || BitConverter.ToInt32(inData, compressedLength - 4) != sig)
throw new ApplicationException("No QCLZ sig");
byte[] outData = new byte[decompressedLength];
if (!isDataCompressed) {
copy(inData, headerLength, outData, 0, decompressedLength);
return outData;
}
int inIndex = headerLength;
public static void decompress(byte[] inData, int inIndex, byte[] outData) {
int decompressedLength = outData.Length;
int outIndex = 0;
uint val1 = 1;
uint count;
@ -89,18 +66,30 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor {
inIndex += 3;
}
else if ((val2 & 8) == 0) {
size = (int)((val2 >> 4) & 0x07FF) + 3;
count = val2 >> 15;
if (count != 0) {
size = (int)((val2 >> 4) & 0x07FF) + 3;
inIndex += 4;
}
else {
size = (int)read32(inData, inIndex + 4);
count = read32(inData, inIndex + 8);
inIndex += 12;
}
copy(outData, (int)(outIndex - count), outData, outIndex, size);
outIndex += size;
inIndex += 4;
}
else {
byte b = (byte)(val2 >> 16);
size = (int)(val2 >> 4) & 0x0FFF;
if (size == 0) {
size = (int)read32(inData, inIndex + 3);
inIndex += 7;
}
else
inIndex += 3;
for (int i = 0; i < size; i++)
outData[outIndex++] = b;
inIndex += 3;
}
}
else {
@ -121,7 +110,39 @@ namespace de4dot.code.deobfuscators.dotNET_Reactor {
outData[outIndex++] = inData[inIndex++];
val1 >>= 1;
}
}
}
class QuickLZ : QuickLZBase {
static int DEFAULT_QCLZ_SIG = 0x5A4C4351; // "QCLZ"
public static bool isCompressed(byte[] data) {
if (data.Length < 4)
return false;
return BitConverter.ToInt32(data, 0) == DEFAULT_QCLZ_SIG;
}
public static byte[] decompress(byte[] inData) {
return decompress(inData, DEFAULT_QCLZ_SIG);
}
public static byte[] decompress(byte[] inData, int sig) {
int mode = BitConverter.ToInt32(inData, 4);
int compressedLength = BitConverter.ToInt32(inData, 8);
int decompressedLength = BitConverter.ToInt32(inData, 12);
bool isDataCompressed = BitConverter.ToInt32(inData, 16) == 1;
int headerLength = 32;
if (BitConverter.ToInt32(inData, 0) != sig || BitConverter.ToInt32(inData, compressedLength - 4) != sig)
throw new ApplicationException("No QCLZ sig");
byte[] outData = new byte[decompressedLength];
if (!isDataCompressed) {
copy(inData, headerLength, outData, 0, decompressedLength);
return outData;
}
decompress(inData, headerLength, outData);
return outData;
}
}

View File

@ -0,0 +1,137 @@
/*
Copyright (C) 2011-2012 de4dot@gmail.com
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.Collections.Generic;
using Mono.Cecil;
using de4dot.blocks.cflow;
namespace de4dot.code.deobfuscators.Spices_Net {
public class DeobfuscatorInfo : DeobfuscatorInfoBase {
public const string THE_NAME = "Spices.Net";
public const string THE_TYPE = "sn";
public DeobfuscatorInfo()
: base() {
}
public override string Name {
get { return THE_NAME; }
}
public override string Type {
get { return THE_TYPE; }
}
public override IDeobfuscator createDeobfuscator() {
return new Deobfuscator(new Deobfuscator.Options {
ValidNameRegex = validNameRegex.get(),
});
}
protected override IEnumerable<Option> getOptionsInternal() {
return new List<Option>() {
};
}
}
class Deobfuscator : DeobfuscatorBase {
Options options;
bool foundSpicesAttribute = false;
bool startedDeobfuscating = false;
StringDecrypter stringDecrypter;
internal class Options : OptionsBase {
}
public override string Type {
get { return DeobfuscatorInfo.THE_TYPE; }
}
public override string TypeLong {
get { return DeobfuscatorInfo.THE_NAME; }
}
public override string Name {
get { return DeobfuscatorInfo.THE_NAME; }
}
public Deobfuscator(Options options)
: base(options) {
this.options = options;
}
protected override int detectInternal() {
int val = 0;
int sum = toInt32(stringDecrypter.Detected) +
toInt32(foundSpicesAttribute);
if (sum > 0)
val += 100 + 10 * (sum - 1);
return val;
}
protected override void scanForObfuscator() {
stringDecrypter = new StringDecrypter(module);
stringDecrypter.find();
findSpicesAttributes();
}
void findSpicesAttributes() {
foreach (var type in module.Types) {
if (type.FullName == "NineRays.Decompiler.NotDecompile") {
addAttributeToBeRemoved(type, "Obfuscator attribute");
foundSpicesAttribute = true;
break;
}
}
}
public override void deobfuscateBegin() {
base.deobfuscateBegin();
stringDecrypter.initialize();
foreach (var info in stringDecrypter.DecrypterInfos) {
staticStringInliner.add(info.method, (method2, args) => {
return stringDecrypter.decrypt(method2);
});
}
DeobfuscatedFile.stringDecryptersAdded();
startedDeobfuscating = true;
}
public override void deobfuscateEnd() {
if (Operations.DecryptStrings != OpDecryptString.None) {
addTypeToBeRemoved(stringDecrypter.Type, "String decrypter type");
stringDecrypter.cleanUp();
}
base.deobfuscateEnd();
}
public override IEnumerable<string> getStringDecrypterMethods() {
var list = new List<string>();
foreach (var info in stringDecrypter.DecrypterInfos)
list.Add(info.method.MetadataToken.ToInt32().ToString("X8"));
return list;
}
}
}

View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2011-2012 de4dot@gmail.com
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/>.
*/
namespace de4dot.code.deobfuscators.Spices_Net {
class QclzDecompressor : QuickLZBase {
static int SPICES_QCLZ_SIG = 0x3952534E; // "9RSN"
public static byte[] decompress(byte[] data) {
if (read32(data, 0) == SPICES_QCLZ_SIG)
return QuickLZ.decompress(data, SPICES_QCLZ_SIG);
int headerLength, decompressedLength, compressedLength;
if ((data[0] & 2) != 0) {
headerLength = 9;
compressedLength = (int)read32(data, 1);
decompressedLength = (int)read32(data, 5);
}
else {
headerLength = 3;
compressedLength = data[1];
decompressedLength = data[2];
}
bool isCompressed = (data[0] & 1) != 0;
byte[] decompressed = new byte[decompressedLength];
if (isCompressed)
decompress(data, headerLength, decompressed);
else
copy(data, headerLength, decompressed, 0, decompressed.Length);
return decompressed;
}
}
}

View File

@ -0,0 +1,326 @@
/*
Copyright (C) 2011-2012 de4dot@gmail.com
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.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Metadata;
using de4dot.blocks;
namespace de4dot.code.deobfuscators.Spices_Net {
class StringDecrypter {
ModuleDefinition module;
TypeDefinition decrypterType;
FieldDefinition encryptedDataField;
StringDataFlags stringDataFlags;
MethodDefinitionAndDeclaringTypeDict<DecrypterInfo> methodToInfo = new MethodDefinitionAndDeclaringTypeDict<DecrypterInfo>();
byte[] decryptedData;
byte[] key;
byte[] iv;
[Flags]
enum StringDataFlags {
Compressed = 0x1,
Encrypted1 = 0x2,
Encrypted2 = 0x4,
Encrypted3DES = 0x8,
}
public class DecrypterInfo {
public MethodDefinition method;
public int offset;
public int length;
public DecrypterInfo(MethodDefinition method, int offset, int length) {
this.method = method;
this.offset = offset;
this.length = length;
}
}
public TypeDefinition Type {
get { return decrypterType; }
}
public bool Detected {
get { return decrypterType != null; }
}
public IEnumerable<DecrypterInfo> DecrypterInfos {
get { return methodToInfo.getValues(); }
}
public StringDecrypter(ModuleDefinition module) {
this.module = module;
}
public void find() {
foreach (var type in module.Types) {
if (type.HasNestedTypes || type.HasInterfaces)
continue;
if (type.HasEvents || type.HasProperties)
continue;
if (type.Fields.Count != 2)
continue;
if ((type.Attributes & ~TypeAttributes.Sealed) != 0)
continue;
if (type.BaseType == null || type.BaseType.FullName != "System.Object")
continue;
if (hasInstanceMethods(type))
continue;
var cctor = DotNetUtils.getMethod(type, ".cctor");
if (cctor == null)
continue;
FieldDefinition encryptedDataFieldTmp;
StringDataFlags stringDataFlagsTmp;
if (!checkCctor(cctor, out encryptedDataFieldTmp, out stringDataFlagsTmp))
continue;
if (!initializeDecrypterInfos(type))
continue;
encryptedDataField = encryptedDataFieldTmp;
stringDataFlags = stringDataFlagsTmp;
decrypterType = type;
return;
}
}
static bool hasInstanceMethods(TypeDefinition type) {
foreach (var method in type.Methods) {
if (!method.IsStatic)
return true;
if (method.PInvokeInfo != null)
return true;
}
return false;
}
bool checkCctor(MethodDefinition cctor, out FieldDefinition compressedDataField, out StringDataFlags flags) {
flags = 0;
var instructions = cctor.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
var ldci4 = instructions[i];
if (!DotNetUtils.isLdcI4(ldci4))
continue;
var instrs = DotNetUtils.getInstructions(instructions, i + 1, OpCodes.Newarr, OpCodes.Dup, OpCodes.Ldtoken, OpCodes.Call);
if (instrs == null)
continue;
var newarr = instrs[0];
if (newarr.Operand.ToString() != "System.Byte")
continue;
var field = instrs[2].Operand as FieldDefinition;
if (field == null || field.InitialValue == null || field.InitialValue.Length == 0)
continue;
int index = i + 1 + instrs.Count;
if (index < instructions.Count && instructions[index].OpCode.Code == Code.Call)
flags = getStringDataFlags(instructions[index].Operand as MethodDefinition);
compressedDataField = field;
return true;
}
compressedDataField = null;
return false;
}
StringDataFlags getStringDataFlags(MethodDefinition method) {
if (method == null || method.Body == null)
return 0;
if (method.Parameters.Count != 1)
return 0;
if (!checkClass(method.Parameters[0].ParameterType, "System.Byte[]"))
return 0;
if (!checkClass(method.MethodReturnType.ReturnType, "System.Byte[]"))
return 0;
StringDataFlags flags = 0;
if (hasInstruction(method, Code.Not))
flags |= StringDataFlags.Encrypted2;
else if (hasInstruction(method, Code.Xor))
flags |= StringDataFlags.Encrypted1;
else if (check3DesCreator(method))
flags |= StringDataFlags.Encrypted3DES;
if (callsDecompressor(method))
flags |= StringDataFlags.Compressed;
return flags;
}
bool check3DesCreator(MethodDefinition method) {
foreach (var instr in method.Body.Instructions) {
if (instr.OpCode.Code != Code.Call)
continue;
var calledMethod = instr.Operand as MethodDefinition;
if (calledMethod == null)
continue;
if (calledMethod.MethodReturnType.ReturnType.EType == ElementType.Void)
continue;
if (calledMethod.Parameters.Count != 0)
continue;
if (!get3DesKeyIv(calledMethod, ref key, ref iv))
continue;
return true;
}
return false;
}
bool get3DesKeyIv(MethodDefinition method, ref byte[] key, ref byte[] iv) {
if (!new LocalTypes(method).exists("System.Security.Cryptography.TripleDESCryptoServiceProvider"))
return false;
var instrs = method.Body.Instructions;
var arrays = ArrayFinder.getArrays(method, module.TypeSystem.Byte);
if (arrays.Count != 1 && arrays.Count != 2)
return false;
key = arrays[0];
if (arrays.Count == 1)
iv = module.Assembly.Name.PublicKeyToken;
else
iv = arrays[1];
return true;
}
static bool callsDecompressor(MethodDefinition method) {
foreach (var instr in method.Body.Instructions) {
if (instr.OpCode.Code != Code.Call)
continue;
var called = instr.Operand as MethodDefinition;
if (called == null)
continue;
if (called.MethodReturnType.ReturnType.EType != ElementType.I4)
continue;
var parameters = called.Parameters;
if (parameters.Count != 4)
continue;
if (!checkClass(parameters[0].ParameterType, "System.Byte[]"))
continue;
if (parameters[1].ParameterType.EType != ElementType.I4)
continue;
if (!checkClass(parameters[2].ParameterType, "System.Byte[]"))
continue;
if (parameters[3].ParameterType.EType != ElementType.I4)
continue;
return true;
}
return false;
}
static bool hasInstruction(MethodDefinition method, Code code) {
foreach (var instr in method.Body.Instructions) {
if (instr.OpCode.Code == code)
return true;
}
return false;
}
static bool checkClass(TypeReference type, string fullName) {
return type != null && (type.EType == ElementType.Object || type.FullName == fullName);
}
static bool isStringType(TypeReference type) {
return type != null && (type.EType == ElementType.Object || type.EType == ElementType.String);
}
bool initializeDecrypterInfos(TypeDefinition type) {
foreach (var method in type.Methods) {
if (!method.IsStatic || method.Body == null)
continue;
if (method.Parameters.Count != 0)
continue;
if (!isStringType(method.MethodReturnType.ReturnType))
continue;
var info = createInfo(method);
if (info == null)
continue;
methodToInfo.add(method, info);
}
return methodToInfo.Count != 0;
}
DecrypterInfo createInfo(MethodDefinition method) {
var instrs = method.Body.Instructions;
for (int i = 0; i < instrs.Count - 1; i++) {
var ldci4_1 = instrs[i];
var ldci4_2 = instrs[i + 1];
if (!DotNetUtils.isLdcI4(ldci4_1) || !DotNetUtils.isLdcI4(ldci4_2))
continue;
int offset = DotNetUtils.getLdcI4Value(ldci4_1);
int length = DotNetUtils.getLdcI4Value(ldci4_2);
return new DecrypterInfo(method, offset, length);
}
return null;
}
public void initialize() {
if (decrypterType == null)
return;
decryptedData = new byte[encryptedDataField.InitialValue.Length];
Array.Copy(encryptedDataField.InitialValue, 0, decryptedData, 0, decryptedData.Length);
if ((stringDataFlags & StringDataFlags.Encrypted1) != 0) {
for (int i = 0; i < decryptedData.Length; i++)
decryptedData[i] ^= (byte)i;
}
if ((stringDataFlags & StringDataFlags.Encrypted2) != 0) {
var k = module.Assembly.Name.PublicKey;
int mask = (byte)(~k.Length);
for (int i = 0; i < decryptedData.Length; i++)
decryptedData[i] ^= k[i & mask];
}
if ((stringDataFlags & StringDataFlags.Encrypted3DES) != 0)
decryptedData = DeobUtils.des3Decrypt(decryptedData, key, iv);
if ((stringDataFlags & StringDataFlags.Compressed) != 0)
decryptedData = QclzDecompressor.decompress(decryptedData);
}
public void cleanUp() {
if (decrypterType == null)
return;
encryptedDataField.InitialValue = new byte[1];
encryptedDataField.FieldType = module.TypeSystem.Byte;
}
public string decrypt(MethodDefinition method) {
var info = methodToInfo.find(method);
return Encoding.Unicode.GetString(decryptedData, info.offset, info.length);
}
}
}

View File

@ -109,8 +109,6 @@ namespace de4dot.code.deobfuscators.Unknown {
return "Manco .NET Obfuscator";
if (Regex.IsMatch(type.FullName, @"^EMyPID_\d+_$"))
return "BitHelmet Obfuscator";
if (type.FullName == "NineRays.Decompiler.NotDecompile")
return "Spices.Net Obfuscator";
if (type.FullName == "YanoAttribute")
return "Yano Obfuscator";
}

View File

@ -41,6 +41,7 @@ namespace de4dot.cui {
new de4dot.code.deobfuscators.Goliath_NET.DeobfuscatorInfo(),
new de4dot.code.deobfuscators.Skater_NET.DeobfuscatorInfo(),
new de4dot.code.deobfuscators.SmartAssembly.DeobfuscatorInfo(),
new de4dot.code.deobfuscators.Spices_Net.DeobfuscatorInfo(),
new de4dot.code.deobfuscators.Xenocode.DeobfuscatorInfo(),
};
}