I am the former Lead GM Voidd from the VZTZ Server and a while back we had issues with some hackers that some how gained access to all GM accounts.
I conducted an investigation and found the most likely source of the issue but with further information a fix for the source could not be issued.
Instead I come up with a way to protect our GM accounts with ip account limiting with account status. Below I have attached a patch for the latest version of eqemu from the SVN.
I think I got all the code from the source correct but if anyone has issues please reply here and I will look to see if I missed any code.
I strongly encourage all server admins to add this patch to prevent someone from hacking your GM accounts. You will need to add the sql statements below and add your GM account id with your ip address in the gm_ips table.
SVN Patch (Tortoise Patch File)
Code:
Index: common/database.cpp
===================================================================
--- common/database.cpp (revision 315)
+++ common/database.cpp (working copy)
@@ -252,8 +252,43 @@
return true;
}
//End Lieka Edit
+
+ bool Database::CheckGMIPs(const char* ip_address, int32 account_id) {
+ char errbuf[MYSQL_ERRMSG_SIZE];
+ char *query = 0;
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+ if (RunQuery(query, MakeAnyLenString(&query, "SELECT * FROM `gm_ips` WHERE `ip_address` = '%s' AND `account_id` = %i", ip_address, account_id), errbuf, &result)) {
+ safe_delete_array(query);
+ if (mysql_num_rows(result) == 1) {
+ mysql_free_result(result);
+ return true;
+ } else {
+ mysql_free_result(result);
+ return false;
+ }
+ mysql_free_result(result);
+ } else {
+ safe_delete_array(query);
+ return false;
+ }
+
+ return false;
+}
+bool Database::AddGMIP(char* ip_address, char* name) {
+ char errbuf[MYSQL_ERRMSG_SIZE];
+ char *query = 0;
+
+ if (!RunQuery(query, MakeAnyLenString(&query, "INSERT into `gm_ips` SET `ip_address` = '%s', `name` = '%s'", ip_address, name), errbuf)) {
+ safe_delete_array(query);
+ return false;
+ }
+ safe_delete_array(query);
+ return true;
+}
+
sint16 Database::CheckStatus(int32 account_id)
{
char errbuf[MYSQL_ERRMSG_SIZE];
Index: common/database.h
===================================================================
--- common/database.h (revision 315)
+++ common/database.h (working copy)
@@ -140,6 +140,8 @@
int32 GetCharacterID(const char *name);
bool CheckBannedIPs(const char* loginIP); //Lieka Edit: Check incomming connection against banned IP table.
bool AddBannedIP(char* bannedIP, const char* notes); //Lieka Edit: Add IP address to the Banned_IPs table.
+ bool CheckGMIPs(const char* loginIP, int32 account_id);
+ bool AddGMIP(char* ip_address, char* name);
/*
* Account Related
Index: common/ruletypes.h
===================================================================
--- common/ruletypes.h (revision 315)
+++ common/ruletypes.h (working copy)
@@ -86,6 +86,7 @@
RULE_BOOL ( World, ClearTempMerchantlist, true) //cavedude: Clears temp merchant items when world boots.
RULE_INT ( World, AccountSessionLimit, -1 ) //Max number of characters allowed on at once from a single account (-1 is disabled)
RULE_INT ( World, ExemptAccountLimitStatus, -1 ) //Min status required to be exempt from multi-session per account limiting (-1 is disabled)
+RULE_BOOL ( World, GMAccountIPList, false) // Voidd: Check ip list against GM Accounts, AntiHack GM Accounts.
RULE_CATEGORY_END()
RULE_CATEGORY( Zone )
Index: world/client.cpp
===================================================================
--- world/client.cpp (revision 315)
+++ world/client.cpp (working copy)
@@ -1,6 +1,7 @@
#include "../common/debug.h"
#include "../common/EQPacket.h"
#include "../common/EQStreamIntf.h"
+#include "../common/misc.h"
#include <iostream>
using namespace std;
#include <iomanip>
@@ -181,6 +182,14 @@
return false;
}
+ // Voidd: Anti-GM Account hack, Checks source ip against valid GM Account IP Addresses
+ if (RuleB(World, GMAccountIPList) && this->GetAdmin() > 0) {
+ if(!database.CheckGMIPs(long2ip(this->GetIP()).c_str(), this->GetAccountID())) {
+ clog(WORLD__CLIENT,"GM Account not permited from source address %s and accountid %i", long2ip(this->GetIP()).c_str(), this->GetAccountID());
+ eqs->Close();
+ }
+ }
+
if (GetAccountID() == 0 && opcode != OP_SendLoginInfo) {
// Got a packet other than OP_SendLoginInfo when not logged in
clog(WORLD__CLIENT_ERR,"Expecting OP_SendLoginInfo, got %s", OpcodeNames[opcode]);
SQL Rule
Code:
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`) VALUES (0, 'World:GMAccountIPList', 'true');
SQL Table Structure
(The name is only for human reference but the account_id must match your GM account with your current ip address otherwise after server select your eq client will crash. You may add multiple entries for 1 gm account id with ip addresses.)
Code:
CREATE TABLE IF NOT EXISTS `gm_ips` (
`name` varchar(64) NOT NULL,
`account_id` int(11) NOT NULL,
`ip_address` varchar(15) NOT NULL,
UNIQUE KEY `account_id` (`account_id`,`ip_address`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;