Writing your first Burp Suite extension_

This guide will help you to write your first Burp extension in any of the supported languages (Java, Python & Ruby).

Basic steps to get an extension running

Before we get into specifics for each language, there is some general context to bear in mind: Burp looks for a class called BurpExtender to instantiate (with no constructor parameters) and then calls registerExtenderCallbacks() on this object passing in a "callbacks" object. Think of this as the entrypoint for your extension, allowing you to tell Burp what your extension is capable of, and when Burp should ask your extension questions.

First of all you'll need an IDE. Some popular options are: IntelliJ IDEA, Netbeans, and Eclipse.

Create a new project, and create a package called "burp". Next you'll need to copy in the interface files which you can export from Burp at Extender / APIs / Save interface files. Save the interface files into the folder that was created for the burp package.

Now that you have the general environment set up you'll need to create the actual extension file. Create a new file called BurpExtender.java (or a new class called BurpExtender, if your IDE makes the files for you) and paste in the following code:

package burp;
public class BurpExtender implements IBurpExtender
{ public void registerExtenderCallbacks (IBurpExtenderCallbacks callbacks) {
// your extension code here }
}

This example does nothing at all, but will compile and can be loaded into Burp after you generate a JAR file from your IDE - it will usually be in a build or dist directory. In Burp, go to the Extender tool, and the Extensions tab, and add a new extension. Select the extension type "Java", and specify the location of your JAR file. This should be loaded into Burp with no errors.

Burp relies on Jython to provide its Python support. You will need to download the "Standalone Jar" version and configure Burp with its location (at Extender / Options / Python environment).

Now create a new file with any name you like, ending in '.py', and add the following content to that file:

from burp import IBurpExtender
class BurpExtender(IBurpExtender): def registerExtenderCallbacks( self, callbacks): # your extension code here
return

Then go to the Extensions tab, and add a new extension. Select the extension type "Python", and specify the location of your file.

This example does nothing at all, but should load into Burp without any errors.

Note: Because of the way in which Jython dynamically generates Java classes, you may encounter memory problems if you load several different Python extensions, or if you unload and reload a Python extension multiple times. If this happens, you will see an error like:

java.lang.OutOfMemoryError: PermGen space

You can avoid this problem by configuring Java versions lower than 8 to allocate more PermGen storage, by adding a XX:MaxPermSize option to the command line when starting Burp. For example:

java -XX:MaxPermSize=1G -jar burp.jar

This option is not available in Java 8+, and permgen size should instead be automatically managed by the JVM.

Burp relies on JRuby to provide its Ruby support. You will need to download the "Complete .jar" version and configure Burp with its location (at Extender / Options / Ruby environment).

Now create a new file with any name you like, ending in '.rb', and add the following content to that file:

require 'java'
java_import 'burp.IBurpExtender'

class BurpExtender include IBurpExtender

def registerExtenderCallbacks(callbacks) # your extension code here end
end

Then go to the Extensions tab, and add a new extension. Select the extension type "Ruby", and specify the location of your file.

This example does nothing at all, but should load into Burp without any errors.

Useful APIs

The first important thing to note about programming extensions for Burp is that, for the most part, the data you will be inspecting is provided in the form of byte arrays (byte[]) which might take some getting used to if you'd normally program with strings. It's important to understand that while it is possible to convert from byte to a string, this process is not entirely trivial and may bloat the memory usage of your extension. In some cases this is unavoidable (e.g. you need to execute a regex against a request/response), but on the whole you should try to stick to working with bytes directly.

Burp will provide various data objects to you which model HTTP requests, responses, parameters, cookies, etc. as well as Burp-specific items such as issues. We will discuss a number of these models under the assumption that you are familiar with HTTP and with Burp as a tool.

Assuming a starting point of the blank extension you created above, because you've implemented the IBurpExtender interface, the entry point for your extension will be the method:

void registerExtenderCallbacks (IBurpExtenderCallbacks callbacks);

Sidenote: as the Python and Ruby compatibility is provided via a translation layer to Java bytecode, we will talk about all interfaces in the strictest sense. To read them as Python or Ruby functions you can simply drop the types. e.g. in Python:

def registerExtenderCallbacks self, callbacks)

or in Ruby:

def registerExtenderCallbacks(callbacks)

Using the Java definitions allows us to be as precise as possible, and allows us to discuss the finer points of the translation layer where it's useful. In the interests of clarity some of the method names mentioned in this document have been qualified further with the name of the interface.

The first useful thing you can do with the callbacks object is to tell Burp what your extension is called:

callbacks.setExtensionName (Your extension name);

This allows users to see a pretty name for your extension.

The first thing you're likely to do is to get a copy of the helpers to make your life easier:

IExtensionHelpers helpers = callbacks.getHelpers();

The first tools to check out are your byte utilities:

int indexOf (byte[] data, byte[] pattern, boolean caseSensitive, int from, int to);
String bytesToString(byte[] data);
byte[] stringToBytes(String data);
String urlDecode(String data);
String urlEncode(String data);
byte[] urlDecode(byte[] data);
byte[] urlEncode(byte[] data);
byte[] base64Decode(String data);
byte[] base64Decode(byte[] data);
String base64Encode(String data);
String base64Encode(byte[] data);

These allow you to convert between string and byte where required, and provide you with encodings useful for working with data on the web.

Burp's extender APIs return a few data types to you apart from byte, to model HTTP concepts in a convenient manner. Briefly, those are IHttpRequestResponse which gives you access to the IHttpService (i.e. the domain, port and protocol) and wraps up the request and response. The requests and responses can be further processed via the various overloads for IExtensionHelpers.analyzeRequest() in order for you to inspect parameters, cookies, etc.

A core part of writing an extension is in telling Burp what your extension is capable of dealing with, and this works through a mechanism of registering bits of your code with Burp so that this code can be executed by Burp at a later point. We won't go through all of the functionality provided by the IExtenderCallbacks object; it's best that you browse the source to see what possible functionality Burp understands, but as an example you could register an object (maybe your current one this (in Java) or self (in Python/Ruby) via the following API:

void IBurpExtenderCallbacks.registerExtensionStateListener(IExtensionStateListener listener);

e.g.

callbacks.registerExtensionStateListener(this);

Now, in order to understand what it means to be an IExtensionStateListener you should take a look at the source for that API which is pretty simple since there's only one method to implement:

void IExtensionStateListener.extensionUnloaded();

So you can determine when your extension is unloaded by the user, Burp will inform you of this (if you registered!) in order for you to do some cleanup, like closing a database connection. This is commonly referred to as an "Event-driven architecture", and is common in scenarios where you know that certain events might happen but you don't know when exactly. The registration mechanism allows you to be selective in what you want to deal with, allowing you to focus your efforts.

Often you might want to save some state; maybe you've asked a user for configuration, or simply discovered something that you would like to use later on. The easiest way to do this is to use the following APIs:

void IBurpExtenderCallbacks.saveExtensionSetting(String name, String value);
String IBurpExtenderCallbacks.loadExtensionSetting(String name);

The reason these APIs exist and are useful is that they avoid the complication of managing files or databases ourselves. For simple saving and re-loading of data you should always prefer to use these mechanisms rather than introducing the headache of filesystems, permissions, and differences across operating systems.

Another API that helps you to avoid managing files is:

ITempFile IBurpExtenderCallbacks.saveToTempFile(byte[] buffer);
byte[] ITempFile.getBuffer();

This is useful in case you are dealing with large amounts of data and do not wish to bloat your memory usage. Note that ITempFile.delete() is deprecated; temp files are removed automatically by Burp on shutdown.

A special note on dealing with Java arrays in Python & Ruby

As mentioned previously, Python and Ruby compatibility is achieved via a translation layer and this implementation detail leaks out on occasion. You will notice this mostly when dealing with Java arrays, and most often with bytes.

To create a byte-compatible object to pass to the Burp Extender APIs:

bytearray("foo") # => new byte[] {'f', 'o', 'o'}

To convert an existing list to a Java array:

from jarray import array
array([1, 2, 3], 'i') ># => new int[] {1, 2, 3}

Note the 'i' which corresponds to "integer". The various primitive type names can be found in the Jython documentation.

To create a byte-compatible object to pass to the Burp Extender APIs:

"foo".bytes.to_a # => new byte[] {'f', 'o', 'o'}

To convert an existing array to a Java array:

[1, 2, 3].to_java :int # => new int[] {1, 2, 3}

Note the :int which corresponds to "integer". The various primitive type names can be found in the JRuby documentation.

More examples

Some of the Burp APIs have been highlighted in example extensions at the bottom of the official extensibility page, their source code contains comments that explain the code, providing a rich resource for learning the various available APIs in a practical setting.

Burp Community

For more help and examples of Burp extensions, you can refer to the Burp Extensions community discussions in the Support Center.

Take a look