Francesco Abate - Computer engineer

Home    CTFs  Misc  Rocketry  About  Contacts       

Welcome to my website! Here you can find CTF writeups, embedded projects, desktop and web applications, tutorials and much more.

View on GitHub
7 April 2025

Vote for Pedro

by Francesco Abate

Vote for Pedro is a RSA challenge from CryptoHack

🗳️ CTF Writeup: Vote for Pedro

If you want my flag, you better vote for Pedro! Can you sign your vote to the server as Alice?


🔍 Challenge Overview

You’re given access to:

🧠 Goal

Forge a vote for Pedro and submit a valid RSA signature as if it came from Alice.


📄 Challenge Files

alice.key

N = 22266616657574989868109324252160663470925207690694094953312891282341426880506924648525181014287214350136557941201445475540830225059514652125310445352175047408966028497316806142156338927162621004774769949534239479839334209147097793526879762417526445739552772039876568156469224491682030314994880247983332964121759307658270083947005466578077153185206199759569902810832114058818478518470715726064960617482910172035743003538122402440142861494899725720505181663738931151677884218457824676140190841393217857683627886497104915390385283364971133316672332846071665082777884028170668140862010444247560019193505999704028222347577

e = 3

13375.py

from Crypto.Util.number import bytes_to_long, long_to_bytes
from utils import listener

Imports essential utilities: bytes_to_long and long_to_bytes help convert between integers and byte strings, crucial for RSA operations. listener is a helper provided by Cryptohack to handle incoming socket connections for CTF interactions.

FLAG = "crypto{????????????????????}"

Placeholder for the flag that will be returned upon successful submission.

class Challenge():
    def __init__(self):
        self.before_input = "Place your vote. Pedro offers a reward to anyone who votes for him!\n"

Defines the challenge class with an introductory message shown to the player before they send input.

    def challenge(self, your_input):
        if 'option' not in your_input:
            return {"error": "You must send an option to this server"}

Main method handling user input. If the input doesn’t include an “option” key, it immediately responds with an error.

        elif your_input['option'] == 'vote':
            vote = int(your_input['vote'], 16)

Handles the “vote” option. Expects the input value to be a hex string representing an integer — this is supposed to be the signature forged by the attacker.

            verified_vote = long_to_bytes(pow(vote, ALICE_E, ALICE_N))

Performs RSA signature verification by computing vote^e mod N, then converts the resulting integer to bytes.

            vote = verified_vote.split(b'\x00')[-1]

Removes padding (very simplistically) by splitting the decrypted message at null bytes and taking the last chunk, assuming that is the meaningful message.

            if vote == b'VOTE FOR PEDRO':
                return {"flag": FLAG}
            else:
                return {"error": "You should have voted for Pedro"}

Checks if the message matches the expected vote content. If so, it reveals the flag; otherwise, it returns an error.

        else:
            return {"error": "Invalid option"}

Fallback if the provided option is not recognized (i.e., not “vote”).

import builtins; builtins.Challenge = Challenge
listener.start_server(port=13375)

Registers the challenge class globally so the server can find it, and starts the socket server on port 13375, waiting for connections from challengers.

Solution

This script is an exploit for the “Vote for Pedro” CTF challenge. It leverages RSA’s low public exponent (e=3) vulnerability by using a pre-computed signature to forge a valid vote. Below is a block-by-block explanation of the code.

from pwn import *
from json import *
from Crypto.Util.number import long_to_bytes, bytes_to_long

Imports:

def send(hsh):
    return r.sendline(dumps(hsh))

Helper Function:

ALICE_N = 22266616657574989868109324252160663470925207690694094953312891282341426880506924648525181014287214350136557941201445475540830225059514652125310445352175047408966028497316806142156338927162621004774769949534239479839334209147097793526879762417526445739552772039876568156469224491682030314994880247983332964121759307658270083947005466578077153185206199759569902810832114058818478518470715726064960617482910172035743003538122402440142861494899725720505181663738931151677884218457824676140190841393217857683627886497104915390385283364971133316672332846071665082777884028170668140862010444247560019193505999704028222347577
ALICE_E = 3

RSA Key Constants:

r = remote('socket.cryptohack.org', 13375)
print(r.recv())

Remote Connection:

vote = 855520592299350692515886317752220783

Pre-computed Signature:

option = {
    'option': 'vote',
    'vote': hex(vote)
}
send(option)

Building and Sending the Payload:

get = loads(r.recv())
flag = get['flag']
print(flag)

Receives the server’s response, decodes the JSON string into a Python dictionary, extracts the flag from the response, and prints it. If the forged signature was valid, the server responds with the flag.

Overall, the script automates the process of connecting to the server, sending a precomputed forged RSA signature (that takes advantage of the low exponent and lack of padding), and printing the flag received as a result.

In the end you obtain the flag :

crypto{y0ur_v0t3_i5_my_v0t3}
tags: CTF - cryptography - Cryptohack - Cybersecurity