Logo

破解 某软件记录

August 5, 2025

首先需要感谢的是这个软件并没有做太多的邪恶校验(当然这是因为App Store不允许混淆加壳吧hhhhh)

打开是卡在激活界面的,因为点击会跳转到itunes到app store,所以可以在这里找字符串(itunes)从而定位到-[NOMORawStoreView jumpAction],然后往上跳转到-[NOMORawStoreView nomoItemClick]

void __cdecl -[NOMORawStoreView nomoItemClick](NOMORawStoreView *self, SEL a2)
{
  void *v3; // x20
  void *v4; // x21
  void *v5; // x22
  void *v6; // x23
  void *v7; // x23
  unsigned __int8 v8; // w24

  if ( -[NOMORawStoreView checkNetworkAccessIsOK](self, "checkNetworkAccessIsOK") )
  {
    self->userHasClickedBtn = 1;
    v3 = objc_msgSend(
           objc_alloc((Class)&OBJC_CLASS___NSUserDefaults),
           "initWithSuiteName:",
           CFSTR("group.com.blink.academy.nomo"));
    v4 = objc_retainAutoreleasedReturnValue(objc_msgSend(v3, "objectForKey:", CFSTR("NOMO-Receipt")));
    v5 = objc_retainAutoreleasedReturnValue(objc_msgSend(v3, "objectForKey:", CFSTR("NOMO-Token")));
    v6 = objc_retainAutoreleasedReturnValue(objc_msgSend(v3, "objectForKey:", CFSTR("NOMO-VIP")));
    objc_release(v6);
    if ( v6 )
    {
      v7 = objc_retainAutoreleasedReturnValue(objc_msgSend(v3, "objectForKey:", CFSTR("NOMO-VIP")));
      v8 = (unsigned __int8)objc_msgSend(v7, "boolValue");
      objc_release(v7);
      if ( (v8 & 1) == 0 )
        goto LABEL_10;
    }
    else if ( !objc_msgSend(v4, "length") && !objc_msgSend(v5, "length") )
    {
      goto LABEL_10;
    }
    if ( self->userHasClickedBtn )
    {
      -[NOMORawStoreView autoCheckNOMOStatus](self, "autoCheckNOMOStatus");
LABEL_11:
      objc_release(v5);
      objc_release(v4);
      objc_release(v3);
      return;
    }
LABEL_10:
    -[NOMORawStoreView jumpAction](self, "jumpAction");
    goto LABEL_11;
  }
}

简单阅读可以发现主要的判断逻辑是对xxxx-VIP这个key的,所以考虑直接把objectForKey都监听一下看看是哪个地方引用了。
frida-trace -U -N "com.blink.academy.nomoraw" -m "-[* objectForKey:]",然后改下代码

defineHandler({
  onEnter(log, args, state) {
    let key = new ObjC.Object(args[2]).toString();
    log(`-[xxxClass objectForKey:${key}]`);
  },

  onLeave(log, retval, state) {
      log(`-[xxxClass objectForKey:${this.key}] -> ${typeof retval} ${new ObjC.Object(retval)}`);
  }
});

可以看到只有NSUserDefaults被用了,所以直接hook这个就好了。

    const objectForKey = NSUserDefaults['- objectForKey:'];
    const objectForKey_old = objectForKey.implementation;
    objectForKey.implementation = ObjC.implement(objectForKey, (self,sel,any) => {
        const key = new ObjC.Object(any).toString();
        if(key === "NOMO-VIP") {
            console.warn(`----------\\nO: -[NSUserDefaults objectForKey:${key}] -> 1\\n----------`);
            return 1
        }
        return objectForKey_old(self,sel,any);
    })

嗯这样发现跑不了?继续看ida可以看到还有个地方检测了subscription_is_validsubscription_expire_at,这两个一个是bool一个是int,那就得把这几个全都hook一遍了

于是,最终代码:

if(ObjC.available) {
    console.log("ObjC.available: true");
    const fakePrefs = {
        "xxxx-VIP": true,
        "subscription_is_valid": true,
        "subscription_expire_at": "7983066142000",
    };

    const NSUserDefaults = ObjC.classes.NSUserDefaults;
    const boolForKey = NSUserDefaults['- boolForKey:'];
    const boolForKey_old = boolForKey.implementation;
    boolForKey.implementation = ObjC.implement(boolForKey, (self,sel,any) => {
        const key = new ObjC.Object(any).toString();
        if(key in fakePrefs) {
            console.warn(`----------\\nO: -[NSUserDefaults boolForKey:${key}] -> ${fakePrefs[key] ? "true" : "false"}\\n----------`);
            return fakePrefs[key] ? 1 : 0;
        }
        let retval = boolForKey_old(self,sel,any);
        return retval;
    })

    const stringForKey = NSUserDefaults['- stringForKey:'];
    const stringForKey_old = stringForKey.implementation;
    stringForKey.implementation = ObjC.implement(stringForKey, (self,sel,any) => {
        const key = new ObjC.Object(any).toString();
        if(key in fakePrefs) {
            console.warn(`----------\\nO: -[NSUserDefaults stringForKey:${key}] -> ${fakePrefs[key]}\\n----------`);
            return ObjC.classes.NSString.stringWithString_(fakePrefs[key]);
        }
        let retval = stringForKey_old(self,sel,any);
        return retval;
    })

    const integerForKey = NSUserDefaults['- integerForKey:'];
    const integerForKey_old = integerForKey.implementation;
    integerForKey.implementation = ObjC.implement(integerForKey, (self,sel,any) => {
        const key = new ObjC.Object(any).toString();
        if(key in fakePrefs) {
            console.warn(`----------\\nO: -[NSUserDefaults integerForKey:${key}] -> ${fakePrefs[key]}\\n----------`);
            let value = parseInt(fakePrefs[key]);
            return value
        }
        let retval = integerForKey_old(self,sel,any);
        return retval;
    })

    const objectForKey = NSUserDefaults['- objectForKey:'];
    const objectForKey_old = objectForKey.implementation;
    objectForKey.implementation = ObjC.implement(objectForKey, (self,sel,any) => {
        const key = new ObjC.Object(any).toString();
        if(key === "NOMO-VIP") {
            console.warn(`----------\\nO: -[NSUserDefaults objectForKey:${key}] -> 1\\n----------`);
            return 1
        }
        if(key in fakePrefs) {
            console.warn(`----------\\nO: -[NSUserDefaults objectForKey:${key}] -> ${fakePrefs[key]}\\n----------`);
            return fakePrefs[key];
        }
        return objectForKey_old(self,sel,any);
    })

    console.log("Hooked");
}

PS: 现在启动的时候必须要进入界面然后点击激活才能用。同时在激活后还会弹窗,所以这个方式显然是不对的。

如果是走dylib注入就没那么多事了,在程序启动时直接校验正确了就不会有后面什么事了(笑

Update 8.5 网上找了下还真有,但是我不贴link, 想要自己去找吧(

comment

留言 / 评论

如果暂时没有看到评论,请点击下方按钮重新加载。