> For the complete documentation index, see [llms.txt](https://youssefsahnoun.gitbook.io/youssefsahnoun/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://youssefsahnoun.gitbook.io/youssefsahnoun/cryptohack-writeups/rsa/blinding-light.md).

# Blinding Light

SECTION : SIGNATURE PART1

<figure><img src="https://900490735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkFkdoZ9Zx4nBg7cvLHlR%2Fuploads%2FGUnxq3N2ujRDoFMBhe76%2Fimage_2025-09-23_161729821.png?alt=media&amp;token=3fab8f4c-c6d3-4896-a56a-fb3da00093e0" alt=""><figcaption></figcaption></figure>

## Challenge Script:

```python
#!/usr/bin/env python3

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

FLAG = "crypto{?????????????????????????????}"
ADMIN_TOKEN = b"admin=True"


class Challenge():
    def __init__(self):
        self.before_input = "Watch out for the Blinding Light\n"

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

        elif your_input['option'] == 'get_pubkey':
            return {"N": hex(N), "e": hex(E) }

        elif your_input['option'] == 'sign':
            msg_b = bytes.fromhex(your_input['msg'])
            if ADMIN_TOKEN in msg_b:
                return {"error": "You cannot sign an admin token"}

            msg_i = bytes_to_long(msg_b)
            return {"msg": your_input['msg'], "signature": hex(pow(msg_i, D, N)) }

        elif your_input['option'] == 'verify':
            msg_b = bytes.fromhex(your_input['msg'])
            msg_i = bytes_to_long(msg_b)
            signature = int(your_input['signature'], 16)

            if msg_i < 0 or msg_i > N:
                # prevent attack where user submits admin token plus or minus N
                return {"error": "Invalid msg"}

            verified = pow(signature, E, N)
            if msg_i == verified:
                if long_to_bytes(msg_i) == ADMIN_TOKEN:
                    return {"response": FLAG}
                else:
                    return {"response": "Valid signature"}
            else:
                return {"response": "Invalid signature"}

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


import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13376)

```

## Explanation:

Challenge logic:

* Sets a token `ADMIN_TOKEN = b"admin=True"` .
* Exposes three actions:
  * `get_pubkey` which returns the public key.
  * `sign` which signs any message with any **size** except if it contains the string `"admin=True"` .&#x20;
  * `verify`  which takes a message and a signature, computes `verified = pow(signature, E, N)` then checks if your message matches the signature and if the message is the `ADMIN_TOKEN`. It also verifies that the message is in the range ]0;N\[.\
    so the attacker is not able to pass the message `m=ADMIN_TOKEN + N` which is congruent to `ADMIN_TOKEN` and has the same signature.

## Exploit:

The misconfiguration here is that the challenge only verifies that the message is contained in the range ]0;N\[  in the verify function but let's us sign any message with any size. So by simply signing `m=ADMIN_TOKEN + N`  we can pass the verification. First we need to understand that:

```
(ADMIN_TOKEN + kN) ≡ m  (mod N)
=> (ADMIN_TOKEN + kN)^d ≡ m^d (mod N)
```

So the server processes our `(ADMIN_TOKEN + kN)` as follows:

* `Our_signature = (ADMIN_TOKEN + kN)^d (mod N)` which is equal to `target_signature= (ADMIN_TOKEN)^d (mod N)`
* `verifiied = our_signature^e (mod N) = (ADMIN_TOKEN + kN)^(d*e) (mod N)`, and with `d*e = 1` we have `verified = ADMIN_TOKEN (mod N)`.

```python
#!/usr/bin/env python3
import json
from pwn import remote
from Crypto.Util.number import bytes_to_long
from pkcs1 import emsa_pkcs1_v15

def main(host, port):
    # Connect to the challenge server
    io = remote(host, port)

    
    io.recvline(timeout=2)
    target="admin=True"
    # Receive the public key
    io.sendline(json.dumps({"option": "get_pubkey"}).encode())
    data = json.loads(io.recvline().strip())
    N = int(data['N'], 16)
    e = int(data['e'],16)
    # pass TOKEN + N as our message
    io.sendline(json.dumps({"option": "sign","msg":hex(bytes_to_long(target.encode())+N)[2:]}).encode())
    data = json.loads(io.recvline().strip())
    target_signature=data['signature']
    # verify with message = Token and our signature
    io.sendline(json.dumps({"option": "verify","msg":hex(bytes_to_long(target.encode()))[2:],"signature":target_signature}).encode())
    data = json.loads(io.recvline().strip())
    print(data)

if __name__ == '__main__':
    main('socket.cryptohack.org', 13376)
```

## Execution:

```bash
python3 solve.py   
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/kali/.cache/.pwntools-cache-3.13/update to 'never' (old way).
    Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):
        [update]
        interval=never
[*] You have the latest version of Pwntools (4.14.1)
[q] Opening connection to socket.cryptohack.org on port 13376: Trying 134.122.11[+] Opening connection to socket.cryptohack.org on port 13376: Done
{'response': 'crypto{m4ll3************}'}
[*] Closed connection to socket.cryptohack.org port 13376

```
