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

325 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()