BBGun06
4 minutes to read
We are given a Python source code that asks for a signature. This is the main
function:
def main(s):
rsa = RSA(2048)
user, data = parseEmail()
signature = rsa.sign(user)
rsa.verify(user, signature)
headers = generateHeaders(rsa, signature)
valid_email = headers + data
sendMessage(s, valid_email + "\n\n")
try:
forged_signature = recieveMessage(s, "Enter the signature as hex: ")
forged_signature = bytes.fromhex(forged_signature)
if not rsa.verify(user, forged_signature):
sendMessage(s, "Invalid signature")
if different(rsa, signature, forged_signature):
sendMessage(s, FLAG)
except:
sendMessage(s, "An error occured")
The function rsa.verify
will do this:
def verify(self, message, signature):
keylength = len(long_to_bytes(self.n))
decrypted = self.encrypt(signature)
clearsig = decrypted.to_bytes(keylength, "big")
r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
m = r.match(clearsig)
if not m:
raise VerificationError('Verification failed')
if m.group(1) != self.asn1:
raise VerificationError('Verification failed')
if m.group(2) != sha1(message).digest():
raise VerificationError('Verification failed')
It runs self.encrypt
, which will be $s ^ e \mod{n}$, where $s$ is the signature, $e$ is the public exponent and $n$ is the public modulus.
We have $n$ and $e$ because the server sends these values in a PEM format:
$ nc 127.0.0.1 1337
signature: 6d0367ad7e069a4eef22535a8fa5df13e9701b31c94d30db5905e7923535835463b8673ed13fe3b6eefcfdd6074d76bbb9ec95022f56049137282aa8ab01b25956884b18116d052fc16aed4af66bfda92ca64575f46c03fbc322de10fb8518edfe3d09020b3c109e438108edcdc7fbb33d34b6f4da745c0cec157b06ad7939e66edf89c6681aef841574dfccd98a803ba583e488b528df1501e717edcf4047ee8f2bc18556c1f41f89385fe68ef7af2ee2e132f7042e9aed724cc6659310db8153756d16f9da99662ac7d5abb8e00d64735ac544f8f57d5519e3edae3824db913aa67d8bfc64afbeb1787f56c455ffb1198fb2da5b40ea67cd0d810e8015ac4b
certificate:
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAnxwOqoIroFTNdDdVS13C
I8wDj4H/3bI8T6wSMaL5GE4GLjgz33szj+u1XPxUb0vfdMxkfcTsvtYP9oZtPX3L
FMsi8KnrpvlqTzPNbKDO6Ga1viO3C6GPKIJwDyXzWI2IxnSrDHjty30dbKl1lO9E
XXWMj7GXFFVOBDnPfxWFwyMUnJc/oooyDF2vVpwvmIWFTfxRLMrmJpeHlA9Vg94A
Ype/CkbURdckwFJt0Yvp0wFFRrVhsQeFlIrq0GQtSif8YlIAjyANTAqNDoKS0fq5
Fc3388Gt2D04JNcny7qjmxWuPcnn+2PZaog10K4YrjK8ZVtptwl97f06CuktB14C
jQIBAw==
-----END PUBLIC KEY-----
From: IT Department <it@cloudcompany.com>
To: j.smith@sheesh.gov.edu
Subject: Confirm your identity
Hello there,
You have recently changed the password for your CloudCompany account.
Please verify your account using the link below to confirm your identity:
https://ccloud.cloudcompany.co/confirm-identity/5912031940529412
Below are some common reasons for unwanted or irregular activity:
- Unauthorized or unexpected resource activity - An unpatched CloudCompany
Elastic Compute Cloud (CCEC2) instance could be infected and become a botnet
agent.
- Exposed credentials or access keys.
- Unintentional misuse - An end user of a service provided by an CloudCompany
customer might post malware files to a public S1 bucket.
- False complaints - Sometimes Internet users falsely report legitimate
activities as misuse.
Thank you for your patience while we work on this case.
We appreciate your feedback. Please share your experience by rating this
correspondence using the CloudCompany Support Center link at the bottom of this
correspondence. Each correspondence can also be rated by selecting the stars in
the upper right corner of each correspondence in the CloudCompany Support
Center.
Best regards,
CloudCompany
<img width=396 height=129 id="1" src="cid:Logo.jpg">
Enter the signature as hex:
Moreover, once the signature is decrypted, the server uses a Regular Expression to parse the decrypted bytes.
Using the following Python sentence, we can see what is the payload that is being signed:
print(f'{clearsig = }')
$ python3 server.py
clearsig = b'\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'
Notice that the Regular Expression does not check the number of bytes "\xff"
, but it checks that the decrypted string starts with "\x00\x01\xff"
and its length is 256 bytes. Hence, we can pass the Regular Expression check setting less bytes "\xff"
and padding the string by the end. For example:
$ python3 -q
>>> clearsig = b'\x00\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'
>>> clearsig.count(b'\xff')
218
>>> len(clearsig)
256
>>> import re
>>> r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
>>> m = r.match( b'\x00\x01' + b'\xff' * 218 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
>>> m.groups()
(b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
>>> m = r.match((b'\x00\x01' + b'\xff' * 1 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\0'))
>>> m.groups()
(b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p')
We only need to find a number $t$ so that $t ^ 3$ (remember that $e = 3$) passes the Regular Expression check.
So using this code we can obtain such value $t$:
forged_min = int((b'\x00\x01' + b'\xff' * 1 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\x00').hex(), 16)
forged_max = int((b'\x00\x01' + b'\xff' * 217 + b'\x000!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p').ljust(256, b'\xff').hex(), 16)
perfect_cube_range = range(iroot(forged_min, e)[0], iroot(forged_max, e)[0])
regex = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
keylength = len(long_to_bytes(n))
for t in perfect_cube_range:
clearsig = (t ** e).to_bytes(keylength, 'big')
m = regex.match(clearsig)
if m and m.groups() == (b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14', b'\xdb}\xdd?yeA\xdaO\x80]yHo\xd3w\x07\x9c2p'):
break
r.sendafter(b'Enter the signature as hex: ', hex(t)[2:].encode())
log.success(f'Flag: {r.recv().decode()}')
r.close()
Using this solution script: solve.py
we can obtain the flag:
$ python3 solve.py 178.62.23.240:30323
[+] Opening connection to 178.62.23.240 on port 30323: Done
[+] Flag: HTB{4_8131ch3n84ch32_254_vu1n}
[*] Closed connection to 178.62.23.240 port 30323