There are new articles available, click to refresh the page.
Before yesterdayReverse Engineering

ViperSoftX: Hiding in System Logs and Spreading VenomSoftX

21 November 2022 at 13:04

We’ve been closely monitoring an information stealer called ViperSoftX. ViperSoftX was first reported on Twitter in 2020, and by Fortinet in the same year. Some aspects of ViperSoftX were also described previously by Colin Cowie. However, it has undergone very intensive development since then, intensifying throughout 2022. The malware authors’ constant game of hide-and-seek in which they continually improve their strategies and techniques to avoid detections shows no signs of stopping. We, therefore, decided to put the pieces together to provide a comprehensive analysis.

This multi-stage stealer exhibits interesting hiding capabilities, concealed as small PowerShell scripts on a single line in the middle of otherwise innocent-looking large log files, among others. ViperSoftX focuses on stealing cryptocurrencies, clipboard swapping, fingerprinting the infected machine, as well as downloading and executing arbitrary additional payloads, or executing commands.

One of the payloads ViperSoftX distributes is a specific information stealer in the form of a browser extension for Chromium-based browsers. Due to its standalone capabilities and uniqueness, we decided to give it its own name, VenomSoftX. The malicious extension provides full access to every page the victim visits, carries out man-in-the-browser attacks to perform cryptocurrency addresses swapping by tampering with API requests’ data on popular cryptocurrency exchanges, steals credentials and clipboard content, tampers with crypto addresses on visited websites, reports events using MQTT to the C&C server, and more.

ViperSoftX is mostly spread via cracked software such as Adobe Illustrator, Corel Video Studio, Microsoft Office, and more, commonly distributed over torrents.

Campaign overview

Since the beginning of 2022, we have protected more than 93,000 of our users. As the malware is mostly spread via torrents and software-sharing sites, ViperSoftX activity is distributed all over the world. The most impacted countries are India (7,000+), USA (6,000+), and Italy (5,000+).

Map illustrating the targeted countries since the beginning of 2022

Monetary gain

Both ViperSoftX and VenomSoftX focus on stealing cryptocurrencies from infected computers, either by scanning local files or by using more sophisticated techniques. In the table below, we show an estimation of the attackers’ total earnings for relevant cryptocurrency wallets.

Cryptocurrency Earnings in cryptocurrency ~Earning in USD
Bitcoin 5.947 BTC $116,812.81
Ethereum 5.312 ETH $7,826.13
Dogecoin 34,355.528 DOGE $3,474.47
Bitcoin Cach 9.11997194 BCH $1,021.39
Cosmos (ATOM) 65.153 ATOM $846.44
Tezos 191.445553 XTZ $241.32
Dash 4.72446445 DASH $199

Table with monetary gain (data refreshed 2022-11-08)

The amounts in the wallets ViperSoftX and VenomSoftX redirect stolen cryptocurrencies to add up to about $130,421.56, as of November 8, 2022. This is just the amount sent to cryptocurrency wallets, and doesn’t include other possible profits from other activities.

Technical analysis

Overview of the infection chain

From cracked software to fake logs

In the beginning, ViperSoftX is served to victims when they download what they believe to be cracked software. It is commonly named Activator.exe or Patch.exe. Upon execution, however, the victim is infected with ViperSoftX.

Activator.exe extraction

Activator.exe is in fact a loader that decrypts data from itself using AES in CBC mode.

The decryption algorithm performs a checksum as follows:

  1. Read 4 bytes at offset 0x24 from the end of the file which gives an offset
  2. Hash the offset value using SHA256
  3. Read the rest of the bytes (from -0x20) and compare it to the hash

If the checksum holds, the offset points to the location where the data is stored at offset+0x24 from the end of the binary. Since the data is stored from the end of the binary, offset is also the size of the data blob. This blob can be decrypted straight away using a hardcoded key as well as IV inside the binary:

Key 71C54C3BCFFCE591A70C0B5BA6448327BC975D89F3021053125F1CB9A7C0AF72

The decrypted data blob is a serialized protocol buffer structure. The structure template contains two protobuf messages as shown below:

We can use this template to deserialize the structure, revealing five different files:

  • A log file with a hidden additional payload resulting in the ViperSoftX PowerShell (see next subsections)
  • XML file for the task scheduler
  • SyncAppvPublishingServer.vbs (clean) that is used to create a scheduled task for persistence
  • Application binary (usually clean) that is supposed to be cracked
  • Manifest file

Last but not least, you can find the extraction Python script in our GitHub repository.

Analyzing the files

The most interesting file is the aforementioned “log file” which is usually more than 5 MB in size and contains a single malicious line of code (usually from 17,302 lines in total, but this might vary in different versions).

This log file is usually named and stored as:
although we saw variants dropping the same “log” files disguising as a “driver” or a “text” file, e.g.
where the subfolder names and the GUIDs are randomized.

An example of such a malicious line can be found below. As we will see later, this line actually contains a script that is decoded and executed.

Example of the log file with a single malicious line on the line 17,034

The malware creates a scheduled task using the legacy SyncAppvPublishingServer.vbs script for executing these hidden scripts afterward as well for ensuring persistence. Note that the line number varies depending on the malware configuration provided in the scheduled task.

Hidden Script Variants

We have seen two variants of hidden scripts lurking in the logs so far.

The first variant is a simple dropper that downloads another payload from a hardcoded C&C server and executes it. We have only seen the ViperSoftX information stealer downloaded as a payload so far. We will cover the stealer separately in the subsection below.

The first variant of the PowerShell script – simple dropper

The second variant is in the form of a PowerShell script without the encoding. This script contains two parts, the first one is a set of decryption functions and the second is an encrypted data blob.

Example of the log file with a single malicious line on line 17,034 (second variant)

The script uses AES in CBC mode to decrypt the payload, the ViperSoftX stealer. The AES key is passed via the command line by the scheduled task created by Activator.exe. You can find the decryption process in this CyberChef template.

ViperSoftX Information Stealer

When the payload is dropped, an obfuscated ViperSoftX PowerShell script is presented to us. We have seen multiple variants of ViperSoftX, suggesting that the malware is under active development. In the text below, we will cover the stealer’s features as a whole.

Stealing Capabilities

First, let’s have a look at what ViperSoftX is actually capable of stealing. ViperSoftX performs fingerprinting of the infected machine, focusing on various types of information, including:

  • Computer name
  • Username
  • OS information and its architecture
  • Installed antivirus or other security software and whether the solution is active

The malware focuses on stealing cryptocurrencies. To do so, it searches the typical locations for web browser extensions and locally stored wallets. The full list of locations can be found in the Appendix. More generic information, such as OS, architecture, and username, is obtained using WMI and system variables.

ViperSoftX searches for cryptocurrencies stored locally on the infected device in cryptocurrency software and browser extensions, and monitors the clipboard for cryptocurrency wallet addresses to perform clipboard swapping.

The gathered data, as well as the fingerprint, is then concatenated together into a single string, encoded by base64, and sent to the hardcoded C&C server in the User-Agent HTTP header. Note that C&C servers vary across versions:

Each time the victim copies anything to their clipboard, ViperSoftX scans the content using predefined regular expressions in the background. If the expression matches one of the configured wallet addresses belonging to the specific cryptocurrency, the malware replaces the content with the attacker’s address and sends a notification to the C&C server in the X-Notify HTTP header in a form of three values:
Cryptocurrency type - victim’s address - attacker’s address

The cryptocurrency type reflects what type of cryptocurrency was matched and can be one of the following: BTC, BCH, BNB, ETH, XMR, XRP, DOGE, or DASH.

The malware also checks title texts of opened windows (MainWindowTitle property) and if it spots an application focused on cryptocurrencies or finance, it logs its presence into:
The full list of searched keywords can be found in the Appendix.

On top of the information-stealing core, ViperSoftX provides RAT functionalities as well, like executing arbitrary commands on the command line, downloading an additional arbitrary payload provided by the C&C server, and executing it, as well as removing itself completely from the system. The RAT functionality can also be used, for example, to steal cryptocurrencies from their locations, which it previously identifies and sends to the C&C server.

Host Header Spoofing

Aside from trying to steal cryptocurrencies, the malware spoofs host headers to obfuscate its communication with the C&C servers.

The spoofed host header consists of five to 10 lowercase alpha letters, but the true destination is in the hardcoded $meta_host variable. This way, the real C&C server address is obfuscated by a random-looking domain that doesn’t exist.

VenomSoftX Browser Extensions

Newer versions of ViperSoftX information stealer are capable of loading a custom malicious browser extension to Chromium-based browsers installed on infected computers. The extension is provided by the C&C server. The extension is basically another standalone information stealer, we are calling VenomSoftX, but is installed by ViperSoftX, as described below. The extension disguises itself as various popular browser extensions to avoid user detection. 

The main goal VenomSoftX is also to steal cryptocurrencies from the unsuspecting victim. The difference is, VenomSoftX mainly does this by hooking API requests on a few very popular crypto exchanges victims visits/have an account with. When a certain API is called, for example, to send money, VenomSoftX tampers with the request before it is sent to redirect the money to the attacker instead. Although similar in principle to what information stealers like ViperSoftX do and rather a common clipboard swapping, this technique is performed at a lower level, meaning the victim has little to no chance of noticing the money is being transferred elsewhere.

Installing the extension

ViperSoftX’s approach is simple. The malware downloads a VenomSoftX PowerShell installer from the C&C server e.g. by base64-decoding a hardcoded request metadata directly from the PowerShell script, following a request to:
depending on the malware version. This can hold different payloads, but we will focus on the VenomSoftX browser extension.

After the installer script is downloaded from the C&C server and the VenomSoftX browser extension is extracted, the installer searches several locations for .lnk files and if such a link file belongs to Chrome, Brave, Opera, or Edge, it modifies the link file with a parameter --load-extension=<path_to_the_malicious_extension>. This way, when the user starts their favorite browser, they actually load the malicious extension with it.

These locations are checked for link files leading to browsers:

  • %USERPROFILE%\Desktop
  • %USERPROFILE%\OneDrive\Desktop
  • %PUBLIC%\Desktop
  • %ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs
  • %APPDATA%\Microsoft\Windows\Start Menu\Programs
  • %APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar

The extension ID is randomly generated, provided random lowercase characters to represent the extension folder and randomly generated version number of the extension:

$(Get-Random -Minimum 1 -Maximum 10).$(Get-Random -Minimum 1 -Maximum 10).$(Get-Random -Minimum 1 -Maximum 10)._0

We observed further versions of ViperSoftX containing a full update mechanism. These versions were able to walk through the modified .lnk files, parse the manifest.json file of the malicious extension, and when an older version or an old write timestamp of the extension was detected on the infected system than what was advertised by the C&C server, the malware requested an update and replaced the extension files to the newest version by a dedicated command (provided by the C&C server).

The extension tries to disguise itself as well known and common browser extensions such as Google Sheets. In reality, the VenomSoftX is yet another information stealer deployed onto the unsuspecting victim with full access permissions to every website the user visits from the infected browser.

Diving into javascript shenanigans

The extension contains several files, as shown in the table below. Each file has a different purpose:

File name Purpose
128.png Google Sheets icon to disguise the extension
content.bootstrap.js Orchestrates the malicious activity, sends results using MQTT
manifest.json The extension’s manifest file
rules.json URL filter rules for Kucoin
webpack_block.js Request tampering and credential theft on
webpack_bnb.js Request tampering and cryptocurrency theft on Binance
webpack_cb.js Request tampering and cryptocurrency theft on Coinbase
webpack_common.js Contains an address book for pattern matching, clipboard monitoring and stealing
webpack_content.js When no exchange is detected – attacker’s addresses are replaced on the visited websites with the ones that victim entered earlier
webpack_gt.js Request tampering and cryptocurrency theft on
webpack_kuc.js Request tampering and cryptocurrency theft on Kucoin

The malware focuses on five big cryptocurrency exchanges, reflected by abbreviations in the modules names.

In the paragraphs below, we’ll focus on what each of the modules do, in detail.


This module is the starting point of VenomSoftX and it is loaded with every site visit. It orchestrates what modules to load and it is also responsible for sending stolen data to the C&C server.

The scripts are loaded depending on the visited domain. The bootstrap checks what site is being loaded and if it is one of the following:, Binance, CoinBase,, or Kucoin, the module loads an appropriate “webpack”. If the user is on any other site, webpack_content.js is loaded. The module webpack_common.js is loaded by default regardless of which site the victim visits.

Determination process which modules to load (deobfuscated)

All the modules serve their own purpose. However, two of the modules, webpack_common.js and webpack_block.js, are capable of sending data back to the collector server using a Paho MQTT client present in the content.bootstrap.js. The MQTT client has an event listener set to a hardcoded value b8b0becb-080a-46af-9688-e3671fcc4166 that indicates data should be sent to an MQTT broker, harvesting the data:
Note that the collector address can vary in different versions.

The sent data are structured as follows:
MSG: time: <utc_datetime>
ip: <client_ip>
data: <data>

The time and ip fields are obtained using a public service (

The data field is either clipboard content with a cryptowallet and other metadata, or stolen credentials for See further sections below for details.


The “common” module is loaded to every website, regardless of whether it is a cryptocurrency exchange or something else. It is used to define an address book with regular expression patterns, which is used for crypto address matching on several occasions in other modules.

The structure of the address book is in the form of a dictionary, where the key is the regular expression and the value is yet another dictionary containing three values: coin, address, and network. An example of such an address book can be seen in the below snippet. For the full address book, see Appendix.

A snippet of a possible address book (incomplete, deobfuscated)

Furthermore, each time the user pastes anything into any website (except the malicious address from the address book), this module checks whether the clipboard content matches any of the regular expressions from the address book and if they do, it sends the following data to the collector server encoded using base64. The data for the MQTT message has a following construct:


Site: <URL>
Browser: <USER-AGENT>


This module monitors two input elements for content the user fills into websites and it is loaded when the victim is outside of any of the mentioned exchange sites:

  • HTMLInputElement
  • HTMLTextAreaElement

This is done by implementing hooks in getters of these elements to try to find a compatible crypto address for the user’s input and if found, the malware creates new localStorage entry with a combination of visited site and the compatible address:
website_attackerAddress: userAddress
The compatible address is found when the provided address matches any of the regular expressions from the address book described in the previous section.

After that, a custom MutationObserver watches for dynamic changes in the site (e.g. loading the page, displaying a message sent earlier in the user’s messenger client, etc.) and if such a change occurs, the malware replaces all mentions of the malicious address (if found) with the user’s address using the localStorage. This effectively hides all traces of the malicious address in the website’s body.

Note that since the information is stored directly in the persistent localStorage, the functionality survives a browser restart, PC restart, or re-visiting the page anytime in the future. The victim has to clear the user data in their browser or uninstall the malware extension altogether to get rid of the malicious behavior.

For the sake of demonstrating this behavior, we decided to create a demo PoC of a simple page that illustrates the whole process of content tampering while the malware is active:

  1. First two buttons, Attacker address and User address, fill in attacker address or user address to the “Dynamic content to be pasted” line on the webpage
  2. Get value” button triggers the getter but the hook is inactive since the provided address doesn’t match cryptowallet address pattern (it is too short)
  3. The user fills in a compatible address
  4. Get value” button creates a localStorage entry with the entered address
  5. The attacker’s address is always replaced with the one from localStorage
Demo illustrating the content tampering process

Draining the victim’s accounts

As we already mentioned, VenomSoftX focuses on five different crypto exchanges/websites, namely on, Binance, Coinbase,, and Kucoin.

In these modules, the malware tries to tamper with API requests the sites use for several actions like money withdrawal or sending security codes. This is done by creating hooks on the API calls (or rather functions responsible for sending those requests), parsing their structure, and replacing the response’s body with the desired attacker’s contents – commonly meaning that the recipient’s address is replaced by the attacker’s one as well as the amount is set to the value of an available account balance if known. The request is then processed by the malware without the user noticing anything, completely draining the victim’s portfolio as a result.

Note that the user has little to no chance to notice this. In comparison to a rather common clipboard swapping, for example, this “swapping” is performed on a lower level. Since the transactions on blockchains/ledgers are inherently irreversible, when the user checks the transaction history of payments afterward, it is already too late.

The complete list of hooked API functions can be found in the Appendix. The only file that differs from the rest is webpack_block.js. This module focuses on and it tries to hook It also modifies the getter of the password field to steal entered passwords. Once the request to the API endpoint is sent, the wallet address is extracted from the request, bundled with the password, and sent to the collector as a base64-encoded JSON via MQTT.

Since the rest of the hooks are the same on the higher level if we ignore API differences, we will use the Binance module as an illustration example.

The Binance module recognizes six different API calls invoking malicious interactions. When the user logs in to the site, it is expected that at some point the API function
will be called. VenomSoftX then parses and saves all assets available on the victim’s account. When the user tries to manipulate their savings, e.g. withdrawing their money, the malware intercepts the request
and tampers with the request body, modifying the address to redirect the money to the attacker’s address if a compatible attacker’s address was found. The amount of the request is also set to the maximum amount available obtained by the previous step. After the tampering, the request is passed further like nothing happened, effectively draining the victim’s wallet.


In this blog post, we took a closer look at ViperSoftX, a long-standing information stealer, and its malicious browser extension payload VenomSoftX. We described both information stealers’ infection chains, and how the original payload hides and decrypts on the infected system.

We described what both ViperSoftX and VenomSoftX steal and how the browser extension leverages its full access to every page the victim visits and carries out man-in-the-browser attacks by silently tampering with the API requests popular cryptocurrency exchanges use, resulting in draining the victim’s accounts.

Indicators of Compromise (IoC)


File name SHA256
Activator.exe e1dc058fc8282acb95648c1ee6b0bc36b0d6b5e6853d4f602df5549e67d6d11a
Hidden log script first variant 0bad2617ddb7586637ad81aaa32912b78497daf1f69eb9eb7385917b2c8701c2
Hidden log script second variant 0cb5c69e8e85f44725105432de551090b28530be8948cc730e4b0d901748ff6f
ViperSoftX PowerShell 23b9075dac7dbf712732bb81ecd2c21259f384eb79ae8fdebe29b7c5a12d0519
ViperSoftX’s browser installer 5c5202ed975d6647bd157ea494d0a09aac41d686bcf39b16a870422fa77a9add


File name SHA256
content.bootstrap.js 3fe448df20c8474730415f07d05bef3011486ec1e070c67683c5034ec76a2fcb
manifest.json 0de9a23f88b9b7bda3da989dce7ad014112d88100dceaabca072d6672522be26
rules.json 1d6845c7b92d6eb70464a35b6075365872c0ae40890133f4d7dd17ea066f8481
webpack_block.js 7107ab14a1760c6dccd25bf5e22221134a23401595d10c707f023f8ca5f1b854
webpack_bnb.js ddee23e2bfd6b9d57569076029371e6e686b801131b6b503e7444359d9d8d813
webpack_cb.js 947215a1c401522d654e1d1d241e4c8ee44217dacd093b814e7f38d4c9db0289
webpack_common.js 7b75c1150ef10294c5b9005dbcd2ee6795423ec20c512eb16c8379b6360b6c98
webpack_content.js d7dfc84af13f49e2a242f60804b70f82efff7680cddf07f412667f998143fe9c
webpack_gt.js 4da1352e3415faa393e4d088b5d54d501c8d2a9be9af1362ca5cc0a799204b37
webpack_kuc.js 705deecbbb6fd4855df3de254057c90150255c947b0fb985ea1e0f923f75a95f




Scripts and tools

  • Extractor for ViperSoftX’s initial payloads (commonly named Activator.exe)
  • CyberChef template for decrypting the second variant of the hidden scripts in logs

List of wallet addresses

Cryptocurrency Address
ADA addr1q9c27w7u4uh55sfp64ahtrnj44jkthpe7vyqgcpt73z9lrq7fw3juld8k2ksz2p82tv45j8yc5wzqmr4ladxyt0vjxrsf33mjk
ATOM cosmos1mcah8lel6rxhlqsyrzpm8237cqcuzgyw70nm6f
BNB bnb1u64a2n3jhw4yh73s84rc58v8wxrwp7r8jwakpr
BNB bnb1vmwl54jxj9yvsgz33xtyuvqnurdjy2raqnttkq
BTC 1L8EBHDeiHeumtcpcroaxBceXnWFiYU5dh
BTC 1Pqkb4MZwKzgSNkaX32wMwg95D9NfW9vZX
BTC 32Wx3dsHCCxyJZLwseFYkgeFqVk16tCCcF
BTC 3JvBvRuBfYvB6MjzMornj9EQpxhq9W7vXP
BTC bc1qn6ype8u5kgj672mvsez9wz9wt9wk22tzd5vprp
BTC bc1qxgz2g8kn2kg0wqqrmctyxu5n925pnwphzlehaw
BTC qq9yrhef7csy3yzgxgs0rvkvez440mk53gv8ulyu6a
BTC qqh3g98z60rdl05044xxt7gkgncezmdfy5tja99z53
DASH Xtwj8uGx77NYBUki1UCPvEhe4kHYi6yWng
DOT 122zNSYNN2TSR2H5wBCX16Yyvq7qLFWo1d6Lvw2t9CNxMxt1
DOGE DDxhfK5wbJkRN25mAbBYk3ND4xLjiMRyNq
ETH 0x12507F83Dde59C206ec400719dF80D015D9D17B6
ETH 0x884467182849bA788ba89300e176ebe11624C882
KAVA kava1emxzwjw84e0re7awgue9kp4gseesyqrttg69sm
SOL 7j5bxiFPSsScScBEjLj9qud5Yc2CqXGmembX3hQBdFTd$
XMR 475WGyX8zvFFCUR9ufThrNRtJmzmU13gqH9GV2WgAjbR7FgRVCWzokdfVf2hqvRbDBaMzBm1zpDiBTpBgxLt6d7nAdEEhC4
XMR 48qx1krgEGzdcSacbmZdioNwXxW6r43yFSJDKPWZb3wsK9pYhajHNyE5FujWo1NxVwEBvGebS7biW9mjMEWdMevqMGmDJ6x
XRP rH6dyKWNpcvFz6fQ4ohyDbevSxcxdxfSmz
XRP rpzn8Ax7Kz1A4Yi8KqvzV43KYsa59SH2Aq
XTZ tz1g6rcQAgtdZc8PNUaTUzrDD8PYuCeVj4mb
ZEC t1XjiZx8EydDDRuLisoYyVifcSFb96a3YBj
ZIL zil1aw3kyrymt52pq2e4xwzusdfce9e5tmewvshdrm

Also in our GitHub.


List of monitored cryptocurrency locations

Complete list in our Github.

List of monitored window titles

Monitored window titles

Also in our GitHub.


Address book

Complete list in our Github.

List of hooked API calls

The post ViperSoftX: Hiding in System Logs and Spreading VenomSoftX appeared first on Avast Threat Labs.

PNG Steganography Hides Backdoor

10 November 2022 at 12:15

Our fellow researchers from ESET published an article about previously undocumented tools infiltrating high-profile companies and local governments in Asia. The tools, active since at least 2020 are designed to steal data. ESET dubbed them Worok. ESET monitored a significant break in activity from May 5, 2021 to the beginning of 2022. Nevertheless, when Worok became active again, new targeted victims – including energy companies in Central Asia and public sector entities in Southeast Asia – were infected to steal data based on the types of the attacked companies.

The researchers from ESET described two execution chains and how victims’ computers are compromised. The initial compromise is unknown, but the next stages are described in detail, including describing how the final payload is loaded and extracted via steganography from PNG files. However, the final payload has not been recovered yet. Detailed information about Worok, chains, and backdoor commands can be found in the ESET’s article Worok: The big picture.

Our analysis aims to extend the current knowledge of ESET research. We have captured additional artifacts related to Worok at the end of the execution chain. The PNG files captured by our telemetry confirm that the purpose of the final payload embedded in these is data stealing. What is noteworthy is data collection from victims’ machines using DropBox repository, as well as attackers using DropBox API for communication with the final stage.

Compromise Chain

We intend to remain consistent with the terminology set by ESET’s research. Our research also has not discovered the whole initial compromise of the malware. However, we have a few new observations that can be part of an infiltrating process.

Figure 1 illustrates the original compromise chain described by ESET. In some cases, the malware is supposedly deployed by attackers via ProxyShell vulnerabilities. In some corner cases, exploits against the ProxyShell vulnerabilities were used for persistence in the victim’s network. The attackers then used publicly available exploit tools to deploy their custom malicious kits. So, the final compromise chain is straightforward: the first stage is CLRLoader which implements a simple code that loads the next stage (PNGLoader), as reported by ESET.

Figure 1. Worok compromise chain
Initial Compromise

The specific initial attack vector is still unknown, but we found four DLLs in compromised machines containing the code of CLRLoader. Our detections captured a process tree illustrated in Figure 2.

Figure 2. Process tree running CLRLoader

This process tree was observed for WLBSCTRL.DLL, TSMSISrv.DLL, and TSVIPSrv.DLL. The mutual process that executes the DLLs is svchost -k netsvcs. Therefore, the initial process is SvcHost introducing a Windows service. The DLL files help us to identify two Windows services, namely IKE and AuthIP IPsec Keying Modules (IKEEXT) and Remote Desktop Configuration (SessionEnv). Both services are known for their DLL hijacking of DLL files missing in the System32 folder by default, SCM and DLL Hijacking Primer.

Lateral movement

The DLL hijacking in the System32 folder is not a vulnerability by itself because the attackers need administrator privileges to write into it. However, we assume the existence of an implemented reverse shell with administrator privileges as a consequence of the initial compromise. In that case, the attacker can efficiently perform the lateral movement via Service Control Manager (SVCCTL).

In short, the attackers place the hijacked DLL files into %SYSTEMROOT%\System32 and then start an appropriate service remotely. 

List of abused Windows services and their DLL files:

  • IKE and AuthIP IPsec Keying Modules
    • C:\Windows\System32\WLBSCTRL.dll
  • Remote Desktop Configuration
    • C:\Windows\System32\TSMSISrv.dll
    • C:\Windows\System32\TSVIPSrv.dll

The second observed DLL hijacked is related to VMware machines. The attackers can misuse the hijacking of vmGuestLib.dll, which is used by the WMI Performance Adapter (WmiApSrv) service to provide performance information.

On system start, WmiApSrv loads vmStatsProvider.dll, which tries to call vmGuestLib.dll from %ProgramFiles%\VMware\VMware Tools\vmStatsProvider\win32 as the first one. However, the original library is located at %SYSTEMROOT%\System32. Hence, if the attackers place vmGuestLib.dll into the %ProgramFiles% location, it also leads to DLL hijacking.

These two approaches are probable scenarios of how CLRLoader can be executed, and the compromise chain shown in Figure 1 launched. The elegance of this approach is that attackers do not have to create a new service that may reveal suspicious activities. The attackers abuse only export functions of hijacked DLLs, whose empty reimplementation does not cause an error or any other indicator of compromise. Moreover, the persistence of CLRLoader is ensured by the legitim Windows services.


CLRLoader is a DLL file written in Microsoft Visual C++. It implements the DllMain method, which is responsible for loading the next stage (.NET variant of PNGLoader). The rest of the exported functions correspond to the interfaces of the hijacked DLLs, but the implementation of the export functions is empty. So, invoking this function does not cause a crash in the calling processes. Just for completeness, the hijacked files also contain digital signatures of the original DLL files; naturally, the signature is invalid.

CLRLoader is activated by calling LoadLibraryExW from an abused process/service. LoadLibraryExW is called with zero dwFlags parameters, so the DllMain is invoked when the malicious DLL is loaded into the virtual address space. An example of the CLRLoader code can be seen in Figure 3.

Figure 3. DllMain of hijacked DLL

CLRLoader checks the presence of the .NET DLL file containing PNGLoader, creates a mutex, and finally executes PNGLoader via CorBindToRuntimeEx API.

We recognized two variants of PNGLoader with the entry points as follow:

  • Jsprofile.Jspfilter (Setfilter)
  • pngpcd.ImageCoder (PngCoder)


The second stage (PNGLoader) is loaded by CLRLoader or, as reported by ESET, by PowHeartBeat. We do not see any code deploying PNGLoader on infiltrated systems yet, but we expect to see it in a similar manner as the lateral movement.

PNGLoader is a loader that extracts bytes from PNGs files and reconstructs them into an executable code. PNGLoader is a .NET DLL file obfuscated utilizing .NET Reactor; the file description provides information that mimics legitimate software such as Jscript Profiler or Transfer Service Proxy.

The deobfuscated PNGLoader code includes the entry point (Setfilter) invoked by CLRLoader. There is a hardcoded path loader_path that is searched for all PNG files recursively. Each .png file is verified to the specific bitmap attributes (height, width) and steganographically embedded content (DecodePng). The Setfilter method is shown in Figure 4.

Figure 4. The Setfilter method of PNGLoader

The steganographic embedding relies on one of the more common steganographic techniques called least-significant bit (LSB) encoding. In general, this method embeds the data in the least-significant bits of every pixel. In this specific implementation, one pixel encodes a nibble (one bit per each alpha, red, green, and blue channel), i.e. two pixels contain a byte of hidden information, as illustrated in Figure 5. While this method is very easy to detect by a simple statistical analysis, such change in pixel value is hardly perceivable by the naked eye.

Figure 5. Byte reconstruction from 2 pixels

The steganographically embedded content is then extracted in four steps as follows.

  • The first 16 bytes (32 pixels) of the PNG file are extracted, and the first 8 bytes must match a magic number. This check is performed due to the computational complexity necessary to pull the rest of the pixels (approx. hundreds of thousands of pixels). The following 8 bytes then represent the length of the embedded payload.
  • The following extracted data is an encrypted payload in Gzip format.
  • The extracted payload is decrypted using a multiple-byte XOR hard-coded in PNGLoader.
  • The result of XORing is Gzip data that is un-gzipped.

The result of these steps is the final payload steganographically embedded in the PNG file.

Steganographically Embedded Payload

If PNGLoader successfully processes (extract → decode → unpack) the final payload, it is compiled in runtime and executed immediately. Our telemetry has picked up two variants of PNGLoader working with the magic numbers recorded in Figure 6.

Figure 6. Data structure embedded in PNG bitmap

The first payload implementation is a PowerShell script, as demonstrated by the code fragment of PNGLoader in Figure 7. Like our ESET colleagues, we have no sample of this payload yet, but we expect a similar function as the second payload implementation described below.

Figure 7. Code fragment of PNGLoader executing the PowerShell payload

The second payload implementation is .NET C# compiled and executed via the CompileAssemblyFromSource method of the CSharpCodeProvider class, see Figure 8.

Figure 8. Execution of C# payload embedded in PNG bitmap

The .NET C# payload has a namespace Mydropbox, class Program, and method Main. The namespace indicates that the payload operates with DropBox. Our telemetry captured a few PNG files, including the steganographically embedded C# payload.

PNG Files

At first glance, the PNG pictures look innocent, like a fluffy cloud; see Figure 9. Our telemetry has captured three PNG pictures with the following attributes: 

  • Size: 1213 x 270 (px)
  • Bit Depth: 8, Color Type: 6 (RGB + Alpha)
Figure 9. Malicious PNG file with steganographically embedded C# payload

As we mentioned before, malware authors rely on LSB encoding to hide malicious payload in the PNG pixel data, more specifically in LSB of each color channel (Red, Green, Blue, and Alpha). Let us have a look at their bit-planes. Figure 10 shows one of the higher bit planes for each color channel; notice that each of these images looks far from random noise. If we had a look at an image without data embedded in its LSB, we would usually see similar patterns.

Figure 10. One of the RGB bit-planes without hidden data

Now, to put it into contrast, let us have a look at LSB bit-planes. Figure 11 shows LSB bit-planes for every channel of the PNG image with the embedded encrypted (and compressed) payload. Recall that both encryption and compression should usually increase the entropy of the image. Therefore, it should be no surprise that LSB bit-planes of such an image look like random noise. It is evident that the whole canvas of LSB bit-planes is not used.

Figure 11. Zero (LSB) bit-plains channels with embedded data

The payload occupies only pixels representing the payload size, and the rest are untouched; see the algorithm below.

In this specific case, the PNG files are located in C:\Program Files\Internet Explorer, so the picture does not attract attention because Internet Explorer has a similar theme as Figure 12 shows.

Figure 12. Example of graphic Internet Explorer theme


At this time, we can extend the ESET compromise chain by the .NET C# payload that we call DropBoxControl – the third stage, see Figure 13.

Figure 13. Extended compromise chain

DropBoxControl is a backdoor that communicates with the attackers via the DropBox service. Noteworthy, the C&C server is a DropBox account, and whole communications, such as commands, uploads, and downloads, are performed using regular files in specific folders. Therefore, the backdoor commands are represented as files with a defined extension. DropBoxControl periodically checks the DropBox folder and executes commands based on the request files. The response for each command is also uploaded to the DropBox folder as the result file.

The text below describes the individual DropBoxControl components and the whole backdoor workflow.

DropBox API

DropBoxControl implements the DropBox communication by applying the standard API via HTTP/POST. There is a dedicated class, DropBoxOperation, wrapping the API with the method summarized in Table 1. A DropBox API key, sent in the HTTP header, is hard-coded in the DropBoxControl sample but can be remotely changed.

DropBoxControl Method API
Table 1. DropBox API implemented by DropBoxControl

The attackers control the backdoor through ten commands recorded in Table 2.

Command Description
cmd Run cmd /c <param> & exit, the param is sent by the attackers.
exe Execute a defined executable with specific parameters.
FileUpload Download data from the DropBox to a victim’s machine.
FileDownload Upload data from a victim’s machine to the DropBox.
FileDelete Delete data from a victim’s machine.
FileRename Rename data from a victim’s machine.
FileView Sent file information (name, size, attributes, access time) about all victim’s files in a defined directory
ChangeDir Set a current directory for the backdoor
Info Send information about a victim’s machine to the DropBox
Config Update a backdoor configuration file; see Configuration
Table 2. Backdoor commands

The Info command sends basic information about an infiltrated system as follows:

  • ClientId hard-coded in each DropBoxControl sample
  • Version of DropBoxControl sample (seen
  • Hostname of a victim’s machine
  • List of all victim’s IPs
  • Version and file size of explorer.exe
  • Windows architecture
  • List of hard drivers, including total size, available free space, and drive type
  • The current time of victim’s machine

DropBoxControl, the object of this study, uses three files located on C:\Program Files\Internet Explorer. The file names try to look legitimate from the perspective of the Internet Explorer folder.


This file contains the DropBoxControl configuration that is encrypted. It configures four variables as follows:

  • DropboxId: API key used for authorization
  • Interval: how often the DropBox disk is checked
  • UpTime/DownTime: defines the time interval when the backdoor is active (seen 7 – 23)

See the example of the configuration file content:
Bearer WGG0iGT****AAGkOdrimId9***QfzuwM-nJm***R8nNhy,300,7,23


The iexplore.log file is a log file of DropBoxControl which records most actions like contacting the DropBox, downloading/uploading files, configuration loading, etc. Log entities are logged only if a sqmapi.dat file exists. The login engine is curiously implemented since the log file is not encrypted and contains decrypted data of the ieproxy.dat file.


DropBoxControl encrypts the configuration file (actually without effect), and the DropBox communication. The config file is encrypted using multi-byte XOR with a hard-coded key (owe01zU4). Although the API communication is encrypted via HTTPS, data stored on the DropBox is encrypted by its own algorithm.

The data is encrypted using another hard-coded byte array (hexEnc), TaskId, and ClientId. Moreover, TaskId is used as an index to the hexEnc array, and the index is salted with ClientId in each iteration; see Figure 14. It is similar to the algorithm used by PowHeartBeat, as described in the ESET report.

Figure 14. Encryption algorithm used for DropBox files
DropBox Files

As we mentioned above, the communication between the backdoors and the attackers is performed using the DropBox files. In general, DropBox files that contain valuable information are encrypted. Each file, in addition to the data itself, also includes flags, the task type (command), and other metadata, as seen in Figures 15 and Table 3.

Figure 15. The file structure of DropBox files
Item Length Description
EncType 1 Flag – data in the file is encrypted
GzipType 1 Flag – data in the file is gzipped
TaskType 2 Command type
DataLen 4 Data length
Table 3. DropBox file header

Returning to the DropBox files, we explore a DropBox file structure of the DropBox account. A root folder includes folders named according to the ClientId that is hard-coded in the DropBoxControl sample; more precisely, in the PNG file.

Each client folder holds a time.txt file which includes a number that is a count of the backdoor iteration. One iteration means contacting and processing an appropriate client folder in the DropBox repository.

The attackers specify the task type and eventual parameters. The task type and parameters are then packed using the file header and uploaded into the appropriate client folder as a request file (.req). Further analysis found that the backdoor processes its .req files and creates a result file (.res) as a response for each request file. The result file has the same file structure shown in Figure 15, but data, data length, and task type have different values, since returned data contains requested (stolen) information.

Comparing all DropBox folders (Figure 16), we determined the name of the request and result files in this form: [0-9]+-[0-9]+. The filename is used for request/response identification and also for data encrypting.

For example, let’s use the request file name 31-1233.req. The IDMessage is 31-1233 and TaskId is 1233. So, the data is encrypted using the ClientId and TaskId, plus hard-coded hexEnc; see Encryption.

Figure 16. List of DropBox files
DropBoxControl Workflow

We defined and described the basic functionality of DropBoxControl in the sections above. Therefore, we can summarize all this knowledge into a backdoor workflow and outline the whole process of data collecting, uploading, downloading, and communicating with the DropBox repository.

In the beginning, PNGLoader extracts the stenographically embedded DropBoxControl and invokes the Main method of the C# Mydropbox.Program class. DropBoxControl then decrypts and loads the configuration file containing the DropBox API key. Most of the actions are recorded in the log file.

If the current time is between UpTime and DownTime interval, DropBoxControl is active and starts the main functionality. It contacts the DropBox repository and uploads the time.txt file into the client folder. If the time.txt upload is successful, the backdoor downloads a list of all files stored in the client folder. The file list is iterated, and each request (.req) file is downloaded and processed based on the tasks type (command). DropBoxControl executes the command and creates the result file (.res) with the requested information. The resulting encrypted file is uploaded back into the client folder. Finally, the processed request (.req) file is deleted.


The victims we saw targeted in this campaign are similar to those that ESET saw. The victims of this campaign were companies and government institutions in Asia and North America, namely Mexico. Vietnam and Cambodia are the other countries affected by DropBoxControl. One of the DropBoxControl connections was monitored from an IP associated with the Ministry of Economic Development of Russia.


The third stage of the compromise chain is represented by the C# implementation of DropBoxControl. The DropBoxControl functionality allows attackers to control and spy on victims’ machines. Moreover, the backdoor has access to the Program Files folder, so we expect it to run under administrator privileges. The most common command observed in log files is obtaining information about victims’ files, followed by data collecting.

The typical command for the data collecting is via the cmd command; see the example below:

rar.exe a -m5 -r -y -ta20210204000000 -hp1qazxcde32ws -v2560k Asia1Dpt-PC-c.rar c:\\*.doc c:\\*.docx c:\\*.xls c:\\*.xlsx c:\\*.pdf c:\\*.ppt c:\\*.pptx c:\\*.jpg c:\\*.txt >nul

The attacks focus on collecting all files of interest, such as Word, Excel, PowerPoint, PDF, etc. They recursively search the files in the C:\ drive and pack them into an encrypted rar archive, split into multiple files.

Another command decrypted from the request file executes Ettercap, which sniffs live network connections using man-in-the-middle attacks; see the command below:

ettercap.exe -Tq -w a.cap -M ARP / //

The attackers can sniff network communications and intercept user credentials sent via, e.g., web pages.

In short, DropBoxControl is malware with backdoor and spy functionality.

DropBox Account

Our telemetry has captured these three DropBox APIs:

Bearer gg706X***************Ru_43QAg**********1JU1DL***********ej1_xH7e
Bearer ARmUaL***************Qg02vynP**********ASEyQa***********deRLu9Gx
Bearer WGG0iG***************kOdrimId**********ZQfzuw***********6RR8nNhy

Two keys are registered to “Veronika Shabelyanova” ([email protected][.]com) with Chinese localization. The email is still active, as well as the DropBox repository. The user of the email is a Slavic transcription of “Вероника Шабелянова”.

The third DropBox repository is connected with a Hong Kong user “Hartshorne Yaeko” ([email protected][l].com)

DropBox Files

We are monitoring the DropBox repositories and have already derived some remarkable information. The DropBox accounts were created on 11 July 2019 based on README files created on account’s creation.

At this time, there is only one DropBox repository that seems to be active. It contains seven folders with seven time.txt files, so there are seven active DropBoxControl instances, since the time.txt files have integers that are periodically incremented; see DropBox Files. Moreover, the integer values indicate that the backdoors run continuously for tens of days. Regarding the backdoor commands, we guess the last activity that sent request files was on 1 June 2022, also for seven backdoors. Finally, the total count of folders representing infiltrated machines equals twenty-one victims.

In April 2022, the attackers uploaded a Lua script implementing the nmap Library shortport searching for Telnet services using s3270 to control IBM mainframes; see the script below.

Code Quality of DropBoxControl

While we usually refrain from commenting on the code quality, in this case it deserves mentioning as the code quality is debatable at best and not every objection can be blamed on obfuscation.

The code contains a lot of redundant code; both duplicate code and code that serves no function. An indication of unfamiliarity with C# is usage of one’s own implementation of serialization/deserialization methods instead of using C# build-in functions. The threading code does not rely on usual synchronization primitives such semaphores, mutexes, etc. but rather uses bool flags with periodic checks of thread states. The code also contains parts that are presumably copied from API documentation. For instance, the implementation of DropBox_FileDownload contains the same comment as in the DropBox documentation; see the illustration below.

Another weird quirk is the encryption method for the configuration file. The DropBoxControl author has attempted to obfuscate the configuration in the ieproxy.dat file because the API key is sensitive information. However, when the config file is decrypted and applied, the configuration content is logged into the iexplore.log file in plain text.

In other words, the whole DropBoxControl project looks like a school project. Authors do not adhere to usual coding practices, rely on their own implementation of common primitives, and reuse code from documentation examples. This leads us to an assessment that DropBoxControl authors are different from authors of CLRLoader and PNGLoader due to significantly different code quality of these payloads.


The purpose of this study has been to confirm the assumptions of our fellow researchers from ESET published in the article about the Worok cyberespionage group. Our research managed to extend their compromise chain, as we have managed to find artifacts that fit the chain accompanying the samples in question.

We have described probable scenarios of how the initial compromise can be started by abusing DLL hijacking of Windows services, including lateral movement. The rest of the compromise chain is very similar to the ESET description.

The key finding of this research is the interception of the PNG files, as predicted by ESET. The stenographically embedded C# payload (DropBoxControl) confirms Worok as the cyberespionage group. They steal data via the DropBox account registered on active Google emails.

The prevalence of Worok’s tools in the wild is low, so it can indicate that the toolset is an APT project focusing on high-profile entities in private and public sectors in Asia, Africa, and North America.


DropBoxControl Log

[02:00:50]:[+]Main starts.
[02:00:50]:[+]Config exists.
[02:00:50]:[__]DecryptContent is 1,Bearer gg706Xqxhy4*****************gQ8L4OmOLdI1JU1DL**********1ej1_xH7e#,300,7,23
[10:39:40]:[+]In work time.
[10:39:42]:[UPD] UploadData /data/2019/time.txt Starts!
[10:40:08]:[UPD] UploadData /data/2019/time.txt Success!
[10:40:10]:[UPD] UploadData Ends!
[10:40:10]:[+]Get Time.txt success.
[10:40:11]:[+] DropBox_GetFileList Success!
[10:40:11]:[DOWN] DownloadData /data/2019/31-3.req Starts!
[10:40:13]:[DOWN] DownloadData /data/2019/31-3.req Success!
[10:40:13]:[DOWN] DownloadData Ends!
[10:40:26]:[UPD] UploadData /data/2019/31-3.res Starts!
[10:40:27]:[UPD] UploadData /data/2019/31-3.res Success!
[10:40:27]:[UPD] UploadData Ends!
[10:40:27]:[DEL] Delete /data/2019/31-3.req  Starts!
[10:40:28]:[DEL] Delete /data/2019/31-3.req Success!
[10:40:28]:[DEL] Delete Ends!
[10:40:28]:[DOWN] DownloadData /data/2019/31-4.req Starts!
[10:40:29]:[DOWN] DownloadData /data/2019/31-4.req Success!
[10:40:29]:[DOWN] DownloadData Ends!
[10:40:34]:[UPD] UploadData /data/2019/31-4.res Starts!
[10:40:36]:[UPD] UploadData /data/2019/31-4.res Success!
[10:40:36]:[UPD] UploadData Ends!
[10:40:36]:[DEL] Delete /data/2019/31-4.req  Starts!
[10:40:36]:[DEL] Delete /data/2019/31-4.req Success!
[10:40:36]:[DEL] Delete Ends!
[10:40:36]:[DOWN] DownloadData /data/2019/31-5.req Starts!
[10:40:37]:[DOWN] DownloadData /data/2019/31-5.req Success!
[10:40:37]:[DOWN] DownloadData Ends!
[10:40:42]:[UPD] UploadData /data/2019/31-5.res Starts!
[10:40:43]:[UPD] UploadData /data/2019/31-5.res Success!
[10:40:43]:[UPD] UploadData Ends!
[10:40:43]:[DEL] Delete /data/2019/31-5.req  Starts!
[10:40:44]:[DEL] Delete /data/2019/31-5.req Success!
[10:40:44]:[DEL] Delete Ends!
[10:40:44]:[DOWN] DownloadData /data/2019/31-7.req Starts!
[10:40:44]:[DOWN] DownloadData /data/2019/31-7.req Success!
[10:40:44]:[DOWN] DownloadData Ends!
[10:40:49]:[UPD] UploadData /data/2019/31-7.res Starts!
[10:40:50]:[UPD] UploadData /data/2019/31-7.res Success!
[10:40:50]:[UPD] UploadData Ends!
[10:40:50]:[DEL] Delete /data/2019/31-7.req  Starts!
[10:40:52]:[DEL] Delete /data/2019/31-7.req Success!
[10:40:52]:[DEL] Delete Ends!

Task Type Values
Command Task Type
Cmd_Request 0x01
Cmd_Response 0x02
Exe_Request 0x03
Exe_Response 0x04
FileUpload_Request 0x05
FileUpload_Response 0x06
FileDownload_Request 0x07
FileDownload_Response 0x08
FileView_Request 0x09
FileView_Response 0x0A
FileDelete_Request 0x0B
FileDelete_Response 0x0C
FileRename_Request 0x0D
FileRename_Response 0x0E
ChangeDir_Request 0x0F
ChangeDir_Response 0x10
Info_Request 0x11
Info_Response 0x12
Config_Request 0x13
Config_Response 0x14

PNG file with steganographically embedded C# payload





[1] Worok: The big picture
[2] Lateral Movement — SCM and DLL Hijacking Primer
[3] Dropbox for HTTP Developers

The post PNG Steganography Hides Backdoor appeared first on Avast Threat Labs.

Troubleshooting NT_STATUS_ACCESS_DENIED from Samba on Manjaro Linux

8 November 2022 at 08:19

A few months ago, I switched my main desktop to Manjaro, and I’m glad about it. Manjaro Linux is a polished and well-designed Linux distribution. As I like simplicity and a minimalistic approach, I chose the XFCE Desktop edition. Switching to Linux did not make me abandon the Windows platform completely. I spend lots of my work and hobby time on this OS. But I run it in QEMU-KVM VMs, configured through the Virtual Manager. As I experiment with various system settings, I have a base VM image and clone it when necessary for new projects/research. Thanks to this configuration, I finally stopped breaking my main system 🙂 One thing I needed to figure out was a way to share files between my Linux host and Windows VMs. I picked Samba as I wanted something which would look native in Windows. And here my troubleshooting story begins 🙂 I could summarize it in one sentence: “always check the system journald log,” but if you’re interested in a more extended and convoluted approach, please read on 🙂


My smb.conf file looks as follows:

   browse list = yes
   config backend = file
   debug pid = yes
   debug timestamp = yes
   debug uid = yes
   dns proxy = no
   follow symlinks = no
   guest account = nobody
   load printers = no
   log file = /var/log/samba/%m.log
   log level = 2
   logging = systemd file
   map to guest = Bad User
   max log size = 1000
   name resolve order = lmhosts bcast host wins
   passdb backend = tdbsam
   security = user
   server role = standalone server
   usershare path = /var/lib/samba/usershare
   usershare allow guests = yes
   usershare max shares = 100
   usershare owner only = yes
   workgroup = WORKGROUP

   browseable = no
   comment = Home Directories
   create mask = 0660
   directory mask = 0770
   guest ok = no
   read only = no
   valid users = %S

   browseable = yes
   comment = Share directory
   guest ok = no
   path = /mnt/data/winshare
   read only = no
   force group = +winshare
   valid users = me,ssolnica
   browseable = yes
   comment = Symbols
   guest ok = no
   path = /mnt/data/symbols
   read only = no
   valid users = me

I created the Windows user (smbpasswd -a me) and enabled smb and nmb services (systemctl enable nmb && systemctl enable smb). I configured Samba in Server Standalone mode as I did not need any of the AD features (by the way, it’s incredible that you may set up the whole AD in Linux!). When I tried my shares in Windows, the \\mypc.local\me share was working fine, but \\mypc.local\winshare was returning NT_STATUS_ACCESS_DENIED. I stopped the Samba service and ran it manually with debug level set to 3 (alternatively, you could specify debug level in the smb.conf file):

# systemctl stop smb

# smbd --no-process-group --foreground -d 3 --debug-stdout

Then, I tried the share in smbclient:

$ smbclient -U me //mypc/winshare 
Password for [WORKGROUP\me]:
Try "help" to get a list of possible commands.
smb: \> ls

The error reported by Samba pointed to the file system. So I restarted the service and attached strace to it. You need to make sure to trace the child processes (-f/-ff) as the primary Samba server launches a child server for each client session:

strace -p 4350 -ff -o smbd.strace

Here is some interesting content from the output file:

readlink("/mnt/data/winshare", 0x7ffe77011d00, 1023) = -1 EINVAL (Invalid argument)
setgroups(12, [956, 1000, 998, 991, 3, 90, 98, 1001, 962, 961, 150, 1002]) = 0
setresgid(-1, 1000, -1)                 = 0
getegid()                               = 1000
setresuid(1000, 1000, -1)               = 0
geteuid()                               = 1000
chdir("/mnt/data/winshare")             = 0
newfstatat(AT_FDCWD, ".", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, 0) = 0
getcwd("/mnt/data/winshare", 4096)      = 19
getcwd("/mnt/data/winshare", 1024)      = 19
newfstatat(12, "", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, AT_EMPTY_PATH) = 0
openat(12, ".", O_RDONLY|O_NOFOLLOW|O_PATH) = 26
newfstatat(26, "", {st_mode=S_IFDIR|S_ISGID|0770, st_size=4096, ...}, AT_EMPTY_PATH) = 0
newfstatat(25, "", {st_mode=S_IFREG|0600, st_size=45056, ...}, AT_EMPTY_PATH) = 0
munmap(0x7f4b82f0c000, 696)             = 0
mmap(NULL, 36864, PROT_READ|PROT_WRITE, MAP_SHARED, 25, 0x2000) = 0x7f4b82e63000
openat(AT_FDCWD, "/proc/self/fd/26", O_RDONLY|O_DIRECTORY) = -1 EACCES (Permission denied)
close(26)                               = 0

We can see that the Samba process switches the effective user and group to the authenticated user (me) and then performs actions on the file system. We can see in the trace that the openat syscall fails with the EACCESS error. I double-checked all file system permissions and made me the owner of the winshare folder. Still, the EACCESS error persisted. I was so confused that I even wrote a simple app to reproduce the syscalls above:

#include <iostream>
#include <array>
#include <sstream>

#include <unistd.h>
#include <grp.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char* argv[]) {
    std::cout << "euid: " << ::geteuid() << std::endl;
    std::cout << "egid: " << ::getegid() << std::endl;

    std::array<gid_t, 12> groups {956, 1000, 998, 991, 3, 90, 98, 1001, 962, 961, 150, 1002};
    if (::setgroups(groups.size(), != 0) {
        std::cout << "setgroups error: " << errno << std::endl;
        return 2;

    if (int err = ::setresgid(-1, 1000, -1); err != 0) {
        std::cout << "error: " << err << std::endl;
        return err;

    if (int err = ::setresuid(1000, 1000, -1); err != 0) {
        std::cout << "error: " << err << std::endl;
        return err;

    std::cout << "euid: " << ::geteuid() << std::endl;
    std::cout << "egid: " << ::getegid() << std::endl;

    if (int err = ::chdir("/mnt/data/winshare"); err != 0) {
        std::cout << "error: " << err << std::endl;
        return err;

    std::array<char, 1024> cwd{};
    if (::getcwd(, cwd.size()) == nullptr) {
        std::cout << "getcwd error: " << errno << std::endl;
        return -1;
    std::cout << "cwd: " << << std::endl;

    // strace: openat(AT_FDCWD, ".", O_RDONLY|O_NOFOLLOW|O_PATH) = 12
    if (int fd = ::openat64(AT_FDCWD, ".", O_RDONLY|O_NOFOLLOW|O_PATH); fd != -1) {
        std::cout << "Folder opened: " << fd << std::endl;

        // strace: openat(AT_FDCWD, "/proc/self/fd/26", O_RDONLY|O_DIRECTORY) = -1 EACCES (Permission denied)
        std::stringstream ss{};
        ss << "/proc/self/fd/" << fd;
        auto proc_path = ss.str();
        if (int proc_fd = ::openat64(AT_FDCWD, proc_path.c_str(), O_RDONLY|O_DIRECTORY); proc_fd != -1) {
            std:: cout << "Proc folder opened: " << proc_fd << std::endl;

            std::cin >> proc_path;

        } else {
            std::cout << "proc openat error: " << errno << std::endl;

        return 0;
    } else {
        std::cout << "openat error: " << errno << std::endl;
        return -1;

As you may guess, there was no error when I ran it. I scratched my head, looking online for similar issues, but could find nothing. As I had a lot of pending work, I started using the \\mypc.local\me share. Samba worked fine except for two issues: it was impossible to list the browseable shares from the Windows machines, and, secondly, the initial I/O requests over Samba were often very slow. Still, the initial problem was bugging me the most.

After a few weeks, I finally found some time to give it a second try.

Filesystem security checks are not the only ones

I again struggled with Samba config (I read the whole smb.conf man page! :)), but ended with strace. As I had my sample application working, I started comparing the process properties in the proc file system. And there, I discovered the attr folder, which stores various security-related attributes. The /proc/{pid}/attr/current file for my sample process contained unconfined while for the smbd process, its content was smbd (enforce). After searching through manual pages and Arch Linux wiki, I found that those settings come from the AppArmor module. The aa-status command only confirmed that:

# aa-status
apparmor module is loaded.
80 profiles are loaded.
77 profiles are in enforce mode.
9 processes are in enforce mode.
   /usr/bin/avahi-daemon (1479) avahi-daemon
   /usr/bin/avahi-daemon (1489) avahi-daemon
   /usr/bin/dnsmasq (1698) dnsmasq
   /usr/bin/dnsmasq (1699) dnsmasq
   /usr/bin/nmbd (1778) nmbd
   /usr/bin/smbd (1785) smbd
   /usr/bin/smbd (1787) smbd
   /usr/bin/smbd (1788) smbd
   /usr/bin/smbd (5225) smbd

Now, I needed to locate the problematic AppArmor profiles. But how to find their names? Obviously, in the system journal! I should have checked it in the very beginning. I was studying the smb unit logs while all the details were at my fingertips:

# journalctl -fx
lis 06 12:19:14 mypc audit[5535]: AVC apparmor="DENIED" operation="open" profile="smbd" name="/mnt/data/winshare/" pid=5535 comm="smbd" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000

The smbd profile, defined in /etc/apparmor.d/usr.sbin.smbd, denies access to my target folder. Let’s have a look at it (I left only the essential parts):

abi <abi/3.0>,

include <tunables/global>

profile smbd /usr/{bin,sbin}/smbd {

  /etc/mtab r,
  /etc/netgroup r,
  /etc/printcap r,
  /etc/samba/* rwk,
  @{PROC}/@{pid}/mounts r,
  @{PROC}/sys/kernel/core_pattern r,
  /usr/lib*/samba/vfs/*.so mr,
  /usr/lib*/samba/auth/*.so mr,
  /usr/lib*/samba/charset/*.so mr,
  /usr/lib*/samba/gensec/*.so mr,
  /usr/lib*/samba/pdb/*.so mr,
  /usr/lib*/samba/{,samba/}samba-bgqd Px -> samba-bgqd,
  /usr/lib*/samba/{,samba/}samba-dcerpcd Px -> samba-dcerpcd,
  /usr/lib*/samba/{lowcase,upcase,valid}.dat r,
  /usr/lib/@{multiarch}/samba/*.so{,.[0-9]*} mr,
  /usr/lib/@{multiarch}/samba/**/ r,
  /usr/lib/@{multiarch}/samba/**/*.so{,.[0-9]*} mr,
  /usr/share/samba/** r,
  /usr/{bin,sbin}/smbd mr,
  /usr/{bin,sbin}/smbldap-useradd Px,
  /var/cache/samba/** rwk,
  /var/{cache,lib}/samba/printing/printers.tdb mrw,
  /var/lib/samba/** rwk,
  /var/lib/sss/pubconf/kdcinfo.* r,
  @{run}/dbus/system_bus_socket rw,
  @{run}/ rwk,
  @{run}/samba/** rk,
  @{run}/samba/ncalrpc/ rw,
  @{run}/samba/ncalrpc/** rw,
  @{run}/samba/ rw,
  /var/spool/samba/** rw,

  @{HOMEDIRS}/** lrwk,
  /var/lib/samba/usershares/{,**} lrwk,

  # Permissions for all configured shares (file autogenerated by
  # update-apparmor-samba-profile on service startup on Debian and openSUSE)
  include if exists <samba/smbd-shares>
  include if exists <local/usr.sbin.smbd-shares>

  # Site-specific additions and overrides. See local/README for details.
  include if exists <local/usr.sbin.smbd>

Now, all is clear. AppArmor adds MAC (Mandatory Access Control) to the Samba process and interferes with the file system access checks. My share path (/mnt/data/winshare) was not in the AppArmor profile; thus, access was denied. I believe that Debian and openSUSE users might not experience this problem thanks to the update-apparmor-samba-profile script, but I haven’t had a chance to check it. Anyway, the solution for me was to create /etc/apparmor.d/local/usr.sbin.smbd-shares with the missing access rights (I will have more shares from the data drive, so I just gave access to /mnt/data).

While testing my shares with the system journal monitored, I discovered some more rules missing in the default AppArmor profiles. And I found that I wasn’t the only one with this problem. Inglebard reported a very similar issue and provided updates to the rules that worked for him. I added a comment with my findings. Finally, below are the updates that fixed all my problems with Samba.

$ cat /etc/apparmor.d/local/usr.sbin.smbd-shares
/mnt/data/** lrwk,
$ cat /etc/apparmor.d/local/samba-dcerpcd
# Site-specific additions and overrides for 'samba-dcerpcd'

@{run}/ lrwk,

/var/cache/samba/** rwk,

@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,

include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>
$ cat /etc/apparmor.d/local/samba-rpcd
# Site-specific additions and overrides for 'samba-rpcd'

/var/cache/samba/** rwk,

@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,

include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>
$ cat /etc/apparmor.d/local/samba-rpcd-classic
# Site-specific additions and overrides for 'samba-rpcd-classic'

/var/cache/samba/** rwk,
/dev/urandom rwk,

@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,

include if exists <samba/smbd-shares>
include if exists <usr.sbin.smbd-shares>


Avast Q3/2022 Threat Report

2 November 2022 at 08:00

Cybercriminals actively recruiting and paying people to support their malicious activities


Three months have passed since we published the Avast Q2/2022 Threat Report and here we are again reviewing the cyber threat landscape via the Avast telemetry and Avast’s experts’ insights. I’m not sure about you, but Q3 passed very quickly for me, probably thanks to the summer holidays here in Europe.

Threat reports are often scary and intimidating, as they describe malware outbreaks and dramatic increases in attacks of various threat types. This report is different, though. We observed a decline in a vast majority of malware types in Q3/2022, which is positive. The common belief in the security industry is that malware authors take off over the summer, causing a decline in malicious activity. The drop in attacks is also caused by users spending more time offline, reducing the attack surface. The war in Ukraine and the recent mobilization of forces in Russia likely also played a part in the decline. It will be interesting to see how this trend will continue in the next quarter.

Despite fewer attacks in Q3/2022, this report still contains many highlights. Raccoon Stealer’s activity is like a rollercoaster ride, and it went rampant this quarter, spreading via cracked software. The other stealers, Formbook and AgentTesla, reminded us that Office macros are mostly dead, for now. Malware authors are instead abusing ISO and IMG formats on Windows. Coniminers are still one of the top malware types, and 70% of their attacks are deployed using web coinminers on infected pages. We’ve also seen a new botnet called Pitraix, which is, fortunately, not prevalent, at least for now. Unfortunately, we cannot say the same about the Warzone RAT, which significantly boosted its presence in various countries such as Hungary and New Zealand. Furthermore, adware on Windows significantly grew in Central, South, and Eastern Europe, and mobile adware is still the top threat targeting Android users.

In addition to the malware activity we observed, this report also describes how cybergangs are actively recruiting and paying people to support their criminal activities. The LockBit group was very active this quarter, beginning a bug bounty program and even offering $1,000 to anyone tattooing their logo onto their body. The NoName057(16) hacker group, desperate to continue DDoSing governments and businesses supporting Ukraine, started paying people to download their program and DDoS for them after their Bobik botnet C2 server was taken down (coincidentally after we published a blog post about them).

Keep safe and happy reading!

Jakub Křoustek, Malware Research Director


This report is structured into two main sections – Desktop-related threats, where we describe our intelligence around attacks targeting the Windows, Linux, and Mac operating systems, and Mobile-related threats, where we describe the attacks focusing on the Android and iOS operating systems.

Furthermore, we use the term risk ratio in this report to describe the severity of particular threats, calculated as a monthly average of “Number of attacked users / Number of active users in a given country.” Unless stated otherwise, calculated risks are only available for countries with more than 10,000 active users per month.

We changed the threat labeling algorithm we use for our Threat Reports to ensure our data is even more accurate. As a result, the numbers appearing in this Threat Report should not be compared with those from our previous reports. We recomputed statistics from previous quarters to provide quarter-over-quarter comparisons in this Threat Report.

Desktop-Related Threats

Advanced Persistent Threats (APTs)

Among other threat actor groups, we continue to track Chinese threat actors, as well as a few groups in the Southeast Asia region and a Russian-speaking threat group. We gained new insights into their activities and campaigns, but their operations retain a similar modus operandi and targets. We continuously share our insights at cybersecurity conferences.

We recently presented our research on Operation Dragon Castling at Virus Bulletin 2022. This operation was facilitated by CVE-2022-24934, a zero-day vulnerability in WPS Office that enabled concealing execution of malware via the office suite’s update mechanism.

At the beginning of December, we will present our research on a huge operation in Southeast Asia in a talk titled "Hitching a ride with Mustang Panda" at the AVAR conference in December 2022. We presume a Chinese-speaking group called Mustang Panda is responsible for the operation due to the target selection and the toolset used.

Chinese-speaking Groups

LuckyMouse, a well-known Chinese-speaking threat group, known for targeting government agencies in Asia and the Middle East, attacked agencies in the United Arab Emirates, Taiwan, and the Philippines in Q3/2022. We found backdoors on infected machines, password stealers for Chrome, and open-source tools, like BadPotato, for privilege escalation. LuckyMouse uses a HyperBro backdoor loaded and decrypted by a sideloaded DLL. The attackers likely infected machines through a compromised server, where instead of the MiMi chat application, they inserted a backdoor. TrendMicro recently described LuckyMouse’s backdoor infection vector and the post-exploitation tools.

Southeast Asian Actors

At the beginning of August, researchers from Morphisec released a blog post describing changes in the yty malware framework, a well-known tool used by the Donot Team (also known as APT-C-35). Office documents containing malicious macros or a combination of RTF injection and the Microsoft Equation editor (CVE-2017-1182) vulnerability usually deliver the next stage to victims.

Our telemetry shows the group was most active in Pakistan, where we discovered DLL modules from yty’s framework on several infected machines in our user base. Malicious documents with the `.inp` extension are the source of infection. The infected victims installed outdated versions of Inpage software, a word processor for Urdu and Arabic languages widely used in the region. We believe the attackers are leveraging old known vulnerabilities in the Inpage software, as described by Palo Alto Networks. We assume the victims work for governmental institutions, based on the documents’ metadata and filenames.

Transparent Tribe, or APT36, is another group from the region we are tracking. They continue to attack victims in India and Afghanistan, as other researchers also reported. The group is believed to originate from Pakistan and focuses its activities on neighboring countries. The group infects victim PCs using spear-phishing and Office documents with malicious VBA macros dropping embedded obfuscated .NET-based executables into arbitrary paths in the `%ALLUSERSPROFILE%` directory. We identified that the executables belong to the CrimsonRAT strain, Transparent Tribe‘s custom malware used to access infected networks. The activity is analogous to what was described in greater detail by researchers from Cisco Talos and Fortinet.

Russian Actors

The Gamaredon group continues to be very active and tightly focused on Ukraine in Q3/2022, broadening its attacks on military and government institutions motivated by the Russian aggression in Ukraine. The overall number of attacks and general modus operandi has not changed since last quarter. Still, they introduced a few new tools to their toolset, including file exfiltration tools, various droppers, and new ways of distributing payloads and IPs of C&C servers. Our telemetry also shows the group targeted foreign embassies in Ukraine.

Luigino Camastra, Malware Researcher
Igor Morgenstern, Malware Researcher
Jan Holman, Malware Researcher
Tomáš Zvara, Malware Researcher


Desktop adware rapidly accelerated at the end of Q3/2022. In the beginning and middle of the observed quarter, adware activity stabilized with a slight downward trend, as the graph below illustrates.

Graph showing users (globally) Avast protected from desktop adware in Q2/2022 vs. Q3/2022

The peak at the end of Q3/2022 began on September 16, 2022. Adware activity significantly grew predominantly in Central, South, and Eastern Europe:

Graph showing users Avast protected in the Czech Republic, Slovakia, Poland, Greece, Croatia, Estonia, Latvia, Lithuania, and Ukraine in Q3/2022

We identified an adware variant responsible for the peak in September. The adware called DealPly is a Chrome extension that modifies a new page design in the Chrome browser. The extension is called Internal Chromium Extension and has permission to replace newly opened tabs, read browsing history, change bookmarks, and manage apps, extensions, and themes in the browser.

DealPly Adware Chrome extension: Internal Chromium Extension

The new tab can look similar to the screenshot below. The extension modifies advertising shortcuts and sends statistical and search information to attackers.

The new Chrome tab modified by the malicious Internal Chromium Extension

DealPly’s extension is not usually downloaded by users directly, but other malware installs it without the user’s knowledge and ensures its persistence, so they cannot remove the extension manually.

The adware we detected in the beginning and middle of Q3/2022 was adware on suspicious websites. This type of adware waits for a user to click on an arbitrary hyperlink and replaces the original link with one that redirects the user to advertising websites.

Here’s a list of ad servers:

  • deshaici[.]net
  • gapscult[.]com
  • informeresapp[.]com
  • mobile5shop[.]com
  • naigristoa[.]com
  • saumeechoa[.]com
  • go.ad2upapp[.]com

The suspicious websites lure victims by offering prizes or free services; see the examples below. However, the redirections lead to websites with malicious content or pages that want contact or login information.

Examples of adware websites

We monitored a noticeable decrease in the adware risk ratio for users in Brazil, the United States, the United Kingdom, Italy, Austria, and Switzerland. On the other hand, there was an increase in the risk ratio for users in Poland, Croatia, Latvia, and Hungary; see the map below.

Map showing global risk ratio for adware in Q3/2022

In Q3/2022, more than 40% of the adware we saw was from various adware families. However, the clearly identified strains of adware are: DealPly, RelevantKnowledge, DownloadAssistant, and CloverPlus.

The most common adware threats for MacOS were: Bundlore, Pirrit, Spigot, Adload, and MaxOfferDeal.

Martin Chlumecký, Malware Researcher


The botnet landscape was rather calm in comparison to the previous turbulent quarters – no miraculous revivals or medialized takedowns. Nevertheless, botnet activity remained consistent, consistently dangerous. With Ukraine defending itself from Russian aggression and the Western World providing support to Ukraine, some Russian groups are utilizing their resources to attack organizations and infrastructure in Europe. There are also several other entrenched botnets and spambots plaguing our lives with their existence (and spam).

In our Q2/2022 Threat Report, we noted botnets experimenting with new formats of malicious attachments, such as ISO or IMG files. While these formats have some limitations on who can actually open them, based on the version of the used operating system, they are still gaining popularity in spite of the fact that the original motivation for their usage is no longer valid.

The pro-Russian group NoName057(16) remains very active. Their DDoS botnet Bobik is still attacking organizations in countries voicing their support for Ukraine or imposing sanctions on Russia. Their targets include both private institutions, such as news agencies or banks, and government institutions including courts, parliament, and police. Their attacks are retaliatory. The sites they target change depending on current events. For example, the group attacked sites belonging to the Finnish government after Finland announced their intention to join NATO in August. The group’s success rate (the number of sites they manage to take down vs. the number of sites they target) is 40%, based on our observations. Moreover, approximately 20% of the attacks they claim to be responsible for cannot be accounted for in their configuration files.

The main Bobik C2 server was taken down, after we published our blog post about NoName057(16), and the botnet stopped working. On August 15, 2022, the group announced they were recruiting for a new project, presumably to continue their DDoS attacks. They later opened a new group dedicated to their DDOSIA project, as reported by Radware. As of late-October, the Telegram group had 777 members. The project allows anyone to download a binary through which they can be identified and carry out DDoS attacks and in return, be awarded cryptocurrencies from the group. We have been monitoring DDOSIA’s configurations since August 1, 2022. The configuration file is updated four times a day, on average.

A new botnet called Pitraix is gaining a bit of traction on hacking fora. The botnet source code was originally hosted on Github and written in Go. Go has become a popular choice for smaller projects lately. For instance, Black Lotus Labs recently described another newish botnet written in Go. The botnet has P2P architecture relying on TOR for its communication. Rather unusual, the project was not framed as a security tool nor for educational purposes as is usual for similar projects.

Quarterly comparison of protected users. Notice the first peak in Q1/2022 corresponding to the week when Russia attacked Ukraine

Overall, the botnet risk ratio is significantly lower than in the previous quarter, slowly getting back to the pre-war situation. We noticed a significant decline in Emotet’s activity, and a similar trend holds true for Tofsee. The only considerable outlier is MyKings. MyKings’ activity soared, with Ursnif trailing behind. Other botnet activity only slightly increased.

Currently, our data indicates that the following botnets (and their variants) are the most active in their recruitment:

  • Phorpiex
  • Emotet
  • Tofsee
  • MyloBot
  • Nitol
  • Dorkbot
  • MyKings
  • Ursnif
  • Amadey

Adolf Středa, Malware Researcher


The value of cryptocurrencies is stagnating at long-time lows, but coinminers are still one of the most prevalent malware types we block in the wild. The number of coinminers we protected our users from in Q3/2022 decreased slightly (-4%).

Graph showing users (globally) Avast protected from coinminers in Q3/2022

Users in Serbia were most at risk of encountering coinminers in Q3/2022, with a 7.28% risk ratio. The risk ratio for users in Madagascar encountering a coinminer was 4.55%, up slightly compared to the previous quarter. Users in Madagascar were among those most at risk of encountering coinminers. We also detected an increase in coinminer activity in Montenegro (6.59% risk ratio), as well as in Egypt where the risk ratio rose to 3.81% (+32% QoQ).

Map showing global risk ratio for coinminers in Q3/2022

Web coinminers continue to lead, gaining even more market share in Q3/2022. Web coinminer activity increased by 6% and they now hold 70% of the coinmining market share. We observed an increase in KingMfcMiner detections and protected 45% more users from the miner in Q3/2022 compared to Q2/2022. CoinHelper’s activity also increased its market share by 9%.

XMRig remains the leading coinmining executable. However, XMRig activity dropped by 11%. According to our telemetry, XMRig holds 15% of the coinminer market share.

The most common coinminers in Q3/2022 were:

  • Web miners (various strains)
  • XMRig
  • CoinBitMiner
  • VMiner
  • CoinHelper
  • NeoScrypt
  • FakeKMSminer

Jan Rubín, Malware Researcher

Information Stealers

Raccoon Stealer activity went rampant in Q3/2022 following the malware’s announced return, which we reported in our previous report. We protected +370% more users from Raccoon Stealer in Q3/2022 vs. Q2/2022. Despite Raccoon Stealer’s growth, overall information stealer activity declined by 14% in Q3/2022.

Graph showing users (globally) Avast protected from information stealers in Q3/2022

The countries where users are most at risk of encountering information stealers remained the same, for the most part, except for some countries in Africa, as can be seen in the heatmap below. Users in Mali encountered more information stealers (+14% risk ratio) than in Q2/2022, as did users in Yemen (+16% risk ratio) and Congo (+11% risk ratio). Further notable changes occurred in Brazil, where the information stealer risk ratio dropped by 24%. Avast’s presence in Brazil, where we saw a 28% drop in the number of users we protected from information stealers, is significant and is part of the reason we observed an overall decrease in information stealer numbers.

Map showing global risk ratio for information stealers in Q3/2022

FormBook continues to be the most active information stealer in Q3/2022, further increasing its market share by 8%, gaining 26% of the overall information stealer market share. The market share held by other top information stealer strains declined in Q3/2022: Lokibot (-35%), RedLine Stealer (-17%), and AgentTesla (-4%). Raccoon Stealer and SnakeKeylogger, on the other hand, significantly increased their market share by 450% and 53%, respectively.

The most common information stealers in Q3/2022 were:

  • FormBook
  • RedLine Stealer 
  • AgentTesla
  • Lokibot
  • Raccoon Stealer
  • SnakeKeylogger

Raccoon Stealer Reaches New Heights

We protected significantly more users from the second version of Raccoon Stealer at the beginning of Q3/2022.

Graph showing users (globally) Avast protected from Raccoon Stealer in Q3/2022

Raccoon Stealer mainly makes its way onto computers via “cracked” software. The archives through which Raccoon Stealer spreads promise cracked versions of software like Adobe Photoshop, Filmora Video Editor, and uTorrent Pro, but deliver Raccoon Stealer instead.

Raccoon Stealer not only steals data but is also capable of downloading and executing further malicious files, including miners and other stealers.

GuLoader Phishing Emails

We observed new phishing email campaigns rising in late August and September, mainly targeting users in Spain, the Czech Republic, Romania, and other countries. We protected over 26,000 users. The campaigns use ISO archive attachments containing new versions of GuLoader that drop AgentTesla or FormBook.

Graph showing users (globally) Avast protected from the GuLoader campaigns in Q3/2022

Discord Based Information Stealers Attacking Linux Users

We also observed some new malware families (i.ex. A new variant of Sshbru or ServerHijacker-B) written in Go programming language and abusing Discord Webhooks to leak information. These malware strains first identify or create an attack vector to hijack the system (i.ex. by enumerating the vulnerabilities in the LAN network of the victim, changing the password for root, and so on) and then get the public IP address which is leaked to the attackers via Discord Webhooks for a later intrusion. Computer access is likely to be sold on the black market.

Jan Rubín, Malware Researcher
Vladimir Martyanov, Malware Researcher
David Álvarez, Malware Analyst


Ransomware activity increased by nearly a quarter (+24%) in Q2/2022. In Q3/2022, ransomware activity stabilized, and slightly decreased. There were no peaks in ransomware activity in Q3/2022, as shown in the graph below, and is the reason for this decrease in risk ratio.

New countries are on top of the list of countries in which users are most at risk of encountering ransomware in Q3/2022:

  • Papua New Guinea
  • Mozambique
  • Afghanistan
  • Ghana
  • Vietnam

The risk ratio for ransomware remained the same or slightly decreased in most countries in Q3/2022 (compared to the Q2/2022), but there are some outliers. The ransomware risk ratio increased by 70% in Vietnam, 49% in Thailand, 33% in Denmark, 16% in Canada, and 12% in Spain and Germany.

Here is a map of the ransomware risk ratio by country:

STOP, and WannaCry ransomware continued to be the most prevalent ransomware strains targeting our user base:

  • STOP
  • WannaCry
  • Thanatos
  • Sodinokibi / REvili (and its successors)
  • Magniber
  • LockerGoga
  • Conti offsprings
  • LockBit

Intermittent File Encryption

More and more ransomware strains now use partial (intermittent) methods of encryption (AtomSilo, Conti, BlackMatter, LockBit), to rapidly encrypt files. During a ransomware attack, file encryption needs to be quick to avoid user detection. The longer encryption takes, the higher the chances the potential victim notices the attack. A vigilant user may notice increased disk activity and check what’s going on. Also, the time needed to fully encrypt a collection of large files (such as movies or databases) may be significantly high.

CrySiS ransomware implemented partial encryption already in 2016, for example, but now more ransomware strains use complicated methods of partial encryption, and they are often configurable:

  • Full Encryption: The file is fully encrypted. This is the “safest” method (from the point of view of the attackers) but can take a very long time, especially when encrypting movie files or large databases.
  • Header only: The ransomware only encrypts the beginning of the file (up to a specified amount of bytes). This invalidates headers of most file types and renders them unrecognizable.
  • Header + Tail: In addition to the file header, the header + tail method also encrypts part of the file end. This covers ZIP-like files (ZIP archives and MS Office files)
  • Dot Pattern: The ransomware encrypts files by blocks – N bytes are encrypted, M bytes are left intact.

The methods described above can be combined, such as encryption of the file header and encryption of the rest using Dot Pattern encryption.

Multiple new ransomware strains emerged in Q3/2022, often attacking Windows, Linux, and ESXi servers. One of them was Luna ransomware, allegedly originating from Russia. Luna is written in the Rust programming language and can therefore be compiled for multiple platforms. Security researchers from Kaspersky confirmed all platform versions were built from the same source files.

Furthermore, ransomware authors continue innovating their ransoming techniques, and some recent attacks in the enterprise sector no longer involve file encryption, but data exfiltration followed by secure file deletion or corruption. In this scenario, companies depend on criminals to provide the original files after payment.

The LockBit Story

An interesting series of events involving the LockBit ransomware gang took place in Q3/2022. At the end of June, the gang behind the ransomware released a new version of the encryptor, code-named Black (because they copied it from the Black Matter ransomware gang). With this release, they announced a bug bounty program. Any bug or vulnerability reported to the gang will bring significant rewards. Reported bugs can be a weakness in the encryption process, a vulnerability in their website, or vulnerabilities in the TOX messenger or the TOR network. The juiciest reward (one million USD) is up for grabs and will go to the person who finds out the name of the affiliate boss.

In addition to the bounty program, the gang offered $1,000 USD to anyone who tattooed the LockBit logo on their body. The gang demanded video proof. According to photos posted to Twitter, some desperate people actually got the tattoo. We hope they got their reward and it was worth it…

The group paid a bounty reward of $50,000 to a person(s) who found a vulnerability in the encryption of large database files. They may pay more for bugs than others pay for RCE vulnerabilities, but they should consider paying their developers more. One of their developers got angry and leaked the builder of the cryptor. The package was briefly available on Github, but Github disabled it. The leaked package contained an RSA key generator and the builder of the ransomware+decryptor. With the leaked package, anyone could create their build of the ransomware and start a ransomware gang. Some seized the opportunity and did just that – the BlooDy ransomware gang, and TommyLeaks/School boys gang took the builder and made their own cryptors.

One of the LockBit gang’s victims is a security company called Entrust, which suffered a cyber attack on June 18, 2022. Shortly after the attack, the LockBit gang claimed they were behind the attack. Together with the ransomware attack, they also extorted Entrust’s internal data and threatened to leak it, if the company didn’t pay the ransom.

The leaked data (including legal documents, marketing spreadsheets, and accounting data) was published on the gang’s Tor sites. Nevertheless, the sites went offline shortly after due to a DDoS attack, believed to originate from Entrust. Entrust never confirmed they were behind the attack.

But the story didn’t end there. Following the (counter) attack, the LockBit gang announced they were back with new triple-extortion tactics – encryption, extortion, and DDosing. The group published a torrent with 342 GB of Entrust’s stolen data online. Furthermore, the LockBit gang announced they would strengthen their infrastructure to prevent future DDoS attacks.

This quarter was also the sixth anniversary of the NoMoreRansom initiative, which helps millions of victims of ransomware attacks. Avast is a partner and we recently added a decryptor for the MafiaWare666 ransomware.

Jakub Křoustek, Malware Research Director
Ladislav Zezula, Malware Researcher

Remote Access Trojans (RATs)

RAT activity, in most parts of the world, continues to decline, just like in previous quarters. In our Q2/2022 Threat Report, we speculated that RAT activity would continue to decline over the summer, and we were right.

Graph showing users (globally) Avast protected from RATs in Q2/2022 and Q3/2022

Users in Afghanistan, Yemen, and Iraq were most at risk of encountering a RAT in Q3/2022. RAT activity did however significantly increase in Hungary and New Zealand. The Warzone RAT is responsible for the increase in Hungary (+118%), the 59% increase in New Zealand is mostly due to Remcos and njRAT activity.

The countries where the risk ratio declined the most are: Spain (-36%), Canada (-31%), Czech Republic (-29%), and Slovakia (-28%). In our Q2/2022 Threat Report, we reported Japan as the country with the biggest increase in RAT attacks. In this quarter the number decreased, and Japan is among the safest countries together with Finland, France, and Switzerland.

Map showing global risk ratio for RATs in Q3/2022

The most prevalent RATs in our user base in Q3/2022 were:

  • HWorm
  • njRAT
  • Warzone
  • Remcos
  • NanoCore
  • AsyncRat
  • NetWire
  • QuasarRAT
  • DarkComet
  • Adwind

The top strains mostly stayed the same. As already mentioned, we saw a rather large campaign spreading Warzone in Hungary. A Remcos campaign also hit most of Asia, and the Netwire RAT targeted users in South Africa with a campaign.

Other RATs with a significant increase in prevalence in Q3/2022:

  • LimeRAT (+85%)
  • SpyNet (+41%)
  • BoubedzRAT (+40%)

While these RATs are not as prevalent, their prevalence increased considerably in Q3/2022. LimeRAT was mostly active in Africa and South Asia, while SpyNet was active in Brazil and the BoubedzRAT in Columbia.

We published a blog post about a RAT called Backdoorit written in Go in Q3/2022. Backdoorit mainly focuses on stealing Minecraft related files, Visual Studio, and IntelliJ projects.

Several new RATs appeared or were discovered during Q3/2022. ApolloRAT is a new and interesting RAT because of its use of Nuitka to compile Python source to C source as reported by Cyble. The set of features is quite common in the domain of RATs with the exception of “Prank” commands such as >rickroll. It uses Discord for its C&C communication.

CodeRAT appeared in Q2/2022. In Q3/2022 the developer publicly shared the code on GitHub, after being confronted by security researchers from SafeBreach. CodeRAT’s main goal is to monitor its victims’ social media activity and what they do on local machines. It features approximately 50 commands interacting with various parts of the operating system. It can also deploy other malware. The communication methods are also interesting, CodeRAT makes use of Telegram groups or a USB flash drive.

WoodyRAT was active for at least a year before it was discovered by Malwarebytes. The attackers make use of the Follina vulnerability to spread their RAT. According to the analysis, the malware can extract data from the infected computer, run commands and code, including injecting to other processes.

The Lazarus APT group added a new tool to their arsenal, as reported by Cisco Talos. This tool is called MagicRAT. MagicRAT is a relatively simple tool that can launch additional payloads, run arbitrary commands and manipulate files on infected machines. What makes it stand out is its use of the Qt Framework. Since MagicRAT does not have a user interface, the Qt Framework is likely used to increase the complexity of the malware and to make analysis harder.

Last but not least, the developer and seller of Imminent Monitor RAT SaaS was arrested by the Australian Federal Police. The RAT allows operators to spy on their victims via their webcam and microphone, among other things. According to the report the RAT has been sold to more than 14,500 individuals across 128 countries.

Ondřej Mokoš, Malware Researcher


Rootkit activity declined in Q3/2022, as shown in the chart below.

Graph showing users (globally) Avast protected from rootkits in Q1-Q3/2022

The distribution trend of rootkit strains continued as expected based on the previous two quarters (Q1/2022 and Q2/2022). The primary strain in Q3/2022 was the R77RK rootkit developed by the bytecode77 group. R77RK holds a 40% market share.

Users (globally) Avast protected from rootkits vs. users (globally) Avast protected from the R77Rootkit in Q3/2022

The chart above shows R77RK is a major rootkit, as its trend copies the overall rootkit trend in Q3/2022. The R77RK’s GitHub repository is still active. One notable correlation can be seen on September 1, 2022, when the authors’ released new functionality for R77RK. They implemented a rootkit activation via injection of a specific shell code. The release date corresponds with the peak; see the chart above.

The map below animates R77RK’s activities moved to Eastern Europe and Northern Asia. On the other hand, Canada and the United States remain the least affected countries.

Map showing global distributions of R77Rootkit activities in Q2/2022 and Q3/2022

Another rootkit making rounds in Q3/2022 was Alureon, which steals credentials and credit card information by capturing the system’s network traffic. However, Alureon’s market share in the wild is only about 5%.

Map showing global risk ratio for rootkits in Q3/2022

The global risk ratio of all rootkits is the same as in Q2/2022, and China remains the country in which users have the highest risk of encountering a rootkit. Q3/2022 confirmed that R77RK is still the most popular open-source rootkit in the wild.

Martin Chlumecký, Malware Researcher

Technical support scams

Technical support scams dipped at the end of July and the beginning of August. We assume the scammer community wanted to enjoy their summer break. This calm period lasted only a few weeks and ended at the end of August. Our September stats show more activity compared to July.

Graph showing users (globally) Avast protected from tech support scams in Q2-Q3/2022

The top affected countries remained the same as in Q1 and Q2/2022. Users in Japan were targeted most, with a risk ratio of 3.16%, followed by Germany, the United States, and Canada, where activity slightly increased.

Map showing global risk ratio for tech support scams in Q3/2022
Screenshot of a prevalent TSS targeting users in Germany

In Q3/2022, we registered hundreds of unique telephone numbers used in TSS scams. Here are the top 20 phone numbers:

+1(888)-350-3496 +1(888)-350-3495
+1(833)-690-1082 +1(833)-690-1085
+1(833)-690-1079 +1(844)-449-0455
+1(888)-213-0940 +1(866)-622-6692
+1(844)-838-9290 +1(833)-522-6669
+1(817)-813-2707 +1(844)-300-0063
+1(844)-819-3386 +1(866)-344-4412
+1(877)-294-2845 +1(888)-320-3547
+1(805)-271-6246 +1(888)-850-1320
+1(877)-512-2485 +1(844)-594-2674

Alexej Savčin, Malware Analyst

Vulnerabilities and Exploits

At the end of July, Microsoft published research about a private-sector offensive actor they refer to as KNOTWEED. KNOTWEED deployed a custom piece of malware, called Subzero, through a number of infection vectors, including zero-day exploits for Microsoft Windows and Adobe Reader. While the researchers were not successful in recovering the Adobe exploit, they found and patched CVE-2022-22047, a nasty bug used for privilege escalation.

Also noteworthy were new Microsoft Exchange zero-days (CVE-2022–41040 and CVE-2022–41082), discovered in the wild by GTSC Cyber Security. The exploits were strikingly similar to ProxyShell, an Exchange exploit discovered in 2021. As far as we know, the zero-days were only used in a limited number of targeted attacks, thus far.

Our own exploit research in Q3/2022 was mostly focused on Roshtyak, the backdoor payload associated with Raspberry Robin. Roshtyak uses CVE-2020-1054 and CVE-2021-1732, both Windows LPE exploits, to elevate privileges. Read our blog if you are interested in more details.

We also continued to track browser exploit kits, and we found PurpleFox, Rig, and Underminer to be active throughout the quarter.

The most frequently used exploit for MacOS was MacOS:CVE-2019-8900. A vulnerability in the Boot ROM of some Apple devices can be exploited by an unauthenticated local user to execute arbitrary code upon booting those devices.

Jan Vojtěšek, Malware Reseracher

Web skimming

In Q3/2022, the most common malicious domain used for web skimming attacks was hubberstore[.]com. Infected e-commerce websites, like sites selling event tickets, notebooks, and wine – mostly in Brazil, called code from the malicious domain. We protected nearly 20,000 users from the webskimmer in Q3/2022. In some cases, malicious code was present on an infected site, while in other cases, sites loaded additional code from hubberstore[.]com/app.js or a similar file name. The GET request exfiltrated payment details to the hubberstore malicious domain.

Here are some examples of what the GET requests look like:

  • hubberstore[.]com/<infected-webpage-name>.php?&drac=<user-data-base64-encoded>
  • hubberstore[.]com/chk/apicielo.php?chave=<user-data-plaintext>
  • hubberstore[.]com/v2/search?public_key=<user-data-base-64>

A Czech e-commerce site called bohemiadrogerie[.]cz was also infected. In this case, the attackers inserted their payment form on the website. The image below shows what the site looks like with and without the fake payment form. After entering payment details, customers receive an error message: The selected payment method is currently unavailable, please try again. The page is then reloaded and displayed without the payment form.

The skimmer on the Czech site uses a specific pattern ;function boms()in the malicious code. The same pattern was on the domain naturalfreshmall[.]com to host the malicious skimmer code, which we reported in our Q1/2022 Threat Report.

Attackers also exploited other legitimate sites, such as sites selling clothes, shoes, jewellery, furniture and medical supplies, to host their skimming code. Specifically, they used guyacave[.]fr, servair[.]com and stripefaster[.]com. Attackers exfiltrated payment details via the POST request to URLs like guyacave[.]fr/js/tiny_mce/themes/modern/themes.php and similar for the other domains. In some cases, the POST request was sent to the infected e-commerce site itself, indicating that the attacker has full access to the compromised sites. We protected nearly 17,000 users globally from this webskimmer.

In conclusion, there are still many long-term infected websites. Malicious code often remains on an infected website even after the exfiltration domain no longer exists.

Pavlína Kopecká, Malware Analyst

Mobile-Related Threats


Continuing the trend from previous years, adware was still the dominant threat facing mobile users in Q3/2022. This dominance brings intrusive advertisements, often paired with several stealth features. These combine to rake in money through advertisements for the adware creators while negatively impacting the user experience of mobile users worldwide.

HiddenAds and FakeAdBlockers continue to be the most prevalent adware families. They often use overlays to display advertisements to the user, even when using other applications on the phone. They may delay this activity by several days to confuse the user about the source of the intrusive advertisements. As per their name, HiddenAds can also hide their icon from the home screen, making it more difficult for mobile users to find the source of these frustrating ads.

Several new waves of HiddenAds made it onto the Google Play Store, such as Scylla, with added obfuscation but a similar set of features to previous HiddenAds strains. FakeAdBlockers continue to spread through fake games and applications downloaded from unofficial sources. Both families often come under the guise of games, camera filters, wallpaper apps, and keyboard themes, to name a few. It is advisable to avoid third-party stores and unknown websites when downloading applications, instead using Google’s Play Store while checking reviews and requested permissions.

Adware mostly affects mobile users in Asia, the Middle East, and South America. Brazil, India, Argentina, and Mexico again hold the top spots in the quarter, with increases in affected users in India and Mexico. The US holds fifth place, but we see a 25% decrease in affected users compared to last quarter. Adware is the most common mobile threat facing mobile phone users worldwide today.

Map showing global risk ratio for mobile adware in Q3/2022


Cerberus/Alien keeps its top place in the banker sphere in Q3/2022, while Hydra and RoamingMantis finally surpass Flubot in terms of protected users. Following an eventful last quarter with the Flubot group disbanding by Europol, we finally saw a marked decrease of 50% in Flubot’s reach in Q3/2022. Considering Flubot dominated the banker sphere with its SMS phishing campaigns attacking users across Europe and the US, it is encouraging to see the positive effects of Europol’s actions.

Bankers still rely on established methods of infection and delivery, with SMS phishing being the favored approach. Several new droppers appeared on the Google Play Store, third-party stores, and forums, propagating known or slightly adjusted versions of existing bankers. Most recently, TrendMicro discovered the DawDropper dropper, which delivers a multitude of banker strains over the span of an extended period. We, therefore, believe it is a dropper service used by multiple banker strains, mitigating cost and effort for banker authors.

Interestingly, the number of protected users in Q3/2022 was slightly higher than last quarter. However, we continue to be on a long-term downward trend, as can be seen in the chart below. Flubot’s demise significantly contributed to this decline, as we’ve seen fewer banker-spreading campaigns since its disbanding.

Graph showing users (globally) Avast protected from mobile bankers in Q3/2021-Q3/2022

We saw some movement in the top affected countries in Q3/2022, with Spain, France, and Turkey coming in as the most targeted, while France shows a striking 70% increase in protected users. Contrary to this, we see a sharp decline in protected users in Italy, Germany, Australia, and the UK, up to a 40% drop.

Map showing global risk ratio for mobile bankers in Q3/2022


In Q3/2022 we observed a continuation of existing premium SMS scams which started late last year and a few older strains retiring. SMSFactory and Darkherring remain the main TrojanSMS offenders this quarter. UltimaSMS and GriftHorse have finally been eliminated, as their number of protected users plummeted to nearly zero.

These TrojanSMS families rely on premium SMS subscriptions or sending SMS messages to premium numbers to extract money from victims. Left undetected, these malwares can rack up expensive phone bills, which is why they often come with stealth features to avoid discovery, hiding the application icon and the sent SMS messages. In the worst case scenario, the user forgets about the application or cannot identify the culprit while their money is siphoned away.

It is interesting to compare the methods of delivery of theseTrojanSMS strains. Families such as UltimaSMS, GriftHorse, and DarkHerring were distributed through the Google Play Store, and their numbers were in the tens of millions when discovered. However, following their discovery and takedown from the Play Store, these strains were nearly eliminated and no longer affected large numbers of users. On the other hand, SMSFactory, which uses pop-ups, malvertising, and fake app stores to deliver its payload, is still operating today, and we see a steady number of protected users still affected. While we observed some minor changes to the application and their C2 servers in the past few months, the malware and its functionality remain the same. SMSFactory accounts for over 60% of protected users this quarter, clearly dominating the TrojanSMS market.

The distribution of protected users is similar to last quarter, with Brazil, Russia, Ukraine, Germany, and India holding the top spots. Azerbaijan, Kyrgyzstan. and Iraq show the highest risk ratio numbers.

Map showing global risk ratio for mobile TrojanSMS in Q3/2022

With the exit of UltimaSMS and GriftHorse, as well as declining numbers for DarkHerring, the overall TrojanSMS trend is downward in Q3/2022. However, SMSFactory appears to be here to stay; hence we predict the numbers will maintain or slightly decline into the next quarter.

Graph showing users (globally) Avast protected from mobile TrojanSMS in Q3/2022


Spyware has been a persistent threat to users for the last several years. More recently, we tracked some spikes in activity in Q3/2022. Spymax leads with the most reach for several quarters now, while we observe Facestealer becoming a more persistent threat this year.

Spyware’s purpose is to spy on the user’s activity, including photos, messages, location, and other personal information. More recently, these malwares tend to look for login credentials, banking details, and even crypto wallet addresses. Spymax has accrued these features over the span of several years and often comes heavily obfuscated to evade detection. It imitates a variety of applications and made it onto the Google Play Store a few times during the Covid pandemic. FaceStealer, on the other hand, is rather new, appearing last year, with the ability to create convincing overlays to trick users into entering login credentials. According to our observations, and research conducted by Meta, these apps were reasonably successful in attacking users, often using the Play Store as a delivery method. The apps aim to steal logins initially only to social media platforms, but now also steal a variety of logins.

Of note is another form of Spyware we’ve seen more of in the last few quarters. These are malicious modified versions of popular messaging apps such as WhatsApp and Telegram. Numerous mods posted on forums, discord servers, and third-party app stores offer functionality not present in the original messaging applications, which is where malicious versions of these applications may spread. We advise users to avoid installing and using modded applications as there’s no guarantee that they are safe to use. There’s potential for personal information, photos, and messages to be stolen from user accounts. Malicious actors may even steal unique keys associated with the account, which may lead to loss of access to the account itself. Additionally, Whatsapp’s FAQ warns that unofficial applications or mods may lead to account suspension or a complete ban. We, therefore, advise users to only install messaging applications from official app stores.

Spyware appears to have a relatively broad global distribution of affected users, with Brazil having the most affected users despite a 21% drop in Q3/2022. Following are India, Egypt, and the US, each with roughly a 10% increase in protected users this quarter.

Map showing global risk ratio for mobile Spyware in Q3/2022

We observed a downward trend last quarter. Still, it appears that new versions of FaceStealer bolstered the numbers of protected users this quarter. Overall, Spyware has been on the rise for the last two years.

Graph showing users (globally) Avast protected from mobile Spyware in Q3/2022

Jakub Vávra, Malware Analyst

Acknowledgements / Credits

Malware researchers

Adolf Středa
Alexej Savčin
Daniel Beneš
David Álvarez
Igor Morgenstern
Jakub Křoustek
Jakub Vávra
Jan Holman
Jan Rubín
Jan Vojtěšek
Ladislav Zezula
Luigino Camastra
Michal Salát
Martin Chlumecký 
Ondřej Mokoš
Pavlína Kopecká
Tomáš Zvara
Vladimir Martianov
Vladimír Žalud

Data analysts
  • Pavol Plaskoň
  • Marina Ziegler
  • Stefanie Smith

The post Avast Q3/2022 Threat Report appeared first on Avast Threat Labs.

Free Micropatches For Bypassing MotW Security Warning with Invalid Signature (0day)

28 October 2022 at 19:52

by Mitja Kolsek, the 0patch Team


Nine days ago we issued micropatches for a vulnerability that allows attackers to bypass the warning Windows normally present to users when they try to open a document or executable obtained from an untrusted source (Internet, email, USB key, network drive). That vulnerability, affecting all supported and many legacy Windows versions, still has no official patch from Microsoft so our (free!) patches are the only actual patches in existence as of this writing.

On the very same day we issued these micropatches, Will Dormann - who researched said vulnerability - replied to a tweet by another security researcher, Patrick Schläpfer. Patrick works at HP Wolf Security where they analyzed the Magniber Ransomware and wrote a detailed analysis of its working. Will asked Patrick about the ZIP files used in the malware campaign to see if they were exploiting the same vulnerability or employing some other trick to bypass the "Mark of the Web".

Patrick responded that malicious files extracted from the attacker's ZIP files did have the Mark of the Web but still executed without a security warning. Remember that on Windows 10 and Windows 11, opening any potentially harmful file triggers a SmartScreen inspection of said file, whereby SmartScreen determines if the file is clear to get launched or the user should be warned about it (see image below).

SmartScreen determined that this file could be harmful and warned the user. The user needs to click "More Info" and then press "Run" if they really want to open the file.

When deciding whether to trust the file or not, the Mark of the Web plays an important role: files with this mark are considered unconditionally untrusted as they originated from an untrusted source. So why did these malicious Magniber files not trigger the SmartScreen warning?

Patrick remarked that Authenticode signatures on extracted malicious files must have been causing this behavior because with signatures removed, the warning would (correctly) appear. 

Will then noticed that these signatures were not valid at all and should not have been trusted by Windows. In fact, they were malformed such that Windows could not even properly parse them. This, for some peculiar reason, led to Windows trusting them - and letting malicious executables execute without a warning.

And so a new 0day - already exploited in the wild - was revealed.

This information was sufficient for us to start our patch development process. We reproduced the issue using a sample .JS file with various malformed signatures until we reached the flawed execution path that bypassed the SmartScreen warning. We then searched for the cause, and found... well... it's complicated.

The logic behind determining whether to show a security warning to the user, and whether to show the SmartScreen warning or the old "Open File - Security Warning" dialog, is complex and distributed among various executable modules. An application attempting to open a file on user's behalf partly inspects the file itself to decide whether to send it to further inspection by SmartScreen, and if so, sends a DCOM request to SmartScreen.exe, which is another process constantly running on Windows. SmartScreen.exe then does its own inspection and, if networking is available, asks Microsoft's servers for an opinion, displays the warning if needed, then delivers the final verdict back to the requesting process in form of an error code and the information on user's decision ("Run" or "Don't run").

What looked like the most serious flaw to us was the fact that when SmartScreen.exe returns an error, this would be considered identical to the user having pressed "Run." A strange decision, security-wise.

The malformed signature discovered by Patrick and Will caused SmartScreen.exe to throw an exception when the signature could not be parsed, resulting in SmartScreen returning an error. Which we now know means "Run."

Mystery solved. Now let's patch it.

To understand our patch, we need to take a closer look at function DoSafeOpenPromptForShellExec in shdocvw.dll as this is the function that performs the flawed logic. The image below shows the relevant part of its code: register edi is initialized to 0 and is reserved for holding the final decision on whether to run/open the file or not. The function sends a request to SmartScreen and waits for its response, then based on the error code returned takes the left or the right branch. The right branch, executed when there was no error, stores the user's decision to edi; the left branch, executed when SmartScreen returned an error, leaves edi on 0 - which explains why an error equals the user's decision to run/open the file.

The simplest way to fix this would be to simply initialize edi to a value that would mean "Don't run", whereby any error in SmartScreen would lead to the file not being run/opened. However, this approach might confuse users as such files would just silently not run/open without any feedback to the user.

We therefore decided on a different approach: in the same function, there is a code block that displays the old "Open File - Security Warning" dialog to the user, and in case of a SmartScreen error, we redirect execution to that block. This way, when SmartScreen errors out, the user is presented with the old security warning and can still select whether to run/open the file or not.

While our patch fixes the most obvious flaw, its utility depends on the application opening the file using function DoSafeOpenPromptForShellExec in shdocvw.dll and not some other mechanism. We're not aware of another such mechanism in Windows, but it could technically exist. Our tests with opening files directly from Windows Explorer, from ZIP files opened with Windows Explorer, and directly from most major web browsers and email clients were successful. Except with Google Chrome, which we've long ago decided not to patch because its sandboxing model is causing us problems with injection. And even if we did patch Chrome, there is another weird logic in shdocvw.dll that gets executed in Chrome due to sandboxing which for some reason prevents SmartScreen from returning an error while still not showing the warning. We did not investigate the latter any further.

We did, however, investigate a possibility of patching SmartScreen.exe, which could potentially cover all cases, but that would have to be a much more complex patch. In addition, the SmartScreen code can throw an exception in numerous places in addition to the one triggered by a malformed signature, and each of these could lead to the same warning bypass.

Needless to say, we're very interested in learning how Microsoft will fix this issue. Meanwhile though, our patch probably covers the majority of attack scenarios.


Our Micropatch In Action

You can see the effect of our micropatch in the following video. There are two executable files on the desktop: goodsig.exe and badsig.exe. Both are really just calc.exe and both have the Mark of the Web, but the former has a proper parsable signature while the latter has a malformed, unparsable signature that causes SmartScreen to error out.

Without our micropatch, double-clicking goodsig.exe correctly shows the SmartScreen warning because any executable with Mark of the Web should trigger the warning. Double-clicking badsig.exe, on the other hand, just launches the executable without any warning - demonstrating the vulnerability.

With 0patch enabled and our micropatch applied, double-clicking goodsig.exe behaves the same as before (which is still correct), but double-clicking badsig.exe now triggers the "Open File - Security Warning" dialog, warning the user and allowing them to back out via the "Cancel" button.

Micropatch Availability

Since this is a "0day" vulnerability with no official vendor fix available, we are providing our micropatches for free until such fix becomes available.

Micropatches were written for: 

  1. Windows 11 v21H2
  2. Windows 10 v21H2
  3. Windows 10 v21H1
  4. Windows 10 v20H2
  5. Windows 10 v2004
  6. Windows 10 v1909
  7. Windows 10 v1903
  8. Windows 10 v1809
  9. Windows 10 v1803
  10. Windows Server 2022
  11. Windows Server 2019 

Note that Windows 7 is not affected by this issue, neither are Windows Server 2008 and 2012.

These micropatches have already been distributed to all online 0patch Agents. If you're new to 0patch, create a free account in 0patch Central, then install and register 0patch Agent from Everything else will happen automatically. No computer reboot will be needed.

To learn more about 0patch, please visit our Help Center

We'd like to thank Patrick Schläpfer for sharing the details on their Magniber ransomware analysis and Will Dormann for their analysis of this vulnerability that allowed us to reproduce it and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Micropatches for two Windows Print Spooler Elevation of Privilege issues (CVE-2022-30206, CVE-2022-21997)

26 October 2022 at 19:28


by Mitja Kolsek, the 0patch Team

On September 24, 2022 we were made aware of a POC for a Print Spooler elevation of privilege vulnerability discovered by security researcher luckyu with NSFOCUS TIANYUAN LAB. It turned out to be another symbolic link issue that Print Spooler has quite a history of.

The POC sets up a new printer with a custom spool directory as a non-admin user. This directory is a symbolic link to another directory, which contains a .SHD file that is itself again a symbolic link to some existing file which a non-admin user lacks permissions to delete. Then, by using file locking and performing some operations on the printer, the Print Spooler process (running as Local System) is made to delete said .SHD file, which in fact deletes the file it points to. A non-admin can therefore delete any local file that Local System is able to delete. This can, surprisingly, lead to arbitrary code execution using a trick earlier discovered by another researcher Abdelhamid Naceri and described in this ZDI article.

Microsoft assigned CVE-2022-30206 to this issue and fixed it with July 2022 updates. Not for everyone, of course: we determined that older Windows versions that had stopped receiving updates by then were also affected, and therefore needed our patch.

Having decided to patch this issue, we analyzed it, found the root cause and determined that the best approach would be similar to what we've done with other symbolic link issues in Print Spooler before: add a check in localspl.dll before the DeleteFile call to see if a path to the .SHD file is a symbolic link - and skip the deletion in case it was. This would not break the typical use case without symbolic links, and we doubt that any legitimate use case would involve a .SHD file being a symbolic link.

Having written such micropatch, we started testing it and discovered something peculiar: the linked-to file still got deleted even if our patch properly bypassed the DeleteFile call. What was going on?

It turned out that while we have patched the issue at hand, there was another very similar issue elsewhere in the code that also led to deleting the .SHD file. Interestingly though, this issue was not present on a Windows 10 v1909 updated to its last updates in May 2022. This version of Windows 10 was the last security-adopted Windows version at the time and received Windows updates a bit longer than others - which meant that it must have received a patch for this "newly discovered" issue while others haven't.

To identify this second issue, we tested the POC with our patch on Windows 10 v1909 with different Windows updates starting with May 2022 (the last updates for v1909) and going back in time. This revealed that the issue was fixed with February 2022 updates.

Looking at all vulnerabilities fixed with February 2022 updates, only one fit the description: CVE-2022-21997. It was a "Windows Print Spooler Elevation of Privilege Vulnerability", with the Microsoft advisory stating that "an attacker would only be able to delete targeted files on a system." The issue was reported by Bo Wu, whom we tried to contact for confirmation but unfortunately so far failed to reach.

In any case, it would make no sense to only patch the originally intended CVE-2022-30206, so we also went on to patch this "bonus" issue CVE-2022-21997. We did that with an identical patch, just in another location in localspl.dll. With both patches in place, the POC and the technique it was using no longer worked.

These micropatches were written for the following Versions of Windows with all available Windows Updates installed:

  1. Windows 10 v2004
  2. Windows 10 v1909 (CVE-2022-30206 only)
  3. Windows 10 v1903
  4. Windows 10 v1809
  5. Windows 10 v1803
  6. Windows 7 without ESU, with year 1 of ESU and with year 2 of ESU
  7. Windows Server 2008 R2 without ESU, with year 1 of ESU and with year 2 of ESU
Micropatches have already been distributed to all online 0patch Agents with a PRO or Enterprise license. To obtain these micropatches and have them applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch.

To learn more about 0patch, please visit our Help Center. For a trial or demo please contact [email protected].

We'd like to thank luckyu for publishing their analysis with a proof-of-concept that allowed us to reproduce both vulnerabilities and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Recovery of function prototypes in Visual Basic 6 executables

26 October 2022 at 13:53

I was recently probing into the Visual Basic 6 format trying to see how the IDispatch implementation resolved vtable offsets for functions.

While digging through the code, I came across several structures that I had not yet explored. These structures revealed a trove of information valuable to reverse engineers.

As we will show in this post, type information from internal structures will allow us to recover function prototypes for public object methods in standard VB6 executables.

This information is related to IDispatch internals and does not require a type library like those found with ActiveX components.

For our task of malware analysis, a static parsing approach will be of primary interest.


Most VB6 code units are COM objects which support the IDispatch interface. This allows them to be easily passed around as generic types and used with scripting clients.

IDispatch allows functions to be called dynamically by name. The following VB6 code will operate through IDispatch methods:

This simple code demonstrates several things:

  • VB6 can call the proper method from a generic object type
  • named arguments in the improper order are correctly handled
  • values are automatically coerced to the correct type if possible (here the string “5” is converted to long)

If we add a DebugBreak statement, we can also see that the string “myFunc” is passed in as an argument to the underlying __vbaLateMemNamedCallLd function. This allows IDispatch::Invoke to call the proper function by string name.

From this we can intuit that the IDispatch implementation must know the complete function prototype along with argument names and types for correct invocation to occur.

We can further test this by changing an argument name, adding unexpected arguments, or setting an argument type to something that can not be coerced too long.

Each of these conditions raises an error before the function is called.

Note that there is no formal type library when this code is compiled as a standard executable. This type information is instead embedded somewhere within the binary itself.

So where is this type information stored, and how can we retrieve it?

The Hunt

If we start probing the runtime at BASIC_CLASS_Invoke we will encounter internal functions such as FuncSigOfMember, EpiGetInvokeArgs, CoerceArg, and so on. As we debug the code, we can catch access to known VB6 structures and watch where the code goes from there.

Before we get deeper, I will give a quick high-level overview of the Visual Basic 6 file format.

VB6 binaries have a series of nested structures that layout all code units, external references, forms etc. They start with the VBHeader and then branch out, covering the various aspects of the file.

In the same way that the Windows loader reads the PE file to set up proper memory layout, the VB runtime (msvbvm60.dll) reads in the VB6 file structures to ready it for execution within its environment.

Good references for the VB6 file format include:

The structure and fields names I use in this document primarily come from the Semi-VBDecompiler source. A useful structure browser is the free vbdec disassembler:

For our work, we will start with the ObjectTable found through the VBHeader->ProjectInfo->ObjectTable.

The ObjectTable->ObjectArray holds the number of Object structures as defined by its ObjectCount field. An example is shown below:

This is the top-level structure for each individual code object. Here we will find the ProcNamesArray containing ProcCount entries. This array reveals the public method names defined for the object.

The Meat

The Object->ObjInfo structure will also lead us to further information of interest. ObjInfo->PrivateObject will lead us to the main structure we are interested in for this blog post.

I could not find public documentation on this structure or those below it. What follows is what I have put together through analysis.

Development of this information had me working across four different windows simultaneously.

  • disassembly of the vb runtime
  • disassembly of the target executable
  • debugger stepping through the code and syncing the disasm views
  • specialized tool to view and search vb6 structures

The following is my current definition of the PrivateObj structure:

In the debugger, I saw vb runtime code accessing members within this structure to get to the vtable offsets for an Invoke call. I then compiled a number of source code variations while observing the effects on the structure. Some members are still unknown, but the primary fields of interest have been identified.

The PrivateObj structure leads us to arrays describing each code object’s public function, variable, and event type information.

Counts for the event and variable arrays are held directly within the PrivateObj itself. To get the count for the public functions, we have to reference the top-level Object->ProcCount field.

Note that there is a null pointer in the FuncType array above. This corresponds to a private function in the source code. If you look back, you will also see that same null entry was found in the ProcNames array listed earlier.

Each one of these type information structures is slightly different. First, we will look at what I am calling the FuncTypDesc structure.

I currently have it defined as follows:

The structure displayed above is for the following prototype:

This structure is where we start to get into the real meat of this article. The number of types defined for the method is held within the argSize field. The first three bits are only set for property types.

  • 111 is a property Set
  • 010 is a property Let
  • 001 is a property Get (bFlags bit one will additionally be set)

The type count will be the remaining bits divided by four.

If the bFlags bit one is set, the last entry represents the method’s return value. In all other scenarios, the types represent the arguments from one to arg count. VB6 supports a maximum of 59 user arguments to a method.

An argSize of 0 is possible for a sub routine that takes no arguments and has no return value.

The vOff field is the vtable offset for the member. The lowest bit is used as a flag in the runtime and cleared before use. Final adjusted values will all be 32-bit aligned.

The optionalVals field will be set if the method has optional parameters which include default values.

LpAryArgNames is a pointer to a string array. It is not null- terminated, so you should calculate the argument count before walking it.

After the structure, we see several extra bytes. These represent the type information for the prototype. This buffer size is dynamic.

A mapping of these values was determined by compiling variations and comparing output for changes. The following values have been identified.

The above code, shows that 0x20, 0x40, and 0x80 bits have been set to represent ByRef, Array, and Optional specifiers. These are combined with the base types identified in the enumeration. These do not align with the standard VARIANT VARENUM type values.

The comobj and internal flags are special cases that embed an additional 32-bit value after the type specifier byte.

This will link to the targets ObjInfo structure for internal objects.

If the comobj type is encountered, an offset to another structure will be found that specifies the objects library guid, clsid, library name, and dll path. We will show this structure later when we cover public variables.

It is important to note that these offsets appear to always be 32-bit aligned. This can introduce null padding between the type byte and the data offset. Null padding has also been observed between individual type bytes as well. Parsers will have to be tolerant of these variations.

Below is the type definition for the following prototype:

Next up comes a quick look at the PubVarDesc structure. Here is a structure for the following prototype:

This example shows flag 0x1D to declare an external COM object type. An offset then follows this to a structure which defines the details of the COM object itself.

The value 0x3c in the varOffset field is where the data for this variable is stored. For VB6 COM objects the ObjPtr() returns a structure where the first member is a pointer to the objects Vtable. The areas below that contain class instance data. Here myPublicVar would be found at ObjPtr()+0x3c.

Finally, we will look at an EventDesc structure for the following prototype:

This structure closely follows the layout of the PubFuncDesc type. One thing to note, however, is that I have not currently been able to locate a link to the Event name strings embedded within the compiled binaries.

In practice, they are embedded after the strings of the ProcNamesArray.

Note that the class can raise these events to call back to the consumer. There is no implementation of an event routine within the code object that defines it.


In this post, we have detailed several structures which will allow analysts to parse the VB object structures and extract function prototypes for public object members.

This type information is included as part of the standard IDispatch plumbing for every user-generated VB6 form, class, user control, etc. This does not apply to functions in BAS code modules as they are not COM objects internally.

It is also interesting to note that VB6 embeds this type data right along with the other internal structures in the .text section of the PE file. No type library is required, and this feature can not be disabled.

While the structures portion of the .text section can contain various compiler-generated native code stubs, all user code falls into the memory range as defined by the VBHeader.ProjectInfo.StartOfCode and EndOfCode offsets.

The framework required to analyze a VB6 binary and resolve the information laid out in this posting can be fairly complex. Luckily open-source implementations already exist to help ease the burden.

Structure and prototype extraction routines have already been included in the free vbdec disassembler. This tool can also generate IDC scripts to apply the appropriate structure definitions to a disassembly.

VB6 binaries are typically considered hard to analyze. Extracting internal function prototypes will be a very welcome addition for reverse engineers.

The post Recovery of function prototypes in Visual Basic 6 executables appeared first on Avast Threat Labs.

Micropatches for Kerberos Elevation of Privilege (CVE-2022-33647, CVE-2022-33679)

25 October 2022 at 22:02

by Mitja Kolsek, the 0patch Team

September 2022 Windows Updates brought a fix for an elevation of privilege vulnerability in Kerberos protocol, discovered by James Forshaw of Google Project Zero. James published a detailed analysis, and a POC was subsequently added to their Rubeus tool.

Microsoft assigned James' finding two separate CVE IDs, CVE-2022-33647 and CVE-2022-33679, but these really both have the same root cause, namely the fact that Kerberos supported two weak encryption types: RC4-MD4 (type -128) and RC4-HMAC-OLD (type -133).

James demonstrated that downgrading encryption to RC4-MD4 can allow an attacker to extract the Ticket Granting Ticket (TGT) key and use it for requesting a new TGT for the targeted user, which can be used for launching any code on the domain controller as said user.

Microsoft removed support for both weak encryption types from the Kerberos code. Our micropatch, written only for Windows Server 2008 R2 (the only server that didn't get Microsoft's patch) is logically equivalent to Microsoft's:

MODULE_PATH "..\Affected_Modules\kdcsvc.dll_6.1.7601.24499_Srv2008R2_64-bit_NoESU\kdcsvc.dll"
VULN_ID 7509

        cmp ecx, 0xFFFFFF80  ; is encryption type RC4-MD4?
        je ERROR             ; if so, error out
        cmp ecx, 0xFFFFFF7B 
; is encryption type RC4-HMAC-OLD?
        jne SKIP             ; if so, error out
        mov rax, 0x0         ; rax 0 means "unsupported"




This video demonstrates the effect of our micropatch. With 0patch disabled, launching the POC against a vulnerable Windows 2008 R2 server provides a Ticket Granting Ticket for server's administrator, which then makes it possible to launch a remote terminal session to the server as that user. With 0patch enabled, RC4-MD4 is no longer accepted and the attack fails.

This micropatch has already been distributed to all online Windows Server 2008 R2 computers running 0patch Agent with PRO or Enterprise license. To obtain the micropatch and have it applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch.

To learn more about 0patch, please visit our Help Center. For a trial or demo please contact [email protected].

We'd like to thank James Forshaw of Google Project Zero for publishing their analysis and providing a proof-of-concept that allowed us to reproduce the vulnerability and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.


Instrumenting binaries using revng and LLVM

By: Layle
23 August 2021 at 08:59
Instrumenting binaries using revng and LLVM

One of the first things I ever wanted to implement was an import hooking library that placed the hooks by rewriting the calls statically instead of hooking the functions in-memory. To implement this I ended up using revng. We’ll be exploring the implementation of a similar example to show how you can instrument your own ELF binaries using revng and LLVM. You’ll need a working LLVM development environment and workspace. If you want to set it up using CMake check out this guide.

What is revng?

I think the revng repository explains it very well:

revng is a static binary translator. Given a input ELF binary for one of the supported architectures (currently i386, x86-64, MIPS, ARM, AArch64 and s390x) it will analyze it and emit an equivalent LLVM IR. To do so, revng employs the QEMU intermediate representation (a series of TCG instructions) and then translates them to LLVM IR.

What are we gonna do?

To keep it simple, we’ll be developing a tool that finds a call to dlsym, injects another call to printf printing out the string pointer passed to dlsym. I had this idea during a CTF where the binary wouldn’t allow me to debug the process (debugging it would break a crucial race condition). The task was to figure out what the arguments were and what functions were being called. I ended up using the LD_PRELOAD trick but I figured why not solve it differently :)
You’ll need a dummy binary. Compile the following source code to follow along:

#include <iostream>
#include <dlfcn.h>

typedef uint32_t random_function_t(const char*);

const char* g_encrypted_fn = "qtur";
const char* g_encrypted_str = "udru";

// xor string with key 0x1
char* decrypt(const char* encrypted, size_t encrypted_size) {
    char* ptr = (char*)malloc(encrypted_size + 1);
    for (int i = 0; i < encrypted_size; ++i) {
        ptr[i] = encrypted[i] ^ 1;
    ptr[encrypted_size] = '\0';
    return ptr;

int main() {
    puts("-- test dlsym --");

    auto fn_name = decrypt(g_encrypted_fn, 4);
    auto fn_ptr = (random_function_t*)dlsym((void*)-1, fn_name);

    auto str = decrypt(g_encrypted_str, 4);

    return 0;

Compile it using g++ dummy.cpp -ldl -O0 -o dummy.

Setting up revng

First things first, we need the dependencies, or the setup is gonna kneecap us later on:

sudo apt install python3-pip git git-lfs graphviz graphviz-dev

We’ll have to work with orchestra, which is essentially a build system for various revng tools and dependencies. We’ll be using orchestra in all future posts, so make sure you use this installation method.

# install orchestra
pip3 install --user --force-reinstall
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or use ~/.zshrc, whichever you use
source ~/.bashrc
cd ~
git clone

# update orchestra components
cd orchestra
orc update
orc components

# install orchestra dependencies
sudo ./.orchestra/ci/

# configure revng cmake
orchestra clone revng
orc configure -b revng

# drop into orchestra shell to build revng
orc shell -c revng

# build revng

# make revng available as command
orc install -b revng

revng should now be built and available as a command (revng) as long as you’re in the orchestra shell. You can drop back into it by executing orc shell -c revng in orchestra’s root folder.

Lifting weights binaries 2.0

revng actually executes a series of different binaries. The steps are:

  1. Lift the program to LLVM
  2. Link QEMU helpers to emulate some complex instructions such as floating points
  3. Compile LLVM IR to object code
  4. Link the object code. If you add functions from other libraries while processing the IR, make sure to add them to the command (we won’t be doing this)
  5. Merge linked code with a dynamic binary to fix up various things, resulting in a functional executable

To get a list of (almost) all steps executed, you can run revng --verbose translate ./dummy. We can make two shell scripts out of the output. Note that $1 would originally be dummy.translated.ll:

/home/layle/orchestra/root/bin/revng-lift \
  -g \
  ll \
  dummy \
/home/layle/orchestra/root/bin/llvm-link \
  -S \
  $1 \
  ../../../../../../home/layle/orchestra/root/share/revng/support-x86_64-normal.ll \
  -o \

/home/layle/orchestra/root/bin/llc \
  -O0 \
  $1.linked.ll \
  -o \
  $1.linked.ll.o \
  -disable-machine-licm \

/home/layle/orchestra/root/link-only/bin/c++ \
  ./$1.linked.ll.o \
  -lz \
  -lm \
  -lrt \
  -lpthread \
  -L \
  ./ \
  -o \
  ./dummy.translated \
  -fno-pie \
  -no-pie \
  -Wl,-z,max-page-size=4096 \
  -Wl,--section-start=.o_r_0x400000=0x400000 \
  -Wl,--section-start=.o_rx_0x401000=0x401000 \
  -Wl,--section-start=.o_r_0x402000=0x402000 \
  -Wl,--section-start=.o_rw_0x403d68=0x403d68 \
  -fuse-ld=bfd \
  -Wl,--section-start=.elfheaderhelper=0x3fffff \
  -Wl,-Ttext-segment=0x405000 \
  -Wl,--no-as-needed \
  -ldl \
  -lstdc++ \
  -lc \

# this step is actually not shown but it's needed
cp ./dummy.translated ./dummy.translated.tmp

/home/layle/orchestra/root/bin/revng \
  merge-dynamic \
  ./dummy.translated.tmp \
  ./dummy \
  ./dummy.translated will give us dummy.translated.ll which is our lifted LLVM IR. We’ll be operating on this file.

Examining the LLVM IR

Let’s look at the code where puts is used:

bb.main:                                          ; preds = %dispatcher.entry_epoch_0_address_space_0_type_Code_x86_64
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198977 }, i64 4, i32 1, i8* null)
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198981 }, i64 1, i32 0, i8* null)
  %225 = load i64, i64* @rbp
  %226 = load i64, i64* @rsp
  %227 = add i64 %226, -8
  %228 = inttoptr i64 %227 to i64*
  store i64 %225, i64* %228
  store i64 %227, i64* @rsp
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198982 }, i64 3, i32 0, i8* null)
  %229 = load i64, i64* @rsp
  store i64 %229, i64* @rbp
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198985 }, i64 4, i32 0, i8* null)
  %230 = load i64, i64* @rsp
  %231 = add i64 %230, -32
  store i64 %231, i64* @rsp
  store i64 32, i64* @cc_src
  store i64 %231, i64* @cc_dst
  store i32 17, i32* @cc_op
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198989 }, i64 7, i32 0, i8* null)
  store i64 4202511, i64* @rdi
  call void (%struct.PlainMetaAddress, i64, i32, i8*, ...) @newpc(%struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4198996 }, i64 5, i32 0, i8* null)
  %232 = load i64, i64* @rsp
  %233 = add i64 %232, -8
  %234 = inttoptr i64 %233 to i64*
  store i64 4199001, i64* %234
  store i64 %233, i64* @rsp
  store i64 4198592, i64* @pc
  call void @function_call(i8* blockaddress(@root, %bb.0x4010c0), i8* blockaddress(@root, %bb.main.0x18), %struct.PlainMetaAddress { i32 0, i16 0, i16 4, i64 4199001 }, i64* null, i8* null)
  br label %bb.0x4010c0

revng has a concept of denoting the presence of an original function through a marker (function_call). If we look closer, we can also notice that there are symbols called as the x64 register names: @rdi, @rsp, etc. These are global variables which revng uses to emulate the original CPU state. In revng jargon we call these “CSV” (CPU State Variables).

store i64 4202511, i64* @rdi

If we look even closer, we notice that 4202511 is being stored into @rdi. That decimal is the same as 0x40200f in hexadecimal which is the address our -- test dlsym -- is located in. As a first step, we need to look up libc’s dlsym function prototype: void *dlsym(void *handle, const char *symbol);. We now know that symbol is represented by the x64 register rsi as it’s the 2nd argument. We’ll need this to define the function using LLVM. We are now able to form an idea of what we can do:

  1. Find all references to function_call
  2. Walk all instructions upwards for each reference finding the store instruction pointing to @rsi
  3. Inject a printf call that prints @rsi (second argument in calling convention) before it executes function_call but after executing the store to @rsi

This way, it will eventually print out the string passed into dlsym.

Automation using LLVM and C++

With an idea in mind we can start implementing it. We’ll have to be able to parse and dump LLVM IR files:

#include <iostream>
#include <fstream>
#include <utility>

#include <llvm/IR/Module.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/Verifier.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>

using llvm::LLVMContext;
using llvm::SMDiagnostic;
using llvm::Module;
using llvm::IRBuilder;
using llvm::CallInst;
using llvm::StoreInst;
using llvm::BranchInst;
using llvm::Instruction;
using llvm::FunctionType;
using llvm::ConstantInt;
using llvm::Type;
using llvm::ArrayRef;

void parse(const char* path, std::unique_ptr<Module>& program, LLVMContext& ctx)
    SMDiagnostic error;
    program = llvm::parseIRFile(path, error, ctx);
    if (!program)
        printf("Failed to parse IR file\n");
        error.print(path, llvm::errs());


void dump(const char* path, std::unique_ptr<Module>& program)
    std::string ir;
    llvm::raw_string_ostream stream(ir);
    program->print(stream, nullptr);

    std::ofstream output(path);
    output << ir;

void process(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    // TODO: Process IR

int main(int argc, char* argv[])
    LLVMContext context;
    std::unique_ptr<Module> program = nullptr;
    parse(argv[1], program, context);

    printf("Loaded IR: %s\n", program->getModuleIdentifier().data());

    IRBuilder builder(context);
    process(program, builder);

    // 0 => generated IR is valid
    printf("Verification: %d\n", llvm::verifyModule(*program, &llvm::dbgs()));
    dump(argv[2], program);
    return 0;

It’s time to get to the fun part. The very first thing we are going to do is getting a reference to function_call. Once we have that, we are going to fetch all references (function_call->users() in LLVM).

const auto function_call = program->getFunction("function_call");

for (const auto& user : function_call->users())
    // make sure the reference is actually a call instruction
    if (!llvm::isa<CallInst>(user))

    auto call_instruction = llvm::cast<CallInst>(user);

We are now looping through all references dismissing all non-call instructions. Our next step is getting a reference to the variable @rsi. In the same basic block, there must be at least one store instruction that writes to @rsi. This may not always be the case for all references but it must be the case where function_call actually ends up calling dlsym. Our next task is to find that instruction for each call to function_call. For this, we implement another function that returns the most recent instruction that stores into @rsi.

Instruction* find_store(Instruction* start, const char* target)
    auto previous_instruction = start->getPrevNode();

    while (previous_instruction != nullptr)
        // we only want to check store instructions
        if (llvm::isa<StoreInst>(previous_instruction))
            const auto store_instruction = llvm::cast<StoreInst>(previous_instruction);
            const auto target_operand = store_instruction->getOperand(1);
            const auto operand_name = target_operand->getName().data();

            // make sure the operand (register) to be written matches our target
            if (strcmp(operand_name, target) == 0)
                return previous_instruction;

        previous_instruction = previous_instruction->getPrevNode();

    return nullptr;

Now that we are able to find the store instructions, it’s time to create our printf declaration. We’ll be using the format dlsym => %p\n to print our findings at runtime.

void create_printf(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    // uint64_t printf(char*, uint64_t);
    std::vector<Type*> args = { builder.getInt8Ty()->getPointerTo(), builder.getInt64Ty() };
    auto function_type = FunctionType::get(builder.getInt64Ty(), args, false);

    program->getOrInsertFunction("printf", function_type);

It’s finally time to inject calls to printf into our IR. To do this, we’re going to iterate through all function_call references, get the most recent @rsi store instruction and then emit the printf call immediately after the store instruction. To successfully emit our printf call we also have to generate our format string. To do this we have to keep in mind that @rsi points to a global storage of type i64. This means that we have to load the literal value from the pointer first. To do this we can use builder.CreateLoad(rsi).

void process(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    const auto function_call = program->getFunction("function_call");
    const auto fmt_str = builder.CreateGlobalStringPtr("dlsym => %p\n", "dlsym_fmt", 0, program.get());
    const auto print = program->getFunction("printf");

    for (const auto& user : function_call->users())
        // make sure the reference is actually a call instruction
        if (!llvm::isa<CallInst>(user))

        const auto call_instruction = llvm::cast<CallInst>(user);
        const auto store_instruction = find_store(call_instruction, "rsi");
        if (store_instruction == nullptr)

        const auto rsi = store_instruction->getOperand(1);

        // we want to emit instructions after the store instruction
        const auto loaded = builder.CreateLoad(rsi);
        builder.CreateCall(print, { fmt_str, loaded });

Now that we have written an instrumentation utility that prints out the pointer address passed into dlsym through rsi (2nd argument), we can finally run it on the translated IR and then recompile it back to a functioning ELF executable. To do this, execute the following commands:

# in orchestra shell
dlsym_hook dummy.translated.ll dummy.translated.processed.ll
bash dummy.translated.processed.ll

Originally the binary would print the following text:

$ ./dummy
-- test dlsym --

Let’s see what the instrumented version outputs:

$ ./dummy.translated
dlsym => 0x1
dlsym => 0x4165d4c8
dlsym => 0x4165d4c8
dlsym => 0xffff
dlsym => 0x404021
-- test dlsym --
dlsym => 0x4
dlsym => 0x1d94dc0
dlsym => 0x4

This looks great! Note that not all of those are arguments passed into dlsym. We are intercepting all function_calls. Fun fact: The 0x4 is actually the size passed into decrypt! Either way, we can confidently assume that 0x1d94dc0 points to the decrypted string which is the name of a libc function to be loaded at runtime. To verify this, let’s check out what gdb has to say about it. Execute b printf to set a breakpoint on printf and continue using the command c until you see something along the lines of:

gef➤  c
dlsym => 0x4a2dc0

Make sure that this string was printed after -- test dlsym --. Also, note that the exact pointer may vary. You should be able to see the following string behind the pointer:

Instrumenting binaries using revng and LLVM

At this point, we figured out how to instrument the LLVM IR to dump “decrypted” contents from memory without having to debug the binary at all.

Our code so far looks like this:

#include <iostream>
#include <fstream>
#include <utility>

#include <llvm/IR/Module.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/Verifier.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>

using llvm::LLVMContext;
using llvm::SMDiagnostic;
using llvm::Module;
using llvm::IRBuilder;
using llvm::CallInst;
using llvm::StoreInst;
using llvm::BranchInst;
using llvm::Instruction;
using llvm::FunctionType;
using llvm::ConstantInt;
using llvm::Type;
using llvm::ArrayRef;
using llvm::ConstantDataArray;

void parse(const char* path, std::unique_ptr<Module>& program, LLVMContext& ctx)
    SMDiagnostic error;
    program = llvm::parseIRFile(path, error, ctx);
    if (!program)
        printf("Failed to parse IR file\n");
        error.print(path, llvm::errs());


void dump(const char* path, std::unique_ptr<Module>& program)
    std::string ir;
    llvm::raw_string_ostream stream(ir);
    program->print(stream, nullptr);

    std::ofstream output(path);
    output << ir;

Instruction* find_store(Instruction* start, const char* target)
    auto previous_instruction = start->getPrevNode();

    while (previous_instruction != nullptr)
        // we only want to check store instructions
        if (llvm::isa<StoreInst>(previous_instruction))
            const auto store_instruction = llvm::cast<StoreInst>(previous_instruction);
            const auto target_operand = store_instruction->getOperand(1);
            const auto operand_name = target_operand->getName().data();

            // make sure the operand (register) to be written matches our target
            if (strcmp(operand_name, target) == 0)
                return previous_instruction;

        previous_instruction = previous_instruction->getPrevNode();

    return nullptr;

void process(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    const auto function_call = program->getFunction("function_call");
    const auto fmt_str = builder.CreateGlobalStringPtr("dlsym => %p\n", "dlsym_fmt", 0, program.get());
    const auto print = program->getFunction("printf");

	for (const auto& user : function_call->users())
        // make sure the reference is actually a call instruction
        if (!llvm::isa<CallInst>(user))

        const auto call_instruction = llvm::cast<CallInst>(user);
        const auto store_instruction = find_store(call_instruction, "rsi");
        if (store_instruction == nullptr)

        const auto rsi = store_instruction->getOperand(1);

        // we want to emit instructions after the store instruction
        const auto loaded = builder.CreateLoad(rsi);
        builder.CreateCall(print, { fmt_str, loaded });

void create_printf(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    std::vector<Type*> args = { builder.getInt8Ty()->getPointerTo(), builder.getInt64Ty() };
    auto function_type = FunctionType::get(builder.getInt64Ty(), args, false);

    program->getOrInsertFunction("printf", function_type);

int main(int argc, char* argv[])
    LLVMContext context;
    std::unique_ptr<Module> program = nullptr;
    parse(argv[1], program, context);

    printf("Loaded IR: %s\n", program->getModuleIdentifier().data());

    IRBuilder builder(context);

    create_printf(program, builder);
    process(program, builder);

    printf("Verification: %d\n", llvm::verifyModule(*program, &llvm::dbgs()));
    dump(argv[2], program);
    return 0;

Going the extra mile

Let’s go the extra mile and instrument the binary so that we can output the string at runtime, not just the pointer!
To do this, we have to implement a function that checks the provided pointer and makes sure it’s actually pointing to valid memory. Then, if the check passes, we can print the content of the pointer. To do this, we are going to introduce a new function called print_checked which we are going to implement in C.
To get started let’s create a new function declaration for LLVM first:

void create_print_checked(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    // void print_checked(char*);
    std::vector<Type*> args = { builder.getInt8Ty()->getPointerTo() };
    auto function_type = FunctionType::get(builder.getVoidTy(), args, false);

    program->getOrInsertFunction("print_checked", function_type);

The next step is to create a call to print_checked. As revng emulates x64 registers through global LLVM variables the storage type of rsi is i64. In order to pass LLVMs type checks for print_checked we have to cast our literal value stored in @rsi to a i8*. To do this we insert the following code into our process function:

// emit a call to an external checked printf
const auto ptr_type = Type::getIntNPtrTy(program->getContext(), 8);
const auto ptr = builder.CreateCast(Instruction::CastOps::IntToPtr, loaded, ptr_type);
builder.CreateCall(print_checked, { ptr });

Our code should now look like this:

#include <iostream>
#include <fstream>
#include <utility>

#include <llvm/IR/Module.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/Verifier.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IRReader/IRReader.h>
#include <llvm/Support/SourceMgr.h>

using llvm::LLVMContext;
using llvm::SMDiagnostic;
using llvm::Module;
using llvm::IRBuilder;
using llvm::CallInst;
using llvm::StoreInst;
using llvm::BranchInst;
using llvm::Instruction;
using llvm::FunctionType;
using llvm::ConstantInt;
using llvm::Type;
using llvm::ArrayRef;
using llvm::ConstantDataArray;

void parse(const char* path, std::unique_ptr<Module>& program, LLVMContext& ctx)
    SMDiagnostic error;
    program = llvm::parseIRFile(path, error, ctx);
    if (!program)
        printf("Failed to parse IR file\n");
        error.print(path, llvm::errs());


void dump(const char* path, std::unique_ptr<Module>& program)
    std::string ir;
    llvm::raw_string_ostream stream(ir);
    program->print(stream, nullptr);

    std::ofstream output(path);
    output << ir;

Instruction* find_store(Instruction* start, const char* target)
    auto previous_instruction = start->getPrevNode();

    while (previous_instruction != nullptr)
        // we only want to check store instructions
        if (llvm::isa<StoreInst>(previous_instruction))
            const auto store_instruction = llvm::cast<StoreInst>(previous_instruction);
            const auto target_operand = store_instruction->getOperand(1);
            const auto operand_name = target_operand->getName().data();

            // make sure the operand (register) to be written matches our target
            if (strcmp(operand_name, target) == 0)
                return previous_instruction;

        previous_instruction = previous_instruction->getPrevNode();

    return nullptr;

void process(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    const auto function_call = program->getFunction("function_call");
    const auto fmt_str = builder.CreateGlobalStringPtr("dlsym => %p\n", "dlsym_fmt", 0, program.get());
    const auto print = program->getFunction("printf");
    const auto print_checked = program->getFunction("print_checked");

	for (const auto& user : function_call->users())
        // make sure the reference is actually a call instruction
        if (!llvm::isa<CallInst>(user))

        const auto call_instruction = llvm::cast<CallInst>(user);
        const auto store_instruction = find_store(call_instruction, "rsi");
        if (store_instruction == nullptr)

        const auto rsi = store_instruction->getOperand(1);

        // we want to emit instructions after the store instruction
        const auto loaded = builder.CreateLoad(rsi);
        builder.CreateCall(print, { fmt_str, loaded });

        // emit a call to an external checked printf
        const auto ptr_type = Type::getIntNPtrTy(program->getContext(), 8);
        const auto ptr = builder.CreateCast(Instruction::CastOps::IntToPtr, loaded, ptr_type);
        builder.CreateCall(print_checked, { ptr });

void create_printf(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    std::vector<Type*> args = { builder.getInt8Ty()->getPointerTo(), builder.getInt64Ty() };
    auto function_type = FunctionType::get(builder.getInt64Ty(), args, false);

    program->getOrInsertFunction("printf", function_type);

void create_print_checked(const std::unique_ptr<Module>& program, IRBuilder<>& builder)
    std::vector<Type*> args = { builder.getInt8Ty()->getPointerTo() };
    auto function_type = FunctionType::get(builder.getVoidTy(), args, false);

    program->getOrInsertFunction("print_checked", function_type);

int main(int argc, char* argv[])
    LLVMContext context;
    std::unique_ptr<Module> program = nullptr;
    parse(argv[1], program, context);

    printf("Loaded IR: %s\n", program->getModuleIdentifier().data());

    IRBuilder builder(context);

    create_printf(program, builder);
    create_print_checked(program, builder);
    process(program, builder);

    printf("Verification: %d\n", llvm::verifyModule(*program, &llvm::dbgs()));
    dump(argv[2], program);
    return 0;

As we are compiling with an undefined function we have to seperately generate LLVM IR for it. First things first, let’s create the C code in a file called utils.cpp:

#include <cstdio>
#include <cstdint>

extern "C" void print_checked(char* ptr) {
    if ((uint64_t)ptr > 0xffff) {
        printf("dlsym(???, \"%s\");\n", ptr);

It’s a naive check but it will do the job. Now we have to generate LLVM IR from utils.cpp. clang can actually do this. In add a call to clang and then insert the newly created LLVM IR file (utils.ll) into the llvm-link command. Your file should now look like this (unchanged parts ommited):

clang -S -emit-llvm utils.cpp

/home/layle/orchestra/root/bin/llvm-link \
  -S \
  $1 \
  utils.ll \
  ../../../../../../home/layle/orchestra/root/share/revng/support-x86_64-normal.ll \
  -o \

# ...

Let’s process and then recompile our LLVM IR files again:

# in orchestra shell
dlsym_hook dummy.translated.ll dummy.translated.processed.ll
bash dummy.translated.processed.ll

Let’s examine the output:

$ ./dummy.translated
dlsym => 0x1
dlsym => 0x42f8c4c8
dlsym(???, ���B);
dlsym => 0x42f8c4c8
dlsym(???, ���B);
dlsym => 0xffff
dlsym => 0x404021
dlsym(???, );
-- test dlsym --
dlsym => 0x4
dlsym => 0x1c78dc0
dlsym(???, "puts");
dlsym => 0x4

In case you can’t spot it in all the noise: dlsym(???, "puts");!

We finally did it! We instrumented our executable in a way that it now dumps strings passed to dlsym at runtime without having to place any in-memory hooks. This also means that most anti-debugging tricks are rendered useless.
You can find the entire project including the example code on my GitHub.


I’d like to thank @antoniofrighez, @fcremo and @alerevng for helping me solve all the roadblocks I encountered while using LLVM and revng. Also huge thank you to the entire @_revng team for creating such a wonderful tool.

LLVM with CMake: It's easier than you'd think!

By: Layle
23 August 2021 at 00:00
LLVM with CMake: It's easier than you'd think!

Have you ever wondered how you can set up LLVM using CMake? It’s actually easier than you might think. All thanks to an amazing fork of a project called hunter. You may be wondering: “What’s hunter?”. It’s a very easy to use C++ package manager that you can integrate directly into your CMake projects. We’ll be using a fork that is maintained by my friend @mrexodia. The fork contains definitions for the LLVM project sources.

A few words

We’ll be using Windows in this example, however, in theory this should also work on any other platform.

Setting up your project

We’ll be using a very basic “Hello World!” example that uses LLVM. In essence, we’ll be outputting LLVM IR that hosts a main function which will call puts("Hello World!\n"). I’m using a mixture between WSL2 and Git Bash for some commands. That means, I’ll be using some Linux commands to create files, etc. You are free to use the same set up as I use or use Windows' equivalents.

Let’s create a new project:

mkdir llvm_hello_world
cd llvm_hello_world
touch CMakeLists.txt
mkdir src
touch .\src\main.cpp
mkdir CMake
touch .\CMake\LLVM.cmake
touch .\CMake\HunterPackages.cmake

Your structure should now look like this:

[email protected]:~/Projects/llvm_hello_world$ tree
├── CMake
│   ├── HunterPackages.cmake
│   └── LLVM.cmake
├── CMakeLists.txt
└── src
    └── main.cpp

First things first: HunterPackages.cmake. This file will contain the hunter information needed to pull the LLVM package and make it available to us. We’ll be using version 12.0.1 of LLVM as we’ll need this version anyways in future posts ;) Paste the following code into CMake/HunterPackages.cmake:

# HUNTER_URL is the URL to the latest source code archive on GitHub
# HUNTER_SHA1 is the hash of the downloaded archive

set(HUNTER_URL "")
set(HUNTER_SHA1 "43D382102BE6A8CF218B79E0C33360EDA58FC4BA")


message(STATUS "Fetching hunter...")
FetchContent_Declare(SetupHunter GIT_REPOSITORY

Now that we have those set up we can implement CMake/LLVM.cmake. Paste the following code:

# This is an INTERFACE target for LLVM, usage:
#   target_link_libraries(${PROJECT_NAME} <PRIVATE|PUBLIC|INTERFACE> LLVM)
# The include directories and compile definitions will be properly handled.


# Find LLVM

message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

# Split the definitions properly (

# Some diagnostics (
message(STATUS "LLVM libraries: ${LLVM_LIBRARIES}")
message(STATUS "LLVM includes: ${LLVM_INCLUDE_DIRS}")
message(STATUS "LLVM definitions: ${LLVM_DEFINITIONS}")

add_library(LLVM INTERFACE)
target_include_directories(LLVM SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS})
target_link_libraries(LLVM INTERFACE ${LLVM_AVAILABLE_LIBS})


Alright, now we should have LLVM fully exposed to our project. All we have to do now is build against it. To do this we have to hop over to our CMakeLists.txt and insert a fairly standard version of a CMakeLists file.

cmake_minimum_required(VERSION 3.19)



# Enable solution folder support

# Append the CMake module search path so we can use our own modules

# Require C++20

# LLVM wrapper

# MSVC-specific options
    # This assumes the installed LLVM was built in Release mode

        set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
        set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)

add_executable(${PROJECT_NAME} src/main.cpp)

# Link against LLVM
target_link_libraries(${PROJECT_NAME} PRIVATE LLVM)

# Set the plugin as the startup project

We should be all set now! To test our build system with an IDE execute the commands below. If you want to use VSCode: open the project root folder and configure the project using whatever kit you want. I personally like to use the “Visual Studio Community 2019 Release - amd64” kit most of the time. In this case, skip the commands once you’ve configured your project.

mkdir build
cd build
cmake ..

Keep in mind that this might take a while if it’s your first time configuring LLVM using hunter. Now that we have everything set up we can go ahead and create a simple example.

Hello world LLVM!

Let’s jump into our C++ source file main.cpp. We should first define a raw skeleton. That is, the includes, types we need and our main function:

#include <llvm/IR/Module.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/IRBuilder.h>

#include <vector>
#include <memory>

using llvm::LLVMContext;
using llvm::IRBuilder;
using llvm::Module;
using llvm::FunctionType;
using llvm::Function;
using llvm::BasicBlock;
using llvm::Type;
using llvm::ArrayRef;

int main(int argc, char* argv[])
    return 0;

The first thing we have to do is create a Module which is the host of all of our definitions such as imports, exports, functions, basic blocks, etc. We also need a IRBuilder which is the object allowing us to interact with the instructions, basic blocks and types. Note that I’m providing a rough TLDR, for more information always consult the LLVM documentation.

LLVMContext context;
IRBuilder builder(context);
const auto module = std::make_unique<Module>("hello_llvm", context); // hello_llvm => module name

Before we can create basic blocks we have to create a function and it’s type. In LLVM we can use the classes FunctionType and Function. We have to define our main function first (the entrypoint to our program). A simple void main(); should suffice for that:

// builder.getVoidTy() => void main() {
const auto func_type = FunctionType::get(builder.getVoidTy(), false);
const auto main_func = Function::Create(func_type, Function::ExternalLinkage, "main", module.get());

Now that we have our main function set up we can finally create our first basic block.

// main_func being the parent of the basic block
const auto entry = BasicBlock::Create(context, "entrypoint", main_func);
builder.SetInsertPoint(entry); // set instruction insertion point to this basic block

You may be wondering how we are going to use an external function such as puts from within LLVM. It’s actually quite simple. All we have to do is create another function definition and a definition for the arguments. puts is defined as int puts(const char *s);, let’s implement this in LLVM:

// builder.getInt8Ty()->getPointerTo() => char*, a pointer to the null terminated string
const std::vector<Type*> puts_args = { builder.getInt8Ty()->getPointerTo() };
const ArrayRef puts_args_ref(puts_args);

// builder.getInt32Ty() => uint32_t, the return type
const auto puts_type = FunctionType::get(builder.getInt32Ty(), puts_args_ref, false);
const auto puts_func = module->getOrInsertFunction("puts", puts_type);

There’s only two more things to implement to get a working example: Creating a global string containing “Hello LLVM!” and inserting instructions into the basic block.

auto str = builder.CreateGlobalStringPtr("Hello LLVM!\n");

// equivalent to: puts("Hello LLVM!"); return;
builder.CreateCall(puts_func, { str });
builder.CreateRetVoid(); // in LLVM, blocks need a terminator

That’s it! Let’s have a look at the generated IR. To do this call module->dump(). Here you can find the full source code:

#include <llvm/IR/Module.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/IRBuilder.h>

#include <vector>
#include <memory>

using llvm::LLVMContext;
using llvm::IRBuilder;
using llvm::Module;
using llvm::FunctionType;
using llvm::Function;
using llvm::BasicBlock;
using llvm::Type;
using llvm::ArrayRef;

int main(int argc, char* argv[])
    LLVMContext context;
    IRBuilder builder(context);
    const auto module = std::make_unique<Module>("hello_llvm", context);

    const auto func_type = FunctionType::get(builder.getVoidTy(), false);
    const auto main_func = Function::Create(func_type, Function::ExternalLinkage, "main", module.get());

    const auto entry = BasicBlock::Create(context, "entrypoint", main_func);

    const std::vector<Type*> puts_args = { builder.getInt8Ty()->getPointerTo() };
    const ArrayRef puts_args_ref(puts_args);

    const auto puts_type = FunctionType::get(builder.getInt32Ty(), puts_args_ref, false);
    const auto puts_func = module->getOrInsertFunction("puts", puts_type);

    auto str = builder.CreateGlobalStringPtr("Hello LLVM!\n");

    builder.CreateCall(puts_func, { str });


    return 0;

You should see something along the lines of:

; ModuleID = 'hello_llvm'
source_filename = "hello_llvm"

@0 = private unnamed_addr constant [13 x i8] c"Hello LLVM!\0A\00", align 1

define void @main() {
  %0 = call i32 @puts(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @0, i32 0, i32 0))
  ret void

declare i32 @puts(i8* %0)

If you are interested in generating an executable from the IR, save it to a file called hello_llvm.ll and execute the following commands:

$LLVM12_PATH = "C:\.hunter\_Base\Cellar\204034a1dbe9cf2995b07fc1f3542b939059d116\204034a\raw\bin"
& "$($LLVM12_PATH)\llvm-link.exe" .\hello_llvm.ll -o hello_llvm.bc
& "$($LLVM12_PATH)\llc.exe" -filetype=obj .\hello_llvm.bc
& "$($LLVM12_PATH)\clang.exe" .\hello_llvm.obj -o hello_llvm.exe
Hello LLVM!

Congrats! You’ve just set up LLVM using CMake for the first time and created your first functional “Hello World!” equivalent using LLVM IR :)

Lifting binaries to LLVM with McSema

By: Layle
25 July 2021 at 00:00
Lifting binaries to LLVM with McSema

Before embarking on my journey of lifting x64 binaries to LLVM by using revng and eventually my own tooling I worked with McSema which looked very promising. Unfortunately, using McSema wasn’t as straight forward as I had hoped and working with the lifted LLVM IR never really yielded sufficient results. This post will guide you through my set up and we’ll explore what worked and what didn’t (maybe it works for you!). We’ll be using Windows as host system as most of you have IDA Pro for Windows anyways ;) Along with Windows we’ll also be making use of WSL 2, so make sure you have that already set up! Alternatively, a Ubuntu 20.04 LTS VM works too.

Getting ready

Boot into your Ubuntu instance (may that be WSL 2 or another VM). Make sure you’re able to share files between host and guest.

We’ll need a few dependencies first:

sudo apt-get update
sudo apt-get upgrade

sudo apt-get install \
     git \
     curl \
     cmake \
     python3 python3-pip python3-virtualenv \
     wget \
     xz-utils pixz \
     clang \
     rpm \
     build-essential \
     gcc-multilib g++-multilib \
     libtinfo-dev \
     lsb-release \
     zip \
     zlib1g-dev \
     ccache \

Now that we have the dependencies set up, we can execute the following commands (taken from the README) to pull McSema and build it:

# I used my home directory but feel free to place it wherever you want
cd ~

git clone --depth 1 --single-branch --branch master
git clone --depth 1 --single-branch --branch master

# Get a compatible anvill version
git clone --branch master
( cd anvill && git checkout -b release_bc3183b bc3183b )

export CC="$(which clang)"
export CXX="$(which clang++)"

# Download cxx-common, build Remill. 
./remill/scripts/ --llvm-version 9 --download-dir ./
pushd remill-build
sudo cmake --build . --target install

# Build and install Anvill
mkdir anvill-build
pushd anvill-build
# Set VCPKG_ROOT to whatever directory the remill script downloaded
cmake -DVCPKG_ROOT=$(pwd)/../vcpkg_ubuntu-20.04_llvm-9_amd64 ../anvill
sudo cmake --build . --target install

# Build and install McSema
mkdir mcsema-build
pushd mcsema-build
cmake -DVCPKG_ROOT=$(pwd)/../vcpkg_ubuntu-20.04_llvm-9_amd64 ../mcsema
sudo cmake --build . --target install

pip install ../mcsema/tools


Now that McSema is set up we can finally get to lifting binaries! I’ll be using /bin/cat with the MD5 7e9d213e404ad3bb82e4ebb2e1f2c1b3. Let’s hop over to our Windows host.

Lifting weights binaries

One of the first things we have to do is recovering a control flow graph. To do this, McSema actually comes with IDAPython scripts. To recover the control flow graph execute the following command in Powershell:

# Path to your totally legit IDA Pro installation
$IDA_ROOT = "D:\Reversing\Tools\IDA Pro 7.6\IDA Pro 7.6"
# Path to your cloned McSema repository
$MCSEMA_ROOT = "C:\Users\luca\Documents\Git\mcsema"
# Path to your executable 
$EXECUTABLE_TO_LIFT = "C:\Users\luca\Downloads\cat"
# Path to outputted control flow graph
$CFG_PATH = "C:\Users\luca\Downloads\cat.cfg"

& "$($IDA_ROOT)\ida64.exe" -S"$($MCSEMA_ROOT)\tools\mcsema_disass\ida7\ --output $($CFG_PATH) --log_file \\.\nul --arch amd64 --os linux --entrypoint main --pie-mode --rebase 535822336" $EXECUTABLE_TO_LIFT

The arguments should all be self explanatory. However, the argument --rebase may not. We need to specify the address to rebase to when we use --pie-mode (PIE binaries). This number can be any address, in this example I used 0x1ff00000 in decimal. More information here.

IDA Pro should pop up. Confirm the architecture and hit “OK”. Once IDA Pro finished recovering the control flow graph verify that you have it in the specified path.

We are now ready to lift the control flow graph to LLVM. To do that execute the following command in your console of choice (make sure you’re in either WSL 2 or in your VM):

# cd into the folder that contains cat.cfg
mcsema-lift-9.0 --cfg cat.cfg --output cat.bc --os linux --arch amd64 --explicit_args --merge_segments --name_lifted_sections

Alright, we now have the LLVM bitcode file. This is essentially the LLVM IR bitcode of the cat binary. Ideally we’d want to look at the LLVM IR in human readable format. To do that execute the following command:

llvm-dis cat.bc -o cat.ll

Congrats, you finally have lifted your binary to LLVM! Now let’s examine what happens if we try to recompile it back:

llvm-link cat.ll -o cat.recompiled.bc
# to figure out the libraries to link against use "ldd /bin/cat"
remill-clang-9 -o cat.recompiled cat.recompiled.bc -Wl,--section-start=.section_1ff00000=0x1ff00000

Alright, let’s give it a shot:

./cat.recompiled helloworld.txt
Segmentation fault (core dumped)

Well, that’s a bummer. I figured it’s a hit or miss situation. I tried McSema on some other binaries (mostly CTF challenges) and it seemed to work. However, as soon as I tried instrumenting the IR (by adding simple calls or primitive instructions) every binary started segfaulting again. This may be a mistake on my side, however, at this point I started using revng and ditched McSema entirely. We’ll cover more about that in my next article (with a hands on example!).

That being said: I hope you’ll find more luck with McSema!

RACEAC: Breaking Dead by Daylight's integrity checks

By: Layle
4 June 2021 at 00:00
RACEAC: Breaking Dead by Daylight's integrity checks

In an attempt to stop people from cheating by modifying game files, Dead by Daylight received an update that introduced integrity checks for the pak files/assets. In other words, things such as disabling models to get a better view and/or disabling certificate pinning for network interception were no longer possible. Unless…?

The bug

The bug is quite simple, I stumbled upon this behavior when I was analyzing how DbD loads their assets using Procmon and I noticed that EAC performs checks on the files, but the game itself reopens the file to read the actual content. I am not too familiar how the EAC SDK looks like but I’d assume that the SDK gives you the capability to get a handle to a file (and have it verify it’s integrity automatically) and you are supposed to reuse that handle as you read from the file.
In other words, we found a TOCTOU (Time Of Check, Time Of Use vulnerability)! The game verifies the assets' integrity, but what happens if we modify the assets right before the game reopens the file again, just seconds after finishing the integrity checks? Let’s find out…

The PoC

There’s no fancy tricks needed here, all we need is a bit of force when opening files. The PoC disables certificate pinning which allows us to sniff traffic using tools like Fiddler. The SSL settings are stored in the file pakchunk0-WindowsNoEditor.pak. Now, let’s peek at the code:

void detect_eac(std::wstring path)
	while (true)
		auto pak = open_pak(path);
		if (pak == INVALID_HANDLE_VALUE) break;

void* race_eac(std::wstring path)
	while (true)
		auto pak = open_pak(path);
		if (pak != INVALID_HANDLE_VALUE) return pak;

	return nullptr;


We call detect_eac to make sure that the pak file is currently opened and therefore locked by EAC. This leads us to the next stage, race_eac. We try to open the pak file until it just works™. Once we get a valid handle the following pattern is replaced with NULL bytes:

const std::vector<char> pattern =
    0x2B, 0x50, 0x69, 0x6E, 0x6E, 0x65, 0x64, 0x50, 0x75, 0x62, 0x6C, 0x69,
    0x63, 0x4B, 0x65, 0x79, 0x73, 0x3D, 0x22, 0x73, 0x74, 0x65, 0x61, 0x6D,
    0x2E, 0x6C, 0x69, 0x76, 0x65, 0x2E, 0x62, 0x68, 0x76, 0x72, 0x64, 0x62,
    0x64, 0x2E, 0x63, 0x6F, 0x6D, 0x3A, 0x2B, 0x2B, 0x4D, 0x42, 0x67, 0x44,
    0x48, 0x35, 0x57, 0x47, 0x76, 0x4C, 0x39, 0x42, 0x63, 0x6E, 0x35, 0x42,
    0x65, 0x33, 0x30, 0x63, 0x52, 0x63, 0x4C, 0x30, 0x66, 0x35, 0x4F, 0x2B,
    0x4E, 0x79, 0x6F, 0x58, 0x75, 0x57, 0x74, 0x51, 0x64, 0x58, 0x31, 0x61,
    0x49, 0x3D, 0x3B, 0x45, 0x58, 0x72, 0x45, 0x65, 0x2F, 0x58, 0x58, 0x70,
    0x31, 0x6F, 0x34, 0x2F, 0x6E, 0x56, 0x6D, 0x63, 0x71, 0x43, 0x61, 0x47,
    0x2F, 0x42, 0x53, 0x67, 0x56, 0x52, 0x33, 0x4F, 0x7A, 0x68, 0x56, 0x55,
    0x47, 0x38, 0x2F, 0x58, 0x34, 0x6B, 0x52, 0x43, 0x43, 0x55, 0x3D, 0x22


This pattern translates to the following sequence of chars:


This is one of the public keys used for certificate pinning. Simply removing it from the pak file will disable certificate pinning.


The following demo shows a successful bypass, allowing us to dump network traffic. Tools for network analysis can be found on my other repo.

Last words

The PoC does not work with the current version of DbD but it seems like they disabled integrity checks altogether. That is, the code never breaks out of race_eac but restarting DbD without terminating the PoC actually makes it work again. I didn’t look at this in the past few months but if you know more, please let me know!

Breaking Dead by Daylight without process interaction

By: Layle
15 April 2020 at 00:00
Breaking Dead by Daylight without process interaction

This article is a mirror of the previous release posted on the secret club blog.

For the past few months I’ve been looking into a game called Dead by Daylight which is protected by EasyAntiCheat. This game is unique in a special way and we’ll be exploring why. All these methods are already to be found on various types of forums and nothing is per se ground breaking. This is a guide about how I approached these things while shedding light into the inner workings of the actual cheats. Do not use this to get an advantage in the game.


What makes Dead by Daylight special? Dead by Daylight has a quite long record in terms of bugs and lazy coding. The game communicates with the game server using a REST API which is quite easy to reverse. As mentioned, the game is protected by EasyAntiCheat but in this case I tried to develop as many bypasses and cheats as possible without interacting with the game process at all.

Version 3.0.0

Version 3.0.0 is quite famous in the particular cheating scene. It was one of the last versions that had no SSL pinning embedded, therefore making it easy to reverse the API. However, currently version 3.6.2 is out meaning that version 3.0.0 is no longer available to download through the store. Luckily, this isn’t stopping us thanks to Steam’s depot system which allows us to download any version of the game as long as we know the game specific IDs which can be looked up on SteamDB. To do this we have to run Steam with the “-console” flag and then enter download_depot 381210 381211 9043651681125706667. Unfortunately, this isn’t working in the latest versions of Steam, but no worries, I’ll explain why. It turns out that the manifest file for this version is not available anymore. Luckily, Steam doesn’t really care about it as long as we just invert the check :)

Breaking Dead by Daylight without process interaction

You can find an automated patcher on GitHub which you can run while Steam is running. Once the files are downloaded we can copy them over to Dead by Daylights installation path and fire up a sniffer like mitmdump or Fiddler.


There’s 2 request of particular interest. The first one is a POST request which sends the current game version to the server to check whether there’s any:

POST<token> HTTP/1.1
Accept: */*
Accept-Encoding: deflate, gzip
Content-Type: application/json
User-Agent: DeadByDaylight/++DeadByDaylight+Live-CL-134729 Windows/10.0.19587.1.256.64bit
Content-Length: 78


This is easily bypassed by intercepting the traffic and changing the JSON body with the latest version IDs:


The response of this request contains a cookie called bhvrSession which we will need to further authenticate to the server.

One of the other important requests is the following GET request:

Accept: */*
Accept-Encoding: deflate, gzip
Content-Type: application/json
User-Agent: DeadByDaylight/++DeadByDaylight+Live-CL-134729 Windows/10.0.19587.1.256.64bit
Content-Length: 0



The request was stripped down to the most important part. The server returns all available versions that are allowed to log into the game server. This is easily bypassed by adding the identifiers for version 3.0.0 to the response body. Here’s a snippet from my script:

response["availableVersions"][""] = ""
response["availableVersions"][""] = ""
response["availableVersions"][""] = ""

Unfortunately, Dead by Daylight introduced further checks based on locally generated tokens which are not present in this version of the game. This didn’t stop other cheaters to just embed the keys from the latest version into their own script and sending the values manually.

Of course I won’t let you sit around without knowing how some of the cheats handle this which is why I reversed one of them:

public static string DecryptSettings(string cryptedString)
    string result;
    using (DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider())
        using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(cryptedString)))
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateDecryptor(Encoding.ASCII.GetBytes("Kowalski"), Encoding.ASCII.GetBytes("XSkipper")), CryptoStreamMode.Read))
                using (StreamReader streamReader = new StreamReader(cryptoStream))
                    result = streamReader.ReadToEnd();
    return result;

Yeah, that’s it. The keys are stored in settings.cfg. The last keys I had dumped from the cheat are:

    "shortVersion": "gABAC7ps70O7AIQpTyDat2oXiesXt9MfXTwF4N+JuJ2bPtVLgUTNoScQqOCPG6t5YuQKkwfPeT6wT38PgeSZneZYeWtbfLVugRnnL/TbeS69utpJ6LXRYfX++4/AYQZ/6vurmRBop30Ss+QhizqLsZ0p7t3p7C6mAbULpI8MWkLu/NwmwgBKCg6Q6RzvjL+yUJWZNwZCOobiFwCLtla7yLjfXs93NJqny8UkFeIHbM4HOiUw19+aERrCvkYRgiOAfl7UVT3hISabVjj9I3XXOki+Ax45FT/mIygwrOwj3RpKPCx8a7/N4rEULj17etYFZWxAK6gi02gtgctP01G0Kg==",
    "longVersion": "fdNyatrP2l9g4EtW6S/FPsymfTGs6AjtgJi0vdLc3eCFLBBVd20gaTSC2JgKVsmx+r8sphoAraxWYT5hkMylKAlmC+7o2ZXILhLWSdOrFWqhs7gYlSXc/+6gaLOZ4fYC4m42hRLekInZL1ikIdzab6cvdbVdvmCNSWeaR9fXSRM+KKNFl9RagD5ZKOh2vFCDV1xquol2Wq+y5Q7LCBpqtvppQ59YimbtjZoaFHPVVIbaxFyhueelqe02IOOC4OWD9Kmtj7WmbGpekcMJRhdjz/NDmnJc1tmy4U5VvgnVwmC8o+plQtcLIFvpFKpKm6bkAnyiXCdy9puQe/X8S2kV6Q=="

Feel free to decrypt the newer versions and use his keys :) Anyways, we wont do that. Lets upgrade to 3.6.2 and do it like the cool kids.

Version 3.6.2

Upgrade as usual, you can do that by invalidating the files in Steam. Reinstalling works fine too. As mentioned before, Dead by Daylight has introduced SSL pinning, so how do we do it? Let’s take a deep dive into reverse engineering the game itself.

The true hero: Constants

First things first, we need as much information of the target binary as possible. One of the first things I like to do is checking for strings:

[email protected]$ rabin2 -zzz DeadByDaylight-Win64-Shipping.exe | grep curl
1245065 0x04a4e368 0x144a4f568  37  38 (.rdata) ascii CLIENT libcurl 7.55.1-DEV\r\n%s\r\nQUIT\r\n

Now we know they use libcurl version 7.55.1, probably a debug build. Even better! Let’s check the official documentation on how SSL certificates are handled. You can find the specific page here. Two constants are important: CURLOPT_SSL_VERIFYHOST and CURLOPT_SSL_VERIFYPEER. Let’s download the specific source code and open it up. Searching for those constants reveal an enum in curl.h:


Now let’s navigate to url.c where Curl_setopt is defined. Here’s a short excerpt of the function:

CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option,
                     va_list param)
  char *argptr;
  CURLcode result = CURLE_OK;
  long arg;
  curl_off_t bigsize;

  switch(option) {
    data->set.dns_cache_timeout = va_arg(param, long);
    /* remember we want this enabled */
    arg = va_arg(param, long);
    data->set.global_dns_cache = (0 != arg) ? TRUE : FALSE;
// ...
   if(strcasecompare(argptr, "ALL")) {
      /* clear all cookies */
      Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
    else if(strcasecompare(argptr, "SESS")) {
      /* clear session cookies */
      Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
    else if(strcasecompare(argptr, "FLUSH")) {
      /* flush cookies to file, takes care of the locking */
      Curl_flush_cookies(data, 0);
    else if(strcasecompare(argptr, "RELOAD")) {
      /* reload cookies from file */

The possibilities are endless. We have a lot of inlined constants and a lot of string references at our disposal to find the function in memory. The function starts with the following assembly:

mov qword ptr ss:[rsp+8],rbx
mov qword ptr ss:[rsp+10],rbp
mov qword ptr ss:[rsp+18],rsi
push rdi
sub rsp,30
xor ebp,ebp
mov rsi,r8
mov rbx,rcx
cmp edx,D2

As we can see the register edx will contain the constant. Depending on what value is set it will branch to different code to apply the options passed to the function. We are primarily looking for the constants 0x40 and 0x51. As we know from the original source code, the third argument contains the value, in this case it will be either true or false. The third register is r8 and contains a pointer to the memory holding the flag. We can use x64dbg’s conditional breakpoints to automatically patch memory on trigger:

Breaking Dead by Daylight without process interaction

In the following image you can see that edx is set to 0x40 and that register r8 points to our patched value in memory.

Breaking Dead by Daylight without process interaction

You may be wondering: “But we are touching the process?”. That’s right, we are and that’s why I’m gonna stop here :) I’m currently working on an usermode emulator for EasyAntiCheat (which I’ve been using in the above screenshot), we will cover more about the debugging features in that article.


Looking back, I wish I had tried doing this before. Looking at Dead by Daylight’s track record it’s quite obvious that they must have messed up something trivial even in the latest version. The game uses Unreal Engine 4. All game assets are stored in so called “pak” files. Checking the strings of these files already reveals a whole lot of other information. Would you believe me if I told you Dead by Daylight doesn’t do any integrity checks on the “pak” files? Right. There’s 2 ways to approach this, either we give QuickBMS a shot or we use a hex editor. In this article we will be using the hex editor approach, doing it with QuickBMS is left as an exercise to the reader. The advantage would be that you can patch all configuration files and assets in the game, allowing you to get really close to a full wallhack. The file we will be editing is called pakchunk0-WindowsNoEditor.pak and hosts the main configuration files that are relevant for cheating purposes. Look for SSL and just erase the content to null bytes.

Breaking Dead by Daylight without process interaction

Here’s a dump of all the pinned keys that are being used:


Anyways, save the file, open up your proxy (in my case mitmweb) and run the game!


At this stage you should be able to see the requests. As you can see there’s also a new request to the endpoint clientVersion/check. Remember the keys I mentioned earlier? Those keys are sent to this endpoint, verifying our bhvrSession to be allowed to send further requests to the game server. However, we don’t have to worry about them anymore since the game does that for us without an issue.

Breaking Dead by Daylight without process interaction

Eventually we will end up with a request to FullProfile/binary at this stage it’s a GET request which fetches the profile data of the current player.



This data contains the inventory, which is particularly interesting to cheat items. A very similar endpoint is used using a POST request which uploads the inventory. It is possible to decrypt the inventory, edit it, encrypt it and then send this to the server. This allows us to get whatever items we want, with whatever perks we want. To make things crystal clear, here’s how they decrypt the profile:

cipher  ="5BCC2D6A95D4DF04A005504E59A9B36E", AES.MODE_ECB)
profile = flow.response.content.decode()[8:]
profile = base64.b64decode(profile)
profile = cipher.decrypt(profile)
profile = "".join([chr(c + 1) for c in profile])
profile = base64.b64decode(profile[8:])
profile = profile[4:len(profile)]
profile = zlib.decompress(profile).decode("utf16")
profile = json.loads(profile)

Encryption works the same way, just in reverse. This is also left as an exercise for the reader.


There’s also another request sent to and endpoint named /getUrl which provides you with a URL to a websocket endpoint. There’s a lot of information flowing through, some of it is in the game state :) Unfortunately, only one client can be connected at a time, doesn’t stop you from proxying the websocket connection though. This is out of scope and is only mentioned as information, we will be doing this in an even funnier way. Nonetheless, sample code is provided here.

Predicting killers

Recently some posts emerged of people polling the Dead by Daylight logfile to figure out the killer while being in the lobby. That’s all fun and stuff, but code like this just shouldn’t be written at all. Especially not if the author claims it to be for “learning purpose”:

yourId2 = (str(x2[len(x2) - 2].split('\n[')[0]))[46:]
yourId3 = (str(x2[len(x2) - 3].split('\n[')[0]))[46:]
yourId4 = (str(x2[len(x2) - 4].split('\n[')[0]))[46:]
yourId5 = (str(x2[len(x2) - 5].split('\n[')[0]))[46:]
verifyKiller2 = (((((str(x2[len(x2) - 2].split('GameFlow: Verbose:')[0]))[509:]).split('\n'))[0]).split('_'))[0]
verifyKiller3 = (((((str(x2[len(x2) - 3].split('GameFlow: Verbose:')[0]))[509:]).split('\n'))[0]).split('_'))[0]
verifyKiller4 = (((((str(x2[len(x2) - 4].split('GameFlow: Verbose:')[0]))[509:]).split('\n'))[0]).split('_'))[0]
verifyKiller5 = (((((str(x2[len(x2) - 5].split('GameFlow: Verbose:')[0]))[509:]).split('\n'))[0]).split('_'))[0]

Let’s do it the right way. From our debugging session earlier you might have noticed that the game prints strings to the “Log” tab in x64dbg. This is because Windows redirects all debug strings that end up in DbgPrint to the debugger if one is attached. However, it’s actually possible to access these strings without a debugger and I’ve just recently reversed how to do so from DebugView. You can find the reversed project on my personal GitHub. The basic idea is to access Windows' message buffer (DBWIN_BUFFER) and wait for certain events (DBWIN_BUFFER_READY and DBWIN_DATA_READY). This allows us to interact with the games logs in realtime without having to poll any text files and accidentally filter out old information which isn’t relevant. Utilizing very basic Regular Expressions we are able to determine the killer (even if he changes the character while being in the lobby which the original script can’t do!) and also his Steam profile. The options are endless! Here’s a broken down version of what is actually going on:

const auto process_id = find_process("DeadByDaylight-Win64-Shipping.exe");

const std::regex character_id_pattern("Spawn new pawn characterId (\\d+)\\.");
const std::regex steam_id_pattern("Session:GameSession PlayerId:([0-9\\-a-z]+)\\|(\\d+)");
const std::regex killer_pattern("MatchMembersA=\\[\\\"([a-z0-9\\-]+)\\\"\\]");
auto buffer_ready = open_event(
auto data_ready = open_event(
auto file = open_mapping(
auto buffer = reinterpret_cast<message*>(
        0, 0, 0));

std::string killer_id;

while (wrapper::wait_for_single_object(
    if (buffer->process_id == process_id)
        auto message = std::string(buffer->data);
        std::smatch matches;

        if (std::regex_search(message, matches, character_id_pattern))
            auto character_id = std::stoi(matches[1].str());
            auto killer = KILLERS.find(character_id);
            if (killer != KILLERS.end())
                std::cout << "Killer: " << killer->second << std::endl;
        else if (std::regex_search(message, matches, steam_id_pattern))
            auto player_id = matches[1].str();
            auto steam_id = matches[2].str();
            if (player_id == killer_id)
                std::cout << "Killer Steam Profile:" << steam_id << std::endl;
        else if (std::regex_search(message, matches, killer_pattern))
            killer_id = matches[1].str();
            std::cout << "Found Killer PlayerID: " << killer_id << std::endl;



This code is just for demonstration purposes, you can find the project here.

Breaking Dead by Daylight without process interaction

Ranking up

During my research I created a script ( that automates information dumping (such as bhvrSession) and also decrypts the inventory. The data is being written in separate JSON files. Once the files are created you are free to run the scripts and The scripts send basic POST requests to the endpoints /api/v1/ranks/pips and /api/v1/extensions/playerLevels/earnPlayerXp respectively. The needed JSON bodies are as follows (in the same order as above):

    "data": {
        "consecutiveMatch": 1,
        "emblemQualities": [
        "isFirstMatch": true,
        "levelVersion": 1337,
        "matchTime": 1000,
        "platformVersion": "steam",
        "playerType": "survivor"
/api/v1/ranks/pips request body
    "forceReset": true,
    "killerPips": 2,
    "survivorPips": 2
/api/v1/extensions/playerLevels/earnPlayerXp request body

Final words

To wrap things up I would like to say that there’s way more to explore and the code published can be improved in a lot of ways. I believe the most important take away is that debug events/strings can be really dangerous if used excessively. I believe that the general approaches and efforts from Behaviour are fine. However, some details still have room for improvement such as fixing their anticheat to check all game files. One more important note that I haven’t mentioned is the fact the pak files do not use an encryption key. Unreal Engine 4 has a feature to encrypt all game files with a specific key. Of course this can be reverse engineered but it makes creating cheats harder nonetheless. If there’s any open questions or feedback feel free to reach out to me on Twitter.

Free Micropatches For Bypassing "Mark of the Web" on Unzipped Files ("ZippyReads" / CVE-2022-41049)

17 October 2022 at 13:44


by Mitja Kolsek, the 0patch Team

Update 11/8/2022: This issue, nicknamed "ZippyReads", got an official fix with November 2022 Windows Updates which assigned it CVE-2022-41049. Our micropatches for it are therefore no longer free and require a PRO or Enterprise license. Details have also been published.


In May, security researcher Will Dormann found a vulnerability in Windows that allows an attacker to prevent Windows from setting the "Mark of the Web" flag on files extracted from a ZIP archive, even if the ZIP archive came from an untrusted source such as Internet, email, or a USB key.

Mark of the Web (MOTW) is an important security mechanism in Windows:

  • Windows will show a security warning before launching an executable file with MOTW;
  • Smart App Control only works on files with MOTW (source);
  • Microsoft Office blocks macros on documents with MOTW (source).


Attackers therefore understandably prefer their malicious files not being marked with MOTW; this vulnerability allows them to create a ZIP archive such that extracted malicious files will not be marked.

Will has notified Microsoft about this issue in July, but an official fix has not been provided yet. Meanwhile, the vulnerability is apparently being exploited in the wild.

We're happy to report that we've just issued micropatches for this vulnerability, and are - according to our guidelines - providing it free of charge until Microsoft has issued their official fix. 

We will not provide details on the vulnerability at this point.


Our Micropatch In Action

The video below shows our micropatch in action. With 0patch disabled (and the micropatch therefore not applied), an executable file in a ZIP archive that is marked with MOTW executes without a warning, because the file itself is not marked with MOTW upon extraction. With our micropatch in place, the extraction code is corrected and properly applies the MOTW to the extracted file - which results in the security warning being shown to the user.

Similarly, an attacker could deliver Word or Excel files in a downloaded ZIP that would not have their macros blocked due to the absence of the MOTW (depending on Office macro security settings), or would escape the inspection by Smart App Control.

Micropatch Availability

Since this is a "0day" vulnerability with no official vendor fix available, we are providing our micropatches for free until such fix becomes available.

Micropatches were written for: 

  1. Windows 11 v21H2
  2. Windows 10 v21H2
  3. Windows 10 v21H1
  4. Windows 10 v20H2
  5. Windows 10 v2004
  6. Windows 10 v1909
  7. Windows 10 v1903
  8. Windows 10 v1809
  9. Windows 10 v1803
  10. Windows 7 with or without ESU
  11. Windows Server 2022
  12. Windows Server 2019 
  13. Windows Server 2016
  14. Windows Server 2012
  15. Windows Server 2012 R2
  16. Windows Server 2008 R2 with or without ESU

These micropatches have already been distributed to all online 0patch Agents. If you're new to 0patch, create a free account in 0patch Central, then install and register 0patch Agent from Everything else will happen automatically. No computer reboot will be needed.

To learn more about 0patch, please visit our Help Center

Two More Years of Critical Security Patches for Windows 7 and Windows Server 2008 R2

12 October 2022 at 11:50

Extended Security Updates about to be terminated? Don't worry, we have your back.

by Mitja Kolsek, the 0patch Team



Is your organization still using Windows 7 or Windows Server 2008 R2? We understand: these are good stable Windows versions that just work, do not force you to perform unneeded upgrades that change your user interface, don't distract users with ads and news they never wanted to see, don't send tons of telemetry data to Microsoft and most of all, reliably support your work processes.

Perhaps you've kept using these Windows versions without any security updates when free updates were terminated in January 2020 (narrator: "That's a bit risky."). Or you may have purchased Extended Security Updates (ESU) to keep receiving official security fixes from Microsoft (narrator: "That's a bit expensive."). Or, you may have been using 0patch to keep running Windows 7 and Server 2008 R2 securely by receiving our security micropatches for the most-likely-to-be-exploited critical vulnerabilities.

If you chose the Extended Security Updates path, you have a big decision ahead of you, as these updates are about to be terminated soon: Windows 7 and Windows Server 2008 R2 (on-prem) will receive their last ESU update in January 2023.

After that, no new security patches for these two Windows versions will exist.

... is what could be said if it weren't for 0patch.

We have decided to keep providing security patches for Windows 7 and Windows Server 2008 R2 for critical vulnerabilities that are likely to get exploited, and will be happy to keep you secured for a fraction of what you had paid for ESU so far. And you won't have to restart your computer even once, because our patches are applied directly in the memory of running processes instead of changing your executable files!

If you're using ESU and want to continue with 0patch, just keep applying all remaining ESU updates, including the last one in January 2023. Then install 0patch Agent on all your Windows 7 and Server 2008 R2 computers, and register them to your 0patch account holding a suitable amount of licenses. 

That's it. No really, that's it.

I mean, that's it if you want to "set and forget" 0patch, which many of our users actually do. Otherwise you can use 0patch Central to decide which of our micropatches shall be applied on which computers by organizing computers in groups and setting group-specific patching policies. (Enterprise licenses are needed for that.)

Of course it would be a good idea to test 0patch in your environment first instead of waiting until January. We'll be happy to set you up with a trial so you can see how 0patch works and how it co-exists with other components in your environment. Just email [email protected] and you'll be quickly on your way.

Frequently Asked Questions

Q: How long do you plan to provide critical security patches for Windows Server 2008 R2 and Windows 7 after January 2023?

A: For at least two more years - until January 2025. Depending on the demand, we'll consider a further extension.

Q: How many vulnerabilities have you patched for Windows Server 2008 R2 and Windows 7 since January 2020?

A: Since January 2020 when we "security-adopted" Windows 7 and Windows Server 2008 R2, we have issued patches for 52 critical security issues on these systems that were at high risk of being exploited. Many of them were later actually confirmed to be exploited in the wild and even more of them became part of various exploit kits that even a low-budget attacker could easily use against unpatched systems.

Q: We haven't been using Extended Security Updates and have had no security patches since January 2020. Can we still use 0patch to get up to speed with critical security patches?

A: Of course. Just make sure you have January 2020 updates installed on your Windows 7 and Server 2008 R2 computers and start using 0patch; it will apply all micropatches that other users without Extended Security Updates have been receiving since 2020.

Q: We have been using Extended Security Updates but only for one or two years. Can we still use 0patch to get up to speed with critical security patches?

A: Absolutely. All micropatches for Windows 7 and Server 2008 R2 we have ever issued were ported written for fully updated computers (1) without ESU, (2) with only the first year of ESU, and (3) with only the second year of ESU. We'll keep doing this, and will - starting with February 2023 - also port new patches to fully updated computers (4) with the the third year of ESU, so anyone can join in and get all our patches.

Q: Are your micropatches actual code patches or "virtual patches"?

A: Our patches are actual code patches; we add a couple of CPU instructions to the original (vulnerable) executable code to remove the vulnerability. Such patches cannot be bypassed, which literally any other exploit protection mechanisms - including virtual patches - can be.

Q: What is the main difference between 0patch patches and original vendor patches?

A: These are the main differences:

  1. Our patches are applied in memory only and don't modify executable files.
  2. Due to the above, applying or un-applying our patches does not require you to even relaunch patched applications, much less restart the entire computer.
  3. Our patches are really tiny, usually just a couple of CPU instructions; by modifying only the tiniest possible fraction of original code we also minimize the possibility of introducing new flaws in the code.
  4. We don't provide patches for all security issues, only for those that are sufficiently severe and likely to be exploited.

Q: Are you also going to security-adopt Windows Server 2012 when it reaches end of support next year?

A: Yes we are.

Q: We'd like to see a demo.

A: Send an email to [email protected] and provide your company name and time zone so we can suggest a couple of dates for the demo. 

Q: We'd like to set up a trial.

A: Create a free account in 0patch Central, then let us know at [email protected] which email you used for that so we can issue a couple of trial licenses to your account.

Q: We have more questions about 0patch

A: Our Help Center has a lot of answers but if you can't find yours there, feel free to contact us at [email protected].

Micropatches for Windows IKE Extension Remote Code Execution (CVE-2022-34721)

5 October 2022 at 12:45

by Mitja Kolsek, the 0patch Team

September 2022 Windows Updates brought a fix for a remote code execution vulnerability in Windows IKE Extension discovered by Yuki Chen with Cyber KunLun. Soon after that, researchers from 78ResearchLab published an analysis and POC for this vulnerability. This made it possible for us to create a patch for affected "security-adopted" Windows systems that no longer receive official fixes from Microsoft.

The vulnerability is in the code responsible for handling IKEv1 (Internet Key Exchange version 1) key exchange protocol, which is deprecated but still supported for legacy reasons. It is a memory corruption issue, with the POC causing the svchost.exe process hosting the IKEEXT service to crash by attempting to read data beyond an allocated buffer. The crash only occurs with page heap (a debugging accessory) enabled for the process, while in a typical production configuration, the vulnerability could potentially be used for arbitrary code execution (as confirmed by Microsoft's advisory).

Microsoft assigned this issue CVE-2022-34721 and fixed it by adding a check for the length of incoming data, and bypassing the processing of such data if the length is too small. Our micropatch is logically equivalent to Microsoft's:

MODULE_PATH ".\ikeext.dll"
PATCH_ID 1000009
VULN_ID 1000010

    PIT ikeext!0xaafd8,ikeext!0x2d1c0,ikeext!0x2d14f
    ; 0xaafd8 -> IkeCopyIncomingData
    ; 0x2d1c0 -> first WfpMemFree block
    ; 0x2d14f -> jump to NtohHeader in same block as patch
        mov r8d, 1Ch        ; number of characters to copy; for memcpy in IkeCopyIncomingData
        lea rcx, [rbp-30h]  ; new buffer; for memcpy in IkeCopyIncomingData        mov rdx, r14           ; buffer to copy from; for memcpy in IkeCopyIncomingData
        call PIT_0xaafd8    ; call IkeCopyIncomingData
        mov rbx, rax        ; save return from IkeCopyIncomingData
        test rax, rax       ; check if return from IkeCopyIncomingData is non-zero
        jnz PIT_0x2d1c0     ; jump to WfpMemFree block if non-zero
        lea rcx, [rbp-30h]  ; buffer with copied data
        jmp PIT_0x2d14f     ; jmp to NtohHeader in same block as patch


This video demonstrates the effect of our micropatch. With 0patch disabled, launching the POC against a vulnerable computer causes a svchost.exe process to crash due to memory access violation. With 0patch enabled, the vulnerability is no longer there, the malformed IKEv1 packet is blocked, and the service doesn't crash.


The micropatch was written for the following Versions of Windows with all available Windows Updates installed:

  1. Windows 10 v2004
  2. Windows 10 v1909
  3. Windows 10 v1903
  4. Windows 10 v1809
  5. Windows 10 v1803 
  6. Windows 7 without ESU, with year 1 of ESU and with year 2 of ESU
  7. Windows Server 2008 R2 without ESU, with year 1 of ESU and with year 2 of ESU
This micropatch has already been distributed to all online 0patch Agents with a PRO or Enterprise license. To obtain the micropatch and have it applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch.

To learn more about 0patch, please visit our Help Center. For a trial or demo please contact [email protected].

We'd like to thank Yuki Chen for finding this issue, and 78ResearchLab researchers for publishing their analysis and providing a proof-of-concept that allowed us to reproduce the vulnerability and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Decrypted: MafiaWare666 Ransomware

4 October 2022 at 11:36

Avast releases a MafiaWare666 ransomware decryption tool. MafiaWare666 is also known as JCrypt, RIP Lmao, BrutusptCrypt or Hades.

Skip to how to use the MafiaWare666 ransomware decryptor.

MafiaWare666’s Behavior

MafiaWare666 is a ransomware strain written in C# which doesn’t contain any obfuscation or anti-analysis techniques. It encrypts files using the AES encryption. We discovered a vulnerability in the encryption schema that allows some of the variants to be decrypted without paying the ransom. New or previously unknown samples may encrypt files differently, so they may not be decryptable without further analysis.

The ransomware searches special folder locations (Desktop, Music, Videos, Pictures and Documents) and encrypts files with the following extensions:

3fr 7z accdb ai apk arch00 arw asp aspx asset avi bar bat bay bc6 bc7 big bik bkf bkp blob bsa c cas cdr cer cfr cpp cr2 crt crw cs css csv csv d3dbsp das dazip db0 dba dbf dcr der desc divx dmp dng doc doc docm docx docx dwg dxg epk eps erf esm ff flv forge fos fpk fsh gdb gho h hkdb hkx hplg hpp html hvpl ibank icxs indd index itdb itl itm iwd iwi jpe jpeg jpg js kdb kdc kf layout lbf litemod lrf ltx lvl m2 m3u m4a map mcmeta mdb mdb mdbackup mddata mdf mef menu mkv mlx mov mp3 mp4 mpeg mpqge mrwref ncf nrw ntl odb odc odm odp ods odt odt ogg orf p12 p7b p7c pak pdd pdf pef pem pfx php pk7 pkpass png ppt ppt pptm pptx pptx psd psk pst ptx py qdf qic r3d raf rar raw rb re4 rgss3a rim rofl rtf rw2 rwl sav sb sid sidd sidn sie sis slm sln snx sql sql sr2 srf srw sum svg syncdb t12 t13 tax tor txt upk vb vcf vdf vfs0 vpk vpp_pc vtf w3x wallet wav wb2 wma wmo wmv wotreplay wpd wps x3f xlk xls xls xlsb xlsm xlsx xlsx xml xxx zip zip ztmp

Encrypted files are given a new extension, which varies among the samples.

  • .MafiaWare666
  • .jcrypt
  • .brutusptCrypt
  • .bmcrypt
  • .cyberone
  • .l33ch

The ransomware displays a window with instructions explaining how to pay the ransom, once it completes the encryption process. The instructions tell victims to contact the attacker and pay them in Bitcoin. The ransom price is relatively low, between $50 – $300, although some of the older samples with different names demand much more, up to one Bitcoin, which is around $20,000 at the time of publishing.

Here are some examples of MafiaWare666 ransom notes:

How to use the Avast MafiaWare666 ransomware decryption tool  to decrypt files encrypted by the  ransomware

Follow these steps to decrypt your files:

1) Download the free decryptor

2) Run the executable file. It starts as a wizard, leading you through the configuration of the decryption process.

3) On the initial page, you can read the license information if you want, but you really only need to click “Next”

4) On the next page, select the list of locations you want to be searched and decrypted. By default, it contains a list of all local drives:

5) On the third page, you need to provide a file in its original form and encrypted by the MafiaWare666 ransomware. Enter both names of the files. If you have an encryption password created by a previous run of the decryptor, you can select “I know the password for decrypting files” option:

6) The next page is where the password cracking process takes place. Click “Start” when you are ready to start the process. The password cracking process uses all known MafiaWare666 passwords to determine the right one.

7) Once the password is found, you can proceed to decrypt all the encrypted files on your PC by clicking “Next”.

8) On the final page, you can opt-in to backup your encrypted files. These backups may help if anything goes wrong during the decryption process. This option is on by default, which we recommend. After clicking “Decrypt” the decryption process begins. Let the decryptor work and wait until it finishes decrypting all of your files.

Indicators of Compromise (IoCs):

IoCs are available at











The post Decrypted: MafiaWare666 Ransomware appeared first on Avast Threat Labs.

Micropatches for Windows Kerberos Elevation of Privilege (CVE-2022-35756)

30 September 2022 at 11:14


by Mitja Kolsek, the 0patch Team

August 2022 Windows Updates brought a fix for a local privilege escalation in Windows Kerberos, discovered by Nick Landers (@monoxgas) of NetSPI. Nick and James Forshaw (@tiraniddo) presented this vulnerability at the BlackHat USA 2022 conference and subsequently published proof-of-concept scripts. This made it possible for us to create a patch for affected "security-adopted" Windows systems that no longer receive official fixes from Microsoft.

The vulnerability allows an attacker to bypass an integrity check for a security buffer of a PAC structure sent inside attacker's AP-REQ request. The flawed integrity check improperly inspects the security buffer type by comparing it to constant SECBUFFER_TOKEN while ignoring that its value can also include two bit flags in the upper byte. Nick's and James' proof-of-concept adds one such flag to the value, bypassing the integrity check, and can therefore arbitrarily modify the PAC structure - for instance, to claim the requestor is not the actual low-privileged user but a local administrator. According to Microsoft's advisory, "a domain user could use this vulnerability to elevate privileges to a domain admin."

Microsoft assigned this issue CVE-2022-35756 and fixed it by removing the execution branch that led to the bypass. Our micropatch is logically equivalent to Microsoft's:

MODULE_PATH "..\AffectedModules\kerberos.dll_6.1.7601.24545_Win7_32-bit_NoESU\kerberos.dll"
VULN_ID 7492

        push eax                  ;save the original rax value
        mov eax, [ebp-0x14]       ;get the flag location pointer +0x8 for the previous push
        bt dword[eax+0x28], 0x19  ;test the 0x19-th bit
        pop eax                   ;restore original rax value
        jb SKIP                   ;if bit is set, keep the old rcx value
        mov eax, 1                ;if bit is not set, mov 1 into rcx
        ; the value of rax here will be stored to rcx and serve as an argument
        ; in a call to KerbCreateTokenFromTicketEx      



The micropatch was written for the following Versions of Windows with all available Windows Updates installed:

  1. Windows 10 v2004
  2. Windows 10 v1909
  3. Windows 10 v1903
  4. Windows 10 v1809
  5. Windows 10 v1803
  6. Windows 7 without ESU, with year 1 of ESU and with year 2 of ESU
  7. Windows Server 2008 R2 without ESU, with year 1 of ESU and with year 2 of ESU
This micropatch has already been distributed to all online 0patch Agents with a PRO or Enterprise license. To obtain the micropatch and have it applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch.

To learn more about 0patch, please visit our Help Center. For a trial or demo please contact [email protected].

We'd like to thank Nick Landers (@monoxgas) and James Forshaw (@tiraniddo) for publishing their analysis with a proof-of-concept that allowed us to reproduce the vulnerability and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Raspberry Robin’s Roshtyak: A Little Lesson in Trickery

22 September 2022 at 10:48

There are various tricks malware authors use to make malware analysts’ jobs more difficult. These tricks include obfuscation techniques to complicate reverse engineering, anti-sandbox techniques to evade sandboxes, packing to bypass static detection, and more. Countless deceptive tricks used by various malware strains in-the-wild have been documented over the years. However, few of these tricks are implemented in a typical piece of malware, despite the many available tricks. 

The subject of this blog post, a backdoor we dubbed Roshtyak, is not your typical piece of malware. Roshtyak is full of tricks. Some are well-known, and some we have never seen before. From a technical perspective, the lengths Roshtyak takes to protect itself are extremely interesting. Roshtyak belongs to one of the best-protected malware strains we have ever seen. We hope by publishing our research and analysis of the malware and its protection tricks we will help fellow researchers recognize and respond to similar tricks, and harden their analysis environments, making them more resistant to the evasion techniques described.

Roshtyak is the DLL backdoor used by Raspberry Robin, a worm spreading through infected removable drives. Raspberry Robin is extremely prevalent. We protected over 550K of our users from the worm this year. Due to its high prevalence, it should be no surprise that we aren’t the only ones taking note of Raspberry Robin. 

Red Canary’s researchers published the first analysis of Raspberry Robin in May 2022. In June, Symantec published a report describing a mining/clipboard hijacking operation, which reportedly made the cybercriminals at least $1.7 million. Symantec did not link the malicious operation to Raspberry Robin. Nevertheless, we assess with high confidence that what they analyzed was Raspberry Robin. This assessment is based on C&C overlaps, strong malware similarity, and coinfections observed in our telemetry. Cybereason, Microsoft, and Cisco published further reports in July/August 2022. Microsoft reported that Raspberry Robin infections led to DEV-0243 (a.k.a Evil Corp) pre-ransomware behavior. We could not confirm this connection using our telemetry. Still, we find it reasonable to believe that the miner payload is not the only way Raspberry Robin infections are being monetized. Other recent reports also hint at a possible connection between Raspberry Robin and Evil Corp.

A map showing the number of users Avast protected from Raspberry Robin

There are many unknowns about Raspberry Robin, despite so many published reports. What are the ultimate objectives behind the malware? Who is responsible for Raspberry Robin? How did it become so prevalent? Unfortunately, we do not have answers to all these questions. However, we can answer an important question we saw asked multiple times: What functionality is hidden inside the heavily obfuscated DLL (or Roshtyak as we call it)? To answer this question, we fully reverse engineered a Roshtyak sample, and present our analysis results in this blog post.


Roshtyak is packed in as many as 14 protective layers, each heavily obfuscated and serving a specific purpose. Some artifacts suggest the layers were originally PE files but were transformed into custom encrypted structures that only the previous layers know how to decrypt and load. Numerous anti-debugger, anti-sandbox, anti-VM, and anti-emulator checks are sprinkled throughout the layers. If one of these checks successfully detects an analysis environment, one of four actions are taken. 

  1. The malware calls TerminateProcess on itself to avoid exhibiting any further malicious behavior and to keep the subsequent layers encrypted.
  2. Roshtyak crashes on purpose. This has the same effect as terminating itself, but it might not be immediately clear if the crash was intentional or because of a bug thanks to Roshtyak’s obfuscated nature.
  3. The malware enters an infinite loop on purpose. Since the loop itself is located in obfuscated code and spans thousands of instructions, it might be hard to determine if the loop is doing something useful or not.
  4. The most interesting case is when the malware reacts to a successful check by unpacking and loading a fake payload. This happens in the eighth layer, which is loaded with dozens of anti-analysis checks. The result of each of these checks is used to modify the value of a global variable. There are two payloads encrypted in the data section of the eighth layer: the real ninth layer and a fake payload. The real ninth layer will get decrypted only if the global variable matches the expected value after all the checks have been performed. If at least one check succeeded in detecting an analysis environment, the global variable’s value will differ from the expected value, causing Roshtyak to unpack and execute the fake payload instead. 
Roshtyak’s obfuscation causes even relatively simple functions to grow into large proportions. This necessitates some custom deobfuscation tooling if one wants to reverse engineer it within a reasonable timeframe.

The fake payload is a BroAssist (a.k.a BrowserAssistant) adware sample. We believe this fake payload was intended to mislead malware analysts into thinking the sample is less interesting than it really is. When a reverse engineer focuses on quickly unpacking a sample, it might look like the whole sample is “just” an obfuscated piece of adware (and a very old one at that), which could cause the analyst to lose interest in digging deeper. And indeed, it turns out that these fake payload shenanigans can be very effective. As can be seen on the screenshot below, it fooled at least one researcher, who misattributed the Raspberry Robin worm, because of the fake BrowserAssistant payload.

A security researcher misattributing Raspberry Robin because of the fake payload. This is not to pick on anyone, we just want to show how easy it is to make a mistake like this given Roshtyak’s trickery and complexity.

The Bag of Tricks

For the sake of keeping this blog post (sort of) short and to the point, let’s get straight into detailing some of the more interesting evasion techniques employed by Roshtyak.

Segment registers

Early in the execution, Roshtyak prefers to use checks that do not require calling any imported functions. If one of these checks is successful, the sample can quietly exit without generating any suspicious API calls. Below is an example where Roshtyak checks the behavior of the gs segment register. The check is designed to be stealthy and the surrounding garbage instructions make it easy to overlook.

A stealthy detection of single-stepping. Only the underscored instructions are useful.

The first idea behind this check is to detect single-stepping. Before the above snippet, the value of cx was initialized to 2. After the pop ecx instruction, Roshtyak checks if cx is still equal to 2. This would be the expected behavior because this value should propagate through the stack and the gs register under normal circumstances. However, a single step event would reset the value of the gs selector, which would result in a different value getting popped into ecx at the end.

But there is more to this check. As a side effect of the two push/pop pairs above, the value of gs is temporarily changed to 2. After this check, Roshtyak enters a loop, counting the number of iterations until the value of gs is no longer 2. The gs selector is also reset after a thread context switch, so the loop essentially counts the number of iterations until a context switch happens. Roshtyak repeats this procedure multiple times, averages out the result, and checks that it belongs to a sensible range for a bare metal execution environment. If the sample runs under a hypervisor or in an emulator, the average number of iterations might fall outside of this range, which allows Roshtyak to detect undesirable execution environments.

Roshtyak also checks that the value of the cs segment register is either 0x1b or 0x23. Here, 0x1b is the expected value when running on native x86 Windows, while 0x23 is the expected value under WoW64.

APC injection through a random ntdll gadget

Roshtyak performs some of its functionality from separate processes. For example, when it communicates with its C&C server, it spawns a new innocent-looking process like regsvr32.exe. Using shared sections, it injects its comms module into the address space of the new process. The injected module is executed via APC injection, using NtQueueApcThreadEx.

Interestingly, the ApcRoutine argument (which marks the target routine to be scheduled for execution) does not point to the entry point of the injected module. Instead, it points to a seemingly random address inside ntdll. Taking a closer look, we see this address was not chosen randomly but that Roshtyak scanned the code section of ntdll for pop r32; ret gadgets (excluding pop esp, because pivoting the stack would be undesirable) and picked one at random to use as the ApcRoutine

A random pop r32; ret gadget used as the entry point for APC injection

Looking at the calling convention for the ApcRoutine reveals what’s going on. The pop instruction makes the stack pointer point to the SystemArgument1 parameter of NtQueueApcThreadEx and so the ret instruction effectively jumps to wherever SystemArgument1 is pointing. This means that by abusing this gadget, Roshtyak can treat SystemArgument1 as the entry point for the purpose of APC injection. This obfuscates the control flow and makes the NtQueueApcThreadEx call look more legitimate. If someone hooks this function and inspects the ApcRoutine argument, the fact that it is pointing into the ntdll code section might be enough to convince them that the call is not malicious.

Checking read/write performance on write-combined memory

In this next check, Roshtyak allocates a large memory buffer with the PAGE_WRITECOMBINE flag. This flag is supposed to modify cache behavior to optimize sequential write performance (at the expense of read performance and possibly memory ordering). Roshtyak uses this to detect if it’s running on a physical machine. It conducts an experiment where it first writes to the allocated buffer and then reads from the allocated buffer, all while measuring the read/write performance using a separate thread as a counter. This experiment is repeated 32 times and the check is passed only if write performance was at least six times higher than read performance most of the times. If the check fails, Roshtyak intentionally selects a wrong RC4 key, which results in failing to properly decrypt the next layer.

Hiding shellcode from plain sight

The injected shellcode is interestingly hidden, too. When Roshtyak prepares for code injection, it first creates a large section and maps it into the current process as PAGE_READWRITE. Then, it fills the section with random data and places the shellcode at a random offset within the random data. Since the shellcode is just a relatively small loader followed by random-looking packed data, the whole section looks like random data. 

A histogram of the bytes inside the shared section. Note that it looks almost random, the most suspicious sign is the slight overrepresentation of null bytes.

The section is then unmapped from the current process and mapped into the target process, where it is executed using the above-described APC injection technique. The random data was added in an attempt to conceal the existence of the shellcode. Judging only from the memory dump of the target process, it might look like the section is full of random data and does not contain any valid executable code. Even if one suspects actual valid code somewhere in the middle of the section, it will not be easy to find its exact location. 

The start of the shellcode within the shared section. It might be hard to pinpoint the exact start address because it unconventionally starts on an odd bt instruction.


Roshtyak makes a point of cleaning up after itself. Whenever a certain string or piece of memory is no longer needed, Roshtyak wipes and/or frees it in an attempt to destroy as much evidence as possible. The same holds for Roshtyak’s layers. Whenever one layer finishes its job, it frees itself before passing execution onto the next layer. However, the layer cannot just simply free itself directly. The whole process would crash if it called VirtualFree on the region of memory it’s currently executing from.

Roshtyak, therefore, frees the layer through a ROP chain executed during layer transitions to avoid this problem. When a layer is about to exit, it constructs a ROP chain on the stack and returns into it. An example of such a ROP chain can be seen below. This chain starts by returning into VirtualFree and UnmapViewOfFile to release the previous layer’s memory. Then, it returns into the next layer. The return address from the next layer is set to RtlExitUserThread, to safeguard execution.

A simple ROP chain consisting of VirtualFree -> UnmapViewOfFile -> next layer -> RtlExitUserThread

MulDiv bug

MulDiv is a function exported by kernel32.dll, which takes three signed 32-bit integers as arguments. It multiplies the first two arguments, divides the multiplication result by the third argument, and returns the final result rounded to the nearest integer. While this might seem like a simple enough function, there’s an ancient sign extension bug in Microsoft’s implementation. This bug is sort of considered a feature now and might never get fixed.

Roshtyak is aware of the bug and tests for its presence by calling MulDiv(1, 0x80000000, 0x80000000). On real Windows machines, this triggers the bug and MulDiv erroneously returns 2, even though the correct return value should be 1, because (1 * -2147483648) / -2147483648 = 1. This allows Roshtyak to detect emulators that do not replicate the bug. For example, this successfully detects Wine, which, funnily enough, contains a different bug, which makes the above call return 0.

Tampering with return addresses stored on the stack

There are also tricks designed to obfuscate function calls. As shown in the previous section, Roshtyak likes to call functions using the ret instruction. This next trick is similar in that it also manipulates the stack so a ret instruction can be used to jump to the desired address. 

To achieve this, Roshtyak scans the current thread’s stack for pointers into the code section of one of the previous layers (unlike the other layers, this one was not freed using the ROP chain technique). It replaces all these pointers with the address it wants to call. Then it lets the code return multiple times until a ret instruction encounters one of the hijacked pointers, redirecting the execution to the desired address.

Exception-based checks

Additionally, Roshtyak contains checks that set up a custom vectored exception handler and intentionally trigger various exceptions to ensure they all get handled as expected.

Roshtyak sets up a vectored exception handler using RtlAddVectoredExceptionHandler. This handler contains custom handlers for selected exception codes. A top-level exception handler is also registered using SetUnhandledExceptionFilter. This handler should not be called in the targeted execution environments (none of the intentionally triggered exceptions should fall through the vectored exception handler). So this top-level handler just contains a single call to TerminateProcess. Interestingly, Roshtyak also uses ZwSetInformationProcess to set SEM_FAILCRITICALERRORS using the ProcessDefaultHardErrorMode class. This ensures that even if the exception somehow is passed all the way to the default exception handler, Windows would not show the standard error message box, which could alert the victim that something suspicious is going on.

When everything is set up, Roshtyak begins generating exceptions. The first exception is generated by a popf instruction, directly followed by a cpuid instruction (shown below). The value popped by the popf instruction was crafted to set the trap flag, which should, in turn, raise a single-step exception. On a physical machine, the exception would trigger right after the cpuid instruction. Then, the custom vectored exception handler would take over and move the instruction pointer away from the C7 B2 opcodes, which mark an invalid instruction. However, under many hypervisors, the single-step exception would not be raised. This is because the cpuid instruction forces a VM exit, which might delay the effect of the trap flag. If that is the case, the processor will raise an illegal instruction exception when trying to execute the invalid opcodes. If the vectored exception handler encounters such an exception, it knows that it is running under a hypervisor. A variation of this technique is described thoroughly in a blog post by Palo Alto Networks. Please refer to it for more details. 

The exception-based check using popf and cpuid to detect hypervisors

Another exception is generated using the two-byte int 3 instruction (CD 03). This instruction is followed by garbage opcodes. The int 3 here raises a breakpoint exception, which is handled by the vectored exception handler. The vectored exception handler doesn’t really do anything to handle the exception, which is interesting. This is because by default, when Windows handles the two-byte int 3 instruction, it will leave the instruction pointer in between the two instruction bytes, pointing to the 03 byte. When disassembled from this 03 byte, the garbage opcodes suddenly start making sense. We believe this is a check against some overeager debuggers, which could “fix” the instruction pointer to point after the 03 byte.

Moreover, the vectored exception handler checks the thread’s CONTEXT and makes sure that registers Dr0 through Dr3 are empty. If they are not, the process is being debugged using hardware breakpoints. While this check is relatively common in malware, the CONTEXT is usually obtained using a call to a function like GetThreadContext. Here, the malware authors took advantage of CONTEXT being passed as an argument to the exception handler, so they did not need to call any additional API functions.

Large executable mappings

This next check is interesting mostly because we are not sure what it’s really supposed to check (in other words, we’d be happy to hear your theories!). It starts with Roshtyak creating a large PAGE_EXECUTE_READWRITE mapping of size 0x386F000. Then it maps this mapping nine times into its own address space. After this, it memsets the mapping to 0x42 (opcode for inc edx), except for the last six bytes, which are filled with four inc ecx instructions and jmp dword ptr [ecx] (see below). Next, it puts the nine base addresses of the mapped views into an array, followed by an address of a single ret instruction. Finally, it points ecx into this array and calls the first mapped view, which results in all the mapped views being called sequentially until the final ret instruction. After the return, Roshtyak validates that edx got incremented exactly 0x1FBE6FCA times (9 * (0x386F000 - 6)).

The end of the large mapped section. The jmp dword ptr [ecx] instruction is supposed to jump to the start of the next mapped view.

Our best guess is that this is yet another anti-emulator check. For example, in some emulators, mapped sections might not be fully implemented, so the instructions written into one instance of the mapped view might not propagate to the other eight instances. Another theory is the check could be done to request large amounts of memory that emulators might fail to provide. After all, the combined size of all the views is almost half of the standard 32-bit user mode address space.

Detecting process suspension

This trick abuses an undocumented thread creation flag in NtCreateThreadEx to detect when Roshtyak’s main process gets externally suspended (which could mean that a debugger got attached). This flag essentially allows a thread to keep running even when PsSuspendProcess gets called. This is coupled with another trick abusing the fact that the thread suspend counter is a signed 8-bit value, which means that it maxes out at 127. Roshtyak spawns two threads, one of which keeps suspending the other one until the suspend counter limit is reached. After this, the first thread keeps periodically suspending the other one and checking if the call to NtSuspendThread keeps failing with STATUS_SUSPEND_COUNT_EXCEEDED. If it does not, the thread must have been externally suspended and resumed (which would leave the suspend counter at 126, so the next call to NtSuspendThread would succeed). Not getting this error code would be suspicious enough for Roshtyak to quit using TerminateProcess. This entire technique is described in more detail in a blog post by Secret Club. We believe that’s where the authors of Roshtyak got this trick from. It’s also worth mentioning Roshtyak uses this technique only on Windows builds 18323 (19H1) and later because the undocumented thread creation flag was not implemented on prior builds.

Indirect registry writes

Roshtyak performs many suspicious registry operations, for example, setting up the RunOnce key for persistence. Since modifications to such keys are likely to be monitored, Roshtyak attempts to circumvent the monitoring. It first generates a random registry key name and temporarily renames the RunOnce key to the random name using ZwRenameKey. Once renamed, Roshtyak adds a new persistence entry to the temporary key before finally renaming it back to RunOnce. This method of writing to the registry can be easily detected, but it might bypass some simple hooking-based monitoring methods.

Similarly, there are multiple methods Roshtyak uses to delete files. Aside from the apparent call to NtDeleteFile, Roshtyak is able to effectively delete a file by setting FileDispositionInformation or FileRenameInformation in a call to ZwSetInformationFile. However, unlike the registry modification method, this doesn’t seem to be implemented in order to evade detection. Instead, Roshtyak will try these alternative methods if the initial call to NtDelete file fails. 

Checking VBAWarnings

The VBAWarnings registry value controls how Microsoft Office behaves when a user opens a document containing embedded VBA macros. If this value is 1 (meaning “Enable all macros”), macros are executed by default, even without the need for any user interaction. This is a common setting for sandboxes, which are designed to detonate maldocs automatically. On the other hand, this setting is uncommon for regular users, who generally don’t go around changing random settings to make themselves more vulnerable (at least most of them don’t). Roshtyak therefore uses this check to differentiate between sandboxes and regular users and refuses to run further if the value of VBAWarnings is 1. Interestingly, this means that users, who for whatever reason have lowered their security this way, are immune to Roshtyak.

Command line wiping

Roshtyak’s core is executed with very suspicious command lines, such as RUNDLL32.EXE SHELL32.DLL,ShellExec_RunDLL REGSVR32.EXE -U /s "C:\Users\<REDACTED>\AppData\Local\Temp\dpcw.etl.". These command lines don’t look particularly legitimate, so Roshtyak attempts to hide them during execution. It does this by wiping command line information collected from various sources. It starts by calling GetCommandLineA and GetCommandLineW and wiping both of the returned strings. Then it attempts to wipe the string pointed to by PEB->ProcessParameters->CommandLine (even if this points to a string that has already been wiped). Since Roshtyak is often running under WoW64, it also calls NtWow64QueryInformationProcess64 to obtain a pointer to PEB64 to wipe ProcessParameters->CommandLine obtained by traversing this “second” PEB. While the wiping of the command lines was probably meant to make Roshtyak look more legitimate, the complete absence of any command line is also highly unusual. This was noticed by the Red Canary researchers in their blog post, where they proposed a detection method based on these suspiciously empty command lines.

Roshtyak’s core process, as shown by Process Explorer. Note the suspiciously empty command line.

Additional tricks

Aside from the techniques described so far, Roshtyak uses many less sophisticated tricks that are commonly found in other malware as well. These include:

  • Hiding threads using ThreadHideFromDebugger (and verifying that the threads really got hidden using NtQueryInformationThread)
  • Patching DbgBreakPoint in ntdll
  • Detecting user inactivity using GetLastInputInfo
  • Checking fields from PEB (BeingDebugged, NtGlobalFlag)
  • Checking fields from KUSER_SHARED_DATA (KdDebuggerEnabled, ActiveProcessorCount, NumberOfPhysicalPages)
  • Checking the names of all running processes (some are compared by hash, some by patterns, and some by character distribution)
  • Hashing the names of all loaded modules and checking them against a hardcoded blacklist
  • Verifying the main process name is not too long and doesn’t match known names used in sandboxes
  • Using the cpuid instruction to check hypervisor information and the processor brand
  • Using poorly documented COM interfaces
  • Checking the username and computername against a hardcoded blacklist
  • Checking for the presence of known sandbox decoy files
  • Checking MAC addresses of own adapters against a hardcoded blacklist
  • Checking MAC addresses from the ARP table (using GetBestRoute to populate it and GetIpNetTable to inspect it)
  • Calling ZwQueryInformationProcess with ProcessDebugObjectHandle, ProcessDebugFlags, and ProcessDebugPort
  • Checking DeviceId of display devices (using EnumDisplayDevices)
  • Checking ProductId of \\.\PhysicalDrive0 (using IOCTL_STORAGE_QUERY_PROPERTY)
  • Checking for virtual hard disks (using NtQuerySystemInformation with SystemVhdBootInformation)
  • Checking the raw SMBIOS firmware table (using NtQuerySystemInformation with SystemFirmwareTableInformation)
  • Setting up Defender exclusions (both for paths and processes)
  • Removing IFEO registry keys related to process names used by the malware


We’ve shown many anti-analysis tricks that are designed to prevent Roshtyak from detonating in undesirable execution environments. These tricks alone would be easy to patch or bypass. What makes analyzing Roshtyak especially lethal is the combination of all these tricks with heavy obfuscation and multiple layers of packing. This makes it very difficult to study the anti-analysis tricks statically and figure out how to pass all the checks in order to get Roshtyak to unpack itself. Furthermore, even the main payload received the same obfuscation, which means that statically analyzing Roshtyak’s core functionality also requires a great deal of deobfuscation. 

In the rest of this section, we’ll go through the main obfuscation techniques used by Roshtyak.

A random code snippet from Roshtyak. As can be seen, the obfuscation makes the raw output of the Hex-Rays decompiler practically incomprehensible.

Control flow flattening

Control flow flattening is one of the most noticeable obfuscation techniques employed by Roshtyak. It is implemented in an unusual way, giving the control flow graphs of Roshtyak’s functions a unique look (see below). The goal of control flow flattening is to obscure control flow relations between individual code blocks. 

Control flow is directed by a 32-bit control variable, which tracks the execution state, identifying the code block to be executed. This control variable is initialized at the start of each function to refer to the starting code block (which is frequently a nop block). The control variable is then modified at the end of each code block to identify the next code block that should be executed. The modification is performed using some arithmetic instructions, such as add, sub, or xor.

There is a dispatcher using the control variable to route execution into the correct code block. This dispatcher is made up of if/else blocks that are circularly linked into a loop. Each dispatcher block takes the control variable and masks it using arithmetic instructions to check if it should route execution into the code block that it is guarding. What’s interesting here is there are multiple points of entry from the code blocks into the dispatcher loop, giving the control flow graphs the jagged “sawblade” look in IDA. 

Branching is performed using a special code block containing an imul instruction. It relies on the previous block to compute a branch flag. This branch flag is multiplied using the imul instruction with a random constant, and the result is added, subbed, or xored to the new control variable. This means that after the branch block, the control variable will identify one of the two possible succeeding code blocks, depending on the value that was computed for the branch flag.

Control flow graph of a function obfuscated using control flow flattening

Function activation keys

Roshtyak’s obfuscated functions expect an extra argument, which we call an activation key. This activation key is used to decrypt all local constants, strings, variables, etc. If a function is called with a wrong activation key, the decryption results in garbage plaintext, which will most likely cause Roshtyak to get stuck in an infinite loop inside the control flow dispatcher. This is because all constants used by the dispatcher (the initial value of the control variable, the masks used by the dispatcher guards, and the constants used to jump to the next code block) are encrypted with the activation key. Without the correct activation key, the dispatcher simply does not know how to dispatch.

Reverse engineering a function is practically impossible without knowing the correct activation key. All strings, buffers, and local variables/constants remain encrypted, all cross-references are lost, and worse, there is no control flow information. Only individual code blocks remain, with no way to know how they relate to each other.

Each obfuscated function has to be called from somewhere, which means the code calling the function has to supply the correct activation key. However, obtaining the activation key is not that easy. First, call targets are also encrypted with activation keys, so it’s impossible to find where a function is called from without knowing the right activation keys. Second, even the supplied activation key is encrypted with the activation key of the calling function. And that activation key got encrypted with the activation key of the next calling function. And so on, recursively, all the way until the entry point function.

This brings us to how to deobfuscate the mess. The activation key of the entry point function must be there in plaintext. Using this activation key, it is possible to decrypt the call targets and activation keys of functions that are called directly from this entry point function. Applying this method recursively allows us to reconstruct the full call graph along with the activation keys of all the functions. The only exceptions would be functions that were never called and were left in by the compiler. These functions will probably remain a mystery, but since the sample does not use them, they are not that important from a malware analyst’s point of view.

Variable masking

Some variables are not stored in plaintext form but are masked using one or more arithmetic instructions. This means that if Roshtyak is not actively using a variable, it keeps the variable’s value in an obfuscated form. Whenever Roshtyak needs to use the variable, it has to first unmask it before it can use it. Conversely, after Roshtyak uses the variable, it converts it back into the masked form. This masking-based obfuscation method slightly complicates tracking variables during debugging and makes it harder to search memory for a known variable value.

Loop transformations

Roshtyak is creative with some loop conditions. Instead of writing a loop like for (int i = 0; i < 1690; i++), it transforms the loop into e.g. for (int32_t i = 0x06AB91EE; i != 0x70826068; i = i * -0x509FFFF + 0xEC891BB1). While both loops will execute exactly 1690 times, the second one is much harder to read. At first glance, it is not clear how many iterations the second loop executes (and if it even terminates). Tracking the number of loop iterations during debugging is also much harder in the second case.


As mentioned, Roshtyak’s core is hidden behind multiple layers of packing. While all the layers look like they were originally compiled into PE files, all but the strictly necessary data (entry point, sections, imports, and relocations) were stripped away. Furthermore, Roshtyak supports two custom formats for storing the stripped PE file information, and the layers take turns on what format they use. Additionally, parts of the custom formats are encrypted, sometimes using keys generated based on the results of various anti-analysis checks.

This makes it difficult to unpack Roshtyak’s layers statically into a standalone PE file. First, one would have to reverse engineer the custom formats and figure out how to decrypt the encrypted parts. Then, one would have to reconstruct the PE header, the sections, the section headers, and the import table (the relocation table doesn’t need to be reconstructed since relocations can just be turned off). While this is all perfectly doable (and can be simplified using libraries like LIEF), it might take a significant amount of time. Adding to this that the layers are sometimes interdependent, it might be easier to just analyze Roshtyak dynamically in memory.

A section header in one of the custom PE-like file formats: raw_size corresponds to SizeOfRawData, raw_size + virtual_padding_size is effectively VirtualSize. There is no VirtualAddress or PointerToRawData equivalent because the sections are loaded sequentially.

Other obfuscation techniques

In addition to the above-described techniques, Roshtyak also uses other obfuscation techniques, including:

  • Junk instruction insertion
  • Import hashing
  • Frequent memory wiping
  • Mixed boolean-arithmetic obfuscation
  • Redundant threading
  • Heavy polymorphism

Core Functionality

Now that we’ve described how Roshtyak protects itself, it might be interesting to also go over what it actually does. Roshtyak’s DLL is relatively large, over a megabyte, but its functionality is surprisingly simple once you eliminate all the obfuscation. Its main purpose is to download further payloads to execute. In addition, it does the usual evil malware stuff, namely establishing persistence, escalating privileges, lateral movement, and exfiltrating information about the victim.


Roshtyak first generates a random file name in %SystemRoot%\Temp and moves its DLL image there. The generated file name consists of two to eight random lowercase characters concatenated with a random extension chosen from a hardcoded list. The PRNG used to generate this file name is seeded with the volume serial number of C:\. The sample we analyzed hardcoded seven extensions (.log, .tmp, .loc, .dmp, .out, .ttf, and .etl). We observed other extensions being used in other samples, suggesting this list is somewhat dynamic. With a small probability, Roshtyak will also use a randomly generated extension. Once fully constructed, the full path to the Roshtyak DLL might look like e.g. C:\Windows\Temp\wcdp.etl.

After the DLL image is moved to the new filesystem path, Roshtyak stomps its Modified timestamp to the current system time. It then proceeds to set up a RunOnce(Ex) registry key to actually establish persistence. The registry entry is created using the previously described indirect registry write technique. The command inserted into the key might look as follows:

RUNDLL32.EXE SHELL32.DLL,ShellExec_RunDLL REGSVR32.EXE -U /s "C:\Windows\Temp\wcdp.etl."

There are a couple of things to note here. First, regsvr32 doesn’t care about the extensions of the DLLs it loads, allowing Roshtyak to hide under an innocent-looking extension such as .log. Second, the /s parameter puts regsvr32 into silent mode. Without it, regsvr32 would complain that it did not find an export named DllUnregisterServer. Finally, notice the trailing period character at the end of the path. This period is removed during path normalization, so it practically has no effect on the command. We are not exactly sure what the author’s original intention behind including this period character is. It looks like it could have been designed to trick some anti-malware software into not being able to connect the persistence entry with the payload on the filesystem.

By default, Roshtyak uses the HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce key for persistence. However, under some circumstances (such as when it detects that Kaspersky is running by checking for a process named avp.exe) the key HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx will be used instead. The RunOnceEx key is capable of loading a DLL, so when using this key, Roshtyak specifies shell32.dll directly, omitting the use rundll32.

A RunOnceEx persistence entry established by Roshtyak

Privilege escalation

Roshtyak uses both UAC bypasses and regular EoP exploits in an attempt to elevate its privileges. Unlike many other pieces of malware, which just blindly execute whatever UAC bypasses/exploits the authors could find, Roshtyak makes efforts to figure out if the privilege escalation method is even likely to be successful. This was probably implemented to lower the chances of detection due to the unnecessary usage of incompatible bypasses/exploits. For UAC bypasses, this involves checking the ConsentPromptBehaviorAdmin and ConsentPromptBehaviorUser registry keys. For EoP exploits, this is about checking the Windows build number and patch level.

Besides checking the ConsentPromptBehavior(Admin|User) keys, Roshtyak performs other sanity checks to ensure that it should proceed with the UAC bypass. Namely, it checks for admin privileges using CheckTokenMembership with the SID S-1-5-32-544 (DOMAIN_ALIAS_RID_ADMINS). It also inspects the value of the DbgElevationEnabled flag in KUSER_SHARED_DATA.SharedDataFlags. This is an undocumented flag that is set if UAC is enabled. Finally, there are AV checks for BitDefender (detected by the module atcuf32.dll), Kaspersky (process avp.exe), and our own Avast/AVG (module aswhook.dll). If one of these AVs is detected, Roshtyak avoids selected UAC bypass techniques, presumably the ones that might result in detection.

As for the actual UAC bypasses, there are two main methods implemented. The first is an implementation of the aptly named ucmDccwCOM method from UACMe. Interestingly when this method is executed, Roshtyak temporarily masquerades its process as explorer.exe by overwriting FullDllName and BaseDllName in the _LDR_MODULE structure corresponding to the main executable module. The payload launched by this method is a randomly named LNK file, dropped into %TEMP% using the IShellLink COM interface. This LNK file is designed to relaunch the Roshtyak DLL, through LOLBins such as advpack or register-cimprovider.

The second method is more of a UAC bypass framework than a specific bypass method, because multiple UAC bypass methods follow the same simple pattern: first registering some specific shell open command and then executing an autoelevating Windows binary (which internally triggers the shell open command). For instance, a UAC bypass might be accomplished by writing a payload command to HKCU\Software\Classes\ms-settings\shell\open\command and then executing fodhelper.exe from %windir%\system32. Basically, the same bypass can be achieved by substituting the pair ms-settings/fodhelper.exe with other pairs, such as mscfile/eventvwr.exe. Roshtyak uses the following six pairs to bypass UAC:

Class Executable
mscfile eventvwr.exe
mscfile compmgmtlauncher.exe
ms-settings fodhelper.exe
ms-settings computerdefaults.exe
Folder sdclt.exe
Launcher.SystemSettings slui.exe

Let’s now look at the kernel exploits (CVE-2020-1054 and CVE-2021-1732) Roshtyak uses to escalate privileges. As is often the case in Roshtyak, these exploits are stored encrypted and are only decrypted on demand. Interestingly, once decrypted, the exploits turn out to be regular PE files with completely valid headers (unlike the other layers in Roshtyak, which are either in shellcode form or stored in a custom stripped PE format). Moreover, the exploits lack the obfuscation given to the rest of Roshtyak, so their code is immediately decompilable, and only some basic string encryption is used. We don’t know why the attackers left these exploits so exposed, but it might be due to the difference in bitness. While Roshtyak itself is x86 code (most of the time running under WoW64), the exploits are x64 (which makes sense considering they exploit vulnerabilities in 64-bit code). It could be that the obfuscation tools used by Roshtyak’s authors were designed to work on x86 and are not portable to x64.

Snippet from Roshtyak’s exploit for CVE-2020-1054, scanning through IsMenu to find the offset to HMValidateHandle.

To execute the exploits, Roshtyak spawns (the AMD64 version of) winver.exe and gets the exploit code to run there using the KernelCallbackTable injection method. Roshtyak’s implementation of this injection method essentially matches a public PoC, with the biggest difference being the usage of slightly different API functions due to the need for cross-subsystem injection (e.g. NtWow64QueryInformationProcess64 instead of NtQueryInformationProcess or NtWow64ReadVirtualMemory64 instead of ReadProcessMemory). The code injected into winver.exe is not the exploit PE itself but rather a slightly obfuscated shellcode, designed to load the exploit PE into memory.

The kernel exploits target certain unpatched versions of Windows. Specifically, CVE-2020-1054 is only used on Windows 7 systems where the revision number is not higher than 24552. On the other hand, the exploit for CVE-2021-1732 runs on Windows 10, with the targeted build number range being from 16353 to 19042. Before exploiting CVE-2021-1732, Roshtyak also scans through installed update packages to see if a patch for the vulnerability is installed. It does this by enumerating the registry keys under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages and checking if the package for KB4601319 (or higher) is present.

Lateral movement

When it comes to lateral movement, Roshtyak simply uses the tried and tested PsExec tool. Before executing PsExec, Roshtyak ensures it makes sense to run it by checking for a SID matching the “well-knownWinAccountDomainAdminsSid group. If domain admin rights are not detected, Roshtyak skips its lateral movement phase entirely.

Roshtyak attempts to get around detection by setting Defender exclusions, as PsExec is often flagged as a hacktool (for good reasons). It sets a path exclusion for %TEMP% (where it will drop PsExec and other files used for lateral movement). Later, it sets up a process exclusion for the exact path from which PsExec will be executed. 

While we would expect PsExec to be bundled inside Roshtyak, it turns out Roshtyak downloads it on demand from https://download.sysinternals[.]com/files/ The downloaded zip archive is dropped into %TEMP% under a random name with the .zip extension. PsExec is then unzipped from this archive using the Windows Shell COM interface (IShellDispatch) into a randomly named .exe file in %TEMP%.

The payload to be executed by PsExec is a self-extracting package created by a tool called IExpress. This is an archaic installer that’s part of Windows, which is probably why it’s used, since Roshtyak can rely on it already being on the victim machine. The installer generation is configured by a text file using the Self Extraction Directive (SED) syntax. 

Roshtyak’s IExpress configuration template

Roshtyak uses a SED configuration template with three placeholders (%1, %2, and %3) that it substitutes with real values at runtime. As seen above, the configuration template was written in mixed-case, which is frequently used in Raspberry Robin in general. Once the SED configuration is prepared, it is written into a randomly named .txt file in %TEMP%. Then, iexpress is invoked to generate the payload using a command such as C:\Windows\iexpress.exe /n /q <path_to_sed_config>. The generated payload is dumped into a randomly named .exe file in %TEMP%, as configured by the TargetName directive (placeholder %1).

Once the payload is generated, Roshtyak proceeds to actually run PsExec. There are two ways Roshtyak can execute PsExec. The first one uses the command <path_to_psexec> \\* -accepteula -c -d -s <path_to_payload>. Here, the \\* wildcard instructs PsExec to run the payload on all computers in the current domain. Alternatively, Roshtyak might run the command <path_to_psexec> @<path_to_target_file> -accepteula -c -d -s <path_to_payload>. Here, the target_file is a text file containing a specific list of computers to run the payload on. Roshtyak builds this list by enumerating Active Directory objects using API functions exported from activeds.dll.

Profiling the victim

USB worms tend to have a life of their own. Since their worming behavior is usually completely automated, the threat actor who initially deployed the worm doesn’t necessarily have full control over where it spreads. This is why it’s important for threat actors to have the worm beacon back to their C&C servers. With a beaconing mechanism in place, the threat actor can be informed about all the machines under their control and can use this knowledge to manage the worm as a whole.

The outgoing beaconing messages typically contain some information about the infected machine. This helps financially-motivated cybercriminals decide on how to best monetize the infection. Roshtyak is no exception to this, and it collects a lot of information about each infected victim. Roshtyak concatenates all the collected information into a large string, using semicolons as delimiters. This large string is then exfiltrated to one of Roshtyak’s C&C servers. The exfiltrated pieces of information are listed below, in order of concatenation.

  • External IP address (obtained during a Tor connectivity check)
  • A string hardcoded into Roshtyak’s code, e.g. AFF123 (we can’t be sure what’s the meaning behind this, but it looks like an affiliate ID)
  • A 16-bit hash of the DLL’s PE header (with some fields zeroed out) xored with the lower 16 bits of its TimeDateStamp. The TimeDateStamp appears to be specially crafted so that the xor results in a known value. This could function as a tamper check or a watermark.
  • Creation timestamp of the System Volume Information folder on the system drive
  • The volume serial number of the system drive
  • Processor count (GetActiveProcessorCount)
  • Windows version (KUSER_SHARED_DATA.Nt(Major|Minor)Version)
  • Windows product type (KUSER_SHARED_DATA.NtProductType)
  • Windows build number (PEB.OSBuildNumber)
  • Local administrative privileges (ZwQueryInformationToken(TokenGroups)/CheckTokenMembership, check for DOMAIN_ALIAS_RID_ADMINS)
  • Domain administrative privileges (check for WinAccountDomainAdminsSid/WinAccountDomainUsersSid)
  • System time (KUSER_SHARED_DATA.SystemTime)
  • Time zone (KUSER_SHARED_DATA.TimeZoneBias)
  • System locale (NtQueryDefaultLocale(0))
  • User locale (NtQueryDefaultLocale(1))
  • Environment variables (username, computername, userdomain, userdnsdomain, and logonserver)
  • Java version (GetFileVersionInfo("javaw.exe") -> VerQueryValue)
  • Processor information (cpuid to obtain the Processor Brand String)
  • Path to the image of the main executable module (NtQueryVirtualMemory(MemorySectionName))
  • Product ID and serial number of the main physical drive (DeviceIoControl(IOCTL_STORAGE_QUERY_PROPERTY, StorageDeviceProperty))
  • MAC address of the default gateway (GetBestRoute -> GetIpNetTable)
  • MAC addresses of all network adapters (GetAdaptersInfo)
  • Installed antivirus software (root\securitycenter2 -> SELECT * FROM AntiVirusProduct)
  • Display device information (DeviceId, DeviceString, dmPelsWidth, dmPelsHeight, dmDisplayFrequency) (EnumDisplayDevices -> EnumDisplaySettings)
  • Active processes (NtQuerySystemInformation(SystemProcessInformation))
  • Screenshot encoded in base64 (gdi32 method)


Once collected, Roshtyak sends the victim profile to one of its C&C servers. The profile is sent over the Tor network, using a custom comms module Roshtyak injects into a newly spawned process. The C&C server processes the exfiltrated profile and might respond with a shellcode payload for the core module to execute.

Let’s now take a closer look at this whole process. It’s worth mentioning that before generating any malicious traffic, Roshtyak first performs a Tor connectivity check. This is done by contacting 28 legitimate and well-known .onion addresses in random order and checking if at least one of them responds. If none of them respond, Roshtyak doesn’t even attempt to contact its C&C, as it would most likely not get through to it anyway.

As for the actual C&C communication, Roshtyak contains 35 hardcoded V2 onion addresses (e.g. ip2djbz3xidmkmkw:53148, see our IoC repository for the full list). Like during the connectivity check, Roshtyak iterates through them in random order and attempts to contact each of them until one responds. Note that while V2 onion addresses are officially deprecated in favor of V3 addresses (and the Tor Browser no longer supports them in its latest version) they still appear to be functional enough for Roshtyak’s nefarious purposes.

Roshtyak’s hardcoded C&C addresses

The victim profile is sent in the URL path, appended to the V2 onion address, along with the / character. As the raw profile might contain characters forbidden for use in URLs, the profile is wrapped in a custom structure and encoded using Base64. The very first 0x10 bytes of the custom structure serve as an encryption key, with the rest of the structure being encrypted. The custom structure also contains a 64-bit hash of the victim profile, which presumably serves as an integrity check. Interestingly, the custom structure might get its end padded with random bytes. Note that the full path could be pretty large, as it contains a doubly Base64-encoded screenshot. The authors of Roshtyak were probably aware that the URL path is not suitable for sending large amounts of data and decided to cap the length of the victim profile at 0x20000 bytes. If the screenshot makes the exfiltrated profile larger than this limit, it isn’t included.

When the full onion URL is constructed, Roshtyak goes ahead to launch its Tor comms module. It first spawns a dummy process to host the comms module. This dummy process is randomly chosen and can be one of dllhost.exe, regsvr32.exe, or rundll32.exe. The comms module is injected into the newly spawned process using a shared section, obfuscated through the previously described shellcode hiding technique. The comms module is then executed via NtQueueApcThreadEx, using the already discussed ntdll gadget trick. The injected comms module is a custom build of an open-source Tor library packed in three additional protective shellcode layers.

The core module communicates with the comms module using shared sections as an IPC mechanism. Both modules synchronously use the same PRNG with the same seed (KUSER_SHARED_DATA.Cookie) to generate the same section name. Both then map this named section into their respective address spaces and communicate with each other by reading/writing to it. The data read/written into the section is encrypted with RC4 (the key also generated using the synchronized PRNGs).

The communication between the core module and the comms module follows a simple request/response pattern. The core module writes an encrypted onion URL (including the URL path to exfiltrate) into the shared section. The comms module then decrypts the URL and makes an HTTP request over Tor to it. The core module waits for the comms module to write the encrypted HTTP response back to the shared section. Once it’s there, the core module decrypts it and unwraps it from a custom format (which includes decrypting it yet again and computing a hash to check the payload’s integrity). The decrypted payload might include a shellcode for the core module to execute. If the shellcode is present, the core module allocates a huge chunk of memory, hides the shellcode there using the shellcode hiding technique, and executes it in a new thread. This new thread is hidden using the NtSetInformationThread -> ThreadHideFromDebugger technique (including a follow-up anti-hooking check using NtGetInformationThread to confirm that the NtSetInformationThread call did indeed succeed).


In this blog post, we took a technical deep dive into Roshtyak, the backdoor payload associated with Raspberry Robin. The main focus was to describe how to deal with Roshtyak’s protection mechanisms. We showed some never-before-seen anti-debugger/anti-sandbox/anti-VM tricks and discussed Roshtyak’s heavy obfuscation. We also described Roshtyak’s core functionality. Specifically, we detailed how it establishes persistence, escalates privileges, moves laterally, and uses Tor to download further payloads.

We have to admit that reverse engineering Roshtyak was certainly no easy task. The combination of heavy obfuscation and numerous advanced anti-analysis tricks made it a considerable challenge. Nick Harbour, if you’re looking for something to repurpose for next year’s final Flare-On challenge, this might be it.

Indicators of Compromise (IoCs)

IoCs are available at

The post Raspberry Robin’s Roshtyak: A Little Lesson in Trickery appeared first on Avast Threat Labs.

Micropatch For Memory Corruption in Microsoft Outlook (CVE-2022-35742)

14 September 2022 at 13:21


by Mitja Kolsek, the 0patch Team


August 2022 Windows Updates brought a fix for a memory corruption vulnerability in Microsoft Outlook, discovered by security researcher insu of 78ResearchLab. The vulnerability exploits a flaw in Outlook's processing of multiple Content-Type headers in a multipart/signed email, whereby a malicious email can lead to free'ing an unallocated memory address and crashing Outlook as such email is downloaded (even before one can view it). Once such email is in user's Inbox, Outlook crashes whenever the user clicks on it or it gets displayed in the Preview pane.

While Microsoft categorized this flaw as "denial of service", it seems possible it could be exploited for arbitrary code execution.

0patch has security-adopted Office 2010 in November 2020 when its support was officially terminated, but Microsoft kept providing security updates for it until April 2021. After that date, we analyzed every published vulnerability affecting still-supported versions of Office to see if Office 2010 was affected, and until now, have not confirmed any. This is the first case where we could reproduce a publicly detailed, potentially critical issue in an Office 2010 component.

Thankfully, the researcher published an analysis and a POC for this vulnerability. This made it possible for us to create a patch for Outlook 2010 that no longer receives official fixes from Microsoft.

Microsoft assigned this issue CVE-2022-35742 and fixed it by properly preserving the flag (bit) that denotes whether a Content-Type buffer needs to be free'd or not. Our micropatch is logically equivalent to Microsoft's:

MODULE_PATH "..\AffectedModules\OUTLMIME.DLL_14.0.7268.5000_Office-2010_64bit_202104\outlmime.dll"
VULN_ID 7481

    PIT outlmime!0x27db9,outlmime!0x27d7d,outlmime!0x272ac
        mov r15, 0              ; default r15 for setz command
        call PIT_0x272ac        ; rewrite original code for patch placement
        mov ebp, eax            ; rewrite original code for patch placement
        test eax, eax           ; rewrite original code for patch placement
        js PIT_0x27db9          ; rewrite original code for patch placement
        mov eax, [rdi+100h]     ; get flag from memory
        and al, 2               ; check flag state
        cmp al, 2               ; check flag state
        setz r15b               ; set r15 accordingly to flag
        jmp PIT_0x27d7d         ; jump to block where memory is copied

    PIT outlmime!0x27db9
        mov [rdi+12Ch], eax     ; rewrite original code for patch placement
        mov rax, [rbx+0F8h]     ; read value from memory
        test byte[rax+10h], 4   ; check if read memory+10h contains 4
        jnz PIT_0x27db9         ; if memory+10h contains 4 then check flags
                                ; else jump to function return block
        test r15d, r15d         ; check if flag is set
        jz AND_BLOCK            ; if set jump to AND_BLOCK
        or dword[rdi+100h], 2   ; if flag not set then set it
        jmp PIT_0x27db9         ; jump to function return block
        and dword[rdi+100h], 0FFFFFFFDh    ; reset flag
                                ; continue normal execution


This video demonstrates the effect of our micropatch. With 0patch disabled, downloading the malicious email in Outlook 2010 crashes Outlook, and restarting Outlook leads to the same result, effectively disabling user's email; with 0patch enabled, the malicious email gets downloaded and while it can't be displayed due to malformed content, it sits there doing no harm.


The micropatch was written for 32-bit and 64-bit versions of Outlook 2010, fully updated with its latest free updates from April 2021.

This micropatch has already been distributed to all online 0patch Agents with a PRO or Enterprise license. To obtain the micropatch and have it applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch. 

To learn more about 0patch, please visit our Help Center

We'd like to thank insu of 78ResearchLab for publishing their analysis and providing a proof-of-concept that allowed us to reproduce the vulnerability and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Micropatches for Windows IKE Extension Remote Code Execution (CVE-2022-21849)

8 September 2022 at 13:05


by Mitja Kolsek, the 0patch Team

January 2022 Windows Updates brought a fix for a remote code execution vulnerability in Windows IKE Extension discovered by Polar Bear. Ten days ago (as of this writing), researchers from 78ResearchLab published an analysis and a POC for this vulnerability. This made it possible for us to create a patch for affected "security-adopted" Windows systems that no longer receive official fixes from Microsoft.

The vulnerability allows a remote attacker to cause memory (heap) corruption on the target computer by sending a malformed ISAKMP packet using the IKE protocol, whereby the VendorID payload is longer than the expected 10h characters. The vulnerable code namely prepares a 10-character buffer on the stack for storing this value, and in case a longer value is provided, the memcpy (memory copy) operation results in memory locations beyond the end of buffer being overwritten with attacker-chosen content. In the absence of a negative proof, such vulnerabilities are assumed to be exploitable for arbitrary code execution (although the POC at hand only results in crashing the process.)

Microsoft assigned this issue CVE-2022-21849 and fixed it by adding a check for the length of the VendorID value: if the length isn't exactly 10h (if the size of the entire payload including the 10h-byte prologue isn't exactly 20h), it ignores this value. Our micropatch with just two CPU instructions is logically equivalent to Microsoft's:

MODULE_PATH "..\AffectedModules\ikeext.dll_10.0.17134.254_Win10-1803_64-bit_u202105\ikeext.dll"
VULN_ID 7502

    PIT IKEEXT.DLL!0x1fb64
        cmp r13d, 20h     ; is the size of the VendorID payload equal to 20h?
        jne PIT_0x1fb64   ; if not, ignore the value


The micropatch was written for the following Versions of Windows with all available Windows Updates installed:

  1. Windows 10 v2004
  2. Windows 10 v1903
  3. Windows 10 v1803
Note that Windows 7 and Server 2008 R2 are not affected by this issue, and Windows 10 v1909 was still receiving official updates in January 2022. 
This micropatch has already been distributed to all online 0patch Agents with a PRO or Enterprise license. To obtain the micropatch and have it applied on your computers along with our other micropatches, create an account in 0patch Central, install 0patch Agent and register it to your account with a PRO or Enterprise subscription. Note that no computer restart is needed for installing the agent or applying/un-applying any 0patch micropatch.

To learn more about 0patch, please visit our Help Center

We'd like to thank Polar Bear for finding this issue, and 78ResearchLab researchers for publishing their analysis and providing a proof-of-concept that allowed us to reproduce the vulnerability and create a micropatch. We also encourage security researchers to privately share their analyses with us for micropatching.

Pro-Russian Group Targeting Ukraine Supporters with DDoS Attacks

6 September 2022 at 07:00

It has now been six months since the war in Ukraine began. Since then, pro-Russian and pro-Ukrainian hacker groups, like KillNet, Anonymous, IT Army of Ukraine, Legion Spetsnaz RF, have carried out cyberattacks. A lesser-known group called NoName057(16) is among the pro-Russian groups attacking Ukraine and the countries surrounding it and siding with Ukraine.

NoName057(16) is performing DDoS attacks on websites belonging to governments, news agencies, armies, suppliers, telecommunications companies, transportation authorities, financial institutions, and more in Ukraine and neighboring countries supporting Ukraine, like Ukraine itself, Estonia, Lithuania, Norway, and Poland. A full list of the group’s targets can be found at the end of this post. 

To carry out DDoS attacks, hacker groups utilize botnets. They control them via C&C servers, sending commands to individual bots, which essentially act as soldiers. Uncovering and tracking botnets is complex and time-consuming.

We got our hands on malware called Bobik. Bobik is not new, it’s been around since 2020, and is known as a Remote Access Trojan. Things have, however, recently changed. Devices infected with Bobik are now part of a botnet, and carrying out DDoS attacks for NoName057(16). We can confidently attribute the attacks to the group, as we have analyzed and compared what the C&C server is instructing devices infected with Bobik to do with the attacks the group claims to be responsible for on their Telegram channel.


The bots used by the botnet are infected with malware called Bobik, which is written in .NET. The malware has not been tied to a certain group in the past, and is actually a Remote Access Trojan. Its spyware functionalities include keylogging, running and terminating processes, collecting system information, downloading/uploading files, and dropping further malware onto infected devices.

Kill Chain

In the wild, one of the most monitored droppers for Bobik is RedLine Stealer, a botnet-as-a-service cybercriminals can pay for to spread their malware of choice. The usual workflow of Bobik is illustrated in the image below.

At first, an unknown group seems to have purchased RedLine Stealer to deploy – Bobik. The final DDoS module deployment is composed of two basic stages. The first executes Bobik’s Updater via a RedLine Stealer bot. In the second stage, Bobik’s Updater extracts and drops the final DDoS module (Bobik’s RuntimeBroker) and ensures the module’s persistence.

Bobik deployment

When RuntimeBroker is run, the module contacts a C&C server and downloads a configuration file defining targets for DDoS attacks. The module then starts the attacks using a defined count of threads, usually five threads.

The detailed workflow of the Bobik deployment is shown below. The RedLine Stealer Cryptic (installer) deobfuscates the .NET payload of Bobik’s Updater and injects it into the newly created process of the .NET ClickOnce Launch Utility (AppLaunch.exe); see steps 1 – 5.

Bobik deployment using RedLine Stealer Cryptic

The same process is used to execute Bobik’s RuntimeBroker (the DDoS module), because the dropped RuntimeBroker is also packaged and obfuscated via RedLine Stealer Cryptic. Therefore, the dropped Bobik’s RuntimeBroker also deobfuscates the .NET payload of Bobik’s RuntimeBroker and injects it into another AppLaunch process; see steps 6 – 8. After all these steps, the Bobik’s DDoS module is deployed, persistent, and ready to attack.

C&C Servers and Communication

Since June 1, 2022, we have observed Bobik’s network activities. Bobik bots communicate with C&C servers located in Russia and Romania. These two servers are already offline. However, another Romanian server is still active and able to send commands to the bots.

C&C Servers

Since tracking the botnet activity, we have captured three production C&C servers controlling Bobik bots and one development server. The servers run on OS Ubuntu with Nginx (v 1.18.0). RiskIQ reports all servers as malicious with self-signed certificates and servers with bad reputations that previously hosted many suspicious services.

Server 1 

The last active server is, located in Romania, and its first Bobik’s activity we saw was on June 13, 2022. We also have two DNS records for this malicious server: and

Server 2

The second server is also in Romania, but the communication with the Bobik bots was deactivated around July 14, 2022. The server is still active. Nevertheless, the server responds with a 502 HTTP code (Bad Gateway).  Based on the findings from Server 1, this server used the same DNS record, which was reconfigured to Server 1 in the middle of June.

Server 3

The first Bobik’s C&C server we saw was in Russia. The server had opened ports 80 and 443 until June 9, 2022. It is not usable, and therefore de facto offline, by Bobik bots because there is only one opened port for OpenSSH since Bobik requires port 80 for its C&C communication.

Dev Server

One of the C&C servers is a suspected  development server at, listening on port 5001. The server has been active since April and is located in Russia; its reputation is also suspicious. Avast has not detected any hits for this server in the wild. However, one Python sample uses the server as a testing environment.

C&C Communication

The communication between Bobik bots and the C&C servers is mediated using a simple unsecured HTTP request and response via the Nginx web server. The bots obtain appropriate commands from the C&Cs utilizing a URL, see the diagram below.

HTTP communication

The request URL uses the following template:

ip: Bobik bots hardcode one of the C&C IPs or one of the DNS records, see Section C&C Servers.
request: defines the purpose of the communications; we registered three types of requests in the form of a GUID.
– notice: the bots report their states.
– admin: this request can open the admin console of the Nginx web server.
– dropper: is a path to a malicious executable representing Bobik’s RuntimeBroker followed by an exe file name.
The exact GUIDs are listed in Appendix.
id: the hash is computed from Windows Management Instrumentation (WMI) information about a victim’s machine like Win32_DiskDrive, Win32_Processor, Win32_BaseBoard, etc. The hash can provide a unique identifier for Bobik bots.
v: the Bobik version; Avast has captured sample versions ranging from 8 to 19.
pr: is a flag (0,1) representing whether the communication with C&C has timed out at least once. 

A body of the HTTP request contains one simple XML tag with information about the victim; for instance:
<client a0="1" a1="en-US" a2="en-US" a3="14:03:53" a4="600">; where

  • a0: ProductType (1: Workstation, 2: Domain Controller, 3: Server)
  • a1: CultureInfo.InstalledUICulture
  • a2: CultureInfo.CurrentUICulture
  • a3: DateTime.Now
  • a4: Default timeout for the update of the DDoS target list from the C&C server

See the examples of the notice URLs:


The body of the HTTP response contains an encrypted and gzipped XML file configuring bots to the defined DDoS attacks. See the example below:

The bot receives the encrypted data that is decrypted using a simple algorithm, as shown below. The full script is located in the IOC repository.

HTTP response decryptor

The encrypted XML file has an elementary structure, as shown below:

Decrypted XML config

Most of the XML attributes are intuitive, so we will just explain the compound brackets in the path and body attributes. The configuration often uses dynamically generated pieces (definitions) like this: {.,15,20}. The definition dictates what long random text should be generated and in which position.

The definitions are abundantly applied in the path or body of the HTTP requests, where the attackers expect an increased load on the server. The effect is that bots flood servers with meaningless requests. For instance, the first <task> in the image directly above (decrypted XML config) uses this definition: query={.,15,20} which means that the bots generate random texts of 15 – 20 characters long as requests to, for example, the calendar of Poland’s presidential office. Similarly, the second <task> flooded the reference system of bus lines in Ukraine with requests for a password reset, as illustrated in this definition email={.,5,15}

For the most part, we captured definitions sending data to login pages, password recovery sites, and site searches; as can be seen from the XML config snippet below:

  • Login data

  • Search requests
  • Password recovery request


Consequently, the attackers try to overload a server with these requests, as they are computationally intensive. The requests require many accesses to server databases, e.g., verifying emails for password resetting, trying to login with random data (definitions), etc.

Bobik Botnet

The Avast telemetry data cannot paint a precise picture of the botnet’s size, but we can estimate the approximate representation of Bobik in the wild, see map below. The map shows where, according to Avast’s telemetry, the bots that attempt to carry out DDoS attacks for NoName057(16) are located. Avast has protected these devices from Bobik or from connecting to the C&C server. Most of the bots are located in Brazil, India, and Southeast Asia.

Distribution of users Avast protected from Bobik

According to our data, the number of Bobik bots is a few hundred. However, the total number must be much larger considering the DDoS attacks’ acute effectiveness and frequency. We, therefore, estimate there are thousands of Bobik bots in the wild.

Selection of DDoS Targets

We estimated a procedure as to how the attackers determine which web servers to DDoS attack because we have configurations of unsuccessful attacks.

The first step is looking for a target that supports Ukraine or a target with anti-Russian views. The attackers analyze the structure of the target’s  website and identify pages that can cause server overloading, especially requests requiring higher computing time, such as searching, password resetting, login, etc.

The second step is filling in the XML template, encrypting it, and deploying it to the C&C servers. The attackers monitor the condition of the target server and modify the XML configuration based on needs (modification of URL parameters, bodies, etc.) to be more effective. The configuration is changed approximately three times per day.

Suppose the configuration is successful and a targeted server is in trouble. In that case, the configuration is fixed until the web server crashes or a server admins implement anti-DDoS technique or firewall rules based on GeoIP.

If the attack is unsuccessful, a new target is selected, and the whole procedure of selection is repeated.


In the first phase, the attackers targeted Ukrainian news servers they defined as being against the war in Ukraine. Then, the attacks targeted websites belonging to Ukrainian cities, local governments, distribution of electrical power, Ukrainian companies supplying the Ukraine army with weapons, railway, bus, companies, and postal offices. 

The second phase targeted organizations publicly supporting Ukraine financially or materially, like Ukraine banks and financial institutions, and operators of local Ukraine gas reservoirs that publicly declared help for the defenders of Ukraine.

As the political situation around the war changed, so did the targets of the DDoS attacks. Bobik performed DDoS attacks on GKN Aerospace, which is the supplier of the Northrop Grumman Corporation because the US Defense Department convened a meeting with America’s eight prime defense contractors (including Northrop Grumman Corporation) to ensure long-term readiness to meet “Ukraine’s weapons needs”. 

Another global company under attack was Group 4 Securitas (G4S), which published a document assessing and exploring key elements of the conflict in Ukraine. In terms of telecommunications companies, we observed an attack on American telco company Verizon, which declared a waiver of call charges to and from Ukraine. And so, we could continue listing companies that were under Bobik attacks due to their support for Ukraine. You can see a few screenshots from affected websites below.

Screenshots of websites supporting Ukraine

Other attacks were more politically motivated based on government declarations of a given country. Baltic states (Lithuania, Latvia, and Estonia) were the significant targets, outside Ukraine, of DDoS attacks carried out by the group. Let’s summarize targets outside of Ukraine, chronologically, since we started monitoring Bobik.

  • June 7, 2022: Significant DDoS attack on Estonia central bank; see Twitter.
  • June 18, 2022: Bobik configuration changed to target Lithuanian transportation companies, local railway, and bus transportation companies after Lithuanian authorities announced a ban on transit through their territory to the Russian exclave of Kaliningrad of goods that are subject to EU sanctions. The attackers also targeted financial sectors in Lithuania, like UAB General Financing, Unija Litas, and more.
  • July 1, 2022: Goods were stopped by Norwegian authorities destined for the roughly 400 miners in the town of Barentsburg employed by the Russian state coal mining company Arktikugol. NoName057(16)’s DDoS attacks focused on Norwegian websites as retaliation for the blockade. The main targets were transportation companies (Kystverket, Helitrans, Boreal), the Norwegian postal service (Posten), and financial institutions (Sbanken, Gjensidige).
  • July 7, 2022: There were not any specific acts by Poland that caused the group to specifically target Polish sites. However, Poland has supported Ukraine from the beginning of the Ukraine conflict, and therefore sites in the country became targets. The first wave of DDoS attacks on Polish sites was aimed at government websites like the Polish Cyberspace Resource Center, Polish 56th Air Base, Military Recruitment Center in Chorzów, and more.
  • July 9, 2022: Bobik was reconfigured back to target Lithuanian websites, focusing on energy companies (Ignitis Group, KN), transportation companies (Ingstad & Co, Asstra-Vilnius), and banks (Turto Bankas, Šiaulių Bankas, Swedbank, SEB, Kredito unija Litas).
  • July 25, 2022: Polish sites were targeted again, this time  the Polish government and airports were attacked. We observed a DDoS configuration including the Polish Sejm, Presidential Office, Ministry of National Defense, Poznań Airport, Szczecin Goleniów Airport, Gdansk Airport, Kraków Airport, and more.
  • August 5, 2022: Polish regional and district courts were targeted.
  • August 9, 2022: When Finland announced their intention to join NATO, the Bobik configuration was reconfigured to target Finnish government institutions, like the  Parliament of Finland (Eduskunta), State Council, Finnish police, and more.
  • August 14, 2022: Latvian financial sector (Latvian Payment Services and Electronic Money, Luminor Interneto bankas) was attacked.
  • August 16, 2022: The second wave of attacks on the Polish justice system began. We monitored a configuration with specific district courts in Krakow, Olsztyn, Warszawa, Poznan.
  • August 23, 2022: Estonia’s largest news portal, Delfi, was under DDoS attack because it published Russophobic content.
  • August 26, 2022: The group targeted another Estonian company, Tallink Grupp, a company providing transport services in the northern Baltic Sea region, including air transport. Tallink’s airports, such as Kärdla, Tartu, and Pärnu were targeted.
  • August 27, 2022: Lithuania’s ministries of National Defense, Culture, Education, Science and Sports, and Public Procurement Offices were targeted, along with the airports and transport companies.
  • August 29, 2022: Ukrainian banks were under DDoSed by the group after a long break. We observed Acordbank, Trust capital, JSC Poltava-Bank, and Pravex Bank under attack.
  • September 1 and 2, 2022: Ukrainian schools were under attack at the beginning of the new school year. Fortunately, none of the group’s 14 targets were taken down.
  • September 3, 2022: Polish armaments plants (Dezamet, Zakłady Mechaniczne Tarnów) and Lithuanian investment companies (Unija Litas, General Financing Bankas) were the group’s first victims after their unsuccessful attack attempts on Ukrainian school institutions.
  • September 6, 2022: The second attempt to attack Ukrainian school institutions (Athens School in Kyiv, Cherkasy National University, First Cambridge Education Center, and more).

The graph below shows a timeline of Bobik DDoS attacks, including successful and unsuccessful attacks from the beginning of June to mid-July 2022, captured by Avast telemetry.

Finally, we inspected all hosts from the XML configuration files within our three-month observation period. The pie chart below illustrates that sites from Lithuania and Poland are the main targets of the NoName057(16) group.

Looking at the distribution of attacked institutions, courts come in first, and second is logistic companies, followed by banks. The remaining targets are airports, transportation, and logistic companies, governments, and telecommunications companies. A full list of the targets can be found at Appendix.

Identifying NoName057(16)

We have tried identifying the hacker group controlling Bobik bots and C&C servers. It was evident that the group must be pro-Russia, so we looked for the most famous DDoS attacks.

Shortly after the war in Ukraine began, a pro-Russia hacking group called Killnet appeared and began carrying out DDoS attacks against companies and governments supporting Ukraine, and even targeted the 2022 Eurovision Song Contest.

Bobik initially attacked websites Killnet has marked as “undesirable”. Killnet reports their DDoS attacks on their Telegram account. At first, it looked like the attacks carried out by Bobik distantly resembled Killnet’s activity, because the timeline of attacked countries was similar to the XML configurations. However, many successful DDoS attacks by Bobik were not posted by Killnet.

On June 21, 2022, the Killnet group publicly thanked a group called NoName057(16) for their support during a “special military operation”:

When we finished analyzing NoName057(16)’s Telegram channel, we confirmed that NoName057(16) is responsible for the DDoS attacks performed by the Bobik bots. All the XML configurations we captured from the NoName057(16) C&C servers exactly match the posts on the Telegram channel.


NoName057(16) is a little-known pro-Russian hacker group. They boast about their successful attack attempts on their Telegram channel, which has more than 14K subscribers. The group was active before we began tracking them on June 1, 2022. Their Telegram channel was created on March 11, 2022. We suspect they were either using a different set of botnets before June 1, 2022, or updating the malware used to control the bots in June.

NoName057(16) has been threatening to punish “propaganda” sources that “lie” about the Russian “special operation” in Ukraine, as well as governments from neighboring countries supporting them in their fight against Russia. The group became visible in the media at the beginning of August after carrying out successful attacks on Finnish and Polish parliaments.

A Wikipedia page about NoName057(16) was created on August 17, 2022. The page summarizes the group’s main activity. It classifies the group as a pro-Russia hacker group that claimed responsibility for cyberattacks on Ukrainian, US, and European websites belonging to government agencies, media, and private companies.

NoName057(16) released a manifesto declaring cyberwar as an act of revenge for open information war against Russia:

As the group increased its activities and media profile, it became easier to determine they were behind the attacks. Therefore, we can clearly state that Bobik is controlled by the pro-Russian hacker group called NoName057(16).

Success Rate

The group only reports successful DDoS attacks on their Telegram channel. Although the reported number of successful attacks seems large, statistical information indicates the contrary.

The group exclusively concentrates on DDoS attacks. They do not try to steal data or gain access to systems like other dangerous groups. The question is if they have the necessary knowledge, strength, and infrastructure to do more. Carrying out DDoS attacks is straightforward and does not require deep technical knowledge. Furthermore, the Bobik implementation only sends a simple HTTP request.

Our three-month observation shows that the group’s attack success is around 40%. We compared XML configurations captured by Avast to the achievements the group posts on their Telegram channel. Moreover, there is a particular set of targets, making up ~20% of their posts on Telegram, NoName057(16) claimed they successfully attacked, but we did not match them to the targets listed in their configuration files. For example, NoName057(16) claims to be responsible for attacking websites belonging to Lithuanian airports on June 25, 2022:

NoName057(16) claiming to be responsible for a DDoS attack on Lithuanian airports, posted on NoName057(16)’s Telegram channel

However, we did not find any records of the attack in the configuration files. The likelihood of them not using all of their bots in attacks is slim. In addition to this outage, NoName057(16) declared the sites were under a continuous fourteen-day attack. This would require an extensive bot network, especially considering the group performed other attacks during the same time frame, and the websites were still offline. From what we have seen, it is unlikely that NoName057(16) has an extensive bot network. Moreover, most of their DDoS attacks last a few hours, maximally a few days.

Impact and Protection

The power of the DDoS attacks performed by NoName057(16) is debatable, to say the least. At one time, they can effectively strike about thirteen URL addresses at once, judging by configuration history, including subdomains. Furthermore, one XML configuration often includes a defined domain/target as a set of subdomains, so Bobik effectively attacks five different domains within one configuration. Consequently, they cannot focus on more domains for capacity and efficiency reasons.

Most of the successful attacks result in servers being down for several hours or a few days. To handle the attacks, site operators often resort to blocking queries coming from outside of their country. It is a typical and suitable solution for local servers/domains such as local ticket portals of local bus/train companies, local institutions/companies, etc. Therefore, the DDoS impact on these domains has a minimal effect on the servers of local and smaller companies. Some operators or owners of affected servers have unregistered their domains, but these are extreme cases.

The DDoS attacks carried out were more difficult to handle for some site operators of prominent and significant domains, such as banks, governments, and international companies. After a successful attack, we noticed larger companies implementing enterprise solutions, like Cloudflare or BitNinja, which can filter incoming traffic and detect DDoS attacks in most cases. On the other hand, most large, international companies expect heavier traffic and run their web servers in the Cloud with anti-DDoS solutions, making them more resilient to attacks. For example, the group was unsuccessful in taking down sites belonging to Danish bank, Danske Bank (attacked June 19 – 21, 2022), and Lithuanian bank, SEB (attacked July 12 – 13, 2022 and July 20 – 21, 2022). 

The success of DDoS attacks depends on victim selection. The more “successful” attacks affected companies with simple sites, including about us, our mission, and a contact page, for example. These types of companies do not use their web pages as the main part of their business. These servers are therefore not typically designed to be heavily loaded and do not implement anti-DDoS techniques, making them a very easy target.

The group’s  DDoS attack on Poznań-Ławica Airport in Poland took the site offline for 16 minutes. NoName057(16) configured Bobik bots based on the <tasks> shown in the screenshot below:

XML configuration for Poznań-Ławica Airport

They tried to overload the server with requests for searching, form submitting, and getting data via WordPress API. When the server started to return 502 errors, NoName057(16) did not forget to brag on their Telegram channel. They also included a link to to prove their “revenge”.

NoName057(16)’s Telegram post related to their DDoS attack on Poznań-Ławica Airport

However, affected servers very often run back online within several minutes if they implement some anti-DDoS techniques because the algorithms learn to recognize the given type of attacks. The report below demonstrates that the DDoS attack on Poznań-Ławica Airport had a minimal impact since the website was offline for 16 minutes. report for the DDoS attack on Poznań-Ławica Airport, which took the site offline for 16 minutes

On June 23, 2022, NoName057(16) reported on Telegram that Lithuanian authorities lifted a ban on the transit of Russian cargo to Kaliningrad. The group attributes the lifting of the ban, amongst other things, to the efforts of their cyber attacks on Lithuania’s infrastructure, which is debatable at best. However, the attacks on Lithuanian servers have continued.


The botnet went into an idle state on September 1, 2022, at 6 PM UTC, and remained idle persisted for 12 hours. The botnet was reactivated on September 2, 2022, at 4 AM UTC. The XML file sent to the bots contained empty <tasks>, like in this example: <config><tasks delay="0" thread_count="-6"/></config>

A decline in the botnet’s performance may be a possible explanation for this. The group only posted two general posts to their Telegram channel on September 1 and 2, 2022, instead of boasting about successful attacks, our first indication the botnet might not be performing well.  

The first post was about the beginning of the new school year and day of knowledge. The group also mentioned being on the defense of the cyber front for their country and the for the safety of the younger generation. The second post was about “information guns and DDoS tanks” that worked quietly on very difficult and important work.

In fact, NoName057(16) changed targets ten times each day in the XML configurations, which is abnormal. We monitored the targets for these days, and none of the attacks were successful. Therefore, it is evident that the botnet had some trouble.

Most of the sites attacked by the group have implemented anti-DDoS protections. This slowdown  implies that the botnet is relatively static without many changes, such as recruiting new bots or dynamically changing bots’ IPs. A static botnet is an advantage for anti-DDoS protections, because malicious traffic can be easily identified.

NoName057(16) has continued to attack other easier targets since September. Only the future will reveal the Bobik botnet’s successes and failures. However, the attack’s success rate has been only around 25% since the beginning of September.


We investigated and analyzed malware used to carry out DDoS attacks on sites in and around Ukraine, starting in June, 2022. We identified the malware as a .NET variant of a RAT called Bobik, including a DDoS module, and spreading via a bot-net-as-a-service, RedLine Stealer.

The first technical part of this investigation uncovered C&C servers and the HTTP communication protocol used by the Bobik bots. We also successfully decrypted the HTTP protocol, including its parameters. This allowed us to monitor the C&C servers and collect information about the botnet architecture and XML configurations defining the DDoS targets.

The second aim was to determine the bad actors behind the attacks. We identified a pro-Russian hacker group called NoName057(16), as the users or possibly even the authors of Bobik, based on the XML configurations and what the group posts to their Telegram channel.

NoName057(16) focuses exclusively on DDoS attacks and looks for companies and organizations that support Ukraine or are “anti-Russian”. They do not try to steal data or gain access to the system like other dangerous groups. Therefore, we can declare that their activities are only harmful in the sense that they can lose companies’ business while their sites are offline, but attacked sites that have gone offline have luckily recovered quickly. Their activities are more annoying than dangerous. 

We found that the successful attacks defined by NoName057(16) make up just ~ 40% of all of their attack attempts. The success of their attacks depends on the quality of the targeted infrastructure. The evidence suggests that well-secured and designed servers can withstand the group’s DDoS attacks. 

The group focuses on servers/domains as retaliation for cyber-attacks and sanctions on Russia. All successful attacks, and even successful attacks the group is not responsible for (but claims to be), are posted to their Telegram channel.

If you are concerned your device might be infected with Bobik and supporting NoName057(16)’s efforts, we highly recommend you install security software, like Avast Antivirus, which detects, blocks and can remove Bobik.


The full list of IoCs is available in the IOC repository




[request] value
notice bcaa8752-51ff-4e35-8ef9-4aefbf42b482
admin 27bff71b-42c0-4a47-ba39-04c83f2f40bb
dropper fb82275d-6255-4463-8261-ef65d439b83b/<file_name>

Bobiks’ Targets
Full list of the targets can be found in the IOC repository


[1] Threat Encyclopedia
[2] US Defense Department convened a meeting with America’s eight prime defense contractors
[3] Ukraine Conflict Overview And Impact To Security In The UK
[4] Verizon Waives Calling Charges to and From Ukraine
[5] Kaliningrad sanctions to take effect, Lithuania says
[6] Norway Greenlights Blocked Goods for Russian Arctic Miners
[7] Hacker wars heat up as the pro-Russian Killnet attacks Italy
[8] What is known about the Russian hacker group NoName057(16), which hacked the website of the Finnish Parliament?
[9] Russian hacker group NoName057 (16) attacks Poland and Finland
[10] Wikipedia – NoName057(16)

The post Pro-Russian Group Targeting Ukraine Supporters with DDoS Attacks appeared first on Avast Threat Labs.

Windows Kernel Introspection (WKI)

2 September 2022 at 00:00
Table of contents Table of contents Introduction User-Mode Application Kernel-Mode Driver Example: Listing Kernel Memory Pool Tag Final Thoughts Introduction Over the last few years that I spent learning more and more about Microsoft Windows, it has been more and more apparent that studying the NT kernel is an incredibly deep and vast subject, nevertheless particularly interesting. A lot of research exists online and Windows Internals books are probably the best allies for this journey.

Context IS Memorabilia - Common Language Runtime Hook for Persistence

13 May 2022 at 00:00
Archive of from 22 AUG 2019. Table of contents Table of contents Introduction .Net Overview Common Language Runtime Application Domain and Application Domain Manager Assembly and Global Assembly Cache Wrapping Everything Up Identifying .Net Framework-Based Application Introduction This blog post explains how it is possible to execute arbitrary code and maintain access to a Microsoft Windows system by leveraging the Common Language Runtime application domain manager. During scenario-based assessments or digital-based Red Team assessments, gaining initial access to the internal network of an organisation is challenging, requires time, and effort.

Context IS Memorabilia - DynamicWrapperEx – Windows API Invocation from Windows Script Host

13 May 2022 at 00:00
Archive of from 01 FEB 2021. Table of contents Table of contents Introduction COM and OLE Automation Basics Leveraging OLE Automation x86_64 Standard Calling Convention Registration-Free Activation Limitations and Operational Security Considerations Example of Shellcode Execution References Introduction The Component Object Model (COM) was a revolutionary specification when it first appeared in 1995, despite this, there is still a large veil of mystery surrounding it. Those who have worked closely with Microsoft Windows systems may have heard of it, but probably in negative terms.

Context IS Memorabilia - AMSI Bypass

13 May 2022 at 00:00
Archive of from 12 JUN 2019 Table of contents Table of contents Introduction How AMSI Operates Enumerating AMSI Functions Finding the Function’s Address Egg Hunter Patching Final Notes Introduction AMSI stands for Anti-Malware Scan Interface and was introduced in Windows 10. The name is reasonably self-explanatory; this is an interface that applications and services are able to utilise, sending “content” to an anti-malware provider installed on the system (e.g. Windows Defender).

Context IS Memorabilia - Bring your own .NET Core Garbage Collector

13 May 2022 at 00:00
Archive of from 19 JUN 2020. Table of contents Table of contents Introduction .NET Core Configuration Knobs Standalone Garbage Collector Environment Variable Path Traversal Building a Custom GC Application Whitelisting Bypass Scenario Remediation Timeline Introduction This blog post explains how it is possible to abuse a legitimate feature of .Net Core, and exploit a directory traversal bug to achieve application whitelisting bypass. The .NET Core is an open-source software framework based on the .