用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, );
001D1001 8B 1D mov esi,dword ptr [__imp__GetProcessHeap@0 (1D2008h)]
001D1007 6A push
001D1009 6A push
001D100B FF D6 call esi
001D100D push eax
001D100E FF 1D call dword ptr [__imp__HeapAlloc@12 (1D2000h)] __imp__GetProcessHeap@0:
001D2008 B9 E1 76 ; kernel32.dll!_GetProcessHeapStub@0 (76E114B9h) __imp__HeapAlloc@12:
001D2000 E0 77 ; ntdll.dll!_RtlAllocateHeap@12 (7755E046h)

;x64 code
LPVOID lpMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, );
000000013F141006 FF call qword ptr [__imp_GetProcessHeap (13F142010h)]
000000013F14100C BA mov edx,
000000013F141011 8B C8 mov rcx,rax
000000013F141014 8B C2 mov r8d,edx
000000013F141017 FF E3 0F call qword ptr [__imp_HeapAlloc (13F142000h)]
000000013F14101D 8B D8 mov rbx,rax __imp_GetProcessHeap:
000000013F142010 1A 00 ; kernel32.dll!GetProcessHeapStub (0000000077251A20h) __imp_HeapAlloc:
000000013F142000 3A 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如下:

// x86
VOID 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,
je l01_02
mov eax,ecx
l01_01: push dword [esp + eax * ]
loop l01_01
;------------------------------------------------
; 调用真正的API l01_02: call 0x12345678 ; API的相对地址,动态写入
push eax ; 备份返回值 ;------------------------------------------------
; 调用Log Function lea ecx,[esp + ]
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 + ],r9
mov [rbp + ],r8
mov [rbp + ],rdx
mov [rbp + ],rcx ;------------------------------------------------
; 栈上参数压栈,如果有的话 mov rcx,0x12345678 ; 参数个数,根据XML动态写入
cmp rcx,
jle l01_02
sub rcx,
l01_01: push qword [rbp + rcx * + ]
loop l01_01 l01_02: mov rcx,[rbp + ] ;------------------------------------------------
; 调用真正API sub rsp,20h
mov rax,0x123456789abcdef ; API地址,动态写入
call rax mov rsp,rbp
push rax ; 备份返回值 ;------------------------------------------------
; 调用Log Function lea r9,[rbp + ] ; 指向参数列表的指针
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 - ] 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,还没有经过太多测试,有兴趣的朋友可以测试一下。感谢阅读。

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

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

    #29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类T ...

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

    Axure RP是一个专业的快速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司:RP则是Rapid Prototyping(快速原型)的缩写. Axure简要介绍 Axur ...

  3. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

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

    Axure RP是一个专业的高速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司.RP则是Rapid Prototyping(高速原型)的缩写. Axure简要介绍 Axur ...

  5. Skywalking-02:如何写一个Skywalking trace插件

    如何写一个Skywalking trace插件 javaagent 原理 美团技术团队-Java 动态调试技术原理及实践 类图 实现 ConsumeMessageConcurrentlyInstrum ...

  6. K8 系统中省市县数据表的设计可以反映出什么? 通过一个基础业务表的设计品味软件系统的整体架构

    1:没有严谨的Id思想,不变化的Id思想,看不见的Id的思想. 2:数据不严谨,没有上下级关系,没有树形结构,ParentId 的思想. 3:表之间的关系都是弱关联,基础数据一修改业务数据就容易乱套. ...

  7. 资产信息之收集资产代码流程,API的一个认证,数据库表的设计

    收集资产代码流程 1.起初我们些的代码是面条式的一堆的逻辑判断.   后来通过了不断的优化升级实现了一个3种方案都支持的CMDB系统,我们用哪种方案只需要在配置文件里修改一下设置就行了.   同时我们 ...

  8. ios开发:一个音乐播放器的设计与实现

    github地址:https://github.com/wzpziyi1/MusicPlauer 这个Demo,关于歌曲播放的主要功能都实现了的.下一曲.上一曲,暂停,根据歌曲的播放进度动态滚动歌词, ...

  9. 【大型web架构】一个大型web系统架构设计和技术选型的讨论摘录

    1.数据库压力问题 所有的压力最终都会反映到数据库方面,一定要对数据库有一个整体的规划. 可以按照业务.区域等等特性对数据库进行配置,可以考虑分库.使用rac.分区.分表等等策略,确保数据库能正常的进 ...

随机推荐

  1. 奥利奥好吃吗?Android 8.0新特性适配测试报告来啦!

    WeTest 导读 谷歌2017 I/O开发者大会上发布了Android 8.0的正式版, 其官方代号为Oreo(奥利奥).网上关于Android8.0新功能特性的介绍已铺天盖地,新功能特性会对程序应 ...

  2. ios自定义数字键盘

    因为项目又一个提现的功能,textfiled文本框输入需要弹出数字键盘,首先想到的就是设置textfiled的keyboardType为numberPad,此时你会看到如下的效果:   但是很遗憾这样 ...

  3. 【转】缓存淘汰算法系列之3——FIFO类

    原文地址:http://www.360doc.com/content/13/0805/16/13247663_304923435.shtml 1 FIFO 1.1. 原理 按照“先进先出(First ...

  4. session和cookie作用原理,区别

    Cookie概念 在浏览某些 网站 时,这些网站会把 一些数据存在 客户端 , 用于使用网站 等跟踪用户,实现用户自定义 功能. 是否设置过期时间: 如果不设置 过期时间,则表示这个 Cookie生命 ...

  5. Pyhton编程(四)之基本数据类型-字符串详解

    一:字符串是什么? 字符串是Python最常用的一种数据类型,虽然看似简单,但能够以不同的方式来使用它们. 字符串就是一系列的字符,在Python中,用引号括起来的都是字符串,其中的引号可以是单引号, ...

  6. LeetCode 169. Majority Element (众数)

    Given an array of size n, find the majority element. The majority element is the element that appear ...

  7. LeetCode 75. Sort Colors(排序颜色)

    Given an array with n objects colored red, white or blue, sort them so that objects of the same colo ...

  8. 基于场景解析RecyclerView的回收复用机制原理

    最近在研究 RecyclerView 的回收复用机制,顺便记录一下.我们知道,RecyclerView 在 layout 子 View 时,都通过回收复用机制来管理.网上关于回收复用机制的分析讲解的文 ...

  9. IDEA 护眼色设置 背景行颜色取消等设置

    首先做一些简答的记录,护眼色 等等的设置很久以前机器上已经设置过了,今天偶尔要在其他机器上重新做一些设置反而忘记了很多步骤, 设置后的HTML页面如何所示: 默认情况下,当只是设置General通用的 ...

  10. Element ui表格展示图片问题

    当需要遍历图片时,不能直接使用prop绑定值,具体 代码如下 <el-table-column label="头像" width="100"> &l ...