macOS逆向之超级右键无限期试用

语言: CN / TW / HK
  • 注意事项:此文仅限于技术交流,请不要做违法的事情。对于那些居心叵测的人根据此文造成违法的事情与本人无关。此文章不得转载!!!如果APP方需要删除,请发邮件 [email protected] ,谢谢。
  • 推荐先阅读 macOS逆向之跳过XtraFinder试用界面 ,这篇文章各种工具的使用介绍的更详细。
  • 开发环境:macOS11.2.3、Xcode11.7、IDA7.0、class-dump、超级右键2.1.9(写这篇文章时的最新版)
  • 具备技能:X64汇编基础、OC基础知识、iOS逆向基础知识
  • 为什么使用:
    • macOS系统本身并不支持右键新建文件,本人每次通过 iTerm2 ,使用 touch 命令新建文件,这样就很麻烦,效率很低。
      cd 目标目录
      touch test.txt
      touch test.md
      
    • 如果要用 VSCode 打开一个文件夹,需要首先打开 VSCode ,然后再从里面选取对应的文件夹。一两次操作还行,次数多了,真的是浪费时间。
    • 超级右键是macOS上最强大的右键菜单管理工具,拥有丰富的功能,可以提升使用体验。针对上面两个问题,超级右键一键解决,看下图是不是很爽。
  • 目标结果:使用了一段时间,正爽的时候,突然给你来个弹框,提示你试用到期了,让你付费使用。作为一个技术,难道还能被技术难住了,于是走向了逆向之路。

分析界面

  • 新建一个macOS App项目,用来调试超级右键界面。如下图所示,发现购买弹框是一个 NSWindow 类对象,名字叫 BuyWindows
  • /Applications/iRightMouse.app/Contents/MacOS 拷贝出二进制文件 iRightMouse ,拖到IDA里面进行分析。看来已经适配了 M1 机型,本人还是老式处理器,所以直接选择 x86_64 架构进行分析。
  • 分析完毕后,打开 Strings window 窗口,搜索 buy 时,已经出现了如下结果
  • 双击 buyProWindow 进去后,使用 x 查看引用关系,发现购买窗口其实是 AppDelegate 的一个成员变量
    __objc2_ivar <offset _OBJC_IVAR_$_AppDelegate_buyProWindow, \ ; NSWindow *buyProWindow;
    
  • 在IDA里面搜索 buyProWindow 并没有搜索到,用 class-dump 导出 iRightMouse 头文件,找到 AppDelegate.h 发现有这个成员变量,没有深究原因。分析到这里后,可以确定弹框操作就是在 AppDelegate 里面执行的。整个弹框过程应该是这样的:当新建某个文件或要把某个目录在 VSCode 中打开时,会首先判断试用到期了没有,如果到期了,就 [self->buyProWindow showMethod] 显示弹框。

分析弹框

  • 为了不让购买弹框显示出来,我们需要判断哪里进行了弹框操作。
    if (!isOutOfDate) { // 如果试用没有到期
      // 正常使用,不弹框
    } else {
        // 弹框
    }
    
  • 显示弹框就是显示一个window,在iOS中是需要调用 makeKeyAndVisible 方法的,而本人并没有做过macOS开发,不知道在macOS显示window,需要探究一下。
    [self.window makeKeyAndVisible]
    
  • macOS中不是用 UIWindow ,而是用 NSWindow 。使用刚才新建的项目,进入 NSWindow.h 文件,发现三个与显示window有关的方法。
    makeKeyAndOrderFront:
    makeKeyWindow
    makeMainWindow
    
  • 使用LLDB连接上iRightMouse,对 NSWindow 上面的三个方法进行断点,猜想会调用某个方法显示弹窗。
    $ lldb
    (lldb) attach -n iRightMouse
    ...
    (lldb) br set -n makeKeyWindow
    Breakpoint 2: 5 locations.
    (lldb) br set -n makeMainWindow
    Breakpoint 4: 3 locations.
    (lldb) br set -n makeKeyAndOrderFront:
    Breakpoint 5: 6 locations.
    
  • 随意进入一个目录,然后在目录里面 右键 -> 进入 VSCode新建文件 ,这个时候会命中断点。按 c 后,购买弹窗就会显示出来,说明 makeKeyAndOrderFront: 就是显示弹框的方法。
    Process 11199 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
        frame #0: 0x00007fff22f7c275 AppKit` -[NSWindow makeKeyAndOrderFront:]
    AppKit`-[NSWindow makeKeyAndOrderFront:]:
    ->  0x7fff22f7c275 <+0>:  push   rbp
        0x7fff22f7c276 <+1>:  mov    rbp, rsp
        0x7fff22f7c279 <+4>:  push   r14
        0x7fff22f7c27b <+6>:  push   rbx
        0x7fff22f7c27c <+7>:  mov    rbx, rdi
        0x7fff22f7c27f <+10>: mov    rsi, qword ptr [rip + 0x65cddfca] ; "_resolveAutomaticEnterFullScreenFlags"
        0x7fff22f7c286 <+17>: mov    r14, qword ptr [rip + 0x5d7b1d53] ; (void *)0x00007fff203c5d00: objc_msgSend
        0x7fff22f7c28d <+24>: call   r14
    Target 0: (iRightMouse) stopped.
    (lldb)
    
  • 重复上面的步骤,当命中断点后,执行 bt 打印调用堆栈。
    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x00007fff22f7c275 AppKit` -[NSWindow makeKeyAndOrderFront:]
        frame #1: 0x00000001026ff9c0 iRightMouse` ___lldb_unnamed_symbol423$$iRightMouse  + 135
     ...
    (lldb)
    
  • 很明显在 0x00000001026ff9c0 调用了显示弹框的方法,这个地址是在内存中的地址,我们需要的是在Mach-O中的地址,使用如下命令打印出我们需要的地址 0x000000010001f9c0
    (lldb) image lookup -a 0x00000001026ff9c0
        Address: iRightMouse[0x000000010001f9c0] (iRightMouse.__TEXT.__text + 124128)
        Summary: iRightMouse`___lldb_unnamed_symbol423$$iRightMouse + 135
    (lldb)
    
  • 进入IDA,输入 G ,在弹框内输入 0x000000010001f9c0 ,点击确定后,会到以下代码出,这不正是我们需要的。
    LABEL_11:
      v14 = v8;
      v15 = 0LL;
      goto LABEL_12;
    }
    objc_msgSend((void *)self->buyProWindow, "makeKeyAndOrderFront:", self);
    objc_msgSend(NSApp, "activateIgnoringOtherApps:", 1LL);
    v9 = 0;
    LABEL_14:
      objc_release(v8);
      return v9;
    }
    
  • 上面显示的是伪代码界面,需要按空格键切换到流程图界面,方便定位哪里进行了 判断是否过期 的操作。沿着上一步 弹窗代码 一直往上找,直到遇到某个判断跳转方法的分支就停下来,最终会来到如下图所示的代码处。如果执行左边红线的代码,就会到上一步显示弹框的代码处;如果执行右边绿线的代码,就会直接执行相应的操作。
  • 可以很明显发现弹窗操作是在 -[AppDelegate application:openFile:] 方法中进行的,这不正印证了上面所说的弹窗操作是在 AppDelegate 里面执行的

汇编代码分析

  • 上图汇编指令解析
    • 指令 cmp 的意思是对两个数执行减法。当运算结果为0时,把 ZF (零标志位)置1,否则置0。
    • 指令 jz 的意思是根据 cmp 运算结果的值,判断执行的逻辑。如果结果为0就执行 short loc_10001F9E0 ,否则执行弹框操作。
    • 指令 lea 是地址赋值的意思,就是把 byte_100038940 这个地址值赋值给 rax .
  • 经过上面几步的分析,我们已经知道了目前是走左边红色线条的逻辑(也就是说会进行购买弹框操作),可以得出结论:
    • 此时的 cmp byte ptr [rax], 0 运算结果不为0
    • 此时的 byte ptr [rax] 不为0,也就是说 [rax] 不为0。
  • 我们可以使用LLDB在地址 000000010001F9A9 打断点验证一下上面的猜想
    • 因为并没有恢复Mach-O的符号,所以只能使用地址断点,
      $ lldb
      (lldb) attach -n iRightMouse
      Executable module set to "/Applications/iRightMouse.app/Contents/MacOS/iRightMouse".
      ...省略很多...
      Architecture set to: x86_64h-apple-macosx-.
      (lldb) image list -o -f | grep iRightMouse
      [  0] 0x0000000004560000 /Applications/iRightMouse.app/Contents/MacOS/iRightMouse
      (lldb) br set -a 0x0000000004560000+0x000000010001F9A9
      Breakpoint 1: where = iRightMouse`___lldb_unnamed_symbol423$$iRightMouse + 112, address = 0x000000010457f9a9
      (lldb) c
      
    • 当执行相应的操作后,会命中断点,然后打印 [rax] 的值,发现此时的值为1,这不正验证了上一步我们的猜想
      Process 50188 stopped
      * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
          frame #0: 0x000000010457f9a9 iRightMouse` ___lldb_unnamed_symbol423$$iRightMouse  + 112
      iRightMouse`___lldb_unnamed_symbol423$$iRightMouse:
      ->  0x10457f9a9 <+112>: cmp    byte ptr [rax], 0x0
          0x10457f9ac <+115>: je     0x10457f9e0               ; <+167>
          0x10457f9ae <+117>: mov    rdi, qword ptr [r12 + 0x8]
          0x10457f9b3 <+122>: mov    rsi, qword ptr [rip + 0x1b15e] ; "makeKeyAndOrderFront:"
          0x10457f9ba <+129>: mov    rdx, r12
          0x10457f9bd <+132>: call   r13
          0x10457f9c0 <+135>: mov    rax, qword ptr [rip + 0x10651] ; (void *)0x00007fff88569260: NSApp
          0x10457f9c7 <+142>: mov    rdi, qword ptr [rax]
      Target 0: (iRightMouse) stopped.
      (lldb) memory read $rax
      0x10459b940: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
      0x10459b950: 65 df 58 04 01 00 00 00 88 4c 59 04 01 00 00 00  e�X......LY.....
      (lldb)
      
  • 怎样让代码走右边绿色线条的逻辑(也就是说不会出现购买弹框,直接进行相应的操作)呢?上面已经分析了很多,到这里已经很简单了,让 cmp byte ptr [rax], 0 运算结果为0就可以了。从上面可以知道此时 byte ptr [rax] 的值为1,所以把 cmp byte ptr [rax], 0 中0更改为1,就能都达到让整个运算结尾为0的效果。