Skip to content

Commit

Permalink
KLCTF writeups
Browse files Browse the repository at this point in the history
  • Loading branch information
Pharisaeus committed Oct 13, 2017
1 parent 2d81001 commit 9809d28
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 0 deletions.
36 changes: 36 additions & 0 deletions 2017-10-06-klctf/bad_computations/README.md
@@ -0,0 +1,36 @@
# Bad Computations (Crypto, 800p)

In the task we get [crypto code](crypt.py) and encrypted flag: `hnd/goJ/e4h1foWDhYOFiIZ+f3l1e4R5iI+Gin+FhA==`

We proceed with labelling the poorly obfuscated functions.
It's quite clear we have there something like: `primes_in_range`, `prime_sieve`, `extended_gcd` and `mod_inv`.
Next we proceed to label the flag encryption procedure and it's easy to notice that it's Paillier Cryptosystem.
So once we label the code we end up with:

```python
flag_bytes = [ord(x) for x in flag]
paillier_encrypted_constant = paillier_encrypt(b, g, n, r)

for i in range(len(flag_bytes)):
flag_bytes[i] = (paillier_encrypt(flag_bytes[i], g, n, r) * paillier_encrypted_constant) % (n * n)
flag_bytes[i] = paillier_decrypt(flag_bytes[i], [p, q], g)

flag_bytes = b64encode(bytearray(flag_bytes))
print(str(flag_bytes)[2:-1])
```

So in reality encrypted flag byte is multiplied by encrypted constant and later decrypted.
But Paillier has homomorphic properties.
We can calculate `encrypt(plaintext1 * plaintext2)` by doing `encrypt(plaintex1)^plaintext2 mod n^2` and we can calculate `encrypt(plaintext1 + plaintext2)` by doing `encrypt(plaintex1) * encrypt(plaintex2)`.
Here we have the second option, so in reality the encryption code is just doing:

`decrypt(encrypt(flag[i]) * encrypt(22)) = decrypt(encrypt(flag[i]+22)) = flag[i] + 22`

So we can simply run:
```python
print("".join([chr(ord(c) - 22) for c in 'hnd/goJ/e4h1foWDhYOFiIZ+f3l1e4R5iI+Gin+FhA=='.decode("base64")]))
```

to get the flag: `paillier_homomorphic_encryption`

I fail to see how this is worth 800p.
110 changes: 110 additions & 0 deletions 2017-10-06-klctf/bad_computations/crypt.py
@@ -0,0 +1,110 @@
from random import choice
from sys import argv
from base64 import b64encode


b = 22


def dwfregrgre(x, z):
wdef = []
for a in range(x, z + 1):
for i in range(2, a):
if (a % i) == 0:
break
else:
wdef.append(a)

return wdef


def sdsd(edefefef):
fvfegve = [x for x in range(2, edefefef)]

x = 2
rrerrrr = True
while rrerrrr:
for i in range(x * x, edefefef, x):
if i in fvfegve:
fvfegve.remove(i)

rrerrrr = False
for i in fvfegve:
if i > x:
x = i
rrerrrr = True
break

return fvfegve


def swsdwd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = swsdwd(b % a, a)
return (g, x - (b // a) * y, y)

def swsdwdwdwa(a, m):
g, x, y = swsdwd(a, m)
if g != 1:
raise Exception('Oops! Error!')
else:
return x % m

def L(u, n):
return (u - 1) // n


if __name__ == '__main__':
print("Key cryptor v1.0")

if len(argv) != 2:
print("Start script like: python crypt.py <YourOwnPasswordString>")

if (not str(argv[1]).startswith("KLCTF{")) or (not str(argv[1]).endswith("}")):
print("Error! Password must starts with KLCTF")
exit()

p = choice(dwfregrgre(100, 1000))
q = choice(dwfregrgre(200, 1000))

print("Waiting for encryption...")

n = p * q
g = None
for i in range(n + 1, n * n):
if ((i % p) == 0) or ((i % q) == 0) or ((i % n) == 0):
continue

g = i
break

if g is None:
print("Error! Can't find g!")
exit()

lamb = (p - 1) * (q - 1)
mu = swsdwdwdwa(L(pow(g, lamb, n * n), n), n) % n

rc = sdsd(n - 1)
if len(rc) == 0:
print("Error! Candidates for r not found!")
exit()

if p in rc:
rc.remove(p)
if q in rc:
rc.remove(q)

r = choice(rc)

wdwfewgwggrgrg = [ord(x) for x in argv[1][6:-1]]
dcew = (pow(g, b, (n * n)) * pow(r, n, (n * n))) % (n * n)

for i in range(len(wdwfewgwggrgrg)):
wdwfewgwggrgrg[i] = (((pow(g, wdwfewgwggrgrg[i], (n * n)) * pow(r, n, (n * n))) % (n * n)) * dcew) % (n * n)
wdwfewgwggrgrg[i] = (L(pow(wdwfewgwggrgrg[i], lamb, (n * n)), n) * mu) % n

wdwfewgwggrgrg = b64encode(bytearray(wdwfewgwggrgrg))
print(str(wdwfewgwggrgrg)[2:-1])
30 changes: 30 additions & 0 deletions 2017-10-06-klctf/cameras/README.md
@@ -0,0 +1,30 @@
# Cameras (Crypto, 300p)

In the task we get [encrypted png](sec.png).
Our first guess is that it's going to be classic repeating key xor, so we try to solve it as such.
We know the constant 16 bytes of PNG header, so we can xor the first 16 bytes of the encrypted file to recover first 16 bytes of the xor key, and then xor the whole file with this key repeated.
We might be missing some bytes in the key by we can try padding it with 0 until we get some reasonable results for some padding length.
We can always look for some constant parts we should find -> IDAT, IHDR, IEND.

```python
import codecs

from crypto_commons.generic import xor


def main():
with codecs.open("sec.png", "rb") as input_file:
data = [ord(c) for c in input_file.read()]
header = [137, 80, 78, 71, 13, 10, 26, 10]
key = xor(data, header)
print(key)
with codecs.open("out.png", "wb") as output_file:
output_file.write("".join([chr(c) for c in xor(data, key*10000)]))


main()
```

It turns that the recovered key is `[255, 255, 255, 255, 255, 255, 255, 255]` so it's even simpler then we expected.
Whole file is actually xored with the same value `0xFF`, or basically bits are negated.
We get: ![](out.png)
Binary file added 2017-10-06-klctf/cameras/out.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2017-10-06-klctf/cameras/sec.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions 2017-10-06-klctf/decrypt_message/README.md
@@ -0,0 +1,89 @@
# Decrypt message (Crypto, 700p)

In the task we get access to a webpage where we can put username and it stores some encrypted string in our cookie.
The value from cookie must store our username since it's later displayed on the webpage.

We start off by trying to play around with the encryption oracle and with the ciphertext we have.
We can notice quickly that:

- It's a 16 byte block crypto - we can see alignment by 16 bytes
- It is using some block chaining method, because many blocks of identical input are encrypted differently

Fortunately errors are printed to the screen so we can get some information about the code running there by changing the ciphertext we have.

- It's actually AES CBC with random IV stored as first block of the ciphertext
- Data are stored as JSON and there is some more data behind our input
- Data are being decoded into ascii before printing out on the screen, so if the data decrypt into some non-ascii values then we get encoding error

First we recover the whole data, using the encoding error messages.
We can change k-th byte of the IV until the decryption fails with encoding error and it will tell us how the k-th data byte in first block was finally decrypted.
Since we know how we changed the IV we can recover the initial plaintext value on the server.
This was we know that the plaitext is `{"name": "our_input","show_flag": false}`.

It's clear now that we need to set the parameter `show_flag` to `true` to get the flag.

It's AES CBC so we can easily influence decryption of a single block by changing the IV, but here we need to work with 2 blocks, because the code checks for `name` parameter before it checks the `show_flag` parameter.
So we need to have both and this means 2 blocks.

This is tricky, because to force decryption of second block we need to change the first block, and this will make this first block to decrypt into some garbage values.
However, we can use the IV to "fix" the first block decryption afterwards.
We can again use the fact that server tells us when it can't decode a certain value, so we know how certain byte was decrypted on the server, and we can change the IV byte accordingly.

Since we know exactly the plaintext and ciphertext, and we know what plaintext we want to get, we can right away use xor to get the first block ciphertext we need.
Rest is just using the IV to force this first block to decrypt to expected value.

```python
import base64

import requests
from crypto_commons.generic import xor_string


def get_ciphertext(plaintext):
url = "http://95.85.51.183/?name=" + plaintext
result = requests.get(url)
return result.cookies['user_info'].decode("base64").encode("hex")


def get_plaintext(ciphertext):
url = "http://95.85.51.183/"
payload = base64.b64encode(ciphertext.decode("hex"))
result = requests.get(url, cookies={'user_info': payload})
return result.content


def main():
forged_ct = get_ciphertext("a" * 22).decode("hex")
ct_block_1 = forged_ct[16:32]
ct_block_2 = forged_ct[32:48]
orig = """{"name": "aaaaaaaaaaaaaaaaaaaaaa"""
want = """{"name": "aaaaa","show_flag":1}\1"""
pt_block_2 = orig[16:32]
expected_pt_block_1 = want[:16]
expected_pt_block_2 = want[16:32]
c1 = xor_string(xor_string(pt_block_2, expected_pt_block_2), ct_block_1)
new_iv = ""
for index in range(16):
for i in range(256):
print('testing value %d at index %d' % (i, index))
forged_ct = new_iv + chr(i)
forged_ct += (16 - len(forged_ct)) * "a"
forged_ct += c1 + ct_block_2
pt = get_plaintext(forged_ct.encode("hex"))
if "t decode byte" in pt:
decrypted_byte = pt.split("t decode byte")[1].split()[0].strip()
position = pt.split("position")[1].split(":")[0]
if int(position.strip()) != index:
continue
print index, new_iv.encode("hex"), pt
decrypted_byte = int(decrypted_byte, 16)
real = decrypted_byte ^ ord(expected_pt_block_1[index]) ^ i
new_iv += chr(real)
break
print get_plaintext((new_iv + c1 + ct_block_2).encode("hex"))


main()
```

And we get: `Flag: KLCTFFDA616A6DAF4E63A9F7B55B43124E548`

0 comments on commit 9809d28

Please sign in to comment.