2.被调试进程的初始化
创建一个调试进程需要使用微软提供的API,
BOOL CreateProcess(
LPCTSTR lpApplicationName, // name of executable module
LPTSTR lpCommandLine, // command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
BOOL bInheritHandles, // handle inheritance option
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // new environment block
LPCTSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFO lpStartupInfo, // startup information
LPPROCESS_INFORMATION lpProcessInformation // process information
);
使用时需要把其中dwCreationFlags设置成DEBUG_PROCESS。
附加进程需要使用
BOOL DebugActiveProcess(
DWORD dwProcessId // process to be debugged
);
其中dwProcessId为调试进程Id。可以通过进程快照或其他方法获得。
创建或附加调试进程后就需要等待调试事件的到来,具体如下:
DEBUG_EVENT DebugEv; // debugging event information
DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation
for(;;)
{
WaitForDebugEvent(&DebugEv, INFINITE);
switch (DebugEv.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_BREAKPOINT:
case EXCEPTION_DATATYPE_MISALIGNMENT:
case EXCEPTION_SINGLE_STEP:
case DBG_CONTROL_C: .
}
case CREATE_THREAD_DEBUG_EVENT:
case CREATE_PROCESS_DEBUG_EVENT:
case EXIT_THREAD_DEBUG_EVENT:
case EXIT_PROCESS_DEBUG_EVENT:
case LOAD_DLL_DEBUG_EVENT:
case UNLOAD_DLL_DEBUG_EVENT:
case OUTPUT_DEBUG_STRING_EVENT:
}
ContinueDebugEvent(DebugEv.dwProcessId,
DebugEv.dwThreadId, dwContinueStatus);
}
该框架是微软在msdn上给出的,大家可以参考一下。
3.普通断点
普通断点的设置有两种方法,int3法和jmp法,jmp法不清楚,所以我使用了int3法。
在设置断点的时候,需要获得设置断点的指令地址,并且为了方便恢复,在设置前还需要报存指
令的第一个字节。然后使用WriteProcessMemory在该地址处写上0xCC(即int3的机器码),这样当被调试进
程执行到我们设置0xCC的地址处时就会触发一个EXCEPTION_BREAKPOINT的调试事件,并由操作系统负责向
我们发送,我们只需要在捕获这个EXCEPTION_BREAKPOINT事件并对其进行处理即可。
设置断点之后,删除断点也非常容易,在删除断点时只需要把先前设置断点时保存的字节写回到指令
处即可。但是这时eip已经指向了0xCC后的那一条指令,并且那一条指令很有可能是错误的,所以我们需要把
eip恢复到执行0xCC之前,使用eip--即可完成;
在普通断点的设置与删除时有一个问题就是同时设置多个断点的情况,此时应当建立一张普通断点的
表,用以记录设置的每个普通断点,并且在捕获到EXCEPTION_BREAKPOINT事件时需要查看当前的断点是否在
普通断点表中,如果在,则中断下来,不在则可以继续执行,而普通断点的删除和设置也要在普通断点表中有
相应的操作,以便管理。
这里给出读写内存的API的原型:
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process
LPVOID lpBaseAddress, // base of memory area
LPVOID lpBuffer, // data buffer
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten // number of bytes written
);
BOOL ReadProcessMemory(
HANDLE hProcess, // handle to the process
LPCVOID lpBaseAddress, // base of memory area
LPVOID lpBuffer, // data buffer
DWORD nSize, // number of bytes to read
LPDWORD lpNumberOfBytesRead // number of bytes read
);
4.内存断点
内存断点的设置和普通断点大不相同,内存断点是根据操作系统对内存属性的管理来设置的。
一般情况下,我们设置一个内存读断点,则需要把原来的内存属性去掉相应的读属性,那么
被调试进程在读取这片内存时就会引发读保护,因为该内存区域已经不可读了。此时,操作系统会向
我们发送一个访问权限(EXCEPTION_ACCESS_VIOLATION)的异常事件,我们捕获到这个时间之后就
可以对其进行处理了。
内存写断点和内存读断点一样,不同的是它去掉的是内存的写属性,使被调试进程在写内存时
触发保护。
下面是设置内存断点的过程中需要用到的两个函数:
BOOL VirtualProtectEx(
HANDLE hProcess, // handle to process
LPVOID lpAddress, // region of committed pages
SIZE_T dwSize, // size of region
DWORD flNewProtect, // desired access protection
PDWORD lpflOldProtect // old protection
);
DWORD VirtualQueryEx(
HANDLE hProcess, // handle to process
LPCVOID lpAddress, // address of region
PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer
DWORD dwLength // size of buffer
);
具体的使用请查阅msdn。
需要注意的是,修改内存属性时,是按照页来划分,一次至少修改一个页的内存属性,也就是
说,对内存属性的操作是按页来进行的。