tk555 diary

プログラミング、もしくはそれ以外のこと書きます。

SECCON Beginners CTF 2021 初心者writeup

CTFにはwriteupというものを書く文化があるみたいなので書きましたが力尽きました。



f:id:tk55513:20210523195901p:plain

welcome合わせて16問で88位/943位。

解けたやつ

welcome

simple_RSA

何もわからないので「RSA ctf decrypt」ググるとこのStackOverFlowの質問が出てくる。

p,n,uncipherが分かっているとRsaCtfToolというので解けるみたい。

python3 ./RsaCtfTool.py -n 略 -e 3 --uncipher 略
  • 日本語の記事があまりなくて使うのに躊躇した。(仮想環境で解いてたのでインストール)
  • 他人のwrite upを見てるとgmpy2というので解いてる。

Logical_SEESAW

とりあえず与えられたスクリプトのFLAG部分を0b11110000に変えて試すと以下の対応になることがわかる。

確実に1が出力される桁 元の桁は1
確実に0が出力される桁 元の桁は0
0と1が不確定に出力される桁 元の桁は1

くっつけて終了

from Crypto.Util.number import *
from collections import Counter
cipher = [] #略

ans=['0','b']
for i in range(287):
    print(i)
    counter=Counter()
    counter.update([cip[i] for cip in cipher])
    print(counter)
    if len(counter)==1:
        if counter['1']==16:
            ans.append('1')
        else:
            ans.append('0')
    else:
        ans.append('1')
x=int(''.join(ans),2)
print(long_to_bytes(x))

GFM

謎の拡張子が出てきたのでググるsageMath というものらしい。

ソースを読むとフラグが仕込まれた行列Mが他の行列keyでかけられている(key*M*key)ので両側から逆行列をかけることを考える。

p=331941721759386740446055265418196301559
key = Matrix(GF(p),[[]])#略
enc = Matrix(GF(p),[[]])#略

SIZE=8
key_inv=key^(-1)
ans=key_inv*enc*key_inv
for i in range(SIZE):
    for j in range(SIZE):
        n = i * SIZE + j
        print(ans[i,j])

で出力される数字を文字になおして終了。

  • GFM(atrix)

imaginary

何もわからないのでこことかここを見た。
AESのECBモードはブロック単位で並べ替えても復号には成功するという話だと思われる。

うまいことサーバー側で
{"hoge + hogei":[hoge, hoge], "1337i":[hoge, hoge]}
みたいな文字列に復号(化)される暗号文を作る。

|------128bit平文-------|1337i":[hoge, hoge]
...適当な複素数...hoge] "|------128bit平文-------|

みたいな二文を暗号化してくっつける。

  • うまいことブロック長を合わせるとこがミソのはず。
  • 選択平文攻撃(CPA)をやってるんだと思ったけど鍵自体は求めてないので違う...?

only_read

objdump -d chall.out

でcmpが連続してる所が怪しかったので文字に直したらフラグ出た。

children

ps fで見てpidをコピペ。
複数回増えた場合、pidの大きい方入れたら出来た。

  • 子プロセスの追加の子プロセスの区別つけかたがわからない。

rewriter

こことかこことかこことかここみてやったはずだけど今やっても出来ない。

とりあえずobjdumpでwin関数のアドレスとchecksecでcanaryが無いことは確認したはず。

osoba

ディレクトリトラバーサルして下さいってURLが言ってる。

?page=../../../../../../flag

weresolf

開発者ツールでhtmlに以下を追加してsubmit

<input class="input" type="text" name="_Player__role" value="WEREWOLF">

check_url

ソースを読んでurlのパラメータにlocalhost的なやつ入れて下さいという気持ちを察する。これ思い出したので入れる。

json

まず、ヘッダー:X-Forwarded-Forを192.168.11.*に設定してリクエスト元を偽る。(このアドオンを入れた。)

後は以下実行

xhr = new XMLHttpRequest();
xhr.open("POST", "/");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
  if (xhr.status === 200) {
    console.log(
      '<article class="message is-success"><div class="message-header"><p>Success</p></div><div class="message-body">' +
      JSON.parse(xhr.response).result +
      "</div></article>");
  } else {
    console.log(
      '<article class="message is-danger"><div class="message-header"><p>Error</p></div><div class="message-body">' +
      JSON.parse(xhr.response).error +
      "</div></article>");
  }
};
data = JSON.stringify({
  id: 2,
  ID:1
});
xhr.send(data);

ソースコード見てJsonキーの小文字、大文字の区別が聞かれた気がしたけどdata = "{\"id\":2,\"id\":1}"でも行けるらしい。

cant_use_db

データベースの完全性とか基本情報で見たなと懐かしい気持ちになった。
複数ブラウザ立ち上げてーセッションIDを一緒にしてーとかやったが、普通にクリックで行けた。

git-leak

git reflogとgit resetで手動線形探索

Mail_Address_validator

確か同じ文字で正規表現やると遅くなるんだよな...で行けた。

depixelization

import cv2
import numpy as np

# flag = list("cpaw")+[chr(x) for x in range(ord('a'),ord('z')+1)]+[chr(x) for x in range(ord('A'),ord('Z')+1)]+[str(x) for x in range(1,10))]
def spaced(s):
    if len(s)<25:
        s=s+"x"*25
        s=s[:25]
    return s
def cpawed(s):
    return 'cpaw{'+s+"}"

letters=[chr(x) for x in range(ord('a'),ord('z')+1)]+[chr(x) for x in range(ord('A'),ord('Z')+1)]+[str(x) for x in range(1,10)]+['_','!','?']
flag_img=cv2.imread('output.png')
ans=[]
#31
for i in range(5,30):
    mi=None
    diff_min=10000000000000000000000000000000000000000000000
    candidate=None
    for let in letters:
        ans_tmp=ans[:]
        ans_tmp.append(let)
        #print('ans_tmp:',ans_tmp)
        #print('"".join(ans_tmp):',''.join(ans_tmp))
        #print('spaced("".join(ans_tmp)):',spaced(''.join(ans_tmp)))
        flag=cpawed(spaced(''.join(ans_tmp)))
        print("FLAG",flag)
        # char2img
        images = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
        for c in flag:
            img = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
            cv2.putText(img, c, (0, 80), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
            #   pixelization
            cv2.putText(img, "P", (0, 90), cv2.FONT_HERSHEY_PLAIN, 7, (0, 0, 0), 5, cv2.LINE_AA)
            cv2.putText(img, "I", (0, 90), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
            cv2.putText(img, "X", (0, 90), cv2.FONT_HERSHEY_PLAIN, 9, (0, 0, 0), 5, cv2.LINE_AA)
            simg = cv2.resize(img, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST) # WTF :-o
            img = cv2.resize(simg, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
    # concat
            if images.all():
                images = img
            else:
                images = cv2.hconcat([images, img])
        ## complete image
        im_diff=flag_img.astype(int)-images.astype(int)
        im_diff_abs=np.abs(im_diff)
        diff_size=len(list(zip(*np.where(im_diff_abs>100))))
        if diff_min>diff_size:
            print('update!')
            print(f'diff_min:{diff_min}->{diff_size}')
            print(f'candidate:{candidate}->{let}')
            diff_min=diff_size
            candidate=let
    ans.append(candidate)
    print(i,'no moji ha "'+candidate+'"')

解けなかったやつやる(随時追記

please_not_trace_me

そのまま実行すると"flag decrypted. bye."と出る。ghidraで逆コンパイルする。

ここを参考にbreakpointを入れる。

(gdb) b main
(gdb) run
(gdb) i proc mapping
process 3176
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0 
(gdb) x/16xb 0x555555554000 #確認のため
0x555555554000:	0x7f	0x45	0x4c	0x46	0x02	0x01	0x01	0x00 #ここがelfのヘッダーになってる
(gdb) b *0x55555555526c
(gdb) n

mainの逆コンパイルしたものを見ると以下で良い事がわかる。

  • switch文直前までで事前準備を済ませる。
  • case 0xfを通る。(鍵をmalloc)
  • case 0x13を通る。(鍵を読み込む)
  • rc4で復号。

適宜jumpで飛んでいく。

(gdb) b main
(gdb) run

#break point張る
(gdb) b *0x555555555494
(gdb) b *0x555555555270
(gdb) b *0x555555555289
(gdb) b *0x55555555564e
(gdb) b *0x55555555526e

(gdb) n
Single stepping until exit from function main,
which has no line number information.

Breakpoint 6, 0x000055555555526e in main ()
(gdb) jump *0x555555555494
Continuing at 0x555555555494.

Breakpoint 2, 0x0000555555555494 in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.

Breakpoint 6, 0x000055555555526e in main ()
(gdb) jump *0x555555555270
Continuing at 0x555555555270.

Breakpoint 3, 0x0000555555555270 in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.

Breakpoint 6, 0x000055555555526e in main ()
(gdb) jump *0x555555555289
Continuing at 0x555555555289.

Breakpoint 4, 0x0000555555555289 in main ()
(gdb) s
Single stepping until exit from function main,
which has no line number information.

Breakpoint 5, 0x000055555555564e in rc4 ()
(gdb) s
Single stepping until exit from function rc4,
which has no line number information.
__GI___libc_malloc (bytes=27) at malloc.c:3023
3023	malloc.c: そのようなファイルやディレクトリはありません.
(gdb) fin
Run till exit from #0  __GI___libc_malloc (bytes=27) at malloc.c:3023
0x0000555555555653 in rc4 ()
Value returned is $1 = (void *) 0x5555555592c0
(gdb) fin
Run till exit from #0  0x0000555555555653 in rc4 ()
0x000055555555529c in main ()
(gdb) x/27bc ((char [])*0x5555555592c0)
0x5555555592c0:	99 'c'	116 't'	102 'f'	52 '4'	98 'b'	123 '{'	100 'd'	49 '1'
0x5555555592c8:	100 'd'	95 '_'	121 'y'	48 '0'	117 'u'	95 '_'	100 'd'	51 '3'
0x5555555592d0:	99 'c'	114 'r'	121 'y'	112 'p'	116 't'	95 '_'	114 'r'	99 'c'
0x5555555592d8:	52 '4'	63 '?'	125 '}'
  • ghidraはJVMがjava11じゃないと変なエラーが出る。(11以降の16とかでもだめだった)