2022年3月に開催されたpicoCTF 2022に参加しました。
昨年は個人で出場していましたが、今年はTeam TaruTaruとして出場しています。TaruTaruは9200点で595位でした。もっと精進せねば...
自分が解いた問題についてWrite Upを記しました。チームメイトと結構重複はしています。
実際に使用したコードはGitHubに置いてあります。可読性は皆無なので、気になるところがあったら気軽に尋ねていただけると幸いです。
Binary Exploitation
buffer overflow 0 (100pts)
配布されたvuln.c
には
int main(int argc, char **argv){
//略
signal(SIGSEGV, sigsegv_handler);
//略
}
という関数が存在し、sigsegv_handler
の内容は
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
となっています。
したがって、実行中にSegmentation Faultを発生させればsigsegv_handler
が実行されflagが出力されるようです。
main
関数内で実行されるvuln
関数の中身は
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
とinputが16文字未満であることを想定していますが、このinputはmain
関数内で
int main(int argc, char **argv){
//略
char buf1[100];
gets(buf1);
vuln(buf1);
//略
}
の形で最大100文字(Null文字含む)まで渡すことが可能です。
したがって、プログラムに大量の文字を送りつければSegmentation Faultが発生してflagが表示されます。
CVE-XXXX-XXXX (100pts)
「Windows Print Spooler Service CVE」でGoogle検索をすると、IPAのウェブサイトが最初にヒットします。
CVE番号はCVE-2021-34527です。
この問題のジャンルはBinary Explotionであってますか???
RPS (200pts)
サーバーとじゃんけんをして、5連勝したらフラグが入手できるようです。勝敗の判定は
char* loses[3] = {"paper", "scissors", "rock"};
//中略
if (strstr(player_turn, loses[computer_turn])) {
puts("You win! Play again?");
return true;
} else {
puts("Seems like you didn't win this time. Play again?");
return false;
}
で実装されています。
palyer_turn
はユーザー入力の文字列で、computer_turn
は0~2の乱数です。
strstr(const char *s1, const char *s2)
は文字列s1が文字列s2を 含む ときその開始位置を返すので、入力としてrockscissorspaper
を入力すると相手の手がなんであったとしても勝利として判定されます。
これを5回繰り返せばフラグが表示されます。
Cryptography
basic-mod1 (100pts)
message.txt
に記載された数のmod37を計算し、その値が0-25であればA-Zに、26-35であれば0-9に、36ならば_に置き換えればいいようです。
スクリプトを書いて実行します。
print("picoCTF{", end="")
with open("message.txt", "r") as fp:
for n in fp.read().strip().split(" "):
n = int(n)
n = n%37
if n==36:
print("_", end="")
elif n>=26:
print(n-26, end="")
else:
print(chr(ord('a')+n), end="")
print("}")
basic-mod2 (100pts)
message.txt
に記載された数のmod41での逆元を計算し、その値が1-26であればA-Zに、27-36であれば0-9に、37であれば_に置き換えます。
x*y \equiv 1\ (mod\ n)
が成立するとき、yをxの(mod nにおける)逆元と呼びます。逆元を効率的に計算するアルゴリズムは存在しますが、mod 41なら全探索で計算してしまえばOKです。
def get_inv(i):
for j in range(1, 41):
if (i*j)%41==1:
return j
return 999
print("picoCTF{", end="")
with open("message.txt", "r") as fp:
for n in fp.read().strip().split(" "):
n = int(n)
n = get_inv(n)
if n==37:
print("_", end="")
elif n>=27:
print(n-27, end="")
else:
print(chr(ord('a')+n-1), end="")
print("}")
rail-fence (100pts)
CyberChefでデコードできます。
substitution0 (100pts)
単一換字式暗号です。
文章の最後にある
Pmj tuec xg: fxslSPT{...}
は明らかにThe flag is: picoCTF{...}
を置き換えたものでしょう。
フラグは大文字で構成されていますが、それ以外の文章は小文字ばかりであるので、大文字と小文字で置換が異なる場合は大文字の置換先の特定が不可能そうに見えるので、多分大文字と小文字の置換先は同じである...気がします。というかそうであってほしいです。
あとはそれっぽい単語を見つけて1文字1文字置換先を特定していくだけです。
substitution1 (100pts)
同じく単一換字式暗号です。
eqs coxa dj: zdifIEC{...}
はthe flag is: picoCTF{...}
を置き換えたものでしょう。 残りは気合で置き換えていきます。
substitution2 (100pts)
暗号文を見た瞬間にブチギレそうになりますが、ぐっとこらえてコーヒーを一杯飲みます。これまでの流れからして単一換字式暗号でしょうし、gvjnxqceupemzMGN{...}
はtheflagispicoCTF{...}
を置換したものなのでしょう。
あとは気合です。気合なのです。
頻度解析なんてのは知らない子なのです。
Vigenere (100pts)
CyberChefでデコードできました。
diffie-hellman (200pts)
AliceとBobは、ディフィーヘルマン鍵交換で秘密の値を共有し、その値を用いてCaesar暗号で平文を暗号化したそうです。ディフィーヘルマン鍵交換に使われた値が軒並み小さいので、簡単に復号ができそうですが、そんな小難しいことを考えなくても最後がCaesar暗号ですので力業で複合すればOKです。
平文として意味を持ちそうだったのがC4354R_C1PH3R_15_4_817_0U7D473D_84AA1DA8
であったので、これをpicoCTF{}
でwrapすればフラグになりました。
Forensics
Enhance! (100pts)
配布されたsvgファイルをテキストエディタで開くと
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:0.00352781px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332;"
x="107.43014"
y="132.08501"
id="text3723"><tspan
sodipodi:role="line"
x="107.43014"
y="132.08501"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3748">p </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.08942"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3754">i </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.09383"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3756">c </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.09824"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3758">o </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.10265"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3760">C </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.10706"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3762">T </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.11147"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3764">F { 3 n h 4 n </tspan><tspan
sodipodi:role="line"
x="107.43014"
y="132.11588"
style="font-size:0.00352781px;line-height:1.25;fill:#ffffff;stroke-width:0.26458332;"
id="tspan3752">c 3 d _ a a b 7 2 9 d d }</tspan></text>
flagが記入されているのが見えます。
File types (100pts)
配布されたファイルの拡張子は.pdf
ですが、file
コマンドでファイルタイプを確認すると
% file Flag.pdf
Flag.pdf: shell archive text
と表示されます。これは、
% sh Flag.pdf
とすることで解凍できます。
このように、file
コマンドでファイルタイプを調べる→対応したアプリケーションで解凍する を繰り返すと最終的に
7069636f4354467b66316c656e406d335f6d406e3170756c407431306e5f
6630725f3062326375723137795f33633739633562617d0a
という中身のテキストファイルが得られます。どうやらHexのようなので、CyberChefでデコードしたらフラグが表示されました。
Lookey here (100pts)
% cat anthem.flag.txt | grep pico
we think that the men of picoCTF{gr3p_15_@w3s0m3_4c479940}
Packets Primer (100pts)
配布されたpcapファイルをWiresharkで読み込むとフラグが表示されました。
Redaction gone wrong (100pts)
書類の一部が黒塗りにされていますが、ドラッグしてコピーしたあとテキストエディタにペーストすれば読むことができます。
Sleuthkit Intro (100pts)
mmlsを実行すると
% mmls disk.img
DOS Partition Table
Offset Sector: 0
Units are in 512-byte sectors
Slot Start End Length Description
000: Meta 0000000000 0000000000 0000000001 Primary Table (#0)
001: ------- 0000000000 0000002047 0000002048 Unallocated
002: 000:000 0000002048 0000204799 0000202752 Linux (0x83)
となります。
サーバーにアクセスすると What is the size of the Linux partition in the given disk image?
と尋ねられるので、202752を送信するとフラグが入手できます。
Eavesdrop (300pts)
工事中...
SideChannel (400pts)
pin_checker
というバイナリが配布され、実行すると "Please enter your 8-digit PIN code:"と尋ねられます。 試しに"00000000"を入力したところ、"Access denied."と表示されました。おそらく適切な8桁のPINコードを入力するとAccessに成功するのでしょう。
問題タイトルが"SideChannel"であり、ヒントには"Read about timing-based side-channel attacks."と書かれていることから、PINコード(の一部?)が正解と一致しているか否かで実行時間に変化が出るのだと思われます。思われるのですが、正解のPINコードは8桁の数字と考えられるので、候補は00000000から99999999のわずか$10^8$通りしかありません。もう全部試してしまいましょう。
まず、stringsでバイナリを調べると、"Access granted. You may use your PIN to log into the master server."というメッセージが用意されていることがわかります。正しいPINコードを入力すると、このメッセージが表示されるのでしょう。
あとは適当なシェルを組んで、正解メッセージが出てくるまで全探索を行います。
% seq 0 99999999 | xargs -t -P1024 -r -I @ sh -c 'echo @ && printf "%08d\n" "@" | ./pin_checker ' > log.txt
このシェルスクリプトは、「"0から99999999までの数を表示したあと、(8桁に0埋めして)pin_checker
に渡す" 処理を1024並列で行う」スクリプトです。32コア64スレッドのAMD Ryzen ThreadRipper 3970Xで並列実行をぶん回したところ、4日ほどあれば終わりそうな雰囲気がしていました。実際には2日経過した時点で上記の正解メッセージが表示されました。
注意点として、上記のスクリプトはlog.txt
への書き込みの際に排他制御のようなものを一切行っていないので、正しいPINコードがファイルに書き込まれてから正解メッセージが書き込まれるまでの間に他のスレッドが大量の書き込みを行っています。とはいえ、正解メッセージの近くに正解のPINコードも書き込まれていると考えて問題は無いでしょう。
% cat log.txt | grep -3 granted
Please enter your 8-digit PIN code:
8
Checking PIN...
Access granted. You may use your PIN to log into the master server.
48390594
Please enter your 8-digit PIN code:
8
どうやら、正しいPINコードは48390594の周辺であるようです。改めて、並列処理をせずにこの付近を探索します。
% seq 48389600 48390600 | xargs -t -r -I@ sh -c 'echo @ && printf "%08d\n" "@" | ./pin_checker ' > log2.txt
% cat log2.txt | grep -5 granted
Access denied.
48390513
Please enter your 8-digit PIN code:
8
Checking PIN...
Access granted. You may use your PIN to log into the master server.
48390514
Please enter your 8-digit PIN code:
8
Checking PIN...
Access denied.
正しいPINコードは48390513でした。あとはこれをサーバーに送ればフラグが入手できます。
去年もこんなことばかりしてた気がします。真面目にやれ。
Revers Engineering
file-run1 (100pts)
% strings run | grep pico
picoCTF{U51N6_Y0Ur_F1r57_F113_9bc52b6b}
file-run2 (100pts)
% strings run | grep pico
picoCTF{F1r57_4rgum3n7_be0714da}
実行なんて必要なかったんだ...
GDB Test Drive (100pts)
配布されたbinaryをダウンロード後、問題文に記されたとおりにコマンドを実行するとフラグが表示されました。
patchme.py (100pts)
配布されたpythonファイルを実行するとPlease enter correct password fo flag:
と尋ねられます。ファイルの中身を見ると
if( user_pw == "ak98" + \
"-=90" + \
"adfjhgj321" + \
"sleuth9000"):
という表記があるので、pwとしてak98-=90adfjhgj321sleuth9000
を入力するとフラグが入手できます。
Safe Opener (100pts)
配布されたJavaファイルには
String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";
と書かれており、またSafeOpenerクラス内には
Base64.Encoder encoder = Base64.getEncoder();
という表記も見えます。どうやらb64 encodeした入力とencodedkey
を比較しているようなので、これをdecodeしたところpl3as3_l3t_m3_1nt0_th3_saf3
となりました。これをpicoCTF{}
で囲ったものがフラグです。
unpackme.py (100pts)
unpackme.py
のplain = f.decrypt(payload)
とexec(plain.decode())
の間にprint(plain)
と挿入して、何がexec()
で実行されているのかを確かめます。
実行したところ、plain
の中にフラグが含まれていました。
bloat.py (100pts)
配布されたpythonファイルを実行すると
Please enter correct password for flag:
と言われます。
中身は微妙な難読化が施されていてイライラしますが、どうやら
if arg432 == a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]:
の部分でパスワードの判定をしているようです。
なので、適当な位置にprint(a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68])
と差し込んで中身を表示させると、この値がhappychance
であることがわかります。
あとは改めてこの値を入力することでフラグが表示されます。
Fresh Java (200pts)
Javaのデコンパイラを用いて配布されたバイナリをリバースします。私はDockerを用いて
% docker run -it --rm -v $PWD:/mnt kwart/jd-cli /mnt/KeygenMe.class > decode.java
としてリバース結果を取得しました。
中身を見ると、
//略
if (str.charAt(2) != 'c') {
System.out.println("Invalid key");
return;
}
if (str.charAt(1) != 'i') {
System.out.println("Invalid key");
return;
}
if (str.charAt(0) != 'p') {
System.out.println("Invalid key");
return;
}
とフラグを1文字ずつ正解と比較しているので、これを繋ぎ合わせればフラグが入手できます。
Bbbbloat (300pts)
与えられたBinaryを実行するとWhat's my favorite number?
と尋ねられ、適当な数字を入れるとSorry, that's not it!
と言われました。
GhidraでBinaryをデコンパイルしたところ
printf("What\'s my favorite number? ");
__isoc99_scanf();
if (local_48 == 0x86187) {
__s = (char *)FUN_00101249(0,&local_38);
fputs(__s,stdout);
putchar(10);
free(__s);
}
else {
puts("Sorry, that\'s not it!");
}
という結果が見えたので、549255(=0x86187)を入力したところフラグが表示されました。
Web Exploitation
Includes (100pts)
ウェブページのstyle.css
にフラグの前半が、script.js
に後半が書かれていました。
Inspect HTML (100pts)
ウェブページのHTMLファイルにフラグがコメントで記載されています。
Local Authority (100pts)
ログインフォームが表示されるので、適当なidとpwでログインするとlogin.php
に飛ばされました。このページではsecure.js
というファイルを読み込んでおり、その中身は
function checkPassword(username, password)
{
if( username === 'admin' && password === 'strongPassword098765' )
{
return true;
}
else
{
return false;
}
}
という激ヤバ実装なので、このidとpwで改めてログインしたところフラグが表示されました。
Search source (100pts)
本質的にはIncludesと変わりませんが、文量が多いので目で探すのは辛いです。
まず、
% wget -r http://saturn.picoctf.net:58133/
としてウェブページを手元に保存します。-r
オプションを付けることで、jsやcssなどのリンク先も同時に保存することができます。
つづいて、
% grep -rl pico
./saturn.picoctf.net:58133/css/style.css
としてpico
という文字列が含まれているファイルを探します。どうやら./saturn.picoctf.net:58133/css/style.css
にフラグが含まれていそうです。
最後に
% cat ./saturn.picoctf.net:58133/css/style.css | grep pico
/** banner_main picoCTF{1nsp3ti0n_0f_w3bpag3s_587d12b8} **/
としてフラグを取得します。
Forbidden Paths (200pts)
Filenameとして../../../../flag.txt
を入力するとフラグが表示されます。
Power Cookie (200pts)
isAdmin: 0
というCookieがセットされているので、これを1
に書き換えたところフラグが表示されました。
Cookieの書き換えにはEditThisCookieというChrome拡張が便利です。
Secrets (200pts)
問題文にWe have several pages hidden.
とあるので、おそらくリンクが貼られていないページが存在するのでしょう。dirbを用いてbrute forceを実行します。
% dirb http://saturn.picoctf.net:61481/
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Sun Mar 20 07:40:05 2022
URL_BASE: http://saturn.picoctf.net:61481/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://saturn.picoctf.net:61481/ ----
+ http://saturn.picoctf.net:61481/index.html (CODE:200|SIZE:1023)
==> DIRECTORY: http://saturn.picoctf.net:61481/secret/
---- Entering directory: http://saturn.picoctf.net:61481/secret/ ----
==> DIRECTORY: http://saturn.picoctf.net:61481/secret/assets/
==> DIRECTORY: http://saturn.picoctf.net:61481/secret/hidden/
+ http://saturn.picoctf.net:61481/secret/index.html (CODE:200|SIZE:468)
---- Entering directory: http://saturn.picoctf.net:61481/secret/assets/ ----
---- Entering directory: http://saturn.picoctf.net:61481/secret/hidden/ ----
+ http://saturn.picoctf.net:61481/secret/hidden/index.html (CODE:200|SIZE:2118)
-----------------
END_TIME: Sun Mar 20 08:39:18 2022
DOWNLOADED: 18448 - FOUND: 3
dribはhttp://saturn.picoctf.net:61481/secret/hidden/index.html
というページを発見しました。なんかそれっぽいのでアクセスしてみます。
すると、http://saturn.picoctf.net:61481/secret/hidden/superhidden/login.css
というリソースをロードしていることがわかります。
そこでhttp://saturn.picoctf.net:61481/secret/hidden/superhidden/
にアクセスしたところ、ページ内に背景と同じ色でフラグが記載されているのを発見しました。
SQL Direct (200pts)
指定されたSQLサーバーに接続し、テーブル一覧を調べ、テーブルの中身を表示します。
pico=# \dt
List of relations
Schema | Name | Type | Owner
--------+-------+-------+----------
public | flags | table | postgres
(1 row)
pico=# select * from flags;
id | firstname | lastname | address
----+-----------+-----------+----------------------------------------
1 | Luke | Skywalker | picoCTF{L3arN_S0m3_5qL_t0d4Y_31fd14c0}
2 | Leia | Organa | Alderaan
3 | Han | Solo | Corellia
(3 rows)
SQLiLite (300pts)
Usernameを' or 1=1 ;--
として、pwに適当な値を設定するとログインができます。あとは、開発者ツールでページを見るとフラグが記載されていました。