QiuChenly 发表于 2023-3-22 22:24

破解Navicat Premium 16.1.7: 基于lldb动态调试配合静态注入+FridaHook

本帖最后由 QiuChenly 于 2023-3-22 22:37 编辑

# 破解Navicat Premium 16.1.7 MAS订阅版本

## 1.准备工作

下载地址是MacAppStore应用商店。


IDA 7.0
Hopper Disassembler 5.7.7
Xcode
VS Code + Frida

## 2.开始分析

首先老样子找到界面上的关键字,在HP里面找到对应的地址。


我们看到只有一个地方引用了

跟到这里你会发现

这个地方引用了这个字符串,那么我们看看上面有没有jmp大跳:
一翻发现有这段代码:

首先他获取了IAPHelper这个类拿到了insatnce,然后rax 执行了一个方法
```c
r14 = ;
    ;
    rax = *ivar_offset(_isInTrialPeriod);
    stack[-80] = r15;
    if (r14 != 0x0) {
            *(int8_t *)(r15 + rax) = 0x1;
            stack[-56] = objc_loadWeakRetained(*ivar_offset(_welcomeTitleLabel) + r15);
            rax = ;
            rax = ;
            stack[-64] = rax;
            rax = ;
            rax = ;
            rbx = rax;
            stack[-88] = rax;
            rax = ;
            rax = ;
            r15 = rax;
            rax = ;
            rax = ;
            r14 = rax;
            rax = ;
            rax = ;
             setStringValue:rax, rcx, 0x0];
            ;
            ;
            ;
             release];
             release];
             release];
            r14 = objc_loadWeakRetained(*ivar_offset(_purchaseViewTitleLabel) + stack[-80]);
            r13 = *_objc_msgSend;
            rax = ;
            rax = ;
            r15 = rax;
            rax = ;
            rax = ;
            ;
            ;
            ;
            ;
            stack[-56] = objc_loadWeakRetained(*ivar_offset(_welcomeMessageLabel) + stack[-80]);
            rax = ;
            rax = ;
            stack[-64] = rax;
            r13 = [ retain];
            rax = ;

```
通过这个方法得到了返回值如果是1那就进入开始试用阶段。
我们编写简单的frida试一下:

然后用python启动:
```python
def navicat():
    launchApp(
      "/Applications/Navicat Premium.app/Contents/MacOS/Navicat Premium"
    )
   
def launchApp(image, args=[], js="_index.js"):
    pid = frida.spawn(image, argv=args)
    frida.resume(pid)
    os.system(f"frida -p {pid} -l {js} --debug --runtime=v8")
```
js脚本的代码可以参阅我其他的帖子,这里就不重复贴出来了。
启动发现变成了这样:

显然光绕过不行,这里是免费试用到期的状态,因为上面那个if完事了有一个else

这里就是继续享受强大功能的提示。

注意 我们这个时候其实是frida把App挂在了Hook上了,所以我们需要用lldb来动态调试反汇编:
`lldb --attach-name Navicat\ Premium`
加载完之后可以看到stop在这里了:

为了反汇编代码与IDA/Hopper Disassembler一致,我们需要将其反汇编代码风格改为intel:
`settings set target.x86-disassembly-flavor intel`

这样就可以和IDA里的反汇编代码风格一致了。
这里是自动断下的,我们先输入c让他继续运行:

那么我们需要下个断点在这个调用函数上.
前面我们说到这里是通过搜索string找到的windowDidLoad函数引用,所以我们就要在这个函数的开头下breakpoint让他Suspend,方便我观察Stack。
/* @Class IAPWindowController */
-(void)windowDidLoad {}
输入:

这里地址是

这个地方得到的偏移地址.
然后我们输入r重启app

直接回车即可.
可以看到函数这个时候被断下了:

现在我们要查这个函数从哪里调用的堆栈,输入简写bt看堆栈:
```bash
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
    frame #0: 0x00000001001b8502 Navicat Premium`___lldb_unnamed_symbol8329
Navicat Premium`___lldb_unnamed_symbol8329:
->0x1001b8502 <+0>: push   rbp
    0x1001b8503 <+1>: mov    rbp, rsp
    0x1001b8506 <+4>: push   r15
    0x1001b8508 <+6>: push   r14
Target 0: (Navicat Premium) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
* frame #0: 0x00000001001b8502 Navicat Premium`___lldb_unnamed_symbol8329
    frame #1: 0x00007ff8038d00aa AppKit`- + 548
    frame #2: 0x00007ff8038cbc87 AppKit`- + 110
    frame #3: 0x00000001001b7fee Navicat Premium`___lldb_unnamed_symbol8326 + 142
    frame #4: 0x0000000100ab284a Navicat Premium`___lldb_unnamed_symbol28791 + 892
    frame #5: 0x00007ff801b16b21 Foundation`__NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
    frame #6: 0x00007ff801b16a19 Foundation`- + 98
    frame #7: 0x00007ff801b169af Foundation`__NSOPERATION_IS_INVOKING_MAIN__ + 17
    frame #8: 0x00007ff801b15c1b Foundation`- + 785
    frame #9: 0x00007ff801b158f1 Foundation`__NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 17
    frame #10: 0x00007ff801b157c8 Foundation`__NSOQSchedule_f + 182
    frame #11: 0x00007ff800a683c0 libdispatch.dylib`_dispatch_block_async_invoke2 + 83
    frame #12: 0x00007ff800a5b317 libdispatch.dylib`_dispatch_client_callout + 8
    frame #13: 0x00007ff800a67c78 libdispatch.dylib`_dispatch_main_queue_drain + 943
    frame #14: 0x00007ff800a678bb libdispatch.dylib`_dispatch_main_queue_callback_4CF + 31
    frame #15: 0x00007ff800d16f37 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #16: 0x00007ff800cd7fcf CoreFoundation`__CFRunLoopRun + 2771
    frame #17: 0x00007ff800cd6e3c CoreFoundation`CFRunLoopRunSpecific + 562
    frame #18: 0x00007ff8099865e6 HIToolbox`RunCurrentEventLoopInMode + 292
    frame #19: 0x00007ff80998634a HIToolbox`ReceiveNextEventCommon + 594
    frame #20: 0x00007ff8099860e5 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 70
    frame #21: 0x00007ff803710fad AppKit`_DPSNextEvent + 927
    frame #22: 0x00007ff80370f66a AppKit`- + 1394
    frame #23: 0x00007ff803701d19 AppKit`- + 586
    frame #24: 0x00007ff8036d5c97 AppKit`NSApplicationMain + 817
    frame #25: 0x00000001009c6562 Navicat Premium`___lldb_unnamed_symbol27130 + 3498
    frame #26: 0x0000000101b3152e dyld`start + 462
(lldb)
```
可以看到调用方来自frame #3,我们输入up来跳到调用方:
```bash
(lldb) up
frame #1: 0x00007ff8038d00aa AppKit`- + 548
AppKit`-:
->0x7ff8038d00aa <+548>: jmp    0x7ff8038d018a            ; <+772>
    0x7ff8038d00af <+553>: mov    r12, qword ptr ; "owner"
    0x7ff8038d00b6 <+560>: mov    rdi, rbx
    0x7ff8038d00b9 <+563>: mov    rsi, r12
(lldb)
```
可以看到我们在#1,我们在跳两次到#3:
```
(lldb) up 3
frame #4: 0x0000000100ab284a Navicat Premium`___lldb_unnamed_symbol28791 + 892
Navicat Premium`___lldb_unnamed_symbol28791:
->0x100ab284a <+892>: mov    rdi, qword ptr ; (void *)0x00000001019c8cf0
    0x100ab2851 <+899>: mov    rsi, qword ptr ; "backend"
    0x100ab2858 <+906>: call   r12
    0x100ab285b <+909>: mov    rdi, rax
(lldb) down 1
frame #3: 0x00000001001b7fee Navicat Premium`___lldb_unnamed_symbol8326 + 142
Navicat Premium`___lldb_unnamed_symbol8326:
->0x1001b7fee <+142>: mov    rdi, rax
    0x1001b7ff1 <+145>: call   0x1011fdbae               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x1001b7ff6 <+150>: mov    rbx, rax
    0x1001b7ff9 <+153>: mov    rsi, qword ptr ; "runModalForWindow:"
(lldb)
```
这里输入up 3就是跳3次堆栈,我这里还down 1是因为我跳到#4了,所以往回跳1个堆栈.
我们看下这个代码:
```
    0x1001b7fb6 <+86>:mov    rsi, qword ptr ; "setIsStartup:"
    0x1001b7fbd <+93>:movsxebx, bl
    0x1001b7fc0 <+96>:mov    edx, ebx
    0x1001b7fc2 <+98>:call   qword ptr ; (void *)0x00007ff800aa5400: objc_msgSend
    0x1001b7fc8 <+104>: test   bl, bl
    0x1001b7fca <+106>: je   0x1001b800b               ; <+171>
    0x1001b7fcc <+108>: mov    rax, qword ptr ; (void *)0x00007ff8421b99b0: NSApp
    0x1001b7fd3 <+115>: mov    r14, qword ptr
    0x1001b7fd6 <+118>: mov    rdi, qword ptr
    0x1001b7fdd <+125>: mov    rsi, qword ptr ; "window"
    0x1001b7fe4 <+132>: mov    r15, qword ptr ; (void *)0x00007ff800aa5400: objc_msgSend
    0x1001b7feb <+139>: call   r15
->0x1001b7fee <+142>: mov    rdi, rax
    0x1001b7ff1 <+145>: call   0x1011fdbae               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x1001b7ff6 <+150>: mov    rbx, rax
    0x1001b7ff9 <+153>: mov    rsi, qword ptr ; "runModalForWindow:"
    0x1001b8000 <+160>: mov    rdi, r14
    0x1001b8003 <+163>: mov    rdx, rax
    0x1001b8006 <+166>: call   r15
    0x1001b8009 <+169>: jmp    0x1001b8042               ; <+226>
    0x1001b800b <+171>: mov    rdi, qword ptr
    0x1001b8012 <+178>: mov    rsi, qword ptr ; "window"
    0x1001b8019 <+185>: mov    r14, qword ptr ; (void *)0x00007ff800aa5400: objc_msgSend
    0x1001b8020 <+192>: call   r14
    0x1001b8023 <+195>: mov    rdi, rax
    0x1001b8026 <+198>: call   0x1011fdbae               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x1001b802b <+203>: mov    rbx, rax
    0x1001b802e <+206>: mov    rdx, qword ptr
    0x1001b8035 <+213>: mov    rsi, qword ptr ; "makeKeyAndOrderFront:"
    0x1001b803c <+220>: mov    rdi, rax
    0x1001b803f <+223>: call   r14
    0x1001b8042 <+226>: mov    rdi, rbx
    0x1001b8045 <+229>: add    rsp, 0x8
    0x1001b8049 <+233>: pop    rbx
    0x1001b804a <+234>: pop    r14
    0x1001b804c <+236>: pop    r15
    0x1001b804e <+238>: pop    rbp
    0x1001b804f <+239>: jmp    qword ptr ; (void *)0x00007ff800aa8000: objc_release
(lldb)
```
我们看->的指向位置,回到Hopper我们看下对应位置到底写了什么.
```
frame #0: 0x00000001001ec52f Noto`___lldb_unnamed_symbol44926 + 1151
Noto`___lldb_unnamed_symbol44926:
->0x1001ec52f <+1151>: cmp    qword ptr , 0x0
    0x1001ec534 <+1156>: je   0x1001ec8c9               ; <+2073>
    0x1001ec53a <+1162>: mov    rdi, r13
    0x1001ec53d <+1165>: call   0x100da89f0               ; symbol stub for: swift_bridgeObjectRetain
(lldb) gui
```
为了方便操作堆栈,我们输入gui切换到GUI模式.

现在他默认在堆栈顶部,我们要切换到右侧"___lldb_unnamed_"开头的堆栈查看源代码:
按下TAB,我们会按顺序切换到右侧:

现在高亮#4就代表我们在堆栈#4,左侧的代码会对应显示出来.我们记下这个地址到Hopper里面查一下:
0x0000000100ab284a
按下G键开始跳转:

```c
void sub_100ab24ce(void * _block) {
    rax = ;
    rax = ;
    rbx = ;
    ;
    if (rbx == 0x0) goto loc_100ab2834;

loc_100ab2525:
    rax = ;
    rax = ;
    stack[-64] = rax;
    stack[-120] = [ retain];
    rax = ;
    rax = ;
    stack[-112] = rax;
    r14 = [ retain];
    rax = ;
    rax = ;
    stack[-96] = rax;
    stack[-104] = [ retain];
    rax = ;
    rax = ;
    stack[-80] = rax;
    stack[-88] = [ retain];
    rax = ;
    rax = ;
    stack[-72] = rax;
    rax = ;
    rax = ;
    stack = rax;
    stack[-128] = [ details:r14 firstButtonTitle:stack[-104] secondButtonTitle:stack[-88] thirdButtonTitle:stack] retain];
    ;
    release];
    release];
    release];
    release];
    release];
    ;
    release];
    release];
    release];
    r13 = stack[-128];
    rax = ;
    rax = ;
    r14 = rax;
    rax = ;
    rax = ;
    ;
    ;
    rax = ;
    rax = ;
    ;
    ;
    rax = ;
    if (rax == 0x3e9) goto loc_100ab28bf;

loc_100ab27cb:
    if (rax != 0x3e8) goto loc_100ab2928;

loc_100ab27d7:
    r15 = [ retain];
    rax = ;
    rax = ;
    rbx = rax;
    ;
    goto loc_100ab2917;

loc_100ab2917:
    ;
    ;
    goto loc_100ab2928;

loc_100ab2928:
    ;
    ;
    return;

loc_100ab28bf:
    r15 = [ retain];
    rax = ;
    rax = ;
    rbx = rax;
    ;
    goto loc_100ab2917;

loc_100ab2834:
    ;
    CCNavicat::validateAppStoreReceipt();
    rax = ;
    rax = ;
    rbx = ;
    ;
    if (rbx == 0x0) {
            [**_NSApp terminate:0x0];
    }
    return;
}

```
很明显 我们看到上面调用了一个函数:isProductSubscriptionInGracePeriod,以楼主没几两墨水的肚子勉强猜出来这个函数是否产品订阅在宽容周期内,也就是已过期但是你还可以用.

由于Hopper对原始C函数反编译能力较弱,我们用IDA看下比较精确的伪代码:
```c
void *sub_100AB24CE()
{
struct objc_object *v0; // rax
void *v1; // r13
char v2; // bl
void *v3; // rax
void *v4; // rax
void *v5; // ST48_8
void *v6; // rax
__int64 v7; // ST10_8
void *v8; // rax
void *v9; // rax
void *v10; // ST18_8
void *v11; // rax
__int64 v12; // r14
void *v13; // rax
void *v14; // rax
void *v15; // ST28_8
void *v16; // rax
__int64 v17; // ST20_8
void *v18; // rax
void *v19; // rax
void *v20; // ST38_8
void *v21; // rax
__int64 v22; // ST30_8
void *v23; // rax
void *v24; // rax
void *v25; // ST40_8
void *v26; // rax
__int64 v27; // rax
__int64 v28; // r13
void *v29; // rax
void *v30; // rax
void *v31; // rax
void *v32; // r14
void *v33; // rax
void *v34; // rbx
void *v35; // rax
void *v36; // rbx
void *v37; // rax
void *v38; // rax
void *v39; // r15
void *v40; // rax
__int64 v41; // rbx
CCNavicat *v42; // rax
struct objc_object *v43; // rax
void *v44; // r13
char v45; // bl
void *result; // rax
void *v47; // rax
void *v48; // rax
void *v49; //
__int64 retaddr; //

v0 = +(&OBJC_CLASS___IAPHelper, "sharedHelper");
v1 = (void *)((__int64 (__fastcall *)(struct objc_object *))objc_retainAutoreleasedReturnValue)(v0);
v2 = (unsigned __int64)objc_msgSend(v1, "isProductSubscriptionInGracePeriod");
objc_release(v1);
if ( v2 )
{
    v3 = objc_msgSend(&OBJC_CLASS___NSBundle, "mainBundle");
    v4 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v3);
    v5 = v4;
    v6 = objc_msgSend(v4, "localizedStringForKey:value:table:", CFSTR("Subscription Expired"), &stru_101595060, 0LL);
    v7 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v6);
    v8 = objc_msgSend(&OBJC_CLASS___NSBundle, "mainBundle");
    v9 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v8);
    v10 = v9;
    v11 = objc_msgSend(
            v9,
            "localizedStringForKey:value:table:",
            CFSTR("Please check your subscription status."),
            &stru_101595060,
            0LL);
    v12 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v11);
    v13 = objc_msgSend(&OBJC_CLASS___NSBundle, "mainBundle");
    v14 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v13);
    v15 = v14;
    v16 = objc_msgSend(v14, "localizedStringForKey:value:table:", CFSTR("Manage Subscriptions"), &stru_101595060, 0LL);
    v17 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v16);
    v18 = objc_msgSend(&OBJC_CLASS___NSBundle, "mainBundle");
    v19 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v18);
    v20 = v19;
    v21 = objc_msgSend(v19, "localizedStringForKey:value:table:", CFSTR("Manage Payments"), &stru_101595060, 0LL);
    v22 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v21);
    v23 = objc_msgSend(&OBJC_CLASS___NSBundle, "mainBundle");
    v24 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v23);
    v25 = v24;
    v26 = objc_msgSend(v24, "localizedStringForKey:value:table:", CFSTR("Continue"), &stru_101595060, 0LL);
    v27 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v26);
    v28 = v27;
    retaddr = v27;
    v29 = objc_msgSend(
            &OBJC_CLASS___NSAlert,
            "alertWithQuestion:details:firstButtonTitle:secondButtonTitle:thirdButtonTitle:",
            v7,
            v12,
            v17,
            v22);
    v49 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v29);
    objc_release(v28);
    objc_release(v25);
    objc_release(v22);
    objc_release(v20);
    objc_release(v17);
    objc_release(v15);
    objc_release(v12);
    objc_release(v10);
    objc_release(v7);
    objc_release(v5);
    v30 = objc_msgSend(v49, "buttons");
    v31 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v30);
    v32 = v31;
    v33 = objc_msgSend(v31, "objectAtIndex:", 0LL);
    v34 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v33);
    objc_msgSend(v34, "setKeyEquivalent:", &stru_101595060);
    objc_release(v34);
    v35 = objc_msgSend(v32, "objectAtIndex:", 2LL);
    v36 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v35);
    objc_msgSend(v36, "setKeyEquivalent:", CFSTR("\r"));
    objc_release(v36);
    v37 = objc_msgSend(v49, "runModal");
    if ( v37 == (void *)1001 )
    {
      v47 = objc_msgSend(&OBJC_CLASS___NSWorkspace, "sharedWorkspace");
      v39 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v47);
      v48 = objc_msgSend(&OBJC_CLASS___NSURL, "URLWithString:", CFSTR("itms-apps://apps.apple.com/account/billing"));
      v41 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v48);
      objc_msgSend(v39, "openURL:", v41);
    }
    else
    {
      if ( v37 != (void *)1000 )
      {
LABEL_10:
      objc_release(v32);
      return (void *)objc_release(v49);
      }
      v38 = objc_msgSend(&OBJC_CLASS___NSWorkspace, "sharedWorkspace");
      v39 = (void *)((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v38);
      v40 = objc_msgSend(
            &OBJC_CLASS___NSURL,
            "URLWithString:",
            CFSTR("itms-apps://apps.apple.com/account/subscriptions"));
      v41 = ((__int64 (__fastcall *)(void *))objc_retainAutoreleasedReturnValue)(v40);
      objc_msgSend(v39, "openURL:", v41);
    }
    objc_release(v41);
    objc_release(v39);
    goto LABEL_10;
}
+(&OBJC_CLASS___IAPWindowController, "showWindow:", 1LL);
v42 = +(&OBJC_CLASS___CCCore, "backend");
CCNavicat::validateAppStoreReceipt(v42);
v43 = +(&OBJC_CLASS___IAPHelper, "sharedHelper");
v44 = (void *)((__int64 (__fastcall *)(struct objc_object *))objc_retainAutoreleasedReturnValue)(v43);
v45 = (unsigned __int64)objc_msgSend(v44, "isProductSubscriptionStillValid");
result = (void *)objc_release(v44);
if ( !v45 )
    result = objc_msgSend(NSApp, "terminate:", 0LL);
return result;
}
```
可以看到isProductSubscriptionInGracePeriod如果返回1那么就会提示你product expired,这显然不是我们需要的结果。那如果不在宽容周期内呢?
我们直接翻到最底下赫然发现函数isProductSubscriptionStillValid:
```c
v45 = (unsigned __int64)objc_msgSend(v44, "isProductSubscriptionStillValid");
result = (void *)objc_release(v44);
if ( !v45 )
    result = objc_msgSend(NSApp, "terminate:", 0LL);
return result;
```
如果产品一直验证通过,那么直接返回v45,也就是说v45必须等于1否则terminate.
我们Hook一下看看.


可以看到确实调用了这个函数,crack OK!
现在可以直接体验正版。但是我们显然不能每次都要开frida,有没有办法静态注入进app呢?
## 3.编写Object Dylib注入进App Hook函数
我们发现isProductSubscriptionStillValid是一个ObjectC函数,那么ObjectC有一个method Swizzing技术,也就是替换IMP函数具体实现的地址来用我们的代码替换原始函数实现代码。我们只需要Hook掉isProductSubscriptionStillValid这个函数返回值即可:
```c
- (BOOL)new_activated {
    return 1;
}

-(void) validate{
    NSLog(@"==== validate 函数绕过成功。");
}

/**
* Navicat Premium 16.1.7
* MAS版本 https://apps.apple.com/cn/app/navicat-premium-16/id1594061654?mt=12
*/
void NavicatPremium(void){
    if (!checkSelfInject("com.navicat.NavicatPremium")) return;
   
    //class_getInstanceMethod 得到类的实例方法
    //class_getClassMethod 得到类的类方法
   
    if (!checkAppVersion("16.1.7.x")){
      
      uint32_t size = _dyld_image_count();//获取所有加载的映像
      NSLog(@"==== 加载的映像数量: %i",size);
      
      NSLog(@"==== 函数地址:validate = %p ,函数地址:isProductSubscriptionStillValid = %p",
            getMethodStrByCls(@"AppStoreReceiptValidation",@"validate"),
            getMethodStr(@"IAPHelper", @"isProductSubscriptionStillValid")
      );
      
      for(int a=0;a<size;a++){
            const char* name = _dyld_get_image_name(a);//根据映像下标取名称
            NSLog(@"==== Slide: %i,ModuleName: %s",a,name);
            
            if(strcmp("/Applications/Navicat Premium.app/Contents/Frameworks/libcc-premium.dylib", name)==0){
                NSLog(@"==== find libcc-premium.dylib!");
                //class_getClassMethod这里不能用Instance 会返回nil 从gpt回复中可以看出类的实例不代表类函数 所以一般应该用getClassMethod就不会报错nil
                // 下面是ChatGPT的回答
                /**
               可以这样理解:

               在Objective-C中,方法是通过消息传递来调用的。当我们调用一个方法时,实际上是向对象发送了一个消息,让它去执行对应的方法。因此,我们需要知道方法的名称和参数类型,才能正确地发送消息。

               在获取方法的时候,我们需要使用Method对象来表示方法。Method对象包含了方法的名称、参数类型、返回值类型等信息,可以用来发送消息。

               class_getInstanceMethod和class_getClassMethod就是用来获取Method对象的函数。它们的区别在于,class_getInstanceMethod用于获取实例方法,即针对对象的方法;而class_getClassMethod用于获取类方法,即针对类的方法。

               举个例子,假设我们有一个Person类,它有一个实例方法run和一个类方法eat。如果我们要获取run方法的Method对象,可以使用class_getInstanceMethod(Person.class, @selector(run));如果要获取eat方法的Method对象,可以使用class_getClassMethod(Person.class, @selector(eat))。

               需要注意的是,类方法是属于类的,而不是属于类的实例。因此,如果要获取类方法的Method对象,需要使用类对象而不是实例对象。例如,对于类Person,可以使用获取它的类对象,然后再使用class_getClassMethod获取类方法的Method对象。
               */
            //switchMethod(getMethodStrByCls(@"AppStoreReceiptValidation",@"validate"), getMethod(, @selector(validate)));
                continue;
            }
            
            if(strcmp("/Applications/Navicat Premium.app/Contents/MacOS/Navicat Premium", name)==0){
                NSLog(@"==== find Navicat Premium!");
                switchMethod(getMethodStr(@"IAPHelper", @"isProductSubscriptionStillValid"), getMethod(, @selector(new_activated)));
                continue;
            }
      }
    }
}


/**
* 获取函数IMP
* @param cls
* @param name
* @return
*/
Method getMethod(Class _Nullable cls, SEL _Nonnull name) {
    return class_getInstanceMethod(cls, name);
}

Method getMethodByCls(Class _Nullable cls, SEL _Nonnull name) {
    return class_getClassMethod(cls, name);
}

/**
* 获取函数IMP 字符串方式
* @param cls ObjectC 类名
* @param name ObjectC 函数名
* @return
*/
Method getMethodStr(NSString *cls, NSString *name) {
    return getMethod(NSClassFromString(cls), NSSelectorFromString(name));
}

Method getMethodStrByCls(NSString *cls, NSString *name) {
    return getMethodByCls(NSClassFromString(cls), NSSelectorFromString(name));
}

/**
* 交换函数IMP实现
* @param original 原始函数
* @param new 伪造函数
*/
void switchMethod(Method original, Method new) {
    method_exchangeImplementations(original, new);
}

/**
* 根据提供的地址hook掉对应位置的函数
* @param imageIndex App镜像序号
* @param addr IDA中的函数偏移指针地址
* @param replaceMethod 将被替换的函数
* @param retOriginalFunctionAddress 如果有需要 此处返回被hook的原函数实现\n
* 像这样声明将被保存的原函数:int (*functionName)(char *functionArgs);\n
* 参数提供:(void **) &functionName
* @return 成功或者失败 0/1
*/
BOOL hookPtr(uint32_t imageIndex, intptr_t addr, void *replaceMethod, void **retOriginalFunctionAddress) {
    NSLog(@"==== 正在Hook Ptr %p",addr);
    intptr_t originalAddress = _dyld_get_image_vmaddr_slide(imageIndex) + addr;
    return rd_route((void *) originalAddress, replaceMethod, retOriginalFunctionAddress) == KERN_SUCCESS;
}

BOOL hookPtrZ(intptr_t addr, void *replaceMethod, void **retOriginalFunctionAddress) {
    return hookPtr(0, addr, replaceMethod, retOriginalFunctionAddress);
}

BOOL hookPtrA(intptr_t addr, void *replaceMethod) {
    return hookPtrZ(addr, replaceMethod, NULL);
}
```
这里switchMethod和getMethod/getMethodStr函数我也贴一下避免有些朋友看不懂.
而实际上我们注入只需要一行代码即可,由于这个插件我写了二十多个app的激活hook代码,所以可以看到上面有很多的app版本和bundleid检查。
```c
switchMethod(getMethodStr(@"IAPHelper", @"isProductSubscriptionStillValid"), getMethod(, @selector(new_activated)));
```
至于如何新建Xcode项目,可以看我之前的帖子,也有详细说明,这里再写就万字长文了,就不贴了。
command b编译 注入,我们挑一个好欺负的framework来注入:

选它没别的原因 ,就是这个文件只有800KB方便复制,你也不想选个10MB的大文件Copy来Copy去吧?
注入工具和编译后的dylib文件可以查阅我的github,这里就不写了。

我们直接打开App看看.

小小的脑袋大大的疑惑,大无语事件发生,集美们!那么这种情况我们不慌,可以看到app实际上是打开了的。这种错误其实是exit(173),也就是告诉macOS用户授权票不匹配或者不存在,也就是说他检测到了app不是你购买过的,没有有效的订阅授权文件。
那么我们怎么去调试他?

很简单,直接lldb hook exit函数即可。因为我阿妈每天早上六点起来给我充电子烟,所以exit(173)是固定写法,不这么写是不会弹出这个提示的。

我们来看下,直接通过lldb可以看出他exited with status = 173 (0x000000ad) ,说明我所言非虚。

```c
Process 20625 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 7.1
    frame #0: 0x00007ff800bdb1e0 libsystem_kernel.dylib`__exit
libsystem_kernel.dylib`:
->0x7ff800bdb1e0 <+0>:mov    eax, 0x2000001
    0x7ff800bdb1e5 <+5>:mov    r10, rcx
    0x7ff800bdb1e8 <+8>:syscall
    0x7ff800bdb1ea <+10>: jae    0x7ff800bdb1f4            ; <+20>
Target 0: (Navicat Premium) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 7.1
* frame #0: 0x00007ff800bdb1e0 libsystem_kernel.dylib`__exit
    frame #1: 0x00007ff800b0ac13 libsystem_c.dylib`exit + 56
    frame #2: 0x0000000108b70734 libcc-premium.dylib`___lldb_unnamed_symbol81087 + 1448
    frame #3: 0x0000000108676b38 libcc-premium.dylib`___lldb_unnamed_symbol59527 + 24
    frame #4: 0x0000000107a2ea10 libcc-premium.dylib`CCNavicat::setupContext(bool, bool) + 2100
    frame #5: 0x0000000100ab34d1 Navicat Premium`___lldb_unnamed_symbol28793 + 323
    frame #6: 0x00007ff800cce75c CoreFoundation`__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
    frame #7: 0x00007ff800d6bb32 CoreFoundation`___CFXRegistrationPost_block_invoke + 49
    frame #8: 0x00007ff800d6bab0 CoreFoundation`_CFXRegistrationPost + 496
    frame #9: 0x00007ff800ca03e8 CoreFoundation`_CFXNotificationPost + 735
    frame #10: 0x00007ff801ade7fe Foundation`- + 82
    frame #11: 0x00007ff80371985e AppKit`- + 305
    frame #12: 0x00007ff8037195ac AppKit`- + 208
    frame #13: 0x00007ff80371717c AppKit`- + 541
    frame #14: 0x00007ff803716dd0 AppKit`- + 665
    frame #15: 0x00007ff801b09724 Foundation`- + 308
    frame #16: 0x00007ff801b09596 Foundation`_NSAppleEventManagerGenericHandler + 80
    frame #17: 0x00007ff807390470 AE`___lldb_unnamed_symbol826 + 1849
    frame #18: 0x00007ff80738fcda AE`___lldb_unnamed_symbol825 + 34
    frame #19: 0x00007ff80738931f AE`aeProcessAppleEvent + 419
    frame #20: 0x00007ff809997ce2 HIToolbox`AEProcessAppleEvent + 54
    frame #21: 0x00007ff803711402 AppKit`_DPSNextEvent + 2036
    frame #22: 0x00007ff80370f66a AppKit`- + 1394
    frame #23: 0x00007ff803701d19 AppKit`- + 586
    frame #24: 0x00007ff8036d5c97 AppKit`NSApplicationMain + 817
    frame #25: 0x00000001009c6562 Navicat Premium`___lldb_unnamed_symbol27130 + 3498
    frame #26: 0x0000000101b3152e dyld`start + 462
(lldb) br s -n '_exit'

```
我们输入br s -n '_exit'来设置当App退出时的断点,这样app退出时我们可以让lldb接收到堆栈信息。
我们看上面的堆栈,可以看到libsystem_c.dylib和libsystem_kernel.dylib都不谈了,妥妥的系统库。

但是下面这个内存地址不是以0x00007ff8随机化地址开头的函数就有意思了,0x0000000108b70734显然是在app的内存空间里,说明了一个问题,这个库是App的。那么我们就需要知道这个库到底搞了什么鬼让他exit。我们记下偏移地址0x0000000108b70734去查一下。

在查之前我们需要一个概念,Apple有一个技术叫ASLR,随机化虚拟内存启动地址,也就是说,我们要找到偏移需要通过ASLR偏移计算出原始二进制文件的偏移。我们先看一下内存地址模块导出的基本地址:
输入image list -o -f回车。
```c
(lldb) image list -o -f
0x0000000000000000 /Applications/Navicat Premium.app/Contents/MacOS/Navicat Premium
0x0000000101b2c000 /usr/lib/dyld
0x00000001033e7000 /Applications/Navicat Premium.app/Contents/Frameworks/libmozjs-52.dylib
0x0000000107a02000 /Applications/Navicat Premium.app/Contents/Frameworks/libcc-premium.dylib
0x0000000101efe000 /Applications/Navicat Premium.app/Contents/Frameworks/libdbdiagram.dylib
0x0000000102649000 /Applications/Navicat Premium.app/Contents/Frameworks/libncharts.dylib
0x0000000101ce4000 /Applications/Navicat Premium.app/Contents/Frameworks/libcf.dylib
0x00000001028e5000 /Applications/Navicat Premium.app/Contents/Frameworks/libdv.dylib
0x0000000101df8000 /Applications/Navicat Premium.app/Contents/Frameworks/libvf.dylib
0x00000001021e5000 /Applications/Navicat Premium.app/Contents/Frameworks/libsv.dylib
[ 10] 0x000000010217e000 /Applications/Navicat Premium.app/Contents/Frameworks/libstat.dylib
[ 11] 0x0000000102362000 /Applications/Navicat Premium.app/Contents/Frameworks/libssl.1.1.dylib
[ 12] 0x00000001023f6000 /Applications/Navicat Premium.app/Contents/Frameworks/libsqlite3mc.0.dylib
[ 13] 0x0000000102f99000 /Applications/Navicat Premium.app/Contents/Frameworks/libcrypto.1.1.dylib
[ 14] 0x0000000104af3000 /Applications/Navicat Premium.app/Contents/Frameworks/EE.framework/Versions/A/EE
[ 15] 0x0000000102245000 /Applications/Navicat Premium.app/Contents/Frameworks/HexFiend.framework/Versions/A/HexFiend
[ 16] 0x0000000102148000 /Applications/Navicat Premium.app/Contents/Frameworks/FileMonitor.framework/Versions/A/FileMonitor
[ 17] 0x0000000103225000 /Applications/Navicat Premium.app/Contents/Frameworks/qb.framework/Versions/A/qb
[ 18] 0x00000001025c6000 /Applications/Navicat Premium.app/Contents/Frameworks/NAVTabBarView.framework/Versions/A/NAVTabBarView
```

我们看下这里起始地址为:
0x0000000107a02000 /Applications/Navicat Premium.app/Contents/Frameworks/libcc-premium.dylib

计算公式为 0x0000000107a02000 + 文件二进制偏移 = 内存地址偏移0x0000000108b70734
所以0x108676b38 - 0x0000000107a02000 = 0x116E734。


确实存在,我们用hopper看下。
直接G跳转到0x116E734看一下:


其实我们直接lldb里面可以看到
```c
                     loc_116e71e:
000000000116e71e         mov      edi, 0xad                                 ; End of try block started at 0x116e6cf, Begin of try block (catch block at 0x116e83a), argument "status" for method imp___stubs__exit, CODE XREF=++267
000000000116e723         call       imp___stubs__exit                           ; exit
                        ; endp
000000000116e728         jmp      sub_116e740+60                              ; End of try block started at 0x116e71e

                     loc_116e72a:
000000000116e72a         mov      edi, 0xad                                 ; Begin of try block (catch block at 0x116e844), argument "status" for method imp___stubs__exit, CODE XREF=++523
000000000116e72f         call       imp___stubs__exit                           ; exit
                        ; endp
000000000116e734         jmp      sub_116e740+60                              ; End of try block started at 0x116e72a

                     loc_116e736:
000000000116e736         mov      edi, 0xad                                 ; Begin of try block (catch block at 0x116e7ba), argument "status" for method imp___stubs__exit, CODE XREF=++586
000000000116e73b         call       imp___stubs__exit                           ; exit
                        ; endp
```
mov      edi, 0xad 就是173,call exit就不谈了,懂得都懂。
IDA看下伪代码:

我们发现这个函数中执行了exit(173)的操作。

稍有不慎就是exit(173),好狠的手段!欺我风灵月影宗无人!今日我倒要看看,就算我实力不足全盛时期十之二三,你Navicat代码混淆就算没有符号表又能奈我何?区区蝼蚁罢了,本座反手堆栈跟踪覆灭之!

话到一半,却又眉头一紧:"这厮竟然无法使用xref大法查找溯源?事情有些棘手,我却要如何?不如查看堆栈#3看调用方."

当即长啸一声,回到终端:

按下Tab键切换到Threads,找到#3。当即便复制0x0000000108676b38 - 0x0000000107a02000 = 0xC74B38,拿到地址后质问IDA Pro,冷笑道:“你可知此地址为何处?我已知晓其中关键,再敢搪塞本座,必要你小命!”

那IDA惶恐道:


```c
char sub_C74B20()
{
+(&OBJC_CLASS___AppStoreReceiptValidation, "validate");
return 1;
}
```
原来此乃Navicat不知名内存偏移调用方,以指针方式访问内存地址,故不可得到其实际引用。

既然+是exit(173)祸首,那么此人也不必留着,该永绝后患。适时眼中寒芒一闪,表面却淡淡一笑“这次就饶你一条狗命!滚!”

那Navicat正兀自暗喜自以为暗桩未被神识检测到,却不料被反手Hook掉了要害罩门:
```ts
    hook(
      getClassMethod("IAPHelper", "- isProductSubscriptionStillValid"),
      (ths, retv) => {
      retv.replace(ptr(1)); //app ProductSubscription仍然有效
      }
    );

    Interceptor.replace(
      new NativeFunction(
      getClassMethod("AppStoreReceiptValidation", "+ validate").target,
      "void",
      []
      ),
      new NativeCallback(
      function () {
          console.log("Hook ptrace Bypass!!!");
      },
      "void",
      []
      )
    );
```
我冷笑一声按下F5启动frida

霎时间之间Navicat惨叫一声,当场殒命。

“灭你如灭蝼蚁!”

## 4.收尾
最后将
//switchMethod(getMethodStrByCls(@"AppStoreReceiptValidation",@"validate"), getMethod(, @selector(validate)));取消注释,替换原始函数,编译后完美启动Navicat。

阿妈不准我说藏话,但是如果你需要鼓励,那么--我测你码。

由于使用的方法为Method Swizzing,并没有用偏移地址做hook,所以只要Navicat不改函数名称不删除符号表,这个hook代码将一直通杀后续版本。
二进制参考github:
https://github.com/QiuChenly/MyMacsAppCrack

MIAIONE 发表于 2023-4-8 03:01

我记得win的版本有一个注册机是通过静态替换publickey,然后利用host模拟断网,启动签名验证的破解方法,这么做可以改成任意用户,就是更新会失效,而且地址可能变换,到时候静态patch可能找不准,而且无法更新

yun520530 发表于 2023-3-23 08:13

这么详细的教程感谢分享

dailexing 发表于 2023-3-22 22:26

学习了,正好用得上,谢谢

高锰酸钾 发表于 2023-3-22 22:46

鼠标滚轮速度拉倒下面,可惜是苹果系统的,不知道win可行不可行。先占个位置方便查找。

zx52 发表于 2023-3-23 01:01

666,nice

eric890327 发表于 2023-3-23 02:56

MAC的方法应该跟Windows的不太同,希望有Windows的版本XD

Qwe95 发表于 2023-3-23 06:55

!!! 好厉害!!!

codeaftercode 发表于 2023-3-23 08:10

我的天呐,这么详细的步骤,这么多截图!

connoryan 发表于 2023-3-23 08:21

楼主真厉害,原来这个工具是这样破解的

blindcat 发表于 2023-3-23 08:33

学习下,感谢大佬分享
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 破解Navicat Premium 16.1.7: 基于lldb动态调试配合静态注入+FridaHook