本文仅做技术交流,请支持正版软件

前言

邮件这个东西对我来说一直很重要,我最开始用的是巨硬的那个邮件 UWP 客户端,那个是真的好用,但是耐不住巨硬要推他那个 Outlook New,然后邮件 UWP 就死翘翘了

之前我找到一家厂子的邮件客户端很好用,但是它限制普通用户只能有 10 个邮箱账户,我自己的邮箱都不止十个了,这哪里够用,然后我就转战了 DreamMail

但是 DreamMail 我近期发现有些邮件它看不了(体现为邮件内容全白),并且这个客户端存在编码问题,会导致邮件内容显示出问题

于是我就又尝试用回了某厂的这个邮箱软件,但是限制 10 个账户咋办嘛,那就只能发挥我只学了一点点的逆向技术去改一改了

本文旨在记录我逆向研究的过程,请支持正版软件

破解过程

本次用到的客户端版本是某厂的 5.4.2.1011 版本,这个算是一个比较老的版本了

安装软件

这点倒没啥好说的,勾选同意各种协议即可

因为我以前的数据是在的,所以安装完打开就告诉我登录邮箱达到上限了

定位限制所在位置

先看看这个程序是什么东西写的,打开目录发现本体才几百 K,有个版本号目录,点进去发现有 Qt 组件,应该是 Qt 没跑了

尝试找一下提示所在的位置,添加邮箱的时候,软件会弹出一个提示,让我们开通会员

所以直接找提示,又因为在当前目录找到了一个 webui.zip,所以猜软件应该是从 zip 文件中获取它的前端内容,解压一下,直接搜关键词「登录邮箱」,搜出来一坨

直接看到有个 app.min.js,直觉告诉我一般在这种地方,不对的话……再说吧

1
s.createElement(_.AlertMaster,{className:O.account_limit_tips,alertType:"error",alertContent:r.dashiPlusVipState>0?"登录邮箱达到最高上限,建议您适当删减不常用的邮箱。":"登录邮箱达到上限,建议开通大师会员解锁更多邮箱数量或删减不常用的邮箱。",closable:!0,onClose:function(){t.setState({showAddAccountLimitTip:!1}),w.stat(w.STATS_KEYS.pc_accountlist_email_limit_close_click,{})},seamless:!0,actions:r.dashiPlusVipState>0?[]:[{text:"立即解锁",onClick:function(){y.AccountInfoPrefHandler.navigateTo({path:"web",params:{url:t.state.configInfo.ds_setting_accountlist_dashi_master_link||"",isModal:!0,sharedWebId:"pay_vip_window",pageSize:{width:786,height:600}}}),w.stat(w.STATS_KEYS.pc_accountlist_email_limit_unlock_click,{})}}]})

可以看到上面的内容,与我们看到的弹窗基本相符。可以看到这里有一个检测是否为 VIP 的过程,稍微搜一下什么 Account Limit 之类的关键词,可以搜到有一个 addAccountLimit,可以猜测与账户数量限制有关

刚刚说了这是一个 Qt 程序,它的主逻辑也不在那个几百 k 的启动程序,所以再去看看文件,发现有 mastercore.dll

尝试丢进 IDA 反编译,搜索字符串,发现对应的东西

双击以后按 X 看看引用,可以看到有这样的逻辑

1
2
3
4
5
HIBYTE(v24) = 0;
v28[0] = sub_1009211B((char *)&v24 + 3);
sub_1002B300(v37, "addAccountLimit");
sub_10036C40(v37, v28[0]);
sub_1002B220(v37);

进入 sub_1009211B 函数看看,可以发现限制所在的位置

1
2
3
4
5
6
7
8
9
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
char __cdecl sub_1009211B(_BYTE *a1)
{
int v1; // ebx
_DWORD *v2; // eax
int n100_1; // esi
char v4; // bl
int var_24; // [esp+Ch] [ebp-24h] BYREF
int var_20; // [esp+10h] [ebp-20h] BYREF
char Str1[12]; // [esp+14h] [ebp-1Ch] BYREF
_BYTE v9[12]; // [esp+20h] [ebp-10h] BYREF

var_20 = 10;
var_24 = 100;
sub_10091545(&var_20, &var_24);
v1 = sub_1010B2F4();
v2 = (_DWORD *)sub_1070F6F0(v9);
n100_1 = (v2[1] - *v2) >> 3;
sub_1008FDA9(v9);
if ( v1
&& (unsigned __int8)sub_104FC0B0(v1)
&& (sub_1002B300(Str1, "IsVip"), v4 = sub_104E1C60(Str1), sub_1002B220(Str1), v4) )
{
if ( n100_1 >= var_24 )
{
*a1 = 1;
return 1;
}
}
else if ( n100_1 >= var_20 )
{
*a1 = 0;
return 1;
}
return 0;
}

这里对 VIP 和非 VIP 进行了判断,明显下面的 else if 就是我们非会员的情况,限制了 10 个账户,而会员给到了 100 个的额度,在上面的 js 代码中也能看到会员其实也有数量限制,能够确认的是 var_20 是普通用户限制,var_24 是会员用户限制,所以先改个名字为 userLimitvipLimit

1
2
userLimit = 10;
vipLimit = 100;

修改上限数值

对这里的数值进行修改,虽然这里给到了 int 类型,但是我还是改的稍微保守一点,都改成 255 吧(FFh

我这里用 patch bytes 改的,keypatch 不知道为啥会炸

改好了以后预览一下改的内容

然后导出程序,得到修改后的 dll,直接覆盖到程序里面去,结果……没成功?

nop 掉恢复函数

经查发现后面还带着这两个限制变量跑了一个 sub_10091545(&userLimit, &vipLimit);

翻了一下这个函数,是一个从文件读取配置的函数,读取不到就自动恢复成 10 和 100

1
2
3
4
5
6
7
8
9
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
int *__cdecl sub_10091545(int *arg0, int *n100_2)
{
char v2; // bl
int *n100_1; // eax
int v4; // eax
int v5; // eax
_BYTE v6[176]; // [esp+Ch] [ebp-B8h] BYREF
int p_n10; // [esp+BCh] [ebp-8h] BYREF
int n100; // [esp+C0h] [ebp-4h]

v2 = 0;
p_n10 = 10;
n100 = 100;
sub_10507FE0();
if ( (unsigned __int8)sub_10507030(&p_n10, 1) )
{
*arg0 = p_n10;
n100_1 = (int *)n100;
*n100_2 = n100;
}
else
{
if ( (unsigned __int8)sub_1002D970(2) )
{
v4 = sub_1002DA20("E:\\mailmaster\\mailmasterQT\\src\\qui\\ui\\utils\\utils.cpp", 392, 2);
v5 = sub_1002A3E0(v4 + 8, "GetAddAccountLimitConf");
sub_1002A3E0(v5, ": GetAddAccountLimit failed");
v2 = 1;
}
if ( (v2 & 1) != 0 )
sub_1002DB90(v6);
*arg0 = 10;
n100_1 = n100_2;
*n100_2 = 100;
}
return n100_1;
}

我管你那么多,直接不让跑,nop 掉

重新导出一下,覆盖

这下就没有讨人厌的提示了

验证

此时尝试添加一个新的邮箱,发现登陆框正常弹出,游戏结束

做 Patcher

以上原理有了,让 AI 写个脚本来 patch 吧

因为在反编译的代码里面,userLimit 和 vipLimit 都给到了 int 类型,所以可以大胆改成一个很大的值

https://github.com/GamerNoTitle/MailMaster-Account-Limit-Escaper

后记

这是我第一次做这种破解类的事情,但是但是但是,好孩子不要学哦,我做这个只是用来学习的,被 Cracked 的程序已经被我恢复原样了