[原]逆向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 ...
随机推荐
- NYoj 素数环(深搜入门)
题目链接: http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=488 深搜模板: void dfs(int 当前状态) { if(当前状态为边界状 ...
- 多校训练赛2 ZCC loves cards
ZCC loves cards Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) ...
- MVC中,视图的Layout使用
本文目标 1.能够重用Razor模板进行页面的组件化搭建 本文目录 1.母板页_Layout.cshtml 2.用户自定义控件 3.默认Layout引用的使用(_ViewStart.cshtml) 1 ...
- PHP第六课 使用方法数组
学习要点: *使用主阵列功能的理解 *知道如何遍历 *了解超全局数组和使用的基本关系 数组 1.数组定义和遍历 2.数组函数 数组定义: $arr=array(1,2,3);//索引数组,下标全是数字 ...
- 使用myeclipse将Javaj项目标ar套餐邂逅classnotfound解决问题的方法
做一件事的今天,该Java项目打包成jar文件.折腾2小时,最终运行jar文件报告classnotfound异常,我觉得程序写入依赖jar包不玩成,但是,我手动添加.或不.网上找了很多办法.或不.后. ...
- IBM即将倒闭,微软也从崩溃18个月
IBM公司20发布2014在第三季度财报.其三阶季度净利润1800万美元,下跌99.6%. 可见IBM我已经危及. 技术专家sun收购崩溃,说明一些原因,自满技术公司可能已用完.. sun以前靠小型机 ...
- linux中如何用root去修改其他用户的密码
linux中如何用root去修改其他用户的密码 昨天linux实验课,我有很多自己想摸索的东西.今天周五,本是下午一二节是编译的实验,可强烈的欲望让我今早就来实验室了,摸索吧,碰到了这个问题.... ...
- Android KitCat 4.4.2 ADB 官方所支持的所有Services格式翻译
在之前的文章中有转帖网上同行制作的ADB协议表格<<adb概览及协议参考>>,但不够详尽,所以这里自己另外基于Android 4.4.2的技术文档重新做一次翻译. HOST S ...
- linux下C语言中的flock函数使用方法 .
表头文件 #include<sys/file.h> 定义函数 int flock(int fd,int operation); 函数说明 flock()会依參数operation所指 ...
- javascript的预编译和执行顺序
原文:javascript的预编译和执行顺序 最近在复习javascript的事件处理时发现了一个问题,然后也是我来写javascript的预编译和执行顺序的问题 代码: 代码一<html> ...
