老文章档案 · 2003 年

破解 API Spy for NT v1.4

破解教程 · 算法分析 · 2003-06-09 · 作者:blowfish 人气: · 出处:赢政天下收集整理)

APIS32NT v1.4

http://www.biosys.net/apis32

这个软件加过壳,壳中似有检测调试器断点的反跟踪代码。真正入口如下:

001B:004185BD MOV ESP,[ESP+14]

001B:004185C1 POP ESI

001B:004185C2 MOV EDI,ESI

001B:004185C4 ADD ESI,000015D7

001B:004185CA PUSH 05

001B:004185CC POP ECX

001B:004185CD REPZ MOVSB

001B:004185CF POPAD

001B:004185D0 POPF

001B:004185D2 JMP 004045A0 //入口地址

这里不关心脱壳,主要是讲如何制作它的注册机。

先用bpx GetDlgItemTextA设断,会中断两次,分别读入UserName和UserKey。然后用BPM或BPR监视你输入的假注册码,发现它将注册码写入注册表之后未再对读入的注册码作进一步的判断就弹出了“错误的注册码”对话框。这说明它肯定判断的是从注册表中读出来的注册码,即先写入注册表再读出来进行判断,用RegMon也可以证实这一点。于是用bpx RegQueryValueExA设断,中断后按两下F12,就看见了判断的代码。首先是判断长度:

:0041EEEC 83EC2C sub esp, 0000002C

:0041EEEF 53 push ebx

:0041EEF0 55 push ebp

:0041EEF1 56 push esi

:0041EEF2 57 push edi

:0041EEF3 6A50 push 00000050

:0041EEF5 68C0AE4000 push 0040AEC0

* Possible StringData Ref from Data Obj ->"UserKey"

|

:0041EEFA 6898864000 push 00408698

:0041EEFF E868030000 call 0041F26C //RegQueryValueExA( )

:0041EF04 83C40C add esp, 0000000C

:0041EF07 83F811 cmp eax, 00000011 //strlen(UserKey) >= 0x11?

:0041EF0A 7D0A jge 0041EF16

:0041EF0C 33C0 xor eax, eax //bad guy

:0041EF0E 5F pop edi

:0041EF0F 5E pop esi

:0041EF10 5D pop ebp

:0041EF11 5B pop ebx

:0041EF12 83C42C add esp, 0000002C

:0041EF15 C3 ret

:0041EF16 6A2F push 0000002F

:0041EF18 68C0BE4000 push 0040BEC0

* Possible StringData Ref from Data Obj ->"UserName"

|

:0041EF1D 6888864000 push 00408688

:0041EF22 E845030000 call 0041F26C //RegQueryValueExA( )

:0041EF27 83C40C add esp, 0000000C

:0041EF2A 83F805 cmp eax, 00000005 //strlen(UserName) >= 5 ?

:0041EF2D 7D0A jge 0041EF39

:0041EF2F 33C0 xor eax, eax //bad guy

:0041EF31 5F pop edi

:0041EF32 5E pop esi

:0041EF33 5D pop ebp

:0041EF34 5B pop ebx

:0041EF35 83C42C add esp, 0000002C

:0041EF38 C3 ret

:0041EF39 6A1E push 0000001E

:0041EF3B 8D442418 lea eax, dword ptr [esp+18]

:0041EF3F 68C0BE4000 push 0040BEC0

:0041EF44 50 push eax

:0041EF45 E812070000 call 0041F65C

:0041EF4A 83C40C add esp, 0000000C

:0041EF4D 8D4C2414 lea ecx, dword ptr [esp+14]

:0041EF51 51 push ecx

:0041EF52 E845FFFFFF call 0041EE9C //strupr(UserName)

:0041EF57 8D7C2418 lea edi, dword ptr [esp+18]

:0041EF5B 83C9FF or ecx, FFFFFFFF

:0041EF5E 33C0 xor eax, eax //bad guy

:0041EF60 83C404 add esp, 00000004

:0041EF63 F2 repnz

:0041EF64 AE scasb

:0041EF65 F7D1 not ecx

:0041EF67 49 dec ecx

:0041EF68 83F905 cmp ecx, 00000005 //strlen(UserName) >= 5?

:0041EF6B 7308 jnb 0041EF75

:0041EF6D 5F pop edi

:0041EF6E 5E pop esi

:0041EF6F 5D pop ebp

:0041EF70 5B pop ebx

:0041EF71 83C42C add esp, 0000002C

:0041EF74 C3 ret

接下来它要判断注册码的第8位,即UserKey[8]。如下,显然要满足条件

(UserKey[8] ^ 0x20) + 0xF3 = 0 (溢出的进位位不考虑)

从而可知注册码的第8个字符是横杠字符"-"。

:0041EF75 8A1DC8AE4000 mov bl, byte ptr [0040AEC8] //取出UserKey[8]

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

:0041EF85 80F320 xor bl, 20

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

:0041EFCB 80C3F3 add bl, F3

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

:0041EFD0 740A je 0041EFDC

:0041EFD2 33C0 xor eax, eax //bad guy

:0041EFD4 5F pop edi

:0041EFD5 5E pop esi

:0041EFD6 5D pop ebp

:0041EFD7 5B pop ebx

:0041EFD8 83C42C add esp, 0000002C

:0041EFDB C3 ret

注意到此时bl寄存器的值应为0,这bl要作为下面这个循环的循环控制变量。该循环先将注册码的另外16个字符转换成8个字节,比如注册码是"11223344-55667788",则得到的8个字节是

0x11, 0x22,0x33,0x44,0x55,0x66,0x77,0x88

用数组a[ ]表示这8个字节,下面的循环对应的C语句就是:

char a[8];

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

{

a[k] = UserKey[k] ^ (k + 0x50);

}

由于异或运算可逆,所以已知a[k]是可以求出UserKey[k]的。

:0041EFDC BEC1AE4000 mov esi, 0040AEC1

:0041EFE1 BFD4AE4000 mov edi, 0040AED4

:0041EFE6 57 push edi

:0041EFE7 E8E0010000 call 0041F1CC //将注册码中的两个字符转换成一个字节

:0041EFEC 8ACB mov cl, bl //(cl) = k

:0041EFEE 83C404 add esp, 00000004

:0041EFF1 80C150 add cl, 50 //(cl) = k + 0x50

:0041EFF4 83C702 add edi, 00000002 //指向后续的两个字符

:0041EFF7 32C1 xor al, cl // a[k] ^= k + 0x50

:0041EFF9 FEC3 inc bl // k++

:0041EFFB 8846FF mov byte ptr [esi-01], al

:0041EFFE C60600 mov byte ptr [esi], 00

:0041F001 46 inc esi

:0041F002 80FB08 cmp bl, 08

:0041F005 72DF jb 0041EFE6 //继续循环

之后对a[ ]进行不可逆变换,得到新的8个字节,如下:

* Referenced by a CALL at Address:

|:0041F011

|

:0041F1FC 53 push ebx

:0041F1FD 55 push ebp

:0041F1FE 8B6C2410 mov ebp, dword ptr [esp+10]

:0041F202 56 push esi

:0041F203 57 push edi

:0041F204 8B7C2414 mov edi, dword ptr [esp+14]

:0041F208 33C9 xor ecx, ecx //外循环控制变量初值

:0041F20A 2BFD sub edi, ebp

:0041F20C 897C2418 mov dword ptr [esp+18], edi

:0041F210 EB04 jmp 0041F216

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

|:0041F260(C)

|

:0041F212 8B7C2418 mov edi, dword ptr [esp+18]

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

|:0041F210(U)

|

:0041F216 8D3429 lea esi, dword ptr [ecx+ebp]

:0041F219 33D2 xor edx, edx

:0041F21B B801000000 mov eax, 00000001 //累乘器的初值

:0041F220 C744241407000000 mov [esp+14], 00000007 //内循环控制变量的初值

:0041F228 8A1437 mov dl, byte ptr [edi+esi]//取出a[k]

:0041F22B 8BFA mov edi, edx

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

|:0041F24C(C)

|

:0041F22D 8BD7 mov edx, edi

:0041F22F 0FAFC2 imul eax, edx //累乘器乘以a[k]

:0041F232 3D99880000 cmp eax, 00008899

:0041F237 7E0A jle 0041F243

:0041F239 99 cdq

:0041F23A BB99880000 mov ebx, 00008899

:0041F23F F7FB idiv ebx //对0x8899求余

:0041F241 8BC2 mov eax, edx

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

|:0041F237(C)

|

:0041F243 8B542414 mov edx, dword ptr [esp+14]

:0041F247 4A dec edx //内循环控制变量减1

:0041F248 89542414 mov dword ptr [esp+14], edx

:0041F24C 75DF jne 0041F22D //内循环,求乘幂

:0041F24E 99 cdq

:0041F24F BFBB000000 mov edi, 000000BB

:0041F254 F7FF idiv edi //对0xBB求余

:0041F256 41 inc ecx

:0041F257 83F908 cmp ecx, 00000008 //8个字节均处理完?

:0041F25A 8816 mov byte ptr [esi], dl //保存余数

:0041F25C C6042900 mov byte ptr [ecx+ebp], 00

:0041F260 7CB0 jl 0041F212 //外循环,共处理8个字节

:0041F262 5F pop edi

:0041F263 5E pop esi

:0041F264 5D pop ebp

:0041F265 5B pop ebx

:0041F266 C3 ret

上述循环可等价表示如下:

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

{

temp = 1L;

for(j = 7; j > 0; j--)

{

temp *= a[k];

if (temp > 0x00008899L)

{

temp %= 0x00008899L;

}

}

a[k] = temp % 0x000000BBL;

}

对于这个不可逆的变换,我们可以根据变换之后的8字节的值来用穷举的方法猜出变换之前的8个字节的值,最坏的情况只需要猜(256 * 8)次即可,当然也可能无解。优化一下的话最坏只要猜256次。

紧跟着它要从UserName得到一个新的长为8的串UserString。下面的处理等价于C语句:

char index = 0;

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

{

UserString[k] = UserName[index++];

index %= NameLen;

}

即如果UserName的长度大于等于8,则新串UserString就是UserName的前8个字符,否则把UserName串重复多次,然后取整个串的前8个字符即可。

:0041F016 8D7C241C lea edi, dword ptr [esp+1C]

:0041F01A 83C9FF or ecx, FFFFFFFF

:0041F01D 33C0 xor eax, eax

:0041F01F 83C408 add esp, 00000008

:0041F022 F2 repnz

:0041F023 AE scasb

:0041F024 F7D1 not ecx

:0041F026 2BF9 sub edi, ecx

:0041F028 33ED xor ebp, ebp

:0041F02A 8BD1 mov edx, ecx

:0041F02C 8BF7 mov esi, edi

:0041F02E BFDEAE4000 mov edi, 0040AEDE

:0041F033 C1E902 shr ecx, 02

:0041F036 F3 repz

:0041F037 A5 movsd

:0041F038 8BCA mov ecx, edx

:0041F03A 83E103 and ecx, 00000003

:0041F03D F3 repz

:0041F03E A4 movsb

:0041F03F 8D7C2414 lea edi, dword ptr [esp+14]

:0041F043 83C9FF or ecx, FFFFFFFF

:0041F046 F2 repnz

:0041F047 AE scasb

:0041F048 F7D1 not ecx

:0041F04A 49 dec ecx

:0041F04B 80F908 cmp cl, 08 //判UserName的长度

:0041F04E 884C2410 mov byte ptr [esp+10], cl

:0041F052 732F jnb 0041F083 //大于等于8则取前8个字符

:0041F054 8B542410 mov edx, dword ptr [esp+10] //否则拼也要拼8个字符出来

:0041F058 8D7C2414 lea edi, dword ptr [esp+14]

:0041F05C 81E2FF000000 and edx, 000000FF

:0041F062 83C9FF or ecx, FFFFFFFF

:0041F065 81C2DEAE4000 add edx, 0040AEDE

:0041F06B F2 repnz

:0041F06C AE scasb

:0041F06D F7D1 not ecx

:0041F06F 2BF9 sub edi, ecx

:0041F071 8BC1 mov eax, ecx

:0041F073 8BF7 mov esi, edi

:0041F075 8BFA mov edi, edx

:0041F077 C1E902 shr ecx, 02

:0041F07A F3 repz

:0041F07B A5 movsd

:0041F07C 8BC8 mov ecx, eax

:0041F07E 83E103 and ecx, 00000003

:0041F081 F3 repz

:0041F082 A4 movsb

最后就是利用UserString[ ]和UserKey[ ]进行判断。这也是一个循环。

:0041F028 33ED xor ebp, ebp

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

:0041F083 C605E6AE400000 mov byte ptr [0040AEE6], 00

:0041F08A B9D4AE4000 mov ecx, 0040AED4

:0041F08F BE08000000 mov esi, 00000008 //循环控制变量k = 8

:0041F094 8A01 mov al, byte ptr [ecx] //取出a[k]

:0041F096 3C20 cmp al, 20 //和0x20相比,分两种情况处理

:0041F098 730E jnb 0041F0A8

:0041F09A 33D2 xor edx, edx

:0041F09C 25FF000000 and eax, 000000FF

:0041F0A1 8A510A mov dl, byte ptr [ecx+0A] //取出UserString[k]

:0041F0A4 0BD0 or edx, eax //与a[k]相或

:0041F0A6 EB0C jmp 0041F0B4

:0041F0A8 33D2 xor edx, edx

:0041F0AA 25FF000000 and eax, 000000FF

:0041F0AF 8A510A mov dl, byte ptr [ecx+0A] //取出UserString[k]

:0041F0B2 33D0 xor edx, eax //与a[k]异或

:0041F0B4 03EA add ebp, edx //累加到ebp中

:0041F0B6 41 inc ecx //下一个字节

:0041F0B7 4E dec esi //k--;

:0041F0B8 75DA jne 0041F094 //继续循环

:0041F0BA 33C0 xor eax, eax

:0041F0BC 5F pop edi

:0041F0BD 85ED test ebp, ebp //累加和为0吗?

:0041F0BF 5E pop esi

:0041F0C0 5D pop ebp

:0041F0C1 0F94C0 sete al //为0则OK,否则bad guy

:0041F0C4 5B pop ebx

:0041F0C5 83C42C add esp, 0000002C

:0041F0C8 C3 ret

上面这个循环可等价为:

long ebp = 0

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

{

if (a[k] < 0x20)

{

ebp += UserString[k] | a[k];

}

else

{

ebp += UserString[k] ^ a[k];

}

}

注册码正确与否的标准为:

if (ebp)

{

printf("Wrong key");

}

else

{

printf("good guy");

}

最后这个循环初看之下也比较难求逆,因为其中的或运算是不可逆的。但实际上我们只要让(a[k] < 0x20)这个条件永远不会满足即可避开这个或运算,此时上面这个循环简化为可逆的异或变换:

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

{

ebp += UserString[k] ^ a[k];

}

这样要想让累加和ebp为0,只要满足a[k] = UserString[k](k = 0, ..., 7)即可,由于UserString[k]是由可显示的用户名得来的,所以肯定满足(a[k] >= 0x20)。至此得到注册机如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

void main(void)

{

char UserName[128];

char UserString[8];

char a[8];

int index, len;

char k, j;

signed long temp, guess;

char SuccessCounter;

do

{

printf("Input your name(at least 5 chars):");

gets(UserName);

len = strlen(UserName);

} while(len < 5);

strupr(UserName);

index = 0;

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

{

a[k] = UserString[k] = UserName[index++];

index %= len;

}

//下面用穷举求出变换前的8个字节

SuccessCounter = 0;

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

{

for(guess = 0; guess <= 255; guess++)

{

temp = 1L;

for(j = 7; j > 0; j--)

{

temp *= guess;

if (temp > 0x00008899L)

{

temp %= 0x00008899L;

}

}

if ((temp % 0x000000BBL) == a[k])

{

SuccessCounter++;

a[k] = guess;

break;

}

}

}

//判断8个字节是否全部穷举成功

if (SuccessCounter != 8)

{

printf("Guess failed.\n");

exit(-1);

}

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

{

a[k] ^= (k + 0x50);

}

printf("Your registration key is: ");

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

{

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

void main(void)

{

char UserName[128];

char UserString[8];

char a[8];

int index, len;

char k, j;

signed long temp, guess;

char SuccessCounter;

do

{

printf("Input your name(at least 5 chars):");

gets(UserName);

len = strlen(UserName);

} while(len < 5);

strupr(UserName);

index = 0;

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

{

a[k] = UserString[k] = UserName[index++];

index %= len;

}

//下面用穷举求出变换前的8个字节

SuccessCounter = 0;

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

{

for(guess = 0; guess <= 255; guess++)

{

temp = 1L;

for(j = 7; j > 0; j--)

{

temp *= guess;

if (temp > 0x00008899L)

{

temp %= 0x00008899L;

}

}

if ((temp % 0x000000BBL) == a[k])

{

SuccessCounter++;

a[k] = guess;

break;

}

}

}

//判断8个字节是否全部穷举成功

if (SuccessCounter != 8)

{

printf("Guess failed.\n");

exit(-1);

}

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

{

a[k] ^= (k + 0x50);

}

printf("Your registration key is: ");

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

{

printf("%02X", a[k] & 0xFF);

}

printf("-");

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

{

printf("%02X", a[k] & 0xFF);

}

printf("\n");

}

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

相关文章:

·ASProtect的加密算法初步分析

·MD5算法研究

·Active Ebook Compiler的注册算法

·语音界面2.0算法分析及注册机源码!

·简单算法——小护士 V1.2

·侠客系统修改器1.21破解手记--算法分析

·中文拨号上网计时计费器 V4.12注册算法分析

·加密算法之RSA算法

·加密算法之DES算法

·加密算法之ElGamal算法

相关软件:

·胜新出纳-支票打印软件 V4.0X 的注册码算法

·胜新现代办公百宝箱 V5.01 的注册码算法

·胜新信函宝典-信封打印软件 V2.02 注册码的算法

·胜新通用企业计划管理系统的注册码算法

·共享软件加密算法库 V3.2

·操作系统内存分配银行家算法模拟系统 XP

→ 特别推荐

→ 热点TOP10

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

关于我们 |

帮助(?)

| 广告服务 |

版权声明 |

商业合作 |

发展历程 |

加盟我们 |

联系我们

Copyright © 1999-2003

winzheng.com All Rights Reserved.

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

Powered

by:MeSky.Net

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

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

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