このネタも今回で最後にしておきます。とりあえず1週間通してやってみましたので。
最後はIMAGE_DIRECTORY_ENTRY_EXPORTです。前回のある意味反対です。
前回に使用した概念をいろいろと利用するのでみておくとよくわかると思います。
この場面で使用する構造体は以下の通りです。
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY;
前回と同じく
IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
から参照されるメモリアドレスのデータを使用します。DLLにはエクスポートできるモジュールはいくつもありますが、エクスポート対象のファイルは一つしかないのでこの構造体だけです。
よく使用するメンバは以下の通りです。
- NumberOfNames
エクスポート対象のモジュールの数を示します。
- AddressOfNames
「エクスポート対象となるモジュールの名前へのアドレス(相対アドレス)の配列」へのアドレス(相対アドレス)を示します。
ちょっとわかりづらい説明なのでカギ括弧をつけました。示した先のアドレスはDWORD *と考えることができます。これは次のメンバのAddressOfNameOrdinalsのデータと一対一で対応します。
- AddressOfNameOrdinals
「エクスポート対象となるモジュールの名前の序数(DLL内部での順番)」へのアドレス(相対アドレス)を示します。
これもわかりづらい説明なのでカギ括弧をつけました。示した先のアドレスはWORD *と考えることができます。なぜか序数はWORDサイズになっています。
参照するときは名前から序数への変換が必要になるため、この部分で変換を行います。
- AddressOfFunctions
「エクスポート対象となるモジュールの呼び出し先コードへのアドレス(相対アドレス)の配列」へのアドレス(相対アドレス)を示します。
相変わらず面倒です。ちなみに、この配列の参照は序数順で行います。序数が0のモジュールは配列番号が0の場所にコードアドレスがある、という状態になります。
基本はこの3つを使用します。それ以外にももちろん意味はありますが、参照するだけなら使用はしないと思います。
で、この説明を使うと実はWinAPIにあるGetProcAddressの物まねができるようになります。あくまで「物まね」なので完全な動作ではありません。
コード的にはこんな感じです。
FARPROC GetProcAdr(HMODULE hMoudle,LPCSTR lpProcName) { //hModuleはDLLイメージのモジュールハンドル(先頭アドレス)、lpProcNameは有効なモジュールの名前が示されているものとする IMAGE_NT_HEADERS *lpImage; IMAGE_EXPORT_DIRECTORY *ied; DWORD *nameptr,*adrtable; WORD *ordinalptr; LPCSTR name; FARPROC func; BYTE *pst,*ped; int ordinal,cnt,i; ・・・ //lpImageはDLLイメージのIMAGE_NT_HEADERSが示されているものとする pst = (BYTE *)hModule + lpImage->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; ped = pst + lpImage->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; ied = (IMAGE_EXPORT_DIRECTORY *)pst; func = NULL; if(!IMAGE_ORDINAL((uintptr_t)lpProcName)){ cnt = ied->NumberOfNames; nameptr = (DWORD *)((BYTE *)hModule + ied->AddressOfNames); ordinalptr = (WORD *)((BYTE *)hModule + ied->AddressOfNameOrdinals); for(i = 0;i < cnt;i++,nameptr++,ordinalptr++){ name = (LPCSTR)((BYTE *)hModule + *nameptr); if(lstrcmpA(lpProcName,name) == 0){ ordinal = *ordinalptr; adrtable = (DWORD *)((BYTE *)hModule + ied->AddressOfFunctions); func = (FARPROC)((BYTE *)hModule + adrtable[ordinal]); break; } } } return func; }
ちなみに、名前が正確に示されているパターンでしか有効ではありません。序数で示されている場合はちょっと違うやり方になりますがここでは扱いません。
また、対象のDLLにはないけれども対象のDLLがロードしているDLL側に素通りで渡しているパターン(kernel32.HeapAlloc=>ntdll.RtlAllocateHeap)でもうまくいきません。
この系のやり方は別のページで探してみてください。・・・いい加減ですな~。
また、ここまでのちょっとしたキーポイントとしてこの例でも扱ったとおり、HINSTANCEやHMODULEなどの対象モジュールを示すハンドルとは、実行プログラム中での
対象モジュールがロードされたメモリアドレスと同じものです。なので、リードオンリーであれば直接そのメモリにアクセスすることはできると思っていいと思います。
Windowsプログラムが実行できる仕掛けについてちょっとみてきましたが、実行ファイルにはまだまだ仕掛けがたっぷりとあります。
これらの仕掛けはwinnt.hにいろいろと記述してあるのでみてみるとおもしろいと思います。
それでは~。