Гайд Насущный CRC

ANTICHEAT_OBFUSCATE_CODEMARKER
Пользователь
Статус
Оффлайн
Регистрация
2 Июл 2020
Сообщения
136
Реакции[?]
279
Поинты[?]
109K
СОДЕРЖАНИЕ:​

Введение
1. CRC секций
2. CRC file
3. CRC disk
4. CRC static
Заключение
Введение

⠀⠀⠀⠀⠀Зачастую при реверсе программ под протекторами, да и просто самописной защитой, часто встречается проверки целостности. Не смотря на проблему, мало кто делится информацией, а тем более пытается понять смысл защиты и реализовать для себя её логику. Довольно забавно,но некоторые протекторы реализуют лишь небольшую часть проверок целостности(проверку одной секции при вызове SDK API или проверка только кода протектора с обфусцированным кодом, но не код без обфускации).
⠀⠀⠀⠀⠀Уже были разобраны обходы проверки целостности у протекторов в другой статье, поэтому предлагаю рассмотреть с противоположного угла саму проблему т.е как реализуется сами проверки целостности. Приведённый код в статье не будут очень большими и это просто пример для начинающих реверсеров. Заранее извиняюсь за не супер хороший стиль кода(если будет что-то непонятно, то сверху перед кодом будет написана логики реализации или можете написать под комментарием сам вопрос).
⠀⠀⠀⠀⠀Целью статьи является изучение основных проверок целостности кода и их реализация, включая их достоинства или недостатки. Так же будет сказано про уловки или какую головную боль может вызвать у реверсера сочетания проверок целостности кода.



1.CRC секций(runtime)

⠀⠀⠀⠀⠀Самое простое в реализации – CRC проверка секций. Обычно используют только как самописную защиту. Нас интересует пройтись по всем секциям нашего модуля и проверить Characteristics на IMAGE_SCN_MEM_READ и !IMAGE_SCN_MEM_WRITE. Дальше мы можем получить crc ,основываясь на VirtualAddress и VirtualSize:

⠀⠀⠀⠀⠀Размер секции можно по желанию выровнять по SectionAlignment,чтобы не оставлять выровненный размер без проверки,но это необязательно.
Само получение можно реализовать следующим образом:
C++:
NO_INLINE auto add_crc_module(PVOID addr_mod, BOOLEAN is_copy = FALSE, BOOLEAN steals_check = FALSE) -> BOOLEAN
{
     BOOLEAN is_any_add = FALSE;
    PIMAGE_NT_HEADERS nt_headers = NULL;
    PIMAGE_SECTION_HEADER sec = NULL;
    CRC_INFO crc_info = { NULL };

    nt_headers = reinterpret_cast<IMAGE_NT_HEADERS*>(reinterpret_cast<CHAR*>(addr_mod) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_mod)->e_lfanew);
    if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
        return is_any_add;
    sec = IMAGE_FIRST_SECTION(nt_headers);

    for (uint16_t i = NULL, check_sec = NULL; i <= nt_headers->FileHeader.NumberOfSections; i++)
    {
        if ((sec[i].Characteristics & IMAGE_SCN_MEM_READ) && !(sec[i].Characteristics & IMAGE_SCN_MEM_WRITE))
        {
             //flag
             crc_info.crc_is_copy = is_copy;
             crc_info.crc_steals_check = steals_check;

             //info
            crc_info.crc_addr = reinterpret_cast<uint8_t*>(addr_mod) + sec[i].VirtualAddress;
            crc_info.crc_size =  sec[i].Misc.VirtualSize;
             crc_info.crc_coped_mem = NULL;
            

             if (crc_info.crc_is_copy || crc_info.crc_steals_check)
             {
                 crc_info.crc_coped_mem = reinterpret_cast<uint8_t*>(VirtualAlloc(NULL, crc_info.crc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
                 if (crc_info.crc_coped_mem)
                 {

                     if (!ReadProcessMemory(NtCurrentProcess, crc_info.crc_addr, crc_info.crc_coped_mem, crc_info.crc_size, NULL))
                     {
                         VirtualFree(crc_info.crc_coped_mem, NULL, MEM_RELEASE);
                         crc_info.crc_coped_mem = NULL;
                     }
                     else
                     {
                         //short time change(we init)
                         crc_info.crc_addr = crc_info.crc_coped_mem;
                         crc_info.crc_res = crc32(&crc_info);
                         crc_info.crc_addr = reinterpret_cast<uint8_t*>(addr_mod) + sec[i].VirtualAddress;
                         is_any_add = TRUE;
                     }

                 }
             }
          
             if (!crc_info.crc_steals_check)
             {
                 crc_info.crc_res = crc32(&crc_info);
                 is_any_add = TRUE;
             }
            crc_list.push_back(crc_info);
        }

    }
     return is_any_add;
}
И сама проверка:
C++:
NO_INLINE  auto sec_check() -> BOOLEAN
{
    uint8_t* crc_coped = NULL;
    CRC_INFO crc_info = { NULL };

    for (size_t i = 0; i < crc_list.size(); i++)
    {
        if (crc_list[i].crc_steals_check)
        {
            memcpy(&crc_info, &crc_list[i], sizeof(crc_info));

            crc_coped = reinterpret_cast<uint8_t*>(VirtualAlloc(NULL, crc_info.crc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
            if (crc_coped)
            {
                if (ReadProcessMemory(NtCurrentProcess, crc_info.crc_addr, crc_coped, crc_info.crc_size, NULL))
                {
                    crc_info.crc_addr = crc_coped;
                    if (crc32(&crc_info) != crc_list[i].crc_res)
                    {
                        if (crc_list[i].crc_is_copy && crc_list[i].crc_coped_mem)
                        {
                            crc_unhook_code(&crc_list[i]);
                        }
                        return TRUE;
                    }
                }
                else
                {
                    if (crc32(&crc_list[i]) != crc_list[i].crc_res)
                    {
                        if (crc_list[i].crc_is_copy && crc_list[i].crc_coped_mem)
                        {
                            crc_unhook_code(&crc_list[i]);
                        }
                        return TRUE;
                    }
                }
                VirtualFree(crc_coped, NULL, MEM_RELEASE);
            }
            else
            {
                if (crc32(&crc_list[i]) != crc_list[i].crc_res)
                {
                    if (crc_list[i].crc_is_copy && crc_list[i].crc_coped_mem)
                    {
                        crc_unhook_code(&crc_list[i]);
                    }
                    return TRUE;
                }
            }
        }
        else
        {
            if (crc32(&crc_list[i]) != crc_list[i].crc_res)
            {
                if (crc_list[i].crc_is_copy && crc_list[i].crc_coped_mem)
                {
                    crc_unhook_code(&crc_list[i]);
                }
                return TRUE;
            }
        }
    }
    return FALSE;
}

Из плюсов:
Возможность произвести unhook байтов(скопированные байты лучше хранить в зашифрованном виде или сжать их).
Можно спокойно реализовать скрытное чтение,чтобы избежать быстрое обнаружение тем же HWBP.
Имеет смысл использовать с сочетанием SDK протектора,поскольку некоторые моменты могут перекрывать друг друга(приводить примера не буду,но подумайте сами).
Из минусов:
Получение происходит после вызова OEP.
Много способов обойти:уменьшить количество секций, подмена. Characteristics, подмена VirtualAddress(например,на одну и ту же секцию,которую мы не изменяем) или VirtualSize(уменьшить до NULL) и т.д
Хукнуть начала получения и вернуть адрес скопированного модуля.


2.CRC file(static)

⠀⠀⠀⠀⠀Проверка целостности файла на диске – пожалуй, можно назвать, наиболее распространённой проверкой у протекторов. Единственный плюс такой проверки – обнаружение статического патча бинарника и … на этом всё.
⠀⠀⠀⠀⠀Сама проверка не супер сложна в реализации и приводится пример с учётом изменения только данных в секциях бинарника,но без проверки самого PE(SizeOfHeaders),но можете это сами исправить т.к делается относительно легко. Можно сделать подсчёт по адресу(PointerToRawData) и размеру(SizeOfRawData) секций на диске или от начала расчёта(sec[NULL].PointerToRawData) до конца самого размера (с учётом игнорирования памяти, куда будет сохранять результат).Если место куда сохраняем начинается с 1 байт 1 секции,то нужно пропустить эти байти и тогда у нас будет 1 crc res, иначе 2.
Сам код подсчёта:
Код:
auto disk_calc_file_crc(PVOID addr_file,uint32_t size_file, uint8_t* addr_pattern) -> BOOLEAN
{
    PIMAGE_NT_HEADERS nt_headers = NULL;
    PIMAGE_SECTION_HEADER sec = NULL;
    CRC_FILE_INFO crc_file_info = {NULL};

    nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_file) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_file)->e_lfanew);
    if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
        return FALSE;

    sec = IMAGE_FIRST_SECTION(nt_headers);
  

    if (addr_pattern - addr_file == nt_headers->OptionalHeader.SizeOfHeaders)//skip pe and next start sec - pattern
    {
        crc_file_info.crc_reg[NULL].crc_addr_rva = nt_headers->OptionalHeader.SizeOfHeaders + sizeof(crc_file_info);
        crc_file_info.crc_reg[NULL].crc_size = size_file - nt_headers->OptionalHeader.SizeOfHeaders - sizeof(crc_file_info);
        crc_file_info.crc_reg[NULL].crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + crc_file_info.crc_reg[NULL].crc_addr_rva, crc_file_info.crc_reg[NULL].crc_size);
      
        crc_file_info.crc_num = 1;
    }
    else
    {
        crc_file_info.crc_reg[NULL].crc_addr_rva = nt_headers->OptionalHeader.SizeOfHeaders;
        crc_file_info.crc_reg[NULL].crc_size = (addr_pattern - addr_file) - nt_headers->OptionalHeader.SizeOfHeaders;
        crc_file_info.crc_reg[NULL].crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + crc_file_info.crc_reg[NULL].crc_addr_rva, crc_file_info.crc_reg[NULL].crc_size);

        crc_file_info.crc_reg[1].crc_addr_rva = (addr_pattern - addr_file)  + sizeof(crc_file_info);
        crc_file_info.crc_reg[1].crc_size = size_file - (addr_pattern - addr_file)  - sizeof(crc_file_info);
        crc_file_info.crc_reg[1].crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + crc_file_info.crc_reg[1].crc_addr_rva, crc_file_info.crc_reg[1].crc_size);

        crc_file_info.crc_num = 2;
    }

    reinterpret_cast<CRC_FILE_INFO*>(addr_pattern)->crc_num = crc_file_info.crc_num;

    addr_pattern += sizeof(uint32_t);

    for (size_t i = 0; i < crc_file_info.crc_num; i++)
    {
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_addr_rva = crc_file_info.crc_reg[i].crc_addr_rva;
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_size = crc_file_info.crc_reg[i].crc_size;
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_res = crc_file_info.crc_reg[i].crc_res;

        addr_pattern += sizeof(CRC_INFO_FILE);
    }
    return TRUE;
}

Сама проверка:
C++:
CRC_FILE_INFO crc_res_table = {0xBEEF,2,0XDEADC0DE};

auto crc_file_check(PVOID addr_file) -> BOOLEAN
{
    PIMAGE_NT_HEADERS nt_headers = NULL;
    PIMAGE_SECTION_HEADER sec = NULL;
    
    nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_file) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_file)->e_lfanew);
    sec = IMAGE_FIRST_SECTION(nt_headers);

    //rva hear is va
    for (size_t i = 0; i < crc_res_table.crc_num; i++)
    {
        if (crc32(reinterpret_cast<uint8_t*>(addr_file) + crc_res_table.crc_reg[i].crc_addr_rva, crc_res_table.crc_reg[i].crc_size) != crc_res_table.crc_reg[i].crc_res)
        {
            return FALSE;
        }
    }
    return TRUE;
}

auto  is_file_valid(PVOID mod_address) -> BOOLEAN
{
    BOOLEAN is_valid = TRUE;
    uint32_t old_size_file = NULL;
    DWORD num_read = NULL;
    PVOID memory_module = NULL;
    PVOID memory_file = NULL;
    HANDLE access_file = NULL;
    WCHAR file_path[MAX_PATH] = { NULL };

  
    if (mod_address && GetModuleFileNameW(reinterpret_cast<HMODULE>(mod_address), file_path, sizeof(file_path)))
    {
        access_file = CreateFileW(file_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if (access_file != INVALID_HANDLE_VALUE)
        {
            old_size_file = GetFileSize(access_file, NULL);
            if (old_size_file)
            {
                memory_file = malloc(old_size_file);
                if (memory_file)
                {
                    memset(memory_file, NULL, old_size_file);
                    if (ReadFile(access_file, memory_file, old_size_file, &num_read, NULL))
                    {
                        is_valid = crc_file_check(memory_file);
                    }
                    free(memory_file);
                }
            }
            CloseHandle(access_file);
        }
    }
    return is_valid;
}

Зачастую для такой проверки достаточно просто подменить путь на оригинальный скопированный файл при обращении к файлам на диске(как было с обходом Themida
Пожалуйста, авторизуйтесь для просмотра ссылки.
из прошлой статьи).

Из плюсов:
Статический детект патча байтов в бинарнике.
Можно совмещать с другим примеров(слегка ниже),чтобы слегка усложнит простую подделку с путём подделки на скопированный бинарник.
Из минусов:
Легко обходится.
Требует загрузку файла с диска
Можно игнорировать, если используете динамический патч(играть честно,когда все играют краплёными картами – задумка не очень).


3.CRC disk(runtime)

⠀⠀⠀⠀⠀Можно не пытаться устраивать танцы с бубнами и просто загрузить файл с диска, пофиксить все данные (обычно достаточно исправить IMAGE_DIRECTORY_ENTRY_BASERELOC и IMAGE_DIRECTORY_ENTRY_IMPORT), которые изменяются при загрузки бинарника и сравнить с текущей памятью(можно сказать,просто частичное повторение manual map injector). Можно совместить с CRC file и это не позволит лениво обойти это(подмена файла( т.к память будет отличаться,если просто подменить путь к оригинальному файлу. Нужно проверять только секции с Characteristics на IMAGE_SCN_MEM_READ и !IMAGE_SCN_MEM_WRITE. Для файла с диска проверяемосновываясь на PointerToRawData,а на файле VirtualAddress из-за выравнивания PE и секций кода,но должны использовать SizeOfRawData. Протекторы не используют данную технику,но некоторые AC используют данный трюк.
Реализация довольно тривиальна:
C++:
auto disk_fix(PVOID addr_file, PVOID addr_mod) -> BOOLEAN
{
    uint32_t end_reloce = NULL;
    uint64_t delta_fix = NULL;
    HMODULE base_imp_dll = NULL;

    CHAR* name_imp_dll = NULL;
    uint16_t* relative_info = NULL;
    uint64_t* orig_first_thunk = NULL;
    uint64_t* first_thunk = NULL;

    PIMAGE_NT_HEADERS nt_headers = NULL;
    PIMAGE_BASE_RELOCATION va_reloce = NULL;
    PIMAGE_BASE_RELOCATION size_reloce = NULL;
    PIMAGE_SECTION_HEADER sec = NULL;
    PIMAGE_IMPORT_DESCRIPTOR imp_descript = NULL;
    PIMAGE_IMPORT_BY_NAME import_name = NULL;

    nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_file) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_file)->e_lfanew);
    if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
        return FALSE;

    sec = IMAGE_FIRST_SECTION(nt_headers);

    delta_fix = reinterpret_cast<uint64_t>(addr_mod) - nt_headers->OptionalHeader.ImageBase;
    if ( delta_fix)
    {
        if (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
        {
            va_reloce = reinterpret_cast<PIMAGE_BASE_RELOCATION>(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress, nt_headers,sec));
            size_reloce = reinterpret_cast<PIMAGE_BASE_RELOCATION>(reinterpret_cast<uintptr_t>(va_reloce) + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size);

            while (va_reloce < size_reloce && va_reloce->SizeOfBlock)
            {
                end_reloce = (va_reloce->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
                relative_info = reinterpret_cast<uint16_t*>(va_reloce + 1);

                for (uint32_t i = NULL; i != end_reloce; ++i, ++relative_info)
                {
                    switch ((*relative_info) >> SHIFT_RELOCE)//https://github.com/DarthTon/Blackbone/blob/5ede6ce50cd8ad34178bfa6cae05768ff6b3859b/src/BlackBoneDrv/ldrreloc.c#L252
                    {
                    case IMAGE_REL_BASED_DIR64:
                    {
                        *reinterpret_cast<uint64_t*>(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(va_reloce->VirtualAddress, nt_headers, sec) + ((*relative_info) & 0xFFF)) += delta_fix;
                        break;
                    }
                    case IMAGE_REL_BASED_HIGHLOW:
                    {
                        *reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(va_reloce->VirtualAddress, nt_headers, sec) + ((*relative_info) & 0xFFF)) += delta_fix;
                        break;
                    }
                    case IMAGE_REL_BASED_LOW:
                    {
                        *reinterpret_cast<uint16_t*>(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(va_reloce->VirtualAddress, nt_headers, sec) + ((*relative_info) & 0xFFF)) += delta_fix;
                        break;
                    }
                    case IMAGE_REL_BASED_ABSOLUTE:
                    {
                        break;
                    }
                    default:
                    {
                        return FALSE;
                        break;
                    }
                    }

                }
                va_reloce = reinterpret_cast<PIMAGE_BASE_RELOCATION>(reinterpret_cast<BYTE*>(va_reloce) + va_reloce->SizeOfBlock);
            }
        }
      
        if (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)
        {
                imp_descript = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(reinterpret_cast<CHAR*>(addr_file) + rva_to_va(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, nt_headers, sec));
                for (; imp_descript->Name; ++imp_descript)
                {
                    name_imp_dll = reinterpret_cast<CHAR*>(addr_file) + rva_to_va(imp_descript->Name,nt_headers,sec);
                    if (name_imp_dll)
                    {
                        base_imp_dll = LoadLibraryA(name_imp_dll);
                        if (base_imp_dll)
                        {

                            orig_first_thunk = reinterpret_cast<uint64_t*>(reinterpret_cast<CHAR*>(addr_file) + rva_to_va(imp_descript->OriginalFirstThunk,nt_headers,sec));
                            first_thunk = reinterpret_cast<uint64_t*>(reinterpret_cast<CHAR*>(addr_file) + rva_to_va(imp_descript->FirstThunk, nt_headers, sec));

                            if (!orig_first_thunk) //load by index https://stackoverflow.com/questions/42413937/why-pe-need-original-first-thunkoft
                            {
                                orig_first_thunk = first_thunk;
                            }

                            for (; *orig_first_thunk; orig_first_thunk++, first_thunk++)
                            {

                                if (IMAGE_SNAP_BY_ORDINAL(*orig_first_thunk))//load by index?
                                {
                                    *first_thunk = reinterpret_cast<uint64_t>(GetProcAddress(base_imp_dll, reinterpret_cast<CHAR*>(IMAGE_ORDINAL(*orig_first_thunk))));
                                }
                                else
                                {

                                    import_name = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(reinterpret_cast<CHAR*>(addr_file) + rva_to_va(*orig_first_thunk,nt_headers,sec));
                                    if (import_name->Name)
                                    {
                                        *first_thunk = reinterpret_cast<uint64_t>(GetProcAddress(base_imp_dll, import_name->Name));
                                    }
                                }
                            }
                        }
                    }
                }
        }
    }
  
    return TRUE;
}

auto disk_compare(PVOID addr_file, PVOID addr_mod) -> BOOLEAN
{
    PIMAGE_NT_HEADERS nt_headers_disk = NULL;
    PIMAGE_SECTION_HEADER sec_disk = NULL;

    PIMAGE_NT_HEADERS nt_headers_memory = NULL;
    PIMAGE_SECTION_HEADER sec_memory = NULL;

    nt_headers_disk = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_file) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_file)->e_lfanew);
    nt_headers_memory = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_mod) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_mod)->e_lfanew);

  
    if
    (
        nt_headers_disk->Signature != IMAGE_NT_SIGNATURE ||
        nt_headers_memory->Signature != IMAGE_NT_SIGNATURE ||
        nt_headers_disk->FileHeader.NumberOfSections != nt_headers_memory->FileHeader.NumberOfSections
    )
    {
        return FALSE;
    }
    sec_disk = IMAGE_FIRST_SECTION(nt_headers_disk);
    sec_memory = IMAGE_FIRST_SECTION(nt_headers_memory);


    for (uint16_t i = NULL, check_sec = NULL; i <= nt_headers_disk->FileHeader.NumberOfSections; i++)
    {
        if (sec_disk->Characteristics != sec_memory->Characteristics)
        {
            return FALSE;
        }
        if (sec_disk[i].PointerToRawData)
        {
            if (sec_disk[i].VirtualAddress != sec_memory[i].VirtualAddress || sec_disk[i].PointerToRawData != sec_memory[i].PointerToRawData || sec_disk[i].SizeOfRawData != sec_memory[i].SizeOfRawData)
            {
                return FALSE;
            }

            if ((sec_disk[i].Characteristics & IMAGE_SCN_MEM_READ) && !(sec_disk[i].Characteristics & IMAGE_SCN_MEM_WRITE))
            {
                if (
                    crc32(reinterpret_cast<uint8_t*>(addr_file) + sec_disk[i].PointerToRawData, sec_disk[i].SizeOfRawData) !=
                    crc32(reinterpret_cast<uint8_t*>(addr_mod) + sec_memory[i].VirtualAddress, sec_memory[i].SizeOfRawData)
                    )
                {
                    return FALSE;
                }
            }
        }
    }
    return TRUE;
}

auto  is_file_valid(PVOID mod_address) -> BOOLEAN
{
    BOOLEAN is_valid = TRUE;
    uint32_t old_size_file = NULL;
    DWORD num_read = NULL;
    PVOID memory_module = NULL;
    PVOID memory_file = NULL;
    HANDLE access_file = NULL;
    WCHAR file_path[MAX_PATH] = { NULL };

  

    if (mod_address && GetModuleFileNameW(reinterpret_cast<HMODULE>(mod_address), file_path, sizeof(file_path)))
    {
        access_file = CreateFileW(file_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      
        if (access_file != INVALID_HANDLE_VALUE)
        {
            old_size_file = GetFileSize(access_file, NULL);
            if (old_size_file)
            {
                memory_file = malloc(old_size_file);
                if (memory_file)
                {
                    memset(memory_file, NULL, old_size_file);
                    if (ReadFile(access_file, memory_file, old_size_file, &num_read, NULL))
                    {
                        if (disk_fix(memory_file, mod_address))
                        {
                            is_valid = disk_compare(memory_file, mod_address);
                        }
                    }
                    free(memory_file);
                }
            }
            CloseHandle(access_file);
        }
    }
    return is_valid;
}
Из плюсов:
Можно параллельно проверить CRC file
Основывается только на файле,который находится на диске и на данных,котоыре мы должны исправить
Из минусов:
Требует загрузку файла с диска
Нужно исправить все данные,которые меняются при загрузке бинарника
Для некоторых файлах нужно исключить секции при проверки(особенно, если секция была упакована).


4.CRC static

⠀⠀⠀⠀⠀Вместо подсчёта на основе загрузки файла или когда система всё пофиксит, можно просто рассчитать статические данные, которые не меняются при загрузке бинарника. Такой подход является,на взгляд автора,наиболее правильным и должен присутствовать в любом протекторе. К удивлению, некоторые протекторы не реализуют его или реализуют частично (проверка 1 секции .text или только свой обфусцированный код). Нужно получить адреса и размер данных,которые изменяются при загрузке бинарника(в примере демонстрируется IMAGE_DIRECTORY_ENTRY_BASERELOC и IMAGE_DIRECTORY_ENTRY_IMPORT),чтобы отфильтровать по адресу. После этого пройтись по секциям и проверить Characteristics на IMAGE_SCN_MEM_READ и !IMAGE_SCN_MEM_WRITE. Если секция попадает под наши не менее жёсткие требования,то просто игнорировать сами данные и получить адрес и размер статических данных. Вот основные моменты,чтобы понять принцип работы:

  • если в секции отсутствуют не статические данные – берём полный её полный размер.
  • если в секции несколько данных, то берём начало и идём до конца секции.
  • если секция начинается с не статических данных, то начинаем с окончанием не статических данных(адрес + размер) и идём до конца секции.
  • если следующая нестатическая данная находится за пределами,то получаем размер = окончание секции - текущие статические данные.
  • если присутствует только 1 не статическая данная, то берём начало секции до адреса этой данной и конце не статической данной до конца секции.
  • 4 пункт, но с условием для 3 пункта.
Сам код получения
Пожалуйста, авторизуйтесь для просмотра ссылки.
смотрите на Github:
C++:
auto disk_calc_static_crc(PVOID addr_file, uint8_t* addr_pattern) -> BOOLEAN
{
    BOOLEAN fisrt_crc_init = FALSE;
    BOOLEAN ignore_first_sec_byte = FALSE;
    BOOLEAN any_crc_ignore = FALSE;
    uint32_t end_reloce = NULL;
    HMODULE base_imp_dll = NULL;

    CHAR* name_imp_dll = NULL;
    uint16_t* relative_info = NULL;
    uint64_t* orig_first_thunk = NULL;
    uint64_t* first_thunk = NULL;

    PIMAGE_NT_HEADERS nt_headers = NULL;
    PIMAGE_BASE_RELOCATION va_reloce = NULL;
    PIMAGE_BASE_RELOCATION size_reloce = NULL;
    PIMAGE_SECTION_HEADER sec = NULL;
    PIMAGE_IMPORT_DESCRIPTOR imp_descript = NULL;
    PIMAGE_IMPORT_BY_NAME import_name = NULL;
    CRC_IGNORE_INFO crc_cur_ignore = { NULL };
    CRC_INFO_FILE crc_cur_file = { NULL };
    nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<CHAR*>(addr_file) + reinterpret_cast<IMAGE_DOS_HEADER*>(addr_file)->e_lfanew);
    if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
        return FALSE;

    sec = IMAGE_FIRST_SECTION(nt_headers);

      //get reloce & import
    std::sort(crc_ignore.begin(), crc_ignore.end(), [](CRC_IGNORE_INFO& cur, CRC_IGNORE_INFO& next){return cur.rva < next.rva; });

    for (size_t i = NULL; i < nt_headers->FileHeader.NumberOfSections; i++)
    {
        fisrt_crc_init = FALSE;
        if ((sec[i].Characteristics & IMAGE_SCN_MEM_READ) && !(sec[i].Characteristics & IMAGE_SCN_MEM_WRITE) && sec[i].SizeOfRawData > sizeof(PVOID))
        {
            any_crc_ignore = TRUE;
            for (size_t j = NULL; j < crc_ignore.size(); j++)
            {
                if (crc_ignore[j].rva >= sec[i].VirtualAddress && (sec[i].VirtualAddress + sec[i].Misc.VirtualSize - sizeof(PVOID)) > crc_ignore[j].rva)
                {
                    any_crc_ignore = FALSE;
                    break;
                }
            }

            if (any_crc_ignore)    //crc res all secthion
            {
                crc_cur_file.crc_addr_rva = sec[i].VirtualAddress;
                crc_cur_file.crc_size = sec[i].SizeOfRawData;
                crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);

                crc_res.push_back(crc_cur_file);

            }
            else //get crc
            {
                fisrt_crc_init = TRUE;

                for (size_t j = NULL; j < crc_ignore.size(); j++)
                {
                    if (crc_ignore[j].rva >= sec[i].VirtualAddress && (sec[i].VirtualAddress + sec[i].Misc.VirtualSize - sizeof(PVOID)) > crc_ignore[j].rva)
                    {
                        ignore_first_sec_byte = FALSE;
                        if (crc_ignore[j].rva == sec[i].VirtualAddress)
                        {
                            ignore_first_sec_byte = TRUE;
                        }

                        if (!ignore_first_sec_byte)
                        {
                            if (fisrt_crc_init)
                            {


                                if (j == NULL && crc_ignore[j].rva - sec[i].VirtualAddress > NULL)
                                {
                                    if (crc_ignore.size() > j && crc_ignore[j + 1].rva - crc_ignore[j].rva > NULL)
                                    {
                                        crc_cur_file.crc_addr_rva = sec[i].VirtualAddress;
                                        crc_cur_file.crc_size = crc_ignore[j].rva - sec[i].VirtualAddress;
                                        crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                        crc_res.push_back(crc_cur_file);

                                        fisrt_crc_init = FALSE;

                                    }
                                }
                                else
                                {
                                    if (crc_ignore.size() > j && crc_ignore[j + 1].rva - (crc_ignore[j].rva + crc_ignore[j].size) > NULL)
                                    {
                                        if (crc_ignore[j + 1].rva >= sec[i].VirtualAddress && (sec[i].VirtualAddress + sec[i].Misc.VirtualSize) > crc_ignore[j + 1].rva)
                                        {
                                            crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                            crc_cur_file.crc_size = crc_ignore[j + 1].rva - crc_cur_file.crc_addr_rva;
                                            crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                            fisrt_crc_init = FALSE;
                                            crc_res.push_back(crc_cur_file);
                                        }
                                        else
                                        {
                                            break;
                                        }

                                    }
                                    else if (crc_ignore.size() == 1)
                                    {
                                      
                                        //not check code
                                        crc_cur_file.crc_addr_rva = sec[i].VirtualAddress;
                                        crc_cur_file.crc_size = crc_ignore[j].rva - crc_cur_file.crc_addr_rva;
                                        crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                        crc_res.push_back(crc_cur_file);

                                        //not check code
                                        crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                        crc_cur_file.crc_size = sec[i].SizeOfRawData - crc_cur_file.crc_addr_rva;
                                        crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                        crc_res.push_back(crc_cur_file);
                                        fisrt_crc_init = FALSE;
                                    }

                                }
                            }
                            else
                            {
                                if (crc_ignore.size() > j)
                                {
                                    if (crc_ignore[j + 1].rva - (crc_ignore[j].rva + crc_ignore[j].size) > NULL)
                                    {
                                        if (crc_ignore[j + 1].rva >= sec[i].VirtualAddress && (sec[i].VirtualAddress + sec[i].Misc.VirtualSize) > crc_ignore[j + 1].rva + crc_ignore[j + 1].size)
                                        {
                                            if (crc_ignore[j + 1].rva > crc_ignore[j].rva && crc_ignore[j + 1].rva > crc_ignore[j].rva + crc_ignore[j].size)
                                            {
                                                crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                                crc_cur_file.crc_size = crc_ignore[j + 1].rva - crc_cur_file.crc_addr_rva;
                                                crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                                fisrt_crc_init = FALSE;

                                                crc_res.push_back(crc_cur_file);
                                            }
                                        }
                                        else  if (sec[i].VirtualAddress + sec[i].SizeOfRawData - (crc_ignore[j].rva + crc_ignore[j].size) > NULL)
                                        {
                                            crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                            crc_cur_file.crc_size = sec[i].VirtualAddress + sec[i].SizeOfRawData - crc_cur_file.crc_addr_rva;
                                            crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                            fisrt_crc_init = FALSE;

                                            crc_res.push_back(crc_cur_file);
                                            break;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                }
                                else if (crc_ignore.size() == 1)
                                {
                                    __debugbreak();
                                }
                            }
                        }
                        else//start by cur and next rva
                        {
                            if (crc_ignore.size() > j)
                            {
                                if (crc_ignore[j + 1].rva - (crc_ignore[j].rva + crc_ignore[j].size) > NULL)
                                {
                                    if (crc_ignore[j + 1].rva > crc_ignore[j].rva && crc_ignore[j + 1].rva > crc_ignore[j].rva + crc_ignore[j].size)
                                    {
                                        crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                        crc_cur_file.crc_size = crc_ignore[j + 1].rva - crc_cur_file.crc_addr_rva;
                                        crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);

                                        crc_res.push_back(crc_cur_file);
                                    }
                                }
                            }
                            else if (crc_ignore.size() == 1)
                            {

                                //not check code
                                crc_cur_file.crc_addr_rva = crc_ignore[j].rva + crc_ignore[j].size;
                                crc_cur_file.crc_size = sec[i].VirtualAddress + sec[i].SizeOfRawData - crc_ignore[j].size;
                                crc_cur_file.crc_res = crc32(reinterpret_cast<uint8_t*>(addr_file) + rva_to_va(crc_cur_file.crc_addr_rva, nt_headers, sec), crc_cur_file.crc_size);
                                crc_res.push_back(crc_cur_file);
                                fisrt_crc_init = FALSE;

                            }
                        }
                    }
                }

            }
        }
    }




    //copy size
    reinterpret_cast<CRC_STATIC_INFO*>(addr_pattern)->crc_num = crc_res.size();
    addr_pattern += sizeof(uint32_t);

    for (size_t i = NULL; i < crc_res.size(); i++)
    {
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_addr_rva = crc_res[i].crc_addr_rva;
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_res = crc_res[i].crc_res;
        reinterpret_cast<CRC_INFO_FILE*>(addr_pattern)->crc_size = crc_res[i].crc_size;
        addr_pattern += sizeof(CRC_INFO_FILE);
    }

    return TRUE;
}
Сама проверка:
Код:
CRC_STATIC_INFO crc_res_table = { 0xDEADC0DE, {0xBEEF,1,0XDEADC0DE} };

auto  crc_static_check(PVOID addr_mod) -> BOOLEAN
{
    for (size_t i = NULL; i < crc_res_table.crc_num; i++)
    {
        if (crc32(reinterpret_cast<uint8_t*>(addr_mod) + crc_res_table.crc_reg[i].crc_addr_rva, crc_res_table.crc_reg[i].crc_size) != crc_res_table.crc_reg[i].crc_res)
        {
            printf("rva ->\t%p\n", crc_res_table.crc_reg[i].crc_addr_rva);
            printf("crc_res ->\t%p\n", crc_res_table.crc_reg[i].crc_res);

            return FALSE;
        }
    }
    return TRUE;
}

Из плюсов:
Буквально нужно только получить адрес проверяемого модуля и всё
Можно зашифровывать некоторые данные, основываясь на crc подсчитанный и расшифровывать на результате crc в памяти
Из минусов:
Правильная реализация нахождения всех статических данных


Заключительные слова

⠀⠀⠀⠀⠀Нужно понимать,что многие проверки показаны не как 1337 защита,а просто пример для обучения. CRC является просто дополнительным слоем защиты и небольшой занозой в заднице. В тех же протекторах могут использовать результат рандомного CRC для расшифровки констант из-за чего может происходить поломка работы программы из-за тех же хуков/патчев. Надеюсь, эта статья принесла что-то новое читателю и спасибо за чтение статьи :).

Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Последнее редактирование:
Сверху Снизу