老文章档案 · 2003 年

如何破解Opera 4.0 Final

破解教程 · 破解入门 · 2003-09-26 · 作者:人气: · 出处:赢政天下整理收藏)

Opera 4.0 Final的注册码与4.0 beta 5相比,差别不大,但是和以前的版本相比,注册码判断算法有根本的变化,找注册码等价于写注册机。不过我觉得这种只改变算法的策略并不能很好地保护其软件。

首先,我们用bpx GetDlgItemTextA设断点,它会调用该函数3次,分别读入Name、Company、Code。由于Name、Company与Code无关,故我们只要在它最后一次调用GetDlgItemTexA时拦住即可。如下:

:004420A6 8B6C2410 mov ebp, dword ptr [esp+10]

:004420AA BB2B010000 mov ebx, 0000012B

:004420AF 57 push edi

* Reference To: USER32.GetDlgItemTextA, Ord:0104h

|

:004420B0 8B3D5C555300 mov edi, dword ptr [0053555C]

:004420B6 8D8638010000 lea eax, dword ptr [esi+00000138]

:004420BC 53 push ebx

:004420BD 50 push eax

:004420BE 68BD2B0000 push 00002BBD

:004420C3 55 push ebp

:004420C4 FFD7 call edi //读取Name

:004420C6 8D8664020000 lea eax, dword ptr [esi+00000264]

:004420CC 53 push ebx

:004420CD 50 push eax

:004420CE 68BE2B0000 push 00002BBE

:004420D3 55 push ebp

:004420D4 FFD7 call edi //读取Company

:004420D6 8D8690030000 lea eax, dword ptr [esi+00000390]

:004420DC 53 push ebx

:004420DD 50 push eax

:004420DE 68BF2B0000 push 00002BBF

:004420E3 55 push ebp

:004420E4 FFD7 call edi //读取Code

查阅一下API手册,可以知道传递给GetDlgItemTextA( )的第3个参数是缓冲区的首址。看一下上面的代码,发现在3次调用GetDlgItemTextA( )时使用的缓冲区首址是esi+00000390,于是等它执行完CS:004420E4处的call edi指令后,马上用BPR esi+390 esi+390+len rw作为断点监视你输入的假注册码,看它如何处理你输入的假注册码,其中len是假注册码的长度。然后按F5执行,马上在下面的地方又中断下来:

:0050E680 F7C703000000 test edi, 00000003

:0050E686 7514 jne 0050E69C

:0050E688 C1E902 shr ecx, 02

:0050E68B 83E203 and edx, 00000003

:0050E68E 83F908 cmp ecx, 00000008

:0050E691 7229 jb 0050E6BC

:0050E693 F3 repz

:0050E694 A5 movsd //在这里中断

:0050E695 FF2495A8E75000 jmp dword ptr [4*edx+0050E7A8]

很显然,它要把我们的假注册码搬到内存的另一个地方去。清除所有断点,直接按一下F12,回到上一层,我们准备从更高的调用层次上来分析一下程序大致流程,免得过早地陷入细节分析中去。如无特殊说明,以后就只按F10了。

:0043CF6C 68BC040000 push 000004BC

:0043CF71 56 push esi

:0043CF72 57 push edi

:0043CF73 E8E8160D00 call 0050E660 //刚才是在这里面

...............................................

:0043D00C FF750C push [ebp+0C]

:0043D00F 894514 mov dword ptr [ebp+14], eax

:0043D012 E849110D00 call 0050E160 //求注册码长度

:0043D017 83F80C cmp eax, 0000000C //长度等于12吗?

:0043D01A 59 pop ecx

:0043D01B 753C jne 0043D059

:0043D01D 56 push esi

:0043D01E E8A40F0D00 call 0050DFC7

:0043D023 BE00010000 mov esi, 00000100

:0043D028 56 push esi

:0043D029 E8D30F0D00 call 0050E001

:0043D02E 59 pop ecx

:0043D02F 8BF8 mov edi, eax

:0043D031 59 pop ecx

:0043D032 56 push esi

:0043D033 57 push edi

* Possible Reference to String Resource ID=22005: "Invalid registration number. "

"You have probably entered a previous"

|

:0043D034 68F5550000 push 000055F5

:0043D039 8B0DB8205600 mov ecx, dword ptr [005620B8]

:0043D03F E8E6500800 call 004C212A

:0043D044 6A10 push 00000010

:0043D046 FF7514 push [ebp+14]

:0043D049 57 push edi

:0043D04A FF7508 push [ebp+08]

* Reference To: USER32.MessageBoxA, Ord:01BEh

|

:0043D04D FF1574565300 Call dword ptr [00535674]

上述代码检查你输入的假注册码长度是否为12,若为12则认为是老版本的注册码,否则继续检查你输入的假注册码的前12位是否和旧版本的相同。

:0043D086 8D45C4 lea eax, dword ptr [ebp-3C] //字符串1

:0043D089 50 push eax

:0043D08A 8D857CFFFFFF lea eax, dword ptr [ebp-84] //字符串2

:0043D090 50 push eax

:0043D091 E87A0F0D00 call 0050E010 //比较字符串

:0043D096 83C420 add esp, 20

:0043D099 85C0 test eax, eax

:0043D09B 7521 jne 0043D0BE

:0043D09D 56 push esi

:0043D09E E8240F0D00 call 0050DFC7

:0043D0A3 BE00010000 mov esi, 00000100

:0043D0A8 56 push esi

:0043D0A9 E8530F0D00 call 0050E001

:0043D0AE 59 pop ecx

:0043D0AF 8BF8 mov edi, eax

:0043D0B1 59 pop ecx

:0043D0B2 56 push esi

:0043D0B3 57 push edi

* Possible Reference to String Resource ID=22006: "Invalid registration number."

"You have entered a 3.5x or 3.6x"

|

:0043D0B4 68F6550000 push 000055F6

:0043D0B9 E97BFFFFFF jmp 0043D039

如果不相同,则马上到了关键的判断(由于W32Dasm犯傻,故改用SoftICE反汇编出来的代码),总算是有点盼头了。

015F:0043D0CF 8D8790030000 LEA EAX,[EDI+00000390]

015F:0043D0D5 50 PUSH EAX //假注册码

015F:0043D0D6 E812040000 CALL 0043D4ED //核心判断

015F:0043D0DB 59 POP ECX

015F:0043D0DC 84C0 TEST AL,AL

015F:0043D0DE 59 POP ECX

015F:0043D0DF 7507 JNZ 0043D0E8

015F:0043D0E1 C745FC01000000 MOV DWORD PTR [EBP-04],00000001 //设good标志位

按F8跟进call 0043D4ED中去,可以看到这个函数写得简明扼要:

:0043D4ED 55 push ebp

:0043D4EE 8BEC mov ebp, esp

:0043D4F0 83EC18 sub esp, 00000018

:0043D4F3 E8319FFDFF call 00417429 //准备性的计算工作,不必跟进去

:0043D4F8 84C0 test al, al

:0043D4FA 7530 jne 0043D52C //这里是绝对不会跳的

:0043D4FC FF7508 push [ebp+08]

:0043D4FF 8D45E8 lea eax, dword ptr [ebp-18]

:0043D502 50 push eax

:0043D503 E8EC690B00 call 004F3EF4 //重要,必须跟进去

:0043D508 59 pop ecx

:0043D509 84C0 test al, al

:0043D50B 59 pop ecx

:0043D50C 751E jne 0043D52C

:0043D50E 8D45E8 lea eax, dword ptr [ebp-18]

:0043D511 50 push eax

:0043D512 E8889FFDFF call 0041749F //重要,必须跟进去

:0043D517 84C0 test al, al

:0043D519 59 pop ecx

:0043D51A 7510 jne 0043D52C

:0043D51C 8D45E8 lea eax, dword ptr [ebp-18]

:0043D51F 50 push eax

:0043D520 E8D56B0B00 call 004F40FA //重要,必须跟进去

:0043D525 84C0 test al, al

:0043D527 59 pop ecx

:0043D528 7502 jne 0043D52C

:0043D52A C9 leave

:0043D52B C3 ret

:0043D52C 0CFF or al, FF //bad guy

:0043D52E C9 leave

:0043D52F C3 ret

由此可知,要点在上述的3个call中。下面得改按F8,逐一分析这3个call。先跟进call 004F3EF4:

:004F3EF4 55 push ebp

:004F3EF5 8BEC mov ebp, esp

:004F3EF7 51 push ecx

:004F3EF8 53 push ebx

:004F3EF9 56 push esi

:004F3EFA 8B750C mov esi, dword ptr [ebp+0C]

:004F3EFD B02D mov al, 2D

:004F3EFF 57 push edi

:004F3F00 384601 cmp byte ptr [esi+01], al //第1个字符为'-'吗?

:004F3F03 0F85A4000000 jne 004F3FAD

:004F3F09 384607 cmp byte ptr [esi+07], al //第7个字符为'-'吗?

:004F3F0C 0F859B000000 jne 004F3FAD

:004F3F12 38460D cmp byte ptr [esi+0D], al //第13个字符为'-'吗?

:004F3F15 0F8592000000 jne 004F3FAD

:004F3F1B 384613 cmp byte ptr [esi+13], al //第19个字符为'-'吗?

:004F3F1E 0F8589000000 jne 004F3FAD

:004F3F24 384619 cmp byte ptr [esi+19], al //第25个字符为'-'吗?

:004F3F27 0F8580000000 jne 004F3FAD

:004F3F2D 56 push esi

:004F3F2E E82DA20100 call 0050E160 //求注册码长度

:004F3F33 83F81F cmp eax, 0000001F //长度为31吗?

:004F3F36 59 pop ecx

:004F3F37 7574 jne 004F3FAD

到了这里,我们就知道注册码应包含31个字符,且第1、7、13、19、25个均为横杠字符'-'。于是将假注册码改成

w-RSuhU-8bbTm-taXah-v3uMt-EDrKv(这个是可用的,呵呵),重复上面的步骤,到如下的地方。

下面是一个两重循环,在这个两重循环中,它要把你输入的假注册码的后5部分转换成5个长整数。根据其转换过程,我们知道注册码的后5部分可以看作是5个五十进制数。即RSuhU、8bbTm、taXah、v3uMt、EDrKv为5个五十进制的数,只不过最低位

在最左边。它所使用的五十进制数的表格为"abcdefhijkmnprstuvwxyzABCDEFHJKLMNPQRSTUVWXY345678"。

下面举个例子,看它如何把注册码的第2部分即五十进制数“RSuhU”转换为对应的十六进制数。

1、首先取五十进制数的最低位,即R,查表,发现R在该表中的下标为36,即五十进制中的R对应十进制中的36。用

用36乘以50的0次方,得到36。

2、再取五十进制数RSuhU的次低位,即S,查表,发现S在该表中的下标为37,即五十进制中的S对应十进制中的37。

用37乘以50的1次方,得到1850。

3、取五十进制数RSuhU的u位,查表,发现五十进制的u对应十进制的16。

用16乘以50的2次方,得到40000。

4、取五十进制数RSuhU的h位,查表,发现五十进制的h对应十进制的6。

用6乘以50的3次方,得到750000。

5、取五十进制数RSuhU的U位,查表,发现五十进制的U对应十进制的39。

用39乘以50的4次方,得到243750000。

将上述5个乘积累加,得到244541886。十进制数244541886的十六进制形式为0E9369BE,故五十进制数RSuhU对应的十六进制数为0E9369BE(当然前提是遵循Opera的作者所制定的转换规则,即使用上面的那张表)。

同理可把8bbTm、taXah、v3uMt、EDrKv转换为对应的十六进制数:04023177、023DCE97、05D430D9、068EFB70。

除了转换之外,它还限制转换后的十六进制数不得大于0x0FFFFFFF,即最高4 bit必须为0,这点在后面会用到。

:004F3F39 8B4508 mov eax, dword ptr [ebp+08]

:004F3F3C 8A0E mov cl, byte ptr [esi] //注册码的第1部分

:004F3F3E 8365FC00 and dword ptr [ebp-04], 00000000

:004F3F42 8D7E02 lea edi, dword ptr [esi+02]

:004F3F45 8808 mov byte ptr [eax], cl //把注册码的第1部分搬到另外的地方

:004F3F47 83C004 add eax, 00000004

:004F3F4A 894508 mov dword ptr [ebp+08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F3FA7(C)

|

:004F3F4D 33DB xor ebx, ebx //累加和清0

:004F3F4F C7450C01000000 mov [ebp+0C], 00000001 //50的0次方

:004F3F56 33F6 xor esi, esi //循环控制变量清0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F3F8A(C)

|

:004F3F58 0FBE0437 movsx eax, byte ptr [edi+esi] //取五十进制数的一位

:004F3F5C 50 push eax

:004F3F5D FF3584D35300 push dword ptr [0053D384]

:004F3F63 E888AA0100 call 0050E9F0 //查表

:004F3F68 59 pop ecx

:004F3F69 85C0 test eax, eax

:004F3F6B 59 pop ecx

:004F3F6C 743F je 004F3FAD

:004F3F6E 2A0584D35300 sub al, byte ptr [0053D384]

:004F3F74 0FB6C0 movzx eax, al //查表所得的下标

:004F3F77 0FAF450C imul eax, dword ptr [ebp+0C] //乘以50的n次方

:004F3F7B 03D8 add ebx, eax //累加到ebx中

:004F3F7D 8B450C mov eax, dword ptr [ebp+0C]

:004F3F80 6BC032 imul eax, 00000032 //50的n次方再乘以50

:004F3F83 46 inc esi //循环控制变量++

:004F3F84 89450C mov dword ptr [ebp+0C], eax

:004F3F87 83FE05 cmp esi, 00000005 //5位全转换完了?

:004F3F8A 7CCC jl 004F3F58 //内循环

:004F3F8C F7C3000000F0 test ebx, F0000000 //转换结果不得大于0x0FFFFFFF

:004F3F92 7519 jne 004F3FAD //jump if bad guy

:004F3F94 8B4508 mov eax, dword ptr [ebp+08]

:004F3F97 FF45FC inc [ebp-04]

:004F3F9A 83450804 add dword ptr [ebp+08], 00000004

:004F3F9E 83C706 add edi, 00000006

:004F3FA1 837DFC05 cmp dword ptr [ebp-04], 00000005 //注册码的后5部分全转换完了?

:004F3FA5 8918 mov dword ptr [eax], ebx

:004F3FA7 7CA4 jl 004F3F4D //外循环

:004F3FA9 32C0 xor al, al //good guy

:004F3FAB EB02 jmp 004F3FAF

经过上面的转换,注册码的后5部分被转换成了5个十六进制数。显然上面的转换是可逆的,即给定一个十六进制数,我们可以写出其五十进制的表示。这对写注册机是有用的。下面我们继续看它如何处理这5个数。用数组a[5]表示这5个数,则下面的汇编代码可以翻译为对应的C程序:

for(k = 0; k < 4; k++)

{

a[k] ^= a[k+1];

}

* Referenced by a CALL at Address:

|:004174A3

|

:004174F3 8B442404 mov eax, dword ptr [esp+04]

:004174F7 56 push esi

:004174F8 6A04 push 00000004 //循环4次

:004174FA 83C004 add eax, 00000004

:004174FD 5A pop edx

:004174FE 8B7004 mov esi, dword ptr [eax+04] //取出a[k+1]

:00417501 8D4804 lea ecx, dword ptr [eax+04]

:00417504 3130 xor dword ptr [eax], esi //与a[k]异或

:00417506 4A dec edx

:00417507 8BC1 mov eax, ecx

:00417509 75F3 jne 004174FE //循环

:0041750B 32C0 xor al, al

:0041750D 5E pop esi

:0041750E C3 ret

由于异或运算是可逆的,因此上面的变换也可逆。我们继续看它如何处理异或过的这5个数。接下来将是一个大的异或变换,不过不用担心,这个变换仍是可逆的。如下:

* Referenced by a CALL at Address:

|:004174B4

|

:004174C4 56 push esi

:004174C5 57 push edi

:004174C6 33FF xor edi, edi //循环控制变量edi清0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004174EC(C)

|

:004174C8 6A05 push 00000005

:004174CA 8BC7 mov eax, edi

:004174CC 33D2 xor edx, edx

:004174CE 59 pop ecx

:004174CF F7F1 div ecx //循环控制变量edi对5求余

:004174D1 8B44240C mov eax, dword ptr [esp+0C]

:004174D5 6A00 push 00000000

:004174D7 8D749004 lea esi, dword ptr [eax+4*edx+04]

:004174DB E870FFFFFF call 00417450 //取常数数列的下一项

:004174E0 25FFFFFF0F and eax, 0FFFFFFF //屏蔽掉高4个bit

:004174E5 59 pop ecx

:004174E6 3106 xor dword ptr [esi], eax //与a[edi % 5]异或

:004174E8 47 inc edi //循环控制变量加1

:004174E9 83FF65 cmp edi, 00000065

:004174EC 72DA jb 004174C8 //继续循环

:004174EE 5F pop edi

:004174EF 32C0 xor al, al

:004174F1 5E pop esi

:004174F2 C3 ret

上面的汇编代码可翻译为如下的伪码:

for(edi = 0; edi < 0x65; edi++)

{

NextNumber = GetNumberFromSequence( );

NextNumber &= 0x0FFFFFFF;

a[edi % 5] ^= NextNumber;

}

即用一个常数数列的前0x65项来分别与我们在上一步所得到的5个数进行异或。这个循环的异或过程可以等价于:

a[0] ^= 常数1;

a[1] ^= 常数2;

a[2] ^= 常数3;

a[3] ^= 常数4;

a[4] ^= 常数5;

其中上述5个常数是可以求出来的,因为所用到的常数数列是确定的,跟进call 00417450就可以看清常数数列的计算过程。

用Sequence[n]表示该数列的第n项,则call 00417450的作用就是根据Sequence[n]计算出Sequence[n+1]来,相当于数列的递推公式,如下所示。

:0041745C 8B0D50625400 mov ecx, dword ptr [00546250] //取出Sequence[n]

:00417462 8BC1 mov eax, ecx

:00417464 8D1409 lea edx, dword ptr [ecx+ecx]

:00417467 24FE and al, FE

:00417469 33C2 xor eax, edx

:0041746B 8BD1 mov edx, ecx

:0041746D D1E0 shl eax, 1

:0041746F 83E2FC and edx, FFFFFFFC

:00417472 33C2 xor eax, edx

:00417474 8BD1 mov edx, ecx

:00417476 C1E002 shl eax, 02

:00417479 83E2F0 and edx, FFFFFFF0

:0041747C 33C2 xor eax, edx

:0041747E 8BD1 mov edx, ecx

:00417480 C1E002 shl eax, 02

:00417483 83E2C0 and edx, FFFFFFC0

:00417486 33C2 xor eax, edx

:00417488 8BD1 mov edx, ecx

:0041748A C1E019 shl eax, 19

:0041748D 81E200000080 and edx, 80000000

:00417493 33C2 xor eax, edx

:00417495 D1E9 shr ecx, 1

:00417497 0BC1 or eax, ecx

:00417499 A350625400 mov dword ptr [00546250], eax //保存Sequence[n+1]

:0041749E C3 ret

看一下DS:[00546250]中存放的初始值,就知道Sequence[0] = 0x01A26A75。根据异或运算的结合律,可以知道:

常数1 = (Sequence[1] ^ Sequence[6] ^ ... ^ Sequence[96] ^ Sequence[101]) & 0x0FFFFFFF

常数2 = (Sequence[2] ^ Sequence[7] ^ ... ^ Sequence[97]) & 0x0FFFFFFF

常数3 = (Sequence[3] ^ Sequence[8] ^ ... ^ Sequence[98]) & 0x0FFFFFFF

常数4 = (Sequence[4] ^ Sequence[9] ^ ... ^ Sequence[99]) & 0x0FFFFFFF

常数5 = (Sequence[5] ^ Sequence[10] ^ ... ^ Sequence[100]) & 0x0FFFFFFF

经过了上面的循环异或后,我们再来看看它下面是怎么处理的。运算了半天,总该判断一下了吧。

在如下的代码中,它要判断注册码的第2部分。

* Referenced by a CALL at Address:

|:004F4107

|

:004F3FB4 55 push ebp

:004F3FB5 8BEC mov ebp, esp

:004F3FB7 83EC18 sub esp, 00000018

:004F3FBA 8B4508 mov eax, dword ptr [ebp+08]

:004F3FBD 56 push esi

:004F3FBE 8B750C mov esi, dword ptr [ebp+0C]

:004F3FC1 57 push edi

:004F3FC2 BA000000F0 mov edx, F0000000 //掩码,用来提取一个数的高4bit(即28~31 bit)

:004F3FC7 8B4E08 mov ecx, dword ptr [esi+08] //取出a[1]

:004F3FCA 89480C mov dword ptr [eax+0C], ecx

:004F3FCD 8B4E0C mov ecx, dword ptr [esi+0C] //取出a[2]

:004F3FD0 894810 mov dword ptr [eax+10], ecx

:004F3FD3 8B4E10 mov ecx, dword ptr [esi+10] //取出a[3]

:004F3FD6 894814 mov dword ptr [eax+14], ecx

:004F3FD9 8B4E14 mov ecx, dword ptr [esi+14] //取出a[4]

:004F3FDC 894818 mov dword ptr [eax+18], ecx

:004F3FDF 8B4E04 mov ecx, dword ptr [esi+04] //取出a[0]

:004F3FE2 C1E104 shl ecx, 04 //a[0]左移4位

:004F3FE5 8BF9 mov edi, ecx

:004F3FE7 23FA and edi, edx //与掩码相与

:004F3FE9 09780C or dword ptr [eax+0C], edi //把a[0]的4个bit分给a[1]

:004F3FEC C1E104 shl ecx, 04 //a[0]再左移4位

:004F3FEF 8BF9 mov edi, ecx

:004F3FF1 23FA and edi, edx //与掩码相与

:004F3FF3 097810 or dword ptr [eax+10], edi //把a[0]的4个bit分给a[2]

:004F3FF6 C1E104 shl ecx, 04 //a[0]再左移4位

:004F3FF9 8BF9 mov edi, ecx

:004F3FFB 23FA and edi, edx //与掩码相与

:004F3FFD 097814 or dword ptr [eax+14], edi //把a[0]的4个bit分给a[3]

:004F4000 C1E104 shl ecx, 04 //a[0]再左移4位

:004F4003 8BF9 mov edi, ecx

:004F4005 23FA and edi, edx //与掩码相与

:004F4007 BA000F0000 mov edx, 00000F00 //另一个掩码,用来提取一个数的第8~11 bit

:004F400C 097818 or dword ptr [eax+18], edi //把a[0]的4个bit分给a[4]

:004F400F C1E910 shr ecx, 10 //a[0]再右移16位

:004F4012 8BF9 mov edi, ecx

:004F4014 23FA and edi, edx //与掩码相与,

:004F4016 3BFA cmp edi, edx //与掩码比较!

:004F4018 752E jne 004F4048 //jump if bad guy

:004F401A 8BD1 mov edx, ecx

:004F401C 83E101 and ecx, 00000001 //与另一掩码相与,提取最低bit(即第0 bit)

:004F401F D1EA shr edx, 1 //右移一位,等于去掉了原来的第0 bit

:004F4021 6A06 push 00000006

:004F4023 83E27F and edx, 0000007F //与掩码7F相与,提取出最低的7个bit

:004F4026 8908 mov dword ptr [eax], ecx //保存第0个bit,以后再判断

:004F4028 59 pop ecx

:004F4029 8D7DE8 lea edi, dword ptr [ebp-18]

:004F402C 895008 mov dword ptr [eax+08], edx //保存7个bit,以后再判断

:004F402F F3 repz

:004F4030 A5 movsd

上面有很多位运算,实际上是在对a[0]进行五马分尸,要把a[0]的28个bit共拆分为7个部分(a[0]的最高4个bit均为0,故不考虑在内)。详细说明如下:

假定十六进制表示为:

a[0] = 0MNYZUVW,a[1] = 0xxxxxxx,a[2] = 0yyyyyyy,a[3] = 0zzzzzzz,a[4] = 0kkkkkkk。

则将a[0]拆分为7部分后,变为:

a[1] = Mxxxxxxx,a[2] = Nyyyyyyy,a[3] = Yzzzzzzz,a[4] = Zkkkkkkk。

即a[0]给了a[1]、a[2]、a[3]、a[4]各4个bit(即M、N、Y、Z)作为他们的最高4个bit。另外,a[0]的最低8个bit(即VW)被拆分为两部分,一部分只有一个bit,另外一部分有7个bit,并分别保存起来,后面它要对这两部分进行判断,此为后话。

a[0]的另外4个bit(即U)在上面的CS:004F4016处进行了判断,如果满足a[0] & 00000F00 = 00000F00,则OK,由此可知 a[0]的第8~11 bit均应为1(即U = F)。

经过这一番折腾,它把a[0]瓜分了,显然是想分散我们的注意力,只要我们漏过a[0]的7部分中的任一部分,有效的注册码就找不到。

还记不记得注册码的第1部分只有一个字符?接下来它就要对注册码的第1部分进行查表变换。

:004F4031 0FB655E8 movzx edx, byte ptr [ebp-18] //取出注册码的第1部分

:004F4035 33C9 xor ecx, ecx //下标清0

:004F4037 0FBEB180D35300 movsx esi, byte ptr [ecx+0053D380]//查表,从表"emuw"中取出一个字符

:004F403E 3BF2 cmp esi, edx //和注册码的第一部分相等?

:004F4040 740A je 004F404C //相等则将下标记下来

:004F4042 41 inc ecx //下标加1

:004F4043 83F903 cmp ecx, 00000003 //该表中仅4个字符

:004F4046 7EEF jle 004F4037 //循环,遍历该表

:004F4048 0CFF or al, FF //bad guy

:004F404A EB05 jmp 004F4051

:004F404C 894804 mov dword ptr [eax+04], ecx //保存此时的下标

:004F404F 32C0 xor al, al //good guy

:004F4051 5F pop edi

:004F4052 5E pop esi

:004F4053 C9 leave

:004F4054 C3 ret

由上可见,如果你输入的假注册码的第一个字符如果不是e或m或u或w,则bye-bye了。否则它就记下该下标,此后要对这个下标进行判断,耐心等等,后面还会提到。

接下来,它要根据a[1]、a[2]、a[3]计算出一个数来,然后和a[4]进行比较。至此我们知道a[4]是根据a[1]、a[2]、a[3]计算出来的,这对写注册机很重要。计算没什么特别的地方,照搬到我们的注册机中,稍加改动即可。计算过程如下:

* Referenced by a CALL at Address:

|:004F411A

|

:004F4055 55 push ebp

:004F4056 8BEC mov ebp, esp

:004F4058 83EC0C sub esp, 0000000C

:004F405B 8B4D08 mov ecx, dword ptr [ebp+08]

:004F405E 53 push ebx

:004F405F 56 push esi

:004F4060 33F6 xor esi, esi

:004F4062 8B4104 mov eax, dword ptr [ecx+04]

:004F4065 57 push edi

:004F4066 40 inc eax

:004F4067 C745FC79786573 mov [ebp-04], 73657879

:004F406E C745F865626162 mov [ebp-08], 62616265

:004F4075 8945F4 mov dword ptr [ebp-0C], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40DF(C)

|

:004F4078 8BC6 mov eax, esi

:004F407A 6A03 push 00000003

:004F407C 99 cdq

:004F407D 5F pop edi

:004F407E F7FF idiv edi

:004F4080 46 inc esi

:004F4081 6A03 push 00000003

:004F4083 8BC6 mov eax, esi

:004F4085 5B pop ebx

:004F4086 8B7C910C mov edi, dword ptr [ecx+4*edx+0C]

:004F408A 99 cdq

:004F408B F7FB idiv ebx

:004F408D 8B45F4 mov eax, dword ptr [ebp-0C]

:004F4090 85C0 test eax, eax

:004F4092 8B54910C mov edx, dword ptr [ecx+4*edx+0C]

:004F4096 7E37 jle 004F40CF

:004F4098 8B5DFC mov ebx, dword ptr [ebp-04]

:004F409B 894508 mov dword ptr [ebp+08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40AA(C)

|

:004F409E 8BC3 mov eax, ebx

:004F40A0 0FAFC3 imul eax, ebx

:004F40A3 33C7 xor eax, edi

:004F40A5 FF4D08 dec [ebp+08]

:004F40A8 8BD8 mov ebx, eax

:004F40AA 75F2 jne 004F409E

:004F40AC 895DFC mov dword ptr [ebp-04], ebx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40D2(U)

|

:004F40AF 8B4108 mov eax, dword ptr [ecx+08]

:004F40B2 83C003 add eax, 00000003

:004F40B5 85C0 test eax, eax

:004F40B7 7E1B jle 004F40D4

:004F40B9 894508 mov dword ptr [ebp+08], eax

:004F40BC 8B45F8 mov eax, dword ptr [ebp-08]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40CB(C)

|

:004F40BF 8BF8 mov edi, eax

:004F40C1 0FAFF8 imul edi, eax

:004F40C4 33FA xor edi, edx

:004F40C6 FF4D08 dec [ebp+08]

:004F40C9 8BC7 mov eax, edi

:004F40CB 75F2 jne 004F40BF

:004F40CD EB0A jmp 004F40D9

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F4096(C)

|

:004F40CF 8B5DFC mov ebx, dword ptr [ebp-04]

:004F40D2 EBDB jmp 004F40AF

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40B7(C)

|

:004F40D4 8B45F8 mov eax, dword ptr [ebp-08]

:004F40D7 EB03 jmp 004F40DC

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40CD(U)

|

:004F40D9 8945F8 mov dword ptr [ebp-08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F40D7(U)

|

:004F40DC 83FE1F cmp esi, 0000001F

:004F40DF 7C97 jl 004F4078

:004F40E1 81E3F0F0F000 and ebx, 00F0F0F0 //最高4 bit被干掉了

:004F40E7 250F0F0F0F and eax, 0F0F0F0F //最高4 bit被干掉了

:004F40EC 0BD8 or ebx, eax

:004F40EE 8B450C mov eax, dword ptr [ebp+0C]

:004F40F1 5F pop edi

:004F40F2 5E pop esi

:004F40F3 8918 mov dword ptr [eax], ebx //保存计算的结果

:004F40F5 32C0 xor al, al

:004F40F7 5B pop ebx

:004F40F8 C9 leave

:004F40F9 C3 ret

注意上面的注释!在CS:004F40E1和CS:004F40E7这两处,最高4个bit都被变为0了,这意味着计算出来的这个数的最高4 bit全部为0。但是它又要拿这个计算出来的数和a[4]进行比较,那说明此时a[4]的最高4 bit也必须为0。

还记不记得此时a[4]的最高4 bit是从a[0]中分来的(十六进制形式为Zkkkkkkk)!至此我们又搞定a[0]的7部分

中的一部分。

下面是和a[4]进行比较:

:004F4112 8D4508 lea eax, dword ptr [ebp+08]

:004F4115 50 push eax

:004F4116 8D45E4 lea eax, dword ptr [ebp-1C]

:004F4119 50 push eax

:004F411A E836FFFFFF call 004F4055 //根据a[1]、a[2]、a[3]计算

:004F411F 59 pop ecx

:004F4120 84C0 test al, al

:004F4122 59 pop ecx

:004F4123 7528 jne 004F414D

:004F4125 8B45FC mov eax, dword ptr [ebp-04]

:004F4128 3B4508 cmp eax, dword ptr [ebp+08] //和a[4]比较

:004F412B 7520 jne 004F414D

紧跟着它要判断a[1]、a[2]、a[3]了,如下:

* Referenced by a CALL at Address:

|:004F4131

|

:004F4155 8B442404 mov eax, dword ptr [esp+04]

:004F4159 56 push esi

:004F415A BE74D35300 mov esi, 0053D374

:004F415F 8D480C lea ecx, dword ptr [eax+0C]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:

|:004F4178(C)

|

:004F4162 8B01 mov eax, dword ptr [ecx] //取出a[k],k=1,2,3

:004F4164 33D2 xor edx, edx

:004F4166 F736 div dword ptr [esi] //分别除以常数

:004F4168 85D2 test edx, edx //余数不为0则bad guy

:004F416A 7512 jne 004F417E

:004F416C 83C604 add esi, 00000004

:004F416F 83C104 add ecx, 00000004 //余数为0则继续比较

:004F4172 81FE80D35300 cmp esi, 0053D380

:004F4178 7CE8 jl 004F4162 //循环

:004F417A 32C0 xor al, al //a[1]、a[2]、a[3]均通过检查则OK

:004F417C 5E pop esi

:004F417D C3 ret

由上可知,此时的a[1]、a[2]、a[3]应为常数的整数倍,执行上面的那条除法指令时敲dd esi,可以看见常数分别为00114BCF, 0x0013D39F, 0x003687A9。至于究竟是多少倍,我们写注册机的时候可以用随机数(我用GetTickCount模拟随机数),这也说明这个软件有很多个注册码。注意到此时的a[1]、a[2]、a[3]的最高4 bit都是从a[0]中分来的一杯羹,于是我们又搞定了a[0]的7部分之中的3部分。

至此,我们已经可以写程序生成a[1]、a[2]、a[3],然后再生成a[4],现在只剩下a[0]的7部分中的两部分(即最低8bit位,分成7个bit和1个bit共两部分)和注册码的第一部分(即查表后的下标)了。

下面就是对这3个东西的判断:

:004F412D 8D45E4 lea eax, dword ptr [ebp-1C]

:004F4130 50 push eax

:004F4131 E81F000000 call 004F4155 //这里头检查a[1]、a[2]、a[3]

:004F4136 84C0 test al, al

:004F4138 59 pop ecx

:004F4139 7512 jne 004F414D

:004F413B 837DE400 cmp dword ptr [ebp-1C], 00000000 //a[0]的最低bit,即第0bit

:004F413F 750C jne 004F414D

:004F4141 837DE803 cmp dword ptr [ebp-18], 00000003 //注册码的第1部分所对应的下标

:004F4145 7506 jne 004F414D

:004F4147 837DEC02 cmp dword ptr [ebp-14], 00000002 //a[0]的第1~7bit

:004F414B 7404 je 004F4151

根据上面的比较可知,注册码的第一部分在表格"emuw"中的下标应为3,即注册码的第一个字符固定为w;

a[0]的最低8个bit用十六进制表示为0x04,结合前面我们得到的关于a[0]的信息,就可以把a[0]表示出来了。

至此可以写出注册机。(累死了,呵呵)

为保持完整性,把注册机再贴一下:

//keygen for Opera 4.0 Final

//compiled with Visual C++ 5

#include <stdio.h>

#include <windows.h>

unsigned long Sequence;

const unsigned long factor[3] = { 0x00114BCF, 0x0013D39F, 0x003687A9 };

const char table[50] = "abcdefhijkmnprstuvwxyzABCDEFHJKLMNPQRSTUVWXY345678";

int k, m;

unsigned long a[5];

long EBP_04, EBP_08, EBP_0C, EBP_p08;

void main(void)

{

printf("Key generator for Opera 4.0 final.\n");

printf("http://www.opera.com.\n");

printf("coded by dr0, 2000.6.30.\n");

for (k = 1; k <= 3; k++)

{

a[k] = factor[k-1] * (GetTickCount( ) % (0xFFFFFFFFL / factor[k-1] + 1));

}

_asm

{

push eax

push ebx

push ecx

push edx

push esi

push edi

mov ecx, offset a

XOR ESI,ESI

MOV EAX,0x03

INC EAX

MOV DWORD PTR [EBP_04],0x73657879L

MOV DWORD PTR [EBP_08],0x62616265L

MOV [EBP_0C],EAX

_004F4078: MOV EAX,ESI

PUSH 0x03

CDQ

POP EDI

IDIV EDI

INC ESI

PUSH 0x03

MOV EAX,ESI

POP EBX

MOV EDI,[EDX*4+ECX+4]

CDQ

IDIV EBX

MOV EAX,[EBP_0C]

TEST EAX,EAX

MOV EDX,[EDX*4+ECX+4]

JLE _004F40CF

MOV EBX,[EBP_04]

MOV [EBP_p08],EAX

_004F409E: MOV EAX,EBX

IMUL EAX,EBX

XOR EAX,EDI

DEC DWORD PTR [EBP_p08]

MOV EBX,EAX

JNZ _004F409E

MOV [EBP_04],EBX

_004F40AF: MOV EAX,0x02

ADD EAX,0x03

TEST EAX,EAX

JLE _004F40D4

MOV [EBP_p08],EAX

MOV EAX,[EBP_08]

_004F40BF: MOV EDI,EAX

IMUL EDI,EAX

XOR EDI,EDX

DEC DWORD PTR [EBP_p08]

MOV EAX,EDI

JNZ _004F40BF

JMP _004F40D9

_004F40CF: MOV EBX,[EBP_04]

JMP _004F40AF

_004F40D4: MOV EAX,[EBP_08]

JMP _004F40DC

_004F40D9: MOV [EBP_08],EAX

_004F40DC: CMP ESI,0x1F

JL _004F4078

AND EBX,0x00F0F0F0L

AND EAX,0x0F0F0F0FL

OR EBX,EAX

mov [ecx+4*4], ebx

pop edi

pop esi

pop edx

pop ecx

pop ebx

pop eax

}

a[0] = 0;

for (k = 1; k <= 3; k++)

{

a[0] |= a[k] >> 28;

a[0] <<= 4;

}

a[0] <<= 12;

a[0] |= 0x00000F04L;

for (k = 0; k < 4; k++)

{

a[k] &= 0x0FFFFFFFL;

}

Sequence = 0x01A26A75;

for(k = 0; k < 0x65; k++)

{

_asm

{

push eax

push ecx

push edx

MOV ECX, [Sequence]

MOV EAX, ECX

LEA EDX, [ECX+ECX]

AND AL, 0xFE

XOR EAX, EDX

MOV EDX, ECX

SHL EAX, 1

AND EDX, -0x04

XOR EAX, EDX

MOV EDX, ECX

SHL EAX, 0x02

AND EDX, -0x10

XOR EAX, EDX

MOV EDX, ECX

SHL EAX, 0x02

AND EDX, -0x40

XOR EAX, EDX

MOV EDX, ECX

SHL EAX, 0x19

AND EDX, 0x80000000

XOR EAX, EDX

SHR ECX, 1

OR EAX, ECX

MOV [Sequence], EAX

pop edx

pop ecx

pop eax

}

a[k % 5] ^= (Sequence & 0x0FFFFFFFL);

}

for (k = 3; k >= 0; k--)

{

a[k] ^= a[k+1];

}

printf("Your code is: ");

putchar('w');

for(k = 0; k < 5; k++)

{

putchar('-');

for(m = 0; m < 5; m++)

{

putchar(table[a[k] % 50L]);

a[k] /= 50L;

}

}

printf("\n");

}

(出处:赢政天下整理收藏)

相关文章:

·5StarZip 2001 破解--初学者破解入门

·DFCG破解入门第一课

·DFCG破解入门第二课

·DFCG破解入门第三课

·DFCG破解入门第四课

·DFCG破解入门第五课

·DFCG破解入门第六课

·DFCG破解入门第七课

·DFCG破解入门第八课

·DFCG破解入门第九课

相关软件:

→ 特别推荐

→ 热点TOP10

我为人人《《==》》人人为我

关于我们 |

帮助(?)

| 广告服务 |

版权声明 |

商业合作 |

发展历程 |

加盟我们 |

联系我们

Copyright © 1999-2003

winzheng.com All Rights Reserved.

繁体版:由华琦数码技术提供

Powered

by:MeSky.Net

2003-2004 年,宽带刚刚普及,下载站的文章区是许多人学电脑的第一课堂。
🕰 → 🤖 从那时到现在

当年一篇教程能让一台电脑焕然一新;今天的效率工具换成了 AI。 想知道当下最可靠的 AI 模型怎么选?看 赢政指数——我们用了 28 年的选软件严谨,现在用来评测大模型。

档案性质声明:本页为赢政天下(1998-2011)老文章的历史档案复原, 内容反映当年发表时的原貌——包括当年的技术表述、软件版本与观点,部分方法在今天已不适用或不合规,仅作互联网历史记录与研究用途, 不提供任何形式的下载。原始数据经由公共网络存档(Internet Archive)复原。发现问题请联系我们
← 返回老文章档案 · 软件史档案馆 · 赢政天下站史