Zh3r0 CTFに参加したおはなし

はじめに

ctftime.orgにて見つけた、Zh3r0 CTFとやらに、2日目の昼ぐらいから1人でひっそりと参加しました。 結果は、1384ポイントで、810チーム中175位でした。 もうちょっと頑張りたかったなあというお気持ちです。

いろんなジャンルの問題が出題されていたんですが、ほとんどのジャンルの触りの問題しか解けなかったです。つらい。 以下にCTFdのリッチなスコア画面の一部を載せておきます。 16時前から20時ぐらいまでが一番取り組んでたはずなのに、全然スコアが変わってないですね。悲しい。

f:id:ush1ken:20200618135108p:plain
スコアの遷移

では、備忘録としてwriteupを書いていきます(welcome系の問題は省きます)。

目次

[toc]

Writeups

RSA-Warmup - Crypt (20)

RSA is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and distinct from the decryption key which is kept secret. You all know this :p here is a warmup question.

nc crypto.zh3r0.ml 8451

Author : Finch

Cryptの超簡単な問題らしいです。 手始めに、与えられたサーバーにアクセスすると、次のように、NCTと、よく見たらeが与えられます。

$ nc crypto.zh3r0.ml 8451
N: 308051270710135702996040605286818433793711353550617317701193169151276189067532164511643510276134063421796768864465665703584702749269261428789682425289413761516824552560721023024186455596868904214620356109141770431007778797144062315815972973290199066311100079569526397683776312985291516152625811225886351004501626870823
e 65537
CT: 30709473425159171299520825095261005615552655385449912891827878235048571229401267923174369034256520103525411275610842692530767429124293047575582224576587413577905912072168026583801146478806821956101260895155089710384650128939221689021763048961179401991902924891096688884606683625578665778409730072721585153101434625997

RSA - GrinningChicken CTFによれば、N素因数分解して、pqが得られれば、dを求めることができ、それと、Nを使ってCTを求めることができるようです。


d = e^{-1}\bmod (p-1)(q-1) \\
CT = c^{d}\bmod N

Integer factorization calculatorというサイトで、Nを与えると、pqが得られます。

import binascii
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse
import gmpy2

N = 360603269950717127958760246163951087043653062932088423467851263183841195982469186986247797462136128745502344222370032221671761738315302847959684763944339067125598238100462217330746123763793945305815944226742458211575982476444545607727592701928013154987794678644639056679851880595959438464365438444242105681976170048233
CT = 42437077913617929464480903272150567089748882677114169372376817944866452865821528116587879624328040402591495026434331016168378827908346491973919641330181046283054065817159232012784632830695669632367600387846791108451794862353062701183232865747649340186603902638878987762345311390914593947979228065750978945968847039156
e = 65537

p = 3945777523
q = 91389660934696628601267503891133876035223454470493830593968656246471602190853979119858197200779210973649804589443912299784207001859213248896517869881012819365081632885682140201787074695086387237706393165731067045661201227448054883080034388935316856937702891021471069563860932510045989854548988580733548286771

d = inverse(e, (p-1)*(q-1))

key = RSA.construct((N, e, d))

print(binascii.unhexlify(hex(key.decrypt(CT))[2:]).decode("utf-8"))

というわけで、以上のようなコードで、フラグが得られます。 コードはSECCON Beginners CTF 2018 - RSA is Power - こんとろーるしーこんとろーるぶいを参考にさせていただきました。

$ python dec.py 
zh3r0{RSA_1s_Fun}

Web-Warmup - Web (50)

Chall Link : http://web.zh3r0.ml:8080/ Easy peasy.

Author : careless_finch

とりあえず、curlすると、以下のようなHTMLが得られます。 なんだか構造がすごく気持ち悪いですね。(暴言)

$ curl http://web.zh3r0.ml:8080/
<html>
<title>Welcome Here</title>
     <link rel="stylesheet" type="text/css" href="bg.css">
     <h1> Hey. This is just WARM-UP</h1>
       <input type="hidden" id="one" name="one" value="none"> 
    <img src="warm.jpg" alt="nothing is here " id='selector'>

inputがあるし、そこにPOSTするのかなあ、warm.jpgのステガノかなあ、と思ったりしたのですが、bg.cssを覗くとフラグがありました。

$ curl http://web.zh3r0.ml:8080/bg.css
/*css is easy, I think. Don't you?zh3r0{y3s_th1s_1s_w4rmup}*/
#selector {
    width:100%;
    height:100%;

Tokens - Web (50)

The flag was sent by Mr.4N0NYM4U5 to my victim. But i dont have the username and password of the victim to login into the discord account. The only thing i have is a god damn token. Can you help me to get the flag. Ill give you the token and it is all you need. Token : NzIyMzM1MTQ5NDA0MTkyODIw.XunLaw.xASADEeu9iXsYf1wqTFOil_jgfo

Author : Mr.4N0NYM4U5

discordのAPIを与えられたTokenを使って叩けみたいな問題でした。 discordにはユーザー用のTokenとbot用のTokenがあるようで、最初はdiscordのbot用のSDK的なsomethingを使って、頑張って調査して、時間を溶かしてしまいました。 まあ普通に問題文を読めば、ユーザー用だよねってことがわかるので、ユーザー用のTokenとしてどう使うかを調査しました。

BurpSuiteを立ち上げながら、ブラウザ版のdiscordにログインしたり、適当な操作をやったあと、一通り通信を眺めてみました。 結果的に、AuthorizationヘッダにTokenを含めて/api/v6/以下のエンドポイントを叩いていることがわかりました。

とりあえず、そのへんにあった/api/v6/users/@me/guildsというRequestのTokenを書き換えて投げてみると、フラグが得られました。

f:id:ush1ken:20200618150147p:plain
/api/v6/users/@me/guildsへのRequest/Response

(あとがき)writeupを書きながら検証していると、上記と同じ手順で解けなかったので、おそらく想定解ではなかった?のか、discord内の何かが変わった可能性があります。writeup執筆時点での、うまくいった手順は以下のとおりです。 APIのドキュメントはこちらを参考にしました。

  1. /api/v6/users/@me/channelsにTokenを含めてGETリクエス
  2. レスポンスに見に行くべきchannelのidが含まれている
[
   {
      "id":"722336834264498177",
      "last_message_id":"722774529239285781",
      "last_pin_timestamp":"2020-06-16T20:48:55.080000+00:00",
      "type":1,
      "recipients":[
         {
            "id":"402012560553148426",
            "username":"Mr.4N0NYM4U5",
            "avatar":"c5c89aac100ab60a58dadda34a3797c5",
            "discriminator":"2435",
            "public_flags":0
         }
      ]
   }
]
  1. /api/v6/channels/722336834264498177/messagesにTokenを含めてGETリクエス
  2. レスポンスにフラグらしき文字列が含まれている
[
    ...(中略)
   {
      "id":"722338901221703732",
      "type":0,
      "content":"`flag : zh3r0{1et_7he_F0rce_8e_With_YoU}`",
      "channel_id":"722336834264498177",
      "author":{
         "id":"402012560553148426",
         "username":"Mr.4N0NYM4U5",
         "avatar":"c5c89aac100ab60a58dadda34a3797c5",
         "discriminator":"2435",
         "public_flags":0
      },
      ...(中略)
   }
]

Katycat - Forensics (175)

katycat trying to find the flag but she is lazy. will you help her to find the flag?

Author : cryptonic007

猫の写った、png画像が与えられます。 とりあえず、stringsや、binwalk、exiftoolsをかけてみましたが、めぼしいヒントは得られず。 次に、青空白猫に入れて、ステガノグラフィー解析機能で、次候補ボタンをポチポチ押していました。 すると、ビット0抽出で、左上に、黒い点の列が見えたので、LSBにデータが隠されていることがわかりました。

f:id:ush1ken:20200618152653p:plain
青空白猫解析画面

というわけで、同ソフトウェアのビット抽出機能を使って、RGBそれぞれのLSB(0ビット目)のみ抽出して、データを表示してみると、https://pastebin.com/hvgCXNcPというURLが得られました。

サイトに行くと、Base64エンコードされたような、文字列が保存されていたので、CyberChefに入れて、Base64 Decodeすると、出力されたバイナリの先頭にPK、中間にflag.txtという文字列が存在することがわかりました。 PKはzipファイルのマジックナンバーなので、zipファイルのバイナリデータであることがわかりました。

以下のコマンドで、zipファイルとしてダウンロードします。

$ curl -s https://pastebin.com/raw/hvgCXNcP | base64 -D > out.zip

しかし、解凍しようとすると、パスワードがかかっていることがわかります。

$ unzip out.zip 
Archive:  out.zip
[out.zip] flag.txt password: 

zip2johnを使ってパスワードハッシュを求め、johnでそのハッシュを使って、パスワードをcrackします。

$ zip2john out.zip > test.hash
ver 1.0 efh 5455 efh 7875 out.zip/flag.txt PKZIP Encr: 2b chk, TS_chk, cmplen=37, decmplen=25, crc=BCC1DEEE
$ john test.hash
...(前略)
kitkat           (out.zip/flag.txt)
...(後略)

上記パスワードを使って、unzipしてflag.txtの中を見てみると、暗号化されたような文字列がまた現れます。

$ unzip out.zip 
Archive:  out.zip
[out.zip] flag.txt password: 
 extracting: flag.txt                
ushiken:Katycat user$ cat flag.txt 
K9bC_L`D?f0DEb8c?_06cDJN

一応ASCIIに収まっていることから、ROTかな?という軽い推測をして、CyberChefに再度入力してみると、ROT13ではなく、ROT47でフラグを複合できました。

zh3r0{1sn7_st3g4n0_e4sy}

snakes everywhere - Reversing (357)

Can a snake go backwards

Author : Whit3_D3vi1

py_dis1snake.txtという2つのファイルが与えられます。 py_dis1には、以下のような文字列が入っており、snake.txtには数十バイトのバイナリデータが入っていました。

$ head -20 py_dis1 
  2           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('flag1',))
              4 IMPORT_NAME              0 (flags)
              6 IMPORT_FROM              1 (flag1)
              8 STORE_NAME               1 (flag1)
             10 POP_TOP

  3          12 LOAD_NAME                1 (flag1)
             14 STORE_NAME               2 (flag)

  4          16 LOAD_CONST               2 ('zh3r0{fake flag}')
             18 STORE_NAME               3 (fake_flag)

  6          20 LOAD_CONST               3 ('I_l0v3_r3v3r51ng')
             22 STORE_NAME               4 (key)

  8          24 LOAD_NAME                5 (len)
             26 LOAD_NAME                2 (flag)
             28 CALL_FUNCTION            1
             30 LOAD_CONST               4 (38)
...(後略)

py disでggると、dis --- Python バイトコードの逆アセンブラ — Python 3.8.3 ドキュメントというサイトが出てきました。 pythonのbytecodeを逆アセンブルすることができるらしいです。 早速インストールして使ってみました。

$ pip install dis
$ echo "a=1;print(a)" | python -m dis 
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)
              4 LOAD_NAME                1 (print)
              6 LOAD_NAME                0 (a)
              8 CALL_FUNCTION            1
             10 POP_TOP
             12 LOAD_CONST               1 (None)
             14 RETURN_VALUE

1列目が、入力されたコードの行番号、2列目がバイトコードの位置、3列目が人間が読める形式のオペコード(命令)、4列目は命令が参照する内部変数のインデックス、5列目がオペランド(被演算子)のようです。 内部変数には、co_consts, co_names, co_varnamesなどのタプルがあり、こちらに変数名などを保持しているそうです(参考: inspect --- 活動中のオブジェクトの情報を取得する — Python 3.8.3 ドキュメント")。

このコードを簡単に解説します。 コードでは、最初にLOAD_CONST (1)1がスタックに積まれて、STORE_NAME (a)aにスタックのトップにある1をストアしています(a=1;)。 次に、LOAD_NAME (print)LOAD_NAME (a)をそれぞれスタックに積み、CALL_FUNCTIONでスタックのトップを引数、その次を関数として呼び出します(print(a))。 最後に、プログラムの終了処理を行っています。

disをつかった解析は、writeup執筆中に見つけた、dis/inspect モジュールを使った Python のハッキングというサイトに詳しく日本語で書いてありました。

以上のような形で、逆アセンブル結果から、元のコードを(無理やり)復元できることがわかったので、復元してみた結果が、以下のコードです。 このコードを$ python -m dis rev1.pyとすると、だいたい元の逆アセンブル結果と同じ結果が得られます。

from flags import flag1

flag = flag1
fake_flag = "zh3r0{fake flag}"

key = "I_l0v3_r3v3r51ng"

if not len(flag) == 38: raise AssertionError

def xor(str1, str2):
    return chr(ord(str1) ^ ord(str2))


ciphertext = ''

for i in range(len(flag)//3):
    ciphertext += chr(ord(key[i])*ord(flag[i])-i)

for i in range(len(flag)//3, len(flag)//3*2):
    ciphertext += chr(ord(flag[i])*ord(key[i % len(key)])+i)

for i in range(len(key)//2, len(flag)):
    ciphertext += xor(key[i % 16], flag[i])

file = open('ciphertext.txt', 'w')
print(len(ciphertext))
file.write(ciphertext)
file.close()

このコードから、以下のことがわかります。

  • flags.pyflag1という変数にフラグが格納されている
  • フラグの文字列長は38文字
  • I_l0v3_r3v3r51ngという文字列を鍵として、フラグを暗号化している
  • ciphertext.txtというファイルに暗号化されたフラグが格納されている
  • おそらく、ciphertext.txtsnake.txtは同じもの

以上のことから、このコードからさらに、暗号化されたフラグを復号するコードを書く必要があることがわかります。 ということで、復号するコードが以下のコードです。

from rev1 import xor

FLAG_LEN = 38
key = "I_l0v3_r3v3r51ng"

with open('snake.txt', 'r') as f:
    ciphertext = f.read()

flag = ["A" for _ in range(FLAG_LEN)]

for i in range(FLAG_LEN//3):
    flag[i] = chr(int((ord(ciphertext[i])+i)/ord(key[i])))

for i in range(FLAG_LEN//3, FLAG_LEN//3*2):
    flag[i] = chr(int((ord(ciphertext[i])-i)/ord(key[i % len(key)])))

lev3_offset = FLAG_LEN//3*2

c = 0
for i in range(len(key)//2, FLAG_LEN):
    flag[i] = xor(key[i % 16], ciphertext[c+lev3_offset])
    c += 1

print("".join(flag))

ここでは、次の4つの法則性を使って、復号コードを作成しました。

  1. chr(ord('A')) => 'A'
  2. ord(chr(0x41)) => 0x41
  3. c = a ^ b = b ^ a
  4. a = b ^ c

というわけで、このコードを使って復号すると、フラグが得られます。

$ python dec.py 
54
zh3r0{Python_disass3mbly_is v3ry_E4sy}

Subset of subset of hacking machines challenges

Description: Here's the url ;)

hackit.zh3r0.ml

Author : Mr.Holmes

hackthebox的な問題で、1つのマシンをターゲットにフラグを探す問題になっていて、全部で6つのフラグが隠されていました。 そのうちの3つを見つけたので、それぞれの解法をスコアの低い順に書いていきます。

Flag 5 (50)

手始めに、nmap -sV hackit.zh3r0.mlで、ポートスキャンをしたところ、tcpの22番と99番がopenになっていることが確認されました。 22番はhttpらしきサービスが、99番はOpenSSH 7.6p1が立っているようでした。

まずは、httpから見ていこうと思い、Firefoxhttp://hackit.zh3r0.ml:22/にアクセスすると、このアドレスへの接続は制限されていますと言われてアクセスできなかったので、こちらを参考に以下の手順で設定を追加しました。

  1. about:configをURLバーに打ち込み危険性を承知の上で使用するボタンをクリック
  2. 検索バーにnetwork.security.ports.banned.overrideと打ち込み、文字列にチェックを入れて、+ボタンをクリック
  3. 値を打ち込めるので、22と入力し、チェックボタンをクリック

以上の手順のあと、もう一度http://hackit.zh3r0.ml:22/にアクセスしてみると、z3hr0{shouldve_added_some_filter_here}というフラグが得られました。

Flag 1 (227)

http://hackit.zh3r0.ml:22/に対して、dirbというブルートフォースwebサービス上のディレクトリやファイルを探すコマンドを使ってみた結果、robots.txtが存在することがわかったため、curlでアクセスしてみました。

$ curl http://hackit.zh3r0.ml:22/robots.txt
Hmmm you shouldnt be here.... 2yryYz3sx16kWt72agdFZJqxZ5kqqvocsnU416bULtVs63Cvwmvr6f34Ck8sSHQU1PPnQqD7bqW9q    By the way did you see any ads on this thing?

暗号化されたような文字列を得られたので、CyberChefにて、Baseなんちゃら系で復号していると、Base58で復号できることがわかりました。

I tried to hide this, anyway, check out /clue3349203.txt

復号された文字列によると、/clue3349203.txtに次のヒントが隠されているようだったので、curlにて再びアクセスしてみました。

$ curl http://hackit.zh3r0.ml:22/clue3349203.txt
([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+...(後略)

JSFuckと呼ばれる、javascriptbrainfuck言語のような文字列が与えられたので、JsUnFuckというサイトで、デコードしてみた結果、console.log('Employee ID: 865151c643cbbb7e3bf4fd5dbb71354e')という文字列が得られました。

しかし、これを使える場所がまだ見つかっていなかったので、他を当たることにしました。

その後、99番ポートのSSHに対して、UsernameEnumerationしていましたが、途中のアナウンスで、

:TIME_SAVING_TIP: SSH is out of scope. Bruteforcing SSH on hacking-machine is not required. Allowed? Yes. But while setting passwords I made sure they are not in any publicly available wordlist.

と言われていることに気づきました。

そして、Well-knownポートしかスキャンしていないことに、途中で気づいて、全ポートスキャンをしました(その間にラーメン花月油そばを食べに行ってきました)。 全ポートスキャンをした結果、新たにtcpの324番ポートにvsftpd 3.0.3と4994番ポートにunknownなサービスが立ち上がっていることがわかりました。

とりあえず、ftpの調査をしたところ、以下の通り煽られてしまいました。

$ ftp hackit.zh3r0.ml 324
Connected to hackit.zh3r0.ml.
220 (vsFTPd 3.0.3)
Name (hackit.zh3r0.ml:user): anonymous
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -l
229 Entering Extended Passive Mode (|||51696|)
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp            22 Jun 16 23:45 test.txt
226 Directory send OK.
ftp> get test.txt
local: test.txt remote: test.txt
229 Entering Extended Passive Mode (|||23036|)
150 Opening BINARY mode data connection for test.txt (22 bytes).
100% |****************************************************************************************************************************************|    22       41.71 KiB/s    00:00 ETA
226 Transfer complete.
22 bytes received in 00:00 (0.12 KiB/s)
ftp> ^D
221 Goodbye.
$ cat test.txt 
LOL Nothing here. ;-;

次に、4994番ポートにcurlでアクセスしてみたところ、以下のような出力でフラグが得られました。 しかし、curlコマンドが終了しませんでした。

$ curl http://hackit.zh3r0.ml:4994/
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                     ||Employee Entry||

----------------------------------------------------------
                     Sherlock Holmes Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5}
Heyo, Watcha looking at? Employee ID yoo! : 
Go away kiddo, huh, Kids these days!

Flag 3 (445)

curlコマンドが終了しなかったことから、4994番ポートはhttpではなく、なんらかのプログラムが動いているのではないかと気づき、ncコマンドにて再度アクセスしてみたところ、標準入力で何かを入力することができました。 このメッセージから、Employee IDを入力しろと言われているので、先に得られたidを入力したところ、フラグを得ることができました。

$ nc hackit.zh3r0.ml 4994

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                     ||Employee Entry||

----------------------------------------------------------
                     Sherlock Holmes Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5}
Heyo, Watcha looking at? Employee ID yoo! : 
865151c643cbbb7e3bf4fd5dbb71354e
Hey I know you! You work here!
If you don't like to read, you haven't found the right book. - JK Rowling

 Flag 3: zh3r0{y0ur_b0nu5_i5_p4id}

おわりに

ほとんど、初級の問題しか解けなかったので、もっと精進したい(いつも言ってる気がする)。 いろいろ知らなかった技術とか、忘れてたことを思い出せたので、よかったです。 あと7日間(2020/06/25まで?)はサーバーが動いているらしいので、writeupを読みながら解けなかった問題を解いていきたいです。