feat(loader): check for wsc service before starting

This commit is contained in:
es3n1n
2025-05-30 12:01:43 +02:00
parent 3583c4636f
commit df93be27ec
13 changed files with 210 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
#include "core/core.hpp"
#include "shared/ctx.hpp"
#include "shared/names.hpp"
#include "shared/strings.hpp"
#include <memory>
#include <print>
@@ -17,7 +17,7 @@
namespace loader {
namespace {
constexpr std::string_view kTaskName = names::kProjectName;
constexpr std::string_view kTaskName = strings::kProjectName;
/// A very basic implementation, a lot of stuff is missing
template <typename Ty>
@@ -164,7 +164,7 @@ namespace loader {
principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
/// Info
reg_info->put_Author(bstr_t(names::kRepoUrl.data()));
reg_info->put_Author(bstr_t(strings::kRepoUrl.data()));
/// Start even if we're on batteries
settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);

View File

@@ -24,4 +24,5 @@ namespace loader {
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name);
[[nodiscard]] bool add_to_autorun(AutorunType type);
[[nodiscard]] bool remove_from_autorun();
void ensure_environment();
} // namespace loader

View File

@@ -0,0 +1,26 @@
#include "core/core.hpp"
#include "shared/strings.hpp"
#include "util/scm.hpp"
#include <format>
#include <stdexcept>
namespace loader {
void ensure_environment() {
auto manager = scm::Manager();
if (!manager.valid()) [[unlikely]] {
throw std::runtime_error("Unable to open scm::Manager");
}
auto service = manager.get_service(L"wscsvc");
if (!service.valid() || !service.query_status()) {
throw std::runtime_error(strings::wsc_unavailable_error().data());
}
const auto state = service.state();
if (state != scm::ServiceState::RUNNING) {
throw std::runtime_error(std::format("{}\nService state: {}, but should be RUNNING", strings::wsc_unavailable_error(),
scm::kServiceStateNames[static_cast<std::underlying_type_t<scm::ServiceState>>(state)]));
}
}
} // namespace loader

View File

@@ -250,11 +250,13 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="core\autorun.cpp" />
<ClCompile Include="core\ensure_environment.cpp" />
<ClCompile Include="core\inject.cpp" />
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="core\core.hpp" />
<ClInclude Include="util\scm.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@@ -24,10 +24,16 @@
<ClCompile Include="core\autorun.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="core\ensure_environment.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="core\core.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="util\scm.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@
#include "shared/ctx.hpp"
#include "shared/defer.hpp"
#include "shared/ipc.hpp"
#include "shared/names.hpp"
#include "shared/strings.hpp"
#include <argparse/argparse.hpp>
#include <format>
@@ -38,12 +38,12 @@ namespace {
std::println("** loading defendnot");
auto dll_path = shared::get_this_module_path().parent_path();
dll_path /= names::kDllName;
dll_path /= strings::kDllName;
if (!std::filesystem::exists(dll_path)) {
throw std::runtime_error(std::format("{} does not exist!", names::kDllName));
throw std::runtime_error(std::format("{} does not exist!", strings::kDllName));
}
return loader::inject(dll_path.string(), names::kVictimProcess);
return loader::inject(dll_path.string(), strings::kVictimProcess);
}
void wait_for_finish(shared::InterProcessCommunication& ipc) {
@@ -65,8 +65,8 @@ namespace {
void banner(const loader::Config& config) {
std::println();
std::println("thanks for using {}", names::kProjectName);
std::println("please don't forget to leave a star at {}", names::kRepoUrl);
std::println("thanks for using {}", strings::kProjectName);
std::println("please don't forget to leave a star at {}", strings::kRepoUrl);
if (!config.from_autorun && config.alloc_console) {
system("pause");
@@ -75,7 +75,7 @@ namespace {
} // namespace
int main(int argc, char* argv[]) try {
argparse::ArgumentParser program(std::format("{}-loader", names::kProjectName), names::kVersion.data(), argparse::default_arguments::none);
argparse::ArgumentParser program(std::format("{}-loader", strings::kProjectName), strings::kVersion.data(), argparse::default_arguments::none);
const auto fatal_print = [](const std::string_view str) -> void {
shared::alloc_console();
@@ -94,11 +94,11 @@ int main(int argc, char* argv[]) try {
.help("shows version and exits")
.default_value(false)
.implicit_value(true)
.action([&fatal_print](const auto& /*unused*/) -> void { fatal_print(std::format("{}-loader v{}", names::kProjectName, names::kVersion)); });
.action([&fatal_print](const auto& /*unused*/) -> void { fatal_print(std::format("{}-loader v{}", strings::kProjectName, strings::kVersion)); });
/// defendnot-loader parameters:
program.add_argument("-n", "--name").help("av display name").default_value(std::string(names::kDefaultAVName)).nargs(1);
program.add_argument("-d", "--disable").help(std::format("disable {}", names::kProjectName)).default_value(false).implicit_value(true);
program.add_argument("-n", "--name").help("av display name").default_value(std::string(strings::kDefaultAVName)).nargs(1);
program.add_argument("-d", "--disable").help(std::format("disable {}", strings::kProjectName)).default_value(false).implicit_value(true);
program.add_argument("-v", "--verbose").help("verbose logging").default_value(false).implicit_value(true);
program.add_argument("--silent").help("do not allocate console").default_value(false).implicit_value(true);
program.add_argument("--autorun-as-user").help("create autorun task as currently logged in user").default_value(false).implicit_value(true);
@@ -132,6 +132,8 @@ int main(int argc, char* argv[]) try {
}
setup_window(config);
loader::ensure_environment();
setup_context(config);
/// \todo @es3n1n: move this to a separate function and add move ctor for ipc
@@ -150,6 +152,7 @@ int main(int argc, char* argv[]) try {
return EXIT_SUCCESS;
} catch (std::exception& err) {
shared::alloc_console();
std::println(stderr, "** fatal error: {}", err.what());
system("pause");
return EXIT_FAILURE;

View File

@@ -0,0 +1,113 @@
#pragma once
#include <array>
#include <memory>
#include <optional>
#include <string_view>
#include <Windows.h>
namespace scm {
using SCHandleRaw = SC_HANDLE;
using SCHandle = std::unique_ptr<std::remove_pointer_t<SCHandleRaw>, decltype(&CloseServiceHandle)>;
inline SCHandle make_sc_handle(SCHandleRaw handle) {
return SCHandle(handle, CloseServiceHandle);
}
enum class ServiceState : std::uint8_t {
UNKNOWN = 0,
STOPPED,
STOP_PENDING,
START_PENDIND,
RUNNING,
PAUSED,
PAUSE_PENDING,
CONTINUE_PENDING,
};
/// \note @es3n1n: we should use magic_enum once we have more than one enum where we need to get value name
constexpr auto kServiceStateNames =
std::to_array<std::string_view>({"UNKNOWN", "STOPPED", "STOP_PENDING", "START_PENDING", "RUNNING", "PAUSED", "PAUSE_PENDING", "CONTINUE_PENDING"});
class Service {
public:
Service(SCHandleRaw handle): handle_(make_sc_handle(handle)) { };
~Service() = default;
public:
bool query_status() noexcept {
if (status_process_.has_value()) {
return true;
}
SERVICE_STATUS_PROCESS status;
DWORD needed = 0;
if (!QueryServiceStatusEx(handle_.get(), SC_STATUS_PROCESS_INFO, reinterpret_cast<PBYTE>(&status), sizeof(status), &needed)) {
return false;
}
status_process_ = status;
return true;
}
[[nodiscard]] ServiceState state() noexcept {
if (!query_status() || !status_process_.has_value()) {
return ServiceState::UNKNOWN;
}
switch (status_process_->dwCurrentState) {
case SERVICE_STOPPED:
return ServiceState::STOPPED;
case SERVICE_STOP_PENDING:
return ServiceState::STOP_PENDING;
case SERVICE_START_PENDING:
return ServiceState::START_PENDIND;
case SERVICE_RUNNING:
return ServiceState::RUNNING;
case SERVICE_PAUSED:
return ServiceState::PAUSED;
case SERVICE_PAUSE_PENDING:
return ServiceState::PAUSE_PENDING;
case SERVICE_CONTINUE_PENDING:
return ServiceState::CONTINUE_PENDING;
default:
return ServiceState::UNKNOWN;
}
}
[[nodiscard]] bool valid() const noexcept {
return handle_.get() != nullptr;
}
[[nodiscard]] explicit operator bool() const noexcept {
return valid();
}
private:
std::optional<SERVICE_STATUS_PROCESS> status_process_ = std::nullopt;
SCHandle handle_;
};
class Manager {
constexpr static auto kDesiredPermissions = GENERIC_READ;
public:
Manager(): handle_(make_sc_handle(OpenSCManagerW(nullptr, nullptr, kDesiredPermissions))) { };
~Manager() = default;
public:
[[nodiscard]] Service get_service(const std::wstring_view service_name) noexcept {
return Service(OpenServiceW(handle_.get(), service_name.data(), kDesiredPermissions));
}
public:
[[nodiscard]] bool valid() const noexcept {
return handle_.get() != nullptr;
}
[[nodiscard]] explicit operator bool() const noexcept {
return valid();
}
private:
SCHandle handle_;
};
} // namespace scm