NP=nProtect GameGuard
***********************************************************************************
**标题:逆向NP之注入npggNT.des
**作者:堕落天才
**时间:2007-2-1
************************************************************************************
    
    我们知道NP启动后会向所有进程注入模块npggNT.des,这个过程包括:向目标进程注入执行代码及数据,创建远程线程,远程线程执行,装入

npggNT.des。之后的事我们都知道了:npggNT.des挂钩关键系统函数。在得到NP注入代码前,这个注入过程我想对于象我这样的菜鸟来说是非常

神秘的,于是就产生了种种幻想,装入npggNT.des是不是使用更底层的系统函数?还是NP自己构建的LoadLibrary?又或是使用COPY来的系统函

数代码?但是这两天分析的结果令我大跌眼睛它竟然是使用LoadLibraryA装入npggNT.des的,这怎么可能??但事实上真的是这样,下面我们

来看看代码就知道了。 
   
    1,主要代码(这里是npggNT.des装入的主要执行过程,要看完整的二进制代码请看附件NPRemotThread.asm,编译后反汇编看)

代码:
00C10000    59              pop     ecx                              
00C10001    58              pop     eax                              
00C10002    51              push    ecx                              ;这里是一般函数都会做的寄存器压栈,好像没什么好说
00C10003    53              push    ebx
00C10004    55              push    ebp
00C10005    56              push    esi
00C10006    57              push    edi
00C10007    2BC9            sub     ecx, ecx                         ; ecx清零
00C10009    E8 00000000     call    00C1000E
00C1000E    5B              pop     ebx                              ; ebx=00C100E ->自定位函数入口地址
00C1000F    64:8B51 30      mov     edx, fs:[ecx+30]                 ; [ecx+30]=[30]
00C10013    83C3 F2         add     ebx, -0E                         ; EBX-E=00C10000 ->线程起始地址
00C10016    85C0            test    eax, eax
00C10018    53              push    ebx                              ; ebx指向线程起始地址
00C10019    50              push    eax                              ; 0
00C1001A    E8 23000000     call    00C10042

00C10042    64:FF31         push    dword ptr fs:[ecx]               ; fs:[ecx]=fs:[0]->SEH安装
00C10045    64:8921         mov     fs:[ecx], esp
00C10048    89A3 93000000   mov     [ebx+93], esp                    ; SMC [ebx+93]=[00C10093]->保存SEH返回的ESP
00C1004E    78 2C           js      short 00C1007C
00C10050    394A 0C         cmp     [edx+C], ecx                     ; [edx+c]=[7FFDC00C]=00241EA0
00C10053    75 30           jnz     short 00C10085

00C10085    68 0000807C     push    7C800000                         ; kernel32.dll 的HANDLE ->这里NP硬编码了kernel32.dll的

句柄
00C1008A    53              push    ebx                              ; 线程起始地址
00C1008B    50              push    eax                              ; 0
00C1008C    FC              cld
00C1008D    E8 16010000     call    00C101A8

00C101A8    E8 00000000     call    00C101AD
00C101AD    5F              pop     edi                              ; EDI=00C101AD ->函数入口地址
00C101AE    8B5424 0C       mov     edx, [esp+C]                     ; EDX=kernel32.dll HANDLE
00C101B2    81C7 EF010000   add     edi, 1EF                         ; EDI=00C1039C ->npggNT.des路径
00C101B8    8D77 E4         lea     esi, [edi-1C]                    ; ESI=00C10380
00C101BB    6A 04           push    4
00C101BD    59              pop     ecx                              ; ECX=4
00C101BE    AD              lods    dword ptr [esi]
00C101BF    8B28            mov     ebp, [eax]                       ; 这里非常精彩,后面会详细分析
00C101C1    03EA            add     ebp, edx                         ; 
00C101C3    896E FC         mov     [esi-4], ebp                     ; 
00C101C6  ^ E2 F6           loopd   short 00C101BE
00C101C8    8B5F FE         mov     ebx, [edi-2]                     ; [edi-2]=[00C1039A]=3A440002
00C101CB    0B5F FA         or      ebx, [edi-6]                     ; [edi-6]=[00C10396]=00090100 EBX=3A4D0102
00C101CE    8B47 F4         mov     eax, [edi-C]                     ; [edi-C]=[00C10390]=00000003
00C101D1    66:894F FA      mov     [edi-6], cx                      ; word ptr [edi-6]=[00C10396]=cx=0
00C101D5    F6C7 08         test    bh, 8                            ; bh=01 and 8 =0001b and 1000b =0
00C101D8    0F85 CA000000   jnz     00C102A8
00C101DE    8B07            mov     eax, [edi]                       ; [edi]=[00C1039C]=475C3A44->npggNT.des路径
00C101E0    F6C7 02         test    bh, 2                            ; bh=01 and 2 =0001b and 0010b=0
00C101E3    75 10           jnz     short 00C101F5
00C101E5    8BF0            mov     esi, eax                         ; esi->npggNT.des路径开始4个字节
00C101E7    85C0            test    eax, eax                         ; 判断路径是不是为空
00C101E9    74 02           je      short 00C101ED
00C101EB    8BF7            mov     esi, edi                         ; esi=00C1039C
00C101ED    84DB            test    bl, bl                           ; bl=02
00C101EF    56              push    esi                              ; \
00C101F0    78 3D           js      short 00C1022F                   ;  eax=GetModuleHandleA("D:\\...\\npggNT.des")
00C101F2    FF57 E4         call    [edi-1C]                         ; /
00C101F5    F6C3 02         test    bl, 2                            ; bl=02
00C101F8    75 4B           jnz     short 00C10245                   ; bl 不等于零 跳转实现

00C10245    F6C7 02         test    bh, 2                            ; bh=01
00C10248    75 2F           jnz     short 00C10279                   ; bh=01 and 2=0001b and 0010b=0 跳转未实现
00C1024A    85F6            test    esi, esi                         ; esi=00C1039C->npggNT.des所在路径
00C1024C    74 2B           je      short 00C10279                   ; 不为零
00C1024E    F6C7 01         test    bh, 1                            ; bh=01 and 1=0001b and 0001b=1
00C10251    74 04           je      short 00C10257
00C10253    85C0            test    eax, eax                         ; eax是执行GetModuleHandleA("D:\\...\\npggNT.des")的结果
00C10255    75 22           jnz     short 00C10279                   ; 确定npggNT.des未被加载
00C10257    FF4F F8         dec     dword ptr [edi-8]                ; [00C10394]=00000001-1=0 jmp回来-1=-1
00C1025A    78 1D           js      short 00C10279                   ; 不为负 为负跳
00C1025C    84DB            test    bl, bl                           ; bl=02
00C1025E    56              push    esi                              ; \
00C1025F    78 05           js      short 00C10266                   ;  eax=LoadLibraryA("D:\\...\\npggNT.des") 
00C10261    FF57 E8         call    [edi-18]                         ; /
00C10264  ^ EB F1           jmp     short 00C10257

00C10279    85C0            test    eax, eax                         ; eax为模块npggNT.des的句柄
00C1027B    75 07           jnz     short 00C10284
00C1027D    B8 B6F3C2E1     mov     eax, E1C2F3B6
00C10282    EB 64           jmp     short 00C102E8
00C10284    8B4F F4         mov     ecx, [edi-C]                     ; [edi-C]=[00C10390]=00000003
00C10287    F6C7 04         test    bh, 4                            ; bh=01 and 4=0001b and 0100b=0
00C1028A    74 04           je      short 00C10290
00C1028C    03C1            add     eax, ecx
00C1028E    EB 18           jmp     short 00C102A8
00C10290    85C9            test    ecx, ecx                         ; ecx=3
00C10292    75 0B           jnz     short 00C1029F
00C10294    8D8F 04010000   lea     ecx, [edi+104]
00C1029A    8039 00         cmp     byte ptr [ecx], 0
00C1029D    74 49           je      short 00C102E8
00C1029F    84DB            test    bl, bl                           ; bl=02
00C102A1    51              push    ecx                              ; \
00C102A2    50              push    eax                              ;   eax=GetProcAddress(HandleOfNpggNT.des,03)
00C102A3  ^ 78 CB           js      short 00C10270
00C102A5    FF57 F0         call    [edi-10]                         ; /
00C102A8    85C0            test    eax, eax                         ; eax=458A1830->npggNT.des function #03
00C102AA    75 07           jnz     short 00C102B3
00C102AC    B8 B7F3C2E1     mov     eax, E1C2F3B7
00C102B1    EB 35           jmp     short 00C102E8
00C102B3    8AD7            mov     dl, bh                           ; dl=bh=01
00C102B5    0FB74F FC       movzx   ecx, word ptr [edi-4]            ; ecx=word ptr [edi-4]=[00C10398]=0009=00000009
00C102B9    8BD8            mov     ebx, eax                         ; ebx=eax=npggNT.des.func#03
00C102BB    8BEC            mov     ebp, esp                         ; esp=00E1FF8C
00C102BD    E3 24           jecxz   short 00C102E3                   ; ecx=9
00C102BF    8DBF 0C030000   lea     edi, [edi+30C]                   ; [edi+30C]=[00C1039C+30C]->00C106A8
00C102C5    8D748F FC       lea     esi, [edi+ecx*4-4]               ; [edi+ecx*4-4]=[00C1039C+9*4-4]->00C106C8
00C102C9    FD              std
00C102CA    E8 58000000     call    00C10327                         ; 该函数用来装入某些数据 然后判断
00C102CF    A8 80           test    al, 80                           ; al=01
00C102D1   /75 18           jnz     short 00C102EB
00C102D3   |A8 40           test    al, 40
00C102D5   |75 19           jnz     short 00C102F0
00C102D7   |A8 20           test    al, 20
00C102D9   |75 26           jnz     short 00C10301
00C102DB   |A8 10           test    al, 10
00C102DD   |75 2B           jnz     short 00C1030A
00C102DF   |AD              lods    dword ptr [esi]                  ; [esi]=[00C106C8]=0
00C102E0   |50              push    eax
00C102E1  ^|E2 FC           loopd   short 00C102DF
00C102E3   |FC              cld
00C102E4   |FFD3            call    ebx                              ; ebx=458A1830->npggNT.des.func#03 ->这里npggNT.des就开

始工作了
00C102E6   |8BE5            mov     esp, ebp
00C102E8   |C2 0C00         retn    0C

00C10092    BC 9CFFE100     mov     esp, 0E1FF9C                     ;还记得这里么?忘了请回头看00C10048处代码
00C10097    64:8F05 0000000>pop     dword ptr fs:[0]                 ;SEH异常返回这里
00C1009E    59              pop     ecx
00C1009F    8A4C24 02       mov     cl, [esp+2]
00C100A3    5A              pop     edx
00C100A4    8AF1            mov     dh, cl
00C100A6    59              pop     ecx
00C100A7    5F              pop     edi
00C100A8    5E              pop     esi
00C100A9    5D              pop     ebp
00C100AA    5B              pop     ebx
00C100AB    F6C6 40         test    dh, 40
00C100AE    75 16           jnz     short 00C100C6
00C100B0    F6C6 20         test    dh, 20
00C100B3    74 07           je      short 00C100BC

00C100BC    F6C2 04         test    dl, 4
00C100BF    5A              pop     edx
00C100C0    58              pop     eax
00C100C1    58              pop     eax
00C100C2    74 2E           je      short 00C100F2

00C100F2    6A 00           push    0
00C100F4    8BC4            mov     eax, esp
00C100F6    51              push    ecx
00C100F7    8BCC            mov     ecx, esp
00C100F9    56              push    esi
00C100FA    6A FE           push    -2
00C100FC    52              push    edx
00C100FD    68 00800000     push    8000
00C10102    50              push    eax
00C10103    51              push    ecx
00C10104    6A FF           push    -1
00C10106    52              push    edx
00C10107    E8 AAFFFFFF     call    00C100B6
00C1010C    E8 04000000     call    00C10115
00C10111    03D0            add     edx, eax
00C10113    FFE2            jmp     edx                             ; ntdll.ZwFreeVirtualMemory ->这个函数执行完毕后,这段代

码就消失了
00C10115    8B15 043C927C   mov     edx, [7C923C04]
00C1011B    C3              retn

数据:
00C10374  47 65 74 4D 6F 64 75 6C 65 48 61 6E 28 2C 80 7C  GetModuleHan(,?
00C10384  58 2F 80 7C 14 2A 80 7C B0 2C 80 7C 03 00 00 00  X/?*???...
00C10394  01 00 00 01 09 00 02 00 44 3A 5C 47 61 6D 65 5C  .....D:\Game\
00C103A4  BE AA CC EC B6 AF B5 D8 43 61 62 61 6C 20 4F 6E  惊天动地Cabal On
00C103B4  6C 69 6E 65 5C 47 61 6D 65 47 75 61 72 64 5C 6E  line\GameGuard\n
00C103C4  70 67 67 4E 54 2E 64 65 73                       pggNT.des
 
      **********************************************************************************************************
      2,精彩地方分析

      看完上面代码后,不知你有没有失望的感觉。但我是有的,因为以前一直觉得神秘的东西一下子变得不神秘,而且不是自己想象中那样,

真的有点失望。但是代码里面也有一些精彩的地方值得学习。

      2.1,自定位

      在远程进程里面我们不能象在本地进程那样方便地使用数据,当我们不得已需要用到某数据时,就需要定位数据。数据COPY到远程进程

后,怎么把它找出来呢?上面可以看到npggNT.des路径地址在00C1039C,但是本地线程一般不知道有个00C1039C,我们看看NP怎么把它找出来。
00C10009    E8 00000000     call    00C1000E
00C1000E    5B              pop     ebx                              ; ebx=00C100E ->自定位函数入口地址
00C1000F    64:8B51 30      mov     edx, fs:[ecx+30]                 ; [ecx+30]=[30]
00C10013    83C3 F2         add     ebx, -0E                         ; EBX-E=00C10000 ->线程起始地址
      我们知道call指令执行完毕之后,当前esp保存了函数返回地址-call指令下一条指令执行的地址,看看上面
      call 00C1000E
 00C1000E:
       pop  ebx   ;ebx->call指令下一条指令的地址,那正好就是pop ebx的地址
       哈哈,定位了一条指令的地址后,原理上我们就可以把整一段代码定位了,比如上面通过 add  ebx, -0E 就把ebx指向线程的起始位置

,这样通过ebx相对寻址的话,整段代码的位置都可以确定了。
      上面还有一个自定位的地方
00C101A8    E8 00000000     call    00C101AD
00C101AD    5F              pop     edi          ; EDI=00C101AD ->函数入口地址
       原理就跟上面分析的一样了。
   
      2.2找系统函数

      略一看上面代码,不知道你会不会决定奇怪,GetModuleHandleA,LoadLibraryA,FreeLibrary,GetProcAddress这些kernel32.dll里面的

函数是怎么来的呢?
     为什么
00C101F2    FF57 E4         call    [edi-1C] 
     就是GetModuleHandleA呢?我们先分析一下这段代码:

00C101A8    E8 00000000     call    00C101AD
00C101AD    5F              pop     edi                              ; EDI=00C101AD ->函数入口地址
00C101AE    8B5424 0C       mov     edx, [esp+C]                     ; EDX=kernel32.dll HANDLE
00C101B2    81C7 EF010000   add     edi, 1EF                         ; EDI=00C1039C ->npggNT.des路径
00C101B8    8D77 E4         lea     esi, [edi-1C]                    ; ESI=00C10380
00C101BB    6A 04           push    4
00C101BD    59              pop     ecx                              ; ECX=4
00C101BE    AD              lods    dword ptr [esi]                  ;从00C10380开始依次4个字节地取出某数据,循环四次
00C101BF    8B28            mov     ebp, [eax]                       ;根据数据寻址,将其指向的数据拿出来保存到ebp 
00C101C1    03EA            add     ebp, edx                         ; ebp+=edx,上面可以看到edx=kernel32.dll的句柄也就是装
00C101C3    896E FC         mov     [esi-4], ebp                     ; 载基地址了,基地址+偏移(基地址+RVA),想到了么?  
    好了,我们先看看00C10380跟其后面的数据是什么
    dword ptr [00C10380]=7C802C28 
    dword ptr [00C10384]=7C802F58 
    dword ptr [00C10388]=7C802A14
    dword ptr [00C1038C]=7C802CB0
    然后找到这些数据所指向的地址再看看
    [7C802C28]=B529
    [7C802F58]=1D77
    [7C802A14]=AA66
    [7C802CB0]=AC28
    然后把他们加上kernel32.dll的基地址也就是handle(如果你忘了的话,请看00C10085行)
    7C800000+B529->GetModuleHandleA
    7C800000+1D77->LoadLibraryA
    7C800000+AA66->FreeLibrary
    7C800000+AC28->GetProcAddress
    到这里我们终于可以明白了,原来这些函数是这样来的。然后这些函数地址分别保存到00C10380、00C10384、00C10388及00C1038C中
 
    2.3自修改代码

    不知你有没有留意,上面有一个小小的自修改。
00C10048    89A3 93000000   mov     [ebx+93], esp          ; SMC [ebx+93]=[00C10093]->保存SEH返回的ESP
00C10092    BC 9CFFE100     mov     esp, 0E1FF9C           ;
    在mov [ebx+93], esp 指令执行前00C10092处的代码是mov esp,0 执行后就变成上面这个样子了。这里是把ESP数值直接写入代码里,呵呵

,可以省了一个变量。
    
   ************************************************************************************************************
   3,逆向代码(C)

   根据上面的分析,我们已经可以理解NP装入npggNT.des的主要方法了,下面是代码,忽略了各种判断跳转。而且由于是用C来写,所以这里

没有用到自定位,而是直接把数据的地址当参数传递给线程函数(更详细代码请看附件InjectDll.cpp)。

   //////////////////////////////////声明API////////////////////////////////
typedef HMODULE (WINAPI*GETMODULEHANDLEA)(LPCSTR lpModuleName);             //GetModuleHandleA
typedef HMODULE (WINAPI*LOADLIBRARYA)(LPCSTR lpLibFileName);                //LoadLibraryA
typedef BOOL    (WINAPI*FREELIBRARY)(HMODULE hLibModule);                   //FreeLibrary
typedef FARPROC (WINAPI*GETPROCADDRESS)(HMODULE hModule,LPCSTR lpProcName); //GetProcAddress

//////////////////////////////////定义数据结构//////////////////////////////
typedef struct _INJECTDATA
{
  BYTE                bName[12];                       //12 bytes="GetModuleHan"; 
  GETMODULEHANDLEA   _GetModuleHandleA;                //4  bytes=hKernel32+0x2C28 ->输出函数地址表
  LOADLIBRARYA       _LoadLibraryA;                    //4  bytes=hKernel32+0x2F58
  FREELIBRARY        _FreeLibrary;                     //4  bytes=hKernel32+0x2A14
  GETPROCADDRESS     _GetProcAddress;                  //4  bytes=hKernel32+0x2CB0
  BYTE               someNumber[12];                   //12 bytes  
  TCHAR              szLibraryPath[MAX_PATH];          //MAX_PATH  
}INJECTDATA,*PINJECTDATA;

///////////////////////////////////定义远程线程////////////////////////////////////////////
static VOID WINAPI RemoteThread(LPVOID lpParam)
{  
  PINJECTDATA myData=(PINJECTDATA)lpParam;
  DWORD       dwGetModuleHandleA,
            dwLoadLibraryA,
        dwFreeLibrary,
        dwGetProcAddress,
              hKernel32;

  dwGetModuleHandleA= (DWORD)myData->_GetModuleHandleA;
  dwLoadLibraryA    = (DWORD)myData->_LoadLibraryA;
  dwFreeLibrary     = (DWORD)myData->_FreeLibrary;
  dwGetProcAddress  = (DWORD)myData->_GetProcAddress;
  hKernel32         =        myData->hKernel32;

    ///////////////////////下面的汇编代码是根据输出函数地址表找到相应的函数地址的RVA值////////////
   _asm{    
    mov  ebx,hKernel32            // <--- 这个是kernel32.dll的句柄,NP直接硬编码到这里

    mov  eax,dwGetModuleHandleA
    mov  edx,[eax]                // <--- 根据地址表找出相应函数的RVA值
    add  edx,ebx                  // <--- 函数地址=模块加载基地址(即handle)+相应RVA值
    mov  dwGetModuleHandleA,edx  

    mov  eax,dwLoadLibraryA
    mov  edx,[eax]
    add  edx,ebx
    mov  dwLoadLibraryA,edx

    mov  eax,dwFreeLibrary
    mov  edx,[eax]
    add  edx,ebx
    mov  dwFreeLibrary,edx

    mov  eax,dwGetProcAddress
    mov  edx,[eax]
    add  edx,ebx
    mov  dwGetProcAddress,edx
  }
   ///////////////////////////////////////////////////////////////////

    myData->_GetModuleHandleA= (GETMODULEHANDLEA)dwGetModuleHandleA;
    myData->_LoadLibraryA    = (LOADLIBRARYA)    dwLoadLibraryA;
    myData->_FreeLibrary     = (FREELIBRARY)     dwFreeLibrary;
    myData->_GetProcAddress  = (GETPROCADDRESS)  dwGetProcAddress;

    myData->_LoadLibraryA(myData->szLibraryPath); // 加载DLL
  ////////////////////////////////////////////////////
  //你可以在这里添加其他代码
  //////////////////////////////////////////////////
}
 **********************************************************************************************************
   4,总结
   还有什么想说呢?你是不是想说“我hook了LoadLibraryA后npggNT.des是不是就无法载入了?” ,我想理论上是。上面的代码没有对

LoadLibraryA进行任何的检校,但是我们别忘了还有一个NP主进程GameMon.des,这些事应该是它来干的。从反npggNT.des注入来说,我想这里

并没有比上一篇文章《反NP监视原理》更有价值,只是我们可以学习一点东西,明白一点东西而已。  

   最后是废话,我只是一个小菜鸟,跟我真正交流过的人都会非常认同这个观点,千万别来找我做挂,我只是对技术感兴趣。反NP监视、读写

游戏内存(NP保护下)这两个工具在两个月前就已经发布在我所加入的所有技术Q群了,比两篇文章出现还早很多。我想既然文章都出来了,就

更没必要拿到看雪上面来浪费空间了,看看文章就知道怎么回事。实际上它们不会给你更大的惊喜,用过的朋友都知道。