目前在科锐学习三阶段,有一个项目是使用x86汇编始编写一个windows端的控制台调试器的过程,记录一下学习编写调试器的过程
windows的调试框架是依托于异常体系,由事件驱动的
这里的事件指的是调试事件(DebugEvent),整个用户态异常的处理过程如下:
当CPU执行到一些特殊的指令,比如int3
、int1
或者是除0再或者是在3环执行了特权指令,CPU就会触发异常,触发异常的具体动作是保存现场环境(CONTEXT)然后去执行IDT
表中对应的内核中断函数,比如in3
就是执行 KiTrap03
,在中断函数中执行一些特定异常的处理工作,之后就会执行到内核的异常派发函数KiDispatchException
,在KiDispatchException
判断EPROCESS.DebugPort
是否为NULL,如果不为NULL则说明存在3环调试器,则通过激活DebugPort
中的同步事件来通知3环调试器,有调试事件来了,然后调用KeWaitForSingleObject
等待调试器的返回
再回到调试器,调试器在启动/附加到被调试进程之后(所谓建立调试会话),就会进入一个循环,不停的调用WaitForDebugEvent
等待调试事件的到来,这个函数就是在等待DebugPort
中的同步事件,一旦调试事件到来,则处理调试事件,然后返回处理的结果,返回之后再次进入WaitForDebugEvent
等待调试事件
调试器返回之后,内核中的KeWaitForSingleObject
函数就会执行完毕返回,此时这就是调试器的第一次暂停,调试器返回的结果可以是处理完毕回到异常触发现场继续执行DBG_CONTINUE
,也可以是继续处理异常DBG_EXCEPTION_NOT_HANDLED
,如果是继续处理异常,则会返回到用户态,执行用户态异常分发KiUserExceptionDispatcher
:VEH->SEH->UEH
,如果处理完之后还是没能将异常处理成功,则继续通过ZwRaiseException
返回内核,然后就是第二次派发给调试器,如果调试器还是没有处理成功,则将异常发送给异常处理端口(csrss.exe),然后结束进程
在以上过程中,调试器、VEH、SEH都可以成功处理异常,然后返回到异常触发现场继续执行代码
示意图如下:
上面说到,当CPU触发异常,就会走到内核态的异常分发函数,内核异常分发函数判断如果存在3环调试器,就发送调试事件给3环调试器,这个调试事件就是最关键的一个结构体
所谓发送调试事件给调试器,就是将的DEBUG_EVENT
这个内核结构体挂在DebugObject
的事件链表上,然后激活DebugObject
上的等待事件
调试器这边,调用完WaitForDebugEvent
之后,也会进入内核,主要的逻辑在NtWaitDebugEvent
中,当DebugObject
中的等待事件被激活,就从事件链表中取出一个内核态的DEBUG_EVENT
结构体,返回3环后转换成3环的DEBUG_EVENT
,注意这个3环的跟0环的不一样,对于我们应用层的调试器开发,只需要关心3环的DEBUG_EVENT
,结构体如下:
当调试事件为不同种类是,下面的联合体为不同的结构体
根据上述结构体,我们可以认为调试事件可以分为9种,而除了异常事件之外,其他的八种事件仅仅是通知调试器一下,并不需要调试器做出什么回应,调试器需要关注的是异常事件,被调试进程中触发的所有异常都会发送两次给调试器,对于调试器来说,最重要的就是三大断点(软件、硬件、内存)和单步,都是通过异常来实现的
经过上面两节的学习,应该对调试体系有了一些认识,接下来就来看一下,编写一个简单的命令行调试器的一般步骤:
框架代码如下:
环境:win11、x86汇编、radasm
在控制台版的调试器中,什么时候用户可以输入呢?可以参考windbg,如果不强行暂停的话,就只能在int3断点断下时输入,那么我们来加一下接收用户输入的代码
我们在使用OD时,经常会对一条汇编指令按下F2下断点,在程序运行到这个地址的时候OD就会停下,这就是对这个地址下了一个软件断点
软件断点的本质是在指定地址处写了一个会触发异常的指令,最常用的是int 3指令(也可以不是int3,只要是可以抛异常的指令就行,帮比如特权指令),机器码是0xCC,当CPU运行到0xCC的时候,经过一系列异常派发,最终调试器会接收到异常调试事件,此时DEBUG_EVENT.dwDebugEventCode
是EXCEPTION_DEBUG_EVENT
,DEBUG_EVENT
的第三个成员此时是EXCEPTION_DEBUG_INFO
异常记录结构体中保存了关于异常的信息
其中ExceptionCode
就是异常码,当因为调试器接收到int3异常时,ExceptionCode
为EXCEPTION_BREAKPOINT
所以,我们自己编写调试器实现软件断点也使用int3指令,要设置一个int3断点很简单,步骤如下:
在触发断点之后,调试器会接收到int3异常,我们在int3异常中需要做一些事情来消除我们下的int3断点的影响,因为此时int3指令已经执行完了,而原有保存在这里的指令并没有执行,步骤如下:
首先,响应调试事件中的异常事件
响应异常事件中和int3异常和单步异常
在处理int3断点时,首先需要判断一下,是否是系统断点,如果是系统的初始断点就不用处理(eip已经指向下一条指令了,返回就能正常运行),如果是自己下的断点就需要特殊处理
处理单步异常
当我们调用OutPutDebugStringA/W时,本质上也是发送调试事件给调试器,我们可以在调试器里面接收到打印的日志,并且输出,这里需要注意的是,调试字符串的地址是被调试进程的地址,不是调试器的,所以需要跨进程读写内存
调用函数GetThreadSelectorEntry
可在3环获取段描述符,解析段描述符就可以拿到段的基址和界限
单步断点是是调试器的一个最重要的功能,就是在OD中按下F8或者F7之后运行到下一条指令,分两种情况:
单步步过很好处理,只需要在用户输入命令之后,设置Elfags.TF=1,那么被调试进程则会在执行完这条机器指令之后抛出单步异常(0x80000004),然后就会被我们的调试器接收到,停下来继续接收用户的输入就好了
单步步过就有一点麻烦了,首先的判断当前这条指令是不是call指令,如果是call的话就需要对下一条指令下一个软件断点,然后返回继续运行,直到被调试程序执行到这个断点,就将这个断点删除,注意跟自己下的软件断点区分开来,自己下的需要再次设置单步来重设CC,而因为单步步过下的软件断点只需要恢复被CC覆盖的指令,而不需要设置单步后重设CC
还有两个问题就是,我们如何得知当前指令是call、以及下一条指令的地址是多少,要算出来这两个,就需要反汇编引擎的支持
Capstone是一个多架构的反汇编框架,支持多种CPU架构和多种文件格式。它是一个开源项目,可以在许多平台上免费使用。Capstone提供了一个易于使用的API,可以将二进制代码反汇编为汇编代码,以及提取出汇编代码中的操作数和操作数类型。
我们使用capstone就可以判断当前的指令是否是call,以及call指令的长度,下面来看学习capstone的用法
首先需要下载capstone的库,这里可以选择直接下载二进制文件(DLL和lib)或者是下载源码自己编译,我这里就直接下载二进制文件了
下载下来之后发现又头文件(include),静态库(capstone.lib)和动态库(capstone.dll、capstone_dll.lib),这里选择使用静态库
创建一个vs的控制台工程,将include文件夹和capstone.lib复制到vs工程目录下,在链接器->输入->附加依赖项中添加lib文件,然后写代码测试:
调用cs_disasm函数我们可以反汇编一段机器码,并且可以指定反汇编出来的指令条数,传出参数是cs_insn结构体指针,每一个cs_insn结构体代表一个机器指令,从中我们可以获取汇编指令的操作码和操作数,机器指令的长度,接下来我们编写一个DLL,封装反汇编的代码,使我们从汇编层面使用更方便
新建DLL工程,配置如上,导出函数DisAsmOne
接下来就编写单步的代码
步骤:
步骤:
调试器的追踪功能就是自动单步执行指令并记录执行过的指令,比如在x64dbg下使用Trace功能
首先点击跟踪,步进直到条件满足,意思是一直自动单步步入,直到满足某个条件
在弹出的窗口中可以填写自动单步的终点的条件,比如eip=0x12345678,然后在日志文本中填写要记录的信息,这里的信息需要使用x64dbg的字符串格式化功能,比如{p:eip} {i: eip}
,{}就相当于C语言的printf,冒号前面的i和p相当于格式化符号,i表示指定地址处的汇编指令,p表示将指定数据格式化成十六进制地址的形式,冒号写数据,详细说明见x64dbg手册
点击日志文件,选择保存到的文件路径,点击确定就从当前eip开始自动单步
当执行到满足暂停条件之后,执行就会下来,这时候可以去看日志文件,发现每执行一次单步,就会向文件中写入一条日志文本:
od的trace也差不多
我们要在自己的调试器中实现trace功能的话也很简单,步骤如下:
但是要支持暂停条件就有点麻烦,那就写简单一点,暂时只支持trace 0x12345678
,单步执行到地址0x12345678就停下,然后单步可选择是单步步入或者是单步步入,这里暂时写死单步步过,下面来看代码实现
首先接受用户输入,判断如果输入的是trace命令,则调用OnTrace
函数
在SetTrace
函数中,调用Setp_
函数来下单步步过的命令,利用之前写的代码完成,并且设置trace标志与终止地址,然后就返回继续执行
此时可能有两种结果:
这两种情况分别区分于p命令时遇到call和没遇到call,那么需要在这两个地方停下来处理
触发单步断点时,如果trace标志为真,则表示正在trace,调用OnTrace
函数
触发int3断点时,如果trace标志为真,则表示正在trace,调用OnTrace
函数
这两处统一调用OnTrace
函数处理
在OnTrace
函数中,先获取当前eip,如果当前按eip等于要暂停的地址,则清除标志,接受用户输入,否则单纯打印一下当前寄存器环境和汇编指令,然后继续设置单步步进
硬件断点是intel CPU层面支持的一种断点,故得名硬件断点,在调试器中,可以通过设置被调试进程的DR0~7这8个调试寄存器来设置硬件断点
硬件断点最多只能设置4个,支持执行、读、写三种断点,对于读写断点,还支持设置长度(1、2、4、8字节)
由于硬件断点是通过寄存器来设置的,众所周知,每个线程都有自己的寄存器环境,所以硬件断点是线程相关的,比如给线程A设置的硬件断点,线程B并不会触发,虽然CPU支持设置"全局的"硬件断点,但是windows并不支持
由于设置硬件断点并不需要修改代码,不像软件断点那样需要修改指令,所以硬件断点有自己独特的应用场景,比如一段代码会被解密后执行,或者说一段内存会被填充代码之后执行,那么在填充之前,下CC断点是没有用的,因为填充内存时CC会被覆盖掉,这时候就需要硬件断点了,无论代码怎么改,只要走到了这个地址,硬件执行断点就会断下来
下面是CR0~CR7寄存器的结构
设置硬件断点的步骤可分为三步
上面8个寄存器,我们需要使用的分为两类:
设置断点类型和长度,DR7的16~31位一共16位,分别表示4个断点的类型和长度,比如16、17位表示CR0处的断点的类型(执行、读、写),18、19位表示CR0处的断点的长度(1、2、4)
启用断点,DR7的0~7位的每两位分别表示对应的断点是否启用,比如L0=1表示CR0处的断点启用,L0=1表示断点是线程相关的,G0=1表示是进程相关的,但是windows不支持,所以Gx位都给0就好了,第8位和第9位是L位和G位的大开关,如果要启动局部断点,那么LE位是必须置1的,当然GE位也是没用的
注意:如果rw位设置为0的话是执行断点,此时len位只能为0
现在我们知道了硬件断点该如何设置,还有一个问题就是如何给线程设置DR寄存器的值?使用GetThreadContext\SetThreadContext
即可
我们在自己的调试器中模拟windbg的硬件断点命令格式
当用户输入上述格式命令时,解析断点类型、长度、地址,传入SetHardWareBp
作为参数
在函数SetHardWareBp
中,我们首先获取线程上下文环境,然后通过Dr7.Lx位来判断断点是否启用,如果没有启用则使用这个位置来保存新的断点,如果四个断点都启用就打印提示断点用完了并且返回
然后通过参数dwType
和dwLen
来设置Dr7中对应断点的LEN和RW位,以及将对应的Dr7.Lx位置1,将Dr7.LE位置1,最后设置线程上下文
触发断点也分两种情况:
硬件断点触发的也是单步异常,所以在单步异常的响应函数中增加判断,IsHbp
函数通过判断dr6寄存器的低4位来返回是否触发了硬件断点,从而调用OnHbp
响应硬件断点
g_bIsHsbp
是硬件执行断点触发后设置的标志,为1表示这是硬件执行断点之后的一个单步,需要恢复断点,函数RestoreHbp
也是简单的将Dr7.Lx位置1
在OnHbp
函数中,首先通过Dr6寄存器的低4位判断是哪个断点触发了
然后计算出对应断点的类型和长度,如果是执行断点,则设置Dr7.Lx=0禁用此断点,然后设置Eflags.TF=1单步断点,最后将线程环境设置回去
内存断点也是调试器不可缺少的重要功能,在x64dbg中,我们可以下内存的读、写、执行断点,内存断点的原理是将指定内存修改为不可访问,然后当执行/读/写这块内存的时候,就会触发内存访问异常C05,这时我们的调试器就可以接收到异常,然后再判断是否是因内存断点指定的地址处抛的异常,如果是的话则触发断点断下,如果不是断点处(修改内存属性的单位是一个页,触发异常的位置很可能不是下断点的位置),则继续执行
当CPU抛出内存访问异常时,是因为这条指令的执行违反的内存属性,所以这条指令是不可能执行成功的,所以当抛出异常时,eip还是指向当前的指令处,所以,无论有没有触发断点,都需要将内存属性恢复,然后设置单步断点和标志,在下一次单步来的时候重新设置内存属性为不可访问
核心的原理就是这样,但是还有很多细节需要考虑
在开始写内存断点之前,我们需要考虑一下问题:
1. 内存不存在
指定内存不存在的话,需要在下内存断点之前,需要调用VirtualQueryEx
函数来查询内存是否存在
2. 断点重叠
断点重叠,由于内存的最小单位是一个页(4096字节),所以设置内存的属性也是按页来设置的,如果说一个页内设置多个断点的话,那么如果直接修改内存,保存内存页原属性的话,第一个断点保存的是正常的,后面的断点保存的属性都是被第一个断点修改之后的属性,那么恢复内存属性的时候就会出问题,所以我们需要一张表,保存所有被修改过内存属性的页面,由于下断点的时候,修改内存属性只需要暴力修改成不可访问就行了,触发断点再来判断是不是我们需要的断点(r/w),所以,这张表中只需要保存内存页的基址
3. 断点跨页
我们的内存断点要支持任意长度,那么就必须要考虑跨页的问题,一个断点可能会跨2个甚至多个页,那么在恢复内存属性的时候,也必须恢复多个页的内存属性,那就必须将这个断点和页的关系保存下来,便于日后恢复的时候查询,那就还需要一张断点表和一张断点-内存表
综上所属,我们需要三张表:
断点表:保存断点的地址、长度和类型
内存页表:保存被修改过内存属性的页基址,内存页原属性
断点-内存表:保存一个断点占了的内存页。比如断点设在了0x401000上,但是长度为0x2000,那么就占了0x401000和0x402000这两个页,需要保存0x401000-0x401000、0x401000-0x402000这两项
在设置断点时,需要先计算出断点所占的页面,然后去内存页表中查询页面是否已经被修改过了,如果修改过了就直接不用修改内存属性,如果没修改过就需要修改页面属性,加入页面表中,然后再将断点加入断点表,断点对应几张内存页加入断点-内存表中
在触发断点时,我们从断点表中找到触发的断点,遍历断点-内存表,找到对应的内存页,然后查询每个内存页是否在断点-内存表中还有其他的断点在用,如果在用,则只要删除断点-内存表中自己这一项,不去修改内存属性,因为别的断点还要用,如果断点对应的内存页已经没有其他断点用了,那么就恢复内存属性,并且在内存页表中删除对应的内存页,最后删除断点表中的断点
这里我们采用如下的形式来设置、显示、删除内存断点
在处理用户输入时,如果用户输入的是bmp,则解析地址、类型、长度,并调用SetBmp
来设置断点
在SetBmp
中,首先遍历断点所占的内存页,调用IsBadAddr
判断内存是否存在,如果不存在则提示下断点失败,IsBadAddr
中调用VirtualQueryEx
来获取内存属性,如果内存的状态为MEM_FREE,则说明被调试程序没有申请这块内存
然后,遍历断点所占页,看是否已经在被修改过属性的页面数组g_arrPageMod
中,如果存在,就不用修改属性,先前已经被修改过了,如果不存在,则调用VirtualProtectEx
修改内存属性为PAGE_NOACCESS
,然后加入到被修改过属性的页面数组g_arrPageMod
中
紧接着保存断点信息(地址、长度、类型)到断点数组g_arrBmp
最后,保存断点和页面的对应关系到断点页面数组g_arrBmpAndPage
(断点地址-占用的页面),一个断点可能占用多个页面,所以可能占用多个数组项
当我们将内存页的属性设置为PAGE_NOACCESS
之后,任何对内存的访问将会导致EXCEPTION_ACCESS_VIOLATION
(C05异常),那么我就在异常事件的处理中来响应C05异常,处理我们的内存断点
在OnC05
中,我们从DEBUG_EVENT中获取异常事件结构体EXCEPTION_DEBUG_INFO
,在异常事件结构体中获取异常记录结构体EXCEPTION_RECORD
在异常记录结构体中:
所以通过数组ExceptionInformation
我们就可以获取到这次内存访问异常的所有信息
首先调用IsSelfC05
来判断这次C05异常是否是因为调试器修改内存属性而导致的,IsSelfC05
内部通过遍历被修改过属性的内存页表来判断,如果不是则返回DBG_EXCEPTION_NOT_HANDLED
否则,调用VirtualProtectEx
来恢复内存属性
为了让内存断点恢复,也需要设置一个单步断点,做一个标记,记录这个内存页面的基址,当下一个单步断点来的时候,重新将内存属性设置为PAGE_NOACCESS
最后,遍历断点数组,通过判断断点的范围和类型来看这个断点是不是下的断点,是的话停下来接收用户输入
在单步断点中,判断上面讲的内存断点的单步标志,然后调用VirtualProtectEx
来重新设置内存属性为PAGE_NOACCESS
前面软/硬件断点都忘记说怎么删除断点了,不过这两个断点删除起来也简单,软件断点将CC覆盖的指令恢复,将断点从数组中删除;硬件断点将DR7.Lx位置0就好了
内存断点删起来因为三张表的存在有点麻烦
这里定义DelBmp
l来删除断点,传入参数是断点的序号,也就是在断点数组中的索引
首先从断点数组中保存对应断点到局部变量,然后将数组中这个断点清空
然后,遍历断点-内存页数组,找到这个断点涉及的页面,保存到局部变量,然后清空断点-内存页数组中这个断点的数据
再来遍历此断点对应的内存页,对于每个页,再次遍历断点-内存页数组,如果在断点-内存页还有这个内存页,说明还有其他断点在用这个内存页,那就说明都不做,如果没有其他断点用这个内存页了,那就从内存页表中删除这个内存页的项,再恢复这个内存页的属性
到这里调试器主要的软件、硬件、内存断点、trace、单步已经实现完成了,剩下的功能就是显示反汇编、显示修改数据、寄存器、运行到返回等小功能了,那么这个简易的调试器就算是完成了,感谢阅读。
科锐44期学员
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
/
/
调试事件的种类
DWORD dwProcessId;
/
/
进程
id
DWORD dwThreadId;
/
/
线程
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_INFO RipInfo;
/
/
系统错误
} u;
} DEBUG_EVENT,
*
LPDEBUG_EVENT;
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
/
/
调试事件的种类
DWORD dwProcessId;
/
/
进程
id
DWORD dwThreadId;
/
/
线程
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_INFO RipInfo;
/
/
系统错误
} u;
} DEBUG_EVENT,
*
LPDEBUG_EVENT;
.
386
.model flat, stdcall ;
32
bit memory model
option casemap :none ;case sensitive
include Stdlib.Inc
include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc
includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib
assume fs:nothing
.data
g_hProcess dd
0
g_szExe db
"winmine.exe"
,
0
g_CreateProcessFailed db
"CreateProcessFailed!"
,
0
g_szEXCEPTION_DEBUG_EVENT db
"EXCEPTION_DEBUG_EVENT"
,
0
g_szCREATE_THREAD_DEBUG_EVENT db
"CREATE_THREAD_DEBUG_EVENT "
,
0
g_szCREATE_PROCESS_DEBUG_EVENT db
"CREATE_PROCESS_DEBUG_EVENT"
,
0
g_szEXIT_THREAD_DEBUG_EVENT db
"EXIT_THREAD_DEBUG_EVENT"
,
0
g_szEXIT_PROCESS_DEBUG_EVENT db
"EXIT_PROCESS_DEBUG_EVENT"
,
0
g_szLOAD_DLL_DEBUG_EVENT db
"LOAD_DLL_DEBUG_EVENT"
,
0
g_szUNLOAD_DLL_DEBUG_EVENT db
"UNLOAD_DLL_DEBUG_EVENT"
,
0
g_szOUTPUT_DEBUG_STRING_EVENT db
"OUTPUT_DEBUG_STRING_EVENT "
,
0
g_szRIP_EVENT db
"RIP_EVENT"
,
0
g_szFmt db
"%s"
,
0dh
,
0ah
,
0
.code
StartDbg proc
LOCAL @si: STARTUPINFO
LOCAL @pi: PROCESS_INFORMATION
LOCAL @de: DEBUG_EVENT
LOCAL @dwContinueStatus: DWORD
;
1.
启动调试进程
invoke CreateProcess, NULL, offset g_szExe, NULL, NULL, NULL, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr @si, addr @pi
.
if
eax
=
=
0
;启动失败
invoke crt_printf, g_szFmt, offset g_CreateProcessFailed
ret
.endif
invoke CloseHandle, @pi.hThread
mov eax, @pi.hProcess
mov g_hProcess, eax
;
2.
循环接收调试事件
.
while
TRUE
invoke RtlZeroMemory,addr @de, size @de
invoke WaitForDebugEvent, addr @de, INFINITE
;默认处理为继续执行
mov @dwContinueStatus, DBG_CONTINUE
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
CREATE_THREAD_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szCREATE_THREAD_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
CREATE_PROCESS_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szCREATE_PROCESS_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
EXIT_THREAD_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szEXIT_THREAD_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
EXIT_PROCESS_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szEXIT_PROCESS_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
LOAD_DLL_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szLOAD_DLL_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
UNLOAD_DLL_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szUNLOAD_DLL_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
OUTPUT_DEBUG_STRING_EVENT
invoke crt_printf,offset g_szFmt, offset g_szOUTPUT_DEBUG_STRING_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
RIP_EVENT
invoke crt_printf, offset g_szFmt, offset g_szRIP_EVENT
.endif
;
4.
返回处理结果
invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwContinueStatus
.endw
ret
StartDbg endp
start:
invoke StartDbg
invoke ExitProcess,
0
;
end start
.
386
.model flat, stdcall ;
32
bit memory model
option casemap :none ;case sensitive
include Stdlib.Inc
include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
include msvcrt.inc
includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
includelib msvcrt.lib
assume fs:nothing
.data
g_hProcess dd
0
g_szExe db
"winmine.exe"
,
0
g_CreateProcessFailed db
"CreateProcessFailed!"
,
0
g_szEXCEPTION_DEBUG_EVENT db
"EXCEPTION_DEBUG_EVENT"
,
0
g_szCREATE_THREAD_DEBUG_EVENT db
"CREATE_THREAD_DEBUG_EVENT "
,
0
g_szCREATE_PROCESS_DEBUG_EVENT db
"CREATE_PROCESS_DEBUG_EVENT"
,
0
g_szEXIT_THREAD_DEBUG_EVENT db
"EXIT_THREAD_DEBUG_EVENT"
,
0
g_szEXIT_PROCESS_DEBUG_EVENT db
"EXIT_PROCESS_DEBUG_EVENT"
,
0
g_szLOAD_DLL_DEBUG_EVENT db
"LOAD_DLL_DEBUG_EVENT"
,
0
g_szUNLOAD_DLL_DEBUG_EVENT db
"UNLOAD_DLL_DEBUG_EVENT"
,
0
g_szOUTPUT_DEBUG_STRING_EVENT db
"OUTPUT_DEBUG_STRING_EVENT "
,
0
g_szRIP_EVENT db
"RIP_EVENT"
,
0
g_szFmt db
"%s"
,
0dh
,
0ah
,
0
.code
StartDbg proc
LOCAL @si: STARTUPINFO
LOCAL @pi: PROCESS_INFORMATION
LOCAL @de: DEBUG_EVENT
LOCAL @dwContinueStatus: DWORD
;
1.
启动调试进程
invoke CreateProcess, NULL, offset g_szExe, NULL, NULL, NULL, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr @si, addr @pi
.
if
eax
=
=
0
;启动失败
invoke crt_printf, g_szFmt, offset g_CreateProcessFailed
ret
.endif
invoke CloseHandle, @pi.hThread
mov eax, @pi.hProcess
mov g_hProcess, eax
;
2.
循环接收调试事件
.
while
TRUE
invoke RtlZeroMemory,addr @de, size @de
invoke WaitForDebugEvent, addr @de, INFINITE
;默认处理为继续执行
mov @dwContinueStatus, DBG_CONTINUE
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
CREATE_THREAD_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szCREATE_THREAD_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
CREATE_PROCESS_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szCREATE_PROCESS_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
EXIT_THREAD_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szEXIT_THREAD_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
EXIT_PROCESS_DEBUG_EVENT
invoke crt_printf,offset g_szFmt, offset g_szEXIT_PROCESS_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
LOAD_DLL_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szLOAD_DLL_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
UNLOAD_DLL_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szUNLOAD_DLL_DEBUG_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
OUTPUT_DEBUG_STRING_EVENT
invoke crt_printf,offset g_szFmt, offset g_szOUTPUT_DEBUG_STRING_EVENT
.endif
.
if
@de.dwDebugEventCode
=
=
RIP_EVENT
invoke crt_printf, offset g_szFmt, offset g_szRIP_EVENT
.endif
;
4.
返回处理结果
invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwContinueStatus
.endw
ret
StartDbg endp
start:
invoke StartDbg
invoke ExitProcess,
0
;
end start
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @szUserInput[MAX_PATH]: CHAR
LOCAL @szCmd[MAX_PATH]: CHAR
LOCAL @uOpData: DWORD
;接收用户输入
invoke crt_gets, addr @szUserInput
;处理用户输入
invoke crt_sscanf, addr @szUserInput, offset g_szInputFmt, addr @szCmd, addr @uOpData
;恢复运行
OnBreakPoint endp
OnException proc pDebugEvent: ptr DEBUG_EVENT
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
;int3断点
.
if
[esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_BREAKPOINT
invoke OnBreakPoint, addr [esi].u.Exception
.endif
OnException endp
;...
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke OnException, addr @de
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
.endif
;...
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @szUserInput[MAX_PATH]: CHAR
LOCAL @szCmd[MAX_PATH]: CHAR
LOCAL @uOpData: DWORD
;接收用户输入
invoke crt_gets, addr @szUserInput
;处理用户输入
invoke crt_sscanf, addr @szUserInput, offset g_szInputFmt, addr @szCmd, addr @uOpData
;恢复运行
OnBreakPoint endp
OnException proc pDebugEvent: ptr DEBUG_EVENT
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
;int3断点
.
if
[esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_BREAKPOINT
invoke OnBreakPoint, addr [esi].u.Exception
.endif
OnException endp
;...
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke OnException, addr @de
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
.endif
;...
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
/
/
异常记录
DWORD dwFirstChance;
/
/
第
1
次还是第
2
次异常
} EXCEPTION_DEBUG_INFO,
*
LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
/
/
异常记录
DWORD dwFirstChance;
/
/
第
1
次还是第
2
次异常
} EXCEPTION_DEBUG_INFO,
*
LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD
*
ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD
*
ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
SetBP proc to: DWORD
LOCAL @Int3Index: DWORD
;判断是否还可以继续下断点
.
if
g_dwInt3Cnt >
=
256
;断点数量已满,无法再下断点
invoke crt_printf, offset g_szInt3CntIsFull
ret
.endif
inc g_dwInt3Cnt
;找到保存断点的数组下标
invoke GetPosToSaveInt3
.
if
(eax
=
=
-
1
) || (eax >
=
g_dwInt3Cnt)
;断点数量已满,无法再下断点
invoke crt_printf, offset g_szInt3CntIsFull
ret
.endif
mov @Int3Index, eax
lea eax, [eax
*
4
]
mov ebx, offset g_arrInt3Addr
add eax, ebx
mov edi, to
mov dword ptr [eax], edi ;保存写int3的地址
mov edx, offset g_arrInt3CoverIns
add edx, @Int3Index
invoke ReadMem, to, edx,
1
;保存int3覆盖的指令
;向被调试进程指定地址写入
0xCC
invoke WriteMem, to, offset g_InsCC,
1
ret
SetBP endp
SetBP proc to: DWORD
LOCAL @Int3Index: DWORD
;判断是否还可以继续下断点
.
if
g_dwInt3Cnt >
=
256
;断点数量已满,无法再下断点
invoke crt_printf, offset g_szInt3CntIsFull
ret
.endif
inc g_dwInt3Cnt
;找到保存断点的数组下标
invoke GetPosToSaveInt3
.
if
(eax
=
=
-
1
) || (eax >
=
g_dwInt3Cnt)
;断点数量已满,无法再下断点
invoke crt_printf, offset g_szInt3CntIsFull
ret
.endif
mov @Int3Index, eax
lea eax, [eax
*
4
]
mov ebx, offset g_arrInt3Addr
add eax, ebx
mov edi, to
mov dword ptr [eax], edi ;保存写int3的地址
mov edx, offset g_arrInt3CoverIns
add edx, @Int3Index
invoke ReadMem, to, edx,
1
;保存int3覆盖的指令
;向被调试进程指定地址写入
0xCC
invoke WriteMem, to, offset g_InsCC,
1
ret
SetBP endp
;...
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
invoke OnException, addr @de
mov @dwContinueStatus, eax
.endif
;...
;...
;
3.
处理调试事件
.
if
@de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT
invoke crt_printf, offset g_szFmt, offset g_szEXCEPTION_DEBUG_EVENT
invoke OnException, addr @de
mov @dwContinueStatus, eax
.endif
;...
OnException proc pDebugEvent: ptr DEBUG_EVENT
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
.
if
[esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_BREAKPOINT ;int3
invoke OnBreakPoint, addr [esi].u.Exception
.elseif [esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_SINGLE_STEP ;单步
invoke OnSingleStep, addr [esi].u.Exception
.endif
ret
OnException endp
OnException proc pDebugEvent: ptr DEBUG_EVENT
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
.
if
[esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_BREAKPOINT ;int3
invoke OnBreakPoint, addr [esi].u.Exception
.elseif [esi].u.Exception.pExceptionRecord.ExceptionCode
=
=
EXCEPTION_SINGLE_STEP ;单步
invoke OnSingleStep, addr [esi].u.Exception
.endif
ret
OnException endp
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @hThread: HANDLE
LOCAL @ThreadCtx: CONTEXT
LOCAL @dwCurAddr: DWORD
LOCAL @dwBpIdx: DWORD
invoke RtlZeroMemory, addr @ThreadCtx, size @ThreadCtx
.
if
g_IsSysBp
=
=
TRUE
;如果是系统断点,直接运行
invoke crt_printf, offset g_szSysBp
mov g_IsSysBp, FALSE
.
else
;如果不是系统断点,就认为是自己的断点
invoke crt_printf, offset g_szSelfBp
;打开当前停下来的线程
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId
mov @hThread, eax
;获取线程环境,计算断点的地址
mov @ThreadCtx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ThreadCtx
mov eax, @ThreadCtx.regEip
mov @dwCurAddr, eax
dec @dwCurAddr ;int3断点停下来的位置是
0xCC
指令的下一条指令
;遍历断点数组,找到当前断点的索引
invoke FindBp, @dwCurAddr
mov @dwBpIdx, eax
.
if
eax !
=
-
1
;恢复
0xCC
覆盖的
1
字节指令
mov eax, offset g_arrInt3CoverIns
add eax, @dwBpIdx
invoke WriteMem, @dwCurAddr, eax,
1
.endif
;设置eip
-
1
dec @ThreadCtx.regEip
;设置单步断点tf寄存器,用于恢复
0xCC
or
@ThreadCtx.regFlag,
0100h
invoke SetThreadContext, @hThread, addr @ThreadCtx
mov g_IsNeedWriteCC,TRUE ;在下一次单步异常来的时候,是否应该重写CC
mov eax, @dwCurAddr
mov g_dwAddrWriteCC, eax ;往哪里写
.endif
;TODO:打印地址、指令、寄存器
invoke UserInput
ret
OnBreakPoint endp
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @hThread: HANDLE
LOCAL @ThreadCtx: CONTEXT
LOCAL @dwCurAddr: DWORD
LOCAL @dwBpIdx: DWORD
invoke RtlZeroMemory, addr @ThreadCtx, size @ThreadCtx
.
if
g_IsSysBp
=
=
TRUE
;如果是系统断点,直接运行
invoke crt_printf, offset g_szSysBp
mov g_IsSysBp, FALSE
.
else
;如果不是系统断点,就认为是自己的断点
invoke crt_printf, offset g_szSelfBp
;打开当前停下来的线程
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId
mov @hThread, eax
;获取线程环境,计算断点的地址
mov @ThreadCtx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ThreadCtx
mov eax, @ThreadCtx.regEip
mov @dwCurAddr, eax
dec @dwCurAddr ;int3断点停下来的位置是
0xCC
指令的下一条指令
;遍历断点数组,找到当前断点的索引
invoke FindBp, @dwCurAddr
mov @dwBpIdx, eax
.
if
eax !
=
-
1
;恢复
0xCC
覆盖的
1
字节指令
mov eax, offset g_arrInt3CoverIns
add eax, @dwBpIdx
invoke WriteMem, @dwCurAddr, eax,
1
.endif
;设置eip
-
1
dec @ThreadCtx.regEip
;设置单步断点tf寄存器,用于恢复
0xCC
or
@ThreadCtx.regFlag,
0100h
invoke SetThreadContext, @hThread, addr @ThreadCtx
mov g_IsNeedWriteCC,TRUE ;在下一次单步异常来的时候,是否应该重写CC
mov eax, @dwCurAddr
mov g_dwAddrWriteCC, eax ;往哪里写
.endif
;TODO:打印地址、指令、寄存器
invoke UserInput
ret
OnBreakPoint endp
;触发单步断点
OnSingleStep proc pExcption: ptr EXCEPTION_RECORD
LOCAL @dbCC: CHAR
mov @dbCC,
0cch
;判断是否是因需要重写CC而下的单步
.
if
g_IsNeedWriteCC
=
=
TRUE
mov g_IsNeedWriteCC, FALSE
invoke WriteMem, g_dwAddrWriteCC, addr @dbCC,
1
;重写CC
.endif
;TODO: 如果是手动输入的单步 f7 f8,那就需要停下来接收用户输入,否则直接返回
mov eax, DBG_CONTINUE
ret
OnSingleStep endp
;触发单步断点
OnSingleStep proc pExcption: ptr EXCEPTION_RECORD
LOCAL @dbCC: CHAR
mov @dbCC,
0cch
;判断是否是因需要重写CC而下的单步
.
if
g_IsNeedWriteCC
=
=
TRUE
mov g_IsNeedWriteCC, FALSE
invoke WriteMem, g_dwAddrWriteCC, addr @dbCC,
1
;重写CC
.endif
;TODO: 如果是手动输入的单步 f7 f8,那就需要停下来接收用户输入,否则直接返回
mov eax, DBG_CONTINUE
ret
OnSingleStep endp
OnOutPutDebugString proc pDebugEvent: ptr DEBUG_EVENT
LOCAL @szDbgStr[MAX_PATH
*
2
+
2
]: CHAR
LOCAL @szStr[MAX_PATH
+
1
]: CHAR
invoke RtlZeroMemory, addr @szDbgStr, MAX_PATH
*
2
+
2
invoke RtlZeroMemory, addr @szStr, MAX_PATH
+
1
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
lea edi, [esi].u.DebugString
assume edi: ptr OUTPUT_DEBUG_STRING_INFO
invoke ReadMem, [edi].lpDebugStringData, addr @szDbgStr, [edi].nDebugStringiLength
.
if
[edi].fUnicode
;
unicode
to ansi
invoke WideCharToMultiByte, CP_ACP,
0
, addr @szDbgStr,
-
1
, addr @szStr, MAX_PATH, NULL, NULL
.
else
invoke crt_strcpy, addr @szStr, addr @szDbgStr
.endif
;控制台输出
invoke crt_printf, offset g_szDebugStr, addr @szStr
ret
OnOutPutDebugString endp
OnOutPutDebugString proc pDebugEvent: ptr DEBUG_EVENT
LOCAL @szDbgStr[MAX_PATH
*
2
+
2
]: CHAR
LOCAL @szStr[MAX_PATH
+
1
]: CHAR
invoke RtlZeroMemory, addr @szDbgStr, MAX_PATH
*
2
+
2
invoke RtlZeroMemory, addr @szStr, MAX_PATH
+
1
mov esi, pDebugEvent
assume esi: ptr DEBUG_EVENT
lea edi, [esi].u.DebugString
assume edi: ptr OUTPUT_DEBUG_STRING_INFO
invoke ReadMem, [edi].lpDebugStringData, addr @szDbgStr, [edi].nDebugStringiLength
.
if
[edi].fUnicode
;
unicode
to ansi
invoke WideCharToMultiByte, CP_ACP,
0
, addr @szDbgStr,
-
1
, addr @szStr, MAX_PATH, NULL, NULL
.
else
invoke crt_strcpy, addr @szStr, addr @szDbgStr
.endif
;控制台输出
invoke crt_printf, offset g_szDebugStr, addr @szStr
ret
OnOutPutDebugString endp
BOOL
GetThreadSelectorEntry(
[
in
] HANDLE hThread,
[
in
] DWORD dwSelector,
[out] LPLDT_ENTRY lpSelectorEntry
);
BOOL
GetThreadSelectorEntry(
[
in
] HANDLE hThread,
[
in
] DWORD dwSelector,
[out] LPLDT_ENTRY lpSelectorEntry
);
int
main(void)
{
csh handle;
/
/
引擎句柄
cs_insn
*
insn;
/
/
指令结构体
size_t count;
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle) !
=
CS_ERR_OK)
/
/
打开句柄
return
-
1
;
count
=
cs_disasm(handle, CODE, sizeof(CODE)
-
1
,
0x1000
,
0
, &insn);
/
/
反汇编机器码
if
(count >
0
) {
size_t j;
for
(j
=
0
; j < count; j
+
+
) {
printf(
"0x%"
PRIx64
":\t%s\t\t%s\n"
, insn[j].address, insn[j].mnemonic,
/
/
循环输出汇编指令
insn[j].op_str);
}
cs_free(insn, count);
/
/
释放内存
}
else
printf(
"ERROR: Failed to disassemble given code!\n"
);
cs_close(&handle);
/
/
关闭引擎句柄
return
0
;
}
int
main(void)
{
csh handle;
/
/
引擎句柄
cs_insn
*
insn;
/
/
指令结构体
size_t count;
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle) !
=
CS_ERR_OK)
/
/
打开句柄
return
-
1
;
count
=
cs_disasm(handle, CODE, sizeof(CODE)
-
1
,
0x1000
,
0
, &insn);
/
/
反汇编机器码
if
(count >
0
) {
size_t j;
for
(j
=
0
; j < count; j
+
+
) {
printf(
"0x%"
PRIx64
":\t%s\t\t%s\n"
, insn[j].address, insn[j].mnemonic,
/
/
循环输出汇编指令
insn[j].op_str);
}
cs_free(insn, count);
/
/
释放内存
}
else
printf(
"ERROR: Failed to disassemble given code!\n"
);
cs_close(&handle);
/
/
关闭引擎句柄
return
0
;
}
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
BOOL
APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
/
/
返回值:汇编代码的长度
/
/
参数:机器指令的地址、机器指令的长度、指令的Eip、输出的汇编代码的地址、保存汇编代码的缓冲区大小
EXTERN_C __declspec(dllexport)
uint32_t __stdcall DisAsmOne(uint8_t
*
pCode, uint32_t uCodeSize, uint32_t uEip, uint8_t
*
pAsm, uint32_t uAsmSize)
{
/
/
初始化Capstone引擎
csh handle;
cs_insn
*
insn;
cs_err err
=
cs_open(CS_ARCH_X86, CS_MODE_32, &handle);
if
(err !
=
CS_ERR_OK) {
return
0
;
}
/
/
解析指令并输出
size_t count
=
cs_disasm(handle, pCode, uCodeSize, uEip,
1
, &insn);
if
(count
=
=
0
)
{
return
0
;
}
sprintf_s(
(char
*
const)pAsm,
uAsmSize,
"%s %s"
,
insn[
0
].mnemonic,
insn[
0
].op_str);
DWORD dwSize
=
insn
-
>size;
cs_free(insn, count);
/
/
关闭Capstone引擎
cs_close(&handle);
return
dwSize;
}
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
BOOL
APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
/
/
返回值:汇编代码的长度
/
/
参数:机器指令的地址、机器指令的长度、指令的Eip、输出的汇编代码的地址、保存汇编代码的缓冲区大小
EXTERN_C __declspec(dllexport)
uint32_t __stdcall DisAsmOne(uint8_t
*
pCode, uint32_t uCodeSize, uint32_t uEip, uint8_t
*
pAsm, uint32_t uAsmSize)
{
/
/
初始化Capstone引擎
csh handle;
cs_insn
*
insn;
cs_err err
=
cs_open(CS_ARCH_X86, CS_MODE_32, &handle);
if
(err !
=
CS_ERR_OK) {
return
0
;
}
/
/
解析指令并输出
size_t count
=
cs_disasm(handle, pCode, uCodeSize, uEip,
1
, &insn);
if
(count
=
=
0
)
{
return
0
;
}
sprintf_s(
(char
*
const)pAsm,
uAsmSize,
"%s %s"
,
insn[
0
].mnemonic,
insn[
0
].op_str);
DWORD dwSize
=
insn
-
>size;
cs_free(insn, count);
/
/
关闭Capstone引擎
cs_close(&handle);
return
dwSize;
}
;...
;输入t命令时
;t
.
if
@szCmd[
0
]
=
=
't'
invoke SetT
ret
.endif
;...
;设置单步断点
SetT proc
;设置tf标志位
invoke SetTF
mov g_IsSingleStepMaual, TRUE ;标记一下这个单步断点是手动下的
ret
SetT endp
;给调试线程的eflags的tf位设置
1
SetTF proc
LOCAL @hThread: HANDLE
LOCAL @ctx: CONTEXT
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId ;打开调试线程
mov @hThread, eax
mov @ctx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ctx ;获取线程环境
or
@ctx.regFlag,
0100h
;设置TF位
invoke SetThreadContext,@hThread, addr @ctx ;设置线程环境
invoke CloseHandle, @hThread
ret
SetTF endp
;触发单步断点
OnSingleStep proc pExcption: ptr EXCEPTION_RECORD
LOCAL @dbCC: CHAR
mov @dbCC,
0cch
;要重写CC而下的单步断点,重写CC
.
if
g_IsNeedWriteCC
=
=
TRUE
mov g_IsNeedWriteCC, FALSE
invoke WriteMem, g_dwAddrWriteCC, addr @dbCC,
1
.endif
;手动输入的单步断点,,什么都不需要做,接收用户输入
.
if
g_IsSingleStepMaual
mov g_IsSingleStepMaual, FALSE
invoke UserInput
.endif
ret
OnSingleStep endp
;...
;输入t命令时
;t
.
if
@szCmd[
0
]
=
=
't'
invoke SetT
ret
.endif
;...
;设置单步断点
SetT proc
;设置tf标志位
invoke SetTF
mov g_IsSingleStepMaual, TRUE ;标记一下这个单步断点是手动下的
ret
SetT endp
;给调试线程的eflags的tf位设置
1
SetTF proc
LOCAL @hThread: HANDLE
LOCAL @ctx: CONTEXT
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId ;打开调试线程
mov @hThread, eax
mov @ctx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ctx ;获取线程环境
or
@ctx.regFlag,
0100h
;设置TF位
invoke SetThreadContext,@hThread, addr @ctx ;设置线程环境
invoke CloseHandle, @hThread
ret
SetTF endp
;触发单步断点
OnSingleStep proc pExcption: ptr EXCEPTION_RECORD
LOCAL @dbCC: CHAR
mov @dbCC,
0cch
;要重写CC而下的单步断点,重写CC
.
if
g_IsNeedWriteCC
=
=
TRUE
mov g_IsNeedWriteCC, FALSE
invoke WriteMem, g_dwAddrWriteCC, addr @dbCC,
1
.endif
;手动输入的单步断点,,什么都不需要做,接收用户输入
.
if
g_IsSingleStepMaual
mov g_IsSingleStepMaual, FALSE
invoke UserInput
.endif
ret
OnSingleStep endp
Setp_ proc
LOCAL @Eip: DWORD
LOCAL @Code[
10h
]: BYTE
LOCAL @Asm[
100h
]: BYTE
LOCAL @CodeLen: DWORD
;获取Eip
invoke GetEip
mov @Eip, eax
;获取Eip处的机器指令
invoke ReadMem,@Eip, addr @Code,
10h
;反汇编
invoke DisAsmOne, addr @Code,
10h
, @Eip, addr @Asm,
100h
mov @CodeLen, eax
;判断下一条指令是不是call
invoke crt_strstr, addr @Asm, offset g_szCall
.
if
eax
=
=
NULL
;不是call,直接设置t就可以了
invoke SetT
.
else
;是call
;需要在call指令的条指令下int3断点,并且设置p标志
mov ebx, @Eip
add ebx, @CodeLen
invoke SetBP, ebx
mov g_dwPbpIndex, eax ;记录P下的软件断点的索引,以便于删除
mov g_IsPBp, TRUE ;p的标志
.endif
ret
Setp_ endp
;触发int3断点
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @hThread: HANDLE
LOCAL @ThreadCtx: CONTEXT
LOCAL @dwCurAddr: DWORD
LOCAL @dwBpIdx: DWORD
invoke RtlZeroMemory, addr @ThreadCtx, size @ThreadCtx
.
if
g_IsSysBp
=
=
TRUE
;如果是系统断点,直接运行
invoke crt_printf, offset g_szSysBp
mov g_IsSysBp, FALSE
.
else
;打开当前停下来的线程
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId
mov @hThread, eax
;获取线程环境,计算断点的地址
mov @ThreadCtx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ThreadCtx
mov eax, @ThreadCtx.regEip
mov @dwCurAddr, eax
dec @dwCurAddr ;int3断点停下来的位置是
0xCC
指令的下一条指令
;遍历断点数组,找到当前断点的索引
invoke FindBp, @dwCurAddr
mov @dwBpIdx, eax
.
if
eax !
=
-
1
;恢复
0xCC
覆盖的
1
字节指令
mov eax, offset g_arrInt3CoverIns
add eax, @dwBpIdx
invoke WriteMem, @dwCurAddr, eax,
1
.endif
;设置eip
-
1
dec @ThreadCtx.regEip
.
if
g_IsPBp
;p指令下的int3断点,不需要单步之后恢复CC
mov g_IsPBp, FALSE
;从数组中删除断点
invoke DelBp, g_dwPbpIndex
.
else
;正经的int3断点,需要单步之后恢复CC
;如果不是系统断点,就认为是自己的断点
invoke crt_printf, offset g_szSelfBp
;设置单步断点tf寄存器,用于恢复
0xCC
or
@ThreadCtx.regFlag,
0100h
mov g_IsNeedWriteCC,TRUE ;在下一次单步异常来的时候,是否应该重写CC
mov eax, @dwCurAddr
mov g_dwAddrWriteCC, eax ;往哪里写
.endif
invoke SetThreadContext, @hThread, addr @ThreadCtx
invoke CloseHandle, @hThread
.endif
invoke UserInput
ret
OnBreakPoint endp
Setp_ proc
LOCAL @Eip: DWORD
LOCAL @Code[
10h
]: BYTE
LOCAL @Asm[
100h
]: BYTE
LOCAL @CodeLen: DWORD
;获取Eip
invoke GetEip
mov @Eip, eax
;获取Eip处的机器指令
invoke ReadMem,@Eip, addr @Code,
10h
;反汇编
invoke DisAsmOne, addr @Code,
10h
, @Eip, addr @Asm,
100h
mov @CodeLen, eax
;判断下一条指令是不是call
invoke crt_strstr, addr @Asm, offset g_szCall
.
if
eax
=
=
NULL
;不是call,直接设置t就可以了
invoke SetT
.
else
;是call
;需要在call指令的条指令下int3断点,并且设置p标志
mov ebx, @Eip
add ebx, @CodeLen
invoke SetBP, ebx
mov g_dwPbpIndex, eax ;记录P下的软件断点的索引,以便于删除
mov g_IsPBp, TRUE ;p的标志
.endif
ret
Setp_ endp
;触发int3断点
OnBreakPoint proc pExcption: ptr EXCEPTION_RECORD
LOCAL @hThread: HANDLE
LOCAL @ThreadCtx: CONTEXT
LOCAL @dwCurAddr: DWORD
LOCAL @dwBpIdx: DWORD
invoke RtlZeroMemory, addr @ThreadCtx, size @ThreadCtx
.
if
g_IsSysBp
=
=
TRUE
;如果是系统断点,直接运行
invoke crt_printf, offset g_szSysBp
mov g_IsSysBp, FALSE
.
else
;打开当前停下来的线程
mov esi, g_pDebugEvent
assume esi: ptr DEBUG_EVENT
invoke OpenThread, THREAD_ALL_ACCESS, FALSE, [esi].dwThreadId
mov @hThread, eax
;获取线程环境,计算断点的地址
mov @ThreadCtx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, @hThread, addr @ThreadCtx
mov eax, @ThreadCtx.regEip
mov @dwCurAddr, eax
dec @dwCurAddr ;int3断点停下来的位置是
0xCC
指令的下一条指令
;遍历断点数组,找到当前断点的索引
invoke FindBp, @dwCurAddr
mov @dwBpIdx, eax
.
if
eax !
=
-
1
;恢复
0xCC
覆盖的
1
字节指令
mov eax, offset g_arrInt3CoverIns
add eax, @dwBpIdx
invoke WriteMem, @dwCurAddr, eax,
1
.endif
;设置eip
-
1
dec @ThreadCtx.regEip
.
if
g_IsPBp
;p指令下的int3断点,不需要单步之后恢复CC
mov g_IsPBp, FALSE
;从数组中删除断点
invoke DelBp, g_dwPbpIndex
.
else
;正经的int3断点,需要单步之后恢复CC
;如果不是系统断点,就认为是自己的断点
invoke crt_printf, offset g_szSelfBp
;设置单步断点tf寄存器,用于恢复
0xCC
or
@ThreadCtx.regFlag,
0100h
mov g_IsNeedWriteCC,TRUE ;在下一次单步异常来的时候,是否应该重写CC
mov eax, @dwCurAddr
mov g_dwAddrWriteCC, eax ;往哪里写
.endif
invoke SetThreadContext, @hThread, addr @ThreadCtx
invoke CloseHandle, @hThread
.endif
invoke UserInput
ret
OnBreakPoint endp
0062E8F2
mov ebp, esp
0062E8F4
sub esp,
0x10
0062E8F7
mov eax, dword ptr ds:[
0x00699FE8
]
0062E8FC
and
dword ptr ss:[ebp
-
0x8
],
0x0
0062E900
and
dword ptr ss:[ebp
-
0x4
],
0x0
0062E904
push ebx
0062E905
push edi
0062E906
mov edi,
0xBB40E64E
0062E90B
cmp
eax, edi
0062E90D
mov ebx,
0xFFFF0000
0062E912
je
0x0062E921
0062E914
test ebx, eax
0062E916
je
0x0062E921
0062E918
not
eax
0062E91A
mov dword ptr ds:[
0x00699FEC
], eax
0062E91F
jmp
0x0062E981
0062E981
pop edi
0062E982
pop ebx
0062E983
leave
0062E984
ret
0062E8F2
mov ebp, esp
0062E8F4
sub esp,
0x10
0062E8F7
mov eax, dword ptr ds:[
0x00699FE8
]
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-3-26 17:56
被st0ne编辑
,原因: 修改内容