Фундаментальные основы хакерства. Определяем «почерк» компилятора по вызовам функций - «Новости» » Самоучитель CSS
Меню
Наши новости
Учебник CSS

Невозможно отучить людей изучать самые ненужные предметы.

Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3

Надо знать обо всем понемножку, но все о немногом.

Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы

Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)

Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода

Самоучитель CSS

Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5

Новости

Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости

Справочник CSS

Справочник от А до Я
HTML, CSS, JavaScript

Афоризмы

Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы

Видео Уроки


Наш опрос



Наши новости

       
18-07-2021, 00:01
Фундаментальные основы хакерства. Определяем «почерк» компилятора по вызовам функций - «Новости»
Рейтинг:
Категория: Новости

пре­дыду­щей статье – при­мер на Visual C++):
void MyFunc(double, struct XT) proc near
arg_0 = qword ptr 8
arg_8 = qword ptr 10h

IDA рас­позна­ла два аргу­мен­та, переда­ваемых фун­кции. Одна­ко не сто­ит безого­вороч­но это­му доверять, если один аргу­мент (нап­ример, int64) переда­ется в нес­коль­ких машин­ных сло­вах, то IDA оши­боч­но при­мет его не за один, а за нес­коль­ко аргу­мен­тов! Поэто­му резуль­тат, получен­ный IDA, надо трак­товать так: фун­кции переда­ется не менее двух аргу­мен­тов.


Впро­чем, и здесь не все глад­ко! Ведь ник­то не меша­ет вызыва­емой фун­кции залезать в стек материн­ской так далеко, как она захочет! Может быть, нам не переда­вали никаких аргу­мен­тов вов­се, а мы самоволь­но полез­ли в стек и стя­нули что‑то отту­да. Хотя это слу­чает­ся в основном вследс­твие прог­раммист­ских оши­бок из‑за путани­цы с про­тоти­пами, счи­тать­ся с такой воз­можностью необ­ходимо. Ког­да‑нибудь вы все рав­но с этим встре­титесь, так что будь­те начеку.


Чис­ло, сто­ящее пос­ле arg, выража­ет сме­щение аргу­мен­та отно­ситель­но начала кад­ра сте­ка.


Из­вле­каем передан­ные аргу­мен­ты из регис­тров про­цес­сора и раз­меща­ем их в памяти (при этом вспо­мина­ем, что переда­вали из вызыва­ющей фун­кции):


mov [rsp+arg_8], rdx ; указатель на буфер
movsd [rsp+arg_0], xmm0 ; значение с плавающей запятой

Да­лее ини­циали­зиру­ем стек, под­готав­лива­ем регис­тры к работе, про­изво­дим необ­ходимые вычис­ления, затем кла­дем в регис­тры зна­чения для переда­чи парамет­ров фун­кции printf:


mov
eax, 1imul
rax, 0mov
rcx, [rsp+28h+arg_8]add
rcx, raxmov
rax, rcxmov
r9, raxmov
rax, [rsp+28h+arg_8]mov
r8d, [rax+14h]movsd
xmm1, [rsp+28h+arg_0]movq
rdx, xmm1lea
rcx, _Format ; "%f,%x,%sn"call
printf

Об­рати вни­мание, перед вызовом printf прог­рамма в трех регис­трах раз­меща­ет зна­чения парамет­ров для переда­чи, а в чет­вертом регис­тре RCX (так же для переда­чи) помеща­ет ука­затель на фор­матную стро­ку спе­цифи­като­ров вывода: %f,%x,%sn. Фун­кция printf, как извес­тно, име­ет перемен­ное чис­ло аргу­мен­тов, тип и количес­тво которых как раз и зада­ют спе­цифи­като­ры. Вспом­ним: спер­ва в стек мы заноси­ли ука­затель на стро­ку, и дей­стви­тель­но, край­ний пра­вый спе­цифи­катор %s обоз­нача­ет вывод стро­ки. Затем в стек заноси­лась перемен­ная типа int и вто­рой спра­ва спе­цифи­катор, есть – вывод целого чис­ла в шес­тнад­цатерич­ной фор­ме.


А вот затем идет пос­ледний спе­цифи­катор %f. Заг­лянув в руководс­тво прог­раммис­та по Visual C++, мы проч­тем, что спе­цифи­катор %f выводит вещес­твен­ное зна­чение, которое в зависи­мос­ти от типа может занимать и четыре бай­та (float), и восемь (double). В нашем слу­чае оно явно занима­ет восемь бай­тов, сле­дова­тель­но, это double. Таким обра­зом, мы вос­ста­нови­ли про­тотип нашей фун­кции, вот он:


cdecl MyFunc(double a, struct B b)

Тип вызова cdecl озна­чает, что стек вычища­ет вызыва­ющая фун­кция. Вот толь­ко, увы, под­линный порядок переда­чи аргу­мен­тов вос­ста­новить невоз­можно. C++ Builder, кста­ти, так же вычищал стек вызыва­ющей фун­кци­ей, но самоволь­но изме­нял порядок переда­чи парамет­ров.


Мо­жет показать­ся, что если прог­рамму собира­ли в C++ Builder, то мы прос­то изме­няем порядок аргу­мен­тов на обратный, вот и все. Увы, это не так прос­то. Если име­ло мес­то явное пре­обра­зова­ние типа фун­кции в cdecl, то C++ Builder без лиш­ней самоде­ятель­нос­ти пос­тупил бы так, как ему велели, и тог­да бы обра­щение поряд­ка аргу­мен­тов дало бы невер­ный резуль­тат!


Впро­чем, под­линный порядок сле­дова­ния аргу­мен­тов в про­тоти­пе фун­кции не игра­ет никакой роли. Важ­но лишь свя­зать переда­ваемые и при­нима­емые аргу­мен­ты, что мы и сде­лали.


Об­ратите вни­мание: это ста­ло воз­можно лишь при сов­мес­тном ана­лизе и вызыва­емой, и вызыва­ющей фун­кций! Ана­лиз лишь одной из них ничего бы не дал!



info


Ни­ког­да не сле­дует безого­вороч­но полагать­ся на дос­товер­ность стро­ки спе­цифи­като­ров. Пос­коль­ку спе­цифи­като­ры фор­миру­ются «вруч­ную» самим прог­раммис­том, тут воз­можны ошиб­ки, под­час весь­ма труд­ноуло­вимые и дающие пос­ле ком­пиляции чрез­вычай­но загадоч­ный код!



Да­лее деини­циали­зиру­ем стек и зак­ругля­емся.


add rsp, 28h
retn
 

Соглашения о вызовах


Кое‑какие прод­вижения уже есть — мы уве­рен­но вос­ста­нови­ли про­тотип нашей пер­вой фун­кции. Но это толь­ко начало. Еще мно­го миль пред­сто­ит прой­ти… Если устал — передох­ни, тяп­ни ква­са, побол­тай с кем‑нибудь и про­дол­жим на све­жую голову. Мы прис­тупа­ем еще к одной очень важ­ной теме — срав­нитель­ному ана­лизу раз­ных типов вызовов фун­кций и их реали­зации в раз­ных ком­пилято­рах.


 

stdcall


Нач­нем с изу­чения стан­дар­тно­го сог­лашения о вызове — stdcall. Рас­смот­рим сле­дующий при­мер.


Ис­ходник при­мера stdcall#include <string.h>int __stdcall MyFunc(int a, int b, const char* c){ return a + b + strlen(c);}int main(){ printf("%xn", MyFunc(0x666, 0x777, "Hello,World!"));}

Вот, как дол­жен выг­лядеть резуль­тат его ком­пиляции в Visual C++ с отклю­чен­ной опти­миза­цией, то есть клю­чом /0d (в ином слу­чае ком­пилятор так заоп­тимизи­рует код, что исчезнет вся­кая поз­наватель­ная сос­тавля­ющая):


main proc near
sub rsp, 28h

IDA хорошо нам под­ска­зыва­ет, что кон­стан­та c содер­жит стро­ку Hello,World!. Ука­затель на нее помеща­ется в регистр R8, пред­назна­чен­ный для переда­чи целочис­ленных парамет­ров или, собс­твен­но, ука­зате­лей. Пер­вым по поряд­ку переда­ется ука­затель на стро­ку, заг­лянув в исходные тек­сты (бла­го они у нас есть), мы обна­ружим, что это самый пра­вый аргу­мент, переда­ваемый фун­кции. Сле­дова­тель­но, перед нами вызов типа stdcall или cdecl, но не PASCAL.


lea r8, c ; "Hello,World!"
mov edx, 777h ; b
mov ecx, 666h ; a

Сле­дом помеща­ем в два 32-бит­ных регис­тра EDX и ECX зна­чения двух перемен­ных 0x777 и 0x666, соот­ветс­твен­но. Пос­леднее – ока­залось самым левым аргу­мен­том. Что показы­вает нам пра­виль­но вос­ста­нов­ленный про­тотип фун­кции. Но так быва­ет не всег­да, IDA иног­да оши­бает­ся.


call MyFunc(int,int,char const *)

Об­рати вни­мание, пос­ле вызова фун­кции отсутс­тву­ют коман­ды очис­тки сте­ка от занесен­ных в него аргу­мен­тов. Если ком­пилятор не схит­рил и не при­бег­нул к отло­жен­ной очис­тке, то, ско­рее все­го, стек очи­щает сама вызыва­емая фун­кция, зна­чит, тип вызова — stdcall (что, собс­твен­но, и тре­бова­лось доказать).


mov edx, eax

Те­перь, переда­ем воз­вра­щен­ное фун­кци­ей зна­чение сле­дующей фун­кции, как аргу­мент.


Эта сле­дующая фун­кция printf, и стро­ка спе­цифи­като­ров показы­вают, что передан­ный аргу­мент име­ет тип int.


call printf
xor eax, eax
add rsp, 28h
retn
main endp

Те­перь рас­смот­рим фун­кцию MyFunc:


; int __fastcall MyFunc(int a, int b, const char *c)
int MyFunc(int, int, char const *) proc near

IDA пыта­ется самос­тоятель­но вос­ста­новить про­тотип фун­кции и… обла­мыва­ется. Ины­ми сло­вами, дела­ет это не всег­да успешно. Нап­ример, «Ида» оши­боч­но пред­положи­ла тип вызова fastcall, хотя на самом деле – stdcall. Вспом­ним: fastcall на 32-бит­ной плат­форме пред­полага­ет переда­чу парамет­ров через регис­тры про­цес­сора, тог­да как на плат­форме x64 пер­вые четыре парамет­ра всег­да переда­ются через регис­тры про­цес­сора, незави­симо от ука­зан­ного типа вызова.


var_18 = qword ptr -18h
var_10 = qword ptr -10h
arg_0 = dword ptr 8
arg_8 = dword ptr 10h
arg_10 = qword ptr 18h

Пе­редан­ные аргу­мен­ты из регис­тров помеща­ются в память, затем пос­ле ини­циали­зации сте­ка про­исхо­дит раз­мещение чис­ловых зна­чений в регис­трах, где про­исхо­дит их сло­жение: add ecx, eax.


mov [rsp+arg_10], r8
mov [rsp+arg_8], edx
mov [rsp+arg_0], ecx
sub rsp, 18h
mov eax, [rsp+18h+arg_8]
mov ecx, [rsp+18h+arg_0]
add ecx, eax
mov eax, ecx

Пре­обра­зова­ние двой­ного сло­ва (EAX) в учет­верен­ное (RAX):


cdqe

Ко­пиро­вание из сте­ка ука­зате­ля в стро­ку, в регистр RCX и в перемен­ную var_10. Далее ини­циали­зиру­ем перемен­ную var_18 зна­чени­ем -1, оче­вид­но, она будет счет­чиком.


mov rcx, [rsp+18h+arg_10]
mov [rsp+18h+var_10], rcx
mov [rsp+18h+var_18], 0FFFFFFFFFFFFFFFFh
loc_140001111:

И дей­стви­тель­но, на сле­дующем шаге она уве­личи­вает­ся на еди­ницу. К тому же здесь мы видим мет­ку безус­ловно­го перехо­да. Похоже, вмес­то того, что­бы узнать дли­ну стро­ки пос­редс­твом вызова биб­лиотеч­ной фун­кции strlen, ком­пилятор решил самос­тоятель­но сге­нери­ровать код. Что ж, флаг ему в руки!


inc [rsp+18h+var_18]

Зна­чения перемен­ных вновь копиру­ются в регис­тры для про­веде­ния опе­раций над ними.


mov rcx, [rsp+18h+var_10]
mov rdx, [rsp+18h+var_18]
cmp byte ptr [rcx+rdx], 0

Зна­чения регис­тров RCX и RDX скла­дыва­ются, а сум­ма срав­нива­ется с нулем. В слу­чае, если выраже­ние тож­дес­твен­но, то флаг ZF уста­нав­лива­ется в еди­ницу, в обратном слу­чае – в ноль. Инс­трук­ция JNZ про­веря­ет флаг Z. Если он равен нулю, тог­да про­исхо­дит переход на мет­ку loc_140001111, отку­да блок кода начина­ет выпол­нять­ся поновой.


jnz short loc_140001111

Ког­да флаг ZF равен еди­нице, осу­щест­вля­ется выход из цик­ла и переход на сле­дующую за ним инс­трук­цию, которая накоп­ленное в перемен­ной‑счет­чике чис­ло записы­вает в регистр RCX. Пос­ле это­го про­исхо­дит сло­жение зна­чений в регис­трах RCX и RAX, как пом­ним, в пос­леднем содер­жится сум­ма двух передан­ных чис­ловых аргу­мен­тов.


mov rcx, [rsp+18h+var_18]
add rax, rcx

В завер­шении фун­кции про­исхо­дит деини­циали­зация сте­ка:


add rsp, 18h
retn
int MyFunc(int, int, char const *) endp

Воз­вра­щение целочис­ленно­го аргу­мен­та на плат­форме x64 пре­дус­мотре­но в регис­тре RAX.


На вывод прог­рамма печата­ет: de9.


Вы­вод прог­раммы stdcall

Лег­ко про­верить: Hello,World! – 12 сим­волов, то есть 0xC. Открой каль­кулятор в Windows:


0x666 + 0x777 + 0xC = 0xDE9


пре­дыду­щей статье – при­мер на Visual C ): void MyFunc ( double , struct XT ) proc near arg_0 = qword ptr 8 arg_8 = qword ptr 10h IDA рас­позна­ла два аргу­мен­та, переда­ваемых фун­кции. Одна­ко не сто­ит безого­вороч­но это­му доверять, если один аргу­мент (нап­ример, int64) переда­ется в нес­коль­ких машин­ных сло­вах, то IDA оши­боч­но при­мет его не за один, а за нес­коль­ко аргу­мен­тов! Поэто­му резуль­тат, получен­ный IDA, надо трак­товать так: фун­кции переда­ется не менее двух аргу­мен­тов. Впро­чем, и здесь не все глад­ко! Ведь ник­то не меша­ет вызыва­емой фун­кции залезать в стек материн­ской так далеко, как она захочет! Может быть, нам не переда­вали никаких аргу­мен­тов вов­се, а мы самоволь­но полез­ли в стек и стя­нули что‑то отту­да. Хотя это слу­чает­ся в основном вследс­твие прог­раммист­ских оши­бок из‑за путани­цы с про­тоти­пами, счи­тать­ся с такой воз­можностью необ­ходимо. Ког­да‑нибудь вы все рав­но с этим встре­титесь, так что будь­те начеку. Чис­ло, сто­ящее пос­ле arg, выража­ет сме­щение аргу­мен­та отно­ситель­но начала кад­ра сте­ка. Из­вле­каем передан­ные аргу­мен­ты из регис­тров про­цес­сора и раз­меща­ем их в памяти (при этом вспо­мина­ем, что переда­вали из вызыва­ющей фун­кции): mov _

Теги: CSS

Просмотров: 572
Комментариев: 0:   18-07-2021, 00:01
Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь. Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

 
Еще новости по теме:



Другие новости по теме: