前言

編寫首個 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.dyliblibz.dylibdylib1.o

專案名稱_LDFLAGS = -lsqlite3.0 -lz -dylib1.o

結語

至此,基礎階段的#JB 系列貼文於本篇告一段落。

本系列即將推出進階內容之貼文,為獲得更佳的閱讀體驗,建議事先研讀《iOS 應用逆向與安全》