Skip to content

Commit de1eab6

Browse files
1.7 - Memory offsets caching
Starting with this version, memory offsets of things to patch are cached, so the driver won't search for them at each boot.
1 parent 8a75460 commit de1eab6

5 files changed

Lines changed: 223 additions & 29 deletions

File tree

src/Driver.c

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,40 @@
11
#include "TimeDefuser.h"
22

3+
#ifndef TD_LEGACY
4+
BOOLEAN PatchExGetExpirationDate(void* pExGetExpirationDate){
5+
PMDL mdl = NULL;
6+
void* map = NULL;
7+
// Create a MDL paging to get over write protection.
8+
mdl = IoAllocateMdl(pExGetExpirationDate, 8, FALSE, FALSE, NULL);
9+
if (!mdl) {
10+
TDPrint("[X] TimeDefuser: IoAllocateMdl failed.\n");
11+
return FALSE;
12+
}
13+
14+
MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
15+
map = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
16+
if (!map) {
17+
TDPrint("[X] TimeDefuser: MmMapLockedPagesSpecifyCache failed.\n");
18+
return FALSE;
19+
}
20+
MmProtectMdlSystemAddress(mdl, PAGE_READWRITE);
21+
// Write to newly created MDL mapping.
22+
*(int*)map = 0xC3C03148; // xor eax,eax \ ret | This is apparently same for both x86 and x64
23+
// Unmap the MDL
24+
MmUnmapLockedPages(map, mdl);
25+
MmUnlockPages(mdl);
26+
IoFreeMdl(mdl);
27+
return TRUE;
28+
}
29+
#endif
30+
331
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
432
LARGE_INTEGER* li = KUSERSystemExpirationDate; // Address of SystemExpirationDate field at KUSER_SHARED_DATA
533
unsigned long long TimebombStamp = 0; // Expiration date stamp
634
RTL_PROCESS_MODULES ModuleInfo = { 0 }; // Structure used for getting kernel base address
735
unsigned long long* KernelBase = NULL; // Kernel Base address
836
ULONG KernelSize = 0; // Kernel image size
37+
HANDLE hKey = OpenRegistryKey(RegistryPath);
938
#ifndef TD_LEGACY
1039
unsigned int KernelSize2 = 0; // Var used in loops as a max value
1140
PAGESections ps[5] = { 0 }; // PE sections that name starts with "PAGE"
@@ -14,7 +43,6 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
1443

1544
// Unrefence unused variables.
1645
UNREFERENCED_PARAMETER(DriverObject);
17-
UNREFERENCED_PARAMETER(RegistryPath);
1846

1947
// Print version info.
2048
TDPrint("[*] TimeDefuser: version " td_version td_variant" loaded "
@@ -39,6 +67,52 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
3967
KernelSize = ModuleInfo.Modules[0].ImageSize;
4068
TDPrint("[+] TimeDefuser: Kernel Base address is 0x%p and size is %lu\n", KernelBase, KernelSize);
4169

70+
// Check whether addresses are cached
71+
if (hKey) {
72+
if (CompareKernelVersion(hKey)) {
73+
// Get cached address offsets for timestamps.
74+
int Stamp1 = RegReadValue(hKey, L"Stamp1", NULL, 0),
75+
Stamp2 = RegReadValue(hKey, L"Stamp2", NULL, 0);
76+
77+
// Zero first timestamp
78+
if (!Stamp1) {
79+
// No cached address, assume nothing is cached.
80+
goto patchBeginning;
81+
}
82+
TDPrint("[*] TimeDefuser: Cached addresses are found on registry.\n");
83+
TDPrint("[+] TimeDefuser: Cached ExpNtExpirationDate address 0x%p is used.\n", (unsigned long long*)((char*)KernelBase + Stamp1));
84+
*(unsigned long long*)((char*)KernelBase+Stamp1) = 0;
85+
#ifdef TD_LEGACY
86+
// On legacy, for some reason, actual timebomb stamp
87+
// is the next qword (on XP 2526). We will zero that too.
88+
*(unsigned long long*)((char*)KernelBase+Stamp1+8) = 0;
89+
#endif
90+
91+
// Zero second timestamp if available.
92+
if (Stamp2) {
93+
TDPrint("[+] TimeDefuser: Cached ExpNtExpirationData address 0x%p is used.\n", (char*)KernelBase + Stamp2);
94+
#ifdef TD_LEGACY
95+
RtlZeroMemory((char*)KernelBase + Stamp2, 16);
96+
#else
97+
*(unsigned long long*)((char*)KernelBase + Stamp2) = 0;
98+
#endif
99+
}
100+
101+
#ifndef TD_LEGACY
102+
int Function = RegReadValue(hKey, L"Function", NULL, 0);
103+
TDPrint("[+] TimeDefuser: Cached ExGetExpirationDate function address 0x%p is used.\n", (char*)KernelBase + Function);
104+
if (!PatchExGetExpirationDate((char*)KernelBase + Function))
105+
goto patchFail;
106+
#endif
107+
goto patchOK;
108+
}
109+
else {
110+
// Kernel version mismatch.
111+
}
112+
}
113+
patchBeginning:
114+
TDPrint("[*] TimeDefuser: No or mismatching cached addresses are found on registry.\n");
115+
SaveKernelVersion(hKey);
42116
#ifdef TD_LEGACY
43117
// Search for timebomb stamp in memory
44118
KernelSize /= sizeof(unsigned __int64);
@@ -49,6 +123,7 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
49123
// For some reason actual timebomb was the next qword on XP 2526, I'll save this and search for it again.
50124
TimebombStamp = KernelBase[i + 1]; // Save the lower part of stamp.
51125
KernelBase[i + 1] = 0; // And null where I found it too.
126+
RegWriteDword(hKey, L"Stamp1", (ULONG)((unsigned char*)&KernelBase[i] - (unsigned char*)KernelBase));
52127
break;
53128
}
54129
}
@@ -58,6 +133,7 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
58133
if ((int)KernelBase[i] == (int)TimebombStamp) {
59134
TDPrint("[+] TimeDefuser: ExpNtExpirationData found at 0x%p\n", &KernelBase[i]);
60135
RtlZeroMemory(&KernelBase[i], 16);
136+
RegWriteDword(hKey, L"Stamp2", (ULONG)((unsigned char*)&KernelBase[i] - (unsigned char*)KernelBase));
61137
goto patchOK;
62138
}
63139
}
@@ -98,18 +174,22 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
98174
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[+] TimeDefuser: searching for stamp at 0x%p in %d bytes\n", PotentialTimestamp, KernelSize2));
99175

100176
KernelSize2;
101-
for (size_t i = 0; i < KernelSize2; i++) {
177+
for (ULONG i = 0; i < KernelSize2; i++) {
102178
if (*(unsigned long long*) & PotentialTimestamp[i] == TimebombStamp) {
103179
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[+] TimeDefuser: Timebomb stamp found at 0x%p\n", &PotentialTimestamp[i]));
104180
*(unsigned long long*)(&PotentialTimestamp[i]) = 0;
105181
pExpNtExpirationDate = &PotentialTimestamp[i];
106182

107183
if (occurance) {
108184
pExpNtExpirationDate = &PotentialTimestamp[i];
185+
RegWriteDword(hKey, L"Stamp2", (ULONG)(&PotentialTimestamp[i] - (unsigned char*)KernelBase));
109186
occurance = 2;
110187
break;
111188
}
112-
else occurance = 1;
189+
else {
190+
occurance = 1;
191+
RegWriteDword(hKey, L"Stamp1", (ULONG)((unsigned char*)&PotentialTimestamp[i] - (unsigned char*)KernelBase));
192+
}
113193
}
114194
}
115195

@@ -168,31 +248,13 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
168248
for (unsigned char j = 0; j < 100; j++) {
169249
if (*(unsigned char*)&PotentialTimeRef[i - j] == 0xe8) { // CALL instruction found.
170250
unsigned char* pExGetExpirationDate = &PotentialTimeRef[i - j + 5];
171-
PMDL mdl = NULL;
172-
void* map = NULL;
173251
pExGetExpirationDate += *(unsigned int*)&PotentialTimeRef[i - j + 1]; // Next 4 bytes are relative address to our current location.
252+
RegWriteDword(hKey, L"Function", (ULONG)(pExGetExpirationDate - (unsigned char*)KernelBase));
174253
TDPrint("[+] TimeDefuser: ExGetExpirationDate found at 0x%p\n", pExGetExpirationDate);
175-
// Create a MDL paging to get over write protection.
176-
mdl = IoAllocateMdl(pExGetExpirationDate, 8, FALSE, FALSE, NULL);
177-
if (!mdl) {
178-
TDPrint("[X] TimeDefuser: IoAllocateMdl failed.\n");
179-
goto patchFail;
180-
}
181-
182-
MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
183-
map = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
184-
if (!map) {
185-
TDPrint("[X] TimeDefuser: MmMapLockedPagesSpecifyCache failed.\n");
254+
if (!PatchExGetExpirationDate(pExGetExpirationDate))
186255
goto patchFail;
187-
}
188-
MmProtectMdlSystemAddress(mdl, PAGE_READWRITE);
189-
// Write to newly created MDL mapping.
190-
*(int*)map = 0xC3C03148; // xor eax,eax \ ret | This is apparently same for both x86 and x64
191-
// Unmap the MDL
192-
MmUnmapLockedPages(map, mdl);
193-
MmUnlockPages(mdl);
194-
IoFreeMdl(mdl);
195-
goto patchOK;
256+
else
257+
goto patchOK;
196258
}
197259
}
198260
break;
@@ -212,7 +274,7 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
212274
// Clear the ExpirationdDate field in SharedData.
213275
// Since 1.4, this is the last step so it will stay there in case of failure
214276
// and won't cause any false positives anymore.
215-
li->QuadPart = 0;
277+
li->QuadPart = 0; ZwClose(hKey);
216278
TDPrint("[*] TimeDefuser: Patch completed successfully.\n");
217279
return STATUS_SUCCESS;
218280
}

src/Registry.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#include "TimeDefuser.h"
2+
3+
HANDLE OpenRegistryKey(PUNICODE_STRING KeyPath){
4+
HANDLE ret = 0;
5+
OBJECT_ATTRIBUTES oa;
6+
InitializeObjectAttributes(
7+
&oa,
8+
KeyPath,
9+
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
10+
NULL,
11+
NULL
12+
);
13+
14+
ZwCreateKey(
15+
&ret,
16+
KEY_READ | KEY_WRITE,
17+
&oa,
18+
0,
19+
NULL,
20+
REG_OPTION_NON_VOLATILE,
21+
NULL
22+
);
23+
24+
return ret;
25+
}
26+
27+
// Writes a DWORD value
28+
NTSTATUS RegWriteDword(HANDLE hKey, PCWSTR ValueName, ULONG Data) {
29+
UNICODE_STRING valName;
30+
RtlInitUnicodeString(&valName, ValueName);
31+
32+
return ZwSetValueKey(
33+
hKey,
34+
&valName,
35+
0,
36+
REG_DWORD,
37+
&Data,
38+
sizeof(Data)
39+
);
40+
}
41+
42+
// Reads a value from registry
43+
// If DWORD, return value is the value.
44+
// Else, value is saved at ValueOutput.
45+
ULONG RegReadValue(_In_ HANDLE KeyHandle, _In_ PCWSTR ValueName, _Out_ PVOID ValueOutput, _In_ ULONG ValueOutputSz) {
46+
NTSTATUS status;
47+
PKEY_VALUE_PARTIAL_INFORMATION kvpi;
48+
ULONG size = sizeof(KEY_VALUE_PARTIAL_INFORMATION)+128;
49+
ULONG retLen = 0, ret = 0;
50+
UNICODE_STRING valName;
51+
52+
// Allocate memory for kvpi
53+
kvpi = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePoolWithTag(PagedPool, size, 'rgeR');
54+
if (!kvpi) return 0;
55+
56+
RtlInitUnicodeString(&valName, ValueName);
57+
58+
status = ZwQueryValueKey(
59+
KeyHandle,
60+
&valName,
61+
KeyValuePartialInformation,
62+
kvpi,
63+
size,
64+
&retLen
65+
);
66+
67+
if (NT_SUCCESS(status)) {
68+
if (kvpi->Type == REG_DWORD && kvpi->DataLength == sizeof(ULONG)) {
69+
ret = *(ULONG*)kvpi->Data;
70+
}
71+
else {
72+
if (kvpi->DataLength < ValueOutputSz) {
73+
ret = kvpi->DataLength;
74+
RtlCopyMemory(ValueOutput, kvpi->Data, ret);
75+
}
76+
else status = STATUS_INVALID_PARAMETER;
77+
}
78+
}
79+
80+
ExFreePoolWithTag(kvpi, 'rgeR');
81+
return ret;
82+
}
83+
84+
// Saves kernel build version to a value key named "KernelVersion"
85+
NTSTATUS SaveKernelVersion(_In_ HANDLE hKey) {
86+
UNICODE_STRING valName;
87+
NTSTATUS status;
88+
89+
RtlInitUnicodeString(&valName, L"KernelVersion");
90+
91+
ULONG major, minor, build;
92+
PsGetVersion(&major, &minor, &build, NULL);
93+
94+
WCHAR kernelVersionString[64] = L"";
95+
RtlStringCchPrintfW(kernelVersionString, 64, L"%u.%u.%u", major, minor, build);
96+
97+
SIZE_T len = (wcslen(kernelVersionString) + 1) * sizeof(WCHAR);
98+
99+
status = ZwSetValueKey(
100+
hKey,
101+
&valName,
102+
0,
103+
REG_SZ,
104+
(PVOID)kernelVersionString,
105+
(ULONG)len
106+
);
107+
108+
return status;
109+
}
110+
111+
BOOLEAN CompareKernelVersion(_In_ HANDLE hKey) {
112+
// Firstly we will read the version of current kernel and make it a string.
113+
ULONG major, minor, build;
114+
PsGetVersion(&major, &minor, &build, NULL);
115+
WCHAR kernelVersionCurrent[64] = L"";
116+
RtlStringCchPrintfW(kernelVersionCurrent, 64, L"%u.%u.%u", major, minor, build);
117+
118+
// Then we will get the value from registry.
119+
WCHAR kernelVersionReg[64] = L"";
120+
if (!RegReadValue(hKey, L"KernelVersion", kernelVersionReg, 64)) return FALSE;
121+
122+
// Lastly we will compare and return.
123+
return wcscmp(kernelVersionCurrent, kernelVersionReg) == 0;
124+
}

src/TimeDefuser.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/// General definitions for TimeDefuser
22

3-
/// Includes (only one)
3+
/// Includes
44
#include <ntddk.h>
5+
#include <ntstrsafe.h>
56

67
/// Definitions
7-
#define td_version "1.6.1"
8+
#define td_version "1.7"
89
#ifdef TD_LEGACY
910
#define td_variant " (Legacy)"
1011
#else
@@ -60,5 +61,12 @@ extern __kernel_entry NTSTATUS NTAPI ZwQuerySystemInformation(
6061
PULONG ReturnLength OPTIONAL
6162
);
6263

64+
/// TimeDefuser Registry Functions
65+
HANDLE OpenRegistryKey(PUNICODE_STRING KeyPath);
66+
NTSTATUS RegWriteDword(HANDLE hKey, PCWSTR ValueName, ULONG Data);
67+
ULONG RegReadValue(_In_ HANDLE KeyHandle, _In_ PCWSTR ValueName, _Out_ PVOID ValueOutput, _In_ ULONG ValueOutputSz);
68+
NTSTATUS SaveKernelVersion(_In_ HANDLE hKey);
69+
BOOLEAN CompareKernelVersion(_In_ HANDLE hKey);
70+
6371
/// TimeDefuser macros
6472
#define TDPrint(...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ##__VA_ARGS__);

src/TimeDefuser.inf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Class = System
1111
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318}
1212
Provider = %ManufacturerName%
1313
CatalogFile = TimeDefuser.cat
14-
DriverVer = 12/03/2025,1.6.1
14+
DriverVer = 12/04/2025,1.7
1515
PnpLockdown = 1
1616

1717
[DestinationDirs]

src/TimeDefuser.rc

-8 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)