deadbyte-mcp 0.15.72 → 0.15.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +143 -35
- package/PROVENANCE.json +47 -21
- package/README.txt +29 -18
- package/RELEASE.json +85 -18
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +50 -2
- package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
- package/bin/deadbyte-desktop-uia-r199.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199.obj +0 -0
- package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
- package/bin/deadbyte-fs-read-r198.exe +0 -0
- package/bin/deadbyte-fs-read-r198.obj +0 -0
- package/bin/deadbyte-owner-token-r193.exe +0 -0
- package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +2 -8
- package/native/deadbyte-desktop-uia-r199.cpp +345 -0
- package/native/deadbyte-fs-read-r197.cpp +174 -0
- package/native/deadbyte-fs-read-r198.cpp +181 -0
- package/native/deadbyte-owner-token-r193.cpp +156 -0
- package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
- package/package.json +4 -2
- package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
- package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
- package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
- package/scripts/build-native-fs-read-r197.ps1 +28 -0
- package/scripts/build-native-fs-read-r198.ps1 +28 -0
- package/scripts/build-native-root-probe-r197.ps1 +28 -0
- package/scripts/gate-windows-r193.ps1 +108 -0
- package/scripts/gate-windows-r194.ps1 +112 -0
- package/scripts/gate-windows-r195.ps1 +114 -0
- package/scripts/gate-windows-r206.ps1 +116 -0
- package/scripts/init-desktop-policy-r199.ps1 +85 -0
- package/scripts/npm-interactive-r194.mjs +13 -0
- package/scripts/npm-publication-r65.mjs +64 -4
- package/scripts/npm-release-r48.mjs +75 -11
- package/scripts/npm-release-readiness-r193.mjs +80 -0
- package/scripts/npm-release-readiness-r194.mjs +80 -0
- package/scripts/npm-release-readiness-r195.mjs +72 -0
- package/scripts/npm-release-readiness-r206.mjs +76 -0
- package/scripts/r188-owner-uac-review.ps1 +91 -0
- package/scripts/r188-uac-review-preview.mjs +65 -0
- package/scripts/r193-compile-token.ps1 +22 -0
- package/scripts/r193-evidence-relocate.ps1 +12 -0
- package/scripts/r193-focused-gate.mjs +20 -0
- package/scripts/r193-import-r188-scripts.ps1 +14 -0
- package/scripts/r193-limited-subject-probe.ps1 +25 -0
- package/scripts/r193-migrate-runtime-limited.ps1 +103 -0
- package/scripts/r193-owner-keygen.mjs +43 -0
- package/scripts/r193-owner-request-live.mjs +49 -0
- package/scripts/r193-owner-uac-approve.ps1 +42 -0
- package/scripts/r193-owner-uac-broker.mjs +139 -0
- package/scripts/r193-owner-uac-enroll-and-approve.ps1 +62 -0
- package/scripts/r193-test-limited-token.ps1 +49 -0
- package/scripts/r193-toolchain-inventory.ps1 +7 -0
- package/scripts/r193-uac-bridge-focused.mjs +21 -0
- package/scripts/r205-candidate-selection.mjs +22 -0
- package/src/candidate-selection-evidence-r206.mjs +71 -0
- package/src/candidate-selection-r205.mjs +96 -0
- package/src/desktop-postcondition-r196.mjs +30 -0
- package/src/desktop-runtime-r40.mjs +44 -7
- package/src/desktop-uia-identity-r199.mjs +51 -0
- package/src/desktop-uia-r40.mjs +9 -1
- package/src/machine-fs-handle-read-r196.mjs +43 -0
- package/src/machine-fs-native-read-r197.mjs +73 -0
- package/src/machine-fs-native-read-r198.mjs +76 -0
- package/src/machine-fs-read-worker.mjs +5 -25
- package/src/machine-fs.mjs +31 -16
- package/src/r188-owner-uac.mjs +105 -0
- package/src/r188-uac-boundary.mjs +21 -0
- package/src/r193-live-owner-subject.mjs +110 -0
- package/src/r193-owner-secret-acl.mjs +67 -0
- package/src/r193-owner-uac-broker.mjs +85 -0
- package/src/r193-uac-r70-bridge.mjs +54 -0
- package/src/r195-canonical-owner-observation.mjs +15 -0
- package/src/remote-destination-journal-r202.mjs +120 -0
- package/src/remote-device-r44.mjs +89 -14
- package/src/remote-payload-boundary-r201.mjs +71 -0
- package/src/remote-terminal-evidence-r200.mjs +60 -0
- package/src/version.mjs +1 -1
- package/test/desktop-runtime-r40.test.mjs +111 -0
- package/test/desktop-uia-r40.test.mjs +15 -0
- package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
- package/test/fixtures/r203-journal-writer.mjs +6 -0
- package/test/process-policy-refresh.test.mjs +4 -4
- package/test/r130-release-security-contract.test.mjs +8 -8
- package/test/r132-release-identity.test.mjs +7 -7
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +3 -3
- package/test/r165-live-cli-skew.test.mjs +1 -1
- package/test/r167-release-contract.test.mjs +11 -11
- package/test/r168-release-contract.test.mjs +4 -4
- package/test/r172-release-contract.test.mjs +6 -6
- package/test/r173-release-contract.test.mjs +16 -16
- package/test/r179-release-contract.test.mjs +12 -12
- package/test/r188-owner-uac.test.mjs +48 -0
- package/test/r188-uac-boundary.test.mjs +42 -0
- package/test/r190-release-contract.test.mjs +10 -10
- package/test/r191-release-contract.test.mjs +12 -12
- package/test/r192-release-identity.test.mjs +10 -10
- package/test/r193-limited-migration-contract.test.mjs +26 -0
- package/test/r193-live-owner-subject.test.mjs +61 -0
- package/test/r193-native-process-observer.test.mjs +36 -0
- package/test/r193-native-token.test.mjs +43 -0
- package/test/r193-owner-enrollment-entrypoint.test.mjs +38 -0
- package/test/r193-owner-secret-acl.test.mjs +23 -0
- package/test/r193-owner-uac-broker.test.mjs +82 -0
- package/test/r193-owner-uac-entrypoint.test.mjs +36 -0
- package/test/r193-release-identity.test.mjs +58 -0
- package/test/r193-release-readiness.test.mjs +87 -0
- package/test/r193-uac-r70-bridge.test.mjs +88 -0
- package/test/r193-windows-gate.test.mjs +30 -0
- package/test/r194-npm-auth-recovery.test.mjs +77 -0
- package/test/r194-npm-preauth.test.mjs +57 -0
- package/test/r194-release-identity.test.mjs +47 -0
- package/test/r194-release-readiness.test.mjs +87 -0
- package/test/r194-windows-gate.test.mjs +22 -0
- package/test/r195-canonical-owner-observation.test.mjs +32 -0
- package/test/r195-release-readiness.test.mjs +22 -0
- package/test/r196-desktop-postcondition.test.mjs +53 -0
- package/test/r196-handle-bound-read.test.mjs +99 -0
- package/test/r196-remote-outcome.test.mjs +94 -0
- package/test/r197-namespace-race.test.mjs +41 -0
- package/test/r197-native-dispatch-race.test.mjs +49 -0
- package/test/r197-native-file-read.test.mjs +60 -0
- package/test/r197-native-root-open.test.mjs +61 -0
- package/test/r198-root-identity.test.mjs +88 -0
- package/test/r199-native-uia-worker.test.mjs +28 -0
- package/test/r199-uia-identity.test.mjs +43 -0
- package/test/r200-remote-reconcile.test.mjs +106 -0
- package/test/r201-payload-boundary.test.mjs +78 -0
- package/test/r201-payload-fuzz.test.mjs +37 -0
- package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
- package/test/r202-destination-journal.test.mjs +92 -0
- package/test/r203-atomic-journal.test.mjs +86 -0
- package/test/r205-candidate-selection-cli.test.mjs +31 -0
- package/test/r205-candidate-selection.test.mjs +83 -0
- package/test/r206-candidate-evidence.test.mjs +64 -0
- package/test/r206-release-contract.test.mjs +42 -0
- package/test/r206-release-readiness.test.mjs +23 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-machine-fs-large-read.test.mjs +26 -12
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#include <windows.h>
|
|
2
|
+
#include <UIAutomation.h>
|
|
3
|
+
#include <cstdio>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include <cerrno>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <vector>
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cwctype>
|
|
11
|
+
#include <oleauto.h>
|
|
12
|
+
|
|
13
|
+
static constexpr size_t kMaxRequest = 8192;
|
|
14
|
+
static constexpr int kMaxVisited = 2048;
|
|
15
|
+
static constexpr int kMaxResultsHard = 16;
|
|
16
|
+
|
|
17
|
+
struct Match {
|
|
18
|
+
LONG left{};
|
|
19
|
+
LONG top{};
|
|
20
|
+
LONG width{};
|
|
21
|
+
LONG height{};
|
|
22
|
+
int controlType{};
|
|
23
|
+
bool enabled{};
|
|
24
|
+
bool offscreen{};
|
|
25
|
+
std::vector<LONG> runtimeId;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct Request {
|
|
29
|
+
HWND expectedHwnd{};
|
|
30
|
+
DWORD expectedPid{};
|
|
31
|
+
std::string kind;
|
|
32
|
+
int maxResults{8};
|
|
33
|
+
std::string queryUtf8;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
static void fail(const char* message, int code = 2) {
|
|
37
|
+
std::fprintf(stderr, "deadbyte-desktop-uia: %s\n", message);
|
|
38
|
+
std::exit(code);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static bool parseUnsigned(const std::string& text, unsigned long long& out, int base = 10) {
|
|
42
|
+
if (text.empty()) return false;
|
|
43
|
+
char* end = nullptr;
|
|
44
|
+
errno = 0;
|
|
45
|
+
const auto v = std::strtoull(text.c_str(), &end, base);
|
|
46
|
+
if (errno != 0 || end == text.c_str() || *end != '\0') return false;
|
|
47
|
+
out = v;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static std::vector<std::string> readHeaderLines(const std::string& bytes, size_t count, size_t& offset) {
|
|
52
|
+
std::vector<std::string> lines;
|
|
53
|
+
offset = 0;
|
|
54
|
+
for (size_t i = 0; i < count; ++i) {
|
|
55
|
+
const auto pos = bytes.find('\n', offset);
|
|
56
|
+
if (pos == std::string::npos) fail("request header truncated");
|
|
57
|
+
std::string line = bytes.substr(offset, pos - offset);
|
|
58
|
+
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
59
|
+
lines.push_back(line);
|
|
60
|
+
offset = pos + 1;
|
|
61
|
+
}
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static std::string field(const std::string& line, const char* key) {
|
|
66
|
+
const std::string prefix = std::string(key) + "=";
|
|
67
|
+
if (line.rfind(prefix, 0) != 0) fail("request field order/name invalid");
|
|
68
|
+
return line.substr(prefix.size());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static Request readRequest() {
|
|
72
|
+
std::string bytes;
|
|
73
|
+
bytes.resize(kMaxRequest + 1);
|
|
74
|
+
const size_t got = std::fread(bytes.data(), 1, bytes.size(), stdin);
|
|
75
|
+
if (std::ferror(stdin)) fail("stdin read failed");
|
|
76
|
+
if (got > kMaxRequest) fail("request exceeds bound");
|
|
77
|
+
bytes.resize(got);
|
|
78
|
+
|
|
79
|
+
size_t offset = 0;
|
|
80
|
+
const auto lines = readHeaderLines(bytes, 6, offset);
|
|
81
|
+
if (lines[0] != "DEADBYTE_DESKTOP_UIA_V1") fail("request magic invalid");
|
|
82
|
+
|
|
83
|
+
Request r;
|
|
84
|
+
const auto hwndText = field(lines[1], "expected_hwnd");
|
|
85
|
+
const auto pidText = field(lines[2], "expected_pid");
|
|
86
|
+
r.kind = field(lines[3], "query_kind");
|
|
87
|
+
const auto maxText = field(lines[4], "max_results");
|
|
88
|
+
const auto queryBytesText = field(lines[5], "query_bytes");
|
|
89
|
+
|
|
90
|
+
unsigned long long hwndValue = 0, pidValue = 0, maxValue = 0, queryBytes = 0;
|
|
91
|
+
std::string h = hwndText;
|
|
92
|
+
if (h.rfind("0x", 0) == 0 || h.rfind("0X", 0) == 0) h = h.substr(2);
|
|
93
|
+
if (!parseUnsigned(h, hwndValue, 16) || hwndValue == 0) fail("expected_hwnd invalid");
|
|
94
|
+
if (!parseUnsigned(pidText, pidValue) || pidValue == 0 || pidValue > 0x7fffffffULL) fail("expected_pid invalid");
|
|
95
|
+
if (!parseUnsigned(maxText, maxValue) || maxValue < 1 || maxValue > kMaxResultsHard) fail("max_results invalid");
|
|
96
|
+
if (!parseUnsigned(queryBytesText, queryBytes) || queryBytes > 512) fail("query_bytes invalid");
|
|
97
|
+
if (offset + queryBytes != bytes.size()) fail("query payload length mismatch");
|
|
98
|
+
|
|
99
|
+
r.expectedHwnd = reinterpret_cast<HWND>(static_cast<uintptr_t>(hwndValue));
|
|
100
|
+
r.expectedPid = static_cast<DWORD>(pidValue);
|
|
101
|
+
r.maxResults = static_cast<int>(maxValue);
|
|
102
|
+
r.queryUtf8.assign(bytes.data() + offset, static_cast<size_t>(queryBytes));
|
|
103
|
+
|
|
104
|
+
const bool needsQuery = r.kind != "root";
|
|
105
|
+
if (needsQuery && r.queryUtf8.empty()) fail("query required");
|
|
106
|
+
if (!needsQuery && !r.queryUtf8.empty()) fail("root query must be empty");
|
|
107
|
+
if (r.kind != "root" && r.kind != "name_exact" && r.kind != "name_contains" &&
|
|
108
|
+
r.kind != "automation_id_exact" && r.kind != "control_type") fail("query_kind invalid");
|
|
109
|
+
return r;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static std::wstring utf8ToWide(const std::string& input) {
|
|
113
|
+
if (input.empty()) return {};
|
|
114
|
+
const int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
115
|
+
static_cast<int>(input.size()), nullptr, 0);
|
|
116
|
+
if (n <= 0) fail("query UTF-8 invalid");
|
|
117
|
+
std::wstring out(static_cast<size_t>(n), L'\0');
|
|
118
|
+
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, input.data(),
|
|
119
|
+
static_cast<int>(input.size()), out.data(), n) != n) fail("query UTF-8 conversion failed");
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static std::wstring lower(std::wstring s) {
|
|
124
|
+
std::transform(s.begin(), s.end(), s.begin(), [](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
|
|
125
|
+
return s;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static int controlTypeFromQuery(const std::string& q) {
|
|
129
|
+
struct Pair { const char* name; int id; };
|
|
130
|
+
static const Pair map[] = {
|
|
131
|
+
{"button", UIA_ButtonControlTypeId}, {"checkbox", UIA_CheckBoxControlTypeId},
|
|
132
|
+
{"combobox", UIA_ComboBoxControlTypeId}, {"edit", UIA_EditControlTypeId},
|
|
133
|
+
{"hyperlink", UIA_HyperlinkControlTypeId}, {"list", UIA_ListControlTypeId},
|
|
134
|
+
{"listitem", UIA_ListItemControlTypeId}, {"menu", UIA_MenuControlTypeId},
|
|
135
|
+
{"menuitem", UIA_MenuItemControlTypeId}, {"radio", UIA_RadioButtonControlTypeId},
|
|
136
|
+
{"tab", UIA_TabControlTypeId}, {"tabitem", UIA_TabItemControlTypeId},
|
|
137
|
+
{"text", UIA_TextControlTypeId}, {"toolbar", UIA_ToolBarControlTypeId},
|
|
138
|
+
{"tree", UIA_TreeControlTypeId}, {"treeitem", UIA_TreeItemControlTypeId},
|
|
139
|
+
{"document", UIA_DocumentControlTypeId}, {"window", UIA_WindowControlTypeId},
|
|
140
|
+
{"pane", UIA_PaneControlTypeId}, {"group", UIA_GroupControlTypeId}
|
|
141
|
+
};
|
|
142
|
+
for (const auto& item : map) if (q == item.name) return item.id;
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static bool matches(IUIAutomationElement* e, const Request& r, const std::wstring& queryWide, int queryControlType) {
|
|
147
|
+
if (r.kind == "root") return true;
|
|
148
|
+
if (r.kind == "control_type") {
|
|
149
|
+
CONTROLTYPEID type = 0;
|
|
150
|
+
return SUCCEEDED(e->get_CurrentControlType(&type)) && type == queryControlType;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
BSTR raw = nullptr;
|
|
154
|
+
HRESULT hr = E_FAIL;
|
|
155
|
+
if (r.kind == "automation_id_exact") hr = e->get_CurrentAutomationId(&raw);
|
|
156
|
+
else hr = e->get_CurrentName(&raw);
|
|
157
|
+
if (FAILED(hr) || raw == nullptr) {
|
|
158
|
+
if (raw) SysFreeString(raw);
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
std::wstring value(raw, SysStringLen(raw));
|
|
162
|
+
SysFreeString(raw);
|
|
163
|
+
value = lower(std::move(value));
|
|
164
|
+
const auto query = lower(queryWide);
|
|
165
|
+
if (r.kind == "name_contains") return value.find(query) != std::wstring::npos;
|
|
166
|
+
return value == query;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// RuntimeId is an ephemeral bounded value, not a long-lived COM reference.
|
|
170
|
+
static bool readRuntimeId(IUIAutomationElement* e, std::vector<LONG>& out) {
|
|
171
|
+
SAFEARRAY* ids = nullptr;
|
|
172
|
+
const HRESULT identityStatus = e->GetRuntimeId(&ids);
|
|
173
|
+
if (FAILED(identityStatus) || !ids) {
|
|
174
|
+
if (ids) SafeArrayDestroy(ids);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const UINT dims = SafeArrayGetDim(ids);
|
|
178
|
+
LONG low = 0, high = -1;
|
|
179
|
+
const bool boundsOk = dims == 1 &&
|
|
180
|
+
SUCCEEDED(SafeArrayGetLBound(ids, 1, &low)) &&
|
|
181
|
+
SUCCEEDED(SafeArrayGetUBound(ids, 1, &high));
|
|
182
|
+
const long long count = boundsOk ? static_cast<long long>(high) -
|
|
183
|
+
static_cast<long long>(low) + 1 : 0;
|
|
184
|
+
if (!boundsOk || count < 1 || count > 64) {
|
|
185
|
+
SafeArrayDestroy(ids); return false;
|
|
186
|
+
}
|
|
187
|
+
std::vector<LONG> candidate;
|
|
188
|
+
candidate.reserve(static_cast<size_t>(count));
|
|
189
|
+
for (long long j = 0; j < count; ++j) {
|
|
190
|
+
LONG i = static_cast<LONG>(static_cast<long long>(low) + j);
|
|
191
|
+
LONG value = 0;
|
|
192
|
+
if (FAILED(SafeArrayGetElement(ids, &i, &value))) {
|
|
193
|
+
SafeArrayDestroy(ids); return false;
|
|
194
|
+
}
|
|
195
|
+
candidate.push_back(value);
|
|
196
|
+
}
|
|
197
|
+
SafeArrayDestroy(ids);
|
|
198
|
+
out = std::move(candidate);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
static bool captureMatch(IUIAutomationElement* e, Match& out) {
|
|
202
|
+
RECT rc{};
|
|
203
|
+
if (FAILED(e->get_CurrentBoundingRectangle(&rc))) return false;
|
|
204
|
+
if (rc.right <= rc.left || rc.bottom <= rc.top) return false;
|
|
205
|
+
BOOL offscreen = TRUE, enabled = FALSE;
|
|
206
|
+
e->get_CurrentIsOffscreen(&offscreen);
|
|
207
|
+
e->get_CurrentIsEnabled(&enabled);
|
|
208
|
+
if (offscreen) return false;
|
|
209
|
+
CONTROLTYPEID type = 0;
|
|
210
|
+
e->get_CurrentControlType(&type);
|
|
211
|
+
out.left = rc.left; out.top = rc.top;
|
|
212
|
+
out.width = rc.right - rc.left; out.height = rc.bottom - rc.top;
|
|
213
|
+
out.controlType = type; out.enabled = enabled != FALSE; out.offscreen = offscreen != FALSE;
|
|
214
|
+
return readRuntimeId(e, out.runtimeId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static void walk(IUIAutomationTreeWalker* walker, IUIAutomationElement* parent, const Request& req,
|
|
218
|
+
const std::wstring& queryWide, int queryControlType, int depth, int& visited,
|
|
219
|
+
bool& truncated, std::vector<Match>& matchesOut) {
|
|
220
|
+
if (depth > 64 || visited >= kMaxVisited || static_cast<int>(matchesOut.size()) >= req.maxResults) return;
|
|
221
|
+
IUIAutomationElement* child = nullptr;
|
|
222
|
+
if (FAILED(walker->GetFirstChildElement(parent, &child)) || child == nullptr) return;
|
|
223
|
+
while (child != nullptr) {
|
|
224
|
+
++visited;
|
|
225
|
+
if (visited >= kMaxVisited) truncated = true;
|
|
226
|
+
if (matches(child, req, queryWide, queryControlType)) {
|
|
227
|
+
Match m;
|
|
228
|
+
if (captureMatch(child, m)) matchesOut.push_back(m);
|
|
229
|
+
}
|
|
230
|
+
if (!truncated && static_cast<int>(matchesOut.size()) < req.maxResults) {
|
|
231
|
+
walk(walker, child, req, queryWide, queryControlType, depth + 1, visited, truncated, matchesOut);
|
|
232
|
+
}
|
|
233
|
+
IUIAutomationElement* next = nullptr;
|
|
234
|
+
walker->GetNextSiblingElement(child, &next);
|
|
235
|
+
child->Release();
|
|
236
|
+
child = next;
|
|
237
|
+
if (truncated || static_cast<int>(matchesOut.size()) >= req.maxResults) {
|
|
238
|
+
if (child) child->Release();
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static unsigned long long hwndNumber(HWND h) {
|
|
245
|
+
return static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(h));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static int runSelftest() {
|
|
249
|
+
if (controlTypeFromQuery("button") != UIA_ButtonControlTypeId) return 2;
|
|
250
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-selftest.v1\",\"status\":\"passed\"}\n");
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
int wmain(int argc, wchar_t** argv) {
|
|
255
|
+
if (argc == 2 && std::wstring(argv[1]) == L"--selftest") return runSelftest();
|
|
256
|
+
const Request req = readRequest();
|
|
257
|
+
|
|
258
|
+
HWND fg = GetForegroundWindow();
|
|
259
|
+
DWORD pid = 0;
|
|
260
|
+
if (fg) GetWindowThreadProcessId(fg, &pid);
|
|
261
|
+
if (fg != req.expectedHwnd || pid != req.expectedPid) fail("foreground window identity changed", 3);
|
|
262
|
+
|
|
263
|
+
const HRESULT init = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
264
|
+
if (FAILED(init) && init != RPC_E_CHANGED_MODE) fail("CoInitializeEx failed", 4);
|
|
265
|
+
const bool uninit = SUCCEEDED(init);
|
|
266
|
+
|
|
267
|
+
IUIAutomation* automation = nullptr;
|
|
268
|
+
HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&automation));
|
|
269
|
+
if (FAILED(hr) || !automation) {
|
|
270
|
+
if (uninit) CoUninitialize();
|
|
271
|
+
fail("CUIAutomation creation failed", 5);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
IUIAutomationElement* root = nullptr;
|
|
275
|
+
hr = automation->ElementFromHandle(fg, &root);
|
|
276
|
+
if (FAILED(hr) || !root) {
|
|
277
|
+
automation->Release(); if (uninit) CoUninitialize();
|
|
278
|
+
fail("ElementFromHandle failed", 6);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
std::vector<Match> found;
|
|
282
|
+
int visited = 0;
|
|
283
|
+
bool truncated = false;
|
|
284
|
+
const std::wstring queryWide = utf8ToWide(req.queryUtf8);
|
|
285
|
+
const int queryControlType = req.kind == "control_type" ? controlTypeFromQuery(req.queryUtf8) : 0;
|
|
286
|
+
if (req.kind == "control_type" && queryControlType == 0) {
|
|
287
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
288
|
+
fail("unsupported control_type query", 7);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (req.kind == "root") {
|
|
292
|
+
++visited;
|
|
293
|
+
RECT wr{};
|
|
294
|
+
if (GetWindowRect(fg, &wr)) {
|
|
295
|
+
const LONG vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
296
|
+
const LONG vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
297
|
+
const LONG vr = vx + GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
298
|
+
const LONG vb = vy + GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
299
|
+
const LONG left = (std::max)(wr.left, vx);
|
|
300
|
+
const LONG top = (std::max)(wr.top, vy);
|
|
301
|
+
const LONG right = (std::min)(wr.right, vr);
|
|
302
|
+
const LONG bottom = (std::min)(wr.bottom, vb);
|
|
303
|
+
if (right > left && bottom > top && IsWindowVisible(fg)) {
|
|
304
|
+
Match m;
|
|
305
|
+
m.left = left; m.top = top; m.width = right - left; m.height = bottom - top;
|
|
306
|
+
m.controlType = UIA_WindowControlTypeId;
|
|
307
|
+
m.enabled = IsWindowEnabled(fg) != FALSE;
|
|
308
|
+
m.offscreen = false;
|
|
309
|
+
found.push_back(m);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
IUIAutomationTreeWalker* walker = nullptr;
|
|
314
|
+
hr = automation->get_ControlViewWalker(&walker);
|
|
315
|
+
if (FAILED(hr) || !walker) {
|
|
316
|
+
root->Release(); automation->Release(); if (uninit) CoUninitialize();
|
|
317
|
+
fail("ControlViewWalker unavailable", 8);
|
|
318
|
+
}
|
|
319
|
+
walk(walker, root, req, queryWide, queryControlType, 0, visited, truncated, found);
|
|
320
|
+
walker->Release();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
std::printf("{\"schema\":\"deadbyte.desktop-uia-ground.v1\",\"foreground_hwnd\":\"0x%llx\",\"foreground_pid\":%lu,"
|
|
324
|
+
"\"query_kind\":\"%s\",\"visited\":%d,\"truncated\":%s,\"matches\":[",
|
|
325
|
+
hwndNumber(fg), static_cast<unsigned long>(pid), req.kind.c_str(), visited, truncated ? "true" : "false");
|
|
326
|
+
for (size_t i = 0; i < found.size(); ++i) {
|
|
327
|
+
if (i) std::printf(",");
|
|
328
|
+
const auto& m = found[i];
|
|
329
|
+
std::printf("{\"left\":%ld,\"top\":%ld,\"width\":%ld,\"height\":%ld,\"control_type\":%d,"
|
|
330
|
+
"\"enabled\":%s,\"offscreen\":%s,\"runtime_id\":[",
|
|
331
|
+
m.left, m.top, m.width, m.height, m.controlType,
|
|
332
|
+
m.enabled ? "true" : "false", m.offscreen ? "true" : "false");
|
|
333
|
+
for (size_t j = 0; j < m.runtimeId.size(); ++j) {
|
|
334
|
+
if (j) std::printf(",");
|
|
335
|
+
std::printf("%ld", m.runtimeId[j]);
|
|
336
|
+
}
|
|
337
|
+
std::printf("]}");
|
|
338
|
+
}
|
|
339
|
+
std::printf("]}\n");
|
|
340
|
+
|
|
341
|
+
root->Release();
|
|
342
|
+
automation->Release();
|
|
343
|
+
if (uninit) CoUninitialize();
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#define WIN32_LEAN_AND_MEAN
|
|
2
|
+
#define NOMINMAX
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
#include <winternl.h>
|
|
5
|
+
#include <bcrypt.h>
|
|
6
|
+
#include <cstdio>
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <cwchar>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <vector>
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#pragma comment(lib, "bcrypt.lib")
|
|
14
|
+
|
|
15
|
+
// A Windows-only experimental reader. Kernel resolution is anchored to an
|
|
16
|
+
// already-open directory HANDLE. No reparse component is followed.
|
|
17
|
+
// The caller must supply the independently authorized root and relative path.
|
|
18
|
+
// This file is not a release authorization or production deployment.
|
|
19
|
+
using NtCreateFilePtr=NTSTATUS (NTAPI *)(PHANDLE,ACCESS_MASK,POBJECT_ATTRIBUTES,
|
|
20
|
+
PIO_STATUS_BLOCK,PLARGE_INTEGER,ULONG,ULONG,ULONG,ULONG,PVOID,ULONG);
|
|
21
|
+
static constexpr ULONG kFileOpen=1, kFileDir=0x1, kFileNotDir=0x40;
|
|
22
|
+
static constexpr ULONG kFileSync=0x20, kFileNoReparse=0x00200000, kObjNoReparse=0x1000;
|
|
23
|
+
static void fail(const char* m,int exitCode=2){
|
|
24
|
+
std::fprintf(stderr,"R197_NATIVE_REFUSE=%s\n",m);std::exit(exitCode);
|
|
25
|
+
}
|
|
26
|
+
static bool noReparse(HANDLE h){
|
|
27
|
+
FILE_ATTRIBUTE_TAG_INFO t{};
|
|
28
|
+
return GetFileInformationByHandleEx(h,FileAttributeTagInfo,&t,sizeof(t)) &&
|
|
29
|
+
(t.FileAttributes&FILE_ATTRIBUTE_REPARSE_POINT)==0;
|
|
30
|
+
}
|
|
31
|
+
static unsigned long long parseNumber(const wchar_t* input){
|
|
32
|
+
if(!input||!*input)fail("missing_number");
|
|
33
|
+
for(const wchar_t* q=input;*q;++q)if(*q<L'0'||*q>L'9')fail("invalid_number");
|
|
34
|
+
wchar_t* end=nullptr;
|
|
35
|
+
errno=0;
|
|
36
|
+
const auto n=std::wcstoull(input,&end,10);
|
|
37
|
+
if(errno||!end||*end!=L'\0')fail("number_overflow");
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
static std::vector<std::wstring> split(const std::wstring& rel){
|
|
41
|
+
if(rel.empty()||rel.size()>1024||rel[0]==L'\\'||rel[0]==L'/'||rel.find(L':')!=std::wstring::npos)
|
|
42
|
+
fail("invalid_relative_path");
|
|
43
|
+
std::vector<std::wstring> result;size_t start=0;
|
|
44
|
+
for(size_t i=0;i<=rel.size();++i){
|
|
45
|
+
if(i==rel.size()||rel[i]==L'/'||rel[i]==L'\\'){
|
|
46
|
+
const auto s=rel.substr(start,i-start);
|
|
47
|
+
if(s.empty()||s==L"."||s==L".."||s.size()>255||s.back()==L'.'||s.back()==L' '||
|
|
48
|
+
s.find(L'\0')!=std::wstring::npos)fail("invalid_segment");
|
|
49
|
+
result.push_back(s);start=i+1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if(result.empty()||result.size()>32)fail("segment_budget");
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
static std::string base64(const unsigned char* b,size_t n){
|
|
56
|
+
static constexpr char abc[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
57
|
+
std::string out;out.reserve(((n+2)/3)*4);
|
|
58
|
+
for(size_t i=0;i<n;i+=3){
|
|
59
|
+
const unsigned a=b[i],x=i+1<n?b[i+1]:0,y=i+2<n?b[i+2]:0;
|
|
60
|
+
out.push_back(abc[a>>2]);out.push_back(abc[((a&3)<<4)|(x>>4)]);
|
|
61
|
+
out.push_back(i+1<n?abc[((x&15)<<2)|(y>>6)]:'=');
|
|
62
|
+
out.push_back(i+2<n?abc[y&63]:'=');
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
static std::string hex(const unsigned char* b,size_t n){
|
|
67
|
+
static constexpr char chars[]="0123456789abcdef";
|
|
68
|
+
std::string out;out.reserve(n*2);
|
|
69
|
+
for(size_t i=0;i<n;++i){out.push_back(chars[b[i]>>4]);out.push_back(chars[b[i]&15]);}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
static void seek(HANDLE h,unsigned long long pos){
|
|
73
|
+
LARGE_INTEGER p{};p.QuadPart=static_cast<LONGLONG>(pos);
|
|
74
|
+
if(!SetFilePointerEx(h,p,nullptr,FILE_BEGIN))fail("seek_failed");
|
|
75
|
+
}
|
|
76
|
+
static std::string hashAll(HANDLE h){
|
|
77
|
+
BCRYPT_ALG_HANDLE algorithm=nullptr;BCRYPT_HASH_HANDLE hash=nullptr;
|
|
78
|
+
if(BCryptOpenAlgorithmProvider(&algorithm,BCRYPT_SHA256_ALGORITHM,nullptr,0)<0)fail("hash_alg");
|
|
79
|
+
if(BCryptCreateHash(algorithm,&hash,nullptr,0,nullptr,0,0)<0)fail("hash_create");
|
|
80
|
+
seek(h,0);
|
|
81
|
+
std::vector<unsigned char> buffer(65536);
|
|
82
|
+
DWORD got=0;
|
|
83
|
+
for(;;){
|
|
84
|
+
if(!ReadFile(h,buffer.data(),static_cast<DWORD>(buffer.size()),&got,nullptr))fail("hash_read");
|
|
85
|
+
if(got==0)break;
|
|
86
|
+
if(BCryptHashData(hash,buffer.data(),got,0)<0)fail("hash_update");
|
|
87
|
+
}
|
|
88
|
+
unsigned char sum[32]{};
|
|
89
|
+
if(BCryptFinishHash(hash,sum,sizeof(sum),0)<0)fail("hash_finish");
|
|
90
|
+
BCryptDestroyHash(hash);BCryptCloseAlgorithmProvider(algorithm,0);
|
|
91
|
+
return hex(sum,sizeof(sum));
|
|
92
|
+
}
|
|
93
|
+
int wmain(int argc,wchar_t** argv){
|
|
94
|
+
if(argc!=6)fail("usage_root_relative_offset_length_hash");
|
|
95
|
+
const std::wstring root=argv[1];
|
|
96
|
+
if(root.size()<4||root.size()>2048||root[1]!=L':'||(root[2]!=L'\\'&&root[2]!=L'/'))
|
|
97
|
+
fail("root_must_be_absolute_drive_path");
|
|
98
|
+
const auto names=split(argv[2]);
|
|
99
|
+
const auto offset=parseNumber(argv[3]),requested=parseNumber(argv[4]);
|
|
100
|
+
if(requested>4*1024*1024ULL||offset>9007199254740991ULL)fail("range_budget");
|
|
101
|
+
if(std::wcscmp(argv[5],L"0")!=0&&std::wcscmp(argv[5],L"1")!=0)fail("invalid_hash_mode");
|
|
102
|
+
const bool wantHash=argv[5][0]==L'1';
|
|
103
|
+
const HANDLE rootHandle=CreateFileW(root.c_str(),FILE_READ_ATTRIBUTES|FILE_LIST_DIRECTORY|SYNCHRONIZE,
|
|
104
|
+
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,nullptr,OPEN_EXISTING,
|
|
105
|
+
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT,nullptr);
|
|
106
|
+
if(rootHandle==INVALID_HANDLE_VALUE)fail("open_root");
|
|
107
|
+
if(!noReparse(rootHandle))fail("root_reparse_or_unverified");
|
|
108
|
+
FILE_ID_INFO rootId{};if(!GetFileInformationByHandleEx(rootHandle,FileIdInfo,&rootId,sizeof(rootId)))
|
|
109
|
+
fail("root_identity_unavailable");
|
|
110
|
+
HMODULE ntdll=GetModuleHandleW(L"ntdll.dll");
|
|
111
|
+
auto nt=reinterpret_cast<NtCreateFilePtr>(GetProcAddress(ntdll,"NtCreateFile"));
|
|
112
|
+
if(!nt)fail("ntcreatefile_unavailable");
|
|
113
|
+
HANDLE parent=rootHandle;std::vector<HANDLE> chain;chain.reserve(names.size());
|
|
114
|
+
for(size_t i=0;i<names.size();++i){
|
|
115
|
+
const bool dir=i+1<names.size();
|
|
116
|
+
UNICODE_STRING name{};name.Buffer=const_cast<PWSTR>(names[i].c_str());
|
|
117
|
+
name.Length=static_cast<USHORT>(names[i].size()*sizeof(wchar_t));
|
|
118
|
+
name.MaximumLength=name.Length;
|
|
119
|
+
OBJECT_ATTRIBUTES oa{};oa.Length=sizeof(oa);oa.RootDirectory=parent;
|
|
120
|
+
oa.ObjectName=&name;oa.Attributes=OBJ_CASE_INSENSITIVE|kObjNoReparse;
|
|
121
|
+
IO_STATUS_BLOCK ios{};HANDLE next=nullptr;
|
|
122
|
+
const ULONG options=kFileSync|kFileNoReparse|(dir?kFileDir:kFileNotDir);
|
|
123
|
+
const ACCESS_MASK access=FILE_READ_ATTRIBUTES|SYNCHRONIZE|(dir?FILE_LIST_DIRECTORY:FILE_READ_DATA);
|
|
124
|
+
const NTSTATUS status=nt(&next,access,&oa,&ios,nullptr,FILE_ATTRIBUTE_NORMAL,
|
|
125
|
+
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,kFileOpen,options,nullptr,0);
|
|
126
|
+
if(status<0||!next)fail("nt_open_component_refused",3);
|
|
127
|
+
chain.push_back(next);
|
|
128
|
+
if(!noReparse(next))fail("component_reparse_or_unverified",4);
|
|
129
|
+
FILE_ID_INFO id{};if(!GetFileInformationByHandleEx(next,FileIdInfo,&id,sizeof(id)))
|
|
130
|
+
fail("component_identity_unavailable",5);
|
|
131
|
+
parent=next;
|
|
132
|
+
}
|
|
133
|
+
const HANDLE leaf=chain.back();
|
|
134
|
+
FILE_ID_INFO identity{};
|
|
135
|
+
FILE_BASIC_INFO initialBasic{},finalBasic{};
|
|
136
|
+
FILE_STANDARD_INFO initialStandard{},finalStandard{};
|
|
137
|
+
if(!GetFileInformationByHandleEx(leaf,FileIdInfo,&identity,sizeof(identity)) ||
|
|
138
|
+
!GetFileInformationByHandleEx(leaf,FileBasicInfo,&initialBasic,sizeof(initialBasic)) ||
|
|
139
|
+
!GetFileInformationByHandleEx(leaf,FileStandardInfo,&initialStandard,sizeof(initialStandard)))
|
|
140
|
+
fail("file_identity_unavailable");
|
|
141
|
+
const auto fileSize=initialStandard.EndOfFile.QuadPart;
|
|
142
|
+
if(fileSize<0||fileSize>9007199254740991LL)fail("file_size_out_of_range");
|
|
143
|
+
if(offset>static_cast<unsigned long long>(fileSize))fail("offset_exceeds_size");
|
|
144
|
+
const auto length=static_cast<size_t>(std::min(requested,static_cast<unsigned long long>(fileSize)-offset));
|
|
145
|
+
std::vector<unsigned char> data(length);
|
|
146
|
+
seek(leaf,offset);
|
|
147
|
+
size_t read=0;
|
|
148
|
+
while(read<length){
|
|
149
|
+
DWORD got=0;
|
|
150
|
+
if(!ReadFile(leaf,data.data()+read,static_cast<DWORD>(length-read),&got,nullptr))
|
|
151
|
+
fail("range_read_failed");
|
|
152
|
+
if(got==0)fail("unexpected_range_eof");
|
|
153
|
+
read+=got;
|
|
154
|
+
}
|
|
155
|
+
const auto digest=wantHash?hashAll(leaf):std::string();
|
|
156
|
+
if(!GetFileInformationByHandleEx(leaf,FileBasicInfo,&finalBasic,sizeof(finalBasic))||
|
|
157
|
+
!GetFileInformationByHandleEx(leaf,FileStandardInfo,&finalStandard,sizeof(finalStandard)))
|
|
158
|
+
fail("file_postidentity_unavailable");
|
|
159
|
+
if(initialStandard.EndOfFile.QuadPart!=finalStandard.EndOfFile.QuadPart||
|
|
160
|
+
initialBasic.LastWriteTime.QuadPart!=finalBasic.LastWriteTime.QuadPart||
|
|
161
|
+
initialBasic.ChangeTime.QuadPart!=finalBasic.ChangeTime.QuadPart)
|
|
162
|
+
fail("file_mutated_during_read");
|
|
163
|
+
unsigned long long nativeId=0;
|
|
164
|
+
std::memcpy(&nativeId,identity.FileId.Identifier,sizeof(nativeId));
|
|
165
|
+
const auto encoded=base64(data.data(),data.size());
|
|
166
|
+
std::printf("{\"schema\":\"deadbyte.machine-fs-read-worker.v1\",\"bytes_read\":%llu,\"data_base64\":\"%s\",\"file_sha256\":",
|
|
167
|
+
static_cast<unsigned long long>(read),encoded.c_str());
|
|
168
|
+
if(wantHash)std::printf("\"%s\"",digest.c_str());else std::printf("null");
|
|
169
|
+
std::printf(",\"file_size\":%llu,\"file_dev\":\"%llu\",\"file_ino\":\"%llu\"}",
|
|
170
|
+
static_cast<unsigned long long>(fileSize),
|
|
171
|
+
static_cast<unsigned long long>(identity.VolumeSerialNumber & 0xffffffffULL),nativeId);
|
|
172
|
+
for(auto it=chain.rbegin();it!=chain.rend();++it)CloseHandle(*it);
|
|
173
|
+
CloseHandle(rootHandle);return 0;
|
|
174
|
+
}
|