Skip to content

Instantly share code, notes, and snippets.

@Recoskie
Last active January 18, 2022 07:48
Show Gist options
  • Save Recoskie/1c75264cb072aaf41e871ffd2a1f7370 to your computer and use it in GitHub Desktop.
Save Recoskie/1c75264cb072aaf41e871ffd2a1f7370 to your computer and use it in GitHub Desktop.
Template for JDisassembly plugins.
package Format;
import swingIO.*;
import swingIO.tree.*;
import javax.swing.tree.*;
public class Format extends Window.Window implements JDEventListener
{
private JDNode root;
private Descriptor header; //note this can be made into an array for more complex formats.
public Format() throws java.io.IOException
{
//Setup.
tree.setEventListener( this ); file.Events = false;
//Change ".xxx" to your file formats file extension.
((DefaultTreeModel)tree.getModel()).setRoot(null); tree.setRootVisible(true); tree.setShowsRootHandles(true); root = new JDNode( fc.getFileName() + ( fc.getFileName().indexOf(".") > 0 ? "" : ".xxx" ), -1 );
//Begin reading the file header.
header = new Descriptor( file ); header.setEvent( this::infoExample );
JDNode FHeader = new JDNode("File Header.h",
new long[]{ 1, 7 } //Put readable arguments in here. These are given to the open method when the user clicks on the node.
); root.add( FHeader );
//Example of reading and defining a value.
int myValue = 0;
long myValue2 = 0;
header.LUINT32("Value"); myValue = (int)header.value; //Make sure you cast to the proper type.
header.LUINT64("Value 2"); myValue2 = (long)header.value; //Make sure you cast to the proper type.
tools.update(); //Update the window and its tools.
//Set binary tree view, and enable IO system events.
((DefaultTreeModel)tree.getModel()).setRoot(root); file.Events = true;
//Set the selected node.
tree.setSelectionPath( new TreePath( FHeader.getPath() ) );
//Make it as if we clicked and opened the node.
open( new JDEvent( this, "", new long[]{ 1, 7 } ) );
}
//This method is called when opening a new file format to get rid of variables and arrays needed by this format reader by
//setting values and arrays to null. If this is not done, then program will eventually crash when loading too many files.
public void Uninitialize() { }
//This event is called when the user clicks on an tree node.
public void open(JDEvent e)
{
//This is given to the open method when the user goes to load a new file format. It triggers the Uninitialize method.
if( e.getID().equals("UInit") ) { Uninitialize(); }
//When the user clicks on the "File header.h" node we will receive the array arguments associated with the node.
if( e.getArg(0) == 1 && e.getArg(1) == 7 ) //Array should contain values 1, and 7 in "File Header.h" node.
{
ds.setDescriptor( header ); //Set and Display the data descriptor we used to read the data.
}
}
//Each thing read by our descriptor goes in order from 0 to x number of things.
//We can create an array of html strings that describe the value or bytes read in the file here.
//If the index is -1 then that means the user just clicked on the node which sets the descriptor from the open method.
//When -1 you can describe what the section is used for in html.
//In our case we only have read two values by our descriptor so we use an string array with two index.
private static final String[] infoEx = new String[]
{
"<html>This is an example Property.</html>",
"<html>This is the second value read by the descriptor.</html>"
};
public void infoExample( int index )
{
if( index < 0 )
{
info("You just set this descriptor, but did not click on any values read in the descriptor.");
}
else
{
info( infoEx[ index ] );
}
}
}
/*-----------------------------------------------------------------------------------------------------------------------------
This is the basic setup. It is easy to make an array of descriptors and nodes using iteration to read over values and properties.
-------------------------------------------------------------------------------------------------------------------------------
In the most general case when a file format is just a set of types and is not very complex, then you can define a
descriptor array and have the first number in the nodes as which descriptor to set from the descriptor array.
Each descriptor can be set to a method that describes the sections data. In the above example we set the descriptor to infoExample.
Which describes the section and its data.
-------------------------------------------------------------------------------------------------------------------------------
It is rare for if you want to compare an index to break the value or data down further in HTML in the descriptors info method.
So keep in mind you can do that as well instead of using string array indexes.
----------------------------------------------------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------------------------------------------------
In some cases you want the node to select start and end bytes in the hex editor without a descriptor.
-------------------------------------------------------------------------------------------------------------------------------
Instead of using the first value in each node given to the open method when user clicks on a node as which descriptor to set
from the descriptor array you can use the first number as a command value.
In such a case you decide value 3 as the command to set an descriptor. So the next value after 3 is which descriptor to set.
You decide on number 7 as the first value for selecting bytes, and the next 2 numbers as start and end.
You can then run the algorithm and then display the output using html for the selected bytes.
You can have more than one byte selection command for different purposes and algorithms.
-------------------------------------------------------------------------------------------------------------------------------
The same is true if you want to add in disassembly as a node command, and then highlight the bytes.
This way your tree nodes can have different functions. In most cases just reading the data using descriptors and setting info
methods to describe the descriptors data is enoughs to describe the entire binary file, so do not go crazy when it is not necessary.
-------------------------------------------------------------------------------------------------------------------------------
You can also speed up loading by storing nodes with dummy nodes and when the user clicks on them, then begin reading the data
at the define offset and then replace the dummy node by the real nodes and descriptor array indexes before it expands.
This makes it appear as if the data was always read.
-----------------------------------------------------------------------------------------------------------------------------*/

Adding a new file format to JDisassembly.

Any format reader that you create for a file format that reads the file format using the descriptors Must be put under the folder Format.

There is two ways of adding in your binary file format.

First is the preferred method for loading binary file formats.

Start by opening the file app.java.

Look for the following section.

//Use the file signature codes instead of file extension.

private byte Signature[][] = new byte[][]
{
  new byte[] { 0x4D, 0x5A }, //Microsoft binaries.
  new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, //Linux/UNIX binaries.
  new byte[] { 0x42, 0x4D }, //Bit map pictures.
  new byte[] { -1, -40 }, //JPEG start of image marker.
  new byte[] { 0x52, 0x49, 0x46, 0x46 }, //Multimedia RIFF file.
  new byte[] { 0x52, 0x46, 0x36, 0x34 } //Multimedia RIFF/64 file.
};

Binary file formats have a set of bytes at the start of the file that identifies the file format.

Some file formats can have no file extension so we must use the unique signature that is at the start of the binary file format to know if it is the right binary file format.

Linux applications start with an ELF signature code and can have no file extension, so this is the preferred method for detecting the binary format when possible.

All microsoft binaries start with 4D, 5A hexadecimal.

The longest signature is 4 bytes so you must locate the following line.

//Buffer should be set to the length of the largest signature sequence.

private byte[] Sig = new byte[4];

Make sure it is set to the longest signature which is 4 bytes.

In the case you have a 12 byte long signature then this array should be set 12.

In this final step locate the following section.

//The file to load. To begin decoding file types.

private String DecodeAPP[] = new String[]{ "Format.EXE", "Format.ELF", "Format.BMP", "Format.JPEG", "Format.RIFF", "Format.RIFF" };

This list tells the application what decoder to use for each signature.

The first signature is 4D, 5A which loads the File EXE.java under the folder Format.

The next signature in the signature list is new byte[] { 0x7F, 0x45, 0x4C, 0x46 }, //Linux/UNIX binaries.

The next file in the DecodeAPP list is Format.ELF which loads the File ELF.java under the folder Format.

The two lists go in order to each other.

If you add a new signature at the end of the signature list then you must also add the file to load to the end of the DecodeAPP list.

Lastly locate the following section.

//Depending on the file format we do not need a virtual address space.

private static boolean SignatureV[] = new boolean[]
{
  true, true, false, false, false, false
};

This list is used to enable, or disable virtual address view. It should match the number of signatures and DecodeAPP list.

Set false if your format does not need to map sections of the file to different virtual address locations.

A Linux ELF, or Microsoft binary is made of segments that must be loaded into RAM memory.

The segments use address locations that are spaced apart by a set address in the file format.

In order to read the sections of the program properly, we need to use the virtual address locations.

This is, because the segments use virtual address locations rather than a positions across the file.

The virtual address locations are also added to a base address to space applications away from each other in RAM memory to allow you to load multiple applications at once.

Multimedia file formats do not need virtual address space mapping.

Adding file formats by file extension.

Usually all files have a file extension that tells the OS what program to use to load the file.

Modern operating systems hide what comes after the last dot in the file name.

The combination after the last dot in the file name tells the operating system what program to use to load the file.

Start by opening the file app.java. Locate to the following section.

//By file extension.

private String Extension[] = new String[]{ ".com" };

On windows we associate file types by extension in the registry, and tell windows which program to use on the disk drive.

The program is given the files location to start reading.

Linux also does the same thing, and macOS. If there is no registered program for a file extension, then the OS will display the extension combination.

Operating systems do not use byte signatures in files for which program to use to load the file.

Not all file formats use a signature at the start of the files binary data to ensure it is the right file format.

You can trick operating systems into loading the wrong program by changing the file extension at the end of the files name.

This application uses signatures first to prevent this from happening.

In some cases we are stuck with using the file extensions for the file type as there is no signatures at the start of the file.

Once you add the file extension to the Extension list, then locate to the following section.

//What file to load by file extension.

private String DecodeAPP_EX[] = new String[]{ "Format.COM" };

This tells it what Format plugin to use to read the file. These two lists work the same as the signature list and DecodeAPP list.

Once you add the plugin you made to the list then locate to the section.

//Depending on the file format extension we do not need a virtual address space.

private static boolean ExtensionV[] = new boolean[] { true };

Specify weather the format needs virtual address space mapping, or not.

Adding file format icons.

This step is optional.

Start by opening the folder SwingIO. Locate and open the file JDTree.java.

Locate to the section.

private static ImageIcon h_file = new ImageIcon( FileIconManager.class.getResource( "Icons/H.gif" ) );
private static ImageIcon disk = new ImageIcon( FileIconManager.class.getResource( "Icons/disk.gif" ) );
private static ImageIcon exe_file = new ImageIcon( FileIconManager.class.getResource( "Icons/EXE.gif" ) );
private static ImageIcon dll_file = new ImageIcon( FileIconManager.class.getResource( "Icons/dll.gif" ) );
private static ImageIcon sys_file = new ImageIcon( FileIconManager.class.getResource( "Icons/sys.gif" ) );
private static ImageIcon elf_file = new ImageIcon( FileIconManager.class.getResource( "Icons/ELF.gif" ) );
private static ImageIcon bmp_file = new ImageIcon( FileIconManager.class.getResource( "Icons/bmp.gif" ) );
private static ImageIcon jpg_file = new ImageIcon( FileIconManager.class.getResource( "Icons/jpg.gif" ) );
private static ImageIcon pal_file = new ImageIcon( FileIconManager.class.getResource( "Icons/pal.gif" ) );
private static ImageIcon ani_file = new ImageIcon( FileIconManager.class.getResource( "Icons/ani.gif" ) );
private static ImageIcon webp_file = new ImageIcon( FileIconManager.class.getResource( "Icons/webp.gif" ) );
private static ImageIcon wav_file = new ImageIcon( FileIconManager.class.getResource( "Icons/wav.gif" ) );
private static ImageIcon midi_file = new ImageIcon( FileIconManager.class.getResource( "Icons/mid.gif" ) );
private static ImageIcon avi_file = new ImageIcon( FileIconManager.class.getResource( "Icons/avi.gif" ) );

Put an Icon picture for your file format in the folder Icons. Add a line for your picture.

private static ImageIcon My_file = new ImageIcon( FileIconManager.class.getResource( "Icons/MyIcon.gif" ) );

Change MyIcon.gif to your picture file. Change the variable My_file to a meaningful name like the rest of the icons.

Locate to the section.

public static String FType[] = new String[]
{
  ".h", ".disk",
  ".com", ".exe", ".dll", ".sys", ".drv", ".ocx", ".efi", ".mui",
  ".axf", ".bin", ".elf", ".o", ".prx", ".puff", ".ko", ".mod", ".so",
  ".bmp", ".dib",
  ".jpg", ".jpeg", ".jpe", ".jif", ".jfif", ".jfi",
  ".pal",
  ".ani",
  ".webp",
  ".wav", ".rmi",
  ".avi"
};

Add your file extension to this list.

Locate to the section.

public static ImageIcon LoadedPic[] = new ImageIcon[]
{
  h_file, disk,
  exe_file, exe_file, dll_file, sys_file, sys_file, sys_file, sys_file, sys_file,
  elf_file, elf_file, elf_file, elf_file, elf_file, elf_file, elf_file, elf_file, elf_file,
  bmp_file, bmp_file,
  jpg_file, jpg_file, jpg_file, jpg_file, jpg_file, jpg_file,
  pal_file,
  ani_file,
  webp_file,
  wav_file, midi_file,
  avi_file
};

Add the variable names you gave each icon to this list for each file extension.

Some file formats use more than one file exertion combination for the same file type.

This is because the binary format is the same, but the purpose of the file may be different.

We define what Icon image to use for each file execution.

So we have a file extension list and a list that specifies which Icon to use for each extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment