课前提问

1. 异常的分发一共有多少轮?

  • 一共有两轮。 KiDispatchExcption 函数的最后一个参数 FirstChance 表示当前是第几次进行异常的的分发,另一个函数 RaiseException 最后一个参数也表示当前是第几次进行分发

2. 通过什么可以区分当前所处的是 R0 还是 R3

  • 在 windows 下,代码被分为了 R3 和 R0 权限,CS 段寄存器的最低两位就表示当前所处的是 3环(用户) 还是 0环(内核),可以通过 mov eax, cs + test eax, 1

3. 异常的产生方式有多少种

  • CPU 满足特定的条件之后,内部主动产生的异常,类似 int 3(IDT)
  • 用户通过 RaiseException 构建 ExceptionRecord 主动抛出异常(KiDispatchException)

4. 编译器会为用户自定义的 try except 添加怎样的异常处理函数

  • 在同一个函数内,无论用户编写了多少个 SEH,编译器只会安装一个 except_handler4

5. 当用户模式下产生异常时,SEH 函数会在什么时候被调用

  • int3 -> idt[3] -> _KiTrap03 -> CommonDispatchException -> KiDispatchExceptijon
    -> KeUserExceptionDispatcher(3) -> RtlDispatchException(3) -> RtlpExecuteHandlerForException(3) -> except_handler4 -> except_handler4_common
    -> 用户通过 _try _except 安装的异常处理函数

6. 在 R0 中异常是如何被传递给三环调试器的

  • DbgkForwardException -> DbgkpSendApiMessage -> 三环调试器

7. R0 和 R3 的RtlDispathException 有什么区别

  • KiDispatchException[0] -> RtlDispathException[0] -> SEH
  • KiUserExceptionDispatcher[3] -> RtlDispathException[3] -> VEH SEH UEH (VCH)

    1. BeginDebug

  1. #include <iostream>
  2. #include <windows.h>
  3. // 对于所有的用户层 PEB 静态反调试,可以在程序正式的运行之前
  4. // 先挂起用户程序,然后修改相应的字段为非调试状态,再继续执行
  5. // 当程序被调试的时候, BeingDebugged 字段保存的是 1
  6. bool CheckBeingDebugged()
  7. {
  8. __asm
  9. {
  10. ; 通过 FS : [0x30] 获取到 PEB 结构体的地址
  11. mov eax, dword ptr fs:[0x30]
  12. ; 通过 PEB 偏移为 0x02 的地方获取到 BeingDebugged
  13. movzx eax, byte ptr [eax + 0x02]
  14. }
  15. }
  16. int main()
  17. {
  18. if (CheckBeingDebugged())
  19. printf("当前处于[被]调试状态\n");
  20. else
  21. printf("当前处于[非]调试状态\n");
  22. system("pause");
  23. return 0;
  24. }

2. IsDebuggerPresent

  1. #include <iostream>
  2. #include <windows.h>
  3. int main()
  4. {
  5. // IsDebuggerPresent 就是在检查 PEB.BeingDebugged 字段
  6. if (IsDebuggerPresent())
  7. printf("当前处于[被]调试状态\n");
  8. else
  9. printf("当前处于[非]调试状态\n");
  10. system("pause");
  11. return 0;
  12. }

3. NtGlobalFlag

  1. #include <iostream>
  2. #include <windows.h>
  3. // 如果当前的进程被调试,保存的就是 0x70
  4. bool CheckNtGlobalFlag()
  5. {
  6. int NtGlobalFlag = 0;
  7. __asm
  8. {
  9. ; 通过 TEB 偏移为 0x30 找到 PEB 结构
  10. mov eax, dword ptr fs : [0x30]
  11. ; 通过 PEB 偏移为 0x68 的地方找到 NtGlobalFlag
  12. mov eax, dword ptr[eax + 0x68]
  13. ; 将结果保存到变量,目的是方便比较
  14. mov NtGlobalFlag, eax
  15. }
  16. return NtGlobalFlag == 0x70 ? true : false;
  17. }
  18. int main()
  19. {
  20. if (CheckNtGlobalFlag())
  21. printf("当前处于[被]调试状态\n");
  22. else
  23. printf("当前处于[非]调试状态\n");
  24. system("pause");
  25. return 0;
  26. }

4. ProcessHeap

  1. #include <iostream>
  2. #include <windows.h>
  3. // _HEAP 结构不是一个公开的结构体,不同版本的 nt 内核可能
  4. // 对这个结构体有不同的实现,所以兼容性不够强
  5. bool CheckProcessHeap()
  6. {
  7. int Flags = 0, ForceFlags = 0;
  8. __asm
  9. {
  10. ; 通过 fs : [0x30] 可以找到 PEB 的地址
  11. mov eax, dword ptr fs:[0x30]
  12. ; 通过 PEB 偏移为 0x18 的位置找到 ProcessHeap(_HEAP)
  13. mov eax, dword ptr [eax + 0x18]
  14. ; 通过 _HEAP 偏移为 0x40 0x44 的字段找到两个标志
  15. mov ecx, dword ptr [eax + 0x40]
  16. mov Flags, ecx
  17. mov ecx, dword ptr [eax + 0x44]
  18. mov ForceFlags, ecx
  19. }
  20. printf("%08X %08X\n", Flags, ForceFlags);
  21. return Flags != 2 || ForceFlags != 0;
  22. }
  23. int main()
  24. {
  25. if (CheckProcessHeap())
  26. printf("当前处于[被]调试状态\n");
  27. else
  28. printf("当前处于[非]调试状态\n");
  29. system("pause");
  30. return 0;
  31. }

5. ProcessDebugPort

  1. #include <iostream>
  2. #include <windows.h>
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. // 对于所有的使用函数进行反调试的情况,都可以使用 ApiHook
  6. // 来进行反反调试,但是要注意 NtQueryInformationProcess
  7. // 功能非常的多,在函数内应该过滤和调试相关的枚举值进行操
  8. // 作,不应该影响到其它的查询信息。
  9. // 如果进程处于被调试状态,那么函数接收的就是 -1
  10. bool CheckProcessDebugPort()
  11. {
  12. int nDebugPort = 0;
  13. NtQueryInformationProcess(
  14. GetCurrentProcess(), // 目标进程句柄
  15. ProcessDebugPort, // 查询信息类型
  16. &nDebugPort, // 输出查询信息
  17. sizeof(nDebugPort), // 查询类型大小
  18. NULL); // 实际返回数据大小
  19. return nDebugPort == 0xFFFFFFFF ? true : false;
  20. }
  21. int main()
  22. {
  23. if (CheckProcessDebugPort())
  24. printf("当前处于[被]调试状态\n");
  25. else
  26. printf("当前处于[非]调试状态\n");
  27. system("pause");
  28. return 0;
  29. }

6. DebugObjectHandle

  1. #include <iostream>
  2. #include <windows.h>
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. // 如果当前的程序被调试了,那么保存的就是非零值
  6. bool CheckProcessDebugObjectHandle()
  7. {
  8. HANDLE hProcessDebugObjectHandle = 0;
  9. NtQueryInformationProcess(
  10. GetCurrentProcess(), // 目标进程句柄
  11. (PROCESSINFOCLASS)0x1E, // 查询信息类型
  12. &hProcessDebugObjectHandle, // 输出查询信息
  13. sizeof(hProcessDebugObjectHandle), // 查询类型大小
  14. NULL); // 实际返回大小
  15. return hProcessDebugObjectHandle ? true : false;
  16. }
  17. int main()
  18. {
  19. if (CheckProcessDebugObjectHandle())
  20. printf("当前处于[被]调试状态\n");
  21. else
  22. printf("当前处于[非]调试状态\n");
  23. system("pause");
  24. return 0;
  25. }

7. DebugFlag

  1. #include <iostream>
  2. #include <windows.h>
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. // 如果被调试了就是0,非则就是其它值
  6. bool CheckProcessDebugFlag()
  7. {
  8. BOOL bProcessDebugFlag = 0;
  9. NtQueryInformationProcess(
  10. GetCurrentProcess(), // 目标进程句柄
  11. (PROCESSINFOCLASS)0x1F, // 查询信息类型
  12. &bProcessDebugFlag, // 输出查询信息
  13. sizeof(bProcessDebugFlag), // 查询类型大小
  14. NULL); // 实际返回大小
  15. return bProcessDebugFlag ? false : true;
  16. }
  17. int main()
  18. {
  19. if (CheckProcessDebugFlag())
  20. printf("当前处于[被]调试状态\n");
  21. else
  22. printf("当前处于[非]调试状态\n");
  23. system("pause");
  24. return 0;
  25. }

8. CheckParentPID

  1. #include <iostream>
  2. #include <windows.h>
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. // 当一个普通的程序被双击打开的时候,实际是被资源管理器(Explorer)打开的,
  6. // 所以普通程序的父进程应该就是资源管理器,检查到自己的父进程不是,就被调试了
  7. bool CheckParentProcess()
  8. {
  9. struct PROCESS_BASIC_INFORMATION {
  10. ULONG ExitStatus; // 进程返回码
  11. PPEB PebBaseAddress; // PEB地址
  12. ULONG AffinityMask; // CPU亲和性掩码
  13. LONG BasePriority; // 基本优先级
  14. ULONG UniqueProcessId; // 本进程PID
  15. ULONG InheritedFromUniqueProcessId; // 父进程PID
  16. }stcProcInfo;
  17. // 查询到进程相关的基本信息,需要提供一个结构体进行接收
  18. NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation,
  19. &stcProcInfo, sizeof(stcProcInfo), NULL);
  20. DWORD ExplorerPID = 0;
  21. DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;
  22. // 以资源管理器的类名查询到资源管理所在的进程PID
  23. GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);
  24. // 如果相同就说明没有被调试
  25. return ExplorerPID == CurrentPID ? false : true;
  26. }
  27. int main()
  28. {
  29. if (CheckParentProcess())
  30. printf("当前处于[被]调试状态\n");
  31. else
  32. printf("当前处于[非]调试状态\n");
  33. system("pause");
  34. return 0;
  35. }

9. KernelDebuggerEnabled

  1. #include <iostream>
  2. #include <windows.h>
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. bool CheckSystemKernelDebuggerInformation()
  6. {
  7. // 保存了和系统调试相关的属性
  8. struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
  9. {
  10. BOOLEAN KernelDebuggerEnabled;
  11. BOOLEAN KernelDebuggerNotPresent;
  12. } DebuggerInfo = { 0 };
  13. // 查询当前系统的调试情况
  14. NtQuerySystemInformation(
  15. (SYSTEM_INFORMATION_CLASS)0x23, // 查询信息类型
  16. &DebuggerInfo, // 输出查询信息
  17. sizeof(DebuggerInfo), // 查询类型大小
  18. NULL); // 实际返回大小
  19. return DebuggerInfo.KernelDebuggerEnabled;
  20. }
  21. int main()
  22. {
  23. if (CheckSystemKernelDebuggerInformation())
  24. printf("当前处于[被]调试状态\n");
  25. else
  26. printf("当前处于[非]调试状态\n");
  27. system("pause");
  28. return 0;
  29. }

a. ThreadHideFromDebugger

  1. #include <iostream>
  2. #include <windows.h>
  3. typedef enum THREAD_INFO_CLASS {
  4. ThreadHideFromDebugger = 17
  5. };
  6. // 一个未公开的函数,需要手动的使用 GetProcAddress 获取
  7. typedef NTSTATUS(NTAPI* ZW_SET_INFORMATION_THREAD)(
  8. IN HANDLE ThreadHandle,
  9. IN THREAD_INFO_CLASS ThreadInformaitonClass,
  10. IN PVOID ThreadInformation,
  11. IN ULONG ThreadInformationLength);
  12. void ZSIT_DetachDebug()
  13. {
  14. ZW_SET_INFORMATION_THREAD Func = (ZW_SET_INFORMATION_THREAD)
  15. // 加载了 ntdll 模块,因为函数保存在这个模块中
  16. GetProcAddress(LoadLibrary(L"ntdll.dll"),
  17. // 函数的名称
  18. "ZwSetInformationThread");
  19. // 对调试器隐藏当前的线程,原理上就是让 DbgkpSendApiMessage 函数
  20. // 不向建立了调试关系的调试器发送调试信息。
  21. Func(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL);
  22. }
  23. int main()
  24. {
  25. ZSIT_DetachDebug();
  26. printf("runnning...\n");
  27. system("pause");
  28. return 0;
  29. }

b. CheckWindow

  1. #include <iostream>
  2. #include <windows.h>
  3. int main()
  4. {
  5. if (FindWindow(L"OllyDbg", NULL))
  6. printf("存在调试器\n");
  7. else
  8. printf("没检测到调试器\n");
  9. return 0;
  10. }

c. 插件加载的原理

  1. #include <vector>
  2. #include <string>
  3. #include <iostream>
  4. #include <windows.h>
  5. using namespace std;
  6. // 1. 应用程序如何找到自己的插件
  7. // 所有支持插件的应用程序,都会存在一个插件路径
  8. // 用户提供的就应该放置到这个路径底下,应用程序
  9. // 通过遍历文件的方式来确保能够找到插件。
  10. // 2. 插件的存在形式是什么?
  11. // 插件的存在形式通常都是 dll 文件,这些文件可
  12. // 以是任何后缀名结尾的,并不影响插件本身的功能
  13. // 3. 应用程序如何识别插件路径下的模块是否是插件?
  14. // 一个合格的插件,应该能够提供相应的导出函数说明
  15. // 当前插件的名称,版本以及能够支持的应用程序。
  16. // 4. 插件是如何提供功能的?
  17. // 通过实现指定的导出函数可以提供相应的功能。
  18. // 定义一个函数指针的别名
  19. using f_query = bool (*)(int v, char name[20], char version[20]);
  20. using f_run = void (*)();
  21. // 用于保存插件信息的结构体
  22. typedef struct _PLUGIN_INFO
  23. {
  24. char name[20];
  25. char version[20];
  26. HMODULE module;
  27. } PLUGIN_INFO, *PPLUGIN_INFO;
  28. // 定义容器用于保存所有的插件
  29. vector<PLUGIN_INFO> plugins;
  30. // 初始化函数
  31. void init()
  32. {
  33. // 保存找到的文件的信息
  34. WIN32_FIND_DATAA FileInfo = { 0 };
  35. // 1. 遍历当前的插件目录,搜索所有的模块
  36. HANDLE FindHandle = FindFirstFileA(".\\plugin\\*.plugin", &FileInfo);
  37. // 2. 如果说第一个文件查找成功就继续查找下一个
  38. if (FindHandle != INVALID_HANDLE_VALUE)
  39. {
  40. do {
  41. // 保存当前插件的信息
  42. PLUGIN_INFO plugin_info = { 0 };
  43. // 模块文件的路径
  44. string path = string(".\\plugin\\") + FileInfo.cFileName;
  45. // 尝试使用 LoadLibrary 加载模块
  46. plugin_info.module = LoadLibraryA(path.c_str());
  47. // 判断一下这是不是一个有效的模块
  48. if (plugin_info.module != NULL)
  49. {
  50. // 判断当前有没有导出指定函数
  51. f_query f = (f_query)GetProcAddress(plugin_info.module, "query");
  52. // 如果函数获取成功,并且函数返回true就表示这是一个有效的插件
  53. if (f != nullptr && f(1, plugin_info.name, plugin_info.version))
  54. {
  55. printf("插件: [%s %s] 已经被加载了\n",
  56. plugin_info.name, plugin_info.version);
  57. plugins.push_back(plugin_info);
  58. }
  59. }
  60. } while (FindNextFileA(FindHandle, &FileInfo));
  61. }
  62. }
  63. // 运行期间的函数
  64. void run()
  65. {
  66. // 可以遍历插件列表,调用其中约定好的函数
  67. for (auto& p : plugins)
  68. {
  69. // 获取函数,这个函数可以不提供
  70. f_run f = (f_run)GetProcAddress(p.module, "run");
  71. if (f) f();
  72. }
  73. }
  74. // 清理资源的函数
  75. void release()
  76. {
  77. // 一般会在 release 调用插件的清理函数
  78. for (auto& p : plugins)
  79. {
  80. // 获取函数,这个函数可以不提供
  81. f_run f = (f_run)GetProcAddress(p.module, "release");
  82. if (f) f();
  83. }
  84. }
  85. // 结束进程前调用的函数
  86. void my_exit()
  87. {
  88. // 程序结束前应该卸载插件
  89. for (int i = 0; i < plugins.size(); ++i)
  90. {
  91. // 将插件进行卸载(从内存空间移除)
  92. // 卸载插件必须卸载所有可能调用插件函数的之后
  93. FreeLibrary(plugins[i].module);
  94. }
  95. }
  96. int main()
  97. {
  98. // 通产在程序运行的时候加载插件
  99. init();
  100. run();
  101. release();
  102. my_exit();
  103. return 0;
  104. }

d. 提供的插件

  1. #include <windows.h>
  2. #include <iostream>
  3. // 插件导出一个约定好的函数,函数告诉了应用程序自己的名称版本等信息
  4. extern "C" __declspec(dllexport) bool query(int v, char name[20], char version[20])
  5. {
  6. // 参数一: 传入的参数,告诉插件自己的版本信息
  7. // 参数二: 传出的参数,告诉应用程序自己的名称
  8. // 参数三: 传出的参数,告诉应用程序自己的版本
  9. // 如果目标应用不是 1.0 版本,该插件就不支持
  10. if (v != 1)
  11. return false;
  12. memcpy(name, "plugin2", 8);
  13. memcpy(version, "2.0", 4);
  14. return true;
  15. }
  16. extern "C" __declspec(dllexport) void run()
  17. {
  18. printf("这是插件提供的功能");
  19. }

e. OD插件

  1. #include "Plugin/od_plugin.h"
  2. #pragma comment(lib, "Plugin/ollydbg.lib")
  3. #include <winternl.h>
  4. #pragma comment(lib,"ntdll.lib")
  5. // 1. 包含头文件和链接LIB
  6. // 2. 项目属性->C/C++->命令行选项 \J(使用无符号char)
  7. #define PLUGINNAME L"testplugin" // 插件名称
  8. #define MY_VERSION L"0.00.01" // 插件版本
  9. // 创建用于保存菜单的数组,菜单必须以一个全 0 结构结尾
  10. t_menu MainMenu[2] = { 0 };
  11. t_menu DasmMenu[2] = { 0 };
  12. // 必须导出的函数,用于提供插件的名字和版本号,并且检查是否符合要求
  13. extc int __cdecl ODBG2_Pluginquery(int ollydbgversion, ulong *features,
  14. wchar_t pluginname[SHORTNAME], wchar_t pluginversion[SHORTNAME])
  15. {
  16. // 1. 检查OllyDBG的兼容版本
  17. if (ollydbgversion < 201)
  18. return 0;
  19. // 2. 设置OllyDBG插件的名称与版本
  20. wcscpy_s(pluginname, SHORTNAME, PLUGINNAME); // 设置插件名称
  21. wcscpy_s(pluginversion, SHORTNAME, MY_VERSION); // 设置插件版本
  22. // 3. 返回需要的API版本
  23. return PLUGIN_VERSION;
  24. };
  25. // 菜单的回调函数,返回值是菜单的显示方式
  26. int MenuFunc(t_table *pt, wchar_t *name, ulong index, int mode)
  27. {
  28. // 如果是第一次显示菜单,会是 MENU_VERIFY 方式
  29. if (mode == MENU_VERIFY)
  30. {
  31. if (index == 1)
  32. return MENU_CHECKED | MENU_NORMAL;
  33. else
  34. return MENU_NORMAL;
  35. }
  36. if (mode == MENU_EXECUTE)
  37. {
  38. if (index == 1)
  39. MessageBoxA(0, "主菜单", "插件", MB_OK);
  40. else
  41. MessageBoxA(0, "汇编菜单", "插件", MB_OK);
  42. }
  43. return MENU_NORMAL;
  44. }
  45. // 插件第二个被调用的函数,可以不被提供
  46. extc int __cdecl ODBG2_Plugininit(void)
  47. {
  48. // 对插件的初始化操作
  49. // 设置主菜单的信息
  50. MainMenu[0].index = 1; // 用于区分是哪一个菜单
  51. MainMenu[0].name = (WCHAR*)L"主菜单"; // 菜单的名称
  52. MainMenu[0].help = (WCHAR*)L"主菜单"; // 主菜单
  53. MainMenu[0].menufunc = MenuFunc; // 菜单的回调函数
  54. // 设置汇编菜单的信息
  55. DasmMenu[0].index = 2;
  56. DasmMenu[0].name = (WCHAR*)L"汇编菜单";
  57. DasmMenu[0].help = (WCHAR*)L"汇编菜单";
  58. DasmMenu[0].menufunc = MenuFunc;
  59. return 0;
  60. };
  61. // 当有任何的菜单需要相应的时候,都会调用这个函数
  62. extc t_menu* __cdecl ODBG2_Pluginmenu(wchar_t *type)
  63. {
  64. // 1. 判断是否为主菜单弹出请求,是则弹出主菜单
  65. if (!wcscmp(type, PWM_MAIN))
  66. return MainMenu;
  67. // 2. 判断是否为CPU窗口右键菜单弹出请求,是则弹出右键菜单
  68. if (!wcscmp(type, PWM_DISASM))
  69. return DasmMenu;
  70. // 3. 返回空表示不设置菜单
  71. return NULL;
  72. };
  73. // 返回不同时刻产生的重要的信息
  74. pentry(void) ODBG2_Pluginnotify(int code, void *data, ulong parm1, ulong parm2)
  75. {
  76. // 可以在这里进行反反调试
  77. static int First = TRUE;
  78. // 执行唯一的一次反调试
  79. if (code == PN_NEWPROC && First)
  80. {
  81. DWORD Size = 0;
  82. // 获取进程的句柄
  83. HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parm1);
  84. // 保存进程的基本信息
  85. PROCESS_BASIC_INFORMATION Base = { 0 };
  86. // 获取被调试进程PEB
  87. NtQueryInformationProcess(Handle,
  88. ProcessBasicInformation,
  89. &Base, sizeof(Base), &Size);
  90. DWORD Peb = (DWORD)Base.PebBaseAddress;
  91. // 写入数据
  92. WriteProcessMemory(Handle, (LPVOID)(Peb + 2),"", 1, &Size);
  93. First = FALSE;
  94. }
  95. }