Found much better and simpler way. Just as I thought com.apple.imagent
daemon is very important and it's him who is handling kCTMessageReceivedNotification
. This is why we get empty message object when handling kCTMessageReceivedNotification
yourself - com.apple.imagent
is removing it from CTMessageCenter
.
We need to hook just two methods but finding and hooking them is quite tricky. Both methods are hooked in com.apple.imagent
daemon.
First, SMSServiceSession -(void)_processReceivedMessage:(CTMessage*)msg
. This is where incoming message is being initially processed, saved to the SMS database and passed to all other iOS components. Problem is there is no information about this API anywhere. com.apple.imagent
appears to be not using it if you disassemble it. It's because it's being loaded manually at runtime.
When com.apple.imagent
is started he's loading several plugins. The one that we need is located in /System/Library/Messages/PlugIns/SMS.imservice/
- this is where SMSServiceSession
is implemented. You will not find binary in there because just like all the frameworks it's compiled into /System/Library/Caches/com.apple.dyld/dyld_shared_cache_armv7
. IDA recognizes this file and let's you choose which binary inside of it you want to disassemble.
To delete incoming message and prevent any notifications about it you need to call [[CTMessageCenter sharedMessageCenter] acknowledgeIncomingMessageWithId:[msg messageId]]
and return from _processReceivedMessage:
without calling original implementation. Calling CTMessageCenter
method is important because it queues incoming messages.
Now we need to find a way to know whenSMS.imservice
plugin is being actually loaded. Initially imagent only creates NSBundle
objects without loading any code. So you can't hook any methods because classes are not yet loaded from the plugins. To solve this we can hook IMDService -(void)loadServiceBundle
method from private IMDaemonCore.framework
. Call original implementation and you can hook methods inside the plugin. To determine which plugin is being loaded you can check bundle identifier in IMDService -(NSBundle*)bundle
.
This method works only with SMS and MMS messages. iMessages are processed in similar way but with different plugin - /System/Library/Messages/PlugIns/iMessage.imservice
. Hooking MessageServiceSession -(void)_handler:(id) incomingMessage:(id) encryptionType:(id) messageID:(id) fromIdentifier:(id) fromToken:(id) timeStamp:(id) storageContext:(id) allowRetry:(char) completionBlock:(id)
should do the trick.
UPDATE
Works on iOS 7
UPDATE 2
On iOS 8 everything works the same way except you need to hook different SMSServiceSession
method - -(void)_processReceivedDictionary:(NSDictionary*)msg
. Dictionary will contain all SMS message contents.
If you don't want to rewrite everything for iOS 8 you can reuse your old code. Incoming SMS notification is handled by hidden non-exported C callback function - you can't hook it. First, it calls SMSServiceSession -(id)_convertCTMessageToDictionary:(CTMessage*)msg requiresUpload:(BOOL*)upload
to convert SMS message object to dictionary. Then it calls SMSServiceSession -(void)_processReceivedDictionary:(NSDictionary*)msg
to process message. Finally, it calls SMSServiceSession -(BOOL)relayDictionaryToPeers:(NSDictionary*)msg requiresUpload:(BOOL)upload
to notify all other iOS components about incoming message.
To block SMS you need to hook _convertCTMessageToDictionary
where you can use the same code you used on previous iOS versions. You also need to hook both _processReceivedDictionary
and relayDictionaryToPeers
to actually block incoming message. Just return from them without calling original implementation. You can set some global variable in _convertCTMessageToDictionary
and check and reset it in other methods. It's perfectly safe to do it that way - these methods are called one after another synchronously. That C callback function is the only places where these methods are called.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…