
Приветствую, дорогой читатель. Хочу представить вашему вниманию пример, как можно упростить себе жизнь при исследовании кода программ, используя скриптинг в Ghidra.
Если вы уже имели опыт работы с дизассемблером, то заметили, что читать его вывод не так легко, если целью является понять более высокие абстракции, заложенные в нём. Возможно, вы даже пытались декомпилировать его в псевдокод, но работать с переменными типа local_1-999 – то ещё удовольствие. Да, можно щёлкнуть на каждую из них и присвоить имя на основе логики. А что, если у вас 2000 строк и более?
Чтобы не натереть мозоль, давайте разберёмся, как написать скрипт, который сделает большую часть работы за нас.
Все манипуляции были проделаны на версии 11.1.2. Чтобы попасть в список доступных скриптов, откройте меню Window → Script Manager и там же создайте новый скрипт, нажав в правом верхнем углу кнопку Create New Script и выбрав язык Python.
Важное ограничение: Ghidra использует внутреннюю реализацию языка Python версии 2.7.
Пишем код.
Первым делом, нужно объявить кодировку, чтобы не получить кучу ошибок о наличии не-ASCI символов
# -*- coding: utf-8 -*-
Указываем, что исходный файл сохранён в кодировке UTF-8 (поддержка Unicode символов).
from ghidra.util.task import Task
Импортируем класс Task из модуля Ghidra, который позволяет создавать задачи для выполнения операций в фоне.
from ghidra.app.decompiler import DecompInterface
Импортируем интерфейс декомпилятора, позволяющий получать C-подобный код из бинарных данных.
from ghidra.util.task import ConsoleTaskMonitor
Импортируем класс ConsoleTaskMonitor для отслеживания прогресса выполнения задач с выводом в консоль.
from ghidra.program.model.symbol import SourceType,
SymbolType
Импортируем типы источников и типов символов, используемые для обозначения происхождения имен (например, USER_DEFINED) и вида символа (функция, переменная, метка).
from ghidra.program.model.pcode import HighFunctionDBUtil
Импортируем утилиты для работы с высокоуровневым представлением функции (HighFunction) в базе данных Ghidra.
from ghidra.program.model.pcode.HighFunctionDBUtil import ReturnCommitOption
Импортируем опцию фиксации (commit) изменений в базе данных при обновлении параметров функции.
from java.awt import BorderLayout
Импортируем менеджер компоновки BorderLayout для организации компонентов в окне Java.
from javax.swing import JButton,
JFrame,
JTextArea,
JScrollPane,
JPanel
Импортируем стандартные Swing-компоненты: кнопку, окно, текстовую область, панель прокрутки и панель для построения GUI.
import re
Импортируем модуль регулярных выражений для поиска и обработки строк.
class RenameDialog(JFrame):
Объявляем класс RenameDialog, наследующийся от JFrame. Он представляет окно диалога для ввода новых имён.
def __init__(self, suggestions):
Конструктор класса, принимающий список предложенных имен (suggestions) для переименования.
JFrame.__init__(self, "Advanced Renamer")
Инициализируем базовый класс JFrame, задавая заголовок окна “Advanced Renamer”.
self.setSize(800, 600)
Устанавливаем размер окна – 800 пикселей по ширине и 600 по высоте.
self.setLayout(BorderLayout())
Задаём менеджер компоновки BorderLayout для организации компонентов внутри окна.
self.text_area = JTextArea()
Создаём текстовую область, в которой пользователь сможет редактировать имена.
self.text_area.setText("# Format: old=new\n" + "\n".join(suggestions))
Заполняем текстовую область начальным текстом с примером формата и списком предложенных имён, разделённых переносами строк.
scroll_pane = JScrollPane(self.text_area)
Создаём панель прокрутки, содержащую нашу текстовую область, чтобы можно было просматривать длинный текст.
self.add(scroll_pane, BorderLayout.CENTER)
Добавляем панель прокрутки в центр окна (согласно BorderLayout).
button_panel = JPanel()
Создаём панель для размещения кнопок в окне.
self.apply_btn = JButton("Apply", actionPerformed=lambda _: self.setVisible(False))
Создаём кнопку “Apply” с обработчиком события: при нажатии окно будет скрыто (setVisible(False)).
button_panel.add(self.apply_btn)
Добавляем кнопку “Apply” на панель кнопок.
self.add(button_panel, BorderLayout.SOUTH)
Размещаем панель кнопок в нижней части окна.
class AdvancedRenamer(Task):
Объявляем класс AdvancedRenamer, наследующийся от Task. Он отвечает за логику переименования символов в Ghidra.
def __init__(self, program, function):
Конструктор класса принимает объект программы (program) и функцию (function), над которой будет производиться переименование.
super(AdvancedRenamer, self).__init__("Advanced Renamer", True, False, True)
Вызываем конструктор базового класса Task, задавая имя задачи и некоторые флаги (например, показывать прогресс, отменяемость и т.д.).
self.program = program
Сохраняем ссылку на текущую программу Ghidra.
self.function = function
Сохраняем ссылку на функцию, которую собираемся анализировать и переименовывать.
self.monitor = ConsoleTaskMonitor()
Создаем экземпляр монитора задач, который выводит статус выполнения в консоль.
self.skipped = {'int', 'char', 'void', 'return', 'break', 'float'}
Определяем множество ключевых слов, которые не будут изменяться при переименовании (например, базовые типы и управляющие конструкции).
def find_and_rename(self, old_name, new_name):
Определяем метод для поиска символа с именем old_name и его переименования в new_name.
decompiler = DecompInterface()
Создаём экземпляр интерфейса декомпилятора.
decompiler.openProgram(self.program)
Открываем текущую программу в декомпиляторе для дальнейшей работы.
results = decompiler.decompileFunction(self.function, 60, self.monitor)
Декомпилируем функцию с таймаутом 60 секунд, используя монитор для отслеживания прогресса.
if results.decompileCompleted():
Проверяем, успешно ли завершилась декомпиляция.
hfunction = results.getHighFunction()
Получаем высокоуровневое представление функции (HighFunction) из результатов декомпиляции.
signatureSrcType = self.function.getSignatureSource()
Получаем тип источника сигнатуры функции для дальнейшего обновления базы данных.
HighFunctionDBUtil.commitParamsToDatabase(hfunction,
True,
ReturnCommitOption.COMMIT,
signatureSrcType)
Фиксируем изменения параметров функции в базе данных, используя указанные опции. Это необходимо для того, чтобы локальные имена, такие как lVar1, uVar2, pVar3, были согласованы с базой данных, потому, что они генерируются самим декомпилятором и просто выводятся на экран, без коммита в базу.
if old_name.startswith("FUN_"):
Если имя символа начинается с “FUN_”, считаем его именем функции.
return self.rename_function(old_name, new_name)
Вызываем метод для переименования функции.
elif old_name.startswith(("DAT_", "PTR_", "UNK_", "LAB_")):
Если имя начинается с “DAT_”, “PTR_”, “UNK_” или “LAB_”, обрабатываем его как метку или данные. Покаяние: У меня так и не получилось дать ума PTR_ и UNK_, хоть это и глобальные имена, как DAT_ и LAB_, они не переименовываются данной функцией, но я оставил их как есть :з
return self.rename_label(old_name, new_name)
Вызываем метод для переименования метки.
elif old_name.startswith(("local_", "param_", "uVar", "lVar")):
Если имя соответствует шаблону локальной переменной или параметра, то…
return self.rename_local_variable(old_name, new_name)
… вызываем метод для переименования локальной переменной.
else:
Если ни одно из условий не выполнено, по умолчанию обрабатываем как локальную переменную.
return self.rename_local_variable(old_name, new_name)
Вызываем метод для переименования локальной переменной.
def rename_function(self, old_name, new_name):
Определяем метод, который переименовывает функцию с именем old_name в new_name.
try:
Начинаем блок обработки исключений, чтобы избежать сбоев при ошибках.
addr_str = old_name[4:] if old_name.startswith("FUN_")
else old_name
Если имя начинается с “FUN_”, удаляем этот префикс, чтобы получить строку адреса функции.
addr = toAddr("0x{}".format(addr_str))
Преобразуем строку с адресом в объект адреса Ghidra, добавляя префикс “0x”.
func = getFunctionAt(addr)
Получаем объект функции, расположенной по данному адресу.
if func and func.getName() == old_name:
Если функция найдена и её имя соответствует old_name, то…
func.setName(new_name, SourceType.USER_DEFINED)
… устанавливаем новое имя для функции, указывая, что оно задано пользователем (USER_DEFINED).
except: pass
Если возникает ошибка (например, неверный адрес), просто игнорируем её.
def rename_label(self, old_name, new_name):
Определяем метод для переименования метки или символа, представляющего данные.
try:
Начинаем блок обработки исключений.
addr_str = old_name[4:] if old_name.startswith(("LAB_",
"DAT_",
"PTR_",
"UNK_"))
else old_name
Извлекаем адрес метки, убирая префикс (например, “LAB_”) если он присутствует.
addr = toAddr(addr_str)
Преобразуем строку адреса в объект адреса Ghidra.
for sym in self.program.getSymbolTable().getSymbols(addr):
Перебираем все символы, зарегистрированные по этому адресу, из таблицы символов программы.
if sym.getName() == old_name:
Если имя символа совпадает с old_name, то…
sym.setName(new_name, SourceType.USER_DEFINED)
… устанавливаем новое имя для символа с типом источника USER_DEFINED.
return True
Возвращаем True, указывая, что переименование прошло успешно.
except: pass
При возникновении исключения игнорируем его.
def rename_local_variable(self, old_name, new_name):
Определяем метод для переименования локальной переменной или параметра.
try:
Начинаем блок обработки исключений.
if new_name.lower() in self.skipped:
Если новое имя (в нижнем регистре) содержится в списке ключевых слов для пропуска, то…
return False
… прекращаем переименование, возвращая False.
# Далее идет блок, необходимый для обработки параметров
for param in self.function.getParameters():
Перебираем все параметры текущей функции.
if param.getName() == old_name:
Если имя параметра совпадает с old_name, то…
param.setName(new_name, SourceType.USER_DEFINED)
… устанавливаем новое имя для параметра.
# Local variables (даже если наш пациент был найдет среди параметров
# необходимо пройтись и по локальным переменным,
# иначе они не переименовываются, возможно надо тут просто
# сделать коммит в базу)
decompiler = DecompInterface()
Создаём новый экземпляр декомпилятора для обработки локальных переменных.
decompiler.openProgram(self.program)
Открываем программу в декомпиляторе.
results = decompiler.decompileFunction(self.function,
60,
self.monitor)
Декомпилируем функцию с таймаутом 60 секунд.
if results.decompileCompleted():
Если декомпиляция прошла успешно, то…
hfunction = results.getHighFunction()
… получаем высокоуровневое представление функции.
syms = hfunction.getLocalSymbolMap().getSymbols()
Получаем список локальных символов (переменных) из высокоуровневой функции.
for sym in syms :
Перебираем каждый локальный символ.
if sym.getName() == old_name and sym.getName() != new_name:
Если имя символа совпадает с old_name и ещё не равно new_name, то…
HighFunctionDBUtil.updateDBVariable(sym,
new_name,
None,
SourceType
.USER_DEFINED)
… обновляем имя переменной в базе данных с новым именем и источником USER_DEFINED.
return True
Возвращаем True после успешного переименования локальной переменной.
return False
Если ни один из блоков не сработал, возвращаем False – переименование не выполнено.
except Exception as e:
Ловим исключения и сохраняем их в переменной e для отладки.
print("Error: {} -> {} ({})".format(old_name, new_name, str(e)))
Выводим сообщение об ошибке с указанием старого и нового имени, а также текста ошибки. Обратите внимание на форматирование строки, т.к. это Python 2.7.
return False
Возвращаем False, сигнализируя, что произошла ошибка при переименовании.
def run(self):
Определяем метод run, который является точкой входа при выполнении задачи AdvancedRenamer.
decompiler = DecompInterface()
Создаем экземпляр декомпилятора для работы с функцией.
decompiler.openProgram(self.program)
Открываем программу в декомпиляторе.
results = decompiler.decompileFunction(self.function, 60, self.monitor)
Декомпилируем функцию с таймаутом 60 секунд.
if not results.decompileCompleted():
Если декомпиляция не завершилась успешно, то…
print("Decompilation failed!")
… выводим сообщение об ошибке декомпиляции.
return
Прерываем выполнение метода run (задача не может продолжаться без декомпиляции).
code = results.getDecompiledFunction().getC()
Получаем декомпилированный C-подобный код функции в виде строки.
entities = re.findall(r'\b([A-Za-z_][A-Za-z0-9_]*)\b', code)
С помощью регулярного выражения находим все идентификаторы (слова, начинающиеся с буквы или подчёркивания) в коде.
filtered_names = [n for n in set(entities) if n not in self.skipped]
Создаём множество уникальных идентификаторов и исключаем те, что присутствуют в self.skipped (ключевые слова).
dialog = RenameDialog(filtered_names)
Создаем диалоговое окно RenameDialog, передавая список найденных имён для потенциального переименования.
dialog.setLocationRelativeTo(None)
Устанавливаем расположение диалога по центру экрана (None означает центр относительно родительского окна).
dialog.setVisible(True)
Делаем диалог видимым – ожидаем, пока пользователь внесёт изменения и нажмёт “Apply”.
while dialog.isVisible(): pass
Активно ждём, пока окно не будет закрыто (пользователь не закончит ввод).
success = 0
Инициализируем счётчик успешно переименованных символов.
for line in dialog.text_area.getText().split('\n'):
Перебираем каждую строку из текста, введённого пользователем в текстовой области диалога.
line = line.strip()
Удаляем пробелы в начале и конце строки.
if line and '=' in line and not line.startswith('#'):
Если строка не пуста, содержит символ “=” и не является комментарием (не начинается с “#”), то…
old, new = line.split('=', 1)
Разбиваем строку на две части по первому символу “=”, где левая часть – старое имя, правая – новое.
if self.find_and_rename(old.strip(), new.strip()):
Вызываем метод find_and_rename с очищенными от пробелов старыми и новыми именами; если переименование прошло успешно, то…
success += 1
… увеличиваем счётчик успешных переименований.
print("Successfully renamed: {}/{}".format(success, len(filtered_names)))
Выводим итоговое сообщение о том, сколько из найденных идентификаторов было успешно переименовано.
if __name__ == "__main__":
Стандартная проверка: выполняется ли скрипт как главный модуль.
func = getFunctionContaining(currentAddress)
Получаем функцию, в которой находится текущий адрес курсора в Ghidra.
if func:
Если функция найдена, то…
task = AdvancedRenamer(currentProgram, func)
Создаем экземпляр задачи AdvancedRenamer, передавая текущую программу и найденную функцию.
task.run()
Запускаем выполнение задачи переименования.
else:
Если функция не найдена (курсор не находится внутри функции), то…
print("Position cursor inside function!")
… выводим сообщение с просьбой установить курсор внутри функции для корректной работы скрипта.
Подготовка.
Когда все готово, можем пойти в любую нейросеть. По-моему мнению самая лучшая на сегодняшний день для данной задачи будет DeepSeek-r1.
Выверенный опытным путем промпт.
Прокомментируй каждую строку и переименнуй переменные и функции нормально.
Саму структуру кода оставь, как есть, не сокращая и не меняя,
то есть никаких "и так далее" - пиши код полностью!
Все имена должны быть подробными, то есть не tmp и прочее,
никаких сокращенных бысмысленных имен. Опиши к чему пренадлежат переменные,
не просто windowStruct, а UIState как напимер.
Так же переименую DAT_ как g_..новоеИмя, и LABEL_куда прыгаем
{НАШ_ПСЕВДО_КОД}
после кода выведи все, что переименовал в формате списка
старое_имя=новое_имя\n
в список включай все переменные, функции, DAT_, LAB_, param_ и прочее без исключений прям все пиши
Выбираем цель, для эксперимента

В итоге, у меня получился вот такой вот список для моей функции
param_1=hWndPointer
bVar1=isCursorConfinedDueToCapture
bVar2=isMarginApplied
cVar3=isCursorCapturedResult
local_res8=clientTopLeftPoint
local_res10=clientBottomRightPoint
local_38=confinedCursorRect
local_28=windowRect
Запускаем!!
После запуска скрипта (при условии, что курсор установлен внутри функции) откроется диалоговое окно, в котором будут выведены найденные идентификаторы.
Введите список переименований в формате:
ВАЖНО!!! Переменные по типу xVarN нужно сортировать по убыванию, так как они не внесены в базу. Переименование меньшей переменной приведёт к тому, что большая займёт её место, и список станет невалидным.
После ввода нажмите кнопку Apply и дождитесь окончания выполнения.

В логах появится сообщение..

В итоге должно получиться нечто подобное:

Спасибо за внимание! Скачать PDF и сам скрипт можно по ссылке – https://t.me/osiechan/62
А начать путь в реверс-инжиниринге можно на увлекательном бесплатном курсе по ботостроению для ММОРПГ – https://t.me/osiechan/41
HEXFFFFFFFF
Хм. Сначала хотел написать что бред какой то))) Но почитал и понял что не совсем бред. Не совсем понял нафига скрипты, и зачем вообще так длинно. В любом слчае это дело вкуса, что чем и как конвертить. Но основная идея - заставить ИИ расствить осмысленные имена переменным после декомпиляции это супер! Основной вопрос только в том насколько адекватно ИИ справляется с этим очень не простым заданием. Вы вообще смогли оценить насколько подставленные ИИ имена соответствуют сути? А то у меня есть подозрение что это будет немногим лучьше рандома.
Собственно смысл статьи должен быть в том что- "я попробовал расставлять осмысленные имена после декомпиляции с помощью ИИ и у меня получилось то-то и то-то"
И кстати. А нельзя скормить ИИ бинарник с заданием декомпилировать максимально близко к исходникам с осмысленными именами?
osieman Автор
DeepSeek-r1 справляется великолепно)
Он по второстепенным признакам находит взаимосвязи.
В любом случае быстро накидать названия приятне для глаза)
Ilya_JOATMON
Ida тоже умеет по функциям Вин АПИ ставить имена. Без всякого ИИ.
Вот я бы на него посмотрел, когда никакой экстра информации нет. А это 90% кода.
pfemidi
IDA больно уж дорогой по сравнению с Ghidra и Deepseek для личного пользования.
Ilya_JOATMON
Если вы под флагом со скрещенными костями - то не дорогой.
pfemidi
Я не сторонник данного флага. Так что для меня IDA дороговат.
osieman Автор
Ghidra тоже их ставит автоматически