反调试
过滤器异常
什么是异常:
最简单的一句话:CPU遇到了无法执行的指令。
异常的处理过程:
CPU-操作系统-调试器(如果有)-VEH-SEH-过滤器异常SetUnhandledExceptionFilter
即过滤器异常就是前面都没有处理异常,那么我就处理。
#include <Windows.h>
#include <iostream>
/*
@function 异常处理函数
@param ExceptionInfo 异常信息
@return 返回值是否处理
*/
LONG WINAPI UnhandledExceptionFilterProc(
_EXCEPTION_POINTERS* ExceptionInfo
) {
MessageBoxA(0,0,0,0);
ExceptionInfo->ContextRecord->Eip += 3; // 跳过异常处
return 1; // 1异常我自己处理了
}
int main() {
// 1表示自己处理了 0表示继续搜索下一个异常处理 -1表示继续执行
SetUnhandledExceptionFilter(UnhandledExceptionFilterProc);
__asm {
mov eax,0
mov [eax],1 // 往0地址写入数据 报错
}
system("pause");
return 0;
}
异常的作用,前面我们也说了,在编写程序的时候有的会故意触发异常,然后在异常处理函数里面判断是否有调试器的存在。
就像PEB+0x2的位置就能判断是否有调试器存在
反调试1
调试器的工作流程:
-1.创建进程,通过CreateProcess并设置DEBUG_PROCESS模式
-2.附加进程,调用DebugActiveProcess函数
程序在被调试的时候,进程执行的一些操作会以事件的方式通知调试器。
当有事件需要通知调试器的时候,操作系统会挂起进程所有线程,然后把事件发送给调试器。
调试器通过WaitForDebugEvent来等待事件消息。
程序被调试后会有那些特征?
-调试程序的时候,调试器会对被调试的程序设置标志。
BeginDebugged=1 API:isDebugPresent
BeginDebugged 存放着PE+02偏移
NtGlobalFlag 调试的时候值为0x70存放在PEB+0x68偏移处,附加进程的方式值不变
ProcessDebugPort API:CheckRemoteDebuggerPresent 不仅可以检测自己,还能检测别的进程
#include <Windows.h>
#include <iostream>
int main() {
/*
mov eax, dword ptr fs:[18] // 拿到TEB
mov eax, dword ptr [eax+30] // 拿到PEB
movzx eax, byte ptr [eax+2] // 拿到isDebugged这个标志
retn
*/
BOOL ret = IsDebuggerPresent();
if (ret) {
printf("存在调试器\n");
}else {
printf("不存在调试器\n");
}
DWORD isDebug = 1;
__asm {
mov eax, fs: [0x30] ;
mov eax, [eax + 0x68];
mov isDebug, eax;
};
if (isDebug == 0x70) {
printf("isDebug 存在调试器\n");
}else {
printf("isDebug 不存在调试器\n");
}
system("pause");
return 0;
}
#include <Windows.h>
#include <iostream>
int main() {
BOOL ret;
CheckRemoteDebuggerPresent(GetCurrentProcess(),&ret);
if (ret) {
printf("存在调试器\n");
}else {
printf("不存在调试器\n");
}
system("pause");
return 0;
}
反调试2
使用NtQueryInfomationProcess来实现CheckRemoteDebuggerPresent函数的功能,检测某个进程是否被调试。
他们的本质就是调试端口。
__kernel_entry NTSTATUS NtQueryInformationProcess(
HANDLE ProcessHandle, 要查询的进程句柄
PROCESSINFOCLASS ProcessInformationClass, 查询进程信息的类型
PVOID ProcessInformation, 输出参数 缓冲区
ULONG ProcessInformationLength, 缓冲区大小
PULONG ReturnLength 实际返回的大小
);
PROCESSINFOCLASS为枚举类型
enum PROCESSINFOCLASS{
...
ProcessDebugPort // 7 调试端口
...
ProcessDebugObjectHandle // 30 获取调试对象句柄,为空表示未处于调试状态
ProcessDebugFlags // 31 检测调试标志为0 表示处于调试状态,1非调试状态
}
#include <Windows.h>
#include <iostream>
// 定义函数指针
typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)(
HANDLE ProcessHandle, // 要查询的进程句柄
DWORD ProcessInformationClass, // 查询进程信息的类型
PVOID ProcessInformation, // 输出参数 缓冲区
ULONG ProcessInformationLength, // 缓冲区大小
PULONG ReturnLength //实际返回的大小
);
// 定义函数指针对象
_NtQueryInformationProcess NtQueryInformationProcess;
int main() {
// 这个函数需要动态调用
HMODULE hNtDll = LoadLibraryA("ntdll.dll");
if (hNtDll == 0) {
printf("加载dll失败");
system("pause");
return 0;
}
NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
DWORD debugPort = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
7, // 获取调试端口信息
&debugPort, // 输出参数
sizeof(DWORD), // 输出参数大小
NULL);
if (debugPort != 0) {
printf("当前程序正在被调戏\n");
}else {
printf("当前程序没有事情哩\n");
}
HANDLE debugHandle = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
30, // 获取句柄
&debugHandle, // 输出参数 ProcessDebugObjectHandle
sizeof(HANDLE), // 输出参数大小
NULL);
if (debugHandle != 0) {
printf("当前程序正在被调戏\n");
}else {
printf("当前程序没有事情哩\n");
}
DWORD debugFlags = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
31, // 检测标志 ProcessDebugFlags 0为被调试
&debugFlags, // 输出参数 ProcessDebugObjectHandle
sizeof(DWORD), // 输出参数大小
NULL);
if (debugFlags == 0) {
printf("当前程序正在被调戏\n");
}else {
printf("当前程序没有事情哩\n");
}
FreeLibrary(hNtDll);
system("pause");
return 0;
}
通过CloseHandle来实现反调试
CloseHandle试图关闭一个不存在的句柄,如果程序处于调试状态则会触发异常,否则没有反应。
__try {
CloseHandle((HANDLE)0x1234);
}__except (1) {
printf("CloseHandle 当前程序正在被调戏\n");
}
即如果是正常执行,是没有任何反应的,只有被调试了,才会报异常。
设置线程信息分离调试器:
通过为线程设置 ThreadHideFromDebugger,可以禁止线程产生调试事件。
typedef NTSTATUS(NTAPI* ZwSetInformationThread)(
IN HANDLE ThreadHandle,
IN THREAD_INFO_CLASS ThreadInformaitonClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
typedef enum _THREADINFOCLASS {
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger // 17
} THREAD_INFO_CLASS;
#include <Windows.h>
#include <iostream>
// 定义函数指针
typedef NTSTATUS(NTAPI* _ZwSetInformationThread)(
HANDLE ThreadHandle, // 线程句柄
DWORD ThreadInformaitonClass, // 枚举值类型 要设置的线程信息类型
PVOID ThreadInformation, // 缓冲区
ULONG ThreadInformationLength // 缓冲区大小
);
_ZwSetInformationThread ZwSetInformationThread;
// 定义函数指针对象
int main() {
// 这个函数需要动态调用
HMODULE hNtDll = LoadLibraryA("ntdll.dll");
if (hNtDll == 0) {
printf("加载dll失败");
system("pause");
return 0;
}
ZwSetInformationThread = (_ZwSetInformationThread)GetProcAddress(hNtDll,"ZwSetInformationThread");
ZwSetInformationThread(GetCurrentThread(),17,NULL,NULL);
printf("helloStarHook");
FreeLibrary(hNtDll);
system("pause");
return 0;
}
反调试3
硬件断点反调试:
回顾硬件断点相关的寄存器
DR0~DR3保存硬件断点的地址
DR6为调试异常产生后显示的一些信息,DR7断点属性。
DR7解析:L0=1表示DR0这个地址断点有效,并且是一个局部断点。
L0~L3对应DR0~DR3的断点是否有效,L是局部断点。
G0~G3对应的DR0~DR3的断点是否有效,G是全局断点,Windows下没用
LEN0~LEN3对应的DR0~DR3的断点长度,00表示一个字节,01两个字节,11四个字节
RW0~RW3对应DR0~DR3断点的类型,00表示执行断点,01写入断点,11读写断点
#include <Windows.h>
#include <iostream>
EXCEPTION_DISPOSITION WINAPI myExceptHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext
) {
if (pcontext->Dr0 != 0 || pcontext->Dr1 != 0 || pcontext->Dr2 != 0 || pcontext->Dr3 != 0) {
printf("当前程序被调戏了\n");
ExitProcess(0);
}
return ExceptionContinueSearch;
}
int main() {
CONTEXT context{0};
context.ContextFlags = CONTEXT_DEBUG_REGISTERS; // 获取调试寄存器的信息
GetThreadContext(GetCurrentThread(),&context);
if (context.Dr0 != 0 || context.Dr1 != 0 || context.Dr2 != 0 || context.Dr3 != 0) {
printf("当前程序被调戏了\n");
}else {
printf("当前程序没有被调试\n");
}
DWORD setProcAddr = (DWORD)myExceptHandler;
__asm {
push setProcAddr;
mov eax, fs: [0] ;
push eax;
mov fs : [0] , esp;
};
system("pause");
return 0;
}
反调试4-检测调试对象
我们可以通过查询父进程的ID判断是否被调试了
正常打开的程序的父进程是explorer.exe,通过查询父进程ID是否是explorer来判断是否被调试了。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers, // Note: this is kernel mode only
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
ProcessDeviceMap,
ProcessSessionInformation,
ProcessForegroundInformation,
ProcessWow64Information,
ProcessImageFileName,
ProcessLUIDDeviceMapsEnabled,
ProcessBreakOnTermination,
ProcessDebugObjectHandle,
ProcessDebugFlags,
ProcessHandleTracing,
ProcessIoPriority,
ProcessExecuteFlags,
ProcessResourceManagement,
ProcessCookie,
ProcessImageInformation,
MaxProcessInfoClass // MaxProcessInfoClass should always be the last enum
} PROCESSINFOCLASS;
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
DWORD PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
// 定义函数指针
typedef NTSTATUS(NTAPI* _NtQueryInformationProcess)(
HANDLE ProcessHandle, // 要查询的进程句柄
PROCESSINFOCLASS ProcessInformationClass, // 查询进程信息的类型
PVOID ProcessInformation, // 输出参数 缓冲区
ULONG ProcessInformationLength, // 缓冲区大小
PULONG ReturnLength //实际返回的大小
);
_NtQueryInformationProcess NtQueryInformationProcess;
int main() {
// 所有程序都会加载ntdll.dll
HMODULE hNtDll = GetModuleHandleA("ntdll.dll");
NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(hNtDll,"NtQueryInformationProcess");
// 查询ProcessBasicInformation可以拿到父进程 返回PROCESS_BASIC_INFORMATION
PROCESS_BASIC_INFORMATION basicInfomation{0};
NtQueryInformationProcess(
GetCurrentProcess(),
ProcessBasicInformation,
&basicInfomation,
sizeof(PROCESS_BASIC_INFORMATION),
NULL);
PROCESSENTRY32 pe32{ 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
if (Process32First(hSnapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"explorer.exe")) {
if (pe32.th32ProcessID != basicInfomation.InheritedFromUniqueProcessId) {
printf("当前进程被调戏了\n");
}else {
printf("当前进程没被调戏\n");
}
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
system("pause");
return 0;
}
当调试器调试某进程时会创建一个调试对象类型的内核对象,检测是否存在调试对象实现反调试。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
//NtQueryObject 参数2
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass // MaxObjectInfoClass should always be the last enum
} OBJECT_INFORMATION_CLASS;
typedef NTSTATUS (NTAPI* _NtQueryObject)(
__in HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,//查询对象类型枚举值
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,//输出结果缓冲区
__in ULONG ObjectInformationLength,//缓冲区大小
__out_opt PULONG ReturnLength//实际需要大小
);
_NtQueryObject NtQueryObject;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_TYPE_INFORMATION {
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_TYPES_INFORMATION {
ULONG numberOfTypesInfo; // 数组长度
OBJECT_TYPE_INFORMATION typeinfo[1]; // 上面的结构体数组
}OBJECT_TYPES_INFORMATION,*POBJECT_TYPES_INFORMATION;
int main() {
POBJECT_TYPES_INFORMATION typesInfo = NULL;
HMODULE hNtDll = GetModuleHandleA("ntdll.dll");
NtQueryObject = (_NtQueryObject)GetProcAddress(hNtDll, "NtQueryObject");
// 查询所有内核对象类型信息
CHAR* buff = new CHAR[0x4000];
DWORD realWrite = 0;
NTSTATUS ret = NtQueryObject(
NULL,
ObjectTypesInformation,
buff,
0x4000,
&realWrite);
if (ret == 0) {
// 没有错误
typesInfo = (POBJECT_TYPES_INFORMATION)buff;
POBJECT_TYPE_INFORMATION typeInfo = typesInfo->typeinfo; // 默认指向首地址
// 那么就拿到了所有的内核对象
for (ULONG i = 0; i < typesInfo->numberOfTypesInfo; i++) {
if (wcscmp(L"DebugObject", typeInfo->TypeName.Buffer)) {
// 如果存在这样名称的内核对象 并且数量大于0
if (typeInfo->TotalNumberOfObjects > 0) {
printf("当前进程被调戏了\n");
}else {
printf("当前进程没被调戏\n");
}
break;
}
char* temp = (CHAR*)typeInfo->TypeName.Buffer; // 指向UNICODE_STRING最后一个字段
temp += typeInfo->TypeName.MaximumLength; // buffer大小
temp = temp + (DWORD)temp % 4; // 还要进行对齐
// 这样就跳过了buffer
DWORD data = *(DWORD*)temp;
while (data == 0) { // 然后把接下来的0都跳过
temp += 4;
data = *(DWORD*)temp;
}
// 那么就找到了下一个结构体的首地址
typeInfo = (POBJECT_TYPE_INFORMATION)temp;
}
}
system("pause");
return 0;
}
反调试5
附加进程反调试
附加进程流程:调试器调用DebugActiveProcess后,进程中有多少个线程创建,调试器会发送多少个CREATE_THREAD_DEBUG_EVENT事件给调试器.
加载了多少个dll会发送多少次LOAD_DLL_DEBUG_EVENT事件给调试器,调试器通过WaitForDebugEvent函数获取调试事件,并做相应处理。
处理完毕后调用ContinueDebugEvent使得程序继续执行,等待新的调试事件,操作系统恢复进程中的所有线程。
在第一个线程被恢复的时候会去调用Ntdll.dll中的DbgBreakPoint函数
而DbgBreakPoint其实就是一个int3,利用这一点可以hook掉DbgBreakPoint直接调用终止进程函数,使得附加进程的时候直接结束。
反调试6-CRC32
CRC32:
CRC的全称是循环冗余校验,作用是为了检测数据的准确性,完整性。
CRC32检测的原理:
程序被编译后,代码是固定的,我们在调试程序的时候,下段或者修改汇编指令都会影响它的CRC32值。
这个时候只需要检测最初的CRC32跟某一时刻的CRC32值是否一致就能判断出代码是否被修改了。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
const UINT32 table[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
UINT32 make_crc32(unsigned char* buf, int nLength)
{
if (nLength < 1)
return 0xffffffff;
UINT32 crc = 0;
for (int i(0); i != nLength; ++i)
{
crc = table[(crc ^ buf[i]) & 0xff] ^ (crc >> 8);
}
crc = crc ^ 0xffffffff;
return crc;
}
void test1()
{
int a = 0;
a++;
int b = a + 1;
printf("11111111\n");
}
void test2()
{
int a = 0;
a++;
int b = a + 1;
printf("22222222\n");
}
void test3()
{
int a = 0;
a++;
int b = a + 1;
printf("33333333\n");
}
void test4()
{
int a = 0;
a++;
int b = a + 1;
printf("444444444\n");
}
int main() {
// 那么我们肯定是对程序的代码段进行检测,代码段运行之后肯定就不会去修改了
CHAR* buff = (CHAR*)GetModuleHandleA(NULL); // 获取当前的exe模块基址
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buff;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buff);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&pNtHeader->OptionalHeader;
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
// 前两个区段是代码段 直接偷懒
UINT32 crc1 = make_crc32((unsigned char*)(pSectionHeader->VirtualAddress + buff), pSectionHeader->Misc.VirtualSize);
pSectionHeader++;
UINT32 crc2 = make_crc32((unsigned char*)(pSectionHeader->VirtualAddress + buff), pSectionHeader->Misc.VirtualSize);
// 这样前两个区段都加密完毕
printf("crc1=%x\n", crc1);
printf("crc2=%x\n", crc2);
system("pause");
return 0;
}
反调试7-过CRC校验,虚拟机检测
过CRC检测思路:
CRC检测必然会取代码段数据进行计算CRC值,可以结合CE搜索哪里访问了该代码找到关键判断点。
找到计算crc值的call 修改为固定返回值,这样不管我们怎么改都是返回初始的值。
虚拟机的检测:
1.虚拟机环境检测:
检测wmtoolsd.exe进程
检测C:\\Program Files\\VMware\\VMware Tools\\wmtoolsd.exe文件是否存在
检测系统服务
2.in eax,dx指令
在真实的环境中会触发异常,而在虚拟机中不会
push edx
push ecx
push ebx //保存环境
mov eax, 'VMXh'
mov ebx, 0 //将ebx清零
mov ecx, 10 //指定功能号,用于获取VMWare版本,为0x14时获取VM内存大小
mov edx, 'VX' //端口号
in eax, dx //从端口edx 读取VMware到eax
cmp ebx, 'VMXh' //判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
setz [bResult] //设置返回值
pop ebx //恢复环境
pop ecx
pop edx
__try
{
__asm
{
push edx
push ecx
push ebx //保存环境
mov eax, 'VMXh'
mov ebx, 0 //将ebx清零
mov ecx, 10 //指定功能号,用于获取VMWare版本,为0x14时获取VM内存大小
mov edx, 'VX' //端口号
in eax, dx //从端口edx 读取VMware到eax
cmp ebx, 'VMXh' //判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
setz [bResult] //设置返回值
pop ebx //恢复环境
pop ecx
pop edx
}
}__except (1) //如果未处于VMware中,则触发此异常
{
printf("当前处于正常环境\n");
return;
}
printf("当前处于虚拟机环境\n");
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
int main() {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32 pe32{0};
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"vmtoolsd.exe")) {
printf("处于虚拟机环境\n");
ExitProcess(0);
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
system("pause");
return 0;
}
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
int main() {
/*
[in, optional] lpMachineName
目标计算机的名称。如果指针为 NULL 或指向空字符串,则该函数将连接到本地计算机上的服务控制管理器。
[in, optional] lpDatabaseName
服务控制管理器数据库的名称。此参数应设置为 SERVICES_ACTIVE_DATABASE。如果为 NULL,则默认情况下打开SERVICES_ACTIVE_DATABASE数据库。
[in] dwDesiredAccess
对服务控制管理器的访问。有关访问权限的列表,请参阅服务安全性和访问权限。
*/
SC_HANDLE scScmanger = OpenSCManagerA(NULL, NULL ,SC_MANAGER_ALL_ACCESS);
LPENUM_SERVICE_STATUSA lpService = NULL; // 查询结果
lpService = (LPENUM_SERVICE_STATUSA)LocalAlloc(LPTR, 1024 * 64);
DWORD pcbBytesNeeded = 0; // 如果缓冲区太小,这个保存还有多少没接收
DWORD lpServicesReturned = 0; // 返回实际的服务数量
DWORD resumeHanle = 0;
// 遍历系统服务
BOOL ret = EnumServicesStatusA(
scScmanger,
SERVICE_WIN32,// 要枚举的服务类型
SERVICE_STATE_ALL, // 要枚举的服务状态 运行还是停止还说啥
lpService, // 接收结果的缓冲区
1024 * 64, // 缓冲区的大小
&pcbBytesNeeded, // 如果缓冲区太小,这个保存还有多少没接收
&lpServicesReturned, // 返回实际的服务数量
&resumeHanle); // 首次调用此函数时,必须将此值设置为零。在输出时,如果函数成功,则此值为零。
if (ret == FALSE)
{
return 0;
}
for (size_t i = 0; i < lpServicesReturned; i++) {
if (strstr(lpService[i].lpDisplayName, "VMware") == 0) {
// 如果包含 VMware
MessageBoxA(0, "检测到虚拟机", "提示", MB_OK);
break;
}
}
if (scScmanger){
CloseServiceHandle(scScmanger);
}
system("pause");
return 0;
}
反调试-调试器原理
调试进程的两种情况:
1.打开进程
2.附加进程
打开进程的方式:
要调试进程需要调用CreateProcess和DEBUG_PROCESS标志来启动一个新进程作为调试目标。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
int main(int argc,char *argv[]) {
STARTUPINFOA si = {0};
si.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi = {0};
BOOL ret = CreateProcessA(NULL, argv[1], NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
system("pause");
return 0;
}
附加方式:
通过DebugActiveProcess附加到进程
注意:无论是CreateProcess创建调试目标,还是通过DebugActiveProcess创建调试目标,在调试器与操作系统之间的交互方式都是相同的。
3.创建了调试进程后接下来就是死循环等待调试事件:
当调试进程的时候,被调试进程执行的一些操作将会通知给调试器,例如动态库的加载和卸载,线程创建与退出,异常信息等。
当这些事件需要被发送到调试器的时候,Windows内核将首先挂起进程中的所有线程,然后把发生的事件通知给调试器,并且等待来自调试器的处理。
调试器通过WaitForDebugEvent等待调试事件,调试事件被封装到DEBUG_EVENT结构体中,调试器需要做的就是循环接收调试事件,并处理结构体中传递过来的不同的调试信息。
这里有一点需要注意,当发送事件给调试器的时候,被调试进程会挂起。
直到调试器调用ContinueDebugEvent函数。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
/*
我们以调试的方式创建目标进程
那么目标进程就不能再被其他调试器进程调试了
*/
int main() {
// FateMouse.exe
STARTUPINFOA si{0};
si.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi{0};
BOOL ret = CreateProcessA(
"FateMouse.exe",
NULL,
NULL,
FALSE,
DEBUG_PROCESS,
NULL,
NULL,
NULL,
&si,
&pi
);
if (!ret) {
// 进程打开失败
MessageBoxA(NULL, "打开进程失败!", "提示", NULL);
}
while (true) {
DEBUG_EVENT dev = {0};
ret = WaitForDebugEvent(&dev,INFINITE); // 等待被调试进程发送事件
if (ret) {
// 有要处理的事件
printf("EventCode=%d\n", dev.dwDebugEventCode); // 调试的事件类型
// 处理完了,要恢复挂起的被调试线程
ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId,DBG_CONTINUE);
}
}
system("pause");
return 0;
}
反调试9-过掉Strong Od反反附加
反附加:
DebugActiveProcess附加进程调试,最终会调用DebugBreakPoint使程序触发异常暂停下来。
这种方式很容易被干掉,我们来看一下在OD是无效的。
77294D10
DebugActiveProcess到底做了那些事情?
在目标进程创建一个线程,目标线程最后调用DebugBreakPoint触发异常暂停下来。
call ntdll.DbgUiIssueRemoteBreakin 这个远程断点是怎么实现的
创建一个远程线程完了之后,让这个线程从DbgUiRemoteBreakin的位置开始执行,远程线程去执行了一个功能,这个功能触发了int3断下来了。
//NtClose
DebugActiveProcess--->ntdll.DbgUiDebugActiveProcess-->call ntdll.DbgUiIssueRemoteBreakin
//push ntdll.DbgUiRemoteBreakin -->ntdll.ZwCreateThreadEx
DbgUiIssueRemoteBreakin//这个函数就相当于 创建了一个远程线程,完了之后呢,让这个线程从
DbgUiRemoteBreakin 这个函数执行。---》call ntdll.DbgBreakPoint DEBUG_Event
这就是为什么我们附加进程,可以让目标进程停下来。
调试器主要就是依靠DEBUG_EVENT
Strong OD 反反附加的原理:
LdrInitializeThunk
调用 NtCreateThreadEx 创建新线程,该函数最终执行会到ring0,ring0会先去调用LdrInitializeThunk
LdrInitializeThunk---》 call ntdll.ZwContinue重新回到内核,这时NtCreateThreadEx 才会返回。
Strong OD就是直接HOOK了LdrInitializeThunk,不让你进行返回到NtCreateThreadEx,后面的东西就不会执行了。
如何对抗?
调试器时附加到我们目标进程进行调试的,这也就意味着我们的目标进程时站先机的,我们可以在调试器hook之前先进行hook,这样达到反附加效果,轻松过掉strong od的反反附加
创建一个线程:这个线程做什么呢? 写一个循环 不停的检测,一旦检测到LdrInitializeThunk 这个函数的前五个字节被修改了,我们直接给他改回去。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
DWORD g_LdrInitializeFunAddr;
BYTE g_LdrInitializeFunCode[5];
/*
调用 NtCreateThreadEx 创建新线程,该函数最终执行会到ring0,ring0会先去调用LdrInitializeThunk
LdrInitializeThunk---》 call ntdll.ZwContinue重新回到内核,这时NtCreateThreadEx 才会返回。
而String OD就是HOOK了Ldr,让回不去NtCreateThreadEx。
那么我们需要在Stoing OD HOOK之前先进行HOOK 保存,然后循环当检测到Strong OD HOOK了,我们马上修改
*/
BOOL SaveLdrInitializeCode() {
DWORD funAddr = (DWORD)GetProcAddress(GetModuleHandleA("ntdll.dll"),"LdrInitializeThunk");
g_LdrInitializeFunAddr = funAddr;
DWORD oldProtect;
VirtualProtect((LPVOID)funAddr, 0x5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(g_LdrInitializeFunCode,(LPVOID)funAddr, 0x5); // 保存被HOOK前的 前5个字节
VirtualProtect((LPVOID)funAddr, 0x5, oldProtect, &oldProtect);
return TRUE;
}
DWORD WINAPI CheckLdrInitializeCodeProc(LPVOID args) {
while (true) {
DWORD newCode = *(DWORD*)g_LdrInitializeFunAddr; // 直接取四个字节出来比较 1个字节其实也行 5个也
if (newCode != *(DWORD*)g_LdrInitializeFunCode) { // 然后进行四个字节的比较
// 不同就表示当前被Strong od 给hook了,我们填充回去 原本的5个字节
printf("发现Strong OD进行Hook操作\n");
DWORD oldProtect;
VirtualProtect((LPVOID)g_LdrInitializeFunAddr, 0x5, PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy((LPVOID)g_LdrInitializeFunAddr, g_LdrInitializeFunCode, 0x5);
VirtualProtect((LPVOID)g_LdrInitializeFunAddr, 0x5, oldProtect, &oldProtect);
break;
}
}
return 0;
}
BOOL hookDebugBreakPoint() {
PBYTE funAddr = (PBYTE)GetProcAddress(GetModuleHandleA("ntdll.dll"), "DbgBreakPoint");
DWORD oldProtect;
VirtualProtect(funAddr, 0x1, PAGE_EXECUTE_READWRITE, &oldProtect);
funAddr[0] = 0xC3; // 把CC int 3 变为 C3 retn
VirtualProtect(funAddr, 0x1, oldProtect, &oldProtect);
return TRUE;
}
int main() {
SaveLdrInitializeCode();
hookDebugBreakPoint();
HANDLE hThread = CreateThread(NULL, 0, CheckLdrInitializeCodeProc, NULL, 0, NULL);
while (true)
{
printf("hello star hook\n");
Sleep(3000); // 测试
}
system("pause");
return 0;
}
流程:
-修改了DbgBreakPoint这样就附加不上了
-然后在Strong Od进行HOOK之前我们进行保存
-开启一个线程监听Strong Od的Hook操作。发现被Hook掉了,修改回去
-这样就可以重新执行到DbgBreakPoint继续附加不上
反调试11-环境检测-TF标志位
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
// 检测调试器窗口
BOOL checkDbgWindow() {
HWND hWin = FindWindowA(NULL, "xdbg32");
if (hWin) {
printf("打开了x32Dbg\n");
return TRUE;
}
return FALSE;
}
// 检测进程
BOOL checkDbgProcess() {
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (!hSnapShot) {
return FALSE;
}
PROCESSENTRY32 pe32{ 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapShot, &pe32)) {
do {
if (wcscmp(pe32.szExeFile, L"xdbg32.exe") == 0) {
printf("打开了x32Dbg\n");
CloseHandle(hSnapShot);
return TRUE;
}
}while(Process32Next(hSnapShot, &pe32));
}
CloseHandle(hSnapShot);
return FALSE;
}
DWORD WINAPI CheckDbgProc(LPVOID args) {
while (true) {
if (checkDbgWindow()) {
break;
}
if (checkDbgProcess()) {
break;
}
}
return 0;
}
int main(){
HANDLE hThread = CreateThread(NULL, 0, CheckDbgProc, NULL, 0, NULL);
while (true) {
printf("test\n");
Sleep(3000);
}
system("pause");
return 0;
}
标志寄存器中的TF(Trap Flag)位,CPU在单步执行完一条指令后,如果检测到标志寄存器的TF位为1,则会产生一个int 1中断,然后再将TF置为0,后进行int 1中断后继续执行。
即TF为1,执行完我们完我们的nop指令以后,会产生异常,如果有调试器会交给调试器,调试器会继续执行。
如果没有调试器那么就发生异常了。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
using namespace std;
void HaveStep()
{
cout << "检测到了单步调试" << endl;
ExitProcess(0);
}
void NoStep()
{
cout << "没有检测到单步调试" << endl;
}
void checkTFflag() {
DWORD addr = (DWORD)HaveStep;
__try {
__asm {
pushfd; // 保存标志寄存器
or dword ptr ss : [esp] , 0x100; // 把第八位TF标志位设置为1
popfd; // 还原标志寄存器
nop; // 设置为1就会进入单步执行, 单步执行我让他执行nop指令
// 如果程序处于调试模式,直接退出进程
jmp addr;
};
}__except (1) {
NoStep();
}
}
int main(){
checkTFflag();
system("pause");
return 0;
}
只有单步执行才有效
反调试12-通过程序子窗口特点检测
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
using namespace std;
BOOL CALLBACK enumWindowProc(HWND hwnd, LPARAM lParam) {
CHAR buff[0x100] = { 0 };
SendMessageA(hwnd, WM_GETTEXT, 0x100, (LPARAM)buff);
printf("%s\n",buff);
return TRUE; // 继续枚举
}
int main(){
// 获取桌面窗口
HWND hDeskTop = GetDesktopWindow();
HWND hDeskChild = GetWindow(hDeskTop,GW_CHILD); // 获取桌面窗口的子窗口
while (hDeskChild) {
EnumChildWindows(hDeskChild, enumWindowProc, NULL); // 枚举所有子窗口
hDeskChild = GetWindow(hDeskChild, GW_HWNDNEXT); // 获取同级别的下一个窗口
}
system("pause");
return 0;
}
关闭其他进程句柄【解决游戏限制多开之类的】
步骤:
查看某进程拥有那些句柄
1.Ntdll的NtQueryInformationProcess函数查询指定进程句柄信息。
2.NtQueryObject获取某个句柄信息,遍历句柄,判断句柄是否为有效句柄:DuplicaeHandle函数返回成功表示有效
3.获取句柄名称判断是否是我们要关闭的句柄
4.DulicateHandle复制句柄并关闭源句柄,最后关闭复制到句柄。
#include <Windows.h>
#include <iostream>
#include <TlHelp32.h>
using namespace std;
typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers, // Note: this is kernel mode only
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
ProcessDeviceMap,
ProcessSessionInformation,
ProcessForegroundInformation,
ProcessWow64Information,
ProcessImageFileName,
ProcessLUIDDeviceMapsEnabled,
ProcessBreakOnTermination,
ProcessDebugObjectHandle,
ProcessDebugFlags,
ProcessHandleTracing,
ProcessIoPriority,
ProcessExecuteFlags,
ProcessResourceManagement,
ProcessCookie,
ProcessImageInformation,
MaxProcessInfoClass
} PROCESSINFOCLASS;
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectHandleFlagInformation,
ObjectSessionInformation,
MaxObjectInfoClass
} OBJECT_INFORMATION_CLASS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT* Buffer;
#else // MIDL_PASS
PWSTR Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef struct _OBJECT_NAME_INFORMATION { // ntddk wdm nthal
UNICODE_STRING Name; // ntddk wdm nthal
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION; // ntddk wdm nthal
typedef NTSTATUS(NTAPI* _NtQueryInformationProcess)(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);
typedef NTSTATUS(NTAPI* _NtQueryObject)(
__in HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
__out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
__in ULONG ObjectInformationLength,
__out_opt PULONG ReturnLength
);
_NtQueryInformationProcess NtQueryInformationProcess;
_NtQueryObject NtQueryObject;
int main(){
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 15192);
HMODULE hNtDll = GetModuleHandleA("ntdll.dll");
NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
NtQueryObject = (_NtQueryObject)GetProcAddress(hNtDll, "NtQueryObject");
// 遍历所有的句柄信息
DWORD handleCount = 0;
NtQueryInformationProcess(
hProcess, // 要查询的进程句柄
ProcessHandleCount, // 查询句柄数量
&handleCount,
sizeof(DWORD),
NULL
);
DWORD handleData = 4;
HANDLE copyHandleData;
OBJECT_NAME_INFORMATION *ni = (POBJECT_NAME_INFORMATION)malloc(0x1000);
// 句柄他都是4的整数倍,一个进程的有效句柄,我们可以认为是4开始的。
for (size_t i = 0; i < handleCount; handleData+=4) {
// 判断句柄是否有效
BOOL ret = DuplicateHandle(
hProcess, // 从哪个进程复制
(HANDLE)handleData, // 要复制的进程句柄
GetCurrentProcess(), // 复制到哪个进程
©HandleData, // 复制到哪个变量去
0, // 填0就好
FALSE,// 是否可以被继承
DUPLICATE_SAME_ACCESS // 原样复制
);
if (ret) {
NtQueryObject(
copyHandleData, // 要获取的句柄
ObjectNameInformation, // 获取名称信息
ni, // 缓冲区
0x1000, // 缓冲区大小
NULL
);
printf("句柄名称:%ls\n",ni->Name.Buffer);
// 判断是不是我们要的句柄
if (ni->Name.Length != 0 && wcscmp(ni->Name.Buffer, L"\\Sessions\\11\\BaseNamedObjects\\FateMouse")) {
DuplicateHandle(
hProcess, // 从哪个进程复制
(HANDLE)handleData, // 要复制的进程句柄
GetCurrentProcess(), // 复制到哪个进程
©HandleData, // 复制到哪个变量去
0, // 填0就好
FALSE,// 是否可以被继承
DUPLICATE_CLOSE_SOURCE // 关闭原句柄
);
CloseHandle(copyHandleData); // 关闭复制过来的句柄
break;
}
CloseHandle(copyHandleData); // 关闭复制过来的句柄
i++; // 如果是有效的句柄 我们才找下一个句柄
}else {
CloseHandle(copyHandleData);
continue;
}
}
system("pause");
return 0;
}