`
isiqi
  • 浏览: 16825307 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

用Debug函数实现API函数的跟踪(2)

阅读更多
用Debug函数实现API函数的跟踪(2)
作者:彭春华阅读人次:554文章来源:赛迪网发布时间:2007-9-3网友评论(0)条

对目标进程设置断点:

我们的目标是监视API函数的输入输出,那么,首先应该知道DLL模块中提供了哪些API函以及这些API的入口地址。在前面将过,广义的API还包括未导出的内部函数。如果你有DLL模块的调试版本和调试连接文件(pdb文件),也可以根据调试信息得到内部函数的信息。

· 得到函数名及函数入口地址

通过程序得到函数的入口地址有很多种方法。对于用VC编译出来的DLL,如果是Debug版本,可以通过ImageHlp库函数得到调试信息,分析出函数的入口地址。如果没有Debug版本,也可以通过分析导出函数表得到函数的入口地址。

1.用Imagehlp库函数得到Debug版本的函数名和函数入口地址。

可以利用Imagehlp库函数分析Debug信息,关联的函数为SymInitialize、SymEnumerateSymbols和UnDecorateSymbolName。详细可以参考MSDN中关于这些函数的说明和用法。不过,用Imagehlp只能分析出用VC编译的程序,对C++Builder编译的程序不能用这种方法分析。

2.DLL的导出表得到函数导出函数名和函数的入口地址。

在大多数情况下,我们还是希望监视的是Release版本的输入输出参数,毕竟Debug版本不是我们最终提供给用户的产品。Debug和Release的编译条件不同导致产生的结果不同,在很多BBS中都讨论过。所以,我认为跟踪监视Release版本更加有实用价值。

通过分析DLL导出表得到导出函数名在MSDN上就有源代码。关于导出表的说明大家可以参考关于PE结构的文章。

3.通过OLE函数取得COM接口

你也可以通过OLE函数分析DLL提供的接口函数。接口函数不是通过DLL导出表导出的。你可以通过LoadTypeLib函数来分析COM接口,得到COM记录接口的入口地址,这样,你就可以监视COM接口的调用了。这是API HOOK没法实现的。在这里我不打算分析分析COM接口的方式了。在MSDN上通过搜索LoadTypeLib sample关键词你就可以找到相关的源代码进行修改实现你的目标。

这里是通过计算机自动分析目标模块得到DLL导出函数的方案,作为我们监视的目的而言,这些工作只是为了得到一系列的函数名和函数地址而已。函数名只是一个让我们容易识别函数的名称而已,该函数入口地址才是我们真正关心的目标。换句话说,如果你能够确保某一个地址一定是一个函数(包括内部函数)的入口地址,你就完全可以给这个函数定义自己的名称,将它加入你的函数管理表中,同样可以实现监视该函数的输入输出参数的功能。这也是实现Exe内部函数的监视功能的原因。如果你有Exe编译时生成的Map文件(你可以在编译时选择生成Map文件),你就可以通过分析Map文件,得到内部函数的入口地址,将内部函数加入到你的函数管理表中。(一个函数的名称对于监视功能来讲究竟是FunA还是FunB并没有什么意义,但名称是FunA还是FunB的名称对于监视者分析监视结果是有意义的,你完全可以将MessageBox的函数在输出监视结果是以FunA的名称输出,所以在监视一些内部无名称的函数时,你完全可以定义你自己的名字)。

· 在函数入口地址处设置断点

设置断点非常简单,只要将0xCC(int 3)写入指定的地址就可以了。这样程序运行到指定地址时,将产生调试中断信息通知调试程序。修改指定进程的内存数据可以通过WriteProcessMemory函数来完成。由于一般情况下作为程序代码段都被保护起来了,所以还有一个函数也会用到。VirtualProtectEx。在实际情况下,当调试断点发生时,调试程序还应该将原来的代码写回被调试程序。

unsigned char SetBreakPoint(DWORD pAdd, unsigned char code)
{
	unsigned char b;
	BOOL rc;
	DWORD dwRead, dwOldFlg;
// 0x80000000以上的地址为系统共有区域,不可以修改
if( pAdd >= 0x80000000 || pAdd == 0)
    	return code;
// 取得原来的代码
rc = ReadProcessMemory(_ghDebug, pAdd, &b, sizeof(BYTE), &dwRead);
// 原来的代码和准备修改的代码相同,没有必要再修改
if(rc == 0 || b == code)
	    return code;
// 修改页码保护属性
VirtualProtectEx(_ghDebug, pAdd, sizeof(unsigned char), PAGE_READWRITE, 
&dwOldFlg);
// 修改目标代码
WriteProcessMemory(_ghDebug, pAdd, &code, sizeof(unsigned char), &dwRead);
// 恢复页码保护属性
VirtualProtectEx(_ghDebug, pAdd, sizeof(unsigned char), dwOldFlg, &dwOldFlg);
	return b;
}

在设置断点时你必须将原来的代码保存起来,这样在恢复断点时就可以将代码还原了。一般用法为:设置断点m_code = SetBreakPoint( pFunAdd, 0xCC); 恢复断点:SetBreakPoint( pFunAdd, m_code); 记住,每个函数入口地址的代码都可能不同,你应该为每个断点地址保存一个原来的代码,在恢复时就不会发生错误了。

好了,现在目标程序中已经设置好了断点,当目标程序调用设置了断点的函数时,将产生一个调试中断信息通知调试程序。我们就要在调试程序中编写我们的调试中断程序了。

编写调试中断处理程序

被调试程序产生中断时,将产生一个EXCEPTION_DEBUG_EVENT信息通知调试程序进行处理。同时将填充EXCEPTION_DEBUG_INFO结构。

typedef struct _EXCEPTION_DEBUG_INFO { 
  EXCEPTION_RECORD ExceptionRecord; 
  DWORD dwFirstChance; 
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_RECORD { 
  DWORD ExceptionCode; 
  DWORD ExceptionFlags; 
  struct _EXCEPTION_RECORD *ExceptionRecord; 
  PVOID ExceptionAddress; 
  DWORD NumberParameters; 
  ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

在该结构中,我们比较感兴趣的是产生中断的地址ExceptionAddress和产生中断的信息代码ExceptionCode。在信息代码中与我们任务相关的信息代码为:

EXCEPTION_BREAKPOINT:断点中断信息代码
EXCEPTION_SINGLE_STEP:单步中断信息代码

断点中断是由于我们在前面设置断点0xCC代码运行时产生的。由于产生中断后,我们必须将原来的代码写回被调试程序中继续运行。但是,代码一旦被写回目标程序,这样,当目标程序再次调用该函数时将不会产生中断,我们就只能实现一次监视了。所以,我们必须在将原代码写回被调试程序后,应该让被调试程序已单步的方式运行,再次产生一个单步中断的调试信息。在单步中断处理中,我们再次将0xCC代码写入函数的入口地址,这样就可以保证再次调用时产生中断。

首先,在进行中断处理前我们必须作些准备工作,管理起线程ID和线程句柄。为了管理单步中断处理,我们还必须维护一个基于线程的单步地址的管理,这样就可以允许被调试程序拥有多线程的功能。--我们不能保证单步运行时不被该进程的其他线程所打断。

// 我们利用一个map进行管理线程ID和线程句柄之间的关系
// 同时也用一个map管理函数地址和断点的关系
typedef map<DWORD, HANDLE, less<DWORD> > THREAD_MAP;
typedef map<DWORD, void*, less<DWORD> > THREAD_SINGLESTEP_MAP;
THREAD_MAP _gthreads;
FUN_BREAK_MAP _gFunBreaks;
// 并且假设设置断点时采用了如下方案进行原来代码的管理
BYTE code = SetBreakPoint(pFunAdd, 0xCC);
if(code != 0xCC)
_gFunBreaks[pFunAdd] = code;
…
// 调试处理程序
BOOL WINAPI OnDebugEvent(DEBUG_EVENT* pEvent)
{
BOOL rc = TRUE;
switch(pEvent->dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
// 记录线程ID和线程句柄的关系
_gthreads[pEvent->dwThreadId] = pEvent->u.CreateProcessInfo.hThread;
…
break;
case CREATE_THREAD_DEBUG_EVENT:
// 记录线程ID和线程句柄的关系
_gthreads [pEvent->dwThreadId] = pEvent->u.CreateThread.hThread;
…
break;
case EXIT_THREAD_DEBUG_EVENT:
// 线程退出时清除线程ID
_gthreads.erase (pEvent->dwThreadId);
…
break;
case EXCEPTION_DEBUG_EVENT:
// 中断处理程序
rc = OnDebugException(pEvent);
break;
…
}
return rc;
}

下面进行中断处理程序。同样,我们只考虑我们关心的中断信息代码。在发生中断时,我们通过GetThreadContext(&context)得到中断线程的上下文信息。此时,context.esp就是函数的返回地址,context.esp+4位置的值就是函数的第一个参数,context.esp+8就是第二个参数,依次类推可以得到你想要的任何参数。需要注意的是因为参数是在被调试进程中的内容,所以你必须通过ReadProcessMemory函数才能得到:

DWORD buf[4]; // 取4个参数
ReadProcessMemory(_ghDebug, (void*)(context.esp + 4), &buf,  sizeof(buf), 
&dwRead);

那么buf[0]就是第一个参数,buf[1]就是第二个参数。。。注意,在FunA(int a, char* p, OPENFILENAME* pof)函数调用时,buf[0] = a, buf[1] = p这里buf[1]是p的指针而不是p的内容,如果你希望访问p的内容,必须同样通过ReadProcessMemory函数再次取得p的内容。对于结构体指针也必须如此:

// 取得p的内容:
char pBuf[256];
ReadProcessMemory(_ghDebug, (void*)(buf[1]), &pBuf,  sizeof(pBuf), &dwRead);
//取得pof的内容:
OPENFILENAME of
ReadProcessMemory(_ghDebug, (void*)(buf[2]), &of,  sizeof(of), &dwRead);

如果结构体中还有指针,要取得该指针的内容,也必须和取得p的内容一样的方式读取被调试程序的内存。总的来说,你必须意识到监视目标程序的所有内容都是对目标进程的内存读取操作,这些指针都是目标进程的内存地址,而不是调试进程的地址。

原文链接:http://www.bccn.net/Article/kfyy/vc/jszl/200709/6207.html

分享到:
评论

相关推荐

    用Debug函数实现API函数的跟踪

    用Debug函数实现API函数的跟踪 大家知道,VC可以用来调试程序,除了调试Debug程序,当然也可以调试Release程 序(调试Release程序时为汇编代码)。如果知道函数的入口地址,只需在函数入口上设置断点,当程序调用了...

    用Debug函数实现API函数的跟踪.txt

    ### 使用Debug函数实现API函数跟踪 #### 概述 在软件开发过程中,有时我们需要对特定的应用程序接口(API)函数进行跟踪,以便更好地理解和调试程序的行为。本文将介绍一种利用Debug函数来实现API函数跟踪的方法。...

    win7下使用debug api实现网截源码

    Debug API主要包括DebugActiveProcess、DebugBreak、ContinueDebugEvent、WaitForDebugEvent等函数,它们使得开发者可以在目标进程中设置断点、检查内存、跟踪执行流程等。在进行网络截取时,我们需要对与网络通信...

    精通windowsAPI函数接口编程实例源代码

    《精通Windows API函数接口编程实例源代码》是一个深入学习Windows API编程的重要资源,它涵盖了大量实践案例,旨在帮助开发者熟练掌握API接口的使用技巧。Windows API是微软为开发者提供的一个平台,通过它,程序员...

    VBA中API 函数集锦

    使用 API 函数 `SetWindowPos` 可以实现让窗体总是在最前面的效果。首先,我们需要在模块中声明 `SetWindowPos` 函数: `Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hwndInsertAfter ...

    Windows API函数大(中文版)

    《Windows API函数大全》中文版是一本针对Windows操作系统编程的核心参考资源,对于深入学习和理解高级编程至关重要。Windows API(应用程序接口)是微软提供的一系列函数、常量、数据类型和结构,允许开发者直接与...

    汇川机器人通过API函数编写

    API函数是软件开发中的一个重要概念,它是一组预先定义好的函数,允许开发者调用这些函数来实现特定功能,而无需理解底层实现细节。在汇川机器人的上下文中,API函数使得程序员能够更方便地编写控制逻辑,控制...

    内核debugfs使用简介

    - `debugfs_create_x8`、`debugfs_create_x16`、`debugfs_create_x32`、`debugfs_create_x64`:与上述函数类似,但使用`x`前缀表示数据以十六进制格式显示。 - `debugfs_create_size_t`:用于创建表示`size_t`类型...

    ORACLE数据库API接口函数设计说明.pdf

    本文档提供了关于Oracle数据库API接口函数的详细设计说明,包括如何设置项目环境以及如何使用提供的接口函数进行数据库操作。通过学习本文档,开发者能够有效地利用这些API接口函数完成与Oracle数据库的交互任务。...

    精通Windows.API-函数、接口、编程实例

    同时,还需要熟悉C或C++语言,因为大多数API函数都是用这两种语言的语法进行调用的。对于初学者来说,理解参数的意义、函数的返回值以及错误处理机制是关键。 在实践中,开发者往往需要借助诸如调试器(如DebugView...

    基于ucos操作系统的API函数

    在嵌入式系统开发中,uC/...总之,理解和熟练掌握uCOS的API函数以及LWIP网络库的使用,是开发高效、可靠的嵌入式系统的关键。通过深入学习和实践,开发者可以充分利用这些工具实现复杂的功能,满足各种应用场景的需求。

    对付杀不了的进程---WINDOWS API EnableDebugPrivilege提权,杀进程.zip

    除了使用`EnableDebugPrivilege` API外,还可以借助其他Windows API函数来尝试结束进程,例如通过`CreateToolhelp32Snapshot`和`Process32First/Next`遍历进程,然后使用`OpenProcess`和`TerminateProcess`。...

    毕设&课程作业_基于STM32_C8T6的Debug函数项目.zip

    在"基于STM32_C8T6的Debug函数项目"中,我们关注的是如何在STM32开发过程中实现有效的调试功能。Debug功能对于任何软件开发都至关重要,它帮助开发者追踪代码执行、定位错误、理解程序运行状态,从而优化代码性能。...

    实现delphi的debugprint

    这个单元可能包含了`DebugPrint`函数的声明和实现,以及如何在项目中使用它的示例。通过查看此文件,我们可以学习如何自定义调试工具,以提高我们的开发效率和调试体验。 总的来说,`DebugPrint`是为了解决`...

    Vista下动态开启Local kernel Debug的实现与分析

    在分析VistaLKD的驱动程序时,我们发现了一个名为GetProcessNameOffset()的函数,它通过IoGetCurrentProcess和_strnicmp等API来识别当前进程,这在内核编程中常见于识别关键系统进程的场景。GetProcessNameOffset()...

    WebGL编程指南函数库

    WebGL是一种基于OpenGL标准的JavaScript API,用于在任何兼容的Web浏览器中实现硬件加速的2D和3D图形渲染。这个“WebGL编程指南函数库”包含了一系列辅助脚本,帮助开发者更方便地进行WebGL编程。以下是这些脚本的...

    webgl公用函数库(cuon-matrix.js,cuon-utils.js,webgl-debug.js,webgl-utils.js)

    WebGL是一种基于OpenGL标准的JavaScript API,用于在任何兼容的Web浏览器中实现2D和3D图形渲染。在WebGL编程中,为了提高代码的可重用性和简化复杂任务,通常会使用各种公用函数库。这里提到的"cuon-matrix.js"、...

    在VB中使用API函数 (之四)...

    ### 在VB中使用API函数(之四):深入探讨回调函数与窗口过程 #### 标题解析 本篇文章主要讨论了如何在Visual Basic (VB) 编程环境中使用API函数,特别是聚焦于第四部分的内容——回调函数的应用以及窗口过程的...

    简单的钩子函数

    2. **WH_GETMESSAGE**:在消息被放入消息队列之前捕获它,常用于实现消息过滤或优先级调整。 3. **WH_CALLWNDPROC**:在窗口过程调用前后捕获消息,允许修改消息或处理。 4. **WH_CALLWNDPROCRET**:在窗口过程返回...

    Debugviewer,DLL查看器

    Debugviewer是一款专为C语言API开发设计的DLL(动态链接库)查看工具,它提供了对公有函数的详细查看功能,对于深入理解底层开发和Windows API编程的开发者来说,是不可或缺的辅助工具。通过Debugviewer,程序员能够...

Global site tag (gtag.js) - Google Analytics