Computerdores

Analysing the Panchan Botnet

Written: 2026-05-23

Last Updated: 2026-05-23


Intro

A while ago I wanted to reverse engineer some real-world malware and decided to setup an SSH honeypot to see what kind of stuff I might get. Relatively quickly after that, the first malicious actor uploaded and tried to execute a piece of malware. However, instead of just executing the malware the infected system provided it with a list of IPv4 addresses which piqued my interest.

In this writeup, I will first outline what I found while reverse engineering the malware. In the second section, I will talk about mapping the botnet. Finally, I will point out some prior work on this sample that I found and how their results compare to mine.

The Malware

I analysed this sample in ghidra and quickly realised that the malware was written in golang. Since I have not previously worked with golang binaries there were a couple of interesting things I learned:

  1. Golang binaries, even when stripped, usually contain a .gopclntab section which contains a lot of useful metadata that is usually only part of the stripped debug information (function names, type names, some struct definitions, string locations, ...)
  2. Ghidra has support for the golang calling conventions, however, to use these the compiler of the binary has to be set to "golang", instead of the default "gcc".

Generally, the malware is structured in a relatively modular fashion. Each module gets initialised by the entrypoint and implements different functionality, with some cooperation. The malware also has call-home functionality which originally informed the operator of newly infected clients. However, this was implemented using a hardcoded discord webhook url which has since broken. The auto-incrementing ID of the webhook indicates that the webhook was created before early 2025 and after mid 20211. Further, the malware implements persistence by copying itself to /bin/systemd-worker and creating a systemd service (systemd-worker.service)

Spreader - This module implements worming functionality to spread the malware. One approach for this, which was noted by akamai as possibly being novel, is read the ssh known_hosts file and using ssh keys on the system to try to connect to and infect the known hosts. Further, the malware tries a dictionary attack against ssh servers on random IPv4 addresses with just 82 different passwords. Of the usernames and passwords that are tried, most are relatively expected except for "ジェリーフィッシュ" (jp. jellyfish). The Akamai writeup suggests that the operator might be japanese, which matches this observation. The reason for including this password candidate is not clear, my best quess is that perhaps some japanese cloud operator, device manufaturer, or similar used this as a default password. After the spreader module has compromised a new system, it will copy itself to ./.<random number>/sshd on the target system and then execute that binary with the IPv4 addresses of several known peers.

P2P - The p2p module implements peer-to-peer functionality in order to distribute certain configuration without having to connect to a central C2 server. For this, the malware listens on port 1919/tcp and once a client connects it sends the eponymous header pan-chan's mining island hi!. This is followed by sending the IPv4 addresses of 5 randomly selected peers. If the client has any configuration information, it will also share this with the connected peer. However, if the peer shares any such configuration the client will perform a cryptographic signature validation in order to assure that only the operator of the botnet can issue such configuration.

Updater - This module only runs if an update configuration was received by the p2p module. If so it will further check that the id hash of the update differs from its own hash. If this is the case, then it will fetch the updated binary from a URL that is provided as part of the update information. Since none of the clients identified in my research had such update information, no update URL could be found.

Cryptomining - This module uses the configuration obtained via the p2p module in order to launch two different crypto miners. As this part was not particularly interesting to me, I did not reverse engineer this module further. The writeups by Akamai and Nozomi give more details for those that are interested.

Anti-Kill - This module hooks into SIGINT and redirects the signal in order to avoid soft-kills of the process. However, since it can not prevent SIGKILL from stopping the process, this does not prevent the process from being able to be killed.

Anti-Taskmanager - This module monitors the active processes to look for htop and top and kills both crypto miners if they are detected. This is likely an attempt to evade detection by sysadmins that try to investigate increased GPU usage.

Protector - This module seems to attempt to prevent automated takeover of the system by another malicious actor using a dictionary attack, while still allowing the legitimate owner to login via ssh to prevent detection. To do this it kills sshd and implements it's functionality itself using golang.org/x/crypto/ssh. However, when a shell session is started after a successfull login it shows the following prompt: Please type "I am human" to prove that you are human: . Only after the user complies is a shell session actually opened.

Overall, this malware seems to be well engineered with a number of non-essential features that improve its persistence and worming. Further, the operator seems to have learned from past botnets by avoiding a central C2 server that could be taken over and requiring cryptographic signatures for the p2p control messages.

Mapping the Botnet

Once I realised that the p2p functionality always responds with a random set of 5 peer IPs, I quickly had the idea to use this in order to completely enumerate the botnet. So after I was mostly done analysing the malware binary, I wrote a script to do just that. The script would "squeeze" an infected host, by repeatedly connecting to the p2p port until a certain number of consecutive requests return no new peer IP address. This way, similar to squeezing a sponge, the script would squeeze out most of the useful information without wasting to much time in an attempt to get out absolutely everything. I then setup the script to squeeze all known previously infected hosts once an hour and let it run for around a week. In this time the script found ~5k unique peer IP addresses. Of these around 200 responded at least once and could thus be confirmed to be infected. However, there was a significant rate of flux in the set of infected, online hosts, as at any point in time roughly 100 of the 200 were last seen online.

In comparison the writeup by Akamai from 2022 only found a total of 200 peers with 40 of them being online. This could be explained by the botnet simply being smaller in size at the time. Alternatively, it is possible that Akamai only queried each peer once, which would lead to a smaller set of discovered peers

Nozomi Networks in their writeup from 2024 found around 100 peers that were online. However, instead of spidering the p2p mesh, they used a dataset of Go-based SSH servers and filtering for those that were actually infected. Depending on how complete their dataset was and whether there was a netsplit of the p2p botnet at the time, this approach could have either lead to a more complete or to a less complete enumeration.

Klavansec found only roughly 100 total peers of which they confirmed 50 to be up in their writeup from Feburary 2026. However, according to their description of the enumeration they indeed only queried each infected host once and only went three layers deep. This would limit the amount of peers they could possibly find to just 125. This means that their enumeration is most likely incomplete and should not be misunderstood as a complete one. It is also woth noting that the number of total and online peers that they found is similar to those of Akamai, which might mean that Akamai did indeed only query each peer once.

Overall, I noticed both during different times in my scan and in the numbers of these writeups that the number of infected hosts often hovers closely around 100. One could speculate that this is due to the operator proactively limiting the size of the botnet. However, in my opinion it is much more likely that the infection rate is simply very close to the disinfection rate, leading to a relatively stable size of the botnet. This is further reinforced by the observation that in my scanning none of the hosts returned a mining or update config and that the discord webhook is non-functional when the operator could have fixed this via the update functionality. As it is the evidence points towards a rogue botnet which due to its worming capabilities has survived on the internet for close to four years today.

Prior Work

There are a number of previously published writeups on this botnet, which I have also previously referenced in this writeup. The following subsections will point out some of the differences between these and my research, in bullet points.

Akamai

Nozomi Networks Labs

klavansec

1

Discord webhook ids were around 1370000000000000000 in early 2025 and around 850000000000000000 in mid 2021. While the ID of the webhook used in the malware is 960954050583613549.