尝试对某应用实现防撤回功能的过程

准备工作

  • frida-ios-dump对应用进行脱壳
  • class-dump导出.app文件的头文件
  • ida解析mach-o文件
  • 对设备端口转发通过USB连接SSH

寻找切入点

要做到这个功能,必须要先找到切入点,这对我来说是最难的步骤。不过“撤回”是有提示的,有提示就好办,先看看它提示什么
QQ20200502-160557
所以可以根据这个文字去ida中搜索

静态分析

再等待了漫长的两个小时以及月亮快起来以后,ida终于解析完。打开string window
QQ20200502-161210
右键选择setup,增加Unicode的支持QQ20200501-215330 QQ20200501-215259
就可以搜索中文了,搜索“对方撤回”后,双击地址,点击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

补充

通过相同的步骤,又依次得到了讨论组和群聊的撤回方法,和上述好友间撤回只有名称的不同,并且都在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

执行方法为空就行了,发送一条消息看看,结果是可行的,对方撤回后我还是能看到。这样子三种不同情况的防撤回就都实现了。


存在的问题

当然了,由于我这种纯小白菜鸡的做法,导致了比如撤回后完全没有提示,并不能知道哪些消息是普通消息还是是被撤回的消息。这些问题还需要我自己慢慢去调试解决,比如可以为被撤回的消息改变颜色,或者在其下方增加拦截成功之类的提示

4 Likes

这个是不是应该放到干货区?

好的,已经修改

搜寻交叉引用很巧妙!

0x01

/QQMainProject(0x0000000100e08000)

0x02

br s -a '0x0000000100e08000+0x1e66284'
Breakpoint 1: where = QQMainProject`___lldb_unnamed_symbol207354$$QQMainProject + 64, address = 0x0000000102c6e284
(lldb) br s -a '0x0000000100e08000+0x1eac7ec'
Breakpoint 2: where = QQMainProject`___lldb_unnamed_symbol208484$$QQMainProject + 68, address = 0x0000000102cb47ec
(lldb) br s -a '0x0000000100e08000+0x1eac7fc'
Breakpoint 3: where = QQMainProject`___lldb_unnamed_symbol208484$$QQMainProject + 84, address = 0x0000000102cb47fc
(lldb) c
Process 13842 resuming

按你的方法,撤回后断点不会触发,能帮忙看看啥原因吗?

是不是断点下错了?我看到symbol208484对应的是-[QQMessageRecallModule getRecallMessageContent:bindUin:],这个方法在1对1好友间的撤回时并不会调用。

的确是断点下错了,新版的多了好几个方法。:smile:

我的就是最新版啊,8.3.5.612
你就根据显示的那几个交叉引用下断点就行