调试dll的时候会有一件事情比较烦人,就是dll加载的地址不会很固定(默认设置下编译的dll基址总是0x10000000,多个同基址的dll加载时,后面的肯定会被重定位),这给前后多次调试时对比分析结果造成了一些麻烦,要解决这个问题,有两种办法。
方法一:直接修改dll文件PE头中的ImageBase为一个不大可能被占用的地址。
但是这个方法有一个小小的局限,就是有些文件是存在校验的,改了文件之后会出一些问题,比如拒绝加载之类的。 这种情况就要用第二种方法了。
方法二:动态修改dll的加载基址
当然,首先要搞清楚是哪里决定了dll的加载基址。
下面是dll的加载过程
kernel32!LoadlibraryA ->kernel32!LoadLibraryExA ->kernel32!LoadLibraryExW ->ntdll!LdrLoadDll ->ntdll!LdrpLoadDll ->ntdll!LdrpMapDll ->ntdll!LdrpCreateDllSection ->ntdll!ZwOpenFile //打开目标文件 ->ntdll!ZwCreateSection //创建映射 ->ntdll!ZwMapViewOfSection //映射
关键就在映射这一步~ 函数原型如下:
NTSTATUS ZwMapViewOfSection( IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits, IN SIZE_T CommitSize, IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, IN OUT PSIZE_T ViewSize, IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType, IN ULONG Win32Protect );
第三个参数就是要映射的基址了,关于这个参数,MSDN上有这样的说明:
BaseAddress
Pointer to a variable that receives the base address of the view. If the value of this parameter is not NULL, the view is allocated starting at the specified virtual address rounded down to the next 64-kilobyte address boundary.
也就是说,如果这个参数不是NULL的话,就会以这个地址向后对齐后作为基址,而系统加载dll时调用该函数时参数指向的值是0,也就是自行分配空闲地址
我们只需要在这里挂钩该函数,判断是目标dll在加载时,设置该值不为NULL就可以了,使其指向一个未分配的固定地址,比如0x60000000什么的~
这个挂钩可以在ring3进行也可以在ring0进行,但是ring3的话需要保证持钩的dll在目标dll之前加载,所以我干脆ring0了~
if(PreviousMode == UserMode && 是目标dll) { *BaseAddress = 0x60000000 ;//要固定的基址 } return OriginalZwMapViewOfSection(SectionHandle,ProcessHandle,BaseAddress...);
在ZwMapViewOfSection的Hook函数中要判断是不是目标dll,只有一个SectionHandle参数可以用,但是单从一个SectionHandle不好判断是哪个dll(ring0可以直接取SectionObject.Segment.ControlArea.FilePointer进行判断,比较精确),我用的方法是ZwQuerySection查询这个句柄的SectionImageInformation信息,然后取ImageFileSize进行判断,这个ImageFileSize就是目标dll的真实文件大小,这个方法ring3和ring0都可以用,拿来判断问题不是很大,但是想绝对精确的话还是和ZwCreateSection时的FileHandle结合判断一下比较好~