加载exe:
(调试器代码:http://bbs.pediy.com/showthread.php?t=107005)

先看看代码

int LoadDebuggedProcess(LPCWSTR FilePath)
{//加载被调试进程
  STARTUPINFO         stStartInfo;

  memset ( &stStartInfo   , NULL , sizeof ( STARTUPINFO         ) ) ;
  memset ( &stProcessInfo , NULL , sizeof ( PROCESS_INFORMATION ) ) ;

  if(!CreateProcess(FilePath, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE | DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &stStartInfo, &stProcessInfo))
  {
    int err = GetLastError();
    AfxMessageBox(_T("创建进程失败"));
    return err;
  }

  StopOnDebugEvent(CREATE_PROCESS_DEBUG_EVENT); //在进程加载事件时候挂起
  return 0;
}

1.利用CreateProcess函数,创建一个新的进程,然后将对应的exe模块加载进程。
然后看到StopOnDebugEvent函数。这个函数只是简单的调用了WaitForDebugEvent和ContinueDebugEvent事件。我们在CREATE_PROCESS_DEBUG_EVENT事件暂停程序。

2.进程加载完了。但是程序被暂停在CREATE_PROCESS事件上,我们还不能在内存里面看到代码段的数据。
所以,我在test程序里面自己添加了        ResumeDebuggedThread()  StopOnException();这两个函数。让程序停在第一个异常。

3.第一个异常来自于系统。对于我们和系统都有很重要的作用。它在进程里面所有模块都加载过后,马上进入exe模块代码空间之前。异常的细节和对于系统有什么重要作用不是我们现在需要讨论的话题。对于我们的调试器的重要作用才是我们现在的话题。由于exe模块已经被加载。那么我们可以获取exe模块加载后的基地址。这个地址是被加载到内存的基地址。有可能是400000,也可能是一个被重定位的地址。

4.我们利用

DWORD GetBaseAddress(DWORD dwPID)
{//获取exe模块的加载地址

  HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
  MODULEENTRY32 me32;

  // Take a snapshot of all modules in the specified process.
  hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID );
  if( hModuleSnap == INVALID_HANDLE_VALUE )
  {
    AfxMessageBox(_T("Invalid handle value"));
      return 0;
  }

  // Set the size of the structure before using it.
  me32.dwSize = sizeof( MODULEENTRY32 );

  // Retrieve information about the first module,
  // and exit if unsuccessful
  if( !Module32First( hModuleSnap, &me32 ) )
  {
    CloseHandle( hModuleSnap );           // clean the snapshot object
    return 0;
  }

DWORD Value = (DWORD)me32.modBaseAddr;


  CloseHandle( hModuleSnap );

  return Value;

}
这个函数获取实际加载地址。原理很简单。没有什么内部api,完全利用MS提供给我们的,获取进程相关信息的函数实现。代码就不多做介绍了。细节可以参考msdn关于CreateToolhelp32Snapshot例子代码

5.得到实际加载的地址后。我们需要获取程序实际的入口地址。

DWORD GetEntryPoint()
{//获取被调试程序的入口地址
  IMAGE_DOS_HEADER  RemoteImageBase;
  IMAGE_OPTIONAL_HEADER RemoteOptionalHeader;

  PIMAGE_DOS_HEADER ImageBase = (PIMAGE_DOS_HEADER)GetBaseAddress(stProcessInfo.dwProcessId);
  ReadProcessMemory(stProcessInfo.hProcess, ImageBase, &RemoteImageBase, sizeof(IMAGE_DOS_HEADER), NULL);
  
  PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)((DWORD)RemoteImageBase.e_lfanew + (DWORD)ImageBase);

  PIMAGE_OPTIONAL_HEADER OptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)NtHeaders + 24);//24即sizeof(IMAGE_FILE_HEADER)+sizeof(DOWRD)
  ReadProcessMemory(stProcessInfo.hProcess, OptionalHeader, &RemoteOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER), NULL);
  
  return (DWORD)ImageBase + RemoteOptionalHeader.AddressOfEntryPoint;
}

和读取PE文件进行分析没有什么区别。但是,需要清楚意识到,我们操作的是另一个进程空间的地址。
所以,在获取文件头的信息时,需要先在被调试进程中读取这部分信息。

6.我们获得了程序的入口地址。这个当然是为了能动态从内存里面获取代码段的指令进行反汇编。为了确认是否正确。我们将入口地址后1000个字节的代码进行反汇编。
加载debuggee程序


 

对比winhex,可以看到我们得到正确的入口地址。但是,大家肯定看到call后的地址没有加上实际加载的基地址。这个问题……  需要你去解决。


我们在来看看调试器exe加载的过程:

首先,创建进程,这个时候调试器得到进程加载事件。从而,调试器开始控制被调试程序(给我们的感觉是),进行控制debuggee到系统的软断点异常到来。这个时候,程序马上将进入exe模块的代码空间。而所有的dll都已经加载完成。Exe加载完成。即 进程已经完成了自己的初始化,所以,我们可以利用MS提供的API获取进程的exe模块的真实加载地址。然后,我们找到程序的入口点。当然,我们还需要找到代码段的开始地址。然后将我们想看到的指令,进行反汇编。
程序停在ntdll空间,马上将进入exe模块。调试器这个时候完成自己需要的“初始化”。

很快,调试器将和程序的设计者开始打交道了。

待续……