• 7575
  • 0
  • 1
Нравится блог?
Подписывайтесь!

Полуперемещаемый код

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

program Project1;
uses Dialogs;
begin
ShowMessage('Пример');
end.

Ставим бряк(breakpoint) на ShowMessage и запускаем. Смотрим окно CPU (если ты уже забыл,
то его удобнее вызывать с помощью CTRL-ALT- C):
http://www.stranger.nextmail.ru/del4.jpg
По адресу $00451F71 лежит команда E8D2FBFFFF E8 – это CALL (относительный ближний
вызов). Запускаем калькулятор, переключаем в HEX-режим (4 байта) и считаем:
$451F71 + $5 + $FFFFFBD2 = $451B48;
5 - длина команды CALL; FFFFFBD2 - смещение (не забывай, что младшие байты числа лежат
по младшим адресам). Получаем $451B48 и переходим по этому адресу (CTRL-G):
http://www.stranger.nextmail.ru/del5.jpg
Попали как раз в начало функции ShowMessage! Надеюсь я понятно объяснил относительную
адресацию.
Теперь представь, что мы переместили код, содержащий вызов функции ShowMessage, куда-
нибудь в кучу. И оказался ее вызов где-нибудь по адресу $00860828. Если снова посчитать
смещения перехода, то получится адрес $008603FF, естественно здесь никакого вызова
ShowMessage не будет, а будет ошибка доступа.
Таким образом, чтобы создать перемещаемый код, надо научиться вызывать функции не по
относительному смещению, а по абсолютному адресу. Что тебе это даст? Ты сможешь
переместить код своей функции в другое место, например в кучу, стек, в адресное пространство
какой-нибудь dll’ки, и спрячешь ее вызов от отладчика.
В общем идея состоит в том, чтобы вызывать функцию по указателю. А для того, чтобы
нормально использовать параметры, этот указатель будем приводить к прототипу этой самой
функции. Полное извращение. :) Смотри сам:

program Project1;
uses Dialogs;
// Прототип функции ShowMessage
type _ShowMessage = procedure (const Msg: string);
begin
// Вызов функции по указателю
_ShowMessage(@ShowMessage)('Test');
end.

Снова запускаем пример и смотрим, во что превратился вызов ShowMessage:
http://www.stranger.nextmail.ru/del6.jpg
Здесь в регистр EBX непосредственно заносится уже знакомый тебе адрес $451B48, по
которому расположена функция ShowMessage. Потом происходит CALL на это содержимое
регистра EBX. Никаких относительных смещений нет и этот код будет работать в любом месте
адресного пространства твоей программы!
Теперь настало время объяснить, почему я назвал этот код полуперемещаемым. Дело в том, что
этот код будет работать только в адресном пространстве твоей программы. Если ты
попробуешь внедрить его в другую прогу, то снова будет ошибка доступа. Почему? Да потому,
что абсолютный адрес $451B48 имеет значение только для твоей проги, а в другой по этому
адресу будет лежать что-то совсем другое, но точно не функция ShowMessage. Вот так-то.
Кроме того у метода вызова функций по абсолютному адресу есть еще одно совершенно
неочевидное ограничение. С API-функциями Windows никаких проблем нет. Но вот в Delphi
все функции делятся на настоящие и ненастоящие(встроенные). Отличить их легко и ты
сталкивался с этим не раз. Если навести мышку на имя функции и кликнуть по нему, удерживая
CTRL, то тебя забросит в место, где эта функция определяется, и ты сможешь посмотреть ее
код. Но это относится только к настоящим функциям.
Если ты сделаешь то же самое скажем с функцией AssignFile, то тебя забросит куда-то в начало
модуля System. Так по крайней мере в Delphi 7. Это пример ненастоящей (встроенной)
функции. На место ее вызова компилятор вставляет целый блок своего кода. Поэтому с
ненастоящими функциями вызов по указателю не прокатит. Если ты попробуешь сделать так:

type _AssignFile = procedure (var f: File; FileName: String);
var f: File;
begin

_AssignFile(@AssignFile)(f,'test.txt');

end.

то компилятор просто откажется тебя понимать и нивкакую не будет компилировать код. Имей
это ввиду. Решить эту проблему можно, сделав функцию-переходник, которая будет уже
вызывать встроенную функцию. В демо-проекте токой переходник сделан для функции
FreeMem.
Еще одна засада поджидает тебя при использовании строк. В нашем примере в функцию
ShowMessage передается непосредственно указатель на строку, но так бывает далеко не всегда.
Строки в Delphi это совсем непростые динамические структуры данных, для обработки которых
компилятор вставляет специальный код. Там происходит выделение памяти в куче, всякие
контроли границ, обработка исключений и т.п. Лучше всего типом String не пользоваться, а
создавать строку непосредственно в функции в виде символьного массива. Примерно так:

var
str: array[0..4] of Char;

str[0] := 'T';
str[1] := 'e';
str[2] := 's';
str[3] := 't';
str[4] := #0;

Это конечно неудобно, но зато надежно. Еще вариант – передавать указатели на строки с
параметрах функции. Есть и будут другие подводные камни, например при обработке
исключительных ситуаций. Поэтому не забывай про отладчик и постоянно контролируй, чего
тебе Delphi там накомпилировала.
В качестве рабочего примера мы сейчас соорудим секретную функцию, которая будет
прятаться в стеке и записывать имя пользователя в файл.
// Пользовательская функция освобождения памяти
// Заменим этой функцией ненастоящую функцию FreeMem

procedure FreeMemory(P: Pointer);
begin
FreeMem(P);
end;

// Секретная функция, которая будет работать в стеке
//Получает в параметрах указатели на строки сообщений
function Secret(pTitle: PChar; pMessage: PChar): Boolean; stdcall;
type
// Прототипы использованных Функций
_AllocMem = function(Size: Cardinal): Pointer;
...........
_MessageBox = function (hWnd: HWND; lpText, lpCaption: PChar;
uType: UINT): Integer; stdcall;
var
FileName: array[0..5] of Char;
FileHandle: Integer;
UserName: PChar;
NameLength: Cardinal;
begin
Result := False;
//Резервируем память под имя пользователя.
NameLength := 256; // Максимальная длина имени пользователя
UserName := _AllocMem(@AllocMem)(NameLength);
// Получаем имя пользователя
_GetUserName(@GetUserName)(UserName,NameLength);
// Создаем строку с именем файла 'х.txt'
FileName[0] := 'x';
FileName[1] := '.'; // Строки надежнее всего создавать так.
FileName[2] := 't';
FileName[3] := 'x';
FileName[4] := 't';
FileName[5] := #0 ; // Не забываем терминальный нуль
// Создаем файл для записи. В модуле SysUtils есть функция FileCreate, но
// она использует в параметрах тип String, что приводит к вставке
// незапланированного кода, поэтому ее не используем.
FileHandle := Integer(_CreateFile(@CreateFile)(@FileName,GENERIC_WRITE,
0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0));
dec(NameLength); // Обрубаем хвостовой нуль
//Пишем в файл имя пользователя
if _FileWrite(@FileWrite)(FileHandle,UserName^, NameLength) = NameLength
then Result := True;
// Освобождаем ресурсы.
_FileClose(@FileClose)(FileHandle);
// Вызов ненастоящей функции FreeMem мы заменим
// на нашу настоящую функцию FreeMemory
_FreeMemory(@FreeMemory)(UserName);
// Хвалимся успехами
if Result then _MessageBox(@MessageBox)(0,pMessage,pTitle,0);
end;

Часть прототипов функций и различные проверки опущены для краткости (смотри их в
прилагаемом демо-проекте). Имя пользователя получаем как обычно с помощью функции
GetUserName и записываем его в файл x.txt. В конце выскакивает окно сообщения с текстом,
который передали функции в параметрах. Функция объявлены как stdcall, чтобы четко
определиться, что параметры передаются через стек (а не через регистры, как в Delphi по
умолчанию), чтобы удобнее было за ними следить в отладчике.
Теперь остается зарезервировать память в стеке, переместить туда нашу функцию и передать на
нее управление. Для резервирования памяти просто объявим локальный байтовый массив, т.к.
все локальные переменные резервируются в стеке.

procedure Main;
var
buf: array[0..511] of Byte; // Место в стеке c запасом


Ну вот. В стеке у нас есть место на полкилобайта – с запасом на всякий случай. Осталось
скопировать туда функцию Secret. Для копирования можно использовать CopyMemory или
Move.
Move(Secret,buf,FuncLen);
Стоп! А чему равна FuncLen - длина функции Secret? Здесь придется делать допущение, что в
скомпилированном коде функции будут располагаться в том же порядке, как мы написали в
исходнике (и это действительно так). Поэтому если сделаем так:

program Project1;
...
function Secret(pTitle: PChar; pMessage: PChar): Boolean; stdcall;
...
procedure Main;
begin
//Здесь копируем в стек функцию Secret
...
end;
begin
Main;
end.

и получается, что длину функции можно будет вычислить путем вычитания адреса функции
Secret из адреса функции Main:

FuncLen := Cardinal(@Main) - Cardinal(@Secret);

И еще один момент. Раньше можно было без проблем выполнять код, расположенный в стеке,
но теперь с этим стали жестоко бороться и запрещать исполняемый стек программными а
аппаратными средствами. (если хочешь знать больше, то ищи статьи про DEP - Data Execution
Prevention) По идее это должно привести с уменьшению количества атак типа buffer overflow
(переполнение буфера). Поэтому, чтобы код работал везде, надо явно разрешить исполнение
кода в странице памяти стека (куда мы копируем функцию). Станица памяти имеет размер 2 кб
и с помощью VirtualProtect нам нужно изменить атрибуты 2-х страниц, на тот случай, если
функция окажется в конце первой страницы.

VirtualProtect(buf, 2, PAGE_EXECUTE_READWRITE, OldPageProtection);

В общем виде код функции Main выглядит так:

procedure Main;
var
FuncLen: Cardinal; // Размер кода функции
buf: array[0..511] of Byte; // Место в стеке c запасом
MovedFunction: _Secret; // Указатель на функцию в стеке
OldPageProtection: Cardinal;// СТарые аттрибуты страницы памяти
title, ok_string: String; // Строки для всплывающего окна
begin
title := 'Пример выполнения кода в стэке';
ok_string := 'РАБОТА В СТЕКЕ: Файл успешно создан.';
// Загоняем функцию Secret в стэк
FuncLen := Cardinal(@Main) - Cardinal(@Secret);
Move(Secret,buf,FuncLen);
MovedFunction := @buf; //Устанавливаем указатель на начало функции в стеке
// Устанавливаем в стеке аттрибут выполнения кода
if not VirtualProtect(@buf,2,PAGE_EXECUTE_READWRITE,OldPageProtection)
then Exit;
try
// Запускаем функцию в стеке
if MovedFunction(PChar(title), PChar(ok_string)) then
ShowMessage('Работа в стеке прошла успешно.')
else
ShowMessage('Ошибка при работе в стеке.');
// Восстанавливаем аттрибуты страниц
VirtualProtect(@buf,FuncLen,OldPageProtection,OldPageProtection);
except
ShowMessage('Hello hacker!');
end;
end;

Код полностью откомментирован. Вопросов быть не должно. Вызов секретной функции
находится в блока trу..except. Это надо делать в любом случае. Мало ли что может произойти у
пользователя на компе. Но нам обработка исключений потребуется для совершенно
конкретного случаю – ловли взломщика! J
Компилим экзешник (до сих пор использую Delphi 7) и запускаем его. Появляются подряд два
окошка, которые сообщают, что все идет нормально, а каталоге программы создается файл x.txt
с именем пользователя.
Теперь попробуем поймать вызов секретной функции. Мы знаем, что имя пользователя
получается с помощью функции GetUserName. Поэтому загружаем экзешник в отладчик
OllyDbg и ставим точку останова на функции GetUserNameA (bpx GetUserNameA в командной
строке). Запускаем на выполнение - вываливается сообщение «Hello hacker!», а отладчик
всплывает где-то в ntdll.dll Файл x.txt не создается. Тоже самое происходит при попытке
отладить секретную функцию с помощью стандартного отладчика Delphi. Но вот если
поставить железный бряк (hardware on execution), то отладчик послушно всплывает посередине
стека и позволяет полностью протрассировать код секретной функции.
Получился довольно хитрый антиотладочный прием против начинающих взломщиков.
Разберемся, как он работает? Обязательно! Когда отладчик ставит бряк, то он ставит на это
место специальную инструкцию int 3 (опкод CC), которая вызывает отладочное исключение.
Это исключение отладчик отлавливает и сразу заменяет это CC на тот байт, который стоял на
этом месте до установки бряка.
А теперь смотри, что получается у нас. Вот окрестности вызова GetUserNameA в стеке. Серым
подсвечена инструкция, которую заменит отладчик.
http://www.stranger.nextmail.ru/del7.jpg
Точка останова (инструкция int3) ставится в секции кода на вызове функции GetUserNameA, но
этот код никогда не исполняется. Когда мы копируем функцию в стек, то вместе с остальным
кодом копируется и инструкция int3.
http://www.stranger.nextmail.ru/del8.jpg
Но об этой новой точке останова отладчик ничего не знает, т.к. он ее не устанавливал! Поэтому
int 3 в коде секретной функции не удаляется. Процессор ее выполняет и возникает исключение.
Его-то и ловит блок try..except и приветствует неудавшегося взломщика. И кроме того посмотри
как изуродовался код после int3. В случае с железным бряком никакого изменения кода не
происходит, поэтому трассировка проходит нормально. Собственно таким образом я и получил
эти скриншоты.Если тебе все еще показалось мало описанных в этой статье извращений, то никто не мешает
взять на вооружение технику модификации кода, описанную в первой части. Изменяй код
скопированной с стек функции, как твоей душе будет угодно. Здесь я этого делать не стал,
чтобы совсем не запутать. В результате идет лесом не только отладчик, но еще и дизассемблер.
Можно даже прикрутить к проге простенький дизассембер и удалять из скопированного кода
точки останова. Хотя, имхо, это уже стрельба из пушки по воробьям, т.к. железный бряк сведет
на нет все усилия.

 

Уроки для хакеров и программистов

huzrus
3 февраля 2013, 21:36
188

Loading...

Комментарии

Оставьте свой комментарий

Спасибо за открытие блога в Yvision.kz! Чтобы убедиться в отсутствии спама, все комментарии новых пользователей проходят премодерацию. Соблюдение правил нашей блог-платформы ускорит ваш переход в категорию надежных пользователей, не нуждающихся в премодерации. Обязательно прочтите наши правила по указанной ссылке: Правила

Также можно нажать Ctrl+Enter

Популярные посты

Исламская ипотека в Казахстане: в этом году выйдет на розничный рынок

Исламская ипотека в Казахстане: в этом году выйдет на розничный рынок

Многие клиенты, вздыхая о харамном ссудном проценте местных банков, сокрушались по поводу отсутствия у нас халяльного кредитования. Поддержка исламского банкинга на уровне МФЦА есть...
DanaJarlygapova
24 марта 2017 / 18:28
  • 20790
  • 59
Рианна за $2 млн. Очередные понты от «ЭКСПО-2017»

Рианна за $2 млн. Очередные понты от «ЭКСПО-2017»

Концерт Рианны всё-таки состоится летом в рамках культурно-зрелищных и спортивных мероприятий ЭКСПО-2017. Рианна крута, с этим никто не спорит. Но соберёт ли «Астана Арена» 30 тысяч зрителей при...
openqazaqstan
сегодня / 14:29
  • 4291
  • 7
Компания LG Electronics открыла предзаказ на новый флагман LG G6

Компания LG Electronics открыла предзаказ на новый флагман LG G6

LG Electronics объявляет о начале предварительного заказа онлайн на новый смартфон LG G6. Оформить предзаказ можно на сайтах магазинов электроники до 16 апреля 2017 года.
LG Electronics
24 марта 2017 / 14:20
  • 5190
  • 0
Беременность по-американски. Никто не ждет до 40 дней, с малышом гуляют с первого дня

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

Мои волшебные 9 месяцев протекали в новой среде и далеко от всех родственников и подружек. Никто из моих знакомых в США на тот момент не успел обзавестись малышом, поэтому мне не с кем было...
Zarema_
23 марта 2017 / 9:25
  • 5201
  • 16
Страну, где так строят дороги, победить невозможно!

Страну, где так строят дороги, победить невозможно!

Шестиминутный ролик про строительство автомобильной дороги в США - это как острый нож в пузо нашим чиновникам. Они же подавятся бешбармаком, увидев его! Похлеще любого пропагандистского фильма...
Timur_Tregulov
24 марта 2017 / 11:26
  • 4673
  • 38
Это вам не Дисней. Реальный прототип «Красавицы и Чудовища»

Это вам не Дисней. Реальный прототип «Красавицы и Чудовища»

Многие уверенны на 100%, что сказка «Красавица и Чудовище» - это интересная выдумка ее создателей. Однако, действия, происходящие в мультфильме и фильме, имели место в истории, и у главных героев...
Naomi_K
24 марта 2017 / 18:42
  • 4869
  • 34
Семь причин почему вам надо перестать откладывать переезд за границу

Семь причин почему вам надо перестать откладывать переезд за границу

Вы думаете о переезде за границу, но постоянно откладываете дату, потому что вас беспокоит тысяча мелочей, которые могут пойти не так? У меня есть семь причин, почему пора перестать мотать нервы...
Lesch
24 марта 2017 / 17:50
  • 3805
  • 14
Гонения на «Свидетелей Иеговы». Кому это выгодно?

Гонения на «Свидетелей Иеговы». Кому это выгодно?

Я не "Свидетель Иеговы". Мне приходилось защищать в судах и кришнаитов и коммунистов, хотя я и не разделяю их убеждений. Я защищаю свободу совести. Сегодня "Свидетели Иеговы", завтра,- каждый из вас.
nasreddin
25 марта 2017 / 17:18
  • 3947
  • 48
Страна обыденной роскоши: Ташкент-Самарканд-Бухара

Страна обыденной роскоши: Ташкент-Самарканд-Бухара

Узбекистан - это страна, где роскошь стала обыденностью. Это и повсеместный зелёный крупнолистовой чай. И вкусная натуральная еда с минимумом специй. И то, что дети играют среди исторических памятников.
MadinaR
25 марта 2017 / 10:37
  • 3189
  • 59