[算法分析] C Program KenGenMe
本帖最后由 〇〇木一 于 2014-5-30 09:04 编辑【文章作者】: 〇〇木一
【软件名称】: C Program KenGenMe
【下载地址】: http://www.52pojie.cn/thread-261639-1-1.html
【使用工具】: IDA , OD , VC6
分析不出,难受。分析出来了,不发表一下思路也难受。 所以一天时间就没了。
找实习,让我感到未来一片渺茫。大学以来我感觉我都没认真干过什么事,上不上课看心情,总喜欢自己搞一套,到现在绩点也就刚刚毕毕业,还有重修科目在身。找实习也受了不少挫,最后找到了个windows驱动开发的实习岗位,总的来说还算和我胃口。现在就是天天泡图书馆,想想以后要买房子,养老婆...不勤奋不行了。
IDA的使用也是图书馆看的,为了熟悉,我就一直找东西来试试。这个KenGenMe是昨天Blackk大大发的,今天在图书馆试试,结果发现点思路就停不下来了。
下面是正文:
IDA分析KenGenMe的具体流程是:
1.获取输入key,判断key的长度是否为24,不是则失败。2.对得到的key进行第一步处理,并把处理结果储存在一个全局数组中。3.对第二步全局数组中的数据进行2次处理,获取数据进行比较验证。
下面是主函数部分:.text:00410240 ; Attributes: bp-based frame
.text:00410240
.text:00410240 _main_0 proc near ; CODE XREF: _mainj
.text:00410240
.text:00410240 anonymous_0 = byte ptr -7Ch
.text:00410240 var_70 = byte ptr -70h
.text:00410240 var_30 = dword ptr -30h
.text:00410240 var_2C = dword ptr -2Ch
.text:00410240 var_28 = dword ptr -28h
.text:00410240 var_24 = dword ptr -24h
.text:00410240 _key_len = dword ptr -20h
.text:00410240 char_table = dword ptr -1Ch
.text:00410240 _key_str = byte ptr -18h
.text:00410240
.text:00410240 push ebp
.text:00410241 mov ebp, esp
.text:00410243 sub esp, 70h
.text:00410246 push ebx
.text:00410247 push esi
.text:00410248 push edi
.text:00410249 lea edi,
.text:0041024C mov ecx, 1Ch
.text:00410251 mov eax, 0CCCCCCCCh
.text:00410256 rep stosd ; ↓获取字符表
.text:00410258 mov , offset a123456789abcde ; "123456789abcdefghijklmnpqrstuvwxyz"
.text:0041025F push offset asc_426120 ; "***************************************"...
.text:00410264 call _printf
.text:00410269 add esp, 4
.text:0041026C push offset asc_425A68 ; "\n\n"
.text:00410271 call _printf
.text:00410276 add esp, 4
.text:00410279 push offset aPleaseKeygenMe ; "******** Please KeyGen M"...
.text:0041027E call _printf
.text:00410283 add esp, 4
.text:00410286 push offset asc_425A68 ; "\n\n"
.text:0041028B call _printf
.text:00410290 add esp, 4
.text:00410293 push offset aHaveFun_byBlac ; "******** Have Fun.By Blackk"...
.text:00410298 call _printf
.text:0041029D add esp, 4
.text:004102A0 push offset asc_425A68 ; "\n\n"
.text:004102A5 call _printf
.text:004102AA add esp, 4
.text:004102AD push offset asc_426120 ; "***************************************"...
.text:004102B2 call _printf
.text:004102B7 add esp, 4
.text:004102BA push offset asc_42592C ; "\n"
.text:004102BF call _printf
.text:004102C4 add esp, 4
.text:004102C7 push offset aPleaseEnterPas ; "Please Enter Password:"
.text:004102CC call _printf
.text:004102D1 add esp, 4
.text:004102D4 lea eax,
.text:004102D7 push eax ; ↓获取key
.text:004102D8 push offset aS ; "%s"
.text:004102DD call _scanf
.text:004102E2 add esp, 8 ; ↓获取key串长度
.text:004102E5 lea ecx,
.text:004102E8 push ecx ; Str
.text:004102E9 call _strlen
.text:004102EE add esp, 4
.text:004102F1 mov , eax
.text:004102F4 cmp , 18h
.text:004102F8 jz loc_4103A6 ; 长度不为24就失败↓
.text:004102FE push offset aBadWork_please ; "Bad Work.Please try again!"
.text:00410303 call _printf
.text:00410308 add esp, 4
.text:0041030B mov edx, File._cnt
.text:00410311 sub edx, 1
.text:00410314 mov File._cnt, edx
.text:0041031A cmp File._cnt, 0
.text:00410321 jl short loc_410345
.text:00410323 mov eax, File._ptr
.text:00410328 movsx ecx, byte ptr
.text:0041032B and ecx, 0FFh
.text:00410331 mov , ecx
.text:00410334 mov edx, File._ptr
.text:0041033A add edx, 1
.text:0041033D mov File._ptr, edx
.text:00410343 jmp short loc_410355
.text:00410345 ; ---------------------------------------------------------------------------
.text:00410345
.
.
.
.text:0041038C ; ---------------------------------------------------------------------------
.text:0041038C
.text:0041038C loc_41038C: ; CODE XREF: _main_0+129j
.text:0041038C push offset File ; File
.text:00410391 call __filbuf
.text:00410396 add esp, 4
.text:00410399 mov , eax
.text:0041039C
.text:0041039C loc_41039C: ; CODE XREF: _main_0+14Aj
.text:0041039C push 0 ; Code
.text:0041039E call _exit
.text:004103A3 ; ---------------------------------------------------------------------------
.text:004103A3 add esp, 4
.text:004103A6
.text:004103A6 loc_4103A6: ; CODE XREF: _main_0+B8j
.text:004103A6 lea ecx,
.text:004103A9 push ecx ; key_str
.text:004103AA mov edx,
.text:004103AD push edx ; char_table
.text:004103AE mov eax,
.text:004103B1 push eax ; key_len
.text:004103B2 call _DealKey ; 首先处理key
.text:004103B7 add esp, 0Ch
.text:004103BA push offset _GlobalArray_0
.text:004103BF movsx ecx, _GlobalArray_1
.text:004103C6 push ecx
.text:004103C7 call _VerifyKey ; 再验证
.text:004103CC add esp, 8
.text:004103CF mov edx, File._cnt
.text:004103D5 sub edx, 1
.text:004103D8 mov File._cnt, edx
.text:004103DE cmp File._cnt, 0
.text:004103E5 jl short loc_410409
.text:004103E7 mov eax, File._ptr
.text:004103EC movsx ecx, byte ptr
.text:004103EF and ecx, 0FFh
.text:004103F5 mov , ecx
.text:004103F8 mov edx, File._ptr
.text:004103FE add edx, 1
.text:00410401 mov File._ptr, edx
.text:00410407 jmp short loc_410419
.text:00410409 ; ---------------------------------------------------------------------------
.text:00410409
.
.
.
.text:00410450 ; ---------------------------------------------------------------------------
.text:00410450
.text:00410450 loc_410450: ; CODE XREF: _main_0+1EDj
.text:00410450 push offset File ; File
.text:00410455 call __filbuf
.text:0041045A add esp, 4
.text:0041045D mov , eax
.text:00410460
.text:00410460 loc_410460: ; CODE XREF: _main_0+20Ej
.text:00410460 pop edi
.text:00410461 pop esi
.text:00410462 pop ebx
.text:00410463 add esp, 70h
.text:00410466 cmp ebp, esp
.text:00410468 call __chkesp
.text:0041046D mov esp, ebp
.text:0041046F pop ebp
.text:00410470 retn
.text:00410470 _main_0 endp
搞清楚各种变量并修改后,使用IDA的f5功能可以一目了然(几乎和源代码一样)
int __cdecl main_0()
{
int v0; // eax@13
char v2; // @0
char v3; // @1
int v4; // @13
int v5; // @10
int v6; // @6
int v7; // @3
size_t _key_len; // @1
char *char_table; // @1
char _key_str; // @1
char_table = "123456789abcdefghijklmnpqrstuvwxyz";// 字符表
printf("**************************************************************");
printf("\n\n");
printf("******** Please KeyGen Me. ********");
printf("\n\n");
printf("******** Have Fun.By Blackk ********");
printf("\n\n");
printf("**************************************************************");
printf("\n");
printf("Please Enter Password:");
scanf("%s", _key_str);
_key_len = strlen(_key_str);
if ( _key_len != 24 ) // 判断长度
{
printf("Bad Work.Please try again!");
...
exit(0);
}
DealKey(24, char_table, _key_str); // 处理函数(key长度,字符表,key)
VerifyKey(GlobalArray_1, GlobalArray_0); // 验证函数(全局变量1首字符,全局变量2)
...
return _chkesp(1, v0, v2);
}
这里有部分的代码省略了,都是些关于调试或者控制台有关的代码,无关紧要。
要搞清楚算法既验证原理就要逆行而上,先来看看验证部分代码
int __cdecl VerifyKey0(int gArr1Char0, int globalArray0)
.text:004017A0 _VerifyKey0 proc near ; CODE XREF: _VerifyKeyj
.text:004017A0
.text:004017A0 anonymous_0 = byte ptr -5Ch
.text:004017A0 var_50 = byte ptr -50h
.text:004017A0 Dst = byte ptr -0Ch
.text:004017A0 changeG1 = word ptr -8
.text:004017A0 var_5 = byte ptr -5
.text:004017A0 var_4 = byte ptr -4
.text:004017A0 gArr1Char0 = dword ptr8
.text:004017A0 globalArray0 = dword ptr0Ch
.text:004017A0
.text:004017A0 push ebp
.text:004017A1 mov ebp, esp
.text:004017A3 sub esp, 50h
.text:004017A6 push ebx
.text:004017A7 push esi
.text:004017A8 push edi
.text:004017A9 lea edi,
.text:004017AC mov ecx, 14h
.text:004017B1 mov eax, 0CCCCCCCCh
.text:004017B6 rep stosd
.text:004017B8 mov , 1
.text:004017BC mov eax, ; 取首字符运算后(changeG1)判断是否符合要求↓
.text:004017BF and eax, 0F0h
.text:004017C4 sar eax, 5
.text:004017C7 mov , ax
.text:004017CB movsx ecx,
.text:004017CF cmp ecx, 1
.text:004017D2 jnz short loc_4017EB
.text:004017D4 push 1 ; int
.text:004017D6 push 0Eh ; int
.text:004017D8 mov edx,
.text:004017DB push edx ; keyStr
.text:004017DC push (offset _GlobalArray_1+1) ; globalArr1pchar1
.text:004017E1 call j__DealKey1 ; changeG1为1时,第二次处理函数程序崩溃
.text:004017E6 add esp, 10h
.text:004017E9 jmp short loc_4017F8
.text:004017EB ; ---------------------------------------------------------------------------
.text:004017EB
.text:004017EB loc_4017EB: ; CODE XREF: _VerifyKey0+32j
.text:004017EB movsx eax,
.text:004017EF cmp eax, 2
.text:004017F2 jz short loc_4017F8
.text:004017F4 mov , 0; changeG1不为2时失败
.text:004017F8
.text:004017F8 loc_4017F8: ; CODE XREF: _VerifyKey0+49j
.text:004017F8 ; _VerifyKey0+52j
.text:004017F8 movsx ecx,
.text:004017FC test ecx, ecx
.text:004017FE jz short loc_401873 ; 只用changeG1为2时,才能真正运行到验证
.text:00401800 push 1 ; int
.text:00401802 push 0Eh ; int
.text:00401804 push offset keyStr ; "9QX6K882ISS5M"
.text:00401809 push (offset _GlobalArray_1+1) ; globalArr1pchar1
.text:0040180E call j__DealKey1 ; 处理函数2
.text:00401813 add esp, 10h
.text:00401816 push 3 ; Size
.text:00401818 push (offset _GlobalArray_1+1) ; Src
.text:0040181D lea edx,
.text:00401820 push edx ; Dst
.text:00401821 call _memcpy ; 最后取值1
.text:00401826 add esp, 0Ch
.text:00401829 push 4 ; Size
.text:0040182B push (offset _GlobalArray_1+0Ah) ; Src
.text:00401830 lea eax, ; 最后取值2
.text:00401833 push eax ; Dst
.text:00401834 call _memcpy
.text:00401839 add esp, 0Ch
.text:0040183C mov , 0
.text:00401840 push offset Str2 ; "FB40"
.text:00401845 lea ecx,
.text:00401848 push ecx ; Str1
.text:00401849 call _strcmp ; 比较
.text:0040184E add esp, 8
.text:00401851 test eax, eax
.text:00401853 jnz short loc_401864
.text:00401855 push offset Format ; "Good Job!"
.text:0040185A call _printf
.text:0040185F add esp, 4
.text:00401862 jmp short loc_401871
.text:00401864 ; ---------------------------------------------------------------------------
.text:00401864
.text:00401864 loc_401864: ; CODE XREF: _VerifyKey0+B3j
.text:00401864 push offset aBadWork_please ; "Bad Work.Please try again!"
.text:00401869 call _printf
.text:0040186E add esp, 4
.text:00401871
.text:00401871 loc_401871: ; CODE XREF: _VerifyKey0+C2j
.text:00401871 jmp short loc_401880
.text:00401873 ; ---------------------------------------------------------------------------
.text:00401873
.text:00401873 loc_401873: ; CODE XREF: _VerifyKey0+5Ej
.text:00401873 push offset aBadWork_please ; "Bad Work.Please try again!"
.text:00401878 call _printf
.text:0040187D add esp, 4
.text:00401880
.text:00401880 loc_401880: ; CODE XREF: _VerifyKey0:loc_401871j
.text:00401880 pop edi
.text:00401881 pop esi
.text:00401882 pop ebx
.text:00401883 add esp, 50h
.text:00401886 cmp ebp, esp
.text:00401888 call __chkesp
.text:0040188D mov esp, ebp
.text:0040188F pop ebp
.text:00401890 retn
.text:00401890 _VerifyKey0 endp
F5之后
int __cdecl VerifyKey0(int gArr1Char0, int globalArray0)
{
int v2; // eax@7
char v4; // @0
char v5; // @1
char Dst; // @6
__int16 changeG1; // @1
char v8; // @6
char v9; // @1
memset(&v5, -858993460, 0x50u);
v9 = 1;
changeG1 = (gArr1Char0 & 0xF0) >> 5; // changeG1的值必为2,为1时DealKey引入空值报错
if ( (signed __int16)((gArr1Char0 & 0xF0) >> 5) == 1 )// gArr1Char0的大小必须在0x40-0x5F
{
j__DealKey1(&GlobalArray_1, (char *)globalArray0, 14, 1);// 陷阱程序崩溃
}
else
{
if ( changeG1 != 2 ) // changeG1不为2时失败
v9 = 0;
}
if ( v9 )
{
j__DealKey1(&GlobalArray_1, "9QX6K882ISS5M", 14, 1);// 第2次处理
memcpy(Dst, &GlobalArray_1, 3u); // 从全局数组中第2位开始取3个
memcpy(&Dst, &GlobalArray_1, 4u); // 再从第10位开始取4个
v8 = 0;
if ( strcmp(Dst, "FB40") ) // 最后判断是否字串与"FB40"相等
v2 = printf("Bad Work.Please try again!");
else
v2 = printf("Good Job!");
}
else
{
v2 = printf("Bad Work.Please try again!");
}
return _chkesp(1, v2, v4);
}
从上述代码中可以看出GloabalArray_1用来判断是否真正进入验证:changeG1=(GloabalArray_1&0xF0)>>5;而changeG1必须等于2才能进入真正的验证,所以GloabalArray_1的值的二进制形式必须是,其中问号表示未知(1 or 0),也就是GloabalArray_1的范围必须在0x40~0x5F之间。最后的验证为取GloabalArray_1开始的3个字符,和GloabalArray_1开始的4个字符。但是其实GloabalArray_1之后取得只有前两个字符有用,因为验证最后是与字符串”FB40”比较是否相等所以要使验证通过最后肯定是这样的:GloabalArray_1==’F’;GloabalArray_1==’B’;GloabalArray_1==’4’;GloabalArray_1==’0’;GloabalArray_1==’\0’;
再来看一下第二个处理函数,篇幅问题就直接看下F5的结果吧int __cdecl DealKey1(char *globalArr1pchar1, char *keyStr, int gaLen, int status)
{
char v5; // @0
char v6; // @1
int keyStrLen; // @1
int i; // @2
int v9; // @6
int v10; // @6
unsigned int j; // @6
int kslTMP; // @1
memset(&v6, -858993460, 0x58u);
kslTMP = strlen(keyStr);
keyStrLen = kslTMP;
if ( status == 1 ) // 传进来的status均为1,仅此if范围内代码有用
{
kslTMP = 0;
for ( i = 0; i < gaLen; ++i )
{
kslTMP = i;
globalArr1pchar1 ^= keyStr;// 与传进的keyStr对应下标位置值异或
}
}
if ( status == 2 ) // 无用
{
v9 = 0;
v10 = 0;
for ( j = 0; ; ++j )
{
kslTMP = j__max0(gaLen, keyStrLen);
if ( kslTMP <= j )
break;
globalArr1pchar1 ^= keyStr;
++v9;
if ( v9 == gaLen )
v9 = 0;
if ( v10 == keyStrLen )
v10 = 0;
}
}
strcmp(GlobalArray_0, globalArr1pchar1);
return _chkesp(1, kslTMP, v5);
}
对此函数调用的地方仅仅有两处:可以看出两处调用第4个参数status均为1
第一处是个陷阱会导致程序崩溃j__DealKey1(&GlobalArray_1, (char *)globalArray0, 14, 1);// 陷阱程序崩溃他的第二个参数是调用某个全局字串,通过分析可以发现,这个全局数组其实一直会是什么都没有,这就导致处理函数在获取字串长为0,globalArr1pchar1 ^= keyStr;// 与传进的keyStr对应下标位置值异或keyStrLen为0,已0为除数(取余会有除操作)导致系统错误,程序崩溃。
第2处的调用传入keyStr="9QX6K882ISS5M",j__DealKey1(&GlobalArray_1, "9QX6K882ISS5M", 14, 1);// 第2次处理
传进的GlobalArray_1数组中的值是与keyStr对应下标的值进行异或,这样就可以得出:我把第一次处理key后的全局变量GloabalArray_1[?] 写成<?>(为了方便).<1> ^ ’9’==’F’;<2> ^ ‘Q’==’B’;<3> ^ ‘X’==’4’;<10> ^ ‘S’==’0’;<11> ^ ‘S’==’\0’;计算后再加上方面对GloabalArray_1的分析可以得出:<0> = 0x40~0x5F (010?????B)<1> = 0x7F (01111111B)<2> = 0x13 (00010011B)<3> = 0x6C (01101100B)<10> = 0x63 (01100011B)<11> = 0x53 (01010011B)
有了这些数据接下来就去看一下第一次处理也是最主要的处理key的部分:标注一些变量后F5能够很清晰的了解算法int __cdecl DealKey0(int key_len, char *char_table, char *key_str)
{
char v4; // @0
char v5; // @1
int res; // @14
char vResult; // @11
int vCmp5; // @9
int vCmp8; // @11
int minC58; // @14
int _index; // @9
int _gArrIndex; // @9
int v13; // @14
int v14; // @14
char c; // @3
int i; // @1
unsigned int j; // @3
char _arr; // @6
size_t char_table_len; // @4
memset(&v5, 0xCCCCCCCCu, 0x174u);
for ( i = 0; i < key_len; ++i ) // 将key转为字符表对应下标
{
c = key_str;
for ( j = 0; ; ++j )
{
char_table_len = strlen(char_table);
if ( char_table_len <= j )
break;
if ( char_table == c )
{
_arr = j;
break;
}
}
}
vCmp5 = 0;
_index = 0;
_gArrIndex = 0;
while ( _index < key_len )
{
vCmp8 = 0;
vResult = 0;
while ( vCmp8 < 8 && _index < key_len )
{
LOBYTE(res) = _arr;
v14 = 8 - vCmp8;
v13 = 5 - vCmp5;
minC58 = min(5 - vCmp5, 8 - vCmp8);
if ( vCmp5 <= vCmp8 )
LOBYTE(res) = (_BYTE)res << vCmp8;
else
LOBYTE(res) = (signed int)(unsigned __int8)res >> vCmp5;
vResult |= res;
vCmp8 += minC58;
vCmp5 += minC58;
if ( vCmp5 >= 5 )
{
++_index;
vCmp5 = 0;
}
}
GlobalArray_1 = vResult;
}
GlobalArray_1 = 0;
return _chkesp(1, 5 * key_len / 8, v4);
}
上面就是关键算法的实现部分了,首先它根据输入key将每个字符在字符表中的下标存入_arr[]中(为了方便我将把_arr[?]直接写成[?],与上面<?>对应),然后再去[?]中的值进行运算,将每个运算结果写入到对应的<?>中。[?]的值只能是在字符表的范围内,在这里字符表为”123456789abcdefghiklmnpqrstuvwxyz”共34个字符(它里面竟然不包含’o’,简直坑爹,害的我很苦),所以[?]值得范围是0~33(00000000B~00100001B).这个算法的分析没什么好讲的,因为这已经是源代码了。下面主要看看每个<?>对应的生成方式,每个<?>会用多个[?]来生成。改变一下这段代码就能获取每个<?>的生成方式。改变后的c代码:
#include <stdio.h>
#include <stdlib.h>
#include <string>
using namespace std;
#define min(x,y) (((x)<(y)) ? (x):(y))
string toStr(int a)
{
char b;
itoa(a,b,10);
return b;
}
int main()
{
printf("\n_arr:<?>\nGloabalArray_1:[?]\n\n");
int key_len=24;
int vCmp5 = 0 , vCmp8 = 0;
int _index = 0;
int _gArrIndex = 0;
string vResult;
int minC58;
string res;
while ( _index < key_len )
{
vCmp8 = 0;
vResult = "<";
vResult += toStr(_gArrIndex);
vResult += "> = ";
bool p=false;
while ( vCmp8 < 8 && _index < key_len )
{
res = "([";
res += toStr(_index);
res += "]";
minC58 = min(5 - vCmp5, 8 - vCmp8);
if ( vCmp5 <= vCmp8 )
res = res + "<<" + toStr(vCmp8);
else
res = res + ">>" + toStr(vCmp5);
res+=")";
if(p)
vResult +="|";
vResult +=res;
p=true;
vCmp8 += minC58;
vCmp5 += minC58;
if ( vCmp5 >= 5 )
{
++_index;
vCmp5 = 0;
}
}
_gArrIndex++;
vResult +="\n";
printf(vResult.c_str());
}
printf("\n");
system("pause");
return 0;
}
运行后就能看到每个<?>的生成方式了。
其中只有<0>,<1>,<2>,<3>,<10>,<11>是我们需要的
下面就是最主要关键的部分了,根据已知的东西来分析获取key:第一个有用的[?]的范围:[?] = (00000000B~00100001B);先定义[?]为未知Def[?] = 00??????B(黄底的部分说明原本是在[?]中的部分,下同)一步步来①:<0> = (<<0) | (<<5) )= 010?????B↓00??????B| ???00000B = 010?????B↓000?????B| 01000000B = 010?????B所以①可以得出: = 000?????B , = 00???010B②<1> = (>>3) | (<<2) | (<<7) = 01111111B↓00000???B | ??????00B | ?0000000B = 01111111B↓00000?11B | 01111?00B | 00000000B = 01111111B = 00?11010B 要小于00100001B所以 由②可得: = 00011010B , = 00011111B , = 00?????0B③<2> = (>>1) | (<<4) = 00010011B↓000?????B | ????0000B = 00010011B↓000?00111B | 000?0000B = 00010011B = 00?001110B , 要小于00100001B所以 由③可得: = 00000110B , = 00??0001B④<3> = (>>4) | (<<1) | (<<6) = 01101100B↓000000??B | 0??????0B | ??000000B = 01101100B↓00000000B | 0?101100B | 0?000000B = 01101100B = 00?10110B,要小于00100001B所以 有④可得: = 00000001B , = 00010110B , = 00????01B⑤<10> = (<<0) | (<<5) = 01100011B↓00??????B | ???00000B = 01100011B↓00000011B |01100000B = 01100011B所以 由⑤可得: = 00000011B , = 00???011B⑥<11> = ( >> 3) | (<<2) | (<<7) =01010011B↓00000???B | ??????00B | ?0000000B = 01010011B↓00000011B | 0101000B | 00000000B = 01010011B所以 由⑥可得: = 0001101?B , = 00010100B , = 00?????0B最后综上可得:
= 000?????B = (0~31) = 00011010B= 26 = 00011111B =31 = 00000110B = 6 = 00000001B = 1 = 00010110B = 22 = 00????01B = {1,5,9,13,17,21,25,29,33} = 00000011B = 3 = 00011011B = 27 = 00010100B = 20 = 00?????0B = {2,4,6,8...30,32}
最后通过对应字符表可得最后key字符串的24位字符的范围:
charTable = '123456789abcdefghijklnpqrstuvwxyz';
key = {'1' ~ 'w'};
key = 's';
key = 'x';
key = '7';
key = '2';
key = 'n';
key = {'2','6','a','e','i','m','r','v','z'};
key ~ key = 任意;
key = '4';
key = 't';
key = 'l';
key = {'1','3','5','7','9','b','d','f','h','j','l','n','q','s','u','w','y'};
key ~ key = 任意;
//这里的任意是charTable中的任意字符(很坑爹的不包含字母'o');
随便测试几个key:
分析完了,明天继续图书馆。{:301_1003:} 谢谢分享好精的汇编语言呀 看看楼主的大作,支持原创,支持吾爱 不错不错~{:1_902:} 最近也在写一个keygen,代码好多 学这些就要靠大神你的教程了!!大神我失业中求安慰{:1_918:} 因为精华来的,可惜小女子看不懂 学习了! 思路相当的清楚,学习了 好厉害,谢谢分享。。。 这膜拜大牛啊虽然看不懂还是觉得非常厉害 {:1_931:}学习了!
膜拜 表示在围观 支持一下 学习了,非常感谢! 膜拜会算法的大牛 !!手置了! 兄弟呀,说的太高深了,看不明白呀 哎 这简直是在看天书什么时候自己可以这样 好复杂,初学看不懂