diff --git a/CMakeLists.txt b/CMakeLists.txt index 929228d..0b67c4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,8 @@ include_directories("${PROJECT_BINARY_DIR}") set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) -set(CMAKE_CXX_FLAGS_DEBUG "/W4 /WX /MDd /EHsc /Zi") -set(CMAKE_C_FLAGS_DEBUG "/W4 /WX /MDd /EHsc /Zi") +set(CMAKE_CXX_FLAGS_DEBUG "/W4 /WX /Od /MDd /EHsc /Zi") +set(CMAKE_C_FLAGS_DEBUG "/W4 /WX /Od /MDd /EHsc /Zi") set(CMAKE_CXX_FLAGS_RELEASE "/W4 /WX /O2 /Oi /Ot /Gy /MD /EHsc /MP") set(CMAKE_C_FLAGS_RELEASE "/W4 /WX /O2 /Oi /Ot /Gy /MD /EHsc /MP") @@ -36,4 +36,5 @@ add_definitions(-D_MBCS) add_subdirectory(RpcView) add_subdirectory(RpcDecompiler) -add_subdirectory(RpcCore) \ No newline at end of file +add_subdirectory(RpcCore) +add_subdirectory(RpcDecompileIt) \ No newline at end of file diff --git a/RpcDecompileIt/CMakeLists.txt b/RpcDecompileIt/CMakeLists.txt new file mode 100644 index 0000000..9eac4c7 --- /dev/null +++ b/RpcDecompileIt/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required (VERSION 3.0.2) + +message("[RpcDecompileIt]") + +add_executable(RpcDecompileIt + RpcDecompileIt.cpp + DecompileItRpcStub.cpp + RpcDecompileIt.h +) + + +target_link_libraries(RpcDecompileIt + RpcDecompilerStatic + Rpcrt4.lib + ntdll.lib + Dbghelp.lib +) + +target_include_directories(RpcDecompileIt PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../" +) \ No newline at end of file diff --git a/RpcDecompileIt/DecompileItRpcStub.cpp b/RpcDecompileIt/DecompileItRpcStub.cpp new file mode 100644 index 0000000..23c7b32 --- /dev/null +++ b/RpcDecompileIt/DecompileItRpcStub.cpp @@ -0,0 +1,137 @@ +#include "RpcDecompileIt.h" +#include + +PVOID +DecompileItRpcAlloc( + _In_ size_t Size +) +{ + return malloc(Size); +} + +void +DecompileItRpcFree( + _In_ PVOID pMem +) +{ + free(pMem); +} + +void +DecompileItRpcPrint( + _In_ PVOID Context, + _In_ const char *pText +) +{ + UNREFERENCED_PARAMETER(Context); + + printf(pText); +} + +void +DecompileItRpcDebug( + _In_ const char *pFunction, + _In_ ULONG Line, + _In_ const char *pFormat, + ... +) +{ + va_list Arg; + UNREFERENCED_PARAMETER(pFunction); + UNREFERENCED_PARAMETER(Line); + va_start(Arg, pFormat); + _vcprintf(pFormat, Arg); +} + +bool +DecompileItRpcGetInterfaceName( + _In_ GUID *pIfId, + _Out_ UCHAR *pName, + _Out_ ULONG NameLength +) +{ + HKEY hKey = NULL; + ULONG DataLength; + UCHAR SubKeyName[MAX_PATH]; + RPC_CSTR pUuidString = NULL; + BOOL bResult = FALSE; + + if (UuidToStringA(pIfId, &pUuidString) != RPC_S_OK) goto End; + sprintf_s((char*) SubKeyName, sizeof(SubKeyName), "Interface\\{%s}", pUuidString); + + if (RegOpenKeyExA(HKEY_CLASSES_ROOT, (LPCSTR)SubKeyName, 0, KEY_READ, &hKey) != ERROR_SUCCESS) goto End; + DataLength = NameLength; + if (RegQueryValueExA(hKey, NULL, NULL, NULL, pName, &DataLength) != ERROR_SUCCESS) goto End; + + bResult = TRUE; +End: + if (hKey != NULL) RegCloseKey(hKey); + if (pUuidString != NULL) RpcStringFreeA(&pUuidString); + return (bResult); +} + + +bool __fastcall +DecompileItRpcGetProcessData( + _In_ RpcModuleInfo_T *Context, + _In_ RVA_T Rva, + _Out_ VOID* pBuffer, + _Out_ UINT BufferLength +) +{ + HANDLE hTargetProcess = INVALID_HANDLE_VALUE; + BOOL bResult = FALSE; + VOID* pAddress = NULL; + + RpcModuleInfo_T *DecompileContext = (RpcModuleInfo_T *)Context; + + if ((Context == NULL) || (DecompileContext->Pid == 0)) + { + goto End; + } + + hTargetProcess = OpenProcess( + PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, + FALSE, + DecompileContext->Pid + ); + + if (hTargetProcess == INVALID_HANDLE_VALUE) + { + goto End; + } + + pAddress = (VOID*)(DecompileContext->pModuleBase + Rva); + bResult = ReadProcessMemory( + hTargetProcess, + pAddress, + pBuffer, + BufferLength, + NULL + ); + +End: + if (hTargetProcess != INVALID_HANDLE_VALUE) + { + CloseHandle(hTargetProcess); + } + + return (bResult); +} + + +void +DecompileItInitRpcViewStub +( + _Inout_ RpcViewHelper_T *RpcViewStub, + _In_ PVOID Context +) +{ + RpcViewStub->pContext = &Context; + RpcViewStub->RpcAlloc = (RpcAllocFn_T) &DecompileItRpcAlloc; + RpcViewStub->RpcFree = (RpcFreeFn_T) &DecompileItRpcFree; + RpcViewStub->RpcGetProcessData = (RpcGetProcessDataFn_T)&DecompileItRpcGetProcessData; + RpcViewStub->RpcPrint = &DecompileItRpcPrint; + RpcViewStub->RpcDebug = &DecompileItRpcDebug; + RpcViewStub->RpcGetInterfaceName = (RpcGetInterfaceNameFn_T)&DecompileItRpcGetInterfaceName; +} \ No newline at end of file diff --git a/RpcDecompileIt/RpcDecompileIt.cpp b/RpcDecompileIt/RpcDecompileIt.cpp new file mode 100644 index 0000000..07fab04 --- /dev/null +++ b/RpcDecompileIt/RpcDecompileIt.cpp @@ -0,0 +1,384 @@ +#include "RpcDecompileIt.h" + +#include +#include +#include + +#include "RpcDecompiler/IdlInterface.h" + +#if _WIN64 + #define RPC_CORE_IS_WOW64 false +#else + #define RPC_CORE_IS_WOW64 true +#endif + +bool +DecompileInit( + _Inout_ PDECOMPILE_IT_CTXT Context +) +{ + WCHAR RefModuleName[MAX_PATH]; + HMODULE hMods[1024]; + DWORD ModulesSize; + BOOL bOwnProcessWow64 = false; + BOOL bRemoteProcessWow64 = false; + + HANDLE hProcess = OpenProcess( + PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, + FALSE, + Context -> TargetPID + ); + + if (hProcess==NULL) + { + printf("[x] Could not access the remote process : %d\n", GetLastError()); + return false; + } + Context->hTargetProcess = hProcess; + + IsWow64Process(GetCurrentProcess(), &bOwnProcessWow64); + IsWow64Process(hProcess, &bRemoteProcessWow64); + + if (bOwnProcessWow64 != bRemoteProcessWow64) + { + #define PROCESS_ARCH(bIsWow64) (bIsWow64) ? "Wow64" : "x64" + + printf("[x] Remote process does not have the same arch as own process : %s != %s\n", + PROCESS_ARCH(bRemoteProcessWow64), + PROCESS_ARCH(bOwnProcessWow64) + ); + return false; + } + + + + + + if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &ModulesSize)) + { + return GetLastError(); + } + + // stop at the first if ModuleName is not set + if (!Context->ModuleName) + { + Context->ModuleBaseAddress = (uintptr_t) hMods[0]; + } + else + { + swprintf_s(RefModuleName, sizeof(RefModuleName) / sizeof(WCHAR), L"%hs", Context->ModuleName); + + for (auto ModuleIndex = 0; (unsigned int) ModuleIndex < (ModulesSize / sizeof(HMODULE)); ModuleIndex++ ) + { + WCHAR ModuleName[MAX_PATH]; + + + if (GetModuleBaseNameW( + hProcess, + hMods[ModuleIndex], + ModuleName, + sizeof(ModuleName) / sizeof(WCHAR) + )) + { + if (!_wcsicmp(ModuleName, RefModuleName)) + { + Context->ModuleBaseAddress = (uintptr_t) hMods[ModuleIndex]; + break; + } + } + } + } + + if (!Context->ModuleBaseAddress) + { + return false; + } + + + if (Context->bAbsoluteAddress) + { + Context->DescriptorAddress = Context->DescriptorArg; + Context->DescriptorOffset = Context->DescriptorAddress - Context->ModuleBaseAddress; + + Context->FormatStrAddress = Context->FormatStrArg; + Context->FormatStrOffset = Context->FormatStrAddress - Context->ModuleBaseAddress; + } + else + { + Context->DescriptorOffset = Context->DescriptorArg; + Context->DescriptorAddress = Context->DescriptorOffset + Context->ModuleBaseAddress; + + Context->FormatStrOffset = Context->FormatStrArg; + Context->FormatStrAddress = Context->FormatStrOffset + Context->ModuleBaseAddress; + } + + return true; +} + + + +bool +ReadRpcInterface( + _In_ RpcViewHelper_T *RpcViewHelper, + _In_ RpcModuleInfo_T ModuleInfo, + _In_ MIDL_STUB_DESC MidlDescription, + _Out_ RPC_IF_ID *RpcInterface +) +{ + unsigned int RpcInterfaceInformationStructSize = 0; + RPC_CLIENT_INTERFACE RpcClientInterface = {0}; + + + if (!RpcInterface || !RpcViewHelper) + { + return false; + } + + // Read RPC_CLIENT_INTERFACE.size to know how many bytes we need to read + if (!RpcViewHelper->RpcGetProcessData( + &ModuleInfo, + (RVA_T) ( (uintptr_t) MidlDescription.RpcInterfaceInformation - ModuleInfo.pModuleBase), + &RpcInterfaceInformationStructSize, + sizeof(RpcClientInterface.Length) + )) + { + return false; + } + + + switch (RpcInterfaceInformationStructSize) + { + // RPC_SERVER_INTERFACE and RPC_CLIENT_INTERFACE are pretty much the same structure + //case sizeof(RPC_SERVER_INTERFACE): + case sizeof(RPC_CLIENT_INTERFACE): + + // Read the full structure + if (!RpcViewHelper->RpcGetProcessData( + &ModuleInfo, + (RVA_T)((uintptr_t)MidlDescription.RpcInterfaceInformation - ModuleInfo.pModuleBase), + &RpcClientInterface, + sizeof(RPC_CLIENT_INTERFACE) + )) + { + return false; + } + + RpcInterface->Uuid = RpcClientInterface.InterfaceId.SyntaxGUID; + RpcInterface->VersMajor = RpcClientInterface.InterfaceId.SyntaxVersion.MajorVersion; + RpcInterface->VersMinor= RpcClientInterface.InterfaceId.SyntaxVersion.MinorVersion; + + break; + + default: + return false; + } + + return true; +} + +bool +DecompileIt( + _In_ DECOMPILE_IT_CTXT Context +) +{ + //uintptr_t DecompilerHelperAddr = NULL; + //RpcDecompilerHelper_T* DecompilerHelper = NULL; + MIDL_STUB_DESC MidlStubDesc; + size_t StubDescBytesRead; + RPC_IF_ID RpcInterfaceId; + RpcDecompilerInfo_T RpcDecompilerInfoStub; + RpcDecompilerCtxt_T DecompilerStubContext; + RpcViewHelper_T RpcViewHelperStub; + + RpcModuleInfo_T ModuleInfoStub = { + /*Pid = */ Context.TargetPID, + /*pModuleBase = */ Context.ModuleBaseAddress + }; + + + // init RpcView helper stubs in order to use RpcGetProcessData + DecompileItInitRpcViewStub(&RpcViewHelperStub, (PVOID) &Context); + + // Read MIDL_STUB_DESC and RPC_INTERFACE structures from remote target + if (!ReadProcessMemory( + Context.hTargetProcess, + (LPCVOID)Context.DescriptorAddress, + &MidlStubDesc, + sizeof(MidlStubDesc), + (SIZE_T*) &StubDescBytesRead + )) + { + printf("[x] Could not read MIDL_STUB_DESC structure from desc-offset rva.\n"); + return false; + } + + if (!ReadRpcInterface( + &RpcViewHelperStub, + ModuleInfoStub, + MidlStubDesc, + &RpcInterfaceId + )) + { + printf("[x] Could not retrieve a RPC_INTERFACE for the MIDL_STUB_DESC structure.\n"); + return false; + } + + + // Init stubs for RpcDecompiler + RpcDecompilerInfoStub.ppProcNameTable = new WCHAR*[Context.NumberOfProcedures]; + for (size_t i = 0; i < Context.NumberOfProcedures; i++) + { + RpcDecompilerInfoStub.ppProcNameTable[i] = NULL; + } + +#ifdef _AMD64_ + RpcDecompilerInfoStub.bIs64Bits = TRUE; +#else + RpcDecompilerInfoStub.bIs64Bits = FALSE; +#endif + + RpcDecompilerInfoStub.pFormatStringOffsetTable = Context.FormatStrOffsets; + RpcDecompilerInfoStub.pProcFormatString = (RVA_T) Context.FormatStrOffset; + RpcDecompilerInfoStub.pTypeFormatString = (RVA_T) ((uintptr_t)MidlStubDesc.pFormatTypes - Context.ModuleBaseAddress); + + DecompilerStubContext.pRpcDecompilerInfo = &RpcDecompilerInfoStub; + DecompilerStubContext.pRpcModuleInfo = &ModuleInfoStub; + DecompilerStubContext.pRpcViewHelper = &RpcViewHelperStub; + + + // is it a 64 bits application ? + is64B = RpcDecompilerInfoStub.bIs64Bits; + + // robust flags case + robustFlagWasSet = (MidlStubDesc.Version >= NDR_VERSION_5_2) ? TRUE : FALSE; + + + // Decode function + std::string IfaceName("DecompileItInterface"); + IdlInterface Interface(IfaceName, RpcInterfaceId, Context.NumberOfProcedures); + if (DS_SUCCESS == Interface.decode((PVOID)&DecompilerStubContext)) + { + std::cout << Interface; + + } + + return true; +} + +bool +DecompileUninit( + _In_ DECOMPILE_IT_CTXT Context +) +{ + if (Context.hTargetProcess == INVALID_HANDLE_VALUE) + { + CloseHandle(Context.hTargetProcess); + } + + return true; +} + + +int main(int argc, char* argv[]) +{ + DECOMPILE_IT_CTXT Context = {0}; + char* EndPtr; + bool status; + + if (argc < 7) + { + printf("Usage: %s --pid PID [--module MODULE] --descriptor DESC_OFFSET --format-str FORMAT_STRING_OFFSET [--absolute] [--format-str-offsets OFF1,OFF2,OFFn]\n", argv[0]); + printf(" --pid : PID of the target process. %s must be able to open a handle to read the target process memory.\n", argv[0]); + printf(" --module : module name to read memory from. If not set, %s read the target executable own module. Ignored if --absolute is set.\n", argv[0]); + printf(" --descriptor : offset to the rpc header descriptor for the interface. If --absolute is set, --descriptor is interpreted as a virtual address.\n"); + printf(" --format-str : offset to the rpc format string for the chosen proc. If --absolute is set, --format-str is interpreted as a virtual address.\n"); + printf(" --absolute : treat descriptor and format-str as absolute virtual addresses instead of offsets.\n"); + printf(" --format-str-offsets : offsets within the format string for the various procedures (default:0).\n"); + + return 0; + } + + + bool bFormatStrOffsetsProvided = false; + for (auto ArgIndex = 0; ArgIndex < argc; ArgIndex++) + { + char *CurrentArgument = argv[ArgIndex]; + + if (!_stricmp(CurrentArgument, "--pid")) + { + Context.TargetPID = (DWORD) strtoumax(argv[ArgIndex + 1], &EndPtr, 10); + } + if (!_stricmp(CurrentArgument, "--module")) + { + Context.ModuleName = argv[ArgIndex + 1]; + } + else if (!_stricmp(CurrentArgument, "--descriptor")) + { + Context.DescriptorArg = (size_t) strtoumax(argv[ArgIndex + 1], &EndPtr, 16); + } + else if (!_stricmp(CurrentArgument, "--format-str")) + { + Context.FormatStrArg = (size_t) strtoumax(argv[ArgIndex + 1], &EndPtr, 16); + } + else if (!_stricmp(CurrentArgument, "--absolute")) + { + Context.bAbsoluteAddress = true; + } + else if (!_stricmp(CurrentArgument, "--format-str-offsets")) + { + bFormatStrOffsetsProvided = true; + + // parsing offsets using STL since strtok sucks. + std::string offsets_str(argv[ArgIndex + 1]); + std::vector offsets; + + size_t last_pos = 0; + size_t pos = offsets_str.find(','); + + while (pos != std::string::npos) { + offsets.push_back(offsets_str.substr(last_pos, pos - last_pos)); + last_pos = ++pos; + pos = offsets_str.find(',', pos); + + if (pos == std::string::npos) + { + offsets.push_back( + offsets_str.substr(last_pos, offsets_str.length()) + ); + } + + } + + Context.NumberOfProcedures = offsets.size(); + Context.FormatStrOffsets = new uint16_t[Context.NumberOfProcedures]; + + for (size_t i = 0; i < offsets.size(); i++) + { + Context.FormatStrOffsets[i] = (uint16_t)strtoumax(offsets[i].c_str(), &EndPtr, 0); + } + + } + } + + if (!bFormatStrOffsetsProvided) + { + Context.NumberOfProcedures = 1; + Context.FormatStrOffsets = new uint16_t[1]; + Context.FormatStrOffsets[0] = 0; + } + + + if (!DecompileInit( + &Context + )) + { + printf("Could not init the DecompileIt context : %d.\n", GetLastError()); + return -1; + } + + status = DecompileIt(Context); + + + DecompileUninit(Context); + return !status; +} \ No newline at end of file diff --git a/RpcDecompileIt/RpcDecompileIt.h b/RpcDecompileIt/RpcDecompileIt.h new file mode 100644 index 0000000..177b2e1 --- /dev/null +++ b/RpcDecompileIt/RpcDecompileIt.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +#include "RpcCore\RpcCore.h" +#include "RpcCommon\RpcView.h" +#include "RpcDecompiler\RpcDecompiler.h" + +#pragma warning ( push ) +#pragma warning ( disable: 4201 ) +typedef struct _DECOMPILE_IT_CTXT { + + DWORD TargetPID; + HANDLE hTargetProcess; + + char* ModuleName; + uintptr_t ModuleBaseAddress; + + bool bAbsoluteAddress; + + union + { + uintptr_t DescriptorArg; + + struct { + size_t DescriptorOffset; + uintptr_t DescriptorAddress; + }; + }; + + union + { + uintptr_t FormatStrArg; + + struct { + size_t FormatStrOffset; + uintptr_t FormatStrAddress; + }; + }; + + uint16_t *FormatStrOffsets; + + size_t NumberOfProcedures; + +} DECOMPILE_IT_CTXT, *PDECOMPILE_IT_CTXT; +#pragma warning ( pop ) + + +#pragma region RpcViewStub + +void +DecompileItInitRpcViewStub +( + _Inout_ RpcViewHelper_T *RpcViewStub, + _In_ PVOID Context +); + + +// #define ASSIGN_RPC_VIEW_STUB(RpcViewHelperStub, Context) \ +// RpcViewHelper_T RpcViewHelperStub; \ +// DecompileItInitRpcViewStub(&RpcViewHelperStub, (PVOID) Context) + + +#pragma endregion RpcViewStub diff --git a/RpcDecompiler/CMakeLists.txt b/RpcDecompiler/CMakeLists.txt index b10c0cd..a1f99f6 100644 --- a/RpcDecompiler/CMakeLists.txt +++ b/RpcDecompiler/CMakeLists.txt @@ -2,36 +2,41 @@ cmake_minimum_required (VERSION 3.0.2) message("[RpcDecompiler]") -add_library(RpcDecompiler SHARED - internalComplexTypesArrays.cpp - internalComplexTypesArrays.h - InternalComplexTypesMisc.cpp - InternalComplexTypesMisc.h - internalComplexTypesPointers.cpp - internalComplexTypesPointers.h - internalComplexTypesStrings.cpp - internalComplexTypesStrings.h - internalComplexTypesStructs.cpp - internalComplexTypesStructs.h - internalComplexTypesUnions.cpp - internalComplexTypesUnions.h - internalRpcDecompiler.cpp - internalRpcDecompiler.h - InternalRpcDecompTypeDefs.cpp - internalRpcDecompTypeDefs.h - internalRpcDecompTypeDefsNew.h - internalRpcUtils.h - InternalsRpcUtils.cpp - internalTypeTools.cpp - internalTypeTools.h - RpcDecompiler.cpp - RpcDecompiler.h - IdlFunction.h - IdlFunction.cpp - IdlType.h - IdlType.cpp - IdlInterface.h - IdlInterface.cpp - RpcDecompilerResource.rc) + +set(RpcDecompilerFiles + internalComplexTypesArrays.cpp + internalComplexTypesArrays.h + InternalComplexTypesMisc.cpp + InternalComplexTypesMisc.h + internalComplexTypesPointers.cpp + internalComplexTypesPointers.h + internalComplexTypesStrings.cpp + internalComplexTypesStrings.h + internalComplexTypesStructs.cpp + internalComplexTypesStructs.h + internalComplexTypesUnions.cpp + internalComplexTypesUnions.h + internalRpcDecompiler.cpp + internalRpcDecompiler.h + InternalRpcDecompTypeDefs.cpp + internalRpcDecompTypeDefs.h + internalRpcDecompTypeDefsNew.h + internalRpcUtils.h + InternalsRpcUtils.cpp + internalTypeTools.cpp + internalTypeTools.h + RpcDecompiler.cpp + RpcDecompiler.h + IdlFunction.h + IdlFunction.cpp + IdlType.h + IdlType.cpp + IdlInterface.h + IdlInterface.cpp + RpcDecompilerResource.rc +) + +add_library(RpcDecompiler SHARED ${RpcDecompilerFiles}) +add_library(RpcDecompilerStatic ${RpcDecompilerFiles}) diff --git a/RpcDecompiler/RpcDecompiler.h b/RpcDecompiler/RpcDecompiler.h index caea6e5..69dd3a2 100644 --- a/RpcDecompiler/RpcDecompiler.h +++ b/RpcDecompiler/RpcDecompiler.h @@ -24,7 +24,7 @@ typedef struct _RpcDecompilerInfo_T{ UINT64 pModuleBase; UINT NDRVersion; UINT MIDLVersion; - UINT NDRFags; + UINT NDRFags; // Typo here ! UINT NumberOfProcedures; RVA_T* ppProcAddressTable; //A table containing the address of each function RVA_T* ppDispatchProcAddressTable; diff --git a/RpcView/CMakeLists.txt b/RpcView/CMakeLists.txt index 0d8855f..4755384 100644 --- a/RpcView/CMakeLists.txt +++ b/RpcView/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_AUTOMOC ON) # Find the QtWidgets library # set CMAKE_PREFIX_PATH=C:\Qt\Qt5.9.1\5.9.1\msvc2015 or c:\Qt\Qt5.9.1\5.9.1\msvc2015_64 find_package(Qt5Widgets) +find_package(Qt5WinExtras) add_executable( RpcView @@ -47,4 +48,4 @@ add_executable( # C4091 in ShlObj.h and Dbghelp.h # C4127 (conditional expression is constant) in qt headers set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "/wd4091 /wd4127") -target_link_libraries(RpcView Qt5::Widgets $ENV{CMAKE_PREFIX_PATH}/lib/Qt5WinExtras.lib) \ No newline at end of file +target_link_libraries(RpcView Qt5::Widgets Qt5::WinExtras) \ No newline at end of file