土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

picoCTFを始めてみた(Beginner picoMini 2022:全13問完了)

前回 は、サイバーセキュリティのトレーニングができるサイトを始めてみましたが、少し合わなかったかなって感じでした。

今回も、こりずに、picoCTF という CTF の学習や、コンテストを開催しているサイトがあるので、挑戦してみたいと思います。

それでは、やっていきます。

※2024/10/3:追記

今回は、picoCTF の picoGym で、「Beginner picoMini 2022」の 全13問をやってみました。もし、これから「Beginner picoMini 2022」をやってみようと思う方にお知らせです。問題のタイトルに番号が付いてるものがあります(例:PW Crack 2)。それらの問題については、番号の若い順に解くことをお勧めします。番号が進むにつれて難易度が上がる形になっているためです。

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧
・第1回:Ghidraで始めるリバースエンジニアリング(環境構築編)
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方とGPUで実行したときの時間を見積もってみる
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(クリア状況は随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリhttps://github.com/SECCON/SECCON2017_online_CTF.gitティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ
・第27回:x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく)
・第28回:入門セキュリティコンテスト(CTFを解きながら学ぶ実践技術)のPwnable問題をやってみる
・第29回:実行ファイルのセキュリティ機構を調べるツール「checksec」のまとめ
・第30回:setodaNote CTF Exhibitionにチャレンジします(クリア状況は随時更新します)
・第31回:常設CTFのksnctfにチャレンジします(クリア状況は随時更新します)
・第32回:セキュリティコンテストチャレンジブックの「Part2 pwn」を読んだ
・第33回:セキュリティコンテストチャレンジブックの「付録」を読んでx86とx64のシェルコードを作った
・第34回:TryHackMeを始めてみたけどハードルが高かった話
・第35回:picoCTFを始めてみた(Beginner picoMini 2022:全13問完了) ← 今回

picoCTF の公式サイトは以下です。英語のサイトですが、こちらも、やってる方が多いみたいです。

picoctf.com

それでは、やっていきます。

picoCTFに登録する

公式サイトにアクセスすると、以下の項目が並んでいます。

項目 内容 詳細
Learn picoPrimerで知識を習得 役に立ちそうな大量のテキストがありました
Practice picoGymとミニコンテストで練習 過去のコンテストの内容で練習できるようです
Compete コンテストに参加 1年に1、2回開催されているようです、前回は2024年3月に実施されたようです
Visualize サイバーセキュリティのキャリアの紹介 カーネギーメロン大学のWebサイトに飛ぶ
Facilitate 生徒、または、グループの進捗状況を監視? よく分かりません
Play 参加して楽しくプレイ コンテストと同じ画面に見えます、観客用?

まずは、Practice(picoGym)に行ってみようと思います。

Practiceのログイン画面
Practiceのログイン画面

ユーザ名や、E-Mailアドレスを入力して、いくつかの質問に答えて、Sign Up をクリックします。

ユーザ登録画面
ユーザ登録画面

確認メールが届くので、届いたら、リンクをクリックすると、完了です。

登録は以上です。

Practice(picoGym)を始めてみる

登録が完了すると、以下のようなトップページが表示されます。左側のフィルタをチェックすると、所望の問題が表示されるようです。

Practice(picoGym)のトップ画面
Practice(picoGym)のトップ画面

試しに、「Beginner picoMini 2022」というのがあったので、それでフィルタしてみます。全部で 13問あるようです。最初の方は、Easy の問題が続き、最後の方は、Medium があります。

Beginner picoMini 2022を表示
Beginner picoMini 2022を表示

良さそうな感じなので、「Beginner picoMini 2022」をやってみます。

runme.py

最初は、Pythonスクリプト(runme.py)がダウンロードできて、それを実行するとフラグが表示されるようです。

runme.py問題
runme.py問題

PW Crack 2

先に PW Crack 2 が並んでますが、次の問題は PW Crack です。

Pythonスクリプト(level2.py)と暗号化されたパスワードファイル(level2.flag.txt.enc)がダウンロードできます。

PW Crack 2問題
PW Crack 2問題

パスワードファイルをバイナリエディタで開くと、31byte でした。

Pythonスクリプトを実行してみると、パスワードを聞かれます。Pythonスクリプトを読むと、入力するパスワードが分かります。簡単でした。

$ python level2.py
Please enter correct password for flag:

PW Crack 1

上の「PW Crack 2」と同じ問題形式です。level1.py と level1.flag.txt.enc がダウンロードできます。

PW Crack 1問題
PW Crack 1問題

バイナリファイルは、今度は、30byte でした。Pythonスクリプトを見ると、すぐに答えが分かります。PW Crack 2 より簡単でした。

$ python level1.py
Please enter correct password for flag: 

HashingJobApp

次は、インスタンスを起動します。すると、「nc saturn.picoctf.net 53399」と表示されて、15分のカウントダウンが始まります。15分以内に、答える必要があるようです。

HashingJobApp問題
HashingJobApp問題

netcat で接続してみます。3回正解すると、フラグが表示されました。

$ nc saturn.picoctf.net 53399
Please md5 hash the text between quotes, excluding the quotes: 'Al Pacino'
Answer:
1c9909508d984c65fb3a2f3b28d27faf
1c9909508d984c65fb3a2f3b28d27faf
Correct.
Please md5 hash the text between quotes, excluding the quotes: 'choir boys'
Answer:
ce6b6964402e279f878ce9cf5e19d14b
ce6b6964402e279f878ce9cf5e19d14b
Correct.
Please md5 hash the text between quotes, excluding the quotes: 'carnival workers'
Answer:
d3818fc79b44a9f961c502fb3a9bd42d
d3818fc79b44a9f961c502fb3a9bd42d
Correct.

Glitch Cat

次の問題もインスタンスを起動する形式です。

Glitch Cat問題
Glitch Cat問題

以下のように表示されました。

$ nc saturn.picoctf.net 61936
'picoCTF{gl17ch_m3_n07_' + chr(0x61) + chr(0x34) + chr(0x33) + chr(0x39) + chr(0x32) + chr(0x64) + chr(0x32) + chr(0x65) + '}'

別のターミナルで、以下を実行すればフラグが表示されました。

$ python -c "print('picoCTF{gl17ch_m3_n07_' + chr(0x61) + chr(0x34) + chr(0x33) + chr(0x3
9) + chr(0x32) + chr(0x64) + chr(0x32) + chr(0x65) + '}')"

fixme2.py

Pythonスクリプト(fixme2.py)をダウンロードできます。次の問題が、「fixme1.py」なので、順番を変えてやってみてもいいかもしれません。

fixme2.py問題
fixme2.py問題

Pythonスクリプトを見て、修正すればいいようです。修正すると、Pythonスクリプトを実行できるようになって、フラグが表示されました。

$ python fixme2.py
That is correct! Here's your flag:

fixme1.py

「fixme2.py」と同じ問題形式です。Pythonスクリプト(fixme1.py)をダウンロードできます。

fixme1.py問題
fixme1.py問題

修正できると、Pythonスクリプトを実行できるようになります。修正すると、Pythonスクリプトを実行できるようになって、フラグが表示されました。

$ python fixme1.py
That is correct! Here's your flag:

convertme.py

これもPythonスクリプト(convertme.py)をダウンロードする問題形式です。

convertme.py問題
convertme.py問題

以下のように質問されます。

$ python convertme.py
If 23 is in decimal base, what is it in binary base?
Answer:

以下で、2進数に変換できます。

$ python -c 'print(f"{bin(23)}")'
0b10111

回答するとフラグが表示されました。

Codebook

Pythonスクリプト(code.py)と、テキストファイル(codebook.txt)をダウンロードできます。

Codebook問題
Codebook問題

テキストファイルには、azbycxdwevfugthsirjqkplomn と書かれていました。

Pythonスクリプトを実行すると、フラグが表示されました。

Serpentine

ここから難易度が Medium になります。Pythonスクリプト(serpentine.py)をダウンロードできます。

Serpentine問題
Serpentine問題

Pythonスクリプトを見てみると、フラグを表示する関数(print_flag関数)は、どこからも呼ばれていません。main関数の先頭で、print_flag関数を呼び出すようにすると、フラグが表示れました。

Pythonスクリプトを変更したらダメだったかもしれません。それが許されたら、どの問題も簡単になってしまいそうです。もう一度、Pythonスクリプトを見直してみると、print_flag関数の置き場所を間違えたので、ソースをチェックしてください!というメッセージがありました。ソース変更で良かったようです。

PW Crack 5

次も Medium の問題です。4つのファイル(level5.py、level5.flag.txt.enc、level5.hash.bin、dictionary.txt)をダウンロードできます。

PW Crack 5問題
PW Crack 5問題

Pythonスクリプト(level5.py)は以下です。

import hashlib

### THIS FUNCTION WILL NOT HELP YOU FIND THE FLAG --LT ########################
def str_xor(secret, key):
    #extend key to secret length
    new_key = key
    i = 0
    while len(new_key) < len(secret):
        new_key = new_key + key[i]
        i = (i + 1) % len(key)        
    return "".join([chr(ord(secret_c) ^ ord(new_key_c)) for (secret_c,new_key_c) in zip(secret,new_key)])
###############################################################################

flag_enc = open('level5.flag.txt.enc', 'rb').read()
correct_pw_hash = open('level5.hash.bin', 'rb').read()

def hash_pw(pw_str):
    pw_bytes = bytearray()
    pw_bytes.extend(pw_str.encode())
    m = hashlib.md5()
    m.update(pw_bytes)
    return m.digest()

def level_5_pw_check():
    user_pw = input("Please enter correct password for flag: ")
    user_pw_hash = hash_pw(user_pw)
    
    if( user_pw_hash == correct_pw_hash ):
        print("Welcome back... your flag, user:")
        decryption = str_xor(flag_enc.decode(), user_pw)
        print(decryption)
        return
    print("That password is incorrect")

level_5_pw_check()

実行すると、パスワードを聞かれるので、正しいパスワードを入力するとフラグが表示されそうです。

パスワードの辞書が提供されているので、それを 1つずつ入力すれば、いつかは正解しますが、辞書には、65536個のパスワードが含まれていました。

Pythonスクリプトを繰り返し実行する Pythonスクリプトを実装すれば良さそうです。pwntools を使います。

import os, sys
from pwn import *

def prologue_python( prog, ss ):
    
    # level5.py起動
    proc = process( ['python', prog] )
    
    # "Please enter correct password for flag: "
    print( proc.recv(timeout=1) )
    
    print( ss )
    proc.sendline( ss )
    
    # "That password is incorrect"
    ret = proc.recvline()
    print( ret )
    
    return proc, ret

fpath = "dictionary.txt"
prog  = "level5.py"

with open(fpath) as ff:
    for line in ff:
        print( f"line={line}" )
        proc, ret = prologue_python( prog, line.encode('utf-8') )
        if "Welcome" in ret.decode('utf-8'):
            break
        else:
            proc.close()

print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )

実装して実行しましたが、かなり時間がかかります(2時間ぐらい)。逆順にやった方が絶対早いだろうなと思いましたが、やっぱり、だいぶ後ろの方でヒットしました(笑)。

$ python tmp.py
line=0000

[+] Starting local process '/home/user/20240819/bin/python': pid 209134
b'Please enter correct password for flag: '
b'0000\n'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 209134)
line=0001

[+] Starting local process '/home/user/20240819/bin/python': pid 209136
b'Please enter correct password for flag: '
b'0001\n'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 209136)
line=0002
(以下、省略)

PW Crack 4

次も Medium の問題です。3つのファイル(level4.py、level4.flag.txt.enc、level4.hash.bin)をダウンロードできます。

PW Crack 4問題
PW Crack 4問題

上の「PW Crack 5」と似ていて、ちょっと簡単です。前回と同じく、パスワードを入力して、正しければ、フラグが表示されます。前回は、辞書ファイルに 65536個のパスワードの候補が入っていましたが、こちらは、ダウンロードした「level4.py」の中に、100個のパスワード候補がリストに定義されている状態です。

Pythonスクリプト(level4.py)は以下です。

import hashlib

### THIS FUNCTION WILL NOT HELP YOU FIND THE FLAG --LT ########################
def str_xor(secret, key):
    #extend key to secret length
    new_key = key
    i = 0
    while len(new_key) < len(secret):
        new_key = new_key + key[i]
        i = (i + 1) % len(key)        
    return "".join([chr(ord(secret_c) ^ ord(new_key_c)) for (secret_c,new_key_c) in zip(secret,new_key)])
###############################################################################

flag_enc = open('level4.flag.txt.enc', 'rb').read()
correct_pw_hash = open('level4.hash.bin', 'rb').read()

def hash_pw(pw_str):
    pw_bytes = bytearray()
    pw_bytes.extend(pw_str.encode())
    m = hashlib.md5()
    m.update(pw_bytes)
    return m.digest()

def level_4_pw_check():
    user_pw = input("Please enter correct password for flag: ")
    user_pw_hash = hash_pw(user_pw)
    
    if( user_pw_hash == correct_pw_hash ):
        print("Welcome back... your flag, user:")
        decryption = str_xor(flag_enc.decode(), user_pw)
        print(decryption)
        return
    print("That password is incorrect")

level_4_pw_check()

# The strings below are 100 possibilities for the correct password. 
#   (Only 1 is correct)
pos_pw_list = ["158f", "1655", "d21e", "4966", "ed69", "1010", "dded", "844c", "40ab", "a948", "156c", "ab7f", "4a5f", "e38c", "ba12", "f7fd", "d780", "4f4d", "5ba1", "96c5", "55b9", "8a67", "d32b", "aa7a", "514b", "e4e1", "1230", "cd19", "d6dd", "b01f", "fd2f", "7587", "86c2", "d7b8", "55a2", "b77c", "7ffe", "4420", "e0ee", "d8fb", "d748", "b0fe", "2a37", "a638", "52db", "51b7", "5526", "40ed", "5356", "6ad4", "2ddd", "177d", "84ae", "cf88", "97a3", "17ad", "7124", "eff2", "e373", "c974", "7689", "b8b2", "e899", "d042", "47d9", "cca9", "ab2a", "de77", "4654", "9ecb", "ab6e", "bb8e", "b76b", "d661", "63f8", "7095", "567e", "b837", "2b80", "ad4f", "c514", "ffa4", "fc37", "7254", "b48b", "d38b", "a02b", "ec6c", "eacc", "8b70", "b03e", "1b36", "81ff", "77e4", "dbe6", "59d9", "fd6a", "5653", "8b95", "d0e5"]

上の「PW Crack 5」で実装した Pythonスクリプトを少し変更します。

import os, sys
from pwn import *

def prologue_python( prog, ss ):
    
    # level4.py起動
    proc = process( ['python', prog] )
    
    # "Please enter correct password for flag: "
    print( proc.recv(timeout=1) )
    
    print( ss )
    proc.sendline( ss )
    
    # "That password is incorrect"
    ret = proc.recvline()
    print( ret )
    
    return proc, ret

prog  = "level4.py"
pos_pw_list = ["158f", "1655", "d21e", "4966", "ed69", "1010", "dded", "844c", "40ab", "a948", "156c", "ab7f", "4a5f", "e38c", "ba12", "f7fd", "d780", "4f4d", "5ba1", "96c5", "55b9", "8a67", "d32b", "aa7a", "514b", "e4e1", "1230", "cd19", "d6dd", "b01f", "fd2f", "7587", "86c2", "d7b8", "55a2", "b77c", "7ffe", "4420", "e0ee", "d8fb", "d748", "b0fe", "2a37", "a638", "52db", "51b7", "5526", "40ed", "5356", "6ad4", "2ddd", "177d", "84ae", "cf88", "97a3", "17ad", "7124", "eff2", "e373", "c974", "7689", "b8b2", "e899", "d042", "47d9", "cca9", "ab2a", "de77", "4654", "9ecb", "ab6e", "bb8e", "b76b", "d661", "63f8", "7095", "567e", "b837", "2b80", "ad4f", "c514", "ffa4", "fc37", "7254", "b48b", "d38b", "a02b", "ec6c", "eacc", "8b70", "b03e", "1b36", "81ff", "77e4", "dbe6", "59d9", "fd6a", "5653", "8b95", "d0e5"]

for line in pos_pw_list:
    print( f"line={line}" )
    proc, ret = prologue_python( prog, line.encode('utf-8') )
    if "Welcome" in ret.decode('utf-8'):
        break
    else:
        proc.close()

print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )

実行します。今度は 1分ぐらいでヒットしました。

$ python tmp.py
line=158f
[+] Starting local process '/home/user/20240819/bin/python': pid 209456
b'Please enter correct password for flag: '
b'158f'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 209456)
line=1655
[+] Starting local process '/home/user/20240819/bin/python': pid 209458
b'Please enter correct password for flag: '
b'1655'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 209458)
line=d21e
[+] Starting local process '/home/user/20240819/bin/python': pid 209460
b'Please enter correct password for flag: '
b'd21e'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 209460)
(以下、省略)

PW Crack 3

次も Medium の問題です。3つのファイルをダウンロードできます。

PW Crack 3問題
PW Crack 3問題

内容は、上の「PW Crack 4」と同じ問題で、パスワードの候補がリストに格納されているところも同じで、候補数が 100個から 7個に減っています。

先ほどの Pythonスクリプトを変更します。

import os, sys
from pwn import *

def prologue_python( prog, ss ):
    
    # level4.py起動
    proc = process( ['python', prog] )
    
    # "Please enter correct password for flag: "
    print( proc.recv(timeout=1) )
    
    print( ss )
    proc.sendline( ss )
    
    # "That password is incorrect"
    ret = proc.recvline()
    print( ret )
    
    return proc, ret

prog  = "level3.py"
pos_pw_list = ["8799", "d3ab", "1ea2", "acaf", "2295", "a9de", "6f3d"]

for line in pos_pw_list:
    print( f"line={line}" )
    proc, ret = prologue_python( prog, line.encode('utf-8') )
    if "Welcome" in ret.decode('utf-8'):
        break
    else:
        proc.close()

print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )
print( proc.recvline() )

実行します。今度はすぐにヒットしました。

$ python tmp.py
line=8799
[+] Starting local process '/home/user/20240819/bin/python': pid 210010
b'Please enter correct password for flag: '
b'8799'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 210010)
line=d3ab
[+] Starting local process '/home/user/20240819/bin/python': pid 210012
b'Please enter correct password for flag: '
b'd3ab'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 210012)
line=1ea2
[+] Starting local process '/home/user/20240819/bin/python': pid 210014
b'Please enter correct password for flag: '
b'1ea2'
b'That password is incorrect\n'
[*] Stopped process '/home/user/20240819/bin/python' (pid 210014)
(以降、省略)

以上で、「Beginner picoMini 2022」の全13問を完了しました!

おわりに

今回は、picoCTF に登録して、その中の picoGym で、「Beginner picoMini 2022」を全問(13問)やりました。

picoCTF は、とても良かったです。シンプルで、分かりやすいシステムで、英語ですが、特に困ったりはしませんでした。

「Beginner picoMini 2022」は、CTF をやったことない方にお勧めでしょうか。逆に、CTF をやったことある方は、早く解くことを目標にやってみてはいかがでしょうか。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。