10.3 发送iMessage

经过10.2节的洗礼,相信部分读者会产生跟笔者相同的感觉:一步一步用LLDB调试虽然准确严谨,但工作量巨大,容易让人感到厌倦。逆向工程就是要勇于试错,不走寻常路,用跳跃的思维和大胆的猜想来达到目的。本节就会采用这种方式——尽量少地使用LLDB,尽量多地通过IDA和class-dump中看到的关键词,并用Cycript配合联想来达到发送iMessage的目的。

10.3.1 从MobileSMS界面元素寻找逆向切入点

相对于检测iMessage,发送iMessage的切入点就要明显得多,在图10-55所示的iOS截图上,这个大大的“Send”按钮,不就是苹果送给我们的大礼么?

图10-55 显眼的“Send”

按下“Send”,发送一条iMessage,这就是发送iMessage最直观的表现。跟10.2节一样,先想想怎么把这个现象给具象化成逆向工程:

“Send”按钮是一个UIView,具体地说,可能是一个UIButton;点击这个UIButton,调用UIButton的响应动作;全套响应动作包括更新界面、发送信息、添加已发送记录,等等,也就是说,发送信息的操作只是全套响应动作的子集。

在MobileSMS发送界面中,我们的输入只有收件人地址和信息内容,它们是原始数据源。因为可以拿到全套响应动作,而发送信息的操作一定要以原始数据源为参数,所以可以根据这2个条件,在全套响应动作里筛选出发送信息的操作。与上节的终点倒推起点不同,这次将从起点到达终点,演示逆向工程的另一种思路。

总结一下,逆向工程的思路是这样的:先用Cycript定位“Send”按钮的响应函数,然后用IDA纵览全套响应动作,结合LLDB和数据源,寻找可疑的发送操作。

10.3.2 用Cycript找出“Send”按钮的响应函数

因为前面在10.2节中已经找到了“Send”所在的view是一个CKMessageEntryView,所以这里就可以直接重复10.2.2节的分析思路,得出下面的结果:


cy# ?expand
expand == true
cy# [UIApp windows]
@[#"<UIWindow: 0x14e12fa0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14e11f50>; layer = <UIWindowLayer: 0x14ee4570>>",#"<UITextEffectsWindow: 0x14fa6000; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x14fa66d0>; layer = <UIWindowLayer: 0x14fa5fc0>>",#"<CKJoystickWindow: 0x14d22310; baseClass = UIAutoRotatingWindow; frame = (0 0; 320 568); hidden = YES; gestureRecognizers = <NSArray: 0x14d21ab0>; layer = <UIWindowLayer: 0x14d22140>>"]
cy# [#0x14fa6000 subviews]
@[#"<UIInputSetContainerView: 0x14d03930; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x14d03770>>"]
cy# [#0x14d03930 subviews]
@[#"<UIInputSetHostView: 0x14d033f0; frame = (0 250; 320 318); layer = <CALayer: 0x14d03290>>"]
cy# [#0x14d033f0 subviews]
@[#"<UIKBInputBackdropView: 0x160441a0; frame = (0 65; 320 253); userInteractionEnabled = NO; layer = <CALayer: 0x16043b60>>",#"<_UIKBCompatInputView: 0x14f78a20; frame = (0 65; 320 253); layer = <CALayer: 0x14f78920>>",#"<CKMessageEntryView: 0x160c6180; frame = (0 0; 320 65); opaque = NO; autoresize = W; layer = <CALayer: 0x16089920>>"]
cy# [#0x160c6180 subviews]
@[#"<_UIBackdropView: 0x16069d40; frame = (0 0; 320 65); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <_UIBackdropViewLayer: 0x14d627c0>>",#"<UIView: 0x16052920; frame = (0 0; 320 0.5); layer = <CALayer: 0x160529d0>>",#"<UIButton: 0x1605a8b0; frame = (266 27; 53 33); opaque = NO; layer = <CALayer: 0x16052a00>>",#"<UIButton: 0x14d0b2c0; frame = (266 30; 53 26); hidden = YES; opaque = NO; gestureRecognizers = <NSArray: 0x160f9800>; layer = <CALayer: 0x1605a140>>",#"<UIButton: 0x1606f040; frame = (15 33.5; 25 18.5); opaque = NO; gestureRecognizers = <NSArray: 0x14d07970>; layer = <CALayer: 0x1605aaa0>>",#"<_UITextFieldRoundedRectBackgroundViewNeue: 0x160e5ed0; frame = (55 8; 209.5 49.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x160d3a10>>",#"<UIView: 0x160a3390; frame = (55 8; 209.5 49.5); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x160b8ab0>>",#"<CKMessageEntryWaveformView: 0x160c4750; frame = (15 25.5; 251 35); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x160c47e0>>"]

其中,“UIView:0x16052920”就是“iMessage”所在的view,还记得吧?那么,紧随其后的2个UIButton就显得十分可疑了,直觉告诉笔者,“Send”就是它俩其中之一。同时我们注意到,第三个UIButton的hidden属性是YES,也就是说这个按钮是隐藏的,那么可见的“Send”肯定就是“UIButton:0x1605a8b0”了。还是用Cycript来确认一下,如下:


cy# [#0x1605a8b0 setHidden:YES]

执行之后,界面变成了图10-56的样子:

图10-56 隐藏“Send”

准确无误。按下这个UIButton后,发送一条iMessage;UIButton与其点击之后的动作一般是通过addTarget:action:forControlEvents:函数来关联的,这是UIButton的父类UIControl中的一个函数。而UIControl类本身就提供了一个actionsForTarget:forControlEvent:来反查UIControl对象的动作。可以利用这个函数,来看看按下“Send”之后会触发什么动作,如下:


cy# [#0x1605a8b0 setHidden:NO]
cy# button = #0x1605a8b0
#"<UIButton: 0x1605a8b0; frame = (266 27; 53 33); hidden = YES; opaque = NO; layer = <CALayer: 0x16052a00>>"
cy# [button allTargets]
[NSSet setWithArray:@[#"<CKMessageEntryView: 0x160c6180; frame = (0 0; 320 65); opaque = NO; autoresize = W; layer = <CALayer: 0x16089920>>"]]]
cy# [button allControlEvents]
64
cy# [button actionsForTarget:#0x160c6180 forControlEvent:64]
@["touchUpInsideSendButton:"]

可以看到,触发的函数是[CKMessageEntryView touchUpInsideSendButton:button]。现在,转战到IDA和LLDB上,看看这个函数的内部实现。

10.3.3 在响应函数中寻找可疑的发送操作

[CKMessageEntryView touchUpInsideSendButton:button]的实现很简单,如图10-57所示。

图10-57 [CKMessageEntryView touchUpInsideSendButton:button]

先[[self delegate]messageEntryViewSendButtonHit:self],然后[self updateEntryView]。看名字就知道后者是简单地更新视图,那发送的动作应该就包含在前者内。下面先用Cycript看看[self delegate]是什么,如下:


cy# [#0x160c6180 delegate]
#"<CKTranscriptController: 0x15537200>"

在IDA中前往[CKTranscriptController messageEntryViewSendButtonHit:CKMessageEntry View]。这个函数的逻辑比较简单,如图10-58所示。

图10-58 [CKTranscriptController messageEntryViewSendButtonHit:CKMessageEntryView]

相信你用肉眼就能看出,实际的发送操作暗藏在[self sendComposition:[CKMessageEntry-View compositionWithAcceptedAutocorrection]]中。接下来在Cycript中看看[self composition-WithAcceptedAutocorrection]是什么:


cy# [#0x160c6180 compositionWithAcceptedAutocorrection]
#"<CKComposition: 0x160b79d0> text:'iMessage {\n}' subject:'(null)'"

它是一个CKComposition对象,且明明白白地显示了要发送的标题和内容。继续看sendComposition:的内部实现,如图10-59所示。

图10-59 [self sendComposition:]

其实现很复杂,有必要在到LLDB上一步一步调试之前,先大概地过一下进程流程中的几个分支,看看其逻辑走向。先来到loc_268D427C中,如图10-60所示。

图10-60 loc_268D427C

如果“有内容”就走右边,我们发送的内容是“iMessage”,当然算是“有内容”,走右边,到达图10-61。

图10-61 分支

“下一个待调整的媒体对象”?难道是指的图片、语音、视频这类东西?我们要发的iMessage是纯文字,应该不涉及这些东西,走右边,到达图10-62。

图10-62 分支

R0是个啥?回到sendComposition:的开始部分,如图10-63所示。

R0原来是self->_newRecipient,在Cycript中看看它的值是多少,如下:


cy# #0x15537200->_newRecipient
1

图10-63 追踪R0的值

因此“TST.W R0,#4”的结果是0,走右边到达loc_268D4604,如图10-64所示。

图10-64 loc_268D4604

“正在发送信息”?按下“Send”之后才发送信息,那这里的“正在”指的是按下“Send”之前还是之后呢?像下面这样分别测试一下好了:


cy# [#0x15537200 isSendingMessage]
0

然后按下“Send”,再测一次:


cy# [#0x15537200 isSendingMessage]
0

可见,不管是按下“Send”之前还是之后,[self isSendingMessage]的返回值都是0,走左边那条路,继续寻找下一个分支,如图10-65所示。

图10-65 分支

“能发送给收件人吗?”我们的目标地址是一个有效的iMessage账号,当然能了!走左边,到达图10-66。

图10-66 分支

“能发送写好的内容吗?”因为刚才都已经把CKComposition的内容打印出来了,所以这里也没问题,走左边,到达图10-67。

图10-67 分支

这里又是一个分支。往上看看,就可以在图10-60中找到R5的值,可见,这里要再次判断发送的信息是否“有内容”了。走右边,到达图10-68。

图10-68这张图的信息量略大,但仔细看看,你就会发现前面做的一系列动作都是UI层面的刷新操作,只有最后一个sendMessage:十分可疑。这个函数的参数是什么?往上回溯,可以看到其实就是[self sendComposition:]的参数,即一个CKComposition对象。继续分析[CKTranscriptController sendMessage:]的实现,如图10-69所示。

这个函数的流程看起来分支众多,但在浏览一遍(思路跟浏览sendComposition:时一样)后就会发现,分支里都只是做了一些准备工作,“_startCreatingNewMessageForSending:”才是可能发出信息的地方。去它的实现里看看,如图10-70所示。

这里的逻辑略纠结。按照上面描述过的思路,大致浏览一遍。相信你在浏览关键词的时候,也会同笔者一样注意到“sendMessage:newComposition:”,且它一共出现了2次,即图10-71所示的2个深色方块。

下面来看看这个函数的实现,如图10-72所示。

图10-68 分支

图10-69 [CKTranscriptControllersendMessage:]

图10-70 [CKTranscriptController_startCreatingNewMessageForSending:]

图10-71 [CKTranscriptController_startCreatingNewMessageForSending:]

图10-72 [CKConversation sendMessage:newComposition:]

它进一步调用了“sendMessage:onService:newComposition:”,继续跟过去,如图10-73所示。

图10-73 [CKConversation sendMessage:onService:newComposition:]

流程比较简洁,大致浏览一下,就会看到诸如“Sending message with guid:%@”、“=>Sending account:%@”、“=>Recipients:[%@]”等的字眼,且它们大都是_CKLogExternal的参数——都已经开始记录这些字眼了,不恰恰说明正在发生“发送信息”这件事吗?进一步,在图10-74中又看到了可疑的字眼“sendMessage:”。

图10-74 loc_2691f836

它的调用者和参数是什么?还是直接用IDA把它们给找出来。先看调用者R0,它来自R5。R5又来自哪里呢?往上走,往上走,到loc_2691F726处,如图10-75所示。

图10-75 loc_2691f726

其中,“LDR R5,[SP,#0xA4+var_98]”决定了R5的值,那[SP,#0xA4+var_98]是什么呢?还记得在10.2节中是如何处理这类问题的吗?把光标放在var_98上,然后按下“x”,查看其交叉引用,如图10-76所示。

图10-76 查看交叉引用

双击第一条交叉引用,跳转至“STR R0,[SP,#0xA4+var_98]”,R0来自[R6 chat]。R6在[CKConversation sendMessage:onService:newComposition:]的开始部分第一次出现,很明显是self,所以“sendMessage:”的调用者是[self chat]。接着回到图10-74中,可看到它的参数R2来自R6。往上拉一点,可以看到R6来自loc_2691F6F4中的“LDR R6,[SP,#0xA4+var_80]”,如图10-77所示。

图10-77 loc_2691f6f4

接下来该怎么办?我们在1分钟前刚完成了一次类似的操作,这里就不再用文字描述了,只用几张图片(如图10-78至图10-80所示)作为小提示,由读者自己来完成这个操作。

图10-78 查看交叉引用

图10-79 [CKConversation setChat:]

图10-80 [CKConversation sendMessage:onService:newComposition:]

所以[[self chat]sendMessage:]的参数与[self sendMessage:onService:newComposition:]的第一个参数一脉相承。那么[self chat]是什么类型,参数又是什么类型呢?在上面的静态分析中,我们并没有在IDA中找到什么明显的线索,因此,是时候让LLDB出来热热身了。

先编写一条iMessage,然后在[CKConversation sendMessage:onService:newComposition:]尾部“sendMessage:”下方的那个objc_msgSend上下个断点,接着按下“Send”,触发断点,如下:


Process 233590 stopped
* thread #1: tid = 0x39076, 0x30ad1846 ChatKit`-[CKConversation sendMessage:onService:newComposition:] + 686, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
    frame #0: 0x30ad1846 ChatKit`-[CKConversation sendMessage:onService:newComposition:] + 686
ChatKit`-[CKConversation sendMessage:onService:newComposition:] + 686:
-> 0x30ad1846:  blx    0x30b3bf44                ; symbol stub for: MarcoShouldLogMadridLevel$shim
   0x30ad184a:  movw   r0, #49322
   0x30ad184e:  movt   r0, #2541
   0x30ad1852:  add    r0, pc
(lldb) p (char *)$r1
(char *) $0 = 0x32b26146 "sendMessage:"
(lldb) po $r0
<IMChat 0x5ef2ce0> [Identifier: snakeninny@icloud.com   GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com   Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363   Style: -   State: 3  Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null) Group ID: F399B0B5-800F-47A4-A66C-72C43ACC0428  Unread Count: 0  Failure Count: 0]
(lldb) po $r2
IMMessage[from=(null); msg-subject=(null); account:(null); flags=100005; subject='<< Message Not Loggable >>' text='<< Message Not Loggable >>' messageID: 0 GUID:'966C2CD6-3710-4D0F-BCEF-BCFEE8E60FE9' date:'437730968.559627' date-delivered:'0.000000' date-read:'0.000000' date-played:'0.000000' empty: NO finished: YES sent: NO read: NO delivered: NO audio: NO played: NO from-me: YES emote: NO dd-results: NO dd-scanned: YES error: (null)]
(lldb) ni

结果不能再明显了,[IMChat sendMessage:IMMessage]就是我们要找的答案。注意,笔者在打印完所有需要的信息后执行了“ni”命令,然后听到iPhone发出了一个熟悉的“信息已发送”的提示音。这从侧面说明,实际的发送操作正是在[IMChat sendMessage:IMMessage]内完成的。因为IMChat和IMMessage的前缀均为IM,所以它们来自ChatKit以外的库,ChatKit所提供的最底层的发送信息函数到[CKConversation sendMessage:onService:newComposition:]为止。此时,可以肯定,只要能够构造我们自己的IMChat和IMMessage,就可以实现发送iMessage的功能了。那么问题来了,怎么构造这2个类的对象呢?化繁为简,在class-dump的头文件里找找看有没有线索。

要构造IMChat和IMMessage,就先看看它们的头文件里有没有什么明显的构造方法。先打开IMChat.h,看看有没有包含“init”字眼的关键词,如下:


- (id)_initWithDictionaryRepresentation:(id)arg1 items:(id)arg2 participantsHint:(id)arg3 accountHint:(id)arg4;
- (id)init;
- (id)_initWithGUID:(id)arg1 account:(id)arg2 style:(unsigned char)arg3 roomName:(id)arg4 displayName:(id)arg5 items:(id)arg6 participants:(id)arg7;

上面的代码中参数众多,如何一个个构造它们,我们一点头绪都没有。那接下来该怎么办呢?

还记得是如何找到“sendMessage:”调用者的吗?对了,使用[self chat]——self是一个CKConversation对象,看看[CKConversation chat]是怎么来的,不就知道IMChat是如何生成的了吗?在IDA里定位到[CKConversation chat],如图10-81所示。

图10-81 [CKConversation chat]

[CKConversation chat]就是实例变量_chat的值,这个场景似曾相识啊——还记得大明湖畔的夏雨荷,哦不,是图10-22里的_composeSendingService吗?此处的想法与操作与那里完全一致,LLDB又要在逆向推导中派上大用场了!删掉已发送的iMessage对话(即删掉这个CKConversation),新建一条iMessage(新建一条CKConversation),然后在[CKConversation setChat:]上下一个断点,点击“Send”,触发断点,如下:


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x30ad277c ChatKit`-[CKConversation setChat:], queue = 'com.apple.main-thread, stop reason = breakpoint 13.1
    frame #0: 0x30ad277c ChatKit`-[CKConversation setChat:]
ChatKit`-[CKConversation setChat:]:
-> 0x30ad277c:  movw   r3, #55168
   0x30ad2780:  movt   r3, #2541
   0x30ad2784:  add    r3, pc
   0x30ad2786:  ldr    r3, [r3]
(lldb) po $r2
<IMChat 0x1594f7e0> [Identifier: snakeninny@icloud.com   GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com   Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363   Style: -   State: 0  Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null)Group ID: (null)  Unread Count: 0  Failure Count: 0]
(lldb) p/x $lr
(unsigned int) $20 = 0x30acf625

LR偏移前的值是0x30acf625–0xa1b2000=0x2691d625,这个地址位于[CKConversation initWithChat:]中。不过,[CKConversation initWithChat:]的调用者又是谁呢?如法炮制(注意,每次下断点前都要删掉已发送的iMessage对话,再新建一条iMessage,下同):


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x30acf5ec ChatKit`-[CKConversation initWithChat:], queue = 'com.apple.main-thread, stop reason = breakpoint 14.1
    frame #0: 0x30acf5ec ChatKit`-[CKConversation initWithChat:]
ChatKit`-[CKConversation initWithChat:]:
-> 0x30acf5ec:  push   {r4, r5, r6, r7, lr}
   0x30acf5ee:  add    r7, sp, #12
   0x30acf5f0:  push.w {r8, r10, r11}
   0x30acf5f4:  sub    sp, #8
(lldb) po $r2
<IMChat 0x1470a520> [Identifier: snakeninny@icloud.com GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363 Style: - State: 0 Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null) Group ID: (null) Unread Count: 0 Failure Count: 0]
(lldb) p/x $lr
(unsigned int) $22 = 0x30a8d131

LR偏移前的值是0x30a8d131–0xa1b2000=0x268db131,这个地址位于[CKConversationList_beginTrackingConversationWithChat:]中。继续:


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x30a8d09c ChatKit`-[CKConversationList _beginTrackingConversationWithChat:], queue = 'com.apple.main-thread, stop reason = breakpoint 15.1
    frame #0: 0x30a8d09c ChatKit`-[CKConversationList _beginTrackingConversationWithChat:]
ChatKit`-[CKConversationList _beginTrackingConversationWithChat:]:
-> 0x30a8d09c:  push   {r4, r5, r6, r7, lr}
   0x30a8d09e:  mov    r5, r0
   0x30a8d0a0:  movs   r0, #25
   0x30a8d0a2:  add    r7, sp, #12
(lldb) po $r2
<IMChat 0x15a326a0> [Identifier: snakeninny@icloud.com   GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com   Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363   Style: -   State: 0  Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null) Group ID: (null)  Unread Count: 0  Failure Count: 0]
(lldb) p/x $lr
(unsigned int) $24 = 0x30a8d4f1

LR偏移前的值是0x30a8d4f1–0xa1b2000=0x268db131,这个地址位于[CKConversationList_handleRegistryDidRegisterChatNotification:]中,且这里的IMChat对象来自于[notification object]。因为这里的IMChat对象是通过notification传播的,所以下一个目标不是找到[CKConversationList_handleRegistryDidRegisterChatNotification:]的调用者,而是找到这条notification的发布者,它才是“罪魁祸首”。在这个函数的第一条指令上下一个断点,看看这条notification的结构,如下:


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x30a8d4ac ChatKit`-[CKConversationList _handleRegistryDidRegisterChatNotification:], queue = 'com.apple.main-thread, stop reason = breakpoint 16.1
    frame #0: 0x30a8d4ac ChatKit`-[CKConversationList _handleRegistryDidRegisterChatNotification:]
ChatKit`-[CKConversationList _handleRegistryDidRegisterChatNotification:]:
-> 0x30a8d4ac:  push   {r4, r5, r6, r7, lr}
   0x30a8d4ae:  add    r7, sp, #12
   0x30a8d4b0:  push.w {r8, r10, r11}
   0x30a8d4b4:  sub.w  r4, sp, #64
(lldb) po $r2
NSConcreteNotification 0x15934340 {name = __kIMChatRegistryDidRegisterChatNotification; object = <IMChat 0x147c39f0> [Identifier: snakeninny@icloud.com   GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com   Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363   Style: -   State: 0  Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null) Group ID: (null)  Unread Count: 0  Failure Count: 0]}

这条notification的name是“__kIMChatRegistryDidRegisterChatNotification”,object是一个IMChat对象,因此这个IMChat对象一定是在发布(post)这条notification之前就已经生成了。要找出这条notification的发布者,最好的方法,就是执行grep命令搜索一遍系统文件,看看“__kIMChatRegistryDidRegisterChatNotification”的关键字都会在哪些文件里出现,如下:


FunMaker-5:~ root# grep -r _handleRegistryDidRegisterChatNotification: /System/      
Binary file /System/Library/Caches/com.apple.dyld/dyld_shared_cache_armv7s matches
grep: /System/Library/Caches/com.apple.dyld/enable-dylibs-to-override-cache: No such file or directory
grep: /System/Library/Frameworks/CoreGraphics.framework/Resources/libCGCorePDF.dylib: No such file or directory
grep: /System/Library/Frameworks/CoreGraphics.framework/Resources/libCMSBuiltin.dylib: No such file or directory
grep: /System/Library/Frameworks/CoreGraphics.framework/Resources/libCMaps.dylib: No such file or directory
grep: /System/Library/Frameworks/System.framework/System: No such file or directory

因为它在cache里,所以下面grep一遍decache出的文件,如下:


snakeninnys-MacBook:~ snakeninny$ grep -r __kIMChatRegistryDidRegisterChatNotification /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5/
Binary file /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5//dyld_shared_cache_armv7s matches
grep: /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5//System/Library/Caches/com.apple.xpc/sdk.dylib: Too many levels of symbolic links
grep: /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5//System/Library/Frameworks/OpenGLES.framework/libLLVMContainer.dylib: Too many levels of symbolic links
Binary file /Users/snakeninny/Code/iOSSystemBinaries/8.1_iPhone5//System/Library/PrivateFrameworks/IMCore.framework/IMCore matches

其实到这里,相信你也能猜到,IMCore与ChatKit都负责与信息相关的操作,但IMCore比ChatKit更底层,ChatKit接到的命令都交给IMCore完成,IMCore完成的结果再交给ChatKit展示给用户——MobileSMS是餐馆,ChatKit是服务员,IMCore是厨师,这么比喻,就好理解多了吧。

接下来,在IDA中打开IMCore,全文搜索“__kIMChatRegistryDidRegisterChatNotifi-cation”,如图10-82所示。

图10-82 在IDA里搜索“__kIMChatRegistryDidRegisterChatNotification”

很好,直接双击第一条搜索结果,看看它的上下文,如图10-83所示。

看到“PostNotification”字眼,我们就知道了,ChatKit收到的那条notification正是来自于此。IMChat对象是第二个参数,即R3,而R3来自[SP,#0x98+var_60]。还记得怎么操作吗?还是以提示图(如图10-84与图10-85所示)代替文字,请读者自己来摸索着操作吧。

图10-83 查看搜索结果

图10-84 查看交叉引用

通过上面的分析可知,IMChat对象来自于[IMChatRegistry_registerChatDictionary:forChat:isIncoming:newGUID:]的第二个参数。这个函数的调用者如下:


Process 248623 stopped
 * thread #1: tid = 0x3cb2f, 0x33235944 IMCore`___lldb_unnamed_function2048$$IMCore, queue = 'com.apple.main-thread, stop reason = breakpoint 17.1
    frame #0: 0x33235944 IMCore`___lldb_unnamed_function2048$$IMCore
IMCore`___lldb_unnamed_function2048$$IMCore:
-> 0x33235944:  push   {r4, r5, r6, r7, lr}
   0x33235946:  add    r7, sp, #12
   0x33235948:  push.w {r8, r10, r11}
   0x3323594c:  sub.w  r4, sp, #64
(lldb) po $r3
<IMChat 0x147c7f30> [Identifier: snakeninny@icloud.com GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363 Style: - State: 0  Participants: 1 Room Name: (null) Display Name: (null) Last Addressed: (null) Group ID: (null) Unread Count: 0 Failure Count: 0]
(lldb) p/x $lr
(unsigned int) $27 = 0x3323646f

图10-85 [IMChatRegistry_registerChatDictionary:forChat:isIncoming:newGUID:]

LR偏移前的值是0x3323646f–0xa1b2000=0x2908446F,这个地址位于[IMChatRegistry_registerChat:isIncoming:guid:]中。继续:


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x3323644c IMCore`___lldb_unnamed_function2049$$IMCore, queue = 'com.apple.main-thread, stop reason = breakpoint 20.1
    frame #0: 0x3323644c IMCore`___lldb_unnamed_function2049$$IMCore
IMCore`___lldb_unnamed_function2049$$IMCore:
-> 0x3323644c:  push   {r4, r5, r7, lr}
   0x3323644e:  add    r7, sp, #8
   0x33236450:  sub    sp, #8
   0x33236452:  movw   r1, #9840
(lldb) po $r2
<IMChat 0x15972f20> [Identifier: snakeninny@icloud.com GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363 Style: - State: 0  Participants: 1  Room Name: (null)  Display Name: (null) Last Addressed: (null) Group ID: (null) Unread Count: 0 Failure Count: 0]
(lldb) p/x $lr
(unsigned int) $30 = 0x33237173

LR偏移前的值是0x33237173–0xa1b2000=0x29085173,这个地址位于[IMChatRegistry chatForIMHandle:]中,且[IMChatRegistry_registerChat:isIncoming:guid:]的第一个参数,即IMChat对象来自于R5,在[IMChatRegistry chatForIMHandle:]的最后阶段,R5是以返回值的形象出现的。也就是说,[IMChatRegistry chatForIMHandle:]这个函数返回了一个IMChat!且从IMChatRegistry的名字来看,就知道这个类负责注册登记IMChat,一个IMChat对象由此而来,合情合理。但是,解决了1个老问题,带来了2个新问题——IMChatRegistry和chatForIMHandle:的参数从哪里来?饭要一口一口地吃,逆向要一步一步地来。打开IMChatRegistry.h(如图10-86所示),先从IMChatRegistry下手。

图10-86 IMChatRegistry.h

其中,第44行的sharedInstance,说明IMChatRegistry是一个单例,通过调用[IMChatRegistry sharedInstance]就可以获取到它的实例。So easy!

那chatForIMHandle:的参数从哪里来?自然是从它的调用者那里来,继续用LLDB追踪吧,如下:


Process 248623 stopped
* thread #1: tid = 0x3cb2f, 0x33236d8c IMCore`___lldb_unnamed_function2054$$IMCore, queue = 'com.apple.main-thread, stop reason = breakpoint 21.1
    frame #0: 0x33236d8c IMCore`___lldb_unnamed_function2054$$IMCore
IMCore`___lldb_unnamed_function2054$$IMCore:
-> 0x33236d8c:  push   {r4, r5, r6, r7, lr}
   0x33236d8e:  add    r7, sp, #12
   0x33236d90:  str    r11, [sp, #-4]!
   0x33236d94:  sub    sp, #20
(lldb) po $r2
[IMHandle: <snakeninny@icloud.com:<None>:cn> (Person: <No AB Match>) (Account: P:+86PhoneNumber]
(lldb) p/x $lr
(unsigned int) $32 = 0x30a8dca5

LR偏移前的值是0x30a8dca5–0xa1b2000=0x268dbca5,这个地址已经不再位于IMCore的地址范围内了。刚才也说了,现在正不断地在IMCore和ChatKit间徘徊,正好ChatKit的ASLR偏移也是0xa1b2000,那就去ChatKit看看0x268dbca5在不在它里面,如图10-87所示。

图10-87 [CKConversationList conversationForHandles:displayName:joinedChatsOnly:create:]

0x268dbca5位于[CKConversationList conversationForHandles:displayName:joinedChatsOnly:create:]内部,chatForIMHandle:的参数也是来自于[CKConversationList conversation ForHandles:displayName:joinedChatsOnly:create:]的第一个参数。继续回溯,如下:


Process 292950 stopped
* thread #1: tid = 0x47856, 0x30a8dc60 ChatKit`-[CKConversationList conversationForHandles:displayName:joinedChatsOnly:create:], queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
    frame #0: 0x30a8dc60 ChatKit`-[CKConversationList conversationForHandles:displayName:joinedChatsOnly:create:]
ChatKit`-[CKConversationList conversationForHandles:displayName:joinedChatsOnly:create:]:
-> 0x30a8dc60:  push   {r4, r5, r6, r7, lr}
   0x30a8dc62:  add    r7, sp, #12
   0x30a8dc64:  sub    sp, #8
   0x30a8dc66:  mov    r6, r0
(lldb) po $r2
<__NSArrayM 0x178d2290>(
[IMHandle: <snakeninny@icloud.com:<None>:cn> (Person: <No AB Match>) (Account: P:+86PhoneNumber]
)
(lldb) p/x $lr
(unsigned int) $1 = 0x30a84efd

LR偏移前的值是0x30a84efd–0xa1b2000=0x268d2efd,这个地址位于[CKTranscript-Controller sendMessage:]中!你!敢!信!吗!绕了一大圈,又回到了原点,让人不禁感叹,缘,妙不可言。擦干喜悦的泪珠,来看看,这个IMHandle数组到底是怎么来的,如图10-88所示。

图10-88 IMHandle数组的来源

R2来自R6,R6来自[SP,#0xA8+var_80]。熟悉的套路又回来了,下面还是只给出提示图(如图10-89和图10-90所示),请读者自行分析。

图10-89 查看交叉引用

图10-90 [CKTranscriptController sendMessage:]

你可能也会发现,这里的情况跟前几次不一样了——“STR R0,[SP,#0xA8+var_80]”貌似只是往[SP,#0xA8+var_80]里存了一个初始化过的NSMutableArray而已啊!说好的IMHandle呢?嘿嘿,既然是一个NSMutableArray,那么就可能调用addObject:,往里面加东西,所以图10-89里中间的那一个“LDR R0,[SP,#0xA8+var_80]”……双击跳转过去看看,如图10-91所示。

图10-91 寻找IMHandle

果不其然,它是一个addObject:,而且通过简单观察上下文就会发现,addObject:的参数来自于imHandleWithID:alreadyCanonical:,并且从这个函数的名字就可以看出来,它返回了一个IMHandle。看来,我们的IMHandle有着落了!在图10-91所示的第一个objc_msgSend上下一个断点,看看imHandleWithID:alreadyCanonical:的调用者和参数,如下:


Process 343388 stopped
* thread #1: tid = 0x53d5c, 0x30a84e98 ChatKit`-[CKTranscriptController sendMessage:] + 516, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
    frame #0: 0x30a84e98 ChatKit`-[CKTranscriptController sendMessage:] + 516
ChatKit`-[CKTranscriptController sendMessage:] + 516:
-> 0x30a84e98:  blx    0x30b3bf44                ; symbol stub for: MarcoShouldLogMadridLevel$shim
   0x30a84e9c:  mov    r2, r0
   0x30a84e9e:  ldr    r0, [sp, #40]
   0x30a84ea0:  mov    r1, r11
(lldb) p (char *)$r1
(char *) $0 = 0x30b55fb4 "imHandleWithID:alreadyCanonical:"
(lldb) po $r0
IMAccount: 0x145e30d0 [ID: 26B3EC90-783B-4DEC-82CF-F58FBBB22363 Service: IMService[iMessage] Login: P:+86PhoneNumber Active: YES LoginStatus: Connected]
(lldb) po $r2
snakeninny@icloud.com
(lldb) p $r3
(unsigned int) $3 = 0

2个参数都搞定了,第1个就是iMessage地址,第2个是0(即BOOL的NO),那调用者,这个IMAccount对象是哪里来的呢?如图10-91所示,R0来自[SP,#0xA8+var_84],所以根据提示图10-92和图10-93可知,IMAccount对象来自[[IMAccountController sharedInstance]__ck_defaultAccountForService:[CKConversation sendingService]]。

图10-92 查看交叉引用

图10-93 [CKTranscriptController sendMessage:]

趁热打铁,在图10-93的第2个objc_msgSend上下一个断点,看看[CKConversation sendingService]是什么,如下:


Process 343388 stopped
* thread #1: tid = 0x53d5c, 0x30a84e08 ChatKit`-[CKTranscriptController sendMessage:] + 372, queue = 'com.apple.main-thread, stop reason = breakpoint 2.1
    frame #0: 0x30a84e08 ChatKit`-[CKTranscriptController sendMessage:] + 372
ChatKit`-[CKTranscriptController sendMessage:] + 372:
-> 0x30a84e08:  blx    0x30b3bf44                ; symbol 
stub for: MarcoShouldLogMadridLevel$shim
   0x30a84e0c:  str    r0, [sp, #36]
   0x30a84e0e:  movw   r0, #23756
   0x30a84e12:  add    r2, sp, #44
(lldb) p (char *)$r1
(char *) $4 = 0x30b55f95 "__ck_defaultAccountForService:"
(lldb) po $r2
IMService[iMessage]
(lldb) po [$r2 class]
IMServiceImpl

可见,它是一个IMServiceImpl对象,那在我们自己的代码中,该如何得到这样一个IMServiceImpl对象呢?其实在10.2节中已经拿到这个类的对象了,打开IMServiceImpl.h,如图10-94所示。

图10-94 IMServiceImpl.h

其中的[IMServiceImpl iMessageService]就是了。用Cycript再次确认,如下:


cy# [IMServiceImpl iMessageService]
#"IMService[iMessage]"

到此为止,一个可用的IMChat类是如何生成的,被我们完整地逆向了出来。下面在Cycript里验证一下可行性,如下:


FunMaker-5:~ root# cycript -p MobileSMS
cy# service = [IMServiceImpl iMessageService]
#"IMService[iMessage]"
cy# account = [[IMAccountController sharedInstance] __ck_defaultAccountForService:service]
#"IMAccount: 0x145e30d0 [ID: 26B3EC90-783B-4DEC-82CF-F58FBBB22363 Service: IMService[iMessage] Login: P:+86PhoneNumber Active: YES LoginStatus: Connected]"
cy# handle = [account imHandleWithID:@"snakeninny@icloud.com" alreadyCanonical:NO]
#"[IMHandle: <snakeninny@icloud.com:<None>:cn> (Person: <No AB Match>) (Account: P:+86 MyPhoneNumber]"
cy# chat = [[IMChatRegistry sharedInstance] chatForIMHandle:handle]
#"<IMChat 0x15809000> [Identifier: snakeninny@icloud.com   GUID: iMessage;-;snakeninny@icloud.com Persistent ID: snakeninny@icloud.com   Account: 26B3EC90-783B-4DEC-82CF-F58FBBB22363   Style: -   State: 3  Participants: 1  Room Name: (null)  Display Name: (null)  Last Addressed: (null) Group ID: 6592DD84-4B34-4D54-BB40-E2AB17B2FC67  Unread Count: 0  Failure Count: 0]"

完美!最后的任务,就是构造一个可用的IMMessage对象,这样就可以实现iMessage的发送了,这就行动起来!

打开IMMessage.h,如图10-95所示。

图10-95 IMMessage.h

又是一堆类方法,其中“instantMessageWithText:flags:”引起了我们的注意,这2个参数应该各传什么?针对第一个“text”传一个NSString进去试试,但第二个“flags”呢?不知道你还有没有印象,在本节的前面,当我们找到[IMChat sendMessage:IMMessage]时,曾在LLDB中打印出了一个IMMessage的结构,如下:


(lldb) po $r2
IMMessage[from=(null); msg-subject=(null); account:(null);flags=100005; subject='<< Message Not Loggable >>' text='<<Message Not Loggable >>' messageID: 0 GUID:'966C2CD6-3710-4D0F-BCEF-BCFEE8E60FE9' date:'437730968.559627' date-delivered:'0.000000' date-read:'0.000000' date-played:'0.000000' empty: NO finished: YES sent: NO read: NOdelivered: NO audio: NO played: NO from-me: YES emote: NOdd-results: NO dd-scanned: YES error: (null)]

这里的“text”无法显示,而“flags”是100005。在Cycript中试试,如下:


cy# [IMMessage instantMessageWithText:@"iOSRE test" flags:100005]
-[__NSCFString string]: unrecognized selector sent to instance 0x1468c140

Cycript告诉我们,NSString不能响应@selector(string),也就是说,第一个参数并不是一个NSString对象,正确类型的参数应该是可以响应这个@selector(string)的。重新审视图10-95,看看能不能找出一些蛛丝马迹。注意到第17行的“NSAttributedString*_text”了吗?查阅苹果官方提供的文档,NSAttributedString确实有一个“-(NSString*)string”方法,如图10-96所示。

图10-96 [NSAttributedString string]

重新尝试传一个NSAttributedString对象进去试试,如下:


cy# attributedString = [[NSAttributedString alloc] initWithString:@"iOSRE test"]
#"iOSRE test{\n}"
cy# message = [IMMessage instantMessageWithText:attributedString flags:100005]
#"IMMessage[from=(null); msg-subject=(null); account:(null);flags=186a5; subject='<< Message Not Loggable >>' text='<<Message Not Loggable >>' messageID: 0 GUID:'00A8C645-D207-4F93-9739-07AAC94E7465' date:'437812476.099226' date-delivered:'0.000000' date-read:'0.000000' date-played:'0.000000' empty: NO finished: YES sent: YES read:NOdelivered: NO audio: NO played: NO from-me: YES emote: NOdd-results: YES dd-scanned: NO error: (null)]"
cy# [attributedString release]

这里成功构造了一个IMMessage对象。接下来,“这是我生命中美好的时刻,我要完成我最喜欢的测试,在这美丽的月光下在这美丽的Cycript里”:


cy# [chat sendMessage:message]

效果如图10-97所示。打完收工!

图10-97 成功发送iMessage