SECCON CTF 2023 予選 WriteUp


SECCON CTF 2023に参加しました。
チームとしての順位は115位で、国内46位でした。
自分が解いた問題は実質1問のみでしたが色々学びは得られたので楽しかったです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181686/e68f91a3-cd5a-ec1d-508e-ea951931ea3e.png)

## 解いた問題
- Welcome
- plai_n_rsa

## Welcome
617 Solved
### 問題文
Welcome to SECCON CTF 2023!

The flag is on Discord.
### 解説
問題文の通りでDiscordを眺めるとFlagがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181686/8990f6f1-e407-5127-76c6-91b04c907338.png)

いつもある挨拶がわりの問題です。

## plai_n_rsa
183 Solved
### 問題文
I've dropped the "n" ... where is my "n" :(
### 解説

問題文からRSA暗号の問題と推測できます。

暗号文の生成を行っているプログラムと実行結果のファイルが与えられます。

```py:problem.py
import os

from Crypto.Util.number import bytes_to_long, getPrime

flag = os.getenvb(b"FLAG", b"SECCON{THIS_IS_FAKE}")
assert flag.startswith(b"SECCON{")
m = bytes_to_long(flag)
e = 0x10001
p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 65537
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
hint = p+q
c = pow(m,e,n)

print(f"e={e}")
print(f"d={d}")
print(f"hint={hint}")
print(f"c={c}")
```

```txt:output.txt
e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813
```

hintの値は非常にでかいのでp,qを発見することは困難です。
問題文はnを探して的な雰囲気を醸しているので、nを作る方針を考えます。

そこでproblem.pyを眺めていると、14行目に
```py
d = pow(e, -1, phi)
```
があります。$phi=ϕ$として、式に直すと

```math
d \equiv e^{-1} \pmod ϕ
```
```math
d \cdot e - 1 \equiv 0 \pmod ϕ
```
ここで
```math
\begin{align}
ϕ&=(p-1)(q-1) \\ 
&= p\cdot q - (p+q) + 1 \\
&= N - hint + 1
\end{align}
```
なので
```math
N = ϕ + hint - 1
```
これでNを作る方針が立ちました。

そして途中で出てきた以下の式より

```math
d \cdot e - 1 \equiv 0 \pmod ϕ
```
$d * e - 1$は$ϕ$の倍数だとわかります。
よって、
```math
d \cdot e - 1 = k \cdot ϕ
```
と書けます。

```math
d \cdot e \equiv 1 \pmod ϕ
```
から、$d \cdot e$ は $ϕ$ で割ったあまりが1。
これはある正の整数$n$を掛けた$(n\cdot d)\cdot e$でも成立する。
$n\cdot d > ϕ$だとしても$n\cdot d \cdot e \equiv  1 \pmod ϕ$の関係は変わらないので$d<ϕ$として良い。
よって
```math
d \cdot e = k \cdot ϕ + 1
```
について$k < e$と言えるので、$ϕ$について
```math
ϕ=\frac{d \cdot e-1}{k}
```
で求められる。

ここまでの情報から、1~65536までの$k$を探索すれば解けると分かります。
以下、exploitコードです。

```py:solve.py
from Crypto.Util.number import long_to_bytes

e = 65537
hint = 275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c = 8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813
d = 15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793

def is_valid_ascii(s):
    try:
        s.decode('ascii')
        return True
    except UnicodeDecodeError:
        return False

def generate_phi():
    value = d * e - 1
    for i in range(1,e):
        if value%i==0:
            yield value//i
    
for phi in generate_phi():
    n = phi + hint - 1
    m = pow(c, d, n)

    flag = long_to_bytes(m)
    if is_valid_ascii(flag):
        print(flag)
```

実行するとFlagが得られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181686/2014cff1-41a5-4c2f-efa9-8839772afd11.png)

## 小話

#### ネット海の何処かにある初参加の記録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181686/296fc14a-168b-bcc6-37f2-7f517cd850ed.png)

自分は競プロ系のツイート多いですが、実はTwitter（X）始めた理由はCTF参加がきっかけでした。

2016年にプログラミングを学びはじめ、当時メアドもパスワードも手書きのメモ用紙に書いていたので全て紛失した結果ログインできなくなったサイトが今でも残ってたりします。

村人A倒してるから、CTF戦闘力6くらいにはなってるはず、、、！