C++ CRC Koruma: CRC32 ile Uygulama Bütünlüğü Kontrolü

C++ CRC koruma, Visual Studio ile derlenen bir uygulamanın değiştirilip değiştirilmediğini hızlıca anlamanın pratik yoludur. Bu rehberde CRC32 ile .text bütünlük kontrolünü adım adım kuracağız. Amaç “kırılamaz bir kilit” değil; bozulma, yanlış sürüm ve basit patch gibi durumları erkenden fark etmektir.

CRC nedir, ne işe yarar?

  • CRC (Cyclic Redundancy Check), bir veri bloğundan kısa bir kontrol değeri üretir.
  • Veri değişirse CRC de büyük olasılıkla değişir. Böylece değişiklik/bozulma tespiti yaparsın.
  • CRC kriptografik değildir; hızlı ve pratik bir bütünlük kontrolü yaklaşımıdır.

C++ CRC Koruma Mantığı (CRC32 ile bütünlük kontrolü)

Temel fikir basit: Uygulamanın “beklenen” bir CRC değeri olur. Program açılırken kendi dosyasının (veya belirli bir bölümünün) CRC’sini hesaplar ve beklenen değerle karşılaştırır. Uyuşmazlık varsa “dosya değişmiş olabilir” diye tepki verir.

Bu noktada C++ CRC koruma yaklaşımı, özellikle dosya bozulması ve basit patch’leri erkenden yakalamayı hedefler.

Önemli

CRC, tek başına “anti-crack” değildir. Buradaki amaç; bozulma, yanlış sürüm ve istenmeyen değişiklik gibi durumları hızlıca yakalamaktır.

Neyi CRC’lemek daha mantıklı?

İki yaygın yaklaşım vardır:

Yaklaşım Artısı Eksisi
Tüm EXE/DLL dosyasını CRC’lemek Uygulaması çok basit “Beklenen CRC” değeri dosyanın içine yazılıysa, kendi kendine referans problemi çıkar (CRC değişir). İmzalama/paketleme gibi süreçler CRC’yi etkileyebilir.
Sadece kritik bölümü CRC’lemek (örn. .text) Kod değişikliklerini hedefli yakalar, daha stabil PE section okuma/parçalama gerekir (biraz daha uzun kod)

Bu yazıda pratikte daha sağlıklı olduğu için .text (kod) bölümünü CRC32 ile doğrulama yaklaşımını kullanıyoruz.

Visual Studio’da proje kurulumu

  1. Console App (C++) oluştur.
  2. Platform: x64 seç (güncel Windows’ta yaygın).
  3. Language Standard: C++20 (veya C++17).
  4. Tek dosya olarak crc_integrity.cpp ekle ve aşağıdaki kodu yapıştır.

C++ CRC Koruma için .text CRC32 doğrulaması

Aşağıdaki örnek, C++ CRC koruma için CRC32 tabanlı bir bütünlük kontrolü uygular:

  • Kendi EXE yolunu bulur (GetModuleFileNameW).
  • Dosyayı memory-map eder (hızlı okuma).
  • PE header’dan .text section’ını bulur.
  • .text verisinin CRC32’sini hesaplar ve beklenen değerle karşılaştırır.
#define NOMINMAX
#include <Windows.h>

#include <cstdint>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

// ------------------------------------------------------------
// CRC32 (Table-based) - hızlı ve yaygın
// ------------------------------------------------------------
static uint32_t g_crc32_table[256];
static bool g_crc32_ready = false;

static void crc32_init() {
    for (uint32_t i = 0; i < 256; ++i) {
        uint32_t c = i;
        for (int k = 0; k < 8; ++k) {
            c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
        }
        g_crc32_table[i] = c;
    }
    g_crc32_ready = true;
}

static uint32_t crc32_update(uint32_t crc, const uint8_t* data, size_t len) {
    if (!g_crc32_ready) crc32_init();
    crc = ~crc;
    for (size_t i = 0; i < len; ++i) {
        crc = g_crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
    }
    return ~crc;
}

// ------------------------------------------------------------
// Küçük RAII HANDLE sarmalayıcı
// ------------------------------------------------------------
class unique_handle {
public:
    unique_handle() noexcept = default;
    explicit unique_handle(HANDLE h) noexcept : h_(h) {}
    ~unique_handle() { reset(); }

    unique_handle(const unique_handle&) = delete;
    unique_handle& operator=(const unique_handle&) = delete;

    unique_handle(unique_handle&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
    unique_handle& operator=(unique_handle&& other) noexcept {
        if (this != &other) {
            reset();
            h_ = other.h_;
            other.h_ = nullptr;
        }
        return *this;
    }

    HANDLE get() const noexcept { return h_; }
    explicit operator bool() const noexcept { return h_ && h_ != INVALID_HANDLE_VALUE; }

    void reset(HANDLE h = nullptr) noexcept {
        if (h_ && h_ != INVALID_HANDLE_VALUE) CloseHandle(h_);
        h_ = h;
    }

private:
    HANDLE h_ = nullptr;
};

// ------------------------------------------------------------
// Dosyayı memory-map et
// ------------------------------------------------------------
struct mapped_file {
    unique_handle file;
    unique_handle mapping;
    const std::byte* data = nullptr;
    size_t size = 0;

    mapped_file() = default;
    ~mapped_file() { reset_view(); }

    mapped_file(const mapped_file&) = delete;
    mapped_file& operator=(const mapped_file&) = delete;

    mapped_file(mapped_file&& other) noexcept
        : file(std::move(other.file)),
          mapping(std::move(other.mapping)),
          data(other.data),
          size(other.size)
    {
        other.data = nullptr;
        other.size = 0;
    }

    mapped_file& operator=(mapped_file&& other) noexcept {
        if (this != &other) {
            reset_view();
            mapping = std::move(other.mapping);
            file = std::move(other.file);
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }

private:
    void reset_view() noexcept {
        if (data) {
            UnmapViewOfFile(data);
            data = nullptr;
        }
        size = 0;
    }
};

static mapped_file map_whole_file(const std::filesystem::path& p) {
    mapped_file mf;

    mf.file.reset(CreateFileW(p.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
    if (!mf.file) throw std::runtime_error("CreateFileW failed");

    LARGE_INTEGER li{};
    if (!GetFileSizeEx(mf.file.get(), &li) || li.QuadPart <= 0)
        throw std::runtime_error("GetFileSizeEx failed");

    mf.size = static_cast<size_t>(li.QuadPart);

    mf.mapping.reset(CreateFileMappingW(mf.file.get(), nullptr, PAGE_READONLY, 0, 0, nullptr));
    if (!mf.mapping) throw std::runtime_error("CreateFileMappingW failed");

    void* view = MapViewOfFile(mf.mapping.get(), FILE_MAP_READ, 0, 0, 0);
    if (!view) throw std::runtime_error("MapViewOfFile failed");

    mf.data = reinterpret_cast<const std::byte*>(view);
    return mf;
}

template<typename T>
static const T* view_as(const std::byte* base, size_t size, size_t offset) {
    if (offset > size || (size - offset) < sizeof(T)) return nullptr;
    return reinterpret_cast<const T*>(base + offset);
}

static std::filesystem::path get_own_exe_path() {
    wchar_t buf[MAX_PATH]{};
    DWORD n = GetModuleFileNameW(nullptr, buf, MAX_PATH);
    if (n == 0 || n >= MAX_PATH) return {};
    return std::filesystem::path(std::wstring(buf, buf + n));
}

// ------------------------------------------------------------
// Beklenen CRC değerini .rdata tarafında tutup memory'den okutuyoruz.
// Not: "volatile" kullanmak, derleyicinin bu değeri immediate'a çevirmesini zorlaştırır.
// ------------------------------------------------------------
alignas(4) static volatile uint32_t g_expected_text_crc = 0x00000000u;

// ------------------------------------------------------------
// .text section CRC hesapla
// ------------------------------------------------------------
static bool compute_text_crc(const std::filesystem::path& exePath, uint32_t& outCrc) {
    auto mf = map_whole_file(exePath);

    const auto* dos = view_as<IMAGE_DOS_HEADER>(mf.data, mf.size, 0);
    if (!dos || dos->e_magic != IMAGE_DOS_SIGNATURE) return false;

    const size_t nt_off = static_cast<size_t>(dos->e_lfanew);
    const auto* sig = view_as<DWORD>(mf.data, mf.size, nt_off);
    if (!sig || *sig != IMAGE_NT_SIGNATURE) return false;

    const auto* fileHdr = view_as<IMAGE_FILE_HEADER>(mf.data, mf.size, nt_off + sizeof(DWORD));
    if (!fileHdr) return false;

    const size_t opt_off = nt_off + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER);
    const size_t sec_off = opt_off + fileHdr->SizeOfOptionalHeader;

    // Section table
    for (WORD i = 0; i < fileHdr->NumberOfSections; ++i) {
        const auto* sh = view_as<IMAGE_SECTION_HEADER>(mf.data, mf.size, sec_off + i * sizeof(IMAGE_SECTION_HEADER));
        if (!sh) return false;

        char name[9]{}; // 8 byte + null
        memcpy(name, sh->Name, 8);

        if (std::string_view(name) == ".text") {
            const size_t raw = sh->PointerToRawData;
            const size_t rawSize = sh->SizeOfRawData;

            if (raw == 0 || rawSize == 0) return false;
            if (raw > mf.size || (mf.size - raw) < rawSize) return false;

            const auto* bytes = reinterpret_cast<const uint8_t*>(mf.data + raw);
            outCrc = crc32_update(0u, bytes, rawSize);
            return true;
        }
    }

    return false;
}

// ------------------------------------------------------------
// Bütünlük kontrolü
// ------------------------------------------------------------
static bool verify_self_integrity() {
    const auto exe = get_own_exe_path();
    if (exe.empty()) return false;

    uint32_t crc = 0;
    if (!compute_text_crc(exe, crc)) return false;

    // volatile read
    const uint32_t expected = g_expected_text_crc;
    return (expected != 0u) && (crc == expected);
}

// ------------------------------------------------------------
// Kullanım:
// 1) İlk build'te g_expected_text_crc 0 iken çalıştırırsan FAIL verir.
// 2) --print-text-crc ile çalıştır: .text CRC'yi yazdırır.
// 3) Yazdırılan değeri g_expected_text_crc içine koy, tekrar build et.
// ------------------------------------------------------------
int wmain(int argc, wchar_t** argv) {
    try {
        const auto exe = get_own_exe_path();
        if (exe.empty()) {
            std::wcerr << L"[!] EXE path not found.\n";
            return 1;
        }

        if (argc >= 2 && std::wstring_view(argv[1]) == L"--print-text-crc") {
            uint32_t crc = 0;
            if (!compute_text_crc(exe, crc)) {
                std::wcerr << L"[!] CRC compute failed.\n";
                return 1;
            }
            std::wcout << L".text CRC32 = 0x" << std::hex << crc << std::dec << L"\n";
            std::wcout << L"Bu değeri g_expected_text_crc alanına yazıp tekrar build et.\n";
            return 0;
        }

        if (!verify_self_integrity()) {
            std::wcerr << L"[!] Integrity check FAILED: Kod bölümü değişmiş olabilir.\n";
            std::wcerr << L"İpucu: --print-text-crc ile CRC değerini yazdır.\n";
            return 1;
        }

        std::wcout << L"[OK] Integrity check PASSED.\n";
        // ... uygulamanın asıl işi ...
        return 0;
    }
    catch (const std::exception& ex) {
        std::wcerr << L"[!] Exception: " << ex.what() << L"\n";
        return 1;
    }
}

Beklenen CRC değeri nasıl üretilir?

Beklenen değer .rdata tarafında tutulduğu için, bu değeri güncellemek genellikle .text CRC’sini bozmaz. Bu da “kısır döngü” riskini azaltır.

  1. Projeyi bir kez derle (ilk haliyle, g_expected_text_crc = 0 iken).
  2. EXE’yi şu argümanla çalıştır: --print-text-crc
  3. Konsolda yazan değeri al, örneğin 0x3A12F9C4.
  4. Bu değeri koda yapıştır: g_expected_text_crc = 0x3A12F9C4u;
  5. Tekrar derle ve normal çalıştır. Kontrol “OK” dönecektir.
yourapp.exe --print-text-crc

C++ CRC Koruma: Sınırlar ve iyi pratikler

C++ CRC koruma tek başına kırılmaz değildir; ancak dağıtım hataları ve temel müdahaleleri tespit etmekte etkilidir. CRC32 hızlıdır ve uygulamaya entegrasyonu kolaydır. İhtiyaca göre kontrolü güçlendirmek mümkündür.

İyi pratikler
  • Kontrolü sadece açılışta değil, kritik akışlarda da (ör. lisans kontrolünden önce) yapabilirsin.
  • .text yanında seçili başka bölümleri de doğrulayabilirsin (kritik tablolar gibi).
  • Gerektiğinde CRC’ye ek olarak imza doğrulama veya kriptografik hash yaklaşımı değerlendirilebilir.

İlginizi çekebilecek diğer konular

Yorum Bırakın

E-posta adresiniz yayınlanmayacaktır. Zorunlu alanlar * ile işaretlenmiştir