首先需要感谢的是这个软件并没有做太多的邪恶校验(当然这是因为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_valid和subscription_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, 想要自己去找吧(