#include "access_token.h" #include "child_process.h" #include "elevation.h" #include "file_objects.h" #include "kernel_context.h" #include "text.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace raven { namespace { constexpr uint64_t current_process = UINT64_MAX; // A list the toolhelp calls walk: entries are built when the snapshot is taken. class Snapshot : public KernelObject { public: struct Process { uint32_t id, parent; std::u16string name; }; struct Module { uint64_t base; uint32_t size; std::u16string name, path; }; struct Thread { uint32_t id, owner; int32_t priority; }; std::vector processes; std::vector modules; std::vector threads; size_t next_process = 0, next_module = 0, next_thread = 0; }; // Window stations and desktops are named containers that windows would live in; here every window lives on the one display, so these only // keep names and which ones the process has selected. class WindowStation : public KernelObject { public: std::u16string_view type_name() const override { return u"WindowStation"; } std::u16string name = u"WinSta0"; }; class Desktop : public KernelObject { public: std::u16string_view type_name() const override { return u"Desktop"; } std::u16string name = u"Default"; }; uint64_t filetime_now() { timespec now{}; clock_gettime(CLOCK_REALTIME, &now); return (uint64_t(now.tv_sec) + 11644473600ULL) * 10000000ULL + uint64_t(now.tv_nsec) / 100; } } // CPU time a running child has used so far, in nanoseconds; 0 once the host no longer lists it. static uint64_t child_cpu_nanoseconds(pid_t pid) { std::ifstream stat("/proc/" + std::to_string(pid) + "/stat"); std::string text; std::getline(stat, text); const auto close = text.rfind(')'); if (close == std::string::npos) return 0; std::istringstream fields(text.substr(close + 2)); std::string field; uint64_t user = 0, system = 0; for (int index = 3; fields >> field; ++index) { if (index == 14) user = std::strtoull(field.c_str(), nullptr, 10); else if (index == 15) { system = std::strtoull(field.c_str(), nullptr, 10); break; } } return (user + system) * (1000000000ULL / uint64_t(sysconf(_SC_CLK_TCK))); } // The rights a handle holds as Windows reports them: the generic rights of the request mapped to the specific ones of the object's type. static uint32_t granted_access(std::u16string_view type, uint32_t requested) { struct Mapping { std::u16string_view type; uint32_t read, write, execute, all; }; static constexpr Mapping mappings[] = { {u"File", 0x120089, 0x120116, 0x1200a0, 0x1f01ff}, {u"Section", 0x20005, 0x20002, 0x20008, 0xf001f}, {u"Process", 0x20410, 0x20bea, 0x120000, 0x1fffff}, {u"Thread", 0x20048, 0x20037, 0x120000, 0x1fffff}, {u"Event", 0x20001, 0x20002, 0x120000, 0x1f0003}, {u"Mutant", 0x20001, 0x20000, 0x120000, 0x1f0001}, {u"Semaphore", 0x20001, 0x20002, 0x120000, 0x1f0003}, {u"Key", 0x20019, 0x20006, 0x20019, 0xf003f}, {u"Token", 0x20008, 0x200e0, 0x20000, 0xf01ff}, {u"Job", 0x20004, 0x2000b, 0x120000, 0x1f003f}, {u"IoCompletion", 0x20001, 0x20002, 0x120000, 0x1f0003}}; const auto found = std::find_if(std::begin(mappings), std::end(mappings), [&](const Mapping &item) { return item.type == type; }); if (found == std::end(mappings)) return requested & 0x01ffffffU; uint32_t granted = requested & 0x00ffffffU; if (requested & (0x10000000U | 0x02000000U)) granted |= found->all; if (requested & 0x80000000U) granted |= found->read; if (requested & 0x40000000U) granted |= found->write; if (requested & 0x20000000U) granted |= found->execute; return granted; } static InheritedHandle local_process_information(KernelContext &context, uint32_t information, size_t width) { InheritedHandle answer; if (width != 4 && width != 8) { answer.access = 0xc000000dU; return answer; } std::vector data; const auto put = [&](size_t offset, uint64_t value, size_t size) { std::memcpy(data.data() + offset, &value, size); }; switch (information) { case 0: { if (width == 4 && context.environment.peb() > UINT32_MAX) { answer.access = 0xc00000bbU; return answer; } const auto processors = std::clamp(::sysconf(_SC_NPROCESSORS_ONLN), 1, width * 8); const uint64_t affinity = processors == 64 ? UINT64_MAX : (uint64_t(1) << processors) - 1; data.assign(6 * width, 0); put(0, 259, 4); put(width, context.environment.peb(), width); put(2 * width, affinity, width); put(3 * width, 8, 4); put(4 * width, uint32_t(::getpid()), width); put(5 * width, uint32_t(::getppid()), width); break; } case 7: data.assign(width, 0); break; case 20: data.assign(4, 0); put(0, context.objects.count(), 4); break; case 26: data.assign(width, 0); put(0, context.process.mode() == NativeMode::x86 ? context.environment.peb() : 0, width); break; case 29: data.assign(4, 0); break; case 58: { const auto handles = context.objects.values(); data.assign(handles.size() * 4, 0); for (size_t index = 0; index < handles.size(); ++index) put(index * 4, handles[index], 4); break; } default: answer.access = 0xc0000003U; return answer; } answer.path.assign(reinterpret_cast(data.data()), data.size()); return answer; } void register_process_info_api(ApiRegistry ®istry, KernelContext &context) { auto &objects = context.objects; auto &modules = context.modules; const uint64_t started = filetime_now(); // A 32-bit program passes the current process handle as the 32-bit -1, which reaches here without its sign. const auto is_self = [&objects](uint64_t handle) { return handle == current_process || handle == 0xffffffffU || objects.object_as(handle) != nullptr; }; const auto request_process = [&context, &objects](uint64_t handle, InheritedHandle request) -> std::optional { const auto child = objects.object_as(handle); if (!child || child->exited()) return std::nullopt; auto *channel = child->channel.get(); if (!channel && child->pid == ::getppid()) channel = parent_channel(); const auto peer = channel ? nullptr : process_request_channel(child->pid); if (peer) channel = peer.get(); return channel ? channel->request_process(objects, context.threads, std::move(request)) : std::nullopt; }; enable_process_requests(); objects.process_request = [&context](const InheritedHandle &request) { if (request.kind == "process:image") { InheritedHandle answer; const auto &path = context.launch.image_path; answer.path.assign(reinterpret_cast(path.data()), path.size() * sizeof(char16_t)); return answer; } if (request.kind == "process:threads") { InheritedHandle answer; for (const auto id : context.threads.thread_ids()) { const auto thread = context.threads.find(id); const int32_t priority = int32_t(std::clamp(8LL + thread->priority, 1, 15)); answer.path.append(reinterpret_cast(&id), sizeof(id)); answer.path.append(reinterpret_cast(&priority), sizeof(priority)); } return answer; } if (request.kind == "process:thread-token-set" || request.kind == "process:thread-token-open") { InheritedHandle answer; const auto thread = context.threads.find(request.flags); if (request.kind == "process:thread-token-set") { const auto token = request.handle ? context.objects.object_as(request.handle) : nullptr; if (!thread || thread->state == GuestThread::State::done) answer.access = 0xc000004bU; else if (request.handle && !token) answer.access = 0xc0000008U; else if (token && !(context.objects.access_of(request.handle) & 4)) answer.access = 0xc0000022U; else if (token && token->data().type != 2) answer.access = 0xc00000a8U; else thread->impersonation = token; if (token) context.objects.close(request.handle); } else if (!thread) { answer.access = 0xc0000008U; } else if (!thread->impersonation) { answer.access = 0xc000007cU; } else { thread->impersonation->describe(answer); } return answer; } if (request.kind == "process:token") { InheritedHandle answer; context.process_token->describe(answer); return answer; } if (request.kind == "process:query") return local_process_information(context, uint32_t(request.handle), request.flags); InheritedHandle answer; try { if (request.kind == "process:allocate" || request.kind == "process:free" || request.kind == "process:protect") { if (request.kind == "process:allocate" && request.handle_access == 4 && context.process.mode() != NativeMode::x86) { answer.access = 0xc00000bbU; return answer; } if (request.path.size() != sizeof(uint64_t)) { answer.access = 0xc000000dU; return answer; } uint64_t size; std::memcpy(&size, request.path.data(), sizeof(size)); const auto operation = request.kind == "process:allocate" ? context.memory.allocate(request.handle, size, request.flags, request.bits) : request.kind == "process:free" ? context.memory.free(request.handle, size, request.flags) : context.memory.protect(request.handle, size, request.bits); answer.access = operation.status; answer.size = operation.address; answer.flags = operation.previous; answer.path.assign(reinterpret_cast(&operation.size), sizeof(operation.size)); } else if (request.kind == "process:query-memory") { if (const auto information = context.memory.query(request.handle)) answer.path.assign(reinterpret_cast(information->data()), information->size()); else answer.access = 0xc000000dU; } else if (request.kind == "process:read" && request.flags <= 60000) { const auto data = context.process.read_memory(request.handle, request.flags); answer.path.assign(reinterpret_cast(data.data()), data.size()); } else if (request.kind == "process:write" && request.path.size() <= 60000) { context.process.write_memory(request.handle, {reinterpret_cast(request.path.data()), request.path.size()}); } else { answer.access = 0xc000000dU; } } catch (const std::bad_alloc &) { answer.access = 0xc0000017U; } catch (const std::runtime_error &) { answer.access = 0xc0000005U; } return answer; }; const auto open_token = [&context, &objects, is_self, request_process](ApiCall &call, bool native, uint32_t attributes, size_t output) { const auto fail = [&](uint32_t status, uint32_t error) { if (native) call.ret(status); else call.fail(error); }; try { call.process.validate_write_memory(call[output], call.pointer_width()); const bool own = is_self(call[0]); const auto other = objects.object_as(call[0]); if (!own && (!other || other->exited())) { fail(objects.object(call[0]) && !other ? 0xc0000024U : 0xc0000008U, 6); return; } if (call[0] != current_process && call[0] != 0xffffffffU && !(objects.access_of(call[0]) & 0x1400)) { fail(0xc0000022U, 5); return; } const auto access = token_access(call.u32(1)); if ((attributes & ~2U) || !access) { fail(0xc000000dU, 87); return; } std::shared_ptr token = context.process_token; if (!own) { InheritedHandle request; request.kind = "process:token"; const auto answer = request_process(call[0], std::move(request)); if (!answer || answer->access || answer->descriptor < 0) { if (answer && answer->descriptor >= 0) ::close(answer->descriptor); fail(answer && answer->access ? answer->access : 0xc00000bbU, 50); return; } token = std::make_shared(answer->descriptor); } const auto made = objects.add_object(token); if (made.status) { fail(made.status, 8); return; } objects.set_access(made.value, *access); objects.set_inherit(made.value, (attributes & 2) != 0); call.store_pointer(call[output], made.value); call.ret(native ? 0 : 1); } catch (const ApiFault &) { fail(0xc0000005U, 998); } catch (const std::runtime_error &) { fail(0xc0000005U, 998); } catch (const std::bad_alloc &) { fail(0xc0000017U, 8); } }; registry.add("OpenProcessToken", 3, [open_token](ApiCall &call) { open_token(call, false, 0, 2); }); registry.add("NtOpenProcessToken", 3, [open_token](ApiCall &call) { open_token(call, true, 0, 2); }); registry.add("NtOpenProcessTokenEx", 4, [open_token](ApiCall &call) { open_token(call, true, call.u32(2), 3); }); registry.add("NtQueryInformationProcess", 5, [&context, &objects, is_self, request_process](ApiCall &call) { const bool own = is_self(call[0]); if (!own && !objects.object_as(call[0])) { call.ret(0xc0000008U); return; } if (call[0] != current_process && call[0] != 0xffffffffU && !(objects.access_of(call[0]) & 0x1400)) { call.ret(0xc0000022U); return; } InheritedHandle request; request.kind = "process:query"; request.handle = call.u32(1); request.flags = uint32_t(call.pointer_width()); // A child that has ended can no longer answer; its basic information (class 0) stays with its object: the exit status, its id // and this process as its parent. std::optional answer; if (const auto child = own ? nullptr : objects.object_as(call[0]); child && child->exited() && call.u32(1) == 0) { const size_t width = call.pointer_width(); std::string data(6 * width, '\0'); const auto put = [&](size_t offset, uint64_t value, size_t size) { std::memcpy(data.data() + offset, &value, size); }; put(0, child->exit_code(), 4); put(3 * width, 8, 4); put(4 * width, uint32_t(child->pid), width); put(5 * width, uint32_t(::getpid()), width); answer.emplace(); answer->path = std::move(data); } else { answer = own ? std::optional(local_process_information(context, call.u32(1), call.pointer_width())) : request_process(call[0], request); } if (!answer || answer->access) { call.ret(answer ? answer->access : 0xc0000001U); return; } try { // ProcessHandleTable returns the handle values that fit, in 32-bit entries even for a 64-bit caller. const size_t size = call.u32(1) == 58 ? std::min(answer->path.size(), size_t(call.u32(3) & ~3U)) : answer->path.size(); if (call[4]) call.store(call[4], uint32_t(size)); if (call.u32(3) < size) { call.ret(0xc0000004U); return; } if (!call[2] && size) { call.ret(0xc0000005U); return; } call.write(call[2], {reinterpret_cast(answer->path.data()), size}); call.ret(0); } catch (const ApiFault &) { call.ret(0xc0000005U); } }); registry.add("GetProcessHandleCount", 2, [®istry](ApiCall &call) { const auto query = registry.find("NtQueryInformationProcess"); ApiCall inner(call.process, call.environment, {call[0], 20, call[1], 4, 0}); query->second(inner); if (!inner.result) { call.ret(1); return; } const uint32_t status = uint32_t(inner.result); call.fail(status == 0xc0000008U ? 6 : status == 0xc0000022U ? 5 : status == 0xc0000005U ? 998 : 50); }); const auto memory_operation = [&objects, is_self, request_process](ApiCall &call, std::string_view kind) { const bool own = is_self(call[0]); if (!own && !objects.object_as(call[0])) { call.fail(6); return; } if (call[0] != current_process && call[0] != 0xffffffffU && !(objects.access_of(call[0]) & 8)) { call.fail(5); return; } const bool allocate = kind == "process:allocate", protect = kind == "process:protect"; if (protect) { if (!call[4]) { call.fail(87); return; } call.process.validate_write_memory(call[4], 4); if (own) { const uint64_t base = call[1] & ~uint64_t(4095), offset = call[1] - base; if (call[2] > UINT64_MAX - offset - 4095) { call.fail(87); return; } const uint64_t length = (call[2] + offset + 4095) & ~uint64_t(4095); if (call[4] >= base ? call[4] - base < length : base - call[4] < 4) { call.fail(50); return; } } } InheritedHandle request; request.kind = kind; request.handle = call[1]; request.flags = call.u32(3); request.bits = call.u32(allocate ? 4 : 3); request.handle_access = uint32_t(call.pointer_width()); const uint64_t size = call[2]; request.path.assign(reinterpret_cast(&size), sizeof(size)); const auto answer = own ? std::optional(objects.process_request(request)) : request_process(call[0], request); if (!answer || answer->access) { const uint32_t status = answer ? answer->access : 0xc0000001U; call.fail(status == 0xc0000017U ? 8 : status == 0xc0000018U || status == 0xc000002dU ? 487 : status == 0xc00000bbU ? 50 : 87); return; } if (protect) call.store(call[4], answer->flags); call.ret(allocate ? answer->size : 1); }; registry.add("VirtualAllocEx", 5, [memory_operation](ApiCall &call) { memory_operation(call, "process:allocate"); }); registry.add("VirtualFreeEx", 4, [memory_operation](ApiCall &call) { memory_operation(call, "process:free"); }); registry.add("VirtualProtectEx", 5, [memory_operation](ApiCall &call) { memory_operation(call, "process:protect"); }); registry.add("VirtualQueryEx", 4, [&objects, is_self, request_process](ApiCall &call) { const bool own = is_self(call[0]); if (!own && !objects.object_as(call[0])) { call.fail(6); return; } if (call[0] != current_process && call[0] != 0xffffffffU && !(objects.access_of(call[0]) & 0x400)) { call.fail(5); return; } const size_t size = call.pointer_width() == 8 ? 48 : 28; if (call[3] < size) { call.fail(24); return; } InheritedHandle request; request.kind = "process:query-memory"; request.handle = call[1]; const auto answer = own ? std::optional(objects.process_request(request)) : request_process(call[0], request); if (!answer || answer->access || answer->path.size() != 48) { call.fail(87); return; } std::vector data(size); if (size == 48) { std::memcpy(data.data(), answer->path.data(), size); } else { constexpr size_t offsets[] = {0, 8, 16, 24, 32, 36, 40}; for (const auto offset : {0, 8, 24}) { uint64_t value; std::memcpy(&value, answer->path.data() + offset, 8); if (value > UINT32_MAX) { call.fail(50); return; } } for (size_t index = 0; index < 7; ++index) std::memcpy(data.data() + index * 4, answer->path.data() + offsets[index], 4); } call.write(call[2], data); call.ret(size); }); const auto open_process = [&objects](uint32_t id, uint32_t access, bool inherit) -> NativeObjectResult { if (!id || id > INT32_MAX) return {0, 0xc000000bU}; if (access & ~0xf21fffffU) return {0, 0xc000000dU}; std::shared_ptr object; if (id == uint32_t(::getpid())) { object = std::make_shared(); } else { object = find_child(pid_t(id)); if (!object && runtime_process_alive(pid_t(id))) object = std::make_shared(pid_t(id), true); } if (!object) return {0, 0xc000000bU}; const auto made = objects.add_object(object); if (!made.status) { objects.set_access(made.value, granted_access(u"Process", access)); objects.set_inherit(made.value, inherit); } return made; }; registry.add("OpenProcess", 3, [open_process](ApiCall &call) { const auto made = open_process(call.u32(2), call.u32(0), call.u32(1) != 0); if (made.status) call.fail(made.status == 0xc000000bU || made.status == 0xc000000dU ? 87 : 4); else call.ret(made.value); }); registry.add("NtOpenProcess", 4, [open_process, &context](ApiCall &call) { try { call.process.validate_write_memory(call[0], call.pointer_width()); if (!call[3]) { call.ret(0xc0000030U); return; } uint32_t attributes = 0; if (call[2]) { const size_t width = call.pointer_width(), root_at = width == 8 ? 8 : 4; if (call.load(call[2]) != (width == 8 ? 48 : 24) || call.load_pointer(call[2] + root_at)) { call.ret(0xc000000dU); return; } if (call.load_pointer(call[2] + root_at + width)) { call.ret(0xc0000030U); return; } attributes = call.load(call[2] + root_at + 2 * width); if (attributes & ~0x42U) { call.ret(0xc00000bbU); return; } } const auto id = call.load_pointer(call[3]), thread = call.load_pointer(call[3] + call.pointer_width()); if (id > INT32_MAX) { call.ret(0xc000000bU); return; } if (thread && (id != uint32_t(::getpid()) || thread > UINT32_MAX || !context.threads.find(uint32_t(thread)))) { call.ret(0xc00000bbU); return; } const auto made = open_process(uint32_t(id), call.u32(1), (attributes & 2) != 0); if (!made.status) call.store_pointer(call[0], made.value); call.ret(made.status); } catch (const ApiFault &) { call.ret(0xc0000005U); } catch (const std::runtime_error &) { call.ret(0xc0000005U); } catch (const std::bad_alloc &) { call.ret(0xc0000017U); } }); // A 32-bit program runs under WOW64 on the 64-bit system; a 64-bit one does not. registry.add("IsWow64Process", 2, [is_self](ApiCall &call) { if (!call[1]) { call.fail(87); return; } call.store(call[1], call.pointer_width() == 4 && is_self(call[0]) ? 1 : 0); call.ret(1); }); registry.add("IsWow64Process2", 3, [](ApiCall &call) { if (!call[1]) { call.fail(87); return; } call.store(call[1], call.pointer_width() == 4 ? 0x14c : 0); if (call[2]) call.store(call[2], 0x8664); call.ret(1); }); // The machine runs x64 programs in user and kernel mode and 32-bit x86 ones in the WOW64 container; nothing else. registry.add("GetMachineTypeAttributes", 2, [](ApiCall &call) { if (!call[1]) { call.ret(0x80070057U); return; } const uint16_t machine = uint16_t(call.u32(0)); call.store(call[1], machine == 0x8664 ? 3 : machine == 0x14c ? 5 : 0); call.ret(0); }); registry.add("GetProcessTimes", 5, [started, is_self, &objects](ApiCall &call) { if (const auto child = objects.object_as(call[0]); child && call[1] && call[2] && call[3] && call[4]) { call.store(call[1], started); call.store(call[2], 0); call.store(call[3], 0); call.store(call[4], child_cpu_nanoseconds(child->pid) / 100); call.ret(1); return; } if (!is_self(call[0]) || !call[1] || !call[2] || !call[3] || !call[4]) { call.fail(call[1] && is_self(call[0]) ? 87 : 6); return; } timespec cpu{}; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu); call.store(call[1], started); call.store(call[2], 0); call.store(call[3], 0); call.store(call[4], (uint64_t(cpu.tv_sec) * 1000000000ULL + uint64_t(cpu.tv_nsec)) / 100); call.ret(1); }); // IO_COUNTERS: the read, write and other operation counts, then the bytes of each. Raven's host process performs a program's I/O, // so its Linux counters (/proc//io) are the program's; no operation is counted as "other". registry.add("GetProcessIoCounters", 2, [is_self, &objects](ApiCall &call) { const auto child = objects.object_as(call[0]); if ((!child && !is_self(call[0])) || !call[1]) { call.fail(call[1] ? 6 : 87); return; } std::ifstream source("/proc/" + (child ? std::to_string(child->pid) : std::string("self")) + "/io"); std::map counts; for (std::string name; source >> name;) { uint64_t value = 0; source >> value; counts[name] = value; } const uint64_t values[6] = {counts["syscr:"], counts["syscw:"], 0, counts["rchar:"], counts["wchar:"], 0}; call.write(call[1], std::span(reinterpret_cast(values), sizeof values)); call.ret(1); }); registry.add("GetThreadTimes", 5, [started](ApiCall &call) { if (!call[1] || !call[2] || !call[3] || !call[4]) { call.fail(87); return; } timespec cpu{}; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpu); call.store(call[1], started); call.store(call[2], 0); call.store(call[3], 0); call.store(call[4], (uint64_t(cpu.tv_sec) * 1000000000ULL + uint64_t(cpu.tv_nsec)) / 100); call.ret(1); }); // Cycle counts are taken as CPU time at a nominal 1 GHz, so one nanosecond is one cycle. registry.add("QueryThreadCycleTime", 2, [](ApiCall &call) { if (!call[1]) { call.fail(87); return; } timespec cpu{}; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpu); call.store(call[1], uint64_t(cpu.tv_sec) * 1000000000ULL + uint64_t(cpu.tv_nsec)); call.ret(1); }); registry.add("QueryProcessCycleTime", 2, [is_self, &objects](ApiCall &call) { if (const auto child = objects.object_as(call[0]); child && call[1]) { call.store(call[1], child_cpu_nanoseconds(child->pid)); call.ret(1); return; } if (!call[1] || !is_self(call[0])) { call.fail(call[1] ? 6 : 87); return; } timespec cpu{}; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu); call.store(call[1], uint64_t(cpu.tv_sec) * 1000000000ULL + uint64_t(cpu.tv_nsec)); call.ret(1); }); registry.add("GetSystemTimes", 3, [](ApiCall &call) { timespec uptime{}; clock_gettime(CLOCK_BOOTTIME, &uptime); const uint64_t total = (uint64_t(uptime.tv_sec) * 1000000000ULL + uint64_t(uptime.tv_nsec)) / 100; if (call[0]) call.store(call[0], total / 2); if (call[1]) call.store(call[1], 0); if (call[2]) call.store(call[2], total / 2); call.ret(1); }); registry.add("RtlCaptureStackBackTrace", 4, [](ApiCall &call) { if (call[3]) call.store(call[3], 0); call.ret(0); }); // The window station and desktop: one of each, always visible. auto station = std::make_shared(0); registry.add("GetProcessWindowStation", 0, [&objects, station](ApiCall &call) { if (!*station) { const auto made = objects.add_object(std::make_shared()); if (made.status) { call.fail(4, 0); return; } *station = made.value; } call.ret(*station); }); // The names a program creates are kept so that opening one finds it again; the first station and desktop are the default ones. auto stations = std::make_shared>(); auto desktops = std::make_shared>(); auto desktop_now = std::make_shared(0); const auto make_named = [&objects](ApiCall &call, auto object, const std::u16string &name, std::shared_ptr> table) { object->name = name; const auto made = objects.add_object(object); if (made.status) { call.fail(4, 0); return; } (*table)[name] = made.value; call.ret(made.value); }; registry.add("CreateWindowStationW", 4, [make_named, stations](ApiCall &call) { make_named(call, std::make_shared(), call[0] ? call.wide(call[0], 256) : u"WinSta0", stations); }); registry.add("CreateDesktopW", 6, [make_named, desktops](ApiCall &call) { make_named(call, std::make_shared(), call[0] ? call.wide(call[0], 256) : u"Default", desktops); }); registry.add("OpenWindowStationW", 3, [stations](ApiCall &call) { const auto found = stations->find(call[0] ? call.wide(call[0], 256) : u"WinSta0"); if (found == stations->end()) { call.fail(2, 0); return; } call.ret(found->second); }); registry.add("OpenDesktopW", 4, [desktops](ApiCall &call) { const auto found = desktops->find(call[0] ? call.wide(call[0], 256) : u"Default"); if (found == desktops->end()) { call.fail(2, 0); return; } call.ret(found->second); }); registry.add("SetProcessWindowStation", 1, [&objects, station](ApiCall &call) { if (!objects.object_as(call[0])) { call.fail(6); return; } *station = call[0]; call.ret(1); }); registry.add("CloseWindowStation", 1, [&objects](ApiCall &call) { call.ret(objects.close(call[0]) == 0); }); registry.add("CloseDesktop", 1, [&objects](ApiCall &call) { call.ret(objects.close(call[0]) == 0); }); registry.add("SetThreadDesktop", 1, [&objects, desktop_now](ApiCall &call) { if (!objects.object_as(call[0])) { call.fail(6); return; } *desktop_now = call[0]; call.ret(1); }); registry.add("GetThreadDesktop", 1, [&objects, desktop_now, desktops](ApiCall &call) { if (!*desktop_now) { const auto made = objects.add_object(std::make_shared()); if (made.status) { call.fail(4, 0); return; } *desktop_now = made.value; (*desktops)[u"Default"] = made.value; } call.ret(*desktop_now); }); // The names come back as wide or ANSI text by the form called. for (const bool wide : {true, false}) registry.add(wide ? "GetUserObjectInformationW" : "GetUserObjectInformationA", 5, [&objects, wide](ApiCall &call) { const auto named = objects.object_as(call[0]); const auto desktop = objects.object_as(call[0]); if (!named && !desktop) { call.fail(6); return; } std::vector data; switch (call.u32(1)) { case 1: { // USEROBJECTFLAGS: the window station is visible (WSF_VISIBLE) except to a process in the services' session. data.assign(12, 0); data[8] = named && process_noninteractive() ? 0 : 1; break; } case 2: case 3: { const std::u16string text = call.u32(1) == 2 ? (named ? named->name : desktop->name) : named ? u"WindowStation" : u"Desktop"; if (wide) { data.resize((text.size() + 1) * 2); std::memcpy(data.data(), text.data(), text.size() * 2); } else { const auto narrow = to_utf8(text); data.assign(narrow.begin(), narrow.end()); data.push_back(0); } break; } default: call.fail(87); return; } if (call[4]) call.store(call[4], uint32_t(data.size())); if (!call[2] || call.u32(3) < data.size()) { call.fail(122); return; } call.write(call[2], data); call.ret(1); }); // The module list of the program: the executable first, then the libraries, as psapi reports them. const auto module_list = [&modules, &context]() { std::vector list; list.push_back(&context.entry); for (const auto &[name, module] : modules.all()) if (module.get() != &context.entry) list.push_back(module.get()); return list; }; registry.add("EnumProcessModules", 4, [is_self, module_list](ApiCall &call) { if (!is_self(call[0]) || !call[3]) { call.fail(call[3] ? 6 : 87); return; } const auto list = module_list(); const size_t width = call.pointer_width(); call.store(call[3], uint32_t(list.size() * width)); const size_t fit = std::min(list.size(), call.u32(2) / width); for (size_t index = 0; index < fit; ++index) call.store_pointer(call[1] + index * width, list[index]->loaded.base()); call.ret(1); }); registry.add("EnumProcessModulesEx", 5, [is_self, module_list](ApiCall &call) { if (!is_self(call[0]) || !call[3]) { call.fail(call[3] ? 6 : 87); return; } const auto list = module_list(); const size_t width = call.pointer_width(); call.store(call[3], uint32_t(list.size() * width)); const size_t fit = std::min(list.size(), call.u32(2) / width); for (size_t index = 0; index < fit; ++index) call.store_pointer(call[1] + index * width, list[index]->loaded.base()); call.ret(1); }); const auto module_at = [module_list](uint64_t handle) -> const PEModule * { for (const auto *module : module_list()) if (module->loaded.base() == handle) return module; return nullptr; }; // A module's file name into the caller's buffer, cut to fit; the count of characters copied is the result. const auto put_module_name = [](ApiCall &call, const std::u16string &text, bool wide) { const size_t capacity = call.u32(3); if (!call[2] || !capacity) { call.ret(0); return; } if (wide) call.ret(call.put_wide(call[2], text, capacity) > capacity ? capacity : std::min(text.size(), capacity - 1)); else call.ret(call.put_narrow(call[2], to_utf8(text), capacity) > capacity ? capacity : std::min(text.size(), capacity - 1)); }; // A module of the calling process by its handle; the program itself (no module named) is answered below, for any process. const auto base_name = [module_at, is_self, put_module_name](ApiCall &call, bool wide) { const auto *module = is_self(call[0]) ? module_at(call[1]) : nullptr; if (!module) { call.fail(is_self(call[0]) ? 126 : 6); return; } put_module_name(call, from_utf8(module->name), wide); }; // MODULEINFO: the base, the size of the image and the entry point. registry.add("GetModuleInformation", 4, [is_self, module_at](ApiCall &call) { const auto *module = is_self(call[0]) ? module_at(call[1]) : nullptr; if (!module || !call[2]) { call.fail(module ? 87 : 6); return; } const size_t width = call.pointer_width(); std::vector info(width == 4 ? 12 : 24); const uint64_t base = module->loaded.base(), entry = module->loaded.base() + module->image.entry; const uint32_t size = module->image.image_size; std::memcpy(info.data(), &base, width); std::memcpy(info.data() + width, &size, 4); std::memcpy(info.data() + (width == 4 ? 8 : 16), &entry, width); call.write(call[2], info); call.ret(1); }); // PROCESS_MEMORY_COUNTERS from the host's accounting of this process. registry.add("GetProcessMemoryInfo", 3, [is_self](ApiCall &call) { if (!is_self(call[0]) || !call[1]) { call.fail(call[1] ? 6 : 87); return; } uint64_t pages = 0, resident = 0; std::ifstream statm("/proc/self/statm"); statm >> pages >> resident; const size_t width = call.pointer_width(), size = width == 4 ? 40 : 72; std::vector counters(std::min(call.u32(2), size)); if (counters.size() < size) { call.fail(24); return; } const uint32_t cb = uint32_t(size); std::memcpy(counters.data(), &cb, 4); const uint64_t working = resident * 4096, committed = pages * 4096; const auto put = [&](size_t index, uint64_t value) { std::memcpy(counters.data() + 8 + (width == 4 ? 0 : 0) + index * width, &value, width); }; put(0, working); put(1, working); put(6, committed); put(7, committed); call.write(call[1], counters); call.ret(1); }); const auto enum_processes = [](ApiCall &call) { const size_t capacity = call.u32(1) / sizeof(uint32_t); if (!call[2] || (capacity && !call[0])) { call.fail(87); return; } std::vector ids; if (capacity) { ids.push_back(uint32_t(::getpid())); std::error_code error; for (const auto &entry : std::filesystem::directory_iterator("/proc", error)) { if (ids.size() == capacity) break; const auto name = entry.path().filename().string(); if (name.empty() || !std::all_of(name.begin(), name.end(), [](char c) { return c >= '0' && c <= '9'; })) continue; const auto pid = pid_t(std::stol(name)); if (pid != ::getpid() && runtime_process_alive(pid)) ids.push_back(uint32_t(pid)); } if (error) { call.fail(5); return; } call.write(call[0], {reinterpret_cast(ids.data()), ids.size() * sizeof(uint32_t)}); } call.store(call[2], uint32_t(ids.size() * sizeof(uint32_t))); call.ret(1); }; registry.add("EnumProcesses", 3, enum_processes); registry.add("K32EnumProcesses", 3, enum_processes); // The program a process runs, as a Win32 path or, native, as the volume device path; or the error that stops the query. const auto image_path = [&context, &objects, is_self, request_process](uint64_t handle, bool native) -> std::variant { const bool own = is_self(handle); if (!own && !objects.object_as(handle)) return uint32_t(6); if (handle != current_process && handle != 0xffffffffU && !(objects.access_of(handle) & 0x1400)) return uint32_t(5); auto path = context.launch.image_path; if (!own) { InheritedHandle request; request.kind = "process:image"; const auto answer = request_process(handle, std::move(request)); if (!answer || answer->access || answer->path.size() % sizeof(char16_t)) return uint32_t(31); path.resize(answer->path.size() / sizeof(char16_t)); std::memcpy(path.data(), answer->path.data(), answer->path.size()); } if (native) { if (path.size() < 3 || path[1] != u':') return uint32_t(50); path = NativePaths::device_path(path); } return path; }; for (const bool wide : {true, false}) registry.add(wide ? "QueryFullProcessImageNameW" : "QueryFullProcessImageNameA", 4, [&context, image_path, wide](ApiCall &call) { if (call.u32(1) > 1 || !call[2] || !call[3]) { call.fail(87); return; } const auto found = image_path(call[0], call.u32(1) != 0); if (const auto *error = std::get_if(&found)) { call.fail(*error); return; } const auto &path = std::get(found); const uint32_t capacity = call.load(call[3]); const auto narrow = wide ? std::string() : to_ansi(path, context.launch.ansi_code_page); const size_t length = wide ? path.size() : narrow.size(); if (capacity <= length) { call.fail(122); return; } if (wide) call.put_wide(call[2], path, length + 1); else call.put_narrow(call[2], narrow, length + 1); call.store(call[3], uint32_t(length)); call.ret(1); }); // Without a module, GetModuleBaseName names the file the process was started from. for (const bool wide : {true, false}) registry.add(wide ? "GetModuleBaseNameW" : "GetModuleBaseNameA", 4, [base_name, image_path, put_module_name, wide](ApiCall &call) { if (call[1]) { base_name(call, wide); return; } const auto found = image_path(call[0], false); if (const auto *error = std::get_if(&found)) { call.fail(*error); return; } const auto &path = std::get(found); const auto slash = path.find_last_of(u"\\/"); put_module_name(call, slash == path.npos ? path : path.substr(slash + 1), wide); }); // GetProcessImageFileName answers the device path and its length, cut to the buffer as GetModuleFileName does. for (const bool wide : {true, false}) registry.add(wide ? "GetProcessImageFileNameW" : "GetProcessImageFileNameA", 3, [&context, image_path, wide](ApiCall &call) { const auto found = image_path(call[0], true); if (const auto *error = std::get_if(&found)) { call.fail(*error); return; } const auto &path = std::get(found); const uint32_t capacity = call.u32(2); if (!call[1] || !capacity) { call.fail(122); return; } const auto narrow = wide ? std::string() : to_ansi(path, context.launch.ansi_code_page); const size_t length = std::min(wide ? path.size() : narrow.size(), capacity - 1); if (wide) call.put_wide(call[1], path, length + 1); else call.put_narrow(call[1], narrow, length + 1); if (length < (wide ? path.size() : narrow.size())) call.set_last_error(122); call.ret(length); }); // Toolhelp lists retain the entries present when the snapshot was taken. const auto take_snapshot = [&objects, &context, module_list](uint32_t flags) { auto snapshot = std::make_shared(); if (flags & 2) { // Every running instance of the runtime is a Windows process here: the programs this one started, its parent, and the // processes those started in turn. The name is the program each one runs; its parent is the instance that started it. const auto image = context.launch.image_path; snapshot->processes.push_back({uint32_t(::getpid()), runtime_process_alive(::getppid()) ? uint32_t(::getppid()) : 0, image.substr(image.find_last_of(u'\\') + 1)}); std::error_code error; for (const auto &entry : std::filesystem::directory_iterator("/proc", error)) { const auto name = entry.path().filename().string(); if (name.empty() || !std::all_of(name.begin(), name.end(), [](char c) { return c >= '0' && c <= '9'; })) continue; const auto pid = pid_t(std::stol(name)); if (pid == ::getpid() || !runtime_process_alive(pid)) continue; std::ifstream arguments(entry.path() / "cmdline", std::ios::binary); // The program is the runtime's first argument that is not an option: the last one for the processes it starts, and // the one before -- and the program's own arguments when it was started by hand. std::string argument, last; bool first = true; while (std::getline(arguments, argument, '\0')) { if (std::exchange(first, false) || argument.empty()) continue; if (argument == "--") break; if (argument[0] != '-') { last = argument; break; } } std::ifstream stat(entry.path() / "stat"); std::string text; std::getline(stat, text); pid_t parent = 0; if (const auto close = text.rfind(')'); close != std::string::npos) std::sscanf(text.c_str() + close + 2, "%*c %d", &parent); snapshot->processes.push_back({uint32_t(pid), runtime_process_alive(parent) ? uint32_t(parent) : 0, from_utf8(std::filesystem::path(last).filename().string())}); } } if (flags & 8) for (const auto *module : module_list()) { // The program's own path for the executable; the libraries beside it, or the system directory for those the runtime supplies. const auto name = from_utf8(module->name); const auto path = module == &context.entry ? context.launch.image_path : module->runtime_api ? u"C:\\Windows\\System32\\" + name : !module->file.empty() ? context.paths.dos(module->file) : context.paths.library_location(context.launch.image_path, name, context.entry.image.is_64); snapshot->modules.push_back({module->loaded.base(), module->image.image_size, name, path}); } if (flags & 4) { InheritedHandle request; request.kind = "process:threads"; const auto append_threads = [&](uint32_t owner, const InheritedHandle &answer) { if (answer.access || answer.path.size() % 8) return; for (size_t offset = 0; offset < answer.path.size(); offset += 8) { Snapshot::Thread thread{0, owner, 0}; std::memcpy(&thread.id, answer.path.data() + offset, 4); std::memcpy(&thread.priority, answer.path.data() + offset + 4, 4); snapshot->threads.push_back(thread); } }; append_threads(uint32_t(::getpid()), objects.process_request(request)); std::error_code error; for (const auto &entry : std::filesystem::directory_iterator("/proc", error)) { const auto name = entry.path().filename().string(); if (name.empty() || !std::all_of(name.begin(), name.end(), [](char c) { return c >= '0' && c <= '9'; })) continue; const auto pid = pid_t(std::stol(name)); if (pid == ::getpid()) continue; // A new child already has its parent channel while its process listener is still being initialized. const auto child = find_child(pid); const auto peer = child && child->channel ? nullptr : process_request_channel(pid); auto *channel = child && child->channel ? child->channel.get() : peer.get(); if (channel) if (const auto answer = channel->request_process(objects, context.threads, request)) append_threads(uint32_t(pid), *answer); } } return snapshot; }; registry.add("CreateToolhelp32Snapshot", 2, [&objects, take_snapshot](ApiCall &call) { const auto made = objects.add_object(take_snapshot(call.u32(0))); if (made.status) { call.fail(4, UINT64_MAX); return; } call.ret(made.value); }); // SystemProcessInformation (class 5) lists the same processes as a Toolhelp snapshot: a SYSTEM_PROCESS_INFORMATION for each, its // SYSTEM_THREAD_INFORMATION entries after it and its image name after those, linked by NextEntryOffset. A buffer too small is // answered with STATUS_INFO_LENGTH_MISMATCH and the length needed. The runtime answers the other classes before this one is reached. registry.add("NtQuerySystemInformation", 4, [take_snapshot](ApiCall &call) { if (call.u32(0) != 5) { call.ret(0xc0000003U); return; } const auto snapshot = take_snapshot(2 | 4); const bool wide = call.pointer_width() == 8; const size_t process_size = wide ? 0x100 : 0xb8, thread_size = wide ? 0x50 : 0x40, width = call.pointer_width(); const size_t name_at = 0x38, priority_at = wide ? 0x48 : 0x40, id_at = wide ? 0x50 : 0x44; std::vector bytes; const auto put = [&bytes](size_t at, uint64_t value, size_t size) { std::memcpy(bytes.data() + at, &value, size); }; for (const auto &process : snapshot->processes) { const size_t at = bytes.size(); std::vector threads; for (const auto &thread : snapshot->threads) if (thread.owner == process.id) threads.push_back(&thread); const size_t name_offset = process_size + threads.size() * thread_size; const size_t length = (name_offset + (process.name.size() + 1) * 2 + 7) & ~size_t(7); bytes.resize(at + length); put(at + 4, threads.size(), 4); put(at + name_at, process.name.size() * 2, 2); put(at + name_at + 2, (process.name.size() + 1) * 2, 2); put(at + priority_at, 8, 4); put(at + id_at, process.id, width); put(at + id_at + width, process.parent, width); put(at + id_at + width * 2 + 4, 1, 4); for (size_t index = 0; index < threads.size(); ++index) { const size_t entry = at + process_size + index * thread_size, client = wide ? 0x28 : 0x20; put(entry + client, process.id, width); put(entry + client + width, threads[index]->id, width); put(entry + client + width * 2, uint32_t(threads[index]->priority), 4); put(entry + client + width * 2 + 4, 8, 4); put(entry + client + width * 2 + 12, 5, 4); } std::memcpy(bytes.data() + at + name_offset, process.name.data(), process.name.size() * 2); if (&process != &snapshot->processes.back()) put(at, length, 4); } if (call[3]) call.store(call[3], uint32_t(bytes.size())); if (call.u32(2) < bytes.size()) { call.ret(0xc0000004U); return; } // The names are read through pointers into the caller's own buffer. size_t entry = 0; for (const auto &process : snapshot->processes) { const uint32_t threads = uint32_t(bytes[entry + 4] | bytes[entry + 5] << 8 | bytes[entry + 6] << 16 | bytes[entry + 7] << 24); put(entry + name_at + (wide ? 8 : 4), process.name.empty() ? 0 : call[1] + entry + process_size + threads * thread_size, width); uint32_t next = 0; std::memcpy(&next, bytes.data() + entry, 4); entry += next; } call.write(call[1], bytes); call.ret(0); }); // PROCESSENTRY32: sizes, the process and parent ids, the thread count and the file name; the default heap and module ids are zero. const auto process_entry = [](ApiCall &call, const Snapshot::Process &process, bool wide) { const size_t width = call.pointer_width(); const size_t id_at = 8, heap_at = width == 4 ? 12 : 16, threads_at = heap_at + width + 4, parent_at = threads_at + 4, name_at = parent_at + 12; const size_t size = name_at + 260 * (wide ? 2 : 1); const uint32_t cb = call.load(call[1]); if (cb < size - (width == 4 ? 0 : 0) && cb < name_at + 4) { call.fail(24); return false; } std::vector entry(std::min(cb, (size + width - 1) / width * width)); std::memcpy(entry.data(), &cb, 4); std::memcpy(entry.data() + id_at, &process.id, 4); const uint32_t threads = 1; std::memcpy(entry.data() + threads_at, &threads, 4); std::memcpy(entry.data() + parent_at, &process.parent, 4); const int32_t priority = 8; std::memcpy(entry.data() + parent_at + 4, &priority, 4); for (size_t index = 0; index < process.name.size() && index < 259; ++index) { if (wide) { if (name_at + index * 2 + 2 <= entry.size()) std::memcpy(entry.data() + name_at + index * 2, &process.name[index], 2); } else if (name_at + index < entry.size()) { entry[name_at + index] = char(process.name[index]); } } call.write(call[1], entry); return true; }; const auto process_step = [&objects, process_entry](ApiCall &call, bool first, bool wide) { const auto snapshot = objects.object_as(call[0]); if (!snapshot || !call[1]) { call.fail(snapshot ? 87 : 6); return; } if (first) snapshot->next_process = 0; if (snapshot->next_process >= snapshot->processes.size()) { call.fail(18); return; } if (!process_entry(call, snapshot->processes[snapshot->next_process], wide)) return; ++snapshot->next_process; call.ret(1); }; registry.add("Process32FirstW", 2, [process_step](ApiCall &call) { process_step(call, true, true); }); registry.add("Process32NextW", 2, [process_step](ApiCall &call) { process_step(call, false, true); }); registry.add("Process32First", 2, [process_step](ApiCall &call) { process_step(call, true, false); }); registry.add("Process32Next", 2, [process_step](ApiCall &call) { process_step(call, false, false); }); const auto thread_step = [&objects](ApiCall &call, bool first) { const auto snapshot = objects.object_as(call[0]); if (!snapshot || !call[1]) { call.fail(snapshot ? 87 : 6); return; } if (call.load(call[1]) < 28) { call.fail(24); return; } if (first) snapshot->next_thread = 0; if (snapshot->next_thread >= snapshot->threads.size()) { call.fail(18); return; } const auto &thread = snapshot->threads[snapshot->next_thread]; const uint32_t entry[] = {28, 0, thread.id, thread.owner, uint32_t(thread.priority), 0, 0}; call.write(call[1], {reinterpret_cast(entry), sizeof(entry)}); ++snapshot->next_thread; call.ret(1); }; registry.add("Thread32First", 2, [thread_step](ApiCall &call) { thread_step(call, true); }); registry.add("Thread32Next", 2, [thread_step](ApiCall &call) { thread_step(call, false); }); // MODULEENTRY32: the base and size, the module handle, then the module name and the full path. const auto module_step = [&objects](ApiCall &call, bool first, bool wide) { const auto snapshot = objects.object_as(call[0]); if (!snapshot || !call[1]) { call.fail(snapshot ? 87 : 6); return; } if (first) snapshot->next_module = 0; if (snapshot->next_module >= snapshot->modules.size()) { call.fail(18); return; } const auto &module = snapshot->modules[snapshot->next_module]; const size_t width = call.pointer_width(); const size_t base_at = width == 4 ? 20 : 24, size_at = base_at + width, handle_at = size_at + 4 + (width == 4 ? 0 : 4), name_at = handle_at + width, path_at = name_at + 256 * (wide ? 2 : 1), total = path_at + 260 * (wide ? 2 : 1); const uint32_t cb = call.load(call[1]); std::vector entry(std::min(cb, total)); if (entry.size() < path_at) { call.fail(24); return; } std::memcpy(entry.data(), &cb, 4); const uint32_t process = 1, one = 1; std::memcpy(entry.data() + 8, &process, 4); std::memcpy(entry.data() + 12, &one, 4); std::memcpy(entry.data() + 16, &one, 4); std::memcpy(entry.data() + base_at, &module.base, width); std::memcpy(entry.data() + size_at, &module.size, 4); std::memcpy(entry.data() + handle_at, &module.base, width); for (size_t index = 0; index < module.name.size() && index < 255; ++index) { if (wide) { if (name_at + index * 2 + 2 <= entry.size()) std::memcpy(entry.data() + name_at + index * 2, &module.name[index], 2); } else if (name_at + index < entry.size()) { entry[name_at + index] = char(module.name[index]); } } for (size_t index = 0; index < module.path.size() && index < 259; ++index) { if (wide) { if (path_at + index * 2 + 2 <= entry.size()) std::memcpy(entry.data() + path_at + index * 2, &module.path[index], 2); } else if (path_at + index < entry.size()) { entry[path_at + index] = char(module.path[index]); } } call.write(call[1], entry); ++snapshot->next_module; call.ret(1); }; registry.add("Module32FirstW", 2, [module_step](ApiCall &call) { module_step(call, true, true); }); registry.add("Module32NextW", 2, [module_step](ApiCall &call) { module_step(call, false, true); }); registry.add("Module32First", 2, [module_step](ApiCall &call) { module_step(call, true, false); }); registry.add("Module32Next", 2, [module_step](ApiCall &call) { module_step(call, false, false); }); // The path of a module in this process is the one GetModuleFileName reports; other processes do not expose their modules. for (const bool wide : {true, false}) registry.add(wide ? "GetModuleFileNameExW" : "GetModuleFileNameExA", 4, [®istry, is_self, wide](ApiCall &call) { if (!is_self(call[0])) { call.fail(5); return; } const auto entry = registry.find(wide ? "GetModuleFileNameW" : "GetModuleFileNameA"); if (!entry) { call.fail(5); return; } ApiCall module_call(call.process, call.environment, {call[1], call[2], call[3]}); entry->second(module_call); call.ret(module_call.result); }); // Every page the guest can address is resident: the host does not page guest memory out behind its back. registry.add("QueryWorkingSetEx", 3, [is_self](ApiCall &call) { if (!is_self(call[0])) { call.fail(5); return; } const size_t width = call.pointer_width(), count = call.u32(2) / (width * 2); for (size_t index = 0; index < count; ++index) { const uint64_t entry = call[1] + index * width * 2; const uint64_t attributes = 1 | (1ULL << 1 << 4) | (1ULL << 15); if (width == 4) call.store(entry + 4, uint32_t(attributes)); else call.store(entry + 8, attributes); } call.ret(1); }); // Process information classes: memory priority, commit figures, protection, leap seconds and the machine the process runs as. registry.add("GetProcessInformation", 4, [is_self](ApiCall &call) { if (!is_self(call[0]) && call[0] != UINT32_MAX) { call.fail(5); return; } std::vector info; const auto put = [&](size_t at, uint64_t value, size_t size) { std::memcpy(info.data() + at, &value, size); }; switch (call.u32(1)) { case 0: info.assign(4, 0); put(0, 5, 4); break; case 2: { uint64_t pages = 0, resident = 0; std::ifstream statm("/proc/self/statm"); statm >> pages >> resident; info.assign(32, 0); put(0, 8ULL << 30, 8); put(8, pages * 4096, 8); put(16, pages * 4096, 8); put(24, pages * 4096, 8); break; } case 4: info.assign(12, 0); put(0, 1, 4); break; case 7: info.assign(4, 0); put(0, 0xfffffffeU, 4); break; case 8: info.assign(8, 0); break; case 9: info.assign(8, 0); put(0, call.pointer_width() == 4 ? 0x14c : 0x8664, 2); put(4, 3, 4); break; default: call.fail(87); return; } if (!call[2] || call.u32(3) < info.size()) { call.fail(call[2] ? 24 : 87); return; } call.write(call[2], info); call.ret(1); }); // The file a mapped address of this process comes from, as an NT device path: the image the address lies in. for (const bool wide : {true, false}) registry.add(wide ? "GetMappedFileNameW" : "GetMappedFileNameA", 4, [®istry, &modules, is_self, wide](ApiCall &call) { if (!is_self(call[0])) { call.fail(5); return; } uint64_t base = 0; for (const auto &[name, module] : modules.all()) if (call[1] >= module->loaded.base() && call[1] - module->loaded.base() < module->image.image_size) base = module->loaded.base(); const auto path_of = registry.find("GetModuleFileNameW"); if (!base || !path_of) { call.fail(487); return; } const uint64_t scratch = call.process.map_memory(4096, PROT_READ | PROT_WRITE); ApiCall inner(call.process, call.environment, {base, scratch, 1024}); path_of->second(inner); const auto dos = inner.result ? call.wide(scratch, 1024) : std::u16string(); call.process.unmap_memory(scratch); if (dos.size() < 3 || dos[1] != u':') { call.fail(487); return; } const auto device = NativePaths::device_path(dos); const size_t capacity = call.u32(3); if (!capacity) { call.fail(122); return; } if (wide) call.put_wide(call[2], device.substr(0, capacity - 1), capacity); else call.put_narrow(call[2], to_utf8(device.substr(0, capacity - 1)), capacity); call.ret(std::min(device.size(), capacity - 1)); }); const auto copy_process_memory = [&objects, is_self, request_process](ApiCall &call, bool write, bool native) { if (call[4]) call.store_pointer(call[4], 0); const auto fail = [&](uint32_t status, uint32_t error) { if (native) call.ret(status); else call.fail(error); }; const bool own = is_self(call[0]); if (!own && !objects.object_as(call[0])) { fail(0xc0000008U, 6); return; } const uint32_t access = write ? 0x28 : 0x10; if (call[0] != current_process && call[0] != 0xffffffffU && (objects.access_of(call[0]) & access) != access) { fail(0xc0000022U, 5); return; } uint64_t copied = 0; bool failed = call[3] > UINT64_MAX - call[1] || call[3] > UINT64_MAX - call[2]; while (!failed && copied < call[3]) { const auto count = size_t(std::min(60000, call[3] - copied)); try { if (own) { const auto bytes = call.read((write ? call[2] : call[1]) + copied, count); call.write((write ? call[1] : call[2]) + copied, bytes); } else { InheritedHandle request; request.kind = write ? "process:write" : "process:read"; request.handle = call[1] + copied; request.flags = uint32_t(count); if (write) { const auto bytes = call.read(call[2] + copied, count); request.path.assign(reinterpret_cast(bytes.data()), bytes.size()); } const auto answer = request_process(call[0], std::move(request)); if (!answer || answer->access || (!write && answer->path.size() != count)) { failed = true; break; } if (!write) call.write(call[2] + copied, {reinterpret_cast(answer->path.data()), answer->path.size()}); } copied += count; } catch (const ApiFault &) { failed = true; } } if (call[4]) call.store_pointer(call[4], copied); if (failed) fail(0x8000000dU, 299); else call.ret(native ? 0 : 1); }; registry.add("ReadProcessMemory", 5, [copy_process_memory](ApiCall &call) { copy_process_memory(call, false, false); }); registry.add("WriteProcessMemory", 5, [copy_process_memory](ApiCall &call) { copy_process_memory(call, true, false); }); registry.add("NtReadVirtualMemory", 5, [copy_process_memory](ApiCall &call) { try { copy_process_memory(call, false, true); } catch (const ApiFault &) { call.ret(0xc0000005U); } }); registry.add("NtWriteVirtualMemory", 5, [copy_process_memory](ApiCall &call) { try { copy_process_memory(call, true, true); } catch (const ApiFault &) { call.ret(0xc0000005U); } }); // What the object manager tells about a handle: the basic record, the name (objects here have none), the type and the handle flags. registry.add("NtQueryObject", 5, [&objects](ApiCall &call) { const uint64_t handle = call[0]; const size_t width = call.pointer_width(); std::optional type; if (handle == UINT64_MAX || (width == 4 && handle == UINT32_MAX)) type = u"Process"; else if (handle == UINT64_MAX - 1 || (width == 4 && handle == UINT32_MAX - 1)) type = u"Thread"; else type = objects.type_name(handle); if (!type) { call.ret(0xc0000008U); return; } const bool pseudo = handle >= UINT64_MAX - 1 || (width == 4 && handle >= UINT32_MAX - 1); std::vector info; const auto put = [&](size_t at, uint64_t value, size_t size) { std::memcpy(info.data() + at, &value, size); }; // A UNICODE_STRING at the start of the record whose text follows the record. const auto counted = [&](size_t record, const std::u16string &text) { info.assign(record + (text.size() + 1) * 2, 0); put(0, text.size() * 2, 2); put(2, (text.size() + 1) * 2, 2); put(width, text.empty() ? 0 : call[2] + record, width); std::memcpy(info.data() + record, text.data(), text.size() * 2); }; switch (call.u32(1)) { case 0: info.assign(56, 0); put(0, !pseudo && objects.inherit(handle) ? 2 : 0, 4); put(4, pseudo ? 0x1fffffU : granted_access(*type, objects.access_of(handle)), 4); put(8, 1, 4); put(12, 1, 4); break; case 1: // A file's name is its path on its device; other objects are not named here. if (const auto file = pseudo ? nullptr : objects.object_as(handle)) counted(width == 4 ? 8 : 16, NativePaths::device_path(file->dos_path)); else counted(width == 4 ? 8 : 16, u""); break; case 2: counted(width == 4 ? 0x60 : 0x68, *type); break; case 4: info.assign(2, 0); info[0] = !pseudo && objects.inherit(handle) ? 1 : 0; break; default: call.ret(0xc0000003U); return; } if (call[4]) call.store(call[4], uint32_t(info.size())); if (call.u32(3) < info.size() || !call[2]) { call.ret(0xc0000004U); return; } call.write(call[2], info); if (std::getenv("RAVEN_TRACE_API") && info.size() >= 8) std::fprintf(stderr, "raven: NtQueryObject %llx class %u type %s access %08x\n", (unsigned long long)handle, call.u32(1), to_utf8(*type).c_str(), uint32_t(info[4] | info[5] << 8 | info[6] << 16 | uint32_t(info[7]) << 24)); call.ret(0); }); // kernel32 publishes the psapi functions a second time under a K32 prefix, which is what newer programs import. for (const char *name : {"EnumProcessModules", "EnumProcessModulesEx", "GetModuleBaseNameA", "GetModuleBaseNameW", "GetModuleInformation", "GetProcessMemoryInfo", "GetModuleFileNameExW", "GetModuleFileNameExA", "QueryWorkingSetEx", "GetMappedFileNameW", "GetMappedFileNameA", "GetProcessImageFileNameW", "GetProcessImageFileNameA"}) if (const auto entry = registry.find(name)) registry.add(std::string("K32") + name, entry->first, entry->second); } }