Подмена данных в WEP-пакетах by flufx Предисловие Рассмотренный здесь пример и методика действий хорошо подходят для совершения атаки "IP-перенаправление" ("IP-redirection") в беспроводной сети с WEP шифрованием. Суть атаки заключается в том, чтобы подменить в перехваченном зашифрованном пакете IP адрес получателя на IP адрес своего проводного интерфейса, и отправить этот пакет в эфир. К примеру, точка и ваш интерфейс подключены к Интернет. Тогда точка, приняв зашифрованный пакет, отправит его вам уже расшифрованным. Таким образом, сниффинг паролей в беспроводной сети, которая использует WEP шифрование, можно устроить мгновенно, не накапливая долгими часами пакеты, и не матерясь, узнав, что точка каждые 10 минут меняет ключи.. Более подробно об этой атаке я расскажу в следующей статье. Однако, для её совершения, вовсе не надо знать ни о том, как восстанавливается CRC, ни о том, как производится та или иная атака вообще. Можно взять готовую гуёвую программку, и кликать мышкой по кнопкам в соответствии с FAQ-ом или с инфой из форумов. Эта статья для тех, кто хочет больше и идёт дальше. Описанная здесь методика восстановления CRC вынуждает вставлять в сообщение "4 байта восстановления", которые идут подряд. При этом, если необходимо вставить эти байты до изменяемого отрывка сообщения, то вы сможете изменить всего лишь 4 байта сообщения. Я не стал работать над снятием этих ограничений, потому что мне это не понадобилось. Но, прочитав эту статью, вы сможете сделать это. ...ну а если нет, то просто будете знать, как восстанавливается CRC =) Общие сведения о CRC Аббревиатура CRC расшифровывается как "Cyclic Redundancy Code", что в переводе означает "циклический избыточный код". CRC - это функция для создания "отпечатка" блока информации, подобно хэш-функции. В беспроводных сетях, где используется WEP-шифрование, с помощью CRC вычисляется контрольная сумма отправляемых данных. Эта мера защиты предназначена для обнаружения естественных помех в радио-эфире беспроводной сети. Более кратко, CRC - это хитрая функция для вычисления контрольной суммы отправляемого сообщения. Далее под аббревиатурой crc (маленькими буквами) я буду обозначать результат вычисления, т.е. значение контрольной суммы. Зная в общих чертах, как работает CRC и как оно "восстанавливается", можно изменить данные в перехваченном WEP пакете таким образом, что его контрольная сумма останется неизменной. В свою очередь, возможность изменять зашифрованный пакет позволяет осуществить множество различных атак на беспроводную сеть. Однако есть существенное ограничение: в силу того, что WEP пакет зашифрован, необходимо знать, какая именно информация зашифрована в заменяемом участке пакета. Но об этом в следующей статье. Принцип работы CRC основан на использовании свойств двоичного многочлена, в виде которого представляется исходная битовая последовательность блока данных. При этом каждый бит такой последовательности соответствует одному из полиномиальных коэффициентов. Проще говоря, понимание математического обоснования этого алгоритма и его методов оптимизации требует отличного знания высшей математики. Однако, что бы понять, как "восстанавливается" контрольная сумма WEP пакета, знать все детали работы алгоритма CRC не нужно. Далее битовой последовательностью или просто последовательностью я буду называть то исходное сообщение, контрольную сумму для которого мы считаем. Основная идея CRC состоит в том, чтобы представить файл, как одну огромную строку бит, и поделить её особым образом на некоторое стандартизированное число. Получившийся в результате остаток и есть crc. Особый способ деления заключается в том, что операция "разделить" состоит из последовательности не вычитаний, а xor-ов. К примеру, если таким способом поделить 120 на 9, то в результате получится 14 и 6 в остатке. Пример 1. Деление числа 120 на 9. 1001/1111000\1110 9/120\14 6 1001||| ----||| 1100|| -> A 1001|| -> B ----|| 1010| -> C, лишнее действие 1001| -> D ----| 0110 -> E, временный результат 0000 ---- 110 -> crc Оптимизация расчёта crc Поскольку функция расчёта контрольной суммы вызывается для каждого отправляемого пакета данных, необходимо оптимизировать эту процедуру расчёта, чтобы снизить нагрузку на процессор. Операция побитового сложения обладает следующим свойством: если A xor B = C, то B xor C = A. Составив и решив систему уравнений, можно заметить что, если A xor B = C и C xor D = E, то A xor B xor D = E. Значение C выпало, а результат прежний (см. пример 1). Операцию побитового сложения по модулю два в дальнейшем я буду называть просто ксором. Заметим, что B равняется сдвинутой на один бит влево переменной D, т.е. переменные B и D - это одно и тоже число - делитель битовой последовательности (в примере 1 - 1001b). Таким образом, можно заранее рассчитать результат выражения B xor D, и пример 1 можно сократить на один шаг. Пример 2. Сокращённый вариант деления 120 на 9. 1001 1001 ----- 11011 -> результат выражения B xor D 1001/1111000\1110 9/120\14 6 1001||| ----||| 11000| -> A, сносим два бита, а не один 11011| -> результат выражения B xor D -----| 0110 -> E, временный результат 0000 ---- 110 -> crc Как видно из примера 2, количество операций побитового сложения стало меньше, а результат тот же. При этом для получения числа A необходимо взять из битовой последовательности не один бит, а два. Важным моментом является то, что первые два бита числа A и выражения B xor D равны, а значит, результат их ксора всегда будет равен нулю. Первые два бита числа A в примере 2 можно назвать выдвигаемыми, последние два бита - вдвигаемыми, а результат ксора, т.е. текущее значение crc, будем называть регистром. Так, в примере 2 значение контрольной суммы последовательности бит "1, 1, 1, 1, 0, 0, 0" равняется 110b, а значение регистра в предпоследнем бите этой последовательности - 0110b. Этот важно для понимания рассматриваемой темы. Табличный алгоритм В примере 2 было показано, что во время деления можно сносить не по одному биту, а сразу группу бит. Затем полученное число A надо ксорить с "другим" числом, но таким, что бы выдвигаемые биты сократились (выдвинулись). Это "другое" число есть B xor D - результат ксора делителя с самим собой, но со сдвигом. Рассмотрим другой пример. Пусть делитель будет шестибитным числом 101110b, и сносить будем по три бита. Из трёх бит можно составить 8 различных комбинаций единиц и нулей. Необходимо путём ксора делителя с самим собой, получить такие 8 чисел, первые три бита которых обязательно совпадут с первыми тремя битами числа A. Иными словами, необходимо "заксорить" первые три бита числа A, какими бы они не были. В результате получится таблица значений выражения B xor D, как показано в примере 3. Число D сдвигается таким образом, что бы "заксорить" первые три бита. Иногда надо ксорить несколько раз, лишь бы в результате первые три бита стали нулями. Полученная таблица называется CRC-таблицей. Пример 3. Рассчёт CRC-таблицы. 111|000000 110|000000 101|000000 100|000000 | 111|001000 +101|110 +101|110 +101|110 +101|110 | 110|010100 + 10|1110 + 10|1110 ---------- + 1|01110 | 101|110000 ---------- + 1|01110 000|110000 ---------- | 100|101100 000|001000 ---------- 000|101100 | 011|100100 000|010100 | 010|111000 | 001|011100 011|000000 010|000000 001|000000 000|000000 | 000|000000 + 10|1110 + 10|1110 + 1|01110 +000|000 | + 1|01110 ---------- ---------- ---------- | ---------- 000|111000 000|011100 000|000000 | 000|100100 | Табличный алгоритм для расчёта нового значения регистра: [*]взять первые три бита из текущего регистра; [*]найти соответствующее значение в таблице; [*]найденное значение ксорить с регистром. Пример 4. Ход работы табличного алгоритма. 101110/...10101011101101010.... .......|||||| 111010110||| -> текущее значение регистра 001000||| -> число из таблицы ------||| 011110110 -> новое значение регистра 100100 -> число из таблицы ------||| ......... Для лучшего усвоения, можете проверить этот алгоритм самостоятельно: возьмите листок бумаги и ручку, и разделите любою битовую последовательность на 101110b сперва обычным способом, а затем при помощи сформированной выше таблицы и табличного алгоритма. Результат должен получиться одинаковым. Практическая реализация CRC Все предыдущие пункты были нацелены ответить на вопросы: "Что такое CRC-таблица? Зачем она нужна?". На практике, описанный выше алгоритм оказался медленным, и был оптимизирован. Описание того, каким образом он был оптимизирован - выходит за рамки рассматриваемой темы. Важно знать лишь сам алгоритм (т.е. порядок действий), который приводит к искомому результату. Есть несколько разновидностей функции CRC: CRC8, CRC16, CRC32. Цифра обозначает количество бит в регистре. В протоколе WEP используется CRC32. "Пример 5. Функция вычисления crc на Си выглядит так. /* Расчёт crc32. */ unsigned long Crc32(unsigned char *buf, unsigned long len) { unsigned long crc = 0xFFFFFFFFUL; while (len--) crc = crc_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8); return crc ^ 0xFFFFFFFFUL; }; Этот оптимизированный табличный алгоритм не сильно отличается от того, что описан пунктом выше: [*]регистр ксорится с байтом последовательности и берётся первый байт; [*]соответствующее значение ищется в предварительно рассчитанной таблице; [*]найденное значение ксорится с регистром, сдвинутым вправо на 1 байт; [*]получаем новое значение регистра; [*]процедура повторяется до тех пор, пока байты в последовательности не закончатся. Восстановление CRC Теперь, зная в общих чертах как работает CRC и для чего используется таблица, можно приступать к основной части: восстановление CRC. Итак, имеется последовательность байт, например "k, o, r, n, i, e, n, k, o, m, e, d, v, e, d". Контрольная сумма для этой последовательности подсчитана с помощью функции CRC16, в котором размер регистра составляет 2 байта. Необходимо заменить байты "k, o, r, n, i" на "p, u, t, i, n" таким образом, чтобы контрольная сумма осталась неизменной. Для этого нужно вместо байт "e, n" (или вместо "n, k", или вместо "k, o" и т.д.) вставить такие байты, которые бы "компенсировали" внесённые изменения. Эти байты будут называться байтами восстановления. Пусть эти неизвестные байты будут обозначены как X и Y и вписаны вместо байт "n, k". Задача сводится к тому, что бы рассчитать эти байты. Пусть текущее значение регистра после подсчёта байта "e" равняется "a1, a0", а после подсчёта байта "k" - "d1, d0". В буквенном выражении, расчёт crc будет выглядеть так (следите по примеру 5): Пример 6. Ход работы функции CRC16. Обработка байта X a1, a0 - текущее значение регистра a0 xor X = t1 - индекс в таблице b1, b0 - значение, полученное из таблицы 00, a1 - значение регистра после сдвига 00 xor b1, a1 xor b0 - новое значение регистра Обработка байта Y: b1, a1 xor b0 - текущее значение регистра a1 xor b0 xor Y = t2 - индекс в таблице c1, c0 - значение, полученное из таблицы 00, b1 - значение регистра после сдвига 00 xor c1, b1 xor c0 - новое значение регистра, оно же "d1, d0". В более компактном виде: a0 xor X = t1 a1 xor b0 xor Y = t2 b1 xor c0 = d0 c1 = d1 Для расчёта байт X и Y, необходимо посчитать crc "в обратную сторону". Байты "d1, d0" нам известны, а значит и c1 тоже. И мы можем поискать в таблице такой элемент, первый байт которого будет равен c1. Фокус заключается в том, что такой элемент найдётся, и только один; с0 будет равняться его второму байту; t2 будет равняться индексу этого элемента в таблице. Теперь, зная c0 и d0, можно определить b1. А зная b1, аналогично, по таблице находим b0 и t1. Зная t2, t1, b0, a1 и a0, находим X и Y. Вот и всё! Восстановление CRC32 происходит аналогично CRC16, за исключением того, что размер регистра в CRC32 - 4 байта, и байт восстановления будет не уже 2, а 4: X, Y, Z и W. Практическая реализация восстановления CRC32 Теперь необходимо автоматизировать процесс восстановления CRC32. Алгоритм, описанный выше в теории, можно реализовать на Си таким образом: Пример 7. Реализация функции восстановления CRC32. /* Записывает в buf 4 байта, которые изменят значение регистра с reg_start на reg_end */ void getCrcReverceBytes(char *buf, unsigned long reg_start, unsigned long reg_end) { reg_start^=0xffffffff; reg_end^=0xffffffff; int num_char, t_index; for (num_char=0; num_char<4; num_char++) { for (t_index=0; t_index<256; t_index++) { if ((crc_table[t_index]&0xff000000)==(reg_end&0xff000000)) { reg_end^=crc_table[t_index]; reg_end<<=8; reg_end^=t_index ^ (((0xff000000 >> (num_char*8)) & reg_start) >> ((3-num_char)*8)); break; } } } for (num_char=0; num_char<4; num_char++) buf[num_char] = ((0x000000ff << (num_char*8)) & reg_end) >> (num_char*8); } Значение reg_start - это переменная размером в 4 байта, которая соответствует начальному состоянию регистра "a3, a2, a1, a0", по аналогии с примером 6. Это значение есть crc изменённой последовательности байт, до позиции байт восстановления. В примере 3 мы изменили последовательность байт "k, o, r, n, i" на "p, u, t, i, n". После этого изменения, надо подсчитать crc байтовой последовательности "p, u, t, i, n, e", тем самым узнав текущее значение crc на байте "e". Это значение и есть reg_start. Значение reg_end - это значение crc в неизменённой (оригинальной) последовательности байт на позиции "m" (для CRC16 - "k"). Т.е. reg_end - это значение crc вот такой последовательности: "k, o, r, n, i, e, n, k, o, m". По указателю buf функция getCrcReverceBytes запишет искомые байты "X, Y, Z, W", которые надо вставить в изменённую последовательность вместо "n, k, o, m". После этого контрольная сумма изменённой последовательности "p, u, t, i, n, e, X, Y, Z, W, e, d, v, e, d" восстановится до исходной. Другими словами, у нас есть некоторое значение crc в байте номер n-1, и мы хотим в байте номер n+3 получить какое-то другое конкретно заданное значение crc. Для этого с помощью функции getCrcReverceBytes рассчитываем такие байты "X, Y, Z, W", после вставки которых в позиции n, n+1, n+2, n+3 соответственно, в позиции n+3 будем иметь нужное нам значение crc. Пример использования функции getCrcReverceBytes на Си приведён ниже, в следующем пункте. Дополнение к процедуре восстановления CRC32 При использовании полученных знаний для атаки на WEP, может возникнуть другая проблема: байты восстановления "X, Y, Z, W" иногда должны размещаться не после изменённых байт, а перед ними. Например, если в перехваченном пакете нужно изменить IP адрес получателя, то байты восстановления должны прийтись на поле "IP source", которое стоит перед изменяемыми байтами (смотрите спецификацию протокола IP). Подробности реализации атаки "IP-перенаправление" я расскажу в следующей статье. Но для её понимания, будет полезным дочитать до конца эту; осталось чуть-чуть =) Итак, надо рассчитать байты восстановления при не известном значении reg_end. Но это же значение будет являться reg_start-ом для изменённой последовательности. Поэтому обозначим эту переменную как reg_se (start - end). Смотрите следующий пример: Пример 8. Оптимизация восстановления для IP-перенаправления. k o r n i e n k o m e d v e d ^ ^ k o X Y Z W p u t i e d v e d ^ ^ ^ reg_start reg_se reg_end Здесь нам известны лишь три переменные: reg_start, reg_end и новая последовательность байт "p, u, t, i". Напомню, что reg_end - это конкретно заданное значение, которое мы хотим получить после всех этих манипуляций; как и в прошлом примере, оно берётся из оригинальной последовательности байт. Теперь необходимо рассчитать значение reg_se. Раньше задача ставилась так: есть reg_start и reg_end (4 байта каждый), надо рассчитать 4 байта "X, Y, Z, W". Теперь же нам надо рассчитать значение reg_start, а байты восстановления мы уже знаем: "p, u, t, i". Т.к. длина всех переменных - 4 байта, можно заполнить reg_start байтами восстановления, и на выходе функции getCrcReverceBytes получим искомый reg_start. Для того, что бы сделать это, можно воспользоваться функцией getCrcReverceStart. На вход она принимает reg_end и 4 байта восстановления, которые передаст функции getCrcReverceBytes как reg_start. На выходе вернёт искомое значение reg_se. "Пример 9. Добавочная ф-ция к восстановлению CRC. unsigned long getCrcReverceStart(unsigned char *buf, unsigned long reg_end) { unsigned long reg_start=0; int num_char; for (num_char=0; num_char<4; num_char++) reg_start+=(buf[num_char] << (num_char*8)); getCrcReverceBytes(buf, reg_start, reg_end); reg_start=0; for (num_char=0; num_char<4; num_char++) reg_start+=(buf[num_char] << (num_char*8)); return reg_start; } Пример расчёта байт восстановления Взглянув на пример 9, вспомните, что найденное значение reg_se - это reg_end для расчёта байт восстановления. Так что у нас теперь есть оба значения регистра (начальное и конечное), что бы рассчитать байты восстановления. Вот пример использования этих функций на Си: Пример 10. Использование ф-ций восстановления printf("Значение CRC32 входного файла %s: 0x%x\n", input_filename, Crc32(input_file, len_input)); unsigned long reg_end=Crc32(input_file, posnewdata+len_newput); unsigned long reg_se=getCrcReverceStart(newput_file, reg_end); unsigned long reg_start=Crc32(input_file, posrevercedata); char buf[4]; getCrcReverceBytes(buf, reg_start, reg_se); memcpy(output_file+posrevercedata, buf, sizeof(buf)); printf("Значение CRC32 выходного файла %s: 0x%x\n", output_filename, Crc32(output_file, len_output)); Пример программы для восстановления crc32 прилагается к этой статье. Вот, собсно, и всё ^_6 Заключение Во многих статьях, и даже в некоторых книгах, писатели затрагивают тему контроля целостности WEP пакета, не понимая в полной мере, о чём говорят. Прослышав о том, что данные в пакете можно изменять, они тут же придумывают и описывают новые методы атак на WEP, которые на практике оказываются неосуществимы. Не буду показывать пальцем, но исправлю некоторые ошибки, которые встречал в статьях и книгах на эту тему. [*]Формула CRC(M xor D)=CRC(M) xor CRC(D), т.е. убеждение в линейности CRC - ошибка. CRC - функция полиноминальная, а не линейная. И ошибочность этой формулы вы можете проверить самостоятельно (функция расчёта значения crc в этой статье дана). А если вас замучает паранойя "а та ли эта функция, которая используется в WEP", то вам придётся либо ждать следующей статьи, либо самостоятельно расшифровать WEP пакет и посмотреть.. [*]При изменении байт в последовательности, нам без разницы, какие байты там были (важна лишь их контрольная сумма). А вот при вставке изменённых данных в зашифрованный пакет - важно. Иногда пишут что-то вроде "прибавьте к зашифрованному полю, где IP адрес, единичку, и при расшифровке этот IP изменится на эту единичку". Это верно не всегда, т.е. в общем случае не верно. Однако изменить IP адрес в зашифрованном пакете всё-таки действительно можно. [*]Контрольная сумма, т.е. значение crc32, не находится в открытом виде, а зашифрована вместе с передаваемым сообщением. Однако, во фрейме IEEE 802.11 может быть ещё одна контрольная сумма, но с WEP она не связана; она называется "FCS". Это контрольная сумма всего пакета вместе с заголовком IEEE 802.11, и передаётся в открытом виде. Она создаётся некоторыми wi-fi картами. На приёмной стороне к принятому пакету прибавляется заголовок "Radiotap". В этом заголовке есть такие поля как сила сигнала, мощность сигнала, наличие антенн у передатчика, ещё множество параметров и важный иногда для аудитора "наличие контрольной суммы FCS". Но об этом в следующей статье°. Используемая литература и приложения [*]Исходник программы для восстановления CRC32 на Си: http://flufx.narod.ru/_tmp/11/crc32-reverse.c [*]Вики "Циклический избыточный код": http://ru.wikipedia.org/wiki/Crc [*]Статья "CRC, и как его восстановить": http://www.ruscard.org/Files/crcrevrs.pdf [*]Статья "WEP. Антология уязвимостей и атак": http://www.ank-pki.ru/articles/wep_ank.html ________________________by f1ufx 2008-05-22 °Примечание редакции: Продолжение статьи скорее всего будет опубликовано по адресу http://kostapc.net.ru/~flufx