• 标 题:KeyboardTest 2.2
  • 作 者:calf
  • 时 间:2003/08/26 09:48am
  • 链 接:http://bbs.pediy.com

破解手记 之 KeyboardTest

作者:  Calf
E-mail:  ox_calf@eyou.com
时间:  2003年08月12日

软件名称:  PassMark KeyboardTest
软件版本:  v2.2
运行平台:  Win9x/Me/NT/2000/XP
软件简介:  KeyboardTest是一个专门用来检测键盘的软件,它可以支持对107键盘的测试。
未注册版限制:  软件只能使用30天
下载地址:  http://download.eyou.com.cn/scgi/detail.pl?
s_id=7988

破解人:  Calf
破解工具:  TRW2000 v1.23、W32Dasm黄金修正版、MASM32 v7.0
破解时间:  2003年08月12日
难易程度:  Easy( )  Medium(X)  Hard( )  Pro( )

--------------------<<<<<=====Begin=====>>>>>--------------------

   这几日没什么事,正好手边有这么个软件,便拿来练练手。
   这个软件注册时要求输入用户名(以下均称为name)和注册码(以下均称为key),点击“Continue”按钮进行注册,注册失败将弹出对话框。若不输入name或key,则不进行注册,继续以未注册版使用。
   首先用Fileinfo和Language2000看了看,还不错,没有加壳,用VC写的。然后用W32Dasm反汇编大概看看,便换用TRW2000进行动态跟踪。
   任意输入name和key,按Crtl+N激活TRW2000,设置断点bpx hmemcpy,按F5返回程序,点击“Continue”,被TRW2000拦截,禁止断点后,用pmodule命令回到程序代码中:
:00405DB8 68C8000000              push 000000C8
//这里eax=nameLength(表示name的长度),[esi]=name
//注:“=”表示执行此句前的值,“->”表示执行此句后被赋予的值,下同
//需要注意的是,如果输入的name长度超过99(63h)位,则这里仅保留了前99位,这一点在编写注册机时应注意
:00405DBD 55                      push ebp
:00405DBE 680A040000              push 0000040A
:00405DC3 53                      push ebx
:00405DC4 FFD7                    call edi
:00405DC6 56                      push esi
:00405DC7 E897110000              call 00406F63
:00405DCC 55                      push ebp
:00405DCD E891110000              call 00406F63
:00405DD2 83C408                  add esp, 00000008
:00405DD5 8D4C2414                lea ecx, dword ptr [esp+14]
:00405DD9 51                      push ecx
...
//为节省篇幅,省去若干不重要的代码
:00405E62 8BFE                    mov edi, esi    //[esi]=name
:00405E64 83C9FF                  or ecx, FFFFFFFF
:00405E67 33C0                    xor eax, eax
:00405E69 83C408                  add esp, 00000008
:00405E6C F2                      repnz
:00405E6D AE                      scasb
:00405E6E F7D1                    not ecx
:00405E70 49                      dec ecx    //ecx->nameLength
:00405E71 C644241301              mov [esp+13], 01
:00405E76 83F901                  cmp ecx, 00000001
:00405E79 761D                    jbe 00405E98    //若name的长度不比1大,则跳过
//后面可知,跳过的这段正是验证注册码,故长度小于2的name是不能注册的,这一点在编写注册机时应注意
:00405E7B 8BFD                    mov edi, ebp    //[ebp]=key
:00405E7D 83C9FF                  or ecx, FFFFFFFF
:00405E80 F2                      repnz
:00405E81 AE                      scasb
:00405E82 F7D1                    not ecx
:00405E84 49                      dec ecx    //ecx->keyLength
:00405E85 83F905                  cmp ecx, 00000005
:00405E88 760E                    jbe 00405E98    //若key的长度不比5大,则跳过
:00405E8A 55                      push ebp    //将key入栈
:00405E8B 56                      push esi    //将name入栈
:00405E8C E8AFFAFFFF              call 00405940
//这一句,若用F10则直接跳出注册失败的对话框,显然,验证注册码就在这个call里面,
:00405E91 83C408                  add esp, 00000008
:00405E94 88442413                mov byte ptr [esp+13], al

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00405E79(C), :00405E88(C)
|
:00405E98 56                      push esi
:00405E99 E8C5100000              call 00406F63
...
   下面来跟踪00405E8C所调用的子程序:
//这个子程序虽然有点长,但算法不难,只要稍微分析就能明白
* Referenced by a CALL at Address:
|:00405E8C  
|
:00405940 51                      push ecx    //[ebp]=key,[esi]=name,并已先后入栈
:00405941 8B442408                mov eax, dword ptr [esp+08]    //[eax]->name
:00405945 53                      push ebx
:00405946 55                      push ebp
:00405947 56                      push esi
:00405948 57                      push edi
:00405949 50                      push eax
:0040594A C744241401000000        mov [esp+14], 00000001
//记住这里,这可能就是注册成败的判据,当然,随着压栈和出栈过程,它与栈顶的距离可能会有所变化
:00405952 E8BC140000              call 00406E13
//这个子程序可以跟进去看看,很短,功能是将name中的大写字母转换为小写,这一点在编写注册机时应注意
:00405957 8B6C2420                mov ebp, dword ptr [esp+20]    //[ebp]->key
:0040595B 83C9FF                  or ecx, FFFFFFFF
:0040595E 8BFD                    mov edi, ebp
:00405960 33C0                    xor eax, eax
:00405962 F2                      repnz
:00405963 AE                      scasb
:00405964 F7D1                    not ecx    //ecx->keyLength+1
:00405966 51                      push ecx
:00405967 6A01                    push 00000001
:00405969 E866180000              call 004071D4
:0040596E 6A10                    push 00000010
:00405970 6A01                    push 00000001
:00405972 8BF0                    mov esi, eax
:00405974 E85B180000              call 004071D4
:00405979 8BCE                    mov ecx, esi
:0040597B 83C414                  add esp, 00000014
:0040597E 8BD8                    mov ebx, eax
:00405980 8BC5                    mov eax, ebp
:00405982 2BCD                    sub ecx, ebp

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040598C(C)
|
:00405984 8A10                    mov dl, byte ptr [eax]
:00405986 881401                  mov byte ptr [ecx+eax], dl
:00405989 40                      inc eax
:0040598A 84D2                    test dl, dl
:0040598C 75F6                    jne 00405984
//前面这几句把key复制了一遍,看来程序要对key下手了
//=========================================================
:0040598E 8A06                    mov al, byte ptr [esi]    //[esi]=key
:00405990 8BEE                    mov ebp, esi    //[ebp]->key
:00405992 84C0                    test al, al
:00405994 743C                    je 004059D2

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059D0(C)
|
:00405996 3C3F                    cmp al, 3F
:00405998 7505                    jne 0040599F
:0040599A C6066C                  mov byte ptr [esi], 6C    //用'l'替换'?'
:0040599D EB2B                    jmp 004059CA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405998(C)
|
:0040599F 3C23                    cmp al, 23
:004059A1 7505                    jne 004059A8
:004059A3 C60669                  mov byte ptr [esi], 69    //用'i'替换'#'
:004059A6 EB22                    jmp 004059CA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059A1(C)
|
:004059A8 3C2D                    cmp al, 2D
:004059AA 7505                    jne 004059B1
:004059AC C6066F                  mov byte ptr [esi], 6F    //用'_'替换'o'
:004059AF EB19                    jmp 004059CA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059AA(C)
|
:004059B1 3C2B                    cmp al, 2B
:004059B3 7505                    jne 004059BA
:004059B5 C60630                  mov byte ptr [esi], 30    //用'0'替换'+'
:004059B8 EB10                    jmp 004059CA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059B3(C)
|
:004059BA 3C3D                    cmp al, 3D
:004059BC 7505                    jne 004059C3
:004059BE C60640                  mov byte ptr [esi], 40    //用'@'替换'='
:004059C1 EB07                    jmp 004059CA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059BC(C)
|
:004059C3 3C25                    cmp al, 25
:004059C5 7503                    jne 004059CA
:004059C7 C60631                  mov byte ptr [esi], 31    //用'1'替换'%'

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040599D(U), :004059A6(U), :004059AF(U), :004059B8(U), :004059C1(U)
|:004059C5(C)
|
:004059CA 8A4601                  mov al, byte ptr [esi+01]
:004059CD 46                      inc esi
:004059CE 84C0                    test al, al
:004059D0 75C4                    jne 00405996
//=========================================================
//上面这段简单明了,它对key中的字符进行了一次转换,把一些相对特殊的字符用普通字符代替
//我认为写注册机时无需将这段的逆过程写进去,因为转换前后都是可输入字符,没有本质区别
//为了便于说明,将经过这次转换后的key称为key1,以后的key2、key3等类似

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405994(C)
|
//=========================================================
:004059D2 8A4500                  mov al, byte ptr [ebp+00]    //[ebp]=key1
:004059D5 8BCD                    mov ecx, ebp    //[ecx]->key1
:004059D7 84C0                    test al, al
:004059D9 742E                    je 00405A09

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405A07(C)
|
:004059DB 8A01                    mov al, byte ptr [ecx]
:004059DD 3C30                    cmp al, 30
:004059DF 7C08                    jl 004059E9
:004059E1 3C39                    cmp al, 39
:004059E3 7F04                    jg 004059E9
:004059E5 2C30                    sub al, 30    //在'0'和'9'之间则减去30h,变为0到9
:004059E7 EB16                    jmp 004059FF

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004059DF(C), :004059E3(C)
|
:004059E9 3C40                    cmp al, 40
:004059EB 7C08                    jl 004059F5
:004059ED 3C5B                    cmp al, 5B
:004059EF 7F04                    jg 004059F5
:004059F1 2C36                    sub al, 36    //在'@'和'['之间则减去36h,变为0ah到25h
//注:ASCII表中,'@'在'A'前一个,'['在'Z'后一个
:004059F3 EB0A                    jmp 004059FF

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004059EB(C), :004059EF(C)
|
:004059F5 3C61                    cmp al, 61
:004059F7 7C08                    jl 00405A01
:004059F9 3C7A                    cmp al, 7A
:004059FB 7F04                    jg 00405A01
:004059FD 2C3B                    sub al, 3B    //在'a'和'z'之间则减去3bh,变为26h到3fh

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004059E7(U), :004059F3(U)
|
:004059FF 8801                    mov byte ptr [ecx], al

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004059F7(C), :004059FB(C)
|
:00405A01 8A4101                  mov al, byte ptr [ecx+01]
:00405A04 41                      inc ecx
:00405A05 84C0                    test al, al
:00405A07 75D2                    jne 004059DB
//=========================================================
//上面这段程序将key1中的ASCII字符转换成0~3fh的16进制数,产生key2
//从key到key2,其长度保持不变
//这个转换很简单,其逆过程也很容易写出来,继续往下看

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004059D9(C)
|
//=========================================================
:00405A09 8B7C241C                mov edi, dword ptr [esp+1C]    //[edi]->key
:00405A0D 83C9FF                  or ecx, FFFFFFFF
:00405A10 33C0                    xor eax, eax
:00405A12 33F6                    xor esi, esi
:00405A14 33D2                    xor edx, edx
:00405A16 F2                      repnz
:00405A17 AE                      scasb
:00405A18 F7D1                    not ecx
:00405A1A 49                      dec ecx    //ecx->keyLength
:00405A1B 83E904                  sub ecx, 00000004
:00405A1E 785E                    js 00405A7E    //key的长度小于4则跳转,正常情况下不会跳转

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405A7C(C)
|
:00405A20 83FE0D                  cmp esi, 0000000D
//这是个长度界限,结合下面的程序可知,经过这次转换产生的key3最多有15个字节
//在这里说明一下,[ebp]=key2,edx为其偏移量;[ebx]=key3,esi为其偏移量
//值得注意的是,经过key1到key2的转换,正常情况下,key2的每个字节均应在0到3fh之间,即高两位为0
:00405A23 7359                    jnb 00405A7E
:00405A25 8A4C2A01                mov cl, byte ptr [edx+ebp+01]    //取出key2的第2、6、10、14等字节
:00405A29 8A042A                  mov al, byte ptr [edx+ebp]    //取出key2的第1、5、9、13等字节
:00405A2C C0E106                  shl cl, 06
:00405A2F 243F                    and al, 3F
:00405A31 8B7C241C                mov edi, dword ptr [esp+1C]
:00405A35 02C8                    add cl, al    //此时cl低6位为0,al高2位为0
:00405A37 880C1E                  mov byte ptr [esi+ebx], cl    //存入key3的第1、4、7、10等字节
:00405A3A 8A4C2A01                mov cl, byte ptr [edx+ebp+01]    //取出key2的第2、6、10、14等字节
:00405A3E 8A442A02                mov al, byte ptr [edx+ebp+02]    //取出key2的第3、7、11、15等字节
:00405A42 46                      inc esi
:00405A43 42                      inc edx
:00405A44 C0F902                  sar cl, 02
:00405A47 80E10F                  and cl, 0F
:00405A4A C0E004                  shl al, 04
:00405A4D 02C8                    add cl, al    //此时cl高4位为0,al低4位为0
:00405A4F 880C1E                  mov byte ptr [esi+ebx], cl    //存入key3的第2、5、8、11等字节
:00405A52 8A4C2A01                mov cl, byte ptr [edx+ebp+01]    //取出key2的第3、7、11、15等字节
:00405A56 8A442A02                mov al, byte ptr [edx+ebp+02]    //取出key2的第4、8、12、16等字节
:00405A5A 46                      inc esi
:00405A5B 42                      inc edx
:00405A5C C0F904                  sar cl, 04
:00405A5F 80E103                  and cl, 03
:00405A62 83C202                  add edx, 00000002
:00405A65 C0E002                  shl al, 02
:00405A68 02C8                    add cl, al    //此时cl高6位为0,al低2位为0
:00405A6A 33C0                    xor eax, eax
:00405A6C 880C1E                  mov byte ptr [esi+ebx], cl    //存入key3的第3、6、9、12等字节
:00405A6F 83C9FF                  or ecx, FFFFFFFF
:00405A72 46                      inc esi
:00405A73 F2                      repnz
:00405A74 AE                      scasb
:00405A75 F7D1                    not ecx
:00405A77 83C1FB                  add ecx, FFFFFFFB    //ecx->keyLength-4
:00405A7A 3BD1                    cmp edx, ecx
:00405A7C 7EA2                    jle 00405A20
//=========================================================
//上面这段似乎挺麻烦,不过稍加分析就可理解
//key2的每个字节均应在0到3fh之间,即只有低6位是有效的,4个key2字节便有24位有效位,正好占满3字节
//上面的程序正是将key2中相邻的4字节压缩为3字节,去掉无用的永0位
//显然,它的逆过程就是将key3中相邻的3字节扩充为4字节,其中插入8个0位

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00405A1E(C), :00405A23(C)
|
//=========================================================
:00405A7E 33FF                    xor edi, edi    //edi->0
:00405A80 33F6                    xor esi, esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405AA9(C)
|
:00405A82 8BC6                    mov eax, esi    //eax->0
:00405A84 B930000000              mov ecx, 00000030
:00405A89 99                      cdq
:00405A8A F7F9                    idiv ecx
:00405A8C 52                      push edx
:00405A8D E88EFEFFFF              call 00405920
//eax->看似没有规律的大数,这个call的子程序很短,有点像随机数发生器,不过种子应该是固定的
:00405A92 99                      cdq
:00405A93 B90D000000              mov ecx, 0000000D
:00405A98 83C404                  add esp, 00000004
:00405A9B F7F9                    idiv ecx    //作了个除法,edx->余数,在0到0dh之间
:00405A9D 8A041E                  mov al, byte ptr [esi+ebx]    //[ebx]->key3,esi是偏移量
:00405AA0 02C2                    add al, dl    //给key3的每个字节加上了一个0到0dh之间的数
:00405AA2 88041E                  mov byte ptr [esi+ebx], al
:00405AA5 46                      inc esi
:00405AA6 83FE10                  cmp esi, 00000010
:00405AA9 72D7                    jb 00405A82
//=========================================================
//这段程序对key3作了一次转换产生key4,其中调用的子程序我没看太懂,不过好像也没什么影响。
//程序段给key3的每个字节加上了一个0到0dh之间的数,而被加的数与key3、key、name等都没什么关系,应该是一组固定的数,那就不用管这些数是怎么产生的,只要记下具体数值就可以了
//我用了两组不同的name和key,发现每次所加之数的确是固定的,依次是16进制的:
//0c,07,06, 03,06,09, 0b,05,05, 01,07,06, 07,0b,09
//到此,最初的key经历4次转换成为key4,每次转换过程都已经分析清楚并且可以写出逆过程

//下面连续几次比较,很有可能是在判断注册码的正确性
//此时edi=0,[ebx]=key4,而随着栈顶的变化,在0040594A处的esp+14现在移动到了esp+10,其值一直为1未变
:00405AAB 807B0105                cmp byte ptr [ebx+01], 05
:00405AAF 7404                    je 00405AB5
:00405AB1 897C2410                mov dword ptr [esp+10], edi
//若key4第2字节不为05,则令[esp+10]为0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405AAF(C)
|
:00405AB5 807B0301                cmp byte ptr [ebx+03], 01
:00405AB9 7404                    je 00405ABF
:00405ABB 897C2410                mov dword ptr [esp+10], edi
//若key4第4字节不为01,则令[esp+10]为0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405AB9(C)
|
:00405ABF 8B4308                  mov eax, dword ptr [ebx+08]
:00405AC2 83F8FF                  cmp eax, FFFFFFFF    //ffffffffh=-1
:00405AC5 7412                    je 00405AD9
:00405AC7 3DE8030000              cmp eax, 000003E8    //000003e8h=1,000
:00405ACC 7C07                    jl 00405AD5
:00405ACE 3D80969800              cmp eax, 00989680    //00989680h=10,000,000
:00405AD3 7E04                    jle 00405AD9

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405ACC(C)
|
:00405AD5 897C2410                mov dword ptr [esp+10], edi
//若key4第9~12字节组成的16进制数(低字节在前,高字节在后)不为-1,且不介于1,000和10,000,000之间,则令[esp+10]为0

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00405AC5(C), :00405AD3(C)
|
//key转换完了,轮到name了
:00405AD9 8B7C2418                mov edi, dword ptr [esp+18]    //[edi]->name
:00405ADD 83C9FF                  or ecx, FFFFFFFF
:00405AE0 33C0                    xor eax, eax
:00405AE2 33F6                    xor esi, esi    //esi->0
:00405AE4 33D2                    xor edx, edx
:00405AE6 F2                      repnz
:00405AE7 AE                      scasb
:00405AE8 F7D1                    not ecx
:00405AEA 49                      dec ecx    //ecx->nameLength
:00405AEB 85C9                    test ecx, ecx
:00405AED 7E1C                    jle 00405B0B    //name长度为0则跳转,正常情况下不会跳转

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405B09(C)
|
:00405AEF 8B7C2418                mov edi, dword ptr [esp+18]    //[edi]->name
:00405AF3 83C9FF                  or ecx, FFFFFFFF
:00405AF6 0FBE043A                movsx eax, byte ptr [edx+edi]    //[edi]=name,edx为偏移量
:00405AFA 0FAFC2                  imul eax, edx
:00405AFD 03F0                    add esi, eax    //name的每一位进行计算,结果累加到esi
:00405AFF 33C0                    xor eax, eax
:00405B01 42                      inc edx
:00405B02 F2                      repnz
:00405B03 AE                      scasb
:00405B04 F7D1                    not ecx
:00405B06 49                      dec ecx    //ecx->nameLength
:00405B07 3BD1                    cmp edx, ecx
:00405B09 7CE4                    jl 00405AEF
//这段代码不需要看懂,写注册机时要根据name来推算正确的key,那么关于name的运算不需求逆,可以直接使用

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405AED(C)
|
:00405B0B 33C9                    xor ecx, ecx    //ecx->0
:00405B0D 81E6FF000080            and esi, 800000FF    //保留esi最低8位和符号位
:00405B13 8A4B04                  mov cl, byte ptr [ebx+04]    //cl->key4第5字节
:00405B16 7908                    jns 00405B20
//这里的jns指与800000FF作与运算后的esi最高位为0时跳转
//下面3句使esi高6位全部为1,而ecx高6位全部为0,二者不可能相等
//如果name中有ASCII码大于127的字符(如汉字等),则有可能使esi最高位为1,这一点在编写注册机时应注意
:00405B18 4E                      dec esi
:00405B19 81CE00FFFFFF            or esi, FFFFFF00
:00405B1F 46                      inc esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405B16(C)
|
:00405B20 3BF1                    cmp esi, ecx    //esi=name经转换得到16进制数
:00405B22 7408                    je 00405B2C
:00405B24 C744241000000000        mov [esp+10], 00000000
//若key4第5字节与name经转换得到的数值不相等,则令[esp+10]为0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405B22(C)
|
:00405B2C 837B08FF                cmp dword ptr [ebx+08], FFFFFFFF
:00405B30 7408                    je 00405B3A
:00405B32 C744241000000000        mov [esp+10], 00000000
//若key4第9~12字节组成的16进制数(低字节在前,高字节在后)不为-1(即ff ff ff ff),则令[esp+10]为0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405B30(C)
|
//=========================================================
* Reference To: USER32.LoadCursorA, Ord:01B9h
                                 |
:00405B3A 8B35A8014100            mov esi, dword ptr [004101A8]
:00405B40 68027F0000              push 00007F02
:00405B45 6A00                    push 00000000
:00405B47 FFD6                    call esi    //USER32.LoadCursorA

* Reference To: USER32.SetCursor, Ord:024Dh
                                 |
:00405B49 8B3D70014100            mov edi, dword ptr [00410170]
:00405B4F 50                      push eax
:00405B50 FFD7                    call edi    //USER32.SetCursor
:00405B52 68F4010000              push 000001F4

* Reference To: KERNEL32.Sleep, Ord:0339h
                                 |
:00405B57 FF15EC004100            Call dword ptr [004100EC]    //KERNEL32.Sleep
:00405B5D 68007F0000              push 00007F00
:00405B62 6A00                    push 00000000
:00405B64 FFD6                    call esi    //USER32.LoadCursorA
:00405B66 50                      push eax
:00405B67 FFD7                    call edi    //USER32.SetCursor
//=========================================================
//上面这段对于验证注册码好像没什么贡献
:00405B69 8B742410                mov esi, dword ptr [esp+10]
:00405B6D 83FE01                  cmp esi, 00000001
:00405B70 753A                    jne 00405BAC    //[esp+10]不等于1则跳转
//[esp+10]的初始值是1,程序执行中有几次将其变为0的可能,将其全部列于此处
//1.若key4第2字节不为05,则令[esp+10]为0
//2.若key4第4字节不为01,则令[esp+10]为0
//3.若key4第5字节与name经转换得到的数值不相等,则令[esp+10]为0
//4.若key4第9~12字节不全为ff,则令[esp+10]为0

//=========================================================
//只有[esp+10]等于1时才执行下面这段,这段将key4和name分别复制到(004188D8+68)h和(004188D8+03)h处
:00405B72 8B15D8884100            mov edx, dword ptr [004188D8]
:00405B78 8BC3                    mov eax, ebx    //[ebx]=key4
:00405B7A 83C268                  add edx, 00000068
:00405B7D 8B08                    mov ecx, dword ptr [eax]
:00405B7F 890A                    mov dword ptr [edx], ecx
:00405B81 8B4804                  mov ecx, dword ptr [eax+04]
:00405B84 894A04                  mov dword ptr [edx+04], ecx
:00405B87 8B4808                  mov ecx, dword ptr [eax+08]
:00405B8A 894A08                  mov dword ptr [edx+08], ecx
:00405B8D 8B4C2418                mov ecx, dword ptr [esp+18]    //[ecx]->name
:00405B91 8B400C                  mov eax, dword ptr [eax+0C]
:00405B94 89420C                  mov dword ptr [edx+0C], eax
:00405B97 8B15D8884100            mov edx, dword ptr [004188D8]
:00405B9D 83C203                  add edx, 00000003

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405BA8(C)
|
:00405BA0 8A01                    mov al, byte ptr [ecx]
:00405BA2 41                      inc ecx
:00405BA3 8802                    mov byte ptr [edx], al
:00405BA5 42                      inc edx
:00405BA6 84C0                    test al, al
:00405BA8 75F6                    jne 00405BA0
:00405BAA EB1F                    jmp 00405BCB
//=========================================================

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405B70(C)
|
//=========================================================
//只有[esp+10]不等于1时才执行下面这段
:00405BAC A1007B4100              mov eax, dword ptr [00417B00]
:00405BB1 8B0DBC884100            mov ecx, dword ptr [004188BC]
:00405BB7 6A00                    push 00000000
:00405BB9 68F0224000              push 004022F0
:00405BBE 50                      push eax

* Possible StringData Ref from Data Obj ->"KEYERROR"    //注册失败的信息
                                 |
:00405BBF 6870434100              push 00414370
:00405BC4 51                      push ecx

* Reference To: USER32.DialogBoxParamA, Ord:009Eh
                                 |
:00405BC5 FF15BC014100            Call dword ptr [004101BC]    //USER32.DialogBoxParamA
这个call将弹出注册失败的对话框,可见[esp+10]确实注册成败的标记
//=========================================================

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405BAA(U)
|
:00405BCB 55                      push ebp
:00405BCC E892130000              call 00406F63
:00405BD1 53                      push ebx
:00405BD2 E88C130000              call 00406F63
:00405BD7 83C408                  add esp, 00000008
:00405BDA 8BC6                    mov eax, esi
:00405BDC 5F                      pop edi
:00405BDD 5E                      pop esi
:00405BDE 5D                      pop ebp
:00405BDF 5B                      pop ebx
:00405BE0 59                      pop ecx
:00405BE1 C3                      ret
   如果在key4的几次比较前将内存中数值修改为对应的值,则可注册成功,下面把验证注册码的算法总结一下:
   1.name经过转换计算产生一个单字节16进制数,记为na1
   这里要注意name的长度应大于1且小于100,并且大写字母要转为小写字母。
   2.key->key1
   key : '?', '#', '_', '+', '=', '%'
   key1: 'l', 'i', 'o', '0', '@', '1'
   3.key1->key2
   key1: '0', ..., '9', '@', ..., '[', 'a', ..., 'z'
   减去: 30h, ..., 30h, 36h, ..., 36h, 3bh, ..., 3bh
   key2: 00h, ..., 09h, 0ah, ..., 25h, 26h, ..., 3fh
   4.key2->key3
   顺次取key2中相邻的4个字节,将高两位的0去掉,重新组合为3个字节
   5.key3->key4
   key3的每个字节依次加上:
   0ch,07h,06h, 03h,06h,09h, 0bh,05h,05h, 01h,07h,06h, 07h,0bh,09h
   6.key4需满足的条件(???表示可为任意数值):
   ???,05h,???, 01h,na1,???, ???,???,ffh, ffh,ffh,ffh, ???,???,???
   好了,算法分析完了,只要将其逆过程写成注册机就行了。下面给出我写的注册机的部分源代码。

//=========================================================
.data
r_ErrMess1db "User名长度不能小于2!",0
r_ErrMess2db "您输入的User有非法字符,请更换。",0
r_UserNamedb 100 dup (0)
r_KeyCode3db ?,0feh,?, 0feh,00h,?, ?,?,0fah, 0feh,0f8h,0f9h,0
r_KeyCode2db 20 dup (0)
r_KeyCode1db 20 dup (0)

//=========================================================
.code
RegProc
;在调用此程序前,主程序已经将输入的name的地址赋予eax
;在此程序结束时,应将要输出的key或错误信息的地址赋予eax

;初始化变量
xorebx,ebx
xorecx,ecx
xoredx,edx

;将输入的user name小写化,存入r_UserName
;user name的长度存入ecx
;user name仅保留了99位
leaebx,r_UserName
line01:
movdl,[eax+ecx]
cmpdl,0
jeline03
cmpdl,'A'
jlline02
cmpdl,'Z'
jgline02
adddl,20h
line02:
mov[ebx+ecx],dl
incecx
jmpline01

;user name的长度不能小于2
line03:
cmpecx,02
jllineErrShort

;对user name进行运算,求出特征码存入r_KeyCode3中
xoreax,eax
xoredx,edx
xorebx,ebx
leaedi,r_UserName
line04:
movsxeax,byte ptr [edi+edx]
imuleax,edx
addebx,eax
xoreax,eax
incedx
cmpedx,ecx
jlline04
andebx,800000ffh
jslineErrLong
leaeax,r_KeyCode3
subbl,06
mov[eax+04],bl

;将key code 3转换为key code 2并保存
xoreax,eax
xorecx,ecx
leaebp,r_KeyCode3
xoredx,edx
leaesi,r_KeyCode2
xorebx,ebx
line05:
moveax,[ebp+edx]
addedx,03
movecx,3fh
andecx,eax
mov[esi+ebx],cl
incebx
sareax,06
movecx,3fh
andecx,eax
mov[esi+ebx],cl
incebx
sareax,06
movecx,3fh
andecx,eax
mov[esi+ebx],cl
incebx
sareax,06
movecx,3fh
andecx,eax
mov[esi+ebx],cl
incebx
cmpedx,0ch
jneline05

;将key code 2转换为key code 1并保存
leaebx,r_KeyCode1
leaecx,r_KeyCode2
xoredx,edx
line06:
moval,[ecx+edx]
cmpal,09h
jgline07
addal,30h
jmpline09
line07:
cmpal,25h
jgline08
addal,36h
jmpline09
line08:
addal,3bh
line09:
mov[ebx+edx],al
incedx
cmpedx,10h
jlline06
xoreax,eax
mov[ebx+edx],al

;======================================
;取出最终的key
leaeax,r_KeyCode1
jmplineEnd

;user name的长度小于2错误
lineErrShort:
leaeax,r_ErrMess1
jmplineEnd

;user name的特征码太大
lineErrLong:
leaeax,r_ErrMess2
jmplineEnd

;结束
lineEnd:
ret

RegEndp