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

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

Би­нар­ная инс­тру­мен­тация под­разуме­вает встав­ку кода в ском­пилиро­ван­ную прог­рамму. В отли­чие от ста­тичес­кой, похожей на зараже­ние фай­ла через уста­нов­ку jmp-трам­пли­нов, динами­чес­кая инс­тру­мен­тация не меня­ет код на дис­ке, а инжекти­рует прав­ки в момент исполне­ния. Pin работа­ет как JIT-ком­пилятор. Исходный код переком­пилиру­ется в такие же ассем­блер­ные инс­трук­ции, но с про­изволь­ными встав­ками. И помеща­ется в кеш, где будет исполнять­ся, пока не дой­дет до сле­дующе­го учас­тка, который надо переком­пилиро­вать. Это замед­ляет заг­рузку, но с точ­ки зре­ния ста­рого кода дела­ет встав­ленный код невиди­мым!

Возь­мем для при­мера вот такую пос­ледова­тель­ность инс­трук­ций:

00401000 cmp ecx,A
00401003 jne 00401007
00401005 int 3
00401007 mov eax,0

Вот как дол­жна выг­лядеть кеширо­ван­ная копия пос­ле инс­тру­мен­тации:

01401000 call instrumentation
01401005 cmp ecx,A
01401008 call instrumentation
0140100D jne 0140101B
0140100F call instrumentation
01401014 int 3
01401016 call instrumentation
0140101B mov eax,0

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

 

Установка Pin

Я исполь­зую Pin вер­сии 3.30, но подоз­реваю, что API в будущем не изме­нит­ся. Пер­вым делом качай дис­три­бутив с офи­циаль­ного сай­та. Стра­ница не пус­кает с некото­рых IP, но пря­мые ссыл­ки работа­ют (вот вер­сии для Linux и для Windows). Pin обыч­но ста­вят в каталог C:\PIN, что­бы избе­жать оши­бок, воз­ника­ющих из‑за про­белов в наз­вани­ях стан­дар­тных папок Windows.

Инс­тру­мен­ты, соб­ранные для работы с Pin, называ­ются PinTools. Их мож­но рас­смат­ривать как пла­гины, исполь­зующие API для управле­ния инс­тру­мен­таци­ей. Лицен­зия раз­реша­ет рас­простра­нять их толь­ко в виде исходни­ков. Поэто­му сегод­ня будет мно­го сбор­ки.

 

Собираем исходники

Со­бирать будем для Windows 10. Пос­коль­ку софт в пер­вую оче­редь ори­енти­рован на Linux, там проб­лем вооб­ще не будет. Нам пот­ребу­ется ком­пилятор Visual Studio и GNU Make.

Я исполь­зую Community-вер­сию Visual Studio 2022. Нам нужен пакет Desktop development for C++. Советую при уста­нов­ке выб­рать англий­ский язык, что­бы не путать­ся в меню. Ста­вим Cygwin 64 в корень дис­ка, кача­ем пакет Make из катего­рии Devel.

Нас­тро­им в кон­соли перемен­ные окру­жения:

set PATH=%PATH%;C:\cygwin64\bin

Те­перь make будет запус­кать­ся по име­ни.

"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat"

За­пус­каем 32- или 64-раз­рядную вер­сию перед каж­дой сбор­кой. Вмес­те с Pin идет куча при­меров, собира­ем всю пап­ку или один кон­крет­ный.

cd C:\PIN\source\tools\SimpleExamples
make all TARGET=intel64
make obj-ia32/icount.dll TARGET=ia32
make obj-intel64/icount.dll TARGET=intel64

Тес­товые прог­раммы из статьи собира­ются в IDE, осталь­ное — через тот же Make.

 

Разбираем icount

Возь­мем самый прос­той при­мер — icount.cpp из пос­тавки Pin. Я вырезал необя­затель­ную часть кода, так что твой файл будет нем­ного отли­чать­ся.

C:\PIN\source\tools\SimpleExamples\icount.cpp
#include "pin.H"
#include <iostream>
UINT64 ins_count = 0;
VOID docount()
{
ins_count++;
}
VOID Instruction(INS ins, VOID* v)
{
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}
VOID Fini(INT32 code, VOID* v)
{
std::cerr << "Count " << ins_count << std::endl;
}
INT32 Usage()
{
cerr << "Prints out the number of executedinstructions.\n" << std::endl;
return -1;
}
int main(int argc, char* argv[])
{
if (PIN_Init(argc, argv))
{
return Usage();
}
INS_AddInstrumentFunction(Instruction, 0);
PIN_AddFiniFunction(Fini, 0);
PIN_StartProgram();
return 0;
}

Ис­полне­ние начина­ется в main. Давай раз­берем, что дела­ет API.

  • PIN_Init — ини­циали­зация PIN. Тут про­исхо­дит раз­бор аргу­мен­тов коман­дной стро­ки. В слу­чае неуда­чи воз­вра­щает ненуле­вое зна­чение, а мы выводим справ­ку об исполь­зовании.
  • INS_AddInstrumentFunction регис­три­рует фун­кцию, которая будет выз­вана для каж­дой встре­чаемой по ходу исполне­ния ассем­блер­ной инс­трук­ции. Это так называ­емая Instrumentation Routine, она вызыва­ется лишь однажды. Здесь мы воль­ны ана­лизи­ровать или модифи­циро­вать про­веря­емый код, собс­твен­но про­водить встав­ку сво­его кода.
  • INS_InsertCall при­нима­ет перемен­ное чис­ло аргу­мен­тов, дос­тупные кон­стан­ты ука­заны в до­кумен­тации. Помеща­ет вызов про­изволь­ной фун­кции перед инс­трук­цией ins, в дан­ном слу­чае не переда­вая никаких аргу­мен­тов.
  • docount вызыва­ется каж­дый раз перед иссле­дуемой инс­трук­цией. Это Analysis Routine, фун­кция, которая дол­жна быть мак­сималь­но опти­мизи­рова­на, что­бы не тор­мозить про­цесс. В ней все­го одна коман­да — уве­личе­ние гло­баль­ного счет­чика ins_count.
  • PIN_AddFiniFunction регис­три­рует фун­кцию Fini, она вызыва­ется по завер­шении про­цес­са или вызову PIN_Detach внут­ри инс­тру­мен­та.
  • PIN_StartProgram запус­кает исполне­ние иссле­дуемо­го про­цес­са. Никог­да не воз­вра­щает управле­ние. Пос­ле вызова регис­тра­ция новых фун­кций невоз­можна.
 

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

Пос­ле сбор­ки под 32-раз­рядную архи­тек­туру в пап­ке obj-ia32 появи­лась DLL. При­меним ее на деле.

C:\PIN\pin.exe -t obj-ia32\icount.dll -- cmd /c ver

Microsoft Windows [Version 10.0.19043.1889]
Count 5609927

Ар­гумент -t содер­жит путь до PinTool. Про­чер­ки отде­ляют аргу­мен­ты целевой прог­раммы. В дан­ном слу­чае про­сим кон­соль ска­зать текущую вер­сию ОС.

Обе прог­раммы пишут в кон­соль, сна­чала cmd, затем Fini выводит чис­ло инс­трук­ций. Надо понимать, что учи­тыва­ется не один код при­ложе­ния, а вся трас­са, вклю­чая биб­лиоте­ки и заг­рузчик, то есть часть кода из NTDLL и внут­реннос­ти WinAPI.

C:\PIN\pin.exe -pid 4956 -t obj-ia32\icount.dll

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

 

Гранулярность

В целях опти­миза­ции Pin обра­баты­вает опко­ды груп­пами. Их сущес­тву­ет три вида: трас­са, базовый блок и оди­ноч­ная инс­трук­ция. Trace — это набор базовых бло­ков; BBL — набор инс­трук­ций, который обры­вает­ся коман­дой переда­чи управле­ния; Ins — любая инс­трук­ция.

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

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

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

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


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

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

    Подписаться

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