---
# System prepended metadata

title: PicoCTF - NSA Backdoor
tags: [PicoCTF, Crypto, CTF]

---

# PicoCTF - NSA Backdoor
###### tags: `PicoCTF` `CTF` `Crypto`

## Background
[Baby Step Giant Step - BSGS](https://blog.csdn.net/ACdreamers/article/details/8579511)
> 用来解决如下方程最小正整數解的
> $A^x\equiv B(mod\ C)$，其中$0\le x\lt C$
> 如果$A\ge C, B\ge C$，那麼我們可以先取模，即$A\% = C, B\% = C$，所以在這裡我們只討論$0\le A, B\lt C$的情況。
> 普通的BSGS的步驟是這樣的：
> 1. 首先確定$x$的下限是$0$，上限是$C$，我們令$M=\lceil C\rceil$
> 2. 把$A^0~A^M\ mod\ C$的值存到一个Hash表裡面
> 3. 把$(A^M)^0~(A^M)^M\ mod\ C$的值一一枚舉出來，每枚舉一個就在Hash表裡面尋找是否有一個$val$值滿足$val \cdot (A^M)^i\ mod\ C=B$，如果有則找到答案，否則繼續
> 4. 最終答案就是$i\cdot M+val$的值對應的原來$A$的冪
> 上面是普通Baby Step Giant Step的步驟，比較簡單，只適用為素數的情況。如果為合數呢？

拓展的過程詳見全文

---
[離散對數問題 - pohlig-hellman算法講解](https://blog.csdn.net/oampamp1/article/details/104061969)
> 1. 需要注意的是，pohlig-hellman算法的覆雜度在一般情況下比BSGS高！
因此，使用pohlig-hellman的場合只能是較為特殊的情況，即:$p$是質數，且$p-1$包含的質因子較少&較小。
>2. 和BSGS算法一樣，pohlig-hellman算法也是用於解決離散對數問題（也有很多文獻提到是解決橢圓曲線之類的）。即給定$a,b,p$,求 $a^x \equiv b(mod\ p)$。
>3. 歐拉定理: 若$(a,p)=1$，那麽$a^{φ(p)} \equiv1(mod\ p)······(*)$
>證明略。
>4. 費馬小定理: 如果$p$是質數，那$φ(p)=p-1$。
>
>5. 對於$a^x\equiv b(mod\ p)$，記其原根為$g$，則$a=g^{a_i},b=g^{b_i}$(原根的次冪可以在[1,p-1]中一一對應，故$a_i,b_i$必定存在)，即$g^{a_ix}\equiv g^{b_i}(mod\ p)$，所以$a_ix\equiv b_i(mod\ p-1)$，(因為$g^{p-1}\equiv 1(mod\ p)$，$p−1$是最小循環節，即階)，故利用egcd求出$x$即可。，於是我們的問題就變成了如何求$a_i,b_i$。

之後詳細的步驟詳見原文

## Source code
* output.txt
    ```
    n = 98a3425eee4016a2592706867127e6c52ab2cf8077806f5626095e3afadc73cb4d0e747c5b9bf6234242e9578b12aba5e391e04a5cd2730f6e45d9f0758fb69eb32e0070b9efd3470f6571a8443bae63cd16efcb3e945dc3da1ce46993be4c8b4467ffb4e0525428bb8673ba144b0d36d1c34fe87307d68439070da27a8809551aa6cdf55c39c79bb7b6b7b9c26b45ef79f6c1ebf68033e4beab2d24df66f69dfb7f54d70d3b477fc7b67592cb029dfe6341c591c34a127f84b33626cd117707b69d1ed55f1773e3ba8d26b76f2db95e85de14a6aa1ff3de7fa23ce9f7ebd0e6c18c2fef4bbff47b6bd632d2d767aab7d35bf4d8577e50556626096704f0c425
    c = 8788542cefd7490c9282c06b8d24280d56c6706b996bdf580290cdf2cb90e45efd2ce185fc07d2b916c24b0512d38ca14de0ee608a9d6003f258859bbbed97dad15c1d07410a34fd55cd8305eb43418d38f1ca6e024725b97fd9da701a39c23fe55a13d43b4bf9a3d9ebb44d7fe67bd60beffc29ec27bb4baf05ec5b250bfa68360df0d1379c066297a7878e59d27e68cf6a0da90755450827623e54e4f3d9f280fef53c7620d58decfbd10dd64e9d1d5507b5460603c58f5be70c82e2a8e613d730a950caea4c4389c5fc0521f8207ead5fb26c04eb6d0486fd6fe8d015fdabbda00139b42163acc86ffb30c12988058c6247344c42b8f3cdc984c06f4276f8
    ```
* `gen.py`
    :::spoiler Source Code
    ```python
    #!/usr/bin/python

    from binascii import hexlify
    from gmpy2 import *
    import math
    import os
    import sys

    if sys.version_info < (3, 9):
        math.gcd = gcd
        math.lcm = lcm

    _DEBUG = False

    FLAG  = open('flag.txt').read().strip()
    FLAG  = mpz(hexlify(FLAG.encode()), 16)
    SEED  = mpz(hexlify(os.urandom(32)).decode(), 16)
    STATE = random_state(SEED)

    def get_prime(state, bits):
        return next_prime(mpz_urandomb(state, bits) | (1 << (bits - 1)))

    def get_smooth_prime(state, bits, smoothness=16):
        p = mpz(2)
        p_factors = [p]
        while p.bit_length() < bits - 2 * smoothness:
            factor = get_prime(state, smoothness)
            p_factors.append(factor)
            p *= factor

        bitcnt = (bits - p.bit_length()) // 2

        while True:
            prime1 = get_prime(state, bitcnt)
            prime2 = get_prime(state, bitcnt)
            tmpp = p * prime1 * prime2
            if tmpp.bit_length() < bits:
                bitcnt += 1
                continue
            if tmpp.bit_length() > bits:
                bitcnt -= 1
                continue
            if is_prime(tmpp + 1):
                p_factors.append(prime1)
                p_factors.append(prime2)
                p = tmpp + 1
                break

        p_factors.sort()

        return (p, p_factors)

    while True:
        p, p_factors = get_smooth_prime(STATE, 1024, 16)
        if len(p_factors) != len(set(p_factors)):
            continue
        # Smoothness should be different or some might encounter issues.
        q, q_factors = get_smooth_prime(STATE, 1024, 17)
        if len(q_factors) == len(set(q_factors)):
            factors = p_factors + q_factors
            break

    if _DEBUG:
        import sys
        sys.stderr.write(f'p = {p.digits(16)}\n\n')
        sys.stderr.write(f'p_factors = [\n')
        for factor in p_factors:
            sys.stderr.write(f'    {factor.digits(16)},\n')
        sys.stderr.write(f']\n\n')

        sys.stderr.write(f'q = {q.digits(16)}\n\n')
        sys.stderr.write(f'q_factors = [\n')
        for factor in q_factors:
            sys.stderr.write(f'    {factor.digits(16)},\n')
        sys.stderr.write(f']\n\n')

    n = p * q
    c = pow(3, FLAG, n)

    print(f'n = {n.digits(16)}')
    print(f'c = {c.digits(16)}')
    ```
    :::

## Recon
這一題有點難，應該說觀念很簡單，我也有想到但不知道怎麼實作，簡單來說就是解discrete log的問題，總而言之，這一題和[Very Smooth](https://hackmd.io/@SBK6401/HyTTXZnPh)幾乎一樣，差別在於他把flag當成exponent，i.e. $c=3^{flag}\ mod\ n$，並且提供$n, c$，所以我們要解出flag為多少
## Exploit - Pohlig-Hellman(SageMath)
1. Factor $p$ and $q$
    1. Method 1
        ```python
        from gmpy2 import *
        from Crypto.Util.number import long_to_bytes

        a = 2
        n = 2
        N = "98a3425eee4016a2592706867127e6c52ab2cf8077806f5626095e3afadc73cb4d0e747c5b9bf6234242e9578b12aba5e391e04a5cd2730f6e45d9f0758fb69eb32e0070b9efd3470f6571a8443bae63cd16efcb3e945dc3da1ce46993be4c8b4467ffb4e0525428bb8673ba144b0d36d1c34fe87307d68439070da27a8809551aa6cdf55c39c79bb7b6b7b9c26b45ef79f6c1ebf68033e4beab2d24df66f69dfb7f54d70d3b477fc7b67592cb029dfe6341c591c34a127f84b33626cd117707b69d1ed55f1773e3ba8d26b76f2db95e85de14a6aa1ff3de7fa23ce9f7ebd0e6c18c2fef4bbff47b6bd632d2d767aab7d35bf4d8577e50556626096704f0c425"
        c = "8788542cefd7490c9282c06b8d24280d56c6706b996bdf580290cdf2cb90e45efd2ce185fc07d2b916c24b0512d38ca14de0ee608a9d6003f258859bbbed97dad15c1d07410a34fd55cd8305eb43418d38f1ca6e024725b97fd9da701a39c23fe55a13d43b4bf9a3d9ebb44d7fe67bd60beffc29ec27bb4baf05ec5b250bfa68360df0d1379c066297a7878e59d27e68cf6a0da90755450827623e54e4f3d9f280fef53c7620d58decfbd10dd64e9d1d5507b5460603c58f5be70c82e2a8e613d730a950caea4c4389c5fc0521f8207ead5fb26c04eb6d0486fd6fe8d015fdabbda00139b42163acc86ffb30c12988058c6247344c42b8f3cdc984c06f4276f8"
        c = int(c, 16)
        N = int(N, 16)
        while True:
            a = powmod(a, n, N)
            res = gcd(a-1, N)
            if res != 1 and res != N:
                q = N // res
                print("q = {}\np = {}".format(q, res))
                break
            n += 1
        ```
    3. Method 2
        ```python
        import primefac

        n = 0xd63c7cb032ae4d3a43ecec4999cfa8f8b49aa9c14374e60f3beeb437233e44f988a73101f9b20ffb56454350b1c9032c136142220ded059876ccfde992551db46c27f122cacdd38c86acb844032f8600515aa6ccb7a1d1ac62d04b51b752476d2d6ee9f22d0f933bebdd833a71fd30510479fcc7ba0afb1d4b0a1622cdc2a48341010dffdcfc8d9af45959fb30b692dc2c9e181ac6bcd6a701326e3707fb19b7f9dfe1c522c68f9b0d229d384be1e1c58f72f8df60ca5172a341a7ee81428a064beedd6af7b89cc6079f2b6d3717f0d29330f0a70acca05bf67ab60c2e5cb0b86bfca2c9b8d50d79d24371432a1efb243f3c5f15b377ccc51f6e69bfbf5ecc61
        c = 0x51099773fd2aafd5f84dfe649acbb3558797f58bdc643ac6ee6f0a6fa30031767966316201c36be69241d9d05d0bd181ced13809f57b0c0594f6b29ac74bc7906dae70a2808799feddc71cf5b28401100e5e7e0324b9d8b56e540c725fa4ef87b9e8d0f901630da5f7f181f6d5b4cdc00d5f5c3457674abcb0d0c173f381b92bdfb143c595f024b98b9900410d502c87dfc1633796d640cb5f780fa4b6f0414fb51e34700d9096caf07b36f4dcd3bb5a2d126f60d3a802959d6fadf18f4970756f3099e14fa6386513fb8e6cdda80fdc1c32a10f6cdb197857caf1d7abf3812e3d9dcda106fa87bac382d3e6fc216c55da02a0c45a482550acb2f58bea2cfa03
        q = primefac.pollard_pm1(n)
        p = n//q
        print(f'p = {p}')
        print(f'q = {q}')
        ```
2. Use [SageMath](https://sagecell.sagemath.org/) to address Discrete Log Problem
    ```python
    p = 117635180960139721127318189832610714114593440637486157582828661167364276581210599344857316369131977790468647533227778603367761815400416396281259234299247850289710613080530669849409358755399675041263469367135430665518150110493389671646158566214130516002949975036799297119111385228596853422400303735447298026283
    q = 163800729847029979711295941089800020300275211671661376396219775666688832353701752860857691086339595920419175562271802936423756228938551439950541873798393442729921516031775531740506399414675546114663346731428381174638773512946351966471041847661507898143967764453261943807056370639171597924004988320983393199599
    c = 0x8788542cefd7490c9282c06b8d24280d56c6706b996bdf580290cdf2cb90e45efd2ce185fc07d2b916c24b0512d38ca14de0ee608a9d6003f258859bbbed97dad15c1d07410a34fd55cd8305eb43418d38f1ca6e024725b97fd9da701a39c23fe55a13d43b4bf9a3d9ebb44d7fe67bd60beffc29ec27bb4baf05ec5b250bfa68360df0d1379c066297a7878e59d27e68cf6a0da90755450827623e54e4f3d9f280fef53c7620d58decfbd10dd64e9d1d5507b5460603c58f5be70c82e2a8e613d730a950caea4c4389c5fc0521f8207ead5fb26c04eb6d0486fd6fe8d015fdabbda00139b42163acc86ffb30c12988058c6247344c42b8f3cdc984c06f4276f8
    g = Mod(3,p)
    m = discrete_log(c,g)
    print(hex(m))
    g2 = Mod(3,q)
    m2 = discrete_log(c,g2)
    print(m2)
    print(hex(m2)[2:])
    ```
    * Output
        ```bash
        0x7069636f4354467b6233773472335f30665f63306d7030733174335f6d3064756c315f39396633383833377d
        4028375274964940959020413024799108535910958820283330112174774258028392431441247073675773191542213151242109
        7069636f4354467b6233773472335f30665f63306d7030733174335f6d3064756c315f39396633383833377d
        ```
        
Flag: `picoCTF{b3w4r3_0f_c0mp0s1t3_m0dul1_99f38837}`
## Reference
[pico2022 nsa backdoor](https://youtu.be/pARmkuMg5tk)