Rolling My Own
Problem
I don't trust password checkers made by other people, so I wrote my own. It doesn't even need to store the password! If you can crack it I'll give you a flag. remote
nc mercury.picoctf.net 23773
Solution
Running the program and typing in a password produces an
Illegal instruction (core dumped)
, which is interesting:Summary of the paper linked in the hints: Anti-disassembly using Cryptographic Hash Functions (Publisher Link).
The idea for anti-disassembly is to combine a
key
with a "salt" value, and feed the result as input into a cryptographic hash function. A sub-sequence from the output of the hash function between byteslb
andub
will be interpreted as machine code. This sub-sequence is called arun
. The salt value is set to ensure that the desiredrun
appears in the output of the hash function when the correct key is provided.The task of the analyst is to determine precisely what the code does when executed (the value of
run
) and what the target is (the correct value ofkey
).If the wrong
key
value is used, then therun
is unlikely to consist of useful code. The resistant code could simply try to run it anyway, and possibly crash. This explains theIllegal instruction (core dumped)
we observe when running the program. It is expecting the correct value ofkey
but when provided with the incorrect one it runs the algorithm anyway and thus executes meaningless binary code. What we type in for the password impacts the code that the program executes.An analyst who finds some resistant code has several pieces of information immediately available. The
salt
, the values oflb
andub
, and thekey
's domain (although not its value) are not hidden.Information hidden from an analyst:
The
key
's value. An analyst may know that a computer virus using this anti-disassembly technique targets someone or something, but would not be able to uncover specifics.The
run
. The analyst knows thesalt
and the domain of the key, so given therun
, the analyst can find thekey
by exhaustively testing every possible value.
We start by decompiling the binary using Ghidra. The main function is below:
This function takes our input password and saves it to
acStack153
. Then it runs a loop 4 times to createlocal_58
by appending chunks of the user input password and usingstrncat
. Each iteration, it appends 4 bytes of the password and then 8 bytes of thesalt
. Ghirda did not decompile thesalt
clearly.local_c8
,local_c0
,local_b8
, andlocal_b0
are the 4 components of thesalt
and each one is 8 bytes. The loop index variable,local_100
is bit shifted to the left by 3 (local_100 << 3
) which moves the pointer to the next chunk of thesalt
each iteration.The string created from the aforementioned loop (
local_58
becomesparam_2
) is passed toFUN_00100e3e
, which is shown below:In short, this function takes the string computed in the previous step, splits it into four 12 byte chunks, computes the MD5 hash of each chunk, and stores the hashes.
Now, back in the
main
function, the program takes 4 bytes from each of the hashes starting at indexes 8, 2, 7, and 1, which are stored inlocal_e8
. For example, the program extracts the 4 bytes starting at byte 8 from the first hash. These sequences are combined to create 16 bytes of shellcode. The shellcode is called with the address ofFUN_0010102b
as an argument, which means therdi
assembly register is set to the address ofFUN_0010102b
.FUN_0010102b
is the function that prints the flag:This function will print the flag if it receives
0x7b3dc26f1
as an argument (if therdi
assembly register is0x7b3dc26f1
).So, we can determine the value of
run
as defined in the paper. The value ofrun
is the same as the shellcode. The shellcode has to call the function atrdi
, which is the function to print the flag, while setting therdi
to0x7b3dc26f1
, since the flag printing function checks for this argument, all while being exactly 16 bytes.There is a hint that tells us that the password starts with
D1v1
, so we can determine the first 4 bytes. Let's manually apply the program. First, we appendGpLaMjEW
, which is the reversed (because of little endian) ascii value of0x57456a4d614c7047
, to getD1v1GpLaMjEW
. Next, we calculate the MD5 hash ofD1v1GpLaMjEW
and get23f144e08b603e724889fe489f78fa53
. The actual shellcode is4889fe48
because of the indexes discussed in step 5. The first index is 8 so we go to byte 8 and copy the next 4 bytes. You can use an online disassembler orndisasm
(echo -ne "\x48\x89\xFE\x48" | ndisasm -b64 -
, more info) to convert this shellcode to assembly. The assembly is:Since we know what the shellcode is supposed to do we can predict that the shellcode in assembly is probably:
We can use the
asm
frompwntools
to compile the assembly to shellcode:python -c "from pwn import asm; print(asm('mov rsi, rdi; movabs rdi, 0x7b3dc26f1; call rsi; ret', arch='amd64'))"
. This command will outputH\x89\xfeH\xbf\xf1&\xdc\xb3\x07\x00\x00\x00\xff\xd6\xc3
, which we can manually convert to raw bytes to get\x48\x89\xfe\x48\xbf\xf1\x26\xdc\xb3\x07\x00\x00\x00\xff\xd6\xc3
.Now, we known the
run
and just need to figure out thekey
to get the flag. We can do this by bruteforcing the chunks of thekey
/password until we get MD5 hashes where the corresponding bytes match the bytes in the shellcode. In step 3, an intermediate form of the data is created by appending chunks of the user input password and the salt. In step 4, this intermediate form is split into 4 chunks and the md5 hash of each chunk is calculated. So, the first 4 bytes/characters of each of the 4 sections of the intermediate form are thekey
. In step 5, the sections of 4 bytes are extracted from the hashes at various offsets (8, 2, 7, and 1). We know what 4 bytes should be present at the offsets in each of these 4 hashes. Thus, we can conduct an MD5 bruteforce. This is better explained graphically, where?
is a character of the key and__
is a byte that doesn't matter:The salt values being hashed were taken from the decompiled
main
function, converted from hexadecimal to ascii, and then reversed (because of little endian). We already know the????
for the first segment because of the hint:D1v1
.Finally, connect to the service with
nc mercury.picoctf.net 23773
and enter the passwordD1v1d3AndC0nqu3r
to get the flag.
Flag
picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_3c22f4e9}
Last updated