Как работа­ет выделе­ние и осво­бож­дение памяти? Каким обра­зом устро­ены обыч­ные и умные ука­зате­ли в C++? Как рас­познать опе­рато­ры работы с памятью, исполь­зуя дизас­сем­блер, не понима­ющий их истинную при­роду? Что­бы во всем этом разоб­рать­ся, нам пред­сто­ит по бай­тикам разоб­рать механиз­мы рас­пре­деле­ния динами­чес­кой памяти при­ложе­ния (ины­ми сло­вами, кучи) двух самых популяр­ных ком­пилято­ров и выявить раз­личия в их работе. Поэто­му в статье нас ждет мно­жес­тво дизас­сем­блер­ных лис­тингов и кода на C++.

Фундаментальные основы хакерства

Пят­надцать лет назад эпи­чес­кий труд Кри­са Кас­пер­ски «Фун­дамен­таль­ные осно­вы хакерс­тва» был нас­толь­ной кни­гой каж­дого начина­юще­го иссле­дова­теля в области компь­ютер­ной безопас­ности. Одна­ко вре­мя идет, и зна­ния, опуб­ликован­ные Кри­сом, теря­ют акту­аль­ность. Редак­торы «Хакера» попыта­лись обно­вить этот объ­емный труд и перенес­ти его из вре­мен Windows 2000 и Visual Studio 6.0 во вре­мена Windows 10 и Visual Studio 2019.

Ссыл­ки на дру­гие статьи из это­го цик­ла ищи на стра­нице авто­ра.

 

Идентификация указателя this

Ука­затель this — это нас­тоящий золотой клю­чик или, если угод­но, спа­сатель­ный круг, поз­воля­ющий не уто­нуть в бур­ном оке­ане ООП. Имен­но бла­года­ря this мож­но опре­делять при­над­лежность вызыва­емой фун­кции к тому или ино­му клас­су. Пос­коль­ку все невир­туаль­ные фун­кции объ­екта вызыва­ются непос­редс­твен­но — по фак­тичес­кому адре­су, объ­ект как бы рас­щепля­ется на сос­тавля­ющие его фун­кции еще на ста­дии ком­пиляции. Не будь ука­зате­лей this, вос­ста­новить иерар­хию фун­кций было бы прин­ципи­аль­но невоз­можно!

Та­ким обра­зом, пра­виль­ная иден­тифика­ция this очень важ­на. Единс­твен­ная проб­лема: как отли­чить его от ука­зате­лей на мас­сивы и струк­туры? Ведь экзем­пляр клас­са иден­тифици­рует­ся по ука­зате­лю this (если на выделен­ную память ука­зыва­ет this, это экзем­пляр клас­са), одна­ко сам this по опре­деле­нию — это ука­затель, ссы­лающий­ся на экзем­пляр клас­са. Зам­кну­тый круг! К счастью, есть одна лазей­ка... Код, манипу­лиру­ющий ука­зате­лем this, весь­ма спе­цифи­чен, что и поз­воля­ет отли­чить this от всех осталь­ных ука­зате­лей.

Во­обще‑то у каж­дого ком­пилято­ра свой почерк, который нас­тоятель­но рекомен­дует­ся изу­чить, дизас­сем­бли­руя собс­твен­ные прог­раммы на C++, но сущес­тву­ют и уни­вер­саль­ные рекомен­дации, при­мени­мые к боль­шинс­тву реали­заций. Пос­коль­ку this — это неяв­ный аргу­мент каж­дой фун­кции — чле­на клас­са, то логич­но отло­жить раз­говор о его иден­тифика­ции до раз­дела «Иден­тифика­ция аргу­мен­тов фун­кций». Здесь же мы обсу­дим, как реали­зуют переда­чу ука­зате­ля this самые популяр­ные ком­пилято­ры.

Здесь мы, конеч­но, говорим об архи­тек­туре x64. На 32-бит­ной плат­форме парамет­ры, выров­ненные до 32-бит­ного раз­мера, переда­ются через стек. С дру­гой сто­роны, на 64-бит­ной плат­форме дела обсто­ят инте­рес­нее: пер­вые четыре целочис­ленных аргу­мен­та переда­ются в регис­трах RCX, RDX, R8, R9. Если целочис­ленных аргу­мен­тов боль­ше, осталь­ные раз­меща­ются в сте­ке. Аргу­мен­ты, име­ющие зна­чения с пла­вающей запятой, переда­ются в регис­трах XMM0, XMM1, XMM2, XMM3. При этом 16-бит­ные аргу­мен­ты переда­ются по ссыл­ке. Замечу, все это каса­ется сог­лашения о вызовах в опе­раци­онных сис­темах Microsoft (Microsoft ABI), в Unix-подоб­ных сис­темах дела обсто­ят по‑дру­гому. Но не будем рас­пылять на них свое вни­мание.

Оба про­тес­тирован­ных мною ком­пилято­ра, Visual C++ 2019 и C++Builder 10.3, незави­симо от сог­лашения вызова фун­кции (__cdecl, __clrcall, __stdcall, __fastcall, __thiscall) переда­ют ука­затель this в регис­тре RCX, что соот­ветс­тву­ет его при­роде: this — целочис­ленный аргу­мент.

 

Идентификация операторов new и delete

Опе­рато­ры new и delete тран­сли­руют­ся ком­пилято­ром в вызовы биб­лиотеч­ных фун­кций, которые могут быть рас­позна­ны точ­но так же, как и обыч­ные биб­лиотеч­ные фун­кции. Авто­мати­чес­ки рас­позна­вать биб­лиотеч­ные фун­кции уме­ет, в час­тнос­ти, IDA Pro, сни­мая эту заботу с плеч иссле­дова­теля. Одна­ко IDA Pro есть не у всех и далеко не всег­да в нуж­ный момент находит­ся под рукой, да к тому же не все биб­лиотеч­ные фун­кции она зна­ет, а из тех, что зна­ет, не всег­да узна­ет new и delete... Сло­вом, при­чин иден­тифици­ровать их вруч­ную пре­дос­таточ­но.

Ре­али­зация new и delete может быть любой, но Windows-ком­пилято­ры в боль­шинс­тве сво­ем ред­ко реали­зуют фун­кции работы с кучей самос­тоятель­но. Зачем это? Нам­ного про­ще обра­тить­ся к услу­гам опе­раци­онной сис­темы. Одна­ко наив­но ожи­дать вмес­то new появ­ление вызова HeapAlloc, а вмес­то delete — HeapFree. Нет, ком­пилятор не так прост! Раз­ве он может отка­зать себе в удо­воль­ствии «выреза­ния мат­решек»? Опе­ратор new тран­сли­рует­ся в фун­кцию new, вызыва­ющую для выделе­ния памяти malloc, malloc же, в свою оче­редь, обра­щает­ся к HeapAlloc (или ее подобию — в зависи­мос­ти от реали­зации биб­лиоте­ки работы с памятью) — сво­еоб­разной «обер­тке» одно­имен­ной Win32 API-про­цеду­ры. Кар­тина с осво­бож­дени­ем памяти ана­логич­на.

Уг­лублять­ся в деб­ри вло­жен­ных вызовов слиш­ком уто­митель­но. Нель­зя ли new и delete иден­тифици­ровать как‑нибудь ина­че, с мень­шими тру­дозат­ратами и без лиш­ней голов­ной боли? Разуме­ется, мож­но! Давай вспом­ним все, что мы зна­ем о new:

  • new при­нима­ет единс­твен­ный аргу­мент — количес­тво бай­тов выделя­емой памяти, при­чем этот аргу­мент в подав­ляющем боль­шинс­тве слу­чаев вычис­ляет­ся еще на ста­дии ком­пиляции, то есть явля­ется кон­стан­той;
  • ес­ли объ­ект не содер­жит ни дан­ных, ни вир­туаль­ных фун­кций, его раз­мер равен еди­нице (минималь­ный блок памяти, выделя­емый толь­ко для того, что­бы было на что ука­зывать ука­зате­лю this); отсю­да будет очень мно­го вызовов типа

    mov ecx, 1 ; size
    call XXX

    где XXX и есть адрес new! Вооб­ще же, типич­ный раз­мер объ­ектов сос­тавля­ет менее сот­ни бай­тов... ищи час­то вызыва­емую фун­кцию с аргу­мен­том‑кон­стан­той мень­ше ста бай­тов;

  • фун­кция new — одна из самых популяр­ных биб­лиотеч­ных фун­кций, ищи фун­кцию с «тол­пой» перек­рес­тных ссы­лок;

  • са­мое харак­терное: new воз­вра­щает ука­затель this, а this очень лег­ко иден­тифици­ровать даже при бег­лом прос­мотре кода (обыч­но он воз­вра­щает­ся в регис­тре RCX);

  • воз­вра­щен­ный new резуль­тат всег­да про­веря­ется на равенс­тво нулю (опе­рато­рами типа test RCX, RCX), и, если он дей­стви­тель­но равен нулю, конс­трук­тор (если он есть) не вызыва­ется.

«Родимых пятен» у new более чем дос­таточ­но для быс­трой и надеж­ной иден­тифика­ции, тра­тить вре­мя на ана­лиз кода этой фун­кции совер­шенно ни к чему! Единс­твен­ное, о чем сле­дует пом­нить: new исполь­зует­ся не толь­ко для соз­дания новых экзем­пля­ров объ­ектов, но и для выделе­ния памяти под мас­сивы (струк­туры) и изредка — под оди­ноч­ные перемен­ные (типа int *x = new int, что вооб­ще маразм, но некото­рые так дела­ют). К счастью, отли­чить два этих спо­соба очень прос­то — ни у мас­сивов, ни у струк­тур, ни у оди­ноч­ных перемен­ных нет ука­зате­ля this!

Слож­нее иден­тифици­ровать delete. Каких‑либо харак­терных приз­наков эта фун­кция не име­ет. Да, она при­нима­ет единс­твен­ный аргу­мент — ука­затель на осво­бож­даемый реги­он памяти, при­чем в подав­ляющем боль­шинс­тве слу­чаев это ука­затель this. Но помимо нее, this при­нима­ют десят­ки, если не сот­ни дру­гих фун­кций! Рань­ше в эпо­ху 32-бит­ных кам­ней у иссле­дова­теля была удоб­ная зацеп­ка за то, что delete в боль­шинс­тве слу­чаев при­нимал ука­затель this через стек, а осталь­ные фун­кции — через регистр. В нас­тоящее же вре­мя, как мы уже неод­нократ­но убеж­дались, любые фун­кции при­нима­ют парамет­ры через регис­тры:

mov rcx, [rsp+58h+block] ; block
call operator delete(void *,unsigned __int64)

В дан­ном слу­чае IDA без замеша­тель­ств рас­позна­ла delete.

К тому же delete ничего не воз­вра­щает, но мало ли фун­кций пос­тупа­ют точ­но так же? Единс­твен­ная зацеп­ка — вызов delete сле­дует за вызовом дес­трук­тора (если он есть), но, пос­коль­ку конс­трук­тор как раз и иден­тифици­рует­ся как фун­кция, пред­шес­тву­ющая delete, обра­зует­ся зам­кну­тый круг!

Ни­чего не оста­ется, кро­ме как ана­лизи­ровать содер­жимое фун­кции: delete рано или поз­дно вызыва­ет HeapFree (хотя тут воз­можны и вари­анты: так, Borland/Embarcadero содер­жит биб­лиоте­ки, работа­ющие с кучей на низ­ком уров­не и осво­бож­дающие память вызовом VirtualFree). К счастью, IDA Pro в боль­шинс­тве слу­чаев опоз­нает delete и самос­тоятель­но нап­рягать­ся не при­ходит­ся.

А что про­изой­дет, если IDA не рас­позна­ет delete? Код будет выг­лядеть при­мер­но так:

mov rcx, [rsp+58h+block] ; block
call XXX
cmp [rsp+58h+block], 0
jnz short loc_1400010B0

Нег­лубокий ана­лиз показы­вает: в пер­вой строч­ке в регистр RCX, оче­вид­но для переда­чи в качес­тве парамет­ра, помеща­ется блок памяти. Похоже, это ука­затель на сущ­ность. А пос­ле вызова XXX выпол­няет­ся срав­нение это­го бло­ка памяти с нулем и, если блок не обну­лен, про­исхо­дит переход по адре­су. Таким нес­ложным обра­зом мы можем лег­ко иден­тифици­ровать delete, даже если IDA его не опре­деля­ет.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Крис Касперски

Крис Касперски

Известный российский хакер. Легенда ][, ex-редактор ВЗЛОМа. Также известен под псевдонимами мыщъх, nezumi (яп. 鼠, мышь), n2k, elraton, souriz, tikus, muss, farah, jardon, KPNC.

Юрий Язев

Юрий Язев

Широко известен под псевдонимом yurembo. Программист, разработчик видеоигр, независимый исследователь. Старый автор журнала «Хакер».

Check Also

Производитель сетевого оборудования Belden сообщил о взломе

В конце прошлой недели американский производитель сетевого оборудования Belden официально …

Оставить мнение