多读书多实践,勤思考善领悟

iOS破解入门教程

本文于2021天之前发表,文中内容可能已经过时。

前言

网上已经有很多基于CydiaSubstrate的越狱开发教程,使用theos、MonkeyDev进行开发非常方便、高效,而本文则是脱离CydiaSubstrate,以非Unity游戏《干掉月亮》(Shoot The Moon)为例,纯手工打造动态库,注入到游戏中实现破解,虽说效率不比使用CydiaSubstrate来的高,但是能学到很多基本的知识,非常适合新手入门。

准备工作

设备

  • 一部越狱的iPhone手机(iOS 11.0~11.1.2)
  • 一台Mac机

macOS上的工具

  • Xcode
  • Terminal
  • Console
  • ssh
  • scp
  • Homebrew
  • usbmuxd
  • macho_edit
  • Hopper Disassembler
  • Impactor

iOS上的工具

  • bfinject
  • applst

App

  • 《干掉月亮》(Shoot The Moon)

下载安装

Xcode

从App Store下载,或者从苹果开发者页面下载都可以,下载最新版即可。

Terminal

macOS自带。许多命令需要在终端上执行。

Console

macOS自带。查看手机日志。

ssh

macOS自带。通过电脑登录手机。

scp

macOS自带。在手机和电脑间互传文件。

Homebrew

Homebrew官网(brew.sh)下载安装。
用于安装各种命令行工具。

usbmuxd

打开Terminal,使用Homebrew安装。

1
`$ brew install usbmuxd``# 或者``$ brew install libidevicemobile`

将iPhone上的端口通过usb线映射到电脑上的端口。

macho_edit

https://github.com/Tyilo/macho_edit下载源码。
编译后可以得到macho_edit可执行文件。
用于编辑App的可执行文件。

Hopper Disassembler

请自行搜索下载安装。
用于反编译App。

Impactor

Cydia Impactor官网(www.cydiaimpactor.com)下载。
用于安装ipa到手机。

bfinject

https://github.com/BishopFox/bfinject下载。
根据说明文档,将bfinject放到iPhone中。
用来砸壳、动态库注入等。

applst

https://github.com/DeviLeo/AppList下载。
根据说明文档,将命令行版的applst放到iPhone中。
用于列出手机上安装的App及文档目录。

《干掉月亮》(Shoot The Moon)

App Store下载。
这是一款有趣好玩的游戏。

破解

开局一台机,过程全靠猜。
下面正式进入破解阶段,我会非常啰嗦地写下所有的步骤。
如果您有一定地开发经验,尽情跳过所有已会的步骤。

砸壳

从App Store下载下来的App都是加密的,直接复制出来是无法使用Hopper Disassembler看到任何代码的,所以我们需要砸壳解密。

使用usbmuxd

如果你的Wifi环境不是很理想,可以使用usbmuxd映射端口走usb线。
此处我们映射iPhone上的ssh端口22到macOS上的2222端口,输入以下命令。

1
`$ iproxy ``2222` `22`

ssh登录iPhone

默认密码是 alpine

1
`# -p 后面跟端口,默认是22``$ ssh root@localhost ``-``p ``2222``root@localhost's password: ``# 输入密码,默认密码是alpine`

查询名称及进程ID

启动《干掉月亮》,查询App名称和进程ID。
因为《干掉月亮》的英文名是《Shoot The Moon》,所以我们在搜索进程的时候使用grep来筛选出带有moon的输出行。

1
`$ ps ``-``e | grep moon``1975` `??         ``0``:``59.42` `/``var``/``containers``/``Bundle``/``Application``/``24546F40``-``53DA``-``4C49``-``81C0``-``DED0C13DB814``/``shootthemoon.app``/``shootthemoon`

砸壳

有了App名称和进程ID,我们就可以使用bfinject来砸壳了。

1
`# 以下两条命令二选一,推荐使用App名称` `# 使用App名称``$ bash bfinject ``-``P shootthemoon ``-``L decrypt` `# 使用进程ID``$ bash bfinject ``-``p ``1975` `-``L decrypt` `# 输出结果``[``+``] Liberios detected``[``+``] Injecting into ``'/var/containers/Bundle/Application/24546F40-53DA-4C49-81C0-DED0C13DB814/shootthemoon.app/shootthemoon'``[``+``] Getting Team ``ID` `from` `target application...``[``+``] Thinning dylib into non``-``fat arm64 image``[``+``] Signing injectable .dylib with Team ``ID` `A6DVR4V967 ``and` `platform entitlements...``[bfinject4realz] Calling task_for_pid() ``for` `PID ``1975.``[bfinject4realz] Calling thread_create() on PID ``1975``[bfinject4realz] Looking ``for` `ROP gadget... found at ``0x1826574e0``[bfinject4realz] Fake stack frame at ``0x10b624000``[bfinject4realz] Calling _pthread_set_self() at ``0x182897804``...``[bfinject4realz] Returned ``from` `'_pthread_set_self'``[bfinject4realz] Calling dlopen() at ``0x182657460``...``[bfinject4realz] Returned ``from` `'dlopen'``[bfinject4realz] Success! Library was loaded at ``0x1c41e2d00``[``+``] So ``long` `and` `thanks ``for` `all` `the fish.`

命令执行完成并不表示砸壳已经完成,需要以App界面的提示为准。
下图为砸壳中,务�保持屏幕常亮。

img

下图为砸壳完成。

img

如果你会使用NetCat,选择YES,将砸壳后的App传到电脑上。否则选择No,稍后使用scp来传输文件。
砸壳后的文件在App的文档目录下,名字为decrypted.ipa。

注意
1、bfinject命令必须在bfinject目录下执行,在其它目录下执行会出错。
2、如果砸壳过程中出现闪退,按Ctrl+C退出,重启App,再执行一遍砸壳命令即可。

找到《干掉月亮》的App文档目录

此处使用工具applst查询App的文档目录。

1
`# applst默认会列出所有用户安装的App以及BundleID,``# 使用grep过滤只显示《干掉月亮》的BundleID``$ applst | grep shoot``$ applst com.pipsqueakgames.shootmoon``# 此处会显示《干掉月亮》的相关信息``localizedName:``Shoot Moon` `shortVersionString:``1.6` `vendorName:``Shaun Coleman` `applicationIdentifier:``com.pipsqueakgames.shootmoon` `itemID:``809390893` `itemName:``Shoot The Moon` `teamID:``A6DVR4V967` `bundleURL:``file``:``/``/``/``private``/``var``/``containers``/``Bundle``/``Application``/``24546F40``-``53DA``-``4C49``-``81C0``-``DED0C13DB814``/``shootthemoon.app` `dataContainerURL:``file``:``/``/``/``private``/``var``/``mobile``/``Containers``/``Data``/``Application``/``99B25BD3``-``77BA``-``47F0``-``9503``-``46F912C5A2E4` `applicationType:``User`

其中 bundleURL 就是App的目录,dataContainerURL 是App的文档目录,数据、缓存、临时文件都存储在这个目录。
注意:少数情况下,一个App可能拥有 多个dataContainerURL,applst只能列出其中一个。

传输砸壳后的文件

电脑上输入以下命令,将Documents目录下的decrypted-app.ipa传到电脑上。

1
`# -P 后面跟端口,默认是22``$ scp ``-``P ``2222` `root@localhost:``/``private``/``var``/``mobile``/``Containers``/``Data``/``Application``/``99B25BD3``-``77BA``-``47F0``-``9503``-``46F912C5A2E4``/``Documents``/``decrypted``-``app.ipa .``/``root@localhost's password: ``# 输入密码,与ssh登录时的密码相同`

破解

解压砸壳后的ipa

如下图,选中ipa文件,右键选择解压工具解压,使用系统默认的解压工具即可。

img

备份可执行文件

解压后得到一个文件夹叫Payload,里面有shootthemoon.app,其实是一个文件夹。
如下图,选中后右键选择显示包内容。

img

找到 shootthemoon 没有后缀名的文件,这是一个砸壳后的Mach-O格式的可执行文件。
复制一份到其它目录,准备拿它开刀。

使用Hopper Disassembler

启动Hopper Disassembler,菜单栏中选择File->Read Executable to Disassembler…,或者按下快捷键Command+Shift+O,弹出的对话框中选择 shootthemoon 文件,再点击OK。
紧接着会弹出如下图所示的对话框,保持默认选项,点击OK。

img

等待解析完成后,File->Save或Command+S保存一下,以免闪退后又要重新解析。

猜猜猜

虽然Hopper Disassembler已经为我们解析出来大部分的方法名,但是我们不知道该从何下手。
与其瞎忙活,不如先玩一盘再说。

img

哇哦,10分!在游戏中靠近顶部的地方有一条隐隐约约的横线,当目标正好压在这根线上时,击中会得到10分,连着击中3次10分可以获得双倍分数的加成,而在那根线下面击中时分数会递减,加成也会消失,那就有思路了,我们把那根线移到最下面,这样我们就可以不管目标在哪个位置时,击中都能获得10分。
让我们回到Hopper Disassembler,在左侧列表中输入“ten”,看看能不能搜索到有关设置10分线位置的方法。

img

哇哦,运气太好了,这么容易就找到了,MainEnemy 类里面有一个方法名叫做 tenPointLineY 的方法,看名字应该是10分线的Y坐标,如果能将Y坐标改为屏幕最底部就能达到目的了。
嗯?你想直接改汇编代码?这对于新手来说太难了,我们用高级语言来实现我们想法。

编写动态库

打开Xcode,创建一个Project,选择Cocoa Touch Framework,名字就叫ShootMoonHacker。
首先新建文件,选择Cocoa Touch Class,名字叫HackerLoader,继承NSObject类,动态库加载时初始化的代码写在这个类中。
再新建一个文件,选择Objective-C File,文件类型选择Category,类选择NSObject,名字叫Hacker,所有的破解代码都写在这个分类中。
工程目录结构如下图。

img
3

#### ShootMoonHacker.h

这个文件是创建工程时自动创建的,我们将需要公开的类全部写在这个文件里即可。

1
`#import <UIKit/UIKit.h>` `/``/``! Project version number ``for` `ShootMoonHacker.``FOUNDATION_EXPORT double ShootMoonHackerVersionNumber;` `/``/``! Project version string ``for` `ShootMoonHacker.``FOUNDATION_EXPORT const unsigned char ShootMoonHackerVersionString[];` `/``/` `In this header, you should ``import` `all` `the public headers of your framework using statements like ``#import <ShootMoonHacker/PublicHeader.h>``#import <HackerLoader.h>``#import <NSObject+Hacker.h>`


我们在文件的最后追加我们之前创建的类和分类。
import时记得使用尖括号,而不是双引号。

#### NSObject+Hacker.h

我们在分类头文件中声明一个方法,叫hack,提供给HackerLoader类调用,执行破解代码。

1
`#import <Foundation/Foundation.h>` `@interface` `NSObject (Hacker)` `-` `(void)hack;` `@end`


#### NSObject+Hacker.m

现在我们开始改写 tenPointLineY 方法。
我们可以使用Objective-C运行时的方法交换来达到我们的目的。
此处我事先封装了交换方法的代码,参数是原始方法名、原始类名、新方法、新类、是否为类方法。
虽然我们目前只需要交换 tenPointLineY 这一个方法,但是考虑到以后可能需要交换多个方法,为了避免出现大量重复的代码,所以封装一下,同时也能让代码看起来整洁一点。

1
`-` `(void)exchangeMethod:(NSString ``*``)methodName ofClass:(NSString ``*``)className toMethod:(SEL)method ofClass:(Class)``class` `isInstanceMethod:(``BOOL``)isInstanceMethod {``    ``Class c ``=` `NSClassFromString(className);``    ``SEL sel ``=` `NSSelectorFromString(methodName);``    ``Method om ``=` `isInstanceMethod ? class_getInstanceMethod(c, sel) : class_getClassMethod(c, sel);``    ``Method nm ``=` `isInstanceMethod ? class_getInstanceMethod(``class``, method) : class_getClassMethod(``class``, method);``    ``method_exchangeImplementations(om, nm);``}`


:使用方法交换需要导入<objc/runtime.h>头文件。

1
`#import <objc/runtime.h>`


接下来编写我们自己的 tenPointLineY 方法,名字就叫 my_tenPointLineY

1
`-` `(``float``)my_tenPointLineY {``    ``NSLog(@``">>>>> my_tenPointLineY"``);``    ``float` `ret ``=` `[``self` `my_tenPointLineY];``    ``NSLog(@``">>>>> my_tenPointLineY ret: %f"``, ret);``    ``ret ``=` `200``;``    ``NSLog(@``">>>>> change tenPointLineY ret to: %f"``, ret);``    ``return` `ret;``}`


[self my_tenPointLineY] 这一句,其实调用的是原始的 tenPointLineY 方法,因为在交换方法后,原始的 tenPointLineY 方法名对应的是 my_tenPointLineY 方法的代码段,而 my_tenPointLineY 方法名对应的是 tenPointLineY 方法的代码段。
具体可以搜索 Method Swizzling 来了解方法交换的细节。
我们不知道把10分线的Y坐标改为多少才是我们想要的效果,先改个200试试看。
添加NSLog打印日志是为了确定代码是否按我们预期的那样执行了。



写完 my_tenPointLineY,开始交换方法,也就是实现我们最开始声明的hack方法。

1
`-` `(void)hack {``    ``NSString ``*``className ``=` `@``"MainEnemy"``;``    ``Class selfClass ``=` `[``self` `class``];` `    ``/``/` `TenPointLineY``    ``[``self` `exchangeMethod:@``"tenPointLineY"` `ofClass:className``                ``toMethod:@selector(my_tenPointLineY) ofClass:selfClass``        ``isInstanceMethod:YES];``}`


原始的 tenPointLineY 是属于 MainEnemy 类的,my_tenPointLineY 是属于 NSObject 类的,是实例方法。

#### HackerLoader.m

我们希望在动态库加载时自动执行破解代码的话,需要这么写。

1
`static void __attribute__((constructor)) entry(void) {``    ``NSLog(@``">>>>> Code Injected <<<<<"``);``    ``NSObject ``*``obj ``=` `[[NSObject alloc] init];``    ``[obj hack];``}`


不要忘记导入头文件 NSObject+Hacker.h

1
`#import "NSObject+Hacker.h"`


方法 entry 的名字不是固定的,可以是你想要的任何符合命名规则的名字。
关键的是 attribute((constructor)) ,当动态库被加载的时候,会自动执行拥有此属性的方法。

### 编译

在编译之前,有些工程配置需要修改。

#### 修改运行所需的最低系统版本

如下图,iOS Deployment Target 改为 iOS 11.0

img

修改签名

如下图,签名 改为 None

img

修改编译目标

如下图,编译目标 改为 Generic iOS Device

img

配置修改完成后,按下Command+B编译。
编译成功后,就能得到ShootMoonHacker.framework。

动态库注入

展开Products目录,右键ShootMoonHacker.framework,选择Show in Finder。

img

新开一个Terminal窗口,输入 cd空格,然后把 ShootMoonHacker.framework 目录拖到Terminal窗口上,如下图。

img

松开后,效果如下图。

img

按下回车键后,我们需要将 ShootMoonHacker 文件传到iPhone上,命令如下。

1
`$ scp ``-``P ``2222` `ShootMoonHacker root@localhost:~``/``root@localhost's password: ``# 输入密码`

我猜测 tenPointLineY 这个方法会在开始游戏时调用,所以先将游戏退到初始界面,然后回到通过电脑ssh登录到iPhone上的Terminal窗口,进入到bfinject目录,输入如下命令注入我们的动态库。

1
`# 注入动态库``$ bash bfinject ``-``P shootthemoon ``-``l ~``/``ShootMoonHacker` `# 输出结果``[``+``] Liberios detected``[``+``] Injecting into ``'/var/containers/Bundle/Application/24546F40-53DA-4C49-81C0-DED0C13DB814/shootthemoon.app/shootthemoon'``[``+``] Getting Team ``ID` `from` `target application...``[``+``] Thinning dylib into non``-``fat arm64 image``[``+``] Signing injectable .dylib with Team ``ID` `A6DVR4V967 ``and` `platform entitlements...``[bfinject4realz] Calling task_for_pid() ``for` `PID ``3593.``[bfinject4realz] Calling thread_create() on PID ``3593``[bfinject4realz] Looking ``for` `ROP gadget... found at ``0x1826574e0``[bfinject4realz] Fake stack frame at ``0x10b3c8000``[bfinject4realz] Calling _pthread_set_self() at ``0x182897804``...``[bfinject4realz] Returned ``from` `'_pthread_set_self'``[bfinject4realz] Calling dlopen() at ``0x182657460``...``[bfinject4realz] Returned ``from` `'dlopen'``[bfinject4realz] Success! Library was loaded at ``0x1c0151b40``[``+``] So ``long` `and` `thanks ``for` `all` `the fish.`

注意
1、bfinject命令必须在bfinject目录下执行,在其它目录下执行会出错。
2、如果注入过程中出现闪退,按Ctrl+C退出,重启App,再执行一遍注入命令即可。

注入成功后,我们开始游戏看一下效果。

img
2



就像游戏中的肥蜜蜂一样,我皱起了眉头。
怎么没有任何效果,是不是动态库没有执行?

### 查看日志

让我们在电脑上打开Console,选择iPhone,重启游戏再执行一遍注入。

img

由于日志太多,我们在搜索栏输入“>>>”,按下回车,可以在列表中看到我们注入成功的代码。右键选择我们动态库输出的那行日志,选择Show Process ‘shootthemoon’,这样列表就会隐藏其它和我们App无关的日志了。

img

日志告诉我们,注入成功了,但是10分线的位置为什么没有改变呢?开始游戏的时候似乎也没有调用我们的代码。难道10分线的位置是启动游戏时候调用的?抱着试试看的想法,我们在游戏启动的瞬间执行注入命令,不过这么做会导致游戏卡住然后闪退,只能换一种方式了。

换个姿势插入动态库

还记得我们的 shootthemoon.app 目录吗?把我们的动态库文件 ShootMoonHacker 复制到这个目录下。
接下来我们要使用 macho_edit 工具来修改 shootthemoon 可执行文件的动态库列表。
还记得我们备份出来的那个 shootthemoon 文件吗?我们对它下手。
执行如下命令。

1
`$ macho_edit shootthemoon``# 输出内容``Thin mach``-``o binary:``    ``arm64 arch (offset ``0x0``)` `1` `Fat mach``-``o configuration``2` `Load command edit``3` `Exit` `Select an option: ``2`

选择 2 Load command edit,编辑列表。

1
`1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``1`

选择 1 List load commands,看看原始的列表有哪些内容。

1
`arm64 arch (offset ``0x0``):``    ``0``: LC_SEGMENT_64: __PAGEZERO``    ``1``: LC_SEGMENT_64: __TEXT``    ``2``: LC_SEGMENT_64: __DATA``    ``3``: LC_SEGMENT_64: __LINKEDIT``    ``4``: LC_DYLD_INFO_ONLY``    ``5``: LC_SYMTAB``    ``6``: LC_DYSYMTAB``    ``7``: LC_LOAD_DYLINKER: ``/``usr``/``lib``/``dyld``    ``8``: LC_UUID: ``6f33289b``-``6f33``-``3328``-``9b6d``-``3c3a847d7f7f``    ``9``: LC_VERSION_MIN_IPHONEOS: ``0.0``.``0``    ``10``: LC_SOURCE_VERSION``    ``11``: LC_MAIN: ``0x242d8``    ``12``: LC_ENCRYPTION_INFO_64``    ``13``: LC_LOAD_DYLIB: ``/``usr``/``lib``/``libz.``1.dylib``    ``14``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreText.framework``/``CoreText``    ``15``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``EventKit.framework``/``EventKit``    ``16``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``EventKitUI.framework``/``EventKitUI``    ``17``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``WebKit.framework``/``WebKit``    ``18``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``Social.framework``/``Social``    ``19``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``Security.framework``/``Security``    ``20``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreTelephony.framework``/``CoreTelephony``    ``21``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``MessageUI.framework``/``MessageUI``    ``22``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreData.framework``/``CoreData``    ``23``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreMedia.framework``/``CoreMedia``    ``24``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``StoreKit.framework``/``StoreKit``    ``25``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``AdSupport.framework``/``AdSupport``    ``26``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``SystemConfiguration.framework``/``SystemConfiguration``    ``27``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``GameKit.framework``/``GameKit``    ``28``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``OpenAL.framework``/``OpenAL``    ``29``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``UIKit.framework``/``UIKit``    ``30``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``QuartzCore.framework``/``QuartzCore``    ``31``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``AVFoundation.framework``/``AVFoundation``    ``32``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreGraphics.framework``/``CoreGraphics``    ``33``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``OpenGLES.framework``/``OpenGLES``    ``34``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``Foundation.framework``/``Foundation``    ``35``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``AudioToolbox.framework``/``AudioToolbox``    ``36``: LC_LOAD_DYLIB: ``/``usr``/``lib``/``libobjc.A.dylib``    ``37``: LC_LOAD_DYLIB: ``/``usr``/``lib``/``libc``+``+``.``1.dylib``    ``38``: LC_LOAD_DYLIB: ``/``usr``/``lib``/``libSystem.B.dylib``    ``39``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CFNetwork.framework``/``CFNetwork``    ``40``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreFoundation.framework``/``CoreFoundation``    ``41``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreMotion.framework``/``CoreMotion``    ``42``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``CoreVideo.framework``/``CoreVideo``    ``43``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``GLKit.framework``/``GLKit``    ``44``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``MediaPlayer.framework``/``MediaPlayer``    ``45``: LC_LOAD_DYLIB: ``/``System``/``Library``/``Frameworks``/``MobileCoreServices.framework``/``MobileCoreServices``    ``46``: LC_FUNCTION_STARTS``    ``47``: LC_DATA_IN_CODE``    ``48``: LC_CODE_SIGNATURE` `1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``3`

能看到加载了很多系统的动态库。
选择 3 Insert load command,将我们的动态库注入到文件中。

1
`Select the cmd you want to insert:``1` `LC_LOAD_DYLIB``2` `LC_LOAD_WEAK_DYLIB``3` `LC_RPATH``4` `Cancel` `Select an option: ``1`

选择 1 LC_LOAD_DYLIB,注入动态库。

1
`Dylib path: @executable_path``/``ShootMoonHacker`

输入 @executable_path/ShootMoonHacker,按下回车。
@executable_path 指的是 shootthemoon 这个可执行文件所在的目录,也就是 shootthemoon.app 这个目录。

1
`1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``1`

选择 1 List load commands,看看编辑是否成功。

1
`49``: LC_LOAD_DYLIB: @executable_path``/``ShootMoonHackers``/``MediaP`

如果列表中最后一项不是输入的那样,如上结果后面跟着其它字符,说明编辑出现了bug,需要退出 macho_edit,删除错误内容,再重新插入。

1
`# 此处已省略无关内容``49``: LC_LOAD_DYLIB: @executable_path``/``ShootMoonHackers``/``MediaP` `1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``6`

选择6,取消编辑。

1
`1` `Fat mach``-``o configuration``2` `Load command edit``3` `Exit` `Select an option: ``3`

选择3,退出。重新再次编辑。

1
`$ macho_edit shootthemoon``# 输出内容``Thin mach``-``o binary:``    ``arm64 arch (offset ``0x0``)` `1` `Fat mach``-``o configuration``2` `Load command edit``3` `Exit` `Select an option: ``2`

选择2,编辑。

1
`1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``2`

选择2,删除。

1
`Select a load command to remove:` `# 此处已省略无关内容``50` `LC_LOAD_DYLIB: @executable_path``/``ShootMoonHackers``/``MediaP``51` `Cancel` `Select an option: ``50`

选择50,删除bug导致的错误的动态库路径。

1
`1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``3`

选择3,删除后重新插入动态库。

1
`Select the cmd you want to insert:``1` `LC_LOAD_DYLIB``2` `LC_LOAD_WEAK_DYLIB``3` `LC_RPATH``4` `Cancel` `Select an option: ``1`

选择1,动态库。

1
`Dylib path: @executable_path``/``ShootMoonHacker`

输入动态库路径 @executable_path/ShootMoonHacker

1
`1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``1`

选择1,确认插入的动态库路径是否正确。

1
`arm64 arch (offset ``0x0``):``    ``# 此处已省略无关内容``    ``49``: LC_LOAD_DYLIB: @executable_path``/``ShootMoonHacker` `1` `List` `load commands``2` `Remove load command``3` `Insert load command``4` `Move load command``5` `Remove code signature``6` `Cancel` `Select an option: ``6`

选择6,动态库路径插入正确,可以退出编辑。

1
`1` `Fat mach``-``o configuration``2` `Load command edit``3` `Exit` `Select an option: ``3`

选择3,退出。

打包安装

把编辑好的 shootthemoon 文件覆盖掉 shootthemoon.app 目录下的原始文件。
然后在Finder中,右键 Payload 文件夹,选择 压缩”Payload”,得到 Payload.zip 压缩包,将其重命名为 shootthemoon_hacked.ipa
打开 Impactor 工具,将 shootthemoon_hacked.ipa 文件拖到 Impactorinstall Cydia Extender 的位置上。

img

松开后,会要求输入你的 Apple ID密码,然后会将ipa安装到手机上。
注意
1、Apple账号需要关闭二次验证功能,否则Impactor会安装失败。
2、安装前务必先卸载原来的App,因为从App Store下载的App签名与Impactor安装的App签名不同,所以无法覆盖安装。
推荐:申请一个新的Apple账号,专门用于Impactor安装ipa。

胜败乃兵家常事

安装完成后,赶紧启动游戏看一下效果。

img

好尴尬啊~虽然10分线出现在了底部,但是分数还是没变。看来 tenPointLineY 只是修改位置,并不会影响到分数。

大侠请重新来过

既然改10分线没有用,那就简单粗暴点,直接改分数吧。
让我们回到Hopper Disassembler,在左侧列表中输入“point”,看看能不能搜索到和分数相关的方法,貌似没有,换个词试试,搜索“score”看看。

img

第一条 -[GamePlay updateScore:] 貌似看上去可能或许大概应该好像就是修改分数的方法,尝试一下?尝试一下!
回到Xcode,在 NSObject+Hacker.m 文件中添加以下代码。

1
`-` `(void)my_updateScore:(``int``)score {``    ``NSLog(@``">>>>> my_updateScore: %d"``, score);``    ``score ``=` `10000``;``    ``NSLog(@``">>>>> my_updateScore change score to: %d"``, score);``    ``[``self` `my_updateScore:score];``    ``NSLog(@``">>>>> my_updateScore done"``);``}`

我们把分数固定在10000分,先看看效果如何。
然后修改hack方法,如下。

1
`-` `(void)hack {``    ``NSString ``*``className ``=` `@``"GamePlay"``;``    ``Class selfClass ``=` `[``self` `class``];` `    ``/``/` `UpdateScore``    ``[``self` `exchangeMethod:@``"updateScore:"` `ofClass:className``                ``toMethod:@selector(my_updateScore:) ofClass:selfClass``        ``isInstanceMethod:YES];``}`

注意:updateScore是带参数的,对应的字符串后面要有冒号。
例子- (NSInteger)plus:(NSInteger)a and:(NSInteger)b; 方法对应的字符串就是”plus:and:“。

编译通过后,将动态库传到iPhone上,用bfinject注入,玩一下看看。

img

哈哈哈哈哈,这个貌似有点过分了。虽然和预期想的不太一样,以为updateScore修改的是左下角的总分数,阴差阳错地修改了击中时获得的分数,既然如此我们把分数固定在10分就可以了。
修改代码后,重新编译,再次注入,看看效果。

img

完美。我们有了如此完美的动态库,可以有两种选择。
1、针对越狱机器,我们保留从App Store下载的版本,需要时通过bfinject注入即可。
2、针对非越狱机器,我们用macho_edit插入动态库,重新打包安装。

总结

基本知识点

1、usbmuxd的使用
2、ssh与scp的使用
3、使用bfinject砸壳
4、Hopper Disassembler的使用
5、Method Swizzling,方法交换
6、动态库的编写
7、使用bfinject实时注入动态库
8、使用macho_edit插入动态库
9、重新打包ipa
10、Impactor的使用

其它

1、如果有苹果开发者账号,可以使用 iOS App Signer 对ipa重签名,然后使用Xcode安装即可。
2、少许的汇编知识有助于使用Hopper Disassembler理解方法的实现逻辑,便于更快地找到破解的入口。
3、避免重复造轮子,做一名调包侠没什么不对。当好奇轮子是怎么造出来的时候,再去深入研究它。
4、基础一定要打好,但不是等基础打好了再去干活,而是边干活边打基础。
5、阅读他人代码的最佳方式就是帮他改Bug。
6、走弯路是必然的,但也是值得的,你接触到了走直路接触不到的东西。