Emmm……先悄咪咪说一句,我想求个优秀或者精华,增加一点积分,提升一个用户组欸。版主满足我的愿望吧!
本次注册算法分析的过程需要用到dnSpy、IDA、UltraEdit,涉及一些密码学的知识。
注册算法可用于Windows,Linux,Mac三个平台。
过程中用到的附件和Keygen在此:
Reflector.zip
(785.27 KB, 下载次数: 193)
Reflector 3 这个软件是 Squirrels LLC 开发的一款屏幕镜像工具。它自己的软件介绍如下:
Reflector 3 for Mac 可以将您的手机,平板电脑或计算机镜像到大屏幕上,无需连线或复杂的设置。从手掌中呈现,教导或娱乐。分享您的设备屏幕,反光板比以往更容易。
AirPlay,Google Cast和Miracast
Reflector将Mac,Windows和Android设备转换为AirPlay,Google Cast或Miracast接收器。使用Reflector在更大的屏幕上无线显示和录制iPhone,iPad,Chromebook,Android设备和Windows平板电脑。
我原本只是想逆向一下它实现AirPlay的过程,没想到这个软件还需要注册……那我就顺便把它注册的算法分析了吧!
废话不多说来了……那我们上手吧!
程序的下载地址 https://www.airsquirrels.com/reflector/try
首先从主程序的注册界面入手,主程序一看是.Net,直接拖入dnSpy。居然混淆都没有……简直太顺畅了。
输入注册码的窗口叫做TrialWindow,dnSpy里面一下子就找到了。里面的代码没什么可说的,就是监测输入框里的内容,注册码输完后调用SetFromKeyValidationState
可以看到,关键的验证步骤就在 ReflectorLicense.CheckLicenseIsValid() 我们跟过去看看。
返回值是一个bool,true代表注册成功,false代表注册失败。网上流传的破解版本就是简单粗暴地把这个函数改成
internal static bool? CheckLicenseIsValid(string license)
{
return new bool?(true);
}
就爆破成功了……网上能找到的破解版本几乎都是这样爆破的。没有暗装、没有功能限制,真的就完全破解了。
然鹅,主程序是有数字签名的,此种爆破之后数字签名就被破坏了。这对于我这种对软件有洁癖的人来说简直不能忍!!!
那行吧,我们继续往下研究。
看看ReflectorNativeInstance.ValidateLicenseKey()是从哪冒出来的
现在这个函数已经不在主程序里了,在一个叫做ReflectorNative.dll的文件里。这个dll没有数字签名,爆破在这里其实是可以的。
关键的的代码是
if (b != 0 && *(byte*)(ptr2 + 28L / (long)sizeof(SquirrelsLicense)) == 0 && *(byte*)(ptr2 + 29L / (long)sizeof(SquirrelsLicense)) == 3)
{
this.callback.RFServerWarapperLicenseClearErrorMessage();
return true;
}
再次然鹅…这个dll不是单纯的.Net程序集,而是一个混合程序集。因为里面有一些非托管的代码(我不知道是怎么做到的)。所以不能用直接用dnSpy修改了(重新编译会导致一些奇怪的问题)。
先把dnSpy切换成IL模式,如图
关键部分的代码就是我现在选中的那一块
我来解释一下此处的IL指令
/* 0x00457123 02 */ IL_0073: ldarg.0 /*将this入栈*/
/* 0x00457124 7BE6160004 */ IL_0074: ldfld class RFServerWrapper.IRFServerWrapperCallback RFServerWrapper.RFServerWrapper::callback /*在压入的this中找callback*/
/* 0x00457129 6F88070006 */ IL_0079: callvirt instance void RFServerWrapper.IRFServerWrapperCallback::RFServerWarapperLicenseClearErrorMessage() /*从callback里调用RFServerWarapperLicenseClearErrorMessage()*/
/* 0x0045712E 17 */ IL_007E: ldc.i4.1 /*入栈true*/
/* 0x0045712F 28A300000A */ IL_007F: call valuetype [mscorlib]System.Nullable`1<!0> valuetype [mscorlib]System.Nullable`1<bool>::op_Implicit(!0) /*将bool的true转换为bool?的true*/
/* 0x00457134 2A */ IL_0084: ret /*返回转换后的true*/
然后用UltraEdit打开ReflectorNative.dll,IL最前面的地址找到修改的位置,函数其余部分用0x00(nop)填充。
图中选中部分就是函数原本,大部分都被填充了nop,不是nop的部分就是关键部分的代码的IL了。现在函数变为
public bool? ValidateLicenseKey(string license)
{
this.callback.RFServerWarapperLicenseClearErrorMessage();
return true;
}
这种不破坏数字签名的爆破方法,已经算是比较好的破解了。只是希望注册的话,到此就可以结束了。
不满足于此,就只能跟下去死磕注册码算法了!
前方高能预警!!!
从此之后我们分析的就不仅是.Net程序的IL了,而是本机Native代码!
IDA都拿出来准备好啦!!!
从之前dnSpy里看到的ValidateLicenseKey继续分析
此处的验证调用
SquirrelsLicense* ptr2 = <Module>.RFValidateLicenseKey(this.server, ptr);
这个函数就不是托管代码了,只有如下的定义
// Token: 0x060006B0 RID: 1712 RVA: 0x00414A70 File Offset: 0x00413E70
[SuppressUnmanagedCodeSecurity]
[MethodImpl(MethodImplOptions.Unmanaged | MethodImplOptions.PreserveSig)]
internal unsafe static extern SquirrelsLicense* RFValidateLicenseKey(_RFServer*, sbyte*);
那么函数的主体部分在哪里呢?
我对于ReflectorNative.dll这样的混合程序集并不了解,找了半天没有找到。
我现在找到方法了,将ReflectorNative拖入IDA,IDA询问LoadFile方式的时候选择PE而不是.NetAssembly
然后在Functions窗口搜索函数的RVA(0x00414A70),前面的00直接忽略,输入414A70,然后就能看到函数了
Windows版本反编译之后函数名称不直观,我也就不改我后面的写法了……我还是按照Mac版本分析算法
正当我没有头绪的时候,我突然想起Reflector还有Mac版本呢!
在Mac版本上不可能用到.Net的混合程序集,可能全部都是Native代码!
于是立即就把Mac版本下载下来,拖进IDA一探究竟。
Mac程序拖进IDA,函数名称居然都看得到!!!真的是把我震惊到了……
无论如何我们算是找到了RFValidateLicenseKey的主体部分!
Mac汇编不熟悉,那就不硬磕汇编了,直接F5大法,改改变量名称和类型,可读性还蛮高的
咦?居然有字符串欸!
-----BEGIN PUBLIC KEY-----
MDIwDQYJKoZIhvcNAQEBBQADIQAwHgIXDoK2SPT7JNSYxgO1oJwxO3dIZzYSICcC
AwEAAQ ==
-----END PUBLIC KEY-----
这玩意是个公钥,看来注册码用了非对称加密算法。
公钥使用Asn1编码保存,那么就Asn1Editor伺候。
可以看出这是一个RSA的公钥,简单讲讲RSA:
p,q为两个素数
n=p*q
e为与(p-1)(q-1)互质的任意数字
d为e^(-1) mod (p-1)(q-1)
(n,e)组成公钥
(n,d)组成私钥
看不懂也没关系,不影响分析算法。
大家看到RSA不要害怕,这么小的modulus分分钟就分解了好嘛……
从Asn1Editor得知
n=1389838782556614468794064557539356473141171394217910311
e=65537
去 http://factordb.com ,把n贴进去p和q直接秒解了
得到
p=1152497169371634301857542543
q=1205936829601398332841241577
n=1389838782556614468794064557539356473141171394217910311
计算后知道
e=65537
d=295561028312121944116802381945412767177035108281094065
至此,公钥对应的私钥就算得到了(后面会用到的)
来来来,继续上面的分析
函数返回的结果是v3,又v3=v2,v2=SquirrelsLicenseDecode(,)
其它语句不管他了,可能是类似于callback一样的东西。
我们跟进到SquirrelsLicenseDecode
挑些关键步骤解释一下
L22:
p_decoded = base32decode(license, &a2); //base32解码license,decode会修改a2为解码后的长度
......
L26:
if ( a2 != 180 ) //如果解码长度不是180bit,就返回NULL
goto LABEL_9;
......
L29:
p_decoded_1[22] |= *p_decoded_1 >> 4; //把p_decoded[0]的高4位放到p_decoded[22]的低4位
......
L32-43
调用openssl函数读取公钥
......
L46-55
公钥解密,结果存到to中
......
L56-74
从解密内容构建返回值。
大框架先理解了,然后我们看细节 base32decode
伪代码就不贴了,这个函数其实就是把encoded理解为一串5bit的字节,然后把5bit连起来之后按照8bit读取字节。
注册码长度36,base32编码,即一个字符表示5bit,因此注册码总共36*5bit=180bit。
180bit=180/8byte=22.5byte,decode函数从前往后写,因此base32decode之后的内容占用23字节,最后一个字节(p_decoded[22])只有高4位
举例:
注册码:KKKKKK-KKKKKK-KKKKKK-KKKKKK-KKKKKK-KKKKKK
比特(二进制5bit): 10001 10001 10001 10001 10001 10001 - 10001 10001 10001 10001 10001 10001 - 10001 10001 10001 10001 10001 10001 - 10001 10001 10001 10001 10001 10001 - 10001 10001 10001 10001 10001 10001 - 10001 10001 10001 10001 10001 10001
比特(二进制8bit):10001100 01100011 00011000 11000110 00110001 10001100 01100011 00011000 11000110 00110001 10001100 01100011 00011000 11000110 00110001 10001100 01100011 00011000 11000110 00110001 10001100 01100011 0001????
解码字节:0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0x10
8bit中最后一组4个问号就是空余的4bit(当做4个0),此时空余的4bit在array[22]的低4位
在下一步骤RSA解密时,因为modulus是180bit,所以密文也一定是180bit,此时空余的4bit在array[0]的高4位。因此需要29行高位放到低位的过程
换过之后的密文变为
解码字节:0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0x10
密文字节:0x0C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0x18
这一步其实很巧妙啊!我当时没能理解,浪费了不少时间呢。
然后就是调用openssl用PKCS1Encoding的RSA解密密文字节。
密文是23字节,PKCS1Encoding填充11个字节,因此解密后的长度为23-11=12字节。
最后构建的步骤其实还算简单,但是要能理解BYTE,WORD,DWORD等等的长度,结合之前dnSpy中反编译的内容分析每一字节的作用
理清思路之后总结如下
Decrypted:(解密后的12字节)
00 01 02 03 04 05 06 07 08 09 11
License:(返回值32字节)
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
16-17 TrialDays(=0)=((Decrypted[10]>>2)&0x3C) => Decrypted[10]=0x00
24 Os(&2!=0)=Decrypted[0]&0x1F => Decrypted[0]=0x1F
28 Product(=0)=Decrypted[4]>>6 => Decrypted[4]=0x00-3F
29 Version(=3)=Decrypted[6]>>4 => Decrypted[6]=0x30-0x3F
最后四行,开头表示返回License中的字节位,空格后是作用/含义,括号内是期望值,"=>"箭头推出的就是对解密后12字节对的要求。
哈,只要四个字节符合要求就行了……看了来要求还是很少的嘛!
好了,现在我们可以开始准备生成注册码了!
伪代码如下
byte[] Decrypted = new byte[] {0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00};
/*符合要求的一个解密12字节数组*/
byte[] Decoded = Pkcs1Encoding.RSA.Encrypt(Decrypted, privateKey);
/*用RSA私钥Pkcs1Encoding加密12字节;私钥已在上文中求得*/
Decoded[0] |= (byte)(Decoded[22] << 4);
/*低位高位互换(空余4bit)*/
string Serial = Base32.Encode(Decoded);
/*编码为注册码*/
根据伪代码的Decrypted生成的注册码为 5RY7X52DQUYR4H58BVMYRSNXV3AG9BSNNE7P
注册码可以用于Win,Mac,Linux三平台上的Reflector 3
我用C#写了一个Keygen,在附件中有的,需要自己编译!
附件和Keygen在此:
Emmm为什么附件显示不出来……在文章开头那里有!
大家帮我评评分吧!