Compare commits

...

15 Commits

Author SHA1 Message Date
4d9fcb349e Merge pull request #126 from diamante0018/dependabot/submodules/deps/libtommath-839ae9e
build(deps): bump deps/libtommath from `e823b0c` to `839ae9e`
2025-07-02 15:13:50 +02:00
73f497e0c7 build(deps): bump deps/libtommath from e823b0c to 839ae9e
Bumps [deps/libtommath](https://github.com/libtom/libtommath) from `e823b0c` to `839ae9e`.
- [Release notes](https://github.com/libtom/libtommath/releases)
- [Commits](e823b0c34c...839ae9ea66)

---
updated-dependencies:
- dependency-name: deps/libtommath
  dependency-version: 839ae9ea66718705fba2b5773d1bdfb2b457cea4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 16:56:45 +00:00
d5d580c0c3 chore: update Windows 2025-06-03 10:36:37 +02:00
acf32bb740 Merge pull request #125 from diamante0018/dependabot/submodules/deps/libtomcrypt-d448df1
build(deps): bump deps/libtomcrypt from `3905c28` to `d448df1`
2025-06-03 10:31:20 +02:00
8c60074d8a Merge pull request #124 from diamante0018/dependabot/submodules/deps/GSL-7e0943d
build(deps): bump deps/GSL from `3325bbd` to `7e0943d`
2025-06-03 10:25:55 +02:00
7e5d8b210c build(deps): bump deps/libtomcrypt from 3905c28 to d448df1
Bumps [deps/libtomcrypt](https://github.com/libtom/libtomcrypt) from `3905c28` to `d448df1`.
- [Release notes](https://github.com/libtom/libtomcrypt/releases)
- [Commits](3905c28913...d448df1938)

---
updated-dependencies:
- dependency-name: deps/libtomcrypt
  dependency-version: d448df1938e8988bcdb0eed6591387e82b26874b
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-01 16:50:32 +00:00
3ff1a8a957 build(deps): bump deps/GSL from 3325bbd to 7e0943d
Bumps [deps/GSL](https://github.com/microsoft/GSL) from `3325bbd` to `7e0943d`.
- [Release notes](https://github.com/microsoft/GSL/releases)
- [Commits](3325bbd33d...7e0943d20d)

---
updated-dependencies:
- dependency-name: deps/GSL
  dependency-version: 7e0943d20d3082b4f350a7e0c3088d2388e934de
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-01 16:50:30 +00:00
2de524fa76 Merge pull request #123 from diamante0018/dependabot/submodules/deps/libtomcrypt-3905c28
build(deps): bump deps/libtomcrypt from `a6b9aff` to `3905c28`
2025-05-01 18:36:59 +02:00
7fe1a30fbe build(deps): bump deps/libtomcrypt from a6b9aff to 3905c28
Bumps [deps/libtomcrypt](https://github.com/libtom/libtomcrypt) from `a6b9aff` to `3905c28`.
- [Release notes](https://github.com/libtom/libtomcrypt/releases)
- [Commits](a6b9aff7aa...3905c28913)

---
updated-dependencies:
- dependency-name: deps/libtomcrypt
  dependency-version: 3905c28913232d6e82a2a381cc2b71b0af7aa791
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 16:31:23 +00:00
572b71da00 build: add back clang 2025-04-01 19:03:59 +02:00
0541f9d095 chore: update some other stuff 2025-04-01 18:48:40 +02:00
e5b8b29a01 build: update Premake5 2025-04-01 18:36:58 +02:00
86a5be8bb2 Merge pull request #122 from diamante0018/dependabot/submodules/deps/GSL-3325bbd
build(deps): bump deps/GSL from `2828399` to `3325bbd`
2025-04-01 18:28:59 +02:00
c9eb0efbeb build(deps): bump deps/GSL from 2828399 to 3325bbd
Bumps [deps/GSL](https://github.com/microsoft/GSL) from `2828399` to `3325bbd`.
- [Release notes](https://github.com/microsoft/GSL/releases)
- [Commits](2828399820...3325bbd33d)

---
updated-dependencies:
- dependency-name: deps/GSL
  dependency-version: 3325bbd33d24d1f8f5a0f69e782c92ad5a39a68e
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-01 16:23:04 +00:00
b1e468ba4f chore: update README 2025-03-24 08:36:49 +01:00
21 changed files with 162 additions and 257 deletions

View File

@ -14,12 +14,12 @@ concurrency:
cancel-in-progress: true
env:
PREMAKE_VERSION: "5.0.0-beta5"
PREMAKE_VERSION: "5.0.0-beta6"
jobs:
build:
name: Build binaries
runs-on: windows-2022
runs-on: windows-2025
strategy:
matrix:
configuration:
@ -27,6 +27,7 @@ jobs:
- release
compiler:
- msvc
- clang
steps:
- name: Check out files
uses: actions/checkout@main

View File

@ -12,7 +12,7 @@ This software has been created purely for the purposes of academic research. It
This software is a proof of concept for a vulnerability that is patched. You can't harm anyone with it if you use it on Pluto (If you were to update the addresses for the 1.9 patch of the game).
You also can't use this vulnerability on Tekno as it was fixed in 2021 (2.0.6 version of their client).
If you think your server is vulnerable you should seek help in the appropriate discord server or forum of the client you use.
If you think your server is vulnerable, you should seek help from the appropriate Discord server or forum for your client.
The exploit is documented in [exploit.cpp](src/client/component/exploit.cpp)
## Update

2
deps/GSL vendored

Submodule deps/GSL updated: 2828399820...7e0943d20d

View File

@ -60,7 +60,7 @@ filter {}
filter "configurations:release"
optimize "Size"
defines {"NDEBUG"}
flags {"FatalCompileWarnings"}
fatalwarnings {"All"}
filter "toolset:msc*"
buildoptions "/GL"

14
scripts/format.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
# Navigate to the repository root
cd "$(dirname "$0")/.." || exit 2
# Set the clang-format binary (defaults to 'clang-format')
CLANG_FORMAT_BIN="${CLANG_FORMAT_BIN:-clang-format}"
# Find and format all .hpp and .cpp files in the src directory
find ./src \( -iname '*.hpp' -o -iname '*.cpp' \) -print0 |
xargs -0 "$CLANG_FORMAT_BIN" -i || {
echo "Error: clang-format failed." >&2
exit 1
}

View File

@ -2,7 +2,6 @@
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/thread.hpp>
#include <utils/flags.hpp>
#include <utils/concurrency.hpp>
@ -37,8 +36,7 @@ public:
void post_start() override {
this->terminate_runner_ = false;
this->console_runner_ = utils::thread::create_named_thread(
"Console IO", [this] { this->runner(); });
this->console_runner_ = std::thread([this] { this->runner(); });
}
void post_unpack() override {
@ -77,8 +75,7 @@ private:
int handles_[2]{};
void initialize() {
this->console_thread_ =
utils::thread::create_named_thread("Console", [this] {
this->console_thread_ = std::thread([this] {
if (!utils::flags::has_flag("noconsole")) {
game::Sys_ShowConsole();
}

View File

@ -3,7 +3,6 @@
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include <utils/thread.hpp>
#include "scheduler.hpp"
@ -135,7 +134,7 @@ unsigned int thread_id;
class component final : public component_interface {
public:
void post_unpack() override {
thread = utils::thread::create_named_thread("Async Scheduler", [] {
thread = std::thread([] {
while (!kill) {
execute(pipeline::async);
std::this_thread::sleep_for(10ms);

View File

@ -2,6 +2,19 @@
#include "loader/component_loader.hpp"
namespace {
std::string get_current_date() {
auto now = std::chrono::system_clock::now();
auto current_time = std::chrono::system_clock::to_time_t(now);
std::tm local_time{};
(void)localtime_s(&local_time, &current_time);
std::stringstream ss;
ss << std::put_time(&local_time, "%Y%m%d_%H%M%S");
return ss.str();
}
LONG WINAPI exception_handler(PEXCEPTION_POINTERS exception_info) {
if (exception_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_EXECUTION;
@ -21,8 +34,8 @@ LONG WINAPI exception_handler(PEXCEPTION_POINTERS exception_info) {
| MiniDumpWithFullMemoryInfo //
| MiniDumpWithThreadInfo;
const auto file_name = std::format("minidumps\\mw3-server-freezer_{}.dmp",
game::Sys_Milliseconds());
const auto file_name =
std::format("minidumps\\mw3-server-freezer_{}.dmp", get_current_date());
const auto file_handle = CreateFileA(
file_name.data(), GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS,

View File

@ -24,4 +24,25 @@ void __declspec(naked) Dvar_SetVariant(dvar_t* /*dvar*/, DvarValue /*value*/,
ret
}
}
XAssetEntry* db_find_x_asset_entry(int type_, const char* name) {
static DWORD DB_FindXAssetEntry_t = 0x5c88a0;
XAssetEntry* result{};
__asm {
pushad
push name
mov edi, type_
call DB_FindXAssetEntry_t
add esp, 0x4
mov result, eax
popad
}
return result;
}
XAssetEntry* DB_FindXAssetEntry(int type, const char* name) {
return db_find_x_asset_entry(type, name);
}
} // namespace game

View File

@ -18,6 +18,8 @@ private:
// clang-format off
extern ScreenPlacement* ScrPlace_GetUnsafeFullPlacement();
extern void Dvar_SetVariant(dvar_t* dvar, DvarValue value, DvarSetSource source);
extern XAssetEntry* DB_FindXAssetEntry(int type, const char* name);
// clang-format on
} // namespace game

View File

@ -9,6 +9,59 @@ typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
struct MaterialTechniqueSet {
const char* name;
unsigned char worldVertFormat;
unsigned char unused[2];
MaterialTechniqueSet* remappedTechniqueSet;
void* techniques[54];
};
struct __declspec(align(8)) GfxDrawSurfFields {
uint64_t unused : 1;
uint64_t primarySortKey : 6;
uint64_t surfType : 4;
uint64_t viewModelRender : 1;
uint64_t sceneLightIndex : 8;
uint64_t useHeroLighting : 1;
uint64_t prepass : 2;
uint64_t materialSortedIndex : 12;
uint64_t customIndex : 5;
uint64_t hasGfxEntIndex : 1;
uint64_t reflectionProbeIndex : 8;
uint64_t objectId : 15;
};
union GfxDrawSurf {
__declspec(align(8)) GfxDrawSurfFields fields;
__declspec(align(8)) uint64_t packed;
};
struct MaterialInfo {
const char* name;
unsigned char gameFlags;
unsigned char sortKey;
unsigned char textureAtlasRowCount;
unsigned char textureAtlasColumnCount;
GfxDrawSurf drawSurf;
unsigned int surfaceTypeBits;
};
struct Material {
MaterialInfo info;
char stateBitsEntry[54];
unsigned char textureCount;
unsigned char constantCount;
unsigned char stateBitsCount;
unsigned char stateFlags;
unsigned char cameraRegion;
MaterialTechniqueSet* techniqueSet;
void* textureTable;
void* constantTable;
void* stateBitsTable;
const char** subMaterials;
};
enum bdLogMessageType {
BD_LOG_INFO,
BD_LOG_WARNING,
@ -261,6 +314,8 @@ struct usercmd_s {
int remoteControlMove;
};
static_assert(offsetof(usercmd_s, buttons) == 0x4);
enum LocSelInputState {
LOC_SEL_INPUT_NONE = 0,
LOC_SEL_INPUT_CONFIRM = 1,
@ -367,6 +422,21 @@ struct Font_s {
union XAssetHeader {
Font_s* font;
Material* material;
};
struct XAsset {
int type;
XAssetHeader header;
};
struct XAssetEntry {
XAsset asset;
char zoneIndex;
volatile char inuseMask;
bool printedMissingAsset;
unsigned __int16 nextHash;
unsigned __int16 nextOverride;
};
} // namespace game

View File

@ -80,6 +80,9 @@ WEAK symbol<int(unsigned __int64, const void*, unsigned int)>
WEAK symbol<XAssetHeader(int type, const char* name, int allowCreateDefault)>
DB_FindXAssetHeader{0x4B25C0};
WEAK symbol<int(char* dest, int size, const char* fmt, ...)> Com_sprintf{
0x450CF0};
// Variables
WEAK symbol<CmdArgs> cmd_args{0x1C96850};
WEAK symbol<PlayerKeyState> playerKeys{0xB3A38C};

View File

@ -10,13 +10,16 @@
#include <DbgHelp.h>
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <format>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <source_location>

View File

@ -103,24 +103,6 @@ void hook::quick() {
}
}
bool hook::iat(const nt::library module, const std::string& target_module,
const std::string& process, void* stub) {
if (!module.is_valid())
return false;
auto ptr = module.get_iat_entry(target_module, process);
if (!ptr)
return false;
DWORD protect;
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
*ptr = stub;
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
return true;
}
hook* hook::uninstall(const bool unprotect) {
std::lock_guard _(this->state_mutex_);

View File

@ -75,9 +75,6 @@ public:
void* get_original() const;
void quick();
static bool iat(nt::library module, const std::string& target_module,
const std::string& process, void* stub);
static void nop(void* place, size_t length);
static void nop(DWORD place, size_t length);

View File

@ -135,62 +135,6 @@ void library::free() {
HMODULE library::get_handle() const { return this->module_; }
void** library::get_iat_entry(const std::string& module_name,
const std::string& proc_name) const {
if (!this->is_valid())
return nullptr;
const library other_module(module_name);
if (!other_module.is_valid())
return nullptr;
auto* const target_function = other_module.get_proc<void*>(proc_name);
if (!target_function)
return nullptr;
auto* header = this->get_optional_header();
if (!header)
return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(
this->get_ptr() +
header->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (import_descriptor->Name) {
if (!_stricmp(
reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name),
module_name.data())) {
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(
import_descriptor->OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(
import_descriptor->FirstThunk + this->get_ptr());
while (original_thunk_data->u1.AddressOfData) {
const size_t ordinal_number =
original_thunk_data->u1.AddressOfData & 0xFFFFFFF;
if (ordinal_number > 0xFFFF)
continue;
if (GetProcAddress(other_module.module_,
reinterpret_cast<char*>(ordinal_number)) ==
target_function) {
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
++original_thunk_data;
++thunk_data;
}
// break;
}
++import_descriptor;
}
return nullptr;
}
std::string load_resource(const int id) {
auto* const res = FindResource(library(), MAKEINTRESOURCE(id), RT_RCDATA);
if (!res)

View File

@ -48,27 +48,12 @@ public:
HMODULE get_handle() const;
template <typename T> T get_proc(const std::string& process) const {
if (!this->is_valid())
return T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
}
template <typename T> std::function<T> get(const std::string& process) const {
if (!this->is_valid())
return std::function<T>();
return reinterpret_cast<T*>(this->get_proc<void*>(process));
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
PIMAGE_NT_HEADERS get_nt_headers() const;
PIMAGE_DOS_HEADER get_dos_header() const;
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name,
const std::string& proc_name) const;
private:
HMODULE module_;
};

View File

@ -1,105 +0,0 @@
#include <thread>
#include "nt.hpp"
#include "string.hpp"
#include "thread.hpp"
#include <TlHelp32.h>
#include <gsl/gsl>
namespace utils::thread {
bool set_name(const HANDLE t, const std::string& name) {
const nt::library kernel32("kernel32.dll");
if (!kernel32) {
return false;
}
const auto set_description =
kernel32.get_proc<HRESULT(WINAPI*)(HANDLE, PCWSTR)>(
"SetThreadDescription");
if (!set_description) {
return false;
}
return SUCCEEDED(set_description(t, string::convert(name).data()));
}
bool set_name(const DWORD id, const std::string& name) {
auto* const t = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, id);
if (!t)
return false;
const auto _ = gsl::finally([t]() { CloseHandle(t); });
return set_name(t, name);
}
bool set_name(std::thread& t, const std::string& name) {
return set_name(t.native_handle(), name);
}
bool set_name(const std::string& name) {
return set_name(GetCurrentThread(), name);
}
std::vector<DWORD> get_thread_ids() {
auto* const h =
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
if (h == INVALID_HANDLE_VALUE) {
return {};
}
const auto _ = gsl::finally([h]() { CloseHandle(h); });
THREADENTRY32 entry{};
entry.dwSize = sizeof(entry);
if (!Thread32First(h, &entry)) {
return {};
}
std::vector<DWORD> ids;
do {
const auto check_size =
entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
sizeof(entry.th32OwnerProcessID);
entry.dwSize = sizeof(entry);
if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId()) {
ids.emplace_back(entry.th32ThreadID);
}
} while (Thread32Next(h, &entry));
return ids;
}
void for_each_thread(const std::function<void(HANDLE)>& callback) {
const auto ids = get_thread_ids();
for (const auto& id : ids) {
auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id);
if (thread != nullptr) {
const auto _ = gsl::finally([thread]() { CloseHandle(thread); });
callback(thread);
}
}
}
void suspend_other_threads() {
for_each_thread([](const HANDLE thread) {
if (GetThreadId(thread) != GetCurrentThreadId()) {
SuspendThread(thread);
}
});
}
void resume_other_threads() {
for_each_thread([](const HANDLE thread) {
if (GetThreadId(thread) != GetCurrentThreadId()) {
ResumeThread(thread);
}
});
}
} // namespace utils::thread

View File

@ -1,21 +0,0 @@
#pragma once
namespace utils::thread {
bool set_name(HANDLE t, const std::string& name);
bool set_name(DWORD id, const std::string& name);
bool set_name(std::thread& t, const std::string& name);
bool set_name(const std::string& name);
template <typename... Args>
std::thread create_named_thread(const std::string& name, Args&&... args) {
auto t = std::thread(std::forward<Args>(args)...);
set_name(t, name);
return t;
}
std::vector<DWORD> get_thread_ids();
void for_each_thread(const std::function<void(HANDLE)>& callback);
void suspend_other_threads();
void resume_other_threads();
} // namespace utils::thread