#逆向-反调试篇
🤡5ea1
前段时间一直在研究如何防止静态分析,但是精心策划的花指令和smc总是被做题的人简简单单两下调试就出来了,因为给题目配的反调试总是随便抄两个api下来,导致反调试的绕过变得极其容易。为了守护我的花指令,我决心学习一下反调试技术。
Debuger1:API反调试
最简单朴素的反调试,功能实现全靠windows自带能力
Debuger1-1
调用IsDebuggerPresent()
函数
如果当前进程在调试器的上下文中运行,则返回值为非零。
如果当前进程未在调试器的上下文中运行,则返回值为零。
BOOL CheckDebuger()
{
int Debuger;
Debuger=IsDebuggerPresent();
return Debuger;
}
最简单检测是否调试的函数,返回值直接可以用来判断是否被调试
Debuger1-2
CheckRemoteDebuggerPresent
函数,使用方法稍微复杂一些,但还是较为直接简单
该函数需要两个参数,第一个参数为检测进程的句柄,第二个参数为存放返回值的位置,此处选择使用GetCurrentProcess()函数来获取当前进程的句柄
BOOL CheckDebuger()
{
int Debuger;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &Debuger);
return Debuger;
}
运行后,返回值会存放在Debuger变量中,若为0,则没有被调试。
Debuger1-3
此次将用到NtQueryInformationProcess
函数,这是Ntdll.dll
中的一个API,用来提取一个给定进程的信息
它的第一个参数是进程句柄,第二个参数告诉我们它需要提取进程信息的类型。为第二个参数指定特定值并调用该函数,相关信息就会设置到第三个参数。
它的第二个参数是一个枚举参数,即可根据该参数的不同,更改查找的数据,并对输出数据格式发生变化,所以要依据参数的不同来选择需要的输出数据类型,该枚举数据的部分类别如下图所示
具体调用代码如下
typedef NTSTATUS(WINAPI* NtQueryInformationProcessPtr)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
BOOL CheckDebug_ProcessDebugPort()
{
DWORD_PTR debugPort = 0;
HMODULE hModule = LoadLibrary(L"Ntdll.dll");
if (hModule == NULL) {
// 获取错误代码并处理加载失败情况,比如返回FALSE等
DWORD errorCode = GetLastError();
printf("%d\n", errorCode);
return FALSE;
}
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
NTSTATUS status = NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debugPort, sizeof(debugPort), NULL);
if (!NT_SUCCESS(status)) {
// 根据不同的错误状态码(status的值)进行相应的错误处理,比如输出错误信息、记录日志等
printf("NtQueryInformationProcess failed with status code: 0x%X\n", status);
return FALSE;
}
return debugPort != 0;
}
调用此函数应当先对NTSTATUS
进行定义,以免出现未定义的标识符错误。
Debuger1-4
GetLastError()
函数的作用是获取最后一次的错误信息,所以我们可以通过故意犯错来构造异常信息。CloseHandle()
函数的作用是关闭给定句柄对应的进程,所以我们只需要给定一个不存在的句柄,就能稳定触发异常;
CloseWindow
也是同样的原理
BOOL CheckDebug()
{
DWORD ret = CloseHandle((HANDLE)0x1234);
if (ret != 0 || GetLastError() != ERROR_INVALID_HANDLE)
{
return TRUE;
}
else
{
return FALSE;
}
}
BOOL CheckDebug()
{
DWORD ret = CloseWindow((HWND)0x1234);
if (ret != 0 || GetLastError() != ERROR_INVALID_WINDOW_HANDLE)
{
return TRUE;
}
else
{
return FALSE;
}
}
当正常运行时,ret处的语句产生的异常信息会被下方的if语句检测到,然后返回False,而在调试时,异常信息会直接终止调试,达到一个反调试的效果。
Debuger2:调试器与栈结构
虽然使用Windows API是探测调试器存在的最简单办法,但手动检查数据结构是恶意代码编写者最常使用的办法。这是因为很多时候通过Windows API实现的反调试技术无效,例如这些API函数被rootkit挂钩,并返回错误信息。因此,恶意代码编写者经常手动执行与这些API功能相同的操作。在手动检测中,PEB结构中的一些标志暴露了调试器存在的信息。
Debuger2-1
在Windows系统中,当进程运行时,位置fs:[30h]
将指向PEB的基地址,而PEB块将包含与这个进程相关的所有用户态参数。这些参数也包含了调试器的状态。
为了实现反调试技术,恶意代码通过fs:[30h]
这个位置检查BeingDebugged标志,这个标志标识进程是否正在被调试。
BOOL CheckDebug()
{
int result = 0;
__asm
{
mov eax, fs:[30h]
mov al, BYTE PTR [eax + 2]
mov result, al
}
return result != 0;
}
上面这段代码就能借助内联汇编读取PEB中关于调试器的标志位,从而得知进程是否正在被调试

可以从这张图中看到,BeingDebugged的标志位处于第三个字节处,故在调用时使用PTR [eax + 2]
地址
Debuger3:注册表检测
下面是调试器在注册表中的一个常用位置。
SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug(32位系统)
SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\AeDebug(64位系统)
该注册表项指定当应用程序发生错误时,触发哪一个调试器。默认情况下,它被设置为Dr.Watson。如果该这册表的键值被修改为OllyDbg,则恶意代码就可能确定它正在被调试。
BOOL CheckDebug()
{
BOOL is_64;
IsWow64Process(GetCurrentProcess(), &is_64);
HKEY hkey = NULL;
char key[] = "Debugger";
char reg_dir_32bit[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug";
char reg_dir_64bit[] = "SOFTWARE\\Wow6432Node\\Microsoft\\WindowsNT\\CurrentVersion\\AeDebug";
DWORD ret = 0;
if (is_64)
{
ret = RegCreateKeyA(HKEY_LOCAL_MACHINE, reg_dir_64bit, &hkey);
}
else
{
ret = RegCreateKeyA(HKEY_LOCAL_MACHINE, reg_dir_32bit, &hkey);
}
if (ret != ERROR_SUCCESS)
{
return FALSE;
}
char tmp[256];
DWORD len = 256;
DWORD type;
ret = RegQueryValueExA(hkey, key, NULL, &type, (LPBYTE)tmp, &len);
std::cout << tmp<<'\n';
if (strstr(tmp, "OllyIce") != NULL || strstr(tmp, "OllyDBG") != NULL || strstr(tmp, "WinDbg") != NULL || strstr(tmp, "x64dbg") != NULL || strstr(tmp, "Immunity") != NULL || strstr(tmp, "Immunity") != NULL)
{
return TRUE;
}
else
{
return FALSE;
}
}
不过这个代码在我本机上跑的时候始终检测的是vs2022的调试器,可能在环境上有一定的要求