Экспертная система Delphi.int.ru

Сообщество программистов
Общение, помощь, обмен опытом

Логин:
Пароль:
Регистрация | Забыли пароль?

Delphi.int.ru Expert

Другие разделы портала

Переход к вопросу:

#   

Статистика за сегодня:  


Лучшие эксперты

Подробнее »



Вопрос # 902

/ вопрос открыт /

Здравствуйте, уважаемые эксперты!
Как динамически создать и выполнить процедуру/функцию в памяти и выполнить ее?

SMaks Вопрос ожидает решения (принимаются ответы, доступен мини-форум)

Вопрос задал: SMaks (статус: 1-ый класс)
Вопрос отправлен: 7 сентября 2007, 23:54
Состояние вопроса: открыт, ответов: 1.

Ответ #1. Отвечает эксперт: Матвеев Игорь Владимирович

Здравствуйте, SMaks!
Да, интерестный вопрос.. Как я понял, Вы хотите, чтобы можно было ввести в Memo код процедуры на Паскале и, нажав кнопку, выполнить ее. Такое возможно, нужно использовать скрипт-движок Паскаля, например FastScript или что-то еще, савсем не сложно найти в интернете. Естественно этод подход можно назвать лишь эмуляцией, поскольку скрипту будет доступно толлько то, что Вы разрешите (а разрешить можно и все: доступ к памяти процесса, компонентам, функции работы с диском и т.д.). Чтобы сделать реальный код из текста прийдется писать компилятор Паскаля, или же использовать готовый, например можно добавить к написанной процедуре служебный текст (program/begin/end), скопмилировать и выполнить exe.

Второй вариант - у вас есть asm код процедуры, или его байт-код. В этом случае Вам нужно разместить этот код (если асм - компилировать, если байт-код - править смещения) в какой-то области памяти и разрешить для этой области выполнение (атрибут Executable, для линейки NT достаточно поставить атрибут Readable, поскольку в NT память которая может быть прочитана может быть и выполнена), а затем передать управление на этот код. Сделать это можно многими путями, в зависимости от Вашей ситуации, или через DebugAPI, или через структурную обработку исключений (SEH), или просто push $addr; ret (код поместит в регистр EIP адрес $addr).

Вот так все просто!

Ответ отправил: Матвеев Игорь Владимирович (статус: Студент)
Время отправки: 8 сентября 2007, 02:13
Оценка за ответ: 3

Комментарий к оценке: Про второй вариант можно поподробнее (с примерами)?

Мини-форум вопроса

Всего сообщений: 3; последнее сообщение — 11 сентября 2007, 14:16; участников в обсуждении: 2.
Матвеев Игорь Владимирович

Матвеев Игорь Владимирович (статус: Студент), 10 сентября 2007, 00:21 [#1]:

Извините, примеров прямо сейчас дать не могу - убегаю на учебу.

Но вообще, для начала поупражняйтесь вообще с модификацией кода - это будет полезно во всех отношениях.
Допустим подготовьте тестовый проект, в котором есть одна служебная функция (много какого-нибудь кода, а в конце Beep или MessageBox). в самом начале функции поставьте такой блок:
asm // Метка начала функции
   jmp @@skeep
     db 53h, 43h, 0BBh, 00h, 62h, 5Bh, 79h, 80h
   @@skeep:
end;

Это будет метка, для поиска начала кода функции, которую Мы будем модифицировать, можно было-бы взять и просто адрес @Имя_функции, но компилятор добавляет в начало любой функции свой код - обычно установка SEH, сохранение каких-то данных в стеке и этот код зависит от компилятора к компилятору и также от опций компиляции. Jmp - переход, чтобы метка не выполнялась (метка - это случайная последовательность, не встречаемая больше в коде). Также в конце функции ставьте аналогичную метку (только с другой последовательностью).

Потом, в другой функуции нужно найти промежуток, куда Мы можем записать свой код - этот код на Ваше усмотрение, только учтите, что нельзя использовать константный массив с байтами меток, поскольку при поиске Вы (теоретически) можете наткнуться на него, а не на реальную метку. Начать поиск стоит с @Имя_функции.

Теперь у Вас есть память, которая может читаться и исполняться, нужно ее подправить, для этого нужно добавить памяти атрибут PAGE_READWRITE с помощью VirtualProtect (насколько я помню, нужно передавать размер кратный 4 килобайтам).

Теперь собственно запись кода - это можно сделать многими способами, например с помощью TMemoryStream. Вы можете заполнить всю функцию nop\'ами (инструкция ницего не делания), которая представляет собой байт 90h, тогда, вызывая пропатченную функцию Вы не увидите MessageBox или не услышите Beep.

Какой еще код можно записать? - дело в том, что Вы не можите использовать никаких переменных, функция, методов, классов и т.д. своей программы, да и API в явном виде - Вы не знаете где они находятся, поэтому Вы можете либо писать адресно независимый код, т.н. shell код, что само по себе является довольно сложной, но в то же время крайне интерестной задачей, либо модифичировать свой код в зависимости от адреса куда он будет записан и от положения в памяти переменных и функций (т.е. нужно строить что-то вроде компилятора - править все абсолютные адреса в коде).

Если будет время вечером набросаю пример, как писать компилятор я, честно говоря пока смутно представляю, если планируется использовать один и тот-же набор готовых кодов - можно писать их как asm вставки, а перед записью править смещения (дельту можно расчитать), только тогда весь смысл этой затеи теряется, разве только в учебных целях. Ну а если код будет разным, ч том числе и вводиться пользователем - нужен полноценный асм-транслятор (или другой язык), в простейшем случае это может быть просто перевод команд в машинные слова (для чего прийдется изучить полновесный Intell мануал по архитектуре x86 процессоров), либо искать готовый с исходниками, скажем Fasm, и переделывать под себя.

Скрипт-интерпритатор, конечно, намного проще.
SMaks

SMaks (статус: 1-ый класс), 10 сентября 2007, 14:29 [#2]:

Спасибо, Матвеев Игорь Владимирович.
1) Попробовал я перемещение кода между процедурами, в принципе здесь понятно, вот только как нужно правильно копировать участок кода?
2) Не подскажите ссылку на таблицу соответсвий между операторами ассемблера x86 и их опкодам?
-----------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
asm // Метка начала функции
jmp @@skeep1
mov eax, 43h
@@skeep1:
end;

// edit2.Text := edit1.Text; //34б
showmessage(\'A\');

asm // Метка начала функции
jmp @@skeep2
mov eax, 43h
@@skeep2:
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
asm
jmp @@skeep3
mov eax, 43h
@@skeep3:
nop
... //всего 34 nop
nop
jmp @@skeep4
mov eax, 43h
@@skeep4:
end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
k, adP2, adP1: cardinal;
cd1, cd2: pointer;
begin
adP1 := Cardinal(addr(TForm1.Button2Click));
adP2 := Cardinal(addr(TForm1.Button2Click));
cd1 := Pointer(adP1+13);
cd2 := Pointer(adP2+13);
//VirtualProtect(cd1, 34, PAGE_READONLY, k);
VirtualProtect(cd2, 11, PAGE_READWRITE, k);
Move(cd1^, cd2^, 11); //не срабатывает!
end;
Матвеев Игорь Владимирович

Матвеев Игорь Владимирович (статус: Студент), 11 сентября 2007, 14:16 [#3]:

Небольшой пример патчинга, все о чем я говорил:
// Функция которую будем патчить
procedure TestFunc();
var
  vTime: Integer;
begin
 asm    // Метка начала кода, патчинг нужно будет начинать сразу за меткой
   jmp @@skeep;
   db 53h, 43h, 0BBh, 00h, 62h, 5Bh, 79h, 80h
   @@skeep:
 end;
 
 // Разные бесполезные вычисления (время и условие, чтобы компилятор не пытался
 // вырезать этот код как неиспользуемый)
 vTime := GetTickCount();
 if vTime = 0 then Beep;
 // Вывод
 Beep;
 ShowMessage(\'Hello, this is a original TestFunc() code\');
 
 asm    // Метка конца кода, патчинг нужно будет кончаться сразу перед меткой
   jmp @@skeep;
   db 52h, 42h, 0BAh, 0FFh, 61h, 5Ah, 78h, 7Fh
   @@skeep:
 end;
end;
 
// Эта функция ищет кусок кода между метками
function SearchBlock(const StartPtr: Pointer; MaxSize: Integer; var ResultPtr: Pointer; var Size: Integer): Boolean;
const
  markOne : array[0..7] of Byte = ($80, $79, $5B, $62, $00, $BB, $43, $53);
  markTwo : array[0..7] of Byte = ($7F, $78, $5A, $61, $FF, $BA, $42, $52);
var
  i, j : Integer;
  Ptr1,
  Ptr2 : Pointer;
begin
 // При поиске нельзя сравнивать метку с константной последовательностью байт,
 // поскольку мы можем наткнуться при поиске не на метку, а на саму эту константу
 // (в данном случае это почти невозможно, поскольку мы знаем откуда начинать поиск,
 // но вообще в реальных проектах это нужно учитывать). Поэтому бедем искать
 // последний байт, а потом сравнивать задом-наперед.
 
 // Ищем стартовую метку
 for i := Integer(StartPtr) to Integer(StartPtr) + MaxSize do
   if Byte(Pointer(i)^) = markOne[0] then                      // Ищем последний байт
     begin
       for j := 0 to 7 do                                      // Проверяем байты сзади
         if Byte(Pointer(i-j)^) <> markOne[j] then             // с меткой
           begin
             Ptr1 := 0;                // Различие
             break;
           end else if j = 7 then Ptr1 := Pointer(i);
       if Integer(Ptr1) <> 0 then Break;       // Метка найдена
  end;
 ResultPtr := Pointer(Integer(Ptr1)+1);
 // Ищем завершающую метку
 for i := Integer(ResultPtr) to Integer(StartPtr) + MaxSize do
   if Byte(Pointer(i)^) = markTwo[0] then                      // Ищем последний байт
     begin
       for j := 0 to 7 do                                      // Проверяем байты сзади
         if Byte(Pointer(i-j)^) <> markTwo[j] then             // с меткой
           begin
             Ptr2 := 0;                // Различие
             break;
           end else if j = 7 then Ptr2 := Pointer(i);
       if Integer(Ptr2) <> 0 then Break;       // Метка найдена
  end;
 Size := Integer(Ptr2)-7-2 - Integer(ResultPtr); // Отнимаем 7 байт метки и 2
                                                 // байта jmp перехода
 if (ResultPtr <> nil) and (Size <> 0) then
   Result := True else Result := False;
end;
 
// Тестовый вызов тестовой функции!
procedure TForm1.Button1Click(Sender: TObject);
begin
 TestFunc()
end;
 
// Патчинг
procedure TForm1.Button2Click(Sender: TObject);
var
  p : Pointer;
  s : Integer;
  k : Cardinal;
  i : Integer;
begin
 // ШАГ №1 - Найти интересующий нас кусок кода
 if not SearchBlock(@TestFunc, 4024, p, s) then
   begin
     MessageDlg(\'Ошибка: не найдены поисковые метки\', mtError, [mbOk], -1);
     Exit;
   end;
 // ШАГ №2 - Сделаем память доступной для записи
 if not VirtualProtect(p, s, PAGE_READWRITE, k) then
   begin
     MessageDlg(\'Ошибка: не получилось установить для памяти атрибут PAGE_READWRITE\', mtError, [mbOk], -1);
     Exit;
   end;
 // ШАГ №3 - Непосредстенно патчинг
 try
   for i := 0 to s-1 do
     Byte( Pointer(Integer(p)+i)^ ) := $90;   // Заполняем все nop\'ами 
 except
   on EAccessViolation do MessageDlg(\'Ошибка: невозможно записать в память\', mtError, [mbOk], -1);
 end;
end;

По-поводу соответствия asm-bin - дело в том, что тут нет четкого соответствия, у многих команд есть несколько bin-кодов, в зависимости от параметра, например JMP (безусловняй переход) имеет аж 5 разновидностей и может иметь размер 2, 3 или 5 байт. Кроме того, производители микропроцессором добавляют новый функционал.

Самое лучшее что Вы можете использовать это мануал Intel по архитектуре x86, он всегда есть на сайте Intel. Из русскоязычных материалов можно скажем взять самые основные команды из книг по ассемблеру, как например Ассемблер и программирование для IBM PC под редакцией Еpшова В.Г. Небольшой кусок этой книги: http://www.kamchat-auto.ru/asm_code.rar.

Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.

Версия движка: 2.6+ (26.01.2011)
Текущее время: 22 февраля 2025, 11:27
Выполнено за 0.03 сек.