[ADSI] Inside "System.DirectoryServices" - もっと君を知りたいぜ!(2) / 2 ildasm + Windbg

みなさまごきげんよう。ういこです。

私の上司は大変ユニークな人です。このサイトのタイトルを決めたのも私じゃなくて彼ですが、先日お客様に満足していただくための取組の話をチーム全体にしていて、たとえ話が「ロッキー」。ロッキーの最初のやつはさすがに全員知ってましたが、ロッキー3あたりになると怪しくなってきました。「?」が顔に書いてある同僚!それを見てたら面白くて、いいチームだなと思いました。

ぜひ週末に見てねって言われたんですが、まだ見て無いです。ごめんなさい、上司様。

ちなみにマイ上司はたとえ話をよくしますが、ネタが「キン肉マン」、「ガンダム」だったり、「信長の野望」だったりして最高です。松永久秀のたとえ話を好んでいるようですが、たぶんぴろとくんがそっちがイケる口だからでしょう。茶器の話を私初めて知りましたよ。まるで某マ・クベさんみたいですね

さて、本日は先日の中身見ちゃうぞの続きです。

【今日のお題】

System.DirectoryServices ( 以下 S.DS) 名前空間の処理って、

ぶっちゃけ何で動いてるの?

~その二~

今日は、ildasm の項をすごいさらっと流しちゃったので、もうちょっとだけ…。それと、ついに一番大変(に思えるけど使えると超便利)な Windbg での確認方法について激しくそれでいてしとやかに触れたいと思います★

1. System.DirectoryServices.dll が呼びだす関数について (ildasm)

.NET Framework SDK および Visual Studio に含まれる ildasm ツールを使用することで、今回のように対象の dll の中に格納されているメタデータ (クラスやメソッド、プロパティなどの情報) を確認することが可能です。

今回の対象の S.DS のモジュールを ildasm でメタデータを確認してみますと、ADSI (Activeds.dll) 関連のモジュールを呼び出していることが判ります。

以下、S.DS のメタデータから ADSI 関連の部分を抜き出したものです。

<--- 抜粋ここから --->

.module extern activeds.dll /*1A000002*/

.field /*0400010B*/ public static literal string Activeds = "activeds.dll"

.method /*0600020A*/ public hidebysig static pinvokeimpl("activeds.dll" winapi)

     bool FreeADsMem(native int pVoid) cil managed preservesig

.method /*0600020B*/ public hidebysig static pinvokeimpl("activeds.dll" unicode winapi)

     int32 ADsGetLastError([out] int32& 'error',

               class [mscorlib/*23000001*/]System.Text.StringBuilder/*0100003E*/ errorBuffer,

               int32 errorBufferLength,

               class [mscorlib/*23000001*/]System.Text.StringBuilder/*0100003E*/ nameBuffer,

               int32 nameBufferLength) cil managed preservesig

.method /*0600020C*/ public hidebysig static pinvokeimpl("activeds.dll" unicode winapi)

     int32 ADsSetLastError(int32 'error',

               string errorString,

               string provider) cil managed preservesig

.method /*06000219*/ private hidebysig static pinvokeimpl("activeds.dll" as "ADsOpenObject" nomangle unicode winapi)

     int32 IntADsOpenObject(string path,

                string userName,

                string password,

                int32 'flags',

                [in][out] valuetype [mscorlib/*23000001*/]System.Guid/*01000028*/& iid,

                [out] object& marshal( interface ) ppObject) cil managed preservesig

.method /*06000785*/ public hidebysig static pinvokeimpl("activeds.dll" unicode winapi)

     int32 ADsEncodeBinaryData(uint8[] data,

                   int32 length,

                   native int& result) cil managed preservesig

.method /*06000786*/ public hidebysig static pinvokeimpl("activeds.dll" winapi)

     bool FreeADsMem(native int pVoid) cil managed preservesig

<--- 抜粋ここまで --->

2. デバッガで確認する - コールスタックから

さて、いよいよ本丸、デバッガの一種の Windbg ツールを用いた確認の方法の概要となります。

< 導入手順 >

以下から Debugging Tools for Windows 32 ビット バージョンをインストールします。

Windows 用デバッグツール: 概要

https://www.microsoft.com/japan/whdc/DevTools/Debugging/default.mspx

確認の際は、Visual Studio、WinDBG 共にメソッドなどを特定するシンボル情報が必要です。

デバッグツールとシンボル: はじめに

https://www.microsoft.com/japan/whdc/devtools/debugging/debugstart.mspx#a

1. Windbg.exe を実行します。

2. [File] - [Symbol Search Path Ctrl+S] を選択します。

3. 公開されているシンボルを使用して確認します。

パブリック シンボル ファイルのパスは下記となります。

https://msdl.microsoft.com/download/symbols

以下設定例です。C:\Symbols.Pub 配下にシンボル情報をダウンロードします。

SRV*C:\Symbols.Pub*https://msdl.microsoft.com/download/symbols

4. [File] - [Open Executable... Ctrl+E] を選択します。

5. プログラムを指定し、実行します。

6. 読み込みが行われ、コマンド入力行が *BUSY* から 0:000> のように変化したタイミングで一旦ブレークが発生します。

7. wldap32.dll の読み込まれたタイミングでブレークさせるように以下のコマンドを実行します。

0:000> sxe ld wldap32.dll

0:000> g

8. WLDAP32.dll が読み込まれた時点でブレークが発生します。こんな風になって、コマンド入力行が再度 *BUSY* から 0:000> のように変化します。

0:000> g

ModLoad: 768e0000 76929000 c:\windows\system32\wldap32.dll

eax=00000000 ebx=00000000 ecx=00155555 edx=ffffffff esi=768e1000 edi=40000003

eip=63ef07b0 esp=001cc940 ebp=001cc990 iopl=0 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

9. 初期調査段階などで、対象の dll のどのメソッドが読み込まれているか判らない場合などは、以下のように dll の全てのメソッドにブレークを指定することを取り掛かりとします。

以下は wldap32.dll の全てのメソッドにブレークポイントを設定する例です。

0:000> bm wldap32!*

10. g コマンドを実行し、処理を進めます。

0:000> g

11. p コマンド(ステップ実行)あるいは t コマンド(トレース)を実行していき、レジスタの値や戻り値などをダンプしながら処理をすすめていきます。

12.該当の処理に到達しましたら、k コマンドでコール スタックを確認します。

以下パブリック シンボルを用いて検証をした際のコール スタック例です。

これを見ますと、wldap32!ldap_get_valuesW がコールされていることがわかります。

いったん、シンボルパスを確認してみましょう。

0:000> .sympath

Symbol search path is: SRV*C:\Symbols.Pub*https://msdl.microsoft.com/download/symbols

それでは、コールスタックを見てみましょう。Windbg でコールスタックを見るには、"k" コマンドを使います。今回はそのバリエーションの " ~*kvnL" を使ってみましょう。

このスタックを見ますと、自分の作ったモジュールの関数、MyLDAPTest!MyTest.Form1.Method1() から先の処理がわかります。中でSystem.DirectoryServices.SearchResultCollection() メソッドを呼び出していますね。

ここから先、adsldpc (ADs LDAP Provider C DLL) - adsldp (ADs LDAP Provider DLL) などを介して最終的に WLDAP32 (Win32 LDAP API DLL) を呼び出していることが判ります。

0:000> ~*kvnL

. 0 Id: 15b0.13b8 Suspend: 1 Teb: 7ffde000 Unfrozen

# ChildEBP RetAddr Args to Child

00 0012edcc 76ce6cb3 05a12070 05a138f0 76ced954 WLDAP32!ldap_get_valuesW (FPO: [Non-Fpo])

01 0012ede8 76ced72d 001fc528 05a138f0 76ced954 adsldpc!LdapGetValues+0x1d (FPO: [Non-Fpo])

02 0012ee50 76cee8d3 00211d90 0012ee74 00000000 adsldpc!ReadRootDSENode+0x100 (FPO: [Non-Fpo])

03 0012eea4 76ce397b 00211d90 0012eeec 00205ec0 adsldpc!ReadDomScopeSupportedAttr+0x30 (FPO: [Non-Fpo])

04 0012ef0c 76ce47ba 00211bd0 00205ec0 0012f0bc adsldpc!AddSearchControls+0xa5 (FPO: [Non-Fpo])

05 0012ef34 711a8292 00211bd0 00205ec0 0012ef70 adsldpc!ADsGetFirstRow+0x77 (FPO: [Non-Fpo])

06 0012ef44 011032ee 00205e04 00211bd0 c30a05a1 adsldp!CLDAPGenObject::GetFirstRow+0x24 (FPO: [Non-Fpo])

07 0012ef70 6716f0f6 014203e4 01423414 01401178 CLRStub[StubLinkStub]@11032ee

08 0012f014 00e213ff 00000000 00000000 00000000 System_DirectoryServices_ni!System.DirectoryServices.SearchResultCollection+ResultsEnumerator.MoveNext()+0x5a (Managed)

09 0012f014 00e2108d 00000000 00000000 00000000 MyLDAPTest!MyTest.Form1.Method1()+0x317 (Managed)

0a 0012f0a8 7b060a6b 00000000 00000000 00000000 MyLDAPTest!MyTest.Form1.button_Click(System.Object, System.EventArgs)+0x25 (Managed)

0b 0012f0a8 7b105379 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Control.OnClick(System.EventArgs)+0x57 (Managed)

0c 0012f0a8 7b10547f 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Button.OnClick(System.EventArgs)+0x49 (Managed)

0d 0012f0a8 7b0d02d2 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)+0xc3 (Managed)

0e 0012f0a8 7b072c74 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)+0xf2 (Managed)

0f 0012f114 7b0815a6 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)+0x544 (Managed)

10 0012f150 7b0814c3 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)+0xce (Managed)

11 0012f1b0 7b07a72d 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)+0x2b (Managed)

12 0012f1b0 7b07a706 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)+0xd (Managed)

13 0012f1b0 7b07a515 00000000 00000000 00000000 System_Windows_Forms_ni!System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)+0xd6 (Managed)

こんな感じで見ていくと、実際に内部でどんな流れになるのかがわかってすごく興味深いと思います。ぜひ、一度お試しください!

~ういこう@武田信玄の対男子ラブレターに胸キュン~