Curve Logic
Задание
Наши разведчики нашли в пустоши заброшенный сервер, который остался от тех, кто создавал системы безопасности еще до Великой Войны. Он принимает числа и выполняет какие-то сложные расчеты. Если твой ввод будет правильным, сервер откроет доступ к засекреченной информации. Постарайся, нам очень нужны эти данные!
Решение
Подключение для выполнения данного задания происходит по Netcat.
Для выполнения задания был дан файл с исходным кодом сервиса:
import socket
import os
from random import *
from Crypto.Util.number import *
from Crypto.PublicKey import ECC
from threading import Thread
flag = f"ptech2024{{{os.getenv('flag')}}}\n"
def generate_curve():
nbit = 521
key = ECC.generate(curve='P-521')
order = ECC._curves['NIST P-521'][2]
Q = key.pointQ
O = ECC.EccPoint(Q.x, Q.y, curve='p521').point_at_infinity()
return nbit, order, Q, O
def check_number(n, nbit):
return n.bit_length() == nbit and bin(n)[2:].count('1') >= nbit // 2 + 1
def decimal(n, nbit, order, O, Q):
try:
n = int(n)
except:
return -1
if check_number(n, nbit):
R, P, counter = O, Q.copy(), 0
for bit in bin(n)[2:][::-1]:
if bit == '1':
R = R + P
counter += 1
P = P.double()
counter += 1
if 406 * counter >= 522 * nbit:
break
if R == Q * n and n < order:
return 1
else:
return 0
else:
return -2
def binary(bin_n, nbit, order, O, Q):
try:
bin_n = [int(i) for i in bin_n.split(',')]
flag = all(0 <= i <= 1 for i in bin_n) and len(bin_n) == nbit
except:
return -1
if flag:
R, P, counter, i, n = O, Q.copy(), 0, 0, 0
for bit in bin_n[::-1]:
if bit != 0:
n += 2 ** i * bit
R += P
counter += 1
P = P.double()
counter += 1
i += 1
if 594 * counter >= 600 * nbit:
break
if n.bit_length() == nbit and n < order and R == Q * n:
return 1
else:
return 0
else:
return -2
def handle_client(conn, addr):
print(f"New connection from {addr}")
try:
nbit, order, Q, O = generate_curve()
hello_message = "This super computer uses elliptic curves for its calculations.\nEnter the decimal representation of the number or its binary form to begin the calculation\n1 - decimal or 2 - binary:\n"
conn.sendall(hello_message.encode())
mode = conn.recv(1024).decode().strip().lower()
if mode == '1':
conn.sendall("Enter decimal number:\n".encode())
n = conn.recv(4096).decode().strip()
result = decimal(n, nbit, order, O, Q)
if result == -2:
conn.sendall("Number does not satisfy the requirements\n".encode())
elif result == -1:
conn.sendall("Input is not integer\n".encode())
elif result == 0:
conn.sendall("Wrong number,the calculations failed\n".encode())
elif result == 1:
conn.sendall(f"Calculations completed successfully. There is your flag: {flag}\n".encode())
elif mode == '2':
conn.sendall("Enter binary form of number separated by commas:\n".encode())
B = conn.recv(4096).decode().strip()
result = binary(B, nbit, order, O, Q)
if result == -2:
conn.sendall("Number does not satisfy the requirements\n".encode())
elif result == -1:
conn.sendall("Input is not correct\n".encode())
elif result == 0:
conn.sendall("Wrong number,the calculations failed\n".encode())
elif result == 1:
conn.sendall(f"Calculations completed successfully. There is your flag: {flag}\n".encode())
else:
conn.sendall("Choice is not correct\n".encode())
except Exception as e:
print(f"Error in session with {addr}: {e}")
finally:
conn.close()
def server_get_connections():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 52152))
server_socket.listen(5)
print(f"Server started. Listening port 52152 ....")
while True:
conn, addr = server_socket.accept()
client_thread = Thread(target=handle_client, args=(conn, addr))
client_thread.start()
if __name__ == '__main__':
server_get_connections()
После краткого анализа кода становится понятно, что в данном задании использованы операции на эллиптической кривой P-521.
Если вы не знаете, как работают эллиптические кривые, и зачем они нужны, прочитать про это можно здесь и здесь.
При подключении к сервису пользователю доступно меню с выбором десятичной или двоичной формы ввода.
This super computer uses elliptic curves for its calculations.
Enter the decimal representation of the number or its binary form to begin the calculation
1 - decimal or 2 - binary:
Определим, в каком случае пользователю будет выдан флаг. Из анализа кода становится понятно, что для этого необходимо, чтобы переменная result была равна 1.
elif result == 1:
conn.sendall(f"Calculations completed successfully. There is your flag: {flag}\n".encode())
При этом result является результатом работы функций для обработки ввода пользователя в десятичной или двоичной форме.
result = decimal(n, nbit, order, O, Q)
...
result = binary(B, nbit, order, O, Q)
Рассмотрим отдельно логику функций, используемых для обработки десятичного и двоичного ввода. Начнем с функции для десятичного ввода:
def decimal(n, nbit, order, O, Q):
try:
n = int(n)
except:
return -1
if check_number(n, nbit):
R, P, counter = O, Q.copy(), 0
for bit in bin(n)[2:][::-1]:
if bit == '1':
R = R + P
counter += 1
P = P.double()
counter += 1
if 406 * counter >= 522 * nbit:
break
if R == Q * n and n < order:
return 1
else:
return 0
else:
return -2
Разберемся с параметрами, передаваемыми в эту функцию. Параметры order, Q и O являются параметрами эллиптической кривой и генерируются ранее в коде. Параметр nbit также задан в коде. Параметр n является числом в десятичной форме, введенным пользователем.
nbit = 521
order = ECC._curves['NIST P-521'][2]
Q = key.pointQ
O = ECC.EccPoint(Q.x, Q.y, curve='p521').point_at_infinity()
В первую очередь в данной функции происходит проверка ввода пользователя на соответствие некоторым условиям с помощью функции check_number. Проанализировав код данной функции, прийдем к выводу о том, что ввод пользователя должен быть числом длиной nbit (в нашем случае 521) и должен содержать более nbit // 2 + 1 единиц в двоичном представлении (в нащем случае 261).
def check_number(n, nbit):
return n.bit_length() == nbit and bin(n)[2:].count('1') >= nbit // 2 + 1
Если ввод пользователя прошел проверку, то происходит определение некоторых параметров. Параметр R приравнивается точке на бесконечности, а параметр P - точке Q, сгенерированной для эллиптической кривой. Также задается счетчик counter, равный нулю.
R, P, counter = O, Q.copy(), 0
Далее происходит обработка введенного пользователем числа. При этом число рассматривается побитово в обратном порядке (от младшего бита к старшему). Если бит равен 1, то к точке R прибавляется точка P и значение счетчика counter увеличивается на 1. При этом независимо от значения бита происходит удвоение точки P и увеличение счетчика counter на 1. Когда выполняется условие 406 * counter >= 522 * nbit, то перебор битов числа останавливается.
for bit in bin(n)[2:][::-1]:
if bit == '1':
R = R + P
counter += 1
P = P.double()
counter += 1
if 406 * counter >= 522 * nbit:
break
Потом происходит проверка условия:
if R == Q * n and n < order:
return 1
Для получения флага необходимо, чтобы рассчитанная точка R была равна сложению точки Q с самой собой n раз. Вспомним, что n - это введенное пользователем числом в десятичной форме. Также n равно сумме степеней двойки на местах единичных бит. Добавление Q к R происходит только, если бит n равен 1. Таким образом, для выполнения данного условия необходимо, чтобы при рассмотрении числа по битам было пройдено все число.
Так как перебор битов останавливается, когда выполняется условие 406 * counter >= 522 * nbit, то при значении counter равном 670 перебор будет остановлен. При этом, чтобы пройти все число длиной 521 бит с минимум 261 единицей (условие для прохождения первой проверки) необходимо прибавить counter 782 раза. Соответственно, не получится пройти такое число целиком. Из этого можно сделать вывод о том, что при выборе десятичного ввода пользователю не удастся получить флаг.
Рассмотрим работу функции для двоичного ввода пользователя:
def binary(bin_n, nbit, order, O, Q):
try:
bin_n = [int(i) for i in bin_n.split(',')]
flag = all(0 <= i <= 1 for i in bin_n) and len(bin_n) == nbit
except:
return -1
if flag:
R, P, counter, i, n = O, Q.copy(), 0, 0, 0
for bit in bin_n[::-1]:
if bit != 0:
n += 2 ** i * bit
R += P
counter += 1
P = P.double()
counter += 1
i += 1
if 594 * counter >= 600 * nbit:
break
if n.bit_length() == nbit and n < order and R == Q * n:
return 1
else:
return 0
else:
return -2
В данном случае также длина введенного числа должна быть равна 521 бит, однако НЕТ УСЛОВИЯ ДЛЯ МИНИМАЛЬНОГО КОЛИЧЕСТВА ЕДИНИЦ!
flag = all(0 <= i <= 1 for i in bin_n) and len(bin_n) == nbit
Помимо параметров R, Q и counter, заданных аналогично случаю с десятичным вводом, вводится переменные n и i для сборки числа, введенного пользователем, в десятичном виде.
R, P, counter, i, n = O, Q.copy(), 0, 0, 0
Сложение точек на кривой в данном случае происходит также, как и в случае с десятичным вводом пользователя. Побитовый перебор будет завершен, когда выполнится условие 594 * counter >= 600 * nbit. Таким образом предельное значение counter будет равно 527.
for bit in bin_n[::-1]:
if bit != 0:
n += 2 ** i * bit
R += P
counter += 1
P = P.double()
counter += 1
i += 1
if 594 * counter >= 600 * nbit:
break
Для получения флага необходим пройти следующее условие:
if n.bit_length() == nbit and n < order and R == Q * n:
return 1
То есть для получения флага необходимо пройти все биты числа, введенного пользователем. Так как в данном случае нет условия на минимальное количество единиц в числе и предельное значение counter равно 527, а длина числа 521 бит, то пользователь может ввести число, содержащее до 6 единиц.
Выполним ввод такого числа, пользователь получит флаг:
This super computer uses elliptic curves for its calculations.
Enter the decimal representation of the number or its binary form to begin the calculation
1 - decimal or 2 - binary:
2
Enter binary form of number separated by commas:
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Calculations completed successfully. There is your flag: ptech2024{17d2c5ab414a641c1620623cb5b8cd081f6e749f}