Dark side of the Force

最近有一篇 文章 介绍了如何实现 AppStore App 自动下载,笔者看后收获良多。不过文中只介绍了如何去模拟用户的操作来完成下载,并没有涉及抹机、IP 更换等内容。所以笔者打算在此分享一下自己对这些方面的经验。


FBI WARNING

  1. 以下内容可能会引起很多人不适,请读者自酌。
  2. 18 岁以下请在家长陪同下观看!
  3. 部分内容可能违反你所在地相关法律,请谨慎模仿

为什么要修改 iOS Device ID ?

修改设备唯一可识别标识可以做很多事前,比如防止根据 UUID 的追踪,避免大数据「杀熟」等。但是在 iOS 设备上目前想做到修改的前提是越狱,所以为了多领几个美团红包而选择承担越狱的风险,是否值得还是要考虑清楚的。 不过在业界有大量应用这种技术的产业,比如积分墙、ASO 刷榜…… 不过这些产业就属于「灰黑产」了,涉及到了原力的黑暗面,所以笔者不建议涉世不深的读者继续阅读下去。

当你凝视深渊,深渊也在凝视着你。

现状

在开始讲如何做之前,笔者决定先简单介绍一下业界现在已经能做什么:

如图所示,这是一款在业内非常常见的改机软件。由于作者不可考(不过理应如此,毕竟为了自己的人生安全)、源码遗失、以及 iOS 版本的多次更新,现在已经不值钱了。但是麻雀虽小五脏俱全,它能够修改设备的五码、机型、配置 Apple ID 和一键越狱等。 前人的成功告诉了我们这是可行的,剩下的只是模仿,因此笔者深入逆向并研究了这款软件,在当我看到了一大堆用汇编写的混淆之后…… 放弃了。 所以下面的内容都是笔者编的,大家有兴趣看个开心就好,基本上可以点关闭按钮了 (●°u°●)​ 」

如何破解一款程序?

笔者依稀记得 狗神 在他那本著名的 小黄书 中提到,逆向一款软件最重要的不是最终成品的代码,而是过程的分析与思路。所以经常可以看到一款软件的破解代码重要的也许只有两三行,但是过程有多艰辛也许只有破解者才知道。例如破解 Mac 版 QQ 音乐下载需要 VIP 权限的限制的代码也许加上注释也不到一百行:

/* 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;
}%endunsigned 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 ,另一个则需要与操作系统斗智斗勇罢了。

思路

综上所述,在我们对操作系统下黑手之前应该先理清思路。顺便再说一次以下内容皆是我瞎编的,如有雷同实属巧合:

如图所示,显而易见,如果只是简简单单的修改某个 App 中用到的 Device ID,极大几率只需要勾住「再封装的私有 API」就行了。

而在众多私有 API 中,最著名的当然是大名鼎鼎的 MGCopyAnswer

MGCopyAnswer

// 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 hookMSHookFunction((void *)realMGAddress, (void *)new_MGCA, (void **)&old_MGCA);}else {NSLog(@"MGHooker: CSE Failed");}
}
复制代码

废话不多说了,我们的正题并不在这里。

如何修改 iOS Device ID

接下来的东西我是真的就不会了,但是为了不太斧头蛇尾,我就再瞎掰一段吧。 谈到修改的话,我们首先要弄清楚的一点是我们打算要从哪一层修改?比如 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;
}
复制代码

那么今天的代码就写到这里了,下台鞠躬!


注:以上所有代码全是瞎掰,如能运行,纯属巧合。

参考资料

如何实现 AppStore App 的自动下载

Hooking MGCopyAnswer Like A Boss

浅谈 iOS Device ID 的修改相关推荐

  1. iOS实录15:浅谈iOS Crash

    导语:在当前的iOS开发中,虽然ARC为开发者解决了手动内存管理时代 的许多麻烦,但是内存方面的问题依然是产生iOS Crash的元凶之一,本文介绍内存方面,有关僵尸对象.野指针.内存泄漏.废弃内存这 ...

  2. html代码id,浅谈html中id和name的区别实例代码

    浅谈html中id和name的区别实例代码 更新时间:2008年07月28日 23:00:55   作者: 这个是form里面的name与id的区别 我们可以通过一段代码来分析一下其中的微妙差别: 在 ...

  3. 浅谈iOS和Android后台实时消息推送的原理和区别

    http://www.52im.net/thread-286-1-1.html 前言 iOS和Android上的实时消息推送差异很大,往小了说是技术实现的差异,往大了说是系统实现理念的不同.实时消息推 ...

  4. 浅谈git rebase命令 -- git修改历史提交信息

    浅谈git rebase命令 如果你只想修改git log命令打印到终端上的最后一个提交信息,那么使用git commit --amend命令直接修改就好. 如果你还想合并最近的某几个提交为一个提交, ...

  5. 华山论剑之浅谈iOS的生产线 工厂模式

    工厂模式是一种快速创建大量对象的模式.简单易上手,今天我就谈一下我对工厂模式的理解.工厂模式的核心思想就是继承.子类继承父类的方法并且重写父类方法.下面我们就看一下实际中是如何使用工厂模式的. *** ...

  6. 浅谈iOS 开发中的界面通信

    在任何的软件开发中都离不开界面与界面之间的通信,界面通信的最直接的方法就是界面传值. 在开发过程中我们在页面传值时我们通常使用的方法有:属性传值法,block传值法,代理传值法,以及单例传值法,通知传 ...

  7. 浅谈iOS中的蓝牙技术(一) GameKit framework

    蓝牙低能耗(BLE)技术是低成本.短距离.可互操作的无线技术,工作在免许可的2.4GHz ISM射频频段. Start GameKit.framework 只能用于iOS设备之间的同个应用内连接,多用 ...

  8. ios与android功能特点,浅谈iOS与Android的区别

    说在前面:从事UI设计的同行们关于iOS与Android的基本设计规范相信大家都已经非常了解了,以下是我针对这两种设计规范所作的一点点小总结,如果面试官问道此类问题,希望对你们有用!!! 首先设计语言 ...

  9. 浅谈Joomla!流行模板的修改方法

    使用并研究了Joomla!一小段时间,由于对模板制作不熟悉,所以特地去找了些流行商业模板来做研究,也发现了很多超牛的模板(瞬间觉得以后根本就不用花什么时间去设计网页了),像JA rasite.GK s ...

最新文章

  1. java list键值_java基础之对List,Map,Set等集合键值对的简单认识
  2. iview default-file-list 动态赋值不显示
  3. Java常用集合体系以及相互区别
  4. [走走看看]转载两篇文章:挂牌和追经
  5. 如何使用键盘快捷键在Mac上录制屏幕?
  6. windows的那些好软件
  7. 山石岩读丨前沿领域探析——汽车CAN总线协议详解及攻击面分析
  8. pentaho-server-ce-7.0安装配置说明
  9. 跨境电商机遇爆发,时下应该怎么“玩”?
  10. 程序与进程以及作业之间的区别
  11. 注意函数定义和函数调用的顺序
  12. 物联网开发笔记(5)- 使用Wokwi仿真树莓派Pico实现LED灯交替闪烁(续)
  13. CSS在Mac /Win上兼容显示
  14. spring boot项目报错:Validation failed for query for method public abstract...
  15. Android BLE蓝牙踩坑总结
  16. 17.文件操作(1)----初识文件操作
  17. 五点三次平滑法滤波 C 和 matlab代码
  18. android 表情字符串,Android 显示输入法中的emoji表情以及String字符串转码
  19. 架设MTProxy服务
  20. 「科普」如何评价供应商的MES系统

热门文章

  1. 工具素材分享篇:拍摄剪辑与视频制作必备!
  2. 实验六、完整性控制(包含触发器的使用方法)——SQLServer触发器
  3. 第一部分 HTTP:Web的基础——第2章 URL与资源
  4. 【OCR识别验证码】--基于tesseract
  5. UWB测试是什么,UWB技术是什么?UWB超宽带技术测试,UWB室内定位技术是什么?
  6. 关于餐饮行业环境污染现状的调研报告
  7. 解决 水星(Mercury)UD6S-V1.0-Linux20210616驱动在5.15内核报错
  8. html和css学习心得
  9. 本地推送通知和远程推送通知
  10. 机会是给有准备的人的