Introduction Link to heading

The following challenge details a crypto challenge hosted on HackTheBox

Challenge Name: Secure Signing
Challenge Points: 20 points
Challenge Diff: Easy
Challenge Creator: [azan121468](https://app.hackthebox.com/users/377797)

It is an easy challenge that tests the various properties of XOR and its application and misconfiguration.

The Challenge Link to heading

Looking through we have the following:

─$ ls
'Secure Signing.zip'   server.py
                                  

Opening it, we get the following script

Server.py Link to heading

from hashlib import sha256
from secret import FLAG

WELCOME_MSG = """
Welcome to my Super Secure Signing service which uses unbreakable hash function.
We combine your Cipher with our secure key to make sure that it is more secure than it should be.
"""


def menu():
    print("1 - Sign Your Message")
    print("2 - Verify Your Message")
    print("3 - Exit")


def xor(a, b):
    return bytes([i ^ j for i, j in zip(a, b)])


def H(m):
    return sha256(m).digest()


def main():
    print(WELCOME_MSG)

    while True:
        try:
            menu()
            choice = int(input("> "))
        except:
            print("Try again.")
            continue

        if choice == 1:
            message = input("Enter your message: ").encode()
            hsh = H(xor(message, FLAG))
            print(f"Hash: {hsh.hex()}")
        elif choice == 2:
            message = input("Enter your message: ").encode()
            hsh = input("Enter your hash: ")
            if H(xor(message, FLAG)).hex() == hsh:
                print("[+] Signature Validated!\n")
            else:
                print(f"[!] Invalid Signature!\n")
        else:
            print("Good Bye")
            exit(0)


if __name__ == "__main__":
    main()

The script appears to work in the following ways:

  • It imports sha256 class from hashlib and FLAG from secret
  • It initializes a number of functions such as the custom xor function and uses it in the main function
  • It then calls the main function which acts a shell of sort,providing interface between the user and the server
  • The function provides the user with the chance of encoding(xoring) their message with the FLAG as much as they want(Possible bruteforce detected) and then finally returning its hash value
  • Everything else, seems to be a rabbit hole (The 2nd option)

NB:

  • The XOR function implements the ZIP function. The zip function is unique and ensure that both iterables(anything that can be looped) are always the same length. This implies that:
if len(message) < len(flag):
	flag = flag[:len(message)]
elif len(flag) < len(message):
	message = message[:len(flag)]
  • The digest value of sha256 is returned and not the hexdigest but it is still hexed so we can use the hexdigest

The attack Link to heading

The script has a vulnerability(an exploitation vector) provided by the 1st option. Why? It all stems out from the Identity Element property of XOR. This means:

A xor 0 = A
#Proof
>>> A = 10
>>> xor(A,0)[0] == A
True

This proves that if we send b"\x00" which is the bytes equivalent of 0 , we should get back the hash corresponding to a part of the flag. Since the xor function loops over individual bytes:```

(first byte of msg  ^ first byte of flag) = first_enc value
(sec byte of msg ^ sec byte of flag) = sec_enc value ...

We can be able to extract piece by piece of the flag and compare it to our individual flag and hence obtain values of the flag through bruteforce by sending various lengths of b"\x00".

The Proof of Concept Link to heading

We check if our attack actually works and if we are able to get corresponding parts of the flag. First, I’ll modify the server.py and introduce a secret.py inorder for this to work first on our local machine.

  • Server.py (Modified)
from hashlib import sha256
from secret import FLAG
import socket
import threading

WELCOME_MSG = """
Welcome to my Super Secure Signing service which uses unbreakable hash function.
We combine your Cipher with our secure key to make sure that it is more secure than it should be.
"""

def xor(a, b):
    return bytes([i ^ j for i, j in zip(a, b)])

def H(m):
    return sha256(m).digest()

def handle_client(client_socket):
    client_socket.send(WELCOME_MSG.encode())

    while True:
        client_socket.send("\n1 - Sign Your Message\n2 - Verify Your Message\n3 - Exit\n> ".encode())
        choice = client_socket.recv(1024).decode().strip()

        if choice == '1':
            client_socket.send("Enter your message: ".encode())
            message = client_socket.recv(1024).strip()
            hsh = H(xor(message, FLAG)).hex()
            client_socket.send(f"Hash: {hsh}\n".encode())
        elif choice == '2':
            client_socket.send("Enter your message: ".encode())
            message = client_socket.recv(1024).strip()
            client_socket.send("Enter your hash: ".encode())
            hsh = client_socket.recv(1024).strip().decode()
            if H(xor(message, FLAG)).hex() == hsh:
                client_socket.send("[+] Signature Validated!\n".encode())
            else:
                client_socket.send("[!] Invalid Signature!\n".encode())
        elif choice == '3':
            client_socket.send("Good Bye\n".encode())
            client_socket.close()
            break
        else:
            client_socket.send("Try again.\n".encode())

def main():
    try:
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.bind(('0.0.0.0', 1337))
        server.listen(5)
        print("[*] Listening on 0.0.0.0:1337")

        while True:
            client_socket, addr = server.accept()
            print(f"[*] Accepted connection from {addr[0]}:{addr[1]}")
            client_handler = threading.Thread(target=handle_client, args=(client_socket,))
            client_handler.start()

    except KeyboardInterrupt:
        print("\n[*] Server shutting down.")
        server.close()

if __name__ == "__main__":
    # Check for required modules
    try:
        import socket
        import threading
        from hashlib import sha256
        from secret import FLAG
    except ImportError as e:
        print(f"Error: {e.name} module is required but not installed.")
        exit(1)

    main()
  • secret.py –> Requires to be in byte form else we cannot XOR it properly
FLAG = b"HTB{FAKE_FLAG_FOR_TESTING}"

We will be testing using pwntools as it provides various tools which are easy to use and already-built in. Knowing very well that the first few bytes of the flag(this helps sometimes in other challenges) contain HTB{ , we can use that so that we verify our attack.Let us see:

from pwn import xor,connect
from hashlib import sha256

# Sets up connection between the server and us
host,port = "127.0.0.1:1337".split(":")
rem = connect(host,port)
flag_part = b"HTB{"

# Verifying 
part_1 = flag_part[:1] # H
rem.sendlineafter(b">", b"1") # Data should be sent as bytes as the server decodes the bytes and interprets it
rem.sendlineafter(b":", b"\x00" * 1) # Only one byte is required as the message is only 1 byte long
hash_1 = rem.recvuntil(b"\n").strip().split()[-1].decode() # Extracts the hash
print(f"Test 1: {hash_1 == sha256(part_1).hexdigest()}") # Does the first test (Is our first attack true)

part_2 = flag_part[:2] # HT
rem.sendlineafter(b">", b"1") # Data should be sent as bytes as the server decodes the bytes and interprets it
rem.sendlineafter(b":", b"\x00" * 2) # 2 bytes needed as we have 2 bytes of messgae
hash_2 = rem.recvuntil(b"\n").strip().split()[-1].decode() # Extracts the hash
print(f"Test 2: {hash_2 == sha256(part_2).hexdigest()}") # Does the first test (Is our first attack true)

part_3 = flag_part[:3] # HTB
rem.sendlineafter(b">", b"1") # Data should be sent as bytes as the server decodes the bytes and interprets it
rem.sendlineafter(b":", b"\x00" * 3) # We need 3 bytes since we have 3 bytes of message(HTB)
hash_3 = rem.recvuntil(b"\n").strip().split()[-1].decode() # Extracts the hash
print(f"Test 3: {hash_3 == sha256(part_3).hexdigest()}") # Does the first test (Is our first attack true)

rem.close()

Upon testing we get:

└─$ python3 sol.py
[+] Opening connection to 127.0.0.1 on port 1337: Done
Test 1: True
Test 2: True
Test 3: True
[*] Closed connection to 127.0.0.1 port 1337

So that means the idea is right and now we can write our exploit. NB:

  • Notice that we knew the values of the flag before we run the sol.py. After the { We know nothing, so we need to check every value of hash with a suspected value (bruteforce method).
  • We need to optimize it (making it seem fast and in the process) clean.

The Solution Link to heading

Keeping all that in mind, let us write the final script and run it:

from pwn import remote,string
from hashlib import sha256

# Sets up connection between the server and us
host,port = "127.0.0.1:1337".split(":")
rem = remote(host,port)
flag = "H"

progress_report = rem.progress(flag)

def bruteforce(flag_part):
    rem.sendlineafter(b">", b"1") # Data should be sent as bytes as the server decodes the bytes and interprets it
    rem.sendlineafter(b":", b"\x00" * (len(flag_part) + 1)) # So that we can retrieve the next value in the flag  --> Add 1
    hash_value = rem.recvuntil(b"\n").strip().split()[-1].decode() # Extract hash --> Obtained through initial testing

    # Bruteforce every value in printable characters
    for char in string.printable:
        # Get the hash value of the flag+char and test it
        char_hash = sha256((flag_part+char).encode()).hexdigest()
        if  char_hash == hash_value:
            return flag_part + char
            break

    return flag_part


# The last character is obviously } so that kills the loop when it is achieved

while "}" not in flag:
    flag = bruteforce(flag)
    progress_report.status(flag)

rem.close()

Running it, we get:

└─$ python3 sol.py
[+] Opening connection to 127.0.0.1 on port 1337: Done
[+] H: HTB{FAKE_FLAG_FOR_TESTING}
[*] Closed connection to 127.0.0.1 port 1337

And then we can test it on the sever

Server-Side Link to heading

Replace the string with the copied address from HackTheBox (just copy, delete and paste). Upon running:

└─$ python3 sol.py
[+] Opening connection to 159.65.20.166 on port 30280: Done
[<] H: HTB{r0ll1n6_0v3r_x0r_w17h_h@5h1n6_0r@cl3_15_n07_s3cur3!@#}
[*] Closed connection to 159.65.20.166 port 30280

And like that, we get the flag.

Notes Link to heading

A lot of testing is first required, understanding the server.py is crucial to solving the challenge and you are mandated to know what to exploit. Writing the solution also requires testing and debugging as you are required to fully understand what to do. Always have an attack plan that you know is going to work

“Whatever the mind of man can conceive and believe, it can achieve.” — Napoleon Hill