调试器第二讲,单步步入/步过功能实现,以及基本的断点功能实现
昨天,我们实现了调试器的基本框架,那么今天我们实现单步功能,还有断点功能,以及使用反汇编引擎
作者:IBinary
出处:版权所有,欢迎保留原文链接进行转载:)一丶反汇编引擎的编译,生成LIB
首先,我们有一个反汇编引擎的代码,现在我们编译连接一下(注意,可以使用GitHub下载,或者百度Google下载)
关于反汇编引擎的介绍: 请参考转载博客
先编译成OBJ文件,(不连接),上面两个函数就是我们需要的使用的,准确的来说,是下面的这个,因为上面那个不会给你二进制数据.
比如我们反汇编的结果是
00401000 CC int 3 那么这样就是用下面这个,有二进制代码显示
如果使用上面那个则是
00401000 int3 具体爱好看你使用那个,这里具体讲解第二个
先编译成OBJ
需要的就这有3个, 除了stdafx.obj 还有MyDisasm.obj 其余的都是我们用的
现在我们使用lib工具,讲编译好的obj打包成一个lib(当然你也可以写)
关于lib工具的使用,请参考我写的博客,32位汇编第七讲,混合编程 连接:
现在回车,生成我们的lib
然后我们还需要这两个函数的头文件.
现在准备齐全了,准备开始我们的代码编写.
注意: 这里我们是生成的静态lib,你也可以生成动态链接库 (也就是俗称的 DLL)
二丶使用反汇编引擎API
直接看代码吧
新建一个工程,控制台的(随你便,啥都行,能写代码,调用API的就可以)
把我们的lib,和.h文件,拷贝到我们的程序目录下(静态使用)
#include "stdafx.h"#include#include #include "Decode2Asm.h"#pragma comment(lib,"MyDisAsm.lib")int main(int argc, char* argv[]){ BYTE OpCode[] = { 0xcc,0x90}; //解析的地址,地址里面有机器码,我们不知道是什么(这里举例子我们知道) char szAsm[256] = {NULL}; //通过地址,得出反汇编字符串,放到这个缓冲区中 char szBin[256] = {NULL}; //通过地址,得出二进制机器码,放到这个缓冲区中 UINT binSize = NULL; //每次只解析一个指令长度,所以我们要遍历数组,加上这个长度,接着输出出来. BYTE *pCurCode = OpCode; //遍历的时候需要给个指针 do { Decode2AsmOpcode(pCurCode,szAsm,szBin,&binSize,(UINT)pCurCode);//调用API,解析, printf("%-16p%-20s%-20s\r\n",pCurCode,szBin,szAsm); //输出 pCurCode = pCurCode + binSize; //地址 + 指令长度 = 下一次需要解析的指令的地址 } while (pCurCode < (OpCode + sizeof(OpCode))); //判断是否数组越界 system("pause"); return 0;} Decode2AsmOpcode的各个参数意思: 第一个参数: 地址,你要给我一个地址,我去解析 第二个参数: 反汇编代码的缓冲区,通过地址,解析的反汇编会放到这个缓冲区当中 第三个参数: 二进制代码缓冲区,通过地址,解析二进制,放到这个缓冲区当中 第四个参数: 是否计算偏移 关于这个参数,其实是给EIP的值,(也就是地址),例如我们上面的给的. 这个参数的作用是 比如你有一个 JMP 指令 00401000 JMP 000010 这个是正常的汇编代码,我们知道JMP是JMP的偏移 而后面给了这个参数,它则会给我们计算出来 00401000 JMP 00401010
看下输出结果
配合我们昨天写的,则可以反汇编出来调试进程的代码了.
三丶汇编调用反汇编引擎API,显示调试进程的汇编代码.
在上面,我们对API有了一个认识.
下面我们就结合我们昨天写的汇编代码,接着写,显示出来断点位置的反汇编代码.
首先在处理异常的事件中,我们要调用
先加载我们的LIB,以及INC文件(INC文件中,是那个两个API的函数声明,你可以自己通过LIB生成,
具体请参考上面的混合编程的博客链接)
1.加载LIB,以及INC文件
2.使用ReadProcessMemory,和我们的API配合使用
首先使用Read..读取.然后放到我们的数组中,
然后使用API,获取反汇编的各种信息
invoke ReadProcessMemory,g_hProcess, [ebx].u.Exception.pExceptionRecord.ExceptionAddress, @OpCode,sizeof @OpCode, NULLinvoke ShowAsm,addr @OpCode,[ebx].u.Exception.pExceptionRecord.ExceptionAddress
当然,Read的时候,需要注意要保存g_hProcess句柄.这样才可以.
下面的ShowAsm是封装的函数,内部还是调用的Decode2AsmOpco
ShowAsm proc pCode :ptr byte, pAddr:ptr Byte LOCAL @szAsmCode[256]:BYTE LOCAL @szOpCode[256]:BYTE LOCAL @CodeSize1:DWORD invoke Decode2AsmOpcode, pCode, addr @szAsmCode, addr @szOpCode, addr @CodeSize1, pAddr invoke crt_printf, offset g_szDcode, pAddr, addr @szOpCode, addr @szAsmCode retShowAsm endp
这里提一下简单思路,因为真正的都写出来,汇编代码很多,影响观看,所以都是简单思路,没有完整代码,完整代码,在每天的课件资料中,可以去看,这里这说一下核心代码的思路
四丶汇编设置F2断点,以及单步(步入,步过)
简单思路
1.断点的设置:
1.首先,系统断点第一次来,然后在创建进程的时候会有一个地址,我们使用Read...读取地址内容,然后反汇编出来显示
2.读取出来之前,使用VirtualProtectEx将保护属性去除,(注意保存旧的)
3.使用WriteProcessMemory往地址写入CC(注意保存以前的值)
4.重新修改保护属性,改回去(使用旧的)
然后一个断点即完成了,具体代码,请看课件.
2.单步的设置(步入,进函数)
如果是单步,我们要判断断点是我们设置的还是系统设置的.
1.判断是否使我们设置的断点
2.修改内存保护属性(注意保存旧的)
3.写入CC,(int 3断点)
4.读取内存数据
5.显示反汇编
设置单步(步入)异常
1.打开线程获得线程句柄
2.使用GetThreadContext获取寄存器的值
3.设置单步标志,单步表示是要我们设置的,他是第9个标志
or [esi].regflag,0100h 这样设置即可.设置第九位为1
4.设置寄存器环境 SetThreadContext
设置单步步过异常
但步步过和单步步入一样
只不过遇到Call的时候我们要把他的下一条指令设置一个int3断点才可以.
课堂资料: 链接: 密码:5dw9
作者:IBinary出处:版权所有,欢迎保留原文链接进行转载:)