├── .gitignore
├── BappDescription.html
├── BappManifest.bmf
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── screenshot-client.png
└── screenshot-tab.png
├── src
└── main
│ └── java
│ ├── burp
│ ├── BurpExtender.java
│ ├── LimitedHashMap.java
│ └── TaboratorMessageEditorController.java
│ └── com
│ └── coreyd97
│ └── BurpExtenderUtilities
│ ├── Alignment.java
│ ├── BurpExtensionLogger.java
│ ├── ButtonRenderer.java
│ ├── ComponentGroup.java
│ ├── CustomTabComponent.java
│ ├── DefaultGsonProvider.java
│ ├── FixCookie.java
│ ├── HistoryField.java
│ ├── IGsonProvider.java
│ ├── ILogProvider.java
│ ├── PanelBuilder.java
│ ├── PopOutPanel.java
│ ├── PreferenceFactory.java
│ ├── PreferenceListener.java
│ ├── Preferences.java
│ ├── ProjectSettingStore.java
│ ├── ScrollablePanel.java
│ ├── StdOutLogger.java
│ ├── TypeAdapter
│ ├── AtomicIntegerTypeAdapter.java
│ └── ByteArrayToBase64TypeAdapter.java
│ └── VariableViewPanel.java
└── taborator.iml
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | build/
3 | .gradle/
4 | #intellij
5 | .idea/
6 | .classpath/
7 | .project/
--------------------------------------------------------------------------------
/BappDescription.html:
--------------------------------------------------------------------------------
1 |
This extension shows the Collaborator client in a tab, and provides notifications of new interactions in the tab title.
2 |
3 | To use the extension right click in a repeater tab and choose Taborator->Insert Collaborator payload. This will create a Collaborator payload that is specific to the extension.
4 | You can also use Taborator->Insert Collaborator placeholder this will create a placeholder that is replaced with a Collaborator payload when the message is sent.
5 | The advantage of this is that the original request will be stored and shown in the interactions.
6 |
7 | Copyright © 2019-2022 PortSwigger Ltd.
8 |
--------------------------------------------------------------------------------
/BappManifest.bmf:
--------------------------------------------------------------------------------
1 | Uuid: c9c37e424a744aa08866652f63ee9e0f
2 | ExtensionType: 1
3 | Name: Taborator
4 | RepoName: taborator
5 | ScreenVersion: 2.1.6
6 | SerialVersion: 16
7 | MinPlatformVersion: 0
8 | ProOnly: True
9 | Author: Gareth Heyes, PortSwigger web security
10 | ShortDescription: Improved Collaborator client in its own tab
11 | EntryPoint: build/libs/taborator.jar
12 | BuildCommand: ./gradlew jar
13 | SupportedProducts: Pro
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Taborator
2 | A Burp extension to show the Collaborator client in a tab along with the number of interactions in the tab name.
3 |
4 | # Installation and usage
5 |
6 | To use the extension right click in a repeater tab and choose Taborator->Insert Collaborator payload. This will create a Collaborator payload that is specific to the extension. You can also use Taborator->Insert Collaborator placeholder this will create a placeholder that is replaced with a Collaborator payload. The advantage of this is the original request will be stored and shown in the interactions.
7 |
8 | Install from the BApp store.
9 |
10 | # Screenshots
11 |
12 | 
13 |
14 | 
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | ext {
4 | isDev = false
5 | }
6 |
7 | sourceCompatibility = 1.8
8 | targetCompatibility = 1.8
9 |
10 | repositories {
11 | mavenLocal()
12 |
13 | if (isDev) {
14 | maven {
15 | url 'http://dev-registry.portswigger.com/repository/maven-public'
16 | }
17 | }
18 | else {
19 | mavenCentral()
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'lib', include: '*.jar')
25 | compileOnly 'net.portswigger.burp.extender:burp-extender-api:1.7.22'
26 | compile 'com.google.code.gson:gson:2.8.5'
27 | }
28 |
29 | jar {
30 | from {
31 | configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackvertor/taborator/1ef7f266e77dd6fe0d2742214e2c376d559063a4/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/images/screenshot-client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackvertor/taborator/1ef7f266e77dd6fe0d2742214e2c376d559063a4/images/screenshot-client.png
--------------------------------------------------------------------------------
/images/screenshot-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackvertor/taborator/1ef7f266e77dd6fe0d2742214e2c376d559063a4/images/screenshot-tab.png
--------------------------------------------------------------------------------
/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.coreyd97.BurpExtenderUtilities.DefaultGsonProvider;
4 | import com.coreyd97.BurpExtenderUtilities.ILogProvider;
5 | import com.coreyd97.BurpExtenderUtilities.Preferences;
6 | import com.coreyd97.BurpExtenderUtilities.ProjectSettingStore;
7 | import com.google.gson.reflect.TypeToken;
8 |
9 | import javax.swing.*;
10 | import javax.swing.event.DocumentEvent;
11 | import javax.swing.event.DocumentListener;
12 | import javax.swing.table.DefaultTableCellRenderer;
13 | import javax.swing.table.DefaultTableModel;
14 | import javax.swing.table.TableModel;
15 | import javax.swing.table.TableRowSorter;
16 | import java.awt.*;
17 | import java.awt.datatransfer.StringSelection;
18 | import java.awt.event.*;
19 | import java.io.*;
20 | import java.net.MalformedURLException;
21 | import java.net.URL;
22 | import java.nio.*;
23 | import java.util.*;
24 | import java.util.List;
25 | import java.util.regex.Matcher;
26 | import java.util.regex.Pattern;
27 |
28 | public class BurpExtender implements IBurpExtender, ITab, IExtensionStateListener, IContextMenuFactory, IHttpListener {
29 | private String extensionName = "Taborator";
30 | private String extensionVersion = "2.1.6";
31 | private int maxHashMapSize = 10000;
32 | private IBurpExtenderCallbacks callbacks;
33 | private IExtensionHelpers helpers;
34 | private PrintWriter stderr;
35 | private PrintWriter stdout;
36 | private JPanel panel;
37 | private volatile boolean running;
38 | private int unread = 0;
39 | private ArrayList readRows = new ArrayList<>();
40 | private IBurpCollaboratorClientContext collaborator = null;
41 | private HashMap> interactionHistory = new HashMap<>();
42 | private HashMap> originalRequests = new LimitedHashMap<>(maxHashMapSize);
43 | private HashMap originalResponses = new LimitedHashMap<>(maxHashMapSize);
44 | private JTabbedPane interactionsTab;
45 | private Integer selectedRow = -1;
46 | private HashMap colours = new HashMap<>();
47 | private HashMap textColours = new HashMap<>();
48 | private HashMap comments = new HashMap<>();
49 | private static final String COLLABORATOR_PLACEHOLDER = "$collabplz";
50 | private Thread pollThread;
51 | private long POLL_EVERY_MS = 10000;
52 | private boolean pollNow = false;
53 | private boolean createdCollaboratorPayload = false;
54 | private int pollCounter = 0;
55 | private boolean shutdown = false;
56 | private boolean isSleeping = false;
57 | private Preferences prefs;
58 | private Integer rowNumber = 0;
59 | private DefaultTableModel model;
60 | private JTable collaboratorTable;
61 | private TableRowSorter sorter = null;
62 | private Color defaultTabColour;
63 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
64 | shutdown = false;
65 | isSleeping = false;
66 | helpers = callbacks.getHelpers();
67 | this.callbacks = callbacks;
68 | callbacks.registerExtensionStateListener(this);
69 | callbacks.registerContextMenuFactory(this);
70 | callbacks.registerHttpListener(this);
71 | stderr = new PrintWriter(callbacks.getStderr(), true);
72 | stdout = new PrintWriter(callbacks.getStdout(), true);
73 | callbacks.setExtensionName(extensionName);
74 | defaultTabColour = getDefaultTabColour();
75 | DefaultGsonProvider gsonProvider = new DefaultGsonProvider();
76 |
77 | prefs = new Preferences("Taborator", gsonProvider, new ILogProvider() {
78 | @Override
79 | public void logOutput(String message) {
80 | //System.out.println("Output:"+message);
81 | }
82 |
83 | @Override
84 | public void logError(String errorMessage) {
85 | System.err.println("Error Output:"+errorMessage);
86 | }
87 | }, callbacks);
88 | SwingUtilities.invokeLater(new Runnable() {
89 | public void run() {
90 | stdout.println(extensionName + " " + extensionVersion);
91 | stdout.println("To use Taborator right click in the repeater request tab and select \"Taborator->Insert Collaborator payload\". Use \"Taborator->Insert Collaborator placeholder\" to insert a placeholder that will be replaced by a Collaborator payload in every request. The Taborator placeholder also works in other Burp tools. You can also use the buttons in the Taborator tab to create a payload and poll now.");
92 | running = true;
93 | try {
94 | prefs.registerSetting("config", new TypeToken>() {
95 | }.getType(),new HashMap<>(),Preferences.Visibility.PROJECT);
96 | prefs.registerSetting("readRows", new TypeToken>() {
97 | }.getType(), new ArrayList(), Preferences.Visibility.PROJECT);
98 | prefs.registerSetting("interactionHistory", new TypeToken>>() {
99 | }.getType(), new HashMap<>(), Preferences.Visibility.PROJECT);
100 | prefs.registerSetting("originalRequests", new TypeToken>>() {
101 | }.getType(), new LimitedHashMap<>(maxHashMapSize), Preferences.Visibility.PROJECT);
102 | prefs.registerSetting("originalResponses", new TypeToken>() {
103 | }.getType(), new LimitedHashMap<>(maxHashMapSize), Preferences.Visibility.PROJECT);
104 | prefs.registerSetting("comments", new TypeToken>() {
105 | }.getType(), new HashMap<>(), Preferences.Visibility.PROJECT);
106 | prefs.registerSetting("colours", new TypeToken>() {
107 | }.getType(), new HashMap<>(), Preferences.Visibility.PROJECT);
108 | prefs.registerSetting("textColours", new TypeToken>() {
109 | }.getType(), new HashMap<>(), Preferences.Visibility.PROJECT);
110 | } catch(Throwable e) {
111 | System.err.println("Error registering settings:"+e);
112 | }
113 | panel = new JPanel(new BorderLayout());
114 | JPanel topPanel = new JPanel();
115 | topPanel.setLayout(new GridBagLayout());
116 | JButton exportBtn = new JButton("Export");
117 | exportBtn.setPreferredSize(new Dimension(80,30));
118 | exportBtn.addActionListener(new ActionListener() {
119 | @Override
120 | public void actionPerformed(ActionEvent e) {
121 | JFrame frame = new JFrame();
122 | JFileChooser fileChooser = new JFileChooser();
123 | fileChooser.setDialogTitle("Please choose where to save interactions");
124 | int userSelection = fileChooser.showSaveDialog(frame);
125 | if (userSelection == JFileChooser.APPROVE_OPTION) {
126 | File fileToSave = fileChooser.getSelectedFile();
127 | String filePath = fileToSave.getAbsolutePath();
128 | ProjectSettingStore projectSettingStore = prefs.getProjectSettingsStore();
129 | saveSettings();
130 | String jsonStr = projectSettingStore.getJSONSettings();
131 | FileWriter file = null;
132 | try {
133 | file = new FileWriter(filePath);
134 | file.write(jsonStr);
135 | } catch (IOException ex) {
136 | ex.printStackTrace();
137 | } finally {
138 | try {
139 | file.flush();
140 | file.close();
141 | } catch (IOException ex) {
142 | ex.printStackTrace();
143 | }
144 | }
145 | }
146 | }
147 | });
148 | JLabel searchText = new JLabel("Search (IP,Host):");
149 | JTextField keywordSearch = new JTextField();
150 | keywordSearch.setPreferredSize(new Dimension(160, 30));
151 | JComboBox filter = new JComboBox();
152 | filter.setPreferredSize(new Dimension(160, 30));
153 | filter.addItem("Show all types");
154 | filter.addItem("DNS");
155 | filter.addItem("HTTP");
156 | filter.addItem("SMTP");
157 |
158 | RowFilter rowFilter = new RowFilter() {
159 | @Override
160 | public boolean include(RowFilter.Entry extends TableModel,? extends Integer> row) {
161 | String keyword = keywordSearch.getText();
162 | Boolean hasFilter = filter.getSelectedIndex() > 0;
163 | Boolean hasKeyword = !keyword.equals("");
164 | if(!hasFilter && !hasKeyword) {
165 | return true;
166 | }
167 | if(hasKeyword && hasFilter) {
168 | return (row.getStringValue(3).contains(keyword) || row.getStringValue(4).contains(keyword)) && row.getValue(2).equals(filter.getSelectedItem().toString());
169 | } else if(hasKeyword) {
170 | return row.getStringValue(3).contains(keyword) || row.getStringValue(4).contains(keyword);
171 | } else if(hasFilter) {
172 | return row.getValue(2).equals(filter.getSelectedItem().toString());
173 | } else {
174 | return true;
175 | }
176 | }
177 | };
178 | filter.addActionListener(new ActionListener() {
179 | @Override
180 | public void actionPerformed(ActionEvent e) {
181 | sorter.setRowFilter(rowFilter);
182 | }
183 | });
184 | keywordSearch.getDocument().addDocumentListener(new DocumentListener() {
185 | @Override
186 | public void insertUpdate(DocumentEvent e) {
187 | sorter.setRowFilter(rowFilter);
188 | }
189 |
190 | @Override
191 | public void removeUpdate(DocumentEvent e) {
192 | sorter.setRowFilter(rowFilter);
193 | }
194 |
195 | @Override
196 | public void changedUpdate(DocumentEvent e) {
197 | sorter.setRowFilter(rowFilter);
198 | }
199 | });
200 | JButton createCollaboratorPayloadWithTaboratorCmd = new JButton("Taborator commands & copy");
201 | createCollaboratorPayloadWithTaboratorCmd.setPreferredSize(new Dimension(200, 30));
202 | createCollaboratorPayloadWithTaboratorCmd.addActionListener(new ActionListener() {
203 | @Override
204 | public void actionPerformed(ActionEvent e) {
205 | createdCollaboratorPayload = true;
206 | String payload = collaborator.generatePayload(true) + "?TaboratorCmd=comment:Test;bgColour:0x000000;textColour:0xffffff";
207 | Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(payload),null);
208 | }
209 | });
210 | JLabel generateMsg = new JLabel("Number to generate:");
211 | JButton pollButton = new JButton("Poll now");
212 | JTextField numberOfPayloads = new JTextField("1");
213 | numberOfPayloads.setPreferredSize(new Dimension(50, 30));
214 | JButton createCollaboratorPayload = new JButton("Create payload & copy");
215 | createCollaboratorPayload.addActionListener(new ActionListener() {
216 | @Override
217 | public void actionPerformed(ActionEvent e) {
218 | createdCollaboratorPayload = true;
219 | int amount = 1;
220 | try {
221 | amount = Integer.parseInt(numberOfPayloads.getText());
222 | } catch (NumberFormatException ex) {
223 | amount = 1;
224 | }
225 | StringBuilder payloads = new StringBuilder();
226 | payloads.append(collaborator.generatePayload(true));
227 | for(int i=1;i getColumnClass(int columnIndex) {
275 | if (columnIndex < classes.length)
276 | return classes[columnIndex];
277 | return super.getColumnClass(columnIndex);
278 | }
279 | };
280 | collaboratorTable = new JTable(model);
281 | sorter = new TableRowSorter<>(model);
282 | collaboratorTable.setRowSorter(sorter);
283 | model.addColumn("#");
284 | model.addColumn("Time");
285 | model.addColumn("Type");
286 | model.addColumn("IP");
287 | model.addColumn("Hostname");
288 | model.addColumn("Comment");
289 | collaboratorTable.getColumnModel().getColumn(0).setPreferredWidth(50);
290 | collaboratorTable.getColumnModel().getColumn(0).setMaxWidth(50);
291 | collaboratorTable.getColumnModel().getColumn(2).setPreferredWidth(80);
292 | collaboratorTable.getColumnModel().getColumn(2).setMaxWidth(80);
293 | JPopupMenu popupMenu = new JPopupMenu();
294 | JMenuItem commentMenuItem = new JMenuItem("Add comment");
295 | commentMenuItem.addActionListener(new ActionListener() {
296 | @Override
297 | public void actionPerformed(ActionEvent e) {
298 | int rowNum = collaboratorTable.getSelectedRow();
299 | if(rowNum > -1) {
300 | int realRowNum = collaboratorTable.convertRowIndexToModel(rowNum);
301 | String comment = JOptionPane.showInputDialog("Please enter a comment");
302 | collaboratorTable.getModel().setValueAt(comment, realRowNum, 5);
303 | if(comment.length() == 0) {
304 | if(comments.containsKey(realRowNum)) {
305 | comments.remove(realRowNum);
306 | }
307 | } else {
308 | comments.put(realRowNum, comment);
309 | }
310 | }
311 | }
312 | });
313 | popupMenu.add(commentMenuItem);
314 | JMenu highlightMenu = new JMenu("Highlight");
315 | highlightMenu.add(generateMenuItem(collaboratorTable, null, "HTTP", null));
316 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xfa6364"), "HTTP", Color.white));
317 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xfac564"), "HTTP", Color.black));
318 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xfafa64"), "HTTP", Color.black));
319 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0x63fa64"), "HTTP", Color.black));
320 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0x63fafa"), "HTTP", Color.black));
321 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0x6363fa"), "HTTP", Color.white));
322 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xfac5c5"), "HTTP", Color.black));
323 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xfa63fa"), "HTTP", Color.black));
324 | highlightMenu.add(generateMenuItem(collaboratorTable, Color.decode("0xb1b1b1"), "HTTP", Color.black));
325 | popupMenu.add(highlightMenu);
326 | JMenuItem markReadMenuItem = new JMenuItem("Mark all as read");
327 | markReadMenuItem.addActionListener(new ActionListener() {
328 | @Override
329 | public void actionPerformed(ActionEvent e) {
330 | int answer = JOptionPane.showConfirmDialog(null,"This will mark all interactions as read, are you sure?");
331 | TableModel model = (DefaultTableModel) collaboratorTable.getModel();
332 | if(answer == 0) {
333 | readRows = new ArrayList<>();
334 | for(int i=0;i();
351 | readRows = new ArrayList<>();
352 | unread = 0;
353 | rowNumber = 0;
354 | colours = new HashMap<>();
355 | textColours = new HashMap<>();
356 | comments = new HashMap<>();
357 | ((DefaultTableModel) model).setRowCount(0);
358 | interactionsTab.removeAll();
359 | selectedRow = -1;
360 | updateTab(false);
361 | }
362 | collaboratorTable.clearSelection();
363 | }
364 | });
365 | JMenuItem clearOriginalReqResItem = new JMenuItem("Clear original requests/responses");
366 | clearOriginalReqResItem.addActionListener(new ActionListener() {
367 | @Override
368 | public void actionPerformed(ActionEvent e) {
369 | int answer = JOptionPane.showConfirmDialog(null,"This will remove all req/res history from placeholder usage, are you sure?");
370 | if(answer == 0) {
371 | originalRequests = new LimitedHashMap<>(maxHashMapSize);
372 | originalResponses = new LimitedHashMap<>(maxHashMapSize);
373 | }
374 | collaboratorTable.clearSelection();
375 | }
376 | });
377 | popupMenu.add(clearOriginalReqResItem);
378 | popupMenu.add(clearMenuItem);
379 | popupMenu.add(markReadMenuItem);
380 | collaboratorTable.setComponentPopupMenu(popupMenu);
381 |
382 | JScrollPane collaboratorScroll = new JScrollPane(collaboratorTable);
383 | collaboratorTable.setFillsViewportHeight(true);
384 | collaboratorClientSplit.setTopComponent(collaboratorScroll);
385 | collaboratorClientSplit.setBottomComponent(new JPanel());
386 | panel.add(collaboratorClientSplit, BorderLayout.CENTER);
387 | callbacks.addSuiteTab(BurpExtender.this);
388 | collaborator = callbacks.createBurpCollaboratorClientContext();
389 | DefaultTableCellRenderer tableCellRender = new DefaultTableCellRenderer()
390 | {
391 | @Override
392 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
393 | {
394 | final Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
395 | int modelRow = table.convertRowIndexToModel(row);
396 | int id = (int) table.getModel().getValueAt(modelRow, 0);
397 | putClientProperty("html.disable", Boolean.TRUE);
398 | if(isSelected) {
399 | if(!readRows.contains(id)) {
400 | c.setFont(c.getFont().deriveFont(Font.PLAIN));
401 | readRows.add(id);
402 | unread--;
403 | }
404 | if(selectedRow != row && collaboratorTable.getSelectedRowCount() == 1) {
405 | JPanel descriptionPanel = new JPanel(new BorderLayout());
406 | HashMap interaction = interactionHistory.get(id);
407 | JTextArea description = new JTextArea();
408 | description.setEditable(false);
409 | description.setBorder(null);
410 | interactionsTab.removeAll();
411 | interactionsTab.addTab("Description", descriptionPanel);
412 | if(interaction.get("type").equals("DNS")) {
413 | TaboratorMessageEditorController taboratorMessageEditorController = new TaboratorMessageEditorController();
414 | description.setText("The Collaborator server received a DNS lookup of type " + interaction.get("query_type") + " for the hostname " + interaction.get("hostname") + "\n\n" +
415 | "The lookup was received from IP address " + interaction.get("client_ip") + " at " + interaction.get("time_stamp"));
416 | IMessageEditor messageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
417 | messageEditor.setMessage(helpers.base64Decode(interaction.get("raw_query")), false);
418 | if(originalRequests.containsKey(interaction.get("interaction_id"))) {
419 | HashMap requestInfo = originalRequests.get(interaction.get("interaction_id"));
420 | IHttpService httpService = helpers.buildHttpService(requestInfo.get("host"), Integer.decode(requestInfo.get("port")), requestInfo.get("protocol"));
421 | taboratorMessageEditorController.setHttpService(httpService);
422 | IMessageEditor requestMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
423 | if (requestInfo.get("request") != null) {
424 | requestMessageEditor.setMessage(helpers.stringToBytes(requestInfo.get("request")), true);
425 | interactionsTab.addTab("Original request", requestMessageEditor.getComponent());
426 | }
427 | if (originalResponses.containsKey(interaction.get("interaction_id"))) {
428 | taboratorMessageEditorController.setHttpService(httpService);
429 | IMessageEditor responseMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
430 | if (requestInfo.get("request") != null && originalResponses.get(interaction.get("interaction_id")) != null) {
431 | responseMessageEditor.setMessage(helpers.stringToBytes(originalResponses.get(interaction.get("interaction_id"))), true);
432 | interactionsTab.addTab("Original response", responseMessageEditor.getComponent());
433 | }
434 | }
435 | }
436 | interactionsTab.addTab("DNS query", messageEditor.getComponent());
437 | } else if(interaction.get("type").equals("SMTP")) {
438 | byte[] conversation = helpers.base64Decode(interaction.get("conversation"));
439 | String conversationString = helpers.bytesToString(conversation);
440 | String to = "";
441 | String from = "";
442 | String message = "";
443 | Matcher m = Pattern.compile("^RCPT TO:(.+?)$", Pattern.CASE_INSENSITIVE + Pattern.MULTILINE).matcher(conversationString);
444 | if(m.find()) {
445 | to = m.group(1).trim();
446 | }
447 | m = Pattern.compile("^MAIL From:(.+)?$", Pattern.CASE_INSENSITIVE + Pattern.MULTILINE).matcher(conversationString);
448 | if(m.find()) {
449 | from = m.group(1).trim();
450 | }
451 | m = Pattern.compile("^DATA[\\r\\n]+([\\d\\D]+)?[\\r\\n]+[.][\\r\\n]+", Pattern.CASE_INSENSITIVE + Pattern.MULTILINE).matcher(conversationString);
452 | if(m.find()) {
453 | message = m.group(1).trim();
454 | }
455 | TaboratorMessageEditorController taboratorMessageEditorController = new TaboratorMessageEditorController();
456 | description.setText(
457 | "The Collaborator server received a SMTP connection from IP address " + interaction.get("client_ip") + " at " + interaction.get("time_stamp") + ".\n\n" +
458 | "The email details were:\n\n" +
459 | "From: " + from + "\n\n" +
460 | "To: " + to + "\n\n" +
461 | "Message: \n" + message
462 | );
463 | IMessageEditor messageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
464 | messageEditor.setMessage(conversation, false);
465 | if(originalRequests.containsKey(interaction.get("interaction_id"))) {
466 | HashMap requestInfo = originalRequests.get(interaction.get("interaction_id"));
467 | IHttpService httpService = helpers.buildHttpService(requestInfo.get("host"), Integer.decode(requestInfo.get("port")), requestInfo.get("protocol"));
468 | taboratorMessageEditorController.setHttpService(httpService);
469 | IMessageEditor requestMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
470 | if (requestInfo.get("request") != null) {
471 | requestMessageEditor.setMessage(helpers.stringToBytes(requestInfo.get("request")), true);
472 | interactionsTab.addTab("Original request", requestMessageEditor.getComponent());
473 | }
474 | if (originalResponses.containsKey(interaction.get("interaction_id"))) {
475 | taboratorMessageEditorController.setHttpService(httpService);
476 | IMessageEditor responseMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
477 | if (requestInfo.get("request") != null && originalResponses.get(interaction.get("interaction_id")) != null) {
478 | responseMessageEditor.setMessage(helpers.stringToBytes(originalResponses.get(interaction.get("interaction_id"))), true);
479 | interactionsTab.addTab("Original response", responseMessageEditor.getComponent());
480 | }
481 | }
482 | }
483 | interactionsTab.addTab("SMTP Conversation", messageEditor.getComponent());
484 | interactionsTab.setSelectedIndex(1);
485 | } else if(interaction.get("type").equals("HTTP")) {
486 | TaboratorMessageEditorController taboratorMessageEditorController = new TaboratorMessageEditorController();
487 | URL collaboratorURL = null;
488 | try {
489 | collaboratorURL = new URL(interaction.get("protocol").toLowerCase()+"://"+collaborator.getCollaboratorServerLocation());
490 | } catch (MalformedURLException e) {
491 | stderr.println("Failed parsing Collaborator URL:"+e.toString());
492 | }
493 | if(collaboratorURL != null) {
494 | IHttpService httpService = helpers.buildHttpService(collaboratorURL.getHost(), collaboratorURL.getPort() == -1 ? collaboratorURL.getDefaultPort() : collaboratorURL.getPort(), interaction.get("protocol").equals("HTTPS"));
495 | taboratorMessageEditorController.setHttpService(httpService);
496 | }
497 | byte[] collaboratorResponse = helpers.base64Decode(interaction.get("response"));
498 | byte[] collaboratorRequest = helpers.base64Decode(interaction.get("request"));
499 | taboratorMessageEditorController.setRequest(collaboratorRequest);
500 | taboratorMessageEditorController.setResponse(collaboratorResponse);
501 | description.setText("The Collaborator server received an "+interaction.get("protocol")+" request.\n\nThe request was received from IP address "+interaction.get("client_ip")+" at "+interaction.get("time_stamp") + " for the hostname " + interaction.get("hostname"));
502 | if(originalRequests.containsKey(interaction.get("interaction_id"))) {
503 | HashMap requestInfo = originalRequests.get(interaction.get("interaction_id"));
504 | IHttpService httpService = helpers.buildHttpService(requestInfo.get("host"), Integer.decode(requestInfo.get("port")), requestInfo.get("protocol"));
505 | taboratorMessageEditorController.setHttpService(httpService);
506 | IMessageEditor requestMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
507 | if (requestInfo.get("request") != null) {
508 | requestMessageEditor.setMessage(helpers.stringToBytes(requestInfo.get("request")), true);
509 | interactionsTab.addTab("Original request", requestMessageEditor.getComponent());
510 | }
511 | if (originalResponses.containsKey(interaction.get("interaction_id"))) {
512 | taboratorMessageEditorController.setHttpService(httpService);
513 | IMessageEditor responseMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
514 | if (requestInfo.get("request") != null && originalResponses.get(interaction.get("interaction_id")) != null) {
515 | responseMessageEditor.setMessage(helpers.stringToBytes(originalResponses.get(interaction.get("interaction_id"))), true);
516 | interactionsTab.addTab("Original response", responseMessageEditor.getComponent());
517 | }
518 | }
519 | }
520 | IMessageEditor requestMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
521 | requestMessageEditor.setMessage(collaboratorRequest, true);
522 | interactionsTab.addTab("Request to Collaborator", requestMessageEditor.getComponent());
523 | IMessageEditor responseMessageEditor = callbacks.createMessageEditor(taboratorMessageEditorController, false);
524 | responseMessageEditor.setMessage(collaboratorResponse, true);
525 | interactionsTab.addTab("Response from Collaborator", responseMessageEditor.getComponent());
526 | interactionsTab.setSelectedIndex(1);
527 | }
528 | description.setBorder(BorderFactory.createCompoundBorder(description.getBorder(), BorderFactory.createEmptyBorder(10, 10, 10, 10)));
529 | descriptionPanel.add(description);
530 | collaboratorClientSplit.setBottomComponent(interactionsTab);
531 | selectedRow = row;
532 | updateTab(false);
533 | setDividerLocation(collaboratorClientSplit, 0.5);
534 | }
535 | } else {
536 | if(!readRows.contains(id)) {
537 | c.setFont(c.getFont().deriveFont(Font.BOLD));
538 | }
539 | }
540 | if(colours.containsKey(id) && isSelected) {
541 | if(colours.get(id) == null) {
542 | setBackground(colours.get(id));
543 | colours.remove(id);
544 | textColours.remove(id);
545 | } else {
546 | setBackground(colours.get(id).darker());
547 | }
548 | setForeground(textColours.get(id));
549 | table.repaint();
550 | table.validate();
551 | } else if(colours.containsKey(id)) {
552 | setBackground(colours.get(id));
553 | setForeground(textColours.get(id));
554 | } else if(isSelected) {
555 | if(UIManager.getLookAndFeel().getID().equals("Darcula")) {
556 | setBackground(Color.decode("0x0d293e"));
557 | setForeground(Color.white);
558 | } else {
559 | setBackground(Color.decode("0xffc599"));
560 | setForeground(Color.black);
561 | }
562 | } else {
563 | setBackground(null);
564 | setForeground(null);
565 | }
566 | return c;
567 | }
568 | };
569 | collaboratorTable.setDefaultRenderer(Object.class, tableCellRender);
570 | collaboratorTable.setDefaultRenderer(Number.class, tableCellRender);
571 | Runnable collaboratorRunnable = new Runnable() {
572 | public void run() {
573 | stdout.println("Taborator running...");
574 | loadSettings();
575 | for (Map.Entry> data : interactionHistory.entrySet()) {
576 | int id = data.getKey();
577 | HashMap interaction = data.getValue();
578 | insertInteraction(interaction, id);
579 | }
580 | if(unread > 0) {
581 | updateTab(true);
582 | }
583 |
584 | while(running){
585 | if(pollNow) {
586 | List interactions = collaborator.fetchAllCollaboratorInteractions();
587 | if(interactions.size() > 0) {
588 | insertInteractions(interactions);
589 | }
590 | pollNow = false;
591 | }
592 | try {
593 | isSleeping = true;
594 | pollThread.sleep(POLL_EVERY_MS);
595 | isSleeping = false;
596 | pollCounter++;
597 | if(pollCounter > 5) {
598 | if(createdCollaboratorPayload) {
599 | pollNow = true;
600 | }
601 | pollCounter = 0;
602 | }
603 | } catch (InterruptedException e) {
604 | if(shutdown) {
605 | stdout.println("Taborator shutdown.");
606 | return;
607 | } else {
608 | continue;
609 | }
610 |
611 | }
612 | }
613 | stdout.println("Taborator shutdown.");
614 | }
615 | };
616 | pollThread = new Thread(collaboratorRunnable);
617 | pollThread.start();
618 | }
619 | });
620 | }
621 | private void insertInteraction(HashMap interaction, int rowID) {
622 | model.addRow(new Object[]{rowID,interaction.get("time_stamp"), interaction.get("type"), interaction.get("client_ip"), interaction.get("hostname"), ""});
623 | if(comments.size() > 0) {
624 | int actualID = getRealRowID(rowID);
625 | if(actualID > -1 && comments.containsKey(actualID)) {
626 | String comment = comments.get(actualID);
627 | model.setValueAt(comment, actualID, 5);
628 | }
629 | }
630 | if (interaction.get("type").equals("HTTP")) {
631 | byte[] collaboratorRequest = helpers.base64Decode(interaction.get("request"));
632 | if (helpers.indexOf(collaboratorRequest, helpers.stringToBytes("TaboratorCmd="), true, 0, collaboratorRequest.length) > -1) {
633 | IRequestInfo analyzedRequest = helpers.analyzeRequest(collaboratorRequest);
634 | List params = analyzedRequest.getParameters();
635 | for (int i = 0; i < params.size(); i++) {
636 | if (params.get(i).getName().equals("TaboratorCmd")) {
637 | String[] commands = params.get(i).getValue().split(";");
638 | for (int j = 0; j < commands.length; j++) {
639 | String[] command = commands[j].split(":");
640 | if (command[0].equals("bgColour")) {
641 | try {
642 | Color colour = Color.decode(helpers.urlDecode(command[1]));
643 | colours.put(rowID, colour);
644 | } catch (NumberFormatException e) {
645 |
646 | }
647 | } else if (command[0].equals("textColour")) {
648 | try {
649 | Color colour = Color.decode(helpers.urlDecode(command[1]));
650 | textColours.put(rowID, colour);
651 | } catch (NumberFormatException e) {
652 |
653 | }
654 | } else if (command[0].equals("comment")) {
655 | String comment = helpers.urlDecode(command[1]);
656 | int actualID = getRealRowID(rowID);
657 | if(actualID > -1) {
658 | model.setValueAt(comment, actualID, 5);
659 | }
660 | }
661 | }
662 | break;
663 | }
664 | }
665 | }
666 | }
667 | }
668 | private int getRealRowID(int rowID) {
669 | int rowCount = collaboratorTable.getRowCount();
670 | for (int i = 0; i < rowCount; i++) {
671 | int id = (int) collaboratorTable.getValueAt(i, 0);
672 | if(rowID == id) {
673 | return collaboratorTable.convertRowIndexToView(i);
674 | }
675 | }
676 | return -1;
677 | }
678 | private void loadSettings() {
679 | try {
680 | HashMap config = prefs.getSetting("config");
681 | if(config.size() > 0) {
682 | unread = config.get("unread");
683 | rowNumber = config.get("rowNumber");
684 | }
685 | interactionHistory = prefs.getSetting("interactionHistory");
686 | originalRequests = prefs.getSetting("originalRequests");
687 | originalResponses = prefs.getSetting("originalResponses");
688 | comments = prefs.getSetting("comments");
689 | colours = prefs.getSetting("colours");
690 | textColours = prefs.getSetting("textColours");
691 | readRows = prefs.getSetting("readRows");
692 | } catch(Throwable e) {
693 | System.err.println("Error reading settings:"+e);
694 | }
695 | }
696 | private void saveSettings() {
697 | try {
698 | HashMap config = new HashMap<>();
699 | config.put("unread", unread);
700 | config.put("rowNumber", rowNumber);
701 | prefs.setSetting("config", config);
702 | prefs.setSetting("interactionHistory", interactionHistory);
703 | prefs.setSetting("originalRequests", originalRequests);
704 | prefs.setSetting("originalResponses", originalResponses);
705 | prefs.setSetting("readRows", readRows);
706 | prefs.setSetting("comments", comments);
707 | prefs.setSetting("colours", colours);
708 | prefs.setSetting("textColours", textColours);
709 | } catch (Throwable e) {
710 | System.err.println("Error saving settings:"+e);
711 | }
712 | }
713 | private void insertInteractions(List interactions) {
714 | boolean hasInteractions = false;
715 | for(int i=0;i interactionHistoryItem = new HashMap<>();
718 | rowNumber++;
719 | int rowID = rowNumber;
720 | for (Map.Entry interactionData : interaction.getProperties().entrySet()) {
721 | interactionHistoryItem.put(interactionData.getKey(), interactionData.getValue());
722 | }
723 | interactionHistoryItem.put("hostname", getHostnameFromInteraction(interactionHistoryItem));
724 | insertInteraction(interactionHistoryItem, rowID);
725 | unread++;
726 | interactionHistory.put(rowID, interactionHistoryItem);
727 | hasInteractions = true;
728 | }
729 | updateTab(hasInteractions);
730 | }
731 | @Override
732 | public Component getUiComponent() {
733 | return panel;
734 | }
735 | @Override
736 | public String getTabCaption() {
737 | return unread > 0 ? extensionName + " ("+unread+")" : extensionName;
738 | }
739 |
740 | private void changeTabColour(JTabbedPane tabbedPane, final int tabIndex, boolean hasInteractions) {
741 | if(hasInteractions) {
742 | tabbedPane.setBackgroundAt(tabIndex, new Color(0xff6633));
743 | } else {
744 | tabbedPane.setBackgroundAt(tabIndex, defaultTabColour);
745 | }
746 | }
747 | private Color getDefaultTabColour() {
748 | if(running) {
749 | JTabbedPane tp = (JTabbedPane) BurpExtender.this.getUiComponent().getParent();
750 | int tIndex = getTabIndex(BurpExtender.this);
751 | if (tIndex > -1) {
752 | return tp.getBackgroundAt(tIndex);
753 | }
754 | return new Color(0x000000);
755 | }
756 | return null;
757 | }
758 | private void updateTab(boolean hasInteractions) {
759 | if(running) {
760 | JTabbedPane tp = (JTabbedPane) BurpExtender.this.getUiComponent().getParent();
761 | int tIndex = getTabIndex(BurpExtender.this);
762 | if (tIndex > -1) {
763 | tp.setTitleAt(tIndex, getTabCaption());
764 | changeTabColour(tp, tIndex, hasInteractions);
765 | }
766 | }
767 | }
768 |
769 | private int getTabIndex(ITab your_itab) {
770 | if(running) {
771 | JTabbedPane parent = (JTabbedPane) your_itab.getUiComponent().getParent();
772 | for (int i = 0; i < parent.getTabCount(); ++i) {
773 | if (parent.getTitleAt(i).contains(extensionName)) {
774 | return i;
775 | }
776 | }
777 | }
778 | return -1;
779 | }
780 |
781 | private JMenuItem generateMenuItem(JTable collaboratorTable, Color colour, String text, Color textColour) {
782 | JMenuItem item = new JMenuItem(text);
783 | item.setBackground(colour);
784 | item.setForeground(textColour);
785 | item.setOpaque(true);
786 | item.addActionListener(new ActionListener() {
787 | @Override
788 | public void actionPerformed(ActionEvent e) {
789 | int[] rows = collaboratorTable.getSelectedRows();
790 | for(int i=0;i -1) {
793 | int id = (int) collaboratorTable.getModel().getValueAt(realRow, 0);
794 | colours.put(id, colour);
795 | textColours.put(id, textColour);
796 | }
797 | }
798 | }
799 | });
800 | return item;
801 | }
802 |
803 | public static JSplitPane setDividerLocation(final JSplitPane splitter, final double proportion) {
804 | if (splitter.isShowing()) {
805 | if ((splitter.getWidth() > 0) && (splitter.getHeight() > 0)) {
806 | splitter.setDividerLocation(proportion);
807 | } else {
808 | splitter.addComponentListener(new ComponentAdapter() {
809 | @Override
810 | public void componentResized(ComponentEvent ce) {
811 | splitter.removeComponentListener(this);
812 | setDividerLocation(splitter, proportion);
813 | }
814 | });
815 | }
816 | } else {
817 | splitter.addHierarchyListener(new HierarchyListener() {
818 | @Override
819 | public void hierarchyChanged(HierarchyEvent e) {
820 | if (((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) && splitter.isShowing()) {
821 | splitter.removeHierarchyListener(this);
822 | setDividerLocation(splitter, proportion);
823 | }
824 | }
825 | });
826 | }
827 | return splitter;
828 | }
829 |
830 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
831 | if(messageIsRequest) {
832 | byte[] request = messageInfo.getRequest();
833 | if (helpers.indexOf(request, helpers.stringToBytes(COLLABORATOR_PLACEHOLDER), true, 0, request.length) > -1) {
834 | String requestStr = helpers.bytesToString(request);
835 | Matcher m = Pattern.compile(COLLABORATOR_PLACEHOLDER.replace("$", "\\$")).matcher(requestStr);
836 | ArrayList collaboratorPayloads = new ArrayList<>();
837 | while (m.find()) {
838 | String collaboratorPayloadID = collaborator.generatePayload(false);
839 | collaboratorPayloads.add(collaboratorPayloadID);
840 | requestStr = requestStr.replaceFirst(COLLABORATOR_PLACEHOLDER.replace("$", "\\$"), collaboratorPayloadID + "." + collaborator.getCollaboratorServerLocation());
841 | pollNow = true;
842 | createdCollaboratorPayload = true;
843 | }
844 | request = helpers.stringToBytes(requestStr);
845 | request = fixContentLength(request);
846 | messageInfo.setRequest(request);
847 |
848 | for (int i = 0; i < collaboratorPayloads.size(); i++) {
849 | HashMap originalRequestsInfo = new HashMap<>();
850 | originalRequestsInfo.put("request", helpers.bytesToString(request));
851 | originalRequestsInfo.put("host", messageInfo.getHttpService().getHost());
852 | originalRequestsInfo.put("port", Integer.toString(messageInfo.getHttpService().getPort()));
853 | originalRequestsInfo.put("protocol", messageInfo.getHttpService().getProtocol());
854 | originalRequests.put(collaboratorPayloads.get(i), originalRequestsInfo);
855 | }
856 | }
857 | } else {
858 | byte[] response = messageInfo.getResponse();
859 | byte[] request = messageInfo.getRequest();
860 | for (Map.Entry> entry : originalRequests.entrySet()) {
861 | String payload = entry.getKey();
862 | if(!originalResponses.containsKey(payload) && helpers.indexOf(request,helpers.stringToBytes(payload), true, 0, request.length) > -1) {
863 | originalResponses.put(payload, helpers.bytesToString(response));
864 | }
865 | }
866 | }
867 | }
868 | private GridBagConstraints createConstraints(int x, int y, int gridWidth, int fill) {
869 | GridBagConstraints c = new GridBagConstraints();
870 | c.fill = fill;
871 | c.weightx = 0;
872 | c.weighty = 0;
873 | c.gridx = x;
874 | c.gridy = y;
875 | c.ipadx = 0;
876 | c.ipady = 0;
877 | c.gridwidth = gridWidth;
878 | c.insets = new Insets(5,5,5,5);
879 | return c;
880 | }
881 | public byte[] fixContentLength(byte[] request) {
882 | IRequestInfo analyzedRequest = helpers.analyzeRequest(request);
883 | if (countMatches(request, helpers.stringToBytes("Content-Length: ")) > 0) {
884 | int start = analyzedRequest.getBodyOffset();
885 | int contentLength = request.length - start;
886 | return setHeader(request, "Content-Length", Integer.toString(contentLength));
887 | }
888 | else {
889 | return request;
890 | }
891 | }
892 |
893 | public int[] getHeaderOffsets(byte[] request, String header) {
894 | int i = 0;
895 | int end = request.length;
896 | while (i < end) {
897 | int line_start = i;
898 | while (i < end && request[i++] != ' ') {
899 | }
900 | byte[] header_name = Arrays.copyOfRange(request, line_start, i - 2);
901 | int headerValueStart = i;
902 | while (i < end && request[i++] != '\n') {
903 | }
904 | if (i == end) {
905 | break;
906 | }
907 |
908 | String header_str = helpers.bytesToString(header_name);
909 |
910 | if (header.equals(header_str)) {
911 | int[] offsets = {line_start, headerValueStart, i - 2};
912 | return offsets;
913 | }
914 |
915 | if (i + 2 < end && request[i] == '\r' && request[i + 1] == '\n') {
916 | break;
917 | }
918 | }
919 | return null;
920 | }
921 |
922 | public byte[] setHeader(byte[] request, String header, String value) {
923 | int[] offsets = getHeaderOffsets(request, header);
924 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
925 | try {
926 | outputStream.write( Arrays.copyOfRange(request, 0, offsets[1]));
927 | outputStream.write(helpers.stringToBytes(value));
928 | outputStream.write(Arrays.copyOfRange(request, offsets[2], request.length));
929 | return outputStream.toByteArray();
930 | } catch (IOException e) {
931 | throw new RuntimeException("Request creation unexpectedly failed");
932 | } catch (NullPointerException e) {
933 | throw new RuntimeException("Can't find the header");
934 | }
935 | }
936 |
937 | int countMatches(byte[] response, byte[] match) {
938 | int matches = 0;
939 | if (match.length < 4) {
940 | return matches;
941 | }
942 |
943 | int start = 0;
944 | while (start < response.length) {
945 | start = helpers.indexOf(response, match, true, start, response.length);
946 | if (start == -1)
947 | break;
948 | matches += 1;
949 | start += match.length;
950 | }
951 |
952 | return matches;
953 | }
954 |
955 | private String getHostnameFromInteraction(HashMap interaction) {
956 | String fallback = interaction.get("interaction_id") + "." + collaborator.getCollaboratorServerLocation();
957 | switch(interaction.get("type")) {
958 | case "DNS":
959 | return getHostnameFromDnsRequest(helpers.base64Decode(interaction.get("raw_query")), fallback);
960 | case "HTTP":
961 | return getHostnameFromHttpRequest(helpers.bytesToString(helpers.base64Decode(interaction.get("request"))), fallback);
962 | case "SMTP":
963 | return getHostnameFromSmtpConversation(helpers.bytesToString(helpers.base64Decode(interaction.get("conversation"))), fallback);
964 | default:
965 | return fallback;
966 | }
967 | }
968 |
969 | private static String getHostnameFromDnsRequest(byte[] rawQuery, String fallback) {
970 | StringBuilder hostname = new StringBuilder();
971 | HashSet seenPtrs = new HashSet();
972 |
973 | ByteBuffer bb = ByteBuffer.wrap(rawQuery);
974 |
975 | // Seek to QDCOUNT
976 | bb.position(4);
977 |
978 | // If there is a number of questions other than 1 in the query, something has gone wrong
979 | short num_questions = bb.getShort();
980 | if (num_questions != 1) {
981 | return fallback;
982 | }
983 |
984 | // Seek to Question section
985 | bb.position(12);
986 |
987 | // Read hostname
988 | try {
989 | while (true) {
990 | int token_prefix = bb.get();
991 |
992 | if (token_prefix == 0) {
993 | // Reached the end of the hostname
994 | break;
995 | }
996 |
997 | if ((token_prefix & 0xc0) == 0) {
998 | // It's a length value. Grab the token and follow it up with a dot.
999 | for (int i = 0; i < token_prefix; i++) {
1000 | hostname.append((char) bb.get());
1001 | }
1002 | hostname.append(".");
1003 | } else {
1004 | // It's a special value
1005 | if ((token_prefix & 0xc0) != 0xc0) {
1006 | // It's an illegal (reserved) value
1007 | return fallback;
1008 | }
1009 | // It's a pointer value. See RFC1035 section 4.1.4
1010 | // This isn't necessarily a correct implementation. The Burp Collaborator server doesn't seem to
1011 | // support pointers anyway and we don't really expect to see pointers in DNS queries (?)
1012 |
1013 | // Rewind pos and get the ptr as a short with the high two bits masked off
1014 | bb.position(bb.position() - 1);
1015 | int ptr = bb.getShort() & (0xff - 0xc0);
1016 |
1017 | // Check for loops
1018 | if (seenPtrs.contains(ptr)) {
1019 | return fallback;
1020 | }
1021 | seenPtrs.add(ptr);
1022 |
1023 | // Move to where the pointer points
1024 | bb.position(ptr);
1025 | }
1026 | }
1027 | } catch (BufferUnderflowException | IllegalArgumentException e) {
1028 | // OOB error in the ByteBuffer.get() or .position()
1029 | return fallback;
1030 | }
1031 |
1032 | if (hostname.length() == 0) {
1033 | return hostname.toString();
1034 | } else {
1035 | // Remove the trailing "."
1036 | return hostname.substring(0, hostname.length() - 1);
1037 | }
1038 | }
1039 |
1040 | private static String getHostnameFromHttpRequest(String request, String fallback) {
1041 | String[] lines = request.split("\r\n");
1042 | for (String line : lines) {
1043 | if (line.isEmpty()) {
1044 | break;
1045 | } else if (line.toLowerCase(Locale.ROOT).startsWith("host: ")) {
1046 | return line.split(" ", 2)[1];
1047 | }
1048 | }
1049 | return fallback;
1050 | }
1051 |
1052 | private static String getHostnameFromSmtpConversation(String conversation, String fallback) {
1053 | String[] lines = conversation.split("\r\n");
1054 | Pattern bracketedAddressPat = Pattern.compile("<(.*)>");
1055 | for (String line : lines) {
1056 | if (line.toLowerCase(Locale.ROOT).startsWith("rcpt to:")) {
1057 | String recipient = line.split(":", 2)[1].trim();
1058 | Matcher m = bracketedAddressPat.matcher(recipient);
1059 | if (m.find()) {
1060 | // Parsing email addresses is hard but hopefully we've just found a bracketed email address
1061 | // e.g.
1062 | recipient = m.group(1).trim();
1063 | }
1064 | int pos = recipient.lastIndexOf("@");
1065 | return recipient.substring(pos + 1);
1066 | }
1067 | }
1068 | return fallback;
1069 | }
1070 |
1071 | public List createMenuItems(IContextMenuInvocation invocation) {
1072 | int[] bounds = invocation.getSelectionBounds();
1073 |
1074 | switch (invocation.getInvocationContext()) {
1075 | case IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST:
1076 | break;
1077 | default:
1078 | return null;
1079 | }
1080 | List menu = new ArrayList();
1081 | JMenu submenu = new JMenu(extensionName);
1082 | JMenuItem createPayload = new JMenuItem("Insert Collaborator payload");
1083 | createPayload.addActionListener(e -> {
1084 | if(invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) {
1085 | byte[] message = invocation.getSelectedMessages()[0].getRequest();
1086 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1087 | try {
1088 | outputStream.write(Arrays.copyOfRange(message, 0, bounds[0]));
1089 | outputStream.write(helpers.stringToBytes(collaborator.generatePayload(true)));
1090 | outputStream.write(Arrays.copyOfRange(message, bounds[1],message.length));
1091 | outputStream.flush();
1092 | invocation.getSelectedMessages()[0].setRequest(outputStream.toByteArray());
1093 | pollNow = true;
1094 | createdCollaboratorPayload = true;
1095 | } catch (IOException e1) {
1096 | System.err.println(e1.toString());
1097 | }
1098 | }
1099 | });
1100 | JMenuItem createPlaceholder = new JMenuItem("Insert Collaborator placeholder");
1101 | createPlaceholder.addActionListener(e -> {
1102 | if(invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || invocation.getInvocationContext() == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) {
1103 | byte[] message = invocation.getSelectedMessages()[0].getRequest();
1104 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
1105 | try {
1106 | outputStream.write(Arrays.copyOfRange(message, 0, bounds[0]));
1107 | outputStream.write(helpers.stringToBytes(COLLABORATOR_PLACEHOLDER));
1108 | outputStream.write(Arrays.copyOfRange(message, bounds[1],message.length));
1109 | outputStream.flush();
1110 | invocation.getSelectedMessages()[0].setRequest(outputStream.toByteArray());
1111 | pollNow = true;
1112 | createdCollaboratorPayload = true;
1113 | } catch (IOException e1) {
1114 | System.err.println(e1.toString());
1115 | }
1116 | }
1117 | });
1118 | submenu.add(createPayload);
1119 | submenu.add(createPlaceholder);
1120 | menu.add(submenu);
1121 | return menu;
1122 | }
1123 |
1124 | @Override
1125 | public void extensionUnloaded() {
1126 | shutdown = true;
1127 | running = false;
1128 | stdout.println(extensionName + " unloaded");
1129 | pollThread.interrupt();
1130 | saveSettings();
1131 | }
1132 |
1133 |
1134 | }
--------------------------------------------------------------------------------
/src/main/java/burp/LimitedHashMap.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.text.Utilities;
4 | import java.util.LinkedHashMap;
5 | import java.util.Map;
6 |
7 | // thanks stackoverflow
8 | public class LimitedHashMap extends LinkedHashMap {
9 | private final int maxSize;
10 |
11 | public LimitedHashMap(int maxSize) {
12 | this.maxSize = maxSize;
13 | }
14 |
15 | @Override
16 | protected boolean removeEldestEntry(Map.Entry eldest) {
17 | return size() > maxSize;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/burp/TaboratorMessageEditorController.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | public class TaboratorMessageEditorController implements IMessageEditorController {
4 |
5 | private IHttpService httpService;
6 | private byte[] request;
7 | private byte[] response;
8 | @Override
9 | public IHttpService getHttpService() {
10 | return httpService;
11 | }
12 | public void setHttpService(IHttpService httpService) {
13 | this.httpService = httpService;
14 | }
15 |
16 | @Override
17 | public byte[] getRequest() {
18 | return request;
19 | }
20 | public void setRequest(byte[] request) {
21 | this.request = request;
22 | }
23 | @Override
24 | public byte[] getResponse() {
25 | return response;
26 | }
27 | public void setResponse(byte[] response) {
28 | this.response = response;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/Alignment.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | public enum Alignment {
4 | TOPLEFT, TOPMIDDLE, TOPRIGHT,
5 | MIDDLELEFT, CENTER, MIDDLERIGHT,
6 | BOTTOMLEFT, BOTTOMMIDDLE, BOTTOMRIGHT, FILL
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/BurpExtensionLogger.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 |
5 | public class BurpExtensionLogger implements ILogProvider {
6 |
7 | IBurpExtenderCallbacks callbacks;
8 |
9 | public BurpExtensionLogger(IBurpExtenderCallbacks callbacks){
10 | this.callbacks = callbacks;
11 | }
12 |
13 | @Override
14 | public void logOutput(String message) {
15 | callbacks.printOutput(message);
16 | }
17 |
18 | @Override
19 | public void logError(String errorMessage) {
20 | callbacks.printError(errorMessage);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/ButtonRenderer.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import javax.swing.table.TableCellRenderer;
5 | import java.awt.*;
6 |
7 | /**
8 | * Created by corey on 22/08/17.
9 | */
10 | public class ButtonRenderer implements TableCellRenderer {
11 | @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
12 | JButton button = (JButton)value;
13 | return button;
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/ComponentGroup.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 | import java.awt.event.ActionListener;
6 | import java.util.LinkedHashMap;
7 | import java.util.Map;
8 |
9 | public class ComponentGroup extends JPanel {
10 |
11 | private final PanelBuilder panelBuilder;
12 | private final String title;
13 | private final Map preferenceComponentMap;
14 | private int currentGridY = 1;
15 |
16 | ComponentGroup(PanelBuilder panelBuilder, String title){
17 | super(new GridBagLayout());
18 | this.panelBuilder = panelBuilder;
19 | this.title = title;
20 | this.preferenceComponentMap = new LinkedHashMap<>();
21 |
22 | if(this.title != null && this.title != "") {
23 | this.setBorder(BorderFactory.createTitledBorder(title));
24 | }
25 | }
26 |
27 | ComponentGroup(PanelBuilder panelBuilder){
28 | this(panelBuilder, null);
29 | }
30 |
31 | public JButton addButton(String title, final ActionListener actionListener){
32 | GridBagConstraints gbc = new GridBagConstraints();
33 | gbc.fill = GridBagConstraints.BOTH;
34 | gbc.gridx = 1;
35 | gbc.gridy = currentGridY;
36 | gbc.weightx = 1;
37 | gbc.gridwidth = 2;
38 | JButton button = addButton(title, gbc, actionListener);
39 | currentGridY++;
40 |
41 | return button;
42 | }
43 |
44 | public JButton addButton(String title, GridBagConstraints constraints, final ActionListener actionListener){
45 | JButton button = this.panelBuilder.createButton(title, actionListener);
46 | this.add(button, constraints);
47 | return button;
48 | }
49 |
50 | public JToggleButton addToggleButton(String title, final ActionListener actionListener){
51 | GridBagConstraints gbc = new GridBagConstraints();
52 | gbc.fill = GridBagConstraints.BOTH;
53 | gbc.gridx = 1;
54 | gbc.gridy = currentGridY;
55 | gbc.weightx = 1;
56 | gbc.gridwidth = 2;
57 | JToggleButton button = addToggleButton(title, gbc, actionListener);
58 | currentGridY++;
59 |
60 | return button;
61 | }
62 |
63 | public JToggleButton addToggleButton(String title, GridBagConstraints constraints,
64 | final ActionListener actionListener){
65 | JToggleButton button = this.panelBuilder.createToggleButton(title, actionListener);
66 | this.add(button, constraints);
67 | return button;
68 | }
69 |
70 | public T addPreferenceComponent(final String settingName){
71 | return addPreferenceComponent(settingName, settingName);
72 | }
73 |
74 | public T addPreferenceComponent(final String settingName, final String label){
75 | Object value = this.panelBuilder.getPreferences().getSetting(settingName);
76 | final JComponent component;
77 |
78 | if(value instanceof String){
79 | component = this.panelBuilder.createPreferenceTextField(settingName);
80 | }else if(value instanceof Number){
81 | component = this.panelBuilder.createPreferenceSpinner(settingName);
82 | }else if(value instanceof Boolean){
83 | component = this.panelBuilder.createPreferenceCheckBox(settingName, label);
84 |
85 | //Add to panel here because we don't want to add a label like with the other components.
86 | this.preferenceComponentMap.put(settingName, component);
87 | GridBagConstraints gbc = new GridBagConstraints();
88 | gbc.fill = GridBagConstraints.BOTH;
89 | gbc.gridx = 1;
90 | gbc.weightx = 1;
91 | gbc.gridwidth = 2;
92 | gbc.weighty = 1;
93 | gbc.gridy = currentGridY;
94 | this.add(this.preferenceComponentMap.get(settingName), gbc);
95 | currentGridY++;
96 |
97 | return (T) component;
98 | }else{
99 | component = this.panelBuilder.createPreferenceTextField(settingName);
100 | }
101 |
102 |
103 | this.preferenceComponentMap.put(settingName, component);
104 |
105 | GridBagConstraints gbc = new GridBagConstraints();
106 | gbc.fill = GridBagConstraints.BOTH;
107 | gbc.gridx = 1;
108 | // gbc.ipadx = gbc.ipady = 5;
109 | gbc.weightx = 0.15;
110 | gbc.weighty = 1;
111 | gbc.gridy = currentGridY;
112 | this.add(new JLabel(label), gbc);
113 | gbc.gridx++;
114 | gbc.weightx = 0.85;
115 | this.add(this.preferenceComponentMap.get(settingName), gbc);
116 | currentGridY++;
117 |
118 | return (T) component;
119 | }
120 |
121 |
122 | /**
123 | * Generate the constraints for the next element in the group.
124 | * Useful for customising before addition.
125 | * @return GridBagConstraints The default constraints for the next item in the group.
126 | */
127 | public GridBagConstraints generateNextConstraints(){
128 | GridBagConstraints gbc = new GridBagConstraints();
129 | gbc.fill = GridBagConstraints.BOTH;
130 | gbc.weighty = gbc.weightx = 1;
131 | gbc.gridwidth = 2;
132 | gbc.gridx = 1;
133 | gbc.gridy = currentGridY;
134 | currentGridY++;
135 | return gbc;
136 | }
137 |
138 | /**
139 | * Gets the current Y coord used for generation of the panel.
140 | * @return int The Y coordinate.
141 | */
142 | public int getGridY(){
143 | return this.currentGridY;
144 | }
145 |
146 |
147 | /**
148 | * Sets the Y coord used for generation of the panel.
149 | */
150 | public void setGridY(int y){
151 | this.currentGridY = y;
152 | }
153 |
154 | public JComponent addComponent(JComponent jComponent){
155 | this.add(jComponent, generateNextConstraints());
156 | return jComponent;
157 | }
158 |
159 | public JComponent addComponent(JComponent jComponent, GridBagConstraints gridBagConstraints){
160 | this.add(jComponent, gridBagConstraints);
161 | return jComponent;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/CustomTabComponent.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import javax.swing.border.Border;
5 | import java.awt.*;
6 | import java.awt.event.*;
7 | import java.util.function.Consumer;
8 |
9 | public class CustomTabComponent extends JPanel {
10 |
11 | private final JTabbedPane parentPane;
12 | private JLabel indexLabel;
13 | private JTextField editableField;
14 | private Border editableFieldBorder;
15 | private JButton removeTabButton;
16 |
17 | private int index;
18 | private boolean showIndex;
19 | private boolean isEditable;
20 | private Consumer onTitleChanged;
21 | private boolean isRemovable;
22 | private Consumer onRemovePressed;
23 |
24 | private EditClickListener editClickListener;
25 |
26 | private String originalTitle;
27 | private boolean wasEdited;
28 |
29 | public CustomTabComponent(JTabbedPane parentPane, int index, String title,
30 | boolean showIndex,
31 | boolean isEditable, Consumer onTitleChanged,
32 | boolean isRemovable, Consumer onRemovePressed){
33 | super(new FlowLayout(FlowLayout.RIGHT, 0, 0));
34 | this.setOpaque(false);
35 | this.parentPane = parentPane;
36 | this.index = index;
37 | this.showIndex = showIndex;
38 | this.isEditable = isEditable;
39 | this.onTitleChanged = onTitleChanged;
40 | this.isRemovable = isRemovable;
41 | this.onRemovePressed = onRemovePressed;
42 | this.wasEdited = false;
43 |
44 | if(this.showIndex){
45 | indexLabel = new JLabel(index + ": ");
46 | this.add(indexLabel);
47 | }
48 |
49 | editableField = new JTextField(title);
50 | editableFieldBorder = editableField.getBorder();
51 | editableField.setBorder(null);
52 | editableField.setOpaque(false);
53 | editableField.setBackground(new Color(0,0,0,0));
54 | add(editableField);
55 |
56 | if(this.isEditable) {
57 | this.editClickListener = new EditClickListener();
58 |
59 | if(this.showIndex){
60 | indexLabel.addMouseListener(editClickListener);
61 | }
62 | editableField.addMouseListener(editClickListener);
63 | this.addMouseListener(editClickListener);
64 |
65 | editableField.addFocusListener(new FocusAdapter() {
66 | @Override
67 | public void focusLost(FocusEvent focusEvent) {
68 | endEditLabel();
69 | }
70 | });
71 |
72 | editableField.addKeyListener(new KeyAdapter() {
73 | @Override
74 | public void keyPressed(KeyEvent keyEvent) {
75 | if(keyEvent.getExtendedKeyCode() == KeyEvent.VK_ENTER){
76 | endEditLabel();
77 | }
78 | }
79 | });
80 | }
81 |
82 | if(this.isRemovable) {
83 | removeTabButton = new JButton("x");
84 | removeTabButton.setFont(removeTabButton.getFont().deriveFont(10F));
85 | removeTabButton.addActionListener(actionEvent -> {
86 | if(this.onRemovePressed != null)
87 | this.onRemovePressed.accept(null);
88 | });
89 | removeTabButton.setPreferredSize(new Dimension(20,20));
90 | removeTabButton.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
91 | removeTabButton.setBackground(new Color(0,0,0,0));
92 | add(Box.createHorizontalStrut(5));
93 | add(removeTabButton);
94 | }
95 |
96 | revalidate();
97 | repaint();
98 |
99 | }
100 |
101 | private void editLabel(){
102 | originalTitle = editableField.getText();
103 | editableField.setEditable(true);
104 | editableField.setBorder(editableFieldBorder);
105 | // editableField.setCaretPosition(0);
106 | }
107 |
108 | private void endEditLabel(){
109 | editableField.setBorder(null);
110 | if(!editableField.getText().equals(originalTitle)){
111 | wasEdited = true;
112 | }
113 | if(this.onTitleChanged != null)
114 | this.onTitleChanged.accept(editableField.getText());
115 | editableField.setEditable(false);
116 | editableField.setSelectionStart(0);
117 | editableField.setSelectionEnd(0);
118 | this.requestFocusInWindow();
119 | }
120 |
121 |
122 | private class EditClickListener extends MouseAdapter {
123 | @Override
124 | public void mousePressed(MouseEvent mouseEvent) {
125 | super.mouseClicked(mouseEvent);
126 | if(mouseEvent.getClickCount() > 1) {
127 | editLabel();
128 | }else{
129 | //For some reason, Custom tab components with mouselisteners
130 | //do not bubble up to select the tab. This kind of fixes that.
131 | if(CustomTabComponent.this.parentPane != null){
132 | for (int i = 0; i < parentPane.getTabCount(); i++) {
133 | if(parentPane.getTabComponentAt(i) == CustomTabComponent.this){
134 | parentPane.setSelectedIndex(i);
135 | parentPane.requestFocus(true);
136 | return;
137 | }
138 | }
139 | }
140 | }
141 | }
142 | }
143 |
144 | public void setIndex(int index) {
145 | this.index = index;
146 | if(this.showIndex){
147 | this.indexLabel.setText(index + ": ");
148 | }
149 | }
150 |
151 | public String getTitle(){
152 | return this.editableField.getText();
153 | }
154 |
155 | public void setTitle(String title){
156 | this.editableField.setText(title);
157 | }
158 |
159 | public boolean wasEditedByUser() {
160 | return wasEdited;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/DefaultGsonProvider.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.google.gson.TypeAdapter;
6 |
7 | import java.lang.reflect.Type;
8 |
9 | public class DefaultGsonProvider implements IGsonProvider {
10 |
11 | private GsonBuilder gsonBuilder;
12 | private Gson gson;
13 |
14 | public DefaultGsonProvider(){
15 | this.gsonBuilder = new GsonBuilder();
16 | this.gson = this.gsonBuilder.create();
17 | }
18 |
19 | @Override
20 | public Gson getGson() {
21 | return this.gson;
22 | }
23 |
24 | @Override
25 | public void registerTypeAdapter(Type type, Object typeAdapter) {
26 | this.gsonBuilder.registerTypeAdapter(type, typeAdapter);
27 | this.gson = this.gsonBuilder.create();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/FixCookie.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import burp.ICookie;
4 |
5 | import java.util.Date;
6 |
7 | public class FixCookie implements ICookie {
8 |
9 | private final ICookie originalCookie;
10 | private final String domain;
11 |
12 | public FixCookie(ICookie originalCookie, String domain){
13 | this.originalCookie = originalCookie;
14 | this.domain = domain;
15 | }
16 |
17 |
18 | @Override
19 | public String getDomain() {
20 | return
21 | originalCookie.getDomain() != null ? originalCookie.getDomain() : domain;
22 | }
23 |
24 | @Override
25 | public String getPath() {
26 | return originalCookie.getPath();
27 | }
28 |
29 | @Override
30 | public Date getExpiration() {
31 | return originalCookie.getExpiration();
32 | }
33 |
34 | @Override
35 | public String getName() {
36 | return originalCookie.getName();
37 | }
38 |
39 | @Override
40 | public String getValue() {
41 | return originalCookie.getValue();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/HistoryField.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import com.google.gson.reflect.TypeToken;
4 |
5 | import javax.swing.*;
6 | import javax.swing.plaf.basic.BasicComboBoxEditor;
7 | import java.awt.*;
8 | import java.lang.reflect.Type;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * Created by corey on 05/09/17.
14 | */
15 | public class HistoryField extends JComboBox {
16 |
17 | private static Type HISTORY_TYPE_TOKEN = new TypeToken>(){}.getType();
18 | private final int maxHistory;
19 | private final Preferences preferences;
20 | private final String preferencesKey;
21 | private ArrayList history;
22 |
23 | public HistoryField(final int maxHistory){
24 | this(null, null, maxHistory);
25 | }
26 |
27 | public HistoryField(Preferences preferences, String preferencesKey, final int maxHistory){
28 | this.maxHistory = maxHistory;
29 | this.preferences = preferences;
30 | this.preferencesKey = preferencesKey;
31 |
32 | history = new ArrayList(){
33 | @Override
34 | public boolean add(String s) {
35 | if(this.size() >= maxHistory) remove(0);
36 | return super.add(s);
37 | }
38 | };
39 |
40 | configureComponent();
41 |
42 | loadHistory();
43 | }
44 |
45 | private void configureComponent(){
46 | this.setModel(new HistoryComboModel());
47 | this.setEditor(new BasicComboBoxEditor(){
48 | JTextField editorComponent;
49 | @Override
50 | protected JTextField createEditorComponent() {
51 | editorComponent = new JTextField();
52 | editorComponent.setOpaque(false);
53 | return editorComponent;
54 | }
55 |
56 | @Override
57 | public Component getEditorComponent() {
58 | return editorComponent;
59 | }
60 | });
61 | this.setEditable(true);
62 | this.setOpaque(true);
63 | }
64 |
65 | private void loadHistory(){
66 | if(this.preferences != null && this.preferencesKey != null){
67 | history.clear();
68 | preferences.registerSetting(preferencesKey, HISTORY_TYPE_TOKEN, new ArrayList(), Preferences.Visibility.GLOBAL);
69 | ArrayList oldSearches = (ArrayList) preferences.getSetting(preferencesKey);
70 | history.addAll(oldSearches);
71 | }
72 | }
73 |
74 | public void setForegroundColor(Color color){
75 | this.getEditor().getEditorComponent().setForeground(color);
76 | }
77 |
78 | public void setBackgroundColor(Color color){
79 | this.getEditor().getEditorComponent().setBackground(color);
80 | }
81 |
82 | public class HistoryComboModel extends DefaultComboBoxModel {
83 |
84 | public void addToHistory(String val){
85 | if(val.equals("")) return;
86 | if(history.contains(val)) history.remove(val);
87 | history.add((String) val);
88 |
89 | if(preferences != null && preferencesKey != null ){
90 | preferences.setSetting(preferencesKey, history);
91 | }
92 | this.fireContentsChanged(val, history.size()-1, history.size()-1);
93 | }
94 |
95 | @Override
96 | public int getSize() {
97 | return history.size();
98 | }
99 |
100 | @Override
101 | public Object getElementAt(int i) {
102 | return history.get(history.size() - i -1);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/IGsonProvider.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.TypeAdapter;
5 |
6 | import java.lang.reflect.Type;
7 |
8 | public interface IGsonProvider {
9 | Gson getGson();
10 |
11 | /**
12 | * Register a type adapter for the given class.
13 | * This defines how to de/serialize an object.
14 | * Required if storing custom types as preferenceComponentMap.
15 | * @param type
16 | * @param typeAdapter
17 | */
18 | void registerTypeAdapter(Type type, Object typeAdapter);
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/ILogProvider.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | public interface ILogProvider {
4 | void logOutput(String message);
5 | void logError(String errorMessage);
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/PanelBuilder.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import javax.swing.event.ChangeEvent;
5 | import javax.swing.event.ChangeListener;
6 | import javax.swing.event.DocumentEvent;
7 | import javax.swing.event.DocumentListener;
8 | import java.awt.*;
9 | import java.awt.event.ActionEvent;
10 | import java.awt.event.ActionListener;
11 | import java.util.HashMap;
12 | import java.util.LinkedHashSet;
13 | import java.util.Set;
14 |
15 | public class PanelBuilder {
16 |
17 | private final Preferences preferences;
18 | private Set componentGroups;
19 |
20 | public PanelBuilder(){
21 | this(null);
22 | }
23 |
24 | public PanelBuilder(Preferences preferences){
25 | this.preferences = preferences;
26 | this.componentGroups = new LinkedHashSet<>();
27 | }
28 |
29 | public ComponentGroup createComponentGroup(String title){
30 | ComponentGroup componentGroup = new ComponentGroup(this, title);
31 | this.componentGroups.add(componentGroup);
32 | return componentGroup;
33 | }
34 |
35 | Preferences getPreferences() {
36 | return preferences;
37 | }
38 |
39 | public JButton createButton(String title, ActionListener actionListener){
40 | JButton button = new JButton(title);
41 | if(actionListener != null)
42 | button.addActionListener(actionListener);
43 | return button;
44 | }
45 |
46 | public JToggleButton createToggleButton(String title, ActionListener actionListener) {
47 | JToggleButton button = new JToggleButton(title);
48 | if(actionListener != null)
49 | button.addActionListener(actionListener);
50 | return button;
51 | }
52 |
53 | public JToggleButton createPreferenceToggleButton(String title, String preferenceKey){
54 | throwExceptionIfNoPreferences();
55 | JToggleButton toggleButton = createToggleButton(title, e -> {
56 | this.preferences.setSetting(preferenceKey, ((JToggleButton) e.getSource()).isSelected());
57 | });
58 |
59 | boolean isSelected = this.preferences.getSetting(preferenceKey);
60 | toggleButton.setSelected(isSelected);
61 | return toggleButton;
62 | }
63 |
64 | public JTextField createPreferenceTextField(String preferenceKey){
65 | throwExceptionIfNoPreferences();
66 | final JTextField textComponent = new JTextField();
67 | String defaultValue = this.preferences.getSetting(preferenceKey);
68 | textComponent.setText(defaultValue);
69 | textComponent.getDocument().addDocumentListener(new DocumentListener() {
70 | @Override
71 | public void insertUpdate(DocumentEvent documentEvent) {
72 | preferences.setSetting(preferenceKey, textComponent.getText(), false);
73 | }
74 |
75 | @Override
76 | public void removeUpdate(DocumentEvent documentEvent) {
77 | preferences.setSetting(preferenceKey, textComponent.getText(), false);
78 | }
79 |
80 | @Override
81 | public void changedUpdate(DocumentEvent documentEvent) {
82 | preferences.setSetting(preferenceKey, textComponent.getText(), false);
83 | }
84 | });
85 |
86 | preferences.addSettingListener((changedpreferenceKey, newValue) -> {
87 | if(changedpreferenceKey.equals(preferenceKey)){
88 | textComponent.setText((String) newValue);
89 | }
90 | });
91 |
92 | return textComponent;
93 | }
94 |
95 | public JSpinner createPreferenceSpinner(String preferenceKey){
96 | throwExceptionIfNoPreferences();
97 | final JSpinner spinnerComponent = new JSpinner();
98 | Number value = this.preferences.getSetting(preferenceKey);
99 | spinnerComponent.setValue(value);
100 | spinnerComponent.addChangeListener(new ChangeListener() {
101 | @Override
102 | public void stateChanged(ChangeEvent changeEvent) {
103 | preferences.setSetting(preferenceKey, spinnerComponent.getValue(), false);
104 | }
105 | });
106 |
107 | preferences.addSettingListener((changedSettingName, newValue) -> {
108 | if(changedSettingName.equals(preferenceKey)){
109 | spinnerComponent.setValue(newValue);
110 | }
111 | });
112 |
113 | return spinnerComponent;
114 | }
115 |
116 | public JCheckBox createPreferenceCheckBox(String preferenceKey, String label){
117 | throwExceptionIfNoPreferences();
118 | final JCheckBox checkComponent = new JCheckBox(label);
119 | Boolean value = (Boolean) this.preferences.getSetting(preferenceKey);
120 | checkComponent.setSelected(value);
121 | checkComponent.addActionListener(new ActionListener() {
122 | @Override
123 | public void actionPerformed(ActionEvent actionEvent) {
124 | preferences.setSetting(preferenceKey, checkComponent.isSelected(), false);
125 | }
126 | });
127 |
128 | preferences.addSettingListener((changedSettingName, newValue) -> {
129 | if(changedSettingName.equals(preferenceKey)){
130 | checkComponent.setSelected((boolean) newValue);
131 | }
132 | });
133 |
134 | return checkComponent;
135 | }
136 |
137 | public JTextArea createPreferenceTextArea(String settingName){
138 | throwExceptionIfNoPreferences();
139 | String value = preferences.getSetting(settingName);
140 |
141 | JTextArea textArea = new JTextArea();
142 | textArea.setText(value);
143 |
144 | textArea.getDocument().addDocumentListener(new DocumentListener() {
145 | @Override
146 | public void insertUpdate(DocumentEvent documentEvent) { saveChanges(); }
147 | @Override
148 | public void removeUpdate(DocumentEvent documentEvent) { saveChanges(); }
149 | @Override
150 | public void changedUpdate(DocumentEvent documentEvent) { saveChanges(); }
151 |
152 | private void saveChanges(){
153 | preferences.setSetting(settingName, textArea.getText(), false);
154 | }
155 | });
156 |
157 | return textArea;
158 | }
159 |
160 | public JPanel build(Component singleComponent, Alignment alignment, double scaleX, double scaleY){
161 | return build(
162 | new Component[][]{new Component[]{singleComponent}},
163 | new int[][]{new int[]{1}},
164 | alignment, scaleX, scaleY);
165 | }
166 |
167 | public JPanel build(Component[][] viewGrid, Alignment alignment, double scaleX, double scaleY) {
168 | return build(viewGrid, null, alignment, scaleX, scaleY);
169 | }
170 |
171 | public JPanel build(Component[][] viewGrid, int[][] gridWeights, Alignment alignment, double scaleX, double scaleY) {
172 | if(scaleX > 1 || scaleX < 0) throw new IllegalArgumentException("Scale must be between 0 and 1");
173 | if(scaleY > 1 || scaleY < 0) throw new IllegalArgumentException("Scale must be between 0 and 1");
174 | JPanel containerPanel = new JPanel(new GridBagLayout());
175 | HashMap constraintsMap = new HashMap<>();
176 | int minx = Integer.MAX_VALUE, miny = Integer.MAX_VALUE, maxx = Integer.MIN_VALUE, maxy = Integer.MIN_VALUE;
177 |
178 | for (int row = 0; row < viewGrid.length; row++) {
179 | for (int column = 0; column < viewGrid[row].length; column++) {
180 | Component panel = viewGrid[row][column];
181 | if(panel != null) {
182 | int gridx = column + 1;
183 | int gridy = row + 1;
184 |
185 | if (constraintsMap.containsKey(panel)) {
186 | GridBagConstraints constraints = constraintsMap.get(panel);
187 | if (gridx < constraints.gridx || (constraints.gridx + constraints.gridwidth) < gridx) {
188 | throw new RuntimeException("Panels must be contiguous.");
189 | }
190 | if (gridy < constraints.gridy || (constraints.gridy + constraints.gridheight) < gridy) {
191 | throw new RuntimeException("Panels must be contiguous.");
192 | }
193 |
194 | constraints.gridwidth = gridx - constraints.gridx + 1;
195 | constraints.gridheight = gridy - constraints.gridy + 1;
196 | int weight;
197 | try{
198 | weight = gridWeights[gridy-1][gridx-1];
199 | }catch (Exception e){ weight = 0; }
200 | constraints.weightx += weight;
201 | constraints.weighty += weight;
202 |
203 | } else {
204 | GridBagConstraints constraints = new GridBagConstraints();
205 | constraints.fill = GridBagConstraints.BOTH;
206 | constraints.gridx = gridx;
207 | constraints.gridy = gridy;
208 | int weight;
209 | try{
210 | weight = gridWeights[gridy-1][gridx-1];
211 | }catch (Exception e){ weight = 0; }
212 | constraints.weightx = constraints.weighty = weight;
213 | constraintsMap.put(panel, constraints);
214 | }
215 |
216 | if (gridx < minx) minx = gridx;
217 | if (gridx > maxx) maxx = gridx;
218 | if (gridy < miny) miny = gridy;
219 | if (gridy > maxy) maxy = gridy;
220 | }else{
221 | GridBagConstraints constraints = new GridBagConstraints();
222 | constraints.gridx = column+1;
223 | constraints.gridy = row+1;
224 | constraints.fill = GridBagConstraints.BOTH;
225 | constraints.weightx = constraints.weighty = 1;
226 | JPanel filler = new JPanel();
227 | constraintsMap.put(filler, constraints);
228 | }
229 | }
230 | }
231 |
232 | GridBagConstraints innerPanelGbc = new GridBagConstraints();
233 | innerPanelGbc.fill = GridBagConstraints.BOTH;
234 | innerPanelGbc.gridx = innerPanelGbc.gridy = 2;
235 | innerPanelGbc.weightx = scaleX;
236 | innerPanelGbc.weighty = scaleY;
237 | GridBagConstraints paddingLeftGbc = new GridBagConstraints();
238 | GridBagConstraints paddingRightGbc = new GridBagConstraints();
239 | GridBagConstraints paddingTopGbc = new GridBagConstraints();
240 | GridBagConstraints paddingBottomGbc = new GridBagConstraints();
241 | paddingLeftGbc.fill = paddingRightGbc.fill = paddingTopGbc.fill = paddingBottomGbc.fill = GridBagConstraints.BOTH;
242 | paddingLeftGbc.weightx = paddingRightGbc.weightx = (1-scaleX)/2;
243 | paddingTopGbc.weighty = paddingBottomGbc.weighty = (1-scaleY)/2;
244 | paddingLeftGbc.gridy = paddingRightGbc.gridy = 2;
245 | paddingTopGbc.gridx = paddingBottomGbc.gridx = 2;
246 | paddingLeftGbc.gridx = 1;
247 | paddingRightGbc.gridx = 3;
248 | paddingTopGbc.gridy = 1;
249 | paddingBottomGbc.gridy = 3;
250 |
251 | JPanel topPanel, leftPanel, bottomPanel, rightPanel;
252 |
253 | if(alignment != Alignment.FILL && alignment != Alignment.TOPLEFT
254 | && alignment != Alignment.TOPMIDDLE && alignment != Alignment.TOPRIGHT){
255 | containerPanel.add(topPanel = new JPanel(), paddingTopGbc);
256 | // topPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
257 | }
258 |
259 | if(alignment != Alignment.FILL && alignment != Alignment.TOPLEFT
260 | && alignment != Alignment.MIDDLELEFT && alignment != Alignment.BOTTOMLEFT){
261 | containerPanel.add(leftPanel = new JPanel(), paddingLeftGbc);
262 | // leftPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
263 | }
264 |
265 | if(alignment != Alignment.FILL && alignment != Alignment.TOPRIGHT
266 | && alignment != Alignment.MIDDLERIGHT && alignment != Alignment.BOTTOMRIGHT){
267 | containerPanel.add(rightPanel = new JPanel(), paddingRightGbc);
268 | // rightPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
269 | }
270 |
271 | if(alignment != Alignment.FILL && alignment != Alignment.BOTTOMLEFT
272 | && alignment != Alignment.BOTTOMMIDDLE && alignment != Alignment.BOTTOMRIGHT){
273 | containerPanel.add(bottomPanel = new JPanel(), paddingBottomGbc);
274 | // bottomPanel.setBorder(BorderFactory.createLineBorder(Color.ORANGE));
275 | }
276 |
277 |
278 | JPanel innerContainer = new JPanel(new GridBagLayout());
279 | for (Component component : constraintsMap.keySet()) {
280 | innerContainer.add(component, constraintsMap.get(component));
281 | }
282 | containerPanel.add(innerContainer, innerPanelGbc);
283 |
284 | return containerPanel;
285 | }
286 |
287 | public JPanel build(){
288 | JPanel containerPanel = new JPanel(new GridBagLayout());
289 | GridBagConstraints gbc = new GridBagConstraints();
290 | gbc.gridy = gbc.gridx = 1;
291 | gbc.fill = GridBagConstraints.HORIZONTAL;
292 | for (ComponentGroup componentGroup : this.componentGroups) {
293 | containerPanel.add(componentGroup, gbc);
294 | gbc.gridy++;
295 | }
296 |
297 | gbc.weighty = gbc.weightx = 100;
298 | containerPanel.add(new JPanel(), gbc);
299 |
300 | return containerPanel;
301 | }
302 |
303 | private void throwExceptionIfNoPreferences(){
304 | if(this.preferences == null){
305 | throw new IllegalStateException("Method unavailable. No preference context defined.");
306 | }
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/PopOutPanel.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 | import java.awt.event.WindowEvent;
6 | import java.awt.event.WindowListener;
7 |
8 | /**
9 | * Created by corey on 24/08/17.
10 | */
11 | public class PopOutPanel extends JPanel {
12 | private final Component component;
13 | private final JLabel placeholder;
14 | private String title;
15 | private boolean isPoppedOut;
16 | private JFrame popoutFrame;
17 | private JMenuItem popoutMenuItem;
18 |
19 | public PopOutPanel(Component component, String title){
20 | this.component = component;
21 | this.title = title;
22 | this.placeholder = new JLabel(title + " is popped out.");
23 | this.placeholder.setHorizontalAlignment(SwingConstants.CENTER);
24 | this.setLayout(new BorderLayout());
25 | this.add(component, BorderLayout.CENTER);
26 | }
27 |
28 | public void toggle(){
29 | if (this.isPoppedOut) popIn();
30 | else popOut();
31 | }
32 |
33 | public void popIn(){
34 | this.remove(placeholder);
35 | this.add(component, BorderLayout.CENTER);
36 | this.revalidate();
37 | this.repaint();
38 | this.isPoppedOut = false;
39 | this.popoutFrame.dispose();
40 | if(popoutMenuItem != null) popoutMenuItem.setText("Pop Out " + title);
41 | }
42 |
43 | public void popOut(){
44 | this.popoutFrame = new JFrame();
45 | popoutFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
46 | popoutFrame.addWindowListener(new WindowListener() {
47 | @Override
48 | public void windowOpened(WindowEvent windowEvent) {
49 | popoutFrame.add(component);
50 | isPoppedOut = true;
51 | PopOutPanel.this.add(placeholder, BorderLayout.CENTER);
52 | PopOutPanel.this.revalidate();
53 | PopOutPanel.this.repaint();
54 | popoutFrame.pack();
55 | }
56 |
57 | @Override
58 | public void windowClosing(WindowEvent windowEvent) {
59 | if(isPoppedOut) popIn();
60 | }
61 |
62 | @Override
63 | public void windowClosed(WindowEvent windowEvent) {}
64 |
65 | @Override
66 | public void windowIconified(WindowEvent windowEvent) {}
67 |
68 | @Override
69 | public void windowDeiconified(WindowEvent windowEvent) {}
70 |
71 | @Override
72 | public void windowActivated(WindowEvent windowEvent) {}
73 |
74 | @Override
75 | public void windowDeactivated(WindowEvent windowEvent) {}
76 | });
77 |
78 | popoutFrame.setVisible(true);
79 | if(popoutMenuItem != null) popoutMenuItem.setText("Pop In " + title);
80 | }
81 |
82 | public JMenuItem getPopoutMenuItem(){
83 | if(this.popoutMenuItem == null){
84 | popoutMenuItem = new JMenuItem((isPoppedOut ? "Pop In " : "Pop Out ") + title);
85 | popoutMenuItem.addActionListener(e -> {
86 | this.toggle();
87 | });
88 | }
89 |
90 | return popoutMenuItem;
91 | }
92 |
93 | public JFrame getPopoutFrame() {
94 | return popoutFrame;
95 | }
96 |
97 | public boolean isPoppedOut() {
98 | return isPoppedOut;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/PreferenceFactory.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 |
5 | public abstract class PreferenceFactory {
6 |
7 | protected Preferences prefs;
8 | protected IGsonProvider gsonProvider;
9 | protected ILogProvider logProvider;
10 |
11 | public PreferenceFactory(String extensionIdentifier, IGsonProvider gsonProvider,
12 | ILogProvider logProvider, IBurpExtenderCallbacks callbacks){
13 | this.gsonProvider = gsonProvider;
14 | this.logProvider = logProvider;
15 | prefs = new Preferences(extensionIdentifier, gsonProvider, logProvider, callbacks);
16 | }
17 |
18 | public PreferenceFactory(String extensionIdentifier, IGsonProvider gsonProvider, IBurpExtenderCallbacks callbacks){
19 | this.gsonProvider = gsonProvider;
20 | prefs = new Preferences(extensionIdentifier, gsonProvider, callbacks);
21 | }
22 |
23 | protected abstract void createDefaults();
24 |
25 | protected abstract void registerTypeAdapters();
26 |
27 | protected abstract void registerSettings();
28 |
29 | public Preferences buildPreferences(){
30 | this.registerTypeAdapters();
31 | this.createDefaults();
32 | this.registerSettings();
33 | return this.prefs;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/PreferenceListener.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | public interface PreferenceListener {
4 | void onPreferenceSet(String settingName, Object newValue);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/Preferences.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 | import burp.IHttpRequestResponse;
5 | import com.coreyd97.BurpExtenderUtilities.TypeAdapter.AtomicIntegerTypeAdapter;
6 | import com.coreyd97.BurpExtenderUtilities.TypeAdapter.ByteArrayToBase64TypeAdapter;
7 |
8 | import java.io.PrintWriter;
9 | import java.io.StringWriter;
10 | import java.io.UnsupportedEncodingException;
11 | import java.lang.reflect.Type;
12 | import java.net.MalformedURLException;
13 | import java.net.URLEncoder;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.Set;
17 | import java.util.concurrent.atomic.AtomicInteger;
18 |
19 | public class Preferences {
20 |
21 | public enum Visibility {GLOBAL, PROJECT, VOLATILE}
22 |
23 | private ILogProvider logProvider;
24 | private final String extensionIdentifier;
25 | private final IGsonProvider gsonProvider;
26 | private final IBurpExtenderCallbacks callbacks;
27 | private final HashMap preferences;
28 | private final HashMap preferenceDefaults;
29 | private final HashMap preferenceTypes;
30 | private final HashMap preferenceVisibilities;
31 | private final ArrayList preferenceListeners;
32 | private ProjectSettingStore projectSettingsStore;
33 |
34 | public Preferences(final String extensionIdentifier, final IGsonProvider gsonProvider,
35 | final ILogProvider logProvider, final IBurpExtenderCallbacks callbacks){
36 | this(extensionIdentifier, gsonProvider, callbacks);
37 | this.logProvider = logProvider;
38 | }
39 |
40 | public Preferences(final String extensionIdentifier, final IGsonProvider gsonProvider,
41 | final IBurpExtenderCallbacks callbacks){
42 | this.extensionIdentifier = extensionIdentifier;
43 | this.gsonProvider = gsonProvider;
44 | this.callbacks = callbacks;
45 | this.preferenceDefaults = new HashMap<>();
46 | this.preferences = new HashMap<>();
47 | this.preferenceTypes = new HashMap<>();
48 | this.preferenceVisibilities = new HashMap<>();
49 | this.preferenceListeners = new ArrayList<>();
50 | registerRequiredTypeAdapters();
51 | setupProjectSettingsStore();
52 | }
53 |
54 | public ProjectSettingStore getProjectSettingsStore() {
55 | return this.projectSettingsStore;
56 | }
57 |
58 | private void registerRequiredTypeAdapters(){
59 | this.gsonProvider.registerTypeAdapter(AtomicInteger.class, new AtomicIntegerTypeAdapter());
60 | this.gsonProvider.registerTypeAdapter(byte[].class, new ByteArrayToBase64TypeAdapter());
61 | }
62 |
63 | private void setupProjectSettingsStore(){
64 | try{
65 | //Create store object.
66 | this.projectSettingsStore = new ProjectSettingStore(this, callbacks, extensionIdentifier);
67 | String extensionIdentifierEncoded = URLEncoder.encode(extensionIdentifier, "UTF-8");
68 |
69 | //Load existing from sitemap
70 | IHttpRequestResponse[] existingItems = callbacks.getSiteMap(
71 | projectSettingsStore.getHttpService().toString() + "/" + extensionIdentifierEncoded);
72 |
73 | //If we have an existing item
74 | if(existingItems.length != 0){
75 | //Pick the first one
76 | IHttpRequestResponse existingSettings = existingItems[0];
77 | //If it has a response body (settings json)
78 | if(existingSettings.getResponse() != null){
79 | //Load it into our current store item.
80 | this.projectSettingsStore.setResponse(existingSettings.getResponse());
81 | }
82 | }
83 |
84 | //Add it to the sitemap
85 | callbacks.addToSiteMap(this.projectSettingsStore);
86 | } catch (UnsupportedEncodingException | MalformedURLException e) {
87 | this.projectSettingsStore = null;
88 | logError("Could not initiate the project setting store. See the below stack trace for more info.");
89 | StringWriter sw = new StringWriter();
90 | e.printStackTrace(new PrintWriter(sw));
91 | logError(sw.toString());
92 | }
93 | }
94 |
95 | public void registerSetting(String settingName, Type type){
96 | registerSetting(settingName, type, null, Visibility.GLOBAL);
97 | }
98 |
99 | public void registerSetting(String settingName, Type type, Object defaultValue){
100 | registerSetting(settingName, type, defaultValue, Visibility.GLOBAL);
101 | }
102 |
103 | public void registerSetting(String settingName, Type type, Visibility visibility){
104 | registerSetting(settingName, type, null, visibility);
105 | }
106 |
107 | public void registerSetting(String settingName, Type type, Object defaultValue, Visibility visibility){
108 | throwExceptionIfAlreadyRegistered(settingName);
109 | this.preferenceVisibilities.put(settingName, visibility);
110 |
111 | switch (visibility){
112 | case PROJECT: {
113 | if(projectSettingsStore == null)
114 | throw new RuntimeException("The project settings store was not initialised. Project settings cannot be setup.");
115 |
116 | this.projectSettingsStore.registerSetting(settingName, type, defaultValue);
117 | return;
118 | }
119 | case GLOBAL: {
120 | Object storedValue = getGlobalSettingFromBurp(settingName, type);
121 | this.preferenceTypes.put(settingName, type);
122 |
123 | if(storedValue != null){
124 | this.preferences.put(settingName, storedValue);
125 | }else{
126 | if(defaultValue != null){
127 | setGlobalSetting(settingName, defaultValue, true);
128 | }else{
129 | this.preferences.put(settingName, null);
130 | }
131 | }
132 |
133 | this.preferenceDefaults.put(settingName, defaultValue);
134 |
135 | logOutput("Global setting \"" + settingName + "\" registered with type " + type.getTypeName()
136 | + " and default value: " + (defaultValue != null ? defaultValue : "null"));
137 | return;
138 | }
139 | case VOLATILE: {
140 | this.preferenceTypes.put(settingName, type);
141 | this.preferences.put(settingName, defaultValue);
142 | this.preferenceDefaults.put(settingName, defaultValue);
143 |
144 | logOutput("Volatile setting \"" + settingName + "\" registered with type " + type.getTypeName()
145 | + " and default value: " + (defaultValue != null ? defaultValue : "null"));
146 | return;
147 | }
148 | }
149 |
150 | }
151 |
152 | @Deprecated
153 | public void registerGlobalSetting(String settingName, Type type){
154 | registerSetting(settingName, type, Visibility.GLOBAL);
155 | }
156 |
157 | @Deprecated
158 | public void registerGlobalSetting(String settingName, Type type, Object defaultValue){
159 | registerSetting(settingName, type, defaultValue, Visibility.GLOBAL);
160 | }
161 |
162 | @Deprecated
163 | public void registerProjectSetting(String settingName, Type type) {
164 | registerSetting(settingName, type, Visibility.PROJECT);
165 | }
166 |
167 | @Deprecated
168 | public void registerProjectSetting(String settingName, Type type, Object defaultValue) {
169 | registerSetting(settingName, type, defaultValue, Visibility.PROJECT);
170 | }
171 |
172 |
173 | @Deprecated
174 | public void registerVolatileSetting(String settingName, Type type){
175 | registerSetting(settingName, type, Visibility.VOLATILE);
176 | }
177 |
178 | @Deprecated
179 | public void registerVolatileSetting(String settingName, Type type, Object defaultValue){
180 | registerSetting(settingName, type, defaultValue, Visibility.VOLATILE);
181 | }
182 |
183 | private void setGlobalSetting(String settingName, Object value, boolean notifyListeners) {
184 | Type type = this.preferenceTypes.get(settingName);
185 | Object currentValue = this.preferences.get(settingName);
186 | String currentValueJson = gsonProvider.getGson().toJson(currentValue, type);
187 | String newValueJson = gsonProvider.getGson().toJson(value, type);
188 | if(newValueJson != null && newValueJson.equals(currentValueJson)) return;
189 |
190 | storeGlobalSetting(settingName, newValueJson);
191 | this.preferences.put(settingName, value);
192 |
193 | if(!notifyListeners) return;
194 | for (PreferenceListener preferenceListener : this.preferenceListeners) {
195 | preferenceListener.onPreferenceSet(settingName, value);
196 | }
197 | }
198 |
199 | private void storeGlobalSetting(String settingName, String jsonValue){
200 | this.callbacks.saveExtensionSetting(settingName, jsonValue);
201 | }
202 |
203 | private Object getGlobalSettingFromBurp(String settingName, Type settingType) {
204 | String storedValue = getGlobalSettingJson(settingName);
205 | if(storedValue == null) return null;
206 |
207 | logOutput(String.format("Value %s loaded for global setting \"%s\". Trying to deserialize.", storedValue, settingName));
208 | try {
209 | return gsonProvider.getGson().fromJson(storedValue, settingType);
210 | }catch (Exception e){
211 | logError("Could not load stored setting \"" + settingName
212 | + "\". This may be due to a change in stored types. Falling back to default.");
213 | return null;
214 | }
215 | }
216 |
217 | public HashMap getRegisteredSettings(){
218 | return this.preferenceVisibilities;
219 | }
220 |
221 | public T getSetting(String settingName){
222 | Visibility visibility = this.preferenceVisibilities.get(settingName);
223 | if(visibility == null) throw new RuntimeException("Setting " + settingName + " has not been registered!");
224 |
225 | Object value = null;
226 | switch (visibility){
227 | case VOLATILE:
228 | case GLOBAL: {
229 | value = this.preferences.get(settingName);
230 | break;
231 | }
232 | case PROJECT: {
233 | value = this.projectSettingsStore.getSetting(settingName);
234 | break;
235 | }
236 | }
237 |
238 | return (T) value;
239 | }
240 |
241 | public void setSetting(String settingName, Object value){
242 | setSetting(settingName, value, true);
243 | }
244 |
245 | public void setSetting(String settingName, Object value, boolean notifyListeners){
246 | Visibility visibility = this.preferenceVisibilities.get(settingName);
247 | if(visibility == null) throw new RuntimeException("Setting " + settingName + " has not been registered!");
248 | switch (visibility) {
249 | case VOLATILE: {
250 | this.preferences.put(settingName, value);
251 | break;
252 | }
253 | case PROJECT: {
254 | this.projectSettingsStore.setSetting(settingName, value);
255 | break;
256 | }
257 | case GLOBAL: {
258 | this.setGlobalSetting(settingName, value, notifyListeners);
259 | return;
260 | }
261 | }
262 |
263 | if(!notifyListeners) return;
264 | for (PreferenceListener preferenceListener : this.preferenceListeners) {
265 | preferenceListener.onPreferenceSet(settingName, value);
266 | }
267 | }
268 |
269 | public Type getSettingType(String settingName) {
270 | Visibility visibility = this.preferenceVisibilities.get(settingName);
271 | if(visibility == null) throw new RuntimeException("Setting " + settingName + " has not been registered!");
272 | switch (visibility){
273 | case PROJECT: {
274 | return this.projectSettingsStore.getSettingType(settingName);
275 | }
276 |
277 | case VOLATILE:
278 | case GLOBAL: {
279 | return preferenceTypes.get(settingName);
280 | }
281 | }
282 |
283 | return null;
284 | }
285 |
286 | private String getGlobalSettingJson(String settingName) {
287 | return this.callbacks.loadExtensionSetting(settingName);
288 | }
289 |
290 | public void addSettingListener(PreferenceListener preferenceListener){
291 | this.preferenceListeners.add(preferenceListener);
292 | }
293 |
294 | public void removeSettingListener(PreferenceListener preferenceListener){
295 | this.preferenceListeners.remove(preferenceListener);
296 | }
297 |
298 | public void resetSetting(String settingName){
299 | Object defaultValue = this.preferenceDefaults.getOrDefault(settingName, null);
300 | String jsonDefaultValue = gsonProvider.getGson().toJson(defaultValue);
301 | Object newInstance = gsonProvider.getGson().fromJson(jsonDefaultValue, this.preferenceTypes.get(settingName));
302 | setGlobalSetting(settingName, newInstance, true);
303 | }
304 |
305 | public void resetSettings(Set keys){
306 | for (String key : keys) {
307 | resetSetting(key);
308 | }
309 | }
310 |
311 | IGsonProvider getGsonProvider() {
312 | return gsonProvider;
313 | }
314 |
315 | void logOutput(String message){
316 | if(this.logProvider != null)
317 | logProvider.logOutput(message);
318 | }
319 |
320 | void logError(String errorMessage){
321 | if(this.logProvider != null)
322 | logProvider.logError(errorMessage);
323 | }
324 |
325 | private void throwExceptionIfAlreadyRegistered(String settingName){
326 | if(this.preferenceVisibilities.get(settingName) != null)
327 | throw new RuntimeException("Setting " + settingName + " has already been registered with " +
328 | this.preferenceVisibilities.get(settingName) + " visibility.");
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/ProjectSettingStore.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 | import burp.IHttpRequestResponse;
5 | import burp.IHttpService;
6 | import com.google.gson.Gson;
7 | import com.google.gson.reflect.TypeToken;
8 |
9 | import java.io.PrintWriter;
10 | import java.io.StringWriter;
11 | import java.io.UnsupportedEncodingException;
12 | import java.lang.reflect.Type;
13 | import java.net.MalformedURLException;
14 | import java.net.URL;
15 | import java.net.URLEncoder;
16 | import java.util.HashMap;
17 |
18 | public class ProjectSettingStore implements IHttpRequestResponse {
19 |
20 | private final Preferences preferenceController;
21 | private final IBurpExtenderCallbacks callbacks;
22 | private final IHttpService httpService;
23 | private final byte[] requestBytes;
24 | private String serializedValue;
25 | private HashMap preferences;
26 | private HashMap preferenceTypes;
27 | private HashMap preferenceDefaults;
28 |
29 | public ProjectSettingStore(Preferences preferenceController, IBurpExtenderCallbacks callbacks,
30 | String extensionIdentifier) throws MalformedURLException, UnsupportedEncodingException {
31 | this.preferenceController = preferenceController;
32 | this.callbacks = callbacks;
33 | this.httpService = callbacks.getHelpers().buildHttpService("com.coreyd97.burpextenderutilities", 65535, true);
34 | String encodedExtensionIdentifier = URLEncoder.encode(extensionIdentifier, "UTF-8");
35 | this.requestBytes = callbacks.getHelpers().buildHttpRequest(
36 | new URL(httpService.getProtocol(), httpService.getHost(), httpService.getPort(), "/" + encodedExtensionIdentifier));
37 | this.preferences = new HashMap<>();
38 | this.preferenceTypes = new HashMap<>();
39 | this.preferenceDefaults = new HashMap<>();
40 | }
41 |
42 |
43 | public void registerSetting(String settingName, Type type) {
44 | this.registerSetting(settingName, type, null);
45 | }
46 |
47 | public void registerSetting(String settingName, Type type, Object defaultValue) {
48 | if(this.preferenceTypes.containsKey(settingName)){
49 | throw new RuntimeException("Setting " + settingName + " has already been registered in the project settings store!");
50 | }
51 |
52 | this.preferenceTypes.put(settingName, type);
53 |
54 | if(this.preferences.get(settingName) == null){
55 | this.preferences.put(settingName, defaultValue);
56 | }else{
57 | try {
58 | String existingSerializedValue = (String) this.preferences.get(settingName);
59 | Object deserializedValue = this.preferenceController.getGsonProvider()
60 | .getGson().fromJson(existingSerializedValue, type);
61 | this.preferences.put(settingName, deserializedValue);
62 | preferenceController.logOutput("Deserialized existing value.");
63 | } catch (Exception e) {
64 | StringWriter sw = new StringWriter();
65 | e.printStackTrace(new PrintWriter(sw));
66 | preferenceController.logError(sw.toString());
67 | preferenceController.logError("Could not deserialize the loaded value for setting " +
68 | "\"" + settingName + "\" to type \"" + type + "\". Falling back to the default value.");
69 | this.preferences.put(settingName, defaultValue);
70 | }
71 | }
72 |
73 | this.preferenceDefaults.put(settingName, defaultValue);
74 | preferenceController.logOutput("Project setting \"" + settingName + "\" registered with type " + type.getTypeName()
75 | + " and default value: " + (defaultValue != null ? defaultValue : "null"));
76 | }
77 |
78 | void setSetting(String setting, Object value){
79 | this.preferences.put(setting, value);
80 | saveToProject();
81 | }
82 |
83 | Object getSetting(String settingName){
84 | return this.preferences.get(settingName);
85 | }
86 |
87 | private void loadSettingsFromJson(String json){
88 | //Initially load the stored values as key, serialized value pairs.
89 | //When settings are registered, we will get their value and convert into the requested type.
90 | //We can then update the entry with the converted type.
91 | Gson gson = this.preferenceController.getGsonProvider().getGson();
92 | HashMap tempPreferences = gson.fromJson(json, new TypeToken>(){}.getType());
93 | if(this.preferences == null){
94 | this.preferences = new HashMap<>();
95 | }
96 | if(tempPreferences != null) {
97 | for (String key : tempPreferences.keySet()) {
98 | Object value = tempPreferences.get(key);
99 | this.preferences.put(key, gson.toJson(value));
100 | }
101 | }
102 |
103 | this.serializedValue = json;
104 | }
105 |
106 | public String getJSONSettings() {
107 | return this.preferenceController.getGsonProvider().getGson().toJson(this.preferences);
108 | }
109 |
110 | public void saveToProject(){
111 | this.serializedValue = this.preferenceController.getGsonProvider().getGson().toJson(this.preferences);
112 | this.callbacks.addToSiteMap(this);
113 | }
114 |
115 | @Override
116 | public byte[] getRequest() {
117 | return this.requestBytes;
118 | }
119 |
120 | @Override
121 | public void setRequest(byte[] message) {}
122 |
123 | @Override
124 | public byte[] getResponse() {
125 | if(serializedValue == null) return "".getBytes();
126 | return serializedValue.getBytes();
127 | }
128 |
129 | @Override
130 | public void setResponse(byte[] message) {
131 | loadSettingsFromJson(new String(message));
132 | //Parse the value and load the setting elements.
133 | }
134 |
135 | @Override
136 | public String getComment() {
137 | return null;
138 | }
139 |
140 | @Override
141 | public void setComment(String comment) {}
142 |
143 | @Override
144 | public String getHighlight() {
145 | return null;
146 | }
147 |
148 | @Override
149 | public void setHighlight(String color) {}
150 |
151 | @Override
152 | public IHttpService getHttpService() {
153 | return this.httpService;
154 | }
155 |
156 | @Override
157 | public void setHttpService(IHttpService httpService) {}
158 |
159 | public Type getSettingType(String settingName) {
160 | return this.preferenceTypes.get(settingName);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/ScrollablePanel.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 |
6 | /**
7 | * http://www.camick.com/java/source/ScrollablePanel.java
8 | */
9 | public class ScrollablePanel extends JPanel
10 | implements Scrollable, SwingConstants
11 | {
12 | public enum ScrollableSizeHint
13 | {
14 | NONE,
15 | FIT,
16 | STRETCH
17 | }
18 |
19 | public enum IncrementType
20 | {
21 | PERCENT,
22 | PIXELS
23 | }
24 |
25 | private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE;
26 | private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE;
27 |
28 | private IncrementInfo horizontalBlock;
29 | private IncrementInfo horizontalUnit;
30 | private IncrementInfo verticalBlock;
31 | private IncrementInfo verticalUnit;
32 |
33 | /**
34 | * Default constructor that uses a FlowLayout
35 | */
36 | public ScrollablePanel()
37 | {
38 | this( new FlowLayout() );
39 | }
40 |
41 | /**
42 | * Constuctor for specifying the LayoutManager of the panel.
43 | *
44 | * @param layout the LayountManger for the panel
45 | */
46 | public ScrollablePanel(LayoutManager layout)
47 | {
48 | super( layout );
49 |
50 | IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100);
51 | IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10);
52 |
53 | setScrollableBlockIncrement(HORIZONTAL, block);
54 | setScrollableBlockIncrement(VERTICAL, block);
55 | setScrollableUnitIncrement(HORIZONTAL, unit);
56 | setScrollableUnitIncrement(VERTICAL, unit);
57 | }
58 |
59 | /**
60 | * Get the height ScrollableSizeHint enum
61 | *
62 | * @return the ScrollableSizeHint enum for the height
63 | */
64 | public ScrollableSizeHint getScrollableHeight()
65 | {
66 | return scrollableHeight;
67 | }
68 |
69 | /**
70 | * Set the ScrollableSizeHint enum for the height. The enum is used to
71 | * determine the boolean value that is returned by the
72 | * getScrollableTracksViewportHeight() method. The valid values are:
73 | *
74 | * ScrollableSizeHint.NONE - return "false", which causes the height
75 | * of the panel to be used when laying out the children
76 | * ScrollableSizeHint.FIT - return "true", which causes the height of
77 | * the viewport to be used when laying out the children
78 | * ScrollableSizeHint.STRETCH - return "true" when the viewport height
79 | * is greater than the height of the panel, "false" otherwise.
80 | *
81 | * @param scrollableHeight as represented by the ScrollableSizeHint enum.
82 | */
83 | public void setScrollableHeight(ScrollableSizeHint scrollableHeight)
84 | {
85 | this.scrollableHeight = scrollableHeight;
86 | revalidate();
87 | }
88 |
89 | /**
90 | * Get the width ScrollableSizeHint enum
91 | *
92 | * @return the ScrollableSizeHint enum for the width
93 | */
94 | public ScrollableSizeHint getScrollableWidth()
95 | {
96 | return scrollableWidth;
97 | }
98 |
99 | /**
100 | * Set the ScrollableSizeHint enum for the width. The enum is used to
101 | * determine the boolean value that is returned by the
102 | * getScrollableTracksViewportWidth() method. The valid values are:
103 | *
104 | * ScrollableSizeHint.NONE - return "false", which causes the width
105 | * of the panel to be used when laying out the children
106 | * ScrollableSizeHint.FIT - return "true", which causes the width of
107 | * the viewport to be used when laying out the children
108 | * ScrollableSizeHint.STRETCH - return "true" when the viewport width
109 | * is greater than the width of the panel, "false" otherwise.
110 | *
111 | * @param scrollableWidth as represented by the ScrollableSizeHint enum.
112 | */
113 | public void setScrollableWidth(ScrollableSizeHint scrollableWidth)
114 | {
115 | this.scrollableWidth = scrollableWidth;
116 | revalidate();
117 | }
118 |
119 | /**
120 | * Get the block IncrementInfo for the specified orientation
121 | *
122 | * @return the block IncrementInfo for the specified orientation
123 | */
124 | public IncrementInfo getScrollableBlockIncrement(int orientation)
125 | {
126 | return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock;
127 | }
128 |
129 | /**
130 | * Specify the information needed to do block scrolling.
131 | *
132 | * @param orientation specify the scrolling orientation. Must be either:
133 | * SwingContants.HORIZONTAL or SwingContants.VERTICAL.
134 | * @paran type specify how the amount parameter in the calculation of
135 | * the scrollable amount. Valid values are:
136 | * IncrementType.PERCENT - treat the amount as a % of the viewport size
137 | * IncrementType.PIXEL - treat the amount as the scrollable amount
138 | * @param amount a value used with the IncrementType to determine the
139 | * scrollable amount
140 | */
141 | public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount)
142 | {
143 | IncrementInfo info = new IncrementInfo(type, amount);
144 | setScrollableBlockIncrement(orientation, info);
145 | }
146 |
147 | /**
148 | * Specify the information needed to do block scrolling.
149 | *
150 | * @param orientation specify the scrolling orientation. Must be either:
151 | * SwingContants.HORIZONTAL or SwingContants.VERTICAL.
152 | * @param info An IncrementInfo object containing information of how to
153 | * calculate the scrollable amount.
154 | */
155 | public void setScrollableBlockIncrement(int orientation, IncrementInfo info)
156 | {
157 | switch(orientation)
158 | {
159 | case SwingConstants.HORIZONTAL:
160 | horizontalBlock = info;
161 | break;
162 | case SwingConstants.VERTICAL:
163 | verticalBlock = info;
164 | break;
165 | default:
166 | throw new IllegalArgumentException("Invalid orientation: " + orientation);
167 | }
168 | }
169 |
170 | /**
171 | * Get the unit IncrementInfo for the specified orientation
172 | *
173 | * @return the unit IncrementInfo for the specified orientation
174 | */
175 | public IncrementInfo getScrollableUnitIncrement(int orientation)
176 | {
177 | return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit;
178 | }
179 |
180 | /**
181 | * Specify the information needed to do unit scrolling.
182 | *
183 | * @param orientation specify the scrolling orientation. Must be either:
184 | * SwingContants.HORIZONTAL or SwingContants.VERTICAL.
185 | * @paran type specify how the amount parameter in the calculation of
186 | * the scrollable amount. Valid values are:
187 | * IncrementType.PERCENT - treat the amount as a % of the viewport size
188 | * IncrementType.PIXEL - treat the amount as the scrollable amount
189 | * @param amount a value used with the IncrementType to determine the
190 | * scrollable amount
191 | */
192 | public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount)
193 | {
194 | IncrementInfo info = new IncrementInfo(type, amount);
195 | setScrollableUnitIncrement(orientation, info);
196 | }
197 |
198 | /**
199 | * Specify the information needed to do unit scrolling.
200 | *
201 | * @param orientation specify the scrolling orientation. Must be either:
202 | * SwingContants.HORIZONTAL or SwingContants.VERTICAL.
203 | * @param info An IncrementInfo object containing information of how to
204 | * calculate the scrollable amount.
205 | */
206 | public void setScrollableUnitIncrement(int orientation, IncrementInfo info)
207 | {
208 | switch(orientation)
209 | {
210 | case SwingConstants.HORIZONTAL:
211 | horizontalUnit = info;
212 | break;
213 | case SwingConstants.VERTICAL:
214 | verticalUnit = info;
215 | break;
216 | default:
217 | throw new IllegalArgumentException("Invalid orientation: " + orientation);
218 | }
219 | }
220 |
221 | // Implement Scrollable interface
222 |
223 | public Dimension getPreferredScrollableViewportSize()
224 | {
225 | return getPreferredSize();
226 | }
227 |
228 | public int getScrollableUnitIncrement(
229 | Rectangle visible, int orientation, int direction)
230 | {
231 | switch(orientation)
232 | {
233 | case SwingConstants.HORIZONTAL:
234 | return getScrollableIncrement(horizontalUnit, visible.width);
235 | case SwingConstants.VERTICAL:
236 | return getScrollableIncrement(verticalUnit, visible.height);
237 | default:
238 | throw new IllegalArgumentException("Invalid orientation: " + orientation);
239 | }
240 | }
241 |
242 | public int getScrollableBlockIncrement(
243 | Rectangle visible, int orientation, int direction)
244 | {
245 | switch(orientation)
246 | {
247 | case SwingConstants.HORIZONTAL:
248 | return getScrollableIncrement(horizontalBlock, visible.width);
249 | case SwingConstants.VERTICAL:
250 | return getScrollableIncrement(verticalBlock, visible.height);
251 | default:
252 | throw new IllegalArgumentException("Invalid orientation: " + orientation);
253 | }
254 | }
255 |
256 | protected int getScrollableIncrement(IncrementInfo info, int distance)
257 | {
258 | if (info.getIncrement() == IncrementType.PIXELS)
259 | return info.getAmount();
260 | else
261 | return distance * info.getAmount() / 100;
262 | }
263 |
264 | public boolean getScrollableTracksViewportWidth()
265 | {
266 | if (scrollableWidth == ScrollableSizeHint.NONE)
267 | return false;
268 |
269 | if (scrollableWidth == ScrollableSizeHint.FIT)
270 | return true;
271 |
272 | // STRETCH sizing, use the greater of the panel or viewport width
273 |
274 | if (getParent() instanceof JViewport)
275 | {
276 | return (((JViewport)getParent()).getWidth() > getPreferredSize().width);
277 | }
278 |
279 | return false;
280 | }
281 |
282 | public boolean getScrollableTracksViewportHeight()
283 | {
284 | if (scrollableHeight == ScrollableSizeHint.NONE)
285 | return false;
286 |
287 | if (scrollableHeight == ScrollableSizeHint.FIT)
288 | return true;
289 |
290 | // STRETCH sizing, use the greater of the panel or viewport height
291 |
292 |
293 | if (getParent() instanceof JViewport)
294 | {
295 | return (((JViewport)getParent()).getHeight() > getPreferredSize().height);
296 | }
297 |
298 | return false;
299 | }
300 |
301 | /**
302 | * Helper class to hold the information required to calculate the scroll amount.
303 | */
304 | static class IncrementInfo
305 | {
306 | private IncrementType type;
307 | private int amount;
308 |
309 | public IncrementInfo(IncrementType type, int amount)
310 | {
311 | this.type = type;
312 | this.amount = amount;
313 | }
314 |
315 | public IncrementType getIncrement()
316 | {
317 | return type;
318 | }
319 |
320 | public int getAmount()
321 | {
322 | return amount;
323 | }
324 |
325 | public String toString()
326 | {
327 | return
328 | "ScrollablePanel[" +
329 | type + ", " +
330 | amount + "]";
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/StdOutLogger.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | public class StdOutLogger implements ILogProvider {
4 |
5 | @Override
6 | public void logOutput(String message) {
7 | System.out.println(message);
8 | }
9 |
10 | @Override
11 | public void logError(String errorMessage) {
12 | System.err.println(errorMessage);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/TypeAdapter/AtomicIntegerTypeAdapter.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities.TypeAdapter;
2 |
3 | import com.google.gson.*;
4 |
5 | import java.lang.reflect.Type;
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | public class AtomicIntegerTypeAdapter
9 | implements JsonSerializer, JsonDeserializer {
10 | @Override public JsonElement serialize(AtomicInteger src, Type typeOfSrc, JsonSerializationContext context) {
11 | return new JsonPrimitive(src.incrementAndGet());
12 | }
13 |
14 | @Override public AtomicInteger deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
15 | throws JsonParseException {
16 | int intValue = json.getAsInt();
17 | return new AtomicInteger(--intValue);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/TypeAdapter/ByteArrayToBase64TypeAdapter.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities.TypeAdapter;
2 |
3 | import com.google.gson.*;
4 |
5 | import java.lang.reflect.Type;
6 | import java.util.Base64;
7 |
8 | public class ByteArrayToBase64TypeAdapter implements JsonSerializer, JsonDeserializer {
9 | public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
10 | return Base64.getDecoder().decode(json.getAsString());
11 | }
12 |
13 | public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
14 | return new JsonPrimitive(Base64.getEncoder().encodeToString(src));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/coreyd97/BurpExtenderUtilities/VariableViewPanel.java:
--------------------------------------------------------------------------------
1 | package com.coreyd97.BurpExtenderUtilities;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 |
6 | /**
7 | * Created by corey on 24/08/17.
8 | */
9 | public class VariableViewPanel extends JPanel {
10 | public enum View {HORIZONTAL, VERTICAL, TABS}
11 | private final Component a;
12 | private final String aTitle;
13 | private final Component b;
14 | private final String bTitle;
15 | private Preferences preferences;
16 | private String preferenceKey;
17 | private Component wrapper;
18 | private View view;
19 |
20 | public VariableViewPanel(Preferences prefs, String preferenceKey,
21 | Component a, String aTitle, Component b, String bTitle,
22 | View defaultView){
23 | this.preferences = prefs;
24 | this.preferenceKey = preferenceKey;
25 |
26 | if(this.preferences != null && this.preferenceKey != null){
27 | this.preferences.registerSetting(this.preferenceKey, View.class, defaultView, Preferences.Visibility.GLOBAL);
28 | view = (View) this.preferences.getSetting(this.preferenceKey);
29 | }else{
30 | view = defaultView;
31 | }
32 |
33 | this.a = a;
34 | this.aTitle = aTitle;
35 | this.b = b;
36 | this.bTitle = bTitle;
37 | this.setLayout(new BorderLayout());
38 | this.setView(view);
39 | }
40 |
41 | public VariableViewPanel(Preferences preferences, String preferenceKey,
42 | Component a, String aTitle, Component b, String bTitle) {
43 | this(preferences, preferenceKey, a, aTitle, b, bTitle, null);
44 | }
45 |
46 | public VariableViewPanel(Component a, String aTitle, Component b, String bTitle, View defaultView){
47 | this(null, null, a, aTitle, b, bTitle, defaultView);
48 | }
49 |
50 | public View getView(){
51 | return this.view;
52 | }
53 |
54 | public void setView(View view){
55 | if(view == null) view = View.VERTICAL;
56 | switch (view){
57 | case HORIZONTAL:
58 | case VERTICAL: {
59 | this.wrapper = new JSplitPane();
60 | ((JSplitPane) wrapper).setLeftComponent(a);
61 | ((JSplitPane) wrapper).setRightComponent(b);
62 | if(view == View.HORIZONTAL){
63 | ((JSplitPane) wrapper).setOrientation(JSplitPane.HORIZONTAL_SPLIT);
64 | }else{
65 | ((JSplitPane) wrapper).setOrientation(JSplitPane.VERTICAL_SPLIT);
66 | }
67 | ((JSplitPane) wrapper).setResizeWeight(0.5);
68 | break;
69 | }
70 | case TABS: {
71 | this.wrapper = new JTabbedPane();
72 | ((JTabbedPane) wrapper).addTab(aTitle, a);
73 | ((JTabbedPane) wrapper).addTab(bTitle, b);
74 | break;
75 | }
76 | }
77 | this.removeAll();
78 | this.add(wrapper, BorderLayout.CENTER);
79 | this.revalidate();
80 | this.repaint();
81 | this.view = view;
82 |
83 | if(this.preferences != null && this.preferenceKey != null) {
84 | this.preferences.setSetting(this.preferenceKey, view);
85 | }
86 | }
87 |
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/taborator.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------