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