Burpsuite插件开发(网络资源学习笔记_burp插件开发)

开发语言选择

Burp suite支持三种语言的插件开发:

1、Java

2、Python

3、Ruby

综合个人的实际情况,选择了Java进行了开发(真实原因:Ruby没学、python插件调试不方便)。用Java开发的优势,这里不过多描述,毕竟Burpsuite都是Java写的,最起码Java开发插件的兼容性会好很多。

插件开发环境

JDK 环境

自己百度

编写软件

IDEA社区版

社区版对一般开发来说足够使用,还免费。为啥不用Eclipse,这个问题回答起来也很简答,因为界面丑。

C:\Users\swallow\AppData\Local\TemputoolsDoutuPlugin/tempImage1638846197333.gif

maven

简要描述:

Maven作为一个构建工具,不仅能帮我们自动化构建,还能够抽象构建过程,提供构建任务实现;它跨平台,对外提供了一致的操作接口,这一切足以使它成为优秀的、流行的构建工具。

Maven不仅是构建工具,还是一个依赖管理工具和项目管理工具,它提供了中央仓库,能帮我自动下载构件。

安装

个人学习安装版本如下:

image-20211207110533446

具体安装流程,小伙子自行百度一下。

C:\Users\swallow\AppData\Local\TemputoolsDoutuPlugin/tempImage1638846404144.gif

出现如下界面说明你配好了

image-20211207111637011

插件调试

一般项目配置过程如下:

image-20211207112620098

image-20211207112754080

上图红框内容,需要带入Burp的启动命令内。本人Burp启动bat命令如下:

java --illegal-access=permit -Dfile.encoding=utf-8 -javaagent:BurpSuiteLoader_v2021.8.jar -noverify -`jar burpsuite_pro_v2021.8.jar

开启插件调试命令如下:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 --illegal-access=permit -Dfile.encoding=utf-8 -javaagent:BurpSuiteLoader_v2021.8.jar -noverify -jar burpsuite_pro_v2021.8.jar

image-20211207124103691

上图所示即为开启了Burp的调试。使用Burp安装IDEA导出的插件jar包,在IDEA开始Debug。

image-20211207124341997

下好断点,开启Burp代理进行数据包拦截。IDEA调式模式效果如下:

image-20211207124632540

注:因为国内使用Burp破解版情况比较多,尝试调试可使用Burp社区版。

有时,想单独查看插件处理过的请求。可在Burp>Logger下进行查看。相关设置可结合实际自行选择。

image-20211207125325146

插件编写

官方文档和API

官方各种示例代码:https://portswigger.net/burp/extender

官方API文档:https://portswigger.net/burp/extender/api/

官方针对Burp插件的开发有提供三种语言的编写示例,并提供了Python、Ruby相关注意事项。简单数据包处理,官方示例缝合一下基本够用。推荐过一边官方提供的内容。

HTTP数据包处理

下图为Burp完整的一次请求,其包含的信息相信大家都有一定的了解。原则上,当前页面的信息我们都是可以获取到的。

针对相关信息的操作,可概况为“增删改查”:

请求包:

  • header 的获取、删除、新增、修改
  • body 的获取、修改
  • parameter 的获取、删除、新增、修改
  • 发送一个新的请求

响应包:

  • header 的获取、删除、新增、修改
  • body 的获取、修改

httpService:

  • host、port、protocol 的获取、修改

相关代码示例:

package burp;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;

public class BurpExtender implements IBurpExtender, IHttpListener
{//所有burp插件都必须实现IBurpExtender接口,而且实现的类必须叫做BurpExtender
	private IBurpExtenderCallbacks callbacks;
	private IExtensionHelpers helpers;

	private PrintWriter stdout;
	private PrintWriter stderr;
	private String ExtenderName = "Dome";

	@Override
	public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
	{//IBurpExtender必须实现的方法
		stdout = new PrintWriter(callbacks.getStdout(), true);
		stderr = new PrintWriter(callbacks.getStderr(), true);
		callbacks.printOutput(ExtenderName);
		this.callbacks = callbacks;
		helpers = callbacks.getHelpers();
		callbacks.setExtensionName(ExtenderName); 
		callbacks.registerHttpListener(this); 
        //如果没有注册,下面的processHttpMessage方法是不会生效的。处理请求和响应包的插件,这个应该是必要的
	}

	@Override
	public void processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo)
	{
		if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY){
			//不同的toolFlag代表了不同的burp组件 
            //https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks
			if (messageIsRequest){ //对请求包进行处理
				IRequestInfo analyzeRequest = helpers.analyzeRequest(messageInfo); 
				//对消息体进行解析,messageInfo是整个HTTP请求和响应消息体的总和,各种HTTP相关信息的获取都来自于它,HTTP流量的修改都是围绕它进行的。
				
				/*****************获取参数**********************/
				List<IParameter> paraList = analyzeRequest.getParameters();
				//获取参数的方法
				//当body是json格式的时候,这个方法也可以正常获取到键值对;但是PARAM_JSON等格式不能通过updateParameter方法来更新。
				//如果在url中的参数的值是 key=json格式的字符串 这种形式的时候,getParameters应该是无法获取到最底层的键值对的。

				for (IParameter para : paraList){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
					String key = para.getName(); //获取参数的名称
					String value = para.getValue(); //获取参数的值
					int type = para.getType();
					stdout.println("参数 key value type: "+key+" "+value+" "+type);
				}
				
				/*****************修改并更新参数**********************/
				IParameter newPara = helpers.buildParameter("testKey", "testValue", IParameter.PARAM_BODY); //构造新的参数
				byte[] new_Request = messageInfo.getRequest();
				new_Request = helpers.updateParameter(new_Request, newPara); //构造新的请求包
				messageInfo.setRequest(new_Request);//设置最终新的请求包
				
				/*****************删除参数**********************/
				for (IParameter para : paraList){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
					String key = para.getName(); //获取参数的名称
					if (key.equals("aaa")) {
						new_Request = helpers.removeParameter(new_Request, para); //构造新的请求包
					}
				}
				

				/*****************获取header**********************/
				List<String> headers = analyzeRequest.getHeaders();

				for (String header : headers){// 循环获取参数,判断类型,进行加密处理后,再构造新的参数,合并到新的请求包中。
					stdout.println("header "+header);
					if (header.startsWith("referer")) {
				/*****************删除header**********************/
						headers.remove(header);
					}
				}
				
				/*****************新增header**********************/
				headers.add("myheader:aaaa");
				
				
				/*****************获取body 方法一**********************/
				int bodyOffset = analyzeRequest.getBodyOffset();
				byte[] byte_Request = messageInfo.getRequest();
				
				String request = new String(byte_Request); //byte[] to String
				String body = request.substring(bodyOffset);
				byte[] byte_body = body.getBytes();  //String to byte[]
				
				/*****************获取body 方法二**********************/
				
				int len = byte_Request.length;
				byte[] byte_body1 = Arrays.copyOfRange(byte_Request, bodyOffset, len);
				
				new_Request = helpers.buildHttpMessage(headers, byte_body); 
				//如果修改了header或者数修改了body,不能通过updateParameter,使用这个方法。
				messageInfo.setRequest(new_Request);//设置最终新的请求包
			}
		}
		else{//处理响应包
			IResponseInfo analyzedResponse = helpers.analyzeResponse(messageInfo.getResponse()); //getResponse获得的是字节序列
			short statusCode = analyzedResponse.getStatusCode();
			List<String> headers = analyzedResponse.getHeaders();
			String resp = new String(messageInfo.getResponse());
			int bodyOffset = analyzedResponse.getBodyOffset();//响应包是没有参数的概念的,大多需要修改的内容都在body中
			String body = resp.substring(bodyOffset);

			if (statusCode==200){
				String newBody= body+"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&";
				byte[] bodybyte = newBody.getBytes();
				messageInfo.setResponse(helpers.buildHttpMessage(headers, bodybyte));
			}
		}
	}
}

Burp相关显示内容,验证代码是否执行。
image-20211207132255074

编写注意事项

  • 所有的burp插件都必须实现IBurpExtender这个接口
  • 实现类的包名称必须是burp
  • 实现类的名称必须是BurpExtender
  • 实现类比较是public的
  • 实现类必须有默认构造函数(public,无参),如果没有定义构造函数就是默认构造函数

正式编写之旅

输出显示

学习插件编写先输出一下“hello world”,这该死的仪式感。以官网的example-hello-word改编一下,带大家先了解一下Burp的几个输出位置:

package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender
{
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
    {
        // 设置插件的名称
        callbacks.setExtensionName("Hello world");
        
        // 获取burp提供的标准输出流和错误输出流
        PrintWriter stdout = new PrintWriter(callbacks.getStdout(), true);
        PrintWriter stderr = new PrintWriter(callbacks.getStderr(), true);
        
        // 打印到标准输出流
        stdout.println("Hello world 1");
        
        // 答应到错误输出流
        stderr.println("Hello world 2");
        
        // 写一个报警信息到burp的报警面板
        callbacks.issueAlert("Hello world 3");
        
        // 抛出一个异常,将会在错误输出流中显示
        throw new RuntimeException("Hello world 4");
    }
}

Burp相关显示位置:

image-20211207133053880

image-20211207133125166

image-20211207133300159

### callbacks作用

通过 callbacks 这个实例对象,传递给插件一系列burp的原生方法。

Burp事件监听

这个地方贴一下官方的示例,来说明一下:

package burp;

import java.io.PrintWriter;

public class BurpExtender implements IBurpExtender, IHttpListener, 
        IProxyListener, IScannerListener, IExtensionStateListener
{
    private IBurpExtenderCallbacks callbacks;
    private PrintWriter stdout;
    
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
    {
        this.callbacks = callbacks;
        
        // 设置插件的名称
        callbacks.setExtensionName("Event listeners");
        
        // 获取burp提供的标准输出流和错误输出流
        stdout = new PrintWriter(callbacks.getStdout(), true);
        
        // 注册HTTP listener
        callbacks.registerHttpListener(this);
        
        // 注册Proxy listener
        callbacks.registerProxyListener(this);
        
        // 注册Scanner listener
        callbacks.registerScannerListener(this);
        
        // 注册extension state listener
        callbacks.registerExtensionStateListener(this);
    }

    // 实现HTTP监听

    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
    {
        stdout.println(
                (messageIsRequest ? "HTTP request to " : "HTTP response from ") +
                messageInfo.getHttpService() +
                " [" + callbacks.getToolName(toolFlag) + "]");
    }

    // 实现Proxy监听

    @Override
    public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage message)
    {
        stdout.println(
                (messageIsRequest ? "Proxy request to " : "Proxy response from ") +
                message.getMessageInfo().getHttpService());
    }

    // 实现扫描监听

    @Override
    public void newScanIssue(IScanIssue issue)
    {
        stdout.println("New scan issue: " + issue.getIssueName());
    }

    // 实现Extender监听

    @Override
    public void extensionUnloaded()
    {
        stdout.println("Extension was unloaded");
    }
}

鼠标右键事件

通过上面的几个例子,大家应该对Burp流量监听、数据包处理、数据显示有了一定的了解。

C:\Users\swallow\AppData\Local\TemputoolsDoutuPlugin/tempImage1638861140682.gif

那这部分一起来搞点花活,创建一下简单的鼠标右键事件,示例代码如下:

package burp;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JMenuItem;

public class BurpExtender implements IBurpExtender, IContextMenuFactory
{//所有burp插件都必须实现IBurpExtender接口,而且实现的类必须叫做BurpExtender
    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;

    private PrintWriter stdout;
    private PrintWriter stderr;
    private String ExtenderName = "Burp鼠标右键事件练习";

    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
    {//IBurpExtender必须实现的方法
        stdout = new PrintWriter(callbacks.getStdout(), true);
        stderr = new PrintWriter(callbacks.getStderr(), true);
        callbacks.printOutput(ExtenderName);
        this.callbacks = callbacks;
        helpers = callbacks.getHelpers();
        callbacks.setExtensionName(ExtenderName);
        callbacks.registerContextMenuFactory(this);
    }

    @Override
    public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {

        ArrayList<JMenuItem> menu_item_list = new ArrayList<JMenuItem>();

        JMenuItem eventname = new JMenuItem("选项一");
        eventname.addActionListener(new eventname(invocation));
        menu_item_list.add(eventname);

        return menu_item_list;

    }

    public class eventname implements ActionListener{
        private IContextMenuInvocation invocation;

        public eventname(IContextMenuInvocation invocation) {
            this.invocation  = invocation;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            stdout.println("右键响应");
        }
    }
}

Burp相关位置及事件响应结果显示:

image-20211207142117530

image-20211207142154424

插件界面

我们完成了鼠标右键的事件创建。现在花活继续,来写个界面吧,官方示例代码:

package burp;

import java.awt.Component;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

public class BurpExtender extends AbstractTableModel implements IBurpExtender, ITab, IHttpListener, IMessageEditorController
{
    private IBurpExtenderCallbacks callbacks;
    private IExtensionHelpers helpers;
    private JSplitPane splitPane;
    private IMessageEditor requestViewer;
    private IMessageEditor responseViewer;
    private final List<LogEntry> log = new ArrayList<LogEntry>();
    private IHttpRequestResponse currentlyDisplayedItem;

    @Override
    public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
    {

        this.callbacks = callbacks;


        helpers = callbacks.getHelpers();


        callbacks.setExtensionName("Burp Dome");


        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                // 主要面版分割
                splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

                Table logTable = new Table(BurpExtender.this);
                JScrollPane scrollPane = new JScrollPane(logTable);
                splitPane.setLeftComponent(scrollPane);

                // 带有请求/响应的标签页
                JTabbedPane tabs = new JTabbedPane();
                requestViewer = callbacks.createMessageEditor(BurpExtender.this, false);
                responseViewer = callbacks.createMessageEditor(BurpExtender.this, false);
                tabs.addTab("Request", requestViewer.getComponent());
                tabs.addTab("Response", responseViewer.getComponent());
                splitPane.setRightComponent(tabs);

                // 定制UI组件
                callbacks.customizeUiComponent(splitPane);
                callbacks.customizeUiComponent(logTable);
                callbacks.customizeUiComponent(scrollPane);
                callbacks.customizeUiComponent(tabs);

                // 在Burp的UI中添加自定义的标签
                callbacks.addSuiteTab(BurpExtender.this);

                //注册HTTP监听
                callbacks.registerHttpListener(BurpExtender.this);
            }
        });
    }


    @Override
    public String getTabCaption()
    {
        //返回标签TAG名称
        return "Dome Tag";
    }

    @Override
    public Component getUiComponent()
    {
        return splitPane;
    }

    //HTTP监听及相关处理
    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
    {
        // only process responses
        if (!messageIsRequest)
        {
            // create a new log entry with the message details
            synchronized(log)
            {
                int row = log.size();
                log.add(new LogEntry(toolFlag, callbacks.saveBuffersToTempFiles(messageInfo),
                        helpers.analyzeRequest(messageInfo).getUrl()));
                fireTableRowsInserted(row, row);
            }
        }
    }


    @Override
    public int getRowCount()
    {
        return log.size();
    }

    @Override
    public int getColumnCount()
    {
        return 2;
    }

    @Override
    public String getColumnName(int columnIndex)
    {
        switch (columnIndex)
        {
            case 0:
                return "Tool";
            case 1:
                return "URL";
            default:
                return "";
        }
    }

    @Override
    public Class<?> getColumnClass(int columnIndex)
    {
        return String.class;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex)
    {
        LogEntry logEntry = log.get(rowIndex);

        switch (columnIndex)
        {
            case 0:
                return callbacks.getToolName(logEntry.tool);
            case 1:
                return logEntry.url.toString();
            default:
                return "";
        }
    }

    @Override
    public byte[] getRequest()
    {
        return currentlyDisplayedItem.getRequest();
    }

    @Override
    public byte[] getResponse()
    {
        return currentlyDisplayedItem.getResponse();
    }

    @Override
    public IHttpService getHttpService()
    {
        return currentlyDisplayedItem.getHttpService();
    }

    private class Table extends JTable
    {
        public Table(TableModel tableModel)
        {
            super(tableModel);
        }

        @Override
        public void changeSelection(int row, int col, boolean toggle, boolean extend)
        {
            // show the log entry for the selected row
            LogEntry logEntry = log.get(row);
            requestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
            responseViewer.setMessage(logEntry.requestResponse.getResponse(), false);
            currentlyDisplayedItem = logEntry.requestResponse;

            super.changeSelection(row, col, toggle, extend);
        }
    }

    private static class LogEntry
    {
        final int tool;
        final IHttpRequestResponsePersisted requestResponse;
        final URL url;

        LogEntry(int tool, IHttpRequestResponsePersisted requestResponse, URL url)
        {
            this.tool = tool;
            this.requestResponse = requestResponse;
            this.url = url;
        }
    }
}

Burp相关展示如下:

image-20211207145033644

大多数情况下,依靠官网的示例进行修改可满足需求。如果追求”代码写的好不好,界面一定要漂亮“的原则,那我建议你换一种语言,用Java写界面想想就头冷。

image-20211207151101671

写一个自己的插件

最近正在写一个集成XSStrike的插件,需要将上面Java代码转换为python代码。或者将XSStrike用Java重写,两个都比较费劲。现在正在一种方式中探索,最无法避免的就是两种语言的数据转换问题。

C:\Users\swallow\AppData\Local\TemputoolsDoutuPlugin/tempImage1638861528731.gif

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值