动态库注入

dylib.png

一、前言
二、注入思路
三、动态库注入实现
四、分析实现按钮监听
五、实战修改微信步数

一、前言

在文章《应用签名-脚本签名》中介绍了如何在真机上运行破壳应用(抖音、微信、支付宝等ipa包),来观察应用视图的层级结构,方法调用,类名称等,以便学习参考。应用主要与后台进行数据交互展示(数据拉取及提交数据),既然能对破壳应用重签名并在真机上运行,那么能不能修改应用数据和页面展示呢,如监听微信登录按钮,修改微信步数,下面就这两个功能拓展一下思路。

二、注入思路

ipa包中,主要包含了应用签名、资源文件、Frameworks文件、info.plist配置文件及与ipa包同名的可执行通用文件(这里包含了具体的业务执行指令)。在《Mach-O》中我们对Mach-O文件有一个初步了解,主要有Header、Load commands、Data三部分组成。

Header:包含Mach-O文件的基本信息,字节顺序、架构类型、加载指令的数量等;
Load commands:包含区域位置、符号表、动态符号表,加载Mach-O文件时使用这里的数据确定内存分布;
Data:数据段segement,包含具体代码、常量、类、方法等。

如果要向已有应用ipa中加入自己的代码,必须对Mach-O进行修改重组。根据已有开发经验有思路两个:

1、修改功能代码,向Mach-O中的Data部分增删改数据段;
2、另一个是添加动态库,利用runtime对原始代码进行方法替换,数据、UI的修改。

第一种方案需要分析Mach-O对应的汇编代码,来修改功能,操作较繁琐;第二种方案,需要我们向ipa包下的Frameworks中添加写好的动态库,并向Load commands添加动态库加载指令,同时还需要修改Header中对应的基本信息,相对于第一种不用做汇编指令分析了。

以上只是个人的一些思考,这里就不去研究具体的注入过程,直接使用一个第三方库来完成动态库的注入,先完成微信登录按钮的监听,以后再对具体的操作进一步学习研究。

第三方动态库注入工具:yololib

三、动态库注入实现

破壳ipa获取:
1、通过越狱手机获取破壳应用;
2、通过PP助手获取越狱应用。

《应用签名-脚本签名》中实现了对破壳应用的重签名,并运行在真机上可供调试。

1、创建新工程
常规创建,工程名InsertCode(工程名随意):

project.png

2、插入签名脚本、获取破壳ipa包,放入app文件中

file.png
  • app内的ipa包不建议导入工程,放在文件中即可
  • SignApp.sh脚本放在tool文件下

SignApp.sh脚本如下:

##!/bin/sh
#
##  SignApp.sh
##  inserCode
##
##  Created by hibo on 2019/10/15.
##  Copyright © 2019 hibo. All rights reserved.
#
##定义目录路径 ${SRCROOT}为当前工程的根目录
#
#目标文件路径
BUILD_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
#Info文件路径
TARGET_INFO_PLIST=$BUILD_APP_PATH/Info.plist
#定义临时目录变量,存放解.ipa后产生的临时文件
TEMP_PATH="${SRCROOT}/temp"
#tool路径
TOOL_PATH="${SRCROOT}/tool"
#定义APP资源目录变量,存放要重签名的APP
APP_PATH="${SRCROOT}/app"
#定义ipa包路径
IPA_PATH="${APP_PATH}/*.ipa"


#移除临时文件,并重新创建文件夹
rm -rf "${TEMP_PATH}"
mkdir -p "${TEMP_PATH}"



###########1、解压ipa到指定的文件下###########
unzip -o "$IPA_PATH" -d "$TEMP_PATH"
#获取临时app路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
echo ".app文件路径:$TEMP_APP_PATH"

###########2、创建en.plist权限文件###########
#Xcode中会生成权限文件,注意将生成的目标文件中的权限文件移动到TEMP_APP_PATH中
echo "目标代码路径:$BUILD_APP_PATH"
cp -rf "$BUILD_APP_PATH/embedded.mobileprovision" "$TEMP_APP_PATH/"

###########3、修改应用info.plist中的BundleId、displayName###########
#设置 "Set :KEY Value" "Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TEMP_APP_PATH/Info.plist"
#获取名称并设置到目标文件中的Info.plist文件中
TARGET_DISPLAY_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleDisplayName" "$TARGET_INFO_PLIST")
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $TARGET_DISPLAY_NAME" "$TEMP_APP_PATH/Info.plist"
echo "plist路径:$TARGET_INFO_PLIST"
echo "显示名称:$TARGET_DISPLAY_NAME"

if [[ "$TARGET_DISPLAY_NAME" != "" ]]; then
    for file in `ls "$TEMP_APP_PATH"`;
    do
        extension="${file#*.}"
        if [[ -d "$TEMP_APP_PATH/$file" ]]; then
            if [[ "$extension" == "lproj" ]]; then
                if [[ -f "$TEMP_APP_PATH/$file/InfoPlist.strings" ]];then
                    /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $TARGET_DISPLAY_NAME" "$TEMP_APP_PATH/$file/InfoPlist.strings"
                fi
            fi
        fi
    done
fi


###########4、删除扩展应用及插件###########
echo "Removing PlugIns and Watch"
rm -rf "$TEMP_APP_PATH/PlugIns"
rm -rf "$TEMP_APP_PATH/Watch"
#rm -rf "$TEMP_APP_PATH/com.apple.WatchPlaceholder"


###########5、给可执行文件执行权限###########
#获取可执行文件路径
APP_BINARY=`plutil -convert xml1 -o - $TEMP_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#给可执行文件权限
chmod +x "$TEMP_APP_PATH/$APP_BINARY"


###########6、重新签名Frameworks下的所有动态库###########
#获取动态库路径
APP_FRAMEWORKS_PATH="$TEMP_APP_PATH/Frameworks"
#判断文件是否存在
if [ -d "$APP_FRAMEWORKS_PATH" ]
then
#遍历所有动态库
for FRAMEWORK in "$APP_FRAMEWORKS_PATH/"*
do
echo "framework: $FRAMEWORK"
#对动态库签名 $EXPANDED_CODE_SIGN_IDENTITY xcode上的证书
/usr/bin/codesign -fs "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
echo "EXPANDED_CODE_SIGN_IDENTITY:$EXPANDED_CODE_SIGN_IDENTITY  FRAMEWORK:$FRAMEWORK"
done
fi

###########7、将修改后的.app移动到xcode对应的Products下###########
#BUILT_PRODUCTS_DIR xcode生成的.app包路径
#TARGET_NAME 应用名称
rm -rf "$BUILD_APP_PATH"
mkdir -p "$BUILD_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$BUILD_APP_PATH/"

###########8、注入到可执行文件中###########
if [ -d "$BUILT_PRODUCTS_DIR/HBHook.framework" ]
then
${SRCROOT}/tool/yololib "$BUILD_APP_PATH/$APP_BINARY" "Frameworks/HBHook.framework/HBHook"
else
echo "没有该文件"
fi

3、真机运行,执行要查看的ipa包

选择证书,在真机上运行,ipa会被安装至手机上,注意这里的ipa必须是破壳的,并具有相同架构配置的ipa否则安装失败。

4、创建Frameworks文件

framework.png

在动态库文件下创建Insert类:

create.png
  • 创建动态库文件,用来做方法替换

runtime中通常会使用+load方法,该方法会在编译期被调用,因此在应用运行前,对应用内部方法动动手脚。下面先在load方法中插入打印代码,再向ipa包中插入动态库,看看是否能够插入到新包中。

代码:

@implementation Insert
+ (void)load {
    NSLog(@"插入成功");
}
@end

5、引入注入工具yololib

下载 yololib 并编译(或直接拿到可执行文件),将可执行文件复制到工程中的tool目录下,注意给执行权限。

yololib.png

SignApp.sh脚本最后一行插入注入指令:

#注入
if [ -d "$BUILT_PRODUCTS_DIR/HBHook.framework" ]
then
${SRCROOT}/tool/yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HBHook.framework/HBHook"
else
echo "没有该文件"
fi

完整操作如下:

yololib.png

准备工作完成,运行工程:

insert.png

打印“插入成功”,说明动态库已成功插入到ipa中,用 MachOView 打开.app中WeChat通用二进制文件,查看新的布局如下:

MachO.png

Load commands中已经有了动态库的加载指令,接下来就对原登录按钮的点击方法做替换监听。

四、分析实现按钮监听

替换原登录方法,需要我们知道对应的方法名称。怎么查找?如下:

view.png
  • 通过层级关系立刻定位到了登录方法信息,Action onFirstViewLogin,所属类WCAccountLoginControlLogic

瞬间感觉微信很友好😘!!!

那么开始编写方法替换代码:

#import "Insert.h"
#import <objc/message.h>
@implementation Insert
+ (void)load {
    NSLog(@"插入成功");
    Method old_method = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    Method new_method = class_getInstanceMethod(self, sel_registerName("my_onFirstViewLogin"));
    method_exchangeImplementations(old_method, new_method);
}
-(void)my_onFirstViewLogin {
    NSLog(@"我来了");
    [self my_onFirstViewLogin];
}
@end
  • 交换了交换了原始方法的imp指向
  • 通过runtime函数来获取类及类方法
  • my_onFirstViewLoginWCAccountLoginControlLogic的实例调用,因此self指的是WCAccountLoginControlLogic的实例

运行工程,点击登录按钮,如下:

login.png

第一行打印了“我来了”,说明,我们监听到了按钮的点击事件,但后面出现崩溃,此处很好理解,在原控制器中并没有找到对应的选择器。回忆之前的方法替换,我们是在对应类的分类中去替换,方法在编译时会加入到该类的方法列表中,而此处并不是原类的分类。

那么如何解决呢,这里有三种方法:

1、给原类添加方法

+ (void)load {
    NSLog(@"插入成功");
    Method old_method = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    BOOL result = class_addMethod(objc_getClass("WCAccountLoginControlLogic"),
                    sel_registerName("new_method"), (IMP)new_method, "v@:");//添加新的方法
    NSLog(@"%@",result?@"添加成功":@"添加失败");
    method_exchangeImplementations(old_method, class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"),sel_registerName("new_method")));
}
void new_method(id self, SEL _cmd){
    NSLog(@"我来了");
    [self performSelector:sel_registerName("new_method")];
}
  1. 添加方法并替换方法的imp
  2. imp实现中通过performSelector来调用原有类添加的方法,从而找到原方法对应的实现。
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
  • cls:为哪个类添加方法
  • name:设置方法名称
  • imp:设置方法对应的imp,此处imp设置在当前类,以便调用
  • types:定义方法类型

2、设置原方法实现

+ (void)load {
    NSLog(@"插入成功");
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin")), (IMP)new_method);
}
IMP (*old_imp)(id self,SEL _cmd);
void new_method(id self, SEL _cmd){
    NSLog(@"我来了");
     old_imp(self,_cmd);
}
  1. class_getMethodImplementation:获取原有方法对应的imp
  2. method_setImplementation:给原有类的方法设置新的imp指向;
  3. old_imp(self,_cmd):执行保留原类方法实现继续执行原有方法内部指令。

3、 替换原方法实现

+ (void)load {
    NSLog(@"插入成功");
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    old_imp = class_getMethodImplementation(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"));
    class_replaceMethod(objc_getClass("WCAccountLoginControlLogic"), sel_registerName("onFirstViewLogin"), (IMP)new_method, "v@:");
}
IMP (*old_imp)(id self,SEL _cmd);
void new_method(id self, SEL _cmd){
    NSLog(@"我来了");
     old_imp(self,_cmd);
}
  1. 保留原有方法的实现;
  2. 替换原方法的实现,当触发按钮,将调用该函数;
  3. 在新函数中,执行原方法实现。

以上方法都能完美解决找不到实例方法的问题,点击登录,后打印并跳转到登录页面。

注意:方法与实现是两个不同的概念,方法是SEL(选择器),实现是IMP具体的函数。方法(SEL)指向实现(IMP)。

小结:

OC是一种动态类型语言,在运行时来确定具体的数据类型,利用runtime原理,通过消息发送msg_send(id,SEL),找到对应的C函数实现,selimp相当于一种映射关系,基于这一层的连接,给予我们hook的机会,通过改变这一对应关系来改变程序的执行顺序。

五、实战修改微信步数

修改微信步数同上,通过替换方法,在微信上传步数时,监听方法,替换上传数据。

首先介绍一个工具Class-dump:

该方法利用runtime特性能够提取MachO文件中的信息,并产生原应用对应的所有头文件信息,通过这些信息,能够快速定位目标类,目标方法。下载地址:http://stevenygard.com/projects/class-dump

先使用该工具获取.ipa中对应的头文件:

class-dump -H WeChat -o apph

目标WeChat.app包中的通用二进制文件,导出头文件到apph文件夹中。如下:

login.png

以上就是WX的所有头文件信息,有属性,有方法,看到上面标注的就是前面通过页面找到的类即方法。
这里可以运行查看微信运动页面,找到相关的方法或属性,进行修改。这里就不运行查看了(害怕封号😂)。直接定位到修改步数的类:

modify.png

找到类即属性名称,这里就直接替换原有的方法,直接返回相应的步数就行。代码如下:

+ (void)load {
    NSLog(@"插入成功");
    [self modifyWxStep];
}
+(void)modifyWxStep{
    //修改微信步数
    Class class = objc_getClass("WCDeviceStepObject");
    SEL select = sel_registerName("m7StepCount");
    
    Method method = class_getInstanceMethod(class, select);
    const char *typeEncoding = method_getTypeEncoding(method);
    NSLog(@"typeEncoding:%s",typeEncoding);
    class_replaceMethod(class, select, (IMP)my_m7StepCount, typeEncoding);
}
int my_m7StepCount(id self, SEL _cmd){
    return 56382;
}

安装后,停止xcode运行,再手机上启动应用,登录账号并来到微信运动公众账号,查看自己的步数,如果步数没有修改,杀死应用重新进入即可。如下:

step.png

demo:https://github.com/yahibo/InsertCode

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容