├── screenshot.png ├── BappManifest.bmf ├── BappDescription.html ├── README.md └── AdHocPayloadProccessor.rb /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/adhoc-payload-processors/master/screenshot.png -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: ab878129f0de4649b7952b0cfadc9f66 2 | ExtensionType: 3 3 | Name: Adhoc Payload Processors 4 | RepoName: adhoc-payload-processors 5 | ScreenVersion: 1.1 6 | SerialVersion: 6 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: Geoff Walton 10 | ShortDescription: Generate payload processors on the fly - without having to create individual extensions. 11 | EntryPoint: AdHocPayloadProccessor.rb 12 | BuildCommand: 13 | SupportedProducts: Pro, Community 14 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |
Generate payload processors on the fly, without having to create individual extensions.
2 |Creates a new suite tab "Adhoc Payload Processing" that allows users to quickly define new payload processors with a single ruby function with a signature similar to the Burp Extension API using Ruby strings. All the boiler plate of registering extensions is handled automatically. The tool allows for creating, removing, and saving your payload processors within Burp suite.
3 |Once the function body is defined simply click "Create Payload Processor" to make it immediately available in Intruder. Processors can be similarly removed with the "Remove Processor" button and a fresh processor template is always available clicking "Restore Template." Finally, the currently defined payload processors can be saved using the "Save Extension State" button; this will preserve payload processors across restarts of the suite.
4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # burp--Adhoc-Payload-Processors 2 | Generate payload processors on the fly - without having to create individual extensions. 3 | 4 | Name: Adhoc Payload Processing 5 | 6 | Description: 7 | Creates a new suite tab “Adhoc Payload Processing” that allows users to quickly define new payload processors with a single ruby function with a signature similar to the Burp Extension API using Ruby strings. All the boiler plate of registering extensions is handled automatically. The tool allows for creating, removing, and saving your payload processors within Burp suite. 8 | 9 | Once the function body is defined simply click “Create Payload Processor” to make it immediately available in Intruder. Processors can be similarly removed with the “Remove Processor” button and a fresh processor template is always available clicking “Restore Template.” Finally, the currently defined payload processors can be saved using the “Save Extension State” button; this will preserve payload processors across restarts of the suite. 10 | 11 | Note: there are probably UI layout issues with larger fonts 12 | 13 | Screenshot: 14 |  15 | -------------------------------------------------------------------------------- /AdHocPayloadProccessor.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | java_import 'burp.IExtensionHelpers' 3 | 4 | java_import 'javax.swing.JOptionPane' 5 | java_import 'burp.ITab' 6 | java_import 'javax.swing.JPanel' 7 | java_import 'javax.swing.JScrollPane' 8 | java_import 'java.awt.Dimension' 9 | java_import 'java.awt.Rectangle' 10 | java_import 'java.awt.event.ComponentListener' 11 | 12 | class AbstractBrupExtensionUI < JScrollPane 13 | include ITab 14 | include ComponentListener 15 | 16 | def initialize(extension) 17 | @panel = JPanel.new 18 | #@panel.setPreferredSize(Dimension.new(1024,768)) 19 | @panel.setLayout nil 20 | super(@panel) 21 | @extension = extension 22 | addComponentListener self 23 | end 24 | 25 | def extensionName 26 | @extension.extensionName 27 | end 28 | 29 | def add(component) 30 | bounds = component.getBounds 31 | updateSize(bounds.getX + bounds.getWidth, bounds.getY + bounds.getHeight) 32 | @panel.add component 33 | end 34 | 35 | alias_method :getTabCaption, :extensionName 36 | 37 | def getUiComponent 38 | self 39 | end 40 | 41 | private 42 | #Don't set the size smaller than existing widget positions 43 | def updateSize(x,y) 44 | x = (@panel.getWidth() > x) ? @panel.getWidth : x 45 | y = (@panel.getHeight() > y) ? @panel.getHeight : y 46 | @panel.setPreferredSize(Dimension.new(x,y)) 47 | end 48 | 49 | end 50 | 51 | java_import('java.awt.Insets') 52 | class AbstractBurpUIElement 53 | def initialize(parent, obj, positionX, positionY, width, height) 54 | @swingElement =obj 55 | setPosition parent, positionX, positionY, width, height 56 | parent.add @swingElement 57 | end 58 | 59 | def method_missing(method, *args, &block) 60 | @swingElement.send(method, *args) 61 | end 62 | 63 | private 64 | def setPosition(parent, x,y,width,height) 65 | insets = parent.getInsets 66 | size = @swingElement.getPreferredSize() 67 | w = (width > size.width) ? width : size.width 68 | h = (height > size.height) ? height : size.height 69 | @swingElement.setBounds(x + insets.left, y + insets.top, w, h) 70 | end 71 | end 72 | 73 | class BPanel < AbstractBurpUIElement 74 | include ComponentListener 75 | 76 | def initialize(parent, positionX, positionY, width, height) 77 | obj = JPanel.new 78 | obj.setLayout nil 79 | super parent, obj, positionX,positionY, width, height 80 | end 81 | end 82 | 83 | java_import 'javax.swing.JLabel' 84 | class BLabel < AbstractBurpUIElement 85 | def initialize(parent, positionX, positionY, width, height, caption, align= :left) 86 | case align 87 | when :left 88 | a = 2 89 | when :right 90 | a = 4 91 | when :center 92 | a = 0 93 | else 94 | a = 2 #align left 95 | end 96 | super parent, JLabel.new(caption, a),positionX, positionY, width, height 97 | end 98 | end 99 | 100 | 101 | java_import 'javax.swing.JButton' 102 | class BButton < AbstractBurpUIElement 103 | def initialize(parent, positionX, positionY, width, height, caption, &onClick) 104 | super parent, JButton.new(caption), positionX, positionY, width, height 105 | @swingElement.add_action_listener onClick 106 | end 107 | end 108 | 109 | java_import 'javax.swing.JSeparator' 110 | class BHorizSeparator < AbstractBurpUIElement 111 | def initialize(parent, positionX, positionY, width) 112 | super parent, JSeparator.new(0), positionX, positionY, width, 1 113 | end 114 | end 115 | 116 | class BVertSeparator < AbstractBurpUIElement 117 | def initialize(parent, positionX, positionY, height) 118 | super parent, JSeparator.new(1), positionX, positionY, 1, height 119 | end 120 | end 121 | 122 | java_import 'javax.swing.JCheckBox' 123 | class BCheckBox < AbstractBurpUIElement 124 | def initialize(parent, positionX, positionY, width, height, caption) 125 | super parent, JCheckBox.new(caption), positionX, positionY, width, height 126 | end 127 | end 128 | 129 | java_import 'javax.swing.JTextField' 130 | class BTextField < AbstractBurpUIElement 131 | def initialize(parent, positionX, positionY, width, height, caption) 132 | super parent, JTextField.new(caption), positionX, positionY, width, height 133 | end 134 | end 135 | 136 | java_import 'javax.swing.JComboBox' 137 | class BComboBox < AbstractBurpUIElement 138 | def initialize(parent, positionX, positionY, width, height, &evt) 139 | super parent, JComboBox.new, positionX, positionY, width, height 140 | @swingElement.add_action_listener evt 141 | end 142 | end 143 | 144 | java_import 'javax.swing.JTextArea' 145 | class BTextArea < AbstractBurpUIElement 146 | def initialize(parent, positionX, positionY, width, height) 147 | @textArea = JTextArea.new 148 | super parent, JScrollPane.new(@textArea), positionX, positionY, width, height 149 | @textArea.setLineWrap(true) 150 | end 151 | 152 | def setText(text) 153 | @textArea.setText text 154 | end 155 | 156 | def getText 157 | @textArea.getText 158 | end 159 | 160 | def setEditable(value) 161 | @textArea.setEditable(value) 162 | end 163 | end 164 | 165 | java_import 'burp.ITextEditor' 166 | class BTextEditor < AbstractBurpUIElement 167 | def initialize(parent, callbacks, positionX, positionY, width, height) 168 | @textArea = callbacks.createTextEditor 169 | super parent, JScrollPane.new(@textArea.getComponent), positionX, positionY, width, height 170 | end 171 | 172 | def setText(text) 173 | @textArea.setText text.bytes 174 | end 175 | 176 | def getText 177 | @textArea.getText.map {|b| b.chr}.join 178 | end 179 | end 180 | 181 | ######################################################################################### 182 | #Begin Burp Extension 183 | ######################################################################################### 184 | 185 | java_import 'burp.IIntruderPayloadProcessor' 186 | require 'base64' 187 | class PayloadProcessorFactory 188 | SETTINGKEY = 'AdHocExtState' 189 | attr_reader :extensionName 190 | attr_accessor :callbacks 191 | 192 | class AbstractPayloadProcessor 193 | include IIntruderPayloadProcessor 194 | 195 | attr_accessor :originalText 196 | 197 | def initialize(processorName, helpers) 198 | @processorName = processorName 199 | @helpers = helpers 200 | end 201 | 202 | def getProcessorName 203 | @processorName 204 | end 205 | 206 | def processPayload(bCurrentPayload, bOriginalPayload, bBaseValue) 207 | currentPayload = @helpers.bytesToString(bCurrentPayload) 208 | originalPayload = @helpers.bytesToString(bOriginalPayload) 209 | baseValue = @helpers.bytesToString(bBaseValue) 210 | @helpers.stringToBytes(userProcessPayload(currentPayload, originalPayload, baseValue)) 211 | rescue RuntimeError,ArgumentError => e 212 | puts e.message + ':' + 'for ' + currentPayload + ' / ' + originalPayload + ' / ' + baseValue 213 | "ERROR" 214 | end 215 | 216 | def userProcessPayload(currentPayload, originalPayload, baseValue) 217 | "" #emtpy string 218 | end 219 | 220 | def method_missing(symbol, *args, &blk) 221 | if @helpers.respond_to? symbol 222 | @helpers.send symbol, *args, &blk 223 | else 224 | raise NoMethodError.new("Method `#{m}` doesn't exist.") 225 | end 226 | end 227 | end 228 | 229 | def initialize(name) 230 | @extensionName = name 231 | end 232 | 233 | def getProcessors 234 | #Return an array with processor names 235 | processors = @callbacks.getIntruderPayloadProcessors 236 | processors.map {|p| p.getProcessorName} 237 | end 238 | 239 | def getProcessorText(name) 240 | processors = @callbacks.getIntruderPayloadProcessors 241 | processors.each {|p| return p.originalText if p.getProcessorName == name} 242 | '' 243 | end 244 | 245 | def saveExtensionState 246 | processors = @callbacks.getIntruderPayloadProcessors 247 | items = processors.map {|p| [p.getProcessorName, p.originalText]}.to_h 248 | @callbacks.saveExtensionSetting(SETTINGKEY, Base64.strict_encode64(Marshal.dump(items))) 249 | end 250 | 251 | def loadExtensionState 252 | stored = @callbacks.loadExtensionSetting(SETTINGKEY) 253 | return if stored.nil? 254 | items = Marshal.load Base64.strict_decode64(stored) 255 | items.each {|key,value| create(key, value)} 256 | end 257 | 258 | def destroy(processorName) 259 | processors = @callbacks.getIntruderPayloadProcessors 260 | processors.each do |processor| 261 | if processor.getProcessorName() == processorName 262 | @callbacks.removeIntruderPayloadProcessor(processor) 263 | processor = nil 264 | end 265 | end 266 | rescue => e 267 | raise RuntimeError, "Failed to remove payload Processor: #{e.message}" 268 | end 269 | 270 | def create(name, body) 271 | anon = klass(name, body).new(name, @callbacks.getHelpers) 272 | anon.originalText = body 273 | callbacks.registerIntruderPayloadProcessor anon 274 | rescue => e 275 | raise ScriptError, "Unable to create payload processor #{name}: #{e.message}" 276 | end 277 | 278 | def test(name, body, currentPayload, originalPayload, baseValue) 279 | anon = klass(name, body).new(name, @callbacks.getHelpers) 280 | return anon.userProcessPayload(currentPayload, originalPayload, baseValue) 281 | end 282 | 283 | def klass(name, body) 284 | eval "Class.new(AbstractPayloadProcessor) do\n#{body}\nend" 285 | end 286 | end 287 | 288 | class ExtensionUI < AbstractBrupExtensionUI 289 | 290 | SAMPLEBODY = <<"HERE" 291 | #This method is invoked each time the payload processor is applied 292 | #to an intruder payload. 293 | # 294 | #(string) currentPayload, The value of payload to be processed 295 | #(string) originalPayload, the value of the original payload prior to 296 | # processing by any already-applied processing rules. 297 | #(string) baseValue the base value of the payload position 298 | # You can also use the string related extension helpers functions 299 | # urlEncode, urlDecode, base64Encode, base64Encode, stringToBytes, and 300 | # and bytesToString, directly without additional requires/imports. 301 | 302 | def userProcessPayload(currentPayload, originalPayload, baseValue) 303 | "Sample Payload" 304 | end 305 | HERE 306 | 307 | def buildUI(callbacks) 308 | @c1 = {}; @c2 = {}; 309 | @c1[:lb1] = BLabel.new self, 2, 2, 300, 0, 'Define Payload Processor:' 310 | @c2[:lb1] = BLabel.new self, 705, 2, 50, 0, 'Test Processor:' 311 | @c1[:lb2] = BLabel.new self,2, 26, 0,0, 'Define the body of the ruby function userProcessPayload below.' 312 | @c1[:lb3] = BLabel.new self, 2,50,0,0, 'The value this function yields will be provided to intruder.' 313 | @c1[:lb4] = BLabel.new self,2, 74, 0,0, 'You may define additional functions or require external files as well.' 314 | @c2[:lb2] = BLabel.new self, 705,26,290, 0, "Test the current processor at the left." 315 | @CtrlPanel = BPanel.new(self, 0,502, 714, 220) 316 | @txtArea = BTextEditor.new( self, callbacks, 2, 100,700,400) 317 | @txtArea.setText(SAMPLEBODY) 318 | @c1[:txtArea] = @txtArea 319 | BLabel.new @CtrlPanel, 2,0, 0,0, 'Name for payload processor:' 320 | @txtName = BTextField.new(@CtrlPanel, 362, 0, 350, 12, "NewPayloadProcessor#{rand(5000).to_s}") 321 | BButton.new( @CtrlPanel, 2, 28, 350,0, 'Create Payload Processor') { |evt| createOnClick } 322 | @cmbProcs = BComboBox.new(@CtrlPanel, 2, 58, 350, 0) { |evt| onCmbChange } 323 | @cmbChanges = true 324 | updateRemoveList 325 | BButton.new(@CtrlPanel, 362, 58,350,0, 'Remove Processor') {|evt| removeOnClick } 326 | BButton.new(@CtrlPanel,362,98,350,0, 'Restore Template') {|evt| templateOnClick } 327 | BButton.new(@CtrlPanel, 2, 98, 350, 0, 'Save Extension State') {|evt| saveOnClick } 328 | 329 | @c2[:lb3] = BLabel.new self, 705,50,290 ,0, "Current Payload Test Value:" 330 | @txtCurrentValue = BTextField.new(self, 705,74,290,0, "test") 331 | @c2[:txt1] = @txtCurrentValue 332 | @c2[:lb4] = BLabel.new self, 705,104,290 ,0, "Original Payload Test Value:" 333 | @txtOriginalValue = BTextField.new(self, 705,128,290,0, "test") 334 | @c2[:txt2] = @txtOriginalValue 335 | @c2[:lb4] = BLabel.new self, 705,156,290 ,0, "Base Test Value:" 336 | @txtBaseValue = BTextField.new(self, 705,180,290,0, "test") 337 | @c2[:txt3] = @txtBaseValue 338 | @c2[:btn1] = BButton.new(self,705,210,290,0, "Execute") { |evt| executeOnClick } 339 | @c2[:lb5] = BLabel.new self, 705, 240, 290, 0, "Result:" 340 | @txtResult = BTextArea.new(self, 705, 266,290,78) 341 | @txtResult.setEditable(false) 342 | @c2[:txt4] = @txtResult 343 | componentResized nil 344 | end 345 | 346 | def componentResized(evt) 347 | bounds = self.getBounds 348 | w = bounds.getWidth - 298 349 | x = bounds.getWidth - 292 350 | if bounds.getWidth > 1023 351 | @c1.each_value { |widget| rect = widget.getBounds; h = rect.getHeight; rect.setSize(w,h); widget.setBounds(rect) } 352 | @c2.each_value { |widget| rect = widget.getBounds; y = rect.getY; rect.setLocation(x,y); widget.setBounds(rect) } 353 | end 354 | h = bounds.getHeight 355 | if h > 619 356 | rect = @txtArea.getBounds; w = rect.getWidth; rect.setSize(w,h - 226); @txtArea.setBounds(rect) 357 | h = @txtArea.getBounds().getHeight() + 102 358 | rect = @CtrlPanel.getBounds 359 | rect.setLocation(0,h) 360 | @CtrlPanel.setBounds rect 361 | end 362 | end 363 | 364 | def executeOnClick 365 | @txtResult.setText(@extension.test(@txtName.getText, @txtArea.getText, @txtCurrentValue.getText, @txtOriginalValue.getText, @txtBaseValue.getText)) 366 | rescue StandardError,ScriptError,SecurityError => e 367 | JOptionPane.showMessageDialog(self, e.message, 'Error', 0) 368 | end 369 | 370 | def saveOnClick 371 | JOptionPane.showMessageDialog(self, "External files and other resources, 'requires' etc are not saved!\nYou will need to ensure these are present for processors that depend on them.") 372 | @extension.saveExtensionState 373 | end 374 | 375 | def updateRemoveList 376 | @cmbChanges = false 377 | @cmbProcs.removeAllItems 378 | @extension.getProcessors.each {|p| @cmbProcs.addItem(p) } 379 | @cmbChanges = true 380 | end 381 | 382 | def onCmbChange 383 | if @cmbChanges == true 384 | @txtArea.setText(@extension.getProcessorText(@cmbProcs.getSelectedItem)) 385 | end 386 | end 387 | 388 | def createOnClick 389 | if @extension.getProcessorText(@txtName.getText) == '' 390 | @extension.create(@txtName.getText, @txtArea.getText) 391 | @txtName.setText "NewPayloadProcessor#{rand(5000).to_s}" 392 | else 393 | JOptionPane.showMessageDialog(self, 'Name Conflict Please Choose another name.') 394 | end 395 | updateRemoveList 396 | rescue ScriptError => e 397 | JOptionPane.showMessageDialog(self, e.message, 'Error', 0) 398 | end 399 | 400 | def templateOnClick 401 | @txtArea.setText(SAMPLEBODY) 402 | end 403 | 404 | def removeOnClick 405 | @extension.destroy(@cmbProcs.getSelectedItem) 406 | updateRemoveList 407 | rescue RuntimeError => e 408 | JOptionPane.showMessageDialog(self, e.message, 'Error', 0) 409 | end 410 | 411 | end 412 | 413 | 414 | java_import 'burp.IBurpExtender' 415 | class BurpExtender 416 | include IBurpExtender 417 | ExtensionName = 'Adhoc Payload Processing' 418 | 419 | def initialize 420 | @payloadprocessorfactory = PayloadProcessorFactory.new ExtensionName 421 | @extensionInterface = ExtensionUI.new @payloadprocessorfactory 422 | end 423 | 424 | def registerExtenderCallbacks(callbacks) 425 | callbacks.setExtensionName ExtensionName 426 | callbacks.addSuiteTab @extensionInterface 427 | @payloadprocessorfactory.callbacks = callbacks 428 | @payloadprocessorfactory.loadExtensionState 429 | @extensionInterface.buildUI(callbacks) 430 | end 431 | 432 | end 433 | --------------------------------------------------------------------------------