引言
想玩游戏了,某下载平台需要花钱购买激活码才能显示下载链接
于是就开破
正文
查壳
拖入exeinfope查查壳先,发现啥也没有,如下图
就一简单.NET程序,倒是方便,比上次破的某excel插件还简单
开始逆向
激活码判断逻辑
拖入dnSpy开始狠狠的分析,C#这种解释语言就是好,连变量名和函数名都给你安排的明明白白
见过中英文混输的,没见过拼音和英语混的(迷惑外国友人??)
全程序集查找“邀请码”三个字,得出下述函数
最后一个QueDing_click起名真是难绷, 进去看看,发现内含下述代码
if (YQMB.tt1.Length > 6)
{
this.DengLu();
return;
}
盲猜一波这个 YQMB.tt1.Length就是邀请码的长度,先检查输入够不够7位,够了再执行条件体内的代码,至于DengLu()函数,我相信此处无需多言了
跟进DengLu(),熟悉的中文跃入眼帘。
if (this.IsLoging)
{
MessageBox.Show("正在激活,请勿重复操作!");
return;
}
看来是来到了关键函数了,再往下面翻翻,发现有post请求,如下述代码
HTTP http = new HTTP();
HttpItem item = new HttpItem
{
Method = "POST",
Postdata = "Action=CheckMachineCode&UserName=" + ServiceLocator.Current.GetInstance<MainViewModel>().tt1 + "&MachineCode=" + YQMB.MachineCode,
URL = Ye1.tt1a005,
ContentType = "application/x-www-form-urlencoded"
};
HttpResult html = http.GetHtml(item);
JObject back = JsonConvert.DeserializeObject<JObject>(html.Html);
if (back == null)
{
MessageBox.Show("连接失败");
return;
}
看来是把用户名和机器码发到服务器去验证,此处可以下个断调试一下,看看发送和接收的数据都长啥样
其中UserName就是我瞎jb乱输的激活码,MachineCode是程序计算的机器码,服务器的返回内容如下(已unicode解码):
{"State":false,"Msg":"激活失败,本邀请码错误","Data":null,"Code":0}
其中的Msg字段刚好对应了提示的弹框
由此看来邀请码的计算算法是在服务器上实现的,搞不了了,不过没有关系,根据http请求紧接着的代码:
bool state = (bool)back["State"];
if (state)
{
this.Login(ServiceLocator.Current.GetInstance<MainViewModel>().tt1, YQMB.MachineCode);
string text = back["Data"].ToString();
if (!string.IsNullOrEmpty(text) && text == "104")
{
int num = int.Parse(back["Msg"].ToString());
if (num > 0)
{
ServiceLocator.Current.GetInstance<MainViewModel>().HuanBang = string.Concat(new string[]
{
"本激活码【",
ServiceLocator.Current.GetInstance<MainViewModel>().tt1,
"】",
back["Msg"].ToString(),
"天后可获得换绑机会"
});
}
if (num == 0)
{
ServiceLocator.Current.GetInstance<MainViewModel>().HuanBang = "本激活码【" + ServiceLocator.Current.GetInstance<MainViewModel>().tt1 + "】支持换绑其他电脑";
}
return;
}
return;
}
可知程序是通过判断服务器返回内容中的State字段来判断邀请码是否正确,我们可以在这里下手术刀,判断state的语句的IL中间代码如下
4 0008 ldfld bool YQMA.YQMB/'<>c__DisplayClass12_0'::state
5 000D brfalse 80 (0114) ldarg.0
我们只需要把brfalse指令改成brtrue即可,更改后的逻辑是:如果state是false,那么就激活,反正目的达到了就行,我们可以直接右键-反转更改快速改了它
更改后,源代码窗口自动的就同步更改
或者直接在brtrue判断指令前面加一条ldc.i4.0指令,直接把false压入堆栈,这样就不管state是啥,if都满足条件了,如图
经过分析,发现程序有两处判断,也就是要改两个state,函数调用图解如下
至此,只要随便输入任意邀请码,都可以激活,也就可以正常显示了。
如果只想玩到游戏的话,那么到这里其实工作就可以结束了。接下来对程序的剩余部分再行分析一些。
检测更新逻辑
程序在启动时会检查更新,如果有更新会提示更新,更新服务器地址加了密,经动态调试直接解密得地址为阿里云服务器
内含配置文本如下(节选)
可知判断更新和核心配置就从这儿下载的,把更新的代码全部删掉即可屏蔽更新。
激活码加密逻辑
所有游戏的下载链接、激活码等信息都包含在服务器的一个文件中,程序只需要拉取这个文件即可向用户显示出来,发现代码中存在一行这样的玩意儿:
string text = Path.Combine(Services.AppDataDirectory, "WenJian.json");
用everything全盘搜索此文件,果然在系统临时目录找到了,打开一看,是部分DES加密的
{"Version":"202406022152024112","Content":[{"Id":1,"BH":"wcM2a5Zm+ng=","MM":"ocMsr4JndLc=","Name1":"oiGjoLIwegJXxLL8UwjTz+LkxsysmZa8","Name2":"oiGjoLIwegJXxLL8UwjTz4fP0QLEb6IP","B
不过没有关系,密钥已经写到程序里面了,如下
// Token: 0x04000282 RID: 642
private static byte[] Keys = new byte[] { 18, 52, 86, 120, 144, 171, 205, 239 };
// Token: 0x04000283 RID: 643
private static string DefEncryptKey = "consmkey";
直接丢给GPT让它帮写解密的代码,并写入CSV文件,如下:
import time
import csv
from Crypto.Cipher import DES
import base64
import json
def decrypt(ciphertext_base64):
# DES密钥向量
keys = bytes([18, 52, 86, 120, 144, 171, 205, 239])
# 加密密钥
encrypt_key = "consmkey"
# 创建DES加密器
cipher = DES.new(encrypt_key.encode('utf-8'), DES.MODE_CBC, IV=keys)
# 待解密的密文
ciphertext = base64.b64decode(ciphertext_base64)
# 解密
plaintext = cipher.decrypt(ciphertext)
# 去除填充
plaintext = plaintext.rstrip(b'\x08')
plaintext = plaintext.rstrip(b'\x07')
plaintext = plaintext.rstrip(b'\06')
plaintext = plaintext.rstrip(b'\05')
plaintext = plaintext.rstrip(b'\04')
plaintext = plaintext.rstrip(b'\03')
plaintext = plaintext.rstrip(b'\02')
plaintext = plaintext.rstrip(b'\01')
plaintext = plaintext.rstrip(b'\00')
# 打印解密后的明文
return plaintext.decode('utf-8')
with open ("WenJian.json", "r", encoding="utf-8") as f:
data = json.load(f)
f.close()
with open('game.csv', 'w', newline='', encoding='utf-8') as file:
writer = csv.writer(file)
for i in data['Content']:
data = []
name1 = i['Name1']
print('当前解密:',decrypt(name1))
data.append(decrypt(i['BH']))
data.append(decrypt(name1))
data.append(decrypt(i['Name2']))
data.append(decrypt(i['MM']))
data.append(decrypt(i['RongL']))
writer.writerow(data)
file.close()
结果经过excel美化后,如下
至此,整个程序就分析的大差不差了
结语
过程中需注意dnSpy对于多线程的支持不是很好,若想查看异步的代码需要在视图-选项-反编译器中勾选反编译异步方法和显示编译器生成的隐藏类型和方法,如下图,默认是不勾的
而且,在调试的时候多线程经常出现奇奇怪怪的问题,建议不要用dnspy的调试,而是把文件写出后直接运行,问题就减少了大半,坏处就是不能单步代码了,略考验代码功底哈哈哈。