Се­год­ня я покажу тебе нес­коль­ко при­емов обра­щения с 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.

Так­же вспом­ним, что в сте­ке перемен­ные кла­дут­ся от стар­ших адре­сов к млад­шим (стек рас­тет свер­ху вниз). Поэто­му, что­бы най­ти раз­мер перемен­ной var_420h, надо из адре­са ее рас­положе­ния вычесть адрес рас­положе­ния сле­дующей перемен­ной:

0x420 0x41C = 4 байта

Это раз­мер перемен­ной var_420h, что тре­бова­лось под­твер­дить. Перемен­ные рас­полага­ются впри­тык друг к дру­гу. Ско­рее все­го, в исходном коде на язы­ке высоко­го уров­ня допол­нитель­ной перемен­ной не сущес­тву­ет. Ее для удобс­тва нарисо­вал Radare2 во вре­мя дизас­сем­бли­рова­ния, потому что для копиро­вания точ­но пяти бай­тов спо­соба не сущес­тву­ет. Отсю­да сле­дует, что в перемен­ной var_420h находит­ся стро­ка jabba.

Есть контакт: логин верен, осталось выудить пароль
Есть кон­такт: логин верен, оста­лось выудить пароль

Пос­мотрим, что тво­рит­ся в фун­кции даль­ше, начиная со стро­ки со сме­щени­ем 0x163b. Здесь готовят­ся парамет­ры — оче­вид­но, для переда­чи в фун­кцию: берут­ся ука­зате­ли на перемен­ные var_420h и dest. Пом­нишь, что находит­ся в пос­ледней? Пра­виль­но, копия зна­чения, которое рань­ше ввел поль­зователь. И эти две стро­ки переда­ются в фун­кцию compare_username.

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

Эта фун­кция сов­сем корот­кая, мы ее раз­берем в два сче­та! Аргу­мен­ты‑стро­ки перек­ладыва­ются в перемен­ные: вве­ден­ная поль­зовате­лем стро­ка — в src, jabba — в s2. При этом для перемен­ных сра­зу выделя­ется память с запасом: по восемь бай­тов, qword.

Да­лее в кон­соль на сто­роне сер­вера выводит­ся стро­ка «[–] Comparing username». Затем стро­ка src копиру­ется в область памяти dest. Этот трюк нужен для сня­тия со стро­ки модифи­като­ра const. То есть на новую копию стро­ки он не вли­яет.

Пос­ле это­го стро­ки под­готав­лива­ются для переда­чи в биб­лиотеч­ную фун­кцию strcmp, которая, как извес­тно, слу­жит для срав­нения строк. Она воз­вра­щает 0, ког­да передан­ные стро­ки оди­нако­вы, 1, ког­да пер­вая стро­ка боль­ше вто­рой, и -1, ког­да вто­рая боль­ше пер­вой. Раз­мерность строк в дан­ном слу­чае зависит от кодов пер­вых отли­чающих­ся сим­волов.

Пос­ле воз­вра­та из фун­кции strcmp с помощью инс­трук­ции test про­веря­ется зна­чение в регис­тре EAX, в котором воз­вра­тил­ся резуль­тат. Если это зна­чение не рав­но нулю, то есть стро­ки не рав­ны, выпол­няет­ся переход на коман­ду, где регис­тру EAX (в нем будет воз­вра­щен резуль­тат выз­вавшей фун­кции) прис­ваивает­ся 0. Ког­да стро­ки рав­ны (в EAX ноль), пос­ле test в регистр EAX помеща­ется еди­ница.

Ког­да в EAX будет ноль или еди­ница, выпол­нение прог­раммы воз­вра­щает­ся в фун­кцию check_username. И выпол­нение про­дол­жает­ся со стро­ки 0x1654, где нас под­жида­ет test eax, eax. Здесь все наобо­рот: если 0 — стро­ки нерав­ны, на сер­верной кон­соли выводит­ся [+] Wrong user; если 1, то стро­ки рав­ны, выводит­ся [+] Username is correct.

Пос­ле вывода сооб­щения о том, что юзер­нейм кор­рек­тный, в регистр EAX записы­вает­ся еди­ница для воз­вра­та и выпол­няет­ся безус­ловный переход в конец фун­кции, на инс­трук­ции leave и ret.

Ес­ли юзер­нейм невер­ный, пос­ле вывода стро­ки «Wrong user» с помощью инс­трук­ции CMP прог­рамма срав­нива­ет зна­чение перемен­ной fildes и ноль. Они никог­да не будут рав­ны! Ведь перемен­ная fildes всег­да содер­жит дес­крип­тор сокета. Если же она содер­жит ноль, зна­чит, где‑то рань­ше про­изош­ла ошиб­ка и досюда дело не дой­дет.

Та­ким обра­зом, сле­дующее усло­вие (jne 0x169d) всег­да выпол­няет­ся, регис­тру EAX прис­ваивает­ся ноль, и про­исхо­дит выход из фун­кции, при этом во вре­мя выпол­нения прог­раммы никог­да не будет выз­вана фун­кция secret. В нее переда­ются dest — вве­ден­ная поль­зовате­лем стро­ка и fildes — дес­крип­тор сокета. Код про­тиво­речит сам себе: если выпол­нение прог­раммы каким‑то обра­зом дой­дет досюда, дес­крип­тор сокета будет нулевой!

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

Комментируем листинг дизассемблера

Ес­ли тебе при­ходи­лось работать с IDA Pro, то навер­няка при изу­чении лис­тингов ты оставлял в них ком­мента­рии, что­бы потом быс­тро вспом­нить, что про­исхо­дит в коде. В Radare2 тоже мож­но соз­давать ком­мента­рии. Для это­го исполь­зует­ся коман­да CC с таким син­такси­сом:

CC [комментарий] [@смещение/адрес]

Еще раз обра­тим­ся к check_username, что­бы оста­вить в ней след — наш ком­мента­рий.

Оставим комментарий в подчеркнутой строке
Ос­тавим ком­мента­рий в под­чер­кну­той стро­ке

В коман­дной стро­ке Radare2 вве­ди коман­ду

CC full username: jabba @0x163b

Пос­ле это­го зап­роси дизас­сем­блер­ный лис­тинг фун­кции еще раз:

pdf @ sym.check_username

Ты уви­дишь изме­нения.

Комментарий
Ком­мента­рий

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

Распознавание пароля

На­деюсь, ты хорошо отдохнул и готов с нами выг­рызать пароль. Напом­ню, мы сей­час находим­ся в фун­кции authenticate, сра­зу пос­ле вызова check_username, в стро­ке со сме­щени­ем 0x181f. Здесь с помощью инс­трук­ции test про­веря­ется воз­вра­щен­ное зна­чение.

Ес­ли в регис­тре EAX находит­ся ноль (что быва­ет, ког­да имя вве­дено невер­но), выпол­няет­ся переход в конец фун­кции, соот­ветс­твен­но, authenticate в этом слу­чае тоже воз­вра­щает ноль. Если же в регис­тре EAX еди­ница, то с помощью инс­трук­ции jne выпол­няет­ся условный переход на стро­ку 0x182a. В кон­соли на сто­роне кли­ента выводит­ся стро­ка «Password:», затем вызыва­ется фун­кция check_password, ей переда­ется дес­крип­тор сокета.

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

Ар­гумент перек­ладыва­ется в перемен­ную fildes, а про­чие инс­трук­ции из этой фун­кции похожи на рас­смот­ренные рань­ше ана­логи: под­готав­лива­ется буфер памяти, в него чита­ются дан­ные из сокета, а в сер­верную кон­соль выводит­ся [+] Reading password.

Даль­ше про­исхо­дит все самое инте­рес­ное. В перемен­ную s1 помеща­ется содер­жимое упо­мяну­того буфера. А в перемен­ную s2, судя по ком­мента­риям, оставлен­ным дизас­сем­бле­ром, с двух заходов помеща­ется стро­ка SoloIsMyB*tch (как мы зна­ем, Хан Соло и Джаб­ба Хатт в кон­флик­те). Для удобс­тва Radare2 соз­дал допол­нитель­ную перемен­ную — var_418h.

Мож­но с боль­шой долей веро­ятности пред­положить, что это и есть эта­лон­ный пароль. Наш­ли?! Не спе­ши впе­ред парово­за. Обра­щает на себя вни­мание спо­соб записи дан­ных в перемен­ную, а имен­но исполь­зование инс­трук­ции movabs, которая за один при­сест целиком копиру­ет 64-бит­ное зна­чение в регистр без вспо­мога­тель­ных средств наподо­бие слу­жеб­ного сло­ва qword.

Сто­ит про­верить, что же скры­вает­ся под чис­ловыми кон­стан­тами. Вве­ди в коман­дную стро­ку сле­дующее:

? 0x794d73496f6c6f53
...
? 0x6863742a42794d
Вывод возможных значений переменных
Вы­вод воз­можных зна­чений перемен­ных

Проверяем найденную комбинацию

Приш­ло вре­мя вновь запус­тить server64.elf и под­клю­чить­ся к пор­ту 14884 с помощью netcat.

Введен верный пароль — защита пала
Вве­ден вер­ный пароль — защита пала

Пос­ле вво­да вер­ного пароля фун­кция authenticate воз­вра­щает в глав­ную фун­кцию (main) еди­ницу, в резуль­тате чего про­исхо­дит вызов фун­кции send_pw_list, которая вывали­вает в кон­соль на сто­роне кли­ента содер­жимое фай­ла passwords. Нап­ротив, если authenticate воз­вра­щает ноль, выпол­нение прог­раммы перехо­дит на строч­ку 0x1a6d, где вызыва­ется фун­кция reject. Которая, как мы зна­ем, сооб­щает: «Дро­иды не те, что ты ищешь».

В ито­ге защита взло­мана, пароль най­ден — отлично!

Динамический анализ

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

Что­бы запус­тить наш сер­вер под над­зором отладчи­ка R2, нуж­но вос­поль­зовать­ся клю­чом -d:

r2 -d server64.elf

Ког­да R2 запус­кает прог­рамму в отладчи­ке, пер­вая оста­нов­ка про­исхо­дит не на точ­ке вхо­да, фун­кции main или каких‑то дру­гих биб­лиоте­ках. Поэто­му, что­бы ока­зать­ся в фун­кции main, надо выпол­нить коман­ду dcu main — то есть debug continue until main (фун­кцию мож­но ука­зать любую).

Те­перь ход выпол­нения прог­раммы оста­новит­ся в начале фун­кции main. Дебажить прог­рамму с помощью коман­дной стро­ки негуман­но по отно­шению к самому себе. Radare2 пред­лага­ет впол­не снос­ный визу­аль­ный режим пря­мо в кон­соли. Что­бы его открыть, набери Vpp.

Radare2 в визуальном режиме
Radare2 в визу­аль­ном режиме

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

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

На­жатие кла­виши F7 поз­воля­ет сде­лать шаг впе­ред на сле­дующую инс­трук­цию с заходом в фун­кцию, если такая будет встре­чена. F8 же дела­ет шаг впе­ред, игно­рируя встре­тив­шуюся фун­кцию. Если вдруг нуж­но пос­тавить точ­ку оста­нова на стро­ку, где уста­нов­лен кур­сор, дос­таточ­но нажать F2. Что­бы получить дос­туп к коман­дной стро­ке, надо наб­рать дво­ето­чие. А что­бы покинуть визу­аль­ный режим, дос­таточ­но нажать Q.

На­игравшись с переме­щени­ем со стро­ки на стро­ку, уста­новим точ­ку оста­нова на вызов фун­кции authenticate:

db sym.authenticate

Про­дол­жим выпол­нение вво­дом коман­ды dc. Пос­ле нажатия Enter кур­сор перей­дет на новую стро­ку, но ничего нового мы не уви­дим. Ну конеч­но, прог­рамма зас­тря­ла на брейк‑пой­нте. Из дру­гой кон­соли под­клю­чись к сер­веру, наб­рав nc localhost 14884. Покажет­ся при­ветс­твен­ный экран, но при­ложе­ние оста­новит­ся до приг­лашения ввес­ти имя поль­зовате­ля.

При этом в пер­вой кон­соли, где выпол­няет­ся отладка сер­вера, содер­жимое обно­вит­ся и будет выведе­на информа­ция о дос­тижении точ­ки оста­нова. Обра­ти вни­мание: пер­вая стро­ка фун­кции помече­на бук­вой b, что говорит нам об уста­нов­ленном брейк‑пой­нте. Затем в этой же кон­соли мы вновь откро­ем визу­аль­ный режим, нажав V.

Здесь мы тоже можем баловать­ся перебо­ром строк, одна­ко наша целевая фун­кция — check_username. Пос­тавим на ее начало точ­ку оста­нова:

db sym.check_username

И про­дол­жим про­гон прог­раммы — dc. Вновь Radare2 сооб­щит о дос­тижении точ­ки оста­нова.

От­кро­ем визу­аль­ное отоб­ражение. Далее с чувс­твом, с тол­ком, с рас­ста­нов­кой, шаг за шагом, дос­тигнем инс­трук­ции cmp dword [rbp 0x424], 0.

Меж­ду тем, ког­да мы дой­дем до инс­трук­ции call sym.imp.read, прод­вигать­ся даль­ше будет невоз­можно до тех пор, пока во вто­рой кон­соли мы не вве­дем username. Пос­ле того как про­изой­дет про­вер­ка cmp dword [rbp 0x424], 0, неумо­лимо слу­чит­ся переход jne 0x562b2a12e69d, что не поз­волит прог­рамме исполнить фун­кцию secret.

Неумолимый переход
Не­умо­лимый переход

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

Ломаем сервер и меняем его поведение

На этот раз откро­ем сер­вер с воз­можностью модифи­кации дво­ично­го фай­ла. Для это­го запус­тим Radare2 с фла­гом -w. Не забудь сде­лать копию фай­ла на вся­кий слу­чай, если опе­рация вый­дет из‑под кон­тро­ля!

r2 –w server64.elf

При­выч­ным дви­жени­ем руки запус­тим ана­лиз: aaa. Перей­дем в фун­кцию check_username:

s sym.check_username

Отоб­разим дизас­сем­блер­ный код этой фун­кции, наб­рав pdf. Теперь надо перей­ти к инс­трук­ции, которую мы собира­емся заломить, то есть к условно­му перехо­ду: s 0x1684. Сме­щение в начале стро­ки изме­нит­ся.

Для наг­ляднос­ти мож­но вывес­ти толь­ко кон­крет­ные стро­ки, с которы­ми идет работа. Мож­но добавить или убрать 2–4 стро­ки (ука­зыва­ется в парамет­рах коман­ды): pd 4@$$ ($$ — инди­катор текущей стро­ки).

На сле­дующем шаге будем резать по живому, изба­вим­ся от перехо­да, заменив текущую инс­трук­цию нопами. Для это­го в R2 есть коман­да wao nop (что­бы узнать о ней боль­ше, заг­ляни в справ­ку, оно того сто­ит).

Вновь отоб­рази четыре текущие стро­ки или фун­кцию целиком, что­бы уви­деть изме­нения.

Замененная инструкция
За­менен­ная инс­трук­ция

На­вер­няка ты обра­тил вни­мание, что в ори­гина­ле была одна стро­ка, а пос­ле модифи­кации ста­ло две. Это про­изош­ло по той при­чине, что инс­трук­ция nop занима­ет в памяти один байт, тог­да как условный переход jne вмес­те с адре­сом — два бай­та. Сле­дова­тель­но, что­бы сох­ранить баланс в изме­няемой прог­рамме, вмес­то перехо­да с адре­сом Radare2 вста­вил два нопа. Теперь можешь прог­нать взло­ман­ный сер­вак в отладчи­ке (как мы это делали в пре­дыду­щем раз­деле) и убе­дить­ся, что дело доходит до фун­кции secret.

Выполнение функции secret
Вы­пол­нение фун­кции secret

Сливаем фото

Нас­тало вре­мя открыть фун­кцию secret в дизас­сем­бле­ре и узнать, что она дела­ет:

r2 server64-hacked.elf
aaa
s sym.secret
pdf
Дизассемблерный листинг функции secret
Ди­зас­сем­блер­ный лис­тинг фун­кции secret

В начале фун­кции нет ничего необыч­ного, получен­ные аргу­мен­ты рас­кла­дыва­ются по перемен­ным: вве­ден­ная юзе­ром стро­ка (пер­вый аргу­мент) помеща­ется в перемен­ную s, дес­крип­тор сокета (вто­рой аргу­мент) — в fd. На сер­верной сто­роне выводит­ся стро­ка, сиг­нализи­рующая о вызове фун­кции secret.

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

Да­лее в сер­верную кон­соль выводит­ся стро­ка [+] Code correct, sending data. Потом готовят­ся парамет­ры для вызова фун­кции: счи­тыва­ется зна­чение гло­баль­ной перемен­ной plan_jpg_len (пред­став­ляющее раз­мер изоб­ражения), копиру­ется дес­крип­тор сокета. Мы прек­расно пом­ним, что он был передан в фун­кцию в качес­тве аргу­мен­та, пос­ледним парамет­ром (на самом деле пер­вым) переда­ется ука­затель на мас­сив бай­тов, сос­тавля­ющих изоб­ражение, — plan_jpg.

Пос­ле это­го выпол­няет­ся безус­ловный переход в конец фун­кции. Ниже пос­ледний сим­вол стро­ки s про­веря­ется на равенс­тво вось­ми. Ага! То есть прог­рамма пред­полага­ет одну из двух строк: WtfR2 и WtfR8.

Вы­ше мы рас­смот­рели пер­вый вари­ант и вывод в кли­ент­скую кон­соль мас­сива бай­тов plan_jpg, сей­час мы рас­смат­рива­ем вто­рой вари­ант. В нем дей­ствия похожи, толь­ко в качес­тве раз­мера переда­ваемых дан­ных берет­ся зна­чение гло­баль­ной перемен­ной portfolio_jpg_len, а роль переда­ваемых дан­ных игра­ет мас­сив бай­тов portfolio_jpg.

На этом дизас­сем­блер­ный код сер­вера пол­ностью рас­смот­рен, пора дви­гать­ся даль­ше.

Получаем фото

От­кро­ем две кон­соли: в одной запус­тим сер­вер, во вто­рой под­клю­чим­ся к сер­веру и вве­дем одно из двух спе­циаль­ных имен, к при­меру WtfR2, в резуль­тате получим вывод, при­веден­ный на скрин­шоте ниже.

Получение потока байтов и вывод его в консоль
По­луче­ние потока бай­тов и вывод его в кон­соль

Этот поток бай­тов надо записать в файл. С кли­ент­ской сто­роны вве­ди

echo WtfR2 | nc localhost 14884 > Изображения/picR2.jpg

Пос­мотрим, что, по мне­нию опе­раци­онной сис­темы, этот файл собой пред­став­ляет:

file Изображения /picR2.jpg

На выходе получим data. Мно­гоз­начитель­но! Дан­ные ведь могут быть любого типа. Выходит, Radare2 не опре­делил, что это за файл. Что же мы можем пред­при­нять? Вырезать из фай­ла ненуж­ную часть! Открой файл в тек­сто­вом редак­торе:

nano Изображения/picR2.jpg
Открытый в nano файл
От­кры­тый в nano файл

И уда­ли в нем всю вер­хнюю часть, вклю­чая сло­во Username и дво­ето­чие пос­ле него. В ито­ге дол­жно получить­ся так, как на скрин­шоте.

Удаление части файла
Уда­ление час­ти фай­ла

Сох­раняй файл и зак­рывай редак­тор. Пос­мотрим теперь, как ОС опре­деля­ет файл:

file Изображения/picR2.jpg

Ес­ли на пре­дыду­щем шаге все сде­лано пра­виль­но, ути­лита выведет в кон­соль сле­дующее опи­сание фай­ла:

Изображения/picR2.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=1, datetime=2008:09:27 09:19:53], progressive, precision 8, 564x690, components 1

Пос­ледний тест, который сле­дует про­вес­ти, — это открыть файл в прос­мот­рщи­ке изоб­ражений:

gwenview Изображения/picR2.jpg

Ре­зуль­тат при­водить не буду, что­бы тебе было любопыт­нее пов­торить все опи­сан­ное самому.

Так­же в качес­тве упражне­ния можешь ска­чать с сер­вера вто­рой файл — с име­нем WtfR8.

Об­рати вни­мание: для редак­тирова­ния это­го фай­ла надо исполь­зовать vim. А ты думал, мы прос­то так уста­нав­ливали его в пер­вой статье? Если сно­ва забыл, как им поль­зовать­ся, пос­ле уда­ления ненуж­ной час­ти фай­ла набери дво­ето­чие, коман­ду wq и наж­ми Enter.

Редактирование двоичного файла в vim
Ре­дак­тирова­ние дво­ично­го фай­ла в vim

Выводы

Мо­жем ли мы счи­тать нашу мис­сию завер­шенной? Теперь опре­делен­но! По ходу упражне­ния с дизас­сем­бле­ром нам уда­лось не толь­ко бла­гопо­луч­но получить дос­туп к акка­унту Джаб­бы Хат­та, но и ута­щить с сер­вера сек­ретные матери­алы.

Но глав­ной нашей задачей на про­тяже­нии обе­их ста­тей было научить­ся исполь­зовать Radare2 для ана­лиза прог­рамм в Linux.

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