我想截获魔兽程序中的一个API函数,用到WriteProcessMemory函数将原API函数地址改写为我自己的函数地址,代码如下:
 if ( VirtualProtectEx(GetCurrentProcess(), ppfn,sizeof(pfnNew), PAGE_EXECUTE_READWRITE, &oldProtect) )
{
WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,sizeof(pfnNew), NULL);
VirtualProtectEx(GetCurrentProcess(), ppfn,sizeof(pfnNew), oldProtect, NULL);
}
ppfn为原函数地址,pfnNew为我自定义函数地址,改写之后,运行魔兽发现,截获API成功(因为我自定义的函数里的内容运行了),但问题是,魔兽会弹出一个错误,说什么什么内存不可读,我就纳闷了,我已经将魔兽进程中的内存页权限更改过啊,而且也是更改成功的,为什么会发生这个问题呢?请大家帮我想想问题的原因吧,多谢了!

解决方案 »

  1.   


    说明你写成功了的,看看是不是第二个 VirtualProtectEx 引起的,先把它注释掉
      

  2.   

    当我开启魔兽后,点击局域网,就会弹出类似这样的错误报告:
    This application has encountered a critical error: FATAL ERROR! Program: F:\Warcraft III\War3.exe 
    Exception: 0xC0000005 (ACCESS_VIOLATION) at 001B:6F02D78C The instruction at '0x6F02D78C' referenced memory at '0x00000010'. 
    The memory could not be 'read'. 
    我上网查了一下,大家也帮我看看这个网页里说的跟我这个有关系没?
    http://zhidao.baidu.com/question/15582845.htmlTo Meteor_Code:
    因为我的代码是在动态库里hook到魔兽进程的,所以应该是在同一进程中,应该不会是这个问题。To splei1 && yangzhe:
    我并没改数据啊, 我拦截的是sendto函数,我的函数原型是
    int WINAPI MySendTo( SOCKET s,const char* buf,int len,int flags,const struct sockaddr* to,int tolen)
    {
    return sendto(s,buf,len,flags,to,tolen);
    }
    请大家再帮我想想问题原因吧,一旦解决问题,定重分相赠!
      

  3.   

    嗯看样子不是堆栈平衡的问题啊.刚才又细看了一下你提供的三行代码:
    其中这一句:WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,sizeof(pfnNew), NULL); 我认为第三个参数是不对的,它的原形本来是:
    LPVOID lpBuffer,//Pointer to buffer to write data to.从变量名字命名来看,lpfnNew一定是一个指向新函数的起始地址指针变量吧.而你传入是pfnNew这个变量的地址,而不是你自定义的函数的地址.能明白吗?不要有取地址符.看了你上面提供的那个网页,好像他是装完程序就有这个错误提示,并没有写程序对其虚拟空间进行修改.我没玩过魔兽,这个错误在正常装完游戏程序后也会出现吗?呵呵你再改改试试吧,提供的信息太少,而出错的原因太多,实在不能一下子就改对了.你也可以用OD反汇编一下,看看到底怎么回事.
      

  4.   

    sizeof(pfnNew) 可以这样写么?? 求函数的长度??     
    看LZ代码  pfnNew应该是个函数指针   sizeof指针 肯定是返回4的  而你HOOK 函数 最少也要写5字节吧    这么明显的错误  竟然还看不出来...   CSDN 的星级别水份确实很大
      

  5.   

    to yangyang__ :
    WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,sizeof(pfnNew), NULL); 这句应该是没问题的,这参考了《Windows核心编程》中的相关内容,书中范例也是如此使用的。to IORI915189 :
    你说的HOOK函数,最少5字节应该是用jmp汇编指令跳转的方法实现的HOOK API的方法,而我这里不需要跳转,直接是将源地址更改,如原函数地址用四字节存储,我只需要将这四字节的内容改变,不知道我这样理解是否有误?我也是最近初学HOOK API的,本想将所学的皮毛试验一下,没想到就出这种问题,很是受打击,希望大家再帮我想想办法!多谢
      

  6.   

    需要完整的写回去当然也不是固定的5字节还有有关那个傻b老自以为是的事情
    5字节是不对的为什么?我还说你有什么想法呢 原来就是照样抄的核心编程,核心编程里都谈到了 这样写部分api无效,估计你连为啥无效你都不懂吧 我免费教教你好了 你这样把入口的函数修改5个字节然后跳回去 需要汇编代码的配合,要刚好5个字节是一个完整的汇编代码段 比如像messagebox createprocess这些函数入口是 77D5050B >  8BFF                  mov    edi, edi                        ; ntdll.7C930738 
    77D5050D    55                    push    ebp 
    77D5050E    8BEC                  mov    ebp, esp 这里的汇编代码是8BFF558BEC,刚好5个字节,那么这样做是可以的。 但是并不是所有的api函数的入口都是这种模式的 不信的话可以hook getlasterror看看,你用你的方法能成功么? 当然是不能, 为什么不能呢?因为getlasterror的函数入口和前面谈到的函数不同,如果把他的代码以5个字节截取的话 会把他的入口代码截断,造成无法使用 7C930331 >  64:A1 18000000        mov    eax, dword ptr fs:[18] 
    7C930337    8B40 34              mov    eax, dword ptr [eax+34] 5字节的话64:A1 18000截走 
    剩下的是000 
    8b40 34 
    这样的代码能运行?能完成原先api的任务? 
          
    所以,很清楚的就知道,只要是api的入口不是 
    mov    edi, edi            
    push    ebp 
    mov    ebp, esp 
    你的方法就不行 不幸的是,很多核心层的代码都吧是这样的入口 所以看完你的代码我就知道了 你其实也就hook过用户态的api,什么ssdt hook啥的你就知道一名。 麻烦你不懂不要装懂好不好 害人害己啊!
      

  7.   

    aspower_ 说的很对,我也说过我是最近才初学HOOK API,自然不会理解那么透彻。至于你说的跳转5字节对部分API失效的问题,我以前也在相关资料见到过,但我这里并不是采用这种方法的啊。我确实有很多地方不懂,也没弄透彻,所以我虚心向你们请教,哪位高手如果愿意的话,我可以把代码发到你邮箱,帮我指正一下!
      

  8.   

    发论坛上吧email好久没用了如果害怕公开你可以谈下你hook那个函数具体的hook代码和思路就可以了你如果是先写跳转然后写回可以不管长度但是你注意因为jmp 你的函数位置这个长度的话是5字节
      

  9.   


    你也不说明白... 直接修改4字节地址 替换原地址的是 程序导入表HOOK     这样的话   上面的代码是没问题的   如果出错  就是你的接管函数的问题了  
      

  10.   


    这可能性也太多了吧.
    1.你要写的内存地址本身不对.例如要hook的API定位地址没对,这样写入的数据自然会出问题.
    2.ppfn,pfnNew是啥类型的也没写出来,就不定写入的字节数不对.
    .......你最好还是给出完整的某段核心代码,不然单这几句代码,这也太难推算出是啥问题了.
      

  11.   

    不管怎么说,真的很感激有这么多的好心人帮我一起思考这个问题,真心跟大家说声谢谢!我现在就把主要的几个函数模块贴出来,请大家再帮我看看:
    1:hook模块
    LRESULT __stdcall  CALLBACK ShellProc(int nCode,WPARAM wParam,LPARAM lParam)
    {
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    if (nCode == HSHELL_LANGUAGE)
    {
    HWND hwd = GetForegroundWindow();
    if (hwd != NULL)
    {
    CWnd* pwd = CWnd::FromHandle(hwd);
    CString s = _T("");
    pwd->GetWindowText(s);
    if (s == "Warcraft III")
    {
    PROC pfnOrig = GetProcAddress(::LoadLibrary("wsock32"),"sendto");
    HMODULE hmod = GetModuleHandle("War3.exe");
    if (hmod != NULL)
    {
    ReplaceIATEntryInOneMod("wsock32.dll",pfnOrig,(PROC)MySendTo,hmod);   
    }
    else
    {
    ::WritePrivateProfileString("引用动态库","句柄值","NULL","D:\\1.ini");
    }
    }
    }
    }
    return CallNextHookEx( hkb2, nCode, wParam, lParam );
    }
    2:函数拦截模块
    void ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) 
    {
    ULONG ulSize;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
          ImageDirectoryEntryToData(hmodCaller, TRUE,
          IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);   if(pImportDesc == NULL)
    {
      return;  
    } for(int i = 0; pImportDesc->Name; pImportDesc++, i++)
    {
      PSTR pszModName = (PSTR)((PBYTE) hmodCaller + pImportDesc->Name);
      if(lstrcmpiA(pszModName, pszCalleeModName) == 0)
     break;
    } if(pImportDesc->Name == 0)
      return; PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
      ((PBYTE) hmodCaller + pImportDesc->FirstThunk); for(int i = 0; pThunk->u1.Function; pThunk++, i++) 
    {
      PROC* ppfn = (PROC*) &pThunk->u1.Function;  
      BOOL fFound = (*ppfn == pfnCurrent);
      if(fFound)
      {
     ::WritePrivateProfileString("截获sendto","有这个函数吗","Yes","D:\\1.ini");
     DWORD oldProtect;
     if ( VirtualProtectEx(GetCurrentProcess(), ppfn,sizeof(pfnNew), PAGE_EXECUTE_READWRITE, &oldProtect) )
     {
     WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,sizeof(pfnNew), NULL);
     VirtualProtectEx(GetCurrentProcess(), ppfn,sizeof(pfnNew), oldProtect, NULL);
     }
     break;
      }
    }
    }
    3:自定义函数模块
    int WINAPI MySendTo( SOCKET s,const char* buf,int len,int flags,const struct sockaddr* to,int tolen)
    {
    ::WritePrivateProfileString("截获sendto","截获成功吗","Yes","D:\\3.ini");
    return sendto(s,buf,len,flags,to,tolen);
    }
    以上就是最主要的三个模块,现在的运行效果就是,D:\\3.ini文件中已成功写入“截获成功吗:Yes",我想这说明我的自定义函数已经起到作用了,但现在关键就是出现我在5楼描述的错误,请高手指正!!!
      

  12.   

    我前面说的关于WriteProcessMemory的问题是我错了.我细看了一下,是我理解错了.LZ说核心编程,我就找出书来看了一下.在函数ReplaceIATEntryInOneMod中,你比书上的少了那么一点,就是开头的异常处理.而书上的异常处理在捕获异常后,没有做任何处理,并让程序继续正常执行.并且作者也有明确的说明:(书上原文)"如果在调用ReplaceIATEntryInOneMod时,在最后一个参数中传入的是一个无效的模块句柄,就会触发0xC0000005异常.举个例子,这种情况可能发生在Windows资源管理器的环境中,Windows资源管理器会在另一个线程中快速地动态载入和卸载DLL,从而导致ReplaceIATEntryInOneMod所在的线程所引用的模块句柄变成无效的."(核心编程P603第一段最后4行).你自己看看书,再把你的函数加上异常处理试试.呵呵其实我也只是一学生,也在看这本书,但是还没看到这里.和楼主共同学习啦.
      

  13.   

    int WINAPI MySendTo( SOCKET s,const char* buf,int len,int flags,const struct sockaddr* to,int tolen) 

    ::WritePrivateProfileString("截获sendto","截获成功吗","Yes","D:\\3.ini"); 
    return sendto(s,buf,len,flags,to,tolen); 

    你这里调用的是你修改后的sendto
    死循环了啊
    IAT hook由于处理dll之类的有点麻烦,而且容易漏钩
    所以没有仔细看
    但是你前面已经把sendto转到MySendTo了,你这里再调这个不又回到了MySendTo?
    核心编程好久没看了
    等会有空翻出来看下。
      

  14.   

    To yangyang__ :
    我在我看的核心编程资料中没发现有这样的异常处理代码啊,因为我看的是《Windows核心编程》的电子书,不过你说的很有道理,我也试了在ReplaceIATEntryInOneMod中加上异常处理,改为这样:
    void ReplaceIATEntryInOneMod(...)
    {
       __try
      {
        ...//原函数内容
      }
      __except(EXCEPTION_ACCESS_VIOLATION) {}
    }
    运行还是一样的错误啊!!!
      

  15.   

    __try
    {
    ...
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {}
      

  16.   

    这个我也试过,还是不行啊,郁闷ing
      

  17.   

    你有没有考虑过aspower_说的死循环啊?在修改IAT表之前,先保存原来的函数的地址,然后在自已的函数里调用它.不介意的话给我看看你的源码呗[email protected]