dbghelp有个功能,那就是MiniDumpWithUnloadedModules,
当os维护了已经unload的dll的列表时,它会在minidump文件中存入unloaded modules的信息。
只可惜的是,msdn中明说了,dbghelp 5.1并不支持这个标志。
看了一下winxpsp2自带的dbghelp.dll,恰好是这个版本的。
这一个标志是很重要的,因为某些BUG,经查恰好运行到一个非法的地址时Crash,
而这个非法的地址怎么看都像是原来有一个dll恰好加载在这里,但如今
这个dll已经被FreeLibrary了。如果有了这个unload modules的信息,
那么这个BUG就很容易找出来。如果没有的话,就麻烦了,因为程序代码
中都是com指针,虚表调来调处的根本不知道调用处正常情况应当调用的是哪个dll。
既然这个标志重要,自然应当把它启用,但winxp sp2恰恰不支持,如果为了这一个功能
就打包一个体积不小的dbghelp.dll的话,也划不来。
因此,想到一点:
何不自己把unloaded modules的信息,记载下来跟.dmp文件一起上报呢?
如是开始研究,先写了一个很小的程序来实现产生minidump的功能,然后用ollydbg来调试,
看dbghelp.dll是如何拿到unloaded modules的信息的。
程序虽小,五脏俱全,代码如下:
int Filter(_EXCEPTION_POINTERS * source, _EXCEPTION_POINTERS * dest) { *dest->ContextRecord = *source->ContextRecord; *dest->ExceptionRecord = *source->ExceptionRecord; return EXCEPTION_EXECUTE_HANDLER; } void Doxx(EXCEPTION_POINTERS * p) { HANDLE ho = CreateFile(L"oo.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, 0); _MINIDUMP_EXCEPTION_INFORMATION ExInfo; ExInfo.ThreadId = GetCurrentThreadId(); ExInfo.ExceptionPointers = p; ExInfo.ClientPointers = TRUE; MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), ho, MiniDumpWithUnloadedModules, &ExInfo, 0, 0); } int main() { EXCEPTION_RECORD ExceptionRecord; CONTEXT ContextRecord; EXCEPTION_POINTERS ptr; ptr.ExceptionRecord = &ExceptionRecord; ptr.ContextRecord = &ContextRecord; __try { *(int*)0 = 0; } __except (Filter(GetExceptionInformation(), &ptr)) { Doxx(&ptr); } } |
然后反复查找应用的断点,找字符串找到:
GenGetProcessInfo.EnumUnloadedModules(0x%x) failed, 0x%08x
就从它入手了,找到相应的代码引用,然后看上下文,不多久找到了
NtWin32LiveSystemProvider::EnumUnloadedModules 函数,
一看实现,是从已经有的内存里copy一个字符串出来而已。
那还要找到这段内存如何来的,在指定改写处下硬点断点重跑,
发现这个内存是用ReadProcessMemory读来的,读的地址是7C99D8C0,
跑到那里一看,果然有unloaded 的 shimengine.dll的信息。
看了下od的log确认这个dll在exe的入口点之前被卸载。
接下来就查这个7C99D8C0是哪里来的了,往上翻代码,发现是调了一个函数,
在那里下断点重跑,找到了:
030656DE |. FFD0 CALL EAX ; ntdll.RtlGetUnloadEventTrace |
google一把RtlGetUnloadEventTrace,发现msdn2上居然有详细说明。
到是出乎意料。
OK,大功告成,总结如下:
windows xp sp2和2003 sp1以后,ntdll维护了unloaded module的信息。
由ntdll写入一个叫RtlpUnloadEventTrace的数组,并导出函数
RtlGetUnloadEventTrace返回该数组。代码实现:
#define RTL_UNLOAD_EVENT_TRACE_NUMBER 64 typedef struct _RTL_UNLOAD_EVENT_TRACE { PVOID BaseAddress; // Base address of dll SIZE_T SizeOfImage; // Size of image ULONG Sequence; // Sequence number for this event ULONG TimeDateStamp; // Time and date of image ULONG CheckSum; // Image checksum WCHAR ImageName[32]; // Image name } RTL_UNLOAD_EVENT_TRACE, *PRTL_UNLOAD_EVENT_TRACE; RTL_UNLOAD_EVENT_TRACE RtlpUnloadEventTrace[RTL_UNLOAD_EVENT_TRACE_NUMBER]; PRTL_UNLOAD_EVENT_TRACE RtlGetUnloadEventTrace() { return RtlpUnloadEventTrace; } |
我们只需要调用RtlGetUnloadEventTrace即可拿到指向数组首元素的指针。
但是这个数组有多少项呢?msdn2上说是有64项,但dbghelp只读了16个,为什么呢?
如果64个,占用内存大小应当是:
sizeof(RTL_UNLOAD_EVENT_TRACE) * 64 = 54h*40h = 1500h
打开ntdll.dll的data segment一看,其实大小并没有这么多,只有540h个字节,
也就是说,至少在windows xp sp2,这个结构的大小只有10h即16个元素。
也许在其它版本如vista更大了吧,不管它。
到这里,要做的,无非就是GetProcAddress拿到RtlGetUnloadEventTrace,
拿到返回的指针,再读出16个结构出来,然后整理成我需要的报告形式,与
.dmp一起上报就好了。