Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


What does it take to write your own memory scanner?

 
Post new topic   Reply to topic    Cheat Engine Forum Index -> General programming
View previous topic :: View next topic  
Author Message
horsedeg
Newbie cheater
Reputation: 0

Joined: 26 Jun 2017
Posts: 24

PostPosted: Thu Jul 06, 2017 4:41 am    Post subject: What does it take to write your own memory scanner? Reply with quote

Or, an alternative question: What are alternative methods to find memory addresses that aren't detected by everything?

Obviously since Cheat Engine is detected by most multiplayer games, so it's probably a good idea to use an alternative. The idea of it seems rather simple. Iterate through a region of memory and compare values. But it's probably way more difficult in reality. I just don't know how. I don't know anything about threading, and I'm not sure how you would know if something is a float, double, etc. So obviously it's harder than it seems to me.

What does it take? I have good fundamental knowledge of C++. I took an intro class (learning the basic stuff, through pointers and classes) and then a data structure class. I've kind of just been learning a little bit of game hacking in general.

Is it not too difficult to just make a really basic one?
Back to top
View user's profile Send private message
FreeER
Master Cheater
Reputation: 12

Joined: 09 Aug 2013
Posts: 344

PostPosted: Thu Jul 06, 2017 7:51 am    Post subject: Reply with quote

Disclaimer: I haven't done it before but

Look at any trainer template that uses aob/pattern scans and it'll have a way to scan memory for (usually) the first aob, just make it keep scanning instead of stopping at the first. Once you've got that you just need to store the results and on future scans check based on those stored results rather than all memory. And then provide a way to show the results and input the aob.

Basic scanner done. A nice touch would be adding interfaces for ints and floats instead of just aobs but that's not technically necessary.

Further you could probably narrow the regions you need to scan by checking the memory region's protection (read/write/executable), though that's not something I remember seeing in trainers....of course there's the obvious option of taking a scan range as input from the user.
Back to top
View user's profile Send private message
atom0s
Moderator
Reputation: 137

Joined: 25 Jan 2006
Posts: 7309
Location: 127.0.0.1

PostPosted: Thu Jul 06, 2017 1:18 pm    Post subject: Reply with quote

This is an old example I showed someone to scan for 4 byte values:

Code:
/**
 * Simple Memory Scanner Example
 * (c) 2014 atom0s [[email protected]]
 */

#include <Windows.h>
#include <string>
#include <TlHelp32.h>

/**
 * @brief The target process to scan within.
 */
#define TARGET_NAME "winmine.exe"

/**
 * @brief Obtains the process id of the given target.
 *
 * @return The process id if found, 0 otherwise.
 */
unsigned int getTargetProcessId()
{
    PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };

    // Obtain a snapshot of the current process list..
    auto handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (handle == INVALID_HANDLE_VALUE)
        return 0;

    // Obtain the first process..
    if (!::Process32First(handle, &pe32))
    {
        ::CloseHandle(handle);
        return 0;
    }

    // Loop each process looking for the target..
    do
    {
        if (!_stricmp(pe32.szExeFile, TARGET_NAME))
        {
            ::CloseHandle(handle);
            return pe32.th32ProcessID;
        }
    } while (::Process32Next(handle, &pe32));

    // Cleanup..
    ::CloseHandle(handle);
    return 0;
}

/**
 * @brief Entry point of this application.
 *
 * @param argc  The count of arguments passed to this application.
 * @param argv  The array of arguments passed to this application.
 *
 * @return Non-important return.
 */
int __cdecl main(int argc, char* argv[])
{
    // Obtain the target process id..
    auto processId = getTargetProcessId();
    if (processId == 0)
        return 0;

    // Open a handle to the target..
    auto handle = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId);
    if (handle == INVALID_HANDLE_VALUE)
        return 0;

    // Obtain the current system information..
    SYSTEM_INFO sysInfo = { 0 };
    ::GetSystemInfo(&sysInfo);

    auto addr_min = (long)sysInfo.lpMinimumApplicationAddress;
    auto addr_max = (long)sysInfo.lpMaximumApplicationAddress;

    auto found = 0;

    // Loop the pages of memory of the application..
    while (addr_min < addr_max)
    {
        MEMORY_BASIC_INFORMATION mbi = { 0 };
        if (!::VirtualQueryEx(handle, (LPCVOID)addr_min, &mbi, sizeof(mbi)))
        {
            printf_s("Failed to query memory.\n");
            break;
        }

        // Determine if we have access to the page..
        if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_GUARD) == 0) && ((mbi.Protect & PAGE_NOACCESS) == 0))
        {
            //
            // Below are flags about the current region of memory. If you want to specifically scan for only
            // certain things like if the area is writable, executable, etc. you can use these flags to prevent
            // reading non-desired protection types.
            //

            auto isCopyOnWrite = ((mbi.Protect & PAGE_WRITECOPY) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0);
            auto isExecutable = ((mbi.Protect & PAGE_EXECUTE) != 0 || (mbi.Protect & PAGE_EXECUTE_READ) != 0 || (mbi.Protect & PAGE_EXECUTE_READWRITE) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0);
            auto isWritable = ((mbi.Protect & PAGE_READWRITE) != 0 || (mbi.Protect & PAGE_WRITECOPY) != 0 || (mbi.Protect & PAGE_EXECUTE_READWRITE) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0);

            // Dump the region into a memory block..
            auto dump = new unsigned char[mbi.RegionSize + 1];
            memset(dump, 0x00, mbi.RegionSize + 1);
            if (!::ReadProcessMemory(handle, mbi.BaseAddress, dump, mbi.RegionSize, NULL))
            {
                printf_s("Failed to read memory of location: %08X\n", mbi.BaseAddress);
                break;
            }

            // Scan for 4 byte value of 1337..
            for (auto x = 0; x < mbi.RegionSize - 4; x += 4)
            {
                if (*(DWORD*)(dump + x) == 1337)
                    found++;
            }

            // Cleanup the memory dump..
            delete[] dump;
        }

        // Step the current address by this regions size..
        addr_min += mbi.RegionSize;
    }

    printf_s("Found %d results!\n", found);

    // Cleanup..
    ::CloseHandle(handle);
    return ERROR_SUCCESS;
}


You can alter it to include other scan types as well as break the scanning into threads based on the regions being scanned etc.

_________________
- Retired.
Back to top
View user's profile Send private message Visit poster's website
horsedeg
Newbie cheater
Reputation: 0

Joined: 26 Jun 2017
Posts: 24

PostPosted: Fri Jul 07, 2017 3:41 am    Post subject: Reply with quote

Wow, thank you for this. One thing I noticed that wasn't working is that for some reason, addr_min and addr_max return weird values. addr_min returns 65537, and addr_max returns -65537. So it never reaches the while loop and always returns 0 results. I guess I could just search from 0x0 to 0x7FFFFFFF manually? Or is there a fix?

Also, I guess I don't understand this enough. How would one search for 8 bytes? Surely it's not simply iterating by 8 instead of 4, is it? I would guess that it's just using a 64-bit storage instead of DWORD and iterating by 8 instead of 4? Side question, I've heard of QWORD and I would assume it's a 64 bit version of DWORD. Would it work? I also don't know what header it's in, because it's not in Windows.h

What about a float? Is float just 4 bytes but the number is kind of "converted"?
Back to top
View user's profile Send private message
FreeER
Master Cheater
Reputation: 12

Joined: 09 Aug 2013
Posts: 344

PostPosted: Fri Jul 07, 2017 9:05 am    Post subject: Reply with quote

horsedeg wrote:
addr_min returns 65537, and addr_max returns -65537
looks normal enough to me... as a 4 byte value 65537 is 10001 in hex and -65537 is FFFEFFFF (FFFFFFFFFFFEFFFF as an 8 byte value) which should cover pretty much anything you'd want to scan. You'd probably want to use unsigned long not just long since negative memory addresses don't exactly make sense... or maybe something like IntPtr

The rest of your assumptions seem correct, 8 bytes are the same thing as 4 but with twice the storage same as doubles are twice the size of a 4 byte float (just a different storage format based on scientific notation). As for qword, have you tried using it after including Windows.h? Just because it's not in that particular file doesn't mean that file isn't including some other file which defines/typedefs it.

Personally I'd probably use uint32_t and uint64_t instead of DWORD and QWORD but that's just personal preference.

edit: apparently I can't double post yet but
I played around with the code and got this https://pastebin.com/mSqZVW2g

It's probably horrific by C++ standards lol and definitely as a general use thing but for a simple demo...

Oh, and make sure you're running it as administrator, that's probably your issue from earlier Smile
Back to top
View user's profile Send private message
horsedeg
Newbie cheater
Reputation: 0

Joined: 26 Jun 2017
Posts: 24

PostPosted: Sat Jul 08, 2017 12:53 am    Post subject: Reply with quote

Yeah, I've been using uint32_t and uint64_t.

One problem I just realized now, though, is finding pointers and offsets. Don't you kind of need a disassembler of some sort to find the offsets?
Back to top
View user's profile Send private message
FreeER
Master Cheater
Reputation: 12

Joined: 09 Aug 2013
Posts: 344

PostPosted: Sat Jul 08, 2017 8:45 am    Post subject: Reply with quote

Manually you'd need more than a disassembler since that alone isn't going to tell you what instruction is accessing the value you care about. Though there's no reason you couldn't just go with the unix philosophy of focusing on one thing and doing it well, leaving the other tasks to another program (like ollydbg or x32dbg). Use the scanner to find the address and use your favorite debugger to add a breakpoint and inspect the code. That would allow the scanner to focus on providing more types (flash, *100, etc.) or rules for automatically finding things or simply optimizing the code.

Don't forget about the pointer scanner in CE though, it doesn't look at the code to figure out the offset, (I'm guessing here but) it just scans memory and tries to calculate any possible offset to the desired address. Which is why there are so many false positives in the beginning if you don't give it more information to restrict the results.
Back to top
View user's profile Send private message
horsedeg
Newbie cheater
Reputation: 0

Joined: 26 Jun 2017
Posts: 24

PostPosted: Sat Jul 08, 2017 6:19 pm    Post subject: Reply with quote

I looked up some of these debuggers, and I'm just wondering if these are somehow detected easily like Cheat Engine is?

Also, how exactly does the pointer scanner work? If I were to guess, I would obviously first find the address I'm looking for that represents a value. Then, scan through all of memory, and find an address that holds the value of an address. Then do a check to see how close it is to how close that address is to the address I want. Maybe put a limit on plus or minus 2000 or something (since the largest I've seen is -1520). But maybe that'd give too many results.

Lastly, how does cheat engine know if something is a static address? How would a pointer scanner that I would write lead to a starting address of something like "chrome.exe+03AF3230"?
Back to top
View user's profile Send private message
FreeER
Master Cheater
Reputation: 12

Joined: 09 Aug 2013
Posts: 344

PostPosted: Sat Jul 08, 2017 6:53 pm    Post subject: Reply with quote

Without plugins, sure. But anything you write is probably going to be detectable just as easily for any program that really tries. Anti-Debugging and Anti-Anti-debugging is it's own separate rabbit hole lol

The other debuggers already have plugins for anti-anti-debugging that are usually fairly effective.

As for the pointer scanner, like I said I was guessing as to how it works, but feel free to checkout the github code... https://github.com/cheat-engine/cheat-engine

As for static, it's explained on the forum somewhere... If I recall correctly it assumes anything within some range of the module base addresses are static and for the threads see http://forum.cheatengine.org/viewtopic.php?p=5602055#5602055
Back to top
View user's profile Send private message
horsedeg
Newbie cheater
Reputation: 0

Joined: 26 Jun 2017
Posts: 24

PostPosted: Sat Jul 08, 2017 7:33 pm    Post subject: Reply with quote

So what do most people do when trying to find pointers and addresses where Cheat Engine is easily detectable? I'm thinking the simplest way is just writing a memory scanner and pointer scanner. Anti-anti-debugging seems too complicated and I kind of doubt most game cheaters do that.
Back to top
View user's profile Send private message
FreeER
Master Cheater
Reputation: 12

Joined: 09 Aug 2013
Posts: 344

PostPosted: Sun Jul 09, 2017 8:35 am    Post subject: Reply with quote

Well most people probably give up because they don't really know much about CE or any other debuggers yet and they aren't programmers so wouldn't dream of creating another tool (let alone know how), others would ask someone else to do it or how to bypass the detection, some might try renaming CE or compiling an "undetectable" version of CE (changing certain things that anti-debug checks are checking for),

others would use a different debugger (potentially with a stealth plugin) that the game isn't detecting to find where the detection code is and write an aob script to disable it (or simply patch it if they're going to make a, eg. C++, trainer that wouldn't be detected). However, a lot of the people who can do that don't often bother with pointers and tend to use aob scripts because they're faster and more powerful than a pointer, especially as 64bit games are using more and more memory.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> General programming All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites