VirtualBox

source: vbox/trunk/src/VBox/Installer/win/MsiHack/MsiHack.cpp@ 96407

Last change on this file since 96407 was 96407, checked in by vboxsync, 3 years ago

scm copyright and license note update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.3 KB
Line 
1/* $Id: MsiHack.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * MsiHack - Exterimental DLL that intercept small ReadFile calls from
4 * MSI, CABINET and WINTEROP, buffering them using memory mapped files.
5 *
6 * @remarks Doesn't save as much as hoped on fast disks.
7 */
8
9/*
10 * Copyright (C) 2016-2022 Oracle and/or its affiliates.
11 *
12 * This file is part of VirtualBox base platform packages, as
13 * available from https://www.virtualbox.org.
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation, in version 3 of the
18 * License.
19 *
20 * This program is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, see <https://www.gnu.org/licenses>.
27 *
28 * SPDX-License-Identifier: GPL-3.0-only
29 */
30
31
32/*********************************************************************************************************************************
33* Header Files *
34*********************************************************************************************************************************/
35#include <iprt/win/windows.h>
36#include <iprt/string.h>
37#include <iprt/asm.h>
38#include <stdio.h>
39#include <wchar.h>
40
41
42/*********************************************************************************************************************************
43* Defined Constants And Macros *
44*********************************************************************************************************************************/
45/** @MSI_HACK_HANDLE_TO_INDEX
46 * Handle to g_papHandles index.
47 */
48#if ARCH_BITS == 64
49# define MSI_HACK_HANDLE_TO_INDEX(hHandle) (((uintptr_t)hHandle & ~UINT64_C(0x80000000)) >> 3)
50#elif ARCH_BITS == 32
51# define MSI_HACK_HANDLE_TO_INDEX(hHandle) (((uintptr_t)hHandle & ~UINT32_C(0x80000000)) >> 2)
52#else
53# error "Unsupported or missing ARCH_BITS!"
54#endif
55
56/** Generic assertion macro. */
57#define MSIHACK_ASSERT(a_Expr) \
58 do { \
59 if (!!(a_Expr)) { /* likely */ } \
60 else MsiHackErrorF("Assertion failed at line " RT_STR(__LINE__) ": " #a_Expr "\n"); \
61 } while (0)
62
63/** Assertion macro that returns if expression is false. */
64#define MSIHACK_ASSERT_RETURN(a_Expr, a_fRc) \
65 do { \
66 if (!!(a_Expr)) { /* likely */ } \
67 else \
68 { \
69 MsiHackErrorF("Assertion failed at line " RT_STR(__LINE__) ": " #a_Expr "\n"); \
70 return (a_fRc); \
71 } \
72 } while (0)
73
74/** Assertion macro that executes a statemtn when false. */
75#define MSIHACK_ASSERT_STMT(a_Expr, a_Stmt) \
76 do { \
77 if (!!(a_Expr)) { /* likely */ } \
78 else \
79 { \
80 MsiHackErrorF("Assertion failed at line " RT_STR(__LINE__) ": " #a_Expr "\n"); \
81 a_Stmt; \
82 } \
83 } while (0)
84
85/** Assertion macro that executes a statemtn when false. */
86#define MSIHACK_ASSERT_MSG(a_Expr, a_Msg) \
87 do { \
88 if (!!(a_Expr)) { /* likely */ } \
89 else \
90 { \
91 MsiHackErrorF("Assertion failed at line " RT_STR(__LINE__) ": " #a_Expr "\n"); \
92 MsiHackErrorF a_Msg; \
93 } \
94 } while (0)
95
96
97/*********************************************************************************************************************************
98* Structures and Typedefs *
99*********************************************************************************************************************************/
100/**
101 * Intercepted handle data.
102 */
103typedef struct MSIHACKHANDLE
104{
105 /** The actual handle value. */
106 HANDLE hHandle;
107 /** The buffer. */
108 uint8_t *pbBuffer;
109 /** Valid buffer size. */
110 size_t cbBuffer;
111 /** The allocated buffer size. */
112 size_t cbBufferAlloc;
113 /** The file offset of the buffer. */
114 uint64_t offFileBuffer;
115 /** The file size. */
116 uint64_t cbFile;
117 /** The current file offset. */
118 uint64_t offFile;
119 /** Whether pbBuffer is a memory mapping of hHandle. */
120 bool fMemoryMapped;
121 /** We only try caching a file onece. */
122 bool fDontTryAgain;
123 /** Reference counter. */
124 int32_t volatile cRefs;
125 /** Critical section protecting the handle. */
126 CRITICAL_SECTION CritSect;
127} MSIHACKHANDLE;
128/** Pointer to an intercepted handle. */
129typedef MSIHACKHANDLE *PMSIHACKHANDLE;
130
131
132/**
133 * Replacement function entry.
134 */
135typedef struct MSIHACKREPLACEMENT
136{
137 /** The function name. */
138 const char *pszFunction;
139 /** The length of the function name. */
140 size_t cchFunction;
141 /** The module name (optional). */
142 const char *pszModule;
143 /** The replacement function or data address. */
144 uintptr_t pfnReplacement;
145} MSIHACKREPLACEMENT;
146/** Pointer to a replacement function entry */
147typedef MSIHACKREPLACEMENT const *PCMSIHACKREPLACEMENT;
148
149
150/*********************************************************************************************************************************
151* Global Variables *
152*********************************************************************************************************************************/
153/** Critical section protecting the handle table. */
154static CRITICAL_SECTION g_CritSect;
155/** Size of the handle table. */
156static size_t g_cHandles;
157/** The handle table. */
158static PMSIHACKHANDLE *g_papHandles;
159
160
161
162void MsiHackErrorF(const char *pszFormat, ...)
163{
164 fprintf(stderr, "MsiHack: error: ");
165 va_list va;
166 va_start(va, pszFormat);
167 vfprintf(stderr, pszFormat, va);
168 va_end(va);
169}
170
171
172void MsiHackDebugF(const char *pszFormat, ...)
173{
174 if (1)
175 {
176 fprintf(stderr, "MsiHack: debug: ");
177 va_list va;
178 va_start(va, pszFormat);
179 vfprintf(stderr, pszFormat, va);
180 va_end(va);
181 }
182}
183
184
185/**
186 * Destroys a handle.
187 */
188DECL_NO_INLINE(static, void) MsiHackHandleDestroy(PMSIHACKHANDLE pHandle)
189{
190 /* The handle value should always be invalid at this point! */
191 MSIHACK_ASSERT(pHandle->hHandle == INVALID_HANDLE_VALUE);
192
193 if (pHandle->fMemoryMapped)
194 UnmapViewOfFile(pHandle->pbBuffer);
195 else
196 free(pHandle->pbBuffer);
197 pHandle->pbBuffer = NULL;
198
199 DeleteCriticalSection(&pHandle->CritSect);
200 free(pHandle);
201}
202
203
204/**
205 * Releases a handle reference.
206 * @param pHandle The handle to release.
207 */
208DECLINLINE(void) MsiHackHandleRelease(PMSIHACKHANDLE pHandle)
209{
210 if (ASMAtomicDecS32(&pHandle->cRefs) != 0)
211 return;
212 MsiHackHandleDestroy(pHandle);
213}
214
215
216/**
217 * Get and retain handle.
218 *
219 * @returns Pointer to a reference handle or NULL if not our handle.
220 * @param hHandle The handle.
221 */
222DECLINLINE(PMSIHACKHANDLE) MsiHackHandleRetain(HANDLE hHandle)
223{
224 uintptr_t const idxHandle = MSI_HACK_HANDLE_TO_INDEX(hHandle);
225 EnterCriticalSection(&g_CritSect);
226 if (idxHandle < g_cHandles)
227 {
228 PMSIHACKHANDLE pHandle = g_papHandles[idxHandle];
229 if (pHandle)
230 {
231 ASMAtomicIncS32(&pHandle->cRefs);
232 LeaveCriticalSection(&g_CritSect);
233 return pHandle;
234 }
235 }
236 LeaveCriticalSection(&g_CritSect);
237 return NULL;
238}
239
240
241/**
242 * Enters @a pHandle into the handle table under @a hHandle.
243 *
244 * @returns true on succes, false on error.
245 * @param pHandle The handle to enter.
246 * @param hHandle The handle value to enter it under.
247 */
248static bool MsiHackHandleEnter(PMSIHACKHANDLE pHandle, HANDLE hHandle)
249{
250 uintptr_t const idxHandle = MSI_HACK_HANDLE_TO_INDEX(hHandle);
251 EnterCriticalSection(&g_CritSect);
252
253 /*
254 * Make sure there is room in the handle table.
255 */
256 bool fOkay = idxHandle < g_cHandles;
257 if (fOkay)
258 { /* typical */ }
259 else if (idxHandle < _1M)
260 {
261 size_t cNew = g_cHandles * 2;
262 while (cNew < idxHandle)
263 cNew *= 2;
264
265 void *pvNew = realloc(g_papHandles, cNew * sizeof(g_papHandles[0]));
266 if (pvNew)
267 {
268 g_papHandles = (PMSIHACKHANDLE *)pvNew;
269 memset(&g_papHandles[g_cHandles], 0, (cNew - g_cHandles) * sizeof(g_papHandles[0]));
270 g_cHandles = cNew;
271 fOkay = true;
272 }
273 else
274 MsiHackErrorF("Failed to grow handle table from %p to %p entries!\n", g_cHandles, cNew);
275 }
276 else
277 MsiHackErrorF("Handle %p (0x%p) is above the max handle table size limit!\n", hHandle, idxHandle);
278 if (fOkay)
279 {
280 /*
281 * Insert it into the table if the entry is empty.
282 */
283 if (g_papHandles[idxHandle] == NULL)
284 {
285 g_papHandles[idxHandle] = pHandle;
286 LeaveCriticalSection(&g_CritSect);
287 return true;
288 }
289 MsiHackErrorF("Handle table entry 0x%p (%p) is already busy with %p! Cannot replace with %p.\n",
290 hHandle, idxHandle, g_papHandles[idxHandle], pHandle);
291 }
292
293 LeaveCriticalSection(&g_CritSect);
294 return false;
295}
296
297
298/**
299 * Prepares a file for potential caching.
300 *
301 * If successful, the handled is entered into the handle table.
302 *
303 * @param hFile Handle to the file to cache.
304 */
305static void MsiHackFilePrepare(HANDLE hFile)
306{
307 DWORD const dwErrSaved = GetLastError();
308
309 LARGE_INTEGER cbFile;
310 if (GetFileSizeEx(hFile, &cbFile))
311 {
312 PMSIHACKHANDLE pHandle = (PMSIHACKHANDLE)calloc(1, sizeof(*pHandle));
313 if (pHandle)
314 {
315 pHandle->cbFile = cbFile.QuadPart;
316 pHandle->pbBuffer = NULL;
317 pHandle->cbBuffer = 0;
318 pHandle->cbBufferAlloc = 0;
319 pHandle->offFileBuffer = 0;
320 pHandle->fMemoryMapped = true;
321 pHandle->cRefs = 1;
322 InitializeCriticalSection(&pHandle->CritSect);
323 if (MsiHackHandleEnter(pHandle, hFile))
324 {
325 SetLastError(dwErrSaved);
326 return;
327 }
328
329 free(pHandle);
330 }
331 }
332
333 SetLastError(dwErrSaved);
334}
335
336
337/**
338 * Worker for MsiHackFileSetupCache
339 *
340 * @returns True if sucessfully cached, False if not.
341 * @param pHandle The file.
342 * @param hFile The current valid handle.
343 */
344static bool MsiHackFileSetupCache(PMSIHACKHANDLE pHandle, HANDLE hFile)
345{
346 DWORD const dwErrSaved = GetLastError();
347 HANDLE hMapping = CreateFileMappingW(hFile, NULL /*pSecAttrs*/, PAGE_READONLY,
348 0 /*cbMaxLow*/, 0 /*cbMaxHigh*/, NULL /*pwszName*/);
349 if (hMapping != NULL)
350 {
351 pHandle->pbBuffer = (uint8_t *)MapViewOfFile(hMapping, FILE_MAP_READ, 0 /*offFileHigh*/, 0 /*offFileLow*/,
352 (SIZE_T)pHandle->cbFile);
353 if (pHandle->pbBuffer)
354 {
355 pHandle->cbBuffer = (size_t)pHandle->cbFile;
356 pHandle->cbBufferAlloc = (size_t)pHandle->cbFile;
357 pHandle->offFileBuffer = 0;
358 pHandle->fMemoryMapped = true;
359 CloseHandle(hMapping);
360
361 SetLastError(dwErrSaved);
362 return true;
363 }
364 CloseHandle(hMapping);
365 }
366 SetLastError(dwErrSaved);
367 pHandle->fDontTryAgain = true;
368 return false;
369}
370
371
372/**
373 * This is called to check if the file is cached and try cache it.
374 *
375 * We delay the actually caching till the file is read, so we don't waste time
376 * mapping it into memory when all that is wanted is the file size or something
377 * like that.
378 *
379 * @returns True if cached, False if not.
380 * @param pHandle The file.
381 * @param hFile The current valid handle.
382 */
383DECLINLINE(bool) MsiHackFileIsCached(PMSIHACKHANDLE pHandle, HANDLE hFile)
384{
385 if (pHandle->pbBuffer)
386 return true;
387 if (!pHandle->fDontTryAgain)
388 return MsiHackFileSetupCache(pHandle, hFile);
389 return false;
390}
391
392
393/** Kernel32 - CreateFileA */
394static HANDLE WINAPI MsiHack_Kernel32_CreateFileA(LPCSTR pszFilename, DWORD dwDesiredAccess, DWORD dwShareMode,
395 LPSECURITY_ATTRIBUTES pSecAttrs, DWORD dwCreationDisposition,
396 DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
397{
398 /*
399 * If this is read-only access to the file, try cache it.
400 */
401 if (dwCreationDisposition == OPEN_EXISTING)
402 {
403 if ( dwDesiredAccess == GENERIC_READ
404 || dwDesiredAccess == FILE_GENERIC_READ)
405 {
406 if (dwShareMode & FILE_SHARE_READ)
407 {
408 if ( !pSecAttrs
409 || ( pSecAttrs->nLength == sizeof(*pSecAttrs)
410 && pSecAttrs->lpSecurityDescriptor == NULL ) )
411 {
412 HANDLE hFile = CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, pSecAttrs,
413 dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
414 if (hFile != INVALID_HANDLE_VALUE && hFile != NULL)
415 {
416 MsiHackDebugF("CreateFileA: %s\n", pszFilename );
417 MsiHackFilePrepare(hFile);
418 }
419 return hFile;
420 }
421 }
422 }
423 }
424
425 /*
426 * Don't intercept it.
427 */
428 return CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, pSecAttrs,
429 dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
430}
431
432
433/** Kernel32 - CreateFileW */
434static HANDLE WINAPI MsiHack_Kernel32_CreateFileW(LPCWSTR pwszFilename, DWORD dwDesiredAccess, DWORD dwShareMode,
435 LPSECURITY_ATTRIBUTES pSecAttrs, DWORD dwCreationDisposition,
436 DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
437{
438 /*
439 * If this is read-only access to the file, try cache it.
440 */
441 if (dwCreationDisposition == OPEN_EXISTING)
442 {
443 if ( dwDesiredAccess == GENERIC_READ
444 || dwDesiredAccess == FILE_GENERIC_READ)
445 {
446 if (dwShareMode & FILE_SHARE_READ)
447 {
448 if ( !pSecAttrs
449 || ( pSecAttrs->nLength == sizeof(*pSecAttrs)
450 && pSecAttrs->lpSecurityDescriptor == NULL ) )
451 {
452 HANDLE hFile = CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, pSecAttrs,
453 dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
454 if (hFile != INVALID_HANDLE_VALUE && hFile != NULL)
455 {
456 MsiHackDebugF("CreateFileW: %ls\n", pwszFilename);
457 MsiHackFilePrepare(hFile);
458 }
459 return hFile;
460 }
461 }
462 }
463 }
464
465 /*
466 * Don't intercept it.
467 */
468 return CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, pSecAttrs,
469 dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
470}
471
472
473/** Kernel32 - SetFilePointer */
474static DWORD WINAPI MsiHack_Kernel32_SetFilePointer(HANDLE hFile, LONG cbMove, PLONG pcbMoveHi, DWORD dwMoveMethod)
475{
476 /*
477 * If intercepted handle, deal with it.
478 */
479 PMSIHACKHANDLE pHandle = MsiHackHandleRetain(hFile);
480 if (pHandle)
481 {
482 if (MsiHackFileIsCached(pHandle, hFile))
483 {
484 int64_t offMove = pcbMoveHi ? ((int64_t)*pcbMoveHi << 32) | cbMove : cbMove;
485
486 EnterCriticalSection(&pHandle->CritSect);
487 switch (dwMoveMethod)
488 {
489 case FILE_BEGIN:
490 break;
491 case FILE_CURRENT:
492 offMove += pHandle->offFile;
493 break;
494 case FILE_END:
495 offMove += pHandle->cbFile;
496 break;
497 default:
498 LeaveCriticalSection(&pHandle->CritSect);
499 MsiHackHandleRelease(pHandle);
500
501 MsiHackErrorF("SetFilePointer(%p) - invalid method!\n", hFile);
502 SetLastError(ERROR_INVALID_PARAMETER);
503 return INVALID_SET_FILE_POINTER;
504 }
505
506 if (offMove >= 0)
507 {
508 /* Seeking beyond the end isn't useful, so just clamp it. */
509 if (offMove >= (int64_t)pHandle->cbFile)
510 offMove = (int64_t)pHandle->cbFile;
511 pHandle->offFile = (uint64_t)offMove;
512 }
513 else
514 {
515 LeaveCriticalSection(&pHandle->CritSect);
516 MsiHackHandleRelease(pHandle);
517
518 MsiHackErrorF("SetFilePointer(%p) - negative seek!\n", hFile);
519 SetLastError(ERROR_NEGATIVE_SEEK);
520 return INVALID_SET_FILE_POINTER;
521 }
522
523 LeaveCriticalSection(&pHandle->CritSect);
524 MsiHackHandleRelease(pHandle);
525
526 if (pcbMoveHi)
527 *pcbMoveHi = (uint64_t)offMove >> 32;
528 SetLastError(NO_ERROR);
529 return (uint32_t)offMove;
530 }
531 MsiHackHandleRelease(pHandle);
532 }
533
534 /*
535 * Not one of ours.
536 */
537 return SetFilePointer(hFile, cbMove, pcbMoveHi, dwMoveMethod);
538}
539
540
541/** Kernel32 - SetFilePointerEx */
542static BOOL WINAPI MsiHack_Kernel32_SetFilePointerEx(HANDLE hFile, LARGE_INTEGER offMove, PLARGE_INTEGER poffNew,
543 DWORD dwMoveMethod)
544{
545 /*
546 * If intercepted handle, deal with it.
547 */
548 PMSIHACKHANDLE pHandle = MsiHackHandleRetain(hFile);
549 if (pHandle)
550 {
551 if (MsiHackFileIsCached(pHandle, hFile))
552 {
553 int64_t offMyMove = offMove.QuadPart;
554
555 EnterCriticalSection(&pHandle->CritSect);
556 switch (dwMoveMethod)
557 {
558 case FILE_BEGIN:
559 break;
560 case FILE_CURRENT:
561 offMyMove += pHandle->offFile;
562 break;
563 case FILE_END:
564 offMyMove += pHandle->cbFile;
565 break;
566 default:
567 LeaveCriticalSection(&pHandle->CritSect);
568 MsiHackHandleRelease(pHandle);
569
570 MsiHackErrorF("SetFilePointerEx(%p) - invalid method!\n", hFile);
571 SetLastError(ERROR_INVALID_PARAMETER);
572 return INVALID_SET_FILE_POINTER;
573 }
574 if (offMyMove >= 0)
575 {
576 /* Seeking beyond the end isn't useful, so just clamp it. */
577 if (offMyMove >= (int64_t)pHandle->cbFile)
578 offMyMove = (int64_t)pHandle->cbFile;
579 pHandle->offFile = (uint64_t)offMyMove;
580 }
581 else
582 {
583 LeaveCriticalSection(&pHandle->CritSect);
584 MsiHackHandleRelease(pHandle);
585
586 MsiHackErrorF("SetFilePointerEx(%p) - negative seek!\n", hFile);
587 SetLastError(ERROR_NEGATIVE_SEEK);
588 return INVALID_SET_FILE_POINTER;
589 }
590
591 LeaveCriticalSection(&pHandle->CritSect);
592 MsiHackHandleRelease(pHandle);
593
594 if (poffNew)
595 poffNew->QuadPart = offMyMove;
596 return TRUE;
597 }
598 MsiHackHandleRelease(pHandle);
599 }
600
601 /*
602 * Not one of ours.
603 */
604 return SetFilePointerEx(hFile, offMove, poffNew, dwMoveMethod);
605}
606
607
608/** Kernel32 - ReadFile */
609static BOOL WINAPI MsiHack_Kernel32_ReadFile(HANDLE hFile, LPVOID pvBuffer, DWORD cbToRead, LPDWORD pcbActuallyRead,
610 LPOVERLAPPED pOverlapped)
611{
612 /*
613 * If intercepted handle, deal with it.
614 */
615 PMSIHACKHANDLE pHandle = MsiHackHandleRetain(hFile);
616 if (pHandle)
617 {
618 if (MsiHackFileIsCached(pHandle, hFile))
619 {
620 EnterCriticalSection(&pHandle->CritSect);
621 uint32_t cbActually = pHandle->cbFile - pHandle->offFile;
622 if (cbActually > cbToRead)
623 cbActually = cbToRead;
624
625 memcpy(pvBuffer, &pHandle->pbBuffer[pHandle->offFile], cbActually);
626 pHandle->offFile += cbActually;
627
628 LeaveCriticalSection(&pHandle->CritSect);
629 MsiHackHandleRelease(pHandle);
630
631 MSIHACK_ASSERT(!pOverlapped); MSIHACK_ASSERT(pcbActuallyRead);
632 *pcbActuallyRead = cbActually;
633
634 return TRUE;
635 }
636 MsiHackHandleRelease(pHandle);
637 }
638
639 /*
640 * Not one of ours.
641 */
642 return ReadFile(hFile, pvBuffer, cbToRead, pcbActuallyRead, pOverlapped);
643}
644
645
646/** Kernel32 - ReadFileEx */
647static BOOL WINAPI MsiHack_Kernel32_ReadFileEx(HANDLE hFile, LPVOID pvBuffer, DWORD cbToRead, LPOVERLAPPED pOverlapped,
648 LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine)
649{
650 /*
651 * If intercepted handle, deal with it.
652 */
653 PMSIHACKHANDLE pHandle = MsiHackHandleRetain(hFile);
654 if (pHandle)
655 {
656 MsiHackHandleRelease(pHandle);
657
658 MsiHackErrorF("Unexpected ReadFileEx call!\n");
659 SetLastError(ERROR_INVALID_FUNCTION);
660 return FALSE;
661 }
662
663 /*
664 * Not one of ours.
665 */
666 return ReadFileEx(hFile, pvBuffer, cbToRead, pOverlapped, pfnCompletionRoutine);
667}
668
669
670/** Kernel32 - DuplicateHandle */
671static BOOL WINAPI MsiHack_Kernel32_DuplicateHandle(HANDLE hSrcProc, HANDLE hSrc, HANDLE hDstProc, PHANDLE phNew,
672 DWORD dwDesiredAccess, BOOL fInheritHandle, DWORD dwOptions)
673{
674 /*
675 * We must catch our handles being duplicated.
676 */
677 if ( hSrcProc == GetCurrentProcess()
678 && hDstProc == hSrcProc)
679 {
680 PMSIHACKHANDLE pSrcHandle = MsiHackHandleRetain(hSrcProc);
681 if (pSrcHandle)
682 {
683 if (dwOptions & DUPLICATE_CLOSE_SOURCE)
684 MsiHackErrorF("DUPLICATE_CLOSE_SOURCE is not implemented!\n");
685 BOOL fRet = DuplicateHandle(hSrcProc, hSrc, hDstProc, phNew, dwDesiredAccess, fInheritHandle, dwOptions);
686 if (fRet)
687 {
688 if (MsiHackHandleEnter(pSrcHandle, *phNew))
689 return fRet; /* don't release reference. */
690
691 CloseHandle(*phNew);
692 *phNew = INVALID_HANDLE_VALUE;
693 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
694 fRet = FALSE;
695 }
696 MsiHackHandleRelease(pSrcHandle);
697 return fRet;
698 }
699 }
700
701 /*
702 * Not one of ours.
703 */
704 return DuplicateHandle(hSrcProc, hSrc, hDstProc, phNew, dwDesiredAccess, fInheritHandle, dwOptions);
705}
706
707
708/** Kernel32 - CloseHandle */
709static BOOL WINAPI MsiHack_Kernel32_CloseHandle(HANDLE hObject)
710{
711 /*
712 * If intercepted handle, remove it from the table.
713 */
714 uintptr_t const idxHandle = MSI_HACK_HANDLE_TO_INDEX(hObject);
715 EnterCriticalSection(&g_CritSect);
716 if (idxHandle < g_cHandles)
717 {
718 PMSIHACKHANDLE pHandle = g_papHandles[idxHandle];
719 if (pHandle)
720 {
721 g_papHandles[idxHandle] = NULL;
722 LeaveCriticalSection(&g_CritSect);
723
724 /*
725 * Then close the handle.
726 */
727 EnterCriticalSection(&pHandle->CritSect);
728 BOOL fRet = CloseHandle(hObject);
729 pHandle->hHandle = INVALID_HANDLE_VALUE;
730 DWORD dwErr = GetLastError();
731 LeaveCriticalSection(&pHandle->CritSect);
732
733 /*
734 * And finally release the reference held by the handle table.
735 */
736 MsiHackHandleRelease(pHandle);
737 SetLastError(dwErr);
738 return fRet;
739 }
740 }
741
742 /*
743 * Not one of ours.
744 */
745 LeaveCriticalSection(&g_CritSect);
746 return CloseHandle(hObject);
747}
748
749
750
751
752/** Replacement functions. */
753static const MSIHACKREPLACEMENT g_aReplaceFunctions[] =
754{
755 { RT_STR_TUPLE("CreateFileA"), NULL, (uintptr_t)MsiHack_Kernel32_CreateFileA },
756 { RT_STR_TUPLE("CreateFileW"), NULL, (uintptr_t)MsiHack_Kernel32_CreateFileW },
757 { RT_STR_TUPLE("ReadFile"), NULL, (uintptr_t)MsiHack_Kernel32_ReadFile },
758 { RT_STR_TUPLE("ReadFileEx"), NULL, (uintptr_t)MsiHack_Kernel32_ReadFileEx },
759 { RT_STR_TUPLE("SetFilePointer"), NULL, (uintptr_t)MsiHack_Kernel32_SetFilePointer },
760 { RT_STR_TUPLE("SetFilePointerEx"), NULL, (uintptr_t)MsiHack_Kernel32_SetFilePointerEx },
761 { RT_STR_TUPLE("DuplicateHandle"), NULL, (uintptr_t)MsiHack_Kernel32_DuplicateHandle },
762 { RT_STR_TUPLE("CloseHandle"), NULL, (uintptr_t)MsiHack_Kernel32_CloseHandle },
763};
764
765
766
767/**
768 * Patches the import table of the given DLL.
769 *
770 * @returns true on success, false on failure.
771 * @param hmod .
772 */
773__declspec(dllexport) /* kBuild workaround */
774bool MsiHackPatchDll(HMODULE hmod)
775{
776 uint8_t const * const pbImage = (uint8_t const *)hmod;
777
778 /*
779 * Locate the import descriptors.
780 */
781 /* MZ header and PE headers. */
782 IMAGE_NT_HEADERS const *pNtHdrs;
783 IMAGE_DOS_HEADER const *pMzHdr = (IMAGE_DOS_HEADER const *)pbImage;
784 if (pMzHdr->e_magic == IMAGE_DOS_SIGNATURE)
785 pNtHdrs = (IMAGE_NT_HEADERS const *)&pbImage[pMzHdr->e_lfanew];
786 else
787 pNtHdrs = (IMAGE_NT_HEADERS const *)pbImage;
788
789 /* Check PE header. */
790 MSIHACK_ASSERT_RETURN(pNtHdrs->Signature == IMAGE_NT_SIGNATURE, false);
791 MSIHACK_ASSERT_RETURN(pNtHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pNtHdrs->OptionalHeader), false);
792 uint32_t const cbImage = pNtHdrs->OptionalHeader.SizeOfImage;
793
794 /* Locate the import descriptor array. */
795 IMAGE_DATA_DIRECTORY const *pDirEnt;
796 pDirEnt = (IMAGE_DATA_DIRECTORY const *)&pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
797 if ( pDirEnt->Size > 0
798 && pDirEnt->VirtualAddress != 0)
799 {
800 const IMAGE_IMPORT_DESCRIPTOR *pImpDesc = (const IMAGE_IMPORT_DESCRIPTOR *)&pbImage[pDirEnt->VirtualAddress];
801 uint32_t cLeft = pDirEnt->Size / sizeof(*pImpDesc);
802 MEMORY_BASIC_INFORMATION ProtInfo = { NULL, NULL, 0, 0, 0, 0, 0 };
803 uint8_t *pbProtRange = NULL;
804 SIZE_T cbProtRange = 0;
805 DWORD fOldProt = 0;
806 uint32_t const cbPage = 0x1000;
807 BOOL fRc;
808
809 MSIHACK_ASSERT_RETURN(pDirEnt->VirtualAddress < cbImage, false);
810 MSIHACK_ASSERT_RETURN(pDirEnt->Size < cbImage, false);
811 MSIHACK_ASSERT_RETURN(pDirEnt->VirtualAddress + pDirEnt->Size <= cbImage, false);
812
813 /*
814 * Walk the import descriptor array.
815 * Note! This only works if there's a backup thunk array, otherwise we cannot get at the name.
816 */
817 while ( cLeft-- > 0
818 && pImpDesc->Name > 0
819 && pImpDesc->FirstThunk > 0)
820 {
821 uint32_t iThunk;
822 const char * const pszImport = (const char *)&pbImage[pImpDesc->Name];
823 PIMAGE_THUNK_DATA paThunks = (PIMAGE_THUNK_DATA)&pbImage[pImpDesc->FirstThunk];
824 PIMAGE_THUNK_DATA paOrgThunks = (PIMAGE_THUNK_DATA)&pbImage[pImpDesc->OriginalFirstThunk];
825 MSIHACK_ASSERT_RETURN(pImpDesc->Name < cbImage, false);
826 MSIHACK_ASSERT_RETURN(pImpDesc->FirstThunk < cbImage, false);
827 MSIHACK_ASSERT_RETURN(pImpDesc->OriginalFirstThunk < cbImage, false);
828 MSIHACK_ASSERT_RETURN(pImpDesc->OriginalFirstThunk != pImpDesc->FirstThunk, false);
829 MSIHACK_ASSERT_RETURN(pImpDesc->OriginalFirstThunk, false);
830
831 /* Iterate the thunks. */
832 for (iThunk = 0; paOrgThunks[iThunk].u1.Ordinal != 0; iThunk++)
833 {
834 uintptr_t const off = paOrgThunks[iThunk].u1.Function;
835 MSIHACK_ASSERT_RETURN(off < cbImage, false);
836 if (!IMAGE_SNAP_BY_ORDINAL(off))
837 {
838 IMAGE_IMPORT_BY_NAME const *pName = (IMAGE_IMPORT_BY_NAME const *)&pbImage[off];
839 size_t const cchSymbol = strlen((const char *)&pName->Name[0]);
840 uint32_t i = RT_ELEMENTS(g_aReplaceFunctions);
841 while (i-- > 0)
842 if ( g_aReplaceFunctions[i].cchFunction == cchSymbol
843 && memcmp(g_aReplaceFunctions[i].pszFunction, pName->Name, cchSymbol) == 0)
844 {
845 if ( !g_aReplaceFunctions[i].pszModule
846 || stricmp(g_aReplaceFunctions[i].pszModule, pszImport) == 0)
847 {
848 MsiHackDebugF("Replacing %s!%s\n", pszImport, pName->Name);
849
850 /* The .rdata section is normally read-only, so we need to make it writable first. */
851 if ((uintptr_t)&paThunks[iThunk] - (uintptr_t)pbProtRange >= cbPage)
852 {
853 /* Restore previous .rdata page. */
854 if (fOldProt)
855 {
856 fRc = VirtualProtect(pbProtRange, cbProtRange, fOldProt, NULL /*pfOldProt*/);
857 MSIHACK_ASSERT(fRc);
858 fOldProt = 0;
859 }
860
861 /* Query attributes for the current .rdata page. */
862 pbProtRange = (uint8_t *)((uintptr_t)&paThunks[iThunk] & ~(uintptr_t)(cbPage - 1));
863 cbProtRange = VirtualQuery(pbProtRange, &ProtInfo, sizeof(ProtInfo));
864 MSIHACK_ASSERT(cbProtRange);
865 if (cbProtRange)
866 {
867 switch (ProtInfo.Protect)
868 {
869 case PAGE_READWRITE:
870 case PAGE_WRITECOPY:
871 case PAGE_EXECUTE_READWRITE:
872 case PAGE_EXECUTE_WRITECOPY:
873 /* Already writable, nothing to do. */
874 fRc = TRUE;
875 break;
876
877 default:
878 MSIHACK_ASSERT_MSG(false, ("%#x\n", ProtInfo.Protect));
879 case PAGE_READONLY:
880 cbProtRange = cbPage;
881 fRc = VirtualProtect(pbProtRange, cbProtRange, PAGE_READWRITE, &fOldProt);
882 break;
883
884 case PAGE_EXECUTE:
885 case PAGE_EXECUTE_READ:
886 cbProtRange = cbPage;
887 fRc = VirtualProtect(pbProtRange, cbProtRange, PAGE_EXECUTE_READWRITE, &fOldProt);
888 break;
889 }
890 MSIHACK_ASSERT_STMT(fRc, fOldProt = 0);
891 }
892 }
893
894 paThunks[iThunk].u1.AddressOfData = g_aReplaceFunctions[i].pfnReplacement;
895 break;
896 }
897 }
898 }
899 }
900
901
902 /* Next import descriptor. */
903 pImpDesc++;
904 }
905
906
907 if (fOldProt)
908 {
909 DWORD fIgnore = 0;
910 fRc = VirtualProtect(pbProtRange, cbProtRange, fOldProt, &fIgnore);
911 MSIHACK_ASSERT_MSG(fRc, ("%u\n", GetLastError())); NOREF(fRc);
912 }
913 return true;
914 }
915 MsiHackErrorF("No imports in target DLL!\n");
916 return false;
917}
918
919
920/**
921 * The Dll main entry point.
922 */
923BOOL __stdcall DllMain(HANDLE hModule, DWORD dwReason, PVOID pvReserved)
924{
925 RT_NOREF_PV(pvReserved);
926
927 switch (dwReason)
928 {
929 case DLL_PROCESS_ATTACH:
930 {
931 /*
932 * Make sure we cannot be unloaded. This saves us the bother of ever
933 * having to unpatch MSI.DLL
934 */
935 WCHAR wszName[MAX_PATH*2];
936 SetLastError(NO_ERROR);
937 if ( GetModuleFileNameW((HMODULE)hModule, wszName, RT_ELEMENTS(wszName)) > 0
938 && GetLastError() == NO_ERROR)
939 {
940 int cExtraLoads = 32;
941 while (cExtraLoads-- > 0)
942 LoadLibraryW(wszName);
943 }
944
945 /*
946 * Initialize globals.
947 */
948 InitializeCriticalSection(&g_CritSect);
949 g_papHandles = (PMSIHACKHANDLE *)calloc(sizeof(g_papHandles[0]), 8192);
950 if (g_papHandles)
951 g_cHandles = 8192;
952 else
953 {
954 MsiHackErrorF("Failed to allocate handle table!\n");
955 return FALSE;
956 }
957
958 /*
959 * Find MSI and patch it.
960 */
961 static struct
962 {
963 const wchar_t *pwszName; /**< Dll name. */
964 bool fSystem; /**< Set if system, clear if wix. */
965 } s_aDlls[] =
966 {
967 { L"MSI.DLL", true },
968 { L"CABINET.DLL", true },
969 { L"WINTEROP.DLL", false },
970 };
971
972 for (unsigned i = 0; i < RT_ELEMENTS(s_aDlls); i++)
973 {
974 HMODULE hmodTarget = GetModuleHandleW(s_aDlls[i].pwszName);
975 if (!hmodTarget)
976 {
977 UINT cwc;
978 if (s_aDlls[i].fSystem)
979 cwc = GetSystemDirectoryW(wszName, RT_ELEMENTS(wszName) - 16);
980 else
981 {
982 cwc = GetModuleFileNameW(GetModuleHandleW(NULL), wszName, RT_ELEMENTS(wszName) - 16);
983 while (cwc > 0 && (wszName[cwc - 1] != '\\' && wszName[cwc - 1] != '/'))
984 wszName[--cwc] = '\0';
985 }
986 wszName[cwc++] = '\\';
987 wcscpy(&wszName[cwc], s_aDlls[i].pwszName);
988 hmodTarget = LoadLibraryW(wszName);
989 if (!hmodTarget)
990 {
991 MsiHackErrorF("%ls could not be found nor loaded (%ls): %u\n", &wszName[cwc], wszName, GetLastError());
992 return FALSE;
993 }
994 }
995
996 if (MsiHackPatchDll(hmodTarget))
997 MsiHackDebugF("MsiHackPatchDll returned successfully for %ls.\n", s_aDlls[i].pwszName);
998 else
999 MsiHackErrorF("MsiHackPatchDll failed for %ls!\n", s_aDlls[i].pwszName);
1000 }
1001 break;
1002 }
1003
1004 case DLL_PROCESS_DETACH:
1005 case DLL_THREAD_ATTACH:
1006 case DLL_THREAD_DETACH:
1007 default:
1008 /* ignore */
1009 break;
1010 }
1011 return TRUE;
1012}
1013
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette