Rolling My Own
Last updated
Was this helpful?
Last updated
Was this helpful?
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
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: ().
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 bytes lb
and ub
will be interpreted as machine code. This sub-sequence is called a run
. The salt value is set to ensure that the desired run
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 of key
).
If the wrong key
value is used, then the run
is unlikely to consist of useful code. The resistant code could simply try to run it anyway, and possibly crash. This explains the Illegal instruction (core dumped)
we observe when running the program. It is expecting the correct value of key
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 of lb
and ub
, and the key
'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 the salt
and the domain of the key, so given the run
, the analyst can find the key
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 create local_58
by appending chunks of the user input password and using strncat
. Each iteration, it appends 4 bytes of the password and then 8 bytes of the salt
. Ghirda did not decompile the salt
clearly. local_c8
, local_c0
, local_b8
, and local_b0
are the 4 components of the salt
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 the salt
each iteration.
The string created from the aforementioned loop (local_58
becomes param_2
) is passed to FUN_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 in local_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 of FUN_0010102b
as an argument, which means the rdi
assembly register is set to the address of FUN_0010102b
.
FUN_0010102b
is the function that prints the flag:
This function will print the flag if it receives 0x7b3dc26f1
as an argument (if the rdi
assembly register is 0x7b3dc26f1
).
So, we can determine the value of run
as defined in . The value of run
is the same as the shellcode. The shellcode has to call the function at rdi
, which is the function to print the flag, while setting the rdi
to 0x7b3dc26f1
, 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 append GpLaMjEW
, which is the reversed (because of little endian) ascii value of 0x57456a4d614c7047
, to get D1v1GpLaMjEW
. Next, we and get 23f144e08b603e724889fe489f78fa53
. The actual shellcode is 4889fe48
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 or ndisasm
(echo -ne "\x48\x89\xFE\x48" | ndisasm -b64 -
, ) 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
from pwntools
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 output H\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 the key
to get the flag. We can do this by bruteforcing the chunks of the key
/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 the key
. 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, (because of little endian). We already know the ????
for the first segment because of the hint: D1v1
.
We can write a Python to bruteforce these 3 unknown segments of the password using the corresponding known sections of the hash. Running the will print the password: D1v1d3AndC0nqu3r
.
Finally, connect to the service with nc mercury.picoctf.net 23773
and enter the password D1v1d3AndC0nqu3r
to get the flag.
picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_3c22f4e9}