吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5336|回复: 54
收起左侧

[.NET逆向] 记录一下一个.Net阅读器的两种破解方式

  [复制链接]
Benx1 发表于 2024-8-1 20:39

声明

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

XX阅读器133,下载见 aHR0cDovL3JlYWQuaG9tZTEzMy5jb20v

原分析帖链接:https://www.52pojie.cn/thread-1949654-1-1.html

1、检查软件

运行起来看看

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

1.png

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

2.png

大概的会员信息都在这里了,下一步

拖到DIT里面查壳

一眼顶针,C# .Net 有混淆,先不管,直接丢到dnspy看代码

3.png

代码分析

直接在dnspy里面搜索 “会员”

4.png

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

5.png

根据代码可以看到,这个状态是联网获取的并且请求和响应没有加密,那这里就有两个方式破解了:

  1. 抓包改相应,直接修改
  2. patch程序

2、破解方式一、抓包破解

分析请求和响应

掏出我们的老朋友HttpDbgPro开始分析,先直接开软件,可以看到登录请求,是个GET请求,请求头里面带上了UID,这个UID就是软件界面显示的UID,实际就是用这个UID判断用户的,响应可以看到是明文的

6.png

我们把json格式化一下

7.png

伪造响应

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

8.png

不对了,现在怎么提示离线版本了,右键菜单也没会员选项了

刚开始以为有校验,仔细看了下伪造的响应,发现回复的数据比之前的数据小

9.png

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

10.png

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

伪造获取用户信息响应

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

12.png

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

13.png

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

14.png

可以看到我们已经变成终生会员了,至此,会员部分就分析完了。

小应用部分

刚开始一直不知道这个积分有什么作用,但是他放在这里就肯定有放在这里的原因,仔细去找了找,发现了个好玩的地方---应用中心

15.png

这里有一些其他的小软件,需要消耗积分下载,我们点击公考行测两万题下载,提示了积分不足,看看请求和响应

响应如下:

{"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是不一样的

16.png

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

17.png

提示你妹购买,看来还有二次检测。

同样的方式,在HttpDbg里面看请求,有一条 “checkApp” 的请求,把他的状态码也改成200

18.png

现在就能正常打开了,答题功能也能用,其他的APP也是同理了,但是我们退出之后再进来应用中心,发现又变成下载了,虽然每次都能正常打开,但是每次都要下载还是挺麻烦的。这个状态每次重开应用中心就会被重置,那肯定有网络请求。

和前面的一样,仔细看就会有个getAppList请求,分析请求就可以发现,你已有的应用的status是1,没有的就是0

19.png

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

20.png

至此,伪造响应破解的分析就完成了,要写成代码很简单,就留给评论区的大牛们去做了。我们开始下一步,Patch程序。

3、破解方式二、修改软件

分析程序

前面已经说了,判断是不是会员的关键点在BindRegister()这个函数里面,关键点就在Bis.LoRes.UserStatus = regResult.Status;在这里设置了用户的会员状态。

修改

修改会员

我们已知永久会员的状态是1,直接右键编辑IL指令

21.png

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

22.png

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

23.png

上面的这个修改了之后switch就变成判断1了,其实也可以改成本地的数据判断,就是改Call,但是我懒得改了,有兴趣的话自己研究一下,不难。

然后 左上角-》文件-》全部保存,可以加点后缀,比如破解会员,避免自己搞混了,然后再运行

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

24.png

可以看到我们已经变成终身会员了,会员功能也有效果

修改应用中心

修改下载

应用中心和之前的一样,下载会判断,所以需要修改判断点,直接在dnspy搜索“需要消耗”,搜出来的第一个就是弹窗提示的内容

25.png

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

26.png

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

27.png

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

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

这里已经可以下载了,但是打开和之前的抓包一样,有二次检测会提示没有购买,所以要找一下检测的点并且修改。

修改二次检测

二次检测稍微麻烦一点,因为提示的内容是服务器返回的,搜不到字符串,所以要跟一下流程。

我们先返回上一步修改下载的位置,可以看到这一块是打开App的,但是没有调用函数,只是改了状态

28.png

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

代码精简了,分开看就知道是在干嘛了

// 动态加载指定路径的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));
}

进这个类就可以看到
30.png

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

打开抓包软件,看看软件请求的哪个数据一直都是200返回值的

31.png

找到了一个符合要求的,这个是每次退出都会调用的,我们吧参数改一下看看是不是还返回200

经过测试修改参数之后返回的还是200,说明没对传入的参数检测

32.png

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

33.png

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

保存,测试

34.png

OK,可以打开了,但是我们还是遇到了和抓包破解一样的问题,每次都要重新下载。

处理状态检测

我们再去找状态检测的地方,继续返回到修改应用中心的地方,在这个类里面可以看到有添加item到列表的操作,那我们就看看在哪里设置了按钮的状态,往上翻翻可以看到一个private void SetActionStyle(ReApp app) 的函数设置了按钮的状态,

这里是用switch语句判断添加的

35.png

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

36.png

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

37.png

修改后的C#代码如下
38.png

这样就没有状态判断了,默认都是已经购买了的。

我们保存打开看看效果(和上面图一样,我就不贴了),可以看到下载了的软件默认就是打开按钮,不需要再次下载了,至此,分析结束。

4、后语

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

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

免费评分

参与人数 13吾爱币 +12 热心值 +12 收起 理由
wasdzjh + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
yuanwiuming + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Skyeddy + 1 谢谢@Thanks!
apull + 1 + 1 谢谢@Thanks!
zzccyydd + 1 + 1 谢谢@Thanks!
lccccccc + 2 + 1 我很赞同!
RCKLV + 1 + 1 用心讨论,共获提升!
RikimaruMarlon + 1 + 1 谢谢@Thanks!
wei5383079 + 1 + 1 用心讨论,共获提升!
LinkWorld + 1 谢谢@Thanks!
vmoranv + 1 热心回复!
ly871108 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

jha334201553 发表于 2024-8-16 22:10
本帖最后由 jha334201553 于 2024-11-7 23:05 编辑

还有方法三:
hosts 文件加入
127.0.0.1 read.home133.com

然后运行本地服务注册
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/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/getPreRegInfo.php",methods=['GET'])
def pre_reg():
    pre_register = {}
    pre_register['status'] = 200
    pre_register['msg'] = 'success'
    pre_register['data'] = {}
    pre_register['data']['member_flag'] = '0'
    pre_register['data']['share_flag'] = "1"
    pre_register['data']['share_code'] = ''
    pre_register['data']['last_version'] = '1.2.0.5'
    pre_register['data']['update_version'] = '1.2.0.0'
    return json.dumps(pre_register)
 
# 用户登录
@app.route("/apii/userLogin.php",methods=['GET'])
def login():
    user_info = {}
    user_info['status'] = 200
    user_info['msg'] = 'success'
    user_info['data'] = '3616926'
    return json.dumps(user_info)
 
# 第二次自动登录
@app.route("/apii/autoLogin_2.php",methods=['GET'])
def auto_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']['append'] = []
    # user_info['data']['site'] = []
    # user_info['data']['app'] = []
    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=['GET'])
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(register_info)
 
# GET ?activeId= HTTP/1.1\r\n
@app.route("/apii/active.php",methods=['GET'])
def active_last():
    active_info = {}
    active_info['status'] = 200
    active_info['msg'] = 'success'
    return json.dumps(active_info)
 
if __name__=='__main__':    app.run(host='127.0.0.1', port=80, debug=True)


登录信息存储在   
%ALLUSERSPROFILE%\Read133

免费评分

参与人数 1吾爱币 +1 收起 理由
aaa31415 + 1 我很赞同!

查看全部评分

RCKLV 发表于 2024-8-16 12:03
学习了,感谢楼主。恭喜荣登“吾爱破解”公众号文章,特地过来收藏点赞!
luxingyu329 发表于 2024-8-2 13:52
pizazzboy 发表于 2024-8-2 14:42
楼主很出色,谢谢。
dglbh 发表于 2024-8-2 16:09
HttpDbgPro能按不同的请求的方法返回不同的数据吗?
 楼主| Benx1 发表于 2024-8-2 16:48
dglbh 发表于 2024-8-2 16:09
HttpDbgPro能按不同的请求的方法返回不同的数据吗?

同一个URL的GET和POST吗,应该是不能的,HttpDbgPro是根据URL来自动回复的,不能判断请求方式。
而且市面上应该不会有GET POST 使用同一个URL的这种设计
user52pj 发表于 2024-8-2 17:33
牛逼啊,真牛逼!(不要判我灌水啊,由衷的由感而发,发自肺腑地)
lancelot623 发表于 2024-8-2 19:23
很详细,感谢分享,学习一下
dglbh 发表于 2024-8-2 22:36
Benx1 发表于 2024-8-2 16:48
同一个URL的GET和POST吗,应该是不能的,HttpDbgPro是根据URL来自动回复的,不能判断请求方式。
而且市 ...

是post 不同的方法
msmvc 发表于 2024-8-3 11:09
思路清晰流畅
a653 发表于 2024-8-4 18:36

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

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-1 09:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表