宮城県沖の太平洋


Date/Time: 2021:02:28 06:09:05
Camera: Apple
Model: iPhone SE (1st generation)
Exporsure Time: 1/120
FNumber: 2.2
Aperture Value: 2.3
Focal Length: 4.2

Close

y2blog » Mac AppStoreからのLionのダウンロード

9

01

2011

Mac AppStoreからのLionのダウンロード

Lion非対応マシンでMac AppStoreからのLionダウンロードインストールに挑戦


先日AppleからLionのインストール用USBドライブが発売されましたが、やはり余分なコストを掛けてLionをインストールする気にはなれません.Lion非対応マシンではMac AppStoreからのLionダウンロードが出来ないことは既に報告していましたが、Mac AppStoreのアプリケーションダウンロードインストールプロセスを色々と調べてみました.
 疑問1.AppStoreアプリケーション自体は機種チェックを行っているのか?
当初はAppStoreアプリケーションプログラムの中で機種チェックを行っていて、ダウンロード先のAppleのサーバとの間でネゴシエーションが行われて、エラーチェックに引っ掛かってできないのではないかと考えて居ましたが、AppStoreアプリケーションのプログラムコード(バイナリデータ)の中身を覗いてみてもそれらしき形跡が見あたりませんでした.


そこで、AppStoreアプリケーションがどのような仕組みでアプリケーションの自動ダウンロードインストール作業を行っているのか調査してみました.


AppStoreでのダウンロードインストールは個人ユーザのAppleIDに紐付けられているため、個々のユーザの設定データや作業ディレクトリは個人のホームディレクトリ上に置かれています.作業ディレクトリの場所は、個人ホームディレクトリ上の “Library/Application Support/AppStore” 配下に置かれています.アプリケーション毎に9桁の数字の名前が付けられた作業ディレクトリが作成され、そこにダウンロードインストールに必要な各種パッケージがサーバからダウンロードされます.この9桁の数字の名前は予めアプリケーション毎に割り当てられた名前のようですが、Lionのダウンロードインストールでは次の2つのファイルがダウンロードされていました.

 
Lion Installer Flying Icon
サーバからダウンロードされた Lionダウンロードインストーラの作業用ファイル

“flyingIcon” という名の画像ファイルと “preflight.pfpkg” という正体不明のファイルがダウンロードされていました.”preflight.pfpkg”という名前からしてAppleのサーバーからOSインストールパッケージをダウンロードするためのスクリプトプログラムパッケージのようです.


このファイルの16進ダンプを取って眺めてみたら、通常のインストーラパッケージ(拡張子が .pkg, mpkgなどのパッケージ)と同じようなxar形式の圧縮ファイルでしたので、xar コマンドで解凍してみました.



imac:~ yasuaki$ cd Library/Application\ Support/AppStore/444303913/ imac:444303913 yasuaki$ ls -la total 1384 drwxr-xr-x 4 yasuaki admin 136 Sep 1 16:40 . drwxr-xr-x 5 yasuaki admin 170 Sep 1 16:40 .. -rw-r--r-- 1 yasuaki admin 49445 Sep 1 16:40 flyingIcon -rw-r--r-- 1 yasuaki admin 655196 Sep 1 16:40 preflight.pfpkg imac:444303913 yasuaki$ mkdir tmp imac:444303913 yasuaki$ cd tmp imac:tmp yasuaki$ xar -xvf ../preflight.pfpkg OSInstall.mpkg Resources/ar.lproj/Localizable.strings Resources/cs.lproj/Localizable.strings Resources/da.lproj/Localizable.strings ... Resources/cs.lproj Resources/ar.lproj Resources imac:tmp yasuaki$ ls -la total 968 drwxr-xr-x 5 yasuaki admin 170 Sep 1 16:46 . drwxr-xr-x 5 yasuaki admin 170 Sep 1 16:43 .. -rw-r--r-- 1 yasuaki staff 7918 Sep 1 16:46 Distribution -rw-r--r-- 1 yasuaki staff 483841 Aug 18 09:51 OSInstall.mpkg drwx------ 24 yasuaki staff 816 Jan 1 1970 Resources  

解凍すると機種やインストール環境のチェックを行っているスクリプトファイル “Distribution” とOSインストーラーパッケージ “OSInstall.mpkg” 、言語などのリソース情報が含まれている “Resources” ディレクトリが現れました.


ここで更に”OSInstall.mpkg”の中身を展開してみたところ、次の様になりました.



imac:tmp yasuaki$ mkdir tmp2 imac:tmp yasuaki$ cd tmp2 imac:tmp2 yasuaki$ xar -xvf ../OSInstall.mpkg Distribution Resources/ar.lproj/License.rtf Resources/ar.lproj/Localizable.strings Resources/ar.lproj/VolumeCheck.strings Resources/cs.lproj/License.rtf ... Resources/cs.lproj Resources/ar.lproj Resources imac:tmp2 yasuaki$ ls -la total 64 drwxr-xr-x 4 yasuaki admin 136 Sep 1 16:53 . drwxr-xr-x 6 yasuaki admin 204 Sep 1 16:53 .. -rwxr-xr-x 1 yasuaki staff 29068 Aug 18 09:51 Distribution drwxr-xr-x 24 yasuaki staff 816 Aug 18 09:51 Resources

“preflight.pfpkg” の場合と同様にスクリプトファイル “Distribution” が含まれています.各々の “Distribution” の中身から推定すると、”preflight.pfpkg”の方がOSインストール用システムOSをインストールするためのスクリプトで、”OSInstall.mpkg” の方はOSインストール用システムOSが起動された状態で、実際にLionをインストールするための本番スクリプトだと思います.


どうやら Lion非対応マシンでMac AppStore経由で Lionをダウンロードし、実際にインストールを行うにはこの2つの “Distribution” スクリプトを修正しなければならないようです.


これらのスクリプトでのマシンチェックの部分に、VMWareやParallels Desktopなどの仮想環境下にもLionをインストールできるようになっていますね.


ひょっとしたらLion非対応マシンでも、これらの仮想環境下でSnow Leopardを動かしてMac AppStoreからダウンロードすればマシンチェックには引っ掛からないのでしょうか? 但し、Lion非対応マシンではVMのゲストOSに2GB以上のメモリを割り当てるのは無理でしょうね.Lionの2GBのメモリ制限に引っ掛かってしまいそうです.

Lionのダウンロードインストールの機種チェックはダウンロードされたインストールスクリプト側で行われている


“preflight.pfpkg”に含まれているインストールスクリプト “Distribution” の中身


[sourcecode language=”XML” wraplines=”true”] <?xml version="1.0" standalone="yes"?> <installer-gui-script minSpecVersion="1"> <options eraseOptionAvailable="true" hostArchitectures="i386" allow-external-scripts="yes" osVersion="10.7" osBuildVersion="11A511"/> <title>MacOSX_Title</title> <license file="License.rtf"/> <conclusion file="Conclusion.rtfd"/> <script> var minRam = 2048; function checkSupportedMachine(machineType) { return true; } function checkSupportedBootRom(machineType) { return true; } function hasAtLeastRam(RAM) { var requiredRAM = (RAM * 1024 * 1024); var actualRAM = system.sysctl(‘hw.memsize’); return (actualRAM &gt; (requiredRAM – 1)); } function is64bit() { var is64bit =system.sysctl(‘hw.cpu64bit_capable’); return is64bit; } function isVirtualMachine(){ var cpuFeatures = system.sysctl( ‘machdep.cpu.features’ ); cpuFeatures=cpuFeatures.split(" "); for( var i = 0; i &lt; cpuFeatures.length; i++ ){ if( cpuFeatures[i] == "VMM" ){ return true; } } return false; } function isSupportedPlatform(){ if( isVirtualMachine() ){ return true; } var platformSupportValues=["Mac-F2268DC8","Mac-F22C86C8","Mac-F22587C8","Mac-F2218FA9","Mac-F2218EA9", "Mac-F42D86A9","Mac-F22C8AC8","Mac-F22586C8","Mac-942B59F58194171B","Mac-F226BEC8","Mac-F4218FC8","Mac-942459F5819B171B","Mac-F4218EC8", "Mac-F2208EC8","Mac-F22C89C8","Mac-F22587A1","Mac-F221DCC8","Mac-F42388C8","Mac-F223BEC8","Mac-F4238CC8","Mac-F222BEC8","Mac-F227BEC8","Mac-F4208AC8","Mac-F22788A9","Mac-F4238BC8","Mac-F221BEC8", "Mac-F2238AC8","Mac-F4208EAA","Mac-F22788C8","Mac-F22589C8","Mac-F4228EC8","Mac-F22788AA","Mac-F42C86C8","Mac-F4208CA9", "Mac-942C5DF58193131B","Mac-F2238BAE","Mac-F42289C8", "Mac-F2268CC8","Mac-F4208DC8","Mac-F2218FC8","Mac-F2218EC8","Mac-F4208DA9","Mac-F42D89C8","Mac-F4208CAA","Mac-F42D89A9", "Mac-F2268AC8","Mac-F42C89C8","Mac-942452F5819B1C1B","Mac-F42786A9","Mac-F42D88C8","Mac-F42187C8","Mac-94245B3640C91C81","Mac-F42D86C8", "Mac-F2268EC8","Mac-F2268DAE","Mac-F42C8CC8","Mac-F42C88C8","Mac-94245A3940C91C80", "Mac-F42386C8","Mac-942B5BF58194151B","Mac-F42189C8"]; var boardID = system.ioregistry.fromPath(‘IOService:/’)[‘board-id’]; if( !boardID || platformSupportValues.length ==0 ) { return false } for( var i = 0; i &lt; platformSupportValues.length; i++ ){ if( boardID == platformSupportValues[i] ){ return true; } } return false; } function installCheckScript(){ try{ var machineType = system.ioregistry.fromPath(‘IODeviceTree:/’)[‘compatible’]; if (typeof(isFNI) == "undefined" &amp;&amp; typeof(hwbeInstallCheck) != "undefined") { if (!hwbeInstallCheck()) { return false; } } if(!is64bit()){ my.result.message = system.localizedStringWithFormat(‘IC_Unsupported_Processor’); my.result.type = ‘Fatal’; return false; } if(!isSupportedPlatform()){ my.result.message = system.localizedStringWithFormat(‘IC_Unsupported_Platform’); my.result.type = ‘Fatal’; return false; } if(!hasAtLeastRam(minRam)){ my.result.message = system.localizedStringWithFormat(‘IC_RAM_message’); my.result.type = ‘Fatal’; return false; } if (system.compareVersions(system.version.ProductVersion, ‘10.7’) &lt; 0 &amp;&amp; system.env.COMMAND_LINE_INSTALL) { my.result.message = system.localizedStringWithFormat(‘IC_Command_Line_message’, ‘10.7’); my.result.type = ‘Fatal’; return false; } if (typeof(findBJPrinters) != "undefined") findBJPrinters(); } catch (e) { system.log(‘installCheckScript threw exception ‘ + e); } return true; } function volCheckScript(){ var target = my.target; var destSystemVersion = target[‘systemVersion’]; if(system.files.fileExistsAtPath(my.target.mountpoint + "/Backups.backupdb")) { my.result.message = system.localizedString(‘VC_Backup_message’); my.result.type = ‘Fatal’; return false; } if(my.target.systemVersion){ if( system.compareVersions(my.target.systemVersion.ProductVersion, ‘10.8’) &gt;= 0){ my.result.message = system.localizedString(‘VC_Newer_message’); my.result.type = ‘Fatal’; return false; } } if (destSystemVersion) { // Don’t allow upgrades on volumes less than 10.6.6 if (-1 == system.numericalCompare(destSystemVersion[‘ProductVersion’], ‘10.6.6’)) { my.result.message = system.localizedString(‘VC_Upgrade_message’); my.result.type = ‘Fatal’; return false; } } //FDE CHECKS var plist = system.files.plistAtPath(‘/System/Library/Extensions/CoreStorage.kext/Contents/Info.plist’); if(plist){ var plistKeyValue = plist[‘CoreStorageDiskFormatVersion’]; if((system.ioregistry.matchingClass(‘CoreStorageGroup’).length != 0) &amp;&amp; (system.compareVersions(plistKeyValue, ‘1’) != 0) ){ my.result.message = system.localizedString(‘CS_message’); my.result.type = ‘Fatal’; return false; } } // SERVER CHECKS if (destSystemVersion &amp;&amp; destSystemVersion.isServer) { // Install Assistant if (system.env.__OSINSTALL_ENVIRONMENT != ‘1’) { // Block if source volume is client and target volume is server if (! system.files.fileExistsAtPath("/System/Library/CoreServices/ServerVersion.plist")) { my.result.message = system.localizedString(‘VC_CannotUpgradeServer_message’); my.result.type = ‘Fatal’; return false; } // For Server upgrades Server.app needs to be purchased if (! system.files.bundleAtPath("/Applications/Server.app")) { my.result.message = system.localizedString(‘VC_NeedServerApp_message’); my.result.type = ‘Fatal’; return false; } // Recovery HD } else if (! system.files.fileExistsAtPath("/System/Installation/Packages/OSInstall.mpkg")) { // Block Server volumes my.result.message = system.localizedString(‘VC_CannotUpgradeServerRecovery_message’); my.result.type = ‘Fatal’; return false; } } return true; } function language_running(langKey){ var appleLanguages = system.defaults[‘AppleLanguages’]; if(!appleLanguages || (appleLanguages.length == 0)) return ((langKey == ‘English’) || (langKey == ‘en’)) return (langKey == appleLanguages[0]); } gLanguageRequired = { }; //Function returns true if the langKey is required, it uses a cache so that the logic doesn’t //have to be run hundreds of times function language_required(langKey){ if(! (gLanguageRequired[langKey])){ var required = false; if(language_running(langKey)){ required = true; } gLanguageRequired[langKey] = required; } return gLanguageRequired[langKey]; } function language_enabled(langKey){ var enabled = !(language_required(langKey)); if(false == enabled){ my.choice.tooltip = system.localizedString(‘TT_Language_Required_message’); } return enabled; } function language_selected(langKey){ var selected = my.choice.selected || language_required(langKey); return selected; } function verCompare(checkVer){ var sysVer = my.target[‘systemVersion’]; if (sysVer) { return system.numericalCompare(sysVer[‘ProductVersion’],checkVer); } return -1; } function upgrade_allowed(){ var argv = upgrade_allowed.arguments; var upgradable = true; var upgradeAction = my.choice.packageUpgradeAction; if(argv.length &gt; 0) { upgradeAction = eval(‘choices.’ + argv[0] + ‘.packageUpgradeAction’); } if((upgradeAction == ‘downgrade’) || (upgradeAction == ‘mixed’)){ my.choice.tooltip = system.localizedString(‘TT_Newer_Package_Installed_message’); upgradable = false; } return upgradable; } function isServer(){ if(!my.target[‘systemVersion’].isServer){ return false; } return true; } function systemHasDVD(){ var obj = system.ioregistry.matchingClass("IODVDBlockStorageDriver"); if (obj) { return true; } var obj2 = system.ioregistry.matchingName("ApplePlatformEnabler","IOService"); if (obj2 ) { if ( obj2[0][‘DVDSupported’] ) { return true; } } return false; } function hasNetInfo() { var path = my.target.mountpoint + "/private/var/db/netinfo/local.nidb"; if (system.files.fileExistsAtPath(path)) { return true; } return false; } </script> <installation-check script="installCheckScript()"/> <volume-check script="volCheckScript()"/> <choices-outline> <line choice="EssentialSystemSoftware"> <line choice="EssentialSystemSoftwareGroup"/> <line choice="AdditionalEssentials"/> <line choice="AdditionalSpeechVoices"/> <line choice="AsianLanguagesSupport"/> <line choice="MediaFiles"/> … 途中省略
start_enabled="sourceVolumeHasServerSoftware() &amp;&amp; hasServerAppInstalled() &amp;&amp; isEmptyTargetVolume()" start_selected="targetVolumeHasServerSoftware()"> <pkg-ref id="com.apple.pkg.ServerEssentials"/> </choice> <script> function hasServerAppInstalled() { var bundle = system.files.bundleAtPath("/Applications/Server.app"); if (bundle) { return true; } return false; } function sourceVolumeHasServerSoftware() { var plist = system.files.fileExistsAtPath("/System/Library/CoreServices/ServerVersion.plist"); if( plist ){ return true; } return false; } function targetVolumeHasServerSoftware() { var mp = my.target.mountpoint; var plist = system.files.fileExistsAtPath(mp + "/System/Library/CoreServices/ServerVersion.plist"); if( plist ){ return true; } return false; } function isEmptyTargetVolume() { var mp = my.target.mountpoint; var plist = system.files.fileExistsAtPath(mp + "/System/Library/CoreServices/SystemVersion.plist"); if( plist ){ return false; } return true; } </script> <pkg-ref id="com.apple.pkg.AdditionalSpeechVoices" auth="root">AdditionalSpeechVoices.pkg</pkg-ref> <pkg-ref id="com.apple.pkg.AddressBook" auth="root">AddressBook.pkg</pkg-ref>
… 途中所略
<pkg-ref id="com.apple.pkg.ServerEssentials" installKBytes="357576" version="10.7.0.1.1.1309412550"/> <system-image id="com.apple.dmg.MacOSX" version="10.7.0.1.1.1309412550" sha1="8b973cf20e1c44109726039551803ae6ce226f5f" external-products="11A511_ServerEssentials"/> </installer-gui-script> [/sourcecode]

インストールスクリプト “Distribution” を書き換えてみたが...


“preflight.pfpkg” のインストールスクリプト”Distribution” を書き換えて、機種チェックで引っ掛からないように修正を施し、元の”preflight.pfpkg”に戻してから AppStore アプリケーションで Lionのダウンロードを試みてみたが、これまでと同じようにこの機種にはダウンロードできないというつれないエラーメッセージが返されるだけだった.

Download Failed
ダウンロード失敗

またしても失敗!!!
AppStoreの作業ディレクトリを調べて見ると、”preflight.pfpkg”のタイムスタンプが更新されていた.念のため”preflight.pfpkg”を解凍し”Distribution” ファイルの中身を調べてみると先程修正を施した部分が完全に消えていた.どうやらAppStoreは毎回”preflight.pfpkg”をダウンロード仕直してしまうようだ.


そこで再度”preflight.pfpkg”に修正を施し、今度はファイルを書き込み禁止(0644 → 0444)にしてみたところ、今度はエラーダイアログが現れずにダウンロードが進行しているような ”ぐるぐるカーソル” が現れ、何となくダウンロードが行われような雰囲気の画面になった.


果たしてLion非対応マシンでのMac AppStoreからのダウンロードは成功するのだろうか?


Downloading
ひょっとしてダウンロード成功か?

やはり駄目みたいです


”ぐるぐるカーソル”は回っているが、作業ディレクトリには一向にファイルがダウンロードされている気配はない.しかもネットワークSWのポートのLEDランプを見る限り、大量のデータをダウンロードしている気配もない.


どうやら”preflight.pfpkg”を更新することができずにAppStoreプログラムが次のステップに進むことができないだけのようだ.


“preflight.pfpkg” を瞬間的に入れ替えて AppStoreプログラムを騙してみた


AppStoreプログラムでLionのインストールボタンを押すと、毎回新たな”preflight.pfpkg”ファイルが作成されて、その中身に従ってダウンロード作業が行われてしまうので、”preflight.pfpkg”に含まれているインストールチェックスクリプトを書き換えてもスクリプト内の機種チェックを回避することができない.


AppStoreプログラムのダウンロード手順としては、”preflight.pfpkg”をダウンロードした後、このファイルを解凍しインストールチェックスクリプトの “Distribution” を実行してLionのインストール対象機種かどうかをチェックしているようだ.従って”preflight.pfpkg”が手元のMacの作業場所 “~/Library/Application Support/AppStore/444303913” にダウンロード完了した直後に、この”preflight.pfpkg”の中身をそっくり予めスクリプトに修正を施したバージョンに入れ替えてやれば、AppStoreプログラムは修正を施したバージョンの”preflight.pfpkg”を解凍し展開するのではないかと思いテストしてみることにした.


“preflight.pfpkg”の中身をそっくり入れ替える作業はほんの一瞬で行わなければならず、とても人間業でできる作業ではないので、”preflight.pfpkg”が新規書き込みモードで作成されてファイルが一旦クローズされるタイミングを見計らって強制的に中身を入れ替えるスクリプトを perl で書いて実行してみた.


この試み自体は上手く行き、予め用意した修正済みの”preflight.pfpkg”をAppStoreプログラムに解凍・展開させることができたが、今度は中身の整合性(おそらくファイルサイズ)が取れていないので処理をキャンセルする旨のエラーダイアログが表示されて上手く行かなかった.


Data Check Error
Verification Error でまたしても失敗



-r--r--r-- 1 yasuaki admin 59542 Oct 20 22:24 flyingIcon -rw-r--r-- 1 yasuaki admin 652801 Oct 21 00:01 preflight.pfpkg -rw-r--r-- 1 yasuaki admin 655875 Oct 20 23:32 preflight.pfpkg.org

修正版 “preflight.pfpkg” とダウンロードされたオリジナル “preflight.pfpkg.org” のファイルサイズが異なっているのが原因かもしれない.


“xar -xvf preflight.pfpkg” で解凍し、Distributionファイルを修正した後、再び “xar -cvf preflight.pfpkg Distribution OSInstall.mpkg Resources” で圧縮すると何故か 3k Byte 近くもファイルサイズが異なってしまう.


セキュリティー面を考えると、ユーザがダウンロードしたコンテンツがサーバ側のコンテンツと同一であることをチェックするのは当たり前の事ですから、この改竄チェックの抜け道を探るのは困難でしょう.Lion非対応機種でのMacApp StoreからのLionインストーラのダウンロードは諦めた方が良さそうですね.


機種IDやboard-id を偽装することは可能か?


“preflight.pfpkg” を入れ替えることが駄目なら、このスクリプトがチェックしている”board-id” 自体を偽装することができれば問題は解決するのですが、”board-id”のデータがどこに記述されているのか詳細を知ることは恐らくできないでしょう.Serial Numberと同じように マザーボードの不揮発RAM領域のどこかに記載されているのでしょうが、残念ながら現時点ではこの領域にアクセスする方法が分かりません.


Mac OS X(BSD系のOS) では “/usr/sbin/sysctl” という BSD System Manager に関するコマンドがありますが、”preflight.pfpkg”が稼働するJavascript ランタイム(JavascriptCore Framework)では”/usr/sbin/sysctl”とは別に実装されているようで、”/usr/sbin/sysctl”は一切使っていないようです.


“/usr/sbin/sysctl”を使っていれば、このコマンドにラッパーを被せることでコマンドの結果を簡単に偽装できるのですが、JavascriptCore Frameworkの中で独自に実装されていては簡単には手が出せません.


Lion非対応マシンでAppStoreからのダウンロードは諦めて、Lion対応マシンでダウンロードだけを行い、それを改造してLion非対応マシンにインストールするしか方法はなさそうです.(v_v)