监视其它进程中某一内存的数据的变化

来源:互联网 发布:oracle数据库安装视频 编辑:程序博客网 时间:2024/05/16 18:15

  怎么样才能得知其他进程中某一内存的内容发生了变化?有几个条件我想首先说明一下,在win32系统下想实时得到其他进程一段内存的内容变化是不可能的。唯一的可能是定时扫描。另外如果想要得知内核态内存的变化也是不太容易实现(姑且允许我这么我这么说,因为在方面我没有进行深入的调查)。还有CPU的要求我现在只是针对intel,因为手头没有AMD的CPU,所以就没有测试过。 

  如果想要实时得到其他进程的内存内容的变化,有两种方案,其一是修改反汇编后的源程序,其二是debug源程序。对于第一种方案来说,工作量和汇编知识要求相对要高一点。那么第二种方案只需要一些win32 程序的debug技术就可以了,相对来说就比较容易实现。在这里我们采用第二种方案。

  intel cpu提供了一组调试寄存器可以用来帮助我们跟踪我们感兴趣的内存地址。下面我来详细的介绍一下这些寄存器的功能。(顺便想说一句这个文本编辑器的功能有点缺陷,真的希望有所改进)

31              23              15              7               0
+---+---+---+---+---+---+---+---+---+-+-----+-+-+-+-+-+-+-+-+-+-+
|LEN|R/W|LEN|R/W|LEN|R/W|LEN|R/W|   | |     |G|L|G|L|G|L|G|L|G|L|
|   |   |   |   |   |   |   |   |0 0|0|0 0 0| | | | | | | | | | | DR7
| 3 | 3 | 2 | 2 | 1 | 1 | 0 | 0 |   | |     |E|E|3|3|2|2|1|1|0|0|
|---+---+---+---+---+---+---+---+-+-+-+-----+-+-+-+-+-+-+-+-+-+-|
|                               |B|B|B|                 |B|B|B|B|
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| | | |0 0 0 0 0 0 0 0 0| | | | | DR6
|                               |T|S|D|                 |3|2|1|0|
|---------------+---------------+-+-+-+---------+-------+-+-+-+-|
|                                                               |
|                            RESERVED                           | DR5
|                                                               |
|---------------+---------------+---------------+---------------|
|                                                               |
|                            RESERVED                           | DR4
|                                                               |
|---------------+---------------+---------------+---------------|
|                                                               |
|                 BREAKPOINT 3 LINEAR ADDRESS                   | DR3
|                                                               |
|---------------+---------------+---------------+---------------|
|                                                               |
|                 BREAKPOINT 2 LINEAR ADDRESS                   | DR2
|                                                               |
|---------------+---------------+---------------+---------------|
|                                                               |
|                 BREAKPOINT 1 LINEAR ADDRESS                   | DR1
|                                                               |
|---------------+---------------+---------------+---------------|
|                                                               |
|                 BREAKPOINT 0 LINEAR ADDRESS                   | DR0
|                                                               |
+---------------+---------------+----------------+--------------+
DR0-DR3是四个调试地址寄存器(debug adress register),其每个寄存器都包含跟四个断点状态有关的线性地址。
地址。断点的状态由DR7来给出。不管有没有分页的情况,这四个寄存器都是有效的。
DR4-DR5 保留。
DR7 调试控制寄存器(debug control register)
调试控制寄存器是用来设定debug condition。对应于DR0-DR3,DR7中相应的R/W0-R/W3指出了产生断点的条件。
RW占据2bit。有一下四种组合。
00 : 表示单条指令中断。(single step)
01 : 表示只当数据被写入的时候中断。
10 : 没有定义。
11 : 表示当数据被读,或写入的时候中断。
对应于DR0-DR3,DR7中相应的LEN0-LEN3指出了所要观测的数据长度。
00: 表示一个字节。
01:表示两个字节
10:没有定义。
11: 表示四个字节。
如果RWn是0的话<指令断点>那么LENn也应该是0。
DR7的低八位支持DR0-DR3哪个寄存器被激活。
其中L表示局部激活,它的意思是当发生任务切换时CPU将自动重设Ln的值。
其中G表示全局激活,它的意思是当发生任务切换时CPU不会自动重设Gn的值。
DR6 调试状态寄存器(debug status register)
调试状态寄存器的作用是用查询当前是何种调试状态。当CPU碰到一个激活的调试异常,
CPU将B0-B3中的一个值设为1。
BT的含义是: debug trap bit.当发生任务切换时,cpu负责在进入调试异常处理函数之前把这个
标志位设为1。
BS 是EFLAGS寄存器的TF标志位。
BD: 当执行下一条指令前,如果上面的八个寄存器中一个发生了改变,CPU这个位设为1。
以上就是调试寄存器的一些关键位的解释。
下面我们来看看具体的代码是怎么实现的。Debug API 可以查看MSDN这里就不多解释了。
函数的调用原型: int WatchAddrss(LPVOID address, BYTE len, LPSTR pszExe);
为了测试的方便,我们可以在被测试程序中设一个全局变量,那么下次启动时它的地址就不会改变了。
然后在测试程序中以一定时间间隔修改它的值。我们的调试程序把修改完的值实时的打印出来。好,现在
我们就来看看int WatchAddrss(LPVOID address, BYTE len, LPSTR pszExe)的实现。这是一个简化
的版本,大家可以扩充。
 
int  WatchMemory(DWORD address, BYTE len,LPSTR pszExe)
{
 STARTUPINFO si;
 PROCESS_INFORMATION pi;
 ZeroMemory(&si,sizeof(si)); 
 ZeroMemory(&si,sizeof(pi));
 si.cb = sizeof(STARTUPINFO);
 CreateProcess(pszExe,NULL,NULL,NULL,TRUE,/
                DEBUG_PROCESS|NORMAL_PRIORITY_CLASS, NULL,NULL,&si,&pi); 

 DEBUG_EVENT DebugEv; // debugging event information
 DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation
 CONTEXT Regs;
 Regs.ContextFlags=CONTEXT_FULL|CONTEXT_DEBUG_REGISTERS;

 int DR0E = 1;         //enable Dr0 
 int LEBPME = 0x100;   //1 0000 0000 : locally enable Dr0 
 int RW = 0x30000;     //break when data read or write
 int WATCHLEN = 0;     //byte length to be watched.
 char *szNTDll = "ntdll"; 
 char *szNTCont =  "NtContinue";
 DWORD NTContAddr = 0;
 HMODULE ntdll;
 DWORD febx=0; 
 DWORD dwBytesRead; 
 DWORD dwBytesWritten;
 DWORD dwBuff;
 DWORD dwBPCnt=0; 
 DWORD dwSSCnt=0;
 DWORD value = 0;
 ResumeThread(pi.hThread); 
 switch(len)
 {
 case 2:
  WATCHLEN = 1 << 18;
  break;
 case 4:
  WATCHLEN = (1 << 19) + (1 << 18);
  break;
 default:
  WATCHLEN = 0;
  break;
 }
 for(;;) 
 {
  // Wait for debugging event to occur
  WaitForDebugEvent(&DebugEv, INFINITE);
  dwContinueStatus=DBG_EXCEPTION_NOT_HANDLED;
  if (DebugEv.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) 
  {
   if (DebugEv.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
   {
    dwContinueStatus=DBG_CONTINUE;
    dwBPCnt++;
    if (dwBPCnt == 1)
    {
     GetThreadContext(pi.hThread,&Regs);
     Regs.Dr7= LEBPME + DR0E;
     ntdll=GetModuleHandle(szNTDll);
     if (ntdll==NULL) //这里的处理是因为第一次中断是由ntdll引起的,为什么要这么做,MS没给解释
      return 0;
     NTContAddr=(DWORD)GetProcAddress(ntdll, szNTCont);
     Regs.Dr0=NTContAddr; 
     SetThreadContext(pi.hThread, &Regs);
    } 
   }
   else if (DebugEv.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
   {
    dwContinueStatus = DBG_CONTINUE;
    dwSSCnt++;
    if (dwSSCnt == 1)
    {
      GetThreadContext(pi.hThread,&Regs);
     
      febx = Regs.Esp;
      febx += 4; //这里加4的是为了获得CONTEXT的地址,原理可以参考BugSlayer专栏,MSDN上有。
     ReadProcessMemory(pi.hProcess,(void*)febx,&dwBuff,4,&dwBytesRead); 
     ReadProcessMemory(pi.hProcess,(void*)dwBuff,(void*)&Regs,sizeof(CONTEXT), &dwBytesRead);
     Regs.Dr0 = address;             //关联要观测的地址
     Regs.Dr7 = RW + LEBPME + DR0E; //设置断点发生的条件。
     WriteProcessMemory(pi.hProcess,(void*)dwBuff,(void*)&Regs, sizeof(CONTEXT),&dwBytesWritten); 
     SetThreadContext(pi.hThread,&Regs);
     
    }
    else
    {
     /*
     //如果想要清除断点的话可以使用下面注释的代码
     GetThreadContext(pi.hThread,&Regs); 
     Regs.Dr0 = 0;
     Regs.Dr7 = 0;
     SetThreadContext(pi.hThread,&Regs);
     */
     //获取要检测内存的内容并且打印出来。
     BOOL ret = ReadProcessMemory(pi.hProcess,(void*)address,&value,len,&dwBytesRead);
     printf("ret = %d/t%d/n",ret,value);

    }
   }
  }
  else if (DebugEv.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
  {
   return 0;
  }
  ContinueDebugEvent(DebugEv.dwProcessId,DebugEv.dwThreadId, dwContinueStatus);
 }
 return 0;
}
调用例程:
int main(int argc, char* argv[])
{
   WatchMemory(0x4168fc,4,"TobeDebug.exe");
   return 1;
}
写在后面:
  要获取其他程序全局变量的RVA可以静态分析它的PE文件。下次准备写这方面的东西。(^_^,有很多人写过)
  有什么写的不对的地方欢迎大家指出来,大家一起交流一下。
 
原创粉丝点击