Ant1-D3bug

26 篇文章 2 订阅

反调试


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

注意,测试时不要用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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值