[原]逆向iOS SDK -- “添加本地通知”的流程分析
观点:
代码面前没有秘密
添加通知的 Demo 代码
- (void)scheduleOneLocalNotification {
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = @"Proteas";
localNotification.];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
[localNotification release]; localNotification = nil;
}
问题
参考如上的 Demo,相应的系统API为:-[UIApplication scheduleLocalNotification:]
可以看到添加本地通知相对简单,但是我们要多问几个为什么:
1、接口背后发生了什么?
2、本地通知有64个限制,如何控制的?
3、... ...
Reverse UIKit
UIApplication 是UIKit中的类,所以我们首先逆向 UIKit。
UIKit 的路径为:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/UIKit.framework
用 Hopper Disassembler(or IDA) 打开这个库,Hopper 会开始反汇编、分析这个 MachO,待分析完毕后,可以看到如下界面:

scheduleLocalNotification的实现
这时我们查找 scheduleLocalNotification 的实现,在 Labels 下面的搜索框中输入 scheduleLocalNotification,Hopper 会查找出相关的实现,如下图:

在搜索出来的结果中点击第一个条目,右边窗口中会定位到实现的起始部分,如下图:

可以看到 scheduleLocalNotification: 的实现比较简单,只有 9 行汇编代码。
我们来分析下这几行代码:
|
1 movw r1, #0xf8c4 2 movt r1, #0x40 ;r1 = 0x40F8C4 3 movw r0, #0xc7be 4 movt r0, #0x41 ;r0 = 0x41C7BE 5 add r1, pc ;r1 = 0x5e5ec8 6 add r0, pc ;r0 = 0x5f2dc4 7 ldr r1, [r1] ;@selector(scheduleLocalNotification:),a = *a 8 ldr r0, [r0] ;@bind__OBJC_CLASS_$_SBSLocalNotificationClient 9 b.w _objc_msgSend$shim |
1、Hopper 使用的 Intel 系列的汇编语法:“目的”在左,“源”在右。
2、ARM没有直接加载32位立即数的指令,而是使用movw与movt。
3、Call Frame
表:ARM ABI Register Usage
|
Register |
Brief |
Preserved |
Rules |
|
r0 |
Argument and result |
No |
r0 and r1 are used for passing the first two arguments to functions, and returning the results of functions. If a function does not use them for a return value, they can take any value after a function. |
|
r1 |
Argument and result |
No |
|
|
r2 |
Argument |
No |
r2 and r3 are used for passing the second two arguments to functions. There values after a function is called can be anything. |
|
r3 |
Argument |
No |
|
|
lr |
Return address |
No |
lr is the address to branch back to when a function is finished, but this does have to contain the same address after the function has finished. |
|
sp |
Stack pointer |
Yes |
sp is the stack pointer, described below. Its value must be the same after the function has finished. |
上面几行代码的功能是:call + [SBSLocalNotificationClient scheduleLocalNotification:]。
当读到这段代码的时候,有个疑问:0x40F8C4 是如何得到的?
在说明这个问题之前需要先说下 MachO 文件的格式,如下图:

Objective-C 被编译、链接后,代码、类、方法、类与方法间关系被放到不同 Section 中,也就是说:
上面的代码与scheduleLocalNotification: (selector)在不同的 Section 中,这样就可以计算地址之间的差值了:
0x40F8C4 = (CodeSectionStartAddress + Offset)
- (SelfRefSectionStartAddress + Offset)
因为 ASLR(Address Space Layout Randomization) 的存在,Section 的开始地址并不是固定的,也就是说差值并不总是0x40F8C4。
Reverse SpringBoardServices
scheduleLocalNotification的实现
搜索 SBSLocalNotificationClient,可以发现它是 SpringBoardServices中的类,
现在我们用同样的方法逆向 SpringBoardServices 库,如下图:

代码如下:
|
push {r4, r7, lr} add r7, sp, #0x4 sub sp, #0x8 movw r1, #0x875c mov r4, r0 movt r1, #0x0 movw r0, #0x8906 movt r0, #0x0 add r1, pc ; 0x13194 add r0, pc ; 0x13340 ldr r1, [r1] ; @selector(arrayWithObject:) ldr r0, [r0] ; @bind__OBJC_CLASS_$_NSArray blx imp___picsymbolstub4__objc_msgSend mov r2, r0 movw r0, #0x8748 movt r0, #0x0 movs r3, #0x0 add r0, pc ; 0x13198 ldr r1, [r0];@selector(_scheduleLocalNotifications:cancel:replace:optionalBundleIdentifier:) movs r0, #0x0 str r0, [sp] str r0, [sp, #0x4] mov r0, r4 blx imp___picsymbolstub4__objc_msgSend add sp, #0x8 pop {r4, r7, pc} |
上面是使用 Hopper 得到的汇编,
但是使用 GDB 调试程序,并进行反汇编后,发现两者不一致,
GDB 给出相对简单,此处以 GDB 为准。

|
状态说明: r0 = SBSLocalNotificationClient r1 = “scheduleLocalNotification:” r2 = UILocalNotification Instance
|
|
代码: mov r4, r0 ; r4 = SBSLocalNotificationClient movw r0, #34450 ; movt r0, #3577 位立即数 movw r1, #34344 ; movt r1, #3577 ; 同上 movw r3, #34612 ; movt r3, #3577 ; 同上 add r1, pc ; 惯用法 add r0, pc ; 同上 add r3, pc ; 同上 ldr r1, [r1, #0] ; r1 = "arrayWithObject:" ldr r5, [r0, #0] ; r5 ="_scheduleLocalNotifications:cancel:replace:optionalBundleIdentifier:" ldr r0, [r3, #0] ; r0 = NSArray blx 0x3058faa4 ; [NSArray arrayWithObject:UILocalNotification] movs r3, #0 ; r3 = 0, movs 影响标志位的 zero 位, 0--->1 mov r1, r5 ; r1 ="_scheduleLocalNotifications:cancel:replace:optionalBundleIdentifier:" mov r2, r0 ; r2 = UILocalNotifications mov r0, r4 ; r4 = SBSLocalNotificationClient str r3, [sp, #0] ; 将 sp 指向的内存空间清零 str r3, [sp, #4] ; 将 sp + 4将 sp 指向的内存空间清零 blx 0x3058faa4 ; 调用 ;_scheduleLocalNotifications:UILocalNotifications ; cancel:NO ; replace:NO ; optionalBundleIdentifier:nil ; 平衡运行栈的代码参考 Hopper 给出的反汇编 |
结论:
+[SBSLocalNotificationClient scheduleLocalNotification:]调用:
+[SBSLocalNotificationClient
_scheduleLocalNotifications:本地通知实例数组
cancel:NO
replace:NO
optionalBundleIdentifier:nil]
_scheduleLocalNotifications......的实现
Hopper 反汇编:

GDB反汇编代码:
|
push {r4, r7, lr} ; add r7, sp, #4 ; sub sp, #12 ; movw r1, #34510 ; 0x86ce ; ldr.w r12, [r7, #8] ; movt r1, #3577 ; 0xdf9 ldr.w lr, [r7, #12] ; add r1, pc ; movs r4, #0 ; ldr r1, [r1, #0] ; stmia.w sp, {r12, lr} ; str r4, [sp, #8] ; blx 0x3058faa4 <dyld_stub_objc_msgSend> add sp, #12 ; pop {r4, r7, pc} ; nop |
这里主要是个参数值问题,不进行逐行分析了,
可以用调试得到结论,
在进行实际的调用前(红色代码),设置断点,打印出参数值:

得到相关参数:
r0 = SBSLocalNotificationClient
r1 = "_scheduleLocalNotifications:cancel:replace:optionalBundleIdentifier:waitUntilDone:"
r2 = 通知数组
r3 = 0, cancel
*(sp) = 0, waitUntilDone
*(sp + 4) = 0, optionalBundleIdentifier
*(sp +8) = 0, replace
结论:
+[SBSLocalNotificationClient
_scheduleLocalNotifications:本地通知实例数组
cancel:NO
replace:NO
optionalBundleIdentifier:nil]
调用:
+[SBSLocalNotificationClient
_scheduleLocalNotifications:本地通知实例数组
cancel:NO
replace:NO
optionalBundleIdentifier:nil
waitUntilDone:NO]
_scheduleLocalNotifications......waitUntilDone的实现
汇编代码,如下:
|
; Begin PUSH {R4-R7,LR} ADD R7, SP, #0xC ; R7此时指向栈参数的开始地址,参数入栈顺序:从右到左 PUSH.W {R8,R10,R11} ; 保存寄存器值,后续会进行修改 SUB SP, SP, #8 ; 开辟 sizeof(int) * 2的栈空间 MOVW R1, #(:lower16:(selRef_archivedDataWithRootObject_ - 0xA930)) MOV R8, R3 ; R8 = shouldCancle MOVT.W R1, #(:upper16:(selRef_archivedDataWithRootObject_ - 0xA930)) MOV R0, #(classRef_NSKeyedArchiver - 0xA932) ;classRef_NSKeyedArchiver ADD R1, PC ; selRef_archivedDataWithRootObject_ ADD R0, PC ; classRef_NSKeyedArchiver LDR R1, [R1] ; "archivedDataWithRootObject:" LDR R0, [R0] ; _OBJC_CLASS_$_NSKeyedArchiver BLX _objc_msgSend ; R2 = Notifications MOV R4, R0 ; R0 = NSData* LDR.W R11, [R7,#shouldReplace] ; R11 = shouldReplace LDR.W R10, [R7,#bundleIdentifier] ; R10 = bundleIdentifier CMP R4, #0 ; check if NSData* is nil BNE loc_A94C ; if NSData* != nil MOVS R5, #0 ; if (NSData* == nil) R5 = 0 MOV R6, R5 ; R6 = 0 B loc_A974 ; ----------------------------------------- loc_A94C: MOV R0, #(selRef_bytes - 0xA958) ; selRef_bytes ; R0 = NSData* ADD R0, PC ; selRef_bytes LDR R1, [R0] ; "bytes" MOV R0, R4 ; R0 = NSData* BLX _objc_msgSend ; [NSData* bytes] MOV R5, R0 ; R5 = void* of NSData MOV R0, #(selRef_length - 0xA96C) ; selRef_length ADD R0, PC ; selRef_length LDR R1, [R0] ; "length" MOV R0, R4 BLX _objc_msgSend ; [NSData* length] MOV R6, R0 ; R6 = length of NSData ; ----------------------------------------- loc_A974: BL _SBSSpringBoardServerPort MOV R4, R0 ; R4 = R0 = port number of server MOV R0, #(unk_F2DF - 0xA98A) TST.W R11, #0xFF ADD R0, PC BEQ loc_A9C0 ; check if bundleIdentifier is nil CMP.W R10, #0 BEQ loc_A9A2 ; R1 = should wait until done MOV R0, #(selRef_UTF8String - 0xA99C) ; selRef_UTF8String ADD R0, PC ; selRef_UTF8String LDR R1, [R0] ; "UTF8String" MOV R0, R10 BLX _objc_msgSend ; ----------------------------------------- loc_A9A2: LDR R1, [R7,#shouldWait] ; R1 = should wait until done MOV R2, R6 UXTB R3, R1 MOV R1, R5 ; void * of NSData STR R3, [SP,#0x20+var_20] UXTB.W R3, R8 STR R0, [SP,#0x20+var_1C] MOV R0, R4 ; port number of the server BL _SBScheduleLocalNotificationsBlocking ; ----------------------------------------- loc_A9B8: ADD SP, SP, #8 POP.W {R8,R10,R11} POP {R4-R7,PC} ; ----------------------------------------- loc_A9C0: CMP.W R10, #0 ; check if bundleIdentifier is nil BEQ loc_A9D8 ; R0 points to the UTF8String of appbundleIdentifier MOV R0, #(selRef_UTF8String - 0xA9D2) ; selRef_UTF8String ADD R0, PC ; selRef_UTF8String LDR R1, [R0] ; "UTF8String" MOV R0, R10 BLX _objc_msgSend ; ----------------------------------------- loc_A9D8: LDR R1, [R7,#shouldWait] ; R0 points to the UTF8String of appbundleIdentifier MOV R2, R6 UXTB R3, R1 MOV R1, R5 ; void * of Notification NSData STR R3, [SP,#0x20+var_20] UXTB.W R3, R8 STR R0, [SP,#0x20+var_1C] MOV R0, R4 ; Port Number Of the XPC Server BL _SBScheduleLocalNotifications B loc_A9B8 ; End |
伪代码的实现,如下:
|
-(void)_scheduleLocalNotifications:(id)notifications cancel:(BOOL)cancel replace:(BOOL)replace optionalBundleIdentifier:(id)bundleID waitUntilDone:(BOOL)wait { id data = [NSKeyedArchiver archivedDataWithRootObject:notifications]; if (data != nil) { bytes = [data bytes]; length = [data length]; } else { bytes = 0; length = 0; } port = _SBSSpringBoardServerPort(); if (wait == NO) { if (bundleID) { var_1C = [bundleID UTF8String]; } var_20 = replace; r2 = length; r3 = cancel; SBScheduleLocalNotifications(/*int*/ port, /*src*/ bytes); return; } if (bundleID != nil) { var_1C = [bundleID UTF8String]; } var_20 = replace; r2 = length; r3 = cancel; SBScheduleLocalNotificationsBlocking(/*int*/ port, /*src*/ bytes); return; } |
上述代码的主要功能为:将通知进行序列化,然后调用本期通知的服务。
后续有时间再分析 SpringBoard 的通知服务部分,
需要说明的是:在对 SpringBoard 进行逆向分析前,需要去除其 ASLR。
[原]逆向iOS SDK -- “添加本地通知”的流程分析的更多相关文章
- [原]逆向iOS SDK -- _UIImageAtPath 的实现(SDK 5.1)
注释过的反汇编代码:http://pan.baidu.com/share/link?shareid=3491166579&uk=537224442 伪代码(不精确,仅供参考): NSStrin ...
- [原]逆向iOS SDK -- _UIImageAtPath 的实现(SDK 6.1)
汇编代码: ; 状态:R0 = imageFileName, R1 = mainBundle, R2 = isRetina PUSH {R4-R7,LR} ; R0 = imageFileNam ...
- [原]逆向iOS SDK -- +[UIImage imageNamed:] 的实现
汇编代码: ; Dump of assembler code for function +[UIImage imageNamed:] ; R0 = UIImage, R1 = "imageN ...
- 基本属性 - iOS中的本地通知
本地通知的基本使用 创建本地通知 设置属性 调度通知(添加通知到本地通知调度池) 注册用户通知权限(只需一次, 可以单独放在Appdelegate中, 或者别的地方) —> iOS8以后必须, ...
- iOS 注冊本地通知(推送)
注:按Home键让App进入后台执行时.方可查看通知. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithO ...
- 逆向iOS SDK -- _UIImageAtPath 的实现(SDK 5.1)
注释过的反汇编代码:http://pan.baidu.com/share/link?shareid=3491166579&uk=537224442 伪代码(不精确,仅供参考): NSStrin ...
- hyperledger fabric超级账本java sdk样例e2e代码流程分析
一 checkConfig Before 1.1 private static final TestConfig testConfig = TestConfig.getConfig() ...
- ios NSURLSession使用说明及后台工作流程分析
NSURLSession是iOS7中新的网络接口,它与咱们熟悉的NSURLConnection是并列的.在程序在前台时,NSURLSession与NSURLConnection可以互为替代工作.注意, ...
- CobaltStrike逆向学习系列(10):TeamServer 启动流程分析
这是[信安成长计划]的第 10 篇文章 关注微信公众号[信安成长计划] 0x00 目录 0x01 基本校验与解析 0x02 初始化 0x03 启动 Listeners 在之前的分析中,都是针对 Cob ...
随机推荐
- MVC中实现多按钮提交(转)
有时候会遇到这种情况:在一个表单上需要多个按钮来完成不同的功能,比如一个简单的审批功能. 如果是用webform那不需要讨论,但asp.net mvc中一个表单只能提交到一个Action处理,相对比较 ...
- android 应用程序框架
携带Android软件开发时间,由开发商开发Android应用程序是通过应用程序框架和Android底层交互,因此,发展以达到最大的部分是应用程序框架. 应用集成框架 那里4一个重要组成部分,以下. ...
- 从头开始学JavaScript(一)——基础中的基础
概要:javascript的组成. 各个组成部分的作用 . 一.javascript的组成 javascript ECMAScript(核心) DOM(文档对象模型) BOM(浏览器对象模型) ...
- Swift语言指南(七)--语言基础之布尔值和类型别名
原文:Swift语言指南(七)--语言基础之布尔值和类型别名 布尔值 Swift有一个基本布尔类型,叫做布尔(bool),布尔值又称逻辑值(logical),因为它只能为真(true)或假(false ...
- NGUI使用教程(2) 使用NGUI创建2D场景而且加入标签和button
1.创建2D场景 要使用NGUI创建2D场景,首先咱们必须新建一个项目,而且导入NGUI作为这个项目的插件,相信假设看过上一篇教程都知道怎么导入NGUI了,这里就不赘述,假设有疑问的能够去看上一篇教程 ...
- MVC扩展ModelBinder使类型为DateTime的Action参数可以接收日期格式的字符串
原文:MVC扩展ModelBinder使类型为DateTime的Action参数可以接收日期格式的字符串 如何让视图通过某种途径,把符合日期格式的字符串放到路由中,再传递给类型为DateTime的控制 ...
- 关于Java中List对象的分页思想,按10个或者n个数对list进行分组
try { List<String> timelist = DateUtils.getDateListBySETime("2015-08-01", "2015 ...
- WPF 读写TxT文件
原文:WPF 读写TxT文件 文/嶽永鹏 WPF 中读取和写入TxT 是经常性的操作,本篇将从详细演示WPF如何读取和写入TxT文件. 首先,TxT文件希望逐行读取,并将每行读取到的数据作为一个数组的 ...
- 记录一下Fedora21下安装Foundation5遇到的问题[尚有遗留问题]
写在前面:之前安装过了gem,所以下面的步骤没有这一过程,再有就是忘记哪一步需要ruby中的一个.h文件.可以使用如下命令解决 sudo yum install ruby-devel ------ S ...
- HBuilder CSS 自定义代码块
=begin 本文档是CSS代码块的编辑文件.注意不要把其他语言的设置放到css里来. HBuilder可使用ruby脚本来编辑代码块和增强操作命令. 1.编辑代码块 如果要新增一个代码块,复制如下一 ...
