Трюки от КрысаКрис Касперски Борьба с хакерами на высоком сишном уровне (без ассемблерных вставок!) продолжается. Сегодня мы рассмотрим технику обфускации указателей на данные и функции, продемонстрировав системно-независимые подходы — легкие в реализации, но устойчивые к взлому. Обфускация указателей на данныеДизассемблеры и отладчики поддерживают мощные механизмы реконструкции перекрестных ссылок, опутывающих всю программу и образующих своеобразный несущий каркас, на который уже навешивается все остальное. Перекрестные ссылки — это артерии, автомагистрали и нити, связывающие крошечные лоскуты кода воедино. Допустим, у нас есть строка «wrong serial mumber» или «trial expired». Достаточно всего одного щелчка мыши, чтобы найти код, выводящий ее на экран. Следующий щелчок перенесет нас в материнскую функцию, осуществляющую проверку серийного номера/срока действия программы. Чтобы воспрепятствовать анализу алгоритма, достаточно ослепить механизм реконструкции перекрестных ссылок. Тогда программа распадется на ряд крошечных лоскутов, неизвестно каким образом связанных друг с другом. Возьмем, к примеру, вариацию на тему «hello, world»:
А теперь посмотрим, как выглядит ее дизассемблерный листинг, сгенерированный IDA Pro:
Как мы видим, IDA Pro автоматически реконструировала перекрестные ссылки на строки, упростив анализ программы до предела. Как этому помешать? Во-первых, мы должны предотвратить попадание незашифрованных указателей в код, сгенерированный компилятором. А во-вторых, — расшифровать указатели в манере, которую не поддерживают ни IDA Pro, ни популярные отладчики. Первая фаза решается тривиально. Оптимизирующие компиляторы поддерживают ряд математических операций (типа сложения и вычитания), вычисляя их еще на стадии трансляции. В результате, в код попадают зашифрованные указатели. Демонстрируется это в следующем примере:
Компилируем файл, загружаем его в IDA Pro и видим:
Вот так сюрприз! А где же наши зашифрованные указатели?! Программа осталась в первозданном виде. Оказывается, оптимизирующий компилятор, вычисливший значение «s1 + _KEY_» на стадии трансляции, вычислил и значение «s1 - _KEY_», автоматически расшифровав указатель s1. Как запретить это компилятору — причем, не какому-то одному отдельно взятому, а всем оптимизаторам сразу? Очень просто: достаточно раскрыть ANSI C и прочитать, что трансляторы не оптимизируют статические и глобальные переменные. Следовательно, для достижения полученного результата, первый проход шифрования надо осуществлять с константой, а второй — с глобальной/статической переменной. Законченный (в смысле, окончательный) пример реализации приведен ниже:
Программа незначительно усложнилась, зато результат превзошел все ожидания (дизассемблерный листинг ты можешь посмотреть в исходнике на диске). Получается, IDA Pro не только не реконструировала перекрестные ссылки, но и распознала указатели на s1 и s2, оставив их в зашифрованном виде. И хотя расшифровать значение указателя вполне возможно (достаточно проанализировать дизассемблерный код), — на это уходит время и, кроме того, все средства для постройки графов тушатся на корню. Достигается все — без применения ассемблерных вставок и прочих нестандартных извращений! Обфускация указателей на функцииЗашифровать указатели на функции намного сложнее, поскольку оптимизаторы не поддерживают математических преобразований над ними. Почему? А потому, что их не поддерживает стандарт. Тот самый, позволяющий законными средствами получить указатель на функцию, но сужающий меню доступных действий только до присвоения нуля — естественно, это не входит в наши планы. Запрет на математические преобразования легко обходится кастингом. В частности, 32?битные операционные системы (Windows 9x/NT, Linux, FreeBSD) используют плоскую модель адресного пространства и 32?битные указатели на код, которыми можно оперировать так же, как и целочисленным типом DWORD (unsigned int). В других случаях разрядность указателя может отличаться от обозначенной. Более того, он вообще может представлять собой сложную структуру, состоящую из селектора и смещения, а потому кастинг — уже хак. Но этот хак работает! Главное, вынести физический тип указателя на код в отдельный define, зависящий от платформы. Кастинг снимает защиту на математические операции с указателями на функции, но все преобразования выполняются на стадии выполнения программы, а вовсе не на стадии компиляции. В итоге, указатели «благополучно» переживают оптимизацию, попадая в машинный код целевого файла, где их распознает IDA Pro вместе с отладчиками. Проблема кажется неразрешимой, но… кто нам мешает доработать откомпилированный код уже после трансляции, зашифровав указатели непосредственно в двоичном файле и выполняя расшифровку уже в самой программе? Перед нами встает проблема поиска указателей в откомпилированном коде. Каково же будет наше решение? Самое простое — загнать указатели в структуру, предваренную специальным маркером — текстовой строкой или константой с уникальным содержимым. После чего нам остается только найти этот маркер в программе и зашифровать следующие за ним указатели. Это можно сделать как вручную в HIEW’е, так и автоматически, с помощью несложной программы. Но довольно слов, больше дела:
После компиляции программы мы должны найти в исполняемом файле «магическую» последовательность 0xDEADBEEF, наложив на следующее за ней двойное слово ключ шифрования 0x66666666 по XOR. Убедившись, что все выполнено правильно и программа работает, а не падает, загружаем ее в дизассемблер (смотри листинг из исходника на диске) и видим убийственный результат обфускации указателей на код и данные. Полный хаос и теперь сам черт не разберет, что это за код и какого он там делает! Да, конечно, при прогоне программы под отладчиком (или плагином-эмулятором для IDA Pro) хакер узнает значение регистра EAX, определив, какая функция тут вызывается. Но… наглядность дизассемблерного листинга необратимо утеряна. Механизмы реконструкции потока управления тихо курят в сторонке, высаживая хакера на измену и увеличивая время анализа программы на порядок-другой. |