代码面前没有秘密
- (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、... ...
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 的实现,在 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。
搜索 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]
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]
汇编代码,如下:
; 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。