mirror of
https://github.com/es3n1n/defendnot.git
synced 2026-08-02 10:32:01 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f57189d1b7 | ||
|
|
dfaae57077 | ||
|
|
62c2f33d04 | ||
|
|
7fc202497e | ||
|
|
a2d0f0c84d | ||
|
|
c159dbe324 | ||
|
|
448b1ced98 | ||
|
|
897ed9c0eb | ||
|
|
bf9d21b626 | ||
|
|
3d969605fd | ||
|
|
3bfc312587 | ||
|
|
a11a1fe7b6 | ||
|
|
cc2b52b86a | ||
|
|
3bb18ef533 | ||
|
|
18aff7944e | ||
|
|
53144b6b12 | ||
|
|
55ac355ac6 | ||
|
|
8caf755016 | ||
|
|
34f69dff18 | ||
|
|
a2b1793378 | ||
|
|
02dffa91b8 | ||
|
|
7f0e9a6fd2 | ||
|
|
96d0cddf8c | ||
|
|
ad032ab0d5 | ||
|
|
782246d642 | ||
|
|
01351bd406 | ||
|
|
73a3633b4e | ||
|
|
33202c4c8f | ||
|
|
4c24a20988 | ||
|
|
4a67d48951 | ||
|
|
db4158da3b | ||
|
|
928be9a45f |
106
.github/workflows/build.yml
vendored
Normal file
106
.github/workflows/build.yml
vendored
Normal 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
1
.gitignore
vendored
@@ -23,6 +23,7 @@ mono_crash.*
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
Win32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
|
||||
60
README.md
60
README.md
@@ -6,6 +6,49 @@ Defendnot is a successor of [no-defender](https://github.com/es3n1n/no-defender)
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
51
cxx-shared/shared/native.hpp
Normal file
51
cxx-shared/shared/native.hpp
Normal 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)
|
||||
@@ -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(®_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""), ®istered_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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
53
install.ps1
Normal 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
|
||||
Reference in New Issue
Block a user