准备工作
- frida-ios-dump对应用进行脱壳
- class-dump导出.app文件的头文件
- ida解析mach-o文件
- 对设备端口转发通过USB连接SSH
寻找切入点
要做到这个功能,必须要先找到切入点,这对我来说是最难的步骤。不过“撤回”是有提示的,有提示就好办,先看看它提示什么
所以可以根据这个文字去ida中搜索
静态分析
再等待了漫长的两个小时以及月亮快起来以后,ida终于解析完。打开string window
右键选择setup,增加Unicode的支持
就可以搜索中文了,搜索“对方撤回”后,双击地址,点击X键以后可以看到交叉引用的情况:
只有两个方法:
-[QQMessageRecallModule getRecallMessageContent:bindUin:]
-[RecallC2CBaseProcessor getRecallMessageContent:]
所以取得ida中的文件偏移,对这两个方法下断点就能知道哪个方法处理的撤回提示功能
动态调试
先拿到ASLR偏移量,这里有个神奇的地方,这个应用的主程序实际上是Frameworks/QQMainProject.framework/QQMainProject,拿错地址会导致设置断点时报错:error: 0 sending the breakpoint request
(lldb) image list -o -f
[ 0] 0x0000000000dd0000 /var/containers/Bundle/Application/199B45A3-9F43-4154-9338-0F59EDAF6FF5/QQ.app/QQ(0x0000000100dd0000)
[ 1] 0x000000010100c000 /Library/Caches/cy-yEWays.dylib(0x000000010100c000)
[ 2] 0x0000000100e14000 /Library/MobileSubstrate/MobileSubstrate.dylib(0x0000000100e14000)
[ 3] 0x00000001010cc000 /private/var/containers/Bundle/Application/199B45A3-9F43-4154-9338-0F59EDAF6FF5/QQ.app/Frameworks/QQMainProject.framework/QQMainProject(0x00000001010cc000)
。。。。。。
然后设置断点,发送一条消息,撤回试试
(lldb) br s -a '0x1010cc000+0x1EAC7A8'
Breakpoint 1: where = QQMainProject`___lldb_unnamed_symbol208484$$QQMainProject, address = 0x0000000102f787a8
(lldb) br s -a '0x1010cc000+0x4D0A858'
Breakpoint 2: where = QQMainProject`___lldb_unnamed_symbol424456$$QQMainProject, address = 0x0000000105dd6858
(lldb) c
Process 3776 resuming
Process 3776 stopped
* thread #10, name = 'DefaultNet', stop reason = breakpoint 2.1
frame #0: 0x0000000105dd6858 QQMainProject`___lldb_unnamed_symbol424456$$QQMainProject
QQMainProject`___lldb_unnamed_symbol424456$$QQMainProject:
-> 0x105dd6858 <+0>: stp x20, x19, [sp, #-0x20]!
0x105dd685c <+4>: stp x29, x30, [sp, #0x10]
0x105dd6860 <+8>: add x29, sp, #0x10 ; =0x10
0x105dd6864 <+12>: mov x19, x2
0x105dd6868 <+16>: bl 0x101101764 ; ___lldb_unnamed_symbol949$$QQMainProject
0x105dd686c <+20>: adrp x8, 10181
0x105dd6870 <+24>: ldr x1, [x8, #0x970]
0x105dd6874 <+28>: bl 0x106a22700 ; symbol stub for: objc_msgSend
Target 0: (QQ) stopped.
可以看到第二个断点被触发,也就得到-[RecallC2CBaseProcessor getRecallMessageContent:]这个方法是处理撤回提示的。再通过po看到当前对象的类是RecallFriendProcessor,它实际上是RecallC2CBaseProcessor的子类,所以断点并没有下错。这里x0寄存器里面保存了被调用的类,x1保存了被调用的方法,x2是第一个参数,然而我看不懂这个参数是啥,再看看调用栈好了
(lldb) po $x0
<RecallFriendProcessor: 0x2829618a0>
(lldb) x/s $x1
0x108fa53b9: "getRecallMessageContent:"
(lldb) po $x2
10753840768
(lldb) bt
* thread #10, name = 'DefaultNet', stop reason = breakpoint 2.1
* frame #0: 0x0000000105dd6858 QQMainProject`___lldb_unnamed_symbol424456$$QQMainProject
frame #1: 0x0000000105dd64d0 QQMainProject`___lldb_unnamed_symbol424454$$QQMainProject + 1816
frame #2: 0x0000000102b870f4 QQMainProject`___lldb_unnamed_symbol190007$$QQMainProject + 88
frame #3: 0x0000000102b86c08 QQMainProject`___lldb_unnamed_symbol189999$$QQMainProject + 48
frame #4: 0x000000010a5dd87c TlibDy`___lldb_unnamed_symbol119$$TlibDy + 416
frame #5: 0x000000010a5dd6ac TlibDy`___lldb_unnamed_symbol118$$TlibDy + 68
frame #6: 0x000000010a5dd4f4 TlibDy`___lldb_unnamed_symbol117$$TlibDy + 60
frame #7: 0x0000000200456690 Foundation`__NSThreadPerformPerform + 336
frame #8: 0x00000001ff960f1c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
frame #9: 0x00000001ff960e9c CoreFoundation`__CFRunLoopDoSource0 + 88
frame #10: 0x00000001ff960784 CoreFoundation`__CFRunLoopDoSources0 + 176
frame #11: 0x00000001ff95b6c0 CoreFoundation`__CFRunLoopRun + 1004
frame #12: 0x00000001ff95afb4 CoreFoundation`CFRunLoopRunSpecific + 436
frame #13: 0x00000001ff95bd10 CoreFoundation`CFRunLoopRun + 80
frame #14: 0x00000002004564a0 Foundation`__NSThread__start__ + 984
frame #15: 0x00000001ff5ed2c0 libsystem_pthread.dylib`_pthread_body + 128
frame #16: 0x00000001ff5ed220 libsystem_pthread.dylib`_pthread_start + 44
frame #17: 0x00000001ff5f0cdc libsystem_pthread.dylib`thread_start + 4
栈是先进的在下,后进的在上。在getRecallMessageContent方法被执行的前面还有三个方法,它显示为lldb_unnamed_symbol,这是因为没有恢复符号的原因,不碍事。可以依次通过内存地址减去ASLR地址得到方法在ida中的偏移地址,比如0x105dd64d0-0x1010cc000=0x4D0A4D0,在ida中点击G,前往地址4D0A4D0,就可以看到lldb_unnamed_symbol424454方法的名称。得到两个OC方法和一个C函数
最后得到这三个方法,并且顺序是从下到上依次调用
4D0A4D0 -[RecallC2CBaseProcessor solveRecallNotify: isOnline: voipNotifyInfo]
1ABB0F4 -[QQMessageRecallModule handleRecallNotify: isOnline: voipNotifyInfo:]
1ABAC08 sub_1ABABD8
先看一下这个C函数
这里有个交叉引用,实际上sub_1ABABD8是在-[QQMessageRecallModule handleC2CRecallNotify: bufferLen: subcmd: isOnline: voipNotifyInfo:]方法内部被调用的,按F5后,这个c函数内部则是调用的recvC2CRecallNotify: bufferLen:subcmd: isOnline: voipNotifyInfo:方法,它也在QQMessageRecallModule类当中。
![QQ20200502-170055](https://cdn.iosre.com/uploads/default/original/2X/3/37d19932e9f499b898c062338393f44c960f1215.png)
补充
通过相同的步骤,又依次得到了讨论组和群聊的撤回方法,和上述好友间撤回只有名称的不同,并且都在QQMessageRecallModule类当中:
- (void)recvDiscussRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2 isOnline:(_Bool)arg3 voipNotifyInfo:(id)arg4;
- (void)recvGroupRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2 isOnline:(_Bool)arg3 voipNotifyInfo:(id)arg4;
HOOK测试
既然这个sub_1ABABD8是第一个被调用的,hook C函数要写代码太多了,那就先试试hook它里面的recvC2CRecallNotify:bufferLen:subcmd:isOnline:voipNotifyInfo:方法,先试试简单的。
%hook QQMessageRecallModule
//好友间
- (void)recvC2CRecallNotify:(const void *)arg1 bufferLen:(int)arg2
subcmd:(int)arg3 isOnline:(_Bool)arg4 voipNotifyInfo:(id)arg5
{
}
//讨论组
- (void)recvDiscussRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2
isOnline:(_Bool)arg3 voipNotifyInfo:(id)arg4
{
}
//群聊
- (void)recvGroupRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2
isOnline:(_Bool)arg3 voipNotifyInfo:(id)arg4
{
}
%end
执行方法为空就行了,发送一条消息看看,结果是可行的,对方撤回后我还是能看到。这样子三种不同情况的防撤回就都实现了。
存在的问题
当然了,由于我这种纯小白菜鸡的做法,导致了比如撤回后完全没有提示,并不能知道哪些消息是普通消息还是是被撤回的消息。这些问题还需要我自己慢慢去调试解决,比如可以为被撤回的消息改变颜色,或者在其下方增加拦截成功之类的提示