[聚合文章] [逆向分析]记一次对".NET"某款奇葩的程序辅助工具的逆向之旅

.Net 2017-12-29 14 阅读

[TOC] ###0x00前言:

在前几天,少年时期的一位童鞋问能不能帮看一款工具,是某款程序的辅助工具,需要注册码注册过后才能使用,我心中在想:”我这小菜鸡怎么搞得定呢“,最后在威逼利诱的情况下,我答应了。
本文很早之前就已经写完了,并未发出来,只是当作了一个过程记录写在了云笔记上,经过一番挣扎之后才决定发出来。
如果该工具作者看到该文并觉得不应该发出来,请联系我。我会对该篇文章进行删除。

###0x01工具:

  • ExeInfo PE
  • dnSpy
  • De4dot

###0x02正文:

鉴于这辅助工具的特别情况,所以下文称主程序为 “程序客户端” 以及插件为 “辅助工具” 由于这款 “辅助工具” 是以插件形式加载到 “程序客户端上” 的,所以我们得先安装 ”程序客户端“ 上。首先由于对 ”辅助工具“ 的使用流程不是很熟悉,所以第一步就装上,然后运行了下。先熟悉一下流程,方便后续的干活。   附一张我对这个 辅助工具 目前所得到的信息的脑图。这样子可以更加清晰的让人知道做到哪。该做些啥。 我们先来看下正常情况下,我们运行 ”辅助工具“ 是提示什么。

经过前期得到的信息可知,该 “辅助工具” 的文件名为: TestProcess.dll 先用 ExeInfo Pe 查一下是否加固了。一般 .NET 的话很大可能都混淆了。 可以看到经过了 dotfuscato 进行混淆,可以直接使 de4dot 反混淆。 反混淆过后的新文件为: TestProcess-cleaned.dll TestProcess.dll 文件重命名一份。 然后把 TestProcess-cleaned.dll 改成 TestProcess.dll 。 接下来直接将 TestProcess.dll 拖入进 dnspy 里。

首先我们看到 “脑图” 。我现在按照 ”脑图“ 的分析思路。从第一个思路开始分析一下。 可以看到输入注册码那个窗口,我们随便输入一个内容后点确定。 可以看到,从确定按钮的点击事件中,所触发的这个弹窗。我们针对该点击事件去找下具体代码。 在Form1-》Button1_Click方法 以下代码:

// TestProcess.Form1
// Token: 0x0600003A RID: 58 RVA: 0x00007BF0 File Offset: 0x00005FF0
private void Button1_Click(object sender, EventArgs e)
{
	RegistryKey currentUser = MyProject.Computer.Registry.CurrentUser;
	RegistryKey registryKey = currentUser.OpenSubKey("Software\\", true);
	string text = this.TextBox1.Text;
	bool flag = this.RegCodeeque(text) == 20;
	if (flag)
	{
		registryKey.SetValue("FastTooL_Register_RegCode", text);
		Interaction.MsgBox("注册成功!", MsgBoxStyle.OkOnly, null);
		this.vvv = 1;
		this.Close();
	}
	else
	{
		Interaction.MsgBox("输入的注册码错误,请重新输入!", MsgBoxStyle.OkOnly, null);
	}
}

从第七行开始看。可以看到获取的 TextBox1 控件的内容并赋值给 text 。然后到第8行去判断 this.RegCodeeque(text) 返回值 是否等于 20 。并且将结果赋值给 flag 。如果等于则为 true ,如果不等于则为 false 重要的内容从第9行开始,我们来分析下写的什么意思。可以很明显的看出来这里写的是,如果 flagtrue 的话则将 text 内容就是输入的注册码内容写进注册表内。然后提示注册成功,窗口结束。如果 flagfalse 的话,则提示注册码错误。很明显,我们刚刚为 false ,所以走进了注册码错误。接下来我们瞄准到 this.RegCodeeque(text) 这个函数中。

进入这个函数可以得知该函数的返回值为 int型 。看到上面验证代码 bool flag = this.RegCodeeque(text) == 20; 可以看到返回函数返回值是否等于 20 。我们需要让这个程序的返回值为 20 ,从而直接到注册成功。所以中间代码。不管他。直接看最下面的代码。

最后如果算出来注册码为正确的话才返回 20 ,否则话就返回 0 。所以我们可以选择尝试直接改 result 改成: return 20; 或者 重新把 result 赋值一遍。 修改完之后,我们保存一下。

重新打开软件,运行 “辅助工具” 然后在尝试一下注册。 提示注册成功了。说实在话,我第一眼看见这个我是非常无比的兴奋的,因为我不敢相信那么快就有结果了。然后在带着无比兴奋的心情重新点击了一下这个功能之后发现事情并没有那么简单....

因为无论你运行多少次。它都只会弹出这个验证窗并且随便输入都提示注册成功。顿时心情一落千丈。由此可以判断直接修改注册窗那块的代码返回值是不可行的。 于是思考了一下,每次运行 “辅助工具” 都会弹出这个注册窗。那么在 “辅助工具” 初始化的时候肯定做了判断,所以才会反复进入注册窗口。于是我们找到 CHECKLOAD-》CheckReg 处。 可以看到返回值也是 int 类型。

可以看到。每次运行 “辅助工具” 都会弹出无申请码这个提示。 所以我们可以确定就是这个函数做了初始化验证功能。 现在开始阅读一下代码。看看这些代码到底做了什么操作。接下来的代码一些操作流程。我都会以注释形式写在以下代码中。

// TestProcess.CHECKLOAD
// Token: 0x06000021 RID: 33 RVA: 0x00006750 File Offset: 0x00004B50
public int CheckReg()
{
	string location = Assembly.GetExecutingAssembly().Location;
	string text = Strings.Mid(location, 1, Strings.InStrRev(location, "\\", -1, CompareMethod.Binary));
	this.LastTime0 = Directory.GetLastAccessTime(text + "\\FastProcess.arx");//获取“辅助工具”当前运行目录下的FastProcess.arx上一次访问时间
	checked
	{
		string text2 = Strings.Mid(text, 1, Strings.InStrRev(text, "\\", -1, CompareMethod.Binary) - 1);
		text2 = Strings.Mid(text2, 1, Strings.InStrRev(text2, "\\", -1, CompareMethod.Binary)) + "CADTools\\SLD&BMP\\";
		int num = 0;
		bool flag;
		DateTime value;
		try
		{
            /*
                CB5文件内容:
                    0,20171010,30
                    1,LHR170479-93678-KC0E92BD6-30902515287231,2A275-EAF86EBC-F9D405BF22D
            */
			flag = File.Exists(text2 + "CB5"); //判断CADTools\SLD&BMP\CB5文件是否存在,如果存在则为true否则为false
			if (flag)
			{
				string[] txtFile = CHECKLOAD.GetTxtFile(text2 + "CB5");//调用GetTextFile函数来对CB5文件进行读取。(该函数其实就是将cb5文件里面的两行内容读取了出来)
				int num2 = 0;
				this.ExeclMac = new string[txtFile.Length - 1 + 1];
				this.ExeclReg = new string[txtFile.Length - 1 + 1];
				string[] array = txtFile;
				for (int i = 0; i < array.Length; i++)
				{
					string expression = array[i];
					string[] array2 = Strings.Split(expression, ",", -1, CompareMethod.Binary);//使用,号对字符串进行分割成为数组。
					this.ExeclMac[num2] = Strings.Trim(array2[1]);//第一次循环结果为:20171010,第二次循环结果为:LHR170479-93678-KC0E92BD6-30902515287231
					this.ExeclReg[num2] = Strings.Trim(array2[2]);//第一次循环结果为:30,第二次循环结果为:2A275-EAF86EBC-F9D405BF22D
                    //下面的图片有cb5文件内容
					num2 = 1 + num2;
				}
			}
			else
			{
				Interaction.MsgBox("未找到定义文件:Reg.csv,程序无法运行!", MsgBoxStyle.OkOnly, null);
			}
			value = Conversions.ToDate(Strings.Format(Conversion.Val(this.ExeclMac[0]), "0000-00-00"));//对第一次循环结果为:20171010进行了格式化处理
			num = Conversions.ToInteger(this.ExeclReg[0]);//获取的则是第一次循环结果为:30
		}
		catch (Exception expr_179)
		{
			ProjectData.SetProjectError(expr_179);
			Interaction.MsgBox("未注册!", MsgBoxStyle.OkOnly, null);
			ProjectData.ClearProjectError();
			goto IL_415;
		}
		string diskVolumeSerialNumber = CHECKLOAD.GetDiskVolumeSerialNumber();
		flag = (Array.IndexOf<string>(this.ExeclMac, diskVolumeSerialNumber) == -1);//如果this.ExeclMac里没有diskVolumeSerialNumber内容的话。就提示无申请码。很明显的告诉我们,我们需要把第二次循环结果为:LHR170479-93678-KC0E92BD6-30902515287231,替换成我们自己的序列号。
		int result;
		if (flag)
		{
			Interaction.MsgBox("无申请码!", MsgBoxStyle.OkOnly, null);//如果不是我们序列号的话。提示没有申请码。
		}
		else
		{
			string value2 = string.Concat(unchecked(new string[]
			{
				Conversion.Hex(Math.Round(Conversions.ToDouble(CHECKLOAD.Ddisk) / 3.5 + 156.0)),
				"-",
				Conversion.Hex(Math.Round((Conversions.ToDouble(CHECKLOAD.Cdisk) + 8011.0) / 3.0 + 92.0, 0)),
				"-",
				Conversion.Hex(Math.Round(CHECKLOAD.mac / 1.8 + 1247.0))
			}));
			flag = (Array.IndexOf<string>(this.ExeclReg, value2) == -1);//判断this.ExeclReg里面没有value2内容的话就提示无注册码。通过value2的方式来计算得到的注册码结果为:AFD634F-26CC46EE-4A8719E5033
			if (flag)
			{
				Interaction.MsgBox("无注册码!", MsgBoxStyle.OkOnly, null);//如果不是我们注册码的话。提示没有申请码。
			}
			else
			{
                //如果申请码,注册码都匹配上则进入判断程序是否过期的时候了。
                /*
                    this.LastTime0.Subtract(value)
                    这里判断了FastProcess.arx上一次访问时间减去第一次循环结果为:20171010的值。
                    最后判断计算出来的值大于第一次循环结果为:30的话就是程序到期。这里就开始判断剩余的有效天数了。
                */
				int num3 = Math.Abs((int)Math.Round(this.LastTime0.Subtract(value).TotalDays));
				flag = (num3 > num);
				if (flag)
				{
					Interaction.MsgBox("程序到期!", MsgBoxStyle.OkOnly, null);
				}
				else
				{
					string text3 = Conversions.ToString(Directory.GetLastWriteTime(text2 + "CB5"));
					text3 = Strings.Replace(text3, "/", "", 1, -1, CompareMethod.Binary);
					text3 = Strings.Mid(text3, 1, Strings.InStr(text3, " ", CompareMethod.Binary) - 1);//获取上一次cb5访问时间
					flag = (Conversions.ToDouble(text3) != 20171010.0);//看到这里发现,上一次cb5访问时间就是当前的时间。所以对于这里的20171010.0的固定值表示有点懵(因为如果这里固定了时间那么如果不修改这时间就永远进不去下面的区间),于是就看了下下面。发现如果不进if里面的话。就直接跳到注册窗口。
                    
					if (!flag)
					{
						string text4 = Conversions.ToString(Directory.GetLastAccessTime(text + "FastProcess.arx"));
						text4 = Strings.Replace(Conversions.ToString(this.LastTime0), "/", "", 1, -1, CompareMethod.Binary);
						text4 = Strings.Mid(text4, 1, Strings.InStr(text4, " ", CompareMethod.Binary) - 1);
						flag = (2017925.0 < Conversion.Val(text4) & Conversion.Val(text4) > 20171031.0);
						if (!flag)
						{
							int num4 = 2;
							Directory.SetLastAccessTime(text + "FastProcess.arx", DateAndTime.Now);
							result = num4;
							return result;//突然发现这里有个返回值,返回的值则为2。所以直接将代码全部删除掉。改成return 2;试试。
						}
						Interaction.MsgBox("授权时间已过期,请联系程序开发者.\r                     Tel:136223*****", MsgBoxStyle.OkOnly, null);
					}
				}
			}
		}
		IL_415:
		Form1 form = new Form1();
		form.ShowDialog();
		result = 0;
		return result;
	}
}

首先按照我们上面代码的分析思路。 我们 CB5 文件的申请码改成我们自己的先。然后运行试试。 可以发现已经不会提示无申请码了。现在提示是无注册码。接下来,我们把计算出来的注册码也修改掉(经过测试这里注册码为3组)。 附上计算 Value2 代码(其实就是将他原来的代码复制了一遍,然后修改点东西,就可以出来了)。

private void button1_Click(object sender, EventArgs e)
{
    object objectValue = RuntimeHelpers.GetObjectValue(Interaction.CreateObject("Scripting.FileSystemObject", ""));
    string Cdisk =  Conversions.ToString(NewLateBinding.LateGet(NewLateBinding.LateGet(objectValue, null, "drives", new object[]
    {"C:"}, null, null, null), null, "SerialNumber", new object[0], null, null, null));
    string Ddisk = Conversions.ToString(NewLateBinding.LateGet(NewLateBinding.LateGet(objectValue, null, "drives", new object[]
    {"D:"}, null, null, null), null, "SerialNumber", new object[0], null, null, null));
    NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
    double mac = (double)Conversions.ToLong("&H" + allNetworkInterfaces[0].GetPhysicalAddress().ToString());
    string value2 = string.Concat(unchecked(new string[]
    {
        Conversion.Hex(Math.Round(Conversions.ToDouble(Ddisk) / 3.5 + 156.0)),
        "-",
        Conversion.Hex(Math.Round((Conversions.ToDouble(Cdisk) + 8011.0) / 3.0 + 92.0, 0)),
        "-",
        Conversion.Hex(Math.Round(mac / 1.8 + 1247.0))
    }));
    textBox1.Text = value2;
}

现在则是直接提示程序到期,这个时候我们去吧 cb5 的日期修改掉 之后程序是出了异常。

然后是直接将整个代码删除了。只返回了一个 2 出去。

现在再来试试修改之后运行 “辅助工具” 可以看到,已经没有验证提示。已经走进了正常的功能中。

0x03后续:

距离上一次分析这款辅助工具已经有一段时间了。之后朋友告诉我,他们老板 财大气粗 已经买下来了,他最开始给我的这款工具是测试版本,只是商家发给客户看的(难怪感觉代码那么奇怪)。 然后把正式版的 辅助工具 发了一份给我。解开了我在上一次分析的一点疑惑。就是:

flag = (Conversions.ToDouble(text3) != 20171010.0);//看到这里发现,上一次cb5访问时间就是当前的时间。所以对于这里的20171010.0的固定值表示有点懵(因为如果这里固定了时间那么如果不修改这时间就永远进不去下面的区间),于是就看了下下面。发现如果不进if里面的话。就直接跳到注册窗口。
if (!flag)
{
/*代码省略*/
}

就是这个地方,为什么判断的时间日期是固定的 20171010 于是我重新看了下正版的工具,发现这一块代码地方改成如下:

if (Conversions.ToDouble(text3) == 20171129.0)
{
    string text4 = Conversions.ToString(Directory.GetLastAccessTime(text + "FastProcess.arx"));
    text4 = Strings.Replace(Conversions.ToString(this.dateTime_0), "/", "", 1, -1, CompareMethod.Binary);
    text4 = Strings.Mid(text4, 1, Strings.InStr(text4, " ", CompareMethod.Binary) - 1);
    if (!(2017925.0 < Conversion.Val(text4) & Conversion.Val(text4) > 20271220.0))
    {
        Directory.SetLastAccessTime(text + "FastProcess.arx", DateAndTime.Now);
        result = 2;
        return result;
    }
    Interaction.MsgBox("授权时间已过期,请联系程序开发者.\r                     *********@qq.com", MsgBoxStyle.OkOnly, "提示");
}

发是正版程序是 Conversions.ToDouble(text3) != 20171010.0 改成了 Conversions.ToDouble(text3) == 20171129.0 以及 if (!(2017925.0 < Conversion.Val(text4) & Conversion.Val(text4) > 20271220.0)) 这个地方的日期也改了。 也就是说这个程序的注册窗口是没有作用的。重点是在 cb5 文件内容处。只要将 Value2 计算出来的结果以及电脑机器码写进去之后,正版程序即可正常使用了。

然后让朋友将他电脑上的该文件内容截图看了下发现的确是在 cb5 文件内加入了该机器注册码。也就是最上边文章内所改形式。

PS:文章开始的 思维脑图 是边分析边写的一个脑图,可能部分小地方有点对不上,是由于后边知道了一个大概的思路就没有继续对脑图进行修改了。画这个脑图的原因,主要就是为了方便分析

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。