201 lines
6.4 KiB
Python
201 lines
6.4 KiB
Python
|
|
"""Reverse Engineering routes."""
|
||
|
|
|
||
|
|
from flask import Blueprint, request, jsonify, render_template
|
||
|
|
from web.auth import login_required
|
||
|
|
|
||
|
|
reverse_eng_bp = Blueprint('reverse_eng', __name__, url_prefix='/reverse-eng')
|
||
|
|
|
||
|
|
|
||
|
|
def _get_re():
|
||
|
|
from modules.reverse_eng import get_reverse_eng
|
||
|
|
return get_reverse_eng()
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== PAGE ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/')
|
||
|
|
@login_required
|
||
|
|
def index():
|
||
|
|
return render_template('reverse_eng.html')
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== ANALYSIS ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/analyze', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def analyze():
|
||
|
|
"""Comprehensive binary analysis."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
result = _get_re().analyze_binary(file_path)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/strings', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def strings():
|
||
|
|
"""Extract strings from binary."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
min_length = int(data.get('min_length', 4))
|
||
|
|
encoding = data.get('encoding', 'both')
|
||
|
|
result = _get_re().extract_strings(file_path, min_length=min_length, encoding=encoding)
|
||
|
|
return jsonify({'strings': result, 'total': len(result)})
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== DISASSEMBLY ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/disassemble', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def disassemble():
|
||
|
|
"""Disassemble binary data or file."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
hex_data = data.get('hex', '').strip()
|
||
|
|
arch = data.get('arch', 'x64')
|
||
|
|
count = int(data.get('count', 100))
|
||
|
|
section = data.get('section', '.text')
|
||
|
|
|
||
|
|
if hex_data:
|
||
|
|
try:
|
||
|
|
raw = bytes.fromhex(hex_data.replace(' ', '').replace('\n', ''))
|
||
|
|
except ValueError:
|
||
|
|
return jsonify({'error': 'Invalid hex data'}), 400
|
||
|
|
instructions = _get_re().disassemble(raw, arch=arch, count=count)
|
||
|
|
elif file_path:
|
||
|
|
offset = int(data.get('offset', 0))
|
||
|
|
instructions = _get_re().disassemble_file(
|
||
|
|
file_path, section=section, offset=offset, count=count)
|
||
|
|
else:
|
||
|
|
return jsonify({'error': 'Provide file path or hex data'}), 400
|
||
|
|
|
||
|
|
return jsonify({'instructions': instructions, 'total': len(instructions)})
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== HEX ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/hex', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def hex_dump():
|
||
|
|
"""Hex dump of file region."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
offset = int(data.get('offset', 0))
|
||
|
|
length = int(data.get('length', 256))
|
||
|
|
length = min(length, 65536) # Cap at 64KB
|
||
|
|
result = _get_re().hex_dump(file_path, offset=offset, length=length)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/hex/search', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def hex_search():
|
||
|
|
"""Search for hex pattern in binary."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
pattern = data.get('pattern', '').strip()
|
||
|
|
if not file_path or not pattern:
|
||
|
|
return jsonify({'error': 'File path and pattern required'}), 400
|
||
|
|
result = _get_re().hex_search(file_path, pattern)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== YARA ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/yara/scan', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def yara_scan():
|
||
|
|
"""Scan file with YARA rules."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
rules_path = data.get('rules_path') or None
|
||
|
|
rules_string = data.get('rules_string') or None
|
||
|
|
result = _get_re().yara_scan(file_path, rules_path=rules_path, rules_string=rules_string)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/yara/rules')
|
||
|
|
@login_required
|
||
|
|
def yara_rules():
|
||
|
|
"""List available YARA rule files."""
|
||
|
|
rules = _get_re().list_yara_rules()
|
||
|
|
return jsonify({'rules': rules, 'total': len(rules)})
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== PACKER ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/packer', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def packer():
|
||
|
|
"""Detect packer in binary."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
result = _get_re().detect_packer(file_path)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== COMPARE ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/compare', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def compare():
|
||
|
|
"""Compare two binaries."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file1 = data.get('file1', '').strip()
|
||
|
|
file2 = data.get('file2', '').strip()
|
||
|
|
if not file1 or not file2:
|
||
|
|
return jsonify({'error': 'Two file paths required'}), 400
|
||
|
|
result = _get_re().compare_binaries(file1, file2)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== DECOMPILE ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/decompile', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def decompile():
|
||
|
|
"""Decompile binary with Ghidra headless."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
function = data.get('function') or None
|
||
|
|
result = _get_re().ghidra_decompile(file_path, function=function)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
# ==================== PE / ELF PARSING ====================
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/pe', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def parse_pe():
|
||
|
|
"""Parse PE headers."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
result = _get_re().parse_pe(file_path)
|
||
|
|
return jsonify(result)
|
||
|
|
|
||
|
|
|
||
|
|
@reverse_eng_bp.route('/elf', methods=['POST'])
|
||
|
|
@login_required
|
||
|
|
def parse_elf():
|
||
|
|
"""Parse ELF headers."""
|
||
|
|
data = request.get_json(silent=True) or {}
|
||
|
|
file_path = data.get('file', '').strip()
|
||
|
|
if not file_path:
|
||
|
|
return jsonify({'error': 'No file path provided'}), 400
|
||
|
|
result = _get_re().parse_elf(file_path)
|
||
|
|
return jsonify(result)
|