CRYPTO-2

Оценка: 20

Описание

В данном задании необходимо придумать алгоритм, который будет отличать зашифрованный вывод от незашифрованного.

Решение

В данном задании участнику предоставляется сервис по хэшированию сообщения. Участник запрашивает бит флага, который будет захэширован и передан участнику (неограниченное количество раз). При этом, для шифрования нулевого бита и единичного бита будут выбираться разные алгоритмы хэширования, которые значительно различаются по времени. Участнику надо проанализировать медианное время хэширование нулевого и единичного бита, а после найти ответ.

Участникам предоставлялось условие задачи на языке программирования python:

from flask import Flask, render_template, request
from flag import flag
from Crypto.Util.number import *
from random import randint

assert flag.endswith(b'}')
flag = bytes_to_long(flag)

flag = bin(flag)[2:]
assert flag[-2] == '0'

n = getPrime(512) * getPrime(512)

app = Flask(__name__)

@app.route('/')
def intro():
    return render_template('intro.html', n = n)

@app.route('/guess_bit', methods=['GET'])
def guess_bit():
    args = request.args
    if 'bit' not in args.keys():
        return {"error": "Bit needed to be guessed"}
    index = abs(int(args['bit']))

    if index >= len(flag):
        return {"error": "Index overflow"}
    bit = flag[index]

    if bit == '1':
        return {"guess": pow(7, getPrime(300), n)}
    else:
        return {"guess": randint(n//2, n)}

def main():
    app.run(host='0.0.0.0', port=1177)

if __name__ == "__main__":
    main()

Для решения данной задачи участнику было нужно:

  1. Найти длину флага при помощи бинарного поиска (или просто перебора);
  2. Понять, что если выбранный бит флага равен единице, то операция будет сильно сложнее, чем операция при бите флага равном нулю.

Ниже представлено решение:

import requests
import time 
from Crypto.Util.number import *

r = requests.Session()
url = 'http://127.0.0.1:5000'

# Данная функция возвращает бит ответа по индексу
def guess_bit(index:int):
    return r.get(f"{url}/guess_bit?bit={index}").json()

# Данная функция возвращает бит ответа по индексу и время, которое на это ушло
def guess_bit_timing(index:int):
    time_start = time.time_ns()
    ans = r.get(f"{url}/guess_bit?bit={index}").json()
    time_end = time.time_ns()
    return ans, time_end - time_start

# Данная функция реализует бинарный поиск по длине ответа, возвращает количество бит в ответе
def find_length():
    msg = guess_bit(0)
    low = 0
    #i think 1000 bits for flag enough
    high = 1000
    #binary search for smart people :D
    while low <= high:
        middle = (low + high)//2
        msg = guess_bit(middle)
        if "error" in msg.keys():
            high = middle - 1 
        else:
            low = middle + 1
    #from zero to length of flag - 1
    return low

length = find_length()
# Данная функция находит среднее время, необходимое, чтобы достать бит по индексу.
def medium_time(index, attempts = 50):
    timings = []

    # hardcoded 50 is a random number
    for _ in range(attempts):
        timings.append(guess_bit_timing(index)[1])
    return sum(timings)/len(timings)


# Находим среднее время для нулевого бита
MED_ZERO_TIME = medium_time(length - 2)

# Находим среднее время для единичного бита
MED_ONE_TIME = medium_time(0)

# Находим медианное время
MED_TO_DIVIDE = (MED_ZERO_TIME + MED_ONE_TIME)/2


# Данная функция находит бит, сравнивая среднее время для бита по индексу и медианное время
def timing_attack_for_bit(index):
    bit_time = medium_time(index, 10)
    return '1' if bit_time > MED_TO_DIVIDE else '0'

# Данная функция находит каждый бит флага
def retrieve_flag():
    flag = ''
    for i in range(length):
        flag += timing_attack_for_bit(i)
    return long_to_bytes(int(flag,2)).decode()
    
if __name__ == "__main__":
    print(retrieve_flag())