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

Спокойствие под прицелом

Леонид «Cr@wler» Исупов

Хакер, номер #105, стр. 068

(crawlerhack@rambler.ru)

Западлянки в стиле сrack

Привет, дружище! Наверное, у тебя есть много задумок того, как можно повеселить друзей. Я, признаюсь, большой любитель подстроить им западло - неважно, маленькое или большое =). Если не переусердствовать, то ты можешь даже остаться без синяка под глазом =). Главное - зашифроваться, чтобы никто ничего не заподозрил.

Наш план таков: берем стандартные приложения и немного проходимся по ним напильничком, то есть отладчиком, редактором ресурсов, шестнадцатеричным редактором, после чего они начинают вытворять разные несообразности: ругаться матом, закрывать окна и вообще вести себя совсем не так, как должны =).

Я долго думал над тем, что же такое сотворить, чтобы просто реализовывалось, максимально бесило, удивляло и смешило, и придумал-таки! Сначала займемся классикой, только не литературной, а крякерской. Будем учиться вставлять в программы окошки, которые выдают ржачные сообщения. Например, я хочу, чтобы при запуске измененного notepad.exe мой друг получал сообщение: «Данная область памяти зарезервирована под нужды компании Microsoft» =). Ничего идея, правда? Главное - не быть банальным и придумать что-то смешное.

Итак, готовь notepad.exe и другие стандартные приложения к «операции», посмотрим, как можно над ним поглумиться.

До чего докатился Microsoft =)

Первым делом мы пропатчим notepad.exe так, чтобы при каждой попытке сохранения файла он выдавал сообщение типа «Sorry, this drive is reserved for Microsoft (c) company!». Реализовать это очень просто. Откроем блокнот под отладчиком и посмотрим, что можно сделать. Как известно, запись в файл производится при помощи функции WriteFile из kernel32. Для того чтобы внедрить вызов процедуры выдачи сообщения (MessageBoxA) со всеми параметрами, нужно немало места. Поэтому я предлагаю наиболее «бескровный» метод. По идее, после процедуры записи файла должна производиться очистка области памяти, в которой находились записываемые данные, при помощи функции LocalFree. А перед этим ей передаются параметры через стек. Так вот, я предлагаю на место инструкций, передающих в стек эти самые параметры, внедрить переход на конец секции кода, куда мы поместим процедуру выдачи сообщения. И параметры при этом никуда не денутся! Мы сначала запихаем их в стек, затем просто восстановим инструкцией push, а после этого возвратимся обратно для выполнения LocalFree! Мы же не варвары, чтобы резать функции, результатом чего будет разбазаривание памяти =)! Приобщайся к крякерской культуре!

Итак, ставим точку останова на все функции WriteFile. Кстати, если возникнут какие-то вопросы по поводу работы отладчика, я советую тебе посетить www.wasm.ru - там имеется множество полезной документации на эту тему. Также рекомендую тебе почитать мои предыдущие статьи крякерской направленности, например «Приближение к Дао».

Теперь попробуем сохранить файл. Программа прервалась на функции WriteFile (по адресу 01004C2A), после которой действительно идет LocalFree. Запишем все инструкции, которые находятся между вызовами WriteFile и LocalFree:

01004C30 MOV ESI,EAX

01004C32 PUSH [EBP-8]

Могу сказать, что нам очень повезло. Безусловный переход требует 5 байт - как раз столько памяти и занимают эти две инструкции! Смело заменяем их инструкцией перехода на конец секции кода:

jmp 01008748

Далее выполняем эту инструкцию и оказываемся по адресу 01008748, откуда мы и начинаем писать наш западлостроительный код. Мы не будем сохранять регистры, так как это не повлияет на работоспособность программы, хотя хорошим тоном было бы сохранить и восстановить их при помощи, например, пары операций pushad/popad.

Итак, сначала положим в стек то, что находится по адресу [EBP-8]. Нас не интересует, что именно там хранится, мы просто забиваем в стек тот параметр для функции LocalFree, инструкцию передачи которого мы затерли командой перехода на наш код. Далее положим в стек параметры для функции MessageBoxA. Вот ее прототип:

int MessageBox(

HWND hWnd, // хэндл окна-предка

LPCTSTR lpText, // адрес текста для окошка

LPCTSTR lpCaption, // адрес заголовка для окошка

UINT uType // стиль окошка

);

Ниже я привожу наш несложный код полностью. Читай описание, и все станет понятно.

01008748 PUSH [EBP-8]; инструкция, которая была ранее затерта

0100874B PUSH 0; стиль окошка

0100874D PUSH 0; заголовок - по умолчанию

0100874F PUSH 01008762; указатель на текст окошка

01008754 PUSH 0; окошко не имеет владельца

01008756 CALL MessageBoxA; вызов MessageBoxA

0100875B MOV ESI,EAX; вторая потертая нами ранее инструкция

0100875D JMP 01004C34; передаем управление блокноту

01008762 54 68 69 73 20; >ASCII "This drive is re"

01008772 73 65 72 76 65; >ASCII "served for Micro"

01008782 73 6F 66 74 20; >ASCII "soft (c) Company"

Не забывай: стек работает по принципу FIFO, так что кладем параметры в обратном порядке. Сначала забиваем в стек байт-код стиля окошка. Пусть это будет нолик, то есть окошко будет содержать только одну кнопку - Ok. Второй параметр - тоже нолик, так как заголовок окна у нас будет по умолчанию - «Ошибка». Далее вводим адрес, по которому располагается текст. Давай сделаем так, чтобы текст находился сразу после нашего кода, начиная с адреса 01008762. Значит, вводим инструкцию push 01008762. Последний параметр - также нолик (владельца или предка у окошка нет). Сразу после передачи параметров находится вызов MessageBox’а. После него следует команда, которую мы заменили переходом на наш код. Ну и, наконец, самая последняя операция - передача управления основной программе по адресу 01004C34, где располагается вызов функции LocalFree. Тебе осталось только забить выдаваемое сообщение, начиная с адреса 01008762. Теперь сохраняй файл под другим именем и заменяй стандартный notepad.exe на машине своего друга. Пусть он удивится наглости мелкомягких =).

Добавлю только, что строка должна заканчиваться ноликом. Мы его не прописывали лишь потому, что после нашего кода и так одни сплошные нули. И еще: если ты будешь использовать метод в других случаях, не забудь вернуть все регистры в исходное состояние. Приведу пример: сначала я планировал сохранить регистры в стек, но забыл их забрать обратно, из-за этого при выполнении западлостроительного кода появлялся артефакт в виде окошка «Ошибка: Операция выполнена успешно» =). Так что будь аккуратен.

Операция «Подмена»

Сейчас мы будем глумиться над твоим другом еще более изощренными методами =). Мы сделаем так, чтобы при выводе любого системного сообщения (MessageBox’а) текст окошка был нами сочиненным! Представь, друг удаляет файл, а ему вместо «Вы действительно хотите удалить этот файл?» выдается что-нибудь вроде «Hello, my black brother!» =).

Для претворения этого коварного плана в жизнь мы попробуем изменить системную библиотеку User32.dll. Внимание! Ни журнал «Хакер», ни автор не будут возмещать ущерб, если ты что-то сделаешь без установленного драйвера и прямых рук! Так что перед выполнением этого западла потренируйся у себя на компе, а когда будешь практиковаться, обязательно сохраняй оригинальную библиотеку!

Итак, будем искать, где же располагается «нутро» API-функции MessageBoxA, с помощью отладки простого файлика example.exe, написанного на ассемблере (мы над ним изгалялись в статье «Программная оборона», помнишь?). Загружай его в отладчик. Если этого файлика у тебя нет, не беда! Запускай любую другую программу, где присутствует вызов MessageBoxA, и ставь точку останова на все call’ы. Загрузив программу под отладчиком, смело трассируй по <F8>, пока не дойдешь до вызова нашей функции MessageBoxA по адресу 0040100E. Теперь два раза нажимай <F7> для детальной трассировки. Ты тут же попадешь в системную библиотеку user32.dll по адресу 77d7050b. Адрес может быть и другим, все зависит от билда твоей ОС, но в данном случае это не играет никакой роли. Теперь трассируй по <F8> вплоть до вызова:

77D7054B CALL user32.MessageBoxExA

В эту функцию также заходи, чтобы посмотреть более подробно (<F7>). Итак, вот что мы видим, приземлившись в окрестностях адреса 77D7057D:

77D7057D MOV EDI,EDI

77D7057F PUSH EBP

77D70580 MOV EBP,ESP

77D70582 PUSH -1

77D70584 PUSH DWORD PTR SS:[EBP+18]

77D70587 PUSH DWORD PTR SS:[EBP+14]

77D7058A PUSH DWORD PTR SS:[EBP+10]

77D7058D PUSH DWORD PTR SS:[EBP+C]

77D70590 PUSH DWORD PTR SS:[EBP+8]

77D70593 CALL user32.MessageBoxTimeoutA

77D70598 POP EBP

77D70599 RETN 14

77D7059C NOP

77D7059D NOP

Здесь, как ты можешь догадаться, идет передача данных в стек, причем строка по адресу [EBP+C] - не что иное, как строка сообщения. Мы заменим ее своей. Как? Очень просто: в конце файла user32.dll есть массив ноликов, туда мы строчку и пихнем. Она будет располагаться по адресу, скажем, 77D8FDA8. Итак, двигайся вниз и вписывай по этому адресу свою строку.

Вернемся к нашему куску кода. Я не случайно включил в листинг два nop’а. Ведь вместо операции PUSH DWORD PTR SS:[EBP+C] нам придется втавить операцию помещения в стек данных, располагающихся по конкретному адресу! Естественно, реальный адрес - это далеко не смещение относительно регистра, и он занимает больше места в памяти. В нашем случае нужно где-то раздобыть 2 байта для помещения по адресу 77D7058D пятибайтной инструкции PUSH 77d8fda8. Тут-то на помощь нам и приходят два nop’а, которые ты видишь в самом конце листинга! Мы сдвинем код вниз на 2 байта и на освободившееся место поставим наш push.

Итак, вырезай инструкции, начиная с адреса 77D70590, и вставляй их по адресу 77D70592. После этого меняй PUSH DWORD PTR SS:[EBP+C] на PUSH 77d8fda8. Теперь, если ты выполнишь этот код, тебе придется начать все сначала, так что не спеши жать <F9>! Объясню почему. Заметил команду retn 14? Это команда возврата, но она работает не с абсолютными адресами, а со смещениями относительно текущего адреса. Мы сдвинули ее на 2 байта вниз. Теперь у нас есть два варианта: либо изменить адресный регистр, что попахивает извращением, либо поменять эту инструкцию на retn 12, что более адекватно. Поэтому меняй retn 14 на retn 12. Теперь наш код должен выглядеть так:

77D7057D MOV EDI,EDI

77D7057F PUSH EBP

77D70580 MOV EBP,ESP

77D70582 PUSH -1

77D70584 PUSH DWORD PTR SS:[EBP+18]

77D70587 PUSH DWORD PTR SS:[EBP+14]

77D7058A PUSH DWORD PTR SS:[EBP+10]

77D7058D PUSH 77D8FDA8

77D70592 PUSH DWORD PTR SS:[EBP+8]

77D70595 CALL user32.77D85FEA

77D7059A POP EBP

77D7059B RETN 12

Итак, теперь смело жми <F9>! Все работает. Мы обманули программу с помощью фейковых данных API-функции. Этот метод, конечно, годится для западла, но будет слишком заметен при другом применении. Изменение системных библиотек при создании руткита, к примеру, - слишком опасный своей заметностью метод, хотя он нередко используется. Теперь сохраняй библиотеку под другим именем (метод сохранения из-под отладчика, я думаю, тебе известен, а если нет, то отсылаю тебя к туторам по OllyDbg и опять же к моим предыдущим статьям). После этого заменяй стандартную user32.dll своей и радуйся, в то время как друг бесится, разыскивая висящие в памяти вредоносные процессы =).

Время математики

Перейдем к более традиционным методам измывательства. Ты, наверное, знаешь, что ресурсы в программе часто хранятся в виде форм, а текстовые данные - вообще в открытом виде. Этим мы и воспользуемся. Возьмем на прицел калькулятор и попробуем нарисовать на его кнопках веселые смайлы (ну и грустные тоже). Для этих целей задействуем мой любимый уникальный шестнадцатеричный редактор WinHex. Подойдет, конечно, и любой другой портируемый редактор, который можно принести к товарищу на флешке =).

Вот в чем заключается мой план: открываем calc.exe под редактором, жмем <Ctrl-F> для поиска и вводим в поле поиска надпись, находящуюся на кнопке, на которую мы хотим влепить смайл. Только тут следует учесть один момент: надписи, естественно, хранятся в юникоде, следовательно, в окне поиска ты должен это указать, иначе строчка просто не будет найдена. Итак, вводи, например, Sin и нажимай <Enter>. Теперь заменяй ее смайлом, но имей в виду, что, так как мы работаем с юникодом, каждый символ закодирован двумя байтами, причем для букв английского алфавита второй байт будет нулевым. Например, строка Sin в побайтовом представлении будет выглядеть так: 53 00 69 00 6E 00. Естественно, менять мы будем только ненулевые байты. Если мы захотим поменять Sin на смайлик «=)», то третий символ (n) можно затереть пробелом. В таком случае побайтовое представление строки будет следующим: 3D 00 29 00 20 00. 20 00 - это код пробела в юникоде.

Тут все предельно понятно, но нужно учесть и еще одну деталь. Надписи, располагающиеся на кнопках, могут дублироваться два или даже три раза, так как калькулятор работает в двух режимах: обычном и инженерном, а для каждого режима в программе предназначена своя форма. Так что найди все вхождения строки в файл и поменяй их на требуемый смайлик!

С другими кнопками все делается аналогично! Все очень просто и эффективно. На диске ты можешь найти замечательный модифицированный калькулятор =).

Неполадки в печати

Сейчас мы попытаемся сделать одну очень любопытную вещь: мы перехватим управление у системы в то время, когда она будет передавать приложению данные для печати на принтер, и заменим эти самые данные своими. Думаю, мы не будем разгребать спулинг и путаться с прочими «системностями», а попробуем решить проблему на уровне конкретного приложения. Целью нашей будет тот же блокнот (ты можешь взять WordPad или - чем черт не шутит - даже Word).

Итак, открываем отладчик, подгружаем notepad.exe и начинаем грести =). Я так думаю, что ни одно приложение не рискнет печатать текст, не зная его фактической ширины. Значит, берем в зубы любимые справочники по API (или лезем за помощью к Microsoft) и обнаруживаем, что ширину текста запросто можно получить посредством использования GetTextExtentPoint32W. Это интересно! Ставим точку останова на все ее вызовы и копаем дальше. Теперь вводим в блокнотик любой текст и приказываем ему распечатать его. Перед самой печатью мы прервемся по адресу 010066E8, где и происходит вызов:

CALL DWORD PTR DS:[<&GDI32.GetTextExtentPoint32W>]

Это очень хорошо! Давай трассировать дальше, но по <F8>, чтобы не затягивать процесс =). Итак, после того как я 38 раз нажал <F8>, я остановился там, где надо =):

01006CD3 MOV ECX,DWORD PTR SS:[EBP-33C]

Перейдем в окошке стека по адресу [EBP-33C]. Там и располагается указатель на наш текст ([ebp-33c]=0006F4C4h). Нам нужно знать, когда этот текст используется, чтобы вовремя его подменить. Можно, конечно, поставить точку останова на область памяти (по событию чтения ячейки), но даю гарантию, что в таком случае на нашу долю выпадет масса ложных срабатываний. Нам головная боль ни к чему, поэтому мы пойдем другим путем: поставим брейкпоинт на вызовы функции DrawTextExW и опять попробуем начать печать. Ставим точку останова и запускаем процесс печати. Вот мы и вычислили вызов DrawTextExW! Он выглядит так:

01006C9C CALL DWORD PTR DS:[<&USER32.DrawTextExW>]

Итак, посмотрим на передаваемые параметры (набор инструкций push прямо перед вызовом самой функции). Нас среди них больше всего интересует вот эта строчка:

01006C8C PUSH DWORD PTR SS:[EBP-33C]

В [EBP-33C] располагается указатель на адрес, по которому находится текст, подлежащий печати! Мы можем поступить так: написать код, который прямо перед этим адресом разместит указатель на нашу строчку (а это будет строка «][akep» =)). Код должен вызываться, естественно, перед передачей параметров в DrawTextExW. Ты, конечно, спросишь, почему мы не стали производить замену указателя, который располагается по адресу [EBP-33C]. Казалось бы, реализовать это намного проще, чем помещать указатель по другому адресу, что сопряжено с изменением инструкции, кладущей в стек текстовую строку, с которой оперирует функция DrawTextExA. С другой стороны, если какой-либо части программы потребуется этот указатель, а он окажется измененным, не избежать ошибок! Поэтому договоримся, что разместим его по адресу [EBP-340]. Из этого вытекает, что инструкцию по адресу 01006C8C нужно поменять на PUSH [EBP-340]. Это мы сразу и делаем, чтобы не забыть.

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

01006C69 TEST EAX,EAX

01006C6B JLE NOTEPAD.01006D94

Вот эти две инструкции мы и заменим операцией перехода на наш код. Записывай их на бумажку и смело меняй на jmp 01008748. Теперь двигайся к адресу 01008748, будем писать основную часть. Итак, вот что мы имеем: мы испортили две инструкции, начиная с адреса 01006С69, заменив их jmp. Теперь нам придется их выполнить в теле внедряемого кода (в самом конце, перед передачей управления обратно блокноту). Более того, так как результат их верного выполнения зависит от значения регистра EAX, придется его значение сохранить в стек еще до начала выполнения кода и восстановить после (то есть к нашему «жучку» добавляются две операции: push EAX и pop EAX). Кроме того, сразу после нашего кода (например, по адресу 1008763) необходимо разместить строку, которая будет напечатана на принтере вместо текста пользователя. А указатель на нее мы разместим в памяти при помощи пары инструкций LEA/MOV. Первая положит в регистр адрес, а вторая перенесет его в конкретную ячейку (в нашем случае это [EBP-340]). Итак, теперь посмотри, как выглядит готовый код, и тебе все станет понятно:

01008748 PUSH EAX; сохраняем регистр EAX

01008749 LEA EAX,[1008763]; помещаем в EAX указатель на нашу строку

0100874F MOV [EBP-340],EAX; помещаем значение из EAX по адресу [EBP-340]

01008755 POP EAX; восстанавливаем регистр EAX

Далее включаем в код две операции, которые мы заменили jmp:

01008756 TEST EAX,EAX

01008758 JLE 01006D94

И, наконец, передаем бразды правления приложению:

0100875E JMP 01006C71; передача управления блокноту

Как видишь, команда LEA EAX,[1008763] помещает в регистр EAX физический адрес нашей строки. Надеюсь, ты уже догадался и разместил ее, начиная с адреса 1008763? Если нет, то скажу, что текст необходимо вводить в юникоде. OllyDbg на это способна. Просто выделяешь требуемый блок данных, жмешь <Ctrl-E> и в поле Unicode вводишь свою строку (я, как уже говорил, ввел «][akep» =)). Теперь сохраняй все изменения и запускай модифицированный notepad.exe! И, если ты сделал все так, как я советовал, вместо набранного текста принтер упорно будет печатать нашу строку: «][akep»! Теперь дело за малым - неси файл к товарищу или админу в универе =).

Удачи в делах!

Ну вот мы и освоили искусство низкоуровневой игры на нервах =)! Пришла пора закругляться. Я думаю, твои жертвы уже разобрали весь валидол в ближайших аптеках =). Напоследок скажу, что у тебя есть два варианта на выбор: делать темные делишки на месте, пользуясь принесенным на флехе portable-софтом, либо притаранить в гости уже готовый экзешник или dll. Предложенные здесь идеи могут быть модифицированы по твоему усмотрению, стоит только подключить фантазию. Особенно неисчерпаема тема западла с применением патчинга API. Да, и еще раз напомню: такие шутки чреваты потерями данных, так что будь осторожен, не лиши друга дипломной работы! Если у тебя есть какие-либо оригинальные идеи, пиши! Удачного западла =D.

DVD

На нашем DVD ты найдешь все программы, которые были созданы и использованы в процессе работы.

VIDEO

На нашем DVD ты найдешь видеоролик, в котором проиллюстрированы все описанные в статье методы. Поэтому, если у тебя что-то не выходит, обратись к диску!

Содержание
ttfb: 44.732093811035 ms