iOS私有API掃描工作總結
背景
蘋果提供的iOS開發框架分PrivateFramework和Framework,PrivateFramework下的庫是絕對不允許在提交的iOS應用中使用的,只允許使用Framework下那些公開的庫。除了不能引入私有的庫,也不能使用私有的API。如果你做了,結果很明顯,你的應用就會被拒掉。
下面是幾個被拒的案例:
-
案例1(來源於網絡)
Thank you for submitting your update to xxx to the App Store. During our review of your application we found it is using a private API, which is in violation of the iPhone Developer Program License Agreement section 3.3.1;
"3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs."While your application has not been rejected, it would be appropriate to resolve this issue in your next update."3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs."
The non-public API that is included in your application is
terminateWithSuccess.If you have defined a method in your source code with the same name as the above mentioned API, we suggest altering your method name so that it no longer collides with Apple's private API to avoid your application being flagged with future submissions.
Please resolve this issue in your next update to xxx.
Regards,
iPhone Developer Program
很幸運,使用了重名的API但是沒被拒,不過Apple還是建議下次更新時要修改那個API的名字。
-
案例2(來源於網絡)
We found that your app uses one or more non-public APIs, which is not in compliance with the App Store Review Guidelines. The use of non-public APIs is not permissible because it can lead to a poor user experience should these APIs change.
We found the following non-public API/s in your app:
allowsAnyHTTPSCertificateForHost:If you have defined methods in your source code with the same names as the above-mentioned APIs, we suggest altering your method names so that they no longer collide with Apple's private APIs to avoid your application being flagged in future submissions.
Additionally, one or more of the above-mentioned APIs may reside in a static library included with your application.If you do not have access to the library's source, you may be able to search the compiled binary using "
strings" or "otool" command line tools. The "strings" tool can output a list of the methods that the library calls and "otool -ov" will output the Objective-C class structures and their defined methods. These techniques can help you narrow down where the problematic code resides.這位就沒那么幸運了,被拒了,不過Apple還算人性化,提供了方法來解決辦法。
-
案例3(XXX)
同樣是因為 使用了一個私有的API,不過我們感覺有點冤,我們只是放那里並沒有調用,
[self performSelector:@selector(_define:) withObject:obj afterDelay:10],一切被拒的原因都是因為_define:,這個api是私有的,蘋果的文檔里沒有這個api,也不是我們自己定義的函數,被認為調用了私有api。
這些都是教訓啊,在今后iOS過程中要避免因為私有api問題而被拒啊。可怎么避免呢?哪些api又是私有的呢?私有的api又放在哪里呢?
鑒於以上的問題有了這里針對私有api掃描的探索工作。
漫漫探索路
哪些是私有的api?私有的api又放在哪里?
蘋果官方也沒有結出明確的定義,我們估且從反面來考慮,官方給出了有文檔的api(也就是建議開發者使用的),同時也有提及沒有文檔的api(不建議使用的,可能隨時會被修改或移除)。帶文檔的api我們可以方便的從Xcode的幫助里查看,不帶文檔的從幫助文檔當然是查不到的,但是在Framework下的頭文件里會有聲明,可以從相應頭文件里查看,例如valueForPasteboardType:,存在於UIKit.framework/UIPasteboard.h里。
難道api就只有這些嗎?答案肯定不是嘍。我們還沒有找到私有的api呢。在前面我們有提到PrivateFramework,這下面的庫都是私有的,蘋果不允許你使用的,當然里面定義的方法也自然成為了私有api的一份子。除了PrivateFramework下的api,其他的在哪呢?當然是在公開的讓你使用的Framwork下了,只是沒有公開,你看不到罷了。但是看不到不代表不存在啊。我們只使用了公開的庫,確以使用私有api的理由拒我們。那所使用的私有api來自何方?必須來自公開的庫啊。
在公開的Framework下定義了很多不為人知的類及方法(一般人我不告訴他^-^)。但是通過一般的方法我們是看不到他們的,那我們就采取點非常手段吧。class-dump上台,class-dump可以查看Mach-O文件里的OC運行時信息。我們就來試試水吧。class-dump請自行解決下載安裝。
class-dump -H /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/ Frameworks/UIKit.framework -o ./UIKit
通過這條命令我們可以得到UIKit下定義的所有類的信息,並在UIKit目錄(隨意指定目錄即可)下生成相應的頭文件。統計下生成的頭文件有13400個,真實情況可能沒有這么多,因為class-dump為每個interface,protocol以及category都會生成一個頭文件,而在*UIKit.framework/Header/\*.h* 下只有137個頭文件,由此可見,即使是公開的Framework,也隱藏很多不為人知的東西。 從我們class-dump出來的頭文件可以看到有些以 _ 開頭,這些是私有的錯不了。我們再窺探下頭文件的內部都有些啥。 下面是`UITextView.h`的部分內容。
#import "UIKeyboardInput.h"
....
@class .....
@interface UITextView : UIScrollView
{
id _private;
NSTextStorage *_textStorage;
...
UIView *_inputAccessoryView;
}
+ (id)_bestInterpretationForDictationResult:(id)arg1;
+ (_Bool)_isCompatibilityTextView;
+ (id)_sharedHighlightView;
@property(readonly, nonatomic) NSTextStorage *textStorage; // @synthesize textStorage=_textStorage;
- (void)_resetDataDetectorsResults;
- (void)_startDataDetectors;
- (id)automaticallySelectedOverlay;
- (void)keyboardInputChangedSelection:(id)arg1;
- (_Bool)keyboardInputChanged:(id)arg1;
- (_Bool)keyboardInputShouldDelete:(id)arg1;
- (void)_promptForReplace:(id)arg1;
- (void)_showTextStyleOptions:(id)arg1;
- (_Bool)_isDisplayingReferenceLibraryViewController;
- (void)_define:(id)arg1;
- (void)dealloc;
- (void)_populateArchivedSubviews:(id)arg1;
- (void)encodeWithCoder:(id)arg1;
- (void)_commonInitWithTextContainer:(id)arg1 isDecoding:(_Bool)arg2 isEditable:(_Bool)arg3 isSelectable:(_Bool)arg4;
- (_Bool)isElementAccessibilityExposedToInterfaceBuilder;
- (_Bool)isAccessibilityElementByDefault;
- (void)drawRect:(struct CGRect)arg1 forViewPrintFormatter:(id)arg2;
- (Class)_printFormatterClass;
@property(nonatomic, setter=_setDrawsDebugBaselines:) _Bool _drawsDebugBaselines;
....
@end
從上面的代碼中可以看到有很多函數是以 _ 開頭的,這些也是私有的,像我們在案例3中提到的_define:,就出現在了這里。但是不以 _ 開頭的也不能說不是私有的,像keyboardInputShouldDelete:這個,不以_開頭,但是是私有的api。由此印證了我們之前的猜測,公開的庫里存在大量的私有api。
通過以上的分析,我們可以對私有api來做個總結了。
小結
私有的api = (class-dump Framework下的庫生成的頭文件中的api - (Framework下的頭文件里的api = 有文檔的api + 沒有文檔的api)) + PrivateFramework下的api。- 私有的api在公開的Framework及私有的PrivateFramework都有。
構建私有API庫
既然已經知道的哪些是私有的api及私有api的位置,就可以建立私有api的專用數據庫了。
具體步驟:
- 用
class-dump對所有的公開庫(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks)進行逆向工程得到所有的頭文件內容。提取每個.h文件中的api得到api集合set_A。 - 獲得帶文檔的api。Xcode自帶幫助文檔,從那里查到的api自然是帶文檔的了,怎么獲得這些文檔呢?記得在Xcode3的時候查個文檔會特別的慢,現在的Xcode5再查某個api的時候很快就會出結果,肯定對api檢索做了優化,在某處放着api的索引。這個想法也是受了
Dash這個工具的啟發,Dash中也可以查看iOS的api,但是幫助文檔不是自己管理的,直接查的Xcode中帶的文檔,按圖索驥找到Xcode的文檔位置(/Users/sngTest/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.AppleiOS7.0.iOSLibrary.docset/Contents/Resources),在里面有個docSet.dsidx的文件,這就是Xcode針對api做的數據庫,從這里可以獲得帶文檔的api的各種信息了,從而有了帶文檔的api集合set_B。 - 現在需要獲得那些沒有文檔的公開api了,從上面的分析知道他們存在於公開的庫的頭文件中,所以要對這些公開的頭文件進行掃描,提取那些沒有文檔的api,這里得到的集合會第2步得到的集合有很大部分是重復的,這不影響最終結果,得到集合
set_C。 - 得到最終的私有api集合
set = set_A - set_B - set_C。
針對xxxx進行具體的掃描工作
- 將xxxx.ipa解壓,得到Payload文件夾,用
strings工具對Payload/xxxx.app/xxx(這是個Mach-O文件)掃描,得到程序中可見的字符串strings。這里截取部分strings結果:encodeInt:forKey: rain setRain: initWithFrame: ... _subscribeBtn ... playing gif count statistics error stop gif, total playing count***********(%d) targetOrient Ti,D,N video T@"Video",&,N zoomAspect TB,D,N context T@"EAGLContext",&,N,Vcontext animationInterval Td,VanimationInterval ...
這里的strings結果中有定義的OC方法,屬性,hardcode字符串,及其他編譯時加入的代碼。可以先從已知的進行排除,像hardcode字符串,可以通過掃描代碼提取這部分字符串。 - 掃描源碼,獲得hardcode字符串,得到結果集
str_set。 - 獲得程序中自己定義的方法。這里使用
otool,用nm也可以拿到方法,但是不能拿到屬性及變量,所以這里用otoo拿到方法,屬性及變量。otool -ov ../../../xxxx,截取部分進行說明:Contents of (__DATA,__objc_classlist) section // 這里是程序中自己定義的所有類的開始部分 01dcf12c 0x1f62b30 _OBJC_CLASS_$_QZFlowerInfo isa 0x1f62b44 _OBJC_METACLASS_$QZFlowerInfo superclass 0x1f6b1b8 _OBJC_CLASS$QZBaseWidgetInfo cache 0x0 vtable 0x0 data 0x1dd22c0 (struct class_ro_t *) flags 0x184 RO_HAS_CXX_STRUCTORS instanceStart 4 instanceSize 24 ivarLayout 0x1cc43f6 layout map: 0x41 name 0x1cc43e9 QZFlowerInfo baseMethods 0x1dd2180 (struct method_list_t *) entsize 12 count 13 name 0x1b09ceb encodeWithCoder: //定義的一般方法 types 0x1cd4287 v12@0:4@8 imp 0xaf85 name 0x1b09c38 sun //這里是property sun 的getter方法 types 0x1cd42a2 i8@0:4 imp 0xb38d name 0x1b09c9d setSun: //這里是property sun 的setter方法 types 0x1cd42a9 v12@0:4i8 imp 0xb3a9 name 0x1b09c63 flowerpicurl types 0x1cd42b3 @8@0:4 imp 0xb47d name 0x1b09cda setFlowerpicurl: types 0x1cd4287 v12@0:4@8 imp 0xb499 baseProtocols 0x0 ivars 0x1dd2224 // 這里是定義的變量 entsize 20 count 5 offset 0x1fbb620 4 name 0x1b09c38 sun type 0x1cd42ba i alignment 2 size 4 offset 0x1fbb624 8 name 0x1b09c4e rain type 0x1cd42ba i alignment 2 size 4 offset 0x1fbb628 12 name 0x1b09c53 love type 0x1cd42ba i alignment 2 size 4 offset 0x1fbb62c 16 name 0x1b09c58 fertilizer type 0x1cd42ba i alignment 2 size 4 offset 0x1fbb630 20 name 0x1b09c63 flowerpicurl type 0x1cd42bc @"NSString" alignment 2 size 4 weakIvarLayout 0x0 baseProperties 0x1dd2290 // 這里是定義的屬性 entsize 8 count 5 name 0x1ba4f40 sun attributes x1ba4f66 Ti,N,Vsun name 0x1ba4f44 rain attributes x1ba4f70 Ti,N,Vrain name 0x1ba4f49 love attributes x1ba4f7b Ti,N,Vlove name 0x1ba4f4e fertilizer attributes x1ba4f86 Ti,N,Vfertilizer name 0x1ba4f59 flowerpicurl attributes x1ba4f97 T@"NSString",&,N,Vflowerpicurl ..... Contents of (\_DATA,__objc_catlist) section //這里開始category 01dd1878 0x1ead610 //這里定義了某個類的category, 有定義屬性 name 0x1cccb53 cls 0x0 instanceMethods 0x1ead5a8 entsize 12 count 6 name 0x1b6d83b addInfiniteScrollingWithActionHandler: types 0x1cd669b v12@0:4@?8 imp 0x10feda1 name 0x1b6d862 triggerInfiniteScrolling types 0x1cd429b v8@0:4 imp 0x10ff0e5 name 0x1b6d7a0 setInfiniteScrollingView: types 0x1cd4287 v12@0:4@8 imp 0x10ff19d name 0x1b6d755 infiniteScrollingView types 0x1cd42b3 @8@0:4 imp 0x10ff265 name 0x1b6d7ba setShowsInfiniteScrolling: types 0x1cd4581 v12@0:4c8 imp 0x10ff285 name 0x1b6d87b showsInfiniteScrolling types 0x1cd4454 c8@0:4 imp 0x10ff8dd classMethods 0x0 protocols 0x0 instanceProperties 0x1ead5f8 entsize 8 count 2 name 0x1c388fb infiniteScrollingView // 定義某個類的私有屬性 attributes x1c38911 T@"SVInfiniteScrollingView",R,D,N name 0x1c38933 showsInfiniteScrolling attributes x1c55f92 Tc,N 01dd1784 0x1dd89bc //這里定義了某個類的category, 沒有定義屬性 name 0x1cc4993 cls 0x0 instanceMethods 0x1dd897c entsize 12 count 2 name 0x1b1269c fontHeight types 0x1cd51a0 f8@0:4 imp 0xbf21d name 0x1b126a7 defaultLineHeight types 0x1cd51a0 f8@0:4 imp 0xbf281 classMethods 0x1dd899c entsize 12 count 2 name 0x1b126b9 boldSystemFontVerdanaOfSize: types 0x1cd5d02 @12@0:4f8 imp 0xbf17d name 0x1b126d6 systemFontVerdanaOfSize: types 0x1cd5d02 @12@0:4f8 imp 0xbf1cd protocols 0x0 instanceProperties 0x0 ... Contents of (__DATA,__objc_classrefs) section //定義的類 01f5e8ec 0x1f6c3b0 _OBJC_CLASS_$_xxxxGuidePageView 01f5e8f8 0x1f66cd0 _OBJC_CLASS_$_xxxxGuideView 01f5e900 0x1f780e8 _OBJC_CLASS_$_WnsLogger ... Contents of (__DATA,__objc_superrefs) section //定義的父類 01f60b4c 0x1f62b30 _OBJC_CLASS_$_QZFlowerInfo 01f60b50 0x1f62b58 _OBJC_CLASS_$_QQGuideWindow 01f60b54 0x1f62b80 _OBJC_CLASS_$_xxxxNewFeedDetailManager 01f60b58 0x1f62ba8 _OBJC_CLASS_$_inputBarCacheObject ...從上面的分析可以提取出方法,變量(set_B_i),屬性(set_B_p),類名(set_B_c)了。 - 用
nm ../../xxxx得到Mach-O中的符號表。0116f0ac t +[AFHTTPClient clientWithBaseURL:] 0117cb08 t +[AFHTTPRequestOperation acceptableContentTypes] 0117c7c8 t +[AFHTTPRequestOperation acceptableStatusCodes] ... 011e20fc t +[NSNumber(uniAttribute) boolValueWithName:inAttributes:] 011e2288 t +[NSNumber(uniAttribute) charValueWithName:inAttributes:] 011e2a90 t +[NSNumber(uniAttribute) doubleValueWithName:inAttributes:] ...
很容易提取出類名與對應的方法,set_C。 - 查看應用都使用了哪些庫,
otool -L ../.../xxxx會看到使用的庫set_Libs。 - 從上面建立的私有api庫中查詢所有屬於
set_Libs的api,與步驟4中得到的方法做一個交集,得到了程序中定義的與私有api重名的那些api(set_Method),至於這些api是否真的會導致被拒,需要人工審核差建立白名單,暫且稱他們為waring_apis。APINAME selectionChanged ------------------------------------------------------------ 庫中定義相同方法的頭文件 UITextInteractionAssistant UITextInteractionAssistant.h UIKit.framework UITextSelection UITextSelection.h UIKit.framework UITextSelectionView UITextSelectionView.h UIKit.framework UIWebDocumentView UIWebDocumentView.h UIKit.framework UIWebSelection UIWebSelection.h UIKit.framework UIWebSelectionAssistant UIWebSelectionAssistant.h UIKit.framework UIWebSelectionView UIWebSelectionView.h UIKit.framework ------------------------------ 程序中定義該方法的類 => [DLTextView selectionChanged] => [RichTextView selectionChanged] => [DLTextContainerView selectionChanged]
- 現在程序中自己定義的方法已經掃描結束了,但是如果程序中引用第三方庫呢,還要掃描所用的第三方庫是否用了私有的api,像之前facebook開源的Three20框架,因定義了很多與私有api重名的方法,導致很多基於該框架的應用被拒。從第一步中得到的
rest_str = strings - str_set - set_B_i - set_B_p - set_B_c。將rest_str與步驟6中查詢得到的api列表相交,這個結果集(set_Rest)就是來自第三方的庫,但是如果沒有第三方庫的源碼,不容易判斷這些交集是屬於方法還是hardcode的字符串。為進一步確定他們是來自哪個庫,可以strings每個三方庫,然后與set_Rest相交,得到每個三方庫中命中的strings。如:WnsSDK -------------------------------------------------- pasteboard random tag: reconnect table apply pointer generator add call take postalAddress read emailAddress pair now types Comment Secure bind adapter curve remove signature order
以上即當前對xxxx進行的掃描工作。
總結
通過以上各種方法,雖然可以看到某些可能“危險”的api, 但是結果還不是非常滿意,需要人工來判斷。還是需要再找找其他的方法,優化掃描結果。
