前言
在玩了iOS逆向之后,看到Mac上面的应用莫名有了一种想要搞事情的冲动。其实在思想上iOS逆向和Mac逆向是差不多的,原理都差不多,走的流程可能不太一样,这篇文章的目的主要是让大家了解一下Mac上面玩逆向的大致流程和分析方法,所以在本文中去实现什么样的功能并不是重点,重点是让大家懂得怎么去分析找到实现。
目的
既然是逆向分析,总的有个目的,有目的才有动力,那么就以QQ撤回这个功能为例吧,网上也有很多文章讲解应该hook哪个函数,具体的分析过程却不是那么清晰,所以这里还是着重分析的过程,最终的实现大家自由发挥。
分析思路
在iOS逆向分析中,大致的思想是这样的(不同情况略有差异):
1 | 界面分析 - > 动态分析 - > 静态分析 - > 动态库注入 - > ...
|
那么在Mac逆向过程中,也可以按照这个套路来搞一波。
界面分析
要分析QQ撤回的动作,这个动作是有在界面上面体现的,别人撤回一条消息后,那条消息会变成被撤回的通知。所以先分析界面上负责处理该状态的类,然后再去看类的调用。
在Mac上面也有类似iOS Reveal
的工具叫做Interface Inspector。
下载下来后打开,发现是一个需要License
的货,既然遇到了那就搞一搞吧。
首先从界面关键词Register
入手吧,拖到hopper
,在Strings
里面搜索Register
。
找到之后按x
查找引用关系,发现是在-[SMEnterLicenseViewController loadView]
这个函数里面存在这个字符串。
继续找到action
为-[SMEnterLicenseViewController register:]
。
里面会调用[r14 enterLicenseViewControllerDidSelectRegister:r15 withLicenseName:r12 code:rbx];
这里registerLicenseWithName:code:
里面是通过[SMLicenseManager verifyLicenseWithName:code:]
验证的。
那直接修改代码返回YES
。
Modify -> Assemble Instruction...
输入:
1 2 | mov eax, 0x1 / / eax用来保存返回值
ret
|
生成一个新的可执行文件,File -> Produce New Executable...
。
选择Remove Signature
。
替换原可执行文件,然后打开,弹出了:
看来它还验证签名了。
那么找到启动时的回调:
1 | - [SMAppDelegate applicationWillFinishLaunching:]
|
可以找到对应的代码。
1 | if ([rax codeSignState] ! = 0x2 ) goto loc_100024851;
|
如果codeSignState
返回不为2就会弹出刚刚那个。
所以这里把:
对应的二进制0F 85 CD 03 00 00
改成0F 84 CD 03 00 00
。
重新打开不显示那个签名问题,但是直接闪退了。
猜测某些地方还有检测,然后exit
或者terminate
了。
在Strings
中查找字符串找到terminate:
的引用。
查看每一处调用,发现这么一段代码:
1 2 3 4 5 6 | void ___24 + [SMLicenseManager load]_block_invoke(void * _block) {
if ([SMLicenseManager verifyLicenseWithName:@ "Test User" code:@ "GAWAE-8C69D-7LZ5H-9D8M3-HVEG7-KHNQC-CQ7RF-SEPQC-CRF82-G47U5-H6DKAB-8SKA7-EWSCM-7Q7SV-MYF4" ] ! = 0x0 ) {
[ * _NSApp terminate: 0x0 ];
}
return ;
}
|
作者自己拿了假的License
测试,如果通过就直接退出了。
所以这里je loc_10010ec26
对应二进制74 1A
改成75 1A
。
然后重新生成可执行文件,打开随便填入License
注册即可。
额,好像有点扯远了,不过本文的目的就是为了学习Mac逆向的分析过程,所以这一部分并不是多余的。
当然这里还有坑,如何你发现attach process
的时候报错的话,请参考Fix Bug for Interface Inspector on macOS Serria这篇文章重新编译mach_inject_bundle_stub.bundle
。
终于准备好了,附加到QQ,选中聊天界面。可以找到MQAIOChatViewController
这样的一个ViewController
。
下面通过动态跟踪函数调用来看看这个类的调用流程。
动态跟踪
动态跟踪的方法有多种,除了iOS逆向中使用的Frida, lldb , Mac上面还可以使用Dtrace。
Frida
Frida
是一个通过js
和Native
交互的跨平台工具,可以通过frida QQ
的方式附加,查看当前加载的类,类的方法等等。除此外还可以加载自己的js
脚本frida QQ -l trace.js
。
在python
中,有可以import frida
来获取设置以及目标等相关信息,同时也能调用js
并获取返回结果。
1 | ObjC.classes.MQAIOChatViewController.$methods
|
frida
还提供了一个非常有用的功能,可以用于此处,也就是frida-trace
。
可以通过这个工具来跟踪指定方法或者指定类的调用过程,这里感兴趣的是MQAIOChatViewController
这个类的调用过程,所以可以通过如下命令来跟踪:
1 | frida - trace - m "-[MQAIOChatViewController *]" QQ
|
然后撤回一条消息可以看到如下调用过程:
1 2 3 4 5 6 7 8 9 | 25623 ms - [MQAIOChatViewController revokeMessages: 0x618000206220 ]
25623 ms | - [MQAIOChatViewController topMsgListViewController]
25623 ms | - [MQAIOChatViewController topMsgListViewController]
25625 ms | - [MQAIOChatViewController scrollViewDidScrollToBottom: 0x7fc28b0dffb0 ]
25632 ms | - [MQAIOChatViewController scheduleRecognitionLogic]
25633 ms | - [MQAIOChatViewController topMsgListViewController]
25639 ms - [MQAIOChatViewController isSimplestModel]
25639 ms - [MQAIOChatViewController isSimplestModel]
25639 ms - [MQAIOChatViewController isSimplestModel]
|
这便找到了一个上层撤回的调用,后面还会继续深入。
Dtrace
讲lldb
前,先讲下Dtrace
这个Linux上的动态追踪神器,可以通过它来监控应用程序或者内核的调用,所以这里可以用于监控OC函数的调用。
新建文件trace.d
,写入内容:
1 2 3 4 5 | objc$target:MQAIOChatViewController::entry{
}
|
然后找到QQ
的进程id ps -e | grep QQ
。再运行dtrace
脚本
撤回消息可得到如下输出结果:
1 2 3 4 5 6 7 8 9 | 2 30436 - revokeMessages::entry
2 44561 - topMsgListViewController:entry
2 44561 - topMsgListViewController:entry
2 30382 - scrollViewDidScrollToBottom::entry
2 44569 - scheduleRecognitionLogic:entry
2 44561 - topMsgListViewController:entry
6 44608 - isSimplestModel:entry
6 44608 - isSimplestModel:entry
0 44608 - isSimplestModel:entry
|
也可以直接运行:
1 | sudo dtrace - n 'objc$target:MQAIOChatViewController::entry{}' - p 24105
|
只是在文件里面可以进行更多的操作。
lldb
lldb
这部分的动态跟踪在后面Xcode
调试会有讲到。
Xcode
找到了函数之后,可以写个动态库注入为了方便调试以及动态库注入这里创建一个dylib
的Xcode项目,选择macOS Library
,点击创建,Type选择Dynamic
。
为了Hook
函数,先选择一个现成的库来做,可以选择:
这里你可能会想一个撤回一条命令一个文件哪有这么麻烦,而这些都是一般逆向中可能用到的,不针对功能。
我个人倾向于使用substitute
,它有着和substrate
一样的api接口。
那么首先要编译一个Mac
平台的substitute
动态库。
直接编译会报错syscall
被弃用,到这里下载一个早一点的比如MacOSX10.11.sdk
,然后放到/Applications/XCode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
下。
然后通过如下命令行编译:
1 | . / configure - - xcode - sdk macosx10. 11 && make - j8
|
在out
目录就可以看到生成的动态库。
查看动态库的加载地址,把其重命名为libsubstitute.0.dylib
并拷贝到/usr/local/lib/
。
1 2 3 | ➜ out git:(master) ✗ otool - L libsubstitute.dylib
libsubstitute.dylib:
/ usr / local / lib / libsubstitute. 0.dylib (compatibility version 0.0 . 0 , current version 0.0 . 0 )
|
然后把头文件和动态库引入Xcode项目, 然后在构造函数里面hook-[MQAIOChatViewController revokeMessages:]
。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @class MQAIOChatViewController;
static void ( * origin_MQAIOChatViewController_revokeMessages)(MQAIOChatViewController * ,SEL, id );
static void new_MQAIOChatViewController_revokeMessages(MQAIOChatViewController * self ,SEL _cmd, id arrays){
NSLog(@ "revokeMessages:%@" ,arrays);
return ;
}
static void __attribute__((constructor)) initialize(void) {
MSHookMessageEx(objc_getClass( "MQAIOChatViewController" ), @selector(revokeMessages:), (IMP)&new_MQAIOChatViewController_revokeMessages, (IMP * )&origin_MQAIOChatViewController_revokeMessages);
}
|
编译生成动态库libMacQQRevoke.dylib
。
动态库注入
动态库注入可以通过DYLD_INSERT_LIBRARIES
注入,也可以直接注入到可执行文件的Load Command
。
这里为了方便就直接通过环境变量注入了。
点击Build Phases
左上角的+
,然后点击New Run Script Phase
。
写入脚本:
1 2 | cd ${TARGET_BUILD_DIR}
export DYLD_INSERT_LIBRARIES = . / libMacQQRevoke.dylib && / Applications / QQ.app / Contents / MacOS / QQ
|
Command + B
, 注入动态库,运行QQ。
然后撤回消息,发现消息还在,并且控制台有如下输出。
也就是Hook生效了,但是重启QQ后,那条消息还是变成了撤回状态。
这个时候需要继续往上层调用去探索。
符号还原+Xcode调试
为了能看到revokeMessages:
被调用的堆栈,先使用restore-symbol还原符号,然后再使用unsign去掉签名。
1 2 3 4 | . / restore - symbol QQ - o QQ_symbol
mv QQ_symbol QQ
. / unsign QQ
mv QQ.unsigned QQ
|
替换原可执行文件之后,在new_MQAIOChatViewController_revokeMessages
断点,Command + B
重新运行。
然后选择Debug -> Attach to Process
。
附加到QQ进程。
撤回消息,Xcode便断在了new_MQAIOChatViewController_revokeMessages
。
查看堆栈如下:
因为block的符号没恢复,所以看来是block里面调用过来的。
找到block内存地址0x103a69663
减去加载基地址0x0000000003757000
,对应到文件偏移0x0000000100312663
。
Hopper
加载文件按g
跳转到指定地址:
所以可以找到是从:
1 2 | - [BHMessageChatModel revokeMessageModel:]
- [MQAIOChatViewController chatMessageModel]
|
调过来的。
在revokeMessageModel
下断点b -[BHMessageChatModel revokeMessageModel:]
,再撤回,可以看到调用堆栈。
这下应该就比较清楚了。
那么就在比较上层的调用中去处理,比如:
1 2 | - [RecallProcessor solveRecallNotify:isOnline:]
- [QQMessageRevokeEngine handleRecallNotify:isOnline:]
|
测试直接hook-[QQMessageRevokeEngine handleRecallNotify:isOnline:]
是不会再本地删除的。
关于hook中的参数类型可以使用class-dump dump出头文件,从头文件可以看到第一个参数是struct RecallModel
类型。
lldb
补充一下上面说的lldb跟踪程序,有了符号直接可以直接通过符号断点来跟踪程序。比如:
1 2 3 4 5 6 | rb \[MQAIOChatViewController * \]
br com add 1
po $rdi
x / s $rsi
c
DONE
|
撤回消息即可看到打印结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10fa1f89e : "revokeMessages:"
c
Process 35302 resuming
Command
po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10fa07e45 : "topMsgListViewController"
c
Process 35302 resuming
Command
po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10fa07e45 : "topMsgListViewController"
c
Process 35302 resuming
Command
po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10fa1f973 : "scheduleRecognitionLogic"
c
Process 35302 resuming
Command
po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10fa07e45 : "topMsgListViewController"
c
Process 35302 resuming
Command
po $rdi
<MQAIOChatViewController: 0x7fca3a8def10 >
x / s $rsi
0x10f9e881c : "isSimplestModel"
c
Process 35302 resuming
Command
|
在x64汇编中,传递参数依次通过以下寄存器传递。
1 | rdi, rsi, rdx, rcx, r8, r9
|
在很多动态分析的过程中可以借助lldb调试来帮助分析解决问题。
MachO注入
为了避免每次从环境变量注入,还可以直接使用insert_dylib注入可执行文件。
1 2 | . / insert_dylib - - weak / path / to / dylib QQ QQ_patch
mv QQ_patch QQ
|
总结
总的来说,Mac上的逆向和iOS逆向差不太多,反而Mac上面操作更方便一些,不用连着手机,还有像Dtrace
这样的神器。
这篇文章主要还是领大家走进Mac逆向的世界,其它靠自己慢慢去探索~
相关工具和代码尽在github
https://github.com/AloneMonkey/MacReverse
几个月前的文章了~ (
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2023-4-7 10:18
被kanxue编辑
,原因: