標籤:blog 使用 問題 代碼 window div
0:000> u ntdll!KiFastSystemCallntdll!KiFastSystemCall:7c92eb8b 8bd4 mov edx,esp7c92eb8d 0f34 sysenterntdll!KiFastSystemCallRet:7c92eb8f 90 nop7c92eb90 90 nop7c92eb91 90 nop7c92eb92 90 nop7c92eb93 90 nopntdll!KiFastSystemCallRet:7c92eb94 c3 ret
而我們通過dump disasm得到的結果
0x77d1a146 : FF 15 24 14 D1 77 call dword ptr [0x77d11424]【ntdll.dll.[.text].NtCallbackReturn】0x7c92d51d : B8 14 00 00 00 mov eax, 0x140x7c92d522 : BA 00 03 FE 7F mov edx, 0x7ffe03000x7c92d527 : FF 12 call dword ptr [edx]
【ntdll.dll.[.text].KiFastSystemCall】 0x7c92eb8b : 8B D4 mov edx, esp 0x7c92eb8d : 0F 34 sysenter 0x7c92eb94 : C3 ret
0x77ef67d4 : C3 ret
根據上面的結果顯示,KiFastSystemCall在執行結束本來應該返回到0x7c92d527的下一條指令即0x7c92d529處執行,但是為什麼會返回到0x77ef67d4處呢?
通過查看調用棧
call [0x0012f43c] : 0x7c92d527 --> 0x7c92eb8b
return[0x0012f458] : 0x77ef67d4 <-- 0x7c92eb94
Thread 38:
[# 0] [0x0012fb20]:[0x7c941739 - 0x7c94173e] ==> [0x7c93c9e4] ntdll.dll.[.text].LdrFindResourceDirectory_U_0x000000af [# 1] [0x0012fa18]:[0x7c93cba6 - 0x7c93cbab] ==> [0x7c921193] ntdll.dll.[.text].LdrInitializeThunk_0x00000015 [# 2] [0x0012f9f8]:[0x7c9211a4 - 0x7c9211a7] ==> [0x77d1f518] USER32.dll.[.text].UserClientDllInitialize [# 3] [0x0012f46c]:[0x77d1f76f - 0x77d1f774] ==> [0x77d1f791] USER32.dll.[.text].UserClientDllInitialize_0x00000279 [# 4] [0x0012f460]:[0x77ef655e - 0x77ef6563] ==> [0x77ef67c8] GDI32.dll.[.text].GdiProcessSetup_0x000001ac [# 5] [0x0012f45c]:[0x77ef67d2 - 0x77ef67d4] ==> [0x7c92eb8b] ntdll.dll.[.text].KiFastSystemCall [# 6] [0x0012f450]:[0x7c92eae0 - 0x7c92eae3] ==> [0x77d1a12e] USER32.dll.[.text].ClientThreadSetup_0x00000134 [# 7] [0x0012f440]:[0x77d1a146 - 0x77d1a14c] ==> [0x7c92d51d] ntdll.dll.[.text].NtCallbackReturn--> [# 8] [0x0012f43c]:[0x7c92d527 - 0x7c92d529] ==> [0x7c92eb8b] ntdll.dll.[.text].KiFastSystemCall
可見,0x77ef67d4是在第一次調用KiFastSystemCall的時候,儲存在棧上的返回地址,當時的棧的位置是0x0012f45c(還沒有將返回地址壓到棧上時);
而在返回時,棧的位置是0x0012f458,因此正好是返回到了第一次調用KiFastSystemCall的位置。
如果是這樣,那麼我們的影子調用棧出現了什麼問題呢?
我們知道,當使用者態程式進入核心態執行後(通過sysenter),核心可能會調度到使用者態的APC等等回呼函數執行,這些回呼函數的執行發起者是核心態代碼,因此它們需要將執行流程返回給核心代碼;
Windows提供了一種機制,當使用者態的回呼函數沒有提供顯式的返回到核心的代碼時,Windows會自動執行預設的返回到核心態的代碼,而這一段代碼恰好也是通過KiFastSystemCall機制完成的(因為KiFastSystemCall是使用者態通向核心態的唯一入口),這一機制被稱為NtCallbackReturn。
因此,我們懷疑,NtCallbackReturn機制會使使用者態的代碼孤立起來看,是不符合棧平衡的,原因就是NtCallbackReturn的核心服務程式中會對使用者態的棧做調整,以掩蓋非同步回呼函數被執行過的痕迹。
因此,我們相信,NtCallbackReturn的存在,使得我們單純地依據使用者態的call/ret指令建立起來的影子調用棧出現了不平衡的現象。
解決方案,就是在調用過NtCallbackReturn之後,添加三次等效ret的效果,可以解決影子棧的不平衡問題。
事情到了這裡,還沒有結束,因為還不知道有哪些系統調用會破壞使用者態棧的平衡。
比如NtContinue就很可疑(根據實現結果),那麼使用者態的影子調用棧要通過什麼方式來保證其正確性呢?
更普遍的做法,可以根據KiFastSystemCallRet來判斷,它可以與KiFastSystemCall配對,從而消除中間的多餘項目。