This is cheating! Learn to hack games and write cheats using a simple example.

Brother

Professional
Messages
2,566
Reputation
3
Reaction score
347
Points
83
50bf7e456d2389e74a2f0.png


Computer games open up new worlds for us. And the world of cheats is one of them. Today we will go from theory to practice together and write our own cheat. If you want to learn how to hack executable files, then this can be a good exercise.

Types of cheats and tactics used​

There are different types of cheats. You can divide them into several groups.
  • External - external cheats that work in a separate process. If we hide our external cheat by loading it into the memory of another process, it will turn into a hidden external.
  • Internal - internal cheats that are built into the process of the game itself using an injector. After loading the game into memory, the cheat entry point is called in a separate thread.
  • Pixelscan is a type of cheat that uses the screen image and pixel patterns to get the information you need from the game.
  • Network proxy - cheats that use network proxies, which, in turn, intercept client and server traffic, receiving or changing the necessary information.
There are three main tactics for modifying game behavior.
  1. Changing the memory of the game. The operating system API is used to find and change memory locations that contain the information we need (for example, lives, cartridges).
  2. Simulation of the player's actions: the application repeats the player's actions by clicking with the mouse in predetermined places.
  3. Interception of game traffic. There is a cheat between the game and the server. It intercepts data by collecting or modifying information in order to trick the client or server.
Most modern games are written for Windows, so we will do examples for it.

Writing a game in C​

It is best to tell about cheats in practice. We will write our own little game on which we can practice. I will be writing a game in C #, but I will try to make the data structure as close as possible to a game in C ++. In my experience, cheating in C # games is very easy.

The principle of the game is simple: you press Enter and you lose. Not very fair rules, right? Let's try to change them.

Let's start reverse engineering​

game_executable.png

Executable file of the game

We have a game file. But instead of the source code, we will study the memory and behavior of the application.

game_behaviour.png

Let's start with the behavior of the game

Each time you press Enter, the player's lives are reduced by 15. The initial number of lives is 100.

We will study memory using the Cheat Engine. This is an application for finding variables inside the application memory, and also a good debugger. Restart the game and connect the Cheat Engine to it.

ce_connected.png

Connecting CE to the game

First, we get a list of all the values 85 in memory.

ce_found.png

All values that CE found

Press Enter, and the life indicator will be equal 70. We will filter out all the values.

ce_final_value_found_1.png

Value found

That's the value you want! Let's change it and press Enter to check the result.

ce_value_changed.png


Value changed

game_value_changed_1.png

Screen of the game after we pressed Enter

The problem is that after restarting the game, the value will already be at a different address. There is no point in sifting it out every time. You must resort to an AOB (Array Of Bytes) scan.

Each time the application is opened again, due to address space randomization (ASLR), the structure describing the player will be in a new place. To find it, you must first discover the signature. A signature is a set of bytes that do not change in structure, by which you can search in application memory.

After several presses on Enter, the number of lives changed to 55. Find the desired value in memory again and open the region in which it is located.

ce_memory_1.png

Region of memory

The allocated byte is the beginning of our int32number. 37 00 00 00 - number 55 in decimal form.

I will copy a small region of memory and paste it into notepad for further study. Now let's restart the application and find the value in memory again. Copy the same memory region again and paste it into notepad. Let's start the comparison. The goal is to find bytes near this signature that will not change.

byte_comparsion.png

We start comparing bytes

Let's check the bytes before the structure.

byte_comparsion_found.png

Bingo!

As you can see, the allocated bytes have not changed, so you can try using them as a signature. The smaller the signature, the faster the scan will take. The signature 01 00 00 00 will obviously be too common in memory. Better to take 03 00 00 01 00 00 00. First, let's find it in memory.

ce_signature_not_unique.png

Signature is not unique

The signature was found, but it is repeated. A more unique sequence is needed. Let's try ED 03 00 00 01 00 00 00.

To confirm the uniqueness, we get the following result:

ce_signature_unique.png

The signature is unique

We need to find the indentation from the signature to get its starting address, not the address of lives. For now, let's save the found signature and postpone it for a while. Don't worry, we'll come back to it later.

Life cycle external​

Using the function OpenProcess, external cheats get a handle for the desired process and make the necessary changes to the code (patching) or read and change variables inside the game's memory. The functions ReadProcessMemory and are used to modify the memory WriteProcessMemory.

Since the dynamic allocation of data in memory makes it difficult to write the necessary addresses and constantly access them, you can use the AOB search technique. The life cycle of an external cheat looks like this:
  1. Find the process ID.
  2. Get a handle to this process with the required rights.
  3. Find addresses in memory.
  4. Patch something if needed.
  5. Render the GUI, if available.
  6. Read or modify memory as needed.

Writing an external cheat for your game​

P / Invoke technology is used to call WinAPI functions from C #. To start working with these functions, you need to declare them in the code. I will take ready-made declarations from pinvoke.net. The first function would be OpenProcess.

Code:
[Flags]
public enum ProcessAccessFlags : uint
{
    All = 0x001F0FFF,
    Terminate = 0x00000001,
    CreateThread = 0x00000002,
    VirtualMemoryOperation = 0x00000008,
    VirtualMemoryRead = 0x00000010,
    VirtualMemoryWrite = 0x00000020,
    DuplicateHandle = 0x00000040,
    CreateProcess = 0x000000080,
    SetQuota = 0x00000100,
    SetInformation = 0x00000200,
    QueryInformation = 0x00000400,
    QueryLimitedInformation = 0x00001000,
    Synchronize = 0x00100000
}

[DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenProcess(
    ProcessAccessFlags processAccess,
    bool bInheritHandle,
    int processId);

The next function is ReadProcessMemory.

Code:
[DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool ReadProcessMemory(
    IntPtr hProcess,
    IntPtr lpBaseAddress,
    [Out] byte[] lpBuffer,
    int dwSize,
    out IntPtr lpNumberOfBytesRead);

Now the function for reading memory WriteProcessMemory.

Code:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
    IntPtr hProcess,
    IntPtr lpBaseAddress,
    byte[] lpBuffer,
    int nSize,
    out IntPtr lpNumberOfBytesWritten);

We are faced with a problem: to search for a pattern, it is necessary to collect all the memory regions of the process. To do this, we need a function and a structure. Function VirtualQueryEx:

Code:
[DllImport("kernel32.dll")]
static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);

Structure MEMORY_BASIC_INFORMATION:

Code:
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
    public IntPtr BaseAddress;
    public IntPtr AllocationBase;
    public uint AllocationProtect;
    public IntPtr RegionSize;
    public uint State;
    public uint Protect;
    public uint Type;
}

Now you can start writing the code for the cheat itself. The first step is to find a game.

Code:
private static int WaitForGame()
{
    while (true)
    {
        var prcs = Process.GetProcessesByName("SimpleConsoleGame");

        if (prcs.Length != 0)
        {
            return prcs.First().Id;
        }

        Thread.Sleep(150);
    }
}

Then let's open the handle to our game.

Code:
private static IntPtr GetGameHandle(int id)
{
    return WinAPI.OpenProcess(WinAPI.ProcessAccessFlags.All, false, id);
}

Let's put it all together in the starting code.

Code:
Console.Title = "External Cheat Example";
Console.ForegroundColor = ConsoleColor.White;

Console.WriteLine("Waiting for game process..");

var processId = WaitForGame();

Console.WriteLine($"Game process found. ID: {processId}");

var handle = GetGameHandle(processId);

if (handle == IntPtr.Zero)
{
    CriticalError("Error. Process handle acquirement failed.\n" +
                  "Insufficient rights?");
}

Console.WriteLine($"Handle was acquired: 0x{handle.ToInt32():X}");
Console.ReadKey(true);

We will find the process ID, then get its handle and, if anything, print an error message. The implementation is CriticalError(string) not that important.

After that, we can already go to the search for a pattern in memory. Let's create a general class that contains all the functions for working with memory. Let's call it MemoryManager. Then we will make a class MemoryRegion to describe the memory region. There is a MEMORY_BASIC_INFORMATION lot of unnecessary data that should not be passed on, so I moved them into a separate class.

Code:
public class MemoryRegion
{
    public IntPtr BaseAddress { get; set; }
    public IntPtr RegionSize { get; set; }
    public uint Protect { get; set; }
}

That's all we need: the region's starting address, its size, and its protection. Now we get all regions of memory. How it's done?
  1. We get information about the memory region at the zero address.
  2. We check the status and protection of the region. If everything is in order, add it to the list.
  3. We receive information about the next region.
  4. We check and add it to the list.
  5. We continue in a circle.
Code:
public List<MemoryRegion> QueryMemoryRegions() {
    long curr = 0;
    var regions = new List<MemoryRegion>();

    while (true) {
        try {
            var memDump = WinAPI.VirtualQueryEx(_processHandle, (IntPtr) curr, out var memInfo, 28);

            if (memDump == 0) break;

            if ((memInfo.State & 0x1000) != 0 && (memInfo.Protect & 0x100) == 0)
            {
                regions.Add(new MemoryRegion
                {
                    BaseAddress = memInfo.BaseAddress,
                    RegionSize = memInfo.RegionSize,
                    Protect = memInfo.Protect
                });
            }

            curr = (long) memInfo.BaseAddress + (long) memInfo.RegionSize;
        } catch {
            break;
        }
    }

    return regions;
}

After receiving the regions, we will scan them for the presence of the pattern we need. The pattern consists of parts of two types - of known and unknown (changing bytes): eg 00 ?? ?? FB. Let's create an interface to describe these parts.

Code:
interface IMemoryPatternPart
{
    bool Matches(byte b);
}

Now let's describe the part that has a known byte.

Code:
public class MatchMemoryPatternPart : IMemoryPatternPart
{
    public byte ValidByte { get; }

    public MatchMemoryPatternPart(byte valid)
    {
        ValidByte = valid;
    }

    public bool Matches(byte b) => ValidByte == b;
}

Let's do the same with the second type.

Code:
public class AnyMemoryPatternPart : IMemoryPatternPart
{
    public bool Matches(byte b) => true;
}

Now let's parse the pattern from the string.

Code:
private void Parse(string pattern)
{
    var parts = pattern.Split(' ');
    _patternParts.Clear();

    foreach (var part in parts)
    {
        if (part.Length != 2)
        {
            throw new Exception("Invalid pattern.");
        }

        if (part.Equals("??"))
        {
            _patternParts.Add(new AnyMemoryPatternPart());
            continue;
        }

        if (!byte.TryParse(part, NumberStyles.HexNumber, null, out var result))
        {
            throw new Exception("Invalid pattern.");
        }

        _patternParts.Add(new MatchMemoryPatternPart(result));
    }
}

As already done above, we check what type of part of the pattern it is, parse it, if necessary, and add it to the list. We need to check the work of this method.

Code:
var p = new MemoryPattern ("01 ?? 02 ?? 03 ?? FF");

pattern_scan_success.png

Success!

Now we need to teach our MemoryManager memory to read.

Code:
public byte[] ReadMemory(IntPtr addr, int size)
{
    var buff = new byte[size];
    return WinAPI.ReadProcessMemory(_processHandle, addr, buff, size, out _) ? buff : null;
}

I first wrote a nice function using Linq to scan memory. But its implementation took a long time. Then I rewrote the method without using this technology, and everything worked much faster. Optimized function result:

pattern_scan_fast.png

Fast scan memory

The result of the original function:

pattern_scan_slow.png

Very slow memory scan

Now I will share the wisdom gained at this stage: do not be afraid to optimize your code. Libraries do not always provide the fastest solutions. Original function:

Code:
public IntPtr ScanForPatternInRegion(MemoryRegion region, MemoryPattern pattern)
{
    var endAddr = (int) region.RegionSize - pattern.Size;
    var wholeMemory = ReadMemory(region.BaseAddress, (int) region.RegionSize);

    for (var addr = 0; addr < endAddr; addr++)
    {
        var b = wholeMemory.Skip(addr).Take(pattern.Size).ToArray();

        if (!pattern.PatternParts.First().Matches(b.First()))
        {
            continue;
        }

        if (!pattern.PatternParts.Last().Matches(b.Last()))
        {
            continue;
        }

        var found = true;

        for (var i = 1; i < pattern.Size - 1; i++)
        {
            if (!pattern.PatternParts[i].Matches(b[i]))
            {
                found = false;
                break;
            }
        }

        if (!found)
        {
            continue;
        }

        return region.BaseAddress + addr;
    }

    return IntPtr.Zero;
}

Fixed function (just use Array.Copy()).

Code:
public IntPtr ScanForPatternInRegion(MemoryRegion region, MemoryPattern pattern)
{
    var endAddr = (int) region.RegionSize - pattern.Size;
    var wholeMemory = ReadMemory(region.BaseAddress, (int) region.RegionSize);

    for (var addr = 0; addr < endAddr; addr++)
    {
        var buff = new byte[pattern.Size];
        Array.Copy(wholeMemory, addr, buff, 0, buff.Length);

        var found = true;

        for (var i = 0; i < pattern.Size; i++)
        {
            if (!pattern.PatternParts[i].Matches(buff[i]))
            {
                found = false;
                break;
            }
        }

        if (!found)
        {
            continue;
        }

        return region.BaseAddress + addr;
    }

    return IntPtr.Zero;
}

This function searches for a pattern within a memory region. The next function uses it to scan the memory of the entire process.

Code:
public IntPtr PatternScan(MemoryPattern pattern)
{
    var regions = QueryMemoryRegions();

    foreach (var memoryRegion in regions)
    {
        var addr = ScanForPatternInRegion(memoryRegion, pattern);

        if (addr == IntPtr.Zero)
        {
            continue;
        }

        return addr;
    }

    return IntPtr.Zero;
}

Let's add two functions for reading and writing a 32-bit number into memory.

Code:
public int ReadInt32(IntPtr addr)
{
    return BitConverter.ToInt32(ReadMemory(addr, 4), 0);
}

public void WriteInt32(IntPtr addr, int value)
{
    var b = BitConverter.GetBytes(value);
    WinAPI.WriteProcessMemory(_processHandle, addr, b, b.Length, out _);
}

Everything is now ready to search for a pattern and write the main cheat code.

Code:
var playerBase = memory.PatternScan (new MemoryPattern ("ED 03 00 00 01 00 00 00"));

We find the pattern in memory, then - the address of the player's lives.

Code:
var playerHealth = playerBase + 24;

We read the value of lives:

Code:
Console.WriteLine ($ "Current health: {memory.ReadInt32 (playerHealth)}");

Why not give the player nearly endless lives?

Code:
memory.WriteInt32 (playerHealth, int.MaxValue);

And again we count the player's lives for demonstration.

Code:
Console.WriteLine ($ "New health: {memory.ReadInt32 (playerHealth)}");

Checking​

Let's start our cheat, then start the game.

external_health_modified.png

Everything works

Let's try to press Enter in the "game".

external_health_modified_apprv.png

Lives have changed

The cheat works!

Writing your first injector​

There are many ways to force the process to load our code. You can use DLL Hijacking, you can use SetWindowsHookEx, but we'll start with the simplest and most well-known function - LoadLibrary. LoadLibrary forces the process we need to load the library itself.

We need a descriptor with the necessary rights. Let's start preparing for the injection. First, get the name of the library from the user.

Code:
Console.Write("> Enter DLL name: ");
var dllName = Console.ReadLine();

if (string.IsNullOrEmpty(dllName) || !File.Exists(dllName))
{
    Console.WriteLine("DLL name is invalid!");
    Console.ReadLine();
    return;
}

var fullPath = Path.GetFullPath(dllName);

Then we ask the user for the process name and find its ID.

Code:
var fullPath = Path.GetFullPath(dllName);
var fullPathBytes = Encoding.ASCII.GetBytes(fullPath);

Console.Write("> Enter process name: ");
var processName = Console.ReadLine();

if (string.IsNullOrEmpty(dllName))
{
    Console.WriteLine("Process name is invalid!");
    Console.ReadLine();
    return;
}

var prcs = Process.GetProcessesByName(processName);

if (prcs.Length == 0)
{
    Console.WriteLine("Process wasn't found.");
    Console.ReadLine();
    return;
}

var prcId = prcs.First().Id;

This code will have problems with processes with the same name.

Now you can go to the first injection method.

Implementing the LoadLibrary injection​

First, let's take a look at the principle of operation of this type of injector.
  1. First, it reads the full path to the library from disk.
  2. Collects it into a string. Then we get the address LoadLibraryA(LPCSTR) with GetProcAddress(HMODULE, LPCSTR).
  3. Allocates memory for a string inside the application, writes it there.
  4. After that, it creates a stream at the address LoadLibraryA, passing the path as an argument.
  5. The Specify the import statement of The statement The statement The of of The of The of of of The statement The operation of The of of The the the the the To OpenProcess, ReadProcessMemory, WriteProcessMemory, GetProcAddress, GetModuleHandle, CreateRemoteThread, VirtualAllocEx.

WWW​

Signatures can be easily found at pinvoke.net.

The first step is to open a handle with full access to the process.

Code:
var handle = WinAPI.OpenProcess(WinAPI.ProcessAccessFlags.All,
                                false,
                                processID);

if (handle == IntPtr.Zero)
{
    Console.WriteLine("Can't open process.");
    return;
}

Let's convert our string to bytes.

Code:
var libraryPathBytes = Encoding.ASCII.GetBytes (libraryPath);

After that, you need to allocate memory for this line.

Code:
var memory = WinAPI.VirtualAllocEx(handle,
                IntPtr.Zero,
                256,
                WinAPI.AllocationType.Commit | WinAPI.AllocationType.Reserve,
                WinAPI.MemoryProtection.ExecuteReadWrite);

The process descriptor is passed to the function handle: _MAX_PATH (the maximum path size in Windows), it is equal to 256. We indicate that it is possible to write to memory, read it and execute it. We write the line inside the process.

Code:
WinAPI.WriteProcessMemory (handle, memory, libraryPathBytes, libraryPathBytes.Length, out var bytesWritten);

Since we will be using the function LoadLibraryA to load the library, we need to get its address.

Code:
var funcAddr = WinAPI.GetProcAddress (WinAPI.GetModuleHandle ("kernel32"), "LoadLibraryA");

Everything is ready to start the injection process. All that remains is to create a stream in the remote application:

Code:
var thread = WinAPI.CreateRemoteThread(handle, IntPtr.Zero, IntPtr.Zero, funcAddr, memory, 0, IntPtr.Zero);

The injector is ready, but we will test it only after writing a simple library.

Writing a framework for internal​

Moving to C ++! Let's start with an entry point and a simple message via WinAPI. A must entry point A A AA A DLL the accept the the the the the aaa aa a three parameters The of The of The of of of of of The of of of of of of of The of The: HINSTANCE, DWORD, LPVOID.
  • HINSTANCE - refers to a library.
  • DWORD Is the reason for calling the entry point (DLL loading and unloading).
  • LPVOID Is a reserved value.
This is what the empty library entry point looks like:

Code:
#include <Windows.h>

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL,
  _In_ DWORD     fdwReason,
  _In_ LPVOID    lpvReserved
)
{
    return 0;
}

First, let's check why the entry point is being called.

Code:
if (fdwReason == DLL_PROCESS_ATTACH) {}

The argument fdwReason will be equal DLL_PROCESS_ATTACHif the library has just been attached to the process, or DLL_PROCESS_DETACHif it is in the process of being unloaded. For the test, we will display the message:

Code:
if(fdwReason == DLL_PROCESS_ATTACH)
{
    MessageBox(nullptr, "Hello world!", "", 0);
}

Now we can check the injector and this library. We launch the injector, enter the name of the library and process.

library-test-loaded.png

Library loaded

Now let's write a simple class with a singleton to make the code beautiful.

Code:
#pragma once

class internal_cheat
{
public:
    static internal_cheat* get_instance();
    void initialize();
    void run();

private:
    static internal_cheat* _instance;
    bool was_initialized_ = false;

    internal_cheat();
};

Now the code itself. Default constructor and singleton.

Code:
internal_cheat::internal_cheat() = default;

internal_cheat* internal_cheat::get_instance()
{
    if(_instance == nullptr)
    {
        _instance = new internal_cheat();
    }

    return _instance;
}

Here's a simple entry point code.

Code:
#include <Windows.h>

#include "InternalCheat.h"

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL,
  _In_ DWORD     fdwReason,
  _In_ LPVOID    lpvReserved
)
{
    if(fdwReason == DLL_PROCESS_ATTACH)
    {
        auto cheat = internal_cheat::get_instance();
        cheat->initialize();
        cheat->run();
    }

    return 0;
}

I must say that the next part took the longest. One small mistake resulted in a huge waste of time. But I drew conclusions and will explain to you where you can make such a mistake and how to find it.

We need to find the pattern inside the game's memory. To do this, first we will iterate over all the memory regions of the application, then we will scan each of them. Below is the implementation of getting a list of memory regions, but only for your own process. I explained how it works earlier.

Code:
DWORD internal_cheat::find_pattern(std::string pattern)
{
    auto mbi = MEMORY_BASIC_INFORMATION();
    DWORD curr_addr = 0;

    while(true)
    {
        if(VirtualQuery(reinterpret_cast<const void*>(curr_addr), &mbi, sizeof mbi) == 0)
        {
            break;
        }

        if((mbi.State == MEM_COMMIT || mbi.State == MEM_RESERVE) &&
            (mbi.Protect == PAGE_READONLY ||
                mbi.Protect == PAGE_READWRITE ||
                mbi.Protect == PAGE_EXECUTE_READ ||
                mbi.Protect == PAGE_EXECUTE_READWRITE))
        {
            auto result = find_pattern_in_range(pattern, reinterpret_cast<DWORD>(mbi.BaseAddress), reinterpret_cast<DWORD>(mbi.BaseAddress) + mbi.RegionSize);

            if(result != NULL)
            {
                return result;
            }
        }

        curr_addr += mbi.RegionSize;
    }

    return NULL;
}

For each region found, this code calls a function find_pattern_in_rangethat looks for a pattern in that region.

Code:
DWORD internal_cheat::find_pattern_in_range(std::string pattern, const DWORD range_start, const DWORD range_end)
{
    auto strstream = istringstream(pattern);

    vector<int> values;
    string s;

First, the function parses the pattern.

Code:
while (getline(strstream, s, ' '))
    {
        if (s.find("??") != std::string::npos)
        {
            values.push_back(-1);
            continue;
        }

        auto parsed = stoi(s, 0, 16);
        values.push_back(parsed);
    }

Then the scanning itself begins.

Code:
for(auto p_cur = range_start; p_cur < range_end; p_cur++ )
    {
        auto localAddr = p_cur;
        auto found = true;

        for (auto value : values)
        {
            if(value == -1)
            {
                localAddr += 1;
                continue;
            }

            auto neededValue = static_cast<char>(value);
            auto pCurrentValue = reinterpret_cast<char*>(localAddr);
            auto currentValue = *pCurrentValue;

            if(neededValue != currentValue)
            {
                found = false;
                break;
            }

            localAddr += 1;
        }

        if(found)
        {
            return p_cur;
        }
    }

    return NULL;
}

I used a vector from intto store the pattern data, which -1 means any byte can be there. I did this to simplify the search for the pattern, speed it up and not translate the same code from an external cheat.

Now a few words about the error I mentioned earlier. I was constantly rewriting the pattern search function until I decided to take a look at the memory region search function. The problem was that I was comparing memory protection completely wrong. Initial version:

Code:
if((mbi.State == MEM_COMMIT || mbi.State == MEM_RESERVE) &&
            (mbi.Protect == PAGE_EXECUTE_READ ||
             mbi.Protect == PAGE_EXECUTE_READWRITE)) { }

The code only accepted pages with read / executable memory and read / write / executable memory. He ignored the rest. The code was changed to this:

Code:
if((mbi.State == MEM_COMMIT || mbi.State == MEM_RESERVE) &&
            (mbi.Protect == PAGE_READONLY ||
                mbi.Protect == PAGE_READWRITE ||
                mbi.Protect == PAGE_EXECUTE_READ ||
                mbi.Protect == PAGE_EXECUTE_READWRITE)) { }

This function began to find all the memory pages it needed.

INFO​

PAGE_READONLY can cause a fatal error while writing data, we always have VirtualProtect.

I discovered this error when I started checking the memory pages in the application using Process Hacker and Cheat Engine. My pattern ended up in one of the very first play-protected memory regions, so it never was.

Now, having found the pattern, we can save it in the field of our class.

Code:
void internal_cheat::initialize()
{
    if(was_initialized_)
    {
        return;
    }

    printf("\n\n[CHEAT] Cheat was loaded! Initializing..\n");

    was_initialized_ = true;
    player_base_ = reinterpret_cast<void*>(find_pattern("ED 03 00 00 01 00 00 00"));

    printf("[CHEAT] Found playerbase at 0x%p\n", player_base_);
}

After that, the function will be called internal_cheat::run(), which should perform all the functions of the reader.

Code:
void internal_cheat::run()
{
    printf("[CHEAT] Cheat is now running.\n");

    const auto player_health = reinterpret_cast<int*>(reinterpret_cast<DWORD>(player_base_) + 7);

    while(true)
    {
        *player_health = INT_MAX;
        Sleep(100);
    }
}

We just get the address of the player's lives from our pattern and set them to the maximum value ( INT_MAX) every 100ms.

Checking our cheat​

We start the game, inject the library.

cheat-injected.png

Cheat is injected

Let's try to press the Enter button a couple of times.

cheat-working.png

Cheat works

Our lives don't change and everything works great!

Let's sum up​

Any element of the game that is processed on our computer can be modified or completely removed. Unfortunately or fortunately, gaming companies do not always take care of anti-cheat, opening the way for us, cheaters.

(c) xakep.ru
 

Poisonjuoice

Professional
Messages
220
Reputation
0
Reaction score
101
Points
28
Telegram
@poisonjuice0875
Really congratulations on your programming skills, unfortunately it's something I've never learned, apart from a few commands on the terminal, I don't know anything ..... I'm 47 years old it's too late to learn a simple language but effective?
 

BigBeast

Professional
Messages
602
Reputation
16
Reaction score
418
Points
63
Really congratulations on your programming skills, unfortunately it's something I've never learned, apart from a few commands on the terminal, I don't know anything ..... I'm 47 years old it's too late to learn a simple language but effective?
It's never too late to learn programming.If you are into carding this means you are already into technology & programming is for tech lovers.You can start it anytime but you need to be consistent in learning.I will recommend to start with Java or C++,because the concept is very vast.If you can learn any of them then it will be very easy to shift to other language like python.
There are many good paid & free courses available on the internet.If you are really interested in starting,i highly recommend to subscribe TheNewBoston on YouTube,his tutorials were uploaded years ago but old is gold.
 

Poisonjuoice

Professional
Messages
220
Reputation
0
Reaction score
101
Points
28
Telegram
@poisonjuice0875
It's never too late to learn programming.If you are into carding this means you are already into technology & programming is for tech lovers.You can start it anytime but you need to be consistent in learning.I will recommend to start with Java or C++,because the concept is very vast.If you can learn any of them then it will be very easy to shift to other language like python.
There are many good paid & free courses available on the internet.If you are really interested in starting,i highly recommend to subscribe TheNewBoston on YouTube,his tutorials were uploaded years ago but old is gold.
thank oyu bro....

On hardware I manage well, since I was 15 (many years ago) I was assembling PCs for friends already overclocking the processor, then 10 years ago I approached the world of Macs and since then I have left windows with forever all its problems and viruses.

The Mac kernel is the same as linux, it makes it much more unassailable to viruses, but first I have to learn the dumps well I have all the equipment, for now I'm trying to copy my card and then I'll buy the dumps and finally I will pass to practice.

I am sending you an attached EMV software bought in the dark at a negligible amount, if you want to take a look at it and you can tell me if it works (I have an MSR605X and an omnikey3121), if it goes well and you give us a tutorial we can resell it at a very low price, from make it accessible to everyone and earn a little something from it.
 

Poisonjuoice

Professional
Messages
220
Reputation
0
Reaction score
101
Points
28
Telegram
@poisonjuice0875
It does not make me send the attachment I put it in the software resources
 

HibrikBilik

Member
Messages
10
Reputation
0
Reaction score
0
Points
1
What a fantastic post; thank you for your work done. You have spent a lot of time writing the entire text, which makes it even more valuable. I've only recently started studying this area of games, and it's all new to me, but I want to study this topic properly. It seems to me that this is an up-and-coming area in games. So far, I'm exploring various articles on the subject of LUA Roblox Aimbot, but I don't quite understand how to structure my training. If you have any advice about what you can read or watch, please write your thoughts here; I will be very grateful to you for your answers.
 
Last edited:
Top