Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Assembler&Delphi!!!!!


Страницы:   1 2 3

4ert ©   (2006-12-27 02:01:00) 

И вновь здравствуйте!!! Хотелось бы узнать(теперь серьзно(вчера тупил)), немного об использовании assembler в delphi. Коротко о задаче. Пишу прогу, пожалуй опишу её предназначение(дабы гнусным вирусописателем не называли). Я по природе человек любопытный и меня всегда интересовал вопрос как же конкретно работает то, или иное приложение. И вот в последнии дни пришла в голову мысль написать софтину призванную помогать мне в этом. Как рашил реализовать: есть dll с функциями перехвата(путем подмены адреса APi функции), её мы подгружаем в указанный процесс), она меняет адреса всех нужных нам APi функций на адреса наших обработчиков, обработчики работают по одному принципу: получают всю возможную инфу о параметрах перехватываемой функции и отсылает главному приложению, та в свою очередь выводит всю информацию в Memo и ждет новых сообщений...
В принципе все просто(благо примеров в инете море!!!!), но вот возникла вовсе неожиданная прблема примеров в нете  много, но все, как правило, иллистрирует перехват одной конкретной функции, а у меня же их по плану ну штук сто наверно будет.С утановкой перехвата всё ништяк, с получением инфы тож, а проблема с реальным выполнением функции, то есть чтобы дать зараженному процессу все-таки выполнить то, что он хочет. В каждом перехватчике я возращаю память в исходное состояиние(до установки перехвата) и похорошему необходимо бы выполнить перехваченную функцию API, а затем установить перехватчик снова... Вот такие идеи. Сложность в том что никак не могу понять как реализовать запуск нужной функции API, причем функция запуска хотелос, чтобы была одна...Ибо обработчик для каждой API я то напишу(благо есть windows.pas), а вот писать ещё сто функций запуска для каждой API это уж слишком.....
Структура перехватчика:


function perexvat(<параметры>): integer; stdcall;
begin
<получаем параметры>
<отсылаем сообщение с параметрами>
<восстонавливаем память в исходное состояние>
<запускаем функцию CallApi>
<устанавливаем перехватчик>
end;


Мотив обращения к вам - вопрос реализации функции CallApi. Этот глобальный вопрос делится на два субвопроса:

1)как реализовать запуск функции зная её адрес(ну и естественно передать ей необходимые параметры)?

2)Как сделать чтобы функция была универсальна, т.е. была в состоянии запустить любую функцию API с любым количеством параметров и их типов?

По поводу первого вопроса я целый день рылся в инете(не без вашего пинка) и в собственных архивах. Я так понимаю едиственный верный вариант реализации CallApi это assembler, параметры передавать через стек(ведь мы даже не знаем сколько их может быть максимально!), а на вход функция должна принимать, я думаю(я знаком с assembler один день!!! А с Delphi всего четыри месяца!) массив параметров затем запускать цикл который будет делать нечто наподобии:


push parametrs[i]


пока не кончится массив, а затем делать собственно:


call addr


Вопрос к вам уважаемые Мастера: как вы думаете алгоритм(теоритически) продуман нормально? аль косячки имеются?
-------------------------------------------
Теперь о практике, забудем немного о необходимости универсальности функции, ибо не получается даже елементарное: Решил сделать тест, получится ли у меня запустить с помощью assembler API функцию MessageBoxA:
Делал так:


function asem(hWnd: HWND; lpText, lpCaption: PAnsiChar; uType: UINT): Integer; stdcall;
asm
push hWnd
push lpText
push lpCaption
push uType
call addr
end;


где


addr:=@MessageBoxA;


При запуске функции появляется таки messagebox но глючит, скажу я вам, его вовсе не подетски, то вместо MB_OK(а именно эта константа передаетсяв качестве uType) появляется три кнопки с надписями "повторить" "отменить" "продолжить" то текст выравне слева, то справа, то присутствует 3D эффект то нет... Короче лажа какая-то!!!! Я подозреваю, что происходит это из-за того, что я не очищаю стек, но как же быть? Ведь если я его очищаю методом который описан у всех авторов(вставляю сразу после "call"):


add        sp,8


(Насчет восмерки не уверен), то замечательный компилятор не менее замечательного Delphi 7 enterprise выдаёт ошибку примерно следующего содержания:


Project test.exe raised exception EAccessViolation with message 'Access violation at address 0012F7C8.
Write of address 0342B299'.


Как же быть? Или поповоду очистки стека я ошибаюсь?
------------------------------------------------------
Также очень бы хотелось Уважаемые Мастера услышать ваше мнение поповоде 1) и 2), а также вцелом алгоритма......

Заранее ОГРОМНЕЙШЕЕ СПАИБО!!!!!!
(надеюсь не задолбались читаь?)


Игорь Шевченко   (2006-12-29 12:02:00) [1]


> правда после работы API функции выскакивает сообщение:
> "
> Project Project1.exe raised exception class EAccessViolation
> with message 'Access Violation at address 001703CA
> '. Process stopped.
> "


Двигатель был очень похож на настоящий, только не работал


GrayFace   (2006-12-29 11:58:00) [2]

Stack Frame в таких случаях не создается. Надо самому создавать.


4ert   (2006-12-28 23:16:00) [3]

Вот кажется нашёл решение, универсальная функция запуска API функции из её двойника, просто копируем определенное число байт со входа двойника на вход API функции:


const
 MaxParam = 32; //макс.число параметров
function f2(a:pointer):integer; assembler;
asm
 push edi
 push esi
 mov ecx,MaxParam*4
 sub esp,ecx
 mov edi,esp
 lea esi,[ebp+8]   //указатель на первый параметр двойника
 rep movsd         //копируем MaxParam*4 байт параметров
 lea edi,[esp+MaxParam*4] //сохраняем значение esp
 call [eax]        //вызов API
 mov esp,edi       //восстанавливаем esp
 pop esi
 pop edi
end;


Всё работает отлично!!!! Функция API с теми же параметрами, что и функция двойник(которая собственно и запускает f2 с параметром: указатель на API функцию).....

правда после работы API функции выскакивает сообщение:
"
Project Project1.exe raised exception class EAccessViolation with message 'Access Violation at address 001703CA
'. Process stopped.
"

Не знает никто как лечить?


GrayFace   (2006-12-27 17:31:00) [4]

> возвращаемый параметр
возвращаемый стандартным обработчиком результат

> Ее надо будет
написать :)


GrayFace   (2006-12-27 17:25:00) [5]

Сергей М. ©   (27.12.06 15:40) [22]
Для начала пойми, что делать "универсальный" перехват (любая API-ф-ция из любого модуля с любым соглашением и произвольными наперед неизвестными параметрами) попросту лишен смысла.

Вовсе нет. Если в каком-то файле хранятся сигнатуры методов, остальное все можно. Можно даже получить возвращаемый параметр.

Во первых, что происходит при вызове call: в стек кладется адрес возврата - адрес следующей за call инструкции и происходит переход на указанную функцию.
При вызове retиз стека извлекается адрес возврата и происходит переход на него

Сам метод:

У на будет процедура, передающая параметры в Memo:
procedure StoreParams(eax, edx, ecx:Integer; StackParams:Pointer; MyRecord:PMyRecord);
Ее надо будет

Для начала для каждой перехваченной функции надо создать структуру TMyRecord, в первые 5 байтах которой в машинных кодах записать call <наш обработчик>, далее расположить адрес изначального обработчика, потом сигнатуру метода и другую информацию, нужную функции StoreParams. И подменяем функцию в таблице на адрес этой структуры.
Получится, что call <наш обработчик>перейдет на наш обработчик, положив в стек адрес следующего за ним "кода" - а там у нас лежит адрес изначального обработчика и то, что нужно функции StoreParams. (могу привести код)

Так будет выглядить наш обработчик:
asm
 // Содержимое стека: <@MyRecord + 5> <Return address> <Params...>
 // Где @OldHandlerAddress - адрес поля структуры, в котором лежит адрес изначального обработчика
 
 // Сохраняем регистры:
  push eax
  push ecx
  push edx

  lea eax, [esp + 4*5] // eax := esp + 16  - положили в eax адрес параметров <Params...>
  push eax
  mov eax, [esp + 4*4] // положили в eax <@MyRecord + 5>
  sub eax, 5 // Теперь eax = @MyRecord
  push eax
  mov eax, [esp + 4*5]
  call StoreParams

  pop edx
  pop ecx
  mov eax, [esp + 4] // eax = @MyRecord + 5
  mov eax, [eax] // Берем адрес старого обработчика из структуры
  xchg eax, [esp] // Меняем местами eax и [esp]

// Все. Теперь в стеке такая картина:
// <Old Handler> <Return address> <Params...>
// В конце нашей процедуры Delphi добавит ret, соответственно будет исполнен старый обработчик. При чем он ничего не заметит - в стеке будет адрес возврата и параметры, как при обычном вызове.
end;


Если есть желание получить значение, которое возвращает процедура, придется проделать еще такую же работу: при каждомвызе нашего обработчика создавать структуру с 5 байтам callв начале, в нее сохранить Return address и подсунуть старому обработчику адрес этой структуры вместо адреса возврата. Эта структура будет вызывать другую функцию, которая будет передавать результат приложению и переходить на сохраненный в структуре Return address.

Уф...


Игорь Шевченко   (2006-12-27 16:56:00) [6]

Сергей М. ©   (27.12.06 16:42) [36]

Если ты хочешь модифицировать, например, передаваемые параметры всякий раз при вызове функции, то задача имеет смысл. Если ты хочешь собрать статистику для разного рода целей, то задача имеет смысл. Да мало ли каких целей можно напридумывать :)


evvcom   (2006-12-27 16:47:00) [7]

> [35] Игорь Шевченко ©   (27.12.06 16:39)
> Обычно после вызова функций меняется содержимое eax/edx

Ну так они относятся к тем, которые могут.

А так сколько я кода в IDA смотрел, по-моему везде о сохранении первоначальных eax, edx нет никакой заботы, зато как только esi, edi юзаются, так их push и pop.


Сергей М.   (2006-12-27 16:42:00) [8]


> Игорь Шевченко ©   (27.12.06 16:36) [31]


Ну это да, согласен.

Только вряд ли задача отследить подобные "внутренние" вызовы имеет практический резон


Игорь Шевченко   (2006-12-27 16:39:00) [9]


> Наверняка где-то у мелкомягких документировано, какие регистры
> не должны меняться, а какие могут.


Это невозможно документировать на все случаи жизни. Обычно после вызова функций меняется содержимое eax/edx


evvcom   (2006-12-27 16:38:00) [10]

> [33] Игорь Шевченко ©   (27.12.06 16:37)

Ну да, не учел :)


Игорь Шевченко   (2006-12-27 16:37:00) [11]

evvcom ©   (27.12.06 16:07) [28]


> Ну так у этой самой DLL есть свои EAT/IAT.


Компиляторы так устроены, что код для вызова функций из того же модуля генерится без использования EAT/IAT.


evvcom   (2006-12-27 16:36:00) [12]

> [30] oxffff ©   (27.12.06 16:30)

Ну может это относится только к их API. Я бы все ж назвал это conventions.


Игорь Шевченко   (2006-12-27 16:36:00) [13]

Сергей М. ©   (27.12.06 15:56) [26]

А кто говорит о собственных ?

Речь идет о вызове функции из кода целевой DLL.
Например, если ты перехватишь вызов некой функции из user32.dll в интересующем тебя процессе, то эта же функция может быть вызвана самой user32.dll при вызове совершенно другой функции оттуда же.


oxffff   (2006-12-27 16:30:00) [14]


> Наверняка где-то у мелкомягких документировано, какие регистры
> не должны меняться, а какие могут. AFAIK, esi, edi не должны
> меняться внутри функций.


Это уже constraints.


evvcom   (2006-12-27 16:11:00) [15]

> [27] oxffff ©   (27.12.06 15:57)
> //гарантии, что в esi тоже что до call нет.

Если esi меняется внутри call edi, то эта edi кривая. Наверняка где-то у мелкомягких документировано, какие регистры не должны меняться, а какие могут. AFAIK, esi, edi не должны меняться внутри функций.


evvcom   (2006-12-27 16:07:00) [16]

> [23] Игорь Шевченко ©   (27.12.06 15:45)
> Если перехватываемая функция вызывается изнутри той же самой
> DLL, то при модификации таблиц импорта этот вызов будет
> неперехвачен, а при модификации кода перехвачен.

Ну так у этой самой DLL есть свои EAT/IAT.

И другой случай. Я как-то наткнулся на следующее. При выгрузке моей dll, что было вполне корректно, т.к. она как плугин "законно" подгружалась, и так же "законно" должна была выгружаться самим приложением, я ловил AV. C помощью отладчика и IDA обнаружил, что где-то во внешнем цикле после загрузки и инициализации всех плугинов читался адрес перехваченной мною функции и держался в одном из регистров (ebx, кажется), и дальнейшее обращение было call ebx, ну а выгрузка плугина оказалась где-то между этим сохранением в ebx и одним из последних call. Естественно причина AV сразу стала ясна.


oxffff   (2006-12-27 15:57:00) [17]

Предложенный универсальный алгоритм криво реализован.

 pop esi // сохраняем оригинальный адрес возврата
 mov edi,addr // адрес оригинальной функции
 call edi // вызов, собственно, перехваченной функции
 
//гарантии, что в esi тоже что до call нет.

 push esi // восстанавливаем оригинальный адрес возврата


Сергей М.   (2006-12-27 15:56:00) [18]


> Игорь Шевченко ©   (27.12.06 15:53) [25]


Мож я тупой, но не придумаю задачи, когда бы потребовалось перехватывать собственные API-вызовы для целей исследования чужих аналогичных...


Игорь Шевченко   (2006-12-27 15:53:00) [19]

Сергей М. ©   (27.12.06 15:51) [24]


> А оно и нафиг не нужно)


А это от целей перехвата зависит


Сергей М.   (2006-12-27 15:51:00) [20]


> Игорь Шевченко ©   (27.12.06 15:45) [23]


> Если перехватываемая функция вызывается изнутри той же самой
> DLL, то при модификации таблиц импорта этот вызов будет
> неперехвачен


А оно и нафиг не нужно)
Нас-то как бы интересует IAT модулей процесса-жертвы, а не своя собственная)


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо