反NP監視原理

NP=nProtect GameGuard(如果你不知道這是什麼,請不要往下看)
*******************************************
*標題:【原創】反NP監視原理                   *
*作者:墮落天才                               *
*日期:2007年1月3號                           *
*版權聲明:請保存文章的完整,轉Q載請註明出處*
*******************************************
一、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下通過
 
發佈了5 篇原創文章 · 獲贊 2 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章