Когда большинство вирусов было бутовыми, когда обновлялся аидстестовский вирлист, лозинский еще не впал в маразм, а касперский еще только сосал не причмокивая... Жил-был вирус, и как только он не назывался - Doodle, Yankee, Music, и еще хрен знает как.
И была в том вирусе фича, а именно - коли патчил в вирусе бедняга-программист какой байт, так байт этот на место возвращался. То есть становился обратно, как был.
И с тех пор мучают вирмэйкеры себя и людей, доставая их вопросами - как же енто он, проклятый, восстанавливается, не делая второй своей копии?
Интуитивно ясно, что внесение избыточности в информацию позволяет находить и восстанавливать помехи. Пример тому - избыточность естественных языков. Сколько избыточности вносить и как ее считать, можно прочитать у Шеннона.
Мы, вирмэйкеры, люди простые, нам Шеннон ни к чему, всякие там теоремы мы вообще знать не желаем, и поэтому требуется простой способ восстанавливать вирус после патча.
Итак, представляем защищаемый блок данных в виде матрицы. По каждому столбцу и каждой строке матрицы считаем контрольную сумму, попросту XORим байты один на другой.
Теперь представим, что изменился один байт. Тогда изменятся контрольные суммы соответствующих столбца/строки матрицы, своими номерами указывая координаты байта.
Вот и все. Максимальное количество восстанавливаемых подряд байт равно числу столбцов. Количество строк и столбцов выбирается как вам удобнее.
Конечно, можно попросту хранить вторую копию вируса. Но ее будет легко пропатчить. И вообще, это уже совсем другая байка.
Кстати, судя по всему похожая фишка используется в RARе для защиты архивов от глюков, для -rr1 число столбцов (длина строки) соответственно 512 байт - один сектор.
Пример.
Есть данные, и есть контрольные суммы по строкам и по столбцам, посчитанные XORом. Пусть байт 74 (jz) изменили на EB (jmp).
до изменения: после изменения: 90 90 90 90 90 90 90 90 90 90 90 90 90 74 12 90 90 F6 90 EB 12 90 90 69 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 00 E4 82 00 00 00 7B 82 00 00
В результате изменятся два байта контрольных сумм - один в суммах по строкам и один
в суммах по столбцам.
Таким образом мы узнали координаты измененного байта в массиве данных.
Как вычислить старое значение байта?
- проXORить старую контрольную сумму на новую и на
новое значение байта.
F6 xor 69 xor EB = 74, либо E4 xor 7B xor EB = 74.
Следующую мысль выражу кратко: если и здесь не понятно, то это пиздец.
Пример на C++ |
---|
Примечание (для тех кто не знает C):с++ pascal c = d ^ e; c := d xor e; a ^= b; a := a xor b; a++; a := a + 1; |
#include <stdio.h> #include <stdlib.h> #define COLS 4 // число столбцов (x) #define ROWS 4 // число строк (y) void main() { unsigned char a[ROWS][COLS]; // массив данных for (int y=0; y<ROWS; y++) // заполним его всякой хуйней for (int x=0; x<COLS; x++) a[y][x]=random(256); unsigned char oy[ROWS]={0}; // контрольные суммы: по строкам unsigned char ox[COLS]={0}; // по столбцам for (int y=0; y<ROWS; y++) // считаем контрольные суммы for (int x=0; x<COLS; x++) { oy[y]^=a[y][x]; ox[x]^=a[y][x]; } int y = random(ROWS); // пропатчим два рандомных байта int x = random(COLS); // (они должны быть в одной строке) int r = random(256); printf("randomizing a[%i][%i]: %02X --> %02X\n", y,x, a[y][x], r); a[y][x] = r; x--; r = random(256); printf("randomizing a[%i][%i]: %02X --> %02X\n", y,x, a[y][x], r); a[y][x] = r; unsigned char ny[ROWS]={0}; // новые контрольные суммы unsigned char nx[COLS]={0}; for (int y=0; y<ROWS; y++) // считаем и их for (int x=0; x<COLS; x++) { ny[y]^=a[y][x]; nx[x]^=a[y][x]; } for (int y=0; y<ROWS; y++) // для каждого байта данных for (int x=0; x<COLS; x++) // если не совпадают суммы по строкам и столбцам if ((oy[y]!=ny[y])&&(ox[x]!=nx[x])) { printf("found error in a[%i][%i]\n", y,x); // два варианта "старых" значений байта int v1 = oy[y]^ny[y]^a[y][x]; int v2 = ox[x]^nx[x]^a[y][x]; printf(v1==v2?"correcting\n":"trying to correct\n"); printf("%02X -> %02X\n", a[y][x], v2); // в качестве восстановленного байта используем оный посчитанный // при помощи суммы по столбцу (v2), так как вероятность изменения // нескольких байт подряд (т.е. в одной строке) больше ny[y] ^= a[y][x] ^ v2; // корректируем контрольные суммы в nx[x] ^= a[y][x] ^ v2; // соответствии с восстанавливаемым байтом a[y][x] = v2; // восстанавливаем байт } } // main |
Результат работы программы: |
randomizing a[3][3]: 51 --> A6 randomizing a[3][2]: FC --> 11 found error in a[3][2] trying to correct 11 -> FC found error in a[3][3] correcting A6 -> 51 |
Может возникнуть вопрос: какими должны быть изменения, чтобы подобный метод не смог восстановить пропатченый байт?
В основном такими:
00 00 00 00 00 nn Нельзя определить, какие из указанных четырех байтов 00 A B 00 00 ** изменились: это могут быть A и D либо B и C, 00 C D 00 00 ** либо любые комбинации по 3 байта, либо все 4. 00 00 00 00 00 nn 00 00 00 00 00 nn nn ** ** nn nn
То есть такая схема подсчета контрольных сумм хорошо работает только если изменения произошли в пределах одной строки/столбца. Поэтому строки эффективно делать длиннее столбцов.
Если вас заинтересовало помехозащищенное кодирование - ищите линейный блоковый код, коды M из N, коды Хэмминга и тому подобную хрень.
* * *
(x)