de4dot-cex/dumpMethodsN/dumpMethodsN.cpp
2011-09-22 04:55:30 +02:00

599 lines
16 KiB
C++

/*
Copyright (C) 2011 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/>.
*/
#include <Windows.h>
#include <tlhelp32.h>
#include <metahost.h>
#include <stdio.h>
#include <tchar.h>
#include <vector>
#include "DotNetFile.h"
#include "MakeWritable.h"
#include "DllAlloc.h"
#include "strex.h"
#include "log.h"
#include "common.h"
#pragma pack(push)
#pragma pack(1)
struct CORINFO_METHOD_INFO {
void* ftn;
void* scope;
unsigned char* ILCode;
unsigned ILCodeSize;
unsigned short maxStack;
unsigned short EHcount;
char other[0x64];
};
#pragma pack(pop)
struct ScopeInfo {
void* imageBase;
CORINFO_METHOD_INFO cmi;
};
static const unsigned int DUMPED_METHODS_HEADER_MAGIC = 0x12345678;
#pragma pack(push)
#pragma pack(1)
struct DumpedMethodsHeader {
unsigned int magic;
unsigned int numMethods;
};
struct DumpedMethod {
// method header fields
unsigned short mhFlags; // method header Flags
unsigned short mhMaxStack; // method header MaxStack
unsigned int mhCodeSize; // method header CodeSize
unsigned int mhLocalVarSigTok; // method header LocalVarSigTok
// methodDef fields
unsigned short mdImplFlags; // methodDef ImplFlags
unsigned short mdFlags; // methodDef Flags
unsigned int mdName; // methodDef Name (index into #String)
unsigned int mdSignature; // methodDef Signature (index into #Blob)
unsigned int mdParamList; // methodDef ParamList (index into Param table)
// Misc
unsigned int token; // metadata token
};
#pragma pack(pop)
void hookCompileMethod();
const ScopeInfo* findDotNetAssembly(const DotNetFile* dotNetFile);
DotNetFile* dotNetFile = 0;
DllAlloc* dllAlloc = 0;
HMODULE hJitDll = 0; // eg. HMODULE of "clrjit.dll"
void* dotNetAssemblyBase = 0; // Image base of .NET assembly we should dump
CORINFO_METHOD_INFO defaultCmi;
std::wstring inputFilename; // Path of input file
typedef void* (__stdcall *getJit_t)(void);
typedef int (__stdcall* compileMethod_t)(void* self, void* comp,
CORINFO_METHOD_INFO* info, unsigned flags, BYTE** nativeEntry,
ULONG* nativeSizeOfCode);
compileMethod_t original_compileMethod = 0;
compileMethod_t ourObfuscatedCompileMethod = 0;
getJit_t getJit_func = 0;
struct MyCorinfoMethodInfo {
static const int SIG = 0xFA149C31;
MyCorinfoMethodInfo(const CORINFO_METHOD_INFO& _cmi)
: cmi(_cmi), sig(SIG) {
}
CORINFO_METHOD_INFO cmi; // Must be first field
void* data;
int sig; // Always SIG so we know when we're dumping methods
};
struct DumpMethodContext {
DumpMethodContext() : f(0), numMethods(0) {}
~DumpMethodContext() {
if (f)
fclose(f);
}
FILE* f;
unsigned numMethods;
unsigned token;
const char* name;
size_t methodHeaderRva;
unsigned char* methodHeader;
size_t headerSize;
void* methodBody;
unsigned LocalVarSigTok;
unsigned short flags;
void* methodDef;
const MetaDataType* type;
};
getJit_t getGetJitFunc() {
getJit_t getJit_func = (getJit_t)GetProcAddress(hJitDll, "getJit");
if (!getJit_func)
throw strex("Could not get address of getJit() function");
return getJit_func;
}
static const char* wtoa(const wchar_t* s) {
static char buf[8*1024];
int i = 0;
do {
if (i >= sizeof(buf))
throw strex("Buffer overflow in wtoa");
buf[i] = (char)s[i];
i++;
} while (s[i] != 0);
return buf;
}
HMODULE getJitDllModule() {
#if 0
ICLRMetaHost* mh;
if (CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&mh) != S_OK)
throw strex("Could not get ref to CLRMetaHost");
ICLRRuntimeInfo* rtti;
// Version must match a version dir in %WINDIR%\Microsoft.NET\Framework
if (mh->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&rtti) != S_OK)
throw strex("Could not get ref to ICLRRuntimeInfo");
HMODULE hClr;
if (rtti->LoadLibrary(L"clrjit.dll", &hClr) != S_OK)
throw strex("Could not load JIT dll file");
return hClr;
#else
HMODULE hClr = LoadLibraryW(L"clrjit.dll");
if (hClr != 0) {
logv("Loaded CLR JIT DLL %s\n", "clrjit.dll");
return hClr;
}
wchar_t name[1024];
DWORD size = sizeof(name);
DWORD len;
if (GetCORSystemDirectory(name, size, &len) != S_OK)
throw strex("Could not get CLR installation directory");
wcscat(name, L"mscorjit.dll");
hClr = LoadLibraryW(name);
if (hClr != 0) {
logv("Loaded CLR JIT DLL %s\n", wtoa(name));
return hClr;
}
throw strex("Could not load JIT DLL");
#endif
}
// Returns base addr or NULL
void* findModule(void* addr) {
HANDLE hSnap;
for (;;) {
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
if (hSnap != INVALID_HANDLE_VALUE)
break;
if (GetLastError() != ERROR_BAD_LENGTH)
break;
}
if (hSnap == INVALID_HANDLE_VALUE)
throw strex("Could not get module snapshot");
MODULEENTRY32 me32;
me32.dwSize = sizeof(me32);
void* baseAddr = 0;
for (BOOL res = Module32First(hSnap, &me32); res; res = Module32Next(hSnap, &me32)) {
if (me32.modBaseAddr <= addr && addr <= me32.modBaseAddr + me32.modBaseSize - 1) {
baseAddr = me32.modBaseAddr;
}
}
CloseHandle(hSnap);
return baseAddr;
}
bool hasProtectionHookedJit() {
compileMethod_t* jit_vtbl = *(compileMethod_t**)getJit_func();
return *jit_vtbl != ourObfuscatedCompileMethod;
}
// Used only by the following two methods
static const char* dump_methodName = 0;
static unsigned dump_token = 0;
const char* __stdcall returnNameOfMethod(void* arg1, void* arg2, void* arg3) {
(void)arg1; (void)arg2; (void)arg3;
return dump_methodName;
}
unsigned __stdcall returnMethodToken(void* arg1, void* arg2) {
(void)arg1; (void)arg2;
return dump_token;
}
bool dumpMethodIndex(unsigned index, DumpMethodContext* ctx) {
dump_token = 0x06000000 + index;
dump_methodName = dotNetFile->getMethodName(index, dotNetAssemblyBase);
logvv("\n========================================================\n");
logvv("Method name: '%s' (%08X), token: %08X\n", dump_methodName, dump_methodName, dump_token);
unsigned char* methodHeader = (unsigned char*)dotNetFile->getMethodHeader(index, dotNetAssemblyBase);
if (!methodHeader)
throw strex("Could not get method header");
if (methodHeader != dotNetAssemblyBase)
dumpMem(methodHeader, 16, "methodHeader");
size_t headerSize, codeSize, LocalVarSigTok, methodHeaderRva;
unsigned short maxStack, flags;
void* methodBody;
if (methodHeader == dotNetAssemblyBase) {
headerSize = 0;
maxStack = 0;
codeSize = 0;
flags = 0;
LocalVarSigTok = 0;
methodBody = 0;
methodHeaderRva = 0;
}
else if ((*methodHeader & 3) == 2) { // Tiny header
headerSize = 1;
maxStack = 8;
codeSize = *methodHeader >> 2;
flags = 2;
LocalVarSigTok = 0;
methodBody = methodHeader + headerSize;
methodHeaderRva = methodHeader - (unsigned char*)dotNetAssemblyBase;
}
else { // Fat header
headerSize = (methodHeader[1] >> 4) * 4;
maxStack = *(unsigned short*)(methodHeader + 2);
codeSize = *(unsigned int*)(methodHeader + 4);
flags = *(WORD*)methodHeader;
LocalVarSigTok = *(DWORD*)(methodHeader + 8);
methodBody = methodHeader + headerSize;
methodHeaderRva = methodHeader - (unsigned char*)dotNetAssemblyBase;
}
ctx->token = dump_token;
ctx->name = dump_methodName;
ctx->methodHeaderRva = methodHeaderRva;
ctx->methodHeader = methodHeader;
ctx->headerSize = headerSize;
ctx->methodBody = methodBody;
ctx->flags = flags;
ctx->LocalVarSigTok = LocalVarSigTok;
ctx->methodDef = dotNetFile->getMethodDef(index, dotNetAssemblyBase);
ctx->type = dotNetFile->getMethodType();
MyCorinfoMethodInfo info = defaultCmi;
info.data = ctx;
info.cmi.ILCode = (BYTE*)methodBody;
info.cmi.ILCodeSize = codeSize;
info.cmi.maxStack = maxStack;
void* mem[0x10];
memset(mem, 0, sizeof(mem));
mem[1] = &mem[2];
mem[3] = (void*)0x14;
mem[5] = (void*)0x1C;
mem[6] = &mem[7];
mem[7] = returnNameOfMethod;
mem[8] = &mem[0];
mem[13] = returnMethodToken; // Older .NET version
mem[14] = returnMethodToken; // Newer .NET version
void* self = getJit_func();
BYTE* nativeEntry = 0;
ULONG nativeSizeOfCode = 0;
void* comp = mem;
compileMethod_t compileMethod = **(compileMethod_t**)self;
logvv("Calling protection to decrypt code, scope: %08X, token: %08X\n", info.cmi.scope, dump_token);
compileMethod(self, comp, &info.cmi, 0, &nativeEntry, &nativeSizeOfCode);
logvv("Calling protection to decrypt code: DONE\n");
return true;
}
extern "C" __declspec(dllexport) int __stdcall dumpCode(const wchar_t* methodsFilename) {
try {
if (!hasProtectionHookedJit()) {
// CliSecure sometimes fails to load for an unknown reason. It happens rarely, but
// it doesn't seem to happen when I use .NET 2.0, only 4.0.
loge("Protection hasn't hooked JIT yet! Try again or try .NET != 4.0!\n");
return 0;
}
std::wstring name(inputFilename + L".methods");
if (methodsFilename != 0 && *methodsFilename != 0)
name = methodsFilename;
DumpMethodContext ctx;
ctx.f = _wfopen(name.c_str(), L"wb");
if (!ctx.f)
throw strex("Could not create methods file");
DumpedMethodsHeader header = {0};
header.magic = DUMPED_METHODS_HEADER_MAGIC;
if (fwrite(&header, 1, sizeof(header), ctx.f) != sizeof(header))
throw strex("Could not write to dumped methods file");
const size_t numMethods = dotNetFile->getNumMethods(); (void)numMethods;
for (size_t i = 1; i <= numMethods; i++)
dumpMethodIndex(i, &ctx);
if (fseek(ctx.f, 0, SEEK_SET))
throw strex("Could not seek #3");
header.numMethods = ctx.numMethods;
if (fwrite(&header, 1, sizeof(header), ctx.f) != sizeof(header))
throw strex("Could not write to dumped methods file");
}
catch (std::exception& ex) {
loge("EXCEPTION #3: %s\n", ex.what());
return 0;
}
return 1;
}
#define PE_ALIGNMENT 0x10000
bool isPeImage(void* addr) {
__try {
if (!addr)
return false;
if (!IS_ALIGNED((size_t)addr, PE_ALIGNMENT))
return false;
unsigned char* p = (unsigned char*)addr;
IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)p;
if (idh->e_magic != IMAGE_DOS_SIGNATURE)
return false;
p += idh->e_lfanew;
if (*(DWORD*)p != IMAGE_NT_SIGNATURE)
return false;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return false;
}
return true;
}
static void* __getImagebase1(void* addr) {
HMODULE hMod;
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)addr, &hMod) == 0)
return 0;
return (void*)hMod;
}
static void* __getImagebase2(void* addr) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0)
return 0;
return mbi.AllocationBase;
}
static void* __getImagebase3(void* addr) {
size_t offs = ALIGN_DOWN((size_t)addr, PE_ALIGNMENT);
for ( ; offs; offs -= PE_ALIGNMENT) {
if (IsBadReadPtr((const void*)offs, 1))
return 0; // A PE image shouldn't have empty holes >= PE_ALIGNMENT
if (isPeImage((void*)offs))
return (void*)offs;
}
return 0;
}
void* filterImageBase(void* addr) {
if (isPeImage(addr))
return addr;
return 0;
}
void* findImageBase(void* addr) {
void* imageBase = 0;
if (imageBase == 0)
imageBase = filterImageBase(findModule(addr));
if (imageBase == 0)
imageBase = filterImageBase(__getImagebase1(addr));
if (imageBase == 0)
imageBase = filterImageBase(__getImagebase2(addr));
if (imageBase == 0)
imageBase = filterImageBase(__getImagebase3(addr));
return imageBase;
}
std::vector<ScopeInfo> imageBases;
void addImageBase(void* imageBase, const CORINFO_METHOD_INFO* info) {
if (!imageBase)
return;
for (size_t i = 0; i < imageBases.size(); i++) {
if (imageBases[i].imageBase == imageBase) {
if (imageBases[i].cmi.scope != info->scope)
logw("===WARNING=== new scope but same imageBase! %08X vs %08X\n", imageBases[i].cmi.scope, info->scope);
return;
}
}
logv("%d. Added imageBase %08X\n", imageBases.size() + 1, imageBase);
ScopeInfo si;
si.cmi = *info;
si.imageBase = imageBase;
imageBases.push_back(si);
}
const ScopeInfo* findDotNetAssembly(const DotNetFile* dotNetFile) {
for (size_t i = 0; i < imageBases.size(); i++) {
if (dotNetFile->isYourImageBase(imageBases[i].imageBase)) {
return &imageBases[i];
}
}
return 0;
}
int __stdcall myCompileMethod(void* self, void* comp,
CORINFO_METHOD_INFO* info, unsigned flags, BYTE** nativeEntry,
ULONG* nativeSizeOfCode) {
if (((MyCorinfoMethodInfo*)info)->sig == MyCorinfoMethodInfo::SIG) {
// We're dumping methods
logvv("code: %08X, size: %08X, maxStack: %04X\n", info->ILCode, info->ILCodeSize, info->maxStack);
DumpMethodContext* ctx = (DumpMethodContext*)((MyCorinfoMethodInfo*)info)->data;
dumpMem(info->ILCode, info->ILCodeSize, "ILCode");
dumpMem(ctx->methodDef, ctx->type->size(), "methodDef table");
if (ctx->methodHeader)
dumpMem(ctx->methodHeader, 16, "method header");
DumpedMethod dm;
dm.mhFlags = ctx->flags;
dm.mhMaxStack = info->maxStack;
dm.mhCodeSize = info->ILCodeSize;
dm.mhLocalVarSigTok = ctx->LocalVarSigTok;
dm.mdImplFlags = (unsigned short)ctx->type->read(ctx->methodDef, 1);
dm.mdFlags = (unsigned short)ctx->type->read(ctx->methodDef, 2);
dm.mdName = ctx->type->read(ctx->methodDef, 3);
dm.mdSignature = ctx->type->read(ctx->methodDef, 4);
dm.mdParamList = ctx->type->read(ctx->methodDef, 5);
dm.token = ctx->token;
dumpMem(&dm, sizeof(dm), "DumpedMethod");
if (fwrite(&dm, 1, sizeof(dm), ctx->f) != sizeof(dm))
throw strex("Could not write DumpedMethod");
if (fwrite(info->ILCode, 1, info->ILCodeSize, ctx->f) != info->ILCodeSize)
throw strex("Could not write ILCode");
ctx->numMethods++;
return 0;
}
else {
void* imageBase = findImageBase(info->ILCode);
addImageBase(imageBase, info);
logvv("myCompileMethod: called. %08X (%3d), base: %08X, info: %08X, scope: %08X\n", info->ILCode, info->ILCodeSize, imageBase, info, info->scope);
return original_compileMethod(self, comp, info, flags, nativeEntry, nativeSizeOfCode);
}
}
// dest => memory to write to (start of method). Must be HOOK_METHOD_SIZE bytes.
// destFunc => the real func we should execute
void createHookMethod(void* dest, void* destFunc) {
/*
* Obfuscate the code a little bit just in case a protection checks
* for JMP XYZ.
* B8 xx xx xx xx mov eax, xxxxxxxx
* 2D xx xx xx xx sub eax, xxxxxxxx
* 50 push eax
* C3 retn
*/
#define HOOK_METHOD_SIZE 12
const size_t offs = (size_t)destFunc;
unsigned char* p = (unsigned char*)dest;
*p++ = 0xB8;
*(DWORD*)p = offs + 0xC0DEBEEF;
p += 4;
*p++ = 0x2D;
*(DWORD*)p = 0xC0DEBEEF;
p += 4;
*p++ = 0x50;
*p++ = 0xC3;
}
void hookCompileMethod() {
ourObfuscatedCompileMethod = (compileMethod_t)dllAlloc->alloc(HOOK_METHOD_SIZE);
{
MakeWritable dummy(ourObfuscatedCompileMethod, HOOK_METHOD_SIZE);
createHookMethod(ourObfuscatedCompileMethod, myCompileMethod);
}
getJit_func = getGetJitFunc();
compileMethod_t* jit_vtbl = *(compileMethod_t**)getJit_func();
original_compileMethod = *jit_vtbl;
{
MakeWritable dummy(jit_vtbl, sizeof(void*));
*jit_vtbl = ourObfuscatedCompileMethod;
}
logv("Original compileMethod: %p\n", original_compileMethod);
}
extern "C" __declspec(dllexport) int __stdcall initialize1(int logLevel, const wchar_t* filename) {
static bool initialized = false;
int ret = 1;
if (!initialized) {
try {
setLogLevel(logLevel);
hJitDll = getJitDllModule();
dllAlloc = new DllAlloc(hJitDll);
hookCompileMethod();
inputFilename = filename;
dotNetFile = new DotNetFile(filename);
initialized = true;
}
catch (std::exception& ex) {
loge("EXCEPTION #1: %s\n", ex.what());
ret = 0;
}
}
return ret;
}
extern "C" __declspec(dllexport) int __stdcall initialize2() {
static bool initialized = false;
int ret = 1;
if (!initialized) {
try {
const ScopeInfo* si = findDotNetAssembly(dotNetFile);
if (!si)
throw strex("Could not find .NET assembly's image base");
dotNetAssemblyBase = si->imageBase;
defaultCmi = si->cmi;
initialized = true;
}
catch (std::exception& ex) {
loge("EXCEPTION #2: %s\n", ex.what());
ret = 0;
}
}
return ret;
}
extern "C" __declspec(dllexport) int __stdcall foundAssembly() {
return findDotNetAssembly(dotNetFile) != 0;
}
extern "C" __declspec(dllexport) void __stdcall debug_addImageBase(void* imageBase) {
CORINFO_METHOD_INFO info = {0};
addImageBase(imageBase, &info);
}