Свое расширение для Burp: контекстное меню и аналог Hackvertor

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Автор petrinh1988
Источник https://xss.is


Мы уже успели поработать с разными видами сканирования, создавали точки инъекции и запускали новые процессы. В этой статье предлагаю поработать немного глобальнее. Итогом этой статьи будет расширение наподобие Hackvertor. Это одно из самых популярных расширений для BurpSuite. Оно позволяет преобразовывать строки в разные форматы при помощи XML-подобных теэгов. Выглядит это следующим образом (на примере все той же лабы):

1722459200386.png


1722459231936.png



Что произошло? Нужный кусок текста обернули в тэги <@hex_entities><@/hex_entities>, в итоге на сервер пришел запрос с закодированной инъекцией и атака не была обнаружена.

Это не весь функционал Hackvertor, есть мощный интерфейс для работы с кодированием/декодированием. Есть встроенные в интерфейс элементы, например, на скрине выше вкладка в Repeater и т.д. Но мы еще не добрались до построения интерфейсов, о них будет речь в следующих статьях, поэтому наш Hackvertor будет урезанным.

Вот, что будет уметь наше расширение по итогу:

  1. Перехватывать все исходящие запросы Burp, чтобы найти свои тэги и произвести замены
  2. Преобразовывать строки в несколько разных форматов
  3. Поддержка контекстного меню BurpSuite, для удобной работы

Перехват запросов​

Чтобы ловить все запросы Burp, потребуется уже знакомый интерфейс IHttpListener. В первой статье использовал его для создания простого логгера. IHttpListener, который предполагает всего один метод processHttpMessage(). Простое расширение, которое логгирует все запросы, будет выглядеть следующим образом:
Python: Скопировать в буфер обмена
Код:
from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks
from datetime import datetime


class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, cb):
        cb.registerHttpListener(HttpListener(cb))
        print('Logger loaded')


class HttpListener(IHttpListener):
    tools = {
            IBurpExtenderCallbacks.TOOL_COMPARER: 'COMPARER',
            IBurpExtenderCallbacks.TOOL_DECODER: 'DECODER',
            IBurpExtenderCallbacks.TOOL_EXTENDER: 'EXTENDER',
            IBurpExtenderCallbacks.TOOL_INTRUDER: 'INTRUDER',
            IBurpExtenderCallbacks.TOOL_PROXY: 'PROXY',
            IBurpExtenderCallbacks.TOOL_REPEATER: 'REPEATER',
            IBurpExtenderCallbacks.TOOL_SCANNER: 'SCANNER',
            IBurpExtenderCallbacks.TOOL_SEQUENCER: 'SEQUENCER',
            IBurpExtenderCallbacks.TOOL_SPIDER: 'SPIDER',
            IBurpExtenderCallbacks.TOOL_SUITE: 'SUITE',
            IBurpExtenderCallbacks.TOOL_TARGET: 'TARGET'
    }
 
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
     
    def processHttpMessage(self, tool, isRequest, messageInfo):
        messageTime = datetime.now()      
        direction = ''
        if isRequest:
            direction = 'Request'
            httpMessage = self._helpers.bytesToString(messageInfo.getRequest())
        else:
            direction = ' Response'
            response = messageInfo.getResponse()
            responseInfo = self._helpers.analyzeResponse(response)
            httpMessage = self._helpers.bytesToString(response)[:responseInfo.getBodyOffset()]


        print('\n[' + messageTime.ctime() + '] ' + self.tools[tool] + direction)
        print(httpMessage)

Как говориться - RTFM! Правда я бы передал на RTFMC - Read The Fucking Manual Carefully или Читай Чертову Документацию Внимательно. Во время написания статьи внимательно пересматривал документацию и нашел функцию getToolName(int toolFlag). Не нужно было городить конструкцию tools в примере выше… Но оставлю это здесь, чтобы был урок не только для меня. Продолжим.
Нажмите, чтобы раскрыть...

В отличии от прошлого логгера, здесь все немного замороченнее. Во-первых, задействованы константы IBurpExtenderCallbacks. Для информативного вывода, чтобы корректно отображать название инструмента пославшего запрос, составлен словарь. Напомню, изначально tool имеет целочисленный тип. Другая особенность в том, что обрезается ответ. Смысла выводить кучу HTML-кода нет, поэтому полученное сообщение обрезается по индексу начала тела ответа.

Для реализации задуманного, нам нужно обработать messageInfo в который попадает объект IHttpResponseRequest, кстати про этот объект…

Фундаментальные вещи​

Думаю есть смысл остановиться и поговорить о базе. На самом деле, уже давно стоило обсудить, какие базовые объекты в Burp используются и как их принято называть в справке и расширениях. Но лучше поздно, чем никогда. Возможно, это уберет некую путаницу. Вещи могут показаться излишне очевидными, но сам часто плевался из-за того, что автор не раскрыл суть некоторых понятий, считая их очевидными.

В отношении запросов и ответов, самым базовым является объект IHttpRequestResponse. Я неоднократно в статьях называл его по разному, чтобы формировать некое понимание. В сущности, это пара запрос(request) и ответ (response). Именно эту пару нам отдает Burp в сканерах, т.к. мы получаем данные об уже выполненном запросе. Получить отдельно запрос или ответ, можно при помощи getRequest() и getResponse() соответственно.

Важно! IHttpRequestResponse отработавшего запроса мы получаем в сканерах! В том же processHttpMessage интерфейса IHttpListener, при messageIsRequest в состоянии True, запрос еще не выполнен и мы можем повлиять на него через setRequest().

Отдельно запрос или ответ, в расширениях Burp, принято называть httpMessage. Получается, что у нас может быть, либо сообщение запроса либо сообщение ответа от сервера. Request и response это httpMessage, по сути своей массив байт, которые отличаются только направлением этого сообщения (или пакета). Соответственно, набор байт (или массив байт) для нас это текст составленный по определенным правилам, для машины нули и единички. Но это уже совсем основы компьютерной грамотности, поэтому их объяснение избегу)))

IHttpRequestResponse это объект, который не только содержит два набора байт, но так же комментарии к ним и цвет выделения. Управляется это методами getComment(), setComment() и getHighlight(), setHighlight(), соответственно. Кстати, сами запросы/ответы тоже можно прямо назначать через setRequest() и getRequest(). На этом моменте можно вернуться к первым статьям и посмотреть, где можно было работу с запросами перестроить.

Последней важной частью IHttpRequestResponse является объект IHttpService, который управляем соответствующими методами: getHttpService() и getHttpService(). По сути своей, объект хранит в себе информацию о хосте: имя или IP-адрес, порт и протокол.

Для удобного взаимодействия с объектами запросов и ответов, предусмотрены специальные классы IRequestInfo и IResponseInfo, соответственно. Никто не мешает написать свои обработчики и работать напрямую с массивами байт, но гораздо удобнее ввести getHeaders() и получить все заголовки удобным списком строк. Чтобы привести байты к этим объектам, в хелперах есть методы analyzeRequest() и analyzeResponse().

IRequestInfoIResponseInfo
Тип данныхНазвание методаТип данныхНазвание метода
intgetBodyOffset()intgetBodyOffset()
bytegetContentType()ICookie[]getCookies()
string[]getHeaders()string[]getHeaders()
stringgetMethod()stringgetInferredMimeType()
IParameter[]getParameters()stringgetStatedMimeType()
java.net.URLgetUrl()short(int)getStatusCode()


С большинством методов не должно возникнуть никаких вопроса, они интуитивно понятны. Единственное, могут возникнуть вопросы с методами связанными с Mime-type ответа. getStatedMimeType() возвращает, какой тип указан в ответе сервера, а getInferredMimeType() возвращает предполагаемый Mime-type на основе содержимого. В данном случае, Burp может помочь увидеть несоответствие между тем, что отдает сервер и тем, как он это представляет.

IParameter и ICookie два типа, которые хранят в себе соответствующие данные. IParameter уже разбирал в предыдущих материалах, ICookie достаточно прост:
1722459256812.png



На этом закончим с базой. В большинстве случаев, при построении запросов, дело придется иметь с HTTP-сообщением. Методы, которые позволяют удобно строить и модифицировать запросы, находятся в хелперах, которые мы получаем из коллбэков. Но к этому еще вернемся в будущем.

Модификация сообщения​

Возвращаясь к нашему расширению… Учитывая все вышесказанное, нас интересуют сугубо запросы. Если isRequest будет False, сразу выходим. В ином случае, берем getRequest(), и ищем в нем наши тэги. Причем, договоримся сразу, что должен быть и открывающий тэг и закрывающий. Если тэги найдены, на основе запроса, строим новый запрос и выполняем его через makeHttpRequest().

Меня интересует построение масштабируемого решения, чтобы можно было добавлять все новые и новые варианты преобразования строк. Поэтому, оптимально будет каждый вариант преобразования выполнить в виде объекта. Объекта содержащего открывающий тэг, закрывающий тэг, метод преобразования convert(), методы возвращающие имя name() и описание detail(), на случай если потребуется логгирование. Чтобы было по феншую, создам класс, который будет выступать родителем для остальных:
Python: Скопировать в буфер обмена
Код:
class IConvert():
    def __init__(self, tagName):
        self._tagName = tagName


    def getName(self): pass
 
    def getTag(self):
        return self._tagName
 
    def getDetail(self): pass
 
    def convert(self, payload): pass
     
    def getOpenTag(self):
        return '<@' + self.tagName + '>'
     
    def getCloseTag(self):
        return '</' + self.tagName + '>'


Раз уже работали с hex entities, создам соответствующий класс преобразования:
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert


class HexEntity(IConvert):
 
    def getName(self):
        return 'Hex entities'
 
    def getDetail(self):
        return 'Converts text to hex entities. Example: 1 => &#x31;'
 
    def convert(self, payload):
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in payload]) + ';'
        return hex_payload


Самое время оживить наше расширение, добавив в него возможность делать первые преобразования:
Python: Скопировать в буфер обмена
Код:
from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks
from HexEntity import HexEntity
import re


class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, cb):
        cb.registerHttpListener(HttpListener(cb))
        print('Logger loaded')
     
class HttpListener(IHttpListener):
    converters = [
        HexEntity('my-hex-entity')
    ]
 
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
     
    def processHttpMessage(self, tool, isRequest, messageInfo):      
        if not isRequest: return
     
        modified = False
     
        request = messageInfo.getRequest()
        requestStr = self._helpers.bytesToString(request)
        for converter in self.converters:
            tag = converter.getTag()
            pattern = '(?<=<@' + tag + '>).*[\n]*(?=<\/' + tag + '>)'
            forReplace = re.findall(pattern, requestStr)
            if len(forReplace):
                for item in forReplace:
                    payload = converter.convert(item)
                    pattern = '<@' + tag + '>.*[\n]*<\/' +  tag + '>'
                    requestStr = re.sub(pattern, payload, requestStr)
                modified = True
     
        if modified:
            messageInfo.setRequest(self._helpers.stringToBytes(requestStr))

Разберу по шагам, что происходит:

  1. Если мы имеем дело с Response, выходим. Мы уже никак не можем повлиять на запрос, он выполнен.
  2. Устанавливаем флаг modified в False, чтобы понимать, нужно ли обновлять запрос.
  3. Получаем запрос и конвертируем его в строку
  4. Циклом проходим по всем конвертерам. Это позволит нам бесконечно добавлять их. Ну как бесконечно, главное не переусердствовать.
  5. Получаем тэг и формируем паттерн. Вообще, по хорошему, стоит убрать этот процесс под капот. Например, запихать подобные процедуры в отдельный класс и от него наследовать конверторы.
  6. Регулярным выражением ищем совпадения, если они есть проходим по ним. Плюс решения в том, что конвертироваться будут только полные пары тэгов открытия и закрытия. Минус — висящие тэги уйдут в запрос.
  7. Конвертируем пэйлоад и производим замену по регулярному выражению.

Как и писал выше, если запрос еще не выполнен, в processHttpMessage() мы можем повлиять на него, т.к. объект messageInfo передается по ссылке, а не по значению. Соответственно, финальный шаг — это обновление запроса. Самое время протестировать в Repeater
1722459282193.png



Отлично! Видим, что отправили пиписку и получили пиписку. Как говориться, что посеяли, то и пожали. А значит наше расширение работает исправно. Поэтому, предлагаю убрать под капот составление регулярок и добавить еще метод конвертации для разнообразия. После перейдем к работе с меню BurpSuite.
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
import random


class RandomCase(IConvert):
    def __init__(self, tagName):
        self.tagName = tagName
 
    def getName(self):
        return 'RaNDom CaSE'
 
    def getDetail(self):
        return 'Randomize Letter Case in a String'
 
    def changeSymb(self, simb):
        if simb.isalpha():
            rnd = random.randint(0,1)
            print(rnd)
            if rnd:
                return simb.upper()
            return simb.lower()
        return simb
 
    def convert(self, payload):
        newPayload = ''
        for i in range(len(payload)):
            newPayload = newPayload + self.changeSymb(payload[i])
        return newPayload

Бесхитростное преобразование строки в строку с рандомным регистром. Остается только добавить в список конвертеров и можно использовать.

Обновленный родительский класс выглядит следующим образом:
Python: Скопировать в буфер обмена
Код:
class IConvert():
    tagName = ''


    def getName(self): pass
 
    def getTag(self):
        return self.tagName
 
    def getDetail(self): pass
 
    def convert(self, payload): pass
     
    def getOpenTag(self):
        return '<@' + self.tagName + '>'
     
    def getCloseTag(self):
        return '</' + self.tagName + '>'
 
    def getPatternFind(self):
        return '(?<=<@' + self.tagName + '>).*[\n]*(?=<\/' + self.tagName + '>)'
 
    def getPatternReplace(self):
        return '<@' + self.tagName + '>.*[\n]*<\/' +  self.tagName + '>'

Теперь достаточно запросить паттерн для поиска или паттерн для замены.

Добавляем свои пунктики в контекстное меню​

Чтобы добавить свои пункты меню, нам потребуется Несколько важных ингредиентов. Как обычно, все работает через слушатели. BurpSuite надо сообщить о том, что у нас есть свой обработчик контекстного меню, который добавит свои пункты. Обработчиком будет выступать объект на основе интерфейса IContextMenuFactory. Ну и “по классике”, чтобы сообщить об этом Burp, достаточно вызвать подходящую функцию регистрации - registerContextMenuFactory() из коллбэков экстендера.

from burp import IBurpExtender, IHttpListener, IBurpExtenderCallbacks, IIContextMenuFactory
from HexEntity import HexEntity
import re

Python: Скопировать в буфер обмена
Код:
class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, cb):
        cb.registerHttpListener(HttpListener(cb))
        cb.registerContextMenuFactory(MyContextMenu(cb))
        print('Logger loaded')
     
class MyContextMenu(IContextMenuFactory):
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()

Наш класс “MyContextMenu” должен реализовывать всего один метод createMenuItems(IContextMenuInvocation invocation). Как видим, он принимает загадочный аргумент с типом IContextMenuInvocation., который полностью нам объясняет где мы оказались и что вокруг происходит. А вернуть методо должен список объектов javax.swing.JMenuItem. Здесь стоит вспомнить, что расширения это не CPython, а Jython. Это значит, что мы можем использовать не только пакеты пайтен. Вернее их использовать мы можем не только лишь все, мало какие можем использовать. Зато можем спокойно юзать библиотеки Java. Самое время почитать подробнее про этот JMenuItem.

Да, чтиво длинное, но не будем впадать в панику. В конце-то концов, нам нужно совсем немногое оттуда. Надо понять, как добавлять надписи и привязывать события. Первое, что от нас требуется, это импортнуть пакет:

from javax.swing import JMenuItem

Импортнули, создадим наш супер-пункт меню и проверим:

Python: Скопировать в буфер обмена
Код:
class MyContextMenu(IContextMenuFactory):
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
     
    def createMenuItems(self, invocation):
        menuItem1 = JMenuItem("Converter Super Menu")
        return [menuItem1]

1722459303123.png




Батюшки мои! Пункт меню добавился. Не знаю откуда у меня такая радость, когда выполняется обычный код, но всегда кайфую когда в существующем ПО появляется что-то от меня))))

Нужно заставить меню что-то делать. Создадим обработчик события и привяжем к нашему меню:
Python: Скопировать в буфер обмена
Код:
def createMenuItems(self, invocation):
        menuItem1 = JMenuItem("Converter Super Menu", actionPerformed=self.menuClick)
        return [menuItem1]
         
    def menuClick(self, event):
        print('Our first event')
        print(event.toString())

Пока просто распечатал информацию о событии при помощи метода toString(). Для понимания процесса, в event передался объект java.util.EventObject. Среди его методов есть toString().

В расширениях убираю и снова ставлю галочку на нашем расширении, чтобы перезагрузить. После кликаю по пункту меню и смотрю вывод:

Код: Скопировать в буфер обмена
Код:
Our first event
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Converter Super Menu,when=1722440230590,modifiers=Button1] on javax.swing.JMenuItem[,1,4,180x24,invalid,alignmentX=0.0,alignmentY=0.0,border=com.formdev.flatlaf.ui.FlatMenuItemBorder@38dc8149,flags=256,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=3,left=6,bottom=3,right=6],paintBorder=true,paintFocus=false,pressedIcon=,rolloverEnabled=false,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=Converter Super Menu]

Чтоже, достаточно информативно. Мы видим подробнейшую информацию о том где и на чем произошло событие. Но информация касается исключительно самого объекта меню. Нет тех прелестей, которые нам сулил передаваемый в фабрику объект IContextMenuInvocation. Есть несколько вариантов решить эту проблему. Первый - работать с меню внутри класса. Запомнить invocation в self и обращаться к нему. Тогда можно остаться в рамках указанной функции. Выглядеть работа будет следующим образом:
Python: Скопировать в буфер обмена
Код:
class MyContextMenu(IContextMenuFactory):
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
     
    def createMenuItems(self, invocation):
        self.invocation = invocation
        menuItem1 = JMenuItem("Converter Super Menu", actionPerformed=self.menuClick)
        return [menuItem1]
         
    def menuClick(self, event):
        toolFlag = self.invocation.getToolFlag()
        toolName = self._cb.getToolName(toolFlag)
        print(toolName)

Перезагружаю расширение и в репитере кликаю по нашему контекстному меню.

1722459319182.png



Все четко работает. Но есть альтернатива, которая мне кажется более приемлемой — создать отдельный класс.

Итак, в целом, с меню разобрались. Но нам нужно связать его с нашим списком конвертеров. Для этого сделаем отдельный класс, который
Python: Скопировать в буфер обмена
Код:
from burp import IContextMenuFactory
from javax.swing import JMenuItem
from java.awt.event import ActionListener
from converter_list import converters


class MyContextMenu(IContextMenuFactory):
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
     
    def createMenuItems(self, invocation):
        self.invocation = invocation


        menuItems = []
        for converter in converters:
            menuItem = JMenuItem(converter.getName())
            menuItemClick = MenuItemListener(self._cb, invocation, converter)
            menuItem.addActionListener(menuItemClick)
            menuItems.append(menuItem)
        return menuItems


class MenuItemListener(ActionListener):
    def __init__(self, cb, invocation, converter):
        self._cb = cb
        self.invocation = invocation      
        self.converter = converter


    def actionPerformed(self, event):
        toolFlag = self.invocation.getToolFlag()
        toolName = self._cb.getToolName(toolFlag)
        print(toolName + ' ' + self.converter.getName())

Вынес меню в отдельный файл. Реализовал события, как это принято в Java. Теперь каждое событие это объект, в котором храниться вся нужная информация.

Чтобы все было красиво, нужно правильно организовать вставку данных. А именно, если пользователь просто кликнул в запросе по нашему пункту меню, мы просто вставляем тэги. Но, если пользователь выделил какой-то текст, наша задача обернуть выделенный текст в тэги. Ровно так же, как делает расширение Hackvertor. Для этого потребуется несколько методов из объекта invocation. А именно, getSelectedMessages(), который вернет IHttpRequestResponse[]. Так мы получим подробную информацию об запросе/ответе на котором кликнул пользователь. Второй метод это getSelectionBounds(), который покажет точные индексы выделения. Все, что нам останется сделать, это выполнить замену в нужных границах в нужном запросе/ответе. Звучит может и муторно, но реализуется без особых сложностей:
Python: Скопировать в буфер обмена
Код:
def actionPerformed(self, event):
        start,end = self._invocation.getSelectionBounds()
        httpMessages = self._invocation.getSelectedMessages()
        for i in range(len(httpMessages)):
            request = self._cb.getHelpers().bytesToString(httpMessages[i].getRequest())
            newRequest = request[:start] + self._converter.getOpenTag() + request[start:end] + self._converter.getCloseTag() + request[end:]
            httpMessages[i].setRequest(newRequest)

Получаем координаты начала и конца выделения, через getSelectionBounds(). На выходе массив из двух чисел, а раз знаем заране что получаем, можно воспользоваться деструктуризацией. Далее получаем выделенные сообщения и проходимся по ним циклом. Так как для тестов я использовал репитер, там будет всего одно значение, но не важно, пусть будет цикл.

Я знаю, что работать буду исключительно с Request, поэтому игнорирую респонс. Кстати, так как я забыл в конструкторе класса сохранить хелперы, использую для доступа к ним getHelpers().

Следующая строка просто пересобирает заново строковый реквест по частям. Если выделен какой-то текст, позиция start и end будут отличаться. Если нет выделения, обе позиции будут указывать на позицию курсора в запросе. В любом случае произойдет вставка. Ну и завершаю процесс через setRequest(0. Важно понимать, что в данном контексте, сообщение будет указывать на открытый в редакторе запрос.

Один важный момент​

Есть нюанс, который очень важен, но остался за кадром. При взаимодействии с процессингом в IHttpListener нужно быть внимательным. В данном примере я не стал делать фильтрацию по используемому инструменту. В нашем случае это не имеет значения. Но бывает, что могут возникнуть неприятные ситуации.

Например, вы написали расширение, которое преобразует параметр запроса search в base64. Пока ничего не происходит. Внезапные непонятные ситуации могут начаться, если вы пустите трафик браузера через Burp. IHttpListener будет делать то, что ему скажут… сказали конвертить search и base64, значит будет. На такой случай, можно сделать простой фильтр:

Python: Скопировать в буфер обмена
Код:
if tool == IBurpExtenderCallbacks.TOOL_PROXY:
            return

Итоговые файлы расширения​

Спойлер: main.py
Python: Скопировать в буфер обмена
Код:
from burp import IBurpExtender
from context_menu import MyContextMenu
from http_listeener import HttpListener

class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, cb):
        cb.registerHttpListener(HttpListener(cb))
        cb.registerContextMenuFactory(MyContextMenu(cb))
        print('Logger loaded')
Файл регистрирует коллбэки

Спойлер: http_listener.py
Python: Скопировать в буфер обмена
Код:
from burp import IHttpListener
from converter_list import converters
import re

class HttpListener(IHttpListener):
  
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
        
    def processHttpMessage(self, tool, isRequest, messageInfo):       
        if not isRequest: return
        
        modified = False
        
        request = messageInfo.getRequest()
        requestStr = self._helpers.bytesToString(request)
        for converter in converters:
            tag = converter.getTag()
            pattern = converter.getPatternFind()
            forReplace = re.findall(pattern, requestStr)
            if len(forReplace):
                for item in forReplace:
                    payload = converter.convert(item)
                    pattern = converter.getPatternReplace()
                    requestStr = re.sub(pattern, payload, requestStr)
                modified = True
        
        if modified:
            messageInfo.setRequest(self._helpers.stringToBytes(requestStr))
Файл отвечающий за IHttpListener

Спойлер: converter_list.py
Python: Скопировать в буфер обмена
Код:
from HexEntity import HexEntity
from RandomCase import RandomCase

converters = [
    HexEntity('my-hex-entity'),
    RandomCase('my-random-case')
]
Здесь хранится список конвертеров. При добавлении нового класса конвертера, сюда нужно добавить тоже. При этом, указав тэг.

Спойлер: HexEntity.py
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert

class HexEntity(IConvert):
    def __init__(self, tagName):
        self.tagName = tagName
    
    def getName(self):
        return 'Hex entities'
    
    def getDetail(self):
        return 'Converts text to hex entities. Example: 1 => &#x31;'
    
    def convert(self, payload):
        hex_payload = ';'.join([hex(ord(char)).replace('0x', '&#x') for char in payload]) + ';'
        return hex_payload

Спойлер: RandomCase.py
Python: Скопировать в буфер обмена
Код:
from IConvert import IConvert
import random

class RandomCase(IConvert):
    def __init__(self, tagName):
        self.tagName = tagName
    
    def getName(self):
        return 'RaNDom CaSE'
    
    def getDetail(self):
        return 'Randomize Letter Case in a String'
    
    def changeSymb(self, simb):
        if simb.isalpha():
            rnd = random.randint(0,1)
            print(rnd)
            if rnd:
                return simb.upper()
            return simb.lower()
        return simb
    
    def convert(self, payload):
        newPayload = ''
        for i in range(len(payload)):
            newPayload = newPayload + self.changeSymb(payload[i])
        return newPayload
Два конвертора

Спойлер: context_menu.py
Python: Скопировать в буфер обмена
Код:
from burp import IContextMenuFactory
from javax.swing import JMenuItem
from java.awt.event import ActionListener
from converter_list import converters

class MyContextMenu(IContextMenuFactory):
    def __init__(self, cb):
        self._cb = cb
        self._helpers = cb.getHelpers()
        
    def createMenuItems(self, invocation):
        self.invocation = invocation

        menuItems = []
        for converter in converters:
            menuItem = JMenuItem(converter.getName())
            menuItemClick = MenuItemListener(self._cb, invocation, converter)
            menuItem.addActionListener(menuItemClick)
            menuItems.append(menuItem)
        return menuItems

class MenuItemListener(ActionListener):
    def __init__(self, cb, invocation, converter):
        self._cb = cb
        self._invocation = invocation       
        self._converter = converter

    def actionPerformed(self, event):
        start,end = self._invocation.getSelectionBounds()
        httpMessages = self._invocation.getSelectedMessages()
        for i in range(len(httpMessages)):
            request = self._cb.getHelpers().bytesToString(httpMessages[i].getRequest())
            newRequest = request[:start] + self._converter.getOpenTag() + request[start:end] + self._converter.getCloseTag() + request[end:]
            httpMessages[i].setRequest(newRequest)
Файл отвечающий за контекстное меню

Подводим итоги​

В результате, получилось довольно неплохое расширение, которое можно спокойно масштабировать, добавляя в него все новые конверторы. Свои, кастомные, уникальные и непревзойденные. При этом, умудрились захватить фундамент по разработке расширений, которую нужно было давно выдать. Плюс, ближе разобрались с IHttpListener и реализовали еще одну его возможность (подмену запросов). Разобрали, хоть и поверхностно, базовое взаимодействие с компонентами Java. Научились делать меню, привязывать к нему события и взаимодействовать с запросами/ответами в интерфейсе Burp.

Серия ширится и растет, это уже четвертая статья из серии (тыц, тыц и тыц). Если не читали, советую. Жду ваши вопросы, пожелания и замечания, а пока пойду писать про свинг… Нет, не про тот свинг, что в интересных роликах, а про Swing в Java . То, на базе чего строятся пользовательские интерфейсы. Сегодня мы коснулись его компонента JMenuItem, но пора переходить к полноценным решениям.

Надеюсь вам понравилось!
 
Сверху Снизу