Here is a more modern method that I made and fine-tuned with a friend (devnull) and used in various projects of mine:
- bool Compare(const BYTE* pData, const BYTE* bMask, const char* szMask)
- for (; *szMask; ++szMask, ++pData, ++bMask)
- if (*szMask == 'x' && *pData != *bMask) return 0;
- return (*szMask) == NULL;
- DWORD Pattern(DWORD dwAddress, DWORD dwLen, BYTE *bMask, char * szMask)
- for (DWORD i = 0; i < dwLen; i++)
- if (Compare((BYTE*)(dwAddress + i), bMask, szMask)) return (DWORD)(dwAddress + i);
- return 0;
First, this is how I collect the data (externally) for usage with this method. We read the block of memory we want to scan then convert it into a vector.
Note: This same method can be done internally without needing ReadProcessMemory! Simply just by setting the vector to use the base of FFXiMain.dll instead of the base of 'data'.
- // Read the module into local memory..
- auto data = new unsigned char[this->m_FFXiMainSize + 1];
- ::ReadProcessMemory(handle, (LPVOID)this->m_FFXiMainBase, data, this->m_FFXiMainSize, NULL);
- std::vector<BYTE> rawdata(data, data + this->m_FFXiMainSize);
- delete data;
Now the implementation of our scanning method is as follows:
The first part of our implementation is to build the pattern into a paired vector. This takes our pattern and mask and makes a vector of paired values holding the pattern to compare the data against. We do this because std::search allows us to pass an enumerable haystack and needle to look for. This makes things very easy to handle both the pattern byte and mask to compare against all in one go. The value pair is the byte of the pattern, then the boolean if the pattern byte is a wildcard or not based on the mask. (The mask implementation is based on the first example above, so we use 'x' as a required character, and anything else as a wildcard.)
- * @brief Scans a given chunk of data for the given pattern and mask.
- * @param data The data to scan within for the given pattern.
- * @param baseAddress The base address of where the scan data is from.
- * @param lpPattern The pattern to scan for.
- * @param pszMask The mask to compare against for wildcards.
- * @param offset The offset to add to the pointer.
- * @param resultUsage The result offset to use when locating signatures that match multiple functions.
- * @return Pointer of the pattern found, 0 otherwise.
- static DWORD __stdcall FindPattern(std::vector<unsigned char> data, unsigned int baseAddress, const unsigned char* lpPattern, const char* pszMask, int offset, int resultUsage)
- // Build vectored pattern..
- std::vector<std::pair<unsigned char, bool>> pattern;
- for (size_t x = 0; x < strlen(pszMask); x++)
- pattern.push_back(std::make_pair(lpPattern[x], pszMask[x] == 'x'));
- // The result count for multiple results..
- auto resultCount = 0;
- auto scanStart = data.begin();
- while (true)
- // Search for the pattern..
- auto ret = std::search(scanStart, data.end(), pattern.begin(), pattern.end(),
- [&](unsigned char curr, std::pair<unsigned char, bool> currPattern)
- return (!currPattern.second) || curr == currPattern.first;
- // Did we find a match..
- if (ret != data.end())
- // If we hit the usage count, return the result..
- if (resultCount == resultUsage || resultUsage == 0)
- return (std::distance(data.begin(), ret) + baseAddress) + offset;
- // Increment the found count and scan again..
- scanStart = ++ret;
- return 0;
The next step is our loop to call std::search. This is looped because we allow multiple matches to be found on the same signature. Sometimes, a pattern may match multiple functions and the first found match is not what is wanted. This is where the resultUsage parameter comes in. This allows us to define what match to return, if we want to use multiple matches.
std::search is setup to to take the following parameters:
- Iterator to determine the starting offset in the data to begin searching at. (Haystack start.)
- Iterator to define the end of the data we are scanning. (Haystack end.)
- Iterator to define the start of our pattern. (Needle start.)
- Iterator to define the end of our pattern. (Needle end.)
- Function to handle the comparison of the needle to the current haystack data.
Because we are making use of multiple results, scanStart is stored and used as the start of the haystack in case the user wants a different result other then the first. scanStart is set to the pattern+1 offset inside of the data each time it is found if the resultUsage is set.
Our comparison function is a simple lambda that checks our pattern object to determine if the current part of the pattern matches the current data from the haystack. Our first check determines if the pattern byte is a wildcard by seeing if its true (required). The second check is to ensure the byte of the pattern matches the current byte of the haystack.
Threaded Scanning With std::async!
The next step is to implement asynchronous scanning through C++11's async and shared_future features. You can read more into these here:
To start, we need to realize the difference between std::future and std::shared_future. We make use of std::shared_future because of a "limitation" with std::future. (And I say limitation in quotes because it is not really a limitation.) std::future is a single-shot async object. This means that once the method .get() is called once, the object is done. .get() cannot be called again on the object or an exception will be raised. Instead, we make use of std::shared_future because it keeps the result alive until the future is removed from scope. This means we can call .get() as many times as we want on the object and it will remain valid until the future is deleted / removed from scope.
We can store our pointers into a map that allows us to lookup the pointer later by name. This is in my opinion the easiest method of retrieving the pointers at a later time when needed. So our map would be like this:
The next step is to load our signatures into a future within this map. We can do this like this:
- std::map<std::string, std::shared_future<unsigned long>> m_Signatures;
- std::ref(rawdata) is a reference to our vector of data dumped to scan within. (Haystack.)
- this->m_Signatures["sigName"] = std::async(std::launch::async, &FindPattern, std::ref(rawdata), sizeOfData, signature, mask, offset, resultUsage);
- sizeOfData is the length of rawdata.
- signature is the signature to scan for.
- mask is the mask to compare against.
- offset is the offset to add after the pattern is found.
- resultUsage is the result to use if multiple results are known.
The last step is to validate the futures and ensure they completed before we attempt to use them. For this, we need to ensure the future's task is completed. For that we can do the following:
And that's it! The last bit is just obtaining a signature that we want at another point in time. That can be done like this for example:
- // Ensure all futures are completed..
- std::for_each(this->m_Signatures.begin(), this->m_Signatures.end(), (std::pair<std::string, std::shared_future<unsigned long>> s)
- // Obtain the current future status..
- auto status = std::future_status::timeout;
- status = s.second.wait_for(std::chrono::milliseconds(5));
- } while (status != std::future_status::ready);
- // Obtain the status value..
- auto pointer = s.second.get();
- // At this point you can check if pointer is valid and handle
- // any invalid pointers as needed. Perhaps you want the application
- // to fail to load if any pointers are invalid etc.
- * @brief Returns a pointers current value.
- * @param name The name of the pointer to obtain.
- * @return The value of the pointer, 0 if not found.
- unsigned long Memory::GetPointer(const std::string& name) const
- auto pointer = this->m_Signatures.find(name);
- if (pointer == this->m_Signatures.end())
- return 0;
- return pointer->second.get();