Rating:
# Internetwache CTF 2016 : Bank
**Category:** Crypto
**Points:** 90
**Solves:** 79
**Description:**
> Description: Everyone knows that banks are insecure. This one super secure and only allows only 20 transactions per session. I always wanted a million on my account.
>
>
> Attachment: [crypto90.zip](./crypto90.zip)
>
>
> Service: 188.166.133.53:10061
## Write-up
On connection, we are provided with an account and a limit of 20 transactions.
There is a limit of 5000 that we are allowed to add to our account in one transaction,
but we are greedy and want to get 1000000!
A transaction is initiated by typing 'create \<a\>' to add \<a\> coins to your account.
After doing so, the server provides us with a transaction id and a verification code.
To complete the transaction we must then type 'complete \<tid\> \<hash\>'.
Looking at the code, we see that the hash is obtained by encrypting the string
"TRANSACTION: [amount]" using a XOR cipher with a stream generated by the Randomizer class.
```python
def encrypt(self, t):
self.__r.set_x(t.get_k())
ct = ""
s = str(t)
l = len(s)
for c in s:
ct += chr( ord(c) ^ (self.__r.get_next() % 2**7) )
return ct.encode('hex')
```
When a new transaction is created, the transaction gets a random secret k. The k is then
used as a seed to initialize the Randomizer stream.
When calling 'complete <tid> <hash>', the server decrypts the verification code using
the seed associated with the tid, and adds the amount specified to our account.
But they don't check if this hash is the same as the hash generated during the
'create ' command.
```python
def decrypt(self, t, ct):
self.__r.set_x(t.get_k())
try:
ct = ct.decode('hex')
pt = ""
for c in ct:
pt += chr( ord(c) ^ (self.__r.get_next() % 2**7) )
if(not "TRANSACTION" in pt):
return None
a = int(pt.replace("TRANSACTION:",""))
t.set_a(a)
return t
except:
return None
```
So we can modify the verification code to encode a different value in the transaction amount.
In order to do that, we need to recover the secret k of the transaction.
We did this by a simple brute force (the original k is 32 bytes long, but the Randomizer
only works with values mod 2^32, so we only need to brute force 2^32 values for k).
For this, we create a valid transaction with amount 5000. The server gives a verification code
associated to this transaction. We then use the following script to bruteforce the seed k:
```python
import sys
class Randomizer:
def __init__(self, a, c, m, s):
self.__a = a
self.__c = c
self.__m = m
self.__x = s
def get_next(self):
self.__x = (self.__a*self.__x + self.__c) % self.__m
return self.__x
def get_a(self):
return self.__a
def set_a(self, a):
self.__a = a
def get_c(self):
return self.__c
def set_c(self, c):
self.__c = c
def get_m(self):
return self.__m
def set_m(self, m):
self.__m = m
def get_x(self):
return self.__x
def set_x(self, x):
self.__x = x
def decrypt(ct, r):
ct = ct.decode('hex')
pt = ""
for c in ct:
pt += chr(ord(c) ^ (r.get_next() % 2**7))
return pt
def encrypt(pt, r):
ct = ""
for c in pt:
ct += chr(ord(c) ^ (r.get_next() % 2**7))
ct = ct.encode('hex')
return ct
def find_k(code):
r = Randomizer(1664525, 1013904223, 2**32, 0)
for k in xrange(2**32):
r.set_x(k)
if decrypt(code, r) == "TRANSACTION: 5000":
return k
return None
def modify_transaction(code):
k = find_k(code)
r = Randomizer(1664525, 1013904223, 2**32, k)
return encrypt("TRANSACTION:99999", r)
print modify_transaction(sys.argv[1])
```
We use this k to generate a new verification code which contains the string
"TRANSACTION:99999". The length of the new verification code must be at most
34 hex chars, corresponding to 17 bytes, 12 of which are taken by the
"TRANSACTION:" prefix. Normally this would leave us with only 4 digits if the
space was also included in the prefix, but notice the space is ignored during
decryption, allowing us to replace it with another digit for a larger amount!.
```python
def modify_transaction(code):
k = find_k(code)
r = Randomizer(1664525, 1013904223, 2**32, k)
return encrypt("TRANSACTION:99999", r)
```
We can then send the obtained code to add 99999 coins per transaction!
It takes 11 transactions to reach 1000000, and then we get the flag:
> IW{SHUT_UP_AND_T4K3_MY_M000NEYY}
## Other write-ups and resources
* <https://www.xil.se/post/internetwache-2016-crypto90-kbeckmann/>
* <https://cryptsec.wordpress.com/2016/02/22/internetwache-ctf-write-up-2016-bank-crypto-90/>
# CTF-Writeups