iOS 越獄的Tweak開發
原文鏈接在我的博客 https://yohunl.com/ios-yue-yu-de-tweakkai-fa/ 上,如果有更新,以博客為準
iOS越獄開發中,各種破解補丁的統稱為Tweak,通常意義上我們說的越獄開發,都是指開發一個Tweak.
基本上,tweak都依賴于一個名叫cydia Substrate (以前名字也叫mobile Substrate)的動態庫,Mobile Substrate是Cydia的作者Jay Freeman (@saurik)的作品,也叫Cydia Substrate,它的主要功能是hook某個App,修改代碼比如替換其中方法的實現,Cydia上的tweak都是基于Mobile Substrate實現的.
iOS的tweak開發可以有兩種發布方式
- 只能在越獄設備上安裝的打包成deb格式的安裝包
- 直接使用開發者自己的證書/企業證書直接將補丁打包成ipa,這樣不需要越獄也是可以安裝的,只是這種非越獄的限制比較大,通常只是用來給某個app打個補丁或者類似的功能啥的
tweak是啥?
tweak的實質就是ios平臺的動態庫。IOS平臺上有兩種形勢的動態庫,dylib與framework。Framework這種開發者用的比較多,而dylib這種就相對比較少一點,比如libsqlite.dylib,libz.dylib等。而tweak用的正是dylib這種形勢的動態庫。我們可以在設備的/Library/MobileSubstrate/DynamicLibraries目錄下查看手機上存在著的所有tweak。這個目錄下除dylib外還存在著plist與bundle兩種格式的文件,plist文件是用來標識該tweak的作用范圍,而bundle是tweak所用到的資源文件
開發tweak最常用的theos環境安裝
TheOS被設計為一個在基于Unix平臺開發IOS程序的集成開發環境,它給我們準備好了一些代碼模板、預置一些基本的Makefile腳本,這樣我們開發一個tweak就會變得方便的多,這里我們不說簡單的多,是因為其配置還是稍顯繁瑣的,并且,也不能像我們通常開發xcode工程那樣方便的調試.
theos的開發者是大神DHowett,不過大從2015年開始,原作者不再更新,交給社區維護了,新的地址是https://github.com/theos/theos.
網上的多數教程都是基于原作者兩年前的原始theos來配置環境的,原始的配置環境的方式相對來說比較繁瑣.
這里,我們介紹最新的theos環境配置
最新環境的地址是 https://github.com/theos/theos
其實只要將git地址的內容下載下來,然后放到一個目錄下就可以了,網上的教程都是放到/opt/theos
1.是將theos環境下載下來,theos是放在github上的,使用git命令來clone比較方便,雖然可以放在任何目錄下,但是官方建議大家放在 /opt/下
打開 終端
輸入 export THEOS=/opt/theos #這個是建立一個環境變量方便后面的操作(引用這個環境變量是用$THEOS)
這種建立環境變量的方式,只是在當前終端中起作用了,關閉終端后又得重新設置,為了避免每次都建立這個環境變量,我們可以建立一個永久的環境變量
編輯~/.profile文件,在其中添加export THEOS=/opt/theos/,這個環境變量就是永久的了.
ps:怎么查看定義了哪些環境變量呢? 終端中輸入命令env!
git clone --recursive https://github.com/theos/theos.git $THEOS
這個目錄和終端變量$THEOS只是方便我們操作,其實你也可以放在任何你想放置的目錄.
新版的theos下載下來后,其內部已經內置了cydia framework和iOS的一系列私有的頭文件等,不需要像以前版本那樣,自己從手機上或者其它地方拷貝cydia的lib來了,其放置的目錄是vendor/lib和vendor/include
,新版的已經是內置CydiaSubstrate.framework,不是網上其它教程中說的需要運行bootstrap.sh腳本或者是從手機上拷貝等方式.
備注:最新版的已經沒有這個bootstrap.sh腳本文件了.2016.08.15,并且已經集成了最新的cydia Substrate,在目錄Vendor/lib下
這里的cydia使用的是framework模式了
看到官方的git中有注釋
[common] Move vendored includes and libraries to a vendor/ subdir.
舊版的中
首先運行Theos的自動化配置腳本:
~~ sudo /opt/theos/bin/bootstrap.sh substrate ~~
由于Theos存在一個bug,所以無法自動生成一個有效的libsubstrate.dylib文件,需要手動添加,需>> 要再Cydia中搜索安裝CydiaSubstrate,并且拷貝到電腦中,重命名為libsubstrate.dylib后放 到/opt/theos/lib/中
2.配置用來簽名的ldid,如果不安裝,那么產生的deb文件就安裝不到手機上,
用來專門簽名iOS可執行文件的工具,用以在越獄iOS中取代Xcode自帶的codesign.
安裝這個ldid,推薦的方式是采用brew來安裝-- brew install ldid
從http://joedj.net/ldid下載ldid,放到/opt/theos/bin/下,然后用命令chmod 777 /opt/theos/bin/ldid 來提升它的權限
看到另一篇文章(http://www.kanxue.com/bbs/showthread.php?p=1303343)說的可以在 git clone git://git.saurik.com/ldid.git 下載編譯ldid
完成以上操作會在ldid目錄下生產一個mac 可執行程序 ldid
3.配置dpkg-deb
新版的theos,其沒有內置 dpkg-deb,需要你用brew來安裝dpkg (brew install dpkg)
brew查看安裝了哪些工具的命令是 brew list
如果你沒安裝,那么可能會收到如下警告
SZ-lingdaiping:FLEXLoader-master yohunl$ make package
==> Error: /Applications/Xcode.app/Contents/Developer/usr/bin/make package requires dpkg-deb.
make: *** [internal-package-check] Error 1
deb是越獄開發包的標準格式,dpkg-deb是個用于操作deb文件的工具,有了這個工具,Theos才能正確的把工程打包成deb文件,舊版的
從https://github.com/DHowett/dm.pl 下載dm.pl文件(其實新版的theos的bin目錄下包含了這個文件了),將其重命名為dpkg-deb后,放到/opt/theos/bin/目錄下,chmod 777 /opt/theos/bin/dpkg-deb 來提升它的權限,再拷貝到theos/bin下了!!
4.配置Theos NIC templates (可選)
目前最新版的已經內置了所有模板了
tweak的demo簡介
下面我們來創建一個默認的demo來簡單說明一下
打開一個終端,
$THEOS/bin/nic.pl
可以看到
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/activator_event
[2.] iphone/application_modern
[3.] iphone/cydget
[4.] iphone/flipswitch_switch
[5.] iphone/framework
[6.] iphone/ios7_notification_center_widget
[7.] iphone/library
[8.] iphone/notification_center_widget
[9.] iphone/preference_bundle_modern
[10.] iphone/tool
[11.] iphone/tweak
[12.] iphone/xpc_service
Choose a Template (required):
通常我們建立的都是tweak,所以選11(可能你的不是11)
接下來,輸入工程的名稱
Project Name (required): yohunlTemp
再下來是輸入package的名字,這里可以回車,采用默認值,這里的默認是是com.yourcompany.project Name
Package Name [com.yourcompany.flextemp]:
再下來是輸入作者名,默認值是你的電腦的用戶名
Author/Maintainer Name [yohunl]:
再下來,是輸入tweak可以作用的對象的bundle identifier
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]:
這里要說明一下了,這里的com.apple.springboard是iOS的桌面app,如果我們的tweak是想作用于所有的app呢?那么這里應該填 com.apple.UIKit,這一步填入的內容是對應于建立后的一個名字為 工程名.plist的配置文件,這個文件的內容大概如以下這樣
{ Filter = { Bundles = ( "com.apple.springboard" ); }; }
當然了,這里可以更進一步的控制,具體可以去網上搜索
接下來,是輸入安裝完成后,需要重啟的應用
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]:
建立后的工程目錄如下
共有4個文件,其中
control文件中
Package: com.yourcompany.flextemp
Name: flexTemp
Depends: mobilesubstrate
Version: 0.0.1
Architecture: iphoneos-arm
Description: An awesome MobileSubstrate tweak!
Maintainer: yohunl
Author: yohunl
Section: Tweaks
是工程的一些常用的配置
flexTemp.plist文件,就是我們建立工程中輸入的[iphone/tweak] MobileSubstrate Bundle filter
{ Filter = { Bundles = ( "com.apple.springboard" ); }; }
makefile文件
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = flexTemp
flexTemp_FILES = Tweak.xm
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 SpringBoard"
這里有$(THEOS),這個變量,這也是我們上面用export建立的,如果你沒建立,新版的你要自己修改這里了
include $(THEOS)/makefiles/common.mk
這個都要添加的,因為很多關鍵的變量等,都在common.mk中,不包含這個,很多東西都用不了.例如編譯過程中很多的變量的定義都在其中
THEOS_MAKE_PATH := $(THEOS)/makefiles
THEOS_BIN_PATH := $(THEOS)/bin
THEOS_LIBRARY_PATH := $(THEOS)/lib
THEOS_INCLUDE_PATH := $(THEOS)/include
THEOS_MODULE_PATH := $(THEOS)/mod
flexTemp_FILES = Tweak.xm 是我們要包含的需要被編譯的文件,格式就是 工程名_FILES = 要編譯的文件名
Tweak.xm文件
%hook ClassName
// Hooking a class method
+ (id)sharedInstance {
return %orig;
}
// Hooking an instance method with an argument.
- (void)messageName:(int)argument {
%log; // Write a message about this call, including its class, name and arguments, to the system log.
%orig; // Call through to the original function with its original arguments.
%orig(nil); // Call through to the original function with a custom argument.
// If you use %orig(), you MUST supply all arguments (except for self and _cmd, the automatically generated ones.)
}
// Hooking an instance method with no arguments.
- (id)noArguments {
%log;
id awesome = %orig;
[awesome doSomethingElse];
return awesome;
}
// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end
*/
這個文件中,就是我們tweak的核心代碼,其中的%hook,%orig,%log等都是theos對cydia Substrate提供的函數的宏封裝,cydia Substrate提供的方法的介紹可以參考Cydia_Substrate
cydia Substrate提供了三方最重要的方法
IMP MSHookMessage(Class class, SEL selector, IMP replacement, const char* prefix); > // prefix should be NULL.
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP *result);
void MSHookFunction(void* function, void* replacement, void** p_original);
這三個都是用來進行hook操作的,也就是我們在非越獄開發中常說的swizzle!
cydia Substrate還提供了MobileLoader:“鉤子”需要在運行時被加載,靠的就是MobileLoader的功勞。MobileLoader會在適當的時機加載/Library/MobileSubstrate/DynamicLibraries/目錄下的動態庫(.dylib,這是tweak的最終產品)
MobileLoader能夠在運行時候加載dylib的核心是利用
** attribute((constructor))的方法會在main函數執行之前執行!!!!!!**
...
// The attribute forces this function to be called on load.
__attribute__((constructor))
static void initialize() {
NSLog(@"MyExt: Loaded");
MSHookFunction(CFShow, replaced_CFShow, &original_CFShow);
}
Tweak.xm的文件名后綴x代表這個文件支持Logos語法,如果只有一個x代表源文件支持Logos和C語法;如果是xm,說明源文件支持Logos和C/C++語法。其中的%hook,%orig,%log等都是Logos語法支持的內容,詳細語法說明請參考http://iphonedevwiki.net/index.php/Logos (不要被這個語法嚇著了,Logos作為Theos開發組件的一部分,通過一組特殊的預處理指令,可以讓編寫函數鉤子(hook)代碼變得非常簡單和清晰,Logos是隨著Theos發布的,你能夠在用Theos創建的項目中直接使用Logos的語法,如果不是Theos創建的工程,則使用不了哦)
到此為止,我們的demo已經建立起來了
如何編譯和安裝呢?
命令 make package就是編譯deb安裝包的
執行后,目錄下會多出來幾個文件和文件夾
其中packages文件夾下保留的是每一次編譯成功產生的deb安裝包文件
還有個隱藏的目錄 .theos
其內容如下
其中的_文件夾下面
DEBIAN文件夾下面是deb安裝到手機上后的控制文件信息,這個文件就是我們建立工程時候生成的那個control文件
其他的目錄和文件都是安裝后對應到手機上的真實文件,在這里顯示出來,是為了方便用開發者查看,安裝后在手機系統中哪些目錄生成了哪些文件
怎么安裝deb包和卸載deb包?
上面我們生成了能夠用于安裝到手機上的deb安裝包了,怎么安裝到手機上呢?
大體上可以有兩種方法
方法一:
圖形方式,使用iTools等工具將這個deb包拷貝到手機中,利用iFile瀏覽到此文件,進行安裝
方法二:
需要使用到openssh服務,確保你手機上已經安裝了該服務(cydia中搜索安裝)
要安裝 OpenSSH 首先需要將設備越獄。越獄完成之后,就可以在 Cydia 中直接查找和安裝 OpenSSH。安裝完成之后就可以通過下面的步驟來將你的 Mac 連接到 iOS 設備。
- 首先得保證你的 iOS 設備和 Mac 在同一局域網的同一網段中。
- 打開終端,輸入 ssh root@192.168.xxx.xxx
- 輸入 iOS 設備密碼,默認 alpine(強烈建議修改此默認密碼,否則任何人都可以通過ssh連接到你手機上,然后獲取你的信息)
- 等待連接,稍后,您就連接到您的iPhone、iPad上,可以執行 Unix 命令了。
- 還可以使用 Transmit 等軟件來管理 iOS 設備的文件系統,非常方便。
在編譯用的makefile文件最上面
添加
THEOS_DEVICE_IP = 你的手機的IP地址
然后使用 make package install命令,可以一次性完成編譯,打包,安裝一條龍服務,這個過程中可能需要要你兩次輸入ssh的root密碼((一次是簽名打包,一次是安裝)).
這樣還是稍顯繁瑣,每一次修改后,編譯運行,都得輸入兩此手機的root密碼,如果你連這兩次都懶得輸入,也是有辦法的
brew安裝openssh(brew install openssl)和 ssh-copy-id(brew install ssh-copy-id)
執行命令
ssh-keygen -t rsa -b 2048
按提示輸入存放keygen存放的目錄(最好是自己輸入存放的目錄的文件,而不是采用默認的,以防萬一覆蓋了其它的ssh)
再執行命令
ssh-copy-id root@<iP Address of your device>
然后,就可以不用密碼安裝了! 節約了兩次密碼的輸入(一次是簽名打包,一次是安裝)
說完了安裝,那么我們怎么卸載一個安裝的deb包呢?
方式一: cydia可刪除它,安裝的deb包都在cydia的已安裝列表中有顯示
方式二
如果手機上安裝了dpkg(越獄手機上,一般都是安裝了的,名字叫 Debian Package),那么將一個deb文件拷貝到手機里,就可以在手機中的終端(可以cydia安裝MTerminal,MTerminal是一款越獄手機上的命令行終端環境)中執行dpkg -i com.daapps.FLEXInjected_0.0.1-1-7_iphoneos-arm.deb 來安裝一個deb,(當然你也可以使用cydia來自動安裝,或者pp助手等,也可以),安裝完要重啟springboard,使用命令 killall -HUP SpringBoard
卸載一個軟件 dpkg -r com.daapps.FLEXInjected
**例如 dpkg -r com.yourcompany.testmywtweak **
(我們在theos環境打包出來的deb名字可能是com.yourcompany.testmywtweak_0.0.1-1_iphoneos-arm.deb等,但是最后在系統里的tweak的名字是com.yourcompany.testmywtweak )!
順便提一句
若是只是想修改其它的deb包的某個文件,該怎么弄呢?
deb包的解讀
,其實它就是一個壓縮文件而已…你可以使用rar等解壓縮工具解壓縮,但是這樣會丟失原有的文件的權限信息!
一個 deb 安裝包由兩部分組成,一個是安裝控制/識別信息,另外一個就是實際的程序文件。
需要修改現有的deb包,那么第一步就是解包。
假設deb的文件名是abc.deb,解包命令為:
dpkg-deb -x abc.deb tmp #將abc.deb的程序文件解包到tmp文件夾
dpkg-deb -e abc.deb tmp/DEBIAN #將abc.deb的安裝控制/識別信息解包到DEBIAN文件夾
注:安裝控制/識別信息必須在當前程序文件文件夾中的DEBIAN文件夾中,必須大寫。
進入DEBIAN目錄,可以看到有一個control文件,無后綴名,這個文件就是用來記錄deb的安裝信息。
另外,postinst,preinst,prerm,postrm,extrainst_這些腳本文件,不是必須存在的,當安裝包需要使用到腳本的時候才會用到的。腳本在后面的章節會詳細講到的,這一節我們先不管。
接下來介紹的是打包命令:
假設將需要打包的文件放在tmp文件夾中,DEBIAN文件夾也要在放在這個文件夾中,然后輸入命令:
chmod -R 0755 tmp/DEBIAN #首先設置權限,如果沒有包含腳本可以不需要設置權限
dpkg-deb -b tmp 1.deb #打包成一個叫做1.deb的包
如此這般,就完成了修改某個已存在的deb包了
OK,目前為止,我們已經介紹完了基礎知識
下面讓我們用一個稍微復雜一些的例子來演示一下 如何讓Flex可以嵌入到所有的APP中
flex的動態嵌入
flex的簡介
https://github.com/Flipboard/FLEX, FLEX是Flipboard開源的一款方便的應用內調試工具,開發者可在toolbar中查看和修改運行中的應用.
它提供了功能:
- 可以在層級中檢測和調整視圖,可查看每個對象上的屬性和變量;
- 動態調整任何屬性和變量;
- 動態調用實例和類方法;
- 通過掃描 heap訪問任何活躍的對象;
- 在app的sandbox中查看文件系統;
- 探究應用中所有類和系統框架(公開的和私有的);
- 快速訪問有用的對象(比如[UIApplication sharedApplication])、app委托以及關鍵窗口的根視圖控制器等;
- 動態查看和調整 NSUserDefaults 值
- 顯示所有的NSLog信息
- 顯示所有的網絡包等等
它是一個開源的框架集合,在我們自己的工程中,當然可以添加源碼就可以用起來了,那么如果我們能夠在其他的app中也嵌入它,那我們豈不是可以直接學習到其他app的UI布局等等,想想是不是就很激動呢?
官網截圖
看到沒,可以用來分析系統的電話界面
額.....但是.....官網并沒有告訴我們怎么做到這點,說是留給我們自己一個小練習....呵呵,這個練習可不小呀..
動態嵌入flex
首先,我們應該有一個可以選取所有的app的列表,通過這個列表,我們可以決定哪個應用可以嵌入flex.可喜的是,這個工作已經有人替我們完成了
APPlist 是個輔助獲取已安裝程序列表的插件,利用PreferenceLoader在設置中增加一個App列表,并可以供用戶設置
preferenceloader是手機越獄必備的軟件,如果少了preferenceloader,你的iphone.ipad.越獄后很多插件都會無法使用。preferenceloader是很多插件的依賴,例如非常出名的Activator需要它才能正常工作
https://github.com/DHowett/preferenceloader
說的再簡單一點,就是我們可以利用preferenceloader和applist,很方便的在系統給的設置那里提供一個可以選擇app的列表
需要在設備的/Library/PreferenceLoader/Preferences下放置一個指定格式的plist文件和兩個圖標文件(圖標文件是為了在系統的設置中顯示)
怎么這個文件放在theos工程的哪里,最后安裝后,才能在設備的/Library/PreferenceLoader/Preferences目錄下呢?
Theos已經幫我們想到這點了,在Theos建立的工程的根目錄下建立一個 layout文件夾,這個文件夾就相當于設備的根目錄了!在編譯生成的deb包中,會自動放到對應的文件夾
在工程的目錄下創建 layout文件夾 這里的layout相當于iOS的文件系統的根目錄
在其中放入一個控制系統設置項的plist文件FLEXInjected.plist
entry = {
bundle = AppList;
cell = PSLinkCell;
icon = "/Library/PreferenceLoader/Preferences/FLEXInjected.png";
isController = 1;
label = FLEXInjected;
ALSettingsPath = "/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist";
ALSettingsKeyPrefix = "FLEXInjectedEnabled-";
ALChangeNotification = "com.yourcompany.flexinjected.settingschanged";
ALSettingsDefaultValue = 0;
ALSectionDescriptors = (
{
title = "User Applications";
predicate = "(isSystemApplication = FALSE)";
"cell-class-name" = "ALSwitchCell";
"icon-size" = 29;
"suppress-hidden-apps" = 1;
},
{
title = "System Applications";
predicate = "(isSystemApplication = TRUE)";
"cell-class-name" = "ALSwitchCell";
"icon-size" = 29;
"suppress-hidden-apps" = 1;
"footer-title" = "? yohunl create for demo";
}
);
};
下載Flex的工程,編譯產生FLEX.framework這個動態庫,將其放入
打開Tweak.xm文件
添加如下代碼
/* How to Hook with Logos
Hooks are written with syntax similar to that of an Objective-C @implementation.
You don't need to #include <substrate.h>, it will be done automatically, as will
the generation of a class list and an automatic constructor.
*/
#include <dlfcn.h>
@interface MyDKFLEXLoader : NSObject
@end
@implementation MyDKFLEXLoader
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static MyDKFLEXLoader *_sharedInstance;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (void)show
{
// [[FLEXManager sharedManager] showExplorer];
Class FLEXManager = NSClassFromString(@"FLEXManager");
id sharedManager = [FLEXManager performSelector:@selector(sharedManager)];
[sharedManager performSelector:@selector(showExplorer)];
}
@end
%ctor {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist"] ;
NSString *libraryPath = @"/Library/Application Support/FLEXLoader/FLEX.framework/FLEX";
NSString *keyPath = [NSString stringWithFormat:@"FLEXInjectedEnabled-%@", [[NSBundle mainBundle] bundleIdentifier]];
NSLog(@"SSFLEXLoader before loaded %@,keyPath = %@,prefs = %@", libraryPath,keyPath,prefs);
if ([[prefs objectForKey:keyPath] boolValue]) {
if ([[NSFileManager defaultManager] fileExistsAtPath:libraryPath]){
void *haldel = dlopen([libraryPath UTF8String], RTLD_NOW);
if (haldel == NULL) {
char *error = dlerror();
NSLog(@"dlopen error: %s", error);
} else {
NSLog(@"dlopen load framework success.");
[[NSNotificationCenter defaultCenter] addObserver:[MyDKFLEXLoader sharedInstance]
selector:@selector(show)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
NSLog(@"SSFLEXLoader loaded %@", libraryPath);
} else {
NSLog(@"SSFLEXLoader file not exists %@", libraryPath);
}
}
else {
NSLog(@"SSFLEXLoader not enabled %@", libraryPath);
}
NSLog(@"SSFLEXLoader after loaded %@", libraryPath);
[pool drain];
}
簡單的代碼說明
NSDictionary *prefs = [NSDictionary dictionaryWithContentsOfFile:@"/var/mobile/Library/Preferences/com.yourcompany.flexinjected.plist"] .這個文件是我們在上面的plist中定義的用來存放用戶選擇的app列表的.通過讀取它我們就可以知道用戶選中了哪個app.
然后使用dlopen動態的打開framework,注入到響應的app進程中去
詳細的代碼我已經放到github上去了,地址是 https://github.com/yohunl/FlexInjected
編譯的命令是 make
打包成deb安裝包的命令是 make package
編譯,打包,安裝一條龍的命令是 make package isntall,當然了,你需要先修改Makefile文件中的THEOS_DEVICE_IP = 10.0.44.136 為你自己越獄設備的ip地址
稍后,你的越獄設備將會重啟,然后,就可以在設置那里看到了
選中 系統應用 計算器
然后,打開計算器應用(如果已經是打開的,需要先退出它,重新進,才能看到效果)
自此,我們的第一部分就結束了,通過本部分,我們了解了theos的基本配置,還有flex的越獄注入.