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.
Insted of:

mov eax,4400FC
We might make it
mov eax, pointer to serialnumber


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

IDA 4.04:
Can be found on the net otherwise any version will do.
Filemon:
Any version will do.
Symantech ResEdit or Borlands Resource workshop.
Both are very easy to find on the net.



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

  1. You can use Ultraedit for 45 days.
  2. You can register Ultraedit by entering a name and serialnumber
  3. Once the name and serialnumber has been entered, Ultraedit needs to be restarted in order to validate the serialnumber and name
  4. There's a NagScreeN poping up at startup
  5. The About-box show Unregistered
Load the target (uedit32.exe) in IDA. Make sure that LoadResources is checked and that System Dll directory is correct. Usually c:\windows\system\.

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:

Lets begin with finding the NagScreeN. Use Imidiate Search in IDA and search for 6E which is the handle to the NagScreeN. You'll find it at:


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], ebx
Hmmn 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 NagScreeN
How 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.

  1. Uedit32.ini
  2. Uedit32.cfg

The a copy of the ini file is also stored in uedits directory. Uedit first tries to locate the ini file in the windows directory if that fails it loads the ini file from uedits directory.

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,531
The 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:004517EBo
Investigating these three locations you'll find that: If you examine the the code that reads mousepos from the ini file you'll see that the value of mousepos is stored in two variables.

: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:00451863r
Daysleft 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.
So where is the first reference to the mousepos variables?

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 ; Continue

So 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, eax
dword_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.


Now you know why the jump at 00451732 is safe to patch. If the jump is not executed, the ini file will not be updated. Therefore (assuming you're still on a trial period) uedit will never receive a new mousepos value for the days that passes. And you'll be rid of the NagScreeN. The only thing that's left is the name in the about box that say's: Unregistered.

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.

These bytes are references from four location. Examine each of them and look for nearby code that opens a file.

As you can see of the references one of them is located in the same area as the the code that handles the 45 days limit. Here the reg file is deleted, when some condition is ture. We are looking for when the REG file is opened and read.

At 405D47 the reg file is opened for writing.
405F57 has something to do with uninstalling Ultraedit.
At 440E20 it's opened for reading!!

If the file was successfully opened, we start to handle the regfiles data at 440EC9. Put a mark (alt-m) here and name it to Regfile handler.

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 loop

If 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 bytes
To 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], eax
When 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  retn
Back 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.

Let's review:

.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

Investigating the reference:


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)

Next we enter a loop that will loop 60 times.

Here's pseudocode for what happens:

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


Thats all that's made for our Name->Serial conversion.

Uedit then compares buffer with RegSerial_string and if they equal we become registered. Else uedit compares buffer_2 with RegSerial and if they match we are registered otherwise we run as unregistered. So we have two valid serialnumbers stored in buffer and buffer2.

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 :)

that it is a windowprocedure.
So where is the first reference to the mousepos variables?