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 tomalcat.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 toinvalidate()
). 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
isTrue
), throws aValueError
with the most recent error (i.e everything besidemalcat.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:
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 usinginvalidate()
.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:
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: