iOS逆向 - shell 脚本自动重签名与代码注入

5,165 阅读5分钟

前言:

本篇文章建立在上篇博客 :

iOS逆向 - 应用签名原理及重签名 (重签微信应用实战) 的基础知识之上的 , 不了解的同学欢迎去阅读 . 本篇会讲述如何利用 shell 脚本自动重签名和应用调试 .

如果篇幅不过长的话 , 我们再来玩一玩代码注入 .

另外 笔者使用的是 Xcode 11 , 所以 Xcode 新版本目前没发现有问题 . 一样玩 .

利用 Xcode 重签名和调试

这里之所以要用 Xcode 来做一次重签 , 有以下几个目的 :

  • 节省自己创建描述文件 / 重签名应用的步骤 .
  • 有了工程可以看得到部分打印.
  • 方便我们自己添加代码运行 .
  • ... 等等

利用 Xcode 重签的原理和上篇博客中基本一样 ( 因为我们也提到过 , Xcode 本身也就是利用 codeSign 来做的 ) , 因此 , 我这边快速过一遍 , 不过多讲述了 .

步骤 1 : 新建同名工程

之所以要用同名工程 , 因为是有一些坑的 .

( 不使用同名工程时 , 每次 build 会重新创建一个 Mach-O , 也就是说此时你就算更换了包 , 但是这个包里有两个 Mach-O , 而运行的还会是你自己的空工程 , 所以在我们已经下载到 ipa 看得到包里的 Mach-O 的情况下 , 我们就起和它一样的名字就好 )

ok , 新建工程 , 选择开发者 , 选择真机 , 直接 run .

run 成功之后查看生产的 app

看完前面一篇博客的同学应该都清楚 , 这一步是为了把描述文件安装到手机里 .

步骤 2 : 替换 App 包

找到之前从pp助手里下载好的 已经砸过壳的微信应用包 , 直接替换.

步骤 3 : 删插件

这一步跟我们之前自己纯手动操作一样 .

  • 打开 WeChat , 显示包内容 . 找到 PlugIns 文件夹, 直接删除 插件普通账号是签不了的 .

  • 找到 Watch 文件夹 , 因为这里也有插件 , 我们暂时不需要 Watch , 直接删掉 .

步骤 4 : 重签 FrameWork

进入 FrameWork 文件夹

利用 CodeSign , 使用我们的证书进行重签名.

codesign -fs "复制的你自己的证书名字" 要重签的FrameWork名称

例如:

codesign -fs "iPhone Developer: ha ha (123456)" andromeda.framework

FrameWork 文件夹下所有的库全部重签.

注意 ⚠️:

这个时候 App 包里的Bundle ID 还是微信的 ,但是我们先不改 , 直接运行 .

重签成功

注意看原本我们的空工程 , 在手机上已经安装上了微信的包 ,

那么你再去查看微信包里的 Info.plist 里的 Bundle ID , 已经被自动替换成我们的包名了 .

那么对比一下我们纯手写的重签 , 这个显然更加简单且实用 . 但是每次重签都这么搞一下也不太合适 , 当我们理解了重签原理之后 , 就没有必要每次这么搞了 .

而且我也不一定能保证我确实是同名工程 , 我希望我随便叫什么名字都可以重签 .

ok. 进入 Shell 部分 .

利用 Shell 脚本自动重签名

Shell 以及 Shell 脚本

为了统一大家前导知识的前提 , 我们还是先来介绍下 SHELL 脚本 , 已经熟悉的同学可以自行跳过 .

  • Shell 是一种特殊的交互式工具,它为用户提供了启动程序、管理文件系统中文件以及运行在系统上的进程的途径。

  • Shell 一般是指命令行工具。它允许你输入文本命令,然后解释命令,并在内核中执行。

  • Shell 脚本,也就是用各类命令预先放入到一个文本文件中,方便一次性执行的一个脚本文件。

简单来说 , 大家可以理解为我们平时在 终端 / iTerm2 中写的命令的集合 . 当然 , 它就必不可少的设计到环境变量和权限等问题 .

MAC 系统中 , 默认使用的 shellbash . 我们使用 iTerm 的时候 , 经常会配置另一个 shellzsh . 当然 , 还有一些其他的 shell .

我们需要知道的是 bash 的初始化文件是家目录下的 .bash_profile zsh 的初始化文件同样是家目录下的 .zshrc . 里面配置了一些环境变量.

写一个简单的 shell 脚本

例如我们经常有以下操作 :

mkdir test
cd test
touch 123.txt
cd ..

那么我们想将这些指令打包在一起 , 统一运行 . 类似于数据库中事务一样 .

输入命令 :

vi 123.sh

输入文件内容:

mkdir test
cd test
touch 123.txt

好 , 此时 我们已经生成了一个最简单的脚本 123.sh .

那么如何执行呢 . 方法有很多种

    1. 使用 MAC 自带的 bash shell :
    bash 123.sh
    

脚本执行完 , 我们的文件也创建好了 .

    1. zsh
    zsh 123.sh
    
    1. source
    source 123.sh
    
    1. ./
    ./123.sh
    

注意 :

source执行完之后停留在子目录里 .

./ 可能会报没有执行权限的问题 . +x 即可

区别

  • $source FileName

    • 意思:在当前 shell 环境中读取并执行 FileName 中的命令
    • 特点:
      • 命令可以强行让一个脚本去立即影响当前的环境(一般用于加载配置文件)。
      • 命令会强制执行脚本中的全部命令,而忽略文件的权限。
  • $bash FileName / $zsh FileName

    • 意思:重新建立一个子 shell,在子 shell 中执行脚本里面的句子。
  • $./FileName

    • 意思:读取并执行文件中的命令。但有一个前提,脚本文件需要有可执行权限。

shell 重签名

步骤 1. 新建工程

名称随意 , 新建完毕选择开发者 . 证书 

运行一下空工程 ( 将描述文件安装到手机上 )

步骤 2. 准备要重签的包

  • 在工程根目录下新建 APP 文件夹 , 也可以起其他名称 , 与 Shell 脚本中的文件名对应起来就可以.
  • 将下载好的砸壳应用 ipa 放入 APP 文件夹中.

步骤 3. 新建 Run Script Phase

可以看到他默认使用的是 shshell .

当然 大家可以先随便写点 然后 build 一下看看有没有执行.

开始编写脚本 :


# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包
ASSETS_PATH="${SRCROOT}/APP"
#目标ipa包路径
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"
#清空Temp文件夹
rm -rf "${SRCROOT}/Temp"
mkdir -p "${SRCROOT}/Temp"



#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# echo "路径是:$TEMP_APP_PATH"


#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"

rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"



#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"



#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
#  设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"


#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径WeChat
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#上可执行权限
chmod +x "$TARGET_APP_PATH/$APP_BINARY"



#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do


#签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
done
fi

脚本中步骤都有详细注释说明. 那么我们来总结一下这个脚本做了什么.

其实跟我们手动重签的步骤是一模一样的.

  • 1 在工程跟目录中找到 APP 文件夹. 找到里面的后缀为 .ipa 的文件路径 , 创建并清空 Temp 文件夹 . (以便后面存放解压ipa包的内容)

  • 2 将 ipa 解压到 Temp 路径中 , 找到其中 app 包的路径. ( Palyod 里的 .app 包) 并将解压出来的 .app 包拷贝进入工程下 ( 其实就是替换掉工程 build 产生的.app 包)

  • 3 删除 PlugInsWatchAPP.

  • 4 更新 info.plist 文件 CFBundleIdentifier

  • 5 给 MachO 文件上执行权限

  • 6 重签名第三方 FrameWorks

( shell 中添加 echo 可以进行打印输出 , 在 build 记录中 )

当然 , 你可以直接在 Run Script Phase 里写完这些 shell , 也可以写到一个 app.sh 脚本文件中 , 然后在 Run Script Phase 里写一个 ./app.sh 即可 .

如果你遇到

相信你已经知道怎么解决了. 是的 chmod +x app.sh

重签成功 !

framework 代码注入

一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用 FrameWork 或者 Dylib 等三方库的方式注入。

当然 , 也可以直接修改 Mach-O 文件 , 这个涉及到汇编以及二进制 , 后面会考虑要不要继续解读 .

首先我们需要知道的是 Mach-O 文件拥有固定的格式 , 而对于 iOS 系统应用来说 , 需要通过 dyld 去读取 Mach-O , 从而决定文件包需要加载哪些库 , 如何去加载等等 ,

后续我会专门讲一讲 Mach-O 文件 以及 dyld 链接流程 和 符号表的一些内容.

使用 MachOView 可视化预览 Mach-O 文件内容 ( otools 命令也可以 ) .

那么因此 , 我们可以思考 :

如果我可以修改 Mach-O 文件的 Load Commands ( 加载指令 , 就是告诉 dyld 如何去加载 ) , 往其中添加一条 , 添加一条指令让其去加载 我们自己生成的 framework , 是否可行呢 .

答案是肯定的 .

步骤 1 : 工程准备

准备好已经重签完的工程 或者上述 准备好了 shellipa 的工程.

步骤 2 : 新建 framework

File - New - TargetCocoa Touch Framework , 我这里取名 LBHook

步骤 3 : framework 中新建一个类

.m 文件中写 load 方法 .

熟悉 load 方法机制的同学都知道 , load 方法是在 main 函数之前 , dyld 加载 _objc_init 时就会调用的. ( 不清楚的同学可以去阅读一下 iOS load方法调用机制解析 这篇文章 )

步骤 4 : cmd + B 编译一下

编译后打开包内容 , 查看 Frameworks 文件夹 , 可以看到我们的 framework 已经放进去了 .

但是没用啊 , 微信的 Mach-O 的 加载指令 load Commands 里并不会去加载我的这个库 .

步骤 5 : 修改 Mach-O

  • 给当前 Mach-O 添加指令 让其加载我们的 framework . 这里要使用一个工具 yololib 提取码 : wxvv

  • 下载完毕后 , 将其复制到 usr/local/bin 中 , 这样环境变量就可以在任意路径下使用 yololib 了.

  • 随便找一个 Mach-O 文件 , go2shell , 或者手动 cd 到这个目录.

  • yololib Wechat Frameworks/你的framework名称路径/你的framework

    • 例 : yololib Wechat Frameworks/LBHook.framework/LBHook
    • 注意 这个 framework 路径是相当于前面 macho 文件的路径 .

修改完毕后可以利用 MachOView 来看下我们的加载指令添加进去了没.

  • 那么实验完毕可以添加指令 , 我们就来修改一下微信的原包里的 Mach-O , 让其加载我们的 framework .
  • 打开我们 APP 路径下的 ipa 包 , 改为 zip , 解压 , Payload 里的包 , 显示包内容 , 找到 Mach-O .
  • yololib Wechat Frameworks/LBHook.framework/LBHook
  • 修改完毕后 重新压缩 使用 zip -ry Wechat.ipa Payload ( 其实不重新压缩也可以 跟我们 shell 脚本里对应起来就好了 ) .

见证奇迹的时刻 :

cmd + r 运行 . 看控制台打印

思考题

那么既然我们可以注入代码 . 那我们可以做什么 ?

给个思路 :

可以注入代码 , 我们就可以 hook 微信的方法 . 上一篇文章我提到过 view Debug , 我们是可以看得到点击 登录 / 注册 的时候 , 调用的具体是什么方法的 . 又有黑魔法的存在 , 我们可以做的很多 . 大家自己去玩 .

补充

  • 代码注入时修改 Mach-O 的 load commands 我们是使用手动注入的 . 其实我们在把 yololib 环境变量配置好之后 , 完全可以在之前的 shell 脚本里做注入 .
    • 上面 shell 脚本中最后一行 加上
    • yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HankHook.framework/HankHook"
    • framework 的名称注意去修改就行
    • 这样的话 , 我们做重签名和代码注入只需要 新建个空工程 运行 , 然后下载完微信的 ipa , 根目录新建个 APP 文件夹 , 放进去运行即可.

dylib 代码注入

有些工具做代码注入的是 Library 就是 dylib 的库 , 其实它的原理和我们注入 framework 是一样的 , 只不过步骤上略有不同.

我们就使用 shell 简单带过一下.

另外 , iOS 现在已经不能添加 dylib,只能从 macOS 添加

工程准备

同样是准备好已经重签完的工程 或者上述 准备好了 shellipa 的工程 .

新建库

File - New - TargetmacOS - Library , 我这里取名 LBHookDylib

配置

  • 打开 Build Settings , 选择 target , 找到 Base SDK , 选择 iOS.
  • 找到 Code Sign Identity , 选择 iOS 的证书
  • 选择 Build Phases , 切换为主工程的 target, 新建 Copy Files

  • 添加

  • Destination 选择 Frameworks

  • shell 脚本中最后一行多加一句

    • yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libLBHookDylib.dylib"
  • 运行 , 代码注入成功 .