探究 Logos、Makefile 語法
前言
於 編寫首個 Tweak 越獄套件 中,稍微提及 Logos、Makefile 語法。
本文將常用、進階的 Logos、Makefile 語法做了整理,以供參考。
關於 Logos
Logos 提供的語法大大的簡化了 Tweaks 的開發,是 Theos 開發組件的一部分。
通過一組特殊的預處理指令,可以讓編寫函式鉤子(hook)程式碼變得非常簡單和清晰。
相關副檔名:
.x
:先被 Logos 處理,然後被作為 Objective-C 預處理和編譯。.xm
:先被 Logos 處理,然後被作為 Objective-C++ 預處理和編譯。
關於 Makefile
在軟體開發中,make 是一個工具程式,經由讀取叫做 Makefile 的檔案,自動化建構軟體。
make 常被用來編譯原始碼,生成結果程式碼,然後把結果程式碼連接起來生成可執行檔或者庫檔案。
Makefile 是用來確定檔案的依賴關係,然後把生成這個 target 的相關命令傳給 shell 去執行。
Logos 語法
%hook
指定需要 hook 的類別,必須以%end
結尾。
// hook SpringBoard類別中的 applicationDidFinishLaunching 函式
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
HBLogDebug(@"hook"); // 輸出 hook 字串到 Log
%orig; // 呼叫原始函式
}
%end
%orig
於%hook
內部使用,執行被 hook 住的函式的原始程式碼。
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
HBLogDebug(@"orig"); // 輸出 orig 字串到 Log
%orig; // 呼叫原始函式
}
%end
如果未使用%orig
則原始函式不會執行:
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
HBLogDebug(@"no orig"); // 輸出 no orig 字串到 Log
}
%end
也可以利用%orig
更改原始函式參數:
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
HBLogDebug(@"orig nil"); // 輸出 orig nil 字串到 Log
%orig(nil); // 將參數 arg1 調換成 nil
}
%end
%group
用於將%hook
分組,利於程式碼管理及按條件初始化分組,必須以%end
結尾。
一個%group
中可包含多個%hook
。
所有不屬於某個自定義%group
的%hook
被隱式歸類到%group_ungrouped
中。
/*
於 %group iOS9 中 hook iOS9Class 類別中的 iOS9Method 函式。
於 %group iOS10 中 hook iOS10Class 類別中的 iOS10Method 函式。
於 %group _ungrouped 中 hook SpringBoard 類別中的 powerDown 函式。
*/
%group iOS9
%hook iOS9Class
- (void)ios9Method
{
HBLogDebug(@"這個方法只有 iOS9 適用。");
%orig;
}
%end
%end // Group iOS9
%group iOS10
%hook iOS10Class
- (void)iOS10Method
{
HBLogDebug(@"這個方法只有 iOS10 適用。");
%orig;
}
%end
%end // Group iOS10
%hook SpringBoard
- (void)powerDown
{
%orig;
}
%end
%init
用於初始化某個%group
,必須在%hook
或%ctor
中呼叫。
如果帶參數,則初始化指定的%group
,如果不帶參數,則初始化_ungrouped
。
注意,只有呼叫了%init
,對應的%group
才能起作用。
#ifndef kCFCoreFoundationVersionNumber_iOS_10
#define kCFCoreFoundationVersionNumber_iOS_10 1348.00
#endif
%hook SpringBoard
- (void)applicationDidFinishLaunching:(id)arg1
{
%orig;
%init; // %init; 等價於 %init(_ungrouped);
if(kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_10)
%init(iOS9);
if(kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10)
%init(iOS10);
}
%end
%ctor
完成初始化工作,一般可以用來初始化%group
等操作。
置於程式碼最後且不需要以%end
結尾。
如果不顯式定義,Theos 會自動生成一個%ctor
,並在其中呼叫%init(_ungrouped)
。
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
%orig;
}
%end
以上程式碼可以成功生效,因為 Theos 隱式定義了如下內容:
%ctor
{
%init(_ungrouped);
}
以下程式碼中的%hook
無法生效,因為定義了%ctor
,卻沒有呼叫%init(_ungrouped)
:
%hook SpringBorad
- (void)applicationDidFinishLaunching:(id)arg1
{
%orig;
}
%end
%ctor {}
%new
於%hook
內部使用,讓現有的類別加上新的函式。
%hook SpringBoard
%new
- (void)addNewMethod
{
HBLogDebug(@"動態添加一個方法到 SpringBoard 類別。");
}
%new
- (void)addNewMethod2
{
HBLogDebug(@"再動態添加一個方法到 SpringBoard 類別。");
}
%end
%c
動態獲取一個類的定義,在%hook
或%ctor
內使用。
%hook SpringBoard
UIKeyboardImpl * impl = [%c(UIKeyboardImpl) activeInstance];
%end
Makefile 語法
# 設備 IP 和埠號(用於自動安裝)
THEOS_DEVICE_IP = localhost
THEOS_DEVICE_PORT = 2222
INSTALL_TARGET_PROCESSES = SpringBoard #安裝後結束 SpringBoard,也就是 Respring
include $(THEOS)/makefiles/common.mk # 固定寫法
TWEAK_NAME = hello # 專案名稱
hello_FILES = Tweak.xm # 專案包含的原始檔案(不包含標頭檔,空白分隔)
hello_CFLAGS = -fobjc-arc # 開啟 ARC
include $(THEOS_MAKE_PATH)/tweak.mk # 固定寫法
設備 IP 和埠號
THEOS_DEVICE_IP = localhost
THEOS_DEVICE_PORT = 2222
上述語法適用於 USB 連線,需配合 透過 SSH 安裝越獄套件 做埠號映射使用。
如果設備與電腦在同一個區域網路下,則可以使用真實 IP 位址。
處理器架構
ARCHS = armv7 arm64
指定專案支援的指令集類型,支援的指令集愈多,程式佔用空間愈大。
指令集 | 處理器 | 設備舉例 |
---|---|---|
armv7 | A4-A5 | iPad Mini |
armv7s | A6-A6X | iPhone 5c |
arm64 | A7-A11 | iPhone 7(Plus) |
arm64e | A12- | iPhone XR |
arm64e 向下相容 arm64,armv7s 亦向下相容 armv7,但 arm64、armv7 互不相容。
目前的主流設備都是 64 位元的 arm64 指令集,基本上 arm64 就夠用了。
若要支援老設備(iPhone 5S 之前)才需要添加上 armv7。
版本變數
DEBUG = 0
FINALPACKAGE = 1
DEBUG
預設值為 1,開啟除錯版本,將輸出HBLogDebug()
的內容,版本號後綴+debug
。
反之,為 0,關閉除錯版本,HBLogDebug()
將不作用。
FINALPACKAGE
預設值為 0,版本號後綴「-該版本編譯次數」。
同理,為 1,版本號無後綴,同時關閉除錯版本,HBLogDebug()
也將不作用。
變數 | 版本號 | 除錯版本/HBLogDebug() |
---|---|---|
DEBUG=1(預設) | 0.1-5+debug | 開啟/作用 |
DEBUG=0 | 0.1-5 | 關閉/不作用 |
FINALPACKAGE=1 | 0.1 | 關閉/不作用 |
ARC
Automatic Reference Counting,是自動化的管理記憶體,讓開發者不用多花心力在處理記憶體部分。
使用下列語法為整個專案開啟 ARC:
專案名稱_CFLAGS = -fobjc-arc
也可以手動為指定檔案關閉 ARC:
檔案名(含副檔名)_CFLAGS = -fno-objc-arc
Frameworks
專案名稱_FRAMEWORKS = UIKit
專案名稱_PRIVATE_FRAMEWORKS = Preferences
導入所需的 Frameworks、Private Frameworks,兩個以上以空白分隔。
不同 iOS 版本可能發生變化,需確認導入的 Private Frameworks 確實存在。
若預設目錄(Xcode 中)找不到 Private Frameworks,可指定 SDK 目錄:
SYSROOT = $(THEOS)/sdks/iPhoneOS11.2.sdk
導入第三方套件:
專案名稱_CFLAGS = -I./BookLib/include # .a 標頭檔尋找目錄
專案名稱_LDFLAGS = -L./BookLib # .a 二進位檔案連結目錄
專案名稱_LIBRARIES = BookLib # .a
專案名稱_CFLAGS = -F./BookFramework # .framework 標頭檔尋找目錄
專案名稱_LDFLAGS = -F./BookFramework # .framework 二進位檔案連結目錄
專案名稱_FRAMEWORKS = BookFramework # .framework
或是將套件放入$THEOS/lib
中,利用專案名稱_EXTRA_FRAMEWORKS
導入。
程式碼中直接引用:
#import <BookLib/BookLib.h> // .a
#import <BookFramework/BookFramework.h> // .framework
連結 Mach-O 檔案
Theos 採用 GNU Linker 來連結 Mach-O 二進位檔案,包含.dylib
、.a
與.o
。
例如,要連結libsqlite3.0.dylib
、libz.dylib
和dylib1.o
:
專案名稱_LDFLAGS = -lsqlite3.0 -lz -dylib1.o
結語
至此,基礎階段的#JB 系列貼文於本篇告一段落。
本系列即將推出進階內容之貼文,為獲得更佳的閱讀體驗,建議事先研讀《iOS 應用逆向與安全》。