Skip to content Skip to navigation

Secc

Category: 
Задание:
 
This key verification scheme is built on elliptic crypto, bet this points are
impossible.
 
nc 195.133.87.171 5555
 
password: secch4l*
 
Так же нам даны исходники этой схемы
Решение:
 
Открываем файл task.py и видим:
 
def main():
    print "Auth:"
 
    auth = raw_input()
 
    if hashlib.sha1(auth).hexdigest() != "375d5c01ca1b8c3863024d10aac7713472eb5033": # secch4l*
        print "nope"
        return
 
    prefix = os.urandom(8)
 
    print "Proof of work, please"
    print "Prefix is (hexed) ", prefix.encode("hex")
     
    test = raw_input().decode("hex")
     
    if not test.startswith(prefix) or len(test) > 16:
        print "nope"
        return
     
    h = hashlib.sha1(test).hexdigest()
     
    if not h.startswith("000000"):
        print "nope"
        return
     
    goflag()
 
Первым делом надо пройти защиту от брутфорса - сервер пришлет строчку из 8 байт и мы дожны подобрать еще не более 7 байт так, чтобы хеш от всей строки начинался с 6 нулей. Это можно легко сделать на питоне, например вот так:
 
#!/usr/bin/env python
from socket import create_connection
from time import sleep
import string
import hashlib
 
def findproof(prefix):
	max_len = 256**7
	 
	for i in xrange(0, max_len,1):
		if i % 100000 == 0:
			print i
		ft = hex(i)
		ft = ft[2:]
		if len(ft) % 2 != 0:
			ft = "0" + ft
		 
		footer = ft.decode("hex")
		header = prefix.decode("hex")
		 
		test = header + footer
		h = hashlib.sha1(test).hexdigest()
		if h.startswith("000000"):
			return test.encode("hex")
	print "Bad luck =("
 
conn = create_connection(('195.133.87.171', 5555));
conn.recv(1024);
conn.recv(1024);
conn.send("secch4l*\n");
sleep(0.5);
 
res = conn.recv(1024);
prefix = res.split()[-1]
 
s = findproof(prefix);
conn.send(s + "\n")
sleep(3);
 
print conn.recv(1024)
print conn.recv(1024)
print conn.recv(1024)
print conn.recv(1024)
 
Единственная проблема - этот скрипт будет достаточно долго искать подходящий хеш (минуты 2-3, может дольше), но в итоге сервер ответит что-то такое:
 
EC PASSWORD CHECK
R = (32009104608775058819477673947201651309102020463966621153833804363847565759174L, 54506028353458734953786314314179377292053505167654412444092825486798869159312L)
SHARED SECRET = R ^ PASSWORD
ENCRYPTED MESSAGE: 4c106c176590bc2f26c822ba7c164dc8d04567c15511b2fbd1
 
В файле task.py мы видим:
 
def goflag():
	print "EC PASSWORD CHECK"
	 
	r = random.randint(31337, 1 << 250)
	R = p256.power(G, r)
	 
	print "R =", R
	 
	print "SHARED SECRET = R ^ PASSWORD"
	 
	S = p256.power(R, PASSWORD)
	 
	key = p256.derive(S)
	 
	cipher = encrypt(FLAG, key)
	print "ENCRYPTED MESSAGE:", cipher.encode("hex")
 
def encrypt(msg, key):
	iv = os.urandom(8)
	stream = hashlib.sha256(iv + key).digest()
	stream = hashlib.sha256(stream + iv + key).digest()
	cipher = iv + xor(msg, stream)
	return cipher
 
А значит зашифрованное сообщение и есть флаг, осталось только расшифровать =)
Шифрования происходит простым xor'ом с гаммой, которая получается хешированием ключа и iv. Вектор инициализации содержится в шифртексте, значит нужно найти ключ, который получается с помощью функции derive() из известной нам точки R, возведенной в степень PASSWORD, который нам не известен.
 
Настало время открыть файл ecc.py. Там находится реализация эллиптической криптографии, на первый взгляд - хорошая: функции add, power выглядят правильно, кривая взята NIST'овская. Зато функция derive() вызывает подозрения:
 
def derive(self, p):
	return hashlib.sha256(str((p[0] << 10) / p[1])).digest()
 
Выражение str((p[0] << 10) / p[1]) возможно будет иметь очень плохое распределение, проверим это, добавив в derive строчку print str((p[0] << 10) / p[1]) и запустив такой скрипт:
 
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
import os
import random
import hashlib
 
from ecc import p256, G, s2n, xor
 
PASSWORD = s2n("adadsd")
FLAG = "111"
 
for i in xrange(0, 10000, 1):
	r = random.randint(31337, 1 << 250)
	R = p256.power(G, r)
	S = p256.power(R, PASSWORD)
	key = p256.derive(S)
 
В консоли мы увидим что-то вроде такого:
 
1698
994
524
18
273
1493
1716
375
1012
5504
255
1405
1943
688
3221
1078
1012
26146
106
776
757
229
2860
6505
141
481
1330
 
Видно, что числа редко превосходят даже 10000, а значит можно просто перебрать все варианты и попробовать расшифровать сообщение на каждом из них, проверяя, что в результате расшифрования получилась строка, содержащая только печатные символы. Например вот так:
 
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
import os
import random
import hashlib
import string
 
def xor(a, b):
	return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in xrange(len(a))])
 
encoded_msg = '4c106c176590bc2f26c822ba7c164dc8d04567c15511b2fbd1'
decoded_msg = encoded_msg.decode("hex")
 
def main():
	for i in xrange(0, 1000000,1):
		key = hashlib.sha256(str(i)).digest()
	 
		result = decrypt(decoded_msg,key)
		if all(c in string.printable for c in result):
			print result
			break
	 
def decrypt(msg, key):
	iv = msg[0:8]
	stream = hashlib.sha256(iv + key).digest()
	stream = hashlib.sha256(stream + iv + key).digest()
	cipher = xor(msg[8:], stream)
	return cipher
	 
if __name__ == '__main__':
	main()
 
В результате получаем:
ecc_is_too_s3cure
 
Флаг: ecc_is_too_s3cure
 
Скрипт, который выполняет всю атаку:
 
#!/usr/bin/env python
from socket import create_connection
from time import sleep
import string
import hashlib
 
def xor(a, b):
	return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in xrange(len(a))])
 
def findproof(prefix):
	max_len = 256*256*256*256*256*256*256
 
	for i in xrange(0, max_len,1):
		if i % 100000 == 0:
			print i
		ft = hex(i)
		ft = ft[2:]
		if len(ft) % 2 != 0:
			ft = "0" + ft
		 
		footer = ft.decode("hex")
		header = prefix.decode("hex")
		 
		test = header + footer
		h = hashlib.sha1(test).hexdigest()
		if h.startswith("000000"):
			return test.encode("hex")
	print "Bad luck =("
 
def decrypt(msg, key):
	iv = msg[0:8]
	stream = hashlib.sha256(iv + key).digest()
	stream = hashlib.sha256(stream + iv + key).digest()
	cipher = xor(msg[8:], stream)
	return cipher
 
conn = create_connection(('195.133.87.171', 5555));
conn.recv(1024);
conn.recv(1024);
conn.send("secch4l*\n");
sleep(0.5);
 
res = conn.recv(1024);
prefix = res.split()[-1]
 
s = findproof(prefix);
conn.send(s + "\n")
sleep(3);
 
res = conn.recv(1024)
 
encoded_msg = res.split()[-1]
decoded_msg = encoded_msg.decode("hex")
 
for i in xrange(0, 1000000,1):
	key = hashlib.sha256(str(i)).digest()
 
	result = decrypt(decoded_msg,key)
	if all(c in string.printable for c in result):
		print result
		break