Contents

More PWN Challenge CTF Examples

PWN Challenges

PWN challenges are a type of CTF challenge that require you to exploit a binary typically running on a remote server. This can be done by exploiting a vulnerability in the binary, or by using a vulnerability in the binary to gain access to the system.

If you haven’t already read my Introduction to PWN Challenges post, I highly recommend reading that first. This post will briefly explain two additional examples from the LTDH24 CTF.

Example One: KindaCrypto

For the kindaCrypto challenge, players are given an ip and port to netcat into- through which users are presented with the following:

Decode the following message within 5 seconds...
Encoded Message:  ycd iqpoe gtbxr fbh sqvun bjdt ycd mwkz abl
Your Answer: 

Players are given five seconds to solve the encoded message. Each time a user connects to the port a new encoded message is provided. The encoding used is a caesar cipher - which is where each letter in the plaintext is shifted a fixed number of positions down or up the alphabet. For example, with a shift of 3, A becomes D, B becomes E, and so on…

In order to write a payload to solve this challenge we of course, use PWN Tools.

I would highly suggest trying to solve the challenge yourself, especially if you have already read my PWN Challenge Introduction post. The full code solution is available at the bottom of the post. However, the first key component is to establish a connection to the remote server.

from pwn import *

conn = remote('localhost', 6000)  # Replace with the actual IP and port
conn.interactive()
conn.close()

This will give us an interactive shell through the socket on the remote, just like how NC or netcat commands work.

Next we want to create some helpful functions to help us bruteforce-solve the cipher. First let’s just write a shift decode function.

def decode_with_shift(encoded_message, shift):  
    alphabet = string.ascii_lowercase  
    shifted_alphabet = alphabet[shift:] + alphabet[:shift]  
    decipher_dict = dict(zip(shifted_alphabet, alphabet))  
    decoded_message = ''.join(decipher_dict.get(char, char) for char in encoded_message)  
    return decoded_message

Now we need a way to work out if the decoded text is english. There are a number of ways to do this. My solution involves a small list of the most common english words, and a check to see if the decoded message contains any of them.

def is_english_text(text):  
    common_words = ["the", "be", "to", "of", "and", "a", "in", "that", "have", "I", "it", "for", "not", "on", "with", "he", "as", "you", "do", "at", "this", "but", "his", "by", "from", "they", "we", "say", "her", "she", "or", "an", "will", "my", "one", "all", "would", "there", "their", "what", "so", "up", "out", "if", "about", "who", "get", "which", "go", "me"]  
    return all(word in text for word in common_words)
# Brute force the shift  
decoded_message = ""  
for shift in range(1, 26):  # Try all possible shifts (1 to 25)  
    decoded_message = decode_with_shift(encoded_message, shift)  
    if is_english_text(decoded_message):  
        print(f"Decoded Message with shift {shift}: ", decoded_message)  
        break

Now we have all these sections of code we can bring it all together with a couple conn.recvuntil(), conn.recvline(), and conn.sendline(decoded_message) to send through our decoded message. Putting it all together is good practise, try it out or just take a look at the full solution :).

Full Python Solution:

from pwn import *  
import string  
  
# Function to decode the message with a given shift  
def decode_with_shift(encoded_message, shift):  
    alphabet = string.ascii_lowercase  
    shifted_alphabet = alphabet[shift:] + alphabet[:shift]  
    decipher_dict = dict(zip(shifted_alphabet, alphabet))  
    decoded_message = ''.join(decipher_dict.get(char, char) for char in encoded_message)  
    return decoded_message  
  
# Check if the decoded message makes sense (heuristic: contains common English words)  
def is_english_text(text):  
    common_words = ["the", "be", "to", "of", "and", "a", "in", "that", "have", "I", "it", "for", "not", "on", "with", "he", "as", "you", "do", "at", "this", "but", "his", "by", "from", "they", "we", "say", "her", "she", "or", "an", "will", "my", "one", "all", "would", "there", "their", "what", "so", "up", "out", "if", "about", "who", "get", "which", "go", "me"]  
    return all(word in text for word in common_words)  
  
# Connect to the server  
conn = remote('localhost', 6000)  # Replace with the actual IP and port  
  
# Receive the challenge message from the server  
conn.recvuntil("Encoded Message: ")  
encoded_message = conn.recvline().decode().strip()  
print("Encoded Message: ", encoded_message)  
  
# Brute force the shift  
decoded_message = ""  
for shift in range(1, 26):  # Try all possible shifts (1 to 25)  
    decoded_message = decode_with_shift(encoded_message, shift)  
    if is_english_text(decoded_message):  
        print(f"Decoded Message with shift {shift}: ", decoded_message)  
        break  
  
# Send the decoded message back to the server  
conn.sendline(decoded_message)  
  
# Receive the flag from the server  
conn.recvline()  
flag = conn.recvline().decode().strip()  
print("Flag: ", flag)  
  
conn.close()

Example Two: Numbers_R_Sequential

Once again we are given an IP address and a port. Through which we get the following…

Find the missing number in the sequence within 1 second...
Sequence:  [42, 112, 182, 252, '?', 392, 462, 532, 602, 672, 742]
Your Answer:

Let’s grab a connection to our box…

from pwn import *

conn = remote('localhost', 6000)  # Replace with the actual IP and port
conn.interactive()
conn.close()

Next we should write a python function that given the list we solve what the missing number is. Let’s try it…

First let’s convert our sequence into a list and replace the ? with a None.

seq = [int(num) if num != "?" else None for num in sequence]

With our seq variable we can workout what our common step value is across the sequences…

    # Find the common step
    steps = []
    for i in range(1, len(seq)):
        if seq[i] is not None and seq[i-1] is not None:
            steps.append(seq[i] - seq[i-1])
    
    if steps:
        step = steps[0]
    else:
        return None

Finally, lets calculate the missing number using our step.

    # Find the missing number
    for i in range(len(seq)):
        if seq[i] is None:
            missing_number = seq[i-1] + step
            return missing_number

Now we just need to put this all into a function that we can call within our pwntools connection. One final addition/shortcut I used for my solution is I used regular expressions to extract the sequence of both numbers and the ? character from the bytes/string received through our connection. sequence = re.findall(r'\d+|\?', sequence_str).

Full Python Solution:

from pwn import *  
import re  
  
  
# Function to find the missing number in an arithmetic sequence  
def find_missing_number(sequence):  
    # Convert the sequence to a list of numbers, replacing "?" with None  
    seq = [int(num) if num != "?" else None for num in sequence]  
  
    # Find the common step  
    steps = []  
    for i in range(1, len(seq)):  
        if seq[i] is not None and seq[i - 1] is not None:  
            steps.append(seq[i] - seq[i - 1])  
  
    if steps:  
        step = steps[0]  
    else:  
        return None  
  
    # Find the missing number  
    for i in range(len(seq)):  
        if seq[i] is None:  
            missing_number = seq[i - 1] + step  
            return missing_number  
  
    return None  
  
  
# Connect to the server  
conn = remote('localhost', 6000)  # Replace with the actual IP and port  
  
# Receive the challenge message from the server  
conn.recvuntil("Sequence: ")  
sequence_str = conn.recvline().decode().strip()  
sequence = re.findall(r'\d+|\?', sequence_str)  
print("Sequence: ", sequence)  
  
# Find the missing number  
missing_number = find_missing_number(sequence)  
print("Missing Number: ", missing_number)  
  
# Send the missing number back to the server  
conn.sendline(str(missing_number))  
  
# Receive the flag from the server  
conn.recvline()  
flag = conn.recvline().decode().strip()  
print("Flag: ", flag)  
  
conn.close()

Conclusion

PWN challenges offer a unique and engaging way to sharpen your binary exploitation skills. They require a combination of reverse engineering, programming, and problem-solving abilities. The examples discussed from the LTDH24 CTF, KindaCrypto and Numbers_R_Sequential, illustrate the variety of problems you might encounter and the creative solutions needed to tackle them.

The hardest PWN challenge I have written is the StayFit challenge, which is requires writing an evolutionary algorithm to solve. This challenge showcases the interdisciplinary nature of CTF challenges. Check out my post on Evolutionary Algorithms, at the bottom there is also an explanation of how the challenge was solved. It is written as an introduction and doesn’t require prior knowledge of machine learning or optimisation problems.

If you’re new to PWN challenges, don’t be discouraged by the complexity. Start with simple tasks, build your foundational knowledge, and gradually move on to more advanced problems. Practice is key, and there are plenty of resources available to help you along the way.

Remember, the primary goal of these challenges is to learn and improve your skills. Take your time, experiment with different approaches, and don’t hesitate to look up concepts or ask for help when needed.

Interested in seeing some more CTF challenge write ups? Click here.