Analysis object (analysis)

analysis: Analysis

scripts run inside the Script editor have access to the current analysis result via the analysis global variable.

The malcat.Analysis class is the entry point of Malcat’s scripting interface. It contains the analysis results for a single file/project. You have access to all of the specialized analyses as well as to the raw file using the File object (analysis.file).

The Analysis object

class malcat.Analysis

Attributes

architecture: malcat.Architecture

The main CPU architecture of the file (only set for programs). This property can be overriden, disabling the automatic file type detection. Note that changing it will also invalidate all previous computations (like a call to invalidate()). Setting it to malcat.Architecture.None will re-enable the automatic CPU architecture detection.

a = malcat.analyse("/the/file")

a.architecture = malcat.FileType.Architecture.X86
# this is likely to impact a lot of analyses, so evtg was invalidated. re-run all analyses.
a.run()
type: str

The file type as a short string, e.g. “PE”. This comes from the parser’s FileTypeAnalyzer.name attribute. This property can be overriden, disabling the automatic file type detection. Note that changing it will also invalidate all previous computations (like a call to invalidate()). Setting it to the empty string ("") will re-enable the automatic type detection.

a = malcat.analyse("/the/file")

a.type = "PE"
# this is likely to impact a lot of analyses, so evtg was invalidated. re-run all analyses.
a.run()
category: malcat.FileType.Category

The category of the identified file type. This comes from the parser’s FileTypeAnalyzer.category attribute.

imagebase: int

The virtual address at which the file should be loaded

entrypoint: int

The effective address of the entrypoint if any, or None

metadata: Dict[str, Dict[str, str]]

The file metadata, as extracted from the File parsers. This is what you see in the Summary view. Metadata are str->str associations sorted into categories (the first dictionnary keys).

file: malcat.File

A pointer to the File object (analysis.file).

history: malcat.UndoRedoManager

A pointer to the undo/redo list manager for this analysis.

Error management

ok: bool

True if the analysis ran without error

failed: bool

True if there was an error (analysis result is only partially valid, better to just discard evtg)

log: List[AnalysisError]

the complete list of all errors/warning that took place during the last run

raise_if_failed()

if the analysis failed (failed is True), throws a ValueError with the most recent error (i.e everything beside malcat.AnalysisError.Status.WARNING) seen

Access to annotations / sub-analyses

entropy: malcat.Entropy

A pointer to the File entropy (analysis.entropy).

map: malcat.MappingAnnotation

A pointer to the Address mapping (analysis.map).

struct: malcat.FileStructure

A pointer to the File structures (analysis.struct).

asm: malcat.Asm

A pointer to the Disassembly (analysis.asm).

cfg: malcat.CFG

A pointer to the Control Flow Graph (analysis.cfg).

loops: malcat.Loops

A pointer to the Strongly Connected Components (analysis.loops).

fns: malcat.Functions

A pointer to the Functions (analysis.fns).

strings: malcat.Strings

A pointer to the Strings (analysis.strings).

xrefs: malcat.CrossReferences

A pointer to the Cross References (analysis.xrefs).

syms: malcat.Symbols

A pointer to the Symbols (analysis.syms).

sigs: malcat.Signatures

A pointer to the Yara signatures (analysis.sigs).

constants: malcat.Constants

A pointer to the Known constants (analysis.constants).

carved: malcat.SubFiles

A pointer to the Carved files (analysis.carved).

vfiles: List[malcat.VirtualFile]

The list of virtual files identified by the File parsers.

anomalies: malcat.Anomalies

A pointer to the Anomalies (analysis.anomalies).

comments: malcat.UserComments

A pointer to the User comments (analysis.comments).

highlights: malcat.UserHighlights

A pointer to the User highlighted regions (analysis.highlights).

parser: malcat.FileTypeAnalyzer

A pointer to the parser that was used to parse the file format, cf. Writing new parsers.

Kesakode

kesakode_quota(license, endpoint='https://cloud.malcat.fr', ssl_verify=True)

Queries the Kesakode server specified by endpoint about the remaining quota for the given license key. Doesn’t consume any token. Note that you need to specify the license key since Malcat may not know your license key (e.g. if activated using Offline activation).

Parameters:
  • license (str) – the Malcat or Kesakode license key that you want to know the quota of.

  • endpoint (str) – which Kesakode endpoint to use

  • ssl_verify (bool) – enforce SSL certificate validity. Set this to false if your company does HTTPS mitm.

Returns:

(#calls left for this month, #calls allowed per month)

Return type:

(int, int)

kesakode_lookup(license, endpoint='https://cloud.malcat.fr', ssl_verify=True)

Performs a Kesakode lookup query on the current file and get the result back. Consumes one token. Note that you need to specify the license key since Malcat may not know your license key (e.g. if activated using Offline activation).

Parameters:
  • license (str) – your Malcat license key, or your special keskakode key if you’ve got one with more quota.

  • endpoint (str) – which Kesakode endpoint to use

  • ssl_verify (bool) – enforce SSL certificate validity. Set this to false if your company does HTTPS mitm.

Returns:

the Kesakode lookup result: global verdict and function/strings match details

Return type:

malcat.KesakodeResult

Computation

run()

Some edit operations in Malcat (e.g. malcat.Functions.force()) invalidate one ore several sub-analyses. The whole analysis result can also be invalidated manually using invalidate().

This function re-runs all invalidated/dirty sub-analyses. This call is blocking and can take some time.

Note

If you Run a script from the user interface, you don’t have to call this function since the UI will do it for you at the end of your script

Warning

This will invalidate all Malcat objects (functions, instructions, etc) that were obtained from the previous state of the analysis. If you keep using them after this call, this could lead to undefined behavior / crashes.

invalidate()

Tag all analyses as dirty and needing recomputation. The recomputation can then be triggered via run().

a = malcat.analyse("/the/file")

# modify the file
a.file[10:200] = b"\x00" * 190

# we don't know what we have modified, so re-run all analyses
a.invalidate()
a.run()

# the different sub-analyses know reflect the new state of the file

I/O

save(project_file_path)

Save any user modifications to a Malcat project file (usually ending with a .malcat extension). User modifications include changed flags, user comments, highlighted regions, user labels, forced function starts, etc.

a = malcat.analyse("/the/file")

# make some modifications to the analysis
a.comments[a.p2a(0x100)] = "a comment"

# save the project
if a.save("/the/file.malcat"):
    print("changes were saved and will be loaded by the UI")
Parameters:

project_file_path (str) – the destination path for the project file. If you wan the UI to load it automatically, it should be <path of the file>.malcat.

Raises:

RuntimeError – if something went wrong during the save

Return type:

bool

Note

Everything except the file data is saved. You can save any modification made to the file using malcat.File.save().

load(project_file_path)

Load any user modifications from a Malcat project file (usually ending with a .malcat extension). User modifications include changed flags, user comments, highlighted regions, user labels, forced function starts, etc. After loading, the whole analysis is automatically invalidated. Call run()

a = malcat.analyse("/the/file")
if a.load("/the/file.malcat"):
    print("user changes loaded")
a.run() # rerun the analysis
Parameters:

project_file_path (str) – the path of the project file.

Raises:

RuntimeError – if something went wrong during the save

Return type:

bool

Helpers

ppa(ea, resolve=True, interactive=False)

Format the given effective address using the best string representation. If the effective address lies in memory, its virtuall address will be returned (prefixed with 0x), otherwise its file offset will be returned (prefixed with #).

address = analysis.p2a(0x1000)
print(analysis.ppa(address, resolve=True))

# if you run the script from the UI and use gui.print, you can make clickable addresses like this
gui.print("Click here to go to {}".format(analysis.ppa(address, interactive=True)))
Parameters:
  • ea (int) – effective address to print

  • resolve (bool) –

    if True, the function will append additional context to the address, e.g.:

    • (sub_401c4e + 1e) if the address lies within a function

    • (<Optional header> + 3c) if the address lies within a structure

    • (.section0 + 123c) if the address lies within a section

  • interactive (bool) – if True, the address will be encosed in markers to make it clickable when printed via malcat.UI.print().

Return type:

str

p2a(offset)

converts a file offset to an effective address. Shortcut for malcat.MappingAnnotation.from_phys().

a2p(effective_address)

converts an effective address to a file offset. Shortcut for malcat.MappingAnnotation.to_phys().

v2a(virtual_address)

converts a virtual address to an effective address. Shortcut for malcat.MappingAnnotation.from_virt().

a2v(effective_address)

converts an effective address to a virtual address. Shortcut for malcat.MappingAnnotation.to_virt().

r2a(rva)

converts a Relative Virtual Address to an effective address. Shortcut for malcat.MappingAnnotation.from_rva().

a2r(effective_address)

converts an effective address to a virtual address. Shortcut for malcat.MappingAnnotation.to_rva().

hex(start, end, exclude_off=False, exclude_disp=False, exclude_reg=False, exclude_imm=False)

Get the masked out hex bytes at [start,end[ e.g. 558BEC68????????8374??45..

Parameters:
  • start (int) – the effective addres of the first byte you want

  • end (int) – the effective addres of the first byte you do not want (i.e. address of last byte + 1)

  • exclude_off (bool) – exclude absolute offsets in valid instructions, e.g. mov eax, off_15bf34

  • exclude_disp (bool) – exclude displacements in valid instructions, e.g. mov eax, [ecx+128]

  • exclude_reg (bool) – exclude registers in valid instructions, e.g. push eax

  • exclude_imm (bool) – exclude immediates in valid instructions, e.g. mov eax, 0x1223

Return type:

str

open_vfile(path)

Helper method to open a virtual file by path. Will return the first vfile found with the given path, or raise an error.

import hashlib
vf = analysis.open_vfile("DLG/REPLACEFILEDLG/uk-ua")
print(hashlib.sha256(vf[:]).hexdigest())
Parameters:

path (str) – the path of the virtual file to open, e.g. “DLG/REPLACEFILEDLG/uk-ua”

Return type:

malcat.File

Errors

The malcat.Analysis.last_error and malcat.Analysis.log attributes both manipulate error objects, which are defined below:

class malcat.AnalysisError

This class describes an error that took place during analysis

severity: malcat.AnalysisError.Status

The severity of the error (see below)

module: int

A unique identifier for the sub-analysis module at fault (or 0 if none)

msg: str

An explicative error message

__repr__()

Error pretty printing

Return type:

str

class malcat.AnalysisError.Status
WARNING

A warning: something was unusual, but analysis could continue

ERROR

An error: something unexepected happened that did prevent the analysis to continue

SETUP

An error happened during the analysis setup phase taht prevented the analysis to start at all

MISSING

Malcat is missing one dependency (an analysis engine, a library): no result at all

CRASH

One of Malcat’s analyses unexpectly crashed. Analysis result is only partial at best

Category enum

The malcat.Analysis.category attribute is an enum which can take the following values:

class FileType.Category

This enum describes the type/category of the analyzed file. I can has one of the following values:

UNKNOWN

No file type could be infered, i.e the file was rejected by all parsers

PROGRAM

The file a an executable program (PE, ELF, NSIS script, etc.)

IMAGE

The file is an image

SOUND

The file is a sound file format

DOCUMENT

The file is a document, e.g. an Excel stylesheet

ARCHIVE

The file is an archive, e.g. zip or rar

FILESYSTEM

The file is a filesystem, e.g. a SquashFS container or a FAT32 image

DATABASE

The file is a database, e.g. a SQlite file

CPU architectures enum

The malcat.Analysis.architecture attribute is an enum which can take the following values:

class malcat.Architecture

This enum describes the main CPU architecture that should be used to interpret the code portion of the file (if any). Note that some file types main contain code for more than one architecture, e.g. Visual Basic Pcode + x86.

NONE
X86
X64
DOTNET
PCODE

Visual Basic Pcode

AU3

AutoIt tokens

BIFF

Biff8 or Biff12 Excel stylesheet. Stylesheets can contain bytcode formulas, thus the architecture.

PY36
PY37
PY38
PY39
PY310
NSIS

The NSIS virtual machines is used in the setup scripts of NSIS installers

PASCALSCRIPT

Pascalscript is a special VM-based language used in InnoSetup installers

MSI

Tells Malcat to parse MSI tables

VBA

Tells Malcat to use the VBA decompiler

Undo/redo manager

Each edit operations in Malcat (e.g. a file write, a new comment or a forced function start) is saved into the list of undoed operations. This list is programmatically available through the Analysis.history parameter.

Undo/redo manager

class malcat.UndoRedoManager

This class allows you to interact with Malcat’s undo/redo functionnalities for a particular analysis.

__iter__()

Iterate over all the undoable operations for this analysis

for undoable_op in analysis.history:
    print(f"{undoable_op} done at {analysis.ppa(undoable_op.address)}")
Return type:

iterator over UndoableOperation

__len__()

return the number of undoable operations

if len(analysis.history) == 0:
    raise ValueError("No user edit so far")
Return type:

int

undo()

undo the last undoable edit operation. Returns falls if nothing can be undone or an error happened

Return type:

bool

undo_all()

undo all edit operations. Returns true iff no error.

Return type:

bool

redo()

redo the next redoable edit operation. Returns falls if nothing can be redone or an error happened

Return type:

bool

record(bool enable)

enable or disable recording subsequent edit operations inside this undo/redo manager

analysis.history.record(False)
# ... add your comments, they won't appear in the undo list
for i in range(malcat.map.end):
     malcat.comments[i] = "..."
# ... reactivate undo/redo
analysis.history.record(True)
Parameters:

enable (bool) – if True, edit operations for this analysis will be saved inside the undo/redo manager

group()

context manager that will group all edit operations in a single undoable operation

with analysis.history.group()
    # ... add your comments
    for i in range(malcat.map.end):
         malcat.comments[i] = "..."
analysis.history.undo()     # <-- will undo all comments in one go
Parameters:

enable (bool) – if True, edit operations for this analysis will be saved inside the undo/redo manager

Undoable operation

Edits which can be undone (which is currently all edits supported by Malcat) are represented in the undo/redo manager by a UndoableOperation instance, defined below:

class malcat.UndoableOperation

An user edit targetting this analysis which can be undo

address: int (effective address)

the (first) address where the edit took place

size: int

the number of bytes affected by the edit

label: str

the content of the comment. May contains newlines.

__repr__()

return the value of the label property

Return type:

str

__len__()

return the value of the size property

Return type:

int