某.net组件库 注册码逆向分析

查看 96|回复 8
作者:pjy612   
前情提要
在摸鱼时翻nuget,发现有个 组件库 ,组件还不少,感觉可以分析一波。
PS.发帖的时候 用MD 图片打算先占位,结果直接拦截,内容全丢了,导致重写了一次...
[color=]实惨!!!QAQ
{:301_972:}
受害者 介绍
aHR0cHM6Ly93d3cucmViZXgubmV0L3RvdGFsLXBhY2sv


0.png (128.24 KB, 下载次数: 0)
下载附件
2023-3-28 21:18 上传

准备工作
//Rebex.Licensing.Key = RebexPatch.GetLicense();
using (var client = new Rebex.Net.Sftp())
{
    // connect and log in
    client.Connect("127.0.0.1",2222);
    client.Login("tester", "password");
    // download a file
    SftpItemCollection list = client.GetList();
}
分析
然后没加Key 跑起来直接提示异常。
临时Key 可以在 下面地址申请。(好感+1
aHR0cHM6Ly93d3cucmViZXgubmV0L3N1cHBvcnQvdHJpYWwv
那么来看看它都干了啥吧。
首先当然是扔 dnspy 里面 看看
定位点是
Rebex.Licensing.Key


1.png (21.51 KB, 下载次数: 0)
下载附件
2023-3-28 20:22 上传



2.png (38.96 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传



3.png (51.44 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

一般用到静态类和静态字段的话,鉴权一般也会放在对应的类里面。
所以简单看了下这个类 haqbo
发现了两个可疑的函数,然后查查他们的调用链


4.png (68.05 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

拥有相同的调用链
那么进去就看看


5.png (113.93 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

里面返回的是一个枚举,有两个值,然后 优化下函数逻辑后可知。
internal static gqkze ihbdt(avgel p0)
{
  sioyq hopul = p0.hopul;
  string nnstu = p0.nnstu;
  int wxeiw = p0.wxeiw;
  if (p0.vjlwo == hjedi.exnqw)
    return gqkze.kfryg;
  Dictionary eursq = haqbo.eursq;
  bool lockTaken = false;
  try
  {
    Monitor.Enter((object) eursq, ref lockTaken);
    fbqkl fbqkl1 = (fbqkl) null;
    fbqkl fbqkl2 = (fbqkl) null;
    foreach (byte[] p0_1 in haqbo.oripl)
    {
      switch ((char) p0_1[0])
      {
        case '#':
          if (fbqkl2 == null)
          {
            fbqkl2 = fbqkl.djjbo(nnstu);
            continue;
          }
          continue;
        case 'A':
        case 'T':
          fbqkl2 = haqbo.ufgeu(p0_1, nnstu, wxeiw);
          if (fbqkl2 == null)
            return gqkze.irmxg;
          continue;
        case 'F':
          fbqkl1 = haqbo.tjeki(p0_1, hopul, nnstu, wxeiw);
          if (fbqkl1 == null)
            return gqkze.kfryg;
          continue;
        default:
          continue;
      }
    }
    if (p0.vjlwo != hjedi.brafw && !ffppi.eztdl)
    {
      Assembly jovip = p0.jovip;
      if (jovip != (Assembly) null)
      {
        AssemblyName name = jovip.GetName();
        if (name != null && name.Name == nnstu)
        {
          fbqkl fbqkl3 = haqbo.eicwj(haqbo.raxrd(jovip), nnstu, wxeiw);
          if (fbqkl3 == null)
            return gqkze.irmxg;
          fbqkl2 = fbqkl2 ?? fbqkl3;
        }
      }
    }
    throw (fbqkl1 ?? fbqkl2) ?? fbqkl.djjbo(nnstu);
  }
  finally
  {
    if (lockTaken)
      Monitor.Exit((object) eursq);
  }
}
internal enum gqkze
{
    // Token: 0x04000074 RID: 116
    kfryg, //prod
    // Token: 0x04000075 RID: 117
    irmxg  //trial
}
我们可以直接在这个类 return gqkze.kfryg!
yeah!搞定收工!(bushi
继续分析...
其中有 fbqkl.djjbo 和 fbqkl.audqj


6.png (68.8 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

可知 fbqkl 类 是个异常类 所以后续可以忽略。
我们先瞅瞅 T 和 A 的逻辑。
private static void dvrgx(byte[] p0, out DateTime p1, out DateTime p2, out int p3, out int p4)
{
    int num = (int)p0[0] * 256 + (int)p0[1];
    p3 = (int)p0[2] * 256 + (int)p0[3];
    p4 = (int)p0[4];
    p2 = new DateTime(2010, 12, 31).AddDays((double)num);
    p1 = p2.AddDays(-64.0);
    //这里给了两个时间, 其中有个 -64天
    //p2=trial 到期时间
    //p1=trial - 64天
}
//p0 trialKey
//p1 程序集名
//p2 程序集版号
private static fbqkl ufgeu(byte[] p0, string p1, int p2)
{
    byte[] array = haqbo.kaueg(p0); // 下面函数 对trialKey 进行了一个处理
    if (array == null)
    {
        return new fbqkl("Invalid license key.");
    }
    DateTime dateTime;
    DateTime dateTime2;
    int num;
    int num2;
    haqbo.dvrgx(array, out dateTime, out dateTime2, out num, out num2);//处理完的数据 在上面 进行关键值提取
    if ((num2 & 1) != 0)
    {
        return fbqkl.djjbo(p1);//异常
    }
    if (num  0 && p2  dateTime2)
    {
        //异常
        return fbqkl.audqj(p1);  //这里trial到期
    }
    return null;
}
private static byte[] kaueg(byte[] p0)
{
    RSAParameters rsaparameters = default(RSAParameters);
    rsaparameters.Modulus = new byte[]
    {
        182, 28, 156, 84, 172, 217, 205, 220, 65, 15,
        164, 101, 241, 26, 18, 244, 193, 44, 214, 186,
        203, 99, 154, 131, 134, 199, 190, 107, 233, 99,
        126, 147
    };
    rsaparameters.Exponent = new byte[] { 1, 0, 1 };
    RSAManaged rsamanaged = new RSAManaged(true);
    rsamanaged.ImportParameters(rsaparameters);
    byte[] array = new byte[p0.Length - 1];
    Array.Copy(p0, 1, array, 0, array.Length);
    byte[] array2;
    try
    {
        //说实话 这里为什么要用加密函数??? 这套trial 的逻辑 我反而没看懂
        //但是这里不是系统库,可能重写了什么RSA的逻辑,不过这不是这次的重点不深究了
        array2 = rsamanaged.EncryptValue(array);
        array2 = RSAManaged.tclma(array2, 7, true);
    }
    catch (CryptographicException)
    {
        array2 = null;
    }
    return array2;
}
还是回到 F 的吧...
//p0 key 字节数组
//p1 鉴权枚举
//p2 程序集名
//p3 程序集版本
private static fbqkl tjeki(byte[] p0, sioyq p1, string p2, int p3)
{
    //这里知道 字节数组 长度要大于 45
    if (p0.Length
有个明显的 DSAManaged 应该是用到一些DSA的逻辑。


7.png (129.56 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

分析一下 发现 接口实现挺多,但是总之应该 DSA 的鉴权。
先跳过,继续分析。。。。
这里有个 jveps类 用来解析 license 的真实数据。


8.png (248.31 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

其中用到的几个函数
public uint wsbya()
{
    this.kxkiy(this.kzdmt, 0, 4);
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(this.kzdmt, 0, 4);
    }
    return BitConverter.ToUInt32(this.kzdmt, 0);
}
public int jywwn()
{
    this.kxkiy(this.kzdmt, 0, 4);
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(this.kzdmt, 0, 4);
    }
    return BitConverter.ToInt32(this.kzdmt, 0);
}
public byte bbdwb()
{
    if (this.mcprv >= this.mbfba)
    {
        throw new fyvfn("Not enough data available.");
    }
    byte b = this.nfkkb[this.mcprv];
    this.mcprv++;
    return b;
}
public ushort mepme()
{
    this.kxkiy(this.kzdmt, 0, 2);
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(this.kzdmt, 0, 2);
    }
    return BitConverter.ToUInt16(this.kzdmt, 0);
}
直觉上来说就是 自制的 字节流处理类。类似 ByteArray BinaryReader 什么的。
那么 逆推 的 伪代码就是
var br = new BinaryReader();
br.ReadUInt32();
while (br.BaseStream.Position
其中我们能知道
num2 为 1或2
num3 越大越好
那么 这个 num 是什么呢?
继续看
[Flags]
internal enum sioyq
{
    // Token: 0x04000054 RID: 84
    rcuhg = 1,
    // Token: 0x04000055 RID: 85
    fibxo = 2,
    // Token: 0x04000056 RID: 86
    odpil = 4,
    // Token: 0x04000057 RID: 87
    ztmcw = 8,
    // Token: 0x04000058 RID: 88
    iuqnd = 16,
    // Token: 0x04000059 RID: 89
    nyqjp = 32,
    // Token: 0x0400005A RID: 90
    xsxcx = 64,
    // Token: 0x0400005B RID: 91
    gzzvp = 128,
    // Token: 0x0400005C RID: 92
    tvtws = 256,
    // Token: 0x0400005D RID: 93
    jalwj = 512,
    // Token: 0x0400005E RID: 94
    gnjbt = 1024,
    // Token: 0x0400005F RID: 95
    nfklw = 2048,
    // Token: 0x04000060 RID: 96
    nquoh = 4096,
    // Token: 0x04000061 RID: 97
    cocit = 8192,
    // Token: 0x04000062 RID: 98
    tcmnt = 16384,
    // Token: 0x04000063 RID: 99
    mbwum = 65536,
    // Token: 0x04000064 RID: 100
    ltklj = 131072,
    // Token: 0x04000065 RID: 101
    ejqlp = 262144,
    // Token: 0x04000066 RID: 102
    icusp = 524288,
    // Token: 0x04000067 RID: 103
    bvrug = 1048576,
    // Token: 0x04000068 RID: 104
    udpdo = 2097152,
    // Token: 0x04000069 RID: 105
    eynsu = 4194304,
    // Token: 0x0400006A RID: 106
    jncra = 8388608,
    // Token: 0x0400006B RID: 107
    evzga = 33554432
}
internal enum vshmk
{
    // Token: 0x0400007B RID: 123
    iwkpg = 1,
    // Token: 0x0400007C RID: 124
    cxbcm,
    // Token: 0x0400007D RID: 125
    olrse = 4,
    // Token: 0x0400007E RID: 126
    qnxpz = 8,
    // Token: 0x0400007F RID: 127
    oglpz = 16,
    // Token: 0x04000080 RID: 128
    oelig = 32,
    // Token: 0x04000081 RID: 129
    fsikk = 64,
    // Token: 0x04000082 RID: 130
    awqdf = 128,
    // Token: 0x04000083 RID: 131
    zugrh = 256,
    // Token: 0x04000084 RID: 132
    ojtis = 512,
    // Token: 0x04000085 RID: 133
    yckct = 1024,
    // Token: 0x04000086 RID: 134
    lacls = 2048,
    // Token: 0x04000087 RID: 135
    yjmkv = 4096,
    // Token: 0x04000088 RID: 136
    mnbep = 15728816,
    // Token: 0x04000089 RID: 137
    xcpuh = 251658296,
    // Token: 0x0400008A RID: 138
    brzmp = 2147483647
}
两个枚举 还有一个 [Flags]
配上函数


9.png (105.59 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

就是拼装权限了,那么能不能一个值就拿到最大权限呢?
写个demo 试试。。。
//evzga = 33554432
var foo = (2147483647 & 33554432) == 33554432;
//foo = true
那么三个值就都有了。
我们可以自己尝试去加,如果类自身包含写的逻辑也可以用反射去实现。
这里推荐反射,以彼之道还至彼身。


10.png (201.82 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

license 结构已经搞定,还差个Sign
我们先就用系统的DSA试试,只要没啥魔改。
DSA dsa = DSA.Create();
//备用 之后 有用
byte[] priKey = dsa.ExportPkcs8PrivateKey();
byte[] signByte = dsa.SignData(value,HashAlgorithmName.SHA1);
有了Sign那注册码的最终形态就呼之欲出了!
byte[] xor = new byte[value.Length];
for (int i = 0; i
接着我们来处理 鉴权逻辑的处理。
如果用的是系统函数,那么我们True就行了。但是这次的受害者好像有自己的想法。


11.png (114.82 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

接口的实现是自制的。
先看看公钥部分 jidav.dbmak
//jidav.dbmak 的实现
hhtwd jidav.zmris(PublicKeyInfo p0)
{
    cvxbb cvxbb = this.kcaco.iyfql();
    cvxbb.fkqdt(p0);
    return cvxbb;
}
public virtual void fkqdt(PublicKeyInfo p0)
{
    this.fqoio();
    if (this.ihotv())
    {
        cvxbb.qapbb();
    }
    byte[] array = this.gjnry.vonlf(p0);//又是接口
    this.ymifo(array);
}
public byte[] vonlf(PublicKeyInfo p0)
{
    if (p0 == null)
    {
        throw new ArgumentNullException("publicKeyInfo");
    }
    DSAParameters dsaparameters = p0.GetDSAParameters();//这个函数可以入手
    return this.yrqtx(ref dsaparameters, false);
}
private byte[] yrqtx(ref DSAParameters p0, bool p1)
{
    nwyxq.prftd(ref p0, p1);
    if (p1)
    {
        nwyxq.bwert(ref p0);
    }
    int num = p0.P.Length;
    int num2 = Marshal.SizeOf(typeof(ogska.jajew)) + p0.P.Length + p0.G.Length + num;
    if (p1)
    {
        num2 += 20;
    }
    byte[] array = new byte[num2];
    jveps jveps = new jveps(array); //熟悉的流操作对象
    int num3 = (p1 ? 1448104772 : 1112560452);
    jveps.jabtz(num3);
    jveps.jabtz(p0.P.Length);
    if (p0.Counter
鉴权的实现也是...
public virtual bool iygcq(byte[] p0, byte[] p1, djrra p2)
{
    this.fqoio();
    return this.tdrmf(p0, p1, p2);
}
protected virtual bool tdrmf(byte[] p0, byte[] p1, djrra p2)
{
    if (p0 == null)
    {
        throw new ArgumentNullException("hash");
    }
    if (p1 == null)
    {
        throw new ArgumentNullException("signature");
    }
    if (p2 == null)
    {
        throw new ArgumentNullException("signVerifyParameters");
    }
    this.oqsoc();
    bool flag;
    using (cvxbb.jzvtl jzvtl = this.ylokf(p2))
    {
        uint num = ogska.BCryptVerifySignature(this.fygoz, jzvtl.negbf, p0, p0.Length, p1, p1.Length, jzvtl.zrnyc);
        if (num == 3221266432U)
        {
            flag = false;
        }
        else if (num == 3221225485U)
        {
            flag = false;
        }
        else
        {
            ogska.kwiot(num);
            flag = true;
        }
    }
    return flag;
}
[DllImport("Bcrypt.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern uint BCryptVerifySignature(ogska.svapf hKey, IntPtr pPaddingInfo, byte[] hashBytes, int hashBytesSize, byte[] signatureBytes, int signatureBytesSize, uint flags);
所以下手点 就决定是你了 PublicKeyInfo.GetDSAParameters
开工
祭出神器 HarmonyLib


12.png (160.48 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

结果


13.png (58.38 KB, 下载次数: 0)
下载附件
2023-3-28 20:23 上传

搞定收工!

下载次数, 下载附件

pjy612
OP
  

坑爹的图片逻辑。。。搞得重写了一遍。。。坑死啊!!!
以后 坚决不用 []() 占位了。。。
沙发自己坐。
3yu3   

大佬,膜拜中。。这种程序分支盘根错节,毫无可读性,一看分析的头都大。写出来这么多分析结果,实属不易,得仔细阅读学习修炼功力。
另外HarmonyLib功能强大啊,可否出一篇文章讲一下,比如 如何对已经加载的插件类软件或者运行起来的程序对内存进行patch,patch它的方法和一些常量。
zxxiaopi   

感谢分享!
yiting8   

哪里下载的,我来练练手
pjy612
OP
  


yiting8 发表于 2023-3-29 11:07
哪里下载的,我来练练手

介绍里base64。
nuget上面也能找到。
我是拿了一个sftp的包弄的,你有兴趣可以去试试unzip组件包。核心鉴权都在一个common库里。
所以一般这种推荐内存补丁的形式去用。免的去操心强签名什么的
pjy612
OP
  


3yu3 发表于 2023-3-29 10:44
大佬,膜拜中。。这种程序分支盘根错节,毫无可读性,一看分析的头都大。写出来这么多分析结果, ...

其实 这些基本就是练 代码阅读理解 和 调试分析。
刚开始可能还要配合调试器看看,之后基本就是看代码片段,看调用链上下游。。。
直觉练出来之后,正常开发中定位问题会变得很快。。。
而且 一些逻辑不复杂的直接带着混淆看.... 前提是没加强壳
qctutu   

谢谢学长值得分享
wauiiu   

大佬,膜拜中。
您需要登录后才可以回帖 登录 | 立即注册