2025鹏城杯 -- Crypto -- RandomAudit详解

2025鹏城杯 -- Crypto -- RandomAudit详解

题目描述:
cmd 复制代码
某内部审计系统号称"全部采用自研高强度密码模块",为了节省日志空间,他们把敏感日志AES加密上传后,又用RSA对关键密钥做了"备份加密",最后还用"秘密分享"来保护真正的FLAG。不行的是,开发同学对"随机数"和"密码学"理解似乎有点... ...偏差。你从审计服务器拿到以下文件你的任务是:从这些文件中还原出最终的flag。
题目附件
cmd 复制代码
aes_ct.txt
rsa_pub.txt
session_ids.txt
shares.enc
题目解析
  • 四个文件,打开查看一下数据信息
    aes_ct.txt

    cmd 复制代码
    iv = 42c92a1cdbb02d347b4b12b9609cf761
    ciphertext = 1268d5a5513b091aa91bd263479e066b07fadc2fbbe45bef3c551f5743df6290efac6008efec815a92d781f56d46a70c

    rsa_pub.txt

    python 复制代码
    from Crypto.Util.number import isPrime
    n = 14448446619901911082459007889685721725680439060725350743569322155645458011734556057675947993220704371020476516568646586885283913148938740926737898805445614714032672445417590527807998210095698512153249389800494251979617943578391716055258550807448612914300134993287344474991004931078983886017648843976928491853922545681367649020611719416129281298024839864263193236272253977406788185152922537075953161349835965958367362419140954183280033874927397931106816815848243331537133623628059136650221110030928439294443459932885523261901634369102152105618433841943766668404214910274232926198895411937001894534370492568479216945821
    e = 65537
    print(isPrime(n)) # False
    print(n.bit_length()) # 2047

    session_ids.txt

    python 复制代码
    session_id = [
        18180927788752802137,
        17282895955721322606,
        6099480220253429739,
        6300661433352782096,
        16375414315099041565
    ]
    for i in session_id:
        print(i.bit_length(), end = " ") # 64 64 63 63 64

    shares.en

    python 复制代码
    enc = [
        10490446977619103069001003040486311071978258142421336052507257522875474797470114964826023919192727268692922256856652858277992495171032562230040420656266197389810578603393339709249435149905947489583447642314971169203129701324545382296605329968093805623085091037041383918898863606006040657610555353505097417933879241971194430768615532922845973883691987688479825332766028800851528002249541074495918035787961557869335382147521674475402254162082487461333820079507872678735070636201634641388295625530938494923015901907636472951311020506187207731872422229080880406432931064027509903450627233085289319384639770111853961194173,
        743543530169071239116476194995130287577397444839878381190323439278920253193070703147739971809232103531663814326885396949461522610039497489729382962956546531947712520339554515115672653928505197575781138133407250554871658829577958508307679587210232844184449562790093797350878222669855209441010484097564691896453015601662453896822719270868321247424008816580067245157535459310278265418077691933129567685517708559976634296731742166139336765265993523200086705895447733895631261834250507718941102582843992200156414235064121973675311925953582587558104979368104990418797298789916260850166852409948945330444433345847549654807,
        2029042383383543423822480961040444011316223475888350349028507603033900194528521850810498682278559241519416272300206175404920960777005556199927430201052663390710468284846655098680434563893510099837791622819154273540446883979349265553248678057937124051615313506877155912975894736822847688340682915149135134624117775360734190231872747665460355709192535066382845245616076861873767871845621325187272538154068026983469680440318851689871048209435452483669288691299287219165436861956437625332354783255272742721764184259145427360294854601680425317982867911864451490378130715517007586443259629430886305855145096221339953320736
    ]
    for c in enc:
        print(c.bit_length(), end = " ") # 2047 2043 2045
  • 我们通过上面的分析可以看出来,session_id 分别为64-bit 的参数,而RSA 为常规的CTF-RSA 赛题,大概率对应着shares.enc 的公钥。此后,我以为这是一个基本符合安全标准的"伪加密系统",注意到这里给的其他信息很少,我们需要一层一层抽丝剥茧,题目描述中提及到的随机数 我认为是非常重要的,抱着多尝试的心态,这里我以为这里的随机数是出题人提及的密码系统 以时间戳为种子产生的相应随机数字,于是我开始以2020年~2026年 的所有时间戳为种子,在Python/C++/Java 编程环境下都进行了尝试,但是都没有找到满足session_ids.txt 的连续随机数,死马当活马医,我尝试使用LCG 试一下,后面发现的确是LCG

    python 复制代码
    temp = [18180927788752802137, 17282895955721322606, 6099480220253429739, 6300661433352782096, 16375414315099041565]
    a = (temp[2] - temp[1]) * pow(temp[1] - temp[0], -1, 2 ** 64) % (2 ** 64)
    b = (temp[1] - a * temp[0]) % (2 ** 64)
    print(f"a = {a}"); print(f"b = {b}")
    # a = 2985016781437487305
    # b = 7999203871274490253
    for i in range(1, len(temp)):
        print((a * temp[i - 1] + b) % (2 ** 64) == temp[i], end = " ") # True True True True
  • 之后问题感觉变简单了许多了,学过一些TLS 协议的话,当握手结束之后,接下来的适应大概就是密钥协商了,这里还是抱着尝试一下的心态,我利用现有的LCG 状态向后寻找AES的密钥,来对应密码系统中的会话密钥,有如下发现:

    python 复制代码
    next = lambda state : (a * state + b) & (1 << 64) - 1
    state = 16375414315099041565
    a = 2985016781437487305
    b = 7999203871274490253
    for i in range(10):
        state = next(state)
        print(hex(state)[2:])
    """
    e18e0b036273ff52
    1ae1729d164360ef
    42c92a1cdbb02d34 # iv[:16]
    7b4b12b9609cf761 # iv[16:]
    c27b9072fa995cb6
    23ef04e1eae1d473
    2d60a86204b9ffd8
    9c5147b880d57a25
    ae1acaa5c523289a
    7edc153c41ef0a77
    """

    这里我发现LCG 再向后的第3 个状态和第4 个状态就是iv ,于是这里就尝试将第1 个状态和第2 个状态作为key 进行AES密钥的猜测。

  • 由于这里存在iv ,并且密码系统中现在常用的对称加密一般为AES-GCM ,于是这边先尝试了一下AES-GCM解密:

    python 复制代码
    from Crypto.Cipher import AES
    key = bytes.fromhex('e18e0b036273ff521ae1729d164360ef')
    iv = bytes.fromhex('42c92a1cdbb02d347b4b12b9609cf761')
    ciphertext = bytes.fromhex('1268d5a5513b091aa91bd263479e066b07fadc2fbbe45bef3c551f5743df6290efac6008efec815a92d781f56d46a70c')
    tag = ciphertext[-16:]
    ciphertext = ciphertext[:-16]
    my_aes = AES.new(key = key, mode = AES.MODE_GCM, nonce = iv)
    
    print(my_aes.decrypt(ciphertext)) # b'oY\xa2\xebt?^\xe7\x9b\xe3\xb4\xae\xb2oJ\xb9\x83\x7f&:\x05\x02\xbd]\xdam^;\x15GC\xdc'

    这里发现并没有什么题目中所说的日志 ,于是乎,我采用了CBCCTR模式解密...

    python 复制代码
    # CBC
    from Crypto.Cipher import AES
    key = bytes.fromhex('e18e0b036273ff521ae1729d164360ef')
    iv = bytes.fromhex('42c92a1cdbb02d347b4b12b9609cf761')
    ciphertext = bytes.fromhex('1268d5a5513b091aa91bd263479e066b07fadc2fbbe45bef3c551f5743df6290efac6008efec815a92d781f56d46a70c')
    # tag = ciphertext[-16:]
    # ciphertext = ciphertext[:-16]
    my_aes = AES.new(key = key, iv = iv, mode = AES.MODE_CBC)
    print(my_aes.decrypt(ciphertext)) # b'\xf1\x0b?\xd0&\x7f`_\x83\xfct\x87\x9ei\xf9$5\x91\x9d\xe5KoC\xfe\x030\xd5^\xc6\x1bE,\xc5\xc5\x8c\xb3\xf4!\xad+\x1d\xf9\x14\x13\xe2\xabq\x10'
    python 复制代码
    # CTR
    from Crypto.Cipher import AES
    from Crypto.Util import Counter
    
    key = bytes.fromhex('e18e0b036273ff521ae1729d164360ef')
    iv = bytes.fromhex('42c92a1cdbb02d347b4b12b9609cf761')
    ciphertext = bytes.fromhex('1268d5a5513b091aa91bd263479e066b07fadc2fbbe45bef3c551f5743df6290efac6008efec815a92d781f56d46a70c')
    ctr = Counter.new(128, initial_value = int(iv.hex(), 16))
    my_aes = AES.new(key = key, mode = AES.MODE_CTR, counter = ctr)
    print(my_aes.decrypt(ciphertext)) # b'rsa_seed=106141198856029082317733263764736461207'

    这里我们就发现了有意义的明文rsa_seed=106141198856029082317733263764736461207 ,这个应该就是题目描述中所说的日志了。(其实,恢复LCG 之后吗,我觉得接下来就是由LCG 产生RSA 的分解素数了,因为在C++这类的编程语言中,大素数的来源一般都是由小素数经过一定的变化而来的,但是由于生成方式太多了,并且爆破一种的时间就很多,因此打算换条路径,看一看keyivLCG的关系)

  • 此后,我们就找到了这里的关键点,那就是我们可以分解RSA 的模数N 了(这里由于N2047-bit 的模数,因此采用1024的参数获取):

    python 复制代码
    import random
    
    n = 14448446619901911082459007889685721725680439060725350743569322155645458011734556057675947993220704371020476516568646586885283913148938740926737898805445614714032672445417590527807998210095698512153249389800494251979617943578391716055258550807448612914300134993287344474991004931078983886017648843976928491853922545681367649020611719416129281298024839864263193236272253977406788185152922537075953161349835965958367362419140954183280033874927397931106816815848243331537133623628059136650221110030928439294443459932885523261901634369102152105618433841943766668404214910274232926198895411937001894534370492568479216945821
    seed = 106141198856029082317733263764736461207
    random.seed(seed)
    while True:
        p = random.getrandbits(1024)
        if n % p == 0:
            print(f"p = {p}")
            break
    # p = 90609714628980382832928314067735122109537000815405703652905812504340171456915234535659504024209916610947095995757554416998229460671634350728278188486124871962281306292021145245327286154856863282195166928593488382708996274001548097657625671236135154197435672004124259946571008184688393674125715687022036411833

    如果大家要问,为何这里由rsa_seed=106141198856029082317733263764736461207 就想到要用random 模块了,是因为在常规的密码系统中,我们并没有getPrime()这样的函数使用,一般的生成方式就是伪随机数发生器,直接利用或组合生成。

  • 之后,按照题目描述的逻辑,我们要进行秘密分享 的恢复阶段了。秘密分享 一般的方式就是两种,一种是基于CRT 的,另外一种就是基于拉格朗日插值法 的。前者的碎片是**(m, s),后者碎片为 (x, f(x)),但是我们剩下的唯一的数据就是 shares.enc中的 3个将近 2048-bit的数据了,做到这里的时候我才真觉得这道题目离谱,此后经过不断地尝试,我决定直接对shares.enc**中的内容进行解密了:

    python 复制代码
    from Crypto.Util.number import long_to_bytes
    
    n = 14448446619901911082459007889685721725680439060725350743569322155645458011734556057675947993220704371020476516568646586885283913148938740926737898805445614714032672445417590527807998210095698512153249389800494251979617943578391716055258550807448612914300134993287344474991004931078983886017648843976928491853922545681367649020611719416129281298024839864263193236272253977406788185152922537075953161349835965958367362419140954183280033874927397931106816815848243331537133623628059136650221110030928439294443459932885523261901634369102152105618433841943766668404214910274232926198895411937001894534370492568479216945821
    e = 65537
    p = 90609714628980382832928314067735122109537000815405703652905812504340171456915234535659504024209916610947095995757554416998229460671634350728278188486124871962281306292021145245327286154856863282195166928593488382708996274001548097657625671236135154197435672004124259946571008184688393674125715687022036411833
    q = n // p
    d = pow(e, -1, (p - 1) * (q - 1))
    shares = [
        10490446977619103069001003040486311071978258142421336052507257522875474797470114964826023919192727268692922256856652858277992495171032562230040420656266197389810578603393339709249435149905947489583447642314971169203129701324545382296605329968093805623085091037041383918898863606006040657610555353505097417933879241971194430768615532922845973883691987688479825332766028800851528002249541074495918035787961557869335382147521674475402254162082487461333820079507872678735070636201634641388295625530938494923015901907636472951311020506187207731872422229080880406432931064027509903450627233085289319384639770111853961194173,
        743543530169071239116476194995130287577397444839878381190323439278920253193070703147739971809232103531663814326885396949461522610039497489729382962956546531947712520339554515115672653928505197575781138133407250554871658829577958508307679587210232844184449562790093797350878222669855209441010484097564691896453015601662453896822719270868321247424008816580067245157535459310278265418077691933129567685517708559976634296731742166139336765265993523200086705895447733895631261834250507718941102582843992200156414235064121973675311925953582587558104979368104990418797298789916260850166852409948945330444433345847549654807,
        2029042383383543423822480961040444011316223475888350349028507603033900194528521850810498682278559241519416272300206175404920960777005556199927430201052663390710468284846655098680434563893510099837791622819154273540446883979349265553248678057937124051615313506877155912975894736822847688340682915149135134624117775360734190231872747665460355709192535066382845245616076861873767871845621325187272538154068026983469680440318851689871048209435452483669288691299287219165436861956437625332354783255272742721764184259145427360294854601680425317982867911864451490378130715517007586443259629430886305855145096221339953320736
    ]
    
    for share in shares:
        m = pow(share, d, n)
        print(long_to_bytes(m))
    """
    b'\\\xe0\xe9\xa5`\x15\xfe\xc5\xaa\xdf\xa3(\xae9\x81\x07'
    b'\xb9\xc1\xd3J\xc0+\xfd\x8bU\xbfFQ\\s\x02\x1c'
    b'\x01\x16\xa2\xbc\xf0 A\xfcQ\x00\x9e\xe9z\n\xac\x831'
    """
  • 至此,的确没辙了,过了一段时间之后了解到题目有提示说flag为**flag{uuid}**的格式,但是这里实在找不到思路了,想着会不会解密之后的三个值,就是秘密分享的碎片?

    python 复制代码
    from Crypto.Util.number import long_to_bytes
    
    n = 14448446619901911082459007889685721725680439060725350743569322155645458011734556057675947993220704371020476516568646586885283913148938740926737898805445614714032672445417590527807998210095698512153249389800494251979617943578391716055258550807448612914300134993287344474991004931078983886017648843976928491853922545681367649020611719416129281298024839864263193236272253977406788185152922537075953161349835965958367362419140954183280033874927397931106816815848243331537133623628059136650221110030928439294443459932885523261901634369102152105618433841943766668404214910274232926198895411937001894534370492568479216945821
    e = 65537
    p = 90609714628980382832928314067735122109537000815405703652905812504340171456915234535659504024209916610947095995757554416998229460671634350728278188486124871962281306292021145245327286154856863282195166928593488382708996274001548097657625671236135154197435672004124259946571008184688393674125715687022036411833
    q = n // p
    d = pow(e, -1, (p - 1) * (q - 1))
    shares = [
        10490446977619103069001003040486311071978258142421336052507257522875474797470114964826023919192727268692922256856652858277992495171032562230040420656266197389810578603393339709249435149905947489583447642314971169203129701324545382296605329968093805623085091037041383918898863606006040657610555353505097417933879241971194430768615532922845973883691987688479825332766028800851528002249541074495918035787961557869335382147521674475402254162082487461333820079507872678735070636201634641388295625530938494923015901907636472951311020506187207731872422229080880406432931064027509903450627233085289319384639770111853961194173,
        743543530169071239116476194995130287577397444839878381190323439278920253193070703147739971809232103531663814326885396949461522610039497489729382962956546531947712520339554515115672653928505197575781138133407250554871658829577958508307679587210232844184449562790093797350878222669855209441010484097564691896453015601662453896822719270868321247424008816580067245157535459310278265418077691933129567685517708559976634296731742166139336765265993523200086705895447733895631261834250507718941102582843992200156414235064121973675311925953582587558104979368104990418797298789916260850166852409948945330444433345847549654807,
        2029042383383543423822480961040444011316223475888350349028507603033900194528521850810498682278559241519416272300206175404920960777005556199927430201052663390710468284846655098680434563893510099837791622819154273540446883979349265553248678057937124051615313506877155912975894736822847688340682915149135134624117775360734190231872747665460355709192535066382845245616076861873767871845621325187272538154068026983469680440318851689871048209435452483669288691299287219165436861956437625332354783255272742721764184259145427360294854601680425317982867911864451490378130715517007586443259629430886305855145096221339953320736
    ]
    
    for share in shares:
        m = pow(share, d, n)
        print(m)
        # print(long_to_bytes(m))
    """
    123456789012345678901234567890123456775
    246913578024691357802469135780246913564
    370370367037037036703703703670370370353
    """

    这里先尝试一下,将解密后的内容当作**(m, s)或者说是 (x, f(x))中的s**,看一下能否恢复:

    python 复制代码
    from Crypto.Util.number import *
    
    def recover_secret(shares, threshold, modulus):
    
        if len(shares) < threshold:
            raise ValueError(f"Need at least {threshold} shares to recover secret, but only {len(shares)} provided")
    
        secret = 0
        for i, (xi, yi) in enumerate(shares):
            numerator = 1
            denominator = 1
            for j, (xj, _) in enumerate(shares):
                if i != j:
                    numerator = (numerator * (0 - xj)) % modulus
                    denominator = (denominator * (xi - xj)) % modulus
    
            denominator_inv = pow(denominator, -1, modulus)
            lagrange_coef = (numerator * denominator_inv) % modulus
    
            secret = (secret + yi * lagrange_coef) % modulus
    
        return secret
    if __name__ == "__main__":
        n = 14448446619901911082459007889685721725680439060725350743569322155645458011734556057675947993220704371020476516568646586885283913148938740926737898805445614714032672445417590527807998210095698512153249389800494251979617943578391716055258550807448612914300134993287344474991004931078983886017648843976928491853922545681367649020611719416129281298024839864263193236272253977406788185152922537075953161349835965958367362419140954183280033874927397931106816815848243331537133623628059136650221110030928439294443459932885523261901634369102152105618433841943766668404214910274232926198895411937001894534370492568479216945821
        shares = [(123456789012345678901234567890123456775, 10490446977619103069001003040486311071978258142421336052507257522875474797470114964826023919192727268692922256856652858277992495171032562230040420656266197389810578603393339709249435149905947489583447642314971169203129701324545382296605329968093805623085091037041383918898863606006040657610555353505097417933879241971194430768615532922845973883691987688479825332766028800851528002249541074495918035787961557869335382147521674475402254162082487461333820079507872678735070636201634641388295625530938494923015901907636472951311020506187207731872422229080880406432931064027509903450627233085289319384639770111853961194173), (246913578024691357802469135780246913564, 743543530169071239116476194995130287577397444839878381190323439278920253193070703147739971809232103531663814326885396949461522610039497489729382962956546531947712520339554515115672653928505197575781138133407250554871658829577958508307679587210232844184449562790093797350878222669855209441010484097564691896453015601662453896822719270868321247424008816580067245157535459310278265418077691933129567685517708559976634296731742166139336765265993523200086705895447733895631261834250507718941102582843992200156414235064121973675311925953582587558104979368104990418797298789916260850166852409948945330444433345847549654807), (370370367037037036703703703670370370353, 2029042383383543423822480961040444011316223475888350349028507603033900194528521850810498682278559241519416272300206175404920960777005556199927430201052663390710468284846655098680434563893510099837791622819154273540446883979349265553248678057937124051615313506877155912975894736822847688340682915149135134624117775360734190231872747665460355709192535066382845245616076861873767871845621325187272538154068026983469680440318851689871048209435452483669288691299287219165436861956437625332354783255272742721764184259145427360294854601680425317982867911864451490378130715517007586443259629430886305855145096221339953320736)]
        flag = recover_secret(shares, 3, n)
        print(long_to_bytes(flag))

    到这里我有个很脑洞的发现,那就是,当我们对shares.enc 的内容进行RSA 解密后,得到的是一个等差数列 ,这个会不会就是题目中所说的分享的秘密?这个等差数列一定是出题人故意而为之的,但是目前走到这里,有了下面的发现之后,虽然猜测很大胆,但是目前也没有想到别的思路了。

    python 复制代码
    m = [
        123456789012345678901234567890123456775,
        246913578024691357802469135780246913564,
        370370367037037036703703703670370370353
    ]
    
    assert m[2] - m[1] == m[1] - m[0]
    diff = m[1] - m[0] # sum(m)
    secret = hex(diff)[2:]
    print(secret) # 5ce0e9a56015fec5aadfa328ae398115
    print(len(secret)) # 32
  • 我先尝试的是对m 直接求和,但是发现16 进制下,以及直接转bytes 都是不满足uuid(长度32)的。至此,到这里平台的提交系统已经关闭了,我也不知道我的推断是否是正确的,我认为的最终的flag为:

    cmd 复制代码
    flag{5ce0e9a5-6015-fec5-aadf-a328ae398115}
  • 真想看看正确的flag 是什么...,大脑燃烧了那么久...这道题目虽然出题人想要复现TLS协议的场景,但是还是有些脑洞,鄙人认为相当于是现实中分析着密码方案,信息搜集的人告诉你"可能吧"这种场景,感觉改成真实的流量包,可以看到完整的操作流程的那种考察效果会更好。

相关推荐
落羽凉笙20 小时前
Python基础(4)| 玩转循环结构:for、while与嵌套循环全解析(附源码)
android·开发语言·python
努力变大白20 小时前
借助AI零基础快速学会Python爬取网页信息-以天眼查爬虫为例
人工智能·爬虫·python
_Rookie._20 小时前
关于迭代协议:可迭代协议和迭代器协议,生成器函数 生成器对象的理解
javascript·python
农夫山泉2号20 小时前
【rk3588】——在rk3588上,用python进行qwen3-vl模型推理
python·flask·rk3588·qwen3-vl
967720 小时前
python基础自学
开发语言·windows·python
毕设源码-朱学姐20 小时前
【开题答辩全过程】以 基于Python的茶语店饮品管理系统的设计与实现为例,包含答辩的问题和答案
开发语言·python
我想回家种地20 小时前
python期末复习重点
前端·javascript·python
rgeshfgreh20 小时前
掌握PyWinAuto:高效Windows自动化
python
xwill*20 小时前
wandb的使用方法,以navrl为例
开发语言·python·深度学习
rgeshfgreh20 小时前
解决Windows系统Python命令无效问题
python