/* 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 . */ using System; using System.Collections.Generic; using System.IO; using System.Text; using Mono.MyStuff; using de4dot.PE; namespace de4dot.code.deobfuscators.MaxtoCode { // Decrypts methods and resources class FileDecrypter { MainType mainType; PeImage peImage; PeHeader peHeader; McKey mcKey; byte[] fileData; class PeHeader { const int XOR_KEY = 0x7ABF931; EncryptionVersion version; byte[] headerData; public EncryptionVersion EncryptionVersion { get { return version; } } public PeHeader(MainType mainType, PeImage peImage) { uint headerOffset; version = getHeaderOffsetAndVersion(peImage, out headerOffset); headerData = peImage.offsetReadBytes(headerOffset, 0x1000); } public uint getMcKeyRva() { return getRva2(0x0FFC, XOR_KEY); } public uint getRva1(int offset, uint xorKey) { return (readUInt32(offset) ^ xorKey); } public uint getRva2(int offset, uint xorKey) { return (readUInt32(offset) ^ xorKey); } public uint readUInt32(int offset) { return BitConverter.ToUInt32(headerData, offset); } static EncryptionVersion getHeaderOffsetAndVersion(PeImage peImage, out uint headerOffset) { headerOffset = 0; var version = getVersion(peImage, headerOffset); if (version != EncryptionVersion.Unknown) return version; var section = peImage.findSection(".rsrc"); if (section == null) return EncryptionVersion.Unknown; headerOffset = section.pointerToRawData; uint end = section.pointerToRawData + section.sizeOfRawData - 0x1000 + 1; while (headerOffset < end) { version = getVersion(peImage, headerOffset); if (version != EncryptionVersion.Unknown) return version; headerOffset++; } return EncryptionVersion.Unknown; } static EncryptionVersion getVersion(PeImage peImage, uint headerOffset) { uint m1lo = peImage.offsetReadUInt32(headerOffset + 0x900); uint m1hi = peImage.offsetReadUInt32(headerOffset + 0x904); foreach (var info in encryptionInfos_Rva900h) { if (info.MagicLo == m1lo && info.MagicHi == m1hi) return info.Version; } return EncryptionVersion.Unknown; } } class McKey { PeHeader peHeader; byte[] data; public byte this[int index] { get { return data[index]; } } public McKey(PeImage peImage, PeHeader peHeader) { this.peHeader = peHeader; this.data = peImage.readBytes(peHeader.getMcKeyRva(), 0x2000); } public byte readByte(int offset) { return data[offset]; } public uint readUInt32(int offset) { return BitConverter.ToUInt32(data, offset); } } enum EncryptionVersion { Unknown, V1, V2, V3, V4, V5, } class EncryptionInfo { public uint MagicLo { get; set; } public uint MagicHi { get; set; } public EncryptionVersion Version { get; set; } } static readonly EncryptionInfo[] encryptionInfos_Rva900h = new EncryptionInfo[] { // PE header timestamp // 462FA2D2 = Wed, 25 Apr 2007 18:49:54 (3.20) new EncryptionInfo { MagicLo = 0xA098B387, MagicHi = 0x1E8EBCA3, Version = EncryptionVersion.V1, }, // 482384FB = Thu, 08 May 2008 22:55:55 (3.36) new EncryptionInfo { MagicLo = 0xAA98B387, MagicHi = 0x1E8EECA3, Version = EncryptionVersion.V2, }, // 4A5EEC64 = Thu, 16 Jul 2009 09:01:24 // 4C6220EC = Wed, 11 Aug 2010 04:02:52 // 4C622357 = Wed, 11 Aug 2010 04:13:11 new EncryptionInfo { MagicLo = 0xAA98B387, MagicHi = 0x128EECA3, Version = EncryptionVersion.V2, }, // 4C6E4605 = Fri, 20 Aug 2010 09:08:21 // 4D0E220D = Sun, 19 Dec 2010 15:17:33 // 4DC2FC75 = Thu, 05 May 2011 19:37:25 // 4DFA3D5D = Thu, 16 Jun 2011 17:29:01 new EncryptionInfo { MagicLo = 0xAA98B387, MagicHi = 0xF28EECA3, Version = EncryptionVersion.V2, }, // 4DC2FE0C = Thu, 05 May 2011 19:44:12 new EncryptionInfo { MagicLo = 0xAA98B387, MagicHi = 0xF28EEAA3, Version = EncryptionVersion.V2, }, // 4ED76740 = Thu, 01 Dec 2011 11:38:40 // 4EE1FAD1 = Fri, 09 Dec 2011 12:10:57 new EncryptionInfo { MagicLo = 0xAA983B87, MagicHi = 0xF28EECA3, Version = EncryptionVersion.V3, }, // 4F832868 = Mon, Apr 09 2012 20:20:24 new EncryptionInfo { MagicLo = 0xAA913B87, MagicHi = 0xF28EE0A3, Version = EncryptionVersion.V4, }, // 4F8E262C = Wed, 18 Apr 2012 02:25:48 new EncryptionInfo { MagicLo = 0xBA983B87, MagicHi = 0xF28EDDA3, Version = EncryptionVersion.V5, }, }; static readonly EncryptionInfo[] encryptionInfos_McKey8C0h = new EncryptionInfo[] { // 462FA2D2 = Wed, 25 Apr 2007 18:49:54 (3.20) new EncryptionInfo { MagicLo = 0x6AA13B13, MagicHi = 0xD72B991F, Version = EncryptionVersion.V1, }, // 482384FB = Thu, 08 May 2008 22:55:55 (3.36) new EncryptionInfo { MagicLo = 0x6A713B13, MagicHi = 0xD72B891F, Version = EncryptionVersion.V2, }, // 4A5EEC64 = Thu, 16 Jul 2009 09:01:24 // 4C6220EC = Wed, 11 Aug 2010 04:02:52 // 4C622357 = Wed, 11 Aug 2010 04:13:11 // 4C6E4605 = Fri, 20 Aug 2010 09:08:21 // 4D0E220D = Sun, 19 Dec 2010 15:17:33 // 4DC2FC75 = Thu, 05 May 2011 19:37:25 // 4DC2FE0C = Thu, 05 May 2011 19:44:12 // 4DFA3D5D = Thu, 16 Jun 2011 17:29:01 new EncryptionInfo { MagicLo = 0x6A713B13, MagicHi = 0xD72B891F, Version = EncryptionVersion.V2, }, // 4ED76740 = Thu, 01 Dec 2011 11:38:40 // 4EE1FAD1 = Fri, 09 Dec 2011 12:10:57 new EncryptionInfo { MagicLo = 0x6A731B13, MagicHi = 0xD72B891F, Version = EncryptionVersion.V3, }, // 4F832868 = Mon, Apr 09 2012 20:20:24 new EncryptionInfo { MagicLo = 0x6AD31B13, MagicHi = 0xD72B8A1F, Version = EncryptionVersion.V4, }, // 4F8E262C = Wed, 18 Apr 2012 02:25:48 new EncryptionInfo { MagicLo = 0xAA731B13, MagicHi = 0xD723891F, Version = EncryptionVersion.V5, }, }; class MethodInfos { MainType mainType; PeImage peImage; PeHeader peHeader; McKey mcKey; uint structSize; uint methodInfosOffset; uint encryptedDataOffset; uint xorKey; Dictionary infos = new Dictionary(); IDecrypter decrypter; const int ENCRYPTED_DATA_INFO_SIZE = 0x13; public class DecryptedMethodInfo { public uint bodyRva; public byte[] body; public DecryptedMethodInfo(uint bodyRva, byte[] body) { this.bodyRva = bodyRva; this.body = body; } } public MethodInfos(MainType mainType, PeImage peImage, PeHeader peHeader, McKey mcKey) { this.mainType = mainType; this.peImage = peImage; this.peHeader = peHeader; this.mcKey = mcKey; structSize = getStructSize(mcKey); uint methodInfosRva = peHeader.getRva2(0x0FF8, mcKey.readUInt32(0x005A)); uint encryptedDataRva = peHeader.getRva2(0x0FF0, mcKey.readUInt32(0x0046)); methodInfosOffset = peImage.rvaToOffset(methodInfosRva); encryptedDataOffset = peImage.rvaToOffset(encryptedDataRva); } static uint getStructSize(McKey mcKey) { uint magicLo = mcKey.readUInt32(0x8C0); uint magicHi = mcKey.readUInt32(0x8C4); foreach (var info in encryptionInfos_McKey8C0h) { if (magicLo == info.MagicLo && magicHi == info.MagicHi) return 0xC + 6 * ENCRYPTED_DATA_INFO_SIZE; } return 0xC + 3 * ENCRYPTED_DATA_INFO_SIZE; } EncryptionVersion getVersion() { if (peHeader.EncryptionVersion != EncryptionVersion.Unknown) return peHeader.EncryptionVersion; uint m2lo = mcKey.readUInt32(0x8C0); uint m2hi = mcKey.readUInt32(0x8C4); foreach (var info in encryptionInfos_McKey8C0h) { if (info.MagicLo == m2lo && info.MagicHi == m2hi) return info.Version; } Log.w("Could not detect MC version. Magic2: {0:X8} {1:X8}", m2lo, m2hi); return EncryptionVersion.Unknown; } public DecryptedMethodInfo lookup(uint bodyRva) { DecryptedMethodInfo info; infos.TryGetValue(bodyRva, out info); return info; } byte readByte(uint offset) { return peImage.offsetReadByte(methodInfosOffset + offset); } short readInt16(uint offset) { return (short)peImage.offsetReadUInt16(methodInfosOffset + offset); } uint readUInt32(uint offset) { return peImage.offsetReadUInt32(methodInfosOffset + offset); } int readInt32(uint offset) { return (int)readUInt32(offset); } short readEncryptedInt16(uint offset) { return (short)(readInt16(offset) ^ xorKey); } int readEncryptedInt32(uint offset) { return (int)readEncryptedUInt32(offset); } uint readEncryptedUInt32(uint offset) { return readUInt32(offset) ^ xorKey; } interface IDecrypter { byte[] decrypt(int type, byte[] encrypted); } class Decrypter : IDecrypter { MethodInfos methodInfos; int[] typeToMethod; public Decrypter(MethodInfos methodInfos, int[] typeToMethod) { this.methodInfos = methodInfos; this.typeToMethod = typeToMethod; } public byte[] decrypt(int type, byte[] encrypted) { if (0 <= type && type < typeToMethod.Length) { switch (typeToMethod[type]) { case 1: return methodInfos.decrypt1(encrypted); case 2: return methodInfos.decrypt2(encrypted); case 3: return methodInfos.decrypt3(encrypted); case 4: return methodInfos.decrypt4(encrypted); case 5: return methodInfos.decrypt5(encrypted); case 6: return methodInfos.decrypt6(encrypted); case 7: return methodInfos.decrypt7(encrypted); } } throw new ApplicationException(string.Format("Invalid encryption type: {0:X2}", type)); } } static readonly int[] typeToTypesV1 = new int[] { -1, 1, 4, 2, 3, 5, 6, 7 }; static readonly int[] typeToTypesV2 = new int[] { -1, 3, 2, 1, 4, 5, 6, 7 }; static readonly int[] typeToTypesV3 = new int[] { -1, 1, 2, 3, 4, 5, 6, 7 }; static readonly int[] typeToTypesV4 = new int[] { -1, 2, 1, 3, 4, 5, 6, 7 }; static readonly int[] typeToTypesV5 = new int[] { -1, 4, 2, 3, 1, 5, 6, 7 }; void initializeDecrypter() { switch (getVersion()) { case EncryptionVersion.V1: decrypter = new Decrypter(this, typeToTypesV1); break; case EncryptionVersion.V2: decrypter = new Decrypter(this, typeToTypesV2); break; case EncryptionVersion.V3: decrypter = new Decrypter(this, typeToTypesV3); break; case EncryptionVersion.V4: decrypter = new Decrypter(this, typeToTypesV4); break; case EncryptionVersion.V5: decrypter = new Decrypter(this, typeToTypesV5); break; case EncryptionVersion.Unknown: default: throw new ApplicationException("Unknown MC version"); } } public void initializeInfos() { initializeDecrypter(); int numMethods = readInt32(0) ^ readInt32(4); if (numMethods < 0) throw new ApplicationException("Invalid number of encrypted methods"); xorKey = (uint)numMethods; int numEncryptedDataInfos = ((int)structSize - 0xC) / ENCRYPTED_DATA_INFO_SIZE; var encryptedDataInfos = new byte[numEncryptedDataInfos][]; uint offset = 8; for (int i = 0; i < numMethods; i++, offset += structSize) { uint methodBodyRva = readEncryptedUInt32(offset); uint totalSize = readEncryptedUInt32(offset + 4); uint methodInstructionRva = readEncryptedUInt32(offset + 8); var decryptedData = new byte[totalSize]; // Read the method body header and method body (instrs + exception handlers). // The method body header is always in the first one. The instrs + ex handlers // are always in the last 4, and evenly divided (each byte[] is totalLen / 4). // The 2nd one is for the exceptions (or padding), but it may be null. uint offset2 = offset + 0xC; int exOffset = 0; for (int j = 0; j < encryptedDataInfos.Length; j++, offset2 += ENCRYPTED_DATA_INFO_SIZE) { // readByte(offset2); <-- index int encryptionType = readEncryptedInt16(offset2 + 1); uint dataOffset = readEncryptedUInt32(offset2 + 3); uint encryptedSize = readEncryptedUInt32(offset2 + 7); uint realSize = readEncryptedUInt32(offset2 + 11); if (j == 1) exOffset = readEncryptedInt32(offset2 + 15); if (j == 1 && exOffset == 0) encryptedDataInfos[j] = null; else encryptedDataInfos[j] = decrypt(encryptionType, dataOffset, encryptedSize, realSize); } int copyOffset = 0; copyOffset = copyData(decryptedData, encryptedDataInfos[0], copyOffset); for (int j = 2; j < encryptedDataInfos.Length; j++) copyOffset = copyData(decryptedData, encryptedDataInfos[j], copyOffset); copyData(decryptedData, encryptedDataInfos[1], exOffset); // Exceptions or padding var info = new DecryptedMethodInfo(methodBodyRva, decryptedData); infos[info.bodyRva] = info; } } static int copyData(byte[] dest, byte[] source, int offset) { if (source == null) return offset; Array.Copy(source, 0, dest, offset, source.Length); return offset + source.Length; } byte[] readData(uint offset, int size) { return peImage.offsetReadBytes(encryptedDataOffset + offset, size); } byte[] decrypt(int type, uint dataOffset, uint encryptedSize, uint realSize) { if (realSize == 0) return null; if (realSize > encryptedSize) throw new ApplicationException("Invalid realSize"); var encrypted = readData(dataOffset, (int)encryptedSize); var decrypted = decrypter.decrypt(type, encrypted); if (realSize > decrypted.Length) throw new ApplicationException("Invalid decrypted length"); Array.Resize(ref decrypted, (int)realSize); return decrypted; } byte[] decrypt1(byte[] encrypted) { var decrypted = new byte[encrypted.Length]; for (int i = 0; i < decrypted.Length; i++) decrypted[i] = (byte)(encrypted[i] ^ mcKey.readByte(i % 0x2000)); return decrypted; } byte[] decrypt2(byte[] encrypted) { if ((encrypted.Length & 7) != 0) throw new ApplicationException("Invalid encryption #2 length"); const int offset = 0x00FA; uint key4 = mcKey.readUInt32(offset + 4 * 4); uint key5 = mcKey.readUInt32(offset + 5 * 4); byte[] decrypted = new byte[encrypted.Length & ~7]; var writer = new BinaryWriter(new MemoryStream(decrypted)); int loopCount = encrypted.Length / 8; for (int i = 0; i < loopCount; i++) { uint val0 = BitConverter.ToUInt32(encrypted, i * 8); uint val1 = BitConverter.ToUInt32(encrypted, i * 8 + 4); uint x = (val1 >> 26) + (val0 << 6); uint y = (val0 >> 26) + (val1 << 6); writer.Write(x ^ key4); writer.Write(y ^ key5); } return decrypted; } static byte[] decrypt3Shifts = new byte[16] { 5, 11, 14, 21, 6, 20, 17, 29, 4, 10, 3, 2, 7, 1, 26, 18 }; byte[] decrypt3(byte[] encrypted) { if ((encrypted.Length & 7) != 0) throw new ApplicationException("Invalid encryption #3 length"); const int offset = 0x015E; uint key0 = mcKey.readUInt32(offset + 0 * 4); uint key3 = mcKey.readUInt32(offset + 3 * 4); byte[] decrypted = new byte[encrypted.Length & ~7]; var writer = new BinaryWriter(new MemoryStream(decrypted)); int loopCount = encrypted.Length / 8; for (int i = 0; i < loopCount; i++) { uint x = BitConverter.ToUInt32(encrypted, i * 8); uint y = BitConverter.ToUInt32(encrypted, i * 8 + 4); foreach (var shift in decrypt3Shifts) { int shift1 = 32 - shift; uint x1 = (y >> shift1) + (x << shift); uint y1 = (x >> shift1) + (y << shift); x = x1; y = y1; } writer.Write(x ^ key0); writer.Write(y ^ key3); } return decrypted; } byte[] decrypt4(byte[] encrypted) { var decrypted = new byte[encrypted.Length / 3 * 2 + 1]; int count = encrypted.Length / 3; int i = 0, j = 0, k = 0; while (count-- > 0) { byte k1 = mcKey.readByte(j + 1); byte k2 = mcKey.readByte(j + 2); byte k3 = mcKey.readByte(j + 3); decrypted[k++] = (byte)(((encrypted[i + 1] ^ k2) >> 4) | ((encrypted[i] ^ k1) & 0xF0)); decrypted[k++] = (byte)(((encrypted[i + 1] ^ k2) << 4) + ((encrypted[i + 2] ^ k3) & 0x0F)); i += 3; j = (j + 4) % 0x2000; } if ((encrypted.Length % 3) != 0) decrypted[k] = (byte)(encrypted[i] ^ mcKey.readByte(j)); return decrypted; } byte[] decrypt5(byte[] encrypted) { throw new NotImplementedException("Encryption type #5 not implemented yet"); } byte[] decrypt6(byte[] encrypted) { throw new NotImplementedException("Encryption type #6 not implemented yet"); } byte[] decrypt7(byte[] encrypted) { throw new NotImplementedException("Encryption type #7 not implemented yet"); } } public FileDecrypter(MainType mainType) { this.mainType = mainType; } public bool decrypt(byte[] fileData, ref DumpedMethods dumpedMethods) { peImage = new PeImage(fileData); peHeader = new PeHeader(mainType, peImage); mcKey = new McKey(peImage, peHeader); this.fileData = fileData; dumpedMethods = decryptMethods(); if (dumpedMethods == null) return false; decryptResources(); decryptStrings(); return true; } DumpedMethods decryptMethods() { var dumpedMethods = new DumpedMethods(); var methodInfos = new MethodInfos(mainType, peImage, peHeader, mcKey); methodInfos.initializeInfos(); var metadataTables = peImage.Cor20Header.createMetadataTables(); var methodDef = metadataTables.getMetadataType(MetadataIndex.iMethodDef); uint methodDefOffset = methodDef.fileOffset; for (int i = 0; i < methodDef.rows; i++, methodDefOffset += methodDef.totalSize) { uint bodyRva = peImage.offsetReadUInt32(methodDefOffset); if (bodyRva == 0) continue; var info = methodInfos.lookup(bodyRva); if (info == null) continue; uint bodyOffset = peImage.rvaToOffset(bodyRva); ushort magic = peImage.offsetReadUInt16(bodyOffset); if (magic != 0xFFF3) continue; var dm = new DumpedMethod(); dm.token = (uint)(0x06000001 + i); dm.mdImplFlags = peImage.offsetReadUInt16(methodDefOffset + (uint)methodDef.fields[1].offset); dm.mdFlags = peImage.offsetReadUInt16(methodDefOffset + (uint)methodDef.fields[2].offset); dm.mdName = peImage.offsetRead(methodDefOffset + (uint)methodDef.fields[3].offset, methodDef.fields[3].size); dm.mdSignature = peImage.offsetRead(methodDefOffset + (uint)methodDef.fields[4].offset, methodDef.fields[4].size); dm.mdParamList = peImage.offsetRead(methodDefOffset + (uint)methodDef.fields[5].offset, methodDef.fields[5].size); var reader = new BinaryReader(new MemoryStream(info.body)); byte b = reader.ReadByte(); if ((b & 3) == 2) { dm.mhFlags = 2; dm.mhMaxStack = 8; dm.mhCodeSize = (uint)(b >> 2); dm.mhLocalVarSigTok = 0; } else { reader.BaseStream.Position--; dm.mhFlags = reader.ReadUInt16(); dm.mhMaxStack = reader.ReadUInt16(); dm.mhCodeSize = reader.ReadUInt32(); dm.mhLocalVarSigTok = reader.ReadUInt32(); uint codeOffset = (uint)(dm.mhFlags >> 12) * 4; reader.BaseStream.Position += codeOffset - 12; } dm.code = reader.ReadBytes((int)dm.mhCodeSize); if ((dm.mhFlags & 8) != 0) { reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3; dm.extraSections = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); } dumpedMethods.add(dm); } return dumpedMethods; } void decryptResources() { uint resourceRva = peHeader.getRva1(0x0E10, mcKey.readUInt32(0x00A0)); uint resourceSize = peHeader.readUInt32(0x0E14) ^ mcKey.readUInt32(0x00AA); if (resourceRva == 0 || resourceSize == 0) return; if (resourceRva != peImage.Cor20Header.resources.virtualAddress || resourceSize != peImage.Cor20Header.resources.size) { Log.w("Invalid resource RVA and size found"); } Log.v("Decrypting resources @ RVA {0:X8}, {1} bytes", resourceRva, resourceSize); int resourceOffset = (int)peImage.rvaToOffset(resourceRva); for (int i = 0; i < resourceSize; i++) fileData[resourceOffset + i] ^= mcKey[i % 0x2000]; } void decryptStrings() { uint usHeapRva = peHeader.getRva1(0x0E00, mcKey.readUInt32(0x0078)); uint usHeapSize = peHeader.readUInt32(0x0E04) ^ mcKey.readUInt32(0x0082); if (usHeapRva == 0 || usHeapSize == 0) return; var usHeap = peImage.Cor20Header.metadata.getStream("#US"); if (usHeap == null || peImage.rvaToOffset(usHeapRva) != usHeap.fileOffset || usHeapSize != usHeap.Length) { Log.w("Invalid #US heap RVA and size found"); } Log.v("Decrypting strings @ RVA {0:X8}, {1} bytes", usHeapRva, usHeapSize); Log.indent(); int mcKeyOffset = 0; int usHeapOffset = (int)peImage.rvaToOffset(usHeapRva); int usHeapEnd = usHeapOffset + (int)usHeapSize; usHeapOffset++; while (usHeapOffset < usHeapEnd) { if (fileData[usHeapOffset] == 0 || fileData[usHeapOffset] == 1) { usHeapOffset++; continue; } int usHeapOffsetOrig = usHeapOffset; int stringDataLength = DeobUtils.readVariableLengthInt32(fileData, ref usHeapOffset); int usHeapOffsetString = usHeapOffset; int encryptedLength = stringDataLength - (usHeapOffset - usHeapOffsetOrig == 1 ? 1 : 2); for (int i = 0; i < encryptedLength; i++) { byte k = mcKey.readByte(mcKeyOffset++ % 0x2000); fileData[usHeapOffset] = rolb((byte)(fileData[usHeapOffset] ^ k), 3); usHeapOffset++; } try { Log.v("Decrypted string: {0}", Utils.toCsharpString(Encoding.Unicode.GetString(fileData, usHeapOffsetString, stringDataLength - 1))); } catch { Log.v("Could not decrypt string at offset {0:X8}", usHeapOffsetOrig); } usHeapOffset++; } Log.deIndent(); } byte rolb(byte b, int n) { return (byte)((b << n) | (b >> (8 - n))); } } }