Go to Top

Go to Top

SW 공급망 공격 보안 해법은 없을까? 공격자 관점의 대응 전략
SW 공급망 공격 보안 해법은 없을까? 공격자 관점의 대응 전략
SW 공급망 공격 보안 해법은 없을까? 공격자 관점의 대응 전략
SW 공급망 공격 보안 해법은 없을까? 공격자 관점의 대응 전략

Threat Intelligence

Threat Intelligence

Threat Intelligence

In-Depth Analysis of the APT Down - The North Korea Files leak

In-Depth Analysis of the APT Down - The North Korea Files leak

In-Depth Analysis of the APT Down - The North Korea Files leak

EnkiWhiteHat

EnkiWhiteHat

Sep 22, 2025

Sep 22, 2025

Sep 22, 2025

Content

Content

Content

Overview

This report provides a follow-up analysis of the data from the "APT Down - The North Korea Files" leak, originally published in Phrack Magazine. The leak included the actor's VMware VM and VPS dump files, which allowed for an in-depth analysis of their activities and helped infer the threat actor behind the operations.

Notably, the leaked files contained rootkit source code that was a direct match for a rootkit identified by our firm during a 2022 incident investigation at a South Korean financial institution. The code logic and even the encryption keys were identical. Furthermore, we discovered the source code for an updated 2025 version of this rootkit.

Other findings include an exploit for an Ivanti 1-day vulnerability, what appears to be exfiltrated source code from the Ministry of Foreign Affairs website and the GPKISecureWebX solution, and evidence of phishing attacks targeting the Public Prosecutor's Office and the Defense Counterintelligence Command. These findings indicate that the actor has been persistently and actively targeting South Korea. The exfiltration of data that is difficult to obtain externally, combined with the use of a custom rootkit and a 1-day exploit, suggests the actor possesses a high level of technical capability and has achieved deep network penetration to steal sensitive data.

File Analysis Overview

Before delving into the analysis, this section provides a brief overview of the files that were released alongside the "APT Down - The North Korea Files" report.

List of public files posted on the .onion site

List of public files posted on the .onion site

The main files are work.zip, vps.zip, and file-lists_and_misc.zip, which are described below.

work.zip

  • A dump of the actor's VMware VM (Deepin 20.9), containing their workstation environment. It includes malware source code, attack tools, exfiltrated data, and various logs.

  • The host machine's C:\ drive was mounted via hgfs, allowing for the inspection of files on the actor's host system.

vps.zip

  • A dump of a VPS owned by the actor, which contains a history of its use in spear-phishing attacks and various logs.

  • Evidence of spear-phishing campaigns targeting multiple organizations, including the Defense Counterintelligence Command and the Public Prosecutor's Office, can be found here.

file-lists_and_misc.zip

  • Contains the actor's Google Timeline, command history, and a complete file listing of the workstation.

  • The full file list makes it possible to identify paths that do not exist in the main dump files.

1. Malware and Attack Analysis

This section analyzes the malware discovered in the actor's VMware dump and the spear-phishing and infrastructure operations identified in the VPS dump. Notably, the rootkit and backdoor found in the tomcat20220420_rootkit and tomcat20250414_rootkit_linux234 directories are linked to a past incident at a South Korean financial institution investigated by our team.

1.1. tomcat20220420_rootkit

1.1.1. Backdoor

The backdoor source code is located in the work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/work directory. The backdoor's primary functions are to execute malicious commands or to act as a proxy for relaying network traffic. It is always executed by the rootkit and does not run as a standalone process.

The backdoor's execution arguments are described in the table below.

| First Argument | Argument Count: 1 | Argument Count: 2 |
| --- | --- | --- |
| cb | Uses hardcoded IP address and port. | Uses hardcoded IP and the second argument as the port. |
| proxy | Activates the proxy flag. | Not applicable. |
| Any other value | Uses the first argument as the port. | Not applicable

caption - Command-line options

When the proxy flag is enabled, the command-handling logic is bypassed, and the backdoor waits to receive an AES-256-CBC encrypted "__step2__".

Data validation logic in master.c

caption - Data validation logic in master.c

The AES key and IV are as follows:

  • AES key: 603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe

  • IV: 603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe

To masquerade its traffic, the backdoor can wrap its socket communication in one of the following protocols, as defined in install_common.h:

  • HTTP

  • HTTPS

  • SSL

  • TCP

  • SMTP

Protocol definition in install_common.h

caption - Protocol definition in install_common.h

All data, including command codes, is encrypted and decrypted using a multi-byte XOR operation with the key "1101link". The implementation of this logic results in an operation equivalent to a single-byte XOR with the value 1.

EncodeDecode function in encrypt.c

caption - EncodeDecode function in encrypt.c

The supported commands are listed below.

| Command Name | Command Code | Description | Response Name | Response Code |
| --- | --- | --- | --- | --- |
| CMD_LOGIN | 0x11 | Responds to the client to confirm connection. | CMD_LOGIN_YES | 0x12 |
| CMD_FILE_UP | 0x15 | Receives and saves a file from the client. | CMD_UP_YES | 0x16 |
| CMD_FILE_DOWN | 0x18 | Sends a specified file to the client. | CMD_DOWN_YES | 0x19 |
| CMD_SHELL | 0x1b | Executes a given shell command and sends the result to the client. | CMD_SHELL_YES | 0x1c |
| CMD_TRANSFER | 0x1f | Receives connection details for a new backdoor instance from the client. It sends a password to the new backdoor to trigger the termination of the current instance, then acts as a proxy, forwarding packets from the client to the new backdoor. | CMD_TRANSFER_YES | 0x20 |
| CMD_PROXY_TRANSFER | 0x21 | Receives connection details for a new backdoor instance. It sends an AES-encrypted password, __step2__, and __step3__ to the new backdoor to configure it as a proxy. It then acts as a proxy, forwarding subsequent packets from the client to the new backdoor. | CMD_PROXY_TRANSFER_YES | 0x22 |
| CMD_SOCKS_PROXY_TRANSFER | 0x23 | Functions similarly to CMD_PROXY_TRANSFER but establishes a SOCKS5 proxy connection. | CMD_SOCKS_PROXY_TRANSFER_YES | 0x24 |
| CMD_BACK | 0x13 | Responds to the client to confirm the back command. | CMD_BACK_YES | 0x14 |
| CMD_SINGLE_CMD | 0x25 | Executes a single shell command, returns the result, and terminates the backdoor process. | CMD_SINGLE_CMD_YES | 0x26 |
| CMD_IN_SINGLE_CMD | 0x27 | Executes a single shell command and returns the result without terminating. | CMD_IN_SINGLE_CMD_YES | 0x28 |
| CMD_D_SINGLE_CMD | 0x2b | Executes a shell command in a detached process. | CMD_D_SINGLE_CMD_YES | 0x2c |
| CMD_D_OUT_SINGLE_CMD | 0x2d | Executes a shell command in a detached process and then terminates the backdoor process. | CMD_D_OUT_SINGLE_CMD_YES | 0x2e

caption - Backdoor command descriptions

1.1.2. syslogk rootkit

The file work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/main.c contains the source code for the syslogk rootkit. This rootkit hides the directory containing the backdoor and related malware to evade detection. It also ensures the backdoor only runs when triggered by a specific magic packet from the actor.

The rootkit hooks three functions to hide processes, ports, and directories.

| Hooked Function | Hooking Function | Action |
| --- | --- | --- |
| proc_roo_readdir | hk_proc_readdir | Hides processes |
| tcp4_seq_show | hk_t4_seq_show | Hides open ports |
| readdir | hk_root_readdir | Hides directories

caption - Function hooks

The hooked functions use strstr to compare target entries against predefined strings (process names, paths, ports) and remove any matches before returning the results.

The rootkit then uses nf_register_hook to register callback functions at two Netfilter hook points:

  • NF_INET_LOCAL_IN: Intercepts packets just before they are delivered to a local process.

  • NF_INET_LOCAL_OUT: Intercepts packets just before they are sent from a local process.

Netfilter hook registration logic in hkcap.c

caption - Netfilter hook registration logic in hkcap.c

This allows the rootkit to monitor all traffic and execute malicious actions upon receiving a magic packet.

The NF_INET_LOCAL_IN callback function inspects incoming TCP packets, decrypts the payload, and checks if it matches specific conditions. The decryption keys are listed below.

| Type | Value |
| --- | --- |
| AES key | 603deb15153a715e2b73aef3857d758b1f552c573e6158d72d9811a33914defe |
| IV | 12a3bb47535ec0d53953a6fbad43f573 |
| XOR key | 1101link

caption - Decryption keys

The conditions and corresponding actions are detailed below.

| Condition | Action | Callback Function |
| --- | --- | --- |
| 1. The first 4 bytes of the XOR-decrypted payload are 0x44332211.-n2. The next 8 bytes are ssecuremw. | Executes a shell command via /bin/sh -c. | nfinpro |
| 1. The packet is a SYN packet.-n2. The packet's window value is 1022, and its ID and sequence numbers are in predefined lists (id_list, seq_list). | Executes the backdoor via /bin/sh -c and forwards the packet payload. | nfin, nfinpro |
| 1. The packet is from the C&C IP.-n2. The payload is ssecuremw. | Terminates the currently running backdoor process. | nfin, nfinpro |
| 1. The packet is from the C&C IP.-n2. The payload is not ssecuremw. | Changes the packet's destination port to the backdoor's listening port. | nfin, nfinpro |
| 1. The first 4 bytes are 0x0000002c.-n2. The data at offset 10 contains the AES-encrypted password (ssecuremw). | Executes the backdoor with the proxy argument via /bin/sh -c. | nfinpro |
| The data begins with the AES-encrypted password (ssecuremw). | Executes the backdoor with the proxy argument via /bin/sh -c. | nfinpro |
| The data begins with the AES-encrypted string __step3__. | Updates the C&C IP to the source IP of the current packet. | nfinpro

caption - Conditions and actions

At the NF_INET_LOCAL_OUT hook point, the callback function changes the source port of packets originating from the backdoor to a non-listening port.

After registering the Netfilter callbacks, the rootkit removes itself from the system's module list to evade detection by tools like lsmod.

hide_module function

caption - hide_module function

The hidden rootkit can be made visible again by writing the value of MAGIC_DRBIN (defined in install.h) to a specific file controlled by the rootkit.

proc_write function logic

caption - proc_write function logic

1.1.3. Backdoor Client

The file work/home/user/Desktop/tomcat20220420_rootkit/tomcat20220420_rootkit/work/tcat.c contains the source code for the backdoor client. This tool was likely run by the actor on their C&C server.

The client accepts several command-line options, which are detailed below.

| Long Option | Short Option | Description |
| --- | --- | --- |
| HOST | H | Backdoor address. |
| PORT | P | Backdoor port. |
| password | p | Password for backdoor connection (default: ssecuremw). |
| callback | c | Enable callback mode. |
| single_command | s | Send a single command for execution. |
| daemon_command | d | Send a command to be executed in a new process (background execution). |
| proxy | x | Use a proxy server. |
| socks_proxy | y | Use a SOCKS5 proxy server. |
| socks_aim_hostname | i | Address of the SOCKS5 proxy server. |
| socks_aim_port | o | Port of the SOCKS5 proxy server. |
| socks_username | u | Username for the SOCKS5 proxy server. |
| socks_password | a | Password for the SOCKS5 proxy server. |
| knock_protocol | k | Protocol for the initial connection attempt (0: TCP, 1: HTTP, 2: SSH). |
| ethernet_interface | e | Ethernet interface to use. |
| cookie | 5 | Cookie value for HTTP/HTTPS communication. |
| host | 6 | Custom host header value for HTTP/HTTPS communication. |
| main_protocol | m | Protocol for backdoor communication (0: TCP, 1: HTTP, 99: old HTTP). |
| kc | 1 | Send a command to be executed by the kernel module without output

caption - Command-line options

Commands can be delivered to the backdoor in four ways:

  • single cmd: Sends a single command to the backdoor for execution.

  • single daemon cmd: Sends a command to be executed in a new process on the backdoor host.

  • input loop: Enters an interactive loop, accepting commands until exit is entered.

  • kernel cmd: Sends a command to be executed by the kernel module without returning output.

Except for the input loop, the client terminates after sending the command. The commands available in the input loop are listed below.

| Command | Description |
| --- | --- |
| shell | Forwards shell commands to the backdoor until exit is entered. |
| trans | Uses the current backdoor as a proxy to connect to a new backdoor instance. Sends the CMD_TRANSFER command. |
| upload | Uploads a single file to the backdoor. |
| download | Downloads a single file from the backdoor. |
| back | Terminates the current connection and reconnects to the previous backdoor in a proxy chain. |
| exit | Terminates the client process. |
| proxy_trans | Uses the current backdoor as a proxy to connect to a new backdoor instance. Sends the CMD_PROXY_TRANSFER command. |
| socks_trans | Uses the current backdoor as a SOCKS5 proxy to connect to a new backdoor instance. Sends the CMD_SOCKS_PROXY_TRANSFER command. |
| old_trans | Same as trans but does not set a communication protocol. |
| old_proxy_trans | Same as proxy_trans but does not set a communication protocol. |
| old_socks_trans | Same as socks_trans but does not set a communication protocol. |
| sh | Sends a single command to the backdoor for execution. |
| dsh | Sends a command to be executed in a new process on the backdoor host (background execution). |
| cookie | Sets or displays the current cookie value for HTTP communication. |
| host | Displays information about the currently connected backdoor

caption - Backdoor client command descriptions

All communication is encrypted with the XOR key "1101link". For each message, the client computes a SHA512 hash of the password combined with the GENERAL_MODULE and GENERAL_PROTOCOL values defined in common.h. This hash is prepended to the message before transmission. Communication with the backdoor can be configured to use one of the following protocols:

  • TCP

  • HTTP

  • OLD HTTP (A variant of HTTP that does not use cookies)

1.2. tomcat20250414_rootkit_linux234

1.2.1. Backdoor

The directory work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/work contains the source code for the 2025 version of the backdoor. This is an upgraded version of the 2022 backdoor, with new features such as password verification for delayed callbacks, configurable callback timers, and rate-limiting for file transfers. Its command-line options are listed below.

| Long Option | Short Option | Description |
| --- | --- | --- |
| module | m | Set callback mode. |
| protocol | p | Set communication protocol. |
| port | P | Set port. |
| HOST | H | Set address for the callback proxy. |
| ft | f | Set initial delay time for callbacks. |
| tt | t | Set subsequent delay time for callbacks. |
| LL | L | Set log file path

caption - Command-line options

In addition to the existing password, this version introduces a master password. When a client connects, the backdoor computes a SHA512 hash of the master password and connection settings. The master password, !@nf4@#fndskgadnsewngaldfkl, is defined in common.h. While the hash is stored in a global variable and checked during SSL communications, it is not actively used. The client's provided password is then hashed in the same manner and verified.

passcheck_check function in encrypt.c

caption - passcheck_check function in encrypt.c

The password is Miu2jACgXeDsxd. Configuration settings, including the password, can be modified in config.sh and are applied at build time.

Default settings in config.sh

caption - Default settings in config.sh

The communication protocols are the same as the 2022 version:

  • HTTP

  • HTTPS

  • SSL

  • TCP

  • SMTP

While the original XOR key is still present, five new XOR keys and corresponding encryption/decryption functions have been added.

All XOR keys found in encrypt.c

caption - All XOR keys found in encrypt.c

This version adds several new commands and modifies the behavior of commands with "TRANSFER" in their names. The newly added command codes are:

  • CMD_NEW_UPLOAD

  • CMD_NEW_DOWNLOAD

  • CMD_LISTEN_PROXY_TRANS

  • CMD_TRANSFER

  • CMD_PROXY_TRANSFER

  • CMD_SOCKS_PROXY_TRANSFER

  • CMD_SOCKS_PROXY

  • CMD_NEW_SINGLE_CMD

The command behaviors are detailed in the table below. (new or significantly modified commands are grouped with an asterisk).

| Command Name | Command Code | Description | Response Name | Response Code |
| --- | --- | --- | --- | --- |
| CMD_LOGIN | 0x11 | Responds to the client to confirm connection. | CMD_LOGIN_YES | 0x12 |
| CMD_FILE_UP | 0x15 | Receives and saves a file from the client. | CMD_UP_YES | 0x16 |
| CMD_FILE_DOWN | 0x18 | Sends a specified file to the client. | CMD_DOWN_YES | 0x19 |
| *CMD_NEW_UPLOAD* | *0xb3* | *Receives and saves a file from the client.* | *CMD_NEW_UPLOAD_YES* | *0xb4* |
| *CMD_NEW_DOWNLOAD* | *0xb1* | *Sends a specified file to the client according to specified options (e.g., transfer speed, chunk size).* | *CMD_NEW_DOWNLOAD_YES* | *0xb2* |
| CMD_SHELL | 0x1b | Executes a given shell command and sends the result to the client. | CMD_SHELL_YES | 0x1c |
| *CMD_LISTEN_PROXY_TRANS* | *0x3f* | *Acts as a callback proxy server.* | *CMD_LISTEN_PROXY_TRANS_YES* | *0x40* |
| *CMD_LISTEN_TRANS* | *0x3d* | *Acts as a callback server.* | *CMD_LISTEN_TRANS_YES* | *0x3e* |
| *CMD_TRANSFER* | *0x1f* | *Receives connection details for a new backdoor instance, sends a password to activate it, and then acts as a proxy, forwarding packets from the client to the new backdoor.* | *CMD_TRANSFER_YES* | *0x20* |
| *CMD_PROXY_TRANSFER* | *0x21* | *Receives connection details for a new backdoor instance, sends a SHA512 hash of the password and connection settings, and then acts as a proxy. The first 9 bytes of the packet are set to 0000002c061e00000020.* | *CMD_PROXY_TRANSFER_YES* | *0x22* |
| *CMD_SOCKS_PROXY_TRANSFER* | *0x23* | *Functions similarly to CMD_PROXY_TRANSFER but establishes a SOCKS5 proxy server. The first 9 bytes of the packet are set to 0000002c061e00000020.* | *CMD_SOCKS_PROXY_TRANSFER_YES* | *0x24* |
| CMD_BACK | 0x13 | Responds to the client to confirm the back command. | CMD_BACK_YES | 0x14 |
| *CMD_SOCKS_PROXY* | *0x29* | *Uses the client as a proxy.* | *CMD_SOCKS_PROXY_YES* | *0x30* |
| CMD_SINGLE_CMD | 0x25 | Executes a single shell command, returns the result, and terminates the backdoor process. | CMD_SINGLE_CMD_YES | 0x26 |
| CMD_IN_SINGLE_CMD | 0x27 | Executes a single shell command and returns the result without terminating. | CMD_IN_SINGLE_CMD_YES | 0x28 |
| CMD_D_SINGLE_CMD | 0x2b | Executes a shell command in a detached process. | CMD_D_SINGLE_CMD_YES | 0x2c |
| CMD_D_OUT_SINGLE_CMD | 0x2d | Executes a shell command in a detached process and then terminates the backdoor process. | CMD_D_OUT_SINGLE_CMD_YES | 0x2e |
| *CMD_NEW_SINGLE_CMD* | *0x2f* | *Executes a given command and returns the result to the client.* | *CMD_NEW_SINGLE_CMD_YES* | *0x30

caption - Backdoor command descriptions

1.2.2. syslogk rootkit

The file at work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/main.c is the 2025 version of the rootkit. Like its predecessor, it registers callbacks at Netfilter hook points, but it features significant changes to its function hooking, module hiding, and Netfilter implementation.

While the 2022 rootkit used the udis86 library for function hooking, the 2025 version uses the khook library.

caption - Left: 2022 rootkit (udis86). Right: 2025 rootkit (khook).

caption - Left: 2022 rootkit (udis86). Right: 2025 rootkit (khook).

This version hooks a total of five functions, up from three in the 2022 version.

| Hooked Function | Hooking Function | Action |
| --- | --- | --- |
| tcp4_seq_show | khook_tcp4_seq_show | Hides open ports |
| proc_filldir | khook_proc_filldir | Hides processes |
| filldir | khook_filldir | Hides directories |
| filldir64 | khook_filldir64 | Hides directories |
| proc_root_readdir | khook_proc_root_readdir | Hides directories

caption - Function hooks

The number of Netfilter hooks has increased from two to four.

  • NF_INET_LOCAL_IN: Before a packet is delivered to a local process.

  • NF_INET_LOCAL_OUT: Before a packet is sent from a local process.

  • NF_INET_PRE_ROUTING: Immediately after a packet enters the network stack.

  • NF_INET_POST_ROUTING: Just before a packet is placed on the wire, after routing.

Netfilter hook registration logic in hkcap.c

caption - Netfilter hook registration logic in hkcap.c

The NF_INET_PRE_ROUTING callback performs two main actions. First, it checks for a magic packet to execute the backdoor, similar to the NF_INET_LOCAL_IN hook in the 2022 version. However, the condition is simpler: it only checks for a TCP SYN packet where the ID and sequence number are present in the id_list and seq_list.

If the condition is met, the packet's window value is used to determine which masquerade protocol to use when launching the backdoor.

| Magic Packet window Value | Masquerade Protocol |
| --- | --- |
| 1022 | TCP |
| 2025 | HTTP |
| 29201 | HTTPS |
| 1130 | SSL |
| 2101 | SMTP

caption - Masquerade protocols

The method for executing the backdoor has also changed significantly. Instead of /bin/sh -c, this version uses call_usermodehelper to launch the backdoor on a random port between 3000 and 8000.

kernel_run function in hkcap.c

caption - kernel_run function in hkcap.c

The second action of the

PRE_ROUTING

hook is to change the destination port of any packet from the C&C IP to the randomly generated backdoor port. This allows the actor to communicate with the backdoor without knowing the randomly assigned port in advance.

Code to change packet destination in hkcap.c

caption - Code to change packet destination in hkcap.cThe NF_INET_LOCAL_IN callback function then changes the packet's destination port again, from the random port to the actual port the backdoor is listening on. This two-step redirection appears designed to obscure the backdoor's true listening port.

Code to change packet destination to the backdoor port in hkcap.c

caption - Code to change packet destination to the backdoor port in hkcap.c

The NF_INET_LOCAL_OUT callback changes the source port of outgoing backdoor packets from the listening port to an arbitrary port.

The NF_INET_POST_ROUTING callback then changes the source port from the arbitrary port to the actual C&C port.

Code to change packet destination to the actor's port in hkcap.c

caption - Code to change packet destination to the actor's port in hkcap.c

The rootkit's hiding mechanism has been enhanced to remove the module from sysfs in addition to the standard module list.

Module hiding code in hkmod.c

caption - Module hiding code in hkmod.c

The sysfs-related functions are taken directly from the kv_hide_mod function of the KoviD rootkit.

kv_hide_mod function in the KoviD rootkit

caption - kv_hide_mod function in the KoviD rootkit

While writing a specific string to the rootkit still unhides it, this version adds the ability to dynamically configure the ports used by the Netfilter hooks by writing other specific strings.

Port configuration code in hkcap.c

caption - Port configuration code in hkcap.c

All strings written to the rootkit, except for the unhide command, are first decrypted with AES-256-CBC. The key and IV are as follows:

  • AES key: d03deb92153a71458973aef3857d75b27e552cc63e6158a8339811873994de47

  • IV: efa3c987532cc0bdac533845ad8df5ea

The actions triggered by writing to the rootkit are listed below.

| String Name | String | Action |
| --- | --- | --- |
| ModuleDecodeKey | mrdyZwIh2Bh | Unhides the rootkit module. |
| PROC_CMD_TRANSREG | h9WJZ1 | Sets the C&C IP/port and backdoor port to the values provided after the string. |
| PROC_CMD_TRANSCLR | 64OWE2 | Deletes C&C session information corresponding to the backdoor port provided after the string. |
| PROC_CMD_CONNSET | C8lnS3 | Modifies C&C session information for a given backdoor port to the IP and port values provided after the string

caption - Strings and triggered actions

1.2.3. Backdoor Client

The file

work/mnt/hgfs/Desktop/tomcat20250414_rootkit_linux234/tomcat20250414_rootkit_linux2345/work/tcat.c contains the source code for the 2025 backdoor client. Compared to the 2022 version, the primary change is the removal of the kernel command (kc) option. A new LLL option for specifying a log file path has been added. The full list of options is below.

| Long Option | Short Option | Description |
| --- | --- | --- |
| HOST | H | Backdoor address. |
| PORT | P | Backdoor port. |
| password | p | Password for backdoor connection (default: ssecuremw). |
| callback | c | Enable callback mode. |
| single_command | s | Send a single command for execution. |
| daemon_command | d | Send a command to be executed in a new process (background execution). |
| proxy | x | Use a proxy server. |
| socks_proxy | y | Use a SOCKS5 proxy server. |
| socks_aim_hostname | i | Address of the SOCKS5 proxy server. |
| socks_aim_port | o | Port of the SOCKS5 proxy server. |
| socks_username | u | Username for the SOCKS5 proxy server. |
| socks_password | a | Password for the SOCKS5 proxy server. |
| knock_protocol | k | Protocol for the initial connection attempt (0: TCP, 1: HTTP, 2: SSL, 3: HTTPS, 4: SMTP). |
| ethernet_interface | e | Ethernet interface to use. |
| cookie | 5 | Cookie value for HTTP/HTTPS communication. |
| host | 6 | Custom host header value for HTTP/HTTPS communication. |
| main_protocol | m | Protocol for backdoor communication (0: TCP, 1: HTTP, 2: SSL, 3: HTTPS, 4: SMTP). |
| LLL | L | Log file path

caption - Command-line options

With the removal of the kernel command feature, commands are delivered in three ways:

  • single cmd: Sends a single command to the backdoor for execution.

  • single daemon cmd: Sends a command to be executed in a new process on the backdoor host.

  • input loop: Enters an interactive loop, accepting commands until exit is entered.

While the 2022 client only accepted simple commands, the 2025 client allows options to be specified for each command, enabling more precise control. The number of top-level commands has decreased, but their functionality is more sophisticated. The commands available in the input loop are listed below.

| Command | Description |
| --- | --- |
| shell | Forwards shell commands to the backdoor. |
| inc | Displays connection information for all currently connected backdoors. |
| trans | Uses the current backdoor as a proxy server to connect to a new backdoor. |
| socks5 | Uses the current backdoor as a SOCKS5 proxy server to connect to a new backdoor. |
| upload | Uploads a single file to the backdoor. |
| download | Downloads a single file from the backdoor. |
| nup | Uploads a file to the backdoor with specified options. |
| ndown | Downloads a file from the backdoor with specified options. |
| back | Terminates the current connection and reconnects to the previous backdoor in a proxy chain. |
| exit | Terminates the client process. |
| cookie | Sets or displays the current cookie value. |
| host | Displays information about the currently connected backdoor

caption - Backdoor client command descriptions

A full analysis of the new features was not possible, as key functions for the new commands (e.g., tcat_new_send_file, tcat_new_recv_file) were not defined in the provided source code.

Unlike the 2022 client, which sent a SHA512 hash of the password with every communication, the 2025 client sends it only once during the initial connection.

All communication is still encrypted with the XOR key "1101link". However, the available communication protocols have changed. The "OLD HTTP" option has been removed, and HTTPS and SMTP have been added. The supported protocols are:

  • TCP

  • HTTP

  • HTTPS

  • SMTP

1.3. Cobalt Strike

1.3.1. Cobalt Strike Loader

A Rust-based loader designed to decrypt and execute embedded shellcode was found at the following paths:

  • work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe

  • work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe

  • work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz

  • work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe

  • work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe

  • work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe

  • work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe

The loader dynamically resolves Windows APIs using djb2 hashing. It then decrypts the shellcode with AES-256-CBC and executes it in memory.

API hashing function

caption - API hashing function

The AES keys and IVs for each loader instance are listed below.

| File Path | Key | IV |
| --- | --- | --- |
| work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe | 0e9cae6dc34ae064b71604c221bca933f1ffcb63df4af7e6806fa94849d66924 | 33081eba056e1ad1c8e5618829b84030 |
| work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe | b5976580954f47b49a33a5c580610aeb77e8c2ef66e0ab337ddfb2d1851e7998 | 545644526a2a08dff2dd4e3a812be126 |
| work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz | ae49597d8c87d5cf23f485fa0fc138d06c13aa743405e3c9c44b3097ba008dcf | a61080d5ee883489ee8ce40f84569fc3 |
| work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe | 6f627869fe1f05add75dc039fce970fce9bb4531d5601f894877aab3bc9dfe89 | 5fb9604da0d50bce37e53c194ab074b0 |
| work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe | 62609005b9ace1387a1065e6a72b7f796e12fd7c3b34b30a94af5d9112287b3c | 5990b37f9e73a20c2d5605cba72399cc |
| work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe | 0e9cae6dc34ae064b71604c221bca933f1ffcb63df4af7e6806fa94849d66924 | 33081eba056e1ad1c8e5618829b84030 |
| work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe | b5976580954f47b49a33a5c580610aeb77e8c2ef66e0ab337ddfb2d1851e7998 | 545644526a2a08dff2dd4e3a812be126

caption - Cobalt Strike Loader AES keys and IVs

1.3.2. Shellcode

The shellcode executed by the Cobalt Strike Loader decrypts and runs the final Cobalt Strike Beacon payload in memory. This shellcode was also found as standalone files at the following paths:

  • work/home/user/.cache/vmware/drag_and_drop/5wdgDr/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe

  • work/home/user/.cache/vmware/drag_and_drop/NDBu65/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/yf91yD/payload.bin

  • work/home/user/.cache/vmware/drag_and_drop/zlLWeR/payload.bin

The file work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe is a compiled executable but is functionally identical to the other shellcode files.

DIE result for Black.x64.exe

caption - DIE result for Black.x64.exe

Like the loader, the shellcode dynamically resolves Windows APIs using djb2 hashing. The embedded Cobalt Strike Beacon is decrypted using RC4 and executed in memory.

RC4 ksa 함수

caption - RC4 ksa function

The RC4 keys used by each file are listed below.

| File Path | RC4 Key |
| --- | --- |
| work/mnt/hgfs/Desktop/New folder (2)/DboRrmSS.exe | QGWYqgYFSXgkEvmll |
| work/mnt/hgfs/Desktop/New folder (2)/m01QzOfI.exe | bVeuvyKwejaykgTg |
| work/mnt/hgfs/Desktop/New folder (2)/voS9AyMZ.tar.gz | YPJngQShuxKNEcPw |
| work/home/user/.cache/vmware/drag_and_drop/0pkbW4/3Powwovv.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/gWMDML/GnAN3FhY.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/QqiN9h/DboRrmSS.exe | QGWYqgYFSXgkEvmll |
| work/home/user/.cache/vmware/drag_and_drop/rM0FG0/m01QzOfI.exe | bVeuvyKwejaykgTg |
| work/home/user/.cache/vmware/drag_and_drop/5wdgDr/payload.bin | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/6bX9mm/Black.x64.exe | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/NDBu65/payload.bin | xYHuKSOLEkSzrxVq |
| work/home/user/.cache/vmware/drag_and_drop/yf91yD/payload.bin | bVeuvyKwejaykgTg |
| work/home/user/.cache/vmware/drag_and_drop/zlLWeR/payload.bin | xYHuKSOLEkSzrxVq

caption - Shellcode RC4 keys

Notably, the Cobalt Strike Beacons deployed via this shellcode patch ETW and AMSI functions to evade detection. The encrypted beacon configuration was truncated, preventing a full analysis of its contents.

1.3.3. Cobalt Strike Beacon

The source code for a Cobalt Strike Beacon was found under work/mnt/hgfs/Desktop/111/beacon. Upon execution, it decrypts its configuration using a single-byte XOR key (0x2e). A portion of the decrypted configuration, as parsed by CobaltStrikeParser, is shown below.

CobaltStrikeParser output

caption - CobaltStrikeParser output

Although the configuration contains a C&C server address and endpoint, the beacon ignores these and uses hardcoded values instead.

send_Metadata function in comm.cpp

caption - send_Metadata function in comm.cpp

After parsing the configuration, the beacon generates metadata to send to the C&C server. Key values are listed below.

| Variable Name | Description |
| --- | --- |
| beacon_key | Random 16-byte session key |
| codepage | 65001 (hardcoded) |
| oem | 65001 (hardcoded) |
| beacon_id | Random 4-byte agent identifier |
| MajorVersion | Major version (from uname output) |
| dwBuildNumber | Minor version (from uname output) |
| build | Patch version (from uname output) |
| machine | 2 for x86_64, 0 otherwise |
| hostinfo | IP address |
| ComputerName | Hostname |
| UserName | Username |
| ProcessName | Basename of the program

caption - Metadata fields

After sending the initial metadata, it begins polling for commands from the C&C server via HTTP GET requests every 5 seconds and sends the results back via HTTP POST. The supported command ids are listed below.

| id | Function Name | Status | Description |
| --- | --- | --- | --- |
| 5 | cd | Functional | Changes the current directory |
| 10 | upload | Disabled | File upload-related code is commented out |
| 11 | download | Disabled | File download-related code is commented out |
| 12 | execute | Unimplemented | The main function BeaconExecuteCommand is undefined, related code is commented out |
| 14 | proxy_connect | Unimplemented | Call to undefined function is commented out |
| 15 | proxy_write | Unimplemented | Call to undefined function is commented out |
| 16 | proxy_close | Disabled | Code to set the global gBeaconRportfwd.state = 0 is commented out |
| 17 | proxy_listen | Unimplemented | Call to undefined function is commented out |
| 39 | pwd | Functional | Returns the current directory |
| 53 | ls | Functional | Lists the files in the current directory |
| 100 | bof | Unimplemented | Call to undefined function is commented out

caption - Cobalt Strike Beacon commands

The majority of commands are either unimplemented or disabled, suggesting this is an incomplete version of the beacon.

1.3.4. C# Loader

The directory work/home/user/Desktop/0128.zip contained a C# loader (ok.dll), its source code (ok.cs), and a runner script (ok.sct) designed to inject a Cobalt Strike Beacon into another process.

The frequent use of the word "test" in function names and arguments suggests this tool was experimental.

ok.hta

The ok.hta file is an HTML Application that downloads a script from hxxp://192.168[.]123.200/ok.sct and calls its Fuk method.

ok.hta content

caption - ok.hta content

The downloaded script is presumed to be the ok.sct file found in the same archive.

ok.sct

This script deserializes an embedded byte array into a C# object and invokes its Work method. This object is presumed to be the ok.dll loader.

C# Loader 역직렬화 코드

caption - deserialization code in C# Loader

The C# loader injects a Cobalt Strike Beacon into a conhost.exe process. The beacon payload is stored as a zlib-compressed and Base64-encoded string.

Cobalt Strike Beacon 디코딩 함수

caption - Cobalt Strike Beacon decode function

Below is a portion of the beacon's configuration, extracted using CobaltStrikeParser.

C# Loader에 저장된 Cobalt Strike Beacon 설정 정보

caption - CobaltStrikeParser output

1.4. Ivanti Connect Secure

1.4.1. BRUSHFIRE

The zip file at work/mnt/hgfs/Desktop/ivanti-new-exp-20241220.zip contains Python scripts that leverage an Ivanti Connect Secure RCE exploit to deploy a backdoor.

The script crafts a clientCapabilities value to trigger CVE-2025-0282 and achieve RCE.

exploit in exp*.py

caption - exploit in exp*.py

CVE-2025-0282 has been exploited by UNC5221, a suspected China-nexus threat actor.

Multiple versions of these scripts were found, all functionally equivalent with minor variations in function offsets and service endpoints. The command-line arguments accepted by the scripts are listed below.

| Long Option | Short Option | Description |
| --- | --- | --- |
| ip | i | Target IP address |
| port | p | Target port |
| leak |  | Leak memory info |
| detect_version |  | Check if the server is vulnerable |
| install |  | Install backdoor |
| check |  | Check config |
| check_path |  | Check if the exploit works |
| delay |  | Set the timeout |
| check_backdoor |  | Check for backdoor |
| cmd |  | Execute command as root |
| python |  | Execute Python script using perl-static |
| perl |  | Execute Perl script using perl-static |
| lcmd |  | Execute command |
| version |  | Read /home/ssl-vpn-VERSION |
| config |  | Execute dsls -B -R -S / |
| local_path |  | Path to upload |
| upload |  | Upload file |
| download |  | Download file |
| clear_log |  | Clear logs |
| clean |  | Clear tracks and uninstall |
| verbose | v | Unused

caption - Command-line options

The script establishes a random 3-byte magic value and a random 4-byte key for each host. It uses the RCE exploit to execute plugins/install, which overwrites the legitimate ssl_read function with the backdoor from plugins/ssl_read. During communication, when the backdoor receives a payload starting with themagic value, it XOR-decrypts the rest of the payload with the key and executes it as shellcode.

caption - shellcode execution code in ssl_read

The script generates and uploads different shellcode payloads to the target server based on the provided options. It uses the replace method to patch the shellcode, adapting hardcoded placeholder values for the target environment. The table below details the options that deploy shellcode from the plugins directory and the purpose of each payload.

| Option | Shellcode Name | Description |
| --- | --- | --- |
| install | install | Overwrites ssl_read with the ssl_read backdoor shellcode |
| install | leak_info | Leaks function addresses needed for the inject shellcode |
| install | inject | Overwrites ssl_read using the leaked addresses |
| install | patch | The strncpy function used during ssl_read injection |
| version, download | read_file_asm | Reads a file, encrypts it with the key, and sends it |
| python, perl, upload | write_file_asm | Receives data, decrypts it with the key, and saves it to a file |
| upload, lcmd, cmd, config | cmd_asm | Executes a command, encrypts the output with the key, and sends it

caption - Options and corresponding shellcode

The plugins/ssl_read backdoor and its associated file paths match the description of the BRUSHFIRE malware used by UNC5221, as detailed in this report.

1.4.2. SPAWN Family Client

The zip file at work/mnt/hgfs/Desktop/New folder/203.234.192.200_client.zip was found to contain a tunneling script, an SSH client, and an SSH private key. The included readme.txt file contains instructions for using client.py and controller.py to connect to 203.234[.]192.200, an IP address belonging to the South Korean news organization Hankyoreh.

content of readme.txt

caption - content of readme.txt

client.py uses a SOCKS5 proxy to tunnel network traffic and accepts the following options:

| Option | Description | Default |
| --- | --- | --- |
| -h | local socks5 bind address, default: 127.0.0.1 | 127.0.0.1 |
| -p | local socks5 bind port, default: 1080 | 1080 |
| -H | Pulse Connect Secure address/hostname | raddr |
| -P | Pulse Connect Secure port, default: 443 | 443 |
| -v | enable verbose logging |  |
| --ca | CA certificate, default: use embeded CA |  |
| --cert | client certificate, default: ./client.crt |  |
| --key | client private key, default: ./client.key |  |

caption - Option descriptions

client.py initializes values for client_hello and client_key_exchange packets, which are used as follows:

  • client_hello: Before transmission, random data is written to client_hello[15:43], the CRC32 checksum of this data is written to client_hello[11:15], and a random 32-byte sessionid is included in the message.

  • client_key_exchange: Before transmission, a random 256-byte value is written to the premaster field, and a random 32-byte value is written to the enc_handshake_msg field.

The initial hardcoded value of client_hello is identical to the magic packet in the SPAWNMOLE sample detailed in this UNC5221 report. The runtime modification—writing random data and its CRC32 checksum—is consistent with the behavior of the SPAWNCHIMERA sample described in JPCert's report.

Like SPAWNMOLE, SPAWNCHIMERA was deployed by UNC5221 using CVE-2025-0282 as an initial access vector.

controller.py is an SSH client that uses the SOCKS5 proxy established by client.py for communication.

ssh 서버에 접속하는 controller.py

caption - SSH client code in controller.py

SPAWNMOLE was discovered alongside SPAWNSNAIL, which is capable of running an SSH server. SPAWNCHIMERA also includes SSH server functionality. Therefore, controller.py is likely a client for the SSH server component of either SPAWNSNAIL or SPAWNCHIMERA.

1.4.3. ROOTROT Client

The script at work/mnt/hgfs/Desktop/ivanti_control/main.py functions as a client for a webshell. Depending on the options provided, it generates a Perl script fragment, Base64-encodes it, sets a cookie to the encoded value, and sends an HTTP GET request. The options and their corresponding scripts are listed below.

  • download {file_path}

    my $s = "";
    my $path = "{file_path}";
    local *FH;open(*FH, "<". $path);
    while (<FH>) { $s.= "$_";}
    close(*FH);
    print  "<!-- ".MIME::Base64::encode_base64($s,"")."-->"
    
    
  • {command}

    my $l ="";
    my $r = "";
    my $fd = popen(*PIPE,"{command}",\\\\'r\\\\') ;
    while (<PIPE>) {$l.= $_;};
    $r.= MIME::Base64::encode_base64($l,"");
    my $s = "<!-- ". pack("C*", unpack("C*", $r));$s.= "-->";
    print $s
    
    

Both Perl scripts Base64-encode their output and wrap it in HTML comments. The main.py client then extracts the content of the last comment in the HTTP response, Base64-decodes it, and prints the result.

요청을 보내고 주석의 내용을 디코딩하는 함수

caption - The function that sends the request and decodes the response

ROOTROT, as described in this UNC5221 report, is a Perl-based web shell that Base64-decodes a cookie value before executing it with eval. It then sends the Base64-encoded results back within HTML comments. This behavior confirms that main.py is the client for the ROOTROT web shell.

Like the other UNC5221 malware mentioned, ROOTROT has been found in attacks targeting Ivanti Connect Secure vulnerabilities.

1.5. Phishing Attacks

1.5.1. Naver AitM Attack

Files related to an Adversary-in-the-Middle (AitM) attack targeting Naver credentials were found in work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing. The file work/mnt/hgfs/Desktop/New folder/readme.txt contains a configuration guide written in Chinese.

content of readme.txt

caption - content of readme.txt

Below is a machine translation of

readme.txt:

Bastion VPS Configuration
Configure wildcard domain certificate
apt install snapd
apt install certbot
For details, see <https://www.digitalocean.com/community/tutorials/how-to-acquire-a-let-s-encrypt-certificate-using-dns-validation-with-acme-dns-certbot-on-ubuntu-18-04>

certbot certonly  -d "*.nid-security.com" -d nid-security.com --manual --preferred-challenges dns-01  --server <https://acme-v02.api.letsencrypt.org/directory>
In forward.conf, replace `Require ip 27.255.80.170` with the phishing VPS address
In default-ssl.conf, replace `/etc/letsencrypt/live/nid-security.com/` with the path to your own generated certificate
Replace the IP in ProxyPass and ProxyPassReverse with the backend phishing VPS address
Place default-ssl.conf and forward.conf into the tomcat configuration folder sites-available
Replace /etc/apache2/ports.conf with ports.conf
a2enmod ssl
a2enmod headers
a2enmod proxy proxy_http proxy_connect
a2ensite forward.conf
systemctl restart apache2

Phishing VPS environment configuration
Install Python dependencies
apt install python3-pip
pip install -r requirements.txt
In the config folder, modify the `server` field in naverconfig.py to the actual phishing bare domain, for example `navertest.com`. The phishing subdomain is fixed as `nid.navertest.com`
Modify AUT_OK_302 to the actual redirect address
Run
python .\\\\

The script work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/cipherginx.py acts as a proxy between Naver and the victim. When the victim accesses Naver through this proxy, their headers, cookies, username, and password are saved to the cookies directory under the following filenames:

  • {nid_id}.headers

  • {nid_id}.cookie

  • accounts.txt

accounts.txt writer in cipherginx.py

caption - accounts.txt writer in cipherginx.py

The configuration file work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/naverconfig.py contains a malicious JavaScript payload. The proxy injects this payload into the Naver login page to capture and exfiltrate user credentials.

Naver credential exfiltration code in naverconfig.py

caption - Naver credential exfiltration code in naverconfig.py

Scripts using the exfiltrated credentials to steal the victim's email, contacts, account details, and other data were found at the following paths:

| File Path | Description |
| --- | --- |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/download.py | Collects emails, contacts, and login history. |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/gen_eml.py | Packages collected emails into EML files. |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/manuel_download.py | Manually triggers email collection for a given nid. |
| work/mnt/hgfs/Desktop/New folder/vps2/Cipherishing/downloader/models.py | Logs exfiltrated credentials and mailSN to his.db to prevent duplicates

caption - File descriptions

1.5.2. Kakao Login Phishing Attack

The file vps/var/www/html/kakao-login.php is a phishing page that spoofs the Kakao login portal. The HTML template for this page is located at vps/var/www/html/templates/kakao.html.

On a GET request, kakao-login.php checks if a cookie named ft is set. If the cookie exists, it redirects the user to https://mail.daum.net.

GET request handler

caption - GET request handler

If the ft cookie is not set, the script displays the fake Kakao login page. When a user attempts to log in, the form submits the entered credentials via a POST request.

Upon receiving a POST request, the script saves the credentials to log/password_log.txt and sets the ft cookie to no.

POST request handler

caption - POST request handler

Although password_log.txt contains 14 entries, all but one appear to be test data (e.g., (123123, 1212121212), (ttttt222, 2323232323).

1.5.3. Email Phishing Attack

Web server code for generating phishing emails and managing victim logs was found under vps/var/www/html.

The web server scripts save logs, and all except generator.php use a common format.


For parameters, only the xml parameter is treated specially, as detailed in the request.php section. Log files are saved with the format {name}.txt.{%Y%m%d}.

generator.php

vps/var/www/html/generator.php is a page for generating phishing emails. Its behavior depends on the value of the cookie HnoplYTfPX and the request method.

| Cookie HnoplYTfPX Exists | Request Method | Behavior |
| --- | --- | --- |
| No | POST | Compares the passwd parameter with "hkjOPQIBBgU". If it matches, sets the cookie HnoplYTfPX. |
| Yes | GET | Shows the phishing email generation form. |
| Yes | POST | Shows the generated phishing email content

caption - Execution logic

The phishing email generation form contains the following fields.

| Name | Label | HTML Tag | Default Value |
| --- | --- | --- | --- |
| to_email_address | to email_address | input type="text" |  |
| onlysniff | only sniff | input type="checkbox" |  |
| phishing_url | Phishing URL | input type="text" | https://nid.navermails.com/nidlogin.login?mode=form&url= |
| redirect_url | Redirect URL | input type="text" | https://bigfile.mail.naver.com/bigfileupload/download?fid=9leqBrJ4Wrdjb4kOM6kmK3Y/aAu/KIYZKAurKxUwFxgXKxbmKCYwFA2dFAg9axvlHruqMq0opx2wazUlMovlFzK/K4MlKqMlp6JoKx0CMq04 |
| content | html content %img% %link_url% | textarea |  |
| saveForm |  | input type="submit" | Submit

caption - Phishing email generation form fields

The generated phishing email components are displayed as follows.

| Title | Value |
| --- | --- |
| dot image url: | An img tag pointing to {base_url}/request.php?i={b64encode(to_email_address)}&dot.png. |
| redict url: | {base_url}/bigDataDownload/{b64encode(to_email_address)}. |
| link url: | {phishing_url}{urlencode(redirect_url)}&login_id={b64encode(to_email_address)}{"&f=yes" if onlysniff else ""}. |
| content: | The content field with %img% and %link_url% substituted with the values above

The value under content: is presumed to be the email body. Each generation is logged to log/generator_log.txt.{%Y%m%d} in the format below.

caption - Generated phishing email components

--------{%B %d, %Y, %I:%M %p}----------------
[to_emailaddress]_[b64encode(to_email_address)]
[link url]
[client ip]

If the victim's email client is configured to load external resources, the img tag triggers a request to request.php.

request.php

vps/var/www/html/request.php logs victim system information to log/request_log.txt.{%Y%m%d}.

If the request URI contains .png, the script returns a randomly colored image. Otherwise, it returns a page containing JavaScript to gather information about the victim's system and sends the results as URL parameters.

| Script Path | Collected Information |
| --- | --- |
| payload/adobe.js | Adobe Reader version (navigator.plugins/ActiveX) |
| payload/flash.js | Adobe Flash version |
| payload/java.js.bak | JRE versions |
| payload/plugin.js | Plugins and versions (navigator.plugins) |
| payload/xecure.js | XecureWeb version |
| payload/ie/drive.js.bak | Drive letters (via FilesystemObject, WMIC, etc.) |
| payload/ie/office.js | Microsoft Office version (via Word.Application COM/ActiveX) |
| payload/ie/pdf.js | Adobe Reader (via AcroPDF.PDF ActiveX) |
| payload/ie/xecure.js | XecureWeb ActiveX version |
| payload/ie/xmldom.js | Installed programs |
| payload/non-ie/webrtc.js.bak | Local and public IP addresses (via WebRTC)

caption - JavaScript files and collected information

The script then redirects to response.php?i={b64encode(email)}&{results}.

response.php The script at vps/var/www/html/response.php inspects the victim's IP address, user-agent, and locale. If these attributes meet specific criteria, the script Base64-decodes the i parameter and logs the result to log/response_log.txt.{%Y%m%d}. The conditions are as follows:

  1. The locale is on a whitelist.

  2. The IP address and user-agent are not on a blacklist.

The locale whitelist includes:

  • us

  • jp

  • ja

  • kr

  • ko

The user-agent blacklist includes:

  • bot

  • spider

  • crawl

  • trend

  • symantec

  • virus

  • spam

  • secure

The IP blacklist is detailed below.

| IP range | Info | IP range | Info |
| --- | --- | --- | --- |
| 103.246.39.0/24 | bluecoat | 222.122.128.0/24 | Korea Telecom KORNET |
| 104.132.0.0/14 | Google | 222.122.185.0/24 | Korea Telecom KORNET |
| 121.156.79.0/24 | Korea Telecom KORNET | 23.192.0.0/11 | Akamai |
| 125.141.228.0/24 |  | 52.84.0.0/14 | Amazon |
| 15.211.153.0/24 | HP | 52.88.0.0/13 | Amazon |
| 150.70.0.0/16 | Trend Micro | 54.192.0.0/16 | Amazon |
| 182.162.206.103 | with symantec client v1 | 59.12.199.252 |  |
| 182.162.206.39 | with symantec client v2 | 59.18.85.26 |  |
| 183.99.48.26 | 20170124 fake chrome | 61.98.76.115 |  |
| 199.19.248.0/21 | bluecoat 199.19.248.0/21 | 61.111.0.0/18 | LG DACOM KIDC |
| 200.63.47.32/32 | AboveNet Engineering [ipeng@zayo.com](mailto:ipeng@zayo.com) | 66.102.0.0/20 | Google |
| 210.121.169.0/24 |  | 66.249.64.0/19 | Google |
| 211.115.119.0/24 | jnjedu | 72.13.86.0/24 | ??echosting |
| 211.233.80.0/ | Ahnlab | 83.223.96.0/19 | Gyron |
| 211.55.19.0/24 | KT CUSTOMER | 89.145.108.0/22 | Gyron |
| 216.104.0.0/19 | Trend Micro | 89.148.104.0/21 | DUNAWEB_89.148.104.0/21 |
| 218.153.8.0/24 |  | 115.90.74.131 |  |
| 221.141.3.0/24 | SK Broadband Co Ltd | 211.249.40.0/24 | naver |
| 221.252.42.0/24 | NRI SecureTechnologies | 211.56.96.0/24 | kakao |
| 222.99.190.104 | 20170124 chrome alike testing

caption - IP blacklist

A summary of the log files, their corresponding scripts, and activity statistics is provided below.

| Log Path | Script | Suspected Victims / Total Logs |
| --- | --- | --- |
| log/generator_log.txt | generator.php | 220 / 315 |
| log/password_log.txt | kakao-login.php | 1 / 14 |
| log/request_log.txt | redirect.php, request.php | 42 / 536 |
| log/response_log.txt | response.php | 4 / 33

caption - Summary of all logs and actual victim logs

1.6. Yonsei University Email Exfiltration Malware

The script at vps/var/www/html/js/chks.js is designed to exfiltrate emails from Yonsei University's webmail service (mail.yonsei.ac.kr).

The script first collects user information from the endpoint https://mail.yonsei.ac.kr/common/json/agent.do. It then adds cimoon185@daum.net to the account's list of forwarding addresses.

chks.js Forward 함수

caption - Forward function in chks.js

After setting up the forwarding rule, it exfiltrates all emails dated since 2017-10-01 from the ent_{user} and TOTAL mailboxes. The emails are sent to https://service.navers.org/emuy.php?i={user} via POST requests. Notably, the script manually constructs the multipart/form-data request bodies instead of using the standard FormData API.

2. Analysis of Exfiltrated Data

This section analyzes data from the actor's VMware VM dump that is normally impossible or difficult to obtain, classifying it as "exfiltrated data."

2.1. Ministry of Foreign Affairs' AyersRock Mail

The file work/mnt/hgfs/Desktop/mofa.go.kr.7z contains the source code for the AyersRock Mail solution, which is believed to have been used internally by the South Korean Ministry of Foreign Affairs (MOFA). AyersRock Mail is a webmail solution developed by Naravision in 2016 based on its Kebi Mail platform.

The extracted directory structure consists of Kebi-based modules such as kebi-batch, kebi-cor, and kebi-web-mail. Within these directories, various app-*.xml configuration files contain mail elements with the host value set to mofa.go.kr.

Host value in the mail element of app-*.xml

caption - Host value in the mail element of app-*.xml

The file mofa.go.kr/kebi-web-parent/mail/document/info.txt contains account credentials and notes for internal development servers, databases, and test servers.

Content of info.txt

caption - Content of info.txt

Another file, mofa.go.kr/kebi-web-parent/mail/document/worklist.txt, contains notes on development schedules and task lists.

Content of worklist.txt

caption - Content of worklist.txt

The presence of Git-related files like .gitignore and .gitmodules, along with developer-authored notes in info.txt and worklist.txt, suggests that mofa.go.kr.7z is an archive of files exfiltrated from the developer's Git repository.

2.2. Secureki APPM & iRASS

2.2.1. APPM & iRASS Server Management Programs

The path work/mnt/hgfs/Desktop/111/home/home contains appm and irass directories, which house programs likely used for managing APPM and iRASS servers, respectively.

APPM

The work/mnt/hgfs/Desktop/111/home/home/appm directory contains the appm_master binary and a crypto directory with encryption-related modules. During execution, appm_master checks for the existence of several binaries and configuration files, many of which are missing from the dump. Key missing files include cloud configuration files and a decrypted appmkey file.

List of released files

caption - List of released files

iRASS

The work/mnt/hgfs/Desktop/111/home/home/irass/bin directory contains several iRASS-related binaries, including irass_master, irass_db, irass_admin, and irass_rdp. A history.txt file in this directory contains the keyword "KB국민은행"(KB Kookmin Bank) and appears to be a developer's log of issues and patches.

Content of history.txt

caption - Content of history.txt

2.3. Ministry of the Interior and Safety's GPKISecureWebX

2.3.1. GPKISecureWebX Source Code

The directory work/mnt/hgfs/Desktop/111/GPKISecureWebX contains the source code for GPKISecureWebX, a public certificate security program developed by Dream security.

A Visual Studio Build Log file, work/mnt/hgfs/Desktop/111/GPKISecureWebX/GPKISecureWebX.plg, contains the string "01\_행자부 웹보안API" (01_Ministry of Public Administration and Home Affairs Web Security API). "행자부" (Haengjabu) is the former name of the current Ministry of the Interior and Safety, linking this source code to the ministry's GPKI system.

caption - Content of GPKISecureWebX.plg

caption - Content of GPKISecureWebX.plg

Additionally, the file work/mnt/hgfs/Desktop/111/1.rar is a compressed archive of the work/mnt/hgfs/Desktop/111/GPKISecureWebX directory.

2.3.2. GPKISecureWeb Package

The file work/mnt/hgfs/Desktop/111/gpki.7z contains GPKI API, GPKISecureWebX, and setup directories. The archive includes manuals for the standard GPKI API, as well as various keys and certificate files.

Notably, the gpki/gpkisecureweb/log directory contains certificate validation logs from December 2017 to April 2020.

Part of the log content

caption - Part of the log content

2.3.3. GPKISecureWeb Documents and Module Source Code

The directory work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수 (01_Ministry of Public Administration and Home Affairs Web Security API (ORG) - Handover to Kwon Yu-mi) contains GPKISecureWeb-related documents for a personnel handover, along with the complete source code for all modules, including GPKIInstaller and GPKICertManager.

The following documents were identified:

  • work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/웹용 표준보안API_매뉴얼(유선).chm

  • work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/설계문서/SSA_AO_AD_WT_002_웹보안 프로토콜설계서_Ver1.0_.doc

  • work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/행자부 웹보안API 인수인계.doc

The file properties indicate that the documents were created a long time ago.

doc file info

caption - doc file info

The ReadMe.txt file at work/mnt/hgfs/Desktop/111/2/01_행자부 웹보안API(ORG) - 권유미 인수/02_src/ReadMe.txt provides a description of all the modules.

Content of ReadMe.txt

caption - Content of ReadMe.txt

2.4. GPKI Certificate and Private Key Files

The directory work/home/user/Downloads/cert/extracted-key-20200512 contains 2,475 certificate and private key files. The Subject field of the certificates revealed a variety of organizations, including the Ministry of Government Legislation ("법제처") and other government-affiliated agencies and committees. It is unclear how the actor collected these files.

File listing of work/home/user/Downloads/cert/extracted-key-20200512

caption - File listing of work/home/user/Downloads/cert/extracted-key-20200512

2.5. Onnara Login Automation Script

The directory work/mnt/hgfs/Desktop/111/onnara_auto contains a script that automates logins to Onnara, a service for public officials used by the South Korean government. The client_main.py script has a hardcoded account ID, organization ID, and subdomain for the Onnara service.

main function in client_main.py

caption - main function in client_main.py

The login method involves executing the onnaraSSO.jar file, located in work/mnt/hgfs/Desktop/111/onnara_auto/script, to generate an SSO token based on the provided information.

generate_L1 function in onnara_sso.py

caption - generate_L1 function in onnara_sso.py

The onnaraSSO.jar file encrypts or decrypts a string using either the ARIA or DES algorithm, depending on the options provided. The usage format is as follows:

  • onnaraSSO.jar <string> <0:DES | 1:ARIA> <0:encrypt | 1:decrypt>

The keys used for both ARIA and DES are hardcoded in the JAR file.

Traces of files transferred between the host and the VMware VM show that the actor left the output of a test run as comments in the onnara_sso_test.py file.

/work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara/onnara_auto/onnara_sso_test.py 파일 데이터

caption - Content of work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara/onnara_auto/onnara_sso_test.py

The directory work/home/user/.cache/vmware/drag_and_drop/Z3INst/onnara contains the file onnara9-onnara4-SSO-login.saz, which can be decompressed to reveal actual HTTP requests and responses.

Content of onnara9-onnara4-SSO-login.saz/raw/001_c.txt

caption - Content of onnara9-onnara4-SSO-login.saz/raw/001_c.txt

3. Actor and System Information

This chapter analyzes information about the threat actor and their system environment based on the disclosed data.

3.1. System Information

3.1.1. deepin

The actor's VMware VM ran deepin, a Linux distribution developed by a Chinese company primarily targeted at the Chinese market.

While independently verifiable data on user numbers is limited, deepin reported that it had "more than 3 million users worldwide" in December 2022 and claimed in September 2024 that it had "over 5.4 million loyal users, nearly 3 million of whom are from overseas". However, the lack of external verification casts doubt on the accuracy of these claims.

Obtaining statistics for Linux distributions is difficult, as the user base tends to oppose telemetry and data collection, and the small market for commercial desktop software limits market research. However, publicly available data can provide a rough estimate of the popularity of various distributions. The table below summarizes search volume, Stack Overflow Developer Survey results, and self-reported statistics for several distributions.

| Distribution | Google Trends (Topic, relative to Ubuntu) | Ahrefs Search Traffic | Semrush | Subreddit weekly visitors/members | Stack Overflow (personal/work) | self-reported users |
| --- | --- | --- | --- | --- | --- | --- |
| Ubuntu | 16 | 1.4M-n(ref:https://ahrefstop.com/websites/ubuntu.com) | 3.9M | 251K / 258K | 27.8% / 27.7% | 6M+(2023)-n(ref:https://discourse.ubuntu.com/t/ubuntu-desktop-charting-a-course-for-the-future/38092), 2762K(2017 popcon)-n(ref:https://web.archive.org/web/20171222174904/https://popcon.ubuntu.com/by_inst) |
| Debian | 5 | 297.1K-n(ref:https://ahrefstop.com/websites/debian.org) | 1.1M | 158K / 115K | 11.4% / 10.4% | 265K(2025)-n(ref:https://popcon.debian.org/) |
| Arch Linux | 3 | 342.0K-n(ref:https://ahrefstop.com/websites/archlinux.org) | 1.3M | 271K / 324K | 9.7% / 4.6% |  |
| Linux Mint | 2 | 492.4K-n(ref:https://ahrefstop.com/websites/linuxmint.com) | 1.3M | 172K / 150K |  | 98K(2025)-n(ref:https://blog.linuxmint.com/?p=4811) |
| Fedora | 2 | 222.0K-n(ref:https://ahrefstop.com/websites/fedoraproject.org) | 582.5K | 156K / 142K | 5.8% / 3.7% | 323.3K+(2025)-n(ref:https://discussion.fedoraproject.org/t/fedora-usage-stats/144635/16) |
| Red Hat | 2 | 1.5M-n(ref:https://ahrefstop.com/websites/redhat.com) | 2.5M | 22K / 49K | 1.8% / 5.7% |  |
| CentOS | 1 |  | 208.4K | 3.5K / 16K |  |  |
| NixOS | 1 |  | 142K | 46K / 44K | 3.4% / 1.8% |  |
| openSUSE | <1 |  | 136.7K | 28K / 41K |  | 269K(15.6), 186K(tumbleweed 2025)-n(ref:https://metrics.opensuse.org/d/osrt_access) |
| Gentoo | <1 |  | 27.5K | 22K / 33K |  |  |
| Rocky Linux | <1 |  | 75.7K | 4.2K / 10K |  |  |
| AlmaLinux | <1 |  | 38.1K | 3.7K / 7.3K |  |  |
| deepin | <

caption - Distribution Statistics

Based on these metrics, it is unlikely that deepin's user base outside of China approaches 3 million. Given the lack of independent verification, the number is more likely in the thousands. Furthermore, deepin is associated with the following issues:

  • Policies and Regulations: The EULA and Privacy Policy state that deepin collects personal information, "may collect and use your personal information without seeking your consent," and that matters "shall be governed by the laws of the P.R.C.".

  • History of Data Collection Controversies: deepin previously faced criticism for using CNZZ Analytics in its App Store. After clarifying that its function was similar to Google Analytics and announcing its removal, the company later quietly added Umeng+ Analytics.

  • Security: The deepin desktop environment has been criticized for recurring security issues and attempts to circumvent security validation, leading to its removal from openSUSE in May 2025.

In summary, the probability of a user outside of China choosing or using deepin is extremely low.

3.1.2. IME

Traces of multiple Chinese Input Method Editors (IMEs) were found on the VMware VM. As deepin is primarily targeted at the Chinese market, fcitx-pinyin, fcitx-sunpinyin, and fcitx-table-wubi are installed by default. If the system is installed with Chinese as the selected language, wubi, shuangpin, pinyin, and sunpinyin are enabled; if English is selected, they are disabled. Almost all of the locale-dependent features in configuration files are set to English, suggesting it was installed as English.

List of fcitx packages in the default installation

caption - List of fcitx packages in the default installation

Additional IME installations were identified:

  • fcitx plugins: Traces of huayupy, wbpy, rime, and iflyime being installed were found. The enabled input methods were wbpy and pinyin.

  • ibus plugins: Traces of rime being installed and used were identified.

  • SogouPY: Evidence of additional configuration for Sogou Pinyin was present.

diff of EnabledIMList in the default settings and work/home/user/.config/fcitx/profile

caption - diff of EnabledIMList in the default settings and work/home/user/.config/fcitx/profile

3.1.3. Other Artifacts

Installation and Locale Information

Almost all of the locale-dependent features in configuration files are set to English, indicating the system was configured as English. The content of work/home/user/.config/locale.conf is shown below.

Content of work/home/user/.config/locale.conf

caption - Content of work/home/user/.config/locale.conf

However, messages in logs such as work/var/log/{access.log*,vmware-network.*.log} are localized in Korean.

Content of work/var/log/vmware-network.1.log

caption - Content of work/var/log/vmware-network.1.log

deepin Configuration Changes and Activity Traces

The file work/home/user/.config/deepin/dde-desktop/dde-desktop.conf references a ProtonVPN OpenVPN profile, though the profile itself was not present in the dump.

Content of work/home/user/.config/deepin/dde-desktop/dde-desktop.conf

caption - Content of work/home/user/.config/deepin/dde-desktop/dde-desktop.conf

The file work/home/user/.config/deepin/dde-file-manager/dde-file-manager.obtusely.json contains a history of file access, including paths with personal information, as well as connections to remote and public shares. Examples include:

  • standard://downloads/cert/gpki_cert/*

  • file:///mnt/hgfs/share_data/GitHack/www.caa.org.tw/*

  • ftp:host=192.168.50.1:21212

In work/home/user/.config/deepin/dcc-weather-plugin.conf, the Location was set to Waterloo, Canada.

Content of work/home/user/.config/deepin/dcc-weather-plugin.conf

caption - Content of work/home/user/.config/deepin/dcc-weather-plugin.conf

Other application traces include:

  • FileZilla: work/home/user/.config/filezilla/recentservers.xml suggests access to ftp.fu-berlin.de, a public FTP mirror.

  • Foxit Reader: A configuration file at work/home/user/.config/Foxit Software/Foxit Reader.conf referenced a file path containing an email address: tmp/kclee/hiaei84@gmail.com.pst_pst_*.msg.

3.2. Browser Information

An analysis of all browser profiles under work/home/user revealed that the primary profile was work/home/user/.config/google-chrome/Default. The table below lists all browser profiles and their identified activity periods.

| Profile Path | Activity Period |
| --- | --- |
| work/home/user/.config/BraveSoftware/Brave-Browser/Default | 2021-05-18 07:15:25.107 ~ 2021-05-18 07:25:22.279 |
| work/home/user/.config/browser/Default | 2021-11-15 17:54:39.645 ~ 2021-11-15 17:55:58.790 |
| work/home/user/.config/chromium/Default | 2019-05-17 05:52:04.363 ~ 2020-12-15 09:21:59.050 |
| work/home/user/.config/google-chrome/Default | 2020-12-02 01:56:37.844 ~ 2025-06-06 04:41:48.697 |
| work/home/user/.mozilla/firefox/06yb5px0.default-release | 2020-12-21T15:59:20.598000 ~ 2021-01-04T17:09:34.052025 |
| work/home/user/.mozilla/firefox/l8g1zjqb.default | Created 2020-12-21T15:59:20.146000 (unused) |
| work/home/user/.thunderbird/3qg2p785.default-default | est. 2021-04-13T11:39:36.277000 ~ 2021-04-13T12:22:31.051914 |
| work/home/user/.thunderbird/3s68zwb6.default | Created 2020-12-21T11:44:07.857000 (unused) |
| work/home/user/.thunderbird/8inzqqf5.default-release | est. 2023-12-25T11:47:56.099000 ~ 2023-12-27T10:18:21.103

caption - Activity periods

The earliest record in the work/home/user/.config/google-chrome/Default profile is from 2020-12-02 01:56:37.844, with browsing history present from 2025-03-18 04:04:20.873 to 2025-05-28 07:11:01.791. The other profiles were either unused or showed only short-term activity.

3.2.1. Language

Historical versions of work/home/user/.config/google-chrome/Default/Preferences were preserved in files named .com.google.Chrome.*.

| file name | profile.last_engagement_time |
| --- | --- |
| .com.google.Chrome.yf0Cko | 2020-04-23T10:28:01.605486 |
| .com.google.Chrome.wNxXMb | 2021-04-08T03:15:41.989060 |
| .com.google.Chrome.oM0Y0k | 2021-05-18T07:46:52.497051 |
| .com.google.Chrome.qTDg5z | 2025-04-10T16:13:27.859136 |
| .com.google.Chrome.m6662X | 2025-05-20T00:26:05.876747 |
| .com.google.Chrome.LajSSi | 2025-05-22T00:31:18.814059 |
| Preferences | 2025-05-28T07:11:01.793570

caption - last_engagement_time data

The timeline of changes is as follows:

  • Before 2020-04-23T10:28:01.605486: intl.*_languages was set to English and Chinese. Translation for Chinese was blocked (translate_blocked_languages).

  • Before 2021-04-08T03:15:41.989060: Korean was added, and translation for Korean was blocked.

  • Before 2025-04-10T16:13:27.859136: Translation for English was also blocked.

The actor primarily accepted translation prompts for English and Korean pages, with the target translation language set to Chinese. There is no record of accepting translations from Chinese to any other language.

The actor mainly visited English and Chinese websites, with visits to Korean sites occurring until 2021-04-08. After 2025-04-10, visits to Korean sites were rare.

3.2.2. history

The work/home/user/.config/google-chrome/Default profile was used for both operational and non-operational activities. In addition to work-related browsing for vulnerabilities and malware, the history shows activity on forums and other sites.

A selection of visits related to Cobalt Strike is shown below.

| First Visit | URL | Title |
| --- | --- | --- |
| 2025-03-24 05:25:47.145 | https://github.com/TH3xACE/EDR-Test | GitHub - TH3xACE/EDR-Test: Automating EDR Testing with reference to MITRE ATTACK via Cobalt Strike \[Purple Team]. |
| 2025-03-25 08:37:57.366 | https://github.com/georgesotiriadis/Chimera | GitHub - georgesotiriadis/Chimera: Automated DLL Sideloading Tool With EDR Evasion Capabilities |
| 2025-03-26 05:17:51.538 | https://github.com/kyleavery/AceLdr | GitHub - kyleavery/AceLdr: Cobalt Strike UDRL for memory scanner evasion. |
| 2025-04-07 01:13:41.868 | https://github.com/tomcarver16/BOF-DLL-Inject | GitHub - tomcarver16/BOF-DLL-Inject: Manual Map DLL injection implemented with Cobalt Strike's Beacon Object Files. |
| 2025-04-10 01:49:21.313 | https://github.com/Cobalt-Strike/ProxyDLLExample | GitHub - Cobalt-Strike/ProxyDLLExample: code for the Proxy DLL example blog post |
| 2025-04-16 04:49:48.157 | https://github.com/n3k7ar91/Cobalt-Strike-EDR-AV-bypass | GitHub - n3k7ar91/Cobalt-Strike-EDR-AV-bypass: Various resources to enhance Cobalt Strike's functionality and its ability to evade antivirus/EDR detection |
| 2025-04-29 02:04:12.124 | https://github.com/CrackerCat/CobaltStrike-KunKun | GitHub - CrackerCat/CobaltStrike-KunKun: 坤坤 CS 基于 CobaltStrike cat 4.5 二开项目 添加反沙箱、反测绘。集成常用后渗透插件,开箱即用(GitHub - CrackerCat/CobaltStrike-KunKun: KunKun CS is a CobaltStrike cat 4.5 based project that adds anti-sandbox and anti-mapping capabilities. It also integrates common post-exploitation plugins and is ready to use.) |
| 2025-04-29 02:04:46.756 | https://github.com/D13Xian/CobaltStrike-KunKun | GitHub - D13Xian/CobaltStrike-KunKun: 坤坤 CS 基于 CobaltStrike cat 4.5 二开项目 添加反沙箱、反测绘。集成常用后渗透插件,开箱即用(GitHub - D13Xian/CobaltStrike-KunKun: KunKun CS is a CobaltStrike cat 4.5 based project that adds anti-sandbox and anti-mapping capabilities. It also integrates common post-exploitation plugins and is ready to use.) |
| 2025-05-27 01:14:48.105 | https://github.com/RedefiningReality/Cobalt-Strike | GitHub - RedefiningReality/Cobalt-Strike: Various resources to enhance Cobalt Strike's functionality and its ability to evade antivirus/EDR detection |

caption - Cobalt Strike-related browsing history

A selection of visits related to rootkit is shown below.

| first visit | url | title |
| --- | --- | --- |
| 2025-03-24 00:57:27.819 | https://github.com/D4stiny/spectre | GitHub - D4stiny/spectre: A Windows kernel-mode rootkit that abuses legitimate communication channels to control a machine. |
| 2025-03-24 00:57:30.161 | https://github.com/TheMalwareGuardian/Bentico | GitHub - TheMalwareGuardian/Bentico: Windows Kernel Mode Rootkit |
| 2025-03-24 00:57:33.816 | https://github.com/eversinc33/Banshee | GitHub - eversinc33/Banshee: Experimental Windows x64 Kernel Rootkit with anti-rootkit evasion features. |
| 2025-03-24 00:57:35.016 | https://github.com/alal4465/Win_Rootkit | GitHub - alal4465/Win_Rootkit: A kernel-mode rootkit with remote control |
| 2025-03-24 01:27:30.574 | https://github.com/Nullxyzz/Windows-RootKit | GitHub - Nullxyzz/Windows-RootKit: Its not made by me, Its just a compilation of diffrent windows rootkits |
| 2025-03-24 01:39:51.900 | https://github.com/brosck/Frosty | GitHub - brosck/Frosty: 「🧊」Ring 3 Rootkit for Windows 10 |
| 2025-03-24 01:39:51.900 | https://github.com/MrEmpy/Frosty | GitHub - brosck/Frosty: 「🧊」Ring 3 Rootkit for Windows 10 |
| 2025-03-24 01:40:05.815 | https://github.com/Dol3v/Mark | GitHub - Dol3v/Mark: Windows kernel rootkit for the highschool's cyber track |
| 2025-03-24 01:40:07.609 | https://github.com/memN0ps/eagle-rs | GitHub - memN0ps/eagle-rs: Rusty Rootkit - Windows Kernel Rookit in Rust (Codename: Eagle) |
| 2025-03-24 01:40:32.416 | https://github.com/eLoopWoo/zwhawk | GitHub - eLoopWoo/zwhawk: A kernel rootkit with remote command and control interface for windows |
| 2025-03-24 01:46:34.018 | https://github.com/rakendrathapa/NetRootKit | GitHub - rakendrathapa/NetRootKit: Hide the TCP Connection |
| 2025-03-25 09:13:08.826 | https://github.com/Meowoverflow/Rootkits_Subverting_the_Windows_Kernel | GitHub - Meowoverflow/Rootkits_Subverting_the_Windows_Kernel: source code for the examples and topics from the book |
| 2025-03-25 09:16:46.105 | https://github.com/landhb/HideProcess | HideProcess,一个基本的直接内核对象操作 rootkit,它从 EPROCESS 列表中删除一个进程,并将其从任务管理器中隐藏起来(HideProcess, a basic direct kernel object manipulation rootkit that removes a process from the EPROCESS list and hides it from Task Manager) |
| 2025-04-03 02:36:21.421 | https://github.com/carloslack/KoviD | GitHub - carloslack/KoviD: Red-Team Linux kernel rootkit |
| 2025-04-03 02:48:13.341 | https://github.com/FunnyWolf | FunnyWolf (rootkit) · GitHub |
| 2025-04-10 04:37:45.062 | https://github.com/BeneficialCode/WinArk | GitHub - BeneficialCode/WinArk: Windows Anti-Rootkit Tool |
| 2025-04-10 05:10:11.795 | https://github.com/swwwolf/wdbgark | GitHub - swwwolf/wdbgark: WinDBG Anti-RootKit Extension |
| 2025-04-10 05:10:15.137 | https://github.com/eternalklaus/EternalAntirootkit | GitHub - eternalklaus/EternalAntirootkit: Anti-rootkit works as a Windows system driver. |
| 2025-04-10 05:10:43.093 | https://github.com/forentfraps/AntiRootkit | GitHub - forentfraps/AntiRootkit: Usermode rootkit scanner written in C x86 + x86_64 |
| 2025-04-10 06:45:19.255 | https://github.com/codetronik/KernelV | KernelV,Rootkit 和 Anti-rootkit,下载 KernelV 的源码*GitHub*帮酷 |
| 2025-04-17 01:03:23.699 | https://github.com/XaFF-XaFF/Black-Angel-Rootkit | GitHub - XaFF-XaFF/Black-Angel-Rootkit: Black Angel is a Windows 11/10 x64 kernel mode rootkit. Rootkit can be loaded with enabled DSE while maintaining its full functionality. |
| 2025-04-17 01:03:46.823 | https://github.com/XaFF-XaFF/Cronos-Rootkit | GitHub - XaFF-XaFF/Cronos-Rootkit: Cronos is Windows 10/11 x64 ring 0 rootkit. Cronos is able to hide processes, protect and elevate them with token manipulation. |
| 2025-04-17 01:08:20.491 | https://github.com/Idov31/Nidhogg | GitHub - Idov31/Nidhogg: Nidhogg is an all-in-one simple to use windows kernel rootkit. |
| 2025-04-17 01:08:22.483 | https://github.com/ColeHouston/Sunder | GitHub - ColeHouston/Sunder: Windows rootkit designed to work with BYOVD exploits |
| 2025-04-17 01:08:24.541 | https://github.com/ispoleet/malware | GitHub - ispoleet/malware: Some of my old malware |
| 2025-04-17 01:10:21.217 | https://github.com/joaoviictorti/shadow-rs | GitHub - joaoviictorti/shadow-rs: Windows Kernel Rootkit in Rust |
| 2025-04-17 01:14:23.331 | https://github.com/ZeroMemoryEx/Chaos-Rootkit | GitHub - ZeroMemoryEx/Chaos-Rootkit: Now You See Me, Now You Don't |
| 2025-04-17 01:14:28.746 | https://github.com/crvvdev/MasterHide | GitHub - crvvdev/MasterHide: A x64 Windows Rootkit using SSDT or Hypervisor hook |
| 2025-04-17 01:14:40.074 | https://github.com/ZeroMemoryEx/URootkit | URootkit,简单的用户模式 Rootkit,下载 URootkit 的源码*GitHub*帮酷(GitHub - kumar/URootkit: Simple user-mode rootkit. Download the source code for URootkit.) |
| 2025-04-17 01:14:58.455 | https://github.com/assarbad/Nidhogg | GitHub - assarbad/Nidhogg: Nidhogg is an all-in-one simple to use rootkit for red teams. |
| 2025-04-17 01:15:13.143 | https://github.com/adamhlt/Basic-Rootkit | GitHub - adamhlt/Basic-Rootkit: POC Ring3 Windows Rootkit (x86 / x64) - Hide processes and files |
| 2025-04-17 01:15:17.263 | https://github.com/huoji120/numen | GitHub - huoji120/numen: 简单安排一下 autochk.sys 这个 rootkit |
| 2025-04-27 05:33:00.318 | https://github.com/NoviceLive/research-rootkit | GitHub - NoviceLive/research-rootkit: LibZeroEvil & the Research Rootkit project. |
| 2025-04-27 05:40:00.848 | https://github.com/T1erno/ALPART | GitHub - T1erno/ALPART: Automatic Linux Persistence And Rootkit Tool |
| 2025-04-27 05:40:02.549 | https://github.com/MatthiasCr/LKM-Rootkit | GitHub - MatthiasCr/LKM-Rootkit: Malware to gain persistence on a victims machine. |
| 2025-04-27 05:45:31.565 | https://github.com/pythonmandev/rootkit_persistence- | rootkit*persistence-,专注于 Linux rootkit 的代码集合,下载 rootkit_persistence-的源码\_GitHub*帮酷(GitHub - rootkit_persistence/rootkit_persistence-: A collection of code focused on Linux rootkits. Download the source code.) |
| 2025-04-27 05:45:34.469 | https://github.com/DualHorizon/blackpill | GitHub - DualHorizon/blackpill: A Linux kernel rootkit in Rust using a custom made type-2 hypervisor, eBPF XDP and TC programs |
| 2025-04-27 05:51:12.876 | https://github.com/MatheuZSecurity/Rootkit | Rootkit,专注于 Linux rootkit 的代码集合,下载 Rootkit 的源码*GitHub*帮酷 |
| 2025-04-27 05:54:57.312 | https://github.com/killvxk/Rootkit-MatheuZSecurity | GitHub - killvxk/Rootkit-MatheuZSecurity: Collection of codes focused on Linux rootkits |
| 2025-05-20 05:11:12.591 | https://github.com/f0rb1dd3n/Reptile | GitHub - f0rb1dd3n/Reptile: LKM Linux rootkit |

caption - Rootkit-related browsing history

Visits to translate.google.* show that the actor frequently translated text from English and Korean documents, source code, and LLM responses into Chinese. Examples include:

Example from Google Translate history

caption - Example from Google Translate history

| timestamp | text |
| --- | --- |
| 2025-03-27 04:52:46.942 | 문서는 대한민국의 안전보장상 중요한      내용과 개인정보가 수록되어 있으므로 아래      \\n     사항을 준수해야  |
| 2025-04-03 00:54:16.358 | CryptoMalloc\\nEncrypt your RAM!!! Allows you to encrypt the "physical" memory of any process.\\n\\nHow does it work?\\nCryptoMalloc as the name suggest overloads the libc standard malloc function and replaces it with the one that will map process\\' virtual memory to a managed ANONYMOUS mapping. ... |
| 2025-04-22 08:35:59.740 | 무선용 비대칭키 암호 메시지를 처리한다.\\nParameters:\\nbaCert - 키분배용 인증서\\nbaPriKey - 키분배용 개인키\\nbaWapEnvData - 무선용 비대칭키 암호 멧시지\\nReturns:\\n성공시 0 리턴 (baReturnArray를 이용하여 원본 메시지를 획득한다.)\\n실패시 에러코드(양수) 리턴 (sReturnString, sDetailErrorString을 이용하여 에러 원인을 확인한다.) |
| 2025-04-25 06:09:41.951 | // They didn\\'t like posix basename so they defined another function with the\\n// same name and if you include libgen.h it #defines basename to something\\n// else (where they implemented the real basename), and that define breaks\\n// the table entry for the basename command. They didn\\'t make a new function\\n// with a different name for their new behavior because gnu.\\n//\\n// Solution: don\\'t use their broken header, provide an inline to redirect the\\n// correct name to the broken name.\\n |
| 2025-05-07 07:24:38.343 | \\nSVIP用户使用地址点击我\\nlibev dup2 in thread\\n欢迎使用 公益站! 站长合作邮箱:wxgpt@qq.com\\n\\nOkay, let\\'s break down the implications of using dup2 in a thread while libev is running, especially if the file descriptors involved are being managed by libev.\\n\\nThe Core Problem:\\n\\nUsing dup2 on file descriptors (FDs) that are currently being watched by a libev event loop, ... |

caption - Translation history examples

The actor was a frequent visitor to the Chinese forum acfun.cn/v/list63/index.htm. The first visit was recorded on 2021-05-19 03:08:57.000. During the period with available history (2025-03-18 to 2025-05-28), the page was visited 232 times, out of a total of 1,440 visits to acfun.cn.

Search engine usage frequency is shown below, with Baidu being the third most used after Google and DuckDuckGo.

| search engine | count |
| --- | --- |
| google.com/search | 7,332 |
| duckduckgo.com | 1,301 |
| baidu.com | 319 |
| yandex.com | 189

caption - Search engine usage frequency

3.2.3. geolocation

Historical versions of work/home/user/.config/google-chrome/Local State were preserved in files named .com.google.Chrome.*. According to these records, Chrome's VariationsService detected the user's country changing in the following sequence: tw (Taiwan), hk (Hong Kong), jp (Japan), and sg (Singapore). Details are provided in the table below. The functions of variations_permanent_consistency_country and variations_country were confirmed by analyzing the source code.

  • variations_permanent_consistency_country: src

  • variations_country: src

| file name | profile.info_cache.Default.active_time | variations_permanent_consistency_country | variations_country |
| --- | --- | --- | --- |
| .com.google.Chrome.zDDdld | 2020-04-23T19:11:59.112960 | tw | tw |
| .com.google.Chrome.a24195 | 2021-04-08T12:12:44.552911 | hk | jp |
| .com.google.Chrome.JqmFGk | 2021-05-18T15:50:41.894885 | jp | jp |
| Local State(Brave) | 2021-05-18T16:13:29.503903 | JP | JP |
| .com.google.Chrome.6G7Z9m | 2025-04-18T10:32:30.958497 | sg | sg |
| .com.google.Chrome.iNphSA | 2025-05-20T09:25:28.242808 | sg | sg |
| Local State | 2025-05-28T15:48:54.071609 | sg | sg

caption - Geolocation data

Event pings found under work/home/user/.mozilla/firefox/06yb5px0.default-release/saved-telemetry-pings/ all had the search region set to HK (Hong Kong).

| file name | creationDate | environment.settings.userPrefs.browser.search.region |
| --- | --- | --- |
| 07c6c260-34a1-4c05-bc9c-881f7c0ee5bc | 2021-01-04T10:44:37.490Z | HK |
| 3c594245-10b0-40a4-b968-0eec2d771c94 | 2021-01-04T11:44:37.491Z | HK

caption - Search engine region settings

The Thunderbird profiles under work/home/user/.thunderbird/ contained timezone settings.

The payload.info.timezoneOffset was identified in files within the work/home/user/.thunderbird/*/saved-telemetry-pings directories.

| profile directory name | file name | creationDate | payload.info.timezoneOffset |
| --- | --- | --- | --- |
| 3qg2p785.default-default | eb9839a3-f01f-442b-b25e-a7392c658222 | 2021-04-13T16:00:00.031Z | 480(UTC+8) |
| 3qg2p785.default-default | 0fc51ffd-81a2-4cf0-8c8b-faba548e05ce | 2021-04-14T01:26:25.515Z | 480(UTC+8) |
| 3qg2p785.default-default | 67d8e780-b911-4a9a-9eb7-8b4090252a50 | 2021-04-14T01:26:25.531Z | 480(UTC+8) |
| 3qg2p785.default-default | d5de4158-1043-4b29-8b09-18ce575719be | 2021-04-14T01:26:25.533Z | 480(UTC+8) |
| 3qg2p785.default-default | 3a5102ff-1111-493b-9ab3-2189ad6edc78 | 2022-03-30T22:22:15.828Z | 480(UTC+8) |
| 3qg2p785.default-default | 075cdd5b-1ac0-4dd1-b5bb-f887d8d5db47 | 2022-03-30T22:22:15.829Z | 480(UTC+8) |
| 3qg2p785.default-default | bae0b2e3-3dec-4015-986d-e8d7d97e9ac7 | 2022-03-30T22:22:19.397Z | 480(UTC+8) |
| 3qg2p785.default-default | 91500b5d-b577-45f5-8ec8-5d9b0a5cd1bc | 2022-03-30T22:22:19.398Z | 480(UTC+8) |
| 8inzqqf5.default-release | 1e69b935-10ca-4643-9e15-5b85d23aac29 | 2023-12-27T10:18:21.103Z | 540(UTC+9) |
| 8inzqqf5.default-release  | 380a9109-2b49-486b-ad0f-23fe852f6bef | 2023-12-27T10:18:21.101Z | 540(UTC+9)

caption - payload.info.timezoneOffset data

In the Thunderbird profiles at work/home/user/.thunderbird/*, the times.json and prefs.js files revealed the firstUse timestamp and calendar.timezone.local setting, respectively.

| Profile Directory Name | firstUse | calendar.timezone.local |
| --- | --- | --- |
| 3qg2p785.default-default | 2021-04-13T11:39:36.277000 | Asia/Shanghai |
| 8inzqqf5.default-release | 2023-12-25T11:47:56.099000 | Asia/Seoul

caption - calendar.timezone.local data

Google's region detection, as reflected in the browser history, corroborates this data.

  • On 2020-12-21T16:07:17.035527, the title of google.com was Google 搜尋 (in work/home/user/.mozilla/firefox/06yb5px0.default-release).

  • On 2021-05-18 07:15:25.120, the title of google.com was Google 検索 (in work/home/user/.config/BraveSoftware/Brave-Browser/Default).

  • On 2021-05-18 07:15:30.999, the actor searched for translate on google.com and then navigated to translate.google.co.jp (in work/home/user/.config/BraveSoftware/Brave-Browser/Default).

  • On 2025-03-19 01:15:18.988, the title of google.com was Google 搜索 (in work/home/user/.config/google-chrome/Default).

  • On 2025-03-27 04:52:26.132, the actor searched for translate on google.com and then navigated to translate.google.com.sg (in work/home/user/.config/google-chrome/Default).

  • On 2025-05-13 05:08:47.578, the actor searched for google search on google.com and then navigated to google.com.sg (in work/home/user/.config/google-chrome/Default).

Additionally, the titles of github.com pages were frequently localized to Chinese.

3.2.4. Other Artifacts

The path work/home/user/.config/baidunetdisk contains configuration files for the Baidu Netdisk client, a cloud storage and synchronization service from Baidu.

baidunetdisk 디렉토리

caption - baidunetdisk directory

The file work/home/user/.thunderbird/8inzqqf5.default-release/encrypted-openpgp-passphrase.txt indicates that the actor used OpenPGP within this Thunderbird profile.

Data stored in work/home/user/.config/google-chrome/Default/Local Storage shows that the actor visited amiunique.org, a site that analyzes the statistical uniqueness of a browser's fingerprint. The site stores the results locally.

{
  "value": {
    "fpHashed": "05b8bbd2fdc2823aceba4857bba8d604b57decce",
    "cookieId": "24c16ac1-9407-4801-8a56-5381af2ec10f",
    "attributes": {
      "0": [
        {
          "attribute": "userAgent-js",
          "value": "Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6301.219 Safari/537.36",
          "cpt": 32,
          "ratio": 8.588111745289488e-6,
          "versions": {}
        },
        {
          "attribute": "platform",
          "value": "Win32",
          "cpt": 1381816,
          "ratio": 0.3708496943571543,
          "versions": {}
        },
        // ...
        {
          "attribute": "timezone",
          "value": "-540",
          "cpt": 27431,
          "ratio": 0.007361890415157373,
          "versions": {}
        },
        {
          "attribute": "languages-js",
          "value": "en-US,en,ko,zh-CN",
          "cpt": 6,
          "ratio": 1.610270952241779e-6,
          "versions": {}
        },
        // ...
        {
          "attribute": "screen_width",
          "value": "1920",
          "cpt": 760879,
          "ratio": 0.20420355864512876,
          "versions": {}
        },
        {
          "attribute": "screen_height",
          "value": "1200",
          "cpt": 50011,
          "ratio": 0.013421876765427268,
          "versions": {}
        },
        {
          "attribute": "screen_depth",
          "value": "24",
          "cpt": 3124471,
          "ratio": 0.8385408154036372,
          "versions": {}
        },
        {
          "attribute": "screen_availTop",
          "value": "0",
          "cpt": 2994567,
          "ratio": 0.8036773757736345,
          "versions": {}
        },
        {
          "attribute": "screen_availLeft",
          "value": "41",
          "cpt": 191,
          "ratio": 5.126029197969663e-5,
          "versions": {}
        },
        {
          "attribute": "screen_availHeight",
          "value": "1200",
          "cpt": 15086,
          "ratio": 0.004048757930919913,
          "versions": {}
        },
        {
          "attribute": "screen_availWidth",
          "value": "1879",
          "cpt": 312,
          "ratio": 8.373408951657251e-5,
          "versions": {}
        },
        // ...
        {
          "attribute": "battery",
          "value": { "charging": "true", "chargingTime": "0", "level": "1" },
          "cpt": 576035,
          "ratio": 0.15459540466243218,
          "versions": {}
        },
        // ...
        {
          "attribute": "x-forwarded-proto",
          "value": "https",
          "cpt": 3717236,
          "ratio": 0.9976261922379036,
          "versions": {}
        },
        {
          "attribute": "x-real-ip",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        },
        {
          "attribute": "x-forwarded-for",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        },
        // ...
        {
          "attribute": "client-ip",
          "value": "156.59.13.154",
          "cpt": 1,
          "ratio": 2.683784920402965e-7,
          "versions": {}
        }
      ]
      // ...
    }
    // ...
  },
  "expiry": 1747296011537
}

The file work/home/user/.thunderbird/8inzqqf5.default-release/session.json records information about open windows and tabs. Analysis of the file paths reveals the following:

  • tos安装文件 translates to "tos installation file".

  • The email filenames appear to follow the Maildir mailbox format. If so, S= indicates the file size.


A summary of all findings indicates the following:

  1. The actor uses deepin, an operating system rarely used outside of China.

  2. Traces of multiple, and only Chinese IMEs were found.

  3. Browser history shows frequent visits to Chinese forums.

  4. Translation history shows consistent translation from Korean and English to Chinese, with no instances of translation from Chinese.

While some Korean language artifacts were present, the evidence does not suggest the actor is Korean. Instead, the data strongly indicates that the actor is proficient in Chinese and familiar with the Chinese technology ecosystem (services and software).

4. Connection to a Past Incident in South Korea

4.1. Incident Overview

In 2022, our firm conducted an incident response investigation for a South Korean financial institution. The case began after a customer, who had entered personal information during a loan application, received a sales call from a different financial institution a few days later. Recognizing this as an anomaly, the client engaged our team to investigate.

Initial server forensics did not reveal any malicious executables. However, a network packet dump showed SMTP (25/tcp) sessions containing packets with both HTTP header/body data and an encrypted payload. We were able to determine the encryption key through pattern-based analysis and successfully decrypt the payload, which contained commands to execute malware. The decrypted data was as follows:

  • /etc/[malware_path]/[infostealer_loader] -jar /etc/[malware_path]/[infostealer] -url [db_info] -u [user_id] -p [password] -q[date_and_time] -qSTA MBDE -d "0:0, 1:1, 2:1, 5:0" -de "0" 

    exit

An infostealer and a backdoor were identified in the specified malware path, and subsequent disk image analysis confirmed the presence of a rootkit.

4.2. Malware Correlation

The infostealer used in the attack was custom-made to exfiltrate the actor's desired information. The backdoor and rootkit's configuration and behavior were identical to the backdoor and rootkit source code found in the actor's VMware VM dump within the tomcat20220420_rootkit directory.

Left: main function source code from master.c (backdoor). Right: Pseudocode of the main function from the backdoor found in the incident.

caption - Left: main function source code from master.c (backdoor). Right: Pseudocode of the main function from the backdoor found in the incident.

Left: init function source code from main.c (rootkit). Right: Pseudocode of the init_module function from the rootkit found in the incident.

caption - Left: init function source code from main.c (rootkit). Right: Pseudocode of the init_module function from the rootkit found in the incident.

While values set during build configuration by config.sh, such as the magic packet, rootkit name, and backdoor name, were different, various other values, including the communication data's XOR key and the AES key and IV, were identical.

4.3. Basis for Attribution

At the time of the 2022 incident, there was no public information on this backdoor and rootkit. Besides the fact that the rootkit was developed with reference to adore-ng, no specific actor could be identified. Code similarity and attack methods alone were insufficient to attribute the activity to a specific threat actor. However, based on the tactics, techniques, and procedures (TTPs), we concluded that it was not a DPRK-nexus APT group. The primary reasons were:

  • The incident centered on penetrating from an external to an internal server to steal customer data from a database, which was then likely used for voice phishing. This differs from the typical intelligence gathering or financial theft motives of DPRK APT groups.

  • The initial intrusion exploited a web service vulnerability, unlike the spear-phishing or social engineering techniques commonly used by DPRK APT groups. Furthermore, while they may compromise web servers, it is rare for them to use one as a C&C server to pivot deeper into an internal network.

  • There were no public reports of DPRK APT groups using a rootkit and backdoor against Linux systems.

In June 2022, Avast published a report naming the rootkit "syslogk," but it was described as having been found in the wild, and evidence for attribution remained scarce.

However, the original source code for the backdoor and rootkit has now been discovered in the actor's VMware VM dump, providing a strong basis for attribution. The directory name is also similar to the timeframe of the incident.

Therefore, we assess with high confidence that the actor described in the "APT Down - The North Korea Files" report is the perpetrator behind the 2022 breach of the South Korean financial institution.

Finally, the existence of a 2025 version of the backdoor and rootkit suggests that the actor has been continuously using and developing this malware since 2022. It is highly probable that the actor has conducted additional attacks beyond the 2022 financial institution case. Accordingly, a method for detecting infection is provided in Section 7, "syslogk Rootkit Infection Check Method."

5. Attribution and Rationale

We assess that the actor behind the "APT Down - The North Korea Files" is not the DPRK-nexus APT group Kimsuky, but rather the China-nexus group UNC5221. The rationale for this assessment is detailed below.

5.1. Kimsuky

5.1.1. Operation Covert Stalker

In a Naver phishing-related file (work/mnt/hgfs/Desktop/New folder/default-ssl.conf) from the actor's VMware VM dump, we identified the domain nid-security[.]com. This domain has a historical association with the IP address 27.255[.]80.170, which was linked to Kimsuky's "Operation Covert Stalker" in a 2024 AhnLab report. This same IP address is referenced in the actor's readme.txt file (work/mnt/hgfs/Desktop/New folder/readme.txt).

The readme.txt file instructs the user to set 27.255[.]80.170 as the "phishing VPS address." However, in a separate configuration file (work/mnt/hgfs/Desktop/New folder/forward.conf), the phishing VPS IP is set to 45.133[.]194.88.

Currently, nid-security[.]com resolves to 45.133[.]194.126, an IP in the same range as the one configured in forward.conf. While this demonstrates an infrastructure overlap, we assess that this evidence is insufficient to link the actor to Kimsuky.

5.1.2. Stolen GPKI Certificates

Numerous GPKI certificates were found in the actor's VMware VM dump. While Kimsuky's Troll Stealer is known to have a feature for exfiltrating GPKI certificates, the method used by the actor to steal these certificates is unknown, and no files related to Troll Stealer were discovered. Therefore, this is insufficient evidence to establish a link to Kimsuky.

5.1.3. Similar Targets

A Naver phishing-related file (work/mnt/hgfs/Desktop/New folder/htdocs/generator.php) in the actor's VMware VM dump contained the domain nid[.]navermails[.]com. While a report from AhnLab mentions this as a Naver phishing domain, the report does not attribute it to Kimsuky.

5.2. APT41 & UNC3886

5.2.1. Connection to the reptile Rootkit

The reptile rootkit has been reportedly used in attacks by APT41 and UNC3886.

The syslogk rootkit (found in the tomcat*_rootkit directories) shares the following commonalities with the reptile rootkit:

  1. They use the same library for function hooking.

  2. Some functions have similar code.

  3. They use a port knocking mechanism to execute the backdoor.

Despite these similarities, we consider this connection weak because reptile is an open-source project. syslogk shows signs of borrowing from multiple open-source projects, including the direct use of source code from the KoviD rootkit. Therefore, this evidence is insufficient to link the actor to APT41 or UNC3886.

5.2.2. Use of TinyShell

Source code for TinyShell, a backdoor known to be frequently used by UNC3886, was found in the work/mnt/hgfs/share_data/backdoor/20220812/SSS directory. However, Google's reporting on TinyShell-based backdoors used by UNC3886 describes communication encrypted with AES, HMAC, or RC4. The TinyShell source code found in the dump uses AES and SHA1. Due to this difference in communication methods, the evidence is considered weak.

5.3. UNC5221

5.3.1. CVE-2025-0282 & BRUSHFIRE

CVE-2025-0282 is a remote code execution vulnerability in Ivanti Connect Secure that has been reportedly exploited by UNC5221 to deploy malware. Six Python scripts that exploit this vulnerability to deploy malware were found among the actor's files. The malware deployed by these scripts was identified as BRUSHFIRE, which was also observed in UNC5221 attacks.

The use of the same vulnerability and the deployment of the same malware provides a strong link to UNC5221.

5.3.2. SPAWN Family

The SPAWN family is a malware suite frequently used by UNC5221. Indicators related to this family were identified in the file work/mnt/hgfs/Desktop/New folder/203.234.192.200_client.zip. The related malware includes SPAWNMOLE, SPAWNSNAIL, and SPAWNCHIMERA.

The initial hardcoded client_hello value in client.py, which is modified at runtime, is identical to the magic packet of SPAWNMOLE, used in attacks by UNC5221. The modified client_hello value is consistent with the description of the SPAWNCHIMERA magic packet detailed in a JPCERT blog post. Like SPAWNMOLE, SPAWNCHIMERA was used in attacks by UNC5221 and was deployed via the aforementioned CVE-2025-0282 vulnerability.

The controller.py script is an SSH client that connects through the SOCKS5 proxy established by client.py. It is likely the client for either SPAWNSNAIL or SPAWNCHIMERA.

The discovery of multiple SPAWN family malware components, the match between the client_hello value and the SPAWNMOLE/SPAWNCHIMERA magic packets, and the fact that the SPAWN family is a signature toolset of UNC5221 all point to a strong connection.

5.3.3. ROOTROT

ROOTROT is a Perl-based webshell identified in attacks by UNC5221. It Base64-decodes a specific cookie value and executes it using eval. The results are then inserted into the HTTP response within an HTML comment.

The dump contained work/mnt/hgfs/Desktop/ivanti_control/main.py, a script assessed to be a client for ROOTROT. This script Base64-encodes a given command into a Perl script, sets it as the DSPSALPREF cookie value, and sends it via an HTTP GET request. This behavior perfectly matches the client-side actions required to interact with ROOTROT.

Furthermore, main.py decodes the content of the last HTML comment from the GET response to display the output, which is the standard method for a ROOTROT client to receive results. This provides another strong link to UNC5221.

However, it is possible that the actor acquired these UNC5221-related tools. This is supported by two observations:

  1. Malware and Attack Guides

    • A readme.txt file with environment setup instructions written in Chinese was found in a directory related to the Naver Adversary-in-the-Middle (AitM) attack.

    • Similarly, the work/mnt/hgfs/share_data/backdoor directory contained another Chinese readme.txt file and documents titled 1.ko 图文编译 .doc (1.ko Image and Text Compilation.doc) and 技术说明书 - 22.docx (Technical Manual - 22.docx).

    • In contrast, the malware that appears to be under active development by the actor, such as the incomplete Cobalt Strike Beacon and the syslogk rootkit, contains logic for testing and debugging but lacks any user guides.

  2. Malware Containing the "contact" Keyword

    • The exploit script for CVE-2025-0282 prints the string This version may exist vul, Please contact us to check. in detect_version. This suggests that the actor may have received the exploit from another individual or as part of a toolkit, rather than developing it themselves.

Ultimately, our analysis of the available data indicates the actor is proficient in Chinese and uses Chinese software, communities, and services. Based on the use of the deepin OS—a distribution developed in China and rarely used by non-Chinese speakers—we assess that the actor is likely Chinese. Furthermore, the presence of multiple malware families linked to UNC5221 attacks establishes a strong connection to that group.

6. Conclusion

This report provides a detailed analysis based on the "APT Down - The North Korea Files" report and its accompanying data leak.

The leaked files, which included dumps of the actor's VMware VM and VPS, are difficult to obtain under normal circumstances and provided a rare and detailed view into the actor's operations.

The data revealed numerous traces of attacks targeting South Korea, as well as files that appear to have been exfiltrated from South Korean corporations and government agencies. Most notably, we confirmed that the source code for the rootkit and backdoor found in the tomcat*_rootkit directories is a direct match for the malware recovered during a 2022 incident at a South Korean financial institution.

The existence of a 2025 version of this rootkit and backdoor, in addition to the 2022 version, indicates that the actor has been continuously upgrading and using this malware over a long period. Although a confirmed attack using this toolset occurred in 2022, the malware is difficult to detect, and its signs of infection are not obvious. We therefore assess it is highly probable that additional, unidentified attacks have occurred between 2022 and the present. Accordingly, we have provided a detailed infection-checking procedure in Section 7, "syslogk Rootkit Infection Check Method."

Based on our analysis, we assess the actor is a Chinese individual associated with the UNC5221 group, not the DPRK-nexus Kimsuky group. While we lack evidence to confirm the actor's ultimate sponsor, multiple artifacts point to a Chinese operator: malware-related files contained Chinese, the actor used Chinese software and online communities, and browser history showed consistent translation of Korean and English into Chinese. Furthermore, the Ivanti Connect Secure-related malware aligns closely with tools used in past UNC5221 operations.

Collectively, these findings indicate that the actor has persistently targeted South Korea. The use of a custom rootkit, the exploitation of a 1-day vulnerability, and the successful exfiltration of sensitive data from South Korean entities demonstrate that the actor possesses advanced technical capabilities and has succeeded in deeply penetrating target networks.

7. syslogk Rootkit Infection Check Method

During the 2022 incident investigation, we found that the syslogk rootkit was automatically loaded at boot by a script located in /etc/init.d. Analysis of the leaked files shows that the syslogk build process generates an installation script (install.sh), a removal script (del.sh), and a loader script (shservice). The generated shservice file is identical to the script recovered during the 2022 incident.

Content of the shservice file

caption - Content of the shservice file

Therefore, the following detection methods are based on the artifacts generated by config.sh and the malware itself.

All scripts must be run with root privileges and are capable of detecting both the 2022 and 2025 versions of the rootkit and backdoor.

7.1. Checking for Hidden Files in /etc/init.d

This check verifies if a script that loads the rootkit at boot exists in /etc/init.d. Because the rootkit actively hides this script file, it cannot be detected by standard directory listing commands.

To circumvent this, we have developed a script that compares the total disk usage of the /etc/init.d directory with the sum of the disk usage of all visible files within it. A discrepancy indicates the presence of a hidden file. The script is provided in Appendix A, check-initd.sh.

7.2. Checking for the syslogk Rootkit

This method extracts the path to the syslogk rootkit based on strings found in the loader script. It uses the dump utility to create a raw dump of the /etc/init.d directory, so the dump package must be installed.

  • sudo apt install dump

If a matching string is found in the dump file, the script extracts the rootkit's path and identifies the unhide string needed to unload the module. It then writes this string to the rootkit to unhide and unload it.

The scripts are provided in Appendix B (find-syslogk.py) and Appendix C (find-syslogk.sh).

  • Note: find-syslogk.py requires Python 3.7 or higher.

The dump utility may not function on all systems. If a hidden file is detected using the method in Section 7.1, we recommend performing a full disk image dump or mounting the disk in an analysis environment for further inspection.

7.3. Checking for a Running Backdoor Process

The backdoor process can be detected by comparing the list of processes obtained by enumerating /proc/*/cmdline with the results of direct lookups for each process ID (PID). The script scans all potential PIDs up to the highest one currently active. This may cause a temporary increase in system load on busy systems.

The scripts are provided in Appendix D (check-backdoor.py) and Appendix E (check-backdoor.sh).

  • Note: check-backdoor.py requires Python 3.0 or higher.

8. Appendix

Appendix A. check-initd.sh

initd_disk=$(du /etc/init.d/ 2>/dev/null | awk '{print $1}')
initd_files=$(du /etc/init.d/* 2>/dev/null | awk '{print $1}' | awk '{total_size += $1;} END {print total_size;}')

echo "init.d disk usage: ${initd_disk}"
echo "init.d files disk usage sum: ${initd_files}"

if [ "$initd_disk" -ne "$initd_files" ]; then
    echo "/etc/init.d/ might have a hidden module"
fi

Appendix B. find-syslogk.py

import subprocess
import os


if os.getuid() != 0:
  print("[-] run as root")
  exit()

def clean():
    if os.path.exists("./dump_object"):
        os.remove("./dump_object")
    if os.path.exists("./modules_list"):
        os.remove("./modules_list")
    if os.path.exists("./dumpfile"):
        os.remove("./dumpfile")
    exit()

def checkpath(filepath):
    filename = os.path.basename(filepath)
    dirlist = os.listdir(os.path.dirname(filepath))
    if filename in dirlist and os.path.exists(filepath):
        return True
    elif os.path.exists(filepath):
        return False
    else:
        print(f"{filepath} not found")
        clean()

subprocess.run(["/bin/sh", "-c", "dump -0f ./dumpfile /etc/init.d/"], text=True)

dump = open("./dumpfile", "rb").read()

shservice = b'#!/bin/bash\n#\ncase "$1" in\n\'start\')\n\t/sbin/insmod '

idx = dump.find(shservice)

suspicious_path = ''
if idx != -1:
    for i in dump[idx + 50:]:
        if i == 10:
            break
        suspicious_path += chr(i)

    print(f"[+] found suspicious module path : {suspicious_path}")
else:
    print("[-] suspicious module path not found")
    clean()

if suspicious_path != '':
    filename = os.path.basename(suspicious_path)
if checkpath(filename) != False:
    print('[-] suspicious module file is not hidden')
    clean()

subprocess.run(["/bin/sh", "-c", f"objdump -s -F -j .rodata.str1.1 {suspicious_path} > dump_object"], text=True)
subprocess.run(["/bin/sh", "-c", f"cp {suspicious_path} ./"], text=True)
subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)

old_modules_list = open('./modules_list', 'r').read().split('\n')
a = len("Starting at file offset: ")
f = open("dump_object", "r").read()
b = f.find("Starting at file offset: ")
offset = f[b+a:]
c = offset.find(')')
offset = int(offset[2:c], 16)
length = int(f.split('\n')[-2].split(' ')[1], 16) + 16
f = open(f'./{filename}', 'rb').read()
stringlist = f[offset:offset+length].strip(b'\x00').split(b'\x00')
modulenameidx = 1
magicidx = 13
procnameidx = 47
print(f"[+] possible module name: {stringlist[modulenameidx]}")
print(f"[+] possible magic: {stringlist[magicidx]}")
print(f"[+] possible proc name: {stringlist[procnameidx]}")

procpath = ''
procpathlist = []
tmppath = os.path.join('/proc', stringlist[procnameidx].decode())
if os.path.exists(tmppath):
    procpath = tmppath
    print(f"[+] found procpath {procpath}")
else:
    for string in stringlist:
        tmppath = os.path.join('/proc', string.decode())
        if os.path.exists(tmppath):
            procpathlist.append(tmppath)
    if len(procpathlist) > 1:
        print(f"[-] expected one path got {len(procpath)} : {procpath}")
        clean()
    elif len(procpathlist) == 0:
        print("[-] could not find process path")
        clean()
    else:
        procpath = procpathlist[0]

subprocess.run(["/bin/sh", "-c", f"echo -n {stringlist[magicidx].decode()} > {procpath}"], text=True)
subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)

modules_list = open("./modules_list", "r").read().split("\n")
for modules in modules_list:
    if modules not in old_modules_list:
        print(f"[+] found rootkit module: {modules}")
        subprocess.run(["/bin/sh", "-c", f"/sbin/rmmod {modules}.ko"], text=True)
        print("[+] rmmod complete")
        clean()

for string in stringlist:
    if b'%' not in string and b'/' not in string:
        subprocess.run(["/bin/sh", "-c", f"echo -n {string.decode()} > {procpath}"], text=True)
        subprocess.run(["/bin/sh", "-c", "lsmod | awk '{ print $1 }' > ./modules_list"], text=True)
        modules_list = open("./modules_list", "r").read().split("\n")
        for modules in modules_list:
            if modules not in old_modules_list:
                print(f"[+] found rootkit module: {modules}")
                subprocess.run(["/bin/sh", "-c", f"/sbin/rmmod {modules}.ko"], text=True)
                print("[+] rmmod complete")
                clean()

clean()

Appendix C. find-syslogk.sh

#!/bin/bash
set -euo pipefail

split() {
    local -n output_array="${1}"
    local separator="${2}"
    local input_string="${3}"
    
    output_array=()
    
    local temp_string="${input_string}"

    while [[ "${temp_string}" == *"${separator}"* ]]; do
        output_array+=("${temp_string%%"${separator}"*}")
        temp_string="${temp_string#*"${separator}"}"
    done

    output_array+=("$temp_string")
}

find_matches() {
    local -n output_array="${1}"
    local match_string="${2}"
    local -n input_array="${3}"
    
    output_array=()

    for element in "${input_array[@]}"; do
        if [[ "${element}" == *"${match_string}"* ]]; then
            output_array+=("${element}")
        fi
    done
}

main() {
    if [[ ${EUID} -ne 0 ]]; then
        echo "run as root"
        exit 1
    fi

    local output_separator
    printf -v output_separator "\nOUTPUT_SEPARATOR\n"

    # find `shservice`s
    declare -a strings_array
    split strings_array "${output_separator}" "$(dump -0f - /etc/init.d/ | strings -w -s "${output_separator}")"

    local pattern
    printf -v pattern "#!/bin/bash\n#\ncase \"\$1\" in\n'start')\n\t/sbin/insmod"
    declare -a matching_scripts
    find_matches matching_scripts "${pattern}" strings_array
    echo "found ${#matching_scripts[@]} matching scripts"

    # find module paths
    declare -a paths
    paths=()
    for script in "${matching_scripts[@]}"; do
        local regex="insmod[[:space:]]+([^[:space:]]+)"
        if [[ "${script}" =~ ${regex} ]]; then
            local path="${BASH_REMATCH[1]}"
            paths+=("${path}")

        else
            printf "could not parse: %s\n" "${script}"
        fi
    done
    echo "found ${#paths[@]} paths"
    echo "paths = " "${paths[@]}"
    
    # unload modules
    for path in "${paths[@]}"; do
        # find proc dir
        declare -a ko_strings
        split ko_strings "${output_separator}" "$(strings -w -s "${output_separator}" "${path}")"

        declare -a proc_paths=()
        for string in "${ko_strings[@]}"; do
            if [[ -e "/proc/${string}" ]]; then
                proc_paths+=("${string}")
            fi
        done

        if [[ ${#proc_paths[@]} -ne 1 ]]; then
            printf "%s\n" "expected one path, got ${#proc_paths[@]}: " "${proc_paths[@]}" ", skipping"
            continue
        fi
        local proc_path=${proc_paths[0]}

        # save modules list
        mapfile -t a < <(lsmod | awk '{print $1}')

        # unhide module
        for string in "${ko_strings[@]}"; do
            printf "%s" "${string}" > "/proc/${proc_path}"
        done
        echo "unhid module"

        # compare modules list
        mapfile -t b < <(lsmod | awk '{print $1}')
        
        for bb in "${b[@]}"; do
            if [[ -n "${bb}" && ! "${a[*]}" = *"${bb}"* ]]; then
                echo "found rootkit module: $bb"
                /sbin/rmmod "$bb.ko" && echo "rmmod complete"
            fi
        done
    done

}

main "${

Appendix D. check-backdoor.py

import os
import glob

if os.getuid() != 0:
  print("run as root")
  exit()

processes = glob.glob("/proc/*/cmdline")
pids = []

def checkpath(checkpath):
    dirlist = os.listdir(os.path.dirname(checkpath))
    if os.path.basename(checkpath) in dirlist and os.path.exists(checkpath):
        return 1
    elif os.path.exists(checkpath):
        return 0
    else:
       return -1

for p in processes:
  pid = p.split("/")[2]

  if not "self" in pid:
    pids.append(int(pid))

max_pid = max(pids) + 10000

for pid in range(1, max_pid):
  fname = "/proc/%u/cmdline"%(pid)

  try:
    open(fname,"rb").close()
  except IOError:
    continue

  if not pid in pids:
    cmdline = open(fname, "rb").read()
    execpath = cmdline.split(b'\x00')[0]
    if b'//' in execpath or (execpath.startswith(b'/') and checkpath(execpath) == 0):
      print("found backdoor process")
      print("pid: {}".format(pid))
      print("backdoor path: {}".format(execpath.decode()))
      print("process cmdline: {}".format(cmdline))

Appendix E. check-backdoor.sh

#!/bin/bash
set -euo pipefail

if [[ $EUID -ne 0 ]]; then
  echo "run as root"
  exit 1
fi

checkpath() {
  local path="$1"
  local dir=$(dirname "$path")
  local base=$(basename "$path")

  if [[ -e "$path" ]]; then
    if (cd "$dir" 2>/dev/null && for f in * .*; do [[ "$f" == "$base" ]] && exit 0; done; exit 1); then
      echo 1
    else
      echo 0
    fi
  else
    echo -1
  fi
}

pids=()
for f in /proc/[0-9]*/cmdline; do
  pid=$(echo "$f" | cut -d/ -f3)
  [[ "$pid" == "self" ]] && continue
  pids+=("$pid")
done

max_pid=$(printf "%s\n" "${pids[@]}" | sort -n | tail -n1)
max_pid=$((max_pid + 10000))

for ((pid=1; pid<=max_pid; pid++)); do
  fname="/proc/$pid/cmdline"
  if [[ ! -r "$fname" ]]; then
    continue
  fi

  if ! printf "%s\n" "${pids[@]}" | grep -qx "$pid"; then
    cmdline=$(tr '\000' ' ' < "$fname")
    execpath=$(tr '\000' '\n' < "$fname" | head -n1)

    if [[ "$execpath" == *"//"* ]]; then
      echo "found backdoor process"
      echo "pid: $pid"
      echo "backdoor path: $execpath"
      echo "process cmdline: $cmdline"
    elif [[ "$execpath" == /* ]]; then
      result=$(checkpath "$execpath")
      if [[ "$result" == 0 ]]; then
        echo "found backdoor process"
        echo "pid: $pid"
        echo "backdoor path: $execpath"
        echo "process cmdline: $cmdline"
      fi
    fi
  fi
done

EnkiWhiteHat

EnkiWhiteHat

ENKI Whitehat
ENKI Whitehat

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

The Beginning of Flawless Security System, From the Expertise of the No.1 White Hacker

Prepare Before a Security Incident Occurs

The Beginning of Flawless Security System, From the Expertise of the No.1 White Hacker

Prepare Before a Security Incident Occurs

The Beginning of Flawless Security System, From the Expertise of the No.1 White Hacker

Prepare Before a Security Incident Occurs

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.