r3反调试

R3反调试方法非常多,且充斥着各种猥琐的方法。

1.调用API反调试
IsDebuggerPresent:它查询PEB中的IsDebugged标志
CheckRemoteDebuggerPresent:这个函数将一个进程句柄作为参数,检查这个句柄对应的进程是否被调试器附加
NtQueryInfomationProcess:它用来提取一个给定进程的信息。第一个参数是进程的句柄,第二个参数告诉我们它需要提取进程信息的类型,例如将该参数置为ProcessDebugPort(值为0x7),将会告诉你这个句柄标识的进程是否正在被调试。如果进程正在被调试,返回调试端口,否为返回0

 

2.OutputDebugString
这个函数的作用是在调试器中显示一个字符串。同时也可以用来探测调试器的存在。
使用SetLastError设置错误码,如果进程没有被调试器附加,调用OutputDebugString函数,因为这个函数调用失败,重新设置了错误码,因此GetLastError获取的错误码应该不是我们设置的任意值。如果进程被附加并调用OutputDebugString函数,那么调用应该会成功,这时GetLastError值不会变

DWORD errorValue = 12345;
SetLastError(errorValue);
OutputDebugString("Test for Debugger");
if(GetLastError() == errorValue)
{
    ExitProcess();
}
else
{
    RunMaliciousPayload();
}

也可以调用DeleteFiber函数,对于DeleteFiber函数,如果给它传递一个无效的参数的话会抛出ERROR_INVALID_PARAMETER异常。如果进程正在被调试的话,异常会被调试器捕获。所以,同样可以通过验证LastError值来检测调试器的存在。 

 

3.手动检测数据结构
1)、手动检测数据结构

mov eax,dword ptr fs:[30h]
mov ebx,byte ptr [eax+2]   // +2偏移为BeingDebugged标志的位置
test ebx,ebx
jz NoDebuggerDetected

 2)、检测ProcessHeap属性
PEB的Reserved4数组中有一个未公开的位置叫做ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap位于PEB结构的0x18处。第一个堆头部有一个属性字段,它告诉内核这个堆是否在调试器中创建。这些属性叫做ForceFlags和Flags

mov eax,large fs:30h
mov eax,dword ptr[eax+18h]
cmp dword ptr ds:[eax+10h],0    // XP中ForceFlags属性位于堆头部偏移量0x10处,win7 32位中在0x44处。
jne DebuggerDetected

 对付这种反调试的方法是手动修改ProcessHeap标志

 

4. 识别调试器的行为

1)、INT扫描
调试器下断点会往内存写入int 3,也就是0xCC。反调试技术就是在它的代码中查找机器码0xCC,来扫描调试器对它代码的INT 3修改
 

call xxx
pop edi
sub edi,5
mov ecx,400h
mov eax,0CCh
repne scasb
jz DebuggerDetected

这段代码首先执行一个函数调用,随后用pop指令将EIP寄存器的值存入EDI。然后将EDI设置为代码的开始。

接下来扫描这段代码的0xCC字节。如果发现了0xCC字节,证明存在调试器。对抗这种反调试技术的方法是使用硬件断点。

 2)、执行代码校验,检查代码是否被修改
执行代码中机器码的CRC或者MD5校验和检查。
可以通过使用硬件断点,或者在代码运行过程中用调试器手动修改执行路径来对抗这种反调试技术。

3)、时钟检测
记录执行一段操作前后的时间戳,然后比较这里两个时间戳,如果存在滞后,则可以认为存在调试器
记录触发一个异常前后的时间戳。如果不调试进程,可以很快地处理完异常,因为调试器处理异常的速度非常慢。默认情况下,调试器处理异常时需要人为干预,这导致大量延迟。
虽然很多调试器允许我们忽略异常,将异常直接返回给程序,但这样操作仍然存在不小的延迟。
rdtsc指令(操作码0x0F31),它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入EDX:EAX中。
可以运行两次rdstc指令,然后比较两次读取之间的差值
 

rdtsc
xor ecx,ecx
add ecx,eax
rdtsc
sub eax,ecx
cmp eax,0xFF
jb NoDebuggerDetected

QueryPerformanceCounter和GetTickCount函数也可以计算时间
可以通过识别这些函数的两个连续调用及随后的一个比较来识别它们。
利用这种检测找到调试器的前提是:当前仅当在获取时间增量的两次调用之间,你单步执行了程序或是设置了断点。因此,避免这种检测的最简单方法就是在时钟检测后设置断点,接着再开始你的单步执行。

 

5. 干扰调试器的功能
1)、使用TLS回调

调试器是从程序PE头部指定的入口点处开始运行。TLS回调被用来在程序入口点执行之前运行代码

TLS是Windows的一个存储类,其中数据对象不是一个自动的堆栈变量,而是代码中运行的每个线程的一个本地变量。

TLS允许每个线程维护一个用TLS声明的专有变量。

在应用程序实现TLS的情况下,可执行程序的PE头部会包含一个.tls段。TLS提供了初始化和终止TLS数据对象的回调函数。

Windows系统在执行程序正常的入口点之前运行这些回调函数。

通常情况下,正常程序不使用.tls段,如果在可执行程序中看到.tls段,你应该立即怀疑它使用了反调试技术

用IDA PRO可以很容易分析TLS回调函数

2)、使用异常

可以使用异常来破坏或者探测调试器。调试器捕获异常后,并不会立即将处理权返回被调试进程处理,

大多数利用异常的反调试技术往往据此来探测调试器。

多数调试器默认的设置是捕获异常后不将异常传递给应用程序。如果调试器不能将异常结果正确返回到被调试进程,

那么这种异常失效可以被进程内部的异常处理机制探测

当分析时候,建议设置调试器的选项,把所有异常传递给应用程序

也可以通过WINDBG 附加游戏来找出这线程ID是多少在发送异常。

找到发送异常线程后。我们通过线程入口地址的后4位做为特征。来结束这个发异常的线程

3)、插入中断

一种传统的反调试方法是使用异常来干扰分析人员,通过在合法指令序列中插入中断,破坏程序的正常运行。

因为与调试器自身设置软件断点的机制相同,根据调试器的设置,这种插入能够导致调试器停止运行。

 

6.窗口检测

EnumWindow 函数调用后,系统枚举所有顶级窗口,为每个窗口调用一次回调函数。

在回调函数中用 GetWindowText 得到窗口标题,用 strstr 等函数查找有无 Ollydbg 字符串。 StrStr(大小写敏感,对应的 StrStrI 大小写不敏感)函数返回 str2 第一次出现在

str1 中的位置,如果没有找到,返回 NULL。

 

GetForeGroundWindow 返回前台窗口(用户当前工作的窗口)。 当程序被调试时,调用这个函数将获得 Ollydbg 的窗口句柄,这样就可以向其发送 WM_CLOSE 消息将其关闭了

SendMessage(handle,WM_CLOSE,NULL,NULL)

 

// 使OD窗口不可用

HWND hd_od=FindWindow("ollydbg",NULL);

SetWindowLong(hd_od,GWL_STYLE,WS_DISABLED);

 

Windows下当我们双击某个可执行文件时,由explorer进程负责启动。所以,一般手动启动的程序进程的父进程都是explorer。

如果父进程的 PID 不是 explorer.exe, cmd.exe, Services.exe 的 PID,则目标进程很可能被调试

 

7.OD:Guard Pages

这个检查是针对 OllyDbg 的,因为它和 OllyDbg 的内存访问/写入断点特性相关。除了硬件断点和软件断点外, OllyDbg 允许设置一个内存访问/写入断点,这种类型的断点是

通过页面保护来实现的。简单地说,页面保护提供了当应用程序的某块内存被访问时获得通知这样一个途径。

页面保护是通过 PAGE_GUARD 页面保护修改符来设置的,如果访问的内存地址是受保护页面的一部分,将会产生一个 STATUS_GUARD_PAGE_VIOLATION(0x80000001)异常。如

果进程被 OllyDbg 调试并且受保护的页面被访问,将不会抛出异常,访问将会被当作内存断点来处理,而壳正好利用了这一点。

 

8.调用函数GetWindowLongA检查OD的窗口属性

OD的窗口属性比较特殊,之前调试一些传奇私服的时候,他们就是用的这种检测方法
 

char name[MAX_PATH];
GetWindowTextA(hwnd,name,MAX_PATH);
LONG mStyle = GetWindowLongA(hwnd,GWL_STYLE);
LONG ExtStyle=GetWindowLongA(hwnd,GWL_EXSTYLE);
if(mStyle==0x57c70000&&ExtStyle==0x140)
{
    printf("find od 1 %08x %s\r\n",dwEventThread,name);
}
if (mStyle==0x56CF0000&&ExtStyle==0x140)
{
    printf("Find od 2 %08x %s\r\n",dwEventThread,name);
}

9.特征码检测

特征码检测枚举当前正在运行的进程,并在进程的内存空间中搜索特定调试器的代码片段。

例如 OllyDbg 有这样一段特征码:
 

0x41, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00,
0x20, 0x00, 0x4f, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x79, 0x00,
0x44, 0x00, 0x62, 0x00, 0x67, 0x00, 0x00, 0x00, 0x4f, 0x00,
0x4b, 0x00, 0x00, 0x00

示例:
 

BOOL CheckDebug()
{
    BYTE sign[] = {0x41, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00,
                0x20, 0x00, 0x4f, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x79, 0x00,
                0x44, 0x00, 0x62, 0x00, 0x67, 0x00, 0x00, 0x00, 0x4f, 0x00,
                0x4b, 0x00, 0x00, 0x00;}

    PROCESSENTRY32 sentry32 = {0};
    sentry32.dwSize = sizeof(sentry32);
    HANDLE phsnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    Process32First(phsnap, &sentry32);
    do{
        HANDLE hps = OpenProcess(MAXIMUM_ALLOWED, FALSE, sentry32.th32ProcessID);
        if (hps != 0)
        {
            DWORD szReaded = 0;
            BYTE signRemote[sizeof(sign)];
            ReadProcessMemory(hps, (LPCVOID)0x4f632a, signRemote, sizeof(signRemote), &szReaded);
            if (szReaded > 0)
            {
                if (memcmp(sign, signRemote, sizeof(sign)) == 0)
                {
                    CloseHandle(phsnap);
                    return 0;
                }
            }
        }
    }
    sentry32.dwSize = sizeof(sentry32);
}while(Process32Next(phsnap, &sentry32));

 

10.对调试需要用到的函数进行3环的hook,阻止调试
比如DbgBreakPointDbgUiRemoteBreakinDbgUserBreakPoint ,这三个函数。附加调试器后需要中断给用户。

DbgBreakPointDbgUserBreakPoint       直接 retn返回,附加游戏的时候,会创建一个远程线程,而创建这个线程来调试的时候没有了CC中断指令,这样调试器就接受不到中断指令。

DbgUiRemoteBreakinntdll提供的用于在目标进程中创建远线程下软件断点的函数

当我们用OD调试时,CreateRemoteThread函数在目标程序中创建DbgUiRemoteBreakin线程的时候,是下了INT 3软中断。
因为是在调试状态,所以调试器可以捕获这个异常。如果目标程序没有被调试的话,程序就会正常执行。

类似的还有很多函数比如:

DebugActiveProcess:调试某个进程

DbgUiConnectToDbg:连接调试子系统

ZwCreateDebugObject:创建调试对象

ZwWaitForDebugEvent:等待一个调试事件

ZwDebugContinue:继续调试

 

 

 

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值