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

Исходник [Сурс] Apex Legends — Утилита для дампа PE памяти через Kernel Driver (C++)

  • Автор темы Автор темы hex_cat
  • Дата начала Дата начала
Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
429
Реакции
10
Выкатили свежий релиз дампера для Apex Legends под DX12.

Очередная паста, но вполне рабочая база для тех, кто хочет поковырять оффсеты самостоятельно, а не клянчить их в каждом треде после очередного обновления игры. Если вы устали от того, что ваш софт отлетает в детект из-за кривых сигнатур или неактуальных адресов — этот инструмент поможет сдампить PE-образ прямо из памяти процесса.

Как это работает:
  1. Ядро: Требуется ваш личный Kernel-драйвер для взаимодействия с памятью (RPM). Без него ничего не взлетит.
  2. Подготовка: Запускаете игру, заходите в стрельбище (Fire Range). Подвигайтесь, постреляйте, поменяйте пушки — это нужно, чтобы игра подгрузила все необходимые модули в память, иначе дамп будет неполным.
  3. Снятие дампа: Запускаете тулзу, она цепляется к r5apex_dx12.exe, вытягивает CR3 и выплевывает файл game_dumped.exe.
  4. Разбор: Полученный файл можно прогнать через профильные ресурсы для автоматического поиска оффсетов.

Техническая инфа:
Сурс написан на C++, юзает read_physical для обхода стандартных ограничений API Windows. В коде реализован фикс IAT (Import Address Table) и релокаций, чтобы полученный дамп можно было адекватно просмотреть в IDA или Ghidra. Также добавлена проверка энтропии секций — если видите высокий показатель, значит, там могут быть упакованные данные или защита от статического анализа.

Код:
Expand Collapse Copy
#define _CRT_SECURE_NO_WARNINGS
 
#include <Windows.h>
#include <winternl.h>
#include <process.h>
#include <TlHelp32.h>
#include <inttypes.h>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <chrono>
#include <sstream>
#include <memory>
#include <string_view>
#include <cstdint>
#include <string>
#include <cmath>
#include <thread>
#include <cassert>
#include <xstring>
#include <dwmapi.h>
#include <vector>
#include <map>
#include <array>
#include <fstream>
#include <direct.h>
#include <set>
#include <stack>
#include <unordered_set>
#include <wininet.h>
#include <random>
#include <winternl.h>
#include <Psapi.h>
#include <urlmon.h>
 
#include "driver.h"
 
#pragma comment(lib, "wininet.lib")
 
// Fallback NT header finder
uint64_t FindNtHeaders(uint64_t base, size_t maxSize = 0x1000000) {
    for (size_t i = 0; i < maxSize; i += 0x1000) {
        DWORD sig = Read<DWORD>(base + i);
        if (sig == IMAGE_NT_SIGNATURE)
            return base + i;
    }
    return 0;
}
 
// Safe memory reader
bool ReadSafe(uint64_t address, void* buffer, size_t size) {
    uint8_t* out = reinterpret_cast<uint8_t*>(buffer);
    for (size_t offset = 0; offset < size; offset += 0x1000) {
        size_t chunk = (size - offset < 0x1000) ? (size - offset) : 0x1000;
        if (read_physical(reinterpret_cast<PVOID>(address + offset), out + offset, (DWORD)chunk)) {
            // Fill unreadable pages with nulls to maintain alignment
            memset(out + offset, 0, chunk);
        }
    }
    return true;
}
 
// Entropy calculation
double CalculateEntropy(const uint8_t* data, size_t size) {
    if (!size) return 0.0;
    int freq[256]{};
 
    for (size_t i = 0; i < size; i++)
        freq[data[i]]++;
 
    double entropy = 0.0;
    for (int i = 0; i < 256; i++) {
        if (freq[i]) {
            double p = static_cast<double>(freq[i]) / size;
            entropy -= p * log2(p);
        }
    }
    return entropy;
}
 
// Relocations
bool FixRelocations(std::vector<uint8_t>& image, IMAGE_NT_HEADERS64& nt, uint64_t runtimeBase) {
    auto& dir = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    if (!dir.VirtualAddress || !dir.Size)
        return true;
 
    uint64_t delta = runtimeBase - nt.OptionalHeader.ImageBase;
    uint8_t* cur = image.data() + dir.VirtualAddress;
    uint8_t* end = cur + dir.Size;
 
    while (cur < end) {
        auto* block = reinterpret_cast<IMAGE_BASE_RELOCATION*>(cur);
        cur += sizeof(*block);
 
        size_t count = (block->SizeOfBlock - sizeof(*block)) / sizeof(WORD);
        WORD* entries = reinterpret_cast<WORD*>(cur);
 
        for (size_t i = 0; i < count; i++) {
            WORD type = entries[i] >> 12;
            WORD off = entries[i] & 0xFFF;
 
            if (type == IMAGE_REL_BASED_DIR64) {
                uint64_t* patch = reinterpret_cast<uint64_t*>(image.data() + block->VirtualAddress + off);
                *patch += delta;
            }
        }
        cur += count * sizeof(WORD);
    }
 
    nt.OptionalHeader.ImageBase = runtimeBase;
    return true;
}
 
// Fix Import Address Table (IAT) by copying original memory addresses
bool FixIAT(uint64_t base, std::vector<uint8_t>& image, IMAGE_NT_HEADERS64* nt) {
    auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    if (!dir.VirtualAddress || !dir.Size) return true;
 
    auto* imports = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(image.data() + dir.VirtualAddress);
 
    for (; imports->Name; imports++) {
        uint64_t firstThunkRVA = imports->FirstThunk;
        uint64_t thunkRVA = imports->OriginalFirstThunk ? imports->OriginalFirstThunk : imports->FirstThunk;
 
        for (int i = 0; ; i++) {
            uint64_t funcAddr = 0;
            uint64_t offset = i * sizeof(uint64_t);
 
            // Read the actual resolved address from the live process memory
            if (!read_physical(reinterpret_cast<PVOID>(base + firstThunkRVA + offset), &funcAddr, sizeof(funcAddr)))
                break;
 
            if (funcAddr == 0) break;
 
            // Patch the dump's memory with the live resolved address
            *reinterpret_cast<uint64_t*>(image.data() + firstThunkRVA + offset) = funcAddr;
        }
    }
    return true;
}
 
namespace Utils {
    double CalculateEntropy(const uint8_t* data, size_t size) {
        if (!size) return 0.0;
        int freq[256]{};
        for (size_t i = 0; i < size; i++) freq[data[i]]++;
        double entropy = 0.0;
        for (int i = 0; i < 256; i++) {
            if (freq[i]) {
                double p = static_cast<double>(freq[i]) / size;
                entropy -= p * log2(p);
            }
        }
        return entropy;
    }
}
 
// Dump module
bool DumpModule(uint64_t base, const std::string& outName) {
    IMAGE_DOS_HEADER dos{};
    if (!read_physical((PVOID)base, &dos, sizeof(dos)) || dos.e_magic != IMAGE_DOS_SIGNATURE)
        return false;
 
    IMAGE_NT_HEADERS64 nt{};
    if (!read_physical((PVOID)(base + dos.e_lfanew), &nt, sizeof(nt)))
        return false;
 
    // 1. Prepare Buffer (SizeOfImage is the size in memory)
    size_t imageSize = nt.OptionalHeader.SizeOfImage;
    std::vector<uint8_t> image(imageSize, 0);
 
    // 2. Read Headers
    ReadSafe(base, image.data(), nt.OptionalHeader.SizeOfHeaders);
 
    // 3. Read Sections
    uint64_t sectionHeaderAddr = base + dos.e_lfanew + sizeof(IMAGE_NT_HEADERS64);
    std::vector<IMAGE_SECTION_HEADER> sections(nt.FileHeader.NumberOfSections);
    read_physical((PVOID)sectionHeaderAddr, sections.data(), sections.size() * sizeof(IMAGE_SECTION_HEADER));
 
    for (auto& s : sections) {
        uint64_t sectionVA = base + s.VirtualAddress;
        uint32_t sectionSize = s.Misc.VirtualSize;
 
        std::cout << "[SEC] " << (char*)s.Name << " | VA: 0x" << std::hex << s.VirtualAddress << " | Size: 0x" << sectionSize;
 
        ReadSafe(sectionVA, image.data() + s.VirtualAddress, sectionSize);
 
        // Check for encryption/compression
        double entropy = Utils::CalculateEntropy(image.data() + s.VirtualAddress, sectionSize);
        std::cout << " | Entropy: " << std::dec << entropy << (entropy > 7.4 ? " [ENCRYPTED]" : "") << std::endl;
 
        // CRITICAL: Set PointerToRawData = VirtualAddress and SizeOfRawData = VirtualSize
        // This makes the file "Disk Layout" identical to "Memory Layout"
        auto* headerInDump = reinterpret_cast<IMAGE_SECTION_HEADER*>(image.data() + (sectionHeaderAddr - base) + (&s - &sections[0]) * sizeof(IMAGE_SECTION_HEADER));
        headerInDump->PointerToRawData = s.VirtualAddress;
        headerInDump->SizeOfRawData = s.Misc.VirtualSize;
    }
 
    // 4. Fix IAT (Optional but recommended for analysis)
    FixIAT(base, image, &nt);
 
    // 5. Write to File
    std::ofstream f(outName, std::ios::binary);
    if (f.is_open()) {
        f.write(reinterpret_cast<char*>(image.data()), image.size());
        f.close();
        return true;
    }
    return false;
}
 
void SetColor(WORD color) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
 
// ==================== Main ====================
int main() {
    SetConsoleTitleA("PE Memory Dumper");
 
    const WORD COLOR_SUCCESS = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
    const WORD COLOR_ERROR = FOREGROUND_RED | FOREGROUND_INTENSITY;
    const WORD COLOR_INFO = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; // cyan
 
    // Driver
    SetColor(COLOR_INFO);
    std::cout << "[*] Searching for driver...\n";
    if (!find_driver()) {
        SetColor(COLOR_ERROR);
        std::cerr << "[-] Failed to find driver. Make sure it is loaded!\n";
        system("pause"); return 1;
    }
    SetColor(COLOR_SUCCESS);
    std::cout << "[+] Driver connected!\n";
 
    // Process
    SetColor(COLOR_INFO);
    std::wcout << L"[*] Searching for process: r5apex_dx12.exe\n";
    if (!find_process("r5apex_dx12.exe")) {
        SetColor(COLOR_ERROR);
        std::cerr << "[-] Failed to find process. Is the game running?\n";
        system("pause"); return 1;
    }
    SetColor(COLOR_SUCCESS);
    std::cout << "[+] Process found!\n";
 
    // CR3
    SetColor(COLOR_INFO);
    std::cout << "[*] Getting CR3...\n";
    uint64_t cr3 = CR3();
    if (!cr3) { SetColor(COLOR_ERROR); std::cerr << "[-] Failed to get CR3!\n"; system("pause"); return 1; }
    SetColor(COLOR_SUCCESS);
    std::cout << "[+] CR3 obtained: 0x" << std::hex << cr3 << std::dec << "\n";
 
    // Module base
    SetColor(COLOR_INFO);
    std::cout << "[*] Finding module base...\n";
    uint64_t moduleBase = find_image();
    if (!moduleBase) { SetColor(COLOR_ERROR); std::cerr << "[-] Failed to find module base!\n"; system("pause"); return 1; }
 
    IMAGE_DOS_HEADER dos{};
    if (!read_physical((PVOID)moduleBase, &dos, sizeof(dos)) || dos.e_magic != IMAGE_DOS_SIGNATURE) {
        SetColor(COLOR_ERROR); std::cerr << "[-] Failed to read DOS header!\n"; system("pause"); return 1;
    }
    IMAGE_NT_HEADERS64 nt{};
    if (!read_physical((PVOID)(moduleBase + dos.e_lfanew), &nt, sizeof(nt)) || nt.Signature != IMAGE_NT_SIGNATURE) {
        SetColor(COLOR_ERROR); std::cerr << "[-] Failed to read NT headers!\n"; system("pause"); return 1;
    }
    uint64_t moduleSize = nt.OptionalHeader.SizeOfImage;
 
    SetColor(COLOR_SUCCESS);
    std::cout << "[+] Module base: 0x" << std::hex << moduleBase
        << "  Size: 0x" << moduleSize << std::dec << "\n";
 
    // Dump module
    std::vector<uint8_t> dumpedImage;
    SetColor(COLOR_INFO);
    std::cout << "[*] Dumping module...\n";
    if (!DumpModule(moduleBase, "game_dumped.exe")) {
        SetColor(COLOR_ERROR); std::cerr << "[-] Dump failed!\n"; system("pause"); return 1;
    }
    SetColor(COLOR_SUCCESS);
    std::cout << "[+] Dump completed successfully!\n";
 
    SetColor(COLOR_SUCCESS);
    std::cout << "[*] Press Enter to exit...\n";
    std::cin.get();
 
    SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    return 0;
}

Важные замечания:
  1. Безопасность: Юзайте только на твинках. Любой прямой доступ к памяти через кастомный драйвер — это потенциальный триггер для античита, если ваш драйвер не андетект (UD) или вы не используете DMA-карту.
  2. Зависимости: Не забудьте подкинуть актуальные заголовки и убедиться, что у вас установлен VCRedist.
  3. Профит: Не надейтесь на кнопки "сделать все за меня". Это лишь инструмент для исследования, а не готовый чит.

Пока остальные ловят баны от кривых бесплатных инжекторов с ютуба или горят от потных киберспортсменов, юзеры YouGame юзают проверенные решения, копаются в сурсах, доминируют на сервере и сохраняют свои аккаунты.

Ссылка на гитхаб проекта:
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Дамп всегда будет неполным, некоторые участки кода вызываются в определенный момент и пока они не будут вызваны - код будет забит нулями.
 
1774285761783.png
мне кажется это чутка не так работает
ты же тупо с соседнего форума перезалил тему
 
Последнее редактирование:
Назад
Сверху Снизу