# Usage

# General

To use dotnetfile, all you have to do is to import the module and create an instance of the class DotNetPE with the .NET assembly path as a parameter. A minimal example that prints out the number of streams of an assembly is shown below:

# Import class DotNetPE from module dotnetfile
from dotnetfile import DotNetPE

# Define the file path of your assembly
dotnet_file_path = '/Users/<username>/my_dotnet_assembly.exe'

# Create an instance of DotNetPE with the file path as a parameter
dotnet_file = DotNetPE(dotnet_file_path)

# Print out the number of streams of the assembly
print(f'Number of streams: {dotnet_file.get_number_of_streams()}')

Before using a method that resides in a metadata table class, you should always check if the appropriate table is present in the .NET file. This might take a bit of time to get familiar with, but on the other side you'll learn how the header is organized. It can be accomplished in several ways:

  1. For short codes, you can use the try...except statement. Example to get the assembly name:
# Import class DotNetPE from module dotnetfile
from dotnetfile import DotNetPE

# Create an instance of DotNetPE with the file path as a parameter
dotnet_file = DotNetPE('/Users/<username>/my_dotnet_assembly.exe')

# Print assembly name if present
try:
    print(f'Assembly name: {dotnet_file.Assembly.get_assembly_name()}')
except AttributeError:
    print('.NET file does not have an Assembly table and thus no assembly name.')
  1. For short codes and as a cleaner solution, you can also use the metadata_table_exists() method. Example to get the assembly name:
# Import class DotNetPE from module dotnetfile
from dotnetfile import DotNetPE

# Create an instance of DotNetPE with the file path as a parameter
dotnet_file = DotNetPE('/Users/<username>/my_dotnet_assembly.exe')

# Print assembly name if present
if dotnet_file.metadata_table_exists('Assembly'):
    print(f'Assembly name: {dotnet_file.Assembly.get_assembly_name()}')
else:
    print('.NET file does not have an Assembly table and thus no assembly name.')
  1. For longer codes, you can use the existent_metadata_tables() method. Example to get the assembly and module names:
# Import class DotNetPE from module dotnetfile
from dotnetfile import DotNetPE

# Create an instance of DotNetPE with the file path as a parameter
dotnet_file = DotNetPE('/Users/<username>/my_dotnet_assembly.exe')

# Get list of available metadata tables
available_tables = dotnet_file.existent_metadata_tables()

# Print module name if present
if 'Module' in available_tables:
    print(f'Module name: {dotnet_file.Module.get_module_name()}')
else:
    print('.NET file does not have a Module table and thus no module name.')

# Print assembly name if present
if 'Assembly' in available_tables:
    print(f'Assembly name: {dotnet_file.Assembly.get_assembly_name()}')
else:
    print('.NET file does not have an Assembly table and thus no assembly name.')

# Fast load

Since dotnetfile v0.2.4, use can use fast load options when loading a file with DotNetPE(). This was introduced to more quickly parse bigger .NET files that sometimes take very long to be loaded. There are several stages of fast load that can be used subsequently or independently together with the full load method, depending on your needs.

# Fast load options

  1. header_only - Load only header data and skip metadata table loading.
  2. normal - Load header data and only those metadata tables supported in the interface library. Optionally, you can choose your own list of metadata tables to be loaded via fast_load_tables=. However, it's advised not to change the default list unless you really know what you are doing.
  3. normal_resources - Load header data, only those metadata tables supported in interface library and the resources. Again, you can choose your own list of metadata tables to be loaded via fast_load_tables=.

By default, when no fast load option is used, all data is loaded which can be slow for big files.

# Full load options

When you have used a fast loading option, you have to eventually load the remaining data at some point. For this, there's the full_load_data() method which the full load option passed as a parameter.

  1. normal - Load header data and only those metadata tables supported in interface library. Possible preceding fast load option: header_only
  2. normal_resources - Depending on the previous fast load option(s), load only those metadata tables supported in interface library and the resources or only the resources. Possible preceding fast load option(s): header_only, normal
  3. full - Depending on the previous fast load option(s), load all or only the remaining metadata tables supported in interface library. Possible preceding fast load option(s): header_only, normal, normal_resource

# Example

The following code example uses the fast load option header_only to get basic header information. It checks if an assembly has .NET resources before actually parsing them (together with the necessary metadata tables) with the full load option normal_resources. Batch processing samples to find those with resources in such a way is much faster than to fully parse each assembly to get the data.

# Import class DotNetPE from module dotnetfile
from dotnetfile import DotNetPE

# Create an instance of DotNetPE with the file path as a parameter and fast load option "header_only"
dotnet_file = DotNetPE('/Users/<username>/my_dotnet_assembly.exe', fast_load='header_only')

# Get list of metadata table names from assembly via an (undocumented) internal data structure
table_names = dotnet_file.dotnet_metadata_stream_header.table_names

# Check if the assembly has .NET resources, load them via the full load option "normal_resources" and print them out
if 'ManifestResource' in table_names:
    dotnet_file.full_load_data('normal_resources')

    resource_data = dotnet_file.get_resources()
    for data in resource_data:
        for resource_item in data.items():
            if resource_item[0] == 'SubResources':
                if resource_item[1]:
                    print(f'\tSubResources:')
                    for sub_resource in resource_item[1]:
                        for sub_resource_item in sub_resource.items():
                            print(f'\t\t{sub_resource_item[0]}: {sub_resource_item[1]}')
                        print('\t\t---')
            else:
                print(f'\t{resource_item[0]}: {resource_item[1]}')
        print('\t---')
else:
    print('Assembly does not have any resources.')