The minsc plugin at https://github.com/arizvisa/ida-minsc supports structure/union decoding, arithmetic, and layouting. This can be used to print out a manually defined structure
Disclaimer: I'm the maintainer of the minsc plugin.
As an example, I just looked up some structure that had some data already defined. To fetch the structure associated with a specific address, the database.type.structure
function can be used. The structure that I'm using has the following layout (and just happens to be variable-length).
Python>db.t.struc(0x29748)
<class 'structure' name='_EH4_SCOPETABLE' offset=0x29748 size=0x10>
Python>_.members
<class 'structure' name='_EH4_SCOPETABLE' offset=0x29748 size=0x10>
[0] 29748+0x4 DWORD 'GSCookieOffset' (<class 'int'>, 4)
[1] 2974c+0x4 DWORD 'GSCookieXOROffset' (<class 'int'>, 4)
[2] 29750+0x4 DWORD 'EHCookieOffset' (<class 'int'>, 4)
[3] 29754+0x4 DWORD 'EHCookieXOROffset' (<class 'int'>, 4)
[4] 29758+0x0 struct _EH4_SCOPETABLE_RECORD[] 'ScopeRecord' [<class 'structure' name='_EH4_SCOPETABLE_RECORD' offset=0x29758 size=0xc>, 0]
Python>db.t.struc().up()
[ref_t(address=0x29748, access=access_t(r)), ref_t(address=0x29768, access=access_t(r))]
It's worth noting that the members_t
(accessed by the structure_t.members
property) supports basic arithmetic (for translating the containing fields to an address/offset), slice-assignments (for copying members/types), or contiguous layout.
Python>db.t.struc().members * 3
[<class 'structure' name='_EH4_SCOPETABLE' offset=0x29748 size=0x10>
[0] 29748+0x4 DWORD 'GSCookieOffset' (<class 'int'>, 4)
[1] 2974c+0x4 DWORD 'GSCookieXOROffset' (<class 'int'>, 4)
[2] 29750+0x4 DWORD 'EHCookieOffset' (<class 'int'>, 4)
[3] 29754+0x4 DWORD 'EHCookieXOROffset' (<class 'int'>, 4)
[4] 29758+0x0 struct _EH4_SCOPETABLE_RECORD[] 'ScopeRecord' [<class 'structure' name='_EH4_SCOPETABLE_RECORD' offset=0x29758 size=0xc>, 0], <class 'structure' name='_EH4_SCOPETABLE' offset=0x29758 size=0x10>
[0] 29758+0x4 DWORD 'GSCookieOffset' (<class 'int'>, 4)
[1] 2975c+0x4 DWORD 'GSCookieXOROffset' (<class 'int'>, 4)
[2] 29760+0x4 DWORD 'EHCookieOffset' (<class 'int'>, 4)
[3] 29764+0x4 DWORD 'EHCookieXOROffset' (<class 'int'>, 4)
[4] 29768+0x0 struct _EH4_SCOPETABLE_RECORD[] 'ScopeRecord' [<class 'structure' name='_EH4_SCOPETABLE_RECORD' offset=0x29768 size=0xc>, 0], <class 'structure' name='_EH4_SCOPETABLE' offset=0x29768 size=0x10>
[0] 29768+0x4 DWORD 'GSCookieOffset' (<class 'int'>, 4)
[1] 2976c+0x4 DWORD 'GSCookieXOROffset' (<class 'int'>, 4)
[2] 29770+0x4 DWORD 'EHCookieOffset' (<class 'int'>, 4)
[3] 29774+0x4 DWORD 'EHCookieXOROffset' (<class 'int'>, 4)
[4] 29778+0x0 struct _EH4_SCOPETABLE_RECORD[] 'ScopeRecord' [<class 'structure' name='_EH4_SCOPETABLE_RECORD' offset=0x29778 size=0xc>, 0]]
As per the answer to your question, decoding structures can be done using the database.get.structure
function from the database.get
namespace. A related function, database.get.array
, can also be used if you want to decode a variable amount of data at a given address.
These functions return structure values as a python dictionary which means that if you want to perform an action based on the specific type associated with the value, the field name would need to be correlated with the field information from the member_t
that was fetched using database.type.structure
.
Python>db.get.struc()
{'EHCookieOffset': 0xffffffc0, 'EHCookieXOROffset': 0x0, 'GSCookieOffset': 0xfffffffe, 'GSCookieXOROffset': 0x0, 'ScopeRecord': [{'EnclosingLevel': 0xfffffffe, 'FilterFunc': 0x113d6, 'HandlerFunc': 0x113e4}]}
Python>pp(db.get.struc())
{'EHCookieOffset': 4294967232,
'EHCookieXOROffset': 0,
'GSCookieOffset': 4294967294,
'GSCookieXOROffset': 0,
'ScopeRecord': [{'EnclosingLevel': 4294967294,
'FilterFunc': 70614,
'HandlerFunc': 70628}]}
Python>db.disasm()
'29768: _EH4_SCOPETABLE <0FFFFFFFEh, 0, 0FFFFFFC8h, 0, <0FFFFFFFEh, \\'
# Combine both to print out each field name and their value.
Python>decoded = db.get.struc()
Python>for m in db.t.struc().members:
Python> print([m.name, decoded[m.name]])
Python>
['GSCookieOffset', 4294967294]
['GSCookieXOROffset', 0]
['EHCookieOffset', 4294967240]
['EHCookieXOROffset', 0]
['ScopeRecord', {'EnclosingLevel': 4294967294, 'FilterFunc': 160384, 'HandlerFunc': 160388}]
If you need to decode an arbitrary address (using a structure that hasn't been applied to said address), the database.get.structure
function has the following variations. The example following the list of variations decodes a GUID
at offset 0x2a00c.
Python>?db.get.structure
Help on method structure in module database:
structure(*arguments, **keywords) class method of database.get
get.structure(**byteorder) -> Return the decoded fields of the structure at current address as a dictionary.
get.structure(ea=int, **byteorder) -> Return the decoded fields of the structure at address `ea` as a dictionary.
get.structure(ea=int, sptr=struc_t, **byteorder) -> Return a dictionary containing the decoded fields of the structure represented by `sptr` using the data at address `ea`.
get.structure(name=str, *suffix, **byteorder) -> Return the decoded fields for the structure belonging to the symbol `name` as a dictionary.
get.structure(ea=int, structure=structure_t|tinfo_t|str|int, **byteorder) -> Return the decoded fields of the given `structure` from the address `ea` as a dictionary.
get.structure(ea=int, sptr=struc_t|structure_t, size=int, **byteorder) -> Return a dictionary containing the decoded fields of the structure `sptr` using `size` bytes from the data at address `ea`.
get.structure(ea=int, structure=tinfo_t|int|str, size=int, **byteorder) -> Return a dictionary containing the decoded fields of the given `structure` using `size` bytes from the data at address `ea`.
# Treat data at address 0x2a00c as a GUID.
Python>db.get.structure(0x2a00c, 'GUID')
{'Data1': 0x44bf19b1, 'Data2': 0x520, 'Data3': 0x1993, 'Data4': array('B', [0, 0, 0, 0, 0, 0, 0, 0])}
Likewise, the database.get.array
function has similar variations.
Python>?db.get.array
Help on method array in module database:
array(*arguments, **keywords) class method of database.get
get.array(**byteorder) -> Return the values of the array at the current selection or address.
get.array(bounds=bounds_t, **byteorder) -> Decode the data within the provided `bounds` as an array.
get.array(ea=int, **byteorder) -> Decode the data at the address `ea` as an array.
get.array(bounds=bounds_t, type, **byteorder) -> Return the values within the provided `bounds` as an array of the pythonic element `type`.
get.array(ea=int, type, **byteorder) -> Decode the data at the address `ea` as an array of the pythonic element `type`.
get.array(name=str, *suffix, **byteorder) -> Decode the data at the symbol identified by `name` as an array.
get.array(ea=int, length=int, **byteorder) -> Decode the data at the address `ea` as a `length`-element array.
get.array(ea=int, type, length=int, **byteorder) -> Decode the data at the address `ea` as a `length`-element array of the pythonic element `type`.
# Decode data at address 0x2a00c as a 4-element array of 16-bit sized integers.
Python>db.get.array(0x2a00c, (int,2), length=4)
array('H', [6577, 17599, 1312, 6547])
# Decode data at address 0x2a00c as a 4-element array of GUID structures.
Python>db.get.array(0x2a00c, struc.by('GUID'), length=4)
[{'Data1': 0x44bf19b1, 'Data2': 0x520, 'Data3': 0x1993, 'Data4': array('B', [0, 0, 0, 0, 0, 0, 0, 0])}, {'Data1': 0x0, 'Data2': 0x2020, 'Data3': 0x2020, 'Data4': array('B', [32, 32, 32, 32, 32, 32, 32, 32])}, {'Data1': 0x20202020, 'Data2': 0x2020, 'Data3': 0x2020, 'Data4': array('B', [32, 32, 32, 32, 32, 32, 32, 32])}, {'Data1': 0x20202020, 'Data2': 0x2020, 'Data3': 0x2020, 'Data4': array('B', [32, 32, 32, 32, 32, 32, 32, 32])}]
In versions of IDA prior to 9.0, frames can also be treated as structures. Hence, the function.frame
function can be used to get the current frame and allow it to be treated as a structure. Similar to all structure/unions, this can be used with database.get.structure
to decode arbitrary data using the fields from the frame.
Python>func.frame().members
<class 'structure' name='$ F1F974' offset=-0x6c size=0x88>
-6c+0xc [None, 12]
[0] -60+0x4 int 'var_5C' (<class 'int'>, 4)
[1] -5c+0x4 int 'var_58' (<class 'int'>, 4)
[2] -58+0x4 int 'var_54' (<class 'int'>, 4)
[3] -54+0x4 void* 'Src' (<class 'type'>, 4)
[4] -50+0x4 int 'var_4C' (<class 'int'>, 4)
[5] -4c+0x4 int 'var_48' (<class 'int'>, 4)
...
[28] 4+0x4 tRECEIVE_SESSION* 'arg_0' (<class 'type'>, 4)
[29] 8+0x2 USHORT 'arg_4' (<class 'int'>, 2)
a+0x2 [None, 2]
[30] c+0x4 tBASIC_DATA_PACKET_HEADER* 'arg_8' (<class 'type'>, 4)
[31] 10+0x1 UCHAR 'arg_C' (<class 'int'>, 1)
11+0x3 [None, 3]
[32] 14+0x4 KIRQL* 'arg_10' (<class 'type'>, 4)
[33] 18+0x4 KIRQL* 'arg_14' (<class 'type'>, 4)
Python>pp(db.get.struc(h(), func.frame()))
{' r': array('B', [132, 151, 9, 0]),
' s': array('B', [0, 133, 246, 15]),
'Entry': 1354806799,
'Irp': 475958103,
...
'var_6': 255,
'var_7': 133,
'var_8': 196,
'var_C': 2106194059}
When dealing with frames, a common operation is to lay them out contiguously in case a calculation need to be performed. This might be useful if a distance between two fields might need to be calculated or perhaps if one is attempting to discover overlapping fields between two different scopes of execution.
This can be done using either the structure.left
or structure.right
functions depending on whether you wish to lay out your fields and types along the highest or lowest address. By combining this with database.get.structure
, this can be used to view what the frame for one function might look like if executing with a different piece of arbitrary data.
Python>[parent] = func.up()
# Get the frame of the parent and the current function.
Python>parent_frame = func.frame(parent)
Python>current_frame = func.frame()
Python>struc.right(0, [current_frame, parent_frame])
[<class 'structure' name='$ F1F974' offset=-0xc0 size=0x88>, <class 'structure' name='$ F203D8' offset=-0x38 size=0x38>]
# These functions actually support various types.
Python>struc.right(0, [current_frame, 8, parent_frame, 'int[4]'])
[<class 'structure' name='$ F1F974' offset=-0xd8 size=0x88>, 0x8, <class 'structure' name='$ F203D8' offset=-0x48 size=0x38>, 'int[4]']
Python>struc.right(0x7fffffffcf80, [current_frame.members, parent_frame.members])
[<class 'structure' name='$ F1F974' offset=0x7fffffffcec0 size=0x88>
7fffffffcec0+0xc [None, 12]
[0] 7fffffffcecc+0x4 int 'var_5C' (<class 'int'>, 4)
[1] 7fffffffced0+0x4 int 'var_58' (<class 'int'>, 4)
[2] 7fffffffced4+0x4 int 'var_54' (<class 'int'>, 4)
[3] 7fffffffced8+0x4 void* 'Src' (<class 'type'>, 4)
[4] 7fffffffcedc+0x4 int 'var_4C' (<class 'int'>, 4)
[5] 7fffffffcee0+0x4 int 'var_48' (<class 'int'>, 4)
...
7fffffffcf36+0x2 [None, 2]
[30] 7fffffffcf38+0x4 tBASIC_DATA_PACKET_HEADER* 'arg_8' (<class 'type'>, 4)
[31] 7fffffffcf3c+0x1 UCHAR 'arg_C' (<class 'int'>, 1)
7fffffffcf3d+0x3 [None, 3]
[32] 7fffffffcf40+0x4 KIRQL* 'arg_10' (<class 'type'>, 4)
[33] 7fffffffcf44+0x4 KIRQL* 'arg_14' (<class 'type'>, 4), <class 'structure' name='$ F203D8' offset=0x7fffffffcf48 size=0x38>
7fffffffcf48+0x10 [None, 16]
[0] 7fffffffcf58+0x4 PKSPIN_LOCK 'var_14' (<class 'type'>, 4)
[1] 7fffffffcf5c+0x4 PKSPIN_LOCK 'SpinLock' (<class 'type'>, 4)
[2] 7fffffffcf60+0x4 SEQ_TYPE 'var_C' (<class 'int'>, 4)
[3] 7fffffffcf64+0x4 tADDRESS_CONTEXT* 'var_8' (<class 'type'>, 4)
7fffffffcf68+0x2 [None, 2]
[4] 7fffffffcf6a+0x1 KIRQL 'var_2' (<class 'int'>, 1)
[5] 7fffffffcf6b+0x1 KIRQL 'NewIrql' (<class 'int'>, 1)
[6] 7fffffffcf6c+0x4 char[4] ' s' [(<class 'int'>, 1), 4]
[7] 7fffffffcf70+0x4 char[4] ' r' [(<class 'int'>, 1), 4]
[8] 7fffffffcf74+0x4 SEQ_TYPE 'arg_0' (<class 'int'>, 4)
[9] 7fffffffcf78+0x4 tBASIC_DATA_PACKET_HEADER* 'arg_4' (<class 'type'>, 4)
[10] 7fffffffcf7c+0x4 int 'arg_8' (<class 'int'>, 4)]