程序反调试的方法"/>
C/C++ 程序反调试的方法
C/C++ 要实现程序反调试有多种方法,BeingDebugged,NtGlobalFlag,ProcessHeap,CheckRemoteDebuggerPresent,STARTUPINFO,IsDebuggerPresent,父进程检测,TLS 线程局部存储,RDTSC时钟检测反调试,MapFileAndCheckSum,等都可实现反调试,这里我分别编写了一些案例,基本上一锅端了。
使用WinDBG随便载入一个二进制文件,并加载调试符号链接文件.
0:000> .sympath srv*c:\symbols*
0:000> .reload
在创建进程时,操作系统会为每个线程分配TEB(线程环境块)
,而且环境块FS段寄存器总是被设置为fs:[0]
的位置上,也就是默认指向当前线程的TEB数据,首先我们可以通过通配符找到TEB结构的具体名称.
0:000> dt ntdll!*teb*ntdll!_TEB (线程环境块)ntdll!_TEB32ntdll!_TEB64ntdll!_TEB_ACTIVE_FRAME_CONTEXTntdll!_TEB_ACTIVE_FRAMEntdll!_GDI_TEB_BATCH64ntdll!_GDI_TEB_BATCH32ntdll!_GDI_TEB_BATCHntdll!_TEB_ACTIVE_FRAME_CONTEXT
接着可通过dt命令,查询下ntdll!_TEB
结构,如下我们可以看到偏移为+0x018
的位置就是TEB结构头指针,在该地址基础上向下偏移0x30
就可以得到PEB(进程环境块)
的基地址.
0:000> dt -rv ntdll!_TEBstruct _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=0081e0000:000> dd $teb+0x18 # 手动验证地址
0081e018 0081e000 00000000 0000139c 0000194c
0081e028 00000000 0081e02c 0081b000 000000000:000> dd $teb + 0x30 # 在teb基础上+30 得到PEB基地址
0081e030 0081b000 00000000 00000000 000000000:000> !teb
TEB at 0081e000ExceptionList: 00b3f8e0StackBase: 00b40000StackLimit: 00b3d000ClientId: 0000139c . 0000194cTls Storage: 0081e02cPEB 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 000000000: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_IDadd eax,0x0 // 加上偏移0得到 UniqueProcessmov eax, [eax] // 取出内存地址中的值mov Pid,eax}return Pid;
}DWORD GetSelfTid()
{DWORD Pid = 0;__asm{mov eax, fs:[0x18] // 获取到PEB基地址add eax, 0x20 // 加上20得到 _CLIENT_IDadd eax, 0x04 // 加上偏移04得到 UniqueThreadmov eax, [eax] // 取出内存地址中的值mov Pid, eax}return Pid;
}int main(int argc,char* argv[])
{printf("进程 Pid = %d \n", GetSelfPid());printf("线程 Tid = %d \n", 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=007f10000:000> dd 007f1000 + 0x30
007f1030 007ee000 00000000 00000000 00000000
007f1040 00000000 00000000 00000000 000000000:000> r $peb
$peb=007ee0000: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 edxmov al, [edx + 2]mov Debug,al}return Debug;
}int IsDebugC()
{DWORD Debug = 0;__asm{mov eax, fs:[0x18] // TEB Self指针mov eax, [eax+0x30] // PEBmovzx eax, [eax+2] // PEB->BeingDebuggedmov Debug,eax}return Debug;
}int main(int argc,char* argv[])
{if (IsDebugC())printf("正在被调试");elseprintf("没有被调试");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] // 找到PEBmov eax, [eax + 0x68] // 找打 NtGlobalFlagmov Debug,eax // 取出值}if (Debug == 112)printf("程序正在被调戏 \n");elseprintf("程序正常 \n");return Debug;
}int main(int argc, char * argv[])
{printf("返回状态: %d \n", 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("正在被调戏. \n");}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 Heap1270000 NT Heap0:000> !heap -a 1270000 // 查询heep的内存
Index Address Name Debugging options enabled1: 01270000 Segment at 01270000 to 0136f000 (00006000 bytes committed)Flags: 40000062ForceFlags: 40000060Granularity: 8 bytesSegment Reserve: 00100000Segment Commit: 000020000: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] // 定位 ProcessHeapmov eax, [eax + 0x44] // 定位到 ForceFlagsmov Debug, eax}return Debug;
}int IsDebugB()
{DWORD Debug = 0;__asm{mov eax, fs:[0x18] // TED基地址mov eax, [eax + 0x30] // PEB基地址mov eax, [eax + 0x18] // 定位 ProcessHeapmov eax, [eax + 0x40] // 定位到 Flagsmov Debug, eax}return Debug;
}int main(int argc, char * argv[])
{int ret = IsDebugA();if (ret != 0)printf("进程正在被调试: %x \n", ret);int ret2 = IsDebugB();if (ret2 != 2)printf("进程正在被调试: %x \n", 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("正在被调戏. \n");}}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("正在被调试 \n");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 \n", ret);system("pause");return 0;
}
IsDebuggerPresent 函数反调试: 这个函数同样可以实现判断是否被调试,不过由于这个函数的实现过于简单,很容易就能够被分析者突破,因此现在也没有软件再使用该函数来进行反调试了.
#include <stdio.h>
#include <Windows.h>DWORD WINAPI ThreadProc(LPVOID lpParam)
{while (TRUE){//检测用 ActiveDebugProcess()来创建调试关系if (IsDebuggerPresent() == TRUE){printf("当前进程正在被调试 \r\n");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{ // 先判断是不是我们自己进程的PIDif (ProcessId == pe32.th32ProcessID){ // 判断父进程是否是 Explorer.exeif (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("进程正在被调试 \n");}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("程序正在被调试 \n");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,ecxadd ecx,eax // 将eax与ecx相加rdtsc // 再次调用时钟sub eax,ecx // 两次结果做差值mov Debug,eax}//printf("打印差值: %d \n", Debug);if (Debug >= 21)return 1;return 0;
}int main(int argc, char * argv[])
{int ret = IsDebug();if (ret == 1)printf("被调试了 \n");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 ...\r\n";// 当有线程访问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!\r\n");return;
}// TLS回调函数B
void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red)
{if (DLL_THREAD_DETACH == Reason) // 如果线程退出则打印信息 printf("t_TlsCallBack_B -> ThreadDetach!\r\n");/* Reason 什么事件触发的DLL_PROCESS_ATTACH 1DLL_THREAD_ATTACH 2DLL_THREAD_DETACH 3DLL_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")
#endifvoid 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;
#endifint 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("文件校验和错误 \n");elseprintf("文件正常 \n");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, 0mov ecx, 10 // 指定功能号mov edx, 'VX'in eax, dx // 从端口dx读取VMware版本到eaxcmp ebx, 'VMXh' // 判断ebx中是否包含VMware版本VMXhsetz[VmWare] // 设置返回值 True/False}}__except (EXCEPTION_EXECUTE_HANDLER){VmWare = false; // 如果未处于虚拟机中,将会产生异常}return VmWare;
}int main()
{int ret = IsInsideVM();if (ret == 1)printf("当前代码在虚拟机中 \n");elseprintf("宿主机 \n");system("pause");return 0;
}
更多推荐
C/C++ 程序反调试的方法
发布评论