Vishwa CTF 2024
Poly Fun [300 pts]
Its a simple symmetric key encryption, I am sure you will be able to solve it (what do you mean the key looks weird)
Author : Revak Pandkar
challenge.py
encoded_flag.txt
encoded_key.txt
Here’s the source file:
import numpy as np
import random
polyc = [4,3,7]
poly = np.poly1d(polyc)
def generate_random_number():
while True:
num = random.randint(100, 999)
first_digit = num // 100
last_digit = num % 10
if abs(first_digit - last_digit) > 1:
return num
def generate_random_number_again():
while True:
num = random.randint(1000, 9999)
if num % 1111 != 0:
return num
def transform(num):
number = random.randint(1, 100000)
org = number
number *= 2
number += 15
number *= 3
number += 33
number /= 6
number -= org
if number == 13:
num1 = random.randint(1, 6)
num2 = random.randint(1, 6)
number = num1 * 2
number += 5
number *= 5
number += num2
number -= 25
if int(number / 10) == num1 and number % 10 == num2:
number = generate_random_number()
num1 = int(''.join(sorted(str(number), reverse=True)))
num2 = int(''.join(sorted(str(number))))
diff = abs(num1 - num2)
rev_diff = int(str(diff)[::-1])
number = diff + rev_diff
if number == 1088:
org = num
num *= 2
num /= 3
num += 5
num *= 4
num -= 9
num -= org
return num
else:
number = generate_random_number_again()
i = 0
while number != 6174:
digits = [int(d) for d in str(number)]
digits.sort()
smallest = int(''.join(map(str, digits)))
digits.reverse()
largest = int(''.join(map(str, digits)))
number = largest - smallest
i += 1
if i <= 7:
org = num
num *= 2
num += 7
num += 5
num -= 12
num -= org
num += 4
num *= 2
num -= 8
num -= org
return num
else:
org = num
num **= 4
num /= 9
num += 55
num *= 6
num += 5
num -= 23
num -= org
return num
else:
org = num
num *= 10
num += 12
num **= 3
num -= 6
num += 5
num -= org
return num
else:
org = num
num += 5
num -= 10
num *= 2
num += 12
num -= 20
num -= org
return num
def encrypt(p,key):
return ''.join(chr(p(transform(i))) for i in key)
key = open('key.txt', 'rb').read()
enc = encrypt(poly,key)
print(enc)
Basically, this file contains a lot of checks on some operations that tell us what number is returned. To be honest, this is very easily cheesed by simply reusing the transform function on every possible character, and then creating a map that maps from output of the transform function to the corresponding character. The actual intended solution is to notice that every single check is always the same result no matter what (each sequence of operations always results in the same number), so therefore it is easy to figure out what the final result should be. (Note that each check can be easily tested with short Python scripts), and develop the same map from that.
Once you have the map, it’s easy to decode the key. Now, you’re supposed to realize that the flag must first be decoded from base64 (it is clearly in the correct format) but then blindly guess (based on the fact that it’s symmetric encryption) to use AES ECB. Not exactly sure why they even required you to guess that it’s AES ECB, as there are many more symmetric cryptoschemes, but yeah.
Here’s my full implementation:
import numpy as np
import random
import base64
from Crypto.Cipher import AES
polyc = [4,3,7]
poly = np.poly1d(polyc) # 4x^2 + 3x + 7
# for i in range(1, 100001):
# number = i
# org = number
# number *= 2
# number += 15
# number *= 3
# number += 33
# number /= 6
# number -= org
# if(number != 13): print(org)
# for i in range(1, 7):
# for j in range(1, 7):
# num1 = i
# num2 = j
# number = num1 * 2
# number += 5
# number *= 5
# number += num2
# number -= 25
# if not (int(number / 10) == num1 and number % 10 == num2):
# print(num1, num2)
def generate_random_number(): # first and last digit are more than 1 apart
while True:
num = random.randint(100, 999)
first_digit = num // 100
last_digit = num % 10
if abs(first_digit - last_digit) > 1:
return num
# for i in range(1000):
# number = generate_random_number()
# num1 = int(''.join(sorted(str(number), reverse=True)))
# num2 = int(''.join(sorted(str(number))))
# diff = abs(num1 - num2)
# rev_diff = int(str(diff)[::-1])
# number = diff + rev_diff
# if number == 1088:
# print(number)
def generate_random_number_again(): # not a multiple of 1111
while True:
num = random.randint(1000, 9999)
if num % 1111 != 0:
return num
# for a in range(100):
# number = generate_random_number_again()
# i = 0
# while number != 6174:
# digits = [int(d) for d in str(number)]
# digits.sort()
# smallest = int(''.join(map(str, digits)))
# digits.reverse()
# largest = int(''.join(map(str, digits)))
# number = largest - smallest
# i += 1
# if i > 7: print(i)
m = dict()
for i in range(118):
num = i
org = num
num *= 2
num += 7
num += 5
num -= 12
num -= org
num += 4
num *= 2
num -= 8
num -= org
# print(chr(poly(num)), chr(i))
m[chr(poly(num))] = chr(i)
enc_key = open("encoded_key.txt", "r").read()
key = b''
for i in enc_key:
key += m[i].encode()
# print()
# print(m.keys())
print(key)
# literally just guess it's b64 --> AES
enc_flag = open("encoded_flag.txt", 'r').read()
enc_flag = base64.b64decode(enc_flag)
c = AES.new(key, AES.MODE_ECB)
pt = c.decrypt(enc_flag)
print(pt)
Run the script to get the flag!
VishwaCTF{s33_1_t0ld_y0u_1t_w45_345y}
Thoughts
Crypto challenge that was secretly rev + forensics