try-finally与try-except的区别
- try-finally的try块中产生异常时,异常是无法被捕获, 它的特点是,无论try块中的代码是以何种方式离开的(return,break,continue,goto),finally块的代码都会被执行.
- try-except是用于处理异常的, 只有try块中的代码触发了异常,except的过滤表达式才会被调用(注意,这里指过滤表达式,并非excep块,要区分清楚.) , 至于except块的代码是否被调用, 取决于过滤表达式的值.
try-except过滤表达式三种结果
- EXCEPTION_EXECUTE_HANDLER(1) : 执行except块
- EXCEPTION_CONTINUE_SEARCH(0) : 搜索下一个异常处理
- EXCEPTION_CONTINUE_EXECUTION(-1) : 继续执行产生异常的指令.
在异常过滤函数中获取异常产生的地址和异常的类型
异常过滤函数或异常处理函数的参数一般都有EXCEPTION_POINTERS结构体 ,
这个结构体中, 有两个结构体,
一个是CONTEXT结构体,保存的是产生异常时所有寄存器的值.
另一个结构体是EXCEPTION_RECORD,保存有异常类型(ExceptionCode) 异常码,异常产生的地址(ExceptionAddress).
SetUnhandleExceptionFilter
进程的任何线程中若发生一个未处理的异常,就会导致调用程序自己的异常过滤器。需要将这个过滤器的地址作为参数传递给SetUnhandledExceptionFilter。按SEH格式包装所有的线程函数
注意:在进程被调试时,被设置的UEH异常处理函数不会被调用,因为这个差异,此机制也会被用于反调试.
VEH 是什么?如何设置?与SEH有什么区别?
VEH是向量化异常处理,
使用AddVectoredExceptionHandler来设置
VEH和SEH之间的区别
- 先执行的
是面向整个进程的, 已经设置,无论在进程的哪个位置触发了异常, 所设置的异常处理函数都被调用 .
SEH
后执行的
- 是面向try块的, 只有try块中的代码产生了异常, 异常过滤函数才会被调用
IDT :Interrupt Descriptor Table(中断描述符表)
- 是一张用来存储中断服务程序的表
- 中断可以由硬件(称为外部中断),也可以由软件产生(称为内部中断),在程序中写入int n 指令可以产生n号中断和异常(0-FFh)。每一种中断对应一个中断号,CPU 执行中断指令时,会去IDT表中查找对一个你的中断服务程序。
- 中断是CPU 的机制,不管运行的是什么操作系统,只有是运行在x86架构,IDT结构式必然存在的(中断服务程序应该是由操作系统提供)。
异常分发有几轮
- 第一轮异常分发是从中断服务程序内部的CommonDispatchException()发起.
- 第二轮异常分发是从KiUserExceptionDispatcher ()函数内部发起.
产生了异常,是先发送给调试器处理,还是先被程序的异常处理机制处理掉
- 需要判断产生异常的程序是当前是否被调试.
- 如果触发异常的进程处于被调试状态,则将异常交给调试器处理,如果没有调试器(或者调试器处理不了)则将异常交给程序的异常处理机制处理.
反调试的目的是什么?
- a) 防止程序被调试.
- b) 防止程序被暴力破解
什么是静态反调试?什么是动态反调试?分别都有哪些?
a) 静态反调试: 一般在调试开始时阻拦调试者,调试者只需找到原因后可一次性突破,检测某些固定的标志位,如利用PEB中的各个字段判断调试状态.
b) 动态反调试: 一般在调试过程中阻拦调试者,可在调试的过程中被频繁触发,因此需要调试者随时关注,例如利用SEH机制.
如何破解常见的反调试手段?
a) 对于利用PEB的反调试,只需要将PEB的相应字段的值重写即可.
b) 对于使用了未文档化的API查询信息的反调试, 只能HOOK相应的API,然后根据参数中的功能号判断API调用是否是查询调试信息的,如果是则将错误的参数通过形参输出回去.
例如:
NTSTATUS WINAPI MyNtQueryInformationProcess(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength){
// 查询调试端口
if(ProcessInformationClass== ProcessDebugPort){
*(PDWORD*) ProcessInformation=0xFFFFFFFF;
if(ReturnLength) *ReturnLength = 4;
return 0;
}
else{
// 调用原版函数:
return pSrcNtQueryInformationProcess(ProcessHandle, ProcessInformationClass,
ProcessInformation,
ProcessInformationLength,
ReturnLength);
}
}
OD插件工程中,哪些函数时必须导出的
ODBG2_Pluginquery :
如果没有导出该函数, 则DLL将不能被OD识别, 无法加载到OD中, 此外,这个函数还用于版本检查, 检查OD版本和插件所使用的SDK版本是否一致,如果不一致, OD还是不会加载DLL
X64dbg插件工程中, 哪些函数是必须导出的
extern “C” declspec(dllexport) bool pluginit(PLUG_INITSTRUCT* initStruct);
X64dbg调用该函数来检查版本.
extern “C” declspec(dllexport) bool plugstop();
当x64dbg被关掉时会调用此函数, 一般会在这里保存资源或者释放资源.
extern “C” __declspec(dllexport) void plugsetup(PLUG_SETUPSTRUCT* setupStruct);
当插件通过x64dbg的版本检查之后, x64dbg会调用此函数让插件进行初始化.
创建调试会话的方式都有哪些?
一般有三种:
1. 使用CreateProcess加上调试标志,创建一个未运行的exe作为调试进程
2. 使用DebugActiveProcess附加一个正在运行的进程
3. 注册实时调试器(当进程崩溃时由系统附加) : 将调试器的路径设置到注册表:
32位:HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/Microsoft/Windows NT/CurrentVersion/AeDebug/Debugger
64位:HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/Debugger
调试事件都有什么?分别是在什么时候产生的
- REATE_PROCESS_DEBUG_EVENT: 创建进程之后发送此类调试事件,这是调试器收到的第一个调试事件
- CREATE_THREAD_DEBUG_EVENT:创建一个线程之后发送此类调试事件
- EXCEPTION_DEBUG_EVENT:发生异常时发送此类调试事件。
- EXIT_PROCESS_DEBUG_EVENT:进程结束后发送此类调试事件。
- EXIT_THREAD_DEBUG_EVENT:一个线程结束后发送此类调试事件。
- LOAD_DLL_DEBUG_EVENT:装载一个DLL模块之后发送此类调试事件。
- OUTPUT_DEBUG_STRING_EVENT:被调试进程调用OutputDebugString之类的函数时发送此类调试事件。
- RIP_EVENT:发生系统调试错误时发送此类调试事件。
- UNLOAD_DLL_DEBUG_EVENT:卸载一个DLL模块之后发送此类调试事件。
为什么要回复调试子系统? 能回复几种结果? 这些结果会造成什么影响?
进程在产生调试事件后, 系统会通过发消息的方式将调试事件的信息发送给调试器, 并等待调试器回复, 如果调试器不回复, 意味着被调试的进程将不会继续运行.
一般情况下,可以回复两种结果, 一是DBG_CONTINUE,表示继续执行., 而是 DBG_EXCEPTION_NOT_HANDLE,表示异常未处理. 处理异常事件,其余事件都必须要回复继续执行. 而异常事件下,不同的情形回复不同的代码:
回复继续执行 , 产生异常的代码会继续被执行, 如果异常没有被处理掉,那么异常代码再次执行后必将再次产生异常.
回复异常没有被处理, 按照异常分发流程 ,异常将会被被调试进程中的异常处理机制所处理.
因此, 如果异常是调试器主动制造而产生的,那么调试器接收到异常之后应当先去除异常,然后再回复继续执行.异常如果是被调试程序产生的,则回复异常未被处理.
软件断点的实现原理是什么? 实现步骤有几步?
软件断点实际就是 int 3
指令, CPU在执行这条指令之后, 就会触发异常. 而异常被触发后,通过异常分发流程,调试器将会接收到一个异常事件. 此时, 就相当于’断点断下’到了调试器中.
软件断点的思路是, 不管程序是怎么运行的, 如果想要程序在飞速执行的过程中精准地停在某个位置. 那么必须事先在想要断下来的位置设置断点(将int3指令的机器码写入), 这样一来程序运行到int3指令自然就会停下来.
步骤:
1. 将写入位置的内存分页属性设置为可读可写
2. 备份被0xcc(int 3的机器码)覆盖的1个字节
3. 将int3写入
硬件断点的读写断点怎么设置?
使用硬件断点寄存器. 可用的硬件断点寄存器总共有Dr0,Dr1,Dr2,Dr3,Dr6,Dr7, 其中,Dr0到Dr3用于保存断点的地址.Dr7保存断点中断的条件.
Dr7是一个32位寄存器, 里面总共保存着Dr0~Dr3四个断点地址寄存器的中断条件.中断条件有三种:
1.断点寄存器是否被启用: L0~L3
被置1了,表示断点被启用,CPU会检测对应的DR0~DR3寄存器所记录的地址是否产生中断.
2.断点的范围(长度): LEN0~LEN3
记录了断点地址的命中范围, 这个范围只有以下几种: 0:1字节, 1:2字节, 2:4或8字节,3:4字节.
断点的长度会受到断点地址的影响, 当地址是4的倍数时,只能设置4字节, 当地址是2的倍数时,可设置成2字节和4字节,当地址是1的倍数时,可以设置1/2/4字节.
3.断点的中断类型: RW0 ~ RW3
记录断点的中断类型, 一般有以下几种: 0:执行断点 , 1:写入断点 , 2:I/O读写断点, 3:数据读写断点
当断点中断类型为0时, 长度必须设置为0(表示1字节的长度),否则不会有效果
因此, 要设置读写断点,应将被访问的地址存入Dr0~Dr3中的一个, 将Dr7中对应的L0~L3设置为1,将对应的LEN0~LEN3设置为0/1/3,将对应的RW0~RW3设置为3.
例如:
DR0 = 0x403000
DR7.L0 = 1
DR7.LEN0 = 3
DR7.RW0 = 3
内存访问断点的原理是什么?
内存以分页作为管理单位, 内存分页具有读,写,执行的分页属性, 如果一个内存分页中缺少了读权限,但又在这个分页上读取内容,就会产生内存访问异常. 内存访问断点正是基于这个性质而提出的. 当要下断点时,一般就是将断点地址所在的内存分页设置为没有任何分页访问属性, 这样一来, 无论是在这个分页上进行什么操作, 都会产生访问异常. 如果想要精确的设置,比如要设置地址被写入时才断下, 那么便可以只将内存分页设置为不可写即可.
断点类型 | 原理 | 说明 |
---|---|---|
软件中断断点 |
| 软件断点在X86系统中为中断指令INT 3,其二进制代码opcode是0xCC。
当程序执行到INT 3指令时,会引发软件中断。
操作系统的INT 3中断处理器会寻找注册在该进程上的调试处理程序。
陷阱类异常 | 步骤: 1. 将写入位置的内存分页属性设置为可读可写
2. 备份被0xcc(int 3的机器码)覆盖的1个字节
3. 将int3写入 |
| 内存访问断点
| 修改断点地址所在的内存分页的访问属性
错误类异常 | 内存以分页作为管理单位, 内存分页具有读,写,执行的分页属性, 如果一个内存分页中缺少了读权限,但又在这个分页上读取内容,就会产生内存访问异常. 内存访问断点正是基于这个性质而提出的. 当要下断点时,一般就是将断点地址所在的内存分页设置为没有任何分页访问属性, 这样一来, 无论是在这个分页上进行什么操作, 都会产生访问异常. 如果想要精确的设置,比如要设置地址被写入时才断下, 那么便可以只将内存分页设置为不可写即可. |
| 硬件断点
| 硬件断点为CPU提供给我们的断点,由4个寄存器(DR0-DR3)存放断点地址,断点属性可设置为读、写、执行,由于寄存器数量有限,只能存在4个硬件断点,这4个断点是否执行由DR7的0、2、4、6位决定,当置为1时,断点生效。
是陷阱类异常 | 使用硬件断点寄存器. 可用的硬件断点寄存器总共有Dr0,Dr1,Dr2,Dr3,Dr6,Dr7, 其中,Dr0到Dr3用于保存断点的地址.Dr7保存断点中断的条件.
Dr7是一个32位寄存器, 里面总共保存着Dr0~Dr3四个断点地址寄存器的中断条件.中断条件有三种:
1.断点寄存器是否被启用: L0~L3
被置1了,表示断点被启用,CPU会检测对应的DR0~DR3寄存器所记录的地址是否产生中断.
2.断点的范围(长度): LEN0~LEN3
记录了断点地址的命中范围, 这个范围只有以下几种: 0:1字节, 1:2字节, 2:4或8字节,3:4字节.
断点的长度会受到断点地址的影响, 当地址是4的倍数时,只能设置4字节, 当地址是2的倍数时,可设置成2字节和4字节,当地址是1的倍数时,可以设置1/2/4字节.
3.断点的中断类型: RW0 ~ RW3
记录断点的中断类型, 一般有以下几种: 0:执行断点 , 1:写入断点 , 2:I/O读写断点, 3:数据读写断点
当断点中断类型为0时, 长度必须设置为0(表示1字节的长度),否则不会有效果
因此, 要设置读写断点,应将被访问的地址存入Dr0~Dr3中的一个, 将Dr7中对应的L0~L3设置为1,将对应的LEN0~LEN3设置为0/1/3,将对应的RW0~RW3设置为3.
例如:
DR0 = 0x403000
DR7.L0 = 1
DR7.LEN0 = 3
DR7.RW0 = 3 |
| 条件断点
| 在软件断点的基础上,增加限定条件。适用于某一下断处会被多地方调用,则加上限定条件,以达到真正需要断下时触发。 | 例:[esp-4]!=某一地址 |