0x00 前言
上一篇文章主要是介绍burp插件的基本情况,对插件编写有一个入门的了解,本篇将以调试分析的角度,记录一下学习方法和编写流程。
0x01 debug 前期文件准备
在编写插件代码的时候,若不进行一步步的调试的话,只能是凭空造轮子,耗时耗力。
因为我们大部分测试者都是使用的破解版,所以在配置调试的时候因为某些原因是不太适用的,故需要下载一个社会版。这里我已经1.7.26版本,2.0的版本的文件太大了。
下载地址:https://portswigger.net/burp/releases/professional-community-1-7-26
网盘版地址:链接: https://pan.baidu.com/s/10rwx1eWdNCEQWqw4KpqtEw 提取码: xtnt
正因为我们使用的社区版,所以在挑选现成的burp插件的时候,需要避开那些要使用到专业版或者企业版的API的插件。
这里我以 request-timer 插架为dome开始进行分析,该插件的功能介绍如下:
Burp Request Timer
此扩展捕获所有Burp工具发出的请求的响应时间。在发现潜在的定时攻击中可能很有用。
可以通过工具(代理,扫描仪,入侵者中继器),目标作用域以及与URL中的字符串进行匹配来过滤结果。
需要Java 8。
选用该插件作为分析的原因还有一个就是,几个月前,看到 ch1st 写了一个插件,就是基于被动或者主动识别存在java各种反序列化特征的插件,而现在这个大体上都差不多。
0x02 debug 操作流程
下载好上面的那个插件源码后,因为github上没有直接附上成品和相应API接口文件,所以需要我们自己导入到相应文件中。
整理好后,直接打包成jar文件即可。
1、首先我们使用以下命令打开burp社区版
1 java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8888 -jar burpsuite_free_v1.7 .26 .jar
该命令启动的burp,是便于我们进行远程调试的,即开启了远程调试端口。
2、我们导入刚才编译好的jar插件。
3、这个插件的页面是这个样子的。
4、我们开始设置eclipse中的远程调试配置。
4.1、运行→调试配置
4.2、选择 远程Java应用程序→鼠标右键→新建
4.3、名称可以随便设置,主要需要设置好端口号和启动burp的端口号一致,剩下的点击应用和调试即可。
4.3、选择IDE右上角的调试界面按钮,我们就能看到整个运行过程。
4.4、我们先下个断点,看看效果吧。
4.5、我们在99、100、101这三行加了断点。
4.6、在burp界面一顿操作啊操作(让插件进行相应功能),然后我们就可以看到调试界面显示的参数了。
我们可以看到 toolFlag 这个接受到的参数是 4,具体这个参数4是什么意思,需要去翻看API手册中即可。
注: 需要注意的是,编译的 jar 中的代码必须和我们进行调试的代码一模一样,这样在远程调试的时候,不会出错。
0x03 Burp Request Timer 源码分析
先写一个好的插件,就需要参考前辈们的代码,既减少了重复造轮子,也学习了前辈们编写的思路。
首先我们看一下这个源码函数的大致UML图:
源码整体函数可以分为三个部分,分别是:
插件主体逻辑部分:BurpExtender
UI界面代码部分:MainPanel
功能数据存放部分:LogTablemodel、LogTable、Log
由于源码继承问题,我从下往上进行分析(功能数据存放部分→UI界面代码部分→插件主体逻辑部分)。
0x04 功能数据存放部分 Log
源码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package burp;import java.net.URL;import java.time.LocalDateTime;class Log { final LocalDateTime timestamp; final String tool; final IHttpRequestResponsePersisted requestResponse; final URL url; final long time; final short status; final String mimeType; Log(LocalDateTime timestamp, String tool, IHttpRequestResponsePersisted requestResponse, URL url, short status, String mimeType, long time) { this .timestamp = timestamp; this .tool = tool; this .requestResponse = requestResponse; this .url = url; this .time = time; this .status = status; this .mimeType = mimeType; } }
整体类函数功能:
该代码部分主要是把相应字段信息以常量参数的形式进行储存,便于数据的调用和展示,而Log类主要被 LogTableModel类使用。
0x05 功能数据存放部分 LogTableModel
源码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 package burp;import java.util.ArrayList; import java.util.List; import javax.swing.table.AbstractTableModel; public class LogTableModel extends AbstractTableModel { private final java.util.List<Log> logArray = new ArrayList <>(); @Override public int getRowCount () { return logArray.size(); } @Override public int getColumnCount () { return 6 ; } @Override public String getColumnName (int columnIndex) { switch (columnIndex) { case 0 : return "Timestamp" ; case 1 : return "Tool" ; case 2 : return "Request URL" ; case 3 : return "MIME Type" ; case 4 : return "Response Time (ms)" ; case 5 : return "HTTP Status" ; default : return "" ; } } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case 0 : return String.class; case 1 : return String.class; case 2 : return String.class; case 3 : return String.class; case 4 : return Long.class; case 5 : return Short.class; default : return Object.class; } } @Override public Object getValueAt (int rowIndex, int columnIndex) { Log logEntry = logArray.get(rowIndex); switch (columnIndex) { case 0 : return logEntry.timestamp; case 1 : return logEntry.tool; case 2 : return logEntry.url.toString(); case 3 : return logEntry.mimeType; case 4 : return logEntry.time; case 5 : return logEntry.status; default : return "" ; } } List<Log> getLogArray () { return logArray; } }
整体类函数功能:
该类的构造函数是生成了一个 ArrayList() 的列表,而列表的类型为 Log,然后分别创建或重新了以下几个函数:
getRowCount():获取列表内元素的个数,该函数是抽象类 AbstractTableModel 强制要重写的函数。
getColumnCount():返回字段的列数,由于我们前期就设置了6个固定字段(Timestamp、Tool、Request URL、MIME Type、Response Time (ms)、HTTP Status),该字段值数是与UI页面显示字段数相一致的,该函数是抽象类 AbstractTableModel 强制要重写的函数。。
getColumnName(int columnIndex):该函数主要是根据固定值返回对应字段的名称。
getColumnClass(int columnIndex):该函数主要是根据固定值返回对应字段的对象类型。
getValueAt(int rowIndex, int columnIndex):该函数主要是根据固定的值返回对应字段下的数据内容。
getLogArray():返回当前类的List对象。
0x06 功能数据存放部分 LogTable
源码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package burp;import javax.swing.JTable; public class LogTable extends JTable implements IMessageEditorController { private LogTableModel logTableModel; private IMessageEditor requestViewer; private IMessageEditor responseViewer; private IHttpRequestResponse currentlyDisplayedItem; LogTable(LogTableModel logTableModel) { super (logTableModel); this .logTableModel = logTableModel; this .requestViewer = BurpExtender.callbacks.createMessageEditor(this , false ); this .responseViewer = BurpExtender.callbacks.createMessageEditor(this , false ); setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); getColumnModel().getColumn(0 ).setMinWidth(200 ); getColumnModel().getColumn(1 ).setMinWidth(100 ); getColumnModel().getColumn(2 ).setPreferredWidth(1000 ); getColumnModel().getColumn(3 ).setMinWidth(100 ); getColumnModel().getColumn(4 ).setMinWidth(150 ); getColumnModel().getColumn(5 ).setMinWidth(100 ); setAutoCreateRowSorter(true ); } @Override public byte [] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte [] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public IHttpService getHttpService () { return currentlyDisplayedItem.getHttpService(); } @Override public void changeSelection (int row, int col, boolean toggle, boolean extend) { Log logEntry = logTableModel.getLogArray().get(convertRowIndexToModel(row)); requestViewer.setMessage(logEntry.requestResponse.getRequest(), true ); responseViewer.setMessage(logEntry.requestResponse.getResponse(), false ); currentlyDisplayedItem = logEntry.requestResponse; super .changeSelection(row, col, toggle, extend); } IMessageEditor getRequestViewer () { return requestViewer; } IMessageEditor getResponseViewer () { return responseViewer; } }
整体类函数功能:
该类函数主要功能可以分为两个部分,一个部分为自适应调整UI界面的字段位置,另一个就是展示HTTP数据的请求和相应的具体数据,而该部分的作用主要是便于在UI界面代码部分进行直接调用显示。
0x07 UI界面代码部分 MainPanel
源码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 package burp;import java.awt.Color; import java.awt.Component; import java.awt.FlowLayout; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; public class MainPanel extends JPanel implements ITab { private LogTableModel logTableModel; private JTextField urlFilterText; private JCheckBox scopeCheckBox; MainPanel(BurpExtender extender) { setLayout(new BoxLayout (this , BoxLayout.Y_AXIS)); JSplitPane splitPane = new JSplitPane (JSplitPane.VERTICAL_SPLIT); logTableModel = new LogTableModel (); LogTable logTable = new LogTable (logTableModel); JScrollPane scrollPane = new JScrollPane (logTable); splitPane.setLeftComponent(scrollPane); JTabbedPane tabs = new JTabbedPane (); tabs.setBorder(BorderFactory.createLineBorder(Color.black)); tabs.addTab("Request" , logTable.getRequestViewer().getComponent()); tabs.addTab("Response" , logTable.getResponseViewer().getComponent()); splitPane.setRightComponent(tabs); JPanel controlPanel = new JPanel (new FlowLayout (FlowLayout.LEFT)); JLabel toolLabel = new JLabel ("Select tool: " ); controlPanel.add(toolLabel); String[] tools = {"All" , "Proxy" , "Intruder" , "Scanner" , "Repeater" }; JComboBox<String> toolList = new JComboBox <>(tools); toolList.addActionListener(e -> { String tool = (String) ((JComboBox) e.getSource()).getSelectedItem(); if (tool != null ) { switch (tool) { case "All" : extender.setToolFilter(0 ); break ; case "Proxy" : extender.setToolFilter(IBurpExtenderCallbacks.TOOL_PROXY); break ; case "Intruder" : extender.setToolFilter(IBurpExtenderCallbacks.TOOL_INTRUDER); break ; case "Scanner" : extender.setToolFilter(IBurpExtenderCallbacks.TOOL_SCANNER); break ; case "Repeater" : extender.setToolFilter(IBurpExtenderCallbacks.TOOL_REPEATER); break ; default : throw new RuntimeException ("Unknown tool: " + tool); } } }); controlPanel.add(toolList); JButton startButton = new JButton ("Start" ); controlPanel.add(startButton); JButton stopButton = new JButton ("Stop" ); controlPanel.add(stopButton); JButton clearButton = new JButton ("Clear" ); stopButton.setEnabled(false ); controlPanel.add(clearButton); JLabel scopeLabel = new JLabel ("In-scope items only?" ); controlPanel.add(scopeLabel); scopeCheckBox = new JCheckBox (); controlPanel.add(scopeCheckBox); JLabel filterLabel = new JLabel ("Filter URL:" ); controlPanel.add(filterLabel); urlFilterText = new JTextField (40 ); controlPanel.add(urlFilterText); startButton.addActionListener(e -> { extender.setRunning(true ); startButton.setEnabled(false ); stopButton.setEnabled(true ); }); stopButton.setEnabled(false ); stopButton.addActionListener(e -> { extender.setRunning(false ); startButton.setEnabled(true ); stopButton.setEnabled(false ); }); clearButton.addActionListener(e -> { extender.getReqResMap().clear(); logTableModel.getLogArray().clear(); logTableModel.fireTableDataChanged(); }); controlPanel.setAlignmentX(0 ); add(controlPanel); add(splitPane); BurpExtender.callbacks.customizeUiComponent(this ); } @Override public String getTabCaption () { return "Request Timer" ; } @Override public Component getUiComponent () { return this ; } LogTableModel getLogTableModel () { return logTableModel; } String getURLFilterText () { return urlFilterText.getText(); } boolean isScopeSelected () { return scopeCheckBox.isSelected(); } }
整体类函数功能:
该部分代码不解读,我感觉应该是使用了类似WindowBuilder的工具进行设置的UI界面,部分代码意思可以详见以上代码注释部分。
0x08 插件主体逻辑部分 BurpExtender
源码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 package burp;import java.io.PrintWriter; import java.net.URL; import java.time.LocalDateTime; import java.util.HashMap; public class BurpExtender implements IBurpExtender , IHttpListener { static IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private MainPanel panel; private HashMap<URL, Long> reqResMap = new HashMap <>(); private boolean isRunning = false ; private int toolFilter = 0 ; @Override public void registerExtenderCallbacks (final IBurpExtenderCallbacks callbacks) { BurpExtender.callbacks = callbacks; helpers = callbacks.getHelpers(); callbacks.setExtensionName("Request Timer" ); panel = new MainPanel (this ); callbacks.addSuiteTab(panel); callbacks.registerHttpListener(this ); } void setRunning (boolean running) { this .isRunning = running; } void setToolFilter (int toolFilter) { this .toolFilter = toolFilter; } @Override public void processHttpMessage (int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { if (isRunning) { if (toolFilter == 0 || toolFilter == toolFlag) { URL url = helpers.analyzeRequest(messageInfo).getUrl(); if (messageIsRequest) { reqResMap.put(url, System.currentTimeMillis()); } else { if (reqResMap.containsKey(url)) { long time = System.currentTimeMillis() - reqResMap.get(url); reqResMap.remove(url); synchronized (panel.getLogTableModel().getLogArray()) { int row = panel.getLogTableModel().getLogArray().size(); if (panel.getURLFilterText().isEmpty() && !panel.isScopeSelected()) { addLog(messageInfo, toolFlag, time, row); } else if (!panel.isScopeSelected() && !panel.getURLFilterText().isEmpty() && helpers.analyzeRequest(messageInfo).getUrl().toExternalForm().contains(panel.getURLFilterText())) { addLog(messageInfo, toolFlag, time, row); } else if (panel.isScopeSelected() && panel.getURLFilterText().isEmpty() && callbacks.isInScope(helpers.analyzeRequest(messageInfo).getUrl())) { addLog(messageInfo, toolFlag, time, row); } else if (panel.isScopeSelected() && !panel.getURLFilterText().isEmpty() && callbacks.isInScope(helpers.analyzeRequest(messageInfo).getUrl()) && helpers.analyzeRequest(messageInfo).getUrl().toExternalForm().contains(panel.getURLFilterText())) { addLog(messageInfo, toolFlag, time, row); } } } } } } } private void addLog (IHttpRequestResponse messageInfo, int toolFlag, long time, int row) { panel.getLogTableModel().getLogArray().add(new Log (LocalDateTime.now(), callbacks.getToolName(toolFlag), callbacks.saveBuffersToTempFiles(messageInfo), helpers.analyzeRequest(messageInfo).getUrl(), helpers.analyzeResponse(messageInfo.getResponse()).getStatusCode(), helpers.analyzeResponse(messageInfo.getResponse()).getStatedMimeType(), time)); panel.getLogTableModel().fireTableRowsInserted(row, row); } HashMap<URL, Long> getReqResMap () { return reqResMap; } }
整体类函数功能:
整体界面主要函数,和上一个入门篇文章中记录的编写思路是一样的,而 processHttpMessage() 的功能,主要是计算请求的URL所花费的时间,和把请求的数据进行一个储存,并且保持不会出现重复记录的情况。
0x09 总结
总结起来Burp插件的思路,其实就是对API接口的调用,然后进行一系列数据规则的处理,通过上面源码分析可以更便于我们自行进行造轮子和在别人轮子的基础上进行优化调整。
如果对于某些轮子中的函数和参数不太理解的话, 可以只用用文章一开始debug的方式,进行更加深入的了解某些参数和函数的作用,还有整个插件程序的执行顺序。