LinuxやWindowsからmacOSに移行すると、まるで未知の土地を歩いているような気分になるかもしれません。 Linuxはオープンソースであり、Windowsはドキュメントが充実していて非常に人気があります(そしてmacOSはどちらでもありません)。そのため、macOSは時に手強く感じられることがあります。 このブログ記事では、macOSで最初に気づくであろういくつかのこと、つまり「アプリ、どこもかしこもアプリ!」について説明したいと思います。
WindowsやLinuxの背景から来ると、「アプリ」という概念は奇妙に思えるかもしれません。 スレッドが「実行の単位」であり、プロセスが独自のアドレス空間を持つスレッドの入れ物であることは誰もが知っています。それ以上の何があるというのでしょうか? 実際には、プロセスが単一のファイルとして展開されることはほとんどありません。WindowsとLinuxの両方で、コードが動作するために必要となるものは数多くあります。その一部を挙げると:
.dll, .so)。例えば、Cランタイムライブラリ(Windowsではmsvcr<version>.dll、Linuxではlibc-<version>.so)や、その他の依存関係があります。PEと呼ばれる形式で、ディレクトリを持ちます。そのうちの1つがリソースディレクトリです(こちらに文書化されています)。リソース(画像、文字列など)を含むことができます。もちろん、リソースはディスクから動的に読み込むこともできます。PEファイル自体(こちらを参照)またはカタログファイル(つまり外部)に存在する場合があります。xml、ini、json)とWindowsレジストリに分かれています。さて、macOSはアプリケーションバンドルを非常に重視しています。その考え方は、プログラムの実行に必要な(ほぼ)すべてをディレクトリ構造にパッケージ化するというものです。リソースやローカリゼーション情報なども含まれます。もちろん、(例えばCランタイムライブラリのように)きれいにパッケージ化できないものもありますが、それでも物事がうまくバンドルされるということを意味します。巨大なレジストリを操作したり、分かりにくい設定ファイルの場所を調べるためにマニュアルページを読む必要はありません。アプリケーションバンドルは、.appで終わる単なるディレクトリです。UIが.app拡張子(そしてディレクトリであるという事実)を隠しているとしてもです。
攻撃者の視点から見ると、これは興味深いことです。Application Bundleは任意のアイコンを持つことができ、.app拡張子を隠すため、無防備なユーザーを騙してそのようなアプリをクリックさせることでマルウェアを配布できる可能性があります。例えば、PDFアイコンを持つResume.appファイルを考えてみてください。
Application Bundleのディレクトリ構造は、もちろん、組み込みのCalculatorアプリを使えば簡単に確認できます:
jbo@McJbo ~ % cd /System/Applications/Calculator.app
jbo@McJbo Calculator.app % ll
total 0
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 43 root wheel 1376 Mar 17 21:34 ..
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 Contents
jbo@McJbo Calculator.app % cd Contents
jbo@McJbo Contents % ll
total 16
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 .
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 ..
-rw-r--r-- 1 root wheel 2147 Mar 17 21:34 Info.plist
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 MacOS
-rw-r--r-- 204 root wheel 8 Mar 17 21:34 PkgInfo
drwxr-xr-x 4 root wheel 128 Mar 17 21:34 PlugIns
drwxr-xr-x 54 root wheel 1728 Mar 17 21:34 Resources
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 _CodeSignature
-rw-r--r-- 1 root wheel 461 Mar 17 21:34 version.plist
jbo@McJbo Contents % cd MacOS
jbo@McJbo MacOS % ll
total 344
drwxr-xr-x 3 root wheel 96 Mar 17 21:34 .
drwxr-xr-x 9 root wheel 288 Mar 17 21:34 ..
-rwxr-xr-x 1 root wheel 540912 Mar 17 21:34 Calculator
jbo@McJbo MacOS %
見てのとおり、Calculator.appはディレクトリです。その下には単一の項目、つまりContentsという別のディレクトリがあります。
Contentsの下には複数の項目があります:
Info.plist - アプリに関するメタデータを含みます。これについては後ほど詳しく説明します。MacOS - アプリのメイン実行ファイルを含みます(3番目のディレクトリ一覧で確認できます)。PkgInfo - 必須ではありません。パッケージ情報を含むバイナリファイルです。PlugIns - 必須ではありません。アプリのプラグインを含む可能性のあるディレクトリです。Calculatorには2つあります。「Basic and Scientific」用と「Hexadecimal」用です(なぜそのような分け方をしたのか、私にはまったくわかりませんし、気にもしません)。Resources - 必須ではありません。名前が示すとおり、リソースを含みます。アイコンを含む.icnsファイルや、ローカライズに関連する.lprroj接尾辞を持つディレクトリなど、いくつかの項目が見つかるかもしれません。_CodeSignature - 必須ではありません。名前が示すとおり、コード署名情報を含みます。version.plist - 必須ではありません。バージョン情報を含みます。正式に必須とされる項目はごくわずかであることに注意してください。実際、何もコンパイルせずに最初のアプリを作成することもできます!
しかし、その前にInfo.plistファイルについて説明する必要があります。
macOSを調べれば調べるほど、これらの奇妙なファイルを見かけることになります。これらは、いわば設定ファイルの強化版にすぎません。
それらは常に.plist拡張子を持ちます。これは正式名称であるProperty listファイルを短く呼んだものです。
残念ながら、Appleが保守しているplist形式には3つの種類があります:
xml形式。json形式。bplistというテキストで識別できます。幸い、plutilと呼ばれるユーティリティがあり、すべての形式に対応しています。plistファイルを出力するには、単にplutil -pを使用します。例:
jbo@McJbo Contents % plutil -p Info.plist | head -n 20
{
"BuildMachineOSBuild" => "22A380007"
"CFBundleDevelopmentRegion" => "English"
"CFBundleExecutable" => "Calculator"
"CFBundleGetInfoString" => "10.14, Copyright © 2000-2018, Apple Inc."
"CFBundleHelpBookFolder" => "Calculator.help"
"CFBundleHelpBookName" => "com.apple.Calculator.help"
"CFBundleIconFile" => "AppIcon"
"CFBundleIconName" => "AppIcon"
"CFBundleIdentifier" => "com.apple.calculator"
"CFBundleInfoDictionaryVersion" => "6.0"
"CFBundleName" => "Calculator"
"CFBundlePackageType" => "APPL"
"CFBundleShortVersionString" => "10.16"
"CFBundleSignature" => "????"
"CFBundleSupportedPlatforms" => [
0 => "MacOSX"
]
"CFBundleVersion" => "223"
"CTIgnoreUserFonts" => 1
jbo@McJbo Contents %
plutilには変換機能も組み込まれていますが、今回は説明しません。
AppleはアプリのInfo.plistにおいていくつかの要件を文書化していますが、実際に必須のフィールドはごくわずかです。いくつか興味深いフィールドを紹介します:
CFBundleExecutable - メイン実行ファイルの名前。MacOSディレクトリの下にあることが期待されます。CFBundleIconFile - アイコンファイルの名前。必須ではありません。CFBundleIdentifier - アプリバンドルの識別子。Appleは逆DNS表記(例:com.apple.calculator)を推奨しています。CFBundleName - バンドル名。これを踏まえると、コードを書かずに最初の素晴らしいアプリを作成できます! 見てみましょう:
#!/bin/zsh
# Create Bundle structure
mkdir -p ./MyApp.app/Contents/MacOS
# Create main executable file - a shell script in our case
cat <<EOF > ./MyApp.app/Contents/MacOS/MyApp
#!/bin/zsh
osascript -e 'tell app "Finder" to display dialog "Hello from MyApp!"'
EOF
chmod +x ./MyApp.app/Contents/MacOS/MyApp
# Create the Info.plist file
cat <<EOF > ./MyApp.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>MyApp</string>
<key>CFBundleIdentifier</key>
<string>com.myapp</string>
<key>CFBundleName</key>
<string>MyApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
</dict>
</plist>
EOF
これにより、MyAppという新しいアプリが作成されます。クリックすると、単にzshスクリプト(MyAppという名前)が実行されます。
ここではosascriptを使用していることに注意してください。これはAppleScriptインタプリタであり、厄介な問題をはらんでいますが、Hello from MyApp!と表示するダイアログを出すだけです。当然ながら、そのzshシェルファイルには任意のコードを書くことができます。新しいmacOSバージョンでは、zshがosascriptを呼び出すことを許可するよう求めるプロンプトが表示されるかもしれません。なぜそれが起こるのかは今後の記事で説明しますが、最初の承認後は尋ねられないことに注意してください。
アプリを起動するということは、依然としてプロセスが作成されるということです。もちろん、それはCFBundleExecutableが指すプロセスです。それはどのプロセスの下で実行されるのでしょうか?見てみましょう:
jbo@McJbo ~ % open -a Calculator
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12067 1 12067 0 1 S ?? 0:00.41 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ %
openコマンドは、Calculatorアプリをダブルクリックするのと同じです。これはかなり興味深いので、すぐに説明します。
Calculatorが実行されているので、psを使って実行中のプロセスを表示します。予想どおり、/System/Applications/Calculator.app/Contents/MacOS/Calculatorが実行中のプロセスであり、PIDは12067です。しかし、その親プロセスIDは1です!
macOSのプロセスID1は/sbin/launchdです。これは「システム全体およびユーザーごとのデーモン/エージェントマネージャー」です。Windows出身ならservices.exe、Linuxに詳しければsystemdを想像するとよいでしょう。サービス(macOSではLaunch AgentsおよびLaunch Daemonsと呼ばれます)の管理に加えて、すべてのアプリケーションの親でもあります。これは、macOSで意味のあるプロセスツリーを取得することが難しい理由の1つです。
興味深いことに、/System/Applications/Calculator.app/Contents/MacOS/Calculatorを直接呼び出すことで、Calculatorアプリを単なるプロセスとして実行することもできます。ただし、その場合launchdの子プロセスにはなりません:
jbo@McJbo ~ % /System/Applications/Calculator.app/Contents/MacOS/Calculator &
[1] 12502
jbo@McJbo ~ % 2023-04-04 15:54:22.987 Calculator[12502:1297087] XType: XTFontStaticRegistry is enabled by Info.plist.
jbo@McJbo ~ % ps -A -j | grep Calculator | grep -v grep
jbo 12502 951 12502 0 1 SN s000 0:00.31 /System/Applications/Calculator.app/Contents/MacOS/Calculator
jbo@McJbo ~ % echo $$
951
jbo@McJbo ~ %
確かに、Calculatorはスムーズに実行されますが、今度はターミナルの子プロセスになっています。
ここで見た動作の1つ注目すべき点は、攻撃者がこれをさまざまな目的に利用する可能性があるということです。例えば、攻撃者はプロセスツリーから簡単に脱出してセキュリティツールを回避したり、ロジックの脆弱性を悪用したりできます(時間があれば、私のmacOSサンドボックス逃避の脆弱性に関する記事を読んでみてください)。
launchdには他にも興味深い役割があります(LaunchAgentsとLaunchDaemonsについて読んでください)が、今はそれらについては説明しません。
ここでは1種類のバンドルを紹介しましたが、他にもたくさんあります(完全なリストではありません):
.app - これはすでに見ました。アプリのコンテナとなるApplication Bundlesです。.framework - Frameworksを含みます。これはロード可能なバンドルです。そうです、macOSではロード可能なファイル(.dylib)に対してdlopenを呼び出したり、フレームワークバンドル全体(リソース、コードなどを含む)をロードしたりできます。.kext - kernel extensionsを含みます。これはロード可能なバンドルですが、対象はmacOSカーネルです。最近のOSバージョンでは、Appleはカーネル拡張の数を減らすことに非常に力を入れています。.plugin - 名前が示すとおり、プラグインのコンテナです。これは、macOSリサーチへの移行を支援することを目的とした、短いブログ記事シリーズの最初の記事です。
オフェンシブセキュリティの観点から最初に気づいたのは、ペイロードをきれいなアプリ構造にパッケージ化するのがいかに簡単かということでした。
しかし、物事はそれほど単純ではありません。次のいくつかのブログ記事では、macOSの数多くのセキュリティ機能により、コード実行を獲得することがそれほど簡単ではないことを見ていきます。
お楽しみに!
Jonathan Bar Or (https://jonathanbaror.com)