0x1 反调试
- 所有的反调试手段都是以函数的方式封装好的
TRUE
:表示检测到调试
0x1.1 IsDebuggerPresent
BOOL
IsDebuggerPresentAPI (
VOID
)
{
return IsDebuggerPresent();
}
IsDebuggerPresent
这是windows提供的api- 本质上就是返回
PEB->BeingDebugged
0x1.2 PEB 检测
PEB
是windows准备的一个结构体- 用于保存当前程序的运行信息
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
BOOL
IsDebuggerPresentPEB(
VOID
)
{
#if defined (ENV64BIT)
PPEB pPeb = (PPEB)__readgsqword(0x60);
#elif defined(ENV32BIT)
PPEB pPeb = (PPEB)__readfsdword(0x30);
#endif
return pPeb->BeingDebugged == 1;
}
__readgsqword
:读取gs:[]
中的数据__readfsdword
:读取fs:[]
中的数据- 32位架构下的
peb
在FS:[0x30]
- 64位架构下的
peb
在GS:[0x60]
0x1.3 CheckRemoteDebuggerPresent
- windows提供的一个用于检测其他程序是否被调试的api
- 也可以用于检测自己
BOOL
CheckRemoteDebuggerPresentAPI (
VOID
)
{
BOOL bIsDbgPresent = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &bIsDbgPresent);
return bIsDbgPresent;
}
- 本质上是调用
NTDLL
的导出函数NtQueryInformationProcess
NtQueryInformationProcess
中设置PROCESSINFOCLASS
为7
(ProcessDebugPort)
0x1.4 NtSetInformationThread
- 这是windows提供的设置程序运行信息的api
- 可以过
SharpOD
如下图设置仍然可以检测到
- 检测代码
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS *PNTSTATUS;
typedef NTSTATUS(WINAPI *pNtSetInformationThread)(HANDLE, UINT, PVOID, ULONG);
enum API_IDENTIFIER
{
API_CsrGetProcessId,
API_EnumSystemFirmwareTables,
API_GetActiveProcessorCount,
API_GetSystemFirmwareTable,
API_GetNativeSystemInfo,
API_GetProductInfo,
API_EnumProcessModulesEx_Kernel,
API_EnumProcessModulesEx_PSAPI,
API_IsWow64Process,
API_LdrEnumerateLoadedModules,
API_NtClose,
API_NtCreateDebugObject,
API_NtDelayExecution,
API_NtOpenDirectoryObject,
API_NtQueryInformationThread,
API_NtQueryInformationProcess,
API_NtQueryLicenseValue,
API_NtQueryDirectoryObject,
API_NtQueryObject,
API_NtQuerySystemInformation,
API_NtSetInformationThread,
API_NtWow64QueryInformationProcess64,
API_NtWow64QueryVirtualMemory64,
API_NtWow64ReadVirtualMemory64,
API_NtYieldExecution,
API_RtlGetVersion,
API_RtlInitUnicodeString,
API_WudfIsAnyDebuggerPresent,
API_WudfIsKernelDebuggerPresent,
API_WudfIsUserDebuggerPresent,
};
class API
{
private:
static bool ShouldFunctionExistOnCurrentPlatform(API_OS_BITS bits, API_OS_VERSION minVersion, API_OS_VERSION removedInVersion);
public:
static void Init();
static void PrintAvailabilityReport();
static bool IsAvailable(API_IDENTIFIER api);
static void* GetAPI(API_IDENTIFIER api);
};
#ifndef STATUS_INFO_LENGTH_MISMATCH
#define STATUS_INFO_LENGTH_MISMATCH ((DWORD)0xC0000004L)
#endif
#ifndef STATUS_DATATYPE_MISALIGNMENT
#define STATUS_DATATYPE_MISALIGNMENT ((DWORD)0x80000002L)
#endif
BOOL NtSetInformationThread_ThreadHideFromDebugger()
{
struct AlignedBool
{
alignas(4) bool Value;
};
const int ThreadHideFromDebugger = 0x11;
auto NtSetInformationThread = static_cast<pNtSetInformationThread>(API::GetAPI(API_IDENTIFIER::API_NtSetInformationThread));
auto NtQueryInformationThread = static_cast<pNtQueryInformationThread>(API::GetAPI(API_IDENTIFIER::API_NtQueryInformationThread));
NTSTATUS Status;
bool doQITcheck = false;
if (API::IsAvailable(API_IDENTIFIER::API_NtQueryInformationThread))
{
doQITcheck = IsWindowsVistaOrGreater();
}
AlignedBool isThreadHidden;
isThreadHidden.Value = false;
Status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &isThreadHidden, 12345);
if (Status == 0)
return TRUE;
Status = NtSetInformationThread((HANDLE)0xFFFF, ThreadHideFromDebugger, NULL, 0);
if (Status == 0)
return TRUE;
Status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);
if (Status == 0)
{
if (doQITcheck)
{
Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &isThreadHidden.Value, sizeof(bool), NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
return TRUE;
}
if (Status == 0)
{
AlignedBool bogusIsThreadHidden;
bogusIsThreadHidden.Value = false;
Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &bogusIsThreadHidden.Value, sizeof(BOOL), NULL);
if (Status != STATUS_INFO_LENGTH_MISMATCH)
{
return TRUE;
}
const size_t UnalignedCheckCount = 8;
bool bogusUnalignedValues[UnalignedCheckCount];
int alignmentErrorCount = 0;
#if _WIN64
const size_t MaxAlignmentCheckSuccessCount = 2;
#else
const size_t MaxAlignmentCheckSuccessCount = 4;
#endif
for (size_t i = 0; i < UnalignedCheckCount; i++)
{
Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &(bogusUnalignedValues[i]), sizeof(BOOL), NULL);
if (Status == STATUS_DATATYPE_MISALIGNMENT)
{
alignmentErrorCount++;
}
}
if (UnalignedCheckCount - MaxAlignmentCheckSuccessCount > alignmentErrorCount)
{
return TRUE;
}
return isThreadHidden.Value ? FALSE : TRUE;
}
}
}
else
{
return TRUE;
}
return FALSE;
}
原理:
- 调用
NtSetInformationThread
会导致ThreadInformationClass=0x11
(ThreadHideFromDebugger) - 将
hThread=NULL
传递给NtSetInformationThread
会导致调用的线程被隐藏,同时导致调试器接收不到调试事件 - 这个程序同样会检查
ThreadHideFromDebugger
是否被封锁
0x1.5 JobObject
- 核心:一般情况下,主进程在主线程中启动核心代码
QueryInformationJobObject
这个api可以获取当前程序所有的进程列表不论是主进程还是主线程,他们的
ImageFileName
应该是都是源程序的文件名filename.exe
控制台程序会有一个特殊的父进程
\\Windows\\System32\\conhost.exe
- 原理:查询当前程序的进程列表,通过进程id反查程序文件名.只要有进程id不匹配就记为异常
核心代码
BOOL ProcessJob()
{
BOOL foundProblem = FALSE;
DWORD jobProcessStructSize = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + sizeof(ULONG_PTR) * 1024;
JOBOBJECT_BASIC_PROCESS_ID_LIST* jobProcessIdList = static_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(malloc(jobProcessStructSize));
if (jobProcessIdList) {
SecureZeroMemory(jobProcessIdList, jobProcessStructSize);
jobProcessIdList->NumberOfProcessIdsInList = 1024;
//获取进程列表数组
if (QueryInformationJobObject(NULL, JobObjectBasicProcessIdList, jobProcessIdList, jobProcessStructSize, NULL))
{
int ok_processes = 0;
//穷举进程列表
for (DWORD i = 0; i < jobProcessIdList->NumberOfAssignedProcesses; i++)
{
ULONG_PTR processId = jobProcessIdList->ProcessIdList[i];
// 匹配到主进程,合法
if (processId == (ULONG_PTR)GetCurrentProcessId())
{
ok_processes++;
}
// 检查其他的进程是否是 白名单程序
else
{
HANDLE hJobProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, (DWORD)processId);
if (hJobProcess != NULL)
{
const int processNameBufferSize = 4096;
LPTSTR processName = static_cast<LPTSTR>(malloc(sizeof(TCHAR) * processNameBufferSize));
if (processName) {
SecureZeroMemory(processName, sizeof(TCHAR) * processNameBufferSize);
// 根据进程id查询进程名称,一般是exe的文件名
if (GetProcessImageFileName(hJobProcess, processName, processNameBufferSize) > 0)
{
String pnStr(processName);
// 忽略控制台进程
if (pnStr.find(String(L"\\Windows\\System32\\conhost.exe")) != std::string::npos)
{
ok_processes++;
}
}
free(processName);
}
CloseHandle(hJobProcess);
}
}
}
// 进程列表中只要有一个不合法就记为异常
foundProblem = ok_processes != jobProcessIdList->NumberOfAssignedProcesses;
}
free(jobProcessIdList);
}
return foundProblem;
}
- 可以过
SharpOD
如下图设置仍然可以检测到
0x1.6 检查硬件断点
- 直接检查寄存器,不多说
BOOL HardwareBreakpoints()
{
BOOL bResult = FALSE;
// 寄存器结构体
PCONTEXT ctx = PCONTEXT(VirtualAlloc(NULL, sizeof(CONTEXT), MEM_COMMIT, PAGE_READWRITE));
if (ctx) {
SecureZeroMemory(ctx, sizeof(CONTEXT));
// 设置属性:希望获取寄存器
ctx->ContextFlags = CONTEXT_DEBUG_REGISTERS;
// 获取寄存器
if (GetThreadContext(GetCurrentThread(), ctx)) {
// 检查寄存器/硬件断点
if (ctx->Dr0 != 0 || ctx->Dr1 != 0 || ctx->Dr2 != 0 || ctx->Dr3 != 0)
bResult = TRUE;
}
VirtualFree(ctx, 0, MEM_RELEASE);
}
return bResult;
}
0x1.7 检查软件断点
int3
和int 3
都是软件断点int3
->0xcc
是处理器的硬件指令int 3
->0x cd 03
是windows的中断实现- 扫描有没有这些特殊指令即可
// 模拟被保护的函数
VOID My_Critical_Function()
{
int a = 1;
int b = 2;
int c = a + b;
_tprintf(_T("I am critical function, you should protect against int3 bps %d"), c);
}
/*
这个函数需要跟在 被保护函数的后面
用来计算扫描长度
*/
VOID Myfunction_Adresss_Next()
{
My_Critical_Function();
};
BOOL SoftwareBreakpoints()
{
// 计算长度
size_t sSizeToCheck = (size_t)(Myfunction_Adresss_Next)-(size_t)(My_Critical_Function);
// 获取检查的起始地址
PUCHAR Critical_Function = (PUCHAR)My_Critical_Function;
for (size_t i = 0; i < sSizeToCheck; i++) {
// 这里可以用 亦或 检查
// 0xCC xor 0x55 = 0x99
if (Critical_Function[i] == 0xCC)
return TRUE;
}
return FALSE;
}