Се­год­ня я покажу тебе нес­коль­ко при­емов обра­щения с Radare2 — мощ­ным фрей­мвор­ком для обратной раз­работ­ки. Мы выведа­ем скры­тый в прог­рамме пароль, для чего научим­ся извле­кать из исполня­емых фай­лов струк­туриро­ван­ную информа­цию и кон­верти­ровать получа­емые зна­чения. Затем изме­ним исполня­емый файл и выпол­ним недос­тупную при кор­рек­тном поведе­нии фун­кцию.

Уп­ражнять­ся мы будем на crackme, которую я написал спе­циаль­но для демонс­тра­ции. Ска­чать файл ты можешь с мо­его GitHub. Все дей­ствия мы будем про­водить в Debian Linux.

В статье «Radare2 с самого начала. Учим­ся исполь­зовать опен­сор­сный фрей­мворк для ана­лиза при­ложе­ний в Linux» мы уже начали иссле­довать этот исполня­емый файл. При запус­ке он откры­вает сокет и начина­ет слу­шать порт 14884. Если под­клю­чить­ся к это­му сокету при помощи netcat, то мож­но уви­деть приг­лашение для вво­да име­ни поль­зовате­ля или пароля. Если ввес­ти невер­ный пароль, сеанс завер­шится.

Ис­поль­зуя основные воз­можнос­ти Radare2, мы выяви­ли в прог­рамме фун­кции main, authenticate, check_username, check_password, start_server и нес­коль­ко дру­гих. В authenticate есть пять локаль­ных перемен­ных, а так­же вызов фун­кции с говоря­щим наз­вани­ем check_username, которой в качес­тве единс­твен­ного аргу­мен­та переда­ется зна­чение перемен­ной fd.

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

 

Распознание имени пользователя

Дизассемблированная функция check_username
Ди­зас­сем­бли­рован­ная фун­кция check_username

В фун­кции check_username дес­крип­тор сокета, передан­ный в аргу­мен­те, помеща­ется в перемен­ную fildes. Далее с помощью биб­лиотеч­ной фун­кции memset готовит­ся буфер памяти: src запол­няет­ся нулями, затем в него с помощью фун­кции read чита­ется поль­зователь­ский ввод из сокета, на который ука­зыва­ет fildes. То есть чита­ется как бы с уда­лен­ного устрой­ства.

Даль­ше в кон­соль на сто­роне сер­вера фун­кция printf выводит стро­ку [+] Reading username, потом в нее же с помощью fputs вылива­ется содер­жимое буфера src, содер­жащего вве­ден­ное имя поль­зовате­ля. Далее воз­вра­щен­ный фун­кци­ей fputs резуль­тат срав­нива­ется с -1. Если равенс­тво вер­но, выводит­ся сооб­щение об ошиб­ке, если же воз­вра­щен­ное зна­чение не рав­но -1 (ноль или положи­тель­ное зна­чение), то выпол­няет­ся переход на стро­ку 0x1605. Здесь про­исхо­дит вывод сим­вола кон­ца стро­ки — \n.

Пос­ле это­го готовят­ся парамет­ры для вызова биб­лиотеч­ной фун­кции strcpy. Она копиру­ет имя поль­зовате­ля src в новую область памяти — dest. Далее в стро­ке со сме­щени­ем 0x1628 в перемен­ную var_420h копиру­ется зна­чение 0x6262616a. В ком­мента­рии рядом Radare2 оста­вил мет­ку, зак­лючен­ную в оди­нар­ные кавыч­ки, — jabb:

mov dword [var_420h], 0x6262616a ; 'jabb'

По мне­нию Radare2, это шес­тнад­цатерич­ное чис­ло — набор букв в кодиров­ке UTF-8, исполь­зуемой в боль­шинс­тве дис­три­бути­вов Linux. Доверяй, но про­веряй! В коман­дную стро­ку под дизас­сем­бли­рован­ным лис­тингом фун­кции вве­ди

? 0x6262616a

Ни­же появит­ся спи­сок, в котором ука­зан­ное зна­чение будет кон­верти­рова­но в раз­ные типы дан­ных.

Приведение числа 0x6262616a к разным типам данных
При­веде­ние чис­ла 0x6262616a к раз­ным типам дан­ных

Нас инте­ресу­ет тип string. Нап­ротив него мы видим стро­ку jabb, что и тре­бова­лось доказать.

Неверно введенное имя пользователя
Не­вер­но вве­ден­ное имя поль­зовате­ля

Вер­немся в фун­кцию check_username. Стро­кой ниже (0x1632) мы видим, что в перемен­ную var_21ch помеща­ется сим­вол a, пока непонят­но для чего. Сно­ва про­мота­ем лис­тинг к началу фун­кции, где рас­положе­ны ком­мента­рии о перемен­ных:

...
; var int64_t var_41ch @ rbp-0x41c
; var int64_t var_420h @ rbp-0x420
...

Нас инте­ресу­ют эти две перемен­ные. Теперь пос­мотрим на код прис­воения им зна­чений:

mov dword [var_420h], 0x6262616a ; 'jabb'
mov word [var_41ch], 0x61 ; 'a'

Ис­тина где‑то рядом. В UTF-8 сим­вол может занимать от одно­го до четырех бай­тов, одна­ко латин­ские сим­волы, которые мы видим в лис­тинге, никог­да не пре­выша­ют одно­го бай­та. Таким обра­зом, зна­чение 0x6262616a — это четыре бай­та, что под­твержда­ет раз­мер при­емни­ка — двой­ное сло­во, dword.

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

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

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

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии