本帖最后由 macyoyo 于 2023-12-24 14:32 编辑
前言故事的由来前几天有人在求助区发了一个求助帖,,通过分析源程序代码,发现帖子的贴图注册码明显不对,于是就勾起了我的好奇心! 然后我就在虚拟机中安装了,打开注册窗口直接弹出错误信息崩溃了(后来经过分析是因为在虚拟机中调试的原因),这我的暴脾气就上来了,到底要看看怎么个情况,查找问题的途中学到了很多新东西,所以就发出来和大家分享. 分享的目的分享的目的不是为了让大家去破解这个软件,而是在破解的途中学习关于Visual Studio 开平台下的C#编程语言中的Winform开发模式,学习别人的代码架构和注册验证逻辑,还有一些功能实现的代码应用. 破解是逆向的过程,那么开发就是正向的过程,和衣服一个道理,只有知道衣服是怎么穿上的,脱衣服的时候才能游刃有余! 对于这个逆向的研究将会分为三个独立的帖子进行解析,依次是: 1.程序崩溃的代码修复-中等难度 某工程制图插件逆向(一)程序崩溃的代码修复 2.各种爆破点的定位和效果点评(本帖内容)-初级难度 3.注册码的分析与注册机的编写-高级难度 某工程制图插件逆向(三) 注册码分析和注册机的编写 所需工具dnspy(查看代码/修改程序) 前期准备完成"程序崩溃的代码修复"帖子所有内容. 请看完上一篇帖子再阅读本帖某工程制图插件逆向(一)程序崩溃的代码修复 从注册的视角分析注册按钮事件定位还记得之前我们编写调试程序的代码吗?定位到那个类和注册窗口对比 首先看一下InitializeComponent方法, 对于C#的Winform程序员来说再熟悉不过了,是加载窗体控件数据的,通过里面的代码我么可以获得如下信息: button1控件是激活按钮,并有相应的按钮事件 进入button1事件方法中可知 string text = this.textBox1.Text.ToString(); textBox1文本框控件是我们输入的激活码 if (text.Length == 46 && text == this.softReg_0.GetRNum2())
{
PDT.setStatus(SoftReg.VALIDATE_STATUS.FUL);
} 还记得之前我们在SoftBuy类中InitializeComponent的代码吗? 里面对VALIDATE_STATUS属性标注了说明,有正式版,有效期正式版,试用版,过期版,四个状态 这个按钮事件代码太多我就不贴太多了,大体上就是对比计算出来的注册码和输入的注册码对比. 等等..捋一下思路,为什么邀请码的对话框是灰色的呢? 好,我们看一下预加载程序 this.textBox2.ReadOnly = true; 哦~,原来textBox2是邀请码的文本框,而且设置了只读属性 修改程序代码嗯~既然程序里获取的注册码和输入的对比,那么我们直接把获取的注册码在加载的时候自动填写到注册码输入框不就完事了? 说干就干~ 在预加载方法public FormValidate()中,右键选择修改方法 我们加一行代码,把上面标红的报错代码删掉 this.textBox1.Text=this.softReg_0.GetRNum2(); 然后编译,保存文件(记得备份一下未修改的文件,以后要用) 效果评估打开宿主软件,打开注册窗口,注册码已经自动填写进去了 点击注册按钮,提示已激活 重新打开注册窗,已经显示是正式版了 从绕过注册过程的视角分析分析注册验证的过程中都有哪些操作我们恢复这个文件到刚才未修改之前的状态,然后把我们上一篇帖子提到的那个硬盘ID修改一下,就可以恢复未注册状态了 继续分析注册时的代码内容然后我们回到激活按钮的验证事件代码中 if (text.Length == 46 && text == this.softReg_0.GetRNum2())
{
this.bool_0 = true;//注册状态标志
string text2 = "UPDATE TB_SOFT SET SOFT_KEY='" + text + "' where SOFT_TIMES='0'";
//写入注册信息
if (Class29.smethod_1(text2) > 0)
{
PDT.setStatus(SoftReg.VALIDATE_STATUS.FUL);//设置授权类型
ToolBox.SHOW_AUTHENICATE_INFO(4);//弹出提示框
}
} 通过对这段代码的反向调用跟踪得知 this.bool_0 = true状态最终停在了FormSoftBuy类中的public bool getResult()方法内,并未被使用,也有可能是其他程序调用了这个方法,当然感觉不保险可以直接修改返回true即可 关于Class29.smethod_1(text2) 通过反向调用跟踪得知是向SQL数据库中写入注册信息,这里不做过度分析,有兴趣的可以研究研究. 我们重点关注一下 PDT.setStatus(SoftReg.VALIDATE_STATUS.FUL) 通过反向调用跟踪得知软件通过这个状态来验证注册状态,我们反向跟踪一下 public static void setStatus(SoftReg.VALIDATE_STATUS aStutas)
{
PDT.status = aStutas;
} 在插件主命名空间(DLL的主入口),对PDT.status进行了赋值 并且setStatus这个方法只在上面的验证注册码时进行了赋值,我们暂且放下. 然后我们对PDT.status进行反向跟踪,看看这个字段的值都在哪里使用了 授权状态标志大揭秘赋值有两处1.在当前类下的 private void method_5()
{
for (;;)
{
PDT.status = PDT.softReg_0.Validate();
Thread.Sleep(10000);
}
} 2.另一个我们已经分析过了,就是注册过程中的赋值 跟进PDT.softReg_0.Validate() 在PDT.classes.SoftReg类中 public SoftReg.VALIDATE_STATUS Validate()方法 大体内容就是从SQL数据库中读取注册时保存的注册码数据,然后对数据进行逐个比较来判断授权类型,和我们注册时的按钮事件方法内容大同小异. 读取有两处1.PDT.forms.FormSoftBuy类中的public FormSoftBuy()预加载方法中 通过对比这个值和对应的授权类型来显示授权状态,只影响到注册框的文本内容, 而且你仔细观察会发现在比较之前调用了PDT.validate(); 2.在PDT.PDT类中(DLL入口类) public static bool validate()
{
if (PDT.status != SoftReg.VALIDATE_STATUS.FUL && PDT.status != SoftReg.VALIDATE_STATUS.TEM)
{
if (PDT.status != SoftReg.VALIDATE_STATUS.FUL_PRIOD)
{
if (PDT.status == SoftReg.VALIDATE_STATUS.OUTOFDATE)
{
ToolBox.SHOW_AUTHENICATE_INFO(1);
return true;
}
return PDT.status == SoftReg.VALIDATE_STATUS.ERR && false;
}
}
return true; 通过代码分析我们得知如果有使用权限这个方法就返回true 我们不做过多分析,后面会有详细说明 修改关键代码绕过注册码验证因为是读取PDT.status的状态,所以我们需要让这个值直接返回对应符合条件的授权类型 1.修改method_5()的赋值代码,直接返回授权 2.修改SoftReg类中的SoftReg.VALIDATE_STATUS Validate()返回值 3.找一个合适的地方对PDT.status进行赋值(这是废话,忽略忽略...) 我推荐使用第二种方法,也就是对SoftReg.VALIDATE_STATUS Validate()方法内代码进行修改,为什么呢? 第一种方法你99.999%的可能性无法通过修改代码方式编译,除非你使用IL代码,至于为什么后面会详细讲解. 第三种方法虽然是废话,但是如果你对这个软件的架构和执行逻辑已经非常熟悉的话是可以找得到相应的位置的,甚至可以通过HOOK的方式来实现(非本帖讨论范畴) 比如我们在上一篇帖子提到的那个窗口的激活按钮事件里 我们直接把调用注册码窗体的代码删掉,改成赋值PDT.status 当然需要添加命名空间和相关代码,难度稍稍高一些,不过对于会C# Winform开发的程序员来说这都不是事,只不过每次运行软件都需要点击一下激活按钮. 我们开工 dnspy右键修改方法,代码就一行 编译一下,然后保存文件打开软件注册框看效果 OK,没有任何问题,不用输入注册码直接就是正式版~Nice~ 从插件功能授权验证视角分析我们从注册码的过程中分析完了,我们再从宿主软件调用插件的视角看一下这个软件应该怎么过验证. 当然要把我们脱壳后的程序替换回来,变成最开始没有修改过的状态 分析DLL主入口类切换到PDT.PDT类中 通过命名空间我们可以发现这里调用了大量的宿主软件引入库文件 这也是为什么我上面提到的那个99.99你不能通过代码方式编译的原因,因为这些文件在PDT的目录下根本不存在,所以我们要把宿主软件中的这些文件复制到插件目录下 补全文件具体复制哪些文件可以看这个插件的引用,用到什么就复制什么 没有三角符号的就不需要复制,因为不是NET程序 分析代码通过当前类中的代码我们可以发现,大部分方法中都进行了验证操作 if (PDT.validate()) 必须返回true才能执行对应的功能,否则就会调用show_validate()方法 public static void show_validate()
{
PDT.fv = new FormSoftBuy();
PDT.fv.StartPosition = FormStartPosition.CenterScreen;
PDT.fv.ShowInTaskbar = false;
Application.ShowModelessDialog(Application.MainWindow.Handle, PDT.fv, false); 通过代码可以看出只要不返回true就会调用注册窗口(前面分析过的,不再解释) 所以我们要把PDT.validate()强制返回True 把前面的代码全部删掉,直接一行代码搞定 public static bool validate()
{
return true;
} 编译之后保存,看效果 虽然依然是未注册状态,但是插件正常使用已经不受影响了. 当然我们也可以给这个方法的条件改为满足条件,那就是上面讲到的直接赋值PDT.status 好了,正向和逆向已经完全对接上了,打通了任督二脉,具体用什么招式的武功完全看个人喜好了. :lol 实用小技巧我们在使用修改代码的方式进行操作时,代码中标红的代码一般是因为无法读取解析上下文代码中的代码关系,所以我们就要去还原或者修补代码,上下打通了,才能顺利编译代码. 记得我提到的IL方式修改代码吗,这个不受上下文代码影响,但是要保证栈平衡,具体怎么操作呢? 我们在方法代码中右键选择编辑IL指令 然后把上面的代码全部NOP掉,只保留 ldc.i4.1 ret
对于C#逆向来说,学习一些必要的IL指令知识和用法是非常必要的,对dnspy的编译器不可能像Visual Studio的编译器那样完善和强大,某些情况下只能用IL指令修改的方式来修改程序 个人点评对于这个软件,爆点是相当的多,所有关键验证参数都是用返回值,这样就给了逆向的人更多的操作空间,如果编写注册机,甚至都不需要拷贝代码,直接调用插件本身的方法返回函数就可以直接获取所有和注册相关数据,甚至是插件功能的返回数据. 对于初级程序员来说,通过引用的方式来实现获取数据 对于高级程序员来说,甚至可以在不脱壳的情况下利用C#的反射功能来获取相关数据,甚至是修改内存数据来实现. 总结虽然作为一个官方已经近乎放弃更新的插件,里面还是有很多值得我们学习借鉴的代码,比如操作数据库,宿主软件的插件API参数,和宿主软件的一些操作函数. 同时也为广大程序员提了个醒,软件验证爆点越多,软件越容易被逆向,毕竟基于NET开发的软件代码保护程度相对来说都比较低. 对于软件的逆向,有时候思路比技术更重要,有时候顺着来不行就倒着来,找因果关系,因果关系理清了,软件开发者挖的坑我们才能顺利避开. 逆向的目的不是为了白嫖资源,而是为了规避哪些已经暴露的潜在风险. 创作不易,感谢大家的支持
注:若转载请注明大神论坛来源(本贴地址)与作者信息。
|