Hide and Seek (HNS) is a malicious worm which mainly infects Linux based IoT devices and routers. The malware spreads via bruteforcing SSH/Telnet credentials, as well as some old CVEs. What makes HNS unique is there’s no command and control server; instead, it receives updates using a custom peer-to-peer network created out of infected devices.
Each HNS infected device runs a UDP server on a port which is either provided upon infection, or randomized. Newly infected devices are given a list of IP and port combinations which correspond to other HNS infected devices (known as peers). Infected devices maintains a list of other peers which has a limited size based on available RAM (usually around 512).
Until the peer list is full, a device will send peer request messages to peers in its’ peer list. Upon receiving a peer request, the recipient will do two things: Firstly, it will add the sender’s IP and port to its own peer list; then it will reply with a peer from its’ own peer list. Using this technique, infected devices will discover other infected devices and maintain connectivity with them.
In order to receive updates, each bot periodically asks peers in its’ peer lists for their config version number. When a bot finds a peer with a version number higher than its own, it downloads the new config from said peer. To perform an update, the botnet operator only needs to upload a new config to a single infected device; the device will then distribute the updated config to all its peers, which in turn will do the same.
Normally, tracking a P2P botnet would be pretty simple. New peers can be found by sending peer requests to known ones; thus, recursively sending peer requests to all peers should eventually result in discovery of the entire botnet. Unfortunately, HNS has some counter measures.
Above is the code responsible for handling incoming peer requests. If the recipient’s peer list is full (which it almost always is), the sender’s IP is used as a list index [line 7]. Essentially, we will always get the same response when issuing multiple peer requests to the same target from the same IP.
We could query a peer to find a new peer, then query that new peer to find another, and so on; however, if any peer returns a peer we already have, or a dead one, the process will grind to a halt.
In testing, I only got about 4 new peers from each starting peer before a a non-responsive one was returned. There is some give though.
Here is an snippet from the code responsible for updating the peer list. If a request from a peer not already in the peer list is received, but there is no free space in the list, the above code is hit.
The code checks if the lower 7 bits of the timestamp are zero (which occurs every 128 seconds). If so, a random peer in the list is overwritten with the new peer, and a mutex is set.
Due to the fact there are more peers than peer list entries, the code causes the peer list to overwrite 1 peer almost every 128 seconds. Assuming the peer list is limited to 512 peers, every ~18+ hours all entries should have been replaced or moved.
So, with the peer list constantly churning, we can slowly build up a bigger list. Firstly we recursively send peer requests until we get a dead peer (usually after 4 iterations). Now we wait a few hours, then recursively query all the previously found peers again. This should result in our peer list multiplying by ~4 every ~18 hours.
Full article at https://www.malwaretech.com/2019/01/tracking-the-hide-and-seek-botnet.html