From 1dea325bd16f1267d479eff59104adbab6124bac Mon Sep 17 00:00:00 2001 From: Brok Malkotsis Date: Wed, 29 Jul 2026 23:39:52 +0000 Subject: [PATCH] feat: optional SHA-256 integrity verification (#4) Python: hash script vs JAVELIN_EXPECTED_SHA256 and guarded-exit on mismatch. C++: keep CRC32 and add optional build-time SHA-256 of the running executable. Document how to set expected values in INTEGRITY.md. --- AntiCheat.cpp | 119 ++++++++++++++++++++++++++++++++------------------ INTEGRITY.md | 47 ++++++++++++++++++++ README.md | 10 ++++- anticheat.py | 54 +++++++++++++++++++++++ 4 files changed, 186 insertions(+), 44 deletions(-) create mode 100644 INTEGRITY.md create mode 100644 anticheat.py diff --git a/AntiCheat.cpp b/AntiCheat.cpp index 7a76632..26e7030 100644 --- a/AntiCheat.cpp +++ b/AntiCheat.cpp @@ -1,36 +1,33 @@ // AntiCheat.cpp // Javelin Project - Minimal Anti-Cheat guards -// Features: debugger detection, suspicious process scan, basic self-integrity (CRC32) +// Features: debugger detection, suspicious process scan, self-integrity (CRC32 + optional SHA-256) #include #include +#include #include #include #include #include #include +#include +#include + +#pragma comment(lib, "advapi32.lib") static const char* kTag = "[Javelin AntiCheat] "; -// --- Configurable lists --- static std::vector kSuspiciousProcesses = { - "cheatengine.exe", - "ollydbg.exe", - "x64dbg.exe", - "httpdebuggerui.exe", - "ida.exe", - "ida64.exe", - "scylla.exe", - "processhacker.exe" + "cheatengine.exe", "ollydbg.exe", "x64dbg.exe", "httpdebuggerui.exe", + "ida.exe", "ida64.exe", "scylla.exe", "processhacker.exe" }; -// --- Utils --- static std::string toLower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), ::tolower); return s; } -// Simple CRC32 (polynomial 0xEDB88320) +// CRC32 (poly 0xEDB88320) static uint32_t crc32(const std::vector& data) { uint32_t crc = 0xFFFFFFFFu; for (uint8_t b : data) { @@ -51,71 +48,100 @@ static bool readFile(const std::wstring& path, std::vector& out) { if (size <= 0) return false; f.seekg(0, std::ios::beg); out.resize(static_cast(size)); - if (!f.read(reinterpret_cast(out.data()), size)) return false; - return true; + return static_cast(f.read(reinterpret_cast(out.data()), size)); +} + +static bool sha256Hex(const std::vector& data, std::string& outHex) { + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) + return false; + bool ok = false; + if (CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { + if (CryptHashData(hHash, data.data(), static_cast(data.size()), 0)) { + DWORD len = 32; + BYTE hash[32]{}; + if (CryptGetHashParam(hHash, HP_HASHVAL, hash, &len, 0)) { + std::ostringstream oss; + for (DWORD i = 0; i < len; ++i) + oss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + outHex = oss.str(); + ok = true; + } + } + CryptDestroyHash(hHash); + } + CryptReleaseContext(hProv, 0); + return ok; } -// --- Checks --- static bool checkDebugger() { if (IsDebuggerPresent()) return true; - - // Secondary anti-debug: CheckBeingDebugged flag in PEB (best-effort) #ifdef _M_IX86 - // 32-bit: fs:[30h] -> PEB, offset 2 = BeingDebugged (BYTE) __try { BYTE* peb = *(BYTE**)_readfsdword(0x30); if (peb && peb[2]) return true; } __except (EXCEPTION_EXECUTE_HANDLER) {} #elif defined(_M_X64) - // 64-bit: gs:[60h] -> PEB __try { BYTE* peb = *(BYTE**)_readgsqword(0x60); if (peb && peb[2]) return true; } __except (EXCEPTION_EXECUTE_HANDLER) {} #endif - return false; } static bool checkSuspiciousProcesses() { HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snap == INVALID_HANDLE_VALUE) return false; - PROCESSENTRY32 pe{}; pe.dwSize = sizeof(pe); - if (!Process32First(snap, &pe)) { - CloseHandle(snap); - return false; - } - + if (!Process32First(snap, &pe)) { CloseHandle(snap); return false; } do { std::string name = toLower(pe.szExeFile); for (const auto& bad : kSuspiciousProcesses) { - if (name == toLower(bad)) { - CloseHandle(snap); - return true; - } + if (name == toLower(bad)) { CloseHandle(snap); return true; } } } while (Process32Next(snap, &pe)); - CloseHandle(snap); return false; } -static bool checkSelfIntegrity(uint32_t expectedCrc) { +static bool getSelfPath(std::wstring& pathOut) { wchar_t path[MAX_PATH]{}; if (!GetModuleFileNameW(nullptr, path, MAX_PATH)) return false; + pathOut = path; + return true; +} +static bool checkSelfIntegrityCrc(uint32_t expectedCrc) { + std::wstring path; + if (!getSelfPath(path)) return false; std::vector bytes; if (!readFile(path, bytes)) return false; + return crc32(bytes) == expectedCrc; +} - uint32_t current = crc32(bytes); - return current == expectedCrc; +static bool checkSelfIntegritySha256(const std::string& expectedLowerHex) { + if (expectedLowerHex.empty()) return true; + std::wstring path; + if (!getSelfPath(path)) return false; + std::vector bytes; + if (!readFile(path, bytes)) return false; + std::string got; + if (!sha256Hex(bytes, got)) return false; + std::string exp = expectedLowerHex; + for (char& c : exp) c = (char)tolower((unsigned char)c); + return got == exp; } -// --- Entry helper (embed a baseline CRC once you ship a build) --- #ifndef JAVELIN_EXPECTED_CRC32 -#define JAVELIN_EXPECTED_CRC32 0u // Set this at build time (e.g., /DJAVELIN_EXPECTED_CRC32=0x12345678) +#define JAVELIN_EXPECTED_CRC32 0u +#endif + +// Optional: /DJAVELIN_EXPECTED_SHA256=\"abc...\" (64 hex chars). Empty = skip. +#ifndef JAVELIN_EXPECTED_SHA256 +#define JAVELIN_EXPECTED_SHA256 "" #endif int main() { @@ -123,18 +149,25 @@ int main() { if (checkDebugger()) { std::cerr << kTag << "Debugger detected. Exiting.\n"; - return 0xDEB; // code for debugger + return 0xDEB; } - if (checkSuspiciousProcesses()) { std::cerr << kTag << "Suspicious process detected. Exiting.\n"; - return 0xBAD; // code for bad process + return 0xBAD; } - if (JAVELIN_EXPECTED_CRC32 != 0u) { - if (!checkSelfIntegrity(JAVELIN_EXPECTED_CRC32)) { - std::cerr << kTag << "Integrity check failed (CRC mismatch). Exiting.\n"; - return 0xCRC; // custom code (note: non-standard, may be truncated) + if (!checkSelfIntegrityCrc(JAVELIN_EXPECTED_CRC32)) { + std::cerr << kTag << "Integrity check failed (CRC32 mismatch). Exiting.\n"; + return 0xC32; + } + } + { + std::string expectedSha = JAVELIN_EXPECTED_SHA256; + if (!expectedSha.empty()) { + if (!checkSelfIntegritySha256(expectedSha)) { + std::cerr << kTag << "Integrity check failed (SHA-256 mismatch). Exiting.\n"; + return 0xA56; + } } } diff --git a/INTEGRITY.md b/INTEGRITY.md new file mode 100644 index 0000000..9727f63 --- /dev/null +++ b/INTEGRITY.md @@ -0,0 +1,47 @@ +# Integrity verification + +## Python + +1. Compute the hash of `anticheat.py` after you freeze the file: + +```bash +python -c "import hashlib,pathlib; p=pathlib.Path('anticheat.py'); print(hashlib.sha256(p.read_bytes()).hexdigest())" +``` + +2. Set the env var before launch: + +```bash +export JAVELIN_EXPECTED_SHA256=<64-char-hex> +python anticheat.py +``` + +3. On mismatch the process exits with code `0xA56` (2646) and does not continue. + +Unset `JAVELIN_EXPECTED_SHA256` to skip the check (dev mode). + +## C++ (Windows) + +### CRC32 (existing) + +Compile with a build-time CRC of the final `.exe`: + +```text +cl /EHsc /DJAVELIN_EXPECTED_CRC32=0xDEADBEEF AntiCheat.cpp +``` + +Use `0` (default) to skip CRC checks while iterating. + +### SHA-256 (optional) + +```text +cl /EHsc /DJAVELIN_EXPECTED_SHA256=\"<64-hex>\" AntiCheat.cpp +``` + +Compute the SHA-256 of the **built executable** after linking, then recompile once with that constant, or inject via your packer/CI. + +Mismatch exits with code `0xA56`. + +## Notes + +- Hash the **artifact you ship** (script or exe), not intermediate objects. +- Recompute after every intentional release; CI should fail if constants go stale. diff --git a/README.md b/README.md index eb4d6af..3616523 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# py-workedtask \ No newline at end of file +# py-workedtask (Javelin AntiCheat) + +Minimal anti-cheat guards: + +- Debugger detection (Windows C++) +- Suspicious process scan (Windows C++) +- **Self-integrity**: CRC32 / SHA-256 of running executable (C++), SHA-256 of script via `JAVELIN_EXPECTED_SHA256` (Python) + +See [INTEGRITY.md](INTEGRITY.md) for how to set expected hashes. diff --git a/anticheat.py b/anticheat.py new file mode 100644 index 0000000..3ca6170 --- /dev/null +++ b/anticheat.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Javelin minimal anti-cheat helpers (Python). + +Integrity: SHA-256 of this script file vs env JAVELIN_EXPECTED_SHA256. +On mismatch, exits with a guarded non-zero code. +""" +from __future__ import annotations + +import hashlib +import os +import sys +from pathlib import Path + +TAG = "[Javelin AntiCheat] " +EXIT_INTEGRITY = 0xA56 # 2646 + + +def script_path() -> Path: + return Path(__file__).resolve() + + +def sha256_file(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def check_integrity() -> None: + expected = os.environ.get("JAVELIN_EXPECTED_SHA256", "").strip().lower() + if not expected: + print(f"{TAG}integrity skipped (JAVELIN_EXPECTED_SHA256 unset)") + return + got = sha256_file(script_path()) + if got != expected: + print( + f"{TAG}Integrity check failed (SHA-256 mismatch). " + f"expected={expected} got={got}", + file=sys.stderr, + ) + raise SystemExit(EXIT_INTEGRITY) + print(f"{TAG}integrity ok") + + +def main() -> int: + print(f"{TAG}starting checks...") + check_integrity() + print(f"{TAG}All clear. Continue.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())