一个Win32API Trace Tool的设计与实现

用VC编程也有不短的时间了,对kernel32、advapi32、user32、gdi32等动态库里的API多数都已经很熟悉了。API是操作系统提供给应用程序的一组服务,很久以前就想要做个小工具,用来跟踪应用程序对API的调用,对于分析程序的行为、功能的实现原理以及Bug的定位都会有很大的帮助。可是长久以来,都没有付诸实际行动。最近,为了定位一个有趣的Bug,终于动手把这个设想实现出来。

PE文件动态链接的细节原理就是:在代码中调用API时,按__stdcall调用约定传参,然后call Import Table中对应的Entry,Import Table中对应的Entry其实是一个绝对地址。这个才是API的真正地址,是在PE文件被加载时由系统加载器填写的。例如:

;x86 code    LPVOID lpMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 8);
001D1001 8B 35 08 20 1D 00    mov         esi,dword ptr [[email protected] (1D2008h)]
001D1007 6A 08                push        8
001D1009 6A 08                push        8
001D100B FF D6                call        esi
001D100D 50                   push        eax
001D100E FF 15 00 20 1D 00    call        dword ptr [[email protected] (1D2000h)]

[email protected]:
001D2008 B9 14 E1 76    ; [email protected] (76E114B9h)

[email protected]:
001D2000 46 E0 55 77    ; [email protected] (7755E046h)
;x64 code
    LPVOID lpMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 8);
000000013F141006 FF 15 04 10 00 00    call        qword ptr [__imp_GetProcessHeap (13F142010h)]
000000013F14100C BA 08 00 00 00       mov         edx,8
000000013F141011 48 8B C8             mov         rcx,rax
000000013F141014 44 8B C2             mov         r8d,edx
000000013F141017 FF 15 E3 0F 00 00    call        qword ptr [__imp_HeapAlloc (13F142000h)]
000000013F14101D 48 8B D8             mov         rbx,rax

__imp_GetProcessHeap:
000000013F142010 20 1A 25 77 00 00 00 00    ; kernel32.dll!GetProcessHeapStub (0000000077251A20h)

__imp_HeapAlloc:
000000013F142000 60 33 3A 77 00 00 00 00    ; ntdll.dll!RtlAllocateHeap (00000000773A3360h)

Hook Import Table的原理就是把Hook Import Table中指定API的地址替换成我们自己实现的Stub函数的地址,在Stub函数中做一些我们想要的处理和调用原始的API。预期目标是想要通过Hook Import Table来实现对API调用的跟踪,要求能够获得“哪个模块调用了哪个模块中的哪个API”,传递的参数和返回值。

整体设计思路:做一个HookApi.dll,在被加载时从一个XML文件读取要进行Hook的模块和API列表,为每一个要Hook的API生成一个Stub函数,Stub函数的功能是调用原始API并将参数和返回值输出到Log文件,最后将Import Table中的API地址替换为相应的Stub函数地址即完成Hook。

接下来详细说明各部分的设计:

一,XML列表格式

<hook logmax="1024">
    <library name="HookTest.exe">
        <import dll="kernel32.dll">
            <api name="HeapAlloc" args="3" />
            <api name="HeapFree" args="3" />
        </import>
        <import dll="user32.dll">
            <api name="DestroyWindow" args="1" />
        </import>
    </library>
    <library name="gdiplus.dll">
        <import dll="gdi32.dll">
            <api name="Ellipse" args="5" />
            <api name="Pie" args="9" />
            <api name="Chord" args="9" />
            <api name="Arc" args="9" />
        </import>
    </library>
</hook>

根节点“hook”的“logmax”属性指定Log文件的Max Size,以MB为单位,取值范围32~32768。“library”为要被Hook Import的调用者模块,“import”则是包含被调用API的模块,“api”的“args”属性指定API的参数个数。

二,Log Function的设计

Log Function被设计用来记录API调用、传递的参数和返回值。在生成的每个API的Stub函数中都会调用Log Function,所以为了尽可能减小对性能的影响,使用FileMapping来将Log写到文件,Log Function接收的参数不包含任何模块、函数名称字符串,而是依赖于XML列表的索引值。x86和x64版本的Log Function的prototype如下:

// x86VOID WINAPI LogOut32(DWORD dwLibIdx, DWORD dwDllIdx, DWORD dwApiIdx, DWORD dwArgCnt, DWORD dwRetVal, LPVOID lpArgs);// x64
VOID WINAPI LogOut64(UINT64 uLibIdxDllIdx, UINT64 uApiIdxArgCnt, UINT64 uRetVal, LPVOID lpArgs);

输出的每条Log记录的格式为(伪代码):

struct LogRecord
{
    DWORD dwLibIdx;            // 调用者模块索引
    DWORD dwDllIdx;            // API所在模块索引
    DWORD dwApiIdx;            // API索引
    DWORD dwArgCnt;            // 参数个数
    PVOID aryArgs[dwArgCnt];   // 参数列表,是否存在取决于参数个数,在x86平台每个参数大小为4字节,x64平台为8字节。
    DWORD dwRetVal;            // 返回值
};

跟踪完成后,使用另一个工具“Log2Text.exe”来将Log文件转化为txt格式。FileMapping的最大文件大小从XML文件的“logmax”指定,每次映射到内存中的View大小为8MB,当检测到使用过半时向后移动4MB重新映射。使用CriticalSection做多线程同步。

三,Stub Function的汇编代码

这里的Stub Function主要是想要实现一段通用的代码,可以在运行时根据XML中的参数信息为每个要Hook的API动态的生成。经过几次修改后确定下来,思路是:x86平台,将栈上的参数按照原始顺序再次压栈,然后调用真正的API,将原始参数地址、返回值和模块、API索引等信息传递给Log Function,然后返回同时清理栈;x64平台,首先备份传参寄存器,如果栈上还有参数,按原始顺序再次压栈,调用真正API,将栈上备份参数地址、返回值和模块、API索引等信息传递给Log Function,返回。也就是说从传参与栈的角度看,Stub函数等价于一个与API prototype一致的C函数。具体代码如下:

x86 code;------------------------------------------------; 参数压栈,如果有的话

        mov     ecx,0x12345678        ; 参数个数,根据XML动态写入
        cmp     ecx,0
        je      l01_02
        mov     eax,ecx
l01_01: push    dword [esp + eax * 4]
        loop    l01_01
;------------------------------------------------
; 调用真正的API

l01_02: call    0x12345678            ; API的相对地址,动态写入
        push    eax                   ; 备份返回值

;------------------------------------------------
; 调用Log Function

        lea     ecx,[esp + 8]
        push    ecx                   ; 指向参数列表的指针
        push    eax                   ; API返回值
        push    0x12345678            ; ArgCnt,根据XML动态写入
        push    0x12345678            ; ApiIdx,根据XML动态写入
        push    0x12345678            ; DllIdx,根据XML动态写入
        push    0x12345678            ; LibIdx,根据XML动态写入

        call    0x12345678            ; Log Function相对地址,动态写入

;------------------------------------------------
; 恢复返回值,返回并清理栈

        pop     eax

        ret     0x1234                ; 栈上参数大小,根据XML动态写入
x64 code;------------------------------------------------; 备份传参寄存器到栈上的预留空间
        push    rbp
        mov     rbp,rsp

        mov     [rbp + 40],r9
        mov     [rbp + 32],r8
        mov     [rbp + 24],rdx
        mov     [rbp + 16],rcx

;------------------------------------------------
; 栈上参数压栈,如果有的话

        mov     rcx,0x12345678        ; 参数个数,根据XML动态写入
        cmp     rcx,4
        jle     l01_02
        sub     rcx,4
l01_01: push    qword [rbp + rcx * 8 + 40]
        loop    l01_01

l01_02: mov     rcx,[rbp + 16]

;------------------------------------------------
; 调用真正API

        sub     rsp,20h
        mov     rax,0x123456789abcdef ; API地址,动态写入
        call    rax

        mov     rsp,rbp
        push    rax                   ; 备份返回值

;------------------------------------------------
; 调用Log Function

        lea     r9,[rbp + 16]         ; 指向参数列表的指针
        mov     r8,rax                ; API返回值
        mov     rdx,0x123456789abcdef ; ApiIdx | (ArgCnt << 32)得到的一个UINT64,根据XML生成并动态写入
        mov     rcx,0x123456789abcdef ; LibIdx | (DllIdx << 32)得到的一个UINT64,根据XML生成并动态写入
        sub     rsp,20h
        mov     rax,0x123456789abcdef ; Log Function地址,动态写入
        call    rax

;------------------------------------------------
; 恢复返回值并返回

        mov     rax,[rbp - 8]

        mov     rsp,rbp
        pop     rbp

        ret

以上汇编代码中类似于“0x12345678”只是用来占位而已,没有实际意义。其中调用Log Function的代码与上节中的prototype相对应。在实行Hook时,有专门的代码为每个Stub分配可执行虚拟内存、填写需要动态写入的值,再将Import Table中对应的Entry指向Stub函数。

这种设计要求XML中指定的参数个数必须精确。在设计Stub函数时,如果不需要跟踪返回值,可以在调用真正API之前,通过Log Function输出参数列表等信息,然后直接jmp到API地址,避免参数再次压栈,提高性能也同时避免了因为XML中错误的参数个数造成的栈破坏。

四,Log2Text转换工具

此工具的作用是根据XML列表,将Log文件转化成可以直接阅读的txt文件,为了优化性能同样使用FileMapping,txt文件最大大小同样取决于“logmax”。输出的txt文本格式如下:

HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x00000004 ) : 0x00614350
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00614350 ) : 0x00000001
HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x00000008 ) : 0x00614350
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00614350 ) : 0x00000001
HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x0000000c ) : 0x00615838
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00615838 ) : 0x00000001
HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x00000010 ) : 0x00615838
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00615838 ) : 0x00000001
HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x00000014 ) : 0x00613768
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00613768 ) : 0x00000001
HookTest.exe : kernel32.dll : HeapAlloc ( 0x005c0000, 0x00000008, 0x00000018 ) : 0x00613768
HookTest.exe : kernel32.dll : HeapFree ( 0x005c0000, 0x00000000, 0x00613768 ) : 0x00000001

依次是:“调用者模块名称 : 包含API的模块名称 : API名称 ( 参数列表 ) : 返回值”,对于void函数也会取到返回值,就是当时eax/rax的值,没有任何意义。其实也可以把函数的返回地址一起由Log文件输出,可以更精确地跟踪到模块中调用API的代码位置。

此工具目前只实现了Import Table的Hook,基本上也可以按照同样思路Hook Export Table来应对GetProcAddress或者用户自己实现类似函数。inline Hook可算是终极手法,但是想要做成一个普适型工具好像不是很可行。实现这个工具的目的,只是为了辅助我们宏观上大致定位一下我们感兴趣的位置,在下调试断点时可以更明确,更深入的跟踪分析还是需要自己去调试。

上述可执行文件已上传至我的百度云:http://pan.baidu.com/s/1nNB0y,还没有经过太多测试,有兴趣的朋友可以测试一下。感谢阅读。

时间: 2024-10-10 04:28:44

一个Win32API Trace Tool的设计与实现的相关文章

Axure RP一个专业的快速原型设计工具

Axure RP是一个专业的快速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司:RP则是Rapid Prototyping(快速原型)的缩写. Axure简要介绍 Axure RP已被一些大公司采用. Axure RP的使用者主要包括商业分析师.信息架构师.可用性专家.产品经理.IT咨询师.用户体验设计师.交互设计师.界面设计师等,另外,架构师.程序开发工程师也在使用Axure. Axure RP--是一个非常专业的快速原型设计的一个工具,客户提出需求,然后根据需求定

Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。

#29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类Truck是Car类的子类,其中包含的属性有载重量payload.每个 类都有构造方法和输出相关数据的方法.最后,写一个测试类来测试这些类的功 能. package hanqi; public class Vehicle { private int wheels; private int weight

Axure RP一个专业的高速原型设计工具

Axure RP是一个专业的高速原型设计工具.Axure(发音:Ack-sure).代表美国Axure公司.RP则是Rapid Prototyping(高速原型)的缩写. Axure简要介绍 Axure RP已被一些大公司採用. Axure RP的使用者主要包含商业分析师.信息架构师.可用性专家.产品经理.IT咨询师.用户体验设计师.交互设计师.界面设计师等.另外.架构师.程序开发project师也在使用Axure. Axure RP--是一个很专业的高速原型设计的一个工具,客户提出需求,然后依

推荐一个iOS应用UI界面设计网站

定义和用法 <!DOCTYPE> 声明必须是 HTML 文档的第一行,位于 <html> 标签之前. <!DOCTYPE> 声明不是 HTML 标签:它是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令. 在 HTML 4.01 中,<!DOCTYPE> 声明引用 DTD,因为 HTML 4.01 基于 SGML.DTD 规定了标记语言的规则,这样浏览器才能正确地呈现内容. DOCTYPE   3种类型 HTML 4.01 Strict 该

怎样做一个搜索网站之前期设计(一)

大学做毕业设计的时候开始接触到搜索这个方向,当时做了一个很简单的爬虫然后将博客园的数据爬到了自己的数据库里,又download了博客园整个网站的样式,弄了一个作品交给老师混了优-.现在回过头来发现当时自己做的那个爬虫和设计都十分简陋,从时间和空间上来看,都跟菜鸟一样,飞不高.爬不快,而且时不时就挂掉了,而且还是单线程的(虽然当时有弄多线程,但是晕晕萌萌,也不知道效率怎么样),所以自那以后就一直惦记着要重新弄一个. 但是,当我在思考要怎么设计一个爬虫时(如今我已是毕业一年,参加工作一年了),我发现

niubi-job:一个分布式的任务调度框架设计原理以及实现

niubi-job的框架设计是非常简单实用的一套设计,去掉了很多其它调度框架中,锦上添花但并非必须的组件,例如MQ消息通讯组件(kafka等).它的框架设计核心思想是,让每一个jar包可以相对之间独立的运行,并且由zk辅助进行集群中任务的调度. 接下来,咱们就一步一步的来看下niubi-job整个的框架设计与实现. 框架设计概述 讲解之前,让我们先来看一张niubi-job的框架设计图.如下: 可以看到,该图的结构非常简单,只有四个部分组成. web控制台:负责发布任务,监控任务的状态信息,上传

怎样设计一个好玩的游戏——游戏设计的艺术

前言: 一个好玩的游戏,就是要让玩家在玩游戏的过程中感到愉快的游戏体验.游戏品质一般可以分为三个层次:普通.精品.经典. 仅仅要游戏能赚钱的好游戏可算是精品游戏,而经典的游戏,必定有深厚的游戏内涵,甚至可以从这个游戏产生周边产品:比如从游戏改编电影.玩具等等,有额外附加值.一个游戏的好坏由多方面决定,这里我们仅仅关注趣味性.其实趣味性是一个游戏最重要的部分.游戏画面优美程度,或者玩家可玩时间,或者角色的主角的乳房部位多边形数目,这些都是其次. 1.给予玩家目标(让玩家入局) 游戏開始的时候,我们

推荐一个iOS应用UI界面设计站点

Patterns是一个分享ios应用UI界面的站点,专注于分享iOS应用UI界面的细节.依照设计元素进行分类,依照iOS经常使用功能对各类UI进行分类展示. 链接:url=http%3A%2F%2Fwww.patternsofdesign.co.uk%2F&link2key=fa4fb02efb" target="_blank" rel="nofollow" style="color:rgb(173,98,85); text-decora

今天胡乱看看到了一个很长时间的设计题,表达一下我的想法!有不足多多指教~~

设计任务:1.最近总有人骚扰我们的投票模块,需要你来设计一个投票限制的东东要求如下:1)要求每个QQ号码(假设此QQ号码在UNIT32 内可以表示)10分钟这内只能投5票.2)我们的用户很踊跃,平均每天要有2000万人左右通过此程序投票.说明:1)无需写代码,只需要图跟文字即可.2)对于关键逻辑,请用图加代码表示出来,这也是对你文字表达能力的一个考验.3)对你能想到的所有的边界条件列出来,这是对你逻辑思维全面与敏捷性的考验.4)存储部分,尽你所能吧.如果,你需要一个自己设计的存储层,那么把这个存