年賀状CTF(お年玉付き)解説
年賀状CTFの解説をしていこうと思います.
Write upはBono(@Bono_iPad)さんが書いてくださっているので,
想定解を問題のソースなどを交えて紹介していきます.
BonoさんのWrite up
年賀状CTF2016 (by @_N4NU_) writeup by Bono-iPad
問題
Stage1
ジャンルとしては Forensic + Reversing ( + Steganography ) ですかね.
年賀状の画像が渡されるところから始まります.
fileコマンドを叩くと
$ file wc16g_tf_0003.png wc16g_tf_0003.png: PNG image data, 1748 x 1181, 8-bit/color RGBA, non-interlaced
binwalkで叩くと
$ binwalk wc16g_tf_0003.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 1748 x 1181, 8-bit/color RGBA, non-interlaced 54 0x36 Zlib compressed data, default compression 2662 0xA66 Zlib compressed data, default compression 3402577 0x33EB51 gzip compressed data, from Unix, last modified: 2015-12-31 14:26:13
何か後ろにくっついてますね.
gzipで展開するとtarであることが分かるのでtarで展開すると,stage1というフォルダが現れました.
$ ls stage1 stage1.pyc stage2.tar.gz.gpg
stage1.pycの元のコードは
#!/usr/bin/env python2 import sys from os.path import getsize, basename import random try: from PIL import Image except ImportError: import Image if len(sys.argv) < 3: sys.stderr.write('Usage: {0:s} png_file message\n'.format(sys.argv[0])) sys.exit(1) im = Image.open(sys.argv[1]) width, height = im.size message = sys.argv[2] for i in xrange(len(message)): pix = list(im.getpixel((i % width, i / width))) pix[random.randint(0, 3)] ^= ord(message[i]) im.putpixel((i % width, i / width), tuple(pix)) im.save(basename(sys.argv[1]).split('.')[0].encode('rot13') + '.png')
pythonのデコンパイラを使うとほぼ同じ内容のコードを復元できると思います.
こいつを少し変えるとsolverになります.
#!/usr/bin/env python2 import sys from os.path import getsize, basename try: from PIL import Image except ImportError: import Image if len(sys.argv) < 3: sys.stderr.write('Usage: {0:s} original_file stegged_file\n'.format(sys.argv[0])) sys.exit(1) im1 = Image.open(sys.argv[1]) im2 = Image.open(sys.argv[2]) width, height = im1.size message = '' i = 0 while not '\x00' in message: pix1 = list(im1.getpixel((i % width, i / width))) pix2 = list(im2.getpixel((i % width, i / width))) message += chr(max([t[0] ^ t[1] for t in zip(pix1, pix2)])) i += 1 print message.strip('\x00')
こいつにネットから探してきた元の画像とwc16g_tf_0003.pngを渡せばflagが出てきます.
Congratz!!! Password: D3c0mpilin6_4nd_R3v3rs1n6_pyc_15_700_345y
Stage2
ジャンルとしては (Javascript) Reversing です.
この問題はStage1〜Stage3の中で最も年賀状CTFらしいネタが詰まっています.
問題のLife GameはMonkey Xという,クロスプラットフォームプログラミング言語を用いて作成しました.
なおLife Game本体はサンプルコード*1から引用しました.
実行すると普通のLife Gameが始まります.
Javascriptで動いているのでコードを見てみると難読化がかけられているので,
気合で人力デコードしたり,ブラウザにデコードさせたり,難読化解除ツールを使ったりしてコードを復元します.
元のコードを解析すると盤面がある条件を満たした状態でキーボードのFを押すとflagが現れることが分かります.
実はこのある条件を再現すると2016という文字が浮かび上がります.
Stage3
ジャンルは Reversing です.
$ file stage3 stage3: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped
Statically LinkedでStrippedという面倒な設定ですが,
その代わり32bitだったり元のコードが1612バイトと小さかったりすることで,
ある程度の難しさに調整しています.
こいつはUPXでパックされていますが,
単純にupx -dではアンパックできないようになっています.
UPXがパックされるとき独自のヘッダーがくっつき,
upx -dではその独自のヘッダーを解釈してアンパックしています.
この独自ヘッダーの一部を書き換えてやると動くのにupx -dでアンパックできないというバイナリが出来ます.
想定解ではメモリ上に展開された状態でダンプを取って解析することを想定していました.
このプログラムの元ソースは次の通りです.
#include <stdio.h> #include <stdlib.h> #include <string.h> unsigned char key_matrix[4][4]; unsigned char flag_matrix[4][4]; unsigned char enc_matrix[4][4] = {0}; unsigned char correct_matrix[4][4] = {{0xd0, 0x3f, 0x3e, 0x08}, {0x5b, 0x37, 0x79, 0x1e}, {0x46, 0xe1, 0x10, 0x50}, {0xb1, 0xe6, 0x73, 0xa7}}; int is_valid_matrix() { for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { if(enc_matrix[i][j] != correct_matrix[i][j]){ return 0; } } } return 1; } void mul_matrix() { for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { for(int k = 0; k < 4; k++) { enc_matrix[i][j] += key_matrix[i][k] * flag_matrix[k][j]; } } } } void gen_key_matrix() { srand(735); for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { key_matrix[i][j] = rand() % 0x100; } } } void flag2matrix(unsigned char *flag) { for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { flag_matrix[i][j] = flag[i * 4 + j]; } } } int is_valid_flag(unsigned char* flag) { if(strlen(flag) != 16) return 0; if(flag[4] != '-' || flag[11] != '-') return 0; flag2matrix(flag); gen_key_matrix(); mul_matrix(); if(is_valid_matrix()) return 1; return 0; } int main(int argc, char* argv[]) { if(argc != 2) { fprintf(stderr, "Usage: %s flag\n", argv[0]); exit(1); } if (is_valid_flag(argv[1])) { puts("Congrats! You got the amazon code."); } else { puts("Invalid. Try again."); } return 0; }
randで生成した行列と入力したアマゾンギフト券のコードを行列として解釈した2行列の積が
correct_matrixになればいいというプログラムです.
想定解ではkey_matrixの逆行列を求めてcorrect_matrixにかけてflagを復元することを想定していました.
まとめ
結構な人に参加していただけたようで主催及び作問者としてはとてもうれしく思っています.
来年の年賀状CTFは2017/1/1 12:00(JST)に開催しようと考えています.
また来年は1位の人だけでなく3位の人ぐらいまでお年玉を出せればいいなと考えています.