├── .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 | ![Tab Screenshot](https://github.com/hackvertor/taborator/blob/master/images/screenshot-tab.png) 13 | 14 | ![Client Screenshot](https://github.com/hackvertor/taborator/blob/master/images/screenshot-client.png) -------------------------------------------------------------------------------- /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 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 | --------------------------------------------------------------------------------