国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何實現一個IOS網絡監控組件

xbynet / 2955人閱讀

摘要:此文由作者朱志強授權網易云社區發布。代碼演示核心代碼將原方法放在中聲明函數指針實現函數使用系統方法的函數指針完成系統的實現在這里獲取到了系統方法調用的時機在程序啟動后調用對委托模型的監控替換方法時需要指定類名,而的的類并不確定。

此文由作者朱志強授權網易云社區發布。

Mobile Application Monitor IOS組件設計技術分享

背景
應用程序性能管理Application Performance Management(APM)是近年來比較火的互聯網產業, Mobile Application Monitor(MAM)是其核心功能之一。 APM主要指對企業的關鍵業務應用進行監測、優化,它可以提高企業應用的可靠性和質量,保證用戶得到良好的服務,降低IT總擁有成本(TCO)。 一個企業的關鍵業務應用的性能強大,可以提高競爭力,并取得商業成功,因此,加強應用性能管理可以產生巨大商業利益。 目前成熟的產品有:

目標
iOS客戶端的網絡統計組件,用于統計iOS app的http請求的數據,如請求時間,數據,錯誤

設計一個可復用的框架,方便后續添加幀率、用戶體驗等監測內容

對應用的影響盡可能小,使用方便

設計模型
處理數據分4步:
數據收集,數據組裝,數據持久化,數據發送
線程模型:
數據收集負責初始化MAMDataBuilder,在持久化層隊列完成數據組裝和數據庫插入操作。
滿足發送數據條件時,首先持久化層隊列從數據庫查找數據,然后在發送層隊列中發送數據,發送結束后在持久化層隊列刪除該條數據,再處理下一個數據。
下圖使用圖形演示了程序執行過程,灰色矩形代表API接口

本文主要針對常用網絡技術的攔截技術做全面細致的講解和分析。

數據收集Hooker
針對IOS主要的網絡技術:NSURLConnection和CFNetwork的HTTP請求做數據收集

NSURLConnection的hook
對Objective-C對象發送消息的攔截

技術背景

Runtime
Objective-C是一門運行時語言,它會盡可能地把代碼執行的決策從編譯和鏈接的時候,推遲到運行時。 這樣對寫代碼帶來很大的靈活性,比如說可以把消息轉發給你想要的對象,或者隨意交換一個方法的實現。 Method Swizzling正是使用交換方法實現的方式來達到hook的目的。

動態綁定
在編譯的時候,我們不知道最終會執行哪一些代碼,只有在執行的時候,通過selector去查詢,我們才能確定具體的執行代碼。
Objective-C的方法類型是SEL(selector)。實例對象performSelector時,會在各自的消息選標(selector)/實現地址(address) 方法鏈表中根據 selector 去查找具體的方法實現(IMP), 然后用這個方法實現去執行具體的實現代碼。

IMP類型
IMP 是消息最終調用的執行代碼的函數指針,可以理解為Objective-C的每個方法都會在編譯時被轉換成C函數,IMP就是這個C函數的函數指針,下面會演示調用這個IMP和調用Objective-C方法是等效的。 一個Objective-C方法:

-(void)setFilled:(BOOL)arg;
它的Objective-C調用方式會是:

[aObject setFilled:YES];
調用基類NSObject的方法- (IMP)methodForSelector:(SEL)aSelector得到IMP

void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];
等價的C調用是對IMP(函數指針)的調用:

setter(self, @selector(setFilled:), YES)
Method Swizzling
正常情況,我們無法知道系統方法在何時被調用,但替換掉系統方法的代碼實現,就可以獲取系統方法的調用時機,這就是Method Swizzling!
如下圖,修改selector對應的IMP為保存原IMP的函數,這樣就實現了對系統調用的hook。

代碼演示
Method Swizzling核心代碼:

BOOL HTSwizzleMethodAndStore(Class class, BOOL isClassMethod, SEL original, IMP replacement, IMP* store) {
IMP imp = NULL;
Method method ; if (isClassMethod) {

  method= class_getClassMethod(class, original);

}else{

  method= class_getInstanceMethod(class, original);

} if (method) {

  imp = method_setImplementation(method,(IMP)replacement);      if (!imp) {
      imp = method_getImplementation(method);
  }

}else{

  MAMLog(@"%@:not found%@!!!!!!!!",NSStringFromClass(class),NSStringFromSelector(original));

} if (imp && store) { *store = imp; }//將原方法放在store中
return (imp != NULL);
}
聲明函數指針IMP store,實現函數MAM IMP

static NSURLConnection (Original_connectionWithRequest)(id self,

                                                SEL _cmd,                                                    NSURLRequest *request,                                                    id delegate);static NSURLConnection * MAM_connectionWithRequest(id self,
                                                  SEL _cmd,                                                      NSURLRequest *request,                                                      id delegate){  //使用系統方法的函數指針完成系統的實現

id result = Original_connectionWithRequest(self,

                                    _cmd,
                                    request,
                                    hookDelegate);//在這里獲取到了系統方法調用的時機

return result;
}
在程序啟動后調用Swizzling

HTSwizzleMethodAndStore(NSClassFromString(@"NSURLConnection"),

                      YES,                          @selector(connectionWithRequest:delegate:),
                      (IMP)MAM_connectionWithRequest,
                      (IMP *)&Original_connectionWithRequest);

對委托模型的監控
Runtime替換方法時需要指定類名,而NSURLConnection的delegate的類并不確定。如果還是使用Method Swizzling攔截delegate的消息,每多一個使用NSURLConnectionDelegate的類都需要動態聲明一次IMP store和MAM IMP,效率太低。
解決辦法是使用proxy delegate替換NSURLConnection原來的delegate。只要保證proxy delegate將所有接收到的網絡回調,轉發給原來的delegate就好了。

CFNetwork的hook
對C函數調用的攔截

技術背景

使用Dynamic Loader hook 庫函數 ---- fishhook
Dynamic Loader (dyld)通過更新Mach-O文件中保存的指針的方法來綁定符號。借用它,可以在運行時修改C函數調用的函數指針!
fishhook查找函數符號名的過程見下圖

上圖中,1061是間接符號表(Indirect Symbol Table)的偏移量,存放的符號表(Symbol Table)偏移量16343。
符號表中包含了字符表(String Table)偏移量,然后找到中真實符號名(Actual Symbol Name),fishhook對間接符號表的偏移量做了修改,這樣就修改了字符表偏移量,指向字符表中的真實符號名發生了變化,最終,通過修改真實符號名修改了真實調用函數的指針,達到hook的目的。

Stream的read size和Toll-Free Bridge
CFNetwork使用CFReadStreamRef做數據傳遞,其接收服務器響應的方式是使用回調函數。獲取服務器數據的方式是,當回調函數收到流中有數據的通知后,從流中讀取數據,保存在客戶端內存中。
對流的讀取不適合使用修改字符串表的方式,這樣做需要hook 系統也在使用的read函數,而系統的read函數不僅僅被網絡請求的stream調用,還有所有的文件處理,并且hook一個頻繁調用的函數也是不可取的!
但是怎么才能只針對網絡請求的stream做處理呢,對一個C類型真的是很難,但是倘若對一個對象而言,我們有很多辦法可以用,能不能轉換呢?
能,用Toll-Free Bridge!有了它,就可以將CFReadStreamRef類型直接轉換成NSInputStream對象!!
Toll-Free Bridge可以將Cocoa對象轉換為CoreFoundation類型,查看CFReadStreamRead源碼:

CFIndex CFReadStreamRead(CFReadStreamRef readStream, UInt8 *buffer, CFIndex bufferLength) {
CF_OBJC_FUNCDISPATCH2(__kCFReadStreamTypeID, CFIndex, readStream, "read:maxLength:", buffer, bufferLength);
函數的第一行調用的是Cocoa的方法read:maxLength:,這就確認了Toll-Free Bridge的實現機制——用Objective-C實現了一個可以用純C調用的類庫。
最后,這樣設計被監控的stream:
這樣就成功地將hook一個C函數的問題轉變成了hook一個Objective-C方法的問題,但是,NSInputStream仍然是一個底層的公共類,仍然需要對系統的read方法做hook,能不能只針對某個stream對象進行hook呢?
能,用Trampoline!

Objective-C消息轉發機制和Trampoline ---- 對指定對象的hook
當某個實例對象接收到一個消息,但是沒有找到這個消息的實現時,會調用下面的兩個方法,給開發者提供了轉發消息的選擇

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
借用轉發機制,可以實現對指定對象的hook:
設計一個繼承自NSObject的Proxy類,持有一個NSInputStream,記為OriginalStream。
使用上面的方法中將發向Proxy的消息轉發給OriginalStream。這樣一來,所有發向Proxy的消息的都由OriginalStream處理了。再重寫NSInputStream read方法就可以獲取到stream的size了。這種修改程序執行方向的設計就稱為Trampoline,它的本意是蹦床,象征著將方法反彈給真正的接收對象。
MAMNSStreamProxy的核心代碼:

-(instancetype)initWithClient:(id*)stream
{if (self = ![super init])
{
_stream = ![stream retain];
}return self;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{return ![_stream methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
![anInvocation invokeWithTarget:_stream];
}
-(NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
{
NSInteger rv = [_stream read:buffer maxLength:len];//在這里記錄sizereturn rv;
}
代碼演示
和Method Swizzling類似,需要聲明函數指針和函數的實現:

static CFReadStreamRef(*original_CFReadStreamCreateForHTTPRequest)(CFAllocatorRef alloc,

                                       CFHTTPMessageRef request);/**

MAMNSInputStreamProxy持有original CFReadStreamRef,轉發消息到original CFReadStreamRef,在方法 read 中獲取數據大小。

以original CFReadStreamRef為鍵,保存CFHTTPMessageRef request

*/static CFReadStreamRefMAM_CFReadStreamCreateForHTTPRequest(CFAllocatorRef alloc,

                               CFHTTPMessageRef request){ //使用系統方法的函數指針完成系統的實現

CFReadStreamRef originalCFStream = original_CFReadStreamCreateForHTTPRequest(alloc,

                                                                          request); //將CFReadStreamRef轉換成NSInputStream,保存在MAMNSInputStreamProxy中,返回的時候再轉換成CFReadStreamRef

NSInputStream stream = (__bridge NSInputStream)originalCFStream;
MAMNSInputStreamProxy outReadStream = ![![MAMNSInputStreamProxy alloc] initWithStream:stream]; /內存管理, create的CF stream ref轉成NS stream proxy,CF不再引用,使用結束后release掉*/
CFRelease(originalCFStream); /內存管理,ARC轉交引用管理給CF/
CFReadStreamRef result = (__bridge_retained CFReadStreamRef)((id)outReadStream); return result;
}
使用fishhook替換函數地址

save_original_symbols();int bFishHookWork = rebind_symbols((struct rebinding![1])
{{"CFReadStreamCreateForHTTPRequest", MAM_CFReadStreamCreateForHTTPRequest},},1);
void save_original_symbols(){
original_CFReadStreamCreateForHTTPRequest = dlsym(RTLD_DEFAULT, "CFReadStreamCreateForHTTPRequest");
}
數據攔截模型
根據CFNetwork API 的調用方式,使用fishhook和proxyStream獲取C函數的設計模型如下:

更多網易技術、產品、運營經驗分享請訪問網易云社區。

文章來源: 網易云社區

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25330.html

相關文章

發表評論

0條評論

xbynet

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<