前言
前段时间报考了计算机二级的python,在报名页面加了考点Q群,群主居然在推销题库。看它题库挺新的,就想买来练下手。下载之后看着粗制滥造的界面顿时感觉这四十多块好不值。激活的时候发现它有离线激活,于是萌生破解的想法,权当一次练手了
小白第一次破解,可能写得比较啰嗦,见谅~~
软件地址:aHR0cDovL3d3dy53eWs4LmNvbS8=
分析
掏出exeinfope看看有没有壳先
49cd3efda3e8d885abcda67ba3344d64.png (189.33 KB, 下载次数: 2)
下载附件
2025-1-9 10:56 上传
欸嘿,没壳而且还是 .NET,这不就简单了吗
直接拖进dotPeek反编译分析一下
d714298eca3609a74d52b973b9e19dde.png (83.9 KB, 下载次数: 2)
下载附件
2025-1-9 10:55 上传
在入口文件 Program 中,注意到这段代码,解密了 Register.UI.dll 然后动态加载
计算 Exam.exe 的MD5,作为AES128-ECB的密钥,然后解密再加载
需要注意的是文件的末尾还附加了 Register.UI.dll 解密后的MD5,用于校验完整性
合理猜测,这个加密的dll应该就是跟激活有关的
写个py脚本解密看看先
import cryptography
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import modes, Cipher
import hashlib
with open("./Exam.exe", 'rb') as f:
file_data = f.read()
md5_hash = hashlib.md5(file_data).digest()
with open("Register.UI.dll", "rb") as f:
encrypted_data = f.read()
cipher = Cipher(algorithms.AES128(md5_hash), modes.ECB())
decryptor = cipher.decryptor()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
with open("Register.UI.dec.dll", "wb") as f:
f.write(decrypted_data)
解密完还是拖到dotpeek分析,一眼锁定 Activation
aeef34e8db316abd9daf72966402a355.png (37.42 KB, 下载次数: 2)
下载附件
2025-1-9 10:55 上传
找到个 VerifyRegisterCode 函数,应该就是验证注册码的
b3956e1a8e62f597a0e969a96e268e59.png (64.5 KB, 下载次数: 2)
下载附件
2025-1-9 10:55 上传
追踪一下调用,到了 ActiveWindow 里的 ActiveOffline 函数,这里的 card 和 password 对应的应该就是卡号和卡密
138d2bc31211a2dc8f802153b4b2959f.png (41.48 KB, 下载次数: 2)
下载附件
2025-1-9 10:54 上传
06b5e897a60436ed1433f5adf74da633.png (73.39 KB, 下载次数: 2)
下载附件
2025-1-9 10:54 上传
到这里就发现不对劲了,WebAccessor.GetOffineActiveCode 难道还要联网获取注册码?
2bf1bc8ee52c87212d1d463332f6255a.png (66.78 KB, 下载次数: 2)
下载附件
2025-1-9 10:53 上传
好吧,还真是得联网,这怎么破解,完结
继续分析
正当我一筹莫展的时候,突然想到既然我不能通过生成注册码的方式激活,那能不能直接绕过激活,直接就是已激活的状态呢
接着前文分析,在 ActiveOffline 函数中,注意到当注册码校验成功之后,会给 IsActivation 赋值为true
PersionVersionEncryptModuleEntry.IsActivation = true;
定位到 IsActivation 的位置
da08908b0e69d704aea4aab3988df0f0.png (21.63 KB, 下载次数: 2)
下载附件
2025-1-9 10:53 上传
可以看到,基本都是通过这个变量确定是否激活,包括公共变量 IsActivationed 也是读取该变量
f80179bf7cb67de477bd88599d7bd168.png (103.91 KB, 下载次数: 2)
下载附件
2025-1-9 10:52 上传
至此,我们的目标就是想办法修改 Register.UI.dll ,使得 get IsActivation 时的值始终为True
反编译&重编译
一开始,看到dotpeek可以直接导出C#源码,我就直接导出了。
但是由于我完全不会C#,visual studio打开项目之后一堆报错,好不容易把报错消完了,编译出来的还跑不了(┬┬﹏┬┬)
后来在网上搜索.NET反编译的原理时了解到,反编译时其实是先把dll转为IL代码,再把IL代码转换成C# (应该没理解错吧)
那既然C#跑不通,那就试试底层一点的IL代码,还不用处理各种依赖关系
在dotPeek中刚好可以对照着看IL代码,非常方便,从 windows -> IL viewer 打开
32171b2050db9c78a59976d0ed8ae757.png (57.26 KB, 下载次数: 1)
下载附件
2025-1-9 10:52 上传
嗯,很好,看不懂一点 无所谓,直接把IL扔给LLM
6e1809628dde6b97fca285cc58b8d088.png (56.32 KB, 下载次数: 2)
下载附件
2025-1-9 10:51 上传
GPT真好用
现在的步骤就很明确了:
实践
使用ildasm反编译
.\ildasm.exe /UTF8 ./Register.UI.dec.dll /out=Register.UI.il
修改IL
61d76142194ad33a0b53bfb157956c73.png (65.39 KB, 下载次数: 1)
下载附件
2025-1-9 10:51 上传
重编译
.\ilasm.exe .\Register.UI.il /output=test.dll /dll
补上MD5,重新加密
import cryptography
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import modes, Cipher
import hashlib
def encrypt_file(file_data: bytes, key: bytes) -> bytes:
padding_data = hashlib.md5(file_data).digest()
cipher = Cipher(algorithms.AES128(key), modes.ECB())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(file_data+padding_data) + encryptor.finalize()
return encrypted_data
with open("./Exam.exe", 'rb') as f:
file_data = f.read()
md5_hash = hashlib.md5(file_data).digest()
with open("./Register.UI.dll", "wb") as f:
with open("./test.dll", "rb") as f2:
f.write(encrypt_file(f2.read(), md5_hash))
替换掉原来的 Register.UI.dll ,测试成功
3cf3ad370b358dd78082b2bc9b8674ec.png (173.21 KB, 下载次数: 2)
下载附件
2025-1-9 10:49 上传
后记
第一次成功破解软件,成就感满满~~。虽然文章写得挺顺利的,但是前前后后还是了折腾三天,走了不少弯路。浅浅记录了一下,不当之处还请大佬们指正。