首页
社区
课程
招聘
七夕福利, GrindEQ公式套装注册算法
发表于: 2023-8-22 18:25 13455

七夕福利, GrindEQ公式套装注册算法

2023-8-22 18:25
13455

目标软件是GrindEQ word-to-latex, 一个可以在word和latex, mathml之间转换的工具. 为何搞这个就说来话长了.
拖延症老半仙几个月前买了一个标准文档, ISO停售了, IEC官方买的谁知道也是扫描版.
为了将它转为pdf, ABBYY识别, 手工精校, 输出为pdf但是有几个公式无法正确识别, 用图片方式则是无法满足(强迫症)纯文字能复制的标准, 输出成docx后手动复原公式发现word里的公式小里小气的, 最后想到了latex的公式, tex宣传是能精确控制每一个元素的排版语言, 它生成的同样公式已经和扫描版相当接近了.
但是从docx转latex时候, 试了一些在线工具和离线工具pandoc等, 并不能识别到等宽文字, 而原扫描文档正文里面大量混合了关键字排版, 关键字都是等宽粗体.
一个个加太不累人了.
然后看到有人推荐这个word-to-latex, 看界面挺好看的啊, 估计做的也比那些丑东西完善吧, 兴冲冲下载安装, 打开我精校的docx, 一转换word本身直接崩溃了. 然后照着官方试验怎么设置避免崩溃呢, 这软件的10次试用也到期了.
网上搜了下只有些老版本的破解, 爆破, 我所不能接受也! 干脆看看能不能逆出算法吧.
经过查看过期时候弹出的字符串和MessageBox的调用栈, 在GrindEQw2l.cnv定位到了一个提示过期函数:

这个插件是集成在office里面, 该函数在选择输出tex时候调用的, 第一次调用时候msgbox参数固定传入的负值因此不会弹出过期提示, 第二次是非负就弹出过期提示了.
弹出过期提示后会设置注册表, 然后下次就不弹, 干扰分析.
这个gchkloop计数器是在另一个函数func_chkmd5里面循环检测的, 为0则不断检查用户授权的md5, 计次试用期间它的值会在func_placeopt中*2+1, 从0,1,3,7一直递增, 不过到1就不再循环了, 估计也是干扰人的分析, 用算术运算取代逻辑赋1.
10次计次未用完时候, 为何不会弹出过期提示呢? 要么是没有调用这个函数, 要么是调用时候参数是负值. 在虚拟机全新安装了一次, 跟踪发现只有一次负值调用, 第二次不再调用. 那么第二次的调用在哪里发生的呢?
就在刚提到的func_chkmd5:

这个ghashmap里面正常会填充0x100个md5的hash, 他用了类似hmac的算法确保注册用户的授权信息都落在这个map里面, 当我们伪造了一个用户放入注册表后, 它拿出来hash一下就会导致map新增一项纪录(下标操作符在key不存在时候会执行插入), 从而size变成0x101, 实现了正确和错误的注册用户调用的是不同的函数, 因为有abs给参数整形, 调用到func_showExpired时候参数一定是非负, 从而弹出对话框.
那么问题就来了, 这个ghashmap里面预执的那些key, 我们有没有办法碰撞出来呢? 这个key是一个23字节的hashsrc算出来的, 看看它是如何生成的吧:

可见这是个明文加盐操作, 盐在末尾, 来自于官方授权系统发放的注册码, 这个盐和明文的角色在官方看来可能相反, 末尾是15字节明文(他们知道), 然后根据用户注册信息的md5首字节加8字节可变盐在前面, 生成256种组合, 在代码中插入到ghashmap.
要想写注册机而不是爆破, 我们就得通过操作用户授权信息和注册信息末尾的15字节明文, 生成一个在表里存在的hash. 前半部分的8个YN我们不用管, 因为官方设计的就是不管用户名和日期都是啥, 生成的前缀都在256个范围内. 得把后面的15字节的跑出来, 算了下要好多好多显卡年(647667346537838518), 似乎此路不通啊. 那么还有一个办法就是去买一次官方软件, 解出这个后缀, 一查价格$99, 教育优惠是$49, 看了下这里面三个插件cnv里面内置的hashmap表都不同, 要买得买三套, 正在我到处找学生兄弟代购教育授权的时候, 我突然想起了之前的迷思, 计次试用未过期时候, 这个ghashmap是怎么避免插入的? 立刻运行一波, 生成的头部是NYYYNYNY, 附加后缀是tthegaofhecould, 结果是7a125e22cdbfeeff44ec824c3870ecbd, 这字串恰好是在表里的. 而过期后, 生成的头部是NYYYNNNN, 结果dd47febc1be4f5d974ebde499f5dcf44不在表内, 就跑进了报错函数.
在注册表有注册信息的时候, 这个头部的pre值来自注册信息的产品id+版本号+标志位+各种时间+编号+用户名, 经验上好的散列函数每个bit的概率都接近正态分布, 我们很容易"碰撞"出首字节是AE的md5结果. 写了个程序验证, 我选择控制注册信息中一个四字节的"启动次数", 循环2^32次, 跑了下花费7分20秒, 其中命中了16774599次AE. 是不是很接近16777216? 平均一下就是每次碰撞耗时, 26.23us.

然后序列号怎么存储到注册表和怎么显示? 代码在插件的GrindEQw2l.dll里面, 是类似于base64但更换了映射表, 315字节注册信息对应的是420位序列号.

brbEbbb4kXd3ZGcexpCzaSSbbUUbHk12xp6bbbGbsrajb3UbxbbTb1bbbbbbbbbbbbbbbbbbbbabbrbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbuzYgodxObbbabbbbbbbbbURYbbbbGZw!k95iJOHObbbbbbbbbbbbbbbbbbbbbbbbbrbbbb
bbbbbbbqQXpb1bbbbbbbz0KkbbbbaMdqG2bbbbbbbapb1bbTsbBbbbbbbbS1PxdqG2bbbbbqQXpb1bb4obska!bqkbkb*MdqG2b
bzbzqQXpb1bbaQbbbalb36bUbbbbbbbbb2bQqQXpb1bbUbU6CHsbGbb1bbbbbbbbbbbbbbbbbbbbbGbbbbbbbbbUbbbbbbbbbbb
bbbbx4HRe0E!A4xWspY!A3Mh

该段序号是我构造的在launches递增到5时候(就凑巧产生了碰撞)的注册信息.
用户注册信息的二进制格式不再赘述, 只有"关键"的五个时间字段进行了交织编码, 可能是为了让字段的变化尽可能分散避免碰撞吧.
regtime 19,17,15,13
time1 7,5,3,1
time0 6,4,2,0
time8 18,16,10,8
launches 14,12,11,9
regtime 不能小于2020年3月, 注册标志是限期订阅的话, time0从当地时间一天前算过期. time8是time0的镜像.

我又收集了一下其他版本的15字节后缀和base64表, 回头优化下注册机发上来, 有时候生活就像一部老式游戏, 只是为了清掉一个红点就要往返于各个大陆最后开始荒野求生~~~

老版本的算法也是类似, 网上破解都是爆破, 可能是大佬们觉得md5原像攻击不现实吧!

void func_showExpired(int msgbox)
{
    int chkloop = gchkloop;
    if (msgbox >= 0) {
        MessageBoxW(gMsgHwnd, gMsgCaption.c_str(), gEvalExpired.c_str(), MB_ICONWARNING);
        std::wstring cat(L"tex");
        cat.insert(0, "a"); // atex
        cat.insert(0, "c"); // catex
        cat.resize(3); // cat
        if (gchkloop) {
            gchkloop -= chkloop;
        }
    }
}
void func_showExpired(int msgbox)
{
    int chkloop = gchkloop;
    if (msgbox >= 0) {
        MessageBoxW(gMsgHwnd, gMsgCaption.c_str(), gEvalExpired.c_str(), MB_ICONWARNING);
        std::wstring cat(L"tex");
        cat.insert(0, "a"); // atex
        cat.insert(0, "c"); // catex
        cat.resize(3); // cat
        if (gchkloop) {
            gchkloop -= chkloop;
        }
    }
}
std::string mymd5 = md5hex(hashsrc, 23);
int div16 = pre / 16;
ghashmap[mymd5] = div16;
//这里选择一个func, 不能选报错func?
//100个是111EDCDC
void (__stdcall *)(int)func10 = g_expfuncs[ghashmap.size()];
func10(abs(ghashmap[mymd5])); // abs(div16)
std::string mymd5 = md5hex(hashsrc, 23);
int div16 = pre / 16;
ghashmap[mymd5] = div16;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-8-22 18:54 被曾半仙编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (7)
雪    币: 5537
活跃值: (390213)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享,点赞支持!
2023-8-22 19:50
0
雪    币: 3729
活跃值: (4111)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢大佬分享。
2023-8-22 21:22
0
雪    币: 3135
活跃值: (3932)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-8-22 23:59
0
雪    币: 3614
活跃值: (31036)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-8-23 09:16
1
雪    币: 50161
活跃值: (20630)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
6
半仙 七夕快乐!
2023-8-23 18:09
0
雪    币: 108
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
谢谢楼主分享。
2023-9-1 10:55
0
雪    币: 59
活跃值: (1606)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
楼主,请问帖子里的420位序列号可用吗?输入之后提示注册成功,确定后还是显示未注册唉。
2023-10-7 11:39
0
游客
登录 | 注册 方可回帖
返回
//