欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Ant1-D3bug

程序员文章站 2024-03-25 08:13:16
...

反调试


几乎任何技术在经过构造后都能变成反调试技术。

注意,测试时不要用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);
    }
}

推荐阅读