坑爹的私有API


iOS私有API掃描工作總結

背景

蘋果提供的iOS開發框架分PrivateFramework和Framework,PrivateFramework下的庫是絕對不允許在提交的iOS應用中使用的,只允許使用Framework下那些公開的庫。除了不能引入私有的庫,也不能使用私有的API。如果你做了,結果很明顯,你的應用就會被拒掉。

下面是幾個被拒的案例:
  1. 案例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. 案例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. 案例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來做個總結了。

小結
  1. 私有的api = (class-dump Framework下的庫生成的頭文件中的api - (Framework下的頭文件里的api = 有文檔的api + 沒有文檔的api)) + PrivateFramework下的api
  2. 私有的api在公開的Framework及私有的PrivateFramework都有。

構建私有API庫

既然已經知道的哪些是私有的api及私有api的位置,就可以建立私有api的專用數據庫了。

具體步驟:

  1. class-dump對所有的公開庫(/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks)進行逆向工程得到所有的頭文件內容。提取每個.h文件中的api得到api集合set_A
  2. 獲得帶文檔的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
  3. 現在需要獲得那些沒有文檔的公開api了,從上面的分析知道他們存在於公開的庫的頭文件中,所以要對這些公開的頭文件進行掃描,提取那些沒有文檔的api,這里得到的集合會第2步得到的集合有很大部分是重復的,這不影響最終結果,得到集合set_C
  4. 得到最終的私有api集合 set = set_A - set_B - set_C

針對xxxx進行具體的掃描工作

  1. 將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字符串,可以通過掃描代碼提取這部分字符串。
  2. 掃描源碼,獲得hardcode字符串,得到結果集str_set
  3. 獲得程序中自己定義的方法。這里使用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)了。
  4. 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
  5. 查看應用都使用了哪些庫,otool -L ../.../xxxx會看到使用的庫set_Libs
  6. 從上面建立的私有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]
    
  7. 現在程序中自己定義的方法已經掃描結束了,但是如果程序中引用第三方庫呢,還要掃描所用的第三方庫是否用了私有的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, 但是結果還不是非常滿意,需要人工來判斷。還是需要再找找其他的方法,優化掃描結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM