观点:
代码面前没有秘密
添加通知的 Demo 代码
- (void)scheduleOneLocalNotification {
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = @"Proteas";
localNotification.fireDate = [[NSDate date] dateByAddingTimeInterval:300000];
[[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, movt 加载32位立即数 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。