補足: ld -z relro でどこがreadonlyになるのか (とelfutilsについて)


前に書いたrelro記事の補足です。ld -z relro すると (.got 以外は) どこがreadonlyになるのか、について。


これは、readelf -S でセクション一覧を表示して、/proc//maps の出力とセクションのアドレスを見比べる方法で知ることができますが、elfutilsを使えばもっと簡単です。elfutilsの説明は、http://people.redhat.com/drepper/ の左側、"ELF" のところをクリックしていただき、"elfutils" という記事を探して読んでもらえばわかりますが、要するにUlrich Drepperさんの手による、binutilsのreplacementですね。ELFに特化している分、出力がより親切だったり読みやすかったりします。Fedoraには標準で含まれています。見当たらなければ、 # yum install elfutils elfutils-libs elfutils-libelf すればよいです。


elfutilsには、eu-readelf, eu-strip など、eu- からはじまるバイナリ閲覧操作関係のコマンドが含まれています。binutilsのreplacementということで、コマンドラインオプションも eu- なしのものと殆ど一緒のようです。また eu-findtextrel など、ちょっと便利*1なコマンドもいくつか含まれています。


で、本題。どこがreadonlyになっているかですが、eu-readelf によるとこんな感じだそうです。

% g++ -O2 -Wall -W -Wl,-z,relro,-z,now -o relro test_relro.cpp
% eu-readelf -l relro
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000120 0x000120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x000013 0x000013 R   0x1
        [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x002a21 0x002a21 R E 0x1000
  LOAD           0x002e78 0x0804be78 0x0804be78 0x00024c 0x00240c RW  0x1000
  DYNAMIC        0x002e90 0x0804be90 0x0804be90 0x0000f0 0x0000f0 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x000020 0x000020 R   0x4
  GNU_EH_FRAME   0x002640 0x0804a640 0x0804a640 0x00008c 0x00008c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x000000 0x000000 RW  0x4
  GNU_RELRO      0x002e78 0x0804be78 0x0804be78 0x000188 0x000188 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01      [RO: .interp]
   02      [RO: .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame .gcc_except_table]
   03      [RELRO: .ctors .dtors .jcr .dynamic .got] .data .bss
   04      [RELRO: .dynamic]
   05      [RO: .note.ABI-tag]
   06      [RO: .eh_frame_hdr]
   07
   08      [RELRO: .ctors .dtors .jcr .dynamic .got]

"Section to Segment mapping" に注目してください。.data と .bss 以外はreadonlyになっているようですね*2。relroしないと次の出力になります。

% g++ -O2 -Wall -W -o no_relro test_relro.cpp
% eu-readelf -l no_relro
(略)
 Section to Segment mapping:
  Segment Sections...
   00
   01      [RO: .interp]
   02      [RO: .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame .gcc_except_table]
   03      .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
   04      .dynamic
   05      [RO: .note.ABI-tag]
   06      [RO: .eh_frame_hdr]
   07

.ctors .dtors .jcr .dynamic .got .got.plt .dynamic に書き込み可能です。そのため、前に書いた .got に書き込む攻撃の他、.dtorsをいじる攻撃も可能です。この、.dtors overwrite と呼ばれる(古典的)攻撃手法についても、リハビリの一環としてまとめておきます。こちら

*1:eu-findtextrel: TEXTRELなDSO/PIEを探すコマンド。readelf -d するより簡単でよい。text relocationは、起動が遅くなる原因になる/メモリを無駄に食う原因になるほか、SELinuxによるプロセス保護に穴をあけることになるので、探して駆逐するとよい。って説明になってませんが。TEXTRELについてはこちらeu-findtextrelを含む詳細についてはこちらが良い資料です

*2:あ、mapsのほうは確認してないので(ぉぃ)、間違ってたら誰か突っ込んでください