2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程——用bfinject脱壳、注入自己的动态framework、cycript的使用

开篇

本篇文章是继上一篇文章:2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程——按键精灵脚本来模拟合成灯笼后本继续分享的教程,一天最多写一篇了,有时候太懒了沉迷打游戏就没写了~

网上很多教程讲了一大堆话,最终翻来翻去不知道是想实现什么功能和效果,我觉得一开始把需求&最终效果展示能让读者了解个大概和引起兴趣,不会不知所以然。后面我会按这种文章思路来分享,先把结果呈现,再详叙过程,我想是个不错的分享思路:grin:

需求&最终效果

bfinject对正在运行的APP脱壳打包成.ipa

脱壳打包成.ipa文件

注入自己的动态framework

动态注入后的效果

cycript的使用

cycript的使用

环境要求与即将使用的工具

之前我的MacOSX版本是10.10,只能安装xcode7以下的版本,xcode7以下的版本没有真机调试功能,于是升级了版本到MacOSX Catalina 10.15.4也安装了最新版xcode,但是这个版本太卡而且有些问题,很多软件兼容不了了,想以后降级。

环境 版本
操作系统 MacOSX Catalina 10.15.4 版本太新了不太好用很多工具用不了,我后面打算降级
手机系统 Iphone7 IOS11 需要越狱
bfinject 最新版
手机助手传输工具或SSH连接操作 -
xcode 11.5
电脑上的cycript 最新版
手机终端工具比如terminal -
电脑工具class-dump用来导出头文件查看 最新

工具介绍

bfinject是一款注入工具,安装后坑挺多。可以注入xcode开发的framework,也可以注入ios10以前人们用的cycript工具,因为ios11已经不支持cycript的使用了,只能通过这个工具来执行cycript的全部命令,然后用电脑的cycript连接手机cycript提供出来的端口来操作。
电脑上的cycript安装教程参考这篇文章:https://www.jianshu.com/p/d93e9fccef4b,这玩意安装后坑很多,一一填坑吧,而且官网打开好慢~。
cycript是一款动态注入工具,可以动态执行cy代码,常用来打印ui界面和调试。
ios11的ssh本人用不了,从cydia安装了openssh,但是用命令行执行ssh报无法打开二进制文件的英文错误,不知道为何,谁能在ios11运行ssh并且电脑连接手机ssh的麻烦告知我一下谢谢。

实现过程

bfinject对正在运行的APP脱壳打包成.ipa

安装bfinject

首先电脑下载bfinject,然后用手机助手等工具把二进制文件bfinject拷贝到iphone手机的随意位置下,我是放在/User/Media/目录下。bfinject下载和安装教程参考github。
这个bfinject的坑还是很多的安装后执行会出现很多报错~比如electra和bootstrap目录问题的坑;还有md5: command not found的报错,我的做法是把md5sum这个命令复制一个改成md5执行就不报错,网上有填坑例子,遇到的可以看看。另外说明,这个bfinject的执行需要关闭Tweaks才能运行成功:打开越狱工具Electra,把Tweaks选项禁用,然后重新启动。
这个bfinject的执行需要关闭Tweaks才能运行成功:打开越狱工具Electra,把Tweaks选项禁用,然后重新启动。
这个bfinject的执行需要关闭Tweaks才能运行成功:打开越狱工具Electra,把Tweaks选项禁用,然后重新启动。

手机使用bfinject

以下及下文所有手机命令都是用root用户来操作。
打开terminal到/User/Media/目录下执行:

bash bfinject -P test1.app -L test

上图界面的app是我自己随便写的一个demo app ,安装在了iphone里,我调试用的,这个app叫test1.app,这里拿来演示,-L test 是指调用bfinject内置的framework来注入,用来确定bfinject是否安装成功和生效,成功界面如下:
运行
打开到app的结果

打包

接下来用这个命令来导出ipa

bash bfinject -P test1.app -L decrypt

回到app界面
脱壳打包成.ipa文件
打包完毕!我们选择No它会把包存储到App的文档目录。
我们把包找出来:

find /var/mobile/Containers/Data/Application/ -name decrypted-app.ipa

结果
其中某个decrypted-app.ipa就是我们打包出来的路径了,把这个包/User/Media/目录下,然后用电脑手机助手工具就能拿到啦。接下来就是提取头文件了。

注入自己的动态framework

提取头文件

class-dump安装比较简单我就不说了,是用来提取match-o格式文件的工具,可以把ios开发的app的头文件导出来,就能知道app里面的类和方法、变量名。以此来注入指定方法。
电脑命令:

class-dump -H test1 -o test1Headers

test1是我把decrypted-app.ipa解压后里面的match-o文件,test1Headers是指输出所有头文件到这个文件夹。

好了,我打开其中一个头文件
头文件
因为这个app是我自己写的,我知道down是其中一个Button按钮是点击后输出一个弹窗的功能,我就拿这个方法来演示注入把,就是实现点击Button按钮后再注入一个弹窗。当然其他app就要靠经验分析啦,可以用hooper等反编译工具分析。

编写注入代码

打开xcode,新建工程选择IOS中的Framework & Library 中的Framework。
Product Name 等信息你们自己填,我的命名是snakeGameHacker
创建好工程目录后,snakeGameHacker.h就是头文件,我的这样写:


#import <UIKit/UIKit.h>

//! Project version number for snakeGameHacker.
FOUNDATION_EXPORT double snakeGameHackerVersionNumber;

//! Project version string for snakeGameHacker.
FOUNDATION_EXPORT const unsigned char snakeGameHackerVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import 
#import <HackerLoader.h>
#import <NSObject+Hacker.h>

然后再创建Cocoa Touch class 文件,HackerLoader,会自动生成HackerLoader.h和HackerLoader.m文件。
HackerLoader.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface HackerLoader : NSObject

@end

HackerLoader.m


#import "HackerLoader.h"
#import "NSObject+Hacker.h"
#import <objc/runtime.h>
@implementation HackerLoader
static void __attribute__((constructor)) entry(void) {
    NSLog(@">>>>> Code Injected 哈哈哈3哈<<<<<");  
    NSObject *obj = [[NSObject alloc] init];
    [obj hack];
}

@end

再创建objective-c File文件,类型选择Category,名字Hacker
最终产生:NSObject+Hacker.h和NSObject+Hacker.m
NSObject+Hacker.h


#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface NSObject (Hacker)

- (void)hack;

@end

NSObject+Hacker.m
这就是我们的核心文件了

#import "NSObject+Hacker.h"
#import <objc/runtime.h>
@implementation NSObject (Hacker)

- (void)hack {
    NSLog(@">>>>> Code Injected powerby maimai <<<<<");
    NSString *className = @"ViewController";
    [self hookMethod:@"down" ofClass:className hookMethodName:@"down2"];
}

// 封装方法挂载函数
- (void)hookMethod:(NSString *)oriMethodName ofClass:(NSString *)ClassName hookMethodName:(NSString *)hookMethodName   {
    
    NSLog(@"挂载方法。。。。");
    
           static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           Class oriMethodClass = NSClassFromString(ClassName);

           Class class = [self class];

          
           SEL originalSelector = NSSelectorFromString([oriMethodName stringByAppendingString:@":"]);
           SEL swizzledSelector = NSSelectorFromString([hookMethodName stringByAppendingString:@":"]);
    
           Method originalMethod = class_getInstanceMethod(oriMethodClass, originalSelector);
           Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            BOOL didAddMethod = class_addMethod(oriMethodClass,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
    
           if (didAddMethod) {// 判断是否已经有这个方法了
    
               class_replaceMethod(oriMethodClass,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
    
           } else {
    
               method_exchangeImplementations(originalMethod, swizzledMethod);
    
          }
       });
}



// 按钮按下
- (void)down2:(id)sender{
    NSLog(@"----down2----weirui3----");
    [self showError:@"我在app的方法里注入自己的代码啦!"];
    return [self down2:sender];
}


- (UIViewController *)_topViewController:(UIViewController *)vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [self _topViewController:[(UINavigationController *)vc topViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [self _topViewController:[(UITabBarController *)vc selectedViewController]];
    } else {
        return vc;
    }
    return nil;
}


- (UIViewController *)topViewController {
    UIViewController *resultVC;
    resultVC = [self _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
    while (resultVC.presentedViewController) {
        resultVC = [self _topViewController:resultVC.presentedViewController];
    }
    return resultVC;
}



- (void)showError:(NSString *)errorMsg {
    UIViewController *uvc = [self topViewController];
    NSLog(@"----weirui3----当前vc%@", NSStringFromClass([uvc class]));
    // 1.弹框提醒
    // 初始化对话框
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:errorMsg preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
    // 弹出对话框
    [uvc presentViewController:alert animated:true completion:nil];
    NSLog(@"uvc show");
}

@end

代码讲解

static void attribute((constructor)) entry(void) {}
这个方法会被进程注入的时候执行到这里。我们实例化一个NSObject执行NSObject+Hacker.m.hack()方法,hack方法里面有一段

NSString *className = @"ViewController";
    [self hookMethod:@"down" ofClass:className hookMethodName:@"down2"];
    

hookMethod是我封装的函数调用hook方法,是利用OC语言中swizzledMethod实现方法替换和注入,theos 中tweak的%orig原理就是用了swizzledMethod实现hook。后面我会分享tweak注入的教程,我觉得比bfinject稳定靠谱。
意思是ViewController类下面的down()方法挂载一个我们指定的方法down2()
接下来我们编写down2方法。

// 按钮按下
- (void)down2:(id)sender{
    NSLog(@"----down2----weirui3----");
    [self showError:@"我在app的方法里注入自己的代码啦!"];
    return [self down2:sender];
}

拦截了down的执行,插入了一段代码 [self showError:@“我在app的方法里注入自己的代码啦!”];
就是弹窗代码,在本页面弹窗的实现,具体你们自己看弹窗实现代码吧,这个网上很多类似代码。然后再 return [self down2:sender];
就是真正的%orig那段代码了,看似是递归调用,实际不会。

编译

我们先测试一下报错没有吧。如果要用单例测试要让编译目标 改为 iOS Simulators,我选ios8,然后按command+U,运行,证实不报错跑通到
hack()方法里面:
单例测试
当然down2方法是不会被执行的,因为down2是动态执行,需要利用bfinject注入后启动对应app触发down那个方法才会被执行。
我们编译代码。
注意,编译目标 切换为Generic iOS Device。
注意,编译目标 切换为Generic iOS Device
注意,编译目标 切换为Generic iOS Device
注意,编译目标 切换为Generic iOS Device
其他证书相关配置改为None,目标版本改为你手机能执行的版本,等一些手续。
然后按下Command+B编译。
Products/目录下产生snakeGameHacker.framework目录,用finder打开进入找到里面的snakeGameHacker的 Unix可执行文件,就是我们注入的对象了。

注入

把snakeGameHacker上传到手机目录。
手机里执行

bash bfinject -P test1.app -l snakeGameHacker

注意这里的 -l 和上面不同,这里的是小写的。
执行后回到app按一下Button按钮(down方法的执行触发)
结果注入成功:
动态注入后的效果
至此,framework注入讲解完毕。

cycript的使用

手机执行:

bash bfinject -P test1.app -L cycript

cycript的使用
然后电脑打开终端,输入对应地址:

cycript -r 192.168.0.101:1337

不过我经常连接好久或失败,感觉不好用。多试几次才能成功。
本来想写几个命令的,现在连接不上,算了~

结束

此教程仅做学习交流和知识记录方便以后查看使用,如果涉及到利益相关的请告知本人进行删帖处理。

本人想通过这些博客记录自己这两周内折腾的过程以及分享最后成功的成果。另外想结交志同道合对IT行业感兴趣的盆友,互相交流学习。可以通过博客联系我或加QQ号:1321691245

今天就先写到这,后面我再把我学习的其他相关知识分享给大家。下一篇就介绍theos 中编写tweak实现修改游戏数据的文章吧。

博文主索引目录入口

我会把这系列的文章更新到这个入口里面,分享我的心得,大家互相学习。
2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程主目录入口

6 个赞

加油哦。。

问个疑问呢?注入重新打包成ipa,不涉及到证书吗?

重打包用的不是你自己的证书么,要不叫重打包呢

看不到图片是什么原因啊