iOS逆向-哔哩哔哩增加3倍速播放(3)-[横屏视频-全屏播放]场景

前言

作为哔哩哔哩的重度用户,我一直期待官方支持 3 倍速播放,但该功能迟迟未上线。于是,我利用 iOS 逆向工程知识,为 B 站 App 添加这一功能。

修改前 :最高仅支持 2.0 倍速。

修改后 :成功添加 3.0 倍速选项

本系列分为多篇,本文聚焦 横屏视频全屏播放 场景下的 3 倍速实现。

系列回顾

场景

开发环境

分析

1. 定位倍速面板控件

  • 通过 Lookin 查看层级,播放速度浮层为 BBPlayerFloatingWidgetView,其包含 _rootWidget 属性(类型为 BBPlayerNavigationWidget)。
objc 复制代码
@interface BBPlayerFloatingWidgetView : UIView {
    /* instance variables */
    BBPlayerContext *_context;
    BBPlayerWidget *_rootWidget;
}

/* instance methods */
- (id)initWithContext:(id)context rootWidget:(id)widget;
- (id)hitTest:(struct CGPoint { double x0; double x1; })test withEvent:(id)event;

@end

2. 追踪倍速列表

  • 我们在Xcode添加符号断点-[BBPlayerFloatingWidgetView initWithContext:rootWidget:],看看rootWidget的值是什么?
  • -[BBPlayerFloatingWidgetView initWithContext:rootWidget:]断点触发,我们打印参数的值,知道rootWidget的类型是BBPlayerNavigationWidget
objc 复制代码
(lldb) p (id)$x3
(BBPlayerNavigationWidget *) 0x00000002827e2be0
  • Mach-O文件导出的BBPlayerNavigationWidgetOC头文件可以知道,BBPlayerNavigationWidget继承自BBPlayerObjectBBPlayerObjectsubWidgets属性
objc 复制代码
@interface BBPlayerNavigationWidget : BBPlayerFloatingWidget <CAAnimationDelegate, UIGestureRecognizerDelegate> {
    /* instance variables */
    NSMutableArray *_widgets;
    UIView *_view;
}

@property (readonly, nonatomic) BBPlayerFloatingWidget *rootWidget;
...
/* instance methods */
- (id)initWithContext:(id)context rootWidget:(id)widget;
- (id)view;
- (void)pushWidget:(id)widget animated:(_Bool)animated;
- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id /* block */)completion;
...

@end
objc 复制代码
@interface BBPlayerWidget : BBPlayerObject <BBPlayerWidgetDelegate> 

@property (nonatomic) long long lifecycleFlag;
@property (readonly, nonatomic) UIView *view;
@property (readonly, weak, nonatomic) BBPlayerWidget *superWidget;
@property (readonly, copy, nonatomic) NSArray *subWidgets;
...
- (void)willAppear:(_Bool)appear;
- (void)willLayoutSubWidgets;
- (void)didLayoutSubWidgets;
- (void)didAppear:(_Bool)appear;
- (void)willDisappear:(_Bool)disappear;
- (void)didDisappear:(_Bool)disappear;
...

@end
  • 我们在Xcode尝试添加符号断点-[BBPlayerNavigationWidget didLayoutSubWidgets],但是添加不成功
  • 我们hook BBPlayerNavigationWidgetdidLayoutSubWidgets方法,并添加断点
objc 复制代码
%hook BBPlayerNavigationWidget

- (void)didLayoutSubWidgets {
    %orig;
    NSLog(@"%@:%@-%p-%s", nj_logPrefix, NSStringFromClass([(id)self class]), self, __FUNCTION__);
}

%end
  • BBPlayerNavigationWidgetdidLayoutSubWidgets方法的断点触发,我们打印subWidgets属性,发现为空
objc 复制代码
(lldb) po [self subWidgets]
nil
  • 我们知道BBPlayerNavigationWidget有个_widgets成员变量,我们打印它的值,发现为空
objc 复制代码
@interface BBPlayerNavigationWidget : BBPlayerFloatingWidget <CAAnimationDelegate, UIGestureRecognizerDelegate> {
    /* instance variables */
    NSMutableArray *_widgets;
    UIView *_view;
}
objc 复制代码
(lldb) po ((BBPlayerNavigationWidget *)self)->_widgets
<__NSArrayM 0x280448600>(
)
  • 我们知道BBPlayerNavigationWidget有个- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法,我们添加断点,看widget的值为多少
objc 复制代码
%hook BBPlayerNavigationWidget

- (void)didLayoutSubWidgets {
    %orig;
    NSLog(@"%@:%@-%p-%s", nj_logPrefix, NSStringFromClass([(id)self class]), self, __FUNCTION__);
}

- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion {
    %orig;
}

- (void)addWidget:(id)widget animated:(_Bool)animated completion:(id)completion {
    %orig;
}

%end
  • - (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法的断点触发,发现widget的类型是BBPlayerPlaybackRateListWidget
objc 复制代码
(lldb) p widget
(BBPlayerPlaybackRateListWidget?) 0x0000000282437b60
  • 而且- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法执行完后,_widgets属性有值了,subWidgets属性也有值了,类型是BBPlayerPlaybackRateListWidget
objc 复制代码
(lldb) po ((BBPlayerNavigationWidget *)self)->_widgets
<__NSArrayM 0x2804a05d0>(
<BBPlayerPlaybackRateListWidget: 0x282437b60>

)

(lldb) po [self subWidgets]
<__NSSingleObjectArrayI 0x2817c38e0>(
<BBPlayerPlaybackRateListWidget: 0x282437b60>
)
  • Mach-O文件导出的BBPlayerCommonSwift.BBPlayerPlaybackRateListWidgetswift文件可以知道,它有一个rateArray属性,猜测它是播放速度数组,rateArray属性只读,getter方法叫做sub_10D829128
swift 复制代码
class BBPlayerCommonSwift.BBPlayerPlaybackRateListWidget: BBPlayerFloatingWidget {
  /* fields */
    var lazy tableView: UITableView?
    var lazy playbackRate: (private) ?
    var lazy rateArray: Array -> BBPlayerCommonSwift.BBPlayerPlayerRateModel ?
    var lazy playbackRateProxy: BBPlayerPlaybackRateValueService??
  /* methods */
    func tableView.getter.sub_10d828e50
    // <stripped> func tableView.setter 
    // <stripped> func tableView.modify 
    func playbackRate.getter.sub_10d829074
    // <stripped> func playbackRate.setter 
    // <stripped> func playbackRate.modify 
    func rateArray.getter.sub_10d829128
    // <stripped> func rateArray.setter 
    // <stripped> func rateArray.modify 
...
}
  • 我们添加一个符号断点sub_10D829128,看看rateArray的值是多少?
  • sub_10D829128断点触发,打印它的返回值,发现是播放速度,这也证明了rateArray属性是播放速度数组
objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStorageC19BBPlayerCommonSwift23BBPlayerPlayerRateModel_$ 0x2835d09b0>(
<BBPlayerPlayerRateModel: 0x28107fb20; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x28107fae0; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x28107dc40; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x28107fc60; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x28107e880; value = 0.750000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x28107e660; value = 0.500000; text = 0.5>
)

3. 找到数组创建函数

  • IDA查看sub_10D829128的伪代码,分析后猜测是由sub_10D82E870方法创建的播放速度数组
objc 复制代码
__int64 sub_10D829128()
{
...
  v1 = OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray;
  v2 = *(_QWORD *)(v0 + OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray);
  if ( v2 )
  {
    v3 = *(_QWORD *)(v0 + OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray);
  }
  else
  {
    v4 = sub_10D82E870();
    v3 = sub_10D82918C(v4);
    v5 = *(_QWORD *)(v0 + v1);
    *(_QWORD *)(v0 + v1) = v3;
    ((void (*)(void))swift_bridgeObjectRetain)();
    swift_bridgeObjectRelease(v5);
    v2 = 0LL;
  }
  swift_bridgeObjectRetain(v2);
  return v3;
}
  • 查看sub_10D82E870方法的伪代码,知道它返回一个包含 6 个 BBPlayerPlayerRateModel 的数组。
c 复制代码
__int64 sub_10D82E870()
{
...
  sub_10D7DBF44();
  v0 = swift_allocObject();
  *(_OWORD *)(v0 + 16) = xmmword_110613B30;
  v1 = (objc_class *)type metadata accessor for BBPlayerPlayerRateModel();
  v2 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
  v3 = &v2[OBJC_IVAR___BBPlayerPlayerRateModel_value];
  swift_beginAccess(&v2[OBJC_IVAR___BBPlayerPlayerRateModel_value], v38, 1LL, 0LL);
  *(_QWORD *)v3 = 0x3FE0000000000000LL;
  v4 = &v2[OBJC_IVAR___BBPlayerPlayerRateModel_text];
  swift_beginAccess(&v2[OBJC_IVAR___BBPlayerPlayerRateModel_text], v37, 1LL, 0LL);
  v5 = *((_QWORD *)v4 + 1);
  *(_QWORD *)v4 = 3485232LL;
  *((_QWORD *)v4 + 1) = 0xE300000000000000LL;
  swift_bridgeObjectRelease(v5);
  *(_QWORD *)(v0 + 32) = v2;
  v6 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
  v7 = &v6[OBJC_IVAR___BBPlayerPlayerRateModel_value];
  swift_beginAccess(&v6[OBJC_IVAR___BBPlayerPlayerRateModel_value], v36, 1LL, 0LL);
  *(_QWORD *)v7 = 0x3FE8000000000000LL;
  v8 = &v6[OBJC_IVAR___BBPlayerPlayerRateModel_text];
  swift_beginAccess(&v6[OBJC_IVAR___BBPlayerPlayerRateModel_text], v35, 1LL, 0LL);
  v9 = *((_QWORD *)v8 + 1);
  *(_QWORD *)v8 = 892808752LL;
  *((_QWORD *)v8 + 1) = 0xE400000000000000LL;
  swift_bridgeObjectRelease(v9);
  *(_QWORD *)(v0 + 40) = v6;
  v10 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
  v11 = &v10[OBJC_IVAR___BBPlayerPlayerRateModel_value];
  swift_beginAccess(&v10[OBJC_IVAR___BBPlayerPlayerRateModel_value], v34, 1LL, 0LL);
  *(_QWORD *)v11 = 0x3FF0000000000000LL;
  v12 = &v10[OBJC_IVAR___BBPlayerPlayerRateModel_text];
  swift_beginAccess(&v10[OBJC_IVAR___BBPlayerPlayerRateModel_text], v33, 1LL, 0LL);
  v13 = *((_QWORD *)v12 + 1);
  *(_QWORD *)v12 = 3157553LL;
  *((_QWORD *)v12 + 1) = 0xE300000000000000LL;
  swift_bridgeObjectRelease(v13);
  *(_QWORD *)(v0 + 48) = v10;
...
  return v0;
}
  • 我们添加一个符号断点sub_10D82E870,看看方法返回值多少?
  • sub_10D82E870断点触发,打印它的返回值,发现是播放速度数组[0.5、0.75、1.0、1.25、1.5、2.0],这也证明了是由sub_10D82E870方法创建的播放速度数组
objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStorageC19BBPlayerCommonSwift23BBPlayerPlayerRateModel_$ 0x2834b2da0>(
<BBPlayerPlayerRateModel: 0x281164280; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x281165760; value = 0.750000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x2811655c0; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x281164840; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x281165500; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x281164a40; value = 2.000000; text = 2.0>
)

越狱解决方案

MSHookFunction

MSHookFunction 来自 Cydia Substrate(MobileSubstrate) ,用于 直接 Hook 一个函数地址,而不是 Objective-C 方法。

函数原型

arduino 复制代码
void MSHookFunction(
    void *symbol,
    void *replace,
    void **result
);

参数说明

参数 说明
symbol 原始函数地址
replace 你的替换函数
result 保存原始函数指针(可选)

方案

  • 使用 MSHookFunction 直接 hook sub_10D82E870,修改返回数组的 valuetext 属性。

核心代码:

objc 复制代码
#import "NJChangePlaybackRateTool.h"
#import "NJCommonDefine.h"

static const double NJChangePlaybackRateFlag = 3.0;

@interface NJChangePlaybackRateTool ()

/// 播放速度
@property (nonatomic, strong) NSArray<NSString *> *playbackRates;

@end

@implementation NJChangePlaybackRateTool

#pragma mark - Life Cycle Methods

- (instancetype)init {
    self = [super init];
    if (self) {
        [self doInit];
    }
    return self;
}

#pragma mark - Do Init

- (void)doInit {
    self.playbackRates = @[@"0.5", @"1.0", @"1.25", @"1.5", @"2.0", @"3.0"];
}

#pragma mark - Override Methods

#pragma mark - Public Methods

- (NSArray *)changePlaybackRateWithRateArray:(NSArray *)rateArray {
    if (![self shouldChange:rateArray]) {
        return rateArray;
    }
//    NSLog(@"[%@] change before rateArray = %@, class:%@", nj_logPrefix, rateArray, NSStringFromClass([rateArray class]));
    NSInteger rateCount = rateArray.count;
    NSInteger newRateCount = self.playbackRates.count;
    NSInteger count = MIN(rateCount, newRateCount);
    for (NSInteger i = 0; i < count; i++) {
        BBPlayerPlayerRateModel *rateModel = rateArray[i];
        NSString *newRateStr = self.playbackRates[i];
        rateModel.value = newRateStr.doubleValue;
        rateModel.text = newRateStr;
    }
//    NSLog(@"[%@] change after rateArray = %@, class:%@", nj_logPrefix, rateArray, NSStringFromClass([rateArray class]));
    return rateArray;
}

#pragma mark - Private Methods

- (BOOL)shouldChange:(NSArray *)rateArray {
    BOOL flag = YES;
    for (BBPlayerPlayerRateModel *item in rateArray) {
        if (item.value == NJChangePlaybackRateFlag) {
            flag = NO;
            break;
        }
    }
    return flag;
}

@end

Hook 实现

swift 复制代码
// 声明原函数类型
public typealias orig_landscapeVideo_fullScreenPlayback_RateModelArr_type = @convention(c) () -> Int64

// 定义全局函数指针变量,并绑定一个 C 名字
@_silgen_name("orig_landscapeVideo_fullScreenPlayback_RateModelArr")
nonisolated(unsafe) public var orig_landscapeVideo_fullScreenPlayback_RateModelArr: orig_landscapeVideo_fullScreenPlayback_RateModelArr_type? = nil
// [横屏视频-全屏播放]播放速度数组
@_cdecl("my_landscapeVideo_fullScreenPlayback_RateModelArr")
func my_landscapeVideo_fullScreenPlayback_RateModelArr() -> Int64 {
    if let orig_landscapeVideo_fullScreenPlayback_RateModelArr {
        let origPtr = orig_landscapeVideo_fullScreenPlayback_RateModelArr()
        let origArr = unsafeBitCast(origPtr, to: [NSObject].self)
        let tool = NJChangePlaybackRateTool()
        tool.changePlaybackRate(withRateArray: origArr)
        let retPtr = unsafeBitCast(origArr, to: Int64.self)
        return retPtr
    }
    return 0
}
objc 复制代码
// __int64 sub_10D82E870()
// [横屏视频-全屏播放]播放速度数组
long long landscapeVideo_fullScreenPlayback_RateModelArr_address = g_slide+0x10D82E870;
NSLog(@"[%@] cal func landscapeVideo_fullScreenPlayback_RateModelArr_address address:0x%llx", nj_logPrefix, landscapeVideo_fullScreenPlayback_RateModelArr_address);
MSHookFunction((void *)landscapeVideo_fullScreenPlayback_RateModelArr_address,
			   (void*)my_landscapeVideo_fullScreenPlayback_RateModelArr,
			   (void**)&orig_landscapeVideo_fullScreenPlayback_RateModelArr);

非越狱解决方案

通过 IDA Pro 修改汇编指令,直接硬编码新倍速。

分析

  • 我们知道sub_10D82E870方法返回的是BBPlayerPlayerRateModel数组,而BBPlayerPlayerRateModel的结构如下,所以我们要修改BBPlayerPlayerRateModelvaluetext的值
objc 复制代码
@interface BBPlayerPlayerRateModel : NSObject // (Swift)

@property (nonatomic) double value;
@property (nonatomic, copy) NSString *text;

/* instance methods */
- (id)initWithValue:(double)value text:(id)text;
- (id)init;

@end

修改value

chatgpt帮忙分析sub_10D82E870方法的汇编指令,给我们赋值value的地址,给出如下表格:

元素 index 地址 旧值: 旧汇编指令 新值: 新汇编指令
0 0x10D82E8FC "0.5"MOV X8, #0x3FE0000000000000 不变
1 0x10D82E970 "0.75"MOV X8, #0x3FE8000000000000 "1.0"MOV X8, #0x3FF0000000000000
2 0x10D82E9E0 "1.0"MOV X8, #0x3FF0000000000000 "1.25"MOV X8, #0x3FF4000000000000
3 0x10D82EA4C "1.25"MOV X8, #0x3FF4000000000000 "1.5"MOV X8, #0x3FF8000000000000
4 0x10D82EAB8 "1.5"MOV X8, #0x3FF8000000000000 "2.0"MOV X8, #0x4000000000000000
5 0x10D82EB28 "2.0"MOV X8, #0x4000000000000000 "3.0"MOV X8, #0x4008000000000000

示例

  • 我们修改000000010D82E970的指令

    objc 复制代码
    __text:000000010D82E970                 MOV             X8, #0x3FE8000000000000
    • 鼠标点击000000010D82E970
    objc 复制代码
    __text:000000010D82E970                 MOV             X8, #0x3FE8000000000000
    • 右键选择Assemble
    • 改成
    objc 复制代码
    MOV     X8, #0x3FF0000000000000
    • 点击enter
  • 保存到Mach-O文件中

    • IDA->Edit->Patch program->Apply patches to input file->Apply patches
  • 保存后:底部会显示log

    objc 复制代码
    Patch successful: /Users/touchworld/Documents/iOSDisassembler/hook/bilibili/IDA_max_0/bili-universal
  • 验证修改结果

    • 将修改后的Mach-O文件放入.app文件中
    • Xcode添加符号断点sub_10D82E870
    • sub_10D82E870断点触发,打印它的返回值,发现元素1value已经从0.75改为了1.0,验证修改成功。
    objc 复制代码
    (lldb) po (id)$x0
    <_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x303adb570>(
    <BBPlayerPlayerRateModel: 0x301d18f60; value = 0.500000; text = 0.5>,
    <BBPlayerPlayerRateModel: 0x301d19880; value = 1.000000; text = 0.75>,
    <BBPlayerPlayerRateModel: 0x301d197e0; value = 1.000000; text = 1.0>,
    <BBPlayerPlayerRateModel: 0x301d19840; value = 1.250000; text = 1.25>,
    <BBPlayerPlayerRateModel: 0x301d19780; value = 1.500000; text = 1.5>,
    <BBPlayerPlayerRateModel: 0x301d19860; value = 2.000000; text = 2.0>
    )
  • 全部修改结果

objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3009c9360>(
<BBPlayerPlayerRateModel: 0x3028a4cc0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x3028a5660; value = 1.000000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x3028a49a0; value = 1.250000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x3028a4900; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x3028a48a0; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x3028a4400; value = 3.000000; text = 2.0>
)

修改text

chatgpt分析sub_10D82E870方法的汇编指令,给我们赋值text的地址,给出如下表格:

元素 index 地址 旧值: 旧汇编指令
0 0x10D82E928 0.5MOV W8, #0x352E30
1 0x10D82E998 0.75MOV W8, #0x35372E30
2 0x10D82EA08 1.0MOV W28, #0x302E31
3 0x10D82EA74 1.25MOV W8, #0x35322E31
4 0x10D82EAE0 1.5MOV W8, #0x352E31
5 0x10D82EB50 2.0ADD X8, X28, #1

分析

5个元素的0x10D82EB50的指令是ADD X8, X28, #1,而x28寄存器只在__text:000000010D82EA08 MOV W28, #0x302E31赋过值,而#0x302E31的值是1.0,所以ADD X8, X28, #1相当于是X8 = X28 + 1 = 1.0 + 1 = 2.0 ,所以第5个元素显示的是2.0

所以方案就是,1.0要使用MOV W28, #0x302E312.0使用ADD X8, X28, #13.02.0一致。

下面在元素1中,给出具体的调试过程,其他元素类似。


元素0

元素0不需要修改,就是0.5

元素1

  • 原本0x10D82E998MOV W8, #0x35372E30(0.75),占两个指令空间,要改成MOV W28, #0x302E31(1.0)

    • 修改前
    objc 复制代码
    __text:000000010D82E998                 MOV             W8, #0x35372E30
    • 修改后
    objc 复制代码
    __text:000000010D82E998                 MOV             W28, #0x302E31
伪指令

伪指令Pseudo-Instruction)不是 CPU 真实存在的机器指令,而是编译器/汇编器提供的"方便写法"。

最终汇编器会把伪指令转换成 一条或多条真实的机器指令。

看个例子:

  • 鼠标点击000000010D82E998
objc 复制代码
__text:000000010D82E998                 MOV             W8, #0x35372E30
  • 查看000000010D82E99816进制视图,发现指令占用了8 bytes,也就是两个指令的空间,所以MOV W8, #0x35372E30是一个伪指令
  • chatgptMOV W8, #0x35372E30可以拆分成什么指令?回答如下
objc 复制代码
MOVZ    W8, #0x2E30
MOVK    W8, #0x3537, LSL #16
获取伪指令的真实指令
  • chatgptMOV W28, #0x302E31可以拆分成:
objc 复制代码
MOVZ    W28, #0x2E31            // 写入低 16 位
MOVK    W28, #0x0030, LSL #16   // 写入高 16 位
获取汇编指令的机器码
  • main.S文件
objc 复制代码
.global _main
_main:
    MOVZ    W28, #0x2E31            // 写入低 16 位
    MOVK    W28, #0x0030, LSL #16   // 写入高 16 位
    ret
  • 编译main.S文件
objc 复制代码
clang -arch arm64 main.S -o main
  • 使用MachOView查看main文件(Mach-O文件)的机器码
  • 所以MOV W28, #0x302E31的机器码是
objc 复制代码
3C C6 85 52 1C 06 A0 72
修改指令的机器码
  • 鼠标点击000000010D82E998
  • IDA->Edit->Patch program->Change byte
  • 显示Patch Bytes弹框
  • Origin value
    • 08 C6 85 52 E8 A6 A6 72 1B 80 FC D2 C8 6E 00 A9
  • 修改 Values 为:
    • 3C C6 85 52 1C 06 A0 72 1B 80 FC D2 DC 6A 00 A9
  • 点击OK,真正修改
保存
  • 保存到Mach-O文件中

    • IDA->Edit->Patch program->Apply patches to input file->Apply patches
  • 保存后:底部会显示log
objc 复制代码
Patch successful: /Users/touchworld/Documents/iOSDisassembler/hook/bilibili/IDA_max_0/bili-universal
验证失败
  • 将修改后的Mach-O文件放入.app文件中
  • Xcode添加符号断点sub_10D82E870
  • sub_10D82E870断点触发,打印它的返回值,发现元素1text已经从"0.75"改为了"1.0",但是打印的数组显示不全,而且倍速列表显示的是1.0,不是1.0X,所以还是有问题。
objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3036c0190>(
<BBPlayerPlayerRateModel: 0x301058d80; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30105b380; value = 1.000000; text = 1.0
处理问题
  • 我们知道0x10D82EA08MOV W28, #0x302E31的值是1.0,它的下一条指令是STP X28, X26, [X22]
objc 复制代码
__text:000000010D82EA08                 MOV             W28, #0x302E31
__text:000000010D82EA10                 STP             X28, X26, [X22]
  • 而修改后的000000010D82E998MOV W28, #0x302E31,它的下一条指令是STP X8, X27, [X22]
objc 复制代码
__text:000000010D82E998                 MOV             W28, #0x302E31
__text:000000010D82E9A4                 STP             X8, X27, [X22]
  • 我们尝试将0x10D82E9A4STP X8, X27, [X22],改为STP X28, X26, [X22]
再次验证
  • sub_10D82E870断点触发,打印它的返回值,发现元素1text已经从"0.75"改为了"1.0",而且数组全部打印了,倍速列表显示的是1.0X,修改成功。
objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x301b6b8e0>(
<BBPlayerPlayerRateModel: 0x303bbd1c0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x303bbd200; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x303bbcf80; value = 1.250000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x303bbcf40; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x303bbcb20; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x303bbc860; value = 3.000000; text = 2.0>
)

元素2

  • 原本0x10D82EA08MOV W28, #0x302E31(1.0),占两个指令空间,要改成MOV W8, #0x35322E31(1.25)

    • 修改前
    objc 复制代码
    __text:000000010D82EA08                 MOV             W28, #0x302E31
    • 修改后
    objc 复制代码
    __text:000000010D82EA08                 MOV             W8, #0x35322E31
  • MOV W8, #0x35322E31拆分成:

objc 复制代码
MOVZ    W8, #0x2E31              // 写低 16 bit
MOVK    W8, #0x3532, LSL #16     // 写高 16 bit
  • MOV W8, #0x35322E31的机器码
objc 复制代码
28 C6 85 52 48 A6 A6 72
  • 还需要将0x10D82EA10STP X28, X26, [X22],占一个指令空间,改成STP X8, X27, [X22]

    • 修改前
    objc 复制代码
    __text:000000010D82EA10                 STP             X28, X26, [X22]
    • 修改后
    objc 复制代码
    __text:000000010D82EA10                 STP             X8, X27, [X22]
  • 验证

objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3022f5f90>(
<BBPlayerPlayerRateModel: 0x30057cca0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30057f360; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x30057ccc0; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30057f3c0; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30057c960; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x30057ca00; value = 3.000000; text = 2.0>
)

元素3

  • 原本0x10D82EA74MOV W8, #0x35322E31(1.25),占两个指令空间,要改成MOV W8, #0x352E31(1.5)

    • 修改前
    objc 复制代码
    __text:000000010D82EA74                 MOV             W8, #0x35322E31
    • 修改后
    objc 复制代码
    __text:000000010D82EA74                 MOV             W8, #0x352E31
  • MOV W8, #0x352E31拆分成

objc 复制代码
MOVZ    W8, #0x2E31              // 写低16位
MOVK    W8, #0x0035, LSL #16     // 写高16位
  • MOV W8, #0x352E31的机器码
objc 复制代码
28 C6 85 52 A8 06 A0 72
  • 还需要将0x10D82EA7CSTP X8, X27, [X22],占一个指令空间,改成STP X8, X26, [X22]

    • 修改前
    objc 复制代码
    __text:000000010D82EA7C                 STP             X8, X27, [X22]
    • 修改后
    objc 复制代码
    __text:000000010D82EA7C                 STP             X8, X26, [X22]
  • 验证

objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x300d97610>(
<BBPlayerPlayerRateModel: 0x302ac6ac0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x302af3760; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x302af3420; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x302af3b40; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x302af2de0; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x302af3d00; value = 3.000000; text = 2.0>
)

元素4

  • 原本0x10D82EAE0MOV W8, #0x352E31(1.5)占三个指令空间) ,要改成MOV W8, #0x302E32(2.0)(占两个指令空间) + 一个NOP(占一个指令空间)

    • 修改前
    objc 复制代码
    __text:000000010D82EAE0                 MOV             W8, #0x352E31
    • 修改后
    objc 复制代码
    __text:000000010D82EAE0                 MOV             W8, #0x302E32
    __text:000000010D82EAE8                 NOP
  • MOV W8, #0x302E32拆分成

objc 复制代码
MOVZ    W8, #0x2E32            // 写低 16 位
MOVK    W8, #0x0030, LSL #16   // 写高 16 位
  • MOV W8, #0x302E32的机器码
objc 复制代码
48 C6 85 52 08 06 A0 72
  • NOP的机器码
objc 复制代码
1F 20 03 D5
  • 总的机器码
objc 复制代码
48 C6 85 52 08 06 A0 72 1F 20 03 D5
  • 验证
objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x303fe4f00>(
<BBPlayerPlayerRateModel: 0x3018ffe60; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x3018ffe80; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x3018ffa20; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x3018ffb20; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x3018ffb00; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x3018ff860; value = 3.000000; text = 2.0>
)

元素5

  • 原本0x10D82EB50ADD X8, X28, #1(2.0),占一个指令空间,要改成ADD X8, X28, #2(3.0)

    • 修改前
    objc 复制代码
    __text:000000010D82EB50                 ADD             X8, X28, #1
    • 修改后
    objc 复制代码
    __text:000000010D82EB50                 ADD             X8, X28, #2
  • 验证

objc 复制代码
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x301eb6f80>(
<BBPlayerPlayerRateModel: 0x30393ac20; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30393ac40; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x30393ac60; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30393ac80; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x30393aca0; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x30393acc0; value = 3.000000; text = 3.0>
)

效果

横屏全屏模式下,倍速菜单成功显示并支持 3.0 倍速播放。

总结

  • 横屏全屏场景下,播放倍速列表由 BBPlayerPlaybackRateListWidgetrateArray 生成,最终创建函数为 sub_10D82E870
  • 通过 越狱 Hook非越狱 Mach-O Patch ,即可稳定新增 3.0x 倍速。

代码

BiliBiliMApp-无广告版哔哩哔哩

相关推荐
2501_915918417 小时前
iOS 性能监控 运行时指标与系统行为的多工具协同方案
android·macos·ios·小程序·uni-app·cocoa·iphone
00后程序员张7 小时前
IPA 混淆技术全解,从成品包结构出发的 iOS 应用安全实践与工具组合
android·安全·ios·小程序·uni-app·cocoa·iphone
BigPomme8 小时前
Flutter IOS 出现实机运行问题和transpoter传包异常
flutter·ios
2501_916008898 小时前
IOScer 证书到底是什么和怎么使用的完整说明
android·ios·小程序·https·uni-app·iphone·webview
ShawnRacine8 小时前
iOS开发-安装cocoapods
ios·xcode·cocoapods
00后程序员张8 小时前
iOS 抓包工具实战指南,从代理到数据流,全流程工具分工解析
android·ios·小程序·https·uni-app·iphone·webview
FreeBuf_8 小时前
苹果0Day漏洞遭利用,针对特定iPhone用户发起复杂攻击
ios·iphone
东坡肘子8 小时前
周日小插曲 -- 肘子的 Swift 周报 #115
人工智能·swiftui·swift