Bots are Currently Playing on your Account
If you've played Bango Classic server back in 2021~2022 - the characters are still around. I have been doing backups frequently in order to have the game state restored in case of critical failures. There was cron job running every week dumping entire snapshot of your achievements. Last backup is from 15/02/2022 (from the day of server closure).
I have connected top 300 accounts to my botting service and re-launched test server. The bots are playing your old accounts and they are having fun!
And they somehow know your login credentials (without exposing it to outside). How - you may ask? I'll try to explain in this post. I'll also give some tips how to secure yourself from getting scammed as a player and also how admin can mitigate such account "hacks".
Kal Stores Passwords in Plain Text
Yes. Correct me if I'm wrong, but KalOnline private servers still store account passwords in plain text*. What does it mean? Anybody who has access to the database (in the best scenario - server admins) have access to your login and password!
This is bad authentication design. In 2023 there should be no service that store passwords in plain text - this is asking for trouble. There are authentication techniques that allow storing password in encrypted way which allow system to authenticate user without checking against original password in database. It should rely on password hash instead. This makes it impossible for website/service admins to know your password but still be able to authenticate you by verifying if provided password hash matches hash during password creation. It is impossible (or incredibly expensive computationally) to reverse the hashing process with proper hashing method.
Why private servers store passwords in plain text? Because InixSoft designed it that way. It's quite hard to change it and players do not care about that anyway. You can even lookup entire kal databases leaked on elitepvpers.com and scrape passwords on your own.
* To clarify, password is obfuscated with simple character<=>byte
mapping and stored in varbinary
SQL type. It does not change a fact that the mapping is well know to almost everyone now.
Inix Way of Solving Problems
InixSoft has always had unique ways of solving vulnerabilities in their systems. I would bet they still store passwords in plain text seeing that they introduced secondary password system years ago. This is numerical 8-digit password which needs to be provided with a mouse after passing first password. What is really there in their backend nowadays - we could never know.
Exporting Top Account Credentials
Selecting Top 300 Bango Players
To get credentials of top 300 Bango players, first I needed to query characters with highest level in descending order. Additionally characters with the same level should be ordered by experience. MSSQL query:
USE [kal_db]
SELECT TOP 300 [Name], [Level], [Exp]
FROM [Player]
WHERE [Admin]=0 -- skip admins
ORDER BY [Level] DESC, [Exp] DESC
Name | Level | Exp | |
---|---|---|---|
1 | Little | 66 | 0 |
2 | Agent | 65 | 26094645 |
3 | MoDa | 65 | 16892965 |
4 | LucLK | 65 | 16199780 |
5 | Channy | 65 | 3530008 |
6 | Shiawase | 65 | 2635108 |
7 | CreativeZen | 65 | 460924 |
Joining Data from Other DB
Now we need to make inner join with account table. Unfortunately the table is another database. KalOnline uses 2 databases: kal_auth
for accounts info and kal_db
for player data.
To join multiple tables from different databases I have used this query:
SELECT TOP 300
player_tbl.[Name] as [name],
login_tbl.[ID] as [login],
login_tbl.[PWD] as [password]
FROM
[kal_db].[dbo].[Player] as player_tbl
JOIN
[kal_auth].[dbo].[Login] as login_tbl
ON player_tbl.[UID]=login_tbl.[UID]
WHERE [Admin]=0 ORDER BY [Level] DESC, [Exp] DESC
name | login | password | |
---|---|---|---|
1 | Little | < hidden > | 0x48DBFC |
2 | Agent | < hidden > | 0x48DBFC |
3 | MoDa | < hidden > | 0x48DBFC |
But what is this number in hex - 0x48DBFC
? This is varbinary
SQL type. This is binary data which we can simply see is 3 byte long - 0x48
, 0xDB
, 0xFC
. To convert it to plain text just map those bytes back to letters (let me know on discord if you want access to the entire mapping):
letter | byte |
---|---|
a | 0x48 |
b | 0xDB |
c | 0xFC |
d | 0x09 |
e | 0x1F |
So the final password is abc
(of course faked for purpose of this post).
Dumping 300 credentials to JSON
My botting service accepts --accounts_file
parameter which is path to JSON file with account credentials. Usually it looks like this:
[
{
"login": "bot1",
"password": "passwd",
"seed": 1306493084
},
{
"login": "bot2",
"password": "passwd",
"seed": 246375358
}
]
I have modified the SQL script to return data in JSON format instead of tabular data:
SELECT TOP 300
player_tbl.[Name] as [name],
login_tbl.[ID] as [login],
login_tbl.[PWD] as [password]
FROM
[kal_db].[dbo].[Player] as player_tbl
JOIN
[kal_auth].[dbo].[Login] as login_tbl
ON player_tbl.[UID]=login_tbl.[UID]
WHERE [Admin]=0 ORDER BY [Level] DESC, [Exp] DESC
FOR JSON PATH -- this line tells MSSQL to prepare JSON
[
{
"name": "Little",
"login": "< hidden >",
"password": "SNv8"
},
{
"name": "Agent",
"login": "< hidden >",
"password": "SNv8"
},
{
"name": "MoDa",
"login": "< hidden >",
"password": "SNv8"
}
]
Dumped password is not abc
! Why? This is base64 encoded binary array of SQL type varbinary
.
Making Bots Understand Base64 Password
Since I do not need to know players passwords, but botting service needs it, I tweaked it a little to support password_b64
field and make it automatically read base64 encoded string. The service is written in Python, therefore following scripts are in Python.
>>> import base64
>>> base64.b64decode("SNv8".encode('ascii'))
b'H\xdb\xfc'
Still not what we need, but closer. There are 3 bytes: H
, xDB
, xFC
. Looks similar to 0x48DBFC
! Now simply decode each byte with reversed mapping:
>>> ENCODE_TABLE = {
... "!": "95", "\"": "88", "#": "9D", "$": "4C", "%": "F2", "&": "3E",
... "\'": "BB", "(": "C0", ")": "7F", "*": "18", "+": "70", ",": "A6",
... "-": "E2", ".": "EC", "/": "77", "0": "2C", "1": "3A", "2": "4A",
... "3": "91", "4": "5D", "5": "7A", "6": "29", "7": "BC", "8": "6E",
... "9": "D4", ":": "40", ";": "17", "<": "2E", "=": "CB", ">": "72",
... "?": "9C", "@": "A1", "A": "FF", "B": "F3", "C": "F8", "D": "9B",
... "E": "50", "F": "51", "G": "6D", "H": "E9", "I": "9A", "J": "B8",
... "K": "84", "L": "A8", "M": "14", "N": "38", "O": "CE",
... "P": "92", "Q": "5C", "R": "F5", "S": "EE", "T": "B3", "U": "89",
... "V": "7B", "W": "A2", "X": "AD", "Y": "71", "Z": "E3", "[": "D5",
... "\\": "BF", "]": "53", "^": "28", "_": "44",
... "`": "33", "a": "48", "b": "DB", "c": "FC", "d": "09", "e": "1F",
... "f": "94", "g": "12", "h": "73",
... "i": "37", "j": "82", "k": "81", "l": "39", "m": "C2", "n": "8D",
... "o": "7D", "p": "08", "q": "4F", "r": "B0", "s": "FE", "t": "79",
... "u": "0B", "v": "D6", "w": "23", "x": "7C",
... "y": "4B", "z": "8E", "{": "06", "|": "5A", "}": "CC", "~": "62"
... }
>>> DECODE_TABLE = {v: k for k, v in ENCODE_TABLE.items()}
>>> ''.join([DECODE_TABLE[bytes([b]).hex().upper()] for b in base64.b64decode("SNv8".encode('ascii'))])
'abc'
There we go - our abc
password!
Securing Yourself from Account Hacks
Player Perspective
Now when you see how easy it is to extract passwords from database you may think twice before setting up the same password on multiple servers. This is very bad idea since most KalOnline admins know how to scrape passwords from obfuscated password field - process described above. They can use it against you to scam yourself on other servers. Change the password when jumping between servers.
Admin Perspective
The only way to change the authentication method is to alter AuthServer/MainServer applications by binary modifications. One could use technique described in previous blog post. I know at least one person who managed to successfully change it therefore it sounds do-able!
I'm out for now.
lafreak