反调试浅析
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习),本文仅作技术研究。 IsDebuggerPresent 具体链接:https://learn.microsoft.com/zh-cn/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent 该函数可以用
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习),本文仅作技术研究。
IsDebuggerPresent
具体链接:https://learn.microsoft.com/zh-cn/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent
该函数可以用于确定是否正在由用户模式调试器调试调用进程。
#include
BOOL IsDebug() {
return IsDebuggerPresent();
}
int main() {
if (IsDebug()) {
MessageBox(NULL, "Debug", "Debug", NULL);
return 1;
}
else
{
MessageBox(NULL, "Not", "Not", NULL);
MessageBox(NULL, "Hello", "Hello", NULL);
}
}

NtCurrentPeb()
IsDebuggerPresent函数是调用NtCurrentPeb()返回当前线程的Peb的指针,然后调用BeingDebugged来判断,当前进程是否处于调式模式

通过windbg也可以看到

#include <windows.h>
#include <iostream>
#include "ntdll.h"
using namespace std;
BOOL WINAPI lIsDebuggerPresent(VOID)
{
return (BOOL)NtCurrentPeb()->BeingDebugged;
}
int main() {
if (lIsDebuggerPresent()) {
cout << "Is Debug Now" << endl;
return 1;
}
else
{
cout << "Not Debug" << endl;
}
}
除了使用NtCurrentPeb()去获取Peb,我们也可以直接从寄存器去读取
#include <windows.h>
#include <iostream>
#include "ntdll.h"
using namespace std;
BOOL lBeingDebugged() {
//获取PEB 地址
#if defined(_WIN64)
PEB* pPEB = (PEB*)__readgsqword(0x60);
#else
PEB* pPEB = (PEB*)__readfsdword(0x30);
#endif
BOOL Flag = pPEB->BeingDebugged;
return Flag;
}
int main() {
if (lBeingDebugged()) {
cout << "Is Debug Now" << endl;
return 1;
}
else
{
cout << "Not Debug" << endl;
}
}
CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent函数可以判断指定的进程是否处于调试状态
#include <windows.h>
#include <iostream>
using namespace std;
int main() {
BOOL pbDebuggerPresent;
BOOL Check = CheckRemoteDebuggerPresent(GetCurrentProcess(), &pbDebuggerPresent);
if (!Check) {
cout << "CheckRemoteDebuggerPresent Error" << GetLastError() << endl;
return 1;
}
cout << "CheckRemoteDebuggerPresent Success" << endl;
if (pbDebuggerPresent) {
cout << "Is Debug Now" << endl;
return 1;
}
else
{
cout << "Not Debug" << endl;
cout << "Hello" << endl;
}
}

NtQueryInformationProcess
通过在reactos查看,可以看到CheckRemoteDebuggerPresent函数的实现方式,

可以看到主要就是调用的NtQueryInformationProcess这个函数去实现,好的直接复制过来吧
#include <windows.h>
#include <iostream>
#include "ntdll.h"
#pragma comment(lib, "ntdll.lib")
using namespace std;
DWORD lBaseSetLastNTError(IN NTSTATUS Status)
{
DWORD dwErrCode;
dwErrCode = RtlNtStatusToDosError(Status);
SetLastError(dwErrCode);
return dwErrCode;
}
BOOL WINAPI lCheckRemoteDebuggerPresent(IN HANDLE hProcess, OUT PBOOL pbDebuggerPresent)
{
HANDLE DebugPort;
NTSTATUS Status;
/* Make sure we have an output and process*/
if (!(pbDebuggerPresent) || !(hProcess))
{
/* Fail */
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
/* Check if the process has a debug object/port */
Status = NtQueryInformationProcess(hProcess,ProcessDebugPort,&DebugPort,sizeof(DebugPort),NULL);
if (NT_SUCCESS(Status))
{
/* Return the current state */
*pbDebuggerPresent = DebugPort != NULL;
return TRUE;
}
lBaseSetLastNTError(Status);
return FALSE;
}
int main() {
BOOL pbDebuggerPresent;
BOOL Check = lCheckRemoteDebuggerPresent(GetCurrentProcess(), &pbDebuggerPresent);
if (!Check) {
cout << "CheckRemoteDebuggerPresent Error" << GetLastError() << endl;
return 1;
}
cout << "CheckRemoteDebuggerPresent Success" << endl;
if (pbDebuggerPresent) {
cout << "Is Debug Now" << endl;
return 1;
}
else
{
cout << "Not Debug" << endl;
cout << "Hello" << endl;
}
}
NtGlobalFlag
NtGlobalFlag位于PEB中

#include <windows.h>
#include <iostream>
#include "ntdll.h"
using namespace std;
BOOL lNtGlobalFlag() {
//获取PEB 地址
#if defined(_WIN64)
PEB* pPEB = (PEB*)__readgsqword(0x60);
#else
PEB* pPEB = (PEB*)__readfsdword(0x30);
#endif
DWORD Flag = pPEB->NtGlobalFlag;
if (Flag == 0x70) {
return TRUE;
}
return FALSE;
}
int main() {
if (lNtGlobalFlag()) {
cout << "Is Debug Now" << endl;
return 1;
}
else
{
cout << "Not Debug" << endl;
cout << "Hello" << endl;
}
}
当程序处于调试状态下的时候,其返回值是0x70

父进程检测
一般情况下,新建的进程(除开服务进程)其父进程基本都是explorer.exe,这里以记事本为例子

当程序用调试器启动的时候,其父进程就是调试器的进程

我们可以利用NtQueryInformationProcess来判断当前进程的父进程是否是explorer.exe,以此来判断是否在被调试状态
主要就是利用以下这个结构体
typedef struct _PROCESS_BASIC_INFORMATION
{
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
- UniqueProcessId:本进程pid
- InheritedFromUniqueProcessId:父进程pid
拿到父进程的pid,根据pid获得父进程的句柄,获得父进程的进程名,然后再判断是否为explorer.exe进程
#include <windows.h>
#include <iostream>
#include "ntdll.h"
#pragma comment(lib, "ntdll.lib")
using namespace std;
int main() {
PROCESS_BASIC_INFORMATION Pro;
NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &Pro, sizeof(Pro), NULL);
HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)Pro.InheritedFromUniqueProcessId);
wchar_t path[MAX_PATH];
DWORD maxpath = MAX_PATH;
QueryFullProcessImageNameW(Handle, 0, path, &maxpath);
if (wcsstr(path, L"explorer.exe") != NULL) {
cout << "Not Debug" << endl;
}
else
{
cout << "Is Debug Now" << endl;
return 1;
}
system("pause");
}

TLS反调试
Thread Local Storage(TLS),即线程本地存储,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS回调函数会在main函数之前执行,因此我们可以利用此来进行相关的反调试。
#include<Windows.h>
#include <stdio.h>
void NTAPI __stdcall TLS_CALLBACK(PVOID Dllhandle, DWORD Reason, PVOID Reserved);
//编译器声明使用TLS
#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback")
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:__tls_callback")
#endif
//注册TLS回调函数
#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK _tls_callback[] = { TLS_CALLBACK, 0 };
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif
//定义TLS回调函数
void NTAPI __stdcall TLS_CALLBACK(PVOID Dllhandle, DWORD Reason, PVOID Reserved) {
printf("TLS\n");
}
DWORD WINAPI ThreadF(LPVOID param) {
while (true) {
printf("Thread Running\n");
Sleep(200);
}
return 0;
}
int main() {
CreateThread(0, 0, ThreadF, 0, 0, 0);
while (true)
{
Sleep(-1);
}
}
程序运行后如下,可以看见TLS回调函数在main函数之前就运行。

反调试实现,利用IsDebuggerPresent()检测是否正在调试程序,如果是则结束进程
#include<Windows.h>
#include <stdio.h>
void NTAPI __stdcall TLS_CALLBACK(PVOID Dllhandle, DWORD Reason, PVOID Reserved);
//编译器声明使用TLS
#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback")
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:__tls_callback")
#endif
//注册TLS回调函数
#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK _tls_callback[] = { TLS_CALLBACK, 0 };
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif
//定义TLS回调函数
void NTAPI __stdcall TLS_CALLBACK(PVOID Dllhandle, DWORD Reason, PVOID Reserved) {
if (IsDebuggerPresent()) {
MessageBox(NULL, "Debug", "Debug", NULL);
ExitProcess(0);
}
else
{
MessageBox(NULL, "No Debug", "No Debug", NULL);
}
}
int main() {
MessageBox(NULL, "Hello", "Hello", NULL);
}

但是使用PE bear查看时可以看见很明显的TLS

NtSetInformationThread
如果程序正在被调试,则强制将自己从调试器里分离出来。其中的ThreadHideFromDebugger标志,如果为线程设置了此标志,则该线程将停止发送有关调试事件的通知。
#include <windows.h>
#include <iostream>
#include "ntdll.h"
using namespace std;
void main()
{
NTSTATUS status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);
cout << "Hello" << endl;
}
调试信息
有两种异常类型:DBG_PRINTEXCEPTION_C (0x40010006)和 DBG_PRINTEXCEPTION_W(0x4001000A)它们可用于程序是否处于调试状态。
#include <windows.h>
#include <iostream>
using namespace std;
#define DBG_PRINTEXCEPTION_WIDE_C 0x4001000A
int main() {
WCHAR* outputString = (WCHAR*)"Any text";
ULONG_PTR args[4] = { 0 };
args[0] = (ULONG_PTR)wcslen(outputString) + 1;
args[1] = (ULONG_PTR)outputString;
__try
{
RaiseException(DBG_PRINTEXCEPTION_WIDE_C, 0, 4, args);
MessageBox(NULL, "Debugging", "Debugging", MB_OK);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
MessageBox(NULL, "Hello", "Hello", MB_OK);
}
}
