User32.dll,kernel32.dll,shell32.dll,gdi32.dll,rpcrt4.dll,comctl32.dll,advapi32.dll,version.dll等dll代表了Win32 API的基本提供者;
Win32 API中的所有调用最终都转向了ntdll.dll,再由它转发至ntoskrnl.exe。ntdll.dll是本机 API用户模式的终端。真正的接口在ntoskrnl.exe里完成。事实上,内核模式的驱动大部分时间调用这个模块,如果它们请求系统服务。Ntdll.dll的主要作用就是让内核函数的特定子集可以被用户模式下运行的程序调用。Ntdll.dll通过软件中断int 2Eh进入ntoskrnl.exe,就是通过中断门切换CPU特权级。
Ntdll.dll 上面的相关API函数原型和参数都没有文档化(Undocumented ): http://undocumented.ntinternals.net/ 这里提供了Ntdll.dll部分未公开函数的原型.
理解window API及函数原型对我们的调试将是非常重要的: 因为你时常需要去察看一些函数的参数,或者根据参数找到某些输入指针.
例如:
17 Id: a84.cc4 Suspend: 1 Teb: 7ff3a000 Unfrozen
ChildEBP RetAddr Args to Child
187ffdb8 77845e6c 7782fc72 00001938 00000000 ntdll!KiFastSystemCallRet
187ffdbc 7782fc72 00001938 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
187ffe20 7782fb56 00000000 00000000 00000000 ntdll!RtlpWaitOnCriticalSection+0x13e
187ffe48 01b05d13 0x77c8ba60 81fa55ed 028766c8 ntdll!RtlEnterCriticalSection+0x150
从堆栈可以看出线程17 正在进入某一个临界区. 0x77c8ba60 就是传入的临界值 参数.
17> !cs 0x77c8ba60 --> !cs 是用来查看临界区信息的命令
DebugInfo = 0x77fbde20
Critical section = 0x77c8ba60 (GDI32!semColorSpaceCache+0x0)
LOCKED
LockCount = 0x0
OwningThread = 0x00000dd8
RecursionCount = 0x1
LockSemaphore = 0x0
SpinCount = 0x00000000
可以看到 LOCKED 代表临界区是锁定状态. 即被占用.
OwningThread 即是占用线程.
临界区信息结构定义在ntdll, 可以使用如下指令进行察看.
> dt ntdll!_RTL_CRITICAL_SECTION
+0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : Int4B
+0x008 RecursionCount : Int4B
+0x00c OwningThread : Ptr32 Void
+0x010 LockSemaphore : Ptr32 Void
+0x014 SpinCount : Uint4B
察看某个动态库函数表的指令:
x ntdll!*
x kernal!*
察看结构体定义:
dt ntdll!*
任何动态库包括window 32的用户态dll 和用户自定义动态库都是生长在进程内存空间上的.
DLL 没有自己的"私有"地址空间. 它们总是被影射到应用程序的虚拟地址空间,在需要时才会被读取到物理内存中.
在本系列的其它章节我会谈到虚拟地址空间的内容.
通过指令可以看到ntdll 被映射到77800000 ~ 7793c000的内存空间中.
> x *!
77800000 7793c000 ntdll (pdb symbols) c:\mylocalsymbols\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb
当你的代码(线程)栈中出现地址范围在 77800000 ~7793c000 之间的函数调用都表示在call NTDLL.dll
比如:
7 Id: a84.c34 Suspend: 1 Teb: 7ff3f000 Unfrozen
ChildEBP RetAddr Args to Child
089bfe8c 77845e6c 75a0179c 00000d98 00000000 ntdll!KiFastSystemCallRet
089bfe90 75a0179c 00000d98 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
089bfefc 75c9f003 00000d98 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98
089bff14 75c9efb2 00000d98 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75
089bff28 69434fea 00000d98 ffffffff 0780c178 kernel32!WaitForSingleObject+0x12
WARNING: Stack unwind information not available. Following frames may be wrong.
此线程中WARNING: Stack unwind information not available. Following frames may be wrong.表示windbg无法翻译或者找到对应symbols来显示code stack. 这种错误往往是保存dump file时出现的某种异常信息.window也没有给出合理的解释.
以下是MSDN的原话:
In some cases, the stack trace function will fail in the debugger. This can be caused by a call to an invalid address that caused the debugger to lose the location of the return address; or you may have come across a stack pointer for which you cannot directly get a stack trace; or there could be some other debugger problem. In any case, being able to manually walk a stack is often valuable.
这时候你需要手动的进行恢复栈调用. 如果你了解每个动态库的映射地址你就很容易进行分析了.
察看动态库中每个函数映射的地址可以采用如下指令 :
x ntdll!
手动恢复栈的大致原理如下:
1. 列出线程环境信息
0:000> !teb
Win32 API中的所有调用最终都转向了ntdll.dll,再由它转发至ntoskrnl.exe。ntdll.dll是本机 API用户模式的终端。真正的接口在ntoskrnl.exe里完成。事实上,内核模式的驱动大部分时间调用这个模块,如果它们请求系统服务。Ntdll.dll的主要作用就是让内核函数的特定子集可以被用户模式下运行的程序调用。Ntdll.dll通过软件中断int 2Eh进入ntoskrnl.exe,就是通过中断门切换CPU特权级。
Ntdll.dll 上面的相关API函数原型和参数都没有文档化(Undocumented ): http://undocumented.ntinternals.net/ 这里提供了Ntdll.dll部分未公开函数的原型.
理解window API及函数原型对我们的调试将是非常重要的: 因为你时常需要去察看一些函数的参数,或者根据参数找到某些输入指针.
例如:
17 Id: a84.cc4 Suspend: 1 Teb: 7ff3a000 Unfrozen
ChildEBP RetAddr Args to Child
187ffdb8 77845e6c 7782fc72 00001938 00000000 ntdll!KiFastSystemCallRet
187ffdbc 7782fc72 00001938 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
187ffe20 7782fb56 00000000 00000000 00000000 ntdll!RtlpWaitOnCriticalSection+0x13e
187ffe48 01b05d13 0x77c8ba60 81fa55ed 028766c8 ntdll!RtlEnterCriticalSection+0x150
从堆栈可以看出线程17 正在进入某一个临界区. 0x77c8ba60 就是传入的临界值 参数.
17> !cs 0x77c8ba60 --> !cs 是用来查看临界区信息的命令
DebugInfo = 0x77fbde20
Critical section = 0x77c8ba60 (GDI32!semColorSpaceCache+0x0)
LOCKED
LockCount = 0x0
OwningThread = 0x00000dd8
RecursionCount = 0x1
LockSemaphore = 0x0
SpinCount = 0x00000000
可以看到 LOCKED 代表临界区是锁定状态. 即被占用.
OwningThread 即是占用线程.
临界区信息结构定义在ntdll, 可以使用如下指令进行察看.
> dt ntdll!_RTL_CRITICAL_SECTION
+0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : Int4B
+0x008 RecursionCount : Int4B
+0x00c OwningThread : Ptr32 Void
+0x010 LockSemaphore : Ptr32 Void
+0x014 SpinCount : Uint4B
察看某个动态库函数表的指令:
x ntdll!*
x kernal!*
察看结构体定义:
dt ntdll!*
任何动态库包括window 32的用户态dll 和用户自定义动态库都是生长在进程内存空间上的.
DLL 没有自己的"私有"地址空间. 它们总是被影射到应用程序的虚拟地址空间,在需要时才会被读取到物理内存中.
在本系列的其它章节我会谈到虚拟地址空间的内容.
通过指令可以看到ntdll 被映射到77800000 ~ 7793c000的内存空间中.
> x *!
77800000 7793c000 ntdll (pdb symbols) c:\mylocalsymbols\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb
当你的代码(线程)栈中出现地址范围在 77800000 ~7793c000 之间的函数调用都表示在call NTDLL.dll
比如:
7 Id: a84.c34 Suspend: 1 Teb: 7ff3f000 Unfrozen
ChildEBP RetAddr Args to Child
089bfe8c 77845e6c 75a0179c 00000d98 00000000 ntdll!KiFastSystemCallRet
089bfe90 75a0179c 00000d98 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
089bfefc 75c9f003 00000d98 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98
089bff14 75c9efb2 00000d98 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75
089bff28 69434fea 00000d98 ffffffff 0780c178 kernel32!WaitForSingleObject+0x12
WARNING: Stack unwind information not available. Following frames may be wrong.
此线程中WARNING: Stack unwind information not available. Following frames may be wrong.表示windbg无法翻译或者找到对应symbols来显示code stack. 这种错误往往是保存dump file时出现的某种异常信息.window也没有给出合理的解释.
以下是MSDN的原话:
In some cases, the stack trace function will fail in the debugger. This can be caused by a call to an invalid address that caused the debugger to lose the location of the return address; or you may have come across a stack pointer for which you cannot directly get a stack trace; or there could be some other debugger problem. In any case, being able to manually walk a stack is often valuable.
这时候你需要手动的进行恢复栈调用. 如果你了解每个动态库的映射地址你就很容易进行分析了.
察看动态库中每个函数映射的地址可以采用如下指令 :
x ntdll!
手动恢复栈的大致原理如下:
1. 列出线程环境信息
0:000> !teb
TEB at 7fffe000
ExceptionList: 0012ff88
StackBase: 00130000
StackLimit: 00126000
……….
2. 打开整个线程栈.
0:000> dds 00126000 00130000