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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2024-38063 — CVE-2024-38063 - IPv6経由でカーネルをリモートから悪用する | Kitploit
ツール/GitHubGitHub/faizan-khanx/cve-2024-38063
メモリフォレンジック脆弱性分析エクスプロイトリバースエンジニアリングネットワークセキュリティ論文と研究学習と教育バイナリエクスプロイト
GitHubfaizan-khanx/cve-2024-38063

CVE-2024-38063

CVE-2024-38063 - IPv6経由でカーネルをリモートから悪用する

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

人気

すべて見る →

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

すべてのツールを探索

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

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

CVE-2024-38063 - IPv6経由でカーネルをリモートで悪用する

  • 8月13日に最新のWindowsパッチが公開されて以来、私はtcpip.sys(TCP/IPパケットの処理を担当するカーネルドライバー)の深部に没頭してきました。Windowsカーネルの最も到達しやすい部分にあるCVSSスコア9.8の脆弱性は、見逃すわけにはいきませんでした。これまでIPv6(またはそれを解析するドライバー)を実際に調べたことがなかったので、この脆弱性をリバースエンジニアリングするのは非常に難しいだろうと分かっていましたが、良い学習経験になるとも思っていました。

史上最も簡単なパッチ解析

  • 通常、どのコード変更が脆弱性に対応するかを特定するためにパッチをリバースエンジニアリングするだけでも数日から数週間かかることがありますが、今回は一瞬でした。実際、あまりにも簡単だったため、ソーシャルメディアで複数の人が私が間違っていて、バグは別の場所にあると指摘するほどでした。私は実際に彼らの言うことを聞いて、間違ったドライバーのリバースエンジニアリングに丸一日を無駄にしたのでしょうか? それは誰にも分かりません。

ドライバーファイル全体で変更されたのは正確に1箇所だけで、結局それが本当にバグでした。 image パッチ適用前後のtcpip.sysのbindiff概要。

ドライバー全体で変更されたのは単一の関数だけでした。通常なら、注目すべき関数を特定するだけで20以上の関数変更を調べるのに丸一日かかることもありますが、今回は違いました。 image

Ipv6pProcessOptions() パッチ適用前。

image Ipv6pProcessOptions() . パッチ適用後。

変更されたのは単一の関数だけでなく、コードの1行だけでした。

  • 非常に長い名前の Feature_2660322619__private_IsEnabledDeviceUsage_3() 関数は、Microsoftが部分的なパッチのロールバックを可能にするために時々追加するものです。この呼び出しはグローバルフラグまたはレジストリ設定の存在をチェックし、設定されている場合、関数はfalseを返し、パッチ適用版ではなく元のコードが実行されることになります。

  • Microsoftがこれを行う理由は、セキュリティパッチが時々意図せず何かを壊すことがあるためで、この設定により管理者は毎月のパッチロールアップ全体をアンインストールしてシステムのセキュリティを大幅に弱めることなく、単一の脆弱性のパッチを無効にすることができます。

  • これを考慮すると、このパッチが行っているのは IppSendErrorList() への呼び出しを IppSendError() に置き換えるだけであり、問題が何らかのリストに関係しているという手掛かりが得られます。史上最も簡単なパッチ差分(少なくとも私はそう思っていました)

脆弱性は任意、悪用は必須

  • パッチをリバースエンジニアリングして変更されたコードを見つけるのは、挑戦の半分に過ぎません(この場合は0.1%未満ですが)。残りのプロセスは、何が起こっているのかを理解するためにコードベースの十分な部分をリバースエンジニアリングし、どのような種類の脆弱性がパッチされたのかを特定し、ターゲットコードに到達するためのリクエストをどう構築するか、そしてどのような状態が悪用可能な状態になるかを突き止めることで構成されています。

  • 最初の部分は十分簡単です。変更は Ipv6pProcessOptions() にあり、これはIPv6であり、オプションの処理に関係していることを示しています。そこで、RFCを簡単に確認すれば、IPv6オプションが何であり、どこにあるのかが正確に分かります。

image Wikipediaにある宛先オプションヘッダーのレイアウト。

なるほど、いいですね。探しているものは、メインのIPv6ヘッダーの直後に配置される宛先オプションヘッダーのようです。Pythonライブラリの'scapy'を使ってテスト用のIPv6パケットを構築してみましょう。

注記: 偽装IPアドレスを使用したDDoS攻撃を軽減するため、Windowsは生のIPパケットを構築する機能を制限しています。そのため、私は概念実証の開発にLinuxを使用することにしました。Linuxではユーザーが生のレイヤー2およびレイヤー3パケットを構築して送信できますが、Pythonスクリプトをrootとして実行する必要があります。

root@kitploit:~
import sys
import struct
from scapy.all import *


def send_ipv6_option_packet(dest_ip):
	ethernet_header = Ether()
	ip_header = IPv6(dst=dest_ip)
	options_header = IPv6ExtHdrDestOpt()
	sendp(ethernet_header / ip_header / options_header)
	
	
if len(sys.argv) < 2:
	print('Use: python3 script.py <target_ipv6_address>')
	exit(-1)

send_ipv6_option_packet(sys.argv[1])
  • tcpip!Ipv6pProcessOptions にブレークポイントを設定してスクリプトを実行したところ、脆弱な関数に到達するために必要なのは、空のオプション構造を持つIPv6パケットを送信することだけであることが明確になりました。次に、構造にいくつかの無効なオプションを追加して、 IppSendErrorList() への呼び出しに到達できるかどうかを試しました。

簡単なコードレビューにより、ほとんどすべての無効なオプション形式がIppSendErrorListへの呼び出しをトリガーできることが示されました。そこで、無効な長さ(65535バイト未満)のジャンボパケットオプションを使用することにしました。

options_header = IPv6ExtHdrDestOpt(options=[Jumbo(jumboplen=0x1337)])

  • では、 IppSendErrorList() は実際に何をするのでしょうか? コードは非常にシンプルです。 image IppSendErrorList関数全体。

  • このコードは連結リストを反復処理し、リスト内のすべての項目に対して IppSendError() を呼び出します。ここでも運が味方して、ここまでは簡単でした。IppSendErrorListがリスト内の各項目に対してIppSendErrorを呼び出すだけで、パッチがIppSendErrorListへの呼び出しをIppSendErrorに置き換えるのであれば、問題は最初以外のリスト項目に対してIppSendErrorが呼び出されたときに発生します。

彼はリストを作り、それを52,567回チェックする

  • ここから状況は明白なものから異常に困難なものへと変わりました。ただし、その大部分は、私が持つ2つの脳細胞のうちの1つがひどいコロナ感染と戦うことに専念していたためだと思います。コードの一部を理解し、眠りに落ち、そして自分が解明したことを忘れてしまう、という日々を数日過ごしました。全体のプロセスには、何が起こっているのかを解明するためにtcpip.sysの一部をリバースエンジニアリングする1週間以上が必要でした。しかし、Axelのブログ記事が非常に役立ちました。

  • Axelがリバースエンジニアリングした関数と構造、およびそれらが渡される他の関数を見ると、 Ipv6pProcessOptions() に渡される唯一の引数が、この記事で定義されているのと同じ packet_t 構造体であることが明らかです。つまり、 Ipv6pProcessOptions に渡され、 IppSendErrorList によって反復処理されるポインターは、パケットの連結リストです。

  • そこで、 Ipv6pProcessOptions() にブレークポイントを設定してリストを検査しました。

image

list->NextエントリはNULLです。

  • ブレークポイントがヒットするたびに、リストには1つのパケットしか含まれていませんでした。なぜそうなるのか、そしてリストを実際にリストにするにはどうすればよいのかを解明するのに、認めたくないほど長い時間を費やしました。最初に考えたのはIPv6フラグメンテーションでした。IPv6では送信者が大きなパケットを複数の小さなパケットに分割でき、それらをリストにまとめておくのは理にかなっています。

  • 広範なリバースエンジニアリングの後、私の仮定が正しいことを確認しましたが、フラグメントリストはここで扱っているリストとは関係ありません。

  • 実際には、完全に偶然に答えを見つけました。たまにリストが複数項目になることがありましたが、その理由は不明でした。何度も堂々巡りをした後、カーネルのブレークポイントがトリガーされるとカーネル全体が一時停止し、ネットワークアダプターがパケットを蓄積することに気づきました。カーネルが再開すると、これらのパケットはきれいなリストとしてスタックを下ってtcpip.sysに渡されます。これは、パケットがカーネルの一時停止中に送信されたが、次のブレークポイントがヒットする前に処理されなかった場合にのみ発生しました。

  • この動作はおそらくパフォーマンス最適化であり、低スループットではカーネルはパケットを個別に処理しますが、高ボリュームではパケットはリストに整理されてバッチ処理されます。リストはおそらく、処理を高速化するためにプロトコルや送信元アドレスなどの要因に基づいて分離されているため、私たちのリストには送信したIPv6パケットのみが含まれるはずです。

  • 高スループット時にパケットがリストに結合されることが分かったので、最も簡単なオプションが何であるかは明らかです。皮肉なことに、私たちのDoS PoCはDoS状態をトリガーするためにDoSを使用する必要があります。システムにIPv6パケットのバーストを大量に送信すれば、 IppSendErrorList() に渡される大きなリストを取得できるはずです。

  • 最初は、どれだけ多くのパケットを送信しても、カーネルを一時停止した場合にしかリストをn > 1にできませんでした。しかし…Python(非常に遅い)をVM(2倍非常に遅い)内で使用しているため、いくつかの設定を調整する必要がありそうです。攻撃システムで発生しているVM-ception(VMの中のVM)に対抗するために、ターゲットVMをシングルCPUコアのみを使用するように再構成することにしました。

image

いいですね! パケットリストが多くのエントリを含むリストになりました!

  • どうやら、VMの中のVMはDoSには最適な選択肢ではないようです。誰が想像したでしょうか? しかし、最終的には何とか動作させました。次に、 IppSendError() が何をするのか、そして問題がどの部分にあるのかを解明する必要があります。

さらに永遠に続くリバースエンジニアリング。

  • 広範なリバースエンジニアリングの後、IppSendErrorが何をするのかがかなり明確になりました。通常の状況では、net_buffer_list->Statusを0xC000021B(STATUS_DATA_NOT_ACCEPTED)に設定してパケットを無効化するだけです。次に、エラーのあるパケットに関する情報を含むICMPエラーを送信者に送り返します。

    image IppSendErrorの関連する2つの部分。

  • 最初に確認したのは、tcpip.sysにnet_buffer_list->Status値を無視する関数があるかどうかでした。これにより、ドライバーが未定義または予期しない状態のパケットを処理することになり、うまくいけば悪用条件につながるでしょう。

image

パケットの処理を担当するメインループ。

  • すべての解析関数を呼び出すループがエラーチェックで囲まれているため(エラーコードが設定されるとどこにも進めないことを意味します)、これは間違った深みにはまるルートだと考えました。代わりに、IppSendErrorに戻り、エラーコードを設定する前にパケットの状態を変更するコードパスがあるかどうかを確認することにしました。それが競合状態につながる可能性があります。

  • さらに多くのリバースエンジニアリングの後、IppSendErrorの非常に下部近くで次のコードを見つけました。

image

IppSendError内でpacket_sizeをゼロに設定するコードパス。

  • IppSendErrorList(つまりIppSendError)がalways_send_icmp引数をtrueに設定して呼び出されると、リスト内のすべてのパケットにICMPエラーを送信しようとするようです。

  • そして、おそらく神のみぞ知る理由で、packet->packet_sizeフィールドがゼロに設定されるコードブロックに到達します。

  • always_send_icmpをtrueに設定するために必要なのは、'Option Type'の値を0x80より大きい任意の数値に設定して、オプションヘッダー処理で特定のエラーを引き起こすことだけです。

root@kitploit:~
def build_malicious_option(next_header, header_length, option_type, option_length):
    dest_options_header = 60
	
    options_header = struct.pack('BBBB', next_header, header_length, option_type, option_length) + b'1337'
    return Ether(dst=mac_addr) / IPv6(dst=ip_addr, nh=dest_options_header) / raw(options_header)

packet = build_malicious_option(next_header=59, header_length=0, option_type=0x81, option_length=0)	
sendp(packet)

しかし、packet_sizeをゼロに設定するとパーサーが壊れるのではないでしょうか?

image

パケットの処理を担当するメインループからのスニペット。

  • パケットハンドラーは、事前解析中に設定されたときから変更されていないpacket->next_header値に基づいてVTable関数を呼び出すだけです。これにより、パケット処理を継続でき、さらにはどの処理を実行するかを制御することもできます。

  • packet->next_header値はIPv6パケットの'Next Header'フィールドから取得されるため、任意の有効なIPv6ヘッダー値に設定でき、ループは対応するパーサーを呼び出します。これにより、多くの潜在的な攻撃対象が得られます。 image

    IPv6パケット形式。

  • 残っているのは、packet_sizeフィールドに対して何かおかしなことをする、到達可能なIPv6パーサーの部分を見つけることだけです。

フラグメンテーションに戻る

  • 最初に調べることにしたのはIPv6フラグメントパーサーです。なぜなら、以前のcve-2021-24086脆弱性があった場所であり、より奇抜なコードを見つけるのに良い場所のように思えたからです。 image

うーん…あと一歩なのに、でも遠い

  • ここには確かに脆弱性がありますが、RCEではありません。

  • 基本的に、ほとんどのCPUではレジスタは循環式です。レジスタをその最大可能値を超えてインクリメントすると、ゼロに戻ります。同様に、最小可能値を下回ってデクリメントすると、最大可能値に戻ります。これらはそれぞれ整数オーバーフローおよび整数アンダーフローと呼ばれます。この動作は符号付き整数では少し異なりますが、ここでは扱っていません。

  • 最初の行、 fragment_size = LOWORD(packet->packet_size) - 0x30 は、次のASMコードで構成されています:

    image

    フラグメントサイズを計算するASMコード。

  • AXはEAXレジスタの下位16ビットです。EAXレジスタは32ビットですが、AXは独自の16ビットレジスタであるかのように動作するため、オーバーフローやアンダーフローはAXに限定され、EAXレジスタの残りの部分には影響しません。これは非常に便利です。EAXレジスタでアンダーフローが発生すると40億という値になり、4GBのメモリ割り当てが試行されて、おそらく失敗するからです。

  • packet->packet_sizeの値はゼロ なので、このコードはaxをゼロに設定してから、そこから0x30を減算します。

  • 通常の条件下ではパケットヘッダーは0x30バイトなので、 packet_size - 0x30 はフラグメントデータのサイズです。

  • 私たちの場合、packet->packet_sizeは0なので、そこから1でも減算すると、レジスタは最大可能な16ビット整数値(0xFFFF)に循環します。0x30を減算しているため、AXの値はアンダーフローして MAX_VALUE - 0x2F、つまり0xFFD0 になり、これは65,488です。

  • 残念ながら、メモリ割り当てとデータコピーの両方に同じ計算が使用されているため、バッファオーバーフローは発生しません。 RtlCopyMdlToBuffer() はソースバッファに対しても境界チェックを実行すると思うので、範囲外読み取りも発生しません。しかし、完全に手ぶらで終わるわけではありません。

  • ExAllocatePoolWithTagPriority() は割り当てられたメモリをゼロにしないため、また RtlCopyMdlToBuffer() は利用可能な実際のデータ量のみをコピーするため、約65KBの初期化されていないカーネルメモリが得られます。メモリアドレスは解放後に再利用されるため、バッファには再割り当て前にそのアドレスに保存されていたものがおそらく入っています。フラグメンテーションを使用して、ICMPエコーリクエストのように私たちに送り返されるパケットを構築できれば、ランダムなカーネルメモリを漏えいさせて、ASLR回避につなげられる可能性があります。

IPv6フラグメントは、次の3つの条件のいずれかが発生するまでメモリに残ります:

  • フラグメンテーションをひどく失敗させ、システムが停止すべき時だと伝える。

  • 'More'フィールドが0に設定されたフラグメントを送信する。これは最後のフラグメントであることを示し、システムは再構築を開始する。

  • タイムアウト期間(60秒)が切れる前に最後のフラグメントを送信せず、システムがフラグメントを破棄する。

  • Ipv6pReassemblyTimeout()は条件3の下で呼び出されるので、これをどのように悪用できるか調べてみましょう。

Ipv6pReassemblyTimeout() は条件3の下で呼び出されるので、これをどのように悪用できるか調べてみましょう。

image

これはまさに私たちが必要としているものです!

  • 以前の問題は、コードがメモリ割り当てとコピー操作の両方にまったく同じ計算を使用していたことでした。一方、このコードはそうではありません。ASMをもう少し詳しく見て、どのように悪用可能かを見てみましょう。

image

割り当てサイズの計算を担当するアセンブリコード。

  • ここで見られるように、計算の最初の部分(fragment_list->net_buffer_length + reassembly->packet_length + 8)は16ビットのDXレジスタを使用して行われます。

  • 先ほど覚えているかもしれませんが、reassembly->packet_lengthをアンダーフローさせて0xFFD0にしました。したがって、DXレジスタは8バイトを加算した後、0xFFD8になります。fragment_list->net_buffer_lengthが0x27(39バイト)より大きい場合、DXはオーバーフローしてゼロにリセットされます。

  • fragment_list->net_buffer_lengthは0x38バイト程度になるはずなので、DXレジスタがオーバーフローして8になります。0x28バイトが加算された後、わずか48バイトのメモリ割り当てになります。

  • 後続の memmove() 呼び出しはサイズにそのままのreassembly->packet_length値を使用するため、65,488バイトがreassembly->payloadから30バイトのバッファにコピーされることになります。さらに素晴らしい点は、コピーされるデータの多くがフラグメントペイロード(私たちが制御でき、任意の形式の任意のデータにできる)から来るため、かなり制御しやすいカーネルプールベースのバッファオーバーフローが得られることです。

  • 脆弱性をトリガーする可能性を得るには、IppSendErrorListが呼び出された時点で、連結リスト内の不正な形式のオプションパケットの後に1つ以上のフラグメントパケットが配置されている必要があります。しかし、私のテストからすると、これだけでは悪用が保証されないようです。他にも満たす必要のある条件があると思います。IppSendError内の同期コードにより、競合状態にも勝たなければならないのではないかと推測していますが、確認はしていません。

ソーシャル

Faizan's GitHub stats

instagram twitter linkedin github

ツールをダウンロード
  • さらに、コードはreassembly->fragment_sizeもアンダーフローした16ビット整数(65,488)に設定するため、バッファオーバーフローを引き起こすために使用できる可能性のある2つの別々の変数が得られました。

  • 解決策(または少なくともその1つ)は Ipv6pReassemblyTimeout() です。初期のフラグメント処理ではオーバーフローを引き起こせませんが、クリーンアップ中には引き起こせるようです。