Thunk——形实转换程序

今天,第一次玩博客,先转载一篇优秀的文章,分享给大家。以后会陆续地将收集到的优秀文章转载到这儿,和大家一起学习。等有能力了,再贴出自己的原创作品。


今天的主题是Thunk——形实转换程序。


研究Thunk的起因,是因为在IDA中时常会看到Attributes : thunk,它经常被标准在某些函数的头部。

至于Thunk出现的原因及作用,下面这篇文章已经讲述的十分详细了,所以,我就不废话了,直接给出原文。


原文链接:

http://www.cnblogs.com/homeofish/archive/2009/02/20/1395208.html

标题:也说说Thunk 

面向对象是个好东西,用接近世界的方式抽象程序世界,直观。

全局函数(或许我应该特指Windows API)也是好东西,要什么调什么,毫不含糊.

那么,当他们走到一起,矛盾就产生了.

类时刻保护着自己的成员,以至于为每一个方法加入一个指向自己的指针.


比如有以下类

1 class TestClass()
2 {
3 void Func();
4 };


则Func被编译器安插了this以针,以便Func内部可以访问类TestClass的成员变量,即Func变为如下样子:


void Func(TestClass* this); 



在实际的开发中,使用API时常常会要求我们提供回调函数,比如SetTimer,我们需要设置向这个API提供一个如下类型的函数指针:


typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD); 



假如我们有如下类

1   class TestClass()
2   {
3       void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )
4      {
5         //do something
6      }
7   };
8
9


并希望将成员函数


void OnTimeProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );



设为API:


1  UINT_PTR
2  WINAPI
3  SetTimer(
4  __in_opt HWND hWnd,
5  __in UINT_PTR nIDEvent,
6  __in UINT uElapse,
7  __in_opt TIMERPROC lpTimerFunc);
8
9

的第四个参数,以便定时器的时间到时,我们的类成员函数TestFunc:OnTimerProc被调用。




根据最前面对Func的分析,在编译时,OnTimerProc会被安插this指针,变成如下形式:


void OnTimeProc(TestClass *this, HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );


很显然,我们无法直接设置。




那我们应该怎么做呢,在完成这个任务之前,让我们先看一下一个稍简单一点的例子,用以说明Thunk原理。

Thunk的原理其实说起来很简单:巧妙的将数据段的几个字节的数据设为特殊的值,然后告诉系统,这几个字节的数据是代码(即将一个函数指针指向这几个字节的第一个字节),让系统来执行。

这样说起来就很简单.

相信对于后一个操作:将一个函数指针指向这几个字节的第一个字节我们都应该会:



比如有结构体:

1   typedef struct thunk
2   {
3    DWORD dwMovEsp;
4    DWORD dwThis;
5    BYTE bJmp;
6    DWORD dwRealProc;
7
8  }THUNK;
9


函数指针:

typedef void (*FUNC)(DWORD dwThis); 




则如下代码将一个thunk的结构体强转为FUNC型的函数指针:


1   THUNK testThunk;
2
3   FUNC fun = (FUNC)&testThunk;
4
5   fun(NULL);//先设为NULL
6


这样,系统便会把testThunk所指向的内存加载到缓冲中。 



现在的问题是是将这个结构体设为多少比较好?

在x86 指令集中,我们可以查到:

汇编指令JMP为0xe9

所以,我们写下如下函数用于设置这个结构体的值:



1    void Init(DWORD proc,void* pThis)
2    {
3       dwMovEsp = 0x042444C7; //C7 44 24 04
4 dwThis = (DWORD)pThis;
5 bJmp = 0xe9;
6 dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(thunk)));
7 FlushInstructionCache(GetCurrentProcess(),this,sizeof(thunk));
8    }


前两行用于将pThis指针压栈,接下来的两句用于设置跳转的相对地址。最后一个是更新缓存(说实话,我个人觉得这句在这种情况下是可有可无的,但也可能是我认识不够深,望指教)。




整个代码如下:

Code
1 #include "stdafx.h"
2 #include "wtypes.h"
3
4 #include <iostream>
5 using namespace std;
6
7 typedef void (*FUNC)(DWORD dwThis);
8 void JmpedFun(DWORD dwThis);
9
10 #pragma pack(push,1)
11 typedef struct tagTHUNK
12 {
13 DWORD dwMovEsp;
14 DWORD dwThis;
15 BYTE bJmp;
16 DWORD dwRealProc;
17
18 void Init(DWORD proc,void* pThis)
19 {
20 dwMovEsp = 0x042444C7; //C7 44 24 04
21 dwThis = (DWORD)pThis;
22 bJmp = 0xe9;
23 dwRealProc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(THUNK)));
24 FlushInstructionCache(GetCurrentProcess(),this,sizeof(THUNK));
25 }
26 }THUNK;
27 #pragma pack(pop)
28
29 class Test
30 {
31 public:
32 THUNK m_thunk;
33 int m_nTest;
34
35 //构造函数中初始化为3,仅为测试,以便查看外面的方法JmpedTest是否可以正确取得这个值
36 Test() : m_nTest(3)
37 {}
38
39 void TestThunk()
40 {
41 m_thunk.Init((DWORD)JmpedFun,this);
42 FUNC f = (FUNC)&m_thunk;
43 f(1);
44
45 cout << "Test::fun()" << endl;
46 }
47
48 int GetTestValue()
49 {
50 return m_nTest;
51 }
52 };
53
54 void JmpedFun(DWORD dwThis)
55 {
56 cout << "JmpFun access the Test class's data member:" << ((Test*)dwThis)->GetTestValue() << endl;
57 }
58
59 int _tmain(int argc, _TCHAR* argv[])
60 {
61 Test t;
62 t.TestThunk();
63 system("pause");
64 return 0;
65 }
66
67

测试成功,接下来是将thunk技术应用到实际中,就是一开始提出的问题。


首先,要使用定时器的功能,肯定要调用API:SetTimer,而调用这个API需要一个如下签名的函数指针:

typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);


因此,我们要做的,就是利用Thunk技术,让这个回调函数调用我们的类的成员方法。

我们可以用一个代理类来完成这一系列的工作,然后我们的真正的业务逻辑类就继承自这个代理类。 




现在想想这个代理类要完成这个任务需要那些数据?

首先,他要知道当他被API回调时,他应该调用哪一个类的成员方法,类的面员方法的函数指针时需要指定类类型。如下所示:


void (Base:: * )( HWND , UINT , UINT , DWORD );

看到这里,相信任何一个初级的刚入门的c++程序员都可以快速的写下以下类:



1 class SimpleTest;
2 class SimpleTimerAdapter
3 {
4 public:
5 CALLBACKThunk thunk;
6 typedef void (SimpleTest::*func)(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
7 typedef func MemberCallBackType;
8 MemberCallBackType mTimerProc;
9
10 void Init(TIMERPROC proc, void* pThis,int nPos = 0)
11 {
12 assert(pThis != NULL);
13 if(pThis)
14 {
15 thunk.m_mov = 0x042444C7; //C7 44 24 04, here 04 is first param ,08 is second
16 thunk.m_this = (DWORD)pThis;
17 thunk.m_jmp = 0xe9;
18 thunk.m_relproc = (int)proc - ((int)this + sizeof(CALLBACKThunk));
19 }
20 }
21
22 TIMERPROC MakeCallback(MemberCallBackType lpfn,void* pThis, int nPos = 0)
23 {
24 assert(pThis);
25 if (pThis)
26 {
27 Init(DefaultCallBackProc, pThis ,nPos);
28 mTimerProc = lpfn;
29 return (TIMERPROC)&thunk;
30 }
31 return NULL;
32 }
33
34 UINT_PTR SetTimer(UINT uElapse, MemberCallBackType lpTimerFunc)
35 {
36 return ::SetTimer(NULL, 0, uElapse, MakeCallback(lpTimerFunc,this));
37 }
38
39 BOOL KillTimer(UINT_PTR uIDEvent)
40 {
41 return ::KillTimer(NULL, uIDEvent);
42 }
43
44 static void CALLBACK DefaultCallBackProc( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )
45 {
46 (BaseType(hwnd)->*MemberFuncType(hwnd))(0, uMsg, idEvent, dwTime);
47 }
48
49 static SimpleTest* BaseType(void* pThis)
50 {
51 return reinterpret_cast<SimpleTest*>(pThis);
52 }
53
54 template <class T>
55 static MemberCallBackType MemberFuncType(T pThis)
56 {
57 return reinterpret_cast<SimpleTest*>(pThis)->mTimerProc;
58 }
59 };
60
61
62
63
64
65 class SimpleTest : public SimpleTimerAdapter
66 {
67 public:
68 bool mQuit;
69
70 void TimerProc2(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )
71 {
72 mQuit = true;
73 KillTimer( idEvent);
74 printf("good! %d\n", idEvent);
75 }
76 };
77
78
79
80


然后在MAIN中写下测试代码:


1 int main(void)
2 {
3 SimpleTest a;
4 a.mQuit = false;
5 SetTimer(NULL, 0, 1000, a.MakeCallback(&SimpleTest::TimerProc2,&a));
6
7 MSG msg;
8 while(!a.mQuit && GetMessage(&msg, 0, 0, 0) )
9 {
10 printf("before dispatch!\n");
11 DispatchMessage(&msg);
12 }
13
14 system("pause");
15 return 0;
16 }
17
18


以上方法确实可以完成任务,但仅限于完成这一个任务而已,

甚至,在一个类中SimpleTimeAdapter中写出了这样的代码:


Code
1 typedef void (SimpleTest::*func)(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime );
2

我写出这段代码只是为了更清楚的显示代理类是如何工作的,除此之外,以上代码,没有任何作用,为真正的纯垃圾代码


为了抽象出一个中间代理类,我们需要用到模板,对于上面提到的定义问题,用模板可以很轻松的解决。同时,把最基本的内容从代理类中抽象出来。于是得到以下三个类:



1 /**//*
2 * class Base,最终的功能类,目地的要跳转到Base的成员函数中去
3 * class Impl,中间类,界于Adapt与Base之间
4 * MemberCallBackType Base的成员函数
5 * CallBackType,我们给API的回调函数
6 */
7template <class Base, class Impl, class MemberCallBackType, class CallBackType>
8 class CallBackAdapter
9 {
10 protected:
11 typedef CallBackAdapter<Base, Impl, MemberCallBackType, CallBackType> SelfType;
12 typedef MemberCallBackType BaseMemberCallBackType;
13
14 CALLBACKThunk thunk;
15
16 void Init(CallBackType proc, SelfType* pThis,int nPos = 0)
17 {
18 thunk.m_mov = 0x042444C7; //C7 44 24 04, here 04 is first param ,08 is second
19 thunk.m_this = (DWORD)pThis;
20 thunk.m_jmp = 0xe9;
21 thunk.m_relproc = (int)proc - ((int)this + sizeof(CALLBACKThunk));
22 }
23
24 CallBackType _CallBackProcAddress(void){
25 return (CallBackType)&thunk;
26 }
27 public:
28 template <class T>
29 static Base* BaseType(T pThis){
30 return reinterpret_cast<Base*>(pThis);
31 }
32
33 template <class T>
34 static MemberCallBackType MemberFuncType(T pThis){
35 return reinterpret_cast<SelfType*>(pThis)->mTimerProc;
36 }
37
38 MemberCallBackType mTimerProc;
39
40 operator CallBackType(){
41
42 Init(&Impl::DefaultCallBackProc, this);
43 mTimerProc = &Base::TimerProc;
44 return (CallBackType)&thunk;
45 }
46 CallBackType MakeCallback(MemberCallBackType lpfn,int nPos = 0){
47
48 Init(&Impl::DefaultCallBackProc, this,nPos);
49 mTimerProc = lpfn;
50 return (CallBackType)&thunk;
51 }
52 };
53
54
55
56
57
58
59
60 template <class Base>
61 class TimerAdapter : public CallBackAdapter<
62 Base,
63 TimerAdapter<Base>,
64 void (Base:: * )( HWND , UINT , UINT , DWORD ),
65 void (CALLBACK *)( HWND , UINT , UINT , DWORD )>
66 {
67 public:
68 typedef typename TimerAdapter<Base>::BaseMemberCallBackType MemCallBackType;
69
70 UINT_PTR SetTimer(UINT uElapse, MemCallBackType lpTimerFunc)
71 {
72 return ::SetTimer(NULL, 0, uElapse, MakeCallBackProc(lpTimerFunc));
73 }
74
75 BOOL KillTimer(UINT_PTR uIDEvent)
76 {
77 return ::KillTimer(NULL, uIDEvent);
78 }
79
80 static void CALLBACK DefaultCallBackProc( HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime )
81 {
82 (BaseType(hwnd)->*MemberFuncType(hwnd))(0, uMsg, idEvent, dwTime);
83 //(Base*)(hwnd)->*(reinterpret_cast<Base*>(hwnd)->mTimerProc)(0, uMsg, idEvent, dwTime);
84 }
85
86 };
87
88



测试代码如下:


1 int main(void)
2 {
3 Test a;
4 printf("timer id is %d", a.SetTimer(100, &Test::TimerProc2));
5 a.mQuit = false;
6 SetTimer(NULL, 0, 100, a.MakeCallback(&Test::TimerProc2));
7
8 //SimpleTest a;
9 //a.mQuit = false;
10 //SetTimer(NULL, 0, 1000, a.MakeCallback(&SimpleTest::TimerProc2,&a));
11
12 MSG msg;
13 while(!a.mQuit && GetMessage(&msg, 0, 0, 0) )
14 {
15 printf("before dispatch!\n");
16 DispatchMessage(&msg);
17 }
18
19 system("pause");
20 return 0;
21 }
22


参考:

ATL Under the HOOK Part 5 : http://www.codeproject.com/KB/atl/atl_underthehood_5.aspx

还有一篇也是CodeProject上的,但由于看文章的时间太久了,今天再去找时没有找到。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值