课前提问
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
 
#include <iostream>#include <windows.h>// 对于所有的用户层 PEB 静态反调试,可以在程序正式的运行之前//    先挂起用户程序,然后修改相应的字段为非调试状态,再继续执行// 当程序被调试的时候, BeingDebugged 字段保存的是 1bool 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;}
2. IsDebuggerPresent
#include <iostream>#include <windows.h>int main(){    // IsDebuggerPresent 就是在检查 PEB.BeingDebugged 字段    if (IsDebuggerPresent())        printf("当前处于[被]调试状态\n");    else        printf("当前处于[非]调试状态\n");    system("pause");    return 0;}
3. NtGlobalFlag
#include <iostream>#include <windows.h>// 如果当前的进程被调试,保存的就是 0x70bool 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;}
4. ProcessHeap
#include <iostream>#include <windows.h>// _HEAP 结构不是一个公开的结构体,不同版本的 nt 内核可能//    对这个结构体有不同的实现,所以兼容性不够强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;}
5. ProcessDebugPort
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib,"ntdll.lib")// 对于所有的使用函数进行反调试的情况,都可以使用 ApiHook//    来进行反反调试,但是要注意 NtQueryInformationProcess//    功能非常的多,在函数内应该过滤和调试相关的枚举值进行操//    作,不应该影响到其它的查询信息。// 如果进程处于被调试状态,那么函数接收的就是 -1bool 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;}
6. DebugObjectHandle
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib,"ntdll.lib")// 如果当前的程序被调试了,那么保存的就是非零值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;}
7. DebugFlag
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib,"ntdll.lib")// 如果被调试了就是0,非则就是其它值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;}
8. CheckParentPID
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib,"ntdll.lib")// 当一个普通的程序被双击打开的时候,实际是被资源管理器(Explorer)打开的,//  所以普通程序的父进程应该就是资源管理器,检查到自己的父进程不是,就被调试了bool CheckParentProcess(){    struct PROCESS_BASIC_INFORMATION {        ULONG ExitStatus;                   // 进程返回码        PPEB  PebBaseAddress;               // PEB地址        ULONG AffinityMask;                 // CPU亲和性掩码        LONG  BasePriority;                 // 基本优先级        ULONG UniqueProcessId;              // 本进程PID        ULONG InheritedFromUniqueProcessId; // 父进程PID    }stcProcInfo;    // 查询到进程相关的基本信息,需要提供一个结构体进行接收    NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation,        &stcProcInfo, sizeof(stcProcInfo), NULL);    DWORD ExplorerPID = 0;    DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;    // 以资源管理器的类名查询到资源管理所在的进程PID    GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);    // 如果相同就说明没有被调试    return ExplorerPID == CurrentPID ? false : true;}int main(){    if (CheckParentProcess())        printf("当前处于[被]调试状态\n");    else        printf("当前处于[非]调试状态\n");    system("pause");    return 0;}
9. KernelDebuggerEnabled
#include <iostream>#include <windows.h>#include <winternl.h>#pragma comment(lib,"ntdll.lib")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;}
a. ThreadHideFromDebugger
#include <iostream>#include <windows.h>typedef enum THREAD_INFO_CLASS {    ThreadHideFromDebugger = 17};// 一个未公开的函数,需要手动的使用 GetProcAddress 获取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)        // 加载了 ntdll 模块,因为函数保存在这个模块中        GetProcAddress(LoadLibrary(L"ntdll.dll"),         // 函数的名称        "ZwSetInformationThread");    // 对调试器隐藏当前的线程,原理上就是让 DbgkpSendApiMessage 函数    //  不向建立了调试关系的调试器发送调试信息。    Func(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL);}int main(){    ZSIT_DetachDebug();    printf("runnning...\n");    system("pause");    return 0;}
b. CheckWindow
#include <iostream>#include <windows.h>int main(){    if (FindWindow(L"OllyDbg", NULL))        printf("存在调试器\n");    else        printf("没检测到调试器\n");    return 0;}
c. 插件加载的原理
#include <vector>#include <string>#include <iostream>#include <windows.h>using namespace std;// 1. 应用程序如何找到自己的插件//    所有支持插件的应用程序,都会存在一个插件路径//    用户提供的就应该放置到这个路径底下,应用程序//    通过遍历文件的方式来确保能够找到插件。// 2. 插件的存在形式是什么?//    插件的存在形式通常都是 dll 文件,这些文件可//    以是任何后缀名结尾的,并不影响插件本身的功能// 3. 应用程序如何识别插件路径下的模块是否是插件?//    一个合格的插件,应该能够提供相应的导出函数说明//    当前插件的名称,版本以及能够支持的应用程序。// 4. 插件是如何提供功能的?//    通过实现指定的导出函数可以提供相应的功能。// 定义一个函数指针的别名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 };    // 1. 遍历当前的插件目录,搜索所有的模块    HANDLE FindHandle = FindFirstFileA(".\\plugin\\*.plugin", &FileInfo);    // 2. 如果说第一个文件查找成功就继续查找下一个    if (FindHandle != INVALID_HANDLE_VALUE)    {        do {            // 保存当前插件的信息            PLUGIN_INFO plugin_info = { 0 };            // 模块文件的路径            string path = string(".\\plugin\\") + FileInfo.cFileName;            // 尝试使用 LoadLibrary 加载模块            plugin_info.module = LoadLibraryA(path.c_str());            // 判断一下这是不是一个有效的模块            if (plugin_info.module != NULL)            {                // 判断当前有没有导出指定函数                f_query f = (f_query)GetProcAddress(plugin_info.module, "query");                // 如果函数获取成功,并且函数返回true就表示这是一个有效的插件                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(){    // 一般会在 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;}
d. 提供的插件
#include <windows.h>#include <iostream>// 插件导出一个约定好的函数,函数告诉了应用程序自己的名称版本等信息extern "C" __declspec(dllexport) bool query(int v, char name[20], char version[20]){    // 参数一: 传入的参数,告诉插件自己的版本信息    // 参数二: 传出的参数,告诉应用程序自己的名称    // 参数三: 传出的参数,告诉应用程序自己的版本    // 如果目标应用不是 1.0 版本,该插件就不支持    if (v != 1)        return false;    memcpy(name, "plugin2", 8);    memcpy(version, "2.0", 4);    return true;}extern "C" __declspec(dllexport) void run(){    printf("这是插件提供的功能");}
e. OD插件
#include "Plugin/od_plugin.h"#pragma comment(lib, "Plugin/ollydbg.lib")#include <winternl.h>#pragma comment(lib,"ntdll.lib")// 1. 包含头文件和链接LIB// 2. 项目属性->C/C++->命令行选项 \J(使用无符号char)#define PLUGINNAME L"testplugin"    // 插件名称#define MY_VERSION L"0.00.01"       // 插件版本// 创建用于保存菜单的数组,菜单必须以一个全 0 结构结尾t_menu MainMenu[2] = { 0 };t_menu DasmMenu[2] = { 0 };// 必须导出的函数,用于提供插件的名字和版本号,并且检查是否符合要求extc int __cdecl ODBG2_Pluginquery(int ollydbgversion, ulong *features,    wchar_t pluginname[SHORTNAME], wchar_t pluginversion[SHORTNAME]){    // 1. 检查OllyDBG的兼容版本    if (ollydbgversion < 201)        return 0;    // 2. 设置OllyDBG插件的名称与版本    wcscpy_s(pluginname, SHORTNAME, PLUGINNAME);    // 设置插件名称    wcscpy_s(pluginversion, SHORTNAME, MY_VERSION); // 设置插件版本    // 3. 返回需要的API版本    return PLUGIN_VERSION;};// 菜单的回调函数,返回值是菜单的显示方式int  MenuFunc(t_table *pt, wchar_t *name, ulong index, int mode){    // 如果是第一次显示菜单,会是 MENU_VERIFY 方式    if (mode == MENU_VERIFY)    {        if (index == 1)            return MENU_CHECKED | MENU_NORMAL;        else            return MENU_NORMAL;    }    if (mode == MENU_EXECUTE)    {        if (index == 1)            MessageBoxA(0, "主菜单", "插件", MB_OK);        else            MessageBoxA(0, "汇编菜单", "插件", MB_OK);    }    return MENU_NORMAL;}// 插件第二个被调用的函数,可以不被提供extc int __cdecl ODBG2_Plugininit(void) {    // 对插件的初始化操作    // 设置主菜单的信息    MainMenu[0].index = 1;                    // 用于区分是哪一个菜单    MainMenu[0].name = (WCHAR*)L"主菜单";        // 菜单的名称    MainMenu[0].help = (WCHAR*)L"主菜单";    // 主菜单    MainMenu[0].menufunc = MenuFunc;        // 菜单的回调函数    // 设置汇编菜单的信息    DasmMenu[0].index = 2;    DasmMenu[0].name = (WCHAR*)L"汇编菜单";    DasmMenu[0].help = (WCHAR*)L"汇编菜单";    DasmMenu[0].menufunc = MenuFunc;    return 0;};// 当有任何的菜单需要相应的时候,都会调用这个函数extc t_menu* __cdecl ODBG2_Pluginmenu(wchar_t *type){    // 1. 判断是否为主菜单弹出请求,是则弹出主菜单    if (!wcscmp(type, PWM_MAIN))        return MainMenu;    // 2. 判断是否为CPU窗口右键菜单弹出请求,是则弹出右键菜单    if (!wcscmp(type, PWM_DISASM))        return DasmMenu;    // 3. 返回空表示不设置菜单    return NULL;};// 返回不同时刻产生的重要的信息pentry(void) ODBG2_Pluginnotify(int code, void *data, ulong parm1, ulong parm2){    // 可以在这里进行反反调试    static int First = TRUE;    // 执行唯一的一次反调试    if (code == PN_NEWPROC && First)    {        DWORD Size = 0;        // 获取进程的句柄        HANDLE Handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parm1);        // 保存进程的基本信息        PROCESS_BASIC_INFORMATION Base = { 0 };        // 获取被调试进程PEB        NtQueryInformationProcess(Handle,            ProcessBasicInformation,            &Base, sizeof(Base), &Size);        DWORD Peb = (DWORD)Base.PebBaseAddress;        // 写入数据        WriteProcessMemory(Handle, (LPVOID)(Peb + 2),"", 1, &Size);        First = FALSE;    }}