调试器编写第一讲,调试器基本框架
作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)
今天开始调试器第一讲,调试器的基本框架,我们用过很多调试器,比如 WinDbg,OllyDbg,那为什么我们还要自己编写调试器哪?
原因是,OllyDbg等等的各种调试器都太容易被针对了,写调试器,主要是理解别人怎么反调试,并且我们怎么在安全开发的时候,让我们的软件针对调试器.今天就开始调试器第一讲,调试器的基本框架
很多人认为调试器怎么写,没思路,其实调试器就是调用API,熟练运用这些API,则可以进行软件调试
一丶写调试器注意的问题
首先,我们思考一个问题,我们要调试我们的程序,要怎么让我们的程序知道被调试了
是这样的,微软已经帮我们提供了API了,比如我们常用WriteProcessMemory这个API,想一下,微软怎么可能提供在别人进程里面写内存的API哪?
其实这个就是调试器用的,只不过被我们玩坏了.
那么我们MSDN搜索一下这个API,就可以找到所有和调试器相关的API
可以在下方看到,所以和调试器相关的API了.
API就怎么多,熟练运用即可.
二丶调试器API各个API的意思
这里介绍下各个API的意思,并不细讲,等到用到的时候才会细讲怎么用.主要是熟悉一下,算是翻译一下API吧.
/* ContinueDebugEvent :看名字就知道,继续调试事件,意思就是调试程序的时候有事件来,你处理完了要继续. DebugActiveProcess :调试程序,附加进程,这个API就是,如果我们的程序打开了,那么调用这个API,传入进程ID是可以附加调试的. DebugActiveProcessStop :停止调试器,调试的指定进程,也就是调试器要停止对某一个进程的调试 debugBreak :如果程序处于调试的状态,,如果发生断点异常(下断点),允许线程,通知我们的调试器来调试,处理这个异常.否则系统接收
DebugBreakProcess :在指定的进程中,产生一个断点异常
DebugSetProcessKillOnExit :设置调试的线程,退出的时候进行的操作. FatalExit :强制退出调用的进程,并将控制权交给调试器 FlushInstructionCache :刷新指令的高速缓存 GetThreadContext :获取寄存器的信息,具体获取那个,要设置标志位,标志位在注释中(MSDN查不到) GetThreadSelectorEntry :获取指定选择器和线程的描述的入口表. IsDebuggerPresent :判断进程是否在调试器下面运行(和我们前面说的反调试那个差不多,都是判断调试是否运行) OutPutDebugString :调试输出字符串. ReadProcessMemory :读取指定进程的某块数据 SetThreadContext :设置寄存器 WaitForDebugEvent :等待调试事件 WriteProcessMemory :往指定进程写某块数据 */
三丶查阅MSDN,看下调试器的说明
我们要调试一个程序,第一步就要创建这个程序,那么创建程序是用CreateProcess,那么我们看下MSDN有没有特别说明
随便进去一个调试的API,看到下方说了一个基本的调试,点击去看看.
第一个说,关于基本调试,也就是介绍一下
第二个说,调试的参考,我们先看下第一个怎么说把
可以看到,他告诉了我们,关于基本调试个各种步骤.
第一个: 说的是,调试的函数,也就是上面的我们那些调试API
第二个: 说的是,调试的时候,进程,线程,和异常函数的各种特性,也就是说调试进程,线程,还有异常的时候,该怎么做.
第三个: 告诉了我们使用基本的调试函数可以创建一个基本的调试器.这些函数,可以下断点,异常等等.
第四个: 这个则是告诉了我们,调试程序的时候来的各种事件.
看下第二个把,调试的时候的各种特性.
说的是,进程函数,线程函数,异常函数,我们现在首要任务是调试进程,所以看第一个
告诉了我们,要调试一个进程,你要用CreateProcess,并且要设置标志为上面画框框的地方.
下面还说了,我们要还可以通过OpenPeocess获得一个进程ID,通过DebugActiveProcess附加这个进程调试.
那么我们现在知道了,要调试一个程序,首先要创建进程,并且给出特定的参数.
四丶编写调试器的基本框架(汇编编写,C/C++一样编写)
我们知道了,要调试一个程序,要先创建进程,然后我们应该要等待事件的到来,进行处理,如果是否继续处理,交给
ContinueDebugEvent
开始编写程序:
RadAsm创建控制台程序
汇编代码
1.创建调试进程
LOCAL @si:STARTUPINFO LOCAL @di:PROCESS_INFORMATION mov @si.cb,sizeof STARTUPINFO ;创建调试进程 invoke CreateProcess,offset g_szAppName,NULL,NULL,NULL,FALSE,DEBUG_PROCESS,NULL,NULL,addr @si,addr @di
CTRL + D 调试,看下计算器是否启动.
2.调用调试函数,等待事件到来.
注重第一个参数,说了一个DEBUG_EVENT,看下这个结构体
typedef struct _DEBUG_EVENT { DWORD dwDebugEventCode; DWORD dwProcessId; DWORD dwThreadId; 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;
说了异常来的时候,这个结构体会有异常的代码,进程的ID,线程的ID,以及根据不同异常,产生不同的结构体
(因为是共用体,所以什么异常来了,就会有不同的结构体,保存了不同的异常信息)
举个例子,第一个:
异常信息是EXCEPTION_DEBUG_EVENT 表示调试的时候异常信息事件来了,我们看下它的结构体是什么
typedef struct _EXCEPTION_DEBUG_INFO { EXCEPTION_RECORD ExceptionRecord; DWORD dwFirstChance; } EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
它的结构体则保存了和异常信息,什么的,(和筛选器异常的结构体差不多.)
上面一个框是一个参数,下面说了,只有线程,被创建调试进程的时候才能用,也就是创建调试进程使用.
下面有例子,抄例子
我就不截图看了.
直接编写汇编代码:
assume ecx:ptr DEBUG_EVENT ;EXCEPTION_DEBUG_EVENT 代表我不处理,DBG_CONTINUE代表我处理,这就是OD的F9运行起来的功能 .while TRUE invoke WaitForDebugEvent,addr @DebugEv,INFINITE ;等待事件到来 lea ecx,@DebugEv ;结果放到ecx当中,因为ecx已经假设为DebugEvent结构体了 mov ebx,[ecx].dwDebugEventCode ;将结构体的异常代码给ebx,然后下方通过ebx判断是哪个事件来了 .if ebx == EXCEPTION_DEBUG_EVENT ;检测事件是什么 invoke crt_puts,offset g_szEcpt mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == CREATE_THREAD_DEBUG_EVENT invoke crt_puts,offset g_szEcpt mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == CREATE_PROCESS_DEBUG_EVENT invoke crt_puts,offset g_szEcpt mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == EXIT_THREAD_DEBUG_EVENT invoke crt_puts,offset g_szEcpt mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == EXIT_PROCESS_DEBUG_EVENT mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == LOAD_DLL_DEBUG_EVENT mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == UNLOAD_DLL_DEBUG_EVENT mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .elseif ebx == OUTPUT_DEBUG_STRING_EVENT mov @dwContinueStatus,EXCEPTION_DEBUG_EVENT .endif invoke ContinueDebugEvent,[ecx].dwProcessId, [ecx].dwThreadId, ;继续处理事件 @dwContinueStatus .endw
代码很简单,利用waitforDebugEvent获取异常代码,然后异常代码给ebx,通过ebx模拟switch,看看那个异常事件回来.当然,汇编代码会放到课堂资料中,带着C代码一起发布,这里只是简单解释一下.
五丶异常事件是什么
上面说了,异常事件和ebx(异常代码比较)那么分别代表什么意思?
EXCEPTION_DEBUG_EVENT :被调试的调试程序的时候来,会在调试的程序中下一个int3断点.如果被调试的时候,则回来,属于系统断点
CREATE_THREAD_DEBUG_EVENT :被调试的程序创建线程的时候会来
CREATE_PROCESS_DEBUG_EVENT :被调试的程序创建进程的时候会来
EXIT_THREAD_DEBUG_EVENT :被调试的程序退出线程的时候会来
EXIT_PROCESS_DEBUG_EVENT :被调试的程序退出进程的时候会来
LOAD_DLL_DEBUG_EVENT :被调试的程序加载DLL的时候会来
UNLOAD_DLL_DEBUG_EVENT :被调试的程序卸载DLL会来
OUTPUT_DEBUG_STRING_EVENT :被调试的程序调试输出的时候会来
RIP_EVENT :64位系统的事件,如果编写32位调试器,这个则不重要.
今天主要是讲了一个框架,具体可以回去自己写一下就明白,很简单.
课堂资料:
链接:http://pan.baidu.com/s/1jHDJOUU 密码:mbn4
作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)