通用 Thunk
2008-05-25 21:39:20 来源:WEB开发网2.转移指令的目的地
许多转移指令的目的地使用“到源的偏移量”来表示
比如:当CPU 执行到0xFF000000 处的指令时, 指令像这个样子:0xFF000000 : 0xE9 0x33 0x55 0x77 0x99
0xFF000005 : ...
0xE9 是一个 JMP 指令,紧接着的4字节将被解释为偏移
offset = 0x99775533 (在Intel x86 机器上,低字节存储在低地址上) = -1720232653
源 (src) = 0xFF000000 (JMP指令的地址) = 4278190080
目的地 (dst) = src+offset+5 (JMP占1字节,偏移占4字节) = 4278190080 – 1720232653 +5 = 2557957432 = 0x98775538
所以在指令 “ JMP -1720232653 “ 之后,下一条被执行的指令将在
0x98775538 : ...
基于这点,我们可以实现2个方法:
void SetTransterDST(
更多详细信息 见 ThunkBase.cpp 3.栈的生长在Win32平台下,栈朝着低地址生长。也就是说,当栈增加N ESP就减少N,反之亦然。我们来设计这个类
void *src /* the address of transfer instruction*/
,int dst /* the destination*/ ) {
unsigned char *op = static_cast<unsigned char *>(src);
switch (*op++) {
case 0xE8: // CALL offset (dword)
case 0xE9: // JMP offset (dword)
{
int *offset = reinterpret<int*>(op);
*offset = dst – reinterpret<int>(src) - sizeof(*op)*1 – sizeof(int);
}
break;
case 0xEB: // JMP offset (byte)
...
break;
case ...:
...
break;
default :
assert(!”not complete!”);
}
}
int GetTransnferDST(const void *src) {
const unsigned char *op = static_cast< const unsigned char *>(src);
switch (*op++) {
case 0xE8: //CALL offset (dword)
case 0xE9: //JMP offset (dword)
{
const int *offset = reinterpret_cast<const int*>(op);
return *offset + PointerToInt32(src) + sizeof(*op) +sizeof(int);
}
break;
case 0xEB: // JMP offset(byte)
...
break;
case ...:
...
break;
default:
assert(!”not complete!”);
break;
}
return 0;
}class ThisToStd
我们以如下方式使用这个类 :
{
public:
ThisToStd(const void *Obj = 0,int memFunc = 0);
const void *Attach(const void *newObj);
int Attach(int newMemFunc);
private:
#pragma pack( push , 1) // 强制编译器使用1字节长度对齐结构
unsigned char MOV_ECX;
const void *m_this;
unsigned char JMP;
const int m_memFunc;
#pragma pack( pop ) // 恢复对齐
};
ThisToStd:: ThisToStd(const void *Obj,int memFunc)
: MOV_ECX(0xB9),JMP(0xE9) {
Attach(Obj); // 设置this指针
Attach(memFunc); // 设置成员函数地址(使用偏移)
}
const void* ThisToStd::Attach(const void *newObj) {
const void *oldObj = m_this;
m_this = newObj;
return oldObj;
}
int ThisToStd::Attach(int newMemFunc) {
int oldMemFunc = GetTransferDST(&JMP);
SetTransferDST(&JMP,newMemFunc);
return oldMemFunc;
}typedef void ( __stdcall * fun1)(int);
它是如何工作的,当CPU执行到 fun(1212); 机器码如下:
class C { public : void __thiscall fun1(int){} };
C obj;
ThisToStd thunk;
thunk.Attach(&obj); // 假设 &obj = OBJ_ADD
int memFunc = PointerToInt32(&C::fun1); //假设memFunc = MF_ADD
thunk.Attach(memFunc); // thunk.m_memFunc 将被设置为MF_ADD – (&t.JMP)-5
fun1 fun = reinterpret_cast<fun1>(&thunk); //假设 &thunk = T_ADD
fun(1212); // 与 obj.fun(1212) 有同样效果PUSH 1212;
CALL DWORD PTR [fun];
0x50000 : … ; 假设 RET_ADD = 0x50000
// CALL DOWRD PTR [fun] 与CALL(0xE8) offset(dword) 不同
//我们只需要知道: 它将RET_ADD压栈,然后跳转到T_ADD
更多精彩
赞助商链接