mirror of
https://github.com/es3n1n/defendnot.git
synced 2026-10-01 10:01:36 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
525176a377 | ||
|
|
192b2d798c | ||
|
|
0d15ea6666 | ||
|
|
2acdf7724a | ||
|
|
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 |
107
.github/workflows/build.yml
vendored
Normal file
107
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
name: Build/Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Tag for the release'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-2022
|
||||||
|
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
|
||||||
|
Copy-Item -Path ".\extra-strip.bat" -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/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
|
Win32/
|
||||||
[Aa][Rr][Mm]/
|
[Aa][Rr][Mm]/
|
||||||
[Aa][Rr][Mm]64/
|
[Aa][Rr][Mm]64/
|
||||||
bld/
|
bld/
|
||||||
|
|||||||
78
README.md
78
README.md
@@ -1,38 +1,84 @@
|
|||||||
# defendnot
|
# defendnot
|
||||||
|
|
||||||
|
<img src="https://i.imgur.com/F9gWA92.png" align="right" width="50%"/>
|
||||||
|
|
||||||
An even funnier way to disable windows defender.
|
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, 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 laws when using this tool.
|
||||||
|
>
|
||||||
|
> By downloading, installing, or using this tool, you acknowledge that you agree to these terms.
|
||||||
|
|
||||||

|
## Installation
|
||||||
|
|
||||||
## How it works
|
> [!WARNING]
|
||||||
|
> You may need to temporarily disable realtime and tamper protection before proceeding, otherwise defender will block defendnot binaries from running.
|
||||||
|
>
|
||||||
|
> On newer Windows 11 builds you may also have to turn off Smart App Control.
|
||||||
|
>
|
||||||
|
> - Real-time protection: `windowsdefender://threatsettings/`
|
||||||
|
> - Smart App Control: `windowsdefender://appbrowser/`
|
||||||
|
|
||||||
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.
|
### One-liner
|
||||||
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.
|
Open the powershell as administrator and execute any of these:
|
||||||
|
|
||||||
## Limitations
|
```powershell
|
||||||
|
# Example 1: Basic installation
|
||||||
|
irm https://dnot.sh/ | iex
|
||||||
|
|
||||||
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 :(
|
# Example 2: With custom AV name and firewall
|
||||||
|
& ([ScriptBlock]::Create((irm https://dnot.sh/))) --name "Custom AV name" --firewall
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Example 5: Uninstall
|
||||||
|
& ([ScriptBlock]::Create((irm https://dnot.sh/))) --disable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
Download the [latest](https://github.com/es3n1n/defendnot/releases/latest) release, extract it somewhere and launch `defendnot-loader`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```commandline
|
```commandline
|
||||||
Usage: defendnot-loader [--help] [--version] [--name VAR] [--disable] [--verbose]
|
Usage: defendnot-loader [--help] [--version] [--name VAR] [--disable] [--verbose] [--firewall] [--silent] [--autorun-as-user] [--disable-autorun]
|
||||||
|
|
||||||
Optional arguments:
|
Optional arguments:
|
||||||
-h, --help shows help message and exits
|
-h, --help prints help message and exits
|
||||||
-v, --version prints version information and exits
|
--version shows version and exits
|
||||||
-n, --name av display name [default: "https://github.com/es3n1n/defendnot"]
|
-n, --name av display name [default: "dnot.sh"]
|
||||||
-d, --disable disable defendnot
|
-d, --disable disable defendnot
|
||||||
-v, --verbose verbose logging
|
-v, --verbose verbose logging
|
||||||
|
--firewall also register a fake firewall
|
||||||
|
--silent do not allocate console
|
||||||
|
--autorun-as-user create autorun task as currently logged in user
|
||||||
|
--disable-autorun disable autorun task creation
|
||||||
```
|
```
|
||||||
|
|
||||||
## Implementation
|
## Stripping Defender out further (optional)
|
||||||
|
|
||||||
A more detailed writeup will be coming in a few days.
|
defendnot only registers a fake AV through WSC, but if you want to strip more Defender stuff out, run the optional `extra-strip.bat` as admin after defendnot is active. It disables a bunch more Defender policies in registry: real-time monitoring, behavior monitoring, cloud reporting, signature updates, etc.
|
||||||
|
|
||||||
|
It's a separate script because the keys are finicky and undoing them on `--disable` would mean saving every value first somewhere and writing all of them back, which is _waaay_ more bookkeeping than I feel like implementing.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- **Needs to stay on disk:**
|
||||||
|
To keep the AV registration after reboot, defendnot adds itself to autorun.
|
||||||
|
|
||||||
|
## Writeup
|
||||||
|
|
||||||
|
[How I ruined my vacation by reverse engineering WSC](https://blog.es3n1n.eu/posts/how-i-ruined-my-vacation/)
|
||||||
|
|
||||||
## Special thanks
|
## Special thanks
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
<ProjectCapability Include="SourceItemsFromImports" />
|
<ProjectCapability Include="SourceItemsFromImports" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\com.hpp" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ctx.hpp" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ctx.hpp" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)shared\defer.hpp" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\defer.hpp" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ipc.hpp" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\ipc.hpp" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)shared\names.hpp" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\strings.hpp" />
|
||||||
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\native.hpp" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)shared\util.hpp" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)shared\util.hpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</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
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "shared/util.hpp"
|
#include "shared/util.hpp"
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
namespace shared {
|
namespace shared {
|
||||||
constexpr std::size_t kMaxNameLength = 128;
|
constexpr std::size_t kMaxNameLength = 128;
|
||||||
constexpr std::string_view kCtxPath = "ctx.bin";
|
constexpr std::string_view kCtxPath = "ctx.bin";
|
||||||
@@ -28,6 +29,7 @@ namespace shared {
|
|||||||
State state = State::ON;
|
State state = State::ON;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
std::array<char, kMaxNameLength + 1> name = {0}; // +1 for the nullterm
|
std::array<char, kMaxNameLength + 1> name = {0}; // +1 for the nullterm
|
||||||
|
bool register_firewall = false;
|
||||||
|
|
||||||
void serialize() const {
|
void serialize() const {
|
||||||
std::ofstream stream(detail::ctx_path(), std::ios::binary);
|
std::ofstream stream(detail::ctx_path(), std::ios::binary);
|
||||||
@@ -50,3 +52,4 @@ namespace shared {
|
|||||||
|
|
||||||
static_assert(std::is_trivially_copyable_v<Context>);
|
static_assert(std::is_trivially_copyable_v<Context>);
|
||||||
} // namespace shared
|
} // namespace shared
|
||||||
|
#pragma pack(pop)
|
||||||
|
|||||||
@@ -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.6.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);
|
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
|
} // namespace shared
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#include "core/core.hpp"
|
#include "core/core.hpp"
|
||||||
|
|
||||||
|
#include "shared/com.hpp"
|
||||||
#include "shared/ctx.hpp"
|
#include "shared/ctx.hpp"
|
||||||
#include "shared/defer.hpp"
|
#include "shared/strings.hpp"
|
||||||
#include "shared/names.hpp"
|
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include <comdef.h>
|
#include <comdef.h>
|
||||||
#include <taskschd.h>
|
#include <taskschd.h>
|
||||||
@@ -16,157 +18,135 @@
|
|||||||
|
|
||||||
namespace loader {
|
namespace loader {
|
||||||
namespace {
|
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>
|
template <typename Callable>
|
||||||
[[nodiscard]] bool with_service(Callable&& callback) {
|
[[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)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
hr = service->Connect(variant_t{}, variant_t{}, variant_t{}, variant_t{});
|
||||||
CoUninitialize();
|
|
||||||
};
|
|
||||||
|
|
||||||
ITaskService* service = nullptr;
|
|
||||||
hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_ITaskService, reinterpret_cast<void**>(&service));
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<ITaskFolder> root_folder;
|
||||||
service->Release();
|
hr = service->GetFolder(bstr_t(L"\\"), root_folder.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
hr = service->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{});
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ITaskFolder* root_folder = nullptr;
|
/// Cleanup our task, we will recreate it in the callback if needed
|
||||||
hr = service->GetFolder(BSTR(L"\\"), &root_folder);
|
root_folder->DeleteTask(bstr_t(kTaskName.data()), 0);
|
||||||
if (FAILED(hr)) {
|
return callback(service.get(), root_folder.get());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
defer->void {
|
|
||||||
root_folder->Release();
|
|
||||||
};
|
|
||||||
|
|
||||||
root_folder->DeleteTask(BSTR(kTaskName.data()), 0);
|
|
||||||
return callback(service, root_folder);
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
[[nodiscard]] bool add_to_autorun() {
|
[[nodiscard]] bool add_to_autorun(AutorunType type) {
|
||||||
const auto bin_path = shared::get_this_module_path();
|
const auto bin_path = shared::get_this_module_path();
|
||||||
|
|
||||||
return with_service([bin_path](ITaskService* service, ITaskFolder* folder) -> bool {
|
return with_service([bin_path, type](ITaskService* service, ITaskFolder* folder) -> bool {
|
||||||
ITaskDefinition* task = nullptr;
|
/// Deduce the autorun config based on type
|
||||||
auto hr = service->NewTask(0, &task);
|
const auto task_trigger = type == AutorunType::AS_SYSTEM_ON_BOOT ? TASK_TRIGGER_BOOT : TASK_TRIGGER_LOGON;
|
||||||
|
const auto logon_type = type == AutorunType::AS_SYSTEM_ON_BOOT ? TASK_LOGON_SERVICE_ACCOUNT : TASK_LOGON_INTERACTIVE_TOKEN;
|
||||||
|
|
||||||
|
BSTR user_id = nullptr;
|
||||||
|
auto bstr_sys = bstr_t(L"SYSTEM");
|
||||||
|
if (type == AutorunType::AS_SYSTEM_ON_BOOT) {
|
||||||
|
user_id = bstr_sys;
|
||||||
|
}
|
||||||
|
|
||||||
|
com::Ptr<ITaskDefinition> task;
|
||||||
|
auto hr = service->NewTask(0, task.ref_to_ptr());
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<IRegistrationInfo> reg_info;
|
||||||
task->Release();
|
hr = task->get_RegistrationInfo(reg_info.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
IRegistrationInfo* reg_info = nullptr;
|
|
||||||
hr = task->get_RegistrationInfo(®_info);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<IPrincipal> principal;
|
||||||
reg_info->Release();
|
hr = task->get_Principal(principal.ref_to_ptr());
|
||||||
};
|
|
||||||
reg_info->put_Author(_bstr_t(names::kRepoUrl.data()));
|
|
||||||
|
|
||||||
IPrincipal* pPrincipal = nullptr;
|
|
||||||
hr = task->get_Principal(&pPrincipal);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<ITriggerCollection> trigger_collection;
|
||||||
pPrincipal->Release();
|
hr = task->get_Triggers(trigger_collection.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
ITriggerCollection* trigger_collection = nullptr;
|
|
||||||
hr = task->get_Triggers(&trigger_collection);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<ITrigger> trigger;
|
||||||
trigger_collection->Release();
|
hr = trigger_collection->Create(task_trigger, trigger.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
ITrigger* trigger = nullptr;
|
|
||||||
hr = trigger_collection->Create(TASK_TRIGGER_LOGON, &trigger);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<IActionCollection> action_collection;
|
||||||
trigger->Release();
|
hr = task->get_Actions(action_collection.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
IActionCollection* action_collection = nullptr;
|
|
||||||
hr = task->get_Actions(&action_collection);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<IAction> action;
|
||||||
action_collection->Release();
|
hr = action_collection->Create(TASK_ACTION_EXEC, action.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
IAction* action = nullptr;
|
|
||||||
hr = action_collection->Create(TASK_ACTION_EXEC, &action);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<IExecAction> exec_action;
|
||||||
action->Release();
|
hr = action->QueryInterface(IID_IExecAction, reinterpret_cast<void**>(exec_action.ref_to_ptr()));
|
||||||
};
|
|
||||||
|
|
||||||
IExecAction* exec_action = nullptr;
|
|
||||||
hr = action->QueryInterface(IID_IExecAction, (void**)&exec_action);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
com::Ptr<ITaskSettings> settings;
|
||||||
exec_action->Release();
|
hr = task->get_Settings(settings.ref_to_ptr());
|
||||||
};
|
|
||||||
|
|
||||||
ITaskSettings* settings = nullptr;
|
|
||||||
hr = task->get_Settings(&settings);
|
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer->void {
|
/// Elevated, when system boots
|
||||||
settings->Release();
|
principal->put_UserId(user_id);
|
||||||
};
|
principal->put_LogonType(logon_type);
|
||||||
|
principal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
|
||||||
|
|
||||||
pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
|
/// Info
|
||||||
|
reg_info->put_Author(bstr_t(strings::kRepoUrl.data()));
|
||||||
|
|
||||||
|
/// Start even if we're on batteries
|
||||||
settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
|
settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
|
||||||
settings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
|
settings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
|
||||||
exec_action->put_Path(_bstr_t(bin_path.string().c_str()));
|
|
||||||
exec_action->put_Arguments(_bstr_t("--from-autorun"));
|
|
||||||
|
|
||||||
IRegisteredTask* registered_task = nullptr;
|
/// Binary
|
||||||
hr = folder->RegisterTaskDefinition(_bstr_t(kTaskName.data()), task, TASK_CREATE_OR_UPDATE, VARIANT{}, VARIANT{}, TASK_LOGON_INTERACTIVE_TOKEN,
|
exec_action->put_Path(bstr_t(bin_path.string().c_str()));
|
||||||
_variant_t(L""), ®istered_task);
|
exec_action->put_Arguments(bstr_t("--from-autorun"));
|
||||||
|
|
||||||
defer->void {
|
/// Register the task and we are done
|
||||||
registered_task->Release();
|
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);
|
return SUCCEEDED(hr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,37 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "shared/ctx.hpp"
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
namespace loader {
|
namespace loader {
|
||||||
|
enum class AutorunType : std::uint8_t {
|
||||||
|
AS_SYSTEM_ON_BOOT = 0, ///< launch on system boot as NT AUTHORITY\SYSTEM
|
||||||
|
AS_CURRENT_USER_ON_LOGIN = 1, ///< launch on user login
|
||||||
|
};
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
public:
|
public:
|
||||||
std::string name;
|
std::string name;
|
||||||
bool disable;
|
bool disable;
|
||||||
|
bool alloc_console;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
|
bool register_firewall;
|
||||||
bool from_autorun;
|
bool from_autorun;
|
||||||
|
AutorunType autorun_type;
|
||||||
|
bool enable_autorun;
|
||||||
|
|
||||||
|
Config& operator=(const shared::Context& ctx) {
|
||||||
|
verbose = ctx.verbose;
|
||||||
|
register_firewall = ctx.register_firewall;
|
||||||
|
name = ctx.name.data();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name);
|
[[nodiscard]] HANDLE inject(std::string_view dll_path, std::string_view proc_name);
|
||||||
[[nodiscard]] bool add_to_autorun();
|
[[nodiscard]] bool add_to_autorun(AutorunType type);
|
||||||
[[nodiscard]] bool remove_from_autorun();
|
[[nodiscard]] bool remove_from_autorun();
|
||||||
|
void ensure_environment();
|
||||||
} // namespace loader
|
} // 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 "core/core.hpp"
|
||||||
|
|
||||||
#include "shared/defer.hpp"
|
#include "shared/defer.hpp"
|
||||||
|
#include "shared/native.hpp"
|
||||||
|
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -19,11 +21,20 @@ namespace loader {
|
|||||||
.bInheritHandle = TRUE,
|
.bInheritHandle = TRUE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// By setting ReadImageFileExecOptions to FALSE and attaching ourselves as a debugger we can skip the IFEO
|
||||||
|
/// \xref: https://github.com/es3n1n/defendnot/issues/7#issuecomment-2874903650
|
||||||
|
native::get_peb()->read_image_file_exec_options = 0;
|
||||||
|
|
||||||
std::println("** booting {}", proc_name);
|
std::println("** booting {}", proc_name);
|
||||||
if (!CreateProcessA(nullptr, const_cast<char*>(proc_name.data()), &sa, &sa, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
|
const auto process_flags = CREATE_SUSPENDED | DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS;
|
||||||
|
if (!CreateProcessA(nullptr, const_cast<char*>(proc_name.data()), &sa, &sa, FALSE, process_flags, nullptr, nullptr, &si, &pi)) {
|
||||||
throw std::runtime_error(std::format("unable to create process: {}", GetLastError()));
|
throw std::runtime_error(std::format("unable to create process: {}", GetLastError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detach
|
||||||
|
native::debug_set_process_kill_on_exit(false);
|
||||||
|
native::debug_active_process_stop(pi.dwProcessId);
|
||||||
|
|
||||||
defer->void {
|
defer->void {
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
/// Not closing hProcess because we return it
|
/// Not closing hProcess because we return it
|
||||||
|
|||||||
@@ -250,11 +250,13 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="core\autorun.cpp" />
|
<ClCompile Include="core\autorun.cpp" />
|
||||||
|
<ClCompile Include="core\ensure_environment.cpp" />
|
||||||
<ClCompile Include="core\inject.cpp" />
|
<ClCompile Include="core\inject.cpp" />
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="core\core.hpp" />
|
<ClInclude Include="core\core.hpp" />
|
||||||
|
<ClInclude Include="util\scm.hpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
|||||||
@@ -24,10 +24,16 @@
|
|||||||
<ClCompile Include="core\autorun.cpp">
|
<ClCompile Include="core\autorun.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="core\ensure_environment.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="core\core.hpp">
|
<ClInclude Include="core\core.hpp">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="util\scm.hpp">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#include "shared/ctx.hpp"
|
#include "shared/ctx.hpp"
|
||||||
#include "shared/defer.hpp"
|
#include "shared/defer.hpp"
|
||||||
#include "shared/ipc.hpp"
|
#include "shared/ipc.hpp"
|
||||||
#include "shared/names.hpp"
|
#include "shared/strings.hpp"
|
||||||
#include <argparse/argparse.hpp>
|
#include <argparse/argparse.hpp>
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void setup_window(const loader::Config& config) {
|
void setup_window(const loader::Config& config) {
|
||||||
if (!config.from_autorun || config.verbose) {
|
if ((!config.from_autorun || config.verbose) && config.alloc_console) {
|
||||||
shared::alloc_console();
|
shared::alloc_console();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@ namespace {
|
|||||||
|
|
||||||
shared::ctx.state = config.disable ? shared::State::OFF : shared::State::ON;
|
shared::ctx.state = config.disable ? shared::State::OFF : shared::State::ON;
|
||||||
shared::ctx.verbose = config.verbose;
|
shared::ctx.verbose = config.verbose;
|
||||||
|
shared::ctx.register_firewall = config.register_firewall;
|
||||||
std::ranges::copy(config.name, shared::ctx.name.data());
|
std::ranges::copy(config.name, shared::ctx.name.data());
|
||||||
|
|
||||||
/// No need to overwrite ctx if we are called from autorun
|
/// No need to overwrite ctx if we are called from autorun
|
||||||
@@ -38,12 +39,12 @@ namespace {
|
|||||||
std::println("** loading defendnot");
|
std::println("** loading defendnot");
|
||||||
|
|
||||||
auto dll_path = shared::get_this_module_path().parent_path();
|
auto dll_path = shared::get_this_module_path().parent_path();
|
||||||
dll_path /= names::kDllName;
|
dll_path /= strings::kDllName;
|
||||||
if (!std::filesystem::exists(dll_path)) {
|
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) {
|
void wait_for_finish(shared::InterProcessCommunication& ipc) {
|
||||||
@@ -56,8 +57,8 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void process_autorun(const loader::Config& config) {
|
void process_autorun(const loader::Config& config) {
|
||||||
if (shared::ctx.state == shared::State::ON) {
|
if (shared::ctx.state == shared::State::ON && config.enable_autorun) {
|
||||||
std::println("** added to autorun: {}", loader::add_to_autorun());
|
std::println("** added to autorun: {}", loader::add_to_autorun(config.autorun_type));
|
||||||
} else {
|
} else {
|
||||||
std::println("** removed from autorun: {}", loader::remove_from_autorun());
|
std::println("** removed from autorun: {}", loader::remove_from_autorun());
|
||||||
}
|
}
|
||||||
@@ -65,40 +66,83 @@ namespace {
|
|||||||
|
|
||||||
void banner(const loader::Config& config) {
|
void banner(const loader::Config& config) {
|
||||||
std::println();
|
std::println();
|
||||||
std::println("thanks for using {}", names::kProjectName);
|
std::println("thanks for using {}", strings::kProjectName);
|
||||||
std::println("please don't forget to leave a star at {}", names::kRepoUrl);
|
std::println("please don't forget to leave a star at {}", strings::kRepoUrl);
|
||||||
|
|
||||||
if (!config.from_autorun) {
|
if (!config.from_autorun && config.alloc_console) {
|
||||||
system("pause");
|
system("pause");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char* argv[]) try {
|
int main(int argc, char* argv[]) try {
|
||||||
argparse::ArgumentParser program(std::format("{}-loader", names::kProjectName), "1.0.0");
|
argparse::ArgumentParser program(std::format("{}-loader", 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);
|
const auto fatal_print = [](const std::string_view str) -> void {
|
||||||
program.add_argument("-d", "--disable").help(std::format("disable {}", names::kProjectName)).default_value(false).implicit_value(true);
|
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("-v", "--verbose").help("verbose logging").default_value(false).implicit_value(true);
|
||||||
|
program.add_argument("--firewall").help("also register a fake firewall").default_value(false).implicit_value(true);
|
||||||
|
program.add_argument("--silent").help("do not allocate console").default_value(false).implicit_value(true);
|
||||||
|
program.add_argument("--autorun-as-user").help("create autorun task as currently logged in user").default_value(false).implicit_value(true);
|
||||||
|
program.add_argument("--disable-autorun").help("disable autorun task creation").default_value(false).implicit_value(true);
|
||||||
program.add_argument("--from-autorun").hidden().default_value(false).implicit_value(true);
|
program.add_argument("--from-autorun").hidden().default_value(false).implicit_value(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
program.parse_args(argc, argv);
|
program.parse_args(argc, argv);
|
||||||
} catch (...) {
|
} catch (std::exception& e) {
|
||||||
shared::alloc_console();
|
std::stringstream ss;
|
||||||
std::cerr << program;
|
ss << e.what() << '\n';
|
||||||
system("pause");
|
ss << program.help().str();
|
||||||
|
fatal_print(ss.str());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto config = loader::Config{
|
auto config = loader::Config{
|
||||||
.name = program.get<std::string>("-n"),
|
.name = program.get<std::string>("-n"),
|
||||||
.disable = program.get<bool>("-d"),
|
.disable = program.get<bool>("-d"),
|
||||||
|
.alloc_console = !program.get<bool>("--silent"),
|
||||||
.verbose = program.get<bool>("-v"),
|
.verbose = program.get<bool>("-v"),
|
||||||
|
.register_firewall = program.get<bool>("--firewall"),
|
||||||
.from_autorun = program.get<bool>("--from-autorun"),
|
.from_autorun = program.get<bool>("--from-autorun"),
|
||||||
|
.autorun_type = program.get<bool>("--autorun-as-user") ? /// As system on boot is the default value
|
||||||
|
loader::AutorunType::AS_CURRENT_USER_ON_LOGIN :
|
||||||
|
loader::AutorunType::AS_SYSTEM_ON_BOOT,
|
||||||
|
.enable_autorun = !program.get<bool>("--disable-autorun"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 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 = shared::ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.alloc_console && config.verbose) {
|
||||||
|
fatal_print("--silent flag can not be used in combination with --verbose");
|
||||||
|
}
|
||||||
|
|
||||||
setup_window(config);
|
setup_window(config);
|
||||||
|
loader::ensure_environment();
|
||||||
|
|
||||||
setup_context(config);
|
setup_context(config);
|
||||||
|
|
||||||
/// \todo @es3n1n: move this to a separate function and add move ctor for ipc
|
/// \todo @es3n1n: move this to a separate function and add move ctor for ipc
|
||||||
@@ -112,11 +156,17 @@ int main(int argc, char* argv[]) try {
|
|||||||
};
|
};
|
||||||
|
|
||||||
wait_for_finish(ipc);
|
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);
|
banner(config);
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
} catch (std::exception& err) {
|
} catch (std::exception& err) {
|
||||||
|
shared::alloc_console();
|
||||||
std::println(stderr, "** fatal error: {}", err.what());
|
std::println(stderr, "** fatal error: {}", err.what());
|
||||||
system("pause");
|
system("pause");
|
||||||
return EXIT_FAILURE;
|
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,49 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
namespace defendnot {
|
namespace defendnot {
|
||||||
|
namespace {
|
||||||
|
void activate(const auto& step, IWscASStatus* inst) {
|
||||||
|
step("update", inst->UpdateStatus(WSCSecurityProductState::ON, TRUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
void activate(const auto& step, IWscAVStatus4* inst) {
|
||||||
|
step("update", inst->UpdateStatus(WSCSecurityProductState::ON, TRUE));
|
||||||
|
step("scan_update", inst->UpdateScanSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
step("settings_update", inst->UpdateSettingsSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
step("prot_update", inst->UpdateProtectionUpdateSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
void activate(const auto& step, IWscFWStatus2* inst) {
|
||||||
|
step("update", inst->UpdateStatus(WSCSecurityProductState::ON));
|
||||||
|
step("domain_update", inst->UpdateDomainProfileSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
step("private_update", inst->UpdatePrivateProfileSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
step("public_update", inst->UpdatePublicProfileSubstatus(WSCSecurityProductSubStatus::NO_ACTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <com::ComObject Ty>
|
||||||
|
void apply(const std::string_view log_prefix, const BSTR name, const bool should_register) {
|
||||||
|
/// 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 (!should_register) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register and activate
|
||||||
|
const auto step = [&](const std::string_view what, const HRESULT hr) -> void {
|
||||||
|
logln("{}_{}: {:#x}", log_prefix, what, com::checked(hr));
|
||||||
|
};
|
||||||
|
step("register", inst->Register(name, name, 0, 0));
|
||||||
|
activate(step, inst.get());
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void startup() {
|
void startup() {
|
||||||
/// Setup
|
/// Setup
|
||||||
shared::ctx.deserialize();
|
shared::ctx.deserialize();
|
||||||
logln("init: {:#x}", com_checked(CoInitialize(nullptr)));
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// WSC will reject the register request if name is empty
|
/// WSC will reject the register request if name is empty
|
||||||
auto name_w = std::wstring(shared::ctx.name.begin(), shared::ctx.name.end());
|
auto name_w = std::wstring(shared::ctx.name.begin(), shared::ctx.name.end());
|
||||||
@@ -29,12 +59,14 @@ namespace defendnot {
|
|||||||
|
|
||||||
/// Convert to BSTR
|
/// Convert to BSTR
|
||||||
auto name = SysAllocString(name_w.c_str());
|
auto name = SysAllocString(name_w.c_str());
|
||||||
defer->void {
|
defer {
|
||||||
SysFreeString(name);
|
SysFreeString(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Register and activate our AV
|
/// Register our stuff in the WSC interfaces
|
||||||
logln("register: {:#x}", com_checked(inst->Register(name, name)));
|
const bool enabled = shared::ctx.state != shared::State::OFF;
|
||||||
logln("update: {:#x}", com_checked(inst->UpdateStatus(WSCSecurityProductState::ON, 3)));
|
apply<IWscASStatus>("IWscASStatus", name, enabled);
|
||||||
|
apply<IWscAVStatus4>("IWscAVStatus4", name, enabled);
|
||||||
|
apply<IWscFWStatus2>("IWscFWStatus2", name, enabled && shared::ctx.register_firewall);
|
||||||
}
|
}
|
||||||
} // namespace defendnot
|
} // namespace defendnot
|
||||||
|
|||||||
@@ -5,13 +5,23 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "core/log.hpp"
|
#include "core/log.hpp"
|
||||||
|
#include "shared/com.hpp"
|
||||||
|
#include "shared/strings.hpp"
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
namespace defendnot {
|
namespace defendnot {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
inline GUID RCLSID = {0x0F2102C37, 0x90C3, 0x450C, {0x0B3, 0x0F6, 0x92, 0x0BE, 0x16, 0x93, 0x0BD, 0x0F2}};
|
constexpr GUID CLSID_WscIsv = {0xF2102C37, 0x90C3, 0x450C, {0xB3, 0x0F6, 0x92, 0xBE, 0x16, 0x93, 0xBD, 0xF2}};
|
||||||
inline GUID IID_IWscAVStatus = {0x3901A765, 0x0AB91, 0x4BA9, {0xA5, 0x53, 0x5B, 0x85, 0x38, 0xDE, 0xB8, 0x40}};
|
|
||||||
|
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
|
} // namespace detail
|
||||||
|
|
||||||
enum class WSCSecurityProductState : std::uint32_t {
|
enum class WSCSecurityProductState : std::uint32_t {
|
||||||
@@ -21,53 +31,52 @@ namespace defendnot {
|
|||||||
EXPIRED = 3
|
EXPIRED = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
inline HRESULT com_checked(HRESULT result, const std::source_location loc = std::source_location::current()) {
|
enum class WSCSecurityProductSubStatus : std::uint32_t {
|
||||||
if (result == 0) {
|
NOT_SET = 0,
|
||||||
return result;
|
NO_ACTION = 1,
|
||||||
}
|
ACTION_RECOMMENDED = 2,
|
||||||
|
ACTION_NEEDED = 3,
|
||||||
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;
|
|
||||||
|
|
||||||
|
class IWscAVStatus4 : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscAVStatus4> {
|
||||||
public:
|
public:
|
||||||
virtual HRESULT Register(BSTR path_to_signed_product_exe, BSTR display_name) = 0;
|
virtual HRESULT COM_CALLCONV Register(BSTR path_to_signed_product_exe, BSTR display_name, std::uint32_t, std::uint32_t) = 0;
|
||||||
virtual HRESULT Unregister() = 0;
|
virtual HRESULT COM_CALLCONV Unregister() = 0;
|
||||||
virtual HRESULT UpdateStatus(WSCSecurityProductState state, std::uint32_t idk) = 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() {
|
class IWscASStatus : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscASStatus> {
|
||||||
IWscAVStatus* result = nullptr;
|
public:
|
||||||
com_checked(CoCreateInstance(detail::RCLSID, 0, 1, detail::IID_IWscAVStatus, reinterpret_cast<LPVOID*>(&result)));
|
virtual HRESULT COM_CALLCONV Register(BSTR path_to_signed_product_exe, BSTR display_name, std::uint32_t, std::uint32_t) = 0;
|
||||||
return result;
|
virtual HRESULT COM_CALLCONV Unregister() = 0;
|
||||||
}
|
virtual HRESULT COM_CALLCONV UpdateStatus(WSCSecurityProductState state, BOOL unk) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IWscFWStatus2 : public com::IBaseObject<detail::CLSID_WscIsv, detail::IID_IWscFWStatus2> {
|
||||||
|
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) = 0;
|
||||||
|
virtual HRESULT COM_CALLCONV UpdateDomainProfileSubstatus(WSCSecurityProductSubStatus status) = 0;
|
||||||
|
virtual HRESULT COM_CALLCONV UpdatePrivateProfileSubstatus(WSCSecurityProductSubStatus status) = 0;
|
||||||
|
virtual HRESULT COM_CALLCONV UpdatePublicProfileSubstatus(WSCSecurityProductSubStatus status) = 0;
|
||||||
};
|
};
|
||||||
} // namespace defendnot
|
} // namespace defendnot
|
||||||
|
|||||||
30
extra-strip.bat
Normal file
30
extra-strip.bat
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
:: based on https://github.com/es3n1n/defendnot/issues/49 comments
|
||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo This script needs administrator rights
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableAntiSpyware /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableAntiVirus /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableRoutinelyTakingAction /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v ServiceKeepAlive /t REG_DWORD /d 00000000 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableBehaviorMonitoring /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableIntrusionPreventionSystem /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableIOAVProtection /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableOnAccessProtection /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableRawWriteNotification /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableRealtimeMonitoring /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection" /v DisableScanOnRealtimeEnable /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Signature Updates" /v DisableUpdateOnStartupWithoutEngine /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Signature Updates" /v UpdateOnStartup /t REG_DWORD /d 00000000 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\SpyNet" /v DisableBlockAtFirstSeen /t REG_DWORD /d 00000001 /f
|
||||||
|
reg.exe ADD "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\SpyNet" /v SpynetReporting /t REG_DWORD /d 00000000 /f
|
||||||
|
|
||||||
|
echo All done, please restart your machine to apply these changes
|
||||||
|
pause
|
||||||
|
endlocal
|
||||||
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