SECCON 2018 Online CTF Needle in a haystack Writeup

https://score-quals.seccon.jp/scoreboard

チームyharimaとして3年目のSECCON 2018 Online CTF参加。
2535ポイントで28位。国内圏内っぽいので最終結果を待ちます。通ってるといいなぁ。
毎年参加していますが難易度がどんどん上がってきているなぁという感想ですｗ

自分が解いたNeedle in a haystackのWriteupを書きます。
そう**あの**Needle in a haystackです。

問題はこちら。まったく知らない人はまず見てみましょう。
https://www.youtube.com/watch?v=sTKP2btHSBQ

はい、9時間越えの動画をみてフラグを探す問題です。
ビルの風景が夜通し、そして朝まで永遠流れている動画。~~正気じゃない~~
のちにこの風景は大阪の梅田周辺らしいという情報も得られました。はい。

「どうせSECCONだしどっかのフレームにQRコードが埋め込まれてるんじゃない？」とフレームごとの差分が一番大きいところを抽出したものの何も出ず。

何も結果が得られなかったときにメンバーが「右下のビルの窓がかなり怪しいですね」と気づく。
たしかに電気が消えたり、昼間はカーテンが開いたり閉まったりしている部屋がある。。。
調べてみると1分ごとに変化があるみたい。というわけで、1分ごとの動画のフレームを切り出して
そこがONなのかOFFなのかを判断してデータにするということをしてみました。

```python
# -*- coding: utf-8 -*-
import cv2
import numpy as np

def main(): 
    cap = cv2.VideoCapture('Needle in a haystack.mp4')
    
    frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
    fps = cap.get(cv2.CAP_PROP_FPS)
    print(f'frame {frame_count}, fps: {fps}')
    c = int(fps * 30)
    step = int(fps * 60)
    
    while(cap.isOpened()):
        cap.set(cv2.CAP_PROP_POS_FRAMES, c)
        try:
            frame = cv2.cvtColor(cap.read()[1], cv2.COLOR_RGB2GRAY)
        except:
            break

        # 線形濃度変換
        gamma = 2.0
        # 画素値の最大値
        imax = frame.max() 
        # ガンマ補正
        frame = imax * (frame / imax)**(1/gamma)

        # print(frame.shape) # 720, 1280
        trim = frame[586:592, 1142:1148]
        # cv2.imwrite(f"history/{c}.png", trim)
        
        sum = np.sum(trim)

        if c <= 753030:
            if sum > 6000:
                print('1', end='')
            else:
                print('0', end='')
        else:
            if sum < 4000:
                print('1', end='')
            else:
                print('0', end='')
        c += step

    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
```

やってることはフレームをグレースケールに変換、点滅している部分をトリミングし、
そこがどれだけ明るいか、暗いかをピクセルの値をすべて合計して閾値計算する、という内容。
単に1分ごとに切り出してしまうと光が消える瞬間とかカーテンが閉まる間際をキャプチャしてしまう
可能性もあったので、切り出し位置を30秒加算しています。
さらに夜中から朝にかけて明暗の閾値が変わるので、日中になる部分のフレーム位置を計算し、
日中の場合は閾値判定を変えるようにしました。

出力はこんな感じ。結構いい感じにとれる。0が3つ連続しているところが文字の切れ目。
そしてこのデータはどうやらモールス信号らしい。

```
frame 981074.0, fps: 30.0
010101000100011101011101000111010111010001110111011100011101000
111010111011101000101010001110111011100011101110001000111000101
000111011100010001010100011101010101011100010111000111010101010
111000101010001000111010111010001011101000100011100011101010101
011100011101110001000101010001010100010111000111011101000100011
101010101011100011101010100010111010001110111011100010111000111
010100011101011101000001000000000000000000010101000111010101010
111000111010101000111011101110001011101010001110101000101110101
00011101011101110001110101110111010111000
```

実際にモールスに当てはめる。
プログラムにするのがめんどくさかったのでここは手作業。

```
10101       => ...  => S
1           => .    => E
11101011101 => -.-. => C
11101011101 => -.-. => C
11101110111 => ---  => O
11101       => -.   => N
```

よさげ。途中、0が連続していてうまく読み取れていないけど、フラグの前後の文字列から内容を推測できたので解析してない。
Flagはこちら。

```
SECCON(SOMETIMES-A-SECRET-MESSAGE-BROADCASTS-BOLDLY)
```

まさか動画の中でモールス信号を送っているとはまったく思ってもいなかったので、作成者の方お疲れさまでした。
お体ご自愛くださいｗ

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/SECCON?src=hash&amp;ref_src=twsrc%5Etfw">#SECCON</a> お疲れ様でした。2016年のSECCON TOWERに次ぐ最新作、Needle in a haystackはいかがでしたでしょうか？問題考案は <a href="https://twitter.com/kikuchan98?ref_src=twsrc%5Etfw">@kikuchan98</a> 、映像はゲヒルンの大阪お天気カメラからの提供でした！ <a href="https://t.co/udfJZeTJ9X">pic.twitter.com/udfJZeTJ9X</a></p>&mdash; けーいち (@9SQ) <a href="https://twitter.com/9SQ/status/1056439457535025152?ref_src=twsrc%5Etfw">2018年10月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>