Microsoft Research Detours Express 3.0
前から使ってみようと思っていて実際に手をつけていなかった Detours をちょっと触ってみました。と言いつつ、実際は 2 ヶ月ほど前に少し書き溜めていたものを、Thanksgiving の連休でようやく手を付ける時間ができてバックログを減らしている、という状況です。
Detours とは、ユーザーモードの任意の関数呼び出しをフックするための static library です。現在の最新バージョンは 3.0 ですが、Microsoft Research から論文が出たのが 1999 年なので、Windows NT の頃から存在していたのでしょうか。古い。
Detours - Microsoft Research
http://research.microsoft.com/en-us/projects/detours/
Download Detours - Microsoft Research
http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/
Detours は Express と Professional の 2 種類あり、Professional だと、製品に組み込んでそれを販売することも可能なようです。あと、ネイティブの 64bit をサポートしている。なんとお値段、$9,999.95!たけー。SQL Server の Standard ですら $3,000 ぐらいなのに。
Microsoft Detours v3 Professional Download, instrumenting Win32 functions - Microsoft Store
http://www.microsoftstore.com/store/msusa/en_US/pdp/Microsoft-Research-Detours-v3-Professional/productID.253663300
Express は安心の 0 円です。
ダウンロードしたファイルは msi 形式ですが、中身を解凍しているだけなので、 msiexec を直接実行して、ファイルを解凍してしまいます。
C:\MSWORK>start /wait msiexec /a DetoursExpress30.msi targetdir=C:\MSWORK\DetoursExpress30 /q
とりあえず README.txt を読むと、”4. CHANGES IN VERSION 3.0:” のところに “* Support for mixing 32-bit and 64-bit processes.” と書いてあります。これは意味不明。32bit と 64bit なんてミックスできないじゃん。WOW64 のことでしょうかね。確かに結果的には WOW64 で動きました。
今回は以下の環境を使います。
- OS: Windows 8.1 SP1 x64
- IDE: Visual Studio 2013
README.txt によると、そのまま nmake できるらしいので実行してみます。”VS2013 x86 Native Tools Command Prompt” のプロンプトを開いて nmake を実行するだけです。
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC>cd /d C:\MSWORK\DetoursExpress30
C:\MSWORK\DetoursExpress30>nmake
Microsoft (R) Program Maintenance Utility Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
cd "C:\MSWORK\DetoursExpress30\src"
Created ..\include
Created ..\lib.X86
Created ..\bin.X86
Created obj.X86
cl /W4 /WX /Zi /MTd /Gy /Gm- /Zl /Od /DDETOURS_BITS=32 /DWIN32_LEAN_AND_MEAN /D_WIN32_WINNT=0x403 /Gs /DDETOURS_X86=1 /DDETOURS_32BIT=1 /D_X86_ /DDETOURS_OPTION_BITS=64 /Fd..\lib.X86\detours.pdb /Foobj.X86\detours.obj /c .\detours.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
detours.cpp
<...snip...>
cl /nologo /Zi /MT /Gm- /W4 /WX /Od /DDETOURS_BITS=32 /I..\..\include /Gs /DDETOURS_X86=1 /DDETOURS_32BIT=1 /D_X86_ /DDETOURS_OPTION_BITS=64 /Fe..\..\bin.X86\symtest.exe /Fd..\..\bin.X86\symtest.pdb obj.X86\symtest.obj /link /release /incremental:no /machine:x86 ..\..\lib.X86\syelog.lib ..\..\lib.X86\detours.lib kernel32.lib gdi32.lib user32.lib shell32.lib /subsystem:console /incremental:no /fixed:no ..\..\bin.X86\target32.lib
copy "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\dbghelp.dll" ..\..\bin.X86\dbghelp.dll
1 file(s) copied.
cd "C:\MSWORK\DetoursExpress30\samples\member"
Created obj.X86
cl /nologo /nologo /Zi /MT /Gm- /W4 /WX /Od /DDETOURS_BITS=32 /I..\..\include /Gs /DDETOURS_X86=1 /DDETOURS_32BIT=1 /D_X86_ /DDETOURS_OPTION_BITS=64 /Fdobj.X86\vc.pdb /Foobj.X86\member.obj /c
member.cpp
member.cpp
member.cpp(88) : error C2440: 'type cast' : cannot convert from 'void (__thiscall CMember::* )(void)' to 'PBYTE &'
Reason: cannot convert from 'overloaded-function' to 'PBYTE *'
There is no context in which this conversion is possible
member.cpp(88) : error C2660: 'Verify' : function does not take 1 arguments
member.cpp(90) : error C2440: 'type cast' : cannot convert from 'void (__thiscall CDetour::* )(void)' to 'PBYTE &'
Reason: cannot convert from 'overloaded-function' to 'PBYTE *'
There is no context in which this conversion is possible
member.cpp(90) : error C2660: 'Verify' : function does not take 1 arguments
member.cpp(105) : error C2440: 'type cast' : cannot convert from 'void (__thiscall CDetour::* )(void)' to 'PVOID &'
Reason: cannot convert from 'overloaded-function' to 'PVOID *'
There is no context in which this conversion is possible
member.cpp(105) : error C2660: 'DetourAttach' : function does not take 1 arguments
member.cpp(120) : error C2440: 'type cast' : cannot convert from 'void (__thiscall CMember::* )(void)' to 'PBYTE &'
Reason: cannot convert from 'overloaded-function' to 'PBYTE *'
There is no context in which this conversion is possible
member.cpp(120) : error C2660: 'Verify' : function does not take 1 arguments
member.cpp(122) : error C2440: 'type cast' : cannot convert from 'void (__thiscall CDetour::* )(void)' to 'PBYTE &'
Reason: cannot convert from 'overloaded-function' to 'PBYTE *'
There is no context in which this conversion is possible
member.cpp(122) : error C2660: 'Verify' : function does not take 1 arguments
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\cl.EXE"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\nmake.exe"': return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\nmake.exe"': return code '0x2'
Stop.
C:\MSWORK\DetoursExpress30>
おーん、コンパイル エラー C2440。samples\member\member.cpp の該当箇所はこうなっています。
#if (_MSC_VER < 1310)
pfTarget = CMember::Target;
pfMine = CDetour::Mine_Target;
Verify("CMember::Target", *(PBYTE*)&pfTarget);
Verify("*CDetour::Real_Target", *(&(PBYTE&)CDetour::Real_Target));
Verify("CDetour::Mine_Target", *(PBYTE*)&pfMine);
#else
Verify("CMember::Target", (PBYTE)(&(PBYTE&)CMember::Target));
Verify("*CDetour::Real_Target", *(&(PBYTE&)CDetour::Real_Target));
Verify("CDetour::Mine_Target", (PBYTE)(&(PBYTE&)CDetour::Mine_Target));
#endif
Visual Studio 2013 なので _MSC_VER は 1800 ですが、_MSC_VER<1310 のときのコードも含めて何か奇妙です。Verify の第二引数は PVOID なので、メンバー関数へのポインターを void* に変換するのが目的ですが、いずれの条件でも参照型が出てきているのが不思議なところです。それ以前に、リテラルの CMember::Target って頭に & つけなくても動いたっけ、という疑問も。
C++ について新たに覚えたこともあったので、詳細は別記事にまとめるとして、結果的には Detours のコードが間違っていました。古い Visual Studio だと動くのかもしれません。今回は、以下のように _MSC_VER >=1700 のときの条件を members.cpp に追加 (青字部分) することにしました。_MSC_VER=1700 である Visual Studio 2010 でも同様のコンパイル エラーが出ます。2008 や 2005 でも同じだと思いますが、確認していないので 1700 以上にしました。
#if (_MSC_VER < 1310)
void (CMember::* pfTarget)(void) = CMember::Target;
void (CDetour::* pfMine)(void) = CDetour::Mine_Target;
Verify("CMember::Target", *(PBYTE*)&pfTarget);
Verify("*CDetour::Real_Target", *(PBYTE*)&CDetour::Real_Target);
Verify("CDetour::Mine_Target", *(PBYTE*)&pfMine);
#elif (_MSC_VER >= 1700)
void (CMember::* pfTarget)(void) = &CMember::Target;
void (CDetour::* pfMine)(void) = &CDetour::Mine_Target;
Verify("CMember::Target", (PBYTE*&)pfTarget);
Verify("*CDetour::Real_Target", (PBYTE*&)CDetour::Real_Target);
Verify("CDetour::Mine_Target", (PBYTE*&)pfMine);
#else
Verify("CMember::Target", (PBYTE)(&(PBYTE&)CMember::Target));
Verify("*CDetour::Real_Target", *(&(PBYTE&)CDetour::Real_Target));
Verify("CDetour::Mine_Target", (PBYTE)(&(PBYTE&)CDetour::Mine_Target));
#endif
printf("\n");
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
#if (_MSC_VER < 1310)
pfMine = CDetour::Mine_Target;
DetourAttach(&(PVOID&)CDetour::Real_Target,
*(PBYTE*)&pfMine);
#elif (_MSC_VER >= 1700)
pfMine = &CDetour::Mine_Target;
DetourAttach(&(PVOID&)CDetour::Real_Target,
(PBYTE&)pfMine);
#else
DetourAttach(&(PVOID&)CDetour::Real_Target,
(PVOID)(&(PVOID&)CDetour::Mine_Target));
#endif
LONG l = DetourTransactionCommit();
printf("DetourTransactionCommit = %d\n", l);
printf("\n");
#if (_MSC_VER < 1310)
pfTarget = CMember::Target;
pfMine = CDetour::Mine_Target;
Verify("CMember::Target", *(PBYTE*)&pfTarget);
Verify("*CDetour::Real_Target", *(&(PBYTE&)CDetour::Real_Target));
Verify("CDetour::Mine_Target", *(PBYTE*)&pfMine);
#elif (_MSC_VER >= 1700)
pfTarget = &CMember::Target;
pfMine = &CDetour::Mine_Target;
Verify("CMember::Target", (PBYTE*&)pfTarget);
Verify("*CDetour::Real_Target", (PBYTE*&)CDetour::Real_Target);
Verify("CDetour::Mine_Target", (PBYTE*&)pfMine);
#else
Verify("CMember::Target", (PBYTE)(&(PBYTE&)CMember::Target));
Verify("*CDetour::Real_Target", *(&(PBYTE&)CDetour::Real_Target));
Verify("CDetour::Mine_Target", (PBYTE)(&(PBYTE&)CDetour::Mine_Target));
#endif
Visual Studio 2010/Windows 7 はこれでビルドが通ります。Windows 8 以降の場合は、GetVersion API が古いということで、traceapi_win32.cpp において C4996 の警告が出ます。
cl /nologo /nologo /Zi /MT /Gm- /W4 /WX /Od /DDETOURS_BITS=32 /I..\..\include /Gs /DDETOURS_X86=1 /DDETOURS_32BIT=1 /D_X86_ /DDETOURS_OPTION_BITS=64 /Fdobj.X86\vc.pdb /Foobj.X86\trcapi.obj /ctrcapi.cpp trcapi.cpp
c:\mswork\detoursexpress30\samples\traceapi\_win32.cpp(4415) : error C2220: warning treated as error - no 'object' file generated
c:\mswork\detoursexpress30\samples\traceapi\_win32.cpp(4415) : warning C4996: 'GetVersion': was declared deprecated
C:\Program Files (x86)\Windows Kits\8.1\include\um\sysinfoapi.h(110) : see declaration of 'GetVersion'
c:\mswork\detoursexpress30\samples\traceapi\_win32.cpp(4418) : warning C4996: 'GetVersionExA': was declared deprecated
C:\Program Files (x86)\Windows Kits\8.1\include\um\sysinfoapi.h(433) : see declaration of 'GetVersionExA'
c:\mswork\detoursexpress30\samples\traceapi\_win32.cpp(4421) : warning C4996: 'GetVersionExW': was declared deprecated
C:\Program Files (x86)\Windows Kits\8.1\include\um\sysinfoapi.h(442) : see declaration of 'GetVersionExW'
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\cl.EXE"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\nmake.exe"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\BIN\nmake.exe"' : return code '0x2'
Stop.
これに対しては、samples\traceapi_win32.cpp の先頭に次の #pragma を書いて単純に回避できました。
//////////////////////////////////////////////////////////////////////////////
//
// Detours Test Program (_win32.cpp of traceapi.dll)
//
// Microsoft Research Detours Package, Version 3.0.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
///////////////////////////////////////////////////////////////// Trampolines.
//
#pragma warning( disable : 4996 )
int (__stdcall * Real_AbortDoc)(HDC a0)
= AbortDoc;
では Simple というサンプルを選んで、動きを見てみます。
Detours のサンプルは、フォルダー毎に独立しているのではなく、別フォルダーにあるファイルをインクルードしているなどの依存関係があり、分かりにくいです。samples\simple フォルダーには 2 つのソース ファイル sleep5.cpp と simple.cpp があります。simple5.cpp をビルドすると sleep5.exe ができますが、これは単に Sleep 関数を呼ぶだけで、Detours は関係ありません。しかし Detours を使うことで、main 関数からの Sleep 呼び出しをフックすることができるようになります。
Sleep の呼び出し時にフックの処理が記述されているのが simple.cpp です。DllMain があることからも分かるように、このファイルをビルドすると simple32.dll という DLL ができます。
ヘルプにも readme にもこの後どうやって実行すればいいのか書かれていませんが、simple の Makefile を見て、test セクションで実行されるコマンドで使い方が分かります。どうやら setdll.exe と withdll.exe というのを使うようです。
setdll.exe を使うと、sleep5.exe ファイルの内容が書き換えられ、sleep5.exe 実行時にsimple32.dll をロードしてフックが実行されるようになります。withdll.exe は、パラメーターとして指定した sleep5.exe を別プロセスとして実行し、このプロセスが simple32.dll をロードしてフックが行われます。しかし setdll.exe とは違って sleep5.exe を書き換えることはありません。
以下が出力結果です。setdll.exe を実行すると sleep5.exe が書き換わっていることをハッシュから確認しています。また、setdll は、変更前のファイルを sleep.exe~ という名前でバックアップしています。
C:\MSWORK\DetoursExpress30\bin.X86>sleep5.exe
sleep5.exe: Starting.
sleep5.exe: Done sleeping.
C:\MSWORK\DetoursExpress30\bin.X86>sigcheck -h sleep5.exe
Sigcheck v2.1 - File version and signature viewer
Copyright (C) 2004-2014 Mark Russinovich
Sysinternals - www.sysinternals.com
C:\MSWORK\DetoursExpress30\bin.X86\sleep5.exe:
Verified: Unsigned
Link date: 10:03 11/28/2014
Publisher: n/a
Description: n/a
Product: n/a
Prod version: n/a
File version: n/a
MachineType: 32-bit
MD5: 9F8F697CCE6CE4A4F4F985BD895133C2
SHA1: EF53ED6254428B38BE81C367181D2B9375B1EC0F
PESHA1: 82BC2B3B2E6C587CDE627AC08E1B1659159AAA47
PE256: 3F8EC4B687F4CC711496DCFBCB79F1443DC844D086758E5FAA25E6167EE4A31E
SHA256: 815D61B50A9C18A3EF15A2E7AC47251FD005FF0BC4146184ABDD06C6CF865C86
C:\MSWORK\DetoursExpress30\bin.X86>setdll.exe -d:simple32.dll sleep5.exe
Adding simple32.dll to binary files.
sleep5.exe:
simple32.dll
KERNEL32.dll -> KERNEL32.dll
C:\MSWORK\DetoursExpress30\bin.X86>sigcheck -h sleep5.exe
Sigcheck v2.1 - File version and signature viewer
Copyright (C) 2004-2014 Mark Russinovich
Sysinternals - www.sysinternals.com
C:\MSWORK\DetoursExpress30\bin.X86\sleep5.exe:
Verified: Unsigned
Link date: 10:03 11/28/2014
Publisher: n/a
Description: n/a
Product: n/a
Prod version: n/a
File version: n/a
MachineType: 32-bit
MD5: E2ED75D0C57A30FBE4737B3F6A0865EB
SHA1: C6FC17922F623B18AAEA939B3E8B593A7877E512
PESHA1: E59C41CB5BB55AC85B3956B10934F34DEDD71AE9
PE256: BDCF7BEA1BEAA03C5C83138BB67DA16F22A1C2E3A8A592A319DDCF37DB20CF49
SHA256: 80B8A7F7114836829397EC433E8E287D28355C0485B8AFAAAF16A1940686942F
C:\MSWORK\DetoursExpress30\bin.X86>sigcheck -h "sleep5.exe~"
Sigcheck v2.1 - File version and signature viewer
Copyright (C) 2004-2014 Mark Russinovich
Sysinternals - www.sysinternals.com
C:\MSWORK\DetoursExpress30\bin.X86\sleep5.exe~:
Verified: Unsigned
Link date: 10:03 11/28/2014
Publisher: n/a
Description: n/a
Product: n/a
Prod version: n/a
File version: n/a
MachineType: 32-bit
MD5: 9F8F697CCE6CE4A4F4F985BD895133C2
SHA1: EF53ED6254428B38BE81C367181D2B9375B1EC0F
PESHA1: 82BC2B3B2E6C587CDE627AC08E1B1659159AAA47
PE256: 3F8EC4B687F4CC711496DCFBCB79F1443DC844D086758E5FAA25E6167EE4A31E
SHA256: 815D61B50A9C18A3EF15A2E7AC47251FD005FF0BC4146184ABDD06C6CF865C86
C:\MSWORK\DetoursExpress30\bin.X86>sleep5.exe
simple32.dll: Starting.
simple32.dll: Detoured SleepEx().
sleep5.exe: Starting.
sleep5.exe: Done sleeping.
simple32.dll: Removed SleepEx() (result=0), slept 5031 ticks.
C:\MSWORK\DetoursExpress30\bin.X86>setdll -r sleep5.exe
Removing extra DLLs from binary files.
sleep5.exe:
KERNEL32.dll -> KERNEL32.dll
C:\MSWORK\DetoursExpress30\bin.X86>sleep5.exe
sleep5.exe: Starting.
sleep5.exe: Done sleeping.
C:\MSWORK\DetoursExpress30\bin.X86>withdll.exe -d:simple32.dll sleep5.exe
withdll.exe: Starting: `sleep5.exe'
withdll.exe: with `C:\MSWORK\DetoursExpress30\bin.X86\simple32.dll'
simple32.dll: Starting.
simple32.dll: Detoured SleepEx().
sleep5.exe: Starting.
sleep5.exe: Done sleeping.
simple32.dll: Removed SleepEx() (result=0), slept 5016 ticks.
C:\MSWORK\DetoursExpress30\bin.X86>
simple32.dll がロードされたことを示すログと、”slept 5016 ticks” というメッセージから、実際に API がフックされていることが分かります。
どのような仕組みでフックが行われるのか、という点に関しては、ヘルプの “Interception of Binary Functions” のページに書かれています。簡単に言えば、ターゲットとなる関数の先頭部分に jmp 命令をインジェクトしています。フックを元に戻せるようにして、フック中も元の関数へのアドレスを利用できようにしておくなどの細かい処理はたくさんあるようですが。
デバッグのログを貼ります。
C:\MSWORK\DetoursExpress30\bin.X86>E:\debuggers\pub.x86\cdb sleep5.exe 1
Microsoft (R) Windows Debugger Version 6.3.9600.16384 X86
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: sleep5.exe 1
************* Symbol Path validation summary **************
Response Time (ms) Location
Deferred cache*E:\symbols.pub
Deferred srv*http://msdl.microsoft.com/download/symbols
Symbol search path is: cache*E:\symbols.pub;srv*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00d40000 00d68000 sleep5.exe
ModLoad: 771b0000 7731e000 ntdll.dll
ModLoad: 76b10000 76c50000 C:\WINDOWS\SysWOW64\KERNEL32.DLL
ModLoad: 76330000 76407000 C:\WINDOWS\SysWOW64\KERNELBASE.dll
ModLoad: 0faf0000 0fb2d000 C:\MSWORK\DetoursExpress30\bin.X86\simple32.dll
(e80.34dc): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=c34b0000 edx=00000000 esi=7e629000 edi=00000000
eip=7726415d esp=001ff9bc ebp=001ff9e8 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
7726415d cc int 3
0:000> uf sleep5!main
*** WARNING: Unable to verify checksum for sleep5.exe
sleep5!main:
00d41000 55 push ebp
00d41001 8bec mov ebp,esp
00d41003 837d0802 cmp dword ptr [ebp+8],2
00d41007 7526 jne sleep5!main+0x2f (00d4102f)
sleep5!main+0x9:
00d41009 b804000000 mov eax,4
00d4100e c1e000 shl eax,0
00d41011 8b4d0c mov ecx,dword ptr [ebp+0Ch]
00d41014 8b1401 mov edx,dword ptr [ecx+eax]
00d41017 52 push edx
00d41018 e8a3000000 call sleep5!atoi (00d410c0)
00d4101d 83c404 add esp,4
00d41020 69c0e8030000 imul eax,eax,3E8h
00d41026 50 push eax
00d41027 ff1500c0d500 call dword ptr [sleep5!_imp__Sleep (00d5c000)]
00d4102d eb25 jmp sleep5!main+0x54 (00d41054)
sleep5!main+0x2f:
00d4102f 68a8c1d500 push offset sleep5!__xt_z+0x3c (00d5c1a8)
00d41034 e850010000 call sleep5!printf (00d41189)
00d41039 83c404 add esp,4
00d4103c 6888130000 push 1388h
00d41041 ff1500c0d500 call dword ptr [sleep5!_imp__Sleep (00d5c000)]
00d41047 68c0c1d500 push offset sleep5!__xt_z+0x54 (00d5c1c0)
00d4104c e838010000 call sleep5!printf (00d41189)
00d41051 83c404 add esp,4
sleep5!main+0x54:
00d41054 33c0 xor eax,eax
00d41056 5d pop ebp
00d41057 c3 ret
0:000> dds 00d5c000 l1
00d5c000 76b282d0 KERNEL32!SleepStub
0:000> u 76b282d0
KERNEL32!SleepStub:
76b282d0 ff259c00b976 jmp dword ptr [KERNEL32!_imp__Sleep (76b9009c)]
76b282d6 cc int 3
76b282d7 cc int 3
76b282d8 cc int 3
76b282d9 cc int 3
76b282da cc int 3
76b282db cc int 3
76b282dc cc int 3
0:000> dds 76b9009c l1
76b9009c 76331040 KERNELBASE!Sleep
0:000> u 76331040
KERNELBASE!Sleep:
76331040 8bff mov edi,edi
76331042 55 push ebp
76331043 8bec mov ebp,esp
76331045 6a00 push 0
76331047 ff7508 push dword ptr [ebp+8]
7633104a e8e11a0000 call KERNELBASE!SleepEx (76332b30)
7633104f 5d pop ebp
76331050 c20400 ret 4
0:000> u 76332b30
KERNELBASE!SleepEx:
76332b30 6a38 push 38h
76332b32 68b02b3376 push offset KERNELBASE!ValStateReleaseValues+0x58c (76332bb0)
76332b37 e855e5ffff call KERNELBASE!_SEH_prolog4 (76331091)
76332b3c c745b824000000 mov dword ptr [ebp-48h],24h
76332b43 c745bc01000000 mov dword ptr [ebp-44h],1
76332b4a 6a07 push 7
76332b4c 59 pop ecx
76332b4d 33c0 xor eax,eax
0:000> bp 00d41027
0:000> g
simple32.dll: Starting.
simple32.dll: Detoured SleepEx().
Breakpoint 0 hit
eax=000003e8 ebx=00000000 ecx=00000000 edx=00000008 esi=00d4140f edi=00d4140f
eip=00d41027 esp=001ffe04 ebp=001ffe08 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
sleep5!main+0x27:
00d41027 ff1500c0d500 call dword ptr [sleep5!_imp__Sleep (00d5c000)] ds:002b:00d5c000={KERNEL32!SleepStub (76b282d0)}
0:000> dds 00d5c000 l1
00d5c000 76b282d0 KERNEL32!SleepStub
0:000> u 76b282d0
KERNEL32!SleepStub:
76b282d0 ff259c00b976 jmp dword ptr [KERNEL32!_imp__Sleep (76b9009c)]
76b282d6 cc int 3
76b282d7 cc int 3
76b282d8 cc int 3
76b282d9 cc int 3
76b282da cc int 3
76b282db cc int 3
76b282dc cc int 3
0:000> dds 76b9009c l1
76b9009c 76331040 KERNELBASE!Sleep
0:000> u 76331040
KERNELBASE!Sleep:
76331040 8bff mov edi,edi
76331042 55 push ebp
76331043 8bec mov ebp,esp
76331045 6a00 push 0
76331047 ff7508 push dword ptr [ebp+8]
7633104a e8e11a0000 call KERNELBASE!SleepEx (76332b30)
7633104f 5d pop ebp
76331050 c20400 ret 4
0:000> u (76332b30)
KERNELBASE!SleepEx:
76332b30 e9dbe47b99 jmp simple32!TimedSleepEx (0faf1010)
76332b35 cc int 3
76332b36 cc int 3
76332b37 e855e5ffff call KERNELBASE!_SEH_prolog4 (76331091)
76332b3c c745b824000000 mov dword ptr [ebp-48h],24h
76332b43 c745bc01000000 mov dword ptr [ebp-44h],1
76332b4a 6a07 push 7
76332b4c 59 pop ecx
0:000> u (0faf1010) l10
simple32!TimedSleepEx:
0faf1010 55 push ebp
0faf1011 8bec mov ebp,esp
0faf1013 83ec0c sub esp,0Ch
0faf1016 ff1508c0b10f call dword ptr [simple32!_imp__GetTickCount (0fb1c008)]
0faf101c 8945f8 mov dword ptr [ebp-8],eax
0faf101f 8b450c mov eax,dword ptr [ebp+0Ch]
0faf1022 50 push eax
0faf1023 8b4d08 mov ecx,dword ptr [ebp+8]
0faf1026 51 push ecx
0faf1027 ff152462b20f call dword ptr [simple32!TrueSleepEx (0fb26224)]
0faf102d 8945f4 mov dword ptr [ebp-0Ch],eax
0faf1030 ff1508c0b10f call dword ptr [simple32!_imp__GetTickCount (0fb1c008)]
0faf1036 8945fc mov dword ptr [ebp-4],eax
0faf1039 8b55fc mov edx,dword ptr [ebp-4]
0faf103c 2b55f8 sub edx,dword ptr [ebp-8]
0faf103f b82062b20f mov eax,offset simple32!dwSlept (0fb26220)
0:000> dds (0fb26224) l1
0fb26224 363200d8
0:000> u 363200d8
363200d8 6a38 push 38h
363200da 68b02b3376 push offset KERNELBASE!ValStateReleaseValues+0x58c (76332bb0)
363200df e9532a0140 jmp KERNELBASE!SleepEx+0x7 (76332b37)
363200e4 cc int 3
363200e5 cc int 3
363200e6 cc int 3
363200e7 cc int 3
363200e8 cc int 3
0:000> !address 363200d8
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
Usage: <unknown>
Base Address: 36320000
End Address: 36330000
Region Size: 00010000
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 00020000 MEM_PRIVATE
Allocation Base: 36320000
Allocation Protect: 00000040 PAGE_EXECUTE_READWRITE
sleep5!main は Sleep を呼んでいて、フックするのは SleepEx なので少し面倒なことになっています。なお、Visual Studio 2010/Windows 7 の環境で試すと、SleepEx のフックで Sleep の呼び出しはフックされませんでした。詳しく見ていませんが、コンパイラの動作に依存しそうです。
実行時の流れはこのようになっていることが分かります。また、デバッガーの初回アタッチでは、まだフックが行われていません。
sleep5!main
--> call dword ptr [sleep5!_imp__Sleep (00d5c000)] = KERNEL32!SleepStub
--> jmp dword ptr [KERNEL32!_imp__Sleep (76b9009c)] = KERNELBASE!Sleep
--> call KERNELBASE!SleepEx (76332b30)
sleep5!main の call 命令でブレークさせると、今度はフックの処理が確認できました。大きく異なっているのは、KERNELBASE!SleepEx の先頭部分です。
0:000> u (76332b30)
KERNELBASE!SleepEx:
76332b30 e9dbe47b99 jmp simple32!TimedSleepEx (0faf1010)
76332b35 cc int 3
76332b36 cc int 3
76332b37 e855e5ffff call KERNELBASE!_SEH_prolog4 (76331091)
76332b3c c745b824000000 mov dword ptr [ebp-48h],24h
76332b43 c745bc01000000 mov dword ptr [ebp-44h],1
76332b4a 6a07 push 7
76332b4c 59 pop ecx
オリジナルでは、先頭は 2 つの push 命令でしたが、jmp と int 3 に変わっています。ジャンプ先は simple32 の関数であり、これがヘルプの “Interception of Binary Functions” に記載されている Detour Function です。
simple32!TimedSleepEx の中から、元の SleepEx を呼ぶため、simple32!TrueSleepEx というグローバル変数に保存してある関数アドレスを call します。この変数は、Detours の API である DetourAttach を呼び出したときに値が更新されているはずです。
simple32!TrueSleepEx には 363200d8 という、どのモジュールにも属さないアドレスが保存されています。これの中を見ると、KERNELBASE!SleepEx から消えた push が見つかりました。この小さなコード領域が、ヘルプで言うところの Trampoline Function です。
次に、ヘルプの “Payloads and DLL Import Editing” にある動作を見てみます。
0:000> !dh sleep5
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (i386)
5 number of sections
5478B90A time date stamp Fri Nov 28 10:03:54 2014
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
102 characteristics
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic #
12.00 linker version
1A800 size of code
9E00 size of initialized data
0 size of uninitialized data
140F address of entry point
1000 base of code
----- new -----
00d40000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
6.00 operating system version
0.00 image version
6.00 subsystem version
28000 size of image
400 size of headers
0 checksum
<snip>
SECTION HEADER #5
.detour name
950 virtual size
27000 virtual address
A00 size of raw data
22C00 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
(no align specified)
Read Write
0:000> lm m sleep5
start end module name
00d40000 00d68000 sleep5 C (private pdb symbols) e:\symbols.pub\sleep5.pdb\19BC9C78629D43B1B5698CEE4A8354691\sleep5.pdb
0:000> db sleep5+27000 l100
00d67000 40 00 00 00 44 74 72 00-10 09 00 00 10 09 00 00 @...Dtr.........
00d67010 84 12 02 00 28 00 00 00-00 00 00 00 00 00 00 00 ....(...........
00d67020 00 c0 01 00 30 01 00 00-00 70 02 00 d8 00 00 00 ....0....p......
00d67030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00d67040 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
00d67050 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
00d67060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00d67070 00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00 ................
00d67080 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
00d67090 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
00d670a0 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
00d670b0 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
00d670c0 bd 9d 52 15 f9 fc 3c 46-f9 fc 3c 46 f9 fc 3c 46 ..R...<F..<F..<F
00d670d0 bf ad dd 46 d9 fc 3c 46-bf ad e3 46 f6 fc 3c 46 ...F..<F...F..<F
00d670e0 bf ad dc 46 92 fc 3c 46-24 03 f7 46 fa fc 3c 46 ...F..<F$..F..<F
00d670f0 f9 fc 3d 46 b2 fc 3c 46-f4 ae dd 46 f8 fc 3c 46 ..=F..<F...F..<F
ヘルプに書かれている .detour セクションが見つかります。この sleep5.exe は setdll.exe で加工済みのものであり、実行時に simple32.dll をロードするように、インポート テーブルが書き換えられています。インポート テーブルを書き換えつつ、後で変更を元に戻せるようにオリジナルの PE ヘッダーやインポート テーブルを保存する、という目的のために .detours セクションが作られています。セクションの内容をダンプしてみると、00d67040 から PE ヘッダーが始まっていることが分かります。
こんなの何に使えるの、という話は今後書くということで、とりあえずここまで。