macOS逆向之跳过XtraFinder试用界面

说在前面

  • 注意事项:此文仅限于技术交流,请不要做违法的事情。对于那些居心叵测的人根据此文造成违法的事情与本人无关。此文章不得转载!!!如果APP方需要删除,请发邮件zhulongfei28@gmail.com,谢谢。
  • 开发环境:macOS11.2.3、Xcode12.4、IDA7.0、class-dump、XtraFinder1.6.1
  • 具备技能:X64汇编基础、OC基础知识
  • 目标结果:XtraFinder是macOS上超级好用的资源管理器软件,对它爱不释手。一直使用的是无限试用(非付费)版本,每次重启都会有下面这个等待30s的弹框,今天决定去除试用弹框。

分析界面

  • iOS逆向使用Reveal查看界面,macOS逆向直接使用Xcode就可以,不需要使用三方工具,原生的就是最好的。
  • 这里我们不能直接查看XtraFinder界面,XtraFinder是注入到Finder里面的,是一个寄生App,依靠Finder存活的,所以需要查看Finder界面。
  • 新建一个macOS App项目。因为Finder属于macOS App,macOS项目才能调试Finder,如下图:
  • 附加成功后,点击查看视图,分析视图结构。发现XtraFinder属于RegisterWindowController,如图

  • 那么接下来就以RegisterWindowController为突破点,让它不显示出来,又能正常的使用XtraFinder功能。

寻找Mach-O

  • 注意:在使用class-dump导出头文件之前,要执行如下命令,确认没有加壳,要不然导不出头文件。
    1
    otool -l XtraFinderPlugins(XtraFinder) | grep cryptid
  • 按照常识,进入/Applications/XtraFinder.app/Contents/MacOS找到XtraFinder,使用class-dump导出头文件,发现并没有RegisterWindowController.h这个文件,并不是我们需要的Mach-O文件
  • 跟上面同样的方法,继续寻找,发现/Applications/XtraFinder.app/Contents/Resources/XtraFinderPlugins.bundle/Contents/MacOS/XtraFinderPlugins包含RegisterWindowController.h文件,XtraFinderPlugins就是我们需要的Mach-O文件。

汇编基础

  • rdi、rsi、rdx、rcx、r8、r9等寄存器常用于存放函数参数
  • eax、rax常用于函数的返回值
  • rax是64位的寄存器,eax是32位的寄存器,ax是eax的低16位,al是ax的低8位
  • 指令jzJump if Zero的别名,表示如果为0就跳转
  • 指令test用于两个操作数的按位与运算

寻找弹窗方法

  • 上文说过XtraFinder是注入到Finder的,所以相当于直接动态调式Finder
  • 等待连接Finder,-w参数说明要lldb等待应用程序启动
    1
    2
    $ lldb -n Finder -w
    (lldb) process attach --name "Finder" --waitfor
  • 点击访达->XtraFinder->Tools->Restart XtraFinder进行重启,lldb 就会附加到进程上。
    1
    2
    Executable module set to "/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder".
    Architecture set to: x86_64h-apple-macosx-.
  • RegisterWindowController的所有方法下断点,判断哪里进行它的显示
    1
    br set -r '\[RegisterWindowController .*\]'
  • 命中断点后,使用bt查看掉用堆栈,发现-[XtraFinder showRegisterWindow:]决定了它的显示。
    1
    2
    3
    4
    5
    6
    Target 0: (Finder) stopped.
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.8
    * frame #0: 0x00000001213f09b9 XtraFinderPlugins` -[RegisterWindowController setCountdown:]
    frame #1: 0x00000001213f4098 XtraFinderPlugins` -[XtraFinder showRegisterWindow:] + 114
    frame #2: 0x00007fff21303adb Foundation` __NSFireDelayedPerform + 415
  • 根据堆栈可知showRegisterWindow:调用上一级为Foundation模块中的__NSFireDelayedPerform。这里是不能继续往下寻找哪里调用了showRegisterWindow:,我们转到IDA看看有没有线索。

寻找弹框逻辑

  • XtraFinderPlugins拖进去IDA进行分析,分析完成后,搜索showRegisterWindow,得到如图结果:
  • 我们可以很容易发现checkRegistrationAndShowRegisterWindow的方法,猜测这个方法就是检测有没有注册,没有注册就有会有30s的弹框。点击一下这个方法,并Fn+F5一下,得到如下伪代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void __cdecl -[XtraFinder checkRegistrationAndShowRegisterWindow](XtraFinder *self, SEL a2)
    {
    -[XtraFinder checkRegistration](self, "checkRegistration");
    if ( !(unsigned __int8)-[XtraFinder registered](self, "registered") )
    {
    if ( -[XtraFinder daysInUse](self, "daysInUse") )
    objc_msgSend(self, "performSelector:withObject:afterDelay:", "showRegisterWindowWithCountdown", 0LL, 5.0);
    }
    }
  • 通过上面伪代码发现:调用-[XtraFinder registered]检测是否注册,如果没有注册就调用showRegisterWindowWithCountdown,而showRegisterWindowWithCountdown内部调用了showRegisterWindow:进行30s试用弹窗。
  • 鼠标点击到if ( !(unsigned __int8)-[XtraFinder registered](self, "registered") )尾部,按Tab键切换到汇编模式,根据上面的汇编知识可知:
    • 指令test a1, a1运算结果为0的时候进行弹窗,不为0不弹窗。
    • 调用方法-[XtraFinder registered]的返回值存在了al中。
    • 设置al=1,那么test a1, a1就不会为0,不会弹窗
    • 0000000000005F09下断点,改变a1的值为1,验证上面的猜测,
  • 下地址断点必须找到模块偏移量,使用Mach-O里面的地址加上模块偏移地址才能命中断点。因为XtraFinder是Finder启动后注入到里面的,所以并不知道XtraFinderPlugins模块是何时加进去的。这时需要在checkRegistrationAndShowRegisterWindow头部设置断点,打印模块偏移地址,具体操作如下:
    • 使用exit退出当前LLDB,重新附加成功后,执行
      1
      (lldb) b checkRegistrationAndShowRegisterWindow
    • c继续,命中后,查看偏移量
      1
      2
      (lldb) image list -o -f | grep XtraFinderPlugins
      [ 0] 0x0000000123469000 /Applications/XtraFinder.app/Contents/Resources/XtraFinderPlugins.bundle/Contents/MacOS/XtraFinderPlugins
    • 下地址断点
      1
      2
      (lldb) br set -a 0x0000000123469000+0x0000000000005F09
      Breakpoint 2: where = XtraFinderPlugins`-[XtraFinder checkRegistrationAndShowRegisterWindow] + 40, address = 0x000000012346ef09
  • c继续,命中后更改al的值为1,按c继续,这个时候程序正常启动并且没有弹框
    1
    2
    3
    4
    5
    6
    7
    XtraFinderPlugins`-[XtraFinder checkRegistrationAndShowRegisterWindow]:
    -> 0x12346ef09 <+40>: test al, al
    0x12346ef0b <+42>: je 0x12346ef12 ; <+49>

    (lldb) register write al 1
    (lldb) c
    Process 8851 resuming
  • 经过一段猛操作,得出结论修改-[XtraFinder registered]返回值为1,就不会有弹窗

修改返回值

  • 有很多方式可以修改,比如静态注入dylib、直接修改Mach-O汇编代码、注册机等,这里直接修改汇编代码。

安装keypatch

  • IDA没有提供Hopper那样直接修改汇编代码的功能,但是有个keypatch插件可以做到。本人在安装插件过程中还是遇到了一些问题,在这里记录一下
  • 基于Python编写,底层依赖keystone-engine,需要安装sudo pip install keystone-engine
  • 下载https://github.com/keystone-engine/keypatch完成后,将keypatch.py文件放到IDA的插件目录/Applications/IDA 7.0/ida.app/Contents/MacOS/plugins下,关闭IDA重新载入目标程序,这个时候点击要修改的行,右键就会出来Keypatch->Patcher选项
  • 本人不能发现这个选项(IDA7.0,macOS11.2.3),进行了如下操作
    • 通过pip show keystone-engine查看keystone-engine安装路径,发现安装在/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages下面
    • 进入上面的路径拷贝keystone文件夹到/Applications/IDA 7.0/ida.app/Contents/MacOS/python下,关闭IDA重新载入目标程序
    • 在需要修改的代码行右击,就会出来Keypatch->Patcher选项

修改汇编代码

  • 找到-[XtraFinder registered]返回值所在的行,切换到汇编模式。依次选择Keypatch->Patcher命令,在弹出的对话框中直接修改汇编代码为mov eax, 0x1,点击Patcher按钮进行确认。确认后,后面的几次Patch弹框提示全部取消,不要任何修改。

  • 单击Edit->Patch program->Apply patchs to input file,导出修改好的Mach-O文件。

  • 使用修改过的XtraFinderPlugins替换/Applications/XtraFinder.app/Contents/Resources/XtraFinderPlugins.bundle/Contents/MacOS/XtraFinderPlugins,记得提前备份一下。

  • 点击Restart XtraFinder后,发现XtraFinder不能启动了。这是因为程序经过修改后,原来的签名信息验证失败了,程序会错误退出,有两种方式解决

    • 进行重新签名
    • 直接移除签名
  • 简单起见,这里采用移除签名的方式,如下:

    codesign --remove-signature XtraFinderPlugins
    
    codesign -d -vv XtraFinderPlugins
    XtraFinderPlugins: code object is not signed at all
    
  • 重新点击XtraFinder.app打开后,发现使用弹窗没有了,一切正常。