/* * Process Hacker - * general support functions * * Copyright (C) 2009-2016 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include #include #include #include #include #include #include #include #define CINTERFACE #define COBJMACROS #include #undef CINTERFACE #undef COBJMACROS #include "md5.h" #include "sha.h" // We may want to change this for debugging purposes. #define PHP_USE_IFILEDIALOG (WINDOWS_HAS_IFILEDIALOG) typedef BOOLEAN (NTAPI *_WinStationQueryInformationW)( _In_opt_ HANDLE ServerHandle, _In_ ULONG LogonId, _In_ WINSTATIONINFOCLASS WinStationInformationClass, _Out_writes_bytes_(WinStationInformationLength) PVOID WinStationInformation, _In_ ULONG WinStationInformationLength, _Out_ PULONG ReturnLength ); typedef BOOL (WINAPI *_CreateEnvironmentBlock)( _Out_ LPVOID *lpEnvironment, _In_opt_ HANDLE hToken, _In_ BOOL bInherit ); typedef BOOL (WINAPI *_DestroyEnvironmentBlock)( _In_ LPVOID lpEnvironment ); DECLSPEC_SELECTANY WCHAR *PhSizeUnitNames[7] = { L"B", L"kB", L"MB", L"GB", L"TB", L"PB", L"EB" }; DECLSPEC_SELECTANY ULONG PhMaxSizeUnit = MAXULONG32; /** * Ensures a rectangle is positioned within the specified bounds. * * \param Rectangle The rectangle to be adjusted. * \param Bounds The bounds. * * \remarks If the rectangle is too large to fit inside the bounds, it is positioned at the top-left * of the bounds. */ VOID PhAdjustRectangleToBounds( _Inout_ PPH_RECTANGLE Rectangle, _In_ PPH_RECTANGLE Bounds ) { if (Rectangle->Left + Rectangle->Width > Bounds->Left + Bounds->Width) Rectangle->Left = Bounds->Left + Bounds->Width - Rectangle->Width; if (Rectangle->Top + Rectangle->Height > Bounds->Top + Bounds->Height) Rectangle->Top = Bounds->Top + Bounds->Height - Rectangle->Height; if (Rectangle->Left < Bounds->Left) Rectangle->Left = Bounds->Left; if (Rectangle->Top < Bounds->Top) Rectangle->Top = Bounds->Top; } /** * Positions a rectangle in the center of the specified bounds. * * \param Rectangle The rectangle to be adjusted. * \param Bounds The bounds. */ VOID PhCenterRectangle( _Inout_ PPH_RECTANGLE Rectangle, _In_ PPH_RECTANGLE Bounds ) { Rectangle->Left = Bounds->Left + (Bounds->Width - Rectangle->Width) / 2; Rectangle->Top = Bounds->Top + (Bounds->Height - Rectangle->Height) / 2; } /** * Ensures a rectangle is positioned within the working area of the specified window's monitor. * * \param hWnd A handle to a window. If NULL, the monitor closest to \a Rectangle is used. * \param Rectangle The rectangle to be adjusted. */ VOID PhAdjustRectangleToWorkingArea( _In_opt_ HWND hWnd, _Inout_ PPH_RECTANGLE Rectangle ) { HMONITOR monitor; MONITORINFO monitorInfo = { sizeof(monitorInfo) }; if (hWnd) { monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); } else { RECT rect; rect = PhRectangleToRect(*Rectangle); monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST); } if (GetMonitorInfo(monitor, &monitorInfo)) { PH_RECTANGLE bounds; bounds = PhRectToRectangle(monitorInfo.rcWork); PhAdjustRectangleToBounds(Rectangle, &bounds); } } /** * Centers a window. * * \param WindowHandle The window to center. * \param ParentWindowHandle If specified, the window will be positioned at the center of this * window. Otherwise, the window will be positioned at the center of the monitor. */ VOID PhCenterWindow( _In_ HWND WindowHandle, _In_opt_ HWND ParentWindowHandle ) { if (ParentWindowHandle) { RECT rect, parentRect; PH_RECTANGLE rectangle, parentRectangle; GetWindowRect(WindowHandle, &rect); GetWindowRect(ParentWindowHandle, &parentRect); rectangle = PhRectToRectangle(rect); parentRectangle = PhRectToRectangle(parentRect); PhCenterRectangle(&rectangle, &parentRectangle); PhAdjustRectangleToWorkingArea(WindowHandle, &rectangle); MoveWindow(WindowHandle, rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height, FALSE); } else { MONITORINFO monitorInfo = { sizeof(monitorInfo) }; if (GetMonitorInfo( MonitorFromWindow(WindowHandle, MONITOR_DEFAULTTONEAREST), &monitorInfo )) { RECT rect; PH_RECTANGLE rectangle; PH_RECTANGLE bounds; GetWindowRect(WindowHandle, &rect); rectangle = PhRectToRectangle(rect); bounds = PhRectToRectangle(monitorInfo.rcWork); PhCenterRectangle(&rectangle, &bounds); MoveWindow(WindowHandle, rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height, FALSE); } } } /** * References an array of objects. * * \param Objects An array of objects. * \param NumberOfObjects The number of elements in \a Objects. */ VOID PhReferenceObjects( _In_reads_(NumberOfObjects) PVOID *Objects, _In_ ULONG NumberOfObjects ) { ULONG i; for (i = 0; i < NumberOfObjects; i++) PhReferenceObject(Objects[i]); } /** * Dereferences an array of objects. * * \param Objects An array of objects. * \param NumberOfObjects The number of elements in \a Objects. */ VOID PhDereferenceObjects( _In_reads_(NumberOfObjects) PVOID *Objects, _In_ ULONG NumberOfObjects ) { ULONG i; for (i = 0; i < NumberOfObjects; i++) PhDereferenceObject(Objects[i]); } /** * Gets a string stored in a DLL's message table. * * \param DllHandle The base address of the DLL. * \param MessageTableId The identifier of the message table. * \param MessageLanguageId The language ID of the message. * \param MessageId The identifier of the message. * * \return A pointer to a string containing the message. You must free the string using * PhDereferenceObject() when you no longer need it. */ PPH_STRING PhGetMessage( _In_ PVOID DllHandle, _In_ ULONG MessageTableId, _In_ ULONG MessageLanguageId, _In_ ULONG MessageId ) { NTSTATUS status; PMESSAGE_RESOURCE_ENTRY messageEntry; status = RtlFindMessage( DllHandle, MessageTableId, MessageLanguageId, MessageId, &messageEntry ); // Try using the system LANGID. if (!NT_SUCCESS(status)) { status = RtlFindMessage( DllHandle, MessageTableId, GetSystemDefaultLangID(), MessageId, &messageEntry ); } // Try using U.S. English. if (!NT_SUCCESS(status)) { status = RtlFindMessage( DllHandle, MessageTableId, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), MessageId, &messageEntry ); } if (!NT_SUCCESS(status)) return NULL; if (messageEntry->Flags & MESSAGE_RESOURCE_UNICODE) { return PhCreateStringEx((PWCHAR)messageEntry->Text, messageEntry->Length); } else { return PhConvertMultiByteToUtf16Ex((PCHAR)messageEntry->Text, messageEntry->Length); } } /** * Gets a message describing a NT status value. * * \param Status The NT status value. */ PPH_STRING PhGetNtMessage( _In_ NTSTATUS Status ) { PPH_STRING message; if (!NT_NTWIN32(Status)) message = PhGetMessage(GetModuleHandle(L"ntdll.dll"), 0xb, GetUserDefaultLangID(), (ULONG)Status); else message = PhGetWin32Message(WIN32_FROM_NTSTATUS(Status)); if (PhIsNullOrEmptyString(message)) return message; PhTrimToNullTerminatorString(message); // Remove any trailing newline. if (message->Length >= 2 * sizeof(WCHAR) && message->Buffer[message->Length / sizeof(WCHAR) - 2] == '\r' && message->Buffer[message->Length / sizeof(WCHAR) - 1] == '\n') { PhMoveReference(&message, PhCreateStringEx(message->Buffer, message->Length - 2 * sizeof(WCHAR))); } // Fix those messages which are formatted like: // {Asdf}\r\nAsdf asdf asdf... if (message->Buffer[0] == '{') { PH_STRINGREF titlePart; PH_STRINGREF remainingPart; if (PhSplitStringRefAtChar(&message->sr, '\n', &titlePart, &remainingPart)) PhMoveReference(&message, PhCreateString2(&remainingPart)); } return message; } /** * Gets a message describing a Win32 error code. * * \param Result The Win32 error code. */ PPH_STRING PhGetWin32Message( _In_ ULONG Result ) { PPH_STRING message; message = PhGetMessage(GetModuleHandle(L"kernel32.dll"), 0xb, GetUserDefaultLangID(), Result); if (message) PhTrimToNullTerminatorString(message); // Remove any trailing newline. if (message && message->Length >= 2 * sizeof(WCHAR) && message->Buffer[message->Length / sizeof(WCHAR) - 2] == '\r' && message->Buffer[message->Length / sizeof(WCHAR) - 1] == '\n') { PhMoveReference(&message, PhCreateStringEx(message->Buffer, message->Length - 2 * sizeof(WCHAR))); } return message; } /** * Displays a message box. * * \param hWnd The owner window of the message box. * \param Type The type of message box to display. * \param Format A format string. * * \return The user's response. */ INT PhShowMessage( _In_ HWND hWnd, _In_ ULONG Type, _In_ PWSTR Format, ... ) { va_list argptr; va_start(argptr, Format); return PhShowMessage_V(hWnd, Type, Format, argptr); } INT PhShowMessage_V( _In_ HWND hWnd, _In_ ULONG Type, _In_ PWSTR Format, _In_ va_list ArgPtr ) { INT result; PPH_STRING message; message = PhFormatString_V(Format, ArgPtr); if (!message) return -1; result = MessageBox(hWnd, message->Buffer, PhApplicationName, Type); PhDereferenceObject(message); return result; } PPH_STRING PhGetStatusMessage( _In_ NTSTATUS Status, _In_opt_ ULONG Win32Result ) { if (!Win32Result) { // In some cases we want the simple Win32 messages. if ( Status == STATUS_ACCESS_DENIED || Status == STATUS_ACCESS_VIOLATION ) { Win32Result = RtlNtStatusToDosError(Status); } // Process NTSTATUS values with the NT-Win32 facility. else if (NT_NTWIN32(Status)) { Win32Result = WIN32_FROM_NTSTATUS(Status); } } if (!Win32Result) return PhGetNtMessage(Status); else return PhGetWin32Message(Win32Result); } /** * Displays an error message for a NTSTATUS value or Win32 error code. * * \param hWnd The owner window of the message box. * \param Message A message describing the operation that failed. * \param Status A NTSTATUS value, or 0 if there is none. * \param Win32Result A Win32 error code, or 0 if there is none. */ VOID PhShowStatus( _In_ HWND hWnd, _In_opt_ PWSTR Message, _In_ NTSTATUS Status, _In_opt_ ULONG Win32Result ) { PPH_STRING statusMessage; statusMessage = PhGetStatusMessage(Status, Win32Result); if (!statusMessage) { if (Message) { PhShowError(hWnd, L"%s.", Message); } else { PhShowError(hWnd, L"Unable to perform the operation."); } return; } if (Message) { PhShowError(hWnd, L"%s: %s", Message, statusMessage->Buffer); } else { PhShowError(hWnd, L"%s", statusMessage->Buffer); } PhDereferenceObject(statusMessage); } /** * Displays an error message for a NTSTATUS value or Win32 error code, and allows the user to cancel * the current operation. * * \param hWnd The owner window of the message box. * \param Message A message describing the operation that failed. * \param Status A NTSTATUS value, or 0 if there is none. * \param Win32Result A Win32 error code, or 0 if there is none. * * \return TRUE if the user wishes to continue with the current operation, otherwise FALSE. */ BOOLEAN PhShowContinueStatus( _In_ HWND hWnd, _In_opt_ PWSTR Message, _In_ NTSTATUS Status, _In_opt_ ULONG Win32Result ) { PPH_STRING statusMessage; INT result; statusMessage = PhGetStatusMessage(Status, Win32Result); if (!statusMessage) { if (Message) { result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s.", Message); } else { result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"Unable to perform the operation."); } return result == IDOK; } if (Message) { result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s: %s", Message, statusMessage->Buffer); } else { result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s", statusMessage->Buffer); } PhDereferenceObject(statusMessage); return result == IDOK; } /** * Displays a confirmation message. * * \param hWnd The owner window of the message box. * \param Verb A verb describing the operation, e.g. "terminate". * \param Object The object of the operation, e.g. "the process". * \param Message A message describing the operation. * \param Warning TRUE to display the confirmation message as a warning, otherwise FALSE. * * \return TRUE if the user wishes to continue, otherwise FALSE. */ BOOLEAN PhShowConfirmMessage( _In_ HWND hWnd, _In_ PWSTR Verb, _In_ PWSTR Object, _In_opt_ PWSTR Message, _In_ BOOLEAN Warning ) { PPH_STRING verb; PPH_STRING verbCaps; PPH_STRING action; // Make sure the verb is all lowercase. verb = PhaLowerString(PhaCreateString(Verb)); // "terminate" -> "Terminate" verbCaps = PhaDuplicateString(verb); if (verbCaps->Length > 0) verbCaps->Buffer[0] = towupper(verbCaps->Buffer[0]); // "terminate", "the process" -> "terminate the process" action = PhaConcatStrings(3, verb->Buffer, L" ", Object); if (TaskDialogIndirect_Import()) { TASKDIALOGCONFIG config = { sizeof(config) }; TASKDIALOG_BUTTON buttons[2]; INT button; config.hwndParent = hWnd; config.hInstance = PhLibImageBase; config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | (IsWindowVisible(hWnd) ? TDF_POSITION_RELATIVE_TO_WINDOW : 0); config.pszWindowTitle = PhApplicationName; config.pszMainIcon = Warning ? TD_WARNING_ICON : NULL; config.pszMainInstruction = PhaConcatStrings(3, L"Do you want to ", action->Buffer, L"?")->Buffer; if (Message) config.pszContent = PhaConcatStrings2(Message, L" Are you sure you want to continue?")->Buffer; buttons[0].nButtonID = IDYES; buttons[0].pszButtonText = verbCaps->Buffer; buttons[1].nButtonID = IDNO; buttons[1].pszButtonText = L"Cancel"; config.cButtons = 2; config.pButtons = buttons; config.nDefaultButton = IDYES; if (TaskDialogIndirect_Import()( &config, &button, NULL, NULL ) == S_OK) { return button == IDYES; } else { return FALSE; } } else { return PhShowMessage( hWnd, MB_YESNO | MB_ICONWARNING, L"Are you sure you want to %s?", action->Buffer ) == IDYES; } } /** * Finds an integer in an array of string-integer pairs. * * \param KeyValuePairs The array. * \param SizeOfKeyValuePairs The size of the array, in bytes. * \param String The string to search for. * \param Integer A variable which receives the found integer. * * \return TRUE if the string was found, otherwise FALSE. * * \remarks The search is case-sensitive. */ BOOLEAN PhFindIntegerSiKeyValuePairs( _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, _In_ ULONG SizeOfKeyValuePairs, _In_ PWSTR String, _Out_ PULONG Integer ) { ULONG i; for (i = 0; i < SizeOfKeyValuePairs / sizeof(PH_KEY_VALUE_PAIR); i++) { if (PhEqualStringZ(KeyValuePairs[i].Key, String, TRUE)) { *Integer = PtrToUlong(KeyValuePairs[i].Value); return TRUE; } } return FALSE; } /** * Finds a string in an array of string-integer pairs. * * \param KeyValuePairs The array. * \param SizeOfKeyValuePairs The size of the array, in bytes. * \param Integer The integer to search for. * \param String A variable which receives the found string. * * \return TRUE if the integer was found, otherwise FALSE. */ BOOLEAN PhFindStringSiKeyValuePairs( _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, _In_ ULONG SizeOfKeyValuePairs, _In_ ULONG Integer, _Out_ PWSTR *String ) { ULONG i; for (i = 0; i < SizeOfKeyValuePairs / sizeof(PH_KEY_VALUE_PAIR); i++) { if (PtrToUlong(KeyValuePairs[i].Value) == Integer) { *String = (PWSTR)KeyValuePairs[i].Key; return TRUE; } } return FALSE; } /** * Creates a random (type 4) UUID. * * \param Guid The destination UUID. */ VOID PhGenerateGuid( _Out_ PGUID Guid ) { static ULONG seed = 0; // The top/sign bit is always unusable for RtlRandomEx (the result is always unsigned), so we'll // take the bottom 24 bits. We need 128 bits in total, so we'll call the function 6 times. ULONG random[6]; ULONG i; for (i = 0; i < 6; i++) random[i] = RtlRandomEx(&seed); // random[0] is usable *(PUSHORT)&Guid->Data1 = (USHORT)random[0]; // top byte from random[0] is usable *((PUSHORT)&Guid->Data1 + 1) = (USHORT)((random[0] >> 16) | (random[1] & 0xff)); // top 2 bytes from random[1] are usable Guid->Data2 = (SHORT)(random[1] >> 8); // random[2] is usable Guid->Data3 = (SHORT)random[2]; // top byte from random[2] is usable *(PUSHORT)&Guid->Data4[0] = (USHORT)((random[2] >> 16) | (random[3] & 0xff)); // top 2 bytes from random[3] are usable *(PUSHORT)&Guid->Data4[2] = (USHORT)(random[3] >> 8); // random[4] is usable *(PUSHORT)&Guid->Data4[4] = (USHORT)random[4]; // top byte from random[4] is usable *(PUSHORT)&Guid->Data4[6] = (USHORT)((random[4] >> 16) | (random[5] & 0xff)); ((PGUID_EX)Guid)->s2.Version = GUID_VERSION_RANDOM; ((PGUID_EX)Guid)->s2.Variant &= ~GUID_VARIANT_STANDARD_MASK; ((PGUID_EX)Guid)->s2.Variant |= GUID_VARIANT_STANDARD; } FORCEINLINE VOID PhpReverseGuid( _Inout_ PGUID Guid ) { Guid->Data1 = _byteswap_ulong(Guid->Data1); Guid->Data2 = _byteswap_ushort(Guid->Data2); Guid->Data3 = _byteswap_ushort(Guid->Data3); } /** * Creates a name-based (type 3 or 5) UUID. * * \param Guid The destination UUID. * \param Namespace The UUID of the namespace. * \param Name The input name. * \param NameLength The length of the input name, not including the null terminator if present. * \param Version The type of UUID. * \li \c GUID_VERSION_MD5 Creates a type 3, MD5-based UUID. * \li \c GUID_VERSION_SHA1 Creates a type 5, SHA1-based UUID. */ VOID PhGenerateGuidFromName( _Out_ PGUID Guid, _In_ PGUID Namespace, _In_ PCHAR Name, _In_ ULONG NameLength, _In_ UCHAR Version ) { PGUID_EX guid; PUCHAR data; ULONG dataLength; GUID ns; UCHAR hash[20]; // Convert the namespace to big endian. ns = *Namespace; PhpReverseGuid(&ns); // Compute the hash of the namespace concatenated with the name. dataLength = 16 + NameLength; data = PhAllocate(dataLength); memcpy(data, &ns, 16); memcpy(&data[16], Name, NameLength); if (Version == GUID_VERSION_MD5) { MD5_CTX context; MD5Init(&context); MD5Update(&context, data, dataLength); MD5Final(&context); memcpy(hash, context.digest, 16); } else { A_SHA_CTX context; A_SHAInit(&context); A_SHAUpdate(&context, data, dataLength); A_SHAFinal(&context, hash); Version = GUID_VERSION_SHA1; } PhFree(data); guid = (PGUID_EX)Guid; memcpy(guid->Data, hash, 16); PhpReverseGuid(&guid->Guid); guid->s2.Version = Version; guid->s2.Variant &= ~GUID_VARIANT_STANDARD_MASK; guid->s2.Variant |= GUID_VARIANT_STANDARD; } /** * Fills a buffer with random uppercase alphabetical characters. * * \param Buffer The buffer to fill with random characters, plus a null terminator. * \param Count The number of characters available in the buffer, including space for the null * terminator. */ VOID PhGenerateRandomAlphaString( _Out_writes_z_(Count) PWSTR Buffer, _In_ ULONG Count ) { static ULONG seed = 0; ULONG i; if (Count == 0) return; for (i = 0; i < Count - 1; i++) { Buffer[i] = 'A' + (RtlRandomEx(&seed) % 26); } Buffer[Count - 1] = 0; } /** * Modifies a string to ensure it is within the specified length. * * \param String The input string. * \param DesiredCount The desired number of characters in the new string. If necessary, parts of * the string are replaced with an ellipsis to indicate characters have been omitted. * * \return The new string. */ PPH_STRING PhEllipsisString( _In_ PPH_STRING String, _In_ ULONG DesiredCount ) { if ( (ULONG)String->Length / 2 <= DesiredCount || DesiredCount < 3 ) { return PhReferenceObject(String); } else { PPH_STRING string; string = PhCreateStringEx(NULL, DesiredCount * 2); memcpy(string->Buffer, String->Buffer, (DesiredCount - 3) * 2); memcpy(&string->Buffer[DesiredCount - 3], L"...", 6); return string; } } /** * Modifies a string to ensure it is within the specified length, parsing the string as a path. * * \param String The input string. * \param DesiredCount The desired number of characters in the new string. If necessary, parts of * the string are replaced with an ellipsis to indicate characters have been omitted. * * \return The new string. */ PPH_STRING PhEllipsisStringPath( _In_ PPH_STRING String, _In_ ULONG DesiredCount ) { ULONG_PTR secondPartIndex; secondPartIndex = PhFindLastCharInString(String, 0, L'\\'); if (secondPartIndex == -1) secondPartIndex = PhFindLastCharInString(String, 0, L'/'); if (secondPartIndex == -1) return PhEllipsisString(String, DesiredCount); if ( String->Length / 2 <= DesiredCount || DesiredCount < 3 ) { return PhReferenceObject(String); } else { PPH_STRING string; ULONG_PTR firstPartCopyLength; ULONG_PTR secondPartCopyLength; string = PhCreateStringEx(NULL, DesiredCount * 2); secondPartCopyLength = String->Length / 2 - secondPartIndex; // Check if we have enough space for the entire second part of the string. if (secondPartCopyLength + 3 <= DesiredCount) { // Yes, copy part of the first part and the entire second part. firstPartCopyLength = DesiredCount - secondPartCopyLength - 3; } else { // No, copy part of both, from the beginning of the first part and the end of the second // part. firstPartCopyLength = (DesiredCount - 3) / 2; secondPartCopyLength = DesiredCount - 3 - firstPartCopyLength; secondPartIndex = String->Length / 2 - secondPartCopyLength; } memcpy( string->Buffer, String->Buffer, firstPartCopyLength * 2 ); memcpy( &string->Buffer[firstPartCopyLength], L"...", 6 ); memcpy( &string->Buffer[firstPartCopyLength + 3], &String->Buffer[secondPartIndex], secondPartCopyLength * 2 ); return string; } } FORCEINLINE BOOLEAN PhpMatchWildcards( _In_ PWSTR Pattern, _In_ PWSTR String, _In_ BOOLEAN IgnoreCase ) { PWCHAR s, p; BOOLEAN star = FALSE; // Code is from http://xoomer.virgilio.it/acantato/dev/wildcard/wildmatch.html LoopStart: for (s = String, p = Pattern; *s; s++, p++) { switch (*p) { case '?': break; case '*': star = TRUE; String = s; Pattern = p; do { Pattern++; } while (*Pattern == '*'); if (!*Pattern) return TRUE; goto LoopStart; default: if (!IgnoreCase) { if (*s != *p) goto StarCheck; } else { if (towupper(*s) != towupper(*p)) goto StarCheck; } break; } } while (*p == '*') p++; return (!*p); StarCheck: if (!star) return FALSE; String++; goto LoopStart; } /** * Matches a pattern against a string. * * \param Pattern The pattern, which can contain asterisks and question marks. * \param String The string which the pattern is matched against. * \param IgnoreCase Whether to ignore character cases. */ BOOLEAN PhMatchWildcards( _In_ PWSTR Pattern, _In_ PWSTR String, _In_ BOOLEAN IgnoreCase ) { if (!IgnoreCase) return PhpMatchWildcards(Pattern, String, FALSE); else return PhpMatchWildcards(Pattern, String, TRUE); } /** * Escapes a string for prefix characters (ampersands). * * \param String The string to process. * * \return The escaped string, with each ampersand replaced by 2 ampersands. */ PPH_STRING PhEscapeStringForMenuPrefix( _In_ PPH_STRINGREF String ) { PH_STRING_BUILDER stringBuilder; SIZE_T i; SIZE_T length; PWCHAR runStart; SIZE_T runCount; length = String->Length / sizeof(WCHAR); runStart = NULL; PhInitializeStringBuilder(&stringBuilder, String->Length); for (i = 0; i < length; i++) { switch (String->Buffer[i]) { case '&': if (runStart) { PhAppendStringBuilderEx(&stringBuilder, runStart, runCount * sizeof(WCHAR)); runStart = NULL; } PhAppendStringBuilder2(&stringBuilder, L"&&"); break; default: if (runStart) { runCount++; } else { runStart = &String->Buffer[i]; runCount = 1; } break; } } if (runStart) PhAppendStringBuilderEx(&stringBuilder, runStart, runCount * sizeof(WCHAR)); return PhFinalStringBuilderString(&stringBuilder); } /** * Compares two strings, ignoring prefix characters (ampersands). * * \param A The first string. * \param B The second string. * \param IgnoreCase Whether to ignore character cases. * \param MatchIfPrefix Specify TRUE to return 0 when \a A is a prefix of \a B. */ LONG PhCompareUnicodeStringZIgnoreMenuPrefix( _In_ PWSTR A, _In_ PWSTR B, _In_ BOOLEAN IgnoreCase, _In_ BOOLEAN MatchIfPrefix ) { WCHAR t; if (!IgnoreCase) { while (TRUE) { // This takes care of double ampersands as well (they are treated as one literal // ampersand). if (*A == '&') A++; if (*B == '&') B++; t = *A; if (t == 0) { if (MatchIfPrefix) return 0; break; } if (t != *B) break; A++; B++; } return C_2uTo4(t) - C_2uTo4(*B); } else { while (TRUE) { if (*A == '&') A++; if (*B == '&') B++; t = *A; if (t == 0) { if (MatchIfPrefix) return 0; break; } if (towupper(t) != towupper(*B)) break; A++; B++; } return C_2uTo4(t) - C_2uTo4(*B); } } /** * Formats a date using the user's default locale. * * \param Date The time structure. If NULL, the current time is used. * \param Format The format of the date. If NULL, the format appropriate to the user's locale is * used. */ PPH_STRING PhFormatDate( _In_opt_ PSYSTEMTIME Date, _In_opt_ PWSTR Format ) { PPH_STRING string; ULONG bufferSize; bufferSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, Date, Format, NULL, 0); string = PhCreateStringEx(NULL, bufferSize * 2); if (!GetDateFormat(LOCALE_USER_DEFAULT, 0, Date, Format, string->Buffer, bufferSize)) { PhDereferenceObject(string); return NULL; } PhTrimToNullTerminatorString(string); return string; } /** * Formats a time using the user's default locale. * * \param Time The time structure. If NULL, the current time is used. * \param Format The format of the time. If NULL, the format appropriate to the user's locale is * used. */ PPH_STRING PhFormatTime( _In_opt_ PSYSTEMTIME Time, _In_opt_ PWSTR Format ) { PPH_STRING string; ULONG bufferSize; bufferSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, Time, Format, NULL, 0); string = PhCreateStringEx(NULL, bufferSize * 2); if (!GetTimeFormat(LOCALE_USER_DEFAULT, 0, Time, Format, string->Buffer, bufferSize)) { PhDereferenceObject(string); return NULL; } PhTrimToNullTerminatorString(string); return string; } /** * Formats a date and time using the user's default locale. * * \param DateTime The time structure. If NULL, the current time is used. * * \return A string containing the time, a space character, then the date. */ PPH_STRING PhFormatDateTime( _In_opt_ PSYSTEMTIME DateTime ) { PPH_STRING string; ULONG timeBufferSize; ULONG dateBufferSize; ULONG count; timeBufferSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, NULL, 0); dateBufferSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, NULL, 0); string = PhCreateStringEx(NULL, (timeBufferSize + 1 + dateBufferSize) * 2); if (!GetTimeFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, &string->Buffer[0], timeBufferSize)) { PhDereferenceObject(string); return NULL; } count = (ULONG)PhCountStringZ(string->Buffer); string->Buffer[count] = ' '; if (!GetDateFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, &string->Buffer[count + 1], dateBufferSize)) { PhDereferenceObject(string); return NULL; } PhTrimToNullTerminatorString(string); return string; } PPH_STRING PhFormatTimeSpan( _In_ ULONG64 Ticks, _In_opt_ ULONG Mode ) { PPH_STRING string; string = PhCreateStringEx(NULL, PH_TIMESPAN_STR_LEN); PhPrintTimeSpan(string->Buffer, Ticks, Mode); PhTrimToNullTerminatorString(string); return string; } /** * Formats a relative time span. * * \param TimeSpan The time span, in ticks. */ PPH_STRING PhFormatTimeSpanRelative( _In_ ULONG64 TimeSpan ) { PH_AUTO_POOL autoPool; PPH_STRING string; DOUBLE days; DOUBLE weeks; DOUBLE fortnights; DOUBLE months; DOUBLE years; DOUBLE centuries; PhInitializeAutoPool(&autoPool); days = (DOUBLE)TimeSpan / PH_TICKS_PER_DAY; weeks = days / 7; fortnights = weeks / 2; years = days / 365.2425; months = years * 12; centuries = years / 100; if (centuries >= 1) { string = PhaFormatString(L"%u %s", (ULONG)centuries, (ULONG)centuries == 1 ? L"century" : L"centuries"); } else if (years >= 1) { string = PhaFormatString(L"%u %s", (ULONG)years, (ULONG)years == 1 ? L"year" : L"years"); } else if (months >= 1) { string = PhaFormatString(L"%u %s", (ULONG)months, (ULONG)months == 1 ? L"month" : L"months"); } else if (fortnights >= 1) { string = PhaFormatString(L"%u %s", (ULONG)fortnights, (ULONG)fortnights == 1 ? L"fortnight" : L"fortnights"); } else if (weeks >= 1) { string = PhaFormatString(L"%u %s", (ULONG)weeks, (ULONG)weeks == 1 ? L"week" : L"weeks"); } else { DOUBLE milliseconds; DOUBLE seconds; DOUBLE minutes; DOUBLE hours; ULONG secondsPartial; ULONG minutesPartial; ULONG hoursPartial; milliseconds = (DOUBLE)TimeSpan / PH_TICKS_PER_MS; seconds = (DOUBLE)TimeSpan / PH_TICKS_PER_SEC; minutes = (DOUBLE)TimeSpan / PH_TICKS_PER_MIN; hours = (DOUBLE)TimeSpan / PH_TICKS_PER_HOUR; if (days >= 1) { string = PhaFormatString(L"%u %s", (ULONG)days, (ULONG)days == 1 ? L"day" : L"days"); hoursPartial = (ULONG)PH_TICKS_PARTIAL_HOURS(TimeSpan); if (hoursPartial >= 1) { string = PhaFormatString(L"%s and %u %s", string->Buffer, hoursPartial, hoursPartial == 1 ? L"hour" : L"hours"); } } else if (hours >= 1) { string = PhaFormatString(L"%u %s", (ULONG)hours, (ULONG)hours == 1 ? L"hour" : L"hours"); minutesPartial = (ULONG)PH_TICKS_PARTIAL_MIN(TimeSpan); if (minutesPartial >= 1) { string = PhaFormatString(L"%s and %u %s", string->Buffer, (ULONG)minutesPartial, (ULONG)minutesPartial == 1 ? L"minute" : L"minutes"); } } else if (minutes >= 1) { string = PhaFormatString(L"%u %s", (ULONG)minutes, (ULONG)minutes == 1 ? L"minute" : L"minutes"); secondsPartial = (ULONG)PH_TICKS_PARTIAL_SEC(TimeSpan); if (secondsPartial >= 1) { string = PhaFormatString(L"%s and %u %s", string->Buffer, (ULONG)secondsPartial, (ULONG)secondsPartial == 1 ? L"second" : L"seconds"); } } else if (seconds >= 1) { string = PhaFormatString(L"%u %s", (ULONG)seconds, (ULONG)seconds == 1 ? L"second" : L"seconds"); } else if (milliseconds >= 1) { string = PhaFormatString(L"%u %s", (ULONG)milliseconds, (ULONG)milliseconds == 1 ? L"millisecond" : L"milliseconds"); } else { string = PhaCreateString(L"a very short time"); } } // Turn 1 into "a", e.g. 1 minute -> a minute if (PhStartsWithString2(string, L"1 ", FALSE)) { // Special vowel case: a hour -> an hour if (string->Buffer[2] != 'h') string = PhaConcatStrings2(L"a ", &string->Buffer[2]); else string = PhaConcatStrings2(L"an ", &string->Buffer[2]); } PhReferenceObject(string); PhDeleteAutoPool(&autoPool); return string; } /** * Formats a 64-bit unsigned integer. * * \param Value The integer. * \param GroupDigits TRUE to group digits, otherwise FALSE. */ PPH_STRING PhFormatUInt64( _In_ ULONG64 Value, _In_ BOOLEAN GroupDigits ) { PH_FORMAT format; format.Type = UInt64FormatType | (GroupDigits ? FormatGroupDigits : 0); format.u.UInt64 = Value; return PhFormat(&format, 1, 0); } PPH_STRING PhFormatDecimal( _In_ PWSTR Value, _In_ ULONG FractionalDigits, _In_ BOOLEAN GroupDigits ) { static PH_INITONCE initOnce = PH_INITONCE_INIT; static WCHAR decimalSeparator[4]; static WCHAR thousandSeparator[4]; PPH_STRING string; NUMBERFMT format; ULONG bufferSize; if (PhBeginInitOnce(&initOnce)) { if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimalSeparator, 4)) { decimalSeparator[0] = '.'; decimalSeparator[1] = 0; } if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousandSeparator, 4)) { thousandSeparator[0] = ','; thousandSeparator[1] = 0; } PhEndInitOnce(&initOnce); } format.NumDigits = FractionalDigits; format.LeadingZero = 0; format.Grouping = GroupDigits ? 3 : 0; format.lpDecimalSep = decimalSeparator; format.lpThousandSep = thousandSeparator; format.NegativeOrder = 1; bufferSize = GetNumberFormat(LOCALE_USER_DEFAULT, 0, Value, &format, NULL, 0); string = PhCreateStringEx(NULL, bufferSize * 2); if (!GetNumberFormat(LOCALE_USER_DEFAULT, 0, Value, &format, string->Buffer, bufferSize)) { PhDereferenceObject(string); return NULL; } PhTrimToNullTerminatorString(string); return string; } /** * Gets a string representing a size. * * \param Size The size value. * \param MaxSizeUnit The largest unit of size to use, -1 to use PhMaxSizeUnit, or -2 for no limit. * \li \c 0 Bytes. * \li \c 1 Kilobytes. * \li \c 2 Megabytes. * \li \c 3 Gigabytes. * \li \c 4 Terabytes. * \li \c 5 Petabytes. * \li \c 6 Exabytes. */ PPH_STRING PhFormatSize( _In_ ULONG64 Size, _In_ ULONG MaxSizeUnit ) { PH_FORMAT format; // PhFormat handles this better than the old method. format.Type = SizeFormatType | FormatUseRadix; format.Radix = (UCHAR)(MaxSizeUnit != -1 ? MaxSizeUnit : PhMaxSizeUnit); format.u.Size = Size; return PhFormat(&format, 1, 0); } /** * Converts a UUID to its string representation. * * \param Guid A UUID. */ PPH_STRING PhFormatGuid( _In_ PGUID Guid ) { PPH_STRING string; UNICODE_STRING unicodeString; if (!NT_SUCCESS(RtlStringFromGUID(Guid, &unicodeString))) return NULL; string = PhCreateStringFromUnicodeString(&unicodeString); RtlFreeUnicodeString(&unicodeString); return string; } /** * Retrieves image version information for a file. * * \param FileName The file name. * * \return A version information block. You must free this using PhFree() when you no longer need * it. */ PVOID PhGetFileVersionInfo( _In_ PWSTR FileName ) { ULONG versionInfoSize; ULONG dummy; PVOID versionInfo; versionInfoSize = GetFileVersionInfoSize( FileName, &dummy ); if (versionInfoSize) { versionInfo = PhAllocate(versionInfoSize); if (!GetFileVersionInfo( FileName, 0, versionInfoSize, versionInfo )) { PhFree(versionInfo); return NULL; } } else { return NULL; } return versionInfo; } /** * Retrieves the language ID and code page used by a version information block. * * \param VersionInfo The version information block. */ ULONG PhGetFileVersionInfoLangCodePage( _In_ PVOID VersionInfo ) { PVOID buffer; ULONG length; if (VerQueryValue(VersionInfo, L"\\VarFileInfo\\Translation", &buffer, &length)) { // Combine the language ID and code page. return (*(PUSHORT)buffer << 16) + *((PUSHORT)buffer + 1); } else { return (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 1252; } } /** * Retrieves a string in a version information block. * * \param VersionInfo The version information block. * \param SubBlock The path to the sub-block. */ PPH_STRING PhGetFileVersionInfoString( _In_ PVOID VersionInfo, _In_ PWSTR SubBlock ) { PVOID buffer; ULONG length; if (VerQueryValue(VersionInfo, SubBlock, &buffer, &length)) { PPH_STRING string; string = PhCreateStringEx((PWCHAR)buffer, length * sizeof(WCHAR)); // length may include the null terminator. PhTrimToNullTerminatorString(string); return string; } else { return NULL; } } /** * Retrieves a string in a version information block. * * \param VersionInfo The version information block. * \param LangCodePage The language ID and code page of the string. * \param StringName The name of the string. */ PPH_STRING PhGetFileVersionInfoString2( _In_ PVOID VersionInfo, _In_ ULONG LangCodePage, _In_ PWSTR StringName ) { WCHAR subBlock[65]; PH_FORMAT format[4]; PhInitFormatS(&format[0], L"\\StringFileInfo\\"); PhInitFormatX(&format[1], LangCodePage); format[1].Type |= FormatPadZeros | FormatUpperCase; format[1].Width = 8; PhInitFormatC(&format[2], '\\'); PhInitFormatS(&format[3], StringName); if (PhFormatToBuffer(format, 4, subBlock, sizeof(subBlock), NULL)) return PhGetFileVersionInfoString(VersionInfo, subBlock); else return NULL; } VOID PhpGetImageVersionInfoFields( _Out_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, _In_ PVOID VersionInfo, _In_ ULONG LangCodePage ) { ImageVersionInfo->CompanyName = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"CompanyName"); ImageVersionInfo->FileDescription = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"FileDescription"); ImageVersionInfo->ProductName = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"ProductName"); } /** * Initializes a structure with version information. * * \param ImageVersionInfo The version information structure. * \param FileName The file name of an image. */ BOOLEAN PhInitializeImageVersionInfo( _Out_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, _In_ PWSTR FileName ) { PVOID versionInfo; ULONG langCodePage; VS_FIXEDFILEINFO *rootBlock; ULONG rootBlockLength; PH_FORMAT fileVersionFormat[7]; versionInfo = PhGetFileVersionInfo(FileName); if (!versionInfo) return FALSE; langCodePage = PhGetFileVersionInfoLangCodePage(versionInfo); PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, langCodePage); if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) { // Use the windows-1252 code page. PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (langCodePage & 0xffff0000) + 1252); // Use the default language (US English). if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) { PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 1252); if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 0); } } // The version information is language-independent and must be read from the root block. if (VerQueryValue(versionInfo, L"\\", &rootBlock, &rootBlockLength) && rootBlockLength != 0) { PhInitFormatU(&fileVersionFormat[0], rootBlock->dwFileVersionMS >> 16); PhInitFormatC(&fileVersionFormat[1], '.'); PhInitFormatU(&fileVersionFormat[2], rootBlock->dwFileVersionMS & 0xffff); PhInitFormatC(&fileVersionFormat[3], '.'); PhInitFormatU(&fileVersionFormat[4], rootBlock->dwFileVersionLS >> 16); PhInitFormatC(&fileVersionFormat[5], '.'); PhInitFormatU(&fileVersionFormat[6], rootBlock->dwFileVersionLS & 0xffff); ImageVersionInfo->FileVersion = PhFormat(fileVersionFormat, 7, 30); } else { ImageVersionInfo->FileVersion = NULL; } PhFree(versionInfo); return TRUE; } /** * Frees a version information structure initialized by PhInitializeImageVersionInfo(). * * \param ImageVersionInfo The version information structure. */ VOID PhDeleteImageVersionInfo( _Inout_ PPH_IMAGE_VERSION_INFO ImageVersionInfo ) { if (ImageVersionInfo->CompanyName) PhDereferenceObject(ImageVersionInfo->CompanyName); if (ImageVersionInfo->FileDescription) PhDereferenceObject(ImageVersionInfo->FileDescription); if (ImageVersionInfo->FileVersion) PhDereferenceObject(ImageVersionInfo->FileVersion); if (ImageVersionInfo->ProductName) PhDereferenceObject(ImageVersionInfo->ProductName); } PPH_STRING PhFormatImageVersionInfo( _In_opt_ PPH_STRING FileName, _In_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, _In_opt_ PPH_STRINGREF Indent, _In_opt_ ULONG LineLimit ) { PH_STRING_BUILDER stringBuilder; if (LineLimit == 0) LineLimit = MAXULONG32; PhInitializeStringBuilder(&stringBuilder, 40); // File name if (!PhIsNullOrEmptyString(FileName)) { PPH_STRING temp; if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); temp = PhEllipsisStringPath(FileName, LineLimit); PhAppendStringBuilder(&stringBuilder, &temp->sr); PhDereferenceObject(temp); PhAppendCharStringBuilder(&stringBuilder, '\n'); } // File description & version if (!( PhIsNullOrEmptyString(ImageVersionInfo->FileDescription) && PhIsNullOrEmptyString(ImageVersionInfo->FileVersion) )) { PPH_STRING tempDescription = NULL; PPH_STRING tempVersion = NULL; ULONG limitForDescription; ULONG limitForVersion; if (LineLimit != MAXULONG32) { limitForVersion = (LineLimit - 1) / 4; // 1/4 space for version (and space character) limitForDescription = LineLimit - limitForVersion; } else { limitForDescription = MAXULONG32; limitForVersion = MAXULONG32; } if (!PhIsNullOrEmptyString(ImageVersionInfo->FileDescription)) { tempDescription = PhEllipsisString( ImageVersionInfo->FileDescription, limitForDescription ); } if (!PhIsNullOrEmptyString(ImageVersionInfo->FileVersion)) { tempVersion = PhEllipsisString( ImageVersionInfo->FileVersion, limitForVersion ); } if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); if (tempDescription) { PhAppendStringBuilder(&stringBuilder, &tempDescription->sr); if (tempVersion) PhAppendCharStringBuilder(&stringBuilder, ' '); } if (tempVersion) PhAppendStringBuilder(&stringBuilder, &tempVersion->sr); if (tempDescription) PhDereferenceObject(tempDescription); if (tempVersion) PhDereferenceObject(tempVersion); PhAppendCharStringBuilder(&stringBuilder, '\n'); } // File company if (!PhIsNullOrEmptyString(ImageVersionInfo->CompanyName)) { PPH_STRING temp; if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); temp = PhEllipsisString(ImageVersionInfo->CompanyName, LineLimit); PhAppendStringBuilder(&stringBuilder, &temp->sr); PhDereferenceObject(temp); PhAppendCharStringBuilder(&stringBuilder, '\n'); } // Remove the extra newline. if (stringBuilder.String->Length != 0) PhRemoveEndStringBuilder(&stringBuilder, 1); return PhFinalStringBuilderString(&stringBuilder); } /** * Gets an absolute file name. * * \param FileName A file name. * \param IndexOfFileName A variable which receives the index of the base name. * * \return An absolute file name, or NULL if the function failed. */ PPH_STRING PhGetFullPath( _In_ PWSTR FileName, _Out_opt_ PULONG IndexOfFileName ) { PPH_STRING fullPath; ULONG bufferSize; ULONG returnLength; PWSTR filePart; bufferSize = 0x80; fullPath = PhCreateStringEx(NULL, bufferSize * 2); returnLength = RtlGetFullPathName_U(FileName, bufferSize, fullPath->Buffer, &filePart); if (returnLength > bufferSize) { PhDereferenceObject(fullPath); bufferSize = returnLength; fullPath = PhCreateStringEx(NULL, bufferSize * 2); returnLength = RtlGetFullPathName_U(FileName, bufferSize, fullPath->Buffer, &filePart); } if (returnLength == 0) { PhDereferenceObject(fullPath); return NULL; } PhTrimToNullTerminatorString(fullPath); if (IndexOfFileName) { if (filePart) { // The path points to a file. *IndexOfFileName = (ULONG)(filePart - fullPath->Buffer); } else { // The path points to a directory. *IndexOfFileName = -1; } } return fullPath; } /** * Expands environment variables in a string. * * \param String The string. */ PPH_STRING PhExpandEnvironmentStrings( _In_ PPH_STRINGREF String ) { NTSTATUS status; UNICODE_STRING inputString; UNICODE_STRING outputString; PPH_STRING string; ULONG bufferLength; if (!PhStringRefToUnicodeString(String, &inputString)) return NULL; bufferLength = 0x40; string = PhCreateStringEx(NULL, bufferLength); outputString.MaximumLength = (USHORT)bufferLength; outputString.Length = 0; outputString.Buffer = string->Buffer; status = RtlExpandEnvironmentStrings_U( NULL, &inputString, &outputString, &bufferLength ); if (status == STATUS_BUFFER_TOO_SMALL) { PhDereferenceObject(string); string = PhCreateStringEx(NULL, bufferLength); outputString.MaximumLength = (USHORT)bufferLength; outputString.Length = 0; outputString.Buffer = string->Buffer; status = RtlExpandEnvironmentStrings_U( NULL, &inputString, &outputString, &bufferLength ); } if (!NT_SUCCESS(status)) { PhDereferenceObject(string); return NULL; } string->Length = outputString.Length; string->Buffer[string->Length / 2] = 0; // make sure there is a null terminator return string; } /** * Gets the base name from a file name. * * \param FileName The file name. */ PPH_STRING PhGetBaseName( _In_ PPH_STRING FileName ) { PH_STRINGREF pathPart; PH_STRINGREF baseNamePart; if (!PhSplitStringRefAtLastChar(&FileName->sr, '\\', &pathPart, &baseNamePart)) return PhReferenceObject(FileName); return PhCreateString2(&baseNamePart); } /** * Retrieves the system directory path. */ PPH_STRING PhGetSystemDirectory( VOID ) { static PPH_STRING cachedSystemDirectory = NULL; PPH_STRING systemDirectory; ULONG bufferSize; ULONG returnLength; // Use the cached value if possible. systemDirectory = cachedSystemDirectory; if (systemDirectory) return PhReferenceObject(systemDirectory); bufferSize = 0x40; systemDirectory = PhCreateStringEx(NULL, bufferSize * 2); returnLength = GetSystemDirectory(systemDirectory->Buffer, bufferSize); if (returnLength > bufferSize) { PhDereferenceObject(systemDirectory); bufferSize = returnLength; systemDirectory = PhCreateStringEx(NULL, bufferSize * 2); returnLength = GetSystemDirectory(systemDirectory->Buffer, bufferSize); } if (returnLength == 0) { PhDereferenceObject(systemDirectory); return NULL; } PhTrimToNullTerminatorString(systemDirectory); // Try to cache the value. if (_InterlockedCompareExchangePointer( &cachedSystemDirectory, systemDirectory, NULL ) == NULL) { // Success, add one more reference for the cache. PhReferenceObject(systemDirectory); } return systemDirectory; } /** * Retrieves the Windows directory path. */ VOID PhGetSystemRoot( _Out_ PPH_STRINGREF SystemRoot ) { static PH_STRINGREF systemRoot; PH_STRINGREF localSystemRoot; SIZE_T count; if (systemRoot.Buffer) { *SystemRoot = systemRoot; return; } localSystemRoot.Buffer = USER_SHARED_DATA->NtSystemRoot; count = PhCountStringZ(localSystemRoot.Buffer); localSystemRoot.Length = count * sizeof(WCHAR); // Make sure the system root string doesn't have a trailing backslash. if (localSystemRoot.Buffer[count - 1] == '\\') localSystemRoot.Length -= sizeof(WCHAR); *SystemRoot = localSystemRoot; systemRoot.Length = localSystemRoot.Length; MemoryBarrier(); systemRoot.Buffer = localSystemRoot.Buffer; } /** * Locates a loader entry in the current process. * * \param DllBase The base address of the DLL. Specify NULL if this is not a search criteria. * \param FullDllName The full name of the DLL. Specify NULL if this is not a search criteria. * \param BaseDllName The base name of the DLL. Specify NULL if this is not a search criteria. * * \remarks This function must be called with the loader lock acquired. The first entry matching all * of the specified values is returned. */ PLDR_DATA_TABLE_ENTRY PhFindLoaderEntry( _In_opt_ PVOID DllBase, _In_opt_ PPH_STRINGREF FullDllName, _In_opt_ PPH_STRINGREF BaseDllName ) { PLDR_DATA_TABLE_ENTRY result = NULL; PLDR_DATA_TABLE_ENTRY entry; PH_STRINGREF fullDllName; PH_STRINGREF baseDllName; PLIST_ENTRY listHead; PLIST_ENTRY listEntry; listHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList; listEntry = listHead->Flink; while (listEntry != listHead) { entry = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); PhUnicodeStringToStringRef(&entry->FullDllName, &fullDllName); PhUnicodeStringToStringRef(&entry->BaseDllName, &baseDllName); if ( (!DllBase || entry->DllBase == DllBase) && (!FullDllName || PhEqualStringRef(&fullDllName, FullDllName, TRUE)) && (!BaseDllName || PhEqualStringRef(&baseDllName, BaseDllName, TRUE)) ) { result = entry; break; } listEntry = listEntry->Flink; } return result; } /** * Retrieves the file name of a DLL loaded by the current process. * * \param DllHandle The base address of the DLL. * \param IndexOfFileName A variable which receives the index of the base name of the DLL in the * returned string. * * \return The file name of the DLL, or NULL if the DLL could not be found. */ PPH_STRING PhGetDllFileName( _In_ PVOID DllHandle, _Out_opt_ PULONG IndexOfFileName ) { PLDR_DATA_TABLE_ENTRY entry; PPH_STRING fileName; PPH_STRING newFileName; ULONG_PTR indexOfFileName; RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock); entry = PhFindLoaderEntry(DllHandle, NULL, NULL); if (entry) fileName = PhCreateStringFromUnicodeString(&entry->FullDllName); else fileName = NULL; RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock); if (!fileName) return NULL; newFileName = PhGetFileName(fileName); PhDereferenceObject(fileName); fileName = newFileName; if (IndexOfFileName) { indexOfFileName = PhFindLastCharInString(fileName, 0, '\\'); if (indexOfFileName != -1) indexOfFileName++; else indexOfFileName = 0; *IndexOfFileName = (ULONG)indexOfFileName; } return fileName; } /** * Retrieves the file name of the current process image. */ PPH_STRING PhGetApplicationFileName( VOID ) { return PhGetDllFileName(NtCurrentPeb()->ImageBaseAddress, NULL); } /** * Retrieves the directory of the current process image. */ PPH_STRING PhGetApplicationDirectory( VOID ) { PPH_STRING fileName; ULONG indexOfFileName; PPH_STRING path = NULL; fileName = PhGetDllFileName(NtCurrentPeb()->ImageBaseAddress, &indexOfFileName); if (fileName) { if (indexOfFileName != 0) { // Remove the file name from the path. path = PhSubstring(fileName, 0, indexOfFileName); } PhDereferenceObject(fileName); } return path; } /** * Gets a known location as a file name. * * \param Folder A CSIDL value representing the known location. * \param AppendPath A string to append to the folder path. */ PPH_STRING PhGetKnownLocation( _In_ ULONG Folder, _In_opt_ PWSTR AppendPath ) { PPH_STRING path; SIZE_T appendPathLength; if (AppendPath) appendPathLength = PhCountStringZ(AppendPath) * 2; else appendPathLength = 0; path = PhCreateStringEx(NULL, MAX_PATH * 2 + appendPathLength); if (SUCCEEDED(SHGetFolderPath( NULL, Folder, NULL, SHGFP_TYPE_CURRENT, path->Buffer ))) { PhTrimToNullTerminatorString(path); if (AppendPath) { memcpy(&path->Buffer[path->Length / 2], AppendPath, appendPathLength + 2); // +2 for null terminator path->Length += appendPathLength; } return path; } PhDereferenceObject(path); return NULL; } /** * Waits on multiple objects while processing window messages. * * \param hWnd The window to process messages for, or NULL to process all messages for the current * thread. * \param NumberOfHandles The number of handles specified in \a Handles. This must not be greater * than MAXIMUM_WAIT_OBJECTS - 1. * \param Handles An array of handles. * \param Timeout The number of milliseconds to wait on the objects, or INFINITE for no timeout. * * \remarks The wait is always in WaitAny mode. */ NTSTATUS PhWaitForMultipleObjectsAndPump( _In_opt_ HWND hWnd, _In_ ULONG NumberOfHandles, _In_ PHANDLE Handles, _In_ ULONG Timeout ) { NTSTATUS status; ULONG startTickCount; ULONG currentTickCount; LONG currentTimeout; startTickCount = GetTickCount(); currentTimeout = Timeout; while (TRUE) { status = MsgWaitForMultipleObjects( NumberOfHandles, Handles, FALSE, (ULONG)currentTimeout, QS_ALLEVENTS ); if (status >= STATUS_WAIT_0 && status < (NTSTATUS)(STATUS_WAIT_0 + NumberOfHandles)) { return status; } else if (status == (STATUS_WAIT_0 + NumberOfHandles)) { MSG msg; // Pump messages while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } else { return status; } // Recompute the timeout value. if (Timeout != INFINITE) { currentTickCount = GetTickCount(); currentTimeout = Timeout - (currentTickCount - startTickCount); if (currentTimeout < 0) return STATUS_TIMEOUT; } } } /** * Creates a native process and an initial thread. * * \param FileName The Win32 file name of the image. * \param CommandLine The command line string to pass to the process. This string cannot be used to * specify the image to execute. * \param Environment The environment block for the process. Specify NULL to use the environment of * the current process. * \param CurrentDirectory The current directory string to pass to the process. * \param Information Additional parameters to pass to the process. * \param Flags A combination of the following: * \li \c PH_CREATE_PROCESS_INHERIT_HANDLES Inheritable handles will be duplicated to the process * from the parent process. * \li \c PH_CREATE_PROCESS_SUSPENDED The initial thread will be created suspended. * \li \c PH_CREATE_PROCESS_BREAKAWAY_FROM_JOB The process will not be assigned to the job object * associated with the parent process. * \li \c PH_CREATE_PROCESS_NEW_CONSOLE The process will have its own console, instead of inheriting * the console of the parent process. * \param ParentProcessHandle The process from which the new process will inherit attributes. * Specify NULL for the current process. * \param ClientId A variable which recieves the identifier of the initial thread. * \param ProcessHandle A variable which receives a handle to the process. * \param ThreadHandle A variable which receives a handle to the initial thread. */ NTSTATUS PhCreateProcess( _In_ PWSTR FileName, _In_opt_ PPH_STRINGREF CommandLine, _In_opt_ PVOID Environment, _In_opt_ PPH_STRINGREF CurrentDirectory, _In_opt_ PPH_CREATE_PROCESS_INFO Information, _In_ ULONG Flags, _In_opt_ HANDLE ParentProcessHandle, _Out_opt_ PCLIENT_ID ClientId, _Out_opt_ PHANDLE ProcessHandle, _Out_opt_ PHANDLE ThreadHandle ) { NTSTATUS status; RTL_USER_PROCESS_INFORMATION processInfo; PRTL_USER_PROCESS_PARAMETERS parameters; UNICODE_STRING fileName; UNICODE_STRING commandLine; UNICODE_STRING currentDirectory; PUNICODE_STRING windowTitle; PUNICODE_STRING desktopInfo; if (!RtlDosPathNameToNtPathName_U( FileName, &fileName, NULL, NULL )) return STATUS_OBJECT_NAME_NOT_FOUND; if (CommandLine) { if (!PhStringRefToUnicodeString(CommandLine, &commandLine)) return STATUS_NAME_TOO_LONG; } if (CurrentDirectory) { if (!PhStringRefToUnicodeString(CurrentDirectory, ¤tDirectory)) return STATUS_NAME_TOO_LONG; } if (Information) { windowTitle = Information->WindowTitle; desktopInfo = Information->DesktopInfo; } else { windowTitle = NULL; desktopInfo = NULL; } if (!windowTitle) windowTitle = &fileName; if (!desktopInfo) desktopInfo = &NtCurrentPeb()->ProcessParameters->DesktopInfo; status = RtlCreateProcessParameters( ¶meters, &fileName, Information ? Information->DllPath : NULL, CurrentDirectory ? ¤tDirectory : NULL, CommandLine ? &commandLine : &fileName, Environment, windowTitle, desktopInfo, Information ? Information->ShellInfo : NULL, Information ? Information->RuntimeData : NULL ); if (NT_SUCCESS(status)) { status = RtlCreateUserProcess( &fileName, OBJ_CASE_INSENSITIVE, parameters, NULL, NULL, ParentProcessHandle, !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), NULL, NULL, &processInfo ); RtlDestroyProcessParameters(parameters); } RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); if (NT_SUCCESS(status)) { if (!(Flags & PH_CREATE_PROCESS_SUSPENDED)) NtResumeThread(processInfo.Thread, NULL); if (ClientId) *ClientId = processInfo.ClientId; if (ProcessHandle) *ProcessHandle = processInfo.Process; else NtClose(processInfo.Process); if (ThreadHandle) *ThreadHandle = processInfo.Thread; else NtClose(processInfo.Thread); } return status; } /** * Creates a Win32 process and an initial thread. * * \param FileName The Win32 file name of the image. * \param CommandLine The command line to execute. This can be specified instead of \a FileName to * indicate the image to execute. * \param Environment The environment block for the process. Specify NULL to use the environment of * the current process. * \param CurrentDirectory The current directory string to pass to the process. * \param Flags See PhCreateProcess(). * \param TokenHandle The token of the process. Specify NULL for the token of the parent process. * \param ProcessHandle A variable which receives a handle to the process. * \param ThreadHandle A variable which receives a handle to the initial thread. */ NTSTATUS PhCreateProcessWin32( _In_opt_ PWSTR FileName, _In_opt_ PWSTR CommandLine, _In_opt_ PVOID Environment, _In_opt_ PWSTR CurrentDirectory, _In_ ULONG Flags, _In_opt_ HANDLE TokenHandle, _Out_opt_ PHANDLE ProcessHandle, _Out_opt_ PHANDLE ThreadHandle ) { return PhCreateProcessWin32Ex( FileName, CommandLine, Environment, CurrentDirectory, NULL, Flags, TokenHandle, NULL, ProcessHandle, ThreadHandle ); } static const PH_FLAG_MAPPING PhpCreateProcessMappings[] = { { PH_CREATE_PROCESS_UNICODE_ENVIRONMENT, CREATE_UNICODE_ENVIRONMENT }, { PH_CREATE_PROCESS_SUSPENDED, CREATE_SUSPENDED }, { PH_CREATE_PROCESS_BREAKAWAY_FROM_JOB, CREATE_BREAKAWAY_FROM_JOB }, { PH_CREATE_PROCESS_NEW_CONSOLE, CREATE_NEW_CONSOLE } }; FORCEINLINE VOID PhpConvertProcessInformation( _In_ PPROCESS_INFORMATION ProcessInfo, _Out_opt_ PCLIENT_ID ClientId, _Out_opt_ PHANDLE ProcessHandle, _Out_opt_ PHANDLE ThreadHandle ) { if (ClientId) { ClientId->UniqueProcess = UlongToHandle(ProcessInfo->dwProcessId); ClientId->UniqueThread = UlongToHandle(ProcessInfo->dwThreadId); } if (ProcessHandle) *ProcessHandle = ProcessInfo->hProcess; else NtClose(ProcessInfo->hProcess); if (ThreadHandle) *ThreadHandle = ProcessInfo->hThread; else NtClose(ProcessInfo->hThread); } /** * Creates a Win32 process and an initial thread. * * \param FileName The Win32 file name of the image. * \param CommandLine The command line to execute. This can be specified instead of \a FileName to * indicate the image to execute. * \param Environment The environment block for the process. Specify NULL to use the environment of * the current process. * \param CurrentDirectory The current directory string to pass to the process. * \param StartupInfo A STARTUPINFO structure containing additional parameters for the process. * \param Flags See PhCreateProcess(). * \param TokenHandle The token of the process. Specify NULL for the token of the parent process. * \param ClientId A variable which recieves the identifier of the initial thread. * \param ProcessHandle A variable which receives a handle to the process. * \param ThreadHandle A variable which receives a handle to the initial thread. */ NTSTATUS PhCreateProcessWin32Ex( _In_opt_ PWSTR FileName, _In_opt_ PWSTR CommandLine, _In_opt_ PVOID Environment, _In_opt_ PWSTR CurrentDirectory, _In_opt_ STARTUPINFO *StartupInfo, _In_ ULONG Flags, _In_opt_ HANDLE TokenHandle, _Out_opt_ PCLIENT_ID ClientId, _Out_opt_ PHANDLE ProcessHandle, _Out_opt_ PHANDLE ThreadHandle ) { NTSTATUS status; PPH_STRING commandLine = NULL; STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; ULONG newFlags; if (CommandLine) // duplicate because CreateProcess modifies the string commandLine = PhCreateString(CommandLine); newFlags = 0; PhMapFlags1(&newFlags, Flags, PhpCreateProcessMappings, sizeof(PhpCreateProcessMappings) / sizeof(PH_FLAG_MAPPING)); if (StartupInfo) { startupInfo = *StartupInfo; } else { memset(&startupInfo, 0, sizeof(STARTUPINFO)); startupInfo.cb = sizeof(STARTUPINFO); } if (!TokenHandle) { if (CreateProcess( FileName, PhGetString(commandLine), NULL, NULL, !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), newFlags, Environment, CurrentDirectory, &startupInfo, &processInfo )) status = STATUS_SUCCESS; else status = PhGetLastWin32ErrorAsNtStatus(); } else { if (CreateProcessAsUser( TokenHandle, FileName, PhGetString(commandLine), NULL, NULL, !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), newFlags, Environment, CurrentDirectory, &startupInfo, &processInfo )) status = STATUS_SUCCESS; else status = PhGetLastWin32ErrorAsNtStatus(); } if (commandLine) PhDereferenceObject(commandLine); if (NT_SUCCESS(status)) { PhpConvertProcessInformation(&processInfo, ClientId, ProcessHandle, ThreadHandle); } return status; } /** * Creates a Win32 process and an initial thread under the specified user. * * \param Information Parameters specifying how to create the process. * \param Flags See PhCreateProcess(). Additional flags may be used: * \li \c PH_CREATE_PROCESS_USE_PROCESS_TOKEN Use the token of the process specified by * \a ProcessIdWithToken in \a Information. * \li \c PH_CREATE_PROCESS_USE_SESSION_TOKEN Use the token of the session specified by * \a SessionIdWithToken in \a Information. * \li \c PH_CREATE_PROCESS_USE_LINKED_TOKEN Use the linked token to create the process; this causes * the process to be elevated or unelevated depending on the specified options. * \li \c PH_CREATE_PROCESS_SET_SESSION_ID \a SessionId is specified in \a Information. * \li \c PH_CREATE_PROCESS_WITH_PROFILE Load the user profile, if supported. * \param ClientId A variable which recieves the identifier of the initial thread. * \param ProcessHandle A variable which receives a handle to the process. * \param ThreadHandle A variable which receives a handle to the initial thread. */ NTSTATUS PhCreateProcessAsUser( _In_ PPH_CREATE_PROCESS_AS_USER_INFO Information, _In_ ULONG Flags, _Out_opt_ PCLIENT_ID ClientId, _Out_opt_ PHANDLE ProcessHandle, _Out_opt_ PHANDLE ThreadHandle ) { static PH_INITONCE initOnce = PH_INITONCE_INIT; static _WinStationQueryInformationW WinStationQueryInformationW_I = NULL; static _CreateEnvironmentBlock CreateEnvironmentBlock_I = NULL; static _DestroyEnvironmentBlock DestroyEnvironmentBlock_I = NULL; NTSTATUS status; HANDLE tokenHandle; PVOID defaultEnvironment = NULL; STARTUPINFO startupInfo = { sizeof(startupInfo) }; BOOLEAN needsDuplicate = FALSE; if (PhBeginInitOnce(&initOnce)) { HMODULE winsta; HMODULE userEnv; winsta = LoadLibrary(L"winsta.dll"); WinStationQueryInformationW_I = (_WinStationQueryInformationW)GetProcAddress(winsta, "WinStationQueryInformationW"); userEnv = LoadLibrary(L"userenv.dll"); CreateEnvironmentBlock_I = (_CreateEnvironmentBlock)GetProcAddress(userEnv, "CreateEnvironmentBlock"); DestroyEnvironmentBlock_I = (_DestroyEnvironmentBlock)GetProcAddress(userEnv, "DestroyEnvironmentBlock"); PhEndInitOnce(&initOnce); } if ((Flags & PH_CREATE_PROCESS_USE_PROCESS_TOKEN) && (Flags & PH_CREATE_PROCESS_USE_SESSION_TOKEN)) return STATUS_INVALID_PARAMETER_2; if (!Information->ApplicationName && !Information->CommandLine) return STATUS_INVALID_PARAMETER_MIX; startupInfo.lpDesktop = Information->DesktopName; // Try to use CreateProcessWithLogonW if we need to load the user profile. // This isn't compatible with some options. if (Flags & PH_CREATE_PROCESS_WITH_PROFILE) { BOOLEAN useWithLogon; useWithLogon = TRUE; if (Flags & (PH_CREATE_PROCESS_USE_PROCESS_TOKEN | PH_CREATE_PROCESS_USE_SESSION_TOKEN)) useWithLogon = FALSE; if (Flags & PH_CREATE_PROCESS_USE_LINKED_TOKEN) useWithLogon = FALSE; if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) { if (Information->SessionId != NtCurrentPeb()->SessionId) useWithLogon = FALSE; } if (Information->LogonType && Information->LogonType != LOGON32_LOGON_INTERACTIVE) useWithLogon = FALSE; if (useWithLogon) { PPH_STRING commandLine; PROCESS_INFORMATION processInfo; ULONG newFlags; if (Information->CommandLine) // duplicate because CreateProcess modifies the string commandLine = PhCreateString(Information->CommandLine); else commandLine = NULL; if (!Information->Environment) Flags |= PH_CREATE_PROCESS_UNICODE_ENVIRONMENT; newFlags = 0; PhMapFlags1(&newFlags, Flags, PhpCreateProcessMappings, sizeof(PhpCreateProcessMappings) / sizeof(PH_FLAG_MAPPING)); if (CreateProcessWithLogonW( Information->UserName, Information->DomainName, Information->Password, LOGON_WITH_PROFILE, Information->ApplicationName, PhGetString(commandLine), newFlags, Information->Environment, Information->CurrentDirectory, &startupInfo, &processInfo )) status = STATUS_SUCCESS; else status = PhGetLastWin32ErrorAsNtStatus(); if (commandLine) PhDereferenceObject(commandLine); if (NT_SUCCESS(status)) { PhpConvertProcessInformation(&processInfo, ClientId, ProcessHandle, ThreadHandle); } return status; } } // Get the token handle. Various methods are supported. if (Flags & PH_CREATE_PROCESS_USE_PROCESS_TOKEN) { HANDLE processHandle; if (!NT_SUCCESS(status = PhOpenProcess( &processHandle, ProcessQueryAccess, Information->ProcessIdWithToken ))) return status; status = PhOpenProcessToken( processHandle, TOKEN_ALL_ACCESS, &tokenHandle ); NtClose(processHandle); if (!NT_SUCCESS(status)) return status; if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) needsDuplicate = TRUE; // can't set the session ID of a token in use by a process } else if (Flags & PH_CREATE_PROCESS_USE_SESSION_TOKEN) { WINSTATIONUSERTOKEN userToken; ULONG returnLength; if (!WinStationQueryInformationW_I) return STATUS_PROCEDURE_NOT_FOUND; if (!WinStationQueryInformationW_I( NULL, Information->SessionIdWithToken, WinStationUserToken, &userToken, sizeof(WINSTATIONUSERTOKEN), &returnLength )) { return PhGetLastWin32ErrorAsNtStatus(); } tokenHandle = userToken.UserToken; if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) needsDuplicate = TRUE; // not sure if this is necessary } else { ULONG logonType; if (Information->LogonType) { logonType = Information->LogonType; } else { logonType = LOGON32_LOGON_INTERACTIVE; // Check if this is a service logon. if (PhEqualStringZ(Information->DomainName, L"NT AUTHORITY", TRUE)) { if (PhEqualStringZ(Information->UserName, L"SYSTEM", TRUE)) { if (WindowsVersion >= WINDOWS_VISTA) logonType = LOGON32_LOGON_SERVICE; else logonType = LOGON32_LOGON_NEW_CREDENTIALS; // HACK } if (PhEqualStringZ(Information->UserName, L"LOCAL SERVICE", TRUE) || PhEqualStringZ(Information->UserName, L"NETWORK SERVICE", TRUE)) { logonType = LOGON32_LOGON_SERVICE; } } } if (!LogonUser( Information->UserName, Information->DomainName, Information->Password, logonType, LOGON32_PROVIDER_DEFAULT, &tokenHandle )) return PhGetLastWin32ErrorAsNtStatus(); } if (Flags & PH_CREATE_PROCESS_USE_LINKED_TOKEN) { HANDLE linkedTokenHandle; TOKEN_TYPE tokenType; ULONG returnLength; // NtQueryInformationToken normally returns an impersonation token with // SecurityIdentification, but if the process is running with SeTcbPrivilege, it returns a // primary token. We can never duplicate a SecurityIdentification impersonation token to // make it a primary token, so we just check if the token is primary before using it. if (NT_SUCCESS(PhGetTokenLinkedToken(tokenHandle, &linkedTokenHandle))) { if (NT_SUCCESS(NtQueryInformationToken( linkedTokenHandle, TokenType, &tokenType, sizeof(TOKEN_TYPE), &returnLength )) && tokenType == TokenPrimary) { NtClose(tokenHandle); tokenHandle = linkedTokenHandle; needsDuplicate = FALSE; // the linked token that is returned is always a copy, so no need to duplicate } else { NtClose(linkedTokenHandle); } } } if (needsDuplicate) { HANDLE newTokenHandle; OBJECT_ATTRIBUTES objectAttributes; InitializeObjectAttributes( &objectAttributes, NULL, 0, NULL, NULL ); status = NtDuplicateToken( tokenHandle, TOKEN_ALL_ACCESS, &objectAttributes, FALSE, TokenPrimary, &newTokenHandle ); NtClose(tokenHandle); if (!NT_SUCCESS(status)) return status; tokenHandle = newTokenHandle; } // Set the session ID if needed. if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) { if (!NT_SUCCESS(status = PhSetTokenSessionId( tokenHandle, Information->SessionId ))) { NtClose(tokenHandle); return status; } } if (!Information->Environment) { if (CreateEnvironmentBlock_I) { CreateEnvironmentBlock_I(&defaultEnvironment, tokenHandle, FALSE); if (defaultEnvironment) Flags |= PH_CREATE_PROCESS_UNICODE_ENVIRONMENT; } } status = PhCreateProcessWin32Ex( Information->ApplicationName, Information->CommandLine, Information->Environment ? Information->Environment : defaultEnvironment, Information->CurrentDirectory, &startupInfo, Flags, tokenHandle, ClientId, ProcessHandle, ThreadHandle ); if (defaultEnvironment) { if (DestroyEnvironmentBlock_I) DestroyEnvironmentBlock_I(defaultEnvironment); } NtClose(tokenHandle); return status; } NTSTATUS PhpGetAccountPrivileges( _In_ PSID AccountSid, _Out_ PTOKEN_PRIVILEGES *Privileges ) { NTSTATUS status; LSA_HANDLE accountHandle; PPRIVILEGE_SET accountPrivileges; PTOKEN_PRIVILEGES privileges; status = LsaOpenAccount(PhGetLookupPolicyHandle(), AccountSid, ACCOUNT_VIEW, &accountHandle); if (!NT_SUCCESS(status)) return status; status = LsaEnumeratePrivilegesOfAccount(accountHandle, &accountPrivileges); LsaClose(accountHandle); if (!NT_SUCCESS(status)) return status; privileges = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES) * accountPrivileges->PrivilegeCount); privileges->PrivilegeCount = accountPrivileges->PrivilegeCount; memcpy(privileges->Privileges, accountPrivileges->Privilege, sizeof(LUID_AND_ATTRIBUTES) * accountPrivileges->PrivilegeCount); LsaFreeMemory(accountPrivileges); *Privileges = privileges; return status; } /** * Filters a token to create a limited user security context. * * \param TokenHandle A handle to an existing token. The handle must have TOKEN_DUPLICATE, * TOKEN_QUERY, TOKEN_ADJUST_GROUPS, TOKEN_ADJUST_DEFAULT, READ_CONTROL and WRITE_DAC access. * \param NewTokenHandle A variable which receives a handle to the filtered token. The handle will * have the same granted access as \a TokenHandle. */ NTSTATUS PhFilterTokenForLimitedUser( _In_ HANDLE TokenHandle, _Out_ PHANDLE NewTokenHandle ) { static SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; static SID_IDENTIFIER_AUTHORITY mandatoryLabelAuthority = SECURITY_MANDATORY_LABEL_AUTHORITY; static LUID_AND_ATTRIBUTES defaultAllowedPrivileges[] = { { { SE_SHUTDOWN_PRIVILEGE, 0 }, 0 }, { { SE_CHANGE_NOTIFY_PRIVILEGE, 0 }, 0 }, { { SE_UNDOCK_PRIVILEGE, 0 }, 0 }, { { SE_INC_WORKING_SET_PRIVILEGE, 0 }, 0 }, { { SE_TIME_ZONE_PRIVILEGE, 0 }, 0 } }; NTSTATUS status; UCHAR administratorsSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; PSID administratorsSid; UCHAR usersSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; PSID usersSid; UCHAR sidsToDisableBuffer[FIELD_OFFSET(TOKEN_GROUPS, Groups) + sizeof(SID_AND_ATTRIBUTES)]; PTOKEN_GROUPS sidsToDisable; ULONG i; ULONG j; ULONG deleteIndex; BOOLEAN found; PTOKEN_PRIVILEGES privilegesOfToken; PTOKEN_PRIVILEGES privilegesOfUsers; PTOKEN_PRIVILEGES privilegesToDelete; UCHAR lowMandatoryLevelSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; PSID lowMandatoryLevelSid; TOKEN_MANDATORY_LABEL mandatoryLabel; PSECURITY_DESCRIPTOR currentSecurityDescriptor; BOOLEAN currentDaclPresent; BOOLEAN currentDaclDefaulted; PACL currentDacl; PTOKEN_USER currentUser; PACE_HEADER currentAce; ULONG newDaclLength; PACL newDacl; SECURITY_DESCRIPTOR newSecurityDescriptor; TOKEN_DEFAULT_DACL newDefaultDacl; HANDLE newTokenHandle; // Set up the SIDs to Disable structure. // Initialize the Administrators SID. administratorsSid = (PSID)administratorsSidBuffer; RtlInitializeSid(administratorsSid, &ntAuthority, 2); *RtlSubAuthoritySid(administratorsSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; *RtlSubAuthoritySid(administratorsSid, 1) = DOMAIN_ALIAS_RID_ADMINS; // Initialize the Users SID. usersSid = (PSID)usersSidBuffer; RtlInitializeSid(usersSid, &ntAuthority, 2); *RtlSubAuthoritySid(usersSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; *RtlSubAuthoritySid(usersSid, 1) = DOMAIN_ALIAS_RID_USERS; sidsToDisable = (PTOKEN_GROUPS)sidsToDisableBuffer; sidsToDisable->GroupCount = 1; sidsToDisable->Groups[0].Sid = administratorsSid; sidsToDisable->Groups[0].Attributes = 0; // Set up the Privileges to Delete structure. // Get the privileges that the input token contains. if (!NT_SUCCESS(status = PhGetTokenPrivileges(TokenHandle, &privilegesOfToken))) return status; // Get the privileges of the Users group - the privileges that we are going to allow. if (!NT_SUCCESS(PhpGetAccountPrivileges(usersSid, &privilegesOfUsers))) { // Unsuccessful, so use the default set of privileges. privilegesOfUsers = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(defaultAllowedPrivileges)); privilegesOfUsers->PrivilegeCount = sizeof(defaultAllowedPrivileges) / sizeof(LUID_AND_ATTRIBUTES); memcpy(privilegesOfUsers->Privileges, defaultAllowedPrivileges, sizeof(defaultAllowedPrivileges)); } // Allocate storage for the privileges we need to delete. The worst case scenario is that all // privileges in the token need to be deleted. privilegesToDelete = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES) * privilegesOfToken->PrivilegeCount); deleteIndex = 0; // Compute the privileges that we need to delete. for (i = 0; i < privilegesOfToken->PrivilegeCount; i++) { found = FALSE; // Is the privilege allowed? for (j = 0; j < privilegesOfUsers->PrivilegeCount; j++) { if (RtlIsEqualLuid(&privilegesOfToken->Privileges[i].Luid, &privilegesOfUsers->Privileges[j].Luid)) { found = TRUE; break; } } if (!found) { // This privilege needs to be deleted. privilegesToDelete->Privileges[deleteIndex].Attributes = 0; privilegesToDelete->Privileges[deleteIndex].Luid = privilegesOfToken->Privileges[i].Luid; deleteIndex++; } } privilegesToDelete->PrivilegeCount = deleteIndex; // Filter the token. status = NtFilterToken( TokenHandle, 0, sidsToDisable, privilegesToDelete, NULL, &newTokenHandle ); PhFree(privilegesToDelete); PhFree(privilegesOfUsers); PhFree(privilegesOfToken); if (!NT_SUCCESS(status)) return status; // Set the integrity level to Low if we're on Vista and above. if (WINDOWS_HAS_UAC) { lowMandatoryLevelSid = (PSID)lowMandatoryLevelSidBuffer; RtlInitializeSid(lowMandatoryLevelSid, &mandatoryLabelAuthority, 1); *RtlSubAuthoritySid(lowMandatoryLevelSid, 0) = SECURITY_MANDATORY_LOW_RID; mandatoryLabel.Label.Sid = lowMandatoryLevelSid; mandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY; NtSetInformationToken(newTokenHandle, TokenIntegrityLevel, &mandatoryLabel, sizeof(TOKEN_MANDATORY_LABEL)); } // Fix up the security descriptor and default DACL. if (NT_SUCCESS(PhGetObjectSecurity(newTokenHandle, DACL_SECURITY_INFORMATION, ¤tSecurityDescriptor))) { if (NT_SUCCESS(PhGetTokenUser(TokenHandle, ¤tUser))) { if (!NT_SUCCESS(RtlGetDaclSecurityDescriptor( currentSecurityDescriptor, ¤tDaclPresent, ¤tDacl, ¤tDaclDefaulted ))) { currentDaclPresent = FALSE; } newDaclLength = sizeof(ACL) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart) + RtlLengthSid(currentUser->User.Sid); if (currentDaclPresent) newDaclLength += currentDacl->AclSize - sizeof(ACL); newDacl = PhAllocate(newDaclLength); RtlCreateAcl(newDacl, newDaclLength, ACL_REVISION); // Add the existing DACL entries. if (currentDaclPresent) { for (i = 0; i < currentDacl->AceCount; i++) { if (NT_SUCCESS(RtlGetAce(currentDacl, i, ¤tAce))) RtlAddAce(newDacl, ACL_REVISION, MAXULONG32, currentAce, currentAce->AceSize); } } // Allow access for the current user. RtlAddAccessAllowedAce(newDacl, ACL_REVISION, GENERIC_ALL, currentUser->User.Sid); // Set the security descriptor of the new token. RtlCreateSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); if (NT_SUCCESS(RtlSetDaclSecurityDescriptor(&newSecurityDescriptor, TRUE, newDacl, FALSE))) PhSetObjectSecurity(newTokenHandle, DACL_SECURITY_INFORMATION, &newSecurityDescriptor); // Set the default DACL. newDefaultDacl.DefaultDacl = newDacl; NtSetInformationToken(newTokenHandle, TokenDefaultDacl, &newDefaultDacl, sizeof(TOKEN_DEFAULT_DACL)); PhFree(newDacl); PhFree(currentUser); } PhFree(currentSecurityDescriptor); } *NewTokenHandle = newTokenHandle; return STATUS_SUCCESS; } /** * Opens a file or location through the shell. * * \param hWnd The window to display user interface components on. * \param FileName A file name or location. * \param Parameters The parameters to pass to the executed application. */ VOID PhShellExecute( _In_ HWND hWnd, _In_ PWSTR FileName, _In_opt_ PWSTR Parameters ) { SHELLEXECUTEINFO info = { sizeof(info) }; info.lpFile = FileName; info.lpParameters = Parameters; info.nShow = SW_SHOW; info.hwnd = hWnd; if (!ShellExecuteEx(&info)) { // It already displays error messages by itself. //PhShowStatus(hWnd, L"Unable to execute the program", 0, GetLastError()); } } /** * Opens a file or location through the shell. * * \param hWnd The window to display user interface components on. * \param FileName A file name or location. * \param Parameters The parameters to pass to the executed application. * \param ShowWindowType A value specifying how to show the application. * \param Flags A combination of the following: * \li \c PH_SHELL_EXECUTE_ADMIN Execute the application elevated. * \li \c PH_SHELL_EXECUTE_PUMP_MESSAGES Waits on the application while pumping messages, if * \a Timeout is specified. * \param Timeout The number of milliseconds to wait on the application, or 0 to return immediately * after the application is started. * \param ProcessHandle A variable which receives a handle to the new process. */ BOOLEAN PhShellExecuteEx( _In_opt_ HWND hWnd, _In_ PWSTR FileName, _In_opt_ PWSTR Parameters, _In_ ULONG ShowWindowType, _In_ ULONG Flags, _In_opt_ ULONG Timeout, _Out_opt_ PHANDLE ProcessHandle ) { SHELLEXECUTEINFO info = { sizeof(info) }; info.lpFile = FileName; info.lpParameters = Parameters; info.fMask = SEE_MASK_NOCLOSEPROCESS; info.nShow = ShowWindowType; info.hwnd = hWnd; if ((Flags & PH_SHELL_EXECUTE_ADMIN) && WINDOWS_HAS_UAC) info.lpVerb = L"runas"; if (ShellExecuteEx(&info)) { if (Timeout) { if (!(Flags & PH_SHELL_EXECUTE_PUMP_MESSAGES)) { LARGE_INTEGER timeout; NtWaitForSingleObject(info.hProcess, FALSE, PhTimeoutFromMilliseconds(&timeout, Timeout)); } else { PhWaitForMultipleObjectsAndPump(NULL, 1, &info.hProcess, Timeout); } } if (ProcessHandle) *ProcessHandle = info.hProcess; else NtClose(info.hProcess); return TRUE; } else { return FALSE; } } /** * Opens Windows Explorer with a file selected. * * \param hWnd A handle to the parent window. * \param FileName A file name. */ VOID PhShellExploreFile( _In_ HWND hWnd, _In_ PWSTR FileName ) { if (SHOpenFolderAndSelectItems_Import() && SHParseDisplayName_Import()) { LPITEMIDLIST item; SFGAOF attributes; if (SUCCEEDED(SHParseDisplayName_Import()(FileName, NULL, &item, 0, &attributes))) { SHOpenFolderAndSelectItems_Import()(item, 0, NULL, 0); CoTaskMemFree(item); } else { PhShowError(hWnd, L"The location \"%s\" could not be found.", FileName); } } else { PPH_STRING selectFileName; selectFileName = PhConcatStrings2(L"/select,", FileName); PhShellExecute(hWnd, L"explorer.exe", selectFileName->Buffer); PhDereferenceObject(selectFileName); } } /** * Shows properties for a file. * * \param hWnd A handle to the parent window. * \param FileName A file name. */ VOID PhShellProperties( _In_ HWND hWnd, _In_ PWSTR FileName ) { SHELLEXECUTEINFO info = { sizeof(info) }; info.lpFile = FileName; info.nShow = SW_SHOW; info.fMask = SEE_MASK_INVOKEIDLIST; info.lpVerb = L"properties"; info.hwnd = hWnd; if (!ShellExecuteEx(&info)) { // It already displays error messages by itself. //PhShowStatus(hWnd, L"Unable to execute the program", 0, GetLastError()); } } /** * Expands registry name abbreviations. * * \param KeyName The key name. * \param Computer TRUE to prepend "Computer" or "My Computer" for use with the Registry Editor. */ PPH_STRING PhExpandKeyName( _In_ PPH_STRING KeyName, _In_ BOOLEAN Computer ) { PPH_STRING keyName; PPH_STRING tempString; if (PhStartsWithString2(KeyName, L"HKCU", TRUE)) { keyName = PhConcatStrings2(L"HKEY_CURRENT_USER", &KeyName->Buffer[4]); } else if (PhStartsWithString2(KeyName, L"HKU", TRUE)) { keyName = PhConcatStrings2(L"HKEY_USERS", &KeyName->Buffer[3]); } else if (PhStartsWithString2(KeyName, L"HKCR", TRUE)) { keyName = PhConcatStrings2(L"HKEY_CLASSES_ROOT", &KeyName->Buffer[4]); } else if (PhStartsWithString2(KeyName, L"HKLM", TRUE)) { keyName = PhConcatStrings2(L"HKEY_LOCAL_MACHINE", &KeyName->Buffer[4]); } else { PhSetReference(&keyName, KeyName); } if (Computer) { if (WindowsVersion >= WINDOWS_VISTA) tempString = PhConcatStrings2(L"Computer\\", keyName->Buffer); else tempString = PhConcatStrings2(L"My Computer\\", keyName->Buffer); PhDereferenceObject(keyName); keyName = tempString; } return keyName; } /** * Opens a key in the Registry Editor. * * \param hWnd A handle to the parent window. * \param KeyName The key name to open. */ VOID PhShellOpenKey( _In_ HWND hWnd, _In_ PPH_STRING KeyName ) { static PH_STRINGREF regeditKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit"); PPH_STRING lastKey; HANDLE regeditKeyHandle; UNICODE_STRING valueName; PPH_STRING regeditFileName; if (!NT_SUCCESS(PhCreateKey( ®editKeyHandle, KEY_WRITE, PH_KEY_CURRENT_USER, ®editKeyName, 0, 0, NULL ))) return; RtlInitUnicodeString(&valueName, L"LastKey"); lastKey = PhExpandKeyName(KeyName, TRUE); NtSetValueKey(regeditKeyHandle, &valueName, 0, REG_SZ, lastKey->Buffer, (ULONG)lastKey->Length + 2); PhDereferenceObject(lastKey); NtClose(regeditKeyHandle); // Start regedit. If we aren't elevated, request that regedit be elevated. This is so we can get // the consent dialog in the center of the specified window. regeditFileName = PhGetKnownLocation(CSIDL_WINDOWS, L"\\regedit.exe"); if (!regeditFileName) regeditFileName = PhCreateString(L"regedit.exe"); if (!PhGetOwnTokenAttributes().Elevated) { PhShellExecuteEx(hWnd, regeditFileName->Buffer, L"", SW_NORMAL, PH_SHELL_EXECUTE_ADMIN, 0, NULL); } else { PhShellExecute(hWnd, regeditFileName->Buffer, L""); } PhDereferenceObject(regeditFileName); } /** * Gets a registry string value. * * \param KeyHandle A handle to the key. * \param ValueName The name of the value. * * \return A pointer to a string containing the value, or NULL if the function failed. You must free * the string using PhDereferenceObject() when you no longer need it. */ PPH_STRING PhQueryRegistryString( _In_ HANDLE KeyHandle, _In_opt_ PWSTR ValueName ) { PPH_STRING string = NULL; PH_STRINGREF valueName; PKEY_VALUE_PARTIAL_INFORMATION buffer; if (ValueName) PhInitializeStringRef(&valueName, ValueName); else PhInitializeEmptyStringRef(&valueName); if (NT_SUCCESS(PhQueryValueKey(KeyHandle, &valueName, KeyValuePartialInformation, &buffer))) { if (buffer->Type == REG_SZ || buffer->Type == REG_MULTI_SZ || buffer->Type == REG_EXPAND_SZ) { if (buffer->DataLength >= sizeof(WCHAR)) string = PhCreateStringEx((PWCHAR)buffer->Data, buffer->DataLength - sizeof(WCHAR)); else string = PhReferenceEmptyString(); } PhFree(buffer); } return string; } VOID PhMapFlags1( _Inout_ PULONG Value2, _In_ ULONG Value1, _In_ const PH_FLAG_MAPPING *Mappings, _In_ ULONG NumberOfMappings ) { ULONG i; ULONG value2; value2 = *Value2; if (value2 != 0) { // There are existing flags. Map the flags we know about by clearing/setting them. The flags // we don't know about won't be affected. for (i = 0; i < NumberOfMappings; i++) { if (Value1 & Mappings[i].Flag1) value2 |= Mappings[i].Flag2; else value2 &= ~Mappings[i].Flag2; } } else { // There are no existing flags, which means we can build the value from scratch, with no // clearing needed. for (i = 0; i < NumberOfMappings; i++) { if (Value1 & Mappings[i].Flag1) value2 |= Mappings[i].Flag2; } } *Value2 = value2; } VOID PhMapFlags2( _Inout_ PULONG Value1, _In_ ULONG Value2, _In_ const PH_FLAG_MAPPING *Mappings, _In_ ULONG NumberOfMappings ) { ULONG i; ULONG value1; value1 = *Value1; if (value1 != 0) { for (i = 0; i < NumberOfMappings; i++) { if (Value2 & Mappings[i].Flag2) value1 |= Mappings[i].Flag1; else value1 &= ~Mappings[i].Flag1; } } else { for (i = 0; i < NumberOfMappings; i++) { if (Value2 & Mappings[i].Flag2) value1 |= Mappings[i].Flag1; } } *Value1 = value1; } UINT_PTR CALLBACK PhpOpenFileNameHookProc( _In_ HWND hdlg, _In_ UINT uiMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { switch (uiMsg) { case WM_NOTIFY: { LPOFNOTIFY header = (LPOFNOTIFY)lParam; // We can't use CDN_FILEOK because it's not sent if the buffer is too small, defeating // the entire purpose of this callback function. switch (header->hdr.code) { case CDN_SELCHANGE: { ULONG returnLength; returnLength = CommDlg_OpenSave_GetFilePath( header->hdr.hwndFrom, header->lpOFN->lpstrFile, header->lpOFN->nMaxFile ); if ((LONG)returnLength > 0 && returnLength > header->lpOFN->nMaxFile) { PhFree(header->lpOFN->lpstrFile); header->lpOFN->nMaxFile = returnLength + 0x200; // pre-allocate some more header->lpOFN->lpstrFile = PhAllocate(header->lpOFN->nMaxFile * 2); returnLength = CommDlg_OpenSave_GetFilePath( header->hdr.hwndFrom, header->lpOFN->lpstrFile, header->lpOFN->nMaxFile ); } } break; } } break; } return FALSE; } OPENFILENAME *PhpCreateOpenFileName( VOID ) { OPENFILENAME *ofn; ofn = PhAllocate(sizeof(OPENFILENAME)); memset(ofn, 0, sizeof(OPENFILENAME)); ofn->lStructSize = sizeof(OPENFILENAME); ofn->nMaxFile = 0x400; ofn->lpstrFile = PhAllocate(ofn->nMaxFile * 2); ofn->lpstrFileTitle = NULL; ofn->Flags = OFN_ENABLEHOOK | OFN_EXPLORER; ofn->lpfnHook = PhpOpenFileNameHookProc; ofn->lpstrFile[0] = 0; return ofn; } VOID PhpFreeOpenFileName( _In_ OPENFILENAME *OpenFileName ) { if (OpenFileName->lpstrFilter) PhFree((PVOID)OpenFileName->lpstrFilter); if (OpenFileName->lpstrFile) PhFree((PVOID)OpenFileName->lpstrFile); PhFree(OpenFileName); } typedef struct _PHP_FILE_DIALOG { BOOLEAN UseIFileDialog; BOOLEAN Save; union { OPENFILENAME *OpenFileName; IFileDialog *FileDialog; } u; } PHP_FILE_DIALOG, *PPHP_FILE_DIALOG; PPHP_FILE_DIALOG PhpCreateFileDialog( _In_ BOOLEAN Save, _In_opt_ OPENFILENAME *OpenFileName, _In_opt_ IFileDialog *FileDialog ) { PPHP_FILE_DIALOG fileDialog; assert(!!OpenFileName != !!FileDialog); fileDialog = PhAllocate(sizeof(PHP_FILE_DIALOG)); fileDialog->Save = Save; if (OpenFileName) { fileDialog->UseIFileDialog = FALSE; fileDialog->u.OpenFileName = OpenFileName; } else if (FileDialog) { fileDialog->UseIFileDialog = TRUE; fileDialog->u.FileDialog = FileDialog; } else { PhRaiseStatus(STATUS_INVALID_PARAMETER); } return fileDialog; } /** * Creates a file dialog for the user to select a file to open. * * \return An opaque pointer representing the file dialog. You must free the file dialog using * PhFreeFileDialog() when you no longer need it. */ PVOID PhCreateOpenFileDialog( VOID ) { OPENFILENAME *ofn; PVOID ofnFileDialog; if (PHP_USE_IFILEDIALOG) { IFileDialog *fileDialog; if (SUCCEEDED(CoCreateInstance( &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileDialog, &fileDialog ))) { // The default options are fine. return PhpCreateFileDialog(FALSE, NULL, fileDialog); } } ofn = PhpCreateOpenFileName(); ofnFileDialog = PhpCreateFileDialog(FALSE, ofn, NULL); PhSetFileDialogOptions(ofnFileDialog, PH_FILEDIALOG_PATHMUSTEXIST | PH_FILEDIALOG_FILEMUSTEXIST | PH_FILEDIALOG_STRICTFILETYPES); return ofnFileDialog; } /** * Creates a file dialog for the user to select a file to save to. * * \return An opaque pointer representing the file dialog. You must free the file dialog using * PhFreeFileDialog() when you no longer need it. */ PVOID PhCreateSaveFileDialog( VOID ) { OPENFILENAME *ofn; PVOID ofnFileDialog; if (PHP_USE_IFILEDIALOG) { IFileDialog *fileDialog; if (SUCCEEDED(CoCreateInstance( &CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, &IID_IFileDialog, &fileDialog ))) { // The default options are fine. return PhpCreateFileDialog(TRUE, NULL, fileDialog); } } ofn = PhpCreateOpenFileName(); ofnFileDialog = PhpCreateFileDialog(TRUE, ofn, NULL); PhSetFileDialogOptions(ofnFileDialog, PH_FILEDIALOG_PATHMUSTEXIST | PH_FILEDIALOG_OVERWRITEPROMPT | PH_FILEDIALOG_STRICTFILETYPES); return ofnFileDialog; } /** * Frees a file dialog. * * \param FileDialog The file dialog. */ VOID PhFreeFileDialog( _In_ PVOID FileDialog ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { IFileDialog_Release(fileDialog->u.FileDialog); } else { PhpFreeOpenFileName(fileDialog->u.OpenFileName); } PhFree(fileDialog); } /** * Shows a file dialog to the user. * * \param hWnd A handle to the parent window. * \param FileDialog The file dialog. * * \return TRUE if the user selected a file, FALSE if the user cancelled the operation or an error * occurred. */ BOOLEAN PhShowFileDialog( _In_ HWND hWnd, _In_ PVOID FileDialog ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { // Set a blank default extension. This will have an effect when the user selects a different // file type. IFileDialog_SetDefaultExtension(fileDialog->u.FileDialog, L""); return SUCCEEDED(IFileDialog_Show(fileDialog->u.FileDialog, hWnd)); } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; ofn->hwndOwner = hWnd; // Determine whether the structure represents a open or save dialog and call the appropriate // function. if (!fileDialog->Save) { return GetOpenFileName(ofn); } else { return GetSaveFileName(ofn); } } } static const PH_FLAG_MAPPING PhpFileDialogIfdMappings[] = { { PH_FILEDIALOG_CREATEPROMPT, FOS_CREATEPROMPT }, { PH_FILEDIALOG_PATHMUSTEXIST, FOS_PATHMUSTEXIST }, { PH_FILEDIALOG_FILEMUSTEXIST, FOS_FILEMUSTEXIST }, { PH_FILEDIALOG_SHOWHIDDEN, FOS_FORCESHOWHIDDEN }, { PH_FILEDIALOG_NODEREFERENCELINKS, FOS_NODEREFERENCELINKS }, { PH_FILEDIALOG_OVERWRITEPROMPT, FOS_OVERWRITEPROMPT }, { PH_FILEDIALOG_DEFAULTEXPANDED, FOS_DEFAULTNOMINIMODE }, { PH_FILEDIALOG_STRICTFILETYPES, FOS_STRICTFILETYPES }, { PH_FILEDIALOG_PICKFOLDERS, FOS_PICKFOLDERS } }; static const PH_FLAG_MAPPING PhpFileDialogOfnMappings[] = { { PH_FILEDIALOG_CREATEPROMPT, OFN_CREATEPROMPT }, { PH_FILEDIALOG_PATHMUSTEXIST, OFN_PATHMUSTEXIST }, { PH_FILEDIALOG_FILEMUSTEXIST, OFN_FILEMUSTEXIST }, { PH_FILEDIALOG_SHOWHIDDEN, OFN_FORCESHOWHIDDEN }, { PH_FILEDIALOG_NODEREFERENCELINKS, OFN_NODEREFERENCELINKS }, { PH_FILEDIALOG_OVERWRITEPROMPT, OFN_OVERWRITEPROMPT } }; /** * Gets the options for a file dialog. * * \param FileDialog The file dialog. * * \return The currently enabled options. See the documentation for PhSetFileDialogOptions() for * details. */ ULONG PhGetFileDialogOptions( _In_ PVOID FileDialog ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { FILEOPENDIALOGOPTIONS dialogOptions; ULONG options; if (SUCCEEDED(IFileDialog_GetOptions(fileDialog->u.FileDialog, &dialogOptions))) { options = 0; PhMapFlags2( &options, dialogOptions, PhpFileDialogIfdMappings, sizeof(PhpFileDialogIfdMappings) / sizeof(PH_FLAG_MAPPING) ); return options; } else { return 0; } } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; ULONG options; options = 0; PhMapFlags2( &options, ofn->Flags, PhpFileDialogOfnMappings, sizeof(PhpFileDialogOfnMappings) / sizeof(PH_FLAG_MAPPING) ); return options; } } /** * Sets the options for a file dialog. * * \param FileDialog The file dialog. * \param Options A combination of flags specifying the options. * \li \c PH_FILEDIALOG_CREATEPROMPT A prompt for creation will be displayed when the selected item * does not exist. This is only valid for Save dialogs. * \li \c PH_FILEDIALOG_PATHMUSTEXIST The selected item must be in an existing folder. This is * enabled by default. * \li \c PH_FILEDIALOG_FILEMUSTEXIST The selected item must exist. This is enabled by default and * is only valid for Open dialogs. * \li \c PH_FILEDIALOG_SHOWHIDDEN Items with the System and Hidden attributes will be displayed. * \li \c PH_FILEDIALOG_NODEREFERENCELINKS Shortcuts will not be followed, allowing .lnk files to be * opened. * \li \c PH_FILEDIALOG_OVERWRITEPROMPT An overwrite prompt will be displayed if an existing item is * selected. This is enabled by default and is only valid for Save dialogs. * \li \c PH_FILEDIALOG_DEFAULTEXPANDED The file dialog should be expanded by default (i.e. the * folder browser should be displayed). This is only valid for Save dialogs. */ VOID PhSetFileDialogOptions( _In_ PVOID FileDialog, _In_ ULONG Options ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { FILEOPENDIALOGOPTIONS dialogOptions; if (SUCCEEDED(IFileDialog_GetOptions(fileDialog->u.FileDialog, &dialogOptions))) { PhMapFlags1( &dialogOptions, Options, PhpFileDialogIfdMappings, sizeof(PhpFileDialogIfdMappings) / sizeof(PH_FLAG_MAPPING) ); IFileDialog_SetOptions(fileDialog->u.FileDialog, dialogOptions); } } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; PhMapFlags1( &ofn->Flags, Options, PhpFileDialogOfnMappings, sizeof(PhpFileDialogOfnMappings) / sizeof(PH_FLAG_MAPPING) ); } } /** * Gets the index of the currently selected file type filter for a file dialog. * * \param FileDialog The file dialog. * * \return The one-based index of the selected file type, or 0 if an error occurred. */ ULONG PhGetFileDialogFilterIndex( _In_ PVOID FileDialog ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { ULONG index; if (SUCCEEDED(IFileDialog_GetFileTypeIndex(fileDialog->u.FileDialog, &index))) { return index; } else { return 0; } } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; return ofn->nFilterIndex; } } /** * Sets the file type filter for a file dialog. * * \param FileDialog The file dialog. * \param Filters A pointer to an array of file type structures. * \param NumberOfFilters The number of file types. */ VOID PhSetFileDialogFilter( _In_ PVOID FileDialog, _In_ PPH_FILETYPE_FILTER Filters, _In_ ULONG NumberOfFilters ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { IFileDialog_SetFileTypes( fileDialog->u.FileDialog, NumberOfFilters, (COMDLG_FILTERSPEC *)Filters ); } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; PPH_STRING filterString; PH_STRING_BUILDER filterBuilder; ULONG i; PhInitializeStringBuilder(&filterBuilder, 10); for (i = 0; i < NumberOfFilters; i++) { PhAppendStringBuilder2(&filterBuilder, Filters[i].Name); PhAppendCharStringBuilder(&filterBuilder, 0); PhAppendStringBuilder2(&filterBuilder, Filters[i].Filter); PhAppendCharStringBuilder(&filterBuilder, 0); } filterString = PhFinalStringBuilderString(&filterBuilder); if (ofn->lpstrFilter) PhFree((PVOID)ofn->lpstrFilter); ofn->lpstrFilter = PhAllocateCopy(filterString->Buffer, filterString->Length + 2); PhDereferenceObject(filterString); } } /** * Gets the file name selected in a file dialog. * * \param FileDialog The file dialog. * * \return A pointer to a string containing the file name. You must free the string using * PhDereferenceObject() when you no longer need it. */ PPH_STRING PhGetFileDialogFileName( _In_ PVOID FileDialog ) { PPHP_FILE_DIALOG fileDialog = FileDialog; if (fileDialog->UseIFileDialog) { IShellItem *result; PPH_STRING fileName = NULL; if (SUCCEEDED(IFileDialog_GetResult(fileDialog->u.FileDialog, &result))) { PWSTR name; if (SUCCEEDED(IShellItem_GetDisplayName(result, SIGDN_FILESYSPATH, &name))) { fileName = PhCreateString(name); CoTaskMemFree(name); } IShellItem_Release(result); } if (!fileName) { PWSTR name; if (SUCCEEDED(IFileDialog_GetFileName(fileDialog->u.FileDialog, &name))) { fileName = PhCreateString(name); CoTaskMemFree(name); } } return fileName; } else { return PhCreateString(fileDialog->u.OpenFileName->lpstrFile); } } /** * Sets the file name of a file dialog. * * \param FileDialog The file dialog. * \param FileName The new file name. */ VOID PhSetFileDialogFileName( _In_ PVOID FileDialog, _In_ PWSTR FileName ) { PPHP_FILE_DIALOG fileDialog = FileDialog; PH_STRINGREF fileName; PhInitializeStringRefLongHint(&fileName, FileName); if (fileDialog->UseIFileDialog) { IShellItem *shellItem = NULL; PH_STRINGREF pathNamePart; PH_STRINGREF baseNamePart; if (PhSplitStringRefAtLastChar(&fileName, '\\', &pathNamePart, &baseNamePart) && SHParseDisplayName_Import() && SHCreateShellItem_Import()) { LPITEMIDLIST item; SFGAOF attributes; PPH_STRING pathName; pathName = PhCreateString2(&pathNamePart); if (SUCCEEDED(SHParseDisplayName_Import()(pathName->Buffer, NULL, &item, 0, &attributes))) { SHCreateShellItem_Import()(NULL, NULL, item, &shellItem); CoTaskMemFree(item); } PhDereferenceObject(pathName); } if (shellItem) { IFileDialog_SetFolder(fileDialog->u.FileDialog, shellItem); IFileDialog_SetFileName(fileDialog->u.FileDialog, baseNamePart.Buffer); IShellItem_Release(shellItem); } else { IFileDialog_SetFileName(fileDialog->u.FileDialog, FileName); } } else { OPENFILENAME *ofn = fileDialog->u.OpenFileName; if (PhFindCharInStringRef(&fileName, '/', FALSE) != -1 || PhFindCharInStringRef(&fileName, '\"', FALSE) != -1) { // It refuses to take any filenames with a slash or quotation mark. return; } PhFree(ofn->lpstrFile); ofn->nMaxFile = (ULONG)max(fileName.Length / sizeof(WCHAR) + 1, 0x400); ofn->lpstrFile = PhAllocate(ofn->nMaxFile * 2); memcpy(ofn->lpstrFile, fileName.Buffer, fileName.Length + sizeof(WCHAR)); } } /** * Determines if an executable image is packed. * * \param FileName The file name of the image. * \param IsPacked A variable that receives TRUE if the image is packed, otherwise FALSE. * \param NumberOfModules A variable that receives the number of DLLs that the image imports * functions from. * \param NumberOfFunctions A variable that receives the number of functions that the image imports. */ NTSTATUS PhIsExecutablePacked( _In_ PWSTR FileName, _Out_ PBOOLEAN IsPacked, _Out_opt_ PULONG NumberOfModules, _Out_opt_ PULONG NumberOfFunctions ) { // An image is packed if: // // 1. It references fewer than 3 modules, and // 2. It imports fewer than 5 functions, and // 3. It does not use the Native subsystem. // // Or: // // 1. The function-to-module ratio is lower than 3 (on average fewer than 3 functions are // imported from each module), and // 2. It references more than 2 modules but fewer than 6 modules. // // Or: // // 1. The function-to-module ratio is lower than 2 (on average fewer than 2 functions are // imported from each module), and // 2. It references more than 5 modules but fewer than 31 modules. // // Or: // // 1. It does not have a section named ".text". // // An image is not considered to be packed if it has only one import from a module named // "mscoree.dll". NTSTATUS status; PH_MAPPED_IMAGE mappedImage; PH_MAPPED_IMAGE_IMPORTS imports; PH_MAPPED_IMAGE_IMPORT_DLL importDll; ULONG i; //ULONG limitNumberOfSections; ULONG limitNumberOfModules; ULONG numberOfModules; ULONG numberOfFunctions = 0; BOOLEAN hasTextSection = FALSE; BOOLEAN isModuleMscoree = FALSE; BOOLEAN isPacked; status = PhLoadMappedImage( FileName, NULL, TRUE, &mappedImage ); if (!NT_SUCCESS(status)) return status; // Go through the sections and look for the ".text" section. // This rule is currently disabled. hasTextSection = TRUE; //limitNumberOfSections = min(mappedImage.NumberOfSections, 64); //for (i = 0; i < limitNumberOfSections; i++) //{ // CHAR sectionName[IMAGE_SIZEOF_SHORT_NAME + 1]; // if (PhGetMappedImageSectionName( // &mappedImage.Sections[i], // sectionName, // IMAGE_SIZEOF_SHORT_NAME + 1, // NULL // )) // { // if (STR_IEQUAL(sectionName, ".text")) // { // hasTextSection = TRUE; // break; // } // } //} status = PhGetMappedImageImports( &imports, &mappedImage ); if (!NT_SUCCESS(status)) goto CleanupExit; // Get the module and function totals. numberOfModules = imports.NumberOfDlls; limitNumberOfModules = min(numberOfModules, 64); for (i = 0; i < limitNumberOfModules; i++) { if (!NT_SUCCESS(status = PhGetMappedImageImportDll( &imports, i, &importDll ))) goto CleanupExit; if (PhEqualBytesZ(importDll.Name, "mscoree.dll", TRUE)) isModuleMscoree = TRUE; numberOfFunctions += importDll.NumberOfEntries; } // Determine if the image is packed. if ( numberOfModules != 0 && ( // Rule 1 (numberOfModules < 3 && numberOfFunctions < 5 && mappedImage.NtHeaders->OptionalHeader.Subsystem != IMAGE_SUBSYSTEM_NATIVE) || // Rule 2 ((numberOfFunctions / numberOfModules) < 3 && numberOfModules > 2 && numberOfModules < 5) || // Rule 3 ((numberOfFunctions / numberOfModules) < 2 && numberOfModules > 4 && numberOfModules < 31) || // Rule 4 !hasTextSection ) && // Additional .NET rule !(numberOfModules == 1 && numberOfFunctions == 1 && isModuleMscoree) ) { isPacked = TRUE; } else { isPacked = FALSE; } *IsPacked = isPacked; if (NumberOfModules) *NumberOfModules = numberOfModules; if (NumberOfFunctions) *NumberOfFunctions = numberOfFunctions; CleanupExit: PhUnloadMappedImage(&mappedImage); return status; } ULONG PhCrc32( _In_ ULONG Crc, _In_reads_(Length) PCHAR Buffer, _In_ SIZE_T Length ) { Crc ^= 0xffffffff; while (Length--) Crc = (Crc >> 8) ^ PhCrc32Table[(Crc ^ *Buffer++) & 0xff]; return Crc ^ 0xffffffff; } C_ASSERT(RTL_FIELD_SIZE(PH_HASH_CONTEXT, Context) >= sizeof(MD5_CTX)); C_ASSERT(RTL_FIELD_SIZE(PH_HASH_CONTEXT, Context) >= sizeof(A_SHA_CTX)); /** * Initializes hashing. * * \param Context A hashing context structure. * \param Algorithm The hash algorithm to use: * \li \c Md5HashAlgorithm MD5 (128 bits) * \li \c Sha1HashAlgorithm SHA-1 (160 bits) * \li \c Crc32HashAlgorithm CRC-32-IEEE 802.3 (32 bits) */ VOID PhInitializeHash( _Out_ PPH_HASH_CONTEXT Context, _In_ PH_HASH_ALGORITHM Algorithm ) { Context->Algorithm = Algorithm; switch (Algorithm) { case Md5HashAlgorithm: MD5Init((MD5_CTX *)Context->Context); break; case Sha1HashAlgorithm: A_SHAInit((A_SHA_CTX *)Context->Context); break; case Crc32HashAlgorithm: Context->Context[0] = 0; break; default: PhRaiseStatus(STATUS_INVALID_PARAMETER_2); break; } } /** * Hashes a block of data. * * \param Context A hashing context structure. * \param Buffer The block of data. * \param Length The number of bytes in the block. */ VOID PhUpdateHash( _Inout_ PPH_HASH_CONTEXT Context, _In_reads_bytes_(Length) PVOID Buffer, _In_ ULONG Length ) { switch (Context->Algorithm) { case Md5HashAlgorithm: MD5Update((MD5_CTX *)Context->Context, (PUCHAR)Buffer, Length); break; case Sha1HashAlgorithm: A_SHAUpdate((A_SHA_CTX *)Context->Context, (PUCHAR)Buffer, Length); break; case Crc32HashAlgorithm: Context->Context[0] = PhCrc32(Context->Context[0], (PUCHAR)Buffer, Length); break; default: PhRaiseStatus(STATUS_INVALID_PARAMETER); } } /** * Computes the final hash value. * * \param Context A hashing context structure. * \param Hash A buffer which receives the final hash value. * \param HashLength The size of the buffer, in bytes. * \param ReturnLength A variable which receives the required size of the buffer, in bytes. */ BOOLEAN PhFinalHash( _Inout_ PPH_HASH_CONTEXT Context, _Out_writes_bytes_(HashLength) PVOID Hash, _In_ ULONG HashLength, _Out_opt_ PULONG ReturnLength ) { BOOLEAN result; ULONG returnLength; result = FALSE; switch (Context->Algorithm) { case Md5HashAlgorithm: if (HashLength >= 16) { MD5Final((MD5_CTX *)Context->Context); memcpy(Hash, ((MD5_CTX *)Context->Context)->digest, 16); result = TRUE; } returnLength = 16; break; case Sha1HashAlgorithm: if (HashLength >= 20) { A_SHAFinal((A_SHA_CTX *)Context->Context, (PUCHAR)Hash); result = TRUE; } returnLength = 20; break; case Crc32HashAlgorithm: if (HashLength >= 4) { *(PULONG)Hash = Context->Context[0]; result = TRUE; } returnLength = 4; break; default: PhRaiseStatus(STATUS_INVALID_PARAMETER); } if (ReturnLength) *ReturnLength = returnLength; return result; } /** * Parses one part of a command line string. Quotation marks and backslashes are handled * appropriately. * * \param CommandLine The entire command line string. * \param Index The starting index of the command line part to be parsed. There should be no leading * whitespace at this index. The index is updated to point to the end of the command line part. */ PPH_STRING PhParseCommandLinePart( _In_ PPH_STRINGREF CommandLine, _Inout_ PULONG_PTR Index ) { PH_STRING_BUILDER stringBuilder; SIZE_T length; SIZE_T i; ULONG numberOfBackslashes; BOOLEAN inQuote; BOOLEAN endOfValue; length = CommandLine->Length / 2; i = *Index; // This function follows the rules used by CommandLineToArgvW: // // * 2n backslashes and a quotation mark produces n backslashes and a quotation mark // (non-literal). // * 2n + 1 backslashes and a quotation mark produces n and a quotation mark (literal). // * n backslashes and no quotation mark produces n backslashes. PhInitializeStringBuilder(&stringBuilder, 10); numberOfBackslashes = 0; inQuote = FALSE; endOfValue = FALSE; for (; i < length; i++) { switch (CommandLine->Buffer[i]) { case '\\': numberOfBackslashes++; break; case '\"': if (numberOfBackslashes != 0) { if (numberOfBackslashes & 1) { numberOfBackslashes /= 2; if (numberOfBackslashes != 0) { PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); numberOfBackslashes = 0; } PhAppendCharStringBuilder(&stringBuilder, '\"'); break; } else { numberOfBackslashes /= 2; PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); numberOfBackslashes = 0; } } if (!inQuote) inQuote = TRUE; else inQuote = FALSE; break; default: if (numberOfBackslashes != 0) { PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); numberOfBackslashes = 0; } if (CommandLine->Buffer[i] == ' ' && !inQuote) { endOfValue = TRUE; } else { PhAppendCharStringBuilder(&stringBuilder, CommandLine->Buffer[i]); } break; } if (endOfValue) break; } *Index = i; return PhFinalStringBuilderString(&stringBuilder); } /** * Parses a command line string. * * \param CommandLine The command line string. * \param Options An array of supported command line options. * \param NumberOfOptions The number of elements in \a Options. * \param Flags A combination of flags. * \li \c PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS Unknown command line options are ignored instead of * failing the function. * \li \c PH_COMMAND_LINE_IGNORE_FIRST_PART The first part of the command line string is ignored. * This is used when the first part of the string contains the executable file name. * \param Callback A callback function to execute for each command line option found. * \param Context A user-defined value to pass to \a Callback. */ BOOLEAN PhParseCommandLine( _In_ PPH_STRINGREF CommandLine, _In_opt_ PPH_COMMAND_LINE_OPTION Options, _In_ ULONG NumberOfOptions, _In_ ULONG Flags, _In_ PPH_COMMAND_LINE_CALLBACK Callback, _In_opt_ PVOID Context ) { SIZE_T i; SIZE_T j; SIZE_T length; BOOLEAN cont = TRUE; BOOLEAN wasFirst = TRUE; PH_STRINGREF optionName; PPH_COMMAND_LINE_OPTION option = NULL; PPH_STRING optionValue; if (CommandLine->Length == 0) return TRUE; i = 0; length = CommandLine->Length / 2; while (TRUE) { // Skip spaces. while (i < length && CommandLine->Buffer[i] == ' ') i++; if (i >= length) break; if (option && (option->Type == MandatoryArgumentType || (option->Type == OptionalArgumentType && CommandLine->Buffer[i] != '-'))) { // Read the value and execute the callback function. optionValue = PhParseCommandLinePart(CommandLine, &i); cont = Callback(option, optionValue, Context); PhDereferenceObject(optionValue); if (!cont) break; option = NULL; } else if (CommandLine->Buffer[i] == '-') { ULONG_PTR originalIndex; SIZE_T optionNameLength; // Read the option (only alphanumeric characters allowed). // Skip the dash. i++; originalIndex = i; for (; i < length; i++) { if (!iswalnum(CommandLine->Buffer[i]) && CommandLine->Buffer[i] != '-') break; } optionNameLength = i - originalIndex; optionName.Buffer = &CommandLine->Buffer[originalIndex]; optionName.Length = optionNameLength * 2; // Take care of any pending optional argument. if (option && option->Type == OptionalArgumentType) { cont = Callback(option, NULL, Context); if (!cont) break; option = NULL; } // Find the option descriptor. option = NULL; for (j = 0; j < NumberOfOptions; j++) { if (PhEqualStringRef2(&optionName, Options[j].Name, FALSE)) { option = &Options[j]; break; } } if (!option && !(Flags & PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS)) return FALSE; if (option && option->Type == NoArgumentType) { cont = Callback(option, NULL, Context); if (!cont) break; option = NULL; } wasFirst = FALSE; } else { PPH_STRING value; value = PhParseCommandLinePart(CommandLine, &i); if ((Flags & PH_COMMAND_LINE_IGNORE_FIRST_PART) && wasFirst) { PhDereferenceObject(value); value = NULL; } if (value) { cont = Callback(NULL, value, Context); PhDereferenceObject(value); if (!cont) break; } wasFirst = FALSE; } } if (cont && option && option->Type == OptionalArgumentType) Callback(option, NULL, Context); return TRUE; } /** * Escapes a string for use in a command line. * * \param String The string to escape. * * \return The escaped string. * * \remarks Only the double quotation mark is escaped. */ PPH_STRING PhEscapeCommandLinePart( _In_ PPH_STRINGREF String ) { static PH_STRINGREF backslashAndQuote = PH_STRINGREF_INIT(L"\\\""); PH_STRING_BUILDER stringBuilder; ULONG length; ULONG i; ULONG numberOfBackslashes; length = (ULONG)String->Length / 2; PhInitializeStringBuilder(&stringBuilder, String->Length / 2 * 3); numberOfBackslashes = 0; // Simply replacing " with \" won't work here. See PhParseCommandLinePart for the quoting rules. for (i = 0; i < length; i++) { switch (String->Buffer[i]) { case '\\': numberOfBackslashes++; break; case '\"': if (numberOfBackslashes != 0) { PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes * 2); numberOfBackslashes = 0; } PhAppendStringBuilder(&stringBuilder, &backslashAndQuote); break; default: if (numberOfBackslashes != 0) { PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); numberOfBackslashes = 0; } PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); break; } } return PhFinalStringBuilderString(&stringBuilder); } BOOLEAN PhpSearchFilePath( _In_ PWSTR FileName, _In_opt_ PWSTR Extension, _Out_writes_(MAX_PATH) PWSTR Buffer ) { NTSTATUS status; ULONG result; UNICODE_STRING fileName; OBJECT_ATTRIBUTES objectAttributes; FILE_BASIC_INFORMATION basicInfo; result = SearchPath( NULL, FileName, Extension, MAX_PATH, Buffer, NULL ); if (result == 0 || result >= MAX_PATH) return FALSE; // Make sure this is not a directory. if (!NT_SUCCESS(RtlDosPathNameToNtPathName_U( Buffer, &fileName, NULL, NULL ))) return FALSE; InitializeObjectAttributes( &objectAttributes, &fileName, OBJ_CASE_INSENSITIVE, NULL, NULL ); status = NtQueryAttributesFile(&objectAttributes, &basicInfo); RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); if (!NT_SUCCESS(status)) return FALSE; if (basicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) return FALSE; return TRUE; } /** * Parses a command line string. If the string does not contain quotation marks around the file name * part, the function determines the file name to use. * * \param CommandLine The command line string. * \param FileName A variable which receives the part of \a CommandLine that contains the file name. * \param Arguments A variable which receives the part of \a CommandLine that contains the * arguments. * \param FullFileName A variable which receives the full path and file name. This may be NULL if * the file was not found. */ BOOLEAN PhParseCommandLineFuzzy( _In_ PPH_STRINGREF CommandLine, _Out_ PPH_STRINGREF FileName, _Out_ PPH_STRINGREF Arguments, _Out_opt_ PPH_STRING *FullFileName ) { static PH_STRINGREF whitespace = PH_STRINGREF_INIT(L" \t"); PH_STRINGREF commandLine; PH_STRINGREF temp; PH_STRINGREF currentPart; PH_STRINGREF remainingPart; WCHAR buffer[MAX_PATH]; WCHAR originalChar; commandLine = *CommandLine; PhTrimStringRef(&commandLine, &whitespace, 0); if (commandLine.Length == 0) { PhInitializeEmptyStringRef(FileName); PhInitializeEmptyStringRef(Arguments); if (FullFileName) *FullFileName = NULL; return FALSE; } if (*commandLine.Buffer == '"') { PH_STRINGREF arguments; PhSkipStringRef(&commandLine, sizeof(WCHAR)); // Find the matching quote character and we have our file name. if (!PhSplitStringRefAtChar(&commandLine, '"', &commandLine, &arguments)) { PhSkipStringRef(&commandLine, -(LONG_PTR)sizeof(WCHAR)); // Unskip the initial quote character *FileName = commandLine; PhInitializeEmptyStringRef(Arguments); if (FullFileName) *FullFileName = NULL; return FALSE; } PhTrimStringRef(&arguments, &whitespace, PH_TRIM_START_ONLY); *FileName = commandLine; *Arguments = arguments; if (FullFileName) { PPH_STRING tempCommandLine; tempCommandLine = PhCreateString2(&commandLine); if (PhpSearchFilePath(tempCommandLine->Buffer, L".exe", buffer)) { *FullFileName = PhCreateString(buffer); } else { *FullFileName = NULL; } PhDereferenceObject(tempCommandLine); } return TRUE; } // Try to find an existing executable file, starting with the first part of the command line and // successively restoring the rest of the command line. // For example, in "C:\Program Files\Internet Explorer\iexplore", we try to match: // * "C:\Program" // * "C:\Program Files\Internet" // * "C:\Program Files\Internet " // * "C:\Program Files\Internet " // * "C:\Program Files\Internet Explorer\iexplore" // // Note that we do not trim whitespace in each part because filenames can contain trailing // whitespace before the extension (e.g. "Internet .exe"). temp.Buffer = PhAllocate(commandLine.Length + sizeof(WCHAR)); memcpy(temp.Buffer, commandLine.Buffer, commandLine.Length); temp.Buffer[commandLine.Length / sizeof(WCHAR)] = 0; temp.Length = commandLine.Length; remainingPart = temp; while (remainingPart.Length != 0) { BOOLEAN found; BOOLEAN result; found = PhSplitStringRefAtChar(&remainingPart, ' ', ¤tPart, &remainingPart); if (found) { originalChar = *(remainingPart.Buffer - 1); *(remainingPart.Buffer - 1) = 0; } result = PhpSearchFilePath(temp.Buffer, L".exe", buffer); if (found) { *(remainingPart.Buffer - 1) = originalChar; } if (result) { FileName->Buffer = commandLine.Buffer; FileName->Length = ((PCHAR)currentPart.Buffer - (PCHAR)temp.Buffer) + currentPart.Length; PhTrimStringRef(&remainingPart, &whitespace, PH_TRIM_START_ONLY); *Arguments = remainingPart; if (FullFileName) *FullFileName = PhCreateString(buffer); PhFree(temp.Buffer); return TRUE; } } PhFree(temp.Buffer); *FileName = *CommandLine; PhInitializeEmptyStringRef(Arguments); if (FullFileName) *FullFileName = NULL; return FALSE; }