Источник: https://github.com/AnaktaCTF/CTF/blob/main — Reverse/python_static_analysis.md
Введение
В последние годы автоматизация реверс-инжиниринга становится неотъемлемой частью практики анализа бинарных файлов. В соревнованиях Capture The Flag ограниченные по времени задачи вынуждают искать способы сократить рутинную работу: ручной просмотр дизассемблера и поиск потенциальных уязвимостей отвлекает внимание от действительно важных моментов. Инструмент Radare2, будучи универсальным фреймворком для реверс-анализа, предоставляет богатый набор команд для исследования исполняемого кода и структуры бинарников, однако взаимодействие с ним через командную строку по-прежнему требует многократного ввода однотипных команд. Подключение библиотеки r2pipe и написание скриптов на Python позволяет объединить возможности Radare2 и гибкость Python, автоматизируя процессы извлечения строк, функций, потенциально опасных вызовов и даже поиска ROP-цепочек.
В этой статье мы последовательно рассмотрим, как установить и настроить Radare2 и r2pipe, разберём простые примеры скриптов, которые могут сэкономить десятки часов ручной работы, перейдём к более сложным техникам — распознаванию ROP-цепочек и анализу структуры ELF/PE, а в финале покажем, как собрать результаты анализа в удобный отчёт в формате CSV или JSON и интегрировать всё это в CI/CD-пайплайн для массового сканирования.
Установка и базовая настройка Radare2 и r2pipe
Перед тем как приступить к автоматизации, необходимо убедиться, что на вашей системе установлены две ключевые составляющие: сам фреймворк Radare2 и Python-библиотека r2pipe. Рассмотрим процесс установки шаг за шагом.
Шаг 1. Установка Radare2.
Для начала клонируем репозиторий с официального GitHub и соберём инструменты из исходников. Откройте терминал и выполните следующие команды:
git clone https://github.com/radareorg/radare2.git
cd radare2
./sys/install.sh
После выполнения скрипта в системе появятся утилиты r2, r2pm, radiff2 и другие. Проверьте версию:
r2 --version
Ожидаемый вывод должен содержать информацию о текущей версии (например, radare2 6.x.x). Если версия менее свежая, обновите её через r2pm update и r2pm upgrade.
Шаг 2. Установка r2pipe.
Вторая часть — установка Python-модуля для взаимодействия с Radare2 из скриптов. В среде Python 3 достаточно выполнить:
pip install r2pipe
Если вы используете виртуальное окружение (venv или virtualenv), активируйте его перед установкой. Удостоверьтесь, что импорт работает без ошибок:
import r2pipe
print(r2pipe.__version__)
Шаг 3. Подготовка проекта.
Создайте каталог для скриптов:
mkdir r2_automation
cd r2_automation
Здесь будем хранить все наши скрипты и шаблоны для отчётов. В дальнейшем настройте файл requirements.txt, чтобы зафиксировать зависимость:
r2pipe>=5.0.0
Теперь окружение готово, и можно переходить к созданию первых автоматизированных задач.
Простые скрипты для ускорения реверс-анализа
Выгрузка всех строк и поиск «интересных» подсказок
Одной из часто выполняемых операций при статическом анализе является извлечение строк из бинарника и попытка найти среди них очевидные ключи: пароли, URL, имена файлов конфигурации. Radare2 предоставляет команду iz для получения списка всех строк, а r2pipe позволяет легко получить результат в виде текста или JSON. Пример минимального скрипта:
import r2pipe
import re
def extract_strings(filepath):
r2 = r2pipe.open(filepath)
r2.cmd('aaa') # Автоанализ: функции, графы, пересечения
raw = r2.cmd('iz') # Получаем строки в текстовом формате
candidates = []
for line in raw.splitlines():
if re.search(r'https?://', line) or re.search(r'pass(word)?[:=]\S+', line, re.IGNORECASE):
candidates.append(line.strip())
return candidates
if __name__ == '__main__':
import sys
for s in extract_strings(sys.argv[1]):
print(s)
В этом примере мы вызываем r2.cmd('aaa') для того, чтобы Radare2 провёл полный автоплан анализа: обнаружил функции, переменные, графы переходов. Затем команда iz выдаёт все текстовые строки, обнаруженные в секциях .rodata и в других сегментах, подходящих для хранения литералов. Регулярные выражения ищут подстроки http:// или https://, а также простейшие варианты указания пароля, такие как password: или pass=. Такой скрипт сразу же выведет всё, что может быть полезным подсказкам при решении CTF-задачи.
Перечисление функций и их сигнатур
Для более глубокой работы может потребоваться список всех функций и их «сигнатур» — простое описание начала байткода, позволяющее сравнить функцию с набором шаблонов известных уязвимостей (например, паттернами переполнения буфера). Радикально ручной подход — просматривать дизассемблированные блоки одна за другой. Автоматический скрипт может выглядеть так:
import r2pipe
import hashlib
import json
# Пример шаблона: хэш первых 16 байт кода уязвимой функции
TEMPLATES = {
'vuln_func': '3a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d',
}
def list_functions(filepath):
r2 = r2pipe.open(filepath)
r2.cmd('aaa')
funcs = r2.cmdj('aflj') # Получаем функции в JSON
findings = []
for f in funcs:
addr = f['offset']
size = min(f['size'], 16)
# Читаем первые size байт
bytes_hex = r2.cmd(f'px {size} @ {addr}').split()
raw_bytes = bytes.fromhex(''.join(bytes_hex[1:]))
h = hashlib.md5(raw_bytes).hexdigest()
for name, tmpl_hash in TEMPLATES.items():
if h == tmpl_hash:
findings.append({'function': f['name'], 'type': name, 'address': hex(addr)})
return findings
if __name__ == '__main__':
import sys
print(json.dumps(list_functions(sys.argv[1]), indent=2, ensure_ascii=False))
Здесь команда aflj возвращает массив объектов с описанием функций: имя, адрес, размер, количество базовых блоков. Мы читаем первые 16 байт каждой функции, вычисляем MD5 и сравниваем с заранее подготовленными шаблонами. При совпадении скрипт выводит найденную уязвимую функцию с аннотацией, какую именно сигнатуру мы обнаружили.
Массовый экспорт дизассемблированных фрагментов в файлы
Если требуется вручную просматривать код, удобнее иметь дизассемблированный вывод для каждой функции в отдельном текстовом файле, вместо навигации по интерактивному интерфейсу Radare2. Следующий скрипт экспортирует дизассемблер в формате pd (pseudo-disassembly) для каждой функции:
import os
import r2pipe
def export_disasm(filepath, outdir='disasm'):
os.makedirs(outdir, exist_ok=True)
r2 = r2pipe.open(filepath)
r2.cmd('aaa')
funcs = r2.cmdj('aflj')
for f in funcs:
name = f['name'].replace('/', '_')
addr = f['offset']
# Формируем дизассемблированный вывод
disasm = r2.cmd(f'pdf @ {addr}')
filename = os.path.join(outdir, f'{name}_{addr:x}.asm')
with open(filename, 'w') as fd:
fd.write(disasm)
if __name__ == '__main__':
import sys
export_disasm(sys.argv[1])
После запуска в директории disasm появятся файлы вида sym.func_401000_401000.asm, содержащие дизассемблированный код с графом и комментариями Radare2. Таким образом, можно открыть любую функцию в текстовом редакторе или IDE и быстро пролистывать большие объёмы кода.
Расширенные техники анализа
Автоматическое распознавание упрощённых ROP-цепочек
Return-Oriented Programming (ROP) — популярный метод эксплуатации уязвимостей на уровне управления потоком выполнения. Поиск ROP-гаджетов вручную — трудоёмкая задача, но Radare2 умеет искать маленькие последовательности инструкций, завершающиеся инструкцией ret. С помощью r2pipe можно автоматизировать поиск и анализ таких гаджетов.
import r2pipe
import json
def find_rop_chains(filepath, max_chain_length=3):
r2 = r2pipe.open(filepath)
r2.cmd('aaa')
# Поиск всех гаджетов с помощью команды agj
gadgets = r2.cmdj('agj')
# Отбираем «хорошие» гаджеты длиной до 5 инструкций
short_gadgets = [g for g in gadgets if len(g.get('ops', [])) <= 5]
chains = []
# Простая комбинация первых трех подходящих гаджетов в цепочку
for i in range(len(short_gadgets) - max_chain_length + 1):
chain = short_gadgets[i:i + max_chain_length]
addresses = [hex(g['offset']) for g in chain]
chains.append(addresses)
return chains
if __name__ == '__main__':
import sys
rop = find_rop_chains(sys.argv[1])
print(json.dumps(rop, indent=2))
В этом примере мы применяем команду agj (all gadgets in JSON), фильтруем гаджеты, содержащие не более пяти операций (ops), а затем формируем простейшие цепочки из трёх адресов, где каждый адрес указывает на начало гаджета. Более сложные алгоритмы могут учитывать регистры, параметры функций и ограничения контекста, но даже этот базовый подход даёт старт для автоматизированной пост-эксплуатации.
Парсинг структуры ELF/PE и поиск потенциально небезопасных вызовов
Статический анализ импортов и экспортов бинарника позволяет выявить функции, связанные с небезопасными операциями: strcpy, system, memcpy и аналогами. Radare2 умеет выводить таблицу импортов и информацию о формате ELF/PE в JSON. Рассмотрим пример парсера:
import r2pipe
import json
UNSAFE = {'strcpy', 'strcat', 'sprintf', 'gets', 'system', 'memcpy'}
def analyze_imports(filepath):
r2 = r2pipe.open(filepath)
r2.cmd('aaa')
# Получаем информацию об импортированных функциях
imports = r2.cmdj('iij')
findings = []
for imp in imports:
name = imp.get('name')
if name in UNSAFE:
findings.append({'function': name, 'plt': hex(imp['plt'])})
return findings
if __name__ == '__main__':
import sys
result = analyze_imports(sys.argv[1])
print(json.dumps(result, indent=2, ensure_ascii=False))
Сначала мы вызываем aaa, затем через iij получаем список всех импортируемых функций: имя, адрес PLT (Procedure Linkage Table), библиотеку-источник и другие свойства. Сравнение с множеством UNSAFE позволяет быстро обнаружить подозрительные векторы для атак, например, вызов system("sh") или опасные операции копирования памяти без проверки длины.
Интеграция результатов в отчёт
Собрав различные типы находок (строки, функции, ROP-цепочки, небезопасные вызовы), имеет смысл объединить их в единый отчёт, удобный для дальнейшего анализа. Как правило, форматы CSV и JSON наиболее универсальны: их можно легко импортировать в таблицы, визуализировать или применять скрипты обработки.
Ниже пример формирования CSV-файла с описанием потенциальных уязвимостей:
import csv
import json
def merge_findings(strings, funcs, imports, rop):
# Столбцы: тип, имя/описание, детали
rows = []
for s in strings:
rows.append({'type': 'string', 'name': s, 'details': ''})
for f in funcs:
rows.append({'type': 'vuln_func', 'name': f['function'], 'details': f['type']})
for imp in imports:
rows.append({'type': 'unsafe_import', 'name': imp['function'], 'details': imp['plt']})
for chain in rop:
rows.append({'type': 'rop_chain', 'name': ' -> '.join(chain), 'details': ''})
return rows
def write_csv(rows, outpath='report.csv'):
with open(outpath, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=['type', 'name', 'details'])
writer.writeheader()
for r in rows:
writer.writerow(r)
if __name__ == '__main__':
import sys
# Заготовка вызовов предыдущих функций
from extract_strings import extract_strings
from list_functions import list_functions
from analyze_imports import analyze_imports
from find_rop_chains import find_rop_chains
binary = sys.argv[1]
strs = extract_strings(binary)
funcs = list_functions(binary)
imps = analyze_imports(binary)
rop = find_rop_chains(binary)
rows = merge_findings(strs, funcs, imps, rop)
write_csv(rows)
print(f'Отчёт сохранён в report.csv (строк: {len(rows)})')
Здесь мы объединяем результаты четырёх отдельных модулей и формируем единый CSV. Аналогичным образом можно формировать JSON — для этого достаточно заменить код записи на:
with open('report.json', 'w', encoding='utf-8') as f:
json.dump(rows, f, ensure_ascii=False, indent=2)
Полученные файлы легко подхватить внешними средствами визуализации или встроить в систему отчётности.
Рекомендации по дальнейшему развитию и интеграции в CI/CD
-
Модульность и повторное использование. Стоит разделить скрипты на отдельные модули (каждый функционал — отдельный файл), чтобы при необходимости легко заменять или расширять отдельные части анализа.
-
Версионирование и тестирование. Храните скрипты в системе контроля версий (Git), добавьте автотесты для проверки правильности парсинга выходных данных Radare2. Это поможет быстро обнаруживать поломки при обновлениях Radare2 или Python-зависимостей.
-
Интеграция с CI/CD. Настройте GitHub Actions, GitLab CI или Jenkins для автоматического запуска сканирования при появлении новых бинарников в репозитории. Пример фрагмента
.github/workflows/scan.yml:name: Static Analysis on: push: paths: - 'binaries/**' jobs: analyze: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt-get update && sudo apt-get install -y radare2 pip install r2pipe - name: Run analysis run: | python extract_strings.py binaries/${{ github.sha }}.bin > strings.json python list_functions.py binaries/${{ github.sha }}.bin > funcs.json python analyze_imports.py binaries/${{ github.sha }}.bin > imports.json python find_rop_chains.py binaries/${{ github.sha }}.bin > rop.json - name: Merge report run: | python merge_reports.py - name: Upload report uses: actions/upload-artifact@v2 with: name: analysis-report-${{ github.sha }} path: report.csvТакой подход позволит автоматически сканировать все новые сборки и сохранять отчёты как артефакты сборки.
-
Расширение шаблонов уязвимостей. Разработайте базу сигнатур на основе хэшей участков кода, регулярных выражений в дизассемблере или машинного обучения: более точные шаблоны помогут избегать ложных срабатываний и находить неизвестные прежде уязвимости.
-
Визуализация данных. Для удобства восприятия больших отчётов можно построить дашборды на основе загруженных CSV/JSON. Инструменты типа Grafana, Kibana или собственный React-интерфейс сделают процесс анализа результатов более наглядным.
-
Автоматическое уведомление. При обнаружении критических уязвимостей интегрируйте отправку уведомлений в Slack, Telegram или по электронной почте. Это позволит моментально реагировать на появление новых проблем в бинарниках.
Заключение
Автоматизация статического анализа бинарных файлов с помощью Radare2 и r2pipe на Python открывает широкие возможности для ускорения реверс-инжиниринга: от простой выгрузки строк и определения функций до распознавания ROP-цепочек и поиска небезопасных вызовов. В сочетании с системой отчётности CSV/JSON и CI/CD-пайплайнами это превращает разовые инструменты в полноценно работающий сканер, готовый к интеграции в любые процессы разработки и обеспечения безопасности. Начав с небольших скриптов, вы сможете постепенно вырастить надёжную инфраструктуру анализа, экономя время и ресурсы при решении CTF-задач или в промышленном анализе уязвимостей.