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/ [Rr]eleases/
x64/ x64/
x86/ x86/
Win32/
[Aa][Rr][Mm]/ [Aa][Rr][Mm]/
[Aa][Rr][Mm]64/ [Aa][Rr][Mm]64/
bld/ 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) ![](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 ## 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. 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 :( 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 [How I ruined my vacation by reverse engineering WSC](https://blog.es3n1n.eu/posts/how-i-ruined-my-vacation/)
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.
## Special thanks ## Special thanks

View File

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

View File

@@ -7,4 +7,6 @@ namespace names {
constexpr std::string_view kVictimProcess = "Taskmgr.exe"; constexpr std::string_view kVictimProcess = "Taskmgr.exe";
constexpr std::string_view kDllName = "defendnot.dll"; constexpr std::string_view kDllName = "defendnot.dll";
constexpr std::string_view kVersion = "1.2.0";
} // namespace names } // 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 "core/core.hpp"
#include "shared/ctx.hpp" #include "shared/ctx.hpp"
#include "shared/defer.hpp"
#include "shared/names.hpp" #include "shared/names.hpp"
#include <memory>
#include <print> #include <print>
#include <stdexcept> #include <stdexcept>
#include <type_traits>
#include <comdef.h> #include <comdef.h>
#include <taskschd.h> #include <taskschd.h>
@@ -18,155 +19,165 @@ namespace loader {
namespace { namespace {
constexpr std::string_view kTaskName = names::kProjectName; 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> template <typename Callable>
[[nodiscard]] bool with_service(Callable&& callback) { [[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)) { if (FAILED(hr)) {
return false; 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{}); hr = service->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{});
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
ITaskFolder* root_folder = nullptr; ComPtr<ITaskFolder> root_folder;
hr = service->GetFolder(BSTR(L"\\"), &root_folder); hr = service->GetFolder(BSTR(L"\\"), root_folder.ref_to_ptr());
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { /// Cleanup our task, we will recreate it in the callback if needed
root_folder->Release();
};
root_folder->DeleteTask(BSTR(kTaskName.data()), 0); root_folder->DeleteTask(BSTR(kTaskName.data()), 0);
return callback(service, root_folder); return callback(service.get(), root_folder.get());
} }
} // namespace } // namespace
[[nodiscard]] bool add_to_autorun() { [[nodiscard]] bool add_to_autorun(AutorunType type) {
const auto bin_path = shared::get_this_module_path(); const auto bin_path = shared::get_this_module_path();
return with_service([bin_path](ITaskService* service, ITaskFolder* folder) -> bool { return with_service([bin_path, type](ITaskService* service, ITaskFolder* folder) -> bool {
ITaskDefinition* task = nullptr; /// Deduce the autorun config based on type
auto hr = service->NewTask(0, &task); 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)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<IRegistrationInfo> reg_info;
task->Release(); hr = task->get_RegistrationInfo(reg_info.ref_to_ptr());
};
IRegistrationInfo* reg_info = nullptr;
hr = task->get_RegistrationInfo(&reg_info);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<IPrincipal> principal;
reg_info->Release(); hr = task->get_Principal(principal.ref_to_ptr());
};
reg_info->put_Author(_bstr_t(names::kRepoUrl.data()));
IPrincipal* pPrincipal = nullptr;
hr = task->get_Principal(&pPrincipal);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<ITriggerCollection> trigger_collection;
pPrincipal->Release(); hr = task->get_Triggers(trigger_collection.ref_to_ptr());
};
ITriggerCollection* trigger_collection = nullptr;
hr = task->get_Triggers(&trigger_collection);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<ITrigger> trigger;
trigger_collection->Release(); hr = trigger_collection->Create(task_trigger, trigger.ref_to_ptr());
};
ITrigger* trigger = nullptr;
hr = trigger_collection->Create(TASK_TRIGGER_LOGON, &trigger);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<IActionCollection> action_collection;
trigger->Release(); hr = task->get_Actions(action_collection.ref_to_ptr());
};
IActionCollection* action_collection = nullptr;
hr = task->get_Actions(&action_collection);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<IAction> action;
action_collection->Release(); hr = action_collection->Create(TASK_ACTION_EXEC, action.ref_to_ptr());
};
IAction* action = nullptr;
hr = action_collection->Create(TASK_ACTION_EXEC, &action);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<IExecAction> exec_action;
action->Release(); hr = action->QueryInterface(IID_IExecAction, reinterpret_cast<void**>(exec_action.ref_to_ptr()));
};
IExecAction* exec_action = nullptr;
hr = action->QueryInterface(IID_IExecAction, (void**)&exec_action);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { ComPtr<ITaskSettings> settings;
exec_action->Release(); hr = task->get_Settings(settings.ref_to_ptr());
};
ITaskSettings* settings = nullptr;
hr = task->get_Settings(&settings);
if (FAILED(hr)) { if (FAILED(hr)) {
return false; return false;
} }
defer->void { /// Elevated, when system boots
settings->Release(); 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_DisallowStartIfOnBatteries(VARIANT_FALSE);
settings->put_StopIfGoingOnBatteries(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; /// Binary
hr = folder->RegisterTaskDefinition(_bstr_t(kTaskName.data()), task, TASK_CREATE_OR_UPDATE, VARIANT{}, VARIANT{}, TASK_LOGON_INTERACTIVE_TOKEN, exec_action->put_Path(bstr_t(bin_path.string().c_str()));
_variant_t(L""), &registered_task); exec_action->put_Arguments(bstr_t("--from-autorun"));
defer->void { /// Register the task and we are done
registered_task->Release(); 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); return SUCCEEDED(hr);
}); });
} }

View File

@@ -5,15 +5,23 @@
#include <Windows.h> #include <Windows.h>
namespace loader { 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 { struct Config {
public: public:
std::string name; std::string name;
bool disable; bool disable;
bool alloc_console;
bool verbose; bool verbose;
bool from_autorun; bool from_autorun;
AutorunType autorun_type;
bool enable_autorun;
}; };
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name); [[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(); [[nodiscard]] bool remove_from_autorun();
} // namespace loader } // namespace loader

View File

@@ -1,6 +1,8 @@
#include "core/core.hpp" #include "core/core.hpp"
#include "shared/defer.hpp" #include "shared/defer.hpp"
#include "shared/native.hpp"
#include <print> #include <print>
#include <stdexcept> #include <stdexcept>
@@ -19,11 +21,20 @@ namespace loader {
.bInheritHandle = TRUE, .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); 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())); 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 { defer->void {
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
/// Not closing hProcess because we return it /// Not closing hProcess because we return it

View File

@@ -11,7 +11,7 @@
namespace { namespace {
void setup_window(const loader::Config& config) { 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(); shared::alloc_console();
} }
} }
@@ -56,8 +56,8 @@ namespace {
} }
void process_autorun(const loader::Config& config) { void process_autorun(const loader::Config& config) {
if (shared::ctx.state == shared::State::ON) { if (shared::ctx.state == shared::State::ON && config.enable_autorun) {
std::println("** added to autorun: {}", loader::add_to_autorun()); std::println("** added to autorun: {}", loader::add_to_autorun(config.autorun_type));
} else { } else {
std::println("** removed from autorun: {}", loader::remove_from_autorun()); std::println("** removed from autorun: {}", loader::remove_from_autorun());
} }
@@ -68,36 +68,69 @@ namespace {
std::println("thanks for using {}", names::kProjectName); std::println("thanks for using {}", names::kProjectName);
std::println("please don't forget to leave a star at {}", names::kRepoUrl); 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"); system("pause");
} }
} }
} // namespace } // namespace
int main(int argc, char* argv[]) try { 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("-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("-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("-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); program.add_argument("--from-autorun").hidden().default_value(false).implicit_value(true);
try { try {
program.parse_args(argc, argv); program.parse_args(argc, argv);
} catch (...) { } catch (std::exception& e) {
shared::alloc_console(); std::stringstream ss;
std::cerr << program; ss << e.what() << '\n';
system("pause"); ss << program.help().str();
fatal_print(ss.str());
return EXIT_FAILURE; return EXIT_FAILURE;
} }
auto config = loader::Config{ auto config = loader::Config{
.name = program.get<std::string>("-n"), .name = program.get<std::string>("-n"),
.disable = program.get<bool>("-d"), .disable = program.get<bool>("-d"),
.alloc_console = !program.get<bool>("--silent"),
.verbose = program.get<bool>("-v"), .verbose = program.get<bool>("-v"),
.from_autorun = program.get<bool>("--from-autorun"), .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_window(config);
setup_context(config); setup_context(config);

View File

@@ -16,7 +16,7 @@ namespace defendnot {
auto inst = IWscAVStatus::get(); auto inst = IWscAVStatus::get();
/// This can fail if we dont have any avs registered so no com_checked /// 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) { if (shared::ctx.state == shared::State::OFF) {
return; return;
} }
@@ -34,7 +34,7 @@ namespace defendnot {
}; };
/// Register and activate our AV /// 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))); logln("update: {:#x}", com_checked(inst->UpdateStatus(WSCSecurityProductState::ON, 3)));
} }
} // namespace defendnot } // namespace defendnot

View File

@@ -10,7 +10,7 @@
namespace defendnot { namespace defendnot {
namespace detail { 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}}; inline GUID IID_IWscAVStatus = {0x3901A765, 0x0AB91, 0x4BA9, {0xA5, 0x53, 0x5B, 0x85, 0x38, 0xDE, 0xB8, 0x40}};
} // namespace detail } // namespace detail
@@ -21,6 +21,13 @@ namespace defendnot {
EXPIRED = 3 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()) { inline HRESULT com_checked(HRESULT result, const std::source_location loc = std::source_location::current()) {
if (result == 0) { if (result == 0) {
return result; return result;
@@ -53,20 +60,39 @@ namespace defendnot {
} }
class IWscAVStatus { class IWscAVStatus {
private: public:
/// Incomplete stubs to just increase the vfunc id
virtual HRESULT QueryInterface() = 0; virtual HRESULT QueryInterface() = 0;
virtual HRESULT AddRef() = 0; virtual std::uint32_t AddRef() = 0;
virtual HRESULT Release() = 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: 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() { static IWscAVStatus* get() {
IWscAVStatus* result = nullptr; 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; 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