HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2

ANTICHAT — форум по информационной безопасности, OSINT и технологиям

ANTICHAT — русскоязычное сообщество по безопасности, OSINT и программированию. Форум ранее работал на доменах antichat.ru, antichat.com и antichat.club, и теперь снова доступен на новом адресе — forum.antichat.xyz.
Форум восстановлен и продолжает развитие: доступны архивные темы, добавляются новые обсуждения и материалы.
⚠️ Старые аккаунты восстановить невозможно — необходимо зарегистрироваться заново.
Вернуться   Форум АНТИЧАТ > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Безопасность и Анонимность > Защита ОС: вирусы, антивирусы, файрволы.
   
 
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 30.03.2026, 00:40
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
Провел на форуме:
145166

Репутация: 0
По умолчанию

1. Общие сведения
2. Побитовый метод вычисления
3. CRC32 с использованием таблиц
4. Как получить на выходе нуль
5. Применение на практике
6. Послесловие

1. Общие сведения

CRC или "Cyclic Redundancy Check" (контроль циклически избыточным кодом) - это одна из разновидностей подсчёта контрольной суммы блока данных. Например, простейшей проверкой целостности информации можно считать использование бита чётности (или паритета, что одно и то-же). Это когда суммируются все биты передаваемого сообщения, и если сумма оказывается чётной, то в конец сообщения добавляется 0, если нечётной – то 1. При приёме повторно пересчитывается сумма битов, и сравнивается с принятым битом чётности. Если они отличаются, значит при передаче имеем потери.

Но такой способ определения ошибок оставляет желать лучшего, т.к. во-первых на каждый байт требуется лишний бит паритета(9), а во-вторых при искажении не одного, а нескольких битов сообщения, чётность суммы может не измениться. Поэтому существует множество более продвинутых проверок, в том числе CRC.

По сути CRC это не сумма, а остаток от деления некоего объёма информации на константу. Тем не менее, CRC исторически называют "контрольная сумма". Алгоритм считается потоковым (а не блочным), когда в итоговое значение CRC вносит вклад каждый бит. Если хоть один из них сломается, контрольная сумма тут-же слетит, причём существенно.

Что такое исходное сообщение понятно - это последовательность бит произвольной длины. Но что за константа, на которую мы должны делить сообщение?

Это число в диапазоне 16/32/64-бита, которое назвали "Полином". В таблице ниже представлены общепринятые из них. Значения с зеркальным отражением бит используются, когда данные поступают от младшего бита к старшему "Little Endian". Для тех кто в танке, little endian - это способ хранения многобайтных чисел в памяти компьютера, при котором младший байт записывается по наименьшему адресу, а старший - по наибольшему. Это основной стандарт для компьютеров на базе х86. Например число
Код:
0x12345678
будет храниться в ОЗУ как:
Код:
78 56 34 12
. В отличие от него "Big-Endian" располагает байты в порядке убывания (как мы пишем на листе бумаги), и в комп.системах используется при передачи данных по сети. Таким образом, документированным полиномом для CRC-32 является значение
Код:
0xEDB88320
- именно его мы и будем использовать в своём коде.

Как видно из этой таблички, помимо полинома в расчётах принимает участие значение инициализации, а так-же значение XOR уже готового CRC на выходе. Значение Init - это CRC на старте, что позволяет вычислять контрольную сумму данных со-значением нуль (на случай, если данные начинаются с нуля). Соответственно на выходе из функции нужно будет внести эту поправку, для чего и предусмотрено значение "XOR out".

CRC-32 настолько популярен, что начиная с м/архитектуры "Nehalem" в набор SSE4.2 процессоров Intel была включена специальная инструкция
Код:
CRC32
, которая служит для аппаратного ускорения вычисления контрольной суммы. Она использует полином от
Код:
Castagnoli = 0x1EDC6F41
, и позволяет обрабатывать байты, слова или двойные слова в потоке данных, всего за один такт. Перед использованием инструкции необходим тест на поддержку через
Код:
cpuid eax,1
- если взведён бит(20) в регистре
Код:
ECX
, значит ок, иначе задействуем перечисленные ниже программные методы вычисления CRC-32.


2. Побитовый метод вычисленияCRC-32

В простом варианте вычисление контрольной суммы сводится к следующему:

1. Начинаем с 32-битной контрольной суммы 0xffffffff.
2. Сдвинуть предыдущее 32-битное значение CRC вправо на 8-бит.
3. XOR значения из пункта(2) с полиномом, чтобы получить новое значение CRC.
4. Повторить пункты(2,3) по всей длине данных.
5. После завершения цикла выполните XOR вычисленного значения CRC с 0xffffffff (можно NOT).
6. Это окончательный результат CRC32.

Вот реализация этого алгоритма, которая оформлена в процедуру:

C-подобный:


Код:
;
//==============================================
;
// Версия для малых объёмов данных (медленная)
;
// На входе: ESI = адрес данных, ECX = длина
;
// На выходе: EAX = CRC32
;
//==============================================
calc_crc32_slow
:
push    ebx ecx edx
 
    mov     eax
,
0xFFFFFFFF
;
// начальное значение CRC32
or      ecx
,
ecx
;
// есть что считать?
jz      @done_slow
;
// нет - на выход.
@calc_loop_slow
:
movzx   ebx
,
byte
[
esi
]
;
// загружаем очередной байт данных
xor     eax
,
ebx
;
// ксор с CRC
mov     edx
,
8
;
// обрабатываем все 8-бит байта
@bit_loop
:
shr     eax
,
1
;
// сдвиг вправо на 1
jnc     @no_xor
;
// пропустить, если выдвинулся нуль
xor     eax
,
0xEDB88320
;
// иначе: ксор с полиномом
@no_xor
:
dec     edx
;
// счётчик бит -1
jnz     @bit_loop
;
// повторить для всех 8-бит
inc     esi
;
// следующий байт данных
dec     ecx
;
// длина -1
jnz     @calc_loop_slow
;
// на повтор, если не конец.
@done_slow
:
xor     eax
,
0xFFFFFFFF
;
// финальный "XOR out" на выходе (см.таблицу выше)
pop     edx ecx ebx    
ret
Недостатком данного метода является XOR над каждым из 8-битов байта. Так, если блок данных имеет размер 1 МБ, то придётся прокрутить 8 млн.операций, что несомненно окажет отрицательное влияние на скорость выполнения расчёта. Поэтому алго побитового вычисления CRC32 рекомендуют для небольших объёмов данных, а в идеале нужно использовать т.н. "Табличный метод".

3.CRC32 с использованием таблиц

Таблица играет ключевую роль в оптимизации вычисления CRC - разберём её подробно.
Значение байта данных может лежать в диапазоне 0-255. Суть в том, что мы заранее в цикле вычисляем CRC32 для всех возможных значений 0-255, и помещаем эти шаблоны в таблицу, которая займёт в памяти
Код:
256х4=1КБ
. Теперь когда будем читать поток данных по-байтно, то значение этого байта можно будет использовать в качестве (!)индекса в таблице (порядкового номера), чтобы выбрать из неё уже предвычисленное значение CRC. Как результат мы избавляемся от проходов по всем 8-битам байта, что увеличит скорость в ~8 раз. Табличный метод оправдывает себя на больших массивах данных, и его нецелесообразно использовать, например, для строки "Hello World!". Вот как это дело реализовать на практике:

C-подобный:


Код:
;
//=============================
;
// Для начала создаём таблицу
;
//=============================
init_crc_table
:
mov    ecx
,
0
;
// для всех байтов от 0 до 255
@create
:
mov    eax
,
ecx
;
// текущий байт
;
// Обрабатываем по 8-бит (как в медленном методе)
mov    edx
,
8
@process_bit
:
shr    eax
,
1
jnc    @skip_xor
    xor    eax
,
0xEDB88320
@skip_xor
:
dec    edx
    jnz    @process_bit
;
// Сохраняем результат для этого байта в таблице
mov
[
crc_table
+
ecx
*
4
]
,
eax
 
    inc    ecx
    cmp    ecx
,
256
jb     @create
Теперь у нас есть таблица, и можно выбирать из неё значения CRC32 для каждого из возможных байтов так. Адресация вида
Код:
esi+ebx*4
называется базово-индексной с масштабированием (Base-Indexed with Scale), или адресацией SIB (Scale-Index-Base). Она используется для доступа к элементам массивов, где
Код:
esi
= базовый адрес,
Код:
ebx
= индекс, а
Код:
4
= масштаб (размер элемента 4 байта):

C-подобный:


Код:
calc_crc32_fast
:
mov    eax
,
0xFFFFFFFF
@calc_loop
:
movzx  ebx
,
byte
[
esi
]
;
// 1. загружаем байт
xor    bl
,
al
;
// 2. xor с младшим байтом CRC
movzx  ebx
,
bl
;
// 3. индекс в таблице (0-255)
shr    eax
,
8
;
// 4. сдвигаем CRC
xor    eax
,
[
crc_table
+
ebx
*
4
]
;
// 5. xor с табличным значением
inc    esi
;
// следующий байт
dec    ecx
;
// конец данных?
jnz    @calc_loop
;
// нет - продолжить.
xor    eax
,
0xFFFFFFFF
;
// финальный "xor out"
Для лучшего понимания проведём аналогию.
Пусть нам нужно умножить числа от 0 до 255 на 43:

Цитата:

Без таблицы: каждый раз вычислять x *43
С таблицей : создать массив table[x] = x *43, и потом просто брать готовое значение

Таблица CRC делает то же самое: предвычисляет результат всех возможных комбинаций из 256 вариантов, чтобы не тратить время на побитовые операции.

4. Как на выходе получитьCRC32 = нуль

У обоих методов выше (по-битовый, табличный) есть один общий недостаток - если мы хотим периодически проверять контрольную сумму блока данных, то оригинальный CRC приходится где-то хранить, что не есть гуд. Поэтому после повторного пересчёта, CRC данных в идеале должна быть равна нулю, тогда и оригинал будет не нужен.

Учитывая, что в основе алгоритма CRC лежит XOR, получить на выходе нуль довольно легко - для этого достаточно пересчитать сумму блока как обычно, после чего применив операцию
Код:
not crc32
, добавить инверсный CRC в хвост блока данных, младшим байтом вперёд Little-Endian. Если после этого обратно пересчитать CRC с учётом добавленного дворда, то получим нуль, что собственно и требовалось доказать. Вот пример:

C-подобный:


Код:
format pe console
entry start
include
'win32ax.inc'
section
'.data'
data readable writeable
crc_table       dd
256
dup
(
0
)
test_string     db
'Hello, World!'
,
0
test_len
=
$
-
test_string
-
1
data_with_crc   rb
256
;
// буфер для данных + CRC
;
//----------
section
'.code'
code readable executable
;
// ======================================
;
// Инициализация таблицы CRC32
;
// ======================================
init_crc_table
:
push    ebx ecx edx
       xor     ecx
,
ecx
;
// байт(0) из 256
@generate_table
:
mov     eax
,
ecx
;
// текущий байт
mov     edx
,
8
;
// счётчик бит
@process_bit
:
shr     eax
,
1
;
// сдвиг на 1-бит вправо
jnc     @skip_xor
;
// если бит не был установлен
xor     eax
,
0xEDB88320
;
// иначе ксор с полиномом
@skip_xor
:
dec     edx
;
// повтор для всех 8-бит
jnz     @process_bit
 
       mov
[
crc_table
+
ecx
*
4
]
,
eax
;
// сохраняем в таблицу
inc     ecx
       cmp     ecx
,
256
;
// заполняем всю таблицу до 256 двордов
jb      @generate_table
       pop     edx ecx ebx
ret
;
//==================================
;
// Вычисление CRC32
;
// Вход: ESI - данные, ECX - длина
;
// Выход: EAX - CRC32
;
//==================================
align
16
calc_crc32
:
push    ebx edx esi ecx
       mov     eax
,
0xFFFFFFFF
;
// начальное значение CRC-32
or      ecx
,
ecx
       jz      @done
@calc_loop
:
movzx   ebx
,
byte
[
esi
]
;
// берём очередной байт данных
xor     bl
,
al
;
// XOR с младшим байтом CRC
movzx   ebx
,
bl
;
// расширяем до 32-бит
shr     eax
,
8
;
// сдвиг CRC вправо на 8-бит
xor     eax
,
[
crc_table
+
ebx
*
4
]
;
// XOR с значением из таблицы!
inc     esi
;
// следующий байт данных..
dec     ecx
;
// это конец данных?
jnz     @calc_loop
;
// нет на повтор
@done
:
xor     eax
,
0xFFFFFFFF
;
// финальный: CRC32 xor -1
pop     ecx esi edx ebx
ret
;
//===============================
;
//  Точка входа в программу
;
//===============================
align
16
start
:
call    init_crc_table
      cinvoke  printf
,

,
test_len
;
// 1. Вычисляем CRC для исходной строки
mov     esi
,
test_string
       mov     ecx
,
test_len
       call    calc_crc32
       push    eax

      cinvoke  printf
,

,
eax
;
// 2. Копируем данные в буфер (без нуль-терминатора)
mov     esi
,
test_string
       mov     edi
,
data_with_crc
       mov     ecx
,
test_len
       rep     movsb
;
// 3. Добавляем CRC в конец данных
pop     eax
       not     eax
;
// обязательная инверсия!
mov
[
edi
]
,
eax
;
// записываем 4 байта CRC
;
// 4. Обратно вычисляем CRC теперь для всего буфера
cinvoke  printf
,

,
test_len
+
4
mov     esi
,
data_with_crc
       mov     ecx
,
test_len
+
4
call    calc_crc32
       xor     eax
,
-
1
;
// 0xFFFFFFFF
cinvoke  printf
,

,
eax

      cinvoke  getch
      cinvoke  exit
,
0
;
//-------------
section
'.idata'
import data readable writeable
library msvcrt
,
'msvcrt.dll'
import  msvcrt
,
printf
,
'printf'
,
getch
,
'_getch'
,
exit
,
'exit'


5. ПрименениеCRC32 на практике

Рассмотрим пример, когда алгоритмом CRC32 охраняется критический блок в секции-кода нашего приложения. Такой расклад можно наблюдать, если взломщик планирует обратить условие перехода
Код:
JZ
на
Код:
JNZ
при проверке, скажем, пароля. Значит пишем обычное приложение для запроса секретного слова (точнее суммы его байт, аля хэша), а в дополнительном потоке с периодом 1-сек будем пересчитывать контрольную сумму охраняемого периметра (защита от on-line патча). Если получим CRC32 отличную от нуля, сразу включаем сирену, выводим мессагу и на выход. Чтобы реализовать эту схему, нужно:

1. Встроить в код отладочную процедуру, которая покажет исходную сумму CRC32(1).
2. Теперь делаем not crc32(1) и записываем получившееся значение в хвост блока данных.

C-подобный:


Код:
format pe console
entry start
include
'win32ax.inc'
section
'.data'
data readable writeable
crc_table   dd
256
dup
(
0
)
pass        dd
'C'
+
'o'
+
'd'
+
'e'
+
'b'
+
'y'
;
// хэш пароля
align
16
buff        db
0
;
//----------
section
'.code'
code readable executable
;
//================================
;
// Инициализация таблицы CRC32
;
//================================
init_crc_table
:
push    ebx ecx edx
       xor     ecx
,
ecx
;
// байт(0) из 256
@generate_table
:
mov     eax
,
ecx
;
// текущий байт
mov     edx
,
8
;
// счётчик бит
@process_bit
:
shr     eax
,
1
;
// сдвиг на 1-бит вправо
jnc     @skip_xor
;
// если бит не был установлен
xor     eax
,
0xEDB88320
;
// иначе ксор с полиномом
@skip_xor
:
dec     edx
;
// повтор для всех 8-бит
jnz     @process_bit
 
       mov
[
crc_table
+
ecx
*
4
]
,
eax
;
// сохраняем в таблицу
inc     ecx
       cmp     ecx
,
256
;
// заполняем всю таблицу до 256 двордов
jb      @generate_table
       pop     edx ecx ebx
ret
;
//==================================
;
// Вычисление CRC32
;
// Вход: ESI - данные, ECX - длина
;
// Выход: EAX - CRC32
;
//==================================
align
16
calc_crc32
:
push    ebx edx esi ecx
       mov     eax
,
0xFFFFFFFF
;
// начальное значение CRC-32
or      ecx
,
ecx
       jz      @done
@calc_loop
:
movzx   ebx
,
byte
[
esi
]
;
// берём очередной байт данных
xor     bl
,
al
;
// XOR с младшим байтом CRC
movzx   ebx
,
bl
;
// расширяем до 32-бит
shr     eax
,
8
;
// сдвиг CRC вправо на 8-бит
xor     eax
,
[
crc_table
+
ebx
*
4
]
;
// XOR с значением из таблицы!
inc     esi
;
// следующий байт данных..
dec     ecx
;
// это конец данных?
jnz     @calc_loop
;
// нет на повтор
@done
:
xor     eax
,
0xFFFFFFFF
;
// финальный: CRC32 xor -1
pop     ecx esi edx ebx
ret
;
//==============================
;
// Поток для проверки CRC32
;
//==============================
proc CheckCrcThread lpParam
@@
:
invoke  Sleep
,
1000
mov     esi
,
@testPass
       mov     ecx
,
@endBlock
-
@testPass
       add     ecx
,
4
call    calc_crc32
       not     eax
       or      eax
,
eax
       jz      @b

       invoke  MessageBox
,
0
,

,
0
,
0
invoke  ExitThread
,
0
ret
endp
;
//===============================
;
//  Точка входа в программу
;
//===============================
align
16
start
:
call    init_crc_table

       mov     esi
,
@testPass
;
//
,
eax

       invoke  CreateThread
,
0
,
0
,
CheckCrcThread
,
0
,
0
,
0
;
// создаём поток
cinvoke  printf
,

xor     ebx
,
ebx
       mov     eax
,
ebx
@@
:
cinvoke  getch
;
// принимаем пасс у юзера
cmp     al
,
13
;
// это Enter?
je      @stop
;
// да - на выход
add     ebx
,
eax
;
// считаем сумму
jmp     @b
;
// продолжить..
@stop
:
cmp     ebx
,
[
pass
]
;
//
jmp     @end
@
01
:
cinvoke  printf
,

@end
:
cinvoke  getch
      cinvoke  exit
,
0
@endBlock
:
;
//*****************************************
;
// Конец охранной зоны
;
//*****************************************
section
'.idata'
import data readable writeable
library kernel32
,
'kernel32.dll'
,
user32
,
'user32.dll'
,
msvcrt
,
'msvcrt.dll'
include
'api\kernel32.inc'
include
'api\user32.inc'
import  msvcrt
,
printf
,
'printf'
,
scanf
,
'scanf'
,
\
                getch
,
'_getch'
,
exit
,
'exit'
Если собрать сейчас этот исходник по F9 в окне fasm'a, то получим мессагу, мол в охраняемую зону проник нарушитель. Оно и ясно, ведь доп.поток приложения проверяет CRC на нуль, и пока мы не добавим исходный "CRC32 value" в хвост блока (конец охранной зоны), окно с ошибкой так и будет всплывать.

Пропатчить экзешник можно прямо в отладчике x64dbg. Значит открываем бинарь, и спустившись до
Код:
call exit
жмём пробел = "Ассемблирование". В появившемся окне вводим значение дворда
Код:
dd 8b9116f6 --> OK
, и далее применим патч комбинацией
Код:
Ctrl+P
. Сохранять пропатченный EXE нужно под другим именем, и если потом запустить его, то окно с ошибкой уже пропадёт.

Процесс добавления CRC32 в хвост кода



Окно применения патча по Ctrl+P

Теперь можно сказать, что код нашей прожки защищён от модификации, т.к. доп.поток будет в цикле пересчитывать его контрольную сумму CRC32. Попробуйте по аналогичной схеме изменить сейчас
Код:
JZ
на
Код:
JNZ
после ввода пароля (метка @testPass), как тут-же всплывёт мессага выше с варнингом.

6. Послесловие

Под занавес приведём пример расчёта CRC32 аппаратным способом через инструкцию SSE4.2
Код:
crc32
. На каждый байт она расходует всего 1-такт, что позволяет получить скорость гепарда. Вот пример:

C-подобный:


Код:
;
// Проверка поддержки CRC32 инструкций
check_crc32_support
:
push   ebx
    mov    eax
,
1
cpuid
    test   ecx
,
(
1
<
<
20
)
;
// бит 20 - поддержка SSE4.2
jnz    @supported
    pop    ebx
    xor    eax
,
eax
;
// нет поддержки
ret
@supported
:
pop    ebx
    mov    eax
,
1
ret
;
//-----------------
;
// Аппаратное вычисление CRC32 (только для SSE4.2)
;
// На входе: ESI = адрес начала блока, ECX = длина
;
//-----------------
calc_crc32_hardware
:
push   ebx
    mov    eax
,
0xFFFFFFFF
;
// начальное значение
or     ecx
,
ecx
    jz     @done_hw
 
@calc_hw_loop
:
movzx  ebx
,
byte
[
esi
]
crc32  eax
,
bl
;
// аппаратная инструкция CRC32
inc    esi
    dec    ecx
    jnz    @calc_hw_loop
 
@done_hw
:
xor    eax
,
0xFFFFFFFF
pop    ebx
ret
Линк по теме:
Неплохой обзор алгоритма CRC32
Демистификация CRC32 | arm | programming
 
Ответить с цитированием
 





Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.