This is part of my series of writeups on the Shabak 2021 CTF challenges. See the complete collection here.
Introduction
The challenge description reads:
Our agent from the field has obtained a few files related to a program that is used by a terrorist organization!
This zip contains the program and a db file.
We need your help parsing the db!
Give it your best, we heard that they use it and that it might contain some intresting information for you!
Good Luck !!
We are presented with two encrypted files - a database and a DLL - and one program. There must be a a clue to the encryption inside the program, so let's dive in.
When once isn't enough
The program greets us with the following screen:
Presumably, we'll have to figure out the PIN. But first, we need to determine what we're dealing with. Throwing the EXE into CFF Explorer reveals that it is a .NET executable:
Great, so unless the thing is obfuscated, this is going to be a breeze! Let's throw it into dotPeek and see what we can learn.
Firstly, the code is not obfuscated, which is a relief. Secondly, there is a PinForm
class which looks promising. Specifically, it has a method called
buttonCheckPin_Click
:
|
|
Right, so what does this do? First, the function verifies that the length of the PIN
is indeed 4 characters. Note that there is no check that the characters are digits,
as hinted by the dialog box. Then, the code proceeds to repeatedly calculate the MD5
hash on the PIN, and stores the result after 10 and 20 iterations (lines 14-20).
If the hash after 20 iterations matches 2D3114BCC2E5C58BBAC77F04237723D9
,
the code uses the hash after 10 iterations as the key to decrypt both the database
and the DLL (lines 21-25).
Now, the encryption used is AES, so unless by the time you are reading this somebody
managed to break it, we'll have to bruteforce the password. Since we know the PIN
has to be typed-in by hand, and since the DoMD5
method expects the characters to be
ASCII, we can restrict ourselves to ASCII letters, digits, punctuation, and space
(' '
).
Note: the DoMD5
method outputs the hash in uppercase. Make sure your bruteforce
code does as well.
An MD5 hash a day keeps the blockchain away
Excitedly, we type the password into the dialog box. It disappers, and in its place we observe:
Inspecting the newly-decrypted db.txt
file, we can see it has several records similar
to:
|
|
We can also add new records to the DB, by filling out the "Sender", "Recipient", and "What" fields in the program, pressing "Push Transaction", and then "Sign Current Block". Playing around with the program we can observe several things:
- The first number in each block appears to be a running index. The bottom-most block has index 0, the one above has index 1, and so on.
- The second number specifies the number of transactions.
- Then, we have the actual transactions, in the format:
Sender -> Recipient [hex, hex, ...]
- The number of hex strings in each transaction appears to be equal to the length of the "What" field.
- The hex strings look like MD5 hashes, but we can't be sure about that yet.
We can't load the previous transactions from the DB, so presumably we'll have to do some more bruteforcing. In order to do that, we need to understand how the transactions are written into the DB, and it seems that the DLL is responsible for this. Throwing it into our favorite decompiler, however, reveals that it was written in C++, which promises many "fun" hours of reversing. Is there an easier way?
What happens if we place just a single letter in the "What" field? Inputting "a"
(the letter 'a') into the field and signing the block yields:
|
|
And a quick check reveals that the hex string is the MD5 hash of "a"
. What about
"aa"
?
|
|
The first string remains unchanged, but the second one is clearly not the MD5
hash of "a"
. Assuming that the hashes are calculated based only on the "What" field,
perhaps this is the hash of "aa"
? Another quick check reveals that this is so.
Hypothesis: given a transaction with a "What" field $s$, the $i$-th hash in the database is a hash of $s[0 \dots i]$.
This can be checked with, for example, the string "abc"
:
|
|
And, indeed:
|
|
Armed with this knowledge, we can bruteforce the transactions already stored in the DB. To do so, we bruteforce the first letter, then the second letter appended to the first, and so on. And again, since the characters should be typable by hand, we can restrict the character set as we did when bruteforcing the PIN.
In fact, this is much quicker than bruteforcing the whole "What" field at once. With our character set1, bruteforcing a string of length $n$ takes in the worst case $95^{n}$ calculations of the MD5 function. Bruteforcing letter-by-letter, however, takes at most $95n$ calculations.
FIN
-
string.digits + string.ascii_letters + string.punctuation + " "
in Python. ↩︎