![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Assembler&Delphi!!!!!
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 модулей процесса-жертвы, а не своя собственная)