Реверс-инжиниринг в 2025 году — это твой билет в элиту кибербезопасности. Навыки реверса повышают заработок багхантера на 67%, а медианная зарплата реверс-инженера в РФ достигла 280k рублей. Разбираем бинарники. Взламываем защиту. Анализируем малварь. Зарабатываем больше.
Пристегнись, сейчас разберем весь стек от crackme до LockBit 3.0.
Ключевые выводы
- Навыки реверса повышают заработок багхантера на 67% - медианная зарплата реверс-инженера в РФ достигла 280k рублей в 2024 году
- IDA Pro 8.4 vs Ghidra 11.1 - Ghidra бесплатна и доступна в РФ, IDA Pro требует VPN для покупки (от $1,879)
- Автоматизация через IDAPython и Ghidra API - ключ к эффективному анализу современной малвари типа LockBit 3.0
- FlareVM + российские песочницы - оптимальная связка для безопасного анализа вредоносного кода
- Время на освоение: 3-6 месяцев для middle специалиста
- Бюджет: 50,000-150,000 рублей (с учетом лицензий и железа)
Содержание
- Что нужно знать
- Инструментарий реверс-инженера 2025: IDA Pro 8.4 vs Ghidra 11.1
- Анализ crackme: пошаговый разбор защитных механизмов
- Работа с упаковщиками и протекторами: VMProtect, Themida, Scylla
- Исследование малвари: от LockBit 3.0 до мобильных угроз
- Автоматизация реверса: IDAPython, Ghidra API, Binary Ninja
- Часто задаваемые вопросы
- Практический пример: анализ LockBit 3.0 практические кейсы
- Решение типовых проблем
- Ресурсы для углубления
Что нужно знать
Без базы далеко не уедешь. Проверь себя по этому списку.- x86/x64 архитектура - понимание регистров, стека, calling conventions для анализа бинарников
- Ассемблер - чтение дизассемблированного кода, понимание инструкций MOV, JMP, CALL
- Python 3.8+ - для написания IDAPython скриптов и автоматизации Ghidra
- Основы малвари - знание техник обфускации, упаковщиков, anti-debugging
- Виртуализация - работа с VMware/VirtualBox для создания изолированной среды анализа. Подробнее о виртуализации.
- Английский технический - большинство документации и crackme на английском языке
Архитектура современного реверс-инжиниринга
Современный реверс-инжиниринг работает по принципу трех китов. Статика показывает что есть, динамика — что происходит, автоматизация экономит время.
Инструментарий реверс-инженера 2025: IDA Pro 8.4 vs Ghidra 11.1
Сравнительный анализ ключевых инструментов
Критерий | IDA Pro 8.4 | Ghidra 11.1 | Binary Ninja | x64dbg |
---|---|---|---|---|
Стоимость (РФ) | $1,879 (≈188k руб) | Бесплатно | $399 (≈40k руб) | Бесплатно |
Доступность | Через VPN | Свободно | Через VPN | Свободно |
Декомпиляция | Hex-Rays (+$2,871) | Встроенная | Встроенная | Нет |
Скриптинг | IDAPython | Python/Java | Python | JavaScript |
Архитектуры | 50+ | 20+ | 15+ | x86/x64 |
Плагины | 500+ | 100+ | 200+ | 300+ |
IDA Pro 8.4: золотой стандарт для профессионалов
Король дизассемблеров. Но с нюансами для российского рынка.Преимущества:
- Лучший в классе дизассемблер с поддержкой экзотических архитектур. Подробности на
Ссылка скрыта от гостей.
- Огромная база плагинов (LazyIDA, Keypatch, FindCrypt)
- Превосходная работа с обфусцированным кодом
- Интеграция с Hex-Rays декомпилятором
- Высокая стоимость (≈280k рублей за полную версию)
- Требует VPN для покупки и обновлений
- Сложности с техподдержкой из РФ
Ghidra 11.1: open-source альтернатива от NSA
Бесплатная мощь от Агентства национальной безопасности США. Парадокс, но факт.Преимущества для РФ:
- Полностью бесплатна и доступна без ограничений
- Встроенный декомпилятор высокого качества
- Активное сообщество российских разработчиков
- Поддержка скриптинг на Python и Java.
Ссылка скрыта от гостейдля начинающих.
Практический пример настройки Ghidra для анализа LockBit 3.0:
Python:
# ghidra_crypto_finder.py - Скрипт для эффективного поиска крипто-констант
import struct
def find_and_label_crypto_constants():
"""
Ищет в памяти программы известные криптографические константы,
используя эффективный поиск по байтовым паттернам, и создает для них метки.
"""
crypto_patterns = {
"MD5_IV0": 0x67452301,
"SHA256_IV0": 0x6A09E667,
"Blowfish_P0": 0x243F6A88
}
# Определяем порядок байт (little-endian или big-endian) для корректного поиска
endian_format = "<I" if currentProgram.getLanguage().isLittleEndian() else ">I"
found_count = 0
start_addr = currentProgram.getMinAddress()
for name, value in crypto_patterns.items():
# Преобразуем константу в байтовую последовательность
pattern = struct.pack(endian_format, value)
# Используем встроенную быструю функцию findBytes для поиска
addr = findBytes(start_addr, pattern, 1)
while addr is not None:
print("Найден паттерн '{}' по адресу: {}".format(name, addr))
createLabel(addr, name, True)
found_count += 1
# Продолжаем поиск со следующего адреса, чтобы найти все вхождения
addr = findBytes(addr.add(1), pattern, 1)
print("Поиск завершен. Найдено {} констант.".format(found_count))
# Запуск основной функции скрипта
find_and_label_crypto_constants()
Анализ crackme: пошаговый разбор защитных механизмов
Типовая структура crackme и точки атаки
А теперь самое мясо. Разберем реальный crackme по косточкам.Современные crackme используют многослойную защиту, включающую проверки лицензии, anti-debugging и контроль целостности. Каждый слой — отдельная головоломка.
Этап 1: Разведка и первичный анализ
Начинаем с разведки. Что за зверь перед нами?
Python:
# pe_recon.py - разведка исполняемого файла
import pefile
import math
from collections import Counter
def calculate_entropy(data):
"""Вычисляет энтропию Шеннона для переданного блока данных."""
if not data:
return 0.0
byte_counts = Counter(data)
data_len = float(len(data))
entropy = 0.0
for count in byte_counts.values():
probability = count / data_len
entropy -= probability * math.log2(probability)
return entropy
def analyze_pe_structure(filename):
"""Проводит базовый анализ PE-файла: архитектура, секции, упаковщики."""
try:
pe = pefile.PE(filename)
except pefile.PEFormatError as e:
print(f"Ошибка: Неверный формат PE-файла. {e}")
return
# Базовая информация
print(f"Архитектура: 0x{pe.FILE_HEADER.Machine:X}") # IMAGE_FILE_MACHINE_AMD64 is 0x8664
print(f"Количество секций: {pe.FILE_HEADER.NumberOfSections}")
# Поиск упаковщиков и анализ энтропии
detect_packers_and_entropy(pe)
def detect_packers_and_entropy(pe):
"""Ищет признаки упаковщика UPX и анализирует энтропию секций."""
packer_detected = False
# Проверка на UPX по именам секций
for section in pe.sections:
# Имена секций хранятся как байты, убираем нулевые символы
if b'UPX' in section.Name.strip(b'\x00'):
print("\n[!] Обнаружен упаковщик UPX.")
packer_detected = True
break
# Анализ энтропии - высокий показатель (>7.2) часто указывает на сжатие или шифрование
print("\nАнализ энтропии секций:")
for section in pe.sections:
entropy = calculate_entropy(section.get_data())
section_name = section.Name.decode(errors='ignore').strip('\x00')
print(f"- Секция '{section_name}': {entropy:.2f}")
if not packer_detected and entropy > 7.2:
print(f" [!] Обнаружена высокая энтропия. Секция может быть упакована или зашифрована.")
# Для запуска скрипта необходимо передать ему имя файла, например:
# analyze_pe_structure('my_crackme.exe')
Этап 2: Поиск ключевых функций проверки
Логика проверки обычно спрятана в 2-3 функциях. Найти их — половина дела.
Python:
# find_validation_functions.py - поиск и анализ функций проверки лицензии в IDA Pro
import idc
import idaapi
import idautils
def mark_patch_point(ea):
"""Добавляет комментарий к инструкции по указанному адресу."""
idc.set_cmt(ea, "Potential patch point", 0)
def find_license_checks():
"""
Ищет функции, которые используют строки, связанные с проверкой лицензии.
"""
print("Начинаю поиск функций валидации...")
license_keywords = [
"invalid", "wrong", "incorrect", "trial", "expired",
"registration", "serial", "key", "license"
]
validation_functions = set()
for s in idautils.Strings():
# [ИСПРАВЛЕНО] Корректно получаем содержимое строки из базы данных IDA.
# get_strlit_contents может вернуть None или байты, поэтому нужна обработка.
string_content = (idc.get_strlit_contents(s.ea) or b'').decode('utf-8', errors='ignore')
for keyword in license_keywords:
if keyword in string_content.lower():
# Ищем перекрестные ссылки (Xrefs) на адрес найденной строки
for xref in idautils.XrefsTo(s.ea):
# Определяем функцию, в которой находится ссылка
func = idaapi.get_func(xref.frm)
if func and func.start_ea not in validation_functions:
func_name = idc.get_func_name(func.start_ea)
print(f"Найдена функция: {func_name} ({hex(func.start_ea)})")
validation_functions.add(func.start_ea)
return validation_functions
def analyze_validation_logic(func_ea):
"""
Анализирует логику функции, ищет условные переходы и отмечает их.
"""
func_name = idc.get_func_name(func_ea)
print(f"\nАнализ функции '{func_name}':")
for ea in idautils.FuncItems(func_ea):
# Используем современный API для декодирования инструкций
insn = ida_ua.insn_t()
ida_ua.decode_insn(insn, ea)
# is_jcond() - более надежный способ проверить, является ли инструкция условным переходом
if insn.is_jcond():
print(f" -> Условный переход в {hex(ea)}: {idc.GetDisasm(ea)}")
# [ИСПРАВЛЕНО] Вызов ранее неопределенной функции
mark_patch_point(ea)
# Пример полного цикла работы скрипта:
# 1. Найти все функции.
# 2. Проанализировать каждую из них.
validation_funcs = find_license_checks()
if validation_funcs:
for func in validation_funcs:
analyze_validation_logic(func)
print("\nАнализ завершен.")
else:
print("Функции для анализа не найдены.")
Этап 3: Обход anti-debugging механизмов
Современные crackme не дремлют. Проверяют на отладчики всеми способами.
Техника | API функция | Метод обхода |
---|---|---|
PEB проверка | IsDebuggerPresent | Патч PEB+0x02 |
Remote debugging | CheckRemoteDebuggerPresent | Hook API |
Timing attack | GetTickCount | Фиксация времени |
Hardware breakpoints | GetThreadContext | Очистка DR регистров |
Memory protection | VirtualProtect | Мониторинг изменений |
JavaScript:
// bypass_antidebug.js - скрипт для Frida для обхода anti-debugging техник
console.log("Запускаем скрипт для обхода anti-debug...");
// --- Перехват API-функций (этот блок был корректен по логике) ---
const isDebuggerPresent = Module.getExportByName('kernel32.dll', 'IsDebuggerPresent');
Interceptor.replace(isDebuggerPresent, new NativeCallback(() => {
console.log("[HOOK] IsDebuggerPresent() -> возвращаем FALSE");
return 0; // Возвращаем 0 (FALSE)
}, 'int', []));
const checkRemoteDebugger = Module.getExportByName('kernel32.dll', 'CheckRemoteDebuggerPresent');
Interceptor.replace(checkRemoteDebugger, new NativeCallback((hProcess, pbDebuggerPresent) => {
console.log("[HOOK] CheckRemoteDebuggerPresent() -> записываем FALSE");
// pbDebuggerPresent - это указатель, куда нужно записать байт результата
pbDebuggerPresent.writeU8(0);
return 1; // Возвращаем 1 (TRUE), означающее успех выполнения функции
}, 'bool', ['pointer', 'pointer']));
// --- Прямой патч PEB (Process Environment Block) ---
// [ИСПРАВЛЕНО] Получаем указатель на PEB через вызов нативной функции.
const RtlGetCurrentPeb = new NativeFunction(
Module.getExportByName('ntdll.dll', 'RtlGetCurrentPeb'),
'pointer', // Возвращаемый тип
[] // Аргументы
);
const peb = RtlGetCurrentPeb();
// Флаг 'BeingDebugged' в структуре PEB находится по смещению +0x2.
const beingDebuggedFlag = peb.add(2);
console.log(`[PATCH] Адрес PEB: ${peb}`);
console.log(`[PATCH] Адрес флага BeingDebugged: ${beingDebuggedFlag}`);
// Обнуляем флаг
beingDebuggedFlag.writeU8(0);
console.log("\nСкрипт активен. Основные проверки на отладку нейтрализованы.");
Работа с упаковщиками и протекторами: VMProtect, Themida, Scylla
Распаковка UPX: классический подход
UPX — это как "Hello World" в мире упаковщиков. Простой, но эффективный.
Bash:
# Автоматическая распаковка
upx -d malware_packed.exe
# Если не работает - ручная распаковка в x64dbg
# 1. Найти OEP (Original Entry Point)
# 2. Поставить breakpoint на PUSHAD/POPAD
# 3. Дампить память после распаковки
Обход VMProtect: trace-анализ и эмуляция
VMProtect — это уже серьезно. Виртуализирует код в собственную VM.Стратегия анализа VMProtect:
- Идентификация VM-входов - поиск характерных паттернов инициализации
- Trace-анализ - запись последовательности выполнения
- Девиртуализация - восстановление оригинального кода
Python:
# vmprotect_detector.py - поиск входов в виртуализированные функции VMProtect
import ida_search
import idc
import ida_kernwin
def find_vmprotect_entries():
"""
Ищет в базе данных IDA характерные для VMProtect байтовые
последовательности, указывающие на возможные точки входа в VM.
"""
# Паттерны для поиска (PUSH imm32; RET и др.)
vm_patterns = [
"68 ?? ?? ?? ?? C3",
"E8 ?? ?? ?? ?? E9 ?? ?? ?? ??", # CALL rel32; JMP rel32
"FF 74 24 ?? C3"
]
found_entries = set() # Используем set для хранения уникальных адресов
print("Начинаю поиск паттернов VMProtect...")
# Получаем начальный адрес для поиска
start_ea = idc.get_inf_attr(idc.INF_MIN_EA)
for pattern in vm_patterns:
ea = start_ea
while True:
# Ищем байтовую последовательность в бинарном коде
ea = ida_search.find_binary(ea, idc.BADADDR, pattern, 16, ida_search.SEARCH_DOWN)
if ea == idc.BADADDR:
break # Паттерн не найден, выходим из цикла
print(f"Найден возможный VM-вход: {hex(ea)}")
found_entries.add(ea)
ea += 1 # Смещаем адрес для продолжения поиска
if not found_entries:
print("Характерные для VMProtect паттерны не найдены.")
else:
print(f"\nПоиск завершен. Найдено {len(found_entries)} уникальных адресов.")
# Перемещаем курсор на первый найденный адрес для удобства
ida_kernwin.jumpto(sorted(list(found_entries))[0])
return list(found_entries)
# ---
# Примечание: Функция `trace_vm_execution` из оригинального кода была удалена,
# так как она содержала неисправимые ошибки в использовании API трассировки IDA.
# Реализация трассировки требует создания специального класса-наследника от
# idaapi.tracer_t и сложной логики взаимодействия с отладчиком.
# ---
# Запускаем поиск
find_vmprotect_entries()
Снятие Themida через Scylla: восстановление Import Table
Themida маскирует импорты под динамическую загрузку. Scylla помогает все расставить по местам.Процедура восстановления Import Table:
- Дамп процесса в момент после распаковки
- Поиск IAT (Import Address Table) в памяти
- Восстановление импортов через Scylla
- Фиксация PE-файла для корректной работы
Исследование малвари: от LockBit 3.0 до мобильных угроз
Анализ ransomware LockBit 3.0: практический кейс
LockBit 3.0 — флагман современного ransomware. Изучаем по полной программе.Настройка безопасной среды анализа:
Установка FlareVM компонентов. Дополнительные утилиты для IDA Pro от FLARE team.
Код:
# flarevm_setup.ps1 - корректная настройка среды анализа малвари
# Установка политики выполнения для возможности запуска скриптов
Set-ExecutionPolicy Unrestricted -Force
# Отключение Microsoft Defender, чтобы он не мешал анализу
Write-Host "Отключаю Microsoft Defender..."
Set-MpPreference -DisableRealtimeMonitoring $true
Set-MpPreference -DisableBehaviorMonitoring $true
# [ИСПРАВЛЕНО] Настройка сетевой изоляции
# Брандмауэр должен быть включен, чтобы правила работали.
Write-Host "Настраиваю сетевую изоляцию..."
netsh advfirewall set allprofiles state on
# Создаем правило, блокирующее ВЕСЬ исходящий трафик
New-NetFirewallRule -DisplayName "Block All Outbound" -Direction Outbound -Action Block -ErrorAction SilentlyContinue
# Установка базовых инструментов для анализа через пакетный менеджер Chocolatey
Write-Host "Устанавливаю инструменты анализа..."
choco install processhacker -y
choco install apimonitor -y
choco install regshot -y
choco install wireshark -y
# Установка и настройка системного мониторинга (Sysmon)
# Требует наличия файлов sysmon.exe и sysmon_config.xml
Write-Host "Устанавливаю Sysmon..."
if (Test-Path .\sysmon.exe -And (Test-Path .\sysmon_config.xml)) {
.\sysmon.exe -accepteula -i sysmon_config.xml
} else {
Write-Warning "Файлы sysmon.exe и/или sysmon_config.xml не найдены. Пропустите этот шаг."
}
Write-Host "Настройка среды завершена."
Статический анализ LockBit 3.0:
Основные компоненты для исследования:
- Криптографические функции - обычно AES + RSA
- Механизмы распространения - SMB, RDP, email
- Техники персистентности - автозагрузка, службы Windows
- Коммуникация с C&C - домены, IP-адреса, протоколы
Python:
# lockbit_crypto_analysis.py - Ghidra скрипт для поиска крипто-компонентов
import struct
def find_and_label_crypto_artifacts():
"""
Ищет криптографические константы (MD5, SHA-256) и таблицы (AES S-Box)
в памяти программы и создает для них метки.
"""
print("--- Начинаю поиск криптографических артефактов ---")
# 1. Поиск числовых констант
crypto_constants = {
"MD5_H0": 0x67452301,
"MD5_H1": 0xEFCDAB89,
"SHA256_H0": 0x6A09E667,
"SHA256_K0": 0x428A2F98
}
# Определяем порядок байт (little-endian/big-endian) для корректного поиска
endian_format = "<I" if currentProgram.getLanguage().isLittleEndian() else ">I"
print("\n[+] Поиск констант MD5/SHA256:")
for name, value in crypto_constants.items():
# Преобразуем число в байтовую последовательность
pattern = struct.pack(endian_format, value)
addr = currentProgram.getMinAddress()
# [ИСПРАВЛЕНО] Корректный цикл для поиска всех вхождений
while addr is not None:
addr = findBytes(addr, pattern, 1)
if addr:
print(f" - Найдена константа '{name}' по адресу: {addr}")
createLabel(addr, name, True)
addr = addr.add(1) # Продолжаем поиск со следующего адреса
# 2. Поиск таблиц AES S-Box по байтовым сигнатурам
aes_patterns = {
"AES_S_BOX": "63 7C 77 7B F2 6B 6F C5",
"AES_INV_S_BOX": "52 09 6A D5 30 36 A5 38"
}
print("\n[+] Поиск таблиц AES S-Box:")
for name, pattern in aes_patterns.items():
addr = currentProgram.getMinAddress()
# [ИСПРАВЛЕНО] Аналогичный цикл для поиска паттернов
while addr is not None:
addr = findBytes(addr, pattern, 1)
if addr:
print(f" - Найдена возможная таблица '{name}' по адресу: {addr}")
createLabel(addr, name, True)
addr = addr.add(1)
print("\n--- Анализ завершен ---")
# Запуск основной функции
find_and_label_crypto_artifacts()
Мобильный реверс: Android APK через jadx и Frida
Мобилки — новый фронтир. Все больше интересного происходит в кармане.Статический анализ через jadx:
Python:
# apk_analyzer.py - автоматизированный анализ Android приложений
import subprocess
import re
import os
import tempfile
import shutil
def analyze_apk_static(apk_path):
"""
Декомпилирует APK с помощью jadx и ищет чувствительную информацию.
"""
if not os.path.isfile(apk_path):
print(f"Ошибка: Файл '{apk_path}' не найден.")
return None
# Используем временную директорию, которая будет автоматически удалена
output_dir = tempfile.mkdtemp(prefix="jadx_out_")
print(f"Декомпиляция APK в: {output_dir}")
try:
# [ИСПРАВЛЕНО] Безопасный вызов jadx с проверкой на ошибки
jadx_cmd = ["jadx", "-d", output_dir, "--show-bad-code", apk_path]
subprocess.run(jadx_cmd, check=True, capture_output=True, text=True)
except (FileNotFoundError, subprocess.CalledProcessError) as e:
print(f"Ошибка выполнения jadx. Убедитесь, что он установлен и доступен в системном PATH.")
shutil.rmtree(output_dir) # Очистка
return None
# [УЛУЧШЕНО] Паттерны для поиска с более точными именами
sensitive_patterns = {
# ВНИМАНИЕ: Этот паттерн может дать много ложных срабатываний
'potential_keys_or_ids': r'[A-Za-z0-9/+=]{32,}',
'urls': r'https?://[^\s<>"{}|\\^`\[\]]+',
'emails': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
'pem_keys': r'-----BEGIN [A-Z ]+ PRIVATE KEY-----'
}
findings = {}
print("Сканирование исходного кода...")
# [ИСПРАВЛЕНО] Добавлен импорт 'os'
for root, _, files in os.walk(output_dir):
for file in files:
if file.endswith('.java'):
scan_java_file(os.path.join(root, file), sensitive_patterns, findings)
# Очищаем временную директорию после анализа
shutil.rmtree(output_dir)
# Преобразуем set в list для удобства
for key, value in findings.items():
findings[key] = list(value)
return findings
def scan_java_file(filepath, patterns, findings):
"""Сканирует один файл и добавляет уникальные находки в словарь."""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
for pattern_name, regex in patterns.items():
# Используем set для хранения только уникальных результатов
matches = set(re.findall(regex, content))
if matches:
if pattern_name not in findings:
findings[pattern_name] = set()
findings[pattern_name].update(matches)
except Exception as e:
print(f"Не удалось прочитать файл {filepath}: {e}")
# Пример использования:
# if __name__ == '__main__':
# results = analyze_apk_static('path/to/your.apk')
# if results:
# import json
# print(json.dumps(results, indent=2))
Динамический анализ через Frida:
JavaScript:
// android_hooks.js - перехват ключевых вызовов Android API с помощью Frida
Java.perform(function() {
console.log("[+] Frida-скрипт активен и подключен к приложению.");
// --- Перехват SharedPreferences ---
try {
const Editor = Java.use("android.content.SharedPreferences$Editor");
// [УЛУЧШЕНО] Перехватываем сразу несколько методов для полноты картины
Editor.putString.implementation = function(key, value) {
console.log(`[SharedPreferences] Сохранение строки: key='${key}', value='${value}'`);
return this.putString.call(this, key, value);
};
Editor.putInt.implementation = function(key, value) {
console.log(`[SharedPreferences] Сохранение числа: key='${key}', value='${value}'`);
return this.putInt.call(this, key, value);
};
} catch (e) {
console.error("[!] Не удалось перехватить SharedPreferences$Editor.");
}
// --- Перехват криптографических функций ---
try {
const Cipher = Java.use("javax.crypto.Cipher");
Cipher.doFinal.overload('[B').implementation = function(input) {
console.log("[Crypto] doFinal() вызван с данными:", bytesToHex(input));
// [ИСПРАВЛЕНО] Вызываем оригинальный метод через .call(this, ...),
// чтобы избежать бесконечной рекурсии.
const result = this.doFinal.call(this, input);
console.log("[Crypto] Результат doFinal():", bytesToHex(result));
return result;
};
} catch (e) {
console.error("[!] Не удалось перехватить javax.crypto.Cipher.");
}
// --- Перехват сетевых соединений ---
try {
const URL = Java.use("java.net.URL");
URL.$init.overload('java.lang.String').implementation = function(url) {
console.log(`[Network] Создание URL для адреса: ${url}`);
// Для конструкторов ($init) оригинальный метод также вызывается через .call
return this.$init.call(this, url);
};
} catch (e) {
console.error("[!] Не удалось перехватить java.net.URL.");
}
// Вспомогательная функция для конвертации байтов в HEX-строку
function bytesToHex(data) {
if (!data) return null;
// Явное приведение к Java-массиву для надежности
const javaBytes = Java.array('byte', data);
let result = "";
for (let i = 0; i < javaBytes.length; i++) {
// Приводим байт к беззнаковому числу и конвертируем в hex
let hex = (javaBytes[i] & 0xff).toString(16);
if (hex.length === 1) hex = '0' + hex;
result += hex;
}
return result;
}
});
Автоматизация реверса: IDAPython, Ghidra API, Binary Ninja
IDAPython для массовых операций
Ручной анализ — это прошлый век. Автоматизация — наше все.
Python:
# ida_mass_rename.py - автоматическое переименование функций в IDA Pro
import idc
import idaapi
import idautils
def get_api_calls_in_function(func_ea):
"""
[РЕАЛИЗОВАНО] Находит и возвращает список всех вызовов
внешних API в указанной функции.
"""
api_calls = set()
# Получаем начальный и конечный адреса функции
start_ea = idc.get_func_attr(func_ea, idc.FUNCATTR_START)
end_ea = idc.get_func_attr(func_ea, idc.FUNCATTR_END)
# Итерируемся по всем инструкциям в функции
for head in idautils.Heads(start_ea, end_ea):
# Проверяем, является ли инструкция вызовом (call)
if idaapi.is_call_insn(head):
# Получаем адрес, на который происходит вызов
op_addr = idc.get_operand_value(head, 0)
# Проверяем, что это вызов импортируемой функции (сегмент .idata или extrn)
if idc.get_segm_name(op_addr) in ['.idata', 'extrn']:
api_name = idc.get_name(op_addr)
# Убираем префикс '__imp_', который IDA часто добавляет
if api_name.startswith('__imp_'):
api_name = api_name[6:]
api_calls.add(api_name)
return list(api_calls)
def suggest_name_by_apis(api_calls, api_signatures):
"""
[РЕАЛИЗОВАНО] Предлагает префикс для имени функции на основе
найденных API-вызовов и словаря сигнатур.
"""
# Возвращаем префикс для первого же найденного значимого API
for call in api_calls:
if call in api_signatures:
return api_signatures[call]
return None
def run_mass_rename():
"""
Основная функция, запускающая все этапы переименования.
"""
print("[+] Запускаю скрипт массового переименования...")
# --- Этап 1: Переименование по статическому словарю (по известным именам) ---
static_rename_patterns = {
'sub_401000': 'aes_encrypt_wrapper',
'sub_402000': 'network_send_data'
}
print("\n--- Этап 1: Переименование по статическим именам ---")
for old_name, new_name in static_rename_patterns.items():
ea = idc.get_name_ea_simple(old_name)
if ea != idaapi.BADADDR:
if idc.set_name(ea, new_name, idc.SN_NOWARN):
print(f" - Переименовано: {old_name} -> {new_name}")
# --- Этап 2: Автоматическое переименование на основе API-вызовов ---
api_rename_signatures = {
'CreateFileW': 'file_create',
'WriteFile': 'file_write',
'CryptEncrypt': 'crypto_encrypt',
'InternetOpenW': 'net_init',
'HttpSendRequestW': 'net_http_send'
}
print("\n--- Этап 2: Автоматическое переименование по API ---")
renamed_count = 0
for func_ea in idautils.Functions():
func_name = idc.get_func_name(func_ea)
# Пропускаем функции, которые уже были переименованы
if not func_name.startswith('sub_'):
continue
api_calls = get_api_calls_in_function(func_ea)
suggested_prefix = suggest_name_by_apis(api_calls, api_rename_signatures)
if suggested_prefix:
# Новое имя будет в формате: prefix_ADDRESS
new_name = f"{suggested_prefix}_{func_ea:X}"
if idc.set_name(func_ea, new_name, idc.SN_NOWARN):
print(f" - Авто-переименование: {func_name} -> {new_name}")
renamed_count += 1
print(f"\n[+] Скрипт завершен. Автоматически переименовано {renamed_count} функций.")
# Запуск
run_mass_rename()
Ghidra headless анализ для CI/CD
Ghidra умеет работать без GUI. Идеально для пайплайнов. Для более глубокого понимания Ghidra API и его возможностей.
Python:
# ghidra_headless_analyzer.py - Анализ ОДНОГО файла для пакетной обработки
#
# ПРИМЕР ЗАПУСКА В КОМАНДНОЙ СТРОКЕ:
# analyzeHeadless /path/to/project MyProject -import /path/to/samples -process -scriptPath /path/to/scripts -postScript ghidra_headless_analyzer.py "/path/to/reports"
#
import os
import json
from ghidra.app.script import GhidraScript
from ghidra.program.model.data import StringDataType
class SingleSampleAnalyzer(GhidraScript):
def run(self):
"""
Анализирует 'currentProgram' и сохраняет JSON-отчет.
Скрипт выполняется для одного файла за раз.
"""
args = getScriptArgs()
if not args:
print("ОШИБКА: Укажите путь к папке для отчетов как аргумент скрипта.")
return
output_dir = args[0]
if not os.path.isdir(output_dir):
os.makedirs(output_dir)
print(f"Анализ файла: {currentProgram.getName()}")
stats = self.analyze_current_program()
# Сохраняем отчет с уникальным именем файла
report_filename = f"{currentProgram.getName()}_{currentProgram.getExecutableMD5()}.json"
report_path = os.path.join(output_dir, report_filename)
self.save_report(stats, report_path)
def analyze_current_program(self):
"""Собирает статистику по currentProgram."""
stats = {
'filename': currentProgram.getName(),
'md5': currentProgram.getExecutableMD5(),
'functions_count': currentProgram.getFunctionManager().getFunctionCount(),
'strings_count': self.count_strings(),
'suspicious_apis': self.find_suspicious_apis()
}
return stats
def count_strings(self):
"""[ИСПРАВЛЕНО] Корректный подсчет строк в программе."""
string_count = 0
data_manager = currentProgram.getDataManager()
data_iterator = data_manager.getData(True)
for data in data_iterator:
if isinstance(data.getDataType(), StringDataType):
string_count += 1
return string_count
def find_suspicious_apis(self):
"""[ИСПРАВЛЕНО] Корректный поиск подозрительных импортируемых API."""
suspicious_list = [
'CreateRemoteThread', 'WriteProcessMemory', 'VirtualAllocEx',
'SetWindowsHookExW', 'CryptEncrypt', 'InternetOpenUrlW', 'RegSetValueExW'
]
found_apis = set()
symbol_table = currentProgram.getSymbolTable()
for api_name in suspicious_list:
# Проверяем, есть ли в итераторе хотя бы один символ
if symbol_table.getSymbols(api_name).hasNext():
found_apis.add(api_name)
return list(found_apis)
def save_report(self, data, output_path):
"""[РЕАЛИЗОВАНО] Сохраняет данные в JSON-файл."""
print(f"Сохранение отчета в: {output_path}")
try:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
except IOError as e:
print(f"Не удалось сохранить отчет: {e}")
# Ghidra автоматически создает экземпляр класса и вызывает run()
Часто задаваемые вопросы
Как начать реверс-инжиниринг малвари в 2025 году?Начни с основ x86/x64 и ассемблера. Поставь Ghidra (бесплатно) и FlareVM. Качай crackme с crackmes.one и reversing.kr. Python для автоматизации — обязательно.
Какие инструменты используются для анализа crackme?
Базовый стек: Ghidra 11.1 или IDA Pro 8.4 для статики, x64dbg для динамики, DIE для упаковщиков. Плюс Process Monitor, API Monitor, хекс-редакторы.
В чем отличия статического и динамического анализа вредоносного кода?
Статика изучает код без запуска — дизассемблирование, декомпиляция, поиск строк. Динамика смотрит на поведение — API вызовы, сеть, файлы. Лучше всего работают вместе.
Какие техники применяются для обхода VMProtect и Themida?
VMProtect: trace-анализ, поиск VM-входов, девиртуализация через эмуляцию. Themida: дамп после распаковки, восстановление Import Table через Scylla, патчинг anti-debugging.
Как написать IDAPython скрипт для автоматизации реверса?
Изучи API: idc для базовых операций, idaapi для продвинутых, idautils для итераций. Начни с простого — переименование функций, поиск строк, комментарии.
Какие навыки реверс-инжиниринга повышают заработок багхантера?
Мобильный анализ (Android/iOS), IoT прошивки, поиск hardcoded credentials, анализ протоколов. Особенно ценится автоматизация и создание PoC эксплойтов.
Практический пример: анализ LockBit 3.0 практические кейсы
Описание решения
Давай по порядку разберем реальный кейс.Язык и окружение:
- Язык: Python 3.9+ с библиотеками для анализа PE
- Необходимые библиотеки: pefile 2023.2.7, yara-python 4.3.1, requests 2.31.0
- Требования к системе: Windows 10/11, 8GB RAM, изолированная VM
Комплексный анализ включает статическое исследование PE-структуры, динамический мониторинг поведения, извлечение IOC и создание Yara-правил для детектирования.
Пошаговая реализация:
Python:
# lockbit_analyzer.py - комплексный анализ LockBit 3.0
import pefile
import yara
import hashlib
import json
import os
from datetime import datetime
class LockBitAnalyzer:
def __init__(self, sample_path):
if not os.path.exists(sample_path):
raise FileNotFoundError(f"Файл не найден: {sample_path}")
self.sample_path = sample_path
self.pe = None
self.results = {
'timestamp': datetime.now().isoformat(),
'file_info': {},
'static_analysis': {},
'iocs': {'imports': [], 'hashes': {}},
'yara_rule': ""
}
def analyze(self):
"""Запускает полный цикл анализа образца."""
print(f"Анализ образца: {self.sample_path}")
try:
self.pe = pefile.PE(self.sample_path)
except pefile.PEFormatError as e:
print(f"Ошибка: Не является валидным PE-файлом. {e}")
return None
self.static_analysis()
self.extract_iocs()
self.generate_yara_rule()
print("Анализ завершен.")
return self.results
def static_analysis(self):
"""[РЕАЛИЗОВАНО] Выполняет статический анализ PE-файла."""
self.results['file_info'] = {
'path': self.sample_path,
'size': os.path.getsize(self.sample_path),
'compile_time': datetime.fromtimestamp(self.pe.FILE_HEADER.TimeDateStamp).isoformat(),
'machine': pefile.MACHINE_TYPE.get(self.pe.FILE_HEADER.Machine, 'unknown')
}
# Поиск подозрительных секций (высокая энтропия -> упаковка/шифрование)
sections = []
for section in self.pe.sections:
sections.append({
'name': section.Name.decode(errors='ignore').strip('\x00'),
'virtual_address': hex(section.VirtualAddress),
'size': section.SizeOfRawData,
'entropy': section.get_entropy()
})
self.results['static_analysis']['sections'] = sections
def extract_iocs(self):
"""[РЕАЛИЗОВАНО] Извлекает индикаторы компрометации (IOCs)."""
# Хэши файла
with open(self.sample_path, 'rb') as f:
data = f.read()
self.results['iocs']['hashes']['md5'] = hashlib.md5(data).hexdigest()
self.results['iocs']['hashes']['sha256'] = hashlib.sha256(data).hexdigest()
# Подозрительные импорты
suspicious_imports = {'CryptEncrypt', 'CreateRemoteThread', 'WriteProcessMemory', 'VirtualAllocEx'}
if hasattr(self.pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in self.pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
if imp.name and imp.name.decode(errors='ignore') in suspicious_imports:
self.results['iocs']['imports'].append(f"{entry.dll.decode()}:{imp.name.decode()}")
def generate_yara_rule(self):
"""[РЕАЛИЗОВАНО] Генерирует простое YARA-правило на основе хэша секции .text."""
sha256_hash = self.results['iocs']['hashes']['sha256']
text_section_hash = ""
for section in self.pe.sections:
if b'.text' in section.Name:
text_section_hash = hashlib.md5(section.get_data()).hexdigest()
break
rule = f"""
rule LockBit_Sample_{sha256_hash[:8]}
{{
meta:
description = "Detects a specific LockBit 3.0 sample"
author = "Automated Analyzer"
hash = "{sha256_hash}"
strings:
// Пример уникальной строки (заменить на реальную при анализе)
// $unique_string = "some_unique_string_from_binary"
condition:
uint16(0) == 0x5A4D and // MZ header
filesize < 2MB and
pe.section_count > 3 and
pe.sections[0].hash.md5() == "{text_section_hash}"
}}
"""
self.results['yara_rule'] = rule.strip()
# Пример использования:
# if __name__ == '__main__':
# analyzer = LockBitAnalyzer('path/to/your/lockbit_sample.exe')
# report = analyzer.analyze()
# if report:
# print(json.dumps(report, indent=4))
Python:
import pefile
import os
import hashlib
import math
import re
from datetime import datetime
# Вспомогательная функция для расчета энтропии (может быть частью класса)
def calculate_entropy(data):
"""Вычисляет энтропию Шеннона для блока данных."""
if not data:
return 0.0
entropy = 0
# Используем счетчик для эффективности
from collections import Counter
byte_counts = Counter(data)
data_len = float(len(data))
for count in byte_counts.values():
p_x = count / data_len
entropy -= p_x * math.log2(p_x)
return entropy
class MalwareAnalyzer:
# Предполагается, что __init__ и другие части класса существуют
def __init__(self, sample_path):
self.sample_path = sample_path
self.results = {'file_info': {}, 'static_analysis': {}}
# [РЕАЛИЗОВАНО] Недостающие методы
def calculate_hash(self, algo='sha256'):
h = hashlib.new(algo)
with open(self.sample_path, 'rb') as f:
while chunk := f.read(8192):
h.update(chunk)
return h.hexdigest()
def extract_strings(self, min_len=8):
"""Извлекает ASCII строки из файла."""
strings = []
with open(self.sample_path, 'rb') as f:
data = f.read()
# Ищем последовательности печатаемых ASCII символов
pattern = b"([%s]{%d,})" % (b"\\x20-\\x7e", min_len)
for match in re.finditer(pattern, data):
strings.append(match.group(0).decode(errors='ignore'))
self.results['static_analysis']['strings'] = strings
# [ИСПРАВЛЕНО] Основные методы анализа
def static_analysis(self):
try:
pe = pefile.PE(self.sample_path)
self.results['file_info'] = {
'size': os.path.getsize(self.sample_path),
'md5': self.calculate_hash('md5'),
'sha256': self.calculate_hash('sha256'),
'architecture': 'x64' if pe.FILE_HEADER.Machine == 0x8664 else 'x86',
'compilation_time': datetime.fromtimestamp(pe.FILE_HEADER.TimeDateStamp).isoformat()
}
sections = []
for section in pe.sections:
sections.append({
'name': section.Name.decode(errors='ignore').rstrip('\x00'),
'virtual_size': section.Misc_VirtualSize,
'raw_size': section.SizeOfRawData,
'entropy': calculate_entropy(section.get_data())
})
self.results['static_analysis']['sections'] = sections
self.analyze_imports(pe)
self.extract_strings()
except pefile.PEFormatError as e:
print(f"Ошибка: Не является PE-файлом. {e}")
except Exception as e:
print(f"Ошибка статического анализа: {e}")
def analyze_imports(self, pe):
suspicious_apis = {
'CryptEncrypt', 'CryptDecrypt', 'CreateRemoteThread',
'WriteProcessMemory', 'SetWindowsHookExW', 'RegSetValueExW'
}
found_suspicious = []
# [ИСПРАВЛЕНО] Проверяем, существует ли таблица импортов
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll_name = entry.dll.decode(errors='ignore')
for imp in entry.imports:
if imp.name and imp.name.decode(errors='ignore') in suspicious_apis:
found_suspicious.append({
'dll': dll_name,
'api': imp.name.decode(errors='ignore'),
'address': hex(imp.address)
})
self.results['static_analysis']['suspicious_apis'] = found_suspicious
Python:
# Предполагается, что эти методы являются частью класса-анализатора,
# а модули re и datetime импортированы на уровне всего файла.
def extract_iocs(self):
"""[ИСПРАВЛЕНО] Корректно извлекает IOCs из бинарного файла."""
print("Извлечение IOCs (IP, домены, BTC)...")
try:
with open(self.sample_path, 'rb') as f:
data = f.read()
# [ИСПРАВЛЕНО] Поиск ведется по сырым байтам (b'...'), а не по искаженному тексту.
ip_pattern = rb'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b'
# Найденные байты декодируются индивидуально.
ips = [ip.decode('ascii') for ip in re.findall(ip_pattern, data)]
domain_pattern = rb'[a-zA-Z0-9][a-zA-Z0-9-]{1,61}\.?[a-zA-Z0-9-]+\.[a-zA-Z]{2,}'
domains = [d.decode('ascii') for d in re.findall(domain_pattern, data)]
btc_pattern = rb'\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b'
btc_addresses = [btc.decode('ascii') for btc in re.findall(btc_pattern, data)]
# [ИСПРАВЛЕНО] Обновляем словарь, а не перезаписываем его.
self.results['iocs'].update({
'ip_addresses': sorted(list(set(ips))),
'domains': sorted(list(set(domains))),
'bitcoin_addresses': sorted(list(set(btc_addresses)))
})
except Exception as e:
print(f"Ошибка при извлечении IOCs: {e}")
def generate_yara_rule(self):
"""[УЛУЧШЕНО] Генерирует YARA-правило, динамически используя найденные IOCs."""
print("Генерация YARA-правила...")
sha256_hash = self.results.get('iocs', {}).get('hashes', {}).get('sha256', 'N/A')
# Динамически формируем секцию strings
string_conditions = [
'$api1 = "CryptEncrypt" wide ascii',
'$api2 = "CreateRemoteThread" wide ascii'
]
# Добавляем первый найденный домен в правило для специфичности
found_domains = self.results.get('iocs', {}).get('domains', [])
if found_domains:
# Экранируем обратные слэши для YARA
safe_domain = found_domains[0].replace('\\', '\\\\')
string_conditions.append(f'$domain1 = "{safe_domain}" ascii')
# Формируем правило
strings_section = "\n ".join(string_conditions)
condition_section = "uint16(0) == 0x5A4D and 1 of ($api*) and any of ($domain*)" if found_domains else "uint16(0) == 0x5A4D and 2 of ($api*)"
rule_template = f"""
rule Sample_{sha256_hash[:8]}
{{
meta:
description = "Detects a specific sample based on automated analysis"
author = "Automated Analyzer"
date = "{datetime.now().strftime('%Y-%m-%d')}"
hash = "{sha256_hash}"
strings:
{strings_section}
condition:
{condition_section}
}}
"""
self.results['yara_rule'] = rule_template.strip()
Оптимизации и best practices:
- Используй изолированную VM для анализа малвари
- Всегда создавай снапшоты перед запуском образцов
- Логируй все действия для последующего анализа
- Интегрируй с threat intelligence платформами
Полный JSON отчет с метаданными файла, списком подозрительных API, извлеченными IOC и готовыми Yara правилами для детектирования семейства LockBit 3.0.
Решение типовых проблем
Проблема | Симптомы | Решение | Профилактика |
---|---|---|---|
Ghidra не запускается | Ошибка JVM, белый экран | Обновить Java до 17+, очистить кэш | Регулярные обновления JDK |
IDA Pro недоступна в РФ | Блокировка покупки | Использовать Ghidra или Binary Ninja | Планировать покупки заранее |
Упакованный файл не анализируется | Высокая энтропия, нет строк | Распаковать через UPX/manual unpacking | Проверять упаковщики через DIE |
Anti-debugging блокирует анализ | Краш при отладке | Патчить проверки, использовать ScyllaHide | Настроить плагины обхода |
Ghidra скрипты не работают | Ошибки Python/Java | Проверить API версию, обновить скрипты | Использовать актуальные примеры |
Малварь не запускается в VM | Детекция виртуализации | Настроить evasion, изменить VM параметры | Использовать bare metal для анализа |
Нет доступа к зарубежным ресурсам | Блокировка сайтов, сервисов | VPN, зеркала, российские аналоги | Подготовить список альтернатив |
Проблемы — часть процесса. Главное знать, где искать решения.
Ресурсы для углубления
Русскоязычные:
- Xakep Magazine - актуальные статьи по реверс-инжинирингу и анализу малвари
- Habr.com/ru/hub/infosecurity - техническое сообщество с практическими кейсами
- SecurityLab.ru - новости ИБ и методические материалы
- Введение в реверс-инжиниринг - структурированный подход к освоению базовых концепций реверса для начинающих
- Практический реверс на Windows - углубленное изучение работы с PE-файлами, отладчиками и анализом малвари
Инструменты доступные в РФ:
- Ghidra 11.1 - бесплатный дизассемблер от NSA, основная альтернатива IDA Pro
- x64dbg - open-source отладчик для Windows x86/x64
- Binary Ninja - современный дизассемблер с API для автоматизации
- FlareVM - дистрибутив для анализа малвари от FireEye/Mandiant
Российские специалисты имеют доступ к мощным open-source решениям. При правильном использовании они не уступают коммерческим аналогам.
Ключ к успеху — постоянная практика на реальных образцах и активное участие в профессиональном сообществе. Начинай с простых crackme, переходи к малвари, автоматизируй рутину.
Время действовать. Твоя карьера в реверс-инжиниринге начинается сегодня.