N久前, 为了解决C++类成员函数的回调, 写了一段Thunk代码, 很长时间没发现有什么 BUG, 但后来在Windows XP SP2, Windows Server 2003 SP1上一运行就崩溃,不知其因。今 得其因,如下: 数据执行保护 (DEP) 是一套软硬件技术,能够在内存上执行额外检查以帮助防止在系统上 运行恶意代码。在 Microsoft Windows XP Service Pack 2、 Microsoft Windows Server 2003 Service Pack 1 、Microsoft Windows XP Tablet PC Edition 2005 和Windows Vista 中,由硬件和软件一起强制实施 DEP。 DEP 的主要优点是可以帮助防止数据页执行代码。通常情况下,不从默认堆和堆栈执行代码。 硬件实施 DEP 检测从这些位置运行的代码,并在发现执行情况时引发异常。软件实施 DEP 可帮助阻止恶意代码利用 Windows 中的异常处理机制进行破坏。 硬件实施 DEP 是某些 DEP 兼容处理器的功能,可以防止在已标记为数据存储区的内存区域 中执行代码。 此功能也称为非执行和执行保护。 Windows XP SP2 还包括软件实施 DEP,其 目的在于减少利用 Windows 中的例外处理机制的情况。 如何解决: 当然还没有解决,我们知道WTL也用了Thunk技术,于是,用WTl 生成一个Dialog程序,在 Microsoft Windows Server 2003 Service Pack2上,把DEP 设置成回默认的,运行一下Wtl 的 Dialog,可以跑起来,同样是用Thunk,这是什么原因呢,看ATL代码,原来在这里 result = IsProcessorFeaturePresent( 12 /*PF_NX_ENABLED*/ ); 确定处理器的特性 如果result 返回True thunkPage = VirtualAlloc(NULL, PAGE_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 用VirtualAlloc[PAGE_EXECUTE_READWRITE] 来分配Thunk 代码的内存, 到这里问题解决了. 写下这篇文档,希望使用Thunk技术的人,在代码中加上DEP 处理.下面贴上Thunk的代码 Thunk.h 复制代码 /* Autor - zhang luduo (张鲁夺) Email - zhangluduo@163.com MSN - zhangluduo@msn.com Copyright 2006-2009 zhang Luduo. All Rights Reserved. */ #ifndef _THUNK_H #define _THUNK_H // // type redefinition // typedef unsigned char u1byte; // an 8 bit unsigned character type typedef unsigned short u2byte; // a 16 bit unsigned integer type typedef unsigned long u4byte; // a 32 bit unsigned integer type typedef char s1byte; // an 8 bit signed character type typedef short s2byte; // a 16 bit signed integer type typedef long s4byte; // a 32 bit signed integer type typedef void* pvoid; // a 32 bit undefined type pointer class Thunk { public: Thunk(); virtual ~Thunk(); pvoid Thiscall(pvoid pThis, u4byte MemberFxnAddr); pvoid Stdcall (pvoid pThis, u4byte MemberFxnAddr); template static u4byte GetMemberFxnAddr(T MemberFxnName) { union { T From; u4byte To; } union_cast; union_cast.From = MemberFxnName; return union_cast.To; } private: #pragma pack (push, 1) typedef struct _BYTECODE_THISCALL { u1byte Mov; // 0xB9 u4byte This; // this pointer u1byte Jmp; // 0xE9 u4byte Adrr; // relative jmp } BYTECODE_THISCALL, *PBYTECODE_THISCALL; typedef struct _BYTECODE_STDCALL { u1byte Push[3]; // push dword ptr[esp] ; u4byte Move; // mov dword ptr [esp + 4], ?? ?? ?? ?? ; u4byte This; // this pointer u1byte Jmp; // 0xE9 u4byte Adrr; // relative jmp } BYTECODE_STDCALL, *PBYTECODE_STDCALL; #pragma pack (pop) // u1byte m_BytecodeThiscall[10]; // u1byte m_BytecodeStdcall [16]; BYTECODE_THISCALL m_BytecodeThiscall; BYTECODE_STDCALL m_BytecodeStdcall; Thunk* m_pThis; }; #endif // #define _THUNK_H Thunk.cpp 复制代码 /* Autor - zhang luduo (张鲁夺) Email - zhangluduo@163.com MSN - zhangluduo@msn.com Copyright 2006-2009 zhang Luduo. All Rights Reserved. */ //#include "StdAfx.h" #include "Thunk.h" #ifndef _INC_WINDOWS # include <windows.h> #endif Thunk::Thunk() { m_pThis = (Thunk*)VirtualAlloc(NULL, sizeof (Thunk), MEM_COMMIT, PAGE_EXECUTE_READWRITE); } Thunk::~Thunk() { if (NULL == m_pThis) return; VirtualFree(m_pThis, 0, MEM_RELEASE); } pvoid Thunk::Thiscall(pvoid pThis, u4byte MemberFxnAddr) { /** encoded machine instruction equivalent assembly languate notation --------------------------- ------------------------------------- B9 ?? ?? ?? ?? mov ecx, pThis ; load ecx with this pointer E9 ?? ?? ?? ?? jmp target addr ; jump to target function */ /** #define GETBYTE(b, n) ((u1byte)(b >> ((n - 1) * 8) & 0x000000FF)) u4byte dwJmpAdr = MemberFxnAddr - (u4byte)(&(m_pThis->m_BytecodeThiscall)) - sizeof (m_pThis->m_BytecodeThiscall); m_pThis->m_BytecodeThiscall[0] = 0xB9; m_pThis->m_BytecodeThiscall[1] = GETBYTE((u4byte)pThis, 1); m_pThis->m_BytecodeThiscall[2] = GETBYTE((u4byte)pThis, 2); m_pThis->m_BytecodeThiscall[3] = GETBYTE((u4byte)pThis, 3); m_pThis->m_BytecodeThiscall[4] = GETBYTE((u4byte)pThis, 4); m_pThis->m_BytecodeThiscall[5] = 0xE9; m_pThis->m_BytecodeThiscall[6] = GETBYTE((u4byte)dwJmpAdr, 1); m_pThis->m_BytecodeThiscall[7] = GETBYTE((u4byte)dwJmpAdr, 2); m_pThis->m_BytecodeThiscall[8] = GETBYTE((u4byte)dwJmpAdr, 3); m_pThis->m_BytecodeThiscall[9] = GETBYTE((u4byte)dwJmpAdr, 4); FlushInstructionCache(GetCurrentProcess(), &(m_pThis->m_BytecodeThiscall), sizeof (m_BytecodeThiscall)); return &(m_pThis->m_BytecodeThiscall); */ m_pThis->m_BytecodeThiscall.Mov = 0xB9; m_pThis->m_BytecodeThiscall.This = (u4byte)pThis; m_pThis->m_BytecodeThiscall.Jmp = 0xE9; m_pThis->m_BytecodeThiscall.Adrr = MemberFxnAddr - (u4byte)(&(m_pThis->m_BytecodeThiscall)) - sizeof (BYTECODE_THISCALL); FlushInstructionCache(GetCurrentProcess(), &(m_pThis->m_BytecodeThiscall), sizeof (BYTECODE_THISCALL)); return &(m_pThis->m_BytecodeThiscall); } pvoid Thunk::Stdcall (pvoid pThis, u4byte MemberFxnAddr) { /** encoded machine instruction equivalent assembly languate notation --------------------------- ------------------------------------- FF 34 24 push dword ptr [esp] ; save (or duplicate) ; the return Address into stack C7 44 24 04 ?? ?? ?? ?? mov dword ptr [esp+4], pThis ; overwite the old ; return address with 'this pointer' E9 ?? ?? ?? ?? jmp target addr ; jump to target function */ /** #define GETBYTE(b, n) ((u1byte)(b >> ((n - 1) * 8) & 0x000000FF)) u4byte dwJmpAdr = MemberFxnAddr - (u4byte)(&(m_pThis->m_BytecodeStdcall)) - sizeof (m_pThis->m_BytecodeStdcall); m_pThis->m_BytecodeStdcall[ 0] = 0xFF; m_pThis->m_BytecodeStdcall[ 1] = 0x34; m_pThis->m_BytecodeStdcall[ 2] = 0x24; m_pThis->m_BytecodeStdcall[ 3] = 0xC7; m_pThis->m_BytecodeStdcall[ 4] = 0x44; m_pThis->m_BytecodeStdcall[ 5] = 0x24; m_pThis->m_BytecodeStdcall[ 6] = 0x04; m_pThis->m_BytecodeStdcall[ 7] = GETBYTE((u4byte)pThis, 1); m_pThis->m_BytecodeStdcall[ 8] = GETBYTE((u4byte)pThis, 2); m_pThis->m_BytecodeStdcall[ 9] = GETBYTE((u4byte)pThis, 3); m_pThis->m_BytecodeStdcall[10] = GETBYTE((u4byte)pThis, 4); m_pThis->m_BytecodeStdcall[11] = 0xE9; m_pThis->m_BytecodeStdcall[12] = GETBYTE((u4byte)dwJmpAdr, 1); m_pThis->m_BytecodeStdcall[13] = GETBYTE((u4byte)dwJmpAdr, 2); m_pThis->m_BytecodeStdcall[14] = GETBYTE((u4byte)dwJmpAdr, 3); m_pThis->m_BytecodeStdcall[15] = GETBYTE((u4byte)dwJmpAdr, 4); FlushInstructionCache(GetCurrentProcess(), &(m_pThis->m_BytecodeStdcall), sizeof (m_BytecodeStdcall)); return &(m_pThis->m_BytecodeStdcall); */ m_pThis->m_BytecodeStdcall.Push[0] = 0xFF; m_pThis->m_BytecodeStdcall.Push[1] = 0x34; m_pThis->m_BytecodeStdcall.Push[2] = 0x24; m_pThis->m_BytecodeStdcall.Move = 0x042444C7; m_pThis->m_BytecodeStdcall.This = (u4byte)pThis; m_pThis->m_BytecodeStdcall.Jmp = 0xE9; m_pThis->m_BytecodeStdcall.Adrr = MemberFxnAddr - (u4byte)(&(m_pThis->m_BytecodeStdcall)) - sizeof (BYTECODE_STDCALL); FlushInstructionCache(GetCurrentProcess(), &(m_pThis->m_BytecodeStdcall), sizeof (BYTECODE_STDCALL)); return &(m_pThis->m_BytecodeStdcall); } test.cpp 复制代码 #include <windows.h> #include <stdio.h> #include "Thunk.h" class T { public: T() : nCount(10), m_bExit(false) { } ~T() { // 等待所有线程退出, 然后再做析构操作, // 以免因为类生命周期结束, 而线程还在访问nCount, m_bExit m_bExit = true; WaitForMultipleObjects(2, m_Handles, TRUE, INFINITE); printf("destruction\n"); CloseHandle(m_Handles[0]); CloseHandle(m_Handles[1]); } DWORD /* __thiscall */ ThreadProcA(LPVOID lpParameter) { // 在此线程函数中可以访问类的数据成员nCount ! while(nCount >= 0) { if(m_bExit) break; printf("thiscall\t%d\n", nCount); nCount--; Sleep(1000); } printf("ThreadA exit\n"); return 0; } DWORD __stdcall ThreadProcB(LPVOID lpParameter) { // 在此线程函数中可以访问类的数据成员nCount ! while(nCount >= 0) { if(m_bExit) break; printf("stdcall\t\t%d\n", nCount); nCount--; Sleep(1000); } printf("ThreadB exit\n"); return 0; } void testA() { // 将线程函数地址初始到pProc中, 然后赋与CreateThread // 注意调用约定 void* pProc = m_PorcA.Thiscall(this, Thunk::GetMemberFxnAddr(&T::ThreadProcA)); m_Handles[0] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)pProc, 0, 0, 0); } void testB() { // 将线程函数地址初始到pProc中, 然后赋与CreateThread // 注意调用约定 void* pProc = m_PorcB.Stdcall(this, Thunk::GetMemberFxnAddr(&T::ThreadProcB)); m_Handles[1] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)pProc, 0, 0, 0); } private: int nCount; bool m_bExit; HANDLE m_Handles[2]; Thunk m_PorcA; Thunk m_PorcB; }; void main() { T t; t.testA(); // 测试函数 Sleep(200); // 因为没有线程同步操作, 为使两个线程交替输出, 所以... t.testB(); // 测试函数 Sleep(10000); // 给给予线程允分的执行时间 } |
联系客服