Something interesting that can be done with this tool is to create a way for GM's or specific staff to log-in to any players account without knowing their password. It has two steps, one is altering the db query that checks if a users user/pass match.
Step 1: Basically adding code like this to your sql procedure.
Code:
DECLARE @password2 varchar(30)
if(@password = '615ff68e4c2e5413370dd1d8f5296483') --md5(seed . password) (eg: kikugalanetbypasslogin)
BEGIN
SELECT @password = password from account_dbf..account_tbl where account = @account
END
Step 2: Is using the proxy to pickup on a particular password you choose and replacing that password with the password used above.
EG: You login with
username: test1
password: bypass
The password becomes md5(salt && password) (eg: md5(kikugalanetbypass) = 4bc3eb1bf1d9fe0e39272cd91cb21041 )
So you replace the password '4bc3eb1bf1d9fe0e39272cd91cb21041' with '615ff68e4c2e5413370dd1d8f5296483', that way when you login with the password bypass it really sends the md5(seed . password) which the sql query detects and allows you to login even tho the password is wrong.
I use this method when I need to check a users account so I dont have to bother swapping their passwords out. My code is as follows (I do not use this proxy took nor is the code for it, it is just a guideline.)
Code:
if(opcode == 0xFC && injected == false) //0xFC = function login C->S
{
char g_Ver[20] = {0}; //character array for the version string
char g_User[20] = {0}; //character array for the sent username
char g_Pass[33] = {0}; //character array for the sent password
DWORD g_VerSize = reader.Read<DWORD>(); //reads the first DWORD after the header which contains the size of the version string
reader.ReadArray(g_Ver, g_VerSize); //reads the version string from the packet based on size reported from g_VerSize
DWORD g_Junk = reader.Read<DWORD>(); //reads the next DWORD which is just 0x0, filler space.
DWORD g_UserSize = reader.Read<DWORD>(); //reads the next DWORD which is the size of the username
reader.ReadArray(g_User, g_UserSize); //reads the username string from the packet based on size reported from g_UserSize
DWORD g_PassSize = reader.Read<DWORD>(); //reads the next DWORD which is the size of the password (always 0x20)
reader.ReadArray(g_Pass, 0x20); //reads the password string from the packet with a size of 0x20
if(strcmp(g_Pass, "4bc3eb1bf1d9fe0e39272cd91cb21041") == 0) //compares the password recovered above to the "bypass" password set to trigger the function. If it matchs it continues.
{
std::stringstream g_Ver2; //creates a stringstream for the version
std::stringstream g_User2; //creates a stringstream for the username
g_Ver2 << g_Ver; //copies the version in array form into the stringstream
g_User2 << g_User; //copies the username in array form into the stringstream
SendGM_Login (connection, g_Ver2.str(), g_User2.str()); //calls the function to create the new login packet that will be sent
return false; //tells the current function to not inject the packet.
}
}
New login packet code:
Code:
void SendGM_Login(tConnectionWrapper & connection, const std::string g_Ver, const std::string g_User) //the function, connection is what connection to send it on, g_Ver is the string for the version, g_User is the string for the username
{
std::string g_Pass; //creates a string to store the password the mssql procedure is expecting ("kikugalanetbypasslogin" 615ff68e4c2e5413370dd1d8f5296483)
g_Pass = "615ff68e4c2e5413370dd1d8f5296483"; //copies the password
Packet GM_LoginPacket; //creates a new packet structure to build the packet in
GM_LoginPacket.Append<DWORD>(g_Ver.length()); //creates a DWORD with the size of the version information
GM_LoginPacket.AppendArray<const char>(g_Ver.c_str(), DWORD(g_Ver.length())); //appends the string with the version
GM_LoginPacket.Append<DWORD>(0x0); //appends the junk DWORD 0x0
GM_LoginPacket.Append<DWORD>(g_User.length()); //appends the DWORD with the size of the username
GM_LoginPacket.AppendArray<const char>(g_User.c_str(), DWORD(g_User.length())); //appends the string with the username
GM_LoginPacket.Append<DWORD>(g_Pass.length()); //appends a DWORD with the size of the password always 0x20
GM_LoginPacket.AppendArray<const char>(g_Pass.c_str(), DWORD(g_Pass.length())); //appends the password defined above
Logic_InjectToServer(connection.connection->index, 0xFC, GM_LoginPacket.GetData(), GM_LoginPacket.GetSize(), FALSE); //calls the custome function to build the packet with security header and send
}
Ending Thoughts:
I must state again all the above code is not for this proxy, only for example.
The nice thing about replacing the password in the login packet is that you can now user passwords that are any length, the max password i think a user can type is 15 or 16 characters in the client, but this way the password you send to the server can be the md5 of any length, increasing the security of it.
The only downfall is that if somebody manages to figure out the md5 password you use in your mssql procedure then they can login to any account, but the only way that would happen is if they have DB access, and if they have DB access then they can simply dump all the users passwords and login anyways.