-
-
[原创]【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] = 3 月 4 日周二 = - [_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] = 3 月 4 日周二 = - [_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(©, 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
】