mirror of
https://github.com/es3n1n/defendnot.git
synced 2026-08-02 10:32:01 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9be5724f | ||
|
|
a46fdf49ab | ||
|
|
2d0badd5cb | ||
|
|
2d34fcca9e | ||
|
|
77fdc18545 | ||
|
|
50cceeab75 | ||
|
|
5e4a32c819 | ||
|
|
49bbc27908 | ||
|
|
0b09bd6ad5 | ||
|
|
10c77bf51e | ||
|
|
de47e1ce93 | ||
|
|
01980b3052 | ||
|
|
dd13d9c6a0 | ||
|
|
5826feabde | ||
|
|
0848e966db | ||
|
|
8bc56304f5 | ||
|
|
6d6c3fc1c4 | ||
|
|
57560ccac5 | ||
|
|
4f5244a1ca | ||
|
|
df93be27ec | ||
|
|
4d4cec9593 | ||
|
|
3583c4636f | ||
|
|
0b394f3457 | ||
|
|
0ced917255 | ||
|
|
a137fd5d5b | ||
|
|
2e5285eee8 | ||
|
|
e7473fc3ec | ||
|
|
c8f2e84662 | ||
|
|
3b9af89266 | ||
|
|
7a48447498 | ||
|
|
8152977546 | ||
|
|
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/
|
||||
|
||||
86
README.md
86
README.md
@@ -1,38 +1,92 @@
|
||||
# defendnot
|
||||
|
||||
<img src="https://i.imgur.com/F9gWA92.png" align="right" width="50%"/>
|
||||
|
||||
An even funnier way to disable windows defender.
|
||||
|
||||
Defendnot is a successor of [no-defender](https://github.com/es3n1n/no-defender).
|
||||
> [!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
|
||||
|
||||
> [!WARNING]
|
||||
> 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
|
||||
|
||||
# Example 4: Run once, without allocating console
|
||||
& ([ScriptBlock]::Create((irm https://dnot.sh/))) --silent --disable-autorun
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
- **No support for Windows Server:**
|
||||
The Windows Security Center (WSC) service doesn't exist on Windows Server editions, so `defendnot` *won't* work there. See [#17](https://github.com/es3n1n/defendnot/issues/17).
|
||||
|
||||
## Usage
|
||||
- **Defender Detection:**
|
||||
Windows Defender really doesn't like `defendnot` and will flag or remove it as `VirTool:Win64/Defnot.A`. You'll need to (temporarily) disable Defender's 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
|
||||
|
||||
## Implementation
|
||||
> [!IMPORTANT]
|
||||
> If your intended usage falls outside these legitimate use cases, support in issues/DMs might be denied without any further explanations.
|
||||
|
||||
A more detailed writeup will be coming in a few days.
|
||||
## Writeup
|
||||
|
||||
[How I ruined my vacation by reverse engineering WSC](https://blog.es3n1n.eu/posts/how-i-ruined-my-vacation/)
|
||||
|
||||
## Special thanks
|
||||
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
<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>
|
||||
</Project>
|
||||
123
cxx-shared/shared/com.hpp
Normal file
123
cxx-shared/shared/com.hpp
Normal 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
|
||||
@@ -1,10 +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";
|
||||
} // 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 {}", function_name, module_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)
|
||||
30
cxx-shared/shared/strings.hpp
Normal file
30
cxx-shared/shared/strings.hpp
Normal 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.5.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
|
||||
@@ -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
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "core/core.hpp"
|
||||
|
||||
#include "shared/com.hpp"
|
||||
#include "shared/ctx.hpp"
|
||||
#include "shared/defer.hpp"
|
||||
#include "shared/names.hpp"
|
||||
#include "shared/strings.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include <comdef.h>
|
||||
#include <taskschd.h>
|
||||
@@ -16,157 +18,135 @@
|
||||
|
||||
namespace loader {
|
||||
namespace {
|
||||
constexpr std::string_view kTaskName = names::kProjectName;
|
||||
constexpr std::string_view kTaskName = strings::kProjectName;
|
||||
|
||||
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();
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
defer->void {
|
||||
CoUninitialize();
|
||||
};
|
||||
|
||||
ITaskService* service = nullptr;
|
||||
hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, reinterpret_cast<void**>(&service));
|
||||
hr = service->Connect(variant_t{}, variant_t{}, variant_t{}, variant_t{});
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
defer->void {
|
||||
service->Release();
|
||||
};
|
||||
|
||||
hr = service->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{});
|
||||
com::Ptr<ITaskFolder> root_folder;
|
||||
hr = service->GetFolder(bstr_t(L"\\"), root_folder.ref_to_ptr());
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ITaskFolder* root_folder = nullptr;
|
||||
hr = service->GetFolder(BSTR(L"\\"), &root_folder);
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
defer->void {
|
||||
root_folder->Release();
|
||||
};
|
||||
|
||||
root_folder->DeleteTask(BSTR(kTaskName.data()), 0);
|
||||
return callback(service, root_folder);
|
||||
/// Cleanup our task, we will recreate it in the callback if needed
|
||||
root_folder->DeleteTask(bstr_t(kTaskName.data()), 0);
|
||||
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;
|
||||
}
|
||||
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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);
|
||||
com::Ptr<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(strings::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
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,15 +5,24 @@
|
||||
#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();
|
||||
void ensure_environment();
|
||||
} // namespace loader
|
||||
|
||||
47
defendnot-loader/core/ensure_environment.cpp
Normal file
47
defendnot-loader/core/ensure_environment.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
@@ -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());
|
||||
}
|
||||
@@ -65,40 +65,81 @@ 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), "1.0.0");
|
||||
argparse::ArgumentParser program(std::format("{}-loader", strings::kProjectName), strings::kVersion.data(), argparse::default_arguments::none);
|
||||
|
||||
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);
|
||||
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{}", strings::kProjectName, strings::kVersion)); });
|
||||
|
||||
/// defendnot-loader parameters:
|
||||
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);
|
||||
|
||||
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"),
|
||||
};
|
||||
|
||||
/// 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
|
||||
@@ -112,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;
|
||||
|
||||
120
defendnot-loader/util/scm.hpp
Normal file
120
defendnot-loader/util/scm.hpp
Normal 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
|
||||
@@ -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(); }));
|
||||
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)));
|
||||
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
|
||||
|
||||
@@ -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 RCLSID = {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 {
|
||||
@@ -21,53 +31,42 @@ namespace defendnot {
|
||||
EXPIRED = 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 {
|
||||
private:
|
||||
/// Incomplete stubs to just increase the vfunc id
|
||||
virtual HRESULT QueryInterface() = 0;
|
||||
virtual HRESULT AddRef() = 0;
|
||||
virtual HRESULT Release() = 0;
|
||||
enum class WSCSecurityProductSubStatus : std::uint32_t {
|
||||
NOT_SET = 0,
|
||||
NO_ACTION = 1,
|
||||
ACTION_RECOMMENDED = 2,
|
||||
ACTION_NEEDED = 3,
|
||||
};
|
||||
|
||||
class IWscAVStatus4 : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscAVStatus4> {
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
static IWscAVStatus* get() {
|
||||
IWscAVStatus* result = nullptr;
|
||||
com_checked(CoCreateInstance(detail::RCLSID, 0, 1, detail::IID_IWscAVStatus, reinterpret_cast<LPVOID*>(&result)));
|
||||
return result;
|
||||
}
|
||||
class IWscASStatus : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscASStatus> {
|
||||
public:
|
||||
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
58
install.ps1
Normal 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
|
||||
Reference in New Issue
Block a user