iOS底层原理笔记

iOS底层原理笔记

端口映射

1
python tcprelay.py -t 22:10010 // usbmuxd将本地10010端口tcp协议 通过usb转发到iPhone的22端口

Cycript 安装和使用

安装源:apt.saurik.com/cydia

iOS11及以上iOS版本中不能直接使用Cycript,如果想继续使用可使用以下方法

  1. ssh登录iPhone

  2. 执行以下命令(如没有安装wget,在Cydia安装)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    wget [http://apt.saurik.com/debs/cycript_0.9.594_iphoneos-arm.deb ](https://links.jianshu.com/go?to=http%3A%2F%2Fapt.saurik.com%2Fdebs%2Fcycript_0.9.594_iphoneos-arm.deb)

    wget [http://www.tateu.net/repo/files/net.tateu.cycriptlistenertweak_1.0.0_iphoneos-arm.deb ](https://links.jianshu.com/go?to=http%3A%2F%2Fwww.tateu.net%2Frepo%2Ffiles%2Fnet.tateu.cycriptlistenertweak_1.0.0_iphoneos-arm.deb)

    wget [http://www.tateu.net/repo/files/net.tateu.cyrun_1.0.5_iphoneos-arm.deb](https://links.jianshu.com/go?to=http%3A%2F%2Fwww.tateu.net%2Frepo%2Ffiles%2Fnet.tateu.cyrun_1.0.5_iphoneos-arm.deb)

    dpkg -i cycript_0.9.594_iphoneos-arm.deb

    dpkg -i net.tateu.cycriptlistenertweak_1.0.0_iphoneos-arm.deb net.tateu.cyrun_1.0.5_iphoneos-arm.deb
  3. 通过cyrun进入cycript

    1
    2
    cyrun -n SpringBoard -e 
    // -n表示SpringBoard为进程名字,-e表示使cycript能注入到SpringBoard进程中
  4. 退出cycirpt

    1
    2
    1.  control 加 D,退出cycript
    2. 执行 cyrun -n SpringBoard -d,将cycript从之前的进程中抽离出来,才能将cycript注入另一个进程噢。

Cycript使用

打印view层级数

1
2
3
[UIApp.keyWindow recursiveDescription]
[UIApp.keyWindow recursiveDescription].toString() //排版更清楚
UIApp.keyWindow recursiveDescription().toString() // 也可这么写

筛选

1
choose(UIViewController)

查看对象的缩影成员变量

1
*对象

编译

1
2
3
clang test.c // 编译c语言文件为可执行文件
clang -o test2 test.c // test2 为编译后可执行文件名
clang -c test.c// 只编译成.o文件不链接

查看文件信息

1
file Test

查看可执行文件支持架构

1
lipo -info Test

拆分二进制文件架构

1
lipo Test -thin armv7 -output Test_armv7

合并二进制文件

1
lipo -create Test_arm64 Test_armv7 -output Test2

Mach-O基本结构

1
2
3
4
5
6
Header
- 文件类型,目标架构等
Load Commands
- 描述文件在虚拟内存中的逻辑结构、布局
Raw segment data
- 在Load commands中定义的Segment的原始数据

验证可执行文件是否被加壳

  • 使用MachOView 查看Load Commands 中LC——ENCRYPTING_INFO 中 Crypt_ID 是否为0 // 0未加密

  • otool -l Test | grep Crypt // cryptid 为0 则未加密

APP砸壳

APP砸壳使用的工具

  • Clutch (http://github.com/KJCracks/Clutch/releases)

    使用方法:

    将Clutch文件拷贝到iPhone的/usr/bin目录

    注:如果在iPhone上执行Clutch指令,权限不够,需赋予“可执行权限” chmod +x /usr/bin/Clutch

    1
    2
    Clutch - i // 获取加壳应用列表
    Clutch -d + 列表序号
  • dumpdecrypted(https://github.com/stefanesser/dumpdecrypted/)

    在下载后的dumpdecrypted目录执行make,会执行Makefile文件自动编译出dumpdecrypted.dylib动态库

    将动态库文件拷贝到iPhone上(如果是root用户,建议放/var/root目录)

    1
    2
    3
    # 在/var/root目录中执行
    DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib app可执行文件路径
    # 脱壳的App会生成在/var/root/目录下

iOS12真的是对逆向不太友好,解决了各种kill 9之类的问题之后,你会发现上面的方法都砸壳失败了。最终使用CrackerXI 解决

  • flexdecrypt (iOS12之后砸壳可用)

    首先连接到越狱的iPhone上,然后使用wget命令下载最新的deb(wget自行安装):

    1
    iPhone:/tmp root# wget https://github.com/JohnCoates/flexdecrypt/releases/download/1.1/flexdecrypt.deb

    然后直接安装:

    1
    2
    3
    4
    5
    6
    iPhone:/tmp root# dpkg -i flexdecrypt.deb
    Selecting previously unselected package flexdecrypt.
    (Reading database ... 3858 files and directories currently installed.)
    Preparing to unpack flexdecrypt.deb ...
    Unpacking flexdecrypt (1.1) ...
    Setting up flexdecrypt (1.1) ...

    安装完之后就可以使用了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    iPhone:/tmp root# flexdecrypt
    Error: Missing expected argument '<file>'

    OVERVIEW: A tool for decrypting apps and Mach-O binaries. Based on the Flex 3
    jailbreak app's source code.

    USAGE: flex-decrypt <subcommand>

    OPTIONS:
    --version Show the version.
    -h, --help Show help information.

    SUBCOMMANDS:
    file (default) Decrypt file.

    See 'flex-decrypt help <subcommand>' for detailed help.

    看描述,flexdecrypt应该不如clutchname智能,因此只能先cd到要砸壳的app目录下,然后再进行砸壳,具体如何找App的路径可以参考之前的博客,这里以砸Quantumult为例:

    1
    2
    3
    4
    5
    iPhone:/tmp root# cd /var/containers/Bundle/Application/
    iPhone:/var/containers/Bundle/Application/ root# cd 6B8B93D5-DB46-4AAE-A264-F1C93A689B65
    iPhone:/var/containers/Bundle/Application/6B8B93D5-DB46-4AAE-A264-F1C93A689B65 root# cd Quantumult.app/
    iPhone:/var/containers/Bundle/Application/6B8B93D5-DB46-4AAE-A264-F1C93A689B65/Quantumult.app root# flexdecrypt Quantumult
    Wrote decrypted image to /tmp/Quantumult

    可以看到静态砸壳的速度是很快的,基本秒出,但是只有一行输出,告诉了你脱壳文件的位置,使用scp命令将文件取出:

    1
    2
    3
    4
    5
    6
    7
    8
    ➜  ~ scp -P 2222 root@127.0.0.1:"/tmp/Quantumult" ~/Desktop
    root@127.0.0.1's password:
    Quantumult 100% 3235KB 34.9MB/s 00:00
    ➜ ~ cd Desktop
    ➜ Desktop otool -l Quantumult| grep crypt
    cryptoff 16384
    cryptsize 2703360
    cryptid 0
  • Frida

    可能是iOS12之后最好用的砸壳工具了,具体方法看另一个文档

Clutch 解决iOS 12 中kill 9

iOS12中似乎因为越狱不完美导致自己添加的可执行文件不受信任,报错kill: 9 ,其他可执行文件遇到kill 9 也可按照此方法尝试解决

可执行下面的代码添加信任

1
2
3
4
5
6
7
8
# safe place to work in
cd /private/var/mobile/Documents
# Get the ent from bash and save it
ldid -e `which bash` > ent.xml
# sign Clutch with the ent. "-Sent.xml" is the correct usage
ldid -Sent.xml `which Clutch`
# inject into trust cache
inject `which Clutch`

Theos 使用

安装签名工具

1
brew install ldid

配置环境变量

1
2
3
4
# vim ~/.bash_profile
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
# source .bash_profile 使配置生效

clone theos

1
git clone --recursive https://github.com/theos/theos.git $THEOS # $THEOS 是指定安装目录为上面配置的THEOS环境变量

编写tweak代码

创建项目

1
nic.pl # 选择tweak项目

配置Makefile

1
2
3
4
5
6
7
8
9
10
11
12
# 新增设备ip 端口
export THEOS_DEVICE_IP=192.168.XXX.XXX # usb连接设置为localhost
export THEOS_DEVICE_PORT=22 # usb连接设置为本地映射端口

###### 上面两个环境变量也可配置到~/.bash_profile

# 需要编译的文件 以空格分隔
tweak_XXX_FILES = Tweak.xm xxx.m

# 可以使用通配符引入需要编译的文件
tweak_XXX_FILES = src/*.xm src/*.m
tweak_XXX_FILES = $(wildcard src/*/xm)

Logos语法

1
2
3
4
5
6
7
8
%hook %end // hook一个类的开始和结束
%log //打印方法调用详情 -- 可在Xcode中查看日志
HBDebugLog: 跟NSLog类似
%ctor {...} // 在加载动态库时调用
%dtor {...} // 在程序退出时调用
%orig // 调用旧方法并拿到返回值
%new // 添加新方法 如果需要直接调用新声明的方法 需要在@interface中声明一下
%c(className) // 等同于 NSClassFromString(@"className")

类/协议/方法 未定义报错解决办法

1
2
3
4
5
6
7
8
# 声明类
@class SomeClass, SomeClass2;
# 声明协议
@protocal SomeDelegate;
# 声明方法
@interface SomeClass
- (void)someMethod;
@end

添加资源文件

新建layout文件夹

项目安装时会按照layout文件夹中目录结构安装到手机根目录

建议资源文件路径: Library/PreferenceLoader/Preference/XXXX/

1
2
3
4
5
6
7
8
9
10
11
12
13
// 资源文件推荐路径 /Library/PreferenceLoader/Preferences/  
// "AAABBB" "CCCC" 等价于 "AAABBBCCCC"
// 宏定义中#会替换为”“
#define YSFile(path) @"/Library/PreferenceLoader/Preferences/YSWeChat/" #path

%hook PlayVideoController

- (id)disableAdv {
return true
}

// Always make sure you clean up after yourself; Not doing so could have grave consequences!
%end

logify.pl

可以将一个头文件快速转换成一家包含打印信息的xm文件

logify.pl xx.h > xx.xm
// 打印信息太多,可将%log 换成 NSStringFromSelector(_cmd)

注意点:

  • 删掉__weak

  • 删掉inout

  • 删掉协议 比如 或声明协议 @protocol XXXDelegate, XXXXDelegate;

  • 删掉-(void).cxx_destruct{…}

  • 删除HBLog(@”=0x%x,(unsigned int)r”)或改为HBLog(@”=0x%@,r”)

  • 替换类名为void, 比如将XXPerson *替换为void *

    • 或者声明一下类信息@class XXPerson: NSObject;

编译安装

1
2
3
make // 编译Tweak代码为动态库 *.dylib
make package // 将dylib大包围deb文件 make package debug=0 打包release 版本
make install // 将deb文件传送到手机上,通过Cydia安装

插件将安装在/Library/MobileSubstrate/DynamicLibraries文件夹中

  • *.dylib -> 编译后的Tweak代码
  • *.plist -> 存放着需要hooc的APP ID

iOS 命令行工具开发

命令行工具的本质

  • 可执行文件
  • 和App内部可执行文件差不多

权限问题

1
2
3
4
5
6
7
8
# 1.导出权限文件 >覆盖 >> 追加 
ldid -e TestCL > TestCL.entitlements

# 2.修改权限
# 也可直接导出SpringBoard的权限文件

# 3.将修改后的权限导入该可执行文件
ldid -STestCL.entitlements TestCL // 导入权限文件 注意:-S后无空格

开发方法

  1. 修改main函数 return 0

    删除UIApplication相关方法

  2. 删除其他资源文件

  3. 在main函数中编写代码

  4. 编译

  5. 取出编译好的.app文件中的可执行文件,复制到iPhone即可运行

Xcode 调试

编译器发展历程: GCC->LLVM

调试器发展历程: GDB->LLDB

Debugserver 一开始在Xcode里,当手机连接Xcode时会安装到iPhone上(/Develpoer/user/bin/debugserver)

Xcode调试的局限性: 一般只能调试通过Xcode安装的App

Xcode调试App的原理

Xcode 上的LLDB将指令传送到手机上的debugserver,debugserver协助LLDB调试App。

动态调试任意APP

debugserver权限问题

  • 默认情况下,/Developer/usr/bin/debugserver缺少一定的权限,只能调试Xcode安装的APP,无法调试其他APP(比如来自APP Store的APP)
  • 如果希望调试其他APP,需要对debugserver重新签名,签上2个调试相关的权限
    • get-task-allow
    • task_for_pid-allow
    • run-unsigned-code
  • 将重签名的debugserver 放入 /usr/bin 目录

签名方法:

  • 通过ldid签名
1
2
3
$ ldid -e debugserver > debugserver.entitlements # 将debugserver权限导出
# debugserver.entitlements 新增两个权限
$ ldid -Sdebugserver.entitlements debugserver # 重新给debugserver签上权限
  • 通过codesign签名
1
2
3
4
5
6
# 查看权限信息
$ codesign -d --entitlements - debugserver
# 签名权限
$ codesign -f -s - --entitlements debugserver.entitlements debugserver
# 或者简写为
$ codesing -fs- --entitlements debugserver.entitlements debugserver

调试方法

让debugserver附加到某个APP进程

1
2
3
4
5
6
7
# 方式一 将debugserver附加在一个已启动的进程上
$ debugserver IP:端口号 -a 进程
# *:端口号 使用iPhone的某个端口启动debugserver服务 *标识主机地址
# -a 进程 输入APP的进程信息 (进程ID或进程名称)

# 方式二 使用debugserver 启动App
$ debugserver -x auto IP:端口号 应用可执行文件路径

使用LLDB连接debugserver

  1. 启动LLDB
1
2
$ lldb
(lldb)
  1. 连接debugserver服务
1
(lldb) process connect connect: //手机IP地址: debugserver服务端口号
  1. 使用LLDB的c命令让程序先继续执行
1
(lldb) c
  1. 接下来就可以用LLDB命令调试APP了
  2. 退出连接
1
(lldb) process detach

常用的LLDB命令

1
$ debugserver -x auto *:端口号 APP的可执行文件路径

获取偏移地址

1
2
3
4
5
6
(lldb) image list -o -f

# 输出
[ 0] 0x00000000005fc000 /var/containers/Bundle/Application/3C3035C3-3457-47D3-A0A9-4679C4CE4D92/neteasemusic.app/neteasemusic(0x00000001005fc000)
# 0x00000000005fc000 为偏移地址
# 0x00000001005fc000 为代码段地址

遇到的问题

lldb连接debugserver时报错 error: rejecting incoming connection from ::ffff:127.0.0.1 (expecting ::1)

1
2
3
4
5
6
sirde-iPhone:~ root# debugserver_arm64 *:10011 -a neteasemusic
debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.87
for arm64.
Attaching to process neteasemusic...
Listening to port 10011 for a connection from *...
error: rejecting incoming connection from ::ffff:127.0.0.1 (expecting ::1)

解决办法

使用ip地址替换 * 和localhost

手机端:

1
sirde-iPhone:~ root# debugserver 127.0.0.1:10011 -a neteasemusic

电脑端:

1
(lldb) process connect connect://127.0.0.1:10011

mac lldb提示ImportError: cannot import name _remove_dead_weakref

1
2
3
4
5
6
7
8
➜  ~ lldb
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy.py", line 52, in <module>
import weakref
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/weakref.py", line 14, in <module>
from _weakref import (
ImportError: cannot import name _remove_dead_weakref

查看是否安装了python和python@2

1
brew list

remove 多余的 python@2

1
brew remove python@2 --ignore-dependencies

debugserver 在 iOS 12遇到的坑

因iOS12为不完美越狱,开发中总会遇到各种困难 kill:9,可通过如下方法解决

  1. 从/Developer/usr/bin/debugserver取出debugserver (或使用其他工具复制到电脑)

    1
    2
    cp /Developer/usr/bin/debugserver  /var/root/
    scp root@ip:/var/root/debugserver ./
  2. 对debugserver进行瘦身,抽取arm64架构二进制文件

    1
    lipo -thin arm64 debugserver  -output debugserver_arm64
  3. 复制debugserver_arm64到手机usr/bin目录 (可使用其他工具复制)

    1
    scp debugserver_arm64 root@ip:/usr/bin/debugserver_arm64
  4. 用ldid对debugserver进行签名 (也可在在电脑中签名后复制到手机)

    1
    2
    // uncOver越狱会提供debugserver.xml在/usr/share/entitlements/debugserver.xml
    ldid -S/usr/share/entitlements/debugserver.xml /usr/bin/debugserver_arm64
  5. 使用inject命令 (貌似是添加信任)

    1
    inject /usr/bin/debugserver_arm64