首页
社区
课程
招聘
[原创]【iOS逆向与安全】在iOS状态栏中实现秒表功能的插件开发指南
发表于: 2025-3-5 08:57 1385

[原创]【iOS逆向与安全】在iOS状态栏中实现秒表功能的插件开发指南

2025-3-5 08:57
1385

前言

在需要精确掌握时间的场景中,例如抢购活动或需要在整点进行操作的情况下,用户往往需要准确了解当前的秒数。然而,iOS系统默认的状态栏时间显示并不包含秒数,这给用户带来了不便。

为了解决这一问题,开发一个在状态栏显示秒数的插件,可以帮助用户实时掌握精确时间,提升在特定场景下的操作效率。

本篇文章将探讨如何在iOS系统中开发这样一个插件,旨在为用户提供更精确的时间显示。


一、目标

见下图的状态栏时间位置:

二、开发环境和工具清单

  • mac系统
  • frida:动态调试
  • 已越狱iOS设备:脱壳及frida调试
  • IDA Pro:静态分析

三、步骤

1、查找设置状态栏日期的调用栈

在终端执行命令frida-trace -U -m "*[UILabel setText:]" SpringBoard

并修改setText_.js:

1
2
3
4
5
6
7
8
9
10
defineHandler({
  onEnter(log, args, state) {
    log(`-[UILabel setText:${ObjC.Object(args[2])}]`);
    log('UILabel setText called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
});

当时间有变化时,获取到的堆栈信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-[_UIStatusBarStringView setText:0x282967570]
-[UILabel setText:下午9:56]
UILabel setText called from:
0x1afd16180 UIKitCore!-[_UIStatusBarStringView setText:]
0x1afcf71fc UIKitCore!-[_UIStatusBarTimeItem applyUpdate:toDisplayItem:]
0x1afcfeca4 UIKitCore!-[_UIStatusBarItem _applyUpdate:toDisplayItem:]
0x1afd5381c UIKitCore!-[_UIStatusBarDisplayItemState updateWithData:styleAttributes:]
0x1afd3be04 UIKitCore!__78-[_UIStatusBar _updateDisplayedItemsWithData:styleAttributes:extraAnimations:]_block_invoke_2
0x1ad1b2adc CoreFoundation!__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__
0x1ad129280 CoreFoundation!-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]
0x1afd3bb60 UIKitCore!-[_UIStatusBar _updateDisplayedItemsWithData:styleAttributes:extraAnimations:]
0x1afd3b7d0 UIKitCore!-[_UIStatusBar _updateWithAggregatedData:]
0x1afd39af4 UIKitCore!__30-[_UIStatusBar initWithStyle:]_block_invoke
0x1afd4f004 UIKitCore!-[_UIStatusBarDataAggregator _updateForCoalescedKeysWithData:]
0x1afd4ea98 UIKitCore!-[_UIStatusBarDataAggregator _updateForDelayedKeysWithData:]
0x1afd4e9d8 UIKitCore!-[_UIStatusBarDataAggregator _updateForOverlayWithData:]
0x1afd4e3b0 UIKitCore!-[_UIStatusBarDataAggregator updateWithData:]
0x1afd3b4f4 UIKitCore!-[_UIStatusBar _updateWithData:completionHandler:]
0x1afd7d28c UIKitCore!-[UIStatusBar_Modern _updateWithData:force:]

获取到关键方法:[_UIStatusBarTimeItem applyUpdate:toDisplayItem:]

2、查看[_UIStatusBarTimeItem applyUpdate:toDisplayItem:]的源码

使用ida pro9反编译dyld_shared_cache源码。并定位到该方法,获取到关键的伪代码如下:

1
2
3
4
5
6
v38 = objc_retainAutoreleasedReturnValue(objc_msgSend(v6, "data"));
v39 = objc_retainAutoreleasedReturnValue(objc_msgSend(v38, "shortTimeEntry"));
v40 = objc_retainAutoreleasedReturnValue(objc_msgSend(v39, "stringValue"));
v41 = -[_UIStatusBarTimeItem pillTimeView](self, "pillTimeView");
v42 = objc_retainAutoreleasedReturnValue(v41);
objc_msgSend(v42, "setText:", v40);

获取到关键方法stringValue

3、trace stringValue方法

在终端执行命令frida-trace -U -m "*[* stringValue]" SpringBoard,当时间有变化时,获取到的堆栈信息如下:

1
2
3
4
5
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]

修改__handlers__/_UIStatusBarDataStringEntry/stringValue.js

1
2
3
4
5
6
7
8
9
10
defineHandler({
  onEnter(log, args, state) {
    log(`-[_UIStatusBarDataStringEntry stringValue]`);
  },
 
  onLeave(log, retval, state) {
        log(`-[_UIStatusBarDataStringEntry stringValue]=${ObjC.Object(retval)}=`);
 
  }
});

然后再次运行frida-trace -U -m "*[* stringValue]" SpringBoard,当时间有变化时,获取到的堆栈信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=nil=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=34日周二=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=下午10:09=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=10:09=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=下午10:10=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=nil=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=34日周二=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=下午10:10=
-[_UIStatusBarDataStringEntry stringValue]
-[_UIStatusBarDataStringEntry stringValue]=10:10=

确定_UIStatusBarDataStringEntry就是状态栏时间的来源后,继续执行frida-trace -U -m "*[_UIStatusBarDataStringEntry *]" SpringBoard,当时间有变化时,获取到的堆栈信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-[_UIStatusBarDataStringEntry initFromData:0x1087fc000 type:0x0 string:0x1087fc02b maxLength:0x40]
_UIStatusBarDataStringEntry initFromData called from:
0x1afd4fe8c UIKitCore!+[_UIStatusBarDataConverter convertData:fromReferenceData:]
0x1afd7c788 UIKitCore!-[UIStatusBar_Modern _dataFromLegacyData:]
0x1afd7e214 UIKitCore!-[UIStatusBar_Modern statusBarServer:didReceiveStatusBarData:withActions:]
0x1afd5ec1c UIKitCore!-[UIStatusBarServer _receivedStatusBarData:actions:animated:]
0x1afd5ee28 UIKitCore!_UIStatusBarReceivedStatusBarDataAndActions
0x1b020f130 UIKitCore!_XReceivedStatusBarDataAndActions
0x1b4413ef0 AppSupport!migHelperRecievePortCallout
0x1ad1c8fe8 CoreFoundation!__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
0x1ad1c8378 CoreFoundation!__CFRunLoopDoSource1
0x1ad1c208c CoreFoundation!__CFRunLoopRun
0x1ad1c121c CoreFoundation!CFRunLoopRunSpecific
0x1c4cc5784 GraphicsServices!GSEventRunModal
0x1afbfffe0 UIKitCore!-[UIApplication _run]
0x1afc05854 UIKitCore!UIApplicationMain
0x1d2970194 SpringBoard!SBSystemAppMain
0x1ace816b0 libdyld.dylib!start

通过堆栈信息分析,确认-[UIStatusBarServer _receivedStatusBarData:actions:animated:]为我们的目标对象

4、使用frida验证使用效果:

执行命令frida-trace -U -m "-[UIStatusBarServer _receivedStatusBarData:actions:animated:]" SpringBoard后,修改该js文件内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
defineHandler({
  onEnter(log, args, state) {
    log(`-[UIStatusBarServer _receivedStatusBarData:${args[2]} actions:${args[3]} animated:${args[4]}]`);
    var structPtr = args[2];
 
    log("Original timeString:", structPtr.add(43).readCString(64));
 
    const now = new Date();
     
    const hours = String(now.getHours()).padStart(2, '0'); // 补零
    const minutes = String(now.getMinutes()).padStart(2, '0'); // 补零
    const seconds = String(now.getSeconds()).padStart(2, '0'); // 补零
     
    var newTimeString =  `${hours}:${minutes}:${seconds}`;
 
    structPtr.add(43).writeUtf8String(newTimeString);
 
    log("Updated timeString:", structPtr.add(43).readCString(64));
  },
 
  onLeave(log, retval, state) {
  }
});

当状态栏日期变化后。日期正常显示秒表,说明该函数可用。

5、编写tweak代码

Tweak.x源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#import <Foundation/NSUserDefaults+Private.h>
#import "StatusBarUpdater.h"
#import "Structs.h"
#import "headers.h"
#include <string.h>
#include <sys/time.h>
static NSString * nsDomainString = @"com.witchan.precise-time";
static NSString * nsNotificationString = @"com.witchan.precise-time/preferences.changed";
static BOOL enabled;
 
static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
    NSNumber * enabledValue = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"enabled" inDomain:nsDomainString];
    enabled = (enabledValue)? [enabledValue boolValue] : YES;
    NSLog(@"=witchan= 插件状态:%d", enabled);
}
 
%hook UIStatusBarServer
%property (nonatomic, strong) StatusBarUpdater *updater;
 
// SCD_Struct_UI104结构请从ida pro复制
-(void)_receivedStatusBarData:(SCD_Struct_UI104*)arg1 actions:(int)arg2 animated:(BOOL)arg3 {
   if (self.updater == nil) {
      self.updater = [[StatusBarUpdater alloc] init];
   }
 
   if (!enabled) {
      [self.updater stopUpdating];
      %orig;
      return;
   }
    // 获取当前时间
   NSDate *currentDate = [NSDate date];
    
   // 创建日期格式化器
   NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    
   // 设置日期格式
   [dateFormatter setDateFormat:@"HH:mm:ss"]; // 24小时制
    
   // 将当前日期格式化为字符串
   NSString *formattedTime = [dateFormatter stringFromDate:currentDate];
   NSLog(@"=witchan= now timeString: %@", formattedTime);
    
   // _statusBarData.var1 =     strcpy(ret->personName, "12:33:22");
 
   strcpy(arg1->var1, [formattedTime UTF8String]);
 
   %orig(arg1, arg2, arg3);
   NSLog(@"=witchan= _receivedStatusBarData: %d %d", arg2, arg3);
 
   // 创建一个新结构体用于复制
   //  SCD_Struct_UI104 copy;
   //  memcpy(&copy, arg1, sizeof(SCD_Struct_UI104));  // 正确复制结构体数据
 
   [self.updater updateStatusBarData:*arg1 statusBarServer: self];
   [self.updater startUpdating];
}
 
%end
 
%ctor {
 
    NSLog(@"=witchan= precise time plugin load success");
    notificationCallback(NULL, NULL, NULL, NULL, NULL);
 
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, notificationCallback, (CFStringRef)nsNotificationString, NULL, CFNotificationSuspensionBehaviorCoalesce);
}

完整源码下载地址226K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2I4N6h3q4J5K9#2)9J5k6h3y4F1i4K6u0r3M7#2)9J5c8X3f1I4x3e0f1J5j5K6u0X3y4K6R3&6z5b7`.`.


总结

通过上述方法,用户可以在状态栏或屏幕上方实时查看秒数,满足在特定场景下对精确时间的需求,提升操作效率和成功率。

提示:阅读此文档的过程中遇到任何问题,请关住工众好【移动端Android和iOS开发技术分享】或+99 君羊【812546729

image-20230225231548837


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回