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

2010年08月

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 「“米国の”コンピュータフォレンジック産業は成長する」という意味ではないことを祈りつつ(汗)

セキュリティ&プログラミングキャンプ2010に参加しました

 みなさん、こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。今回は、昨日まで行われていた、IPA主催によるセキュリティ&プログラミングキャンプ2010について紹介させて頂きます。

-----

P1000610 2004年から開催されているセキュリティ&プログラミングキャンプは、毎年夏に4泊5日の合宿形式で開催されるのですが、なんと今年は台風の影響により、7回目の開催にして初の開始時間の延期という事態から始まりました。

 私はセキュリティコースのWebセキュリティ組で講師を務めたのですが、今年は、弊社からは他に社長の杉浦、取締役の愛甲がそれぞれネットワークセキュリティ組、ソフトウェアセキュリティ組に講師として参加致しました。初日には、参加者や講師の親睦を深めるために名刺交換ゲームなどが行われました。少しでも和んだ雰囲気を出すために、LACの川口さんやMBSDの国分さんは浴衣姿で参加されていました。同じセキュリティ業界同士とはいえ、なかなかゆっくりと顔を合わせることの少ない講師間でもこの時間は貴重な交流時間となります。
P1000616P1000623 左から弊社の杉浦、LAC川口さん、MSBD国分さんです。また、会場にはボウズマンも駆けつけ、参加者の人気を集めていました。
 今回、例年にないセキュリティ&プログラミングキャンプでの新しい取り組みのひとつとして、講義の総まとめにCTFという競技を取り入れました。CTF競技は、DEFCONをはじめとしてセキュリティ実務者が多く集うイベントで行われる競技であり、Wikipediaなどには、主にゲームの一種としての記述しかありませんが、この業界でCTFと言えば謎解きで行う点取り合戦や、バトルフィールドネットワークに自前のサーバーを置き、それを攻略されないように守りながら相手のサーバーを攻める攻防戦などのことです。
P1000698 遊びの要素を採り入れつつ、さまざまな技術的知見を身につけようというもので、今回の競技では、各クラスで学んだWebアプリケーションの脆弱性に関する知識やネットワークに関する知識、あるいはソフトウェアの解析技術などだけではなく、フォレンジックや暗号、さらにはセキュリティに関連するトリビアなど広範でマニアックな知識も要求される問題が合計78問出題されました。
 私もクロスサイトスクリプティングに関する問題を出題しました。いくつかのパターンで3種類ほど出題したのですが、参加者には少し難しすぎたようです…。
P1000711P1000704 参加者は6人ずつの5チームに分かれ、7時間ほどの競技時間中で次々と問題を解いていき、最終日に各チームごとに解いた問題について解説を行ってもらいます。こうして、濃厚な5日間を過ごし閉講式を終えると、講師陣や各出版社の方々からご寄贈頂いた技術書が参加者に配布されます。これは、ただ単純にプレゼントということではなく、セキュリティ&プログラミングキャンプを終えたことをゴールとするのではなく、これをスタートに今後も技術の向上に励み続けてほしいという講師の願いが込められています。

 私個人としても、またネットエージェント株式会社としても、このように将来のセキュリティ業界を担う若者の育成に深く関われることを非常に誇りに思っています。今後も、日本のセキュリティ技術の向上について、様々な形で教育等にも関わっていきたいと考えておりますので、よろしくお願いいたします。

私がセキュリティの世界に挑戦した理由 ~愛甲健二~

 こんにちは、愛甲です。今日は月第一週目ということで(関係ないですが(汗))「私がセキュリティの世界に挑戦した理由 ~愛甲編~」を書かかせていただきたいと思います。
 と言っても、正直なところ、私の場合、何かひとつこれいったものがあってセキュリティの世界に足を踏み入れたわけではありません(汗)。なのであまり書くこともないのですが、それでもあえて何かひとつ挙げるとするならば、アセンブラを必要とするソフトウェア開発会社が他になかったからでしょうか。

-----

 私が初めてパソコンを触ったのは、おそらく小学5~6年生の頃だったと思います。当時、兄がWindows3.1の載ったノートPCを持っており、それをたまたま使わせてもらったのがきっかけです。ただ、当時はペイントブラシ(現ペイント)というWindowsに標準でインストールされている絵描きソフトで好きな絵を書く程度で、コンピュータについての興味も特にありませんでした。また、小学生の時にパソコンを初めて使ったのは事実ですが、実際のところは1年に数回程度、兄が実家に帰ってきた際に見よう見まねでやっていただけで、特に何かを学んでいたわけでも、特別な興味を持っていたわけでもありません。そういう意味では、本当にコンピュータを使い始めたのは中学に入ってからになります。
 私が中学生になった頃、世間ではWindows95、Windows98などがリリースされていた時期であり、兄のパソコンもWindows95になっていました。しかし、私は相変わらずパソコンに興味はなく、中学では学習塾に通い始め、バスケットボール部に所属していたこともあり、パソコンに触れる時間もほとんどありませんでした。おそらく、もしこのまま高校生になっていたら、私はコンピュータについて興味を持つことも、セキュリティについて学ぶこともなかったと思います。

 中学2年になった頃、叔父から古いノートPCをもらいました。叔父としては「捨てようと思っているんだけど、もし欲しいなら…」という程度だったと思います。ただ実際は、当時の私も特別欲しかったわけでもなく、もらえるならもらっておこうという感覚だったと思います。ノートPCにはMS-DOSとCコンパイラが入っており、家には兄が大学で使っていたC言語の入門書がありました。今思えば、これらがすべてのきっかけだったかもしれません。
 最初のうちは入門書に書かれてあるプログラムを書き写すだけでしたが、高校生になる頃には、自由に自分の好きなプログラムを作れるようになっていました。といっても、完全に独学であるため、すべての変数をグローバルで定義してたり、数千行にも及ぶ関数を平気で作成したりとかなりフリーダムなコードを書いていましたが…(汗)。ただ正直なところ、この時期は毎日がとても楽しかったのを覚えています。実装の時間がもったいないので、学校の授業中にアルゴリズムを考え、家に帰って実装し、次の日、今日考えなければならないプログラムをまとめながら登校します。高校は一応進学校でしたが、私は受験にまったく興味がありませんでしたし、学校の勉強もほとんどしませんでした。もちろん成績は落ちていく一方で、実際問題、赤点連発して留年しそうになったりもしましたが、プログラミングの楽しさと比べると、学校の授業はどうしても見劣りしてしまい、やる気が起きませんでした。
 中学、高校と少しずつコンピュータに触れる機会が増えていき、それなりに知識もついてきました。特に最初に学んだコンピュータについての知識がWindowsの使い方ではなく、C言語であったことも幸運だったかもしれません。大学に進み、一人暮らしを始めると同時にWindowsXPが搭載されたノートPCを購入し、常時接続のインターネット環境を契約しました。ここで初めて本格的にWindowsプログラミングや、Windowsの使い方そのものについて学び始めるのですが、正直なところ、当時はWindowsプログラミングがあまり面白いとは思えませんでした。なぜならWindowsが提供するAPI群はどれも主要な部分を隠すようにラップされていたからです。なぜCreateProcess関数を呼び出すと新しいプロセスを生成できるのか? そういった疑問を持ち始めました。
 MS-DOSは低レイヤーな部分が比較的分かりやすかったのに対し、Windowsは複雑で難しいものでした。そうして、私は少しずつアセンブラの世界に興味を持ち始めました。今になって考えてみると、最初にWindows95を見たときは特に何も思わなかったのに、MS-DOSとCコンパイラに出会った際に、それらがすごく面白そうに感じたのは、そもそも私がプログラミングではなく、コンピュータそのものの動作原理にこそ興味があったからかもしれません。

 そうして私はOSのシステム構造やアセンブラといった世界に少しずつはまっていきました。しかし、こういった知識や技術が一般的なソフトウェア開発の現場において役立つかと言うと、正直、微妙なところです。確かに知らないよりは知っていた方がよいのですが、それよりも大切なことは多々あります。アセンブラが分からなくともソフトウェアは開発できますが、オブジェクト指向を知らずして大規模な開発プロジェクトは動かせません。ソフトウェア開発の現場においては、アセンブラはもはや過去の技術であり、必ずしも覚える必要のないものでした。
 大学3年になり、本格的に就職について考えるようになってから、私はアセンブラでの求人情報を検索しました。しかし、当時はほとんどのソフトウェア企業が.NETやJavaを使っており、アセンブラの出来るエンジニアを募集しているところはありませんでした。私がネットエージェントで働いている理由は、ある意味、2006年の時点において「アセンブラが書ける方」を募集していたからでしかないかもしれません(笑)。こうして私はセキュリティの世界へ進みました。
 ネットエージェントに入社して4年が経過しますが、この業界で思うことは、セキュリティエンジニアリングは常に低いレイヤーの知識が不可欠であるように感じます。バッファオーバーフローを初めとするメモリ破壊系の脆弱性はアセンブラレベルでの理解が必要ですし、Webアプリ脆弱性を代表するSQLインジェクションやXSSは、データベースやブラウザのコアな仕様を熟知している必要があります。セキュリティ問題の多くは、普通の開発者には不必要な低いレイヤーのことばかりです。
 プログラミングが好きで、何かのソフトウェアを開発することが好きならば、それは開発者になるべきです。しかし、OSのカーネルやアセンブラに興味があったり、文字コードやブラウザの細かな挙動に興味があったり、ストレージに展開されているファイルシステムに興味があったりするならば、セキュリティ業界に挑戦するのも楽しいかもしれません。そしてそれらを踏まえての製品開発は、通常のソフトウェア開発とはまた一味違った楽しみがあります。

●お知らせ
 ネットエージェントではこの夏、8月23~8月24日にかけて、インターンシップを開催することとなりました。特に以下の内容に興味がある学生のみなさんのご参加をお待ちしております。
 ・HTTP、SMTP、DNS等、現在のインターネットを支えている根幹技術に興味がある
 ・tcpdumpで一日中パケットを眺めていても飽きない
 ・C言語やアセンブラが大好き
 ・脆弱性を作らない実装、開発フロー、コードレビュー等に関心がある
 詳細はこちらをご覧ください。
記事検索
月別アーカイブ
QRコード
QRコード