发现功能,第3部分(Finding Functions, Part 3)
上次我们找到了签名和需要搜索的内存区域,最终我们如何找到函数地址并加以利用呢?
寻找签名非常简单。
void *FindSignature(unsigned char *pBaseAddress,
size_t baseLength, unsigned char *pSignature, size_t sigLength)
{
unsigned char *pBasePtr = pBaseAddress;
unsigned char *pEndPtr = pBaseAddress + baseLength;
size_t i;
while (pBasePtr < pEndPtr)
{
for (i=0; i<sigLength; i++)
{
if (pSignature[i] != '\x2A' && pSignature[i] != pBasePtr[i])
break;
}
//iff i reached the end, we know we have a match!
if (i == sigLength+1)
return (void *)pBasePtr;
pBasePtr += sizeof(unsigned char *); //search memory in an aligned manner
}
}
//Example usage:
//void *sigaddr = FindSignature(pBaseAddress, baseLength, MKSIG(TerminateRound));
最后,现在我们有了这个函数,它是安全的尝试调用它。 首先,我们必须 '''完全''' 匹配调用约定。 如果我们用错误函数参数调用,程序就会华丽丽的崩溃。。。 幸运的是HL2SDK主要包含两种最常见的约定:cdecl、thiscalls。
Cdecl的C标准的参数在给定平台(x86,参数被完全压入堆栈)。 这些都是静态的,全球性的,或非成员函数。
//就是说,你找到
UTIL_RemoveEntity(CBaseEntity *pEntity)
//调用约定将像这样:
typedef void (*UtilRemoveFunc)(CBaseEntity *);
extern UtilRemoveFunc g_UtilRemoveFunc;
//....
UtilRemoveFunc g_UtilRemoveFunc = NULL;
//....
g_UtilRemoveFunc = (UtilRemoveFunc)sigaddr;
//....
void UTIL_Remove(CBaseEntity *pEntity)
{
(g_UtilRemoveFunc)(pEntity);
}
我们构建了一个简单的函数!
事情变得有点复杂,类的成员函数,内部称为“thiscall。 “thiscall是特殊的,因为它需要一个实例, this 指针。
this指针它包含所有的虚拟表和继承三角洲信息,以及实例化类的成员变量。 成员函数需要一个实例指针,否则它将无法修改任何变量的对象。 基本上,如果一个成员函数是一个静态函数,它需要一个额外的meta-parameter。
在GCC,this指针被推到栈上并位于所有其他参数之前,这种情况下,基本上通过一个专用的cdecl 来调用,在MSVC无论如何,this指针都是通过 ecx 来传递注册。
就是说,我们发现的这个签名对应的function
class CBasePlayer : public CBaseCombatCharacter { //player.h ...
virtual CBaseEntity *GiveNamedItem( const char *szName, int iSubType = 0 );
像SourceHook这样的平台会利用一些很巧妙地模版技巧来实现,但这里,我将展示最原始的完成方法:
#if defined WIN32
typedef CBaseEntity * (*GiveItemFunc)(const char *, int);
#else //GCC takes the this pointer on the stack as the first parameter
typedef CBaseEntity * (*GiveItemFunc)(CBasePlayer *, const char *, int);
#endif
extern GiveItemFunc g_GiveItemFunc;
//....
GiveItemFunc g_GiveItemFunc = NULL;
//....
g_GiveItemFunc = (GiveItemFunc)sigaddr;
//....
CBaseEntity *GiveNamedItem(CBasePlayer *pPlayer, const char *name, int iSubType)
{
CBaseEntity *pReturnEnt;
#if defined WIN32
__asm
{
mov ecx, pPlayer;
push name;
push iSubType;
call g_GiveItemFunc;
mov pReturnEnt, eax;
};
#else
pReturnEnt = (g_GiveItemFunc)(pPlayer, name, iSubType);
#endif
return pReturnEnt;
}
就像你看到的,CGG可以很容易的调用一个静态方法,在windows,我们必须移动 this指针到正确的注册地址,注意,我们没有清理我们的栈,这里没有
add esp, 8
MSVC通常自动清理栈使用 RETURN(0xC2)来代替 RET(0xC3) 这是MSVC的典型,因为微软喜欢使用stdcall公约内部的事情。
相关文章
发现功能,第2部分(Finding Functions, Part 2)
原文
Finding Functions, Part 3
Last time we had found a signature and the memory region to search. How do we finally find the function’s address and make use of it?
Searching for our signature is pretty simple. Say we have:
void *FindSignature(unsigned char *pBaseAddress,
size_t baseLength, unsigned char *pSignature, size_t sigLength)
{
unsigned char *pBasePtr = pBaseAddress;
unsigned char *pEndPtr = pBaseAddress + baseLength;
size_t i;
while (pBasePtr < pEndPtr)
{
for (i=0; i<sigLength; i++)
{
if (pSignature[i] != '\x2A' && pSignature[i] != pBasePtr[i])
break;
}
//iff i reached the end, we know we have a match!
if (i == sigLength+1)
return (void *)pBasePtr;
pBasePtr += sizeof(unsigned char *); //search memory in an aligned manner
}
}
//Example usage:
//void *sigaddr = FindSignature(pBaseAddress, baseLength, MKSIG(TerminateRound));
Lastly, now that we have the function, it's safe to try calling it. First, we must exactly match the calling convention. If we call the function with the wrong parameter style, we'll get a nice instant crash nearly all of the time. Luckily the HL2SDK mainly encompasses the two most common conventions: cdecl and thiscalls.
Cdecl is the definitive C standard of passing arguments on a given platform (on x86, parameters are pushed entirely onto the stack). These are static, global, or non-member functions. Say you're finding
UTIL_RemoveEntity(CBaseEntity *pEntity)
, the calling convention would look as:
typedef void (*UtilRemoveFunc)(CBaseEntity *);
extern UtilRemoveFunc g_UtilRemoveFunc;
//....
UtilRemoveFunc g_UtilRemoveFunc = NULL;
//....
g_UtilRemoveFunc = (UtilRemoveFunc)sigaddr;
//....
void UTIL_Remove(CBaseEntity *pEntity)
{
(g_UtilRemoveFunc)(pEntity);
}
We’ve constructed a simple function!
Things get a bit trickier with class member functions, internally known as a “thiscall.” A thiscall is special because it requires an instance, or “
this
pointer”. A
this
pointer is a pointer that contains all of the virtual table and inheritance delta information, as well as member variables of the instantiated class. A member function needs an instance pointer, otherwise it would not be able to modify any of the variables in the object. So basically, a member function is a static function that takes one extra meta-parameter.
On GCC, the
this
pointer is pushed on the stack before all the other parameters. In that case we’re basically dealing with a special cdecl call. On MSVC, however, the
this
pointer is passed through the
ecx
register. Say we’ve found the signature for this member function:
class CBasePlayer : public CBaseCombatCharacter { //player.h ...
virtual CBaseEntity *GiveNamedItem( const char *szName, int iSubType = 0 );
A platform independent way of doing this can be done with some neat template tricks PM OnoTo has done in SourceHook with callclasses, but I’ll demonstrate the old fashioned way:
#if defined WIN32
typedef CBaseEntity * (*GiveItemFunc)(const char *, int);
#else //GCC takes the this pointer on the stack as the first parameter
typedef CBaseEntity * (*GiveItemFunc)(CBasePlayer *, const char *, int);
#endif
extern GiveItemFunc g_GiveItemFunc;
//....
GiveItemFunc g_GiveItemFunc = NULL;
//....
g_GiveItemFunc = (GiveItemFunc)sigaddr;
//....
CBaseEntity *GiveNamedItem(CBasePlayer *pPlayer, const char *name, int iSubType)
{
CBaseEntity *pReturnEnt;
#if defined WIN32
__asm
{
mov ecx, pPlayer;
push name;
push iSubType;
call g_GiveItemFunc;
mov pReturnEnt, eax;
};
#else
pReturnEnt = (g_GiveItemFunc)(pPlayer, name, iSubType);
#endif
return pReturnEnt;
}
As you can see, GCC makes calling it statically fairly easy. On Windows, we must use assembly to move the
this
pointer into the correct register. Note that we didn’t clean up our stack – there’s no ‘
add esp, 8
‘. MSVC usually cleans up its own stack for thiscalls, using
RETN
(0xC2) rather than
RET
(0xC3). This is typical of MSVC, since Microsoft prefers using the stdcall convention for internal things.
Bonus question: Why did we have to push the Win32 parameters manually after storing ecx, rather than storing ecx and calling the function like we did on GCC?

浙公网安备 33010602011771号