ネットエージェント株式会社 オフィシャルブログ

ドメイン名レピュテーション

 みなさんはじめまして、ネットエージェント株式会社、研究開発部の山口尋久(やまぐちひろひさ)と申します。大阪で製品やサービスの開発をしています。
 今回はドメイン名レピュテーションの扱いについての最近の話題を紹介したいと思います。

-----

 「レピュテーション(Reputation)」とは、通信を制御/ブロックするためのデータをユーザから収集した評価情報をもとに整備する手法のことで、近年では迷惑メール対策によく使われます。レピュテーションを直訳すると「評判」という意味であり、これはいわばユーザからの「タレコミ」と言えます。このような情報提供により、実績報告に基づくデータベースの構築がより早期に可能になり、仮に通信するべきでない問題サイト(マルウェア配布サイトなど)が新たに見つかった場合も、この即応性によりすぐに対応可能となります。
 ただ、この早期対応可能であるという特徴は、悪意を持った報告や誤った報告に対して脆弱な側面もあります。迷惑メール送信実績サイトを集めたブラックリストの中には、この即応性を重視しすぎた結果、大手ISPを丸ごと登録してしまい、なかなか解除されなかったといったトラブルを経験したという話もしばしば聞かれたものです。
 確かに、評価情報を収集する際「迷惑メールを送信した」というネガティブな情報は比較的集めやすいですが、逆に「問題がない」という情報は扱いにくい(集めにくい)印象があります。
 よって、DNSサーバやメールサーバなど、問題のある設定をしてしまいやすいサービスについては、設定状況を確認してポジティブな評価をしていくというのも有効な方法かもしれません。また、どれぐらいの期間それらの情報が有効であるとするかでデータベースの特徴も変わってきます。

 評価情報データベースの実装自体は、key-value型のデータベースを使えば容易に可能なので、レピュテーションに関しては、情報収集の手段(いかにして情報を集めるか?)が各公開リストの大きな特徴(差別化される点)と言えます。迷惑メールの送信実績のあるIPアドレスを収集したもの、そのアドレスを含む同一契約と思しきアドレスブロック単位で提供するもの、ドメイン名単位で収拾するもの、フィッシング(phishing)サイトを収拾したもの、マルウェア配布サイトを収集したものなどは、あちこちで公開されているサーバやアプリケーションの設定サンプルによく含まれています。
 とはいえ多数ある「レピュテーションエンジン」を切替えて使う手法は、色々なサイトで試みられているものの横断的な利用についての議論はあまり多くはなかったように感じます。
 そんな中、今年の8月にIETFのDKIM(DomainKeys Identified Mail)WGの議論からスピンアウトする形でnon-working groupメーリングリストが作られました。non-working groupメーリングリストでは、DKIMに特化した話ではなくすべてのアプリケーションに共通する形でドメイン名のレピュテーションの扱いについて議論されていくようであり、メーリングリスト内では、データベースの用途について考慮すべきか、古いデータをどうするか、提供された評価情報をどのように扱うか、複数データベース間での連携をどうするかなどの、メンテナンスにまつわる諸問題、問合せ記録を見ることによって問合せ側のサイトのプライバシーが明るみに出る可能性をどのように解決するかなど、周辺の諸々の事項についても様々な意見が出されています。個々の議論については、アーカイブが公開されているので、興味のある方は追いかけてみてください。
 個人的に、特に興味深かった話題としては、ドメインレピュテーションリストの用途(メール用かurl用か)について議論すべきであるかという話の流れの中で、「通信の中身(私信なのか取引上の連絡なのか等々)に比べたら、サービスの種別(メールかWebか)はさほど重要ではない」という趣旨の発言がありました。spam送信サイトのリスト、フィッシングサイトのリスト、マルウェア配布サイトのリストといった用途別のリストばかりが念頭にあったので、この視点は新鮮に映りました。
 通信内容の性格を考えると、たとえば私的な通信の場合は個人的なつながりによる関係性により重みづけがきまるためレピュテーションによる評価づけがふさわしくないでしょうし、逆に不特定多数へ送信される広告などはレピュテーションによる評価づけが有用だと言えるでしょう。また、メールアドレスのドメインとwebサイトのドメインが同じである場合にそれらの評価情報に関連性が認められる場合も多いでしょう。用途別のサービス横断的なレピュテーションデータベースが作られるとすれば、どのようなものになるかどのように使いこなしていくのがいいかと想像が広がります。

 レピュテーションという手法は古くからあり、既存の実装も多数存在する中で、こういった議論を経ることで新しい実装のあり方や活用の仕方が見えてくるとまた面白いかもしれません。

64ビット環境におけるリバースエンジニアリング

 こんにちは、愛甲です。今日は64ビット環境におけるリバースエンジニアリングについて書かせていただこうと思います。

-----

 ここ数年のコンピュータ業界の流れのひとつとして、オペレーティングシステムの64ビット化があります。リバースエンジニアリングの視点から考えると、32ビットから64ビットへ移行することでレジスタのサイズが倍になり、命令セットがx86とは異なれば、それらを新たに知識として学習しなければ64ビット用のソフトウェアを解析できません。今回はUbuntu LinuxのAMD64版(x86_64版)を利用して、実行ファイルが扱う64ビットのマシン語(アセンブラ)を見ていきたいと思います。
 まず、64ビット環境(UbuntuLinux x86_64環境)のgccでは、特に指定しない限り、引数の受け渡しにスタックではなくレジスタが使われます。レジスタの数にも限界があるので、どこまでレジスタが利用されるのかを調べるために、次のプログラムを作成します。

// test01.c
#include <stdio.h>

int func(int a, int b, int c, int d, int e, int f, int g)
{
return (a + b + c + d + e + f + g);
}

int main(void)
{
printf("%d\n", func(1, 2, 3, 4, 5, 6, 7));
return 0;
}

 7つの引数を持つ関数を自作しました。これをコンパイルして逆アセンブルします。これでfunc関数が呼び出される際、どれだけの引数がレジスタを経由して関数へ渡されるかを確認できます。

$ gcc -Wall test01.c -o test01
$ ./test01
28
$ gdb test01
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
(gdb) disas main
Dump of assembler code for function main:
0x0000000000400556 <+0>: push %rbp
0x0000000000400557 <+1>: mov %rsp,%rbp
0x000000000040055a <+4>: sub $0x10,%rsp
0x000000000040055e <+8>: movl $0x7,(%rsp)
0x0000000000400565 <+15>: mov $0x6,%r9d
0x000000000040056b <+21>: mov $0x5,%r8d
0x0000000000400571 <+27>: mov $0x4,%ecx
0x0000000000400576 <+32>: mov $0x3,%edx
0x000000000040057b <+37>: mov $0x2,%esi
0x0000000000400580 <+42>: mov $0x1,%edi
0x0000000000400585 <+47>: callq 0x400524 <func>
0x000000000040058a <+52>: mov %eax,%edx
0x000000000040058c <+54>: mov $0x40069c,%eax
0x0000000000400591 <+59>: mov %edx,%esi
0x0000000000400593 <+61>: mov %rax,%rdi
0x0000000000400596 <+64>: mov $0x0,%eax
0x000000000040059b <+69>: callq 0x400418 <printf@plt>
0x00000000004005a0 <+74>: mov $0x0,%eax
0x00000000004005a5 <+79>: leaveq
0x00000000004005a6 <+80>: retq
End of assembler dump.

 0x400585にてfunc関数が呼び出されていますが、その際の引数はedi、esi、edx、ecx、r8d、r9d、(rsp)となっており、最後の引数(0x7)だけがスタックに積まれています。以上から引数は6つまでレジスタを利用し、7つ目からスタックに渡されると分かりました。
 また関数からのリターンにleaveq、retqを用いていることから、引数はレジスタでも、戻り先アドレスはスタックに積まれるようです。よってスタックがオーバーフローすればripが任意の値に変更できます。

// test02.c
#include <stdio.h>

int cpy(char *s)
{
char buff[16];
strcpy(buff, s);
return 0;
}

int main(int argc, char *argv[])
{
if(argc < 2)
return 1;
cpy(argv[1]);
return 0;
}

 単純なオーバーフロー脆弱性を持つプログラム(test02)を作成します。

$ gcc -fno-stack-protector test02.c -o test02
$ gdb test02
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
(gdb) r AAAABBBBCCCCDDDDEEEEFFFFGGGG
Starting program: /home/kenji/tmp/test02
AAAABBBBCCCCDDDDEEEEFFFFGGGG

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

 gccに-fno-stack-protectorを指定し、スタックプロテクション(VC++における/GSオプションと同等)をOFFにしてコンパイルし、gdb上で実行します。するとripが0x47474747に変わりました。
 リターン値は変更できましたが、引数がスタックではなくレジスタに格納されるというのは、return-to-libcによるAPI呼び出しに制限がかかることになります。NX(DEP)は近年ではスタンダードな仕様になりつつあるので、これまで通りのreturn-to-libcが64ビット化に起因したレジスタ経由の引数渡しによって実現できなくなるというのは面白い話です。
 実際は実現できなくなるというよりも、実現が難しくなる(満たさなければならない条件がひとつ増える)という意味であり、例えばpop %rdi; retという連続した命令がどこかにあれば、引数として1つ値を渡せますし、pop %rdi; pop %rsi; retがあれば2つの引数を渡せますので、この時点でsystem関数やexec系関数を呼び出せます。
 ではpop %rdi; ret(5f c3)は現実的によく存在する命令列でしょうか? 試しにlibc.so.6から調べてみます。

# hexdump -C /lib/libc.so.6 | grep "5f c3"
000201d0 c4 38 5b 5d 41 5c 41 5d 41 5e 41[5f c3]0f 1f 00
00022980 00 00 00 89 e8 5b 5d 41 5c 41 5d 41 5e 41[5f c3]
00023340 00 5b 5d 41 5c 41 5d 41 5e 41[5f c3]48 89 d5 48
00025400 5b 5d 41 5c 41 5d 41 5e 41[5f c3]bd 07 00 00 00
00025ca0 5d 41 5e 41[5f c3]48 89 c3 c7 44 24 2c 05 00 00
00026140 41[5f c3]4c 89 e0 31 d2 4c 89 eb 8b 08 85 c9 78
000266a0 00 00 00 89 e8 5b 5d 41 5c 41 5d 41 5e 41[5f c3]
00027730 5d 41 5e 41[5f c3]4c 89 eb 4c 89 e2 31 c9 66 90
00027ac0 41[5f c3]0f b6 41 07 88 45 00 0f b6 41 06 88 45
00028040 5d 41 5c 41 5d 41 5e 41 [5f c3]48 83 7c 24 48 00
000286b0 41[5f c3]0f 1f 44 00 00 8d 4c 0d 00 85 ed 4d 8d
00029010 48 83 c4 78 5b 5d 41 5c 41 5d 41 5e 41[5f c3]90
00029050 00 00 5b 5d 41 5c 41 5d 41 5e 41[5f c3]0f 1f 00
000293e0 41 5e 41[5f c3]0f 1f 00 0f b7 49 02 48 8b 7c 24
000294c0 5b 5d 41 5c 41 5d 41 5e 41[5f c3]90 90 90 90 90
00029890 c4 28 48 89 d8 5b 5d 41 5c 41 5d 41 5e 41[5f c3]
000311a0 5b 5d 41 5c 41 5d 41 5e 41[5f c3]0f 1f 44 00 00
00031a60 5b 5d 41 5c 41 5d 41 5e 41[5f c3]48 8b 55 f0 48
000355d0 [5f c3]48 89 c8 4c 29 c8 4c 8d 2c c2 49 8d 41 ff
00036460 c4 38 5b 5d 41 5c 41 5d 41 5e 41[5f c3]0f 1f 00
00036720 41 5d 41 5e 41[5f c3]66 0f 1f 84 00 00 00 00 00
00036960 5d 41 5c 41 5d 41 5e 41 [5f c3]66 0f 1f 44 00 00
(省略)

 かなりの数が見つかりましたが、これらは本当はpop %r15; ret(41 5f c3)という命令列であり、pop %rdi; ret(5f c3)ではありません。しかし、x86やx86_64は可変長命令であるためpop %r15; ret(41 5f c3)の途中からマシン語を解釈するとpop %rdi; ret(5f c3)となるため、命令コードの途中へジャンプさせることで、事実上pop %rdi; ret(5f c3)として命令コードを使うことが可能です。そして、同じ方法でpop %rsi; ret(5e c3)という命令列が見つからなくとも、pop %r14; pop %r15; ret(41 5e 41 5f c3)の途中へジャンプすることで、pop %rsi; %pop %r15; ret(5e 41 5f c3)という命令を実行できます。つまりpop %r14; pop %r15; ret(41 5e 41 5f c3)があれば、2つの引数を渡して関数呼び出しを実現できます。
 ちなみに、3つの引数を渡したい場合はrdxへ格納する必要があるため、pop %rdx; ret(5a c3)を探さなければなりませんが、pop %r10; ret(41 5a c3)でも同様の理由でOKです。

# hexdump -C /lib/libc.so.6 | grep "5a c3"
00001b80 be 1e 9a 17 c3 6a 30 3f f8 38 5a c3 f8 38 5a c3
00001b90 f9 38 5a c3 b6 1d cd 66 2b 41 bd 7e 54 06 6a ec
000a6090 2d 00 64 c7 00 02 00 00 00 31 c0[5a c3]90 90 90
000a9e10 00 e8[5a c3]04 00 48 81 c4 80 00 00 00 e9 73 ff
000f6160 87 07 85 c0 75 f1 5a 41 [5a c3]66 0f 1f 44 00 00
000fea10 84 e5 27 00 64 c7 00 16 00 00 00 83 c8 ff[5a c3]

 以上から、64ビット環境により引数がレジスタ渡しになったとしても、glibcがASLRされていないならばreturn-to-libcは実現可能であると分かります(まぁASLRされていればどちらにせよreturn-to-libcはできないですが…)。ちなみに、このように「可変長の命令であること」と「retを終端とした実行領域にあるコード」を何度も利用して欲しいプログラム結果を得るテクニックをReturn-Oriented Programmingと呼びます(参考:When Good Instructions Go Bad:Generalizing Return-Oriented Programming to RISC)。
 本来はglibcがASLRされており、return-to-libcで任意のAPIを呼び出せない場合でも、ランダマイズされていないアドレスに配置された命令群を利用して新たなコードを生成するといったテクニックとして利用されるようです。

 続いてスタックプロテクションを見てみます。関数呼び出し時に引数の格納先としてrdi、rsi、rdx、rcx、r8d、r9dのレジスタが使われること、そしてレジスタのサイズが64ビットであるため、スタックが8バイトずつ消費されることが32ビットとの大きな違いですが、カナリア値も4バイトではなく、8バイトの値がスタックに積まれます。

$ cp test02.c test03.c
$ gcc test03.c -o test03
$ gdb test03
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
(gdb) disas cpy
Dump of assembler code for function cpy:
0x0000000000400594 <+0>: push %rbp
0x0000000000400595 <+1>: mov %rsp,%rbp
0x0000000000400598 <+4>: sub $0x30,%rsp
0x000000000040059c <+8>: mov %rdi,-0x28(%rbp)
0x00000000004005a0 <+12>: mov %fs:0x28,%rax
0x00000000004005a9 <+21>: mov %rax,-0x8(%rbp)
0x00000000004005ad <+25>: xor %eax,%eax
0x00000000004005af <+27>: mov -0x28(%rbp),%rdx
0x00000000004005b3 <+31>: lea -0x20(%rbp),%rax
0x00000000004005b7 <+35>: mov %rdx,%rsi
0x00000000004005ba <+38>: mov %rax,%rdi
0x00000000004005bd <+41>: callq 0x400498 <strcpy@plt>
0x00000000004005c2 <+46>: mov $0x0,%eax
0x00000000004005c7 <+51>: mov -0x8(%rbp),%rdx
0x00000000004005cb <+55>: xor %fs:0x28,%rdx
0x00000000004005d4 <+64>: je 0x4005db <cpy+71>
0x00000000004005d6 <+66>: callq 0x400488 <__stack_chk_fail>
0x00000000004005db <+71>: leaveq
0x00000000004005dc <+72>: retq
End of assembler dump.
(gdb) b *0x00000000004005cb
Breakpoint 1 at 0x4005cb
(gdb) r AAAA
Starting program: /home/kenji/tmp/test03 AAAA

Breakpoint 1, 0x00000000004005cb in cpy ()
(gdb) i r rdx
rdx 0x1dd80a9c73dfcd00 2150480489144634624
(gdb) r AAAA
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/kenji/tmp/test03 AAAA

Breakpoint 1, 0x00000000004005cb in cpy ()
(gdb) i r rdx
rdx 0xc431a2165d1c6200 -4309485151481732608
(gdb)

 カナリア値は領域としては8バイトを使いますが、実際は最下位バイトが00hで固定されているようで、事実上、上位7バイトが有効値となります。このカナリア値はどこから来ているのかというと、環境変数領域辺りに「x86_64」で終わるデータ列があり、それの先頭7バイトを取得しています。

$ cp test03.c test04.c
$ gcc -static test04.c -o test04
$ gdb test04
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
(gdb) disas _start
Dump of assembler code for function _start:
0x0000000000400320 <+0>: xor %ebp,%ebp
0x0000000000400322 <+2>: mov %rdx,%r9
0x0000000000400325 <+5>: pop %rsi
0x0000000000400326 <+6>: mov %rsp,%rdx
0x0000000000400329 <+9>: and $0xfffffffffffffff0,%rsp
0x000000000040032d <+13>: push %rax
0x000000000040032e <+14>: push %rsp
0x000000000040032f <+15>: mov $0x400e00,%r8
0x0000000000400336 <+22>: mov $0x400e40,%rcx
0x000000000040033d <+29>: mov $0x40047d,%rdi
0x0000000000400344 <+36>: callq 0x4004c0
<__libc_start_main>
0x0000000000400349 <+41>: hlt
0x000000000040034a <+42>: nop
0x000000000040034b <+43>: nop
End of assembler dump.
(gdb) b *0x400322
Breakpoint 1 at 0x400322
(gdb) disas cpy
Dump of assembler code for function cpy:
0x0000000000400434 <+0>: push %rbp
0x0000000000400435 <+1>: mov %rsp,%rbp
0x0000000000400438 <+4>: sub $0x30,%rsp
0x000000000040043c <+8>: mov %rdi,-0x28(%rbp)
0x0000000000400440 <+12>: mov %fs:0x28,%rax
0x0000000000400449 <+21>: mov %rax,-0x8(%rbp)
0x000000000040044d <+25>: xor %eax,%eax
0x000000000040044f <+27>: mov -0x28(%rbp),%rdx
0x0000000000400453 <+31>: lea -0x20(%rbp),%rax
0x0000000000400457 <+35>: mov %rdx,%rsi
0x000000000040045a <+38>: mov %rax,%rdi
0x000000000040045d <+41>: callq 0x4002a8
0x0000000000400462 <+46>: mov $0x0,%eax
0x0000000000400467 <+51>: mov -0x8(%rbp),%rdx
0x000000000040046b <+55>: xor %fs:0x28,%rdx
0x0000000000400474 <+64>: je 0x40047b <cpy+71>
0x0000000000400476 <+66>: callq 0x40f8f0 <__stack_chk_fail>
0x000000000040047b <+71>: leaveq
0x000000000040047c <+72>: retq
End of assembler dump.
(gdb) b *0x400449
Breakpoint 2 at 0x400449
(gdb) r AAAA
Starting program: /home/kenji/tmp/test04 AAAA

Breakpoint 1, 0x0000000000400322 in _start ()
(gdb) x/15s 0x7fffffffe900
0x7fffffffe900: ""
0x7fffffffe901: ""
0x7fffffffe902: ""
0x7fffffffe903: ""
0x7fffffffe904: ""
0x7fffffffe905: ""
0x7fffffffe906: ""
0x7fffffffe907: ""
0x7fffffffe908: ""
0x7fffffffe909: "\242\314?B\351v\016\204S\355]r,}\016x86_64"
0x7fffffffe920: ""
0x7fffffffe921: ""
0x7fffffffe922: "/home/kenji/tmp/test04"
0x7fffffffe939: "AAAA"
0x7fffffffe93e: "SHELL=/bin/bash"
(gdb) x/32b 0x7fffffffe909
0x7fffffffe909: 0xa2 0xcc 0xca 0x9d 0x42 0xe9 0x76 0x0e
0x7fffffffe911: 0x84 0x53 0xed 0x5d 0x72 0x2c 0x7d 0x0e
0x7fffffffe919: 0x78 0x38 0x36 0x5f 0x36 0x34 0x00 0x00
0x7fffffffe921: 0x00 0x2f 0x68 0x6f 0x6d 0x65 0x2f 0x6b
(gdb) c
Continuing.

Breakpoint 2, 0x0000000000400449 in cpy ()
(gdb) i r $rax
rax 0x76e9429dcacca200 8568453011528786432
(gdb)

 0x7fffffffe909以降の7バイト(a2 cc ca 9d 42 e9 76)がその値です。cpy関数内で用いられるカナリア値(0x76e9429dcacca200)と同じ値になっています。ちなみにBlackHat EU 2009のStack Smashing as of Todayでは、カナリア値の設定に以下のアルゴリズムが用いられていると書かれてあります。

def canary():
__WORDSIZE = 64
ret = 0xff0a000000000000
ret ^= (rdtsc() & 0xffff) << 8
ret ^= (%rsp & 0x7ffff0) << (__WORDSIZE 23)
ret ^= (&errno & 0x7fff00) << (__WORDSIZE 29)
return ret

 これは0x7fffffffe909以降の7バイトがない、かつ、/dev/urandomからの読み込みに失敗した際に使われるアルゴリズムとなっています。__libc_start_main関数を読むことでそれを確認できます。

(gdb) disas __libc_start_main
Dump of assembler code for function __libc_start_main:
0x00000000004004c0 <+0>: push %r15
0x00000000004004c2 <+2>: mov $0x0,%eax
0x00000000004004c7 <+7>: push %r14
0x00000000004004c9 <+9>: push %r13
(省略)
0x0000000000400583 <+195>: mov 0x2a452e(%rip),%rax
# 0x6a4ab8 <_dl_random>
0x000000000040058a <+202>: movq $0x0,0x88(%rsp)
0x0000000000400596 <+214>: lea 0x89(%rsp),%r13
0x000000000040059e <+222>: test %rax,%rax
0x00000000004005a1 <+225>: je 0x400692
<__libc_start_main+466>
(省略)
0x00000000004005c1 <+257>: mov 0x88(%rsp),%r13
0x00000000004005c9 <+265>: mov %r13,%fs:0x28
(省略)
0x0000000000400692 <+466>: xor %esi,%esi
0x0000000000400694 <+468>: mov $0x479a3b,%edi
# /dev/urandom
0x0000000000400699 <+473>: callq 0x40e080 <open64>
0x000000000040069e <+478>: test %eax,%eax
0x00000000004006a0 <+480>: mov %eax,%r14d
0x00000000004006a3 <+483>: js 0x4006c9
<__libc_start_main+521>
0x00000000004006a5 <+485>: mov $0x7,%edx
0x00000000004006aa <+490>: mov %r13,%rsi
0x00000000004006ad <+493>: mov %eax,%edi
0x00000000004006af <+495>: callq 0x40e140 <read>
0x00000000004006b4 <+500>: mov %r14d,%edi
0x00000000004006b7 <+503>: mov %rax,%r15
0x00000000004006ba <+506>: callq 0x40e0e0 <close>
0x00000000004006bf <+511>: cmp $0x7,%r15
0x00000000004006c3 <+515>: je 0x4005c1
<__libc_start_main+257>
0x00000000004006c9 <+521>: movb $0xff,0x6(%r13)
0x00000000004006ce <+526>: movb $0xa,0x5(%r13)
0x00000000004006d3 <+531>: rdtsc
0x00000000004006d5 <+533>: mov $0xffffffffffffffd0,%rdx
0x00000000004006dc <+540>: add %fs:0x0,%rdx
0x00000000004006e5 <+549>: movzwl %ax,%eax
0x00000000004006e8 <+552>: and $0x7ffff0,%r13d
0x00000000004006ef <+559>: and $0x7fff00,%edx
0x00000000004006f5 <+565>: shl $0x8,%rax
0x00000000004006f9 <+569>: shl $0x29,%r13
0x00000000004006fd <+573>: shl $0x23,%rdx
0x0000000000400701 <+577>: xor %rdx,%r13
0x0000000000400704 <+580>: xor 0x88(%rsp),%r13
0x000000000040070c <+588>: xor %rax,%r13
0x000000000040070f <+591>: mov %r13,0x88(%rsp)
0x0000000000400717 <+599>: jmpq 0x4005c9
<__libc_start_main+265>
(省略)

 rdtsc、rsp、errnoアドレスの3つが分かれば特定できるため、この場合はかなり脆弱なカナリア値になってしまいます。とはいえ、7バイトのランダム値が得られなかった場合の最終手段なので基本的に問題なさそうです。

-----

 64ビット化の流れは個人的にはとても楽しみにしています。まだまだ一般のPCは32ビット環境であり、しかもそれで十分事足りるのですが、やはり4GBを超えるリニアなメモリ空間だったり、マルチコア以外の処理性能の向上(それがほんの小さな差だったとしても)など、いろいろと調べてみたいことが多々あります。今後も少しずつですが、64ビット環境について調査していきたいと思います。

Google ChromeOSと10年後のフォレンジック

 こんにちは、ネットエージェント株式会社の松本です。今日は正式リリースに向けてそろそろ動向が気になり始めたGoogle ChromeOSと、10年後のフォレンジックのお話です。

-----

 未来のコンピュータフォレンジックを予測することは、すなわち未来のコンピューティング環境を予測することでもある。今年の6月にExFORENSICSに投稿された「Computer Forensics - The Next Ten Years」は、Larry E. Daniel による10年後のコンピュータフォレンジックの予測である。彼は以下の6つの観点からフォレンジックの未来について語っている。
 1. ユーザデータ領域は標準で暗号化され、ライブメモリフォレンジックがより重要になる
 2. 携帯電話が主要なコンピュータとして活発に利用されるようになり、
   モバイルフォレンジックがフォレンジック調査の主流になる
 3. 10年後もまだWindows7を利用するユーザは存在しており、
   調査員は引き続き調査することになる
 4. クラウドコンピューティングの利用により、クラウドに保管されるデータや、
   モバイルコンピュータを対象としたフォレンジックが重要になる
 5. テクノロジーは発展し続け、より高度なものになる
   それに従ってフォレンジックは常に成長を求められ、より困難になっていく
 6. ともあれ、コンピュータフォレンジック産業は今後も成長する
 この予測は、私にとっても非常に納得できるものである。投稿記事を読むに先立って、Google ChromeOS(以下ChromeOS)環境でのフォレンジック調査について検証をしており、そのセキュリティ機構の完成度(すなわちこれは調査のしにくさでもあるのだが)に頭を悩ませていたからである。そこで今回は、私の納得感を皆さんにも共有してもらうために、ChromeOSが採用しているユーザデータの保護の仕組みについて紹介したいと思う。

 ChromeOSは、2009年11月にGoogleによって発表されたPC向けの新型OSである。先行してGoogleが提供しているOS「Android」はスマートフォンや音楽プレイヤーなどの携帯デバイスを主なターゲットとしており、この二つのOSの住み分けについて議論されることがあるが、一般的にはChromeOSはネットブックやタブレットPCなどの低価格帯モバイルコンピュータをターゲット※1としているといわれている。
 ChromeOSの特徴は、Webブラウザ「Chrome」を核とする※2、非常にシンプルかつセキュアである点だ。基本的にデスクトップ・アプリケーションのインストールは行わず、Googleなどが提供するWebアプリケーションをChromeブラウザ上で実行させるためのOSである。
 ChromeOSは、GoogleDocks等のクラウドサービスを媒介にすることによって、ネットワークに接続しているコンピュータならどの端末からでも、ほぼ前回利用時と同一のユーザ環境を実現することが可能である。複数のユーザが同一端末を利用する事情もあり、ChromeOSのデータ保護、特にユーザデータの保護は徹底している。そこで、公式サイトであるThe Chromium ProjectsのProtecting Cached User Dataを参照しつつ実際のChromeOSを検証してみた。今回はHexxeh氏によってビルドされた「ChromeOS Flow」のVMWare版を利用した。2010年8月11日現在では2010年2月15日にビルドされたバージョンが最新である。
fo_m1 ChromeOS Flowの公式サイトでは、VMWareイメージのほかにUSB起動のイメージも提供されており、UbuntuのインストールやビルドなしにChromeOSを手軽に検証できるので非常に便利である。
 ChromeOS Flowを起動するとログイン画面が立ち上げる。なお初回ログイン時にはGoogleアカウントとネットワーク環境が必要になる。一度ログインすると、ログイン情報はローカルにキャッシュされ、次回以降はローカルログインも可能になる。Googleネットワークログイン時の通信は、Googleアカウント及びパスワードを含め、httpsによって保護されている。
fo_m4 起動時にオープンになっているTCPポートは80番(http)及び443番(https)のみである。
 ChromeOSのディスクはSTATEパーティションとROOTパーティションの構成となっている。STATEパーティションは主にユーザプロファイルやローカルに保存するデータなどが存在し、後に詳しく述べるようにデータはユーザごとに暗号化され保護される。一方、ROOTパーティションは読み込み専用でマウントされ※3、システムの改ざんは細かくチェックされる。
fo_m5 ユーザがGoogleアカウントで最初にログインしたタイミングで、OSはユーザのプロファイル情報等を、STATE領域の隠しディレクトリ(画面では/C-STATE/home/.shadow/)配下の、アカウント名をハッシュ化して作成したディレクトリに暗号化イメージファイルとして作成、保存する。先述したようにChromeOSを搭載したコンピュータが複数のユーザによって共用されることを想定し、この暗号化イメージファイルはユーザごとに作成され、プライバシは保護される。
 イメージファイルには、ChromeOSの核となるChromeブラウザが保存するブラウザの設定情報、Cookie、Historyなどの履歴情報や、シェルの実行履歴であるBash_historyなどが保管される。また、先述したChromeブラウザの設定情報や利用履歴、アカウントやパスワードなどの自動入力情報(ただしクレジットカード情報を除く)などは、Googleが提供するアカウント同期サービスを利用することにより、別のネットワーク環境でも利用することが可能である※4。
fo_m6 暗号化イメージファイルは、ログイン時にループデバイス(dm-crypt device)としてログインユーザのホームディレクトリとしてマウントされ、ユーザ固有のデータを保存する領域として利用される。イメージはユーザのログアウトのタイミングでアンマウントされ、更新された必要な情報を書き込み、不必要な情報のワイプや圧縮の処理を行い再び暗号化される。
 イメージの暗号化キーは、OSのインストール時に生成されるランダムキー+ (対応していればハードウェアベースのジェネレータ )から生成される。そして、暗号化キー自体はGoogleアカウントのパスワードハッシュによって暗号化されている。つまり、フォレンジック調査などでハードディスクに残されたユーザの挙動を確認したい場合には、該当ユーザによってログインされ、暗号化イメージが復号された状態でのフォレンジック、つまりライブフォレンジックによって保全や確認の作業を行う必要があるということだ。
 ChromeOS Flow VMWare版の、ユーザ暗号化領域のイメージファイルは圧縮された状態で134MB程度である。ただし、ChromeOSではローカルにコンテンツを保存し、オフラインで閲覧することも想定されているので、実際にChromeOSを搭載した製品では、ハードディスクの容量に応じて利用可能になると思われる。

 このように、ChromeOSではユーザのデータはOSによって手厚く保護され、利用するユーザは意識することなく暗号化の恩恵を受ける。ChromeOSはGoogleアカウントのみならず、他の認証システム、特にOpenIDに対応することを公言しており、実現すれば利便性は更に高くなる。
 もしかすると、10年といわずここ数年のうちに、ChromeOSを搭載したモバイル端末がクラウドに自由にアクセスし、ローカルのハードディスクに痕跡の残らない、しかも現在では想像もできないような高度なWebアプリケーションを活用する環境が実現するかもしれない。なお、ChromeOSの公式サイトには非常に質の高いドキュメントがそろっているので、興味のある方はぜひチェックしてみてほしい。今回は紹介できなかったが、セキュリティ機構に関するドキュメントも興味深く、これらを読めばChromeOSがいかにセキュアなOSであるかを理解いただけるだろう。
 Larry E. Danielの予測は多くの点でChromeOSが目指すものと合致する。もちろんChromeOSがどの程度のシェアを獲得するのかは未知数であるが、少なくとも自宅や職場に設置しているデスクトップ型のパソコン環境の一部は、いつでもどこでも利用できるクラウドやモバイルコンピュータをベースとしたものにシフトしていくのは間違いないように思える。
 Larryの予言がどうなるかはさておき、個人的には最後の「コンピュータフォレンジック産業は今後も成長する」という言葉に期待して、未来に希望を持ってあと10年はフォレンジックに関わる活動を続けていきたいと思う※5。


※1 個人的には、ChromeOSとAndroidは利用されるハードウェアではなく、ユーザの目的や用途によって選択されるようになるのではないかと考えている

※2 ちなみにChromeOSは2010年後半に無償(Ubuntuベースのオープンソースライセンス)でリリースされる予定であるが、それに向けてかどうかChromeブラウザは今後(7月末時点)6週に1回の驚異的なハイペースで新バージョンがリリースされるらしい
  http://jp.techcrunch.com/archives/20100722google-chrome-versions/

※3 マウントオプションの変更はログイン後手動でオプションを指定して再マウントをするかstartupscriptにより可能

※4 Google Chrome Help “Basic settings: Sync”に詳細が記載されている
  http://www.google.com/support/chrome/bin/answer.py?hl=en&answer=165139

※5 「“米国の”コンピュータフォレンジック産業は成長する」という意味ではないことを祈りつつ(汗)
当ブログはあくまで各個人の視点で書かれており、
記事の内容についてはネットエージェント株式会社の公式見解とは異なる場合があります。
カテゴリ別アーカイブ
タグクラウド
QRコード
QRコード