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

说在前面

  • 注意事项:此文仅限于技术交流,请不要做违法的事情。对于那些居心叵测的人根据此文造成违法的事情与本人无关。此文章不得转载!!!如果APP方需要删除,请发邮件xxoo@hotmail.com,谢谢。
  • 推荐先阅读macOS逆向之跳过XtraFinder试用界面,这篇文章各种工具的使用介绍的更详细。
  • 开发环境:macOS11.2.3、Xcode11.7、IDA7.0、class-dump、超级右键2.1.9(写这篇文章时的最新版)
  • 具备技能:X64汇编基础、OC基础知识、iOS逆向基础知识
  • 为什么使用:
    • macOS系统本身并不支持右键新建文件,本人每次通过iTerm2,使用touch命令新建文件,这样就很麻烦,效率很低。
      1
      2
      3
      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的一个成员变量
    1
    __objc2_ivar <offset _OBJC_IVAR_$_AppDelegate_buyProWindow, \ ; NSWindow *buyProWindow;
  • 在IDA里面搜索buyProWindow并没有搜索到,用class-dump导出iRightMouse头文件,找到AppDelegate.h发现有这个成员变量,没有深究原因。分析到这里后,可以确定弹框操作就是在AppDelegate里面执行的。整个弹框过程应该是这样的:当新建某个文件或要把某个目录在VSCode中打开时,会首先判断试用到期了没有,如果到期了,就[self->buyProWindow showMethod]显示弹框。

分析弹框

  • 为了不让购买弹框显示出来,我们需要判断哪里进行了弹框操作。
    1
    2
    3
    4
    5
    if (!isOutOfDate) { // 如果试用没有到期
    // 正常使用,不弹框
    } else {
    // 弹框
    }
  • 显示弹框就是显示一个window,在iOS中是需要调用makeKeyAndVisible方法的,而本人并没有做过macOS开发,不知道在macOS显示window,需要探究一下。
    1
    [self.window makeKeyAndVisible]
  • macOS中不是用UIWindow,而是用NSWindow。使用刚才新建的项目,进入NSWindow.h文件,发现三个与显示window有关的方法。
    1
    2
    3
    makeKeyAndOrderFront:
    makeKeyWindow
    makeMainWindow
  • 使用LLDB连接上iRightMouse,对NSWindow上面的三个方法进行断点,猜想会调用某个方法显示弹窗。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ 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:就是显示弹框的方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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打印调用堆栈。
    1
    2
    3
    4
    5
    6
    (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
    1
    2
    3
    4
    (lldb) image lookup -a 0x00000001026ff9c0
    Address: iRightMouse[0x000000010001f9c0] (iRightMouse.__TEXT.__text + 124128)
    Summary: iRightMouse`___lldb_unnamed_symbol423$$iRightMouse + 135
    (lldb)
  • 进入IDA,输入G,在弹框内输入0x000000010001f9c0,点击确定后,会到以下代码出,这不正是我们需要的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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的符号,所以只能使用地址断点,
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      $ 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,这不正验证了上一步我们的猜想
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      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的效果。

修改汇编代码

  • 定位到000000010001F9A9 cmp byte ptr [rax], 0这行,右键选择Keypatch->Patcher命令,在弹出的对话框中直接修改汇编代码为cmp byte ptr [rax], 1,点击Patcher按钮进行确认。确认后,后面的几次Patch弹框提示全部取消,不要任何修改。
  • 单击Edit->Patch program->Apply patchs to input file,导出修改好的Mach-O文件。
  • 使用修改过的iRightMouse替换/Applications/iRightMouse.app/Contents/MacOS/iRightMouse,记得提前备份一下。用修改过的Mach-o文件替换原来的Mach-O文件破坏了原有的签名,会导致应用打不开或崩溃,需要移除签名才能正常打开:
    sudo codesign --remove-signature iRightMouse
    codesign -d -vv iRightMouse
    iRightMouse: code object is not signed at all
    
  • 退出应用并重新打开,发现使用弹窗没有了,所有功能都正常使用。