ksnctfにチャレンジ 第22問目です。
※ksnctfは常駐型のCTFサイトです。 ※問題のページはコチラです。
Square Cipher
発想重視の問題です。 タイトルの スクエア などから紆余曲折して
大文字部分を黒く塗ると QRコードになることに気がつくことが まず第一段階です。
次に このQRコードを読み取り可能な状態にする必要があります。 適当な言語で 小文字 -> 0 大文字-> 1 とでも変換したテーブルを用意することは 簡単ですが、この後 テーブルを QRコードの仕様にしたがってそのまま解釈 or テーブルを QRコード読み取りソフトで読み取れる画像に変換 のどちらです。
当然画像化のほうが簡単で手っ取り早いのでこちらを選択します。
画像化の方法ですが、ビットマップ画像 を生成したり HTML5などのコードを生成してブラウザに描いてもらったりと いろいろありますが、 最も最小のコードですむのは XPM 画像 だと思います。
XPM画像は ASCIIテキストだけで表現できる 画像フォーマットで 画像データそのものがCの配列の形をしており X Windows System でソースコード内に画像をそのまま埋め込むときに 使われています。
これならば printf 一本で画像が生成できてしまいます。
以下生成コード
#include <stdio.h>
#include <ctype.h>
#define XPM_BLACK30 "##############################"
#define XPM_WHITE30 "______________________________"
int main()
{
int i,j,k;
char c;
char line[32];
FILE *fp;
fp = fopen("take22.txt", "rb");
printf(
" /* XPM */\n"
"static char *take22_xpm[] = { \n"
" \"%d %d 3 1\", \n"
" \". c none\",\n"
" \"# c black\",\n"
" \"_ c white\",\n",
(31*30), (31*30)
);
for( i=0; !feof(fp); i++)
{
c=fgetc(fp);
if(islower(c)) line[i] = '0';
if(isupper(c)) line[i] = '1';
if( c == '\n')
{
line[32] = '\0';
i = -1;
for(k=0; k<30; k++)
{
printf("\"");
for(j=0;j<31;j++)
{
switch(line[j])
{
case '0': printf(XPM_WHITE30); break;
case '1': printf(XPM_BLACK30); break;
}
}
printf("\",\n");
}
}
}
printf("};");
fclose(fp);
}
line[] に 一行分の 黒白データのリストを作って 改行ごとに 一行分のデータを出力しています。
ASCII 1文字が 1pixel に対応するので1ブロックあたり 30x30 pixel 分 表示するのにちょっときたないループを回しています。 このあたりは綺麗に書きなおしてもうちょっと汎用的な QRコード生成器にしたいところです。
逆にいえばこんな雑なコードでちゃんと画像が作れます。
問題の暗号を take22.txt に保存して上記コードを走らせれば終了です。 QRコードを適当な方法で読み取れば FLAGが表示されます。
前述のとおり 生成された xpm画像の実体は Cのソースコードなので テキストエディタで開けて楽しいです。