密码学实战 - HTB Birds of randomness

1,106 阅读4分钟

概述

Birds of randomness是来自于HTB(hackthebox.com)的一个中级密码学挑战,完成该挑战所需要掌握的知识点在于椭圆曲线算法。

题目分析

相关的任务文件包括source.py源代码和一个在线环境。

source.py内容节选如下

class TrainRoute:
    def __init__(self, a=None):
        self.p = 17101937747109687265202713197737423
        self.Gx = 3543321030468950376213178213609418
        self.Gy = 14807290861072031659976937040569354
        self.ec_order = 17101937747109687496599931614463506
        self.E = ecc.CurveFp(self.p, 2, 3)
        self.G = ecc.Point(self.E, self.Gx, self.Gy, self.ec_order)
        a, x = divmod(a, 30268)
        a, y = divmod(a, 30306)
        a, z = divmod(a, 30322)
        self.seed = int(x) + 1, int(y) + 1, int(z) + 1

    def rotate(self):
        x, y, z = self.seed
        x = (171 * x) % 30269
        y = (172 * y) % 30307
        z = (170 * z) % 30323
        self.seed = x, y, z

    def goToNextStation(self):
        while True:
            self.rotate()
            x, y, z = self.seed
            if(isPrime(x) and isPrime(y) and isPrime(z)):
                d = x * y * z
                new_point = d * self.G
                return int(new_point.x()), int(new_point.y())

def getTicketNumber():
    return int(os.urandom(32).hex(), 16)

def sendMessage(s, msg):
    s.send(msg.encode())

def receiveMessage(s, msg):
    sendMessage(s, msg)
    return s.recv(4096).decode().strip()

def main(s):
    route = TrainRoute(getTicketNumber())

    station_coords = route.goToNextStation()
    sendMessage(
        s, f'The coordinates of the departing station were: {station_coords}\n')

    destination_coords = route.goToNextStation()
    sendMessage(s, 'Your lover has arrived to the destination. Hurry up!\n')

    pegions_left = 6
    sendMessage(
        s, f'Luckily you have {pegions_left} mechapegions in your pockets.\n')
    sendMessage(
        s, 'Use them to find out if your lover is at the destination you think.\n')

    while True:
        sendMessage(s, f'{pegions_left} mechapegions awaiting instructions.\n')
        x = receiveMessage(s, 'Enter the x coordinate: ')
        y = receiveMessage(s, 'Enter the y coordinate: ')
        pegions_left -= 1

        try:
            guessed_coords = ecc.Point(route.E, int(x), int(y), route.ec_order)
            guessed_coords = (int(guessed_coords.x()), int(guessed_coords.y()))
        except Exception as e:
            print(e)
            sendMessage(
                s, 'The mechapegion got lost, maybe try valid coordinates next time\n')
            exit()

        if guessed_coords == destination_coords:
            sendMessage(
                s, f'You found your lover. Here is your flag: {FLAG}\n')
            exit()

        if pegions_left:
            sendMessage(s, f'Try again, your lover is not there\n')
        else:
            sendMessage(s, 'Maybe it wasn\'t meant to be\n')
            exit()

以上代码实现了一个基本的椭圆曲线算法, 代码提供了曲线参数(pa, b, ec_order)和初始点(G)坐标, 连接到服务器后,可以得到第一个点(station_coords)的坐标, 获得flag的条件在于计算出第二个点(destination_coords)的坐标。

解题过程

首先连接服务器后以获得第一个点的坐标,

$ nc 159.65.52.8 30762
The coordinates of the departing station were: (10387607239579938257626703795439257, 10616885760882242602781569890429191)
Your lover has arrived to the destination. Hurry up!
Luckily you have 6 mechapegions in your pockets.
Use them to find out if your lover is at the destination you think.
6 mechapegions awaiting instructions.
Enter the x coordinate: 

通过给出的曲线参数,我们可以对ec_order进行质因数分解

$ sage: factor(17101937747109687496599931614463506)
2 * 3 * 7^2 * 1487 * 3761 * 176489 * 439693 * 3113111 * 43054831

然后使用Pohlig-Hellman算法对第一个点计算其离散对数以获取其对应的d, 并进行进行质因数分解, sagemath代码如下

p = 17101937747109687265202713197737423
a = 2
b = 3
EC = EllipticCurve(GF(p), [a, b])

Gx = 3543321030468950376213178213609418
Gy = 14807290861072031659976937040569354

G = EC(Gx, Gy)

Gn = EC(10387607239579938257626703795439257, 10616885760882242602781569890429191)

ecOrder = 17101937747109687496599931614463506

primes = [2, 3, 7, 7, 1487, 3761, 176489, 439693, 3113111, 43054831]

dlogs = []

product = 1

for fac in primes:
  t = ecOrder // fac
  dlog = discrete_log(t*Gn, t*G, operation="+")
  dlogs.append(dlog)
  print("factor: ", fac, ", Discrete Log: ", dlog) 
  product = product * fac
  
d = crt(dlogs, primes)
print("d =", d)
# d = 1498230992101

assert d * G == Gn

factor(d)
4157 * 16183 * 22271

得到d的质因数之后,我们可以根据代码中给出的rotategoToNextStation方法对上述质因数的6个可能的组合计算出第二个点对应的d以及其坐标

def rotate(seed):
  x, y, z = seed
  x = (171 * x) % 30269
  y = (172 * y) % 30307
  z = (170 * z) % 30323
  seed = x, y, z
  return seed
    
def goToNextStation(seed):
  while True:
    seed = rotate(seed)
    x, y, z = seed
    if(isPrime(x) and isPrime(y) and isPrime(z)):
      d = x * y * z
      print("d = ", d)
      
      new_point = d * G
      print("new_point = ", new_point)
      return new_point
      
factors = [4201, 11329, 20921]      
      
goToNextStation((factors[0], factors[1], factors[2]))
goToNextStation((factors[0], factors[2], factors[1]))

goToNextStation((factors[1], factors[0], factors[2]))
goToNextStation((factors[1], factors[2], factors[0]))

goToNextStation((factors[2], factors[0], factors[1]))
goToNextStation((factors[2], factors[1], factors[0]))

以上代码输出6个可能的坐标,其中的一个可以获取flag。