zoukankan      html  css  js  c++  java
  • C/C++ 程序反调试的方法

    C/C++ 要实现程序反调试有多种方法,BeingDebugged,NtGlobalFlag,ProcessHeap,CheckRemoteDebuggerPresent,STARTUPINFO,IsDebuggerPresent,父进程检测,TLS 线程局部存储,RDTSC时钟检测反调试,MapFileAndCheckSum,等都可实现反调试,这里我分别编写了一些案例,基本上一锅端了。

    使用WinDBG随便载入一个二进制文件,并加载调试符号链接文件.

    0:000> .sympath srv*c:symbols*http://msdl.microsoft.com/download/symbols
    0:000> .reload
    
    0:000> srv*c:\symbols*http://www.blib.cn/symbols
    0:000> .reload
    

    在创建进程时,操作系统会为每个线程分配TEB(线程环境块),而且环境块FS段寄存器总是被设置为fs:[0]的位置上,也就是默认指向当前线程的TEB数据,首先我们可以通过通配符找到TEB结构的具体名称.

    0:000> dt ntdll!*teb*
              ntdll!_TEB (线程环境块)
              ntdll!_TEB32
              ntdll!_TEB64
              ntdll!_TEB_ACTIVE_FRAME_CONTEXT
              ntdll!_TEB_ACTIVE_FRAME
              ntdll!_GDI_TEB_BATCH64
              ntdll!_GDI_TEB_BATCH32
              ntdll!_GDI_TEB_BATCH
              ntdll!_TEB_ACTIVE_FRAME_CONTEXT
    

    接着可通过dt命令,查询下ntdll!_TEB结构,如下我们可以看到偏移为+0x018的位置就是TEB结构头指针,在该地址基础上向下偏移0x30就可以得到PEB(进程环境块)的基地址.

    0:000> dt -rv ntdll!_TEB
    
    struct _TEB, 66 elements, 0xfb8 bytes
      +0x000 NtTib            : struct _NT_TIB, 8 elements, 0x1c bytes                  # NT_TIB结构
      +0x018 Self             : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes         # NT_TIB结构(TEB自身)
         +0x000 ExceptionList    : Ptr32 to struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
         +0x004 StackBase        : Ptr32 to Void
         +0x008 StackLimit       : Ptr32 to Void
         +0x00c SubSystemTib     : Ptr32 to Void
         +0x010 FiberData        : Ptr32 to Void
         +0x010 Version          : Uint4B
         +0x014 ArbitraryUserPointer : Ptr32 to Void
         +0x018 Self             : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes
    
       +0x020 ClientId         : struct _CLIENT_ID, 2 elements, 0x8 bytes                # 进程与线程ID
          +0x000 UniqueProcess    : Ptr32 to Void   # 进程的PID
          +0x004 UniqueThread     : Ptr32 to Void   # 进程的PPID
    
       +0x02c ThreadLocalStoragePointer : Ptr32 to Void
       +0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 111 elements, 0x480 bytes  # 指向了PEB结构体
          +0x000 InheritedAddressSpace : UChar
          +0x001 ReadImageFileExecOptions : UChar
          +0x002 BeingDebugged    : UChar
          +0x003 BitField         : UChar
    

    接着再来验证一下,首先偏移地址0x18是TEB结构基地址,也就是指向自身偏移fs:[0x18]的位置,而!teb地址加0x30正是PEB的位置,在teb的基础上加上0x30就可以得到PEB的基地址,拿到PEB基地址就可以干很多事了.

    0:000> r $teb            # 使用系统符号解析
    $teb=0081e000
    
    0:000> dd $teb+0x18      # 手动验证地址
    0081e018  0081e000 00000000 0000139c 0000194c
    0081e028  00000000 0081e02c 0081b000 00000000
    
    0:000> dd $teb + 0x30    # 在teb基础上+30 得到PEB基地址
    0081e030  0081b000 00000000 00000000 00000000
    
    0:000> !teb
    TEB at 0081e000
        ExceptionList:        00b3f8e0
        StackBase:            00b40000
        StackLimit:           00b3d000
        ClientId:             0000139c . 0000194c
        Tls Storage:          0081e02c
        PEB Address:          0081b000          # 此处地址一致
    

    获取进程/线程PID: 首先我们需要fs:[0x18]定位到TEB(线程环境块)然后在此基础上加上0x20得到ClientId.

    0:000> dd fs:[0x18]                                  # 找到TEB基地址
    0053:00000018  0081e000 00000000 0000139c 0000194c
    0053:00000028  00000000 0081e02c 0081b000 00000000
    
    0:000> dt _teb 0081e000 
    ntdll!_TEB
       +0x000 NtTib            : _NT_TIB
       +0x01c EnvironmentPointer : (null) 
       +0x020 ClientId         : _CLIENT_ID              # 将TEB+0x20定位到这里(进程与线程信息)
       +0x028 ActiveRpcHandle  : (null) 
    
    0:004> dt _CLIENT_ID 0081e000                   # 查看进程详细结构
    ntdll!_CLIENT_ID
       +0x000 UniqueProcess    : 0x00b3f774 Void    # 获取进程PID
       +0x004 UniqueThread     : 0x00b40000 Void    # 获取线程PID
    

    知道了流程,接着我们通过以下公式计算得出本进程的进程与线程ID.

    #include <stdio.h>
    #include <Windows.h>
    
    DWORD GetSelfPid()
    {
    	DWORD Pid = 0;
    	__asm
    	{
    		mov eax, fs:[0x18]   // 获取到PEB基地址
    		add eax,0x20         // 加上20得到 _CLIENT_ID
    		add eax,0x0          // 加上偏移0得到 UniqueProcess
    		mov eax, [eax]       // 取出内存地址中的值
    		mov Pid,eax
    	}
    	return Pid;
    }
    
    DWORD GetSelfTid()
    {
    	DWORD Pid = 0;
    	__asm
    	{
    		mov eax, fs:[0x18]   // 获取到PEB基地址
    		add eax, 0x20        // 加上20得到 _CLIENT_ID
    		add eax, 0x04        // 加上偏移04得到 UniqueThread
    		mov eax, [eax]       // 取出内存地址中的值
    		mov Pid, eax
    	}
    	return Pid;
    }
    
    int main(int argc,char* argv[])
    {
    	printf("进程 Pid = %d 
    ", GetSelfPid());
    	printf("线程 Tid = %d 
    ", GetSelfTid());
    
    	system("pause");
    	return 0;
    }
    

    BeingDebugged 反调试: 进程运行时,位置FS:[30h]指向PEB的基地址,为了实现反调试,恶意代码通过这个位置来检查BeingDebugged标志位是否为1,如果为1则说明进程被调试,则删除自身等.

    首先我们可以使用dt _teb命令解析一下TEB的结构,如下TEB结构的起始偏移为0x0,而0x30的位置指向的是ProcessEnvironmentBlock 也就是指向了进程环境块PEB,

    0:000> dt _teb
    ntdll!_TEB
       +0x000 NtTib            : _NT_TIB
       +0x01c EnvironmentPointer : Ptr32 Void
       +0x020 ClientId         : _CLIENT_ID
       +0x028 ActiveRpcHandle  : Ptr32 Void
       +0x02c ThreadLocalStoragePointer : Ptr32 Void
       +0x030 ProcessEnvironmentBlock : Ptr32 _PEB       // PEB 进程环境块
    

    只需要在进程环境块的基础上+0x2就能定位到线程环境块TEB中BeingDebugged的标志,此处的标志位如果为1则说明程序正在被调试,为0则说明没有被调试.

    0:000> dt _peb
    ntdll!_PEB
       +0x000 InheritedAddressSpace : UChar
       +0x001 ReadImageFileExecOptions : UChar
       +0x002 BeingDebugged    : UChar
       +0x003 BitField         : UChar
       +0x003 ImageUsesLargePages : Pos 0, 1 Bit
       +0x003 IsProtectedProcess : Pos 1, 1 Bit
    

    我们手动来验证一下,首先线程环境块地址是007f1000,在此基础上加0x30即可得到进程环境快的基地址,位置FS:[0x30]指向PEB的基地址,007ee000继续加0x2即可得到BeingDebugged的状态ffff0401此处我们只需要看byte位是否为1即可.

    0:000> r $teb
    $teb=007f1000
    
    0:000> dd 007f1000 + 0x30
    007f1030  007ee000 00000000 00000000 00000000
    007f1040  00000000 00000000 00000000 00000000
    
    0:000> r $peb
    $peb=007ee000
    
    0:000> dd 007ee000 + 0x2
    007ee002  ffff0401 0000ffff 0c400112 19f0775f
    007ee012  0000001b 00000000 09e0001b 0000775f
    

    梳理一下知识点我们可以写出一下反调试代码,本代码单独运行程序不会出问题,一旦被调试器附加则会提示正在被调试,当然除了自己使用汇编代码来实现反调试以外,还可以使用IsDebuggerPresent()这个API函数来完成,其两者原理完全相同.

    #include <stdio.h>
    #include <Windows.h>
    
    int IsDebugA()
    {
    	BYTE Debug = 0;
    	__asm
    	{
    		mov eax, dword ptr fs:[0x30]
    		mov bl, byte ptr[eax + 0x2]
    		mov Debug,bl
    	}
    	return Debug;
    }
    
    int IsDebugB()
    {
    	BYTE Debug = 0;
    	__asm
    	{
    		push dword ptr fs : [0x30]
    		pop edx
    		mov al, [edx + 2]
    		mov Debug,al
    	}
    	return Debug;
    }
    
    int IsDebugC()
    {
    	DWORD Debug = 0;
    
    	__asm
    	{
    		mov eax, fs:[0x18]       // TEB Self指针
    		mov eax, [eax+0x30]      // PEB
    		movzx eax, [eax+2]       // PEB->BeingDebugged
    		mov Debug,eax
    	}
    	return Debug;
    }
    
    int main(int argc,char* argv[])
    {
    	if (IsDebugC())
    		printf("正在被调试");
    	else
    		printf("没有被调试");
    
    	system("pause");
    	return 0;
    }
    

    如果恶意代码中使用该种技术阻碍我们正常调试,我们只需要在X64DBG的命令行中执行dump fs:[30]+2来定位到BeingDebugged()的位置,并将其数值改为0然后运行程序,会发现反调试已经被绕过了.

    NtGlobalFlag 反调试: 首先定位dt -rv ntdll!_TEB找到TEB结构并通过TEB找到PEB结构,然后找到+0x068 NtGlobalFlag,这个位置的NtGlobalFlag类似于BeingDebugged,如果是调试状态NtGlobalFlag的值会是0x70,所以我们可以判断这个标志是否为0x70来判断程序是否被调试了,首先我们来使用汇编代码解决.

    #include <stdio.h>
    #include <windows.h>
    
    DWORD IsDebug()
    {
    	DWORD Debug = 0;
    	__asm
    	{
    		mov eax, fs:[0x18]       // TEB基地址
    		mov eax, [eax + 0x30]    // 找到PEB
    		mov eax, [eax + 0x68]    // 找打 NtGlobalFlag
    		mov Debug,eax            // 取出值
    	}
    
    	if (Debug == 112)
    		printf("程序正在被调戏 
    ");
    	else
    		printf("程序正常 
    ");
    	
    	return Debug;
    }
    
    int main(int argc, char * argv[])
    {
    	printf("返回状态: %d 
    ", IsDebugA());
    
    	system("pause");
    	return 0;
    }
    

    除了使用汇编实现反调试外,我们也可以使用Native API中的ZwQueryInformationProcess()这个函数来读取到程序中的PET数据,然后判断PebBase+0x68是否等于70,由于这个函数并没有公开,所以在使用时应该自行声明一下结构类型.

    #include <stdio.h>
    #include <windows.h>
    #include <winternl.h>
    
    typedef NTSTATUS(NTAPI *typedef_ZwQueryInformationProcess)(
    	IN HANDLE ProcessHandle,
    	IN PROCESSINFOCLASS ProcessInformationClass,
    	OUT PVOID ProcessInformation,
    	IN ULONG ProcessInformationLength,
    	OUT PULONG ReturnLength OPTIONAL
    	);
    
    DWORD IsDebug()
    {
    	HANDLE hProcess = NULL;
    	DWORD ProcessId = 0;
    	PROCESS_BASIC_INFORMATION Pbi;
    	typedef_ZwQueryInformationProcess pZwQueryInformationProcess = NULL;
    	ProcessId = GetCurrentProcessId();
    	hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessId);
    	if (hProcess != NULL)
    	{
    		HMODULE hModule = LoadLibrary(L"ntdll.dll");
    		pZwQueryInformationProcess = (typedef_ZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess");
    		NTSTATUS Status = pZwQueryInformationProcess(hProcess,ProcessBasicInformation,&Pbi,
    			sizeof(PROCESS_BASIC_INFORMATION),NULL);
    		if (NT_SUCCESS(Status))
    		{
    			DWORD ByteRead = 0;
    			WORD NtGlobalFlag = 0;
    			ULONG PebBase = (ULONG)Pbi.PebBaseAddress;
    			if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x68), &NtGlobalFlag, 2, &ByteRead) && ByteRead == 2)
    			{
    				if (NtGlobalFlag == 0x70)
    					return 1;
    			}
    		}
    		CloseHandle(hProcess);
    	}
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	if (IsDebug() == 1)
    	{
    		printf("正在被调戏. 
    ");
    	}
    	system("pause");
    	return 0;
    }
    

    ProcessHeap 反调试: 该属性是一个未公开的属性,它被设置为加载器为进程分配的第一个堆的位置(进程堆标志),ProcessHeap标志位于PEB结构中偏移为0x18处,第一个堆头部有一个属性字段,这个属性叫做ForceFlags属性偏移为0x44,该属性为0说明程序没有被调试,非0说明被调试,另外的Flags属性不为2说明被调试,不为2则说明没有被调试.

    0:000> dt !_peb
    ntdll!_PEB
       +0x000 InheritedAddressSpace : UChar
       +0x001 ReadImageFileExecOptions : UChar
       +0x002 BeingDebugged    : UChar
       +0x018 ProcessHeap      : Ptr32 Void       // 找到Process偏移地址
    
    0:000> !heap                                  // 找出堆区首地址
            Heap Address      NT/Segment Heap
                 1270000              NT Heap
    
    0:000> !heap -a 1270000                       // 查询heep的内存
    Index   Address  Name      Debugging options enabled
      1:   01270000 
        Segment at 01270000 to 0136f000 (00006000 bytes committed)
        Flags:                40000062
        ForceFlags:           40000060
        Granularity:          8 bytes
        Segment Reserve:      00100000
        Segment Commit:       00002000
    
    0:000> dt _HEAP 1270000                       // 找到ForceFlags标志的偏移地址
    ntdll!_HEAP
       +0x000 Segment          : _HEAP_SEGMENT
       +0x000 Entry            : _HEAP_ENTRY
       +0x040 Flags            : 0x40000062
       +0x044 ForceFlags       : 0x40000060
    

    这里需要注意的是堆区在不同系统中偏移值是不同的,在WindowsXP系统中ForceFlags属性位于堆头部偏移量为0x10处,对于Windows10系统来说这个偏移量为0x44,而默认情况如果被调试则ForceFlags属性为0x40000060,而Flags标志为0x40000062,下面通过汇编分别读取出这两个堆头的参数.

    #include <stdio.h>
    #include <windows.h>
    
    int IsDebugA()
    {
    	DWORD Debug = 0;
    	__asm
    	{
    		mov eax, fs:[0x18]       // TED基地址
    		mov eax, [eax + 0x30]    // PEB基地址
    		mov eax, [eax + 0x18]    // 定位 ProcessHeap
    		mov eax, [eax + 0x44]    // 定位到 ForceFlags
    		mov Debug, eax
    	}
    	return Debug;
    }
    
    int IsDebugB()
    {
    	DWORD Debug = 0;
    	__asm
    	{
    		mov eax, fs:[0x18]       // TED基地址
    		mov eax, [eax + 0x30]    // PEB基地址
    		mov eax, [eax + 0x18]    // 定位 ProcessHeap
    		mov eax, [eax + 0x40]    // 定位到 Flags
    		mov Debug, eax
    	}
    	return Debug;
    }
    
    int main(int argc, char * argv[])
    {
    	int ret = IsDebugA();
    
    	if (ret != 0)
    		printf("进程正在被调试: %x 
    ", ret);
    	
    	int ret2 = IsDebugB();
    	if (ret2 != 2)
    		printf("进程正在被调试: %x 
    ", ret2);
    
    	system("pause");
    	return 0;
    }
    

    另一种通过C语言实现的反调试版本,其反调试原理与上方相同,只不过此处我们使用了系统的API来完成检测标志位的.

    #include <stdio.h>
    #include <windows.h>
    #include <winternl.h>
    
    typedef NTSTATUS(NTAPI *typedef_ZwQueryInformationProcess)(
    	IN HANDLE ProcessHandle,
    	IN PROCESSINFOCLASS ProcessInformationClass,
    	OUT PVOID ProcessInformation,
    	IN ULONG ProcessInformationLength,
    	OUT PULONG ReturnLength OPTIONAL
    	);
    
    DWORD IsDebug()
    {
    	HANDLE hProcess = NULL;
    	DWORD ProcessId = 0;
    	PROCESS_BASIC_INFORMATION Pbi;
    	typedef_ZwQueryInformationProcess pZwQueryInformationProcess = NULL;
    	ProcessId = GetCurrentProcessId();
    	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
    	if (hProcess != NULL)
    	{
    		HMODULE hModule = LoadLibrary(L"ntdll.dll");
    		pZwQueryInformationProcess = (typedef_ZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess");
    		NTSTATUS Status = pZwQueryInformationProcess(hProcess, ProcessBasicInformation, &Pbi,
    			sizeof(PROCESS_BASIC_INFORMATION), NULL);
    		if (NT_SUCCESS(Status))
    		{
    			DWORD ByteRead = 0;
    			DWORD ProcessHeap = 0;
    			ULONG PebBase = (ULONG)Pbi.PebBaseAddress;
    			DWORD ForceFlagsValue = 1;
    
    			ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x18), &ProcessHeap, 2, &ByteRead);
    			ReadProcessMemory(hProcess, (LPCVOID)(ProcessHeap + 0x40), &ForceFlagsValue, 4, &ByteRead);
    
    			if (ForceFlagsValue != 0)
    			{
    				printf("正在被调戏. 
    ");
    			}
    		}
    		CloseHandle(hProcess);
    	}
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	IsDebug();
    
    	system("pause");
    	return 0;
    }
    

    CheckRemoteDebuggerPresent 反调试:除了使用汇编实现反调试以外,也可以使用以下方法实现反调试,这个反调试很强大,我还没有发现能够绕过的方法.

    #include <stdio.h>
    #include <windows.h>
    
    typedef BOOL(WINAPI *CHECK_REMOTE_DEBUG_PROCESS)(HANDLE, PBOOL);
    
    BOOL CheckDebugger()
    {
    	BOOL bDebug = FALSE;
    	CHECK_REMOTE_DEBUG_PROCESS CheckRemoteDebuggerPresent;
    
    	HINSTANCE hModule = GetModuleHandle("kernel32");
    	CheckRemoteDebuggerPresent = (CHECK_REMOTE_DEBUG_PROCESS)GetProcAddress(hModule, "CheckRemoteDebuggerPresent");
    
    	HANDLE hProcess = GetCurrentProcess();
    
    	CheckRemoteDebuggerPresent(hProcess, &bDebug);
    	return bDebug;
    }
    
    int main(int argc,char *argv[])
    {
    	if (CheckDebugger() == 1)
    		printf("正在被调试 
    ");
    
    	system("pause");
    	return 0;
    }
    

    STARTUPINFO 反调试: 程序启动时默认会通过explorer资源管理器,调用CreateProcess()函数创建的时候会把STARTUPINFO结构体中的值设置为0,但如果通过调试器启动程序时该值并不会发生变化,我们可以通过判断结构体中的dwFlags参数来实现反调试.

    #include <Windows.h>
    #include <stdio.h>
    
    int IsDebug()
    {
    	STARTUPINFO si = {0};
    	GetStartupInfo(&si);
    
    	if (si.dwFlags != 1)
    		return 1;
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	int ret = IsDebug();
    	printf("%d 
    ", ret);
    
    
    	system("pause");
    	return 0;
    }
    

    IsDebuggerPresent 函数反调试: 这个函数同样可以实现判断是否被调试,不过由于这个函数的实现过于简单,很容易就能够被分析者突破,因此现在也没有软件再使用该函数来进行反调试了.

    #include <stdio.h>
    #include <Windows.h>
    
    DWORD WINAPI ThreadProc(LPVOID lpParam)
    {
    	while (TRUE)
    	{
    		//检测用 ActiveDebugProcess()来创建调试关系
    		if (IsDebuggerPresent() == TRUE)
    		{
    			printf("当前进程正在被调试 
    ");
    			DebugBreak();    // 产生int3异常
    			break;
    		}
    		Sleep(1000);
    	}
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	HANDLE hThread = CreateThread(0, 0, ThreadProc, 0, 0, 0);
    	if (hThread == NULL)
    		return -1;
    
    	WaitForSingleObject(hThread, INFINITE);
    	CloseHandle(hThread);
    
    	system("pause");
    	return 0;
    }
    

    父进程检测实现反调试: 该反调试的原理非常简单,我们的系统在运行程序的时候,都是由Explorer.exe这个进程派生出来,也就是说如果没有被调试得到的父进程就是Explorer.exe的进程ID,如果被调试则该进程的父进程ID就会变成调试器的PID,并直接直接使用TerminateProcess(hProcess, 0);直接将调试器的父进程干掉.

    #include <Windows.h>
    #include <stdio.h>
    #include <tlhelp32.h>
    
    int IsDebug()
    {
    	DWORD ExplorerId = 0;
    	PROCESSENTRY32 pe32 = { 0 };
    	DWORD ProcessId = GetCurrentProcessId();
    
    	GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerId);
    
    	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    	if (hProcessSnap != INVALID_HANDLE_VALUE)
    	{
    		pe32.dwSize = sizeof(PROCESSENTRY32);
    		Process32First(hProcessSnap, &pe32);
    		do
    		{	// 先判断是不是我们自己进程的PID
    			if (ProcessId == pe32.th32ProcessID)
    			{	// 判断父进程是否是 Explorer.exe
    				if (pe32.th32ParentProcessID != ExplorerId)
    				{	// 如果被调试器附加了,我们直接强制干调调试器
    					HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ParentProcessID);
    					TerminateProcess(h_process, 0);
    					return 1;
    				}
    			}
    		} while (Process32Next(hProcessSnap, &pe32));
    	}
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	int ret = IsDebug();
    	if (ret == 1)
    	{
    		printf("进程正在被调试 
    ");
    	}
    	system("pause");
    	return 0;
    }
    

    异常处理实现反调试: 通过安装异常处理函数,然后手动触发函数,如果被调试器附加则会不走异常处理,此时IsDebug将会返回默认的False,并直接走_asm call pBuff;在调试器不忽略int3中断的情况下,调试将会被终止.

    #include <Windows.h>
    #include <stdio.h>
    
    BOOL Exceptioni = FALSE;
    
    LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
    {
    	Exceptioni = TRUE;
    	return EXCEPTION_CONTINUE_EXECUTION;
    }
    
    BOOL IsDebug()
    {
    	ULONG OldProtect = 0;
    	LPTOP_LEVEL_EXCEPTION_FILTER lpsetun;
    	// 安装自己实现的 ExceptionFilter 自定义异常处理函数
    	lpsetun = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);
    	LPVOID pBuff = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
    	*((PWORD)pBuff) = 0xc3;
    	VirtualProtect(pBuff, 0x1000, PAGE_EXECUTE_READ | PAGE_GUARD, &OldProtect);
    
    	_asm call pBuff;                       // 如果被调试,则执行中断,不会进行异常处理
    	SetUnhandledExceptionFilter(lpsetun);  // 恢复异常处理
    	return Exceptioni;
    }
    
    int main(int argc, char * argv[])
    {
    	if (!IsDebug())
    		printf("程序正在被调试 
    ");
    
    	system("pause");
    	return 0;
    }
    

    RDTSC时钟检测反调试: 使用时钟检测方法是利用rdtsc这个汇编指令,它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入EDX:EAX寄存器中,通过运行两次rdstc指令,然后计算出他们的差值,来判断是否被调试了.

    #include <Windows.h>
    #include <stdio.h>
    
    int IsDebug()
    {
    	int Debug = 0;
    	__asm
    	{
    		rdtsc          // 调用时钟
    		xor ecx,ecx
    		add ecx,eax    // 将eax与ecx相加
    		rdtsc          // 再次调用时钟
    		sub eax,ecx    // 两次结果做差值
    		mov Debug,eax
    	}
    	//printf("打印差值: %d 
    ", Debug);
    	if (Debug >= 21)
    		return 1;
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	int ret = IsDebug();
    	if (ret == 1)
    		printf("被调试了 
    ");
    
    	system("pause");
    	return 0;
    }
    

    TLS 线程局部存储反调试: TLS是为了解决多线程变量同步问题,声明为TLS变量后,当线程去访问全局变量时,会将这个变量拷贝到自己线程中的TLS空间中,以防止同一时刻内多次修改全局变量导致变量不稳定的情况,先来看一段简单的案例:

    #include <Windows.h>
    #include <stdio.h>
    
    #pragma comment(linker, "/INCLUDE:__tls_used")
    
    // TLS变量 
    __declspec (thread) int g_nNum = 0x11111111;
    __declspec (thread) char g_szStr[] = "TLS g_nNum = 0x%p ...
    ";
    
    // 当有线程访问tls变量时,该线程会复制一份tls变量到自己tls空间
    // 线程只能修改自己的空间tls变量,不会修改到全局变量
    
    // TLS回调函数A
    void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Red)
    {
    	if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息   
    		printf("t_TlsCallBack_A -> ThreadDetach!
    ");
    	return;
    }
    
    // TLS回调函数B
    void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red)
    {
    	if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息 
    		printf("t_TlsCallBack_B -> ThreadDetach!
    ");
    
    	/* Reason 什么事件触发的
    	DLL_PROCESS_ATTACH   1
    	DLL_THREAD_ATTACH    2
    	DLL_THREAD_DETACH    3
    	DLL_PROCESS_DETACH   0        */
    	return;
    }
    
    // 注册TLS回调函数,".CRT$XLB"
    #pragma data_seg(".CRT$XLB") 
    PIMAGE_TLS_CALLBACK p_thread_callback[] = { t_TlsCallBack_A, t_TlsCallBack_B, };
    #pragma data_seg()
    
    DWORD WINAPI t_ThreadFun(PVOID pParam)
    {
    	printf(g_szStr, g_nNum);
    	g_nNum = 0x22222222;
    	printf(g_szStr, g_nNum);
    	return 0;
    }
    
    int main(int argc, char * argv[])
    {
    	CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
    	Sleep(100);
    	CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
    
    	system("pause");
    	return 0;
    }
    

    前面的那几种反调试手段都是在程序运行后进行判断的,这种判断可以通过OD断下后进行修改从而绕过反调试,但TLS则是在程序运行前抢占式执行TLS中断,所以这种反调试技术更加的安全,但也不绝对仍然能够被绕过.

    #include <Windows.h>
    #include <stdio.h>
    
    // linker spec 通知链接器PE文件要创建TLS目录
    #ifdef _M_IX86
    	#pragma comment (linker, "/INCLUDE:__tls_used")
    	#pragma comment (linker, "/INCLUDE:__tls_callback")
    #else
    	#pragma comment (linker, "/INCLUDE:_tls_used")
    	#pragma comment (linker, "/INCLUDE:_tls_callback")
    #endif
    
    void NTAPI __stdcall TLS_CALLBACK(PVOID DllHandle, DWORD dwReason, PVOID Reserved)
    {
    	if (IsDebuggerPresent())
    	{
    		MessageBox(NULL, L" TLS_CALLBACK: 请勿调试本程序 !", L"TLS Callback", MB_ICONSTOP);
    		ExitProcess(0);
    	}
    }
    
    // 创建TLS段
    EXTERN_C
    #ifdef _M_X64
    	#pragma const_seg (".CRT$XLB")
    	PIMAGE_TLS_CALLBACK _tls_callback = TLS_CALLBACK;
    #else
    	#pragma data_seg (".CRT$XLB")
    	PIMAGE_TLS_CALLBACK _tls_callback = TLS_CALLBACK;
    #endif
    
    int main(int argc ,char * argv [])
    {
    	return 0;
    }
    

    MapFileAndCheckSum反破解: 通过使用系统提供的API实现反破解,该函数主要通过检测,PE可选头IMAGE_OPTIONAL_HEADER中的Checksum字段来实现的,一般的EXE默认为0而DLL中才会启用,当然你可以自己开启,让其支持这种检测.

    // C/C++  -> 常规 -> 调试信息格式 --> 程序数据库
    // 连接器 -> 常规 -> 启用增量链接 -> 否
    // 连接器 -> 高级 -> 设置校验和 -> 是
    #include <stdio.h>
    #include <windows.h>
    #include <Imagehlp.h>
    #pragma comment(lib,"imagehlp.lib")
    
    int main(int argc,char *argv[])
    {
    	DWORD HeadChksum = 1, Chksum = 0;
    	char text[512];
    
    	GetModuleFileName(GetModuleHandle(NULL), text, 512);
    	if (MapFileAndCheckSum(text, &HeadChksum, &Chksum) != CHECKSUM_SUCCESS)
    		return 0;
    
    	if (HeadChksum != Chksum)
    		printf("文件校验和错误 
    ");
    	else
    		printf("文件正常 
    ");
    
    	system("pause");
    	return 0;
    }
    

    利用In指令检测虚拟机: Vmware为真主机与虚拟机之间提供了相互沟通的通讯机制,它使用IN指令来读取特定端口的数据以进行两机通讯,但由于IN指令属于特权指令,在真机中运行将会触发EXCEPTION_PRIV_INSTRUCTION异常,而在虚拟机中并不会发生异常,我们可以利用这个特性判断代码是否在虚拟机中.

    #include <windows.h>
    #include <stdio.h>
    
    bool IsInsideVM()
    {
    	bool VmWare = true;
    	__try
    	{
    		__asm
    		{
    			mov    eax, 'VMXh'
    			mov    ebx, 0
    			mov    ecx, 10         // 指定功能号
    			mov    edx, 'VX'
    			in     eax, dx         // 从端口dx读取VMware版本到eax
    			cmp    ebx, 'VMXh'     // 判断ebx中是否包含VMware版本VMXh
    			setz[VmWare]           // 设置返回值 True/False
    		}
    	}
    	__except (EXCEPTION_EXECUTE_HANDLER)
    	{
    		VmWare = false;            // 如果未处于虚拟机中,将会产生异常
    	}
    	return VmWare;
    }
    
    int main()
    {
    	int ret = IsInsideVM();
    
    	if (ret == 1)
    		printf("当前代码在虚拟机中 
    ");
    	else
    		printf("宿主机 
    ");
    
    	system("pause");
    	return 0;
    }
    
  • 相关阅读:
    《TCP/IP网络协议基础》笔记
    《MySQL基础知识》笔记
    tc。数组的定义。
    tc。单等号,双等号的作用。
    tc。用浏览器加载gif图片。
    tc。做无界面的程序。
    做馒头一斤面放多少水。
    用笼屉蒸馒头好吃,还是用蒸柜蒸馒头好吃。上汽的大小。
    千层馒头的制作。
    馒头店,普遍是开在菜市场附近。
  • 原文地址:https://www.cnblogs.com/LyShark/p/13525917.html
Copyright © 2011-2022 走看看