吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8379|回复: 59
收起左侧

[.NET逆向] 某辅助软件注册逻辑分析

  [复制链接]
慕若曦 发表于 2020-9-28 15:02
本次分析的软件样本是来自吾爱的一个求助帖,原贴现已锁帖。

下载作者提供的样本打开后是这个样子的界面。

左侧为软件的主界面,可以看到一个机器码未通过的字样,所以需要通过逆向分析实现让验证通过。
先查壳,如右图所示。
基础差一点儿的同学可以先去看我写的《.NET零基础逆向教程》
没有壳,直接dnspy载入即可。


机器码的生成:
因为打开软件后在我们没有干涉的情况下,该软件直接读出了一个机器码,还判断出了这个机器码是错误的。
因此能想到在窗口加载后,会有一个生成注册码并验证的逻辑。
所以直接静态分析,从入口点跟入。

入口点是Main方法,中规中矩的窗口程序。

Main中实例化了一个名叫FFToolsMain的窗口对象,也就是这个软件的主界面。
跟进去,看到实例化中也没有特别之处,载入组件,然后做一些初始化设置。

在左侧的列表树中找到这个窗口的加载事件。

代码如下:
[C#] 纯文本查看 复制代码
private void FFToolsMain_Load(object sender, EventArgs e)
{
	this.getCDValue();
	new Thread(new ThreadStart(this.checkzuce)).Start();
}


可以看到这个方法里首先通过getCDValue方法去获取机器码,然后通过线程调用了checkzuce方法来判断是否注册了。

先跟进getCDValue方法看一下机器码是怎么获取的。
[C#] 纯文本查看 复制代码
string text = "";
foreach(ManagementBaseObject managementBaseObject in new ManagementClass("win32_Processor").GetInstances())
{
    ManagementObject managementObject = (ManagementObject) managementBaseObject;
    try
    {
        text = managementObject.Properties["Processorid"].Value.ToString();
        break;
    }
    catch
    {}
}
if(text == "")
{
    this.showlog("获取机器码失败.");
    return;
}
FFToolsMain.CDValue += text;
text = "";
foreach(ManagementBaseObject managementBaseObject2 in new ManagementClass("Win32_NetworkAdapterConfiguration").GetInstances())
{
    ManagementObject managementObject2 = (ManagementObject) managementBaseObject2;
    try
    {
        if((bool) managementObject2["IPEnabled"])
        {
            text = managementObject2.Properties["MACAddress"].Value.ToString();
            text = text.Replace(":", "");
            break;
        }
    }
    catch
    {}
}
if(text == "")
{
    this.showlog("获取机器码失败.");
    return;
}
FFToolsMain.CDValue += text;
text = "";
for(int i = 0; i < FFToolsMain.CDValue.Length; i += 2)
{
    int num = (int) Convert.ToInt16(FFToolsMain.CDValue.Substring(i, 2), 16);
    if(num % 2 == 0)
    {
        num = num / 16 * 16 + (num % 16 + 9) % 16;
    }
    else
    {
        num = num / 16 * 16 + (num % 16 + 7) % 16;
    }
    text = num.ToString("X2") + text;
}
this.showlog("机器码: " + text);
FFToolsMain.CDValue = text;


在VS里创建一个控制台项目来看看这个获取方式。

稍微改一下代码就行。
[C#] 纯文本查看 复制代码
namespace FFTools
{
    class Program
    {
        static void Main(string[] args)
        {
            FFToolsMain.jiqima();
        }
    }
    internal class FFToolsMain
    {
        public static string CDValue
        {
            get;
            internal set;
        }
        public static void jiqima()
        {
            string text = "";
            foreach(ManagementBaseObject managementBaseObject in new ManagementClass("win32_Processor").GetInstances())
            {
                ManagementObject managementObject = (ManagementObject) managementBaseObject;
                try
                {
                    text = managementObject.Properties["Processorid"].Value.ToString();
                    break;
                }
                catch
                {}
            }
            if(text == "")
            {
                Console.WriteLine("获取机器码失败.");
                return;
            }
            FFToolsMain.CDValue += text;
            text = "";
            foreach(ManagementBaseObject managementBaseObject2 in new ManagementClass("Win32_NetworkAdapterConfiguration").GetInstances())
            {
                ManagementObject managementObject2 = (ManagementObject) managementBaseObject2;
                try
                {
                    if((bool) managementObject2["IPEnabled"])
                    {
                        text = managementObject2.Properties["MACAddress"].Value.ToString();
                        text = text.Replace(":", "");
                        break;
                    }
                }
                catch
                {}
            }
            if(text == "")
            {
                Console.WriteLine("获取机器码失败.");
                return;
            }
            FFToolsMain.CDValue += text;
            text = "";
            for(int i = 0; i < FFToolsMain.CDValue.Length; i += 2)
            {
                int num = (int) Convert.ToInt16(FFToolsMain.CDValue.Substring(i, 2), 16);
                if(num % 2 == 0)
                {
                    num = num / 16 * 16 + (num % 16 + 9) % 16;
                }
                else
                {
                    num = num / 16 * 16 + (num % 16 + 7) % 16;
                }
                text = num.ToString("X2") + text;
            }
            Console.WriteLine("机器码: " + text);
            FFToolsMain.CDValue = text;
            Console.ReadLine();
        }
    }
}


跑起来,可以看到获取到的机器码是一样的。



因此这个程序生成机器码的原理就是如上代码,通过网卡的MAC地址,进行了简单的加密处理生成了一个机器码。



注册验证算法

接下来看注册的验证,在checkzuche,话说作者哪里人,z和zh,c和ch不分嘞……

算法如下:
[C#] 纯文本查看 复制代码
private void checkzuce()
{
    this.注册ToolStripMenuItem.Enabled = false;
    string text = "";
    ArrayList arrayList;
    if(!FFToolsMain.NetValue)
    {
        arrayList = new ArrayList();
        arrayList.AddRange(commdata.RGdata);
        for(int i = 0; i < arrayList.Count; i++)
        {
            string text2 = (string) arrayList[i];
            if(!string.IsNullOrEmpty(text2) && text2 == FFToolsMain.CDValue)
            {
                this.zctext.Text = "注册版";
                this.Text = "KeyboardDriverhelp";
                this.zcsjtxt.Text = "内部版本无期限";
                this.showlog("机器码认证通过!");
                return;
            }
        }
        this.showlog("机器码未通过!");
        this.注册ToolStripMenuItem.Enabled = true;
        return;
    }
    arrayList = new ArrayList(FFToolsMain.sqlmainrw.readdb("RegValue", commdata.fnRegValue));
    if(arrayList != null && arrayList.Count > 0)
    {
        text = (string) arrayList[0];
    }
    if(text == "")
    {
        this.showlog("请注册注册!");
        this.注册ToolStripMenuItem.Enabled = true;
        return;
    }
    if(this.zhuce(text))
    {
        this.zctext.Text = "注册版";
        this.Text = "FFTools";
        return;
    }
    this.showlog("请注册注册!");
    this.注册ToolStripMenuItem.Enabled = true;
}


第一个敏感位置,在于一个循环判断:
[C#] 纯文本查看 复制代码
arrayList = new ArrayList();
arrayList.AddRange(commdata.RGdata);
for(int i = 0; i < arrayList.Count; i++)
{
    string text2 = (string) arrayList[i];
    if(!string.IsNullOrEmpty(text2) && text2 == FFToolsMain.CDValue)
    {
        this.zctext.Text = "注册版";
        this.Text = "KeyboardDriverhelp";
        this.zcsjtxt.Text = "内部版本无期限";
        this.showlog("机器码认证通过!");
        return;
    }
}
this.showlog("机器码未通过!");


这段算法是从软件中读出了一些内部的机器码,如果是这些特殊的机器码,则这台电脑就是内部版本。

下个断点就可以看到这些机器码。



所以其实可以直接修改把你的机器码变成内置的。



第二处敏感位置在if判断处:

[C#] 纯文本查看 复制代码
if(this.zhuce(text))
{
    this.zctext.Text = "注册版";
    this.Text = "FFTools";
    return;
}


所以只要zhuce这个方法返回ture就行了。

跟进这个方法去看。
[C#] 纯文本查看 复制代码
private bool zhuce(string dbreg)
{
    string text = "";
    string text2 = "";
    string text3 = "";
    string text4 = "";
    if(FFToolsMain.CDValue.Length < 28)
    {
        return false;
    }
    for(int i = 0; i < FFToolsMain.CDValue.Length / 2; i += 2)
    {
        text += ((int)(Convert.ToInt16(FFToolsMain.CDValue.Substring(i, 2), 16) ^ Convert.ToInt16(FFToolsMain.CDValue.Substring(FFToolsMain.CDValue.Length - i - 2, 2), 16))).ToString("X2");
    }
    byte[] array = new MD5CryptoServiceProvider().ComputeHash(Encoding.Default.GetBytes(text));
    for(int j = 0; j < array.Length; j++)
    {
        text2 += array[j].ToString("X2");
    }
    if(dbreg.Length < 40)
    {
        return false;
    }
    int num = 0;
    for(int k = 0; k < dbreg.Length; k++)
    {
        if(num + 1 < dbreg.Length)
        {
            text3 += dbreg.Substring(num, 2);
            num += 2;
        }
        if(k % 2 == 0 && num < dbreg.Length)
        {
            if(text4.Length < 8)
            {
                text4 += dbreg.Substring(num, 1);
            }
            num++;
        }
        if(num == dbreg.Length)
        {
            k = dbreg.Length;
        }
    }
    if(text4.Length != 8)
    {
        this.showlog("注册码过期...");
        return false;
    }
    int num2 = this.TDClient();
    if(num2 == 0)
    {
        this.showlog("无法获得数据,稍后再试...");
        return false;
    }
    if(!(text2 == text3))
    {
        this.showlog("注册码无效");
        return false;
    }
    int num3 = Convert.ToInt32(text4.Substring(0, 2), 16) - 128;
    int num4 = Convert.ToInt32(text4.Substring(2, 2), 16) - 128;
    int num5 = Convert.ToInt32(text4.Substring(4, 2), 16) - 128;
    int num6 = Convert.ToInt32(text4.Substring(6, 2), 16) - 128;
    this.showlog(string.Concat(new object[]
    {
        "有效期至",
        num3, "年",
        num4, "月",
        num5, "日",
        num6, "时"
    }));
    this.zcsjtxt.Text = string.Concat(new object[]
    {
        "有效期至",
        num3, "年",
        num4, "月",
        num5, "日",
        num6, "时"
    });
    if(num3 * 1000000 + num4 * 10000 + num5 * 100 + num6 > num2)
    {
        this.showlog("注册码通过");
        return true;
    }
    this.showlog("注册码过期...");
    return false;
}


有点儿复杂……

直接返回true就行了。



然后注册下看到可以通过。



爆破的话就这样就行了。



分析注册算法

这个算法不难,所以分析一下具体的算法。
[C#] 纯文本查看 复制代码
string text = "";
string text2 = "";
string text3 = "";
string text4 = "";
if(FFToolsMain.CDValue.Length < 28)
{
    return false;
}
for(int i = 0; i < FFToolsMain.CDValue.Length / 2; i += 2)
{
    text += ((int)(Convert.ToInt16(FFToolsMain.CDValue.Substring(i, 2), 16) ^ Convert.ToInt16(FFToolsMain.CDValue.Substring(FFToolsMain.CDValue.Length - i - 2, 2), 16))).ToString("X2");
}


一开始定义了4个sring,然后判断了下机器码的位数是不是28位。

接着循环读出了一个text,没啥好说的,照抄就行。
[C#] 纯文本查看 复制代码
byte[] array = new MD5CryptoServiceProvider().ComputeHash(Encoding.Default.GetBytes(text));
for(int j = 0; j < array.Length; j++)
{
    text2 += array[j].ToString("X2");
}
if(dbreg.Length < 40)
{
    return false;
}


往下,md5加密,然后得出了text2,也照抄就行。

到判断这里,有个dbreg,db是数据库,reg是注册,猜想这里大概是从数据库里读取旧的注册码。

看了下这是zhuce方法的参数,返回到调用里下断看一下。



很尴尬的是断不下来……

分析一下原因,找一下zhuce方法被调用的记录。



两处调用,第一处是软件打开时触发的checkzuce,由于这个软件的设计思路,当注册码正确时候才会写入数据库,而我们之前的数据库里是没有注册码的,所以当软件打开时并没有触发zhuce。

第二处调用是手动触发,当按注册按钮时的点击事件。

跟进去看看。
[C#] 纯文本查看 复制代码
private void Menuregister_Click(object sender, EventArgs e)
{
    this.checkzuce();
    if(this.zctext.Text == "注册版")
    {
        this.showlog("已注册!");
        return;
    }
    string text = "";
    if(FFToolsMain.CDValue.Length < 28)
    {
        return;
    }
    try
    {
        text = (string) Clipboard.GetData(DataFormats.Text);
    }
    catch
    {}
    if(!string.IsNullOrEmpty(text) && text.Length == 40)
    {
        text = FFToolsMain.InputBox("请输入注册码", FFToolsMain.CDValue, text);
    }
    else
    {
        text = FFToolsMain.InputBox("请输入注册码", FFToolsMain.CDValue, "请输入注册码或直接粘贴");
    }
    if(string.IsNullOrEmpty(text))
    {
        return;
    }
    if(!this.zhuce(text))
    {
        this.showlog("输入的注册码有误或已过期,注册失败,检查后重新注册!");
        return;
    }
    this.zctext.Text = "注册版";
    this.showlog("注册成功!");
    FFToolsMain.sqlmainrw.insdb("RegValue", commdata.fnRegValue, new object[]
    {
        text
    });
}


checkzuce肯定是验证不通过的。

下一行的判断有点儿奇妙:
[C#] 纯文本查看 复制代码
if(this.zctext.Text == "注册版")
{
    this.showlog("已注册!");
    return;
}


随便使用一个功能,会提示:



暂停,调用堆栈,然后也能看到这个判断:



这就很魔性……

给this.zctext.Text赋值,也能成功注册并使用所有功能……



再往下看算法:
[C#] 纯文本查看 复制代码
string text = "";
if(FFToolsMain.CDValue.Length < 28)
{
    return;
}
try
{
    text = (string) Clipboard.GetData(DataFormats.Text);
}
catch
{}
if(!string.IsNullOrEmpty(text) && text.Length == 40)
{
    text = FFToolsMain.InputBox("请输入注册码", FFToolsMain.CDValue, text);
}
else
{
    text = FFToolsMain.InputBox("请输入注册码", FFToolsMain.CDValue, "请输入注册码或直接粘贴");
}
if(string.IsNullOrEmpty(text))
{
    return;
}


如果注册码的长度小于28,直接return,说明注册码长度是大于28的。

然后尝试从剪贴板读取已经复制的文本,如果为空或者长度是40,提示请输入注册码,否则就自动把剪贴板的内容粘贴到编辑框,顺带赋值给text。

不晓得40长度的是个啥,不管他。

最后判断了一下text的内容是不是空的。

然后才能走到zhuce方法里。

那手动触发一下zhuce方法。

大于16且不为40,随便输入个假码触发就行。



瞄了一眼这个参数确实是我刚才输入的假码。

那继续回来跟踪zhuce方法。

如果这个注册码长度小于40,返回假。

那注册码的长度就是大于40呗。

然后是一个循环,算出了text3和text4。

输入一个长度40的假码看看,结果为:



有个注册码无效的错误,看看原因:



哦,text2要等于text3,那下断看看这两个都是啥。



随着循环次数的增加,text2没变,3一直在增加位数。

那我知道了……注册码的前32位就是text2呗。

改改我们的假码。

然后发现还是不对……



读了一遍这个循环,才发现它循环到一定位置时会抽取数值。

其中, 注册码的第3、8、13、18、23、28、33、38位会被抽出来,组成text4。



改改我们的假码算法,注意数组下标是从0开始的。



现在text2和text3就匹配了。



再往下看:
[C#] 纯文本查看 复制代码
if(text4.Length != 8)
{
    this.showlog("注册码过期...");
    return false;
}
int num2 = this.TDClient();
if(num2 == 0)
{
    this.showlog("无法获得数据,稍后再试...");
    return false;
}


text4的长度要为8,然后联网获取了一个num2,实际上是从网络上获取了当前的时间。
[C#] 纯文本查看 复制代码
int num3 = Convert.ToInt32(text4.Substring(0, 2), 16) - 128;
int num4 = Convert.ToInt32(text4.Substring(2, 2), 16) - 128;
int num5 = Convert.ToInt32(text4.Substring(4, 2), 16) - 128;
int num6 = Convert.ToInt32(text4.Substring(6, 2), 16) - 128;
this.showlog(string.Concat(new object[]
{
    "有效期至",
    num3, "年",
    num4, "月",
    num5, "日",
    num6, "时"
}));


再往下是处理时间的,获取text4的内容然后-128。

写对应的逆向算法就行。



分析完毕。




样本文件:

028.zip (2.79 MB, 下载次数: 154)

免费评分

参与人数 23威望 +1 吾爱币 +43 热心值 +21 收起 理由
s940520 + 1 我很赞同!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
小飞虫 + 2 + 1 谢谢@Thanks!
金丰龙卫浴 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
flyz007 + 1 + 1 用心讨论,共获提升!
JOKER688 + 1 + 1 热心回复!
Ezeea + 1 + 1 用心讨论,共获提升!
gh0st_ + 1 + 1 鼓励转贴优秀软件安全工具和文档!
i66235 + 1 + 1 热心回复!
kelvar + 2 + 1 谢谢@Thanks!
满不懂 + 1 + 1 谢谢@Thanks!
mfl666 + 1 + 1 666
nobodycca + 1 + 1 我很赞同!
火绒 + 1 + 1 6666666
sgzhkh + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
D小小贱 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
猫吃 + 1 楼主可以出一个混淆还原的教程嚒
Snprszy + 1 + 1 我很赞同!
Lancigar + 1 + 1 感谢大神分享~
朱朱你堕落了 + 1 + 1 用心讨论,共获提升!
细水流长 + 1 + 1 热心回复!
Bds1r + 1 + 1 很具体

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

EnterpriseSolu 发表于 2020-9-28 15:59
好多地方已经非常接近答案了,换作是我,直接编辑dll/exe文件,另存一份就可以了, 工具Reflexil

免费评分

参与人数 1热心值 +1 收起 理由
慕若曦 + 1 唔,要爆破的话当然很多地方都可以。

查看全部评分

 楼主| 慕若曦 发表于 2020-9-29 11:50
tjl52pj 发表于 2020-9-29 00:48
想从头学  软件破解类 要看哪些教程 老哥

本论坛有很多基础教程,视频区里找那种一个系列的
Bds1r 发表于 2020-9-28 15:17
头像被屏蔽
jkwdxhh 发表于 2020-9-28 17:00
提示: 作者被禁止或删除 内容自动屏蔽
shaunkelly 发表于 2020-9-28 17:02
还算不错,看得很清楚
gagmeng 发表于 2020-9-28 17:08
TIM图片20200928170737.png
Snprszy 发表于 2020-9-28 17:16
分析得很清楚,讲的很仔细。
zjghc 发表于 2020-9-28 17:43
楼主分析得很清楚,学到了。
王兰花秀丽 发表于 2020-9-28 17:44
你这分析的,对我的帮助很大
hxd97244 发表于 2020-9-28 17:55
很清楚,学习了,感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 03:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表