0 0 0

科锐学习笔记-第三阶段-调试器 07

admin
174 0

本文共计10148个字,预计阅读时长40.6分钟。

怎么确定eip所处在哪个模块下? 

这个你要遍历被调试进程。最简单的遍历方式就是快照。用枚举也行。他会返回模块的首地址,还有模块的大小,你就判断eip处在哪个范围就可以了。 

内存断点 

我来说说这个内存的断点。我这节课就写一个。支持多个内存断点的。首先你们要搞清楚这里面的数据关系。 

数据关系 

以下表中的数据都是16进制的。 

img 

这前三个断点有重叠吗?没有吧、没重叠吧。但是这三个断点位于同一个分页内,或者一个分页内说它可以有多个内存断点的。 

img 

发现这个分页还还有内存断点之间的关系,它是一个多对多的关系。那这两者的关系因为是个多对多关系,所以我们就需要建三张表了、 

分页表要存:分页地址、原有内存属性。 

比如有的断点表地址可能跨两个分页、这两个分页的内存属性可能不一样。所以你要有一个分页表去记录一下这些数据。分页表和断点表这两边其实这个地址都能够拿来当主键,但是看着不是很好看。给个编号吧、我们先把这个数据设计出来,其实你虽然完整的数据设计出来,但是不一定能够这些数据这些表上都会用上。例如下图的数据 

img 

首先增加我们说有两种情况被我们给干掉了、一种就是和某个断点有重合、我们就直接干掉不让他设置、然后就是如果是断页我们也不让它设置。所以我们就不考虑重合还有缺页的情况。 

查询 

比如说我现在要加一个断点,00401800长度是32。首先我们这个东西检查一下它有没有和断点表这里面的地址断点重合。跟这个00401100重合了、那这个不行。 

00403400长度32这个不重合、可以在断点表插进去。然后在分页表中有00403000这个分页。那这个时候其实已经有断点用了这个分页,那说明这个它的内存属性已经被改掉了。那我只需要分页断点表这里加一下就行了。 

img 

然后当异常来的时候,我们需要做查询判断断点是否命中、命中命中的话,我们看查哪个表吧。比如这个时候有个地址0040210C有个访问。它会命中的去哪个地方?。查哪个表?应该去查分页表、去查这个分页表,看是不是在范围内。 

如果不是的话,那说明啥?说明压根儿不是我们的异常、所以直接就放掉。那如果说里面某个地方中了,比如说这是0040210C。这里面肯定会命中00402000。命中了这个分页表之后、去断点表中查询看这边命中了哪个断点。这个时候就命中00401100长度2000的这个范围了。这个时候断点就命中了。 

你发现这个时候其实在查的时候只用到了这个分页表,还有这个断点表,这个分页断点表用不上。 

删除 

比如说删除某个断点。删断点的时候,我们要其实是要做的是恢复原来的内存属性、那删除的这个断点去哪查呀?你需要知道它涉及到几个分页、那就是需要到分页断点表去查了。要判断相同编号有几个如果数量大于二的话,就不能在分页表改内存属性了、只需要把分页断点表和断点表把对应的断点删除即可。如果数量是1个,那就可以还原内存属性。 

我们需要把我们讨论的这些东西文档记录下来,这个到时候写代码思路比较清晰。 

设置断点: 

1、判断是否可以插入 

1) 查询断点表,是否与已设断点重叠 

2) 查询内存属性,是否有缺页 

2、插入 

1) 插入断点表 

2) 获取所涉及到的分页 

a. 查询分页表,如果存在,则直接添加到分页断点表 

b. 查询分页表,如果不存在, 修改内存属性,并添加到分页表和分页断点表 

查询是否命中: 

1、查分页表, 如果没有命中分页,则不是自己异常,放过去 

2、查分页表, 如果命中分页 

1) 查断点表,如果没有命中,断步配合,放过 

2) 查断点表,如果命中,则提示,并输入命令,断步配合重设 

删除: 

1、断点表删除 

2、获取断点所涉及的分页,对每一个分页 

1) 获取分页编号 

2) 查询分页断点表,此编号数量 

a. 如果只有一个,则从分页表和分页断点表删除,并恢复内存属性 

b. 如果有多个,则从分页断点表删除 

断点,我还得写一个输入断点的命令。将第五天的工程拷贝过来。 

img 

img 

下图这些跟具体的逻辑没啥关系的,直接丢其他的文件里面。 

img 

新建文件 

img 

img 

导入文件 

img 

img 

增加代码 

img 

tool.asm代码敬上


.586 
.model flat,stdcall 
option casemap:none 

  include windows.inc 
  include user32.inc 
  include kernel32.inc 
  include msvcrt.inc 
  include udis86.inc 

  includelib user32.lib 
  includelib kernel32.lib 
  includelib msvcrt.lib 
  includelib libudis86.lib 

.code 

IsCallMn  proc uses esi edi pDE:ptr DEBUG_EVENT, pdwCodeLen:DWORD 
   LOCAL @dwBytesOut:DWORD 
   LOCAL @dwOff:DWORD 
   LOCAL @pHex:LPSTR 
   LOCAL @pAsm:LPSTR 

   mov esi, pDE 
   assume esi:ptr DEBUG_EVENT 

       ;显示下一条即将执行的指令 
   invoke ReadProcessMemory, g_hExe, [esi].u.Exception.pExceptionRecord.ExceptionAddress, \ 
       offset g_szOutPutAsm, 20, addr @dwBytesOut 
   invoke ud_init, offset g_ud_obj 
   invoke ud_set_input_buffer, offset g_ud_obj, offset g_szOutPutAsm, 20 
   invoke ud_set_mode, offset g_ud_obj, 32 
   invoke ud_set_syntax, offset g_ud_obj, offset ud_translate_intel 
   invoke ud_set_pc, offset g_ud_obj, [esi].u.Exception.pExceptionRecord.ExceptionAddress 

   invoke ud_disassemble, offset g_ud_obj 
   invoke ud_insn_off, offset g_ud_obj 
   mov @dwOff, eax 
   invoke ud_insn_hex, offset g_ud_obj 
   mov @pHex, eax 
   invoke ud_insn_asm, offset g_ud_obj 
   mov @pAsm, eax 
   invoke ud_insn_len, offset g_ud_obj 
   mov edi, pdwCodeLen 
   mov [edi], eax 

   invoke crt_printf, offset g_szOutPutAsmFmt, @dwOff, @pHex, @pAsm 

   mov eax, @pAsm 
   .if dword ptr [eax] == 'llac' 
       mov eax, TRUE 
       ret         
   .endif 

   mov eax, FALSE 
   ret 

IsCallMn endp 

SetTF proc dwTID:DWORD 
   LOCAL @hThread:HANDLE  
   LOCAL @ctx:CONTEXT 

   invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID 
   mov @hThread, eax 

   mov @ctx.ContextFlags, CONTEXT_FULL 
   invoke GetThreadContext, @hThread, addr @ctx 

   or @ctx.regFlag, 100h 

   invoke SetThreadContext, @hThread, addr @ctx 
   invoke CloseHandle, @hThread 

   ret 

SetTF endp 

DecEIP proc dwTID:DWORD 
   LOCAL @hThread:HANDLE  
   LOCAL @ctx:CONTEXT 

   invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID 
   mov @hThread, eax 

   mov @ctx.ContextFlags, CONTEXT_FULL 
   invoke GetThreadContext, @hThread, addr @ctx 

   dec @ctx.regEip 

   invoke SetThreadContext, @hThread, addr @ctx 
   invoke CloseHandle, @hThread 
   ret 

DecEIP endp 

GetContext proc uses esi dwTID:DWORD, pCtx:ptr CONTEXT 
   LOCAL @hThread:HANDLE  

   invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID 
   mov @hThread, eax 

   mov esi, pCtx 
   assume esi:ptr CONTEXT 

   mov [esi].ContextFlags, CONTEXT_ALL 
   invoke GetThreadContext, @hThread, esi 

   assume esi:nothing 

   invoke CloseHandle, @hThread 
   ret 

GetContext endp 

SetContext proc dwTID:DWORD, pCtx:ptr CONTEXT 
   LOCAL @hThread:HANDLE  

   invoke OpenThread, THREAD_ALL_ACCESS, FALSE, dwTID 
   mov @hThread, eax 

   invoke SetThreadContext, @hThread, pCtx 

   invoke CloseHandle, @hThread 
   ret 

SetContext endp 

end

是不是call,这个是单步用的。此次模拟这里也不要单步了,要啥单步呀?直接反汇编就得了。 

img 

改个名字 

img 

修改成 

img 

这个代码我先归拢一下,归拢完之后我们再一起再整体再看一看。这些东西丢一个头文件去。 

新建头文件 

img 

img 

img 

img 

img 

img 

修改为 

img 

分析一下代码 

img 

程序跑起来的时候就设个断点。设个断点之后会来异常。然后去处理。其它代码就不动了。 

删除代码 

img 

img 

img 

img 

这个逻辑其实不是很好处理。 

然后反汇编一条,等待输入命令、注意scanf不行、因为scanf遇到空格会截断 。用gets。 

img 

img 

我就不信你这个能写二百五十六个字节。 

img 

输入命令 

img 

必须是bpm 04001000 32这种格式的输入。 

如果eax<=0说明按的是回车,只要按回车,那我就continue让你重新输入。 

img 

解析命令 

先试试这个语法行不行。 

img 

不认识_DisasmOne@8 

不认识_SetTF@4, 

不认识_DecEIP@4。 

这是因为链接的时候没有tool.obj 

img 

添加编译obj文件(编译多个文件) 

img 

img 

img 

重新构建、一堆错误 

img 

img 

img 

g_szOutPutAsm也剪辑过去。 

img 

img 

需要进程句柄【个人理解进程句柄g_hExe之所以用externdef没有从主界面搬过来是因为这个进程就是从主界面拿的】 

img 

img 

构建 

img 

解决办法:public一下。 

img 

构建ok 完事儿。 

解析命令 

就比如ml是把这个都列出来,、 

img 

这个样子写的话,你发现它其实马上匹配bpm,匹配了bpm之后,它就匹配不上bpml了,所以一般就是长的给它放上面、短的给它放下面。 

img 

这样的话他会先判断长的,长的不行就再判断短的。越短的话就放下面去。 

应该是bm不是bpm、它这都是一个字母,多麻烦。换成bm吧。 

img 

g命令其实没不用做别的,直接让他直接跑就行了,、 

img 

img 

解析命令 

设置内存断点之前,你需要把这个内存断点的命令给它解析完,bm后面接着是地址,再接着是长度。 

c 库是有函数的。 

strtoul: 

str -- 要转换为无符号长整数的字符串。 

endptr -- 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。 

base -- 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。 

返回值 

该函数返回转换后的长整数,如果没有执行有效的转换,则返回一个零值。 

实例 

下面的实例演示了 strtoul() 函数的用法。 


#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
  char str[30] = "2030300 This is test"; 
  char *ptr; 
  long ret; 

  ret = strtoul(str, &ptr, 10); 
  printf("数字(无符号长整数)是 %lu\n", ret); 
  printf("字符串部分是 |%s|", ptr); 

  return(0); 
}

让我们编译并运行上面的程序,这将产生以下结果: 

数字(无符号长整数)是 2030300 

字符串部分是 | This is test| 

img 

img 

@pEnd传输参数干啥用?它一般你比如说你传进去的指针在这个位置,然后它会自己跳过前面的空格,然后遇到数字开始解析。解析完之后,这里告诉你这最后一个地方下一个字母在哪个位置。所以你再次下次再解析的时候,到时候你直接给他这个就可以了。 

给@pEnd当地址就可以紧接着判断长度了 

img 

回来之后,我需要一个就变量把这个地址存一下。 

img 

你不可能在零地址上设设断点,在零地址上啥断点都不能设呀,这个strtoul解析出错的话,返回值就是零。 

img 

长度 

直接拿刚才@pEnd传出参数的位置就是可以解析长度。 

img 

img 

有了这个之后,我们就可以去设置内存断点了。我内存断点有三张表、所以来吧,再来个文件别都写到这里了。 

新建文件 

头文件和原文件 

img 

img 

工程选项添加编译链接文件 

img 

img 

img 

那我们这边就让他调个函数完事儿。函数名字SetBm然后他地址需要给我传进来。然后还需要传进来长度。 

类型此次模拟没有输入。我就默认全部都是访问类型的断点。 

img 

这边我就需要有三张表、 

分页表需要三个字段(编号分页地址原有属性 )、 

这个分页断点表需要两个字段(分页编号断点编号 ), 

这个断点表是需要三个字段(编号地址长度 )。 

我就用数组了,钟情于数组。 

分页表 

img 

然后我这个分页,我给他准备多一点儿。那一百个。 

img 

这里面我还要存一个个数、你得知道有几个。 

img 

分页断点表 

img 

那给两百个了、反正内存也不值钱。 

img 

断点表 

给一百个吧,已经很多了。 

img 

先构建一下,看看我写的这些语法有问题不没问题、没问题。ok 那就没问题。 

目录分割 

判断是否可以插入 

写个函数吧, 

img 

SetBm中调一下IsCanInsert。 

img 

其实不用给0d 0a直接调用puts就完事了。不用printf也可以。 

查询断点表,是否与已设断点重叠 

ecx用来计数、每次自增。挨个遍历。 

img 

再写个函数吧,不然这太麻烦。 

img 

img 

判断是否有重叠,这个咋判断? 

重叠就是下图的这两种情况。 

img 

那我就判断一下,先判断1的首地址在不在0的范围内。再判断0的首地址是不是在1的范围内。有一种情况发生的话,说明就有重叠、、 

判断0的首地址是否在1的范围内 

一旦在范围内可以返回FALSE。 

img 

用几个寄存器吧,反正这代码也不多,寄存器我们也多的是.肯定够用。 

img 

img 

判断1的首地址是否在0的范围内 

img 

IsCanInsert调用一下 

是否有重叠 

IsCanInsert调用一下IsOverlapped 

img 

img 

img 

是否有缺页 

VirtualQueryEx:1:30处有验证这个api的作用。判断它有几个分页,然后每个分页都都来查一下状态是否是一千?一旦有一个分页不是一千的话,那我们直接把它干掉。当然没别的招了、因为它的这个一个内存可能会跨页的。 

怎么算这个分页? 

img 

img 

之所以push ecx  pop ecx因为很多api它压根都不存ecx,也不存ebx。【但是我发现ecx保存的分页个数好像没没啥用】 

img 

插入 

img 

接下来是插入。插入首先是存到这个断点表里面。 

存入断点表 

img 

还有编号。我这个编号也不能用个数来当,因为个数删了的话,编号就重了。 

来个编号吧。 

img 

编号从零开始。然后每次自己加1 

存储编号 

img 

存储地址 

img 

存储长度 

img 

img 

存入分页表 

img 

img 

直接来个函数吧,、 

img 

有的话就加进去。 

查询分页表 

如果存在,则直接添加到分页断点表 

img 

如果不存在, 修改内存属性,并添加到分页表和分页断点表 

这边也有个i d,你要有个i d 值。 

img 

img 

然后还要加到分页断点表、分页断点表我还需要知道知道两个编号才能加进去。 

算了直接封装一个AddToPageBpTable函数吧 

img 

加到分页断点表 

断点的id肯定是新的,就是以前不存在的。所以直接拿g_dwID即可。 

img 

分页表已经存在直接加 

断点的id肯定是新的,就是以前不存在的。所以直接拿g_dwID即可。 

img 

img 

添加代码 

img 

设置写完了,设置完之后,函数声明声明一下。 

img 

构建报错 

img 

解决办法:声明一下 

img 

还报错 

img 

我发现是我这里不能直接加的原因 

img 

修改成 

img 

设置内存断点 

img 

完了之后,这边就要去调调处理异常、 

img 

内存断点 

这里这个时候不在这边输命令了,然后自己统一把命令去。 

给个标志。 

img 

img 

他需要输入命令的。 

img 

img 

内存来了之后看一下。最后的时候。如果说是需要输入命令的话。就是下面的代码 

img 

这里肯定是要跑起来的。我这边感觉返回值还有点不够。 

内存断点 

这里是内存断点,我需要去调一下。让内存这边去处理一下。 

img 

这边所有的表都在这里、所以在这个Membp.asm文件里面去处理这个干净。 

img 

声明一下 

img 

img 

这个时候需要判断的是到底要不要去处理。要不要输命令,我这边就简单一点。其实这边OnNoAccessException的返回值应该给多个。 

img 

img 

然后这个是单步、所有的单步都要让他们去自己去处理一下。 

img 

他的单步其实永远是不需要处理的。【不理解】 

img 

img 

自己去判断要不要处理,反正就是把这个内存的单步给它调一下。 

img 

然后这边的一般单步会有很多。所以后面的每个逻辑都让他们自己处理一下。比如说后面可能是处理T命令单步。处理CC单步 

img 

都通过返回值判断要不要去重新输入命令。 

然后后面是查询是否命中: 

img 

我这里就简单点,我就判断是否命中。不然我这个写不完了,就连一个断点就实现不了。 

img 

img 

img 

然后判断地址。判断这个地址是否是位于断点的范围内。 

判断断点是否命中 

img 

这边的返回值我需要存一下。默认的FALSE、表明是不需要的。 

img 

然后如果命中的话,那它就需要输入命令了。我要做个提示。 

img 

判断一下是否命中。 

img 

img 

调用一下输出 

img 

img 

修改代码 

img 

修改为 

img 

但是不管命中、不命中,我都要去做断步配合。 

断步配合,恢复原来内存属性,设置单步 

设置单步直接调用invoke SetTF, [esi].dwThreadId就完事了。 

img 

img 

接着就是断步配合。其实就是恢复原来内存属性。 

img 

这个地址我得知道它的原先的内存属性,保证它原先的属性我就需要去分页表里面去查、。 

img 

查表 

img 

img 

esi已经被我用了,所以我就只能用edi了。 

img 

img 

img 

相等的话就修改内存属性。 

img 

我回头我还得把这个重设、所以我这个单步,我需要给个标志重设的标志。 

重设标志 

img 

重设的时候还需要知道是哪个地址需要重设。 

img 

因为设的是单步,单步的话,执行完这条指令就来了。 

设置单步之后给个标志。 

通知单步重设断点 

img 

通知单步重设断点、然后断点的地址我要存一下。 

img 

这个东西可以放到while外面去了 

img 

重设内存断点、需要重设的时候才重设,不需要重设就不重设了。 

img 

其实应该只要去掉它的读写属性就可以了。 

构建 

img 

没有包含头文件所以报错。 

img 

继续包含头文件 

img 

img 

完事儿是完事儿,能不能跑另说。不能用扫雷这个程序、它这个内存还是感觉跑起来很难受。我要去自己写个程序去测一测。 

新建工程 

img 

img 

下一步下一步完成。 

准备一个大数组。其他的不要了。 

img 

别的不管。留下以下代码


.586 
.model flat,stdcall 
option casemap:none 

  include windows.inc 
  include user32.inc 
  include kernel32.inc 

  includelib user32.lib 
  includelib kernel32.lib 

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

.data 
  ClassName db "MainWinClass",0 
  AppName  db "Main Window",0 
  g_ary dd 10000h dup(0) 
.data? 
  hInstance HINSTANCE ? 
  CommandLine LPSTR ? 

.code 

; --------------------------------------------------------------------------- 

start: 

invoke ExitProcess,eax 
end start

img 

完事之后,我让他自己弹一下。 

img 

这样我就有个固定地址就可以测了,不然这个地址来回变变的。不好测试。 

运行测试 

img 

img 

修改为test程序 

img 

img 

我这边要设个断点,给他设个断点地址,我找一下地址去这个地址是00403019,我要在这里00401000下个断点。 

img 

img 

运行测试 

img 

运行过这两条之后发现程序直接退了。 

原来是我没有加长度。emmm 

加了长度还是不行, 

img 

3:02有调试过程。 

修改代码 

img 

修改为 

img 

我这边的返回值不够多,所以很多情况判断不了。 

运行测试 

我少了写了一个代码 

img 

img 

img 

重新构建运行测试 

我发现老王的代码有一处逻辑不正确。但是我这样写也不对。 

下图是我写的。 

img 

再看看老王的。老王这个地址跟长度比较肯定有问题。 

img 

算了就跟着老王的代码先模拟算了。就先按错的来。 

img 

3:04调试。 

刚开始设置命令的时候bm 00403020 10 所以是判断16次、个人理解16进制的10、就是10进制的16、因为我的长度是16个所以g  16次就弹出界面了。 

img 

这好像还多了一次、17次。因为我这是<= 

img 

行了吧,后面的可能还有一点点小bug,但问题不大。我就不调了。不能让我给你调完嘛,过分了。给你们打了个样、仅供参考。


最新回复 ( 0 )
全部楼主