SSTIC 2020 step 3

Guillaume Quéré


Challenge information

Getting the hash

Per the instructions we need to find a 3-words password combined from the wordlist that somehow recovers the chat password using the provided binary.

The hashes file looks like it is (somewhat) (badly) encrypted. We can barely distinguish that it’s a hashing algorithm with a salt and a work factor:

meteo:$sstic$ad6w.742e=#1!2=&$t/3?v0.,ebYY_F}QSWYWDfDIuYB@oj2Jc*XqIE{zOwEzDtqCFhj^q
chat:$sstic$ad6w.742e=#1!2=&$t/3?v0.,SzfPIDt~VhWXr]SCECfv5j2rZeob2Z.@4^pgqdU[AGMBW

The simplest way of getting the password is to just let it bruteforce from the wordlist. After all there are only 26^3 combinations to try!

We’ve got a toy example to get a sense of the timings:

time ./show_password hashes.txt meteo
Password for meteo: AlloBruineCrachin
Good password ☀
Welcome ☺
Your password is Jbeh3AWZIve1Tez1Ptw+qg

real	0m6,295s
user	0m16,262s
sys	0m1,019s

time ./show_password hashes.txt meteo
Password for meteo: abc
Wrong password‽

real	1m6,133s
user	2m52,369s
sys	0m12,282s

Uh oh… seems like my CPU is way too weak to solve this challenge this way: a success takes more than 6 seconds and it doesn’t have enough cores to meaningfully parallelize. So this approach would take a bunch of hours to complete…

Another problem is that show_password is a Rust binary, which are a notorious mess to reverse. I really didn’t want to do that. So I first loaded it in gdb and looked at the defined functions:

argon2::argon2::hash_encoded
argon2::argon2::verify_encoded_ext
argon2::argon2::verify_raw
argon2::variant::Variant::from_str

Hey Argon2, that’s kinda what I was expecting! Let’s set a breakpoint in gdb (kitted with gef) and do a test run:

gef➤  b argon2::argon2::verify_encoded_ext
Breakpoint 3 at 0x555555562b90
gef➤  run hashes.txt chat
Starting program: /sstic2020/step3/synapse_data/show_password hashes.txt chat
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Password for chat: abc

Breakpoint 3, 0x0000555555562b90 in argon2::argon2::verify_encoded_ext ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x00007fffffffd9c0  →  0x00005555557a6b40  →  0x0000000000000001
$rcx   : 0x3               
$rdx   : 0x00005555557aaf40  →  0x000000000a636261 ("abc\n"?)
$rsp   : 0x00007fffffffd928  →  0x0000555555562b88  →   add rsp, 0x10
$rbp   : 0x00005555557a8d30  →  "$argon2id$v=19$m=102400,t=2,p=8$ShgCOIbvVzVKtPEKEQ[...]"
$rsi   : 0x4d              
$rdi   : 0x00005555557a8d30  →  "$argon2id$v=19$m=102400,t=2,p=8$ShgCOIbvVzVKtPEKEQ[...]"
$rip   : 0x0000555555562b90  →  <argon2::argon2::verify_encoded_ext+0> push rbp
$r8    : 0x0000555555593782  →   add BYTE PTR [rax], al
$r9    : 0x0               
$r10   : 0x0000555555598891  →   add BYTE PTR [rax], al
$r11   : 0x246             
$r12   : 0x0               
$r13   : 0x0000555555570600  →   push r15
$r14   : 0x4d              
$r15   : 0x0000555555562b70  →   push rax
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd928│+0x0000: 0x0000555555562b88  →   add rsp, 0x10	 ← $rsp
0x00007fffffffd930│+0x0008: 0x0000555555593782  →   add BYTE PTR [rax], al
0x00007fffffffd938│+0x0010: 0x0000000000000000
0x00007fffffffd940│+0x0018: 0x0000000000000000
0x00007fffffffd948│+0x0020: 0x0000555555560570  →   test al, 0x1
0x00007fffffffd950│+0x0028: 0x00007fffffffda03  →  0x00000100007fffff
0x00007fffffffd958│+0x0030: 0x000055555555fe00  →   mov rdx, rsi
0x00007fffffffd960│+0x0038: 0x00005555555933ac  →  ":Error: unexpected line in hashes ()\nstr::from_ut[...]"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555562b8b                  adc    BYTE PTR [rcx+rax*1], ah
   0x555555562b8e                  pop    rcx
   0x555555562b8f                  ret    
 → 0x555555562b90 <argon2::argon2::verify_encoded_ext+0> push   rbp
   0x555555562b91 <argon2::argon2::verify_encoded_ext+1> push   r15
   0x555555562b93 <argon2::argon2::verify_encoded_ext+3> push   r14
   0x555555562b95 <argon2::argon2::verify_encoded_ext+5> push   r13
   0x555555562b97 <argon2::argon2::verify_encoded_ext+7> push   r12
   0x555555562b99 <argon2::argon2::verify_encoded_ext+9> push   rbx
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "show_password", stopped 0x555555562b90 in argon2::argon2::verify_encoded_ext (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555562b90 → argon2::argon2::verify_encoded_ext()
[#1] 0x555555562b88 → add rsp, 0x10
[#2] 0x555555593782 → add BYTE PTR [rax], al
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/s $rbp
0x5555557a8d30:	"$argon2id$v=19$m=102400,t=2,p=8$ShgCOIbvVzVKtPEKEQge3g$zZwnq4W8H4LqtwiCSAULQQ"

Got myself a cleartext hash! Surely this will save me some computing time!

Cracking the hash

Let’s plug the recovered hash in a script to bruteforce the password:

#!/usr/bin/env python3
import argon2
import base64

with open('wordlist', 'r') as f:
    words = f.readlines()

for word1 in words:
    for word2 in words:
        for word3 in words:
            password = '{}{}{}'.format(word1.strip(), word2.strip(), word3.strip()).encode('utf-8')
            password_hash = argon2.low_level.hash_secret(password, salt=base64.b64decode('ShgCOIbvVzVKtPEKEQge3g=='), time_cost=2, memory_cost=102400, parallelism=8, hash_len=16, type=argon2.low_level.Type.ID, version=19)
            if password_hash == b'$argon2id$v=19$m=102400,t=2,p=8$ShgCOIbvVzVKtPEKEQge3g$zZwnq4W8H4LqtwiCSAULQQ':
                print(password)
                exit(0)

This bruteforce recovered the correct 3-words password in about 20 minutes, which is miles better than the 8 hours initially expected!

All that’s left is to get the flag:

./show_password hashes.txt chat
Password for chat: OskegLukachennMabig
Good password ☀
Welcome ☺
Your password is SSTIC{556c34304f6279736e516f3267794e4963637a637051}

We can import Gwrzienn’s Megolm key using this password, and this gives us access to another room conversation:

a

All that we’ve got to start the next step is a PCAP file and … a windows 98 tip. Buckle up, for the next one is a beast.

Next part: step 4