Custom structures
Malcat can apply custom structures to raw bytes, even when the regular File parsers parser did not identify a structure at that location. This is useful when you are reverse-engineering an unknown format, when a parser missed an embedded structure, or when you want to annotate a buffer with a layout you already know.
Custom structures can be applied from the Apply a custom type dialog in the
Hexadecimal and Text views, or from a script with analysis.struct.force.
Available type sources
The custom type dialog shows two kinds of definitions:
static types: C-like structure declarations stored as
.hfiles indata/usertypesand<user data dir>/usertypes.dynamic types: Python structure classes found in
data/filetypes,data/usertypes,<user data dir>/filetypesand<user data dir>/usertypes.
For most custom layouts, static types are the simplest option. They are parsed once, previewed by the dialog, and can be applied anywhere outside an existing file structure.
Dynamic types are more powerful, because they use Malcat’s Python field classes directly. They are better suited for big-endian fields, formatted values, GUIDs, or layouts which are easier to express with Python. Some dynamic types defined for full file parsers may depend on parser context and can fail when applied as a standalone user type.
Defining static types
To add your own static structures, create a .h file directly inside the
usertypes subdirectory of your User data directory. For instance:
<user data dir>/usertypes/myformat.h
Malcat rescans user type files before each analysis. After editing a type file, reanalyze the file or hit Ctrl+R.
Static type declarations use a small C-like syntax. A practical example:
#define MY_NAME_SIZE 16
typedef struct _MY_HEADER {
uint32 magic;
uint16 version;
uint16 flags;
char name[MY_NAME_SIZE];
void *next;
} MY_HEADER;
typedef struct _MY_ENTRY {
MY_HEADER header;
uint64 timestamp;
uint8 key[32];
} MY_ENTRY;
The exported type names for this file will be
usertypes.myformat.MY_HEADER and usertypes.myformat.MY_ENTRY. These are
the names shown in the custom type dialog, and the same names should be used
from scripts.
Static type files support:
integer primitives:
char,uint8,int8,wchar,uint16,int16,uint32,int32,uint64andint64;pointers, whose size follows the current analysis architecture;
fixed-size arrays such as
uint8 key[32];charandwchararrays, displayed as strings;nested structures and local
typedefaliases;integer constants with
#define.
There are a few limits to keep in mind:
each
.hfile is parsed independently, so put the aliases and constants it needs in the same file;only fixed-size arrays are supported;
unions are not supported by the static parser;
typedef names starting with
_are considered internal and are not shown as user-applicable types;static C-like types are best for simple fixed layouts. Use a Python dynamic type if you need richer field classes or custom parsing logic.
Defining dynamic types
Dynamic user types are Python classes stored directly inside
<user data dir>/usertypes. They use the same field classes as Malcat file
parsers.
Example:
from filetypes.types import *
class MyTinyHeader(StaticStruct):
@classmethod
def parse(cls):
yield UInt32(name="Magic")
yield UInt16(name="Version")
yield UInt16(name="Flags")
yield Bytes(16, name="Key")
If this file is saved as <user data dir>/usertypes/myformat.py, the dialog
will expose the type as usertypes.myformat.MyTinyHeader.
Dynamic types can also be discovered from global structure classes in File parsers parsers. This is convenient when an existing parser already defines the structure you want to apply manually.
Applying a custom type
In the GUI, right-click on a byte outside an identified structure and choose . The dialog lets you search for a type, preview it at the selected address, and apply it.
Applied user types are saved in the Malcat project file, so they are restored when the file is reopened. The operation is also integrated with undo/redo.
From Python, use analysis.struct.force with
an effective address and a full type name:
address = analysis.v2a(0x401000)
analysis.struct.force(address, "usertypes.myformat.MY_HEADER")
To remove a user type:
analysis.struct.unforce(address)
When running a script outside the GUI, call malcat.Analysis.run() after
adding or removing user types so dependent analyses are refreshed. The GUI does
this refresh for you when needed.
Troubleshooting
If a type does not appear in the dialog, check that the file is directly below
<user data dir>/usertypes and that it has the right extension: .h for
static C-like types or .py for dynamic Python types.
If parsing fails, Malcat reports an error while parsing user types during analysis. Start with a minimal structure, re-run the analysis, and add fields back gradually. For static types, the most common causes are a missing local typedef, a non-constant array size, or an unsupported union.
If a dynamic type previews with an error, it may depend on parser state that is
only available during a full File parsers parse. In that case,
create a smaller standalone StaticStruct in <user data dir>/usertypes
for manual application.