feat(analysis): add support for IDA Pro 6.6

This commit is contained in:
Rodrigo Verdiani 2026-06-09 19:30:59 -03:00
parent a1c5a70429
commit f2ba9b2019
12 changed files with 1049 additions and 333 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ target/
# Uploaded binaries # Uploaded binaries
uploads/ uploads/
engines/ida5/ida5_files/** engines/ida5/ida5_files/**
engines/ida66/ida66_files/**
### Intellij ### ### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider

View File

@ -0,0 +1,62 @@
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
# Install 32-bit Wine, Xvfb, and utilities
RUN dpkg --add-architecture i386 && \
apt-get update && \
apt-get install -y --no-install-recommends \
wine \
wine32 \
xvfb \
xauth \
fonts-dejavu-core \
fonts-liberation && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Configure Wine environment variables
ENV WINEPREFIX=/wine/prefix
ENV WINEARCH=win32
ENV WINEDLLOVERRIDES="mscoree,mshtml="
RUN mkdir -p /wine/prefix
# Initialize the default Wine prefix
RUN xvfb-run -a wine wineboot -u && wineserver -w && ls -la /wine/prefix/*.reg
# Install Python 2.7 in Wine for IDAPython
COPY ./python-2.7.18.msi /tmp/python-2.7.18.msi
RUN xvfb-run -a wine msiexec /i /tmp/python-2.7.18.msi /qn && \
rm /tmp/python-2.7.18.msi
# Set up application directories
RUN mkdir -p /app/ida /input /output
COPY ./ida66_files /app/ida
# IDAPython local runtime: copy Python stdlib into IDA's python/ directory
RUN cp /wine/prefix/drive_c/Python27/python27.dll /app/ida/ && \
cp -r /wine/prefix/drive_c/Python27/Lib /app/ida/python/lib/ && \
cp -r /wine/prefix/drive_c/Python27/DLLs /app/ida/python/DLLs/ && \
sed -i 's/USE_LOCAL_PYTHON = 0/USE_LOCAL_PYTHON = 1/' /app/ida/cfg/python.cfg
# Import IDA 6.6 license configuration into Wine registry
COPY ida6_license.reg /tmp/ida6_license.reg
COPY ida-hkcu.sh /tmp/ida-hkcu.sh
RUN wine regedit /tmp/ida6_license.reg && \
rm /tmp/ida6_license.reg && \
bash /tmp/ida-hkcu.sh && \
rm /tmp/ida-hkcu.sh && \
wineserver -w && \
grep "Hex-Rays" /wine/prefix/user.reg
WORKDIR /app/ida
# Embed the analysis scripts into the image
COPY extract.py /app/ida/extract.py
# Configure the entrypoint script
COPY entrypoint.sh /app/ida/entrypoint.sh
RUN chmod +x /app/ida/entrypoint.sh
ENTRYPOINT ["/app/ida/entrypoint.sh"]

View File

@ -0,0 +1,39 @@
#!/bin/bash
export WINEPREFIX="/wine/prefix"
export WINEARCH=win32
export WINEDEBUG=-all
export DISPLAY=:99
echo "[INFO] Starting virtual X11 display on :99 (1280x720)..."
Xvfb :99 -screen 0 1280x720x24 +extension RANDR &
sleep 1
# Accept target filename as first argument
TARGET_FILENAME="${1:-target.exe}"
if [ ! -f "/input/${TARGET_FILENAME}" ]; then
echo "[ERROR] Missing /input/${TARGET_FILENAME}"
exit 1
fi
echo "[INFO] Preparing internal analysis environment..."
cp "/input/${TARGET_FILENAME}" "/app/ida/target.exe"
echo "[INFO] Launching IDA Pro 6.6 (auto-script)..."
wine idaq.exe -A -S"extract.py" target.exe
echo "[INFO] Verifying generated artifacts..."
if [ -f "/app/ida/output.json" ]; then
cp /app/ida/output.json /output/output.json
echo "[SUCCESS] Static analysis completed. output.json is ready!"
RET_CODE=0
else
echo "[ERROR] IDAPython script failed to generate output.json"
RET_CODE=1
fi
# Clean up temporary files inside the container before exiting
rm -f /app/ida/target.exe /app/ida/target.idb /app/ida/output.json
exit $RET_CODE

228
engines/ida66/extract.py Normal file
View File

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
# IDAPython extraction script for IDA Pro 6.6
# Equivalent to extract.idc — same JSON output structure
import sys
import traceback
import os
import json
import idc
import idaapi
import idautils
OUTPUT = "output.json"
# Cross-reference type constants (IDA SDK values)
FL_CF = 16 # Call Far
FL_CN = 17 # Call Near
FL_JF = 18 # Jump Far
FL_JN = 19 # Jump Near
def msg(text):
idc.Message(text)
sys.stderr.write(text)
sys.stderr.flush()
def _json_string(s):
"""Escape a string for JSON — returns the quoted representation.
Handles raw bytes in disassembly that aren't valid UTF-8."""
if isinstance(s, unicode):
return json.dumps(s)
try:
return json.dumps(s)
except UnicodeDecodeError:
return json.dumps(s.decode('latin-1'))
def _json_hex(addr):
"""Format an address as 0x-prefixed hex string (uppercase, 8 digits)."""
return "0x%08X" % addr
def extract_func(f, ea):
"""Extract a single function. Returns True on success, False on failure."""
func_name = idc.GetFunctionName(ea)
func_end = idc.FindFuncEnd(ea)
func_flags = idc.GetFunctionFlags(ea)
f.write(" {\n")
f.write(" \"function\": %s,\n" % _json_string(func_name))
f.write(" \"address\": \"%s\",\n" % _json_hex(ea))
f.write(" \"size\": %d,\n" % (func_end - ea))
# ---- flags ----
flags_parts = []
if func_flags & idc.FUNC_LIB:
flags_parts.append("LIBRARY")
if func_flags & idc.FUNC_THUNK:
flags_parts.append("THUNK")
if func_flags & idc.FUNC_FAR:
flags_parts.append("FAR")
if func_flags & idc.FUNC_FRAME:
flags_parts.append("FRAME")
f.write(" \"flags\": %s,\n" % _json_string("|".join(flags_parts)))
# ---- xrefs (who calls/jumps to this function) ----
xref_parts = []
for xref in idautils.XrefsTo(ea):
if xref.type in (FL_CF, FL_CN, FL_JF, FL_JN):
caller_name = idc.GetFunctionName(xref.frm)
if caller_name != "":
if xref.type == FL_CN or xref.type == FL_CF:
type_str = "CALL"
elif xref.type == FL_JN or xref.type == FL_JF:
type_str = "JUMP"
else:
type_str = "UNKNOWN"
xref_parts.append("%s:%s" % (caller_name, type_str))
f.write(" \"xrefs\": %s,\n" % _json_string(" ".join(xref_parts)))
f.flush()
# ---- assembly ----
asm_lines = []
curr_inst = ea
last_inst = 0
while curr_inst < func_end:
if curr_inst == last_inst:
break
last_inst = curr_inst
disasm = idc.GetDisasm(curr_inst)
if disasm:
asm_lines.append("%s %s" % (_json_hex(curr_inst), disasm))
curr_inst = curr_inst + idc.ItemSize(curr_inst)
f.write(" \"assembly\": %s,\n" % _json_string("\n".join(asm_lines)))
f.flush()
# ---- labels (named data/code references from this function) ----
label_parts = []
curr_inst = ea
last_inst = 0
while curr_inst < func_end:
if curr_inst == last_inst:
break
last_inst = curr_inst
for xref in idautils.XrefsFrom(curr_inst):
if xref.type in (FL_CF, FL_CN, FL_JF, FL_JN):
label_name = idc.Name(xref.to)
if label_name != "":
if xref.type == FL_CN or xref.type == FL_CF:
type_str = "CALL"
elif xref.type == FL_JN or xref.type == FL_JF:
type_str = "JUMP"
else:
type_str = "UNKNOWN"
label_parts.append(
"%s:%s:%s" % (label_name, _json_hex(xref.to), type_str)
)
curr_inst = curr_inst + idc.ItemSize(curr_inst)
f.write(" \"labels\": %s\n" % _json_string(" ".join(label_parts)))
f.write(" }")
f.flush()
return True
def extract(f):
entry_ea = idc.MinEA()
entry_name = idc.Name(entry_ea)
if entry_name == "":
entry_name = "entry_point"
f.write(
"{\n \"entry_point\": \"%s:%s\",\n" % (_json_hex(entry_ea), entry_name)
)
# ---- segments ----
f.write(" \"segments\": [\n")
segs = []
seg = idc.FirstSeg()
while seg != idc.BADADDR:
segs.append(
" {\"name\": %s, \"start\": \"%s\", \"end\": \"%s\"}"
% (
_json_string(idc.SegName(seg)),
_json_hex(idc.SegStart(seg)),
_json_hex(idc.SegEnd(seg)),
)
)
seg = idc.NextSeg(seg)
f.write(",\n".join(segs))
f.write("\n ],\n")
f.flush()
# ---- functions ----
f.write(" \"functions\": [\n")
f.flush()
ea = idc.NextFunction(0)
func_count = 0
error_count = 0
while ea != idc.BADADDR:
if func_count > 0:
f.write(",\n")
f.flush()
try:
if extract_func(f, ea):
func_count += 1
else:
error_count += 1
msg("[extract.py] Aviso: falha ao extrair funcao em %s\n"
% _json_hex(ea))
except Exception as e:
error_count += 1
tb_str = traceback.format_exc().strip().replace("\n", "\\n")
msg("[extract.py] Erro na funcao %s: %s\n"
% (_json_hex(ea), str(e)))
# Write a placeholder with the error info so JSON remains valid
f.write(
" {\"function\": \"__ERROR__\","
" \"address\": \"%s\","
" \"error\": %s}"
% (_json_hex(ea), _json_string(tb_str))
)
f.flush()
ea = idc.NextFunction(ea)
msg("[extract.py] Extraidas %d funcoes, %d erros.\n"
% (func_count, error_count))
f.write("\n ]\n}\n")
f.flush()
def main():
msg("[extract.py] Script iniciado. CWD=%s\n" % os.getcwd())
idaapi.autoWait()
msg("[extract.py] Analise concluida. Extraindo...\n")
try:
# Line-buffered — flushes after every newline, prevents data loss
f = open(OUTPUT, "w", 1)
except Exception as e:
msg("[extract.py] ERRO ao criar %s: %s\n" % (OUTPUT, str(e)))
idc.Exit(1)
return
try:
extract(f)
except Exception as e:
msg("[extract.py] ERRO na extracao: %s\n" % str(e))
msg(traceback.format_exc() + "\n")
finally:
f.close()
if os.path.exists(OUTPUT):
msg("[extract.py] %s gerado com sucesso (%d bytes)!\n" %
(OUTPUT, os.path.getsize(OUTPUT)))
else:
msg("[extract.py] FALHA: %s nao foi criado.\n" % OUTPUT)
idc.Exit(0)
if __name__ == "__main__":
main()

50
engines/ida66/ida-hkcu.sh Normal file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# Import HKCU keys for IDA 6.6 — license + suppress nag dialogs
# These key names contain special chars that are tricky to escape in Dockerfile RUN
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "License Zhou Tao, Jiangsu Australia Sinuo Network Technology Co., Ltd." \
/t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "AutoCheckUpdates" /t REG_DWORD /d 0 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "AutoRequestUpdates" /t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "DisplayWelcome" /t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "InformedAboutUpdates2" /t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "InputDirectory" /t REG_SZ /d "C:/Program Files/IDA 6.6" /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "QuickFilterFlags" /t REG_DWORD /d 0 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "SplashWindow" /t REG_DWORD /d 0 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA" \
/v "48-3057-7374-2C6.6" /t REG_DWORD /d 0 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA\\Hidden Messages" \
/v "Congratulations the TIL validator is active " /t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA\\Hidden Messages" \
/v "GOOD NEWS THE BTREE VALIDATOR v66 IS ACTIVE The btree validator plugin will now verify the input d" \
/t REG_DWORD /d 1 /f
wine reg add "HKEY_CURRENT_USER\\Software\\Hex-Rays\\IDA\\Hidden Messages" \
/v "The technical support period of the Hex Rays decompiler has expired You have 3 month grace period t" \
/t REG_DWORD /d 1 /f
echo "HKCU keys imported"
# Add Python27 to system PATH so python.plw finds python27.dll
echo "Updating system PATH..."
CUR=$(wine reg query "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" /v PATH 2>/dev/null | tr -d '\r' | awk '/PATH/{$1=$2=""; sub(/^[ \t]+/, ""); print}')
wine reg add "HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment" /v PATH /t REG_EXPAND_SZ /d "C:\\Python27;${CUR}" /f
echo "PATH: C:\\Python27;${CUR}"

View File

@ -0,0 +1,234 @@
Windows Registry Editor Version 5.00
; =============================================================================
; IDA Pro 6.6 — License, settings, file associations, and runtime configuration
; Extracted from system.reg + user.reg Wine registry dumps
; =============================================================================
; --- HKEY_CURRENT_USER: IDA application settings and license ---
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA]
"48-3057-7374-2C6.6"=dword:00000000
"AutoCheckUpdates"=dword:00000000
"AutoRequestUpdates"=dword:00000001
"DisplayWelcome"=dword:00000001
"InformedAboutUpdates2"=dword:00000001
"InputDirectory"="C:/Program Files/IDA 6.6"
"License Zhou Tao, Jiangsu Australia Sinuo Network Technology Co., Ltd."=dword:00000001
"QState"=hex:40,00,42,00,79,00,74,00,65,00,41,00,72,00,72,00,61,00,79,00,28,00,\
00,00,00,00,00,00,ff,00,00,00,00,00,00,00,01,00,fd,00,00,00,00,00,00,00,02,\
00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
fc,00,02,00,00,00,00,00,00,00,01,00,fb,00,00,00,00,00,00,00,1c,00,00,00,41,\
00,00,00,64,00,00,00,64,00,00,00,72,00,00,00,65,00,00,00,73,00,00,00,73,00,\
00,00,44,00,00,00,65,00,00,00,74,00,00,00,61,00,00,00,69,00,00,00,6c,00,00,\
00,73,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,\
00,00,51,00,00,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,02,00,00,00,00,00,07,\
00,80,00,00,00,00,00,00,00,32,00,fc,00,01,00,00,00,00,00,00,00,01,00,fb,00,\
00,00,00,00,00,00,12,00,00,00,4e,00,00,00,61,00,00,00,76,00,00,00,69,00,00,\
00,67,00,00,00,61,00,00,00,74,00,00,00,6f,00,00,00,72,00,01,00,00,00,00,00,\
00,00,00,00,00,00,00,00,07,00,80,00,00,00,00,00,00,00,46,00,00,00,ff,00,ff,\
00,ff,00,00,00,00,00,07,00,80,00,00,00,00,00,03,00,7b,00,00,00,00,00,00,00,\
04,00,00,00,00,00,00,00,04,00,00,00,00,00,00,00,08,00,00,00,00,00,00,00,08,\
00,fc,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,\
19,00,00,00,00,00,00,00,16,00,00,00,46,00,00,00,69,00,00,00,6c,00,00,00,65,\
00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,\
00,00,72,00,01,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,16,00,00,00,4a,00,\
00,00,75,00,00,00,6d,00,00,00,70,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,\
00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,01,00,00,00,00,00,00,00,3a,00,\
ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,1a,00,00,00,53,00,00,00,65,00,00,00,61,00,00,00,72,00,00,00,\
63,00,00,00,68,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,\
00,00,00,61,00,00,00,72,00,01,00,00,00,00,00,00,00,8c,00,ff,00,ff,00,ff,00,\
ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,1e,\
00,00,00,41,00,00,00,6e,00,00,00,61,00,00,00,6c,00,00,00,79,00,00,00,73,00,\
00,00,69,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,\
00,42,00,00,00,61,00,00,00,72,00,01,00,00,00,00,00,01,00,34,00,ff,00,ff,00,\
ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,16,00,00,00,45,00,00,00,64,00,00,00,69,00,00,00,74,00,00,00,54,00,00,00,\
6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,01,00,00,\
00,00,00,01,00,6e,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,18,00,00,00,44,00,00,00,65,00,00,00,62,\
00,00,00,75,00,00,00,67,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,\
00,00,42,00,00,00,61,00,00,00,72,00,01,00,00,00,00,00,02,00,27,00,ff,00,ff,\
00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,24,00,00,00,42,00,00,00,72,00,00,00,65,00,00,00,61,00,00,00,6b,00,00,\
00,70,00,00,00,6f,00,00,00,69,00,00,00,6e,00,00,00,74,00,00,00,73,00,00,00,\
54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,\
00,01,00,00,00,00,00,03,00,3f,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,1e,00,00,00,53,00,00,00,65,\
00,00,00,67,00,00,00,6d,00,00,00,65,00,00,00,6e,00,00,00,74,00,00,00,73,00,\
00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,\
00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,20,00,00,00,47,00,00,\
00,72,00,00,00,61,00,00,00,70,00,00,00,68,00,00,00,56,00,00,00,69,00,00,00,\
65,00,00,00,77,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,\
00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,\
ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,22,\
00,00,00,53,00,00,00,74,00,00,00,72,00,00,00,75,00,00,00,63,00,00,00,74,00,\
00,00,75,00,00,00,72,00,00,00,65,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,\
00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,\
00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,26,00,00,00,45,00,00,00,6e,00,00,00,75,00,00,00,\
6d,00,00,00,65,00,00,00,72,00,00,00,61,00,00,00,74,00,00,00,69,00,00,00,6f,\
00,00,00,6e,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,\
00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,\
00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,24,00,00,00,4f,00,00,00,70,00,00,00,65,00,00,00,72,00,00,00,61,00,00,\
00,6e,00,00,00,64,00,00,00,54,00,00,00,79,00,00,00,70,00,00,00,65,00,00,00,\
54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,\
00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,20,00,00,00,46,00,00,00,75,\
00,00,00,6e,00,00,00,63,00,00,00,74,00,00,00,69,00,00,00,6f,00,00,00,6e,00,\
00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,\
00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,1c,00,00,\
00,54,00,00,00,72,00,00,00,61,00,00,00,63,00,00,00,69,00,00,00,6e,00,00,00,\
67,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,\
00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,18,00,00,00,4c,\
00,00,00,69,00,00,00,73,00,00,00,74,00,00,00,73,00,00,00,54,00,00,00,6f,00,\
00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,\
00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,1a,00,00,00,47,00,00,00,72,00,00,00,61,00,00,\
00,70,00,00,00,68,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,\
6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,\
00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,24,00,00,00,44,00,00,00,62,00,00,00,67,00,00,00,43,00,00,00,6f,\
00,00,00,6d,00,00,00,6d,00,00,00,61,00,00,00,6e,00,00,00,64,00,00,00,73,00,\
00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,\
00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,2c,00,00,00,43,00,00,\
00,72,00,00,00,6f,00,00,00,73,00,00,00,73,00,00,00,52,00,00,00,65,00,00,00,\
66,00,00,00,65,00,00,00,72,00,00,00,65,00,00,00,6e,00,00,00,63,00,00,00,65,\
00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,\
00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,20,00,\
00,00,55,00,00,00,74,00,00,00,69,00,00,00,6c,00,00,00,69,00,00,00,74,00,00,\
00,69,00,00,00,65,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,\
6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,\
00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,20,00,00,00,53,00,00,00,69,00,00,00,67,00,00,00,6e,00,00,00,54,\
00,00,00,79,00,00,00,70,00,00,00,65,00,00,00,73,00,00,00,54,00,00,00,6f,00,\
00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,\
00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,1c,00,00,00,57,00,00,00,61,00,00,00,74,00,00,\
00,63,00,00,00,68,00,00,00,65,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,\
6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,\
00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,00,00,16,00,00,00,48,00,00,00,69,00,00,00,64,00,00,00,65,\
00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,61,00,\
00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,22,00,00,00,53,00,\
00,00,74,00,00,00,72,00,00,00,75,00,00,00,63,00,00,00,74,00,00,00,45,00,00,\
00,6e,00,00,00,75,00,00,00,6d,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,\
6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,\
00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,1e,00,00,00,43,00,00,00,6f,00,00,00,6d,00,00,00,6d,00,00,00,65,\
00,00,00,6e,00,00,00,74,00,00,00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,\
00,00,6c,00,00,00,42,00,00,00,61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,\
00,ff,00,ff,00,ff,00,ff,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
00,00,00,00,00,00,18,00,00,00,56,00,00,00,69,00,00,00,65,00,00,00,77,00,00,\
00,73,00,00,00,54,00,00,00,6f,00,00,00,6f,00,00,00,6c,00,00,00,42,00,00,00,\
61,00,00,00,72,00,00,00,00,00,00,00,00,00,00,00,ff,00,ff,00,ff,00,ff,00,00,\
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,29,00
"QuickFilterFlags"=dword:00000000
"SplashWindow"=dword:00000000
"SupportExpiredDisplayed_2.0.0.140605"=dword:6a285788
"uiConfig"=hex:ff,ff,ff,ff,00,00,00,00,00,00,00,00,02,00,00,00,01,00,00,00,00,\
00,00,00,ff,ff,ff,ff,00,00,01,00,ff,ff,ff,ff,00,00,00,00,00,00,00,00,02,00,\
00,00,00,00,00,00,00,00,00,00,4d,00,01,05,00,00,00,00
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\Hidden Messages]
"Congratulations the TIL validator is active "=dword:00000001
"GOOD NEWS THE BTREE VALIDATOR v66 IS ACTIVE The btree validator plugin will now verify the input d"=dword:00000001
"The technical support period of the Hex Rays decompiler has expired You have 3 month grace period t"=dword:00000001
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\History]
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\History_A]
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\History_I]
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\History_UIFLTR]
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\Main Windows\Main window]
"Geometry"=hex:40,00,42,00,79,00,74,00,65,00,41,00,72,00,72,00,61,00,79,00,28,\
00,01,00,d9,00,d0,00,cb,00,00,00,01,00,00,00,00,00,00,00,00,00,01,00,e0,00,\
00,00,00,00,01,00,44,00,00,00,00,00,05,00,07,00,00,00,00,00,03,00,bd,00,00,\
00,00,00,01,00,e4,00,00,00,00,00,01,00,62,00,00,00,00,00,05,00,03,00,00,00,\
00,00,03,00,b9,00,00,00,00,00,00,00,00,00,00,00,00,00,29,00
"Maximized"=dword:00000001
[HKEY_CURRENT_USER\Software\Hex-Rays\IDA\RecentScripts]
; --- HKEY_LOCAL_MACHINE: File type associations ---
[HKEY_LOCAL_MACHINE\Software\Classes\.i64]
@="IDApro.Database64"
[HKEY_LOCAL_MACHINE\Software\Classes\.idb]
@="IDApro.Database32"
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database32]
@="IDA Database"
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database32\DefaultIcon]
@="C:\\Program Files\\IDA 6.6\\idaq.exe,0"
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database32\shell\open\command]
@="\"C:\\Program Files\\IDA 6.6\\idaq.exe\" \"%1\""
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database64]
@="IDA Pro (64-bit) Database"
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database64\DefaultIcon]
@="C:\\Program Files\\IDA 6.6\\idaq64.exe,0"
[HKEY_LOCAL_MACHINE\Software\Classes\IDApro.Database64\shell\open\command]
@="\"C:\\Program Files\\IDA 6.6\\idaq64.exe\" \"%1\""
[HKEY_LOCAL_MACHINE\Software\Classes\WinGraph.File]
@="WinGraph file"
[HKEY_LOCAL_MACHINE\Software\Classes\WinGraph.File\DefaultIcon]
@="C:\\Program Files\\IDA 6.6\\wingraph32.exe,0"
[HKEY_LOCAL_MACHINE\Software\Classes\WinGraph.File\shell\open\command]
@="\"C:\\Program Files\\IDA 6.6\\wingraph32.exe\" \"%1\""
; --- HKEY_LOCAL_MACHINE: Uninstall registration ---
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\IDA Pro_6.6_is1]
"DisplayIcon"="C:\\Program Files\\IDA 6.6\\idaq.exe"
"DisplayName"="IDA Pro v6.6 and Hex-Rays Decompiler (ARM,x64,x86)"
"EstimatedSize"=dword:00030f1b
"Inno Setup: App Path"="C:\\Program Files\\IDA 6.6"
"Inno Setup: Deselected Tasks"="desktopicon"
"Inno Setup: Icon Group"="(Default)"
"Inno Setup: Language"="default"
"Inno Setup: Selected Tasks"=""
"Inno Setup: Setup Version"="5.5.3 (a)"
"Inno Setup: User"="rov"
"InstallDate"="20260609"
"InstallLocation"="C:\\Program Files\\IDA 6.6\\"
"NoModify"=dword:00000001
"NoRepair"=dword:00000001
"Publisher"="Hex-Rays SA"
"QuietUninstallString"="\"C:\\Program Files\\IDA 6.6\\unins000.exe\" /SILENT"
"UninstallString"="\"C:\\Program Files\\IDA 6.6\\unins000.exe\""
; --- HKEY_LOCAL_MACHINE: DLL search behavior for IDA executables ---
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\idaq.exe]
"CWDIllegalInDllSearch"=dword:ffffffff
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\idaq64.exe]
"CWDIllegalInDllSearch"=dword:ffffffff
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\idaw.exe]
"CWDIllegalInDllSearch"=dword:ffffffff
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\idaw64.exe]
"CWDIllegalInDllSearch"=dword:ffffffff

Binary file not shown.

View File

@ -2,5 +2,6 @@ package ai.decompile.engine.model;
public enum EngineType { public enum EngineType {
IDA5, IDA5,
IDA66,
GHIDRA GHIDRA
} }

View File

@ -0,0 +1,382 @@
package ai.decompile.engine.service;
import ai.decompile.docker.service.ContainerService;
import ai.decompile.engine.config.EngineProperties;
import ai.decompile.engine.model.AnalysisResult;
import ai.decompile.engine.model.FunctionResult;
import ai.decompile.engine.model.LabelEntry;
import ai.decompile.engine.model.SegmentEntry;
import ai.decompile.engine.model.XrefEntry;
import com.github.dockerjava.api.model.Bind;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import lombok.extern.log4j.Log4j2;
@Log4j2
public abstract class AbstractIdaEngineService implements EngineService {
private static final String KEY_ENTRY_POINT = "\"entry_point\": \"";
private static final String KEY_ADDRESS = "\",\n \"address\": \"";
private static final String KEY_SIZE = "\",\n \"size\": ";
private static final String KEY_FLAGS = ",\n \"flags\": \"";
private static final String KEY_XREFS = "\",\n \"xrefs\": \"";
private static final String KEY_ASSEMBLY = "\",\n \"assembly\": \"";
private static final String KEY_LABELS = "\",\n \"labels\": \"";
private static final String KEY_ENTRY_END = "\"\n }";
private static final String KEY_SEGMENTS_START = "\"segments\": [";
private static final String KEY_SEGMENT_NAME = "\"name\": \"";
private static final String KEY_SEGMENT_START = "\", \"start\": \"";
private static final String KEY_SEGMENT_END = "\", \"end\": \"";
protected final ContainerService containers;
protected final EngineProperties engineProperties;
protected AbstractIdaEngineService(
ContainerService containers, EngineProperties engineProperties) {
this.containers = containers;
this.engineProperties = engineProperties;
}
protected abstract String getDockerfilePath();
protected abstract String getConfigKey();
protected abstract String getTempDirPrefix();
@Override
public AnalysisResult analyze(String filePath) {
var configKey = getConfigKey();
var config = engineProperties.getEngineConfig(configKey);
var absolutePath = Path.of(filePath).toAbsolutePath().normalize();
var filename = absolutePath.getFileName().toString();
containers.ensureImage(config.getImage(), new File(getDockerfilePath()));
var outputDir = createTempDir();
var containerId = createContainer(config.getImage(), filename, absolutePath, outputDir);
try {
var exitCode = containers.waitForExit(containerId, config.getTimeoutSeconds());
var logs = containers.captureLogs(containerId, 2);
validateExitCode(configKey, exitCode, logs);
var outputFile = validateOutputFile(configKey, outputDir, logs);
return parseOutput(readStringTolerant(outputFile));
} catch (Exception e) {
log.error("{} analysis failed for file {}: {}", configKey, filePath, e.getMessage());
throw new RuntimeException(configKey + " analysis failed: " + e.getMessage(), e);
} finally {
containers.stopAndRemove(containerId);
cleanupTempDir(outputDir);
}
}
private Path createTempDir() {
try {
return Files.createTempDirectory(getTempDirPrefix());
} catch (IOException e) {
throw new RuntimeException("Failed to create temporary output directory", e);
}
}
private String createContainer(String image, String filename, Path absolutePath, Path outputDir) {
var inputBind = Bind.parse(absolutePath.getParent() + ":/input:ro");
var outputBind = Bind.parse(outputDir.toAbsolutePath() + ":/output:rw");
var containerId =
containers.createContainer(image, new String[] {filename}, inputBind, outputBind);
containers.startContainer(containerId);
return containerId;
}
private static void validateExitCode(String configKey, int exitCode, String logs) {
if (exitCode != 0) {
log.error("{} container exited with code {}: {}", configKey, exitCode, logs);
throw new RuntimeException(
configKey + " analysis failed with exit code " + exitCode + ". Output: " + logs);
}
}
private static Path validateOutputFile(String configKey, Path outputDir, String logs) {
var outputFile = outputDir.resolve("output.json");
if (!Files.exists(outputFile)) {
throw new RuntimeException(
configKey + " analysis did not produce output.json. Container logs: " + logs);
}
return outputFile;
}
private static void cleanupTempDir(Path dir) {
try (var walk = Files.walk(dir)) {
walk.sorted(Comparator.reverseOrder()).forEach(AbstractIdaEngineService::deleteSilently);
} catch (IOException ignored) {
}
}
private static void deleteSilently(Path path) {
try {
Files.deleteIfExists(path);
} catch (IOException ignored) {
}
}
private static String readStringTolerant(Path path) throws IOException {
var decoder =
StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return decoder.decode(ByteBuffer.wrap(Files.readAllBytes(path))).toString();
}
private static AnalysisResult parseOutput(String raw) {
var text = raw.replace("\r\n", "\n");
var entryPoint = parseEntryPoint(text);
var segments = parseSegments(text);
var functions = new ArrayList<FunctionResult>();
var sections = text.split("\"function\"\\s*:\\s*\"");
for (int i = 1; i < sections.length; i++) {
var func = parseFunctionEntry(sections[i]);
if (Objects.nonNull(func)) {
functions.add(func);
}
}
if (functions.isEmpty()) {
log.warn(
"IDA produced no parsable function entries. Raw output (first 500 chars): {}",
raw.length() > 500 ? raw.substring(0, 500) : raw);
}
return new AnalysisResult(entryPoint, segments, functions);
}
private static String parseEntryPoint(String text) {
var entryRemainder = after(text, KEY_ENTRY_POINT);
if (Objects.isNull(entryRemainder)) {
return "unknown";
}
var entryVal = extractField(entryRemainder, "\"");
if (Objects.isNull(entryVal)) {
return "unknown";
}
return entryVal;
}
private static FunctionResult parseFunctionEntry(String section) {
var name = extractField(section, KEY_ADDRESS);
if (Objects.isNull(name)) {
return null;
}
var remainder = after(section, KEY_ADDRESS);
if (Objects.isNull(remainder)) {
return null;
}
var address = extractField(remainder, KEY_SIZE);
if (Objects.isNull(address)) {
return null;
}
remainder = after(remainder, KEY_SIZE);
if (Objects.isNull(remainder)) {
return null;
}
var sizeStr = extractField(remainder, KEY_FLAGS);
if (Objects.isNull(sizeStr)) {
return null;
}
var size = parseSize(sizeStr);
remainder = after(remainder, KEY_FLAGS);
if (Objects.isNull(remainder)) {
return null;
}
var flags = extractField(remainder, KEY_XREFS);
if (Objects.isNull(flags)) {
return null;
}
remainder = after(remainder, KEY_XREFS);
if (Objects.isNull(remainder)) {
return null;
}
var xrefsRaw = extractField(remainder, KEY_ASSEMBLY);
if (Objects.isNull(xrefsRaw)) {
return null;
}
remainder = after(remainder, KEY_ASSEMBLY);
if (Objects.isNull(remainder)) {
return null;
}
var assembly = extractField(remainder, KEY_LABELS);
if (Objects.isNull(assembly)) {
return null;
}
remainder = after(remainder, KEY_LABELS);
if (Objects.isNull(remainder)) {
return null;
}
var labelsRaw = extractField(remainder, KEY_ENTRY_END);
if (Objects.isNull(labelsRaw)) {
return null;
}
var xrefs = parseXrefs(xrefsRaw.trim());
var labels = parseLabels(labelsRaw.trim());
return new FunctionResult(
name,
address,
size,
flags.endsWith("|") ? flags.substring(0, flags.length() - 1) : flags,
assembly,
null,
null,
xrefs,
labels);
}
private static int parseSize(String sizeStr) {
try {
return Integer.parseInt(sizeStr.trim());
} catch (NumberFormatException e) {
return 0;
}
}
private static String extractField(String text, String endDelim) {
var end = text.indexOf(endDelim);
return end < 0 ? null : text.substring(0, end);
}
private static String after(String text, String delim) {
var idx = text.indexOf(delim);
return idx < 0 ? null : text.substring(idx + delim.length());
}
private static List<SegmentEntry> parseSegments(String text) {
var segments = new ArrayList<SegmentEntry>();
var remainder = after(text, KEY_SEGMENTS_START);
if (Objects.isNull(remainder)) {
return segments;
}
var segmentsBlock = extractField(remainder, "\n ],\n");
if (Objects.isNull(segmentsBlock)) {
return segments;
}
var parts = segmentsBlock.split("},\\s*\\{");
for (var part : parts) {
var segment = parseSegmentEntry(part);
if (Objects.nonNull(segment)) {
segments.add(segment);
}
}
return segments;
}
private static SegmentEntry parseSegmentEntry(String part) {
var clean = part.replace("{", "").replace("}", "").trim();
if (clean.isBlank()) {
return null;
}
var nameRemainder = after(clean, KEY_SEGMENT_NAME);
if (Objects.isNull(nameRemainder)) {
return null;
}
var name = extractField(nameRemainder, KEY_SEGMENT_START);
if (Objects.isNull(name)) {
return null;
}
var startRemainder = after(nameRemainder, KEY_SEGMENT_START);
if (Objects.isNull(startRemainder)) {
return null;
}
var startAddress = extractField(startRemainder, KEY_SEGMENT_END);
if (Objects.isNull(startAddress)) {
return null;
}
var endRemainder = after(startRemainder, KEY_SEGMENT_END);
if (Objects.isNull(endRemainder)) {
return null;
}
var endAddress = extractField(endRemainder, "\"");
if (Objects.isNull(endAddress)) {
return null;
}
return new SegmentEntry(name, startAddress, endAddress);
}
private static List<XrefEntry> parseXrefs(String raw) {
if (Objects.isNull(raw) || raw.isBlank()) {
return List.of();
}
return Arrays.stream(raw.trim().split("\\s+"))
.map(AbstractIdaEngineService::parseXrefEntry)
.toList();
}
private static XrefEntry parseXrefEntry(String token) {
var colon = token.lastIndexOf(':');
if (colon < 0) {
return new XrefEntry(token, "CALL");
}
return new XrefEntry(token.substring(0, colon), token.substring(colon + 1));
}
private static List<LabelEntry> parseLabels(String raw) {
if (Objects.isNull(raw) || raw.isBlank()) {
return List.of();
}
return Arrays.stream(raw.trim().split("\\s+"))
.distinct()
.map(AbstractIdaEngineService::parseLabelEntry)
.toList();
}
private static LabelEntry parseLabelEntry(String token) {
var lastColon = token.lastIndexOf(':');
if (lastColon < 0) {
return new LabelEntry(token, "", "CALL");
}
var type = token.substring(lastColon + 1);
var nameAddr = token.substring(0, lastColon);
var addrColon = nameAddr.lastIndexOf(':');
if (addrColon < 0) {
return new LabelEntry(nameAddr, "", type);
}
return new LabelEntry(
nameAddr.substring(0, addrColon), nameAddr.substring(addrColon + 1), type);
}
}

View File

@ -2,353 +2,34 @@ package ai.decompile.engine.service;
import ai.decompile.docker.service.ContainerService; import ai.decompile.docker.service.ContainerService;
import ai.decompile.engine.config.EngineProperties; import ai.decompile.engine.config.EngineProperties;
import ai.decompile.engine.model.AnalysisResult;
import ai.decompile.engine.model.EngineType; import ai.decompile.engine.model.EngineType;
import ai.decompile.engine.model.FunctionResult;
import ai.decompile.engine.model.LabelEntry;
import ai.decompile.engine.model.SegmentEntry;
import ai.decompile.engine.model.XrefEntry;
import com.github.dockerjava.api.model.Bind;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Log4j2
@Service @Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true") @ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
public class Ida5EngineService implements EngineService { public class Ida5EngineService extends AbstractIdaEngineService {
private static final String DOCKERFILE_PATH = "engines/ida5/Dockerfile.ida5"; public Ida5EngineService(ContainerService containers, EngineProperties engineProperties) {
private static final String KEY_ENTRY_POINT = "\"entry_point\": \""; super(containers, engineProperties);
private static final String KEY_ADDRESS = "\",\n \"address\": \""; }
private static final String KEY_SIZE = "\",\n \"size\": ";
private static final String KEY_FLAGS = ",\n \"flags\": \"";
private static final String KEY_XREFS = "\",\n \"xrefs\": \"";
private static final String KEY_ASSEMBLY = "\",\n \"assembly\": \"";
private static final String KEY_LABELS = "\",\n \"labels\": \"";
private static final String KEY_ENTRY_END = "\"\n }";
private static final String KEY_SEGMENTS_START = "\"segments\": [";
private static final String KEY_SEGMENT_NAME = "\"name\": \"";
private static final String KEY_SEGMENT_START = "\", \"start\": \"";
private static final String KEY_SEGMENT_END = "\", \"end\": \"";
private final ContainerService containers;
private final EngineProperties engineProperties;
@Override @Override
public AnalysisResult analyze(String filePath) { protected String getDockerfilePath() {
var config = engineProperties.getEngineConfig("ida5"); return "engines/ida5/Dockerfile.ida5";
var absolutePath = Path.of(filePath).toAbsolutePath().normalize(); }
var filename = absolutePath.getFileName().toString();
containers.ensureImage(config.getImage(), new File(DOCKERFILE_PATH)); @Override
protected String getConfigKey() {
return "ida5";
}
Path outputDir = createTempDir(); @Override
var containerId = createContainer(config.getImage(), filename, absolutePath, outputDir); protected String getTempDirPrefix() {
return "ida5-output-";
try {
var exitCode = containers.waitForExit(containerId, config.getTimeoutSeconds());
var logs = containers.captureLogs(containerId, 2);
validateExitCode(exitCode, logs);
var outputFile = validateOutputFile(outputDir, logs);
return parseOutput(readStringTolerant(outputFile));
} catch (Exception e) {
log.error("IDA5 analysis failed for file {}: {}", filePath, e.getMessage());
throw new RuntimeException("IDA5 analysis failed: " + e.getMessage(), e);
} finally {
containers.stopAndRemove(containerId);
cleanupTempDir(outputDir);
}
} }
@Override @Override
public EngineType supportedEngine() { public EngineType supportedEngine() {
return EngineType.IDA5; return EngineType.IDA5;
} }
private static Path createTempDir() {
try {
return Files.createTempDirectory("ida5-output-");
} catch (IOException e) {
throw new RuntimeException("Failed to create temporary output directory", e);
}
}
private String createContainer(String image, String filename, Path absolutePath, Path outputDir) {
var inputBind = Bind.parse(absolutePath.getParent() + ":/input:ro");
var outputBind = Bind.parse(outputDir.toAbsolutePath() + ":/output:rw");
var containerId =
containers.createContainer(image, new String[] {filename}, inputBind, outputBind);
containers.startContainer(containerId);
return containerId;
}
private static void validateExitCode(int exitCode, String logs) {
if (exitCode != 0) {
log.error("IDA5 container exited with code {}: {}", exitCode, logs);
throw new RuntimeException(
"IDA5 analysis failed with exit code " + exitCode + ". Output: " + logs);
}
}
private static Path validateOutputFile(Path outputDir, String logs) {
var outputFile = outputDir.resolve("output.json");
if (!Files.exists(outputFile)) {
throw new RuntimeException(
"IDA5 analysis did not produce output.json. Container logs: " + logs);
}
return outputFile;
}
private static void cleanupTempDir(Path dir) {
try (var walk = Files.walk(dir)) {
walk.sorted(Comparator.reverseOrder()).forEach(Ida5EngineService::deleteSilently);
} catch (IOException ignored) {
}
}
private static void deleteSilently(Path path) {
try {
Files.deleteIfExists(path);
} catch (IOException ignored) {
}
}
private static String readStringTolerant(Path path) throws IOException {
var decoder =
StandardCharsets.UTF_8
.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return decoder.decode(ByteBuffer.wrap(Files.readAllBytes(path))).toString();
}
private static AnalysisResult parseOutput(String raw) {
var text = raw.replace("\r\n", "\n");
var entryRemainder = after(text, KEY_ENTRY_POINT);
var entryPoint = "unknown";
if (Objects.nonNull(entryRemainder)) {
var entryVal = extractField(entryRemainder, "\"");
if (Objects.nonNull(entryVal)) {
entryPoint = entryVal;
}
}
var segments = parseSegments(text);
var functions = new ArrayList<FunctionResult>();
var sections = text.split("\"function\"\\s*:\\s*\"");
for (int i = 1; i < sections.length; i++) {
var section = sections[i];
var name = extractField(section, KEY_ADDRESS);
if (name == null) {
continue;
}
var remainder = after(section, KEY_ADDRESS);
if (remainder == null) {
continue;
}
var address = extractField(remainder, KEY_SIZE);
if (address == null) {
continue;
}
remainder = after(remainder, KEY_SIZE);
if (remainder == null) {
continue;
}
var sizeStr = extractField(remainder, KEY_FLAGS);
if (sizeStr == null) {
continue;
}
int size;
try {
size = Integer.parseInt(sizeStr.trim());
} catch (NumberFormatException e) {
size = 0;
}
remainder = after(remainder, KEY_FLAGS);
if (remainder == null) {
continue;
}
var flags = extractField(remainder, KEY_XREFS);
if (Objects.isNull(flags)) {
continue;
}
remainder = after(remainder, KEY_XREFS);
if (remainder == null) {
continue;
}
var xrefsRaw = extractField(remainder, KEY_ASSEMBLY);
if (xrefsRaw == null) {
continue;
}
remainder = after(remainder, KEY_ASSEMBLY);
if (remainder == null) {
continue;
}
var assembly = extractField(remainder, KEY_LABELS);
if (assembly == null) {
continue;
}
remainder = after(remainder, KEY_LABELS);
if (remainder == null) {
continue;
}
var labelsRaw = extractField(remainder, KEY_ENTRY_END);
if (labelsRaw == null) {
continue;
}
var xrefs = parseXrefs(xrefsRaw.trim());
var labels = parseLabels(labelsRaw.trim());
functions.add(
new FunctionResult(
name,
address,
size,
flags.endsWith("|") ? flags.substring(0, flags.length() - 1) : flags,
assembly,
null,
null,
xrefs,
labels));
}
if (functions.isEmpty()) {
log.warn(
"IDA5 produced no parsable function entries. Raw output (first 500 chars): {}",
raw.length() > 500 ? raw.substring(0, 500) : raw);
}
return new AnalysisResult(entryPoint, segments, functions);
}
private static String extractField(String text, String endDelim) {
int end = text.indexOf(endDelim);
return end < 0 ? null : text.substring(0, end);
}
private static String after(String text, String delim) {
int idx = text.indexOf(delim);
return idx < 0 ? null : text.substring(idx + delim.length());
}
private static List<SegmentEntry> parseSegments(String text) {
var segments = new ArrayList<SegmentEntry>();
var remainder = after(text, KEY_SEGMENTS_START);
if (Objects.isNull(remainder)) {
return segments;
}
var segmentsBlock = extractField(remainder, "\n ],\n");
if (Objects.isNull(segmentsBlock)) {
return segments;
}
var parts = segmentsBlock.split("},\\s*\\{");
for (var part : parts) {
var clean = part.replace("{", "").replace("}", "").trim();
if (clean.isBlank()) {
continue;
}
var nameRemainder = after(clean, KEY_SEGMENT_NAME);
if (Objects.isNull(nameRemainder)) {
continue;
}
var name = extractField(nameRemainder, KEY_SEGMENT_START);
if (Objects.isNull(name)) {
continue;
}
var startRemainder = after(nameRemainder, KEY_SEGMENT_START);
if (Objects.isNull(startRemainder)) {
continue;
}
var startAddress = extractField(startRemainder, KEY_SEGMENT_END);
if (Objects.isNull(startAddress)) {
continue;
}
var endRemainder = after(startRemainder, KEY_SEGMENT_END);
if (Objects.isNull(endRemainder)) {
continue;
}
var endAddress = extractField(endRemainder, "\"");
if (Objects.isNull(endAddress)) {
continue;
}
segments.add(new SegmentEntry(name, startAddress, endAddress));
}
return segments;
}
private static List<XrefEntry> parseXrefs(String raw) {
if (Objects.isNull(raw) || raw.isBlank()) {
return List.of();
}
return Arrays.stream(raw.trim().split("\\s+")).map(Ida5EngineService::parseXrefEntry).toList();
}
private static XrefEntry parseXrefEntry(String token) {
var colon = token.lastIndexOf(':');
if (colon < 0) {
return new XrefEntry(token, "CALL");
}
return new XrefEntry(token.substring(0, colon), token.substring(colon + 1));
}
private static List<LabelEntry> parseLabels(String raw) {
if (raw == null || raw.isBlank()) {
return List.of();
}
return Arrays.stream(raw.trim().split("\\s+"))
.distinct()
.map(Ida5EngineService::parseLabelEntry)
.toList();
}
private static LabelEntry parseLabelEntry(String token) {
var lastColon = token.lastIndexOf(':');
if (lastColon < 0) {
return new LabelEntry(token, "", "CALL");
}
var type = token.substring(lastColon + 1);
var nameAddr = token.substring(0, lastColon);
var addrColon = nameAddr.lastIndexOf(':');
if (addrColon < 0) {
return new LabelEntry(nameAddr, "", type);
}
return new LabelEntry(
nameAddr.substring(0, addrColon), nameAddr.substring(addrColon + 1), type);
}
} }

View File

@ -0,0 +1,35 @@
package ai.decompile.engine.service;
import ai.decompile.docker.service.ContainerService;
import ai.decompile.engine.config.EngineProperties;
import ai.decompile.engine.model.EngineType;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnProperty(name = "app.docker.enabled", havingValue = "true")
public class Ida66EngineService extends AbstractIdaEngineService {
public Ida66EngineService(ContainerService containers, EngineProperties engineProperties) {
super(containers, engineProperties);
}
@Override
protected String getDockerfilePath() {
return "engines/ida66/Dockerfile.ida66";
}
@Override
protected String getConfigKey() {
return "ida66";
}
@Override
protected String getTempDirPrefix() {
return "ida66-output-";
}
@Override
public EngineType supportedEngine() {
return EngineType.IDA66;
}
}

View File

@ -54,6 +54,9 @@ app:
ida5: ida5:
image: decompile-ai/ida5:latest image: decompile-ai/ida5:latest
timeout-seconds: 300 timeout-seconds: 300
ida66:
image: decompile-ai/ida66:latest
timeout-seconds: 300
ghidra: ghidra:
image: decompile-ai/ghidra:latest image: decompile-ai/ghidra:latest
timeout-seconds: 600 timeout-seconds: 600