首页
社区
课程
招聘
[更新完毕]调试器实现(1、11、19、29、34、45、50楼更新)(50楼已上传源码)
发表于: 2010-4-27 23:59 217049

[更新完毕]调试器实现(1、11、19、29、34、45、50楼更新)(50楼已上传源码)

2010-4-27 23:59
217049
收藏
免费 8
支持
分享
最新回复 (181)
雪    币: 80
活跃值: (451)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
大牛的调试器  我来顶沙发!
2010-4-28 00:00
0
雪    币: 388
活跃值: (707)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
支持一下孙年忠。。。
2010-4-28 00:00
0
雪    币: 540
活跃值: (338)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
顶咯。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
2010-4-28 00:00
0
雪    币: 9
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
超然总是很执着,研究精神非常值得学习,写的东西总是有很多自己的思想,一直在膜拜!
2010-4-28 00:06
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
大牛,科锐的大牛。膜拜下。
2010-4-28 00:19
0
雪    币: 422
活跃值: (115)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7
昨天晚上忘记传 可执行文件 上来了,现在补上。
2010-4-28 11:17
0
雪    币: 260
活跃值: (47)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
收藏下!不定什么时候有用到。
2010-4-28 12:35
0
雪    币: 200
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
顶猴哥的作品 ~~  哦也 明早请我吃饭
2010-4-29 21:59
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
嘿嘿,看着有难度..不过顶吖..
2010-4-29 22:35
0
雪    币: 422
活跃值: (115)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
11
一个调试器的实现
第二章 INT3断点

     INT3断点,简单地说就是将你要断下的指令地址处的第一个字节设置为0xCC,软件执行到0xCC(对应汇编指令INT3)时,会触发异常代码为EXCEPTION_BREAKPOINT的异常。这样我们的调试程序就能够接收到这个异常,然后进行相应的处理。

INT3断点的管理:

在我的程序中,INT3断点的信息结构体如下:
struct stuPointInfo
{
PointType ptType; //断点类型
int nPtNum; //断点序号
LPVOID lpPointAddr; //断点地址
BOOL isOnlyOne; //是否一次性断点(针对INT3断点)
char chOldByte; //原先的字节(针对INT3断点)
};
而每一个INT3断点信息结构体指针又保存到一个链表中。

INT3断点的设置:

     设置INT3断点比较简单,只需要根据用户输入的断点地址和断点类型(是否一次性断点),将被调试进程中对应地址处的字节替换为0xCC,同时将原来的字节保存到INT3断点信息结构体中,并将该结构体的指针加入到断点链表中。

     一款好的软件,无论大小,必然在程序的逻辑上要求严谨无误。调试器的设计也不例外,如果在被调试的某地址处已经存在一个同样的断点了,那么用户还要往这个地址上设置相同的断点,则必然会因为重复设置断点导致错误。例如这里的INT3断点,如果不对用户输入的地址进行是否重复的检查,而让用户在同一个地址先后下了两次INT3断点,则后一次INT3断点会误以为这里本来的字节就是0xCC。所以在设置INT3断点之前应该先看该地址是否已经下过INT3断点,如果该地址已经存在一个INT3断点,且是非一次性的,则不能再在此地址下INT3断点,如果该地址有一个INT3一次性断点,而用户要继续下一个INT3非一次性断点,则将原来存在的INT3断点的属性从一次性改为非一次性断点。

以下是设置INT3断点的一些关键代码:
//在断点列表中查找是否已经存在此处的一般断点
stuPointInfo tempPointInfo;
stuPointInfo* pResultPointInfo = NULL;
memset(&tempPointInfo, 0, sizeof(stuPointInfo));
tempPointInfo.lpPointAddr = lpAddr;
tempPointInfo.ptType = ORD_POINT;
//判断所下的INT3断点是否是一次性断点
if (stricmp(pCmd->chParam2, "once") == 0)
{
tempPointInfo.isOnlyOne = TRUE;
}
else
{
tempPointInfo.isOnlyOne = FALSE;
}
//如果查找到在要设置INT3断点的地址处已经存在INT3断点
if (FindPointInList(tempPointInfo, &pResultPointInfo, FALSE))
{
if (tempPointInfo.isOnlyOne == FALSE)//要设置的是非一次性断点
{
if (pResultPointInfo->isOnlyOne == FALSE)//查找到的是非一次性断点
{
printf("This Ordinary BreakPoint is already exist!\r\n");
}
else//查找到的是一次性断点
{
//将查找到的断点属性改为非一次性断点
pResultPointInfo->isOnlyOne = FALSE;
}
}
return FALSE;
}

//要设置INT3断点的位置不存在INT3断点(也就是说,该地址可以设置INT3断点)
char chOld;
char chCC = 0xcc;
DWORD dwOldProtect;
//先读出原先的字节
bRet = ReadProcessMemory(m_hProcess, lpAddr, &chOld, 1, NULL);
if (bRet == FALSE)
{
printf("ReadProcessMemory error! may be is not a valid memory address!\r\n");
return FALSE;
}
//将要设置INT3断点的地址处的字节改为0xCC
bRet = WriteProcessMemory(m_hProcess, lpAddr, &chCC, 1, NULL);
if (bRet == FALSE)
{
printf("WriteProcessMemory error!\r\n");
return FALSE;
}
//将该INT3断点信息结构体添加到断点链表中
stuPointInfo* NewPointInfo = new stuPointInfo;
memset(NewPointInfo, 0, sizeof(stuPointInfo));
NewPointInfo->nPtNum = m_nOrdPtFlag;
m_nOrdPtFlag++;
NewPointInfo->ptType = ORD_POINT;
NewPointInfo->lpPointAddr = lpAddr;
NewPointInfo->u.chOldByte = chOld;
NewPointInfo->isOnlyOne = tempPointInfo.isOnlyOne;
g_ptList.push_back(NewPointInfo);

INT3断点被断下的处理:
     INT3断点被断下后,首先从断点链表中找到对应的断点信息结构体。如果没有找到,则说明该INT3断点不是用户下的断点,调试器不做处理,交给系统去处理(其他类型的断点触发异常也需要做同样的处理)。如果找到对应的断点,根据断点信息将断下地址处的字节还原为原来的字节,并将被调试进程的EIP减一,因为INT3异常被断下后,被调试进程的EIP已经指向了INT3指令后的下一条指令,所以为了让被调试进程执行本来需要执行的指令,应该让其EIP减1。

如以下代码:
地址 机器码 汇编代码
01001959 55 push ebp
0100195A 33ED xor ebp,ebp
0100195C 3BCD cmp ecx,ebp
0100195E 896C24 04 mov dword ptr ss:[esp+4],ebp

当用户在0100195A地址处设置INT3断点后,0100195A处的字节将改变为0xCC(原先是0x33)。

此时对应的代码如下:
地址 机器码 汇编代码
01001959 55 push ebp
0100195A CC int3
0100195B ED in eax,dx
0100195C 3BCD cmp ecx,ebp
0100195E 896C24 04 mov dword ptr ss:[esp+4],ebp

     当被调试程序执行到0100195A地址处,触发异常,进入异常处理程序后,获取被调试线程的环境(GetThreadContext),可看出此时EIP指向了0100195B,也就是INT3指令之后,所以我们除了要恢复0xCC为原来的字节之外,还要将被调试线程的EIP减一,让EIP指向0100195A。否则CPU就会执行0100195B处的指令(0100195B ED in eax,dx),显然这是错误的。

     如果查找到的断点信息显示该INT3断点是一个非一次性断点,那么需要设置单步,然后在进入单步后将这一个断点重新设置上(硬件执行断点和内存断点如果是非一次性的也需要做相同的处理)。因为INT3断点同时只会断下一个,所以可以用一个临时变量保存要重新设置的INT3断点的地址,然后用一个BOOL变量表示当前是否有需要重新设置的INT3断点。

关于INT3断点的一些细节:

1. 创建调试进程后,为了能够让被调试程序断在OEP(程序入口点),我们可以在被调试程序的OEP处下一个一次性INT3断点。

2. 在创建调试进程的过程中(程序还没有执行到OEP处),会触发一个ntdll.dll中的INT3,遇到这个断点直接跳出不处理。这个断点在使用微软自己的调试工具WinDbg时会被断下,可以猜测,微软设置这个断点是为了能够在程序到达OEP之前就被断下,方便用户做一些处理(如设置各种断点)。

3. 因为INT3断点修改了被调试程序的代码内容,所以在进行反汇编和显示被调试进程内存数据的时候,需要检查碰到的0xCC字节是否是用户所下的INT3断点,如果是需要替换为原来的字节,再做相应的反汇编和显示数据工作。这一点olldbg做的很不错,而有一些国产的调试器好像没有注意到这些小的细节。

本系列文章参考书目、资料如下:
1.《加密与解密3》 编著:段钢
2.《调试寄存器(DRx)理论与实践》 作者:Hume/冷雨飘心
3.《数据结构》 作者:严蔚敏
2010-4-29 23:19
0
雪    币: 388
活跃值: (707)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
12
支持一下老孙......
2010-4-29 23:20
0
雪    币: 15
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
都这么强,,后悔啊~~~
2010-4-29 23:20
0
雪    币: 388
活跃值: (707)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
14
上尚,回来吧,我们都想你了!
2010-4-29 23:22
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
强悍。
2010-4-29 23:23
0
雪    币: 9
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
刚刚看完,确实考虑的确实周详!
随便还看到强哥被"一些"命中了。
O(∩_∩)O哈哈~
2010-4-29 23:48
0
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
17
为了方便,将主题合并。
谢谢各位愿意与大家分享心得!
2010-4-30 15:03
0
雪    币: 433
活跃值: (1870)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
18
这段时间不少朋友在写调试器啊,乘机学习一下。
2010-4-30 15:43
0
雪    币: 422
活跃值: (115)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
19
第三章 硬件断点

一 硬件断点介绍

     硬件断点,顾名思义是由硬件提供给我们的调试寄存器组,我们可以对这些硬件寄存器设置相应的值,然后让硬件帮我们断在需要下断点的地址。

     硬件断点是CPU提供的功能,所以要怎么做就得听CPU的硬件寄存器的了。先来看看关于硬件寄存器的说明。Intel 80386以上的CPU提供了调试寄存器以用于软件调试。386和486拥有6个(另外两个保留)调试寄存器:Dr0,Dr1,Dr2,Dr3,Dr6和Dr7。这些寄存器均是32位的,如下图所示(该图来源于看雪文章《调试寄存器(DRx)理论与实践》(http://www.pediy.com/bbshtml/BBS6/pediy6751.htm),在此对文章作者Hume/冷雨飘心表示感谢):

|---------------|----------------|
Dr0| 用于一般断点的线性地址
|---------------|----------------|
Dr1| 用于一般断点的线性地址
|---------------|----------------|
Dr2| 用于一般断点的线性地址
|---------------|----------------|
Dr3| 用于一般断点的线性地址
|---------------|----------------|
Dr4| 保留
|---------------|----------------|
Dr5| 保留
|---------------|----------------|
Dr6| |BBB BBB B |
| |TSD 3 2 1 0 |
|---------------|----------------|
Dr7|RWE LEN ... RWE LEN | G GLGLGLGLGL |
| 3 3 ... 0 0 | D E E 3 3 2 21 100 |
|---------------|----------------|
31 15 0

     Dr0~3用于设置硬件断点,由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点,产生的异常是STATUS_SINGLE_STEP(单步异常) 。Dr7是一些控制位,用于控制断点的方式,Dr6用于显示是哪个硬件调试寄存器引起的断点,如果是Dr0~3或单步(EFLAGS的TF)的话,则相对应的位会置一。
即如果是Dr0引起的断点,则Dr6的第0位被置1,如果是Dr1引起的断点,则Dr6的第1位被置1,依此类推。因为硬件断点同时只会触发一个,所以Dr6的低4位最多只有一个位被置1,所以在进入单步后,我们可以通过检测Dr6的低4位是否有值为1的位,来判断当前进入单步的原因是否是因为硬件断点被断下。如果是因为硬件断点被断下,也可以通过判断Dr6的低4位中哪一位是1,来进一步判断是被Dr0到dr3中的哪一个断点所断下。

调试控制寄存器Dr7比较重要,其32位结构如下:

1. 位0 L0和位1 G0:用于控制Dr0是全局断点还是局部断点,如果G0置位则是全局断点,L0置位则是局部断点。G1L1~G3L3用于控制D1~Dr3,其功能同上。

2. LE和GE:P6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。当其中一个被设置的时候,处理器会放慢执行速度,这样当命令执行的时候可以通知这些数据断点。建议在设置数据断点时需要设置其中一个。切换任务时LE会被清除而GE不会被清除。为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。

3. LEN0到LEN3:指定调试地址寄存器DR0到DR3对应断点所下断的长度。如果R/Wx位为0(表示执行断点),则LENx位也必须为0(表示1字节),否则会产生不确定的行为。LEN0到LEN3其可能的取值如下:
(1)00 1字节
(2)01 2字节
(3)10 保留
(4)11 4字节

4. R/W0到R/W3:指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。可能的取值如下:
(1) 00 只执行
(2) 01 写入数据断点
(3) 10 I/O端口断点(只用于pentium+,需设置CR4的DE位,DE是CR4的第3位 )
(4) 11 读或写数据断点

5. GD位:用于保护DRx,如果GD位为1,则对Drx的任何访问都会导致进入1号调试陷阱(int 1)。即IDT的对应入口,这样可以保证调试器在必要的时候完全控制Drx。

二 设置硬件断点

     通过上面的介绍,我们知道设置一个硬件断点一般只需要以下几个步骤。
(1) 在Dr0到Dr3中找一个可用的寄存器,将其值填写为要断下的地址。
(2) 设置Dr7对应的GX或LX位为1。(例如断点设置在Dr0上则设置Dr7的G0或L0位为1)。
(3) 设置Dr7对应的断点类型位(R/W0到R/W3其中之一)为执行、写入或访问类型。
(4) 设置Dr7对应的断点长度位(LEN0到LEN3其中之一)为1、2或4字节。

三 处理硬件断点

     在硬件断点的介绍中已经说过,硬件断点被断下后将触发单步异常,因此在进入单步异常后,我们检测Dr6的低4位是否有值为1的位,就可以判断是否是因为硬件断点被断下,以及进一步判断是被Dr0到Dr3中的哪一个断点所断下。
硬件断点有三种类型,硬件执行断点、硬件访问断点和硬件写入断点。硬件断点被断下后,所断下的位置(也就是程序的EIP值)会因为断点的类型不同而有差异。对于硬件执行断点,会断在所下断点地址指令处,也就是EIP的值和断点设置的值一样,下断点的指令还没有被执行。而对于硬件访问、写入断点,会断在所下断点地址指令的下一条指令处,也就是EIP的值已经是断点指令后的下一条指令的地址了,下断点地址处的指令已经被执行了。

     究其原因是因为硬件执行断点只需要查看EIP的值就可以判断是否命中硬件执行断点了,所以在指令执行前就可以断下,而硬件访问、写入断点是需要在CPU拿到完整指令代码并译码完毕之后才能判断是否命中了硬件访问、写入断点的。此时EIP已经指向了下一条指令,又因为intel的cpu指令是变长指令集,所以不易倒推实际触发硬件访问、写入断点的指令地址,所以intel对硬件访问、写入断点的处理是停在触发异常指令后的下一条指令处(这一段是我本人的理解,如有不对之处,请读者多多指教)。
由于不同类型的硬件断点触发异常的情况不同,所以要区别对待。对于硬件执行断点,触发异常断点被断下后,要先暂时取消硬件执行断点,然后设置单步,到下一次的单步中进行硬件断点的恢复工作。对于硬件访问、写入断点则不需要做多余的处理,断下后显示一下断点信息,并等待用户操作就可以了。

     因为硬件断点的设计比较死板,照着intel手册的说明一步步来就可以了。对Dr7的操作也就是一些位操作。我的代码里面是一个大大的SWITCH里面套了4个小SWITCH来做的,显得拖堂冗长、很不好看,所以就不放上来了。

硬件断点设计需要注意的几点如下:

1. 设置硬件断点的时候也要检查是否重复设置了。

2. 硬件执行断点被断下后,此时需要暂时取消掉该硬件执行断点(否则程序一直被断在这里,跑不下去)。并设置单步,在下一次单步中重设该硬件执行断点。

3. 如果硬件执行断点被断下之后,此时用户执行了取消该断点的操作,则不需要在下一次的单步中恢复该断点的设置了(这一点同样适用于INT3断点和内存断点)。

本系列文章参考书目、资料如下:
1.《加密与解密3》 编著:段钢
2.《调试寄存器(DRx)理论与实践》 作者:Hume/冷雨飘心
3.《数据结构》 作者:严蔚敏
上传的附件:
  • 1.JPG (10.50kb,2708次下载)
2010-5-1 22:08
0
雪    币: 104
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
小菜 看不懂中。。
2010-5-1 22:11
0
雪    币: 210
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
进来试一下新东西
2010-5-2 01:02
0
雪    币: 21
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
膜拜。。。
2010-5-2 09:41
0
雪    币: 15
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
顶一下老孙
2010-5-2 11:04
0
雪    币: 32
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
支持一下强悍的作品
2010-5-2 13:16
0
雪    币: 88
活跃值: (47)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
25
我也来顶~~~
2010-5-2 13:18
0
游客
登录 | 注册 方可回帖
返回
//