• 9416
  • 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
244

Загрузка...
Loading...

Комментарии

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

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

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

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

Мой дом – не гостиница. Я не останавливаюсь у своей родни, потому что знаю, что это такое

Мой дом – не гостиница. Я не останавливаюсь у своей родни, потому что знаю, что это такое

Наступил долгожданный момент и мы смогли заселиться в собственную квартиру. А потом началось... Все знакомые, родственники, даже коллеги и соседи родителей вспомнили о нашем существовании.
Idealovnet
14 окт. 2017 / 20:38
  • 8912
  • 83
Работа на EXPO. «Улыбайтесь, вы – лица Казахстана»

Работа на EXPO. «Улыбайтесь, вы – лица Казахстана»

Продление перерывов, втыки от менеджеров, борьба за стенды, кучкования, как мы друг-друга прикрывали, защищали от гостей. Все эти события доставляли радость, и каждый день на работу я приходила...
madiNAtty
14 окт. 2017 / 22:34
  • 6134
  • 24
О проститутках, ЗППП и других сексуальных страстях

О проститутках, ЗППП и других сексуальных страстях

У меня обширный сексуальный опыт, и я этим не хвастаюсь. Будь у меня возможность, променял бы это всё на одного партнёра. Но так как с личной жизнью не заладилось, а секс я очень люблю, то приходилось изворачиваться.
bez_prav
18 окт. 2017 / 18:01
Я четко помню тот день, когда мне позвонили друзья и сообщили: «Она выходит замуж»

Я четко помню тот день, когда мне позвонили друзья и сообщили: «Она выходит замуж»

У нас была особенная атмосфера, мы постоянно были вместе, читали треки, летом часто поднимались в горы. Гуляли пешком по ночному городу, иногда до утра. Снимали хату и представляли совместную жизнь...
Dominator-kz
14 окт. 2017 / 22:29
Отчего в Казахстане предвзятое отношение к отечественному продукту?

Отчего в Казахстане предвзятое отношение к отечественному продукту?

Вы когда-нибудь пользовались казахстанской косметикой? Я тоже нет, поэтому сразу же откликнулась на приглашение своего фейсбук-френда протестировать отечественные крема… из Степногорска.
Shimanskaya
16 окт. 2017 / 11:32
  • 2806
  • 31
Я помню тот день, когда мне позвонили друзья и сообщили: «Она выходит замуж». Часть 2

Я помню тот день, когда мне позвонили друзья и сообщили: «Она выходит замуж». Часть 2

Я знал дату свадьбы. За неделю до свадьбы в соцсети "Вконтакте" на все мои последние фото, был проставлен лайк с её профиля. Сердце забилось сильнее. В душе загорелась наивная, крошечная надежда.
Dominator-kz
17 окт. 2017 / 15:41
Мой парень – «тиран». Почему я вступила в такие отношения?

Мой парень – «тиран». Почему я вступила в такие отношения?

История из моей жизни. Я вспоминаю эти отношения и сама не могу понять - как так произошло? А дело в том, что вы и сами не заметите. Это наступает плавно и динамично.
Altynai_JA
18 окт. 2017 / 14:17
Льготное кредитование для молодежи Алматы. Не хоромы, но для начала неплохо

Льготное кредитование для молодежи Алматы. Не хоромы, но для начала неплохо

Если вам нет 35 лет и у вас нет своего жилья, то есть интересная гос.программа. Нишевая программа - молодые семьи Алматы до 35 лет, семья - это как минимум 2 супруга без детей.
DanaJarlygapova
19 окт. 2017 / 16:21
«Автобусная неделя». Выдержит ли аким Шымкента давку в общественном транспорте?

«Автобусная неделя». Выдержит ли аким Шымкента давку в общественном транспорте?

Аким Шымкента Габидулла Абдрахимов нашёл решение накопившихся проблем городского транспорта. Все ключевые работники акимата некоторое время будут сами ездить на автобусах.
openqazaqstan
18 окт. 2017 / 10:53
  • 1687
  • 32