最近有一篇 文章 介紹瞭如何實現 AppStore App 自動下載,筆者看後收穫良多。不過文中只介紹瞭如何去模擬用戶的操做來完成下載,並無涉及抹機、IP 更換等內容。因此筆者打算在此分享一下本身對這些方面的經驗。php
修改設備惟一可識別標識能夠作不少事前,好比防止根據 UUID 的追蹤,避免大數據「殺熟」等。可是在 iOS 設備上目前想作到修改的前提是越獄,因此爲了多領幾個美團紅包而選擇承擔越獄的風險,是否值得仍是要考慮清楚的。 不過在業界有大量應用這種技術的產業,好比積分牆、ASO 刷榜…… 不過這些產業就屬於「灰黑產」了,涉及到了原力的黑暗面,因此筆者不建議涉世不深的讀者繼續閱讀下去。ios
當你凝視深淵,深淵也在凝視着你。sass
在開始講如何作以前,筆者決定先簡單介紹一下業界如今已經能作什麼: 安全
如圖所示,這是一款在業內很是常見的改機軟件。因爲做者不可考(不過理應如此,畢竟爲了本身的人生安全)、源碼遺失、以及 iOS 版本的屢次更新,如今已經不值錢了。可是麻雀雖小五臟俱全,它可以修改設備的五碼、機型、配置 Apple ID 和一鍵越獄等。 前人的成功告訴了咱們這是可行的,剩下的只是模仿,所以筆者深刻逆向並研究了這款軟件,在當我看到了一大堆用匯編寫的混淆以後…… 放棄了。 因此下面的內容都是筆者編的,你們有興趣看個開心就好,基本上能夠點關閉按鈕了 (●°u°●) 」架構
筆者依稀記得 狗神 在他那本著名的 小黃書 中提到,逆向一款軟件最重要的不是最終成品的代碼,而是過程的分析與思路。因此常常能夠看到一款軟件的破解代碼重要的也許只有兩三行,可是過程有多艱辛也許只有破解者才知道。例如破解 Mac 版 QQ 音樂下載須要 VIP 權限的限制的代碼也許加上註釋也不到一百行:app
/* 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. %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 */
%config(generator = internal)
#import <Foundation/Foundation.h>
#include <substrate.h>
%hook DownLoadTask
- (BOOL)checkHaveRightToDownload:(int)argument {
return YES;
}
%end
unsigned int (*old_GetFlexBOOL)(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8);
unsigned int new_GetFlexBOOL(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
return 1;
}
%ctor {
NSLog(@"!!!!!!inject success!!!!!!!");
void * Symbol = MSFindSymbol(MSGetImageByName("/Applications/QQMusic.app/Contents/MacOS/QQMusic"), "_GetFlexBOOL");
MSHookFunction(Symbol, &new_GetFlexBOOL, (void *)&old_GetFlexBOOL);
}
複製代碼
而真正重要的是找出思路和逆向分析的過程,操做系統本質上也是一個軟件,修改 Device ID 其實和破解一款音樂 VIP 限制本質上是同樣的,只是一個只須要把 checkHaveRightToDownload
的返回值改爲 YES
,另外一個則須要與操做系統鬥智鬥勇罷了。框架
綜上所述,在咱們對操做系統下黑手以前應該先理清思路。順便再說一次如下內容皆是我瞎編的,若有雷同實屬巧合:dom
如圖所示,顯而易見,若是隻是簡簡單單的修改某個 App 中用到的 Device ID,極大概率只須要勾住「再封裝的私有 API」就好了。iphone
而在衆多私有 API 中,最著名的固然是大名鼎鼎的 MGCopyAnswer
。ide
// Common form: MGCopyAnswer(CFStringRef string);
CFStringRef value = MGCopyAnswer(kMGDeviceColor);
NSLog(@"Value: %@", value);
CFRelease(value);
複製代碼
基本上平時從 UIDevice
仍是其餘大部分途徑獲取 Device ID,皆是經過調用 libMobileGestalt 中的 MGCopyAnswer
函數來獲取的。因此只須要勾住 MGCopyAnswer
,使其返回的 Device ID 爲咱們所要的值便可,很是簡單明瞭。
不過雖然說思路很簡單,可是一個萌新想要勾 MGCopyAnswer
仍是會繞不少彎路的,好比最多見的就是「掛短鉤」。
在 ARM64 架構下,直接對 MGCopyAnswer
掛鉤的話會當即使進程崩潰 invalid instruction
。若是經過反彙編手段分析 libMobileGestalt 庫:
01 00 80 d2 movz x1, #0
01 00 00 14 b MGCopyAnswer_internal
複製代碼
易知 MGCopyAnswer
實際上在內部調用了另外一個私有無符號的函數 MGCopyAnswer_internal
來實現其功能。所以 MGCopyAnswer
這個函數實際上很是短,只有 8 個字節,而咱們使用 Cydia Substrate 對一個 C 函數掛鉤的話,它要求被勾函數至少有 16 個字節。所以直接勾住 MGCopyAnswer
時,MGCopyAnswer
函數地址開始的 16 個字節都會被改成 goto
,從而破壞了相鄰函數的前 8 個字節,使進程崩潰。 所以,當咱們吭哧吭哧讀完彙編以後,首先想到的方法天然是去勾這個被調用的子函數 MGCopyAnswer_internal
,雖然說該函數並無符號,可是在咱們吭哧吭哧讀了彙編以後,發現其函數地址與 MGCopyAnswer
相差 8 字節。故能夠很簡單粗暴的寫出以下代碼:
static CFPropertyListRef (*orig_MGCopyAnswer_internal)(CFStringRef prop, uint32_t* outTypeCode);
CFPropertyListRef new_MGCopyAnswer_internal(CFStringRef prop, uint32_t* outTypeCode) {
return orig_MGCopyAnswer_internal(prop, outTypeCode);
}
extern "C" MGCopyAnswer(CFStringRef prop);
static CFPropertyListRef (*orig_MGCopyAnswer)(CFStringRef prop);
CFPropertyListRef new_MGCopyAnswer(CFStringRef prop) {
return orig_MGCopyAnswer(prop);
}
%ctor {
uint8_t MGCopyAnswer_arm64_impl[8] = {0x01, 0x00, 0x80, 0xd2, 0x01, 0x00, 0x00, 0x14};
const uint8_t* MGCopyAnswer_ptr = (const uint8_t*) MGCopyAnswer;
if (memcmp(MGCopyAnswer_ptr, MGCopyAnswer_arm64_impl, 8) == 0) {
MSHookFunction(MGCopyAnswer_ptr + 8, (void*)new_MGCopyAnswer_internal, (void**)&orig_MGCopyAnswer_internal);
} else {
MSHookFunction(MGCopyAnswer_ptr, (void*)new_MGCopyAnswer, (void**)&orig_MGCopyAnswer);
}
}
複製代碼
顯然這段代碼除了簡單粗暴、沒有任何框架檢測與異常處理以外完美實現了掛鉤任務,可是基於相對偏移量來獲取函數地址也並非很穩。
好在張總在他的一篇博文中提到可使用 Capstone Engine,一款基於 LLVM MC 的多平臺多架構支持的反彙編框架來幫助咱們找到 MGCopyAnswer_internal
的「符號」。
static CFStringRef (*old_MGCA)(CFStringRef Key);
CFStringRef new_MGCA(CFStringRef Key) {
CFStringRef Ret = old_MGCA(Key);
NSLog(@"MGHooker:%@\nReturn Value:%@", Key, Ret);
return Ret;
}
%ctor {
void *Symbol = MSFindSymbol(MSGetImageByName("/usr/lib/libMobileGestalt.dylib"), "_MGCopyAnswer");
NSLog(@"MG: %p", Symbol);
csh handle;
cs_insn * insn;
cs_insn BLInstruction;
size_t count;
unsigned long realMGAddress = 0;
// MSHookFunction(Symbol,(void*)new_MGCA, (void**)&old_MGCA);
if (cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &handle) == CS_ERR_OK) {
/*cs_disasm(csh handle, const uint8_t *code, size_t code_size, uint64_t address, size_t count, cs_insn **insn);*/
count = cs_disasm(handle, (const uint8_t *)Symbol, 0x1000, (uint64_t)Symbol, 0, &insn);
if (count > 0) {
NSLog(@"Found %lu instructions", count);
for (size_t j = 0; j < count; j++) {
NSLog(@"0x%" PRIx64 ":\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str);
if (insn[j].id == ARM64_INS_B) {
BLInstruction = insn[j];
sscanf(BLInstruction.op_str, "#%lx", &realMGAddress);
break;
}
}
cs_free(insn, count);
}
else {
NSLog(@"ERROR: Failed to disassemble given code!%i \n", cs_errno(handle));
}
cs_close(&handle);
// Now perform actual hook
MSHookFunction((void *)realMGAddress, (void *)new_MGCA, (void **)&old_MGCA);
}
else {
NSLog(@"MGHooker: CSE Failed");
}
}
複製代碼
廢話很少說了,咱們的正題並不在這裏。
接下來的東西我是真的就不會了,可是爲了避免太斧頭蛇尾,我就再瞎掰一段吧。 談到修改的話,咱們首先要弄清楚的一點是咱們打算要從哪一層修改?好比 ECID,衆所周知它是燒在芯片上的。講道理的話要修改 ECID 是要對硬件動手的,可是咱們通常不須要作的這麼完全,而是結合具體需求具體分析。例如一個普通、簡單的積分牆,咱們只須要對積分牆調用的 MGCopyAnswer
掛鉤,就能夠愉快的玩耍了。可是若是想對 AppStore 或者 iTunes 下手呢?天然僅僅勾個 MGCopyAnswer
是不行的。 例如咱們想讓手機鏈接 iTunes 時,iTunes 獲取的 Device ID 是僞造的,那麼就須要勾住處理手機與電腦間 USB 通訊的守護進程——好比說 lockdownd。由於 iTunes 並不會直接讀取手機的設備信息,而是從手機上運行的守護進程中請求數據。那麼咱們是否是隻須要在這個守護進程安裝一個鉤子便可?
typedef void *LockdownConnectionRef;
typedef int kern_return_t;
typedef unsigned int __darwin_natural_t;
typedef __darwin_natural_t __darwin_mach_port_name_t;
typedef __darwin_mach_port_name_t __darwin_mach_port_t;
typedef __darwin_mach_port_t mach_port_t;
typedef mach_port_t io_object_t;
typedef io_object_t io_registry_entry_t;
typedef char io_name_t[128];
typedef unsigned int IOOptionBits;
static kern_return_t (*oldIORegistryEntryGetName)(io_registry_entry_t entry, io_name_t name);
kern_return_t newIORegistryEntryGetName(io_registry_entry_t entry, io_name_t name) {
int ret = oldIORegistryEntryGetName(entry, name);
NSLog(@"\n\nGetName:\n\tentry:%zd\n\tio_name_t%s\n\tret:%d", entry, name, ret);
return ret;
}
static CFTypeRef (*oldIORegistryEntrySearchCFProperty)(
io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options);
CFTypeRef newIORegistryEntrySearchCFProperty(
io_registry_entry_t entry, const io_name_t plane, CFStringRef key, CFTypeRef allocator, IOOptionBits options) {
CFTypeRef ret = oldIORegistryEntrySearchCFProperty(entry, plane, key, allocator, options);
NSLog(@"\n\nSearchCFProperty:\n\tkey:%@\n\tret:%@\n\t%lu", key, ret, CFGetTypeID(ret));
return ret;
}
static CFPropertyListRef (*old_lockdown_copy_value)(LockdownConnectionRef connection,
CFStringRef domain,
CFStringRef key);
CFPropertyListRef new_lockdown_copy_value(LockdownConnectionRef connection, CFStringRef domain, CFStringRef Key) {
CFPropertyListRef Ret = old_lockdown_copy_value(connection, domain, Key);
NSLog(@"LDHooker:%@\nReturn Value:%@", Key, Ret);
return old_lockdown_copy_value(connection, domain, Key);
}
% ctor {
void *SymbolGN =
MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"), "_IORegistryEntryGetName");
NSLog(@"GName: %p", SymbolGN);
MSHookFunction((void *)SymbolGN, (void *)newIORegistryEntryGetName, (void **)&oldIORegistryEntryGetName);
void *SymbolSC = MSFindSymbol(MSGetImageByName("/System/Library/Frameworks/IOKit.framework/IOKit"),
"_IORegistryEntrySearchCFProperty");
NSLog(@"SPropertey: %p", SymbolSC);
MSHookFunction(
(void *)SymbolSC, (void *)newIORegistryEntrySearchCFProperty, (void **)&oldIORegistryEntrySearchCFProperty);
}
else {
NSLog(@"MGHooker: CSE Failed");
}
}
複製代碼
其實我想你們應該猜到我下面想作什麼了,既然都已經對守護進程下手了,要不乾脆咱們本身也開一個守護進程的了,加個 root 權限,對全部其餘進程安裝鉤子,若是調用了 Device ID 相關的 API,把返回值魔改掉,豈不美滋滋!代碼以下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 紅紅火火恍恍惚惚
NSLog(@"想不到吧,此次我真的編不出來了😂");
}
return 0;
}
複製代碼
那麼今天的代碼就寫到這裏了,下臺鞠躬!
注:以上全部代碼全是瞎掰,如能運行,純屬巧合。