MSPROJECT

REPRESENTATION OF TIME: The time in this protection is represented as the
number of days passed since 1983-DEC-31. For example the decimal number
1 means 1984-Jan-1 and 4337 means 1995-Aug-17. The routine which converts
the date to the number of passed days starts at 052240E1. It gets day,
month and year as parameters (1983 < year < 2050).

:052240E1  mov cx, word ptr [esp + 0C]		;get year
:052240E6  push esi
:052240E7  movzx eax, cx			;put in eax
:052240EA  movzx edx, word ptr [esp + 0C]	;get month
:052240EF  imul eax, eax, 000005B5	;year * 1461
					;(1641=number of days in 4 year
					,+ a leap day)
:052240F5  sub eax, 002C3ABD		;minus 4 * the number of days
					;passed till 1983-dec-31
					;(correction included for missing
					;leap years like 1000)
:052240FA  sar eax, 00000002		;divide by 4 to get the day number
					;represented by the year
					;lets see the month
:052240FD  cmp edx, 00000001		;January?
:05224100  je 05224125			;yes, go and add the days
:05224102  cmp edx, 00000002		;February?
:05224105  je 0522412C			;yes, no leap day problem go on
:05224107  and cx, 0003			;year modulo 4 (0 means leap year)
:0522410B  cmp cx, 0001			;set carry if leap year
:0522410F  sbb esi, esi			;use carry to compensate
:05224111  neg esi			;for leap day
:05224113  add si, word ptr [2*edx + 053C317C]	;look up from table the
						;number of days passed
						;from the year till the
						;begining of the actual
						;month
:0522411B  add si, word ptr [esp + 08]	;add date day
:05224120  add ax, si			;finally, sum all things up in ax
:05224123  jmp 0522413B			;and return

* Referenced by Jump at Address:
|:05224100(C)
|
:05224125  add ax, word ptr [esp + 08]	;If Jan then just add the days
:0522412A  jmp 0522413B			;return

* Referenced by a Jump at Address:
|:05224105(C)
|
:0522412C  mov dx, word ptr [053C3180]	;If Feb take the days of Jan
:05224133  add dx, word ptr [esp + 08]	;plus the day
:05224138  add ax, dx			;sum all up in ax

* Referenced by Jump at Addresses:
|:05224123(U), :0522412A(U)
|
:0522413B  pop esi
:0522413C  ret 000C			;return



The time limit (installation date + 90 days) is stored in the last digits
of the registry key HKCR\Eced.2\CLSID as a normal decimal number.
(The key also containes a checksum to prevent messing with it.)

Here is the protection routine, with your comments. My comments are marked
with !!

:05167F6A  call 05024E77 		;CALL GetLocalTime
:05167F6F  cmp [ebp-0C], 07C0  		;is 1984?
:05167F75  jb 05167FA6       		;go badflag
:05167F77  cmp [ebp-0C], 0801  		;is 2049?
:05167F7D  ja 05167FA6         		;go badflag
:05167F7F  push [ebp-0C]    		;save validyear
:05167F82  movzx byte ptr ax, [ebp-09] 	;pushday
:05167F87  movzx byte ptr cx, [ebp-0A] 	;pushmonth
:05167F8C  push eax
:05167F8D  push ecx
:05167F8E  call 052240E1       		;call here
					!!This call converts the current
					date to the number of days passed
					since 1983-dec-31

:05167F93  cmp ax, [ebp-1C]             !!Compares it to the time limit
					which is stored in the registry
					and read by the routine at 502538A
					This is the crucial compare.

:05167F97  jge 05167FA6        		;go badflag
:05167F99  mov cx, [ebp-1C]
:05167F9D  sub cx, ax			!!Calculate the remaining days
:05167FA0  mov [ebp-0E], cx		!!store it
:05167FA4  jmp 05167FAC        		;do not badflag

* Referenced by a Jump at Addresses:05167E13(C), :05167ED7(C),
:05167F1B(C), :05167F64(C), :05167F75(C), :05167F7D(C),
:05167F97(C); lotta routines badflag

    :here... this is BADFLAG INTENSIVE
:05167FA6  mov [ebp-02], 0001 		;BAD FLAG !!!***

     :no_bad_flag
:05167FAC  push [ebp-24]
:05167FAF  Call dword ptr[053C5C1C]	;RegCloseKey
:05167FB5  jmp 05167FDC 		;jump 2nd getlocaltime

    :only_5167DE5_calls_this_second_GetLocalTime
:05167FB7  mov [ebp-14], 0000
:05167FBD  lea eax, [ebp-0C]
:05167FC0  push eax 			;SECOND GetLocalTime
:05167FC1  call 05024E77 GetLocalTime  	;call it
:05167FC6  cmp [ebp-0C], 07C0 		;between 1984...
:05167FCC  jb 05167FD6     		;jump flag_unvalid_year
:05167FCE  cmp [ebp-0C], 0801 		;...and 2049?
:05167FD4  jbe 05167FDC    		;jump_to_valid_year

    :flag_unvalid_year
:05167FD6  mov [ebp-02], 0001 		;flag "unvalid_year"

    :valid_year_almost_everybody_calls_here
:05167FDC  cmp [ebp-02], 0000   	;valid_flag?
:05167FE1  jne 05168042  		;beggar off:unvalid something
:05167FE3  cmp [ebp-10], 0000   	;valid ebp-10?
:05167FE8  jne 05167FF8         	;jmp unvalid_e10
:05167FEA  cmp [ebp-14], 0000   	;valid ebp-14?
:05167FEF  je 05168042          	;beggar off,unvalid
					!!Problem with registry key

:05167FF1  cmp [ebp-10], 0000   	;sure that ebp-10=0?
:05167FF6  je 05167FFF          	;continue ifso

    :unvalid_e10
:05167FF8  cmp [ebp-14], 0000   	;check if e14valid
:05167FFD  jne 05168042         	;no? be damned!

    :valid_e10_and_e14
:05167FFF  movsx word ptr ecx, [ebp-0E] ;e10 and e14 true
					!!Get remaining days
:05168003  cmp dword ptr [052C1958], 0001
:0516800B  sbb eax, eax
:0516800D  and eax, 0000005A  		;pretty obvious
:05168010  add eax, 0000005A  		;isnt it?
:05168013  cmp eax, ecx       		;HERE********
					!!Remaining days more than 90
					means system date is set back
					before the installation time

:05168015  jl 05168042        		;beggar off
:05168017  cmp [ebp-0E], 001E 		;0x1E = 30
:0516801C  jg 05168068        		;good guy jump
:0516801E  lea eax, [ebp-0C]		!!Go and send message less than
					30 days remained

The crack at 05168013 can not work well, because it does not check if we
passed the 90 day limit, but it checks whether the system time is set
back before the installation date. If we patch at 5167FA6 then the
main check (at 5167F93) is cracked and ebp-0E (the remaining days) is
not recalculated, but remains as initialized to 5A meaning for the rest
of the protection that not a single day has passed since the installation.


MSMONEY

  In the cracking of MSMONEY the most difficult for me was finding the
targets. While I could collect a lot of different versions I have to
admit I could not find the exact files you were talking about
(noticing the differences in the addresses compared to the code pieces
in your tutorial). While I expected it in the case of MSMONEY 3.0,
because mine is a french version (thanks to Reverser), I was surprised that
even MSMONEY97 has different versions. You did not gave the exact file
version number (my claims to be 5.0p) or file size, so I don't know who's
the later version. I just can hope that if yours the later one the
protection scheme has not changed. (On the other hand, I would be really
surprised keeping in mind that the protection has not changed a bit
since MSMONEY 1.0 (1992)). Well, I described the situation and present
my work, you have to judge, whether cracking different versions disqualify
me automatically, from applying to the +HCU membership.
  Here, I present the analysis of the protections in MSMONEY 3.0 and
MSMONEY97. I have actually done the cracking on MSMONEY 1.0 and just simply
checked these the later versions if they have the same protection routines.
(Of course they have the same protections! Amazing!)

REPRESENTATION OF TIME: In MSMONEY the time is represented in a word
in the following packed format:
	bits 0-4 day (0 representing day 1 of the month)
	bits 5-8 month (0 representing January)
	bits 9-15 year (counted from 1948 which is represented by 0)
Therefore, 0000h is 1948-Jan-1, 62B2h is 1997-Jun-19 and FF7Eh being
the latest date which can be represented 2075-Dec-31. The routine which
codes the system date to the word starts at (51):552, in my MSMONEY 3.0
and at 470640 in MSMONEY97. 

The date encoding function of my MSMONEY 3.0.

:0081.0552  mov ax, ds		
:0081.0554  nop
:0081.0555  inc bp		;function entering set up
:0081.0556  push bp
:0081.0557  mov bp, sp
:0081.0559  push ds
:0081.055A  mov ds, ax		
:0081.055C  sub sp, 000A
:0081.055F  lea ax, [bp-0A]	;where to put the date
:0081.0562  push ax
:0081.0563  call 0010.08C0	;get system date through dos int21/2A
:0081.0568  add sp, 0002
:0081.056B  mov al , [bp-09]	;ok, lets start with month
:0081.056E  sub ah, ah		;clear AH
:0081.0570  dec ax		;count month from 0 (Jan=0)
:0081.0571  shl ax, 05		;shift to position (bit 5-8)
:0081.0574  xor ax, [bp-04]	;?
:0081.0577  and ax, 01E0	;clear everything except month bits
				;01E0 is the mask to isolate month
:0081.057A  xor [bp-04], ax	;store result
:0081.057D  mov al , [bp-04]	;?
:0081.0580  mov cl , [bp-0A]	;get days
:0081.0583  sub ch, ch		;clear ch
:0081.0585  dec cx		;count days from 0
:0081.0586  xor al , cl 	;?
:0081.0588  and ax, 001F	;clear everything except day bits
				;001F is the mask to isolate days
:0081.058B  xor [bp-04], ax	;combine it with the stored month
:0081.058E  mov ax, [bp-08]	;get year
:0081.0591  sub ax, 079C	;minus 1948
:0081.0594  cmp ax, 007F	;compare to the highest number we can
				;represent (7F means 2075)
:0081.0597  jbe 05A8		;less, ok go on

* Referenced by a  Jump at Address:
|:0081.05A6(C)
|
:0081.0599  sub word ptr [bp-08], 0064	;decrease year by 100
:0081.059D  mov ax, [bp-08]
:0081.05A0  sub ax, 079C
:0081.05A3  cmp ax, 007F
:0081.05A6  ja 0599			;until it is less then 2075 :)

* Referenced by  Jump at Address:
|:0081.0597(C)
|
:0081.05A8  mov ax, [bp-08]	;so get the year again
:0081.05AB  sub ax, 001C	;minus 1948
				;if you shift to the left 1948 by 9
				;0011100 will be in bits 15-9 which
				;is exactly 1C before the shift
:0081.05AE  shl ax, 09		;shift to position
:0081.05B1  mov cx, [bp-08]	;?
:0081.05B4  shl cx, 09		;?
:0081.05B7  xor cx, [bp-04]	;get month and day
:0081.05BA  and ch, 01		;clear the year bits (this year is wrong)
:0081.05BD  xor cx, ax		;combine valid year with month and day
:0081.05BF  mov [bp-04], cx	;store the final result
:0081.05C2  mov ax, cx		;result in ax too
:0081.05C4  mov bx, [bp+06]
:0081.05C7  mov ss:[bx], ax	;return the final result
:0081.05CA  mov ax, bx
:0081.05CC  mov dx, ss
:0081.05CE  lea sp, [bp-02]
:0081.05D1  pop ds
:0081.05D2  pop bp
:0081.05D3  dec bp
:0081.05D4  retf 0002

I don't know what the instructions marked by ? mean. Clearly bp-04 is
the position which is used to combine the month, day and year bits, but
the initial value in it has no effect on the final result, and it always
containes the result at the end of the routine.


Here is the corresponding function of my MSMONEY97. It does everything
exactly like the old function.

:004689D0  sub esp, 00000014			;locals
:004689D3  lea eax, dword ptr [esp + 04]	;where to put the date
:004689D7  push eax

* Reference To: KERNEL32.GetLocalTime, Ord:00E2h
                                  |
:004689D8  Call dword ptr [00620140]		;get date
:004689DE  mov ax, word ptr [esp + 06]		;get month
:004689E3  dec ax				;Jan will be 0
:004689E5  shl ax, 0005				;shift to position
:004689E9  xor ax, word ptr [esp + 02]		;?
:004689EE  and ax, 01E0				;isolate month bits
:004689F2  xor word ptr [esp + 02], ax		;store month
:004689F7  mov ax, word ptr [esp + 0A]		;get day
:004689FC  dec ax				;first of month will be 0
:004689FE  xor ax, word ptr [esp + 02]		;?
:00468A03  and ax, 001F				;isolate days
:00468A07  xor word ptr [esp + 02], ax		;combine days with month
:00468A0C  mov eax, dword ptr [esp + 04]	;get year
:00468A10  and eax, 0000FFFF			;clear upper part of eax
:00468A15  sub eax, 0000079C			;minus 1948
:00468A1A  cmp eax, 0000007F			;over 2075
:00468A1D  jle 00468A3E
:00468A1F  mov eax, FFFFFF9C			;-100

* Referenced by a Jump at Address:
|:00468A3C(C)
|
:00468A24  add word ptr [esp + 04], ax		;add to year
:00468A29  mov ecx, dword ptr [esp + 04]
:00468A2D  and ecx, 0000FFFF
:00468A33  sub ecx, 0000079C
:00468A39  cmp ecx, 0000007F
:00468A3C  jg 00468A24				;until its less then 2075

* Referenced by a Jump at Address:
|:00468A1D(C)
|
:00468A3E  mov ax, word ptr [esp + 04]		;?
:00468A43  shl ax, 0009				;?
:00468A47  xor ax, word ptr [esp + 02]		;get month and day
:00468A4C  and ax, 01FF				;clear year bits
:00468A50  mov cx, word ptr [esp + 04]		;get year
:00468A55  sub cx, 001C				;correct with 1948
:00468A59  shl cx, 0009				;shift to position
:00468A5D  xor ax, cx				;combine with month and day
:00468A60  mov word ptr [esp + 02], ax		;store
:00468A65  add esp, 00000014
:00468A68  ret


THE WORKING OF THE PROTECTION: Every time a new MSMONEY data file
(.MNY) is created the actual system date + the time limit (either 60
or 90 days) is written into the data file at offset 224h in MSMONEY 3.0
and 228h in MSMONEY97.
  When the data file is opened this file limit date is compared to the
system date to see if 60 days have passed since the creation of the file
and also it is copied to the fix memory position [64D6] in MSMONEY 3.0
and [00619244] in MSMONEY97, to be used for the check of the date of
every entered transaction.
  The routine which calculates this limit date of a file is also
responsible for checking the year of the system date being the year
when the demo is valid. This routine is the heart of the protection
and starts at (08):14BC and 00470640 in the two versions, respectively.
This routine is called when a new file is created or the year of an opened
file does not match the year of the demo version (MNY files without a date
limit or with date limit from other years can be opened in the demo and
they get a new date limit).

The function from my MSMONEY 3.0. (*** indicates the patches I made in my
crack. They are explained at the end of the essay.)

* Referenced by a CALL at Addresses:
|:0008.0889, :0008.16D6, :0008.172B
|
:0008.14BC enter 0006, 00	;allocate locals
:0008.14C0 cmp word ptr [bp+04], 0000	;calculate limit rigth away?
:0008.14C4 je 14E4		;yes, go and calculate
:0008.14C6 mov ah, [64D7]	;no, lets see what we have already
:0008.14CA and ax, FE00		;isolate year bits with mask
:0008.14CD mov [bp-06], ax	;store it
:0008.14D0 cmp ax, 5C00		;1994?
:0008.14D3 je 14DF		;ok, valid year, short way out
:0008.14D5 cmp ax, 5E00		;1995?
:0008.14D8 je 14DF		;ok, valid year, short way out
:0008.14DA cmp ax, 6000		;1996? (still valid year because the 60
				;day limit might reach into 1996)
:0008.14DD jne 14E4		;none, go and calculate new
				;time limit for the file
* Referenced by a  Jump at Addresses:
|:0008.14D3(C), :0008.14D8(C)
|
:0008.14DF  mov ax, [64D6]	;lets fetch the current time limit
:0008.14E2  jmp 152F		;short way out

* Referenced by a Jump at Addresses:
|:0008.14C4(C), :0008.14DD(C)
|				;this calculates a new time limit
:0008.14E4  lea ax, [bp-04]	;where to put the return value
:0008.14E7  push ax
:0008.14E8  call 0081.0552	;get the coded system date with
				;the previously described function
:0008.14ED  mov bx, ax
:0008.14EF  mov ax, ss:[bx]	;the coded date in ax
:0008.14F2  mov [bp-02], ax	;store it         *** mov [bp-02], F400 ***
:0008.14F5  mov ah, [bp-01]	;take upper byte  *** jmp 151C          ***
:0008.14F8  and ah, FE		;isolate year bits with mask
:0008.14FB  cmp ah, 5C		;1994?
:0008.14FE  je 151C		;ok, valid year
:0008.1500  mov ah, [bp-01]	;get upper half again
:0008.1503  and ah, FE		;isolate year bits
:0008.1506  cmp ah, 5E		;1995?
:0008.1509  je 151C		;ok, valid year
				;notice that 1996 is not valid here
				;because this is pure system date
:0008.150B  push 0917		;prepare for bad system date message
:0008.150E  push 0000
:0008.1510  call 0064.AD1A	;send it
:0008.1515  xor ax, ax		;set flag "not successful"
:0008.1517  leave
:0008.1518  ret 0004		;leave
:0008.151B  nop

* Referenced by a Jump at Addresses:
|:0008.14FE(C), :0008.1509(C)
|				;valid year, lets calculate time limit
:0008.151C  push word ptr [bp-02]	;push date
:0008.151F  push 003C		;push 60 days
:0008.1521  lea ax, [bp-04]	;where to put the result
:0008.1524  push ax
:0008.1525  call 0081.05D8	;calculate sytem date + 60 days
				;we need a whole routine for this
				;because the date is coded
:0008.152A  mov bx, ax
:0008.152C  mov ax, ss:[bx]	;get the new time limit date

* Referenced by a Jump at Address:
|:0008.14E2(U)
|
:0008.152F  mov bx, [bp+06]
:0008.1532  mov [bx], ax	;Store new time limit at [64D6]
:0008.1534  mov ax, 0001	;Flag set "successful"
:0008.1537  leave
:0008.1538  ret 0004		;leave


The similar routine in my MSMONEY 97 does everything like the old one:

* Referenced by a CALL at Addresses:
|:0046F068   , :00470B1E   , :00470C38   , :00470CCE
|
:00470640  sub esp, 00000004	;locals
:00470643  cmp dword ptr [esp + 0C], 0	;calculate new limit right away?
:00470648  je 0047067C		;yes, go calculate
:0047064A  mov ax, [00619244]	;no, let's fetch what we have
:00470650  and ax, FE00		;isolate year bits
:00470654  cmp ax, 6000		;1996?
:00470658  je 00470666		;yes, don't recalculate
:0047065A  cmp ax, 6200		;1997?
:0047065E  je 00470666		;ok, don't recalculate
:00470660  cmp ax, 6400		;1998?
:00470664  jne 0047067C		;none, of them go and calculate new

* Referenced by a Jump at Addresses:
|:00470658(C), :0047065E(C)
|
:00470666  mov ax, [00619244]	;we have a valid year so take the limit
:0047066C  mov ecx, dword ptr [esp + 08]	;where to put the result
:00470670  mov word ptr [ecx], ax		;return the current limit
:00470673  mov eax, 00000001			;set flag "successful"
:00470678  add esp, 00000004
:0047067B  ret

* Referenced by a Jump at Addresses:
|:00470648(C), :00470664(C)
|
:0047067C  call 004689D0		;Get coded time 
:00470681  mov word ptr [esp + 02], ax	;save it  *** mov [esp+02],F400 ***
:00470686  and ax, FE00			;take year*** jmp 004706B8      ***
:0047068A  cmp ax, 6000			;1996?
:0047068E  je 004706B8			;ok, valid go on
:00470690  cmp ax, 6200			;1997?
:00470694  je 004706B8			;ok, go on
:00470696  push 00000000		;prepare to bad system year message
:00470698  mov eax, [00613FF0]
:0047069D  push 00000000
:0047069F  push eax
:004706A0  push 004CF510
:004706A5  push 00002FDC
:004706AA  call 0046BF10		;send it
:004706AF  add esp, 00000014
:004706B2  xor eax, eax			;set flag to "unsuccessful"
:004706B4  add esp, 00000004
:004706B7  ret				;leave

* Referenced by a Jump at Addresses:
|:0047068E(C), :00470694(C)
|
:004706B8  mov eax, dword ptr [esp + 02]	;get the ystem date
:004706BC  push 0000005A			;and 90 days
:004706BE  push eax
:004706BF  call 00468A70			;calculate new time limit
:004706C4  mov ecx, dword ptr [esp + 10]
:004706C8  add esp, 00000008
:004706CB  mov word ptr [ecx], ax		;return it
:004706CE  mov eax, 00000001			;set flag "success"
:004706D3  add esp, 00000004
:004706D6  ret

As I told before, the time limit which is calculated by this function
is stored in each money data file. The routine which checks this time limit
upon opening the file starts at (08):1969 in my MSMONEY 3.0.
It first compares the year of the file date to the year of the demo
version, then goes on to check if the system timer is set back before
the file creation date and finally checks the 60 day limit.

				;File time limit year check
:0008.1696 mov ax, [si+0224]	;get the time limit of the file
:0008.169A mov [bp-0C], ax	;store it
:0008.169D lea ax, [bp-02]
:0008.16A0 push ax
:0008.16A1 call 0081.0552	;Get system date
:0008.16A6 mov bx, ax
:0008.16A8 mov ax, ss:[bx]
:0008.16AB mov [bp-10], ax	;Store system date
:0008.16AE mov ah, [bp-0B]	;Take file time limit
:0008.16B1 and ax, FE00		;Isolate year
:0008.16B4 mov [bp+FE8C], ax	;Store it
:0008.16B8 cmp ax, 5C00		;1994?
:0008.16BB jne 16C0		;no,      *** jmp 16D0 ***
:0008.16BD jmp 1804		;validhecks

* Referenced by a Jump at Address:
|:0008.16BB(C)
|
:0008.16C0 cmp ax, 5E00		;1995?
:0008.16C3 jne 16C8		;no,
:0008.16C5 jmp 1804		;valid year, go on to further checks

* Referenced by a Jump at Address:
|:0008.16C3(C)
|
:0008.16C8 cmp ax, 6000	;1996? (if we started to use the demo in 1995-dec)
:0008.16CB jne 16D0	;none, go to create new time limit
:0008.16CD jmp 1804	;ok, go on to further checks

* Referenced by a Jump at Address:
|:0008.16CB(C)
|
:0008.16D0 lea ax, [bp-0C]	;where to put new time limit
:0008.16D3 push ax
:0008.16D4 push 0001	;force current time limit check
:0008.16D6 call 14BC	;Go and create new file time limit
			;with the above described function
:0008.16D9 or ax, ax	;successful?
:0008.16DB je 16E0	;no, leave short way with error code (07E4) which
			;means that you are not allowed to work with
			;this file, but have to open another one
:0008.16DD jmp 17D0	;yes, go on to checks (pretty useless here)
  .
  .
  .
:0008.17D0 mov ax, [bp-0C]		;get new time limit
:0008.17D3 mov [si+0224], ax		;store it
:0008.17D7 mov byte ptr [si+0223], 00	;?
:0008.17DC mov word ptr [195C], 0001	;?

* Referenced by a Jump at Address:
|:0008.1820(C)
|				;60 day limit check
:0008.17E2 mov ax, [bp-0C]	;get stored file time limit
:0008.17E5 cmp [bp-10], ax	;compare to stored system date
:0008.17E8 jb 1830		;still some day left go on

* Possible Reference to Dialog: DialogID_0494
                |
:0008.17EA push 0494		;Prepare to message 60 day is over
: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	;send it
:0008.1802 jmp 186C		;and leave

* Referenced by a Jump at Addresses:
|:0008.16BD(U), :0008.16C5(U), :0008.16CD(U)
|				;System timer set back check
:0008.1804 push word ptr [bp-10];Push system date
:0008.1807 push 003C		;Push 60 days
:0008.1809 lea ax, [bp-04]	;Where to put result
:0008.180C push ax
:0008.180D call 0081.05D8	;Add system date + 60 days
:0008.1812 mov bx, ax
:0008.1814 mov ax, ss:[bx]
:0008.1817 mov [bp-02], ax	;Store system+60
:0008.181A mov ax, [bp-0C]	;get file time limit
:0008.181D cmp [bp-02], ax	;Compare the two
:0008.1820 jnb 17E2		;File time limit smaller, ok go
				;on to further check
:0008.1822 push 0918		;File time limit higher
:0008.1825 push 0000		;Send system timer set back
:0008.1827 call 0064.AD1A	;message
:0008.182C jmp 1607
:0008.182F nop

* Referenced by a Jump at Address:
|:0008.17E8(C)
|				;last days warning check
:0008.1830 push ax		;push file time limit
:0008.1831 call 0081.08E6	;convert packed time limit to plain
				;number of days passed since 1948
:0008.1836 push word ptr [bp-10];push system date
:0008.1839 mov si, ax 		;store time limit day form
:0008.183B call 0081.08E6	;convert packed system date to passed days
:0008.1840 sub si, ax		;calculate the difference
:0008.1842 mov [bp-02], si	;store
:0008.1845 cmp si, 0007		;more than 1 week?
:0008.1848 jg 186C		;yes, just go on
:0008.184A mov al , [bp-02]	;Prepare for last days message
:0008.184D add al, 30		;number of remaining days in ASCII
:0008.184F mov [bp-12], al
:0008.1852 mov byte ptr [bp-11], 00
:0008.1856 push 4919
: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	;send last days warning

This function is quite long and I intend to patch at the very begining of
the checks, therefore I only cite the first part of the same MSMONEY97
routine which does everything like the old one.

:00470BEC mov ax, word ptr [ebp+0228]		;get file time limit
:00470BF3 mov word ptr [esp + 14], ax		;store it
:00470BF8 call 004689D0				;getsystem date
:00470BFD mov word ptr [esp + 6C], ax		;store it
:00470C02 mov word ptr [esp + 00000178], ax 	;store it
:00470C0A mov ax, word ptr [esp + 14]		;get file time limit
:00470C0F and ax, FE00				;isolate year
:00470C13 cmp ax, 6000			;1996?
:00470C17 je 00470E63			;ok, valid   *** jmp 00470C31 ***
:00470C1D cmp ax, 6200			;1997?
:00470C21 je 00470E63			;ok, valid go on to further checks
:00470C27 cmp ax, 6400			;1998?
:00470C2B je 00470E63			;ok, valid go on to further checks
:00470C31 lea eax, dword ptr [esp + 14]	;where to put new time limit
:00470C35 push 00000001			;force current time limit check
:00470C37 push eax
:00470C38 call 00470640			;go and get new time limit
:00470C3D add esp, 00000008		;with the first described routine
:00470C40 test eax, eax			;success?
:00470C42 jne 00470DEA			;ok we have new time limit
:00470C48 mov ax, 07E4			;problem, leave with error code 07E4
:00470C4C pop ebp			;which means we cannot use this file
:00470C4D pop edi
:00470C4E pop esi
:00470C4F pop ebx
:00470C50 add esp, 00000268
:00470C56 ret


The third component of the protection is the checking if the time of an
entered transaction is over the date limit of the file. This comparison
is done at (0E):2B63 in MSMONEY 3.0 and uses [64D6] where the limit is.

:0014.2B4C cmp word ptr [bp+08], 0000	;flag to check limit or not
					;(set at (35):2234 push 01)
:0014.2B50 je 2B6F			;just go on  *** jmp 2B6F ***
:0014.2B52 test byte ptr [17AC], 01	;?flag (only in MSMONEY 3.0)
:0014.2B57 jne 2B6F			;just go on
:0014.2B59 cmp word ptr [70D8], FFFF	;test if there is a date
:0014.2B5E jne 2B63                     ;yes, go and check the limit
:0014.2B60 jmp 35F8			;no, send missing date warning

* Referenced by a Jump at Address:
|:0014.2B5E(C)
|
:0014.2B63 mov ax, [64D6]		;get time limit
:0014.2B66 cmp [70D8], ax               ;compare to transaction date
:0014.2B6A jb 2B6F                      ;transaction lower ok, accept
:0014.2B6C jmp 3606                     ;over the limit don't accept

* Referenced by a Jump at Addresses:
|:0014.2B50(C), :0014.2B57(C), :0014.2B6A(C)
|
:0014.2B6F cmp word ptr [bp-10], 0000   ;go on with transaction


The similar check in MSMONEY97 starts at 005B550D and uses [619244] where
the limit is stored.

:005B5504 cmp dword ptr [ebp+30], 0	;flag to time check or not
					;(the flag is set at 558BB7 push 01)
:005B5508 je 005B552B			;just go on  *** jmp 005B552B ***
:005B550A mov ecx, dword ptr [ebp+3C]
:005B550D mov ax, word ptr [ecx+01A9]	;get the current transaction date
:005B5514 cmp ax, FFFF			;check if there is any date
:005B5518 je 005B6214                   ;go and send missing date message
:005B551E cmp word ptr [00619244], ax	;compare to the time limit
:005B5525 jbe 005B622D			;over, don't accept

* Referenced by a Jump at Address:
|:005B5508(C)
|
:005B552B cmp dword ptr [ebp+1C], 0	;fine accept the transaction
:005B552F push 00000000
:005B5531 je 005B5551

THE CRACK FOR MSMONEY 3.0:

In MSMONEY there are three levels of protection and we have the three
corresponding code parts. I tried for a long time to devise a single
patch which eliminates all parts of the protection, but could not find
it. Finally, I decided to do a major patch which eliminates most of the
protection, and two other auxillary patches which eliminates the remaining
small parts of the protection.
  The major patch is done in the time limit creating routine and forces it
to create a time limit well into the next century. In MSMONEY 3.0 we change

:0008.14F2  mov [bp-02], ax	> mov [bp-02], F400	;use 2070-01-01
:0008.14F5  mov ah, [bp-01]	> jmp 151C 		;go and create limit

F400 represents 2070-Jan-1 and the routine uses this walue instead of the
current system date. Therefore, the time limit which is writen to the MNY
data file and also used for the transaction check will be 2070-Jan-01 plus
60 days. (We could use an even later date like FExx which is 2075, but it
is not a good idea to push a patch till the limit.) This patch eliminates
the 1994,1995 year check for a new file by jumping over the check,makes
the transactions valid till 2070, and because the 60 day limit check on a
opened MNY file is done only when the year of the limit (written into the
file) is 1994-1996 the patch eliminates it as well. If we open a MNY file
which limit is not 1994-1996 then this function is called and the
limit is replaced by our patched limit. The only problem arise if we want
to open a MNY file which is not created with the patched MSMONEY and the
limit year in the file happens to be 1994-1996, then instead of the patched
routine the 60 day-over check is called. To eliminate this possibility we
have to patch the routine where the file time limit is checked. This can be
done by changing the next instruction:

:0008.16BB jne 16C0	>  jmp 16D0	;always go and get new time limit

This eliminates all checks and directs the program flow to the patched
time limit creation routine where it is set to the 2070 date.
  With these two patches whether we open a money file or create a new one
the time limit which always will be 2070 is copied into [64D6] and used for
the transaction check, therefore that protection is also cracked. This
is useful, because while it seems that all transaction checks are done at
one place both in MSMONEY 3.0 and MSMONEY97, there are so many functions (most
of them I don't even understand) that I can not be sure. Even if there
are multiple transaction check points they all must use the patched date
so they are automatically cracked.
  On the other hand, if we would like to enter transactions without a
date we have to patch the transaction check routine, too. This can be done
by changing:

:0014.2B50 je 2B6F	> jmp 2B6F	;always accept transaction

which eliminates all kinds of transaction checks.

THE CRACK FOR MSMONEY97:

  Because the protection of MSMONEY97 operates exactily the same way like
the older version. The crack can be done along the same lines.
The change in the time limit calculation routine:

:00470681  mov word ptr [esp + 02], ax	> mov word ptr [esp + 02], F400
:00470686  and ax, FE00			> jmp 004706B8

in the file time limit check routine:

:00470C17 je 00470E63			> jmp 00470C31

in the transaction check routine:

:005B5508 je 005B552B			> jmp 005B552B



I hope you could follow my analysis despite my poor english.
Thank you for your attention.
ZER0