はじめに
CTFの布教のために記事を更新していくことにしました(更新度低)
まず、CTFとは何かについて説明しようかなと…。ぺた↓
https://www.nri-secure.co.jp/glossary/ctf
今回は布教なのでユーザが多そうなPythonで解いていきます。
正直、Pythonが一番解きやすいです。ただ、他の言語やそれ以外の知識が問われることも多々あるので
その辺りはあまりこだわらないほうが柔軟に対応できます。
今回のゴールは問題に隠されたFlagを入手することです。では、実際に解いていきましょう。
問題紹介
では今回挑戦する問題がこちら↓
https://play.picoctf.org/practice/challenge/104
問題文は下記の通りになっていました。encがリンクになっていて、そのあとにPythonっぽいコードがありますね。
#Description
#I wonder what this really is... enc
''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])
Pythonとりあえずencファイルをwgetコマンドでダウンロードして中身を見てみましょう。
wget https://mercury.picoctf.net/static/a757282979af14ab5ed74f0ed5e2ca95/enc
cat enc
灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸彤㔲挶戹㍽
Bash文字化けした何かが出てきました。が、これが何を意味するのかが不明なので与えられたコードが何を示すか読んでいきましょう。
一応、ここまでの情報で解けるようになっているので解けそうな方は挑戦してみてください!
※以下でPythonを触ったことがない方のために今回使用する関数について記載しています。これぐらいの認識で問題は解けますが興味がある方は調べてみてください。
連結後文字列 = 区切り文字.join(連結させたい文字列が格納されたリスト)
整数値を対応するUnicode文字に変換
文字を対応するUnicodeコードポイント(整数値)に変換 ※8bit
range(開始値, 終了値, ステップ値) 指定された範囲の数値を生成
では、リスト内包表記なのでfor文から読んでいきます。
これは0からlen(flag)まで2ずつ増加する範囲を生成し、リスト内包表記が2文字ずつ処理するための範囲を提供しています。
つまり[chr((ord(flag[i]) << 8) + ord(flag[i + 1])のiが0から2ずつ増加し、ord(flag[i])はflag[i]のUnicodeコードポイント(0〜255、8ビット)を取得し、ord(flag[i + 1])も同様にflag[i + 1]のUnicodeコードポイントを取得しています。
ここで重要となるのが(ord(flag[i]) << 8)のビットシフトですね。
Pythonでは左シフトをすると自動的に整数のビット幅を拡張し、シフト結果を保持します。
ここにord(flag[i + 1])を下位ビットに結合することにより16ビットの整数として扱われます。
さらにchr関数で整数値を対応するUnicode文字に変換します。
そうして出力された文字列が”灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸彤㔲挶戹㍽”というわけですね。
解法紹介
問題文でエンコードされた文字列をデコードすればFlag(答え)が得られそうですね。
先ほどと逆のこと、つまり、16ビットの整数値を8ビットずつに分けてchr関数で整数値を対応するUnicode文字に変換したらいいわけですから次のようなコードになりますね。
※問題文のような感じのスマートなコードは一番下におまけで記載してます。
def decode_string(encoded):
decoded_chars = []
for char in encoded:
value = ord(char)
decoded_chars.append(chr(value >> 8)) # 上位バイト
decoded_chars.append(chr(value & 0xFF)) # 下位バイト
decoded_string = ''.join(decoded_chars)
return decoded_string.rstrip('\0') # デコードした文字列の末尾のヌル文字を削除
Pythonエンコードした文字を1文字ずつ取り出してord関数で整数値にし、まずは8ビット右シフトします。
これにより下位バイトが消え、上位バイトだけを得ることができます。それをchr関数で対応するUnicode文字に変換し、リストに格納します。同様に、value & 0xFFで先ほどの文字の下位バイトを取得します。これはビット単位のAND演算を用いています。16進数は2進数で言うところの[1111 1111]であるためAND演算をすることで下位8ビットを取得することができます。
これを繰り返し、すべてリストに格納し終えたものを最後にjoin関数で結合すると…
picoCTF{16_bits_inst34d_of_8_d52c6b93}
Bash上記のように出力されたら正解です!
さいごに
いかがだったでしょうか。
セキュリティの学習に適した競技でもあるのでインフラに関心がある方やフルスタックを目指されている方にはおすすめです。
私事ですが丁寧に書いたのでとても疲れました。
次からはもっとさらっと書こうかな…。
★おまけ★
def decode_string(encoded):
return ''.join([chr((ord(encoded[i]) >> 8)) + chr((ord(encoded[i]) & 0xFF)) for i in range(len(encoded))])
Python
コメント