zoukankan      html  css  js  c++  java
  • Windows反调试技术(上)

    写在前面

    在逆向工程中为了防止破解者调试软件,通常都会在软件中采用一些反调试技术来防破解。下面就是一些在逆向工程中常见的反调试技巧与示例。

    BeingDebuged

    利用调试器加载程序时调试器会通过CreateProcess()创建调试进程,同时会创建调试内核对象并保存在当前线程环境块的DbgSsReserved[1]字段中,此时此线程就不同于普通线程了(一般称为调试器线程)。接着CreateProcess()会判断是否有调试器,无则直接返回,有则调用PspCreateProcess(),此函数会根据DbgSsReserved[1]字段设置创建进程的EPROCESS的DebugPort字段,这样此被创建进程就可以称为被调试进程。接着PspCreateProcess()会调用MmCreatePeb(),此函数会根据EPROCESS的Debugport字段设置PEB的BeingDebuged字段。(无调试器时其值为0)
    我们可以直接通过内联汇编来访问BeingDebuged字段,也可以通过IsDebuggerPresent()这个API来得到此字段的值。下面是示例程序。

    int main(int argc, char *argv[])
    {
    	DWORD	dwBeingDebuged;
    	TCHAR	szTest[]	= TEXT("已检测到调试器!");
    	TCHAR	szSuccess[]	= TEXT("运行正常!");
    
      	dwBeingDebuged		= IsDebuggerPresent();
    	if(0 != dwBeingDebuged)
    	{
    		MessageBox(NULL, szTest, NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    }
    

    NtGlobalFlag

    当BeingDebuged被设为“TRUE”后程序会将PEB的NtGlobalFlag设置0x70h标志。我们通过内联汇编检测进程环境快的NtGlobalFlag字段。

    int main(int argc, char *argv[])
    {
    	DWORD	dwNtGlobalFlag;
    	TCHAR	szTest[]	= TEXT("已检测到调试器!");
    	TCHAR	szSuccess[]	= TEXT("运行正常!");
    	
      	dwNtGlobalFlag		= _AntiDebug();
    	if(0 != dwNtGlobalFlag)
    	{
    		MessageBox(NULL, szTest, NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    }
    
    DWORD _AntiDebug()
    {
    	_asm{
    	mov eax,fs:[0x30]
    	mov eax,[eax + 0x68]
    	and eax,0x70
    	}
    }
    

    ProcessHeap的Flags和ForceFlags

    当NtGlobalFlag字段被设置0x70标志后,其会改变PEB的字段ProcessHeap所指向的Flags和ForceFlags的值。
    对于x86平台而言Flags和ForceFlags偏移地址分别是相对于ProcessHeap指向地址的0x40和0x44处。当程序未被调试器调试时Flags的值应包含0x00000002标志,ForceFlags的值应为0,下面是通过内联汇编检测二者的值。

    int main(int argc, char *argv[])
    {
    	DWORD	dwReturn;
    	TCHAR	szTest[]	= TEXT("已检测到调试器!");
    	TCHAR	szSuccess[]	= TEXT("运行正常!");
    	
      dwReturn			= _AntiDebug();
    	if(0 != dwReturn)
    	{
    		MessageBox(NULL, szTest, NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    }
    
    
    DWORD _AntiDebug()
    {
    	_asm{
    	push ebx
    	mov eax,fs:[0x30]
    	mov eax,[eax + 0x18]
    
    	mov ebx,dword ptr [eax + 0x40]
    	and ebx,0x00000002
    	cmp ebx,0
    	je	end0
    	mov ebx,dword ptr [eax + 0x44]
    	cmp ebx,0
    	jne end0
    	xor eax,eax
    	jmp end
    end0:
    	mov eax,1
    end:
    	pop ebx
    	}
    }
    

    Heap Magic

    当进程被调试器调试时其进程堆会被一些特殊的标记填充,这些特殊标记分别是0xABABABAB , 0xBAADF00D , 0xFEEEFEEE。
    通过从进程堆中创建内存块来获得进程堆的起始地址,然后通过对整个进程堆双字遍历来计算这些标志的数量。(如果大于10则证明进程被调试器调试了)

    int main(int argc, char *argv[])
    {
    	DWORD	dwNum = 0;
    	PDWORD	pHeap;
    	TCHAR	szTest[]	= TEXT("已检测到调试器!");
    	TCHAR	szSuccess[]	= TEXT("运行正常!");
    
    	pHeap = (DWORD*)HeapAlloc(GetProcessHeap(), NULL, 0x100);	//通过在自己的进程默认堆中申请内存得到堆中内存的地址,(还可以通过PEB的LDR_MODULE字段中得到这些标志)
    	_try{
    	for(;;){						        //检测Heap Magic标志。
    		switch(*pHeap++){
    		case 0xABABABAB:
    		case 0xBAADF00D:
    		case 0xFEEEFEEE:
    			dwNum++;
    			break;
    		}	
    	      }
    	}
    	_except(EXCEPTION_EXECUTE_HANDLER){
    		if(dwNum > 10)
    		{
    			MessageBox(NULL, szTest, NULL, MB_OK);
    			ExitProcess(NULL);
    		}
    	}
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    
    }
    

    ProcessDebugPort

    如果进程被调试则进程会通过一个调试端口与调试子系统通信,进而与调试器通信。我们可以通过CheckRemoteDebuggerPresent()来检测调试端口是否存在ProcessDebugPort实际就是EPROCESS的DebugPort字段。同时CheckRemoteDebuggerPresent()不仅可以检测自身是否存在调试端口,还可以检测其他进程是否存在调试端口。

    int main(int argc, char *argv[])
    {
    	
    	BOOL	bDebuggerPresent;
    	TCHAR	szTest[]	= TEXT("已检测到调试器!");
    	TCHAR	szSuccess[]	= TEXT("运行正常!");
    	
      	CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent);
    
    	if(TRUE == bDebuggerPresent)
    	{
    		MessageBox(NULL, szTest, NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    }
    

    实际CheckRemoteDebuggerPresent()函数内部是通过调用NtQueryInformationProcess()来检测进程是否存在调试端口的,所以我们也可以直接通过动态调用NtQueryInformationProcess()来检测是否存在调试端口。

    typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(						//typedef后可以掩饰复合类型
    	_In_      HANDLE           ProcessHandle,
            _In_      UINT             ProcessInformationClass,
            _Out_     PVOID            ProcessInformation,
            _In_      ULONG            ProcessInformationLength,
            _Out_opt_ PULONG           ReturnLength
    	);
    //表示检测进程的调试端口
    UINT ProcessDebugPort = 7;	      												
    
    int main(int argc, char *argv[])
    {
    	
    	TCHAR		szTest[]	= TEXT("已检测到调试器!");
    	TCHAR		szSuccess[]	= TEXT("运行正常!");
    	DWORD		dwDebuggerPresent = 0;
    	NTSTATUS	stNtstatus;
    
    	pfnNtQueryInformationProcess NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));
      	stNtstatus = NtQueryInformationProcess(
    		GetCurrentProcess(),
    		ProcessDebugPort,
    		&dwDebuggerPresent,
    		sizeof(DWORD),
    		NULL);
    
    	if(0 != dwDebuggerPresent && stNtstatus == 0x00000000)
    	{
    		MessageBox(NULL, szTest, NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    
    	MessageBox(NULL, szSuccess, NULL, MB_OK);
    	return 0;
    }
    

    ProcessDebugObjectHandle

    如果进程被调试会创建一个调试内核对象,通过NtQueryInformationProcess()可以检测是否存在调试内核对象句柄。

    typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
        _In_      HANDLE           ProcessHandle,
        _In_      UINT             ProcessInformationClass,
        _Out_     PVOID            ProcessInformation,
        _In_      ULONG            ProcessInformationLength,
        _Out_opt_ PULONG           ReturnLength
    );
    
    UINT ProcessDebugObjectHandle = 0x1E;
    
    int main(int argc, char* argv[])
    {
    	HANDLE		hDebugHandle; 
    	NTSTATUS	stNtstatus;
    	pfnNtQueryInformationProcess NtQueryInformationProcess;
    		
    	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));
    
    	stNtstatus = NtQueryInformationProcess(
    		GetCurrentProcess(),
    		ProcessDebugObjectHandle,
    		&hDebugHandle,
    		sizeof(HANDLE),
    		NULL);
    	if(0x00000000 == stNtstatus && NULL != isDebuggerPresent)
    	{
    
    		MessageBox(NULL, TEXT("已检测到调试器!"),NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    
    	MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
    	return 0;
    }
    

    ProcessDebugFlags

    当进程被调试时其进程内核对象EPROCESS的NoDebugInherit字段会被置空,调用NtQueryInformationProcess可以检测此字段的值。

    typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
    	_In_      HANDLE           ProcessHandle,
        _In_      UINT             ProcessInformationClass,
        _Out_     PVOID            ProcessInformation,
        _In_      ULONG            ProcessInformationLength,
        _Out_opt_ PULONG           ReturnLength
    );
    
    UINT ProcessDebugFlags = 0x1F;
    
    int main(int argc, char* argv[])
    {
    	ULONG		DebugFlags; 
    	NTSTATUS	stNtstatus;
    	pfnNtQueryInformationProcess NtQueryInformationProcess;
    		
    	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));
    
    	stNtstatus = NtQueryInformationProcess(
    		GetCurrentProcess(),
    		ProcessDebugFlags,
    		&DebugFlags,
    		sizeof(ULONG),
    		NULL);
    	if(0x00000000 == stNtstatus && NULL == DebugFlags)
    	{
    
    		MessageBox(NULL, TEXT("已检测到调试器!"),NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    
    	MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
    	return 0;
    }
    

    ProcessBasicInformation

    调用NtQuertyInformationProcess检测ProcessBasicInformation得到一个数据结构。通过这个数据结构可以得到进程ID,进一步通过PID创建进程快照得到父进程的PID从而进一步得到父进程的用户名。通过判断父进程的用户名是不是调试器而进行反调试。

    #include <Windows.h>
    #include <winternl.h>
    #include <TlHelp32.h>
    typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
    	_In_      HANDLE           ProcessHandle,
        _In_      UINT             ProcessInformationClass,
        _Out_     PVOID            ProcessInformation,
        _In_      ULONG            ProcessInformationLength,
        _Out_opt_ PULONG           ReturnLength
    );
    
    const UINT uProcessBasicInformation = 0;
    
    DWORD GetParentPID(DWORD dwPid);
    BOOL GetExeNameByPID(char * szExeName, DWORD dwPid);
    int main(int argc, char* argv[])
    { 
    	char 		szExeName[256] = {0};
    	DWORD		dwParent;
    	NTSTATUS	stNtstatus;
    	pfnNtQueryInformationProcess NtQueryInformationProcess;
    	PROCESS_BASIC_INFORMATION stProcessBasicInformation;							
    
    	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));
    
    	stNtstatus = NtQueryInformationProcess(
    		GetCurrentProcess(),
    		uProcessBasicInformation,
    		&stProcessBasicInformation, 
    		sizeof(PROCESS_BASIC_INFORMATION),
    		NULL);
    	dwParent = GetParentPID(stProcessBasicInformation.UniqueProcessId);
    	GetExeNameByPID(szExeName, dwParent);
    	
    	if(0x00000000 == stNtstatus && (lstrcmp(szExeName, "explorer.exe") && lstrcmp(szExeName, "cmd.exe") && lstrcmp(szExeName, "services.exe")))
    	{
    
    		MessageBox(NULL, TEXT("已检测到调试器!"),NULL, MB_OK);
    		ExitProcess(NULL);
    	}
    
    	MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
    	return 0;
    }
    
    
    //获取父进程的PID
    DWORD GetParentPID(DWORD dwPid)
    {
    	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    	PROCESSENTRY32 pe32;
    	pe32.dwSize = sizeof(PROCESSENTRY32);
    	
    	if(hProcessSnap == INVALID_HANDLE_VALUE)
    	{
    		CloseHandle(hProcessSnap);
    		return 0;
    	}
    	
    	if(!Process32First(hProcessSnap, &pe32))
    	{
    		CloseHandle(hProcessSnap);
    		return 0;
    
    	}
    	do{
    
    		if(pe32.th32ProcessID == dwPid)
    			break;
    	}while(Process32Next(hProcessSnap, &pe32));
    	return pe32.th32ParentProcessID;
    
    }
    
    
    //获取对应PID进程的程序名称
    BOOL GetExeNameByPID(char szExeName[], DWORD dwPid)
    {
    	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    	PROCESSENTRY32 pe32;
    	pe32.dwSize = sizeof(PROCESSENTRY32);
    	
    	if(hProcessSnap == INVALID_HANDLE_VALUE)
    	{
    		CloseHandle(hProcessSnap);
    		return 0;
    	}
    	
    	if(!Process32First(hProcessSnap, &pe32))
    	{
    		CloseHandle(hProcessSnap);
    		return 0;
    
    	}
    	do{
    
    		if(pe32.th32ProcessID == dwPid)
    		{
    			
    
    			lstrcpy(szExeName, pe32.szExeFile);
    			break;
    		}
    
    	}while(Process32Next(hProcessSnap, &pe32));
    	return 1;
    }
    
    

    HideFromDebugger

    被调试进程通过调试端口DebugPort与调试子系统通信进一步与调试器通信。如果我们能将DebugPort清除就可以阻止进程与调试器通信达到反调试的目的,但是DebugPort一旦被创建就无法被在设置,所以我们这种思路走不通。
    我们发现进程如果产生异常,其会检测ETHREAD的HideFromDebugger成员是否为“FALSE”,如果是就会向当前进程的调试端口发送调试事件消息(为"TRUE"表示该异常对用户调试器不可见)。我们可以通过ZwSetInformationThread()设置HideFromDebugger字段的值为TRUE从而令用户调试器接收不到调试事件,达到反调试的目的。

    #include <winternl.h>
    typedef NTSTATUS (NTAPI *pfnZwSetInformationThread)(
    	_In_ HANDLE ThreadHandle,
    	_In_ UINT	ThreadInformationClass,
    	_In_ PVOID	ThreadInformation,
    	_In_ ULONG	ThreadInformationLength
    	);
    
    UINT uThreadHideFromDebugger = 0x11;
    
    int main(int argc, char * argv[])
    {
    	NTSTATUS stNtstatus;
    	pfnZwSetInformationThread ZwSetInformationThread;
    	
    	ZwSetInformationThread = (pfnZwSetInformationThread)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("ZwSetInformationThread"));
    
    	ZwSetInformationThread(
    		GetCurrentThread(),
    		uThreadHideFromDebugger,
    		0,
    		0);
    
    	return 0;
    }
    

    NtCreateThreadEx

    通过CreateThread( )的时候利用THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER标志,则在创建该线程时,它将对调试器隐藏。

    //通过CreateThread( )的时候利用THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER标志,则在创建该线程时,它将对调试器隐藏。
    //#include <Windows.h>
    typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx) (
        _Out_    PHANDLE              ThreadHandle,
        _In_     ACCESS_MASK          DesiredAccess,
        _In_opt_ PVOID		  ObjectAttributes,		//POBJECT_ATTRIBUTES
        _In_     HANDLE               ProcessHandle,
        _In_     PVOID                StartRoutine,
        _In_opt_ PVOID                Argument,
        _In_     ULONG                CreateFlags,
        _In_opt_ ULONG_PTR            ZeroBits,
        _In_opt_ SIZE_T               StackSize,
        _In_opt_ SIZE_T               MaximumStackSize,
        _In_opt_ PVOID                AttributeList
    );
    void ThreadProc();
    
    int main(int argc, char* argv[])
    {
    	HANDLE hThread;
    	pfnNtCreateThreadEx NtCreateThreadEx;
    	NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtCreateThreadEx"));
    	NtCreateThreadEx(
    		&hThread,
    		0x1FFFFF,
    		NULL,
    		GetCurrentProcess(),
    		(LPTHREAD_START_ROUTINE)ThreadProc,
    		NULL,
    		0x00000004,					//直接执行并且对调试器隐藏THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER
    		NULL,
    		NULL,
    		NULL,
    		NULL
    		);
    
    	WaitForSingleObject(hThread,INFINITE);
    	int n = GetLastError();
    	return 0;
    }
    //线程回调函数
    void ThreadProc()
    {
    	MessageBox(NULL, TEXT("我是一个新线程!"), NULL, MB_OK);
    }
    
    

    参考资料: 看雪学院《加密解密》
    张银奎《软件调试》
    https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software

  • 相关阅读:
    上传项目到github上
    app widget设置bitmap时,无作用
    Android Studio 启动app 白屏
    android sqlite 数据库中使用的类型
    android 解决华为系列手机调试时不能打印Logcat日志信息
    android 自定义滚动条图标
    检测邮箱
    js检测是否存在中文
    表单的checkbox选中和取消
    javascript
  • 原文地址:https://www.cnblogs.com/revercc/p/13718778.html
Copyright © 2011-2022 走看看