この記事は、 Holger Unterbrink 氏 の協力のもと、 Marcin Noga 氏 により執筆されました。
概要
クラッシュのトリアージは複雑で時間のかかるプロセスですが、適切なツールを使用し、最適なアプローチで取り組めば、このプロセスは多少なりとも簡単になり、必要な時間も短縮することができます。この記事では、トリアージの戦略とツールセットについて、脆弱性に関する2つのクラスを例に説明します。
- スタック ベースのバッファ オーバーフロー
- ヒープ ベースのバッファ オーバーフローまたはヒープ破壊
ここでは、Talos の Marcin Noga が今年初めに発見した実際の脆弱性を例として使用します。
LexMark Perceptive Document Filters XLS Convert Code Execution Vulnerability
Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability
使用する予定のツールは、以下のとおりです。
- Valgrind
- Gdb
- Peda
- DUMA
- IDA
- RRデバッガ
スタック ベースのバッファ オーバーフローのトリアージ
クラッシュの例として LexMark Perceptive Document Filters XLS Convert Code Execution Vulnerability を使い、最初の分析を進めることにします。はじめに、gdb でどのように表示されるかを確認しておきましょう。
icewall@ubuntu:~/bugs/lexmark$ gdb --nx --quiet --args ./convert config
Reading symbols from ./convert...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/icewall/bugs/lexmark/convert config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
0xffffffffff601000 in ?? ()
(gdb) bt 10
#0 0xffffffffff601000 in ?? ()
#1 0x000001c420000000 in ?? ()
#2 0x0000000041c50000 in ?? ()
#3 0x01c700000000c1c6 in ?? ()
#4 0x000001c800000000 in ?? ()
#5 0x0000000001c90000 in ?? ()
#6 0x01cb0000000001ca in ?? ()
#7 0x000001cc00002535 in ?? ()
#8 0x0000000001cd0008 in ?? ()
#9 0xc1cf00000000aece in ?? ()
(More stack frames follow...)
(gdb) x /10i $rip
=> 0xffffffffff601000: Cannot access memory at address 0xffffffffff601000
スタックが上書きされ、コール スタックを破壊していることがわかります。このため、クラッシュを実際に起こした関数を確認できません。先に進む前に、gdb にデフォルトで表示される情報について考えてみましょう。率直に言って、大した情報はありません。特に、長年アプリケーション コードやマルウェアを OllyDbg でデバッグしてきた方なら、gdb にデフォルトで表示される情報は貧弱に見えるかもしれません。しかし、これを改善する方法があります。
その1つが PEDA です。
レジスタ、スタック、およびコードに関して表示される情報がより充実していることに加えて、トリアージやエクスプロイト開発中に便利な機能が備わっています。
peda の機能については、ヘルプをお読みください。良いヒントが提供されています。クラッシュをまた再現してみましょう。ただし今度は、gdb に peda をロードしておきます。
gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x6732c0 --> 0x7ffff40b2eb0 --> 0x7ffff375dfb0 (<common::IRenderable::SetZIndex(int)>: mov DWORD PTR [rdi+0x8],esi)
RBX: 0x199000000000198
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffffffed110 ("ABCD", 'A' <repeats 39 times>)
RDI: 0x675ea8 ("ABCD", 'A' <repeats 39 times>)
RBP: 0x19a00000000
RSP: 0x7ffffffed220 --> 0x1c420000000
RIP: 0xffffffffff601000
R8 : 0x675ea8 ("ABCD", 'A' <repeats 39 times>)
R9 : 0x7ffff69417b8 --> 0x679770 --> 0x0
R10: 0x7ffffffecfb0 --> 0x44 ('D')
R11: 0x7ffff670a500 --> 0xfffc7110fffc6f90
R12: 0x19b0000
R13: 0x1bf40000003019c
R14: 0x1c0001e001c
R15: 0x4201000001c10000
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xffffffffff601000
[------------------------------------stack-------------------------------------]
0000| 0x7ffffffed220 --> 0x1c420000000
0008| 0x7ffffffed228 --> 0x41c50000
0016| 0x7ffffffed230 --> 0x1c700000000c1c6
0024| 0x7ffffffed238 --> 0x1c800000000
0032| 0x7ffffffed240 --> 0x1c90000
0040| 0x7ffffffed248 --> 0x1cb0000000001ca
0048| 0x7ffffffed250 --> 0x1cc00002535
0056| 0x7ffffffed258 --> 0x1cd0008
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xffffffffff601000 in ?? ()
かなり良くなりましたが、ここで、中心となる問題に立ち返ってみましょう。オーバーフローが発生した場所と原因を、どのように特定するか。どうすれば、オーバーフローが発生する前にアプリケーションを停止できるのか。
考えられるアプローチは、いくつかあります。
たとえば、ASLR をオフにすることが考えられます(ターゲットでサポートされている場合)。また、ライト ハードウェア ブレークポイントを設定するというアプローチもあります。
例:スタック アドレスで watch *0x7ffffffed218 を実行します(ここに、書き換えられた戻りアドレスがあります)。さらに、条件付きチェックをいくつか行います。しかし、このアプローチにもまだ問題は残っており、目的の宛先アドレスに到達するまで何度も実行を止める必要があります。
トレースはどうでしょうか。Peda では、以下のようなトレース メソッドがいくつか利用できます。
gdb-peda$ trace
trace tracecall traceinst
しかし動作は遅く、ターゲットが巨大で複雑な場合、目指す脆弱性をトリガするまでに相当な時間がかかるでしょう。もちろん、より効率的なトレーサを選択する手もありますが、今探しているのは更にスマートな解決策です。
ここでは、valgrind というツールをご紹介します。優秀なツールで、特にトリアージの前段階で役に立ち、分析を続ける足がかりを見つけてくれます。
valgrind によるインストルメンテーションの下でターゲットを実行すると、次の結果が観察できます。
注:スイッチ「exp. sgcheck」はオンにして、スタック破壊が検出できるようにしておきます。
詳細については、http://valgrind.org/docs/manual/sg-manual.htmlをご覧ください。
icewall@ubuntu:~/bugs/lexmark$ valgrind --tool=exp-sgcheck ./convert config/
==13227== exp-sgcheck, a stack and global array overrun detector
==13227== NOTE: This is an Experimental-Class Valgrind Tool
==13227== Copyright (C) 2003-2013, and GNU GPL'd, by OpenWorks Ltd et al.
==13227== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==13227== Command: ./convert config/
==13227==
==13227==
==13227== Process terminating with default action of signal 11 (SIGSEGV)
==13227== Bad permissions for mapped region at address 0xFFFFFFFFFF601000
==13227== at 0xFFFFFFFFFF601000: ???
==13227== by 0x1C41FFFFFFF: ???
==13227== by 0x41C4FFFF: ???
==13227== by 0x1C700000000C1C5: ???
==13227== by 0x1C7FFFFFFFF: ???
==13227== by 0x1C8FFFF: ???
==13227== by 0x1CB0000000001C9: ???
==13227== by 0x1CC00002534: ???
==13227== by 0x1CD0007: ???
==13227== by 0xC1CF00000000AECD: ???
==13227== by 0x201D6FFFFFFFF: ???
==13227== by 0xE000E01FEFFFF: ???
==13227==
==13227== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 16 from 15)
Segmentation fault
「exp-sgcheck」ツールはベータ リリースの段階です。今回のケースに valgrind を使用しても残念ながら多くの情報は得られそうにありません。さて、どうしましょうか。「タイム トラベル」デバッガ rr が役に立ちます。
簡単に言えば、「rr」を使うことによりコードを逆実行することが可能になります。つまり、実行形式のコードに対して、デバッグ中に実行ポイントを前後に移動させることができるようになるのです。これは、不正に書き換えられた戻りアドレスで RET 命令が実行された特定コードに戻ることができるため、非常に便利です。クラッシュが起こった関数を特定できたので、分析を進めることができます。
まず、実行結果を記録する必要があります。
icewall@ubuntu:~/bugs/lexmark$ rr record ./convert config/
rr: Saving the execution of `./convert’ to trace directory `/home/icewall/.local/share/rr/convert-15′.
記録された実行結果を使って、クラッシュの根本原因について分析を始めることができます。
icewall@ubuntu:~/bugs/lexmark$ rr replay
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0xffffffffffffffff
RDX: 0x0
RSI: 0x0
RDI: 0x0
RBP: 0x0
RSP: 0x7ffe8e800690 --> 0x2
RIP: 0x7f033d8882d0 --> 0x3768e8e78948
R8 : 0x0
R9 : 0x0
R10: 0x0
R11: 0x246
R12: 0x0
R13: 0x0
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7f033d8882c0: ret
0x7f033d8882c1: nop WORD PTR cs:[rax+rax*1+0x0]
0x7f033d8882cb: nop DWORD PTR [rax+rax*1+0x0]
=> 0x7f033d8882d0 <_start>: mov rdi,rsp
0x7f033d8882d3: call 0x7f033d88ba40 <_dl_start>
0x7f033d8882d8 <_dl_start_user>: mov r12,rax
0x7f033d8882db: mov eax,DWORD PTR [rip+0x221b17] # 0x7f033daa9df8 <_dl_skip_args>
0x7f033d8882e1: pop rdx
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e800690 --> 0x2
0008| 0x7ffe8e800698 --> 0x7ffe8e801223 ("./convert")
0016| 0x7ffe8e8006a0 --> 0x7ffe8e80122d --> 0x2f6769666e6f63 ('config/')
0024| 0x7ffe8e8006a8 --> 0x0
0032| 0x7ffe8e8006b0 --> 0x7ffe8e801235 ("XDG_VTNR=7")
0040| 0x7ffe8e8006b8 --> 0x7ffe8e801240 ("XDG_SESSION_ID=c2")
0048| 0x7ffe8e8006c0 --> 0x7ffe8e801252 ("CLUTTER_IM_MODULE=xim")
0056| 0x7ffe8e8006c8 --> 0x7ffe8e801268 ("SELINUX_INIT=YES")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
(rr) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>: 0x90909090c3087789)
RBX: 0x199000000000198
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffe8e7ef830 ("ABCD", 'A' <repeats 39 times>)
RDI: 0x1476eb8 ("ABCD", 'A' <repeats 39 times>)
RBP: 0x19a00000000
RSP: 0x7ffe8e7ef940 --> 0x1c420000000
RIP: 0xffffffffff601000
R8 : 0x1476eb8 ("ABCD", 'A' <repeats 39 times>)
R9 : 0x7f033c1c17b8 --> 0x147a780 --> 0x0
R10: 0x7ffe8e7ef6d0 --> 0x44 ('D')
R11: 0x7f033bf8a500 --> 0xfffc7110fffc6f90
R12: 0x19b0000
R13: 0x1bf40000003019c
R14: 0x1c0001e001c
R15: 0x4201000001c10000
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xffffffffff601000
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef940 --> 0x1c420000000
0008| 0x7ffe8e7ef948 --> 0x41c50000
0016| 0x7ffe8e7ef950 --> 0x1c700000000c1c6
0024| 0x7ffe8e7ef958 --> 0x1c800000000
0032| 0x7ffe8e7ef960 --> 0x1c90000
0040| 0x7ffe8e7ef968 --> 0x1cb0000000001ca
0048| 0x7ffe8e7ef970 --> 0x1cc00002535
0056| 0x7ffe8e7ef978 --> 0x1cd0008
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
(rr)
「正常」な gdb を実行した場合と比べても違いがないように見えますが、「reverse-」で始まるコマンド セットが新たに使用されている点が異なります。
(rr) reverse-
reverse-continue reverse-finish reverse-next reverse-nexti reverse-search reverse-step reverse-stepi
RET が実行される直前(1 命令前)に戻るのであれば、reverse-stepi (rsi) コマンドを実行します。
(rr) rsi
Warning: not running or target is remote
0x00007f03390c55e6 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
(rr) context
$7 = 0x3f69
[----------------------------------registers-----------------------------------]
RAX: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>: 0x90909090c3087789)
RBX: 0x199000000000198
RCX: 0x56 ('V')
RDX: 0x2b ('+')
RSI: 0x7ffe8e7ef830 ("ABCD", 'A' )
RDI: 0x1476eb8 ("ABCD", 'A' )
RBP: 0x19a00000000
RSP: 0x7ffe8e7ef938 --> 0xffffffffff601000
RIP: 0x7f03390c55e6 --> 0x15850ff0163d66c3
R8 : 0x1476eb8 ("ABCD", 'A' )
R9 : 0x7f033c1c17b8 --> 0x147a780 --> 0x0
R10: 0x7ffe8e7ef6d0 --> 0x44 ('D')
R11: 0x7f033bf8a500 --> 0xfffc7110fffc6f90
R12: 0x19b0000
R13: 0x1bf40000003019c
R14: 0x1c0001e001c
R15: 0x4201000001c10000
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7f03390c55e0: pop r13
0x7f03390c55e2: pop r14
0x7f03390c55e4: pop r15
=> 0x7f03390c55e6: ret
0x7f03390c55e7: cmp ax,0xf016
0x7f03390c55eb: jne 0x7f03390c5406
0x7f03390c55f1: mov rax,QWORD PTR [rbp+0x0]
0x7f03390c55f5: lea rbx,[rsp+0x50]
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef938 --> 0xffffffffff601000
0008| 0x7ffe8e7ef940 --> 0x1c420000000
0016| 0x7ffe8e7ef948 --> 0x41c50000
0024| 0x7ffe8e7ef950 --> 0x1c700000000c1c6
0032| 0x7ffe8e7ef958 --> 0x1c800000000
0040| 0x7ffe8e7ef960 --> 0x1c90000
0048| 0x7ffe8e7ef968 --> 0x1cb0000000001ca
0056| 0x7ffe8e7ef970 --> 0x1cc00002535
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
これにより、以前よりも多くの情報が得られたことは間違いありません。オーバーフローが起きた関数の名前が判別しました。
reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from
分析を進める中で、重要な質問を考えてください。
「0x7ffe8e7ef938」に格納された戻りアドレスは、いつ書き換えられたのでしょうか。
再び rr を使用します。問題のスタック アドレスにライト ハードウェア ブレークポイントを設定し、戻ってみましょう。これを行うには、「watch」コマンドと reverse-continue (rc) を使用します。
(rr) watch *0x7ffe8e7ef938
Hardware watchpoint 1: *0x7ffe8e7ef938
(rr) rc
Continuing.
Warning: not running or target is remote
Hardware watchpoint 1: *0x7ffe8e7ef938
Old value = <unreadable>
New value = 0x390bd6a8
__memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
208 ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S: No such file or directory.
先が見えてきました。コール スタックを確認すると、以下のようになっています。
(rr) bt 5
#0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
#1 0x00007f033a20bf67 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2 0x00007f033a1e131d in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3 0x00007f03390c5606 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#4 0x00007f03390bd6a8 in reader::escher::MsoDispatcher::Dispatch(ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
(以下、スタック フレームが続きます)
オーバーフローが memcpy 内で発生し、以下のポイントから始まったことがわかります。
ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int)
Read の(さらには memcpy の)「size」パラメータがローカル バッファのサイズを上回っていると想定することができます。
これらのパラメータの値を確認するには、以下の関数が呼び出される前のポイントに移動します。
ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int)
ここで、「reverse-finish」コマンドを使用します。
(rr) reverse-finish
Run back to call of #0 __memmove_ssse3_back () at ../sysdeps/x86_64/multiarch/memcpy-ssse3-back.S:208
Warning: not running or target is remote
0x00007f033a20bf62 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
(rr) reverse-finish
Run back to call of #0 0x00007f033a20bf62 in ISYS_NS::CPagedMemoryStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
Warning: not running or target is remote
0x00007f033a1e131a in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
(rr) reverse-finish
Run back to call of #0 0x00007f033a1e131a in ISYS_NS::CDataReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
Warning: not running or target is remote
0x00007f03390c5603 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
(rr) context
$15 = 0x3f69
[----------------------------------registers-----------------------------------]
RAX: 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
RBX: 0x7ffe8e7ef830 --> 0x0
RCX: 0x0
RDX: 0x400
RSI: 0x7ffe8e7ef830 --> 0x0
RDI: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
RBP: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
RSP: 0x7ffe8e7ef7e0 --> 0x7f0338df41a8 --> 0xb00120002a9ee
RIP: 0x7f03390c5603 --> 0x28bda89481050ff
R8 : 0x3
R9 : 0x5
R10: 0x7ffe8e7ef6d0 --> 0x0
R11: 0x7f033bf8a550 --> 0xfffcb240fffcabbe
R12: 0x85a
R13: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>: 0x90909090c3087789)
R14: 0x7ffe8e7ef950 --> 0x400f0160803
R15: 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>: 0x90909090c3087789)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7f03390c55fa: mov edx,DWORD PTR [rsi+0x4]
0x7f03390c55fd: mov rdi,rbp
0x7f03390c5600: mov rsi,rbx
=> 0x7f03390c5603: call QWORD PTR [rax+0x10]
0x7f03390c5606: mov rdx,rbx
0x7f03390c5609: mov eax,DWORD PTR [rdx]
0x7f03390c560b: add rdx,0x4
0x7f03390c560f: lea ecx,[rax-0x1010101]
Guessed arguments:
arg[0]: 0x14771c0 --> 0x7f033bdd7cf0 --> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
arg[1]: 0x7ffe8e7ef830 --> 0x0
arg[2]: 0x400
[------------------------------------stack-------------------------------------]
0000| 0x7ffe8e7ef7e0 --> 0x7f0338df41a8 --> 0xb00120002a9ee
0008| 0x7ffe8e7ef7e8 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f
0016| 0x7ffe8e7ef7f0 --> 0x7f0338df60f8 --> 0xb00220008b030
0024| 0x7ffe8e7ef7f8 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f
0032| 0x7ffe8e7ef800 --> 0xffffffff
0040| 0x7ffe8e7ef808 --> 0x1443610 --> 0x7f0338d4f000 --> 0x10102464c457f
0048| 0x7ffe8e7ef810 --> 0x7f0338ddcb98 --> 0xb00220008b0b2
0056| 0x7ffe8e7ef818 --> 0x14742d0 --> 0x7f0339932eb0 --> 0x7f0338fddfb0 (:IRenderable::SetZIndex(int)>: 0x90909090c3087789)
[------------------------------------------------------------------------------]
オーバーフローが起こった呼び出し関数が見つかりました。スタックが破壊されていないことも確認できます。
(rr) bt
#0 0x00007f03390c5603 in reader::escher::MsofbtDggContainer::Handle(reader::escher::MSOFBH const&, ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#1 0x00007f03390bd6a8 in reader::escher::MsoDispatcher::Dispatch(ISYS_NS::CDataReader&, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#2 0x00007f0339029a5b in reader::excel97_2003::Workbook::Handle(reader::excel97_2003::BIFFRECORDTYPE const&, ISYS_NS::CDataReader&) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#3 0x00007f03390a9c4a in reader::excel97_2003::IBiffEngine::Parse(ISYS_NS::CDataReader&) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#4 0x00007f0339028689 in reader::excel97_2003::Workbook::Workbook(ISYS_NS::CStream*, IStorage*, wchar_t const*, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#5 0x00007f033941e31b in ISYS_NS::LibraryHD::CDocument::openExcel(ISYS_NS::CStream*, IStorage*, bool) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#6 0x00007f033941f138 in ISYS_NS::LibraryHD::CDocument::open(IGR_Stream*, int, wchar_t const*) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#7 0x00007f033941a74a in ISYS_NS::LibraryHD::IGR_HDAPI_Open(IGR_Stream*, int, wchar_t const*, void**, wchar_t*) () from /home/icewall/bugs/lexmark/libISYSreadershd.so
#8 0x00007f0339c80f07 in ISYS_NS::exports::IGR_Open_File_FromStream(wchar_t const*, wchar_t const*, ISYS_NS::CStream*, bool, ISYS_NS::exports::Ext_Open_Options*, int, wchar_t const*, int*, int*, void**, int*, int, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#9 0x00007f0339c85dff in ISYS_NS::exports::IGR_Open_Stream_Ex(IGR_Stream*, int, unsigned short const*, int*, int*, void**, Error_Control_Block*) ()
from /home/icewall/bugs/lexmark/libISYSreaders.so
#10 0x00007f033d44d99b in IGR_Open_Stream_Ex () from /home/icewall/bugs/lexmark/libISYS11df.so
#11 0x00000000004091e0 in processStream(IGR_Stream*, long long&, ToXHTML&, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#12 0x000000000040ab7b in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#13 0x000000000040b7a0 in main ()
#14 0x00007f033be24f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7ffe8e800698, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffe8e800688)
at libc-start.c:287
#15 0x00000000004089e9 in _start ()
また、peda により、その関数は 3 つのパラメータを取ることがわかります。
推測される引数は以下のとおりです。
arg[0]: 0x14771c0 –> 0x7f033bdd7cf0 –> 0x7f033a1e24b0 (:CDataReader::~CDataReader()>: 0x5c8948f8246c8948)
arg[1]: 0x7ffe8e7ef830 –> 0x0
arg[2]: 0x400
arg[0] : is a this pointer on `CDataReader` object.
arg[1]: some buffer located on stack
arg[2]: integer, which is probably size of data which should be copied
ここから先は難しくありません。必要なことは、「0x400 size」値のソースを見つけ、エクスプロイトの中でオーバーフロー中にファイルからどのデータ(オフセット)が使用されたかを突き止めることです。
それについては、もう 1 つの例を調べてみます。
ヒープ破壊のトリアージ
分析を行う例として、次の脆弱性を使用します。
Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability
クラッシュの根本原因については、このアドバイザリに詳しく説明されています。ここでは、以下のような「マジック」行について説明します。
>>> print allocations[‘0x010dcf50’][“stack”]
または
(gdb) heap /b $rdi
[In-use]
[Address] 0x63ae90
[Size] 40
[Offset] +0
また、他の便利なツールやテクニックについても紹介します。脆弱性に詳しくない方は、先に進まれる前にまずこのアドバイザリを一読されることを強くお進めします。
このアドバイザリによれば、「valgrind」は以下のような情報が提供される場合に大変便利であることがわかります。
- メモリ破壊が起こった場所、およびコール スタック情報
- バッファ割り当てに使用されたバイト数、およびスタック コール情報
このような情報があれば、分析を進めるうえでの良い出発点となりますが、ここで疑問となるのは、valgrind を使わずに上記以上の情報が得られるかということです。答えは「はい」です。
ヒープ破壊が起きるポイントを見つけるには、ヒープをインストルメントする必要があります。Windows の場合、別途用意したツールがない場合は、「GFlags」ツールを使うことになります。このツールは、適切なヒープ インストルメンテーションを有効にします。
https://msdn.microsoft.com/en-us/library/windows/hardware/ff549561(v=vs.85).aspx
Linux では、同様の機能を備えたソリューションの1つに「libduma」があります。
http://duma.sourceforge.net/
gdb に DUMA をロードするスクリプトは、以下のようになります。
icewall@ubuntu:~/bugs/lexmark$ cat .duma
define duma
set exec-wrapper env LD_PRELOAD=/usr/lib/libduma.so.0.0.0
echo Enabled DUMA\n
end
duma をインストールし、このスクリプトを実行した状態で、バグが混入したアプリケーションを再実行し、どこで停止するかを確かめることにしましょう。
icewall@ubuntu:~/bugs/lexmark$ ./run ~/Advisories/isys/CBFF/Lexmark_Perceptive_Document_Filters_CBFF_Integer_overflow_advisory_POC.bin
Reading symbols from ./convert...(no debugging symbols found)...done.
gdb-peda$ source .duma
gdb-peda$ duma
Enabled DUMA
gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert /home/icewall/bugs/lexmark/config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
DUMA 2.5.15 (shared library, NO_LEAKDETECTION)
Copyright (C) 2006 Michael Eddington <meddington@gmail.com>
Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7ffff3efb800 --> 0x0
RCX: 0xd0
RDX: 0x1
RSI: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0
RDI: 0x7ffff3efb800 --> 0x0
RBP: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0
RSP: 0x7fffffff2650 --> 0x215
RIP: 0x7ffff7bcb4d8 (mov BYTE PTR [rbx+rax*1],cl)
R8 : 0x0
R9 : 0x1
R10: 0x0
R11: 0x246
R12: 0x215
R13: 0x20000
R14: 0x215
R15: 0x7ffff3efb800 --> 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7bcb4cb: nop DWORD PTR [rax+rax*1+0x0]
0x7ffff7bcb4d0: movzx ecx,BYTE PTR [rbp+rax*1+0x0]
0x7ffff7bcb4d5: add edx,0x1
=> 0x7ffff7bcb4d8: mov BYTE PTR [rbx+rax*1],cl
0x7ffff7bcb4db: mov eax,edx
0x7ffff7bcb4dd: cmp r12,rax
0x7ffff7bcb4e0: ja 0x7ffff7bcb4d0
0x7ffff7bcb4e2: mov rax,rbx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff2650 --> 0x215
0008| 0x7fffffff2658 --> 0x7ffff3601fb8 --> 0x7ffff6346430 --> 0x7ffff4778da0 (<ISYS_NS::CBufferedReader::~CBufferedReader()>: mov QWORD PTR [rsp-0x8],rbp)
0016| 0x7fffffff2660 --> 0x215
0024| 0x7fffffff2668 --> 0x7ffff47793e5 (jmp 0x7ffff4779369)
0032| 0x7fffffff2670 --> 0xffff2a08
0040| 0x7fffffff2678 --> 0x1
0048| 0x7fffffff2680 --> 0x7ffff3424d38 --> 0x7ffff6345530 --> 0x7ffff4747060 (<ISYS_NS::docfile::CIStorageBase::~CIStorageBase()>: push rbp)
0056| 0x7fffffff2688 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
gdb-peda$ bt
#0 0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
#1 0x00007ffff47793e5 in ISYS_NS::CBufferedReader::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2 0x00007ffff477f8a8 in ISYS_NS::CIGRStreamStream::Read(void*, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3 0x00007ffff4741f0b in ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(unsigned int, void*, bool, unsigned int*) () from /home/icewall/bugs/lexmark/libISYSshared.so
#4 0x00007ffff4742f0e in ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() () from /home/icewall/bugs/lexmark/libISYSshared.so
#5 0x00007ffff4746ca5 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#6 0x00007ffff4748d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#7 0x00007ffff40b6629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#8 0x00007ffff40bce86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#9 0x00007ffff41e722c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#10 0x00007ffff79bc138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#11 0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#12 0x000000000040b7a0 in main ()
#13 0x00007ffff6392f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fffffffded8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdec8)
at libc-start.c:287
#14 0x00000000004089e9 in _start ()
アプリケーションは、最初に領域外書き出しが起こったと「valgrind」から報告されたのと同じ場所で停止したことがわかります。破壊が起こった場所は特定できましたが、破壊されたバッファが割り当てられた位置とサイズも知っておきたいと思います。
この情報を得るために、シンプルなスクリプトを作成しました。こちらからご入手いただけます。
https://gist.github.com/icewall/aadd126fa8081263593be17a20a40523
このスクリプトは、以下についての情報を収集します。
- 特定のバッファのサイズ
- コール スタック
「duma」とこのスクリプトを有効にした状態で、アプリケーションをもう一度実行してみましょう。
gdb-peda$ source .duma
gdb-peda$ duma
Enabled DUMA
gdb-peda$ b main
Breakpoint 1 at 0x40b0b4
gdb-peda$ r
Starting program: /home/icewall/bugs/lexmark/convert /home/icewall/bugs/lexmark/config
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
DUMA 2.5.15 (shared library, NO_LEAKDETECTION)
Copyright (C) 2006 Michael Eddington <meddington@gmail.com>
Copyright (C) 2002-2008 Hayati Ayguen <h_ayguen@web.de>, Procitec GmbH
Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com>
[----------------------------------registers-----------------------------------]
RAX: 0x40b0b0 (<main>: push rbp)
RBX: 0x0
RCX: 0x2
RDX: 0x7fffffffdef0 --> 0x7fffffffe2b0 ("XDG_VTNR=7")
RSI: 0x7fffffffded8 --> 0x7fffffffe24f ("/home/icewall/bugs/lexmark/convert")
RDI: 0x2
RBP: 0x7fffffffddf0 --> 0x0
RSP: 0x7fffffffddf0 --> 0x0
RIP: 0x40b0b4 (push r15)
R8 : 0x7ffff3587bf0 --> 0x7ffff354cbf0 --> 0x7ffff38c9bf0 --> 0x7ffff38bbbf0 --> 0x7ffff3842bf0 --> 0x7ffff3810bf0 --> 0x7ffff39ebbf0 --> 0x7ffff39c1bf0 --> 0x7ffff399fbf0 --> 0x7ffff3a8abf0 --> 0x7ffff3e2ebf0 --> 0x7ffff6730e80 --> 0x0
R9 : 0x1
R10: 0x0
R11: 0x246
R12: 0x4089c0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffded0 --> 0x2
R14: 0x0
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x40b0ab: jmp 0x40a40b
0x40b0b0 <main>: push rbp
0x40b0b1 <main+1>: mov rbp,rsp
=> 0x40b0b4: push r15
0x40b0b6: mov r15d,edi
0x40b0b9: push r14
0x40b0bb: push r13
0x40b0bd: push r12
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffddf0 --> 0x0
0008| 0x7fffffffddf8 --> 0x7ffff6392f45 (mov edi,eax)
0016| 0x7fffffffde00 --> 0x0
0024| 0x7fffffffde08 --> 0x7fffffffded8 --> 0x7fffffffe24f ("/home/icewall/bugs/lexmark/convert")
0032| 0x7fffffffde10 --> 0x200000000
0040| 0x7fffffffde18 --> 0x40b0b0 (<main>: push rbp)
0048| 0x7fffffffde20 --> 0x0
0056| 0x7fffffffde28 --> 0x94f83eede6f24ac4
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x000000000040b0b4 in main ()
この「割り当てトレース スクリプト」は、実行ポイントが main 関数に移った後で有効にする(またはロードする)ことが重要です。このようにしないと、不要な情報まで収集してしまうためです。
gdb-peda$ tracealloc
Error: missing argument
Trace malloc chunks allocation
Usage:
tracealloc [start|stop|status]
tracealloc info addr
gdb-peda$ tracealloc start
Breakpoint 2 at 0x7ffff63f3660: malloc. (3 locations)
gdb-peda$ c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x7ffff3efb800 --> 0x0
RCX: 0xd0
RDX: 0x1
RSI: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0
RDI: 0x7ffff3efb800 --> 0x0
RBP: 0x7ffff3404000 --> 0xe11ab1a1e011cfd0
RSP: 0x7fffffff2650 --> 0x215
RIP: 0x7ffff7bcb4d8 (mov BYTE PTR [rbx+rax*1],cl)
R8 : 0x0
R9 : 0x1
R10: 0x0
R11: 0x246
R12: 0x215
R13: 0x20000
R14: 0x215
R15: 0x7ffff3efb800 --> 0x0
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7bcb4cb: nop DWORD PTR [rax+rax*1+0x0]
0x7ffff7bcb4d0: movzx ecx,BYTE PTR [rbp+rax*1+0x0]
0x7ffff7bcb4d5: add edx,0x1
=> 0x7ffff7bcb4d8: mov BYTE PTR [rbx+rax*1],cl
0x7ffff7bcb4db: mov eax,edx
0x7ffff7bcb4dd: cmp r12,rax
0x7ffff7bcb4e0: ja 0x7ffff7bcb4d0
0x7ffff7bcb4e2: mov rax,rbx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff2650 --> 0x215
0008| 0x7fffffff2658 --> 0x7ffff3601fb8 --> 0x7ffff6346430 --> 0x7ffff4778da0 (<ISYS_NS::CBufferedReader::~CBufferedReader()>: mov QWORD PTR [rsp-0x8],rbp)
0016| 0x7fffffff2660 --> 0x215
0024| 0x7fffffff2668 --> 0x7ffff47793e5 (jmp 0x7ffff4779369)
0032| 0x7fffffff2670 --> 0xffff2a08
0040| 0x7fffffff2678 --> 0x1
0048| 0x7fffffff2680 --> 0x7ffff3424d38 --> 0x7ffff6345530 --> 0x7ffff4747060 (<ISYS_NS::docfile::CIStorageBase::~CIStorageBase()>: push rbp)
0056| 0x7fffffff2688 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7bcb4d8 in _duma_memcpy () from /usr/lib/libduma.so.0.0.0
このスクリプトは使う機会が非常に多いため、「PEDA」に組み込むことしました。「source allocation_trace_gist.py」でスクリプトをロードする代わりに「tracealloc start」という行があるのは、そのためです。
特定のバッファについての情報を取得するには、「allocations」ディクショナリをそのままの形で呼び出す代わりに「tracealloc info …」コマンドを使用します。
では、取得した情報を確認しましょう。
gdb-peda$ tracealloc info $rbx
Size of allocation : 0 ( 0x7ffff3efb800 )
#0 0x00007ffff7bcd350 in malloc () from /usr/lib/libduma.so.0.0.0
#1 0x00007ffff4742eaa in ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() () from /home/icewall/bugs/lexmark/libISYSshared.so
#2 0x00007ffff4746ca5 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#3 0x00007ffff4748d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#4 0x00007ffff40b6629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#5 0x00007ffff40bce86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#6 0x00007ffff41e722c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#7 0x00007ffff79bc138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#8 0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#9 0x000000000040b7a0 in main ()
#10 0x00007ffff6392f45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fffffffded8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdec8) at libc-start.c:287
#11 0x00000000004089e9 in _start ()
さて、これで「valgrind」が提供するのと同じ情報を取得することができました。メモリ割り当てのサイズはゼロで、割り当てをポイントするコール スタックはISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors関数に置かれていたことがわかります。
ここで触れたスクリプト以外に、ヒープと割り当てられたバッファに関する情報を取得するには以下のツールを使用することもできます。
https://github.com/rogerhu/gdb-heap
https://github.com/cloudburst/libheap
特筆すべきツールとして、Core Analyzerを挙げておきます。
http://core-analyzer.sourceforge.net/
このプロジェクトはかなり以前からありますが、ヒープに関する情報が多く得られるという意味で、現在でも非常に便利であることに変わりはありません。
Marcin Noga のアドバイザリに出てくる「heap」コマンドは、すべてこのツールに関連しています。
サイズ 0 のバッファが割り当てられた場所を特定できれば、サイズの計算に使用した値を調べることができます。アドバイザリでは「allocation tracing script」を使用してこれらの値が初期化された場所を見つけましたが、今回は興味深い「rr デバッガ」を使用します。
(rr) b ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors
デコンパイルされたコードの一部を、以下に示します。
Line 1 _DWORD *__fastcall ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors(struct_this *this)
Line 2 {
Line 3 struct_this *v1; // rbp@1
Line 4 void *_buff; // rax@3
Line 5 unsigned int __sectMiniFatStart; // ebx@3
Line 6 unsigned int index; // er12@5
Line 7 unsigned int v5; // eax@9
Line 8 bool v6; // cf@9
Line 9 bool v7; // zf@9
Line 10 _DWORD *result; // rax@11
Line 11 _DWORD *v9; // rbx@11
Line 12 unsigned int v10; // er12@15
Line 13 int v11; // edx@16
Line 14 unsigned int v12; // edi@18
Line 15 _DWORD *v13; // r13@18
Line 16 unsigned int v14; // ebx@19
Line 17 unsigned int v15; // er14@21
Line 18 unsigned int v16; // er12@22
Line 19 void *v17; // rax@27
Line 20 _QWORD *v18; // rax@31
Line 21
Line 22 if ( this->_csectMiniFat > 0x10000u )
Line 23 this->_csectMiniFat = 0x10000;
Line 24 _buff = malloc((unsigned int)(this->dword214 * this->_csectMiniFat));
Line 25 __sectMiniFatStart = this->_sectMiniFatStart;
Line 26 this->buff = _buff;
Line 27 if ( __sectMiniFatStart <= 0xFFFFFFF9 && this->_csectMiniFat )
Line 28 {
Line 29 index = 0;
Line 30 do
Line 31 {
Line 32 if ( !(unsigned __int8)ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(
Line 33 (ISYS_NS::docfile::CIStorageBase *)this,
Line 34 __sectMiniFatStart,
Line 35 (char *)this->buff + this->dword214 * index,
Line 36 1,
Line 37 0LL) )
メモリ割り当ての直後の行(24行目が実行された時点)に移動します。
(rr)
[----------------------------------registers-----------------------------------]
RAX: 0x6f4ef0 --> 0x7fbec23677d8 --> 0x7fbec23677c8 --> 0x7fbec23677b8 --> 0x6f65d0 --> 0x0
RBX: 0x7fff95efb420 --> 0x7fff95efbeb0 --> 0x7fbec1f7e7f0 --> 0x7fbec03b79a0 (:CIGRStreamStream::~CIGRStreamStream()>: 0x5c8948f8246c8948)
RCX: 0x7fbec2367760 --> 0x100000000
RDX: 0x6f4ef0 --> 0x7fbec23677d8 --> 0x7fbec23677c8 --> 0x7fbec23677b8 --> 0x6f65d0 --> 0x0
RSI: 0x40080d78
RDI: 0x0
RBP: 0x6f6310 --> 0x7fbec1f7d530 --> 0x7fbec037f060 (:docfile::CIStorageBase::~CIStorageBase()>: 0xec8348fb89485355)
RSP: 0x7fff95efb130 --> 0x7fff95efb420 --> 0x7fff95efbeb0 --> 0x7fbec1f7e7f0 --> 0x7fbec03b79a0 (:CIGRStreamStream::~CIGRStreamStream()>: 0x5c8948f8246c8948)
RIP: 0x7fbec037aeaa --> 0x258858948505d8b
R8 : 0x0
R9 : 0x2
R10: 0x7fff95efaf20 --> 0x0
R11: 0x7fbec037ae00 --> 0x48c2048d49118b48
R12: 0x0
R13: 0x0
R14: 0x6f6538 --> 0x0
R15: 0x6f6588 --> 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7fbec037ae9b: mov edi,DWORD PTR [rbp+0x54]
0x7fbec037ae9e: imul edi,DWORD PTR [rbp+0x214]
0x7fbec037aea5: call 0x7fbec0363d78 <malloc@plt>
=> 0x7fbec037aeaa: mov ebx,DWORD PTR [rbp+0x50]
0x7fbec037aead: mov QWORD PTR [rbp+0x258],rax
0x7fbec037aeb4: cmp ebx,0xfffffff9
0x7fbec037aeb7: ja 0x7fbec037b04d
0x7fbec037aebd: mov ecx,DWORD PTR [rbp+0x54]
[------------------------------------stack-------------------------------------]
「allocation script」やCore Analyzerなど前述のツールを使う以外にも、glibc には、ヒープに関する興味深い関数が備わっています。その内の 1 つを使用すると、ヒープ バッファのサイズを確認できます。これには、「malloc_usable_size」関数を使用します。
(rr) p malloc_usable_size($rax)
$34 = 0x28
malloc はバッファに 0x28 バイトを割り当てていることがわかります。
注目しているフィールドの値を知ることも簡単です。たとえば、
[rbp+0x54]
[rbp+0x214]
ここでは、`rr`を再び使用しています。これらのアドレスにハードウェア ブレークポイントを設定し、「reverse continue」を実行することにより、アドレスが初期化されたポイントがわかります。
(rr) p $rbp+0x214
$42 = (void *) 0x6f6524
(rr) delete breakpoints
(rr) watch *0x6f6524
Hardware watchpoint 3: *0x6f6524
(rr) rc
Continuing.
Warning: not running or target is remote
Hardware watchpoint 3: *0x6f6524
Old value = <unreadable>
New value = 0x0
0x00007fbec037e721 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
(rr) bt
#0 0x00007fbec037e721 in ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) () from /home/icewall/bugs/lexmark/libISYSshared.so
#1 0x00007fbec0380d36 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from /home/icewall/bugs/lexmark/libISYSshared.so
#2 0x00007fbebfcee629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#3 0x00007fbebfcf4e86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#4 0x00007fbebfe1f22c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from /home/icewall/bugs/lexmark/libISYSreaders.so
#5 0x00007fbec35f4138 in IGR_Get_File_Type () from /home/icewall/bugs/lexmark/libISYS11df.so
#6 0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#7 0x000000000040b7a0 in main ()
#8 0x00007fbec1fcaf45 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7fff95f068f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff95f068e8)
at libc-start.c:287
#9 0x00000000004089e9 in _start ()
コードを一通り確認します。
0x00007fbec037e709: call 0x7fbec035aef8 <ISYS_NS::docfile::CIStorageBase::Read_Header()@plt>
0x00007fbec037e70e: test al,al
0x00007fbec037e710: je 0x7fbec037e73d
0x00007fbec037e712: movsx ecx,WORD PTR [rbp+0x32]
0x00007fbec037e716: mov eax,0x1
0x00007fbec037e71b: mov edx,eax
0x00007fbec037e71d: shl edx,cl
0x00007fbec037e71f: mov ecx,edx
=> 0x00007fbec037e721: mov DWORD PTR [rbp+0x214],edx
これで、アドバイザリに記載されていた分析中に見つかったのと同じ場所を特定できたことがわかります。他の注目しているフィールドにも、同じ手順を行います。
今回の記事では、高度な脆弱性分析を、順を追ってご紹介してきました。皆様のご健闘をお祈りします。
本稿は 2016年11月15日に Talos Group のブログに投稿された「Crashing Stacks Without Squishing Bugs: Advanced Vulnerability Analysis」の抄訳です。