344 lines
10 KiB
C#
344 lines
10 KiB
C#
/*
|
|
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.IO;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using dot10.IO;
|
|
using dot10.PE;
|
|
using dot10.DotNet;
|
|
using dot10.DotNet.MD;
|
|
|
|
namespace de4dot.code.deobfuscators.dotNET_Reactor.v3 {
|
|
sealed class MyPEImage : IDisposable {
|
|
IPEImage peImage;
|
|
byte[] peImageData;
|
|
IImageStream peStream;
|
|
DotNetFile dnFile;
|
|
ImageSectionHeader dotNetSection;
|
|
bool ownPeImage;
|
|
|
|
public IPEImage PEImage {
|
|
get { return peImage; }
|
|
}
|
|
|
|
public uint Length {
|
|
get { return (uint)peStream.Length; }
|
|
}
|
|
|
|
public MyPEImage(IPEImage peImage) {
|
|
initialize(peImage);
|
|
}
|
|
|
|
public MyPEImage(byte[] peImageData) {
|
|
this.ownPeImage = true;
|
|
this.peImageData = peImageData;
|
|
initialize(new PEImage(peImageData));
|
|
}
|
|
|
|
void initialize(IPEImage peImage) {
|
|
this.peImage = peImage;
|
|
this.peStream = peImage.CreateFullStream();
|
|
|
|
//TODO: Only init this if they use the .NET MD
|
|
var dotNetDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[14];
|
|
if (dotNetDir.VirtualAddress != 0 && dotNetDir.Size >= 0x48) {
|
|
dnFile = DotNetFile.Load(peImage, false);
|
|
dotNetSection = findSection(dotNetDir.VirtualAddress);
|
|
}
|
|
}
|
|
|
|
ImageSectionHeader findSection(RVA rva) {
|
|
foreach (var section in peImage.ImageSectionHeaders) {
|
|
if (section.VirtualAddress <= rva && rva < section.VirtualAddress + Math.Max(section.VirtualSize, section.SizeOfRawData))
|
|
return section;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static bool isInside(ImageSectionHeader section, uint offset, uint length) {
|
|
return offset >= section.PointerToRawData && offset + length <= section.PointerToRawData + section.SizeOfRawData;
|
|
}
|
|
|
|
public void offsetWriteUInt32(uint offset, uint val) {
|
|
peImageData[offset + 0] = (byte)val;
|
|
peImageData[offset + 1] = (byte)(val >> 8);
|
|
peImageData[offset + 2] = (byte)(val >> 16);
|
|
peImageData[offset + 3] = (byte)(val >> 24);
|
|
}
|
|
|
|
public void offsetWriteUInt16(uint offset, ushort val) {
|
|
peImageData[offset + 0] = (byte)val;
|
|
peImageData[offset + 1] = (byte)(val >> 8);
|
|
}
|
|
|
|
public uint offsetReadUInt32(uint offset) {
|
|
peStream.Position = offset;
|
|
return peStream.ReadUInt32();
|
|
}
|
|
|
|
public ushort offsetReadUInt16(uint offset) {
|
|
peStream.Position = offset;
|
|
return peStream.ReadUInt16();
|
|
}
|
|
|
|
public byte[] offsetReadBytes(uint offset, int size) {
|
|
peStream.Position = offset;
|
|
return peStream.ReadBytes(size);
|
|
}
|
|
|
|
public void offsetWrite(uint offset, byte[] data) {
|
|
Array.Copy(data, 0, peImageData, offset, data.Length);
|
|
}
|
|
|
|
bool intersect(uint offset1, uint length1, uint offset2, uint length2) {
|
|
return !(offset1 + length1 <= offset2 || offset2 + length2 <= offset1);
|
|
}
|
|
|
|
bool intersect(uint offset, uint length, IFileSection location) {
|
|
return intersect(offset, length, (uint)location.StartOffset, (uint)(location.EndOffset - location.StartOffset));
|
|
}
|
|
|
|
public bool dotNetSafeWriteOffset(uint offset, byte[] data) {
|
|
if (dnFile != null) {
|
|
uint length = (uint)data.Length;
|
|
|
|
if (!isInside(dotNetSection, offset, length))
|
|
return false;
|
|
if (intersect(offset, length, dnFile.MetaData.ImageCor20Header))
|
|
return false;
|
|
if (intersect(offset, length, (uint)dnFile.MetaData.TablesStream.FileOffset, dnFile.MetaData.TablesStream.HeaderLength))
|
|
return false;
|
|
}
|
|
|
|
offsetWrite(offset, data);
|
|
return true;
|
|
}
|
|
|
|
public bool dotNetSafeWrite(uint rva, byte[] data) {
|
|
return dotNetSafeWriteOffset((uint)peImage.ToFileOffset((RVA)rva), data);
|
|
}
|
|
|
|
public void Dispose() {
|
|
if (ownPeImage) {
|
|
if (dnFile != null)
|
|
dnFile.Dispose();
|
|
if (peImage != null)
|
|
peImage.Dispose();
|
|
}
|
|
if (peStream != null)
|
|
peStream.Dispose();
|
|
|
|
dnFile = null;
|
|
peImage = null;
|
|
peStream = null;
|
|
}
|
|
}
|
|
|
|
class IniFile {
|
|
Dictionary<string, string> nameToValue = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public string this[string name] {
|
|
get {
|
|
string value;
|
|
nameToValue.TryGetValue(name, out value);
|
|
return value;
|
|
}
|
|
}
|
|
|
|
public IniFile(byte[] data) {
|
|
using (var reader = new StreamReader(new MemoryStream(data), Encoding.UTF8)) {
|
|
while (true) {
|
|
var line = reader.ReadLine();
|
|
if (line == null)
|
|
break;
|
|
var match = Regex.Match(line, @"^([^=]+)=([^;]+);?\s*$");
|
|
if (match.Groups.Count < 3)
|
|
continue;
|
|
var name = match.Groups[1].ToString().Trim();
|
|
var value = match.Groups[2].ToString().Trim();
|
|
nameToValue[name] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unpacks "application mode" files (DNR 3.x)
|
|
class ApplicationModeUnpacker {
|
|
static byte[] key1 = new byte[32] {
|
|
0x6B, 0x6C, 0xA7, 0x24, 0x25, 0x37, 0x67, 0x68,
|
|
0x4A, 0x2F, 0x28, 0x29, 0x33, 0x77, 0x34, 0x35,
|
|
0x5A, 0x5A, 0x48, 0x57, 0x24, 0x35, 0x24, 0x25,
|
|
0x26, 0x67, 0x77, 0x53, 0x41, 0x44, 0x46, 0x32,
|
|
};
|
|
static byte[] iv1 = new byte[16] {
|
|
0x73, 0x64, 0xA7, 0x35, 0x24, 0xA7, 0x26, 0x67,
|
|
0x34, 0x35, 0x37, 0x21, 0x32, 0x33, 0x6E, 0x6D,
|
|
};
|
|
static byte[] key2 = new byte[32] {
|
|
0x28, 0x24, 0x29, 0x28, 0x2F, 0x29, 0x28, 0x29,
|
|
0x3D, 0x66, 0x67, 0x35, 0x35, 0x6A, 0x6D, 0x2C,
|
|
0xA7, 0x39, 0x38, 0x2A, 0x6A, 0x67, 0x74, 0x36,
|
|
0x35, 0x3D, 0xA7, 0x43, 0x33, 0x33, 0x24, 0x74,
|
|
};
|
|
static byte[] iv2 = new byte[16] {
|
|
0x67, 0x26, 0x35, 0xA7, 0x24, 0xA7, 0x37, 0x21,
|
|
0x73, 0x33, 0x6E, 0x6D, 0x34, 0x32, 0x64, 0x35,
|
|
};
|
|
|
|
IPEImage peImage;
|
|
List<UnpackedFile> satelliteAssemblies = new List<UnpackedFile>();
|
|
uint[] sizes;
|
|
string[] filenames;
|
|
bool shouldUnpack;
|
|
|
|
public IEnumerable<UnpackedFile> EmbeddedAssemblies {
|
|
get { return satelliteAssemblies; }
|
|
}
|
|
|
|
public ApplicationModeUnpacker(IPEImage peImage) {
|
|
this.peImage = peImage;
|
|
}
|
|
|
|
public byte[] unpack() {
|
|
byte[] data = null;
|
|
MyPEImage myPeImage = null;
|
|
try {
|
|
myPeImage = new MyPEImage(peImage);
|
|
data = unpack2(myPeImage);
|
|
}
|
|
catch {
|
|
}
|
|
finally {
|
|
if (myPeImage != null)
|
|
myPeImage.Dispose();
|
|
}
|
|
if (data != null)
|
|
return data;
|
|
|
|
if (shouldUnpack)
|
|
Logger.w("Could not unpack file: {0}", peImage.FileName ?? "(unknown filename)");
|
|
return null;
|
|
}
|
|
|
|
byte[] unpack2(MyPEImage peImage) {
|
|
shouldUnpack = false;
|
|
uint headerOffset = (uint)peImage.Length - 12;
|
|
uint offsetEncryptedAssembly = checkOffset(peImage, peImage.offsetReadUInt32(headerOffset));
|
|
uint ezencryptionLibLength = peImage.offsetReadUInt32(headerOffset + 4);
|
|
uint iniFileLength = peImage.offsetReadUInt32(headerOffset + 8);
|
|
|
|
uint offsetClrVersionNumber = checked(offsetEncryptedAssembly - 12);
|
|
uint iniFileOffset = checked(headerOffset - iniFileLength);
|
|
uint ezencryptionLibOffset = checked(iniFileOffset - ezencryptionLibLength);
|
|
|
|
uint clrVerMajor = peImage.offsetReadUInt32(offsetClrVersionNumber);
|
|
uint clrVerMinor = peImage.offsetReadUInt32(offsetClrVersionNumber + 4);
|
|
uint clrVerBuild = peImage.offsetReadUInt32(offsetClrVersionNumber + 8);
|
|
if (clrVerMajor <= 0 || clrVerMajor >= 20 || clrVerMinor >= 20 || clrVerBuild >= 1000000)
|
|
return null;
|
|
|
|
var settings = new IniFile(decompress2(peImage.offsetReadBytes(iniFileOffset, (int)iniFileLength)));
|
|
sizes = getSizes(settings["General_App_Satellite_Assemblies_Sizes"]);
|
|
if (sizes == null || sizes.Length <= 1)
|
|
return null;
|
|
shouldUnpack = true;
|
|
if (sizes[0] != offsetEncryptedAssembly)
|
|
return null;
|
|
filenames = settings["General_App_Satellite_Assemblies"].Split('|');
|
|
if (sizes.Length - 1 != filenames.Length)
|
|
return null;
|
|
|
|
byte[] ezencryptionLibData = decompress1(peImage.offsetReadBytes(ezencryptionLibOffset, (int)ezencryptionLibLength));
|
|
var ezencryptionLibModule = ModuleDefMD.Load(ezencryptionLibData);
|
|
var decrypter = new ApplicationModeDecrypter(ezencryptionLibModule);
|
|
if (!decrypter.Detected)
|
|
return null;
|
|
|
|
var mainAssembly = unpackEmbeddedFile(peImage, 0, decrypter);
|
|
decrypter.MemoryPatcher.patch(mainAssembly.data);
|
|
for (int i = 1; i < filenames.Length; i++)
|
|
satelliteAssemblies.Add(unpackEmbeddedFile(peImage, i, decrypter));
|
|
|
|
clearDllBit(mainAssembly.data);
|
|
return mainAssembly.data;
|
|
}
|
|
|
|
static void clearDllBit(byte[] peImageData) {
|
|
using (var mainPeImage = new MyPEImage(peImageData)) {
|
|
uint characteristicsOffset = (uint)mainPeImage.PEImage.ImageNTHeaders.FileHeader.StartOffset + 18;
|
|
ushort characteristics = mainPeImage.offsetReadUInt16(characteristicsOffset);
|
|
characteristics &= 0xDFFF;
|
|
characteristics |= 2;
|
|
mainPeImage.offsetWriteUInt16(characteristicsOffset, characteristics);
|
|
}
|
|
}
|
|
|
|
UnpackedFile unpackEmbeddedFile(MyPEImage peImage, int index, ApplicationModeDecrypter decrypter) {
|
|
uint offset = 0;
|
|
for (int i = 0; i < index + 1; i++)
|
|
offset += sizes[i];
|
|
string filename = Win32Path.GetFileName(filenames[index]);
|
|
var data = peImage.offsetReadBytes(offset, (int)sizes[index + 1]);
|
|
data = DeobUtils.aesDecrypt(data, decrypter.AssemblyKey, decrypter.AssemblyIv);
|
|
data = decompress(data);
|
|
return new UnpackedFile(filename, data);
|
|
}
|
|
|
|
static uint[] getSizes(string sizes) {
|
|
if (sizes == null)
|
|
return null;
|
|
var list = new List<uint>();
|
|
foreach (var num in sizes.Split('|'))
|
|
list.Add(uint.Parse(num));
|
|
return list.ToArray();
|
|
}
|
|
|
|
uint checkOffset(MyPEImage peImage, uint offset) {
|
|
if (offset >= peImage.Length)
|
|
throw new Exception();
|
|
return offset;
|
|
}
|
|
|
|
static byte[] decompress1(byte[] data) {
|
|
return decompress(decrypt1(data));
|
|
}
|
|
|
|
static byte[] decompress2(byte[] data) {
|
|
return decompress(decrypt2(data));
|
|
}
|
|
|
|
static byte[] decompress(byte[] data) {
|
|
if (!QuickLZ.isCompressed(data))
|
|
return data;
|
|
return QuickLZ.decompress(data);
|
|
}
|
|
|
|
static byte[] decrypt1(byte[] data) {
|
|
return DeobUtils.aesDecrypt(data, key1, iv1);
|
|
}
|
|
|
|
static byte[] decrypt2(byte[] data) {
|
|
return DeobUtils.aesDecrypt(data, key2, iv2);
|
|
}
|
|
}
|
|
}
|