North Korea’s Post-Infection Python Payloads

Throughout the past few months, several publications have written about a North Korean threat actor group’s use of NPM packages to deploy malware to developers and other unsuspecting victims. This blog post provides additional details regarding the second and third-stage malware in these attacks, which these publications have only covered in limited detail.

A few good sources that showcase the progression of the security community’s understanding of this attack workflow include:

Phlyum, which has been tracking this threat since last year
Palo Alto’s Unit 42, which provided additional information in November 2023
– A Medium post detailing a similar attack to the ones described above and in this blog post

Interestingly, it appears that the threat actors may have either moved to – or begun using in parallel – a series of Python scripts for this attack instead of solely delivering malicious DLLs (as observed by Phylum researchers in their original reports). This may be due to the added flexibility and speed of Python scripting, or it may simply be a result of the threat actors attempting to make their delivered tools and files appear more legitimate to users and investigators.

Technical Information

This post focuses on the inner workings of a Python workflow described in the aforementioned Palo Alto and Phylum reports. The files analyzed are below, with hashes corresponding to versions found on VirusTotal where possible.

Name: Frontend.zip
Notes: Malicious node package uploaded to VirusTotal from a user in Bangladesh on 3/28.
SHA256: 8b2f2fad1d1f1e6ad915ea2224dd9f8544edf4aaf910ab9b3a3112cc5806f16d

Name: main_[campaign ID].py or “.npl”
Notes: Obtained by malicious code in Frontend.zip. Obtains and executes next two stages.

Name: brow_[campaign ID].py or “bow”
Notes: Browser stealing module. An example was uploaded to VirusTotal via email on 3/28.
SHA256: 72400a957654371be9363fdd2753ffea8f240a8b3e6e03edc116f8da96fa3ce4

Name: pay_[campaign ID].py or “pay”
Notes: Contains bulk of backdoor actions. This example was uploaded to VirusTotal on 3/26.
SHA256: ba47df4e0cccdff1c6e81b7a9e347ac094efc8c94caab3f53ed0bd32d0293bf0

Name: any_[campaign ID] or “adc”
Notes: Python script obtained by “pay” that downloads and installs an AnyDesk client

The JavaScript file deployed by Frontend.zip is well-described in Phylum’s previous report, and is not covered further here.

Main.py

The main file is hosted on the threat actor’s server and if downloaded directly (i.e. not via part of the malicious workflow itself) it is named main_[campaign ID].py. This is an obfuscated Python script.

When decoded, the script contains two functions:

– download_payload() – downloads a script that serves as the malware’s main backdoor
– download_browse() – downloads an additional browser data stealing component.

In the example provided, these functions both check to see if the next-stage files are already on the device, deleting them if they are. Each function creates a file in a directory named .n2 located in the root of the user folder. The “payload” script is named “pay” and the browser named “bow.”

Deobfuscated Main.py code

brow.py

The browser data stealer is hosted on the threat actor’s server and if downloaded directly is named brow_[campaign ID].py. As noted above, the script saves this to the device using “bow” as the final name. Like main.py, brow.py is encoded in a large block.

When decoded, the malware contains a class named ChromeBase. This class contains a few “universal” functions that support stealing data from various web browsers local databases, a common feature of information stealers. For example, in the code snippets below, the malware extracts information from the Login.db file and credit card information from the webdata.db file.

Browser stealing code

The malware then contains three additional classes, one each for Windows, Linux, and Mac OS. These classes contain functions that extract and decrypt passwords from Chrome, Opera, Brave Browser, and Yandex, with differences in code resulting from the different operating system detected.

The collected information is sent directly to the C2, in this file’s case using an endpoint named “keys.”

pay.py

The “payload” Python file consists of two obfuscated parts that are decoded and executed in sequence.
The first part is a triage component, which performs the following actions:

– Collects the operating system, version, hostname, and username
– Retrieves the device’s approximate geolocation from ip-api[.]com
– Retrieves the device’s internal IP address
– Transmits this information to the C2 server

Information triage portion of the “pay” script

The second component contains the bulk of the backdoor actions. Interestingly, in this example, a second Command and Control server (C2) was used. This backdoor largely follows the example listed in the previously mentioned Palo Alto report.

When initialized, the malware creates a directory named .n2 in the root of the user folder and establishes a connection with the C2 server to receive commands. These commands are all prefixed with “ssh,” and include:

– ssh_kill – uses taskkill to terminate the Chrome and Brave browsers
– ssh_cmd – close the session
– ssh_obj – execute command-line commands
– ssh_upload – upload files to the C2
– ssh_clip – retrieve keylogger data
– ssh_env – collect files from Documents and Downloads, excluding any on an exclusion list
– ssh_any – download and execute AnyDesk
– ssh_run – obtain the browser stealer.

While the browser command would likely obtain the same file as delivered to the user by the main.py, this obtains it from this second C2. This might serves as a backup, in the event the first attempt to retrieve the script failed.

An example of the ssh_upload and ssh_run commands is below:

ssh_run and ssh_upload commands

This portion of the backdoor also contains several additional functions, called by these main command functions. For example, ssh_run calls the down_bro function in the code below:

ssh_run function calls the down_bro function above it

The keylogger is constructed using PyHook and continuously runs when the payload script successfully executes; rather than saving the keystrokes to a file, they are sent directly as a buffer to the threat actor. They keylogger supports common (but basic) functionality including obtaining the clipboard if a user performs a copy/paste action on their keyboard and identifying mouse clicks. A more detailed explanation on how PyHook can be used to create a keylogger can be found here.

A portion of this code is shown below:

A portion of the keylogging code

Any.py

The AnyDesk Python script retrieves AnyDesk from the first C2 server. This blog was unable to obtain a copy to determine if it is legitimate or if it contains any other malicious code. The executable is saved in the user’s root directory:

This file contains hardcoded AnyDesk configuration information, including credentials. This allows AnyDesk to operate as an “unattended client,” a legitimate configuration technique that “bypasses the need for a user to accept the connection request.”

Additional Thoughts

Many of the Python modules authored by this threat actor employ simple techniques. It is unclear if this is to maximize the speed and efficiency of developing new code, or if this was simply a choice the threat actors made in order to drop files that appear more legitimate onto impacted devices.

Regardless of this choice, the broader technique of delivering malicious Node packages to users will likely continue for the foreseeable future.