SECCON 2015 オンライン予選 write-up

はじめに

 SECCON 2015 オンライン予選にぼっちチームhoge444として出てた。

競技開催期間は2015年12月05日(土) 15:00:00(JST)から24時間。

実質2問しか解けなかった。

Title Genre Points
Start SECCON CTF Exercises 50
SECCON WARS 2015 Stegano 100
Connect the server Web/Network 100
Last Challenge (Thank you for playing) Exercises 50

Start SECCON CTF

問題文

ex1
Cipher:PXFR}QIVTMSZCNDKUWAGJB{LHYEO
Plain: ABCDEFGHIJKLMNOPQRSTUVWXYZ{}

ex2
Cipher:EV}ZZD{DWZRA}FFDNFGQO
Plain: {HELLOWORLDSECCONCTF}

quiz
Cipher:A}FFDNEVPFSGV}KZPN}GO
Plain: ?????????????????????

解法

置換表がex1で与えられているのでPythonのstring.maketransを使って変換テーブルを作る。

#!/usr/bin/env python2
# -*- conding: utf-8 -*-

import string

cihper = 'A}FFDNEVPFSGV}KZPN}GO'
table = string.maketrans('PXFR}QIVTMSZCNDKUWAGJB{LHYEO', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ{}')

print cihper.translate(table)

trコマンドでも解ける。

$ echo "A}FFDNEVPFSGV}KZPN}GO" | tr PXFR}QIVTMSZCNDKUWAGJB{LHYEO ABCDEFGHIJKLMNOPQRSTUVWXYZ{}
SECCON{HACKTHEPLANET}

FLAG:

SECCON{HACKTHEPLANET}

Connect the server

問題文

login.pwn.seccon.jp:10000

解法

 まずncコマンドで接続を試みる。

f:id:kira000:20151206054213p:plain

WireSharkで通信を監視して適当にFollow TCP StreamするとFLAGっぽいものが見える。

f:id:kira000:20151206054234p:plain

FLAG:

SECCON{Sometimes_what_you_see_is_NOT_what_you_get}

SECCON WARS 2015

問題文

https://youtu.be/8SFsln4VyEk

解法

[追記]

 よくよく考えたらgifをそのままPythonで処理すればよかった。

  • Youtubeからmp4の動画をダウンロードしFree Video to GIF Converterで動画の00:25から01:05を切り取ってhoge.gifに変換(このときフレームレートは2 fpsで幅は600pxぐらいにするとよい)

あとは以下のようなスクリプトQRコードを作成すればいい。

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from PIL import Image

gif = Image.open('hoge.gif')

width, height = gif.size
png = Image.new('RGB', gif.size, 'black')

while True:
    gif.load()
    for pos in ((x, y) for x in range(width) for y in range(height)):
        if gif.getpixel(pos) < 0x80:
            png.putpixel(pos, (0xff, 0xff, 0xff))

    try:
        gif.seek(gif.tell() + 1)
    except:
        break

png.save('flag.png', 'PNG')
  • gifのフレームレートを10 fpsにしたとき生成される画像

f:id:kira000:20151223012840p:plain

[ここまで追記]

 ひどい解き方をした。手順は次の通り。

  • youtubeから、問題の動画をダウンロード

  • Free Video to GIF Converterで動画の00:25から01:05を切り取ってhoge.gifに変換(フレームレートは1fpsにした)

  • convertコマンドで分離

$ convert +adjoin hoge.gif fuga.gif
  • Gimpで一枚一枚白黒の画像に加工(全部で42枚)。(画像>モード>グレースケールを選択し、色>しきい値からしきい値を127に設定)

f:id:kira000:20151206054353p:plain

(最初まっ黒な画像を作って読み込んだgifに白いところがあったら白を描画してる)

wars_solver.py · GitHub

※このコードは汚いだけでなくて実行時間もかなりかかるので色々とひどい。

f:id:kira000:20151206054433p:plain

  • 最後にQRコードを読み取るとFLAGが分かる。

FLAG:

SECCON{TH3F0RC3AVVAK3N53P7}

Last Challenge (Thank you for playing)

問題文

ex1
Cipher:PXFR}QIVTMSZCNDKUWAGJB{LHYEO
Plain: ABCDEFGHIJKLMNOPQRSTUVWXYZ{}

ex2
Cipher:EV}ZZD{DWZRA}FFDNFGQO
Plain: {HELLOWORLDSECCONCTF}

quiz
Cipher:A}FFDNEA}}HDJN}LGH}PWO
Plain: ??????????????????????

解法

 最初の問題と同じ。

$ echo "A}FFDNEA}}HDJN}LGH}PWO" | tr PXFR}QIVTMSZCNDKUWAGJB{LHYEO ABCDEFGHIJKLMNOPQRSTUVWXYZ{}
SECCON{SEEYOUNEXTYEAR}

FLAG:

SECCON{SEEYOUNEXTYEAR}

おわりに

 問題は全然解けなかったけど、時間はたくさん溶けた。とても辛かった。

x86アセンブリ言語に関するメモ

x86系マイクロプロセッサの持つ主なレジスタ

レジスタ 呼び名 主な機能
EAX アキュムレータ 算術演算の結果を格納
EBX ベースレジスタ メモリアドレスを格納
ECX カウントレジスタ ループ回数をカウント
EDX データレジスタ データを格納
ESI ソースインデックス データ転送元のメモリアドレスを格納
EDI ディスティネーションインデックス データ転送先のメモリアドレスを格納
EBP ベースポインタ データの格納領域のメモリアドレスを格納
ESP スタックポインタ スタック領域のメモリアドレスを格納

主な命令

命令 使用例 意味    詳細
MOV MOV EAX,ECX EAX = ECX ECXの値をEAXに格納
MOVZX MOVZX_EAX,ECX EAX = ECX MOVのサイズが違うレジスタにコピーするとき用いる版
ADD ADD EAX,ECX EAX += ECX EAXにECXを加算
SUB SUB EAX,ECX EAX -= ECX EAXからECXを減算
MUL MUL EAX,ECX EAX *= ECX EAXにECXを乗算
DIV DIV EAX,ECX EAX =EAX / ECX EAXをECXで割って,商をEAXに,余りをEDXに格納
EDX = EAX % ECX
INC INC EAX EAX++ EAXに1を加算
DEC DEC EAX EAX-- EAXに1を減算
XOR XOR EAX,ECX EAX = EAX ^ ECX EAXとECXの各ビットごとに排他的論理和を取り,結果をEAXに格納
LEA LEA EAX,[ECX+4] EAX = ECX + 4 ECX+4(アドレス値)をEAXに格納
CMP CMP EAX,ECX if(EAX==ECX)_ZF=1 EAXとECXの値を比較してフラグに反映
else ZF = 0 EAXとECXが等しければZF=1,EAXとECXが等しくなければ、ZF=0
TEST TEST EAX,EAX if(EAX==0) ZF = 1 EAXの値を0と比較してフラグに反映
else ZF = 0 EAXが0と等しければZF=1,EAXが0でなければZF=0
JE(JZ) JE 041001000 if(ZF == 1) ZFが1なら041001000にジャンプ
GOTO 041001000
JNE(JNZ) JNE 041001000 if(ZF == 0) ZFが0なら041001000にジャンプ
GOTO 041001000
JMP JMP 041001000 GOTO 041001000 無条件で041001000にジャンプ
CALL CALL lstrcmpW lstrcmpW() lstrcmpw関数の呼び出し
PUSH PUSH 00000001 スタックへ00000001を格納
POP POP EAX スタックからEAXへ値を取得
NOP NOP 何もしない
SAR SAR EAX EAXを右にシフトする,すなわち2で割る
RET RET 処理を呼び出し元に戻す

豆知識

  • XOR EAX,EAX は MOV EAX,0 と同様の働きをする。

  • dword ptr は、指定されたメモリアドレスから4バイトのデータを読み出すことを示す。

例えば、

MOV DWORD PTR SS:[EBP-8],1

の場合ベースポインタのアドレス-8から4バイトまでのデータに整数値1を格納している。

Pythonで16進文字列をintに変換

16進文字列からint

  • 基数を指定してintにパースすればいい。
>>> s = 'abcdef'
>>> print int(s, 16)
11259375
  • ちなみにインタラクティブシェルなら0xからはじめた16進文字列を入力すれば10進数にしたものを返してくれる。
>>> 0xdeadbeef
3735928559

intから16進文字列

  • hex()を使う
>>> a = 111223
>>> print hex(a)
0x1b277
  • 先頭に0xがつくのがいやならformatメソッドを使うとよい
>>> a = 111223
>>> print format(a, 'x')
1b277
  • こうなふうに先頭2文字を削除するという手もある
>>> a = 111223
>>> print hex(a)[2:]
1b277
  • フォーマット指定子(%記法)を使ってもいい
>>> a = 111223
>>> print '%x' % a
1b277

簡単なタイピングゲーム

はじめに

 C言語で簡単なタイピングゲームを作ってみた。
 Visual C++でビルドすることを前提としてる。

コード

/*typing_cui1.c*/
#include <time.h>
#include <conio.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
 
#define Qnumber 20
 
int main(){
 
    int i, stage, miss = 0, alpsum = 0;
    char *japanese[Qnumber] = { 
        "交わした約束忘れないよ",
        "目を閉じ確かめる",
        "押し寄せた闇",
        "振り払って進むよ",
        "いつになったらなくした未来を",
        "私ここでまた見ることできるの?",
        "溢れ出した不安の影を",
        "何度でも裂いて",
        "この世界歩んでこう",
        "とめどなく刻まれた",
        "時は今始まり告げ",
        "変わらない思いをのせ",
        "閉ざされた扉開けよう",
        "目覚めた心は走り出した",
        "未来を描くため",
        "難しい道で立ち止まっても",
        "空はきれいな青さで",
        "いつも待っててくれる",
        "だから怖くない",
        "もう何があっても挫けない"
    };
    char *roman[Qnumber] = {
        "kawasitayakusokuwasurenaiyo",
        "mewotojitasikameru",
        "osiyosetayami",
        "huriharattesusumuyo",
        "ituninattaranakusitamiraiwo",
        "watasikokodematamirukotodekiruno",
        "ahuredasitahuannnokagewo",
        "nandodemosaite",
        "konosekaiayundekou",
        "tomedonakukizamareta",
        "tokihaimahajimarituge",
        "kawaranaiomoiwonose",
        "tozasaretatobiraakeyou",
        "mezametakokorohahasiridasita",
        "miraiwoegakutame",
        "muzukasiimitidetatidomattemo",
        "sorahakireinaaosade",
        "itumomattetekureru",
        "dakarakowakunai",
        "mounanigaattemokujikenai"
    };
    double time;
    clock_t start, end;
 
    for (i = 0; i < Qnumber; i++)
        alpsum += strlen(roman[i]);
 
    printf("スペースキーで開始です。\n");
    while (_getch() != ' ')
        ;
 
    start = clock();
 
    for (stage = 0; stage < Qnumber; stage++){
 
        printf("%s\n", japanese[stage]);
        fflush(stdout);
 
        int len = strlen(roman[stage]);
        for (i = 0; i < len; i++){
            int ch;
            do{
                ch = _getch();
                if (isprint(ch)){
                    _putch(ch);
                    if (ch != roman[stage][i]){
                        miss++;
                        _putch('\b');
                    }
                }
            } while (ch != roman[stage][i]);
        }
        printf("\n");
    }
 
    end = clock();
 
    time = (end - start) / CLOCKS_PER_SEC;
 
    printf("\n%.2ftypes/sec\n%.1ftypes/min\nmiss types:%d\n", alpsum / time, alpsum / time * 60,miss);
 
    return 0;
}

入力されるアルファベットも指定しているのでかなり手抜き。
_getch()や_putch()など標準Cでは定義されてない関数を使っているので例えばgccコンパイルしようとするとエラーになる。

参考

新版 明解C言語 中級編

新版 明解C言語 中級編