Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ add_library(lvt_avalonia_plugin SHARED
)
target_compile_definitions(lvt_avalonia_plugin PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_include_directories(lvt_avalonia_plugin PRIVATE src)
target_link_libraries(lvt_avalonia_plugin PRIVATE ole32 version)
target_link_libraries(lvt_avalonia_plugin PRIVATE WIL::WIL ole32 version)
set_target_properties(lvt_avalonia_plugin PROPERTIES
OUTPUT_NAME "lvt_avalonia_plugin"
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:lvt>/plugins
Expand All @@ -253,6 +253,7 @@ add_library(lvt_avalonia_tap SHARED
src/plugin_avalonia/avalonia_tap_native.def
)
target_compile_definitions(lvt_avalonia_tap PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_link_libraries(lvt_avalonia_tap PRIVATE WIL::WIL)
set_property(TARGET lvt_avalonia_tap PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_target_properties(lvt_avalonia_tap PROPERTIES
OUTPUT_NAME "lvt_avalonia_tap_${TAP_ARCH_SUFFIX}"
Expand Down Expand Up @@ -289,7 +290,7 @@ add_library(lvt_chromium_plugin SHARED
)
target_compile_definitions(lvt_chromium_plugin PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_include_directories(lvt_chromium_plugin PRIVATE src)
target_link_libraries(lvt_chromium_plugin PRIVATE nlohmann_json::nlohmann_json ole32 version)
target_link_libraries(lvt_chromium_plugin PRIVATE WIL::WIL nlohmann_json::nlohmann_json ole32 version)
set_target_properties(lvt_chromium_plugin PROPERTIES
OUTPUT_NAME "lvt_chromium_plugin"
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:lvt>/plugins
Expand All @@ -300,7 +301,7 @@ add_executable(lvt_chromium_host
src/plugin_chromium/chromium_host.cpp
)
target_compile_definitions(lvt_chromium_host PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_link_libraries(lvt_chromium_host PRIVATE advapi32)
target_link_libraries(lvt_chromium_host PRIVATE WIL::WIL advapi32)
set_target_properties(lvt_chromium_host PROPERTIES
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:lvt>/plugins/chromium
)
Expand Down
24 changes: 13 additions & 11 deletions src/plugin_avalonia/avalonia_tap_native.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Avalonia apps are always .NET Core/.NET 5+, so only the hostfxr path is needed.

#include <Windows.h>
#include <wil/resource.h>
#include <string>
#include <cstdio>

Expand Down Expand Up @@ -44,17 +45,16 @@ static std::wstring ReadPipeName() {
std::wstring dir = GetDllDirectory();
std::wstring path = dir + L"\\lvt_avalonia_pipe.txt";

HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
wil::unique_hfile hFile(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, 0, nullptr));
if (!hFile) {
LogMsg("Failed to open pipe name file: %lu", GetLastError());
return {};
}

char buf[256]{};
DWORD bytesRead = 0;
ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, nullptr);
CloseHandle(hFile);
ReadFile(hFile.get(), buf, sizeof(buf) - 1, &bytesRead, nullptr);

int wlen = MultiByteToWideChar(CP_UTF8, 0, buf, bytesRead, nullptr, 0);
std::wstring result(wlen, L'\0');
Expand Down Expand Up @@ -87,18 +87,21 @@ static bool TryNetCore(const std::wstring& assemblyPath, const std::wstring& pip
std::wstring dotnetDir = std::wstring(progFiles) + L"\\dotnet\\host\\fxr";

WIN32_FIND_DATAW fd;
HANDLE hFind = FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd);
wil::unique_hfind hFind(FindFirstFileW((dotnetDir + L"\\*").c_str(), &fd));
std::wstring latestFxr;
if (hFind != INVALID_HANDLE_VALUE) {
if (hFind) {
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && fd.cFileName[0] != L'.') {
latestFxr = dotnetDir + L"\\" + fd.cFileName + L"\\hostfxr.dll";
}
} while (FindNextFileW(hFind, &fd));
FindClose(hFind);
} while (FindNextFileW(hFind.get(), &fd));
}

if (!latestFxr.empty()) {
// Intentionally kept resident (raw, not scope-owned): hostfxr pins the
// hosted CoreCLR resolver for the lifetime of this injected process.
// Freeing it on scope exit would diverge from the original behavior and
// risk unloading a host library out from under the running runtime.
hHostfxr = LoadLibraryW(latestFxr.c_str());
LogMsg("Loaded hostfxr from: %ls -> %p", latestFxr.c_str(), hHostfxr);
}
Expand Down Expand Up @@ -218,8 +221,7 @@ BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
DisableThreadLibraryCalls(hMod);
LogMsg("DllMain: DLL_PROCESS_ATTACH");

HANDLE hThread = CreateThread(nullptr, 0, WorkerThread, reinterpret_cast<LPVOID>(hMod), 0, nullptr);
if (hThread) CloseHandle(hThread);
wil::unique_handle hThread(CreateThread(nullptr, 0, WorkerThread, reinterpret_cast<LPVOID>(hMod), 0, nullptr));
}
return TRUE;
}
Expand Down
74 changes: 32 additions & 42 deletions src/plugin_avalonia/lvt_avalonia_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <Psapi.h>
#include <objbase.h>
#include <sddl.h>
#include <wil/resource.h>
#include <cstdio>
#include <cstring>
#include <string>
Expand Down Expand Up @@ -125,48 +126,43 @@ static bool write_pipe_name_file(const std::wstring& dir, const std::wstring& pi
}

static bool inject_dll(DWORD pid, const std::wstring& dllPath) {
HANDLE proc = OpenProcess(
wil::unique_handle proc(OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION,
FALSE, pid);
FALSE, pid));
if (!proc) {
DebugLog("failed to open target process %lu (error %lu)", pid, GetLastError());
return false;
}

size_t pathBytes = (dllPath.size() + 1) * sizeof(wchar_t);
void* remoteMem = VirtualAllocEx(proc, nullptr, pathBytes, MEM_COMMIT, PAGE_READWRITE);
void* remoteMem = VirtualAllocEx(proc.get(), nullptr, pathBytes, MEM_COMMIT, PAGE_READWRITE);
if (!remoteMem) {
DebugLog("VirtualAllocEx failed (error %lu)", GetLastError());
CloseHandle(proc);
return false;
}

if (!WriteProcessMemory(proc, remoteMem, dllPath.c_str(), pathBytes, nullptr)) {
if (!WriteProcessMemory(proc.get(), remoteMem, dllPath.c_str(), pathBytes, nullptr)) {
DebugLog("WriteProcessMemory failed (error %lu)", GetLastError());
VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE);
CloseHandle(proc);
VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE);
return false;
}

auto loadLibAddr = reinterpret_cast<LPTHREAD_START_ROUTINE>(
GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));

HANDLE thread = CreateRemoteThread(
proc, nullptr, 0, loadLibAddr, remoteMem, 0, nullptr);
wil::unique_handle thread(CreateRemoteThread(
proc.get(), nullptr, 0, loadLibAddr, remoteMem, 0, nullptr));
if (!thread) {
DebugLog("CreateRemoteThread failed (error %lu)", GetLastError());
VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE);
CloseHandle(proc);
VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE);
return false;
}

WaitForSingleObject(thread, 5000);
WaitForSingleObject(thread.get(), 5000);

DWORD exitCode = 0;
GetExitCodeThread(thread, &exitCode);
CloseHandle(thread);
VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE);
CloseHandle(proc);
GetExitCodeThread(thread.get(), &exitCode);
VirtualFreeEx(proc.get(), remoteMem, 0, MEM_RELEASE);

if (exitCode == 0) {
DebugLog("LoadLibraryW failed in target process");
Expand Down Expand Up @@ -195,15 +191,14 @@ __declspec(dllexport) LvtPluginInfo* lvt_plugin_info(void) {
__declspec(dllexport) int lvt_detect_framework(DWORD pid, HWND /*hwnd*/, LvtFrameworkDetection* out) {
if (!out) return 0;

HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
wil::unique_handle proc(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
if (!proc) return 0;

auto avaloniaPath = get_module_path(proc, L"Avalonia.Base.dll");
auto avaloniaPath = get_module_path(proc.get(), L"Avalonia.Base.dll");
if (avaloniaPath.empty()) {
// Try alternative: some older versions may just be "Avalonia.dll"
avaloniaPath = get_module_path(proc, L"Avalonia.dll");
avaloniaPath = get_module_path(proc.get(), L"Avalonia.dll");
}
CloseHandle(proc);

if (avaloniaPath.empty()) return 0;

Expand Down Expand Up @@ -258,32 +253,33 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid,
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
PSECURITY_DESCRIPTOR rawSd = nullptr;
ConvertStringSecurityDescriptorToSecurityDescriptorW(
L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, nullptr);
L"D:(A;;GRGW;;;WD)(A;;GRGW;;;AC)", SDDL_REVISION_1, &rawSd, nullptr);
wil::unique_hlocal securityDescriptor(rawSd);
sa.lpSecurityDescriptor = securityDescriptor.get();

HANDLE pipe = CreateNamedPipeW(
wil::unique_hfile pipe(CreateNamedPipeW(
pipeName.c_str(),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1, 0, 1024 * 1024, 10000, &sa);
LocalFree(sa.lpSecurityDescriptor);
1, 0, 1024 * 1024, 10000, &sa));

if (pipe == INVALID_HANDLE_VALUE) {
if (!pipe) {
DebugLog("failed to create named pipe (error %lu)", GetLastError());
return 0;
}

// Start overlapped connect before injection
OVERLAPPED ov = {};
ov.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
ConnectNamedPipe(pipe, &ov);
wil::unique_event connectEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr));
ov.hEvent = connectEvent.get();
ConnectNamedPipe(pipe.get(), &ov);
DWORD connectErr = GetLastError();

// Inject TAP DLL
if (!inject_dll(pid, tapDll)) {
CancelIo(pipe);
CloseHandle(ov.hEvent);
CloseHandle(pipe);
CancelIo(pipe.get());
return 0;
}

Expand All @@ -293,36 +289,32 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid,
if (connectErr == ERROR_IO_PENDING) {
if (WaitForSingleObject(ov.hEvent, 15000) != WAIT_OBJECT_0) {
DebugLog("TAP DLL did not connect (timeout)");
CancelIo(pipe);
CloseHandle(ov.hEvent);
CloseHandle(pipe);
CancelIo(pipe.get());
return 0;
}
} else if (connectErr != ERROR_PIPE_CONNECTED) {
DebugLog("ConnectNamedPipe failed (error %lu)", connectErr);
CloseHandle(ov.hEvent);
CloseHandle(pipe);
return 0;
}
CloseHandle(ov.hEvent);

// Read all data
std::string data;
char buf[4096];
DWORD bytesRead = 0;
OVERLAPPED readOv = {};
readOv.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
wil::unique_event readEvent(CreateEventW(nullptr, TRUE, FALSE, nullptr));
readOv.hEvent = readEvent.get();
for (;;) {
ResetEvent(readOv.hEvent);
BOOL ok = ReadFile(pipe, buf, sizeof(buf), &bytesRead, &readOv);
BOOL ok = ReadFile(pipe.get(), buf, sizeof(buf), &bytesRead, &readOv);
if (!ok) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
if (WaitForSingleObject(readOv.hEvent, 15000) != WAIT_OBJECT_0) {
CancelIo(pipe);
CancelIo(pipe.get());
break;
}
if (!GetOverlappedResult(pipe, &readOv, &bytesRead, FALSE) || bytesRead == 0)
if (!GetOverlappedResult(pipe.get(), &readOv, &bytesRead, FALSE) || bytesRead == 0)
break;
} else {
break;
Expand All @@ -332,8 +324,6 @@ __declspec(dllexport) int lvt_enrich_tree(HWND hwnd, DWORD pid,
}
data.append(buf, bytesRead);
}
CloseHandle(readOv.hEvent);
CloseHandle(pipe);

// Clean up sidecar file
DeleteFileW((tapDir + L"\\lvt_avalonia_pipe.txt").c_str());
Expand Down
Loading
Loading