DNF 90版本分析筆記

DNF 90版本TP分析筆記

時間:2019.1.16

測試環境:Windows 7 7600 32位虛擬機環境

DNF90版本爲現在時間最新的遊戲版本,本篇筆記將會記錄這個遊戲從內核到應用層的各種保護機制。本篇筆記裏面不會去嘗試繞過保護等等,只分析TP模塊阻止調試加入了什麼樣的技術。

關於虛擬機的設置:

在虛擬機的.vmx文件中加入如下的選項以防止虛擬機被檢測:

monitor_control.restrict_backdoor = "true"
isolation.tools.getPtrLocation.disable = "TRUE"
isolation.tools.setPtrLocation.disable = "TRUE"
isolation.tools.setVersion.disable = "TRUE"
isolation.tools.getVersion.disable = "TRUE"
monitor_control.disable_directexec = "TRUE"
monitor_control.disable_chksimd = "TRUE"
monitor_control.disable_ntreloc = "TRUE"
monitor_control.disable_selfmod = "TRUE"
monitor_control.disable_reloc = "TRUE"
monitor_control.disable_btinout = "TRUE"
monitor_control.disable_btmemspace = "TRUE"
monitor_control.disable_btpriv = "TRUE"
monitor_control.disable_btseg = "TRUE"
monitor_control.virtual_rdtsc = "false"

更詳細的說明參見:

isolation: http://faq.sanbarrow.com/index.php?action=artikel&cat=14&id=57&artlang=en

monitor_control : http://faq.sanbarrow.com/index.php?action=artikel&cat=14&id=59&artlang=en

VMX-parameters : http://faq.sanbarrow.com/index.php?sid=5045396&lang=en&action=show&cat=2

DNF遊戲保護的各個階段

  1. 內核態禁用內核調試器
  2. 內核態禁用用戶態調試器附加遊戲
  3. 內核態阻止進程讀取遊戲內存
  4. R3阻止代碼斷點
  5. 遊戲內數據加密

階段1:驅動TesSafe阻止內核調試器的研究分析

調試程序的行爲,自然要讓程序運行起來,然後觀測其行爲來分析判斷它的功能。

**第一波測試:**現象:當啓用內核調試運行遊戲時,遊戲啓動時加載TesSafe.sys,在TesSafe.sys的DriverEntry的時候,就會觸發Trap0E異常,並且在Trap0E的內部再次循環產生0E異常,然後會導致藍屏,以下是調用堆棧:

...
KiTrap0E+0x175
KiTrap0E+0x175
KiTrap0E+0x175
KeUpdateRunTime
TesSafe.sys+xxxxx
DriverEntry

經測試,以上的藍屏屬於特殊情況,在之後的測試中沒有再次出現這個藍屏。

**第二波無修改測試:**直接啓動遊戲,在遊戲點擊登錄之後,出現藍屏。

在這裏插入圖片描述

藍屏錯誤代碼爲0x99999999,很明顯微軟並沒有這個藍屏錯誤,那麼就只有一個可能就是遊戲檢測到了當前正處於調試模式;

第三波嘗試,這一次的思路是,先啓動70版本的遊戲,然後再次啓動90的程序,意圖是先讓70版本的遊戲”初始化“系統環境,然後再啓動90版本,看看有什麼反應。

先啓動70版本的DNF,該版本的TesSafe.sys運行時會調用nt!KdDisableDebugger以及其他的什麼操作,然後再運行90版本DNF,但是此時會直接出現藍屏,

針對以上問題的思路,因爲遊戲嘗試調用了KeBugCheck函數來產生一個藍屏,那麼此時嘗試來中斷KeBugCheck來看看能不能阻止藍屏。 (未測試成功,當修改了KdDebuggerEnabled等變量值時,不會再次產生藍屏。修改了這些用於調試的變量時,Windbg就不能連接觀察了。 關於KdDebuggerEnabled等變量的測試分析參考70版本DNF的分析筆記。

2019.1.17

第四波嘗試,其實已經是昨天的嘗試了,這裏和今天的進展一起記錄。

虛擬機進入調試模式,然後在運行遊戲之前,使用Windbg進行本地調試修改如下的數據;

;; 注意:這些命令需要在本地調試的時候執行,因爲這些數據一旦修改,雙機調試會立即中斷而不能接收到任何調試信息
;; 修改nt!KiDebugRoutine爲nt!KdpStub; 名稱來自於ntoskrnl.exe 的導出
; nt!KdpTrap是當前調試器連接的時候需要調用的函數,該函數內部會將異常發送到調試器進行處理;
ed nt!KiDebugRoutine nt!KdpStub

; 修改nt!KdDebuggerEnabled,這個變量也是一個關鍵變量,所有有關於調試的的函數都會使用該變量進行判斷;
eb nt!KdDebuggerEnabled 0

通過以上修改後測試發現已經不會藍屏,但是在遊戲啓動階段虛擬機直接重啓,也就是說連藍屏的機會都不給了。也就是說遊戲仍然檢測了更多的位置;

既然以上的操作沒用,那麼此時重新迴歸異常和調試的流程重新開始分析;

這裏可以參考看雪的一篇分析文檔。[原創]某驅動的內核調試檢測學習內核調試引擎加載機制

通過觀察WRK和ReactOS的源碼以及查看ntoskrnl.exe的導出函數(通過PE工具可以查看導出函數)得知,ntoskrnl.exe有且導出瞭如下的變量且可以用這些變量來感知當前操作系統是否正處於調試模式:

  • nt!KdDebuggerNotPresent, 當此變量爲TRUE時,則代表當前調試器未就緒,在給調試器發送的數據的時候如果該值爲TRUE,函數會直接返回;
  • nt!KdEnteredDebugger, 這個變量沒有什麼實質的作用,但是該變量在進入調試器的時候,會將此變量置爲1,參見函數nt!KdEnterDebugger

通過以上的分析所得,結合70版本的分析再加上以下命令繼續測試:

;可選修改項
;; 修改nt!KdDebuggerNotPresent;名稱來自於ntoskrnl.exe 的導出
;; 有可能修改此名稱會導致虛擬機越來越卡,
eb nt!KdDebuggerNotPresent 1


;; 修改nt!KdEnteredDebugger; 
; nt!KdEnterdDebugger的修改僅僅來源於函數nt!KdEnterDebugger,在KdEnterDebugger調用結束時進行賦值;
; 注意:此變量的修改需要在以上變量修改之後進行修改,否則如果一旦在過程中中斷到了調試器,那麼此值會被重新覆蓋
eb nt!KdEnteredDebugger 0


;; 注意:nt!KdDebuggerEnabled 變量需要最後進行修改,因爲一旦此變量被置爲FALSE,在WIN7以後,本地調試連接也將中斷

通過以上的修改,發現當前虛擬機已經不藍屏也不重啓了,並且已經進入了遊戲。這就說明TesSafe.sys的確是通過判斷以上變量的值來判斷操作系統是否處於調試模式(經測試,準確說是nt!KdEnteredDebugger這個變量來判斷是否處於調試模式的,當處於遊戲中時,我嘗試通過重新賦值該變量爲1,這時候會立即重啓)。

重新建立雙機調試的探索

還原測試第一波

經過以上修改,現在已經可以正常的進入遊戲並且遊戲不會報錯了。那麼接下來需要解決的問題就是,怎樣才能重新建立器調試連接?

根據以上的修改,我們已經明確的知道了遊戲檢測的位置,那麼是否通過重新還原以上的操作,是否就可以重新建立起來雙機調試呢? 那麼這裏做一下測試:

這裏貼出KdEnterDebugger的函數代碼

kd> uf KdEnterDebugger
nt!KdEnterDebugger:
8437016d 8bff            mov     edi,edi
8437016f 55              push    ebp
84370170 8bec            mov     ebp,esp
84370172 51              push    ecx
84370173 53              push    ebx
84370174 8b5d08          mov     ebx,dword ptr [ebp+8]
84370177 56              push    esi
84370178 57              push    edi
84370179 e8bc1eceff      call    nt!VfIsVerifierEnabled (8405203a)
8437017e 85c0            test    eax,eax
84370180 7408            je      nt!KdEnterDebugger+0x1d (8437018a)  Branch

nt!KdEnterDebugger+0x15:
84370182 6a03            push    3
84370184 58              pop     eax
84370185 e801630000      call    nt!VfNotifyVerifierOfEvent (8437648b)

nt!KdEnterDebugger+0x1d:
8437018a 33ff            xor     edi,edi
8437018c 3bdf            cmp     ebx,edi
8437018e 743a            je      nt!KdEnterDebugger+0x5d (843701ca)  Branch

nt!KdEnterDebugger+0x23:
84370190 f7437000020000  test    dword ptr [ebx+70h],200h
84370197 7506            jne     nt!KdEnterDebugger+0x32 (8437019f)  Branch

nt!KdEnterDebugger+0x2c:
84370199 33c0            xor     eax,eax
8437019b 33d2            xor     edx,edx
8437019d eb07            jmp     nt!KdEnterDebugger+0x39 (843701a6)  Branch

nt!KdEnterDebugger+0x32:
8437019f 57              push    edi
843701a0 ff1554610484    call    dword ptr [nt!_imp__KeQueryPerformanceCounter (84046154)]

nt!KdEnterDebugger+0x39:
843701a6 a388363b84      mov     dword ptr [nt!KdTimerStop (843b3688)],eax
843701ab 2b0580363b84    sub     eax,dword ptr [nt!KdTimerStart (843b3680)]
843701b1 89158c363b84    mov     dword ptr [nt!KdTimerStop+0x4 (843b368c)],edx
843701b7 1b1584363b84    sbb     edx,dword ptr [nt!KdTimerStart+0x4 (843b3684)]
843701bd a390363b84      mov     dword ptr [nt!KdTimerDifference (843b3690)],eax
843701c2 891594363b84    mov     dword ptr [nt!KdTimerDifference+0x4 (843b3694)],edx
843701c8 eb0c            jmp     nt!KdEnterDebugger+0x69 (843701d6)  Branch

nt!KdEnterDebugger+0x5d:
843701ca 893d88363b84    mov     dword ptr [nt!KdTimerStop (843b3688)],edi
843701d0 893d8c363b84    mov     dword ptr [nt!KdTimerStop+0x4 (843b368c)],edi

nt!KdEnterDebugger+0x69:
843701d6 648b3520000000  mov     esi,dword ptr fs:[20h]
843701dd ff1568610484    call    dword ptr [nt!_imp__KeGetCurrentIrql (84046168)]
843701e3 ff750c          push    dword ptr [ebp+0Ch]
843701e6 8886c4040000    mov     byte ptr [esi+4C4h],al
843701ec 53              push    ebx
843701ed e872becdff      call    nt!KeFreezeExecution (8404c064)
843701f2 57              push    edi
843701f3 8ad8            mov     bl,al
843701f5 e8d014d1ff      call    nt!KeGetCurrentProcessorNumberEx (840816ca)
843701fa 8b0485608c1a84  mov     eax,dword ptr nt!KdLogBuffer (841a8c60)[eax*4]
84370201 33f6            xor     esi,esi
84370203 46              inc     esi
84370204 3bc7            cmp     eax,edi
84370206 7416            je      nt!KdEnterDebugger+0xb1 (8437021e)  Branch

nt!KdEnterDebugger+0x9b:
84370208 8b08            mov     ecx,dword ptr [eax]
8437020a c1e104          shl     ecx,4
8437020d 8d4c0108        lea     ecx,[ecx+eax+8]
84370211 0f31            rdtsc
84370213 8901            mov     dword ptr [ecx],eax
84370215 895104          mov     dword ptr [ecx+4],edx
84370218 897108          mov     dword ptr [ecx+8],esi
8437021b 89790c          mov     dword ptr [ecx+0Ch],edi

nt!KdEnterDebugger+0xb1:
8437021e b980e41a84      mov     ecx,offset nt!KdpDebuggerLock (841ae480)
84370223 e88cd3d3ff      call    nt!KeTryToAcquireSpinLockAtDpcLevel (840ad5b4)
84370228 57              push    edi
84370229 a2d4363b84      mov     byte ptr [nt!KdpPortLocked (843b36d4)],al
8437022e e8b6c2cdff      call    nt!KdSave (8404c4e9)
84370233 5f              pop     edi
84370234 8935e48c1a84    mov     dword ptr [nt!KdEnteredDebugger (841a8ce4)],esi ; 此處進行狀態賦值
8437023a 5e              pop     esi
8437023b 8ac3            mov     al,bl
8437023d 5b              pop     ebx
8437023e 59              pop     ecx
8437023f 5d              pop     ebp
84370240 c20800          ret     8

在上列代碼0x84370234處針對nt!KdEnteredDebugger進行的修改, mov dword ptr [nt!KdEnteredDebugger (841a8ce4)],esi,那麼既然要重新建立調試,KdEnterDebugger在不重新寫的情況下,就需要對這裏進行處理,

;直接對這句代碼進行NOP操作
; 至於爲什麼要修改,上面已經說明白了,是因爲TesSafe.sys檢測了nt!KdEnteredDebugger變量的值,當它爲1時,系統就會直接重啓,因此在不重寫函數的情況下,對這句代碼進行NOP是最快的測試方法了。
eb 84370234 90 90 90 90 90 90 

; 變量nt!KdDebuggerNotPresent爲0
eb nt!KdDebuggerNotPresent 0

; 讓異常處理流程走內核調試器分發路線
eb nt!KiDebugRoutine nt!KdpTrap

; 讓調試器生效
eb nt!KeDebuggerEnabled 1

測試結果: 通過以上的還原測試之後,雙機調試的Windbg並沒有收到任何的調試信息,按下CTRL+BREAK仍然沒有中斷, 很明顯TesSafe.sys做的事情還更多更深入;

還原測試第二波

既然以上的流程沒用,那麼很顯然TesSafe.sys在處理異常的流程裏面做了更多的事情(爲什麼說是異常處理流程?調試器的中斷等等都是產生異常進入異常處理流程,然後發送異常到調試器)。既然如此,那麼這裏就需要重新再翻一翻有關於異常的產生和處理了。

這裏寫一段會產生異常的代碼,如下:

__try
{
	__asm int 3;
}
__except(1)
{
    //printf("Exception handled");
}

當這段代碼執行的時候是什麼樣的流程呢(流程取自Win7 32 7600)?

  1. 當進入當前函數時,壓入SEH結構體到SEH鏈表中;
  2. 執行int3時,觸發int 3中斷,並進行棧切換;
  3. CPU查表找到KiTrap03函數並執行;
  4. KiTrap03執行Trap03Handler函數,隨後執行CommonExceptionDispatcher, 並隨後執行KiDispatchException
  5. KiDispatchException中執行KiDebugRoutine函數,此時KiDebugRoutine中保存的函數需要爲nt!KdpTrap時纔會進入調試器處理函數
  6. KdpTrap判斷當前的異常類型,因爲我們這裏是觸發的INT3斷點,所以會走KdpReport函數;
  7. KdpReport函數中的流程:
    1. 首先調用nt!KdEnterDebugger凍結其他的CPU的執行,並且申請nt!KdpDebuggerLock(自旋鎖);
    2. 調用KdpReportExceptionStateChange, 然後在此函數中會調用KdpSendWaitContinue函數進行數據發送和命令接受; KdpReportExceptionStateChange中首先檢查了當前的異常是否在斷點表裏面並且斷點是否是無效的,如果是的話,KdpReportExceptionStateChange將會直接返回,但是此處我們的異常(INT3)是主動產生的,沒有在斷點表裏面,所以這裏可以直接略過,所以即便是TesSafe.sys清空該表那麼也不影響該測試
  8. nt!KdpSendWaitContinue首先調用nt!KdSendPacket進行數據的發送, 但nt模塊中的KdSendPacket是讀取IAT表跳轉至kdcom模塊KdSendPacket的跳轉代碼, 此時流程進入kdcom模塊,執行kdcom!KdSendPacket函數進行數據的發送;
  9. kdcom模塊中數據的發送流程:kdcom!KdpSendString->kdcom!KdCompPutByte->kdcom!CpPutByte->kdcom!KdReadUchar->更多的細節;
  10. 更多的流程

以上爲觸發INT3斷點時的發送異常信息的部分流程,更詳細的流程參見《軟件調試》;

在以上的流程中,可以確定的是,無論TP如何修改,那麼肯定會執行到第5步,無論nt!KiDebugRoutine是否nt!KdpTrap, 因此在保證KiDebugRoutine的值爲nt!KdpTrap的情況下,流程必然會經過第6步進入第7步,爲什麼會經過第6步?因爲這一步裏面除了修改代碼亦或者修改上層傳遞的異常信息,否則非常容易被發現。用PCHunter一掃描,什麼都看到了,很容易暴露;

第7步的時候裏面有個變量值得注意一下: nt!KdpDebuggerLock如果這個變量爲1的話,那麼就說明被程序佔用了。而且如果修改這個變量的話,那麼PCHunter之類的工具是掃描不出來的,因爲它屬於.data區段,是一個全局變量。但是這裏測試的結果表明:TesSafe.sys並沒有使用佔用nt!KdpDebuggerLock來達到阻止調試的目的,在調試器沒有中斷的情況下該值並沒有發生變化

接下來觀察第8步的流程,nt!KdpSendWaitContinue函數中一開始就調用了nt!KdSendPacket,此時NT中使用的KdSendPacket是導入kdcom!KdSendPacket,因此此時程序流程轉入kdcom;

kdcom中就是真正的數據發送流程,該函數首先檢查數據的檢驗和,然後進入KdpSendString函數, 該函數中調用KdCompPutByte,然後繼續跟蹤,在KdCompPutByte調用了CpPutByte,此時在調用CpPutByte的時候,他的第一個參數是PortPort是屬於kdcom模塊中的全局變量:

.text:40311F2A 8B FF                   mov     edi, edi
.text:40311F2C 55                      push    ebp
.text:40311F2D 8B EC                   mov     ebp, esp
.text:40311F2F FF 75 08                push    [ebp+arg_0]
.text:40311F32 68 20 30 31 40          push    offset _Port  ; Com端口信息的結構體
.text:40311F37 E8 D6 FA FF FF          call    _CpPutByte@8  ; CpPutByte(x,x)
.text:40311F3C 5D                      pop     ebp
.text:40311F3D C2 04 00                retn    4

如果Port被修改,那麼PCHunter是掃描不到的,那麼現在來看看虛擬機裏面kdcom!Port這些的值是不是有變化:

1: kd> db kdcom!Port
80bc7020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7080  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

此處明顯就不對了。Port的結構體如下:

typedef struct _CPPORT
{
    PUCHAR Address;     // Com端口的基地址
    ULONG  BaudRate;    // 數據發送波特率, 在雙機調試設置的時候該值一般被設置爲115200
    USHORT Flags;       // Com端口標誌位
} CPPORT, *PCPPORT;

在調試已經建立的情況下,_CPPORT的Address字段和BaudRate字段是不可能爲0的,那就只能說明一個問題,這些數據已經被破壞了。重啓虛擬機不開遊戲觀察這個數據:

1: kd> db kdcom!Port
80bc7020  f8 02 00 00 00 c2 01 00-04 00 00 00 00 00 00 00  ................
80bc7030  00 00 00 00 00 00 00 00-00 00 00 00 f8 02 00 00  ................
80bc7040  00 c2 01 00 01 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7050  00 00 00 00 00 00 00 00-01 00 00 00 ae 5b bc 80  .............[..
80bc7060  c0 5b bc 80 26 06 c4 d9-d9 f9 3b 26 00 00 00 00  .[..&.....;&....
80bc7070  02 00 00 00 00 c2 01 00-00 00 80 80 01 00 80 80  ................
80bc7080  00 00 00 00 00 00 00 00-00 00 00 00 b0 fe 00 00  ................
80bc7090  00 00 00 00 00 00 00 00-02 00 00 00 02 00 00 00  ................

此時注意到這個變量的確有值了。而且第二個字段的值爲0x1c200,十進制正是115200,再次啓動遊戲繼續觀察:

1: kd> db kdcom!Port
80bc7020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7030  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7040  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7050  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7070  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7080  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
80bc7090  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

看來的確這裏是存在問題了,那麼現在就測試一下修改這裏到底會不會有什麼反應。

測試流程如下:

  1. 修改nt!KdDebuggerEnabled的值爲1;
  2. 恢復KiDebugRoutine爲nt!KdpTrap,
  3. NOP掉KdEnterDebugger函數中對nt!KdEnteredDebugger的修改代碼
  4. eb 80bc7020 f8 02 00 00 00 c2 01 00 04 00, 恢復結構體Port的內容
  5. 嘗試觸發一個異常;

當第四步執行後,直接就藍屏了。這說明剛剛的修改裏面還存在問題,現在來分析一下藍屏DUMP,看看到底什麼地方出現的藍屏。

3: kd> k
ChildEBP RetAddr  
WARNING: Frame IP not in any known module. Following frames may be wrong.
8fd6cb20 80bbb828 0x0
8fd6cb70 80bbbab1 kdcom!CpReadLsr+0x18
8fd6cb84 80bbbf1e kdcom!CpGetByte+0x2f
8fd6cb98 80bbb317 kdcom!KdCompPollByte+0x14
8fd6cbb8 84088437 kdcom!KdReceivePacket+0x17
8fd6cbe4 84088384 nt!KdPollBreakIn+0x94
8fd6cbe8 84088361 nt!KdCheckForDebugBreak+0x17
8fd6cc18 84430430 nt!KeUpdateRunTime+0x164
8fd6cc18 93d9f5d6 hal!HalpClockInterruptPn+0x158
8fd6cc98 840a2695 intelppm!C1Halt+0x4
8fd6cd20 8408500d nt!PoIdle+0x538
8fd6cd24 00000000 nt!KiIdleLoop+0xd

經過以上的調用棧分析得知,產生藍屏的原因是kdcom!CpReadLsr+0x18訪問不存在的函數地址(0)導致,那麼在IDA裏面看一下這裏的代碼到底是什麼。

.text:40311810
.text:40311810 8B FF                  mov     edi, edi
.text:40311812 55                     push    ebp
.text:40311813 8B EC                  mov     ebp, esp
.text:40311815 83 EC 40               sub     esp, 40h
.text:40311818 56                     push    esi
.text:40311819 8B 75 08               mov     esi, [ebp+arg_0]
.text:4031181C 8B 06                  mov     eax, [esi]
.text:4031181E 83 C0 05               add     eax, 5
.text:40311821 50                     push    eax             ; _DWORD
.text:40311822 FF 15 60 30 31 40      call    _KdReadUchar    ; CpReadPortUchar(x) ;此處爲藍屏產生的地方
.text:40311828 8A C8                  mov     cl, al                                  + 0x18
.text:4031182A B0 FF                  mov     al, 0FFh
.text:4031182C 88 4D 0B               mov     byte ptr [ebp+arg_0+3], cl

查看上面的代碼得知,當然在調用地址40311822的代碼call _KdReadUchar發生了異常,_KdReadUchar的值爲零。雙擊_KdReadUchar它是個啥:

在這裏插入圖片描述

結果這裏發現,KdReadUchar和Port同樣處於kdcom的.data段中(.data段是PE文件中用來存放全局變量的地方), 此時DUMP一下kdcom的模塊內存看看。

在這裏插入圖片描述

那麼這裏就很明瞭了。TesSafe.sys針對kdcom!的.data段做了清零操作,因爲kdcom的代碼比較完善,即便是全部清零了也沒有出現錯誤。那麼此時嘗試在遊戲運行後恢復一下.data段。

注意:裏面的函數地址需要針對當前kdcom的實際函數地址進行設置,否則會出錯

eb 80b9b000  14 00 00 00 14 00 00 00 01 00 00 00 20 50 52 54 20 4F 56 4C 20 46 52 4D 0A 0D 41 54 0A 0D 00 00 F8 02 00 00 00 C2 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F8 02 00 00 00 C2 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 AE 9B B9 80 C0 9B B9 80 86 06 5A 83 79 F9 A5 7C 00 00 00 00 02 00 00 00 00 C2 01 00 00 00 80 80 01 00 80 80 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00

// 恢復kdcom模塊.data段的內容,裏面的內容需要根據實際的環境進行變動,不能直接複製。

那麼此時再用上一步的流程來測試,額外加上恢復kdcom段的內容:

  1. 修改nt!KdDebuggerEnabled的值爲1;
  2. 恢復KiDebugRoutine爲nt!KdpTrap,
  3. NOP掉KdEnterDebugger函數中對nt!KdEnteredDebugger的修改代碼;
  4. 使用上面的命令進行.data修復;
  5. 嘗試觸發一個異常(R3程序或者R0程序都可以);
.reload 
; u KdCompPollByte

; CpWritePortUchar
; CpReadPortUchar

eb 80b99000  14 00 00 00 14 00 00 00 01 00 00 00 20 50 52 54 20 4F 56 4C 20 46 52 4D 0A 0D 41 54 0A 0D 00 00 F8 02 00 00 00 C2 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F8 02 00 00 00 C2 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 AE 7b B9 80 C0 7B B9 80 86 06 5A 83 79 F9 A5 7C 00 00 00 00 02 00 00 00 00 C2 01 00 00 00 80 80 01 00 80 80 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00
eb 84345234 90 90 90 90 90 90
ed nt!KiDebugRoutine nt!KdpTrap
eb nt!KdDebuggerNotPresent 0
eb nt!KdDebuggerEnabled 1

本次測試結果:已經可以正常斷下了。

在這裏插入圖片描述

阻止內核調試器分析結語

總體來說,TesSafe.sys通過兩點來阻止內核調試器:

  1. 監控nt!KdEnteredDebugger的值,該在KdEnterDebugger函數中被修改;
  2. 清零kdcom.dll的.data段來達到kdcom裏面的所有功能失效的目的;

以上的分析僅僅是探明瞭TesSafe.sys的阻止內核調試器的方式,只是這樣大模大樣的去修改肯定會有問題的。TesSafe.sys肯定也有相關的檢測,那麼這些問題後面再繼續進行分析。

不得不說TesSafe的開發者,很細心找到這麼隱祕的方式來阻止內核調試器的加載。不過在我看來,這樣大範圍的去清空一個段的行爲的確是有一些招搖,如果只是修改了其中某一個小地方的值導致了調試流程的中斷,那麼估計要找到這樣的點估計需要花更多的時間更大的經歷才能搞定了。亦或者嘗試去改變和內核調試器通信的核心數據,讓調試器和內核調試引擎產生衝突而出現錯誤。

額外的測試方式:當確定異常的處理流程時,在流程裏一步一步下斷點來確定是否正常執行(雖然下了斷點會藍屏,但是可以用來確定執行流程),可能比較麻煩,但這個理論應該有用。

遊戲運行之後藍屏異常分析

當啓動遊戲之後,如果正處於調試狀態下,無論是否過了內核調試,一段時間之後都會產生藍屏異常,產生該異常時的調用堆棧。

WARNING: Frame IP not in any known module. Following frames may be wrong.
807f3f48 840823b5 86e25588 00000000 00000000 0x8a23554f
807f3fa4 84082218 807d3120 86e61020 00000000 nt!KiExecuteAllDpcs+0xf9
807f3ff4 840819dc 9bb43c44 00000000 00000000 nt!KiRetireDpcList+0xd5
9bb43c60 8442fb29 00000002 00000000 86e20022 nt!KiDispatchInterrupt+0x2c
9bb43c78 8442fba9 807d3120 00000000 9bb43cb8 hal!HalpCheckForSoftwareInterrupt+0x83
9bb43c88 84087ddf 00000004 86e25588 8fdbac14 hal!KfLowerIrql+0x61
9bb43d50 8422866d 000003e8 b7b8efb6 00000000 nt!KeInsertQueueDpc+0x235
9bb43d78 8442fba9 ffffffff 84053c74 a80b8c0e nt!PspSystemThreadStartup+0x9e
9bb43d90 840da0d9 9cac642a 880408c0 00000000 hal!KfLowerIrql+0x61
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x19

// 待續

階段2:內核態阻止用戶態調試器的研究分析

// 待續

階段3:內核態阻止進程讀取遊戲內存

// 待續

階段4:R3阻止代碼斷點

// 待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章