動機
昔、箱庭XSSという問題がSECCONで出題されました。 どのような問題だったかは他の方のブログを見て頂ければ分かるかと思います。
問題作者のスライドはこちらです。
【XSS Bonsai】 受賞のご挨拶 by @ymzkei5 【SECCON 2014】 - Dec 08, 2014
このアプリケーションは.Net製で、
- 難読化
- 既存のdecompilerでは C#, VB.Netのコードに変換出来ない(大体落ちたり例外が発生する)
- デバッガでattachすると挙動が変わったり、答えが変わる
- バイナリを書き換えると、checksum一致処理に引っかかる
という特性があります。
webの問題として解いてほしいらしく、作者さんのスライド曰く
とのことなので、チートで解こうと考えました。
今回要求される要件は
- debugger検知に引っかからない
- 実行ファイルを書き換えない
- checksumが変わるため
の二つです。
disassembleしてみると、「正規表現で文字列を置換している処理さえ潰せればフラグを取れるのでは?」と思ったので、「Regex.Replaceの挙動を変更する」ことを目標にしました。
しかし箱庭XSSのみに特化したプログラムを書いても面白くないので、もうちょっと大きく出て「.Netアプリケーションのメソッドを、実行ファイルを書き換えずに挙動を変える」ということにしましょう。
(なおこれ以降箱庭XSSは出てきません、手段と目的は往々にして入れ替わります)
実装
に置いてあるのでご自由にどうぞ。(英語でコメント書こうとして挫折した後がありますね) とりあえず試してみたい人は https://github.com/math314/DotNetInjection/releases/tag/v1.0 から Release.zip をダウンロードして試してみてください。動かし方はreadmeに書いてあります。
前置きが長くなりましたがやっていきましょう。
ネイティブアプリケーションなら、DLL injectionをするだけで簡単にメソッドを置き換えられます。 http://inaz2.hatenablog.com/entry/2015/08/08/223643 がわかりやすいかと思います。
.NetでもDLL injectionが出来れば…、と考えたんですが、どうも一筋縄ではいかないようです。 ネイティブアプリケーションであれば、DLL injection -> LoadLibraryExあたりをHookして、書き換えたいメソッドのあるDLLを発見した段階でDLLをメモリ上で書き換える…となるんですが、 そもそも.NetのRuntimeはManaged DLLを読み込む際にLoadLibraryを使っていないんじゃないかと思います(が確信はありません)。 それに、DLLを上手に読み込ませたとしてその後どのように挙動を変えればいいのでしょうか?ILを書き換えればよい…?困難が多そうです。
しかし.NetのRuntimeはこんなニッチな要求に答える機能を持っています。それは Profiler API です。
Profiler API(ICorProfilerCallback interface)
Profiler APIはどういうものか見ていきましょう。
MIDL_INTERFACE("176FBED1-A55C-4796-98CA-A9DA0EF883E7") ICorProfilerCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Initialize( /* [in] */ IUnknown *pICorProfilerInfoUnk) = 0; ... }
これはICorProfilerCallback
というInterfaceです。IUnknownを見て気付いた方もいると思いますが、COM Objectですね。大人しくC++でCOMとたわむれましょう。
.NetのRuntimeが、プロファイラを認識すると、ICorProfilerCallback::Initialize
を呼び出します。
ICorProfilerInfo2* iInfo2; HRESULT hr = pICorProfilerInfoUnk->QueryInterface(__uuidof(iInfo2), (LPVOID*)&iInfo2); if (FAILED(hr)) { DebugPrintf(L"Error: Failed to get ICorProfiler2\n"); //OutputDebugStringWを使って出力するformatメソッド exit(-1); } DWORD eventMask = 0; eventMask |= COR_PRF_MONITOR_JIT_COMPILATION; eventMask |= COR_PRF_DISABLE_OPTIMIZATIONS; eventMask |= COR_PRF_USE_PROFILE_IMAGES; return mCorProfilerInfo2->SetEventMask(eventMask);
その際 IUnknown *pICorProfilerInfoUnk
が引数として渡されるので、使いたいinterfaceがあれば事前に取得します。
今回はICorProfilerInfo2
が欲しいので、上記のように取得しておきます。
また、初期化時に ICorProfilerInfo2::SetEventMask
を呼び出しておきましょう。
- COR_PRF_MONITOR_JIT_COMPILATION: 後述するJITCompilationStartedイベントを発生させるフラグ
- COR_PRF_DISABLE_OPTIMIZATIONS: ランタイムによるあらゆる最適化を無効化する。 入れておくのが無難?
- COR_PRF_USE_PROFILE_IMAGES: ngenであらかじめJIT compileされたライブラリについても、profileが可能な、つまりJIT compileされていないイメージを使用する
- これをしないとSystem.*** 系統のライブラリをHook出来なくなります。後述するJITCompilationStartedが走らなくなるためです。
これで、メソッドのJITコンパイルが開始するイベントを取得できるようになりました。
JITCompilationStarted
ICorProfilerCallbackに宣言されているメソッドの一つに JITCompilationStarted
というものがあります。
virtual HRESULT STDMETHODCALLTYPE JITCompilationStarted( /* [in] */ FunctionID functionId, /* [in] */ BOOL fIsSafeToBlock) = 0;
FunctionIDは(ClassID, ModuleID, mdToken) と 1:1 で紐づいているユニークなIDです。Windows programmingでよく出てくるHANDLEみたいなやつです。 ClassID,ModuleIDも似たようなやつで、これらのIDからメソッド名やクラス名、モジュール名やアセンブリ名、メソッドの返り値や引数を取得することができます。
このFunctionIDから必要な情報を取り出して、 自作のFunctionInfo classに詰め込みます。 なおこのクラスは
Really Easy Logging using IL Rewriting and the .NET Profiling API - CodeProject
のコードを元に適宜変更を加えてあります。
class FunctionInfo { public: static FunctionInfo *CreateFunctionInfo(ICorProfilerInfo *profilerInfo, FunctionID functionID); ~FunctionInfo() {}; FunctionID get_FunctionID() const { return mFunctionID; } ClassID get_ClassID() const { return mClassID; } mdTypeDef get_ClassTypeDef() const { return mClassTypeDef; } ModuleID get_ModuleID() const { return mModuleID; } mdToken get_FunctionToken() const { return mFunctionToken; } const std::wstring& get_ClassName() const { return mClassName; } const std::wstring& get_FunctionName() const { return mFunctionName; } const std::wstring& get_AssemblyName() const { return mAssemblyName; } const std::wstring& get_SignatureText() const { return mSignatureText; } const std::vector<BYTE>& get_SignatureBlob() const { return mSignatureBlob; } DWORD get_MethodAttributes() const { return mMethodAttributes; } ULONG get_ArgumentCount() const { return mArguments.size(); } const std::wstring& get_RetType() const { return mRetType; } const std::vector<std::wstring>& get_Arguments() const { return mArguments; } static PCCOR_SIGNATURE ParseSignature(IMetaDataImport *pMDImport, PCCOR_SIGNATURE signature, WCHAR* szBuffer); private: FunctionInfo() {}; FunctionID mFunctionID; ClassID mClassID; mdTypeDef mClassTypeDef; ModuleID mModuleID; mdToken mFunctionToken; std::wstring mClassName; std::wstring mFunctionName; std::wstring mAssemblyName; std::wstring mSignatureText; DWORD mMethodAttributes; std::vector<BYTE> mSignatureBlob; std::wstring mRetType; std::vector<std::wstring> mArguments; }; const int MAX_LENGTH = 2048; FunctionInfo *FunctionInfo::CreateFunctionInfo(ICorProfilerInfo *profilerInfo, FunctionID functionID) { ClassID classID = 0; ModuleID moduleID = 0; mdToken tkMethod = 0; hrCheck(profilerInfo->GetFunctionInfo(functionID, &classID, &moduleID, &tkMethod)); WCHAR moduleName[MAX_LENGTH]; AssemblyID assemblyID; hrCheck(profilerInfo->GetModuleInfo(moduleID, NULL, MAX_LENGTH, 0, moduleName, &assemblyID)); WCHAR assemblyName[MAX_LENGTH]; hrCheck(profilerInfo->GetAssemblyInfo(assemblyID, MAX_LENGTH, 0, assemblyName, NULL, NULL)); ComPtr<IMetaDataImport> metaDataImport; mdToken token = 0; hrCheck(profilerInfo->GetTokenAndMetaDataFromFunction(functionID, IID_IMetaDataImport, (LPUNKNOWN *)&metaDataImport, &token)); mdTypeDef classTypeDef; WCHAR functionName[MAX_LENGTH]; WCHAR className[MAX_LENGTH]; PCCOR_SIGNATURE signatureBlob; ULONG signatureBlobLength; DWORD methodAttributes = 0; hrCheck(metaDataImport->GetMethodProps(token, &classTypeDef, functionName, MAX_LENGTH, 0, &methodAttributes, &signatureBlob, &signatureBlobLength, NULL, NULL)); hrCheck(metaDataImport->GetTypeDefProps(classTypeDef, className, MAX_LENGTH, 0, NULL, NULL)); PCCOR_SIGNATURE signatureBlobOrigin = signatureBlob; ULONG callConvension = IMAGE_CEE_CS_CALLCONV_MAX; signatureBlob += CorSigUncompressData(signatureBlob, &callConvension); ULONG argumentCount; signatureBlob += CorSigUncompressData(signatureBlob, &argumentCount); WCHAR returnType[MAX_LENGTH]; returnType[0] = '\0'; signatureBlob = ParseSignature(metaDataImport.Get(), signatureBlob, returnType); WCHAR signatureText[MAX_LENGTH] = L""; wsprintf(signatureText, L"fid=%08X|%s %s %s::%s", functionID, (methodAttributes & mdStatic) == 0 ? L"(nonstatic)" : L"static", returnType, className, functionName ); std::vector<std::wstring> arguments; for (ULONG i = 0; (signatureBlob != NULL) && (i < argumentCount); ++i) { WCHAR parameters[MAX_LENGTH]; parameters[0] = '\0'; signatureBlob = ParseSignature(metaDataImport.Get(), signatureBlob, parameters); arguments.push_back(parameters); } lstrcatW(signatureText, L"("); for (ULONG i = 0; i < arguments.size(); i++) { if(i != 0) lstrcatW(signatureText, L","); lstrcatW(signatureText, arguments[i].c_str()); } lstrcatW(signatureText, L")"); FunctionInfo* result = new FunctionInfo(); result->mFunctionID = functionID; result->mClassID = classID; result->mClassTypeDef = classTypeDef; result->mModuleID = moduleID; result->mFunctionToken = tkMethod; result->mFunctionName = functionName; result->mClassName = className; result->mAssemblyName = assemblyName; result->mSignatureText = signatureText; result->mSignatureBlob = std::vector<BYTE>(signatureBlobOrigin, signatureBlob); result->mMethodAttributes = methodAttributes; result->mRetType = returnType; result->mArguments = arguments; return result; }
一部サボっててbuffer over flowが発生する可能性があるので気を付けてください。(すいません)
CorSigUncompressData
はcor.hに定義されているメソッドです。 ParseSignature
はFunctionInfoに定義されているメソッドであり、ここではあまり大事ではないため説明は省略します。
.NetのメソッドはheaderとILの2つで構成されています。header部を後で流用するために signatureBlobOrigin
と signatureBlob
を計算しています。
ここでは、signatureBlobOriginからsignatureBlobまでがheader, signatureBlob以降がIL部を指します。
signatureTextにはメソッドの情報が入ります。例えば DateTime.get_Now()
であれば fid=0547B110|static System.DateTime System.DateTime::get_Now()
,
Regex.Replace(string input,string replacement)
であれば fid=054958AC|(nonstatic) string System.Text.RegularExpressions.Regex::Replace(string,string)
となります。 fidはfunctionIDなので、参照しているモジュールが変わればfidも変わるかと思います。
必要な情報は集まったので、メソッドを置き換えます。例として DateTime.get_Now()
を置き換えてみます。
今回は 2000/01/01 を常に返すように変えてみます。
STDMETHODIMP HakoniwaProfilerImpl::JITCompilationStarted(FunctionID functionID, BOOL fIsSafeToBlock) { std::shared_ptr<FunctionInfo> fi(FunctionInfo::CreateFunctionInfo(mCorProfilerInfo2.Get(), functionID)); //クラス名,メソッド名が一致する物を置き換える if (fi->get_ClassName() == L"System.DateTime" && fi->get_FunctionName() == L"get_Now") { DebugPrintf(L"%s", fi->get_SignatureText().c_str()); Tranpoline tranpoline(mCorProfilerInfo2, fi); tranpoline.Update(L"HakoniwaProfiler.MethodHook.MethodHook", L"get_Now"); } }
namespace HakoniwaProfiler.MethodHook { public class MethodHook { static public DateTime get_Now() { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.get_Now"); return new DateTime(2000,1,1); } } }
Tranpoline::Update
を見てみましょう。
class Tranpoline { public: Tranpoline(Microsoft::WRL::ComPtr<ICorProfilerInfo2>& info, std::shared_ptr<FunctionInfo>& fi) : info(info), fi(fi) {} private: std::vector<BYTE> Tranpoline::GetFunctionSignatureBlob(); mdMemberRef Tranpoline::DefineHakoniwaMethodIntoThisAssembly(const wchar_t* fullyQualifiedClassName, const wchar_t* methodName); ULONG calcNewMethodArgCount(); void* AllocateFuctionBody(DWORD size); std::vector<BYTE> ConstructTranpolineMethodIL(mdMemberRef mdCallFunctionRef); std::vector<BYTE> ConstructTranpolineMethodHeader(DWORD codeSize); Microsoft::WRL::ComPtr<ICorProfilerInfo2> info; std::shared_ptr<FunctionInfo> fi; }; void Tranpoline::Update(const wchar_t* className, const wchar_t* methodName) { // HakoniwaProfiler.MethodHook に定義されているメソッドの mdMemberRef を、対象のアセンブリ内に定義する mdMemberRef newMemberRef = DefineHakoniwaMethodIntoThisAssembly(className, methodName); // 新しいメソッドのheader部とIL部を作成 std::vector<BYTE> newHeader = ConstructTranpolineMethodHeader(newILs.size()); std::vector<BYTE> newILs = ConstructTranpolineMethodIL(newMemberRef); // メソッド用のメモリ領域を確保して書き込み ULONG newMethodSize = newHeader.size() + newILs.size(); void *allocated = AllocateFuctionBody(newMethodSize); memcpy(allocated, &newHeader[0], newHeader.size()); memcpy((BYTE*)allocated + newHeader.size(), &newILs[0], newILs.size()); // メソッドを入れ替える hrCheck(info->SetILFunctionBody(fi->get_ModuleID(), fi->get_FunctionToken(), (LPCBYTE)allocated)); }
今回の肝である ConstructTranpolineMethodIL を見てみましょう。
std::vector<BYTE> Tranpoline::ConstructTranpolineMethodIL(mdMemberRef mdCallFunctionRef) { std::vector<BYTE> newILs; ULONG newArguments = calcNewMethodArgCount(); // ldarg.0, ldarg.1 ... ldarg. newArguments - 1 for (ULONG i = 0; i < newArguments; i++) { if (i < 4) { newILs.push_back(0x02 + (BYTE)i); //ldarg.0 ~ ldarg.3 } else { newILs.push_back(0x0E); newILs.push_back((BYTE)i); //ldarg.s <index> } } // method call newILs.push_back(0x28); newILs.push_back((mdCallFunctionRef >> 0) & 0xFF); newILs.push_back((mdCallFunctionRef >> 8) & 0xFF); newILs.push_back((mdCallFunctionRef >> 16) & 0xFF); newILs.push_back((mdCallFunctionRef >> 24) & 0xFF); // ret newILs.push_back(0x2a); return newILs; }
ここでは
ldarg.0 ldarg.1 ldarg.2 ... call <置き換え先のメソッド> ret
というILを組み立てています。
ここで、普通のアセンブラのようにjmpを使えばいいのでは?と思う方もいると思いますが 別モジュールに含まれるメソッドを呼び出そうとすると System.Security.VerificationExceptionが発生することがあるので使えません。 発生する条件は以下が参考になるかと思います。
OpCodes.Jmp Field (System.Reflection.Emit)
c# - How to use .net cil jmp opcode - Stack Overflow
余談ですが、C#では引数を216-1個まで受け付けることが出来るようです。今回は255個までしか受け付けませんが…
サンプル実行
ついにメソッドの置き換えが実装できました! 色々な種類のメソッドを置き換えてみましょう。
置き換えられるメソッド群です。全てConsoleAppTest.Program
内に定義されています。
using System; using System.Text.RegularExpressions; namespace ConsoleAppTest { class Program { static string getStr1() { Console.WriteLine("ConsoleAppTest.Program.getStr1"); return "getStr1"; } static string haveArguments(string arg1, string arg2) { return string.Format("{0} + {1}", arg1, arg2); } static string haveManyArguments(string arg1, string arg2, string arg3, double arg4, int arg5, int arg6) { return string.Format("{0} + {1} + {2} + {3} + {4} + {5}", arg1, arg2, arg3, arg4, arg5, arg6); } static int intarg2(int x,int b) { return x + b; } static void hoge() { Console.WriteLine(DateTime.Now); string a = Regex.Replace("poyohugapoyopiyo", "piyo", "xxxx"); Console.WriteLine(a); string b = new Regex("piyo").Replace("poyohugapoyopiyo", "xxxx"); Console.WriteLine(b); Console.WriteLine(getStr1()); Console.WriteLine(haveArguments("aa", "bb")); Console.WriteLine(intarg2(1,2)); Console.WriteLine(haveManyArguments("a", "b", "c", 2.0, 3, 4)); } static void Main(string[] args) { hoge(); var x = TestClass.test1(1, "aaa"); Console.WriteLine(x); var y = new TestClass(1).test2("aaa"); Console.WriteLine(y); } } public class TestClass { int _a; public TestClass(int a) { _a = a; } public string test2(string b) { Console.WriteLine("TestClass.test2 : {0}", b); return b; } public static string test1(int a,string b) { Console.WriteLine("TestClass.test2 : {0} , {1}",a, b); return b; } } }
普通にMain関数を実行してみましょう。
> ConsoleAppTest.exe 2017/01/21 23:34:05 poyohugapoyoxxxx poyohugapoyoxxxx ConsoleAppTest.Program.getStr1 getStr1 aa + bb 3 a + b + c + 2 + 3 + 4 TestClass.test2 : 1 , aaa aaa TestClass.test2 : aaa aaa
普通の実行結果ですね。
次に、それぞれMain関数以外を以下の関数でHookしてみます。
using System; using System.Reflection; using System.Text.RegularExpressions; namespace HakoniwaProfiler.MethodHook { public class MethodHook { static public string getStr1() { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.getStr1"); return "HHHH"; } static public DateTime get_Now() { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.get_Now"); return new DateTime(2000,1,1); } static public string haveArguments(string arg1, string arg2) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.haveArguments"); return string.Format("{0} + {1}", arg1, arg2); } static public string haveManyArguments(string arg1, string arg2, string arg3, double arg4, int arg5, int arg6) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.haveManyArguments"); return string.Format("{0} + {1} + {2} + {3} + {4} + {5}", arg1, arg2, arg3, arg4, arg5, arg6); } static public int intarg2(int x, int b) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.intarg2"); return x + b; } static public string Replace(string input, string pattern, string replacement) { return Replace(new Regex(pattern), input, replacement); } static public string Replace(Regex regex, string input, string replacement) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.Replace"); var pattern_info = typeof(Regex).GetField("pattern", BindingFlags.NonPublic | BindingFlags.Instance); string pattern = (string)pattern_info.GetValue(regex); Console.WriteLine("------------------------------"); Console.WriteLine(string.Format("input = {0}\npattern = {1},pattern_length = {2}", input, pattern, pattern.Length)); Console.WriteLine("------------------------------"); return input; } static public string test1(int a, string b) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.test1"); return ""; } static public string test2(ConsoleAppTest.TestClass x, string b) { Console.WriteLine("[!] HakoniwaProfiler.MethodHook.MethodHook.test2"); return ""; } } }
出力は
> Injector.exe ConsoleAppTest.exe ConsoleAppTest started. [!] HakoniwaProfiler.MethodHook.MethodHook.get_Now 2000/01/01 0:00:00 [!] HakoniwaProfiler.MethodHook.MethodHook.Replace ------------------------------ input = poyohugapoyopiyo pattern = piyo,pattern_length = 4 ------------------------------ poyohugapoyopiyo [!] HakoniwaProfiler.MethodHook.MethodHook.Replace ------------------------------ input = poyohugapoyopiyo pattern = piyo,pattern_length = 4 ------------------------------ poyohugapoyopiyo [!] HakoniwaProfiler.MethodHook.MethodHook.getStr1 HHHH [!] HakoniwaProfiler.MethodHook.MethodHook.haveArguments aa + bb 3 [!] HakoniwaProfiler.MethodHook.MethodHook.haveManyArguments a + b + c + 2 + 3 + 4 [!] HakoniwaProfiler.MethodHook.MethodHook.test1 [!] HakoniwaProfiler.MethodHook.MethodHook.test2
となりました。きちんと置き換えられています!
Injector
ここになって急に新しいアプリケーションが出てきました、Injector.exe です。
using System; using System.Diagnostics; using System.IO; namespace Injector { class Program { const string PROFILER_UUID = "{9992F2A6-DF35-472B-AD3E-317F85D958D7}"; const string PROFILER_NAME = "HakoniwaProfiler.dll"; void StartTarget(string targetPath) { string current_dir = Directory.GetCurrentDirectory(); string profiler_path = Path.Combine(current_dir, PROFILER_NAME); ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = targetPath; psi.UseShellExecute = false; psi.EnvironmentVariables.Add("COR_ENABLE_PROFILING", "1"); psi.EnvironmentVariables.Add("COR_PROFILER", PROFILER_UUID); psi.EnvironmentVariables.Add("COR_PROFILER_PATH", profiler_path); psi.EnvironmentVariables.Add("COMPLUS_Version", "v4.0.30319"); //.netのバージョンを強制的に4.0に固定する using (var proc = Process.Start(psi)) { Console.WriteLine("{0} started.",proc.ProcessName); proc.WaitForExit(); } } static void Main(string[] args) { if (args.Length != 1) { Console.WriteLine("usage : Injector.exe <target app>"); return; } new Program().StartTarget(args[0]); } } }
環境変数を追加してプログラムを起動しているだけです。ここにあるように COR_PROFILER
とCOR_PROFILER_PATH
を適切に設定することで、プロファイラ有効になります。
.Netのバージョンを4.0に固定しているので、 バージョンが4.0以下のアプリケーションであればちゃんと動くと思います。
このようにすることで、.NetのRuntimeはCOMのお作法にのっとりHakoniwaProfiler.dll内のDllGetClassObject
を呼び出して ICorProfilerCallback3
を実装している UUID = {9992F2A6-DF35-472B-AD3E-317F85D958D7}
のオブジェクトを取得しています。
Profilerについての公式記事: Profiling in the .NET Framework 4
ILのリスト: https://en.wikipedia.org/wiki/List_of_CIL_instructions
注意点
ここまでさらっと流してきましたが、きちんと動くようにするまでに多数の落とし穴があったので共有します。
対象アプリケーションのプラットフォームターゲットがx86じゃないと動かない
- 何故ならHakoniwaProfiler.dllがx86なので
- x64にも対応したいなら,それ用のdllを作る必要がある
- デフォルトでは"AnyCPU" だが,これでは64bitのOSで32bitアプリケーションとして実行は"出来ない"
- 余談だが,.net4.5以降では "AnyCPU + 32bit優先" というオプションを付けることで,32bitでも動くようになる
- CorFlags.exe というコマンドで,"AnyCPU"を"x86"に変更できる(exeを直接書き換える).
- アウトプロセスサーバでプロファイラを作成すれば苦労が少ない?
- 実はアウトプロセスサーバ+.netの場合,プロファイラのプラットフォームターゲットをAnyCPUにしてはいけない模様
- ハングアップするらしい
- 参考: http://qiita.com/mima_ita/items/57d7c1101543e214b1d6
mCorProfilerInfo2->SetEventMask で COR_PRF_USE_PROFILE_IMAGES を指定する必要がある
- System.*** は NGENでJITコンパイル済みなので,普通はJITCompilationStartedが呼び出される事はない。 そこでJITコンパイルを無効化したprofile用のdllを読み込む様にEventMaskで設定する必要がある
- 参考: http://blogs.msdn.com/b/davbr/archive/2007/03/06/creating-an-il-rewriting-profiler.aspx
System.MethodAccessExceptionが発生する
- 今回発生した理由は2つ
- publicでない関数をcallしようとしたことにより発生した例外
- あるクラスから,別クラスのprivateな関数の呼び出しをするように書き換えていた
- publicな関数を呼び出すコードに書き変える事で対処
- .net4.0から導入されたセキュリティモデルにより発生する例外
- mscorlibのassemblyにはSystem.Security.SecurityTransparent属性が宣言されている
- これが宣言されたアセンブリからは,SecurityTransparent,SecuritySafeCriticalな関数しか呼べない
- デフォルトではSystem.Security.AllowPartiallyTrustedCallersが暗に宣言されているが,これではダメらしい
- 参考http://www.atmarkit.co.jp/fdotnet/special/dotnet4security_01/dotnet4security_01_01.html
System.Security.SecurityTransparentを指定したアセンブリから,System.Security.AllowPartiallyTrustedCallersのアセンブリを呼び出せない
System.BadImageFormatException: バイナリ シグネチャが不適切です。(HRESULT = 0x80131192)
- metaDataEmit->DefineMemberRef で設定したsignatureBlobにバグがあった
署名をしていないアセンブリを,署名済みアセンブリに読み込ませることが出来なかった
- 置き換え先のメソッドが定義されているアセンブリに署名をした
- これにより,HakoniwaProfiler.MethodHook から, ConsoleAppTest を参照できなくなった.
- 解決方法は,ConsoleAppTestに署名をするか,ConsoleAppTestで定義されている型を一切参照しないか,のどちらかである
- 今回は簡単のため,前者の署名をした.
それでは、良いDLL Injection ライフをお楽しみください。