44 Commits

Author SHA1 Message Date
Arsenii es3n1n
2d34fcca9e Merge pull request #39 from es3n1n/bump-1-4-0 2025-07-13 04:22:13 +02:00
es3n1n
77fdc18545 chore: bump to 1.4.0 2025-07-13 04:20:55 +02:00
Arsenii es3n1n
50cceeab75 Merge pull request #38 from es3n1n/fix-callconv 2025-07-13 04:19:20 +02:00
es3n1n
5e4a32c819 fix(com): ensure com calling convention is __stdcall 2025-07-13 04:16:40 +02:00
Arsenii es3n1n
49bbc27908 Merge pull request #33 from es3n1n/bump-1-3-0
build: bump version
2025-05-31 14:28:47 +02:00
es3n1n
0b09bd6ad5 build: bump version 2025-05-31 14:28:05 +02:00
es3n1n
10c77bf51e docs(readme): update pic 2025-05-31 14:27:56 +02:00
Arsenii es3n1n
de47e1ce93 Merge pull request #32 from es3n1n/update-substatuses
feat(bootstrap): set scan/settings/protupdate substatuses to no_action
2025-05-31 14:21:49 +02:00
es3n1n
01980b3052 feat(bootstrap): apply IWscASStatus, pass true to updatestatus 2025-05-31 14:13:19 +02:00
es3n1n
dd13d9c6a0 refactor(com): move com utils to shared/ 2025-05-31 13:30:48 +02:00
es3n1n
5826feabde feat(bootstrap): set scan/settings/protupdate substatuses to no_action 2025-05-30 19:13:37 +02:00
Arsenii es3n1n
0848e966db Merge pull request #31 from es3n1n/wsc-manual-start
feat(loader): start wscsvc manually if needed
2025-05-30 18:40:15 +02:00
es3n1n
8bc56304f5 feat(loader): start wscsvc manually if needed 2025-05-30 18:35:57 +02:00
Arsenii es3n1n
6d6c3fc1c4 Merge pull request #29 from es3n1n/wsc-service-check
feat(loader): check for wsc service before starting
2025-05-30 12:08:36 +02:00
Arsenii es3n1n
57560ccac5 Merge branch 'master' into wsc-service-check 2025-05-30 12:03:50 +02:00
Arsenii es3n1n
4f5244a1ca Merge pull request #28 from es3n1n/windows-server-notice
feat(com): display detailed error message if WSC not available
2025-05-30 12:02:05 +02:00
es3n1n
df93be27ec feat(loader): check for wsc service before starting 2025-05-30 12:01:43 +02:00
es3n1n
4d4cec9593 feat(com): display detailed error message if WSC not available 2025-05-30 11:01:45 +02:00
Arsenii es3n1n
3583c4636f Merge pull request #27 from es3n1n/add-installation-tip
docs(readme): add installation tip
2025-05-27 13:35:10 +02:00
es3n1n
0b394f3457 docs(readme): add installation tip 2025-05-27 13:33:57 +02:00
Arsenii es3n1n
0ced917255 Merge pull request #26 from es3n1n/change-default-av-name
refactor(loader): set default av display name to dnot.sh
2025-05-27 13:19:02 +02:00
es3n1n
a137fd5d5b refactor(loader): set default av display name to dnot.sh 2025-05-27 13:16:27 +02:00
Arsenii es3n1n
2e5285eee8 Merge pull request #22 from es3n1n/permitted-use-notice
docs(readme): add permitted use notice
2025-05-21 18:51:41 +09:00
es3n1n
e7473fc3ec docs(readme): update pic 2025-05-21 18:49:22 +09:00
es3n1n
c8f2e84662 docs(readme): add legitimate use case warning 2025-05-21 18:31:18 +09:00
es3n1n
3b9af89266 docs(readme): add permitted use notice 2025-05-21 18:29:15 +09:00
Arsenii es3n1n
7a48447498 Merge pull request #21 from es3n1n/installer-fix-admin-check
fix(installer): fix administrator check
2025-05-20 15:53:25 +09:00
es3n1n
8152977546 fix(installer): fix administrator check 2025-05-20 15:52:54 +09:00
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
18 changed files with 633 additions and 169 deletions

View File

@@ -1,8 +1,15 @@
name: Build Solution
name: Build/Release
on:
push:
workflow_dispatch:
inputs:
tag:
description: 'Tag for the release'
required: true
permissions:
contents: write
jobs:
build:
@@ -57,6 +64,43 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.platform }}
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 }}

View File

@@ -4,31 +4,90 @@ An even funnier way to disable windows defender.
Defendnot is a successor of [no-defender](https://github.com/es3n1n/no-defender).
![](https://i.imgur.com/VGE8g6a.jpeg)
![](https://i.imgur.com/F9gWA92.png)
> [!CAUTION]
> **Permitted Use Notice**:
>
> Using this tool to facilitate malware distribution, cybercrime, unauthorized access, evading detection, or any illegal activity is strictly prohibited.
>
> Users assume all legal responsibility for how they use this tool and any consequences thereof. You must comply with all applicable local, state, federal, and international laws when using this tool.
>
> By downloading, installing, or using this tool, you acknowledge that you have read, understood, and agree to these terms.
## Installation
> [!TIP]
> You may need to temporarily disable realtime and tamper protection before proceeding, otherwise defender will block `defendnot` binaries due to the `VirTool:Win64/Defnot.A` detection.
### 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: "dnot.sh"]
-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.
This WSC API is undocumented and furthermore requires people to sign an NDA with Microsoft to get its documentation.
The initial implementation of [no-defender](https://github.com/es3n1n/no-defender) used thirdparty code provided by other AVs to register itself in the WSC, while defendnot interacts with WSC directly.
The initial implementation of [no-defender](https://github.com/es3n1n/no-defender) used thirdparty code provided by other AVs to register itself in the WSC, while `defendnot` interacts with WSC directly.
## Limitations
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 :(
- **Needs to stay on disk:**
To keep the AV registration persistent after reboot, `defendnot` adds itself to autorun. That means the binaries have to remain on your system for the Defender "disable" to stick. (Yeah, I wish it were more elegant too.)
- **No support for Windows Server:**
The Windows Security Center (WSC) service doesnt exist on Windows Server editions, so `defendnot` *wont* work there. See [#17](https://github.com/es3n1n/defendnot/issues/17).
## Usage
- **Defender Detection:**
Not surprisingly, Windows Defender really doesnt like `defendnot` and will flag or remove it as `VirTool:Win64/Defnot.A`. Youll need to (temporarily) disable Defenders real-time and tamper protection to install.
```commandline
Usage: defendnot-loader [--help] [--version] [--name VAR] [--disable] [--verbose]
## Legitimate Use Cases
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
```
- Reducing resource consumption in development environments
- Testing system performance under different security configurations
- Educational research on Windows security mechanisms
- Home lab experimentation and learning
> [!IMPORTANT]
> If your intended usage falls outside these legitimate use cases, support in issues/DMs might be denied without any further explanations.
## Writeup

View File

@@ -14,10 +14,11 @@
<ProjectCapability Include="SourceItemsFromImports" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)shared\com.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ctx.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\defer.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ipc.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\names.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\strings.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\native.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)shared\util.hpp" />
</ItemGroup>

123
cxx-shared/shared/com.hpp Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include <Windows.h>
#include "shared/strings.hpp"
#include <cstdint>
#include <format>
#include <print>
#include <source_location>
#include <stdexcept>
#define COM_CALLCONV __stdcall
namespace com {
inline HRESULT checked(HRESULT result, const std::source_location loc = std::source_location::current()) {
if (result == 0) {
return result;
}
auto msg = std::format("Got HRESULT={:#x} at\n{}:{}", static_cast<std::uint32_t>(result) & 0xFFFFFFFF, loc.function_name(), loc.line());
throw std::runtime_error(msg);
}
template <typename Callable>
inline HRESULT retry_while_pending(Callable&& fn) {
bool delayed = false;
HRESULT status = 0;
do {
if (status != 0) {
delayed = true;
std::println("delaying for com retry...");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
status = fn();
} while (status == E_PENDING);
return status;
}
/// A very basic implementation, a lot of stuff is missing
template <typename Ty>
class Ptr {
public:
Ptr() = default;
explicit Ptr(Ty* ptr): ptr_(ptr) { }
~Ptr() {
release();
}
/// No copying
Ptr(const Ptr&) = delete;
Ptr& operator=(const Ptr&) = delete;
/// Move
Ptr(Ptr&& other) noexcept: ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
Ptr& operator=(Ptr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
public:
[[nodiscard]] Ty* get() const {
return ptr_;
}
[[nodiscard]] Ty* operator->() const {
return ptr_;
}
[[nodiscard]] Ty** ref_to_ptr() {
return &ptr_;
}
private:
void release() {
if (ptr_ != nullptr) {
ptr_->Release();
}
}
Ty* ptr_ = nullptr;
};
template <GUID ClsId, GUID IID>
class IBaseObject {
public:
static constexpr GUID kClsId = ClsId;
static constexpr GUID kIID = IID;
private:
virtual HRESULT COM_CALLCONV QueryInterface() = 0;
virtual std::uint32_t COM_CALLCONV AddRef() = 0;
public:
virtual std::uint32_t COM_CALLCONV Release() = 0;
};
template <typename Ty>
concept ComObject = requires {
Ty::kClsId;
Ty::kIID;
};
template <ComObject Ty>
[[nodiscard]] Ptr<Ty> query() {
Ptr<Ty> result;
const auto status = CoCreateInstance(Ty::kClsId, 0, 1, Ty::kIID, reinterpret_cast<LPVOID*>(result.ref_to_ptr()));
if (status == REGDB_E_CLASSNOTREG) {
throw std::runtime_error(strings::wsc_unavailable_error().data());
}
checked(status);
return result;
}
} // namespace com

View File

@@ -1,12 +0,0 @@
#pragma once
#include <string_view>
namespace names {
constexpr std::string_view kProjectName = "defendnot";
constexpr std::string_view kRepoUrl = "https://github.com/es3n1n/defendnot";
constexpr std::string_view kVictimProcess = "Taskmgr.exe";
constexpr std::string_view kDllName = "defendnot.dll";
constexpr std::string_view kVersion = "1.1.0";
} // namespace names

View File

@@ -23,7 +23,7 @@ namespace native {
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));
throw std::runtime_error(std::format("unable to obtain {} from {}", function_name, module_name));
}
return function;
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "shared/util.hpp"
#include <string_view>
namespace strings {
constexpr std::string_view kProjectName = "defendnot";
constexpr std::string_view kRepoUrl = "https://github.com/es3n1n/defendnot";
constexpr std::string_view kVersion = "1.4.0";
constexpr std::string_view kDefaultAVName = "dnot.sh";
constexpr std::string_view kVictimProcess = "Taskmgr.exe";
constexpr std::string_view kDllName = "defendnot.dll";
constexpr std::string_view kWSCUnavailableError = /// !winserver
"Windows Security Center (WSC) is not available on this machine.\n"
"For more details, please refer to: https://github.com/es3n1n/defendnot/issues/25";
constexpr std::string_view kWSCUnavailableErrorWinServer = /// winserver
"Windows Security Center (WSC) is not available on this machine.\n"
"This typically occurs on Windows Server operating systems, which are not supported by this tool.\n"
"For more details, please refer to: https://github.com/es3n1n/defendnot/issues/17";
inline std::string_view wsc_unavailable_error() noexcept {
if (shared::is_winserver()) {
return kWSCUnavailableErrorWinServer;
}
return kWSCUnavailableError;
}
} // namespace strings

View File

@@ -27,4 +27,10 @@ namespace shared {
freopen_s(reinterpret_cast<FILE**>(stderr), "CONOUT$", "w", stderr);
});
}
inline bool is_winserver() {
OSVERSIONINFOEXW osvi = {sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0, 0, VER_NT_WORKSTATION};
const auto cond_mask = VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL);
return !VerifyVersionInfoW(&osvi, VER_PRODUCT_TYPE, cond_mask);
}
} // namespace shared

View File

@@ -1,7 +1,8 @@
#include "core/core.hpp"
#include "shared/com.hpp"
#include "shared/ctx.hpp"
#include "shared/names.hpp"
#include "shared/strings.hpp"
#include <memory>
#include <print>
@@ -17,39 +18,7 @@
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;
};
constexpr std::string_view kTaskName = strings::kProjectName;
void co_initialize() {
static std::once_flag fl;
@@ -66,7 +35,7 @@ namespace loader {
[[nodiscard]] bool with_service(Callable&& callback) {
co_initialize();
ComPtr<ITaskService> service;
com::Ptr<ITaskService> service;
auto hr =
CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, reinterpret_cast<void**>(service.ref_to_ptr()));
if (FAILED(hr)) {
@@ -78,7 +47,7 @@ namespace loader {
return false;
}
ComPtr<ITaskFolder> root_folder;
com::Ptr<ITaskFolder> root_folder;
hr = service->GetFolder(BSTR(L"\\"), root_folder.ref_to_ptr());
if (FAILED(hr)) {
return false;
@@ -104,55 +73,55 @@ namespace loader {
user_id = bstr_sys;
}
ComPtr<ITaskDefinition> task;
com::Ptr<ITaskDefinition> task;
auto hr = service->NewTask(0, task.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<IRegistrationInfo> reg_info;
com::Ptr<IRegistrationInfo> reg_info;
hr = task->get_RegistrationInfo(reg_info.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<IPrincipal> principal;
com::Ptr<IPrincipal> principal;
hr = task->get_Principal(principal.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<ITriggerCollection> trigger_collection;
com::Ptr<ITriggerCollection> trigger_collection;
hr = task->get_Triggers(trigger_collection.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<ITrigger> trigger;
com::Ptr<ITrigger> trigger;
hr = trigger_collection->Create(task_trigger, trigger.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<IActionCollection> action_collection;
com::Ptr<IActionCollection> action_collection;
hr = task->get_Actions(action_collection.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<IAction> action;
com::Ptr<IAction> action;
hr = action_collection->Create(TASK_ACTION_EXEC, action.ref_to_ptr());
if (FAILED(hr)) {
return false;
}
ComPtr<IExecAction> exec_action;
com::Ptr<IExecAction> exec_action;
hr = action->QueryInterface(IID_IExecAction, reinterpret_cast<void**>(exec_action.ref_to_ptr()));
if (FAILED(hr)) {
return false;
}
ComPtr<ITaskSettings> settings;
com::Ptr<ITaskSettings> settings;
hr = task->get_Settings(settings.ref_to_ptr());
if (FAILED(hr)) {
return false;
@@ -164,7 +133,7 @@ namespace loader {
principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
/// Info
reg_info->put_Author(bstr_t(names::kRepoUrl.data()));
reg_info->put_Author(bstr_t(strings::kRepoUrl.data()));
/// Start even if we're on batteries
settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
@@ -175,7 +144,7 @@ namespace loader {
exec_action->put_Arguments(bstr_t("--from-autorun"));
/// Register the task and we are done
ComPtr<IRegisteredTask> registered_task;
com::Ptr<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

@@ -14,6 +14,7 @@ namespace loader {
public:
std::string name;
bool disable;
bool alloc_console;
bool verbose;
bool from_autorun;
AutorunType autorun_type;
@@ -23,4 +24,5 @@ namespace loader {
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name);
[[nodiscard]] bool add_to_autorun(AutorunType type);
[[nodiscard]] bool remove_from_autorun();
void ensure_environment();
} // namespace loader

View File

@@ -0,0 +1,47 @@
#include "core/core.hpp"
#include "shared/strings.hpp"
#include "util/scm.hpp"
#include <format>
#include <print>
#include <stdexcept>
namespace loader {
void ensure_environment() {
auto manager = scm::Manager();
if (!manager.valid()) [[unlikely]] {
throw std::runtime_error("Unable to open scm::Manager");
}
auto service = manager.get_service(L"wscsvc");
if (!service.valid() || !service.query_status()) {
throw std::runtime_error(std::format("{}\nOpen error: {}", strings::wsc_unavailable_error().data(), GetLastError()));
}
if (service.state() == scm::ServiceState::RUNNING) {
/// Wsc service has been already started, no need to start it ourselves
return;
}
/// Let's start the service ourselves
std::println("** wscsvc is not running, starting it..");
if (!service.start()) {
throw std::runtime_error(std::format("{}\nTried to start the service, but go an error: {}", strings::wsc_unavailable_error(), GetLastError()));
}
std::println("** successfully started the service, waiting for it to get up..");
while (true) {
if (!service.query_status(/*force=*/true)) {
throw std::runtime_error(
std::format("{}\nStarted the service, got an error while querying: {}", strings::wsc_unavailable_error(), GetLastError()));
}
if (const auto state = service.state(); state == scm::ServiceState::RUNNING) {
std::println("** we are good to go");
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
} // namespace loader

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
#include "shared/ctx.hpp"
#include "shared/defer.hpp"
#include "shared/ipc.hpp"
#include "shared/names.hpp"
#include "shared/strings.hpp"
#include <argparse/argparse.hpp>
#include <format>
@@ -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();
}
}
@@ -38,12 +38,12 @@ namespace {
std::println("** loading defendnot");
auto dll_path = shared::get_this_module_path().parent_path();
dll_path /= names::kDllName;
dll_path /= strings::kDllName;
if (!std::filesystem::exists(dll_path)) {
throw std::runtime_error(std::format("{} does not exist!", names::kDllName));
throw std::runtime_error(std::format("{} does not exist!", strings::kDllName));
}
return loader::inject(dll_path.string(), names::kVictimProcess);
return loader::inject(dll_path.string(), strings::kVictimProcess);
}
void wait_for_finish(shared::InterProcessCommunication& ipc) {
@@ -65,17 +65,17 @@ namespace {
void banner(const loader::Config& config) {
std::println();
std::println("thanks for using {}", names::kProjectName);
std::println("please don't forget to leave a star at {}", names::kRepoUrl);
std::println("thanks for using {}", strings::kProjectName);
std::println("please don't forget to leave a star at {}", strings::kRepoUrl);
if (!config.from_autorun) {
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), names::kVersion.data(), argparse::default_arguments::none);
argparse::ArgumentParser program(std::format("{}-loader", strings::kProjectName), strings::kVersion.data(), argparse::default_arguments::none);
const auto fatal_print = [](const std::string_view str) -> void {
shared::alloc_console();
@@ -94,12 +94,13 @@ int main(int argc, char* argv[]) try {
.help("shows version and exits")
.default_value(false)
.implicit_value(true)
.action([&fatal_print](const auto& /*unused*/) -> void { fatal_print(std::format("{}-loader v{}", names::kProjectName, names::kVersion)); });
.action([&fatal_print](const auto& /*unused*/) -> void { fatal_print(std::format("{}-loader v{}", strings::kProjectName, strings::kVersion)); });
/// defendnot-loader parameters:
program.add_argument("-n", "--name").help("av display name").default_value(std::string(names::kRepoUrl)).nargs(1);
program.add_argument("-d", "--disable").help(std::format("disable {}", names::kProjectName)).default_value(false).implicit_value(true);
program.add_argument("-n", "--name").help("av display name").default_value(std::string(strings::kDefaultAVName)).nargs(1);
program.add_argument("-d", "--disable").help(std::format("disable {}", strings::kProjectName)).default_value(false).implicit_value(true);
program.add_argument("-v", "--verbose").help("verbose logging").default_value(false).implicit_value(true);
program.add_argument("--silent").help("do not allocate console").default_value(false).implicit_value(true);
program.add_argument("--autorun-as-user").help("create autorun task as currently logged in user").default_value(false).implicit_value(true);
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);
@@ -117,6 +118,7 @@ int main(int argc, char* argv[]) try {
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
@@ -125,7 +127,19 @@ int main(int argc, char* argv[]) try {
.enable_autorun = !program.get<bool>("--disable-autorun"),
};
/// When running from autorun, we'll be missing all the cli arguments, so lets load some relevant ones
if (config.from_autorun) {
shared::ctx.deserialize();
config.verbose = shared::ctx.verbose;
}
if (!config.alloc_console && config.verbose) {
fatal_print("--silent flag can not be used in combination with --verbose");
}
setup_window(config);
loader::ensure_environment();
setup_context(config);
/// \todo @es3n1n: move this to a separate function and add move ctor for ipc
@@ -139,11 +153,17 @@ int main(int argc, char* argv[]) try {
};
wait_for_finish(ipc);
process_autorun(config);
/// Only create autorun task when not running from autorun, no need to recreate it because we're missing some config vars
if (!config.from_autorun) {
process_autorun(config);
}
banner(config);
return EXIT_SUCCESS;
} catch (std::exception& err) {
shared::alloc_console();
std::println(stderr, "** fatal error: {}", err.what());
system("pause");
return EXIT_FAILURE;

View File

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

View File

@@ -7,19 +7,35 @@
#include <Windows.h>
namespace defendnot {
namespace {
template <com::ComObject Ty>
void apply(const std::string_view log_prefix, const BSTR name) {
/// Get the WSC interface
auto inst = com::query<Ty>();
/// This can fail if we dont have any products registered so no com_checked
logln("{}_unregister: {:#x}", log_prefix, com::retry_while_pending([&inst]() -> HRESULT { return inst->Unregister(); }) & 0xFFFFFFFF);
if (shared::ctx.state == shared::State::OFF) {
return;
}
/// Register and activate
logln("{}_register: {:#x}", log_prefix, com::checked(inst->Register(name, name, 0, 0)));
logln("{}_update: {:#x}", log_prefix, com::checked(inst->UpdateStatus(WSCSecurityProductState::ON, static_cast<BOOL>(true))));
/// Update the substatuses, if the interface supports this
if constexpr (std::is_same_v<Ty, IWscAVStatus4>) {
logln("{}_scan_update: {:#x}", log_prefix, com::checked(inst->UpdateScanSubstatus(WSCSecurityProductSubStatus::NO_ACTION)));
logln("{}_settings_update: {:#x}", log_prefix, com::checked(inst->UpdateSettingsSubstatus(WSCSecurityProductSubStatus::NO_ACTION)));
logln("{}_prot_update: {:#x}", log_prefix, com::checked(inst->UpdateProtectionUpdateSubstatus(WSCSecurityProductSubStatus::NO_ACTION)));
}
}
} // namespace
void startup() {
/// Setup
shared::ctx.deserialize();
logln("init: {:#x}", com_checked(CoInitialize(nullptr)));
/// Get the main WSC interface we will be dealing with
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(); }) & 0xFFFFFFFF);
if (shared::ctx.state == shared::State::OFF) {
return;
}
logln("init: {:#x}", com::checked(CoInitialize(nullptr)));
/// WSC will reject the register request if name is empty
auto name_w = std::wstring(shared::ctx.name.begin(), shared::ctx.name.end());
@@ -33,8 +49,8 @@ namespace defendnot {
SysFreeString(name);
};
/// Register and activate our AV
logln("register: {:#x}", com_checked(inst->Register(name, name, 0, 0)));
logln("update: {:#x}", com_checked(inst->UpdateStatus(WSCSecurityProductState::ON, 3)));
/// Register our stuff in the WSC interfaces
apply<IWscASStatus>("IWscASStatus", name);
apply<IWscAVStatus4>("IWscAVStatus4", name);
}
} // namespace defendnot

View File

@@ -5,13 +5,23 @@
#include <thread>
#include "core/log.hpp"
#include "shared/com.hpp"
#include "shared/strings.hpp"
#include <Windows.h>
namespace defendnot {
namespace detail {
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}};
constexpr GUID CLSID_WscIsv = {0xF2102C37, 0x90C3, 0x450C, {0xB3, 0x0F6, 0x92, 0xBE, 0x16, 0x93, 0xBD, 0xF2}};
constexpr GUID IID_IWscFWStatus = {0x9B8F6C6E, 0x8A4A, 0x4891, {0xAF, 0x63, 0x1A, 0x2F, 0x50, 0x92, 0x40, 0x40}};
constexpr GUID IID_IWscFWStatus2 = {0x62F698CB, 0x94A, 0x4C68, {0x94, 0x19, 0x8E, 0x8C, 0x49, 0x42, 0x0E, 0x59}};
constexpr GUID IID_IWscAVStatus = {0x3901A765, 0xAB91, 0x4BA9, {0xA5, 0x53, 0x5B, 0x85, 0x38, 0xDE, 0xB8, 0x40}};
constexpr GUID IID_IWscAVStatus3 = {0xCF007CA2, 0xF5E3, 0x11E5, {0x9C, 0xE9, 0x5E, 0x55, 0x17, 0x50, 0x7C, 0x66}};
constexpr GUID IID_IWscAVStatus4 = {0x4DCBAFAC, 0x29BA, 0x46B1, {0x80, 0xFC, 0xB8, 0xBD, 0xE3, 0xC0, 0xAE, 0x4D}};
constexpr GUID IID_IWscASStatus = {0x24E9756, 0xBA6C, 0x4AD1, {0x83, 0x21, 0x87, 0xBA, 0xE7, 0x8F, 0xD0, 0xE3}};
} // namespace detail
enum class WSCSecurityProductState : std::uint32_t {
@@ -25,75 +35,38 @@ namespace defendnot {
NOT_SET = 0,
NO_ACTION = 1,
ACTION_RECOMMENDED = 2,
ACTION_NEEDED = 3
ACTION_NEEDED = 3,
};
inline HRESULT com_checked(HRESULT result, const std::source_location loc = std::source_location::current()) {
if (result == 0) {
return result;
}
auto msg = std::format("Got HRESULT={:#x} at\n{}:{}", static_cast<std::uint32_t>(result) & 0xFFFFFFFF, loc.function_name(), loc.line());
throw std::runtime_error(msg);
}
template <typename Callable>
inline HRESULT com_retry_while_pending(Callable&& fn) {
bool delayed = false;
HRESULT status = 0;
do {
if (status != 0) {
delayed = true;
logln("delaying for com retry...");
std::this_thread::sleep_for(std::chrono::seconds(5));
}
status = fn();
} while (status == E_PENDING);
if (delayed) {
/// Sleep for additional 15 seconds to let WSC proceed all previous requests
std::this_thread::sleep_for(std::chrono::seconds(15));
}
return status;
}
class IWscAVStatus {
class IWscAVStatus4 : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscAVStatus4> {
public:
virtual HRESULT QueryInterface() = 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;
virtual HRESULT COM_CALLCONV Register(BSTR path_to_signed_product_exe, BSTR display_name, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV Unregister() = 0;
virtual HRESULT COM_CALLCONV UpdateStatus(WSCSecurityProductState state, BOOL unk) = 0;
virtual HRESULT COM_CALLCONV InitiateOfflineCleaning(std::uint16_t*, std::uint16_t*) = 0;
virtual HRESULT COM_CALLCONV NotifyUserForNearExpiration(std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV MakeDefaultProductRequest() = 0;
virtual HRESULT COM_CALLCONV IsDefaultProductEnforced(std::uint32_t* result) = 0;
virtual HRESULT COM_CALLCONV UpdateScanSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT COM_CALLCONV UpdateSettingsSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT COM_CALLCONV UpdateProtectionUpdateSubstatus(WSCSecurityProductSubStatus status) = 0;
virtual HRESULT COM_CALLCONV RegisterAV(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV UnregisterAV() = 0;
virtual HRESULT COM_CALLCONV UpdateStatusAV(WSCSecurityProductState state, BOOL unk) = 0;
virtual HRESULT COM_CALLCONV InitiateOfflineCleaningAV(std::uint16_t*, std::uint16_t*) = 0;
virtual HRESULT COM_CALLCONV NotifyUserForNearExpirationAV(std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV RegisterFW(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV UnregisterFW() = 0;
virtual HRESULT COM_CALLCONV UpdateStatusFW(WSCSecurityProductState state) = 0;
virtual HRESULT COM_CALLCONV RegisterAS(std::uint16_t*, std::uint16_t*, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV UnregisterAS() = 0;
virtual HRESULT COM_CALLCONV UpdateStatusAS(WSCSecurityProductState state, BOOL unk) = 0;
};
class IWscASStatus : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscASStatus> {
public:
static IWscAVStatus* get() {
IWscAVStatus* result = nullptr;
com_checked(CoCreateInstance(detail::CLSID_IWscAVStatus, 0, 1, detail::IID_IWscAVStatus, reinterpret_cast<LPVOID*>(&result)));
return result;
}
virtual HRESULT COM_CALLCONV Register(BSTR path_to_signed_product_exe, BSTR display_name, std::uint32_t, std::uint32_t) = 0;
virtual HRESULT COM_CALLCONV Unregister() = 0;
virtual HRESULT COM_CALLCONV UpdateStatus(WSCSecurityProductState state, BOOL unk) = 0;
};
} // namespace defendnot

58
install.ps1 Normal file
View File

@@ -0,0 +1,58 @@
$ErrorActionPreference = "Stop"
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
Write-Error "This script requires administrator privileges. Please run as Administrator."
exit 1
}
$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