社区
新手乐园
帖子详情
Thunk是什么意思,各位前辈
rocksolid
2006-03-16 12:08:41
Thunk是什么意思,各位前辈
...全文
496
8
打赏
收藏
Thunk是什么意思,各位前辈
Thunk是什么意思,各位前辈
复制链接
扫一扫
分享
转发到动态
举报
写回复
配置赞助广告
用AI写文章
8 条
回复
切换为时间正序
请发表友善的回复…
发表回复
打赏红包
logoes
2006-03-20
打赏
举报
回复
mark
rocksolid
2006-03-16
打赏
举报
回复
我看的那本书中,好象是说32位访问16位的问题,Thunk——好象是指一种转换。
mouse_cute
2006-03-16
打赏
举报
回复
Thunk技术
在MFC中,一般都是一个窗口一个类,比如实现一个最基本的窗口类CMyWnd,你一定会把窗口过程作为这个类的成员函数,但是使用WINAPI创建窗口时必须注册类WNDCLASS,里面有个成员数据lpfnWndProc需要WNDPROC的函数指针,一般想法就是把窗口类的消息处理函数指针传过去,但是类成员函数除非是静态的,否则无法转换到WNDPROC,而全局的消息处理函数又无法得到窗口类对象的指针。
用thunk的时候, 它记录了窗口类对象的this指针,并且这组指令可以当作函数,也可以是窗口过程来使用。thunk先把窗口对象this指针记录下来,然后转向到静态stdProc回调函数,转向之前先记录HWND,然后把堆栈里HWND的内容替换为this指针,这样在stdProc里就可以从HWND取回对象指针,定位到WindowProc了。
比如窗口过程函数定义:
LRESULT WINAPI WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
当我们的窗口类CMyWnd创建窗口的时候,窗口句柄是可以得到并且作为成员数据保存,如此一来,第一个参数hWnd是可以不要的,因为可以通过this->m_hWnd得到,hWnd其实质是一个指针,如果把这个参数替换为窗口类对象的this指针,就可以通过(CMyWnd*)hWnd->WindowProc转到窗口类内部的窗口过程了。
先看系统调用m_thunk时的堆栈(从右到左依次压栈,最后把返回地址压栈):
ret top
HWND
MSG
WPARAM
LPARAM bottom
我们只要在系统调用窗口过程时修改堆栈,把其中的hWnd参数替换掉就行了。这时thunk技术就有用武之地了.
先定义一个结构:
#pragma pack(push, 1) // 该结构必须以字节对齐
struct Thunk
{
BYTE Call;
int Offset;
WNDPROC Proc;
BYTE Code[5];
CMyWnd* Window;
BYTE Jmp;
BYTE ECX;
};
#pragma pack(pop)
窗口类定义
class CMyWnd
{
public:
BOOL Create(...);
LRESULT WINAPI WindowProc(UINT,WPARAM,LPARAM);
static LRESULT WINAPI InitProc(HWND,UINT,WPARAM,LPARAM);
static LRESULT WINAPI stdProc(HWND,UINT,WPARAM,LPARAM);
WNDPROC CreateThunk();
WNDPROC GetThunk(){return m_thunk;}
...
private:
WNDPROC m_thunk;
};
在创建窗口的时候把窗口过程设定为this->m_thunk,m_thunk的类型是WNDPROC,因此是完全合法的,当然这个m_thunk还没有初始化,在创建窗口前必须初始化:
WNDPROC CMyWnd::CreateThunk()
{
Thunk* thunk = new Thunk;
///////////////////////////////////////////////
//
//系统调用m_thunk时的堆栈:
//ret HWND MSG WPARAM LPARAM
//-------------------------------------------
//栈顶 栈底
///////////////////////////////////////////////
//call Offset
//调用code[0],call执行时会把下一条指令压栈,即把Proc压栈
thunk->Call = 0xE8; // call [rel]32
thunk->Offset = (size_t)&(((Thunk*)0)->Code)-(size_t)&(((Thunk*)0)->Proc); // 偏移量,跳过Proc到Code[0]
thunk->Proc = CMyWnd::stdProc; //静态窗口过程
//pop ecx,Proc已压栈,弹出Proc到ecx
thunk->Code[0] = 0x59; //pop ecx
//mov dword ptr [esp+0x4],this
//Proc已弹出,栈顶是返回地址,紧接着就是HWND了。
//[esp+0x4]就是HWND
thunk->Code[1] = 0xC7; // mov
thunk->Code[2] = 0x44; // dword ptr
thunk->Code[3] = 0x24; // disp8[esp]
thunk->Code[4] = 0x04; // +4
thunk->Window = this;
//偷梁换柱成功!跳转到Proc
//jmp [ecx]
thunk->Jmp = 0xFF; // jmp [r/m]32
thunk->ECX = 0x21; // [ecx]
m_thunk = (WNDPROC)thunk;
return m_thunk;
}
这样m_thunk虽然是一个结构,但其数据是一段可执行的代码,而其类型又是WNDPROC,系统就会忠实地按窗口过程规则调用这段代码,m_thunk就把Window字段里记录的this指针替换掉堆栈中的hWnd参数,然后跳转到静态的stdProc:
//本回调函数的HWND调用之前已由m_thunk替换为对象指针
LRESULT WINAPI CMyWnd::stdProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{
CMyWnd* w = (CMyWnd*)hWnd;
return w->WindowProc(uMsg,wParam,lParam);
}
这样就把窗口过程转向到了类成员函数WindowProc,当然这样还有一个问题,就是窗口句柄hWnd还没来得及记录,因此一开始的窗口过程应该先定位到静态的InitProc,CreateWindow的时候给最后一个参数,即初始化参数赋值为this指针:
CreateWindowEx(
dwExStyle,
szClass,
szTitle,
dwStyle,
x,
y,
width,
height,
hParentWnd,
hMenu,
hInst,
this //初始化参数
);,
在InitProc里面取出该指针:
LRESULT WINAPI CMyWnd::InitProc(HWND hWnd,UINT uMsg,UINT wParam,LONG lParam)
{
if(uMsg == WM_NCCREATE)
{
CMyWnd *w = NULL;
w = (CMyWnd*)((LPCREATESTRUCT)lParam)->lpCreateParams;
if(w)
{
//记录hWnd
w->m_hWnd = hWnd;
//改变窗口过程为m_thunk
SetWindowLong(hWnd,GWL_WNDPROC,(LONG)w-CreateThunk());
return (*(WNDPROC)(w->GetThunk()))(hWnd,uMsg,wParam,lParam);
}
}
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
窗口过程转发流程:
InitWndProc -> window->m_thunk -> stdProc -> window->WindowProc
iambic
2006-03-16
打赏
举报
回复
可以用于C++动态实现。
iambic
2006-03-16
打赏
举报
回复
好像是一个内存slot,存放着函数指针和对象指针。
rocksolid
2006-03-16
打赏
举报
回复
Thunk(形实转换?)是可以让32位处理(即从32位程序中调用的32位DLL)来执行16位处理(即16位DLL)的一段代码——或者反向。
rocksolid
2006-03-16
打赏
举报
回复
看上篇的最后一段,有个THUNK。
rocksolid
2006-03-16
打赏
举报
回复
Win32到Win16的反传:
在很大程度上,Windows9x中的Win32 API所做的事情,仅仅是简单的将它反传回Win16中去执行,读者一定会很奇怪为什么微软当初要这么做呢?原因主要有两个:首先是16位的代码要比32位的代码小,当初Windows95设计的目标是能够在4MB的机器上运行,将KERNEL32,USER32、GDI32的代码全部重写的话,代码的尺寸将要大上几百KB,这在4MB的机器上是不可接受的——完成相同的工作竟然有两套代码!二是Win16的代码已经发布很长时间并且在全世界范围内被广泛测试,如果重写成32位代码的,需要做大量的工作并且需要重新做长时间的测试,这会推迟Windows95的发布——这可是比尔盖茨最不愿意见到的事情J
所以反传回Win16成为了Win32设计的一个很重要的特点,但是可惜的是,Win16的多任务设计是有缺陷的,它有很多的代码都是不允许重入的。而Win32程序设计里面,多任务、多线程、并发等等都是家常便饭,怎么解决这个问题呢?
答案是Win16Mutex。所谓的Win16Mutex就是当进入不可重入的Win16代码的时候,就设置这个互斥量,让别的也需要执行Win16关键代码的线程暂时停止并等待,知道上一个Win16Mutex被释放为止。
这样造成的一个问题是,如果某个进程在进入Win16Mutex之后发生错误,那么就将导致整个系统的崩溃,不管是不是Win32进程,Win32进程因为反传也需要调用到Win16Mutex。
从Win32到Win16的反传也是很复杂的,主要使用的技术就是Thunk代码(通过JMP远跳转的一小段汇编代码),联想到前面的复杂的VMM到DOS的反传,就不难想象为什么Windows9x系列与WindowsNT系列相比,是那么的容易崩溃。反传达到了微软设计Windows9x的目标,兼容DOS和Windows3.1,同时又足够小,足够快,可惜,反传同时也是Windows9x不怎么稳定的最重要原因。
如何面对高水平的破解组织 谈暴力破解应对
这句脚本的意思为,&H400000为读取内存地址400000中数值;2为读取类型,为长整数;xue则把读取出来的数值保存到xue这个变量中去。 如果这个400000的地址是用来储存血量的。那我们就可以对xue这个变量来进行判断计算...
操作系统关于中断的实现
这种动态生成的代码实际上是一个
thunk
,可以参见前面关于
thunk
的那个文章,源代码:KeInitializeInterrupt。x86的hal中以及 kernel的x86部分,很多代码是Shie-Lin Tzong(shielint)写的,中文名叫宗世麟,台大资讯系...
用批处理调用API实现文件下载
(ZV友情提示:下面是最枯燥的部分,各位手握锥子,要有一不怕苦,二不怕痛的精神看完它....) (如果定力不高的朋友,或者堆PE文件再熟悉不过的朋友,可以字节转到"JMP S1"处往下看.) (如果只想知道到底怎么回事,或者对...
在ANSI C下设计和实现简便通用的signal-slot机制
其实用C开发这个问题也同样存在,只是C语言中我们有绝招——我已经记不清楚什么时候听说过的这个名字了——那就是“全局变量大法”,相当于把数据从小的集合中移到图中最大的那个矩形里,相信大伙都在“牛人”的代码...
react hooks+redux+immutable.js仿网易云音乐打造精美webApp
其实做这个项目出来效果虽然是还算自然,但是开发的过程是相当曲折的,我在后面也会做持续的分享,把我的开发过程遇到的挑战一五一十地分享给各位。最后,别忘了给这个项目点一个star哦,谢谢支持。 github源码地址...
新手乐园
33,311
社区成员
41,784
社区内容
发帖
与我相关
我的任务
新手乐园
C/C++ 新手乐园
复制链接
扫一扫
分享
社区描述
C/C++ 新手乐园
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章