【Day 16】从一开始的 Anti-Debug 生活 - Anti-Debug

环境

  • Windows 10 21H1
  • x64dbg Aug 2 2020, 13:56:14
  • Visual Studio 2019

前情提要

上一篇【Day 15】从零开始的 Debug 生活 - Debugger 原理提到一些 Debugger 的实作原理,了解断点、Flag 设定的实作方式,认识 Debugger 的基本行为。

这一篇会说明一般常见的 Anti-Debug 的方法,不过方法其实非常多,为了保持这个系列的连贯性,这一篇主要针对上一篇提到的 Debugger 基本行为实作 Anti-Debug。

下面介绍与实作的部分都是以 64-bit 的环境为前提,但是 32-bit 原理其实差不多。另外不同 Debugger 因为实作方式不同,导致 Anti-Debug 不一定能在每个 Debugger 上成功,这篇以 x64dbg 为主。

Anti-Debug

Debug Flag

原理

上一篇在讲 Debug Flag 时,曾说过 Debug Flag 是在 PEB 结构中的一个成员 BeingDebugged。可以透过检查这个 Debug Flag 的值确认目前有没有被 Debug。

这部分的实作方法很丰富,既可以使用 Windows API,也可以自己找出 PEB 结构确认。这篇就使用最简单的 IsDebuggerPresent

POC

程序专案可以参考我的 GitHub zeze-zeze/2021iThome

#include<Windows.h>

int main(int argc, char* argv[]) {
    if (IsDebuggerPresent()) {
        MessageBoxW(0, L"Detect", L"Debugger", 0);
    }
    else {
        MessageBoxW(0, L"Not Detect", L"Debugger", 0);
    }
}

实际测试

正常状况下执行,会跳出 Not Detect 的讯息框;如果使用 Debugger,则会跳出 Detect 的讯息框。当然如果在 Debugger 把 PEB 结构的 BeingDebugged 改成 0 就不会侦测到 Debugger。

Software Breakpoint

原理

上一篇说明 Software Breakpoint 时,有说 Debugger 会把下断点的位址改成 int3,然後 Debugger 会处理产生的 Exception。

同个原理可以也被用在 Step-Over,在 x64dbg 中按下 F8 可以直接把呼叫的函数执行完并停在 call 的下一个指令。这个实作原理就是把呼叫的函数的 Return Address 改成 int3

所以如果我们在程序中定义一个函数,并在函数检查 Return Address 是不是 int3,如果是就改掉,让它无法触发 Exception。

POC

程序专案可以参考我的 GitHub zeze-zeze/2021iThome

#include <intrin.h>
#include <Windows.h>
#pragma intrinsic(_ReturnAddress)

void PatchInt3() {
    PVOID pRetAddress = _ReturnAddress();

    // 如果 Return Address 是 int3 (0xcc),就改掉
    if (*(PBYTE)pRetAddress == 0xCC) {
        DWORD dwOldProtect;
        if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
        {
            // 这边是填成 nop(0x90),在这个 POC 不会  Crash。
            // 但是最好还是改成原本 Return Address 的值,否则可能会 Crash
            *(PBYTE)pRetAddress = 0x90;
            VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
        }
    }
}
int main(int argc, char* argv[]) {
    PatchInt3();
    MessageBoxW(0, L"You cannot keep debugging", L"Give up", 0);
}

实际测试

开 Debugger 到要执行 PatchInt3 之前,按下 F8 Step-Over,会直接跳出 You cannot keep debugging 的讯息框并且直接继续执行到结束,因为断点已经被我们的程序取消了。

Trap Flag

原理

上一篇介绍 Trap Flag 有提到它是 CPU 的其中一个 Flag,在 1(set) 时会触发 EXCEPTION_SINGLE_STEP,而 Debugger 会去处理这个 Exception。

因此我们可以利用这一点,故意设 Trap Flag 为 1(set),如果是在 Debugger 中,会因为 Exception 被 Debugger 处理而不会 Crash;但是如果程序是被正常的执行,则会因为产生的 Exception 而 Crash。

POC

程序专案可以参考我的 GitHub zeze-zeze/2021iThome

  • SetTrapFlag.asm
.CODE
SetTrapFlag PROC
    ; 把 Flag 们丢到 Stack 上
    pushf

    ; 设定 Trap Flag,位置在第 9 个 bit,所以是 0x100
    or dword ptr [rsp], 100h

    ; 把新的 Flag 从 Stack 拿给 Flag 们
    popf
    nop
    ret
SetTrapFlag ENDP
END
  • TrapFlagAntiDebug.cpp
#include <Windows.h>

extern "C" void SetTrapFlag();

int main(int argc, char* argv[]) {
    __try {
        // 如果是在 Debugger 中执行这个 Function,
        // 触发的 Exception 却没有被自己定义的意外处理方式处理,
        // 表示这个 Exception 已经被 Debugger 处理了,也就代表目前正在被 Debug
        SetTrapFlag();
        MessageBoxW(0, L"Detect", L"Debugger", 0);
    }
    __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP
        ? EXCEPTION_EXECUTE_HANDLER
        : EXCEPTION_CONTINUE_EXECUTION) {
        MessageBoxW(0, L"Not Detect", L"Debugger", 0);
    }
}

实际测试

正常执行的情况下,会跳出 Not Detect 的讯息框。如果想要弄出 Detect 的讯息框,就开 Debugger,然後要一步一步执行,所以得按 F7 跟进 SetTrapFlag 函数。

Hardware Breakpoint

原理

上一篇介绍 Hardware Breakpoint 有提到它是透过设定 DR0~DR3 达到断点的效果,所以只要在程序中检查这四个暂存器有没有被使用就可以侦测 Hardware Breakpoint。

具体实作方式可以使用 GetThreadContext 函数,取得的 Context 结构中就有暂存器的资讯。

POC

程序专案可以参考我的 GitHub zeze-zeze/2021iThome

#include <Windows.h>

int main(int argc, char* argv[]) {
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

    // 取得 Context 结构
    if (!GetThreadContext(GetCurrentThread(), &ctx))
        return false;

    // 确认 DR0~DR3 有没有被设定,有非 0 值代表有 Hardware Breakpoint
    if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {
        MessageBoxW(0, L"Detect", L"Hardware Breakpoint", 0);
    }
    else {
        MessageBoxW(0, L"Not Detect", L"Hardware Breakpoint", 0);
    }
}

实际测试

正常执行会跳出 Not Detect 的讯息框;开启 Debugger 设定硬体断点後执行,会跳出 Detect 的讯息框。

参考资料


<<:  Day 19【ERC-721】用兵之道在一个奇字,2999兵分2999路

>>:  Day 17. 常见模板 Template DB MySQL by Zabbix agent 介绍

【Day 28】 服务器监控 on AWS

那麽在先前实作中,我们业已将 WordPress 网站建筑在 AWS 环境中(可以详【Day 05】...

第二十五天:用 dokka 产生 API 文件

当我们在写函式库或框架的时候,通常表示这段逻辑很常用到,希望藉由抽取成函式库或框架来重复使用,减少重...

Day_23: 让 Vite 来开启你的Vue 之 <script setup>

Hi Dai Gei Ho~ 我是 Winnie ~ 在今天文章中,我们要来说说 Compositi...

Day 30: DevOps完赛心得

最後,不免俗的还是要说一下完赛感言: 今年是第一次参赛,参赛完的感言是囤文章很重要!! 其实我对後面...