iOS逆向基础 —— Reveal越狱+非越狱使用
- 本文涉及的资源下载地址: https://pan.baidu.com/s/1Is0NT-VNxrpW4leKtRsA4A 密码: g4t3
- 快速体验:下载后,进入Resign目录,修改resign_app.sh中相关参数,替换授权文件,即可体验丝滑的逆向过程(以米家App为例)
本文将从越狱设备、非越狱设备两种方式进行介绍Reveal工具的使用。虽然是介绍Reveal,但会涉及到较多逆向相关的技术和工具;道高一尺,魔高一丈,逆向永无止境。
1、越狱设备上使用
越狱设备上使用Reveal查看App的界面,还是比较简单的,只有一个条件:你得有一台能越狱的设备~
1.1 环境准备
支持越狱的设备、OpenSSH、CydiaSubstrate
- 越狱
可直接使用爱思助手,进行一键越狱,按照助手步骤进行即可。需要注意,大部分系统重启后,越狱失效,需要重新越狱。
- 安装OpenSSH
正常情况下,Cydia首页,有OpenSSH的访问教程,按照说明安装即可。
如果出现OpenSSH找不到的情况,可以先在软件源Tab中添加威锋源,再进入搜索Tab,搜索"OpenSSH"进行安装。
威锋源地址:
http://apt.91.com
- CydiaSubstrate
正常情况下,越狱后默认安装。如果没有,搜索后安装即可。
1.2 安装Reveal
Cydia源中Reveal版本比较老,无法与Mac端新版本匹配,需要将Mac端Reveal中的iOS库拷贝到越狱设备指定的位置。
- 新建libReveal.plist
将App对应的bundleID写入,如米家com.xiaomi.mihome
(可通过爱思助手查看)。
- 拷贝文件至越狱设备
Mac端通过Help找到RevealServer.framework,将RevealServer.framework/RevealServer和libReveal.plist传至越狱设备,RevealServer在上传时需要将名称重新命名为libReveal.dylib。
reveal-show-in-finder.png可参考如下scp命令,ssh连接设备后,一键傻瓜式操作(使用了usbmux,将22端口重新定向至2222,方便usb方式连接;中途可能需要输入设备密码,默认为alpine):
#Note:
## 0、Cydia中搜索Reveal Loader2并安装(Reveal Loader安装后会和MonkeyDev冲突)
## 1、Reveal.framework从Mac中安装的应用程序中,如:/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/RevealServer.framework
## 2、修改libReveal.plist中需要hook的App BundleId
#ssh root@127.0.0.1 -p 2222
#拷贝 plist至越狱机
scp -P 2222 libReveal.plist root@127.0.0.1:/Library/MobileSubstrate/DynamicLibraries/
#拷贝 Reveal至越狱机
scp -P 2222 RevealServer.framework/RevealServer root@127.0.0.1:/Library/MobileSubstrate/DynamicLibraries/libReveal.dylib
1.3 使用Reveal
越狱设备打开App后,Mac上Reveal会有如下显示,点击图标进入即可。
如果没有出现,需要重启越狱设备。
Reveal-use.png
2、非越狱设备
在非越狱设备上使用Reveal,相对会麻烦一些,涉及到的技术(工具)包括:
-
IPA获取
-
App脱壳
-
iOSOpenDev创建动态库(或MonkeyDev直接调试,会简单很多)
-
动态库注入
-
应用重签名
2.1 IPA获取
有多种方式可获取,这里介绍简单的几种:
-
可通过Apple Configuration 2获取
-
爱思助手中,直接搜索下载即可
-
PP论坛可下载脱壳后的App,已不在维护了,有兴趣的可以看看(自从阿里收购了PP就没有然后了~):https://www.jianshu.com/p/c789f52b1cdf
2.2 App脱壳
AppStore下载的App,会加一层壳,无法进行调试、重签名后无法正常使用(打开闪退)。可通过otool命令,查看二进制文件中对应的字段cryptid
是否为0,来判断应用是否脱壳:
iblue@ibluedeMac-mini Resign % otool -l Payload/MiHome.app/MiHome | grep crypt
cryptoff 16384
cryptsize 121339904
cryptid 0
提一下几种常用的脱壳方法,具体可自行Google之:
- 大杀器:网上搜索脱壳后的App,直接下载即可
- 越狱设备,使用工具clutch,导出ipa
- 越狱设备,使用工具dumpdecrypted.dylib,导出二进制文件、动态库
https://github.com/AloneMonkey/dumpdecrypted
可以砸 framework,App启动后,查看控制台framework保存的位置,再拷出来
- 越狱设备,使用frida,导出ipa
2.3 iOSOpenDev安装及动态库生成
以前AppStore版本的程序,禁止使用非系统的动态库,主要是为了安全和性能的考虑。但不意味着App不可以使用动态库,只要将动态库加入到程序的bundle中,并使用相同的证书对动态库、app进行签名,就可以正常使用。本文为方便展示逆向基础的知识,采用iOSOpenDev来创建动态库(再次说明: 使用MonkeyDev调试会简单很多)。
下载iOSOpenDev文件夹后,执行以下步骤完成安装:
- 运行iOSOpenDev-1.6-2_0.pkg
- 执行install.sh脚本(脚本内注释,说明了干了什么)
重新打开Xcode,就可以看到动态库dylib创建界面了,继续新建CommonCrack工程。
iOSOpenDev.png
为了测试,这里Hook住登录界面类MPLoginViewController
:
1、添加打印 NSLog(@"🍎🍎🍎 %@ did appear...", NSStringFromClass([self class]));
2、填充登录界面用户名和密码
编译生成动态库libCommonCrack.dylib,会自动拷贝至上一层级的Resign目录下,供后续使用。
主要代码参考如下:
//Hook的class
//MPLoginViewController_Hook
CHDeclareClass(MPLoginViewController);
// Hook的函数
// - (void)viewDidAppear:(BOOL)animated
CHMethod(1, void, MPLoginViewController, viewDidAppear, BOOL, animated)
{
NSLog(@"🍎🍎🍎 %@ did appear...", NSStringFromClass([self class]));
CHSuper(1, MPLoginViewController, viewDidAppear, animated);
UITextField *userField = [self valueForKey:@"_userField"];
UITextField *phoneField = [self valueForKey:@"_phoneField"];
UITextField *passwordField = [self valueForKey:@"_passwordField"];
NSString *account = [[NSUserDefaults standardUserDefaults] objectForKey:@"Hook_Login_Account"];
NSString *password = [[NSUserDefaults standardUserDefaults] objectForKey:@"Hook_Login_Password"];
if (account.length) {
userField.text = account;
phoneField.text = account;
passwordField.text = password;
} else {
userField.text = @"189****7580";
phoneField.text = @"189***7580";
passwordField.text = @"xxxxx";
}
}
#pragma clang diagnostic pop
//
// ReConfigManager.m
// Exchange counterDylib
//+
// Created by iblue on 2019/10/15.
// Copyright © 2019 DH. All rights reserved.
//
#import "ReConfigManager.h"
#import "LCLogManager.h"
#import <dlfcn.h>
@implementation ReConfigManager
+ (void)load {
NSLog(@"🍎🍎🍎 %@:: %@", NSStringFromClass([self class]), @"Loaded...");
//app日志导出到文件
[LCLogManager shareInstance].maxLogSize = 10;
[LCLogManager shareInstance].isCycle = YES;
[[LCLogManager shareInstance] startFileLog];
}
...
@end
2.4 动态注入
Github地址:https://github.com/KJCracks/yololib,下载后,编译可生成yololib
即可。
通过命令行工具使用yololib
,将libCommonCrack.dylib注入至App二进制文件中,App在启动后,就会加载libCommonCrack.dylib,执行我们想要的方法。
iblue@ibluedeMac-mini Resign % ./yololib Payload/MiHome.app/MiHome libCommonCrack.dylib
2021-05-26 14:41:51.220 yololib[94245:1688683] dylib path @executable_path/libCommonCrack.dylib
2021-05-26 14:41:51.221 yololib[94245:1688683] dylib path @executable_path/libCommonCrack.dylib
Reading binary: Payload/MiHome.app/MiHome
2021-05-26 14:41:51.221 yololib[94245:1688683] Thin 64bit binary!
2021-05-26 14:41:51.221 yololib[94245:1688683] dylib size wow 64
2021-05-26 14:41:51.221 yololib[94245:1688683] mach.ncmds 100
2021-05-26 14:41:51.221 yololib[94245:1688683] mach.ncmds 101
2021-05-26 14:41:51.221 yololib[94245:1688683] Patching mach_header..
2021-05-26 14:41:51.222 yololib[94245:1688683] Attaching dylib..
2021-05-26 14:41:51.222 yololib[94245:1688683] size 61
2021-05-26 14:41:51.222 yololib[94245:1688683] complete!
注入成功后,使用Mach-O查看MiHome二进制文件 ,可以看到libCommonCrack.dylib已在Load Commands中; 同样,对libReveal.dylib进行相同的操作:
mach-o.png
2.5 重签名
重签名脚本参考附录中的resign_app.sh
,对主要的几个步骤进行说明:
1、进入Resign目录,将授权文件拷贝至此,并重命令为embeddedmobileprovision
2、拷贝脱壳后的App至此,如MiHome.app
3、修改resign_app.sh
相关参数 :
- APP_NAME,如:APP_NAME=MiHome.app
- KEYCHAIN_ID,即对应的SHA-1, 如 KEYCHAIN_ID="B69D7658D231BD17F335B67E07BA333685C1F290"
- BUNDLE_IDENTIFIER,授权文件对应的BundleID
2.6 验证及使用
签名完成后,将resign.ipa安装后手机中,打开App:
- 如果看到手机号已填充,说明代码生效;
-
如果App闪退,或者手机号为空,往往是动态注入或签名有错(闪退一般是由于使用了未脱壳的App)
打开log0.txt,就可以看到添加的打印信息 🍎🍎🍎...
Log.png
2、将手机和Mac连同一个局域网,可以看到米家的App;手机如果通过USB连接到Mac,会多出一个USB连接的标识,建议使用USB。
Reveal-use.png reveal-mihome.png3、案例归档
为方便初学者实操,对非越狱部分的内容进行了归档,https://pan.baidu.com/s/1Is0NT-VNxrpW4leKtRsA4A 密码: g4t3,目录内容包括:
-
CommonCrack:生成libCommonCrack动态库工程,生成后,自动将libCommonCrack.dylib拷贝至Resign目录下
-
MachOExplorer:Mach-O文件查看器
-
Resign:重签名、打包相关,直接运行resign_app.sh即可(需要替换成自己的授权文件 、KeychainId、BundleId)
-
Reverse_Tools:逆向相关的工具包
example.png
附录
重签名脚本
#!/bin/sh
#说明:需要修改的参数
# 1. APP_NAME,如:APP_NAME=MiHome.app
# 2. KEYCHAIN_ID,即证书对应的SHA256, 如 KEYCHAIN_ID="B69D7658D231BD17F335B67E07BA333685C1F290"
# 3. BUNDLE_IDENTIFIER,授权文件对应的BundleID,如BUNDLE_IDENTIFIER="com.dahuatech.lecheng"
# 4. 授权文件,修改为embedded.mobileprovision后放入目录中,如PROVISION_IOS="${TEMP}/embedded.mobileprovision"
############################################################
#通用函数定义
#打印命令
function echoCommand()
{
echo "$1"
$1
}
#打印xcode、编译环境信息
function printXcodeInfo()
{
xcode-select --version
xcode-select --print-path
security find-identity -v -p codesigning
}
# 打印电脑中安装的授权文件
function printProvisionFiles()
{
ls -l ~/Library/MobileDevice/Provisioning\ Profiles/
}
# Generate entitlements
# 通过Profile文件生成签名用的entitlements.plist文件
#参数1:Profile文件,保存至ENTITLEMENTS_PLIST中
#返回值:plist文件路径
function generateEntitlementPlistFile()
{
if [[ -z $1 ]]; then
echo "Error: No profiles input..."
fi
provisionvalue=`cat "${1}"`
parseEntitlement=${provisionvalue#*<key>Entitlements</key>}
entitlementFromMPP=${parseEntitlement%%</dict>*}
entitlementFromMPP="${entitlementFromMPP/<string>\*<\/string>/<array><string>applinks:funcshop.imoulife.com</string><string>applinks:dvl.lechange.cn</string><string>applinks:dx.lechange.cn</string><string>applinks:func.lechange.cn</string><string>applinks:u5c.cn</string></array>}"
entitlementHeader1='<?xml version="1.0" encoding="UTF-8"?>'
entitlementHeader2='<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
entitlementHeader3='<plist version="1.0">'
fullEntitlement=$entitlementHeader1$entitlementHeader2$entitlementHeader3"${entitlementFromMPP}</dict></plist>"
echo "${fullEntitlement}" > "$(pwd)/entitlements.plist"
#echo "------------ Entitlements file used --------------"
#echo "${fullEntitlement}"
#echo "--------------------------------------------------"
echo "$(pwd)/entitlements.plist"
}
#对可执行文件进行签名
#参数1:授权文件路径
#参数2:证书KeychainId
#参数3:可执行文件路径
function resignFile()
{
echo "Resign File: $1, $2, $3"
entitlementsPlist=`generateEntitlementPlistFile $1`
#去除旧的签名
echo "Remove _CodeSignature..."
rm -rf "$3/_CodeSignature"
#拷贝描述文件
echo "Copy provisioning file to ... $3/embedded.mobileprovision"
cp -rf "$1" "$3/embedded.mobileprovision"
#目录下有Frameworks文件夹,则需要对所有动态库进行重签名
if [ -d "$3/Frameworks" ];then
`codesign -v -f -s $2 $3/Frameworks/*`
fi
#对可执行文件进行签名
`codesign -v -f -s $2 --entitlements ${entitlementsPlist} $3`
}
############################################################
# main loop
echo "[******************** *. List Xcode & codesign info... ********************]"
printXcodeInfo
#echo "[******************** *. List Provisionfiles ... ********************]"
#printProvisionFiles
echo "[******************** 0. Check build path ... ********************]"
#文件夹路径
TEMP=`pwd`
cd "$TEMP"
#将xx.app拷贝到Payload目录下,自动读取App名称
#APP_NAME=$(ls "$TEMP/Payload")
#将xxx.app拷贝到Resign目录下
APP_NAME=MiHome.app
APP_BINARY_NAME=${APP_NAME%.*}
echo "Check Path TEMP:${TEMP}"
echo "AppName: $APP_NAME"
#临时处理,只是保证每次动态注入的二进制是原始的
rm -rf ./Payload/*
cp -rf MiHome.app ./Payload/
#检测二进制文件是否脱壳
echo "[*** Check crypt: otool -l Payload/${APP_NAME}/${APP_BINARY_NAME} | grep crypt... ***] "
APP_CRYPT_INFO=`otool -l Payload/${APP_NAME}/${APP_BINARY_NAME} | grep crypt`
echo $APP_CRYPT_INFO
if [[ $APP_CRYPT_INFO =~ "cryptid 1" ]];then
echo "[******************** Fatal error, binary is encrypted... ********************]"
exit
else
echo "[*** Check crypt succeed... ***] "
fi
echo "[******************** 1. Set resign parameters ... ********************]"
#证书签名变量【p12文件修改后需要更新】
KEYCHAIN_ID="B69D7658D231BD17F335B67E07BA333685C1F290"
BUNDLE_IDENTIFIER="com.dahuatech.lecheng"
PROVISION_IOS="${TEMP}/embedded.mobileprovision"
#libCommonCrack.dylib,注入的动态库,不能加上路径,否则App在启动时执行路径会变成 dylib path @executable_path//Users/
LIB_COMMON_CRACK="libCommonCrack.dylib"
LIB_REVEAL="libReveal.dylib"
#DISPLAY_NAME="" #eg.xxx
echo "[******************** 2. Resigning for ${APP_NAME} ... ********************]"
#为方便签名,去除watch和插件文件夹
rm -rf $TEMP/Payload/$APP_NAME/Watch
rm -rf $TEMP/Payload/$APP_NAME/PlugIns
#修改BundleID
if [[ $BUNDLE_IDENTIFIER ]]; then
echo "change bundle ID: ${BUNDLE_IDENTIFIER}"
`/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${BUNDLE_IDENTIFIER}" "$TEMP/Payload/$APP_NAME/Info.plist"`
fi
#修改App名称
if [[ $DISPLAY_NAME ]]; then
echo "change display name: ${DISPLAY_NAME}"
`/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName ${DISPLAY_NAME}" "$TEMP/Payload/$APP_NAME/Info.plist"`
fi
#删除UISupportedDevices
`/usr/libexec/PlistBuddy -c "Delete :UISupportedDevices" "$TEMP/Payload/$APP_NAME/Info.plist"`
#设置为可以通过iTunes进行共享
`/usr/libexec/PlistBuddy -c "Delete :UIFileSharingEnabled" "$TEMP/Payload/$APP_NAME/Info.plist"`
`/usr/libexec/PlistBuddy -c "Add :UIFileSharingEnabled bool 1" "$TEMP/Payload/$APP_NAME/Info.plist"`
#注入动态库
echo "yololib dynamic framework/lib: $LIB_COMMON_CRACK"
./yololib "$TEMP/Payload/${APP_NAME}/${APP_BINARY_NAME}" $LIB_COMMON_CRACK
./yololib "$TEMP/Payload/${APP_NAME}/${APP_BINARY_NAME}" $LIB_REVEAL
#copy 动态库:将需要加载的动态库,拷贝到App主目录下
echo "copy dynamic framework/lib"
cp -rf ./$LIB_COMMON_CRACK "${TEMP}/Payload/${APP_NAME}"
cp -rf ./$LIB_REVEAL "${TEMP}/Payload/${APP_NAME}"
# Resign file
resignFile "${PROVISION_IOS}" "${KEYCHAIN_ID}" "$TEMP/Payload/${APP_NAME}/$LIB_COMMON_CRACK"
resignFile "${PROVISION_IOS}" "${KEYCHAIN_ID}" "$TEMP/Payload/${APP_NAME}/$LIB_REVEAL"
resignFile "${PROVISION_IOS}" "${KEYCHAIN_ID}" "$TEMP/Payload/${APP_NAME}"
echo "==============================================="
echo "Resign result"
codesign -dvvv $TEMP/Payload/${APP_NAME}
#清理临时文件
rm -rf entitlements.plist
# Zip file generate new ipa file
echo "zip file generate new ipa file"
rm -rf resign.ipa
echoCommand "zip -qr resign.ipa Payload "
echo "[******************** End resigning ... ********************]"