VirtualBox

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

Last change on this file since 107349 was 106061, checked in by vboxsync, 4 months ago

Copyright year updates by scm.

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