Skip to content

Commit cedd6c4

Browse files
committed
fix: Make Monika more resilient to updates
Some updates do not increment the raw build number, yet still shift the `.data` segment of `ntoskrnl.exe`. Monika now tries to extract the whole `ProductVersion` resource of `ntoskrnl.exe`, then matches this string to known offsets. Should this method turn out to be wrong, `PicoSppLocateProviderRoutines` does another sanity check of the `Size` field to avoid corrupting critical neighboring data. Fixes #1.
1 parent 1ac99e5 commit cedd6c4

File tree

6 files changed

+247
-18
lines changed

6 files changed

+247
-18
lines changed

lxmonika/lxmonika.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
<ClInclude Include="pico.h" />
124124
<ClInclude Include="picosupport.h" />
125125
<ClInclude Include="PoolAllocator.h" />
126+
<ClInclude Include="winresource.h" />
126127
</ItemGroup>
127128
<ItemGroup>
128129
<None Include="README.md" />

lxmonika/module.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#include "pe.h"
44
#include "os.h"
5+
#include "winresource.h"
56

7+
#include "Logger.h"
68
#include "PoolAllocator.h"
79

810
extern "C"
@@ -178,3 +180,121 @@ MdlpGetProcAddress(
178180

179181
return STATUS_NOT_FOUND;
180182
}
183+
184+
NTSTATUS
185+
MdlpGetProductVersion(
186+
_In_ HANDLE hModule,
187+
_Out_ PCWSTR* pPProductVersion
188+
)
189+
{
190+
if (hModule == NULL || pPProductVersion == NULL)
191+
{
192+
return STATUS_INVALID_PARAMETER;
193+
}
194+
195+
PCHAR pStart = (PCHAR)hModule;
196+
197+
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pStart;
198+
PIMAGE_NT_HEADERS pPeHeader = (PIMAGE_NT_HEADERS)(pStart + pDosHeader->e_lfanew);
199+
200+
PIMAGE_RESOURCE_DIRECTORY pResourceDirectory = (PIMAGE_RESOURCE_DIRECTORY)(pStart +
201+
pPeHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
202+
203+
// For calculating offsets.
204+
PCHAR pRsrcSectionStart = (PCHAR)pResourceDirectory;
205+
206+
// The entries come right after the directory.
207+
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntryList = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)
208+
(pResourceDirectory + 1);
209+
210+
SIZE_T uTotalEntries = pResourceDirectory->NumberOfIdEntries
211+
+ pResourceDirectory->NumberOfNamedEntries;
212+
213+
WORD pResourcePath[] =
214+
{
215+
(WORD)(ULONG_PTR)RT_VERSION,
216+
// This param must be 1
217+
// https://learn.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource
218+
1
219+
};
220+
221+
const auto GoDownPath = [&](PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry)
222+
{
223+
pResourceDirectory = (PIMAGE_RESOURCE_DIRECTORY)
224+
(pRsrcSectionStart + pEntry->OffsetToDirectory);
225+
226+
pEntryList = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pResourceDirectory + 1);
227+
uTotalEntries = pResourceDirectory->NumberOfIdEntries
228+
+ pResourceDirectory->NumberOfNamedEntries;
229+
};
230+
231+
for (SIZE_T i = 0; i < ARRAYSIZE(pResourcePath); ++i)
232+
{
233+
for (SIZE_T j = 0; j < uTotalEntries; ++j)
234+
{
235+
if (!pEntryList[j].NameIsString
236+
&& pEntryList[j].Id == pResourcePath[i]
237+
&& pEntryList[j].DataIsDirectory)
238+
{
239+
// Go down to the tree on this path.
240+
GoDownPath(&pEntryList[j]);
241+
242+
Logger::LogTrace("Found level ", i + 1, " directory with ", uTotalEntries,
243+
" entries.");
244+
245+
goto found;
246+
}
247+
}
248+
249+
Logger::LogError("Cannot find level ", i + 1, " directory with ID ", pResourcePath[i]);
250+
return STATUS_RESOURCE_TYPE_NOT_FOUND;
251+
252+
found:
253+
continue;
254+
}
255+
256+
// Take the first directory type entry. Should be a leaf of the VS_VERSIONINFO data type.
257+
// https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo
258+
for (SIZE_T i = 0; i < uTotalEntries; ++i)
259+
{
260+
if (!pEntryList[i].DataIsDirectory)
261+
{
262+
PIMAGE_RESOURCE_DATA_ENTRY pDataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)
263+
(pRsrcSectionStart + pEntryList[i].OffsetToData);
264+
265+
SIZE_T uVersionInfoLength = pDataEntry->Size;
266+
PCHAR pVersionInfoStart = pStart + pDataEntry->OffsetToData;
267+
268+
PCHAR pProductVersion = pVersionInfoStart;
269+
270+
// TODO: Implement actual resource string tree parsing.
271+
const WCHAR pPattern[] = L"ProductVersion";
272+
while (pProductVersion + sizeof(pPattern)
273+
<= pVersionInfoStart + uVersionInfoLength)
274+
{
275+
if (wcsncmp((PCWSTR)pProductVersion, pPattern, sizeof(pPattern)) != 0)
276+
{
277+
++pProductVersion;
278+
continue;
279+
}
280+
281+
goto found_product_version;
282+
}
283+
284+
Logger::LogError("Cannot find product version");
285+
return STATUS_NOT_FOUND;
286+
287+
found_product_version:
288+
// Align up a 32-bit boundary.
289+
PWCHAR pProductVersionValue = (PWCHAR)ALIGN_UP_BY(
290+
pProductVersion + sizeof(pPattern), 4);
291+
292+
*pPProductVersion = pProductVersionValue;
293+
return STATUS_SUCCESS;
294+
}
295+
}
296+
297+
// No languages available?
298+
Logger::LogError("Cannot find leaf");
299+
return STATUS_RESOURCE_LANG_NOT_FOUND;
300+
}

lxmonika/module.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ NTSTATUS
4343
_Out_ PVOID* pProc
4444
);
4545

46+
NTSTATUS
47+
MdlpGetProductVersion(
48+
_In_ HANDLE hModule,
49+
_Out_ PCWSTR* pPProductVersion
50+
);
51+
4652
#ifdef __cplusplus
4753
}
4854
#endif

lxmonika/monika.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ MapInitialize()
6868

6969
memcpy(&MaOriginalProviderRoutines, pProviderRoutines, sizeof(MaOriginalProviderRoutines));
7070

71+
Logger::LogTrace("Backed up original provider routines.");
72+
7173
// All known versions of the struct contains valid pointers
7274
// for these members, so this should be safe.
7375
pProviderRoutines->DispatchSystemCall = MapSystemCallDispatch;
@@ -77,6 +79,8 @@ MapInitialize()
7779
pProviderRoutines->TerminateProcess = MapTerminateProcess;
7880
pProviderRoutines->WalkUserStack = MapWalkUserStack;
7981

82+
Logger::LogTrace("Successfully patched provider routines.");
83+
8084
return STATUS_SUCCESS;
8185
}
8286

@@ -94,6 +98,7 @@ MapCleanup()
9498
//
9599
// Pico handlers
96100
//
101+
97102
extern "C"
98103
VOID
99104
MapSystemCallDispatch(

lxmonika/picosupport.cpp

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,36 +43,67 @@ PicoSppLocateProviderRoutines(
4343
return status;
4444
}
4545

46-
RTL_OSVERSIONINFOW osVersionInfo;
47-
osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo);
48-
49-
status = RtlGetVersion(&osVersionInfo);
46+
PCWSTR pVersionInfoStringUnicode;
47+
MdlpGetProductVersion(hdlNtKernel, (PCWSTR*)&pVersionInfoStringUnicode);
5048

5149
if (NT_SUCCESS(status))
5250
{
53-
Logger::LogTrace("Detected Windows NT build ", osVersionInfo.dwBuildNumber);
51+
CHAR pVersionInfoStringAnsi[32];
52+
for (SIZE_T i = 0; i < ARRAYSIZE(pVersionInfoStringAnsi); ++i)
53+
{
54+
if (pVersionInfoStringUnicode[i] == L'\0')
55+
{
56+
pVersionInfoStringAnsi[i] = '\0';
57+
break;
58+
}
59+
pVersionInfoStringAnsi[i] = (CHAR)pVersionInfoStringUnicode[i];
60+
}
61+
62+
Logger::LogTrace("Detected Windows NT version ", pVersionInfoStringAnsi);
5463

5564
PVOID pTarget = NULL;
5665

57-
switch (osVersionInfo.dwBuildNumber)
58-
{
5966
#ifdef AMD64
60-
// Windows 11 23H2
61-
case 22621:
62-
pTarget = (PCHAR)hdlNtKernel + 0xC37D00;
63-
break;
64-
#endif
65-
// TODO: Support more builds.
66-
default:
67-
Logger::LogWarning("Windows NT build ",
68-
osVersionInfo.dwBuildNumber, " is not supported.");
67+
if (strncmp(pVersionInfoStringAnsi, "10.0.22621.2715",
68+
sizeof(pVersionInfoStringAnsi)) == 0)
69+
{
70+
pTarget = (PCHAR)hdlNtKernel + 0xC37CA0;
6971
}
72+
#endif
7073

7174
if (pTarget != NULL)
7275
{
7376
Logger::LogTrace("PspPicoProviderRoutines found at ", pTarget);
74-
*pPpr = PspPicoProviderRoutines = (PPS_PICO_PROVIDER_ROUTINES)pTarget;
75-
return STATUS_SUCCESS;
77+
PPS_PICO_PROVIDER_ROUTINES pMaybeTheRightRoutines =
78+
(PPS_PICO_PROVIDER_ROUTINES)pTarget;
79+
80+
// Do a size check first. This reduces the chance of wrong version handling code
81+
// bootlooping Windows.
82+
//
83+
// It might be a good idea to check for the pointers in Lxss as well, however, this
84+
// would defeat the purpose of using known offsets: To support situations where
85+
// other drivers have patched the routines beforehand.
86+
//
87+
// ExitThread is chosen because it is the member right after DispatchSystemCall.
88+
// DispatchSystemCall is absolutely necessary for any Pico provider to function.
89+
90+
if (pMaybeTheRightRoutines->Size <
91+
FIELD_OFFSET(PS_PICO_PROVIDER_ROUTINES, ExitThread)
92+
|| pMaybeTheRightRoutines->Size > sizeof(PS_PICO_PROVIDER_ROUTINES) * 16)
93+
{
94+
Logger::LogWarning("Disregarding known offset due to size being suspicious: ",
95+
pMaybeTheRightRoutines->Size);
96+
}
97+
else
98+
{
99+
*pPpr = PspPicoProviderRoutines = pMaybeTheRightRoutines;
100+
return STATUS_SUCCESS;
101+
}
102+
}
103+
else
104+
{
105+
Logger::LogWarning("Windows NT version ", pVersionInfoStringAnsi,
106+
" is not supported.");
76107
}
77108
}
78109

lxmonika/winresource.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#pragma once
2+
3+
#include <ntdef.h>
4+
#include <intsafe.h>
5+
6+
// winresource.h
7+
//
8+
// Support definitions for handling Win32 resources.
9+
//
10+
// Based on WinUser.h
11+
12+
#define IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0)
13+
#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
14+
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
15+
#ifdef UNICODE
16+
#define MAKEINTRESOURCE MAKEINTRESOURCEW
17+
#else
18+
#define MAKEINTRESOURCE MAKEINTRESOURCEA
19+
#endif // !UNICODE
20+
21+
/*
22+
* Predefined Resource Types
23+
*/
24+
#define RT_CURSOR MAKEINTRESOURCE(1)
25+
#define RT_BITMAP MAKEINTRESOURCE(2)
26+
#define RT_ICON MAKEINTRESOURCE(3)
27+
#define RT_MENU MAKEINTRESOURCE(4)
28+
#define RT_DIALOG MAKEINTRESOURCE(5)
29+
#define RT_STRING MAKEINTRESOURCE(6)
30+
#define RT_FONTDIR MAKEINTRESOURCE(7)
31+
#define RT_FONT MAKEINTRESOURCE(8)
32+
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
33+
#define RT_RCDATA MAKEINTRESOURCE(10)
34+
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)
35+
36+
#define DIFFERENCE 11
37+
#define RT_GROUP_CURSOR MAKEINTRESOURCE((ULONG_PTR)(RT_CURSOR) + DIFFERENCE)
38+
#define RT_GROUP_ICON MAKEINTRESOURCE((ULONG_PTR)(RT_ICON) + DIFFERENCE)
39+
#define RT_VERSION MAKEINTRESOURCE(16)
40+
#define RT_DLGINCLUDE MAKEINTRESOURCE(17)
41+
#if(WINVER >= 0x0400)
42+
#define RT_PLUGPLAY MAKEINTRESOURCE(19)
43+
#define RT_VXD MAKEINTRESOURCE(20)
44+
#define RT_ANICURSOR MAKEINTRESOURCE(21)
45+
#define RT_ANIICON MAKEINTRESOURCE(22)
46+
#endif /* WINVER >= 0x0400 */
47+
#define RT_HTML MAKEINTRESOURCE(23)
48+
#ifdef RC_INVOKED
49+
#define RT_MANIFEST 24
50+
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
51+
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2
52+
#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3
53+
#define ISOLATIONPOLICY_MANIFEST_RESOURCE_ID 4
54+
#define ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID 5
55+
#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID 1 /* inclusive */
56+
#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID 16 /* inclusive */
57+
#else /* RC_INVOKED */
58+
#define RT_MANIFEST MAKEINTRESOURCE(24)
59+
#define CREATEPROCESS_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1)
60+
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(2)
61+
#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(3)
62+
#define ISOLATIONPOLICY_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(4)
63+
#define ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(5)
64+
#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1 /*inclusive*/)
65+
#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(16 /*inclusive*/)
66+
#endif /* RC_INVOKED */

0 commit comments

Comments
 (0)