Attack All Around

今やCTFと競プロばかりだが、トビタテ生のアメリカ留学やTOEFL奮闘記

Lexington Informatics Tournament CTF 2021 writeup

久しぶりにwriteupを書きます。サボっていたわけではなく、一問も解けないCTFが続いたんです…。

Result


f:id:partender810:20210719133016p:plain
久しぶりの好成績


Writeup

7 More Caesar Salads


Here’s an easy Crypto problem, try to decipher mshn{Dlsjvtl_Av_Jyfwavnyhwof}

問題名からCaesar暗号のようですね。以下のサイトで試してみたらROT19で上手くいきました。

rot13.com


flag{Welcome_To_Cryptography}



Zzz


Oh no, somebody shuffled the alphabet, can you help us restore the message? Note that the flag is not wrapped with flag{}, so you will have to do that yourself.
ファイル:zzz.txt

アルファベットをシャッフルしたと問題文にあるので、換字式暗号です。quipquipの出番です。

https://www.quipqiup.com/

ここに、zzz.txtの内容を入れてsolveをクリックすると最初の文章がflagになります。flag{}で囲ってsubmitします。


flag{HOW_DO_YOU_KNOW_YOU_ARE_NOT_SLEEPING}



RSA Improved


The standard RSA only uses 2 prime factors, way too insecure. Let’s improve it by using more factors! More is better, ...right?
ファイル:rsa_improved.py, output.txt

問題文から、multiple primes RSAであることが予測できます。トーシェント関数φ(n)は(nを構成する素数-1)の積になります。nをfactorDBなどに投げると簡単に素因数分解できました。予想通り、素数が3個以上でした。問題ファイルからも分かりますね。

from Crypto.Util.number import *
n = (省略)
e = 65537
ct = (省略)


N = [2151055861, 2319991937, 2341310833, 2391906757, 2448497717, 2493514393, 2586983803, 2758321513, 2784469417, 2816940109, 2865965429, 3092165243, 3218701459, 3438516511, 3526137361, 3663803701, 3673658161, 3789550043, 3866428117, 3919632263, 4147385899]
phi = 1
for nn in N: phi *= (nn-1)
d = inverse(e,phi)
m = pow(ct,d,n)
print(long_to_bytes(m))


flag{rsa_1s_4_pr3tty_imp0rt4nt_crypt0_4lg0r1thm}



Geometry is Fun!


We all love GEOMETRY, let's reminisce of all the times our teacher told us to find how large a certain figure is! Remember, Geometry teachers are bad at encrypting messages so the encryption method is not hard :D
(Note that the graphs may not be precise - only use angles/lengths notated in the diagrams)
ファイル:GeoIsFun.png

f:id:partender810:20210719133338p:plain
GeoIsFun.png

何だこの問題は。

問題文を読むと、とりあえず面積を求めよとのこと。やってみましょう。
左上から右に、上から下に(A) ~ (H)と名付けます。

(A)

基本問題ですね。

3 × 6 ÷ 2 = 9


(B)

正三角形で一辺の長さが分かっています。その長さが2重根号になっています。
正三角形の高さは、(辺の長さ) × (√3/2) です。

√(16√3) × { (√(16√3) ) × (√3/2) } ÷ 2  
= { √(16√3) × √(16√3) } × (√3/2) ÷ 2  
= 16√3 × (√3/2) ÷ 2
= 16 × 3 ÷ 2 ÷ 2
= 12

なんと整数になりました。他の問題もそうなるのかなと予想。


(C)

(B)と考え方は同じですね。

√(10√3) × { (√(10√3) ) × √3 } ÷ 2  
= { √(10√3) × √(10√3) } × √3 ÷ 2  
= 10√3 × √3 ÷ 2
= 10 × 3 ÷ 2
= 15


(D)

(A) と同じですね。

22/3 × 6 ÷ 2 = 22


(E)

これもまた簡単ですね。

1 × 10 ÷ 2 = 5


(F)

三辺の長さだけが分かっている二等辺三角形ですね。

ヘロンの公式を使います。

ヘロンの公式 - Wikipedia

ヘロンの公式
面積S = √(s×(s-a)×(s-b)×(s-c) )
ただし、s = (a+b+c)/2, a,b,c は辺の長さ

これを適用します。

s = ( 5√2 + 5√2 + 2)/2 =  5√2 + 1  
S = √(s×(s-a)×(s-b)×(s-c) )
  = √( (5√2 + 1) × (5√2 + 1 - 5√2) × (5√2 + 1 - 5√2) × (5√2 + 1 - 2) )
  = √( (5√2 + 1) × (5√2 - 1) )
  = √( (5√2)×(5√2) - 1×1 ) 
  = √(50 - 1)
  = 7


(G)

三角形の面積を求めるのに、三角関数を使った方法あったなと思いググる

sin(サイン)を用いる三角形の面積公式を解説!

1/2 × 4 × 5 × sin30 = 5


(H)

直角二等辺三角形で、斜辺の長さだけ分かっています。

(一辺の長さ) = 2√15 × √2/2 = √30
(面積) = √30 × √30 ÷ 2 = 30/2 = 15


復号

以下の値が出揃いました。

9, 12, 15, 22, 5, 7, 5, 15

幾何学の先生だから暗号化方式はそんなに難しくないよ、と問題文にあります。

最大値が22であるので、1~26がa~zに対応しているのでは?とエスパー。復号してみると意味のある文になったビンゴ。


flag{ilovegeo}



Scottish Flag


Was is it Scottish or British? Oh who remembers these days
ファイル:scottish_flag.py, output.txt

scottish_flag.pyを読み、何が出力されているか見ると以下の式が成り立ちます。

t0, t1, t2 : 出力  
ct1, ct2 : 求めたいflag
a0, a1 : プログラム内の値。a1は未知。  


ct1^2 + (ct2-a0)^2 = t0 ・・・ (a)  
(ct1-a1)^2 + ct2^2 = t1 ・・・ (b)  
(ct1-a1)^2 + (ct2-a0)^2 = t2 ・・・ (c)  

未知数がct1, ct2, a1の3つに対し3式あるので、これは式同士を足し引きすれば求められますね。

ct2を求める
(b) - (c)

ct2^2 - (ct2-a0)^2 = t1 - t2
2×a0×ct2 - a0^2 = t1 - t2
ct2 = (t1-t2+a0^2) / 2×a0

ct2が求められました。これをlong_to_bytes()すると、

b'mak35_g00d_pro6I3m}'

となり、方向性は間違っていないようです。


ct1を求める
(a)

ct1^2 + (ct2-a0)^2 = t0
ct1 = √(t0 - (ct2-a0)^2 )

右辺のルート内を計算し、平方数であることを確認。二乗根を取ってlong_to_bytes()するとflagの前半が出ました。

b'flag{6r1t15h_cr0s5_'
from Crypto.Util.number import *
import gmpy2

t0 = 11167218166350596634324970518743041384610511755703179147618262518561344714067317278672955466
t1 = 11167218166350596634324970522379402989880404344356627706412007661522873314144716029510966061
t2 = 11167218166350596634324970517500867796567936885101366588743135261789294824144716029510966061
a0 = 1000000000000000000
c2 = (t1-t2+a0*a0) // (2*a0)
print(long_to_bytes(c2))
cc1 = (t0-pow(c2-a0,2))
print(cc1)
c1, ch = gmpy2.iroot(cc1,2)
assert ch
print(long_to_bytes(int(c1)))
flag = long_to_bytes(int(c1))
flag += long_to_bytes(c2)
print(flag)


flag{6r1t15h_cr0s5_mak35_g00d_pro6I3m}



Leftovers


Leftovers are bad, but I guess at least they help the environment by not wasting food.
ファイル:Leftovers.py, output.txt

Leftovers.pyが少し複雑ですが、読み解いて行きましょう。

変数gはPRNGクラスのインスタンスで、そのフィールドの一つであるseedにはflagを数値にしたものが入ります。

配列resには、1~4e10内で乱数を生成させ、その値未満の素数でseedを割った余りがどんどんappendされ、長さは12です。これはoutput.txtにあります。

はい、flagを特定するには中国剰余定理を用います。

生成する乱数ですが、最初にシードを1337として初期化しているので同じものを生成できます。中国剰余定理はsympyにあるので、それを使えば楽勝です。

from sympy.ntheory.modular import crt
import random
from Crypto.Util.number import *
import sympy
import math

val = [16751036754, 7441743891, 13537409447, 12455208971, 16901669565, 15060041617, 15538665605, 192375025, 2176355740, 21877084789, 3184436468, 13214581420]

random.seed(1337)
mod = []
for i in range(len(val)):
    mod.append(sympy.prevprime(random.randint(1,4e10)))

flag, MOD = crt(mod,val)
flag = int(flag)
print(long_to_bytes(flag))

なんとこれでは意味ある文字列になりませんでした…。思い違いがあるのかと思ったけど、それではなさそう。

Leftovers.pyに怪しげなassert文がありました。

assert(math.log10(ct) <= 128)

上のプログラムで求めているのは、mod[i]で割ると余りがval[i]になる最小値を求めています。何も最小値が答えとは限りません。求めた最小値に、Π mod[i] (= mod[0]×mod[1]×...×mod[n-1]) をどんどん足していって、long_to_bytes()してb"flag"が含まれていたらそれを出力してプログラムを終わらせます。

from sympy.ntheory.modular import crt
import random
from Crypto.Util.number import *
import sympy
import math

val = [16751036754, 7441743891, 13537409447, 12455208971, 16901669565, 15060041617, 15538665605, 192375025, 2176355740, 21877084789, 3184436468, 13214581420]

random.seed(1337)
mod = []
for i in range(len(val)):
    mod.append(sympy.prevprime(random.randint(1,4e10)))


flag, MOD = crt(mod,val)
flag = int(flag)
while math.log10(flag) <= 128:
    FLAG = long_to_bytes(flag)
    flag += MOD
    if b"flag" in FLAG:
        print(FLAG)
        break


flag{ch1nese_f00d_l3ft0v3r_th30r3m_1s_v3ry_d3l1c10u5}



Problems&Solver

GitHub - ksbowler/LITCTF2021

おわりに


最近研究の方も忙しくなり、CTFに参加しても最初の一問がすぐ解けないとそっとPCを閉じていました。CTFとの両立頑張りますが、期待はしないでください(笑)
久しぶりにwriteup書いたので、いつもの形式が違うとかあったらすみません