Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник External AobScan Fast | C# | SIMD & Parallel | .NET 10

Уникальная группа
Уникальная группа
Статус
Онлайн
Регистрация
24 Сен 2024
Сообщения
70
Реакции
26
Доброго дня и ночи. Написал на днях AobScan.

Проверяет маску пачками по 16/32 байта за такт (в зависимости от процессора). Автоматически находит самую длинную непрерывную последовательность байт в паттерне и использует оптимизированный Span.IndexOf (AVX2 под капотом) для первичного отсева. Используется ArrayPool для буферов чтения, чтобы не насиловать GC. Структуры ref struct и Span везде, где это возможно. Сканирование регионов распараллелено через Parallel.ForEach. Используется генераторы кода через атрибут LibraryImport и отключен маршалинг рантайма для снижения оверхеда при вызовах WinAPI.

C#:
Expand Collapse Copy
var pid = ProcessUtils.FindByExeName("game.exe");
using var handle = ProcessUtils.OpenProcessById(pid);

var scanner = new AobScan(handle);

var results = scanner.Scan("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20");

foreach (var address in results)
{
    Console.WriteLine($"Found at: {address:X}");
}

Бенчмарк (notepad.exe, 1000 прогонов): (процесс занимал 7,92 МБ). Процессор: I3-10100F. Паттерн: [20 20]

1767873173131.png


Среднее время на прогон: ~2.03 мс (зависит от размера паттерна и кол-ва памяти).
Код не претендует на звание самого быстрого в мире (в C++ internal вы не выиграете), но для внешнего софта на C# это потолок производительности без ухода в грязь.

Source: https://github.com/larkliy/WinAobscanFast
Ставьте звезду репозиторию, если вам понравилось.:catjam:
 
слабый результат если процесс мало весил и было мало мемори пейджей
с моим аоб сканом за 2-3 секунды получало одну или несколько сигнатур (их адреса) в процессе который весит минимум 2 гб и все сигны находятся ближе к концу мемори пейджей, и все это без лимита мин макс адресов
вот код если кому надо (там немножко кода для p2c, не используйте такую защиту и замените если надо)

C++:
Expand Collapse Copy
[[nodiscard]] std::future<std::vector<void*>> Memory::AoBScan(const std::string& pattern, int64_t min, int64_t max)
{
    std::pair<std::vector<BYTE>, std::vector<BYTE>> aobPair = Memory::ParsePattern(pattern);
    std::vector<BYTE> patternKey(aobPair.first.size(), 0x00);

    return Memory::AoBScan({ aobPair.first, patternKey }, aobPair.second, { std::make_pair(min, max) });
}

[[nodiscard]] std::future<std::vector<void*>> Memory::AoBScan(std::pair<std::vector<BYTE>, std::vector<BYTE>> pattern, const std::vector<BYTE>& mask, std::vector<std::pair<int64_t, int64_t>> mins_maxs)
{
    return std::async(std::launch::async, [this, pattern, mask, mins_maxs]() {
        std::vector<void*> results;

        std::unique_ptr<std::vector<BYTE>> decryptedPattern = std::make_unique<std::vector<BYTE>>(Memory::XorDecrypt(pattern.first, pattern.second));
        std::vector<MemoryRegion> memoryRegions;
        for (auto [min, max] : mins_maxs)
        {
            if (min < this->GetMinAddress())
                min = GetMinAddress();
            if (max < min || max > this->GetMaxAddress())
                max = this->GetMaxAddress();

            for (MemoryRegion region : GetMemoryRegions(this->GetCurrentPHandle(), min, max))
                memoryRegions.push_back(region);
        }
        std::mutex resultsMutex;

        Memory::_busy = true;

        std::vector<std::thread> threads;
        for (const auto& region : memoryRegions) {
            threads.emplace_back([this, &region, &decryptedPattern, &mask, &results, &resultsMutex, mins_maxs]() {
                std::vector<uint8_t> buffer(region.RegionSize);
                SIZE_T bytesRead;
                if (LI_FN(ReadProcessMemory).get()(this->GetCurrentPHandle(), region.BaseAddress, reinterpret_cast<LPVOID>(buffer.data()), region.RegionSize, &bytesRead)) {
                    auto matches = ScanRegion(buffer, *decryptedPattern, mask);
                    std::lock_guard<std::mutex> lock(resultsMutex);

                    for (int offset : matches)
                    {
                        void* matchAddress = static_cast<uint8_t*>(region.BaseAddress) + offset;
                        if (matchAddress == decryptedPattern.get())
                            continue;

                        for (auto [min, max] : mins_maxs)
                        {
                            if (min < this->GetMinAddress())
                                min = this->GetMinAddress();
                            if (max < min || max > this->GetMaxAddress())
                                max = this->GetMaxAddress();

                            if (reinterpret_cast<int64_t>(matchAddress) > max ||
                                reinterpret_cast<int64_t>(matchAddress) < min ||
                                std::find(results.begin(), results.end(), matchAddress) != results.end())
                                continue;

                            results.push_back(matchAddress);
                        }
                    }
                }
            });
        }

        for (auto& thread : threads)
            thread.join();

        Memory::_busy = false;
        decryptedPattern.reset(nullptr);

        return results;
    });
}

std::vector<MemoryRegion> Memory::GetMemoryRegions(HANDLE handle, int64_t min, int64_t max)
{
    std::vector<MemoryRegion> regions;
    MEMORY_BASIC_INFORMATION memInfo;
    void* address = reinterpret_cast<void*>(min);

    while (reinterpret_cast<int64_t>(address) < max)
    {
        if (VirtualQueryEx(this->_phandle, address, &memInfo, sizeof(memInfo)) == 0) break;

        if (memInfo.State == MEM_COMMIT && !(memInfo.Protect & PAGE_GUARD) && !(memInfo.Protect & PAGE_NOACCESS) && (memInfo.Protect & PAGE_READWRITE))
            regions.emplace_back(memInfo.BaseAddress, static_cast<int>(memInfo.RegionSize));

        address = static_cast<uint8_t*>(memInfo.BaseAddress) + memInfo.RegionSize;
    }

    return regions;
}

std::vector<int> Memory::ScanRegion(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask)
{
    std::vector<int> matches;
    int patternLength = pattern.size();

    if (patternLength <= 32 && this->_avx2_support)
        ScanRegionMemoryAvx2(memory, pattern, mask, matches);
    else if (patternLength <= 16 && this->_sse2_support)
        ScanRegionMemorySse2(memory, pattern, mask, matches);
    else
        ScanRegionMemoryFallback(memory, pattern, mask, matches);

    return matches;
}

void Memory::ScanRegionMemoryAvx2(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    __m256i vPattern = _mm256_set1_epi8(pattern[0]);
    __m256i vMask = _mm256_set1_epi8(mask[0]);

    for (int i = 0; i <= memoryLength - 32; i += 32) {
        __m256i vMemory = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(&memory[i]));
        __m256i vResult = _mm256_and_si256(vMemory, vMask);
        __m256i vCmp = _mm256_cmpeq_epi8(vResult, vPattern);

        uint32_t matchMask = _mm256_movemask_epi8(vCmp);

        while (matchMask != 0) {
            unsigned long index;
            _BitScanForward(&index, matchMask);
            if (CheckMatch(memory, i + index, pattern, mask)) {
                matches.push_back(i + index);
            }
            matchMask &= (matchMask - 1);
        }
    }

    for (int i = memoryLength - 32; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

void Memory::ScanRegionMemorySse2(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    __m128i vPattern = _mm_set1_epi8(pattern[0]);
    __m128i vMask = _mm_set1_epi8(mask[0]);

    for (int i = 0; i <= memoryLength - 16; i += 16) {
        __m128i vMemory = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&memory[i]));
        __m128i vResult = _mm_and_si128(vMemory, vMask);
        __m128i vCmp = _mm_cmpeq_epi8(vResult, vPattern);

        uint16_t matchMask = _mm_movemask_epi8(vCmp);

        while (matchMask != 0) {
            unsigned long index;
            _BitScanForward(&index, matchMask);
            if (CheckMatch(memory, i + index, pattern, mask)) {
                matches.push_back(i + index);
            }
            matchMask &= (matchMask - 1);
        }
    }

    for (int i = memoryLength - 16; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

void Memory::ScanRegionMemoryFallback(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    for (int i = 0; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

bool Memory::CheckMatch(const std::vector<uint8_t>& memory, int offset, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask)
{
    if (offset + pattern.size() > memory.size()) return false;

    for (size_t i = 0; i < pattern.size(); i++)
        if ((memory[offset + i] & mask[i]) != (pattern[i] & mask[i])) return false;

    return true;
}
 
слабый результат если процесс мало весил и было мало мемори пейджей
с моим аоб сканом за 2-3 секунды получало одну или несколько сигнатур (их адреса) в процессе который весит минимум 2 гб и все сигны находятся ближе к концу мемори пейджей, и все это без лимита мин макс адресов
вот код если кому надо (там немножко кода для p2c, не используйте такую защиту и замените если надо)

C++:
Expand Collapse Copy
[[nodiscard]] std::future<std::vector<void*>> Memory::AoBScan(const std::string& pattern, int64_t min, int64_t max)
{
    std::pair<std::vector<BYTE>, std::vector<BYTE>> aobPair = Memory::ParsePattern(pattern);
    std::vector<BYTE> patternKey(aobPair.first.size(), 0x00);

    return Memory::AoBScan({ aobPair.first, patternKey }, aobPair.second, { std::make_pair(min, max) });
}

[[nodiscard]] std::future<std::vector<void*>> Memory::AoBScan(std::pair<std::vector<BYTE>, std::vector<BYTE>> pattern, const std::vector<BYTE>& mask, std::vector<std::pair<int64_t, int64_t>> mins_maxs)
{
    return std::async(std::launch::async, [this, pattern, mask, mins_maxs]() {
        std::vector<void*> results;

        std::unique_ptr<std::vector<BYTE>> decryptedPattern = std::make_unique<std::vector<BYTE>>(Memory::XorDecrypt(pattern.first, pattern.second));
        std::vector<MemoryRegion> memoryRegions;
        for (auto [min, max] : mins_maxs)
        {
            if (min < this->GetMinAddress())
                min = GetMinAddress();
            if (max < min || max > this->GetMaxAddress())
                max = this->GetMaxAddress();

            for (MemoryRegion region : GetMemoryRegions(this->GetCurrentPHandle(), min, max))
                memoryRegions.push_back(region);
        }
        std::mutex resultsMutex;

        Memory::_busy = true;

        std::vector<std::thread> threads;
        for (const auto& region : memoryRegions) {
            threads.emplace_back([this, &region, &decryptedPattern, &mask, &results, &resultsMutex, mins_maxs]() {
                std::vector<uint8_t> buffer(region.RegionSize);
                SIZE_T bytesRead;
                if (LI_FN(ReadProcessMemory).get()(this->GetCurrentPHandle(), region.BaseAddress, reinterpret_cast<LPVOID>(buffer.data()), region.RegionSize, &bytesRead)) {
                    auto matches = ScanRegion(buffer, *decryptedPattern, mask);
                    std::lock_guard<std::mutex> lock(resultsMutex);

                    for (int offset : matches)
                    {
                        void* matchAddress = static_cast<uint8_t*>(region.BaseAddress) + offset;
                        if (matchAddress == decryptedPattern.get())
                            continue;

                        for (auto [min, max] : mins_maxs)
                        {
                            if (min < this->GetMinAddress())
                                min = this->GetMinAddress();
                            if (max < min || max > this->GetMaxAddress())
                                max = this->GetMaxAddress();

                            if (reinterpret_cast<int64_t>(matchAddress) > max ||
                                reinterpret_cast<int64_t>(matchAddress) < min ||
                                std::find(results.begin(), results.end(), matchAddress) != results.end())
                                continue;

                            results.push_back(matchAddress);
                        }
                    }
                }
            });
        }

        for (auto& thread : threads)
            thread.join();

        Memory::_busy = false;
        decryptedPattern.reset(nullptr);

        return results;
    });
}

std::vector<MemoryRegion> Memory::GetMemoryRegions(HANDLE handle, int64_t min, int64_t max)
{
    std::vector<MemoryRegion> regions;
    MEMORY_BASIC_INFORMATION memInfo;
    void* address = reinterpret_cast<void*>(min);

    while (reinterpret_cast<int64_t>(address) < max)
    {
        if (VirtualQueryEx(this->_phandle, address, &memInfo, sizeof(memInfo)) == 0) break;

        if (memInfo.State == MEM_COMMIT && !(memInfo.Protect & PAGE_GUARD) && !(memInfo.Protect & PAGE_NOACCESS) && (memInfo.Protect & PAGE_READWRITE))
            regions.emplace_back(memInfo.BaseAddress, static_cast<int>(memInfo.RegionSize));

        address = static_cast<uint8_t*>(memInfo.BaseAddress) + memInfo.RegionSize;
    }

    return regions;
}

std::vector<int> Memory::ScanRegion(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask)
{
    std::vector<int> matches;
    int patternLength = pattern.size();

    if (patternLength <= 32 && this->_avx2_support)
        ScanRegionMemoryAvx2(memory, pattern, mask, matches);
    else if (patternLength <= 16 && this->_sse2_support)
        ScanRegionMemorySse2(memory, pattern, mask, matches);
    else
        ScanRegionMemoryFallback(memory, pattern, mask, matches);

    return matches;
}

void Memory::ScanRegionMemoryAvx2(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    __m256i vPattern = _mm256_set1_epi8(pattern[0]);
    __m256i vMask = _mm256_set1_epi8(mask[0]);

    for (int i = 0; i <= memoryLength - 32; i += 32) {
        __m256i vMemory = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(&memory[i]));
        __m256i vResult = _mm256_and_si256(vMemory, vMask);
        __m256i vCmp = _mm256_cmpeq_epi8(vResult, vPattern);

        uint32_t matchMask = _mm256_movemask_epi8(vCmp);

        while (matchMask != 0) {
            unsigned long index;
            _BitScanForward(&index, matchMask);
            if (CheckMatch(memory, i + index, pattern, mask)) {
                matches.push_back(i + index);
            }
            matchMask &= (matchMask - 1);
        }
    }

    for (int i = memoryLength - 32; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

void Memory::ScanRegionMemorySse2(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    __m128i vPattern = _mm_set1_epi8(pattern[0]);
    __m128i vMask = _mm_set1_epi8(mask[0]);

    for (int i = 0; i <= memoryLength - 16; i += 16) {
        __m128i vMemory = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&memory[i]));
        __m128i vResult = _mm_and_si128(vMemory, vMask);
        __m128i vCmp = _mm_cmpeq_epi8(vResult, vPattern);

        uint16_t matchMask = _mm_movemask_epi8(vCmp);

        while (matchMask != 0) {
            unsigned long index;
            _BitScanForward(&index, matchMask);
            if (CheckMatch(memory, i + index, pattern, mask)) {
                matches.push_back(i + index);
            }
            matchMask &= (matchMask - 1);
        }
    }

    for (int i = memoryLength - 16; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

void Memory::ScanRegionMemoryFallback(const std::vector<uint8_t>& memory, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask, std::vector<int>& matches)
{
    int memoryLength = memory.size();
    int patternLength = pattern.size();

    for (int i = 0; i <= memoryLength - patternLength; i++) {
        if (CheckMatch(memory, i, pattern, mask)) {
            matches.push_back(i);
        }
    }
}

bool Memory::CheckMatch(const std::vector<uint8_t>& memory, int offset, const std::vector<uint8_t>& pattern, const std::vector<uint8_t>& mask)
{
    if (offset + pattern.size() > memory.size()) return false;

    for (size_t i = 0; i < pattern.size(); i++)
        if ((memory[offset + i] & mask[i]) != (pattern[i] & mask[i])) return false;

    return true;
}

Похоже что ты не понял. Для 2-3 гб это ОЧЕНЬ много. Вот мой средний результат с Writable/Readable в процессе 3.5 гб за 10 прогонов при том, что у меня I3-10100F и паттерн с длиной 2.
1767884929053.png
 
v1.1:
В последней версии убрал проверку границ у массивов и заменил индексы на Unsafe.Add, т.к. он быстрее, чем арифметика указателей в C#

1767887830154.png
 
v1.2:
- Добавлена чанкенизация, скорость возросла на 20-30%.

Последние тесты проводились на процессе 5.6GB, процессор I3-10100F и результаты (за 20 прогонов) такие:
256 * 1024:
Прогонов: 20
Найдено вхождений: 118
Среднее время: 690.80 мс
Средние тики: 6,913,115

Это НАМНОГО больше чем в прошлой версии.

Скорость с notepad с теми же 1000 прогонов тоже сильно возросла.
1768004389920.png

Прошлая - 2.xx мс.

Скоро запушу
 
Назад
Сверху Снизу