反调试与反反调试

您所在的位置:网站首页 windbg环境配置 反调试与反反调试

反调试与反反调试

2023-06-13 14:01| 来源: 网络整理| 查看: 265

参考文本

(190条消息) C++ 反反调试(NtQueryInformationProcess)_(-: LYSM :-)的博客-CSDN博客

Windows 平台反调试相关的技术方法总结—part 2 - 先知社区

C/C++ MinHook 库的使用技巧 - lyshark - 博客园 (cnblogs.com)

(177条消息) C++ 反反调试(NtQueryInformationProcess)_(-: LYSM :-)的博客-CSDN博客

https://www.cnblogs.com/BjblCracked/p/3470351.html

[原创]分析实战读书笔记12_常见反调试-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

https://www.cnblogs.com/Sna1lGo/p/15206766.html

CheckRemoteDebuggerPresent function (debugapi.h) - Win32 apps | Microsoft Docs

反调试技术常用API,用来对付检测od和自动退出程序_aijia1857的博客-CSDN博客^v81^insert_down38,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=OD%E4%BF%AE%E6%94%B9forceflag&spm=1018.2226.3001.4187

Windows下反反调试技术汇总 - FreeBuf网络安全行业门户

(165条消息) 汇编语言 + Visual Studio 2019——Visual Studio 2019 中汇编语言环境解决方案_Starzkg的博客-CSDN博客

(165条消息) C/C++ 程序反调试的方法beingdebugged孤风洗剑的博客-CSDN博客

PEB和TEB资料整合 - -Vi - 博客园 (cnblogs.com)

(167条消息) 反调试 - PEB(BeingDebugged ,NtGlobalFlag)_「已注销」的博客-CSDN博客

(167条消息) 180306 逆向-反调试技术(1)BeingDebugged_奈沙夜影的博客-CSDN博客

(167条消息) 反调试技术总结(看雪)常见的反调试技术欧晨

26种对付反调试的方法_网易订阅

(167条消息) 反调试 - NtQueryInformationProcess(ProcessDebugPort,ProcessDebugFlags,ProcessDebugObjectHandle)_(-: LYSM :-)的博客-CSDN博客

TLS及反调试机制 - ylh666 - 博客园 (cnblogs.com)

C/C++ 程序反调试方法总结 - lyshark - 博客园 (cnblogs.com)

(167条消息) 由一道CTF对10种反调试的探究ctf 反调试0xDQ的博客-CSDN博客

(167条消息) 反调试技术总结_weixin_34007906的博客-CSDN博客

检测OD进程名 · 逆向工程笔记整理 · 看云

汇编运行环境配置

1、安装MASM32:Download The MASM32 SDK

2、配置Visusl Studio

右击项目,选择生成依赖项-生成自定义,选择masm自定义文件

右键项目->项目属性 添加依赖项将masm的bin目录添加到附加库目录中

tip:使用的是32位调试器

前置概念 TEB

TEB(Thread Environment Block,线程环境块)系统在此TEB中保存频繁使用的线程相关的数据。位于用户地址空间,在比 PEB 所在地址低的地方。进程中的每个线程都有自己的一个TEB。一个进程的所有TEB都以堆栈的方式,存放在从0x7FFDE000开始的线性内存中,每 4KB为一个完整的TEB,不过该内存区域是向下扩展的。在用户模式下,当前线程的TEB位于独立的4KB段,可通过CPU的FS寄存器来访问该段,一般存储在[FS:0]。

PEB 里其实本来有很多可以用来检测调试器的成员(虽然有的本意不一定是,但确实在被调试时会有固定变化),但是在 Win7 之后,能用的只剩下了两个

- typedef struct _PEB { // Size: 0x1D8 - /*000*/ UCHAR InheritedAddressSpace; - /*001*/ UCHAR ReadImageFileExecOptions; - /*002*/ UCHAR BeingDebugged; // 无调试器时 = 0,有调试器时 = 1 - /*003*/ UCHAR SpareBool; - /*004*/ HANDLE Mutant; - /*008*/ HINSTANCE ImageBaseAddress; - /*00C*/ VOID *DllList; - /*010*/ PPROCESS_PARAMETERS *ProcessParameters; - /*014*/ ULONG SubSystemData; - /*018*/ HANDLE ProcessHeap ; - /*01C*/ KSPIN_LOCK FastPebLock; - /*020*/ ULONG FastPebLockRoutine; - /*024*/ ULONG FastPebUnlockRoutine; - /*028*/ ULONG EnvironmentUpdateCount; - /*02C*/ ULONG KernelCallbackTable; - /*030*/ LARGE_INTEGER SystemReserved; - /*038*/ ULONG FreeList; - /*03C*/ ULONG TlsExpansionCounter; - /*040*/ ULONG TlsBitmap; - /*044*/ LARGE_INTEGER TlsBitmapBits; - /*04C*/ ULONG ReadOnlySharedMemoryBase; - /*050*/ ULONG ReadOnlySharedMemoryHeap; - /*054*/ ULONG ReadOnlyStaticServerData; - /*058*/ ULONG AnsiCodePageData; - /*05C*/ ULONG OemCodePageData; - /*060*/ ULONG UnicodeCaseTableData; - /*064*/ ULONG NumberOfProcessors; - /*068*/ LARGE_INTEGER NtGlobalFlag; // 有调试器时会被赋值为 70h = 112 - /*070*/ LARGE_INTEGER CriticalSectionTimeout; - /*078*/ ULONG HeapSegmentReserve; - /*07C*/ ULONG HeapSegmentCommit; - /*080*/ ULONG HeapDeCommitTotalFreeThreshold; - /*084*/ ULONG HeapDeCommitFreeBlockThreshold; - /*088*/ ULONG NumberOfHeaps; - /*08C*/ ULONG MaximumNumberOfHeaps; - /*090*/ ULONG ProcessHeaps; - /*094*/ ULONG GdiSharedHandleTable; - /*098*/ ULONG ProcessStarterHelper; - /*09C*/ ULONG GdiDCAttributeList; - /*0A0*/ KSPIN_LOCK LoaderLock; - /*0A4*/ ULONG OSMajorVersion; - /*0A8*/ ULONG OSMinorVersion; - /*0AC*/ USHORT OSBuildNumber; - /*0AE*/ USHORT OSCSDVersion; - /*0B0*/ ULONG OSPlatformId; - /*0B4*/ ULONG ImageSubsystem; - /*0B8*/ ULONG ImageSubsystemMajorVersion; - /*0BC*/ ULONG ImageSubsystemMinorVersion; - /*0C0*/ ULONG ImageProcessAffinityMask; - /*0C4*/ ULONG GdiHandleBuffer[0x22]; - /*14C*/ ULONG PostProcessInitRoutine; - /*150*/ ULONG TlsExpansionBitmap; - /*154*/ UCHAR TlsExpansionBitmapBits[0x80]; - /*1D4*/ ULONG SessionId; - } PEB, *PPEB;

tip:不同版本的操作系统,可能会有不同,可以使用windbg进行查看

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

.reload

dt ntdll!teb

dt -rv ntdll!_TEB

PEB

PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息。位于用户地址空间。在Win 2000下,进程环境块的地址对于每个进程来说是固定的,在0x7FFDF000处,这是用户地址空间,所以程序能够直接访问。准确的PEB地址应从系统 的EPROCESS结构的0x1b0偏移处获得,但由于EPROCESS在系统地址空间,访问这个结构需要有ring0的权限。还可以通过TEB结构的偏 移0x30处获得PEB的位置,FS段寄存器指向当前的TEB结构:

FS段寄存器

FS寄存器指向当前活动线程的TEB结构

PEB标识位识别 BeingDebug反调试

通过汇编进行定位BeingDebug位,当BeingDebug位为1 时代表函数处于调试状态

#include #include 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; }

方案一:

手动dump fs:[30]+2找到BeingDebugged,把01修改为00,即可绕过BeingDebugged的反调试

在OllyDbg中安装命令行插件,在命令行窗口输入dump fs:[30]+2

这条命令会将BeingDebugged属性转储到转储面板窗口。右键单击BeingDebugged属性,将该位置设置为0

方案二:

OllyDbg的一些插件可以帮助我们修改BeingDebugged标志。其中最流行的有HideDebugger、Hidedebug和PhantOm,StrongOD。以strongOD为例,插件-》strongOD->options->hidePEB就可以进行绕过

究其根源都是由于BeingDebugged被设为True导致的,但是如果直接将BeingDebugged设置为false,则会导致u无法进行断点操作,因此如果找到一个时机,在NtGlobalFlag改变之前就设BeingDebugged为False即可绕过所有问题。

\1. 在第一次触发LOAD_DLL_DEBUG_EVENT的时候将BeingDebugged设为False,绕过NtGlobalFlag的改变

\2. 在第二次触发LOAD_DLL_DEBUG_EVENT的时候将BeingDebugged设为True,使系统中断正常触发

\3. 系统中断以后再将BeingDebugged清除掉

NtGlobalFlag 反调试

在PEB中的BeingDebugged被设为True后,还会有一些其他的变化产生。

加载时会将PEB中的NtGlobalFlag中的一个位改变,使其值为0x70,而正常状态下不是 在WRK中有一个宏也会随着NtGlobalFlag的改变而在RtlCreateHeap中用RtlDebugCreateHeap创建调试堆。这个调试堆中含有大量的标志(例如0xBAAD0F0D和0xFEEEFEEE等),而正常情况下这个地址中却没有有意义的数据。

在 32 位系统下,NtGlobalFlag 存在于 PEB 的 0x68 的位置。该值默认为 0,当调试器附加后,会设置以下标志。也就是说,我们可以通过判断NtBlobalFlag位的值,来判断程序是否处于调试状态

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

以上两种的原理基本相同,就是作为检测的标识位不同

tip:该方法可以区分vs2022的程序调试器和OD的调试器,进行实验如下,在vs2022的32位调试器中显示程序正常,但是在没有插件的OD中却被检测出调试

 

绕过方案

方案一、手动dump fs:[30]+68找到BeingDebugged,把70修改为00,即可绕过NtGlobalFlag的反调试

 

 

方案二:开启strongOD 的HidePeb选项  

ProcessHeap 反调试

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

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

TIP:由于偏移地址可能不同,所以这里提供查找偏移地址的方法

使用windbg加载任意二进制文件

.reload

dt !_peb

!heap

!heap -a 1270000

dt _HEAP 1270000

#include #include 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 \n", ret); } else { printf("ForceFlag反调试正常%d\n", ret); } int ret2 = IsDebugB(); if (ret2 != 2) { printf("进程正在被调试: %x \n", ret2); } else { printf("Flags反调试正常%d\n", ret); } system("pause"); return 0; }

 同样该程序,可以区分VS2022调试器和OD调试器

绕过方案

strongOD中勾选HidePEB

WinAPI反调试 IsDebuggerPresent 函数反调试

Win32API为程序提供了IsDebuggerPresent来判断自己是否处于调试状态,这个函数读取了当前进程PEB中的BeingDebugged标志当该标志位的值为1时,则证明函数处于调试状态,由于和之前BeingDebug的原理相同因此绕过方式也相同,就不再进行绕过演示

#include #include 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; }

ZwQueryInformationProcess()

ZwQueryInformationProcess()这个函数来读取到程序中的PEB数据,然后判断PebBase+0x68是否等于70,本质上和汇编定位NtGlobalFlag是相同的,绕过方式也相同

#include #include #include typedef NTSTATUS(NTAPI* typedef_ZwQueryInformationProcess)( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ); BOOL 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("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 == 112) { printf("%d\n", NtGlobalFlag); return true; } } } CloseHandle(hProcess); } return false; } int main(int argc, char* argv[]) { if (IsDebug()) { printf("正在被调戏. \n"); } else { printf("程序正常"); } system("pause"); return 0; }

该方法的原理和识别NtGlobalFalg基本相同,同样可以识别VS2022和OD基本可以使用同样的方法进行绕过

绕过方案

方案一:手动dump fs:[30]+68找到NtGlobalFlag,把0x70修改为00,即可绕过NtGlobalFlag的反调试,tip此时该位的显示一般为p

方案二:勾选插件中的HidePEB选项,和之前一样,这次不再进行演示

NtQueryInformationProcess

CheckRemoteDebuggerPresent function (debugapi.h) - Win32 apps | Microsoft Docs

 这个函数是Ntdll.dll中一个API,它用来提取一个给定进程的信息。它的第一个参数是进程句柄,第二个参数告诉我们它需要提取进程信息的类型。为第二个参数指定特定值并调用该函数,相关信息就会设置到第三个参数。第二个参数是一个枚举类型,其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)。例如将该参数置为ProcessDebugPort,如果进程正在被调试,则返回调试端口,否则返回0。

__kernel_entry NTSTATUS NtQueryInformationProcess( IN HANDLE ProcessHandle, // 进程句柄 IN PROCESSINFOCLASS ProcessInformationClass, // 检索的进程信息类型 OUT PVOID ProcessInformation, // 接收进程信息的缓冲区指针 IN ULONG ProcessInformationLength, // 缓冲区指针大小 OUT PULONG ReturnLength // 实际接收的进程信息大小 );

进程处于被调试状态时,ProcessDebugPort = 0xffffffff

#include #include #include #pragma region 依赖 typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)( _In_ HANDLE ProcessHandle, _In_ UINT ProcessInformationClass, _Out_ PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength ); #pragma endregion int main(int argc, CHAR* argv[]) { pfnNtQueryInformationProcess NtQueryInformationProcess = NULL; // 存放 ntdll 中 NtQueryInformationProcess 函数地址 NTSTATUS status; // NTSTATUS 错误代码,0:执行成功 DWORD isDebuggerPresent = -1; // 如果当前被调试,则 = ffffffff HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll")); // ntdll 模块句柄 // ntdll 加载成功 if (hNtDll) { // 取 NtQueryInformationProcess 函数地址 NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess"); // 取地址成功 if (NtQueryInformationProcess) { // NtQueryInformationProcess 检测调试器 status = NtQueryInformationProcess( GetCurrentProcess(), // 进程句柄 0x7, // 要检索的进程信息类型,ProcessDebugPort:调试器端口号 &isDebuggerPresent, // 接收进程信息的缓冲区指针 sizeof(DWORD), // 缓冲区大小 NULL // 实际返回进程信息的大小 ); // NtQueryInformationProcess 执行成功 if (status == 0 && isDebuggerPresent != 0) { // 输出 std::cout Debugging Options->Exceptions来设置把异常传递给应用程序。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3