📜 ⬆️ ⬇️

Quick Password Recovery PRO 1.7 security research

Hello. Today, using the example of Quick Password Recovery Pro 1.7.1, I want to show a study of protection using a couple of interesting techniques and a hash function.


Purpose: Quick Password Recovery PRO 1.7.1 (www.passdecrypt.ru)
Protection: HWID + MD5
Tools: Die 0.65 + KANAL 2.92 plugin, OllyDbg 1.10 (more Olly) + mapimp plugin (available on tport.org), IDR (Interactive Delphi Reconstructor), Keygener Assistant 1.7, Borland Delphi 7 (for writing keygen).
Let's start the "examination of the patient":
image
The program is written in Borland Delphi [ver: 2006] | Object Pascal, so we immediately ship it to IDR to analyze and create a map file for Olly.
The Kanal plugin finds several crypto signatures:
image
We start the program, we are greeted by a window with a message that this is an unregistered version and some functions are not available. In this we can see for yourself:
image

image
')
Press the "Register" button:
image
For registration, we need a name and serial number, which certainly depend on the system identifier (further HWID).

Enter any registration data, for example:
Name: ds@mail.ru
Serial: abcdefghigklmnop

We look at the IDR code, which is executed by clicking on the "Register" button, but we see that in addition to the creation of a pair of entries in the registry, nothing else happens:
image

image
The program offers us to restart it, which means that the check is performed somewhere at the start of the program. Suppose a check is performed when the main form is created (OnCreate event). We are looking for a place to call in IDR and put a breakpoint at the beginning of the function in Olly:
image

We start the program from the debugger and stop here:
Copy Source | Copy HTML<br/>0046EC94 > . 55 PUSH EBP ; Unit1.TForm1.FormCreate <br/>

We reach the first interesting call:
image
On F7 we enter and trace further, until we reach here:
image
I'll run a little bit ahead and say that this is where the HWID generation and subtraction of the first part of the serial are hiding.
Again, we follow F7 and determine that the HWID is generated at 46537F, which is easy to guess by passing the function under the debugger:
Copy Source | Copy HTML<br/>0046537F |. E8 3CFCFFFF CALL <QuickPas._Unit32.sub_00464FC0> ; << Get Hwid <br/>

image
But then we carefully examine the function at the address 00464FC0 and find the code there:
Copy Source | Copy HTML<br/>00465072 | > /8B45 FC /MOV EAX,DWORD PTR SS:[EBP-4]<br/>00465075 |. |0FB64418 FF |MOVZX EAX,BYTE PTR DS:[EAX+EBX-1]<br/>0046507A |. |8BD3 |MOV EDX,EBX<br/>0046507C |. |03D2 |ADD EDX,EDX<br/>0046507E |. |03C2 |ADD EAX,EDX<br/>00465080 |. |8D55 DC |LEA EDX,DWORD PTR SS:[EBP-24]<br/>00465083 |. |E8 3441FAFF |CALL < QuickPas.SysUtils.IntToStr > <br/>00465088 |. |8B55 DC |MOV EDX,DWORD PTR SS:[EBP-24]<br/>0046508B |. |8D45 F4 |LEA EAX,DWORD PTR SS:[EBP-C]<br/>0046508E |. |E8 6D01FAFF |CALL < QuickPas.system. @LStrCat > <br/>00465093 |. |43 |INC EBX<br/>00465094 |. |83FB 04 |CMP EBX,4<br/>00465097 |.^\75 D9 \JNZ SHORT QuickPas.00465072<br/>00465099 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]<br/>0046509C |. E8 93FEF9FF CALL < QuickPas.system. @LStrClr > <br/>004650A1 |. BB 01000000 MOV EBX,1<br/>004650A6 | > 8D45 D8 /LEA EAX,DWORD PTR SS:[EBP-28]<br/>004650A9 |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C]<br/>004650AC |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004650B1 |. E8 6600FAFF |CALL < QuickPas.system. @LStrFromChar > <br/>004650B6 |. 8B55 D8 |MOV EDX,DWORD PTR SS:[EBP-28]<br/>004650B9 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]<br/>004650BC |. E8 3F01FAFF |CALL < QuickPas.system. @LStrCat > <br/>004650C1 |. 43 |INC EBX<br/>004650C2 |. 83FB 06 |CMP EBX,6<br/>004650C5 |.^ 75 DF \JNZ SHORT QuickPas.004650A6 <br/>

Here, the codes of the first 3 characters are taken, + the value of the loop counter multiplied by 2 is glued to a string, and then only the first 5 are taken (the SystemBiosDate parameter, which is considered a hash, can be found in the registry, and more specifically here: HKEY_LOCAL_MACHINE \ HARDWARE \ DESCRIPTION \ System)
image
In pascal, it can be written like this:
Copy Source | Copy HTML<br/>md5_sbd:=md5( SystemBiosDate)<br/> for i:=1 to 3 do<br/> begin <br/> result := result + IntToStr(ord(md5_sbd[i]) + i*2;);<br/> end ; <br/>

After exiting the HWID receiving function, step forward:
Copy Source | Copy HTML<br/>00465389 |> /8D45 F8 /LEA EAX,DWORD PTR SS:[EBP-8]<br/>0046538C |. |E8 BF00FAFF | CALL <QuickPas.system.@UniqueStringA><br/>00465391 |. |BA 06000000 |MOV EDX,6<br/>00465396 |. |2BD3 |SUB EDX,EBX<br/>00465398 |. |8B4D FC |MOV ECX,DWORD PTR SS:[EBP-4]<br/>0046539B |. |0FB64C19 FF |MOVZX ECX,BYTE PTR DS:[ECX+EBX-1]<br/>004653A0 |. |884C10 FF |MOV BYTE PTR DS:[EAX+EDX-1],CL<br/>004653A4 |. |43 |INC EBX<br/>004653A5 |. |83FB 06 |CMP EBX,6<br/>004653A8 |.^\75 DF \JNZ SHORT QuickPas.00465389<br/>004653AA |. BB 01000000 MOV EBX,1<br/>004653AF |> 8D45 F0 /LEA EAX,DWORD PTR SS:[EBP-10]<br/>004653B2 |. 8B55 F8 |MOV EDX,DWORD PTR SS:[EBP-8]<br/>004653B5 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004653BA |. E8 5DFDF9FF | CALL <QuickPas.system.@LStrFromChar><br/>004653BF |. 8B45 F0 |MOV EAX,DWORD PTR SS:[EBP-10]<br/>004653C2 |. E8 313FFAFF | CALL <QuickPas.SysUtils.StrToInt><br/>004653C7 |. 8BF0 |MOV ESI,EAX<br/>004653C9 |. 33F3 |XOR ESI,EBX<br/>004653CB |. 8D55 F4 |LEA EDX,DWORD PTR SS:[EBP-C]<br/>004653CE |. 8BC6 |MOV EAX,ESI<br/>004653D0 |. E8 E73DFAFF | CALL <QuickPas.SysUtils.IntToStr><br/>004653D5 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]<br/>004653D8 |. E8 7300FAFF | CALL <QuickPas.system.@UniqueStringA><br/>004653DD |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C]<br/>004653E0 |. 0FB612 |MOVZX EDX,BYTE PTR DS:[EDX]<br/>004653E3 |. 885418 FF |MOV BYTE PTR DS:[EAX+EBX-1],DL<br/>004653E7 |. 43 |INC EBX<br/>004653E8 |. 83FB 06 |CMP EBX,6<br/>004653EB |.^ 75 C2 \JNZ SHORT QuickPas.004653AF <br/>

Here are some transformations, etc. Again, I present the code section I translated only in Pascal:
Copy Source | Copy HTML<br/>part_of_serial:=ReverseString(hwid);<br/>tmp:= '' ;<br/> for i:= 1 to 5 do<br/> begin <br/> tmp:=tmp + copy(IntToStr(StrToInt(part_of_serial[i]) xor i),1,1);<br/> end ;<br/>part_of_serial:=copy(tmp,1,5); <br/>

In my case, the result was 41413
Go ahead, see ...
image
Knowing that the md5 hash function is used in the program, we check our assumption:
md5 (41413) = FFA54840A3C240E0725C16C7FA48281C
image
This is clearly seen in the stack window:
image
We leave the function, continue tracing and see that the md5-hash from the first 5 characters of the entered serial is taken and compared with the previously received (primitive, but at least somehow).
md5 (abcde) = AB56B4D92B40713ACC5AF89985D4B786
image
Now we know that the first characters of the correct serial (in my case) should be 41413.
We are trying to register a program with a new serial with the foregoing:
41413abcdefghijklmnop, and the mail is the same as before (ds@mail.ru).
Now we are going to check at:
Copy Source | Copy HTML<br/>00465563 |. E8 DCFDF9FF CALL <QuickPas.system.@LStrCmp> <br/>

The program runs without prejudice and says in the About window that everything is fine, but when you click on the function buttons in the program we see the Unregistered version.

Ok, we continue from the place where we passed the test. We set breakpoint at 00465563 and trace further.
We see two very simple pieces of code:
Copy Source | Copy HTML<br/>00465595 |. BB 01000000 MOV EBX,1<br/>0046559A | > /8B45 EC /MOV EAX,DWORD PTR SS:[EBP-14]<br/>0046559D |. |0FB64418 FF |MOVZX EAX,BYTE PTR DS:[EAX+EBX-1]<br/>004655A2 |. |0145 E4 |ADD DWORD PTR SS:[EBP-1C],EAX<br/>004655A5 |. |43 |INC EBX<br/>004655A6 |. |83FB 06 |CMP EBX,6<br/>004655A9 |.^\75 EF \JNZ SHORT QuickPas.0046559A <br/>

Copy Source | Copy HTML<br/>004655BC |. BB 01000000 MOV EBX,1<br/>004655C1 | > 8B55 FC /MOV EDX,DWORD PTR SS:[EBP-4]<br/>004655C4 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004655C9 |. 0155 E8 |ADD DWORD PTR SS:[EBP-18],EDX<br/>004655CC |. 43 |INC EBX<br/>004655CD |. 48 |DEC EAX<br/>004655CE |.^ 75 F1 \JNZ SHORT QuickPas.004655C1 <br/>

They carry out some calculations (first for characters from the 6th to 10th from the entered serial), and then from the entered e-mail.

As a result of all this, the number 1353 and fghijklmnop are being compared, it is logical that this may be the third part of the serial number.
We are trying a new serial number: 41413abcde1353
It seems everything is fine, but there is one "BUT"!
When you click on the function keys for recovery, we get incomprehensible characters, such as:
image
It turns out that somewhere something goes wrong before outputting the results. Let's look at the IDR event by clicking, for example, on The Bat password recovery and set a breakpoint at the beginning of the procedure.
image
Then we go to the function at 465250 and carefully look in the stack window, and in parallel we press F8
It's hard not to notice that the first 5 characters of the serial number and the HWID are stuck together, and then the received string is considered to be an md5 hash.

md5 (4141369735) = 3354B017EB74FB4DC20A1B48491D1431

And then from the received hash is considered a certain amount:

Copy Source | Copy HTML<br/> for i:=4 to 7 do<br/> begin <br/> tmp := tmp + IntToStr(ord(part2[i]));<br/> end ; <br/>


and the first 5 characters are copied:
Copy Source | Copy HTML<br/> some := copy(tmp,1,5); <br/>

As a result of calculations, we get 52664 (we will return to this value).

Further interesting challenge:
CALL 00467A44
For us there is nothing interesting (this is the main function of the program that is trying to recover the password).
Go ahead and look here:
00470919 |. E8 FA4DFFFF CALL <QuickPas._Unit32.sub_00465718>

Very interesting block:
Copy Source | Copy HTML<br/>0046577F |. BB 01000000 MOV EBX,1<br/>00465784 | > 8D45 FC /LEA EAX,DWORD PTR SS:[EBP-4]<br/>00465787 |. E8 C4FCF9FF |CALL < QuickPas.system. @UniqueStringA > <br/>0046578C |. 8D4418 FF |LEA EAX,DWORD PTR DS:[EAX+EBX-1]<br/>00465790 |. 50 |PUSH EAX<br/>00465791 |. 8BC3 |MOV EAX,EBX<br/>00465793 |. 99 |CDQ<br/>00465794 |. F7FF |IDIV EDI<br/>00465796 |. 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]<br/>00465799 |. 0FB60410 |MOVZX EAX,BYTE PTR DS:[EAX+EDX]<br/>0046579D |. 0FBE55 F3 |MOVSX EDX,BYTE PTR SS:[EBP-D]<br/>004657A1 |. F7EA |IMUL EDX<br/>004657A3 |. 8B55 FC |MOV EDX,DWORD PTR SS:[EBP-4]<br/>004657A6 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004657AB |. 03C2 |ADD EAX,EDX<br/>004657AD |. 5A |POP EDX<br/>004657AE |. 8802 |MOV BYTE PTR DS:[EDX],AL<br/>004657B0 |. 43 |INC EBX<br/>004657B1 |. 4E |DEC ESI<br/>004657B2 |.^ 75 D0 \JNZ SHORT QuickPas.00465784 <br/>

Looking through the dump window, we understand that a primitive encryption is going on before output (although this is too loud and does not reach full-fledged encryption, but there is something in common - this message and the key with which encryption goes, so I will continue to call this block encryption).
image
You can explore this code in more detail, but for the final purpose (writing keygens) we don’t need this.

Go ahead and see:

Copy Source | Copy HTML<br/>0047093B |. E8 144DFFFF CALL < QuickPas._Unit32.sub_00465654 > <br/>


The parameter that is passed to this function is very familiar to us - these are serial number symbols from the 6th to the 10th.
Intuition did not let me down, we see such a piece of code:

Copy Source | Copy HTML<br/>004656AC |. BB 01000000 MOV EBX,1<br/>004656B1 | > 8D45 FC /LEA EAX,DWORD PTR SS:[EBP-4]<br/>004656B4 |. E8 97FDF9FF |CALL < QuickPas.system. @UniqueStringA > <br/>004656B9 |. 8D4418 FF |LEA EAX,DWORD PTR DS:[EAX+EBX-1]<br/>004656BD |. 50 |PUSH EAX<br/>004656BE |. 8BC3 |MOV EAX,EBX<br/>004656C0 |. 99 |CDQ<br/>004656C1 |. F7FF |IDIV EDI<br/>004656C3 |. 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]<br/>004656C6 |. 0FB60410 |MOVZX EAX,BYTE PTR DS:[EAX+EDX]<br/>004656CA |. 0FBE55 F3 |MOVSX EDX,BYTE PTR SS:[EBP-D]<br/>004656CE |. F7EA |IMUL EDX<br/>004656D0 |. 8B55 FC |MOV EDX,DWORD PTR SS:[EBP-4]<br/>004656D3 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004656D8 |. 03C2 |ADD EAX,EDX<br/>004656DA |. 5A |POP EDX<br/>004656DB |. 8802 |MOV BYTE PTR DS:[EDX],AL<br/>004656DD |. 43 |INC EBX<br/>004656DE |. 4E |DEC ESI<br/>004656DF |.^ 75 D0 \JNZ SHORT QuickPas.004656B1 <br/>

The same code, but in this case, it “decrypts” the message just before its output.
It is clear that in order to obtain normal data at the output, you need to decrypt with the same key as encrypted.

true_key = 52664
our_key = abcde

message = decrypt (crypt (message, true_key), our_key)
It is easy to guess that in order for message = message, true_key should equal our_key.

In general, all the data for keygen we have, it remains to put everything in one general procedure.
To facilitate, we divide some calculations into separate functions:

1. The function to get the value from the registry:
Copy Source | Copy HTML<br/> function RegQueryStr(RootKey: HKEY; Key, Name: string ;<br/> Success: PBoolean = nil): string ;<br/> var <br/> Handle: HKEY;<br/> Res: LongInt;<br/> DataType, DataSize: DWORD;<br/>begin<br/> if Assigned(Success) then<br/> Success^ := False;<br/> Res := RegOpenKeyEx(RootKey, PChar(Key), 0 , KEY_QUERY_VALUE, Handle);<br/> if Res <> ERROR_SUCCESS then<br/> Exit;<br/> Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType, nil, @DataSize);<br/> if (Res <> ERROR_SUCCESS) or (DataType <> REG_SZ) then<br/> begin<br/> RegCloseKey(Handle);<br/> Exit;<br/> end;<br/> SetString(Result, nil, DataSize - 1 );<br/> Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType,<br/> PByte(@Result[ 1 ]), @DataSize);<br/> if Assigned(Success) then<br/> Success^ := Res = ERROR_SUCCESS;<br/> RegCloseKey(Handle);<br/>end; <br/>


2. HWID calculation function:
Copy Source | Copy HTML<br/> function GetHWID: string;<br/> var <br/>SystemBiosDate,md5_sbd:String;<br/>i: integer ;<br/>digest:pointer;<br/>dwSize:Cardinal;<br/> begin <br/> SystemBiosDate := RegQueryStr(HKEY_LOCAL_MACHINE, 'HARDWARE\DESCRIPTION\System' , 'SystemBiosDate' );<br/> md5_sbd:=SystemBiosDate;<br/> dwSize:=length(md5_sbd);<br/> digest:= GetHash(@md5_sbd[1],dwSize,ALG_MD5);<br/> if digest <> nil then <br/> try<br/> md5_sbd:=BinToHexStr(digest,dwSize);<br/> finally<br/> FreeMem(digest);<br/> end ;<br/> md5_sbd:=Uppercase(md5_sbd);<br/> result := '' ;<br/> for i:=1 to 3 do<br/> begin <br/> result := result + IntToStr(ord(md5_sbd[i]) + i*2);<br/> end ;<br/> result :=copy( Result ,1,5); <br/> End ; <br/>


3. The function of receiving the reverse (reverse line):
Copy Source | Copy HTML<br/> function ReverseString( const s: string ): string ;<br/> var <br/> i, len: Integer;<br/>begin<br/> len := Length(s);<br/> SetLength(Result, len);<br/> for i := len downto 1 do <br/> begin<br/> Result[len - i + 1 ] := s[i];<br/> end;<br/>end; <br/>


4. Function to check whether the string is a number (for correctness of the entered data by the user):
Copy Source | Copy HTML<br/> function IsStrANumber( const S: string ): Boolean;<br/> var <br/> P: PChar;<br/>begin<br/> P := PChar(S);<br/> Result := False;<br/> while P^ <> # 0 do <br/> begin<br/> if not (P^ in [ '0' .. '9' ]) then Exit;<br/> Inc(P);<br/> end;<br/> Result := True;<br/>end; <br/>


The basic generation procedure:
Copy Source | Copy HTML<br/>procedure generate;<br/> var Serial,NameBuf: String ;<br/>len,len1: integer ;<br/>Textname,HWID: PChar ;<br/>hw: String ;<br/> i ,sum_name,sum_part2: integer ;<br/> part1,tmp,part2,part3: string ;<br/> digest: pointer ;<br/>dw Size : Cardinal ;<br/>begin<br/> len := GetWindowTextLengthA( TxtNameHwnd );<br/> len1 := GetWindowTextLengthA( txtHWID );<br/> if len > 1 then<br/> begin<br/> { Get text from name input }<br/> GetMem( Textname, len );<br/> GetWindowTextA( TxtNameHwnd,PAnsiChar(Textname ),len + 1 );<br/> GetMem( HWID, len1 );<br/> GetWindowTextA( TxtHWID,PAnsiChar(HWID ),len1 + 1 );<br/> HW:=String( HWID );<br/>if ( IsStrANumber(hw )) and ( length(hw )= 5 ) then<br/> begin<br/> { Generate Serial }<br/> NameBuf:=String( Textname );<br/> hw:=string( HWID );<br/>hw:=copy( hw,1,5 );<br/>part1:=ReverseString( hw );<br/>tmp:= '' ;<br/>for i := 1 to 5 do<br/> begin<br/> tmp:=tmp + copy( IntToStr(StrToInt(part1[i] ) xor i ), 1 , 1 );<br/> end;<br/>part1:=copy( tmp,1,5 );<br/> //--------------------- <br/>tmp:= '' ;<br/>part2 :=part1+hw;<br/> dw Size :=length( part2 );<br/> digest:= GetHash( @part2[1],dwSize,ALG_MD5 );<br/> if digest <> nil then<br/> try<br/> part2:=BinToHexStr( digest,dwSize );<br/> finally<br/> FreeMem( digest );<br/> end;<br/>part2 := UpperCase ( part2 );<br/>for i := 4 to 7 do<br/> begin<br/> tmp := tmp + IntToStr( ord(part2[i] ));<br/> end;<br/>part2 := copy( tmp,1,5 );;<br/> //--------------------- <br/>sum_name:= 0 ;<br/>sum_part2:= 0 ;<br/>for i := 1 to length( NameBuf )- 1 do<br/> begin<br/> sum_name:=sum_name + ord( NameBuf[i] );<br/> end;<br/>for i := 1 to length( part2 ) do<br/> begin<br/> sum_part2:=sum_part2 + ord( part2[i] );<br/> end;<br/>part3:=IntToStr( sum_name+sum_part2 );<br/>Serial:=part1 + part2 + part3;<br/> end<br/> else // if hwid not numeric or <> 5 <br/> Serial:= 'Invalid HWID' ;<br/> { Display The Results }<br/> SetWindowTextA( TxtSerialHwnd,PChar(Serial ));<br/> FreeMem ( HWID, len1 + 1 );<br/> FreeMem ( Textname, len+1 );<br/> end<br/> Else<br/> { Display Error }<br/> SetWindowText( TxtSerialHwnd,'Not Enough Characters..' );<br/>end; <br/>


I think everything is very clear here, since all the main points were dismantled earlier.
These sections of code can cause questions:
Copy Source | Copy HTML<br/>dwSize:=length(part2);<br/>digest:= GetHash(@part2[1],dwSize,ALG_MD5);<br/> if digest <> nil then <br/> try<br/> part2:=BinToHexStr(digest,dwSize);<br/> finally<br/> FreeMem(digest);<br/> end ; <br/>

In short, this is an md5-hash calculation using HashCryptoAPILib, which in turn uses standard Windows tools (CryptoApi). You can compile the keygen yourself or take it ready on tport.org in the “Download” section.

We generate a serial on any name and we test. The program is registered, works correctly, tries to perform its functions and displays messages in a normal form. That's all.

I hope that the above material was interesting and that someone will learn something useful for themselves.

Source: https://habr.com/ru/post/133508/


All Articles