记录一下一个.Net阅读器的两种破解方式

查看 150|回复 11
作者:Benx1   
声明
本文章中所有内容仅供研究、学习交流使用,不能用作其他任何目的,严禁用于商业用途和非法用途,否则一切后果自负,与作者无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请发送邮件[email protected]联系论坛管理员删除文章

XX阅读器133,下载见 aHR0cDovL3JlYWQuaG9tZTEzMy5jb20v
原分析帖链接:https://www.52pojie.cn/thread-1949654-1-1.html

1、检查软件
运行起来看看
软件运行起来是个框框,右键这个框框有菜单可以注册,注册之后会提示快到期了。

右键系统->会员可以看到会员信息

大概的会员信息都在这里了,下一步
拖到DIT里面查壳
一眼顶针,C# .Net 有混淆,先不管,直接丢到dnspy看代码

代码分析
直接在dnspy里面搜索 “会员”

可以看到有4个结果,一个个打开看,其实第一条就是我们要找的结果,仔细观察这个函数可以得到两个很关键的点,一个会员状态,一个过期时间,会员状态本身就是个 enum 枚举有3个状态,会员(永久会员),非会员(期限会员),过期。

根据代码可以看到,这个状态是联网获取的并且请求和响应没有加密,那这里就有两个方式破解了:
[ol]
  • 抓包改相应,直接修改
  • patch程序
    [/ol]
    2、破解方式一、抓包破解
    分析请求和响应
    掏出我们的老朋友HttpDbgPro开始分析,先直接开软件,可以看到登录请求,是个GET请求,请求头里面带上了UID,这个UID就是软件界面显示的UID,实际就是用这个UID判断用户的,响应可以看到是明文的

    我们把json格式化一下

    伪造响应
    重要数据都出来了,我们直接在HttpDbg里面修改响应的status为1

    不对了,现在怎么提示离线版本了,右键菜单也没会员选项了
    刚开始以为有校验,仔细看了下伪造的响应,发现回复的数据比之前的数据小

    是被自动回复截断了,改成用文件回复

    然后在文件里面改下数据,就可以正常回复,这时候我们再重新打开软件,可以显示会员信息了,但是我们打开会员信息发现和之前的没有变化
    伪造获取用户信息响应

    但是在httpdbg上可以看到有一条新的请求

    和登陆请求差不多,也是获取信息的,那我们把这条请求也进行伪造

    伪造完了点一下刷新用户信息

    可以看到我们已经变成终生会员了,至此,会员部分就分析完了。
    小应用部分
    刚开始一直不知道这个积分有什么作用,但是他放在这里就肯定有放在这里的原因,仔细去找了找,发现了个好玩的地方---应用中心

    这里有一些其他的小软件,需要消耗积分下载,我们点击公考行测两万题下载,提示了积分不足,看看请求和响应
    响应如下:
    {"status":402,"msg":"\u60a8\u7684\u5206\u4eab\u503c\u4e0d\u8db3\uff0c\u65e0\u6cd5\u4e0b\u8f7d\r\n\u8bf7\u524d\u5f80\u3010\u7cfb\u7edf\u3011-\u3010\u6ce8\u518c\u3011\u6a21\u5757\u83b7\u53d6\u66f4\u591a\u7684\u5206\u4eab\u503c"}
    根据前面的响应我们就能猜到这个正确响应的状态码应该是200,我们改成200试试

    这里不赘述怎么修改响应了,和之前的一样方法,只是需要注意自动回复设置成“含有”URL,因为每个App的ID是不一样的


    可以看到开始下载了,下载完点击打开又出问题了

    提示你妹购买,看来还有二次检测。
    同样的方式,在HttpDbg里面看请求,有一条 “checkApp” 的请求,把他的状态码也改成200

    现在就能正常打开了,答题功能也能用,其他的APP也是同理了,但是我们退出之后再进来应用中心,发现又变成下载了,虽然每次都能正常打开,但是每次都要下载还是挺麻烦的。这个状态每次重开应用中心就会被重置,那肯定有网络请求。
    和前面的一样,仔细看就会有个getAppList请求,分析请求就可以发现,你已有的应用的status是1,没有的就是0

    手动把这些状态全部改成1,就可以看到我们已经下载过的APP就不会需要再次重新下载了。

    至此,伪造响应破解的分析就完成了,要写成代码很简单,就留给评论区的大牛们去做了。我们开始下一步,Patch程序。
    3、破解方式二、修改软件
    分析程序
    前面已经说了,判断是不是会员的关键点在BindRegister()这个函数里面,关键点就在Bis.LoRes.UserStatus = regResult.Status;在这里设置了用户的会员状态。
    修改
    修改会员
    我们已知永久会员的状态是1,直接右键编辑IL指令

    我们先把这两句选中,右键用NOP替换,然后在15行填上idc.i4.1(常数1),然后点确定

    可以看到代码里面赋值的地方已经被我们替换成一个常量了。注意看下面的switch语句,判断的是服务器返回值的status,所以还需要修改一下,和上面的同理。

    上面的这个修改了之后switch就变成判断1了,其实也可以改成本地的数据判断,就是改Call,但是我懒得改了,有兴趣的话自己研究一下,不难。
    然后 左上角-》文件-》全部保存,可以加点后缀,比如破解会员,避免自己搞混了,然后再运行

    要注意,dnspy的调试必须先保存再调试才有效果,我们暂时不需要调试,所以保存了之后直接去打开这个exe看效果


    可以看到我们已经变成终身会员了,会员功能也有效果
    修改应用中心
    修改下载
    应用中心和之前的一样,下载会判断,所以需要修改判断点,直接在dnspy搜索“需要消耗”,搜出来的第一个就是弹窗提示的内容

    我们点进去可以看到很明显的get请求,判断状态是不是等于200

    直接右键-》编辑IL指令,这里有几种改法,一个是改成0不执行,跳过这段代码,另一个是取反,让他等于200的时候才提示不足,或者直接删除return等等,我们这里直接取反

    选择这4条语句,右键反转分支即可,因为我们没有积分,返回值必定不可能是200。

    这里修改完之后再保存一下,然后看效果。最好做一步保存一步,这样即使有问题闪退了,也能在上次修改保存好的文件基础上再改

    这里已经可以下载了,但是打开和之前的抓包一样,有二次检测会提示没有购买,所以要找一下检测的点并且修改。
    修改二次检测
    二次检测稍微麻烦一点,因为提示的内容是服务器返回的,搜不到字符串,所以要跟一下流程。
    我们先返回上一步修改下载的位置,可以看到这一块是打开App的,但是没有调用函数,只是改了状态

    所以我们这一步要右键这个this.start然后点击分析,可以看到读取和赋值都是那个函数调用了,追一下可以追到下面这个函数

    代码精简了,分开看就知道是在干嘛了
    // 动态加载指定路径的DLL文件
    Assembly assembly = Assembly.LoadFile(Util.GetAppPath("/app/" + app.Path) + "/" + app.Path + ".dll");
    // 获取assembly中指定类名的Type对象
    Type type = assembly.GetType(app.ClassName);
    // 使用反射创建type类型的实例
    object obj = Activator.CreateInstance(type);
    // 如果obj实现了IACSetter接口,将调用其SetIAC方法
    if (obj is IACSetter setter)
    {
        // 创建一个AppController实例并传给SetIAC方法
        setter.SetIAC(new AppController(app));
    }
    进这个类就可以看到

    这个检测函数了,但是这个检测函数值负责返回,不判断。分析也没有发现调用,那说明是小App的DLL调用了这个方法,小APP有点多,不可能一个个去改,这个函数留给我们的操作空间也不是很多,因为没有声明临时变量,所以修改ApiResult的内容还挺麻烦的,那我们换个思路,让他请求一个返回status返回200的地址。
    打开抓包软件,看看软件请求的哪个数据一直都是200返回值的

    找到了一个符合要求的,这个是每次退出都会调用的,我们吧参数改一下看看是不是还返回200
    经过测试修改参数之后返回的还是200,说明没对传入的参数检测

    那我们修改一下访问的路径

    直接右键编辑C#方法,把这个改成active.php或者修改获取的改成获取active的。
    保存,测试

    OK,可以打开了,但是我们还是遇到了和抓包破解一样的问题,每次都要重新下载。
    处理状态检测
    我们再去找状态检测的地方,继续返回到修改应用中心的地方,在这个类里面可以看到有添加item到列表的操作,那我们就看看在哪里设置了按钮的状态,往上翻翻可以看到一个private void SetActionStyle(ReApp app) 的函数设置了按钮的状态,
    这里是用switch语句判断添加的

    我们点击这个成员变量,找到他定义的地方(这里直接点分析是不行的,因为他是初始化过的一个Map(字典)),要找往字典添加的方法调用,所以先跳转到它定义的地方,因为是个私有变量,所以肯定在这个类内部添加的数据,在代码搜索btnActions

    很容易就能找到赋值的地方,关键点就是这个rApp.Status == 1,那我们就把这个判断去掉,右键,编辑IL指令

    修改后的C#代码如下

    这样就没有状态判断了,默认都是已经购买了的。
    我们保存打开看看效果(和上面图一样,我就不贴了),可以看到下载了的软件默认就是打开按钮,不需要再次下载了,至此,分析结束。
    4、后语

    具体的IL指令代表的什么意思就不解释了,可以百度或者问GPT都可以得到很好地回答。

    这个软件分析没花多久,我个人认为这个软件比较简单,很适合新手练手,不管是Patch技巧还是抓包姿势都很适合。但是还是那句话,只做分析讨论,不提供成品,如有需求请支持正版。

    可以看到, 会员

  • RCKLV   

    学习了,感谢楼主。恭喜荣登“吾爱破解”公众号文章,特地过来收藏点赞!
    jha334201553   

    还有方法三:
    hosts 文件加入
    "

    然后运行本地服务注册
    [Python] 纯文本查看 复制代码#!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import json
    from flask import Flask
    app = Flask(__name__)
    # http://read.home133.com/s.php?u=3616926
    @app.route("/",methods=['GET','POST'])
    def home_page():
        return "home123 register server"
    @app.route("/apii/autoLogin_2.php",methods=['POST'])
    def login():
        user_info = {}
        user_info['status'] = 200
        user_info['msg'] = 'success'
        user_info['data'] = {}
        user_info['data']['last_version'] = '1.2.0.5'
        user_info['data']['update_version'] = '1.2.0.0'
        user_info['data']['chapter_ai_url'] = 'http://read.home133.com/apii/chapter_ai_url.php'
        user_info['data']['member_share_cnt'] = 999999
        user_info['data']['point_day'] = 999999
        user_info['data']['qq_group'] = '760414837'
        user_info['data']['user_name'] = '13800000000'
        user_info['data']['user_create_time'] = '2000-01-01 00:00:00'
        user_info['data']['phone'] = '13800000000'
        user_info['data']['nick_name'] = '本地注册用户'
        user_info['data']['nick_flag'] = 0
        user_info['data']['sex'] = 0
        user_info['data']['head_img'] = None
        user_info['data']['channel'] = ''
        user_info['data']['status'] = '0'
        user_info['data']['expire_date'] = '2099-12-30'
        user_info['data']['msg'] = {}
        user_info['data']['msg']['msg'] = '本地终身会员'
        user_info['data']['msg']['cap'] = '极简单行阅读器提示'
        user_info['data']['msg']['url'] = ''
        user_info['data']['msg']['close'] = false
        user_info['data']['active_id'] = '676875C1DC56B4C0F1DC6F30BB7BE753'
        return json.dumps(user_info)
    @app.route("/apii/getRegInfo.php",methods=['POST'])
    def reg_info():
        register_info = {}
        register_info['status'] = 200
        register_info['msg'] = 'success'
        register_info['data'] = {}
        register_info['data']['status'] = '0'
        register_info['data']['expire_date'] = "2099-12-30"
        register_info['data']['total_share'] = 999999
        register_info['data']['share'] = 999999
        register_info['data']['share_ucnt'] = 999999
        register_info['data']['pay_point'] = 999999
        return json.dumps(user_info)
    if __name__=='__main__':
        app.run(host='127.0.0.1', port=80, debug=True)
    luxingyu329   

    高手,也不知道怎么样才能达到这个水平?
    yinxzh   

    感谢楼主!
    pizazzboy   

    楼主很出色,谢谢。
    dglbh   

    HttpDbgPro能按不同的请求的方法返回不同的数据吗?
    Benx1
    OP
      


    dglbh 发表于 2024-8-2 16:09
    HttpDbgPro能按不同的请求的方法返回不同的数据吗?

    同一个URL的GET和POST吗,应该是不能的,HttpDbgPro是根据URL来自动回复的,不能判断请求方式。
    而且市面上应该不会有GET POST 使用同一个URL的这种设计
    user52pj   

    牛逼啊,真牛逼!(不要判我灌水啊,由衷的由感而发,发自肺腑地)
    lancelot623   

    很详细,感谢分享,学习一下
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部