2017年4月9日星期日

OS X 逆向实例(二)- BetterZip 3.1.2

一直在用 BetterZip,之前是找了一个网上的 License 最近突然不能用了,感觉可能是 BetterZip 升级到 3.x 之后之前的 License 过期 ✊。过期的 BetterZip 有几个不方便的地方:

1. 不可以单个文件拖入/拖出压缩文件夹
2. 不可以保存修改过的压缩文件夹
3. 每次打开会弹框,提示需要更新

之前一直在用 Hopper 静态分析 MachO 感觉不爽的地方很多 😥 也不知道是不是用的有问题,最后还是操起 IDA,MachO 拖进去,静态分析的时候有一个需要注意的问题,因为 IDA 还原出的函数很多很多,需要确定一下哪些函数是 Crack Registration 需要注意的。

直接点开过期的 BetterZip 有一个这样的框,一堆提示,直接到 IDA 里面找上面截取的部分字符串,我这里选的是 "trial period"。这个东西吧这玩意平常看到真的是很烦,不过倒是挺好用的来做入口的 ~
搜到了,然后通过 DATA XREF 找到定义字符串的位置 cfstr_YourTrialPeriod,然后通过 cfstr_YourTrialPeriod 再找到函数段的 DATA XREF ~ 比较重要的是选中高亮的部分。
从 DATA XREF :-[RegistrationController testCode]:loc_100039429 可以看到调用位置,跳到 testCode,另外这个时候注意一下 testCode 的 class 为 RegistrationController 这个类名很有诱惑力,如果现在跟进的 testCode 没有收获的话接下来看看这个 class。

下面是 testCode 还原成 C 为代码之后的样子,可以从我打的注释看出来 testCode 大多是一些根据当前注册状态修改提示界面和信息的代码,没有实质性的 Registration 逻辑代码,所以我也没有往下看了,有兴趣的话可以结合注释往下看看。

// RegistrationController - (char)testCode
char __cdecl -[RegistrationController testCode](struct RegistrationController *self, SEL a2)
{
  struct RegistrationController *self_1; // r15@1
  void (*objc_msgSend)(void *, const char *, ...); // r12@1
  void *v4; // rax@1
  __int64 tfCode; // rax@1
  __int64 tfCode_1; // rbx@1
  __int64 NoticeChkReg_1; // rax@1
  struct NSButton *btnContinue; // r14@1
  void *v9; // rax@1
  void *mainBundle; // rax@1
  void *mainBundle_1; // r13@1
  void *v12; // rax@1
  __int64 laterNotice; // rax@1
  __int64 laterNotice_1; // rbx@1
  __int64 laterNotice_2; // rdi@1
  void (__fastcall *objc_release_1)(void *); // rbx@1
  struct NSButton *btnBuy; // r14@2
  void *v18; // rax@2
  void *v19; // rax@2
  void *v20; // r13@2
  void *v21; // rax@2
  __int64 RegistrationNotice; // rax@2
  __int64 RegistrationNotice_1; // rbx@2
  void (__fastcall *objc_release_2)(void *); // r14@2
  struct NSTextField *tfHead; // r13@2
  void *v26; // rax@2
  void (*objc_msgSend_1)(void *, const char *, ...); // rbx@2
  void *mainBundle_2; // rax@2
  void *mainBundle_3; // r12@2
  void (*v30)(void *, const char *, ...); // r15@3
  __int64 v31; // rax@3
  void *v32; // rax@3
  void *v33; // rbx@3
  void (*objc_msgSend_3)(void *, const char *, ...); // r13@3
  void (*objc_msgSend_4)(void *, const char *, ...); // rbx@4
  __int64 NoticeChkReg_len; // rax@4
  __int64 NoticeChkReg_len_1; // ST20_8@4
  struct NSButton *btnBuy_1; // r12@4
  __int64 v39; // rax@4
  __int64 mainBundle_6; // rax@4
  __int64 mainBundle_7; // r13@4
  __int64 v42; // rax@4
  __int64 buyNowNotice; // rax@4
  __int64 buyNowNotice_1; // r14@4
  void (*objc_msgSend_5)(void *, const char *, ...); // r12@4
  __int64 buyNowNotice_2; // rdi@4
  void (__fastcall *objc_release_5)(_QWORD); // r14@4
  char v48; // r14@5
  __int64 v49; // r12@5
  void (*objc_msgSend_2)(void *, const char *, ...); // r15@6
  __int64 v51; // rax@7
  __int64 upgradeNotice; // rax@7
  __int64 upgradeNotice_1; // rbx@7
  struct NSTextField *tfHead_1; // rdi@7
  __int64 v55; // rdi@7
  void (__fastcall *objc_release_3)(void *); // rbx@7
  struct NSTextField *tfHead_2; // r14@8
  __int64 v58; // rax@8
  __int64 v59; // rax@8
  __int64 registeredNotice; // rbx@8
  void (__fastcall *objc_release_4)(void *); // r15@8
  struct NSButton *btnContinue_1; // ST20_8@8
  __int64 v63; // rax@8
  void *mainBundle_4; // rax@8
  void *mainBundle_5; // r12@8
  __int64 v66; // rax@8
  void *continueNotice_1; // rax@8
  void *continueNotice; // rbx@8
  struct RegistrationController *self_3; // r14@10
  int installedDays_1; // ebx@10
  int trialDaysLeft; // er15@10
  __int64 v72; // rax@11
  __int64 v73; // rax@11
  __int64 v74; // ST20_8@11
  __int64 v75; // rax@11
  __int64 Notice_1; // rax@11
  __int64 Notice; // rbx@11
  __int64 v78; // rax@11
  __int64 Notice_2; // rax@11
  __int64 Notice_3; // r12@11
  __int64 Notice_4; // rdi@11
  void (__fastcall *objc_release_6)(_QWORD); // rbx@11
  __int64 v83; // rax@13
  __int64 mainBundle_8; // rax@13
  __int64 mainBundle_9; // rbx@13
  __int64 v86; // rax@13
  __int64 v87; // rax@13
  void *v88; // rax@14
  void *v89; // rax@14
  void *v90; // r13@14
  void *v91; // rax@15
  __int64 v92; // rax@15
  __int64 v93; // rbx@15
  void *v94; // rax@16
  __int64 v95; // rax@16
  __int64 v96; // rax@18
  __int64 tfCode_2; // rax@18
  __int64 tfCode_3; // rbx@18
  __int64 v99; // rax@18
  __int64 v100; // r12@18
  struct NSTextField *v101; // r14@18
  __int64 v102; // rax@19
  __int64 v103; // rax@19
  __int64 v104; // r15@19
  __int64 v105; // rax@19
  __int64 v106; // rax@19
  __int64 v107; // rbx@19
  __int64 v108; // rdi@19
  void (__fastcall *v109)(_QWORD); // rbx@19
  __int64 v110; // rdi@19
  __int64 v111; // rax@20
  __int64 v112; // rax@20
  __int64 v113; // ST08_8@20
  __int64 v114; // rax@20
  __int64 v115; // rax@20
  __int64 v116; // r12@20
  __int64 v117; // rax@20
  __int64 v118; // rax@20
  __int64 v119; // rbx@20
  __int64 v120; // rdi@20
  __int64 v121; // rax@22
  __int64 v122; // rax@22
  __int64 v123; // rbx@22
  __int64 v124; // rdi@22
  void (__fastcall *v125)(_QWORD); // rbx@22
  struct RegistrationController *self_2; // [sp+10h] [bp-50h]@2
  struct RegistrationController *self_2a; // [sp+10h] [bp-50h]@18
  __int64 NoticeChkReg_2; // [sp+18h] [bp-48h]@1
  int registerFlag; // [sp+20h] [bp-40h]@1
  __int64 registerFlaga; // [sp+20h] [bp-40h]@18
  __int64 NoticeChkReg; // [sp+28h] [bp-38h]@1
  int v133; // [sp+34h] [bp-2Ch]@1

  self_1 = self;
  v133 = 0;
  objc_msgSend = *objc_msgSend_ptr;
  v4 = objc_msgSend_ptr(self->tfCode, selRef_stringValue);
  LODWORD(tfCode) = objc_retainAutoreleasedReturnValue(v4);
  tfCode_1 = tfCode;
  NoticeChkReg = 0LL;
  registerFlag = checkRegistration(tfCode, &NoticeChkReg, &v133);// 检查用户 tfCode 是否符合序列号标准,把返回的信息保存到 NoticeChkReg 里, 是否注册保存到 registerFlag
  LODWORD(NoticeChkReg_1) = objc_retain_ptr(NoticeChkReg, &NoticeChkReg);
  NoticeChkReg_2 = NoticeChkReg_1;
  objc_release(tfCode_1);
  objc_msgSend(self->btnBuy, selRef_setHidden_, 0LL);
  objc_msgSend(self->btnLost, selRef_setHidden_, 0LL);
  objc_msgSend(self->btnBuy, selRef_setKeyEquivalent_, &cfstr_Enter);// btnBuy 和回车键绑定
  objc_msgSend(self->btnBuy, selRef_setTag_, 1LL);
  objc_msgSend(self->btnContinue, selRef_setKeyEquivalent_, &cfstr_ESC);// btnContinue 和 ESC 绑定
  btnContinue = self->btnContinue;
  v9 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
  LODWORD(mainBundle) = objc_retainAutoreleasedReturnValue(v9);
  mainBundle_1 = mainBundle;
  v12 = (objc_msgSend)(
          mainBundle,
          selRef_localizedStringForKey_value_table_,
          &cfstr_Later,
          &cfstr_nil,
          &cfstr_Registration); // Registration.string 里 Later 对应的字符串
  LODWORD(laterNotice) = objc_retainAutoreleasedReturnValue(v12);
  laterNotice_1 = laterNotice;
  objc_msgSend(btnContinue, selRef_setTitle_, laterNotice);
  laterNotice_2 = laterNotice_1;
  objc_release_1 = objc_release;
  objc_release(laterNotice_2);
  objc_release_1(mainBundle_1);
  if ( registerFlag )
  {
    btnBuy = self_1->btnBuy;
    v18 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
    LODWORD(v19) = objc_retainAutoreleasedReturnValue(v18);
    v20 = v19;
    v21 = (objc_msgSend)(
            v19,
            selRef_localizedStringForKey_value_table_,// localizedStringForKey:value:table
            &cfstr_UpgradeNow,                  // key="Upgrade Now"
            &cfstr_nil,                         // value=""
            &cfstr_Registration);               // table="Registration"
    LODWORD(RegistrationNotice) = objc_retainAutoreleasedReturnValue(v21);
    RegistrationNotice_1 = RegistrationNotice;
    objc_msgSend(btnBuy, selRef_setTitle_, RegistrationNotice);
    objc_release_2 = objc_release;
    objc_release(RegistrationNotice_1);
    objc_release_2(v20);
    objc_msgSend(self_1->btnBuy, selRef_setTag_, 2LL);
    tfHead = self_1->tfHead;
    self_2 = self_1;
    v26 = (objc_msgSend)(classRef_NSBundle, selRef_mainBundle);
    objc_msgSend_1 = objc_msgSend;
    LODWORD(mainBundle_2) = objc_retainAutoreleasedReturnValue(v26);
    mainBundle_3 = mainBundle_2;
    if ( registerFlag == 9999998 )
    {                                           // License for 2
      v30 = objc_msgSend_1;
      LODWORD(v31) = (objc_msgSend_1)(
                       mainBundle_2,
                       selRef_localizedStringForKey_value_table_,
                       &cfstr_ThisIsALicenseFor2,
                       &cfstr_nil,
                       &cfstr_Registration);
      LODWORD(v32) = objc_retainAutoreleasedReturnValue(v31);
      v33 = v32;
      (v30)(tfHead, selRef_setStringValue_, v32);
      objc_release_2(v33);
      objc_release_2(mainBundle_3);
      objc_msgSend_3 = v30;
      self_1 = self_2;
    }
    else
    {
      objc_msgSend_2 = objc_msgSend_1;
      if ( registerFlag == 9999999 )
      {                                         // License for 2 from App Store
        LODWORD(v51) = (objc_msgSend_1)(
                         mainBundle_2,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_ThisIsALicenseFor2_0,
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(upgradeNotice) = objc_retainAutoreleasedReturnValue(v51);
        upgradeNotice_1 = upgradeNotice;
        tfHead_1 = tfHead;
        objc_msgSend_3 = objc_msgSend_2;
        (objc_msgSend_2)(tfHead_1, selRef_setStringValue_, upgradeNotice);
        v55 = upgradeNotice_1;
        objc_release_3 = objc_release;
        objc_release(v55);
        objc_release_3(mainBundle_3);
        self_1 = self_2;
      }
      else
      {
        tfHead_2 = tfHead;
        objc_msgSend_3 = objc_msgSend_1;
        LODWORD(v58) = (objc_msgSend_1)(
                         mainBundle_2,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_YourCopyOfBett, // "\nYour copy of BetterZip is registered. Thank you!"
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(v59) = objc_retainAutoreleasedReturnValue(v58);
        registeredNotice = v59;
        (objc_msgSend_2)(tfHead_2, selRef_setStringValue_, v59);
        objc_release_4 = objc_release;
        objc_release(registeredNotice);
        objc_release_4(mainBundle_3);
        btnContinue_1 = self_2->btnContinue;
        LODWORD(v63) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(mainBundle_4) = objc_retainAutoreleasedReturnValue(v63);
        mainBundle_5 = mainBundle_4;
        LODWORD(v66) = (objc_msgSend_3)(
                         mainBundle_4,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_Continue,
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(continueNotice_1) = objc_retainAutoreleasedReturnValue(v66);
        continueNotice = continueNotice_1;
        (objc_msgSend_3)(btnContinue_1, selRef_setTitle_, continueNotice_1);
        objc_release_4(continueNotice);
        objc_release_4(mainBundle_5);
        self_1 = self_2;
        (objc_msgSend_3)(self_2->btnContinue, selRef_setTag_, 0LL);
        (objc_msgSend_3)(self_2->btnContinue, selRef_setKeyEquivalent_, &cfstr_Enter);
        (objc_msgSend_3)(self_2->btnBuy, selRef_setKeyEquivalent_, &cfstr_nil);
        (objc_msgSend_3)(self_2->btnBuy, selRef_setHidden_, 1LL);
        (objc_msgSend_3)(self_2->btnLost, selRef_setHidden_, 1LL);
      }
    }
    v49 = NoticeChkReg_2;
    objc_msgSend_ptr(self_1->tfText, selRef_setStringValue_, NoticeChkReg_2);
    v48 = 1;
  }
  else
  {                                             // Licensed
    objc_msgSend_4 = objc_msgSend;
    LODWORD(NoticeChkReg_len) = (objc_msgSend)(NoticeChkReg_2, selRef_length);
    NoticeChkReg_len_1 = NoticeChkReg_len;
    btnBuy_1 = self_1->btnBuy;
    LODWORD(v39) = (objc_msgSend_4)(classRef_NSBundle, selRef_mainBundle);
    LODWORD(mainBundle_6) = objc_retainAutoreleasedReturnValue(v39);
    mainBundle_7 = mainBundle_6;
    LODWORD(v42) = (objc_msgSend_4)(
                     mainBundle_6,
                     selRef_localizedStringForKey_value_table_,
                     &cfstr_BuyNow,
                     &cfstr_nil,
                     &cfstr_Registration);
    LODWORD(buyNowNotice) = objc_retainAutoreleasedReturnValue(v42);
    buyNowNotice_1 = buyNowNotice;
    (objc_msgSend_4)(btnBuy_1, selRef_setTitle_, buyNowNotice);
    objc_msgSend_5 = objc_msgSend_4;
    buyNowNotice_2 = buyNowNotice_1;
    objc_release_5 = objc_release;
    objc_release(buyNowNotice_2);
    objc_release_5(mainBundle_7);
    if ( NoticeChkReg_len_1 )
    {                                           // Registered
      objc_msgSend_ptr(self_1->tfHead, selRef_setStringValue_, NoticeChkReg_2);
      v48 = 0;
      objc_msgSend_3 = objc_msgSend_4;
      v49 = NoticeChkReg_2;
    }
    else
    {                                           // Trial
      self_3 = self_1;
      installedDays_1 = installedDays;
      trialDaysLeft = 30 - installedDays;
      if ( 30 - installedDays < 2 )
      {
        if ( trialDaysLeft == 1 )
        {
          objc_msgSend_3 = objc_msgSend_5;
          LODWORD(v83) = (objc_msgSend_5)(classRef_NSBundle, selRef_mainBundle);
          LODWORD(mainBundle_8) = objc_retainAutoreleasedReturnValue(v83);
          mainBundle_9 = mainBundle_8;
          LODWORD(v86) = (objc_msgSend_5)(
                           mainBundle_8,
                           selRef_localizedStringForKey_value_table_,
                           &cfstr_BetterzipIsN_2,// "BetterZip is not yet registered. Your trial period will end tomorrow
                           &cfstr_nil,
                           &cfstr_Registration);
          LODWORD(v87) = objc_retainAutoreleasedReturnValue(v86);
          Notice_3 = v87;
          objc_release(mainBundle_9);
          trialDaysLeft = 1;
        }
        else
        {
          v88 = objc_msgSend_ptr(classRef_NSBundle, selRef_mainBundle);
          LODWORD(v89) = objc_retainAutoreleasedReturnValue(v88);
          v90 = v89;
          if ( installedDays_1 == 30 )
          {
            v91 = objc_msgSend_ptr(
                    v89,
                    selRef_localizedStringForKey_value_table_,
                    &cfstr_BetterzipIsN_0,      // BetterZip is not yet registered. Your trial period ends today
                    &cfstr_nil,
                    &cfstr_Registration);
            LODWORD(v92) = objc_retainAutoreleasedReturnValue(v91);
            v93 = v92;
            objc_release(v90);
          }
          else
          {
            v94 = objc_msgSend_ptr(
                    v89,
                    selRef_localizedStringForKey_value_table_,
                    &cfstr_YourTrialPerio,      // "Your trial period is over.
                    &cfstr_nil,
                    &cfstr_Registration);
            LODWORD(v95) = objc_retainAutoreleasedReturnValue(v94);
            v93 = v95;
            objc_release(v90);
            trialDaysLeft = 0;
          }
          objc_msgSend_3 = objc_msgSend_5;
          Notice_3 = v93;
        }
      }
      else
      {
        LODWORD(v72) = (objc_msgSend_5)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v73) = objc_retainAutoreleasedReturnValue(v72);
        v74 = v73;
        LODWORD(v75) = (objc_msgSend_5)(
                         v73,
                         selRef_localizedStringForKey_value_table_,
                         &cfstr_BetterzipIsNot, // "BetterZip is not yet registered. Your trial period will end in %d days.
                         &cfstr_nil,
                         &cfstr_Registration);
        LODWORD(Notice_1) = objc_retainAutoreleasedReturnValue(v75);
        Notice = Notice_1;
        objc_msgSend_3 = objc_msgSend_5;
        LODWORD(v78) = (objc_msgSend_5)(classRef_NSString, selRef_stringWithFormat_, Notice_1, trialDaysLeft);
        LODWORD(Notice_2) = objc_retainAutoreleasedReturnValue(v78);
        Notice_3 = Notice_2;
        Notice_4 = Notice;
        objc_release_6 = objc_release;
        objc_release(Notice_4);
        objc_release_6(v74);
      }
      registerFlaga = Notice_3;
      self_2a = self_3;
      (objc_msgSend_3)(self_3->tfHead, selRef_setStringValue_, Notice_3);
      LODWORD(v96) = (objc_msgSend_3)(self_3->tfCode, selRef_stringValue);
      LODWORD(tfCode_2) = objc_retainAutoreleasedReturnValue(v96);
      tfCode_3 = tfCode_2;
      LODWORD(v99) = (objc_msgSend_3)(tfCode_2, selRef_length);
      v100 = v99;
      objc_release(tfCode_3);
      v101 = self_3->tfText;
      if ( v100 )
      {                                         // self.tfCode is not nil
        LODWORD(v102) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v103) = objc_retainAutoreleasedReturnValue(v102);
        v104 = v103;
        LODWORD(v105) = (objc_msgSend_3)(
                          v103,
                          selRef_localizedStringForKey_value_table_,
                          &cfstr_TheCodeIsInval,// "The code is invalid
                          &cfstr_nil,
                          &cfstr_Registration);
        LODWORD(v106) = objc_retainAutoreleasedReturnValue(v105);
        v107 = v106;
        (objc_msgSend_3)(v101, selRef_setStringValue_, v106);
        v108 = v107;
        v109 = objc_release;
        objc_release(v108);
        v110 = v104;
      }
      else
      {
        LODWORD(v111) = (objc_msgSend_3)(classRef_NSBundle, selRef_mainBundle);
        LODWORD(v112) = objc_retainAutoreleasedReturnValue(v111);
        v113 = v112;
        LODWORD(v114) = (objc_msgSend_3)(
                          v112,
                          selRef_localizedStringForKey_value_table_,
                          &cfstr_TrialUserDDays,// Trial user\n%d days left"
                          &cfstr_nil,
                          &cfstr_Registration);
        LODWORD(v115) = objc_retainAutoreleasedReturnValue(v114);
        v116 = v115;
        LODWORD(v117) = (objc_msgSend_3)(classRef_NSString, selRef_stringWithFormat_, v115, trialDaysLeft);
        LODWORD(v118) = objc_retainAutoreleasedReturnValue(v117);
        v119 = v118;
        (objc_msgSend_3)(v101, selRef_setStringValue_, v118);
        v120 = v119;
        v109 = objc_release;
        objc_release(v120);
        v109(v116);
        v110 = v113;
      }
      v109(v110);
      v49 = NoticeChkReg_2;
      objc_release(registerFlaga);
      v48 = 0;
      self_1 = self_2a;
    }
  }
  LODWORD(v121) = (objc_msgSend_3)(classRef_NSNotificationCenter, selRef_defaultCenter);
  LODWORD(v122) = objc_retainAutoreleasedReturnValue(v121);
  v123 = v122;
  (objc_msgSend_3)(v122, selRef_postNotificationName_object_, MIBRegDidChangeNotification, self_1);
  v124 = v123;
  v125 = objc_release;
  objc_release(v124);
  v125(v49);
  return v48;
}
虽然说粗看一遍不关注上面显示提示信息的代码了,不过发现了另外一个比较关键的函数 checkRegistration 点进去看看,可以看到是验证 tfCode 的函数,如下。

__int64 __fastcall checkRegistration(__int64 tfCode, _QWORD *Notice_2, _DWORD *a3)
{
  _DWORD *v3; // r12@1
  void *tfCode_1; // rax@1
  void *tfCode_2; // r15@1
  int result_mid; // er13@1
  BIO *mem_buf_1; // r14@1
  void *(*objc_msgSend)(void *, const char *, ...); // r14@2
  char *codeLength; // rax@2
  void *v10; // rax@2
  void *lastCode; // rax@2
  void *lastCode_1; // rbx@2
  void *v13; // rax@3
  void *tfCodeEndsWithEuqlSign_1; // rax@3
  void *tfCodeEndsWithEuqlSign; // rbx@3
  BIO *mem_buf; // rax@4
  void *v17; // rax@6
  char *v18; // rax@6
  int (__fastcall *objc_msgSend_1)(void *, char *, signed __int64, signed __int64); // r13@8
  void *v20; // rax@8
  void *decrypt_UTF8; // rax@8
  void *decrypt_UTF8_1; // rbx@8
  unsigned __int8 prefixFlag2; // al@8
  int (__fastcall *objc_msgSend_2)(void *, char *, signed __int64, signed __int64); // rbx@8
  __int64 v25; // rax@9
  __int64 decrypt_UTF8_index16; // rax@9
  __int64 decrypt_UTF8_index16_1; // r13@9
  int decrypt_UTF8_index16_intValue; // ebx@9
  int *v29; // rax@9
  unsigned int v30; // ecx@9
  void *(*objc_msgSend_3)(void *, const char *, ...); // r13@13
  void *v32; // rax@13
  void *decrypt_UTF8_index24_1; // rax@13
  void *decrypt_UTF8_index24; // rbx@13
  void *DateFormatter; // rax@13
  void *v36; // rax@13
  void *v37; // r12@13
  void *v38; // rax@13
  void *decrypt_UTF8_index24_dateFormat; // rax@13
  void *v40; // rax@13
  void *dateFormat_2014_05_1; // rax@13
  void *decrypt_UTF8_index41; // rax@13
  __int64 v46; // rax@13
  void *v45; // rax@13
  void *decrypt_UTF8_index35_1; // rax@13
  void *decrypt_UTF8_index35; // rbx@13
  void *v44; // rax@13
  void *mainBundle; // rax@13
  void *v49; // rax@14
  __int64 v50; // rax@14
  __int64 v51; // rbx@14
  __int64 v52; // r9@14
  void *v53; // rax@14
  void *v54; // rax@16
  __int64 v55; // rax@16
  __int64 Notice_1; // rax@17
  __int64 Notice; // rax@17
  void (__fastcall *objc_release_1)(void *); // r12@17
  void (__fastcall *objc_release_2)(void *); // rbx@20
  __int64 result; // rax@26
  void *NSDate_init; // [sp+8h] [bp-1A8h]@13
  void *v62; // [sp+10h] [bp-1A0h]@14
  void *dateFormat_2014_05_2; // [sp+20h] [bp-190h]@13
  void *decrypt_UTF8_index24_dateFormat_1; // [sp+28h] [bp-188h]@13
  __int64 decrypt_UTF8_index41_2; // [sp+30h] [bp-180h]@13
  void *decrypt_UTF8_index24_2; // [sp+38h] [bp-178h]@13
  unsigned __int8 prefixFlag1; // [sp+43h] [bp-16Dh]@8
  int decrypt_UTF8_index16_intValue_1; // [sp+44h] [bp-16Ch]@13
  _DWORD *decrypt_UTF8_index35_intValue; // [sp+48h] [bp-168h]@7
  void *decrypt_UTF8_2; // [sp+50h] [bp-160h]@8
  int len[2]; // [sp+60h] [bp-150h]@4
  RSA *x; // [sp+68h] [bp-148h]@1
  unsigned __int8 *from; // [sp+70h] [bp-140h]@1
  void *base64Buf; // [sp+78h] [bp-138h]@1
  unsigned __int8 decrypt[16]; // [sp+80h] [bp-130h]@7
  __int128 v76; // [sp+90h] [bp-120h]@7
  __int128 v77; // [sp+A0h] [bp-110h]@7
  __int128 v78; // [sp+B0h] [bp-100h]@7
  __int128 v79; // [sp+C0h] [bp-F0h]@7
  __int128 v80; // [sp+D0h] [bp-E0h]@7
  __int128 v81; // [sp+E0h] [bp-D0h]@7
  __int128 v82; // [sp+F0h] [bp-C0h]@7
  __int128 v83; // [sp+100h] [bp-B0h]@7
  __int128 v84; // [sp+110h] [bp-A0h]@7
  __int128 v85; // [sp+120h] [bp-90h]@7
  __int128 v86; // [sp+130h] [bp-80h]@7
  __int128 v87; // [sp+140h] [bp-70h]@7
  __int128 v88; // [sp+150h] [bp-60h]@7
  __int128 v89; // [sp+160h] [bp-50h]@7
  __int128 v90; // [sp+170h] [bp-40h]@7
  __int64 stack_chk_guard; // [sp+180h] [bp-30h]@1

  v3 = a3;
  stack_chk_guard = *__stack_chk_guard_ptr;
  LODWORD(tfCode_1) = objc_retain_ptr(tfCode, Notice_2);
  tfCode_2 = tfCode_1;
  base64Buf = 0LL;
  from = 0LL;
  x = 0LL;
  *v3 = 0;
  objc_retainAutorelease(&cfstr_nil);
  *Notice_2 = &cfstr_nil;
  result_mid = 0;
  mem_buf_1 = 0LL;
  if ( objc_msgSend_ptr(tfCode_2, selRef_length) >= 0x80 )
  {
    objc_msgSend = *objc_msgSend_ptr;
    codeLength = objc_msgSend_ptr(tfCode_2, selRef_length);
    v10 = objc_msgSend(tfCode_2, selRef_substringFromIndex_, codeLength - 1);// tfCode.substring(length - 1)
    LODWORD(lastCode) = objc_retainAutoreleasedReturnValue(v10);
    lastCode_1 = lastCode;
    LOBYTE(objc_msgSend) = objc_msgSend(lastCode, selRef_isEqualTo_, &cfstr_equalSign);
    objc_release(lastCode_1);
    if ( !objc_msgSend )                        // lastCode != "="
    {
      v13 = objc_msgSend_ptr(tfCode_2, selRef_stringByAppendingString_, &cfstr_equalSign);
      LODWORD(tfCodeEndsWithEuqlSign_1) = objc_retainAutoreleasedReturnValue(v13);
      tfCodeEndsWithEuqlSign = tfCodeEndsWithEuqlSign_1;
      objc_release(tfCode_2);
      tfCode_2 = tfCodeEndsWithEuqlSign;
    }
    decodeBase64(b64_key, &base64Buf, len);
    mem_buf = BIO_new_mem_buf(base64Buf, len[0]);
    mem_buf_1 = mem_buf;
    result_mid = 0;
    if ( mem_buf )
    {                                           // RSA Decrypt
      result_mid = 0;
      if ( PEM_read_bio_RSA_PUBKEY(mem_buf, &x, 0LL, 0LL) )
      {
        LODWORD(v17) = objc_retainAutorelease(tfCode_2);
        tfCode_2 = v17;
        v18 = objc_msgSend_ptr(v17, selRef_UTF8String);
        decodeBase64(v18, &from, len);
        if ( *len == 128LL )
        {
          decrypt_UTF8_index35_intValue = v3;
          v90 = 0LL;
          v89 = 0LL;
          v88 = 0LL;
          v87 = 0LL;
          v86 = 0LL;
          v85 = 0LL;
          v84 = 0LL;
          v83 = 0LL;
          v82 = 0LL;
          v81 = 0LL;
          v80 = 0LL;
          v79 = 0LL;
          v78 = 0LL;
          v77 = 0LL;
          v76 = 0LL;
          *decrypt = 0LL;
          if ( RSA_public_decrypt(128, from, decrypt, x, 1) != -1 )
          {
            objc_msgSend_1 = *objc_msgSend_ptr;
            v20 = objc_msgSend_ptr(classRef_NSString, selRef_stringWithUTF8String_, decrypt);
            LODWORD(decrypt_UTF8) = objc_retainAutoreleasedReturnValue(v20);
            decrypt_UTF8_1 = decrypt_UTF8;
            decrypt_UTF8_2 = decrypt_UTF8;
            prefixFlag1 = (objc_msgSend_1)(decrypt_UTF8, selRef_hasPrefix_, &cfstr___mas_betterzp);// __MAS_BetterZp2_
            prefixFlag2 = (objc_msgSend_1)(decrypt_UTF8_1, selRef_hasPrefix_, &cfstr___mib_betterzi);// __MIB_BetterZip_
            objc_msgSend_2 = objc_msgSend_1;
            result_mid = 0;
            if ( prefixFlag1 | prefixFlag2 )
            {
              LODWORD(v25) = objc_msgSend_2(decrypt_UTF8_2, selRef_substringWithRange_, 16LL, 7LL);// range(16, 16+7)
              LODWORD(decrypt_UTF8_index16) = objc_retainAutoreleasedReturnValue(v25);
              decrypt_UTF8_index16_1 = decrypt_UTF8_index16;
              decrypt_UTF8_index16_intValue = (objc_msgSend_2)(decrypt_UTF8_index16, selRef_intValue);
              objc_release(decrypt_UTF8_index16_1);
              v29 = &unk_1000AEE20;
              v30 = 0;
              do
              {
                if ( *v29 > decrypt_UTF8_index16_intValue )
                  break;
                result_mid = 0;
                if ( *v29 == decrypt_UTF8_index16_intValue )
                  goto LABEL_21;
                ++v29;
                ++v30;
              }
              while ( v30 <= 0x2F );
              decrypt_UTF8_index16_intValue_1 = decrypt_UTF8_index16_intValue;
              objc_msgSend_3 = *objc_msgSend_ptr;
              v32 = objc_msgSend_ptr(decrypt_UTF8_2, selRef_substringWithRange_, 24LL, 10LL);
              LODWORD(decrypt_UTF8_index24_1) = objc_retainAutoreleasedReturnValue(v32);
              decrypt_UTF8_index24 = decrypt_UTF8_index24_1;
              decrypt_UTF8_index24_2 = decrypt_UTF8_index24_1;
              DateFormatter = objc_msgSend_3(classRef_NSDateFormatter, selRef_alloc);
              v36 = objc_msgSend_3(DateFormatter, selRef_init);
              v37 = v36;
              NSDate_init = v36;
              objc_msgSend_3(v36, selRef_setDateFormat_, &cfstr_YyyyMmDd);
              v38 = objc_msgSend_3(v37, selRef_dateFromString_, decrypt_UTF8_index24);
              LODWORD(decrypt_UTF8_index24_dateFormat) = objc_retainAutoreleasedReturnValue(v38);
              decrypt_UTF8_index24_dateFormat_1 = decrypt_UTF8_index24_dateFormat;
              v40 = objc_msgSend_3(v37, selRef_dateFromString_, &cfstr_20140501);// "2014-05-01"
              LODWORD(dateFormat_2014_05_1) = objc_retainAutoreleasedReturnValue(v40);
              dateFormat_2014_05_2 = dateFormat_2014_05_1;
              decrypt_UTF8_index41 = objc_msgSend_3(decrypt_UTF8_2, selRef_substringFromIndex_, 41LL);
              LODWORD(v46) = objc_retainAutoreleasedReturnValue(decrypt_UTF8_index41);
              decrypt_UTF8_index41_2 = v46;
              v45 = objc_msgSend_3(decrypt_UTF8_2, selRef_substringWithRange_, 35LL, 4LL);
              LODWORD(decrypt_UTF8_index35_1) = objc_retainAutoreleasedReturnValue(v45);
              decrypt_UTF8_index35 = decrypt_UTF8_index35_1;
              *decrypt_UTF8_index35_intValue = objc_msgSend_3(decrypt_UTF8_index35_1, selRef_intValue);
              objc_release(decrypt_UTF8_index35);
              LODWORD(decrypt_UTF8_index35) = *decrypt_UTF8_index35_intValue;
              v44 = objc_msgSend_3(classRef_NSBundle, selRef_mainBundle);
              LODWORD(mainBundle) = objc_retainAutoreleasedReturnValue(v44);
              if ( decrypt_UTF8_index35 < 2 )
              {
                v62 = mainBundle;
                v54 = objc_msgSend_3(
                        mainBundle,
                        selRef_localizedStringForKey_value_table_,
                        &cfstr_Registration_2,  // "Registration: # %d on %@\nLicensee: %@"
                        &cfstr_nil,
                        &cfstr_Registration);
                LODWORD(v55) = objc_retainAutoreleasedReturnValue(v54);
                v51 = v55;
                v53 = objc_msgSend_3(
                        classRef_NSString,
                        selRef_stringWithFormat_,
                        v55,
                        decrypt_UTF8_index16_intValue_1,
                        decrypt_UTF8_index24_2);
              }
              else
              {
                v62 = mainBundle;
                v49 = objc_msgSend_3(
                        mainBundle,
                        selRef_localizedStringForKey_value_table_,
                        &cfstr_RegistrationDO,  // "Registration: # %d on %@ for %d licenses\nLicensee: %@"
                        &cfstr_nil,
                        &cfstr_Registration);
                LODWORD(v50) = objc_retainAutoreleasedReturnValue(v49);
                v51 = v50;
                v52 = *decrypt_UTF8_index35_intValue;
                v53 = objc_msgSend_3(
                        classRef_NSString,
                        selRef_stringWithFormat_,
                        v50,
                        decrypt_UTF8_index16_intValue_1,
                        decrypt_UTF8_index24_2,
                        decrypt_UTF8_index41_2,
                        NSDate_init);
              }
              LODWORD(Notice_1) = objc_retainAutoreleasedReturnValue(v53);
              LODWORD(Notice) = objc_autorelease(Notice_1);
              *Notice_2 = Notice;
              objc_release_1 = objc_release;
              objc_release(v51);
              objc_release_1(v62);
              if ( objc_msgSend_ptr(decrypt_UTF8_index24_dateFormat_1, selRef_compare_, dateFormat_2014_05_2) == -1
                || prefixFlag1 )
              {
                decrypt_UTF8_index16_intValue_1 = (prefixFlag1 != 0) | 0x98967E;
              }
              objc_release_2 = objc_release;
              objc_release(decrypt_UTF8_index41_2);
              objc_release_2(dateFormat_2014_05_2);
              objc_release_2(decrypt_UTF8_index24_dateFormat_1);
              objc_release_2(NSDate_init);
              objc_release_2(decrypt_UTF8_index24_2);
              result_mid = decrypt_UTF8_index16_intValue_1;
            }
LABEL_21:
            objc_release(decrypt_UTF8_2);
          }
        }
      }
    }
    else
    {
      mem_buf_1 = 0LL;
    }
  }
  if ( x )
    RSA_free(x);
  if ( mem_buf_1 )
    BIO_free(mem_buf_1);
  free(from);
  free(base64Buf);
  objc_release(tfCode_2);
  result = *__stack_chk_guard_ptr;
  if ( *__stack_chk_guard_ptr == stack_chk_guard )
    result = result_mid;
  return result;
}
也打了一些注释,可以看到先对 tfCode 的格式做了判断,对应补全后做了 base64 + RSA 的解码/解密,还有一些后续对特定字符位置比较的操作,如果要写一个注册机的话从这里入手是个很好的路子

上面提到了好几次 tfCode,这个在 IDA 反编译出来的代码中显示的比较不直观,需要在 Structures(SHIFT + F9)里找到对应类查看,这里推荐使用 class-dump 可以比较方便得导出 oc 运行时的声明文件,也就包括类的定义文件。看上去很舒服,比 IDA 直接查看更加直观。

➜ test ❯❯❯ class-dump -C RegistrationController BetterZip
....
@interface RegistrationController : NSWindowController 
{
    NSTabView *tabView;
    NSTextField *tfEmail;
    NSButton *btnConvert;
    NSTextField *tfHead;
    NSTextField *tfText;
    NSTextField *tfCode;
    NSTextField *tfLabel;
    NSButton *btnBuy;
    NSButton *btnLost;
    NSButton *btnContinue;
    RendezvousController *rendezvous;
    int usersFound;
    NSString *ownName;
    NSTimer *timer;
    int timerCountDown;
    BOOL isModalSheet;
    NSString *hashString;
}

+ (BOOL)isRegistered;
+ (int)trialDaysLeft;
+ (BOOL)trialIsOver;
- (void).cxx_destruct;
- (BOOL)testCode;
- (id)regCode;
- (void)lostKey:(id)arg1;
- (void)laterContinue:(id)arg1;
- (void)buy:(id)arg1;
- (int)registrationStatus;
- (id)createNameArrayFromDiscoveredServices:(id)arg1;
- (void)discoveredServicesDidChange;
- (void)controlTextDidChange:(id)arg1;
- (void)registerWithCode:(id)arg1;
- (void)convert:(id)arg1;
- (void)convertMASReceiptToCode;
- (void)windowDidLoad;
- (id)initWithParams:(id)arg1;

// Remaining properties
@property(readonly, copy) NSString *debugDescription;
@property(readonly, copy) NSString *description;
@property(readonly) unsigned long long hash;
@property(readonly) Class superclass;

@end

如果要根据 checkRegistration 写注册机显然比较麻烦,看看 checkRegistration 的调用位置,DATA XREF 找到 registrationStatus ,是一个操作 self.is_registered 的函数,根据不同的情况保存不同的值到 is_registered 看到这个已经离终点很近了。

因为 checkRegistration 是把当前注册状态写到 is_registered 里的函数,那么肯定就有另外一个读函数,根据 is_registered 的 DATA XREF 找到读函数 isRegistered。

// RegistrationController + (char)isRegistered
char __cdecl +[RegistrationController isRegistered](struct RegistrationController_meta *self, SEL a2)
{
  return is_registered ^ 1;
}
可以看到这个函数其实是在开始很显眼的 RegistrationController 里,逆向中,类名往往可以透漏很多信息。接下来这里的操作只需要把 isRegistered 修改成永远返回 1 的函数就行了,如果要用 IDA 修改的话只能找到对应 x86 汇编指令的 16 进制机器码然后在 HEX-View 里修改比较麻烦。

这里推荐使用 Hopper 来修改,直接 alt + A 就可以修改汇编指令了,很简单 ~ 图如下
改好了之后把 MachO 拖入 BetterZip.app 里面打开,👏👏👏,已经解决之前提出的 1.2 点问题了,现在只剩下问题 3 —— 反复弹框。

按理来说此时 BetterZip 已经认为是注册了的状态,不应该再弹框的,可现在还在弹框,那么有一种很可能的情况,先谈一个窗然后再根据注册状态修改/关闭弹窗。巧了,再回到之前看过的 testCode 当时有这么一句总结:“testCode 大多是一些根据当前注册状态修改提示界面和信息的代码”,刚好证实了我的这个猜想。

然后再看看 -[RegistrationController initWithParams:] 确实有调用相关代码 [[self super] initWithWindowNibName:@"Register"] , 直接把相关代码 ret 掉,大功告成 ~