Ant1-D3bug
反调试
文章目录
几乎任何技术在经过构造后都能变成反调试技术。
注意,测试时不要用vs,因为vs调试并不能算调试器;也不要用带插件的调试器(OD)。推荐windbg。
1. 反调试分类
1.1 静态反调试
特点:调试时开始阻拦。一般找到原因就能突破。
PEB
- BeingDebugged
- Ldr
- Heap
- NtGlobalFlag
TEB
- StaticUnicodeString:静态缓冲区
原始API
- NtQueryInformationProcess():后面整理的4种类型
- NtQuerySystemInfo():
- NtQueryObject():遍历系统内核对象
其它
分离调试ZwSetInformatioThread()
利用TLS回调函数。
打开进程检查SeDubugPrivilege()
还有普通API:
- 检查父进程
- 检查窗口名
- 遍历进程名
- 检查文件名/路径
- 检查注册表
1.2 动态反调试
特点:一般在调试过程中阻拦。
使用SEH:
- 异常
- 断点
SetUnhandledExceptionFilter()
时间检查:RDTSC
,汇编指令,读取时间戳计数器。
补丁检查(常用CRC):
- 扫描0xcc
- 扫描关键代码段hash
- 扫描API断点,第一个字节是否为0xcc
反反汇编(阻止opcode->指令):
- 指令截断:截断第一条指令,进而导致后续指令解析错误;
- 指令混淆
- 指令膨胀
- 代码乱序:用jmp连接。
偷取代码(类似壳):
- 移动OEP加密执行
- 移动API
分页保护:
- 运行时保护分页:修改代码及数据段保护属性干扰分析。
壳:
- 压缩壳:配合OEP加密
- 加密壳:再添加各种反调试手段
虚拟机(类似解释型语言):
- API虚拟机:针对常用API
- 指令级虚拟机:囊括任意指令
还有单步检查、*键盘鼠标、禁用窗口。
2. 部分方法的示例代码
2.1 PEB
PEB地址存于fs:[0x30]
BeingDebugged
PEB偏移为2的字段。
可直接用IsDebuggerPresent()
API,它的反汇编代码如下。
IsDebbugerPresent()
BOOL PEB_BeingDubugged()
{
__asm {
mov eax, dword ptr fs : [0x30]
movzx eax, dword ptr[eax + 0x02];
}
}
ProcessHeap
PEB偏移0x18的字段,如果是64位,则位于
heap结构体的Flags(0x40)和ForceFlags(0x44)在正常运行时分别为2和0。
bool CheckProcessHeap()
{
DWORD Flags = 0, ForceFlags = 0;
__asm
{
; 找到 PEB->FS:[0x30]
mov eax, dword ptr fs : [0x30]
; 找到 ProcessHeap->Peb + 0x18
mov eax, dword ptr[eax + 0x18]
; 找到 Flags->HEAP + 0x40
mov edx, dword ptr[eax + 0x40]
mov Flags, edx
; 找到 ForceFlags->HEAP + 0x44
mov ecx, dword ptr[eax + 0x44]
mov ForceFlags, ecx
}
printf("%08x %08x\n", Flags, ForceFlags);
return !(Flags == 2 && ForceFlags == 0);
}
NtGlobalFlag;
位于PEB偏移0x68处,正常值为0x70。
bool CheckNtGlobalFlag()
{
__asm {
mov eax, dword ptr fs:[0x30];
mov eax, dword ptr [eax + 0x68]
}
}
2.2 NtQueryInformationProcess
NtQueryInformationProcess()
是一个可以在R0和R3运行的函数,用来查看进程信息。这个函数没有官方文档,也就是说未公开。
第二个参数是信息类型,我们有4种选择:
- 0x00,ProcessBasicInformation
- 0x07,ProcessDebugPort
- 0x1e,ProcessDebugObjectHandle
- 0x1f,ProcessDebugFlag
注意要包含头文件和导入库。
ProcessDebugPort(0x07)
填ProcessDebugPort
可以获取调试端口,如果正常运行,则端口为0,否则为-1。
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
BOOL NQIP_ProcessDebugPort()
{
int nDebugPort = 0;
NtQueryInformationProcess(
GetCurrentProcess(), //获取伪句柄
ProcessDebugPort,
&nDebugPort,
sizeof(nDebugPort),
NULL);
return nDebugPort == -1 ? TRUE : FALSE;
}
ProcessDebugObjectHandle(0x1e)
也可以查询目标进程的调试对象句柄,如果正常运行应该得到NULL。
BOOL NQIP_ProcessDebugPort()
{
HANDLE hProcessDebugObjectHandle = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
(PROCESSINFOCLASS)0x1e,
&hProcessDebugObjectHandle,
sizeof(hProcessDebugObjectHandle),
NULL);
return hProcessDebugObjectHandle ? TRUE : FALSE;
}
ProcessDebugFlag(0x1f)
获取目标调试标记,正常为0,调试为1。
BOOL NQIP_ProcessDebugFlag()
{
BOOL bProcessDebugFlag = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
(PROCESSINFOCLASS)0x1f,
&bProcessDebugFlag,
sizeof(bProcessDebugFlag),
NULL);
return bProcessDebugFlag;
}
ProcessBasicInformation(0x00)
最后就是查询父进程PID,与explorer.exe的PID对比,不匹配则证明本进程不是双击运行的。
bool NQIP_PPID()
{
struct PROCESS_BASIC_INFORMATION {
ULONG ExitStatus; // 进程返回码
PPEB PebBaseAddress; // PEB地址
ULONG AffinityMask; // CPU亲和性掩码
LONG BasePriority; // 基本优先级
ULONG UniqueProcessId; // 本进程PID
ULONG InheritedFromUniqueProcessId; // 父进程PID
} stcProcInfo;
NtQueryInformationProcess(
GetCurrentProcess(),
ProcessBasicInformation,
&stcProcInfo,
sizeof(stcProcInfo),
NULL);
DWORD ExplorerPID = 0;
DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;
GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);
return ExplorerPID == CurrentPID ? false : true;
}
当然,在vs里ctrl+f5也显示是调试状态。
2.2 检查系统是否处于调试状态
通过一个api获取系统是否开启调试模式。
/*
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45,
SystemPolicyInformation = 134,
} SYSTEM_INFORMATION_CLASS;
*/
BOOL CheckSystemKernelDebuggerInfomation()
{
struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN KernelDebuggerEnabled;
BOOLEAN KernelDebuggerNotPresent;
} DebuggerInfo = { 0 };
NtQuerySystemInformation(
SystemInterruptInformation,
&DebuggerInfo,
sizeof(DebuggerInfo),
NULL);
return DebuggerInfo.KernelDebuggerEnabled;
}
2.3 NtQueryObject
BOOL NQO_NtQueryObject()
{
typedef struct _OBJECT_TYPE_INFORMATION{
UNICODE_STRING TypeName;
ULONG TotalNumberOfHandlers;
ULONG TotalNumberOfObjects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION {
ULONG NumberOfObjectTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInfo[1];
}OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
// 1. 查询信息大小
ULONG uSize = 0;
NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, &uSize, sizeof(uSize), &uSize);
// 2. 获取信息
POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION)new BYTE[uSize]();
NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, pObjectAllInfo, uSize, &uSize);
// 3. 遍历并处理
POBJECT_TYPE_INFORMATION pObjectTypeInfo = pObjectAllInfo->ObjectTypeInfo;
for (int i = 0; i < pObjectAllInfo->NumberOfObjectTypes; ++i)
{
// 3.1 查看类型是否为DebugObject;
if (!wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer))
{
return TRUE;
}
// 3.2 获取对象名空间大小(考虑对齐)
ULONG uNameLength = pObjectTypeInfo->TypeName.Length;
ULONG uDataLength = uNameLength - uNameLength % sizeof(ULONG) + sizeof(ULONG);
// 3.3 指向下一个对象
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjectTypeInfo->TypeName.Buffer;
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)((PBYTE)pObjectTypeInfo + uDataLength);
}
delete[] pObjectAllInfo;
return FALSE;
}
2.4 脱离调试器
API名叫ZwSetInformationThread()
。
原理,当 DbgkForwardException 检查到线程存在ThreadHideFromDebugger 的时候,就不会向调试器发送调试信息了。
#include <iostream>
#include <windows.h>
typedef enum THREAD_INFO_CLASS {
ThreadHideFromDebugger = 17
};
typedef NTSTATUS (NTAPI *ZW_SET_INFORMATION_THREAD)(
_In_ HANDLE ThreadHandle,
_In_ THREAD_INFO_CLASS ThreadInformationClass,
_In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation,
_In_ ULONG ThreadInformationLength
);
void ZSIT_DetachDebug()
{
ZW_SET_INFORMATION_THREAD Func;
Func = (ZW_SET_INFORMATION_THREAD)GetProcAddress(
LoadLibraryW(L"ntdll.dll"),
"ZwSetInformationThread");
Func(
GetCurrentThread(),
ThreadHideFromDebugger,
NULL, NULL);
}
int main()
{
ZSIT_DetachDebug();
printf("running...\n");
system("pause");
return 0;
}
复习
反调试的目的:
- 防止程序被调试
- 防止程序被暴力**
如何**常见反调试手段:
- 对于PEB手段,将值重写即可;
- 对于无文档的API,HOOK后根据参数判断是否获取调试信息,是的话替换称错误参数即可。
NTSTATUS WINAPI MyNtQueryInformationProcess(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength){
// 查询调试端口
if(ProcessInformationClass== ProcessDebugPort)
{
*(PDWORD*) ProcessInformation=0xFFFFFFFF;
if(ReturnLength) *ReturnLength = 4;
return 0;
}
else{
// 调用原版函数:
return pSrcNtQueryInformationProcess(ProcessHandle,
ProcessInformationClass,
ProcessInformation,
ProcessInformationLength,
ReturnLength);
}
}
上一篇: CV计算机视觉(2)——图像分类中的多标签分类任务
下一篇: EVO install
推荐阅读