反NP監視原理(+Bypass NP in ring0)

標 題: 【原創】反NP監視原理(+Bypass NP in ring0)
作 者: 墮落天才
時 間: 2007-01-03,11:58
鏈 接: http://bbs.pediy.com/showthread.php?t=37353

NP=nProtect GameGuard(如果你不知道這是什麼,請不要往下看)
*******************************************
*標題:【原創】反NP監視原理                *
*作者:墮落天才                            *
*日期:2007年1月3號                        *
*版權聲明:請保存文章的完整,轉載請註明出處*
*******************************************
一、NP用戶層監視原理
    NP啓動後通過WriteProcessMemory跟CreateRemoteThread向所有進程注入代碼(除了系統進程smss.exe),代碼通過np自己的LoadLibrary向目標進程加載npggNT.des。npggNT.des一旦加載就馬上開始幹“壞事”,掛鉤(HOOK)系統關鍵函數如OpenProcess,ReadProcessMemory,WriteProcessMemory,PostMessage等等。掛鉤方法是通過改寫系統函數頭,在函數開始JMP到npggNT.des中的替換函數。用戶調用相應的系統函數時,會首先進入到npggNT.des模塊等待NP的檢查,如果發現是想對其保護的遊戲進行不軌操作的話,就進行攔截,否則就調用原來的系統函數,讓用戶繼續。
    下面是NP啓動前user32.dll中的PostMessageA的源代碼(NP版本900,XP sp2)
    8BFF            MOV EDI,EDI
    55              PUSH EBP
    8BEC            MOV EBP,ESP
    56              PUSH ESI
    57              PUSH EDI
    8B7D 0C         MOV EDI,DWORD PTR SS:[EBP+C]
    8BC7            MOV EAX,EDI
    2D 45010000     SUB EAX,145
    74 42           JE SHORT USER32.77D1CBDA
    83E8 48         SUB EAX,48
    74 3D           JE SHORT USER32.77D1CBDA
    2D A6000000     SUB EAX,0A6
    0F84 D4530200   JE USER32.77D41F7C
    8B45 10         MOV EAX,DWORD PTR SS:[EBP+10]
    8B0D 8000D777   MOV ECX,DWORD PTR DS:[77D70080]
    F641 02 04      TEST BYTE PTR DS:[ECX+2],4
    0F85 03540200   JNZ USER32.77D41FBE
    8D45 10         LEA EAX,DWORD PTR SS:[EBP+10]
    50              PUSH EAX
    57              PUSH EDI
    E8 FBFEFFFF     CALL USER32.77D1CAC0
    FF75 14         PUSH DWORD PTR SS:[EBP+14]
    FF75 10         PUSH DWORD PTR SS:[EBP+10]
    57              PUSH EDI
    FF75 08         PUSH DWORD PTR SS:[EBP+8]
    E8 ACBFFFFF     CALL USER32.77D18B80
    5F              POP EDI
    5E              POP ESI
    5D              POP EBP
    C2 1000         RETN 10

    而下面是NP啓動後user32.dll中的PostMessageA的源代碼(NP版本900,XP sp2)
    E9 A69AB8CD     JMP npggNT.458A6630
    56              PUSH ESI
    57              PUSH EDI
    8B7D 0C         MOV EDI,DWORD PTR SS:[EBP+C]
    8BC7            MOV EAX,EDI
    2D 45010000     SUB EAX,145
    74 42           JE SHORT USER32.77D1CBDA
    83E8 48         SUB EAX,48
    74 3D           JE SHORT USER32.77D1CBDA
    2D A6000000     SUB EAX,0A6
    0F84 D4530200   JE USER32.77D41F7C
    8B45 10         MOV EAX,DWORD PTR SS:[EBP+10]
    8B0D 8000D777   MOV ECX,DWORD PTR DS:[77D70080]
    F641 02 04      TEST BYTE PTR DS:[ECX+2],4
    0F85 03540200   JNZ USER32.77D41FBE
    8D45 10         LEA EAX,DWORD PTR SS:[EBP+10]
    50              PUSH EAX
    57              PUSH EDI
    E8 FBFEFFFF     CALL USER32.77D1CAC0
    FF75 14         PUSH DWORD PTR SS:[EBP+14]
    FF75 10         PUSH DWORD PTR SS:[EBP+10]
    57              PUSH EDI
    FF75 08         PUSH DWORD PTR SS:[EBP+8]
    E8 ACBFFFFF     CALL USER32.77D18B80
    5F              POP EDI
    5E              POP ESI
    5D              POP EBP
    C2 1000         RETN 10
   
    通過對比我們可以發現,NP把PostMessageA函數頭原來的8BFF558BEC五個字節改爲了E9A69AB8CD,即將MOV EDI,EDI  PUSH EBP
MOV EBP,ESP 三條指令改爲了JMP npggNT.458A6630。所以用戶一旦調用PostMessageA的話,就會跳轉到npggNT.des中的458A6630中去。
二、用戶層反NP監視方法
    1,把被NP修改了的函數頭改回去
       上面知道NP是通過在關鍵系統函數頭寫了一個JMP來進行掛鉤的,因此,在理論上我們可以通過把函數頭寫回去來進行調用。在實際操作的時候,這種方法並不理想。因爲npggNT.des也掛鉤了把函數頭改寫回去的所有函數,還有它的監視線程也會進行檢校判斷它掛鉤了的函數是不是被修改回去。因此實現起來很困難,隨時都會死程序。
    2,構建自己的系統函數(感謝JTR提供)
       這種方法適用於代碼比較簡單的系統函數。下面我們看看keybd_event的函數源碼
    8BFF            MOV EDI,EDI                              ; USER32.keybd_event
    55              PUSH EBP
    8BEC            MOV EBP,ESP
    83EC 1C         SUB ESP,1C
    8B4D 10         MOV ECX,DWORD PTR SS:[EBP+10]
    8365 F0 00      AND DWORD PTR SS:[EBP-10],0
    894D EC         MOV DWORD PTR SS:[EBP-14],ECX
    66:0FB64D 08    MOVZX CX,BYTE PTR SS:[EBP+8]
    66:894D E8      MOV WORD PTR SS:[EBP-18],CX
    66:0FB64D 0C    MOVZX CX,BYTE PTR SS:[EBP+C]
    66:894D EA      MOV WORD PTR SS:[EBP-16],CX
    8B4D 14         MOV ECX,DWORD PTR SS:[EBP+14]
    894D F4         MOV DWORD PTR SS:[EBP-C],ECX
    6A 1C           PUSH 1C
    33C0            XOR EAX,EAX
    8D4D E4         LEA ECX,DWORD PTR SS:[EBP-1C]
    40              INC EAX
    51              PUSH ECX
    50              PUSH EAX
    8945 E4         MOV DWORD PTR SS:[EBP-1C],EAX
    E8 9B8DFCFF     CALL USER32.SendInput
    C9              LEAVE
    C2 1000         RETN 10

    由上面我們看到keybd_event進行了一些參數的處理最後還是調用了user32.dll中的SendInput函數。而下面是SendInput的源代碼
    B8 F6110000     MOV EAX,11F6
    BA 0003FE7F     MOV EDX,7FFE0300
    FF12            CALL DWORD PTR DS:[EDX]          ; ntdll.KiFastSystemCall
    C2 0C00         RETN 0C

    SendInput代碼比較簡單吧?我們發現SendInput最終是調用了ntdll.dll中的KiFastSystemCall函數,我們再跟下去,KiFastSystemCall就是這個樣子了
    8BD4            MOV EDX,ESP
    0F34            SYSENTER
    最終就是進入了SYSENTER。
    
    通過上面的代碼我們發現一個keybd_event函數構建並不複雜因此我們完全可以把上面的代碼COPY到自己的程序,用來替代原來的keybd_event。NP啓動後依然會攔截原來的那個,但已經沒關係啦,因爲我們不需要用原來那個keybd_event了。
    這種方法適用於源代碼比較簡單的系統函數,複雜的話實現起來就比較麻煩了。我是沒有信心去重新構建一個PostMessageA,因爲其中涉及到N個jmp和Call,看起來頭都大。 還有在VC6裏嵌入彙編經常死VC(這種事太煩人了),我想不會是我用了盜版的原因吧?
  
    3,進入ring0(感謝風景的驅動鼠標鍵盤模擬工具)
     由上面可以看到,NP用戶層的監視不過是修改了一下系統的函數頭,進行掛鉤監視。因此,要反NP用戶層監視的話,進入ring0的話很多問題就可以解決了。比如WinIO在驅動層進行鍵盤模擬,npggNT.des是攔截不到的。但是由於NP用了特徵碼技術,再加上WinIO名氣太大了,所以WinIO在NP版本8××以後都不能用了。但是如果熟悉驅動開發的話,自己寫一個也不是很困難的事。
 
     說了那麼多看起來很“高深”的東西,現在說一些象我這樣的菜鳥都能明白的東西,呵呵,因爲這是菜鳥想出來的菜辦法。
    4,斷線程
       我們知道NP是通過CreateRemoteThread在目標進程創建遠程線程的,還有一點,很重要的一點就是:NP向目標進程調用了CreateRemoteThread後就什麼都不管了,也就是說,憑本事可以對除遊戲外的所有進程npggNT.des模塊進行任何“處置”。這樣我們可以用一個很簡單的方法就是檢查自己的線程,發現多餘的話(沒特別的事情就是NP遠程創建的)就馬上結束了它,這樣NP就無法注入了。但是由於windows系統是多任務系統,而CreateRemoteThread的執行時間又極短,要在這麼短的時間內發現並結束它的話是一件很困難的事。一旦CreateRemoteThread執行完畢而我們的監視線程還沒有起作用的話,後果就慘重了,npggNT.des馬上把程序“搞死”。因爲我們一直試圖關閉它的線程,而npggNT.des又攔截了TerminateThread,所以我們就只能不斷地“重複重複再重複”去試圖關閉npggNT.des的監視線程。如果我們很幸運地在其執行注入代碼時就能斷了它地線程地話,npggNT.des就無法注入了。這種方法在NP早期版本大概有百分之五十的成功率,現在能有百分之一的成功率都不錯了。

     5,斷線程之線程陷阱
      我知道“線程陷阱”這個詞肯定不是我首創,但用“陷阱”這種方法來對付NP之前在網上是找不到的。爲什麼要叫“線程陷阱”?因爲這確確實實是一個陷阱,在npggNT.des肯定要經過的地方設置一個“陷阱”,等它來到之後,掉進去自動就死掉了。而搭建陷阱的方法簡單得令你難以相信。
       上面我們從npggNT.des的監視原理可以看到,npggNT.des要來掛鉤(HOOK)我們的系統函數,這種的方法我們也會,是不是?哪想想,這種掛鉤方法需要用到哪些系統函數呢? 打開進程OpenProcess或GetCurrentProcess(因爲npggNT.des已經進入了目標進程,所以沒有必要再調用OpenProcess,肯定是用後者)、找模塊地址GetModelHandle、找函數地址GetProcAddress、改寫函數頭的內存屬性VirtualQuery&VirtualProtect、寫內存WriteProcessMemory。嘿嘿,在這些地方設置陷阱就八九不離十了,肯定是npggNT.des幹那壞勾當要經過的地方。
      怎麼設陷阱呢?選一個上面說的函數(我沒有一一嘗試),先自己掛鉤(嘿嘿,NP會我們也會)。等到有人調用的時候,先判斷當前的的線程是不是我們程序的,不是的話,那就斷了它吧(一個ExitThread就可以了)。大概就像下面這個樣子
HANDLE WINAPI MyGetCurrentProcess(VOID)//替換掉原來的GetCurrentProcess
{
   DWORD dwThreadId=GetCurrentThreadId();//得到當前線程ID
   if(!IsMyThread(dwThreadId)){//不是我們要保護的線程
      ExitThread(0);//斷了它吧         
   }
   UnhookGetCurrentProcess(); //是我們要保護的線程調用就恢複函數頭
   HANDLE hProcess=GetCurrentProcess();//讓它調用
   RehookGetCurrentProcess();//重新掛鉤
   return hProcess;   //返回調用結果
}
      這種方法去掉npggNT.des的監視是完全能夠實現的,但是這個函數IsMyThread(dwThreadId)非常關鍵,要考慮周全,不然斷錯線程的話,就“自殺”了。

      6,更簡單的陷阱
         原理跟上面一樣,但是我們將替換函數寫成這個樣子
HANDLE WINAPI MyGetCurrentProcess(VOID)//替換掉原來的GetCurrentProcess

   HMODLE hMod=GetModelHandle("npggNT.des");
   if(hMod!=NULL){
      FreeLibrary(hMod);      //直接Free掉它
   }
   UnhookGetCurrentProcess(); //是我們要保護的線程調用就恢複函數頭
   HANDLE hProcess=GetCurrentProcess();//讓它調用
   RehookGetCurrentProcess();//重新掛鉤
   return hProcess;   //返回調用結果
}
     這種方法就萬無一失了,不用擔心會“自殺”。

三、總結
    由上面可以看到在用戶層上反NP監視是不是很簡單的事?最簡單有效的就是第六種方法,短短的幾行代碼就可以搞定了。但是不要指望去掉了npggNT.des就可以爲所欲爲了,還有NP還在驅動層做了很多手腳,比如WriteProcessMemory在用戶層用沒問題,但是過不了NP的驅動檢查,對遊戲完全沒效果。要在NP下讀寫遊戲內存,說起來又另一篇文章了《如何在NP下讀寫遊戲內存》,請繼續關注。

**********************************************************************
Bypass NP in ring0 (2007年3月16日):
1,Add MyService
2,hook sysenter
3,SystemServiceID->MyServiceID
4,MyService JMP ->SystemService Function + N bytes(參考【原創】SSDT Hook的妙用-對抗ring0 inline hook  )

1、2、3 ->繞過NP SSDT檢測
4       ->繞過NP 內核函數頭檢測

NP968下通過
後來補充:
隨着對NP的瞭解深入,發現這篇文章的謬論真不少啊!自己先汗一下!比如NP ring3 HOOK 系統函數根本就沒有用到ReadProcessMemory、WriteProcessMemory、GetCurrentProcess等(不過NP ring3 HOOK的整個遠程代碼中還是少不了系統函數的,GameMon.des也沒有全部做好保護),所以這篇文章所介紹的在GetCurrentProcess設陷阱的方法依然管用(NP版本961)。真是讓高手見笑了,不過我也不打算改,就讓它作爲一個成長的見證吧!現在反npggNT.des注入的方法真是多如牛毛,最主要原因就是GameMon.des向目標進程注入npggNT.des後,不檢測是否已經成功,也不會不斷檢測是否被卸載,所以我們想怎麼搞都行。不過反npggNT.des注入的意義並不是很大(不能模擬鼠標鍵盤,不能直接打開遊戲進程,不能直接讀寫遊戲內存,呵呵,還有什麼可乾的呢?),在ring0反HOOK纔是正道。

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