SOLUTION FOR THE 1998 +HCU STRAINER

            [MSMONEY 3.0p] [MSMONEY 5.0p] [MSPROJECT 4.1]

                  "What do you want to crack today ?"
                                                    -TOXINE


Each 3 modules was written separetly to be standalone so that
readers can thereby refer in any sequence order they want.
Nevertheless, "interconnections" should be made between them
since history DOES hold a big place in time-protection evolution. :-)
Shall we begin...

[MSMONEY 3.0p]
.------------------------.
| STEP 1 : INSTALLATION  |
'------------------------'
The one we'll be using has been taken from a french magazine in my
local library. We proceed as usual and install it. To make sure,
you might like to spy on the installation program and see what
happened (the file created and registry entries). But here, we find
nothing suspicious which is not much of a surprise since back then
the reg.dat wasn't used that much (though, some sharewares did in
fact use it, Plug-In was among them). Anyway, let's run the program
and see what we have.
.-------------------------.
| STEP 2 :   EXAMINATION  |
'-------------------------'
As we are running MNYDEMO.EXE, we see a nagscreen popping up (definitely 
have to go, for now we'll leave it there). We want to maintain a pause,
giving us control over the program flow, which would be useful later on
as we put BreakPoinTs inside winice. Here's what I noticed the first
time I ran MSMONEY 94 Version d'‚valuation:

(don't be annoyed by the french words, as it has keywords which i'm
sure you'll understand :-)

	[Inside the trial period]
1.  Nagscreen (must go !)
2. "Version d'‚valuation" in the title bar (esthetic)
3. "date limite d'utilisation"  Aboutbox (where does this date come from?)
4. "Minitel non disponible" nagscreen (Could it be crippled ?)
5. "date tap‚e ult‚rieure … date expiration" notice (must go !)
	[Exceeded the trial period]
6.  "est arriv‚e … expiration" notice

That's  only  an  overview of what the protection  offers  us,  looking
further inside the MNYDEMO.EXE we find extra things like:

7.  cette version s'achŠve dans %s jour(s).
8.  La date du systŠme doit ˆtre soit 1994, soit 1995 pour...
9.  La date doit ˆtre correcte [...] Corrigez la date de votre systeme...
10. ne peut fonctionner si vous n'entrez pas de date...
    (I personally would not have thought of this if I did not look 
     inside MSMONEY.EXE)

Snooping  inside a .EXE file is not a bad idea after all. 
What I mean is that we already know that we can crack :
1.  Nagscreen (always there, part of the demo)
2.  "Evaluation" word in title bar (not crack, but rather whack)
4.  "Minitel" nagscreen (part of the demo) 
5.  60-days exceeding range date field (must CMP something to show this)
6.  60-days has expired (must CMP something to show this)
7.  will expire in %1 days (must CMP something to show this)
8.  system date must be in 1994, 1995 (must CMP with the actual year)
9.  system date before installation date (must CMP with the actual date)
10. empty date field (must somehow distinguish an empty field from a date)
.---------------------------------.
| STEP 3:   WIN16 RESOURCES DATA  |
'---------------------------------'
OK,   how  about fetching the ordinal ID of these dialog  boxes?  Each
dialog  box has its own #ID which is used throughout the program.   It
would  be  nice  to have these ordinal ID's which  can  be  later   on
searched  inside our dead listing. I used an old version  of   Borland
Resource Workshop 4.0. Here we go:
:         :
| DIALOG  |   the HEX equivalent
|     ... |    |
|    1170 | (0492h) "Information de ventes", Purchase information
|    1171 | (0493h) "Money version d'‚valuation", the welcome nagscreen
|    1172 | (0494h) "Avertissement de limite", the Notice Of Expiration!
|    1173 | (0495h) "A propos de", the AboutBox
|     ... |
:_________:
Keep these hex numbers in note for later use. So What next?
.---------------------------.
| STEP 4:    DEAD LISTING   |
'---------------------------'
Let's disassemble the code with W32Dasm85... I think I'll take a nap :-)
As we gain back our physical and mental energy and as we are snoozing
think about how the program interacts with the time-protection code
snippet...
Refreshing, indeed! Let's pursue. We'll obtain a huge 18 megs file.
Scanning a little bit inside the +++DIALOG INFORMATION+++, we see this:

Name: DialogID_0494, # of Controls=004, Caption:"Avertissement de limite de"
     001 - ControlID:02CE, Control Class:"" Control Text:"Votre version d'é"
     002 - ControlID:02CF, Control Class:"" Control Text:"Pour continuer à "
     003 - ControlID:0001, Control Class:"" Control Text:"OK" 
     004 - ControlID:02D0, Control Class:"" Control Text:"&Informations..." 

Now, let's search for this DialogID_0494 dialog box:

(Note that I've stripped off the code section allowing longer comments,
for your own reading pleasure :-)

-===[1/3]===[DIALOG: 0494]=========================================-

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0006.00A2(C)
|
:0006.00A7  sub ax, 0291 ; what's this?
:0006.00AA  jne 00AF     ; do not jump NoE (GOOD) 
:0006.00AC *jmp 028A     ; jump to NoE (BAD!)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0006.00AA(C)
|
:0006.00AF  jmp 02A4

  [...]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0006.00AC(U)
|_____DISPLAY_"NOTICE_OF_EXPIRATION"_DIALOGBOX__(BAD)_______:
:0006.028A  push di
:0006.028B  call USER.GETPARENT
:0006.0290  push ax

* Possible Reference to Dialog: DialogID_0494 
                                  |
:0006.0291 *push 0494 ; DialogID: "Notice of expiration"
:0006.0294  push word ptr [bp+0A]
:0006.0297  push word ptr [bp+08]
:0006.029A  push word ptr [bp+06]
:0006.029D  call USER.SENDMESSAGE
:0006.02A2  jmp 02B8

-===[2/3]===[DIALOG: 0494]=========================================-

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.1820(C)
|
:0008.17E2  mov ax, [bp-0C] ; Is this the CurrentDate?  
:0008.17E5**cmp [bp-10], ax ; IF (CurrentDate?) BELOW (ExpireDate?)
:0008.17E8 *jb 1830         ; JUMP OVER NoE (GOOD) 
                            ; ELSE (BAD)
* Possible Reference to Dialog: DialogID_0494 
                                  |
:0008.17EA *push 0494       ; DialogID: "Notice of expiration"
:0008.17ED  push SEG ADDR of Segment 0028
:0008.17F0  push 15C0
:0008.17F3  push word ptr [0B54]
:0008.17F7  push 0000
:0008.17F9  push 0000
:0008.17FB  push 0000
:0008.17FD  call 0005.013A
:0008.1802  jmp 186C

-===[3/3]==[DIALOG: 0494]==========================================-

* Possible Ref to Menu: MAINMENU, Item: "Opérations futures"
                                  |
:0053.246F  push 0002
:0053.2471  call 0006.0F0E             ; (?)
:0053.2476  cmp word ptr [bp-08], 0000 ; IF (?) NOT EQUAL (0000) THEN
:0053.247A *jne 247F                   ; JUMP TO NoE (BAD)
:0053.247C  jmp 254D                   ; ELSE, JUMP AWAY (GOOD)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0053.247A(C)
|________DiSPLAY_NOTICE_OF_EXPIRATION_MESSAGEBOX_(BAD)_____:
:0053.247F  mov bx, [bp-04]
:0053.2482  push word ptr [bx+18]
:0053.2485  call USER.GETPARENT
:0053.248A  push ax

* Possible Reference to Dialog: DialogID_0494 
                                  |
:0053.248B *push 0494             ; DialogID: "Notice of Expiration"

* Possible Ref to Menu: MAINMENU, Item: "Livre de comptes" ; what's this? 
                                  |
:0053.248E  push 0001
:0053.2490  push 0000
:0053.2492  push 0000
:0053.2494  call USER.SENDMESSAGE

We see a CMP at 0008.17E5 which decide if MNYDEMO has expired or not.
Gee... I wonder what value actually holds [bp-0C] and [bp-10]?
So, let's fire up winice. 
.------------------------.
| STEP 5:   WINICING     |
'------------------------'
I'll use good old winice 1.3+ inside Win3.1 (please don't bitch me
because I don't use da newest program, it's the cracker that counts
here, right? :=). Anyway, I do what I can with what I have.

Here, we are trying to find the remaining DialogBox & MessageBox of 
our time-protection which are:

1. intro nagscreen                           DIALOGBOX:  ID_????
4. "Minitel non disponible" nagscreen        MESSAGEBOX: ID_????
5. "date tap‚e … ult‚rieur date expiration"  MESSAGEBOX: ID_????
7. "cette version s'achŠve dans %s jour(s)"  MESSAGEBOX: ID_????
8. "systŠme doit etre soit 1994, soit 1995"  MESSAGEBOX: ID_????
9. "Corrigez la date de votre systeme"       MESSAGEBOX: ID_????
10."pas fonctionner si n'entrez pas de date" MESSAGEBOX: ID_????

Whenever a breakpoint occurs see if you can recreate that particular 
state. If so, then you should probably understand it, I did :-)  
If not, try to pin-point the location gradually using non-sticky
breakpoints and using the STACK command. As you reach the outer layer 
of a call, your understanding of the piece of code should get clearler
and clearler. Do the usual thing: change OS date, say in 2001 then 
backwards in 1991 and see what happens. Here's what I can understand
by breakpointing inside winice:

This part concerns the 5C00/5E00/6000 series (detailed inside the
DATE-ENCRYPTION section), the MESSAGEBOX and the remaining DIALOGBOX
which are all related to the time-protection. Each section are
separated by ==== and sorted in ascending order of address:

-===[1/2]==[MESSAGEBOX: 4AF5?]==[DIALOG: 0495/0492]================-
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0001.21C5(C)
|
* Possible Ref to Menu: MAINMENU, Item: "Livre de comptes"
                                  |
:0001.21CA  push 0001
:0001.21CC  jmp 2198

___NO_XREF,_THIS_COULD_MEAN_AN_INDIRECT_JUMP.____:
:0001.21CE  push 0495 ; DialogID: "About" 
:0001.21D1  jmp 21EF  ; Jump to display

___AGAIN,_NO_XREF,_THIS_COULD_MEAN_AN_INDIRECT_JUMP.____:
:0001.21D3  nop
:0001.21D4 *push 4AF5 ; MessageBox: "no minitel" 
:0001.21D7  push 07D2 ; I, personnaly, never encountered this code
:0001.21DA  push 0000 ; inside Winice. But, it's still the MessageBox 
:0001.21DC  push 0000 ; that correspond to "no minitel", just look at 
:0001.21DE  push 0000 ; the "push 07D2" which is similar to the actual
:0001.21E0  push 0000 ; encountered code at 0030.0CB4 (further down).
:0001.21E2  push 0040
:0001.21E4  call 0064.B144  ; USER.MessageBox
:0001.21E9  jmp 2299        ; Continue...

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0001.183B(U)
|_______THE_USER_HAS_CLICKED_[MB_YES]_BUTTON______:
:0001.21EC *push 0492   ; DialogID: "Purchase Information"

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0001.21D1(U)
|
:0001.21EF  push SEG ADDR of Segment 0028
:0001.21F2  push 15C0
:0001.21F5  jmp 1AB9

-====[DIALOG: 0493]===============================================-
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0002.0E5C(C)
|
:0002.0EAE  cmp word ptr [bp+06], 0001
:0002.0EB2  je 0EBA
:0002.0EB4  cmp word ptr [bp+06], 0009
:0002.0EB8  jne 0EEA

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0002.0EB2(C)
|
:0002.0EBA  push word ptr [0B54]
:0002.0EBE  cmp word ptr [0B72], 0001 ; humm...
:0002.0EC3  sbb ax, ax                ; is something missing?
:0002.0EC5  and al, FE
:0002.0EC7  add ax, 0003
:0002.0ECA  push ax
:0002.0ECB  call USER.SHOWWINDOW 
:0002.0ED0 *push 0493            ; DialogID: Nagscreen (part of demo)
:0002.0ED3  push SEG ADDR of Segment 0028
:0002.0ED6  push 15C0      ; NOTE: there are about 33 other "push 0493"
:0002.0ED9  push word ptr [0B54] ; occurences around!!!  "push 15C0" is
:0002.0EDD  push 0000            ; the 2nd criteria we should look for 
:0002.0EDF  push 0000            ; to eliminate the other possibilities
:0002.0EE1  push 0000            ; (remember: the NoE dialog at 0008.17EA :-)
:0002.0EE3  call 0005.013A       ; USER.DialogBoxParam
:0002.0EE8  jmp 0EF6             ; (Or, we can simply pin-point using winie) 
       
-====[1/2]==[5C00/5E00/6000 series]=================================-
* Referenced by a CALL at Addresses:
|:0008.0889, :0008.16D6, :0008.172B
|
:0008.14BC  enter 0006, 00
:0008.14C0  cmp word ptr [bp+04], 0000 ; what's this?
:0008.14C4 *je 14E4         ; JUMP AWAY (BAD?)
:0008.14C6  mov ah, [64D7]  ; Put [ExpireDate] in AH
:0008.14CA  and ax, FE00    ; Clear AL
:0008.14CD  mov [bp-06], ax ; Put [New_ExpireDate] in [bp-06]
:0008.14D0  cmp ax, 5C00    ; IF (CurrentDate) EQUAL 1994 THEN
:0008.14D3 *je 14DF         ; JUMP AWAY (GOOD)
:0008.14D5  cmp ax, 5E00    ; IF (CurrentDate) EQUAL 1995 THEN
:0008.14D8 *je 14DF         ; JUMP AWAY (GOOD)
:0008.14DA  cmp ax, 6000    ; IF (CurrentDate) NOT EQUAL 1996 THEN
:0008.14DD *jne 14E4        ; JUMP AWAY (BAD!)

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0008.14D3(C), :0008.14D8(C)
|                                    how generous of them!
                                      |
_____WE_ARE_IN_1994,_1995_OR_EVEN_IN_1996_(GOOD)_______:
:0008.14DF  mov ax, [64D6] ; AX= either 5C, 5E or 60)
:0008.14E2  jmp 152F

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0008.14C4(C), :0008.14DD(C)
|
_____WE_ARE_NOT_IN_1994_NEITHER_1995_NOR_1996_(BAD!)___:
:0008.14E4  lea ax, [bp-04]
:0008.14E7  push ax
:0008.14E8  call 0081.0552
:0008.14ED  mov bx, ax        
:0008.14EF  mov ax, ss:[bx]  ; ss:[bx]= CurrentDate= ax
:0008.14F2  mov [bp-02], ax  ; Put CurrentDate 
:0008.14F5  mov ah, [bp-01]  ; Strip CurrentYear to ah
:0008.14F8  and ah, FE	     ; Clear al, AX=??00
:0008.14FB  cmp ah, 5C       ; Are we in 1994?
:0008.14FE  je 151C          ; yes, then jump
:0008.1500  mov ah, [bp-01]  ; Put current year in ah
:0008.1503  and ah, FE       ; Clear al, AX=??00
:0008.1506  cmp ah, 5E       ; Are we in 1995?
:0008.1509  je 151C          ; yes, then jump
:0008.150B *push 0917        ; what messagebox is it? 
:0008.150E  push 0000        
:0008.1510  call 0064.AD1A   ; user.messagebox inside
:0008.1515 *xor ax, ax       ; OH..OH.. RETURN BAD FLAG (AX=00)
:0008.1517  leave
:0008.1518  ret 0004

:0008.151B 90                     nop

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0008.14FE(C), :0008.1509(C)
|
:0008.151C  push word ptr [bp-02]
:0008.151F  push 003C
:0008.1521  lea ax, [bp-04]
:0008.1524  push ax
:0008.1525  call 0081.05D8
:0008.152A  mov bx, ax
:0008.152C  mov ax, ss:[bx]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.14E2(U)
|_________WE_ARE_EITHER_IN_1994,_1995_OR_1996_(GOOD)______:
:0008.152F  mov bx, [bp+06]
:0008.1532  mov [bx], ax

* Possible Ref to Menu: MAINMENU, Item: "Livre de comptes" ; what's this???
                                  |
:0008.1534 *mov ax, 0001   ; RETURN GOOD FLAG (AX=0001)
:0008.1537  leave
:0008.1538  ret 0004
  
-====[2/2]==[5C00/5E00/6000 series]=====[MESSAGEBOX: 0918]========-
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.1691(C)
|
:0008.1696  mov ax, [si+0224]
:0008.169A  mov [bp-0C], ax
:0008.169D  lea ax, [bp-02]
:0008.16A0  push ax
:0008.16A1  call 0081.0552      ; 
:0008.16A6  mov bx, ax          ; ax= bx= CurrentDate stack offset
:0008.16A8  mov ax, ss:[bx]     ; ss:[bx]= ax= CurrentDate(Word)
:0008.16AB  mov [bp-10], ax     ; ax= [bp-10]= CurrentDate(Word)
:0008.16AE  mov ah, [bp-0B]     ; ah= [bp-0B]= CurrentDate(HighByte)
:0008.16B1  and ax, FE00        ; Clear al, AX=??00
:0008.16B4  mov [bp+FE8C], ax   ; Put CurrentDate somewhere else 
:0008.16B8 *cmp ax, 5C00        ; Are we currently in 1994?
:0008.16BB *jne 16C0            ; If not, Jump (BAD)  
:0008.16BD  jmp 1804            ; Else, (GOOD) 

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.16BB(C)
|
:0008.16C0 *cmp ax, 5E00        ; Are we currently in 1995? 
:0008.16C3 *jne 16C8            ; If not, Jump (BAD)
:0008.16C5  jmp 1804            ; Else, (GOOD)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.16C3(C)
|
:0008.16C8 *cmp ax, 6000        ; Are we currently in 1996?
:0008.16CB *jne 16D0            ; If not, Jump (BAD)
:0008.16CD  jmp 1804            ; Else, (GOOD)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.16CB(C)
|
:0008.16D0  lea ax, [bp-0C]
:0008.16D3  push ax

   [...]

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0008.16BD(U), :0008.16C5(U), :0008.16CD(U)
|
|_________WE_ARE_EITHER_IN_1994,_1995_or_1996,_WHICH_IS_GOOD________:
:0008.1804  push word ptr [bp-10]
:0008.1807  push 003C
:0008.1809  lea ax, [bp-04]
:0008.180C  push ax
:0008.180D  call 0081.05D8  ;
:0008.1812  mov bx, ax      ; ax= bx= CurrentDate offset
:0008.1814  mov ax, ss:[bx] ; CurrentDate(WORD)= ax
:0008.1817  mov [bp-02], ax ; [bp-02]= CurrentDate  
:0008.181A  mov ax, [bp-0C] ; [bp-0C]= ax= InstallationDate
:0008.181D  cmp [bp-02], ax ; IF (CurrentDate) BEFORE (InstallationDate)
:0008.1820  jnb 17E2        ; Jump away (GOOD)
:0008.1822 *push 0918 ; MessageBox: "system date before installation date."
:0008.1825  push 0000 ;               (Oouuu... i'm scared... :-)
:0008.1827  call 0064.AD1A ; USER.messagebox inside         
:0008.182C  jmp 1607

-=====[MessageBox: 4919]=========================================-
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0008.17E8(C)
|
:0008.1830  push ax
:0008.1831  call 0081.08E6
:0008.1836  push word ptr [bp-10]
:0008.1839  mov si, ax
:0008.183B  call 0081.08E6
:0008.1840  sub si, ax                                8 DAYS LEFT?
:0008.1842  mov [bp-02], si                           |
:0008.1845  cmp si, 0007     ; IF (DaysLeft) GREATER (7) THEN
:0008.1848 *jg 186C          ; JUMP AWAY (GOOD)
:0008.184A  mov al , [bp-02] ; ELSE, SHOW MESSAGEBOX (BAD)
:0008.184D  add al, 30
:0008.184F  mov [bp-12], al 
:0008.1852  mov byte ptr [bp-11], 00
:0008.1856 *push 4919        ; MessageBox: "will expire in %s days"
:0008.1859  push 07D2
:0008.185C  lea ax, [bp-12]
:0008.185F  push ss
:0008.1860  push ax
:0008.1861  push 0000
:0008.1863  push 0000
:0008.1865  push 0040
:0008.1867  call 0064.B144

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0008.1693(U), :0008.1802(U), :0008.1848(C)
|
_________MORE_THAN_8_DAYS_LEFT__(GOOD)_:
:0008.186C  mov bx, [bp+08]
:0008.186F  cmp word ptr [bx+06], 0000
:0008.1873  jne 1878
:0008.1875  jmp 1D4E

-===[ MESSAGEBOX: 0916 & 091A]====================================-
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0014.2B47(C)
|
:0014.2B4C  cmp word ptr [bp+08], 0000 ; should we care?...but wait...
:0014.2B50  je 2B6F                    ; hey, it jumps over the 2 CHECKs
:0014.2B52  test byte ptr [17AC], 01   ; it is in fact important!
:0014.2B57  jne 2B6F                                       ( -1  )
:0014.2B59 *cmp word ptr [70D8], FFFF ; IF (DateField) NOT (EMPTY) 
:0014.2B5E  jne 2B63                  ; JUMP TO NEXT (GOOD FOR NOW)
:0014.2B60 *jmp 35F8                  ; ELSE, OH..OH..JUMP AWAY (BAD) 



* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0014.2B5E(C)
|_OK,THE_DATEfIELD_ISN'T_EMPTY_NOW_CHECK_IF_WE'RE_IN_THE_TRIAL_PERIOD?__:
:0014.2B63  mov ax, [64D6]   ; ax= ExpireDate 
:0014.2B66 *cmp [70D8], ax   ; IF (DateField) BELOW (ExpireDate) THEN
:0014.2B6A  jb 2B6F          ; JUMP OVER (GOOD)
:0014.2B6C *jmp 3606         ; ELSE, OH...NOoo... JUMP AWAY (BAD)

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0014.2B50(C), :0014.2B57(C), :0014.2B6A(C)
|___:-)___WE_SUCCESSFULLY_PASS_THE_2_CHECKS,_WE_MAY_CONTINUE_(GOOD)_:
:0014.2B6F  cmp word ptr [bp-10], 0000
:0014.2B73  je 2B7E          
:0014.2B75  mov ax, [70D8]   ; [70D8]=ax=[70DA]
:0014.2B78  mov [70DA], ax   ;
:0014.2B7B  jmp 354D         ; BYE... 

  [...]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0014.2B60(U)
|
__:-(___DISPLAY_MESSAGEBOX_091A_(BAD)______:
:0014.35F8 *push 091A   ; MessageBox: "required you enter a date" 
:0014.35FB  push 0000
:0014.35FD  call 0064.AD1A
:0014.3602  jmp 37BA

:0014.3605  nop

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0014.2B6C(U)
|__:-(___DISPLAY_MESSAGEBOX_0916_(BAD)______:
:0014.3606 *push 0916     ; MessageBox: "date field exceeded 60-days"
:0014.3609  push 0000
:0014.360B  push 0000
:0014.360D  push 0000
:0014.360F  call 0064.AF58

* Possible Ref to Menu: MAINMENU, Item: "Synthèse Investissements"
                                  |

* Possible Reference to Dialog: DialogID_0435, CONTROL_ID:0006, "&Oui"
                                  |
:0014.3614  cmp ax, 0006  ; IF (MB_YES clicked) THEN
:0014.3617  je 361C       ; JUMP to display Purchase Information
:0014.3619  jmp 37BA      ; ELSE exit

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0014.3617(C)
|
:0014.361C *push 0492 ;Ahh, yes! that's our "Information de ventes" dialogbox
:0014.361F  push SEG ADDR of Segment 0028
:0014.3622  push 15C0
:0014.3625  mov bx, [bp+0C]
:0014.3628  push word ptr [bx+18]
:0014.362B  push 0000
:0014.362D  push 0000
:0014.362F  push 0000
:0014.3631  call 0005.013A ; user.MessageBox inside
:0014.3636  jmp 37BA       ; BYE.

-===[2/2]===[MESSAGEBOX: 4AF5?]======================================-
:0030.0CB3  nop

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0030.0AAF(U), :0030.0ACB(U), :0030.0AEA(U), :0030.0B09(U), :0030.0B28(U), 
|:0030.0B47(U), :0030.0B66(U), :0030.0B85(U), :0030.0BA4(U), :0030.0BC3(U), 
|:0030.0BE2(U), :0030.0C01(U), :0030.0C20(U), :0030.0C3D(C), :0030.0C59(C), 
|:0030.0CA9(C)
|_____GEE,_SO_MUCH_XREF_!!!_(BAD)________:
:0030.0CB4 *push 4AF5  ; MessageBox: "no minitel" (at startup)
:0030.0CB7  push 07D2  ; Hummm... I wonder what was the REAL reason 
:0030.0CBA  push 0000  ; behind the invention of minitel... :=)
:0030.0CBC  push 0000
:0030.0CBE  push 0000
:0030.0CC0  push 0000
:0030.0CC2  push 0040
:0030.0CC4  call 0064.B144             ; USER.MessageBox inside
:0030.0CC9  cmp word ptr [3392], 0020  ; Continue...
:0030.0CCE  jb 0CD9
:0030.0CD0  push word ptr [3392]
:0030.0CD4  call KERNEL.FREELIBRARY
-----------------------------------------------------[END.]----------

So far, so good. We have found all the 7 missing ID_???? we were 
looking for, here it is:
1. intro nagscreen                           DIALOGBOX:  ID_0492
4. "Minitel non disponible" nagscreen        MESSAGEBOX: ID_4AF5?
5. "date tap‚e … ult‚rieure date expiration" MESSAGEBOX: ID_0916
7. "cette version s'achŠve dans %s jour(s)"  MESSAGEBOX: ID_4919?
8. "systŠme doit ˆtre soit 1994, soit 1995"  MESSAGEBOX: ID_0917
9. "Corrigez la date de votre systŠme"       MESSAGEBOX: ID_0918
10."pas fonctionner si n'entrez pas de date" MESSAGEBOX: ID_091A
.-------------------------------------.
| STEP 5.5:   5C00/5E00/6000 series?  |
'-------------------------------------'
 (Refer to DATE ENCRYPtION section near the end of this document)
  MONEY94 use the exact date format as MONEY97 :-)

     5C00 = 1/1/1994 or 1994
     5E00 = 1/1/1995 or 1995
     6000 = 1/1/1996 or 1996
.----------------------------------.
| STEP 6:   ESTABLISHiNG A CRACK   |        
'----------------------------------'
Now, that we understand most of its time-protection scheme let's make a
crack for M$MONEY 94:

  .--------------------------------------------------------------------.
  | Crack for [Microsoft M$-MONEY V3.0p DEMO]  by TOXINE (4 juli 1997) |
  |--------------------------------------------------------------------|
  | File:      [MNYDEMO.EXE] [1205888 bytez] [2-08-94]  [12:00a]       |
  '--------------------------------------------------------------------'
For those who prefer to see the actual ASM code patching look further down...

///HEX PATCHING///

Patch at OFFSET 4394
original = 68 F5 4A
da'crack = E9 12 00
Patch at OFFSET 63F1
original = 93 04
da'crack = 00 00
Patch at OFFSET 11625
original = 1E
da'crack = 0
Patch at OFFSET 1163E
original = 5
da'crack = 0
Patch at OFFSET 1181C
original = 3
da'crack = 0
Patch at OFFSET 11824
original = 3
da'crack = 0
Patch at OFFSET 1182C
original = 3
da'crack = 0
Patch at OFFSET 11948
original = 72
da'crack = EB
Patch at OFFSET 11980
original = 73
da'crack = EB
Patch at OFFSET 119A8
original = 7F
da'crack = EB
Patch at OFFSET 2DCFE
original = 75
da'crack = EB
Patch at OFFSET 2DD0A
original = 72
da'crack = EB
Patch at OFFSET 4ABB4
original = 68 F5 4A
da'crack = E9 12 00
Patch at OFFSET AEB42
original = 75
da'crack = EB
Patch at OFFSET AEB59
original = 72
da'crack = EB

///ASM PATCHING (with HIEW 5.60)///

Hiew is a VERY powerful tool, I mean, we can code ASM on-the-fly, XREF,
NE & PE-header, use Global or Local address, unlimited filesize... %-)
And of course, at speed only DOS can do: DEFINITELY A BONUS PRODUCTIVITY!
What else do we want? It would be even nicer if Hiew could  display real
Floating-Point instructions (D8, DD, DF series...) instead of displaying
'esc', eventhough this does not prevent us from patching, still, i find
it annoying ;-)
For those unfamiliar with Hiew (who?), press F3 to edit, position your
cursor then press TAB to switch into ASM coding. Now, can you feel its
POWER!

(Cracked code marked by *  followed by comments)
° 0000438A: 6A01                         push   001
° 0000438C: EBCA                         jmps   000004358   ---------- (4)
° *000438E:*68E803                      *push   003E8
° 00004391: EB1C                         jmps   0000043AF   ---------- (5)
° 00004393: 90                           nop
° *0004394:*E91200                      *jmp    0000043A9   ---------- (6)
° 00004397: 68D207                       push   007D2
° 0000439A: 6A00                         push   000
° 0000439C: 6A00                         push   000
° 0000439E: 6A00                         push   000
° 000043A0: 6A00                         push   000
° 000043A2: 6A40                         push   040
° 000043A4: 9A44B12E1F                   call   040:0B144   ---------- (7)
° 000043A9: E9AD00                       jmp    000004459   ---------- (8)
Comment: By jumping over the "no minitel" nagscreen, we crack it
         without changing our stack pointer.
NOTICE: At address :0000438E the "push 00495" instruction has been
        replaced by "push 003E8". This replaces the AboutBox in demo
        version by a retail version AboutBox.
        (not a crack, but just a whack)
---------------------------------------------------------------------
° 000063E5: 24FE                         and    al,0FE  ;"þ"
° 000063E7: 050300                       add    ax,00003
° 000063EA: 50                           push   ax
° 000063EB: 9AF20E0000                   call   SHOWWINDOW
° *00063F0:*680000                      *push   00000
° 000063F3: 68FFFF                       push   0FFFF  ;"ÿÿ"
° 000063F6: 68C015                       push   015C0
° 000063F9: FF36540B                     push   w,[00B54]
° 000063FD: 6A00                         push   000
° 000063FF: 6A00                         push   000
° 00006401: 6A00                         push   000
° 00006403: 9A3A01FFFF                   call   005:0013A   ---------- (3)
° 00006408: EB0C                         jmps   000006416   ---------- (4)
This should crack the intro nagscreen. Note: I didn't patch the call 
at :00006403 cuz it would have changed the stack. Nor, did I RETF (CBh)
inside the call which I would have done if it wasn't used by other
dialogboxes.
---------------------------------------------------------------------
° 0001161B: 90                           nop
° 0001161C: C8060000                     enter  00006,000
° 00011620: 837E0400                     cmp    w,[bp][00004],000
° *0011624:*7400                        *je     000011626   ---------- (1)
° 00011626: 8A26D764                     mov    ah,[064D7]
° 0001162A: 2500FE                       and    ax,0FE00
° 0001162D: 8946FA                       mov    [bp][-0006],ax
° 00011630: 3D005C                       cmp    ax,05C00
° 00011633: 740A                         je     00001163F   ---------- (2)
° 00011635: 3D005E                       cmp    ax,05E00
° 00011638: 7405                         je     00001163F   ---------- (3)
° 0001163A: 3D0060                       cmp    ax,06000
° *001163D:*7500                        *jne    00001163F   ---------- (4)
° 0001163F: A1D664                       mov    ax,[064D6]
° 00011642: EB4B                         jmps   00001168F   ---------- (5)
° 00011644: 8D46FC                       lea    ax,[bp][-0004]
° 00011647: 50                           push   ax
° 00011648: 9A52052815                   call   051:00552   ---------- (6)
° 0001164D: 8BD8                         mov    bx,ax
° 0001164F: 368B07                       mov    ax,ss:[bx]
1 time: This cracks the "must be in 1994 or 1995" notice.
By patching at :0001163D, we don't need to change the two previous "je". 
While winicing, I never encountered the jump at address :00911624,
so I cannot tell for sure if it is ever executed by the demo.
Since it jump to the BAD_GUY area, I better patch it anyway.
---------------------------------------------------------------------
° 00011811: 2500FE                       and    ax,0FE00
° 00011814: 89868CFE                     mov    [bp][0FE8C],ax
° 00011818: 3D005C                       cmp    ax,05C00
° *001181B:*7500                        *jne    00001181D   ---------- (2)
° 0001181D: E94401                       jmp    000011964   ---------- (3)
° 00011820: 3D005E                       cmp    ax,05E00
° *0011823:*7500                        *jne    000011825   ---------- (4)
° 00011825: E93C01                       jmp    000011964   ---------- (5)
° 00011828: 3D0060                       cmp    ax,06000
° *001182B:*7500                        *jne    00001182D   ---------- (6)
° 0001182D: E93401                       jmp    000011964   ---------- (7)
° 00011830: 8D46F4                       lea    ax,[bp][-000C]
° 00011833: 50                           push   ax
° 00011834: 6A01                         push   001
2 time: This cracks the "must be in 1994 or 1995" notice.
---------------------------------------------------------------------
° 00011942: 8B46F4                       mov    ax,[bp][-000C]
° 00011945: 3946F0                       cmp    [bp][-0010],ax
° *0011948:*EB46                        *jmps   000011990   ---------- (4)
° 0001194A: 689404                       push   00494
° 0001194D: 686D05                       push   0056D
° 00011950: 68C015                       push   015C0
° 00011953: FF36540B                     push   w,[00B54]
° 00011957: 6A00                         push   000
° 00011959: 6A00                         push   000
° 0001195B: 6A00                         push   000
° 0001195D: 9A3A01B111                   call   005:0013A   ---------- (5)
° 00011962: EB68                         jmps   0000119CC   ---------- (6)
This cracks the NoE.
---------------------------------------------------------------------
° 00011972: 8BD8                         mov    bx,ax
° 00011974: 368B07                       mov    ax,ss:[bx]
° 00011977: 8946FE                       mov    [bp][-0002],ax
° 0001197A: 8B46F4                       mov    ax,[bp][-000C]
° 0001197D: 3946FE                       cmp    [bp][-0002],ax
° *0011980:*EBC0                        *jmps   000011942   ---------- (2)
° 00011982: 681809                       push   00918
° 00011985: 6A00                         push   000
° 00011987: 9A1AAD6A18                   call   040:0AD1A   ---------- (3)
° 0001198C: E9D8FD                       jmp    000011767   ---------- (4)
This cracks the "system date before installation date. Please correct" 
---------------------------------------------------------------------
° 0001199B: 9AE608EB14                   call   051:008E6   ---------- (3)
° 000119A0: 2BF0                         sub    si,ax
° 000119A2: 8976FE                       mov    [bp][-0002],si
° 000119A5: 83FE07                       cmp    si,007
° *00119A8:*EB22                        *jmps   0000119CC   ---------- (4)
° 000119AA: 8A46FE                       mov    al,[bp][-0002]
° 000119AD: 0430                         add    al,030  ;"0"
This cracks the "%s days remaining" notice.
---------------------------------------------------------------------
² 0002DCE3: F6470C06                     test   b,[bx][0000C],006
° 0002DCE7: 7403                         je     00002DCEC   ---------- (1)
° 0002DCE9: E9010A                       jmp    00002E6ED   ---------- (2)
° 0002DCEC: 837E0800                     cmp    w,[bp][00008],000
° 0002DCF0: 741D                         je     00002DD0F   ---------- (3)
° 0002DCF2: F606AC1701                   test   b,[017AC],001
° 0002DCF7: 7516                         jne    00002DD0F   ---------- (4)
° 0002DCF9: 833ED870FF                   cmp    w,[070D8],0FF  ;"ÿ"
° *002DCFE:*EB03                        *jmps   00002DD03   ---------- (5)
° 0002DD00: E9950A                       jmp    00002E798   ---------- (6)
° 0002DD03: A1D664                       mov    ax,[064D6]
° 0002DD06: 3906D870                     cmp    [070D8],ax
° *002DD0A:*EB03                        *jmps   00002DD0F   ---------- (7)
° 0002DD0C: E9970A                       jmp    00002E7A6   ---------- (8)
° 0002DD0F: 837EF000                     cmp    w,[bp][-0010],000
° 0002DD13: 7409                         je     00002DD1E   ---------- (9)
1 time: This cracks the "no date in trial" & the "exceed 60-days datefield".
---------------------------------------------------------------------
² 0004ABAB: B80100                       mov    ax,00001
° 0004ABAE: A3CC33                       mov    [033CC],ax
° 0004ABB1: EB7B                         jmps   00004AC2E   ---------- (2)
° 0004ABB3: 90                           nop
° *004ABB4:*E91200                      *jmp    00004ABC9   ---------- (3)
° 0004ABB7: 68D207                       push   007D2
° 0004ABBA: 6A00                         push   000
° 0004ABBC: 6A00                         push   000
° 0004ABBE: 6A00                         push   000
° 0004ABC0: 6A00                         push   000
° 0004ABC2: 6A40                         push   040
° 0004ABC4: 9A44B1790A                   call   040:0B144   ---------- (4)
° 0004ABC9: 833E923320                   cmp    w,[03392],020  ;" "
° 0004ABCE: 7209                         jb     00004ABD9   ---------- (5)
Serve as a secure measure. It's the 2nd "no minitel" notice that
I've never encountered inside winice.
---------------------------------------------------------------------
² 000AEB34: 8B760A                       mov    si,[bp][0000A]
² 000AEB37: A1D870                       mov    ax,[070D8]
² 000AEB3A: 894406                       mov    [si][00006],ax
² 000AEB3D: 833ED870FF                   cmp    w,[070D8],0FF  ;"ÿ"
² *00AEB42:*EB0E                        *jmps   0000AEB52   ---------- (4)
² 000AEB44: 681A09                       push   0091A
² 000AEB47: 6A00                         push   000
° 000AEB49: 9A1AADA712                   call   040:0AD1A   ---------- (5)
° 000AEB4E: E9F503                       jmp    0000AEF46   ---------- (6)
° 000AEB51: 90                           nop
° 000AEB52: A1D664                       mov    ax,[064D6]
° 000AEB55: 3906D870                     cmp    [070D8],ax
° *00AEB59:*EB2F                        *jmps   0000AEB8A   ---------- (7)
° 000AEB5B: 681609                       push   00916
° 000AEB5E: 6A00                         push   000
° 000AEB60: 6A00                         push   000
° 000AEB62: 6A00                         push   000
2 time: This crack the "no date in trial" and the "exceed 60-days datefield".
.----------------------------------.
| STEP 6:   BETA TESTING OUR CRACK |
'----------------------------------'
Sometimes humans do make non-intentionnal mistakes. In that case,
a good way to verify if there are any errors is to: Beta testing it.
Change OS date, create new file, open file, reset abruptly the computer,
rerun msmoney3 and open the file that has not been correctly closed,
look for the expired_date inside the about box, test the datefield
with an exceeded_date and with no_date. Luckily, nothing blew up nor
did our modem start to flash and dial phone 1-800-PIRATE :=)
Did you know that Wing Commander 3 contains codes that turn your
modem speaker off then actually dial a 800 hotline at exactly 2:00am
if your copy of WC3 was on your HD? PLEASE DO NOT PANIC for God sake
nor pee in your pants :=) It is not meant to scare you off, but only
to show you why it is so important to feel, to ZEN the code we are
cracking. Like +ORC said, we must feel the code for any suspecting
activity. Though, "blacklist" codes are more frequent these days.

Anyway, we can now confirm that our crack works.

Now, before we say goodbye to mnydemo, would that be nice to crack
the password proctection? Why not! :-)

° 00012782: 8B1E6E50                     mov    bx,[0506E]
° 00012786: 837F0200                     cmp    w,[bx][00002],000
° 0001278A: 7403                         je     00001278F   ---------- (2)
° *001278C: E90000                      *jmp    00001278F   ---------- (3)
° 0001278F: 83FE06                       cmp    si,006
° 00012792: 7403                         je     000012797   ---------- (4)
° *0012794: E90000                      *jmp    000012797   ---------- (5)
° 00012797: FF760E                       push   w,[bp][0000E]
   [...]
° 00012801: 1BC0                         sbb    ax,ax
° 00012803: 3BC9                         cmp    cx,cx
° 00012805: F3A7                         repe   cmpsw
° *0012807:*7500                        *jne    000012809   ---------- (2)
° 00012809: 2BC8                         sub    cx,ax
° 0001280B: F3A6                         repe   cmpsb
° *001280D:*7500                        *jne    00001280F   ---------- (3)
° 0001280F: E94BFF                       jmp    00001275D   ---------- (4)
° 00012812: 90                           nop
° 00012813: 90                           nop
° 00012814: 8BF8                         mov    di,ax
° 00012816: EBDB                         jmps   0000127F3   ---------- (5)
° 00012818: 682608                    !  push   00826 ;        HERE!
° 0001281B: 6A00                         push   000   ; "incorrect password"
° 0001281D: 9A1AADD923                   call   040:0AD1A ; call MessageBox
Using a the VERY useful [F6] refer key, we should be able to find every
single (C) or (U) jump to it.

I guess that's it for MNYDEMO3.

[MSMONEY 5.0p]
.------------------------.
| STEP 1 : INSTALLATION  |
'------------------------'
We  can proceed as usual and install it. Or better yet, since it is a
microsoft produkt, we'll use setup.exe's own log option (/g filename),
how convenient? ;-) Probably you have (like me) a large single
InstallShield  self-exctract file (4.34 meg MONEY97.EXE) which does not
pass any parameter. Since we need to start from the setup.exe file to 
provide the /g option, we'll use another way around. Of course, they're
are many ways around. (If you already know how to do it, jump to point 5.) 

Here's one way to do it:

[assuming that you are already in Win95]
[assuming that you have installed Win95 in C:\WINDOWS\ ]

1.   Make a DOS prompt, then execute it:

     C:\> MSMONEY.EXE  

we're back in windoze main screen we see that InstallShield is 
now decompressing itself... followed by Microsoft Setup Money 97. 

2.   Remember our DOS prompt? Now, let's switch back to it  .
You  see,  by   having  a  DOS  prompt we  gain   control   over   the
installation proccess. I mean, now we can snoop inside setup file  and
copy whatever temporary directory we want! This can also be useful for
other trialware. I'll be using my customized version of good old NC 4.0 
(no ugly double-box panel here, but nice NU8-style panel with my own DOS
soft-font and of course my beloved tool, Hiew, as the default viewer). 
But, we can most certainly use anything else, we only need an utility
that can with ease copy a hidden tempdirectory and sub-directory 
C:\~MSSETUP.T\~MSSTFQF.T\ to a safer place, say C:\save\. 
(Remember to [x] include sub-directory, though) What's the purpose of 
using the hidden attribute now that anyone can see it inside their
file manager? (BTW, Adobe Pagemill 2.0 Trial Edition use this hidden
attribute trick to prevent us from reinstalling, basically a garbage
file, WINEXEC.SYS, hidding inside \WINDOWS\TEMP). 
The hidden ~MSSETUP.T temporary directory is the one that will be 
deleted once you have exited the setup program. Well, did you copy it?
Yes. Now we can exit the setup program.

3.    At  this  point, you should have made a copy of ~MSSETUP.T  into
the   root directory C:\> and you should also have the SETUP.EXE   and
the  related files inside your C:\WINDOWS\TEMP (setup does not  delete
these files).

4.    Now, all we have to do is to run setup with the /g option followed
by any filename:

     C:\WINDOWS\TEMP\> SETUP.EXE /g money97.log 

5.    After installing it, we'll obtain C:\WINDOWS\TEMP\money97.log.
Looking inside this 76kb logfile, we see the fullpath location of every
copied file and also all the registry key created in the installation
process.

This time we don't see or feel anything suspicious except this:

Stamping resource in file: 'D:\Program Files\Microsoft MoneyTrial\MSMONEY.EXE'
, type: '291', ID: '191'

By  comparing my previously installed copy of MSMONEY.EXE, I found out
that my two MSMONEY.EXE were different from each other. That's because
the setup program generates a different Serial ID at each installation
(eventhough I XXXXXXXXXed the setup.ini file :=) Is it part of our time
protection ? I'm not sure, we'll find out as we crack it. (assuming that
we have never crack a microsoft product before)
.-------------------------.
| STEP 2 :   EXAMINATION  |
'-------------------------'
Run the  MSMONEY.EXE program and we see a nagscreen (this must go,
but for now, we'll leave as it is) Here is what I noticed the first time
I ran MSMONEY97 Trial Edition:

	[Inside the trial period]
1.  Nagscreen (must go !)
2. "Trial Edition" in the navigation bar (esthetic)
3. "will be Expire on:" in the Aboutbox (humm...were's this date come from?)
4.  No Online Services in the Trial Edition (still crippled ?)
5. "date entered is outside 90-day range" notice (must go !)

	[Exceeded the trial period]
6.  "90-day trial period has ended" notice
*7. "You cannot create new file" notice *NEW*

Here again, looking further inside the MSMONEY.EXE we found the same
things as in MSMONEY3!

8.  This trial copy of Microsoft Money 97 will expire in %1 day(s).
9.  The system date in your computer must be 1996 or 1997 in order
    to run this Trial Edition of Microsoft Money 97.
10. The system date in your computer is set earlier than the date you
    first used this Trial Edition of Money. Your system date  must be
    set correctly to run this edition. Please fix the date and restart 
    Microsoft Money 97.
    (he! he! he! Whenever I see this, I just start to laugh !)
11. This Trial Edition of Microsoft Money 97 requires that you enter
    a date. The full  retail  edition of Microsoft Money 97 accepts  
    transactions without dates.

So, the more we know about our target the better we crack. Since EVERY
message must somehow popup to inform the user of what he can or cannot
do, because of this 'little hole' it will mostly be easy to crack.
(Unless it used a long series of CMPs and JMPs internal lookup table 
like in Corel Web.Designer Trial Edition from Elan Computer Group,
which would make it a little bit harder :=)
.---------------------------------.
| STEP 3:   WIN32 RESOURCES DATA  |
'---------------------------------'
Now's the fun part. Let's fetch the ordinal ID of these dialogboxes. 
I'm still using that quite old version of BRW 4.0 (not for long, though :-)
You may want to skip the following paragraph if you have BRW 4.5+.

---[For readers that still use BRW4.0, like me :-]-------------------
So I had to alter by hand the resource directory table since an
"unexpected file format error" occurred at Dialog #22029. I just
reduced to 5 (at offset 00218E0E) the number of resource data, thus
leaving behind rcdata, accelerators, icons and versioninfo. Then, I
substitued the remaining dialogs address with a previous one that
worked. Of course, the .EXE file must be restored to the original one
to work properly. Anaywayz, I do what I can with what I have :-]
---------------------------------------------------------------------

Here are my findings:
:           :   (Notice all the "Online Services" dialogboxes
|           |    that aren't used by the demo).
| DIALOG    |
|      ...  |
|     12125 | (2F5Dh) "Purchase information"
|     12126 | (2F5Eh) " Welcome to... "
|     12127 | (2F5Fh) " Notice of expiration "
|     12243 | (2FD3h) " Online services Setup "
|     12251 | (2FDBh) " No Online Services "
|     12252 | (2FDCh) " you cannot create new file"
|     12253 | (2FDDh) " ...is about to expire... "
|      ...  |
:___________:
A powerful tool, indeed! Keep these hex numbers in note for later use.
.---------------------------.
| STEP 4:    DEAD LISTING   |
'---------------------------'
As   usual,  disassemble MSMONEY.EXE with W32dasm87 (or any  available
tool). While it is processing, we need not to use our eyes. So, gently
close them. In fact, it is better to forget about our sight. Therefore,
trying  to  reconstruct a visual image of the program from information
you  recently  received  by your eyes and even  your  ears.  Once   we
learn how to build this picture, it is easy (not always) to   choose
strategies that will crack the protection. As we improve, we'll   find
it  unnecessary to close our eyes. Since we're in summer  time,  let's
relax   outside smelling fresh pine scent, listening to bird  (not  to
much cuz it becomes annoying)  AAahhhhhh... Life could not be better! ;=)
After a while, we'll obtain a huge 39.5 megs file!
While relaxing, have u thought about all the strategies we'll be doing?
Now, let's start to search for these dialogs. 
For example, dialog 2F5D would be "push 00002F5D":
-------------------------------------------------- -- -
[Address] [ASM code...] [Contents................]
------------------------------------------------------- 
:00C8F76  push 00002F5D  " Purchase information "
:00CE9B7       "" 
:01B5652       ""
:01BE345       ""
-------------------------------------------------------
:00A68F6  push 00002F5E  " Welcome to... "
-------------------------------------------------------
:0070225  push 00002F5F  " Notice of expiration "
-------------------------------------------------------
:???????  ?????????????  " Online Services Setup "
-------------------------------------------------------
:003206B  push 00002FDB  " No Online Services "
:00E6378       ""
:00FCF50       ""
-------------------------------------------------------
:006FAA5  push 00002FDC  " you cannot create new file "
-------------------------------------------------------
:006DF63  push 00002FDD  " ...is about to expire... "

Nothing new, we already know that some are used by the program but we
see  that  the dialog Online Services Setup are not there. How come?
Could this mean crippled? Maybe. Even if there was one I wouldn't bother
since it is only available (for the moment) in the US , in which I do 
not reside. Our main purpose here was to crack time-protection and 
gain knowledge from it, not to use this stupid microsoft product, that
basically a look&feel of Quicken. Who needs it? I mean, what the hell
can they manage to save money if they do not stop to consume on 
ridiculous things which remind me of those stupid commercial adds at 
2:00am trying desperately to urge us to buy useless things, claiming to 
be revolutionary or technologically advanced (yeah right, technologically 
my ass!) Don't they realize how stupid they act! What better time than 
2:00 in the morning where people seem to be sleepy with no one around,
no one to influence except the TV! Thinking that we are stupid enough to
buy it. OK. That's enough. Let's pursue. We'll try to concentrate on the  
"Notice of Expiration", which is obviously our main goal.  From that 
starting point alot of new developments will follow. Here's my commented
code snippet, simply follow the alphabet letter. To better appreciate its
full meaning, you should be looking at [A] first then [B], and so on... 

__[STARTING WITH: DialogID: 2F5D]____________________________________
:005B5504  cmp [ebp+30], 00000000
:005B5508  je 005B552B
:005B550A  mov ecx, [ebp+3C]
:005B550D  mov ax, [ecx+000001A9]
:005B5514  cmp ax, FFFF
:005B5518  je 005B6214
:005B551E *cmp [00619244], ax ;      IF (?) BELOW/= (?)
:005B5525 *jbe 005B622D       ; [D.] JUMP AWAY (BAD)
                              ;      ELSE (GOOD...)
*    Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:005B5508(C)
|
:005B552B  cmp [ebp+1C], 00000000
:005B552F  push 00000000
:005B5531  je 005B5551
	     [...]
*    Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:005B5518(C)
|
:005B6214  push 00000000
:005B6216  push 00000000
:005B6218  push 00000000
:005B621A  push 00000000
:005B621C  push 00000000
:005B621E  push 00000910     ; which MessageBox is 910?
:005B6223  call 00474240     ; (the answer is in STEP5)
:005B6228  add esp, 00000018
:005B622B  jmp 005B625F
*    Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:005B5525(C)                ; [C] Let's look up for this Jump.
|                                  (For mode detail about the MB_YES look
:005B622D  push 00000000           inside an API reference: MessageBoxA) 
:005B622F  push 0000090B     ;     Since we have and MB_YES=x06
:005B6234  call 00474BA0     ;     We have a MessageBox, allright. Which
:005B6239  add esp, 00000008 ;     MessageBox is 90B?
:005B623C  cmp ax, 0006      ; [B] IF NOT (MB_YES button) THEN
:005B6240  jne 005B625F      ;     JUMP AWAY (don't show purchase information)
:005B6242  push 00000000     ;     ELSE SHOW IT
:005B6244  mov eax, [ebp+08]
:005B6247  push 00000000
:005B6249  mov ecx, [eax+2C]
:005B624C  push ecx
:005B624D  push 004CF510
:005B6252 *push 00002F5D     ; [A] DialogBox_ID: "Purchase Information"
:005B6257  call 0046BF10
:005B625C  add esp, 00000014
--------------------------------------------------------------[END.]--
See  that jump at :00005B5525 that's the only conditional direct  jump
to  NoE  DialogBox. Hummm... A word at [00619244] CoMPares to the AX 
register. Can this be our expiredate  with our  currentdate? Which one 
goes with which?  We still don't need winice, let's do an educated
assumption:
    AX register    -> changes often -> temporary    = CurrentDate
    [00619244]     -> changes less  -> memory space = ExpireDate
Is this the only CoMPare? Let's search right-away for "00619244", no
brackets:
01. :004588F0 66A144926100       (R) mov ax, [00619244]
02. :00461564 6844926100        x(R) push 00619244
03. :0046EB00 66A144926100       (R) mov ax, [00619244]
04. :0046F058 66812544926100FF01*(W) and word ptr [00619244], 01FF
05. :0046F063 6844926100        x(R) push 00619244
06. :0047064A 66A144926100       (R) mov ax, [00619244]
07. :00470666 66A144926100       (R) mov ax, [00619244]
08. :00470B19 6844926100        x(R) push 00619244
09. :00470CC9 6844926100        x(R) push 00619244
10. :004CF273 668B0D44926100     (R) mov cx, word ptr [00619244]
11. :0057796E 66A344926100      *(W) mov [00619244], ax
12. :005B551E 66390544926100    *(R) cmp word ptr [00619244], ax
13. :005BEEF8 66390544926100    *(R) cmp word ptr [00619244], ax
(W)= write
(R)= read
 * = important
 x = not so important
See,  we  would never have found these PUSHes if we used  brackets.
From our perspective, we see that those two "cmp [00619244],ax" can be
easely cracked, remain the two critical intructions that either create
or overwrite  our  expiredate  represented   by  a  word  at  location
[00619244]. We'll like to know how [00619244]  is created   or   where
does it read from. So, what should be our next move?
.------------------------.
| STEP 5:   WINICING     |
'------------------------'
Yes,  we  reach a critical point were winice should help us interprete
the remaining piece of code. While winice and windoze load itself...  
may I kindly suggest you 100% pure Koolaid juice, please make sure
you've added some ice cubes, also be careful not to exceed the amount
of required water :=) Remember, our brain need not to be left out, it 
needs indeed its plasma glucose dose to function optimally. We must
not forget: sugar is our brain number one fuel ;=) and with some
techno music to go with it... Do you now feel a mental strength in you? 
Yes! Good. At this stage, I'm sure all our readers know how to use 
winice and for that reason it is useless and redundant to show all the
commands I used. I'm sure a lot of you already know what to do now.
That's right! Let's hear our echo. 
We'll do a BreakPoint on Memory access of a Word:

	: bpmw ds:619244 rw
(Sometimes, it's good to change "rw" to "w"rite alone or "r"ead alone)
	: bpmw ds:619244 w
		or
	: bpmw ds:619244 r

We do the same thing as we did with MNYDEMO3: change OS date, say in 2000
then backwards in 1990 and see what happens. Here's what I can understand
by breakpointing inside winice:

This  first part concerns DIALOGBOX and MESSAGEBOX that are separated in
small sections all sorted in ascending order of address:
__[DIALOG:2FDD]_______________________________________________________
:0046EAF7  call 004689D0      ; KERNEL32.GetLocalTime inside
:0046EAFC  mov [ebp-14], ax   ; AX=[ebp-14]= CurrentDate
:0046EB00 *mov ax, [00619244] ; [00619244]=AX= ExpireDate
:0046EB06  mov [ebp-16], ax   ; AX=[ebp-16]=2nd copy of ExpireDate
:0046EB0A  cmp [ebp-14], ax   ; IF (CurrentDate) ABOVE (ExpireDate) THEN
:0046EB0E  mov eax, [00610CAC]; [00610CAC]=EAX=???
:0046EB13  jnb 0046EB75       ; JUMP(GOOD?)
:0046EB15  mov eax, [ebp-16]  ; ELSE
:0046EB18  push eax           ; Save 2nd copy of ExpireDate
:0046EB19  call 00468E50      ; Calculate???
:0046EB1E  add esp, 00000004  ; Erase 4 stack_bytes
:0046EB21  mov si, ax         ; AX=SI=?
:0046EB24  mov eax, [ebp-14]  ; [ebp-14]=EAX=CurrentDate
:0046EB27  push eax           ; Save CurrentDate
:0046EB28  call 00468E50      ; Calculate???
:0046EB2D  add esp, 00000004  ; Erase 4 stack_bytes
:0046EB30  sub si, ax         ; (SI-AX)=(ExpireDateCurrentDate)=DAYS_LEFT=SI
:0046EB33  cmp  si, 000F           ;IF (DAYS_LEFT) GREATER THAN 15 (0Fh) THEN
:0046EB37  mov eax, [00610CAC]     ; [00610CAC]=EAX=???
:0046EB3C *jg 0046EB75             ; JUMP AWAY (GOOD...)
:0046EB3E  movsx word ptr eax, esi ; ELSE (BAD!) 
:0046EB41  push eax
* Possible StringData Ref from Data Obj ->"%d"
               |
:0046EB42  push 006109D8
:0046EB47  lea eax, [ebp-20]
:0046EB4A  push eax
* Reference To: USER32.wsprintfA, Ord:0249h
			|
:0046EB4B  Call dword ptr [00620A08]
:0046EB51  add esp, 0000000C
:0046EB54  push 00000000
:0046EB56  push 00000000
:0046EB58  mov eax, [00613FF0]
:0046EB5D  push eax
:0046EB5E  push 004CF510
:0046EB63 *push 00002FDD   ; Dialog_ID: "..is about to expire..."
:0046EB68  call 0046BF10
:0046EB6D  add esp, 00000014
:0046EB70  mov eax, [00610CAC]
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0046EB13(C), :0046EB3C(C)
|
:0046EB75  test eax, eax
:0046EB77  je 0046EBB1

__[DIALOG: 2FDC]_____________________________________________________
* Referenced by a CALL at Addresses:
|:0046F068   , :00470B1E   , :00470C38   , :00470CCE
|
_FIRST,WHAT_IS_THE_YEAR_OF_OUR_EXPIREdATE?_:
:00470640  sub esp, 00000004
:00470643  cmp [esp + 0C], 00000000
:00470648  je 0047067C   ; Jump if Equal (BAD?)
:0047064A *mov ax, [00619244] ; ExpireDate into ax
:00470650  and ax, FE00  ; Clear AL, AX=??00h
:00470654  cmp ax, 6000  ; Is ExpireDate in 1996?
:00470658  je 00470666   ; Jump if Equal (GOOD)
:0047065A  cmp ax, 6200  ; Now, ExpireDate in 1997?
:0047065E  je 00470666   ; Jump if Equal (GOOD)
:00470660  cmp ax, 6400  ; Or, ExpireDate in 1998?
:00470664  jne 0047067C  ; Jump if Not Equal (BAD)
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00470658(C), :0047065E(C)
|
_OUR_EXPIREdATE_IS_EITHER_IN_1996,1997_OR_1998_(GOOD!)_:
:00470666 *mov ax, [00619244]   ; Once again, ExpireDate into AX
:0047066C  mov ecx, [esp + 08]  ; ECX holds another location
:00470670  mov [ecx], ax        ; ExpireDate in that other location
:00470673 *mov eax, 00000001    ; return a gOOd flag (EnableCreateFile)
:00470678  add esp, 00000004
:0047067B  ret
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00470648(C), :00470664(C)
|
_EXPIREdATE_IS_NEITHER_IN_1996,1997_NOR_1998_(BAD?)___
___NEXT,WHAT_IS_OUR_CURRENTdATE?______________________:
:0047067C  call 004689D0 ; KERNEL32.GetLocalTime inside
:00470681  mov [esp + 02], ax
:00470686  and ax, FE00  ; Clear AL, AX=??00h
:0047068A  cmp ax, 6000  ; Are we currently in 1996?
:0047068E  je 004706B8   ; Jump over if Equal
:00470690  cmp ax, 6200  ; Are we currently in 1997?
:00470694  je 004706B8   ; Jump over if Equal
_SHIT!_WE_ARE_USING_IT_OUTSIDE_1996/1997_(THAT'S BAD!)_:
:00470696  push 00000000
:00470698  mov eax, [00613FF0]
:0047069D  push 00000000
:0047069F  push eax
:004706A0  push 004CF510
:004706A5 *push 00002FDC  ; DialogBox_ID: "You cannot create new file..."
:004706AA  call 0046BF10  ; USER32.DialogBoxParamA inside
:004706AF  add esp, 00000014
:004706B2 *xor eax, eax   ; return a BAD flag (DisableCreateFile)
:004706B4  add esp, 00000004
:004706B7  ret
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0047068E(C), :00470694(C)
| _SINCE_WE_ARE_HERE:_OUR_EXPIREdATE_IS_OUTSIDE_1996/1997/1998_(BAD)_
  _BUT_OUR_CURRENTdATE_IS_EITHER_IN_1996_OR_1997_:
:004706B8  mov eax, [esp + 02]
:004706BC  push  0000005A  ; Humm... I think it's trying to reconstruct
:004706BE  push eax 	   ; a new valid ExpireDate:
:004706BF  call  00468A70  ; Put the ExpireDate in AX=XXXXh DateFormat
:004706C4  mov ecx, [esp + 10] ; At this point, [esp+10]=ecx=00619244!!!
:004706C8  add esp, 00000008   ; Erase 8 previous stack_bytes
:004706CB  mov [ecx], ax       ; Initialize [ecx]=[00619244]=ax
:004706CE *mov eax, 00000001   ; return a GOOD flag (EnableCreateFile)
:004706D3  add esp, 00000004
:004706D6  ret

___[DIALOG: 2F5F]_____________________________________________________ 
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00470E81(C)
|                                   ;[esp+14]=AX=ExpireDate
:00470E07  mov ax, [esp + 14]       ;[esp+178]=CurrentDate 
:00470E0C  cmp [esp + 00000178], ax ;IF (CurrentDate) BELOW (ExpireDate) THEN
:00470E14 *jb 00470E32              ;JUMP AWAY (GOOD)
:00470E16  push 00000000            ;ELSE (BAD...)
:00470E18  mov eax, [00613FF0]
:00470E1D  push 00000000
:00470E1F  push eax
:00470E20  push 004CF510
:00470E25 *push 00002F5F   ; Dialog_ID: "Notice_Of_Expiration"
:00470E2A  call 0046BF10   ; USER32.DialogBoxParamA inside 
:00470E2F  add esp, 00000014

__[MESSAGEBOX: 90f]___________________________________________________
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00470C17(C), :00470C21(C), :00470C2B(C)
|
:00470E63  mov eax, [esp + 6C] 
:00470E67  push 0000005A
:00470E69  push eax
:00470E6A  call 00468A70
:00470E6F  mov [esp + 74], ax 
:00470E74  add esp, 00000008
:00470E77  mov ax, [esp + 14] ; [esp+14]=AX=ExpireDate & [esp+6c]=CurrentDate
:00470E7C  cmp [esp + 6C], ax ; IF (CurrentDate) NOT BELOW (ExpireDate) THEN
:00470E81  jnb 00470E07       ; JUMP (GOOD)
:00470E83  push 00000000      ; ELSE (BAD...) 
:00470E85  push 00000000
:00470E87  push 00000000
:00470E89  push 00000000
:00470E8B  push 00000000
:00470E8D *push  0000090F  ; Dialog_ID: "The system date is earlier than..."
:00470E92  call 00474240
:00470E97  mov ax, 07E4
:00470E9B  add esp, 00000018
:00470E9E  pop ebp
:00470E9F  pop edi
:00470EA0  pop esi
:00470EA1  pop ebx
:00470EA2  add esp, 00000268
:00470EA8  ret

__[DIALOG: 2F5E]______________________________________________________
* Reference To: USER32.GetDesktopWindow, Ord:00E8h
			|
:004A74EA  Call dword ptr [00620B0C]
:004A74F0  push eax
:004A74F1  push 004CF510
:004A74F6 *push 00002F5E     ;Nagscreen Dialog_ID: "Welcome to..."
:004A74FB  call 0046BF10     ;USER32.DialogBoxParamA inside 
:004A7500  add esp, 00000014

__[DIALOG: 2F5D][MESSAGEBOXs: 90B,910]________________________________
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:005B5518(C)
|
:005B6214  push 00000000
:005B6216  push 00000000
:005B6218  push 00000000
:005B621A  push 00000000
:005B621C  push 00000000
:005B621E *push 00000910     ;MessageBox_ID: "requires that you enter a date"
:005B6223  call 00474240     ;Remember? we did not know what it was
:005B6228 *add esp, 00000018 ;now by brkpting inside winie we found it out.
:005B622B  jmp 005B625F
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:005B5525(C)
|
:005B622D  push 00000000
:005B622F *push 0000090B   ; MessageBox_ID: "is outside 90-day range"
:005B6234  call  00474BA0  ; Same thing here. Also, using a breakpoint.
:005B6239  add esp, 00000008
:005B623C  cmp ax, 0006     ; IF NOT (MB_YES) THEN
:005B6240  jne  005B625F    ; JUMP AWAY  (don't show purchase information)
:005B6242  push 00000000    ; ELSE SHOW IT
:005B6244  mov eax, [ebp+08]
:005B6247  push 00000000
:005B6249  mov ecx, [eax+2C]
:005B624C  push ecx
:005B624D  push 004CF510
:005B6252 *push 00002F5D    ; MessageBox_ID: "Purchase Information"
:005B6257  call 0046BF10    ; We already know that, don't we?
:005B625C  add esp, 00000014
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:005B622B(U), :005B6240(C)
|
:005B625F  push 00000001
:005B6261  mov ecx, [ebp+08]
:005B6264  call 005595D0
:005B6269  mov esi, [ebp+38]
:005B626C  jmp 005B649B
_[end of first part]__________________________________________________

Note:  If  at your first run of MSMONEY.EXE, there are some [00619244]
that  did  not trap, just leave the breakpoint as there are, exit  the
program, rerun MSMONEY.EXE, now you should be able to trap most of it. 

___[1st: Dialog_ID 2FDB "OnlineService"]______________________________
* Referenced by a CALL at Addresses:
|:00432B78   , :00432C38
|
:00432C54  lea ecx, dword ptr [ebp-6C]
:00432C57  jmp 004089A0  ;that far?... 
:00432C5C  push 00000000 ;indirect jump? 
:00432C5E  push 00000000
:00432C60  mov eax, [00613FF0] 
:00432C65  push eax
:00432C66  push 004CF510
:00432C6B *push 00002FDB ;DialogID: "Online Services not available"
:00432C70  call 0046BF10
:00432C75  add esp, 00000014
:00432C78  mov eax, dword ptr [ebp-18]
:00432C7B  push eax
:00432C7C  call 004CB220
:00432C81  add esp, 00000004
:00432C84  mov eax, 00432C3D
:00432C89  ret

___[2nd: Dialog_ID 2FDB "OnlineService"]______________________________
:004E6F46  test eax, eax
:004E6F48  je 004E6F58 ;only jump but not over the dialog, crippled?
:004E6F4A  lea edi, dword ptr [ebp-34]
:004E6F4D  mov esi, eax
:004E6F4F  mov ecx, 00000009
:004E6F54  repz
:004E6F55  movsd
:004E6F56  movsw
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004E6F48(C)
|
:004E6F58  lea eax, dword ptr [ebp-0E]
:004E6F5B  lea ecx, dword ptr [ebp-3C]
:004E6F5E  push eax
:004E6F5F  push ecx
:004E6F60  call 004FBAE0
:004E6F65  add esp, 00000008
:004E6F68  mov ecx, dword ptr [00613FF0] :004E6F6E  push 00000000
:004E6F70  push 00000000
:004E6F72  push ecx
:004E6F73  push 004CF510
:004E6F78 *push 00002FDB ;DialogID: "Online Services not available"
:004E6F7D  call 0046BF10
:004E6F82  mov [ebp-04], 00
:004E6F86  add esp, 00000014

___[3rd: DialogID_2FDB "OnlineService"]_______________________________
:004FDB2D  push esi      ;I see no jump...again,could it be crippled?
:004FDB2E  lea ecx, dword ptr [ebp+FE30]
:004FDB34  mov [ebp-04], 01
:004FDB38  call 004FDDB0
:004FDB3D  mov [ebp-04], 02
:004FDB41  push 00000000
:004FDB43  mov eax, [00613FF0]
:004FDB48  push 00000000
:004FDB4A  push eax
:004FDB4B  push 004CF510
:004FDB50 *push 00002FDB ;DialogID: "Online Services not available"
:004FDB55  call 0046BF10
:004FDB5A  mov [ebp-04], 01
:004FDB5E  add esp, 00000014
That's about it for this DEAD LISTING section.
.-------------------------------------.
| STEP 5.5:     DATE ENCRYPTION ?     |
'-------------------------------------'
You probably notice through winnie that [00619244] holds a word value
quite different from the usual "07CD"=1997 style date_format. 
The program changed the return DateValue of the call KERNEL32.GetLocalTime
to another date_format which is then stored as a word (XXXXh). What
follows is a code snippet which transform(encrypt?) our normally return
DateValue to a new 2 bytes date_format, can we name it "4875_date".
By now, we already noticed a couple of them in previous dead listing:
                   5C00 = 1994
                   5E00 = 1995
                   6000 = 1996
                   6200 = 1997
                   6400 = 1998
From our perspective, it would be interesting to know how it is
generated or transformed. And eventually, where it is actually
stored. The reason I chose the following code snippet is that it
shows explicitely how a normally returned date is transform to a
"4875_date".

I've "commented" using actual value that way, I hope, it will be
much more easy to follow.

 < > = value hold by EAX
 [ ] = value hold by ESP
 ( ) = value hold be ECX

* Referenced by a CALL at Addresses:
|:00408F8A   , :00411D14   , :00414D39   , :004214DC   , :0042B496   , 
|:0042BC26   , :00430128   , :0043B756   , :0043BAFD   , :0043BB28   , 
| (about +50 more calls...)

:004689D0  sub esp, 00000014             ; Prepare 14 stack_byte
:004689D3  lea eax, dword ptr [esp + 04] ; Address of Date_location
:004689D7  push eax                      ; Save: Date_location

* Reference To: KERNEL32.GetLocalTime, Ord:00E2h
                                  |               mm/dd/yyyy
:004689D8  Call dword ptr [00620140]     ; return value date = 08/09/1997

  .--INPUT--------------------------------.
  |                    .------date-----.  |
  |                ?    year month  day   |
  |              __|__ __|__ __|__ __|__  | 
  |  ss:esp+02:  FF FF CD 07 08 00 09 00  |
  '---------------------------------------'
------fetch the month--------
.    ?    =  [esp+02] = FF FF
.  month  =  [esp+06] = 08 00
           
:004689DE  mov ax, word ptr [esp + 06]   ; ax = [esp+06] = month = <0008>
:004689E3  dec ax                        ;  <0008> -1    = <0007>
:004689E5  shl ax, 0005                  ;  <0007> shl 5 = <00E0>  
:004689E9  xor ax, word ptr [esp + 02]   ;  <00E0> xor [FFFF] =  
:004689EE  and ax, 01E0                  ;   and  01E0  = <0100>
:004689F2  xor word ptr [esp + 02], ax   ;  [FFFF] xor <0100> = [FFFE]*  
                                         ;  *will be store in reverse order
------fetch the day--------
.  ?   =  [esp+02] = FE FF
. day  =  [esp+0A] = 09 00
  
:004689F7  mov ax, word ptr [esp + 0A]   ; ax = [esp+0A] = day = <0009>
:004689FC  dec ax                        ;  <0009> -1    = <0008>
:004689FE  xor ax, word ptr [esp + 02]   ;  <0008> xor [FEFF] = 
:00468A03  and ax, 001F                  ;   and  001F  = <0017>
:00468A07  xor word ptr [esp + 02], ax   ;  [FEFF] xor <0017> = [E8FE]

------fetch the month & year-----------------------------------------
.  ?         =  [esp+02] = FE E8
. month&year =  [esp+04] = CD 07 08 00

:00468A0C  mov eax, dword ptr [esp + 04] ; eax=[esp+04]=month&year=<000807CD>
                                              08/1997      00/xxxx   00/1997 
                                              |    |        |  |         |             
:00468A10  and eax, 0000FFFF             ; <000807CD> and 0000FFFF=<000007CD> 
                                                1997 - 1948 =      0049
                                                 |      |            |
:00468A15  sub eax, 0000079C             ; <000007CD> - 79C = <00000031>
                                                0049    <       127  = Lower
                                                  |              |
:00468A1A  cmp eax, 0000007F             ; <00000031> cmp <0000007F> = L/E?
:00468A1D--jle 00468A3E------------------; Jump if Lower or Equal ---------
 NOTE:  With my example, we should be jumping to :00468A3E

:00468A1F  mov eax, FFFFFF9C             ; eax= FFFFFF9C = -64h or -100
                                          
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00468A3C(C)                               year                 
|                                           1997  + -100   =    1897
                                             |        |          |  
:00468A24  add word ptr [esp + 04], ax   ; [07CD] +  = 01[0769]
:00468A29  mov ecx, dword ptr [esp + 04] ; ecx= 00010769 
:00468A2D  and ecx, 0000FFFF             ; (00010769) and 0000FFFF=(00000769)

                                           1897  - 1948 =      -52
                                             |       |           |
:00468A33  sub ecx, 0000079C         ; (00000769) - 79C = (FFFFFFCC)

                                             -52   >      127  = Greater
                                              |            |
:00468A39  cmp ecx, 0000007F         ; (FFFFFFCC) cmp 0000007F = G?
:00468A3C  jg 00468A24               ; if Greater, Jump Back

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00468A1D(C)
|                                                     
----Generate LowByte of 4875_date-------
.   ?   = [esp+02] = FE E8
.  year = [esp+04] = CD 07
   
:00468A3E  mov ax, word ptr [esp + 04] ; ax = [esp+04] = year = <07CD>
:00468A43  shl ax, 0009                ; <07CD> shl  0009  = <9A00> 
:00468A47  xor ax, word ptr [esp + 02] ; <9A00> xor [FEE8] = <64E8>
:00468A4C  and ax, 01FF                ; <64E8> and  01FF  = <00E8>
                                       ;  LowByte of "4875_date" generated.
----Generate HiByte of 4875_Date--------
.  year = [esp+04] = CD 07

:00468A50  mov cx, word ptr [esp + 04] ; cx = [esp+04] = year = (07CD)

                                          1997  - 28  =  1969
                                           |      |       |
:00468A55  sub cx, 001C                ; (07CD) - 1C  = (07B1) 
:00468A59  shl cx, 0009                ; (07B1) shl 9 = (6200)                                        
                                       ;  HiByte of "4875_date" generated.
                                       ; "6200" ---> 1/1/1997 ---> 1997
----final 4875_Date---------------------
:00468A5D  xor ax, cx                  ; <00E8> xor (6200) = <62E8>
:00468A60  mov word ptr [esp + 02], ax ; store "62E8" at [esp+02]
                                       ;That's how a "4875_date" is generated!
                                       ; "62E8" represents 08/09/1997 
:00468A65  add esp, 00000014           ; Erase 14 stack_byte
:00468A68  ret                         ; Return back

  .--OUPUT--------------------------------.
  |                    .------date-----.  |
__|          4875_date  year month  day   |--------------------------
  |              __|__ __|__ __|__ __|__  | 
  |  ss:esp+02:  E8 62 CD 07 08 00 09 00  |
  '---------------------------------------'

If you fundle a little bit with this format, you'll see that the
earliest date allowed is 1/1/1948 (0000h) and the latest date
allowed is 8/32/2075 (FFFFh). In fact, the ones that ends with an
xx1Fh, xx3F, xx5F, ... gives a "32" days (invalid). 
  ||
  ||___LowByte: ending with F
  |_____HiByte: odd hex number (1,3,5,7,9,B,D,F)

OK. We now know the actual date is fetch from the system clock but
where does our expiredate come from? It's a fixed value that simply
cannot change at each run-time then it must be stored somewhere in a
file. Let's gather every bit of information we have seen until now : 

1. Remember the log file made at STEP 1...
   No suspicious .INI file created nor registry entry created.
   Wait... the only thing that can be taken into consideration
   is the Last_TOTD_Date value inside the registry it stores in
   the similar "4875_date" format. Then again, it isn't related to
   the ExpireDate.
   So, we've just eliminated the .INI and registry .DAT possibility.

2. By stamping the .EXE does it also put the ExpireDate?...
   Let's verify. Go near the variable area at offset 00455546 and
   search for "E8 62"... not found.  

3. The first time M$MONEY runs it reads from the provided SAMPLE.MNY...
   Can it hold the ExpireDate? It's just a sample file :=)
   How can it keep track if we are able to create a new .MNY
   file or delete SAMPLE.MNY file.

4. M$MONEY always need to have a file OPEN to run...
   When we advanced the OS date, a NoE notice showed up and mentioned
   that we needed to open a file so it can work.
   A .MNY file involved. Humm...

5. Repaired incorrectly close file ...
   Again, a .MNY file involved. Humm... 

6. Spy the I/O file activity using a File Monitor ...
   .DLL, .VXD, .EXE can be eliminated for obvious reason.

Conclusion:  Remain the .MNY file ;-)

Yes, indeed. Every .MNY file holds the the ExpireDate at offset 228
in the "4875_date" format ("E8 62" = 62E8 = 09/08/1997).
NOTE: When we first run MONEY97, the original SAMPLE.MNY holds "00 00". 
      When we first close MONEY97, an ExpireDate is generated in memory
      (see previous dead listing at offset :004706B8) then this
      ExpireDate is written right inside the .MNY file!
      I would never have thought of it because doing so would permit
      everybody to change a .MNY file thus instantly crack half of
      the time-protection scheme :=)

In MONEY94, it is located at offset 224 inside the .MNY file.


* +ORC, I'm not sure if this section answers your question when
* you wrote "to DELVE DEEP inside the date-encryption routines".
* The previous makes use of a XOR instruction, which I guess?,
* make it an encryption routine. Yet, I am aware that there also
* an other date encryption routines (like the one at offset :004706B8
* which generated the ExpireDate in "4875_date" format.) 
* I chose that code snippet because I thought that it explained
* very well how a normal date is transformed to the so called "4875_date".

.----------------------------------.
| STEP 6:   ESTABLISHiNG A CRACK   |
'----------------------------------'
Now, that we understand most of its time-protection scheme let's make a
crack for M$MONEY 97:

  .-----------------------------------------------------------------.
  | Crack for [Microsoft M$-MONEY'97 V5.0p] by TOXINE (4 juli 1997) |
  |-----------------------------------------------------------------|
  | File:      [MSMONEY.EXE] [4929536 bytez] [9-10-96] [12:00a]     |
  | Cracksize: [35bytezlongCRACK!]                                  |
  '-----------------------------------------------------------------'
For those who prefer to see the actual ASM code patching look further down...

If something unknown (to me) occurs, like jump table or indirect jump,
then I think my crack will still work. That is why, I patched every 
single byte of the protection scheme making it a 35 BytezLongCrack! 
What can I say? :=)

///HEX PATCHING///

[1/19]. Patching at OFFSET 57DA5
original = 74
da'crack = EB

[2/19]. Patching at OFFSET 57DAB
original = 74
da'crack = EB

[3/19]. Patching at OFFSET 57DB1
original = 74
da'crack = EB

[4/19]. Patching at OFFSET 6DF3C
original = 7F
da'crack = EB

[5/19]. Patching at OFFSET 6E459
original = 81 25 FF 01
da'crack = C7 05 FE FF

[6/19]. Patching at OFFSET 6FA49
original = 32 
da'crack = 00

[7/19]. Patching at OFFSET 6FA58
original = 74
da'crack = EB

[8/19]. Patching at OFFSET 6FA5E
original = 74
da'crack = EB

[9/19]. Patching at OFFSET 6FA65
original = 16
da'crack = 00

[1o/19]. Patching at OFFSET 6FA8E
original = 74
da'crack = EB

[11/19]. Patching at OFFSET 6FA94
original = 74
da'crack = EB

[12/19]. Patching at OFFSET 6FAC4
original = 8B 4C 24 10 83 C4 08 66 89 01
da'crack = 66 C7 05 44 92 61 00 FE FF 90

[13/19]. Patching at OFFSET 6FAD5
original = 4
da'crack = C

[14/19]. Patching at OFFSET 70019
original = 46 02
da'crack = 00 00

[15/19]. Patching at OFFSET 70023
original = 3C 02
da'crack = 00 00

[16/19]. Patching at OFFSET 7002B
original = 0F 84 32 02
da'crack = E9 33 02 00

[17/19]. Patching at OFFSET 70030
original = 00
da'crack = 90

[18/19]. Patching at OFFSET 70214
original = 72
da'crack = EB

[19/19]. Patching at OFFSET 70281
original = 73
da'crack = EB

///ASM PATCHING (with HIEW 5.60)///

 LITTLE VB INTERMEDE
Would that be nice to have HIEW for Visual Basic or even better
code our own VBcoder? That way, we could modify & crack VB software
on-the-fly. We'll not only be able to display its VB_code (like
DoDi VBdis) but we could edit it. Of course, we'll have to respect
each code length or tokens. 

The following code was taken from an actual VB program.
If you know what software I'm talking about then you likely
know in what field I'm actually studying.
Hint: http://www.madsci.com   I guess that's quite obvious. ;-)
                                 
For example:
             gv0026%(%) = FALSE  ;Set Unregistered version flag.
                                 ;We would want it to be TRUE.
First, it would be understood as:

         nEWlINE   gLOBALvARIABLE_(integer)
            |           |
vb_code:    nl  FALSE   gv  0026%
hex_code:  3549  E237  522D 2600 (the byte are stored in reverse)

This is what we want:
             gv0026%(%) = TRUE   ;Set Registered version flag, instead.

Our crack would then be:
vb_code:    nl   TRUE  gv  0026%
hex_code:  3549  3D38 522D 2600  ;Now Trauma One! is cracked :=)
So we see that:
                      newline:    nl   = 4935 
                    TRUE flag:    TRUE = 383D
 global variable (as integer):    gv   = 2D52

OK. Let's go back to our ASM patch code, so here we go:

(Cracked code marked by *).
° 00057DA1: 663D0060                     cmp    ax,06000
° *0057DA5: EB54                        *jmps   000057DFB   ----------(1)
° 00057DA7: 663D0062                     cmp    ax,06200
° *0057DAB: EB4E                        *jmps   000057DFB   ----------(2)
° 00057DAD: 663D0064                     cmp    ax,06400
° *0057DB1: EB48                        *jmps   000057DFB   ----------(3)
Somehow, we want to fool the program into beliving that is 1996, 1997 
or 1998. By doing this, we'll see for example that we can use TipOfTheDay 
as long as we desire.
----------------------------------------------------------------------------
° 0006DF33: 6683FE0F                     cmp    si,00F
° 0006DF37: A1AC0C6100                   mov    eax,[000610CAC]
° *006DF3C: EB37                        *jmps   00006DF75   ----------(2)
We won't be bothered again by a nag if we have less than 15 days left.
----------------------------------------------------------------------------
° 0006E457: C3                           retn
° *006E458: 66C70544926100FEFF          *mov    w,[000619244],0FFFE ;"°°"
° 0006E461: 6A01                         push   001
No more playing around, we want to make sure the ExpireDate ain't changing.
----------------------------------------------------------------------------
° 0006FA40: 83EC04                       sub    esp,004
° 0006FA43: 837C240C00                   cmp    d,[esp][0000C],000
° 0006FA48: 7400                        *je     00006FA4A   ---------- (1)
° 0006FA4A: 66A144926100                 mov    ax,[000619244]
° 0006FA50: 662500FE                     and    ax,0FE00
° 0006FA54: 663D0060                     cmp    ax,06000
° *006FA58: EB0C                        *jmps   00006FA66   ----------(1)
° 0006FA5A: 663D0062                     cmp    ax,06200
° *006FA5E: EB06                        *jmps   00006FA66   ----------(2)
° 0006FA60: 663D0064                     cmp    ax,06400
° *006FA64: 7500                        *jne    00006FA66   ----------(3)
(Same 6000/6200/6400 remarks as before)
----------------------------------------------------------------------------
° 0006FA8A: 663D0060                     cmp    ax,06000
° *006FA8E: EB28                        *jmps   00006FAB8   ----------(1)
° 0006FA90: 663D0062                     cmp    ax,06200
° *006FA94: EB22                        *jmps   00006FAB8   ----------(2)
(Again, same thing)
----------------------------------------------------------------------------
° *006FAC4: 66C70544926100FEFF          *mov    w,[000619244],0FFFE ;"°°"
° *006FACD: 90                          *nop
° 0006FACE: B801000000                   mov    eax,000000001
° *006FAD3: 83C40C                      *add    esp,00C
° 0006FAD6: C3                           retn
Personally, I think this is THE crucial part of our crack since 619244 is
created here.  This part of the crack is delicate  because we don't wanna 
have a stack crash. So, we'll readjust ESP by adding 04 to 08 making it 0C.
----------------------------------------------------------------------------
° 0007000F: 662500FE                     and    ax,0FE00
° 00070013: 663D0060                     cmp    ax,06000
° 00070017: 0F8400000000                *je     00007001D   ----------(1)
° 0007001D: 663D0062                     cmp    ax,06200
° 00070021: 0F8400000000                *je     000070027   ----------(2)
° 00070027: 663D0064                     cmp    ax,06400
° 0007002B: E933020000                  *jmp    000070263   ----------(3)
Since it used long 'OF' jump instruction, the first two '*je' simply
jumps to its following intruction (since it is set to 00 displacement)
until it reach the 3rd unconditional jump which branch to the GOOD GUY
code. We could have use 3 unconditional jumps. Do what ever you prefer.
---------------------------------------------------------------------------
Yes,  I  know my crack is quite invasive  but here there's no checksum
in this huge 4meg .EXE file  to worry about. If this  program have had 
a  checksum routine,  only then  we'll try to  crack the checksum  or 
instead WinFixBug it ;-)
.----------------------------------.
| STEP 7:   BETA TESTING OUR CRACK |
'----------------------------------'
Sometimes, without knowing, we make mistakes. In that case, a good way
to verify if there are any errors is to: Beta testing it.
Change OS date, click TOTD nexttip, create new file, open file,  reset
abruptly  the computer, rerun msmoney and open the file that  has  not
been  correctly close, look the expiredate inside the about box,  test
the  date field with an exceeded date and without any date (in payment
calendar).  Wow! it has passed every single test. We can  now  confirm
that our crack works.

[MS-WINPROJECT]
.------------------------------------.
| STEP 1:  A "PROTECTIONISTS' BAIT"  |
'------------------------------------'
In following section, we'll explain why the crack at :05168015 for
Winproject 4.1 Evaluation Copy would have NOT always worked. The
following code snippets and comments was based on +ORC lessons 4.2.,
thanks to +Fravia Page of Reverse Engineering :-) I mean, these guys
not only INSPIRE us but also ENLIGHT us!

NOTICE: my funny "IF NOT THEN" syntax, still here :=)

!= opcode that can trigger either an "has expired" MessageBoxA or
   an "will expire in ^1 day(s)" MessageBoxA.
*= opcode that set a BAD flag

:05167F6A  call 05024E77               ; KERNEL32.GetLocalTime inside
:05167F6F  cmp word ptr [ebp-0C], 07C0 ; If (year) BELOW 1984 then
:05167F75  jb 05167FA6                 ; Jump Away (BAD)
:05167F77  cmp word ptr [ebp-0C], 0801 ; If (year) ABOVE 2049 then
:05167F7D  ja 05167FA6                 ; Jump Away (BAD)
:05167F7F  push [ebp-0C]               ; Save: Valid year
:05167F82  movzx ax, byte ptr [ebp-09] ; AX = day 
:05167F87  movzx cx, byte ptr [ebp-0A] ; CX = month
:05167F8C  push eax                    ; Save: day
:05167F8D  push ecx                    ; Save: month
:05167F8E  call 052240E1               ;* DATE_EnCRYPTION Routine *
                                       ; Return value, AX = CURRENTdATE
:05167F93  cmp ax, word ptr [ebp-1C] ; If (CURRENTdATE) >= (EXPIREdATE) then
:05167F97  jge 05167FA6                ; Jump Away (BAD)
:05167F99  mov cx, word ptr [ebp-1C]   ; Else, CX = EXPIREdATE 
:05167F9D  sub cx, ax            ; EXPIREdATE - CURRENTdATE = DAYS_LEFT = CX
:05167FA0  mov word ptr [ebp-0E], cx   ; [DAYS_LEFT] = CX
:05167FA4  jmp 05167FAC                ; Jump anyWay (NEUTRAL)
:05167FA6 *mov [ebp-02], 0001          ; SET FLAG (BAD)
:05167FAC  push [ebp-24]               ; /* Handle of the key to close */
:05167FAF  Call dword ptr [053C5C1C]   ; ADVAPI32.RegCloseKey, Ord:00C2h
:05167FB5  jmp 05167FDC                ; Jump anyWay (NEUTRAL)
:05167FB7  mov [ebp-14], 0000          ; CLEAR [ebp-14] = BAD!
:05167FBD  lea eax, dword ptr [ebp-0C] ; EAX = DATE = 080A07CD = 08/10/1997
:05167FC0  push eax                    ; Save: Date Address
:05167FC1  call 05024E77               ; KERNEL32.GetLocalTime inside
:05167FC6  cmp word ptr [ebp-0C], 07C0 ; If (year) BELOW 1984 then
:05167FCC  jb 05167FD6                 ; Jump Away (BAD)
:05167FCE  cmp word ptr [ebp-0C], 0801 ; If (year) BELOW/= 2049 then
:05167FD4  jbe 05167FDC                ; Jump Away (GOOD)
* Referenced by A LOT OF Jumps at A LOT OF Addresses: [...]
:05167FD6 *mov [ebp-02], 0001          ; Else, SET EXPIRE_FLAG
:05167FDC  cmp word ptr [ebp-02], 0000 ; If (FLAG) NOT ZERO then
:05167FE1 !jne 05168042                ; Jump Away (BAD)
:05167FE3  cmp word ptr [ebp-10], 0000 ; If (?) NOT ZERO then
:05167FE8  jne 05167FF8                ; Jump Away (NEUTRAL)
:05167FEA  cmp word ptr [ebp-14], 0000 ; If (?) ZER0 then
:05167FEF !je 05168042                 ; Jump Away (BAD)
:05167FF1  cmp word ptr [ebp-10], 0000 ; Again?! [ebp-10] is ALREADY =0!
:05167FF6  je 05167FFF                 ; VERY LIKELY TO JUMP
:05167FF8  cmp word ptr [ebp-14], 0000 ; If (?) NOT ZER0 then
:05167FFD !jne 05168042                ; Jump Away (BAD)

We see that:
 [ebp-02] = 00 (GOOD)
 [ebp-10] = 00 or 01 (*NEUTRAL, will always check [ebp-14] value) 
 [ebp-14] = 01 (GOOD)

:05167FFF  movsx ecx, word ptr [ebp-0E] ; ECX = DAYS_LEFT
:05168003  cmp word ptr [052C1958], 0001; Set Zero and Parity Flag? 
:0516800B  sbb eax, eax                 ; EAX = 00000000
:0516800D  and eax, 0000005A            ; EAX = 00000000, do nothing?
:05168010  add eax, 0000005A            ; EAX = 0000005A = 90 DAYS_LIMIT
:05168013  cmp eax, ecx                 ; If (90) LOWER (DAYS_LEFT) then
                 ; If somehow we had more than +90 days left then we're
                 ; are definately BAD, we're frowned upon 8-)
:05168015 !jl 05168042                  ; Jump Away (BAD)
:05168017  cmp word ptr [ebp-0E], 001E  ; If (DAYS_LEFT) GREATER 30_DAYS then
:0516801C !jg 05168068                  ; Jump Away (GOOD)
:0516801E  lea eax, dword ptr [ebp-0C]  ; Else, continue (BAD)
:05168021  push eax                     ; Save: ADDRESS
:05168022  push [ebp-0E]                ; Save: DAYS_LEFT
:05168025  call 050252BF                ; CONVERT HEX to DEC digit 
                               ; Return value, [ebp-10]= DAYS_LEFT (decimal)
:0516802A  push 00000000                ;
:0516802C  lea eax, dword ptr [ebp-0C]  ; EAX = ADDRESS OF DAYS_LEFT (dec)
:0516802F  push 00000000                ; i.e. If we had "21 days left"
:05168031  push 00000000                ;      we would have: 
:05168033  push eax                     ; [ebp-0C] = xxxx3231h = 21 days
:05168034  push 00001521      ; MessageBox "will expire in ^1 day(s)"
:05168039  push 00000001      ; String Resource ID=00001: "Microsoft Project"
:0516803B  call 0500F9A1      ; USER32.MessageBoxA inside
:05168040  jmp 05168068                 ; Jump anyWay (NEUTRAL)
:05168042 *mov [ebp-02], 0001           ; OH...NO... SET FLAG (BAD)
:05168048  push 00001522      ; MessagBox: "Project 4.1 [...] has expired"
:0516804D  push 00000001      ; String Resource ID=00001: "Microsoft Project"
:0516804F  call 0500FBD1      ; USER32.MessageBoxA inside
:05168054  push 00000000                ;
:05168056  mov eax, [052C4340]          ;
:0516805B  push 00000072                ;
:0516805D  push 00000505                ;
:05168062  push eax                     ;
:05168063  call 0504ECC8                ; 
:05168068 *mov ax, word ptr [ebp-02]    ;RETURN VALUE, AX = FLAG
:0516806C  pop edi                                     AX = 0001 (SET=BAD) 
:0516806D  pop esi                       *IMPORTANT*   AX = 0000 (CLEAR=GOOD)
:0516806E  pop ebx
:0516806F  mov esp, ebp
:05168071  pop ebp
:05168072  ret                          ; Bye.

By looking at the previous dead listing, we see that there are 4 jumps
that can trigger our "has expired" MessageBoxA. It must therefore meet
one of the following conditions:

   1.   something_invalid?  ([ebp-14] = 0001)
or 2.   the current year is below 1984 or above 2049 ([ebp-02] = 0001)
        NOTE: expire or not, Winproj would not extend our date even if
        we were below 1984 or above 2049 because of the way date_format
        is calculated.  (see the date-encryption section)
or 3.   have more than +90 days left. ("PROTECTIONISTS' BAIT", indeed :-) 

And for the "expire in ^1 days" MessageBoxA:
   A.   must pass the 3 previous conditions (of course :-)
or B.   must have 30 or less days left.

I guess I've just answer the strainer for WINPROJ. Changing the
opcode "jl" at address :05168015 to "nop" (or equivalent) will
only work IF AND ONLY IF :-)
    1.  we passed condition 1 and 2
and 2.  we passed contition B.

Actually, we have only eliminated condition 3 and that's THE
reason why it would have NOT always worked. 
.------------------------------------.
| STEP 1:  ESTABLISHMENT OF A CRACK  |
'------------------------------------'
The Quick Crack for Micro$oft Project 95 by +ORC works very well
but will show the "will expire in ^1 day(s)" nagscreen if we have
less than 30 days remaining.     

So the second part of the quick crack would be:
ORIGINAL    :0516801C 7F4A               jg 05168068 
CRACK       :0516801C EB4A               jmp 05168068 

But then, looking near the end of our previous dead listing we
see a RET at address :05168072. Hummm... we must be inside a call.
Here's the code again:

:05168068 *mov ax, word ptr [ebp-02]    ;RETURN VALUE, AX = FLAG
:0516806C  pop edi                                     AX = 0001 (SET=BAD) 
:0516806D  pop esi                       *IMPORTANT*   AX = 0000 (CLEAR=GOOD)
:0516806E  pop ebx
:0516806F  mov esp, ebp
:05168071  pop ebp
:05168072  ret                          ; Bye.

Above the RET, we see that [ebp-02] is move to the ax register.
This probably mean that [ebp-02] is less important ONCE we are out
of the call and that ax holds very last "GOOD" or "BAD" flag.
Let's see where it was called :

:05168453 EB07                    jmp 0516845C

* Possible Reference to String Resource ID=00001: "Microsoft Project"
                                  |
:05168455 6A01                    push 00000001
:05168457 E80D64F8FF              call 050EE869
:0516845C E817F7FFFF             *call 05167B78 ; CALL the time-protection?
:05168461 6685C0                  test ax, ax   ;If (ReturnValue) = 0000 THEN
:05168464 0F8596000000            jne 05168500  ;Jump Away (byebye, bad guy) 
:0516846A 6633F6                  xor si, si
   [...]
:051684EF 6685F6                  test si, si
:051684F2 7407                    je 051684FB

* Possible Reference to String Resource ID=00002: "Microsoft Project"
                                  |
:051684F4 6A02                    push 00000002
:051684F6 E80C21EEFF              call 0504A607
:051684FB E8B7D1F9FF              call 051056B7
:05168500 C605682D3C0501          mov byte ptr [053C2D68], 01
:05168507 6A00                    push 00000000
:05168509 E83E290900              call 051FAE4C
:0516850E 68FFFF0000              push 0000FFFF
:05168513 E83053FFFF              call 0515D848
:05168518 6A00                    push 00000000
:0516851A E8E820EEFF              call 0504A607
:0516851F 5F                      pop edi
:05168520 5E                      pop esi
:05168521 5B                      pop ebx
:05168522 8BE5                    mov esp, ebp
:05168524 5D                      pop ebp
:05168525 C20C00                  ret 000C


Could this be the time-protection call? If it was, a RET (C3) at the
very beginning of this call would have cracked the whole protection
scheme. Since we are not that sure, let's glimpse  for imported and
exported functions inside our CALL starting at :05167B78

* Reference To: KERNEL32.lstrlenA, Ord:0274h
* Reference To: KERNEL32.lstrcpyA, Ord:026Eh
* Reference To: KERNEL32.lstrcatA, Ord:0265h
* Reference To: ADVAPI32.RegOpenKeyA, Ord:00D8h
* Reference To: ADVAPI32.RegQueryValueExA, Ord:00E1h
* Reference To: ADVAPI32.RegCloseKey, Ord:00C2h
* Reference To: ADVAPI32.RegQueryValueA, Ord:00E0h

The reason why we're looking for import/export functions inside the
call is that we don't what to crippled instructions that are not
related to the time-protection. Those ADVAPI32.Regx sure gives us a
slight idea of the call purpose :-)  Anyway, I guess we'll have to
try it to see... and it works!

Now, the updated comments:
:0516845C E817F7FFFF             *call 05167B78 ; we have a "ret" inside
                                                ; time-protection CALL.
                                                ; *now confirmed*
We should also consider our ax register but, here:
:05168461 6685C0                  test ax, ax   ; by default, ax = 0000
:05168464 0F8596000000            jne 05168500  ; so it won't jump 

So let's it patch it:
                                          Quite a delay since our last crack
                                                 Vacation, I guess :-)         
  .----------------------------------------------------------|----------.
  | Crack for [M$-PROJECT V4.1 EVALUATION COPY] by TOXINE (11 Aug 1997) |
  |---------------------------------------------------------------------|
  | File:      [WINPROJ.EXE] [4240384 bytez] [09-24-96]  [12:00a]       |
  '---------------------------------------------------------------------'
[1/2]   ORIGINAL  :05167B78 55           push ebp
        CRACK               C3           ret
(With Hiew, goto offset :00166F78)

And just to make sure it will work a 100%, we patch this:

[2/2]   ORIGINAL  :05168461 6685C0       test ax, ax
        CRACK               ..33..        xor ax, ax
(With Hiew, goto offset :00167861)  btw: "xor" are among the fastest opcode  

This "ret" cracking approach can be very useful to prevent invasive
patching or even overhead time-load. Each instruction has a specific
clock cycle. So, the more you have the instructions, the more time it
takes. That's why microsoft products are damn slow. Not to mention, the
ever-growing registry .DAT file that adds to it. Now, do you see why I
prefer to use my customized NC for DOS instead of the Win95 version :-)
(As matter of a fact, try using this "ret" cracking approach on some
Borland's Trials. You'll soon see that there is a LOT of calls within
calls that can be easily crack with a simple "ret" at the upper most
time-protection call which is, incredible as it may seem, near the
proggy entry-point! Reminds me of cracking good old DOS proggies!)
.------------------------------------------.
| STEP 2:  WINPROJ DATE-ENCRYPTION ROUTINE |
'------------------------------------------'
This section is code-intensive but nevertheless self-explanatory.
If you want, you may also follow an actual example that are in { }.
                                   mm  dd  yyyy
                                    |   |   |
 Assuming that the current date is: 08/11/1997
                               hex: 08 0B 07CD

  .--INPUT--------------------------.
  |             .------date-------. |
__|              month  day   year  |--------------------------
  |              __|__ __|__ __|__  | 
  |  ss:esp+08:  08 00 0B 00 CD 07  |
  '---------------------------------'

 ==================================================
   DATE-ENCRYPTION: call 052240E1   at :05167F8E
 ==================================================
:052240E1  mov cx, word ptr [esp + 0C]    ; cx = year
                                          { cx = 07CD }
:052240E6  push esi                       ; Save: Original esi
:052240E7  movzx eax, cx                  ; eax = 0000???? = year
                                          { eax = 000007CD }
:052240EA  movzx edx, word ptr [esp + 0C] ; edx = 0000???? = month
                                          { edx = 00000008 }
:052240EF  imul eax, eax, 000005B5        ; (year * 000005B5) = eax
                                       { 000007CD * 000005B5  = 002C84F1 }
:052240F5  sub eax, 002C3ABD              ; EAX  -  002C3ABD  = eax
                                       { 002C84F1 - 002C3ABD  = 00004A34 }
:052240FA  sar eax, 00000002              ; EAX / (00000002*2) = eax
                                          { 00004A34 / 4 = 0000128D }
So, we see that:
   ((year * 000005B5) - 002C3ABD) / (00000002*2) = eax = Value_1

:052240FD  cmp edx, 00000001   ; Are we in January (x01)?
:05224100  je 05224125         ; Jump if so
:05224102  cmp edx, 00000002   ; Or, Are we in February (x02)?
:05224105  je 0522412C         ; Jump if so
____WE'RE_NOT_IN_JANUARY_NOR_FEBRUARY__:
:05224107  and cx, 0003        ; year and 0003 = 000?               
                               { 07CD and 0003 = 0001 }
                               ; (here, if year ???0 then cx=0000
                                                ???1 then cx=0001
                                                ???2 then cx=0002
                                                ???3 then cx=0003
                                                ???4 then cx=0000
                                                ...   ...    ...
                                                ???D then cx=0001 
                                                ???E then cx=0002
                                                ???F then cx=0003 )
:0522410B  cmp cx, 0001        ; if set Zero and Parity Flag
:0522410F  sbb esi, esi        ; esi = 0                 indexed access
:05224111  neg esi                                          |
:05224113  add si, word ptr [2*edx + 053C317C] ; (2*month+DataTable)= Value_2
                                                  |   |   
                                                word  |         
                                                     (01h-0Ch = Jan-Dec)

(To fetch the DataTable, we will do a memory dump inside winice) 

EDX register:                1     2     3     4     5     6     7     8
Month:                     [Jan] [Feb] [Mar] [Apr] [Mai] [Jun] [Jul] [Aug]
                           __|__ __|__ __|__ __|__ __|__ __|__ __|__ __|__ 
013F:053C317C  00 00 00 00 1F 00 3B 00 5A 00 78 00 97 00 B5 00 D4 00 F3 00
     |         11 01 30 01 4E 01 6D 01 00 00 00 00 00 00 00 00 00 00 00 00                 
 Start Of      __|__ __|__ __|__ __|__
 DataTable     [Sep] [Oct] [Nov] [Dec] (Stops here) 
                 9    10     11   12

:0522411B  add si, word ptr [esp + 08]  ; Value_2 + day  = sum
                                        {  00D4   + 0B   = 00DF }  
:05224120  add ax, si                   ; Value_1 + sum  = 8449_dATE
                                        {  128D   + 00DF = 136C }
:05224123  jmp 0522413B                 ; jump to exit
______WE'RE_IN_JANUARY_________________:
:05224125  add ax, word ptr [esp + 08]  ; Value_1 + day = 8449_dATE
                                        {  128D   + 0B  = 1298 }
:0522412A  jmp 0522413B                 ; jump to exit
______WE'RE_IN_FEBRUARY________________:
:0522412C  mov dx, word ptr [053C3180]  ; dx = 1F00h (see DataTable) 
:05224133  add dx, word ptr [esp + 08]  ; 001Fh + day = correction
                                        { 001F + 000B = 002A }
:05224138  add ax, dx                   ; Value_1 + correction = 8449_dATE
                                        {  128D   +    002A    = 12B7 } 
_______FINISHES_HERE___________________:
:0522413B  pop esi                      ; Return 8449_date in ax
:0522413C  ret 000C                     ; bye.

  .--OUTPUT------------------.
--|                          |--------------------------
  |  ax = 8449_date (word)   | 
  |                          |
  '--------------------------'

In this case, the algorithm used by Winproj to calculated the 8449_date
will consequently affect the allowed date limit:
     Minimum    0000h = 1/1/1984
                 ...  =   ...
     Maximum    FFFFh = 1/1/2049.
I guess different date-encryption gives different date limits.
That's it for Winproj. But wait... Didn't we forget doing something...
.----------------------------------.
| STEP 3:   BETA TESTING OUR CRACK |
'----------------------------------'
Change OS date inside the 1984-2049 range, click TOTD nexttip inside
the menu :-) create new file, open file, test anything related to a
date... Wow! We can now confirm that our crack works.
.---------------------------------------.
| LAST STEP:   OTHERS GIVING US CREDIT  |
'---------------------------------------'
Since no body witnessed our marvelous cracks, we feel quite lonely now.
Would it be fun to spread the good news? Let's have a tea-meeting with
some friends ;-)  Now's the time to listen to people saying just how
unbelievable we are!
Do not talk about this to your girlfriend as we want to keep her 
interested in other more fun stuff... :=)
Our Maslow pyramid has now been fully satisfied!


Hope you enjoyed reading this as much as I enjoyed cracking it.
I've tried to make it as complete as I can. Anyhow, it would be
a GREAT honor for me to be admitted in +HCU class of 98.
                 
---[  TOXINE  ]-----[ August 1997 ]-----------------------------