- 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.
- // 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:
- /**
- * @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..
- ++resultCount;
- scanStart = ++ret;
- }
- else
- break;
- }
- 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:
async: http://en.cppreference.com/w/cpp/thread/async
shared_future: http://en.cppreference.com/w/cpp/thread/shared_future
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:
- std::map<std::string, std::shared_future<unsigned long>> m_Signatures;
- 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:
- // 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;
- do
- {
- 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();
- }