zhj777 发表于 2024-8-16 14:02

.net程序破解实践与理解二、代码分析篇1:启动验证(写给新手,超详细)

本帖最后由 zhj777 于 2024-8-16 14:55 编辑

      
    上次分析到程序重启动后有个验证检查是否是已注册版的地方FuncReadRegFile(),



追踪到这里后直接把其他代码删掉,只把返回语句return result



改成return true就爆力破解了。


   
    当时猜测程序运行到这里后先读取注册表,然后把注册表里面存的注测码和真正的注册码重新进行比对从而验证程序是否是已注册版。今天咱们就来追一下代码,看是不是通过注册表获得的验证,并且分析一下代码的含义,并分享给大家我写的注册机。   
    开工:打开dnspy,加载上原程序,在搜索框里粘贴上方法名FuncReadRegFile,(也可通过上节搜索注册码找到这里),右边选上“以上所有”,不能选“数字/字符串了”,因为这是一个“类”中的“方法”名,不属于字符串。一会儿下方出现方法名后,双击方法名。



    在上面代码窗口中第一行代码      string right = ""左方单击,然后点F9下断,启动┄加载程序┄确定,程序运行一会儿就会在下断处断下来。然后我们点击下面的“局部变量”切换到变量显示窗口。



就可以手动点键盘上的F10,一步步走过代码,通过方法名和代码窗口下面的局部变量窗口来了解代码的作用。代码中橙色的就是方法名。当走过“bool flag = MyProject.Comp....”这一行时知道这一行是检查C:\Users\dell\Documents\VisualStudio\ADOMD_NET\Primary_Interop_Assemblies
目录下面“mspaibzd75.dll”文件是否存在。



存在就返回给flag一个true的值,否则就返回false。现在dll文件不存在,所以就返回给flag一个false的值,



那程序就跳过大括号内面的内容,



直接跳到result=false这一行。“通过这一行把false赋值给result,false的意思是“假”。下一行return result,也就返回了一个“假”,程序就知道是未注册版。程序启动起来后,注册窗口可用。




   
    下面我们再来看看注册成功后的情况。加载我们破解过的那个主程序(不知道的看第一篇帖子),就是随便输入15位注册码能注册成功的那个(因为我们不知道真注册码,只能通过用破解版来实现注册成功),注册成功后点停止调试。





然后移除这个破解版的重新加载原版,



搜索FuncReadRegFile方法名,看看断点还在不在,若不在了就在第一行重新下断,点启动。程序断下来后,再F10单步走过“bool flag = MyProject.Computer.FileSystem.FileExists(RegData.gStrSystemDir + "\\mspaibzd735.dll");”来到“if (flag)”这一行。切换到“局部变量”窗口,可以看到flag的值是true了。


   
说明程序通过上一行代码在目录:C:\Users\dell\Documents\VisualStudio\ADOMD_NET\Primary_Interop_Assemblie中建上了mspaibzd735.dll文件了。你的目录可能不一样,但可以在局部变量窗口中找到它对应的目录。
   



你可以打开目录看一下,如果看不到那要把“文件夹显示”设置一下,让它能显示隐藏文件,原程序建的这个文件属性是隐藏的。




再F10单步走就能走过大括号中的代码了。




检查完dll文件是否存在后,下边代码就是打开dll文件,把里边的两部分加密过的内容分别传给text和text2。加密的内容啥也看不懂,就看着有点乱哈。




后边会把加密内容解密出来,然后把解密后的字符串切割成5段放入数组中,通过数组再传给变量right、left等。




下面就是比较机器码、比较注册码中部分字符串了。都相同那就返回true,程序就知道是已注册版了,



程序启动后注册框就不能再注册了。



否则返回false,程序就知道这是未注册版,注册框就还能用。


  
    小结:通过上面的代码分析,我们明白了此程序启动时验证是否是注册版不是通过注册表验证的,而是通过dll文件验证的。
  下面咱们再到最主要的算法方法里面去逛逛。单击方法名funcGetZhucema进入  



这里代码意思是先清除机器码中的空格、短横线、逗号等得到字符串1,再分别从字符串1左边截取10个字符,
右边截取5个字符拼接成新字符串2。字符串2经过getFalseMd5(text)、.md5(ref falseMd)、funcGetNumber_20100601(ref text2)三个方法的计算就得到注册码中1-4和9-12接成的字符串3。







  程序重启后的验证主要就是字符串3中的前4位和注册码中的前4位进行比较,字符串中的后4位和注册码中
的9至12位进行比较,从而判断是否是已注册版。现在咱就可以复制出字符串3,放到注册  码中对应位置进行
真正的注册了。


写在最后:
  1、程序的验证有多种方法,有的是注册表验证,有的是dll文件验证,有的是ini文件验证等,不管是哪种都是
为了把注册信息存到一个地方,以便程序重启时调用信息来进行验证。因此在OD中有下打  开注册表子键的断点
bp RegOpenKeyEx 、打开文件的断点bp ReadFile 等来查找验证的关键点。
  2、真注册成功后,注册窗口就不能用了,删掉这个mspaibzd735.dll文件就又能玩注册了。
  3、论坛上好多网友说lmze2000大牛分享的注册机不能用了,这里分享一个我写的最新版的注册机,让大家玩玩看看。
  好了收工。




zhj777 发表于 2024-8-19 23:20

爱飞的猫 发表于 2024-8-19 21:05
壳方面我不是很懂,只会大概的理论。建议发求助帖。

已经研究出来了,谢谢您的回复。
现在有一ILProtector加壳的程序,感觉是非常新的壳,不知道wwh1004大牛的思路还适不适用,现在感觉可能适用。char __fastcall sub_1002BBA0(int a1, int a2, int a3, int *a4, int a5, char a6, LONG a7, LONG **a8, int a9, int a10)
{
int v10; // edi@1
int v11; // edi@6
SAFEARRAY *v12; // edi@10
int v13; // eax@10
HRESULT v14; // edi@12
int v15; // edi@13
int v16; // eax@13
int v17; // eax@17
int v18; // edx@18
char v19; // al@21
int *v20; // edx@30
int v21; // edi@32
int v22; // edi@32
int *v23; // ecx@33
int v24; // edx@35
LONG *v25; // eax@37
int v26; // edi@40
char v27; // al@47
int v28; // esi@51
SAFEARRAY *v29; // eax@51
void (__stdcall *v30)(VARIANTARG *); // edi@51
int v31; // eax@52
int v32; // esi@57
SAFEARRAY *v33; // eax@57
int v34; // edx@57
int v35; // edi@58
int (__stdcall *v36)(LONG, _DWORD); // esi@58
int v37; // eax@58
LONG **v38; // ecx@58
int v39; // eax@58
int v40; // eax@62
int v41; // ecx@64
int v42; // edx@64
_DWORD *v43; // eax@64
int v44; // esi@65
int v45; // eax@67
char v47; // @21
int v48; // @21
int v49; // @21
int v50; // @21
int v51; // @21
int v52; // @21
int v53; // @21
VARIANTARG *v54; // @21
VARIANTARG v55; // @1
VARIANTARG v56; // @51
int v57; // @19
SAFEARRAY *psa; // @10
int v59; // @1
int v60; // @1
int v61; // @1
int v62; // @1
int v63; // @1
LONG **v64; // @1
VARIANTARG pvarg; // @1
int v66; // @1
int (__stdcall *v67)(int, int, int, VARIANTARG *); // @1
LONG v68; // @1
int pv; // @10
int v70; // @32
SAFEARRAY *v71; // @10
VARIANTARG *v72; // @30
int *v73; // @21
int v74; // @1
int v75; // @1
LONG rgIndices; // @1
int v77; // @1
int v78; // @1
char v79; // @1
int v80; // @1
int v81; // @21
int *v82; // @21
VARIANTARG *v83; // @21
VARIANTARG *v84; // @21
VARIANTARG v85; // @21
int v86; // @1

v80 = a3;
v63 = a1;
v66 = a2;
v64 = a8;
v74 = a9;
v75 = a10;
v79 = 0;
rgIndices = 0;
v77 = 0;
v86 = 0;
v78 = 0;
v62 = 0;
v60 = 0;
v67 = 0;
v61 = 0;
v59 = 0;
v68 = 0;
VariantInit(&pvarg);
VariantInit(&v55);
LOBYTE(v86) = 3;
v10 = *(_DWORD *)v63;
if ( !*(_DWORD *)v63 )
    sub_1007C650(-2147467261);
if ( v77 )
    (*(void (__stdcall **)(int))(*(_DWORD *)v77 + 8))(v77);
v77 = 0;
if ( (*(int (__stdcall **)(int, int *))(*(_DWORD *)v10 + 72))(v10, &v77) >= 0 )
{
    v11 = *(_DWORD *)v66;
    if ( !*(_DWORD *)v66 )
      goto LABEL_80;
    if ( v78 )
      (*(void (__stdcall **)(int))(*(_DWORD *)v78 + 8))(v78);
    v78 = 0;
    if ( (*(int (__stdcall **)(int, int *))(*(_DWORD *)v11 + 72))(v11, &v78) >= 0 )
    {
LABEL_80:
      v12 = SafeArrayCreateVector(0xCu, 0, 1u);
      v13 = *a4;
      psa = v12;
      rgIndices = 0;
      LOWORD(pv) = 9;
      v71 = (SAFEARRAY *)v13;
      if ( v13 )
      (*(void (__stdcall **)(int))(*(_DWORD *)v13 + 4))(v13);
      v14 = SafeArrayPutElement(v12, &rgIndices, &pv);
      VariantClear((VARIANTARG *)&pv);
      if ( v14 < 0 )
      goto LABEL_71;
      v15 = v80;
      v16 = *(_DWORD *)(*(_DWORD *)(v80 + 56) + 156);
      if ( !v16 )
      sub_1007C650(-2147467261);
      if ( (*(int (__stdcall **)(int, SAFEARRAY *, VARIANTARG *))(*(_DWORD *)v16 + 156))(v16, psa, &pvarg) < 0 )
      goto LABEL_71;
      if ( (pvarg.vt & 0xFFF) != 13 )
      goto LABEL_71;
      v17 = *(_DWORD *)(a7 + 100);
      if ( !(v17 & 1) )
      goto LABEL_71;
      v18 = *(_DWORD *)(v15 + 56);
      if ( *(_BYTE *)(v18 + 8) )
      {
      v57 = a5;
      if ( !(a6 & 2) || v17 & 2 )
      {
          v82 = &v57;
          v83 = &pvarg;
          v84 = &v55;
          *(_DWORD *)&v85.vt = &v77;
          *(_QWORD *)&v85.decVal.Hi32 = __PAIR__(&v78, v74);
          v81 = v15;
          v85.cyVal.Hi = v75;
          qmemcpy(&v47, &v81, 0x20u);
          sub_1002E030(&v83, v47, v48, v49, v50, v51, v52, v53, v54);
          LOBYTE(v86) = 4;
          v73 = &v49;
          sub_1001B600();
          v19 = sub_1002D040(v49, v50, v51, v52, v53, v54);
          LOBYTE(v86) = 3;
          if ( !v19 )
          {
            sub_10010D40();
            goto LABEL_71;
          }
          sub_10010D40();
          v15 = v80;
LABEL_30:
          LOWORD(pv) = 20;
          v71 = (SAFEARRAY *)(a7 ^ 0x4B4F4F4C);
          v72 = (VARIANTARG *)(((unsigned __int64)a7 >> 32) ^ 0x45524548);
          LOBYTE(v86) = 5;
          v20 = *(int **)(*(_DWORD *)(v15 + 56) + 148);
          if ( !v20 )
            sub_1007C650(-2147467261);
          v21 = *v20;
          v51 = pv;
          v52 = v70;
          v53 = (int)v71;
          v54 = v72;
          *(_DWORD *)&v47 = *(_DWORD *)&pvarg.vt;
          *(_QWORD *)&v48 = *(_QWORD *)&pvarg.decVal.Hi32;
          v22 = (*(int (__stdcall **)(int *, _DWORD, ULONG, LONG, __int32, int, int, SAFEARRAY *, VARIANTARG *))(v21 + 100))(
                  v20,
                  *(_DWORD *)&pvarg.vt,
                  pvarg.decVal.Hi32,
                  pvarg.lVal,
                  pvarg.cyVal.Hi,
                  pv,
                  v70,
                  v71,
                  v72);
          LOBYTE(v86) = 3;
          VariantClear((VARIANTARG *)&pv);
          if ( v22 >= 0 )
          {
            v23 = *(int **)(*(_DWORD *)(v80 + 56) + 152);
            if ( !v23 )
            sub_1007C650(-2147467261);
            v24 = *v23;
            *(VARIANTARG *)&v51 = v55;
            *(VARIANTARG *)&v47 = pvarg;
            if ( (*(int (__stdcall **)(int *, _DWORD, ULONG, LONG, __int32, _DWORD, ULONG, LONG, __int32))(v24 + 100))(
                   v23,
                   *(_DWORD *)&pvarg.vt,
                   pvarg.decVal.Hi32,
                   pvarg.lVal,
                   pvarg.cyVal.Hi,
                   *(_DWORD *)&v55.vt,
                   v55.decVal.Hi32,
                   v55.lVal,
                   v55.cyVal.Hi) >= 0
            && *(_BYTE *)(a7 + 100) & 1 )
            {
            v25 = (LONG *)operator new(0xCu);
            if ( v25 )
            {
                *v25 = pvarg.lVal;
                v25 = a7;
                v25 = 0;
            }
            else
            {
                v25 = 0;
            }
            v26 = v80;
            *v64 = v25;
            if ( (unsigned __int8)sub_1002C3F0(v25) )
            {
                if ( (!*(_DWORD *)v63 || (unsigned __int8)sub_10024720(v26, v63, (int)&v62))
                  && (!*(_DWORD *)v66 || (unsigned __int8)sub_10024720(v26, v66, (int)&v60))
                  && (unsigned __int8)sub_10024900(v26, *(_DWORD *)(v26 + 56) + 28, *(_DWORD *)(v26 + 52) + 192, &v67)
                  && (unsigned __int8)sub_1002D510(v26, *(_DWORD *)(v26 + 56) + 32, (int)&v61) )
                {
                  v83 = (VARIANTARG *)off_1009091C;
                  v84 = (VARIANTARG *)v26;
                  *(_DWORD *)&v85.vt = &v59;
                  v85.lVal = (LONG)&v83;
                  LOBYTE(v86) = 6;
                  v73 = &v49;
                  sub_1001B600();
                  v27 = sub_100240C0(v49, v50, v51, v52, v53, v54);
                  LOBYTE(v86) = 3;
                  if ( v27 )
                  {
                  sub_10010D40();
                  if ( *(_DWORD *)v63 && (*(_WORD *)v74 & 0xFFF) == 13 )
                  {
                      v73 = &v51;
                      v68 = -1;
                      sub_1001C8A0(v74);
                      v28 = v67(v51, v52, v53, v54);
                      VariantInit((VARIANTARG *)&pv);
                      VariantInit(&v85);
                      LOBYTE(v86) = 8;
                      LOWORD(pv) = 8204;
                      v29 = SafeArrayCreateVector(0xCu, 0, 1u);
                      v56.vt = 22;
                      v71 = v29;
                      rgIndices = 0;
                      v56.lVal = -1;
                      SafeArrayPutElement(v29, &rgIndices, &v56);
                      v30 = (void (__stdcall *)(VARIANTARG *))VariantClear;
                      VariantClear(&v56);
                      sub_1002DF80(v80, &pv, (int)&v85);
                      *(_DWORD *)(a7 + 56) = *(_DWORD *)v28;
                      *(_DWORD *)(a7 + 60) = *(_DWORD *)(v28 + 4);
                      if ( (*(_BYTE *)v28 ^ 0x80) == 105 )
                      {
                        v31 = *(_DWORD *)(v28 + 1) + ((*(_BYTE *)v28 ^ 0xFC) & 0xF);
                        *(_DWORD *)(a7 + 92) = *(_DWORD *)(v31 + v28);
                        *(_DWORD *)(a7 + 96) = *(_DWORD *)(v31 + v28 + 4);
                      }
                      VariantClear(&v85);
                      LOBYTE(v86) = 3;
                      VariantClear((VARIANTARG *)&pv);
                  }
                  else
                  {
                      v30 = (void (__stdcall *)(VARIANTARG *))VariantClear;
                  }
                  if ( *(_DWORD *)v66 && (*(_WORD *)v75 & 0xFFF) == 13 )
                  {
                      --v68;
                      v73 = &v51;
                      sub_1001C8A0(v75);
                      v32 = v67(v51, v52, v53, v54);
                      VariantInit((VARIANTARG *)&pv);
                      VariantInit(&v85);
                      LOBYTE(v86) = 10;
                      LOWORD(pv) = 8204;
                      v33 = SafeArrayCreateVector(0xCu, 0, 1u);
                      v56.vt = 22;
                      v56.lVal = v68;
                      v71 = v33;
                      rgIndices = 0;
                      SafeArrayPutElement(v33, &rgIndices, &v56);
                      v30(&v56);
                      sub_1002DF80(v80, &pv, (int)&v85);
                      *(_DWORD *)(a7 + 64) = *(_DWORD *)v32;
                      v34 = *(_DWORD *)(v32 + 4);
                      v54 = &v85;
                      *(_DWORD *)(a7 + 68) = v34;
                      v30(v54);
                      LOBYTE(v86) = 3;
                      v30((VARIANTARG *)&pv);
                  }
                  v35 = v80;
                  v36 = *(int (__stdcall **)(LONG, _DWORD))(*(_DWORD *)(v80 + 56) + 316);
                  v37 = v36(**v64, *(_DWORD *)(*(_DWORD *)(v80 + 52) + 316));
                  *(_DWORD *)(a7 + 84) = *(_DWORD *)v37;
                  v38 = v64;
                  *(_DWORD *)(a7 + 88) = *(_DWORD *)(v37 + 4);
                  v39 = v36(**v38, *(_DWORD *)(*(_DWORD *)(v35 + 52) + 240));
                  *(_DWORD *)(a7 + 80) = v39;
                  if ( (*(_BYTE *)v39 ^ 0x11) == 68
                      && (*(_BYTE *)(v39 + 1) ^ 0x72) == -7
                      && (*(_BYTE *)(v39 + 2) ^ 0x61) == -115 )
                  {
                      v54 = (VARIANTARG *)16;
                      *(_DWORD *)(a7 + 36) = v67;
                      v73 = (int *)operator new((size_t)v54);
                      LOBYTE(v86) = 11;
                      if ( v73 )
                        v40 = sub_1001C8A0(v74);
                      else
                        v40 = 0;
                      LOBYTE(v86) = 3;
                      v41 = v61;
                      v42 = v59;
                      *(_DWORD *)(a7 + 48) = v40;
                      *(_DWORD *)(a7 + 40) = v62;
                      v43 = (_DWORD *)v66;
                      *(_DWORD *)(a7 + 72) = v41;
                      *(_DWORD *)(a7 + 76) = v42;
                      if ( *v43 )
                      {
                        v44 = v75;
                        if ( (*(_WORD *)v75 & 0xFFF) == 13 )
                        {
                        v73 = (int *)operator new(0x10u);
                        LOBYTE(v86) = 12;
                        if ( v73 )
                            v45 = sub_1001C8A0(v44);
                        else
                            v45 = 0;
                        *(_DWORD *)(a7 + 52) = v45;
                        *(_DWORD *)(a7 + 44) = v60;
                        }
                      }
                      v79 = 1;
                  }
                  }
                  else
                  {
                  sub_10010D40();
                  }
                }
            }
            }
          }
          goto LABEL_71;
      }
      }
      else if ( (!(a6 & 2) || v17 & 2)
             && (unsigned __int8)sub_10026AE0(v15, *(_DWORD *)(v15 + 56) + 12, &pvarg, v18 + 196, (int)&v55)
             && (unsigned __int8)sub_10026AE0(v15, (int)&v77, &pvarg, *(_DWORD *)(v15 + 56) + 200, v74)
             && (!v78 || (unsigned __int8)sub_10026AE0(v15, (int)&v78, &pvarg, *(_DWORD *)(v15 + 56) + 208, v75)) )
      {
      goto LABEL_30;
      }
LABEL_71:
      if ( psa )
      SafeArrayDestroy(psa);
      goto LABEL_73;
    }
}
LABEL_73:
VariantClear(&v55);
VariantClear(&pvarg);
LOBYTE(v86) = 0;
if ( v78 )
    (*(void (__stdcall **)(int))(*(_DWORD *)v78 + 8))(v78);
v86 = -1;
if ( v77 )
    (*(void (__stdcall **)(int))(*(_DWORD *)v77 + 8))(v77);
return v79;已经把检测hook的伪代码复制出来了,只是现在还读不懂,不知如何绕过检测。不知版主能否给代码加些注释并提示一下绕过检测的方法?

wwb66668 发表于 2024-8-18 23:11

本帖最后由 wwb66668 于 2024-8-18 23:19 编辑

爱飞的猫 发表于 2024-8-17 03:40
注册机加壳就没意思了呀…

另外文章漏掉了从序列号文字到过期时间的映射,这部分代码在 `cmdOK_Cli ...
请版主帮忙看一下,我用python写注册机最后一步的结果哪里不对?
def func_get_number_20100601(tstr):
    if len(tstr) < 30:
      print("本程序在您的电脑上无法注册,请与南方联系。")
      return ""

    # 构造text字符串
    text = tstr + tstr + tstr + tstr + tstr + tstr
    print(f"从MD5值中取的12位数字:{text}")//到这一步得到的值是对的F998BAE207E0
    # 初始化result字符串
    result = ""

    # 遍历text中的每个字符
    for num, char in enumerate(text, 1):
      if char.isdigit():
               
            # 字符转换为整数,进行异或运算,再转回字符
            result += chr(ord(char) ^ 2)
                  

      else:
            # 字符转换为ASCII码值,进行运算,再转回字符
            new_char = chr(ord(char) + 20 - num)
            # 如果结果是大写'O',则替换为'0'
            if new_char.upper() == 'O':
                result += '0'
            else:
                result += new_char
            print(f"数字:{result}")//这里得到的结果Y;;:Q0R025N就不对了,应该是Y111110Q0R025N2
    # 替换所有的'0'为'2'
    result = result.replace('0', '2')
    # 从第3位、7位、5位、1位(Python索引从0开始,所以需要调整)各取2位得到8位数字
    # 注意:Python中的索引是从0开始的,所以我们需要相应地调整索引
    final_result = result + result + result + result

    return final_result
    print(f"数字:{final_result}")
# 使用示例
tstr = "B07AE0E6BAE27F98ACBEB7E4B92B1B09"
result = func_get_number_20100601(tstr)
print("Processed Result:", result)

这个出来的结果是;:R2Q2Y;      实际结果是112Q11Y1
感觉是# 字符转换为整数,进行异或运算,再转回字符
            result += chr(ord(char) ^ 2)这步的结果有问题。请帮忙解答一下,谢谢。

zhj777 发表于 2024-8-16 14:58

不好意思,附件中不知道为什么多了两个图片,在编辑-------附件中也没看到有,版主看到如果能给删掉请帮忙删掉。

msmvc 发表于 2024-8-16 19:57

zhj777 发表于 2024-8-16 14:58
不好意思,附件中不知道为什么多了两个图片,在编辑-------附件中也没看到有,版主看到如果能给删掉请帮忙 ...

我上次也是多出来两个图片,

zhj777 发表于 2024-8-16 19:59

msmvc 发表于 2024-8-16 19:57
我上次也是多出来两个图片,

开始编辑的时候用的是火狐,结果发布后有点乱,就用360又编辑了一遍,就多了两张图。

msmvc 发表于 2024-8-16 20:10

zhj777 发表于 2024-8-16 19:59
开始编辑的时候用的是火狐,结果发布后有点乱,就用360又编辑了一遍,就多了两张图。

我一直用的是火狐

justwz 发表于 2024-8-16 21:48

跟着大佬学逆向

BY丶显示 发表于 2024-8-16 22:27

教程非常详细,感谢分享

W921027 发表于 2024-8-17 02:34

感谢分享

52pojieplayer 发表于 2024-8-17 02:41

谢谢分享!

爱飞的猫 发表于 2024-8-17 03:40

注册机加壳就没意思了呀…

另外文章漏掉了从序列号文字到过期时间的映射,这部分代码在 `cmdOK_Click` 中。

```py
    digit_y1 = {'0': '045Aa', '1': '167Bb', '2': '289Cc', '3': '3'}
    digit_y2 = {'0': '0Aa', '1': '1Bb', '2': '2Cc', '3': '3Dd', '4': '4Ee',
                '5': '5Ff', '6': '6Gg', '7': '7Hh', '8': '8Jj', '9': '9Kk'}
    digit_m1 = dict(digit_y1)
    digit_m2 = dict(digit_y2)
    digit_d1 = dict(digit_y1)
    # d2 不做处理
```

序列号格式:

```py
    # offset: 123456789A12345
    # serial: AAAAyymmAAAAddx
```

其中:

- `AAAAAAAA` 为机器码进行一番变化后得到的 8 个字符(过程大致为 `getFalseMd5` → `md5` → `funcGetNumber_20100601`);
- `yy` 是年份的后两位,最大值为 `36`,即 `2036` 年。
- `mm` 是月(不足 `2` 位向前补 `0`)
- `dd` 是日(不足 `2` 位向前补 `0`)
- `x` 可以是任意字符或不存在(为 `1` 或 `2` 时会在注册前删除 `syspbaz735.dll`)。

页: [1] 2 3 4
查看完整版本: .net程序破解实践与理解二、代码分析篇1:启动验证(写给新手,超详细)