Log in

View Full Version : An unusual driver based Time-trial protection


Kayaker
February 23rd, 2005, 19:17
Hi All,

I thought I'd mention a little driver based time-limited demo found running loose in the wilds...

I was examining a 'security' driver recently, with a friend and fellow member, to see how it operates. Purely academic reversing you understand, so I was quite naturally concerned that its 15-day time trial might interfere with our long term research plans... ;-)

Briefly, it hardcodes the download date (y/m/d) within the .data section of the sys driver, which is bundled with 5 other executables (exe/dll) into a single install file. If you download the install file the next day from the server, the file has been updated to the new date, the only changes between the 2 files. We'll call this weakness #1...

The driver, which loads on system startup, uses KeQuerySystemTime to get the current date, then uses RtlTimeFieldsToTime to convert its hardcoded download date from the .data section, to a comparable format. Then there is a simple comparison and an ExpiredFlag is set.

The ExpiredFlag is checked in 3 different ways. There is a DeviceIoControl call by the main GUI app when it starts up, which simply checks if the ExpiredFlag is set in the driver and returns. If expired, a Nag dialog pops up, thence to _Exit. The other 2 ways are slightly more interesting.


The driver itself is designed to monitor applications starting up as part of its functionality, along with hooking certain IDT and ServiceDescriptorTable vectors, and making version-specific patches to ntoskrnl. It uses both PsSetCreateProcessNotifyRoutine and PsSetCreateThreadNotifyRoutine to register system callback routines, which are user-defined functions which will be notified any time a new process or thread is created or destroyed.


The PsSetCreateProcessNotifyRoutine callback has only 1 purpose - every time an application on your system starts/ends, there is a further check using KeQuerySystemTime and RtlTimeFieldsToTime to set (or not) the ExpiredFlag.
Code:

ProcessNotifyRoutine:
call querytime ; KeQuerySystemTime + RtlTimeFieldsToTime
test eax, eax
jnz @F
mov ExpiredFlag, 1
@@:
retn 0Ch


The 2nd callback registered to the system, by PsSetCreateThreadNotifyRoutine, monitors thread creation and deletion, and is the actual workhorse of the driver. But what it *also* does is sneakily take a look at the ExpiredFlag once again and act accordingly.
Code:

(*PCREATE_THREAD_NOTIFY_ROUTINE) (
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
);

ThreadNotifyRoutine:
...
mov edx, [ebp+ProcessId]
lea ecx, [ebp+var_4]
push ecx ; OUT PEPROCESS *Process,
push edx ; IN PVOID ProcessId
call PsLookupProcessByProcessId
test eax, eax
jl _exitThreadNotifyRoutine
mov eax, ExpiredFlag
test eax, eax
jnz _exitThreadNotifyRoutine
...


And finally, the hardcoded date check itself:
Code:

; Format of embedded trial date in the .sys driver data section
; .data:0 HardcodedTrialDate_year dd 7D518h
; .data:4 HardcodedTrialDate_month dd 200h
; .data:8 HardcodedTrialDate_day dd 1A00h

QueryTime proc near

CurrentTime = LARGE_INTEGER ptr -20h
Time = LARGE_INTEGER ptr -18h
TimeFields = TIME_FIELDS ptr -10h

.text:10 sub esp, 20h
.text:13 push ebx
.text:14 push esi
.text:15 lea eax, [esp+28h+CurrentTime]
.text:19 push edi
.text:1A push eax ; CurrentTime
.text:1B call ds:KeQuerySystemTime ;
.text:1B ; KeQuerySystemTime obtains the current system time
.text:1B ;
.text:1B ; KeQuerySystemTime(
.text:1B ; OUT PLARGE_INTEGER CurrentTime
.text:1B ; );
.text:1B ;
.text:21 mov esi, HardcodedTrialDate_month
.text:27 mov edi, HardcodedTrialDate_day
.text:2D mov ebx, HardcodedTrialDate_year
.text:33 xor ecx, ecx
.text:35 mov dword ptr [esp+2Ch+TimeFields.Year], ecx
.text:39 and esi, 0FFFFh
.text:3F and edi, 0FFFFh
.text:45 mov dword ptr [esp+2Ch+TimeFields.Day], ecx
.text:49 lea edx, [esp+2Ch+Time]
.text:4D lea eax, [esp+2Ch+TimeFields]
.text:51 shr ebx, 8
.text:54 shr esi, 8
.text:57 shr edi, 8
.text:5A mov dword ptr [esp+2Ch+TimeFields.Minute], ecx
.text:5E push edx ; Time
.text:5F push eax ; TimeFields
.text:60 mov dword ptr [esp+34h+TimeFields.Milliseconds], ecx
.text:64 mov [esp+34h+TimeFields.Day], di ; 1Bh = Day 27
.text:69 mov [esp+34h+TimeFields.Year], bx ; 7D5h = Year 2005
.text:6E mov [esp+34h+TimeFields.Month], si ; 02h = Month Feb.
.text:73 call ds:RtlTimeFieldsToTime ;
.text:73 ; RtlTimeFieldsToTime converts TIME_FIELDS information
.text:73 ; to a system time value
.text:73 ;
.text:73 ; RtlTimeFieldsToTime(
.text:73 ; IN PTIME_FIELDS TimeFields,
.text:73 ; IN PLARGE_INTEGER Time
.text:73 ; );
.text:73 ;
.text:73 ; TimeFields
.text:73 ; Points to the following structure, containing
.text:73 ; the time information to be converted:
.text:73 ; typedef struct TIME_FIELDS {
.text:73 ; CSHORT Year;
.text:73 ; CSHORT Month;
.text:73 ; CSHORT Day;
.text:73 ; CSHORT Hour;
.text:73 ; CSHORT Minute;
.text:73 ; CSHORT Second;
.text:73 ; CSHORT Milliseconds;
.text:73 ; CSHORT Weekday;
.text:73 ; } TIME_FIELDS;
.text:73 ;
.text:79 mov ecx, [esp+2Ch+CurrentTime.HighPart]
.text:7D mov eax, [esp+2Ch+Time.HighPart]
.text:81 cmp ecx, eax
.text:83 jl short _notexpired
.text:85 jg short check_nondemo_magic_value
.text:87 mov edx, [esp+2Ch+CurrentTime.LowPart]
.text:8B mov eax, [esp+2Ch+Time.LowPart]
.text:8F cmp edx, eax
.text:91 jbe short _notexpired
.text:93
.text:93 check_nondemo_magic_value:
.text:93 cmp ebx, 709536h ; and the answer is...?
.text:99 jz short _notexpired
.text:9B cmp esi, 709536h
.text:A1 jz short _notexpired
.text:A3 cmp edi, 709536h
.text:A9 jz short _notexpired
.text:AB pop edi
.text:AC pop esi
.text:AD xor eax, eax
.text:AF pop ebx
.text:B0 add esp, 20h
.text:B3 retn
.text:B4 ; ---------------------------------------------------------------------------
.text:B4 _notexpired:
.text:B4 pop edi
.text:B5 pop esi
.text:B6 mov eax, 1
.text:BB pop ebx
.text:BC add esp, 20h
.text:BF retn
.text:BF QueryTime endp



So what we have are 2 driver callback routines that are continually monitoring the Time-trial status while the driver operates, every time a new process or thread is created/deleted. An interesting approach, while regretfully naive, for a "payment non-assurance plan"

Cheers,
Kayaker

disavowed
February 23rd, 2005, 22:42
Quote:
[Originally Posted by Kayaker]and making version-specific patches to ntoskrnl

ughh... that's not good software design

Kayaker
February 24th, 2005, 00:40
Let's see, there are about 26 variables to hold version-specific info, such as index values for the SSDT, EPROCESS/KTHREAD field offsets, etc., plus 2 version specific ntoskrnl patch search patterns, all defined for the 4 common OS versions from NT to Server2003, which makes about er, (26+2)*4, carry the 1, a heck of a lot of,... what you said. It goes with the hack nature of the driver, pretty it's not

LiSa
March 11th, 2005, 12:08
Very interesting post Kayaker, these crappy drivers seems to be used on more application than the well known t*k*.sys found on specific music sofwares....Using ssdt and idt and trampolining int1 or int3 always results in a terrific mess whith non permissives homemade rootkits. It is really nosense to sell such things (in fact look like stolen from a russian virii, I disassembled one year ago) as a protection.

L!sA

Kayaker
March 12th, 2005, 01:50
Hi L!sA, I agree with you. Some of these "protections" get too intrusive for their own good. From what I've read, Pace is an excellent example, it's set the professional audio community into an uproar and any software using it, is recommended to be avoided like the plague. It's unfortunate for the developers that they relied on such an aggressive and expensive protection.

From a discussion group soundly complaining about the driver I found this excellent and chilling essay which should be a lesson to all responsible software developers

http://www.prorec.com/prorec/articles.nsf/files/4F0B51C39C17DA6386256B7F0077FBA6

a few comments:

-----
"Isn’t it a pity that such intrusive code was introduced to such a benign application as an audio plugin?

"My experience is clear: products protected by PACE Interlok are incompatible with one another such that uninstalling one can deactivate the others. It’s a shame when an application will not play fair and interoperate with other applications. It’s ridiculous when it will not interoperate with itself.

The situation is so farcical that the common practice is, “buy the software, but install the crack.” A large majority of people I know and respect have all done exactly that. They purchased the software, so that they own a license, but they installed the pirated version, and so avoided the copy protection altogether.

You might ask why someone would install the pirated version if they own the license. The answer is that the common wisdom is that the pirated version (which does not contain any traces of PACE Interlok) is more stable, and if you need to reinstall it, you don’t have to request a reauthorization"
-----

An interesting reflection on the futility of protections...

Kayaker