tools/makepad_builder replaces tools/makepad_loader: one build target shared across app builds, workspace package selection, checkout progress on the public Git API, detached built apps with a completion state, waits for Windows security scans, manual retry after compiler locks, dedicated-folder installer checks, catalog and runtime fixes. tools/web_server and its scripts leave for github.com/makepad/webserver. Arch USB clone/restore scripts, the qwen38 box scripts and the G-belt serial test join tools/. docs/agents records the agent workflow and the remote-control handoff protocol; AGENTS.md forbids vendored sources and bulk imports. Cargo.toml lists apps/wm-dyn, libs/code_language, libs/search, libs/tar, libs/loader_bundle and tools/makepad_builder, and drops the two removed crates. Squashed from work: - Share Builder target across Makepad app builds - Fix Builder workspace package selection - Align Builder checkout progress with public Git API - Detach built apps and show completion state - Wait for Windows security scans - Offer manual retry after Windows compiler locks - docs: the agent workflow of record and the remote-control handoff protocol - builder: dedicated-folder installer checks, catalog and runtime fixes; Windows job objects hold c_void handles - tools: Arch USB clone/restore scripts, the qwen38 box scripts, and the G-belt serial test - tools: the web server moves to makepad/webserver - AGENTS.md: no vendored sources or bulk imports in the tree Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
350 lines
12 KiB
C
350 lines
12 KiB
C
#define WIN32_LEAN_AND_MEAN
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <windows.h>
|
|
#include <psapi.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#pragma comment(lib, "psapi.lib")
|
|
|
|
static HANDLE g_log = INVALID_HANDLE_VALUE;
|
|
static HANDLE g_com_h[64];
|
|
static wchar_t g_com_n[64][96];
|
|
static volatile LONG g_ncom;
|
|
|
|
static HMODULE g_seen[512];
|
|
static int g_nseen;
|
|
|
|
typedef HANDLE (WINAPI *pCreateFileW)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
|
|
typedef HANDLE (WINAPI *pCreateFileA)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
|
|
typedef BOOL (WINAPI *pWriteFile)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED);
|
|
typedef BOOL (WINAPI *pReadFile)(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED);
|
|
typedef BOOL (WINAPI *pSetCommState)(HANDLE, LPDCB);
|
|
typedef BOOL (WINAPI *pEscapeCommFunction)(HANDLE, DWORD);
|
|
|
|
static pCreateFileW real_CreateFileW, real_CreateFileW_k32;
|
|
static pCreateFileA real_CreateFileA, real_CreateFileA_k32;
|
|
static pWriteFile real_WriteFile, real_WriteFile_k32;
|
|
static pReadFile real_ReadFile, real_ReadFile_k32;
|
|
static pSetCommState real_SetCommState, real_SetCommState_k32;
|
|
static pEscapeCommFunction real_EscapeCommFunction, real_EscapeCommFunction_k32;
|
|
|
|
typedef LONG (WINAPI *NtWriteFile_t)(HANDLE, HANDLE, PVOID, PVOID, PVOID, PVOID, ULONG, PVOID, PVOID);
|
|
static NtWriteFile_t pNtWriteFile;
|
|
|
|
static void *follow(void *p) {
|
|
BYTE *b = (BYTE *)p;
|
|
int hops;
|
|
if (!b) return p;
|
|
for (hops = 0; hops < 4; hops++) {
|
|
if (b[0] == 0xE9) {
|
|
b = b + 5 + *(int *)(b + 1);
|
|
continue;
|
|
}
|
|
if (b[0] == 0xFF && b[1] == 0x25) {
|
|
b = *(BYTE **)(b + 6 + *(int *)(b + 2));
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
static void log_raw(const char *s, int n) {
|
|
unsigned char iosb[16];
|
|
if (g_log == INVALID_HANDLE_VALUE || n <= 0) return;
|
|
if (!pNtWriteFile)
|
|
pNtWriteFile = (NtWriteFile_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtWriteFile");
|
|
if (pNtWriteFile) {
|
|
memset(iosb, 0, sizeof(iosb));
|
|
pNtWriteFile(g_log, NULL, NULL, NULL, iosb, (PVOID)s, (ULONG)n, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static void slog(const char *fmt, ...) {
|
|
char buf[2048];
|
|
va_list ap;
|
|
int n;
|
|
va_start(ap, fmt);
|
|
n = _vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
|
|
va_end(ap);
|
|
if (n > 0) log_raw(buf, n);
|
|
}
|
|
|
|
static int interesting_name(const wchar_t *n) {
|
|
wchar_t u[256];
|
|
int i;
|
|
if (!n) return 0;
|
|
for (i = 0; i < 255 && n[i]; i++) {
|
|
wchar_t c = n[i];
|
|
if (c >= L'a' && c <= L'z') c = (wchar_t)(c - 32);
|
|
u[i] = c;
|
|
}
|
|
u[i] = 0;
|
|
if (wcsstr(u, L"COMLOG") || wcsstr(u, L"COM.LOG") || wcsstr(u, L"IO.LOG")) return 0;
|
|
if (wcsstr(u, L"COM3") || wcsstr(u, L"COM11") || wcsstr(u, L"COM14")) return 1;
|
|
if (wcsstr(u, L"VCP") || wcsstr(u, L"SERIAL") || wcsstr(u, L"FTDI") ||
|
|
wcsstr(u, L"FTSERT") || wcsstr(u, L"ACCUMOTION")) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static const wchar_t *name_for(HANDLE h) {
|
|
int i, n = (int)g_ncom;
|
|
for (i = 0; i < n && i < 64; i++)
|
|
if (g_com_h[i] == h) return g_com_n[i];
|
|
return NULL;
|
|
}
|
|
|
|
static void remember(HANDLE h, const wchar_t *n) {
|
|
LONG i;
|
|
if (h == INVALID_HANDLE_VALUE || h == NULL || !n) return;
|
|
if (name_for(h)) return;
|
|
i = InterlockedIncrement(&g_ncom) - 1;
|
|
if (i < 0 || i >= 64) return;
|
|
g_com_h[i] = h;
|
|
wcsncpy(g_com_n[i], n, 95);
|
|
g_com_n[i][95] = 0;
|
|
}
|
|
|
|
static int query_name(HANDLE h, wchar_t *out, int cap) {
|
|
typedef LONG (WINAPI *QO)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
|
static QO q;
|
|
BYTE buf[1024];
|
|
ULONG len = 0;
|
|
USHORT slen;
|
|
wchar_t *s;
|
|
int n, i;
|
|
if (!q) q = (QO)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryObject");
|
|
if (!q) return 0;
|
|
if (q(h, 1, buf, sizeof(buf), &len) < 0) return 0;
|
|
slen = *(USHORT *)buf;
|
|
s = *(wchar_t **)(buf + 8);
|
|
if (!s || !slen) return 0;
|
|
n = slen / 2;
|
|
if (n >= cap) n = cap - 1;
|
|
for (i = 0; i < n; i++) out[i] = s[i];
|
|
out[n] = 0;
|
|
return 1;
|
|
}
|
|
|
|
static int is_com(HANDLE h) {
|
|
wchar_t name[256];
|
|
const wchar_t *known = name_for(h);
|
|
if (known) return 1;
|
|
if (!query_name(h, name, 256)) return 0;
|
|
if (!interesting_name(name)) return 0;
|
|
remember(h, name);
|
|
return 1;
|
|
}
|
|
|
|
static void hexdump(const char *tag, HANDLE h, const BYTE *buf, DWORD n) {
|
|
const wchar_t *nm = name_for(h);
|
|
char line[1800];
|
|
int o = 0;
|
|
DWORD i, show;
|
|
FILETIME ft;
|
|
ULARGE_INTEGER ul;
|
|
GetSystemTimeAsFileTime(&ft);
|
|
ul.LowPart = ft.dwLowDateTime;
|
|
ul.HighPart = ft.dwHighDateTime;
|
|
o += _snprintf(line + o, sizeof(line) - o,
|
|
"%llu %s h=%p %ls n=%u :",
|
|
(unsigned long long)(ul.QuadPart / 10000), tag, h,
|
|
nm ? nm : L"?", (unsigned)n);
|
|
show = n > 256 ? 256 : n;
|
|
for (i = 0; i < show && o < (int)sizeof(line) - 8; i++)
|
|
o += _snprintf(line + o, sizeof(line) - o, " %02X", buf[i]);
|
|
if (n > show) o += _snprintf(line + o, sizeof(line) - o, " ...");
|
|
o += _snprintf(line + o, sizeof(line) - o, " | ");
|
|
for (i = 0; i < show && o < (int)sizeof(line) - 4; i++) {
|
|
BYTE c = buf[i];
|
|
line[o++] = (c >= 32 && c < 127) ? (char)c : '.';
|
|
}
|
|
line[o++] = '\n';
|
|
log_raw(line, o);
|
|
}
|
|
|
|
static HANDLE WINAPI hook_CreateFileW(LPCWSTR path, DWORD acc, DWORD share,
|
|
LPSECURITY_ATTRIBUTES sa, DWORD disp, DWORD flags, HANDLE tmpl) {
|
|
HANDLE h = real_CreateFileW(path, acc, share, sa, disp, flags, tmpl);
|
|
if (interesting_name(path)) {
|
|
slog("CreateFileW %ls -> %p err=%u acc=%08X\n",
|
|
path, h, (unsigned)GetLastError(), (unsigned)acc);
|
|
if (h != INVALID_HANDLE_VALUE) remember(h, path);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static HANDLE WINAPI hook_CreateFileA(LPCSTR path, DWORD acc, DWORD share,
|
|
LPSECURITY_ATTRIBUTES sa, DWORD disp, DWORD flags, HANDLE tmpl) {
|
|
HANDLE h = real_CreateFileA(path, acc, share, sa, disp, flags, tmpl);
|
|
wchar_t w[256];
|
|
if (path) {
|
|
MultiByteToWideChar(CP_ACP, 0, path, -1, w, 256);
|
|
if (interesting_name(w)) {
|
|
slog("CreateFileA %s -> %p err=%u\n", path, h, (unsigned)GetLastError());
|
|
if (h != INVALID_HANDLE_VALUE) remember(h, w);
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static BOOL WINAPI hook_WriteFile(HANDLE h, LPCVOID buf, DWORD n, LPDWORD w, LPOVERLAPPED o) {
|
|
if (buf && n && is_com(h)) hexdump("W", h, (const BYTE *)buf, n);
|
|
return real_WriteFile(h, buf, n, w, o);
|
|
}
|
|
|
|
static BOOL WINAPI hook_ReadFile(HANDLE h, LPVOID buf, DWORD n, LPDWORD w, LPOVERLAPPED o) {
|
|
BOOL ok = real_ReadFile(h, buf, n, w, o);
|
|
DWORD got = 0;
|
|
if (ok && is_com(h)) {
|
|
if (w) got = *w;
|
|
else if (o) got = n;
|
|
if (got && buf) hexdump("R", h, (const BYTE *)buf, got);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static BOOL WINAPI hook_SetCommState(HANDLE h, LPDCB d) {
|
|
wchar_t name[256];
|
|
if (is_com(h) && d) {
|
|
if (!query_name(h, name, 256)) wcscpy(name, L"?");
|
|
slog("SetCommState %ls baud=%u bits=%u parity=%u stop=%u dtr=%u rts=%u inX=%u outX=%u\n",
|
|
name, (unsigned)d->BaudRate, (unsigned)d->ByteSize, (unsigned)d->Parity,
|
|
(unsigned)d->StopBits, (unsigned)d->fDtrControl, (unsigned)d->fRtsControl,
|
|
(unsigned)d->fInX, (unsigned)d->fOutX);
|
|
}
|
|
return real_SetCommState(h, d);
|
|
}
|
|
|
|
static BOOL WINAPI hook_EscapeCommFunction(HANDLE h, DWORD fn) {
|
|
if (is_com(h)) slog("EscapeCommFunction %ls fn=%u\n", name_for(h) ? name_for(h) : L"?", (unsigned)fn);
|
|
return real_EscapeCommFunction(h, fn);
|
|
}
|
|
|
|
static int already_seen(HMODULE m) {
|
|
int i;
|
|
for (i = 0; i < g_nseen; i++) if (g_seen[i] == m) return 1;
|
|
if (g_nseen < 512) g_seen[g_nseen++] = m;
|
|
return 0;
|
|
}
|
|
|
|
static void *hook_for(void *fn) {
|
|
if (fn == (void *)real_CreateFileW || fn == (void *)real_CreateFileW_k32) return (void *)hook_CreateFileW;
|
|
if (fn == (void *)real_CreateFileA || fn == (void *)real_CreateFileA_k32) return (void *)hook_CreateFileA;
|
|
if (fn == (void *)real_WriteFile || fn == (void *)real_WriteFile_k32) return (void *)hook_WriteFile;
|
|
if (fn == (void *)real_ReadFile || fn == (void *)real_ReadFile_k32) return (void *)hook_ReadFile;
|
|
if (fn == (void *)real_SetCommState || fn == (void *)real_SetCommState_k32) return (void *)hook_SetCommState;
|
|
if (fn == (void *)real_EscapeCommFunction || fn == (void *)real_EscapeCommFunction_k32)
|
|
return (void *)hook_EscapeCommFunction;
|
|
return NULL;
|
|
}
|
|
|
|
static void patch_iat(HMODULE mod) {
|
|
BYTE *base = (BYTE *)mod;
|
|
IMAGE_DOS_HEADER *dos;
|
|
IMAGE_NT_HEADERS64 *nt;
|
|
IMAGE_DATA_DIRECTORY *dir;
|
|
IMAGE_IMPORT_DESCRIPTOR *imp;
|
|
DWORD old;
|
|
if (!base) return;
|
|
__try {
|
|
dos = (IMAGE_DOS_HEADER *)base;
|
|
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return;
|
|
nt = (IMAGE_NT_HEADERS64 *)(base + dos->e_lfanew);
|
|
if (nt->Signature != IMAGE_NT_SIGNATURE) return;
|
|
if (nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) return;
|
|
dir = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
|
if (!dir->VirtualAddress) return;
|
|
imp = (IMAGE_IMPORT_DESCRIPTOR *)(base + dir->VirtualAddress);
|
|
for (; imp->Name; imp++) {
|
|
IMAGE_THUNK_DATA64 *th = (IMAGE_THUNK_DATA64 *)(base + imp->FirstThunk);
|
|
for (; th->u1.Function; th++) {
|
|
void *fn = (void *)(ULONG_PTR)th->u1.Function;
|
|
void *hk = hook_for(fn);
|
|
if (!hk) continue;
|
|
if (!VirtualProtect(&th->u1.Function, sizeof(ULONGLONG), PAGE_READWRITE, &old)) continue;
|
|
th->u1.Function = (ULONGLONG)(ULONG_PTR)hk;
|
|
VirtualProtect(&th->u1.Function, sizeof(ULONGLONG), old, &old);
|
|
}
|
|
}
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
}
|
|
}
|
|
|
|
static void patch_all(void) {
|
|
HMODULE mods[384];
|
|
DWORD needed = 0;
|
|
unsigned i, n;
|
|
if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &needed)) return;
|
|
n = needed / sizeof(HMODULE);
|
|
if (n > 384) n = 384;
|
|
for (i = 0; i < n; i++) {
|
|
int first = !already_seen(mods[i]);
|
|
patch_iat(mods[i]);
|
|
if (first) {
|
|
wchar_t name[MAX_PATH];
|
|
name[0] = 0;
|
|
GetModuleFileNameW(mods[i], name, MAX_PATH);
|
|
slog("module %ls\n", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD WINAPI worker(LPVOID arg) {
|
|
int i;
|
|
(void)arg;
|
|
slog("comlog worker pid=%u\n", (unsigned)GetCurrentProcessId());
|
|
for (i = 0; i < 200; i++) {
|
|
patch_all();
|
|
Sleep(i < 40 ? 100 : 500);
|
|
}
|
|
for (;;) {
|
|
patch_all();
|
|
Sleep(1000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *gp(HMODULE m, const char *n) {
|
|
void *p;
|
|
if (!m) return NULL;
|
|
p = GetProcAddress(m, n);
|
|
return follow(p);
|
|
}
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID res) {
|
|
HMODULE k32, kb;
|
|
(void)res;
|
|
if (reason != DLL_PROCESS_ATTACH) return TRUE;
|
|
DisableThreadLibraryCalls(inst);
|
|
CreateDirectoryW(L"C:\\Users\\playe\\com3sniff", NULL);
|
|
g_log = CreateFileW(L"C:\\Users\\playe\\com3sniff\\com.log",
|
|
FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
slog("=== comlog IAT attach pid=%u ===\n", (unsigned)GetCurrentProcessId());
|
|
k32 = GetModuleHandleW(L"kernel32.dll");
|
|
kb = GetModuleHandleW(L"kernelbase.dll");
|
|
real_CreateFileW_k32 = (pCreateFileW)gp(k32, "CreateFileW");
|
|
real_CreateFileA_k32 = (pCreateFileA)gp(k32, "CreateFileA");
|
|
real_WriteFile_k32 = (pWriteFile)gp(k32, "WriteFile");
|
|
real_ReadFile_k32 = (pReadFile)gp(k32, "ReadFile");
|
|
real_SetCommState_k32 = (pSetCommState)gp(k32, "SetCommState");
|
|
real_EscapeCommFunction_k32 = (pEscapeCommFunction)gp(k32, "EscapeCommFunction");
|
|
real_CreateFileW = (pCreateFileW)gp(kb, "CreateFileW");
|
|
real_CreateFileA = (pCreateFileA)gp(kb, "CreateFileA");
|
|
real_WriteFile = (pWriteFile)gp(kb, "WriteFile");
|
|
real_ReadFile = (pReadFile)gp(kb, "ReadFile");
|
|
real_SetCommState = (pSetCommState)gp(kb, "SetCommState");
|
|
real_EscapeCommFunction = (pEscapeCommFunction)gp(kb, "EscapeCommFunction");
|
|
if (!real_CreateFileW) real_CreateFileW = real_CreateFileW_k32;
|
|
if (!real_CreateFileA) real_CreateFileA = real_CreateFileA_k32;
|
|
if (!real_WriteFile) real_WriteFile = real_WriteFile_k32;
|
|
if (!real_ReadFile) real_ReadFile = real_ReadFile_k32;
|
|
if (!real_SetCommState) real_SetCommState = real_SetCommState_k32;
|
|
if (!real_EscapeCommFunction) real_EscapeCommFunction = real_EscapeCommFunction_k32;
|
|
slog("orig WriteFile kb=%p k32=%p\n", real_WriteFile, real_WriteFile_k32);
|
|
CreateThread(NULL, 0, worker, NULL, 0, NULL);
|
|
return TRUE;
|
|
}
|