調試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結合判斷一下比較好~