【笔记】C++控制台的OD
本帖最后由 Cizel 于 2016-7-11 13:18 编辑大神绕过,菜鸟学习一下:loveliness:
大概的功能分为以下几点:
控制台版本,没有固定的加载文件,可以弹出一个对话框,选择文件就像MFC那样
建立调试、软件断点、硬件断点、内存访问断点、寄存器信息、堆栈信息、PE等
主要是为了学习以下几点:
1)调试器的工作原理和与代码执行的关系
2)操作系统和调试目标如何产生调试事件,特别软件发生异常的时候
3)操作系统和应用程序中的异常处理代码交互
4)调试器怎么控制调试目标以及调试行为将产生怎样的后果
大概流程:
1.//创建进程
BOOLWINAPI CreateProcess(
_In_opt_ LPCWSTR lpApplicationName,//可执行模块路径
_Inout_opt_LPWSTR lpCommandLine,//安全描述符
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,//进程属性是否可以继承
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程属性是否可以继承
_In_ BOOL bInheritHandles,//否从调用进程处继承了句柄
_In_ DWORD dwCreationFlags,//以“只”调试的方式启动
_In_opt_ LPVOID lpEnvironment,//新进程的环境块
_In_opt_ LPCWSTR lpCurrentDirectory,//新进程的当前工作路径(当前目录)
_In_ LPSTARTUPINFOW lpStartupInfo,//指定进程的主窗口的特性
_Out_ LPPROCESS_INFORMATION lpProcessInformation//接收新进程的识别信息
);
2.
当调试进程时,在这个进程执行的一些操作将会被通知给调试器,列入进程的创建和结束,线程的创建和退出,以及在代码或处理器中抛出的异常等。
当该程序有这些时间发生时, windows内核将首先挂起该进程中的所以线程,然后把发生的事件通知给活动的调试器,并将等待调试器的恢复执行命令
通常使用的函数 WaitForDebugEvent 来接收相应的调试信息,主要:只有当被调试的进程发生了上面说到的特殊事件才会产生上面的这些调试事件
//等待调试事件的发生
BOOL WINAPI WaitForDebugEvent(
_Out_ LPDEBUG_EVENT lpDebugEvent,//结构体,接收保存调试的信息
_In_ DWORD dwMilliseconds //等待时间
);
//恢复先前由于调试事件而挂起的线程
BOOL WINAPI ContinueDebugEvent(
_In_ DWORD dwProcessId, //被调试进程的ID
_In_ DWORD dwThreadId, //欲恢复线程的线程ID
_In_ DWORD dwContinuesStatus, //选择以何种方式调试的事件继续
);、
DEBUG_EVENT这个结构体包含了所有可能的事件类型,调试器通过这些类型来解析事件信息
typedefstruct_DEBUG_EVENT{
DWORDdwDebugEventCode;//调试事件的类型
DWORDdwProcessId; //触发异常的进程ID
DWORDdwThreadId; //触发异常的线程ID
union{
EXCEPTION_DEBUG_INFO Exception;//发生异常时产生的调试事件
CREATE_THREAD_DEBUG_INFO CreateThread;//创建线程时产生的调试事件
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;//创建进程时产生的调试事件
EXIT_THREAD_DEBUG_INFO ExitThread;//线程退出产生的调试事件
EXIT_PROCESS_DEBUG_INFO ExitProcess;//进程退出产生的调试事件
LOAD_DLL_DEBUG_INFO LoadDll;//加载模块后产生的调试事件
UNLOAD_DLL_DEBUG_INFO UnloadDll;//卸载模块后产生的调试事件
OUTPUT_DEBUG_STRING_INFO DebugString;//调试字符串输出时产生的事件
RIP_INFORipInfo;//RIP异常事件
}u;
}DEBUG_EVENT,*LPDEBUG_EVENT;
3.
在调试循环中获得一个新的事件后,调试器将对 DEBUG_EVENT 结构体中信息进行解析,并且可能在返回调试循环之前将调试目标的控制权交给调试器的使用者
当有异常产生时,调试器必须对异常进行处理,并且将 DBG_CONTINUE 或者 DBG_EXCEPTON_NOT_HANDLED 作为 ContinueDebugEvent 函数的参数,
如果指定的是 DBG_CONTINUE 参数,那么windows将把该异常标记为已经被正确处理了,并且还将异常发生的条件也清空,因此将从产生异常的地址开始恢复程序的执行,
如果指定的是 DBG_EXCEPTON_NOT_HANDLED 参数,那么windows表现出来的行为是继续将该异常进行分发
调试事件的类型由一组宏来表示
调试事件的类型由一组宏来表示
EXCEPTION_DEBUG_EVENT //异常调试事件
CREATE_PROCESS_DEBUG_EVENT //创建进程时产生的调试事件
CREATE_THREAD_DEBUG_EVENT //创建线程时产生的调试事件
EXIT_PROCESS_DEBUG_EVENT //进程结束后时产生的调试事件
EXIT_THREAD_DEBUG_EVENT //线程结束时产生的调试事件
LOAD_DLL_DEBUG_EVENT //装载模块之后产生的调试事件
UNLOAD_DLL_DEBUG_EVENT //卸载模块后产生的调试事件
OUTPUT_DEBUG_STRING_EVENT //调试字符串输出时产生的调试事件
RIP_EVENT //RIP异常事件
4
调试事件的先后顺序,第一是创建进程事件,第二加载模块事件,当所有的模块被映射到调试目标的地址空间后,调试目标将开始运行并开始接收异常的事件通知了
typedef struct _EXCEPTION_DEBUG_INFO
{
EXCEPTION_RECORD ExceptionRecord;//指向异常结构体
DWORD dwFirstChance//标明该异常时第一次触发
}EXCEPTION_DEBUG_INFO,*LPEXCEPTION_DEBUG_INFO;
结构体下异常的发生地址和原因之结构体:EXCEPTION_RECORD
typedefstruct_EXCEPTION_RECORD{
DWORD ExceptionCode;//异常发生的类型
DWORD ExceptionFlags;//异常标志
struct_EXCEPTION_RECORD *ExceptionRecord;//异常记录可以形成一个链表提供额外的信息
PVOID ExceptionAddress;//异常发生的地址
DWORD NumberParameters;//与异常相关的的参数个数
ULONG_PTR ExceptionInformation;//用于描素异常的附加参数的数组
}EXCEPTION_RECORD,*PEXCEPTION_RECORD;
EXCEPTION_RECORD的第一个字段ExceptionCode,异常发生原因
EXCEPTION_ACCESS_VIOLATION://非法访问异常
EXCEPTION_DATATYPE_MISALIGNMENT://内存对齐异常
EXCEPTION_ILLEGAL_INSTRUCTION://无效指令
EXCEPTION_INT_DIVIDE_BY_ZERO://除0错误
EXCEPTION_PRIV_INSTRUCTION://指令不支持当前模式
EXCEPTION_BREAKPOINT://断点异常0xcc
EXCEPTION_SINGLE_STEP://单步或硬件断点异常TF和DR系列寄存器
5.
断点,约定了一些宏,定义了一个结构体
#define MODE_HARD 1 //硬件断点:DR0~DR3
#define MODE_SOFT 2 //软件断点:0xcc
#define MODE_TF 3 //TF断点:TF=1
#define SOFTTYPE_ONCE 1 //一次性断点
#define SOFTTYPE_NORM 2 //正常断点
#define SOFTTYPE_COND 3 //条件断点
#define CONDITIONTYPE_ADDRE 0 //条件断点的地址类型
#define CONDITIONTYPE_TIMES 1 //条件断点的次数类型
#define TFTYPE_NO 0 //没有断点
#define TFTYPE_TF 1 //正常的TF断点
#define TFTYPE_RELOAD 2 //重新安装软件断点
#define TFTYPE_TIMES 3 //次数断点
#define UPDOWN_EQU 1 //相等
#define UPDOWN_UP 2 //升序
#define UPDOWN_DOWN 3 //降序
typedef struct BREAKPOINTERTYPE
{
DWORDmode : 8;
DWORDtype : 8;// 断点类型 一次性,单步,条件
DWORDmemData : 8;// 内存中原来的内容.下0xcc断点时替换出来的内容
DWORDconditionType : 4;// 条件断点使用哪种条件: 地址,次数
DWORDupDown : 4; // >,==,<
SIZE_T address;// 断点地址
SIZE_T times; // 断下的次数(一次性除外)
SIZE_T condition;// 条件:地址或者次数
}BREAKPOINTERTYPE,*PBREAKPOINTERTYPE;
// 迭代器
typedef map<SIZE_T,BREAKPOINTERTYPE>::iterator softItor;
// 有断点时,会调用此类型的函数来进行一些额外处理
typedef BOOL(*OnBreakPointerEventProc)(SIZE_T nAddress,HANDLE hProcess);
map<SIZE_T,BREAKPOINTERTYPE>m_mapSoft;//软件中断表
vector<BREAKPOINTERTYPE> m_vecHard;//硬件中断表
BREAKPOINTERTYPE m_stcTF;//TF中断
INIT3
创建调试进程后,为了能够让被调试程序断在OEP,我们可以在被调试程序的OEP处下一个一次性INT3断点,在创建调试的进程过程中(程序还没有执行到OEP处),会触发一个ntdll.dll中的INIT3(第一次断点),遇到这个断点直接跳出不处理,这个断点在使用微软自己的调试工具时会被断下,可以猜测,微软设置这个断点是为了能够在程序到达OEP之前被断下,方便用户做一些处理,因为INIT3断点修改了被调试程序的代码内容,所以在进行反汇编和显示被调试进程内存数据的时候,需要检查碰到的0XCC字节是否是用户所下的INIT3断点,如果是需要替换为原来的字节
硬件断点
IA-32处理器定义了8个寄存器,分别为DR0~DR7
2个保留寄存器(DR4~DR5)
4个32位的调试地址寄存器(DR0~DR3)
用于保存断点的线性地址,用于设置硬件断点,由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点,产生的异常是STATUS_SINGLE_STEP(单步异常)
1个32位的调试控制寄存器(DR7)
在DR7中 ,有24位是被划分成四组分别与四个调试地址寄存器相对应,
1个32位的调试状态寄存器(DR6),用于显示是哪个硬件调试寄存器引起的断点,如果是DR0~3或单步(EFLAGS的TF)的话,则相对应的位会置为1.
内存断点
内存访问断点,将目标内存的访问权限设置为PAGE_NOACCESS,当被调试程序对这个内存进行任何“读,写或运行”操作时,都会触发异常。内存写入断点时,将目标内存的访问权限设置为PAGE_EXECUTE_READ,当被调试程序对这个内存进行“写”操作时触发异常
6
反汇编使用的是开源的一个库
#define BEA_ENGINE_STATIC
#define BEA_USE_STDCALL
#include "../../BeaEngine_4.1/Win32/headers/BeaEngine.h"
#ifndef _WIN64
#pragma comment(lib, "../../BeaEngine_4.1/Win32/Win32/Lib/BeaEngine.lib")
#pragma comment(linker, "/NODEFAULTLIB:\"crt.lib\"")
#else
#pragma comment(lib, "../../BeaEngine_4.1/Win64/Win64/Lib/BeaEngine64.lib")
#pragma comment(linker, "/NODEFAULTLIB:\"crt64.lib\"")
代码都全部打包好了,已经分享
膜拜大神...厉害 膜拜大神。。真是牛逼 感谢共享~~正好需要一个调试器的demo 学习了
页:
[1]