Post

Deadface 2024 CTF

This is a writeup for DeadFace ctf 2024

pic1.png

Reverse Engineering

Cereal Killer 01

pic1.png

We are given a binary and an executable file. Running the executable we are prompted for a password I loaded up the binary file in IDA Pro and looked at the strings maybe the password is hardcoded lets see! pic1.pngThe string obboreel stands out trying it out as the password and it was not the password. I did spin up x64dbg to debug the file and set a breakpoint before the jump of printing the bad message Access Denied.

pic1.png

After

pic1.png

The input is being reversed the word booberry looking up the word its a cereal and trying it out it was definetly the password and we get the flag!

pic1.png

Cereal Killer 02

pic1.png

Running the binary gave me the cryptic message, “Please enter the password.” Challenge accepted. I tossed this bad boy into IDA Pro, and right away, I zeroed in on the function sub_13DC. The name didn’t scream “password handler,” but it was clearly doing something important. A few strings in the output hinted it might be a password validation function, so let’s crack it wide open.

pic1.png

sub_13DC – The Password Sieve

So, we’ve got a big ol’ pile of variables in here—s, s1, s2, and some more arrays. This is where the binary initializes everything, probably hashing or messing with the password input in some convoluted way. Here’s the fun part:

  1. The Password Prompt: The function prompts for the password, which gets shoved into inputpassword. Right after, a string of mysterious sub-functions start grinding away at the input. This was a clear invitation to reverse engineer each one.

  2. Breaking Down the Mystery Functions: First up, sub_132F. This function takes a string like "976e86dafec99d2da69bbaa762aba8cc" and stores some hashed result into s2. It was hashing hardcoded strings, which meant the binary was expecting something specific to pass that check
  3. So, after finding the password yellowschoolbus using the hash 104e5dc03561ebf96acf9a0b2b5f184f online, I confidently punched it into the binary. But guess what? It didn’t work! Classic case of getting your hopes up, right?

Then I spotted that sneaky second hash, 976e86dafec99d2da69bbaa762aba8cc. I knew that one was going to need some serious firepower, likely requiring Hashcat to crack it.

Instead, I opted to patch the program. I mean, who wouldn’t choose a shortcut when the end goal is just a patch away? So, I skipped the hassle of cracking that second hash, and just like that, I was ready to snag the flag without any fuss! pic1.png

To really wrap things up, I decided to NOP (No Operation) the JNE instruction in the binary.

pic1.png

Cereal Killer 03

pic11.png We’ve conquered the 250-point challenges, and now it’s time to gear up for the big leagues as we tackle the 500-point ones!

When analyzing the pseudocode we see the password is being hashed inspecting the memory layout I did set a breakpoint before jump is taken the cmp eax, [ecx] synchronizing the ecx variable with the hex view we can see our input password stephen is md5 hashed 7F F3 67 97 53 91 30 F7 74 45 F4 8D A5 D4 A1 26

pic12.pngThe correct password being saved in the edx register I synchronized it as well and it sits on top of the ecx register EC C8 8B 0A F9 02 53 E8 70 06 97 4B 2F 3F DC

I did assemble the edx bytes to the ecx to make a match for the comparison to pass

pic15.png

Running the executable after setting a breakpoint before retn to get the flag pic13.png

Cereal Killer 04

pic16.png

Running the binary we see Access Violation occurred. Ahem seems like its trying to access a memory location that is not valid. Upon inspecting the strings and the source code we see the Windows API functions like VirtualAlloc and VirtualProtect

pic23.png

We see the program accesses an invalid memory location 2BAD2BAD I did set an ip to another instruction from the call accessing the invalid memory location pic17.png

We see the program tries again to access an invalid memory location DEADFACE I did set an ip to another instruction from the call accessing the invalid memory location running the executable it terminated. I did inspect the last call to the invalid memory location DEADFACE by stepping into it

pic18.png

We see as highlighted below the invalid memory address being saved to eax and a jmp being made to it causing an access violation error. pic20.png

I did set an ip after the jmp instruction pic21.png

The executable loads dll's and loads an audio from a remote server and plays it reading the flag out loud! pic22.png

pic24.png

We are given a jar file running it we see it requires a password pic26.png

1. Decompiling
First, I opened the JAR file in jadx-gui and found the XOR function used to decrypt a URL. Here’s the relevant function:

1
2
3
4
5
6
7
private static byte[] decryptURL(byte[] bArr, String str) {
    byte[] bArr2 = new byte[bArr.length];
    for (int i = 0; i < bArr.length; i++) {
        bArr2[i] = (byte) (bArr[i] ^ str.charAt(i % str.length()));
    }
    return bArr2;
}

pic27.png The XOR function loops through encryptedURL, applying a password in a repeating cycle.

The challenge required us to decrypt an encrypted URL using a password. While the password wasn’t explicitly stated, the decryption function provided a key insight: it checks if the decrypted URL starts with "https." This led me to deduce that the password itself might be derived from the string format of the URL. Given that “https” is a common prefix for secure web addresses, it became a logical choice for the password to successfully decrypt the byte array.

By using “https” as the key, we were able to reverse the XOR operation, revealing the full URL.. Since XOR is a reversible operation, I crafted a quick Python script to perform the decryption:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
encrypted_url = [42, 6, 68, 64, 7, 120, 93, 31, 83, 17, 48, 23, 81, 92, 90, 46, 11, 68, 68, 27, 44, 30, 81, 82, 7, 108, 29, 66, 87, 91, 33, 23, 66, 85, 21, 46, 1, 31, 86, 6, 45, 29, 68, 82, 6, 45, 29, 68, 30, 30, 50, 23, 87]

Known plaintext prefix
prefix = "https"

Derive key from prefix and first few bytes of encrypted_url
key = [encrypted_url[i] ^ ord(prefix[i]) for i in range(len(prefix))]
Extend key to match the length of encrypted_url
full_key = (key * (len(encrypted_url) // len(key) + 1))[:len(encrypted_url)]

Decrypt the URL
decrypted_url = ''.join(chr(b ^ k) for b, k in zip(encrypted_url, full_key))

print("Derived Password (Key):", ''.join(chr(k) for k in key))
print("Decrypted URL:", decrypted_url)

pic25.png

This post is licensed under CC BY 4.0 by the author.