Содержание статьи
Итак, 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:\
, чтобы избежать ошибок, возникающих из‑за пробелов в названиях стандартных папок 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.
из поставки 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»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»