thunk (转)

thunk 在网络词典上解释为:形实转换程序或替换程序。那么到底如何转换?如何替换呢?

其实可以把 thunk 理解为一小段代码,但这段代码并不是静态编译在程序的代码段中的,而是在程序运行过程中自动生成的一段代码,然后让程序在合适的时机去执行这段代码。

下面是一个替换函数参数的 thunk 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <windows.h>
#include <iostream>
 
// 定义一个函数指针。原型和 API 函数 MessageBoxA 函数的原型完全一样
typedef int (WINAPI *TEST)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);
 
// 按字节对齐,因为此结构中保存的是一段机器码
#pragma pack(push,1)
// 此 thunk 代码的功能是用新的字符串的地址替换原先的字符串的地址
// 并跳转到 MessageBoxA 函数处执行
struct Thunk
{
    DWORD   m_mov;         
    DWORD   m_pStr;        
    BYTE    m_jmp;         
    DWORD   m_relproc;     
 
    // 初始化后 thunk 代码执行以下功能
    // 1. 把地址 [esp + 0x08] 处的内容替换为参数 pStr 的值
    // 2. 跳转到 MessageBoxA 函数
    BOOL Init(char* pStr) 
    {  
        m_mov = 0x082444C7;  // mov dword ptr [esp+0x8] esp+8处是函数参数 lpText
        m_pStr = PtrToUlong(pStr);  
        m_jmp = 0xe9;   // jmp
        m_relproc = DWORD((INT_PTR)::MessageBoxA - ((INT_PTR)this+sizeof(Thunk)));  
 
        // 不调用此函数程序也能正常执行,但最好是调用一下
        ::FlushInstructionCache(::GetCurrentProcess(), this, sizeof(Thunk));  
        return TRUE;  
    }  
};
#pragma pack(pop)
 
CHAR love[] = "I love you!";
 
int main()
{
    // 在堆栈中生成 thunk 代码
    Thunk thunk;
    thunk.Init(love);
 
    TEST MyMessageBox = (TEST)&thunk;
 
    // 注意 "I hate you!" 会被 thunk 替换为 "I love you!"
    // 此时并不会弹出"I hate you!"而是弹出"I love you!"
    MyMessageBox(NULL,"I hate you!",NULL,0);
}

要彻底理解以上代码的实现原理,必须对汇编语言有所了解。

首先看第47行代码。MyMessageBox 是一个函数指针,此行代码在编译后会产生汇编代码把四个参数从右向左(stdcall) push 到堆栈中然后再跳转到 MyMessageBox 所指向的内存位置处继续执行。在把四个参数 push 到堆栈后,内存地址[ESP + 8](ESP为栈顶指针) 处保存的就是字符串"I hate you!"的地址。而 MyMessageBox 所指向的就是我们的 thunk 代码。

跳转到 thunk 代码处后,首先用字符串"I love you!"的地址替换掉了堆栈中保存的"I hate you!"的地址,然后跳转到 MessageBoxA 函数中执行。注意这里执行函数 MessageBoxA 的代码使用的是“跳转”而不是“调用”。在 MessageBoxA 函数中有 ret 指令,当执行到此指令后程序将返回到第 48 行代码处继续往下执行。

再看第26行代码。此行代码是计算要跳转到的地址相对于当前地址的偏移量,格式是:目标地址 - EIP 中的当前地址。目标地址当然就是函数 MessageBoxA 的地址,而EIP中当前的地址是紧跟结构变量 thunk 之后的那个字节的地址,即:(INT_PTR)this+sizeof(Thunk)。

原文地址:https://www.cnblogs.com/winkyao/p/2497615.html