调试器编写第一讲,调试器基本框架

                  调试器编写第一讲,调试器基本框架


作者: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/
版权所有,欢迎保留原文链接进行转载:)

赞 (0) 评论 分享 ()