Skip to content Skip to navigation

NEOQUEST 2014 Quals - TimeShift 2. Revenge

Category: 

Задание:

        Мое внимание привлекает монитор. На него наклеен стикер с надписью B4365F2. Видимо, это какой-то ключ. На экране мигают две точки, соединенные пунктиром, а ниже бегут пакеты сетевого трафика. Наверное, это передача каких-то команд ракете. Но, по всей видимости, передаваемые данные зашифрованы... На компьютере также открыт файл, в котором записаны два IP-адреса (213.170.102.196:4001, 213.170.102.197:4002). Наверняка IP-адреса помогут мне понять схему работы протокола, по которому передаются команды! Да и в отладочной информации, если покопаться, можно будет обнаружить что-нибудь полезное...

Подключившись к адресам из задания понимаем, что используется какой-то протокол связанный с SSL.

Ответ от 213.170.102.196:4001:

Alert! Expected client hello message.
Format:
	1 byte		type	NEOSSL_HANDSHAKE	0x16
	2 byte		version	NEOSSL1_VERSION		0x01
	3-4 bytes	length (excluding header)
	5 byte		data	NEOSSL_CLIENT_HELLO	0x01
---DEBUG INFO---
Ubuntu Release 10.04 (lucid)
Kernel Linux 2.6.32-21-generic
Memory 1001.9 MiB
Processor Intel(R) Core(TM) i3 CPU
Processing time 1998 cycles
Processing threads - 1 thread
Public-key cryptography algorithm - RSA (with Montgomery multiplication)
Symmetric-key cryptography algorithm - AES-128 (zero IV)
------

Ответ 213.170.102.197:4002:

Alert! Expected server hello message.
Format:
	1 byte		type	NEOSSL_HANDSHAKE	0x16
	2 byte		version	NEOSSL1_VERSION		0x01
	3-4 bytes	length (excluding header)
	5 byte		data	NEOSSL_SERVER_HELLO	0x02
	6 byte		data	RSA_WITH_AES_128_CBC	0x01
	7-n bytes	data	Certificate
---DEBUG INFO---
Ubuntu Release 10.04 (lucid)
Kernel Linux 2.6.32-21-generic
Memory 1001.9 MiB
Processor Intel(R) Core(TM) i3 CPU
Processing time 1625 cycles
Processing threads - 1 thread
Public-key cryptography algorithm - RSA (with Montgomery multiplication)
Symmetric-key cryptography algorithm - AES-128 (zero IV)
------

 Получив формат пакета с ссертификатом от одного сервера и сертификат от другого, приходит идея устроить пересылку сообщений между серверами:

  1. Устанавливаем два подключения
  2. Пересылаем сообщения между серверами друг другу, просматривая их

Понимаем, что устанавливается SSL соединение (не совсем классическое, а несколько упрощенное): 

  1. 1-ый сервер выдает сертификат
  2. 2-ой сервер в ответ на сертификат выдает зашифрованный на открытом ключе первого сервера сеансовый ключ для  AES-128-CBC (из отладочной информации понимаем)
  3. В ответ на это 1 сервер отвечает коротким сообщением об окончании установления соединения
  4. Пересылается один пакет, зашифрованный уже сеансовым симметричным ключом

 

Помучавшись с попыткой подменить сертификат, приходим к выводу, что используется атака по времени. Ибо:

  • Название намекает
  • Намеки в дебажном выводе
  • Слишком много намеков в дебажном выводе

Наиболее простым и правильным решением оказывается проведение Тайминг-атаки по мотивам вот этой статьи: http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf. Ибо Public-key cryptography algorithm - RSA (with Montgomery multiplication).

#!/usr/bin/python
from struct import pack
from sock import Sock
import sys
from fractions import gcd
from numpy import random
from operator import *
from time import *

#Extended Euclidean algorithm
def extended_euclidean(a, b):	
	x = 0
	lastx = 1
	y = 1
	lasty = 0
	
	while b != 0:
		q = a // b
		a, b = b, a % b		
		x, lastx = (lastx - q * x, x)
		y, lasty = (lasty - q * y, y)
	return (a, lastx, lasty)
	
def inverse(var, module):
	"""
	Return b such that b*m mod k = 1, or 0 if no solution
	"""
	v = extended_euclidean(var,module)
	return (v[0]==1)*(v[1] % module)

def code(u):
	buf = ''
	for i in xrange(0, 16, 1):
		t = u % (1 << 32)
		buf += pack('<I', t)
		u = u >> 32
	return buf[::-1]

hello1 = '\x16\x01\x00\x01\x01'
def decryptTime(u):
	tries = 3
	t = 0
	for i in range(0, tries, 1):
		s = Sock("213.170.102.196:4001", timeout=30)
		s.send(hello1)
		cerHello = s.recv(10000)		
		buf = '\x16\x01\x00\x41\x0c' + code(u)
		s.send(buf)
		s.read_until('Processing time ')
		buf = s.read_until(' cycles')
		s.close()
		t += int(buf[1:-6])
	return (t / tries)


Modulus = 0x00d30f0d35084103fdf880a2e23f34b2631cca681eb7651d733cdc09b7c95e68b9b956d37ea3695ea3e6b406c26460a192fc153cf9b688a90282c78dcbee012341
R = 1 << 256
invR = inverse(R, Modulus)

treshold = 50000 #this means 50000 cycles from DEBUG output 
def guess(g0):
	gOrig = g0
	randTries = 1
	for i in xrange(0, 252, 1):
		delta = 0
		g1 = 0
		for j in xrange(0, randTries, 1):
			g = gOrig
			if j > 0:
				g += random.randint(0, 512)
			print '#' + str(i)
			g1 = (1 << (251 - i)) | g

			ug0 = g * invR % Modulus
			print 'g : ' + hex(g)
			print 'g1: ' + hex(g1)
			ug1 = g1 * invR % Modulus

			dt0 = decryptTime(ug0)
			dt1 = decryptTime(ug1)
			delta += abs(dt1 - dt0)
		delta = delta / randTries	
		print 'delta: ' + str(delta)

		if delta < treshold:
			gOrig = g1
	return gOrig


def tryWithG0(g0):
	q = guess(g0)
	print hex(q)
	p = Modulus / q
	if q * p == Modulus:
		print 'SUCCES'
		print hex(q)
		print hex(p)
	else:
		print 'FAIL'


for b1 in range(0, 8):
	g0 = 1 << 255
	print '======================================= ' + str(b1)
	g0 = g0 + b1 * (1 << 252)
	print decryptTime(g0 * invR % Modulus)


g0 = (1 << 255) + 6 * (1 << 252)
tryWithG0(g0)

Примечение. Используется обертка для сокетов Sock, написанная Hellman (https://github.com/hellman/sock).

Если в функции guess выставить переменную randTries переменную равной >1, то скрипт будет использовать Neighborhood из статьи, но в данном случае это необязательно.

В итоге получаем один из множителей RSA модуля, находим закрытый ключ, расшифровываем сеансовый ключ  AES. Далее расшифровываем последнее сообщение. Оно говорит нам, что нужно отправить сообщение вида "XXXXXXX:Connect". В качестве XXXXXXX подставляем код из задания. Все это дело шифруем AES'ом и дописываем заголовок пакета из протокола, используемого в задании:

#!/usr/bin/python
import socket
import struct
from Crypto.Cipher import AES

s1 = socket.socket()
s1.connect(("213.170.102.196", 4001)) 

s2 = socket.socket()
s2.connect(("213.170.102.197", 4002))

hello1 = '\x16\x01\x00\x01\x01'

s1.send(hello1)
cerHello = s1.recv(10000)
s2.send(cerHello)
buf = s2.recv(10000)
print '=== recv on cert:'
print buf.encode('hex')


tmp = buf[-64:]
c = int( '0x' + tmp.encode('hex'), 16)
d = 0x164e0ae945dc091df7fb303b94ce6ee3c691257bc989e818db9fad6f3cdabb5a6431a9262d6d04558cfc5084dfc2709f743f673396617b9d71de6f8da481eea1L
N = 0xd30f0d35084103fdf880a2e23f34b2631cca681eb7651d733cdc09b7c95e68b9b956d37ea3695ea3e6b406c26460a192fc153cf9b688a90282c78dcbee012341L
p = pow(c, d, N)
p = hex(p)[2:-1]
print p
if len(p) % 2 == 1:
	p = '0' + p
p = p.decode('hex')
key = p[-16:]
print len(key)
print key.encode('hex')
iv = '\x00' * 16
aes = AES.new(key, AES.MODE_CBC, iv)


s1.send(buf)
buf = s1.recv(10000)
s2.send(buf)
buf = s2.recv(10000)

cmd = aes.decrypt(buf[-112:])
print cmd

msg = 'B4365F2:Connect'
length = 16 - (len(msg) % 16)
msg += chr(length)*length
print msg
aes = AES.new(key, AES.MODE_CBC, iv)
data = '\x17\x01\x00\x10' + aes.encrypt(msg)

s2.send(data)
aes = AES.new(key, AES.MODE_CBC, iv)
flag = s2.recv(10000)

flag = aes.decrypt(flag[4:])
print 'FLAG:'
print flag

s1.close()
s2.close()

И вот только после этого получаем ключ:

To obtain the access to the missile control system send a message: "XXXXXXX:Connect".
XXXXXXX - ID
B4365F2:Connect
FLAG:
b84395ebd302b3e8943708770d45c4d3

Ключ: b84395ebd302b3e8943708770d45c4d3

Sports brands | UOMO, SCARPE