[toc]
课堂复习
异常的分发共有几轮?
两轮,KiDispatchException 函数的最后一个参数 FirstChance 表示当前是第几次进行异常分发,另一个函数 RaiseException 的最后一个参数也表示当前是第几次进行分发
通过什么可以区分当前所处的是 R0 还是 R3?
windows 下,代码被分为 R3 和 R0 权限,CS 寄存器的最低2位表示当前所处的是3环(用户)还是0环(内核),可通过 mov eax,cs test eax,1 来区分
异常产生方式有几种?
- CPU 满足特定条件后内部主动产生的异常,类似 int 3(IDT)
- 用户通过
RaiseException 构建 ExceptionRecord 主动抛出异常(KiDispatchException)
编译器会为用户自定义的 __try,__except 添加怎样的异常处理函数?
同一个函数内,无论用户编写多少个 SEH,编译器只会安装一个 except_handler4
当用户模式下产生异常时,SEH 函数会在什么时候被调用?
int 3 -> idt[3] -> _KiTrap03 -> CommonDispatchException -> KiDispatchException -> KeUserExceptionDispatcher(3) -> RtlDispatchException(3) -> RtlpExcuteHandlerForException(3) -> except_handler4 -> except_handler4_common
在 R0 中异常是如何被传递给3环调试器的?
DbgkForwardException -> DbgkpSendApiMessage -> 3环调试器
R0 和 R3 的 RtlDispatchException 有什么区别?
KiDispatchException(0) -> RtlDispatchException(0) -> SEH
KiUserExceptionDispatcher(3) -> RtlDispatchException (3) -> VEH SEH UEH (VCH)
反调试与反反调试
静态反调试
静态反调试一般在调试开始时阻拦调试者,调试者只需找到原因后可一次性突破
PEB
对于所有的用户层 PEB 静态反调试,可以在程序正式运行前先挂起用户程序,然后修改相应的字段为非调试状态,再继续执行
kernel32!IsDebuggerPresent() API 检测进程环境块(PEB)中的 BeingDebugged 标志,检查这个标志以确定进程是否正在被用户模式的调试器调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <windows.h>
bool CheckBeingDebugged() { __asm { ; 通过 FS:[0x30] 获取到 PEB 结构体的地址 mov eax, dword ptr fs:[0x30] ; 通过 PEB 偏移为 0x02 的地方获取到 BeingDebugged movzx eax, byte ptr [eax + 0x02] } } int main() { if (CheckBeingDebugged()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
调用 IsDebuggerPresent 字段,间接读 BeingDebugged 字段
1 2 3 4 5 6 7 8 9 10
| int main() { if (IsDebuggerPresent()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
通常程序未被调用时,PEB 另一个成员 NtGlobalFlag(偏移0x68)值为0,若进程被调试,通常值为0x70
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| bool CheckNtGlobalFlag() { int NtGlobalFlag = 0; __asm { ; 通过 TEB 偏移为 0x30 找到 PEB 结构 mov eax, dword ptr fs : [0x30]
; 通过 PEB 偏移为 0x68 的地方找到 NtGlobalFlag mov eax, dword ptr[eax + 0x68]
; 将结果保存到变量,目的是方便比较 mov NtGlobalFlag, eax } return NtGlobalFlag == 0x70 ? true : false; } int main() { if (CheckNtGlobalFlag()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n");
system("pause"); return 0; }
|
_HEAP 结构不是一个公开的结构体,不同版本的 nt 内核可能对这个结构体有不同的实现,所以兼容性不够强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| bool CheckProcessHeap() { int Flags = 0, ForceFlags = 0; __asm { ; 通过 fs : [0x30] 可以找到 PEB 的地址 mov eax, dword ptr fs:[0x30]
; 通过 PEB 偏移为 0x18 的位置找到 ProcessHeap(_HEAP) mov eax, dword ptr [eax + 0x18]
; 通过 _HEAP 偏移为 0x40 和 0x44 的字段找到两个标志 mov ecx, dword ptr [eax + 0x40] mov Flags, ecx mov ecx, dword ptr [eax + 0x44] mov ForceFlags, ecx } printf("%08X %08X\n", Flags, ForceFlags); return Flags != 2 || ForceFlags != 0; } int main() { if (CheckProcessHeap()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
使用原始API
对于所有使用函数进行反调试的情况,都可以使用 ApiHook 来进行反反调试,但是要注意 NtQueryInformationProcess 功能非常多,在函数内应该过滤和调试相关的枚举值进行操作,不应该影响到其它的查询信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream> #include <windows.h> #include <winternl.h> #pragma comment(lib,"ntdll.lib")
bool CheckProcessDebugPort() { int nDebugPort = 0; NtQueryInformationProcess( GetCurrentProcess(), ProcessDebugPort, &nDebugPort, sizeof(nDebugPort), NULL); return nDebugPort == 0xFFFFFFFF ? true : false; } int main() { if (CheckProcessDebugPort()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| bool CheckProcessDebugObjectHandle() { HANDLE hProcessDebugObjectHandle = 0; NtQueryInformationProcess( GetCurrentProcess(), (PROCESSINFOCLASS)0x1E, &hProcessDebugObjectHandle, sizeof(hProcessDebugObjectHandle), NULL); return hProcessDebugObjectHandle ? true : false; } int main() { if (CheckProcessDebugObjectHandle()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| bool CheckProcessDebugFlag() { BOOL bProcessDebugFlag = 0; NtQueryInformationProcess( GetCurrentProcess(), (PROCESSINFOCLASS)0x1F, &bProcessDebugFlag, sizeof(bProcessDebugFlag), NULL); return bProcessDebugFlag ? false : true; } int main() { if (CheckProcessDebugFlag()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
当一个普通的程序被双击打开时,实际是被资源管理器(Explorer)打开的,所以普通程序的父进程应该就是资源管理器,若检查到自己的父进程不是,就是被调试了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| bool CheckParentProcess() { struct PROCESS_BASIC_INFORMATION { ULONG ExitStatus; PPEB PebBaseAddress; ULONG AffinityMask; LONG BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; }stcProcInfo; NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &stcProcInfo, sizeof(stcProcInfo), NULL); DWORD ExplorerPID = 0; DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId; GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID); return ExplorerPID == CurrentPID ? false : true; } int main() { if (CheckParentProcess()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
查询当前系统的调试情况:NtQuerySystemInformation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| bool CheckSystemKernelDebuggerInformation() { struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { BOOLEAN KernelDebuggerEnabled; BOOLEAN KernelDebuggerNotPresent; } DebuggerInfo = { 0 }; NtQuerySystemInformation( (SYSTEM_INFORMATION_CLASS)0x23, &DebuggerInfo, sizeof(DebuggerInfo), NULL); return DebuggerInfo.KernelDebuggerEnabled; } int main() { if (CheckSystemKernelDebuggerInformation()) printf("当前处于[被]调试状态\n"); else printf("当前处于[非]调试状态\n"); system("pause"); return 0; }
|
动态反调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> #include <windows.h> typedef enum THREAD_INFO_CLASS { ThreadHideFromDebugger = 17 };
typedef NTSTATUS(NTAPI* ZW_SET_INFORMATION_THREAD)( IN HANDLE ThreadHandle, IN THREAD_INFO_CLASS ThreadInformaitonClass, IN PVOID ThreadInformation, IN ULONG ThreadInformationLength); void ZSIT_DetachDebug() { ZW_SET_INFORMATION_THREAD Func = (ZW_SET_INFORMATION_THREAD) GetProcAddress(LoadLibrary(L"ntdll.dll"), "ZwSetInformationThread"); Func(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL); } int main() { ZSIT_DetachDebug(); printf("runnning...\n"); system("pause"); return 0; }
|
1 2 3 4 5 6 7 8 9
| int main() { if (FindWindow(L"OllyDbg", NULL)) printf("存在调试器\n"); else printf("没检测到调试器\n");
return 0; }
|
OD插件
插件加载的原理
- 应用程序如何找到自己的插件:所有支持插件的应用程序都会存在一个插件路径,用户提供的就应该放置在这个路径底下,应用程序通过遍历文件的方式来确保能够找到插件
- 插件的存在形式:插件通常以 dll 文件形式存在,这些文件可以是任何后缀名结尾的,并不影响插件本身的功能
- 应用程序如何识别插件路径下的模块是否是插件:一个合格的插件,应该能提供相应的导出函数说明当前插件的名称,版本及能够支持的应用程序
- 插件如何提供功能:通过实现指定的导出函数可以提供相应的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include <vector> #include <string> #include <iostream> #include <windows.h> using namespace std;
using f_query = bool (*)(int v, char name[20], char version[20]); using f_run = void (*)();
typedef struct _PLUGIN_INFO { char name[20]; char version[20]; HMODULE module; } PLUGIN_INFO, *PPLUGIN_INFO;
vector<PLUGIN_INFO> plugins;
void init() { WIN32_FIND_DATAA FileInfo = { 0 };
HANDLE FindHandle = FindFirstFileA(".\\plugin\\*.plugin", &FileInfo);
if (FindHandle != INVALID_HANDLE_VALUE) { do { PLUGIN_INFO plugin_info = { 0 };
string path = string(".\\plugin\\") + FileInfo.cFileName;
plugin_info.module = LoadLibraryA(path.c_str());
if (plugin_info.module != NULL) { f_query f = (f_query)GetProcAddress(plugin_info.module, "query");
if (f != nullptr && f(1, plugin_info.name, plugin_info.version)) { printf("插件: [%s %s] 已经被加载了\n", plugin_info.name, plugin_info.version);
plugins.push_back(plugin_info); } }
} while (FindNextFileA(FindHandle, &FileInfo)); } }
void run() { for (auto& p : plugins) { f_run f = (f_run)GetProcAddress(p.module, "run"); if (f) f(); } }
void release() { for (auto& p : plugins) { f_run f = (f_run)GetProcAddress(p.module, "release"); if (f) f(); } }
void my_exit() { for (int i = 0; i < plugins.size(); ++i) { FreeLibrary(plugins[i].module); } } int main() { init(); run(); release(); my_exit(); return 0; }
|
提供的插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <windows.h> #include <iostream>
extern "C" __declspec(dllexport) bool query(int v, char name[20], char version[20]) { if (v != 1) return false; memcpy(name, "plugin2", 8); memcpy(version, "2.0", 4); return true; } extern "C" __declspec(dllexport) void run() { printf("这是插件提供的功能"); }
|