golangでご覧!wて言いたくてgolangでHTTPサーバ書いた

golangでご覧!wて言いたくてHTTPサーバー書いた。PythonのSimpleHTTPServerっぽくなった。名前がgoranなので「golangのgoranでご覧!w」と言えるようになった。

tkmru/goran: simple http server.

雑に以下のようなかんじで使える。localhostにしないと使えないブラウザのAPIとかあって開発のときにシュッとHTTPサーバーを用意できると便利で一応そういう実用的な側面もある。

$ cd DOCUMENT_ROOT
$ goran
2017/11/07 23:22:29 Starting Goran HTTP Server
2017/11/07 23:22:29 Listen : http://127.0.0.1:8888
2017/11/07 23:22:29 RootDir: ./
2017/11/07 23:22:38 127.0.0.1:65208 GET /

いろいろオプションがあって、アドレス、ポート、ルートディレクトリ、configのパスを指定できる。

$ ./goran -h
Usage:
  -a string
        address to use (short) (default "127.0.0.1")
  -address string
        address to use (default "127.0.0.1")
  -c string
        config path to use (short)
  -config string
        config path to use
  -p uint
        port to use (short) (default 8888)
  -port uint
        port to use (default 8888)
  -r string
        root directory to use (short) (default "./")
  -root string
        root directory to use (default "./")

configファイルにはTOMLを使った。以下のように設定できる。

Port = 6000
Addr = "127.0.0.1"
rootDir = "/var/www/hoge"

Golangの勉強になってよかった。暇なときにworkerprocessとかキャッシュとかリバースプロキシの設定をできるようにしたい。

ActiveResourceを使ったRailsアプリをRedisで高速化した

ActiveResource とは

ActiveResourceはRESTful APIマッピングActiveRecord のモデルとして利用可能にするgemで、これを使うとActiveRecordでDB操作を行うのと同じようにRESTful APIを利用できます。

github.com

ボトルネックになることもある

ActiveResourceを頻繁に使うとAPIに過剰にアクセスしているのと同じこととなり、ボトルネックとなりえます。 以下は今回、チューニングの対象となったアプリのNginxのアクセスログkataribeでパースしたものです。

$ cat /var/log/nginx/access.log | kataribe
...
TOP 12 Slow Requests
 1  14.933  GET / HTTP/1.1
 2  14.898  GET / HTTP/1.1
 3  14.722  GET / HTTP/1.1
 4  14.692  GET / HTTP/1.1
 ...

rootへのGETリクエストが14秒台後半で非常に遅いことがわかります。これはActiveResourceを使って取ってきたデータを一覧するページで、ActiveResourceボトルネックとなっていると推測できました。

Redisでキャッシュする

オンメモリ KVSのDBであるRedisにキャッシュすることで高速化を図ります。Redisはメモリ上でデータを管理するため、Read/Writeともに高速でキャッシュとして適しています。ActiveResourceを使ってデータを引っ張ってくるところをキャッシュしてみました。

RedisとRailsアプリケーションの接続にはmperham/connection_pool: Generic connection pooling for Rubyを使いました。コネクションプールというのはDBのコネクションをあらかじめ一定数確立しておいて使いまわす手法で、これを使うとDBへの接続に必要となるオーバーヘッドをカットできWeb/DBの双方の負荷を下げることができます。また、Web/DB間の接続を使いまわすことで同時接続数を節約します。MongoDB gem、ActiveRecord gemは自前でコネクションプールを持っていますが、Redis gemは持っていないので、このgemを使って接続することでコネクションプールを使えるようにしました。以下がRedisへの接続のために追加したファイルです。

config/initializers/redis.rb

# frozen_string_literal: true

# Load the redis.yml configuration file
redis_config = YAML.load_file(Rails.root + 'config/redis.yml')[Rails.env]

Redis.current = ConnectionPool.new(size: 10, timeout: 5) do
  Redis.new host: redis_config['host'], port: redis_config['port']
end

config/redis.yml

default: &default
  host: localhost
  port: 6379

development:
  <<: *default

test:
  <<: *default

production:
  <<: *default

Redisへのアクセス

Redisへのアクセスはcontroller内で以下のように行えますが、ここで注意しないといけないのは、keyがハッシュであるオブジェクトをJSONに変換しRedisにいれると、Redisから取り出しJSONにした際にkeyがstringになるところです。with_indifferent_accessを使うなどして対処しましょう。

Redis.current.with do |redis|
  test_user = { :name => "tkmru", :email => "tkmru@hoge"}
  redis.set('test_user', test_user.to_json)
  test_user = JSON.parse(redis.get('test_user')) # {"name"=>"tkmru", "email"=>"tkmru@hoge"}
  p test_user[:name]                             # nil
  p test_user['name']                            # tkmru
  test_user = test_user.with_indifferent_access
  p test_user[:name]                             # tkmru
  p test_user['name']                            # tkmru
end

結果

$ cat /var/log/nginx/access.log | kataribe
...
TOP 10 Slow Requests
 1  3.566  GET /hoge/231 HTTP/1.1
 2  1.866  GET / HTTP/1.1
 3  1.482  GET /hoge/240 HTTP/1.1
 4  1.479  GET /hoge/226 HTTP/1.1
 5  1.439  GET / HTTP/1.1
 6  1.415  GET /hoge/238 HTTP/1.1
 7  1.410  GET / HTTP/1.1
 8  1.293  GET / HTTP/1.1
 9  1.119  GET /hoge/243 HTTP/1.1
...

14秒台後半だったrootへのGETリクエストが1.4秒ちょっとで終わりました。 /hoge 以下もキャッシュすればもっと早くできそう。

おわりに

今回は原因が自明であったため、kataribeによるNginxのアクセスログのプロファイリングしか行いませんでしたが、pt-query-digestによるMySQLのスロークエリの解析や、stackprofrblineprofによるRubyのコードのプロファイリングを行うとより詳細にボトルネックを見つけることが可能です。 また、Redisでキャッシュすることによる高速化は、ActiveResourceを使ったアプリケーションに限定されるテクニックではなく、様々なアプリケーションで使うことができます。やっていきましょう。

コマンドラインオプションをflagでパースしたとき-hを指定するとexit status2と出てしまう

どういうこと

golangではコマンドラインオプションをflagパッケージを使ってパースすることができる。 しかし、オプションに-h--helpを指定すると、以下のようにexit status2と出てしまう。 なぜこんな仕様なのか...

$ go run flagSample.go -h
Usage:
  -n int
        number to use (default 1234)
exit status 2

これを出ないようにしたい。

対応策

flag.Usage を使ってUsageを表示するときに動かす関数を指定してあげ、その関数内で os.exit(0) を呼ぶと-h--helpを指定したときの statusコードは0となり、exit status2は出なくなる。以下にコード例を示す。

コード

これは、-nで指定した数字を表示するだけのコマンドラインツールのコードである。flag.Usageで指定したUsage()内でos.exit(0)を呼んでいるので、exit status2 は出ない。

package main

import (
    "flag"
    "fmt"
    "os"
)

var number int

func usage() {
    fmt.Println("Usage:")
    flag.PrintDefaults()
    os.Exit(0)
}

func init() {
    const defaultNumber = 1234
    flag.IntVar(&number, "n", defaultNumber, "number to use")
    flag.Usage = func() { usage() }
}

func main() {
    flag.Usage = func() { usage() }
    flag.Parse()
    fmt.Println("flag test")
    fmt.Printf("Number: %d\n", number)
}

実行結果

$ go run flagSample.go -n 6666
flag test
Number: 6666
$ go run flagSample.go -h
Usage:
  -n int
        number to use (default 1234)

複数人のSSHの鍵をGitHubに登録している鍵を使ってシュッと鯖にいれる

各ユーザーがGitHubに登録している公開鍵は公開されていて誰でも見れるのでこれを使う。 大体の人はGitHubに鍵を登録しているだろうし、これを使えばシュッと鯖にSSHの公開鍵を設定することができて便利。 以下のようにcurlコマンド一発で複数人のSSHの鍵を登録することができる。

$ curl https://github.com/{user_id,user_id2,user_id3}.keys >> ~/.ssh/authorized_keys #user_id間にスペースいれると動かないので注意

ISUCONのときにも役立った。ISUCON、あと3000点ちょい....というところで予選敗退したので来年リベンジするぞ!!

Mitamaeでdotfilesの管理をやるようにした

Mitamaeでdotfilesの管理をするようにした。

Mitamaeとは

プロビジョニングツールのitameのmruby実装である。mruby実装にすることで何がうれしいかというとシングルバイナリとして動作するので、Ruby や gem に依存しなくなるというのがある。Mitamaeのバイナリがあれば動くので、curlで引っ張ってきてシュッと使うことができる。ちなみに読みは「見たまえ」ではなく「えむいたまえ」らしい。

k0kubun.hatenablog.com

これを使うと環境構築のための環境構築の手間を最小限にできる。 dotfilesのデプロイには今までシェルスクリプトを使っていたが、mitamaeを使うほうが便利であった。

構成

ぼくのdotfilesはtkmru/dotfilesで、k0kubun/dotfilesakito19/dotfiles をかなり参考にしている。

github.com

$ tree -aL 2
.
├── .git
│   ...
├── .gitignore
├── README.md
├── bin
│   └── setup_mitamae.sh
├── config
│   ├── .bash_aliases
│   ├── .bash_profile
│   ├── .bashrc
│   ├── .gdbinit
│   ├── .gitconfig
│   ├── .profile
│   ├── .tmux.conf
│   └── .vimrc
├── cookbooks
│   ├── gdb
│   ├── git
│   ├── symboliclinks
│   ├── tmux
│   └── vim
├── deploy.sh
├── init.sh
├── lib
│   └── bootstrap.rb
└── roles
    ├── darwin
    └── ubuntu

最初に実行するのはinit.shで、Mitamaeをcurlでダウンロードする。OSがmacだった場合、Xcode Command Line Toolsのインストールやhomebrewのインストールもここで行う。そして最後に、ホームディレクトリからdotfilesへのシンボリックリンクを張るdeploy.shを実行している。deploy.shはMitamaeを用いてlib/bootstrap.rbを実行し、roles.rbからcookbook以下のレシピを実行することでシンボリックリンクを張っている。 また、OSごとに使うdotfilesが違う(ubuntuの.profileとか)ので、deploy.sh内でunameを使ってOSを判定し、roles以下で異なる処理をさせている。

参考資料

GSoC完走できた〜💪

GSoC完走できた〜!!metasploit-framework に3ヶ月間、コントリビュートしていました。


f:id:TAKEmaru:20170910004947p:plain

tkmr.hatenablog.com

linuxのstager周りを触るということでアセンブリ書いてお金💰もらってたけれど、そうそう業務でアセンブリ書かないだろうし、これが人生で最初で最後になる気がする。Googleからお金もらうのもこれ最後か〜と思うとエモくなってきた。

参加前にwebで参加記みたいなの読んでた時は、進捗報告ミーティングがしょっちゅうあるみたいなことを見ていたのでビビっていたけど、Metasploitでは適宜困ったら相談というかんじで平和に進行していった。試験期間はあまりコミットできないって言ったら了承してもらえたりもした。採用されるOSSによるだろうけど、ビビらずに応募するとよさそう。

期間中のおもしろエピソードとしては、チャットツールが最初はIRCだったけど、「ログ取るのにサーバ必要だしやだ〜」って言ったらTwitterのDMになって、それからGoogle Hangoutになって、最終的にslackになったこととか、rapid7から日本語できる人が出てきたというのがある。

書きかけのshellcodeが2つあるのでこれは後々プルリク送りたい〜。OSSに名前を残せたとかより、チャットなら英語でコミュニケーション問題なくとれることが分かったのが一番の収穫な気がする。とは顔本で書いたものの著名なOSSに名前が入るとアガる。

f:id:TAKEmaru:20170910010731p:plain

これからも継続してコミットしていけるといいですね。

HackIT CTF 2017 rev200 writeup

問題

Description: You haxor, come on you little sciddie… debug me, eh? You fucking little lamer… You fuckin’ come on, come debug me! I’ll get your ass, you jerk! Oh, you IDA monkey! Fuck all you and your tools! Come on, you scum haxor, you try to reverse me? Come on, you asshole!!

rev200.efiというefiファイルが与えられる。

$ file rev200.efi 
rev200.efi: PE32+ executable (DLL) (EFI application) x86-64 (stripped to external PDB), for MS Windows

解く

シンボル付きのバイナリなので読みやすい。efi_main()を見ると、InitializeLib()でライブラリの初期化とInput()で入力の受け付けを行っているのが分かる。Inputの直後でalgo()をcallしていてあやしい。

f:id:TAKEmaru:20170904044949p:plain

algo()を見るとCorrect、Wrongを出力していて、ここで入力値のチェックをしていると分かる。algo()をhopperでデコンパイルすると以下のようになった。

int algo(int arg0) {
    rsp = rsp - 0x180;
    rbp = rsp + 0x80;
    arg_32 = arg0;
    for (arg_-1 = 0x0; arg_-1 <= 0x13; arg_-1 = arg_-1 + 0x1) {
            *(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0x90) = *(int8_t *)(arg_32 + sign_extend_64(arg_-1)) & 0xff & 0xff;
    }

    for (arg_-1 = 0x0; arg_-1 <= 0x13; arg_-1 = arg_-1 + 0x1) { // check input
            *(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0x90) = (((*(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0x90) ^ 0xc) + 0x6 ^ 0xd) + 0x7 ^ 0xe) + 0x8;
            *(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0x40) = (((*(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0x40) ^ 0xf) + 0x9 ^ 0x10) + 0xa ^ 0x11) + 0xb;
    }

    for (arg_28 = 0x0; arg_28 <= 0x13; arg_28 = arg_28 + 0x1) {
            *(int32_t *)(rbp + sign_extend_32(arg_28) * 0x4 + 0xffffffffffffffa0) = *(int32_t *)(rbp + sign_extend_32(arg_28) * 0x4 + 0x90);
    }

    for (arg_29 = 0x14; arg_29 <= 0x27; arg_29 = arg_29 + 0x1) {
            *(int32_t *)(rbp + sign_extend_64(arg_29 + 0xffffffffffffffec) * 0x4 + 0x40) = *(int8_t *)(arg_32 + sign_extend_64(arg_29)) & 0xff & 0xff;
    }
    for (arg_-1 = 0x14; arg_-1 <= 0x27; arg_-1 = arg_-1 + 0x1) {
            *(int32_t *)(rbp + sign_extend_32(arg_-1) * 0x4 + 0xffffffffffffffa0) = *(int32_t *)(rbp + sign_extend_32(arg_-1 - 0x14) * 0x4 + 0x40);
    }

    if (memcmp(&var_-96, correct, 0xa0) == 0x0) {
            rax = Print(u"\nCorrect\n", correct, 0xa0, r9);
    }
    else {
            rax = Print(u"\nWrong\n", correct, 0xa0, r9);
    }
    return rax;
}

入力値をxorしたあと、memcmpでcorrectと比較している。 総当りで解くsolverを書いた。

# coding: UTF-8

correct = [104, 60, 121, 113, 99, 124, 129, 146, 146, 101, 101, 147, 146, 73, 121, 146, 56, 108, 60, 111, 123, 135, 88, 85, 137, 90, 89, 126, 126, 107, 135, 108, 87, 108, 107, 88, 89, 90, 90, 111];
flag = ""

for i in range(20):
    for j in range(256):
        if ((((((j ^ 0xc) + 6) ^ 0xD) + 7) ^ 0xe) + 8) == correct[i]:
            flag += chr(j)

for i in range(20):
    for j in range(256):
        if (((((j ^ 0xf) + 9) ^ 0x10) + 10) ^ 0x11) + 11 == correct[i+20]:
            flag += chr(j)
   
print(flag)

z3pyを使っても解くことができる

# coding: UTF-8

from z3 import *

FLAG_LENGTH = 40
correct = [104, 60, 121, 113, 99, 124, 129, 146, 146, 101, 101, 147, 146, 73, 121, 146, 56, 108, 60, 111, 123, 135, 88, 85, 137, 90, 89, 126, 126, 107, 135, 108, 87, 108, 107, 88, 89, 90, 90, 111];

s = Solver()
text = []

for i in range(FLAG_LENGTH):
    text.append(BitVec(i, 8))
    s.add(And(text[i] >= 0x20, text[i] < 0x7f)) # in printable ascii

for i in range(20):
    s.add(((((((text[i] ^ 0xC) + 6) ^ 0xD) + 7) ^ 0xE) + 8) == correct[i])

for i in range(20, 40):
    s.add((((((text[i] ^ 0xF) + 9) ^ 0x10) + 10) ^ 0x11) + 11 == correct[i])

if s.check() == sat:
    m = s.model()
    flag = ''
    for i in range(FLAG_LENGTH):
        flag += chr(int(str(m[text[i]])))

    print(flag)
$ python solver.py 
h4ck1t{ff77af3cf8d4e1e67c4300aeb5ba6344}