A universal interface for running scripts under multiple disassemblers.
Project description
Dragodis
Dragodis is a Python framework which allows for the creation of universal disassembler scripts. Dragodis currently only supports IDA and Ghidra, but has plans to support additional disassemblers in the future. Dragodis only supports Python 3.
The name Dragodis
comes from the combination of Dragoman
, a professional
interpreter, and Disassembler
.
Dragodis was created due to a need of the ability to run IDA scripts in Ghidra. Many scripts for automated analysis will work fine in most disassemblers. Eliminating the need to use disassemblers which require licenses for automated analysis is ideal.
There are other benefits of a universal disassembler API as well. Many reverse engineers have a preferred disassembler. Dragodis allows for simple transfers of scripts between users of different disassemblers. Dragodis also aims to provide a cleaner and easier to use API than those provided by other disassemblers.
Install
Use pip to install dragodis:
pip install dragodis
Then follow the instructions here to install a backend disassembler.
Usage
To use Dragodis, simply pass in the path to your input binary file into either the IDA
or Ghidra
class.
This will create an instance of the disassembler with the given input file analyzed.
import dragodis
with dragodis.Ghidra(r"C:\strings.exe") as ghidra:
print(ghidra.get_dword(0x401000))
import dragodis
with dragodis.IDA(r"C:\strings.exe") as ida:
print(ida.get_dword(0x401000))
A disassembler can also be run without using a context manager using the start()
and stop()
functions.
import dragodis
ghidra = dragodis.Ghidra(r"C:\strings.exe")
ghidra.start()
ghidra.get_dword(0x401000)
ghidra.stop()
Alternatively, you can use open_program()
to choose the disassembler more dynamically by providing
the disassembler name in the disassembler
parameter or by setting the DRAGODIS_DISASSEMBLER
environment variable.
import dragodis
with dragodis.open_program(r"C:\strings.exe", disassembler="ida") as ida:
print(ida.get_dword(0x401000))
It is highly recommended to use the DRAGODIS_DISASSEMBLER
environment variable to ensure your scripts
are cross compatible without any modification. As well, to give the user the power to choose
which disassembler they would like to use.
A "NotInstalledError" will be thrown if the disassembler chosen is not properly installed.
import os
os.environ["DRAGODIS_DISASSEMBLER"] = "ida"
import dragodis
with dragodis.open_program(r"C:\strings.exe") as dis:
print(f"Disassembler used: {dis.name}")
print(dis.get_dword(0x401000))
If you are locally within the disassembler's interpreter (the output window for IDA or pyhidraw interpreter in Ghidra) then you can initialize a disassembler object by directly acccessing the object:
# If in IDA
import dragodis
dis = dragodis.IDA()
# If in Ghidra
import dragodis
dis = dragodis.Ghidra()
We can also directly call scripts using the open_program()
function locally in the disassembler.
When this happens, the input file path provided must match the detected input file path by the disassembler.
Specifying Processor Type
The processor type can be specified during initialization of the Disassembler
object or through open_program()
.
This can be useful when loading shellcode.
When using open_program()
with any backend disassembler supported, use a dragodis.PROCESSOR_*
flag which will get converted
to a sane default for the respective disassembler.
import dragodis
with dragodis.open_program(r"C:\input.exe", processor=dragodis.PROCESSOR_ARM) as dis:
...
If using a specific disassembler, any option that disassembler supports can be passed in. (Consult the documentation for the respective disassembler to know how to format the argument.)
# IDA
import dragodis
ida = dragodis.IDA(r"C:\input", processor="arm:ARMv7-M")
# Ghidra
import dragodis
ghidra = dragodis.Ghidra(r"C:\input", processor="ARM:LE:32:v7")
Alternatively, we can automatically choose the correct processor for the default disassembler chosen by the user with some initial checks.
import dragodis
PROCESSOR = {
dragodis.BACKEND_IDA: "arm:ARMv7-M",
dragodis.BACKEND_GHIDRA: "ARM:LE:32:v7",
}[dragodis.BACKEND_DEFAULT]
with dragodis.open_program(r"C:\input", processor=PROCESSOR) as dis:
...
Disassembler API Translation Map
As a reference, the following tables provide a rough mapping between the general equivalent API calls for Dragodis and each supported disassembler.
NOTE: These are rough translations between equivalent disassembler API functions to help you transition from using either IDA or Ghidra to using Dragodis. They are not always direct translations. For brevity, some details and differences in results get glossed over here. Please see the source code for more information.
Basics
Dragodis* | IDA | Ghidra |
---|---|---|
dis.processor_name | ida_ida.inf_get_procname() | currentProgram.getLanguage().getProcessor() |
dis.compiler_name | ida_typeinf.get_compiler_name(ida_ida.inf_get_cc_id()) | currentProgram.getCompiler() |
dis.bit_size | ida_ida.inf_get_app_bitness() | currentProgram.getDefaultPointerSize() * 8 |
dis.is_big_endian | ida_ida.inf_is_be() | currentProgram.getLanguage().isBigEndian() |
dis.min_address | ida_ida.inf_get_min_ea() | currentProgram.getMinAddress() |
dis.max_address | ida_ida.inf_get_max_ea() | currentProgram.getMaxAddress() |
dis.base_address | ida_nalt.get_imagebase() | currentProgram.getImageBase() |
dis.get_virtual_address(file_offset) | ida_loader.get_fileregion_ea(file_offset) | currentProgram.getMemory().locateAddressesForFileOffset(file_offset) |
dis.get_file_offset(address) | ida_loader.get_fileregion_offset(address) | currentProgram.getMemory().getAddressSourceInfo(toAddr(address)).getFileOffset() |
dis.entry_point | ida_ida.inf_get_start_ip() | (export with name "entry" or "_start") |
Data
Dragodis* | IDA | Ghidra |
---|---|---|
dis.get_byte(address) | ida_bytes.get_wide_byte(address) | getByte(toAddr(address)) |
dis.get_bytes(address, size) | ida_bytes.get_bytes(address, size) | getBytes(toAddr(address), size) |
dis.find_bytes(b"\xde\xad\xbe\xef", start) | idc.find_binary(start, idc.SEARCH_DOWN, "DE AD BE EF") | currentProgram.getMemory().findBytes(start, b"\xde\xad\xbe\xef", None, True, monitor) |
dis.find_bytes(b"\xde\xad\xbe\xef", start, reverse=True) | idc.find_binary(start, idc.SEARCH_UP, "DE AD BE EF") | currentProgram.getMemory().findBytes(start, b"\xde\xad\xbe\xef", None, False, monitor) |
dis.get_word(address) | ida_bytes.get_wide_word(address) | getShort(toAddr(address)) |
dis.get_dword(address) | ida_bytes.get_wide_dword(address) | getInt(toAddr(address)) |
dis.get_qword(address) | ida_bytes.get_qword(address) | getLong(toAddr(address)) |
dis.get_string_bytes(address) | idc.get_strlit_contents(address) | complex: see source code |
dis.lines(start_address, end_address) dis.line_addresses(start_address, end_address) |
idautils.Heads(start_address, end_address) | currentProgram.getListing().getCodeUnits(address_set, True) |
line = dis.get_line(address) | N/A | code_unit = currentProgram.getListing().getCodeUnitContaining(toAddr(address)) |
line.address dis.get_line_address(address) |
idc.get_item_head(address) | code_unit.getAddress() |
line.name dis.get_name(address) |
ida_name.get_name(address) | code_unit.getLabel() |
line.name = "new_name" | ida_name.set_name(address, "new_name") | symbol = code_unit.getPrimarySymbol(); symbol.setName("new_name", symbol.getSource()) |
line.size | ida_bytes.get_item_size(address) | code_unit.getLength() |
line.type | ida_bytes.get_flags(address) | code_unit.getClass() code_unit.getDataType().getName() |
line.type = LineType.dword | idc.create_dword(address) | createDWord(address) |
line.data | ida_bytes.get_bytes(address, ida_bytes.get_item_size(address)) | code_unit.getBytes() |
line.data = b"new data" | ida_bytes.patch_bytes(address, b"new data") | setBytes(code_unit.getAddress(), b"new data") |
line.get_comment() dis.get_comment(address) |
ida_bytes.get_cmt(address, 0) | code_unit.getComment(0) |
line.set_comment("new comment") | ida_bytes.set_cmt(address, "new comment", 0) | code_unit.setComment(0, "new comment") |
line.next dis.next_line_address(address) |
idc.next_head(address) | currentProgram.getListing().getCodeUnitAfter(code_unit.getAddress()) |
line.prev dis.prev_line_address(address) |
idc.prev_head(address) | currentProgram.getListing().getCodeUnitBefore(code_unit.getAddress()) |
line.undefine() | ida_bytes.del_items(address) | clearListing(code_unit.getAddress()) |
line.value | ida_bytes.get_wide_byte(address) ida_bytes.get_wide_word(address) etc. |
code_unit.getValue() |
line.value = new_value | ida_bytes.patch_byte(address, new_value) ida_bytes.patch_word(address, new_value) ida_bytes.patch_dword(address, new_value) etc. |
setByte(code_unit.getAddress(), new_value) setShort(code_unit.getAddress(), new_value) setInt(code_unit.getAddress(), new_value) etc. |
data_type = dis.get_data_type("dword") | data_type = ida_typeinf.tinfo_t(); data_type.get_named_type(ida_typeinf.get_idati(), "dword") | data_type = DataTypeParser(None, DataTypeParser.AllowedDataTypes.valueOf("ALL")).parse("dword") |
data_type.name | N/A | data_type.getName() |
data_type.size | ida_bytes.get_data_elsize(address, ida_bytes.get_flags(address) & ida_bytes.DT_TYPE) | data_type.getLength() |
dis.undefine(start, end) | ida_bytes.del_items(start, ida_bytes.DELIT_SIMPLE, end - start) | clearListing(toAddr(start), toAddr(end)) |
Cross-References
Dragodis* | IDA | Ghidra |
---|---|---|
dis.references_from(address) dis.get_line(address).references_from dis.get_function(address).references_from |
idautils.XrefsFrom(address) | getReferencesFrom(toAddr(address)) |
dis.references_to(address) dis.get_line(address).references_to dis.get_function(address).references_to |
idautils.XrefsTo(address) | getReferencesTo(toAddr(address)) |
ref.from_address | ref.frm | ref.getFromAddress() |
ref.to_address | ref.to | ref.getToAddress() |
ref.type | ref.type | ref.getReferenceType() |
ref.is_code | ref.iscode | not ref.getReferenceType().isData() |
ref.is_data | not ref.iscode | ref.getReferenceType().isData() |
dis.create_reference(from_address, to_address, dragodis.ReferenceType.*) | ida_xref.add_cref(from_address, to_address, idc.fl_) ida_xref.add_dref(from_address, to_address, idc.dr_) |
currentProgram.getReferenceManager().addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.*, SourceType.USER_DEFINED, 0) |
Imports/Exports
Dragodis* | IDA | Ghidra |
---|---|---|
dis.imports | [ida_nalt.enum_import_names(i, callback) for i in range(ida_nalt.get_import_module_qty())] | currentProgram.getSymbolTable().getExternalSymbols() |
dis.exports | [ida_entry.get_entry_ordinal(i) for i in range(ida_entry.get_entry_qty())] | currentProgram.getSymbolTable().getExternalEntryPointIterator() |
import_.address | returned in callback | symbol.getAddress() |
import_.name | returned in callback | symbol.getName() |
import_.namespace | ida_nalt.get_import_module_name(i) | symbol.getParentSymbol().getName() |
import_.references_to | idautils.XrefsTo(address) | symbol.getReferences() |
export.address | ida_entry.get_entry(ordinal) | symbol.getAddress() |
export.name | ida_entry.get_entry_name(ordinal) | symbol.getName() |
export.references_to | idautils.XrefsTo(ida_entry.get_entry(ordinal)) | symbol.getReferences() |
Functions
Dragodis* | IDA | Ghidra |
---|---|---|
dis.functions() | idautils.Functions() | currentProgram.getListing().getFunctions(True) |
func = dis.get_function(address) | func = ida_funcs.get_func(address) | func = getFunctionContaining(toAddr(address)) |
func.start | func.start_ea | func.getEntryPoint() |
func.end | func.end_ea | func.getBody().getMaxAddress() |
func.name dis.get_name(address) |
ida_funcs.get_func_name(address) | func.getName() |
func.name = "new_name" | ida_name.set_name(address, "new_name") | func.setName("new_name", SourceType.USER_DEFINED) |
func.get_comment() | ida_funcs.get_func_cmt(func, 0) | func.getComment() |
func.set_comment("new comment") | ida_funcs.set_func_cmt(func, "new comment", 0) | func.setComment("new comment") |
dis.get_flowchart(address) func.flowchart |
ida_gdl.FlowChart(func) | BasicBlockModel(currentProgram).getCodeBlocksContaining(func.getBody(), monitor) |
func.stack_frame | ida_frame.get_frame(func) | func.getStackFrame() |
dis.get_function_signature(address) func.signature |
tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, address) func_type_data = ida_typeinf.func_type_data_t() tif.get_func_details(func_type_data) |
func.getSignature() |
func = dis.create_function(start, end) | func = ida_funcs.add_func(start, end) | func = currentProgram.getFunctionManager().createFunction(None, toAddr(start), AddressSet(toAddr(start), toAddr(end)), SourceType.USER_DEFINED) |
func = dis.create_function(start) | func = ida_funcs.add_func(start) | func = CreateFunctionCmd(toAddr(start), True).applyTo(currentProgram).getFunction() |
func.undefine() | ida_funcs.del_func(start) | DeleteFunctionCmd(func.getEntryPoint(), True).applyTo(currentProgram) |
Instructions
Dragodis* | IDA | Ghidra |
---|---|---|
insn = dis.get_instruction(address) | insn = ida_ua.insn_t(); ida_ua.decode_insn(insn, address) | insn = currentProgram.getListing().getCodeUnitAt(toAddr(address)) |
insn.is_call | ida_idp.is_call_insn(insn) | insn.getFlowType().isCall() |
insn.is_jump | complex: see source code | insn.getFlowType().isJump() |
insn.is_return | ida_idp.is_ret_insn(insn) | insn.getFlowType().isTerminal() |
insn.mnemonic dis.get_mnemonic(address) |
ida_ua.ua_mnem(address) | insn.getMnemonicString() |
insn.text | idc.GetDisasm(address) | str(insn) |
insn.operands | insn.ops | N/A: See operands section |
insn.stack_depth | idc.get_spd(address) | CallDepthChangeInfo(currentProgram.getListing().getFunctionContaining(insn.getAddress())).getDepth() |
insn.stack_delta | idc.get_sp_delta(address) | complex: see source code |
Operands
insn
pulled as described above
Dragodis* | IDA | Ghidra |
---|---|---|
operand = dis.get_operand(address, index) operand = insn.operands[index] |
operand = insn.ops[index] | N/A |
operand.type dis.get_operand_type(address, index) |
idc.get_operand_type(address, index) | insn.getOperandType(index) |
operand.width | ida_ua.get_dtype_size(operand.dtype) | complex: see source code |
operand.value dis.get_operand_value(address, index) |
(depends on type) operand.addr operand.reg operand.value |
insn.getOpObjects(index) |
(phrase operand) operand.value.base |
operand.reg ida_intel.x86_base_reg(insn, operand) |
insn.getOpObjects(index)[0] |
(phrase operand) operand.value.index |
ida_intel.x86_index_reg(insn, operand) | insn.getOpObjects(index)[1] |
(phrase operand) operand.value.scale |
1 << ida_intel.sib_scale(operand) | insn.getOpObjects(index)[2] |
(phrase operand) operand.value.offset |
operand.addr idc.get_operand_value(address, index) |
insn.getOpObjects(index)[3] |
(register operand) operand.value |
operand.reg idc.get_operand_value(address, index) |
insn.getOpObjects(index)[0] |
(immediate operand) operand.value |
operand.value idc.get_operand_value(address, index) |
insn.getOpObjects(index)[0] |
(memory reference operand) operand.value |
operand.addr idc.get_operand_value(address, index) |
insn.getOpObjects(index)[0] |
Registers
Dragodis* | IDA | Ghidra |
---|---|---|
register = dis.get_register("eax") | register = ida_idp.reg_info_t(); ida_idp.parse_reg_name(register, "eax") | register = currentProgram.getRegister("eax") |
register.name | ida_idp.get_reg_name(register.reg, register.size) | register.getName() |
register.bit_width | register.size * 8 | register.getBitLength() |
Segments
Dragodis* | IDA | Ghidra |
---|---|---|
dis.segments | [ida_segment.getnseg(n) for n in range(ida_segment.get_segm_qty())] | currentProgram.getMemory().getBlocks() |
segment = dis.get_segment(name) segment = dis.get_segment(address) |
segment = ida_segment.get_segm_by_name(name) segment = ida_segment.getseg(address) |
memory_block = currentProgram.getMemory().getBlock(name) memory_block = currentProgram.getMemory().getBlock(toAddr(address)) |
segment.name | ida_segment.get_segm_name(segment) | memory_block.getName() |
segment.start | segment.start_ea | memory_block.getStart() |
segment.end | segment.end_ea | memory_block.getEnd() |
segment.initialized | ida_bytes.is_loaded(segment.start_ea) | memory_block.isInitialized() |
segment.bit_size | segment.abits() | memory_block.getStart().getSize() |
segment.permissions | segment.perm | memory_block.isRead() memory_block.isWrite() memory_block.isExecute() memory_block.isVolatile() |
dis.create_segment(".new_seg", 0x1234, 256) | ida_segment.add_segm(0, 0x1234, 0x1334, ".new_seg", "XTRN") | currentProgram.getMemory().createUninitializedBlock(".new_seg", toAddr(0x1234), 256, False) |
Strings
Dragodis* | IDA | Ghidra |
---|---|---|
dis.strings(min_length=5) | finder = idautils.Strings(); finder.setup(minlen=5); list(finder) | findStrings(None, 5, 1, False, True) |
* dis
in the dragodis column represents the open disassembler object retrieved from dragodis.open_program()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file dragodis-1.0.0.tar.gz
.
File metadata
- Download URL: dragodis-1.0.0.tar.gz
- Upload date:
- Size: 128.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.11.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3df2ed51b3772ee4cf9792bedfd47758318c4c37d67e0b7b4a7bb86fc41d5d48 |
|
MD5 | 5bbd6fdf8639cf138a42227cccfab068 |
|
BLAKE2b-256 | 49dae74e7a9ee89b1f30064822afc0c6a2e3f6714e21910717f79a67701d42e3 |
File details
Details for the file dragodis-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: dragodis-1.0.0-py3-none-any.whl
- Upload date:
- Size: 126.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.0 CPython/3.11.9
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e134b71137e3beede82d71676b72a8d7ade4e1db9829e1c3f86357d3ca91083a |
|
MD5 | 74a4534c3573c7a9fa0dbae23c54cf69 |
|
BLAKE2b-256 | 1963296f0f5aa475cb459def3f369f39504afbdb1ac3d9d43c3cd7d6e3e72a0c |