diff --git a/README.md b/README.md index ab5322f..616f2b2 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,14 @@ ## 项目介绍 -**HaE**是一个基于`BurpSuite Java插件API`开发的辅助型框架式插件,旨在实现对HTTP消息的高亮标记和信息提取。该插件通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。 +**HaE**是一款网络安全(数据安全)领域下的辅助型框架式项目,旨在实现对HTTP消息(包含WebSocket)的高亮标记和信息提取。本项目通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。 -随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。 +> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。 -**注**: 要想灵活的使用`HaE`,你需要掌握正则表达式阅读、编写、修改能力;由于`Java`正则表达式的库并没有`Python`的优雅或方便,所以HaE要求使用者必须用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)`。 +**注意事项**: + +1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。 +2. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。 ## 使用方法 @@ -54,9 +57,10 @@ HaE目前的规则一共有8个字段,分别是规则名称、规则正则、 | 界面名称 | 界面展示 | | ------------------------ | ---------------------------------------------------- | -| Rules(规则信息管理) | | -| Config(配置信息管理) | | -| Databoard(数据集合面板) | | +| Rules(规则管理) | | +| Config(配置管理) | | +| Databoard(数据集合) | | +| MarkInfo(数据展示) | | ## 文末随笔 diff --git a/build.gradle b/build.gradle index 577c8cf..5b7df41 100644 --- a/build.gradle +++ b/build.gradle @@ -1,33 +1,37 @@ -plugins { - id 'java' -} - -repositories { - mavenCentral() -} - -compileJava { - options.encoding = "UTF-8" -} - -sourceSets { - main { - java { - srcDir './src/main/java' - } - } -} - -task fatJar(type: Jar) { - baseName = project.name + '-all' - from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } - with jar -} - -dependencies { - compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13' - compile 'org.jetbrains:annotations:16.0.2' - compile group: 'org.yaml', name: 'snakeyaml', version: '1.28' - compile 'net.sourceforge.jregex:jregex:1.2_01' - compile 'dk.brics.automaton:automaton:1.11-8' -} +plugins { + id 'java' +} + +sourceCompatibility = 17 +targetCompatibility = 17 + +repositories { + mavenCentral() +} + +sourceSets { + main { + java { + srcDir './src/main/java' + } + } +} + +dependencies { + implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1' + implementation 'org.yaml:snakeyaml:2.0' + implementation 'net.sourceforge.jregex:jregex:1.2_01' + implementation 'dk.brics.automaton:automaton:1.11-8' +} + +test { + useJUnitPlatform() +} + +jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} \ No newline at end of file diff --git a/images/config.png b/images/config.png index 4d3f2a7..765060e 100644 Binary files a/images/config.png and b/images/config.png differ diff --git a/images/databoard.png b/images/databoard.png index 9edf373..316e466 100644 Binary files a/images/databoard.png and b/images/databoard.png differ diff --git a/images/markinfo.png b/images/markinfo.png new file mode 100644 index 0000000..da0302f Binary files /dev/null and b/images/markinfo.png differ diff --git a/images/rgperson.jpg b/images/rgperson.jpg deleted file mode 100644 index bb4170d..0000000 Binary files a/images/rgperson.jpg and /dev/null differ diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java deleted file mode 100644 index 5c0b4a2..0000000 --- a/src/main/java/burp/BurpExtender.java +++ /dev/null @@ -1,245 +0,0 @@ -package burp; - -import burp.config.ConfigLoader; -import burp.core.processor.ColorProcessor; -import burp.core.processor.MessageProcessor; -import burp.core.utils.StringHelper; -import burp.ui.MainUI; -import burp.ui.board.DatatablePanel; -import burp.ui.board.MessagePanel; -import java.util.*; -import javax.swing.*; -import java.awt.*; -import java.io.PrintWriter; -import java.util.List; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -/** - * @author EvilChen & 0chencc - */ - -public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab { - private MainUI main; - public static PrintWriter stdout; - public static IBurpExtenderCallbacks callbacks; - public static IExtensionHelpers helpers; - ColorProcessor colorProcessor = new ColorProcessor(); - MessageProcessor messageProcessor = new MessageProcessor(); - private MessagePanel messagePanel; - - @Override - public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) - { - BurpExtender.callbacks = callbacks; - BurpExtender.helpers = callbacks.getHelpers(); - - new ConfigLoader(); - - String version = "2.6.1"; - callbacks.setExtensionName(String.format("HaE (%s) - Highlighter and Extractor", version)); - - // 定义输出 - stdout = new PrintWriter(callbacks.getStdout(), true); - stdout.println("[ HACK THE WORLD - TO DO IT ]"); - stdout.println("[#] Author: EvilChen & 0chencc"); - stdout.println("[#] Github: https://github.com/gh0stkey/HaE"); - - // UI - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - initialize(); - } - }); - - callbacks.registerHttpListener(BurpExtender.this); - callbacks.registerMessageEditorTabFactory(BurpExtender.this); - - } - - private void initialize() { - messagePanel = new MessagePanel(callbacks, helpers); - main = new MainUI(messagePanel); - callbacks.customizeUiComponent(main); - callbacks.addSuiteTab(BurpExtender.this); - } - - @Override - public String getTabCaption() { - return "HaE"; - } - - @Override - public Component getUiComponent() { - return main; - } - - /** - * 使用processHttpMessage用来做Highlighter - */ - @Override - public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { - // 判断是否是响应,且该代码作用域为:REPEATER、INTRUDER、PROXY(分别对应toolFlag 64、32、4) - if (toolFlag == 64 || toolFlag == 32 || toolFlag == 4) { - if (!messageIsRequest) { - IHttpService iHttpService = messageInfo.getHttpService(); - String host = iHttpService.getHost(); - - List> result = null; - - String originalColor = messageInfo.getHighlight(); - String originalComment = messageInfo.getComment(); - - try { - result = messageProcessor.processMessage(helpers, messageInfo, host, true); - - if (result != null && !result.isEmpty() && result.size() > 0) { - List colorList = new ArrayList<>(); - - if (originalColor != null) { - colorList.add(originalColor); - } - - colorList.add(result.get(0).get("color")); - String resColor = colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList)); - messageInfo.setHighlight(resColor); - - String addComment = String.join(", ", result.get(1).get("comment")); - String allComment = !Objects.equals(originalComment, "") ? String.format("%s, %s", originalComment, addComment) : addComment; - String resComment = StringHelper.mergeComment(allComment); - messageInfo.setComment(resComment); - - messagePanel.add(messageInfo, resComment, resColor); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - } - } - - class MarkInfoTab implements IMessageEditorTab { - private final JTabbedPane jTabbedPane = new JTabbedPane(); - private DatatablePanel dataPanel; - private JTable dataTable; - private final IMessageEditorController controller; - private Map extractRequestMap; - private Map extractResponseMap; - private ArrayList titleList = new ArrayList<>(); - private byte[] message; - - public MarkInfoTab(IMessageEditorController controller, boolean editable) { - this.controller = controller; - } - - @Override - public String getTabCaption() { - return "MarkInfo"; - } - - @Override - public Component getUiComponent() { - jTabbedPane.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent arg0) { - dataTable = ((DatatablePanel)jTabbedPane.getSelectedComponent()).getTable(); - } - }); - return jTabbedPane; - } - - @Override - public boolean isEnabled(byte[] content, boolean isRequest) { - this.message = content; - List> result = null; - if (content.length != 0 && !helpers.bytesToString(content).equals("Loading...")) { - try { - if (isRequest) { - result = messageProcessor.processRequestMessage(helpers, content, "", false); - } else { - result = messageProcessor.processResponseMessage(helpers, content, "", false); - } - } catch (Exception e) { - e.printStackTrace(); - } - - if (result != null && !result.isEmpty()) { - Map dataMap = result.get(0); - if (isRequest) { - extractRequestMap = dataMap; - } else { - extractResponseMap = dataMap; - } - return true; - } - } - return false; - } - - @Override - public byte[] getMessage() { - return message; - } - - @Override - public boolean isModified() { - return false; - } - - /** - * 快捷键复制功能 - */ - @Override - public byte[] getSelectedData() { - return helpers.stringToBytes(dataPanel.getSelectedData(dataTable)); - } - - /** - * 使用setMessage用来做Extractor - */ - @Override - public void setMessage(byte[] content, boolean isRequest) { - if (content.length > 0) { - if (isRequest) { - makeTable(extractRequestMap); - } else { - makeTable(extractResponseMap); - } - } - } - - /** - * 创建MarkInfo表单 - */ - public void makeTable(Map dataMap) { - ArrayList lTitleList = new ArrayList<>(); - - dataMap.keySet().forEach(i->{ - String[] extractData = dataMap.get(i).split("\n"); - lTitleList.add(i); - dataPanel = new DatatablePanel(i, Arrays.asList(extractData)); - jTabbedPane.addTab(i, dataPanel); - }); - - /* - * 使用removeAll会导致MarkInfo UI出现空白的情况,为了改善用户侧体验,采用remove的方式进行删除; - * 采用全局ArrayList的方式遍历删除Tab,以此应对BurpSuite缓存机制导致的MarkInfo UI错误展示。 - */ - titleList.forEach(t->{ - int indexOfTab = jTabbedPane.indexOfTab(t); - if (indexOfTab != -1) { - jTabbedPane.removeTabAt(indexOfTab); - } - }); - - titleList = lTitleList; - } - } - - @Override - public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) { - return new MarkInfoTab(controller, editable); - } -} diff --git a/src/main/java/burp/config/ConfigEntry.java b/src/main/java/burp/config/ConfigEntry.java deleted file mode 100644 index c63cb85..0000000 --- a/src/main/java/burp/config/ConfigEntry.java +++ /dev/null @@ -1,43 +0,0 @@ -package burp.config; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class ConfigEntry { - public static String excludeSuffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip"; - - public static String[] scopeArray = new String[] { - "any", - "any header", - "any body", - "response", - "response header", - "response body", - "request", - "request header", - "request body" - }; - - public static String[] engineArray = new String[] { - "nfa", - "dfa" - }; - - public static String[] colorArray = new String[] { - "red", - "orange", - "yellow", - "green", - "cyan", - "blue", - "pink", - "magenta", - "gray" - }; - - public static Map globalRules = null; - - public static ConcurrentHashMap>> globalDataMap = new ConcurrentHashMap<>(); -} \ No newline at end of file diff --git a/src/main/java/burp/config/ConfigLoader.java b/src/main/java/burp/config/ConfigLoader.java deleted file mode 100644 index 95f96af..0000000 --- a/src/main/java/burp/config/ConfigLoader.java +++ /dev/null @@ -1,153 +0,0 @@ -package burp.config; - -import burp.BurpExtender; -import burp.rule.utils.RuleTool; -import burp.rule.utils.YamlTool; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.ArrayList; -import org.yaml.snakeyaml.Yaml; - -/** - * @author EvilChen - */ - -public class ConfigLoader { - private static final Yaml yaml = YamlTool.newStandardYaml(); - private static final String HaEConfigPath = determineConfigPath(); - private static final String RulesFilePath = String.format("%s/%s", HaEConfigPath, "Rules.yml"); - private static final String ConfigFilePath = String.format("%s/%s", HaEConfigPath, "Config.yml"); - - public ConfigLoader() { - // 构造函数,初始化配置 - File HaEConfigPathFile = new File(HaEConfigPath); - if (!(HaEConfigPathFile.exists() && HaEConfigPathFile.isDirectory())) { - HaEConfigPathFile.mkdirs(); - } - - File configFilePath = new File(ConfigFilePath); - if (!(configFilePath.exists() && configFilePath.isFile())) { - initConfig(); - } - - File rulesFilePath = new File(RulesFilePath); - if (!(rulesFilePath.exists() && rulesFilePath.isFile())) { - initRules(); - } - - ConfigEntry.globalRules = getRules(); - } - - private static String determineConfigPath() { - // 优先级1:用户根目录 - String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home")); - if (isValidConfigPath(userConfigPath)) { - return userConfigPath; - } - - // 优先级2:Jar包所在目录 - String jarPath = BurpExtender.callbacks.getExtensionFilename(); - String jarDirectory = new File(jarPath).getParent(); - String jarConfigPath = String.format("%s/.config/HaE", jarDirectory); - if (isValidConfigPath(jarConfigPath)) { - return jarConfigPath; - } - - return userConfigPath; - } - - private static boolean isValidConfigPath(String configPath) { - File configPathFile = new File(configPath); - return configPathFile.exists() && configPathFile.isDirectory(); - } - - public static void initConfig() { - Map r = new LinkedHashMap<>(); - r.put("excludeSuffix", getExcludeSuffix()); - try { - Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(ConfigFilePath)), StandardCharsets.UTF_8); - yaml.dump(r, ws); - ws.close(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - public static void initRules() { - RuleTool rt = new RuleTool(RulesFilePath); - rt.getRulesFromSite(); - } - - public static String getRulesFilePath() { - return RulesFilePath; - } - - public static String getExcludeSuffix(){ - String excludeSuffix = ""; - File yamlSetting = new File(ConfigFilePath); - - if (yamlSetting.exists() && yamlSetting.isFile()) { - try { - InputStream inorder = Files.newInputStream(Paths.get(ConfigFilePath)); - Map r = yaml.load(inorder); - excludeSuffix = r.get("excludeSuffix").toString(); - } catch (Exception e) { - // e.printStackTrace(); - excludeSuffix = ConfigEntry.excludeSuffix; - } - } else { - excludeSuffix = ConfigEntry.excludeSuffix; - } - - return excludeSuffix; - } - - // 获取规则配置 - public static Map getRules() { - Map rulesMap = YamlTool.loadYaml(getRulesFilePath()); - Map resRule = new HashMap<>(); - String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"}; - - Object rulesObj = rulesMap.get("rules"); - if (rulesObj instanceof List) { - List> groupData = (List>) rulesObj; - for (Map groupFields : groupData) { - ArrayList data = new ArrayList<>(); - - Object ruleObj = groupFields.get("rule"); - if (ruleObj instanceof List) { - List> ruleData = (List>) ruleObj; - for (Map ruleFields : ruleData) { - Object[] valuesArray = new Object[fieldKeys.length]; - for (int i = 0; i < fieldKeys.length; i++) { - valuesArray[i] = ruleFields.get(fieldKeys[i]); - } - data.add(valuesArray); - } - } - - Object[][] dataArray = data.toArray(new Object[data.size()][]); - resRule.put(groupFields.get("group").toString(), dataArray); - } - } - return resRule; - } - - public static void setExcludeSuffix(String excludeSuffix){ - Map r = new LinkedHashMap<>(); - r.put("excludeSuffix", excludeSuffix); - try{ - Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(ConfigFilePath)), StandardCharsets.UTF_8); - yaml.dump(r, ws); - ws.close(); - }catch (Exception ex){ - ex.printStackTrace(); - } - } -} diff --git a/src/main/java/burp/core/processor/ColorProcessor.java b/src/main/java/burp/core/processor/ColorProcessor.java deleted file mode 100644 index 8ee6a14..0000000 --- a/src/main/java/burp/core/processor/ColorProcessor.java +++ /dev/null @@ -1,68 +0,0 @@ -package burp.core.processor; - -import burp.config.ConfigEntry; - -import java.util.*; - -/** - * @author EvilChen - */ - -public class ColorProcessor { - private String finalColor = ""; - - public List retrieveColorIndices(List colors){ - List indices = new ArrayList<>(); - String[] colorArray = ConfigEntry.colorArray; - int size = colorArray.length; - - for (String color : colors) { - for (int i = 0; i < size; i++) { - if (colorArray[i].equals(color)) { - indices.add(i); - } - } - } - return indices; - } - - /** - * 颜色升级递归算法 - */ - private void upgradeColors(List colorList) { - int colorSize = colorList.size(); - String[] colorArray = ConfigEntry.colorArray; - colorList.sort(Comparator.comparingInt(Integer::intValue)); - int i = 0; - List stack = new ArrayList<>(); - while (i < colorSize) { - if (stack.isEmpty()) { - stack.add(colorList.get(i)); - } else { - if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) { - stack.add(colorList.get(i)); - } else { - stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1); - } - } - i++; - } - // 利用HashSet删除重复元素 - HashSet tmpList = new HashSet(stack); - if (stack.size() == tmpList.size()) { - stack.sort(Comparator.comparingInt(Integer::intValue)); - if(stack.get(0) < 0) { - this.finalColor = colorArray[0]; - } else { - this.finalColor = colorArray[stack.get(0)]; - } - } else { - this.upgradeColors(stack); - } - } - - public String retrieveFinalColor(List colorList) { - upgradeColors(colorList); - return finalColor; - } -} diff --git a/src/main/java/burp/core/processor/MessageProcessor.java b/src/main/java/burp/core/processor/MessageProcessor.java deleted file mode 100644 index 57d00d5..0000000 --- a/src/main/java/burp/core/processor/MessageProcessor.java +++ /dev/null @@ -1,138 +0,0 @@ -package burp.core.processor; - -import burp.BurpExtender; -import burp.IExtensionHelpers; -import burp.IHttpRequestResponse; -import burp.IRequestInfo; -import burp.IResponseInfo; -import burp.core.utils.MatchTool; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class MessageProcessor { - private MatchTool matcher = new MatchTool(); - private DataProcessingUnit dataProcessingUnit = new DataProcessingUnit(); - private ColorProcessor colorProcessor = new ColorProcessor(); - - public List> processMessage(IExtensionHelpers helpers, IHttpRequestResponse messageInfo, String host, boolean actionFlag) throws Exception { - - byte[] requestByte = messageInfo.getRequest(); - byte[] responseByte = messageInfo.getResponse(); - - List> reqObj = processRequestMessage(helpers, requestByte, host, actionFlag); - List> resObj = processResponseMessage(helpers, responseByte, host, actionFlag); - List> mergedList = new ArrayList<>(); - - if (reqObj != null && !reqObj.isEmpty()) { - if (resObj != null && !resObj.isEmpty()) { - List colorList = new ArrayList<>(); - - colorList.add(reqObj.get(0).get("color")); - colorList.add(resObj.get(0).get("color")); - Map colorMap = new HashMap<>(); - colorMap.put("color", colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList))); - - Map commentMap = new HashMap<>(); - String commentList = String.format("%s, %s", reqObj.get(1).get("comment"), resObj.get(1).get("comment")); - commentMap.put("comment", commentList); - - mergedList.add(0, colorMap); - mergedList.add(1, commentMap); - } else { - mergedList = new ArrayList<>(reqObj); - } - } else if (resObj != null && !resObj.isEmpty()){ - mergedList = new ArrayList<>(resObj); - } - - return mergedList; - } - - public List> processRequestMessage(IExtensionHelpers helpers, byte[] content, String host, boolean actionFlag) throws Exception { - Map> obj; - - IRequestInfo requestInfo = helpers.analyzeRequest(content); - List requestTmpHeaders = requestInfo.getHeaders(); - String requestHeaders = String.join("\n", requestTmpHeaders); - - try { - String urlString = requestTmpHeaders.get(0).split(" ")[1]; - urlString = urlString.indexOf("?") > 0 ? urlString.substring(0, urlString.indexOf("?")) : urlString; - if (matcher.matchUrlSuffix(urlString)) { - return null; - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - int requestBodyOffset = requestInfo.getBodyOffset(); - byte[] requestBody = Arrays.copyOfRange(content, requestBodyOffset, content.length); - obj = dataProcessingUnit.matchContentByRegex(content, requestHeaders, requestBody, "request", host); - - return getDataList(obj, actionFlag); - } - - public List> processResponseMessage(IExtensionHelpers helpers, byte[] content, String host, boolean actionFlag) throws Exception { - Map> obj; - - IResponseInfo responseInfo = helpers.analyzeResponse(content); - List responseTmpHeaders = responseInfo.getHeaders(); - String responseHeaders = String.join("\n", responseTmpHeaders); - - int responseBodyOffset = responseInfo.getBodyOffset(); - byte[] responseBody = Arrays.copyOfRange(content, responseBodyOffset, content.length); - - if (responseBody.length > 1) { - try { - // TODO: 需要加入文件头校验来排除静态二进制文件 - String inferredMimeType = String.format("hae.%s", responseInfo.getInferredMimeType()); - String statedMimeType = String.format("hae.%s", responseInfo.getStatedMimeType()); - if (matcher.matchUrlSuffix(statedMimeType) || matcher.matchUrlSuffix(inferredMimeType)) - { - return null; - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } else { - return null; - } - - obj = dataProcessingUnit.matchContentByRegex(content, responseHeaders, responseBody, "response", host); - - return getDataList(obj, actionFlag); - } - - private List> getDataList(Map> obj, boolean actionFlag) { - List> highlightList = new ArrayList<>(); - List> extractList = new ArrayList<>(); - - if (obj.size() > 0) { - if (actionFlag) { - List> resultList = dataProcessingUnit.extractColorsAndComments(obj); - List colorList = resultList.get(0); - List commentList = resultList.get(1); - if (!colorList.isEmpty() && !commentList.isEmpty()) { - String color = colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList)); - Map colorMap = new HashMap() {{ - put("color", color); - }}; - Map commentMap = new HashMap() {{ - put("comment", String.join(", ", commentList)); - }}; - highlightList.add(colorMap); - highlightList.add(commentMap); - } - } else { - extractList.add(dataProcessingUnit.extractDataFromMap(obj)); - } - } - - return actionFlag ? highlightList : extractList; - } -} diff --git a/src/main/java/burp/core/utils/MatchTool.java b/src/main/java/burp/core/utils/MatchTool.java deleted file mode 100644 index 4b401d6..0000000 --- a/src/main/java/burp/core/utils/MatchTool.java +++ /dev/null @@ -1,21 +0,0 @@ -package burp.core.utils; - -import jregex.Pattern; -import jregex.REFlags; -import burp.config.ConfigLoader; - -/** - * @author EvilChen - */ - -public class MatchTool { - public boolean matchUrlSuffix(String str) { - Pattern pattern = new Pattern(String.format("[\\w]+[\\.](%s)", ConfigLoader.getExcludeSuffix()), REFlags.IGNORE_CASE); - jregex.Matcher matcher = pattern.matcher(str); - return matcher.find(); - } - - public static boolean matchIP(String str) { - return str.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"); - } -} diff --git a/src/main/java/burp/rule/utils/RuleTool.java b/src/main/java/burp/rule/utils/RuleTool.java deleted file mode 100644 index f4d049c..0000000 --- a/src/main/java/burp/rule/utils/RuleTool.java +++ /dev/null @@ -1,52 +0,0 @@ -package burp.rule.utils; - -import burp.*; -import java.io.FileOutputStream; -import java.net.URL; -import java.util.Arrays; -import javax.swing.JOptionPane; - -/** - * @author EvilChen - */ -public class RuleTool { - private String rulesFilePath; - private boolean isSuccess; - - public RuleTool(String rulesFilePath) { - this.rulesFilePath = rulesFilePath; - } - - public void getRulesFromSite() { - // 以独立线程使用BurpSuite官方请求接口获取规则 - Thread t = new Thread(()->{ - try { - URL url = new URL("https://cdn.jsdelivr.net/gh/gh0stkey/HaE@gh-pages/Rules.yml"); - IHttpService iHttpService = BurpExtender.helpers.buildHttpService(url.getHost(), 443, true); - IHttpRequestResponse iHttpRequestResponse = BurpExtender.callbacks.makeHttpRequest(iHttpService, BurpExtender.helpers.buildHttpRequest(url)); - byte[] responseByte = iHttpRequestResponse.getResponse(); - IResponseInfo iResponseInfo = BurpExtender.helpers.analyzeResponse(responseByte); - int bodyOffset = iResponseInfo.getBodyOffset(); - byte[] responseBodyByte = Arrays.copyOfRange(responseByte, bodyOffset, responseByte.length); - FileOutputStream fileOutputStream = new FileOutputStream(this.rulesFilePath); - fileOutputStream.write(responseBodyByte); - fileOutputStream.close(); - isSuccess = true; - } catch (Exception e) { - isSuccess = false; - } - }); - t.start(); - try { - t.join(10000); - } catch (Exception e) { - isSuccess = false; - } - - if (isSuccess) { - JOptionPane.showMessageDialog(null, "Rules update successfully!", "Info", JOptionPane.INFORMATION_MESSAGE); - } else { - JOptionPane.showMessageDialog(null, "Rule update failed, please check the network!", "Error", JOptionPane.ERROR_MESSAGE); - } - } -} diff --git a/src/main/java/burp/rule/utils/YamlTool.java b/src/main/java/burp/rule/utils/YamlTool.java deleted file mode 100644 index 54b4c25..0000000 --- a/src/main/java/burp/rule/utils/YamlTool.java +++ /dev/null @@ -1,36 +0,0 @@ -package burp.rule.utils; - -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Map; - -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import org.yaml.snakeyaml.representer.Representer; - -/** - * @author EvilChen - */ - -public class YamlTool { - - public static Yaml newStandardYaml() { - DumperOptions dop = new DumperOptions(); - dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Representer representer = new Representer(); - return new Yaml(representer, dop); - } - - public static Map loadYaml(String filePath) { - try { - InputStream inputStream = Files.newInputStream(Paths.get(filePath)); - return newStandardYaml().load(inputStream); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/src/main/java/burp/ui/MainUI.java b/src/main/java/burp/ui/MainUI.java deleted file mode 100644 index de9afd0..0000000 --- a/src/main/java/burp/ui/MainUI.java +++ /dev/null @@ -1,347 +0,0 @@ -package burp.ui; - -import burp.config.ConfigEntry; -import burp.config.ConfigLoader; -import burp.rule.RuleProcessor; -import burp.ui.board.Databoard; -import burp.ui.board.MessagePanel; -import burp.ui.rule.RulePane; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.net.URL; -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; -import java.awt.event.*; -import java.util.Map; - -/** - * @author LinChen && EvilChen - */ - -public class MainUI extends JPanel { - - public MainUI(MessagePanel messagePanel) { - databoardPanel = new Databoard(messagePanel); - initComponents(); - } - - public void closeTabActionPerformed(ActionEvent e) { - if (ruleTabbedPane.getTabCount() > 2 && ruleTabbedPane.getSelectedIndex() != 0) { - String title = ruleTabbedPane.getTitleAt(ruleTabbedPane.getSelectedIndex()); - new RuleProcessor().deleteRuleGroup(title); - ruleTabbedPane.remove(ruleTabbedPane.getSelectedIndex()); - ruleTabbedPane.setSelectedIndex(ruleTabbedPane.getSelectedIndex() - 1); - } - } - - private void onlineUpdateActionPerformed(ActionEvent e) { - // 添加提示框防止用户误触导致配置更新 - int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION); - if (retCode == JOptionPane.YES_OPTION) { - ConfigLoader.initRules(); - reloadRule(); - } - } - - private void reloadRule(){ - ruleTabbedPane.removeAll(); - ruleSwitch.setListen(false); - Map rules = ConfigLoader.getRules(); - rules.keySet().forEach( - i -> ruleTabbedPane.addTab( - i, - new RulePane(rules.get(i), ruleTabbedPane) - ) - ); - ruleTabbedPane.addTab("...", new JLabel()); - ruleSwitch.setListen(true); - } - - private void reloadActionPerformed(ActionEvent e) { - reloadRule(); - } - - private void excludeSuffixSaveActionPerformed(ActionEvent e) { - ConfigLoader.setExcludeSuffix(excludeSuffixTextField.getText()); - } - - private void initComponents() { - JTabbedPane mainTabbedPane = new JTabbedPane(); - ruleTabbedPane = new JTabbedPane(); - JPanel rulePanel = new JPanel(); - rulesPathTextField = new JTextField(); - JLabel rulesPathLabel = new JLabel(); - JButton onlineUpdateButton = new JButton(); - JButton reloadButton = new JButton(); - JLabel excludeSuffixLabel = new JLabel(); - excludeSuffixTextField = new JTextField(); - JButton excludeSuffixSaveButton = new JButton(); - - setLayout(new GridBagLayout()); - ((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0}; - ((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0}; - ((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4}; - ((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4}; - - { - mainTabbedPane.addTab("Rules", ruleTabbedPane); - - { - rulePanel.setLayout(new GridBagLayout()); - ((GridBagLayout) rulePanel.getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0}; - ((GridBagLayout) rulePanel.getLayout()).rowHeights = new int[] {0, 0, 0}; - ((GridBagLayout) rulePanel.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4}; - ((GridBagLayout) rulePanel.getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4}; - - rulesPathTextField.setEditable(false); - rulePanel.add(rulesPathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(5, 0, 5, 5), 0, 0)); - - rulesPathLabel.setText("Rules Path:"); - rulePanel.add(rulesPathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.VERTICAL, - new Insets(5, 5, 5, 5), 0, 0)); - - onlineUpdateButton.setText("Online Update"); - onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed); - rulePanel.add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(5, 0, 5, 5), 0, 0)); - - reloadButton.setText("Reload"); - reloadButton.addActionListener(this::reloadActionPerformed); - rulePanel.add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, - - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(5, 0, 5, 5), 0, 0)); - - excludeSuffixLabel.setText("Exclude Suffix:"); - rulePanel.add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, - new Insets(0, 5, 5, 5), 0, 0)); - rulePanel.add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, - new Insets(0, 0, 0, 5), 0, 0)); - - excludeSuffixSaveButton.setText("Save"); - excludeSuffixSaveButton.addActionListener(this::excludeSuffixSaveActionPerformed); - rulePanel.add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, - new Insets(0, 0, 0, 5), 0, 0)); - } - mainTabbedPane.addTab("Config", rulePanel); - mainTabbedPane.addTab("Databoard", this.databoardPanel); - } - - // 新增Logo - JTabbedPane HaETabbedPane = new JTabbedPane(); - HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane); - HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations ", null); - HaETabbedPane.setEnabledAt(1, false); - HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent e) { - boolean isDarkBg = isDarkBg(); - HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg)); - } - - private boolean isDarkBg() { - Color bg = HaETabbedPane.getBackground(); - int r = bg.getRed(); - int g = bg.getGreen(); - int b = bg.getBlue(); - int avg = (r + g + b) / 3; - - return avg < 128; - } - }); - - add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 0, 0, 0), 0, 0)); - - ConfigEntry.globalRules.keySet().forEach(i-> ruleTabbedPane.addTab(i, new RulePane( - ConfigEntry.globalRules.get(i), - ruleTabbedPane))); - - ruleTabbedPane.addTab("...", new JLabel()); - - rulesPathTextField.setText(ConfigLoader.getRulesFilePath()); - excludeSuffixTextField.setText(ConfigLoader.getExcludeSuffix()); - ruleSwitch = new TabTitleEditListener(ruleTabbedPane); - ruleTabbedPane.addChangeListener(ruleSwitch); - ruleTabbedPane.addMouseListener(ruleSwitch); - deleteMenuItem.addActionListener(this::closeTabActionPerformed); - tabMenu.add(deleteMenuItem); - } - - private ImageIcon getImageIcon(boolean isDark) { - ClassLoader classLoader = getClass().getClassLoader(); - URL imageURL; - if (isDark) { - imageURL = classLoader.getResource("logo.png"); - } else { - imageURL = classLoader.getResource("logo_black.png"); - } - ImageIcon originalIcon = new ImageIcon(imageURL); - Image originalImage = originalIcon.getImage(); - Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST); - ImageIcon scaledIcon = new ImageIcon(scaledImage); - return scaledIcon; - } - - private JTabbedPane ruleTabbedPane; - private JTextField rulesPathTextField; - private JTextField excludeSuffixTextField; - private Databoard databoardPanel; - protected static JPopupMenu tabMenu = new JPopupMenu(); - private final JMenuItem deleteMenuItem = new JMenuItem("Delete"); - private TabTitleEditListener ruleSwitch; -} - -class TabTitleEditListener extends MouseAdapter implements ChangeListener, DocumentListener { - protected final JTextField ruleEditTextField = new JTextField(); - protected final JTabbedPane ruleEditTabbedPane; - protected int editingIndex = -1; - protected int len = -1; - protected Boolean listen = true; - protected Dimension dim; - protected Component tabComponent; - protected Boolean isRenameOk = false; - protected RuleProcessor ruleProcessor = new RuleProcessor(); - - protected final Action startEditing = new AbstractAction() { - @Override public void actionPerformed(ActionEvent e) { - editingIndex = ruleEditTabbedPane.getSelectedIndex(); - tabComponent = ruleEditTabbedPane.getTabComponentAt(editingIndex); - ruleEditTabbedPane.setTabComponentAt(editingIndex, ruleEditTextField); - isRenameOk = true; - ruleEditTextField.setVisible(true); - ruleEditTextField.setText(ruleEditTabbedPane.getTitleAt(editingIndex)); - ruleEditTextField.selectAll(); - ruleEditTextField.requestFocusInWindow(); - len = ruleEditTextField.getText().length(); - dim = ruleEditTextField.getPreferredSize(); - ruleEditTextField.setMinimumSize(dim); - } - }; - - protected final Action renameTabTitle = new AbstractAction() { - @Override public void actionPerformed(ActionEvent e) { - String title = ruleEditTextField.getText().trim(); - if (editingIndex >= 0 && !title.isEmpty()) { - String oldName = ruleEditTabbedPane.getTitleAt(editingIndex); - ruleEditTabbedPane.setTitleAt(editingIndex, title); - ruleProcessor.renameRuleGroup(oldName,title); - } - cancelEditing.actionPerformed(null); - } - }; - - protected final Action cancelEditing = new AbstractAction() { - @Override public void actionPerformed(ActionEvent e) { - if (editingIndex >= 0) { - ruleEditTabbedPane.setTabComponentAt(editingIndex, tabComponent); - ruleEditTextField.setVisible(false); - editingIndex = -1; - len = -1; - tabComponent = null; - ruleEditTextField.setPreferredSize(null); - ruleEditTabbedPane.requestFocusInWindow(); - } - } - }; - - protected TabTitleEditListener(JTabbedPane tabbedPane) { - super(); - this.ruleEditTabbedPane = tabbedPane; - ruleEditTextField.setBorder(BorderFactory.createEmptyBorder()); - ruleEditTextField.addFocusListener(new FocusAdapter() { - @Override public void focusLost(FocusEvent e) { - renameTabTitle.actionPerformed(null); - } - }); - InputMap im = ruleEditTextField.getInputMap(JComponent.WHEN_FOCUSED); - ActionMap am = ruleEditTextField.getActionMap(); - im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel-editing"); - am.put("cancel-editing", cancelEditing); - im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename-tab-title"); - am.put("rename-tab-title", renameTabTitle); - ruleEditTextField.getDocument().addDocumentListener(this); - tabbedPane.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "start-editing"); - tabbedPane.getActionMap().put("start-editing", startEditing); - } - - @Override public void stateChanged(ChangeEvent e) { - if (e.getSource() instanceof JTabbedPane && listen) { - JTabbedPane pane = (JTabbedPane) e.getSource(); - if (!isRenameOk){ - if (pane.getSelectedIndex() == pane.getComponentCount()-1){ - newTab(); - } - }else{ - if (pane.getSelectedIndex() == pane.getComponentCount()-2){ - newTab(); - } - } - } - renameTabTitle.actionPerformed(null); - } - - public void newTab(){ - Object[][] data = new Object[][]{{false, "New Name", "(New Regex)", "", "{0}", "gray", "any", "nfa", false}}; - insertTab(ruleEditTabbedPane, ruleProcessor.newRule(),data); - } - - public void insertTab(JTabbedPane pane,String title,Object[][] data){ - pane.addTab(title,new RulePane(data,pane)); - pane.remove(pane.getSelectedIndex()); - pane.addTab("...",new JLabel()); - } - - public void setListen(Boolean listen){ - this.listen = listen; - } - - @Override public void insertUpdate(DocumentEvent e) { - updateTabSize(); - } - - @Override public void removeUpdate(DocumentEvent e) { - updateTabSize(); - } - - @Override public void changedUpdate(DocumentEvent e) {} - - @Override public void mouseClicked(MouseEvent e) { - switch (e.getButton()){ - case 1: - { - Rectangle r = ruleEditTabbedPane.getBoundsAt(ruleEditTabbedPane.getSelectedIndex()); - boolean isDoubleClick = e.getClickCount() >= 2; - if (isDoubleClick && r.contains(e.getPoint())) { - startEditing.actionPerformed(null); - } else { - renameTabTitle.actionPerformed(null); - } - break; - } - case 3:{ - MainUI.tabMenu.show(e.getComponent(),e.getX(),e.getY()); - break; - } - default: - break; - } - } - - protected void updateTabSize() { - ruleEditTextField.setPreferredSize(ruleEditTextField.getText().length() > len ? null : dim); - ruleEditTabbedPane.revalidate(); - } -} diff --git a/src/main/java/burp/ui/rule/RulePane.java b/src/main/java/burp/ui/rule/RulePane.java deleted file mode 100644 index 6bce47e..0000000 --- a/src/main/java/burp/ui/rule/RulePane.java +++ /dev/null @@ -1,218 +0,0 @@ -package burp.ui.rule; - -import burp.rule.RuleProcessor; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import javax.swing.*; -import javax.swing.event.TableModelEvent; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableRowSorter; -import java.awt.*; -import java.util.Vector; - -/** - * @author LinChen & EvilChen - */ - -public class RulePane extends JPanel { - private RuleProcessor ruleProcessor = new RuleProcessor(); - private Boolean isEdit = false; - private DefaultTableModel model = createModel(); - private static final int YES_OPTION = JOptionPane.YES_OPTION; - private static final String[] TITLE = { - "Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive" - }; - - public RulePane(Object[][] data, JTabbedPane pane) { - initComponents(data, pane); - } - - private DefaultTableModel createModel() { - return new DefaultTableModel() { - @Override - public Class getColumnClass(int column) { - return (column == 0) ? Boolean.class : String.class; - } - - @Override - public boolean isCellEditable(int row, int column) { - return column == 0; - } - }; - } - - private void updateModel() { - model = (DefaultTableModel) ruleTable.getModel(); - } - - private void ruleAddActionPerformed(ActionEvent e, JTabbedPane pane) { - RuleSetting ruleSettingPanel = new RuleSetting(); - ruleSettingPanel.formatTextField.setText("{0}"); - - int showState = JOptionPane.showConfirmDialog(null, ruleSettingPanel, "Add Rule", JOptionPane.OK_OPTION); - if (showState == YES_OPTION) { - Vector ruleData = new Vector<>(); - ruleData.add(false); - ruleData.add(ruleSettingPanel.ruleNameTextField.getText()); - ruleData.add(ruleSettingPanel.firstRegexTextField.getText()); - ruleData.add(ruleSettingPanel.secondRegexTextField.getText()); - ruleData.add(ruleSettingPanel.formatTextField.getText()); - ruleData.add(ruleSettingPanel.colorComboBox.getSelectedItem().toString()); - ruleData.add(ruleSettingPanel.scopeComboBox.getSelectedItem().toString()); - ruleData.add(ruleSettingPanel.engineComboBox.getSelectedItem().toString()); - ruleData.add(ruleSettingPanel.sensitiveComboBox.getSelectedItem()); - model.insertRow(model.getRowCount(), ruleData); - updateModel(); - ruleProcessor.addRule(ruleData, pane.getTitleAt(pane.getSelectedIndex())); - } - } - - private void ruleEditActionPerformed(ActionEvent e, JTabbedPane pane){ - if (ruleTable.getSelectedRowCount() >= 1){ - RuleSetting ruleSettingPanel = new RuleSetting(); - ruleSettingPanel.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString()); - ruleSettingPanel.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString()); - ruleSettingPanel.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString()); - ruleSettingPanel.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString()); - ruleSettingPanel.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString()); - ruleSettingPanel.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString()); - ruleSettingPanel.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString()); - ruleSettingPanel.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(),8)); - - ruleSettingPanel.formatTextField.setEnabled( - ruleSettingPanel.engineComboBox.getSelectedItem().toString().equals("nfa") - ); - - int showState = JOptionPane.showConfirmDialog(null, ruleSettingPanel, "Edit Rule", JOptionPane.OK_OPTION); - if (showState == 0){ - int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); - model.setValueAt(ruleSettingPanel.ruleNameTextField.getText(), select, 1); - model.setValueAt(ruleSettingPanel.firstRegexTextField.getText(), select, 2); - model.setValueAt(ruleSettingPanel.secondRegexTextField.getText(), select, 3); - model.setValueAt(ruleSettingPanel.formatTextField.getText(), select, 4); - model.setValueAt(ruleSettingPanel.colorComboBox.getSelectedItem().toString(), select, 5); - model.setValueAt(ruleSettingPanel.scopeComboBox.getSelectedItem().toString(), select, 6); - model.setValueAt(ruleSettingPanel.engineComboBox.getSelectedItem().toString(), select, 7); - model.setValueAt(ruleSettingPanel.sensitiveComboBox.getSelectedItem(), select, 8); - model = (DefaultTableModel) ruleTable.getModel(); - ruleProcessor.changeRule((Vector) model.getDataVector().get(select), select, pane.getTitleAt(pane.getSelectedIndex())); - } - } - } - - private void ruleRemoveActionPerformed(ActionEvent e, JTabbedPane pane){ - if (ruleTable.getSelectedRowCount() >= 1){ - int isOk = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION); - if (isOk == 0){ - int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); - model.removeRow(select); - model = (DefaultTableModel) ruleTable.getModel(); - ruleProcessor.removeRule(select, pane.getTitleAt(pane.getSelectedIndex())); - } - } - } - - private void ruleTableChange(TableModelEvent e, JTabbedPane pane) { - if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1 && !isEdit){ - model = (DefaultTableModel) ruleTable.getModel(); - int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); - ruleProcessor.changeRule((Vector) model.getDataVector().get(select), select, pane.getTitleAt(pane.getSelectedIndex())); - } - } - - private void initComponents(Object[][] data, JTabbedPane pane) { - // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents - addButton = new JButton(); - editButton = new JButton(); - scrollPane = new JScrollPane(); - ruleTable = new JTable(); - removeButton = new JButton(); - - //======== this ======== - setLayout(new GridBagLayout()); - ((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0}; - ((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0}; - ((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 1.0, 1.0E-4}; - ((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 1.0E-4}; - - //---- addButton ---- - addButton.setText("Add"); - - addButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - isEdit = true; - ruleAddActionPerformed(e, pane); - model = (DefaultTableModel) ruleTable.getModel(); - isEdit = false; - } - }); - - add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(15, 5, 3, 2), 0, 0)); - - //---- editButton ---- - editButton.setText("Edit"); - editButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - isEdit = true; - ruleEditActionPerformed(e, pane); - model = (DefaultTableModel) ruleTable.getModel(); - isEdit = false; - } - }); - - add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 5, 3, 2), 0, 0)); - - //======== scrollPane ======== - { - //---- table ---- - ruleTable.setShowVerticalLines(false); - ruleTable.setVerifyInputWhenFocusTarget(false); - ruleTable.setUpdateSelectionOnSort(false); - ruleTable.setShowHorizontalLines(false); - ruleTable.setModel(new DefaultTableModel()); - ruleTable.setSurrendersFocusOnKeystroke(true); - scrollPane.setViewportView(ruleTable); - } - - add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(15, 5, 5, 5), 0, 0)); - - //---- removeButton ---- - removeButton.setText("Remove"); - - removeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - isEdit = true; - ruleRemoveActionPerformed(e, pane); - model = (DefaultTableModel) ruleTable.getModel(); - isEdit = false; - } - }); - - add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(0, 5, 3, 2), 0, 0)); - - ruleTable.setModel(model); - model.setDataVector(data, TITLE); - model.addTableModelListener(e -> ruleTableChange(e, pane)); - ruleTable.setRowSorter(new TableRowSorter<>(model)); - } - - // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables - public JButton addButton; - public JButton editButton; - public JScrollPane scrollPane; - public JTable ruleTable; - public JButton removeButton; - // JFormDesigner - End of variables declaration //GEN-END:variables -} - diff --git a/src/main/java/hae/Config.java b/src/main/java/hae/Config.java new file mode 100644 index 0000000..7ae59d9 --- /dev/null +++ b/src/main/java/hae/Config.java @@ -0,0 +1,53 @@ +package hae; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class Config { + public static String suffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip"; + + public static String[] scope = new String[] { + "any", + "any header", + "any body", + "response", + "response header", + "response body", + "request", + "request header", + "request body" + }; + + public static String[] ruleFields = { + "Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive" + }; + + public static Object[][] ruleTemplate = new Object[][] { + { + false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false + } + }; + + public static String[] engine = new String[] { + "nfa", + "dfa" + }; + + public static String[] color = new String[] { + "red", + "orange", + "yellow", + "green", + "cyan", + "blue", + "pink", + "magenta", + "gray" + }; + + public static Map globalRules = new HashMap<>(); + + public static ConcurrentHashMap>> globalDataMap = new ConcurrentHashMap<>(); +} diff --git a/src/main/java/hae/HaE.java b/src/main/java/hae/HaE.java new file mode 100644 index 0000000..f50cc22 --- /dev/null +++ b/src/main/java/hae/HaE.java @@ -0,0 +1,47 @@ +package hae; + +import burp.api.montoya.BurpExtension; +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.logging.Logging; +import hae.component.Main; +import hae.component.board.message.MessageTableModel; +import hae.instances.editor.RequestEditor; +import hae.instances.editor.ResponseEditor; +import hae.instances.editor.WebSocketEditor; +import hae.instances.http.HttpMessageHandler; +import hae.instances.websocket.WebSocketMessageHandler; +import hae.utils.config.ConfigLoader; + +public class HaE implements BurpExtension { + @Override + public void initialize(MontoyaApi api) { + // 设置扩展名称 + String version = "3.0"; + api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version)); + + // 加载扩展后输出的项目信息 + Logging logging = api.logging(); + logging.logToOutput("[ HACK THE WORLD - TO DO IT ]"); + logging.logToOutput("[#] Author: EvilChen && 0chencc"); + logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE"); + + // 配置文件加载 + ConfigLoader configLoader = new ConfigLoader(api); + + MessageTableModel messageTableModel = new MessageTableModel(api); + + // 注册Tab页(用于查询数据) + api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel)); + + // 注册HTTP处理器 + api.http().registerHttpHandler(new HttpMessageHandler(api, messageTableModel)); + + // 注册WebSocket处理器 + api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api))); + + // 注册消息编辑框(用于展示数据) + api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api)); + api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api)); + api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api)); + } +} diff --git a/src/main/java/burp/core/GlobalCachePool.java b/src/main/java/hae/cache/CachePool.java similarity index 67% rename from src/main/java/burp/core/GlobalCachePool.java rename to src/main/java/hae/cache/CachePool.java index 3844026..68c09cb 100644 --- a/src/main/java/burp/core/GlobalCachePool.java +++ b/src/main/java/hae/cache/CachePool.java @@ -1,14 +1,8 @@ -package burp.core; +package hae.cache; -import java.util.HashMap; -import java.util.Map; +import java.util.*; -/** - * @author EvilChen - */ - -public class GlobalCachePool { - // 用于缓存匹配结果,以请求/响应的MD5 Hash作为索引 +public class CachePool { private static final Map>> cache = new HashMap<>(); public static void addToCache(String key, Map> value) { @@ -22,5 +16,4 @@ public class GlobalCachePool { public static void removeFromCache(String key) { cache.remove(key); } - } \ No newline at end of file diff --git a/src/main/java/hae/component/Main.java b/src/main/java/hae/component/Main.java new file mode 100644 index 0000000..a07a397 --- /dev/null +++ b/src/main/java/hae/component/Main.java @@ -0,0 +1,87 @@ +package hae.component; + +import burp.api.montoya.MontoyaApi; +import hae.component.board.Databoard; +import hae.component.board.message.MessageTableModel; +import hae.component.config.Config; +import hae.component.rule.Rules; +import hae.utils.config.ConfigLoader; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.net.URL; + +public class Main extends JPanel { + private final MontoyaApi api; + private final ConfigLoader configLoader; + private final MessageTableModel messageTableModel; + + public Main(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { + this.api = api; + this.configLoader = configLoader; + this.messageTableModel = messageTableModel; + + initComponents(); + } + + private void initComponents() { + setLayout(new GridBagLayout()); + ((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0}; + ((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0}; + ((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4}; + ((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4}; + + JTabbedPane mainTabbedPane = new JTabbedPane(); + + // 新增Logo + JTabbedPane HaETabbedPane = new JTabbedPane(); + HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane); + // 中文Slogan:赋能白帽,高效作战 + HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null); + HaETabbedPane.setEnabledAt(1, false); + HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent e) { + boolean isDarkBg = isDarkBg(); + HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg)); + } + + private boolean isDarkBg() { + Color bg = HaETabbedPane.getBackground(); + int r = bg.getRed(); + int g = bg.getGreen(); + int b = bg.getBlue(); + int avg = (r + g + b) / 3; + + return avg < 128; + } + }); + + add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + + // 依次添加Rules、Config、Databoard + Rules rules = new Rules(api, configLoader); + mainTabbedPane.addTab("Rules", rules); + mainTabbedPane.addTab("Config", new Config(api, configLoader, rules)); + mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel)); + } + + private ImageIcon getImageIcon(boolean isDark) { + ClassLoader classLoader = getClass().getClassLoader(); + URL imageURL; + if (isDark) { + imageURL = classLoader.getResource("logo.png"); + } else { + imageURL = classLoader.getResource("logo_black.png"); + } + ImageIcon originalIcon = new ImageIcon(imageURL); + Image originalImage = originalIcon.getImage(); + Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST); + ImageIcon scaledIcon = new ImageIcon(scaledImage); + return scaledIcon; + } +} diff --git a/src/main/java/burp/ui/board/Databoard.java b/src/main/java/hae/component/board/Databoard.java similarity index 59% rename from src/main/java/burp/ui/board/Databoard.java rename to src/main/java/hae/component/board/Databoard.java index 891633e..f7d8aab 100644 --- a/src/main/java/burp/ui/board/Databoard.java +++ b/src/main/java/hae/component/board/Databoard.java @@ -1,13 +1,15 @@ -package burp.ui.board; +package hae.component.board; -import burp.config.ConfigEntry; -import burp.core.utils.StringHelper; -import burp.ui.board.MessagePanel.Table; +import burp.api.montoya.MontoyaApi; +import hae.Config; +import hae.component.board.message.MessageTableModel; +import hae.utils.string.StringProcessor; +import hae.utils.config.ConfigLoader; +import hae.component.board.message.MessageTableModel.MessageTable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import javax.swing.event.*; -import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; @@ -16,98 +18,55 @@ import java.awt.event.*; import java.util.List; import javax.swing.*; -/** - * @author LinChen && EvilChen - */ - public class Databoard extends JPanel { - private static Boolean isMatchHost = false; - private JLabel hostLabel; + private final MontoyaApi api; + private final ConfigLoader configLoader; + private final MessageTableModel messageTableModel; private JTextField hostTextField; private JTabbedPane dataTabbedPane; - private JButton clearButton; private JSplitPane splitPane; - private MessagePanel messagePanel; - private Table table; - private SwingWorker currentWorker; + private MessageTable messageTable; + + private static Boolean isMatchHost = false; private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); private JComboBox hostComboBox = new JComboBox(comboBoxModel); - private ChangeListener changeListenerInstance = new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - int selectedIndex = dataTabbedPane.getSelectedIndex(); - String selectedTitle = ""; - if (selectedIndex != -1) { - selectedTitle = dataTabbedPane.getTitleAt(selectedIndex); - } + public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { + this.api = api; + this.configLoader = configLoader; + this.messageTableModel = messageTableModel; - applyHostFilter(selectedTitle); - } - }; - - public Databoard(MessagePanel messagePanel) { - this.messagePanel = messagePanel; initComponents(); } - private void cleanUI() { - dataTabbedPane.removeAll(); - splitPane.setVisible(false); - } - - private void clearActionPerformed(ActionEvent e) { - int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info", - JOptionPane.YES_NO_OPTION); - if (retCode == JOptionPane.YES_OPTION) { - cleanUI(); - - String host = hostTextField.getText(); - String cleanedHost = StringHelper.replaceFirstOccurrence(host, "*.", ""); - - if (host.contains("*")) { - ConfigEntry.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*")); - } else { - ConfigEntry.globalDataMap.remove(host); - } - - messagePanel.deleteByHost(cleanedHost); - } - } - private void initComponents() { - hostLabel = new JLabel(); - hostTextField = new JTextField(); - dataTabbedPane = new JTabbedPane(JTabbedPane.TOP); - clearButton = new JButton(); - - //======== this ======== setLayout(new GridBagLayout()); - ((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0, 20, 0}; + ((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0,20, 0}; ((GridBagLayout)getLayout()).rowHeights = new int[] {0, 65, 20, 0}; ((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4}; ((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 1.0, 0.0, 1.0E-4}; - //---- hostLabel ---- - hostLabel.setText("Host:"); - add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(8, 0, 5, 5), 0, 0)); - add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(8, 0, 5, 5), 0, 0)); - clearButton.setText("Clear"); - clearButton.addActionListener(this::clearActionPerformed); - add(clearButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(8, 0, 5, 5), 0, 0)); + JLabel hostLabel = new JLabel("Host:"); - hostComboBox.setMaximumRowCount(5); - add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(8, 0, 5, 5), 0, 0)); + JButton clearButton = new JButton("Clear"); + JButton actionButton = new JButton("Action"); + JPanel menuPanel = new JPanel(new GridLayout(1, 1)); + menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + JPopupMenu menu = new JPopupMenu(); + menuPanel.add(clearButton); + menu.add(menuPanel); + hostTextField = new JTextField(); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); - splitPane.setVisible(false); - add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(8, 0, 5, 5), 0, 0)); + dataTabbedPane = new JTabbedPane(JTabbedPane.TOP); + + actionButton.addActionListener(e -> { + int x = 0; + int y = actionButton.getHeight(); + menu.show(actionButton, x, y); + }); + + clearButton.addActionListener(this::clearActionPerformed); splitPane.addComponentListener(new ComponentAdapter() { @Override @@ -116,12 +75,27 @@ public class Databoard extends JPanel { } }); + splitPane.setVisible(false); + + add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 0, 5, 5), 0, 0)); + add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 0, 5, 5), 0, 0)); + add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 0, 5, 5), 0, 0)); + add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 0, 5, 5), 0, 0)); + hostComboBox.setMaximumRowCount(5); + add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 0, 5, 5), 0, 0)); + setAutoMatch(); } private void resizePanel() { splitPane.setDividerLocation(0.4); - TableColumnModel columnModel = table.getColumnModel(); + TableColumnModel columnModel = messageTable.getColumnModel(); int totalWidth = (int) (getWidth() * 0.6); columnModel.getColumn(0).setPreferredWidth((int) (totalWidth * 0.1)); columnModel.getColumn(1).setPreferredWidth((int) (totalWidth * 0.3)); @@ -131,16 +105,7 @@ public class Databoard extends JPanel { columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1)); } - private static List getHostByList() { - return new ArrayList<>(ConfigEntry.globalDataMap.keySet()); - } - - /** - * 设置输入自动匹配 - */ private void setAutoMatch() { - populateComboBoxModel(); - hostComboBox.setSelectedItem(null); hostComboBox.addActionListener(this::handleComboBoxAction); @@ -170,12 +135,6 @@ public class Databoard extends JPanel { }); } - private void populateComboBoxModel() { - for (String host : getHostByList()) { - comboBoxModel.addElement(host); - } - } - private void handleComboBoxAction(ActionEvent e) { if (!isMatchHost && hostComboBox.getSelectedItem() != null) { String selectedHost = hostComboBox.getSelectedItem().toString(); @@ -183,7 +142,7 @@ public class Databoard extends JPanel { populateTabbedPaneByHost(selectedHost); } } - + private void handleKeyEvents(KeyEvent e) { isMatchHost = true; int keyCode = e.getKeyCode(); @@ -213,7 +172,6 @@ public class Databoard extends JPanel { isMatchHost = true; comboBoxModel.removeAllElements(); String input = hostTextField.getText().toLowerCase(); - if (!input.isEmpty()) { for (String host : getHostByList()) { String lowerCaseHost = host.toLowerCase(); @@ -232,36 +190,20 @@ public class Databoard extends JPanel { isMatchHost = false; } - private void applyHostFilter(String filterText) { - TableRowSorter sorter = (TableRowSorter) table.getRowSorter(); - - String cleanedText = StringHelper.replaceFirstOccurrence(filterText, "*.", ""); - - if (cleanedText.contains("*")) { - cleanedText = ""; - } - - RowFilter filter = RowFilter.regexFilter(cleanedText, 1); - sorter.setRowFilter(filter); - - messagePanel.applyHostFilter(filterText); - } - private void populateTabbedPaneByHost(String selectedHost) { if (!Objects.equals(selectedHost, "")) { - ConcurrentHashMap>> dataMap = ConfigEntry.globalDataMap; + ConcurrentHashMap>> dataMap = Config.globalDataMap; Map> selectedDataMap; dataTabbedPane.removeAll(); dataTabbedPane.setPreferredSize(new Dimension(500,0)); dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - dataTabbedPane.removeChangeListener(changeListenerInstance); splitPane.setLeftComponent(dataTabbedPane); if (selectedHost.contains("*")) { // 通配符数据 selectedDataMap = new HashMap<>(); - String hostPattern = StringHelper.replaceFirstOccurrence(selectedHost, "*.", ""); + String hostPattern = StringProcessor.replaceFirstOccurrence(selectedHost, "*.", ""); for (String key : dataMap.keySet()) { if (key.contains(hostPattern) || selectedHost.equals("*")) { Map> ruleMap = dataMap.get(key); @@ -282,71 +224,62 @@ public class Databoard extends JPanel { selectedDataMap = dataMap.get(selectedHost); } - if (selectedHost.equals("**")) { - if (currentWorker != null && !currentWorker.isDone()) { - currentWorker.cancel(true); - } - for (ConcurrentHashMap.Entry>> entry : dataMap.entrySet()) { - JTabbedPane newTabbedPane = new JTabbedPane(); - newTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - for (Map.Entry> entrySet : entry.getValue().entrySet()) { - currentWorker = new SwingWorker() { - @Override - protected Object[] doInBackground() throws Exception { - String tabTitle = String.format("%s (%s)", entrySet.getKey(), - entrySet.getValue().size()); - DatatablePanel datatablePanel = new DatatablePanel(entrySet.getKey(), - entrySet.getValue()); - datatablePanel.setTableListener(messagePanel); - return new Object[] {tabTitle, datatablePanel}; - } - - @Override - protected void done() { - if (!isCancelled()) { - try { - Object[] result = (Object[]) get(); - SwingUtilities.invokeLater(() -> { - newTabbedPane.addTab(result[0].toString(), (DatatablePanel) result[1]); - dataTabbedPane.addTab(entry.getKey(), newTabbedPane); - }); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - }; - currentWorker.execute(); - } - } - - dataTabbedPane.addChangeListener(changeListenerInstance); - } else { - for (Map.Entry> entry : selectedDataMap.entrySet()) { - String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size()); - DatatablePanel datatablePanel = new DatatablePanel(entry.getKey(), entry.getValue()); - datatablePanel.setTableListener(messagePanel); - dataTabbedPane.addTab(tabTitle, datatablePanel); - } + for (Map.Entry> entry : selectedDataMap.entrySet()) { + String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size()); + Datatable datatablePanel = new Datatable(api, entry.getKey(), entry.getValue()); + datatablePanel.setTableListener(messageTableModel); + dataTabbedPane.addTab(tabTitle, datatablePanel); } // 展示请求消息表单 - JSplitPane messageSplitPane = this.messagePanel.getPanel(); + JSplitPane messageSplitPane = messageTableModel.getSplitPane(); this.splitPane.setRightComponent(messageSplitPane); - table = this.messagePanel.getTable(); + messageTable = messageTableModel.getMessageTable(); resizePanel(); splitPane.setVisible(true); applyHostFilter(selectedHost); - - // 主动调用一次stateChanged,使得dataTabbedPane可以精准展示内容 - if (selectedHost.equals("**")) { - changeListenerInstance.stateChanged(null); - } - hostTextField.setText(selectedHost); - } } -} \ No newline at end of file + + private void applyHostFilter(String filterText) { + TableRowSorter sorter = (TableRowSorter) messageTable.getRowSorter(); + + String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", ""); + + if (cleanedText.contains("*")) { + cleanedText = ""; + } + + RowFilter filter = RowFilter.regexFilter(cleanedText, 1); + sorter.setRowFilter(filter); + + messageTableModel.applyHostFilter(filterText); + } + + private List getHostByList() { + return new ArrayList<>(Config.globalDataMap.keySet()); + } + + private void clearActionPerformed(ActionEvent e) { + int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info", + JOptionPane.YES_NO_OPTION); + String host = hostTextField.getText(); + if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) { + dataTabbedPane.removeAll(); + splitPane.setVisible(false); + + String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", ""); + + if (host.contains("*")) { + Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*")); + } else { + Config.globalDataMap.remove(host); + } + + messageTableModel.deleteByHost(cleanedHost); + } + } +} diff --git a/src/main/java/burp/ui/board/DatatablePanel.java b/src/main/java/hae/component/board/Datatable.java similarity index 55% rename from src/main/java/burp/ui/board/DatatablePanel.java rename to src/main/java/hae/component/board/Datatable.java index 44a5513..1b90776 100644 --- a/src/main/java/burp/ui/board/DatatablePanel.java +++ b/src/main/java/hae/component/board/Datatable.java @@ -1,58 +1,48 @@ -package burp.ui.board; +package hae.component.board; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.GridLayout; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; +import burp.api.montoya.MontoyaApi; +import hae.component.board.message.MessageTableModel; import jregex.Pattern; import jregex.REFlags; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; + +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.Comparator; +import java.util.*; import java.util.List; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JTextField; -import javax.swing.RowFilter; -import javax.swing.ScrollPaneConstants; -import javax.swing.TransferHandler; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; -import javax.swing.table.TableRowSorter; +import javax.swing.*; +import java.awt.datatransfer.*; +import javax.swing.event.*; +import javax.swing.table.*; -public class DatatablePanel extends JPanel { - private final JTable table; - private final DefaultTableModel model; +public class Datatable extends JPanel { + private final MontoyaApi api; + private final JTable dataTable; + private final DefaultTableModel dataTableModel; private final JTextField searchField; - private TableRowSorter sorter; - private JScrollPane scrollPane; - private String tableName; - private JCheckBox searchMode = new JCheckBox("Reverse search"); + private final TableRowSorter sorter; + private final JCheckBox searchMode = new JCheckBox("Reverse search"); + private final String tabName; + + public Datatable(MontoyaApi api, String tabName, List dataList) { + this.api = api; + this.tabName = tabName; - public DatatablePanel(String tableName, List list) { - this.tableName = tableName; String[] columnNames = {"#", "Information"}; - model = new DefaultTableModel(columnNames, 0); - table = new JTable(model); - sorter = new TableRowSorter<>(model); + + dataTableModel = new DefaultTableModel(columnNames, 0); + dataTable = new JTable(dataTableModel); + sorter = new TableRowSorter<>(dataTableModel); + + searchField = new JTextField(); + + initComponents(dataList); + } + + private void initComponents(List dataList) { // 设置ID排序 sorter.setComparator(0, new Comparator() { @Override @@ -61,38 +51,19 @@ public class DatatablePanel extends JPanel { } }); - table.setRowSorter(sorter); - TableColumn idColumn = table.getColumnModel().getColumn(0); + dataTable.setRowSorter(sorter); + TableColumn idColumn = dataTable.getColumnModel().getColumn(0); idColumn.setMaxWidth(50); - for (String item : list) { + for (String item : dataList) { if (!item.isEmpty()) { - addRowToTable(model, new Object[]{item}); + addRowToTable(new Object[]{item}); } } - String defaultText = "Search"; - searchField = new JTextField(defaultText); - - // 设置灰色默认文本Search - searchField.setForeground(Color.GRAY); - searchField.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - if (searchField.getText().equals(defaultText)) { - searchField.setText(""); - searchField.setForeground(Color.BLACK); - } - } - - @Override - public void focusLost(FocusEvent e) { - if (searchField.getText().isEmpty()) { - searchField.setForeground(Color.GRAY); - searchField.setText(defaultText); - } - } - }); + // 设置灰色默认文本 + String searchText = "Search"; + addPlaceholder(searchField, searchText); // 监听输入框内容输入、更新、删除 searchField.getDocument().addDocumentListener(new DocumentListener() { @@ -114,14 +85,10 @@ public class DatatablePanel extends JPanel { }); // 设置布局 - scrollPane = new JScrollPane(table); + JScrollPane scrollPane = new JScrollPane(dataTable); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - searchMode.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - performSearch(); - } - }); + searchMode.addItemListener(e -> performSearch()); setLayout(new BorderLayout(0, 5)); @@ -137,31 +104,62 @@ public class DatatablePanel extends JPanel { menu.add(menuPanel); JButton settingsButton = new JButton("Settings"); - settingsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int x = settingsButton.getX(); - int y = settingsButton.getY() - menu.getPreferredSize().height; - menu.show(settingsButton, x, y); - } + settingsButton.addActionListener(e -> { + int x = settingsButton.getX(); + int y = settingsButton.getY() - menu.getPreferredSize().height; + menu.show(settingsButton, x, y); }); optionsPanel.add(settingsButton); optionsPanel.add(Box.createHorizontalStrut(5)); optionsPanel.add(searchField); + dataTable.setTransferHandler(new TransferHandler() { + @Override + public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException { + if (comp instanceof JTable) { + StringSelection stringSelection = new StringSelection(getSelectedData( + (JTable) comp)); + clip.setContents(stringSelection, null); + } else { + super.exportToClipboard(comp, clip, action); + } + } + }); + add(scrollPane, BorderLayout.CENTER); add(optionsPanel, BorderLayout.SOUTH); } - private static void addRowToTable(DefaultTableModel model, Object[] data) { - // 获取当前ID - int rowCount = model.getRowCount(); - int id = rowCount > 0 ? (Integer) model.getValueAt(rowCount - 1, 0) + 1 : 1; + public static void addPlaceholder(JTextField textField, String placeholderText) { + textField.setForeground(Color.GRAY); + textField.setText(placeholderText); + textField.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (textField.getText().equals(placeholderText)) { + textField.setText(""); + textField.setForeground(Color.BLACK); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (textField.getText().isEmpty()) { + textField.setForeground(Color.GRAY); + textField.setText(placeholderText); + } + } + }); + } + + private void addRowToTable(Object[] data) { + int rowCount = dataTableModel.getRowCount(); + int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1; Object[] rowData = new Object[data.length + 1]; - rowData[0] = id; // 设置ID列的值 - System.arraycopy(data, 0, rowData, 1, data.length); // 拷贝其余数据 - model.addRow(rowData); // 添加行 + rowData[0] = id; + System.arraycopy(data, 0, rowData, 1, data.length); + dataTableModel.addRow(rowData); } private void performSearch() { @@ -188,38 +186,7 @@ public class DatatablePanel extends JPanel { } } - public void setTableListener(MessagePanel messagePanel) { - table.setDefaultEditor(Object.class, null); - - // 表格内容双击事件 - table.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - int selectedRow = table.getSelectedRow(); - if (selectedRow != -1) { - String rowData = table.getValueAt(selectedRow, 1).toString(); - messagePanel.applyMessageFilter(tableName, rowData); - } - } - } - }); - - table.setTransferHandler(new TransferHandler() { - @Override - public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException { - if (comp instanceof JTable) { - StringSelection stringSelection = new StringSelection(getSelectedData( - (JTable) comp)); - clip.setContents(stringSelection, null); - } else { - super.exportToClipboard(comp, clip, action); - } - } - }); - } - - public String getSelectedData(JTable table) { + public static String getSelectedData(JTable table) { int[] selectRows = table.getSelectedRows(); StringBuilder selectData = new StringBuilder(); for (int row : selectRows) { @@ -227,14 +194,33 @@ public class DatatablePanel extends JPanel { } // 便于单行复制,去除最后一个换行符 - if (selectData.length() > 0){ + if (!selectData.isEmpty()){ selectData.deleteCharAt(selectData.length() - 1); } return selectData.toString(); } - public JTable getTable() { - return this.table; + public JTable getDataTable() { + return this.dataTable; } -} \ No newline at end of file + + public void setTableListener(MessageTableModel messagePanel) { + dataTable.setDefaultEditor(Object.class, null); + + // 表格内容双击事件 + dataTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + int selectedRow = dataTable.getSelectedRow(); + if (selectedRow != -1) { + String rowData = dataTable.getValueAt(selectedRow, 1).toString(); + messagePanel.applyMessageFilter(tabName, rowData); + } + } + } + }); + } +} + diff --git a/src/main/java/burp/ui/board/LogEntry.java b/src/main/java/hae/component/board/message/MessageEntry.java similarity index 64% rename from src/main/java/burp/ui/board/LogEntry.java rename to src/main/java/hae/component/board/message/MessageEntry.java index b549810..ccdf720 100644 --- a/src/main/java/burp/ui/board/LogEntry.java +++ b/src/main/java/hae/component/board/message/MessageEntry.java @@ -1,19 +1,18 @@ -package burp.ui.board; +package hae.component.board.message; -import burp.IHttpRequestResponsePersisted; -import java.net.URL; +import burp.api.montoya.http.message.HttpRequestResponse; -public class LogEntry { +public class MessageEntry { private final String comment; - private final IHttpRequestResponsePersisted requestResponse; - private final URL url; + private final HttpRequestResponse requestResponse; + private final String url; private final String length; private final String status; private final String color; private final String method; - LogEntry(IHttpRequestResponsePersisted requestResponse, String method, URL url, String comment, String length, String color, String status) { + MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) { this.requestResponse = requestResponse; this.method = method; this.url = url; @@ -27,7 +26,7 @@ public class LogEntry { return this.color; } - public URL getUrl() { + public String getUrl() { return this.url; } @@ -47,7 +46,7 @@ public class LogEntry { return this.status; } - public IHttpRequestResponsePersisted getRequestResponse() { + public HttpRequestResponse getRequestResponse() { return this.requestResponse; } -} +} \ No newline at end of file diff --git a/src/main/java/burp/ui/board/ColorRenderer.java b/src/main/java/hae/component/board/message/MessageRenderer.java similarity index 81% rename from src/main/java/burp/ui/board/ColorRenderer.java rename to src/main/java/hae/component/board/message/MessageRenderer.java index 0f68e61..7cf95fc 100644 --- a/src/main/java/burp/ui/board/ColorRenderer.java +++ b/src/main/java/hae/component/board/message/MessageRenderer.java @@ -1,4 +1,4 @@ -package burp.ui.board; +package hae.component.board.message; import java.awt.Color; import java.awt.Component; @@ -8,13 +8,13 @@ import java.util.Map; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; -public class ColorRenderer extends DefaultTableCellRenderer { +public class MessageRenderer extends DefaultTableCellRenderer { - private List log; + private List log; private Map colorMap = new HashMap<>(); private JTable table; // 保存对表格的引用 - public ColorRenderer(List log, JTable table) { + public MessageRenderer(List log, JTable table) { this.log = log; // 与BurpSuite的颜色保持一致 this.colorMap.put("red", new Color(0xFF, 0x64, 0x64)); @@ -31,13 +31,13 @@ public class ColorRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, - boolean hasFocus, int row, int column) { + boolean hasFocus, int row, int column) { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - LogEntry logEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引 + MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引 // 设置颜色 - String colorByLog = logEntry.getColor(); + String colorByLog = messageEntry.getColor(); Color color = colorMap.get(colorByLog); if (isSelected) { diff --git a/src/main/java/burp/ui/board/MessagePanel.java b/src/main/java/hae/component/board/message/MessageTableModel.java similarity index 54% rename from src/main/java/burp/ui/board/MessagePanel.java rename to src/main/java/hae/component/board/message/MessageTableModel.java index 90b693e..202da24 100644 --- a/src/main/java/burp/ui/board/MessagePanel.java +++ b/src/main/java/hae/component/board/message/MessageTableModel.java @@ -1,28 +1,22 @@ -package burp.ui.board; +package hae.component.board.message; -import burp.IBurpExtenderCallbacks; -import burp.IExtensionHelpers; -import burp.IHttpRequestResponse; -import burp.IHttpRequestResponsePersisted; -import burp.IHttpService; -import burp.IMessageEditor; -import burp.IMessageEditorController; -import burp.IRequestInfo; -import burp.config.ConfigEntry; -import burp.core.GlobalCachePool; -import burp.core.utils.HashCalculator; -import burp.core.utils.StringHelper; +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.core.ByteArray; +import burp.api.montoya.http.message.HttpHeader; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.http.message.responses.HttpResponse; +import burp.api.montoya.ui.UserInterface; +import burp.api.montoya.ui.editor.HttpRequestEditor; +import burp.api.montoya.ui.editor.HttpResponseEditor; +import hae.Config; +import hae.cache.CachePool; +import hae.utils.string.HashCalculator; +import hae.utils.string.StringProcessor; -import java.net.URL; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.*; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; @@ -32,34 +26,37 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; -/** - * @author EvilChen - */ +import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY; -public class MessagePanel extends AbstractTableModel implements IMessageEditorController { - private JSplitPane splitPane; - private IMessageEditor requestViewer; - private IMessageEditor responseViewer; - private final IBurpExtenderCallbacks callbacks; - private final List log = new ArrayList(); - private final List filteredLog = new ArrayList(); - private IHttpRequestResponse currentlyDisplayedItem; - private final IExtensionHelpers helpers; - private final Table logTable; +public class MessageTableModel extends AbstractTableModel { + private final MontoyaApi api; + private final MessageTable messageTable; + private final JTabbedPane messageTab; + private final JSplitPane splitPane; + private final List log = new ArrayList(); + private LinkedList filteredLog; - public MessagePanel(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers) { - this.callbacks = callbacks; - this.helpers = helpers; + public MessageTableModel(MontoyaApi api) { + this.filteredLog = new LinkedList<>(); + this.api = api; - splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + messageTab = new JTabbedPane(); + UserInterface userInterface = api.userInterface(); + HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY); + HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY); + messageTab.addTab("Request", requestViewer.uiComponent()); + messageTab.addTab("Response", responseViewer.uiComponent()); - logTable = new Table(MessagePanel.this); - logTable.setDefaultRenderer(Object.class, new ColorRenderer(filteredLog, logTable)); - logTable.setAutoCreateRowSorter(true); + // 请求条目表格 + messageTable = new MessageTable(MessageTableModel.this, requestViewer, responseViewer); + messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable)); + messageTable.setAutoCreateRowSorter(true); // Length字段根据大小进行排序 - TableRowSorter sorter = (TableRowSorter) logTable.getRowSorter(); + TableRowSorter sorter = (TableRowSorter) messageTable.getRowSorter(); sorter.setComparator(4, new Comparator() { @Override public int compare(String s1, String s2) { @@ -78,151 +75,136 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo return Integer.compare(index1, index2); } private int getIndex(String color) { - for (int i = 0; i < ConfigEntry.colorArray.length; i++) { - if (ConfigEntry.colorArray[i].equals(color)) { + for (int i = 0; i < Config.color.length; i++) { + if (Config.color[i].equals(color)) { return i; } } return -1; } }); + messageTable.setRowSorter(sorter); + messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); - logTable.setRowSorter(sorter); - logTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); - - JScrollPane scrollPane = new JScrollPane(logTable); + splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + // 请求/相应文本框 + JScrollPane scrollPane = new JScrollPane(messageTable); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); splitPane.setLeftComponent(scrollPane); - - JTabbedPane tabs = new JTabbedPane(); - requestViewer = callbacks.createMessageEditor(MessagePanel.this, false); - - responseViewer = callbacks.createMessageEditor(MessagePanel.this, false); - tabs.addTab("Request", requestViewer.getComponent()); - tabs.addTab("Response", responseViewer.getComponent()); - splitPane.setRightComponent(tabs); + splitPane.setRightComponent(messageTab); } - public JSplitPane getPanel() { - return splitPane; - } + public void add(HttpRequestResponse messageInfo, String comment, String color) { + synchronized(log) { + HttpRequest httpRequest = messageInfo.request(); + String url = httpRequest.url(); + String method = httpRequest.method(); - public Table getTable() { - return logTable; - } + HttpResponse httpResponse = messageInfo.response(); + String status = String.valueOf(httpResponse.statusCode()); + String length = String.valueOf(httpResponse.body().length()); - public List getLogs() { - return log; - } + MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status); - @Override - public int getRowCount() - { - return filteredLog.size(); - } + try { + // 比较Hash,如若存在重复的请求或响应,则不放入消息内容里 + byte[] reqByteA = httpRequest.toByteArray().getBytes(); + byte[] resByteA = httpResponse.toByteArray().getBytes(); + boolean isDuplicate = false; - @Override - public int getColumnCount() - { - return 6; - } + if (log.size() > 0) { + for (MessageEntry entry : log) { + HttpRequestResponse reqResMessage = entry.getRequestResponse(); + byte[] reqByteB = reqResMessage.request().toByteArray().getBytes(); + byte[] resByteB = reqResMessage.response().toByteArray().getBytes(); + try { + // 通过URL、请求和响应报文、匹配数据内容,多维度进行对比 + if ((entry.getUrl().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) { + isDuplicate = true; + break; + } + } catch (Exception ignored) { + } + } + } - @Override - public String getColumnName(int columnIndex) - { - switch (columnIndex) - { - case 0: - return "Method"; - case 1: - return "URL"; - case 2: - return "Comment"; - case 3: - return "Status"; - case 4: - return "Length"; - case 5: - return "Color"; - default: - return ""; + if (!isDuplicate) { + log.add(logEntry); + } + } catch (Exception ignored) { + } } + } - @Override - public Class getColumnClass(int columnIndex) - { - return String.class; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) - { - if (filteredLog.isEmpty()) { - return ""; + public void deleteByHost(String filterText) { + filteredLog.clear(); + List rowsToRemove = new ArrayList<>(); + for (int i = 0; i < log.size(); i++) { + MessageEntry entry = log.get(i); + String host = StringProcessor.getHostByUrl(entry.getUrl()); + if (!host.isEmpty()) { + if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) { + rowsToRemove.add(i); + } + } } - LogEntry logEntry = filteredLog.get(rowIndex); - switch (columnIndex) - { - case 0: - return logEntry.getMethod(); - case 1: - return logEntry.getUrl().toString(); - case 2: - return logEntry.getComment(); - case 3: - return logEntry.getStatus(); - case 4: - return logEntry.getLength(); - case 5: - return logEntry.getColor(); - default: - return ""; + + for (int i = rowsToRemove.size() - 1; i >= 0; i--) { + int row = rowsToRemove.get(i); + log.remove(row); + } + + if (!rowsToRemove.isEmpty()) { + int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray(); + fireTableRowsDeleted(rows[0], rows[rows.length - 1]); } } public void applyHostFilter(String filterText) { filteredLog.clear(); fireTableDataChanged(); - String cleanedText = StringHelper.replaceFirstOccurrence(filterText, "*.", ""); + String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", ""); - for (LogEntry entry : log) { - String host = entry.getUrl().getHost(); - if (filterText.contains("*.") && StringHelper.matchFromEnd(host, cleanedText)) { - filteredLog.add(entry); - } else if (host.equals(filterText) || filterText.contains("*")) { - filteredLog.add(entry); + for (MessageEntry entry : log) { + String host = StringProcessor.getHostByUrl(entry.getUrl()); + if (!host.isEmpty()) { + if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) { + filteredLog.add(entry); + } else if (host.equals(filterText) || filterText.contains("*")) { + filteredLog.add(entry); + } } } + fireTableDataChanged(); } public void applyMessageFilter(String tableName, String filterText) { filteredLog.clear(); - for (LogEntry entry : log) { - IHttpRequestResponsePersisted requestResponse = entry.getRequestResponse(); - byte[] requestByte = requestResponse.getRequest(); - byte[] responseByte = requestResponse.getResponse(); + for (MessageEntry entry : log) { + HttpRequestResponse requestResponse = entry.getRequestResponse(); + HttpRequest httpRequest = requestResponse.request(); + HttpResponse httpResponse = requestResponse.response(); - String requestString = new String(requestResponse.getRequest(), StandardCharsets.UTF_8); - String responseString = new String(requestResponse.getResponse(), StandardCharsets.UTF_8); + String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8); + String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); + String requestHeaders = httpRequest.headers().stream() + .map(HttpHeader::toString) + .collect(Collectors.joining("\n")); - List requestTmpHeaders = helpers.analyzeRequest(requestByte).getHeaders(); - String requestHeaders = new String(String.join("\n", requestTmpHeaders).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - int requestBodyOffset = helpers.analyzeRequest(requestByte).getBodyOffset(); - String requestBody = new String(Arrays.copyOfRange(requestByte, requestBodyOffset, requestByte.length), StandardCharsets.UTF_8); - - List responseTmpHeaders = helpers.analyzeResponse(responseByte).getHeaders(); - String responseHeaders = new String(String.join("\n", responseTmpHeaders).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); - int responseBodyOffset = helpers.analyzeResponse(responseByte).getBodyOffset(); - String responseBody = new String(Arrays.copyOfRange(responseByte, responseBodyOffset, responseByte.length), StandardCharsets.UTF_8); + String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8); + String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); + String responseHeaders = httpResponse.headers().stream() + .map(HttpHeader::toString) + .collect(Collectors.joining("\n")); // 标志变量,表示是否满足过滤条件 AtomicBoolean isMatched = new AtomicBoolean(false); - ConfigEntry.globalRules.keySet().forEach(i -> { - for (Object[] objects : ConfigEntry.globalRules.get(i)) { + Config.globalRules.keySet().forEach(i -> { + for (Object[] objects : Config.globalRules.get(i)) { String name = objects[1].toString(); String format = objects[4].toString(); String scope = objects[6].toString(); @@ -276,8 +258,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo filteredLog.add(entry); } } + fireTableDataChanged(); - logTable.lastSelectedIndex = -1; + messageTable.lastSelectedIndex = -1; } private boolean matchingString(String format, String filterText, String target) { @@ -300,92 +283,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo return isMatch; } - public void deleteByHost(String filterText) { - filteredLog.clear(); - List rowsToRemove = new ArrayList<>(); - for (int i = 0; i < log.size(); i++) { - LogEntry entry = log.get(i); - String host = entry.getUrl().getHost(); - if (StringHelper.matchFromEnd(host, filterText) || filterText.contains("*")) { - rowsToRemove.add(i); - } - } - - for (int i = rowsToRemove.size() - 1; i >= 0; i--) { - int row = rowsToRemove.get(i); - log.remove(row); - } - - if (!rowsToRemove.isEmpty()) { - int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray(); - fireTableRowsDeleted(rows[0], rows[rows.length - 1]); - } - } - - @Override - public byte[] getRequest() - { - return currentlyDisplayedItem.getRequest(); - } - - @Override - public byte[] getResponse() - { - return currentlyDisplayedItem.getResponse(); - } - - @Override - public IHttpService getHttpService() - { - return currentlyDisplayedItem.getHttpService(); - } - - public void add(IHttpRequestResponse messageInfo, String comment, String color) { - synchronized(log) { - IRequestInfo iRequestInfo = helpers.analyzeRequest(messageInfo); - URL url = iRequestInfo.getUrl(); - String method = iRequestInfo.getMethod(); - String status = String.valueOf(helpers.analyzeResponse(messageInfo.getResponse()).getStatusCode()); - String length = String.valueOf(messageInfo.getResponse().length); - LogEntry logEntry = new LogEntry(callbacks.saveBuffersToTempFiles(messageInfo), method, url, comment, length, color, status); - - try { - // 比较Hash,如若存在重复的请求或响应,则不放入消息内容里 - byte[] reqByteA = messageInfo.getRequest(); - byte[] resByteA = messageInfo.getResponse(); - boolean isDuplicate = false; - - if (log.size() > 0) { - for (LogEntry entry : log) { - IHttpRequestResponsePersisted reqResMessage = entry.getRequestResponse(); - byte[] reqByteB = reqResMessage.getRequest(); - byte[] resByteB = reqResMessage.getResponse(); - try { - // 通过URL、请求和响应报文、匹配数据内容,多维度进行对比 - if ((entry.getUrl().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) { - isDuplicate = true; - break; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - if (!isDuplicate) { - log.add(logEntry); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - } - - private Map> getCacheData(byte[] content) - throws NoSuchAlgorithmException { + private Map> getCacheData(byte[] content) { String hashIndex = HashCalculator.calculateHash(content); - return GlobalCachePool.getFromCache(hashIndex); + return CachePool.getFromCache(hashIndex); } private boolean areMapsEqual(Map> map1, Map> map2) { @@ -433,15 +333,78 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo return true; } - public class Table extends JTable { - LogEntry logEntry; + + public JSplitPane getSplitPane() + { + return splitPane; + } + + public MessageTable getMessageTable() + { + return messageTable; + } + + public List getLogs() { + return log; + } + + + @Override + public int getRowCount() { + return filteredLog.size(); + } + + @Override + public int getColumnCount() { + return 6; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + if (filteredLog.isEmpty()) { + return ""; + } + MessageEntry messageEntry = filteredLog.get(rowIndex); + + return switch (columnIndex) { + case 0 -> messageEntry.getMethod(); + case 1 -> messageEntry.getUrl(); + case 2 -> messageEntry.getComment(); + case 3 -> messageEntry.getStatus(); + case 4 -> messageEntry.getLength(); + case 5 -> messageEntry.getColor(); + default -> ""; + }; + } + + @Override + public String getColumnName(int columnIndex) + { + return switch (columnIndex) { + case 0 -> "Method"; + case 1 -> "URL"; + case 2 -> "Comment"; + case 3 -> "Status"; + case 4 -> "Length"; + case 5 -> "Color"; + default -> ""; + }; + } + + public class MessageTable extends JTable { + private MessageEntry MessageEntry; private SwingWorker currentWorker; // 设置响应报文返回的最大长度为3MB private final int MAX_LENGTH = 3145728; private int lastSelectedIndex = -1; + private final HttpRequestEditor requestEditor; + private final HttpResponseEditor responseEditor; - public Table(TableModel tableModel) { - super(tableModel); + public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) { + super(messageTableModel); + this.requestEditor = requestEditor; + this.responseEditor = responseEditor; } @Override @@ -450,39 +413,36 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo int selectedIndex = convertRowIndexToModel(row); if (lastSelectedIndex != selectedIndex) { lastSelectedIndex = selectedIndex; - logEntry = filteredLog.get(selectedIndex); + MessageEntry = filteredLog.get(selectedIndex); - requestViewer.setMessage("Loading...".getBytes(), true); - responseViewer.setMessage("Loading...".getBytes(), false); - currentlyDisplayedItem = logEntry.getRequestResponse(); + requestEditor.setRequest(HttpRequest.httpRequest("Loading...")); + responseEditor.setResponse(HttpResponse.httpResponse("Loading...")); if (currentWorker != null && !currentWorker.isDone()) { currentWorker.cancel(true); } - currentWorker = new SwingWorker() { + currentWorker = new SwingWorker<>() { @Override - protected byte[][] doInBackground() throws Exception { - byte[] requestByte = logEntry.getRequestResponse().getRequest(); - byte[] responseByte = logEntry.getRequestResponse().getResponse(); + protected ByteArray[] doInBackground() { + ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray(); + ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray(); - if (responseByte.length > MAX_LENGTH) { + if (responseByte.length() > MAX_LENGTH) { String ellipsis = "\r\n......"; - responseByte = Arrays.copyOf(responseByte, MAX_LENGTH + ellipsis.length()); - byte[] ellipsisBytes = ellipsis.getBytes(); - System.arraycopy(ellipsisBytes, 0, responseByte, MAX_LENGTH, ellipsisBytes.length); + responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis); } - return new byte[][] {requestByte, responseByte}; + return new ByteArray[]{requestByte, responseByte}; } @Override protected void done() { if (!isCancelled()) { try { - byte[][] result = (byte[][]) get(); - requestViewer.setMessage(result[0], true); - responseViewer.setMessage(result[1], false); + ByteArray[] result = (ByteArray[]) get(); + requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0])); + responseEditor.setResponse(HttpResponse.httpResponse(result[1])); } catch (Exception e) { e.printStackTrace(); } @@ -493,5 +453,4 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo } } } - -} \ No newline at end of file +} diff --git a/src/main/java/hae/component/config/Config.java b/src/main/java/hae/component/config/Config.java new file mode 100644 index 0000000..2459729 --- /dev/null +++ b/src/main/java/hae/component/config/Config.java @@ -0,0 +1,89 @@ +package hae.component.config; + +import burp.api.montoya.MontoyaApi; +import hae.component.rule.Rules; +import hae.utils.config.ConfigLoader; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; + +public class Config extends JPanel { + private final MontoyaApi api; + private final ConfigLoader configLoader; + private final Rules rules; + + public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) { + this.api = api; + this.configLoader = configLoader; + this.rules = rules; + + initComponents(); + } + + private void initComponents() { + setLayout(new GridBagLayout()); + ((GridBagLayout) getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0}; + ((GridBagLayout) getLayout()).rowHeights = new int[] {0, 0, 0}; + ((GridBagLayout) getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4}; + ((GridBagLayout) getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4}; + + JLabel rulesFilePathLabel = new JLabel("Rules Path:"); + JTextField rulesFilePathTextField = new JTextField(); + JButton onlineUpdateButton = new JButton("Update"); + JLabel excludeSuffixLabel = new JLabel("Exclude Suffix:"); + JTextField excludeSuffixTextField = new JTextField(); + JButton excludeSuffixSaveButton = new JButton("Save"); + JButton reloadButton = new JButton("Reload"); + + rulesFilePathTextField.setEditable(false); + + onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed); + excludeSuffixSaveButton.addActionListener(e -> excludeSuffixSaveActionPerformed(e, excludeSuffixTextField.getText())); + reloadButton.addActionListener(this::reloadActionPerformed); + + rulesFilePathTextField.setText(configLoader.getRulesFilePath()); + excludeSuffixTextField.setText(configLoader.getExcludeSuffix()); + + add(rulesFilePathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(5, 0, 5, 5), 0, 0)); + add(rulesFilePathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.VERTICAL, + new Insets(5, 5, 5, 5), 0, 0)); + add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(5, 0, 5, 5), 0, 0)); + add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(5, 0, 5, 5), 0, 0)); + add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, + new Insets(0, 5, 5, 5), 0, 0)); + add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 5), 0, 0)); + add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 5), 0, 0)); + } + + private void onlineUpdateActionPerformed(ActionEvent e) { + // 添加提示框防止用户误触导致配置更新 + int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION); + if (retCode == JOptionPane.YES_OPTION) { + configLoader.initRules(); + reloadActionPerformed(null); + } + } + + private void excludeSuffixSaveActionPerformed(ActionEvent e, String suffix) { + if (!suffix.equals(configLoader.getExcludeSuffix()) && !suffix.isEmpty()) { + configLoader.setExcludeSuffix(suffix); + } + } + + private void reloadActionPerformed(ActionEvent e) { + rules.reloadRuleGroup(); + } +} diff --git a/src/main/java/burp/ui/rule/RuleSetting.java b/src/main/java/hae/component/rule/Display.java similarity index 82% rename from src/main/java/burp/ui/rule/RuleSetting.java rename to src/main/java/hae/component/rule/Display.java index f647f20..8e0293d 100644 --- a/src/main/java/burp/ui/rule/RuleSetting.java +++ b/src/main/java/hae/component/rule/Display.java @@ -1,82 +1,79 @@ -package burp.ui.rule; - -import java.awt.*; -import javax.swing.*; -import burp.config.ConfigEntry; - -/** - * @author LinChen & EvilChen - */ - -public class RuleSetting extends JPanel { - public JTextField firstRegexTextField; - public JTextField secondRegexTextField; - public JTextField formatTextField; - public JTextField ruleNameTextField; - public JComboBox scopeComboBox; - public JComboBox engineComboBox; - public JComboBox colorComboBox; - public JComboBox sensitiveComboBox; - - public RuleSetting() { - initComponents(); - } - - private void initComponents() { - setLayout(new GridBagLayout()); - GridBagConstraints c = new GridBagConstraints(); - c.fill = GridBagConstraints.BOTH; - - addLabel("Name:", 0, c); - ruleNameTextField = addTextField(0, c); - - addLabel("F-Regex:", 1, c); - firstRegexTextField = addTextField(1, c); - - addLabel("S-Regex:", 2, c); - secondRegexTextField = addTextField(2, c); - - addLabel("Format:", 3, c); - formatTextField = addTextField(3, c); - - addLabel("Scope:", 4, c); - scopeComboBox = addComboBox(ConfigEntry.scopeArray, 4, c); - - addLabel("Engine:", 5, c); - engineComboBox = addComboBox(ConfigEntry.engineArray, 5, c); - engineComboBox.addActionListener(e -> { - boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString()); - formatTextField.setEnabled(isNfa); - formatTextField.setText(isNfa ? formatTextField.getText() : "{0}"); - }); - - addLabel("Color:", 6, c); - colorComboBox = addComboBox(ConfigEntry.colorArray, 6, c); - - addLabel("Sensitive:", 7, c); - sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c); - } - - private void addLabel(String text, int y, GridBagConstraints c) { - JLabel label = new JLabel(text); - c.gridx = 0; - c.gridy = y; - add(label, c); - } - - private JTextField addTextField(int y, GridBagConstraints c) { - JTextField textField = new JTextField(35); - c.gridx = 1; - c.gridy = y; - add(textField, c); - return textField; - } - - private JComboBox addComboBox(T[] items, int y, GridBagConstraints c) { - JComboBox comboBox = new JComboBox<>(items); - c.gridx = 1; - c.gridy = y; - add(comboBox, c); - return comboBox; - } -} +package hae.component.rule; + +import hae.Config; + +import javax.swing.*; +import java.awt.*; + +public class Display extends JPanel { + public JTextField firstRegexTextField; + public JTextField secondRegexTextField; + public JTextField formatTextField; + public JTextField ruleNameTextField; + public JComboBox scopeComboBox; + public JComboBox engineComboBox; + public JComboBox colorComboBox; + public JComboBox sensitiveComboBox; + + public Display() { + initComponents(); + } + + private void initComponents() { + setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + + addLabel("Name:", 0, c); + ruleNameTextField = addTextField(0, c); + + addLabel("F-Regex:", 1, c); + firstRegexTextField = addTextField(1, c); + + addLabel("S-Regex:", 2, c); + secondRegexTextField = addTextField(2, c); + + addLabel("Format:", 3, c); + formatTextField = addTextField(3, c); + + addLabel("Scope:", 4, c); + scopeComboBox = addComboBox(Config.scope, 4, c); + + addLabel("Engine:", 5, c); + engineComboBox = addComboBox(Config.engine, 5, c); + engineComboBox.addActionListener(e -> { + boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString()); + formatTextField.setEnabled(isNfa); + formatTextField.setText(isNfa ? formatTextField.getText() : "{0}"); + }); + + addLabel("Color:", 6, c); + colorComboBox = addComboBox(Config.color, 6, c); + + addLabel("Sensitive:", 7, c); + sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c); + } + + private void addLabel(String text, int y, GridBagConstraints c) { + JLabel label = new JLabel(text); + c.gridx = 0; + c.gridy = y; + add(label, c); + } + + private JTextField addTextField(int y, GridBagConstraints c) { + JTextField textField = new JTextField(35); + c.gridx = 1; + c.gridy = y; + add(textField, c); + return textField; + } + + private JComboBox addComboBox(T[] items, int y, GridBagConstraints c) { + JComboBox comboBox = new JComboBox<>(items); + c.gridx = 1; + c.gridy = y; + add(comboBox, c); + return comboBox; + } +} diff --git a/src/main/java/hae/component/rule/Rule.java b/src/main/java/hae/component/rule/Rule.java new file mode 100644 index 0000000..9b17f6d --- /dev/null +++ b/src/main/java/hae/component/rule/Rule.java @@ -0,0 +1,164 @@ +package hae.component.rule; + +import burp.api.montoya.MontoyaApi; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import javax.swing.table.TableRowSorter; +import java.util.Vector; + +import hae.Config; +import hae.utils.config.ConfigLoader; +import hae.utils.rule.RuleProcessor; + +import static javax.swing.JOptionPane.YES_OPTION; + +public class Rule extends JPanel { + private final MontoyaApi api; + private final ConfigLoader configLoader; + private final RuleProcessor ruleProcessor; + private final JTabbedPane tabbedPane; + + public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) { + this.api = api; + this.configLoader = configLoader; + this.ruleProcessor = new RuleProcessor(api, configLoader); + this.tabbedPane = tabbedPane; + + initComponents(data); + } + + private void initComponents(Object[][] data) { + setLayout(new GridBagLayout()); + ((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0}; + ((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0}; + ((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 1.0, 1.0E-4}; + ((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 1.0E-4}; + + JButton addButton = new JButton("Add"); + JButton editButton = new JButton("Edit"); + JButton removeButton = new JButton("Remove"); + + JTable ruleTable = new JTable(); + JScrollPane scrollPane = new JScrollPane(); + + ruleTable.setShowVerticalLines(false); + ruleTable.setShowHorizontalLines(false); + ruleTable.setVerifyInputWhenFocusTarget(false); + ruleTable.setUpdateSelectionOnSort(false); + ruleTable.setSurrendersFocusOnKeystroke(true); + scrollPane.setViewportView(ruleTable); + + // 按钮监听事件 + addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane)); + editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane)); + removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane)); + + // 表格 + DefaultTableModel model = new DefaultTableModel() { + @Override + public Class getColumnClass(int column) { + return (column == 0) ? Boolean.class : String.class; + } + + @Override + public boolean isCellEditable(int row, int column) { + return column == 0; + } + }; + + ruleTable.setModel(model); + ruleTable.setRowSorter(new TableRowSorter<>(model)); + + model.setDataVector(data, Config.ruleFields); + model.addTableModelListener(e -> { + if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1){ + int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); + ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())); + } + }); + + add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(15, 5, 3, 2), 0, 0)); + add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 5, 3, 2), 0, 0)); + add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 5, 3, 2), 0, 0)); + add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(15, 5, 5, 5), 0, 0)); + } + + private void ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) { + Display ruleDisplay = new Display(); + ruleDisplay.formatTextField.setText("{0}"); + + int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION); + if (showState == YES_OPTION) { + Vector ruleData = new Vector<>(); + ruleData.add(false); + ruleData.add(ruleDisplay.ruleNameTextField.getText()); + ruleData.add(ruleDisplay.firstRegexTextField.getText()); + ruleData.add(ruleDisplay.secondRegexTextField.getText()); + ruleData.add(ruleDisplay.formatTextField.getText()); + ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString()); + ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString()); + ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString()); + ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem()); + + DefaultTableModel model = (DefaultTableModel) ruleTable.getModel(); + model.insertRow(model.getRowCount(), ruleData); + ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())); + } + } + + private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){ + if (ruleTable.getSelectedRowCount() >= 1){ + DefaultTableModel model = (DefaultTableModel) ruleTable.getModel(); + Display ruleDisplay = new Display(); + + ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString()); + ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString()); + ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString()); + ruleDisplay.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString()); + ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString()); + ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString()); + ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString()); + ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(),8)); + + ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa")); + + int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION); + if (showState == 0){ + int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); + model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1); + model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2); + model.setValueAt(ruleDisplay.secondRegexTextField.getText(), select, 3); + model.setValueAt(ruleDisplay.formatTextField.getText(), select, 4); + model.setValueAt(ruleDisplay.colorComboBox.getSelectedItem().toString(), select, 5); + model.setValueAt(ruleDisplay.scopeComboBox.getSelectedItem().toString(), select, 6); + model.setValueAt(ruleDisplay.engineComboBox.getSelectedItem().toString(), select, 7); + model.setValueAt(ruleDisplay.sensitiveComboBox.getSelectedItem(), select, 8); + model = (DefaultTableModel) ruleTable.getModel(); + ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())); + } + } + } + + private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){ + if (ruleTable.getSelectedRowCount() >= 1){ + if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0){ + DefaultTableModel model = (DefaultTableModel) ruleTable.getModel(); + int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); + + model.removeRow(select); + ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/hae/component/rule/Rules.java b/src/main/java/hae/component/rule/Rules.java new file mode 100644 index 0000000..469f7f1 --- /dev/null +++ b/src/main/java/hae/component/rule/Rules.java @@ -0,0 +1,156 @@ +package hae.component.rule; + +import burp.api.montoya.MontoyaApi; +import hae.Config; +import hae.utils.config.ConfigLoader; +import hae.utils.rule.RuleProcessor; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class Rules extends JTabbedPane { + private final MontoyaApi api; + private final ConfigLoader configLoader; + private final RuleProcessor ruleProcessor; + private final JTextField ruleGroupNameTextField; + + private Component tabComponent; + private int selectedIndex; + + public Rules(MontoyaApi api, ConfigLoader configLoader) { + this.api = api; + this.configLoader = configLoader; + this.ruleProcessor = new RuleProcessor(api, configLoader); + this.ruleGroupNameTextField = new JTextField(); + + initComponents(); + } + + private void initComponents() { + reloadRuleGroup(); + + JTabbedPane tabbedPane = this; + + JMenuItem deleteMenuItem = new JMenuItem("Delete"); + JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.add(deleteMenuItem); + + deleteMenuItem.addActionListener(this::deleteRuleGroupActionPerformed); + + ruleGroupNameTextField.setBorder(BorderFactory.createEmptyBorder()); + ruleGroupNameTextField.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + renameTitleActionPerformed.actionPerformed(null); + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int index = getSelectedIndex(); + Rectangle r = getBoundsAt(index); + if (r.contains(e.getPoint()) && index >= 0) { + switch (e.getButton()) { + case MouseEvent.BUTTON1: + if (e.getClickCount() == 2) { + selectedIndex = index; + tabComponent = getTabComponentAt(selectedIndex); + String ruleGroupName = getTitleAt(selectedIndex); + + if (!"...".equals(ruleGroupName)) { + setTabComponentAt(selectedIndex, ruleGroupNameTextField); + ruleGroupNameTextField.setVisible(true); + ruleGroupNameTextField.setText(ruleGroupName); + ruleGroupNameTextField.selectAll(); + ruleGroupNameTextField.requestFocusInWindow(); + ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize()); + } + } else if (e.getClickCount() == 1) { + if ("...".equals(getTitleAt(getSelectedIndex()))) { + String title = ruleProcessor.newRule(); + Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane); + insertTab(title, null, newRule, null, getTabCount() - 1); + setSelectedIndex(getTabCount() - 2); + } else { + renameTitleActionPerformed.actionPerformed(null); + } + } + break; + case MouseEvent.BUTTON3: + if (!"...".equals(getTitleAt(getSelectedIndex()))) { + popupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + break; + default: + break; + } + } + } + }); + + + InputMap im = ruleGroupNameTextField.getInputMap(JComponent.WHEN_FOCUSED); + ActionMap am = ruleGroupNameTextField.getActionMap(); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); + am.put("cancel", cancelActionPerformed); + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename"); + am.put("rename", renameTitleActionPerformed); + } + + public void reloadRuleGroup() { + removeAll(); + Config.globalRules.keySet().forEach(i-> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this))); + addTab("...", null); + } + + private void deleteRuleGroupActionPerformed(ActionEvent e) { + if (getTabCount() > 2) { + int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info", + JOptionPane.YES_NO_OPTION); + if (retCode == JOptionPane.YES_OPTION) { + String title = getTitleAt(getSelectedIndex()); + ruleProcessor.deleteRuleGroup(title); + remove(getSelectedIndex()); + setSelectedIndex(getSelectedIndex() - 1); + } + } + } + + private Action renameTitleActionPerformed = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + String title = ruleGroupNameTextField.getText(); + if (!title.isEmpty() && selectedIndex >= 0) { + String oldName = getTitleAt(selectedIndex); + setTitleAt(selectedIndex, title); + + if (!oldName.equals(title)) { + ruleProcessor.renameRuleGroup(oldName, title); + } + } + cancelActionPerformed.actionPerformed(null); + } + }; + + private Action cancelActionPerformed = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + if (selectedIndex >= 0) { + setTabComponentAt(selectedIndex, tabComponent); + + ruleGroupNameTextField.setVisible(false); + ruleGroupNameTextField.setPreferredSize(null); + selectedIndex = -1; + tabComponent = null; + + requestFocusInWindow(); + } + } + }; +} + + + + diff --git a/src/main/java/hae/instances/editor/RequestEditor.java b/src/main/java/hae/instances/editor/RequestEditor.java new file mode 100644 index 0000000..fc0bd5d --- /dev/null +++ b/src/main/java/hae/instances/editor/RequestEditor.java @@ -0,0 +1,115 @@ +package hae.instances.editor; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.ui.editor.extension.EditorCreationContext; +import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor; +import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider; +import burp.api.montoya.core.ByteArray; +import burp.api.montoya.core.Range; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.ui.Selection; +import hae.component.board.Datatable; +import hae.instances.http.utils.MessageProcessor; + +import javax.swing.*; +import java.awt.*; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class RequestEditor implements HttpRequestEditorProvider { + private final MontoyaApi api; + + public RequestEditor(MontoyaApi api) { + this.api = api; + } + + @Override + public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) { + return new Editor(api, editorCreationContext); + } + + private static class Editor implements ExtensionProvidedHttpRequestEditor { + private final MontoyaApi api; + private final EditorCreationContext creationContext; + private final MessageProcessor messageProcessor; + private HttpRequestResponse requestResponse; + + private JTabbedPane jTabbedPane = new JTabbedPane(); + + public Editor(MontoyaApi api, EditorCreationContext creationContext) + { + this.api = api; + this.creationContext = creationContext; + this.messageProcessor = new MessageProcessor(api); + } + + @Override + public HttpRequest getRequest() { + return requestResponse.request(); + } + + @Override + public void setRequestResponse(HttpRequestResponse requestResponse) { + this.requestResponse = requestResponse; + } + + @Override + public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) { + HttpRequest request = requestResponse.request(); + if (request != null && !request.bodyToString().equals("Loading...")) { + List> result = messageProcessor.processRequest("", request, false); + jTabbedPane = generateTabbedPaneFromResultMap(api, result); + return jTabbedPane.getTabCount() > 0; + } + return false; + } + + @Override + public String caption() { + return "MarkInfo"; + } + + @Override + public Component uiComponent() { + return jTabbedPane; + } + + @Override + public Selection selectedData() { + return new Selection() { + @Override + public ByteArray contents() { + return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); + } + + @Override + public Range offsets() { + return null; + } + }; + } + + @Override + public boolean isModified() { + return false; + } + } + + public static JTabbedPane generateTabbedPaneFromResultMap(MontoyaApi api, List> result) { + JTabbedPane tabbedPane = new JTabbedPane(); + if (result != null && !result.isEmpty() && result.size() > 0) { + Map dataMap = result.get(0); + if (dataMap != null && !dataMap.isEmpty() && dataMap.size() > 0) { + dataMap.keySet().forEach(i->{ + String[] extractData = dataMap.get(i).split("\n"); + Datatable dataPanel = new Datatable(api, i, Arrays.asList(extractData)); + tabbedPane.addTab(i, dataPanel); + }); + } + } + + return tabbedPane; + } +} diff --git a/src/main/java/hae/instances/editor/ResponseEditor.java b/src/main/java/hae/instances/editor/ResponseEditor.java new file mode 100644 index 0000000..dbd2e98 --- /dev/null +++ b/src/main/java/hae/instances/editor/ResponseEditor.java @@ -0,0 +1,98 @@ +package hae.instances.editor; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.responses.HttpResponse; +import burp.api.montoya.ui.editor.extension.EditorCreationContext; +import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; +import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider; +import burp.api.montoya.core.ByteArray; +import burp.api.montoya.core.Range; +import burp.api.montoya.ui.Selection; +import hae.component.board.Datatable; +import hae.instances.http.utils.MessageProcessor; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.Map; + +public class ResponseEditor implements HttpResponseEditorProvider { + private final MontoyaApi api; + + public ResponseEditor(MontoyaApi api) { + this.api = api; + } + + @Override + public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext editorCreationContext) { + return new Editor(api, editorCreationContext); + } + + private static class Editor implements ExtensionProvidedHttpResponseEditor { + private final MontoyaApi api; + private final EditorCreationContext creationContext; + private final MessageProcessor messageProcessor; + private HttpRequestResponse requestResponse; + + private JTabbedPane jTabbedPane = new JTabbedPane(); + + public Editor(MontoyaApi api, EditorCreationContext creationContext) + { + this.api = api; + this.creationContext = creationContext; + this.messageProcessor = new MessageProcessor(api); + } + + @Override + public HttpResponse getResponse() { + return requestResponse.response(); + } + + @Override + public void setRequestResponse(HttpRequestResponse requestResponse) { + this.requestResponse = requestResponse; + } + + @Override + public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) { + HttpResponse request = requestResponse.response(); + if (request != null && !request.bodyToString().equals("Loading...")) { + List> result = messageProcessor.processResponse("", request, false); + jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result); + return jTabbedPane.getTabCount() > 0; + } + return false; + } + + @Override + public String caption() { + return "MarkInfo"; + } + + @Override + public Component uiComponent() { + return jTabbedPane; + } + + @Override + public Selection selectedData() { + return new Selection() { + @Override + public ByteArray contents() { + return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); + } + + @Override + public Range offsets() { + return null; + } + }; + } + + @Override + public boolean isModified() { + return false; + } + } +} diff --git a/src/main/java/hae/instances/editor/WebSocketEditor.java b/src/main/java/hae/instances/editor/WebSocketEditor.java new file mode 100644 index 0000000..e8a384a --- /dev/null +++ b/src/main/java/hae/instances/editor/WebSocketEditor.java @@ -0,0 +1,94 @@ +package hae.instances.editor; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.core.ByteArray; +import burp.api.montoya.core.Range; +import burp.api.montoya.ui.Selection; +import burp.api.montoya.ui.contextmenu.WebSocketMessage; +import burp.api.montoya.ui.editor.extension.*; +import hae.component.board.Datatable; +import hae.instances.http.utils.MessageProcessor; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.Map; + +public class WebSocketEditor implements WebSocketMessageEditorProvider { + private final MontoyaApi api; + + public WebSocketEditor(MontoyaApi api) { + this.api = api; + } + + @Override + public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) { + return new Editor(api, editorCreationContext); + } + + private static class Editor implements ExtensionProvidedWebSocketMessageEditor { + private final MontoyaApi api; + private final EditorCreationContext creationContext; + private final MessageProcessor messageProcessor; + private ByteArray message; + + private JTabbedPane jTabbedPane = new JTabbedPane(); + + public Editor(MontoyaApi api, EditorCreationContext creationContext) { + this.api = api; + this.creationContext = creationContext; + this.messageProcessor = new MessageProcessor(api); + } + + @Override + public ByteArray getMessage() { + return message; + } + + @Override + public void setMessage(WebSocketMessage webSocketMessage) { + this.message = webSocketMessage.payload(); + } + + @Override + public boolean isEnabledFor(WebSocketMessage webSocketMessage) { + String websocketMessage = webSocketMessage.payload().toString(); + if (!websocketMessage.isEmpty()) { + List> result = messageProcessor.processMessage("", websocketMessage, false); + jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result); + return jTabbedPane.getTabCount() > 0; + } + return false; + } + + @Override + public String caption() { + return "MarkInfo"; + } + + @Override + public Component uiComponent() { + return jTabbedPane; + } + + @Override + public Selection selectedData() { + return new Selection() { + @Override + public ByteArray contents() { + return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); + } + + @Override + public Range offsets() { + return null; + } + }; + } + + @Override + public boolean isModified() { + return false; + } + } +} diff --git a/src/main/java/hae/instances/http/HttpMessageHandler.java b/src/main/java/hae/instances/http/HttpMessageHandler.java new file mode 100644 index 0000000..22a3153 --- /dev/null +++ b/src/main/java/hae/instances/http/HttpMessageHandler.java @@ -0,0 +1,87 @@ +package hae.instances.http; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.core.Annotations; +import burp.api.montoya.core.HighlightColor; +import burp.api.montoya.http.handler.*; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import hae.Config; +import hae.component.board.message.MessageTableModel; +import hae.instances.http.utils.MessageProcessor; +import hae.utils.string.StringProcessor; + +import java.util.*; + +public class HttpMessageHandler implements HttpHandler { + private final MontoyaApi api; + private MessageTableModel messageTableModel; + private final MessageProcessor messageProcessor; + private String host; + + // Montoya API对HTTP消息的处理分为了请求和响应,因此此处设置高亮和标记需要使用全局变量的方式,以此兼顾请求和响应 + // 同时采用 ThreadLocal 来保证多线程并发的情况下全局变量的安全性 + private final ThreadLocal> colorList = ThreadLocal.withInitial(ArrayList::new); + private final ThreadLocal> commentList = ThreadLocal.withInitial(ArrayList::new); + private final ThreadLocal matches = ThreadLocal.withInitial(() -> false); + private final ThreadLocal httpRequest = new ThreadLocal<>(); + + public HttpMessageHandler(MontoyaApi api, MessageTableModel messageTableModel) { + this.api = api; + this.messageTableModel = messageTableModel; + this.messageProcessor = new MessageProcessor(api); + } + + @Override + public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) { + colorList.get().clear(); + commentList.get().clear(); + + Annotations annotations = httpRequestToBeSent.annotations(); + + httpRequest.set(httpRequestToBeSent); + + host = StringProcessor.getHostByUrl(httpRequestToBeSent.url()); + + List suffixList = Arrays.asList(Config.suffix.split("\\|")); + matches.set(suffixList.contains(httpRequestToBeSent.fileExtension())); + + if (!matches.get()) { + List> result = messageProcessor.processRequest(host, httpRequestToBeSent, true); + setColorAndCommentList(result); + } + + return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations); + } + + @Override + public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) { + Annotations annotations = httpResponseReceived.annotations(); + + if (!matches.get()) { + List> result = messageProcessor.processResponse(host, httpResponseReceived, true); + setColorAndCommentList(result); + // 设置高亮颜色和注释 + if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) { + String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get())); + annotations.setHighlightColor(HighlightColor.highlightColor(color)); + String comment = StringProcessor.mergeComment(String.join(", ", commentList.get())); + annotations.setNotes(comment); + + HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived); + + // 添加到Databoard + messageTableModel.add(httpRequestResponse, comment, color); + } + } + + return ResponseReceivedAction.continueWith(httpResponseReceived, annotations); + } + + private void setColorAndCommentList(List> result) { + if (result != null && !result.isEmpty() && result.size() > 0) { + colorList.get().add(result.get(0).get("color")); + commentList.get().add(result.get(1).get("comment")); + } + } +} diff --git a/src/main/java/hae/instances/http/utils/MessageProcessor.java b/src/main/java/hae/instances/http/utils/MessageProcessor.java new file mode 100644 index 0000000..0cfe727 --- /dev/null +++ b/src/main/java/hae/instances/http/utils/MessageProcessor.java @@ -0,0 +1,172 @@ +package hae.instances.http.utils; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.http.message.HttpHeader; +import burp.api.montoya.http.message.requests.HttpRequest; +import burp.api.montoya.http.message.responses.HttpResponse; +import hae.Config; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + + +public class MessageProcessor { + private final MontoyaApi api; + private final RegularMatcher regularMatcher; + + private String finalColor = ""; + + public MessageProcessor(MontoyaApi api) { + this.api = api; + this.regularMatcher = new RegularMatcher(api); + } + + public List> processMessage(String host, String message, boolean flag) { + Map> obj = null; + try { + obj = regularMatcher.match(host, "any", message, message, message); + } catch (Exception ignored) { + } + + return getDataList(obj, flag); + } + + public List> processResponse(String host, HttpResponse httpResponse, boolean flag) { + Map> obj = null; + try { + String response = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8); + String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); + String header = httpResponse.headers().stream() + .map(HttpHeader::toString) + .collect(Collectors.joining("\n")); + + obj = regularMatcher.match(host, "response", response, header, body); + } catch (Exception ignored) { + } + + return getDataList(obj, flag); + } + + public List> processRequest(String host, HttpRequest httpRequest, boolean flag) { + Map> obj = null; + + try { + String request = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8); + String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); + String header = httpRequest.headers().stream() + .map(HttpHeader::toString) + .collect(Collectors.joining("\n")); + obj = regularMatcher.match(host, "request", request, header, body); + } catch (Exception ignored) { + } + + return getDataList(obj, flag); + } + + private List> getDataList(Map> obj, boolean actionFlag) { + List> highlightList = new ArrayList<>(); + List> extractList = new ArrayList<>(); + + if (obj != null && !obj.isEmpty() && obj.size() > 0) { + if (actionFlag) { + List> resultList = extractColorsAndComments(obj); + List colorList = resultList.get(0); + List commentList = resultList.get(1); + if (!colorList.isEmpty() && !commentList.isEmpty()) { + String color = retrieveFinalColor(retrieveColorIndices(colorList)); + Map colorMap = new HashMap() {{ + put("color", color); + }}; + Map commentMap = new HashMap() {{ + put("comment", String.join(", ", commentList)); + }}; + highlightList.add(colorMap); + highlightList.add(commentMap); + } + } else { + extractList.add(extractDataFromMap(obj)); + } + } + + return actionFlag ? highlightList : extractList; + } + + private Map extractDataFromMap(Map> inputData) { + Map extractedData = new HashMap<>(); + inputData.keySet().forEach(key -> { + Map tempMap = inputData.get(key); + String data = tempMap.get("data").toString(); + extractedData.put(key, data); + }); + return extractedData; + } + + private List> extractColorsAndComments(Map> inputData) { + List colorList = new ArrayList<>(); + List commentList = new ArrayList<>(); + inputData.keySet().forEach(key -> { + Map tempMap = inputData.get(key); + String color = tempMap.get("color").toString(); + colorList.add(color); + commentList.add(key); + }); + List> result = new ArrayList<>(); + result.add(colorList); + result.add(commentList); + return result; + } + + public List retrieveColorIndices(List colors){ + List indices = new ArrayList<>(); + String[] colorArray = Config.color; + int size = colorArray.length; + + for (String color : colors) { + for (int i = 0; i < size; i++) { + if (colorArray[i].equals(color)) { + indices.add(i); + } + } + } + return indices; + } + + private void upgradeColors(List colorList) { + int colorSize = colorList.size(); + String[] colorArray = Config.color; + colorList.sort(Comparator.comparingInt(Integer::intValue)); + int i = 0; + List stack = new ArrayList<>(); + while (i < colorSize) { + if (stack.isEmpty()) { + stack.add(colorList.get(i)); + } else { + if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) { + stack.add(colorList.get(i)); + } else { + stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1); + } + } + i++; + } + // 利用HashSet删除重复元素 + HashSet tmpList = new HashSet(stack); + if (stack.size() == tmpList.size()) { + stack.sort(Comparator.comparingInt(Integer::intValue)); + if(stack.get(0) < 0) { + finalColor = colorArray[0]; + } else { + finalColor = colorArray[stack.get(0)]; + } + } else { + upgradeColors(stack); + } + } + + public String retrieveFinalColor(List colorList) { + upgradeColors(colorList); + return finalColor; + } + +} diff --git a/src/main/java/burp/core/processor/DataProcessingUnit.java b/src/main/java/hae/instances/http/utils/RegularMatcher.java similarity index 70% rename from src/main/java/burp/core/processor/DataProcessingUnit.java rename to src/main/java/hae/instances/http/utils/RegularMatcher.java index 67bc2ef..da2993c 100644 --- a/src/main/java/burp/core/processor/DataProcessingUnit.java +++ b/src/main/java/hae/instances/http/utils/RegularMatcher.java @@ -1,66 +1,40 @@ -package burp.core.processor; +package hae.instances.http.utils; -import burp.BurpExtender; -import burp.core.GlobalCachePool; -import burp.core.utils.HashCalculator; -import burp.core.utils.MatchTool; -import burp.config.ConfigEntry; -import burp.core.utils.StringHelper; +import burp.api.montoya.MontoyaApi; import dk.brics.automaton.Automaton; import dk.brics.automaton.AutomatonMatcher; import dk.brics.automaton.RegExp; import dk.brics.automaton.RunAutomaton; -import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.text.MessageFormat; -import java.util.*; - -import java.util.concurrent.ConcurrentHashMap; +import hae.Config; +import hae.cache.CachePool; +import hae.utils.string.HashCalculator; +import hae.utils.string.StringProcessor; import jregex.Matcher; import jregex.Pattern; -/** - * @author EvilChen - */ +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class RegularMatcher { + private final MontoyaApi api; + + public RegularMatcher(MontoyaApi api) { + this.api = api; -public class DataProcessingUnit { - public Map extractDataFromMap(Map> inputData) { - Map extractedData = new HashMap<>(); - inputData.keySet().forEach(key -> { - Map tempMap = inputData.get(key); - String data = tempMap.get("data").toString(); - extractedData.put(key, data); - }); - return extractedData; } - public List> extractColorsAndComments(Map> inputData) { - List colorList = new ArrayList<>(); - List commentList = new ArrayList<>(); - inputData.keySet().forEach(key -> { - Map tempMap = inputData.get(key); - String color = tempMap.get("color").toString(); - colorList.add(color); - commentList.add(key); - }); - List> result = new ArrayList<>(); - result.add(colorList); - result.add(commentList); - return result; - } - - public Map> matchContentByRegex(byte[] content, String headers, byte[] body, String scopeString, String host) - throws NoSuchAlgorithmException { + public Map> match(String host, String type, String message, String header, String body) { // 先从缓存池里判断是否有已经匹配好的结果 - String messageIndex = HashCalculator.calculateHash(content); - Map> map = GlobalCachePool.getFromCache(messageIndex); + String messageIndex = HashCalculator.calculateHash(message.getBytes()); + Map> map = CachePool.getFromCache(messageIndex); if (map != null) { return map; } else { // 最终返回的结果 Map> finalMap = new HashMap<>(); - ConfigEntry.globalRules.keySet().parallelStream().forEach(i -> { - for (Object[] objects : ConfigEntry.globalRules.get(i)) { + Config.globalRules.keySet().parallelStream().forEach(i -> { + for (Object[] objects : Config.globalRules.get(i)) { // 多线程执行,一定程度上减少阻塞现象 String matchContent = ""; // 遍历获取规则 @@ -78,22 +52,22 @@ public class DataProcessingUnit { boolean sensitive = (Boolean) objects[8]; // 判断规则是否开启与作用域 - if (loaded && (scope.contains(scopeString) || scope.contains("any"))) { + if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) { switch (scope) { case "any": case "request": case "response": - matchContent = new String(content, StandardCharsets.UTF_8); + matchContent = message; break; case "any header": case "request header": case "response header": - matchContent = headers; + matchContent = header; break; case "any body": case "request body": case "response body": - matchContent = new String(body, StandardCharsets.UTF_8); + matchContent = body; break; default: break; @@ -102,8 +76,7 @@ public class DataProcessingUnit { try { result.addAll(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive)); } catch (Exception e) { - BurpExtender.stdout.println(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); - e.printStackTrace(); + api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); continue; } @@ -121,8 +94,8 @@ public class DataProcessingUnit { // 添加到全局变量中,便于Databoard检索 if (!Objects.equals(host, "") && host != null) { List dataList = Arrays.asList(dataStr.split("\n")); - if (ConfigEntry.globalDataMap.containsKey(host)) { - ConcurrentHashMap> gRuleMap = new ConcurrentHashMap<>(ConfigEntry.globalDataMap.get(host)); + if (Config.globalDataMap.containsKey(host)) { + ConcurrentHashMap> gRuleMap = new ConcurrentHashMap<>(Config.globalDataMap.get(host)); if (gRuleMap.containsKey(name)) { // gDataList为不可变列表,因此需要重新创建一个列表以便于使用addAll方法 List gDataList = gRuleMap.get(name); @@ -134,39 +107,35 @@ public class DataProcessingUnit { } else { gRuleMap.put(name, dataList); } - ConfigEntry.globalDataMap.remove(host); - ConfigEntry.globalDataMap.put(host, gRuleMap); + Config.globalDataMap.remove(host); + Config.globalDataMap.put(host, gRuleMap); } else { Map> ruleMap = new HashMap<>(); ruleMap.put(name, dataList); // 添加单一Host - ConfigEntry.globalDataMap.put(host, ruleMap); + Config.globalDataMap.put(host, ruleMap); } String[] splitHost = host.split("\\."); + String onlyHost = host.split(":")[0]; - String anyHost = (splitHost.length > 2 && !MatchTool.matchIP(host)) ? StringHelper.replaceFirstOccurrence(host, splitHost[0], "*") : ""; + String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : ""; - if (!ConfigEntry.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) { + if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) { // 添加通配符Host,实际数据从查询哪里将所有数据提取 - ConfigEntry.globalDataMap.put(anyHost, new HashMap<>()); + Config.globalDataMap.put(anyHost, new HashMap<>()); } - if (!ConfigEntry.globalDataMap.containsKey("*")) { + if (!Config.globalDataMap.containsKey("*")) { // 添加通配符全匹配,同上 - ConfigEntry.globalDataMap.put("*", new HashMap<>()); - } - - if (!ConfigEntry.globalDataMap.containsKey("**")) { - // 添加通配符全匹配,同上 - ConfigEntry.globalDataMap.put("**", new HashMap<>()); + Config.globalDataMap.put("*", new HashMap<>()); } } } } } }); - GlobalCachePool.addToCache(messageIndex, finalMap); + CachePool.addToCache(messageIndex, finalMap); return finalMap; } } @@ -222,7 +191,7 @@ public class DataProcessingUnit { return matches; } - public List getFormatString(Matcher matcher, String format) { + private List getFormatString(Matcher matcher, String format) { List indexList = parseIndexesFromString(format); List stringList = new ArrayList<>(); @@ -242,7 +211,7 @@ public class DataProcessingUnit { return stringList; } - public List getFormatString(AutomatonMatcher matcher, String content) { + private List getFormatString(AutomatonMatcher matcher, String content) { List stringList = new ArrayList<>(); while (matcher.find()) { @@ -283,9 +252,9 @@ public class DataProcessingUnit { } private String getSubString(String content, String s) { - byte[] contentByte = BurpExtender.helpers.stringToBytes(content); - byte[] sByte = BurpExtender.helpers.stringToBytes(s); - int startIndex = BurpExtender.helpers.indexOf(contentByte, sByte, false, 1, contentByte.length); + byte[] contentByte = api.utilities().byteUtils().convertFromString(content); + byte[] sByte = api.utilities().byteUtils().convertFromString(s); + int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length); int endIndex = startIndex + s.length(); return content.substring(startIndex, endIndex); } @@ -303,4 +272,3 @@ public class DataProcessingUnit { return format; } } - diff --git a/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java b/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java new file mode 100644 index 0000000..9597478 --- /dev/null +++ b/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java @@ -0,0 +1,47 @@ +package hae.instances.websocket; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.core.HighlightColor; +import burp.api.montoya.proxy.websocket.*; +import hae.instances.http.utils.MessageProcessor; + +import java.util.List; +import java.util.Map; + +public class WebSocketMessageHandler implements ProxyMessageHandler { + private final MontoyaApi api; + private final MessageProcessor messageProcessor; + + public WebSocketMessageHandler(MontoyaApi api) { + this.api = api; + this.messageProcessor = new MessageProcessor(api); + } + + @Override + public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) { + String message = interceptedTextMessage.payload(); + List> result = messageProcessor.processMessage("", message, true); + + if (result != null && !result.isEmpty() && result.size() > 0) { + interceptedTextMessage.annotations().setHighlightColor(HighlightColor.highlightColor(result.get(0).get("color"))); + interceptedTextMessage.annotations().setNotes(result.get(1).get("comment")); + } + + return TextMessageReceivedAction.continueWith(interceptedTextMessage); + } + + @Override + public TextMessageToBeSentAction handleTextMessageToBeSent(InterceptedTextMessage interceptedTextMessage) { + return TextMessageToBeSentAction.continueWith(interceptedTextMessage); + } + + @Override + public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) { + return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage); + } + + @Override + public BinaryMessageToBeSentAction handleBinaryMessageToBeSent(InterceptedBinaryMessage interceptedBinaryMessage) { + return BinaryMessageToBeSentAction.continueWith(interceptedBinaryMessage); + } +} diff --git a/src/main/java/hae/utils/config/ConfigLoader.java b/src/main/java/hae/utils/config/ConfigLoader.java new file mode 100644 index 0000000..df7d1ba --- /dev/null +++ b/src/main/java/hae/utils/config/ConfigLoader.java @@ -0,0 +1,195 @@ +package hae.utils.config; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +import burp.api.montoya.MontoyaApi; +import burp.api.montoya.http.message.HttpRequestResponse; +import burp.api.montoya.http.message.requests.HttpRequest; +import hae.Config; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; + +public class ConfigLoader { + private final MontoyaApi api; + private final Yaml yaml; + private final String configFilePath; + private final String rulesFilePath; + + public ConfigLoader(MontoyaApi api) { + this.api = api; + DumperOptions dop = new DumperOptions(); + dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Representer representer = new Representer(dop); + this.yaml = new Yaml(representer, dop); + + String configPath = determineConfigPath(); + this.configFilePath = String.format("%s/%s", configPath, "Config.yml"); + this.rulesFilePath = String.format("%s/%s", configPath, "Rules.yml"); + + // 构造函数,初始化配置 + File HaEConfigPathFile = new File(configPath); + if (!(HaEConfigPathFile.exists() && HaEConfigPathFile.isDirectory())) { + HaEConfigPathFile.mkdirs(); + } + + File configFilePath = new File(this.configFilePath); + if (!(configFilePath.exists() && configFilePath.isFile())) { + initConfig(); + } + + File rulesFilePath = new File(this.rulesFilePath); + if (!(rulesFilePath.exists() && rulesFilePath.isFile())) { + initRules(); + } + + Config.globalRules = getRules(); + } + + private String determineConfigPath() { + // 优先级1:用户根目录 + String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home")); + if (isValidConfigPath(userConfigPath)) { + return userConfigPath; + } + + // 优先级2:Jar包所在目录 + String jarPath = api.extension().filename(); + String jarDirectory = new File(jarPath).getParent(); + String jarConfigPath = String.format("%s/.config/HaE", jarDirectory); + if (isValidConfigPath(jarConfigPath)) { + return jarConfigPath; + } + + return userConfigPath; + } + + private static boolean isValidConfigPath(String configPath) { + File configPathFile = new File(configPath); + return configPathFile.exists() && configPathFile.isDirectory(); + } + + public void initConfig() { + Map r = new LinkedHashMap<>(); + r.put("excludeSuffix", getExcludeSuffix()); + try { + Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); + yaml.dump(r, ws); + ws.close(); + } catch (Exception ignored) { + } + } + + public String getRulesFilePath() { + return rulesFilePath; + } + + public String getExcludeSuffix() { + File yamlSetting = new File(configFilePath); + if (!yamlSetting.exists() || !yamlSetting.isFile()) { + return Config.suffix; + } + + try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) { + Map r = new Yaml().load(inorder); + + if (r.containsKey("excludeSuffix")) { + return r.get("excludeSuffix").toString(); + } + }catch (Exception ignored) { + } + + return Config.suffix; + } + + // 获取规则配置 + public Map getRules() { + Map rules = new HashMap<>(); + + try { + InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath())); + DumperOptions dop = new DumperOptions(); + dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Representer representer = new Representer(dop); + Map rulesMap = new Yaml(representer, dop).load(inputStream); + + String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"}; + + Object rulesObj = rulesMap.get("rules"); + if (rulesObj instanceof List) { + List> groupData = (List>) rulesObj; + for (Map groupFields : groupData) { + ArrayList data = new ArrayList<>(); + + Object ruleObj = groupFields.get("rule"); + if (ruleObj instanceof List) { + List> ruleData = (List>) ruleObj; + for (Map ruleFields : ruleData) { + Object[] valuesArray = new Object[fieldKeys.length]; + for (int i = 0; i < fieldKeys.length; i++) { + valuesArray[i] = ruleFields.get(fieldKeys[i]); + } + data.add(valuesArray); + } + } + + Object[][] dataArray = data.toArray(new Object[data.size()][]); + rules.put(groupFields.get("group").toString(), dataArray); + } + } + + return rules; + } catch (Exception ignored){ + } + + return rules; + } + + public void setExcludeSuffix(String excludeSuffix) { + Map r = new LinkedHashMap<>(); + r.put("excludeSuffix", excludeSuffix); + try{ + Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); + yaml.dump(r, ws); + ws.close(); + } catch (Exception ignored) { + } + } + + public void initRules() { + Thread t = new Thread() { + public void run() { + pullRules(); + } + }; + t.start(); + try { + t.join(10000); + } catch (Exception ignored) { + } + } + + private void pullRules() { + try { + String url = "https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml"; + HttpRequest httpRequest = HttpRequest.httpRequestFromUrl(url); + HttpRequestResponse requestResponse = api.http().sendRequest(httpRequest); + String responseBody = requestResponse.response().bodyToString(); + if (responseBody.contains("rules")) { + FileOutputStream fileOutputStream = new FileOutputStream(rulesFilePath); + fileOutputStream.write(responseBody.getBytes()); + fileOutputStream.close(); + } + } catch (Exception ignored) { + api.extension().unload(); + } + } +} diff --git a/src/main/java/burp/rule/RuleProcessor.java b/src/main/java/hae/utils/rule/RuleProcessor.java similarity index 56% rename from src/main/java/burp/rule/RuleProcessor.java rename to src/main/java/hae/utils/rule/RuleProcessor.java index bd1d1b1..0912098 100644 --- a/src/main/java/burp/rule/RuleProcessor.java +++ b/src/main/java/hae/utils/rule/RuleProcessor.java @@ -1,103 +1,109 @@ -package burp.rule; - -import burp.config.ConfigEntry; -import burp.config.ConfigLoader; -import burp.rule.model.Rule; -import burp.rule.model.RuleGroup; -import burp.rule.utils.YamlTool; -import java.io.IOException; -import java.nio.file.Files; -import java.util.stream.Collectors; -import org.yaml.snakeyaml.Yaml; -import java.io.File; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.*; - -/** - * @author EvilChen - */ - -public class RuleProcessor { - public void rulesFormatAndSave() { - Yaml yaml = YamlTool.newStandardYaml(); - List ruleGroupList = new ArrayList<>(); - - ConfigEntry.globalRules.forEach((k, v) -> { - List ruleList = Arrays.stream(v) - .map(objects -> new Rule( - (boolean) objects[0], - (String) objects[1], - (String) objects[2], - (String) objects[3], - (String) objects[4], - (String) objects[5], - (String) objects[6], - (String) objects[7], - (boolean) objects[8])) - .collect(Collectors.toList()); - ruleGroupList.add(new RuleGroup(k, ruleList)); - }); - - List> outputGroupsMap = ruleGroupList.stream() - .map(RuleGroup::getFields) - .collect(Collectors.toList()); - - Map outputMap = new LinkedHashMap<>(); - outputMap.put("rules", outputGroupsMap); - - File f = new File(ConfigLoader.getRulesFilePath()); - try (Writer ws = new OutputStreamWriter(Files.newOutputStream(f.toPath()), StandardCharsets.UTF_8)) { - yaml.dump(outputMap, ws); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - - public void changeRule(Vector data, int select, String type) { - ConfigEntry.globalRules.get(type)[select] = data.toArray(); - this.rulesFormatAndSave(); - } - - public void addRule(Vector data, String type) { - ArrayList x = new ArrayList<>(Arrays.asList(ConfigEntry.globalRules.get(type))); - x.add(data.toArray()); - ConfigEntry.globalRules.put(type,x.toArray(new Object[x.size()][])); - this.rulesFormatAndSave(); - } - public void removeRule(int select,String type) { - ArrayList x = new ArrayList<>(Arrays.asList(ConfigEntry.globalRules.get(type))); - x.remove(select); - ConfigEntry.globalRules.put(type,x.toArray(new Object[x.size()][])); - this.rulesFormatAndSave(); - } - - public void renameRuleGroup(String oldName, String newName) { - ConfigEntry.globalRules.put(newName, ConfigEntry.globalRules.remove(oldName)); - this.rulesFormatAndSave(); - } - - public void deleteRuleGroup(String Rules) { - ConfigEntry.globalRules.remove(Rules); - this.rulesFormatAndSave(); - } - - public String newRule() { - int i = 0; - String name = "New "; - Object[][] data = new Object[][] { - { - false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false - } - }; - - while (ConfigEntry.globalRules.containsKey(name + i)) { - i++; - } - - ConfigEntry.globalRules.put(name + i, data); - this.rulesFormatAndSave(); - return name + i; - } -} +package hae.utils.rule; + +import burp.api.montoya.MontoyaApi; +import hae.Config; +import hae.utils.rule.model.Group; +import hae.utils.rule.model.Info; +import hae.utils.config.ConfigLoader; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; +import java.util.stream.Collectors; + +public class RuleProcessor { + private final MontoyaApi api; + private final ConfigLoader configLoader; + + public RuleProcessor(MontoyaApi api, ConfigLoader configLoader) { + this.api = api; + this.configLoader = configLoader; + } + + public void rulesFormatAndSave() { + DumperOptions dop = new DumperOptions(); + dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Representer representer = new Representer(dop); + Yaml yaml = new Yaml(representer, dop); + + List ruleGroupList = new ArrayList<>(); + + Config.globalRules.forEach((k, v) -> { + List ruleList = Arrays.stream(v) + .map(objects -> new Info( + (boolean) objects[0], + (String) objects[1], + (String) objects[2], + (String) objects[3], + (String) objects[4], + (String) objects[5], + (String) objects[6], + (String) objects[7], + (boolean) objects[8])) + .collect(Collectors.toList()); + ruleGroupList.add(new Group(k, ruleList)); + }); + + List> outputGroupsMap = ruleGroupList.stream() + .map(Group::getFields) + .collect(Collectors.toList()); + + Map outputMap = new LinkedHashMap<>(); + outputMap.put("rules", outputGroupsMap); + + File f = new File(configLoader.getRulesFilePath()); + try (Writer ws = new OutputStreamWriter(Files.newOutputStream(f.toPath()), StandardCharsets.UTF_8)) { + yaml.dump(outputMap, ws); + } catch (Exception ignored) { + } + } + + public void changeRule(Vector data, int select, String type) { + Config.globalRules.get(type)[select] = data.toArray(); + this.rulesFormatAndSave(); + } + + public void addRule(Vector data, String type) { + ArrayList x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type))); + x.add(data.toArray()); + Config.globalRules.put(type,x.toArray(new Object[x.size()][])); + this.rulesFormatAndSave(); + } + public void removeRule(int select,String type) { + ArrayList x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type))); + x.remove(select); + Config.globalRules.put(type,x.toArray(new Object[x.size()][])); + this.rulesFormatAndSave(); + } + + public void renameRuleGroup(String oldName, String newName) { + Config.globalRules.put(newName, Config.globalRules.remove(oldName)); + this.rulesFormatAndSave(); + } + + public void deleteRuleGroup(String Rules) { + Config.globalRules.remove(Rules); + this.rulesFormatAndSave(); + } + + public String newRule() { + int i = 0; + String name = "New "; + + while (Config.globalRules.containsKey(name + i)) { + i++; + } + + Config.globalRules.put(name + i, Config.ruleTemplate); + this.rulesFormatAndSave(); + return name + i; + } +} + + diff --git a/src/main/java/burp/rule/model/RuleGroup.java b/src/main/java/hae/utils/rule/model/Group.java similarity index 69% rename from src/main/java/burp/rule/model/RuleGroup.java rename to src/main/java/hae/utils/rule/model/Group.java index 1c9e9a1..74b9990 100644 --- a/src/main/java/burp/rule/model/RuleGroup.java +++ b/src/main/java/hae/utils/rule/model/Group.java @@ -1,37 +1,29 @@ -package burp.rule.model; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * @author EvilChen - */ - -public class RuleGroup { - private Map fields; - - public RuleGroup(String groupName, List rules) { - List> ruleList = new ArrayList<>(); - for (Rule rule : rules) { - ruleList.add(rule.getFields()); - } - - fields = new LinkedHashMap<>(); - fields.put("group", groupName); - fields.put("rule", ruleList); - } - - public RuleGroup() { - - } - - public Map getFields() { - return fields; - } - - public void loadFields(Map fields) { - this.fields = fields; - } -} \ No newline at end of file +package hae.utils.rule.model; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Group { + private Map fields; + + public Group(String groupName, List rules) { + List> ruleList = new ArrayList<>(); + for (Info rule : rules) { + ruleList.add(rule.getFields()); + } + + fields = new LinkedHashMap<>(); + fields.put("group", groupName); + fields.put("rule", ruleList); + } + + public Map getFields() { + return fields; + } + + public void loadFields(Map fields) { + this.fields = fields; + } +} diff --git a/src/main/java/burp/rule/model/Rule.java b/src/main/java/hae/utils/rule/model/Info.java similarity index 78% rename from src/main/java/burp/rule/model/Rule.java rename to src/main/java/hae/utils/rule/model/Info.java index a22a778..e511fca 100644 --- a/src/main/java/burp/rule/model/Rule.java +++ b/src/main/java/hae/utils/rule/model/Info.java @@ -1,37 +1,29 @@ -package burp.rule.model; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * @author EvilChen - */ - -public class Rule { - private Map fields; - - public Rule(boolean loaded, String name, String f_regex, String s_regex, String format, String color, String scope, String engine, boolean sensitive) { - fields = new LinkedHashMap<>(); - fields.put("name", name); - fields.put("loaded", loaded); - fields.put("f_regex", f_regex); - fields.put("s_regex", s_regex); - fields.put("format", format); - fields.put("color", color); - fields.put("scope", scope); - fields.put("engine", engine); - fields.put("sensitive", sensitive); - } - - public Rule() { - - } - - public Map getFields() { - return fields; - } - - public void loadFields(Map fields) { - this.fields = fields; - } +package hae.utils.rule.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class Info { + private Map fields; + + public Info(boolean loaded, String name, String f_regex, String s_regex, String format, String color, String scope, String engine, boolean sensitive) { + fields = new LinkedHashMap<>(); + fields.put("name", name); + fields.put("loaded", loaded); + fields.put("f_regex", f_regex); + fields.put("s_regex", s_regex); + fields.put("format", format); + fields.put("color", color); + fields.put("scope", scope); + fields.put("engine", engine); + fields.put("sensitive", sensitive); + } + + public Map getFields() { + return fields; + } + + public void loadFields(Map fields) { + this.fields = fields; + } } \ No newline at end of file diff --git a/src/main/java/burp/core/utils/HashCalculator.java b/src/main/java/hae/utils/string/HashCalculator.java similarity index 55% rename from src/main/java/burp/core/utils/HashCalculator.java rename to src/main/java/hae/utils/string/HashCalculator.java index 4546581..ef5b833 100644 --- a/src/main/java/burp/core/utils/HashCalculator.java +++ b/src/main/java/hae/utils/string/HashCalculator.java @@ -1,19 +1,17 @@ -package burp.core.utils; - -import burp.BurpExtender; +package hae.utils.string; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * @author EvilChen - */ public class HashCalculator { - public static String calculateHash(byte[] bytes) throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("MD5"); - byte[] hashBytes = digest.digest(bytes); - return bytesToHex(hashBytes); + public static String calculateHash(byte[] bytes){ + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + byte[] hashBytes = digest.digest(bytes); + return bytesToHex(hashBytes); + } catch (Exception ignored) { + return ""; + } } private static String bytesToHex(byte[] bytes) { diff --git a/src/main/java/burp/core/utils/StringHelper.java b/src/main/java/hae/utils/string/StringProcessor.java similarity index 76% rename from src/main/java/burp/core/utils/StringHelper.java rename to src/main/java/hae/utils/string/StringProcessor.java index 8a2bb25..254fea7 100644 --- a/src/main/java/burp/core/utils/StringHelper.java +++ b/src/main/java/hae/utils/string/StringProcessor.java @@ -1,9 +1,10 @@ -package burp.core.utils; +package hae.utils.string; +import java.net.URL; import java.util.HashMap; import java.util.Map; -public class StringHelper { +public class StringProcessor { public static String replaceFirstOccurrence(String original, String find, String replace) { int index = original.indexOf(find); if (index != -1) { @@ -36,6 +37,39 @@ public class StringHelper { return comment; } + Map itemCounts = getStringIntegerMap(comment); + + StringBuilder mergedItems = new StringBuilder(); + + for (Map.Entry entry : itemCounts.entrySet()) { + String itemName = entry.getKey(); + int count = entry.getValue(); + if (count != 0) { + mergedItems.append(itemName).append(" (").append(count).append("), "); + } + } + + return mergedItems.substring(0, mergedItems.length() - 2); + } + + public static String getHostByUrl(String url) { + String host = ""; + + try { + URL u = new URL(url); + int port = u.getPort(); + if (port == -1) { + host = u.getHost(); + } else { + host = String.format("%s:%s", u.getHost(), port); + } + } catch (Exception ignored) { + } + + return host; + } + + private static Map getStringIntegerMap(String comment) { Map itemCounts = new HashMap<>(); String[] items = comment.split(", "); @@ -51,16 +85,7 @@ public class StringHelper { } } - StringBuilder mergedItems = new StringBuilder(); - - for (Map.Entry entry : itemCounts.entrySet()) { - String itemName = entry.getKey(); - int count = entry.getValue(); - if (count != 0) { - mergedItems.append(itemName).append(" (").append(count).append("), "); - } - } - - return mergedItems.substring(0, mergedItems.length() - 2); + return itemCounts; } } +