32 Commits

Author SHA1 Message Date
Arsenii es3n1n
f57189d1b7 Merge pull request #20 from es3n1n/bump-1-2-0
build: bump version to v1.2.0
2025-05-20 15:36:09 +09:00
es3n1n
dfaae57077 build: bump version 2025-05-20 15:35:36 +09:00
Arsenii es3n1n
62c2f33d04 Merge pull request #19 from es3n1n/powershell-fix-newline
refactor(installer): change install loc to program files
2025-05-20 15:33:59 +09:00
es3n1n
7fc202497e refactor(installer): change install loc to program files 2025-05-20 15:33:08 +09:00
Arsenii es3n1n
a2d0f0c84d Merge pull request #18 from es3n1n/powershell-oneliner
feat(installer): add powershell installer
2025-05-20 15:23:08 +09:00
es3n1n
c159dbe324 refactor: minor cleanup 2025-05-20 15:16:32 +09:00
es3n1n
448b1ced98 docs(readme): update installer script url 2025-05-20 15:01:04 +09:00
es3n1n
897ed9c0eb docs(readme): update advanced usage notes 2025-05-20 12:09:56 +09:00
es3n1n
bf9d21b626 docs(readme): make advanced usage examples shorter 2025-05-20 12:08:01 +09:00
es3n1n
3d969605fd docs(readme): update advanced installer usage 2025-05-20 12:07:19 +09:00
es3n1n
3bfc312587 refactor(installer): minor installer cleanup 2025-05-20 12:01:22 +09:00
es3n1n
a11a1fe7b6 feat(installer): add powershell installer 2025-05-20 11:58:22 +09:00
Arsenii es3n1n
cc2b52b86a Merge pull request #16 from es3n1n/ci-release
ci: add auto release in workflow_dispatch
2025-05-19 22:06:16 +09:00
es3n1n
3bb18ef533 ci: add auto release in workflow_dispatch 2025-05-19 22:03:24 +09:00
Arsenii es3n1n
18aff7944e Merge pull request #15 from es3n1n/add-silent-arg
feat(loader): add --silent
2025-05-19 22:01:54 +09:00
es3n1n
53144b6b12 feat(loader): add --silent 2025-05-19 21:01:04 +09:00
Arsenii es3n1n
55ac355ac6 Merge pull request #12 from es3n1n/bump-version
build: bump version
2025-05-16 19:43:49 +09:00
es3n1n
8caf755016 build: bump version 2025-05-16 19:40:06 +09:00
Arsenii es3n1n
34f69dff18 Merge pull request #11 from es3n1n/more-wsc-stuff
feat(wsc): add more IWscAVStatus methods
2025-05-16 19:39:16 +09:00
es3n1n
a2b1793378 feat(wsc): add more IWscAVStatus methods 2025-05-16 19:36:41 +09:00
Arsenii es3n1n
02dffa91b8 Merge pull request #10 from es3n1n/autorun-changes
refactor(autorun): create scheduled task to be launched as system
2025-05-16 19:33:59 +09:00
es3n1n
7f0e9a6fd2 feat(autorun): add option to disable autorun 2025-05-16 19:31:01 +09:00
es3n1n
96d0cddf8c feat(autorun): add AS_SYSTEM_ON_BOOT autorun type 2025-05-15 21:51:22 +09:00
es3n1n
ad032ab0d5 refactor(autorun): add task with Users groupid 2025-05-14 09:19:02 +09:00
Arsenii es3n1n
782246d642 Merge pull request #6 from es3n1n/com-ptr-startup
refactor(autorun): get rid of defers
2025-05-14 08:17:47 +09:00
Arsenii es3n1n
01351bd406 Merge pull request #9 from es3n1n/ife-fix
fix(inject): fix compability with process-hacker replaced taskmgr
2025-05-13 19:25:55 +09:00
es3n1n
73a3633b4e fix(inject): add debug flags to process 2025-05-13 18:38:25 +09:00
es3n1n
33202c4c8f fix(inject): set read_image_file_exec_options to 0 2025-05-13 17:10:36 +09:00
Arsenii es3n1n
4c24a20988 Merge pull request #8 from es3n1n/build-workflow
ci: add build workflow
2025-05-13 12:55:48 +09:00
es3n1n
4a67d48951 ci: add build workflow 2025-05-13 12:50:10 +09:00
es3n1n
db4158da3b refactor(autorun): get rid of defers 2025-05-12 17:03:57 +09:00
es3n1n
928be9a45f docs(readme): add writeup link 2025-05-09 14:46:11 +09:00
13 changed files with 459 additions and 126 deletions

106
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
name: Build/Release
on:
push:
workflow_dispatch:
inputs:
tag:
description: 'Tag for the release'
required: true
permissions:
contents: write
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
platform: [x64, x86, ARM64]
configuration: [Release]
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Map platform
id: platform-mapping
run: |
$platform = '${{ matrix.platform }}'
if ($platform -eq 'x86') {
$platform = 'Win32'
echo "mapped-platform=$platform" >> $env:GITHUB_OUTPUT
} else {
echo "mapped-platform=$platform" >> $env:GITHUB_OUTPUT
}
shell: pwsh
- name: Build Solution
run: |
msbuild.exe defendnot.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=${{ matrix.platform }} -t:rebuild /m /verbosity:normal
- name: Copy artifacts
run: |
$platform = '${{ matrix.platform }}'
$mappedPlatform = '${{ steps.platform-mapping.outputs.mapped-platform }}'
$config = '${{ matrix.configuration }}'
New-Item -Path "artifacts\$platform" -ItemType Directory -Force
Get-ChildItem -Path "out\$mappedPlatform" | Select-Object Name
Copy-Item -Path ".\out\$mappedPlatform\defendnot.dll" -Destination ".\artifacts\$platform\" -Verbose
Copy-Item -Path ".\out\$mappedPlatform\defendnot.pdb" -Destination ".\artifacts\$platform\" -Verbose
Copy-Item -Path ".\out\$mappedPlatform\defendnot-loader.exe" -Destination ".\artifacts\$platform\" -Verbose
Copy-Item -Path ".\out\$mappedPlatform\defendnot-loader.pdb" -Destination ".\artifacts\$platform\" -Verbose
shell: pwsh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.platform }}
path: artifacts/${{ matrix.platform }}
retention-days: 7
create-release:
needs: build
if: github.event_name == 'workflow_dispatch' && github.event.inputs.tag != ''
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
- name: Zip Artifacts
run: |
mkdir -p zipped-artifacts
cd release-artifacts
for platform in */; do
platform_name=${platform%/}
echo "Zipping $platform_name"
(cd "$platform_name" && zip -r "../../zipped-artifacts/$platform_name.zip" .)
done
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.event.inputs.tag }}
name: ${{ github.event.inputs.tag }}
draft: false
prerelease: false
files: zipped-artifacts/*.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ mono_crash.*
[Rr]eleases/
x64/
x86/
Win32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/

View File

@@ -6,6 +6,49 @@ Defendnot is a successor of [no-defender](https://github.com/es3n1n/no-defender)
![](https://i.imgur.com/VGE8g6a.jpeg)
## Installation
### One-liner
Open the powershell as administrator and execute any of these:
```powershell
# Example 1: Basic installation
irm https://dnot.sh/ | iex
# Example 2: With custom AV name
& ([ScriptBlock]::Create((irm https://dnot.sh/))) --name "Custom AV name"
# Example 3: Without allocating console
& ([ScriptBlock]::Create((irm https://dnot.sh/))) --silent
```
> [!NOTE]
> As seen in examples 2 and 3, you can pass the commandline arguments to the installer script and it will forward them to `defendnot-loader`. For reference what commandline arguments are allowed, see the `Usage` section below.
> [!NOTE]
> You can also directly use the 'longer' version of installer script url, which is `https://raw.githubusercontent.com/es3n1n/defendnot/refs/heads/master/install.ps1`
### Manual
Download the [latest](https://github.com/es3n1n/defendnot/releases/latest) release, extract it somewhere and launch `defendnot-loader`.
## Usage
```commandline
Usage: defendnot-loader [--help] [--version] [--name VAR] [--disable] [--verbose] [--silent] [--autorun-as-user] [--disable-autorun]
Optional arguments:
-h, --help prints help message and exits
--version shows version and exits
-n, --name av display name [default: "https://github.com/es3n1n/defendnot"]
-d, --disable disable defendnot
-v, --verbose verbose logging
--silent do not allocate console
--autorun-as-user create autorun task as currently logged in user
--disable-autorun disable autorun task creation
```
## How it works
There's a WSC (Windows Security Center) service in Windows which is used by antiviruses to let Windows know that there's some other antivirus in the hood and it should disable Windows Defender.
@@ -17,22 +60,9 @@ The initial implementation of [no-defender](https://github.com/es3n1n/no-defende
Sadly, to keep this WSC stuff even after reboot, defendnot adds itself to the autorun. Thus, you would need to keep the defendnot binaries on your disk :(
## Usage
## Writeup
```commandline
Usage: defendnot-loader [--help] [--version] [--name VAR] [--disable] [--verbose]
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
-n, --name av display name [default: "https://github.com/es3n1n/defendnot"]
-d, --disable disable defendnot
-v, --verbose verbose logging
```
## Implementation
A more detailed writeup will be coming in a few days.
[How I ruined my vacation by reverse engineering WSC](https://blog.es3n1n.eu/posts/how-i-ruined-my-vacation/)
## Special thanks

View File

@@ -18,6 +18,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)shared\defer.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ipc.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\names.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\native.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\util.hpp" />
</ItemGroup>
</Project>

View File

@@ -7,4 +7,6 @@ namespace names {
constexpr std::string_view kVictimProcess = "Taskmgr.exe";
constexpr std::string_view kDllName = "defendnot.dll";
constexpr std::string_view kVersion = "1.2.0";
} // namespace names

View File

@@ -0,0 +1,51 @@
#pragma once
#include <format>
#include <stdexcept>
#include <Windows.h>
#pragma pack(push, 1)
namespace native {
class PEB {
public:
std::uint8_t inherited_address_space;
std::uint8_t read_image_file_exec_options;
/// - we don't need other fields
};
static_assert(offsetof(PEB, read_image_file_exec_options) == 1);
template <typename Ty>
inline Ty get_system_routine(const std::string_view module_name, const std::string_view function_name) {
const auto mod = GetModuleHandleA(module_name.data());
if (mod == nullptr) {
throw std::runtime_error(std::format("unable to find module {}", module_name));
}
auto function = reinterpret_cast<Ty>(GetProcAddress(mod, function_name.data()));
if (function == nullptr) {
throw std::runtime_error(std::format("unable to obtain {} from {}", module_name, function_name));
}
return function;
}
inline PEB* get_peb() {
static auto function = get_system_routine<PEB*(__stdcall*)()>("ntdll.dll", "RtlGetCurrentPeb");
static auto result = function();
if (result == nullptr) [[unlikely]] {
throw std::runtime_error("no peb");
}
return result;
}
inline bool debug_set_process_kill_on_exit(const bool value) {
static auto function = get_system_routine<BOOL(__stdcall*)(BOOL)>("kernel32.dll", "DebugSetProcessKillOnExit");
return static_cast<bool>(function(static_cast<BOOL>(value)));
}
inline bool debug_active_process_stop(const std::uint32_t process_id) {
static auto function = get_system_routine<BOOL(__stdcall*)(DWORD)>("kernel32.dll", "DebugActiveProcessStop");
return static_cast<bool>(function(process_id));
}
} // namespace native
#pragma pack(pop)

View File

@@ -1,11 +1,12 @@
#include "core/core.hpp"
#include "shared/ctx.hpp"
#include "shared/defer.hpp"
#include "shared/names.hpp"
#include <memory>
#include <print>
#include <stdexcept>
#include <type_traits>
#include <comdef.h>
#include <taskschd.h>
@@ -18,155 +19,165 @@ namespace loader {
namespace {
constexpr std::string_view kTaskName = names::kProjectName;
/// A very basic implementation, a lot of stuff is missing
template <typename Ty>
class ComPtr {
public:
ComPtr() = default;
explicit ComPtr(Ty* ptr): ptr_(ptr) { }
~ComPtr() {
if (ptr_ != nullptr) {
ptr_->Release();
}
}
ComPtr(const ComPtr&) = delete;
ComPtr& operator=(const ComPtr&) = delete;
[[nodiscard]] Ty* get() const {
return ptr_;
}
[[nodiscard]] Ty* operator->() const {
return ptr_;
}
[[nodiscard]] Ty** ref_to_ptr() {
return &ptr_;
}
private:
Ty* ptr_ = nullptr;
};
void co_initialize() {
static std::once_flag fl;
std::call_once(fl, []() -> void {
const auto result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(result)) {
throw std::runtime_error("failed to CoInitializeEx");
}
});
}
template <typename Callable>
[[nodiscard]] bool with_service(Callable&& callback) {
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
co_initialize();
ComPtr<ITaskService> service;
auto hr =
CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, reinterpret_cast<void**>(service.ref_to_ptr()));
if (FAILED(hr)) {
return false;
}
defer->void {
CoUninitialize();
};
ITaskService* service = nullptr;
hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, reinterpret_cast<void**>(&service));
if (FAILED(hr)) {
return false;
}
defer->void {
service->Release();
};
hr = service->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{});
if (FAILED(hr)) {
return false;
}
ITaskFolder* root_folder = nullptr;
hr = service->GetFolder(BSTR(L"\\"), &root_folder);
ComPtr<ITaskFolder> root_folder;
hr = service->GetFolder(BSTR(L"\\"), root_folder.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
root_folder->Release();
};
/// Cleanup our task, we will recreate it in the callback if needed
root_folder->DeleteTask(BSTR(kTaskName.data()), 0);
return callback(service, root_folder);
return callback(service.get(), root_folder.get());
}
} // namespace
[[nodiscard]] bool add_to_autorun() {
[[nodiscard]] bool add_to_autorun(AutorunType type) {
const auto bin_path = shared::get_this_module_path();
return with_service([bin_path](ITaskService* service, ITaskFolder* folder) -> bool {
ITaskDefinition* task = nullptr;
auto hr = service->NewTask(0, &task);
return with_service([bin_path, type](ITaskService* service, ITaskFolder* folder) -> bool {
/// Deduce the autorun config based on type
const auto task_trigger = type == AutorunType::AS_SYSTEM_ON_BOOT ? TASK_TRIGGER_BOOT : TASK_TRIGGER_LOGON;
const auto logon_type = type == AutorunType::AS_SYSTEM_ON_BOOT ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN;
BSTR user_id = nullptr;
auto bstr_sys = bstr_t(L"SYSTEM");
if (type == AutorunType::AS_SYSTEM_ON_BOOT) {
user_id = bstr_sys;
}
ComPtr<ITaskDefinition> task;
auto hr = service->NewTask(0, task.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
task->Release();
};
IRegistrationInfo* reg_info = nullptr;
hr = task->get_RegistrationInfo(&reg_info);
ComPtr<IRegistrationInfo> reg_info;
hr = task->get_RegistrationInfo(reg_info.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
reg_info->Release();
};
reg_info->put_Author(_bstr_t(names::kRepoUrl.data()));
IPrincipal* pPrincipal = nullptr;
hr = task->get_Principal(&pPrincipal);
ComPtr<IPrincipal> principal;
hr = task->get_Principal(principal.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
pPrincipal->Release();
};
ITriggerCollection* trigger_collection = nullptr;
hr = task->get_Triggers(&trigger_collection);
ComPtr<ITriggerCollection> trigger_collection;
hr = task->get_Triggers(trigger_collection.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
trigger_collection->Release();
};
ITrigger* trigger = nullptr;
hr = trigger_collection->Create(TASK_TRIGGER_LOGON, &trigger);
ComPtr<ITrigger> trigger;
hr = trigger_collection->Create(task_trigger, trigger.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
trigger->Release();
};
IActionCollection* action_collection = nullptr;
hr = task->get_Actions(&action_collection);
ComPtr<IActionCollection> action_collection;
hr = task->get_Actions(action_collection.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
action_collection->Release();
};
IAction* action = nullptr;
hr = action_collection->Create(TASK_ACTION_EXEC, &action);
ComPtr<IAction> action;
hr = action_collection->Create(TASK_ACTION_EXEC, action.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
action->Release();
};
IExecAction* exec_action = nullptr;
hr = action->QueryInterface(IID_IExecAction, (void**)&exec_action);
ComPtr<IExecAction> exec_action;
hr = action->QueryInterface(IID_IExecAction, reinterpret_cast<void**>(exec_action.ref_to_ptr()));
if (FAILED(hr)) {
return false;
}
defer->void {
exec_action->Release();
};
ITaskSettings* settings = nullptr;
hr = task->get_Settings(&settings);
ComPtr<ITaskSettings> settings;
hr = task->get_Settings(settings.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
defer->void {
settings->Release();
};
/// Elevated, when system boots
principal->put_UserId(user_id);
principal->put_LogonType(logon_type);
principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
/// Info
reg_info->put_Author(bstr_t(names::kRepoUrl.data()));
/// Start even if we're on batteries
settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
settings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
exec_action->put_Path(_bstr_t(bin_path.string().c_str()));
exec_action->put_Arguments(_bstr_t("--from-autorun"));
IRegisteredTask* registered_task = nullptr;
hr = folder->RegisterTaskDefinition(_bstr_t(kTaskName.data()), task, TASK_CREATE_OR_UPDATE, VARIANT{}, VARIANT{}, TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(L""), &registered_task);
/// Binary
exec_action->put_Path(bstr_t(bin_path.string().c_str()));
exec_action->put_Arguments(bstr_t("--from-autorun"));
defer->void {
registered_task->Release();
};
/// Register the task and we are done
ComPtr<IRegisteredTask> registered_task;
hr = folder->RegisterTaskDefinition(bstr_t(kTaskName.data()), task.get(), TASK_CREATE_OR_UPDATE, VARIANT{}, VARIANT{}, TASK_LOGON_NONE,
variant_t(L""), registered_task.ref_to_ptr());
return SUCCEEDED(hr);
});
}

View File

@@ -5,15 +5,23 @@
#include <Windows.h>
namespace loader {
enum class AutorunType : std::uint8_t {
AS_SYSTEM_ON_BOOT = 0, ///< launch on system boot as NT AUTHORITY\SYSTEM
AS_CURRENT_USER_ON_LOGIN = 1, ///< launch on user login
};
struct Config {
public:
std::string name;
bool disable;
bool alloc_console;
bool verbose;
bool from_autorun;
AutorunType autorun_type;
bool enable_autorun;
};
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name);
[[nodiscard]] bool add_to_autorun();
[[nodiscard]] bool add_to_autorun(AutorunType type);
[[nodiscard]] bool remove_from_autorun();
} // namespace loader

View File

@@ -1,6 +1,8 @@
#include "core/core.hpp"
#include "shared/defer.hpp"
#include "shared/native.hpp"
#include <print>
#include <stdexcept>
@@ -19,11 +21,20 @@ namespace loader {
.bInheritHandle = TRUE,
};
/// By setting ReadImageFileExecOptions to FALSE and attaching ourselves as a debugger we can skip the IFEO
/// \xref: https://github.com/es3n1n/defendnot/issues/7#issuecomment-2874903650
native::get_peb()->read_image_file_exec_options = 0;
std::println("** booting {}", proc_name);
if (!CreateProcessA(nullptr, const_cast<char*>(proc_name.data()), &sa, &sa, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
const auto process_flags = CREATE_SUSPENDED | DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS;
if (!CreateProcessA(nullptr, const_cast<char*>(proc_name.data()), &sa, &sa, FALSE, process_flags, nullptr, nullptr, &si, &pi)) {
throw std::runtime_error(std::format("unable to create process: {}", GetLastError()));
}
/// Detach
native::debug_set_process_kill_on_exit(false);
native::debug_active_process_stop(pi.dwProcessId);
defer->void {
CloseHandle(pi.hThread);
/// Not closing hProcess because we return it

View File

@@ -11,7 +11,7 @@
namespace {
void setup_window(const loader::Config& config) {
if (!config.from_autorun || config.verbose) {
if ((!config.from_autorun || config.verbose) && config.alloc_console) {
shared::alloc_console();
}
}
@@ -56,8 +56,8 @@ namespace {
}
void process_autorun(const loader::Config& config) {
if (shared::ctx.state == shared::State::ON) {
std::println("** added to autorun: {}", loader::add_to_autorun());
if (shared::ctx.state == shared::State::ON && config.enable_autorun) {
std::println("** added to autorun: {}", loader::add_to_autorun(config.autorun_type));
} else {
std::println("** removed from autorun: {}", loader::remove_from_autorun());
}
@@ -68,36 +68,69 @@ namespace {
std::println("thanks for using {}", names::kProjectName);
std::println("please don't forget to leave a star at {}", names::kRepoUrl);
if (!config.from_autorun) {
if (!config.from_autorun && config.alloc_console) {
system("pause");
}
}
} // namespace
int main(int argc, char* argv[]) try {
argparse::ArgumentParser program(std::format("{}-loader", names::kProjectName), "1.0.0");
argparse::ArgumentParser program(std::format("{}-loader", names::kProjectName), names::kVersion.data(), argparse::default_arguments::none);
const auto fatal_print = [](const std::string_view str) -> void {
shared::alloc_console();
std::cerr << str << std::endl;
system("pause");
std::exit(EXIT_FAILURE);
};
/// We are registering these ourselves because we have to alloc console first
program.add_argument("-h", "--help")
.help("prints help message and exits")
.default_value(false)
.implicit_value(true)
.action([&fatal_print, &program](const auto& /*unused*/) -> void { fatal_print(program.help().str()); });
program.add_argument("--version")
.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)); });
/// defendnot-loader parameters:
program.add_argument("-n", "--name").help("av display name").default_value(std::string(names::kRepoUrl)).nargs(1);
program.add_argument("-d", "--disable").help(std::format("disable {}", names::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);
program.add_argument("--disable-autorun").help("disable autorun task creation").default_value(false).implicit_value(true);
program.add_argument("--from-autorun").hidden().default_value(false).implicit_value(true);
try {
program.parse_args(argc, argv);
} catch (...) {
shared::alloc_console();
std::cerr << program;
system("pause");
} catch (std::exception& e) {
std::stringstream ss;
ss << e.what() << '\n';
ss << program.help().str();
fatal_print(ss.str());
return EXIT_FAILURE;
}
auto config = loader::Config{
.name = program.get<std::string>("-n"),
.disable = program.get<bool>("-d"),
.alloc_console = !program.get<bool>("--silent"),
.verbose = program.get<bool>("-v"),
.from_autorun = program.get<bool>("--from-autorun"),
.autorun_type = program.get<bool>("--autorun-as-user") ? /// As system on boot is the default value
loader::AutorunType::AS_CURRENT_USER_ON_LOGIN :
loader::AutorunType::AS_SYSTEM_ON_BOOT,
.enable_autorun = !program.get<bool>("--disable-autorun"),
};
if (!config.alloc_console && config.verbose) {
fatal_print("--silent flag can not be used in combination with --verbose");
}
setup_window(config);
setup_context(config);

View File

@@ -16,7 +16,7 @@ namespace defendnot {
auto inst = IWscAVStatus::get();
/// This can fail if we dont have any avs registered so no com_checked
logln("unregister: {:#x}", com_retry_while_pending([&inst]() -> HRESULT { return inst->Unregister(); }));
logln("unregister: {:#x}", com_retry_while_pending([&inst]() -> HRESULT { return inst->Unregister(); }) & 0xFFFFFFFF);
if (shared::ctx.state == shared::State::OFF) {
return;
}
@@ -34,7 +34,7 @@ namespace defendnot {
};
/// Register and activate our AV
logln("register: {:#x}", com_checked(inst->Register(name, name)));
logln("register: {:#x}", com_checked(inst->Register(name, name, 0, 0)));
logln("update: {:#x}", com_checked(inst->UpdateStatus(WSCSecurityProductState::ON, 3)));
}
} // namespace defendnot

View File

@@ -10,7 +10,7 @@
namespace defendnot {
namespace detail {
inline GUID RCLSID = {0x0F2102C37, 0x90C3, 0x450C, {0x0B3, 0x0F6, 0x92, 0x0BE, 0x16, 0x93, 0x0BD, 0x0F2}};
inline GUID CLSID_IWscAVStatus = {0x0F2102C37, 0x90C3, 0x450C, {0x0B3, 0x0F6, 0x92, 0x0BE, 0x16, 0x93, 0x0BD, 0x0F2}};
inline GUID IID_IWscAVStatus = {0x3901A765, 0x0AB91, 0x4BA9, {0xA5, 0x53, 0x5B, 0x85, 0x38, 0xDE, 0xB8, 0x40}};
} // namespace detail
@@ -21,6 +21,13 @@ namespace defendnot {
EXPIRED = 3
};
enum class WSCSecurityProductSubStatus : std::uint32_t {
NOT_SET = 0,
NO_ACTION = 1,
ACTION_RECOMMENDED = 2,
ACTION_NEEDED = 3
};
inline HRESULT com_checked(HRESULT result, const std::source_location loc = std::source_location::current()) {
if (result == 0) {
return result;
@@ -53,20 +60,39 @@ namespace defendnot {
}
class IWscAVStatus {
private:
/// Incomplete stubs to just increase the vfunc id
public:
virtual HRESULT QueryInterface() = 0;
virtual HRESULT AddRef() = 0;
virtual HRESULT Release() = 0;
virtual std::uint32_t AddRef() = 0;
virtual std::uint32_t Release() = 0;
virtual HRESULT Register(BSTR path_to_signed_product_exe, BSTR display_name, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT Unregister() = 0;
virtual HRESULT UpdateStatus(WSCSecurityProductState state, std::uint32_t) = 0;
virtual HRESULT InitiateOfflineCleaning(std::uint16_t*, std::uint16_t*) = 0;
virtual HRESULT NotifyUserForNearExpiration(std::uint32_t) = 0;
virtual HRESULT MakeDefaultProductRequest() = 0;
virtual HRESULT IsDefaultProductEnforced(std::uint32_t* result) = 0;
virtual HRESULT UpdateScanSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT UpdateSettingsSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT UpdateProtectionUpdateSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT RegisterAV(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT UnregisterAV() = 0;
virtual HRESULT UpdateStatusAV(WSCSecurityProductState state, std::uint32_t) = 0;
virtual HRESULT InitiateOfflineCleaningAV(std::uint16_t*, std::uint16_t*) = 0;
virtual HRESULT NotifyUserForNearExpirationAV(std::uint32_t) = 0;
virtual HRESULT RegisterFW(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT UnregisterFW() = 0;
virtual HRESULT UpdateStatusFW(WSCSecurityProductState state) = 0;
virtual HRESULT RegisterAS(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT UnregisterAS() = 0;
virtual HRESULT UpdateStatusAS(WSCSecurityProductState state, std::uint32_t) = 0;
private:
virtual void dtor() = 0;
public:
virtual HRESULT Register(BSTR path_to_signed_product_exe, BSTR display_name) = 0;
virtual HRESULT Unregister() = 0;
virtual HRESULT UpdateStatus(WSCSecurityProductState state, std::uint32_t idk) = 0;
static IWscAVStatus* get() {
IWscAVStatus* result = nullptr;
com_checked(CoCreateInstance(detail::RCLSID, 0, 1, detail::IID_IWscAVStatus, reinterpret_cast<LPVOID*>(&result)));
com_checked(CoCreateInstance(detail::CLSID_IWscAVStatus, 0, 1, detail::IID_IWscAVStatus, reinterpret_cast<LPVOID*>(&result)));
return result;
}
};

53
install.ps1 Normal file
View File

@@ -0,0 +1,53 @@
#Requires -RunAsAdministrator
$ErrorActionPreference = "Stop"
$InstallPath = "$env:ProgramFiles\defendnot"
switch -Wildcard ($env:PROCESSOR_ARCHITECTURE) {
"AMD64" { $arch = "x64" }
"x86" { $arch = "x86" }
"ARM64" { $arch = "ARM64" }
default {
Write-Error "Unknown architecture: $($env:PROCESSOR_ARCHITECTURE)"
exit 1
}
}
$repo = "es3n1n/defendnot"
$apiReleaseUrl = "https://api.github.com/repos/$repo/releases/latest"
$headers = @{ 'User-Agent'="defendnot-install/1.0" }
try {
$release = Invoke-RestMethod -Uri $apiReleaseUrl -Headers $headers
} catch {
Write-Error "Failed to get latest release info: $_"
exit 2
}
$zipAsset = $release.assets | Where-Object { $_.name -ieq "$arch.zip" }
if (-not $zipAsset) {
Write-Error "Release does not contain asset for $arch"
exit 3
}
$zipUrl = $zipAsset.browser_download_url
$zipPath = Join-Path $env:TEMP "defendnot-$arch.zip"
Write-Host "Downloading $($zipAsset.name)..."
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath
if (Test-Path $InstallPath) {
Write-Host "Removing previous installation..."
Remove-Item $InstallPath -Force -Recurse -ErrorAction SilentlyContinue
}
New-Item -Type Directory -Path $InstallPath -ErrorAction SilentlyContinue | Out-Null
Write-Host "Extracting to $InstallPath..."
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $InstallPath)
Remove-Item $zipPath
Write-Host "Installed to $InstallPath"
Write-Host "Starting..."
Write-Host "Args: $args"
& "$InstallPath\defendnot-loader.exe" @args