fredag den 25. november 2011

Reversing Valve's Source Engine functions: Getting Weapon Information without using Valve Source SDK

I have lately found myself in the need of being able to get the exact data about weapons that Valve uses in their own functions. Due to Valve's SDK being outdated, I decided to reverse the functions that the Source Engine uses to to get information from a file. In this post I will be using OllyDBG, aswell as IDA.
So, to find the place where they actually use the functions/the weapon informations, I simply looked in the SDK provided by Valve(which is now outdated) for a place to start, and found a function called FX_FireBullets. This function calls different functions when it needs to access information about weapons:
v12 = sub_10242390(a4);//call to WeaponIDToAlias
if ( !v12 )
{
    DevMsg("FX_FireBullets: weapon alias for ID %i not found\n", a4);
    return;
}
sub_10322A70(&v37, 0x80u, "weapon_%s", v12);//call to a function similar to sprintf_s
v13 = sub_10173BE0((int)&v37);//call to LookupWeaponInfoSlot

if ( v13 == (unsigned __int16)sub_10110B90() )
{
    DevMsg("FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", &v37);
    return;
}

v14 = (int)sub_10173B00(v13);//call to GetFileWeaponInfoFromHandle
 These functions are essentially all we need(weapon ID's excluded) to get info about weapons, and as you can see, they are very easy to find in your favorite debugger, or IDA, since there are a nice pair of strings that we can find. So if we fire up a debugger and search for one of those strings, we  will see the following disassembly(commented):
MOV EBX,DWORD PTR SS:[ESP+140]; "weapon's ID"
PUSH EBX
CALL client.57153130; "call to WeaponIDToAlias"
ADD ESP,4
TEST EAX,EAX
JNZ SHORT client.57141D9B
PUSH EBX
PUSH client.574AB288; "ASCII 'FX_FireBullets: weapon alias for ID %i not found'"
CALL DWORD PTR DS:[insert garbage here]; "call to DevMsg"
ADD ESP,8
POP EDI
POP ESI
POP EBP
POP EBX
ADD ESP,120
RETN
PUSH EAX; "the return value of WeaponIDToAlias(weapon's alias) is stored in EAX"
PUSH client.574AADF0; "ASCII 'weapon_%s'"
LEA ECX,DWORD PTR SS:[ESP+B8]
PUSH 80
PUSH ECX
CALL client.572351A0; "prints 'weapon_'+alias into a char array"
LEA EDX,DWORD PTR SS:[ESP+C0]; "char array"
PUSH EDX
CALL client.57083BF0; "call to LookupWeaponInfoSlot(returnvalue is an unsigned short)"
ADD ESP,14
MOVZX EDI,AX; "AX is used because of the short's small size"
CALL client.56FBBCF0; "OR AX, 0x0FFFF"
CMP DI,AX; "is the return value valid?"
JNZ SHORT client.57141DF1
LEA EAX,DWORD PTR SS:[ESP+B0]
PUSH EAX
PUSH client.574AB24C; "ASCII 'FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s'"
CALL DWORD PTR DS:[insert garbage here]; "call to DevMsg"
ADD ESP,8
POP EDI
POP ESI
POP EBP
POP EBX
ADD ESP,120
RETN
PUSH EDI; "LookupWeaponInfoSlot return value"
CALL client.57083B10; "GetFileWeaponInfoFromHandle"
ADD ESP,4
Essentially we want to call those functions. Easiest way to get a pointer to them would be to make a signature scan for each individual call. This can be done very easily with a plugin for OllyDBG such as SigMaker, or a similar plugin for IDA. Here are the signatures I made with OllyDBG+SigMaker v.4('?' is a wildcard obviously):
weaponIDToAlias: client.dll "\x8B\x4C\x24\x04\x33\xC0\x39\x0C\xC5????\x74\x0B"
lookupWeaponInfoSlot: client.dll "\x8B\x44\x24\x04\x83\xEC\x08\x85\xC0\x74\x18"
getFileWeaponInfoFromHandle: client.dll "\x66\x8b\x44\x24\x04\x66\x3b\x05????\x73"

Now we have valid pointers to these functions. How do we use them, you ask? I pasted some code below, to show how you can use these functions.
const char* GetWeaponAlias( int weaponID )
{
 static void* weaponIDToAlias = 0;

 if ( !weaponIDToAlias )
 {
  weaponIDToAlias = Global::Get()->Memory()->FindPattern( GetModuleHandle( "client.dll" ), "\x8B\x4C\x24\x04\x33\xC0\x39\x0C\xC5????\x74\x0B" );
 }

 char* weaponAlias = 0;

 _asm
 {
  PUSH weaponID
  CALL weaponIDToAlias
  ADD ESP, 4
  MOV weaponAlias, EAX
 }

 return weaponAlias;
}

void* GetSDKWeaponInfo( void* weapon )
{
 static void* lookupWeaponInfoSlot = 0, getFileWeaponInfoFromHandle = 0;

 if ( !lookupWeaponInfoSlot )
 {
  lookupWeaponInfoSlot = Global::Get()->Memory()->FindPattern( GetModuleHandle( "client.dll" ), "\x8B\x44\x24\x04\x83\xEC\x08\x85\xC0\x74\x18" );
 }

 if ( !getFileWeaponInfoFromHandle )
 {
  getFileWeaponInfoFromHandle = Global::Get()->Memory()->FindPattern( GetModuleHandle( "client.dll" ), "\x66\x8b\x44\x24\x04\x66\x3b\x05????\x73" );
 }

 char weaponName[128];

 sprintf_s( weaponName, sizeof( weaponName ), "weapon_%s", GetWeaponAlias( GetWeaponID( weapon ) ) );

 void* weaponInfo = 0;
 _asm
 {
  LEA EDX, weaponName
  PUSH EDX
  CALL lookupWeaponInfoSlot
  ADD ESP, 4
  MOVZX EDI, AX
  PUSH EDI
  CALL getFileWeaponInfoFromHandle
  ADD ESP, 4
  MOV weaponInfo, EAX
 }

 return weaponInfo;
}

struct WeaponInfo_s
{
 int weaponID;
 int penetration;
 int damage;
 float maxRange;
 float rangeModifier;
 int bulletsPerShot;
 int ammoType;
 float penetrationRange;
 float penetrationPower;
};


WeaponInfo_s GetWeaponInfo( void* localPlayer, void* weapon )
{
 static void* getBulletTypeParameters = 0;

 if ( !getBulletTypeParameters )
 {
  getBulletTypeParameters = 0/*find it yourself if you need it*/;
 }

 void* sdkWeaponInfo = GetSDKWeaponInfo( weapon );

 WeaponInfo_s weaponInfo;

 weaponInfo.penetration = *reinterpret_cast<int*>( (size_t)sdkWeaponInfo + 0x884 );
 weaponInfo.damage = *reinterpret_cast<int*>( (size_t)sdkWeaponInfo + 0x888 );
 weaponInfo.maxRange = *reinterpret_cast<float*>( (size_t)sdkWeaponInfo + 0x88C );
 weaponInfo.rangeModifier = *reinterpret_cast<float*>( (size_t)sdkWeaponInfo + 0x890 );
 weaponInfo.bulletsPerShot = *reinterpret_cast<int*>( (size_t)sdkWeaponInfo + 0x894 );
 weaponInfo.ammoType = *reinterpret_cast<int*>( (size_t)sdkWeaponInfo + 0x6c0 );

 weaponInfo.weaponID = GetWeaponID( weapon );


 if ( weaponInfo.weaponID == WP_M4A1 )//The following was inside FX_Firebullets after these functions were called.
 {
  bool specialWeaponMode = *reinterpret_cast<bool*>( (size_t)weapon + 0x9d8 );

  if ( specialWeaponMode == 1 )
  {
   weaponInfo.rangeModifier = .95f;
  }
 }

if ( weaponInfo.weaponID == WP_GLOCK18 )
 {
  bool specialWeaponMode = *reinterpret_cast<bool*>( (size_t)weapon + 0x9d0 );

  if( specialWeaponMode )
  {
   int burstShotsRemaining = *reinterpret_cast<int*>( (size_t)weapon + 0x9dc );

   if ( burstShotsRemaining > 0 )
   {
    weaponInfo.damage = 18;
    weaponInfo.rangeModifier = .9f;
   }
  }
 }

 if ( weaponInfo.weaponID == WP_USP45 )
 {
  bool specialWeaponMode = *reinterpret_cast<bool*>( (size_t)weapon + 0x9d0 );

  if ( specialWeaponMode )
  {
   weaponInfo.damage = 30;
  }
 }

 _asm
 {
  LEA ECX, weaponInfo.penetrationRange
  PUSH ECX
  LEA EDX, weaponInfo.penetrationPower
  PUSH EDX
  PUSH weaponInfo.ammoType
  MOV ECX, localPlayer
  MOV ECX, DWORD PTR DS:[ECX]
  CALL getBulletTypeParameters
 }

 return weaponInfo;
}