BMPaas
In this crypto challenge we need to decypher an image encrypted in a wrong way.
The challenge
We have two things :
- The source code they use to encrypt the flag
- An access in tcp to an instance to run the code and get as many encrypted flag we want
The source code
Here is the source code (a little bit modified) :
import base64
import os
FLAG = base64.b85encode(open("flag.bmp", "rb").read()).decode()
CHARSET = base64._b85alphabet.decode()
N = len(CHARSET)
def generate_key(length):
random_stream = os.urandom(length)
return "".join(CHARSET[x % N] for x in random_stream)
def encrypt(plaintext, key):
ciphertext = ""
for i in range(len(plaintext)):
ciphertext += CHARSET[(CHARSET.find(plaintext[i]) + CHARSET.find(key[i])) % N]
return ciphertext
key = generate_key(len(FLAG))
print(encrypt(FLAG, key))
Few things about it :
- The flag is an image
- Every time we ask a flag a new key is generated with the same length as the flag
- The flag we get is in base85
- They use
os.urandom
which do not have known vulnerability (we will say it is perfect)
Exploitation
Like I say os.urandom
is perfect so the vulnerability is somewhere else.
The vulnerability is in the fact they use base85. Every byte given by os.urandom
is then taken modulo N
the length of base85 alphabet so modulo 85. And by chance 255 = 3*85
which mean if I take a random byte I have more chance to get a 0 modulo 85 than an other number (because 0, 85, 170 and 255 will give me a 0 modulo 85 so 4 diffferent bytes wherease the other number will only have 3 different bytes).
So we just need to get enough encrypted flags and then for a given char take the one in majority over all the different flags because it would have been statisticly encrypted with a 0. Of course, as it’s only for statistics, we really need to recover a lot of flags or we will get an error.
I recover 50000 flags which take me about 30 minutes.
Here is the code I use :
from pwn import *
SERVER_HOTE = "instances.challenge-ecw.fr"
PORT = 38648
CHARSET = base64._b85alphabet.decode()
N = len(CHARSET)
def get_flag(nb):
con = remote(SERVER_HOTE, PORT)
d = con.recvlines(7)
print(d)
d = con.recv()
print(d)
t = []
for i in range(nb):
print(i)
con.sendline(bytes("1", "utf-8"))
d = con.recvline()[:-1]
t.append(d)
con.recv()
con.close()
return t
def find_max(t):
res = []
for i in range(len(t[0])):
d = dict()
for j in range(len(t)):
a = t[j][i]
if a in d:
d[a] += 1
else:
d[a] = 1
m = 0
r = None
for z in d:
if d[z] > m:
m = d[z]
r = z
res.append(r)
return res
def to_string(r):
s = ""
for i in r:
s += chr(i)
return s
def to_write(e:str):
b = base64.b85decode(e.encode())
with open ("flag.bmp", "wb") as f:
f.write(b)
def main():
t = get_flag(50000)
r = find_max(t)
s = to_string(r)
write_to_txt(t)
to_write(s)
if __name__ == "__main__":
main()
Flag
Finaly we get this image :
And so the flag is ECW{b85_modulo_bias_!!}