About:
Press enter or click to view image in full size
In this challenge, we were given a remote service that repeatedly sends binary data and asks for a “secret” value hidden inside it. Instead of manually reversing each instance, we can automate the entire process using Python and the pwntools library.
This writeup demonstrates how to solve the challenge by identifying a consistent byte pattern and extracting the secret directly from raw binary data. This can be done without using disassemblers such as Ghidra or Binary Ninja.
The challenge itself is one outside the box whereas many reverse engineering challenges rely on tools like disassemblers. It shows how sometimes pattern recognition alone is enough. If a binary repeatedly follows the same structure, we can exploit that structure instead of fully reversing it every time.
In many real-world scenarios, analysts often look for signatures or repeating structures rather than fully understanding every instruction.
Each binary blob contains a recognizable x86 instruction pattern:
c7 45 fc XX XX XX XXThe four bytes immediately following the pattern represent the secret, which we can extract, convert, and send down the pipeline.
This byte pattern corresponds to an x86 instruction that moves a value into a local stack variable. It is important to know that the value being assigned is stored directly in those 4 bytes, making it easy to extract once we recognize the pattern.
The opcode c7 45 fc is commonly used in x86 assembly for instructions such as:
mov DWORD PTR [ebp-0x4], <value>This means the program is assigning a constant value into a variable on the stack. That constant value is the secret and is what we want.
Let’s connect to the program and take a look at its response.
Press enter or click to view image in full size
Over here, we see that the binary is given, 1547565313. When we enter it, it says that we were too slow.
As we know now, speed is the problem in this challenge, meaning manual solving will fail due to time constraints. Therefore we must create a python script which:
Following is the script Max Huddleston created to solve the challenge. For it to work, we must make sure pwntools is installed. If not, we can install it with the command $pip3 install pwntools.
from pwn import *
import reHOST = "mysterious-sea.picoctf.net"
PORT = [YOUR PORT]
r = remote(HOST, PORT)
for i in range(20):
r.recvuntil(b"Here's the next binary in bytes:\n")
hex_blob = r.recvline().strip()
binary = bytes.fromhex(hex_blob.decode())
sig = b"\xc7\x45\xfc"
idx = binary.find(sig)
if idx == -1:
log.failure("Signature not found")
exit()
secret_bytes = binary[idx+3:idx+7]
secret = u32(secret_bytes)
log.success(f"Secret {i+1}: {secret}")
r.recvuntil(b"What's the secret?")
r.sendline(str(secret).encode())
print(r.recvall().decode())
from pwn import *
import reThe first line imports all functions from the pwntools library. The second line imports the module re, which stands for regular expressions (RegEx). It is useful for pattern identification and text manipulation.
HOST = "mysterious-sea.picoctf.net"
PORT = [YOUR PORT]2. Assigning Variables HOST and PORT
The third line defines the host/target server, mysterious-sea.picoctf.net. The fourth line assigns the port, which you will replace with your individual port given in the challenge instance.
for i in range(20): r.recvuntil(b"Here's the next binary in bytes:\n")
hex_blob = r.recvline().strip()
3. Looping, Assigning Variables, and Calling Functions
The line for i in range(20): creates a loop that repeats a block of code 20 times, as there are 20 questions in the challenge. It assigns each iteration an integer from 0 to 19.
The next two lines skip over the machine instructions until they reach "Here's the next binary in bytes". The program listens and captures all output until the next newline character \n.
hex_blob is a variable that stores the binary data. The function r.recvline() reads everything from its current position up to the next newline (\n) character. This newline character is removed using .strip(), which removes leading and trailing whitespace.
binary = bytes.fromhex(hex_blob.decode())4. Hex Encoding and Assigning Variables.
This line converts a hexadecimal-encoded string received as bytes into raw binary data. The variable binary stores this raw binary data.
sig = b"\xc7\x45\xfc" idx = binary.find(sig)
5. Creating a bytes literal
Join Medium for free to get updates from this writer.
The line sig = b"\xc7\x45\xfc" defines a bytes object with three raw hex values to search for. It represents the first three bytes of an x86 assembly instruction, commonly used to initialize local variables on the stack.
The next line searches for this exact byte sequence in the binary variable and stores the result in the variable idx.
if idx == -1:
log.failure("Signature not found")
exit()6. “Not Found” Error Handling
In Python, .find() returns -1 if the pattern is not found. Therefore, in the following lines, we display a failure message ("Signature not found") and exit.
secret_bytes = binary[idx+3:idx+7]
secret = u32(secret_bytes)7. Extracting a 4-byte Slice and Converting to a 32-bit Unsigned Integer
The first line slices 4 bytes from the binary variable, starting at the offset idx. It captures bytes from idx+3 to idx+7 (not including idx+7).
The next line interprets these 4 bytes as a 32-bit unsigned integer.
For data storage, pwntools universally uses little-endian format, where the smallest byte is stored first in the memory. Misinterpreting little-endian format for big-endian or others would result in incorrect values.
This is the formula the computer uses to convert:
Int = B₀+2⁸B₁+2¹⁶B₂+2²⁴B₃
log.success(f"Secret {i+1}: {secret}")8. Formatting and Matching
This line formats the secret using an f-string (f"") and matches it to the correct question. i + 1 is used because the loop starts at 0, but the questions start from 1. Additionally, it helps track progress while the script is running.
Using log.success() from pwntools is helpful because it provides clean and readable output.
r.recvuntil(b"What's the secret?")
r.sendline(str(secret).encode())print(r.recvall().decode())
9. Encoding the Secret, Decoding Binary Data, and Printing Output
The next few lines read input until they reach the string "What's the secret?".
They then encode the secret and send it to the program.
The final line receives the binary data and decodes it into a human-readable string, then prints it to the console.
Press enter or click to view image in full size
Press enter or click to view image in full size
Here, we have run the script and can see that all 20 secrets have been revealed, giving us the flag.
Final Flag: picoCTF{4u7o_r3v_g0_brrr_78c345aa}
.find(), malware pattern detection)Thank you for reading this writeup. If you have any questions, feel free to reach out to me at [email protected].