The challenge was simple on paper: disarm a Linux kernel module rootkit and recover hidden data. In practice, this was my first Hack The Box challenge, and until now I had mostly played with crackmes. Getting an IP address and port as part of a reversing challenge already threw me off for a moment.
The provided file was diamorphine.ko. A .ko file is a Linux kernel object: it can be loaded with insmod, removed with rmmod, and listed with lsmod if the module is not hiding itself.
file identified it as:
1
ELF 64-bit LSB relocatable, x86-64, with debug_info, not stripped
That last part mattered. Because the module was not stripped, Ghidra still showed useful function names instead of leaving me with anonymous blobs. strings produced too much noise to be helpful, so I moved into Ghidra and started from the symbol tree.
The interesting functions were:
diamorphine_init
diamorphine_cleanup
find_task
get_syscall_table_bf
give_root
hacked_getdents
hacked_getdents64
hacked_kill
is_invisible
module_hide
module_show
Every kernel module has an initialization and cleanup path, which in this case were diamorphine_init and diamorphine_cleanup. The rest of the names were already a pretty good map of the rootkit:
module_hide and module_show remove or restore the module from the kernel’s module list, which is why lsmod may not show it.
give_root does exactly what the name suggests.
get_syscall_table_bf locates the syscall table by brute force.
is_invisible checks whether a process has been marked as hidden.
From there, the two most important hooks were hacked_kill and hacked_getdents64.
DNS is easy to overlook during a compromise. It is usually allowed out of a network, it is noisy by nature, and many environments focus harder on HTTP, SSH, or obvious file transfer tools. That makes it a useful channel for moving small amounts of data when a host can resolve external names.
The important trick is that DNS names can carry attacker-controlled text. Instead of sending the file directly over HTTP or copying it with scp, the file is encoded and placed into DNS query labels.
This part reads the file and converts it to DNS-safe text:
After inspecting the site, one small detail in the Survey stood out: Our team reads every word! Be creative and specific. when reading this i instantly thought about XSS.
from http.server import HTTPServer, SimpleHTTPRequestHandler import json
class PostHandler(SimpleHTTPRequestHandler): def do_POST(self): content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) # Log the POST data print(f"POST request received:") print(f"Path: {self.path}") print(f"Headers: {self.headers}") print(f"Body: {post_data.decode('utf-8')}") # Send response self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() response = {'status': 'success', 'received': post_data.decode('utf-8')} self.wfile.write(json.dumps(response).encode())
if __name__ == '__main__': server = HTTPServer(('localhost', 8000), PostHandler) print('Server running on http://localhost:8000') server.serve_forever()
to get the flag all i had to do is start the webserver by running: python3 server.py and sent the payload via the survey.