應(yīng)用場景
在了解iOS逆向工程之前须鼎,我們有必要了解它究竟能做什么,在開發(fā)中能夠獲得哪些幫助府蔗?個人覺得有以下四點
- 促進(jìn)正向開發(fā)晋控,深入理解系統(tǒng)原理
- 借鑒別人的設(shè)計和實現(xiàn),實現(xiàn)自己的功能
- 從逆向的角度實現(xiàn)安全保護(hù)
- 篡改他人的App(不推薦)
以上四點有自己正在學(xué)習(xí)的東西姓赤,也有已經(jīng)有了一些了解的東西赡译,當(dāng)我們知道iOS逆向工程究竟能做什么的時候,下一步就到了應(yīng)該How do的階段不铆,我把How do it蝌焚?根據(jù)不同的目地列出以下幾個步驟裹唆。
- 解密、導(dǎo)出應(yīng)用程序只洒、導(dǎo)出頭文件许帐,為后續(xù)工作做準(zhǔn)備
- 從應(yīng)用界面表現(xiàn)入手,獲取當(dāng)前界面布局及控制器
- hook發(fā)現(xiàn)的一些相關(guān)類毕谴,記錄輸出調(diào)用順序及參數(shù)
- 找到關(guān)鍵函數(shù)成畦,查看調(diào)用堆棧,hook測試效果
- 靜態(tài)分析加動態(tài)調(diào)試分析關(guān)鍵函數(shù)的實現(xiàn)邏輯
- 模擬或篡改函數(shù)調(diào)用邏輯
- 制作插件
并移植到非越獄機器(正在研究)
零涝开、準(zhǔn)備工作
在砸殼之前循帐,需要做很多準(zhǔn)備工作,首先要準(zhǔn)備一臺越獄的設(shè)備舀武,在越獄過程中拄养,會安裝一個叫作Cydia的軟件,相當(dāng)于越獄設(shè)備的AppStore奕剃,可以在上面查找安裝各類軟件包。
有了越獄設(shè)備之后捐腿,接下來就要安裝各種有助于逆向的插件纵朋。
1.SSH:用于遠(yuǎn)程登錄越獄設(shè)備、執(zhí)行命令茄袖,iOS10以前需要在Cydia上安裝openSSH操软,iOS10之后需要安裝dropbear.
2.Filza:方便的查看iOS的文件目錄。
3.Cydia Substrate:一個允許第三方開發(fā)者在越獄系統(tǒng)方法里的加一些運行時補丁和擴(kuò)展方法宪祥,是越獄開發(fā)的基石聂薪。
4.adv-cmds:一個命令行集合工具,提供了很多方便的命令蝗羊。比如后面會用到的ps -e命令藏澳。
5.appsync:直接修改應(yīng)用文件或結(jié)構(gòu)會破壞應(yīng)用的簽名信息,導(dǎo)致修改后的應(yīng)用無法安裝耀找,所以需要appsync補丁讓系統(tǒng)不在驗證應(yīng)用的簽名翔悠。
6.Cycript:使一個允許開發(fā)者使用OC和JS組合語法查看及修改運行時App內(nèi)存信息的工具。
一野芒、解密蓄愁,又稱砸殼
砸殼的工具網(wǎng)絡(luò)上現(xiàn)在主要有兩種Clutch、dumpdecrypted
1. Clutch
Clutch是一個開源的工具狞悲,它會生成一個新的進(jìn)程撮抓,然后暫停進(jìn)程并dump內(nèi)存。
使用上很簡單摇锋,只需要從Git倉庫中拉取項目并編譯丹拯,在Build/Clutch文件目錄下會生成一個叫Clutch的命令行工具忧风。
接下來用scp命里通過ssh遠(yuǎn)程登錄拷貝Clutch到設(shè)備的/usr/bin目錄下,然后通過chmod命令給它附加可執(zhí)行權(quán)限选调。
scp ./Build/Clutch root@10.20.10.197:/usr/bin
//鏈接設(shè)備
ssh root@10.20.10.197
chmod +x /usr/bing/Clutch
這個時候就能通過Clutch破解手機安裝的應(yīng)用了
Clutch -i//顯示手機安裝的應(yīng)用
Clutch -d 應(yīng)用id//通過-d命令對指定應(yīng)用執(zhí)行dump操作暖释,命令執(zhí)行完會沙盒目錄下出現(xiàn)ipa包
接下來同樣用scp命令把對應(yīng)的破解的ipa包拷到手機上,為下一步做準(zhǔn)備剑刑。
2.dumpdecrypted
dumpdecrypted也是一個開源的工具媳纬,與Clutch不同的是它會注入可執(zhí)行文件,動態(tài)地從內(nèi)存中dump出解密后的內(nèi)容(一個完整的ipa包)施掏,相比Clutch它的操作略顯繁瑣钮惠,但成功率很高,Clutch在破解體積較大的App時往往會使手機卡死直至重啟七芭,對不完美越獄來說又得重新越獄一次素挽。
首先也要從Git倉庫拉取項目并編譯,編譯完會生成一個dumpdecrypted.dylib文件狸驳。
接下來需要定位要解密的可執(zhí)行文件预明,把dumpdecrypted.dylib放到App的沙盒目錄Documents下,然后通過DYLD_INSERT_LIBRARIES系統(tǒng)的環(huán)境變量把其注入到應(yīng)用中并開始解密耙箍,最后解密的包會生成在App的應(yīng)用目錄下撰糠。
3.class-dump
class-dump也是個開源的工具,可以從可執(zhí)行文件中獲取類辩昆、方法和屬性信息到工具阅酪,通過頭文件可以快速尋找到想要的方法和屬性。
使用上很簡單汁针,class-dump -H targetApp -o ./Header就可以术辐,-o后跟的是放頭文件的路徑
上面的頭文件可以清楚的看到所有的屬性方法,對分析App很有幫助施无。
4.IDA與Hopper
IDA與Hopper能從解密的應(yīng)用中分析出實現(xiàn)文件(.m文件)進(jìn)行反編譯辉词,兩者的功能類似,但是IDA更強大猾骡,反編譯后的代碼比Hopper容易理解的多较屿,使用上只需要把解密后的文件拖入兩個軟件之中就會自動分析。
兩者相比之下卓练,明顯IDA的更容易讀懂隘蝎。
二、從應(yīng)用界面尋找突入點
這里得提到另一個可以極大提高開發(fā)效率的神器--Reveal襟企,Xcode從7.0開始已經(jīng)集成了Reveal的一部分功能嘱么,但是只能再開發(fā)自己的應(yīng)用時使用。
想要使用Reveal查看手機上的任意應(yīng)用顽悼,首先要在Cydia中安裝Reveal2Loader插件曼振,接著把Reveal程序用自帶的RevealServer.framework導(dǎo)入到越獄設(shè)備的/Library/Framework中几迄,并在設(shè)置中開啟想要查看的應(yīng)用即可。如下圖:
下面我們就Translate Me是如何實現(xiàn)自動識別多語言語音的功能展開接下來的探索冰评。
三映胁、找到關(guān)鍵函數(shù)
在上圖我們可以看到語音識別按鈕的Memory Address,通過此地址我們能通過Cycript進(jìn)一步定位到按鈕的方法甲雅,上文已經(jīng)說過Cycript可以通過OC語法查看App運行時信息解孙,就像下面我們干的一樣。
定位到按鈕調(diào)用的方法為onMicButton:抛人。
四弛姜、分析函數(shù)的實現(xiàn)邏輯
通過查看IDA的onMicButton:的偽函數(shù),可以看到另一個至關(guān)重要的類SDCSpeechViewController的viewModel妖枚,打開頭文件發(fā)現(xiàn)其屬于另一個類廷臼,順藤摸瓜的一路找到了核心類SDCRecognizer!绝页!
當(dāng)看到SFSpeechRecognitionTask荠商、SFSpeechAudioBufferRecognitionRequest、SFSpeechRecognizer這幾個類的時候续誉,我就知道自動識別多語言語音的秘密就在此處莱没,因為這幾個類都是屬于iOS SpeechKit中的核心類,負(fù)責(zé)語音識別的功能屈芜。
這個類肯定通過兩個SFSpeechAudioBufferRecognitionRequest實例保存了不同語言的語音的緩存郊愧,這是我以前實驗過卻沒有成功的地方朴译,然后通過SFSpeechRecognizer和SFSpeechRecognitionTask轉(zhuǎn)換語音對應(yīng)兩種不同語言的文本井佑,根據(jù)processPrimaryResult:和startBackingRecognition這兩個方法名,可以猜測前者識別主語言的語音眠寿,或者識別第二種語言的語音躬翁,并且兩者都調(diào)用了averageConfidence方法,通過averageConfidence這個方法名我有理由去相信其獲取的是語音對應(yīng)不同語言的置信度盯拱。
接下來只要印證它就OK了盒发。
動態(tài)調(diào)試-準(zhǔn)備工作
這里我們可以通過打斷點的方式論證我們的猜想,理清SFSpeechRecognizer的主要函數(shù)的調(diào)用順序狡逢,在iOS開發(fā)中Xcode的斷點調(diào)試是一大利器--LLDB宁舰,當(dāng)Xcode調(diào)試手機App時,Xcode會將debugserver文件復(fù)制到手機中奢浑,以便在手機上啟動一個服務(wù)蛮艰,等待Xcode進(jìn)行遠(yuǎn)程調(diào)試,這里通過修改debugserver文件達(dá)到調(diào)試別人App的目的雀彼。
1.用命令行或者iTools工具把Developer/usr/bin下的debugserver文件拷貝到電腦上壤蚜。
2.用Xcode新建一個entitlements.plist文件即寡,加入以下四個鍵值對
<dict>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
3.通過命令codesign -s - --eentitlements eentitlements.plist -f debugserver給** debugserver**重新簽名
4.把文件拷貝回設(shè)備原目錄并賦予可執(zhí)行權(quán)限
開始調(diào)試
啟動App,鏈接設(shè)備袜刷,通過以下命令即可對目標(biāo)App進(jìn)行調(diào)試
lldb需要獲取App的方法在內(nèi)存中的地址才能夠打斷點聪富,在lldb中可以通過image list -o -f "processName"來獲得App運行的基地址,加上IDA中反編譯的內(nèi)存偏移量著蟹,就能得到App運行時方法的真正地址墩蔓。
接著在lldb輸入c并enter繼續(xù)運行程序,點擊App的語音識別按鈕
可以明顯看到執(zhí)行順序為start->setup->processPrimaryResult->stopRecording-> processPrimaryResult->averageConfidence->startBackingRecognition-> averageConfidence草则,可以看出與我們的猜想基本一致钢拧,并且這個App是識別完一種語言之后在識別另一種語言,這也解釋了為什么App使用時只有說完話才能識別哪種語言的現(xiàn)象炕横,最后我依照SFSpeechRecognizer的邏輯寫了自己的函數(shù)源内,如下:
- (void) configAudioEngine:(void (^)(SFSpeechRecognitionResult *, NSError *, BOOL))resultHandler language:(NSString *) languageCode {
//切換AVAudioSession
AVAudioSession * audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionAllowBluetooth error:nil];
[audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
//設(shè)置語言
NSLocale * locale = [NSLocale localeWithLocaleIdentifier:languageCode];
self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale];
self.speechRecognizer.defaultTaskHint = 1;
NSLocale * targetLocale = [NSLocale localeWithLocaleIdentifier:self.translatedLanguageCode];
self.targetSpeechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:targetLocale];
self.targetSpeechRecognizer.defaultTaskHint = 1;
if (self.recognitionRequest) {
[self.recognitionRequest endAudio];
self.recognitionRequest = nil;
}
if (self.targetRecognitionRequest) {
[self.targetRecognitionRequest endAudio];
self.targetRecognitionRequest = nil;
}
self.recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
self.recognitionRequest.shouldReportPartialResults = YES;
self.targetRecognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
self.targetRecognitionRequest.shouldReportPartialResults = YES;
MJWeakSelf
self.recognitionTask = [self.speechRecognizer recognitionTaskWithRequest:self.recognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
if (result.final) {
NSLog(@"source result === %@", result.bestTranscription.formattedString);
weakSelf.sourceSpeechResult = result;
weakSelf.sourceLanguageConfidence = [weakSelf averageConfidence:result];
[weakSelf recogniTargetLanguage:resultHandler];
}
}];
AVAudioFormat * recordingFormat = [[self.audioEngine inputNode] outputFormatForBus:0];
self.audioEngine = [[AVAudioEngine alloc] init];
[[self.audioEngine inputNode] installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[self.recognitionRequest appendAudioPCMBuffer:buffer];
[self.targetRecognitionRequest appendAudioPCMBuffer:buffer];
}];
[self.audioEngine prepare];
[self.audioEngine startAndReturnError:nil];
}
- (void) closeAudioEngine {
if (self.audioEngine.isRunning) {
[[self.audioEngine inputNode] removeTapOnBus:0];
[self.audioEngine stop];
}
[self.recognitionRequest endAudio];
self.recognitionRequest = nil;
}
- (void) recogniTargetLanguage:(void (^)(SFSpeechRecognitionResult *, NSError *, BOOL source))resultHandler{
MJWeakSelf
self.targetRecognitionTask = [self.targetSpeechRecognizer recognitionTaskWithRequest:self.targetRecognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
NSLog(@"target result === %@", result.bestTranscription.formattedString);
if (result.final) {
float targetConfidence = [weakSelf averageConfidence:result];
if (targetConfidence < weakSelf.sourceLanguageConfidence) {
resultHandler(weakSelf.sourceSpeechResult, nil, YES);
} else {
resultHandler(result, nil, NO);
}
}
if (error) {
NSLog(@"target Recogni error === %@", error);
}
}];
[self.targetRecognitionRequest endAudio];
self.targetRecognitionRequest = nil;
}
- (float) averageConfidence:(SFSpeechRecognitionResult *) result{
NSMutableArray * array = [NSMutableArray array];
NSArray * resultArray = result.bestTranscription.segments;
for (SFTranscriptionSegment * segment in resultArray) {
[array addObject:[NSNumber numberWithFloat:segment.confidence]];
}
float avgConfidence = [[array valueForKeyPath:@"@avg.self"] floatValue];
NSLog(@"result === %@\nConfidence === %f", result.bestTranscription.formattedString, avgConfidence);
return avgConfidence;
}
我把每一次識別的結(jié)果進(jìn)行回調(diào),當(dāng)說完“今天天氣不錯”這句話時中文語言的置信度為0.924333份殿,英文語言的置信度為0.743899膜钓,明顯中文的置信度更高,至此已初步體會到逆向工程給予我們的幫助----借鑒別人的設(shè)計和實現(xiàn)卿嘲,實現(xiàn)自己的功能颂斜,完結(jié)撒花~
當(dāng)然逆向工程還有更多有用的東西我沒有分享,比如模擬或篡改函數(shù)調(diào)用邏輯拾枣、制作插件等沃疮。
希望下次有機會能給大家在做更深一步的分享~
PS:極力推薦 劉培慶 所著《iOS應(yīng)用逆向與安全》,是我找到市面上最新講解iOS逆向工程相關(guān)的書籍梅肤,本文章可以說是此書前幾章的概括