去年年底在APP STORE接触到这款软件,准备练一字马,下载来发现里面的某些课要花钱啊,于是就把数据库导出来折腾了一番塞回去,所有课程都免费了;跟着练了4个月,一字马也没劈下去,大概瘦了3公斤吧;后来就一直没练,前不久想练一下,点开软件要注册还要登陆,注册后发现以前明码标价的课程都改成VIP课程了,又想着去折腾数据库,可是这次失败了,软件会联网实时获取课程信息,并把课程信息写在本地数据库里以备断网时用;目前注册用户采用积分制,开通一季度要2700积分,做一个任务大概就是10-50积分的样子,哪有那个精力,刚好最近开始研究ios安全,练练手吧.
所需工具:cycript,ida,class-dump,Clutch
XX瑜伽版本:6.1.7
测试环境:ios8.1.2,iPhone 5s
备注:已提前脱壳并导出头文件
SSH到手机,找到XX瑜伽进程,把cycript附加上去
iPhone:~ root# ps aux | grep Yoga
mobile 36568 0.0 6.6 757060 67944 ?? Ss 1:50PM 0:05.17 /var/mobile/Containers/Bundle/Application/190D5770-9A19-4B5C-96F2-7DCE57C7A725/DailyYoga.app/DailyYoga
root 36606 0.0 0.1 544488 536 s000 S+ 1:52PM 0:00.01 grep Yoga
iPhone:~ root# cycript -p 36568
找到<全部课程>导航页面,从这个页面入手.
获取rootViewController
cy# UIApp.keyWindow.rootViewController
#"<YGTabBarViewController: 0x15ce9e0c0>"
YGTabBarViewController类信息:
YGTabBarViewController是UITabBarController的子类,所以直接用selectedViewController可以获取当前tab页视图.
cy# #0x15ce9e0c0.selectedViewController
#"<RotationAwareNavigationController: 0x15ceb83b0>"
RotationAwareNavigationController类信息:
RotationAwareNavigationController里面没有什么有价值的,直接viewControllers获取当前正在显示的view
cy# #0x15ceb83b0.viewControllers
@[#"<YGHomeViewController: 0x15cd77140>"]
YGHomeViewController类信息:
从名字能猜出一二了,3个Controller对应导航栏的’推荐课程’,’全部课程’,’瑜伽计划’,所以直接查看当前页面对应的控制器totalSessionViewController
cy# #0x15cd77140.totalSessionViewController
#"<YGTotalSessionViewController: 0x15cd78830>"
YGTotalSessionViewController类信息:
sessionsArray很可能就放着全部课程的信息,cycript验证下
YGSessionInfo类信息:
明显YGSessionInfo存放着当前view中课程的所有信息.编写cycript函数验证下, 获取所有vip课程的名称
function printVipSession(s)
{
var vipArray = []
for(view of s)
{
if([[view isVip] isEqual:[NSNumber numberWithBool:true]])
{
vipArray.push(view.sessionName)
}
}
return vipArray
}
执行结果:
总共有7个VIP课程.要想下载VIP课程,最先想到的就是取消课程的vip标志,把所有课程的isVip都设置为false,可以cycript验证下
命令执行完后,所有YGSessionInfo的isVip都设置为false了,那么在手机上点击下VIP课程看下效果
点击后进入了课程详情,说明验证通过了,那么继续点击”下载”按钮,可怕事情出现了.
应了那句话”然而并没有什么卵用”.再次调用isVip,发现并没有被置回true
cy# [view.isVip for (view of s)]
[@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false,@false]
既然不是vip课程,为什么还会提示购买vip,其实是当前view并不读取YGTotalSessionViewController下的YGSessionInfo的信息.
继续找出当前view视图:
YGSessionInfoViewController类信息:
一眼就能看到isBuy,这里修改下isBuy为true
点击”下载”按钮,发现依然弹出购买VIP窗口,查看isBuy的值发现已经被更改为false
cy# #0x1392560e0->isBuy
false
cy#
这里的值显然不是作为最终的参考,一定有其他地方有vip标记的,看了下这个类,里面还有一个YGSessionInfo *_sessionInfo,打印下里面的值:
修改这里的isVip值为false,表示这个课程不是VIP专属课程
Cy# #0x1392560e0.sessionInfo.isVip = false
点击”下载”,该有的都有了,已经可以下载了.
接下来就应该看看YGSessionInfo的数据来自哪里,要从控制器里找原因了,看一下YGSessionInfoViewController的初始化函数viewDidLoad,此函数有两处代码与加载课程相关
第一段:
可见这一段仅仅是获取下瑜伽课程的分解动作.
第二段:
直接去查看loadSessionDetailWithSessionId的代码吧
看样子是要联网获取数据了,格式化GET的数据包.
这是XX瑜伽的api接口,目前版本(6.1.7)将近100个接口.
http请求的格式这样的
返回的信息包含了课程的详细信息
回调函数里面:
再去看看[YGSessionInfo sessionInfoWithDictionary]
这里设置YGSessionInfo所需的数据,所以XX瑜伽的下载页面每次刷新都会由YGSessionService重新拉取服务器的数据,看一下YGSessionService可以发现一些有意思的东西
目测loadAllSessionsArray是拉取所有课程信息,IDA进去看下:
原理弄清楚之后写代码就简单了,可以hook的地方很多,可以在YGSessionInfo中勾住isBuy,isVip或者勾住NSUserDefaults中的sessionHasPurchased等等.随便选个吧.
在<XX瑜伽>中还有另外两种收费项目,<瑜伽计划>以及<瑜伽音乐>
破解思路一致, <瑜伽计划>的类是YGProgram:变量名已经自解释了.
<瑜伽音乐>的类是YGAlbum
Theos源代码:
@interface NSUserDefaults (Addition)
@end
%hook NSUserDefaults
+ (_Bool)sessionHasPurchased:(id)arg1
{
return true;
}
+ (_Bool)musicHasPurchased:(id)arg1
{
return true;
}
%end
@interface YGProgram : NSObject
{
_Bool _isVip;
}
@property(nonatomic) _Bool isVip; // @synthesize isVip=_isVip;
@end
%hook YGProgram
- (void) setIsVip:(bool)bIsVip
{
%orig(false);
}
%end
效果图:
[课程]FART 脱壳王!加量不加价!FART作者讲授!
上传的附件: