kvs/инструменты/kvsd.py

325 lines
13 KiB
Python
Raw Normal View History

2026-05-21 23:39:30 +03:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kvsd.py
Дизассемблер ELF-файлов для КВС с кастомным синтаксисом
"""
import argparse
import os
import subprocess
import sys
from kvsd_data import translate_mnemonic, translate_operand
try:
from elfreader import (
read_elf_header, read_section_headers, get_section_name,
SHT_PROGBITS, EM_X86_64
)
except ImportError:
print("Ошибка: не найден модуль elfreader.py", file=sys.stderr)
sys.exit(1)
def parse_bytes_hex(bytes_str):
"""Преобразует строку шестнадцатеричных байтов в список байтов."""
result = []
for i in range(0, len(bytes_str), 2):
byte_hex = bytes_str[i:i+2]
if byte_hex:
result.append(int(byte_hex, 16))
return result
def extract_displacement(bytes_list, pos, size, little_endian=True):
"""Извлекает смещение из байтов."""
if pos + size > len(bytes_list):
return None
if little_endian:
result = 0
for i in range(size):
result |= bytes_list[pos + i] << (i * 8)
return result
else:
result = 0
for i in range(size):
result = (result << 8) | bytes_list[pos + i]
return result
def get_instruction_length(mnemonic, bytes_list):
"""Определяет длину инструкции в байтах."""
fixed_lengths = {
"вернуться": 1, "нет_операции": 1, "остановить": 1, "отладка": 1,
"втолкнуть_флаги": 1, "вытолкнуть_флаги": 1,
"установить_перенос": 1, "сбросить_перенос": 1,
"установить_направление": 1, "сбросить_направление": 1,
"переместить_байт": 1, "переместить_слово": 1,
"сравнить_байты": 1, "сканировать_байт": 1,
"вызов_системы": 2, "прервать": 2, "цикл": 2,
"короткий_переход": 2, "короткий_переход_если_равно": 2,
"короткий_переход_если_неравно": 2, "короткий_переход_если_меньше": 2,
"короткий_переход_если_больше": 2, "ввод_байта": 2, "вывод_байта": 2,
"переместить": 3, "сравнить": 3, "проверить": 3,
"прибавить": 3, "вычесть": 3, "и": 3, "или": 3, "исключающее_или": 3,
"увеличить": 3, "уменьшить": 3, "загрузить": 3, "сохранить": 3,
"загрузить_адрес": 3, "обменять": 4,
"сдвиг_влево": 4, "сдвиг_вправо": 4, "сдвиг_арифметический_вправо": 4,
"вращать_влево": 4, "вращать_вправо": 4,
"вращать_через_перенос_влево": 4, "вращать_через_перенос_вправо": 4,
"переход": 5, "вызвать": 5, "переход_если_равно": 6,
"переход_если_неравно": 6, "переход_если_ноль": 6,
"переход_если_не_ноль": 6, "переход_если_меньше": 6,
"переход_если_больше": 6,
}
if mnemonic in fixed_lengths:
return fixed_lengths[mnemonic]
if mnemonic == "переместить_имм":
if len(bytes_list) >= 2:
opcode = bytes_list[0] if bytes_list else 0
if opcode == 0x48 and len(bytes_list) > 1 and bytes_list[1] >= 0xB8:
return 10
elif opcode >= 0xB8 and opcode <= 0xBF:
return 5
return 7
if mnemonic in ["сравнить_с", "прибавить_непосредственно", "вычесть_непосредственно"]:
return 7
return len(bytes_list)
def compute_target_address(address, mnemonic, bytes_list):
"""Вычисляет целевой адрес для инструкций перехода."""
relative_jumps = {
"переход", "короткий_переход", "переход_если_равно", "переход_если_неравно",
"переход_если_меньше", "переход_если_больше", "переход_если_ноль",
"переход_если_не_ноль", "короткий_переход_если_равно", "короткий_переход_если_неравно",
"короткий_переход_если_меньше", "короткий_переход_если_больше",
"короткий_переход_если_перенос", "короткий_переход_если_нет_переноса",
"вызвать", "цикл"
}
if mnemonic not in relative_jumps:
return ""
instr_len = get_instruction_length(mnemonic, bytes_list)
if instr_len == 2 and len(bytes_list) >= 2:
displacement = bytes_list[1]
if displacement > 127:
displacement = displacement - 256
target = address + instr_len + displacement
return f"0x{target:x}"
elif instr_len == 5 and len(bytes_list) >= 5:
displacement = extract_displacement(bytes_list, 1, 4)
if displacement is not None:
if displacement > 0x7FFFFFFF:
displacement = displacement - 0x100000000
target = address + instr_len + displacement
return f"0x{target:x}"
elif instr_len == 6 and len(bytes_list) >= 6:
displacement = extract_displacement(bytes_list, 2, 4)
if displacement is not None:
if displacement > 0x7FFFFFFF:
displacement = displacement - 0x100000000
target = address + instr_len + displacement
return f"0x{target:x}"
return ""
def parse_ndisasm_line(line):
"""Разбирает строку вывода ndisasm и переводит её."""
line = line.strip()
if not line:
return None
parts = line.split(maxsplit=2)
if len(parts) < 3:
return None
address_str = parts[0]
bytes_hex = parts[1]
rest = parts[2]
# Разделяем мнемонику и операнды
rest_parts = rest.split(maxsplit=1)
mnemonic_orig = rest_parts[0]
operands_orig = rest_parts[1] if len(rest_parts) > 1 else ""
# Вычисляем длину инструкции
bytes_list = parse_bytes_hex(bytes_hex)
instr_len = len(bytes_list)
# Переводим мнемонику (с учётом операндов и длины)
translated_mnemonic = translate_mnemonic(mnemonic_orig, operands_orig, instr_len)
# Переводим операнды (регистры внутри)
translated_operands = translate_operand(operands_orig)
return {
'address': int(address_str, 16),
'address_hex': address_str,
'bytes_hex': bytes_hex,
'mnemonic': translated_mnemonic,
'operands': translated_operands,
'instr_len': instr_len
}
def filter_by_address_range(disasm_output, start_addr, end_addr):
"""
Оставляет только строки, адрес которых входит в диапазон [start_addr, end_addr).
"""
filtered_lines = []
for line in disasm_output.splitlines():
if not line.strip():
continue
addr_str = line.split(maxsplit=1)[0]
try:
addr = int(addr_str, 16)
if start_addr <= addr < end_addr:
filtered_lines.append(line)
except ValueError:
continue
return '\n'.join(filtered_lines)
def generate_csv_table(disasm_output):
"""Генерирует CSV-таблицу из вывода ndisasm."""
rows = []
for line in disasm_output.splitlines():
parsed = parse_ndisasm_line(line)
if not parsed:
continue
address = parsed['address']
bytes_hex = parsed['bytes_hex']
mnemonic = parsed['mnemonic']
operands = parsed['operands']
# Формируем исходную команду
if operands:
original_cmd = f"{mnemonic} {operands}"
else:
original_cmd = mnemonic
bytes_list = parse_bytes_hex(bytes_hex)
target = compute_target_address(address, mnemonic, bytes_list)
# Разбиваем байты на отдельные
byte_parts = [bytes_hex[i:i+2] for i in range(0, len(bytes_hex), 2)]
for i, byte in enumerate(byte_parts):
byte_address = address + i
if i == 0:
rows.append({
'address': f"0x{byte_address:x}",
'bytes': byte,
'target': target,
'cmd': original_cmd
})
else:
rows.append({
'address': f"0x{byte_address:x}",
'bytes': byte,
'target': "",
'cmd': ""
})
return rows
def disassemble_elf_to_csv(elf_file, output_file=None):
"""Дизассемблирует ELF-файл и сохраняет результат в CSV."""
try:
if not os.path.exists(elf_file):
return False, f"Файл {elf_file} не найден"
if not output_file:
base_name = os.path.splitext(elf_file)[0]
output_file = f"{base_name}.elf.csv"
with open(elf_file, 'rb') as f:
hdr = read_elf_header(f)
if hdr['e_machine'] != EM_X86_64:
print(f"Предупреждение: архитектура не x86-64")
if hdr['e_shnum'] == 0:
return False, "Файл не содержит таблицы секций"
sections = read_section_headers(f, hdr['e_shoff'], hdr['e_shnum'])
shstrtab = sections[hdr['e_shstrndx']]
shstrtab_offset = shstrtab['sh_offset']
shstrtab_size = shstrtab['sh_size']
offset, size, vaddr = None, None, None
for sec in sections:
name = get_section_name(f, shstrtab_offset, shstrtab_size, sec['sh_name'])
if name == '.text' and sec['sh_type'] == SHT_PROGBITS:
offset = sec['sh_offset']
size = sec['sh_size']
vaddr = sec['sh_addr']
break
if offset is None:
return False, "Секция .text не найдена"
print(f"Секция .text: адрес 0x{vaddr:x}, размер 0x{size:x} ({size} байт)")
cmd = ['ndisasm', '-b64', '-o', f'0x{vaddr - offset:x}', elf_file]
print(f"Запуск: {' '.join(cmd)}")
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
return False, f"Ошибка ndisasm: {result.stderr.strip()}"
end_addr = vaddr + size
filtered_output = filter_by_address_range(result.stdout, vaddr, end_addr)
if not filtered_output:
print("Предупреждение: после фильтрации не осталось строк")
rows = []
else:
rows = generate_csv_table(filtered_output)
with open(output_file, 'w', encoding='utf-8') as out_f:
out_f.write("адрес;байт;целевой_адрес;исходная_команда\n")
for row in rows:
out_f.write(f"{row['address']};{row['bytes']};{row['target']};{row['cmd']}\n")
print(f"CSV сохранён: {output_file} (строк: {len(rows)})")
return True, output_file
except FileNotFoundError:
return False, "ndisasm не найден. Установите NASM"
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(description='Дизассемблер ELF-файлов для КВС')
parser.add_argument('input_file', help='Путь к ELF-файлу')
parser.add_argument('-o', '--output', help='Выходной CSV-файл')
args = parser.parse_args()
print(f"Дизассемблирование: {args.input_file}")
success, result = disassemble_elf_to_csv(args.input_file, args.output)
if success:
print(f"\nГотово! Результат: {result}")
else:
print(f"Ошибка: {result}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()