248 lines
16 KiB
C++
248 lines
16 KiB
C++
// Test-only executable. Never distributed as the sandbox runner.
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define NOMINMAX
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#include <userenv.h>
|
|
#include <sddl.h>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
static bool registryId(const std::wstring& value) {
|
|
if (value.size() != 36 || value[8] != L'-' || value[13] != L'-' || value[18] != L'-' || value[23] != L'-') return false;
|
|
for (size_t index = 0; index < value.size(); ++index) {
|
|
if (index == 8 || index == 13 || index == 18 || index == 23) continue;
|
|
if ((value[index] < L'0' || value[index] > L'9') && (value[index] < L'a' || value[index] > L'f')) return false;
|
|
}
|
|
return true;
|
|
}
|
|
static std::wstring pipeName(const std::wstring& id) { return L"\\\\.\\pipe\\YCodeSandboxTest." + id; }
|
|
static int pipeServer(const std::wstring& id, const std::wstring& profile) {
|
|
if (!registryId(id) || profile.size() != 42 || profile.substr(0, 10) != L"YCode.Run." || profile.substr(10).find_first_not_of(L"0123456789abcdef") != std::wstring::npos) return 2;
|
|
PSID expected = nullptr;
|
|
if (FAILED(DeriveAppContainerSidFromAppContainerName(profile.c_str(), &expected))) return 29;
|
|
HANDLE token = nullptr; DWORD bytes = 0;
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { FreeSid(expected); return 29; }
|
|
GetTokenInformation(token, TokenUser, nullptr, 0, &bytes); std::vector<BYTE> user(bytes);
|
|
const bool gotUser = GetTokenInformation(token, TokenUser, user.data(), bytes, &bytes) != FALSE; CloseHandle(token);
|
|
LPWSTR owner = nullptr, package = nullptr;
|
|
if (!gotUser || !ConvertSidToStringSidW(reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid, &owner) || !ConvertSidToStringSidW(expected, &package)) { if (owner) LocalFree(owner); FreeSid(expected); return 29; }
|
|
const std::wstring sddl = L"D:P(A;;GA;;;SY)(A;;GA;;;" + std::wstring(owner) + L")(A;;0x00120003;;;" + std::wstring(package) + L")S:(ML;;NW;;;LW)";
|
|
std::printf("PIPE_EXPECTED_SID %ls\n", package); std::fflush(stdout);
|
|
LocalFree(owner); LocalFree(package);
|
|
PSECURITY_DESCRIPTOR descriptor = nullptr;
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), SDDL_REVISION_1, &descriptor, nullptr)) { FreeSid(expected); return 29; }
|
|
SECURITY_ATTRIBUTES security = {sizeof(security), descriptor, FALSE};
|
|
HANDLE pipe = CreateNamedPipeW(pipeName(id).c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 5000, &security);
|
|
LocalFree(descriptor);
|
|
if (pipe == INVALID_HANDLE_VALUE) { FreeSid(expected); return 29; }
|
|
const bool connected = ConnectNamedPipe(pipe, nullptr) || GetLastError() == ERROR_PIPE_CONNECTED;
|
|
ULONG pid = 0; bool authenticated = false;
|
|
if (connected && GetNamedPipeClientProcessId(pipe, &pid)) {
|
|
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid), clientToken = nullptr;
|
|
if (process && OpenProcessToken(process, TOKEN_QUERY, &clientToken)) {
|
|
DWORD container = 0, needed = 0;
|
|
if (GetTokenInformation(clientToken, TokenIsAppContainer, &container, sizeof(container), &needed) && container) {
|
|
GetTokenInformation(clientToken, TokenAppContainerSid, nullptr, 0, &needed); std::vector<BYTE> data(needed);
|
|
if (GetTokenInformation(clientToken, TokenAppContainerSid, data.data(), needed, &needed)) authenticated = EqualSid(reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(data.data())->TokenAppContainer, expected) != FALSE;
|
|
}
|
|
CloseHandle(clientToken);
|
|
}
|
|
if (process) CloseHandle(process);
|
|
}
|
|
FreeSid(expected);
|
|
char input[8] = {}; DWORD read = 0, written = 0;
|
|
bool ok = authenticated && ReadFile(pipe, input, sizeof(input), &read, nullptr) && read == 4 && std::string(input, read) == "PING";
|
|
const char response[] = "BROKER_PONG";
|
|
if (ok) ok = WriteFile(pipe, response, sizeof(response) - 1, &written, nullptr) && written == sizeof(response) - 1;
|
|
if (ok) FlushFileBuffers(pipe);
|
|
DisconnectNamedPipe(pipe); CloseHandle(pipe);
|
|
std::printf("PIPE_%s\n", ok ? "AUTHENTICATED" : "REJECTED"); return ok ? 0 : 29;
|
|
}
|
|
static int pipeClient(const std::wstring& id) {
|
|
if (!registryId(id)) return 2;
|
|
HANDLE token = nullptr;
|
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
|
DWORD bytes = 0; GetTokenInformation(token, TokenAppContainerSid, nullptr, 0, &bytes); std::vector<BYTE> data(bytes);
|
|
if (GetTokenInformation(token, TokenAppContainerSid, data.data(), bytes, &bytes)) {
|
|
const PSID sid = reinterpret_cast<TOKEN_APPCONTAINER_INFORMATION*>(data.data())->TokenAppContainer;
|
|
LPWSTR text = nullptr;
|
|
if (sid && ConvertSidToStringSidW(sid, &text)) { std::printf("PIPE_CLIENT_SID %ls\n", text); LocalFree(text); std::fflush(stdout); }
|
|
}
|
|
CloseHandle(token);
|
|
}
|
|
HANDLE pipe = INVALID_HANDLE_VALUE; const auto deadline = GetTickCount64() + 4000;
|
|
do {
|
|
pipe = CreateFileW(pipeName(id).c_str(), FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE, 0, nullptr, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, nullptr);
|
|
if (pipe != INVALID_HANDLE_VALUE) break;
|
|
if (GetLastError() != ERROR_FILE_NOT_FOUND && GetLastError() != ERROR_PIPE_BUSY) { std::printf("PIPE_DENIED %lu\n", GetLastError()); return 29; }
|
|
Sleep(10);
|
|
} while (GetTickCount64() < deadline);
|
|
if (pipe == INVALID_HANDLE_VALUE) { std::printf("PIPE_UNAVAILABLE %lu\n", GetLastError()); return 29; }
|
|
DWORD written = 0, read = 0; char response[64] = {};
|
|
const bool ok = WriteFile(pipe, "PING", 4, &written, nullptr) && written == 4 && ReadFile(pipe, response, sizeof(response), &read, nullptr);
|
|
CloseHandle(pipe);
|
|
if (ok) std::fwrite(response, 1, read, stdout);
|
|
return ok && std::string(response, read) == "BROKER_PONG" ? 0 : 29;
|
|
}
|
|
static int stdioBroker(const std::wstring& mode, const std::wstring& wideId) {
|
|
if (!registryId(wideId)) return 2;
|
|
std::string id;
|
|
for (wchar_t ch : wideId) id += static_cast<char>(ch);
|
|
if (mode == L"--broker-oversize") {
|
|
const std::string frame = "\x1eYCODE_BROKER_V1 " + std::string(20000, 'x') + "\n";
|
|
std::fwrite(frame.data(), 1, frame.size(), stdout); std::fflush(stdout); Sleep(5000); return 30;
|
|
}
|
|
if (mode == L"--broker-flood") {
|
|
for (char index = '0'; index < '9'; ++index) {
|
|
auto current = id; current.back() = index;
|
|
std::printf("\x1eYCODE_BROKER_V1 {\"requestId\":\"%s\",\"operation\":\"echo\",\"payload\":\"probe-echo\"}\n", current.c_str());
|
|
}
|
|
std::fflush(stdout); Sleep(5000); return 30;
|
|
}
|
|
for (int attempt = 0; attempt < (mode == L"--broker-replay" ? 2 : 1); ++attempt) {
|
|
std::string frame = "\x1eYCODE_BROKER_V1 {\"requestId\":\"" + id + "\",\"operation\":\"" +
|
|
(mode == L"--broker-deny" ? "file_read" : "echo") + "\",\"payload\":\"probe-echo\"";
|
|
if (mode == L"--broker-spoof") frame += ",\"runId\":\"unrelated-run\"";
|
|
frame += "}\n"; std::fwrite(frame.data(), 1, frame.size(), stdout); std::fflush(stdout);
|
|
std::string reply; char buffer[1024] = {}; DWORD read = 0;
|
|
while (reply.find('\n') == std::string::npos && reply.size() < 16384 && ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &read, nullptr) && read) reply.append(buffer, read);
|
|
const bool rejected = mode == L"--broker-deny" || mode == L"--broker-spoof";
|
|
if (reply.find("\"requestId\":\"" + id + "\"") == std::string::npos || reply.find(rejected ? "\"ok\":false" : "\"ok\":true") == std::string::npos) return 30;
|
|
}
|
|
std::puts(mode == L"--broker-deny" || mode == L"--broker-spoof" ? "BROKER_DENIAL_RECEIVED" : "BROKER_ECHO_RECEIVED"); return 0;
|
|
}
|
|
static int registryProbe(const std::wstring& mode, const std::wstring& id) {
|
|
if (!registryId(id)) return 2;
|
|
const std::wstring path = L"Software\\YCodeSandboxTests\\" + id;
|
|
if (mode == L"--registry-delete") {
|
|
const LSTATUS result = RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
|
|
std::printf("REG_DELETE %ld\n", result);
|
|
RegDeleteKeyW(HKEY_CURRENT_USER, L"Software\\YCodeSandboxTests");
|
|
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND ? 0 : 27;
|
|
}
|
|
HKEY key = nullptr;
|
|
const bool read = mode == L"--registry-read";
|
|
const LSTATUS opened = read ? RegOpenKeyExW(HKEY_CURRENT_USER, path.c_str(), 0, KEY_QUERY_VALUE, &key)
|
|
: RegCreateKeyExW(HKEY_CURRENT_USER, path.c_str(), 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key, nullptr);
|
|
if (opened != ERROR_SUCCESS) { std::printf("REG_OPEN_DENIED %ld\n", opened); return 27; }
|
|
DWORD value = mode == L"--registry-create" ? 0x123456 : 99, bytes = sizeof(value), type = 0;
|
|
const LSTATUS result = read ? RegQueryValueExW(key, L"fixture", nullptr, &type, reinterpret_cast<BYTE*>(&value), &bytes)
|
|
: RegSetValueExW(key, L"fixture", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
|
|
RegCloseKey(key);
|
|
if (read) std::printf("REG_READ %ld %lu\n", result, value);
|
|
else std::printf("REG_WRITE %ld\n", result);
|
|
return result == ERROR_SUCCESS ? 0 : 27;
|
|
}
|
|
|
|
static std::wstring quoted(const std::wstring& value) { return L"\"" + value + L"\""; }
|
|
static int writeFile(const wchar_t* path) {
|
|
HANDLE file = CreateFileW(path, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (file == INVALID_HANDLE_VALUE) { std::printf("WRITE_DENIED %lu\n", GetLastError()); return 23; }
|
|
const char text[] = "native-probe"; DWORD written = 0;
|
|
const bool ok = WriteFile(file, text, sizeof(text) - 1, &written, nullptr) != FALSE;
|
|
CloseHandle(file); return ok ? 0 : 23;
|
|
}
|
|
static int spawnChild(const std::wstring& arguments, bool wait, DWORD flags = 0) {
|
|
wchar_t executable[32768] = {};
|
|
if (!GetModuleFileNameW(nullptr, executable, 32768)) return 25;
|
|
std::wstring command = quoted(executable) + L" " + arguments;
|
|
STARTUPINFOW startup = {}; startup.cb = sizeof(startup);
|
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
PROCESS_INFORMATION process = {};
|
|
if (!CreateProcessW(executable, command.data(), nullptr, nullptr, TRUE, flags, nullptr, nullptr, &startup, &process)) {
|
|
std::printf("SPAWN_DENIED %lu\n", GetLastError()); return 25;
|
|
}
|
|
CloseHandle(process.hThread);
|
|
DWORD exit = 0;
|
|
if (wait) { WaitForSingleObject(process.hProcess, 10000); GetExitCodeProcess(process.hProcess, &exit); }
|
|
CloseHandle(process.hProcess); return static_cast<int>(exit);
|
|
}
|
|
int wmain(int argc, wchar_t** argv) {
|
|
if (argc < 2) return 2;
|
|
const std::wstring mode = argv[1];
|
|
if (mode.rfind(L"--broker-", 0) == 0 && argc == 3) return stdioBroker(mode, argv[2]);
|
|
if (mode == L"--pipe-server" && argc == 4) return pipeServer(argv[2], argv[3]);
|
|
if (mode == L"--pipe-client" && argc == 3) return pipeClient(argv[2]);
|
|
if (mode == L"--registry-create" || mode == L"--registry-read" || mode == L"--registry-write" || mode == L"--registry-delete") return argc == 3 ? registryProbe(mode, argv[2]) : 2;
|
|
if (mode == L"--hold-directory" && argc == 5) {
|
|
HANDLE directory = CreateFileW(argv[2], FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
|
|
if (directory == INVALID_HANDLE_VALUE) return 28;
|
|
FILE_ATTRIBUTE_TAG_INFO tag = {};
|
|
if (!GetFileInformationByHandleEx(directory, FileAttributeTagInfo, &tag, sizeof(tag)) || (tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { CloseHandle(directory); return 28; }
|
|
if (writeFile(argv[3])) { CloseHandle(directory); return 28; }
|
|
const ULONGLONG deadline = GetTickCount64() + 5000;
|
|
while (GetFileAttributesW(argv[4]) == INVALID_FILE_ATTRIBUTES && GetTickCount64() < deadline) Sleep(10);
|
|
CloseHandle(directory); return 0;
|
|
}
|
|
if (mode == L"--host-facts") {
|
|
HANDLE token = nullptr; TOKEN_ELEVATION elevation = {}; DWORD bytes = 0, container = 0;
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return 3;
|
|
const bool elevated = GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &bytes) && elevation.TokenIsElevated;
|
|
GetTokenInformation(token, TokenIsAppContainer, &container, sizeof(container), &bytes); CloseHandle(token);
|
|
std::printf("{\"elevated\":%s,\"appContainer\":%s}\n", elevated ? "true" : "false", container ? "true" : "false"); return 0;
|
|
}
|
|
if (mode == L"--info") {
|
|
HANDLE token = nullptr; DWORD container = 0, bytes = 0; BOOL job = FALSE;
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return 3;
|
|
GetTokenInformation(token, TokenIsAppContainer, &container, sizeof(container), &bytes); CloseHandle(token);
|
|
IsProcessInJob(GetCurrentProcess(), nullptr, &job);
|
|
wchar_t secret[128] = {};
|
|
const bool inherited = GetEnvironmentVariableW(L"YCODE_FAKE_SECRET", secret, 128) != 0;
|
|
std::printf("{\"appContainer\":%s,\"job\":%s,\"secretInherited\":%s}\n", container ? "true" : "false", job ? "true" : "false", inherited ? "true" : "false");
|
|
return container && job && !inherited ? 0 : 4;
|
|
}
|
|
if (mode == L"--write" && argc == 3) return writeFile(argv[2]);
|
|
if (mode == L"--delete" && argc == 3) {
|
|
const BOOL deleted = DeleteFileW(argv[2]);
|
|
std::printf("DELETE_%s %lu\n", deleted ? "OK" : "DENIED", deleted ? 0 : GetLastError());
|
|
return deleted ? 0 : 26;
|
|
}
|
|
if (mode == L"--paths") {
|
|
wchar_t cwd[32768] = {}, full[32768] = {};
|
|
GetCurrentDirectoryW(32768, cwd); GetFullPathNameW(L".", 32768, full, nullptr);
|
|
std::wprintf(L"CWD=%ls FULL=%ls\n", cwd, full);
|
|
const DWORD attributes = GetFileAttributesW(L".");
|
|
std::printf("ATTR=%lu ERROR=%lu\n", attributes, GetLastError());
|
|
WIN32_FIND_DATAW data = {};
|
|
HANDLE found = FindFirstFileW(L".\\*", &data);
|
|
std::printf("FIND=%s ERROR=%lu\n", found == INVALID_HANDLE_VALUE ? "DENIED" : "OK", GetLastError());
|
|
if (found != INVALID_HANDLE_VALUE) FindClose(found);
|
|
return 0;
|
|
}
|
|
if (mode == L"--read" && argc == 3) {
|
|
HANDLE file = CreateFileW(argv[2], GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
|
if (file == INVALID_HANDLE_VALUE) { std::printf("READ_DENIED %lu\n", GetLastError()); return 22; }
|
|
char buffer[256] = {}; DWORD bytes = 0;
|
|
const bool ok = ReadFile(file, buffer, sizeof(buffer), &bytes, nullptr) != FALSE; CloseHandle(file);
|
|
if (ok) std::fwrite(buffer, 1, bytes, stdout);
|
|
return ok ? 0 : 22;
|
|
}
|
|
if (mode == L"--tcp" && argc == 3) {
|
|
WSADATA data = {}; if (WSAStartup(MAKEWORD(2, 2), &data)) return 24;
|
|
SOCKET socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (socket == INVALID_SOCKET) { WSACleanup(); return 24; }
|
|
sockaddr_in target = {}; target.sin_family = AF_INET; target.sin_port = htons(static_cast<u_short>(_wtoi(argv[2]))); target.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
u_long nonblocking = 1; ioctlsocket(socket, FIONBIO, &nonblocking);
|
|
int connected = connect(socket, reinterpret_cast<sockaddr*>(&target), sizeof(target));
|
|
int error = connected == 0 ? 0 : WSAGetLastError();
|
|
if (connected != 0 && error == WSAEWOULDBLOCK) {
|
|
fd_set ready, failed; FD_ZERO(&ready); FD_ZERO(&failed); FD_SET(socket, &ready); FD_SET(socket, &failed);
|
|
timeval timeout = {1, 0};
|
|
if (select(0, nullptr, &ready, &failed, &timeout) > 0) {
|
|
int length = sizeof(error); getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &length);
|
|
connected = error == 0 ? 0 : SOCKET_ERROR;
|
|
} else { connected = SOCKET_ERROR; error = WSAETIMEDOUT; }
|
|
}
|
|
std::printf("TCP_%s %d\n", connected == 0 ? "CONNECTED" : "DENIED", error);
|
|
closesocket(socket); WSACleanup(); return connected == 0 ? 0 : 24;
|
|
}
|
|
if (mode == L"--delay" && argc == 4) { Sleep(static_cast<DWORD>(_wtoi(argv[2]))); return writeFile(argv[3]); }
|
|
if (mode == L"--spawn-write" && argc == 3) return spawnChild(L"--write " + quoted(argv[2]), true);
|
|
if (mode == L"--spawn-delayed" && argc == 3) return spawnChild(L"--delay 1400 " + quoted(argv[2]), false);
|
|
if (mode == L"--breakaway" && argc == 3) return spawnChild(L"--write " + quoted(argv[2]), true, CREATE_BREAKAWAY_FROM_JOB);
|
|
return 2;
|
|
}
|