craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

[CTF] Me is Mey (Custom SHA256 bypass)

|

For the concluding challenge, you revisit the creator's final statement, "Only the one who is me can be me". After careful consideration, you deduce that these words refer to the creator's personal signature. The key to unlocking the final challenge lies in accurately reproducing this signature.

Understanding the challenge

The following server side code is provided:

from secret import FLAG
from hashlib import sha256

ctf_digest = 'fd9c3208d1bc1a2678e3aaf7c3ce498ee754d112a3cbae586cb7dce7f45cc582'

class hash():

    def __init__(self, message):
        self.message = message

    def rotate(self, message):
        return [((b >> 4) | (b << 3)) & 0xff for b in message]

    def hexdigest(self):
        rotated = self.rotate(self.message)
        return sha256(bytes(rotated)).hexdigest()

def main():
    original_message = b"ready_play_one!"
    original_digest = hash(original_message).hexdigest()
    print(f"Find a message that generates the same hash as this one: {original_digest}")

    while True:
        message = input("Enter your message: ")
        message = bytes.fromhex(message)
        print(b"Got message: " + message)

        digest = hash(message).hexdigest()

        if ((original_digest == digest) and (message != original_message)):
            print("Well done")
        else:
            print("Conditions not satisfied!")

if __name__ == '__main__':
    main()

The goal of the challenge is to find a message, different than the original message, that has the same SHA256 value. Notice that before computing the SHA256, the message is rotated using a custom function.

Vulnerability identification

Notice that each letter of the original message is rotated individually using the custom rotate function. The vulnerability lies in the fact that multiple bytes can actually rotate to the same value. To pass the checks we only need to find a single byte that rotates to the same value as a letter from the original message.

Exploitation

The following script brute-forces the rotated value for each possible byte:

from hashlib import sha256

rotate_byte = lambda b: ((b >> 4) | (b << 3)) & 0xff
rotate_msg = lambda m: [rotate_byte (b) for b in m]

m = b"ready_play_one!"
print('Original message: ' + str(["%02X" % n for n in m ]))

rotated = rotate_msg(m)
print('Rotated message: ' + str(["%02X" % n for n in rotated]))

digest = sha256(bytes(rotated)).hexdigest()
print('SHA256: ' + digest)

# Find another message that rotates to the same value
for x in range(256):
    print("%02X" % x, "%02X" % rotate_byte(x))

For context the original message and it’s rotated version look like this:

Original message (hex): 72656164795F706C61795F6F6E6521
Original message (bytes): [‘72’, ‘65’, ‘61’, ‘64’, ‘79’, ‘5F’, ‘70’, ‘6C’, ‘61’, ‘79’, ‘5F’, ‘6F’, ‘6E’, ‘65’, ‘21’]
Rotated message (bytes): [‘97’, ‘2E’, ‘0E’, ‘26’, ‘CF’, ‘FD’, ‘87’, ‘66’, ‘0E’, ‘CF’, ‘FD’, ‘7E’, ‘76’, ‘2E’, ‘0A’]

Using the brute-force script we can see that 65 rotates to 2E, but so do the E4 and E5 bytes. So a possible solution of the challenge is 72E46164795F706C61795F6F6E6521:

$ python server.py
Find a message that generates the same hash as this one: fd9c3208d1bc1a2678e3aaf7c3ce498ee754d112a3cbae586cb7dce7f45cc582
Enter your message: 72E46164795F706C61795F6F6E6521
Well done