Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд [Сурс] FiveM Adhesive Blocker — Фриз потоков античита (No-Patch)

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
559
Реакции
14
Забирайте в коллекцию годный метод для борьбы с Adhesive в FiveM. Пока одни пытаются патчить байты и отлетают по проверке целостности, умные люди используют No-Patch технику.

Суть метода:
Вместо того чтобы лезть в исполняемый код и триггерить Integrity Checks, софт ищет легитимный модуль adhesive.dll и вычисляет среди его потоков те, что жрут больше всего ресурсов процессора. Как правило, это и есть основные сканирующие потоки античита. Мы их просто замораживаем через SuspendThread.

Что внутри:
  1. Использование NtQueryInformationThread для получения ThreadStartAddress.
  2. Анализ нагрузки через GetThreadTimes — софт не трогает «тихие» потоки, чтобы не уронить процесс.
  3. Автофильтрация под FiveM и GTAProcess.exe.
  4. Логика обхода проверок целостности, так как память модуля остается девственно чистой.

Исходный код на C++:
Код:
Expand Collapse Copy
#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
#include <vector>
#include <string>
#include <psapi.h>
#include <algorithm>
 
#pragma comment(lib, "psapi.lib")
 
typedef NTSTATUS(NTAPI* pNtQueryInformationThread)(
    HANDLE ThreadHandle,
    ULONG ThreadInformationClass,
    PVOID ThreadInformation,
    ULONG ThreadInformationLength,
    PULONG ReturnLength
);
 
uintptr_t GetAdhesiveBase(HANDLE hProcess, size_t& modSize) {
    HMODULE hMods[1024]; DWORD cbNeeded;
    if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) {
        for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
            char szModName[MAX_PATH];
            if (GetModuleBaseNameA(hProcess, hMods[i], szModName, sizeof(szModName))) {
                std::string name = szModName;
                for (auto& c : name) c = tolower(c);
                if (name.find("adhesive") != std::string::npos) {
                    MODULEINFO modInfo; GetModuleInformation(hProcess, hMods[i], &modInfo, sizeof(modInfo));
                    modSize = modInfo.SizeOfImage; return (uintptr_t)modInfo.lpBaseOfDll;
                }
            }
        }
    }
    return 0;
}
 
struct ThreadProfile {
    DWORD id;
    uintptr_t start;
    unsigned __int64 userTime;
};
 
bool ProcessTarget(DWORD pid) {
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
    if (!hProcess) return false;
 
    size_t adhesiveSize = 0;
    uintptr_t adhesiveBase = GetAdhesiveBase(hProcess, adhesiveSize);
    if (!adhesiveBase) {
        CloseHandle(hProcess);
        return false;
    }
 
    std::cout << "[+] Found adhesive.dll at 0x" << std::hex << adhesiveBase << std::endl;
 
    pNtQueryInformationThread NtQueryInfoThread = (pNtQueryInformationThread)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");
    std::vector<ThreadProfile> candidates;
 
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te; te.dwSize = sizeof(te);
    if (Thread32First(hSnap, &te)) {
        do {
            if (te.th32OwnerProcessID == pid) {
                HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, te.th32ThreadID);
                if (hThread) {
                    uintptr_t startAddr = 0;
                    if (NtQueryInfoThread(hThread, 9, &startAddr, sizeof(startAddr), NULL) == 0) {
                        if (startAddr >= adhesiveBase && startAddr < adhesiveBase + adhesiveSize) {
                            FILETIME c, e, k, u;
                            if (GetThreadTimes(hThread, &c, &e, &k, &u)) {
                                ULARGE_INTEGER uli; uli.LowPart = u.dwLowDateTime; uli.HighPart = u.dwHighDateTime;
                                candidates.push_back({ te.th32ThreadID, startAddr, uli.QuadPart });
                            }
                        }
                    }
                    CloseHandle(hThread);
                }
            }
        } while (Thread32Next(hSnap, &te));
    }
    CloseHandle(hSnap);
 
    if (candidates.empty()) {
        CloseHandle(hProcess);
        return false;
    }
 
    std::sort(candidates.begin(), candidates.end(), [](const ThreadProfile& a, const ThreadProfile& b) {
        return a.userTime > b.userTime;
    });
 
    int suspended = 0;
    for (size_t i = 0; i < candidates.size() && i < 3; i++) {
        // Only suspend if UserTime > 500,000 (meaning it actually did something substantial)
        if (candidates[i].userTime > 500000) {
            HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, candidates[i].id);
            if (hThread) {
                std::cout << "[+] [removed]ing Security Thread [" << candidates[i].id << "] CPU: " << std::dec << candidates[i].userTime << " (Offset: +0x" << std::hex << (candidates[i].start - adhesiveBase) << ")" << std::endl;
                SuspendThread(hThread);
                suspended++;
                CloseHandle(hThread);
            }
        }
    }
 
    CloseHandle(hProcess);
    return suspended > 0;
}
 
int main() {
    std::cout << "--- FiveM Adhesive Blocker  ---" << std::endl;
    std::cout << "[*] Hunting for scanner threads..." << std::endl;
    
    while (true) {
        HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        PROCESSENTRY32 pe; pe.dwSize = sizeof(pe);
        if (Process32First(hSnap, &pe)) {
            do {
                std::string n = pe.szExeFile;
                if (n.find("FiveM") != std::string::npos && n.find("GTAProcess.exe") != std::string::npos) {
                    if (ProcessTarget(pe.th32ProcessID)) {
                        std::cout << "\n[SUCCESS] Hitted. Scanner got [removed]ed" << std::endl;
                        CloseHandle(hSnap);
                        system("pause");
                        return 0;
                    }
                }
            } while (Process32Next(hSnap, &pe));
        }
        CloseHandle(hSnap);
        Sleep(1000);
    }
    return 0;
}

Техника старая как мир, но в умелых руках работает безотказно. Главное — правильно выставить фильтр по userTime, чтобы не зафризить основной поток игры и не словить логический дедлок.

Нюансы и риски:
Это паблик-решение, так что для серьезных проектов советую обернуть это в нормальный драйвер или хотя бы добавить обфускацию импортов. Если античит начнет чекать статус своих потоков (Thread Suspended), прилетит мануалбан или мгновенный кик.

Интересно, насколько долго еще будет жить этот вектор, учитывая, что FiveM постепенно закручивает гайки в сторону более глубокого мониторинга состояния потоков.
 
Назад
Сверху Снизу