12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 268|回复: 0

基于异常的反调试绕过实践

[复制链接]
发表于 2020-5-11 16:05:02 | 显示全部楼层 |阅读模式
前言

最近在学习软件调试相关的内容,顺便举一反三找找反调试以及反反调试的相关方法。逛着逛着找到了这个网站:2种基于异常机制的反调试方法,虽然一看就知道不知道从哪转载的,而且搜索引擎有很多这篇文章一模一样的重复结果,但是代码倒是挺有趣的,研究玩玩。
第一题

第一段代码是基于IsDebugPresent()类似实现的反调试,代码如下
#include int AntiDebugInSEH(EXCEPTION_POINTERS* ExceptionInfo){        DWORD state = 0x00;        __asm        {                mov eax,dword ptr fs:[30h];                movzx eax, byte ptr ds:[eax+2h];                mov state,eax;        }        ExceptionInfo->ContextRecord->Eax = state;        ExceptionInfo->ContextRecord->Eip = 0x06+(DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress;        return EXCEPTION_CONTINUE_EXECUTION;}int main(){        int i=0;        __try        {                __asm                {                        xor eax,eax;                        mov dword ptr [eax],0; //触发异常                }                __asm                {                    //异常返回后,eax的值为AntiDebugInSEH中ExceptionInfo->ContextRecord->Eax设定的值                                        mov i,eax                }                if(i==1)                {}                else                {                        MessageBox(NULL,"","",0);                }                i++;        }        __except(AntiDebugInSEH(GetExceptionInformation()))        {        }}程序的流程为通过主动引发异常跳转入异常处理函数,在异常处理函数内部查看fs:[30h]+2h的值,如果为1则不显示任何结果,为0则弹出一个空窗口。在着手这个程序前,先来看看IsDebugPresent究竟是何方神圣。
IsDebugPresent()

VC中有一个用于检测调试状态的APIIsDebugPresent(),当程序处于被调试状态时,该API返回True。出于对软件的保护,我们在程序的变量初始化前就调用该API检测调试情况。代码如下:
#include#include#include#includeint main(){    if (IsDebuggerPresent())//检测是否处于被调试状态    {        std::cout  dd 0091f000+0x300091f030  0091c000 00000000 00000000 000000000:000> dt _PEB 0091c000ntdll!_PEB   +0x000 InheritedAddressSpace : 0 ''   +0x001 ReadImageFileExecOptions : 0 ''   +0x002 BeingDebugged    : 0x1 ''   +0x003 BitField         : 0x4 ''   +0x003 ImageUsesLargePages : 0y0   +0x003 IsProtectedProcess : 0y0   +0x003 IsImageDynamicallyRelocated : 0y1   +0x003 SkipPatchingUser32Forwarders : 0y0 ......PEB是一个庞大的结构体,可以看到,PEB基址偏移0x2处的域刚好就是BeingDebugged。至此,通过IsDebugPresent()反调试的原理已经被摸清,下面来看看怎么绕过。
绕过方式


  • 最简单的当然是修改寄存器的值。首先通过bp KERNELBASE!IsDebuggerPresent在内核API调用前让程序停下来,在ret执行前使用r @eax=0修改EAX的值——大功告成。
  • 复杂但究其根源的方法是修改程序的PEB,将调试位置0。比较方便的方法为先通过dg获取fs寄存器指向的基址,然后通过eb指令将对应位置置0。
    0:000> dg fs                                P Si Gr Pr LoSel    Base     Limit     Type    l ze an es ng Flags---- -------- -------- ---------- - -- -- -- -- --------0053 00e05000 00000fff Data RW Ac 3 Bg By P  Nl 000004f30:000> eb poi(00e05000+0x30)+2 0
回到题目,可以发现,题目中以下语句
mov eax,dword ptr fs:[30h];movzx eax, byte ptr ds:[eax+2h];mov state,eax;就是读取_PEB中BeingDebugged字段的汇编语句,由此得出,绕过的方式与我实现的示例程序一样,修改对应字段的值就好。
第二题

首先,第二题在网站中并没有解答...网页上的原文是
这段代码,还是读者自己试下,我暂时没想到如果绕过检测的方法。
这么多重复的文章,抄来抄去的,居然连这句也一并抄了....
算了,挖挖看看。照例先上题目源码
#include LONG WINAPI UnhandledExcept(EXCEPTION_POINTERS *pExpInfo)  {        if(pExpInfo->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT)        {                return EXCEPTION_EXECUTE_HANDLER;        }        else        {                pExpInfo->ContextRecord->Eip = (DWORD)(pExpInfo->ContextRecord->Eip+1);                pExpInfo->ContextRecord->Eax = 0x0000FEEE;                return EXCEPTION_CONTINUE_EXECUTION;        }}int main(){        HMODULE hMod = LoadLibrary("kernel32.dll");        DWORD* funcAddr = (DWORD*)GetProcAddress(hMod,"ExitProcess");        SetUnhandledExceptionFilter(UnhandledExcept);        _asm int 3;        __asm        {                cmp ax,0xFEEE;                jz next;                push 0;                mov eax,funcAddr;                call eax;next:        }        {                MessageBox(NULL,"","",MB_OK);        }}嗯,通过断点异常来让调试器捕获,于是无法执行程序中内置的异常处理流程,导致程序直接退出。看起来就像是先有鸡还是先有蛋的的悖论,连接上了调试器断点肯定得断,不连接就无法获取程序的信息。但是INT 3断点异常是陷阱,改个EIP就能过去,为了硬核一点,我对程序进行了小小的修改,将异常改为除0异常,如果不执行程序内部的异常处理,调试器将永远卡在一个地方,改EIP也木得用。
#include#include#include#includeDWORD flag = 0;void __stdcall succ(){    MessageBox(NULL, "NoDebug", "Good", MB_OK);}void __stdcall fail(){    MessageBox(NULL, "Debug!", "Oops", MB_OK);}LONG WINAPI UnhandledExcept(EXCEPTION_POINTERS *pExpInfo){    pExpInfo->ContextRecord->Eip = (DWORD)(pExpInfo->ContextRecord->Eip +0xB);    pExpInfo->ContextRecord->Eax = 0x0000FEEE;    return EXCEPTION_CONTINUE_EXECUTION;}int main(){    HMODULE hMod = LoadLibrary("kernel32.dll");    DWORD* funcAddr = (DWORD*)GetProcAddress(hMod, "ExitProcess");    SetUnhandledExceptionFilter(UnhandledExcept);    flag = 6 / flag;//引发除0异常    __asm    {        cmp eax, 0x0000FEEE;        jz next;        mov eax, fail;        call eax;        push 0;        mov eax, funcAddr;        call eax;    next:        mov eax, succ;        call eax;    }    return 0;}这货在VS2017中编译运行的情况是这样的

点击继续无法运行,一直卡在了异常报错处。在Windbg中也好不到哪去
0:000> g(1f84.3ea0): Integer divide-by-zero - code c0000094 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=00000006 ebx=0048b000 ecx=a5e70000 edx=00000000 esi=0032fa08 edi=0032faf0eip=003b19a5 esp=0032fa08 ebp=0032faf0 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246Breakpoints!main+0x75:003b19a5 f73538a13b00    div     eax,dword ptr [Breakpoints!flag (003ba138)] ds:002b:003ba138=000000000:000> g(1f84.3ea0): Integer divide-by-zero - code c0000094 (!!! second chance !!!)eax=00000006 ebx=0048b000 ecx=a5e70000 edx=00000000 esi=0032fa08 edi=0032faf0eip=003b19a5 esp=0032fa08 ebp=0032faf0 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246Breakpoints!main+0x75:003b19a5 f73538a13b00    div     eax,dword ptr [Breakpoints!flag (003ba138)] ds:002b:003ba138=00000000在没有调试器的情况下程序跑得很欢:

成了,十分符合题目的要求,接下来开搞。
失败的尝试-1

首先联想到的是其底层实现可能与IsDebugPresent一样,通过检测PEB中的BeingDebugging位来判断是否将异常传递给调试器。那就改来试试
0:000> dg fs                                  P Si Gr Pr LoSel    Base     Limit     Type    l ze an es ng Flags---- -------- -------- ---------- - -- -- -- -- --------0053 009a1000 00000fff Data RW Ac 3 Bg By P  Nl 000004f30:000> eb poi(009a1000+0x30)+2 00:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)0:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)死循环,看来系统的异常分发机制与该标志位无关。改下EIP强行跳转到下一条语句?
0:000> r @eip=003b19ab0:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)eip=003b19a50:000> g(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)eip=003b19a5不仅还是陷入了死循环,而且EIP还跳回去了。
失败的尝试-2

在windbg中有一条call指令可以直接调用某个地址的方法,格式为.call API(Param1, Param2,..)。如果在引发异常的时候直接调用异常处理API能不能绕过呢?不过异常处理函数的原型为LONG WINAPI UnhandledExcept(EXCEPTION_POINTERS *pExpInfo),参数需要一个指向异常信息的EXCEPTION_POINTERS指针。其原型为
typedef struct _EXCEPTION_POINTERS {  PEXCEPTION_RECORD ExceptionRecord;  PCONTEXT          ContextRecord;} EXCEPTION_POINTERS, *PEX在内存里搜搜看
0:000> .excrUnable to get exception context, HRESULT 0x8000FFFF0:000> .exr -1ExceptionAddress: 003b19a5 (Breakpoints!main+0x00000075)ExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 00000000NumberParameters: 00:000> s -d esp L1000 0xc000009400b3f948  c0000094 00000000 00000000 003ba57c  ............|.;.00b3f9b4  c0000094 753fa0e0 56fb63e5 2ab7db1f  ......?u.c.V...*0:000> .exr 00b3f948ExceptionAddress: 003ba57c (Breakpoints!__dyn_tls_dtor_callback)ExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 00000000NumberParameters: 39090000:000> .exr 00b3f9b4ExceptionAddress: 2ab7db1fExceptionCode: c0000094 (Integer divide-by-zero)ExceptionFlags: 753fa0e0NumberParameters: 0在内存中能搜索到结构体的第一个元素ExceptionRecord,但是指向其的指针却没有任何踪迹,总不能按每4个字节全部解引用一遍看看内容吧。
于是尝试传个0作为参数,手动跳过流程,改改寄存器变量值:
0:000> .call Breakpoints!UnhandledExcept(0)Thread is set up for call, 'g' will execute.WARNING: This can have serious side-effects,including deadlocks and corruption of the debuggee.0:000> t(2668.1f30): Integer divide-by-zero - code c0000094 (first chance)0:000> t(2668.1f30): Integer divide-by-zero - code c0000094 (!!! second chance !!!)不行,无法跳转,EIP已经被锁死了,只能从异常分发机制下手,而非对寄存器或执行顺序进行魔改。
32位

搜索了一番与反调试相关的文章教程,发现了一篇挺不错的文章[How to debug UnhandleExceptionHandler!],刚好适用于这种情况。文章的第一步
1. put a bp on kernel32!UnhandledExceptionFilter
那接下来可就一马平川了,先给kernel32!UnhandledExceptionFilter下个断点,然后看看情况
0:000> bu kernel32!UnhandledExceptionFilter0:000> gBreakpoint 0 hiteax=770eed38 ebx=00000000 ecx=0026f30f edx=777f70b4 esi=0026f408 edi=00000000eip=770eed38 esp=0026f3dc ebp=0026faf4 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202kernel32!UnhandledExceptionFilter:770eed38 6a5c            push    5Ch嗯,看来确实进入了系统分发结构化异常的流程,接下来看看kernel32!UnhandledExceptionFilter会调用哪些方法来确定调试器的存在
kernel32!UnhandledExceptionFilter:770eed38 6a5c            push    5Ch770eed3a 68f0ee0e77      push    offset kernel32!BaseReleaseProcessExePath+0x13b8 (770eeef0)770eed3f e82cd0feff      call    kernel32!_SEH_prolog4 (770dbd70)770eed44 c745e006000000  mov     dword ptr [ebp-20h],6770eed4b 33f6            xor     esi,esi770eed4d 8975e4          mov     dword ptr [ebp-1Ch],esi770eed50 8975dc          mov     dword ptr [ebp-24h],esi770eed53 8975d8          mov     dword ptr [ebp-28h],esi770eed56 8b5d08          mov     ebx,dword ptr [ebp+8]770eed59 8b03            mov     eax,dword ptr [ebx]770eed5b f6400410        test    byte ptr [eax+4],10h770eed5f 0f8538e0ffff    jne     kernel32!UnhandledExceptionFilter+0x29 (770ecd9d)770eed65 c745d401000000  mov     dword ptr [ebp-2Ch],1770eed6c 8138090400c0    cmp     dword ptr [eax],0C0000409h770eed72 0f8473e0ffff    je      kernel32!UnhandledExceptionFilter+0x3f (770ecdeb)770eed78 53              push    ebx770eed79 e843020000      call    kernel32!CheckForReadOnlyResourceFilter (770eefc1)770eed7e 83f8ff          cmp     eax,0FFFFFFFFh770eed81 0f841de0ffff    je      kernel32!UnhandledExceptionFilter+0x91 (770ecda4)770eed87 e8ad010000      call    kernel32!BasepIsDebugPortPresent (770eef39)770eed8c 85c0            test    eax,eax770eed8e 0f8509e0ffff    jne     kernel32!UnhandledExceptionFilter+0x29 (770ecd9d)......该API调用了kernel32!BasepIsDebugPortPresent来确定调试器的端口,动态分析得出调用完该API后,EAX=1,通过test判断非0后经过jne跳转到调试器的处理例程中。在test指令执行前将EAX清空可以实现调试器隐藏。

那么这个API又是如何实现调试器情况监视呢?跟进去看看
kernel32!BasepIsDebugPortPresent:770eef39 8bff            mov     edi,edi770eef3b 55              push    ebp770eef3c 8bec            mov     ebp,esp770eef3e 51              push    ecx770eef3f 8365fc00        and     dword ptr [ebp-4],0770eef43 6a00            push    0770eef45 6a04            push    4770eef47 8d45fc          lea     eax,[ebp-4]770eef4a 50              push    eax770eef4b 6a07            push    7770eef4d 6aff            push    0FFFFFFFFh770eef4f ff154c150977    call    dword ptr [kernel32!_imp__NtQueryInformationProcess (7709154c)].....原来在异常分发的时候,调试器监测的底层实现是ntdll!NtQueryInformationProcess。MSDN上关于该API的介绍为:
__kernel_entry NTSTATUS NtQueryInformationProcess(  IN HANDLE           ProcessHandle,  IN PROCESSINFOCLASS ProcessInformationClass,  OUT PVOID           ProcessInformation,  IN ULONG            ProcessInformationLength,  OUT PULONG          ReturnLength);
Parameters
ProcessHandleA handle to the process for which information is to be retrieved.
ProcessInformationClassThe type of process information to be retrieved. This parameter can be one of the following values from the PROCESSINFOCLASS enumeration.
ValueMeaningProcessBasicInformation 0Retrieves a pointer to a PEB structure that can be used to determine whether the specified process is being debugged, and a unique value used by the system to identify the specified process.Use the CheckRemoteDebuggerPresent and GetProcessId functions to obtain this information.ProcessDebugPort 7Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process is being run under the control of a ring 3 debugger.Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function.
第一个参数为进程的句柄,第二个参数是选定查询信息的类型,第三个参数是指向返回查询结果的指针,第四个参数是信息的长度,第五个参数是返回信息的长度。看看传入的参数信息:
0:000> kbChildEBP RetAddr  Args to Child              001af6f4 770eef55 ffffffff 00000007 001af710 ntdll!NtQueryInformationProcess0:000> dd 001af6f4+8001af6fc  ffffffff 00000007 001af710 00000004001af70c  00000000 00000000 001af798 770eed8c第一个参数FFFFFFFF是一个伪句柄,只适用于线程内部使用,指向其自身。第二个参数7根据表格意为查询调试端口信息,返回地址为001af710。通过查看EPROCESS结构体发现其中有DEBUG_PORT位域,推测与其有关。
0:000> .processImplicit process is now 7ffd40000:000> dt _EPROCESS 7ffd4000ntdll!_EPROCESS  ......   +0x0ec DebugPort        : (null)   .....尝试在该位域下一个读写硬件断点,但是函数执行完毕后没有中断,看来与其无关
0:000> ba r4 7ffd4000++0x0ec0:000> peax=000000ea ebx=001af7c8 ecx=001af71d edx=777f70b4 esi=00000000 edi=00000000eip=777f604d esp=001af6f8 ebp=001af714 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246ntdll!NtQueryInformationProcess+0x5:777f604d ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)0:000> peax=000000ea ebx=001af7c8 ecx=001af71d edx=7ffe0300 esi=00000000 edi=00000000eip=777f6052 esp=001af6f8 ebp=001af714 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246ntdll!NtQueryInformationProcess+0xa:777f6052 ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (777f70b0)}0:000> peax=00000000 ebx=001af7c8 ecx=001af6f4 edx=777f70b4 esi=00000000 edi=00000000eip=777f6054 esp=001af6f8 ebp=001af714 iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246ntdll!NtQueryInformationProcess+0xc:777f6054 c21400          ret     14h函数执行完毕后,查看第三个参数指向的地址,非0,意味着系统监测到该程序处于ring3级别的调试器下,监测的方法未知。
0:000> dd 001af710 L1001af710  ffffffff调用完毕后EAX = 0。如果在此时将EAX置为0x80000000,执行完test指令后,zf=1 sf=1,  of=0,满足下一个jl的跳转条件,便可绕过调试器监测。
770eef4f ff154c150977    call    dword ptr [kernel32!_imp__NtQueryInformationProcess (7709154c)]770eef55 85c0            test    eax,eax770eef57 7c0a            jl      kernel32!BasepIsDebugPortPresent+0x2b (770eef63)770eef59 837dfc00        cmp     dword ptr [ebp-4],0770eef5d 0f85f7d20100    jne     kernel32!BasepIsDebugPortPresent+0x26 (7710c25a)770eef63 33c0            xor     eax,eax770eef65 c9              leave770eef66 c3              ret770eef67 90              nop
不作处理,接着跳转到程序的末尾,API通过清空EAX并自增来返回1这个状态,反汇编如下
7710c25a 33c0            xor     eax,eax7710c25c 40              inc     eax7710c25d c9              leave7710c25e c3              ret在返回前将EAX清空也是一个绕过的方法。
64位

上面网站的教程是32位的,在64位的WIN10下并没有kernel32!UnhandledExceptionFilter这个API。
至此本来打算告一段落了,突然想起,如果在对ntdll!NtQueryInformationProcess下个断,会不会有意外收获?
0:000> bu ntdll!NtQueryInformationProcess0:000> gBreakpoint 3 hiteax=011bf7d8 ebx=00000000 ecx=011bf7dc edx=011bf7e0 esi=011bf860 edi=011bfed8eip=774d0610 esp=011bf7a8 ebp=011bf848 iopl=0         nv up ei ng nz na po cycs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000283ntdll!NtQueryInformationProcess:774d0610 b819000000      mov     eax,19h嗯,一模一样...但为什么没有kernel32!UnHandledExceptionFilter这个API,看了看调用堆栈明白了...全新的异常分发调用,走的是KERNELBASE!UnhandledExceptionFilter。
0:000> kp # ChildEBP RetAddr  00 003af0b0 754f9d74 ntdll!NtQueryInformationProcess01 003af0d4 754fa1d1 KERNELBASE!BasepIsDebugPortPresent+0x1d02 003af16c 77502fff KERNELBASE!UnhandledExceptionFilter+0xf103 003af9dc 774c65fd ntdll!__RtlUserThreadStart+0x3ca0104 003af9ec 00000000 ntdll!_RtlUserThreadStart+0x1b还是一样的配方,还是熟悉的味道,修改KERNELBASE!BasepIsDebugPortPresent的返回结果,大功告成。

总结

玩了一遍Windows调试器监视与异常分发的流程,熟悉了与调试/反调试相关的API,也学到了32位与64位内核态API的前缀区别(血的教训),不过由于网上资料质量参差不齐,走了太多的弯路。

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-25 07:53 , Processed in 0.093750 second(s), 32 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表