看到題目,可以知道目標是要把 flag_info 解密,並且 code file 可以讓我們分析如何得到 flag。而提示告訴我們,要了解這題是如何加密的。
hint 1:Understanding encryption algorithm to come up with decryption algorithm.
將題目所給的檔案下載,發現有一個 python 檔和一個文字檔。
$ ls
custom_encryption.py enc_flag
執行兩個檔案,會發現 python 檔無法執行,enc_flag 中則是出現一個陣列和變數 a 和 b。
$ chmod +x custom_encryption.py
$ python3 custom_encryption.py
Traceback (most recent call last):
File "custom_encryption.py", line 63, in <module>
message = sys.argv[1]
IndexError: list index out of range
$ cat enc_flag
a = 94
b = 29
cipher is: [260307, 491691, 491691, 2487378, 2516301, 0, 1966764, 1879995, 1995687, 1214766, 0, 2400609, 607383, 144615, 1966764, 0, 636306, 2487378, 28923, 1793226, 694152, 780921, 173538, 173538, 491691, 173538, 751998, 1475073, 925536, 1417227, 751998, 202461, 347076, 491691]
打開 python 檔,以下是完整的 code。
from random import randint
import sys
def generator(g, x, p):
return pow(g, x) % p
def encrypt(plaintext, key):
cipher = []
for char in plaintext:
cipher.append(((ord(char) * key*311)))
return cipher
def is_prime(p):
v = 0
for i in range(2, p + 1):
if p % i == 0:
v = v + 1
if v > 1:
return False
else:
return True
def dynamic_xor_encrypt(plaintext, text_key):
cipher_text = ""
key_length = len(text_key)
for i, char in enumerate(plaintext[::-1]):
key_char = text_key[i % key_length]
encrypted_char = chr(ord(char) ^ ord(key_char))
cipher_text += encrypted_char
return cipher_text
def test(plain_text, text_key):
p = 97
g = 31
if not is_prime(p) and not is_prime(g):
print("Enter prime numbers")
return
a = randint(p-10, p)
b = randint(g-10, g)
print(f"a = {a}")
print(f"b = {b}")
u = generator(g, a, p)
v = generator(g, b, p)
key = generator(v, a, p)
b_key = generator(u, b, p)
shared_key = None
if key == b_key:
shared_key = key
else:
print("Invalid key")
return
semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
cipher = encrypt(semi_cipher, shared_key)
print(f'cipher is: {cipher}')
if __name__ == "__main__":
message = sys.argv[1]
test(message, "trudeau")
接著我們一一來看每個函式。
從 main 知道,首先會呼叫 test 這個函式, message 和 "trudeau" 會傳遞到 test() 中。
我們能知道:plain_text = message; text_key = "trudeau"。
if __name__ == "__main__":
message = sys.argv[1]
test(message, "trudeau")
接著看到 test 函式,得知 p = 97, g = 31,並且知道這個函式裡主要進行了三個部分:
(1) 確認 key ?= b_key,若等於,則繼續 (2) 找到 semi_cipher,找到後再進行 (3) 找到 cipher。
def test(plain_text, text_key):
p = 97
g = 31
if not is_prime(p) and not is_prime(g):
print("Enter prime numbers")
return
a = randint(p-10, p)
b = randint(g-10, g)
print(f"a = {a}")
print(f"b = {b}")
u = generator(g, a, p)
v = generator(g, b, p)
key = generator(v, a, p)
b_key = generator(u, b, p)
shared_key = None
if key == b_key:
shared_key = key
else:
print("Invalid key")
return
semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
cipher = encrypt(semi_cipher, shared_key)
print(f'cipher is: {cipher}')
接著來仔細說明三個部分。
(1) 要確定 key = b_key,需要看到 generator 函式,以及 test 的前半段。
def generator(g, x, p):
return pow(g, x) % p
def test(plain_text, text_key):
p = 97
g = 31
if not is_prime(p) and not is_prime(g):
print("Enter prime numbers")
return
a = randint(p-10, p)
b = randint(g-10, g)
print(f"a = {a}")
print(f"b = {b}")
u = generator(g, a, p)
v = generator(g, b, p)
key = generator(v, a, p)
b_key = generator(u, b, p)
shared_key = None
if key == b_key:
shared_key = key
else:
print("Invalid key")
return
根據以上 code 還有 enc_flag 中定義的 a 和 b,我們使用 python 來驗證 key 是否等於 b_key。
>>> g = 31; p = 97; a = 94; b = 29;
>>> u = pow(g,a)%p
>>> v = pow(g,b)%p
>>> key = pow(v,a)%p
>>> b_key = pow(u,b)%p
>>> print(key,b_key)
93 93
從程式碼結果,可以確定 pow( v, a ) % p = pow( u, b ) % p = 93,於是進行第 2 個部分。
(2) 接著再看到 semi_cipher 這個變數,需要呼叫到 dynamic_xor_encrypt 這個函式。
def dynamic_xor_encrypt(plaintext, text_key):
cipher_text = ""
key_length = len(text_key)
for i, char in enumerate(plaintext[::-1]):
key_char = text_key[i % key_length]
encrypted_char = chr(ord(char) ^ ord(key_char))
cipher_text += encrypted_char
return cipher_text
def test(plain_text, text_key):
......
semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
cipher = encrypt(semi_cipher, shared_key)
print(f'cipher is: {cipher}')
看到 dynamic_xor_encrypt() 函式,我們不知道其中 plaintext 是甚麼,僅知
先記住我們目前得到的資訊,然後繼續看下去,試著從結果回推。
(3) 看到 encrypt 函式。
def encrypt(plaintext, key):
cipher = []
for char in plaintext:
cipher.append(((ord(char) * key*311)))
return cipher
def test(plain_text, text_key):
......
cipher = encrypt(semi_cipher, shared_key)
print(f'cipher is: {cipher}')
目前我們已知道 key = 93,也知道 cipher 後的值 list 是 [260307, 491691, 491691, 2487378, 2516301, 0, 1966764, 1879995, 1995687, 1214706, 0, 2400609, 607383, 144615, 1966764, 0, 636306, 2487378, 28923, 1793226, 694152, 780921, 173538, 173538, 491691, 173538, 751998, 1475073, 925536, 1417227, 751998, 202461, 347076, 491691]。
於是,可以由 key 和 cipher 回推 semi_cipher ( 如下程式碼所示 ),得到 semicipher = [9, 17, 17, 86, 87, 0, 68, 65, 69, 42, 0, 83, 21, 5, 68, 0, 22, 86, 1, 62, 24, 27, 6, 6, 17, 6, 26, 51, 32, 49, 26, 7, 12, 17]
>>> cipher = [260307, 491691, 491691, 2487378, 2516301, 0, 1966764, 1879995, 1995687, 1214766, 0, 2400609, 607383, 144615, 1966764, 0, 636306, 2487378, 28923, 1793226, 694152, 780921, 173538, 173538, 491691, 173538, 751998, 1475073, 925536, 1417227, 751998, 202461, 347076, 491691]
>>> semicipher = []
>>> for i in cipher: semicipher.append(int(i/311/93))
...
>>> semicipher
[9, 17, 17, 86, 87, 0, 68, 65, 69, 42, 0, 83, 21, 5, 68, 0, 22, 86, 1, 62, 24, 27, 6, 6, 17, 6, 26, 51, 32, 49, 26, 7, 12, 17]
繼續回推,已知 text_key = ‘trudeau’ ,並且會循環使用 text_key 中的字元來進行加密和解密操作 ( 從第 0 個字元到第 6 個字元循環使用 )。
並且我們知道 XOR 兩次之後會得到原始資訊,也就是將加密過後的 ascii 再次 xor ord(key_char) 會得到 plaintext 的 ascii,於是可以得到以下 code。
>>> plaintext = ""
>>> text_key = "trudeau"
>>> key_length = len(text_key)
>>> for i, j in enumerate(semicipher):
... key_char = text_key[i%key_length]
... decrypt_text_rev = j ^ ord(key_char)
... plaintext += chr(decrypt_text_rev)
最後,再把 plaintext reverse,就會得到 flag。
>>> print(plaintext[::-1])
picoCTF{custom_d2cr0pt6d_751a22dc}
完整的 solution code 如下:
cipher = [260307, 491691, 491691, 2487378, 2516301, 0, 1966764, 1879995, 1995687, 1214766, 0, 2400609, 607383, 144615, 1966764, 0, 636306, 2487378, 28923, 1793226, 694152, 780921, 173538, 173538, 491691, 173538, 751998, 1475073, 925536, 1417227, 751998, 202461, 347076, 491691]
semicipher = []
plaintext = ""
text_key = "trudeau"
key_length = len(text_key)
for i in cipher: semicipher.append(int(i/311/93))
for i, j in enumerate(semicipher):
key_char = text_key[i%key_length]
decrypt_text_rev = j ^ ord(key_char)
plaintext += chr(decrypt_text_rev)
print(plaintext[::-1])