Содержание статьи
Что же такое TLS и чем оно грозит хакерам? Начнем издалека. Популярные языки программирования (в том числе С) поддерживают статические и глобальные переменные, использование которых делает код потоконебезопасным. Все потоки разделяют один и тот же набор глобальных/статических переменных, порождая путаницу и хаос. Поток А положил в переменную foo значение X и только хотел прочитать его обратно, как внезапно пробудившийся поток B записал в foo значение Y, что оказалось для A полной неожиданностью.
Microsoft разработали специальный механизм, именуемый локальной памятью потока (Thread Local Storage, или сокращенно TLS), предоставляющий в распоряжение потоков индивидуальные наборы глобальных/статических переменных. TLS поддерживается как на уровне явно вызываемых API-функций (TLSAlloc, TLSFree, TLSSetValue, TLSGetValue), так и на уровне PE-формата, неявно обрабатываемого системным загрузчиком. PE-формат поддерживает функции обратного вызова (TLS-callback), автоматически вызываемые системой до передачи управления на точку входа. В частности, это позволяет определить наличие отладчика или скрытно выполнить некоторые действия. Системный загрузчик также записывает TLS-индекс в заданную локацию — отличный способ неявной самомодификации программы. Дизассемблерами она не отлавливается и заводит хакера в тупик.
TLS используется в большом количестве протекторов, защит, вирусов, crackme и прочих программ, взлом которых описан в куче различных туторалов. Однако изложение обычно носит поверхностный характер — целостной картины после прочтения не создается. Попробуем это исправить.
www
Обязательно изучи спецификацию PE от Microsoft и слей «крякми» с моего сайта.
Требуется помощь читателей!
К сожалению, файл buckme-crackme.zip не был выложен на DVD «Хакера», а сайт Криса больше не доступен. Если у тебя сохранился этот файл, пожалуйста, пришли его в редакцию на адрес content@glc.ru.
Fundamentals
Прежде всего нам понадобится спецификация PE-формата, последнюю версию которого (представленную в виде XML) можно утянуть прямо из‑под носа Microsoft. Тот же самый файл, только конвертированный в MS Word 2000, я выложил на своем сервере.
TLS-таблица описывается девятым (считая от нуля) четвертным словом в Optional Header Data Directories. Первое двойное слово хранит в себе RVA-адрес TLS-таблицы. Второе — ее размер, который игнорируется всеми известными мне операционными системами. Поэтому здесь можно писать что угодно, хоть 0, хоть FFFFFFFFh. Дизассемблерам это крышу не срывает, во всяком случае IDA-Pro, Olly и даже примитивный DUMPBIN работают как ни в чем не бывало. А вот проверка валидности размера TLS-таблицы может появиться в любой момент, так что лучше не прикалываться и писать то, что нужно.
TLS-таблица может находиться в любой секции с атрибутами [
(например, в секции данных). Некоторые линкеры помещают TLS-таблицу в специальную секцию .TLS или .TLS$ — это делается из чисто эстетических соображений. Системный загрузчик не проверяет имя секции. Правда, некоторые упаковщики не обрабатывают TLS, расположенные вне секции .TLS, но это уже их личные проблемы. Тем более что ряд упаковщиков, что такое TLS, не знает вообще.
Функции обратного вызова вызываются системным загрузчиком при инициализации/терминации процесса, а также при создании/завершении потока. Они имеют тот же самый прототип, что и DllMain:
Прототип функций обратного вызова
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) ( PVOID DllHandle, // Дескриптор модуля DWORD Reason, // Причина вызова PVOID Reserved // Зарезервировано);
Двойное слово Reason, информируя функцию обратного вызова, по какой причине она была вызвана, принимает следующие значения.
С функциями обратного вызова все понятно. Системный загрузчик просто вызывает их одну за другой, игнорируя возвращаемые значения и даже не требуя очистки аргументов из стека, — красота!
А вот с TLS-индексом все чуть‑чуть сложнее. Двойное слово по адресу FS:[
указывает на TLS-массив, содержащий данные локальной памяти потока для всех модулей. Чтобы не возникало путаницы, системный загрузчик при инициации модуля записывает по адресу Address of Index индекс данного модуля. То есть локальная память потока находится по адресу FS:[
. Теоретически index может принимать любые значения, известные только одной операционной системе, но практически он равен нулю для первого модуля и увеличивается на единицу для всех последующих. Если наш файл не загружает никаких DLL, использующих TLS, индекс будет равен нулю (с высокой степенью вероятности, но без всяких гарантий). Как же его можно использовать на практике? Самое надежное — записать в секцию данных число типа 12345678h и натравить на него индекс. После инициализации приложения мы получим что‑то «отличное от» — и дизассемблеры это не засекут!
Теоретическую часть будем считать законченной, приступим к практическим занятиям.
Ручное создание TLS
Для работы с TLS нам необходим компилятор и линкер, поддерживающий обозначенную технологию. Недостатка в таковых нет, хотя нет и полноценной поддержки TLS. Возможно, мы захотим прикрутить TLS к уже упакованной/запротекченной программе, следовательно, нам жизненно необходимо научиться создавать его руками. В случае EXE с убитыми фиксами это очень просто. С DLL уже будет сложнее, так как придется править таблицу перемещаемых элементов. Тут тоже есть свои хитрости и трюки… но сначала — EXE.
Пишем простую программу типа «Hello, world!». Компилируем ее и открываем полученный файл в HIEW. Идем в начало секции .data (по Enter переходим в hex-режим, F8 — для вызова PE-заголовка, F6 — Object Table, подводим курсор к .data и жмем Enter). Пропускаем инициализированные данные, подгоняя курсор к адресу .406100h (в другом случае адрес может быть иным), где и пишем такую магическую последовательность:
0 61 40 00 | 20 61 40 00 | 30 61 40 00 | 60 61 40 00
Ладно, на самом деле она никакая не магическая. Первая пара двойных слов означает начало/конец блока данных локальной памяти потока, который может находиться в любой области памяти, доступной на чтение. Третье двойное слово — адрес двойного слова, куда загрузчик запишет TLS-индекс. В нашем случае это 00406130h, где мы в HIEW ставим 66666666h (чтобы убедиться, что загрузчик действительно перезаписывает это значение). Последнее двойное слово — указатель на таблицу функций обратного вызова, расположенную по адресу 00406160h и содержащую указатель на единственный callback по адресу 00406190h, за которым следует ноль (указывающий, что других callback’ов здесь нет и не предвидится).
Что же касается самого callback’а, то, подогнав курсор к адресу 00406190h, легким нажатием Enter’а мы переходим в режим ассемблера. А тут пишем DEC
, Enter, RET. После чего сохраняем изменения по F9 и выходим, предварительно полюбовавшись на результат нашей работы (см. листинг 2). Ну а кому лень возиться с HIEW, может воспользоваться готовым файлом hello-TLS.exe.
TLS, созданный вручную (hex-дамп)
.00406100: 10 61 40 00-20 61 40 00-30 61 40 00-60 61 40 00 >a@ a@ 0a@ `a@.00406110: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406120: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406130: 66 66 66 66-00 00 00 00-00 00 00 00-00 00 00 00.00406140: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406150: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406160: 90 61 40 00-00 00 00 00-00 00 00 00-00 00 00 00 Рa@.00406170: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406180: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00.00406190: FF 0D 40 61-40 00 C3 00-00 00 00 00-00 00 00 00 d@a@ +
info
Исключения, возникающие внутри TLS callback’ов, давятся системой на автомате. Но зато отлавливаются отладчиками (той же «Ольгой»). При этом хакер не понимает, как это может работать. Что тут думать — надо жать Shift-F9 для передачи управления на точку входа!
Остается только занести TLS в таблицу директорий. В HIEW это делается так: открываем файл, переходим в hex-режим, давим F8 для вызова PE-заголовка, а следом — F10 для вызова директории таблиц. Подгоняем курсор к TLS и редактируем его по F3, вводя RVA-адрес начала TLS-таблицы (в нашем случае — 6100h) и размер (можно брать любой).
Боевое крещение
Загружаем hello-TLS.exe в отладчик (например, в «Ольгу») и ходим по адресу 00406100h. Мы четко видим, что двойное слово 66666666h по адресу 00406130h мистическим образом обратилось в ноль, зато нулевое двойное слово по адресу 00406140h, уменьшившись на единицу, превратилось в FFFFFFFFh — результат записи индекса и вызова callback’а, соответственно. Это произошло до того, как мы успели выполнить хотя бы одну команду, стоя в точке входа.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»