Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #104, АВГУСТ 2007 г.

Диплом за 24 часа

Porosenok (p0r0sen0k@yandex.ru)

Хакер, номер #104, стр. 070

Блестящая защита по-хакерски

Долгожданное лето, конечно, не может не радовать. Но вместе с наступлением лета подошел к концу и учебный год. У одних студентов в полном разгаре сессия, а у других настал тяжелый период в жизни под названием «ДИПЛОМ». Вот и моему товарищу также предстояла защита диплома. Все бы ничего, но до защиты оставалось всего лишь несколько дней :(. Видя, что он уже практически впал в отчаяние и смирился с мыслью о том, что придется защищать диплом на следующий год, я решил ему помочь.

Тема его диплома была следующая: «Разработка автоматизированной системы удаленной диагностики компьютера». Если в нескольких словах, то система должна иметь клиент-серверную технологию, задачей клиентов которой является сбор и отправка значений датчиков (температура процессора, скорость вращения кулера и т.д.) на сервер.

Поиск решения

Первоначально для получения данных от датчиков предполагалось использовать WMI-технологию, но после нескольких часов, проведенных за работой, нам так и не удалось достичь желаемых результатов. Кроме того, в интернете была найдена статья, в которой упоминалось о безуспешной попытке получения скорости вращения кулера с использованием WMI-технологии. Тогда возник вопрос: каким же образом программы аналогичной тематики получают интересующие нас значения? Для исследования была выбрана программа Everest.

В папке с программой находится около двух десятков файлов. Сразу и не сообразить, где располагается код, отвечающий за получение данных от датчиков. Поэтому, чтобы избавиться от лишнего, я стал последовательно переименовывать файлы и запускать программу, проверяя, отображаются или нет нужные нам данные. В итоге осталось всего лишь четыре файла: everest.exe, everest.bin, kerneld.wnt и everest.key, назначение которых нам предстоит выяснить.

Судя по размеру файла everest.exe, который составляет всего 65 Кб, напрашивается вывод, что он не отвечает за получение информации о системе, а выполняет какие-то другие действия. Для того чтобы выяснить, какие именно, откроем его в отладчике OllyDbg. Но для начала проверим, запакован он или нет. PEiD показал, что программа запакована UPX’ом, поэтому для распаковки выполним команду upx.exe -d everest.exe. Прежде чем приступить к отладке, посмотрим, вызовы каких функций используются в программе, для этого откроем окно Found intermodular calls («ПК -> Search for -> All intermodular calls»). Сразу же привлекает внимание вызов CreateProcess. Жмем на нем <F2> для установки брейкпоинта, а затем и <F9> для запуска программы. Через несколько мгновений срабатывает бряк и выполнение программы прекращается. В качестве имени выполняемого файла функции передается строка, содержащая everest.bin. Предположение оказалось верным - выходит, что основная работа выполняется в everest.bin. Поэтому делаем следующее: переименовываем everest.bin в everest_bin.exe и запускаем на выполнение.

После запуска программа выдала сообщение об окончании срока лицензии и закрылась. Значит, что файл everest.exe отвечал за проверку регистрации программы. Но ничего страшного, это мы поправим. Открываем everest_bin.exe в отладчике, не забыв сначала его распаковать все тем же UPX’ом. Программа загружена, но прежде чем нажать <F9>, поставим брейкпоинты на вызов MessageBox. После запуска сработает бряк по адресу 0x00695249h. Посмотрев немного выше, нельзя не заметить команду JBE everest_.0069525A, которая очень походит на проверку на зарегистированность. Чтобы развеять все сомнения, меняем JBE на JMP и перезапускаем программу. Все работает отлично, идем дальше.

При отображении скорости вращения кулера выводится заголовок Cooling Fans, попробуем его поискать в окне отладчика Text strings referenced («ПК -> Search for -> All referenced text strings»). Такая строка действительно есть, и по адресу 0x5AECCB осуществляется взятие ее адреса, ставим бряк. Прервавшись на бряке, начинаем выполнять программу в пошаговом режиме с помощью <F8>. По адресу 0x5AECDC в EDX из памяти по адресу 0x02E9FA04 помещается число 0x96B, десятичное 2411, то есть количество оборотов кулера (смотри скрины). Теперь попробуем выяснить, где оно формируется. Для этого перейдем на начало функции, в которой мы находимся в данный момент и поставим бряк. Для того чтобы определить момент записи числа 0x96B в память, перейдем в окне дампа памяти по адресу 0x02E9FA04. Итак, выполнение программы прервалось на бряке, поставленном в начале функции. В памяти по адресу 0x02E9FA04 находятся нули. Теперь начинаем выполнять программу в режиме анимации, без входа в процедуры (<Ctrl-F8>), и одновременно смотрим в окно дампа. После строчки «CALL everest_.005A37E8» в дампе оказалось число 0x96B. Поэтому заходим в эту функцию и ставим в ее начале бряк. При следующем проходе, прервавшись на нем, снова жмем <Ctrl-F8> (забыл сказать <F12> - пауза). Таким образом, мы дошли до адреса 0x595193, где чуть ниже из регистра AX в память записывается количество оборотов. При этом значения [LOCAL.7] и [LOCAL.8] равны 0. Теперь нам нужно найти место, где производится запись в [LOCAL.9].

/*595193*/ MOV EAX,[LOCAL.7]

/*595196*/ ADD EAX,[LOCAL.8]

/*595199*/ ADD EAX,[LOCAL.9]

/*59519C*/ MOV DWORD PTR DS:[EBX+998],EAX

Такое место находится чуть выше, по адресу 0x59502C.

/*594FEB*/ MOV AL,2A

/*594FED*/ CALL everest_.0058801C

/*594FF2*/ MOV ESI,EAX

.

.

.

/*59500C*/ IMUL ESI,[LOCAL.11]

/*595010*/ MOV [LOCAL.81],ESI

/*595016*/ FILD [LOCAL.81]

/*59501C*/ FLD DWORD PTR DS:[59F5AC]

/*595022*/ CALL everest_.004032FC

/*595027*/ CALL everest_.00402C5C

/*59502C*/ MOV [LOCAL.9],EAX

Здесь происходит следующее: с помощью команд сопроцессора значение, находящееся по адресу 0x59F5AC (=1350000), делится на произведение ESI (=0x46) и [LOCAL.11] (=8). Деление выполняется в вызове «CALL everest_.004032FC». В результате анализа кода было выяснено, что значения [LOCAL.11] и [0x59F5AC] постоянны. В ESI же записывается результат вызова подпрограммы, находящейся по адресу 0x0058801C. Внутри этой подпрограммы находятся два CALL’а (0x588047 и 0x588056), внутри которых происходит вызов DeviceIoControl. Выходит, что программа для получения сведений от датчиков использует обращение к драйверу. Про работу с драйверами можешь почитать в литературе. Скажу лишь, что для общения программ прикладного уровня с драйверами используются запросы, посылаемые с помощью DeviceIOControl.

BOOL DeviceIoControl

(

HANDLE hDevice, // дескриптор устройства

DWORD dwIoControlCode, // код операции

LPVOID lpInBuffer, // буфер входных данных

DWORD nInBufferSize, // размер буфера входных данных

LPVOID lpOutBuffer, // буфер данных результата

DWORD nOutBufferSize, // размер буфера результата

LPDWORD lpBytesReturned, // адрес данных для вывода

LPOVERLAPPED lpOverlapped // адрес перекрывающей структуры

);

Ну и что теперь делать, если с WMI ничего не вышло? Не писать же свой драйвер, когда сроки поджимают… А что если взять уже готовый. Это, конечно же, плагиат, но диплом-то дороже :)! Наша задача сводится к следующему:

1) узнать, какой драйвер используется;

2) узнать, какие данные передаются драйверу с помощью DeviceIoControl;

3) написать свое собственное приложение.

Итак, так как первый параметр функции DeviceIoControl - это дескриптор устройства, который возвращает функция CreateFile, то ставим брейкпоинт на все вызовы CreateFile и перезапускаем программу. Сработал брейкпоинт, в качестве первого параметра FileName функции передается строчка «C:\Program Files\EVEREST Ultimate Edition\everest.key». Нас это мало интересует, поэтому жмем <F9> еще несколько раз, пропуская не интересующие нас вызовы, пока не увидим в стеке строку «\\.\EverestDriver». Запомним значение, возвращаемое функцией, поскольку оно нам еще пригодится (у меня оно равно 0x98). Может возникнуть вопрос: «Откуда же взялся этот драйвер?» Если посмотреть в каталог с программой, то можно увидеть, что у нас остался еще один файл, назначение которого пока еще неизвестно, - kerneld.wnt. Именно он и является драйвером. В этом можно убедиться по вызовам функций IoCreateDevice и IoCreateSymbolicLink, находящихся в нем и создающих виртуальное устройство и символическую ссылку на него. Первый пункт нашего плана выполнен, переходим ко второму. Установим брейкпоинты на адреса 0x588047 и 0x588056, по которым производится вызов DeviceIoControl. Прервавшись, внимательно переписываем значения передаваемых функциям параметров. В первом случае параметры следующие:

hDevice = 0x98

dwIoControlCode = 0x8010205C

lpInBuffer = 0x042BE6E0 //{0x95,0x02,0,0,0x2A,0,0,0,0,0,0,0}

nInBufferSize = 0xC

lpOutBuffer = 0x042BE6E0

nOutBufferSize = 0xC

lpBytesReturned = 0x042BE6DC

lpOverlapped = NULL

hDevice равно значению, которое было получено от функции CreateFile при открытии «\\.\EverestDriver». Следовательно, мы не ошиблись. Параметры функции при втором вызове следующие:

hDevice = 0x98

dwIoControlCode = 0x80102058

lpInBuffer = 0x042BE6DC //{0x96,0x02,0,0,0,0,0,0,0,0,0,0}

nInBufferSize = 0xC

lpOutBuffer = 0x042BE6DC

nOutBufferSize = 0xC

lpBytesReturned = 0x042BE6D8

lpOverlapped = NULL

После второго вызова содержимое выходного буфера следующее: {0x96,0x02,0,0,0x46,0,0,0,0,0,0,0}. Пятый по счету параметр - это будущее значение ESI при вычислении количества оборотов. Теперь можно переходить и к третьему пункту плана.

Реализация

В качестве среды программирования я выбрал VisualC++ 6.0, и уже через несколько минут у меня было рабочее приложение. После этого я аналогично реализовал получение температуры процессора и еще кое-какие данные, а домашним заданием товарища стала реализация загрузки драйвера с помощью SCM-функций и обеспечение передачи данных от клиентов к серверу.

HANDLE hFile=0;

DWORD returnByte=0;

long double speed;

char Buf1[12]={0x95,0x02,0,0,0x2A,0,0,0,0,0,0,0};

char Buf2[12]={0x96,0x02,0,0,0,0,0,0,0,0,0,0};

SERVICE_STATUS servStat;

hFile=CreateFile("\\\\.\\EverestDriver",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

if((int)hFile==-1)

{

::MessageBox(0,"Драйвер не загружен","Ошибка",MB_OK);

return;

}

DeviceIoControl(hFile,0x8010205C,Buf1,12,Buf1,12,&returnByte,NULL);

DeviceIoControl(hFile,0x80102058,Buf2,12,Buf2,12,&returnByte,NULL);

speed=1350000/(Buf2[4]*8);

Happy end

В итоге на следующий день товарищ защитился на отлично и стал дипломированным специалистом, а я получил в качестве вознаграждения энное количество пива :).

Содержание