BinCrack 发表于 2019-12-31 20:51:05

Internet_Download_Manager 6.36 注册算法与网络验证分析

本帖最后由 Rooking 于 2020-1-2 19:08 编辑

飘云阁全网首发,未经许可,禁止转载
更新注册表CLSID生成算法
一直使用的都是破解版的IDM,但版本太低了,有时出现无法截获下载请求的问题。再加上网上只有各种补丁工具,没有找到有人分析IDM注册机制的文章,今天就把最新的IDM6.36做掉,实现无需对程序进行任何patch,就能实现我们想要的注册效果。
首先通过字符串切入到关键点,没用的图片就不贴了。点击注册按钮,胡乱输入注册信息会提示注册码错误('You have entered incorrect Serial Number.'),对该字符串查找交叉引用得到唯一一处结果如下:此处将某指针指向了该字符串,继续查找该指针的交叉引用,会得到多组结果,其中一处猜测为验证函数。
此处完全可以静态分析出注册码的格式为“xxxxx-xxxxx-xxxxx-xxxxx”,当然网上也有现成的注册码供我们参考。刚好以‘-’为分隔符,分成了四部分,接下来程序会对这四部分进行验证。
注释写的很清楚,就不过多解释了,四部分全是相同的操作,这里跳过重复的部分直接分析下一环节的验证过程。验证完毕后就是注册表的操作了,将注册码和身份信息录入注册码,程序会退出。此时本地注册机制已分析完毕,仅给出暴力穷举函数的实现代码,依次暴力穷举四部分,再随机组合就能得到程序全部可用的注册码(给出一组“222YA-2NT7U-2C419-2WKXT”)。import itertools
def getSN(num):
    table = '2YOPB3AQCVUXMNRS97WE0IZD4KLFGHJ8165T'
    for tmp in itertools.product(range(len(table)),repeat=5):
      sum=0
      for i in tmp:
            sum += sum * 36 + i
      if sum % num == 0:
            print "".join(map(lambda x:table,tmp))但此时我们的工作还没结束,如果我们断开网络,IDM自然也能正常运行。但一旦联网就会弹出虚假注册码的提示,很明显是网络校验,网上绝大多数人的做法都是找到验证点patch掉。即使绕过启动时的一处验证,在运行时程序也会开子线程进行验证,patch思路这里不做探讨。今天我们尝试另辟蹊径,我们发现只要程序连过一次网,无论以后网络是否连接都会直接弹出虚假注册码提示,有理由怀疑这个虚假提示信息被存储到了本地。后续分析发现,这些弹窗提示字符串都是加密存储的,会在内存中动态解密。因此对MessageBox下断并向上回溯。定位到此处,分析发现只有该函数返回为0时,程序不会弹出错误提示,并能正常运行出主界面(后续还会有校验,即仅patch此处也行不通),接下来分析该函数。该函数逻辑有些混乱,感兴趣可以自己分析一下。这里仅关注关键点,直接给出正确的执行路径。首先程序从注册表中读取MData表项,并将其作为sub_452030的参数进行解密操作,最后将结果与我们输入的注册码进行比较,相等则成功。接下来分析sub_452030函数。
很清晰的加解密函数结构,为了防止FindCrypt插件,程序作者对加密算法的S盒进行了加密,内存中动态解密。通过识别S盒可以得到IDM使用的算法为RC2,而密钥则是传入的字符串“58BE20ast4si5ls2D13”,没有看到IV自然是ECB模式,至此难点都已分析完毕。我们只需要修改注册表中MData表项的值为RC2算法加密后的HEX值即可,给出与我上面提供的注册码(“222YA-2NT7U-2C419-2WKXT”)对应的MData “6806f3ac00e2967a1aaafc9cfd47b60806cd6e7f4828b021”。

最后啰嗦一句,在32位系统上注册表的路径是不一样的,如果你无法确定路径是什么,或者以后IDM更新了,请使用注册表监视工具Process Monitor。本文主要分析IDM的注册与网络验证机制,最后给出的无需patch的注册操作仅供参考,测试到目前为止还没有出现问题,如果翻车了,就当学习一下思路吧。


=================================================================================================

最终不出所料,果真翻车了,IDM后续还有几处隐藏检测点。只输入上述的内容,会不定时弹出虚假注册码的弹窗,但弹窗不会影响正常使用,即使被呼出注册窗口直接关闭即可。

但对于强迫症来说依然受不了,本文则刨根问底,一探究竟。注意到当对某链接重新下载时,会立即弹出错误提示。最优的做法是监控注册表,程序可能在某个位置又把注册码拿出来校验了一次。实验发现,在开始下载和下载完成两个时机程序都会主动检测,如下图所示:


这里有重要,如上图所示,在读Serial表项前后程序会读取某表的默认项,如上图高亮处所示,而这里存放的注册码的密文,该密文是什么请继续往下看。



上图为一处验证点,sub_452030依然是上文讲到的RC2解密函数,密钥为“lmW02kjM4002FAnzwwsA1”,最后strcmp则是比较解密结果和注册码是否一致,不再赘述。唯一与上文不同的是,这次多了一个sub_593700函数,因此我们对注册码加密后,还必须分析该函数,再进行一层编码。

int __cdecl sub_593700(_BYTE *a1, int a2, int *a3)
{
_BYTE *i; // edi
char *v4; // eax
int v5; // edx
int v6; // ebx
int v7; // eax
int v8; // esi
int result; // eax
_BYTE *v10; // esi
_BYTE *v11; // ecx
unsigned int v12; // ebp
int v13; // edx
_BYTE *v14; // esi
int v15; // eax
int v16; //

for ( i = a1; *i == ' ' || *i == 9; ++i )
    ;
v4 = i + 1;
if ( dword_665B5C[*i] <= 0x3F )
{
    do
      v5 = *v4++;
    while ( dword_665B5C <= 0x3F );
}
v6 = v4 - i - 1;
v7 = (v6 + 3) / 4;
v8 = 3 * v7;
v16 = 3 * v7;
result = sub_5806A0(a2, 3 * v7 + 7);
if ( result )
{
    if ( a3 )
      *a3 = v8;
    v10 = sub_580690(a2);
    v11 = i;
    if ( v6 > 0 )
    {
      v12 = (v6 + 3) >> 2;
      v6 -= 4 * v12;
      do
      {
      v13 = v11;
      v11 += 4;
      *v10 = 4 * LOBYTE(dword_665B5C[*(v11 - 4)]) | (dword_665B5C >> 4);
      v14 = v10 + 1;
      *v14 = 16 * LOBYTE(dword_665B5C[*(v11 - 3)]) | (dword_665B5C[*(v11 - 2)] >> 2);
      v10 = v14 + 2;
      --v12;
      *(v10 - 1) = LOBYTE(dword_665B5C[*(v11 - 1)]) | (LOBYTE(dword_665B5C[*(v11 - 2)]) << 6);
      }
      while ( v12 );
    }
    if ( v6 & 3 )
    {
      if ( dword_665B5C[*(v11 - 2)] <= 63 )
      v15 = v16 - 1;
      else
      v15 = v16 - 2;
      v16 = v15;
    }
    *(sub_580690(a2) + v16) = 0;
    result = 1;
}
return result;
}

代码如上,很明显是base64解码算法,此时不能确实是否为标准base64,我们只需要用rc2加密注册码再以标准base64编码,替换到对应默认表项测试程序即可验证(最终符合我们猜测,提供与我上文提供的注册码对应的密文“Ib1wA+FbvWMoIqeSo2c2n003utBSSh0w”)。

此时,开始下载的检测点就被干掉了,而下载完成的监测点是一模一样的使用相同的方式进行替换。程序依然再测试中,不能保证后续没有其他验证。

总结一下目前为止发现的验证机制:
1. 本机注册码校验:非常挫的四则运算最后取余
2. MData表项校验:来自服务器下发,为RC2加密的HEX值
3. 默认表项校验:目前发现两处,来自服务器下发,为RC2加密的Base64值


本文更多是作为分析软件验证机制,不提供傻瓜式一键Hack成品。而注册表项不同电脑可能不一样,需要你再看懂文章的前提下使用ProcMon找到自己电脑的位置(看MData结尾的自然就一定对了,找默认表项则是看读Serial表项的前后)。
更理想的方案是分析注册时软件与服务器的通信协议,直接伪造服务器下发的MData和默认表项密文,这样就不需要考虑找注册表的问题了,而且不会遗漏。


=======================================================================================================================================


和rooking表哥交流了一下,把注册表CLSID的生成算法搞定了,给做通用注册机的表哥填坑。
if ( sub_4C20F0(&v119) )                      // a22fd67
{
    sub_624442(&v28, &v120);
    LOBYTE(v123) = 1;
    if ( sscanf(v28, aLx, &sum) == 1 )
    {
      if ( strlen(::a3) > 4 )
      {
      strncpy(v117, ::a3, 5u);
      v118 = 0;
      v1 = 1;
      v2 = 0;
      do
      {
          v3 = v117;
          v1 = 7 * v1 + 2 * v3;
      }
      while ( v2 < 5 );
      sum += v1;
      }
      v4 = v28;
      v5 = 1.33;
      if ( strlen(v28) < 0x20 )
      {
      while ( 1 )
      {
          v121 = sum;
          v6 = (sum * v5);
          v122 = v5 + 0.27;
          sub_624442(v117, &v28);
          LOBYTE(v123) = 2;
          sub_621421(&v28, aSLx, *v117, v6);
          LOBYTE(v123) = 1;
          sub_6246CD(v117);
          v4 = v28;
          if ( strlen(v28) >= 0x20 )
            break;
          v5 = v122;
      }
      }
    }
    else
    {
      v4 = v28;
    }
    if ( strlen(v4) > 0x1F )
    {
      memset(&a3, 0x2Du, 0x18u);
      v7 = 0;
      v8 = v4 + 31;
      do
      *(&a3 + v7++) = *v8--;
      while ( v7 < 8 );
      v9 = 9;
      if ( v7 < 12 )
      {
      v10 = 12 - v7;
      v11 = &v4[-v7 + 31];
      v7 = 12;
      do
      {
          *(&a3 + v9++) = *v11--;
          --v10;
      }
      while ( v10 );
      }
      v12 = v9 + 1;
      if ( v7 < 16 )
      {
      v13 = 16 - v7;
      v14 = &v4[-v7 + 31];
      v7 = 16;
      do
      {
          *(&a3 + v12++) = *v14--;
          --v13;
      }
      while ( v13 );
      }
      v15 = v12 + 1;
      if ( v7 < 20 )
      {
      v16 = 20 - v7;
      v17 = &v4[-v7 + 31];
      v7 = 20;
      do
      {
          *(&a3 + v15++) = *v17--;
          --v16;
      }
      while ( v16 );
      }
      v18 = v15 + 1;
      if ( v7 < 32 )
      {
      v19 = &v4[-v7 + 31];
      v20 = &a3 + v18;
      v21 = 32 - v7;
      do
      {
          *v20++ = *v19--;
          --v21;
      }
      while ( v21 );
      }
算法很简单,基本上就是copy就完事,会获取到磁盘驱动器卷标序列号和注册码的前5位进行计算,这里仅贴出以上代码对应的Python算法。
import math
VolumeSerialNumber="CA22FD67"
sn="222YA"
sum = 1
for i in sn:
    sum = 7 * sum + 2 * ord(i)
sum += int(VolumeSerialNumber,16)
v5 = 1.33
clsid = VolumeSerialNumber.lower()
while(len(clsid) < 0x20):
    tmp = sum * v5
    v5 += 0.27
    clsid += "%lx"%(math.trunc(tmp)&0xFFFFFFFF)
clsid = list(clsid[:32])
clsid.insert(12,'-')
clsid.insert(17,'-')
clsid.insert(22,'-')
clsid.insert(27,'-')
print "".join(clsid[::-1])


===============================================================================================================================



后续发现两处CLSID生成算法差异有点大,就将两处算法整合到一个脚本中,省去了基础不好的朋友寻找注册表位置的麻烦。只需要将VolumeSerialNumber变量替换为你本机的磁盘序列号,修改对应的注册码sn变量即可。
直接再cmd窗口输入vol命令即可得到磁盘序列号,请手动去掉中间的‘-’:
C:\Users\fxb>vol
驱动器 C 中的卷是 Windows
卷的序列号是 CA22-FD67
import math
VolumeSerialNumber="CA22FD67"
sn="222YA"
def getCLSID1(VolumeSerialNumber,sn):
    sum = 1
    for i in sn:
      sum = 7 * sum + 2 * ord(i)
    sum += int(VolumeSerialNumber, 16)
    v5 = 1.33
    clsid = VolumeSerialNumber.lower()
    while (len(clsid) < 0x20):
      tmp = sum * v5
      v5 += 0.27
      clsid += "%lx" % (math.trunc(tmp) & 0xFFFFFFFF)
    clsid = list(clsid[:32])
    clsid.insert(12, '-')
    clsid.insert(17, '-')
    clsid.insert(22, '-')
    clsid.insert(27, '-')
    print "".join(clsid[::-1])
def getCLSID2(VolumeSerialNumber,sn):
    sum = 1
    for i in sn:
      sum = 5 * sum + 2 * ord(i)
    sum += int(VolumeSerialNumber,16)
    sum /= 3
    v5 = 1.55
    clsid = ''
    while(len(clsid) < 0x20):
      sum = math.trunc(sum * v5)&0xFFFFFFFF
      v5 += 0.25
      clsid += "%lx"%(sum)
    clsid = list(clsid[:32])
    clsid.insert(8,'-')
    clsid.insert(13,'-')
    clsid.insert(18,'-')
    clsid.insert(23,'-')
    print "".join(clsid)
getCLSID1(VolumeSerialNumber,sn)
getCLSID2(VolumeSerialNumber,sn)



程序功能依然在测试中,暂时不能保证后续不会有弹窗提示,但不会影响正常使用。


BinCrack 发表于 2019-12-31 23:24:03

zwagon 发表于 2019-12-31 23:17
最终我们要如何操作呢

1. 下载最新版IDM
2.点击注册 序列号输入帖子提供的
3.修改注册表MData为帖子提供的数据


xmeson 发表于 2020-4-21 03:39:28

分析的好,6.37build10已用未弹窗,getCLSID2 中 sum /= 3 应改为 sum = math.trunc(sum/3),否则和IDM中的对不上。

smallhorse 发表于 2019-12-31 20:54:15

沙发膜拜kg牛鞭大师!

wgz001 发表于 2019-12-31 20:56:38

666,学习了

薰衣草恋人 发表于 2019-12-31 20:59:01

大神确实厉害,学习了。

我有浊酒一杯 发表于 2019-12-31 21:08:02

话说这个干啥的{:sweat:}

双面灵笼 发表于 2019-12-31 21:27:36

各位表哥元旦快乐

不破不立 发表于 2019-12-31 22:03:16

分析很到位i,收藏学习

不破不立 发表于 2019-12-31 22:04:00

分析很到位,收藏学习

085304229 发表于 2019-12-31 22:15:46

大佬那个注册表信息怎么样查找,我是win10 64位的,应该怎么样找“HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID”找到这个目录后应该找哪个,没有找到跟你文中显示一样的

BinCrack 发表于 2019-12-31 22:20:03

085304229 发表于 2019-12-31 22:15
大佬那个注册表信息怎么样查找,我是win10 64位的,应该怎么样找“HKEY_CURRENT_USER\Software\Classes\WOW ...

找不到的话就用帖子里面说的工具,监控一下你电脑的真实路径
页: [1] 2 3 4 5 6 7 8
查看完整版本: Internet_Download_Manager 6.36 注册算法与网络验证分析