ctf-writeups

babyprng

附件:

import random
from dataclasses import dataclass
from typing import List, Tuple

from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes
from secret import flag

MODULE_BITS = 512
BETA = 0.6


@dataclass(frozen=True)
class MaskSpec:
    low_bits: int
    high_shift: int

    def keep_low(self, value: int) -> int:
        return value & ((1 << self.low_bits) - 1)

    def keep_high(self, value: int) -> int:
        return (value >> self.high_shift) << self.high_shift


@dataclass
class LinearRecurrence:
    modulus: int
    coeffs: List[int]
    history: List[int]

    def tick(self) -> int:
        nxt = (self.coeffs[0] * self.history[-1] + self.coeffs[1] * self.history[-2]) % self.modulus
        self.history.append(nxt)
        return nxt


@dataclass
class ClockworkPRNG:
    n1: int
    n2: int
    x_rec: LinearRecurrence
    y_rec: LinearRecurrence
    z_trace: List[int]

    @classmethod
    def bootstrap(cls) -> "ClockworkPRNG":
        n1 = getPrime(MODULE_BITS)
        n2 = getPrime(MODULE_BITS)

        a_coeffs = [random.randrange(1, n1) for _ in range(2)]
        b_coeffs = [random.randrange(1, n2) for _ in range(2)]

        x_seed = [random.randrange(1, n2) for _ in range(2)]
        y_seed = [random.randrange(1, n2) for _ in range(2)]
        z_seed = [((x_seed[i] - y_seed[i]) % n1) for i in range(2)]

        return cls(
            n1=n1,
            n2=n2,
            x_rec=LinearRecurrence(n1, a_coeffs, x_seed),
            y_rec=LinearRecurrence(n2, b_coeffs, y_seed),
            z_trace=z_seed,
        )

    def step(self) -> Tuple[int, int, int]:
        x_val = self.x_rec.tick()
        y_val = self.y_rec.tick()
        z_val = (x_val - y_val) % self.n1
        self.z_trace.append(z_val)
        return x_val, y_val, z_val

    @property
    def a_coeffs(self) -> List[int]:
        return self.x_rec.coeffs

    @property
    def b_coeffs(self) -> List[int]:
        return self.y_rec.coeffs


MASK = MaskSpec(low_bits=int(MODULE_BITS * BETA), high_shift=int(MODULE_BITS * (1 - BETA)))


def gather_leakage(prng: ClockworkPRNG, count: int = 5) -> Tuple[List[int], List[int]]:
    low_x, high_z = [], []
    for _ in range(count):
        x_val, _, z_val = prng.step()
        low_x.append(MASK.keep_low(x_val))
        high_z.append(MASK.keep_high(z_val))
    return low_x, high_z


def encrypt_flag(prng: ClockworkPRNG) -> bytes:
    pad = prng.step()[1]
    plaintext = bytes_to_long(flag)
    return long_to_bytes(pad ^ plaintext)


def emit_challenge():
    engine = ClockworkPRNG.bootstrap()
    xs, zs = gather_leakage(engine)
    blob = encrypt_flag(engine)
    print(engine.a_coeffs, engine.b_coeffs, engine.n1, engine.n2, xs, zs, blob)


if __name__ == "__main__":
    emit_challenge()

"""
[10622255399663562069145222517631074636757726505025087959277441735616749694319550854863797670443534651186783567693433208322290346562348465617862644514847389, 5150224460963481866238166841282851820818799947914254748673565625815793126729527793491938920282232686079347210569442266283305865531049268112169511496927746], [5204753952338586880092348641669994129156178590855745261366232039885223974622545298276563525577443972124651339577891468011258412431471629386828307912459683, 5079734225447067974754947945327873911346636735905981526770074407711905651696829190513809796947471049379019689297209799730493816027786423191781843875675531], 10958629774530192764747159728121861053823929124378086630226413004437630713430771741823493173237142084991026926121932444744870148381851604042781380970444669, 9200429328134412336748885997782239392391071834610532031258802195740647984625163693533960417075416851627572621668211171924294159251959827253934478090380211, [172725921333705219686828397089776629172393055263012306396644606402731506210035516013543058124, 202745603270735608816524609283552093051331406448017971177380147701504875041184048198495558462, 68257763774743195610382299802371568139328884220019261062295558680693871404298028655323585656, 43570456098916056176492492662033934339435783469233318932894634807752715508937618248659738676, 81841093335490318218610136444202014180397509231955246448074176971442543798922263928961552133], [271002669418479642642553068844767379421050469420412042037755345994650216281759046698397156930164080245025575271381613567101064496930163675096593859608576, 211135275275860792942153398813833694516264877765852833336196411843651599326161620416771042200698342659972594458559247361195195948983619285219890713067520, 991565319401384342708138044676400067103250615156973756673688176776512913820178642376466735143963291078610359922261986209305378761794438074244766231429120, 7241056566248466988336854177955892118871557610915394850944392384375122314753075306409754732545646373509067809914409174184285627474963632984172466026840064, 5077484677424452789903502336300408141020037022065419442932638660556301184491285669822304104044377634411443772881151522717100856738472316902437457252843520], b'\xa9\xeb\x90\xab\t\x8a\xf1\xfe6y\xdf\xa8\xe7\x17\xa6\x9c\xc4|;q\x87_\xd3E\x1f)\x0bT\x08\x8a\xb6\x91c\xfcS\xab7\xc1\x13\x83\xcdgqp=RV\xd9\x82\xcf\xe8\xf9\xf7\xc3\xe2\x12_\xa4\xc7\x9fp\x07\x8a\x07'
"""

题目实现了一个随机数生成器,提供了中间输出的 MSB/LSB 和模数,对于这类题目,可以得到一系列的同余方程式,其中的未知数都是有界的,再用 cuso 求解未知数,再用未知数恢复 Flag:

from sage.all import var
import cuso
from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes

# from task.py
a_coeffs = [
    10622255399663562069145222517631074636757726505025087959277441735616749694319550854863797670443534651186783567693433208322290346562348465617862644514847389,
    5150224460963481866238166841282851820818799947914254748673565625815793126729527793491938920282232686079347210569442266283305865531049268112169511496927746,
]
b_coeffs = [
    5204753952338586880092348641669994129156178590855745261366232039885223974622545298276563525577443972124651339577891468011258412431471629386828307912459683,
    5079734225447067974754947945327873911346636735905981526770074407711905651696829190513809796947471049379019689297209799730493816027786423191781843875675531,
]
n1 = 10958629774530192764747159728121861053823929124378086630226413004437630713430771741823493173237142084991026926121932444744870148381851604042781380970444669
n2 = 9200429328134412336748885997782239392391071834610532031258802195740647984625163693533960417075416851627572621668211171924294159251959827253934478090380211
xs = [
    172725921333705219686828397089776629172393055263012306396644606402731506210035516013543058124,
    202745603270735608816524609283552093051331406448017971177380147701504875041184048198495558462,
    68257763774743195610382299802371568139328884220019261062295558680693871404298028655323585656,
    43570456098916056176492492662033934339435783469233318932894634807752715508937618248659738676,
    81841093335490318218610136444202014180397509231955246448074176971442543798922263928961552133,
]
zs = [
    271002669418479642642553068844767379421050469420412042037755345994650216281759046698397156930164080245025575271381613567101064496930163675096593859608576,
    211135275275860792942153398813833694516264877765852833336196411843651599326161620416771042200698342659972594458559247361195195948983619285219890713067520,
    991565319401384342708138044676400067103250615156973756673688176776512913820178642376466735143963291078610359922261986209305378761794438074244766231429120,
    7241056566248466988336854177955892118871557610915394850944392384375122314753075306409754732545646373509067809914409174184285627474963632984172466026840064,
    5077484677424452789903502336300408141020037022065419442932638660556301184491285669822304104044377634411443772881151522717100856738472316902437457252843520,
]
blob = b"\xa9\xeb\x90\xab\t\x8a\xf1\xfe6y\xdf\xa8\xe7\x17\xa6\x9c\xc4|;q\x87_\xd3E\x1f)\x0bT\x08\x8a\xb6\x91c\xfcS\xab7\xc1\x13\x83\xcdgqp=RV\xd9\x82\xcf\xe8\xf9\xf7\xc3\xe2\x12_\xa4\xc7\x9fp\x07\x8a\x07"

relations = []
modulus = []
bounds = {}

x_seed = [var(f"x_seed_{i}") for i in range(2)]
y_seed = [var(f"y_seed_{i}") for i in range(2)]
z_seed = [var(f"z_seed_{i}") for i in range(2)]

for i in range(2):
    bounds[x_seed[i]] = (1, n2)
    bounds[y_seed[i]] = (1, n2)
    bounds[z_seed[i]] = (0, n1)
    # z_seed[i] = x_seed[i] - y_seed[i] mod n1
    relations.append(z_seed[i] == x_seed[i] - y_seed[i])
    modulus.append(n1)

for i in range(5):
    # tick x
    nxt_x = var(f"nxt_x_{i}")
    # nxt = coeffs[0] * history[-1] + coeffs[1] * history[-2] % modulus
    relations.append(nxt_x == a_coeffs[0] * x_seed[-1] + a_coeffs[1] * x_seed[-2])
    modulus.append(n1)
    x_seed.append(nxt_x)
    bounds[nxt_x] = (0, n1)

    # LSB of nxt_x is xs[i]
    nxt_x_msb = var(f"nxt_x_{i}_msb")
    low_bits = int(512 * 0.6)
    relations.append(nxt_x == nxt_x_msb * (2**low_bits) + xs[i])
    modulus.append(None)
    bounds[nxt_x_msb] = (0, 2 ** (n1.bit_length() - low_bits))

    # tick y
    nxt_y = var(f"nxt_y_{i}")
    # nxt = coeffs[0] * history[-1] + coeffs[1] * history[-2] % modulus
    relations.append(nxt_y == b_coeffs[0] * y_seed[-1] + b_coeffs[1] * y_seed[-2])
    modulus.append(n2)
    y_seed.append(nxt_y)
    bounds[nxt_y] = (0, n2)

    # tick z
    nxt_z = var(f"nxt_z_{i}")
    relations.append(nxt_z == nxt_x - nxt_y)
    modulus.append(n1)
    bounds[nxt_z] = (0, n1)

    # MSB of nxt_z is zs[i]
    nxt_z_lsb = var(f"nxt_z_{i}_lsb")
    high_shift = int(512 * (1 - 0.6))
    relations.append(nxt_z == nxt_z_lsb + zs[i])
    modulus.append(None)
    bounds[nxt_z_lsb] = (0, 2**high_shift)


roots = cuso.find_small_roots(
    relations=relations,
    bounds=bounds,
    modulus=modulus,
)

print(roots)

# tick y
# nxt = coeffs[0] * history[-1] + coeffs[1] * history[-2] % modulus
y = (b_coeffs[0] * roots[0][y_seed[-1]] + b_coeffs[1] * roots[0][y_seed[-2]]) % n2
flag = long_to_bytes(bytes_to_long(blob) ^ y)
print(flag)