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.
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.
And finally, the hardcoded date check itself:
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
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