Как научить ИИ понимать ассемблер | Облегчаем анализ ПО

Как научить ИИ понимать ассемблер | Облегчаем анализ ПО
​​

Пожалуйста, не обращайте внимания на качество картинок, сгенерировано фигмой+моими корявыми клешнями немного допилено до приемлемого уровня.
Исходя из правил конкурса, я использовал ИИ в допустимых нормах. Большая часть текста написана лично мной
Я не претендую на звание супер пупер крутого программиста, я всего лишь обычный любитель, которые изо дня в день поглощает, проверяет и тестирует всю информацию что мне попадётся на глаза. Статьи я тоже не особо красиво умею оформлять, поэтому как сделано - так сделано, сильно не гнобите пожалуйста.
d2ebc43d-90c8-4059-b296-f6ec193e9d15.webp
Оформление может немного поплыть в силу того, что сижу на стареньком мониторе 1600х900,извините :)
В случае, если я выиграю одно из любых мест в конкурсе - все деньги пойдут на благотворительность, ни копейки не будет положено в карман.

В данный момент мы живём в эпоху нейросетей, это ни для кого не секрет. Статья написана исходя из личного опыта, путём проб и ошибок. Наброски были ещё пару месяцев назад, а так как стартовал конкурс, то решил дописать, обновить информацию.
Картинки были созданы с помощью фигмы, где то я сам делал, где-то иишкой красиво подправлял.

Как научить Искусственный интеллект понимать ассемблер​​

Любой, кто хоть раз занимался анализом любого вида софта, понимает о чём я. Вы загружаете малварь/другое ПО в IDA Pro(любой другой дизассемблер,Иду я беру в качестве примера), включаете декомпилятор и сталкиваетесь с кучей всякого непонятного.
Вместо внятной логики перед глазами предстает синтаксическая жесть (по другому не назвать)
Переменные по типу v1, v2, v3
Функций вида sub_403000 , а Константы вообще без комментариев​

​​

long long func_8048A10()
{
long long res;
long long temp_v;

res = internal_call_1(2, 1, 0);
if ( res < 0 )
return -1LL;
temp_v = res;
*(short *)(res + 2) = helper_transform(8080);
*(int *)(temp_v + 4) = parse_addr("127.0.0.1");
if ( (unsigned int)check_status(temp_v, res, 16) < 0 )
{
cleanup(temp_v);
return -1LL;
}
return (unsigned int)temp_v;
}

C

Код:
long long func_8048A10()
{
  long long res;
  long long temp_v;
 
  res = internal_call_1(2, 1, 0);
  if ( res < 0 )
    return -1LL;
  temp_v = res;
  *(short *)(res + 2) = helper_transform(8080);
  *(int *)(temp_v + 4) = parse_addr("127.0.0.1");
  if ( (unsigned int)check_status(temp_v, res, 16) < 0 )
  {
    cleanup(temp_v);
    return -1LL;
  }
  return (unsigned int)temp_v;
}
Современные LLM способны не просто переводить байт код в текст. Вместо того чтобы просто копировать структуру стека, нейросеть может догадаться(очень утрировано) о предназначении функции, основываясь на паттернах использования данных.

Механика декомпиляции​​

Прежде чем интегрировать искусственный интеллект, нужно понять как вообще получается псевдокод из машинного кода.
​

1) Дизассемблирование​​

На старте у нас есть только массив байтов. Задача первого этапа, это сопоставить байты с конкретными процессорными инструкциями.
Стандартом является библиотека Capstone Engine. Она берет на себя всю сложность поддержки различных архитектур (я не буду вдаваться в подробности, это не столь важно)
P.S Для примера я попросил набросать код нейронку

from capstone import *

# Срез байтов из исполняемого сегмента файла
raw_payload = b"\x55\x48\x89\xe5\x48\x83\xec\x10"

# Конфигурация движка под 64-битную архитектуру Intel
disassembler = Cs(CS_ARCH_X86, CS_MODE_64)

for instruction in disassembler.disasm(raw_payload, 0x1000):
print(f"ADDR: 0x{instruction.address:x} | {instruction.mnemonic.upper()} {instruction.op_str}")

Python

Код:
from capstone import *

# Срез байтов из исполняемого сегмента файла
raw_payload = b"\x55\x48\x89\xe5\x48\x83\xec\x10"

# Конфигурация движка под 64-битную архитектуру Intel
disassembler = Cs(CS_ARCH_X86, CS_MODE_64)

for instruction in disassembler.disasm(raw_payload, 0x1000):
    print(f"ADDR: 0x{instruction.address:x} | {instruction.mnemonic.upper()} {instruction.op_str}")

На выходе мы получаем линейный список мнемоник. Это уже можно читать, но общая логика программы все еще скрыта.​​

2) Реконструкция логических связей​​

Давайте уясним одно, программа - это не просто список команд,а целое древо всевозможных решений. Чтобы восстановить так называемое древо, мы строим График потока управления
Суть этого процесса:

  1. Базовые блоки: Мы группируем инструкции в атомарные узлы. Внутри такого блока нет прыжков или вызовов - выполнение идет сверху вниз.
  2. Ребра графа: Соединяем блоки линиями перехода. Условный переход создает две ветки (T/F в просто народе правда или не правда), формируя структуру if-else.
Именно на этом этапе алгоритмы начинают узнавать в коде циклы for или конструкции switch-case.

3) Лифтинг и промежуточное представление​​
Многие ошибочно полагают, что декомпилятор просто заменяет слова MOV на знак =
На самом деле, между сырым ассемблером и читаемым кодом лежит переводческий мост. Это универсальный язык посредник. Представьте, что вы переводите с китайского на инди через английский.

Проблема ассемблера в том, что он перегружен флагами. Инструкция ADD не просто складывает числа, она меняет флаги нуля, переноса, переполнения. IR очищает эту логику, делая её явной и понятной для математического анализа.

Для удобства, я сделал картинку в виде таблицы



​​

​​

​​

Почему это важно для обучения нейронок


Если мы будем закидывать в нейросеть чистый ассемблер, ей придется тратить очень много времени, чтобы понять архитектурные особенности конкретного процессора.​​

​​

4) Генерация C псевдокода​​

Финальный этап: конвертация IR в читаемый C код.
Анализируем CFG, определяем тип блоков, и генерируем соответствующие конструкции:

int function_400000(int arg1) {
int var_4;

var_4 = arg1;

if (var_4 <= 0) {
return 0;
}

return 1;
}

C

Код:
int function_400000(int arg1) {
    int var_4;
  
    var_4 = arg1;
  
    if (var_4 <= 0) {
        return 0;
    }
  
    return 1;
}

Но самое важное, имена переменных все еще автоматические (var_4, arg1) Здесь в нашу скажем так игру вступает ИИ

Интегрируем ИИ​​
Представьте: у вас есть функция с 20 переменными. Вручную анализировать каждую это часы работы. ИИ нам поможет в любом случае:

  1. Понять контекст
  2. Предложить имена
  3. Добавить комментарии:
  4. Определить назначение функции

Конечно,это далеко не весь список,где ИИшки могут помочь, поэтому не стал описывать дальше.

Выбор модели: ChatGPT 5.2 vs альтернативы
​​

Все мы прекрасно понимаем что цензуры есть везде, нейронки не исключение. ChatGPT для анализа вредоносов вам не поможет, вы упрётесь в цензуру.
Обходы ищите сами, я в этом вам не помогу.
Гемини я специально не брал в таблицу, в нём нет смысла от слова совсем.

Как это работает: API интеграция ​​

Декомпилятор отправляет в ИИшку три вещи:
1. Псевдокод функции
2. Контекст
3. Промпт


import requests
[SIZE=4]import json[/SIZE]

[SIZE=4]class AIDecompiler:[/SIZE]
[SIZE=4] def __init__(self, api_key, model="gpt-5.2"):[/SIZE]
[SIZE=4] self.api_key = api_key[/SIZE]
[SIZE=4] self.model = model[/SIZE]
[SIZE=4] self.cache = {}[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] def analyze_function(self, pseudocode, context):[/SIZE]
[SIZE=4] # Проверяем кэш[/SIZE]
[SIZE=4] cache_key = hashlib.sha256(pseudocode.encode()).hexdigest()[/SIZE]
[SIZE=4] if cache_key in self.cache:[/SIZE]
[SIZE=4] return self.cache[cache_key][/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] # Формируем промпт[/SIZE]
[SIZE=4] prompt = self.build_prompt(pseudocode, context)[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] # API запрос[/SIZE]
[SIZE=4] response = requests.post([/SIZE]
[SIZE=4] "https://api.openai.com/v1/chat/completions",[/SIZE]
[SIZE=4] headers={[/SIZE]
[SIZE=4] "Authorization": f"Bearer {self.api_key}",[/SIZE]
[SIZE=4] "Content-Type": "application/json"[/SIZE]
[SIZE=4] },[/SIZE]
[SIZE=4] json={[/SIZE]
[SIZE=4] "model": self.model,[/SIZE]
[SIZE=4] "messages": [[/SIZE]
[SIZE=4] {"role": "system", "content": self.get_system_prompt()},[/SIZE]
[SIZE=4] {"role": "user", "content": prompt}[/SIZE]
[SIZE=4] ],[/SIZE]
[SIZE=4] "temperature": 0.2,[/SIZE]
[SIZE=4] "response_format": {"type": "json_object"}[/SIZE]
[SIZE=4] }[/SIZE]
[SIZE=4] )[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] result = response.json()[/SIZE]
[SIZE=4] analysis = json.loads(result['choices'][0]['message']['content'])[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] # Сохраняем в кэш[/SIZE]
[SIZE=4] self.cache[cache_key] = analysis[/SIZE]
[SIZE=4] return analysis[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] def build_prompt(self, code, ctx):[/SIZE]
[SIZE=4] return f"""Analyze this decompiled function:[/SIZE]

[SIZE=4]```c[/SIZE]
[SIZE=4]{code}[/SIZE]
[SIZE=4]```[/SIZE]

[SIZE=4]Context:[/SIZE]
[SIZE=4]- API calls: {', '.join(ctx.get('apis', []))}[/SIZE]
[SIZE=4]- Strings: {', '.join(ctx.get('strings', []))}[/SIZE]
[SIZE=4]- Constants: {', '.join(map(str, ctx.get('numbers', [])))}[/SIZE]

[SIZE=4]Provide JSON with function_name, purpose, and variable renames."""[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] def get_system_prompt(self):[/SIZE]
[SIZE=4] return "You are an expert reverse engineer. Analyze code and suggest meaningful names based on usage patterns. Output JSON only."

Python

Код:
import requests
[SIZE=4]import json[/SIZE]

[SIZE=4]class AIDecompiler:[/SIZE]
[SIZE=4]    def __init__(self, api_key, model="gpt-5.2"):[/SIZE]
[SIZE=4]        self.api_key = api_key[/SIZE]
[SIZE=4]        self.model = model[/SIZE]
[SIZE=4]        self.cache = {}[/SIZE]
[SIZE=4]   [/SIZE]
[SIZE=4]    def analyze_function(self, pseudocode, context):[/SIZE]
[SIZE=4]        # Проверяем кэш[/SIZE]
[SIZE=4]        cache_key = hashlib.sha256(pseudocode.encode()).hexdigest()[/SIZE]
[SIZE=4]        if cache_key in self.cache:[/SIZE]
[SIZE=4]            return self.cache[cache_key][/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        # Формируем промпт[/SIZE]
[SIZE=4]        prompt = self.build_prompt(pseudocode, context)[/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        # API запрос[/SIZE]
[SIZE=4]        response = requests.post([/SIZE]
[SIZE=4]            "https://api.openai.com/v1/chat/completions",[/SIZE]
[SIZE=4]            headers={[/SIZE]
[SIZE=4]                "Authorization": f"Bearer {self.api_key}",[/SIZE]
[SIZE=4]                "Content-Type": "application/json"[/SIZE]
[SIZE=4]            },[/SIZE]
[SIZE=4]            json={[/SIZE]
[SIZE=4]                "model": self.model,[/SIZE]
[SIZE=4]                "messages": [[/SIZE]
[SIZE=4]                    {"role": "system", "content": self.get_system_prompt()},[/SIZE]
[SIZE=4]                    {"role": "user", "content": prompt}[/SIZE]
[SIZE=4]                ],[/SIZE]
[SIZE=4]                "temperature": 0.2,[/SIZE]
[SIZE=4]                "response_format": {"type": "json_object"}[/SIZE]
[SIZE=4]            }[/SIZE]
[SIZE=4]        )[/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        result = response.json()[/SIZE]
[SIZE=4]        analysis = json.loads(result['choices'][0]['message']['content'])[/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        # Сохраняем в кэш[/SIZE]
[SIZE=4]        self.cache[cache_key] = analysis[/SIZE]
[SIZE=4]        return analysis[/SIZE]
[SIZE=4]   [/SIZE]
[SIZE=4]    def build_prompt(self, code, ctx):[/SIZE]
[SIZE=4]        return f"""Analyze this decompiled function:[/SIZE]

[SIZE=4]```c[/SIZE]
[SIZE=4]{code}[/SIZE]
[SIZE=4]```[/SIZE]

[SIZE=4]Context:[/SIZE]
[SIZE=4]- API calls: {', '.join(ctx.get('apis', []))}[/SIZE]
[SIZE=4]- Strings: {', '.join(ctx.get('strings', []))}[/SIZE]
[SIZE=4]- Constants: {', '.join(map(str, ctx.get('numbers', [])))}[/SIZE]

[SIZE=4]Provide JSON with function_name, purpose, and variable renames."""[/SIZE]
[SIZE=4]   [/SIZE]
[SIZE=4]    def get_system_prompt(self):[/SIZE]
[SIZE=4]        return "You are an expert reverse engineer. Analyze code and suggest meaningful names based on usage patterns. Output JSON only."
[/SIZE]

Оптимизируем наш код, добавляя многопоточность

from concurrent.futures import ThreadPoolExecutor

[SIZE=4]def analyze_all_functions(functions, ai_client):[/SIZE]
[SIZE=4] results = {}[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] with ThreadPoolExecutor(max_workers=8) as executor:[/SIZE]
[SIZE=4] futures = {}[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] for func in functions:[/SIZE]
[SIZE=4] future = executor.submit([/SIZE]
[SIZE=4] ai_client.analyze_function,[/SIZE]
[SIZE=4] func.pseudocode,[/SIZE]
[SIZE=4] func.context[/SIZE]
[SIZE=4] )[/SIZE]
[SIZE=4] futures[future] = func[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] for future in futures:[/SIZE]
[SIZE=4] func = futures[future][/SIZE]
[SIZE=4] try:[/SIZE]
[SIZE=4] result = future.result()[/SIZE]
[SIZE=4] results[func.address] = result[/SIZE]
[SIZE=4] except Exception as e:[/SIZE]
[SIZE=4] print(f"Failed to analyze {func.name}: {e}")[/SIZE]
[SIZE=4] [/SIZE]
[SIZE=4] return results[/SIZE]
[SIZE=4]

Python

Код:
from concurrent.futures import ThreadPoolExecutor

[SIZE=4]def analyze_all_functions(functions, ai_client):[/SIZE]
[SIZE=4]    results = {}[/SIZE]
[SIZE=4]   [/SIZE]
[SIZE=4]    with ThreadPoolExecutor(max_workers=8) as executor:[/SIZE]
[SIZE=4]        futures = {}[/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        for func in functions:[/SIZE]
[SIZE=4]            future = executor.submit([/SIZE]
[SIZE=4]                ai_client.analyze_function,[/SIZE]
[SIZE=4]                func.pseudocode,[/SIZE]
[SIZE=4]                func.context[/SIZE]
[SIZE=4]            )[/SIZE]
[SIZE=4]            futures[future] = func[/SIZE]
[SIZE=4]       [/SIZE]
[SIZE=4]        for future in futures:[/SIZE]
[SIZE=4]            func = futures[future][/SIZE]
[SIZE=4]            try:[/SIZE]
[SIZE=4]                result = future.result()[/SIZE]
[SIZE=4]                results[func.address] = result[/SIZE]
[SIZE=4]            except Exception as e:[/SIZE]
[SIZE=4]                print(f"Failed to analyze {func.name}: {e}")[/SIZE]
[SIZE=4]   [/SIZE]
[SIZE=4]    return results[/SIZE]
[SIZE=4]
[/SIZE]

Результат: С 8 потоками анализ 1к функций занимает +-10-15 минут вместо кучи часов.


Заключение​​

Каждый лично для себя сделает вывод исходя из прочитанного. В наше время ИИ естественно не может полностью заменить человека, но как минимум облегчить рутину, массивные задачи она с лёгкостью сможет.
Благодарю за прочтение. В следующей статье мы рассмотрим реальный кейс,будем анализировать малвари(вы можете скинуть любой свой вирус, его мы тоже можем рассмотреть в качестве подопытного)

Комментарии 0

Пока нет комментариев. Начните обсуждение.
Войдите, чтобы оставить комментарий.
Назад
Верх