Hi! I've gotten a few requests for a dhooks tutorial so I decided to write this tutorial. This is (hopefully) Part I of II (can’t promise I’ll ever get to do Part II). This tutorial will give you a basic breakdown of how to use Dynamic Hooks (While also providing other useful information!).
Note for bigger version of the images click on them!
What you need:
IDA Free 5.0+ (Used in this tutorial is 5.0 Free. Normally I use 6.X)
linux_vtable_dump.idc
Downloads:
IDA Free 5.0 Download link is at the bottom of the page. Install before proceeding.
linux_vtable_dump.idc To save this file either click the link then right click and click Save As. Or Right click the link and click Save As. Save the file in the idc folder where you installed IDA.
Default on x86: C:\Program Files\IDA\idc
Default on x64: C:\Program Files (x86)\IDA\idc
Notepad ++ This isn’t a requirement.
Now that you have everything installed grab a copy of the server.so for your game. Normally this will already download when you install a server. If it does not download you can force it by forcing hldsupdatetool to download the linux binary. To do so just add "-linux" to the game param when using the hldsupdatetool. For example to download the linux binaries for CS:S I would use these options.
Code:
-command update -game "Counter-Strike Source-linux"
Now that you have the server.so, launch IDA and open the server.so file. It should something like this.
I usually like to keep a cleaner workspace so I close most of the tabs so it looks like this
After opening the file it will take a while to analyze (On 5.0 it takes about 45 minutes while 6.0 is about 10-20) But we can finish setting everything up while we wait! Let’s start by enabling the opcodes on each line. This isn’t as useful for this but is for finding signatures. Click on Options->General. This will bring up a box like below. Change "Number of Opcode bytes" to 10
Next (Note I only had to do this on 5.0 Free but you can check it on all versions) Click on Options->Demangled Names. This will bring up a box like bellow. Make sure that "Assume GCC v3.x names" is checked. If it is not, check it.
Now wait for the analysis to finish after it is complete the window will go into graph view to get out of graph view hit the space bar.
For this tutorial I will be hooking CCSGameRules::GoToIntermission(void). Click inside the "Function Names" box and press ALT+T to bring up the search. Search for GoToIntermission and double click the function. It should look something like
Click on the function name inside the IDA View-A and press CTRL+X to bring up the xrefs window. You should get something like this.
This function only exists on the CCSGameRules vtable which makes it easy. Double click it to go to the vtable. This should look something like this.
Make sure you are clicked within the vtable you want. Now click File->IDC File (or File->Script File in 6.0) and select the linux_vtable_dump.idc file.
By default the input will be 1, CHANGE IT TO 0 this will dump the linux values. Click ok and save the file somewhere (with a useful name like the vtable's name) This is how mine looks.
Code:
// Auto reconstructed from vtable block @ 0x00CEB180
// from "server.so", by ida_vtables.idc
0 CGameRules::Name(void)
1 CMultiplayRules::Init(void)
2 CBaseGameSystemPerFrame::PostInit(void)
3 CBaseGameSystemPerFrame::Shutdown(void)
4 CCSGameRules::LevelInitPreEntity(void)
5 CCSGameRules::LevelInitPostEntity(void)
6 CBaseGameSystemPerFrame::LevelShutdownPreClearSteamAPIContext(void)
7 CBaseGameSystemPerFrame::LevelShutdownPreEntity(void)
8 CBaseGameSystemPerFrame::LevelShutdownPostEntity(void)
9 CBaseGameSystemPerFrame::OnSave(void)
10 CBaseGameSystemPerFrame::OnRestore(void)
11 CBaseGameSystemPerFrame::SafeRemoveIfDesired(void)
12 CBaseGameSystemPerFrame::IsPerFrame(void)
13 CCSGameRules::~CCSGameRules()
14 CCSGameRules::~CCSGameRules()
15 CBaseGameSystemPerFrame::FrameUpdatePreEntityThink(void)
16 CGameRules::FrameUpdatePostEntityThink(void)
17 CBaseGameSystemPerFrame::PreClientUpdate(void)
18 CMultiplayRules::Damage_IsTimeBased(int)
19 CMultiplayRules::Damage_ShouldGibCorpse(int)
20 CMultiplayRules::Damage_ShowOnHUD(int)
21 CMultiplayRules::Damage_NoPhysicsForce(int)
22 CMultiplayRules::Damage_ShouldNotBleed(int)
23 CMultiplayRules::Damage_GetTimeBased(void)
24 CMultiplayRules::Damage_GetShouldGibCorpse(void)
25 CMultiplayRules::Damage_GetShowOnHud(void)
26 CMultiplayRules::Damage_GetNoPhysicsForce(void)
27 CMultiplayRules::Damage_GetShouldNotBleed(void)
28 CMultiplayRules::SwitchToNextBestWeapon(CBaseCombatCharacter *,CBaseCombatWeapon *)
29 CCSGameRules::GetNextBestWeapon(CBaseCombatCharacter *,CBaseCombatWeapon *)
30 CCSGameRules::ShouldCollide(int,int)
31 CCSGameRules::DefaultFOV(void)
32 CCSGameRules::GetViewVectors(void)const
33 CGameRules::GetAmmoDamage(CBaseEntity *,CBaseEntity *,int)
34 CGameRules::GetDamageMultiplier(void)
35 CMultiplayRules::IsMultiplayer(void)
36 CCSGameRules::GetEncryptionKey(void)
37 CGameRules::InRoundRestart(void)
38 CGameRules::AllowThirdPersonCamera(void)
39 CCSGameRules::ClientCommandKeyValues(edict_t *,KeyValues *)
40 CCSGameRules::GetTaggedConVarList(KeyValues *)
41 CGameRules::CheckHaptics(CBasePlayer *)
42 CCSGameRules::LevelShutdown(void)
43 CTeamplayRules::Precache(void)
44 CMultiplayRules::RefreshSkillData(bool)
45 CCSGameRules::Think(void)
46 CMultiplayRules::IsAllowedToSpawn(CBaseEntity *)
47 CCSGameRules::EndGameFrame(void)
48 CGameRules::IsSkillLevel(int)
49 CGameRules::GetSkillLevel(void)
50 CGameRules::OnSkillLevelChanged(int)
51 CGameRules::SetSkillLevel(int)
52 CMultiplayRules::FAllowFlashlight(void)
53 CCSGameRules::FShouldSwitchWeapon(CBasePlayer *,CBaseCombatWeapon *)
54 CMultiplayRules::IsDeathmatch(void)
55 CTeamplayRules::IsTeamplay(void)
56 CMultiplayRules::IsCoOp(void)
57 CCSGameRules::GetGameDescription(void)
58 CMultiplayRules::ClientConnected(edict_t *,char const*,char const*,char *,int)
59 CTeamplayRules::InitHUD(CBasePlayer *)
60 CCSGameRules::ClientDisconnected(edict_t *)
61 CCSGameRules::FlPlayerFallDamage(CBasePlayer *)
62 CTeamplayRules::FPlayerCanTakeDamage(CBasePlayer *,CBaseEntity *)
63 CTeamplayRules::ShouldAutoAim(CBasePlayer *,edict_t *)
64 CGameRules::GetAutoAimScale(CBasePlayer *)
65 CGameRules::GetAutoAimMode(void)
66 CGameRules::ShouldUseRobustRadiusDamage(CBaseEntity *)
67 CCSGameRules::RadiusDamage(CTakeDamageInfo const&,Vector const&,float,int,CBaseEntity *)
68 CCSGameRules::FlPlayerFallDeathDoesScreenFade(CBasePlayer *)
69 CMultiplayRules::AllowDamage(CBaseEntity *,CTakeDamageInfo const&)
70 CCSGameRules::PlayerSpawn(CBasePlayer *)
71 CMultiplayRules::PlayerThink(CBasePlayer *)
72 CCSGameRules::FPlayerCanRespawn(CBasePlayer *)
73 CMultiplayRules::FlPlayerSpawnTime(CBasePlayer *)
74 CCSGameRules::GetPlayerSpawnSpot(CBasePlayer *)
75 CCSGameRules::IsSpawnPointValid(CBaseEntity *,CBasePlayer *)
76 CMultiplayRules::AllowAutoTargetCrosshair(void)
77 CCSGameRules::ClientCommand(CBaseEntity *,CCommand const&)
78 CCSGameRules::ClientSettingsChanged(CBasePlayer *)
79 CTeamplayRules::IPointsForKill(CBasePlayer *,CBasePlayer *)
80 CCSGameRules::PlayerKilled(CBasePlayer *,CTakeDamageInfo const&)
81 CCSGameRules::DeathNotice(CBasePlayer *,CTakeDamageInfo const&)
82 CGameRules::GetDamageCustomString(CTakeDamageInfo const&)
83 CGameRules::AdjustPlayerDamageInflicted(float)
84 CGameRules::AdjustPlayerDamageTaken(CTakeDamageInfo *)
85 CMultiplayRules::CanHavePlayerItem(CBasePlayer *,CBaseCombatWeapon *)
86 CMultiplayRules::WeaponShouldRespawn(CBaseCombatWeapon *)
87 CMultiplayRules::FlWeaponRespawnTime(CBaseCombatWeapon *)
88 CMultiplayRules::FlWeaponTryRespawn(CBaseCombatWeapon *)
89 CMultiplayRules::VecWeaponRespawnSpot(CBaseCombatWeapon *)
90 CMultiplayRules::CanHaveItem(CBasePlayer *,CItem *)
91 CMultiplayRules::PlayerGotItem(CBasePlayer *,CItem *)
92 CMultiplayRules::ItemShouldRespawn(CItem *)
93 CMultiplayRules::FlItemRespawnTime(CItem *)
94 CMultiplayRules::VecItemRespawnSpot(CItem *)
95 CMultiplayRules::VecItemRespawnAngles(CItem *)
96 CGameRules::CanHaveAmmo(CBaseCombatCharacter *,int)
97 CGameRules::CanHaveAmmo(CBaseCombatCharacter *,char const*)
98 CMultiplayRules::PlayerGotAmmo(CBaseCombatCharacter *,char *,int)
99 CGameRules::GetAmmoQuantityScale(int)
100 CCSGameRules::InitDefaultAIRelationships(void)
101 CCSGameRules::AIClassText(int)
102 CMultiplayRules::FlHealthChargerRechargeTime(void)
103 CMultiplayRules::FlHEVChargerRechargeTime(void)
104 CMultiplayRules::DeadPlayerWeapons(CBasePlayer *)
105 CMultiplayRules::DeadPlayerAmmo(CBasePlayer *)
106 CTeamplayRules::GetTeamID(CBaseEntity *)
107 CTeamplayRules::PlayerRelationship(CBaseEntity *,CBaseEntity *)
108 CTeamplayRules::PlayerCanHearChat(CBasePlayer *,CBasePlayer *)
109 CGameRules::CheckChatText(CBasePlayer *,char *)
110 CTeamplayRules::GetTeamIndex(char const*)
111 CTeamplayRules::GetIndexedTeamName(int)
112 CTeamplayRules::IsValidTeam(char const*)
113 CTeamplayRules::ChangePlayerTeam(CBasePlayer *,char const*,bool,bool)
114 CCSGameRules::SetDefaultPlayerTeam(CBasePlayer *)
115 CCSGameRules::UpdateClientData(CBasePlayer *)
116 CCSGameRules::PlayTextureSounds(void)
117 CMultiplayRules::PlayFootstepSounds(CBasePlayer *)
118 CCSGameRules::FAllowNPCs(void)
119 CMultiplayRules::EndMultiplayerGame(void)
120 CGameRules::WeaponTraceEntity(CBaseEntity *,Vector const&,Vector const&,unsigned int,CGameTrace *)
121 CCSGameRules::CreateStandardEntities(void)
122 CCSGameRules::GetChatPrefix(bool,CBasePlayer *)
123 CCSGameRules::GetChatLocation(bool,CBasePlayer *)
124 CCSGameRules::GetChatFormat(bool,CBasePlayer *)
125 CGameRules::ShouldBurningPropsEmitLight(void)
126 CGameRules::CanEntityBeUsePushed(CBaseEntity *)
127 CCSGameRules::CreateCustomNetworkStringTables(void)
128 CGameRules::MarkAchievement(IRecipientFilter &,char const*)
129 CMultiplayRules::ResetMapCycleTimeStamp(void)
130 CGameRules::OnNavMeshLoad(void)
131 CGameRules::TacticalMissionManagerFactory(void)
132 CGameRules::ProcessVerboseLogOutput(void)
133 CGameRules::GetGameTypeName(void)
134 CGameRules::GetGameType(void)
135 CMultiplayRules::ShouldDrawHeadLabels(void)
136 CGameRules::ClientSpawned(edict_t *)
137 CGameRules::OnFileReceived(char const*,unsigned int)
138 CGameRules::IsHolidayActive(int)const
139 CMultiplayRules::GetDeathScorer(CBaseEntity *,CBaseEntity *,CBaseEntity *)
140 CMultiplayRules::VoiceCommand(CBaseMultiplayerPlayer *,int,int)
141 CMultiplayRules::HandleTimeLimitChange(void)
142 CMultiplayRules::InitCustomResponseRulesDicts(void)
143 CMultiplayRules::ShutdownCustomResponseRulesDicts(void)
144 CMultiplayRules::GetNextLevelName(char *,int,bool)
145 CMultiplayRules::UseSuicidePenalty(void)
146 CMultiplayRules::ChangeLevel(void)
147 CCSGameRules::GoToIntermission(void)
148 CTeamplayRules::GetCaptureValueForPlayer(CBasePlayer *)
149 CTeamplayRules::TeamMayCapturePoint(int,int)
150 CTeamplayRules::PlayerMayCapturePoint(CBasePlayer *,int,char *,int)
151 CTeamplayRules::PlayerMayBlockPoint(CBasePlayer *,int,char *,int)
152 CTeamplayRules::PointsMayBeCaptured(void)
153 CTeamplayRules::SetLastCapPointChanged(int)
154 CTeamplayRules::TimerMayExpire(void)
155 CTeamplayRules::SetWinningTeam(int,int,bool,bool,bool)
156 CTeamplayRules::SetStalemate(int,bool,bool)
157 CTeamplayRules::SetSwitchTeams(bool)
158 CTeamplayRules::ShouldSwitchTeams(void)
159 CTeamplayRules::HandleSwitchTeams(void)
160 CTeamplayRules::SetScrambleTeams(bool)
161 CTeamplayRules::ShouldScrambleTeams(void)
162 CTeamplayRules::HandleScrambleTeams(void)
163 CTeamplayRules::PointsMayAlwaysBeBlocked(void)
164 CCSGameRules::SpawningLatePlayer(CCSPlayer *)
165 CCSGameRules::SetAllowWeaponSwitch(bool)
166 CCSGameRules::GetAllowWeaponSwitch(void)
As you can see the linux offset is 147.
For windows anything before the destructor (~Classname method) is the same as on linux. After that it will be -1. As we can see the Destructor is at offset 13 so 0-12 are the same offset in windows. Everything after is -1 in windows. So since the linux offset is clearly higher than 13 we subtract one and get an offset of 146 for windows.
In some cases your function will be what is known as an overloaded function. For example the KeyValue functions on CBaseEntity.
Code:
CBaseEntity::KeyValue(char const*, char const*) 30 29
CBaseEntity::KeyValue(char const*, float) 29 30
CBaseEntity::KeyValue(char const*, Vector const&) 28 31
Overloaded functions are functions with the same name but different parameters. For overloaded functions the rule from above still applies but the order is reversed (The first set of offsets is windows while the second line is linux in the code above). So if you want the first function, you would get the offset for the last one and apply the -1 rule if needed! With these rules you should be able to find most offset. There is however some classes where the offsets still vary for other reasons and I will go into detail on how to get them from the windows binary in part II of the tutorial.
Now on to the plugin! As you can see the return type is not given but the params are. To get the return value you can either think of what would make sense to be returned or use hex rays and the paid version of IDA to find out more info (still not always guaranteed to be correct). I happen to know that this returns an int

Let’s start by creating our gamedata file.
Code:
"Games"
{
"cstrike"
{
"Offsets"
{
"GoToIntermission"
{
"windows" "146"
"linux" "147"
}
}
}
}
Now the plugin:
PHP Code:
#pragma semicolon 1
#include <sourcemod>
#include <dhooks>
// int CCSGameRules::GoToIntermission(void)
new Handle: hGoToIntermission;
public OnPluginStart()
{
new Handle:temp = LoadGameConfigFile("test.games");
if(temp == INVALID_HANDLE)
SetFailState("Why you no has gamedata?");
new offset = GameConfGetOffset(temp, "GoToIntermission");
if(offset == -1)
SetFailState("Failed to get offset");
CloseHandle(temp);
hGoToIntermission = DHookCreate(offset, HookType_GameRules, ReturnType_Int, ThisPointer_Ignore, GoToIntermission);
}
public OnMapStart()
{
//Hook Gamerules function in map start
//Set post to true since we don’t plan to block!
DHookGamerules(hGoToIntermission, true);
}
//Since this is set to ignore remove the this param and since it has no params remove the params param
// public MRESReturn: GoToIntermission (this, Handle:hReturn, Handle:hParams) to like so.
public MRESReturn:GoToIntermission(Handle:hReturn)
{
PrintToServer("Going to intermission");
return MRES_Ignored;
}
Compile and that is all there is to it! Hope this tutorial was useful and encourages people to use DHooks

and get people more comfortable with IDA and vtable offsets. Feel free to correct me on anything that i might be wrong on.
Credits:
asherkin - Providing a better explanation of how -1 works on windows.