Cisco Japan Blog

バグをつぶさずにスタック クラッシュを再現:高度な脆弱性分析

11 min read



この記事は、 Holger Unterbrink 氏popup_icon の協力のもと、 Marcin Noga 氏popup_icon により執筆されました。

概要

クラッシュのトリアージは複雑で時間のかかるプロセスですが、適切なツールを使用し、最適なアプローチで取り組めば、このプロセスは多少なりとも簡単になり、必要な時間も短縮することができます。この記事では、トリアージの戦略とツールセットについて、脆弱性に関する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つが PEDApopup_icon です。

レジスタ、スタック、およびコードに関して表示される情報がより充実していることに加えて、トリアージやエクスプロイト開発中に便利な機能が備わっています。

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

しかし動作は遅く、ターゲットが巨大で複雑な場合、目指す脆弱性をトリガするまでに相当な時間がかかるでしょう。もちろん、より効率的なトレーサを選択する手もありますが、今探しているのは更にスマートな解決策です。

ここでは、valgrindpopup_icon というツールをご紹介します。優秀なツールで、特にトリアージの前段階で役に立ち、分析を続ける足がかりを見つけてくれます。

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 を使用しても残念ながら多くの情報は得られそうにありません。さて、どうしましょうか。「タイム トラベル」デバッガ rrpopup_icon が役に立ちます。

簡単に言えば、「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 Grouppopup_icon のブログに投稿された「Crashing Stacks Without Squishing Bugs: Advanced Vulnerability Analysispopup_icon」の抄訳です。

 

コメントを書く