-
-
加密与解密随书光盘习题Harlequin 的Crackme #1破解学习过程[分享]
-
发表于: 2006-5-13 17:06 8263
-
【软件名称】Harlequin 的Crackme #1【下载地址】
【应用平台】Win2000 【软件大小】24KB
【软件限制】 Encrypted Libraries - Crypto knowledge a must
【破解声明】 这个破解对我来说有点难度,我学习了Lighning写的Solution之后,自己跟踪并详细调试了一遍后,感觉收获很大,我写出学习过程,我想自己总结一下,对我这样的菜鸟也有一定帮助吧。
【破解工具】OllyDbg
【软件简介】
程序中给出的简介:Harlequin's Crackme No.1
This program was written for two
reasons:
Firstly to give me a chance to
learn some new things in assembler
Secondly to show how easy it is
to have a very effective protection
scheme.
When you crack this program the
'Check it' button will display a
message box with a serial number and
an email address. Please write to me
at the address and tell me how you
did it, include the serial number in
your mail.
In a normal program the .dll would
contain several functions such as the
save function. Upon registering the
user would recieve a key which when
entered would allow the .dll to be
decrypted by clicking unlock.
Note though that this key is created
using the unique serial so would be
specific to the one machine
in order to crack the program fully
you should be able to register it on
any machine.
I have included the source code for
your interest. I don't think it will
make your job any easier.
Good Luck
【分析过程】
为了尊重Lighning的劳动成果,我先将他的Solution放在下面:
"
A review of Harlequin's Crackme #1
I recently found out about the CrackMe site. I've always loved encryption so Harlequin's
crackme stuck out to me from the difficulty level it was given and being it dealt with
encrypted data. First off, it should be known that the crackme can only be cracked via brute
force. I'll prove this below but a crackme that wants a serial should have a way to have
the serial generated.
The crackme provides source code to it which helps in figuring out what is going on. It
appears from a disassembly of the exe that the source provided does match the exe and has
not been tampered with (although I did not completely compare everything, I compared the
vital areas like the encryption code).
When you run the crackme you get a nice little dialog with a box for a serial, 2 buttons,
and a unique ID. Does the unique ID look familar? It should, it is your Windows serial
number read from the registry. This is not a problem, quite a few serial's use some form of
a unique ID so the serial is slightly harder to figure out. The next step, after entering a
serial, is to press the Unlock button then hit the Check button to see if the serial was
valid. It could have been done in one button but that isn't a problem at all either.
When you press the Unlock button, the following things happen. It first checks to see if it
can open up Coded.dll for read access. If it can then it tries to open up TheKey.dll for
write access with CREATE_ALWAYS set so TheKey.dll is always created. It should be apparent
that Coded.dll is going to be modified and written to TheKey.dll. Viewing Coded.dll in a hex
editor is of no use as it is encrypted.
The next step after opening the files is to clear out the key buffer (256 bytes) and then
read the serial from the box into memory. The following code (converted from assembly to C)
is then ran on the values.
//Key is a char[256], WinKey is a char[128]
for(Counter = 0; Counter <= strlen(Key); Counter++)
Key[Counter] = Key[Counter] - WinKey[Counter];
This means that the key can not be any longer than the windows key. There is no checks or
code ran to make the key upper case. The windows key is always upper case of course and it
does include the - (or dashes in the Windows 98 key). According to the readme that comes
with the crackme, the serial used to encrypt the dll was generated via a random password
generator, was 24 characters long and included upper and lower case letters with numbers.
No problem so far, it is just getting a combination key so that the serial relies on the
windows serial.
The next step is to generate 2 checksums that are used in verifying the serial. Both
checksums are only 1 byte in size. The following code demonstrates how the checksums are
generated.
CheckSum = 0;
CheckSum1 = 0;
for(Counter = 0; Counter <= 256; Counter++)
{
//C doesn't have a ROR command. it would be a substitute for the asm ror
CheckSum1 = CheckSum1 + Key[Counter]; //bytes are the end of the serial are null
CheckSum1 = ROR(CheckSum1, Key[Counter]);
CheckSum = ROR(CheckSum, Key[Counter]);
CheckSum = CheckSum ^ Key[Counter];
}
Now it has 2 checksums based on the serial. Simple checksums but can be useful depending on
the checks or usage. Now comes the fun, or the headache. The Coded.dll is read into memory
in 1k (1024 byte) chunks, some math done on each byte, the new block written to TheKey.dll
and another block processed. We can use the header beginning to see if the correct key is
found. It is not hard to verify if TempByte is correct for the first few positions as the
readme said the dll was never modified. The difficulty is figuring out Key, CheckSum, and
CheckSum1, or is it?
CheckSum, CheckSum1, and Key never change during the encryption of the data. The FilePos
value does change but it can be easily calculated. The buffer that is operated on is done in
1024 byte chunks. If the DLL was never modified then we should be able to brute force the
CheckSum, CheckSum1, and Key values based on the first 1024 bytes (as the header has common
values between DLLs). The other nice thing is that no byte relies on another byte so we can
crack 1 byte at a time to reveal the key. This being all said, lets find some stuff that is
common between DLLs for the first byte of the Key. The Key (according to the readme) is 24
characters long. Byte 0, 24, 48, and 72 are all the same in the normal DLLs. 0x4D, 0x40,
0x00, and 0x21. CheckSum, CheckSum1, and Key[0] (as FilePos % KeyLen is 0 for these bytes)
are all bytes. so 256 * 256 * 256 = 1,677,216 possibles. Loop thru until the encrypted
counterparts (0x44, 0x8C, 0x71, and 0xC0) become the correct values. If all the values come
out then we found CheckSum, CheckSum1 and the first value for Key[0]. From there we can go
thru other known bytes in the file (in the header) and figure out the rest of the Key values
and decrypt the DLL. Once we know the values we can work backwards and make a KeyGen that
will generate these values we want to always decrypt the DLL. The following is some code I
wrote to figure out the value for CheckSum, CheckSum1, and Key[0]
CheckSum=0;
CheckSum1=0;
Key=0;
while(1)
{
while(1)
{
while(1)
{
if(Decrypt((char)0x44, CheckSum, CheckSum1, Key, 0) == 0x4D)
{
if(Decrypt((char)0x8C, CheckSum, CheckSum1, Key, 24) == 0x00)
{
if(Decrypt((char)0x71, CheckSum, CheckSum1, Key, 48) == 0x00)
{
if(Decrypt((char)0xC0, CheckSum, CheckSum1, Key, 72) == 0x21)
{
sprintf(Values, "CheckSum: %d\nCheckSum1: %d\nKey: %d", CheckSum, CheckSum1, Key);
MessageBox(NULL, Values, NULL, MB_OK);
}
}
}
}
Key++;
if(Key == 0) break;
}
CheckSum1++;
if(CheckSum1 == 0) break;
}
CheckSum++;
if(CheckSum == 0) break;
}
char Decrypt(char CheckByte, char CheckSum, char CheckSum1, char Key, short int FilePos)
{
char Result;
_asm
{
//rearranged some so it isn't as complicated. still same execution order for modifying
//dl though
//get the values to use
mov dl, CheckByte
mov cl, CheckSum1
//xor against CheckSum
xor dl, CheckSum
//ror against CheckSum1
ror dl, cl
//get the FilePos
mov ax, FilePos
mov bx, ax
mov cx, ax
//manipulate FilePos
and al, ah
add cl, ch
ror bl, 3
//do screwy things to dl with new FilePos values
rol dl, cl
add dl, cl
xor dl, bl
xor dl, al
//finish up dl decrpytion
neg dl
xor dl, Key
//and return it
mov Result, dl
}
return Result;
}
On an AMD Athlon 900, that code takes alittle bit less than 2 seconds to execute all the
way thru. The catch? You never get a CheckSum, CheckSum1, and Key[0] that satisfies all of
the values. What does this mean? Who knows, the DLL could be corrupt or modified, we could
have an invalid key length, or the encryption and decryption code don't line up. Either way,
you can't crack it, or so it seems.
I wrote to Harlequin and to +Q about the crackme. Come to find out, the key is 27 characters
long and not 24. Hmm, with this new knowledge, let's try some changes to the above code. I
changed the loop above to read positions 0, 27, 54, and 81 with new hex values as needed for
encrypted and wanted decrypted values. I ran it. I got a hit. I got 32 hits actually but the
only diff was CheckSum1 (all rotated the value the same amount). The results was a wanted
value of the following
Checksum: 2
Checksum1: 3 (or any value that causes a ror of 3)
Key[0]: 117
So, knowing a value for Checksum and Checksum1, we'll go about obtaining the other key
values. I proceeded to modify the loop for all 26 other positions, hard coded Checksum and
Checksum1 and came up with the following.
Key[0]: 117
Key[1]: 65
Key[2]: 86
Key[3]: 57
Key[4]: 118
Key[5]: 102
Key[6]: 113
Key[7]: 97
Key[8]: 67
Key[9]: 106
Key[10]: 74
Key[11]: 49
Key[12]: 107
Key[13]: 122
Key[14]: 56
Key[15]: 110
Key[16]: 74
Key[17]: 89
Key[18]: 118
Key[19]: 72
Key[20]: 102
Key[21]: 104
Key[22]: 109
Key[23]: 120
Key[24]: 86
Key[25]: 101
Key[26]: 76
I had modified my brute forcing loop so that it read from a few arrays and checked the
positions for me. From this I only checked 2 groups in the file. It managed to not produce
a few of the values though. Those positions (as near as I can tell) refer to data positions
that could hold values and do apparently that differ between dll's here and there. I decided
then to run thru the loops but to check against a different area of the EXE (preferably
within the top 1024 bytes). Unless the DLL is modified, it has just a few headers, as a
result, most dll's have a large blank area after the PE header. It is in there that I found
the other values. Now, after looking at all that data, we find that the key is
"uAV9vfqaCjJ1kz8nJYvHfhmxVeL", Checksum has to be 2, and Checksum1 has to be a value that
causes the ROR to rotate by 3. Looking over how Checksum and Checksum1 are created, this
isn't a problem. This means we just have to write a keygen that will take that key, and
generate a valid serial key from it.
Attached is a keygen for Harlequin's #1 crackme. It was fun to crack. The difficulty is that
the readme contained invalid information about the key which resulted in not being able to
find a valid key until I contacted Harlequin about it. Here is a difficulty though, the key
is subtracted from the windows key to obtain the valid key. This means that you have
possibly untypable characters as the serial. The only way to get around this is to edit the
registry to remove the windows key temporarily, run the app, and enter the valid key found
above.
Lightning
"
下面是我的学习过程.
运行该程序,会弹出一个"Error"的对话框,"ProductKey".通过程序代码发现,该程序运行首先进行下面的初始化工作,这一部分也可以参考作者的源代码,但我为了方便都给反汇编的代码了.
0040109D > \6A 69 PUSH 69 ; /RsrcName = 105.
0040109F . FF35 75254000 PUSH DWORD PTR DS:[402575] ; |hInst = NULL
004010A5 . E8 2C040000 CALL <JMP.&USER32.LoadIconA> ; \LoadIconA
004010AA . 85C0 TEST EAX,EAX
004010AC . 74 10 JE SHORT crypt.004010BE
004010AE . 50 PUSH EAX ; /lParam
004010AF . 6A 69 PUSH 69 ; |wParam = 69
004010B1 . 68 80000000 PUSH 80 ; |Message = WM_SETICON
004010B6 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004010B9 . E8 24040000 CALL <JMP.&USER32.SendMessageA> ; \SendMessageA
004010BE > 68 7D254000 PUSH crypt.0040257D ; /pHandle = crypt.0040257D
004010C3 . 6A 01 PUSH 1 ; |Access = KEY_QUERY_VALUE
004010C5 . 6A 00 PUSH 0 ; |Reserved = 0
004010C7 . 68 4A244000 PUSH crypt.0040244A ; |Subkey = "Software\Microsoft\Windows\CurrentVersion"
004010CC . 68 02000080 PUSH 80000002 ; |hKey = HKEY_LOCAL_MACHINE
004010D1 . E8 18040000 CALL <JMP.&ADVAPI32.RegOpenKeyExA> ; \RegOpenKeyExA
004010D6 . 85C0 TEST EAX,EAX
004010D8 . 74 1B JE SHORT crypt.004010F5
004010DA . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004010DC . 68 96244000 PUSH crypt.00402496 ; |Title = "Error!!!"
004010E1 . 68 7F244000 PUSH crypt.0040247F ; |Text = "Couldn't open reg key!"
004010E6 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004010E9 . E8 E2030000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
004010EE . 5E POP ESI
004010EF . 5F POP EDI
004010F0 . 5B POP EBX
004010F1 . C9 LEAVE
004010F2 . C2 1000 RETN 10
004010F5 > 68 81254000 PUSH crypt.00402581 ; /pBufSize = crypt.00402581
004010FA . 68 9F244000 PUSH crypt.0040249F ; |Buffer = crypt.0040249F
004010FF . 6A 00 PUSH 0 ; |pValueType = NULL
00401101 . 6A 00 PUSH 0 ; |Reserved = NULL
00401103 . 68 74244000 PUSH crypt.00402474 ; |ValueName = "ProductKey"
00401108 . FF35 7D254000 PUSH DWORD PTR DS:[40257D] ; |hKey = 0
0040110E . E8 D5030000 CALL <JMP.&ADVAPI32.RegQueryValueExA> ; \RegQueryValueExA
00401113 . 85C0 TEST EAX,EAX
00401115 . 74 1B JE SHORT crypt.00401132
00401117 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401119 . 68 96244000 PUSH crypt.00402496 ; |Title = "Error!!!"
0040111E . 68 74244000 PUSH crypt.00402474 ; |Text = "ProductKey"
00401123 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
00401126 . E8 A5030000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
0040112B . 5E POP ESI
0040112C . 5F POP EDI
0040112D . 5B POP EBX
0040112E . C9 LEAVE
0040112F . C2 1000 RETN 10
00401132 > 68 9F244000 PUSH crypt.0040249F ; /Text = ""
00401137 . 6A 66 PUSH 66 ; |ControlID = 66 (102.)
00401139 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040113C . E8 89030000 CALL <JMP.&USER32.SetDlgItemTextA> ; \SetDlgItemTextA
00401141 . FF35 7D254000 PUSH DWORD PTR DS:[40257D] ; /hKey = NULL
00401147 . E8 A8030000 CALL <JMP.&ADVAPI32.RegCloseKey> ; \RegCloseKey
0040114C . 68 00204000 PUSH crypt.00402000 ; /Text = "CrackMe1 bY Harlequin 7/2000"
00401151 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401154 . E8 6B030000 CALL <JMP.&USER32.SetWindowTextA> ; \SetWindowTextA
00401159 . 68 1E204000 PUSH crypt.0040201E ; /Text = "Harlequin's Crackme No.1
This program was written for two
reasons:
Firstly to give me a chance to
learn some new things in assembler
Secondly to show how easy it is
to have a very effective protection
scheme.
When you cra"...
0040115E . 6A 65 PUSH 65 ; |ControlID = 65 (101.)
00401160 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401163 . E8 62030000 CALL <JMP.&USER32.SetDlgItemTextA> ; \SetDlgItemTextA
00401168 . 85C0 TEST EAX,EAX
0040116A .^ 0F84 F5FEFFFF JE crypt.00401065
00401170 . 5E POP ESI
00401171 . 5F POP EDI
00401172 . 5B POP EBX
00401173 . C9 LEAVE
00401174 . C2 1000 RETN 10
程序首先读取注册表,获取你的Windows的ProductKey. 如果读取失败,则会弹出这样的一个对话框了.
由于,我安装的Windows 2000是盗版的.注册表中没有该项.所以会弹出该对话框了.由于在后面的跟踪发现,没有该项并不会影响,而且更简化了程序,所以,我没有手动修改注册表.如果你的电脑上有该项的话,我建议你将其删除(HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ProductKey)
通过拜读Lighning写的Solution,发现Serial必须是27位长度的.
输入任意的27位的Serial,点击"Unlock it",很容易定位到程序代码如下:
004011D8 |. 53 PUSH EBX
004011D9 |. 52 PUSH EDX
004011DA |. 51 PUSH ECX
004011DB |. 56 PUSH ESI
004011DC |. 57 PUSH EDI
004011DD |. 55 PUSH EBP
004011DE |. 54 PUSH ESP
004011DF |. 6A 00 PUSH 0 ; /hTemplateFile = NULL
004011E1 |. 6A 00 PUSH 0 ; |Attributes = 0
004011E3 |. 6A 03 PUSH 3 ; |Mode = OPEN_EXISTING
004011E5 |. 6A 00 PUSH 0 ; |pSecurity = NULL
004011E7 |. 6A 00 PUSH 0 ; |ShareMode = 0
004011E9 |. 68 00000080 PUSH 80000000 ; |Access = GENERIC_READ
004011EE |. 68 2A254000 PUSH crypt.0040252A ; |FileName = "Coded.dll"
004011F3 |. E8 A8020000 CALL <JMP.&KERNEL32.CreateFileA> ; \CreateFileA
004011F8 |. 83F8 FF CMP EAX,-1
004011FB |. 75 1F JNZ SHORT crypt.0040121C
004011FD |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004011FF |. 68 2A254000 PUSH crypt.0040252A ; |Title = "Coded.dll"
00401204 |. 68 34254000 PUSH crypt.00402534 ; |Text = "Couldn't open file!"
00401209 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040120C |. E8 BF020000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401211 |. 5C POP ESP
00401212 |. 5D POP EBP
00401213 |. 5F POP EDI
00401214 |. 5E POP ESI
00401215 |. 59 POP ECX
00401216 |. 5A POP EDX
00401217 |. 5B POP EBX
00401218 |. C9 LEAVE
00401219 |. C2 0400 RETN 4
0040121C |> A3 872A4000 MOV DWORD PTR DS:[402A87],EAX
00401221 |. 6A 00 PUSH 0 ; /hTemplateFile = NULL
00401223 |. 68 80000000 PUSH 80 ; |Attributes = NORMAL
00401228 |. 6A 02 PUSH 2 ; |Mode = CREATE_ALWAYS
0040122A |. 6A 00 PUSH 0 ; |pSecurity = NULL
0040122C |. 6A 00 PUSH 0 ; |ShareMode = 0
0040122E |. 68 00000040 PUSH 40000000 ; |Access = GENERIC_WRITE
00401233 |. 68 1F254000 PUSH crypt.0040251F ; |FileName = "TheKey.dll"
00401238 |. E8 63020000 CALL <JMP.&KERNEL32.CreateFileA> ; \CreateFileA
0040123D |. 83F8 FF CMP EAX,-1
00401240 |. 75 2A JNZ SHORT crypt.0040126C
00401242 |. FF35 872A4000 PUSH DWORD PTR DS:[402A87] ; /hObject = 0000016C (window)
00401248 |. E8 3B020000 CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
0040124D |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
0040124F |. 68 1F254000 PUSH crypt.0040251F ; |Title = "TheKey.dll"
00401254 |. 68 34254000 PUSH crypt.00402534 ; |Text = "Couldn't open file!"
00401259 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040125C |. E8 6F020000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401261 |. 5C POP ESP
00401262 |. 5D POP EBP
00401263 |. 5F POP EDI
00401264 |. 5E POP ESI
00401265 |. 59 POP ECX
00401266 |. 5A POP EDX
00401267 |. 5B POP EBX
00401268 |. C9 LEAVE
00401269 |. C2 0400 RETN 4
0040126C |> A3 8B2A4000 MOV DWORD PTR DS:[402A8B],EAX
00401271 |. C705 8F2A4000>MOV DWORD PTR DS:[402A8F],0
0040127B |. 6A 00 PUSH 0 ; /pFileSizeHigh = NULL
0040127D |. FF35 872A4000 PUSH DWORD PTR DS:[402A87] ; |hFile = 0000016C (window)
00401283 |. E8 24020000 CALL <JMP.&KERNEL32.GetFileSize> ; \GetFileSize
00401288 |. A3 932A4000 MOV DWORD PTR DS:[402A93],EAX
0040128D |. BF 85254000 MOV EDI,crypt.00402585 ; 输入的注册码
00401292 |. B9 40000000 MOV ECX,40
00401297 |. 33C0 XOR EAX,EAX
00401299 |. FC CLD
0040129A |. F3:AB REP STOS DWORD PTR ES:[EDI]
0040129C |. BE 85254000 MOV ESI,crypt.00402585 ; 输入的注册码
004012A1 |. C706 00000000 MOV DWORD PTR DS:[ESI],0
004012A7 |. 68 00010000 PUSH 100 ; /Count = 100 (256.)
004012AC |. 68 85254000 PUSH crypt.00402585 ; |Buffer = crypt.00402585
004012B1 |. 6A 67 PUSH 67 ; |ControlID = 67 (103.)
004012B3 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004012B6 |. E8 21020000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
004012BB |. 85C0 TEST EAX,EAX
004012BD |. 75 35 JNZ SHORT crypt.004012F4
004012BF |. FF35 872A4000 PUSH DWORD PTR DS:[402A87] ; /hObject = 0000016C (window)
004012C5 |. E8 BE010000 CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
004012CA |. FF35 8B2A4000 PUSH DWORD PTR DS:[402A8B] ; /hObject = 00000168 (window)
004012D0 |. E8 B3010000 CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
004012D5 |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004012D7 |. 68 6D254000 PUSH crypt.0040256D ; |Title = "Oops..."
004012DC |. 68 4E254000 PUSH crypt.0040254E ; |Text = "You need to enter a Key first!"
004012E1 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004012E4 |. E8 E7010000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
004012E9 |. 5C POP ESP
004012EA |. 5D POP EBP
004012EB |. 5F POP EDI
004012EC |. 5E POP ESI
004012ED |. 59 POP ECX
004012EE |. 5A POP EDX
004012EF |. 5B POP EBX
004012F0 |. C9 LEAVE
004012F1 |. C2 0400 RETN 4
004012F4 |> 8BC8 MOV ECX,EAX
004012F6 |. 33C0 XOR EAX,EAX
004012F8 |. BE 85254000 MOV ESI,crypt.00402585 ; 输入的注册码
004012FD |. BF 9F244000 MOV EDI,crypt.0040249F
00401302 |> 3BC1 /CMP EAX,ECX
00401304 |. 74 0D |JE SHORT crypt.00401313
00401306 |. 8A1E |MOV BL,BYTE PTR DS:[ESI]
00401308 |. 8A17 |MOV DL,BYTE PTR DS:[EDI]
0040130A |. 2ADA |SUB BL,DL ; 注册码与Windows的ProductKey相减
0040130C |. 881E |MOV BYTE PTR DS:[ESI],BL
0040130E |. 46 |INC ESI
0040130F |. 47 |INC EDI
00401310 |. 40 |INC EAX
00401311 |.^ EB EF \JMP SHORT crypt.00401302
00401313 |> C606 00 MOV BYTE PTR DS:[ESI],0
00401316 |. C605 86264000>MOV BYTE PTR DS:[402686],0 ; CheckSum 设置初始值
0040131D |. C605 85264000>MOV BYTE PTR DS:[402685],0 ; CheckSum1 设置初始值
00401324 |. BE 85254000 MOV ESI,crypt.00402585 ; 输入的注册码
00401329 |. 33DB XOR EBX,EBX
0040132B |. BA 00010000 MOV EDX,100
00401330 |> AC /LODS BYTE PTR DS:[ESI]
00401331 |. 0005 85264000 |ADD BYTE PTR DS:[402685],AL
00401337 |. 8AC8 |MOV CL,AL
00401339 |. D2CB |ROR BL,CL
0040133B |. D20D 85264000 |ROR BYTE PTR DS:[402685],CL
00401341 |. 32D8 |XOR BL,AL
00401343 |. 4A |DEC EDX
00401344 |.^ 75 EA \JNZ SHORT crypt.00401330 ; 这段循环计算CheckSum1的值
00401346 |. 881D 86264000 MOV BYTE PTR DS:[402686],BL ; 计算CheckSum
0040134C |. BF 85254000 MOV EDI,crypt.00402585 ; 输入的注册码
00401351 |. 32C0 XOR AL,AL
00401353 |. 66:B9 0001 MOV CX,100
00401357 |. FC CLD
00401358 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
0040135A |. 4F DEC EDI
0040135B |. 81EF 85254000 SUB EDI,crypt.00402585 ; 输入的注册码
00401361 |. 893D 972A4000 MOV DWORD PTR DS:[402A97],EDI
00401367 |> 6A 00 /PUSH 0 ; /pOverlapped = NULL
00401369 |. 68 9B2A4000 |PUSH crypt.00402A9B ; |pBytesRead = crypt.00402A9B
0040136E |. 68 00040000 |PUSH 400 ; |BytesToRead = 400 (1024.)
00401373 |. 68 87264000 |PUSH crypt.00402687 ; |Buffer = crypt.00402687
00401378 |. FF35 872A4000 |PUSH DWORD PTR DS:[402A87] ; |hFile = 0000016C (window)
0040137E |. E8 FF000000 |CALL <JMP.&KERNEL32.ReadFile> ; \ReadFile
00401383 |. C705 9F2A4000>|MOV DWORD PTR DS:[402A9F],0
0040138D |. 8B0D 9B2A4000 |MOV ECX,DWORD PTR DS:[402A9B]
00401393 |. 0BC9 |OR ECX,ECX
00401395 |. 75 35 |JNZ SHORT crypt.004013CC
00401397 |. FF35 872A4000 |PUSH DWORD PTR DS:[402A87] ; /hObject = 0000016C (window)
0040139D |. E8 E6000000 |CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
004013A2 |. FF35 8B2A4000 |PUSH DWORD PTR DS:[402A8B] ; /hObject = 00000168 (window)
004013A8 |. E8 DB000000 |CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
004013AD |. 6A 00 |PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004013AF |. 68 48254000 |PUSH crypt.00402548 ; |Title = "Done."
004013B4 |. 68 48254000 |PUSH crypt.00402548 ; |Text = "Done."
004013B9 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004013BC |. E8 0F010000 |CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
004013C1 |. 5C |POP ESP
004013C2 |. 5D |POP EBP
004013C3 |. 5F |POP EDI
004013C4 |. 5E |POP ESI
004013C5 |. 59 |POP ECX
004013C6 |. 5A |POP EDX
004013C7 |. 5B |POP EBX
004013C8 |. C9 |LEAVE
004013C9 |. C2 0400 |RETN 4
004013CC |> 51 |PUSH ECX
004013CD |. 33D2 |XOR EDX,EDX
004013CF |. A1 8F2A4000 |MOV EAX,DWORD PTR DS:[402A8F] ; File Position
004013D4 |. 8B1D 972A4000 |MOV EBX,DWORD PTR DS:[402A97] ; 注册码长度
004013DA |. F7F3 |DIV EBX
004013DC |. 8A82 85254000 |MOV AL,BYTE PTR DS:[EDX+402585] ; EDX-> Key[the FilePos % the length of the serial]
004013E2 |. 50 |PUSH EAX
004013E3 |. 66:A1 8F2A400>|MOV AX,WORD PTR DS:[402A8F]
004013E9 |. 66:8BC8 |MOV CX,AX
004013EC |. 66:8BD8 |MOV BX,AX
004013EF |. 02CD |ADD CL,CH
004013F1 |. 8B2D 9F2A4000 |MOV EBP,DWORD PTR DS:[402A9F]
004013F7 |. 81C5 87264000 |ADD EBP,crypt.00402687 ; 逐字节取Coded.dll文件内容
004013FD |. 8A55 00 |MOV DL,BYTE PTR SS:[EBP]
00401400 |. 3215 86264000 |XOR DL,BYTE PTR DS:[402686] ; CheckSum
00401406 |. 860D 85264000 |XCHG BYTE PTR DS:[402685],CL
0040140C |. D2CA |ROR DL,CL ; CL -> CheckSum1
0040140E |. 860D 85264000 |XCHG BYTE PTR DS:[402685],CL
00401414 |. D2C2 |ROL DL,CL ; CL -> Filepos
00401416 |. 02D1 |ADD DL,CL
00401418 |. C0CB 03 |ROR BL,3
0040141B |. 32D3 |XOR DL,BL
0040141D |. 22C4 |AND AL,AH
0040141F |. 32D0 |XOR DL,AL
00401421 |. F6DA |NEG DL
00401423 |. 58 |POP EAX
00401424 |. 32D0 |XOR DL,AL
00401426 |. 8855 00 |MOV BYTE PTR SS:[EBP],DL ; 逐字节解密后的内容存放到BUFFER中
00401429 |. FF05 8F2A4000 |INC DWORD PTR DS:[402A8F]
0040142F |. FF05 9F2A4000 |INC DWORD PTR DS:[402A9F]
00401435 |. 59 |POP ECX
00401436 |. 49 |DEC ECX
00401437 |.^ 75 93 |JNZ SHORT crypt.004013CC
00401439 |. 6A 00 |PUSH 0 ; /pOverlapped = NULL
0040143B |. 68 9B2A4000 |PUSH crypt.00402A9B ; |pBytesWritten = crypt.00402A9B
00401440 |. FF35 9F2A4000 |PUSH DWORD PTR DS:[402A9F] ; |nBytesToWrite = 0
00401446 |. 68 87264000 |PUSH crypt.00402687 ; |Buffer = crypt.00402687
0040144B |. FF35 8B2A4000 |PUSH DWORD PTR DS:[402A8B] ; |hFile = 00000168 (window)
00401451 |. E8 44000000 |CALL <JMP.&KERNEL32.WriteFile> ; \WriteFile
00401456 |. DB05 8F2A4000 |FILD DWORD PTR DS:[402A8F]
0040145C |. DB05 932A4000 |FILD DWORD PTR DS:[402A93]
00401462 |. DEF9 |FDIVP ST(1),ST
00401464 |. DB05 A32A4000 |FILD DWORD PTR DS:[402AA3]
0040146A |. DEC9 |FMULP ST(1),ST
0040146C \.^ E9 F6FEFFFF \JMP crypt.00401367
00401471 . 5C POP ESP
00401472 . 5D POP EBP
00401473 . 5F POP EDI
00401474 . 5E POP ESI
00401475 . 59 POP ECX
00401476 . 5A POP EDX
00401477 . 5B POP EBX
00401478 . C9 LEAVE
00401479 . C2 0400 RETN 4
程序分析并不难,但要想得到正确的注册码,则比较麻烦了.总结一下加密的思路,当你点击"Unlock it" 按钮后,程序,自动根据你输入的注册码,进行下面的工作.:
1 计算CheckSum,存放在402686单元中,计算CheckSum1,存放在402685单元中.
2 一次读取1K个字节Coded.dll文件中的内容,然后逐字节进行运算,写到TheKey.dll文件中去.
用户点击"Check it"按钮后,程序如下:
0040117B |. 53 PUSH EBX
0040117C |. 52 PUSH EDX
0040117D |. 68 1F254000 PUSH crypt.0040251F ; /FileName = "TheKey.dll"
00401182 |. E8 2B030000 CALL <JMP.&KERNEL32.LoadLibraryA> ; \LoadLibraryA
00401187 |. 85C0 TEST EAX,EAX
00401189 |. 74 2F JE SHORT crypt.004011BA
0040118B |. A3 79254000 MOV DWORD PTR DS:[402579],EAX
00401190 |. 68 43244000 PUSH crypt.00402443 ; /ProcNameOrOrdinal = "GetKey"
00401195 |. FF35 79254000 PUSH DWORD PTR DS:[402579] ; |hModule = 06750000 (TheKey)
0040119B |. E8 EE020000 CALL <JMP.&KERNEL32.GetProcAddress> ; \GetProcAddress
004011A0 |. 85C0 TEST EAX,EAX
004011A2 |. 74 16 JE SHORT crypt.004011BA
004011A4 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
004011A7 |. FFD0 CALL EAX ; 调用正确生成的TheKey.dll动态链接库中的GetKey函数,弹出成功对话框。
004011A9 |. FF35 79254000 PUSH DWORD PTR DS:[402579] ; /hLibModule = 06750000
004011AF |. E8 C8020000 CALL <JMP.&KERNEL32.FreeLibrary> ; \FreeLibrary
004011B4 |. 5A POP EDX
004011B5 |. 5B POP EBX
004011B6 |. C9 LEAVE
004011B7 |. C2 0400 RETN 4
004011BA |> 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
004011BC |. 68 38244000 PUSH crypt.00402438 ; |Title = "Try again!"
004011C1 |. 68 F6234000 PUSH crypt.004023F6 ; |Text = "Sorry this function is not available in the unregistered version!"
004011C6 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
004011C9 |. E8 02030000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
004011CE |. 5A POP EDX
004011CF |. 5B POP EBX
004011D0 |. C9 LEAVE
004011D1 \. C2 0400 RETN 4
通过上面分析,只有解密生成的TheKey.dll正确,才能注册成功,在Lighning的Solution中写到
If the DLL was never modified then we should be able to brute force the
CheckSum, CheckSum1, and Key values based on the first 1024 bytes (as the header has common
values between DLLs)
因为所有DLL的前面DOS HEAD内容都是一样的(If the DLL was never modified).而作者说了,DLL没有加密.
按照上面的思路,参考Solution,我写出注册程序如下:
首先计算CheckSum, CheckSum1, and Key[0],3个变量,只需要有3个方程就可以了。因此我们将Coded.dll文件中的第0,27,54这3个字节解密后的数据必须与一般的DLL(以MFCO42D.DLL为例进行查看,解密后必须等于0x4D,0,0)相应的位置相等,根据此进行求解。
#include "stdio.h"
#include "string.h"
#include "windows.h"
char CheckByte=27;
char Decrypt(char CheckByte, char CheckSum, char CheckSum1, char Key, short int FilePos)
{
char Result;
_asm
{
//rearranged some so it isn't as complicated. still same execution order for modifying
//dl though
//get the values to use
mov dl, CheckByte
mov cl, CheckSum1
//xor against CheckSum
xor dl, CheckSum
//ror against CheckSum1
ror dl, cl
//get the FilePos
mov ax, FilePos
mov bx, ax
mov cx, ax
//manipulate FilePos
and al, ah
add cl, ch
ror bl, 3
//do screwy things to dl with new FilePos values
rol dl, cl
add dl, cl
xor dl, bl
xor dl, al
//finish up dl decrpytion
neg dl
xor dl, Key
//and return it
mov Result, dl
}
return Result;
}
void main()
{
char CheckSum=0;
char CheckSum1=0;
char Key[25];
Key[0]=0;
while(1)
{
while(1)
{
while(1)
{
if(Decrypt((char)0x44, CheckSum, CheckSum1, Key[0], 0) == 0x4D)
{
if(Decrypt((char)0xCF, CheckSum, CheckSum1, Key[0], 27) == 0x00)
{
if(Decrypt((char)0xE0, CheckSum, CheckSum1, Key[0], 54) == 0x00)
{
printf("CheckSum: %d\nCheckSum1: %d\nKey %d: %d\n", CheckSum, CheckSum1, 0,Key[0]);
}
}
}
Key[0]++;
if(Key[0] == 0) break;
}
CheckSum1++;
if(CheckSum1 == 0) break;
}
CheckSum++;
if(CheckSum == 0) break;
}
}
这样可以得到很多解,从解中观察,如果要求 CheckSum与CheckSum1大于0,则可取CheckSum=2, CheckSum1=3,Key[0]=117;
下面确定了CheckSum与CheckSum1之后,只需要比较前面27个字节文件内容就可以确定正确的Key了.
for(i=1;i<=26;i++)
{
CheckSum=2;
CheckSum1=0x3;
Key[i]=0;
//de[i]为Coded.dll文件中读取的头27个字节的数据, ade[i]为MFCO42D.DLL文件中读取的头27个字节的数据.
while(1)
{
if(Decrypt((char)de[i], CheckSum, CheckSum1, Key[i], i) == ade[i])
{
break;
}
Key[i]++;
}
}
这样可以得到正确的注册码:uAV9vfqaCjJ1kz8nJYvHfhmxVeL
完整的注册程序如下:
void main()
{
char CheckSum=0;
char CheckSum1=0;
char Key[25];
char Values[256];
Key[0]=0;
char de[27][4];
char ade[27][4];
char temp[27*4];
FILE *stream;
char list[30];
int i, numread, numwritten;
/* Open file in text mode: */
if( (stream = fopen( "D:\\Coded.dll", "r" )) != NULL )
{
numread = fread( temp, sizeof( char ), 27*4, stream );
fclose( stream );
}
for(i=0;i<27*4;i++)
de[i%27][i/27]=temp[i];
if( (stream = fopen( "D:\\MFCO42D.DLL", "r" )) != NULL )
{
numread = fread( temp, sizeof( char ), 27*4, stream );
fclose( stream );
}
for(i=0;i<27*4;i++)
ade[i%27][i/27]=temp[i];
Key[0]=0;
while(1)
{
while(1)
{
while(1)
{
if(Decrypt((char)0x44, CheckSum, CheckSum1, Key[0], 0) == 0x4D)
{
if(Decrypt((char)0xCF, CheckSum, CheckSum1, Key[0], 27) == 0x00)
{
if(Decrypt((char)0xE0, CheckSum, CheckSum1, Key[0], 54) == 0x00)
{
printf("CheckSum: %d\nCheckSum1: %d\nKey %d: %d\n", CheckSum, CheckSum1, 0,Key[0]);
goto here;
}
}
}
Key[0]++;
if(Key[0] == 0) break;
}
CheckSum1++;
if(CheckSum1 == 0) break;
}
CheckSum++;
if(CheckSum == 0) break;
}
here:
for(i=1;i<=26;i++)
{
Key[i]=0;
while(1)
{
if(Decrypt((char)de[i][0], CheckSum, CheckSum1, Key[i], i) == ade[i][0])
{
break;
}
Key[i]++;
if(Key[i] == 0) break;
}
}
for(i=0;i<27;i++)
printf("%c",Key[i]);
printf("\n");
}
【分析总结】
2006-05-13
赞赏
- [原创]VMP编译的完整笔记 15677
- [分享]利用Flex 10.1 SDK开发某软件注册机 17724
- [分享]菜鸟也学Armadillo 脱壳全保护加壳的记事本.doc 6190
- [分享]菜鸟也学Armadillo V4.40主程序脱壳 6434
- [分享]简单打狗文章一二 18706