mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-11-05 21:56:33 +00:00
feat: find and enumerate the windows of other processes
This commit is contained in:
1 parent
ceeaffdf42
commit
84dfa0e477
9 files changed
+209
-9
No files matched your search
@@ -3363,6 +3363,14 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
|
||||
add_custom_target(dialog_font_fixture${width} ALL DEPENDS ${dialog_font_client})
|
||||
add_test(NAME native_dialog_font_x${width} COMMAND raven --run-process --execution=native ${dialog_font_client})
|
||||
set_tests_properties(native_dialog_font_x${width} PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION "Native entry returned 0 ")
|
||||
set(cross_window_client ${CMAKE_CURRENT_BINARY_DIR}/cross-window${width}.exe)
|
||||
add_custom_command(OUTPUT ${cross_window_client}
|
||||
COMMAND ${webservices_compiler} -nostdlib -O1 -fno-builtin -fno-stack-protector -Wl,--entry,${webservices_entry},--no-insert-timestamp
|
||||
-o ${cross_window_client} ${LOADER_SOURCES}/cross_window_client.c -luser32 -lkernel32
|
||||
DEPENDS ${LOADER_SOURCES}/cross_window_client.c VERBATIM)
|
||||
add_custom_target(cross_window_fixture${width} ALL DEPENDS ${cross_window_client})
|
||||
add_test(NAME native_cross_window_x${width} COMMAND raven --run-process --execution=native --timeout-ms=30000 ${cross_window_client})
|
||||
set_tests_properties(native_cross_window_x${width} PROPERTIES TIMEOUT 40 PASS_REGULAR_EXPRESSION "Native entry returned 0 ")
|
||||
# mlang comes with the installer components, whose import library the client links against.
|
||||
if(width EQUAL 64)
|
||||
set(mlang_dir ${RAVEN_INSTALLER_COMPONENT_DIR})
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <signal.h>
|
||||
@@ -115,7 +117,8 @@ void publish_window_traits(uint64_t window, const ForeignWindowTraits &traits)
|
||||
const auto temporary = directory / (window_name(window) + ".traits." + std::to_string(::getpid()));
|
||||
{
|
||||
std::ofstream output(temporary, std::ios::trunc);
|
||||
output << traits.style << ' ' << traits.ex_style << '\n' << traits.class_name;
|
||||
output << traits.style << ' ' << traits.ex_style << ' ' << traits.parent << ' ' << (traits.visible ? 1 : 0) << '\n' << traits.class_name << '\n'
|
||||
<< traits.title;
|
||||
}
|
||||
std::error_code error;
|
||||
std::filesystem::rename(temporary, final_path, error);
|
||||
@@ -151,13 +154,40 @@ std::optional<ForeignWindowTraits> foreign_window_traits(uint64_t window)
|
||||
return std::nullopt;
|
||||
std::ifstream input(window_directory() / (window_name(window) + ".traits"));
|
||||
ForeignWindowTraits traits;
|
||||
if (!(input >> traits.style >> traits.ex_style))
|
||||
std::string first;
|
||||
if (!std::getline(input, first))
|
||||
return std::nullopt;
|
||||
input.ignore(1);
|
||||
std::istringstream fields(first);
|
||||
int visible = 0;
|
||||
if (!(fields >> traits.style >> traits.ex_style >> traits.parent >> visible))
|
||||
return std::nullopt;
|
||||
traits.visible = visible != 0;
|
||||
std::getline(input, traits.class_name);
|
||||
// The title is the rest of the file, line breaks and all.
|
||||
traits.title.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
|
||||
return traits;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> foreign_windows()
|
||||
{
|
||||
std::vector<uint64_t> found;
|
||||
std::error_code error;
|
||||
for (const auto &entry : std::filesystem::directory_iterator(window_directory(), error)) {
|
||||
const auto path = entry.path();
|
||||
if (path.extension() != ".window")
|
||||
continue;
|
||||
const auto stem = path.stem().string();
|
||||
char *end = nullptr;
|
||||
const auto low = std::strtoull(stem.c_str(), &end, 16);
|
||||
if (stem.size() != 8 || !end || *end)
|
||||
continue;
|
||||
// A handle keeps its process tag in the bits above the low word the name carries; window handles fit in those 32 bits.
|
||||
if (foreign_record(low))
|
||||
found.push_back(low);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> foreign_window_owner(uint64_t window)
|
||||
{
|
||||
const auto record = foreign_record(window);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace raven {
|
||||
|
||||
@@ -25,13 +26,19 @@ std::optional<std::pair<uint32_t, uint32_t>> foreign_window_size(uint64_t window
|
||||
// The process that owns a window another live process of the prefix created.
|
||||
std::optional<uint32_t> foreign_window_owner(uint64_t window);
|
||||
|
||||
// What another process may ask of a window it does not own: its styles and the name of its class.
|
||||
// What another process may ask of a window it does not own: its styles, its parent, whether it is shown, the name of its class and
|
||||
// its title (both UTF-8).
|
||||
struct ForeignWindowTraits {
|
||||
uint32_t style = 0, ex_style = 0;
|
||||
std::string class_name;
|
||||
uint64_t parent = 0;
|
||||
bool visible = false;
|
||||
std::string title;
|
||||
};
|
||||
void publish_window_traits(uint64_t window, const ForeignWindowTraits &traits);
|
||||
std::optional<ForeignWindowTraits> foreign_window_traits(uint64_t window);
|
||||
// The windows other live processes of the prefix own, in no particular order.
|
||||
std::vector<uint64_t> foreign_windows();
|
||||
|
||||
// Pixels of a window shared between the process that draws them and the owner: a header page, then 32-bit rows top down.
|
||||
class ForeignSurface {
|
||||
|
||||
@@ -983,6 +983,12 @@ void NativeWindows::register_ext_api(ApiRegistry ®istry)
|
||||
call.ret(0);
|
||||
return;
|
||||
}
|
||||
if (const auto foreign = std::as_const(objects).window(call[0]) ? std::nullopt : foreign_window_traits(call[0])) {
|
||||
const auto text = to_ansi(from_utf8(foreign->title), 1252);
|
||||
call.put_narrow(call[1], text, size_t(call.i32(2)));
|
||||
call.ret(std::min<size_t>(text.size(), size_t(call.i32(2)) - 1));
|
||||
return;
|
||||
}
|
||||
call.ret(send_ansi(call, call[0], 0x0d, call[2], call[1]));
|
||||
});
|
||||
registry.add("TrackPopupMenu", 7, [=, this](ApiCall &call) {
|
||||
|
||||
@@ -187,8 +187,7 @@ void NativeWindows::register_long_api(ApiRegistry ®istry)
|
||||
std::memcpy(bytes.data() + 4, &next, 4);
|
||||
process.write_memory(scratch, bytes);
|
||||
send(call[0], 0x7d, index_word, scratch);
|
||||
if (const auto *changed_window = std::as_const(objects).window(call[0]))
|
||||
publish_window_traits(call[0], {changed_window->style, changed_window->ex_style, objects.window_class(changed_window->class_id)->name});
|
||||
publish_traits(call[0]);
|
||||
if (std::as_const(objects).window(call[0]) && objects.window_visible(call[0])) {
|
||||
invalidate_tree(call[0], true);
|
||||
drawing(call[0]);
|
||||
|
||||
@@ -146,6 +146,10 @@ bool NativeWindows::show_window(uint64_t handle, uint32_t command)
|
||||
window->visible = !hide;
|
||||
window->style = (window->style & ~ws_visible) | (window->visible ? ws_visible : 0);
|
||||
objects.update_window_dcs(window->parent ? window->parent : handle);
|
||||
if (!window->parent) {
|
||||
publish_traits(handle);
|
||||
window = objects.window(handle);
|
||||
}
|
||||
if (show) {
|
||||
// Showing a window paints its whole subtree, so the children's update regions are invalidated with it.
|
||||
if (!objects.invalidate(handle, {0, 0, window->width, window->height}, true))
|
||||
@@ -353,6 +357,21 @@ void NativeWindows::place_window(uint64_t handle, DesktopRect outer, uint64_t in
|
||||
process.unmap_memory(scratch);
|
||||
}
|
||||
|
||||
uint64_t NativeWindows::foreign_top_level(const std::u16string &class_name, bool titled, const std::u16string &title) const
|
||||
{
|
||||
for (const auto handle : foreign_windows()) {
|
||||
const auto traits = foreign_window_traits(handle);
|
||||
if (!traits || traits->parent)
|
||||
continue;
|
||||
if (!class_name.empty() && !equal_fold(from_utf8(traits->class_name), class_name))
|
||||
continue;
|
||||
if (titled && from_utf8(traits->title) != title)
|
||||
continue;
|
||||
return handle;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NativeWindows::register_manager_api(ApiRegistry ®istry)
|
||||
{
|
||||
registry.add("FindWindowW", 2, [this](ApiCall &call) {
|
||||
@@ -371,6 +390,10 @@ void NativeWindows::register_manager_api(ApiRegistry ®istry)
|
||||
call.ret(*root);
|
||||
return;
|
||||
}
|
||||
if (const auto found = foreign_top_level(name, call[1] != 0, title)) {
|
||||
call.ret(found);
|
||||
return;
|
||||
}
|
||||
call.fail(1400);
|
||||
});
|
||||
registry.add("FindWindowA", 2, [this](ApiCall &call) {
|
||||
@@ -390,6 +413,10 @@ void NativeWindows::register_manager_api(ApiRegistry ®istry)
|
||||
call.ret(*root);
|
||||
return;
|
||||
}
|
||||
if (const auto found = foreign_top_level(name, call[1] != 0, title)) {
|
||||
call.ret(found);
|
||||
return;
|
||||
}
|
||||
call.fail(1400);
|
||||
});
|
||||
registry.add("FindWindowExW", 4, [this](ApiCall &call) {
|
||||
@@ -654,7 +681,7 @@ void NativeWindows::register_manager_api(ApiRegistry ®istry)
|
||||
registry.add("WindowFromPoint", 1, [this](ApiCall &call) { call.ret(window_at(int32_t(call[0]), int32_t(call[0] >> 32))); });
|
||||
const auto enumerate = [this](ApiCall &call, const std::vector<uint64_t> &handles, uint64_t procedure, uint64_t parameter) {
|
||||
for (const auto handle : handles) {
|
||||
if (!std::as_const(objects).window(handle))
|
||||
if (!std::as_const(objects).window(handle) && !foreign_window_size(handle))
|
||||
continue;
|
||||
const uint64_t arguments[] = {handle, parameter};
|
||||
const auto result = invoke(procedure, arguments);
|
||||
@@ -668,6 +695,10 @@ void NativeWindows::register_manager_api(ApiRegistry ®istry)
|
||||
registry.add("EnumWindows", 2, [this, enumerate](ApiCall &call) {
|
||||
auto roots = top_level_windows();
|
||||
std::reverse(roots.begin(), roots.end());
|
||||
// The top-level windows of the other processes of the prefix follow, as one desktop holds them all on Windows.
|
||||
for (const auto handle : foreign_windows())
|
||||
if (const auto traits = foreign_window_traits(handle); traits && !traits->parent)
|
||||
roots.push_back(handle);
|
||||
enumerate(call, roots, call[0], call[1]);
|
||||
});
|
||||
registry.add("EnumThreadWindows", 3, [this, enumerate](ApiCall &call) {
|
||||
|
||||
@@ -511,6 +511,15 @@ void NativeWindows::watch_input()
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void NativeWindows::publish_traits(uint64_t handle)
|
||||
{
|
||||
const auto *window=std::as_const(objects).window(handle);
|
||||
if (!window || window->destroying) return;
|
||||
const auto *definition=objects.window_class(window->class_id);
|
||||
publish_window_traits(handle,{window->style,window->ex_style,definition ? definition->name : std::string(),window->parent,window->visible,
|
||||
to_utf8(window->title)});
|
||||
}
|
||||
|
||||
uint64_t NativeWindows::foreign_dc(uint64_t handle)
|
||||
{
|
||||
auto size=foreign_window_size(handle);
|
||||
@@ -1605,6 +1614,8 @@ uint64_t NativeWindows::default_message(std::span<const uint64_t> args)
|
||||
const auto address=message==0x81 ? (narrow ? read(process.read_memory(args[3],48),36,4) :
|
||||
read(process.read_memory(args[3],80),56,8)) : args[3];
|
||||
if (!objects.set_title(args[0],window_text(address))) throw uint32_t(8);
|
||||
// Other processes look top-level windows up by title; a control's text changing often stays out of the shared directory.
|
||||
if (!window->parent) publish_traits(args[0]);
|
||||
if (objects.window_visible(args[0])) present(args[0]);
|
||||
return 1;
|
||||
}
|
||||
@@ -1914,7 +1925,7 @@ uint64_t NativeWindows::create(std::span<const uint64_t> args)
|
||||
if (child) parent_notify(handle,1);
|
||||
if (!std::as_const(objects).window(handle)) return 0;
|
||||
if (const auto *created=std::as_const(objects).window(handle)) publish_window(handle,uint32_t(std::max(created->width,0)),uint32_t(std::max(created->height,0)));
|
||||
if (const auto *created=std::as_const(objects).window(handle)) publish_window_traits(handle,{created->style,created->ex_style,objects.window_class(created->class_id)->name});
|
||||
publish_traits(handle);
|
||||
if (effective_style & 0x10000000U) {
|
||||
auto *window=objects.window(handle);window->visible=true;objects.update_window_dcs(child ? window->parent : handle);
|
||||
if (objects.window_visible(handle)) present(handle);
|
||||
@@ -2648,6 +2659,19 @@ bool NativeWindows::dispatch(std::string_view name)
|
||||
process.write_memory(args[1],rect); result=1;
|
||||
}
|
||||
else if (name=="DefWindowProcW") result=default_message(args);
|
||||
// The title of another process's window comes from what it published; its procedure is not asked.
|
||||
else if (const auto foreign=(name=="GetWindowTextLengthW" || name=="GetWindowTextW") && !std::as_const(objects).window(args[0]) ?
|
||||
foreign_window_traits(args[0]) : std::nullopt) {
|
||||
const auto title=from_utf8(foreign->title);
|
||||
if (name=="GetWindowTextLengthW") result=title.size();
|
||||
else if (int32_t(args[2])>0) {
|
||||
const auto count=std::min<size_t>(title.size(),size_t(uint32_t(args[2]))-1);
|
||||
std::vector<uint8_t> bytes((count+1)*2);
|
||||
std::memcpy(bytes.data(),title.data(),count*2);
|
||||
process.write_memory(args[1],bytes);
|
||||
result=count;
|
||||
}
|
||||
}
|
||||
else if (name=="GetWindowTextLengthW") result=send(args[0],0x0e,0,0);
|
||||
else if (name=="SetWindowTextW") result=send(args[0],0x0c,0,args[1]);
|
||||
else if (name=="GetWindowTextW") {
|
||||
@@ -2833,7 +2857,8 @@ bool NativeWindows::dispatch(std::string_view name)
|
||||
if (withdraw_screen_dc(args[1])) publish_screen_dc(owner);
|
||||
}
|
||||
} else if (name=="IsWindowVisible") {
|
||||
result=objects.window_visible(args[0]);
|
||||
if (std::as_const(objects).window(args[0])) result=objects.window_visible(args[0]);
|
||||
else if (const auto foreign=foreign_window_traits(args[0])) result=foreign->visible;
|
||||
} else if (name=="ShowWindow") {
|
||||
result=show_window(args[0],uint32_t(args[1]));
|
||||
} else if (name=="UpdateWindow") {
|
||||
|
||||
@@ -185,6 +185,10 @@ private:
|
||||
struct ForeignTarget { std::shared_ptr<ForeignSurface> surface; uint64_t bitmap=0; };
|
||||
std::map<uint64_t,ForeignTarget> foreign_targets;
|
||||
uint64_t foreign_dc(uint64_t window);
|
||||
// Tells the other processes of the prefix what they may ask of a window: styles, parent, visibility, class and title.
|
||||
void publish_traits(uint64_t window);
|
||||
// A top-level window of another process of the prefix with this class (any when empty) and, when asked, this title; 0 when none.
|
||||
uint64_t foreign_top_level(const std::u16string &class_name, bool titled, const std::u16string &title) const;
|
||||
std::map<uint64_t,uint32_t> foreign_frames;
|
||||
// Windows that received a frame from another process with something other than black on it.
|
||||
std::set<uint64_t> foreign_content;
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// The windows of one process are visible to another of the same prefix: a child finds its parent's window by class and title, reads
|
||||
// its title and visibility, sees it in EnumWindows and learns which process owns it.
|
||||
#include <windows.h>
|
||||
|
||||
static const wchar_t class_name[] = L"RavenCrossWindow";
|
||||
static const wchar_t title[] = L"Cross window probe é";
|
||||
|
||||
struct search { HWND wanted; int seen; };
|
||||
|
||||
static BOOL CALLBACK visit(HWND window, LPARAM parameter)
|
||||
{
|
||||
struct search *search = (struct search *)parameter;
|
||||
if (window == search->wanted)
|
||||
search->seen = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int same(const wchar_t *left, const wchar_t *right)
|
||||
{
|
||||
return lstrcmpW(left, right) == 0;
|
||||
}
|
||||
|
||||
static int child(DWORD parent)
|
||||
{
|
||||
HWND window = FindWindowW(class_name, title);
|
||||
if (!window)
|
||||
return 10;
|
||||
if (FindWindowW(class_name, NULL) != window || FindWindowW(NULL, title) != window || FindWindowA("RavenCrossWindow", NULL) != window)
|
||||
return 11;
|
||||
if (FindWindowW(class_name, L"Another title"))
|
||||
return 12;
|
||||
wchar_t text[64];
|
||||
if (GetWindowTextLengthW(window) != lstrlenW(title) || GetWindowTextW(window, text, 64) != lstrlenW(title) || !same(text, title))
|
||||
return 13;
|
||||
if (GetWindowTextW(window, text, 6) != 5 || text[5] || text[0] != L'C')
|
||||
return 14;
|
||||
if (GetClassNameW(window, text, 64) != lstrlenW(class_name) || !same(text, class_name))
|
||||
return 15;
|
||||
if (!IsWindow(window) || !IsWindowVisible(window))
|
||||
return 16;
|
||||
DWORD owner = 0;
|
||||
GetWindowThreadProcessId(window, &owner);
|
||||
if (owner != parent)
|
||||
return 17;
|
||||
struct search search = {window, 0};
|
||||
if (!EnumWindows(visit, (LPARAM)&search) || !search.seen)
|
||||
return 18;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int raven_entry(void)
|
||||
{
|
||||
const wchar_t *line = GetCommandLineW();
|
||||
const wchar_t *marker = line;
|
||||
for (; *marker; ++marker)
|
||||
if (marker[0] == L'/' && marker[1] == L'c' && marker[2] == L':') {
|
||||
DWORD parent = 0;
|
||||
for (marker += 3; *marker >= L'0' && *marker <= L'9'; ++marker)
|
||||
parent = parent * 10 + (DWORD)(*marker - L'0');
|
||||
return child(parent);
|
||||
}
|
||||
|
||||
WNDCLASSW definition = {0};
|
||||
definition.lpfnWndProc = DefWindowProcW;
|
||||
definition.hInstance = GetModuleHandleW(NULL);
|
||||
definition.lpszClassName = class_name;
|
||||
if (!RegisterClassW(&definition))
|
||||
return 1;
|
||||
HWND window = CreateWindowExW(0, class_name, title, WS_OVERLAPPEDWINDOW, 10, 10, 200, 120, NULL, NULL, definition.hInstance, NULL);
|
||||
if (!window)
|
||||
return 2;
|
||||
ShowWindow(window, SW_SHOWNORMAL);
|
||||
|
||||
wchar_t path[MAX_PATH], command[MAX_PATH + 64];
|
||||
if (!GetModuleFileNameW(NULL, path, MAX_PATH))
|
||||
return 3;
|
||||
wsprintfW(command, L"\"%s\" /c:%lu", path, GetCurrentProcessId());
|
||||
STARTUPINFOW startup = {sizeof(startup)};
|
||||
PROCESS_INFORMATION information;
|
||||
if (!CreateProcessW(path, command, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &information))
|
||||
return 4;
|
||||
if (WaitForSingleObject(information.hProcess, 20000) != WAIT_OBJECT_0)
|
||||
return 5;
|
||||
DWORD code = 6;
|
||||
GetExitCodeProcess(information.hProcess, &code);
|
||||
CloseHandle(information.hThread);
|
||||
CloseHandle(information.hProcess);
|
||||
DestroyWindow(window);
|
||||
return (int)code;
|
||||
}
|
||||
Reference in new issue
Block a user