写点什么

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

作者:小陈
  • 2025-03-05
    四川
  • 本文字数:4726 字

    阅读完需:约 16 分钟

前言

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


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


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



一、目标

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


二、开发环境和工具清单

  • mac 系统

  • frida:动态调试

  • 已越狱 iOS 设备:脱壳及 frida 调试

  • IDA Pro:静态分析

三、步骤

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

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


并修改 setText_.js:


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) {  }});
复制代码


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


-[_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_20x1ad1b2adc 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_invoke0x1afd4f004 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 源码。并定位到该方法,获取到关键的伪代码如下:


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,当时间有变化时,获取到的堆栈信息如下:


-[_UIStatusBarDataStringEntry stringValue]-[_UIStatusBarDataStringEntry stringValue]-[_UIStatusBarDataStringEntry stringValue]-[_UIStatusBarDataStringEntry stringValue]-[_UIStatusBarDataStringEntry stringValue]
复制代码


修改__handlers__/_UIStatusBarDataStringEntry/stringValue.js


defineHandler({  onEnter(log, args, state) {    log(`-[_UIStatusBarDataStringEntry stringValue]`);  },
onLeave(log, retval, state) { log(`-[_UIStatusBarDataStringEntry stringValue]=${ObjC.Object(retval)}=`);
}});
复制代码


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


-[_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,当时间有变化时,获取到的堆栈信息如下:


-[_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!_UIStatusBarReceivedStatusBarDataAndActions0x1b020f130 UIKitCore!_XReceivedStatusBarDataAndActions0x1b4413ef0 AppSupport!migHelperRecievePortCallout0x1ad1c8fe8 CoreFoundation!__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__0x1ad1c8378 CoreFoundation!__CFRunLoopDoSource10x1ad1c208c CoreFoundation!__CFRunLoopRun0x1ad1c121c CoreFoundation!CFRunLoopRunSpecific0x1c4cc5784 GraphicsServices!GSEventRunModal0x1afbfffe0 UIKitCore!-[UIApplication _run]0x1afc05854 UIKitCore!UIApplicationMain0x1d2970194 SpringBoard!SBSystemAppMain0x1ace816b0 libdyld.dylib!start
复制代码


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

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

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


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 源码如下:


#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);}
复制代码


完整源码下载地址https://pan.quark.cn/s/e1152c2f7899



总结

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


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




发布于: 刚刚阅读数: 3
用户头像

小陈

关注

和昨天不一样 2019-03-12 加入

公众号:移动端Android和iOS开发技术分享

评论

发布
暂无评论
【iOS逆向与安全】在iOS状态栏中实现秒表功能的插件开发指南_ios开发_小陈_InfoQ写作社区