010 Editor 8.0.1 之 逆向分析及注册机编写

前言

  1. 010 Editor 是很好的逆向学习软件,通过编写注册机对提高逆向能力有很大帮助,
  2. 由于本文写的比较早,发布时已经出9.0版本,特添加9.0版本破解补丁。

一、工具及软件介绍

  • 逆向工具:x64 dbg
  • 平台:Win_1803 x64 虚拟机
  • 破解软件:010 Editor v8.0.1 x64

010 Editor 8.0.1 之 逆向分析及注册机编写

二、逆向分析

2.1、找到提示错误注册弹窗

  接上一篇,通过对CreateWindowExA、CreateWindowExW下断点,并且利用堆栈调用找到关键代码处

010 Editor 8.0.1 之 逆向分析及注册机编写

  找到弹窗中的提示字符串,选中这一行,x64dbg会自动分析出该处是从何处跳转过来的,跟踪并分析

2.2、分析跳转处代码

010 Editor 8.0.1 之 逆向分析及注册机编写

  到达跳转处代码,我们会发现有多组比较跳转,其中一个就是弹出无效的用户名和注册码信息处的跳转。

  通过分析推测,这几组比较是判断错误类型的。

  此时,我们选中最上方的比较处,会发现该处比较是从哪里跳转过来的,继续跟踪分析。

010 Editor 8.0.1 之 逆向分析及注册机编写

  来到这里我们发现ebxDB比较,结合分析得出,如果不相等就进入错误判断分支,相等则弹出注册成功弹窗。

  我们选中比较处代码,会发现跳转来源,继续跟踪分析。

010 Editor 8.0.1 之 逆向分析及注册机编写

  通过分析该处代码可知,E7来源于第一个CALL中,第二个CALL的返回值是用于判断注册码是否正确的,即返回结果为DB,接着往下看,当esi等于E7,即第一个CALL的返回结果为E7或者rcx + 3c等于0时都会跳转到下边注册码正确判断的流程中去。

  此时,我们得出的结论是,当第二个CALL的返回值是DB时,注册就会成功,这里我们将第二个CALL函数打上标签为=DB,方便后续分析。

010 Editor 8.0.1 之 逆向分析及注册机编写

  我们进入=DB这个函数分析一下

010 Editor 8.0.1 之 逆向分析及注册机编写

  我们已经知道,要使注册成功,这个函数就必须返回DB,通个观察这个函数,当7FF715213D46这个函数的返回值为2D时会将这个函数的返回值赋值为DB,同时,我们发现这个函数还会返回E7这个值,这时我们想到在分析=DB这个函数之前也有一个函数可能会返回为E7,我们对比一下这2个函数看看。

010 Editor 8.0.1 之 逆向分析及注册机编写

  我们发现这2个是同一函数,此时根据前后关系分析,当该函数的返回值为E7时会跳转到注册码正确验证处,但这个函数的返回值是E7时,=DB这个函数的返回值却不是DB

  通过这2个条件,我们得出这个函数的返回值在等于2D时,注册会成功。

  我们将该函数打上=2D标签,方便分析,同时更改之前的分析注释。

010 Editor 8.0.1 之 逆向分析及注册机编写

2.3、=2D 函数分析

  通过上方分析已知,该函数需要等于2D,才会注册成功。

  此时,我们先找到返回值为2D操作的代码。

010 Editor 8.0.1 之 逆向分析及注册机编写

  找到2D赋值处后,我们就需要跟踪调试这个函数了。

  通过单步跟踪发现00007FF71704597E之前是获取注册码和一些检测工作

2.3.1、获取注册码处分析

  • 使用的测试注册码为:0011-2233-4455-6677-8899

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 注册码与局部变量的对应关系
    • KEY[0] = 00 KEY[1] = 11 KEY[2] = 22 KEY[3] = 33 KEY[4] = 44
    • KEY[5] = 55 KEY[6] = 66 KEY[7] = 77 KEY[8] = 88 KEY[9] = 99
  • 内存中的存储状态【RSP + 28】

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 通过00007FF71704597E及之后的汇编代码确定数据的存储位置
    • KEY[0] = [RSP + 28]
    • KEY[1] = [RSP + 29]
    • KEY[2] = [RSP + 2A]
    • KEY[3] = [RSP + 2B]
    • KEY[4] = [RSP + 2C]
    • KEY[5] = [RSP + 2D]
    • KEY[6] = [RSP + 2E]
    • KEY[7] = [RSP + 2F]
    • KEY[8] = [RSP + 30]
    • KEY[9] = [RSP + 31]
  • 00007FF71704597E之后的代码分析

010 Editor 8.0.1 之 逆向分析及注册机编写

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 通过分析发现,当取出KEY[3]的值后,有3处比较跳转,分别为9C/FC/AC,对应的C代码如下
switch ( KEY[] )
{
case 0x9C:
break;
case 0xFC:
break;
case 0xAC:
break;
}
  • 接下来就可以单独分析每一个分支了

2.3.2、3处分支分析

2.3.2.1、9C情况

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 得到的结果为
    • EBX = ((( KEY[0] ^ KEY[6] ) ^ 0x18 ) + 0x3D ) ^ 0xA7
    • EAX = (((((( KEY[1] ^ KEY[7] ) * 0x100) + ( KEY[2] ^ KEY[5] )) ^ 0x7892 ) + 0x4d30 ) ^ 0x3421) / 0xB 或 0
  • 对应的C代码
case 0x9C:
DWORD EAX = ( ( ( ( ( ( KEY[ ] ^ KEY[ ] ) * 0x100 ) +
( KEY[ ] ^ KEY[ ] ) ) ^ 0x7892 ) + 0x4d30 ) ^ 0x3421 );
DWORD EBX = ( ( ( KEY[ ] ^ KEY[ ] ) ^ 0x18 ) + 0x3D ) ^ 0xA7;
if (EAX % 0xB != )
{
EAX = ; // error
}
if (EAX == || EAX > 0x3E8)
{
return; // error
}
if (EBX == )
{
return; // error
}
// 如果EBX >= 0x2 EBX = 0 否则不变
// EBX 做为下面用户名处理函数的参数
if ( EBX >= 0x2 )
{
EBX = ;
}
// EAX 做为下面用户名处理函数的参数
EAX = EAX / 0xB;
break;

2.3.2.2、FC情况

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 得到的结果为
    • EBX = FF
    • EAX = 0x1
  • 由于后面的判断,如果是0xFC,是不会返回0x2D的,这里略

2.3.2.3、AC情况

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 得到的结果为
    • EBX = EAX
    • DWORD PTR DS:[RDI+0x30] = (((((( KEY[1] ^ KEY[7] ) * 0x100) + ( KEY[2] ^ KEY[5] )) ^ 0x7892 ) + 0x4d30 ) ^ 0x3421) / 0xB 或 0
  • 对应的C代码
case 0xAC:
DWORD EAX = ( ( ( ( ( ( KEY[ ] ^ KEY[ ] ) * 0x100 ) +
( KEY[ ] ^ KEY[ ] ) ) ^ 0x7892 ) + 0x4d30 ) ^ 0x3421 );
if ( EAX % 0xB != )
{
EAX = ; // error
}
if ( EAX == || EAX > 0x3E8 )
{
return; // error
}
DWORD ECX = ( ( ( ( ( ( ( KEY[ ] ^ KEY[ ] ) << 0x8 ) + ( ( KEY[ ] ^ KEY[ ] ) << 0x10 ) + ( KEY[ ] ^ KEY[ ] ) ) ^ 0x5B8C27 ) ^ 0x22C078 ) - 0x2C175 ) ^ 0xFFE53167 ) & 0xFFFFFF; EAX = 0xF0F0F0F1;
DWORD EDX = ;
// 伪代码:EDX : EAX = EAX * ECX
EDX = EDX >> 0x4;
EAX = EDX * 0x11;
ECX = ECX - EAX;
if (ECX == ) // ECX = EAX
{
EAX = EDX; // Right
}
else
{
EAX = ;
}
DWORD RDI0x30 = EAX / 0xB;
EBX = EAX;
break;

2.3.3、用户名处理函数

2.3.3.1、函数调用处分析

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 上面已经准备好了 EBXRDI + 30的值
  • 结合IDA分析得出,用户名处理函数的参数如下:
    • 参数一:用户名字符串首地址
    • 参数二:如果类型是0xFC就是0,否则是1,用于函数内部判断类型
    • 参数三:准备好的EBX
    • 参数四:准备好的RDI + 30

2.3.3.2、函数内部分析

010 Editor 8.0.1 之 逆向分析及注册机编写

010 Editor 8.0.1 之 逆向分析及注册机编写

void function( char* name )
{
DWORD KEY[ ] = { 0x9C };
DWORD EBX = ;
DWORD RDI30 = ;
DWORD LOCAL1 = ;
DWORD LOCAL2 = ( EBX/*参数3*/ * 0x11 ) & 0xFF;
DWORD LOCAL3 = ;
DWORD LOCAL4 = ;
DWORD ESI = RDI30/*参数4*/ * 0xF;
int NameLen = strlen( name );
char EAX[ ] = { }; // 函数返回值
DWORD PW[ ] = {
0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821,
…………
0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945 };
for ( int i = ; i < NameLen; i++ )
{
char nameChar = toupper( name[ i ] );
if ( KEY[ ] != 0xFC )
{
LOCAL1 = ( PW[ nameChar ] ^ PW[ nameChar + 0xD ] ) * PW[ nameChar + 0x2F ]
+ PW[ LOCAL2 ]
+ PW[ ESI ]
+ PW[ LOCAL3 ];
}
else
{
LOCAL1 = ( PW[ nameChar ] ^ PW[ nameChar + 0x3F ] ) + PW[ nameChar + 0x17 ]
+ PW[ LOCAL2 ]
+ PW[ ESI ]
+ PW[ LOCAL4 ];
}
LOCAL3 += 0x13;
LOCAL2 += 0x9;
ESI += 0xD;
LOCAL4 += 0x7;
}
*( DWORD* ) EAX = LOCAL1;
}

2.3.4、用户名处理函数返回值及剩余代码分析

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 通过分析发现,当KEY[3] = 0xFC时,不会跳转到正确的返回值赋值处
  • 所以,,当KEY[3] = 0xFC时,之后的代码都不用分析
  • 对应C代码如下
void function1()
{
DWORD KEY[ ] = { };
DWORD EAX[ ] = { };
if ( KEY[ ] == EAX[ ] &&
KEY[ ] == EAX[ ] &&
KEY[ ] == EAX[ ] &&
KEY[ ] == EAX[ ] )
{
switch ( KEY[] )
{
case 0x9C:
/*
正确
*/
break;
case 0xFC:
/*
错误
*/
break;
case 0xAC:
/*
正确
*/
break;
}
}
}

三、注册机编写

  • 本次使用MFC编写注册机
  • 全局数组密码表(内存中提取)
DWORD PW[  ] = {
0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821,
0x5B5F670F, 0x48315AE8, 0x785B7769, 0x2B7A1547, 0x38D11292, 0x42A11B32, 0x35332244, 0x77437B60,
0x1EAB3B10, 0x53810000, 0x1D0212AE, 0x6F0377A8, 0x43C03092, 0x2D3C0A8E, 0x62950CBF, 0x30F06FFA,
0x34F710E0, 0x28F417FB, 0x350D2F95, 0x5A361D5A, 0x15CC060B, 0x0AFD13CC, 0x28603BCF, 0x3371066B,
0x30CD14E4, 0x175D3A67, 0x6DD66A13, 0x2D3409F9, 0x581E7B82, 0x76526B99, 0x5C8D5188, 0x2C857971,
0x15F51FC0, 0x68CC0D11, 0x49F55E5C, 0x275E4364, 0x2D1E0DBC, 0x4CEE7CE3, 0x32555840, 0x112E2E08,
0x6978065A, 0x72921406, 0x314578E7, 0x175621B7, 0x40771DBF, 0x3FC238D6, 0x4A31128A, 0x2DAD036E,
0x41A069D6, 0x25400192, 0x00DD4667, 0x6AFC1F4F, 0x571040CE, 0x62FE66DF, 0x41DB4B3E, 0x3582231F,
0x55F6079A, 0x1CA70644, 0x1B1643D2, 0x3F7228C9, 0x5F141070, 0x3E1474AB, 0x444B256E, 0x537050D9,
0x0F42094B, 0x2FD820E6, 0x778B2E5E, 0x71176D02, 0x7FEA7A69, 0x5BB54628, 0x19BA6C71, 0x39763A99,
0x178D54CD, 0x01246E88, 0x3313537E, 0x2B8E2D17, 0x2A3D10BE, 0x59D10582, 0x37A163DB, 0x30D6489A,
0x6A215C46, 0x0E1C7A76, 0x1FC760E7, 0x79B80C65, 0x27F459B4, 0x799A7326, 0x50BA1782, 0x2A116D5C,
0x63866E1B, 0x3F920E3C, 0x55023490, 0x55B56089, 0x2C391FD1, 0x2F8035C2, 0x64FD2B7A, 0x4CE8759A,
0x518504F0, 0x799501A8, 0x3F5B2CAD, 0x38E60160, 0x637641D8, 0x33352A42, 0x51A22C19, 0x085C5851,
0x032917AB, 0x2B770AC7, 0x30AC77B3, 0x2BEC1907, 0x035202D0, 0x0FA933D3, 0x61255DF3, 0x22AD06BF,
0x58B86971, 0x5FCA0DE5, 0x700D6456, 0x56A973DB, 0x5AB759FD, 0x330E0BE2, 0x5B3C0DDD, 0x495D3C60,
0x53BD59A6, 0x4C5E6D91, 0x49D9318D, 0x103D5079, 0x61CE42E3, 0x7ED5121D, 0x14E160ED, 0x212D4EF2,
0x270133F0, 0x62435A96, 0x1FA75E8B, 0x6F092FBE, 0x4A000D49, 0x57AE1C70, 0x004E2477, 0x561E7E72,
0x468C0033, 0x5DCC2402, 0x78507AC6, 0x58AF24C7, 0x0DF62D34, 0x358A4708, 0x3CFB1E11, 0x2B71451C,
0x77A75295, 0x56890721, 0x0FEF75F3, 0x120F24F1, 0x01990AE7, 0x339C4452, 0x27A15B8E, 0x0BA7276D,
0x60DC1B7B, 0x4F4B7F82, 0x67DB7007, 0x4F4A57D9, 0x621252E8, 0x20532CFC, 0x6A390306, 0x18800423,
0x19F3778A, 0x462316F0, 0x56AE0937, 0x43C2675C, 0x65CA45FD, 0x0D604FF2, 0x0BFD22CB, 0x3AFE643B,
0x3BF67FA6, 0x44623579, 0x184031F8, 0x32174F97, 0x4C6A092A, 0x5FB50261, 0x01650174, 0x33634AF1,
0x712D18F4, 0x6E997169, 0x5DAB7AFE, 0x7C2B2EE8, 0x6EDB75B4, 0x5F836FB6, 0x3C2A6DD6, 0x292D05C2,
0x052244DB, 0x149A5F4F, 0x5D486540, 0x331D15EA, 0x4F456920, 0x483A699F, 0x3B450F05, 0x3B207C6C,
0x749D70FE, 0x417461F6, 0x62B031F1, 0x2750577B, 0x29131533, 0x588C3808, 0x1AEF3456, 0x0F3C00EC,
0x7DA74742, 0x4B797A6C, 0x5EBB3287, 0x786558B8, 0x00ED4FF2, 0x6269691E, 0x24A2255F, 0x62C11F7E,
0x2F8A7DCD, 0x643B17FE, 0x778318B8, 0x253B60FE, 0x34BB63A3, 0x5B03214F, 0x5F1571F4, 0x1A316E9F,
0x7ACF2704, 0x28896838, 0x18614677, 0x1BF569EB, 0x0BA85EC9, 0x6ACA6B46, 0x1E43422A, 0x514D5F0E,
0x413E018C, 0x307626E9, 0x01ED1DFA, 0x49F46F5A, 0x461B642B, 0x7D7007F2, 0x13652657, 0x6B160BC5,
0x65E04849, 0x1F526E1C, 0x5A0251B6, 0x2BD73F69, 0x2DBF7ACD, 0x51E63E80, 0x5CF2670F, 0x21CD0A03,
0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945
};
  • 获取随机数小于0x3E8的函数(获取注册人数)
DWORD GetRandNum() {
DWORD Result;
srand( ( unsigned ) time( NULL ) );
return Result = rand() % 0x3E8; // 用于获取随机区间,(0, 3E8],为注册人数 1 ~ 1000
}
  • 用户名处理函数
    • 参数1:用户名
    • 参数2:注册人数(随机函数获取)
    • 参数3:注册天数(当版本注册【0x9C】时 = 0,当时间注册时【0xAC】时=要注册的年月日 - 1970.1.1之间的天数)
void CKeyGenDlg::GetEncryStr( char * szName, DWORD Regmen, DWORD day )
{
DWORD ESI = Regmen * 0xF;
DWORD LOCAL1 = ;
DWORD LOCAL2 = ( day * 0x11 ) & 0xFF;
DWORD LOCAL3 = ;
//DWORD LOCAL4 = 0;
BYTE EAX[ ] = { };
int NameLen = strlen( szName );
for ( int i = ; i < NameLen; i++ )
{
char nameChar = toupper( szName[ i ] );
LOCAL1 =
( ( ( LOCAL1 + PW[ nameChar ] ) ^ PW[ ( nameChar + 0xD ) & 0xFF ] )*PW[ ( nameChar + 0x2F ) & 0xFF ] )
+ PW[ LOCAL2 ]
+ PW[ ESI & 0xFF ]
+ PW[ LOCAL3 ]; ESI += 0xD;
LOCAL2 += 0x9;
LOCAL3 += 0x13;
}
*( DWORD* ) EAX = LOCAL1;
KEY[ ] = EAX[ ];
KEY[ ] = EAX[ ];
KEY[ ] = EAX[ ];
KEY[ ] = EAX[ ];
}
  • 注册码计算函数
    • 参数1:用户名
    • 参数2:注册天数
  • 通过该函数可以根据用户名计算出注册码,同时可计算版本注册及天数注册(按选择不同区分)
void CKeyGenDlg::CalcReg( char * szName, DWORD day )
{
char destbuff[ ] = { };
DWORD Regmen = GetRandNum();
/*
判断是否整除函数的结果为:Regmen
对应公式为:Regmen = ((((( KEY[1] ^ KEY[7] ) * 0x100 + ( KEY[2] ^ KEY[5] )) ^ 0x7892 ) + 0x4D30 ) ^ 0x3421) / 0xB
逆向计算得出:( KEY[1] ^ KEY[7] ) * 0x100 + ( KEY[2] ^ KEY[5] ) 的值,WORD
*/
WORD tmp = ( ( ( Regmen * 0xB ) ^ 0x3421 ) - 0x4D30 ) ^ 0x7892; USES_CONVERSION;
if ( status )
{
//MessageBox( L"版本", 0, 0 );
KEY[ ] = 0x9C;
day = ; // 当为版本注册时,天数必须为0
GetEncryStr( szName, Regmen, day );
KEY[ ] = ( HIBYTE( tmp ) ) ^ KEY[ ];
KEY[ ] = ( LOBYTE( tmp ) ) ^ KEY[ ];
/*
版本号 9 < ((( KEY[0] ^ KEY[6] ) ^ 0x18 ) + 0x3D ) ^ 0xA7
这里取 0x18
*/
KEY[ ] = ( ( ( 0x18 ^ 0xA7 ) - 0x3D ) ^ 0x18 ) ^ KEY[ ];
sprintf_s( destbuff, "%02X%02X-%02X%02X-%02X%02X-%02X%02X", KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ] );
m_Reg = A2T( destbuff );
UpdateData( FALSE );
}
else
{
//MessageBox( L"天数", 0, 0 );
KEY[ ] = 0xAC;
GetEncryStr( szName, Regmen, day );
KEY[ ] = ( HIBYTE( tmp ) ) ^ KEY[ ];
KEY[ ] = ( LOBYTE( tmp ) ) ^ KEY[ ];
// 通过天数逆向推导得出计算之前的数值
/*
day = ((((((( KEY[9] ^ KEY[5] ) << 0x10 ) + (( KEY[4] ^ KEY[8] ) << 0x8 ) + KEY[0] ^ KEY[6])xor 005B8C27)
xor 0x22c078) - 0x2C175) xor 0xFFE53167) and 0xFFFFFF
*/
DWORD tmp2 = ( ( ( ( day * 0x11 ) ^ 0xFFE53167 ) + 0x2C175 ) ^ 0x22C078 ) ^ 0x5B8C27;
KEY[ ] = ( tmp2 & 0xFF ) ^ KEY[ ];
KEY[ ] = ( ( tmp2 >> 0x8 ) & 0xFF ) ^ KEY[ ];
KEY[ ] = ( ( tmp2 >> 0x10 ) & 0xFF ) ^ KEY[ ];
sprintf_s( destbuff, "%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ], KEY[ ] );
m_Reg = A2T( destbuff );
UpdateData( FALSE );
}
}
  • 按钮响应函数
  • 该函数对运算逻辑及数据进行处理,当用户名不为空时,计算注册码
void CKeyGenDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
// CDialogEx::OnOK();
memset( KEY, , sizeof( KEY ) ); // 清空数组
UpdateData( TRUE );
if ( !m_User.IsEmpty() )
{
USES_CONVERSION;
char * szName = T2A( m_User );
DWORD day = 0x7224; // 1970.1.1 - 指定日期的天数
CalcReg( szName, day );
}
else
{
MessageBox( _T( "用户名不能为空" ), , MB_ICONWARNING );
}
}

3.1、注册机外观

由于使用了单选控件,于是在选择授权方式时对注册码编辑框进行处理

010 Editor 8.0.1 之 逆向分析及注册机编写

3.2、结果验证

3.2.1、版本授权

010 Editor 8.0.1 之 逆向分析及注册机编写

010 Editor 8.0.1 之 逆向分析及注册机编写

3.2.2、天数授权

010 Editor 8.0.1 之 逆向分析及注册机编写

010 Editor 8.0.1 之 逆向分析及注册机编写

四、网络验证

4.1、两处判断网络验证是否通过处

  • 第一处

010 Editor 8.0.1 之 逆向分析及注册机编写

  • 第二处

010 Editor 8.0.1 之 逆向分析及注册机编写

总结:当RCX + 3C处的值为0时,走正常流程,否则,使用注册机注册的将会提示注册码无效。

010 Editor 8.0.1 之 逆向分析及注册机编写

继续观察第一处,在网络验证处下方有一个函数,我们进去浏览会发现网址拼接,网址拼接中会发现有namepwd字样,此时怀疑该函数可能就是修改RCX + 3C处的值的函数。

4.2、确认是否为网络验证函数

  • 可以通过新建虚拟机或修改RCX + 3C处的值为0,通过修改ZF标志位强制执行该函数,验证结果如下

010 Editor 8.0.1 之 逆向分析及注册机编写

4.3、突破网络验证

  • 方法一:将两处网络验证比较处的跳转进行修改,即JEJMP,使其强制执行网络验证通过后的代码
  • 方法二:进入网络验证函数在其函数头直接RET将函数返回,不对RCX + 3C进行修改

总结:突破网络验证的方法有很多,还需要多学习。

五、附件

010 Editor 8.0.1  提取码:d6ds

010 Editor 9.0     提取码:0lfe

注册机

六、源码

说明:移植MFC部分代码,使用Qt重写,并为时间注册添加自选时间功能

GitHub:KenGen

 
上一篇:操作系统(一)进程管理


下一篇:Tomcat 8.0的并发优化 - 优化server.xml的配置