Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2007.01.21;
Скачать: [xml.tar.bz2];

Вниз

Assembler&Delphi!!!!!   Найти похожие ветки 

 
4ert   (2006-12-27 02:01) [0]

И вновь здравствуйте!!! Хотелось бы узнать(теперь серьзно(вчера тупил)), немного об использовании 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), а также вцелом алгоритма......

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


 
4ert   (2006-12-27 02:11) [1]

Сорри, конечно же не компилятор ошибку выдает :)


 
ors_archangel ©   (2006-12-27 02:32) [2]

Рекомендую читать замечательную справку не менее замечательного Delphi 7 Enterprise :) где, например, написано

Calling conventions
Directive Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes
pascal Left-to-right Routine No
cdecl Right-to-left Caller No
stdcall Right-to-left Routine No
safecall Right-to-left Routine No

- порядок pushинга аргументов при stdcall - right-to-left, а ты сделал left-to-right


 
ors_archangel ©   (2006-12-27 02:45) [3]

А вообще тебе обяз нужно обойтись без перепередачи параметров, потому как фунь может быть и не stdcall, а notstdcall, как-то я писал примерно такое:

  if assigned(before) then begin
     pusha;
     mov edx,@before
     mov eax,beforeParam
     call edx
     popa;
   end;
   mov save_edi,edi
   mov save_esi,esi
   mov edi,original
   pop esi // save original return address
   call edi
   push esi
   mov edi,save_edi
   mov esi,save_esi
   if assigned(after) then begin
     pusha
     mov edx,@after
     mov eax,afterParam
     call edx
     popa
   end;
   ret

здесь нам пофигу что за функция, сколько у неё  параметров и как они передаются, а зачем восстанавливать обработчик каждый раз я не понял и читать задолбался ;)
Надо коммент, пость


 
4ert   (2006-12-27 02:54) [4]

Не, все я правильно сделал :) уже разобрался! ..... Просто переменная hwnd у меня была равна handl"у формы, а это как пишут многие(и как я убедился только что), оказывается в корне неверно первый параметр должен равнятся всегда нулю, как только сказанное выше было сделанно, все стало на свои места .... Спасибо, что потревожили из-за меня - чайника справку наверно уже спящего Delphi....

А можете дать совет по поводу организации "универсальности" этой функции(callapi) в моей dll? А то чего-то на ум ничего не идет..... да и приблизительных примеров вообще не видел...


 
4ert   (2006-12-27 03:04) [5]

Сорри вновь.... Не заметил второе сообшение :(.... сплю уже. Идея неплохая наверное, "наверное" потому, что я ж один день об assembler знаю, и как уважающий себя чайник вовсе не втыкаю как приведенный код работает, если вас не затруднит не могли бы вы хоть немного поподробней о коде....
А востанавливать обработчик надо затем, что .... короче вот код своял на перехват MessageBoxA:


function HMessageBox(hWnd: HWND; lpText, lpCaption: PChar; uType: UINT): Integer; stdcall;
var
  dat: TCopyDataStruct;
  str,tmp: string;
begin
   with dat do
  begin
    dwData := 0;
    tmp:=lptext;
    cbData := StrLen(Pchar(tmp))+1;
    lpData :=PChar(tmp);
  end;
 sendmessage(findwindow(nil,"HPION"),WM_M,666,longint(@dat));
WriteProcessMemory(CurrProc, AdrCreateProcessA, @OldCrp, SizeOf(far_jmp), Writen);
asm                             //Вот в этом месте вместо assemblera хотелось        
push 0                         //вставить запуск мифической универсальной
push lpText                  //функции запуска API.
push lpCaption
push MB_OK
call AdrCreateProcessA
call hookaddr
end;
end;


hookaddr - указатель на функцию установки ловушки.
А вот собственно ее код:


Procedure SetHook;
var
HKernel32, HUser32: dword;
begin
CurrProc := GetCurrentProcess;
AdrCreateProcessA := GetProcAddress(GetModuleHandle("user32.dll"),  "MessageBoxA");
JmpCrProcA.PuhsOp  := $68;
JmpCrProcA.PushArg := @hMessageBox;
JmpCrProcA.RetOp   := $C3;
ReadProcessMemory(CurrProc, AdrCreateProcessA, @OldCrp, SizeOf(OldCode), bw);
WriteProcessMemory(CurrProc, AdrCreateProcessA, @JmpCrProcA, SizeOf(far_jmp), Writen);
end;


Помоему это короче будет, чем объяснять зачем...


 
ors_archangel ©   (2006-12-27 03:13) [6]

Чтобы функция была универсальная тебе нужно сохранить все регистры (pusha), вызвать свой обработчик, потом вернуть все регистры на место (popa), потом сделать pop esi; call addr; push esi - это вызовет нужную функцию с теми параметрами, которые ей передали, нам не надо делать push p1; push p2 - потому что это уже было сделано calleroм этой функции (если ей вообще нужны параметры), pop esi-push esi нужно, так как call, который был сделан на оригинальную функцию сунул (как ему и положено) адрес возврата (4 байта) в стек, т.о. если мы сразу сделаем call addr, то этот call тоже сунет адрес возврата, и мы получим два адреса возврата в стеке, где должен был быть один - предпоследний здесь лишний, т.к. файтически будет занимать место одного из параметров, поэтому мы его предварительно извлекаем из стека (pop esi), а после вызова обратно кладём - push esi, тогда наш ret (который скрывается у тебя под end), передаст управление в нужную точку, понимаешь? Вопросы, дополнения, аплодисменты? Если прогу доделаешь, то интересно будет глянуть, не каждый день получаешь гигабайтовый лог, чего там какая-то прога вызывет!


 
ors_archangel ©   (2006-12-27 03:26) [7]

Да..... (по поводу [5])
Понимаш, здесь мы пишем int 3 (аппаратное прерывание) прям в код dll, откуда вызов должен быть, мне больше по душе вариант, когда мы для каждой функции создаем wrapper, тогда просто он вызывается, делает что там положено (что-то левое вызывает), потом сразу управление, например, MessageBox передаёт.
Вариант с Read/WriteProcessMemory конечно требует восстановления постоянно, но вообще, эти функции тебе не нужны, потому что ты ведь первый параметр указываешь CurrProc - current process - текущий процесс, т.е. все операции происходят в памяти твоего процесса, значит много лучше писать непосредсвенно - mov, movs - в таком духе, быстрее будет уж точно.
Если подробней о коде [3]

  pusha; // сохраняем все регистры процессора
  mov eax,beforeParam // передаём параметр - идентификатор перехваченной функции, видимо, здесь нужно ещё будет как-то параметры передать, ну а в принципи сам обработчким может немного стека взять, но вообще, не знаю, что ты потом с этим всем будешь делать?
  call before // вызываем какой-то обработчик перед вызовом перехваченной функции
  popa; // восстанавливаем все регистры после обработчка
  mov save_edi,edi // запоминаем регистр edi - мы щас его менять будем
  mov save_esi,esi // запоминаем регистр esi - мы щас его менять будем
  pop esi // сохраняем оригинальный адрес возврата
  mov edi,addr // адрес оригинальной функции
  call edi // вызов, собственно, перехваченной функции
  push esi // восстанавливаем оригинальный адрес возврата
  mov edi,save_edi // восстанавливаем регистры, которые юзали выше
  mov esi,save_esi
 // ну дальше, если нужен пост-обработчик, например, узнать, что функция вернула
  pusha
  mov eax,afterParam
  call after
  popa
  ret


 
4ert   (2006-12-27 03:55) [8]

Блин, спасибо!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!
Такого helpa нигде не найдешь!!!!
Прям сщас тестить начинаю!!!!!!!!
Да небольшой вопрос еще(в принципе мож и сам догоню, но от Мастеров как то надежней):
Как при старте моей dll поставить такую ловушку на все скажем функции имена которых лежат в массиве, скажем apis?

Как считаете, если сделать SetHook функцией возращающей AdrCreateProcessA  и вызывать её, ну, скажем, если у нас будет 100 функций, то так(просто, что сразу в голову пришло):


n:=0;
while n<100 do
begin
addrs[n]:=SetHooK(apis[n]);
end;


Как считаете прокатит?
Еще беспокоит вопрос чё с зараженным процессом будет? Вынесет нагрузку? :)


 
4ert   (2006-12-27 03:58) [9]

Блин, чё я косячу постоянно?..... естественно в цикле увеличиваем n на единицу(вообще если так, то наверно надо трехмерный массив пердавать, где будет функция API,  имя библиотеки, и указатель на функцию обработчик)


 
ors_archangel ©   (2006-12-27 05:00) [10]

Ну, во-первых, про Read/WritePM я загнул конечно, меня уж подклинило, видно :(
Но, как же ты тестишь-то, кода-то нет? (всего)


 
ors_archangel ©   (2006-12-27 05:06) [11]

Думаю, всё довольно быстро будет, если SetHook получит уже подготовленные данные, то чё он будет делать даже не знаю, переписывать, наверно, адреса в импорте процесса? Вобщем так предлагаю: сначала нужно узнать все адреса/имена импортируемых процессом функций, затем нужно выделить себе в целевом процессе место под ловушки (VirtualAllocEx идеально подойдёт), наклепать их для всех функций, потом проблема будет, где обработчик хранить - в чужом аль в своём процессе, если в чужом, то к нему этому процессу легко добраться будет, а если ж мы во свояси хотим, то между процессами, значит, какую-то коммуникацию придётся налаживать, что под Виндой не очень рентабельно может получиться. Вот… дальше переписываем адреса в импорт-таблице, чтобы наши ловушки ловили!!!

А почему "заражённый" процесс? Мы, чё, вирусы пишем-с? Ну да по фигу уже, что писать в такую рань!

Что же думаешь? А ты на чём писал? (так, интересно)


 
ors_archangel ©   (2006-12-27 05:11) [12]

Короче, как переписывать импорт-адреса кода у тебя нет? У меня есть, но он корявый (не всегда работает), так что придётся тебе искать самому, мне просто в лом, честно :( Даже здесь на сайте где-то помойму видел. Ещё лог нужно будет как-то непотекстовому организовать, иначе он тебе весь диск, извини, засрёт (такой большой, да, однако), поэтому, например, надо все функции проидексировать и писать вместо имён индексы, причём в бинаре, текст - прочь! Правда просмотрщик логов нужен тогда, вот такая засада, вот так всегда: за всё хорошее приходится платить :)


 
ors_archangel ©   (2006-12-27 05:12) [13]

Короче, как переписывать импорт-адреса кода у тебя нет? У меня есть, но он корявый (не всегда работает), так что придётся тебе искать самому, мне просто в лом, честно :( Даже здесь на сайте где-то помойму видел. Ещё лог нужно будет как-то непотекстовому организовать, иначе он тебе весь диск, извини, засрёт (такой большой, да, однако), поэтому, например, надо все функции проидексировать и писать вместо имён индексы, причём в бинаре, текст - прочь! Правда просмотрщик логов нужен тогда, вот такая засада, вот так всегда: за всё хорошее приходится платить :)


 
4ert   (2006-12-27 07:51) [14]

SetHok работает  так:
определяется адрес перехватываемой функции, и первые 5 байт её начала заменяются на длинный jmp переход по адресу обработчика перехвата.
Переписывать таблицу импорта я не хочу, ибо dll свою внедряю "на ходу" процесса, а хрен его знает, может он где-то уже сохранил адрес нужной ему функции, и будет обращаться к ней минуя таблицу импорта, а значит и наш обработчик..... Бесполезно тогда все. Прога где-то работать будет, а где-то нет.........
........А чё адреса нельзя выяснять походу выполнения цикла


AdrCreateProcessA := GetProcAddress(GetModuleHandle("user32.dll"), apis[n]);


Дело в том, что я хочу ставить ловушки на конкретные функции, которые ручками занесу в массив apis, для того, чтобы сделать красивую расшифровку действий процесса, например: "Шлем данные такие-то через сокет такой-то по адресу такому-то на порт такой-то". Далее перепишу SetHook, следующим образом:


function SetHook(libname,apif: PAnsiChar; adr: pointer): TApis;
var
HKernel32, HUser32: dword;
begin
CurrProc := GetCurrentProcess;
AdrCreateProcessA := GetProcAddress(GetModuleHandle(libname),  apif);
JmpCrProcA.PuhsOp  := $68;
JmpCrProcA.PushArg := adr;    //указатель на функцию обработчика(сюда собственно и скачем)
JmpCrProcA.RetOp   := $C3;
ReadProcessMemory(CurrProc, AdrCreateProcessA, @OldCrp, SizeOf(OldCode), bw);
WriteProcessMemory(CurrProc, AdrCreateProcessA, @JmpCrProcA, SizeOf(far_jmp), Writen);
result.addr:=AdrCreateProcessA;//адрес перехватываемой функции
result.Code:=oldcrp;                //первые пять байт до того пока мы их не        
end;                                        изменили


Где :


type
OldCode = packed record
 One: dword;
 two: word;
end;

TApis=packed record
  libname: PAnsiChar; //имя windows библиотеки
  FuncApi: PAnsiChar;//имя импортируемой функции(она же перехватываемая)
  FuncHook: pointer; //указатель на процедуру обработки
  addr: pointer;//адрес импортируемой функции
  code: oldcode;//старые первые пять байт
end;
var
OldCrp: OldCode;
APis: array of TApis;


процедура которая будет ставить hook:


procedure start(i,n: integer);//i-начиная с какого элемента массива apis ставить hook, n-по какой элемент.
var
tmp: TApis;
begin
while i<n do
  begin
    tmp:=SetHook(Apis[i].libname,Apis[i].FuncApi,Apis[i].FuncHook);
    Apis[i].addr:=tmp.addr;
    Apis[i].code:=tmp.code;
    i:=i+1;
  end;
end;


  Правда не компилил, но в принципе должно работать первые три параметра в каждом элементе массива Apis придется писать вручную.
Вызывать оч. просто, если хотим выставить все ловушки, то i у нас равно нулю, а n числу элементов массива. А дальше каждая процедура обработки после отправки сообщения, перезаписи памяти и запуска реальной функции будет находить себя в массиве Apis и, соответственно присваивать переменной i свой порядковый номер в массиве, а n:=i+1. Таким образом ввсё должно быть на мази.
  Обработчики находится будут в нашей dll, которая будет находится в памяти чужого процесса(все это уже реализовано).
 А процесс зараженный.... ну... типа как бы внедрение чужеродного организма в тело(инъекция) заражает, так и выходит наша dll чужеродный организм(инфекция :) ) , а процесс тело..... Я ж сказал, что не вирус...
В смысле на чем писал?

До логов еще рано :) .......

Так вот вернемся к вопросу о функции CallApi....
Уважаемые мастера, хотелось бы еще раз напомнить, что assembler я знаю всего один день!!!!
Короче что нужно(если это вооще возможно):
Итак нет абсолютно никаких проблем вызвать функцию API есть одна серьезная проблема, как не зная, не количество параметров передаваемой функции не их тип дать этой гадкой функции то, что ей нужно для работы?
Не возможно!!! -- кажется на первый взгляд, но есть маленькая зацепка, наш обработчик(из которого эта CallApi и запускается) принимает те же самые параметры, которые необходимы для работы реальной функции API....
Может есть какие соображения по этому поводу?
Как человек абсолютно не знающий assembler могу предположить схему работы всего этого механизма: наш обработчик, приняв параметры, сохраняет их в каком-то определенном(в каком?) виде в памяти передавая указатель на эту область функции API, а также адресс функции которую нужно выполнить с этими параметрами, CallApi получает как-то(как?) эти параметры и запускает сними нужную функцию..... Эт я так думаю....
Может кто-нить подскажет конкретно как реализовать?
(Извините за обилие слов, просто проблема настолько меня заинтересовала, что я аж сессию перестал сдавать..)


 
ors_archangel ©   (2006-12-27 08:31) [15]

Так, быстро сдавать сессию!!!

Я думаю, алгоритм, как в [6] вполне справляется с задачей вызова функции с любыми параметрами, как я уже писал, код довольно небольшой:

asm
 pusha
 наш обработчик-перехватчик вызова
 потом на асме нужно сделать процедуру прыжка (у тебя она там есть JMpCrProc, если не ошибаюсь.. мне идти уже пора) в памяти и прыгнуть на неё,
 процедура должна содержать код:  
    прочитать куда-нибудь область памяти начала ловушки (сохранить код ловушки)
    popa
    mov temp_var,esi
    pop esi
    call addr // здесь addr - оригинальный адрес
    push esi
    pusha
    записать обратно область памяти начала ловушки (восстановить)
    popa
    mov esi,temp_var
    ret
end


ну вот типа такого и я пошёл


 
Сергей М. ©   (2006-12-27 08:33) [16]


> пришла в голову мысль написать софтину призванную помогать
> мне в этом


Кулибин)

Существующие отладчики-то чем не угодили ?


 
4ert   (2006-12-27 15:02) [17]

Да тут скорее не в самой задачи, просто программист я почти никакой, вот и захотел опыта понабраться, цель по-моему вполне достойная, во всяком случае не голимый hello world! К тому же давно уже мечтал хоть почуть в асме подразобраться.............. Задача объемная затрагивает работу с памятью, работа с WinApi, Общение приложений и мн. др. , так, что мне кажется, вовсе неплохой курс.....
Уважаемый ors_archangel я так понимаю предложенный тобой код надо внедрять в процедуру обработчика......... Если я правильно понял, то работает он так(сорри за тупорыльство): pusha - сохранение всех регистров процессора(правда где?), в регистрах процессора и содержаться все параметры(ток тяжело для восприятия: как же можно сохранить, хрен знает сколько переменных в ограниченное число регистров(ведь данные могут быть не только типа word и DWord но и, скажем, TSocket - структура совсе-е-м не маленькая а помимо неё еще три параметра(функция connect)), потом запускаем функцию обработчик, аналогичную(в плане параметров) перехватываемой, которая читает из того места(из какого?) нужные и сохраненные нами параметры(как именно читает? Как понимает гда какой?), ну и обрабатывает их как-нить, передает управление приведенному тобой коду..... вопрос к моменту передачи управления содеражания регистров не изменится разве? их не нужно заново сохранять ?............ Твой код переписывает память(ну или можно вызвать сторонюю процедуру, написанную на pascal(как-то легче мной воспринимается)), и запускает оригинальную функцию с теми же параметрами, с которыми запускал функцию обработчика(возможно ток тогда, когда состояние регистров к моменту запуска оригинала было идентичным состоянию к моменту запуска функции обработчика(нашей)), два для меня "мутных" моменты(не могу понять):
1) Как же кокретно сохранить это состояние регистров?? (если кому не тяжело, мне бы очень помог для понимания примерчик запуска скажем messgeboxA с сохраненными регистрами( например есть функция идентичная messagebox в ней сохраняются параметры передаваемые ей, а потом непосредственно запускается MessgeBoxA с параметрами передаваемыми в индентичную функцию)?
2)Как же заставить функцию API считывать параметры именно с тех регистров какие мы сохраняли?(или этот набор стандартен для всех API? Тогда возникает вопрос: как же черт возьми функции MessageBoxA и connect могут считывать параметры из одних и тех же регистров, ведь число параметров разное, да их тип тож?(в Connect даже целые структуры!))
Заранее спасибо!!!!
(Еще раз сорри за тупорыльство, assembler язык наверно не сложно, но тяжело поддающийся пониманию(я в плане кода))


 
evvcom ©   (2006-12-27 15:13) [18]

> [17] 4ert   (27.12.06 15:02)
> pusha - сохранение всех регистров процессора(правда где?
> ),

команды push и pop работают со стеком

> в регистрах процессора и содержаться все параметры

зависит от соглашения вызовов, обычно в том же стеке, в Дельфи принято по умолчанию соглашение до 3-х параметров в eax, edx, ecx, остальные в стеке.

> данные могут быть не только типа word и DWord но и, скажем,
> TSocket - структура совсе-е-м не маленькая

А здесь передается не сама структура, а только указатель на нее, т.е. те же 4 байта.

> Как понимает гда какой?

RTFM. Соглашения о вызовах. Calling conventions.

> к моменту передачи управления содеражания регистров не изменится
> разве?

управления куда? Если вызываемой процедуре, то для нее важны только параметры, а если назад в вызывающую, то на то и делается pusha/popa


 
Elen ©   (2006-12-27 15:20) [19]


> pusha - сохранение всех регистров процессора(правда где?
> )

Если не ошибаюсь - В стеке.


> ток тяжело для восприятия: как же можно сохранить, хрен
> знает сколько переменных в ограниченное число регистров(ведь
> данные могут быть не только типа word и DWord но и, скажем,
>  TSocket - структура совсе-е-м не маленькая а помимо неё
> еще три параметра(функция connect)

А в регистрах не вся эта инфа сохраняется а только указатели на память с этими данными, вот их и PUSHают и потом POPают.


> 1) Как же кокретно сохранить это состояние регистров?? (если
> кому не тяжело, мне бы очень помог для понимания примерчик
> запуска скажем messgeboxA с сохраненными регистрами( например
> есть функция идентичная messagebox в ней сохраняются параметры
> передаваемые ей, а потом непосредственно запускается MessgeBoxA
> с параметрами передаваемыми в индентичную функцию)?


Если так уж любопытно то это можно увидеть в отладчике OllyDebuger или иже с ним. Там он очень хорошо показывает какие параметры в какие регистры запихнуть надо для MessageBoxA. А в прочем Делфийский отладчик тоже покажет и объяснит. Делаq прогу и запускай пошагово в дебугере - все  и увидиш. ;)


 
Игорь Шевченко ©   (2006-12-27 15:31) [20]


> Там он очень хорошо показывает какие параметры в какие регистры
> запихнуть надо для MessageBoxA.


Читай матчасть, чудо. Для MessageBox ни один параметр не "запихивается" в регистры.


 
Elen ©   (2006-12-27 15:38) [21]


> Игорь Шевченко

Так прошу прощения. Не в регистрах а в стеке записываются указатели на параметры перед вызовом. (Давно Olly не открываю)


 
Сергей М. ©   (2006-12-27 15:40) [22]


> 4ert   (27.12.06 15:02) [17]


Для начала пойми, что делать "универсальный" перехват (любая API-ф-ция из любого модуля с любым соглашением и произвольными наперед неизвестными параметрами) попросту лишен смысла.

Далее уясни, что перехват методом модификации EAT/IAT в АП процесса-жертвы в подавляющем большинстве случаев проще и эффективней, чем перехват методом модификации секции кода.

Следом вникни, что ассемблер в общем случае тут нафиг не нужен - все задуманное с успехом делается прямо в Паскаль-коде.


 
Игорь Шевченко ©   (2006-12-27 15:45) [23]


> Далее уясни, что перехват методом модификации EAT/IAT в
> АП процесса-жертвы в подавляющем большинстве случаев проще
> и эффективней, чем перехват методом модификации секции кода.
>


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


> делать "универсальный" перехват (любая API-ф-ция из любого
> модуля с любым соглашением и произвольными наперед неизвестными
> параметрами) попросту лишен смысла


Это точно.


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


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


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


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


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

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


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


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


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


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


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


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

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

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

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


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

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

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

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


 
evvcom ©   (2006-12-27 16:11) [29]

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

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


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


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


Это уже constraints.


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

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

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

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


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

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

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


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

evvcom ©   (27.12.06 16:07) [28]


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


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


 
evvcom ©   (2006-12-27 16:38) [34]

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

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


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


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


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


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


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


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

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


 
evvcom ©   (2006-12-27 16:47) [37]

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

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

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


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

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

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


 
GrayFace ©   (2006-12-27 17:25) [39]

Сергей М. ©   (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.

Уф...


 
GrayFace ©   (2006-12-27 17:31) [40]

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

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



Страницы: 1 2 вся ветка

Форум: "Начинающим";
Текущий архив: 2007.01.21;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.64 MB
Время: 0.041 c
8-1148541508
Der Nechk@ssoff
2006-05-25 11:18
2007.01.21
PlayList


2-1167143472
Veltis
2006-12-26 17:31
2007.01.21
Как написать мини фаервол?


2-1167996534
Neket
2007-01-05 14:28
2007.01.21
Query


2-1167297116
DVM
2006-12-28 12:11
2007.01.21
Чем отличаются CopyMemory и MoveMemory в Delphi?


2-1167323023
Kostafey
2006-12-28 19:23
2007.01.21
Null в SQL и Delphi





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский