反调试与反反调试
什么是反调试? 什么是反反调试?
静态反调试
-
特点:一般在调试开始时阻拦调试者,调试只需要找到原因后就可以一次性突破
-
PEB
⭐️BeginDebug
: 调试标志位
// 通过检查 PEB.BeingDebuged 字段判断是否被调试 // 可以在目标程序运行之前修改对应的字段为 0 进行反反调试 bool CheckBeingDebugged() { __asm { ; 获取到 PEB 的内容 mov eax, fs:[0x30] ; 获取 PEB 内偏移为 2 大小为 1 字节的字段 movzx eax, byte ptr[eax + 2] } } int main() { if (CheckBeingDebugged()) printf("当前处于[被]调试状态 "); else printf("当前处于[非]调试状态 "); system("pause"); return 0; }
// 使用 IsDebuggerPresent 原理同样是判断 PEB.BeingDebuged // 通过 HOOK 函数和修改对应字段的方式可以进行反调试 int main() { if (IsDebuggerPresent()) printf("当前处于[被]调试状态 "); else printf("当前处于[非]调试状态 "); system("pause"); return 0; }
Ldr
内存状态Heap
(Flags
,Force Flags
): 堆状态NtGlobalFlag
: 内核全局标记- 通过
PEB.NtGlobalFlag
判断是否被调试,当处于被调试状态时PEB.NtGlobalFlag
保存的是0x70
,可以通过修改标志反反调试
- 通过
bool CheckNtGlobalFlag() { __asm { ; 获取到 PEB, 保存在 FS :[0x30] mov eax, fs : [0x30] ;获取到 NtGlobalFlag 字段的内容 mov eax, [eax + 0x68] } } int main() { if (CheckNtGlobalFlag()) printf("当前处于[被]调试状态 "); else printf("当前处于[非]调试状态 "); system("pause"); return 0; }
-
TEB
StaticUnicodeString
:静态缓冲区
-
使用原始
API
NtQuerySystemInformationProcess()
ProcessDebugPort(0x07)
: 获取调试端口ProcessDebugObjectHandle(0x1E)
: 获取调试句柄ProcessDebugFlag(0x1F)
: 获取调试标记
NtQuerySystemInformation()
:SystemKernelDeBuggerInformation(0x23)
:获取系统调试状态 (双机)
NtQueryObject()
: 遍历系统内核
-
攻击调试器
NtSetInformationThread()
ThreadHideFormDebugger(0x11)
-
打开进程检查
SetDebugPrivilege
➡️ 检查进程是否具有调试权限
-
利用
TLS
回调函数 -
使用普通
API
- 父进程的检查
- 如果一个进程被正常打开,那么它的父进程应该是
Explorer.exe
,也就是资源管理器,如果判断不是,就可能被调试,提供参考。
- 如果一个进程被正常打开,那么它的父进程应该是
bool CheckParentProcess() { struct PROCESS_BASIC_INFORMATION { ULONG ExitStatus; // 进程返回码 PPEB PebBaseAddress; // PEB 地址 ULONG AffinityMask; // CPU 亲和性掩码 LONG BasePriority; // 基本优先级 ULONG UniqueProcessId; // 本进程PID ULONG InheritedFromUniqueProcessId; // 父进程PID }stcProcInfo; // 查询到目标进程的对应信息,主要是 父进程 ID NtQueryInformationProcess( GetCurrentProcess(), ProcessBasicInformation, &stcProcInfo, sizeof(stcProcInfo), NULL); // 查询资源管理器对应的 PID DWORD ExplorerPID = 0; DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId; GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID); // 比对两个 PID 的值,相同就OK,不同就可能被调试 return ExplorerPID == CurrentPID ? false : true; } int main() { if (CheckParentProcess()) printf("当前处于[被]调试状态 "); else printf("当前处于[非]调试状态 "); system("pause"); return 0; }
- 窗口名检查
- 原理是通过查找窗口类或窗口名称对应的窗口是否存在来进行调试器或其它分析工具的检查。[缺点是窗口的的名字通常不是固定的,检查起来非常的不方便]
- 还可以通过遍历进程的方式来检查调试器或其它工具是否存在。[缺点是可以通过修改 exe 的名字修改进程名]
int main() { if (FindWindow(NULL, L"OllyDbg")) printf("存在调试器 "); else printf("没检测到调试器 "); return 0; }
- 进程名检查
- 原理就是查询
EPROCESS
结构体中相关的字段,因为不能在R3
修改R0
的数据,所以只能HOOK
- 原理就是查询
bool CheckProcessDebugPort() { int nDebugPort = 0; NtQueryInformationProcess( GetCurrentProcess(), // 目标进程句柄 ProcessDebugPort, // 查询信息类型(7) &nDebugPort, // 输出查询信息 sizeof(nDebugPort), // 查询类型大小 NULL); // 实际返回数据大小 // 如果返回的是 -1 ,那么就被调试了 return nDebugPort == 0xFFFFFFFF ? true : false; } int main() { if (CheckProcessDebugPort()) printf("当前处于[被]调试状态 "); else printf("当前处于[非]调试状态 "); system("pause"); return 0; }
- 文件名及文件路径检查
- 注册表检查
- 。。。
- 父进程的检查
动态反调试
- 特点:一般在调试的过程中阻拦调试者,可在调试的过程中被频繁触发因此需要调试者随时关注
- 使用
SEH
- 异常
- 断点
SetUnhandleedExceptionFilter()
- 时间检查
- 单步检查
- 补丁检查
- 反反汇编
- 偷取代码
- 分页保护
- 壳
- 虚拟机
OllyDbg
插件编写
- 插件通常是以什么样的方式存在的?
通常以DLL
的方式存在的,但是后缀名可能会有变化
- 应用程序如何找到所有的插件?
所有的插件都应该被保护到具体的路径中
- 应用程序怎样知道是否符合自己的要求?
- 插件必须提供指定的名称的函数说明自己的信息,插件还需要通过对应名称的函数提供具体的功能