动态库注入app以及在非越狱手机使用
1. 动态库编写
动态库编写有多种方式,可以使用Xcode创建动态库,也可以通过tweak生成动态库
- 对于越狱手机,可以直接编写tweak,将tweak打包成动态库
- 对于非越狱手机,可以使用Xcode创建动态库,在Xcode中编写hook代码,生成动态库
第一种,在越狱手机上编写tweak,打包成动态库
1. 安装Xcode,这个就不多说了,在苹果官网下载安装即可
2. tweak环境的安装及编写请看这个
第一个tweak生成后,我们来hook一下之前新建的一个demo, 可以在我的github上面下载到。
@implementation FirstTweakViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"FirstTweakDemo";
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self showAlert];
});
}
- (void)showAlert {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是原始弹窗" preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction * action = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:nil];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];
}
@end
demo的主要内容是在控制器的ViewDidAppear方法中延时两秒弹出一个弹窗,我们现在来一下这个逻辑,hook住弹出弹窗的方法,然后弹出一个actionSheet.
hook前的效果
3. 开始编写hook代码
回到我们生成的Tweak目录,发现四个文件
- control
- FirstTweak.plist
- Makefile
- Tweak.xm
hook的代码在Tweak.xm中编写,在xcode中打开Tweak.xm,logos语法参见Wiki. 编写如下代码
#import <UIKit/UIKit.h>
@interface FirstTweakViewController: UIViewController
- (void)showAlert;
@end
%hook FirstTweakViewController
- (void)showAlert {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];
UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];
UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];
[alert addAction:cancel];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
%end
检查无误后进行配置,打开FirstTweak.plist
, 替换bundleId为我们想要hook的App的bundleId
NH.FirstTweakDemo
4. 安装tweak到手机
然后我们将22端口转发到22222
iproxy 22222 22
回到终端,cd到tweak目录, 连接我们的越狱手机
$ make package install
如果报错,缺少THEOS_DEVICE_IP
,在终端输入如下
export THEOS_DEVICE_IP=localhost:22222
再次执行make package install
结果如下:
$ make package install
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
> Making all for tweak FirstTweak…
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
make[2]: Nothing to be done for `internal-library-compile'.
> Making stage for tweak FirstTweak…
xcrun: error: sh -c '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk -find llvm-dsymutil 2> /dev/null' failed with exit code 17664: (null) (errno=No such file or directory)
xcrun: error: unable to find utility "llvm-dsymutil", not a developer tool or in PATH
dm.pl: building package `com.yourcompany.firsttweak:iphoneos-arm' in `./packages/com.yourcompany.firsttweak_0.0.1-3+debug_iphoneos-arm.deb'
==> Installing…
root@localhost's password:
Selecting previously unselected package com.yourcompany.firsttweak.
(Reading database ... 2140 files and directories currently installed.)
Preparing to unpack /tmp/_theos_install.deb ...
Unpacking com.yourcompany.firsttweak (0.0.1-3+debug) ...
Setting up com.yourcompany.firsttweak (0.0.1-3+debug) ...
install.exec "killall -9 SpringBoard"
root@localhost's password:
输入两次密码后,成功安装到手机。 SpringBoard重启后打开FirstTweakDemo,点击Push,进入FirstTweakViewController控制器,等待两秒后可以看到。
成功的hook住了!!!
那么问题来了,这是给越狱设备使用的,如何给未越狱设备使用呢?
5. 将tweak生成的动态库注入app中,实现非越狱设备安装
回到tweak目录,可以看到,有个.theos
目录,找到.theos/_/Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib
文件,这是tweak打包后生成的动态库
用otool查看动态库的依赖情况
$ otool -l FirstTweak.dylib | grep name
name /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib (offset 24)
name /usr/lib/libobjc.A.dylib (offset 24)
name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)
name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)
name /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate (offset 24)
name /usr/lib/libc++.1.dylib (offset 24)
name /usr/lib/libSystem.B.dylib (offset 24)
name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)
动态库依赖了CydiaSubstrate,这个是属于越狱设备的,非越狱设备上没有,需要修改依赖
这是依赖库 name /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate
至于这个依赖 name /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib
不用管
修改如下
install_name_tool -change /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate @executable_path/Frameworks/libsubstrate.dylib FirstTweak.dylib
再次查看依赖,发现已经修改成功
name /Library/MobileSubstrate/DynamicLibraries/FirstTweak.dylib (offset 24)
name /usr/lib/libobjc.A.dylib (offset 24)
name /System/Library/Frameworks/Foundation.framework/Foundation (offset 24)
name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)
name @executable_path/Frameworks/libsubstrate.dylib (offset 24)
name /usr/lib/libc++.1.dylib (offset 24)
name /usr/lib/libSystem.B.dylib (offset 24)
name /System/Library/Frameworks/UIKit.framework/UIKit (offset 24)
FirstTweak.dylib与libsubstrate.dylib签名
下一步,需要将libsubstrate.dylib
和 FirstTweak.dylib
签名
我们先来查看下电脑上有哪些可用的证书
$ security find-identity -v -p codesigning
$ 1) DFCF477C3F593AFEF3AB261E9FD641873600EAAD "Apple Development: daye (12CD56GH78)"
找到之前用于签名FirstTweakDemo的证书,开始签名
$ codesign -fs 'Apple Development: daye (12CD56GH78)' libsubstrate.dylib
$ codesign -fs 'Apple Development: daye (12CD56GH78)' FirstTweak.dylib
签名成功后,将libsubstrate.dylib
与FirstTweak.dylib
复制到FirstTweakDemo.app/Frameworks/
下
然后将FirstTweak.dylib
注入到FirstTweakDemo
中
$ ./optool install -c load -p @executable_path/Frameworks/FirstTweak.dylib -t FirstTweakDemo.app/FirstTweakDemo
Found thin header...
Inserting a LC_LOAD_DYLIB command for architecture: arm64
Successfully inserted a LC_LOAD_DYLIB command for arm64
Writing executable to FiirstTweakDemo.app/FirstTweakDemo...
生成entitlements.plist文件
将FiirstTweakDemo.app/下的 embedded.mobileprovision 拷贝一份
查看描述文件内容,寻找Entitlements节点,将Entitlements节点下的内容复制到根节点下
security cms -D -i embedded.mobileprovision > entitlements.plist
最终的entitlements.plist
文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.team-identifier</key>
<string>12CD56GH78</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>12CD56GH78.*</string>
<string>com.apple.token</string>
</array>
<key>application-identifier</key>
<string>12CD56GH78.*</string>
</dict>
</plist>
接着对FirstTweakDemo.app
签名
codesign -fs 'Apple Development: daye (12CD56GH78)' --no-strict --entitlements=entitlements.plist FirstTweakDemo.app
zip -ry FirstTweakDemo.ipa Payload
现在就可以安装到未越狱的手机啦~~~
详细的信息参见install.sh
第二种,直接在Xcode中编写动态库
前面的tweak生成动态库方式,编写tweak稍显麻烦,我们直接在Xcode中编写代码。
利用Xcode,将目标APP拷贝到当前APP的目录替换现有APP,注入动态库到目标APP,重新签名,运行到设备。
1. 新建一个普通的iOS 工程, 名为InsertDemo
2. 新建一个target,选择dylib,名为InsertDemoDylib
3. 删除InsertDemoDylib类,新建InsertDemoDylib.xm与InsertDemoDylib.mm文件,工程结构如下
4. 在InsertDemoDylib.xm文件中编写hook代码,还是logos语法, 还是hook前面的FirstTweakDemo
#import <UIKit/UIKit.h>
@interface FirstTweakViewController: UIViewController
- (void)showAlert;
@end
%hook FirstTweakViewController
- (void)showAlert {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];
UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];
UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];
[alert addAction:cancel];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
%end
5. 使用theos生成InsertDemoDylib.mm的代码,这样才能让Xcode识别并编译InsertDemoDylib.mm
/opt/theos/bin/logos.pl InsertDemoDylib.xm > InsertDemoDylib.mm
InsertDemoDylib.mm 的内容如下
#line 1 "InsertDemoDylib/InsertDemoDylib.xm"
#if TARGET_OS_SIMULATOR
#error Do not support the simulator, please use the real iPhone Device.
#endif
#import <UIKit/UIKit.h>
@interface FirstTweakViewController: UIViewController
- (void)showAlert;
@end
#include <substrate.h>
#if defined(__clang__)
#if __has_feature(objc_arc)
#define _LOGOS_SELF_TYPE_NORMAL __unsafe_unretained
#define _LOGOS_SELF_TYPE_INIT __attribute__((ns_consumed))
#define _LOGOS_SELF_CONST const
#define _LOGOS_RETURN_RETAINED __attribute__((ns_returns_retained))
#else
#define _LOGOS_SELF_TYPE_NORMAL
#define _LOGOS_SELF_TYPE_INIT
#define _LOGOS_SELF_CONST
#define _LOGOS_RETURN_RETAINED
#endif
#else
#define _LOGOS_SELF_TYPE_NORMAL
#define _LOGOS_SELF_TYPE_INIT
#define _LOGOS_SELF_CONST
#define _LOGOS_RETURN_RETAINED
#endif
@class FirstTweakViewController;
static void (*_logos_orig$_ungrouped$FirstTweakViewController$showAlert)(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST, SEL); static void _logos_method$_ungrouped$FirstTweakViewController$showAlert(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST, SEL);
#line 15 "InsertDemoDylib/InsertDemoDylib.xm"
static void _logos_method$_ungrouped$FirstTweakViewController$showAlert(_LOGOS_SELF_TYPE_NORMAL FirstTweakViewController* _LOGOS_SELF_CONST __unused self, SEL __unused _cmd) {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提醒" message:@"这是hook后的提醒" preferredStyle:(UIAlertControllerStyleActionSheet)];
UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"Cancel" style:(UIAlertActionStyleCancel) handler:nil];
UIAlertAction * confirm = [UIAlertAction actionWithTitle:@"Confirm" style:(UIAlertActionStyleDestructive) handler:nil];
[alert addAction:cancel];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
static __attribute__((constructor)) void _logosLocalInit() {
{Class _logos_class$_ungrouped$FirstTweakViewController = objc_getClass("FirstTweakViewController"); MSHookMessageEx(_logos_class$_ungrouped$FirstTweakViewController, @selector(showAlert), (IMP)&_logos_method$_ungrouped$FirstTweakViewController$showAlert, (IMP*)&_logos_orig$_ungrouped$FirstTweakViewController$showAlert);} }
#line 27 "InsertDemoDylib/InsertDemoDylib.xm"
别忘了在工程中添加动态库依赖
别忘了把动态库的base SDK改为iOS,否则编译不通过
6. 编译InsertDemo,生成InsertDemoDylib.dylib
我们给工程的InsertDemoDylib target添加RunScript
dylib.sh
的任务很简单,将InsertDemoDylib这个target下的所有 .xm
后缀的文件生成对应的 .mm
,所用的工具即为 logos.pl
dylib.sh
内容如下
TARGET_NAME=${PRODUCT_NAME}
echo "TARGET_NAME:${TARGET_NAME}"
function panic() # args: exitCode, message...
{
local exitCode=$1
set +e
shift
[[ "$@" == "" ]] || \
echo "$@" >&2
exit $exitCode
}
#预处理xm、x文件
function Processor()
{
local logosProcessor="$1"
local currentDirectory="$2"
echo "currentDirectory:$currentDirectory"
if [[ $currentDirectory =~ "Build/Products" ]] || [[ $currentDirectory =~ "Build/Intermediates" ]] || [[ $currentDirectory =~ "Index/DataStore" ]] || [[ $currentDirectory =~ "/LatestBuild/" ]]; then
echo "???????"
return
fi
for file in `ls "$currentDirectory"`;
do
echo "file:${file}"
extension="${file#*.}"
filename="${file##*/}"
if [[ -d "$currentDirectory""$file" ]]; then
Processor "$logosProcessor" "$currentDirectory""$file"
elif [[ "$extension" == "xm" ]]; then
echo "XMFile:${file}"
if [[ ! -f "$currentDirectory/${file%.*}.mm" ]] || [[ `ls -l "$currentDirectory/${file%.*}.mm" | awk '{ print $5 }'` < 10 ]] || [[ `stat -f %c "$currentDirectory/$file"` > `stat -f %c "$currentDirectory/${file%.*}.mm"` ]]; then
echo "Logos Processor: $filename -> ${filename%.*}.mm..."
logosStdErr=$(("$logosProcessor" "$currentDirectory""$file" > "$currentDirectory""${file%.*}.mm") 2>&1) || \
panic $? "Failed Logos Processor: $logosStdErr"
fi
elif [[ "$extension" == "x" ]]; then
if [[ ! -f "$currentDirectory/${file%.*}.m" ]] || [[ `ls -l "$currentDirectory/${file%.*}.m" | awk '{ print $5 }'` < 10 ]] || [[ `stat -f %c "$currentDirectory/$file"` > `stat -f %c "$currentDirectory/${file%.*}.m"` ]]; then
echo "Logos Processor: $filename -> ${filename%.*}.m..."
logosStdErr=$(("$logosProcessor" "$currentDirectory/$file" > "$currentDirectory/${file%.*}.m") 2>&1) || \
panic $? "Failed Logos Processor: $logosStdErr"
fi
fi
done
}
logosProcessor="/opt/theos/bin/logos.pl"
echo "Start to genarate xxx.mm"
Processor "$logosProcessor" "$PROJECT_DIR/${TARGET_NAME}/"
给工程APP添加RunScript
目标APP替换现有APP的主要过程如下
将现有APP下的描述文件拷贝出来
if [ -f "${BUILD_APP_PATH}/../embedded.mobileprovision" ]; then
mv "${BUILD_APP_PATH}/../embedded.mobileprovision" "${BUILD_APP_PATH}"
fi
拷贝目标APP到当前APP的目录下做替换
cp -rf "${TARGET_APP_PATH}/" "${BUILD_APP_PATH}/"
拷贝描述文件到目标APP
if [ -f "${BUILD_APP_PATH}/../embedded.mobileprovision" ]; then
mv "${BUILD_APP_PATH}/../embedded.mobileprovision" "${BUILD_APP_PATH}"
fi
将依赖的动态库与当前公吃过生成的动态库拷贝到APP的Frameworks目录下
cp -rf "${BUILT_PRODUCTS_DIR}/lib""${TARGET_NAME}""Dylib.dylib" "${TARGET_APP_FRAMEWORKS_PATH}"
cp -rf "${DYLIBS_TO_INJECT_PATH}" "${TARGET_APP_FRAMEWORKS_PATH}"
将当前工程生成的动态库注入到APP的二进制文件中
APP_BINARY=`plutil -convert xml1 -o - ${BUILD_APP_PATH}/Info.plist | grep -A1 Exec | tail -n1 | cut -f2 -d\> | cut -f1 -d\<`
"$OPTOOL" install -c load -p "@executable_path/Frameworks/lib""${TARGET_NAME}""Dylib.dylib" -t "${BUILD_APP_PATH}/${APP_BINARY}"
对APP的Frameworks目录下的所有动态库签名
code_sign "${TARGET_APP_FRAMEWORKS_PATH}"
Xcode将APP安装到设备
具体脚本信息请参见install.sh
7. 修改项目配置,安装APP到设备
编译当前项目,发现报错,找不到substrate.h
InsertDemoDylib/InsertDemoDylib.xm:16:10: fatal error: 'substrate.h' file not found
#include <substrate.h>
^~~~~~~~~~~~~
1 error generated.
这是因为我们没有引入substrate.h, 在header Search Path中引入下
再次编译,报错如下
Undefined symbols for architecture armv7:
"_OBJC_CLASS_$_UIAlertAction", referenced from:
objc-class-ref in InsertDemoDylib.o
"_OBJC_CLASS_$_UIAlertController", referenced from:
objc-class-ref in InsertDemoDylib.o
"_MSHookMessageEx", referenced from:
_logosLocalInit() in InsertDemoDylib.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是因为我们的代码依赖UIKit库,但是动态库的target并没有引入,引入即可
再次编译,报错如下
Undefined symbols for architecture arm64:
"_MSHookMessageEx", referenced from:
_logosLocalInit() in InsertDemoDylib.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这是libsubstrate库没有引入的原因
再次编译,报错如下
ld: -weak_library and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used together
关闭bitcode即可
再次编译,不报错了哟~~哈哈
跑个真机试试, 哟,一次成功了!!!
采用Xcode这种方式更简单,不用编写Tweak, 而且可以给Xcode添加一个template,以后就不用一步步配置啦,直接从Xcode新建项目菜单新建一个工程就行了。
剩下的就是把想要Hook的app拖入TargetAPP目录中就可以了。