- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 546
- Reaction score
- 7
FiveM Adhesive Blocker — Thread Suspension Method
Ever dealt with the aggressive scanning from FiveM's adhesive module? Most people try to patch bytes or hook functions, but that usually results in an immediate integrity check failure. This approach is much cleaner — a "No-Patch" technique that avoids triggering the AC's self-defense by simply putting the scanner to sleep.
The logic is straightforward: we identify the high-CPU thread within the adhesive.dll module responsible for the scanning loops and freeze it using SuspendThread. Since we aren't modifying the actual code of the module, the integrity checks are bypassed silently.
Technical Breakdown
Risks & Implementation
While this works for now, be aware that future Adhesive revisions can easily implement a watchdog thread on a separate timer to check if the main scanner threads are still running (heartbeat check). If you use this, ensure you run your loader with admin rights to access ntdll.dll functions properly.
Anyone managed to find the specific offset for the heartbeat thread in the latest FiveM build? Have fun reversing.
Ever dealt with the aggressive scanning from FiveM's adhesive module? Most people try to patch bytes or hook functions, but that usually results in an immediate integrity check failure. This approach is much cleaner — a "No-Patch" technique that avoids triggering the AC's self-defense by simply putting the scanner to sleep.
The logic is straightforward: we identify the high-CPU thread within the adhesive.dll module responsible for the scanning loops and freeze it using SuspendThread. Since we aren't modifying the actual code of the module, the integrity checks are bypassed silently.
Technical Breakdown
- Locate GTAProcess.exe and find the adhesive.dll base address and image size.
- Enumerate all active threads in the target process.
- Use NtQueryInformationThread with
to see if the thread belongs to the adhesive module.Code:
ThreadQuerySetWin32StartAddress - Filter for the most active threads using GetThreadTimes — the scanner is almost always the thread consuming the most cycles.
- Nuke the thread via SuspendThread once the user time threshold is met.
Code:
#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;
}
Risks & Implementation
While this works for now, be aware that future Adhesive revisions can easily implement a watchdog thread on a separate timer to check if the main scanner threads are still running (heartbeat check). If you use this, ensure you run your loader with admin rights to access ntdll.dll functions properly.
This technique is primarily for research. If you plan on using this for long sessions, you might want to randomly resume and re-suspend the threads to simulate regular activity if they ever implement active execution checks.
Anyone managed to find the specific offset for the heartbeat thread in the latest FiveM build? Have fun reversing.