• APIHOOK and ANTIAPIHOOK For Ring3


    作 者: Anskya
    时 间: 2007-01-07,20:56
    链 接: http://bbs.pediy.com/showthread.php?t=37586 

    <<API-HOOK and ANTI-API-HOOK For Ring3>>
    转载请保留版权.谢谢
    Anskya@Gmail.com

    今天突然看到"堕落天才"仁兄的两篇文章
    感谢他的的文章和共享精神.谢谢...突然手痒..有感而发
    API-HOOK和ANTI-API-HOOK已经不算什么新鲜的技术了
    一般大概用的技术都差不多

    [1]简要介绍API-HOOK
    1.IAT补丁
    介绍:
    一般调用函数都是call [MessageBoxA]这样的格式
    很明显[MessageBoxA]下的地址就是函数的真正的地址

    代码:

    Delphi:

    push 0

    push 0

    push 0

    push 0

    call -$000467cd(这里是MessageBox在导入表的偏移)

    -$000467cd下的代码就是:
    jmp dword ptr [$004514b0]
    004514b0下的地址是---77D504EA(刚好就是MessageBoxA的地址)
    IAT补丁的意思就是修改jmp dword ptr [$004514b0]这句为自己的钩子地址
    然后钩子返回的时候返回77D504EA地址
    (这里用Delphi的Debug是因为顺手.还有一点就是..VC看不到栈值)

    优点:简单...
    缺点:如果是动态调用的函数导入表中是不会出现这种函数的
    所以就出现了下面的技术

    2.内存补丁:
    介绍:
    由于IAT的缺点于是牛们就想到了动态修改DLL函数的内存
    还是以MessageBoxA为例子:

    代码:

    77D504EA >  8BFF            MOV EDI,EDI

    77D504EC    55              PUSH EBP

    77D504ED    8BEC            MOV EBP,ESP

    77D504EF    833D BC04D777 0>CMP DWORD PTR DS:[77D704BC],0

    77D504F6    74 24           JE SHORT USER32.77D5051C

    77D504F8    64:A1 18000000  MOV EAX,DWORD PTR FS:[18]

    77D504FE    6A 00           PUSH 0

    77D50500    FF70 24         PUSH DWORD PTR DS:[EAX+24]

    77D50503    68 240BD777     PUSH USER32.77D70B24

    77D50508    FF15 C812D177   CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; 

     

    kernel32.InterlockedCompareExchange

    77D5050E    85C0            TEST EAX,EAX

    77D50510    75 0A           JNZ SHORT USER32.77D5051C

    77D50512    C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1

    77D5051C    6A 00           PUSH 0

    77D5051E    FF75 14         PUSH DWORD PTR SS:[EBP+14]

    77D50521    FF75 10         PUSH DWORD PTR SS:[EBP+10]

    77D50524    FF75 0C         PUSH DWORD PTR SS:[EBP+C]

    77D50527    FF75 08         PUSH DWORD PTR SS:[EBP+8]

    77D5052A    E8 2D000000     CALL USER32.MessageBoxExA

    77D5052F    5D              POP EBP

    77D50530    C2 1000         RETN 10

    一般挂钩法就是修改前5个字节
    mov edi,edi
    push ebp
    mov ebp,esp
    刚好是5个字节.jmp到HookProc的地址
    然后再调回到MessageBoxA+5的地方...

    优点:比较实用
    缺点:Ring3很好用...如果非要弄个缺点就是
    有的时候函数代码头部未必是
    mov edi,edi
    push ebp
    mov ebp,esp
    许多API函数的头部都是这样的但是一些cdecl调用格式
    或者非stdcall格式的函数无法挂钩.但是配合脱钩一起用会发现
    效果不错...可以当bpx用..再配合一点汇编知识就可以获取寄存器数据等

    3.深入上面的
    有许许多多的什么陷井技术,栈填写返回地址等...RelocationTable挂钩技术
    其实就是内存补丁技术...不过用了不同的方法填写返回地址而已

    4.SEH or VEH挂钩
    填写Int3,Int1等指令让程序产生异常然后调转到钩子执行过程.
    个人很习惯这种方法VEH玩过一下...但是由于兼容性不强.你可以在HookSpy的代码
    找到VEH的代码和相关应用.配合着调试API用起来也很过瘾
    至少你不用DebugActiveProcess函数去挂接进程
    直接利用CreateRemoteThread函数注入DLL.注入方法很多看个人喜好
    具体查看罗聪前辈的<<用 SEH 技术实现 API Hook>>
    VEH技术实现最近打算也写一篇...正在孕酿(关于着方面的知识太少了)
    有个地方需要注意~SEH和VEH有点不太好调试~许多异常都会碑调试器捕获到
    所以最好用OD或者专用的调试器...编程工具自带的调试器会捕获所有的异常

    5.调试寄存器
    不多说了看EliCZ叔叔的文章和代码吧

    说了一大对无聊的东西现在来说说反调试的问题

    [2]常用的ANTI-APIHOOK技术
    1.IAT-API-HOOK
    由于修改的导入表地址.
    最简单的方法就是你需要的函数全部使用GetProcAddress函数来获取

    代码:

    typedef int(*TMessageBoxA)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, 

     

    UINT uType);

     

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

      TMessageBoxA MsgBox;

      MsgBox = (TMessageBoxA)GetProcAddress(LoadLibrary("user32.dll"), 

     

    "MessageBoxA");

      MsgBox(0, 0, 0, 0);

    }

    2.反内存补丁(着重介绍这里)

    1.相信许多人都用过Madshi的madCollection
    通过跟踪发现他是
    一般挂钩都是修改前5个字节

    代码:

    77D504EA >- FF25 1E00055F   JMP DWORD PTR DS:[5F05001E]--被补丁了.

    77D504F0    3D BC04D777     CMP EAX,user32.77D704BC

    77D504F5    007424 64       ADD BYTE PTR SS:[ESP+64],DH

    77D504F9    A1 18000000     MOV EAX,DWORD PTR DS:[18]

    77D504FE    6A 00           PUSH 0

    77D50500    FF70 24         PUSH DWORD PTR DS:[EAX+24]

    77D50503    68 240BD777     PUSH user32.77D70B24

    77D50508    FF15 C812D177   CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; 

     

    kernel32.InterlockedCompareExchange

    77D5050E    85C0            TEST EAX,EAX

    77D50510    75 0A           JNZ SHORT user32.77D5051C

    77D50512    C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1

    77D5051C    6A 00           PUSH 0

    77D5051E    FF75 14         PUSH DWORD PTR SS:[EBP+14]

    77D50521    FF75 10         PUSH DWORD PTR SS:[EBP+10]

    77D50524    FF75 0C         PUSH DWORD PTR SS:[EBP+C]

    77D50527    FF75 08         PUSH DWORD PTR SS:[EBP+8]

    77D5052A    E8 2D000000     CALL user32.MessageBoxExA

    77D5052F    5D              POP EBP

    77D50530    C2 1000         RETN 10

    知道就可以脱钩了.那我们如何判断他是否被挂钩呢?

    代码:

    bool IsHook(char *lpChar)

    {

      if(*lpChar == 0xFF) return true;

    }

     

    function IsHook(lpFunc: Pointer): Boolean;

    begin

      Result := False;

      if (Char(lpFunc^)=#$FF) then Result := True;

    end;

    判断第一个字节是否是0xFF,你也可以根据一些特别的挂钩修改
    例如有的是直接jmp 调转到直接的地址...E9.或者call-F8(机器码)

    2.栈保存地址..
    和上面一样不过这里有一点点区别挂钩方式被修改成

    代码:

    77D504EA >  68 04BF4000     PUSH 40BF04------这里被写成钩子过程地址

    77D504EF    C3              RETN-------------返回

    77D504F0    3D BC04D777     CMP EAX,user32.77D704BC

    77D504F5    007424 64       ADD BYTE PTR SS:[ESP+64],DH

    77D504F9    A1 18000000     MOV EAX,DWORD PTR DS:[18]

    77D504FE    6A 00           PUSH 0

    77D50500    FF70 24         PUSH DWORD PTR DS:[EAX+24]

    77D50503    68 240BD777     PUSH user32.77D70B24

    77D50508    FF15 C812D177   CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; 

     

    kernel32.InterlockedCompareExchange

    77D5050E    85C0            TEST EAX,EAX

    77D50510    75 0A           JNZ SHORT user32.77D5051C

    77D50512    C705 200BD777 0>MOV DWORD PTR DS:[77D70B20],1

    77D5051C    6A 00           PUSH 0

    77D5051E    FF75 14         PUSH DWORD PTR SS:[EBP+14]

    77D50521    FF75 10         PUSH DWORD PTR SS:[EBP+10]

    77D50524    FF75 0C         PUSH DWORD PTR SS:[EBP+C]

    77D50527    FF75 08         PUSH DWORD PTR SS:[EBP+8]

    77D5052A    E8 2D000000     CALL user32.MessageBoxExA

    77D5052F    5D              POP EBP

    77D50530    C2 1000         RETN 10

    执行完毕后再调转回来

    对付这种挂钩方式...方法一般2种
    分析一下他是如何挂钩的吧...首先他要先获取你要挂钩的函数地址
    填写前六个字节
    1.你提前挂钩...然后让他挂你的钩子.
    这样你直接调用你自己的返回地址就好了最好可以多复制一点.
    另外说一点.许多API函数都是调用xxxxW或者xxxxEx什么的
    所以一般A系函数都很短你甚至可以直接复制到自身进程里面去执行
    具体代码和资料看下面的描述

    2.脱钩.
    这里代码很多.几乎所有的API-Hook library都有这个函数
    自己看一下吧.我就不多说了


    3.利用反汇编引擎搜索钩子返回地址!Cool.直接调用返回函数!!

    由于挂钩的时候会把修改的代码转移到别的地方
    挂钩需要使用6个字节的空间.所以挂钩的时候就需要计算
    需要多长的指令...(所以一般CodeHook Library里面都自带一个长度反汇编引擎)
    看个人喜好...看你用的是什么库了(29A等许多病毒代码里面都有许多)
    常用的:
    Opcode Length Disassembler Coded By Ms-Rem(ASM,C,Delphi版本)
    Length Disassembler Engine By Zombie
    其它的还有.刚在WASM上发现一个新的,没有用过反正就那两种原理
    也没有多少测试...好了下面说说如何寻找返回地址

    钩子都是需要返回地址的.不然这个函数就会被屏蔽.无效代码
    可是返回地址如何获取呢???既然是挂钩那就肯定有一个返回地址..
    不然他自己怎么调用API函数?

    这里引入一个疑问...
    返回地址一般都是最后~由于是跨段代码调转,所以搜索ret后最后
    一个跨段调转即可...
    1.判断是否被挂钩,
    一般API函数开头都是

    代码:

    mov edi, edi

    push ebp

    mov ebp, esp

     

    or

     

    push ebp

    mov ebp, esp

    第一种长度2字节,第二种长度1字节.一般说来程序第一个字节都不太可能是
    E9,E8,FF之类的(为什么很少看到enter这个指令?据说有BUG?)
    一旦超过5个字节就认为他被挂钩了!

    2.确定他复制走了多少地址
    (由于需要六个字节,但是前面三条指令只有5个字节.他就从第4行开始动手,
    然后下来就是.开始计算了.)
    祭出LDE或者OLD.
    可以确定第一行指令是,push 0x00000000,jmp 0x00000000
    这样的东东...如果有哪位仁兄使用了比较另类的挂钩指令的话.那就需要特殊处理



    SizeOfCode(void *Code, unsigned char **pOpcode);返回指令长度
    SizeOfProc(void *Proc);获取过程长度.-她会从指针开头反汇编.直到遇到ret
    (友情提示.代码有个BUG)

    代码:

    unsigned long __fastcall SizeOfProc(void *Proc)

    {

      ULONG  Length;

      PUCHAR pOpcode;

      ULONG  Result = 0;

     

      do

      {

        Length = SizeOfCode(Proc, &pOpcode);

        Result += Length;

        if ((Length == 1) && (*pOpcode == 0xC3)) break;

        Proc = (PVOID)((ULONG)Proc + Length);

      } while (Length);

      return Result;

    }

    当指令长度为1~机器码为C3才认为结束...许多stdcall都是自己恢复堆栈平衡的
    ...这里友情提示一下吧...自己编程的时候要小心.至于如何修改?嘿嘿..
    很容易我就不多说了.省得别人说我鸡婆...

    使用SizeOfCode函数获取指令长度直到大于6或者等于6
    如果第一行就是5个字节.取代码指针.

    3.既然已经获取到了地址
    稍微跟踪一下就会发现函数返回的地址是ret往上第一条指令!也就是最后一条指令
    由于是跨段调转所以会使用call[]---机器码FF的指令

    代码:

    function GetProcAddressEx(Proc: Pointer): Pointer;

    var

      lpCallRet: Pointer;

      iCodeLen: Integer;

    begin

      Result := nil;

      lpCallRet := nil;

     

      iCodeLen := SizeOfCode(Proc);

      //  判断第一行代码是否为push 0xXXXXXXXX

      if (iCodeLen = 5) and (Byte(Proc^) = $68) then

      begin

        //  获取0xXXXXXXXX

        Proc := Pointer(PDWORD(longword(Proc) + 1)^);

        while True do

        begin

          iCodeLen := SizeOfCode(Proc);

          //  判断是否为call dword ptf[0xXXXXXXXX]

          if (iCodeLen = 6) and (Byte(Proc^) = $FF) and (Byte(Pointer

     

    (longword(Proc) + 1)^) = $15) then

          begin

            //  获取0xXXXXXXXX

            lpCallRet := Proc;

            Break;

          end;

          //  函数结尾

          if (Byte(Proc^) = $C3) then Break;

          Proc := pointer(longword(Proc) + iCodeLen);

        end;

      end;

      if lpCallRet <> nil then

      begin

        Result := Pointer(PDWORD(PDWORD(longword(lpCallRet) + 2)^)^);

      end;

    end;

    不好意思...由于暂时没有编程工具.文章里面出现的代码是以前写的
    这篇文章完全是一篇回忆录...以前写的代码和资料全部丢失了..
    这里使用的是Ms-Rem的Opcode Length Disassembler Engine

    这里描述的是如何bypass 堆跳转挂钩模式...jmp和call调转模式
    都一样的原理和代码可以实现.大家可以自己模拟作一下.关键是思路
    大家有什么好的方法希望提供...

    OLD引擎您可以从Ms-Rem的process_hunter Src中获取到
    www.wasm.ru

    再次感谢许许多多的知名的不知名的大侠们的文章和代码.感谢
    thank:EliCZ,Madshi,Ms-Rem,Aphex...29A Group
    转载请保留版权.谢谢
    Anskya@Gmail.com

  • 相关阅读:
    python 代码规范
    Helm 入门指南
    思路和决断
    awk替换第几行第几列的值
    一个awk命令的demo
    装饰模式
    Java多线程Thread.yield(),thread.join(), Thread.sleep(200),Object.wait(),Object.notify(),Object.notifyAll()的区别
    类继承时,构造函数和析构函数的调用次序
    C++中delete和 delete[]的区别
    回溯
  • 原文地址:https://www.cnblogs.com/longle/p/2072875.html
Copyright © 2020-2023  润新知