《逆向工程核心原理》学习笔记:反调试技术
Posted 思源湖的鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《逆向工程核心原理》学习笔记:反调试技术相关的知识,希望对你有一定的参考价值。
目录
前言
继续学习《逆向工程核心原理》,本篇笔记是第七部分:反调试技术,包括一些静态反调试技术和动态反调试技术
值得注意的是,这本书距今有将近10年了,故这里这些都是比较老的东西了
一、反调试技术概况
反调试技术对调试器和OS有很强的的依赖性,分类如下所示:
二、静态反调试技术
静态反调试技术主要是通过一些API探测调试器,并使程序无法运行
1、PEB
PEB结构体信息可以判断进程是否处于调试状态
介绍具体可见:《逆向工程核心原理》学习笔记(六):高级逆向分析技术
几个用到的成员如下:
结构体成员 | 调试信息 | 破解之法 |
---|---|---|
BeingDebugged | 调试时值为1 | 用OD将其修改为0 即可 |
Ldr | 未使用的堆内存区域全部填充0xFEEEFEEE ,而Ldr 指向的_ PEB _ LDR _ DATA 结构体就在堆内存区域 | 将0xFEEEFEEE 都改写为NULL 即可 |
ProcessHeap | 指向Heap 结构体,非调试状态时,Heap 结构体的Flags 为0x2 ,ForceFlags 为0x0 ;调试时这两个值都会变 | 将这两个值改回0x2 和0x0 即可 |
NtGlobalFlag | 调试时值为0x70 | 用OD将其修改为0 即可 |
一个例子如下:
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void PEB()
HMODULE hMod = NULL;
FARPROC pProc = NULL;
LPBYTE pTEB = NULL;
LPBYTE pPEB = NULL;
BOOL bIsDebugging = FALSE;
// IsDebuggerPresent()
bIsDebugging = IsDebuggerPresent();
printf("IsDebuggerPresent() = %d\\n", bIsDebugging);
if( bIsDebugging ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
// Ldr
pProc = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCurrentTeb");
pTEB = (LPBYTE)(*pProc)(); // address of TEB
pPEB = (LPBYTE)*(LPDWORD)(pTEB+0x30); // address of PEB
printf("PEB.Ldr\\n");
DWORD pLdrSig[4] = 0xEEFEEEFE, 0xEEFEEEFE, 0xEEFEEEFE, 0xEEFEEEFE ;
LPBYTE pLdr = (LPBYTE)*(LPDWORD)(pPEB+0xC);
__try
while( TRUE )
if( !memcmp(pLdr, pLdrSig, sizeof(pLdrSig)) )
printf(" => Debugging!!!\\n\\n");
break;
pLdr++;
__except (EXCEPTION_EXECUTE_HANDLER)
printf(" => Not debugging...\\n\\n");
// Process Heap - Flags
bIsDebugging = FALSE;
LPBYTE pHeap = (LPBYTE)*(LPDWORD)(pPEB+0x18);
DWORD dwFlags = *(LPDWORD)(pHeap+0xC);
printf("PEB.ProcessHeap.Flags = 0x%X\\n", dwFlags);
if( dwFlags != 0x2 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
// Process Heap - ForceFlags
bIsDebugging = FALSE;
DWORD dwForceFlags = *(LPDWORD)(pHeap+0x10);
printf("PEB.ProcessHeap.ForceFlags = 0x%X\\n", dwForceFlags);
if( dwForceFlags != 0x0 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
// NtGlobalFlag
bIsDebugging = FALSE;
DWORD dwNtGlobalFlag = *(LPDWORD)(pPEB+0x68);
printf("PEB.NtGlobalFlag = 0x%X\\n", dwNtGlobalFlag);
if( (dwNtGlobalFlag & 0x70) == 0x70 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
int _tmain(int argc, TCHAR* argv[])
PEB();
printf("\\npress any key to quit...\\n");
_gettch();
return 0;
2、NtQueryInformationProcess()
NtQueryInformationProcess()
定义如下:
NTSYSAPI NTSTATUS NTAPI NtQueryInformationProcess (
IN HANDLE ProcessHandle, // 进程句柄
IN PROCESSINFOCLASS InformationClass, // 信息类型
OUT PVOID ProcessInformation, // 缓冲指针
IN ULONG ProcessInformationLength, // 以字节为单位的缓冲大小
OUT PULONG ReturnLength OPTIONAL // 写入缓冲的字节数
);
第二个参数PROCESSINFOCLASS
是枚举类型,其中与反调试有关的成员有三个:
ProcessDebugPort
(0x7
)ProcessDebugObjectHandle
(0x1E
)ProcessDebugFlags
(0x1F
)
(1)ProcessDebugPort(0x7)
进程处于调试状态时,操作系统会为他分配1个调试端口(Debug Port)
PROCESSINFOCLASS
设为ProcessDebugPort(0x07)
时,调用NtQueryInformationProcess()
函数就可以获取调试端口
- 若处于调试状态,
dwDebugPort
会被置为0xFFFFFFFF
- 若处于非调试状态,
dwDebugPort
值会被设置为0
(2)ProcessDebugObjectHandle(0x1E)
调试进程时,会生成一个调试对象(Debug Obiect)
NtQueryInformationProcess()
函数的第二个参数值PROCESSINFOCLASS
为ProcessDebugObjectHandle(0x1E)
时,函数的第三个参数就能获取到调试对象句柄
- 进程处于调试状态->调试句柄存在->返回值不为 NULL
- 处于非调试状态 , 返回值为 NULL
(3)ProcessDebugFlags(0x1F)
调试标志(Debug Flags) 的值也可以判断进程是否处于被调试状态
当 NtQueryInformationProcess()
第二个参数PROCESSINFOCLASS
为ProcessDebugFlags(0x1F)
时,第三个参数:
- 调试状态:
0
- 非调试状态:
1
(4)例子
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
enum PROCESSINFOCLASS
ProcessBasicInformation = 0,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort = 7,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessDebugObjectHandle = 30,
ProcessDebugFlags = 31,
SystemKernelDebuggerInformation = 35
;
void MyNtQueryInformationProcess()
typedef NTSTATUS (WINAPI *NTQUERYINFORMATIONPROCESS)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
NTQUERYINFORMATIONPROCESS pNtQueryInformationProcess = NULL;
pNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS)
GetProcAddress(GetModuleHandle(L"ntdll.dll"),
"NtQueryInformationProcess");
// ProcessDebugPort (0x7)
DWORD dwDebugPort = 0;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugPort,
&dwDebugPort,
sizeof(dwDebugPort),
NULL);
printf("NtQueryInformationProcess(ProcessDebugPort) = 0x%X\\n", dwDebugPort);
if( dwDebugPort != 0x0 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
// ProcessDebugObjectHandle (0x1E)
HANDLE hDebugObject = NULL;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugObjectHandle,
&hDebugObject,
sizeof(hDebugObject),
NULL);
printf("NtQueryInformationProcess(ProcessDebugObjectHandle) = 0x%X\\n", hDebugObject);
if( hDebugObject != 0x0 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
// ProcessDebugFlags (0x1F)
BOOL bDebugFlag = TRUE;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugFlags,
&bDebugFlag,
sizeof(bDebugFlag),
NULL);
printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\\n", bDebugFlag);
if( bDebugFlag == 0x0 ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
int _tmain(int argc, TCHAR* argv[])
MyNtQueryInformationProcess();
printf("\\npress any key to quit...\\n");
_gettch();
return 0;
(5)破解之法
对PROCESSINFOCLASS
的值进行操作
- 直接 HOOK API
- 修改返回值
- 利用 OD 插件 strong OD 中 KernelMode 可以绕过
3、NtQuerySystemInformation()
ntdll!NtQuerySystemInformation()
API是系统函数,用来获取当前运行的多种OS信息
NTSTATUS WINAPI NtQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, //指定需要的系统信息类型
_Inout_ PVOID SystemInformation, //结构体地址
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
SYSTEM_INFORMATION_CLASS
是枚举类型,如下:
调试状态下 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled
值为1
破解之法:
- Win XP 编辑boot.ini,删除
/debugport=com1 /baudrate=115200 /Debug
- Win 7 执行
bcdedit /debug off
可以参考下:NtQuerySystemInformation用法详解
一个例子如下:
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void MyNtQuerySystemInformation()
typedef NTSTATUS (WINAPI *NTQUERYSYSTEMINFORMATION)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)
GetProcAddress(GetModuleHandle(L"ntdll"),
"NtQuerySystemInformation");
ULONG SystemKernelDebuggerInformation = 0x23;
ULONG ulReturnedLength = 0;
SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = 0,;
NtQuerySystemInformation(SystemKernelDebuggerInformation,
(PVOID) &DebuggerInfo,
sizeof(DebuggerInfo), // 2 bytes
&ulReturnedLength);
printf("NtQuerySystemInformation(SystemKernelDebuggerInformation) = 0x%X 0x%X\\n",
DebuggerInfo.DebuggerEnabled, DebuggerInfo.DebuggerNotPresent);
if( DebuggerInfo.DebuggerEnabled ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
int _tmain(int argc, TCHAR* argv[])
MyNtQuerySystemInformation();
printf("\\npress any key to quit...\\n");
_gettch();
return 0;
4、NtQueryObject()
ntdll!NtQueryObject()
API用来获取各种内核对象的信息
NTSTATUS NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS objectInformationClass,
_Out_opt_ PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength
);
类似的,是第二个参数是枚举类型,相关信息的结构体指针返回到第三个参数
使用ObjectAllTypesInformation
获取系统所有对象信息,从中检测是否存在调试对象
破解之法:在调用 ntdll.ZwQueryObject()
API的CALL ESI
指令下断点,然后将栈中ObjectAllTypesInformation
的值改为0
一个例子:
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
typedef enum _OBJECT_INFORMATION_CLASS
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllTypesInformation,
ObjectHandleInformation
OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
void MyNtQueryObject()
typedef struct _LSA_UNICODE_STRING
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedef NTSTATUS (WINAPI *NTQUERYOBJECT)(
HANDLE Handle,
OBJECT_INFORMATION_CLASS ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);
#pragma pack(1)
typedef struct _OBJECT_TYPE_INFORMATION
UNICODE_STRING TypeName;
ULONG TotalNumberOfHandles;
ULONG TotalNumberOfObjects;
OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION
ULONG NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
#pragma pack()
POBJECT_ALL_INFORMATION pObjectAllInfo = NULL;
void *pBuf = NULL;
ULONG lSize = 0;
BOOL bDebugging = FALSE;
NTQUERYOBJECT pNtQueryObject = (NTQUERYOBJECT)
GetProcAddress(GetModuleHandle(L"ntdll.dll"),
"NtQueryObject");
// Get the size of the list
pNtQueryObject(NULL, ObjectAllTypesInformation, &lSize, sizeof(lSize), &lSize);
// Allocate list buffer
pBuf = VirtualAlloc(NULL, lSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// Get the actual list
pNtQueryObject((HANDLE)0xFFFFFFFF, ObjectAllTypesInformation, pBuf, lSize, NULL);
pObjectAllInfo = (POBJECT_ALL_INFORMATION)pBuf;
UCHAR *pObjInfoLocation = (UCHAR *)pObjectAllInfo->ObjectTypeInformation;
POBJECT_TYPE_INFORMATION pObjectTypeInfo = NULL;
for( UINT i = 0; i < pObjectAllInfo->NumberOfObjectsTypes; i++ )
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjInfoLocation;
if( wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0 )
bDebugging = (pObjectTypeInfo->TotalNumberOfObjects > 0) ? TRUE : FALSE;
break;
// calculate next struct
pObjInfoLocation = (UCHAR*)pObjectTypeInfo->TypeName.Buffer;
pObjInfoLocation += pObjectTypeInfo->TypeName.Length;
pObjInfoLocation = (UCHAR*)(((ULONG)pObjInfoLocation & 0xFFFFFFFC) + sizeof(ULONG));
if( pBuf )
VirtualFree(pBuf, 0, MEM_RELEASE);
printf("NtQueryObject(ObjectAllTypesInformation)\\n");
if( bDebugging ) printf(" => Debugging!!!\\n\\n");
else printf(" => Not debugging...\\n\\n");
int _tmain(int argc, TCHAR* argv[])
MyNtQueryObject();
printf("\\npress any key to quit...\\n");
_gettch();
return 0;
5、ZwSerInformationThread()
ZwSerInformationThread()
等同于 NtSetInformationThread
,通过将ThreadInformationClass
设置 ThreadHideFromDebugger(0x11)
,可以禁止线程产生调试事件。函数原型如下:
typedef enum _THREAD_INFORMATION_CLASS
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger // 17 (0x11)
THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI* ZWSETINFORMATIONTHREAD)(
HANDLE ThreadHandle, //接收当前线程的句柄
THREAD_INFORMATION_CLASS ThreadInformationClass, //表示线程信息类型
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
破解方法:调试执行到该函数时,若发现第ThreadInformationClass
参数值为 0x11
,跳过或者将修改为0
一个例子:
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
void DetachDebugger()
typedef enum _THREAD_INFORMATION_CLASS
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger // 17 (0x11)
THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI* ZWSETINFORMATIONTHREAD)(
HANDLE ThreadHandle,
THREAD_INFORMATION_CLASS ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
ZWSETINFORMATIONTHREAD pZwSetInformationThread = NULL;以上是关于《逆向工程核心原理》学习笔记:反调试技术的主要内容,如果未能解决你的问题,请参考以下文章
ios逆向笔记之反调试 以及反反调试 和反反反调试 ptrace篇
《逆向工程核心原理》学习笔记:64位 & Windows 内核6
《逆向工程核心原理》学习笔记:64位 & Windows 内核6