diff --git a/2017-10-06-klctf/bad_computations/README.md b/2017-10-06-klctf/bad_computations/README.md new file mode 100644 index 00000000..ce1ba936 --- /dev/null +++ b/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. diff --git a/2017-10-06-klctf/bad_computations/crypt.py b/2017-10-06-klctf/bad_computations/crypt.py new file mode 100644 index 00000000..cfbd0832 --- /dev/null +++ b/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 ") + + 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]) diff --git a/2017-10-06-klctf/cameras/README.md b/2017-10-06-klctf/cameras/README.md new file mode 100644 index 00000000..97a0d0b7 --- /dev/null +++ b/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) diff --git a/2017-10-06-klctf/cameras/out.png b/2017-10-06-klctf/cameras/out.png new file mode 100644 index 00000000..2edf63a5 Binary files /dev/null and b/2017-10-06-klctf/cameras/out.png differ diff --git a/2017-10-06-klctf/cameras/sec.png b/2017-10-06-klctf/cameras/sec.png new file mode 100644 index 00000000..ef1873b0 Binary files /dev/null and b/2017-10-06-klctf/cameras/sec.png differ diff --git a/2017-10-06-klctf/decrypt_message/README.md b/2017-10-06-klctf/decrypt_message/README.md new file mode 100644 index 00000000..73956eb3 --- /dev/null +++ b/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`