首页
社区
课程
招聘
[讨论]Make a debugger
发表于: 2008-2-10 19:34 13817

[讨论]Make a debugger

zhuwg 活跃值
11
2008-2-10 19:34
13817
Isaiah大牛提及了1下,能不能自己构造一套调试的框架呢?不依赖于操作系统的提供的机制。比如调试事件。
于是偶发1个讨论帖子,看看有无大牛对这些东西感兴趣,指点1下

我们的目的.一些加壳软件使用自调试,比如arm的copymem2,我们不能方便的调试子进程,因为调试端口已经被占用,OllyDbg不能attach,我们希望实现1个自己的调试器注入进去,实现windbg类似的入侵调试功能,我的想法的制作1个开源debuger计划,欢迎大家帮助开发完善这个小工具,这个小工具并不打算替代OllyDbg,仅仅是在某些OllyDbg一时无法使用的时候作为临时debuger使用

SoftICE是完全自己实现了调试过程
OllyDbg则是利用Platform SDK和最新的DbgHelp.dll提供的API作为引擎写的debugger,也即是的debugger的Host端
其实CPU为Debug提供了强力的支持,比如设单步跟踪的标志,还有Drx机制
我们来看看
OllyDbg使用了下面的一系列API
================
        GetThreadContext
        SetThreadContext
        DebugActiveProcess
        ReadProcessMemory
        WriteProcessMemory
        CreateProcess with DEBUG_PROCESS flag
        WaitForDebugEvent
================
       其中一些API不是调试特有的,我们可以继续使用
我们需要修正的是DebugActiveProcess和WaitForDebugEvent
简单起见,我们只模拟attach过程
       一个调试器,当DebugActiveProcess上一个进程以后,被调试进程主线程被挂起,这种状况将持续到我们的程序调用WaitForDebugEvent为止

背景知识: user-mode debuger工作流程
<1>debuger创建一个新进程,或attach一个正在运行的进程。我们称这个进程为B。
<2>debuger等待进程B产生debug事件
<3>进程B产生debug事件,发送消息给debuger,进程挂起,等待debuger指令。
<3>debuger处理debug事件,发送消息给进程B。
<4>进程B接受debuger发送的消息,进程复苏。
<5>循环2-4
消息传递是通过lpc port来进行的,流程如下所示:
debuger <--> kernel <--> process B

上面所说的消息结构如下:
typedef struct _DEBUG_MESSAGE
{
PORT_MESSAGE PORT_MSG;
DEBUG_EVENT DebugEvent;
}DEBUG_MESSAGE, *PDEBUG_MESSAGE;

typedef struct _PORT_MESSAGE
{
USHORT DataSize;//数据长度
USHORT MessageSize;//总长度
USHORT MessageType;
USHORT DataInfoOffset;
CLIENT_ID ClientId;
ULONG MessageId;
ULONG SectionSize;
//UCHAR Data[];
}PORT_MESSAGE, *PPORT_MESSAGE;

在\Microsoft SDK\samples\winbase\Debug目录下有几个简单的user-mode debuger的源代码,
大家可以参考一下。


   我们不做进一步深入,只说说我们需要的东西,那就是DEBUG_EVENT
   OllyDbg关键部分是对于DEBUG_EVENT的处理

背景知识:
EXCEPTION_DEBUG_EVENT:产生调试例外
CRATE_THREAD_DEBUG_EVENT:新的线程产生
CREATE_PROCESS_DEBUG_EVENT:新的进程产生。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS时如果该程序启动了子进程就可能有多次。
EXIT_THREAD_DEBUG_EVENT:一个线程运行中止
EXIT_PROCESS_DEBUG_EVENT:一个进程中止。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS可能有多次。
LOAD_DLL_DEBUG_EVENT:一个DLL模块被载入。
UNLOAD_DLL_DEBUG_EVENT:一个DLL模块被卸载。

本讨论帖子不求完美,尽可能多保存1点相关undocu资料

还是简单起先,我们只处理EXCEPTION_DEBUG_EVENT事件
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} 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, *PEXCEPTION_RECORD;

产生中断的地址ExceptionAddress和产生中断的信息代码ExceptionCode
这2个是我们必须模拟的,继续简单化处理,我们只管2个
EXCEPTION_BREAKPOINT:断点中断信息代码
EXCEPTION_SINGLE_STEP:单步中断信息代码

我们现在来看看中断,中断,我们先说int3,最简单,向指定地址写入CC
我们需要接管异常处理流程,那么我们的入侵调试器需要有1个dll
用于注入被调试进程,用来接管线程异常,从而我们能够处理异常

操作系统为每个线程分配一个TEB结构的数据块,并用FS指向它:
NT_TIB STRUCT
+0 ExceptionList dd ?
StackBase dd ?
StackLimit dd ?
SubSystemTib dd ?
union
FiberData dd ?
Version dd ?
ends
ArbitraryUserPointer dd ?
Self dd ?
NT_TIB ENDS

EXCEPTION_REGISTRATION_RECORD struct
+0 prev dd ?
+4 handler dd ?
EXCEPTION_REGISTRATION_RECORD ends

FS指向NT_TIB结构,FS:[0]就指向了EXCEPTION_REGISTRATION_RECORD结构

EXCEPTION_RECORD结构包含了发生异常的详细信息,这些信息独立于CPU
EXCEPTION_RECORD STRUCT
+0 ExceptionCode DWORD ? ;异常发生的原因代码
+4 ExceptionFlags DWORD ? ;异常处理操作标志
+8 pExceptionRecord DWORD ?
+0Ch ExceptionAddress DWORD ? ;异常发生的地址
+10h NumberParameters DWORD ?
+14h ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORD ENDS

异常发生时,操作系统向引起异常的线程的堆栈压入3个结构:
EXCEPTION_RECORD,CONTEXT,EXCEPTION_POINTERS

EXCEPTION_POINTERS STRUCT
+0 pExceptionRecord DWORD ?
+4 ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS

即是说:EXCEPTION_POINTERS包含指向EXCEPTION_RECORD,CONTEXT这两个结构的指针

CONTEXT结构包含了特定处理器的寄存器数据
CONTEXT STRUCT
ContextFlags DWORD ?
//调试寄存器
+4 iDr0 DWORD ?
+8 iDr1 DWORD ?
+0C iDr2 DWORD ?
+10 iDr3 DWORD ?
+14 iDr6 DWORD ?
+18 iDr7 DWORD ?
//浮点寄存器
FloatSave FLOATING_SAVE_AREA <>
//段寄存器
+8C regGs DWORD ?
+90 regFs DWORD ?
+94 regEs DWORD ?
+98 regDs DWORD ?
//通用寄存器
+9C regEdi DWORD ?
+A0 regEsi DWORD ?
+A4 regEbx DWORD ?
+A8 regEdx DWORD ?
+AC regEcx DWORD ?
+B0 regEax DWORD ?
//控制寄存器
+B4 regEbp DWORD ?
+B8 regEip DWORD ?
+BC regCs DWORD ?
+C0 regFlag DWORD ?
+C4 regEsp DWORD ?
+C8 regSs DWORD ?
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS

  我们最好直接更改线程的异常处理链,把我们的处理过程放在第一个位置
当然我们可以使用SetUnhandledExceptionFilter,但是那样我们的异常处理过程
被安排到了最后,但是作为做实验,使用SetUnhandledExceptionFilter可以
方便的安装处理过程

整理1下我们一共需要的工作--最简单的
1.1个dll,用来注入被调试进程,安装ExceptionFilter
2.主debuger,和dll通讯,接受异常分析并且处理
3.主exe,完成以下api工作,他们不是调试特有api,可以工作
        GetThreadContext
        SetThreadContext
        ReadProcessMemory
        WriteProcessMemory
4.关键功能,异常处理部分,偶打算完成int3,drx,内存断点
这里需要感谢大牛们对OllyDbg各种功能的完整分析
内存断点其实就是改变内存页属性,使之发生异常
从本质上来说,断点就是发生异常
5.界面设计,我的想法还是参照OllyDbg,一个反汇编窗口+1个寄存器窗口+1个内存查看窗口
其实堆栈查看和内存查看窗口差不多,不过是自动定位esp的地址显示的那片内存
不过偶基本不会做界面,汗..
代码部分偶还没有来得及写,其实主要是不太会写
希望大牛参与帮助 哈哈

欢迎大家参与讨论 哈哈

贴个测试代码
1.异常程序
// dbgtest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

int main(int argc, char* argv[])
{

        MessageBox(0,"int3","dbgtest",MB_OK);
        _asm int 3
        MessageBox(0,"ok","dbgtest",MB_OK);

        return 0;
}
这个程序直接运行是会异常的,
我们需要注入1个DbgDll,执行如下过程
DWORD WINAPI InjectDbgDll(IN LPINJECT_DATA lpData)
{

        SetUnhandledExceptionFilter(UnhandledExceptionFilter);
   
    return 0;
}

LONG WINAPI UnhandledExceptionFilter(IN struct _EXCEPTION_POINTERS* ExceptionInfo)
{
    return -1;
}
什么也不处理,直接返回,这样这个程序就可以处理异常.运行下去了
不过这个模式不适合int3断点,int3断点需要还原代码,eip-1
但是内存异常和drx这样处理应该可以

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 7
支持
分享
最新回复 (23)
雪    币: 817
活跃值: (1927)
能力值: ( LV12,RANK:2670 )
在线值:
发帖
回帖
粉丝
2
继续PECANCER
2008-2-10 19:39
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
3
讨论还没有能力。
排队等着用
2008-2-10 19:42
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
4
祝楼上2位新年快乐
哈哈.2位的帖子数和精华数很精妙阿
照相留念
上传的附件:
  • 1.JPG (9.59kb,1047次下载)
  • 2.JPG (7.37kb,1048次下载)
2008-2-10 21:16
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
,等着k哥违反游戏规则.
2008-2-11 12:33
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
6
用 SetUnhandledExceptionFilter不好。
如果被调试代码的使用了SEH,并且是捕获所有异常并退出的话。所有工作都白费了。
Debugeer要保证做沙发才行。XP的VEH可能好些。或者在ExceptionDispatch上做手脚。
2008-2-12 03:50
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
看明白了,就是想在被调试程序中自己截获int3的中断,这样就不会给调试器机会了。当然,要让被调试程序有自己截获int3中断的能力,就要通过inject了。其实,确实有一个这样的软件是使用这种方法的。但不是通过SetUnhandledExceptionFilter这种方法的,如LS说的,这种方法根本不保险,要做就做沙发才行。我看过一个软件BugTrapper就是用这种方法的。不过BugTrapper只能在9X和2000下运行,因为在9X下用的是驱动实现,在2000下用的是修改执行代码的方式,我怀疑由于2000下用的修改执行代码在XP上没有对应好修改的代码(那时候XP还没有出来),所有不能用了。关于驱动我没有研究过,下面就说说在2000下的实现机制,大家可以扩展到XP以后的了。
2008-2-12 08:31
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
BugTrapper是用来监视函数调用的一个工具,我自己就根据这个创意,以另外一种方法实现了监视函数调用的功能(题外话 )。下面就将我很久以前调查的东西share一下,希望对大家有点帮助。

在很早以前就知道,BugTrapper在要监视的函数的入口第一个BYTE上写上0xCC的指令。这就是让程序产生一个中断调试例外!
因为0xCC(int 3)产生的函数中断,又如何将中断恢复呢?而且,根本就没有调试程序在调试你的程序。BugTrapper又是如何得到的呢?原来,BugTrapper在监视程序时,也采用了DLL的介入方案,接受中断的程序并不是BugTrapper,而是介入的DLL接收了由于修改函数入口而产生的中断例外。

在BugTrapper的介入DLL中,将修改Windows的例外处理接口,使得程序产生例外时,首先跳到Dll的处理函数中,如果该例外的地址为BugTrapper监视的函数入口,就将函数入口的原来指令写回该处,并且,修改例外的CONTEXT参数,使得程序执行时产生单步执行的中断例外。这样,在单步中断的处理中,将该函数的入口指令再次改为0xCC,用来下一次的函数被调用时再次产生中断。

修改例外处理的方法是修改KiUserExceptionDispatcher的处理,将自己的函数地址写在KiUserExceptionDispatcher函数的入口,这样,产生例外时,系统内核将调用该函数,就可以转入自己的例外处理函数了,在自己的例外处理函数中,需保存原来函数的内容,并且在最后运行这些内容,最后跳到原函数中(注意:不是入口,因为入口的内容已经被修改)这种方法在微软的DETOURS包中就进行了详细的介绍。。。。

首先,接入NT的NTDLL.DLL,接管KiUserExceptionDispatcher函数的处理(用微软的Detour技术。将第一个处理例外的接口为自己的函数。)这样,就可以监视由int 3引起的调试中断。当产生中断时,判断是不是自己监视函数产生的中断,如果是,就进行处理!同时进行单步运行,在单步中断时重新设置断点。

我自己用这种方法进行过简单的验证,事实证明确实可行,但要作为一个程序,里面就有很多要考虑的东西了。由于接管了KiUserExceptionDispatcher函数,所以可以保证坐在沙发上了。(5-6年前的调查,现在不知道是不是过时了
2008-2-12 08:42
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
好像搞错了。我突然想起来,好像这个程序被调试后,bugtrapper就接不到中断了。要真正解决这个问题,可能还是要从驱动入手。
另外,SetUnhandledExceptionFilter等这种方法根本上是不行的,因为一个程序被调试时,最先得到消息的还是调试程序,只有调试程序处理完了,才会通知其他的处理程序。debugger坐在沙发上是没有办法改变的。关于调试的调用路径,我记得有本书讲的比较清楚,好像叫<Windows NT/2000 本机 API 参考手册>,里面讲到驱动中如何派发debug消息的,估计在驱动中入手就可以搞定吧。
2008-2-12 08:57
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
10
驱动里面也有一个ExceptionDispatch

很多改版的CE能直接调试使用NP或者HS保护的游戏。主要是自己实现了跨进程的相关函数。开源的。
2008-2-12 15:07
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
11
LS的LS说的没错,在没有调试器接入的时候,ntdll中的KiUserExceptionDispatcher是第一个接管异常的函数,对于这个函数打patch可以实现BugTrapper一样的功能
LS说的 改版的CE能直接调试使用NP或者HS保护的游戏  偶猜测是类似windbg的内核模式调试,
直接切换到NP进程,然后开始调试.是可以的,具体的没有仔细分析,也许和内核调试器机制有关系吧
2008-2-12 16:30
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
12
If the previous mode was user, the following steps are taken:
• If frame-based exception-handling is allowed (SearchFrames == TRUE) and if the
process is not being debugged by a user mode debugger (DebugPort == 0), the
kernel debugger is given a first chance to handle the exception; otherwise, a
description of the exception is forwarded to the user mode debugger via the LPC
mechanism.
• If the exception is not handled by a debugger and the user mode stack appears to
be still valid, the user mode context is adjusted so that upon return to user mode,
the function KiUserExceptionDispatcher will be invoked.
• After returning to user mode, KiUserExceptionDispatcher invokes
RtlDispatchException to search for a frame-based exception handler.
• If RtlDispatchException does not find a handler prepared to handle the exception,
the exception is re-signaled, specifying SearchFrames as FALSE.
• KiDispatchException is entered again and, because SearchFrames is FALSE, the
next step is to give a user mode debugger a last chance to handle the exception.
• If the debugger (if any) still does not handle the exception, a description of the
exception is forwarded to the exception port (if any) of the process.
• The recipient (if any) of the message to the exception port can still handle the
exception, but if it does not, ZwTerminateThread is called to terminate the current
thread.
• If ZwTerminateThread fails for any reason, KeBugCheckEx is invoked to shut down
the system with the bugcheck code KMODE_EXCEPTION_NOT_HANDLED.
2008-2-12 16:43
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
13
汗.无法编辑帖子.写这里好了
从上面可以知道.,
1.kernel debugger is given a first chance
2.the function KiUserExceptionDispatcher will be invoked
3.If RtlDispatchException does not find a handler ,
4.KiDispatchException is entered again and the  next step is to give a user mode debugger

我们可以hook KiUserExceptionDispatcher ?
楼上说 好像这个程序被调试后,bugtrapper就接不到中断了  不知道.?
2008-2-12 16:49
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
重新看了一下,关键是看调试器如何处理的。比如:发现不是自己设置的断点就不处理的话,bugtrapper还是可以继续处理的,如果调试器接手处理的话,bugtrapper就不能处理了。比如VC去调试的程序,bugtrapper就不能处理了。

总的来说,调试器肯定是中断的沙发。bugtrapper的处理好像是用的类似SEH的方法。应该是坐在SEH的沙发上吧。
2008-2-12 18:38
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
15
咨询1下,,清除debug port使之=0可否不先过调试器
2008-2-13 20:25
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
可以的。 但和你的目的不符合。我的理解是你希望有调试器的情况下,可以优先坐沙发。debug port=0后,调试器就再也到不了。  如果这样的话,还不如自己写个调试器来调试目标程序好了。
2008-2-13 20:44
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
17
不错,偶是希望能够在有调试器的情况下进入调试器之前

debug port=0后,会先过来偶的处理,完成以后再用port发送给调试器?
不过看起来也需要驱动,汗..
2008-2-14 10:20
0
雪    币: 178
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
there's some mistakes in your title. i think the correct spelling is "debugger".correct it :)
2008-2-15 11:12
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
19
挂在驱动的ExpcetionDispatch上
2008-2-15 22:58
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
20
thanks..
changed
2008-2-17 17:26
0
雪    币: 193
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
在DRX中设置好断点好,收到EXCEPTION_SINGLE_STEP消息,然后处理了一下,然后被调试程序就不往下运行了呀?怎么才能让目标程序继续运行,除了把DRX设为0
2008-3-11 17:27
0
雪    币: 175
活跃值: (211)
能力值: ( LV12,RANK:330 )
在线值:
发帖
回帖
粉丝
22
好象并不能从本质上解决问题,不如逆向vmware或者钻研下intel vt,只要写出虚拟硬件调试器来肯定秒杀天上地下一切antidebug
2008-3-12 23:31
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
23
楼上对新技术很有研究啊
2008-3-13 10:07
0
雪    币: 215
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
24
在需要DLL处理的时候,让系统认为此进程没有被调试,反之则...
2008-3-18 23:19
0
游客
登录 | 注册 方可回帖
返回
//