標籤:

鎖定APP的目標類與函數

上一節小程介紹了怎麼獲取APP的所有類的結構信息,以這一步為基礎,下一步可以通過注入來做更多研究工作。

注入的最小單位是函數,實際上,編譯執行的程序在編譯後,類就不復存在了,留給小程的只是二進位代碼(指令或數據都是一樣的二進位代碼)。所幸的是,跟小程打交道的,並不是二進位代碼(那會困難很多),而是函數,而且是某個類的函數。

那麼,在用classdump拿到成千上萬個類與函數後,哪個函數才是小程關心的呢?怎麼鎖定它們呢?

本文介紹鎖定目標類與函數的可行的辦法。

基本上小程研究的目標APP都是有豐富的界面的,而小程關心的場景基本都是由特定的界面觸發,所以,從界面入手是個不錯的選擇。

有沒有辦法找出某個界面對應哪一個類呢?然後就可以在classdump拿到的眾多的類中仔細研究這個界面類擁有的函數與成員變數。

小程覺得目前最好的辦法就是使用Reveal工具。

(一)使用Reveal

先下載一個Reveal(有破解版本),比如1.6版本或1.5版本。

(1)拷貝libReveal.dylib到手機

Reveal最大的一個作用是把手機上的某個APP的界面同步顯示到電腦上,要做到這個效果,Reveal既要在電腦上運行,同時也要把一個「內鬼」打入到手機。這個打入手機的「內鬼」就是libReveal.dylib。

在電腦上運行Reveal後,點擊菜單Help,選擇Show Reveal Library in Finder,再點擊iOS Library,就可以找到libReveal.dylib。

拷貝到DynamicLibrary目錄下:

scp libReveal.dylib root@192.168.2.57:/Library/MobileSubstrate/DynamicLibraries

基本上,手機上的APP在啟動運行後,都可以載入DynamicLibraries裡面的動態庫(以dylib為後綴),至於載入哪個動態庫,則由plist文件決定。這個知識點很重要,這意味著你可以寫一個plist文件,讓某個APP在啟動時載入你寫的動態庫,這是注入的前提。

/Library/MobileSubstrate/DynamicLibraries,在手機成功越獄後就會存在。

(2)拷貝libReveal.plist到手機

找一個plist文件來修改,或者直接寫一個plist文件,命名為libReveal.plist。

libReveal.plist文件要指定讓哪個APP啟動時載入Reveal.dylib,比如:

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Filter</key> <dict> <key>Bundles</key> <array> <string>com.tencent.QQKSong</string> </array> </dict></dict></plist>

上面的plist內容,讓「全民k歌」啟動時,載入Reveal.dylib。

com.tencent.QQKSong,是「全民k歌」的BoudleID。至於目標APP的BoundleID是多少,有很多辦法可以查到,比如找到它的plist文件來查看,比如ps看進程信息,比如動態調試等等。

小白:如果不指定這個Filter呢,是不是所有的APP啟動時都載入?

小程:iOS8之前的版本是這樣的,但之後的版本都需要指定APP。所以,不管3724,加上這個Filter總是不會錯的。

然後,拷貝libReveal.plist到手機:

scp libReveal.plist root@192.168.2.57:/Library/MobileSubstrate/DynamicLibraries

之後,在電腦上再次啟動Reveal就可以連接目標APP,來分析界面類了。

比如,在手機上重啟全民k歌,在電腦上重啟Reveal並選擇菜單項,連接全民k歌:

可以看到,全民k歌的一個頁面是這樣(右下解的類名是重點):

(二)讓全民k歌自動切換至歌手頁面

為了「感性」一點,小程做一個演示,通過Reveal定位到全民k歌的目標類,並讓全民k歌啟動後自動切換至歌手頁面。

首先通過Reveal,定位到底部導航條的所在的viewcontroller類:

也就是KSRootTabBarController

然後,通過查看classdump翻譯到的類結構中,找到這個類。可以看到,點擊「我要唱」按鈕,實際就是觸發KSRootTabBarController::onClickTabBarItem函數。

接著,就可以hook這個類了,讓目標APP自動跳轉。這一步的具體操作,小程會在後續詳細介紹,讀者只需要「感性」地知道這回事就好。

最終,自動跳轉的效果是這樣的:

以上小程講解了Reveal的使用。Reveal是定位目標類與函數的有效的辦法,除了這個辦法,還有一個辦法就是,觀察所有類的類名,猜測可能有關係的類(比如應該具備某個關鍵字),再注入這些類的函數並用NSLog輸出信息,或者動態調試觀察執行的流程,最終確定目標類與函數。

小程在這裡介紹動態調試目標APP的辦法,這個辦法在「漫天定位」的時候可能有用,實際大多數情況下可能不必用到。讀者可以在需要時再閱讀這部分內容。


目標:在電腦上遠程調試手機上的進程。

(一)問題

(1)為什麼不用本地調試的方式?

可以在手機上安裝gdb,再使用gdb調試目標進程。但因為本地用gdb來調試可能會遇到很多gdb本身的問題(有一些可以解決,而有一些並不好解決),而lldb被蘋果支持,並用於替代gdb。所以,可考慮使用更好的工具,即lldb+debugserver來遠程調試目標進程。

(2)需要什麼工具?

電腦上需要lldb,如果mac電腦上安裝了xcode那就會有lldb。

手機上需要安裝debugserver。

(3)手機如何安裝debugserver?

debugserver在哪裡?

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/xx.xx(目標手機是什麼系統就選擇什麼版本的目錄),雙擊下面的文件:DeveloperDiskImage.dmg,在打開的finder中,usr/bin目錄中存放了debugserver。

怎麼定製手機上相應的debugserver?

(a)

雙擊打開DeveloperDiskImage.dmg後,

cd /Volumes/DeveloperDiskImage/Library/PrivateFrameworks,

scp -r ARMDisassembler.framework root@192.168.2.22:/System/Library/PrivateFrameworks/,

即把ARMDisassembler.framework拷貝到手機,之所以這樣做,是據別人的經驗,可以讓lldb看彙編代碼的效果更好,不妨照做。

(b)

把usr/bin中的debugserver拷貝到另一個目錄,為後續的簽名作準備。

(c)

創建一個entitlement.xml文件,內容如下:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>com.apple.springboard.debugapplications</key> <true/> <key>get-task-allow</key> <true/> <key>task_for_pid-allow</key> <true/> <key>run-unsigned-code</key> <true/> </dict></plist>

(d)

使用ldid來簽名:

ldid -Sentitlement.xml debugserver

拷貝到手機:

scp debugserver root@192.168.2.22:/usr/bin/

在手機上測試是否可用(比如ssh到手機後輸入命令):

debugserver

(4)重簽名工具ldid怎麼安裝?

如果有安裝iOSOpenDev,那在目錄/opt/iOSOpenDev/bin/下面就有ldid。

如果沒有,那可以拉代碼下來安裝:

git clone git://git.saurik.com/ldid.gitcd ldidgit submodule update --init./make.shwhich ldid #查看是否安裝成功

(5)怎麼使用debugserver與lldb?

在手機上啟動伺服器debugserver:

debugserver *:54321 -a "Spotify"

*表示偵聽任意ip的連接,54321為埠,-a連接目標進程的名稱(可用ps取得)。

然後,在電腦上啟動lldb並連接伺服器:

process connect connect://192.168.2.22:54321

如果手機端的debugserver提示:Waiting for debugger instructions for process 0,那說明已經連接上。

之後可以在lldb端發送命令,比如查看當前調試進程的模塊信息:

image list -o -f

注意,第一次image list -o -f,可能要等上幾分鐘。當有返回後,再輸入c,就可以讓程序繼續運行。

(6)如果覺得debugserver運行很慢,怎麼解決?

使用usbmuxd來啟用數據線來調試。

* 如何安裝與使用usbmuxd? * 下載:http://7xibfi.com1.z0.glb.clouddn.com/uploads/default/original/2X/a/aa9cecf05b47d08a59324edeaaeea3f17e0608ee.zip * 具體使用可以參考:http://www.jianshu.com/p/9333a706641a。

小程直接用lldb跟debugserver,第一次image list等了幾分鐘,整體還可以接受

(7)怎麼調試?

* debugserver *:54321 -a "Spotify" --手機上啟動debugserver並附加到目標進程* lldb --電腦上啟動lldb* process connect connect://192.168.2.22:54321 --lldb連接debugserver* image list -o -f --查看目標進程的所有模塊的信息,比如輸出: [ 0] 0x000d8000 /var/containers/Bundle/Application/A80AF35B-DAD3-4D7E-B467-6C4230E32556/Spotify.app/Spotify(0x00000000000dc000) 第二個值為程序在內存中的基地址,記錄這個值。* br s -a (0x000d8000+目標代碼的偏移地址)* c --讓程序繼續執行* (操作程序)讓程序觸發斷點。* 使用lldb命令進行調試。

(二)示例

這裡簡單演示下怎麼調試Spotify這個APP。

從class-dump得到的信息,嘗試調試類SPTNowPlayingBarModel的pause函數,看看在播放bar條上暫停播放時會不會斷在這個函數上。

class-dump得到的pause的地址是0x00477ff1,在hopper上跳轉到這個地址(G),也可以看到-[SPTNowPlayingBarModel pause]的信息。

* 運行目標應用* debugserver *:54321 -a "Spotify" 手機端啟動伺服器,之後都在客戶端lldb中操作。* lldb* process connect connect://192.168.2.22:54321* image list -o -f 或 image list -o -f | grep Spotify這一步可能要等一會,在ipod5的6.0系統會很快,而在iphone5s的10.1.1系統則很慢。* 從上面的命令找到基地址為:0x000e6000 (每次運行不一樣)* 查看一下即將下斷點的代碼是怎麼樣的:dis -a 0x000e6000+0x00477ff1 結果跟用hopper看到的一樣(0x0047ff1處)。* br s -a 0x000e6000+0x00477ff1 * c* 讓程序觸發到斷點,使用lldb的命令查看數據或設置值,或單步等。

(三)lldb的常用命令

列印信息:expr或e、po、p或printdisplay 表達式 undisplay 序號斷點:br l或breakpoint list #所有斷點breakpoint set -a 函數地址,可簡寫:br s -a xxx #下斷點br s --函數關鍵字(可模糊) breakpoint set --shlib foo.dylib --name foobr del 1 #刪除斷點br del 2 3 4 #刪除幾個斷點內存斷點 watchpoint set expression 地址 watchpoint set variable 變數名稱條件斷點 watchpoint modify -c 表達式單步:s/si #單步進入函數n/ni #單步f #跳出函數線程:thread backtrace,或bt,或bt all #列出所有線程模塊信息:image list [-o -f]image lookup -a expression image lookup -a $pcimage lookup -r -n xxx image lookup -r -n playPause #用來查看一個函數(關鍵字)的地址image lookup -r -s顯示地址處的代碼:disassemble/dis -a 地址 dis -a $pc dis -s 0x0002c000 -c 9 #後面的參數-c用來限制顯示的代碼數。內存操作:memory read [起始地址 結束地址]/寄存器 -outfile 輸出路徑寄存器操作:register read/格式register write 寄存器名稱 數值


總結一下,本文重點在於使用Reveal,從界面入手,定位到關鍵類與函數,進而找到目標類與函數,為分析借鑒或注入提供基礎。對於動態調試,讀者未必需要掌握,如果它實際幫不到你的話。使用Reveal的難度係數為低,而動態調試的難度為中。

推薦閱讀:

iPhone十周年,iOS之父講述與喬布斯並肩打拚的日子
有什麼事情是 iOS 設備能做,而 Android 做不到、做不好的?
iOS 7 中的 Safari 圖標是不是非常丑?
iOS10中更新後的iMessage有哪些新用處和新玩法?
如何客觀的比較iOS、Android以及即將發布的Windows 10 for phone的優勢與劣勢?

TAG:逆向工程 | iOS |