以前听某大牛说过,学习wtl,如果不知道thunk,那么你的wtl就没学好。好吧,下面来看看什么是 传说中的 thunk。
这里只是演示了如何动态生成代码去执行,wtl将hwnd参数改成this指针,是同样的道理。
先贴一段代码:
#pragma pack(push,1)
struct ProgThunk
{
BYTE m_BJmp;
int m_reploc;
void Init(BYTE jmp, DWORD reploc)
{
m_BJmp = jmp;
m_reploc = reploc;
}
};
#pragma pack(pop)
typedef void (*fun)();
class TestC
{
public:
static void test()
{
cout<<"Hello world"<<endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
ProgThunk* progthunk;
if (IsProcessorFeaturePresent(PF_NX_ENABLED))
{
progthunk = (ProgThunk*)::VirtualAlloc(NULL,sizeof(ProgThunk),MEM_RESERVE | MEM_COMMIT,PAGE_EXECUTE_READWRITE);
}
else
{
progthunk = new ProgThunk;
}
TestC testa;
int A = (int)testa.test;
int B = sizeof(ProgThunk);
int C = (int)progthunk;
int reploc = A-B-C;
progthunk->Init(0xe9, reploc);
FlushInstructionCache(GetCurrentProcess(), progthunk, sizeof(ProgThunk));
fun testfun = (fun)(progthunk);
testfun();
return 0;
}
看了上面的代码,明白的人自然一看就知道,不明白的人却比较畏惧。我做点简单的解释:
struct ProgThunk 这里定义了一个结构体,也就是一个代码块,里面只有一个字节和一个整形(我知道应该用无符号数,用int可能不能表示4G空间,不过重点不再这里),那个字节后面会初始化成一条jmp指令,也就是0xe9,后面那个int会初始化成一个相对跳转位置,于是,这个代码块就可以跳转到指定的位置去执行代码了。
开始我调试的时候总是报错,访问地址越界,后来突然想到,代码页的读写与可执行的问题,就改成virtualalloc了的方式来分配内存了,果真就OK了。
顺便说一下,FlushInstructionCache不调用也可以执行成功,但是你不能保证所有情况下都成功,万一在某种条件下CPU繁忙,并没有将你的指令回写到内存中,那么,你再调用thunk代码将会造成不可预知的结果