破解手记 之 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