飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 7337|回复: 15

[原创] 某辅助软件注册逻辑分析

[复制链接]
  • TA的每日心情
    擦汗
    2024-3-20 09:24
  • 签到天数: 373 天

    [LV.9]以坛为家II

    发表于 2020-9-28 13:24:10 | 显示全部楼层 |阅读模式
    本帖最后由 763837023 于 2020-9-28 13:29 编辑

    本次分析的软件样本是来自吾爱的一个求助帖,原贴现已锁帖。


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

    左侧为软件的主界面,可以看到一个机器码未通过的字样,所以需要通过逆向分析实现让验证通过。
    先查壳,如右图所示。
    基础差一点儿的同学可以先去看我写的《.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;
                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;
        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。
    写对应的逆向算法就行。

    分析完毕。




    样本地址:
    游客,如果您要查看本帖隐藏内容请回复



    评分

    参与人数 7威望 +7 飘云币 +6 收起 理由
    UlRevenge + 1 + 1 PYG有你更精彩!
    Wandering... + 1 + 1 Mu Mu
    error + 1 PYG有你更精彩!
    pizazzboy + 1 + 1 PYG有你更精彩!
    Randolph + 1 + 1 万水千山总是情,给个评分最贴心!
    lincosmos + 1 + 1 剥洋葱
    daoben + 1 + 1 PYG有你更精彩!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情
    开心
    5 天前
  • 签到天数: 989 天

    [LV.10]以坛为家III

    发表于 2020-9-28 23:05:10 | 显示全部楼层
    谢谢大表哥分享。
    PYG19周年生日快乐!
    回复 支持 0 反对 1

    使用道具 举报

  • TA的每日心情
    开心
    5 小时前
  • 签到天数: 1865 天

    [LV.Master]伴坛终老

    发表于 2020-9-28 13:49:04 | 显示全部楼层
    向大大学习
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2023-10-9 06:47
  • 签到天数: 573 天

    [LV.9]以坛为家II

    发表于 2020-9-29 06:22:56 | 显示全部楼层
    感谢分享,谢谢飘云阁
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    前天 09:41
  • 签到天数: 2423 天

    [LV.Master]伴坛终老

    发表于 2020-9-29 07:07:44 | 显示全部楼层
    学习一次完整的分析,谢谢分享经验
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    前天 12:28
  • 签到天数: 767 天

    [LV.10]以坛为家III

    发表于 2020-9-29 07:56:16 | 显示全部楼层
    请问有没有keygen?
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 09:14
  • 签到天数: 2059 天

    [LV.Master]伴坛终老

    发表于 2020-9-29 10:35:14 | 显示全部楼层
    表哥V5,感谢分享了
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-2-6 12:47
  • 签到天数: 18 天

    [LV.4]偶尔看看III

    发表于 2020-9-29 10:39:03 | 显示全部楼层
    感谢大佬分享,学习一下
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    擦汗
    2024-3-20 09:24
  • 签到天数: 373 天

    [LV.9]以坛为家II

     楼主| 发表于 2020-9-29 11:53:12 | 显示全部楼层
    gyjijian525 发表于 2020-9-29 07:56
    请问有没有keygen?

    提供了源码啊……自己编译
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    2024-1-15 11:56
  • 签到天数: 437 天

    [LV.9]以坛为家II

    发表于 2020-9-30 00:16:12 | 显示全部楼层
    C#的啊   学习一下
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

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