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.
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
- Console App (C++) oluştur.
- Platform: x64 seç (güncel Windows’ta yaygın).
- Language Standard: C++20 (veya C++17).
- Tek dosya olarak
crc_integrity.cppekle 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
.textsection’ını bulur. .textverisinin 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.
- Projeyi bir kez derle (ilk haliyle,
g_expected_text_crc = 0iken). - EXE’yi şu argümanla çalıştır:
--print-text-crc - Konsolda yazan değeri al, örneğin
0x3A12F9C4. - Bu değeri koda yapıştır:
g_expected_text_crc = 0x3A12F9C4u; - 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.
- Kontrolü sadece açılışta değil, kritik akışlarda da (ör. lisans kontrolünden önce) yapabilirsin.
.textyanı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.