BSides Ahmedabad CTF 2021 Writeup

#1. はじめに
　2021/11/6(土) 12:00 JST ～ 2021/11/7(日) 12:00:00 JST で「BSides Ahmedabad CTF 2021」に参加し、Crypto 問題を 2 問だけ解きました（395点、54位/314チーム）。
　この CTF は zer0pts 作問ですので、競技中解けなかった問題もきっちり補完する予定です。

#2. Writeup

##2-1. dlppp (warmup, 151 points, 39 solves)
###設問
>Can you solve DLP?

```python:task.py
import os
from Crypto.Util.number import getPrime, getRandomNBitInteger

flag = os.getenv("FLAG", "XXXX{sample_flag}").encode()
m = int.from_bytes(flag, 'big')

p = getPrime(512)
y = pow(1 + p, m, p**3)

assert m < p
print(f"p = {hex(p)}")
print(f"y = {hex(y)}")
```

```python:output.txt
p = 0xa1c8e1e9b2301cb1f5d424ec6d959d7f275e11507b2177d55f3dc1268c9a3164b72832f362975023f09623814f80fe0ffad179d0e51c40b8a1f882d1f5f28e71
y = 0x6fa0fcc8c9c5f695a5709243698d7640c27c45352375919d538137333ab3a2c748cae5e7c1294d6ffc4007476f6fec6421c992f9fe1919b381306300caa2260953e48f2ec0de7b8c6417faa42001a748b1b367f5211095ddd6bf4e681f7e7ad787e0a7f562f6f0307d6a8d7e8d18cd59bd7572f0c4f430f0fd4fc61503b203f3bcd6dd0b0f84bbdbd42126d95b525fe77e4be62c6dbd083dbcaa284b20a9ea6faf9cbaf20dd88b0180417c9021fa1dcb52b2348c4376bd6b9b38a6c860086af
```

###方針・解法
　法が p^3 なので、局所体 Z_p で log(y)/log(p+1) を計算したものを lift すれば算出可能です。
　想定解は (p+1)^m を二項展開して・・・というやつだったっぽいです（フラグからそう受け取れます）が、手元に Z_p でやるのソルバがあったのでそちらを使って解きました。（ちなみに、SageMath を予め起動していたこともあって 1st blood をいただきました。）

###ソルバ
```python:solve.sage
from Crypto.Util.number import *
p = 0xa1c8e1e9b2301cb1f5d424ec6d959d7f275e11507b2177d55f3dc1268c9a3164b72832f362975023f09623814f80fe0ffad179d0e51c40b8a1f882d1f5f28e71
y = 0x6fa0fcc8c9c5f695a5709243698d7640c27c45352375919d538137333ab3a2c748cae5e7c1294d6ffc4007476f6fec6421c992f9fe1919b381306300caa2260953e48f2ec0de7b8c6417faa42001a748b1b367f5211095ddd6bf4e681f7e7ad787e0a7f562f6f0307d6a8d7e8d18cd59bd7572f0c4f430f0fd4fc61503b203f3bcd6dd0b0f84bbdbd42126d95b525fe77e4be62c6dbd083dbcaa284b20a9ea6faf9cbaf20dd88b0180417c9021fa1dcb52b2348c4376bd6b9b38a6c860086af

g = p + 1
F = Zp(p, prec=3)

x_Z = F(y).log() / F(g).log()
x = x_Z.lift()

print(long_to_bytes(x).decode())
```

###フラグ
>**Neko{b1n0m1al_th3or3m0o00oo000ooo00000ooooo00000000n}**

　提示されるスクリプトがシンプルで、スッキリした問題でした。

##2-2. floorsa (178 points, 27 solves)
###設問
>


```python:.chall.py
import os
import hashlib
from Crypto.Util.number import getPrime, getRandomNBitInteger
from itertools import product

def floor_sum(n: int, m: int, a: int) -> int:
  """Fast calculation for sum([a * i // m for i in range(n)])
  """
  res, b = 0, 0
  while 0 < n:
    res += n * (n - 1) // 2 * (a // m)
    a %= m
    res += n * (b // m)
    b %= m
    last = a * n + b
    n, m, a, b = last // m, a, m, last % m
  return res

#def floor_sum_tests():
#  for n, m, a in product(range(50), range(1, 50), range(50)):
#    result = floor_sum(n, m, a) 
#    expect = sum([a * i // m for i in range(n)])
#    assert(result == expect)

if __name__ == '__main__':
  #floor_sum_tests()

  flag = os.getenv('FLAG', 'XXXX{sample_flag}').encode()
  flag += hashlib.sha512(flag).digest()
  m = int.from_bytes(flag, 'big')
  assert m.bit_length() < 2048

  p = getPrime(1024)
  q = getPrime(1024)
  n = p * q
  e = 65537
  c = pow(m, e, n)
  s = floor_sum(q, q, p)

  print(f"c = {c}")
  print(f"n = {n}")
  print(f"s = {s}")
```

###方針
　floor_sum の値が解くカギとなることは間違いないので、まずは floor_sum の持つ性質を精査しました。雑に言えば n 程度の大きさですので、totioent がある程度絞られればラッキーと思いながらいろいろいじっていたら

　floor_sum(p,p,q) = floor_sum(q,q,p) = ((p-1)*(q-1))//2

なる性質があることに気付きました。つまり、phi = 2s。 これで Case Closed です。

###ソルバ

```python:solve.py
from Crypto.Util.number import *
c = 23040235907187792043102377766239160347012425454756214402219399982044253963561544138187423569531882081170834886320190973854626011799820277883217582208960795474430615579336090533683566370889382239077437657567815790536809115842475993748108172855688647606634510990327206587307392015543017884876145469179123535144938693302707229724909443912012098405828416163212773471183275687343852756789315215914149905888864296778004572587778916842514807079884644431138433900314631727531570096879428541834626325720522038566946030094711700613155240677360427005636301342509966941323931780006792225168839057660986447452212763627881844882128
n = 25436172154773316497363731760659809627551021985352168624695689317365040018878195925779734249357382145683534839534348832631746578327288150976696513216052612085728199134879493012682967254530827617417753223998955022174337237825391634619718971640535408277659054217709169558518413217237374290054035438476060534235907848570089739603581573457194953083783917042829987113625590656741008590506988903895998008845547262465276679611851110911540261411521221317990139079888782193797945245078986517794660508171428778191152342783279365287710944280356669999624474416422142006473378042779555537142175392801014489187786764971671239717769
s = 12718086077386658248681865880329904813775510992676084312347844658682520009439097962889867124678691072841767419767174416315873289163644075488348256608026306042864099567439746506341483627265413808708876611999477511087168618912695817309859485820267704138829527108854584779259206608618687145027017719238030267117794390566380531016624830798422997060308480467087130633621890831591995264022449058406630323270130520401030807803477672651197312971884784226103671425190328967548002718406368654056897938481966140031709870266384782295285897095028680666943294657806202686252742158733266700286323555374087306844259404255328911060160

phi = 2 * s
d = inverse(0x10001, phi)
print(long_to_bytes(pow(c,d,n)))
```

###フラグ
>**Neko{fl00r_func710n_1s_n0t_4n_3rr0r_ac1c29e1b5ff4fcaf0e1a0c1b36bb45c}**

「floor_sum(p,p,q) = floor_sum(q,q,p) = ((p-1)*(q-1))//2」実に美しい！・・・とうっとりしてその後は 1 問も解けず、Survey を入れ忘れるという失態をやらかしました。（競技後 submit しました。）

#3 おわりに
　zer0pts チームの作問はいつも美しいです。
　zer0ptsCTF 2022 とか CakeCTF2022 とか今から楽しみです。
　zer0ptsCTF 2022 とか CakeCTF2022 とか今から楽しみです。
　（大切な事なので２回記しました。）