吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 18974|回复: 68
收起左侧

[原创] 多平台多角度分析XXX注册算法

  [复制链接]
JemmyloveJenny 发表于 2019-5-3 02:20
本帖最后由 zjh16529 于 2019-6-6 18:25 编辑

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的过程,没想到这个软件还需要注册……那我就顺便把它注册的算法分析了吧!
Register.png

废话不多说来了……那我们上手吧!
程序的下载地址 https://www.airsquirrels.com/reflector/try

首先从主程序的注册界面入手,主程序一看是.Net,直接拖入dnSpy。居然混淆都没有……简直太顺畅了。
TrialWindow.png
输入注册码的窗口叫做TrialWindow,dnSpy里面一下子就找到了。里面的代码没什么可说的,就是监测输入框里的内容,注册码输完后调用SetFromKeyValidationState
SetFromKeyValidationState.png
可以看到,关键的验证步骤就在 ReflectorLicense.CheckLicenseIsValid() 我们跟过去看看。
CheckLicenseIsValid.png
返回值是一个bool,true代表注册成功,false代表注册失败。网上流传的破解版本就是简单粗暴地把这个函数改成

internal static bool? CheckLicenseIsValid(string license)
{
        return new bool?(true);
}

就爆破成功了……网上能找到的破解版本几乎都是这样爆破的。没有暗装、没有功能限制,真的就完全破解了。
然鹅,主程序是有数字签名的,此种爆破之后数字签名就被破坏了。这对于我这种对软件有洁癖的人来说简直不能忍!!!

那行吧,我们继续往下研究。
看看ReflectorNativeInstance.ValidateLicenseKey()是从哪冒出来的
ValidateLicenseKey.png
现在这个函数已经不在主程序里了,在一个叫做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模式,如图
ValidateLicenseKey-IL.png
关键部分的代码就是我现在选中的那一块
我来解释一下此处的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)填充。
ValidateLicenseKey-UltraEdit.png
图中选中部分就是函数原本,大部分都被填充了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
LoadFile.png
然后在Functions窗口搜索函数的RVA(0x00414A70),前面的00直接忽略,输入414A70,然后就能看到函数了
414A70.png
Windows版本反编译之后函数名称不直观,我也就不改我后面的写法了……我还是按照Mac版本分析算法

正当我没有头绪的时候,我突然想起Reflector还有Mac版本呢!
在Mac版本上不可能用到.Net的混合程序集,可能全部都是Native代码!
于是立即就把Mac版本下载下来,拖进IDA一探究竟。

Mac程序拖进IDA,函数名称居然都看得到!!!真的是把我震惊到了……
无论如何我们算是找到了RFValidateLicenseKey的主体部分!
Mac汇编不熟悉,那就不硬磕汇编了,直接F5大法,改改变量名称和类型,可读性还蛮高的
RFValidateLicenseKey.png
咦?居然有字符串欸!

-----BEGIN PUBLIC KEY-----
MDIwDQYJKoZIhvcNAQEBBQADIQAwHgIXDoK2SPT7JNSYxgO1oJwxO3dIZzYSICcC
AwEAAQ ==
-----END PUBLIC KEY-----

这玩意是个公钥,看来注册码用了非对称加密算法。
公钥使用Asn1编码保存,那么就Asn1Editor伺候。
PubKey.png
可以看出这是一个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
SquirrelsLicenseKeyDecode-1.png
SquirrelsLicenseKeyDecode-2.png
挑些关键步骤解释一下

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为什么附件显示不出来……在文章开头那里有!

大家帮我评评分吧!

免费评分

参与人数 42吾爱币 +49 热心值 +36 收起 理由
Dawning + 1 + 1 我很赞同!楼主牛批
phoenix_jn + 1 + 1 我很赞同!
ssyypyp + 1 + 1 用心讨论,共获提升!
默生a + 1 我很赞同!
yixi + 1 + 1 谢谢@Thanks!
青春丿易逝 + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
5omggx + 1 膜拜大神
hebo308 + 1 + 1 好文章啊 收藏!!
voyage1969 + 1 + 1 我很赞同!
bricher9988 + 1 + 1 我很赞同!
solly + 1 + 1 我很赞同!
you920928 + 1 + 1 谢谢@Thanks!
小小的 + 1 + 1 用心讨论,共获提升!
就是这么帅 + 1 + 1 谢谢@Thanks!
fangchang819 + 1 + 1 谢谢@Thanks!
fififa + 1 + 1 谢谢@Thanks!
/bq + 1 + 1 谢谢@Thanks!
一步梦想 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
agool123456 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
dNp + 1 + 1 谢谢@Thanks!
Anonymous、 + 2 + 1 我很赞同!
大龙哥哥 + 1 + 1 我很赞同!
wanmei + 1 + 1 用心讨论,共获提升!
shaunkelly + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
笙若 + 1 + 1 谢谢@Thanks!
wstclzy2010 + 2 + 1 用心讨论,共获提升!
liucq + 2 + 1 赞一个
yanglinman + 1 谢谢@Thanks!
asaSKTY + 1 用心讨论,共获提升!
wwh1004 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
icode2019 + 1 已经处理,感谢您对吾爱破解论坛的支持!
xiaoyu2032 + 1 谢谢@Thanks!
wmsuper + 3 + 1 谢谢@Thanks!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
AmIzero + 1 + 1 用心讨论,共获提升!
tyyv110 + 1 + 1 我很赞同!
52loli + 1 + 1 今天的热心就给楼主啦!
虐心i + 2 谢谢@Thanks!
ltr0030 + 1 我很赞同!
mc_huzi + 1 + 1 膜拜
qaz003 + 1 + 1 赞一个,条理好清析
平淡最真 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

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

wwh1004 发表于 2019-5-3 11:05
好久没看过这样的文章了。
dnSpy可以十六进制编辑程序集的,这样
Snipaste_2019-05-03_11-02-27.png
点我箭头指着的地方,dnSpy会跳转到对应位置
Snipaste_2019-05-03_11-03-51.png
会显示出是哪一条指令
 楼主| JemmyloveJenny 发表于 2019-5-4 11:45
xxxallen 发表于 2019-5-4 11:44
请问下大佬,32位win7 vs2015编译后出错,是平台的问题,还是其它,谢谢

错误提示是什么?报了什么错?
可能是.NetSDK版本的问题。我使用2017写的,可能你需要给项目降级到2015自带的SDK
yhym599 发表于 2019-5-3 03:20
qaz003 发表于 2019-5-3 07:36
谢谢分享……写得好生动……。
肚子会饿 发表于 2019-5-3 08:29
谢谢楼主,希望能多多分享,写的很好
tyyv110 发表于 2019-5-3 09:27
樓主很強!!
lzhq123 发表于 2019-5-3 10:02
大神牛逼,图文并茂!!
mincelia 发表于 2019-5-3 10:30

谢谢楼主,希望能多多分享,写的很好
plmoknplm 发表于 2019-5-3 10:44
学习了,太高深,谢谢分享。
 楼主| JemmyloveJenny 发表于 2019-5-3 11:16
wwh1004 发表于 2019-5-3 11:05
好久没看过这样的文章了。
dnSpy可以十六进制编辑程序集的,这样

wow!这个功能之前没发现欸……
学习了,谢谢!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-15 13:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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