Today I'm going to share a method to be able to bypass client-sided anti-cheats without editing any memory. It is basically update-friendly and will not be patched by regular server updates. It works by utilizing the fact that the server uses a method called [Only registered and activated users can see links. Click Here To Register...] to call code infinitely that protects their client from cheating / reverse engineering.
Having such critical code in it's own thread is easy to bypass and I'm quite surprised that none has yet figured it out. The point of this thread is not only to show people how to bypass the anti-cheats but also to make the server owners aware about it. I'm going to use a server called Flyff Thai to showcase. But this works on most servers which has client-sided protection. I know, it's sad.
Be aware that it's easier to bypass an anti-cheat with this method while not logged into the game since there's less threads running and you'll easier identify which threads are the ones protecting the client.
The server "Flyff Thai" has two types of security (most servers doesn't). One of them is a dll called "GameGuard.dll" and the other one is internal inside of the "Neuz.exe". By looking at the image below you're able to see which threads are the ones keeping the client safe. I've surrounded them with blue and green rectangles. Btw, the program used is called [Only registered and activated users can see links. Click Here To Register...].
[Only registered and activated users can see links. Click Here To Register...]
[Only registered and activated users can see links. Click Here To Register...]
When you've found them, you can easily just stop their flow by suspending them and they'll stop doing what they're doing.
Boom, you've successfully bypassed a anti-cheat without any coding knowledge at all.
The only annoying thing right now, is that you're manually required to do it everytime.
What if we could make the computer do it for us instead?
Luckily we can! The only thing we need to do is find patterns to be able to identify the anti-cheat threads. The pattern is required to be the same everytime the client is restarted. E.g we would not be able to use the TEB address.
The first pattern we can easily detect is the start address of the gameguard threads. Example: GameGuard.dll!Vermillion_Guard+0x3b70. We can see that the thread's associated module is GameGuard.dll, that's the first and easy part of the anti-cheat bypass.
The only thing left is the internal anti-cheat which is located inside of the "Neuz.exe". Those are the threads marked with a green rectangle in the first image above. Sadly we cannot take the approach as we did with the GameGuard threads because there's three threads with the same associated module and one is the main threads for the game, if we suspend that one. The game will be paused.
So we'll need another pattern. There are two patterns which we're able to use to identify the threads. Their "Status" is set to "DelayExecution" and their Start Address's are both the same but the main thread is not. We can use either one of these patterns. I'm using the "DelayExecution" in the code below.
Now too the fun part, the ugly code. Hopefully I've explained good enough. Btw, now that people know about this, servers might hopefully fix it. Bai!
nt_ddk.h - (Required Header) Download: [Only registered and activated users can see links. Click Here To Register...]
Source.cpp
gWin_ProcessEnumerator.h
gWin_ProcessEnumerator.cpp
Having such critical code in it's own thread is easy to bypass and I'm quite surprised that none has yet figured it out. The point of this thread is not only to show people how to bypass the anti-cheats but also to make the server owners aware about it. I'm going to use a server called Flyff Thai to showcase. But this works on most servers which has client-sided protection. I know, it's sad.
Be aware that it's easier to bypass an anti-cheat with this method while not logged into the game since there's less threads running and you'll easier identify which threads are the ones protecting the client.
The server "Flyff Thai" has two types of security (most servers doesn't). One of them is a dll called "GameGuard.dll" and the other one is internal inside of the "Neuz.exe". By looking at the image below you're able to see which threads are the ones keeping the client safe. I've surrounded them with blue and green rectangles. Btw, the program used is called [Only registered and activated users can see links. Click Here To Register...].
[Only registered and activated users can see links. Click Here To Register...]
[Only registered and activated users can see links. Click Here To Register...]
When you've found them, you can easily just stop their flow by suspending them and they'll stop doing what they're doing.
Boom, you've successfully bypassed a anti-cheat without any coding knowledge at all.
The only annoying thing right now, is that you're manually required to do it everytime.
What if we could make the computer do it for us instead?
Luckily we can! The only thing we need to do is find patterns to be able to identify the anti-cheat threads. The pattern is required to be the same everytime the client is restarted. E.g we would not be able to use the TEB address.
The first pattern we can easily detect is the start address of the gameguard threads. Example: GameGuard.dll!Vermillion_Guard+0x3b70. We can see that the thread's associated module is GameGuard.dll, that's the first and easy part of the anti-cheat bypass.
The only thing left is the internal anti-cheat which is located inside of the "Neuz.exe". Those are the threads marked with a green rectangle in the first image above. Sadly we cannot take the approach as we did with the GameGuard threads because there's three threads with the same associated module and one is the main threads for the game, if we suspend that one. The game will be paused.
So we'll need another pattern. There are two patterns which we're able to use to identify the threads. Their "Status" is set to "DelayExecution" and their Start Address's are both the same but the main thread is not. We can use either one of these patterns. I'm using the "DelayExecution" in the code below.
Now too the fun part, the ugly code. Hopefully I've explained good enough. Btw, now that people know about this, servers might hopefully fix it. Bai!
nt_ddk.h - (Required Header) Download: [Only registered and activated users can see links. Click Here To Register...]
Source.cpp
Code:
#include "gWin_ProcessEnumerator.h"
int main() {
gWin::ProcessEnumerator procEnum;
for (auto &process : procEnum.getProcesses()) {
if (process.getName() == "Neuz.exe") {
for (auto &thread : process.getThreads()) {
auto modInfo = process.getAssociatedModule(&thread);
std::cout << modInfo.name << "+" << std::hex <<
thread.getStartAddress() - modInfo.base << std::endl;
if (modInfo.name.find("GameGuard") != std::string::npos)
SuspendThread(thread.getHandle().getRaw());
if (modInfo.name.find("Neuz") != std::string::npos)
if (thread.getWaitReason() == DelayExecution)
SuspendThread(thread.getHandle().getRaw());
}
}
}
return 0;
}
Code:
#pragma once
/*
NT Internals Process Enumerator
- Created by greyb1t 2016-04-22
I'd appreciate if you left this comment here when copy pasting it somewhere else.
Thank you!
*/
#include <iostream>
#include <Windows.h>
#include <vector>
#include <memory>
#include <string>
#include <TlHelp32.h>
#include "nt_ddk.h"
#define PRINT_ERRORS true
std::string getNtErrorCode(NTSTATUS ntStatus);
void PRINT_ERROR(std::string s, NTSTATUS status);
namespace gWin {
struct ModuleInfo {
DWORD base;
DWORD size;
std::string name;
};
class SafeHandle {
public:
SafeHandle() {}
// Legal Usage (constructor):
// gWin::SafeHandle handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
SafeHandle(HANDLE handle) : m_handle(handle) {}
~SafeHandle() {
CloseHandle(m_handle);
}
SafeHandle(const SafeHandle &source) {
this->operator=(source);
}
// Legal Usage (assignment operator):
// handle1 = handle2;
SafeHandle& operator= (const SafeHandle &hSource) {
// TODO: Check if DuplicateHandle is the function which is needed
m_handle = hSource.m_handle;
return *this;
}
SafeHandle& operator= (const HANDLE hSource) {
// TODO: Check if DuplicateHandle is the function which is needed
m_handle = hSource;
return *this;
}
const HANDLE getRaw() const {
return m_handle;
}
private:
HANDLE m_handle;
};
class Thread {
public:
Thread(SYSTEM_THREADS_INFORMATION *threadInfo) : m_threadInfo(threadInfo) {}
~Thread() {}
SYSTEM_THREADS_INFORMATION *getPtr() { return m_threadInfo; }
// Getters
DWORD getStartAddress() { return (DWORD)m_threadInfo->StartAddress; }
DWORD getProcessId() { return (DWORD)m_threadInfo->ClientId.UniqueProcess; }
DWORD getThreadId() { return (DWORD)m_threadInfo->ClientId.UniqueThread; }
int getPriority() { return m_threadInfo->Priority; }
ULONG getContextSwitchCount() { return m_threadInfo->ContextSwitchCount; }
int getState() { return m_threadInfo->State; }
KWAIT_REASON getWaitReason() { return m_threadInfo->WaitReason; }
gWin::SafeHandle getHandle() { return OpenThread(THREAD_ALL_ACCESS, FALSE, getThreadId()); }
private:
SYSTEM_THREADS_INFORMATION *m_threadInfo;
};
class Process {
public:
Process(SYSTEM_PROCESS_INFORMATION *spi, PVOID buf) : m_spi(spi), m_allBuffer(buf) {}
void deallocWholeBuffer() { delete[] m_allBuffer; }
SYSTEM_PROCESS_INFORMATION *getPtr() { return m_spi; }
std::vector<Thread> &getThreads() {
if (m_threads.size() <= 0)
enumThreads();
return m_threads;
}
std::vector<MODULEENTRY32> &getModules() {
if (m_modules.size() <= 0)
enumModules();
return m_modules;
}
std::string getName() {
if (m_spi->ImageName.Buffer != nullptr) {
std::wstring wProcName = m_spi->ImageName.Buffer;
return std::string(wProcName.begin(), wProcName.end());
}
else {
return "nullptr";
}
}
ModuleInfo getAssociatedModule(Thread *thread);
DWORD getId() { return (DWORD)m_spi->UniqueProcessId; }
private:
void enumModules();
void enumThreads();
DWORD getThreadStartAddress(HANDLE hThread);
private:
std::vector<Thread> m_threads;
std::vector<MODULEENTRY32> m_modules;
SYSTEM_PROCESS_INFORMATION *m_spi;
_SYSTEM_HANDLE_INFORMATION_T<PVOID> *m_shi;
PVOID m_allBuffer;
};
class ProcessEnumerator {
public:
ProcessEnumerator() { iterateProcesses(); }
~ProcessEnumerator() {
if (m_processes.size() >= 0)
m_processes[0].deallocWholeBuffer();
}
std::vector<Process> &getProcesses() {
return m_processes;
}
private:
void iterateProcesses();
private:
std::vector<Process> m_processes;
};
}
Code:
#include "gWin_ProcessEnumerator.h"
std::string getNtErrorCode(NTSTATUS ntStatus) {
switch (ntStatus) {
case STATUS_SUCCESS:
return "NTSuccess";
case STATUS_UNSUCCESSFUL:
return "NTUnsuccessful";
case STATUS_NOT_IMPLEMENTED:
return "Not Implemented";
case STATUS_INFO_LENGTH_MISMATCH:
return "Length Mismatch";
case STATUS_NO_MEMORY:
return "No memory";
case STATUS_ACCESS_DENIED:
return "Access Denied";
case STATUS_BUFFER_TOO_SMALL:
return "Buffer too small";
case STATUS_PROCEDURE_NOT_FOUND:
return "Proceadure not found";
case STATUS_NOT_SUPPORTED:
return "Not supported";
case STATUS_NOT_FOUND:
return "Not found";
case STATUS_PARTIAL_COPY:
return "Partial copy";
default:
return "Unkown error";
}
}
void PRINT_ERROR(std::string s, NTSTATUS status) {
#if PRINT_ERRORS
std::cerr << "GetLastError() - " << GetLastError() << " - " << "NTSTATUS - " << getNtErrorCode(status) << " - " << s << std::endl;
#endif
}
namespace gWin {
void Process::enumModules() {
gWin::SafeHandle hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPALL, (DWORD)m_spi->UniqueProcessId);
MODULEENTRY32 modEntry = { 0 };
modEntry.dwSize = sizeof(MODULEENTRY32);
m_modules.clear();
if (!Module32First(hSnapshot.getRaw(), &modEntry))
PRINT_ERROR("Unable to iterate through modules", 0);
do {
m_modules.push_back(modEntry);
} while (Module32Next(hSnapshot.getRaw(), &modEntry));
}
void Process::enumThreads() {
for (ULONG i = 0; i < m_spi->NumberOfThreads; ++i) {
m_spi->Threads[i].StartAddress = (PVOID)getThreadStartAddress(m_spi->Threads[i].ClientId.UniqueThread);
m_threads.push_back(Thread(&m_spi->Threads[i]));
}
}
DWORD Process::getThreadStartAddress(HANDLE hThread) {
HMODULE ntDll = GetModuleHandle("ntdll.dll");
if (ntDll == NULL) {
PRINT_ERROR("Unable to get module handle from ntdll.dll", 0);
return 0;
}
tNtQueryInformationThread NtQueryInformationThread =
(tNtQueryInformationThread)GetProcAddress(ntDll, ("NtQueryInformationThread"));
if (NtQueryInformationThread == NULL) {
PRINT_ERROR("NtQueryInformationThread was not found", 0);
return 0;
}
gWin::SafeHandle hNewThread = OpenThread(THREAD_QUERY_INFORMATION, FALSE, (DWORD)hThread);
DWORD threadStartAddr = 0;
NTSTATUS retval = NtQueryInformationThread(hNewThread.getRaw(), ThreadQuerySetWin32StartAddress, &threadStartAddr, sizeof(DWORD), NULL);
if (!NT_SUCCESS(retval)) {
PRINT_ERROR("Unable to get start address of thread", retval);
return 0;
}
return threadStartAddr;
}
ModuleInfo Process::getAssociatedModule(Thread *thread) {
if (m_modules.size() <= 0)
enumModules();
for (auto &module : m_modules) {
if (thread->getStartAddress() >= (DWORD)module.modBaseAddr &&
thread->getStartAddress() <= ((DWORD)module.modBaseAddr + module.modBaseSize)) {
return ModuleInfo { (DWORD)module.modBaseAddr, module.modBaseSize, module.szExePath };
}
}
return ModuleInfo { 0 };
}
void ProcessEnumerator::iterateProcesses() {
HMODULE ntDll = GetModuleHandle("ntdll.dll");
if (ntDll == NULL)
PRINT_ERROR("Unable to get module handle from ntdll.dll", 0);
tNtQuerySystemInformation NtQuerySystemInformation =
(tNtQuerySystemInformation)GetProcAddress(ntDll, ("NtQuerySystemInformation"));
if (NtQuerySystemInformation == NULL)
PRINT_ERROR("NtQuerySystemInformation was not found", 0);
void *buffer = new void *[1024 * 1024];
PSYSTEM_PROCESS_INFORMATION pSpi = (PSYSTEM_PROCESS_INFORMATION)buffer;
NTSTATUS ntStatus = NtQuerySystemInformation(SystemProcessInformation, pSpi, 1024 * 1024, NULL);
if (!NT_SUCCESS(ntStatus)) {
PRINT_ERROR("Unable to iterate through process list", ntStatus);
return;
}
m_processes.clear();
while (pSpi->NextEntryOffset) {
m_processes.push_back(Process(pSpi, buffer));
ULONG nextEntry = (ULONG)pSpi + pSpi->NextEntryOffset;
pSpi = (PSYSTEM_PROCESS_INFORMATION)(nextEntry);
}
}
}