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?

2 Responses to “Finding Functions, Part 3”

  1. PM says:

    Because modifying ECX and then doing a function call could mess up MSVC’s expectations about the contents of the ECX register?

    Anyway, http://www.angelcode.com/dev/callconv/callconv.html

Leave a Reply

You must be logged in to post a comment.