#追記
最初に追記を書かせて頂きます。
今回の記事の内容を利用して申請したアプリは一度リジェクトされましたが、プライベートAPIを利用したことに関して一切のことは書かれておらず、そこに関してはお咎めなしでした。
ですので、「やむを得ない場合」は是非この記事を参考にしてください。
(アプリ内で利用するデータはiCloudのバックアップの対象から外すようにとの内容でした。どうやらiCloudのバックアップ対象にするのはアプリを通じてユーザーが作成したドキュメントくらいが妥当だそうで、iCloudのバックアップはファイル、ディレクトリの保存場所にも依るかもしれませんがデフォルトではYESのようです。)
##さらに後日談
申請も通りましたので、結果実際にこの方法を利用したアプリは審査を通過したということです。
ちなみに今回の記事で例にしているバイブレーションに関する実装を使用しました。
#プライベートAPIとは
iOS, OSX向けアプリの開発では基本的にアップルが用意したAPIを使用し開発する。
マルチプラットフォームだのなんだのを使っても結局は内部でこれらが使われていたり、WebViewにウェブ言語で開発したものを映したりしていると思われるので要するにAPIで出来ないことは、出来ないということになると思う。
通常、APIはC/C++/Objective-Cのヘッダーにリストされていて、それらのみを使うことができる。
ボディファイルはバイナリになっているためソースは見れない。
しかしそのボディファイルの中にもコンパイル前は記述されていたであろうプライベートなメソッドがある。
Javaで言うところのprivate、Objective-Cで言うところのボディファイル上部に空っぽのカテゴリ名のinterface内にプロトタイプを宣言した関数のことである。
アップルのAPIはヘッダファイルのみ見ることができるので、つまりはそのプライベート関数は使えないし、アップルからすればその存在を知る必要なしということである。
#なぜプライベートAPIに着目するのか
公開されていない機能は使えない。つまりプライベートAPIでしか出来ないこともある。
例えば、バイブレーションを任意の時間作動させること。
アップルが通常使わせてくれるバイブレーションのAPIは特定の時間振動させるものであり、その長さを選ぶことはできない。
これは大変クレイジーなことである。
例えばゲーム開発でバイブレーションを効果的に使いたくても不必要に長いバイブを阿呆みたいに震わせることしか出来ない。
が、しかしプライベートAPIを使えば複雑な振動パターンを実現することができるのだ。
#プライベートAPIの探し方
まずはHomebrewでclass-dumpをインストール。
brew install class-dump
使い方はclass-dumpというキーワードでググると、ご丁寧に自然とアップルのプライベートAPI探しの情報ばかりに行き着く。
なので使い方はそちらにお任せする。
#こっそりと使う
そんなプライベートAPIだがこれを使用して開発したアプリはリジェクトされる。
アプリはリリースしてなんぼ。なのでこっそり使うことにしたいのだが、アップルはどのようにして該当アプリがプライベートAPIを使っているかを調べているのか。
簡単に説明するとnm, otoolなどのコマンドを使っている。
これらはxCodeのコマンドラインツールに含まれるためHomebrewで入れる必要がない。
使い方は
nm アプリ名.app/アプリ名
otool アプリ名.app/アプリ名
で使う。
使用していることがばれていないか調べるため、パイプで使用した関数名でgrepしてやり、その関数名が検出されなければいい。
(使っているAPIや自分で定義した関数名やクラス名などが全て見られてしまうということは、先ほどのclass-dumpを使えば逆にアップルのフレームワーク内のそれらを全て見ることが出来るということである。)
#どうすれば使用していることを隠蔽できるか
オーバー風呂で話し合ってきた。
http://stackoverflow.com/questions/24081115/get-over-apples-preview-even-if-using-private-apis
##まずObjective-C関数なら
これは話し合うまでもなく簡単だ。
[obj performSelector:NSSelectorFromString(@"メソッド名")];
でいける。
と思いきやまだ詰めが甘い。
上記のやり方だと文字列でメソッドにたいするセレクタを取得し呼び出してやるやり方だが、
さきほど紹介したリバースエンジニアリングコマンドに加えてstringという恐ろしいコマンドがある。
nmやotoolと同じく
string アプリ名.app/アプリ名
という使い方だったはず。
アップルさんはこいつも使って文字列も検閲されるそうだ。
なのでURLエンコードするなりして文字列を暗号化してしまうといい。
自分は独自の暗号化、複合用のクラスを作って以下のようにしている。
[obj performSelector:NSSelectorFromString([WPCrypt decrypt:@"暗号化された文字列"])];
念のため、文字列などはインスタンスなどに格納せず一行で書ききっている。
ランタイム時に発見される隙を少しでも小さくするためだ。
(最近のコンパイラはお利口さんだそうで、最適化の段階でどう解釈されているかまでは面倒で調べていない。)
##C関数なら
C関数に関しても隠蔽可能だが、Objective-Cと少し要領が違う。
Cにはクラスという概念がないため使いたい関数をSelectorで指定出来ない。
文字列で呼び出してやる方法はないものか?
ありました。
// thatApi = AudioServicesPlaySystemSoundWithVibration
void (*thatApi)(int, void *, id);
thatApi = dlsym(RTLD_SELF, [WPCrypt decrypt:@"暗号化された文字列"].UTF8String);
thatApi(kSystemSoundID_Vibrate, nil, data);
これが例のバイブレーションAPIの呼び出しです。
NSStringはUTF8Stringメソッドでconst char *に変換できる。
もちろんここでも文字列は暗号化しておく。
最初の行ではメソッドを格納するための変数を宣言しているが、
使いたいC関数の戻り値 (*適当な変数名)(その関数の引数をコンマ区切り)
の形で宣言してやればいい。
上記の例のdataの部分はNSArray *型だが、Objective-Cのクラス型はここでは都合が悪いのでとりあえず全部idとしておけばオッケー。
intやvoid *みたいなCでも通用するやつは使って良いらしい。
最後の行でコメントにもあるようにAudioServicesPlaySystemSoundWithVibrationを呼び出している。
つまり
AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, data);
と等価である。
#〆
以上ではありますが、突っ込みどころ(プライベートAPI使うなや以外)がありましたら教えてください。
プライベートAPIを探す情報はネットにいくつかありましたが、こっそり使う方法、隠蔽する方法はなかったので、知恵のある外人さんと考えました。
Objective-CのAPIに関しては比較的簡単にできるのですが、C関数に関してはなかなか欲しい情報にたどり着くまでに時間がかかりました。
ハックな方々とこの情報を共有できれば幸いです。