注:本文仅用于技术研究与探讨,不提供任何成品和补丁程序
收集信息
今天要逆向的是XX格子这款部分功能付费的Office插件。先安装好后打开看看,看它都有啥可以调教的功能,发现了一个VIP窗口,那这就有意思了,狠狠的开破。
随后我们点击加入会员,点击离线登录,弹出如下窗口:
关键信息都已经取得了,那就开始进入分析环节。
因为现在微软加载项都是以vsto进行部署的,所以我们先打开目录下的vsto文件,看看它调用了哪些DLL库,vsto文件关键节选如下:
<dependency>
<dependentAssembly dependencyType="install" codebase="FFCell.dll.manifest" size="25452">
<assemblyIdentity name="FFCell.dll" version="2.0.0.0" publicKeyToken="b0c5e50cded54578" language="neutral" processorArchitecture="msil" type="win32" />
<hash>
<dsig:Transforms>
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<dsig:DigestValue>HswFQTT/AEBRt6Esc2Qlgpkx1Nc=</dsig:DigestValue>
</hash>
</dependentAssembly>
</dependency>
不难发现其首先带调用的是FFCell.dll这个库,那我们直接开干,拖入exeinfope查壳,发现其为.NET程序并且加了混淆,如下图所示:
可以发现其为IntelliLock加密,无所谓,我会出手,but技术水平有限,就不手工脱壳了,直接拖入NET Reactor Slayer脱壳机,可以直接一键脱掉。
脱壳机
开始分析
基本代码分析
脱壳完毕后直接拖入dnSpy开始分析,我们直接搜索“绑定”,发现找不到,于是把函数名看看,发现关于软件注册的函数并没有在这个模块里面,那我们重新猜想,把目录里面的DLL都拿来分析了下,发现除了本体FFCell.dll外,还有若干个DLL也同样加了壳,猜测这几个DLL可能也是作者原创的DLL,再次执行上述的脱壳大法后拖入dnspy分析,可以在Newmem.dll这个文件中找到关于软件注册的函数,函数名也是如此,那我们就不难推断其实这个DLL专门就是管软件注册的事情的
关于软件注册的函数
随后我们再次搜索关键字符串“绑定”,可以发现出现了结果
在多个函数中均出现了关键字符串,那无所谓我们一个个看,经过分析,发现这几个函数分别对应不同的注册方式,分别有离线和在线两种。
首先先进行离线注册的分析,可在代码中发现确定绑定的按钮如下:
this.Button1.TabIndex = 0;
this.Button1.Text = "确定绑定";
this.Button1.UseVisualStyleBackColor = true;
this.LinkLabel1.AutoSize = true;
那我们就Button1对应的事件,长这样子:
internal virtual Button Button1
{
get
{
return this.button_0;
}
set
{
EventHandler value2 = new EventHandler(this.method_2);
if (this.button_0 != null)
{
this.button_0.Click -= value2;
}
this.button_0 = value;
if (this.button_0 != null)
{
this.button_0.Click += value2;
}
}
}
可以看出好像method_2方法是点击这个按钮后的,无所谓是不是总之我们先跟进去看看就是,节选如下:
private void method_2(object sender, EventArgs e)
{
int num;
int num4;
object obj;
try
{
IL_01:
ProjectData.ClearProjectError();
num = -2;
IL_0A:
int num2 = 2;
string machineName = Environment.MachineName;
IL_13:
num2 = 3;
string computerNo = VipRegV2Helper.GetComputerNo(machineName);
IL_1E:
num2 = 4;
string text = this.TextBox1.Text;
IL_2D:
num2 = 5;
if (Operators.CompareString(text, "授权码", false) != 0)
{
goto IL_4B;
}
IL_42:
num2 = 6;
text = "";
IL_4B:
num2 = 8;
if (Operators.CompareString(text, "", false) != 0)
{
goto IL_7B;
}
IL_5F:
num2 = 9;
Interaction.MsgBox("授权码不能为空", MsgBoxStyle.OkOnly, "提示");
IL_76:
goto IL_2E3;
IL_7B:
num2 = 12;
string left = this.TextBox3.Text.Trim();
IL_91:
num2 = 13;
if (Operators.CompareString(left, this.VerfyImage1.code, false) == 0)
{
goto IL_CC;
}
IL_B0:
num2 = 14;
Interaction.MsgBox("验证码错误", MsgBoxStyle.OkOnly, "提示");
IL_C7:
goto IL_2E3;
IL_CC:
num2 = 17;
string str = "";
IL_D6:
num2 = 18;
if (VipRegV2Helper.CheckAccessCode(text, computerNo, ref str))
{
goto IL_11A;
}
IL_E9:
num2 = 19;
Interaction.MsgBox("授权码错误,原因:" + str, MsgBoxStyle.OkOnly, "提示");
IL_106:
num2 = 20;
this.VerfyImage1.Gen();
IL_115:
goto IL_2E3;
IL_11A:
num2 = 23;
DateTime expireDateFromAccessCode = VipRegV2Helper.GetExpireDateFromAccessCode(text);
IL_125:
num2 = 24;
if (DateTime.Compare(expireDateFromAccessCode, DateTime.Now) < 0)
{
goto IL_1BF;
}
IL_13F:
num2 = 31;
string userNameFromAccessCode = VipRegV2Helper.GetUserNameFromAccessCode(text);
IL_14B:
num2 = 32;
LoginHelper.SetLocalLogin(userNameFromAccessCode, "----");
IL_15C:
num2 = 33;
VipOfflineHelper.SetLocalExpireDate(expireDateFromAccessCode);
IL_166:
num2 = 34;
VipRegHelper.g_isReg = true;
IL_170:
num2 = 35;
VipRegHelper.GetVipUility().SetVipValid(true);
IL_180:
num2 = 36;
Interaction.MsgBox("离线绑定成功!绑定至" + expireDateFromAccessCode.ToString("yyyy年MM月dd日") + "\r\n\r\n到期后可继续申请绑定。", MsgBoxStyle.OkOnly, "离线绑定成功");
IL_1AF:
num2 = 37;
this.DialogResult = DialogResult.OK;
IL_1BA:
goto IL_2E3;
IL_1BF:
num2 = 26;
IL_1C3:
num2 = 27;
Interaction.MsgBox("授权码中绑定的日期已经过期", MsgBoxStyle.OkOnly, "提示");
IL_1DA:
num2 = 28;
this.VerfyImage1.Gen();
IL_1E9:
goto IL_2E3;
IL_1EE:
int num3 = num4 + 1;
num4 = 0;
@switch(ICSharpCode.Decompiler.ILAst.ILLabel[], num3);
IL_296:
goto IL_2D8;
IL_298:
num4 = num2;
if (num <= -2)
{
goto IL_1EE;
}
@switch(ICSharpCode.Decompiler.ILAst.ILLabel[], num);
IL_2B4:;
}
不难看出这个函数其实就是判断看用户输入的注册码是否正确,但是还存在部分没有脱壳脱干净的代码,不过无伤大雅,已经不影响我们对代码的判读了。其实就是先根据电脑名称算一个机器码出来,然后与用户输入的注册码进行对比,对比上了就说明输入正确,予以注册,否则就弹错误窗口,期间还有注册码格式的检查过程等。其中,关键的函数是
if (VipRegV2Helper.CheckAccessCode(text, computerNo, ref str))
{
goto IL_11A;
}
这个CheckAccessCode函数就是我们要找的关键了,跟进看看:
不难发现就是判断注册码输对了没,然后返回一个bool类型,如果是true就说明对了,可以注册,是false就说明不对,不让注册,那我们思路就非常清楚了,可以在函数的返回处下断进行调试。
此处下断直接强行改变量为true,这下不得不给我狠狠注册了
随后程序带着true跳出CheckAccessCode函数,返回到method_2函数,因为改成true了所以现在要判断注册多长时间,因为我刚才的注册码是胡乱敲的,怎么可能包含注册有效时间在里面,所以我们还要在我们再改一处,当程序来到这里的时候我们可以修改expireDateFromAccessCode变量的值,如下图所示:
直接把到期日期设置为2099年1月1日,随后我们继续运行程序,可以看到我们梦寐以求的窗口弹出来了:
至此,我们就成功拿到了会员身份,但是还是有局限。
全程需要断网运行,否则软件一旦联网就发现你这个会员身份是假的,就给你覆盖掉了,又成为了非会员身份。
每次用这个插件都要先用dnspy调试下断改变量,老麻烦了
那首先我们先解决第二个问题,那既然每次都要用,那我们为什么不直接输入正确的注册码呢,那我们就对注册码的算法来一波狠狠的扒皮。
授权码算法分析
通过对多个函数的分析,为了方便读者阅读,特画出超强理解的流程图,保证你一看就懂。
注册表分析
如果觉得上面注册码算法过于困难,还是有点看不懂,没有关系,其实我们还可以利用注册表法来直接把到期时间改了,不难发现SetLocalExpireDate函数就是拿来把到期日期写进电脑系统的注册表的,其代码如下:
public static void SetLocalExpireDate(DateTime dt)
{
Simple3Des simple3Des = new Simple3Des("为防止泄露密钥已隐藏");
string v = simple3Des.EncryptData(dt.ToString());
RegistryHelper.SetValue("FFCELL_VIPOFFLINE_SETTING", "OfflineExpireFlag", v);
}
可以很清楚的发现这是三轮的DES加密,密钥也直接写死在代码中了,为防止泄露,在本文中就隐藏掉了,我们拿到密钥后自己把授权到期日期一加密手动写进注册表就直接达到了授权到2099年的目的了。
再提一嘴,除了加密授权到期的日期外,如果用的是联网登录账号,那么你的账号和密码也是用这个算法来加密存储在注册表里面的。
通信协议分析
联网验证也没关系,我们只需要分析一下通信内容即可,先找到判断是否为VIP的联网通信函数
public static bool GetVipInfo(ref string info, ref VipUser vip1)
{
string localUserName = LoginHelper.GetLocalUserName();
string localEnpwd = LoginHelper.GetLocalEnpwd();
bool result;
if (Operators.CompareString(localUserName, "", false) == 0 | Operators.CompareString(localEnpwd, "", false) == 0)
{
result = false;
}
else
{
try
{
Login login = new Login();
string vipInfo = login.GetVipInfo(localUserName);
if (vipInfo == null)
{
info = "无法连接服务器,请检查网络或联系我们!\r\n" + Information.Err().Description;
result = false;
}
else if (vipInfo.IndexOf("RegOK") > 0)
{
string text = "#@x#";
info = "";
int num = vipInfo.LastIndexOf(")");
string text2 = vipInfo.Substring(checked(num + 1));
string[] array = Regex.Split(text2, text);
if (array.Length != 5)
{
result = false;
}
else
{
if (Operators.CompareString(array[0].ToLower(), "t", false) == 0)
{
vip1.isValid = true;
}
else
{
vip1.isValid = false;
}
vip1.userName = array[1];
vip1.expireDate = array[2];
vip1.lastPc = array[3];
vip1.vipscore = array[4];
if (Operators.CompareString(vip1.vipscore, "", false) == 0)
{
vip1.vipscore = "0";
}
result = true;
}
}
else
{
info = vipInfo;
result = false;
}
}
catch (Exception ex)
{
info = "执行过程出现异常:" + ex.Message;
result = false;
}
}
return result;
}
注意上述代码的第34行,如果从服务器接收的值是t就是会员,是其他值就不是会员,那我们使用Fiddler抓包看看
请求:
POST /vip/VipRegV2/VipRegVer2.aspx HTTP/1.0
接收:
(RegOK:1)f#@x#UserName#@x#2023/2/18 23:51:32#@x##@x#0
可以看到接收的内容中有f,我们猜测这可能就是和是否为会员相关的值,我们使用fiddler的拦截功能将数据包拦下,修改成t后再放行此数据包 ,可以看见已经成为会员了。
接收内容的最后那个0猜测就是会员等级,也可以随意修改的。
总结
至此,这个软件就分析的差不多了,还有其他的一些小功能在此就不再赘述了,本文起到一个抛砖引玉的作用,读者可以参考本文自行分析其余的功能模块,若想获得VIP权限,有如下几种方法。
每次使用软件时用dnSpy调试,修改局部变量, 在内存中改成VIP;
分析授权码算法,自行编写注册机进行授权;
直接拿密钥写注册表;
拦截通讯数据包,抓包改包,欺骗软件自己已经是VIP了。
上述1~3条方法建议配合断网使用,否则可能会失效,不过也可以通过hook方法屏蔽掉它的联网功能,此处不再赘述。
版权归原作者 Frank MARS 所有, 如有侵权,请联系我们删除。