志福's profile阿福台PhotosBlogLists Tools Help

Blog


    4/18/2008

    [转]突破NP屏蔽,实现按键模拟

    突破NP屏蔽,实现按键模拟!

    nProtect GameGuard、XTRAP是两款比较著名的防作弊软件,在玩家使用外挂(无论任何游戏的外挂,就算不是当前游戏的外挂也不可以)的时候

    会提示"检测到游戏被破解修改"并强行关闭游戏。

    什么是nProtect?

    nProtect是设计用于保护个人电脑终端不被病毒和黑客程序感染的新概念的基于网络的反黑客和反病毒的工具。他帮助确保所有输入个人电脑

    终端的信息在网络上不落入黑客手中。在最终用户在执行电子贸易时,可以通过将nProtect配置在那些提供电子商务、进口贸易,电子贸易的

    金融机构的网站上,来提高安全等级。

    nProtect怎样工作?

    nProtect是一种基于服务器端的解决方案并且当那些需要保护的任何网络应用被运行时而自动启动。nProtect被载入内存,所以最终用户不需

    要安装任何应用程序,只要nProtect启动,就开始拒绝黑客工具和病毒的入侵!

    nProtect如何工作?

    用户登陆时nProtect自动启动。
    浏览器确认和自动安装安全模块到用户的个人电脑。
    扫描黑客工具和病毒
    通知用户目前的安全状态
    如果有黑客工具和病毒尝试删除 
    在被入侵时端驻留内存来锁定黑客工具直到电脑或者nProtect关闭。

    nProtect 的主要功能介绍:

    实时侦测并封锁修改游戏之黑客程序。
    实时侦测并封锁各类型系统病毒。
    实时侦测并封锁加速程序。
    实时侦测并封锁自动鼠标(连点)程序。
    封锁不当外挂程序。
    封锁各种意图远程控制玩家个人计算机的动作。
    限制意图侧录键盘鼠标动作的恶性程序。
    限制可疑间谍程序,加强安全性。

    使用此软件的网络游戏有很多,比如:冒险岛、蒸汽幻想、惊天动地、神话等等。

    这篇文章只讲如何突破NP对按键类的封锁。

    NP对按键的封锁方式主要有3种:

    1、封系统API函数,按键模拟类的API函数有KeyBoard_Event(键盘模拟)、Mouse_Event(鼠标模拟)、SetCursor(模拟鼠标移动)、SendInput()

    、SendMessage(发送消息)PutMessage(发送消息)。这种封API函数的方法在NP早期被应用,主要手段是通过钩子函数修改API的CALL地址。但这

    种手段很快被人破解,破解方法有2种,1种方法是移植这些API函数的代码(具体移植方法请见相关的专业资料)我曾用这方法,把整个

    kernel32.dll系统库文件更名复制,然后调用该库的API,从而绕过NP;第2种方法是使用WINIO.SYS驱动,硬件模拟按键动作(此驱动只支持

    8042端口,即只能模拟PS2键盘鼠标动作)。
    2、鉴于上面原因,NP不再封系统API,而是封按键模拟相关的内核API。移植系统API来模拟按键就不能实现了,因为移植的系统API最终要调用

    内核API。这样留给我们的工作只能是通过驱动硬件来模拟按键动作了。
    3、网上流行的硬件模拟是通过对8042端口驱动来实现的,这种操作端口来实现硬件模拟的方法必须要在系统RING0层才能进行。所以,这类操

    作都必须通过驱动或中断程序来实现。目前网络上流行的端口操作驱动程序有2种:WINIO.SYS及PortTalk.SYS,WINIO.SYS操作端口效率较高。

    它们的主要缺陷是只能模拟PS2的键盘、鼠标,当键盘或鼠标是USB的,或者频繁模拟鼠标时就会出现卡死现象。但这类操作端口的模拟也没长

    久,NP也把这类模拟给封了。封的办法很简单,这2个驱动特征太明显了:WINIO把端口操作从RING0层暴露给RING3层,这样的暴露给系统安全

    带来隐患,所以有的杀毒软件会对WINIO进行查杀!NP封这种具有典型特征驱动程序易如反掌;PortTalk是用户级驱动程序,可以通过调用

    DeviceIoControl函数来操作端口,但正因是用户级驱动,其发出IRP在下传时容易被NP截获屏蔽。这样NP给我们剩下的操作空间就不多了:1、

    改写或移植内核API,这种方法因系统及版本不同实现起来相当困难,极容易造成机器死机。2、虚拟一个设备,通过该设备驱动程序来操作端

    口,最好是将其设备虚拟成键盘和鼠标设备。这种方法可行,因为NP难以实现封设备驱动(封错了机器就死机了)。但这方法也有缺陷,驱动

    程序需要安装,安装弹出微软的认证签名窗口(未经签名存在安全问题),这对许多用户来说是难以接受的。
        以上几种反NP屏蔽按键的方法我在写“按键游侠”(下载地址Http://www.net321.net.cn,论坛地址:Http://www.fyhand.com/bbs)各版

    本时都用过。随着NP的升级,按键游侠反NP的措施也在升级,从最初的API拦截,到现在的虚拟设备驱动、移植内核API。按键游侠始终能突破

    NP屏蔽,模拟按键动作。在对NP的较量中,我从本站中找到许多有关NP的资料,在此一并感谢哪些无私的提供最前端资料的高手。同时也感谢

    哪些提供USB技术朋友,让按键游侠能够硬件模拟USB键盘、鼠标!



    相关资料:按键游侠 WINIO.SYS PortTalk.SYS NP KeyBoard_Event Mouse_Event SetCursor SendInput SendMessage PutMessage USB 驱动签

    名 DeviceIoControl  钩子函数 需要了解这些知识的朋友可以按这些资料在百度湖GOOGLE上查找。

    【转】如何在NP下读写游戏内存及如何进入NP进程

    标 题: 【原创】如何在NP下读写游戏内存及如何进入NP进程
    作 者: 堕落天才
    时 间: 2007-01-04,13:28
    链 接: http://bbs.pediy.com/showthread.php?t=37417

    ******************************************************
    *标题:【原创】如何在NP下读写游戏内存及如何进入NP进程 *
    *作者:堕落天才                                       *
    *日期:2007年1月4号                                   *
    *版权声明:请保持文章的完整,转载请注明出处           *
    ******************************************************

       在上一篇文章《反NP监视原理》中说到要去掉NP的注入是很容易的事,但是去掉npggNT.des并不是说我们想对游戏怎么样都可以了,NP还挂钩了很多内核函数,所以很多关键系

    统函数就算我们在用户层能用也对游戏没有什么效果。
       如果我们想在不破解NP前提下读写游戏内存该怎么办呢,我想办法至少有两个
    一、用驱动
        在驱动下读写游戏内存是没问题,但是由于我不懂驱动,所以也没什么可说。
    二、进入游戏进程
        在用户层,如果我们想在不破解NP的前提下读写游戏内存的话,大概就只能进入游戏进程了。因为很简单,我们的程序无法对游戏使用OpenProcess、ReadProcessMemoery及

    WriteProcessMemory这些函数(就算是去掉了NP监视模块npggNT.des),而NP又不可能限制游戏自身使用这些函数,所以只要我们能够进入游戏进程就能够读写游戏的内存。怎么

    进入游戏呢?下面介绍两种方法:

        1,最简单的办法 ―全局消息钩子(WH_GETMESSAGE)
          看似很复杂的东西原来很简单就可以实现,大道至易啊。使用消息钩子进入游戏进程无疑是最简单的一种方法,具体编程大概象这样:一个消息钩子的DLL,里面包含一个消

    息回调函数(什么都不用做),读写内存过程,跟主程序通讯过程或操作界面过程,当然在DLL_PROCESS_ATTACH要判断当前的进程是不是游戏的,是的话就做相应的处理;一个安

    装全局消息钩子的主程序。大概这样就可以了。使用全局消息钩子的好处是简单易用,但是不足之处是要在游戏完全启动(NP当然也启动啦)后才能进入,如果想在NP启动前做一

    些什么事的话是不可能的。
         另外也简单介绍一下防全局钩子的办法,Windows是通过调用LoadLibraryExW来向目标进程注入钩子DLL的,所以只要我们在钩子安装前挂钩了这个函数,全局钩子就干扰不了

    了。

         2,更麻烦的办法 ― 远程注入
           知道远程注入方法和原理的人可能会说“有没有搞错,OpenProcess、WriteProcessMemory这些必备函数都不能用,怎么注入?”,当然啦,NP启动后是不能干这些事情,所

    以我们要在NP启动前完成。这样一来,时机就很重要了。
           游戏启动的流程大概是这样:游戏Main->GameGuard.des->GameMon.des(NP进程)。这里的做法是这样:游戏Main->GameGuard.des(暂停)->注入DLL->GameGuard.des(继

    续)->GameMon.des。关键点就是让GameGuard.des暂停,有什么办法?我想到一个是全局消息钩子(还是少不了它啊)。要实现大概需要做下面的工作:一个全局消息钩子DLL,里面只

    要一个消息回调函数(什么都不用做),DLL_PROCESS_ATTACH下进行当前进程判断找GameGuard.des,找到的话就向主程序SendMessage;主程序,负责安装钩子,接收钩子DLL发来的

    消息,接收到消息就开始查找游戏进程,向游戏进程注入内存操作DLL,返回给SendMessage让GameGuard.des继续,卸载钩子(免得它继续钩来钩去);内存操作DLL,负责对游戏

    内存进行操作。
            具体编写如下(有省略):
    ////////////////////////////////////////////////GameHook.cpp//////////////////////////////////////////////////////////////////
    BOOL IsGameGuard();
    //////////////////////////////////
    LRESULT CALLBACK GetMsgProc(int nCode,WPARAM wParam,LPARAM lParam)
    {
      return (CallNextHookEx(m_hHook,nCode,wParam,lParam));//什么都不需要做
    }
    ///////////////////////////////////////
    BOOL WINAPI DllMain(HINSTANCE hInst,DWORD dwReason,LPVOID lp)
    {
      switch(dwReason){
      case DLL_PROCESS_ATTACH:    
        if(IsGameGuard())//判断当前进程是不是GameGuard.des
           SendMessage(m_hwndRecv,WM_HOOK_IN_GAMEGUARD,NULL,NULL);//向主窗体发送消息,SendMessage是等待接受窗体处理完毕才返回的,
        break;                               //所以进程就暂停在这里,我们有足够的时间去做事情
      case DLL_PROCESS_DETACH:
        break;
      }
      return TRUE;
    }
    ///////////////////////////////////
    GAMEHOOKAPI BOOL SetGameHook(BOOL fInstall,HWND hwnd)
    {
      ...
    }
    ////////////////////////////////////////
    BOOL IsGameGuard()
    {
           TCHAR  szFileName[256];
           GetModuleFileName(NULL,szFileName,256);
           if(strstr(szFileName,"GameGuard.des")!=NULL){//这样的判断严格来说是有问题的,但实际操作也够用了。当然也可以进行更严格的判断,不过麻烦点
              return TRUE;
           }
      return FALSE;
    }
    //////////////////////////////////////////////////////Main////////////////////////////////////////////////////////////////////////
    void OnGameGuard(WPARAM wParam,LPARAM lParam)//处理消息钩子DLL发来的消息就是上面SendMessage的那个
    {  
      DWORD dwProcessId=FindGameProcess(m_strGameName);//开始查找游戏进程
      if(dwProcessId==0){
        MessageBox(m_hWnd,"没有找到游戏进程","查找游戏进程",MB_OK);
        return;
      }  

        if(!InjectDll(dwProcessId)){//查找到就开始注入
        MessageBox(m_hWnd,"向游戏进程注入失败",注入",MB_OK);
        return;
         }
    }
    /////////////////////////////////////////////////
    DWORD FindGameProcess(LPCSTR szGameName)//负责查找游戏进程
    {
      HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
      if(hSnapshot==INVALID_HANDLE_VALUE)
        return 0;
      PROCESSENTRY32 pe={sizeof(pe)};
      DWORD dwProcessID=0;
      for(BOOL fOK=Process32First(hSnapshot,&pe);fOK;fOK=Process32Next(hSnapshot,&pe)){
        if(lstrcmpi(szGameName,pe.szExeFile)==0){
          dwProcessID=pe.th32ProcessID;
          break;
        }
      }
      CloseHandle(hSnapshot);
      return dwProcessID;
    }
    /////////////////////////////////////////////////
    BOOL InjectDll(DWORD dwProcessId)//负责注入,参考自Jeffrey Richter《windows核心编程》
    {
      CString strText;
      char* szLibFileRemote=NULL;

      HANDLE hProcess=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,FALSE,dwProcessId);
      if(hProcess==NULL){
      //  SetRecord("Open game process failed!");               
        return FALSE;
      }
      int cch=lstrlen(szDll)+1;
      int cb=cch*sizeof(char);
      szLibFileRemote=(char*)VirtualAllocEx(hProcess,NULL,cb,MEM_COMMIT,PAGE_READWRITE);
      if(szLibFileRemote==NULL){
      //  SetRecord("Alloc memory to game process failed!");
        CloseHandle(hProcess);
        return FALSE;
      }

      if(!WriteProcessMemory(hProcess,(LPVOID)szLibFileRemote,(LPVOID)szDll,cb,NULL)){
      //  SetRecord("Write game process memory failed!");
        CloseHandle(hProcess);
        return FALSE;
      }

      PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)
         GetProcAddress(GetModuleHandle(TEXT("kernel32")),"LoadLibraryA");
      if(pfnThreadRtn==NULL){
      //  SetRecord("Alloc memory to game process failed!");
        CloseHandle(hProcess);
        return FALSE;
      }

      HANDLE hThread=CreateRemoteThread(hProcess,NULL,0,pfnThreadRtn, szLibFileRemote,0,NULL);
       if(!hThread)
       {
        //    SetRecord("Create remote thread failed!");
        CloseHandle(hProcess);
       return FALSE;
       }      
       if(hThread!=NULL)
         CloseHandle(hThread);  
       CloseHandle(hProcess);
        return TRUE;

    }     
    ///////////////////////////操作游戏内存的DLL就不贴了,大家根据不同的需要各显神通吧///////////////////////////////////////////////////      
          
           这种方法比一个全局消息钩子麻烦一点,但是优点是显然易见的:可以在NP启动前做事情,比如HOOK游戏函数或做游戏内存补丁。下面进入NP进程还要用到这种方法。

    三、进入NP进程
        如果我们对NP有足够的了解,想对它内存补丁一下,来做一些事情,哪又怎样才可以进入NP的进程呢?嗯,我们知道游戏启动流程是这样的游戏Main->GameGuard.des-

    >GameMon.des(NP进程),其中GameGuard.des跟GameMon.des进程是游戏Main通过调用函数CreateProcessA来创建的,上面我们说到有办法在NP进程(GameMon.des)启动前将我们的

    DLL注入到游戏进程里,因此我们可以在GameMon.des启动前挂钩(HOOK)CreateProcessA,游戏创建NP进程时让NP暂停,但是游戏本来创建NP进程时就是让它先暂停的,这步我们

    可以省了。下面是游戏启动NP(版本900)时传递的参数

          ApplicationName:C:\惊天动地Cabal Online\GameGuard\GameMon.des
          CommandLine:\x01\x58\x6d\xae\x99\x55\x57\x5d\x49\xbe\xe4\xe1\x9b\x14\xe6\x88\x57\x68\x6d\x11\xb9\x36\x73\x38\x71\x1e\x88\x46\xa9\x97\xd4\x3a\x20\x90

    \x62\xae\x15\xcd\x4b\xcd\x72\x82\xbd\x75\x0a\x54\xf0\xcc\x01\xad
          CreationFlags:4
          Directory:
          其中的CommandLine好长啊,它要传递的参数是:一个被保护进程的pid,两个Event的Handle,以及当前timeGetTime的毫秒数 (感谢JTR分享)。
          CreationFlags:4 查查winbase.h头文件,发现#define CREATE_SUSPENDED  0x00000004,所以NP进程创建时就是暂停的
        
          在我们替换的CreateProcessA中,先让游戏创建NP进程(由于游戏创建时NP进程本来就是暂停的,所以不用担心NP的问题),让游戏进程暂停(SendMessage就可以了),然后再

    向NP进程注入DLL,最后让游戏进程继续。这样我们的DLL就进入NP进程了。实现起来大概是这样子
    BOOL
    WINAPI
    MyCreateProcessA(//替换原来的CreateProcessA
        LPCSTR lpApplicationName,
        LPSTR lpCommandLine,
        LPSECURITY_ATTRIBUTES lpProcessAttributes,
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        BOOL bInheritHandles,
        DWORD dwCreationFlags,
        LPVOID lpEnvironment,
        LPCSTR lpCurrentDirectory,
        LPSTARTUPINFOA lpStartupInfo,
        LPPROCESS_INFORMATION lpProcessInformation
        )
    {
      UnhookCreateProcessA();
      BOOL fRet=CreateProcessA(lpApplicationName,lpCommandLine,lpProcessAttributes,lpThreadAttributes,bInheritHandles,dwCreationFlags,
         lpEnvironment,lpCurrentDirectory,lpStartupInfo,lpProcessInformation);  
      RehookCreateProcessA();
            SendMessage(hwndRecv,//负责注入的窗体句柄
                       WM_HOOK_NP_CREATE,//自定义消息
                       (WPARAM)lpProcessInformation->dwProcessId,//把NP进程ID传给负责注入的主窗体
                       NULL);
      return fRet;
    }

    四、注意问题
        由于我们是在不破解NP的前提下对游戏内存进行操作,所以一不小心的话,很容易就死游戏。NP保护了游戏进程的代码段,所以在NP启动后就不要再对其代码段进行修改,要

    补丁或HOOK系统函数这些都要在NP启动前完成。当然读写游戏的数据段是没问题的,因为游戏本身也不断进行这样的操作。

    【转】驱动中实现模拟键盘按键

    标 题: 【原创】驱动中实现模拟键盘按键
    作 者: luocong
    时 间: 2007-04-19,11:35
    链 接: http://bbs.pediy.com/showthread.php?t=42980

    在ring3中实现模拟键盘按键有N^N种方式,比如SendInput()、keybd_event()……但在驱动中要怎么模拟呢?

    1、写端口大法
    #define defI8042_DATA_PORT ((PUCHAR)0x60)
    #define defI8042_CTRL_PORT ((PUCHAR)0x64)
    #define defOBUFFER_FULL 0x01

    BOOLEAN
    WaitForKeyboardWrite(VOID)
    {
        INT i;
        UCHAR c;

        for (i = 0; i < 1000; ++i)
        {
            KeStallExecutionProcessor(50);
            c = READ_PORT_UCHAR(defI8042_CTRL_PORT);
            if ((c & defOBUFFER_FULL) == defOBUFFER_FULL)
                break;
        }

        return i ? TRUE : FALSE;
    }

    VOID PressKeyByScanCode(
        IN CONST BYTE ScanCode
    )
    {
        WRITE_PORT_UCHAR(defI8042_CTRL_PORT, 0xd2);

        WaitForKeyboardWrite();

        WRITE_PORT_UCHAR(defI8042_DATA_PORT, ScanCode);
    }

    搞定,简单易行!缺点是只对PS/2键盘有效,USB的就一边凉快去吧。

    2、构造IRP大法
    这个比较麻烦,而且由于某些原因,略……

    3、调用KeyboardClassServiceCallback()大法
    kbdclass驱动是在I8042prt和kbdhid这两个驱动之上的,所以直接调用它的KeyboardClassServiceCallback()是能对PS/2和USB同时生效的,由于某些原因偶也不想给出具体的代码来。大致指点一下方向:

    KeyboaredClassServiceCallback函数的原型如下:

    typedef VOID (* PFN_KeyboardClassServiceCallback)(
        IN PDEVICE_OBJECT DeviceObject,
        IN PKEYBOARD_INPUT_DATA InputDataStart,
        IN PKEYBOARD_INPUT_DATA InputDataEnd,
        IN OUT PULONG InputDataConsumed
        );

    第一个参数DeviceObject是kbdclass的DeviceObject,怎么得到它呢?思路:先找到kbdclass的DriverObject,然后从DriverObject得到DeviceObject。
    第二和第三个参数是KEYBOARD_INPUT_DATA,自己填充,想模拟什么按键就写什么MakeCode和Flags吧。注意第三个参数应该为第二个参数的指针地址值+1。
    第四个参数,随便弄个ULONG型的变量给它就行了,反正是可以丢弃的。(I8042prt里面用到了它,但我们自己的模拟按键不用也行)

    缺点:每个系统的kbdclass.sys中的KeyboardClassServiceCallback的RVA都不相同,要自己hardcode一下,不过写驱动本来就是那么麻烦,习惯了就好。

    网友:cngage评论
    关于写端口部分,不知道楼住从哪里找的代码,写错了吧。
    BOOLEAN
    WaitForKeyboardWrite(VOID)
    {
        INT i;
        UCHAR c;

        for (i = 0; i < 1000; ++i)
        {
            KeStallExecutionProcessor(50);
            c = READ_PORT_UCHAR(defI8042_CTRL_PORT);
            if ((c & defOBUFFER_FULL) == defOBUFFER_FULL)
                break;
        }

        return i ? TRUE : FALSE;
    }

    判断状态寄存器是否为空,判断条件应该是
    if ((c & 0x02)==0)
    下面这句判断出的结果是输出buffer为满的情况
    if ((c & defOBUFFER_FULL) == defOBUFFER_FULL)

    4/17/2008

    【转载】反NP监视原理(+Bypass NP in ring0)

    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下通过
    4/16/2008

    用VC制作修改器教程[转]

    如何使用 Visual C++ 6.0 产生自己的 trainer 程式

    (以修改 StarCraft v1.02 单机版地图作弊为范例 )

    1.在 VC++ 5.0 的主选单中选择 File/New

    2.选择开启新的 MFC AppWizard(exe) 专案

    3.在 Project name: 栏位中输入专案名称 (在此我用 xxx 当范例)

    4.接下来会问你应用程式的型态,选择 " Dialog based " !!

    5.接下来会问你应用程式是否要连结其他的元件,选择内定值即可

    6.接下来会问你是否要在原始程式码中加上注解,选择内定值即可

    7.最後选择完成即可产生 "xxx" 这个应用程式专案

    8.开启 xxx.cpp 这个原始档案,并找到下列程式段落...

    > m_pMainWnd = &dlg;
    > int nResponse = dlg.DoModal();
    > if (nResponse == IDOK)
    > {
    >
    > // TODO: Place code here to handle when the dialog is
    >

    9.将以下程式码加在 " // TODO: " 这行後面...( "//" 之後为程式注解)

    // ------- 程式由此开始 ------------------------------

    HWND hWnd; // hWnd : 该视窗的 handle
    DWORD dwx; // dwx : 为存放内 ProcessID 的变数位址
    HANDLE hProc; // hProc : 为该程式的行程代码

    hWnd = ::FindWindow(NULL,"Starcraft");

    // 呼叫 FindWindow 这个 Win32 API 来取得 Starcraft 的 hWnd

    ::GetWindowThreadProcessId(hWnd,&dwx);

    // 呼叫 GetWindowThreadProcessID 这个 Win32 API 来取得 ProcessID
    // 正常执行无误後,会将 Process ID 存入 dwx 这个位址中

    hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwx);

    // 呼叫 OpenProcess 这个 Win32 API 来取得 Process handle
    // 正常执行无误後,会将 Process handle 存入 hProc
    // PS:
    // 接下来呼叫 WriteProcessMemory 来写入记忆体时需要必须要
    // 有该 process 的 hProc 才能写入...
    //

    char buf; // buf: 存放要写入资料的暂存区(1 Byte)

    #define CodeAddress1 0x00463dab // 要修改的第一个记忆体位址
    #define CodeAddress2 0x00463dac // 要修改的第二个记忆体位址
    #define CodeAddress3 0x00463dad // 要修改的第三个记忆体位址

    #define NewCode1 (char)0x66 // 第一个位址所要填入的机械码
    #define NewCode2 (char)0x33 // 第二个位址所要填入的机械码
    #define NewCode3 (char)0xd2 // 第三个位址所要填入的机械码

    buf = NewCode1; // 将要写入记忆体的程式码放入暂存区

    WriteProcessMemory(hProc, (void *)CodeAddress1, (void *)&buf, 1, NULL);

    // 呼叫 WriteProcessMemory 这个 win32 API 来作写入记忆体的动作
    // hProc 为 StarCraft 的 Process handle
    // CodeAddress1 为要写入的记忆体位址
    // buf 为存放要写入机械码的存区
    // "1" 代表写入一个 byte

    buf = NewCode2;
    WriteProcessMemory(hProc, (void *)CodeAddress2, (void *)&buf, 1, NULL);
    // 重覆执行上一个写入记忆体的程序(这次是写第二个位址)


    buf = NewCode3;
    WriteProcessMemory(hProc, (void *)CodeAddress3, (void *)&buf, 1, NULL);
    // 重覆执行上一个写入记忆体的程序(这次是写第三个位址)

    return TRUE;
    // 写完这三个 Bytes 後,离开程式...

    // ------- 程式到此停止 ------------------------------

    10.储存档案,并选择选单上的 Build/Rebuild All 来编译程式
    若没出现错误讯息的话,会在 xxx 专案目录下产生 xxx.exe

    注: 由於本范例为教学用,所以程式内容则尽可能省略;重点放在
    修改记忆体的 Win32 API 上面,希望能让有兴趣撰写修改程
    式而却不得其门而入的使用者能省去不少时间...

    用VB制作修改器教程[转]

    对不起,我想这是不可能的,因为VB是一个如此简单的编程语音。"如果有人这么告诉你,别去理他。我可以肯定告诉你,对于制作修改器这种简单的程序,VB完全可以胜任。
    然而,有个问题必须首先考虑:使用VB编写的修改器需要VB的运行库才能运行。如果考虑到有些使用者(实际上可能是大部分使用者)没有运行库,那么在最后制作的ZIP压缩文件中就必须包含这些庞大的文件。在下面的教程里我将制作一个修改器,如果为它再制作一个安装程序,那么整个修改器的体积将超过1MB。其中包括一个很好的安装和反安装程序,但大部分还是VB40032.DLL这个文件。
    除了以上这点,使用VB制作修改器是非常简单的。一旦制作了多次后,你会发现能很快地制作出一个修改器。而且使用VB制作的修改器能够毫无困难地解决游戏运行时的动态内存分配问题,因此即使是最新的游戏,也可以使用VB制作修改器。在本教程中将不涉及动态内存分配,因为虽然简单,但仍然属于一个高级的选项。
    一些背景知识
    不象C语音,VB不会自动包括普通的API函数的声明,因此我们必须把他们加入我们的项目文件。在几乎所有的修改器中会使用到6个主要的函数,讨论如下:
    1. FindWindow(ClassName, WindowTitle) - FindWindow 返回符合指定的类名( ClassName )和窗口名( WindowTitle )的窗口句柄。对我们来说,可以让 ClassName 为空( Null ),只给出游戏的 WindowTitle。函数应该这样声明: Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    2. GetWindowThreadProcessId(WindowHandle, ProcessId) - 在这里我们把 FindWindow 函数中得到的句柄作为参数,来获得进程标识符(ProcessId )。声明如下: Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
    3. OpenProcess(DesiredAccess, Inherit, ProcessId) - 这个函数将返回一个我们目标进程的句柄,可以用来对目标进行读写操作。 DesiredAccess 参数的值决定了句柄对进程的存取权利,对我们来说,要使用 PROCESS_ALL_ACCESS (完全存取权限)。Inherit 应该总是 False。 ProcessId 是从 GetWindowThreadProcessId 函数中取得的。 Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
    4. CloseHandle(ProcessHandle) - 每一个打开的句柄必须呼叫这个函数来关闭。 Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    5. WriteProcessMemory(ProcessHandle, Address, Value, SizeofValue, BytesWritten) - 把指定的值 Value 写入由 Address 指定的目标地址。 Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
    6. ReadProcessMemory(ProcessHandle, Address, Value, SizeofValue, BytesWritten) - 把 Address 指定的目标地址的值存入 Value 位置的变量中。 Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
    这些函数一环扣一环,缺一不可。更详细的内容可以参考VB的帮助文件。

    一个简单的修改器范例
    如何使上面介绍的这些函数一起工作,制作出我们需要的修改器呢?下面是一个为Windows的计算器程序制作修改器的例子。这个修改器将读出计算器窗口中显示的数值,并在点击一个按钮后在计算器窗口中显示我们的名字。

    首先我们需要找到计算器显示窗口中显示值的地址。本教程不是关于如何进行内存搜索,因而我将只作简单的说明:
    · 在计算器窗口中输入123456
    · 使用你喜欢的任何一种内存地址搜索程序寻找字串123456
    · 使用另一个值重复上面的过程直到只返回1个地址
    那是制作我们的修改器需要的唯一一个地址。在我的计算器程序里这个地址是40B181 hex, 4239745 dec。用你找到的地址替代在下面的代码里使用的这个地址。

    现在让我们开始设计修改器的界面:
    · 在VB中新建一个项目,加入一个文本框( Textbox )、一个按钮和一个计时器( timer )。文本框用来显示从计算器窗口取得的字串,按钮用来把我们的名字传到计算器窗口
    · 把表单( form )的标题( Caption )属性设为 Calculator Trainer
    · 把文本框改名为 txtDisplay 并清除 Text 属性
    · 把计时器改名为 ReadTimer 并把间隔( interval )设为500
    · 把按钮的标题改为 Display Name,按钮的名字改为 btnPasteName

    在这个修改器中我们将使用所有6个函数,ReadProcessMemory、WriteProcessMemory、OpenProcess、GetWindowThreadProcessId、FindWindow 和 CloseHandle。在项目中插入一个新的模块,增加下列代码。(下面的一些行自动换行了,在你的模块中每一句必须在一行里,或使用延长符_)
    Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
    Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
    Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
    Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
    Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

    下面我们要开始写在计时器窗口中显示我们名字的代码了。首先我们使用 FindWindow 函数取得目标窗口的句柄。把这个返回值保存在一个变量中,并检查它的值是否出错来确保计时器程序正在运行。(FindWindow函数出错时返回0)
    Dim hwnd As Long
    hwnd = FindWindow(vbNullString, "Calculator")
    If (hwnd = 0) Then
    MsgBox "Window not found!"
    Exit Sub
    End If
    注意在这里我们传递了一个 Null 值给 FindWindow 函数,而不是 ClassName。因此任何名为 Calculator 的窗口都符合条件。如果知道计算器程序窗口的 ClassName,你可以传给它,但这不是必须的。

    现在使用得到的窗口句柄来取得进程标识符( ProcessId )。注意 pid 是作为参数传递给函数的,而不是被赋以函数返回值。
    Dim pid As Long
    GetWindowThreadProcessId hwnd, pid

    再利用变量pid得到计算器程序的进程句柄。再次检查函数的返回值,如果是非法数据则退出程序。
    Dim pHandle As Long
    pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
    If (pHandle = 0) Then
    MsgBox "Couldn't get a process handle!"
    Exit Sub
    End If

    在我们的修改器中 WriteProcessMemory 函数是最重要的部分,而且非常容易出错。不妨让我们再仔细讨论一下它的参数。
    WriteProcessMemory (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As)
    hProcess 是目标进程的句柄,从上面的 OpenProcess 函数中取得的。
    lpBaseAddress 是在计算器程序的虚拟内存中将要被修改的地址,也就是使用内存搜索程序找到的那个地址。(在我的程序里是&H40B181)
    lpBuffer 是将要写如上述地址的数据,可以是一个数值、数组、字符串或其他任何数据类型。
    nSize 是希望写入 lpBaseAddress 的字节数。这个位置应该与你的数据类型相符。如果写入的是一个长整数( long ),这里应该是4。如果写入的是一个字符串,那么这里应该是字符串的长度。
    lpNumberOfBytesWritten 是函数执行返回后,写入目标地址的实际字节数。它能被用来确认函数实际的执行情况。

    把我们的数据放到函数中,得到 WriteProcessMemory pHandle, &H40B181, "Beans", 5, 0&。我把0传递到 lpNumberOfBytesWritten 位置是因为不需要检查两次实际写入的字节数。

    最后通过传递进程句柄给 CloseHandle() 函数来关闭由 OpenProcess 打开的句柄。
    CloseHandle hProcess

    现在将所有的代码输入我们的编辑器中。双击按钮,显示它的代码编辑窗口。代码应该加到名为 btnPasteName 的 Click 事件中。(不必输入注释)
    Private Sub btnPasteName_Click()
    ' 声明一些需要的变量
    Dim hwnd As Long ' 储存 FindWindow 函数返回的句柄
    Dim pid As Long ' 储存进程标识符( Process Id )
    Dim pHandle As Long ' 储存进程句柄

    ' 首先取得目标窗口的句柄
    hwnd = FindWindow(vbNullString, "Calculator")
    If (hwnd = 0) Then
    MsgBox "Window not found!"
    Exit Sub
    End If

    ' 取得进程标识符
    GetWindowThreadProcessId hwnd, pid

    ' 使用进程标识符取得进程句柄
    pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
    If (pHandle = 0) Then
    MsgBox "Couldn't get a process handle!"
    Exit Sub
    End If

    ' 在内存地址中写入名字
    WriteProcessMemory pHandle, &H40B181, "Beans", 5, 0&

    ' 关闭进程句柄
    CloseHandle hProcess
    End Sub

    完毕。现在单击按钮将使计算器窗口文本变为我们键如的名字。(可能需要最小化计算器程序,再还原,以便程序更新显示)

    下面将给我们的修改器增加一个新功能。我们将检测计算器程序的窗口显示数据,并在修改器中显示。双击计时器,显示它的代码编辑窗口,然后输入以下代码:
    Private Sub ReadTimer_Timer()
    ' 声明变量
    Dim hwnd As Long ' 储存 FindWindow 函数返回的句柄
    Dim pid As Long ' 储存进程标识符
    Dim pHandle As Long ' 储存进程句柄
    Dim str As String * 20 ' 存储显示文本

    ' 取得目标窗口的句柄
    hwnd = FindWindow(vbNullString, "Calculator")
    If (hwnd = 0) Then Exit Sub

    ' 取得进程标识符
    GetWindowThreadProcessId hwnd, pid

    ' 取得进程句柄
    pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
    If (pHandle = 0) Then Exit Sub

    ' 读取内存数据
    ReadProcessMemory pHandle, &H40B181, str, 20, 0&

    ' 在文本框显示
    txtDisplay = str

    ' 关闭进程句柄
    CloseHandle hProcess
    End Sub

    在这里出现的新东西是 ReadProcessMemory 函数。从 &H40B181 地址中读出的数据被存入变量 str 中,然后显示在名为 txtDisplay 的文本框中。

    本教程中所讲的是非常简单的东西,主要是想起抛砖引玉的目的。最重要的是不断学习,不断实践,了解其他的API并在修改器中使用。练习越多,就会觉得越容易。

    利用HOOK拦截封包原理[转]

    截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。 
    首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下: 
    HHOOK SetWindowsHookEx( 
    int idHook, // hook type 
    HOOKPROC lpfn, // hook procedure 
    HINSTANCE hMod, // handle to application instance 
    DWORD dwThreadId // thread identifier 
    ); 
    具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。 
    这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。 

    之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。 

    DWORD dwCurrentPID = 0; 
    HHOOK hOldHook = NULL; 
    DWORD pSend = 0; 
    DWORD pRecv = 0; 
    GETMESSAGE pGetMessage = NULL; 

    BYTE btNewBytes[8] = { 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 }; 
    DWORD dwOldBytes[3][2]; 

    HANDLE hDebug = INVALID_HANDLE_value; 

    LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam ) 

    DWORD dwSize; 
    DWORD dwPIDWatched; 
    HMODULE hLib; 

    if( dwCurrentPID == 0 ) 

    dwCurrentPID = GetCurrentProcessId(); 
    HWND hwndMainHook; 
    hwndMainHook = ::FindWindow( 0, 'MainHook' ); 
    dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 ); 
    hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 ); 

    if( dwCurrentPID == dwPIDWatched ) 

    hLib = LoadLibrary( 'ws2_32.dll' ); 
    pSend = (DWORD)GetProcAddress( hLib, 'send' ); 
    pRecv = (DWORD)GetProcAddress( hLib, 'recv' ); 

    ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); 

    ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); 

    hLib = LoadLibrary( 'user32.dll' ); 
    pGetMessage = (GETMESSAGE)GetProcAddress( hLib, 'GetMessageA' ); 
    ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); 

    hDebug = ::CreateFile( 'C:\\Trace.log', GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); 



    if( hOldHook != NULL ) 

    return CallNextHookEx( hOldHook, nCode, wParam, lParam ); 


    return 0; 


    上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是 
    mov eax, 0x400000 
    jmp eax 
    这里的0x400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例: 

    BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) 

    DWORD dwSize; 
    char szTemp[256]; 
    BOOL r = false; 

    //Watch here before it's executed. 
    sprintf( szTemp, 'Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \r\n', hWnd, wMsgFilterMin, wMsgFilterMax ); 
    ::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 ); 
    //Watch over 

    // restore it at first 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); 

    // execute it 
    r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax ); 

    // hook it again 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); 

    //Watch here after it's executed 
    sprintf( szTemp, 'Result of GetMessage is %d.\r\n', r ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); 
    if( r ) 

    sprintf( szTemp, 'Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n', 
    lpMsg->hwnd, lpMsg->message, 
    lpMsg->wParam, lpMsg->lParam, lpMsg->time, 
    lpMsg->pt.x, lpMsg->pt.y ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); 

    strcpy( szTemp, '\r\n' ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); 

    //Watch over 

    return r; 


    先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。 
    整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行,但这个我没有试验过。