Date: 2001-10-10 |
UltraEdit 6.00a how to make a KeyGenerator |
Intermediate |
This lesson will continue where the last left. If you haven't read it yet do it at WindowCommander 4.01. We will again practice the art of deadlisting, we will not use a debugger. This lesson deals with IDA which is probably the most advanced disassembler on the market. By using IDA, Ultraedits protection (both the TIME TRIAL and Serialnumber) routines will be exposed.
IDA is alot more complex than Win32Dasm and mastering IDA will take alot of time and require alot of patience. In this lesson we only touch the surface of IDA's capabillities. We will make use of IDA's textediting features such as renaming references and adresses and placing boomarks within the disassembled file. Which is alot easier to understand. When renaming adresses in IDA all references to that adress i renamed. This is not possible in Win32Dasm and I'll show with this lesson how useful it can be. In this lesson we will not pay much attention to string reference, rather we expose this protection by using a basic reverse engineering technique. |
Requirements |
Part I: Finding an entrance. |
There are several ways to find an entry point in ultraedit 6 (uedit from now on), each depending on the tool you use. In win32dasm you would start by examining the string references hoping that you'll spot a string used by the protection routine(s). With winice you'd put a bpx GetDlgItemTextA and so on. But what if there were no string references at all and the target use lowlevel system code to get the text or it could be impossible to debug due to anti softice tricks. Then you would be at the same point as if you would crack/reverse a dos application.
You would then have to think about how to approach the target.
You could say that the same thing goes for cracking with a completly new tool. You will have to learn the tool first and the better you get with it the better you crack with it. Cracking is a philosofy, and today we will practice that philosofy with IDA and uedit. Cracking with all the fancy tools out there are most of the time really nice. You seldom have to do anything by your self but finding some strings and then apply the patch. String references can be encrypted or strings can be created during runtime, in such cases the string references inside the exe file will either be zero or contain garbage. Crackers who's skills are depending on string references will have to find the encryption routine and decrypt the exe. Crackers who don't depend that much on strings will use another approach, the same as we will use now.
Where do we start? A quick eamination of the target reveals that
The entry point we take is the NagScreeNs handle. Use a resource editor and find the handles to the NagScreeN, Authorization dialog (pops up when choosing "Enter registration" and the About box. You should come up with these values:
0040831A sub_40831A proc near ; CODE XREF: .text:004517B7 0040831A 0040831A arg_0= dword ptr 4 0040831A 0040831A push [esp+arg_0] 0040831E push 6Eh ; Handle for Nagscreen saying 00408320 call ?Create@CDialog@@QAEHPBDPAVCWnd@@@Z ; CDialog::Create(char const *,CWnd *) 00408325 neg eax 00408327 sbb eax, eax 00408329 neg eax 0040832B retn 4 0040832B sub_40831A endp
You'll notice the XREF that is reference from where this code is invoked. Set you XREF in the option->General under 'Analysis' to display 100 XREF and depth to 16. That way you'll never miss anything. Enter a comment at 0044831E by pushing shift+';'. Comments are important when examining deadlist's. If you would look at this 10 hours later the comments really help you to get back in shape. Anyway lets see what triggers the creation of this dialog. The XREF leads us to this:
004517AC loc_4517AC: ; CODE XREF: .text:00451732j 004517AC push dword ptr [edi+1Ch] 004517AF lea esi, [edi+120h] 004517B5 mov ecx, esi 004517B7 call sub_40831A ; Call Nagscreen ? 004517BC test eax, eax 004517BE jnz short loc_4517C6 004517C0 mov [edi+13Ch], ebxHmmn this code is not depending on some condition to be met. Follow the XREF again to get here
0045172E mov eax, [ebp+14h] ; LPARAM in eax 00451731 dec eax 00451732 jz short loc_4517AC ; This triggers the NagScreeNHow did I know that [ebp+14h] is LPARAM? If you study this routine to the end you see this:
00451840 push dword ptr [ebp+14h] ; LPARAM 00451843 push dword ptr [ebp+10h] ; WPARAM 00451846 push dword ptr [ebp+0Ch] ; MSG 00451849 push dword ptr [ebp+8] ; HWND handle to window 0045184C call ds:DefWindowProcA
There you go! This means that LPARAM is decremented and then checked against zero. If LPARAM-1 = 0 the nagscreen is showned and we are suposed to be unregistered.
This routine is not called from anywhere, instead there's a data reference to it:
; DATA XREF: .text:0042CF5Fo. What do this mean? What it means is that the code in this
routine is a WindowProcedure. As you know windows is event driven, every time an event takes place ie a user clicks a button a message is sent to the windows windowprocedure. For example in an aboutbox there is usually an "OK" button. When the user clicks on this button a message BN_CLICKED is sent to the aboutbox (which is the owner of the OK button). The message is then processed inside the aboutbox windowprocedure.
Now we can go on and see where this windowprocedure is sent a message with LPARAM = 1 and LPARAM = 2,0,3,4 etc. What we know so far is that the NagScreeN is created if this windowprocedure is sent a message with LPARAM set to 1. This message is also sent from the application (uedit) because if we are registered a message is sent with a value of LPARAM that doesn't equal 1. However we don't know very much about the time trial protection here, so lets find out exactly how it works. This is a good idea for future work on UltraEdit, the newest version is 8.2.
Oh yeah, the jump 00451732, should you nop this jump out you'll crack Ultraedit's TimeTrial BUT without knowing how and why.
Part II: Time Trial Protection |
What happens after the NagScreeN has been showed?
:004517B7 call sub_40831A ; Call Nagscreen ? .text:004517BC test eax, eax .text:004517BE jnz short loc_4517C6 .text:004517C0 mov [edi+13Ch], ebx .text:004517C6 .text:004517C6 loc_4517C6: ; CODE XREF: .text:004517BEj .text:004517C6 cmp dword_4CDFF8, ebx .text:004517CC jnz loc_451863 .text:004517D2 cmp [edi+13Ch], ebx .text:004517D8 jz loc_451863 .text:004517DE push dword_4CDFF0 .text:004517E4 mov esi, offset aSettings_0 ; "Settings" .text:004517E9 mov ecx, edi .text:004517EB push offset aMousepos ; "MousePos" .text:004517F0 push esi .text:004517F1 call ?WriteProfileInt@CWinApp@@QAEHPBD0H@Z ; CWinApp::WriteProfileInt ; (char const *,char const *,int) .text:004517F6 push dword_4CDF7C ; Holds the number of days left .text:004517FC mov ecx, edi .text:004517FE push offset aDaysToExpire ; "Days to expire" .text:00451803 push esi .text:00451804 call ?WriteProfileInt@CWinApp@@QAEHPBD0H@Z ; CWinApp::WriteProfileInt ; (char const *,char const *,int)Now we're getting somewhere. The first interesting thing is the two calls to WriteProfileString which is used for writing in ini files. The second interesting thing is what's written to the ini file. We see that one field is "mousepos" and the other field is "DaysToExpire". Why would any one store the position of the mouse inside an ini file? My first thought was that this had something to do with the protection scheme that dwells here. Out of curiousity I tested whether uedit really did save the mouseposition, as expected, it did not! The second field is very clear it is the remaining days of the trial period. Rename dword_4CDF7C to "daysleft". IDA will now show daysleft instead of dword_4CDF7C which is very neat.
If you look inside your windows directory you'll find two interesting files.
The ini file:
[Settings] Language File=C:\appz\Uedit\wordfile.txt MousePos=11597 Days to expire=44 WindowPos=2,3,0,0,-1,-1,196,120,796,531The first to try is naturally to set "Days to expire" to 45 and see if uedit show value 45 in the NagScreeN. It doesn't. This means that this value is written only, and if you search for aDaysToExpire which is the string to reference "Days to expire" in the ini file, you'll find that this field's value is only written to the inifile. It's never read from the ini file.
If you change the mousepos value something interesting happens, if you decrease it with 1 you'll lose one day of your trial period. Increasing mousepos by 1 has no effect on the trial period. Only dereasing it effects the remaining number of days left.
Running filemon and starting Ultraedit shows that both the ini file and the cfg is opened and read from when Ultraedit starts. So what happens if we delete these files? You might feel what's gonna happen and yes, we do get a new trial period with new 45 days to use Ultraedit. You'll notice (of course) that Ultraedit creates both an ini file and a cfg file. Mousepos in the new ini file is set to 11598. We now know the heart of the time trial mechanism, lets find inside the deadlist too.
Doubleclick the aMousepos, this will take you here:
:004C78E0 aMousepos db 'MousePos',0 ; DATA XREF: .text:004424D6o .data:004C78E0 ; .text:00451758o .data:004C78E0 ; .text:004517EBoInvestigating these three locations you'll find that:
:004424D5 push ebx .text:004424D6 push offset aMousepos ; "MousePos" .text:004424DB push offset aSettings_7 ; "Settings" .text:004424E0 mov ecx, eax .text:004424E2 call ?GetProfileIntA@CWinApp@@QAEIPBD0H@Z ; CWinApp::GetProfileIntA(char const*,char const *,int) .text:004424E7 mov dword_4CDFF8, eax ; Save mousepos here .text:004424EC mov dword_4CDFFC, eax ; And here.dword_4CDFF8 has 14 refererences while dword_4CDFFC only has two. Both are defined at:
:004CDFF8 dword_4CDFF8 dd ? ; DATA XREF: .text:0042D0A9w .data:004CDFF8 ; .text:0042D0B7r .data:004CDFF8 ; .text:0042D10Dr .data:004CDFF8 ; .text:0042D15Er .data:004CDFF8 ; .text:0042D219w .data:004CDFF8 ; .text:0042D236r .data:004CDFF8 ; .text:0042D2A9r .data:004CDFF8 ; .text:0042D2D9w .data:004CDFF8 ; .text:0044129Dr .data:004CDFF8 ; .text:004424E7w .data:004CDFF8 ; .text:00451772r .data:004CDFF8 ; .text:004517C6r .data:004CDFF8 ; .text:0045180Ew .data:004CDFF8 ; .text:0045187Fr .data:004CDFFC dword_4CDFFC dd ? ; DATA XREF: .text:0042D206r .data:004CDFFC ; .text:004424ECw
Now we know where mousepos is used. we could right away start by examining the code where it is used but we wont. Something that alway pays off well is patience. You can save alot of time by having patience when examining a deadlist. Before we throw ourself into 16 location let's see where the daysleft is referenced from. We do that because daysleft is just as important for the time trial protection as mousepos is. We know that from mousepos the remaining number of days left is calculated.
:004CDF7C daysleft dd ? ; DATA XREF: .text:0042D0DAw .data:004CDF7C ; .text:0042D0E4w .data:004CDF7C ; .text:0042D0ECw .data:004CDF7C ; .text:0042D0F1r .data:004CDF7C ; .text:0042D127w .data:004CDF7C ; .text:0042D131w .data:004CDF7C ; .text:0042D139w .data:004CDF7C ; .text:0042D17Bw .data:004CDF7C ; .text:0042D185w .data:004CDF7C ; .text:0042D18Dw .data:004CDF7C ; .text:0042D256w .data:004CDF7C ; .text:0042D260w .data:004CDF7C ; .text:0042D268w .data:004CDF7C ; .text:0042D2B5r .data:004CDF7C ; .text:0042D2C4w .data:004CDF7C ; .text:0042D2D3w .data:004CDF7C ; .text:0042D2E1w .data:004CDF7C ; .text:0042D2E7r .data:004CDF7C ; .text:004517F6r .data:004CDF7C ; .text:00451863rDaysleft variable is used in two areas, the first at 0042D0## and the second is 4517F6 which is where we found it. Click on the first reference and take alook around this area. You'll quickly see that this are starts at 42CF16 and that it is a windowprocedure.
0042D0A9 mov Mousepos_0, eax 0042D0AE push dword ptr [ebp+10h] 0042D0B1 call ds:DeleteFileA 0042D0B7 mov eax, Mousepos_0 ; new Mousepos value in eax 0042D0BC push 2Dh 0042D0BE sub eax, dword_4CDFF0 ; substract with the old 0042D0C4 pop esi 0042D0C5 mov dword_4CDFCC, ebx 0042D0CB mov ecx, esi 0042D0CD add eax, 2Dh ; 2Dh = 45 0042D0D0 cmp eax, esi ; more than 45 days? 0042D0D2 jmp short loc_42D0D6 Location 42D0D6: 0042D0D6 cmp ecx, ebx ; is ecx = 0? 0042D0D8 jge short loc_42D0E2 ; if so jump 0042D0DA mov daysleft, ebx ; Update the daysleft variable 0042D0E0 jmp short loc_42D0F1 ; ContinueSo the value mousepos has is stored in eax. Tracking this leads to.
0042CF4F mov eax, dword_4CDFF0 0042CF54 cdq 0042CF55 mov ecx, 15180h ; 15180 is 24 hours expressed in seconds 0042CF5A mov [ebp-44h], ebx 0042CF5D idiv ecx 0042CF5F mov dword ptr [ebp-40h], offset sub_4516E2 ; Nagscreen Procedure 0042CF66 mov [ebp-3Ch], ebx 0042CF69 mov [ebp-38h], ebx 0042CF6C mov esi, offset aAfx2000x000800 ; "Afx:200:0x0008:0:010" 0042CF71 mov dword_4CDFF0, eaxdword_4CDFF0 get it's value at:
0044179A call _time 0044179F cmp dword_4CD094, ebx 004417A5 pop ecx 004417A6 mov dword_4CDFF0, eax
Things are very clear now. dword_4CDFF0 recieves a value from a function called time. It's reasonable to asume that the time function retrieves the time in som way. If you study it you'll see that it return the time in seconds. Then the seconds are divided by 15180h = 86400 = 24 hours in seonds (3600*25).
What happens next is tests to see if we have used Ultraedit for more tha 45 days. Most of the things are quite easy to understand here.
Part III: Serialnumber and Registration |
In this part we will concentrate on the registration part of the protection. We have seen the "Enter Authorization code" button in the NagScreeN. Pressing that and you'll be given the option to enter a name and a code. The first field, the name is usuallly not too hard to come up with. The second one, code, usually means more work. We know from previous protections that the name is usually used to caclulate a correct code. The correct code is compared to our code. Sometimes our code is manipulated in algorithms and sometime it's not.
Ultraedit informs, us after we filled in a name and code, that it needs to be restarted in able to verify our inputs. This means that Ultraedit stores our registration information either as a file or inside the registry. Restart and run filemon and regmon and you'll discover that A file (UEDIT32.REG) is searched and open and read from and finally closed. Lets locate the code that opens the uedit32.reg file.
Back to IDA.
Searching for "UEDIT32.REG" inside IDA doesn't turn up anything. Neither does examining the names is the name window. There are however occurances of the string "UEDIT32". A byte search for 52 45 47 = R E G turns up one location
dd offset loc_474551+1. If you mark the the lines and do an undefine you'll see.
.data:004C4E98 REG db 52h ; R ; DATA XREF: .text:00405D47o .data:004C4E98 ; .text:00405F57o .data:004C4E98 ; .text:0042D065o ; .data:004C4E98 ; .text:00440E20o ; .data:004C4E99 db 45h ; E .data:004C4E9A db 47h ; G .data:004C4E9B db 0 ;rename the dword_4C4E98 to REG.
You'll soon find that the regfiles data is stored in a local buffer = [ebp-288]. At the beginning of the regfile handler it reads the data from the buffer and starts to "decrypt" it. If you peeked inside you own regfile you'd notice that your inputs aren't there. At least not on the same form as you entered them.
.text:00440EC9 mov cl, [ebp+eax-287h] ; Put byte in cl .text:00440ED0 dec cl ; And decrement it .text:00440ED2 mov [ebp+eax-184h], cl ; Store it here!! .text:00440ED9 mov cl, [ebp+eax-288h] ; Get nexr byte from reg file .text:00440EE0 dec cl ; And decrement it .text:00440EE2 mov [ebp+eax-183h], cl ; And store it before the first byte .text:00440EE9 inc eax ; increment pointer to next byte in file ; which we already have processed .text:00440EEA inc eax ; Now we point to the next Unhandled byte .text:00440EEB cmp eax, esi ; End of File? .text:00440EED jle short loc_440EC9 ; If not loopIf you want to know what's inside you regfile you'll have to "decrypt it" according to the above algorithm. Here's source code for a simple decrypter:
//this code decrypt an ultraedit 6.00a regfile and stores a decrypted // copy of it as filinfo.txt. uedit32.reg must be in the same directory. #pragma hdrstop #include#include #pragma argsused int main(int argc, char* argv[]) { FILE *fp,*wp; unsigned char buf[256],temp[256]; fp=fopen("Uedit32.reg", "r+"); wp=fopen("filinfo.txt", "w+"); if(fp==NULL) { printf("Error: uedit32.reg not found"); return 0; } else { fread(buf,sizeof(char),sizeof(buf),fp); for(int i=0; i< 256; i+=2) { temp[i]=buf[i+1]-1; temp[i+1]=buf[i]-1; } fwrite(temp,sizeof(char),sizeof(temp),wp); printf("All Done!"); fclose(fp); fclose(wp); } return 0; }
Next part that's executes is:
.text:00440EEF lea eax, [ebp-184h] ; Buffer holding the decrypted regfile data .text:00440EF5 push 10h ; Copy 16 bytes .text:00440EF7 push eax .text:00440EF8 lea eax, [ebp-80h] ; And store them here .text:00440EFB push eax .text:00440EFC mov [ebp-70h], bl .text:00440EFF call loc_4620B0 ; Copy the bytesTo understand that the above code actually copy 16 bytes from ebp-184 into ebp-80 we have to examine loc_4620B0. Just by looking at the above code you can feel that it is a byte_copy function but to be 100% sure we need to delve inside the code.
.text:004620B0 push ebp .text:004620B1 mov ebp, esp .text:004620B3 push edi .text:004620B4 push esi .text:004620B5 mov esi, [ebp+0Ch] ; second parameter = source buffer .text:004620B8 mov ecx, [ebp+10h] ; first parameter = 10h .text:004620BB mov edi, [ebp+8] ; third parameter = destination buffer .text:004620BE mov eax, ecx ; EAX = 10h .text:004620C0 mov edx, ecx ; EDX = 10h .text:004620C2 add eax, esi ; Adress the 16th byte in source buffer .text:004620C4 cmp edi, esi ; Not equal in this case .text:004620C6 jbe short loc_4620D0 ; so we don't jump .text:004620C8 cmp edi, eax ; Destination not the same as source+16 ; bytes in this case. .text:004620CA jb loc_462248 ; So we don't jump .text:004620D0 .text:004620D0 loc_4620D0: ; CODE XREF: .text:004620C6j .text:004620D0 test edi, 3 ; .text:004620D6 jnz short loc_4620EC .text:004620D8 shr ecx, 2 ; 16 shr 2 = 4 .text:004620DB and edx, 3 ; edx = 0 .text:004620DE cmp ecx, 8 ; is ecx = 8? .text:004620E1 jb short loc_46210C ; no it's 4 so we jump .text:004620E3 repe movsd .text:004620E5 jmp ds:off_4621F8[edx*4] .text:0046210C jmp ds:off_46218C[ecx*4] ; Here we jump to the adress index at46218c+16 ; ecx*4 = 4*4 = 10h (16) The above jump takes us to the adress stored at 46219c: .text:0046218C off_46218C dd offset loc_4621EF ; DATA XREF: .text:0046210Cr .text:00462190 dd offset loc_4621DC .text:00462194 dd offset loc_4621D4 .text:00462198 dd offset loc_4621CC .text:0046219C dd offset loc_4621C4 ; Here so which means that the jmp will jump to 4621c4 .text:004621A0 dd offset loc_4621BC .text:004621A4 dd offset loc_4621B4 .text:004621A8 dd offset loc_4621AC .text:004621C4 mov eax, [esi+ecx*4-10h] ;store first dword of source in eax .text:004621C8 mov [edi+ecx*4-10h], eax ;and copy it to the destinaion buffer .text:004621CC mov eax, [esi+ecx*4-0Ch] ; Next dword of regfile data in eax .text:004621D0 mov [edi+ecx*4-0Ch], eax ; Store it after the first dword ; in destination .text:004621D4 .text:004621D4 loc_4621D4: ; CODE XREF: .text:0046210Cj .text:004621D4 ; DATA XREF: .text:00462194o .text:004621D4 mov eax, [esi+ecx*4-8] ; third... .text:004621D8 mov [edi+ecx*4-8], eax .text:004621DC .text:004621DC loc_4621DC: ; CODE XREF: .text:0046210Cj .text:004621DC ; DATA XREF: .text:00462190o .text:004621DC mov eax, [esi+ecx*4-4] ; fourth... .text:004621E0 mov [edi+ecx*4-4], eaxWhen we return ebp-80 will have the first 16 decrypted bytes stored in the reg file. If you decrypted the regfile you'll know it's the serial code that's stored in ebp-80.
.text:00462880 mov ecx, [esp+arg_4] ; Move offset to zaza123 in ecx .text:00462884 push edi .text:00462885 push ebx .text:00462886 push esi .text:00462887 mov dl, [ecx] ; Put 'z' in dl .text:00462889 mov edi, [esp+0Ch+arg_0]; buffer + 12 bytes .text:0046288D test dl, dl ; Check so that we actually got something in dl .text:0046288F jz short loc_4628FA ; Else we jump here .text:00462891 mov dh, [ecx+1] ; Put 'a' in dh .text:00462894 test dh, dh ; And check that we got a value in dh .text:00462896 jz short loc_4628E7 ; Else jump .text:00462898 mov esi, edi ; Put adress to (buffer +12 bytes) in ESI .text:0046289A mov ecx, [esp+0Ch+arg_4]; Load again ecx with offset to "zaza123" .text:0046289E mov al, [edi] ; Move first serial char in al .text:004628A0 inc esi ; Increase index to point to the next char .text:004628A1 cmp al, dl ; Is al = dl ie is al = z .text:004628A3 jz short loc_4628BA ; If so jump to check .text:004628A5 test al, al ; Have we read the whole buffer? .text:004628A7 jz short loc_4628B4 ; If so jump .text:004628A9 mov al, [esi] ; Put next byte from buffer in al .text:004628AB inc esi ; Increase buffer pointer .text:004628AC cmp al, dl ; Check if al = 'z' .text:004628AE jz short loc_4628BA ; If so jump .text:004628B0 test al, al ; Is al != 0 .text:004628B2 jnz short loc_4628A9 ; If not continue check al = 'z' We get here if al = 'z'. .text:004628BA mov al, [esi] ; Put the buffers next byte in al .text:004628BC inc esi ; Increase pointer to next byte .text:004628BD cmp al, dh ; Is al = 'a' .text:004628BF jnz short loc_4628AC ; If not go back to : check al = 'z' .text:004628C1 lea edi, [esi-1] ; Store adress where first encounter of "za" .text:004628C4 mov ah, [ecx+2] ; Put 'z' in ah .text:004628C7 test ah, ah ; Check that we got a value in ah .text:004628C9 jz short loc_4628F3 ; If not jump end of roiutine .text:004628CB mov al, [esi] ; Get next byte from buffer in al .text:004628CD add esi, 2 ; Increase pointer with two bytes .text:004628D0 cmp al, ah ; is al = 'z' .text:004628D2 jnz short loc_462898 ; If not jump .text:004628D4 mov al, [ecx+3] ; Put 'a' in al .text:004628D7 test al, al ; Check that al !=0 .text:004628D9 jz short loc_4628F3 ; If al is zero jump .text:004628DB mov ah, [esi-1] ; put next byte in ah .text:004628DE add ecx, 2 ; .text:004628E1 cmp al, ah ; check if al = 'a' .text:004628E3 jz short loc_4628C4 ; If it is jump .text:004628E5 jmp short loc_462898 ; If not jump If everything went well we get here!. .text:004628F3 lea eax, [edi-1] ; Get adress in eax where the 'z' of "zaza123" was found .text:004628F6 pop esi .text:004628F7 pop ebx .text:004628F8 pop edi .text:004628F9 retnBack in the main subroutine we write a zero at position of the first 'z' in "zaza123"
.text:00440F18 pop ecx .text:00440F19 cmp eax, ebx .text:00440F1B pop ecx .text:00440F1C jz loc_440FA6 .text:00440F22 mov [eax], bl ;Put zero over the first 'z' in "zaza123" in buffer.
The zero is added as a divder, the string "zaza123" begins just after the name ends. After that we copy the name which is located at ebp-16e. Start of buffer is ebp-184 the name begins 22 bytes inside the buffer which is ebp-16E.
.text:00440F24 call ?AfxGetModuleState@@YGPAVAFX_MODULE_STATE@@XZ ; AfxGetModuleState(void) .text:00440F29 mov eax, [eax+4] .text:00440F2C lea ecx, [ebp-16Eh] ; Copy the the name from ebp-16e .text:00440F32 add eax, 286h .text:00440F37 push ecx .text:00440F38 push eax ; Store it here .text:00440F39 call _strcpy
Where is the name stored? Well the call to AfxGetModuleState returns something most likely a pointer. At the [return value+4] is an adress. 286h bytes ahead of this adress is the location where we store our name. Since no variable is used to store this adress (YET) UEDIT must call AfxGetModuleState and retrieve the pointer at [return value +4] to get the pointer to our name.
.text:00440F3E pop ecx .text:00440F3F pop ecx .text:00440F40 call ?AfxGetModuleState@@YGPAVAFX_MODULE_STATE@@XZ ; AfxGetModuleState(void) .text:00440F45 mov eax, [eax+4] ; Same call as above .text:00440F48 mov ecx, offset dword_4CCF9C .text:00440F4D add eax, 286h ; Name is located here!! .text:00440F52 push eax .text:00440F53 call sub_47BB4C ; Copy name to 4CCF90
Ah, here we do that call to AfxGetModuleState, retrive the pointer to our name. Just by looking at what's pushed onto the stack as parameters to the call at 440F53 we can suspect that a pointer to our name is copied to dword_4CCF9C. Here what the call looks like:
.text:0047BB4C push esi .text:0047BB4D push edi .text:0047BB4E mov edi, [esp+arg_0] ; Put adress of name in edi .text:0047BB52 mov esi, ecx ; Ecx is the dword_4CCCF9C, now esi .text:0047BB54 test edi, edi ; did we get an adress? .text:0047BB56 jnz short loc_47BB5C ; Yes so proceed with jump .text:0047BB58 xor eax, eax ; No, zero eax .text:0047BB5A jmp short loc_47BB63 ; and leave .text:0047BB5C push edi ; Push our name's adress .text:0047BB5D call ds:lstrlenA ; Get the length of the name .text:0047BB63 push edi ; Push our name adress .text:0047BB64 push eax ; Push string length .text:0047BB65 mov ecx, esi ; Put dword_4CCF9C in ecx .text:0047BB67 call sub_47BAD0 ; Call .text:0047BB6C mov eax, esi .text:0047BB6E pop edi .text:0047BB6F pop esi .text:0047BB70 retn 4 On with the call: .text:0047BAD0 arg_0= dword ptr 0Ch .text:0047BAD0 arg_4= dword ptr 10h .text:0047BAD0 push esi .text:0047BAD1 push edi .text:0047BAD2 mov edi, [esp+arg_0] ;String length in edi .text:0047BAD6 mov esi, ecx ;dword_4CCF9C in esi .text:0047BAD8 push edi .text:0047BAD9 call ?AllocBeforeWrite@CString@@IAEXH@Z ; CString::AllocBeforeWrite(int) .text:0047BADE push edi ; Push string length of name .text:0047BADF push [esp+4+arg_4] ; Push pointer to our name .text:0047BAE3 push dword ptr [esi] ; Push adress dword_4CCF9C .text:0047BAE5 call loc_4620B0 ; Regnognize this call??? It's the copy routine. .text:0047BAEA mov eax, [esi] .text:0047BAEC add esp, 0Ch .text:0047BAEF mov [eax-8], edi .text:0047BAF2 mov eax, [esi] .text:0047BAF4 and byte ptr [edi+eax], 0 .text:0047BAF8 pop edi .text:0047BAF9 pop esi .text:0047BAFA retn 8
The call at 0047BAE5 should be familliar. Its used to copy the serialnumber into ebp-80 at 440EFF. We know from 440EFF that the first paramer is the number of bytes to copied same as string length, second is source buffer and last is the destination buffer. Therefore we can be sure that the name is copied into dword_4CCF9C. Ok lets move on...back to our main routine:
.text:00440F58 mov eax, dword_4CCF9C .text:00440F5D push offset unk_4CCCC0 ; .text:00440F62 push eax .text:00440F63 call __mbscmp .text:00440F68 neg eax .text:00440F6A sbb eax, eax .text:00440F6C pop ecx .text:00440F6D neg eax .text:00440F6F test al, al .text:00440F71 pop ecx .text:00440F72 jz short loc_440F82
The above does a compare between our name and unk_4CCCC0. In the data reference unk_4CCCC0 is decared as: .data:004CCCC0 unk_4CCCC0 db 0 ; So a stringcompare between our name and 0 should return 1. If you examine the __mbscmp you'll see that it return 0 if the strings match else 1.Therefore we dont jump at 440F72.
.text:00440F74 lea eax, [ebp-80h] ; Put adress of serial# in eax .text:00440F77 mov ecx, offset dword_4CCF90 ; adress to dword_4CCF90 in ecx .text:00440F7C push eax ; Push parameter .text:00440F7D call sub_47BB4C ; Call copy eax into ecx
The above is the same thing that was done to our name except here it is the serialnumber that's copied. After this the routine does a few less interesting things before it exits.
Use IDA an rename dword_4CCF9C to RegName_string and dword_4CCF90 to RegSerial_string. Now every reference to dword_4CCF9C will bw shown as RegName_string wich makes it easier to find out where the next part of the protection dwells.
To decide where to go we look at the references of the RegName_string and RegSerial_string. Examine all the references.
data:004CCF90 RegSerial_string dd ? ; DATA XREF: .data:004CCF90 ; .text:00405861o ;Construktor .data:004CCF90 ; .text:00405877o ;Deconstructor .data:004CCF90 ; .text:0042CFFDr ;Inside same routine as date check .data:004CCF90 ; .text:00440F77o ;Where serial is copied .data:004CCF9C RegName_string dd ? ; DATA XREF: .data:004CCF9C ; .text:00405857o Constructor .data:004CCF9C ; .text:00405881o Deconstructor .data:004CCF9C ; .text:004058E6o operator+= .data:004CCF9C ; .text:00405C73o Very Interesting!!!!! .data:004CCF9C ; .text:0042CF95o Inside same routine as date check .data:004CCF9C ; .text:0042CFBFr Inside same routine as date check .data:004CCF9C ; .text:0042D02Ao Inside same routine as date check .data:004CCF9C ; .text:0042D09Dr Inside same routine as date check .data:004CCF9C ; .text:00440F48o Where RegName_string is created .data:004CCF9C ; .text:00440F58r Here it's used in __mbscmp .data:004CCF9C ; .text:0045170Eo Same routine as NagScreeN trigger
00405C73
0042CF95 ->0042D09D
If you take a quick look at 405C73 you'll see that this is the call that creates the REG file. The other call is interesting because that it is located inside the very same procedure that handles the date checking. A closer look at this routine starting from 42CF16 you'll see that the offset to the NagScreen is used in a call to RegisterClass followed further down by a call to CreateWindow. This of course means that the NagScreen is created in this routine :) Another thing that's obvious is that the NagScreen shouldn't be shown if we're registered users.
.text:0045172E mov eax, [ebp+14h] ; LPARAM of message sent to this WindowProcedure .text:00451731 dec eax .text:00451732 jz short loc_4517AC ; This triggers the NagScreeN .text:00451734 dec eax
If Lparam is 1 uedit shows the nagscreen. Somewhere is a windows message sent to this windowprocedure with LPARAM set to 1 if we are unregistered or LPARAM set to 2 or anything else but not 1 if we're registered. To find out where we need to know the handle to the window which is returned by calling CreateWindow.
.text:0042CFE9 call ds:CreateWindowExA .text:0042CFEF mov dword_4CDF80, eax ;Save handle to nagscreen in dword_4CDF80
data:004CDF80 dword_4CDF80 dd ? ; DATA XREF: .data:004CDF80 ; .text:0042CFEFw .data:004CDF80 ; .text:0042D101r .data:004CDF80 ; .text:0042D197r .data:004CDF80 ; .text:0042D2F7r 0042D101 is where we send a message to the nagscreen windowprocedure with LPARAM = 1. 0042D197 same as above but with LPARAM = 2. Here it is, for registered users! tracing this backwards you'll see that this path is depending on a jump at 42D037. 0042D2F7 same as 42D101.
We now have so many references to find the call that makes us registered/unregistered. All references that leads to that call has been found (at least what i can tell). Everything points to this:
.text:0042CFF4 lea eax, [edi+286h] ; Our name .text:0042CFFA mov [eax+10h], bl ; bl is zero due to a xor ebx,ebx at the beginning .text:0042CFFD push RegSerial_string ; Our serialnumber .text:0042D003 push 1 .text:0042D005 push eax ; Put our name on the stack .text:0042D006 push dword ptr [ebp+0Ch] .text:0042D009 call sub_40C72D ; RegCheck routine .text:0042D00E add esp, 10h .text:0042D011 test eax, eax ; Eax zero? .text:0042D013 jz loc_42D1A8 ; If so show nagscreen .text:0042D019 cmp dword_4CDFCC, ebx .text:0042D01F jnz loc_42D1A8 ; If so Show NagScreeN .text:0042D025 push offset aExtensionLicen ; "Extension License" .text:0042D02A mov ecx, offset RegName_string .text:0042D02F call ?Find@CString@@QBEHPBD@Z ; CString::Find(char const *) .text:0042D034 cmp eax, 0FFFFFFFFh .text:0042D037 jz loc_42D15E ; Don't show nagscreen
I only commented what we know and left out what we don't know. Looking at the above code we see that the call at 42D009 is the top call that determines if we are registered/unregistered. This call must return non zero otherwise the nagscreen will be displayed and we run as unregistered. Further more dword_4CDFCC must be zero otherwise we run as unregistered, this will be set to 1 when uedit32 writes our regfile. Lets look at the return value options from routine 40C72D.
End of routinr 40C72D: ---------------------- .text:0040C92E .text:0040C92E loc_40C92E: ; CODE XREF: .text:0040C756j .text:0040C92E ; .text:0040C75Fj .text:0040C92E ; .text:0040C777j .text:0040C92E ; .text:0040C8BAj .text:0040C92E ; .text:0040C8F1j .text:0040C92E ; .text:0040C922j .text:0040C92E xor eax, eax ; Not Registered .text:0040C930 .text:0040C930 loc_40C930: ; CODE XREF: .text:0040C92Cj .text:0040C930 pop edi .text:0040C931 pop esi .text:0040C932 pop ebx .text:0040C933 leave .text:0040C934 retn
Here we see that there are 6 ways to return with eax set to zero but only one that returns with eax set to something else most likely nonzero. Lets look at 40C92C.
.text:0040C924 loc_40C924: ; CODE XREF: .text:0040C904j .text:0040C924 ; .text:0040C91Aj .text:0040C924 mov dword_4CD080, esi .text:0040C92A .text:0040C92A loc_40C92A: ; CODE XREF: .text:0040C8DEj .text:0040C92A ; .text:0040C8ECj .text:0040C92A mov eax, ebx .text:0040C92C jmp short loc_40C930
From the above you'll see that's there are four jumps that takes us here and by that makes us registered. Each of this jumps are depending on string compares. One of them will compare ours serialnumber with the correct one. To know what the other compares are we must investigate this routine closer. The RegCheck routine is quite big and I've therefore choosed to display it in a separate window. RegCheck routine
Part IV: How to calculate a correct code |
Before we enter the regcheck routine there are some changes made to our RegName_string. This is done just below the first reference to RegName_string. Uedit calls setAt which replaces a char at certain position. Both string and position is sent as parameters to the function. Uedit replace our third char in RegName_string with 2h. Next uedit replaces the sixth char with 6th char or 55. This is the same as doing a or 6th char with 55. The new RegName_string is referenced herein as Altered Regname.
RegName = 01IndianTrail = 30h 31h 49h 6Eh 64h 69h 61h 6Eh 54h 72h 61h 69h 6Ch
Altered RegName = 30h 31h 09h 6Eh 64h 7Dh 61h 6Eh 54h 72h 61h 69h 6Ch
It begins with creating a 60 bytes buffer and fill it with '.' = 2Eh and copies Altered RegName to it. Next uedit creates a sum of all chars in AlteredRegName and inverts it by a not sum_of_char.
Sum of all chars = (4)84 byte value = 84.
Inverting the bits = 7B (84h = 10000100b not 84 ->b =01111011b = 7Bh)
loop: a = remainder of loop_index/4 ; get numbers between 0 and 3 (integers) b = remainder of loop_index/16 ; Get number between 0 and 16 (integers) key = byte at adress [4c4c88+a*4] ; 4C4C88 is a vector of adresses, use IDA to find'em key = key+b/2 xor key with inverted sum_of_chars key = key +1 xor key with next byte in buffer overwrite current byte in buffer with key. if index > 8 { key = remainder of key / 10 key = key + 48 } else { key = remainder of key / 26 key = key + 65 } store key in buffer_2 increase index with 1 index = 60 if not loop. ---------------End Loop------- Next we look at each byte in buffer to see if any byte is: 128 if so we add 128 to this byte 127 if so we extract 127 from this byte 33 if so add 33 to this byte 34 if so make it 35 39 if so make it 40 46 if so make it 47 96 if so make it 97 124 if so make it 108
To get the values for [4c4c88+a*4] examine adress 4C4C88:
80 4C 4C 00 58 4C 4C 00 18 80 4C 00 00 D0 83 4C 00
When a is 0, [4c4c88+a*4] will return adress 004C4C80
When a is 1, [4c4c88+a*4] will return adress 004C4C58
When a is 2, [4c4c88+a*4] will return adress 004C8018
When a is 3, [4c4c88+a*4] will return adress 004C83D0
Writing a keygenerator is now very simple, just make sure your name have more than 5 characters, and the serial must be = 16 numbers. I choose not to include any source code, this lesson contains far to much code anyway. An inclosing note is that if you start up IDA and load your project file for Ultraedit, IDA will overwrite the correct REG file! This happens because IDA executes all code inside a project file and that means to delete the registration file. You saw that in 42CF16 the reg file is deleted if some condition is true. That condition is, if the RegName_string is found inside the UEDIT32.REG. I Guess it's a way of protecting against manipulations of the reg file. Like writing your name inside it to get the aboutbox to display it :)