entry : jsonObj.entrySet()) {
94 | String value = jsonObj.getString(entry.getKey());
95 | String kw = keyword;
96 | kw += keyword.equals("") ? entry.getKey() : "." + entry.getKey();
97 | if(value.equals(val)){
98 | keywords.add(kw);
99 | } else {
100 | makeKeyword(value,val,kw);
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | @Override
109 | public MatchResult match(String str, String keyword) {
110 | MatchResult matchResult = new MatchResult();
111 | String rsqData = new String(getRspBody(str.getBytes()));
112 | this.searchJson(rsqData,keyword);
113 | if(result.size() != 0){
114 | String res = result.get(0);
115 | int start = str.indexOf(res); //存在相同值的化,可能不准确,待优化
116 | int end = start + res.length();
117 | matchResult.setResult(res);
118 | matchResult.setStart(start);
119 | matchResult.setEnd(end);
120 | return matchResult;
121 | }
122 | return null;
123 | }
124 |
125 | @Override
126 | public String buildKeyword(String str, String value) {
127 | this.makeKeyword(str,value,null);
128 | if(keywords.size() != 0){
129 | return keywords.get(0);
130 | }
131 | return null;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # captcha-killer-modified 适配新版Burpsuite
2 | [](https://github.com/f0ng/captcha-killer-modified/stargazers)
3 | [](https://github.com/f0ng/captcha-killer-modified/releases)
4 | [](https://github.com/f0ng/captcha-killer-modified/tags)
5 | [](https://github.com/f0ng/captcha-killer-modified/releases)
6 |
7 |
8 |
9 | ## 原项目地址: https://github.com/c0ny1/captcha-killer
10 |
11 | # [用法与常见报错](https://github.com/f0ng/captcha-killer-modified/blob/main/FAQ.md)
12 |
13 | ## 免责声明
14 |
15 | 该工具仅用于安全自查检测
16 |
17 | 由于传播、利用此工具所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。
18 |
19 | 本人拥有对此工具的修改和解释权。未经网络安全部门及相关部门允许,不得善自使用本工具进行任何攻击活动,不得以任何方式将其用于商业目的。
20 |
21 | ### 文章案例
22 | >https://github.com/c0ny1/captcha-killer [插件源项目]
23 | >
24 | >https://gv7.me/articles/2019/burp-captcha-killer-usage/ [原插件用法]
25 | >
26 | >https://github.com/sml2h3/ddddocr [验证码识别项目]
27 | >
28 | >https://github.com/PoJun-Lab/blaster [验证码登录爆破]
29 | >
30 | >https://www.cnblogs.com/4geek/p/17145385.html#!comments [captcha-killer-modified详细用法及部分问题解决方案(如验证码识别位数问题)]
31 |
32 | 交流群
33 |
34 |
35 |
36 |
37 | 二维码失效请加微信`f-f0ng`、备注captchakillermodified交流
38 |
39 | 关注主页公众号(only security),回复`captchakillermodified`获取下载地址】
40 |
41 |
42 | ### 提issue之前请说明如下字段:
43 | 1. burp版本
44 | 2. 启动burp的jdk版本
45 | 3. burp的Extender中Options配置的jdk版本
46 |
47 | ### 安全培训
48 |
49 | 
50 |
51 | 学网络安全,就选玲珑安全!专业漏洞挖掘,精准定位风险;助力技能提升,塑造安全精英;玲珑安全,为您的数字世界保驾护航!
52 | 在线免费学习网络安全,涵盖src漏洞挖掘,0基础安全入门。适用于小白,进阶,高手: https://space.bilibili.com/602205041
53 | 玲珑安全往期学员报喜🎉: https://www.ifhsec.com/list.html
54 | 玲珑安全漏洞挖掘培训学习联系微信: `f-f0ng`
55 | 备注:玲珑安全培训
56 |
57 |
58 |
59 | # 捐赠 (如果项目有帮助到您,可以选择捐赠一些费用用于captcha-killer-modified的后续版本维护,本项目长期维护)
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ## 插件优化的地方
69 | 1. 修改了原项目中`sun.misc.BASE64Encoder`报错的问题
70 |
71 | 2. 优化了验证码`data:image`识别问题
72 |
73 | 3. 添加了ddddocr验证码识别库
74 |
75 | 4. 增加自定义关键词获取验证码
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | 识别成功率在85%左右。
86 |
87 | 具体修改请查看微信公众号文章
88 | https://mp.weixin.qq.com/s/_P6OlL1xQaYSY1bvZJL4Uw
89 |
90 |
91 | ## 更新日志
92 |
93 | 【2022-3-21】 增加可识别情况,~~当出现关键字为B/base64时,进行验证码识别~~
94 |
95 | 【2022-3-24】 增加自定义关键字,删减锁定按钮
96 |
97 |
98 |
99 |
100 |
101 | 【2022-3-30】适配`data:image\/png`与base64中出现`\r\n`情况
102 |
103 |
104 |
105 | 【2022-4-12】提升准确性,修改识别验证码端代码,主要修改如下:
106 |
107 | 1. 增加basic认证,方便部署在公网,使用`tmux`在后台运行即可
108 |
109 | 2. 对验证码识别部分进行修改,针对识别出来多位,可以进行自行删改,举例,如验证码是四位,但是ddddocr识别出来了五位,那么可以截取`text=ocr.classification(img_bytes)[0:4]`前四位;
110 |
111 | 如ddddocr对特定类验证码的识别中字母`O`与数字`0`识别混淆,可以进行替换`text=ocr.classification(img_bytes).replace("0","O")`
112 |
113 | 【2022-7-2】
114 |
115 | 1. 优化验证码对于base64的识别#10 ,原因在于base64编码中存在`\n`,`0.16`版本增加对`\n`的处理,感谢@DreamAndSun 师傅反馈
116 |
117 | 【2022-11-30】 0.17
118 |
119 | 1. 添加响应提取,针对获取验证码请求中有类似token字段,在登录包的同时需要token校验的情况,在需要token校验的字段使用`@captcha-killer-modified@`
120 |
121 |
122 | 2. 增加对验证码进行二次处理的案例(验证码为gif图,且验证码具体是在gif图的第二帧,无法直接识别),见[用法与常见报错](https://github.com/f0ng/captcha-killer-modified/blob/main/FAQ.md)
123 |
124 | 【2022-12-9】 0.18
125 | 1. 添加`@captcha@`参数替代验证码,方便在repeater参数内进行测试
126 |
127 |
128 |
129 | 【2022-12-14】 0.19
130 |
131 | 增加URL解码、过滤图片编码中的`.`
132 |
133 | 【2022-12-23】 0.20
134 |
135 | 修复了url识别问题、爆破顺序错乱问题、响应包直接为base64编码导致爆破失败问题
136 |
137 | 【2023-2-1】 0.21
138 |
139 | - 增加默认验证码模板`ddddocr`,适配`codereg.py`
140 |
141 |
142 |
143 | - 增加识别结果关键字显示,方便查看关键字是否与验证码对应
144 |
145 |
146 | 【2023-2-10】 0.21-beta
147 | - 优化验证码编码中的`\n`处理
148 | - 优化`@captcha@`的判断方式
149 |
150 | 【2023-3-14】 0.22 重要问题修复
151 | - 修复了装载插件会影响proxy选项卡的问题
152 |
153 | 【2023-3-28】 0.23
154 | - 增加[验证码返回包中明文返回验证码爆破案例](https://github.com/f0ng/captcha-killer-modified/blob/main/FAQ.md#13-%E9%AA%8C%E8%AF%81%E7%A0%81%E5%93%8D%E5%BA%94%E5%8C%85%E6%9C%89%E6%98%8E%E6%96%87%E9%AA%8C%E8%AF%81%E7%A0%81%E5%A6%82%E4%BD%95%E9%85%8D%E5%90%88%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8)
155 | - base64编码中应对`fromUrlSafe`函数(`-`转义为`+`,`_`转义为`/`)
156 |
157 | 【2023-5-22】 0.24
158 | - 修复验证码在intruder中无法显示的bug
159 | - 再次修复了装载插件会影响proxy选项卡的问题
160 |
161 | 【2023-7-2】 0.24.1
162 | - 修复加载插件影响intruder速度的问题(临时增加了一个按钮控制是否开启该插件)
163 |
164 |
165 |
166 | 【2023-9-15】 0.24.2
167 | - 优化@captcha-killer-modified@关键字
168 |
169 | 【2023-12-5】 0.24.3
170 | - 修复新版burp获取不到验证码问题
171 |
172 | 【2024-1-4】 0.24.4
173 | - 服务端识别代码增加算术接口,可以进行算术验证码的识别
174 |
175 | 【2024-4-2】 0.24.5
176 | 1. 针对复杂算数验证码,进行训练获得模型,若有训练验证码的需求,可以联系作者代为训练,需捐赠,捐赠具体费用可以联系作者。这里取若依的验证码(默认配置)进行演示,测试了109个验证码,识别错误1个,准确率98%+
177 |
178 |
179 | 2. 添加两个接口,添加reg2【识别无混淆的四则运算,项目默认模板】、reg3模板【识别混淆变形的若依四则运算验证码,默认模板不支持,需额外捐赠,捐赠具体费用可以联系作者】
180 |
181 |
182 | 
183 |
--------------------------------------------------------------------------------
/src/test/java/HighlightKeywordsDemo.java:
--------------------------------------------------------------------------------
1 | import java.awt.Color;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 | import javax.swing.JFrame;
6 | import javax.swing.JTextPane;
7 | import javax.swing.SwingUtilities;
8 | import javax.swing.event.DocumentEvent;
9 | import javax.swing.event.DocumentListener;
10 | import javax.swing.text.BadLocationException;
11 | import javax.swing.text.Document;
12 | import javax.swing.text.Style;
13 | import javax.swing.text.StyleConstants;
14 | import javax.swing.text.StyledDocument;
15 |
16 | public class HighlightKeywordsDemo {
17 |
18 | public static void main(String[] args) {
19 | JFrame frame = new JFrame();
20 | JTextPane editor = new JTextPane();
21 | editor.getDocument().addDocumentListener(new SyntaxHighlighter(editor));
22 | frame.getContentPane().add(editor);
23 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
24 | frame.setSize(500, 500);
25 | frame.setVisible(true);
26 | }
27 |
28 | }
29 |
30 |
31 | /**
32 | * 当文本输入区的有字符插入或者删除时, 进行高亮.
33 | *
34 | * 要进行语法高亮, 文本输入组件的document要是styled document才行. 所以不要用JTextArea. 可以使用JTextPane.
35 | *
36 | * @author Biao
37 | */
38 |
39 | class SyntaxHighlighter implements DocumentListener {
40 | private Set keywords;
41 | private Style keywordStyle;
42 | private Style normalStyle;
43 |
44 | public SyntaxHighlighter(JTextPane editor) {
45 | // 准备着色使用的样式
46 | keywordStyle = ((StyledDocument) editor.getDocument()).addStyle("Keyword_Style", null);
47 | normalStyle = ((StyledDocument) editor.getDocument()).addStyle("Keyword_Style", null);
48 | StyleConstants.setForeground(keywordStyle, Color.RED);
49 | StyleConstants.setForeground(normalStyle, Color.BLACK);
50 |
51 | // 准备关键字
52 | keywords = new HashSet();
53 | keywords.add("public");
54 | keywords.add("protected");
55 | keywords.add("private");
56 | keywords.add("_int9");
57 | keywords.add("float");
58 | keywords.add("double");
59 | }
60 |
61 |
62 | public void colouring(StyledDocument doc, int pos, int len) throws BadLocationException {
63 | // 取得插入或者删除后影响到的单词.
64 | // 例如"public"在b后插入一个空格, 就变成了:"pub lic", 这时就有两个单词要处理:"pub"和"lic"
65 | // 这时要取得的范围是pub中p前面的位置和lic中c后面的位置
66 | int start = indexOfWordStart(doc, pos);
67 | int end = indexOfWordEnd(doc, pos + len);
68 |
69 | char ch;
70 |
71 | while (start < end) {
72 |
73 | ch = getCharAt(doc, start);
74 |
75 | if (Character.isLetter(ch) || ch == '_') {
76 |
77 | // 如果是以字母或者下划线开头, 说明是单词
78 |
79 | // pos为处理后的最后一个下标
80 |
81 | start = colouringWord(doc, start);
82 |
83 | } else {
84 | SwingUtilities.invokeLater(new ColouringTask(doc, start, 1, normalStyle));
85 | ++start;
86 | }
87 |
88 | }
89 |
90 | }
91 |
92 |
93 | /**
94 | * 对单词进行着色, 并返回单词结束的下标.
95 | *
96 | * @param doc
97 | * @param pos
98 | * @return
99 | * @throws BadLocationException
100 | */
101 |
102 | public int colouringWord(StyledDocument doc, int pos) throws BadLocationException {
103 |
104 | int wordEnd = indexOfWordEnd(doc, pos);
105 |
106 | String word = doc.getText(pos, wordEnd - pos);
107 |
108 |
109 | if (keywords.contains(word)) {
110 |
111 | // 如果是关键字, 就进行关键字的着色, 否则使用普通的着色.
112 |
113 | // 这里有一点要注意, 在insertUpdate和removeUpdate的方法调用的过程中, 不能修改doc的属性.
114 |
115 | // 但我们又要达到能够修改doc的属性, 所以把此任务放到这个方法的外面去执行.
116 |
117 | // 实现这一目的, 可以使用新线程, 但放到swing的事件队列里去处理更轻便一点.
118 |
119 | SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos, keywordStyle));
120 |
121 | } else {
122 |
123 | SwingUtilities.invokeLater(new ColouringTask(doc, pos, wordEnd - pos, normalStyle));
124 |
125 | }
126 |
127 |
128 | return wordEnd;
129 |
130 | }
131 |
132 |
133 | /**
134 | * 取得在文档中下标在pos处的字符.
135 | *
136 | *
137 | *
138 | * 如果pos为doc.getLength(), 返回的是一个文档的结束符, 不会抛出异常. 如果pos<0, 则会抛出异常.
139 | *
140 | * 所以pos的有效值是[0, doc.getLength()]
141 | *
142 | * @param doc
143 | * @param pos
144 | * @return
145 | * @throws BadLocationException
146 | */
147 |
148 | public char getCharAt(Document doc, int pos) throws BadLocationException {
149 | return doc.getText(pos, 1).charAt(0);
150 | }
151 |
152 |
153 | /**
154 | * 取得下标为pos时, 它所在的单词开始的下标. ±wor^d± (^表示pos, ±表示开始或结束的下标)
155 | *
156 | * @param doc
157 | * @param pos
158 | * @return
159 | * @throws BadLocationException
160 | */
161 |
162 | public int indexOfWordStart(Document doc, int pos) throws BadLocationException {
163 | // 从pos开始向前找到第一个非单词字符.
164 | for (; pos > 0 && isWordCharacter(doc, pos - 1); --pos) ;
165 | return pos;
166 | }
167 |
168 |
169 | /**
170 | * 取得下标为pos时, 它所在的单词结束的下标. ±wor^d± (^表示pos, ±表示开始或结束的下标)
171 | *
172 | * @param doc
173 | * @param pos
174 | * @return
175 | * @throws BadLocationException
176 | */
177 |
178 | public int indexOfWordEnd(Document doc, int pos) throws BadLocationException {
179 |
180 | // 从pos开始向前找到第一个非单词字符.
181 | for (; isWordCharacter(doc, pos); ++pos) ;
182 | return pos;
183 | }
184 |
185 |
186 | /**
187 | * 如果一个字符是字母, 数字, 下划线, 则返回true.
188 | *
189 | * @param doc
190 | * @param pos
191 | * @return
192 | * @throws BadLocationException
193 | */
194 |
195 | public boolean isWordCharacter(Document doc, int pos) throws BadLocationException {
196 | char ch = getCharAt(doc, pos);
197 | if (Character.isLetter(ch) || Character.isDigit(ch) || ch == '_') {
198 | return true;
199 | }
200 | return false;
201 | }
202 |
203 |
204 | @Override
205 | public void changedUpdate(DocumentEvent e) {
206 | }
207 |
208 |
209 | @Override
210 | public void insertUpdate(DocumentEvent e) {
211 | try {
212 | colouring((StyledDocument) e.getDocument(), e.getOffset(), e.getLength());
213 | } catch (BadLocationException e1) {
214 | e1.printStackTrace();
215 | }
216 | }
217 |
218 |
219 | @Override
220 | public void removeUpdate(DocumentEvent e) {
221 | try {
222 | // 因为删除后光标紧接着影响的单词两边, 所以长度就不需要了
223 | colouring((StyledDocument) e.getDocument(), e.getOffset(), 0);
224 | } catch (BadLocationException e1) {
225 | e1.printStackTrace();
226 | }
227 |
228 | }
229 |
230 |
231 | /**
232 | * 完成着色任务
233 | *
234 | * @author Biao
235 | */
236 | private class ColouringTask implements Runnable {
237 | private StyledDocument doc;
238 | private Style style;
239 | private int pos;
240 | private int len;
241 |
242 | public ColouringTask(StyledDocument doc, int pos, int len, Style style) {
243 | this.doc = doc;
244 | this.pos = pos;
245 | this.len = len;
246 | this.style = style;
247 | }
248 |
249 | public void run() {
250 | try {
251 | // 这里就是对字符进行着色
252 | doc.setCharacterAttributes(pos, len, style, true);
253 | } catch (Exception e) {
254 | }
255 | }
256 | }
257 |
258 | }
--------------------------------------------------------------------------------
/src/main/java/utils/RuleMannager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer)
3 | * License: MIT
4 | */
5 | package utils;
6 |
7 | import burp.BurpExtender;
8 | import entity.MatchResult;
9 | import entity.Rule;
10 | import matcher.impl.JsonMatcher;
11 | import matcher.impl.XmlMatcher;
12 |
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | import static utils.Util.*;
17 |
18 | /**
19 | * autor: c0ny1
20 | * date: 2019-10-14
21 | * description: 匹配规则管理类
22 | */
23 | public class RuleMannager {
24 | // public static String match(String str, Rule rule){
25 | // switch (rule.getType()){
26 | // case Rule.RULE_TYPE_RESPONSE_DATA:
27 | // String res = new String(Util.getRspBody(str.getBytes()));
28 | // return res;
29 | // case Rule.RULE_TYPE_REGULAR:
30 | // return matchByRegular(str,rule.getRule());
31 | // case Rule.RULE_TYPE_POSISTION:
32 | // return matchByPosistion(str,rule.getnStart(),rule.getnEnd());
33 | // case Rule.RULE_TYPE_START_END_STRING:
34 | // return matchByStartEndString(str,rule.getStrStart(),rule.getStrEnd());
35 | // default:
36 | // return "unkown rule type";
37 | // }
38 | // }
39 |
40 | public static MatchResult match(String str,Rule rule){
41 | switch (rule.getType()){
42 | case Rule.RULE_TYPE_RESPONSE_DATA:
43 | return matchRsqData(str);
44 | case Rule.RULE_TYPE_REGULAR:
45 | return matchByRegular(str,rule.getRule());
46 | case Rule.RULE_TYPE_POSISTION:
47 | return matchByPosistion(str,rule.getnStart(),rule.getnEnd());
48 | case Rule.RULE_TYPE_START_END_STRING:
49 | return matchByStartEndString(str,rule.getStrStart(),rule.getStrEnd());
50 | case Rule.RULE_TYPE_JSON_MATCH:
51 | JsonMatcher jsonMatcher = new JsonMatcher();
52 | return jsonMatcher.match(str,rule.getRule());
53 | case Rule.RULE_TYPE_XML_MATCH:
54 | XmlMatcher xmlMatcher = new XmlMatcher();
55 | return xmlMatcher.match(str,rule.getRule());
56 | default:
57 | MatchResult result = new MatchResult();
58 | result.setResult("unkown rule type");
59 | return result;
60 | }
61 | }
62 |
63 | public static MatchResult matchRsqData(String str){
64 | MatchResult result = new MatchResult();
65 | String res = new String(getRspBody(str.getBytes()));
66 | int bodyOffset = str.indexOf(res);
67 | result.setStart(bodyOffset);
68 | result.setEnd(bodyOffset + res.length());
69 | result.setResult(res);
70 | return result;
71 | }
72 |
73 | public static MatchResult matchByRegular(String str,String regular){
74 | MatchResult result = new MatchResult();
75 | String res = null;
76 | int start = 0;
77 | int end = 0;
78 | Pattern r = Pattern.compile(regular);
79 | Matcher m = r.matcher(str);
80 | if (m.find()) {
81 | res = m.group(1);//0会获取多余的内容
82 | start = m.start();
83 | int n = str.substring(start,str.length()).indexOf(res);
84 | start += n;
85 | end = start + res.length();
86 | }
87 | result.setResult(res);
88 | result.setStart(start);
89 | result.setEnd(end);
90 | return result;
91 | }
92 |
93 | public static MatchResult matchByPosistion(String str,int start,int end){
94 | MatchResult result = new MatchResult();
95 | result.setStart(start);
96 | result.setEnd(end);
97 | result.setResult(str.substring(start,end));
98 | return result;
99 | }
100 |
101 | public static MatchResult matchByStartEndString(String str,String start,String end){
102 | int nStart = str.indexOf(start) + start.length();
103 | int nEnd = str.indexOf(end);
104 | MatchResult result = new MatchResult();
105 | result.setStart(nStart);
106 | result.setEnd(nEnd);
107 | result.setResult(str.substring(nStart,nEnd));
108 | return result;
109 | }
110 |
111 | public static String generateRegular(String raw,int start,int end){
112 | int newStart = start;
113 | int newEnd = end;
114 | String startStr = "";
115 | String endStr = "";
116 | String target = raw.substring(start,end);
117 | String strReg = "";
118 |
119 | if(start>3){
120 | newStart -= 3;
121 | }else if(start > 1){
122 | newStart -= 1;
123 | }
124 |
125 | if(end0){
150 | newStart -= 1;
151 | }
152 | if(newEnd= 0){
172 | strStart = raw.substring(s,start);
173 | int startPosition = raw.indexOf(strStart) + strStart.length();
174 | if(startPosition == start){
175 | break;
176 | }
177 | s -= 1;
178 | }
179 |
180 | while (e <= raw.length()) {
181 | strEnd = raw.substring(end,e);
182 | int endPosition = raw.indexOf(strEnd);
183 | if(endPosition == end){
184 | break;
185 | }
186 | e += 1;
187 | }
188 | strStart = Util.escapeJsonString(strStart);
189 | strEnd = Util.escapeJsonString(strEnd);
190 | String rule = String.format("{\"start\":\"%s\",\"end\":\"%s\"}",strStart,strEnd);
191 |
192 | //验证最终生成的规则是否适用
193 | if(matchByStartEndString(raw,strStart,strEnd).getResult().equals(keyword)){
194 | return rule;
195 | }else{
196 | return "generate rule fail,try again";
197 | }
198 | }
199 |
200 | public static void main(String[] args) {
201 | String str = generateStartEndRule("absdsdsdbsdsfwewwfwfwdsdddcdesdfsdffghijkweweefewffsadfssdgslmnopqrsdsdst",12,34);
202 | // System.out.println(str);
203 | }
204 |
205 | }
206 |
--------------------------------------------------------------------------------
/src/main/java/utils/HttpClient.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer)
3 | * License: MIT
4 | */
5 | package utils;
6 |
7 | import burp.BurpExtender;
8 | import burp.IHttpRequestResponse;
9 | import burp.IRequestInfo;
10 | import entity.HttpService;
11 | import java.io.IOException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | public class HttpClient {
16 | private String url;
17 | private String protocol;
18 | private String method;
19 | private String httpversion;
20 | private String host;
21 | private int port;
22 | private String path;
23 | private HttpService service;
24 | private Map headers = new HashMap();
25 | private String data;
26 | private String raw;
27 | private byte[] byteImg;
28 |
29 | public HttpClient(String url,String raw,byte[] byteImg) throws IOException {
30 | this.url = url;
31 | this.raw = processLine(raw);
32 | this.byteImg = byteImg;
33 | //解析标签
34 | parseLabel();
35 | //解析Request各个属性
36 | parserRequest();
37 | //更新Content-Length
38 | updateContentLength();
39 | }
40 |
41 | /**
42 | * 将请求包头中\n替换为\r\n,因为\n可能会导致某些服务器报错,无法正确识别请求包。
43 | * @param reqraw
44 | * @return
45 | */
46 | public String processLine(String reqraw){
47 | String method = null;
48 | String header = null;
49 | String body = null;
50 | String request = null;
51 |
52 | if(reqraw.startsWith("GET")){
53 | method = "GET";
54 | }else if(reqraw.startsWith("POST")){
55 | method = "POST";
56 | }else{
57 | method = "unkown";
58 | return reqraw;
59 | }
60 |
61 | if(method.equals("GET")) {
62 | header = reqraw;
63 |
64 | //将所有\n替换为\r\n,注意\r\n和\n混合情况下的替换。
65 | header.replace("\r\n","\n");
66 | header.replace("\n","\r\n");
67 |
68 | request = header;
69 | }
70 |
71 | if(method.equals("POST")){
72 | int n = reqraw.indexOf("\n\n") != -1 ? reqraw.indexOf("\n\n") : reqraw.indexOf("\r\n\r\n");
73 | header = reqraw.substring(0, n).trim();
74 | body = reqraw.substring(n + 1, reqraw.length()).trim();
75 | if(header.indexOf("\n")>=0 && header.indexOf("\r\n") <0){
76 | header = header.replace("\n","\r\n");
77 | }
78 | request = header + "\r\n\r\n" + body;
79 | }
80 | return request;
81 | }
82 |
83 |
84 | public String getHttpService(){
85 | return service.toString();
86 | }
87 |
88 | public String getRaw(){
89 | return this.raw;
90 | }
91 |
92 |
93 | /**
94 | * 解析标签,可以参考下
95 | */
96 | public void parseLabel() throws IOException {
97 | LableParser parser = new LableParser(byteImg);
98 | // System.out.println("######################");
99 | // System.out.println(raw);
100 | raw = parser.parseAllLable(raw);
101 | // System.out.println("**********************");
102 | // System.out.println(raw);
103 | }
104 |
105 | private void parserRequest(){
106 | // if(Util.isURL(this.url)){
107 | service = new HttpService(this.url);
108 |
109 | try {
110 | IRequestInfo requestInfo = BurpExtender.helpers.analyzeRequest(service, this.raw.getBytes());
111 | requestInfo.getBodyOffset();
112 |
113 | this.method = requestInfo.getMethod();
114 |
115 | for (String header : requestInfo.getHeaders()) {
116 | if (header.indexOf(this.method) >= 0 && header.indexOf("HTTP/") >= 0) {
117 | this.path = header.split(" ")[1];
118 | this.httpversion = header.split(" ")[2];
119 | continue;
120 | }
121 |
122 | if (header.indexOf(":") > 0) {
123 | String key = header.substring(0, header.indexOf(":"));
124 | String value;
125 | try {
126 | value = Util.trimStart(header.substring(header.indexOf(":") + 1));
127 | }catch (Exception e){
128 | value = header.substring(header.indexOf(":") + 1);
129 | }
130 |
131 | if (value.equals(""))
132 | value = " ";
133 | this.headers.put(key, value);
134 | }
135 | }
136 |
137 | if (this.method.equals("POST")) {
138 | // System.out.println("oldd" + data);
139 | // System.out.println(this.raw);
140 | this.data = this.raw.substring(requestInfo.getBodyOffset(), this.raw.length());
141 | // System.out.println("neww" + data);
142 | }
143 | }catch (Exception e){
144 | // BurpExtender.stderr.println(e.getMessage());
145 | }
146 | // }
147 | }
148 |
149 | private void parserRequestOld(){
150 | if(Util.isURL(this.url)){
151 | service = new HttpService(this.url);
152 | String[] rawsArray = this.raw.split(System.lineSeparator());
153 | try {
154 | for (int i = 0; i < rawsArray.length; i++) {
155 | if (i == 0) {
156 | this.method = rawsArray[0].split(" ")[0];
157 | this.path = rawsArray[0].split(" ")[1];
158 | this.httpversion = rawsArray[0].split(" ")[2];
159 | } else if (this.method.equals("POST") && i == rawsArray.length - 1) {
160 | this.data = rawsArray[i].trim();
161 | } else {
162 | if (rawsArray[i].indexOf(": ") > 0) {
163 | String key = rawsArray[i].split(": ")[0];
164 | String value = rawsArray[i].split(": ")[1];
165 | this.headers.put(key.trim(), value.trim());
166 | }
167 | }
168 | }
169 | }catch (Exception e){
170 | BurpExtender.stdout.println(e.getMessage());
171 | }
172 | }
173 | }
174 |
175 |
176 |
177 |
178 |
179 | /**
180 | * 更新请求包的Content-Length头
181 | * 注意:不更新该头部,可能会导致服务端无法获取完整的请求信息。
182 | * @return
183 | */
184 | public void updateContentLength(){
185 | /**
186 | * 在处理GET数据包时,要注意包结果严格来讲最后要有两个\r\n。有的web服务器对数据包要求比较严格,可能会导致请求识别。
187 | * 该问题曾出现在请求某网站的验证码时,返回了403状态。
188 | */
189 | // System.out.println(method);
190 | // System.out.println(data);
191 | if(method.equals("POST")) {
192 | int length = data.length();
193 | headers.put("Content-Length", String.valueOf(length));
194 | String reqLine = String.format("%s %s %s",method,path,httpversion);
195 | reqLine += "\r\n";
196 | for(Map.Entry header:headers.entrySet()){
197 | String line = String.format("%s: %s",header.getKey(),header.getValue());
198 | reqLine += line;
199 | reqLine += "\r\n";
200 | }
201 |
202 | reqLine += "\r\n";
203 | reqLine += data;
204 | this.raw = reqLine;
205 | }
206 | }
207 |
208 |
209 | public byte[] doReust(){
210 | byte[] req = raw.getBytes();
211 | try {
212 | // System.out.println(service);
213 | IHttpRequestResponse reqrsp = BurpExtender.callbacks.makeHttpRequest(service, req);
214 | byte[] response = reqrsp.getResponse();
215 | return response;
216 | }catch (Exception e){
217 | e.printStackTrace();
218 | BurpExtender.stderr.println(e);
219 | }
220 | return null;
221 |
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/main/java/utils/LableParser.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer)
3 | * License: MIT
4 | * autor: c0ny1
5 | * date: 2019-10-20 13:45
6 | * description: 该类用于解析接口请求包中的标签
7 | */
8 | package utils;
9 | import burp.BurpExtender;
10 |
11 | import javax.xml.bind.DatatypeConverter;
12 | import java.io.IOException;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | import static utils.Util.*;
17 |
18 | public class LableParser {
19 | private byte[] byteImage;
20 |
21 |
22 | public LableParser(byte[] byteImage){
23 | this.byteImage = byteImage;
24 | }
25 |
26 |
27 | public String parseAllLable(String str) throws IOException {
28 | String reqTpl = str;
29 |
30 |
31 |
32 | while (reqTpl.indexOf("<@")>=0){
33 | reqTpl = parseOneLable(reqTpl);
34 | }
35 | // System.out.println("1111111111111111111");
36 | // System.out.println(reqTpl);
37 |
38 | if(reqTpl.indexOf("[base64]")>=0){
39 | String b64encode = reqTpl.substring(reqTpl.indexOf("[base64]")+8,reqTpl.lastIndexOf("[base64]"));
40 | String b64decode = new String(base64Decode(b64encode));
41 | reqTpl = reqTpl.replace(String.format("[base64]%s[base64]",b64encode),b64decode);
42 | }
43 | // System.out.println("222222222222222222");
44 | // System.out.println(reqTpl);
45 | return reqTpl;
46 | }
47 |
48 |
49 | /**
50 | * 通过标签位置递归解析标签
51 | * @param str
52 | * @return
53 | */
54 | private String parseOneLable(String str) throws IOException {
55 | String parseRes = "";
56 | String lable = str.substring(str.indexOf("<@")+2,str.indexOf(">"));
57 | if(!lable.equals(null)&&!lable.trim().equals("")) {
58 | String prefix = String.format("<@%s>", lable);
59 | String suffix = String.format("@%s>", lable);
60 | String allContent = str.substring(str.indexOf(prefix), str.indexOf(suffix) + suffix.length());
61 | String subContent = str.substring(str.indexOf(prefix) + prefix.length(), str.indexOf(suffix));
62 |
63 | if (subContent.indexOf("<@") >= 0 && subContent.indexOf(">") >= 0) {
64 | // System.out.println("5555555555555555");
65 | // System.out.println(str);
66 | str = str.replace(subContent, parseOneLable(subContent));
67 | // System.out.println("6666666666666666");
68 | // System.out.println(str);
69 | allContent = str.substring(str.indexOf(prefix), str.indexOf(suffix) + suffix.length());
70 | subContent = str.substring(str.indexOf(prefix) + prefix.length(), str.indexOf(suffix));
71 |
72 | parseRes = str.replace(allContent, encdoeLable(lable, subContent));
73 | } else {
74 | parseRes = str.replace(allContent, encdoeLable(lable, subContent));
75 | }
76 | }
77 | return parseRes;
78 | }
79 |
80 |
81 | /**
82 | * 通过正则表达式解析标签
83 | * 注意:由于内容中若出现\r\n就会导致无法匹配,故暂时弃用!
84 | * @param str
85 | * @return
86 | */
87 | private String parseOneLableByRegular(String str) throws IOException {
88 | String parseRes = "";
89 | String regular1 = "<@(.*?)>";
90 | String lable = matchByRegular(str,regular1);
91 | // System.out.println("123");
92 | if(!lable.equals(null) && !lable.trim().equals("")) {
93 | String regular2 = String.format("<@%s>(.*?)@%s>", lable, lable);
94 | String all_content = matchByRegular(str,regular2,0);
95 | String content = matchByRegular(str,regular2,1);
96 | String sublable = matchByRegular(content,regular1);
97 | if(!sublable.equals(null) && !sublable.trim().equals("")){
98 | str = str.replace(content,parseOneLable(content));
99 | content = matchByRegular(str,regular2,1);
100 | all_content = matchByRegular(str,regular2,0);
101 | parseRes = str.replace(all_content,encdoeLable(lable,content));
102 | }else{
103 | parseRes = str.replace(all_content,encdoeLable(lable,content));
104 | }
105 | }
106 | return parseRes;
107 | }
108 |
109 |
110 | private String encdoeLable(String type,String str) throws IOException { // 处理请求编码
111 | String encodeStr = "";
112 |
113 | switch (type){
114 | case "BASE64":
115 | if(str.startsWith("[base64]")){
116 | str = str.replace("[base64]","");
117 | encodeStr = base64Encode(base64Decode(str));
118 | }else {
119 | encodeStr = base64Encode(str);
120 | }
121 | break;
122 | case "URLENCODE":
123 | if(str.startsWith("[base64]")) {
124 | str = str.replace("[base64]","");
125 | byte[] byteRes = base64Decode(str);
126 | encodeStr = URLEncode(new String(byteRes));
127 | }else {
128 | encodeStr = URLEncode(str);
129 | }
130 | break;
131 | case "IMG_RAW":
132 | // System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaa");
133 | String strr = new String(byteImage);
134 | String words = BurpExtender.gui.tfWords.getText();
135 |
136 |
137 | if (!words.trim().equals("") && strr.contains(words)){
138 | byteImage = dataimgToimg(strr,words);
139 |
140 | }else {
141 | if ((strr.contains("data:image") ) || (strr.contains("data%3Aimage"))) {
142 |
143 | // 0.20 注释下面,原因是响应包里有直接data:image开头的
144 | // if ((strr.contains("data:image") && !strr.startsWith("data:image")) || (strr.contains("data%3Aimage") && !strr.startsWith("data%3Aimage"))) {
145 |
146 |
147 | //strr = new String(byteImage);
148 | strr = strr.replace("\\r\\n","").replace("\\n","");
149 | strr = strr.replace("\\","");
150 | String pattern = "(data:image.*?)[\"|&]|(data%2Aimage.*?)[\"|&]";
151 | Pattern r = Pattern.compile(pattern);
152 | Matcher m = r.matcher(strr);
153 | if (m.find()) {
154 | strr = m.group(0).replace("\"", "").replace("&", "").replace("Base64:", "").replace("base64:", "");
155 | }
156 | if (!strr.contains("data:image")) {
157 | strr = "o;base64," + strr;
158 | }
159 | // BurpExtender.stdout.println(strr);
160 | // byteImage = strr.getBytes();
161 | byteImage = DatatypeConverter.parseBase64Binary(strr.substring(strr.indexOf(",") + 1));
162 | }
163 | }
164 | //注意:byte[]转string后,string再转byte[]无法还原的,故该地方采用base64编码存储byte[],等到使用时在解码为byte[]。
165 | String base64Img = base64Encode(byteImage);
166 | encodeStr = "[base64]" + base64Img + "[base64]";
167 | // System.out.println(encodeStr);
168 | break;
169 | default:
170 | encodeStr = str;
171 | break;
172 | }
173 | return encodeStr;
174 | }
175 |
176 | public static void main(String[] args) {
177 | // String str = "a<@URLENCODE><@BASE64>1@BASE64>@URLENCODE>b<@BASE64>sd<@IMG_RAW>@IMG_RAW>sds\n\rsdsdd@BASE64>";
178 | // str = "<@URLENCODE><@1URLENCODE>+11111@1URLENCODE>@URLENCODE>";
179 | // LableParser lableParser = new LableParser("123".getBytes());
180 | // String res = lableParser.parseAllLable(str);
181 | // System.out.println(res);
182 |
183 | String reqTpl = "a[base64]eHh4[base64]b";
184 | String base64encode = reqTpl.substring(reqTpl.indexOf("[base64]")+8,reqTpl.lastIndexOf("[base64]"));
185 | String base64decode = new String(base64Decode(base64encode));
186 | String str = reqTpl.replace(String.format("[base64]%s[base64]",base64encode),base64decode);
187 | // System.out.println(str);
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import ui.GUI;
4 | import ui.Menu;
5 | import utils.Util;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 | import java.io.IOException;
10 | import java.io.PrintWriter;
11 | import java.util.Arrays;
12 | import java.util.List;
13 |
14 | /* loaded from: captcha-killer-modified-0.21-jdk8.jar:burp/BurpExtender.class */
15 | public class BurpExtender implements IBurpExtender, ITab, IIntruderPayloadGeneratorFactory, IIntruderPayloadGenerator, IHttpListener ,IIntruderPayloadProcessor{
16 | public static IBurpExtenderCallbacks callbacks;
17 | public static IExtensionHelpers helpers;
18 | public static boolean isShowIntruderResult = true;
19 | public static PrintWriter stdout;
20 | public static PrintWriter stderr;
21 | public static GUI gui;
22 | private String extensionName = "captcha-killer-modified";
23 | private String version = "0.24.1";
24 | public String processname = "captcha-killer-modified";
25 | public static Boolean Isreplace = false;
26 |
27 | @Override // burp.IBurpExtender
28 | public void registerExtenderCallbacks(IBurpExtenderCallbacks calllbacks) {
29 | callbacks = calllbacks;
30 | helpers = calllbacks.getHelpers();
31 |
32 |
33 | stdout = new PrintWriter(calllbacks.getStdout(), true);
34 | stderr = new PrintWriter(calllbacks.getStderr(), true);
35 | gui = new GUI();
36 | callbacks.setExtensionName(String.format("%s %s", this.extensionName, this.version));
37 | calllbacks.registerContextMenuFactory(new Menu());
38 | calllbacks.registerIntruderPayloadGeneratorFactory(this);
39 | callbacks.registerIntruderPayloadProcessor(this);
40 | callbacks.registerHttpListener(this);
41 | stdout = new PrintWriter(callbacks.getStdout(), true);
42 | stderr = new PrintWriter(callbacks.getStderr(), true);
43 | SwingUtilities.invokeLater(new Runnable() { // from class: burp.BurpExtender.1
44 | @Override // java.lang.Runnable
45 | public void run() {
46 | BurpExtender burpExtender = BurpExtender.this;
47 | BurpExtender.callbacks.addSuiteTab(BurpExtender.this);
48 | }
49 | });
50 | stdout.println(Util.getBanner(this.extensionName, this.version));
51 | }
52 |
53 | @Override // burp.IHttpListener
54 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
55 | if (!(toolFlag == 4 || toolFlag == 32 || toolFlag == 64)) {
56 | return;
57 | }else if ( toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER || toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER) {
58 | if (messageIsRequest) {
59 | byte[] request = messageInfo.getRequest();
60 | IRequestInfo iRequestInfo = helpers.analyzeRequest(messageInfo);
61 | List headersList = iRequestInfo.getHeaders();
62 | int bodyOffset = iRequestInfo.getBodyOffset();
63 | byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length);
64 |
65 | // String cap = "";
66 | // stdout.println(headersList.contains("@captcha-killer-modified@"));
67 | // stdout.println(headersList);
68 | // stdout.println(new String(body));
69 | if ( (headersList.get(0).contains("@captcha@") || new String(body).contains("@captcha@") || ( (toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER ) && headersList.get(0).contains("@captcha-killer-modified@") ) || ((toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER ) && new String(body).contains("@captcha-killer-modified@") ) || ( toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER )) && gui.getUsebutton() ) {
70 |
71 | BurpExtender.stdout.println(getGeneratorName());
72 | try {
73 | if ( (toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER && headersList.contains("@captcha@"))
74 | ||
75 | (toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER && headersList.contains("@captcha-killer-modified@"))
76 | ||
77 | headersList.contains("@captcha@")
78 | ||
79 | new String(body).contains("@captcha@")
80 | ||
81 | ( (toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER ) && headersList.contains("@captcha-killer-modified@") )
82 | ||
83 | ((toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER ) && new String(body).contains("@captcha-killer-modified@") && new String(body).contains("@captcha@")) ) {
84 | // BurpExtender.stdout.println(Arrays.toString(gui.byteImg));
85 | BurpExtender.gui.cap = GUI.identifyCaptchas(gui.getInterfaceURL().getText(), gui.getTaInterfaceTmplReq().getText(), BurpExtender.gui.byteImg, gui.getCbmRuleType().getSelectedIndex(), gui.getRegular().getText());
86 |
87 | BurpExtender.gui.tfcapex.setText(gui.cap);
88 | }
89 |
90 | } catch (IOException e) {
91 | e.printStackTrace();
92 | }
93 |
94 | int i = 0;
95 | for (String singleheader : headersList) {
96 | headersList.set(i, singleheader.replace("@captcha-killer-modified@", gui.tokenwords));
97 | i++;
98 | }
99 |
100 | byte[] httpmsgresp = helpers.buildHttpMessage(headersList, new String(body).replace("@captcha-killer-modified@", gui.tokenwords).replace("@captcha@", BurpExtender.gui.cap).getBytes());
101 | messageInfo.setRequest(httpmsgresp);
102 |
103 | Isreplace = true;
104 | }
105 |
106 |
107 | } else if (!messageIsRequest && Isreplace) {
108 |
109 | GUI.GetCaptchaThread thread = new GUI.GetCaptchaThread(gui.tfURL.getText(), gui.taRequest.getText());
110 | thread.start();
111 | try {
112 | Thread.sleep(1250);
113 | } catch (InterruptedException e) {
114 | e.printStackTrace();
115 | }
116 | // stdout.println( "response:" + Arrays.toString(BurpExtender.gui.byteImg).length());
117 | // stdout.println(Isreplace);
118 | // stdout.println("+++++++++++++++++++++++");
119 | Isreplace = false;
120 | }
121 | }
122 | }
123 |
124 | @Override // burp.ITab
125 | public String getTabCaption() {
126 | return this.extensionName;
127 | }
128 |
129 | @Override // burp.ITab
130 | public Component getUiComponent() {
131 | return gui.getComponet();
132 | }
133 |
134 | @Override // burp.IIntruderPayloadGenerator
135 | public boolean hasMorePayloads() {
136 | return true;
137 | }
138 |
139 | @Override // burp.IIntruderPayloadGenerator
140 | public byte[] getNextPayload(byte[] bytes) {
141 |
142 | GeneratePayloadSwingWorker gpsw = new GeneratePayloadSwingWorker();
143 | gpsw.execute();
144 | try {
145 | Object result = gpsw.get();
146 | return (byte[]) result;
147 | } catch (Exception e) {
148 | e.printStackTrace();
149 | return String.format("Erro: %s", e.getMessage()).getBytes();
150 | }
151 | }
152 |
153 | @Override // burp.IIntruderPayloadGenerator
154 | public void reset() {
155 | }
156 |
157 | @Override // burp.IIntruderPayloadGeneratorFactory
158 | public String getGeneratorName() {
159 | return this.processname;
160 | }
161 |
162 | @Override // burp.IIntruderPayloadGeneratorFactory
163 | public IIntruderPayloadGenerator createNewInstance(IIntruderAttack iIntruderAttack) {
164 | return this;
165 | }
166 |
167 | @Override
168 | public String getProcessorName() {
169 | return "captcha";
170 | }
171 |
172 | @Override
173 | public byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue) {
174 | return new byte[0];
175 | }
176 | }
--------------------------------------------------------------------------------
/src/main/java/burp/code.java:
--------------------------------------------------------------------------------
1 | ///**
2 | // * MIT License
3 | // *
4 | // * Copyright (c) 2019 c0ny1
5 | // *
6 | // * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // * of this software and associated documentation files (the "Software"), to deal
8 | // * in the Software without restriction, including without limitation the rights
9 | // * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // * copies of the Software, and to permit persons to whom the Software is
11 | // * furnished to do so, subject to the following conditions:
12 | // *
13 | // * The above copyright notice and this permission notice shall be included in all
14 | // * copies or substantial portions of the Software.
15 | // *
16 | // * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // * SOFTWARE.
23 | // */
24 | //package burp;
25 | //
26 | //import ui.GUI;
27 | //import ui.Menu;
28 | //import utils.Util;
29 | //
30 | //import javax.swing.*;
31 | //import java.awt.*;
32 | //import java.io.IOException;
33 | //import java.io.PrintWriter;
34 | //import java.util.Arrays;
35 | //import java.util.List;
36 | //
37 | //public class BurpExtender implements IBurpExtender,ITab,IIntruderPayloadGeneratorFactory, IIntruderPayloadGenerator,IHttpListener{
38 | // public static IBurpExtenderCallbacks callbacks;
39 | // public static IExtensionHelpers helpers;
40 | // private String extensionName = "captcha-killer-modified";
41 | // private String version ="0.23-beta3";
42 | // public static boolean isShowIntruderResult = true; // 识别结果是否显示Intruder模块结果
43 | // public static PrintWriter stdout;
44 | // public static PrintWriter stderr;
45 | // public static GUI gui;
46 | // public Boolean Isreplace = false;
47 | // public Boolean isCaptcha = false;
48 | //
49 | //
50 | // @Override
51 | // public void registerExtenderCallbacks(IBurpExtenderCallbacks calllbacks) {
52 | // this.callbacks = calllbacks;
53 | // this.helpers = calllbacks.getHelpers();
54 | // this.stdout = new PrintWriter(calllbacks.getStdout(),true);
55 | // this.stderr = new PrintWriter(calllbacks.getStderr(),true);
56 | // gui = new GUI();
57 | // callbacks.setExtensionName(String.format("%s %s",extensionName,version));
58 | // calllbacks.registerContextMenuFactory(new Menu());
59 | // calllbacks.registerIntruderPayloadGeneratorFactory(this);
60 | // callbacks.registerHttpListener(BurpExtender.this); // 注册 HttpListener 接口
61 | //
62 | // stdout = new PrintWriter(callbacks.getStdout(),true);
63 | // stderr = new PrintWriter(callbacks.getStderr(),true);
64 | // SwingUtilities.invokeLater(new Runnable() {
65 | // @Override
66 | // public void run() {
67 | // BurpExtender.this.callbacks.addSuiteTab(BurpExtender.this);
68 | // }
69 | // });
70 | // stdout.println(Util.getBanner(extensionName,version));
71 | //
72 | //
73 | // }
74 | //
75 | // @Override
76 | // public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
77 | // {
78 | //// isCaptcha = false;
79 | //
80 | // if ( toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER || toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER){
81 | //
82 | // byte[] request = messageInfo.getRequest();
83 | // IRequestInfo iRequestInfo = helpers.analyzeRequest(messageInfo);
84 | // // 获取请求中的所有参数
85 | // List headersList = iRequestInfo.getHeaders();
86 | // int bodyOffset = iRequestInfo.getBodyOffset();
87 | // byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length);
88 | // int i = 0;
89 | //
90 | // for (String singleheader : headersList) {
91 | // headersList.set(i ,singleheader.replace("@captcha-killer-modified@",gui.tokenwords));
92 | // i++;
93 | // if (singleheader.contains("@captcha@")||singleheader.contains("@captcha-killer-modified@") )
94 | // isCaptcha = true;
95 | // }
96 | //
97 | //
98 | // String cap = "";
99 | // if ( isCaptcha || new String(body).contains("@captcha@") || (new String(body).contains("@captcha-killer-modified@") ) ) {
100 | // try {
101 | // Isreplace = true;
102 | // isCaptcha = true;
103 | //
104 | // } catch (Exception e) {
105 | // e.printStackTrace();
106 | // }
107 | // }
108 | // byte[] httpmsgresp = null;
109 | // int j = 0;
110 | //
111 | // stdout.println(isCaptcha);
112 | // if (messageIsRequest && isCaptcha) {
113 | // try {
114 | // cap = GUI.identifyCaptchas(gui.getInterfaceURL().getText(), gui.getTaInterfaceTmplReq().getText(), gui.byteImg, gui.getCbmRuleType().getSelectedIndex(), gui.getRegular().getText());
115 | // } catch (IOException e) {
116 | // e.printStackTrace();
117 | // }
118 | // if (isCaptcha) {
119 | // for (String singleheader : headersList) {
120 | // headersList.set(j ,singleheader.replace("@captcha@",cap));
121 | //// headersList.set(j ,singleheader.replace("@captcha@",gui.tokenwords));
122 | // j++;
123 | //// BurpExtender.stdout.println(headersList.get(j-1));
124 | // }
125 | //
126 | //
127 | // httpmsgresp = helpers.buildHttpMessage(headersList, (new String(body).replace("@captcha-killer-modified@", gui.tokenwords).replace("@captcha@", cap).getBytes()));
128 | // }
129 | //// else
130 | //// httpmsgresp = helpers.buildHttpMessage(headersList, (new String(body).replace("@captcha-killer-modified@",gui.tokenwords).replace("@captcha@",cap).getBytes()));
131 | //
132 | // messageInfo.setRequest(httpmsgresp);
133 | //
134 | // } else if ( !messageIsRequest ) {
135 | //
136 | // if ( Isreplace ) {
137 | // GUI.GetCaptchaThread thread = new GUI.GetCaptchaThread(gui.tfURL.getText(), gui.taRequest.getText());
138 | // thread.start();
139 | // Isreplace = false;
140 | // }
141 | // }
142 | // }
143 | //
144 | //
145 | // }
146 | //
147 | // @Override
148 | // public String getTabCaption() {
149 | // return extensionName;
150 | // }
151 | //
152 | // @Override
153 | // public Component getUiComponent() {
154 | // return gui.getComponet();
155 | // }
156 | //
157 | // @Override
158 | // public boolean hasMorePayloads() {
159 | // return true;
160 | // }
161 | //
162 | // @Override
163 | // public byte[] getNextPayload(byte[] bytes) {
164 | //// if(!Util.isURL(gui.getInterfaceURL().getText())){
165 | //// return "Interface URL format invalid".getBytes();
166 | //// }
167 | ////
168 | //// CaptchaEntity cap = new CaptchaEntity();
169 | //// int count = 0;
170 | //// try {
171 | //// byte[] byteImg = Util.requestImage(gui.getCaptchaURL(),gui.getCaptchaReqRaw());
172 | //// //遗留问题:burp自带的发包,无法指定超时。如果访问速度过快,这里可能为空。
173 | //// while (count < 3){
174 | //// cap = GUI.identifyCaptcha(gui.getInterfaceURL().getText(),gui.getTaInterfaceTmplReq().getText(),byteImg,gui.getCbmRuleType().getSelectedIndex(),gui.getRegular().getText());
175 | //// if(cap.getResult() == null || cap.getResult().trim().equals("")){
176 | //// Thread.sleep(1000);
177 | //// count += 1;
178 | //// }else{
179 | //// break;
180 | //// }
181 | //// }
182 | ////
183 | //// synchronized (gui.captcha){
184 | //// int row = gui.captcha.size();
185 | //// gui.captcha.add(cap);
186 | //// gui.getModel().fireTableRowsInserted(row,row);
187 | //// }
188 | //// } catch (Exception e) {
189 | //// cap.setResult(e.getMessage());
190 | //// }
191 | ////
192 | //// return cap.getResult().getBytes();
193 | // GeneratePayloadSwingWorker gpsw = new GeneratePayloadSwingWorker();
194 | // gpsw.execute();
195 | // try {
196 | // Object result = gpsw.get();
197 | //// BurpExtender.stdout.println(new String((byte[])result));
198 | // return (byte[])result;
199 | // }catch (Exception e){
200 | // e.printStackTrace();
201 | // return String.format("Erro: %s",e.getMessage()).getBytes();
202 | // }
203 | // }
204 | //
205 | // @Override
206 | // public void reset() {
207 | //
208 | // }
209 | //
210 | // @Override
211 | // public String getGeneratorName() {
212 | // return this.extensionName;
213 | // }
214 | //
215 | // @Override
216 | // public IIntruderPayloadGenerator createNewInstance(IIntruderAttack iIntruderAttack) {
217 | // return this;
218 | // }
219 | //}
220 |
--------------------------------------------------------------------------------
/codereg.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2024/4/19 19:25
3 | # @Software: f0ng
4 | import argparse
5 | import ddddocr # 导入 ddddocr
6 | from aiohttp import web
7 | import base64
8 |
9 | print(
10 | "欢迎使用captcha-killer-modified服务端脚本,项目地址:https://github.com/f0ng/captcha-killer-modified\n玲珑安全漏洞挖掘培训学习联系微信: f-f0ng 备注:玲珑安全培训\n\n")
11 | parser = argparse.ArgumentParser()
12 |
13 | parser.add_argument("-p", help="http port", default="8888")
14 | args = parser.parse_args()
15 | ocr = ddddocr.DdddOcr()
16 | port = args.p
17 |
18 | auth_base64 = "f0ngauth" # 可自定义auth认证
19 |
20 |
21 | # 识别纯整数0-9
22 | async def handle_cb00(request):
23 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
24 | return web.Response(text='Forbidden', status='403')
25 | ocr.set_ranges(0)
26 | img_base64 = await request.text()
27 | img_bytes = base64.b64decode(img_base64)
28 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
29 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
30 | res = ocr.classification(img_bytes,probability=True)
31 | s = ""
32 | for i in res['probability']:
33 | s += res['charsets'][i.index(max(i))]
34 | print(s)
35 | return web.Response(text=s)
36 |
37 | # 识别纯小写英文a-z
38 | async def handle_cb01(request):
39 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
40 | return web.Response(text='Forbidden', status='403')
41 | ocr.set_ranges(1)
42 | img_base64 = await request.text()
43 | img_bytes = base64.b64decode(img_base64)
44 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
45 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
46 | res = ocr.classification(img_bytes,probability=True)
47 | s = ""
48 | for i in res['probability']:
49 | s += res['charsets'][i.index(max(i))]
50 | print(s)
51 | return web.Response(text=s)
52 |
53 | # 识别纯大写英文A-Z
54 | async def handle_cb02(request):
55 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
56 | return web.Response(text='Forbidden', status='403')
57 | ocr.set_ranges(2)
58 | img_base64 = await request.text()
59 | img_bytes = base64.b64decode(img_base64)
60 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
61 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
62 | res = ocr.classification(img_bytes,probability=True)
63 | s = ""
64 | for i in res['probability']:
65 | s += res['charsets'][i.index(max(i))]
66 | print(s)
67 | return web.Response(text=s)
68 |
69 | # 识别小写英文a-z + 大写英文A-Z
70 | async def handle_cb03(request):
71 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
72 | return web.Response(text='Forbidden', status='403')
73 | ocr.set_ranges(3)
74 | img_base64 = await request.text()
75 | img_bytes = base64.b64decode(img_base64)
76 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
77 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
78 | res = ocr.classification(img_bytes,probability=True)
79 | s = ""
80 | for i in res['probability']:
81 | s += res['charsets'][i.index(max(i))]
82 | print(s)
83 | return web.Response(text=s)
84 |
85 | # 识别小写英文a-z + 整数0-9
86 | async def handle_cb04(request):
87 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
88 | return web.Response(text='Forbidden', status='403')
89 | ocr.set_ranges(4)
90 | img_base64 = await request.text()
91 | img_bytes = base64.b64decode(img_base64)
92 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
93 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
94 | res = ocr.classification(img_bytes,probability=True)
95 | s = ""
96 | for i in res['probability']:
97 | s += res['charsets'][i.index(max(i))]
98 | print(s)
99 | return web.Response(text=s)
100 |
101 | # 识别大写英文A-Z + 整数0-9
102 | async def handle_cb05(request):
103 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
104 | return web.Response(text='Forbidden', status='403')
105 | ocr.set_ranges(5)
106 | img_base64 = await request.text()
107 | img_bytes = base64.b64decode(img_base64)
108 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
109 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
110 | res = ocr.classification(img_bytes,probability=True)
111 | s = ""
112 | for i in res['probability']:
113 | s += res['charsets'][i.index(max(i))]
114 | print(s)
115 | return web.Response(text=s)
116 |
117 |
118 | # 识别小写英文a-z + 大写英文A-Z + 整数0-9
119 | async def handle_cb06(request):
120 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
121 | return web.Response(text='Forbidden', status='403')
122 | ocr.set_ranges(6)
123 | img_base64 = await request.text()
124 | img_bytes = base64.b64decode(img_base64)
125 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
126 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
127 | res = ocr.classification(img_bytes,probability=True)
128 | s = ""
129 | for i in res['probability']:
130 | s += res['charsets'][i.index(max(i))]
131 | print(s)
132 | return web.Response(text=s)
133 |
134 | # 识别自定义字符,默认为识别算术
135 | async def handle_cb000(request):
136 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
137 | return web.Response(text='Forbidden', status='403')
138 | ocr.set_ranges(request.headers.get('ranges'))
139 | print(request.headers.get('ranges'))
140 | img_base64 = await request.text()
141 | img_bytes = base64.b64decode(img_base64)
142 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
143 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
144 | res = ocr.classification(img_bytes,probability=True)
145 | s = ""
146 | for i in res['probability']:
147 | s += res['charsets'][i.index(max(i))]
148 | print(s)
149 | if '+' in s:
150 | zhi = int(s.split('+')[0]) + int(s.split('+')[1][:-1])
151 | print(zhi)
152 | return web.Response(text=str(zhi))
153 | elif '-' in s:
154 | zhi = int(s.split('-')[0]) - int(s.split('-')[1][:-1])
155 | print(zhi)
156 | return web.Response(text=str(zhi))
157 | elif '*' in s:
158 | zhi = int(s.split('*')[0]) * int(s.split('*')[1][:-1])
159 | print(zhi)
160 | return web.Response(text=str(zhi))
161 | elif 'x' in s:
162 | zhi = int(s.split('x')[0]) * int(s.split('x')[1][:-1])
163 | print(zhi)
164 | return web.Response(text=str(zhi))
165 | elif '/' in s:
166 | zhi = int(s.split('/')[0]) / int(s.split('/')[1][:-1])
167 | return web.Response(text=str(zhi))
168 | else:
169 | return web.Response(text=s)
170 |
171 |
172 |
173 | # 识别常规验证码
174 | async def handle_cb2(request):
175 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
176 | return web.Response(text='Forbidden', status='403')
177 | # print(await request.text())
178 | img_base64 = await request.text()
179 | img_bytes = base64.b64decode(img_base64)
180 | # return web.Response(text=ocr.classification(img_bytes)[0:4]) 验证码取前四位
181 | # return web.Response(text=ocr.classification(img_bytes)[0:4].replace("0","o")) 验证码取前四位、验证码中的0替换为o
182 | res = ocr.classification(img_bytes)
183 | print(res)
184 |
185 | return web.Response(text=ocr.classification(img_bytes)[0:10])
186 |
187 |
188 | # 识别算术验证码
189 | async def handle_cb(request):
190 | zhi = ""
191 | if request.headers.get('Authorization') != 'Basic ' + auth_base64:
192 | return web.Response(text='Forbidden', status='403')
193 | # print(await request.text())
194 | img_base64 = await request.text()
195 | img_bytes = base64.b64decode(img_base64)
196 | res = ocr.classification(img_bytes).replace("=", "").replace("?", "")
197 | print(res)
198 | if '+' in res:
199 | zhi = int(res.split('+')[0]) + int(res.split('+')[1][:-1])
200 | print(zhi)
201 | return web.Response(text=str(zhi))
202 | elif '-' in res:
203 | zhi = int(res.split('-')[0]) - int(res.split('-')[1][:-1])
204 | print(zhi)
205 | return web.Response(text=str(zhi))
206 | elif '*' in res:
207 | zhi = int(res.split('*')[0]) * int(res.split('*')[1][:-1])
208 | print(zhi)
209 | return web.Response(text=str(zhi))
210 | elif 'x' in res:
211 | zhi = int(res.split('x')[0]) * int(res.split('x')[1][:-1])
212 | print(zhi)
213 | return web.Response(text=str(zhi))
214 | elif '/' in res:
215 | zhi = int(res.split('/')[0]) / int(res.split('/')[1][:-1])
216 | return web.Response(text=str(zhi))
217 | else:
218 | return web.Response(text=res)
219 |
220 |
221 | app = web.Application()
222 | app.add_routes([
223 | web.post('/reg2', handle_cb), # 识别算数验证码
224 | web.post('/reg', handle_cb2), # 识别常规验证码
225 |
226 | web.post('/reg00', handle_cb00), # 识别纯整数0-9
227 | web.post('/reg01', handle_cb01), # 识别纯小写英文a-z
228 | web.post('/reg02', handle_cb02), # 识别纯大写英文A-Z
229 | web.post('/reg03', handle_cb03), # 识别小写英文a-z + 大写英文A-Z
230 | web.post('/reg04', handle_cb04), # 识别小写英文a-z + 整数0-9
231 | web.post('/reg05', handle_cb05), # 识别大写英文A-Z + 整数0-9
232 | web.post('/reg06', handle_cb06), # 识别小写英文a-z + 大写英文A-Z + 整数0-9
233 | web.post('/reg000', handle_cb000), # 识别自定义
234 | ])
235 |
236 | if __name__ == '__main__':
237 | web.run_app(app, port=int(port))
238 |
239 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | * [验证码二次处理案例](#6-验证码进行二次处理的案例验证码为gif图且验证码具体是在gif图的第二帧无法直接识别)
2 | * [验证码调试方法](#7-验证码识别顺序错误验证码识别正确但是intruder登录接口的验证码识别错误的调试方法)
3 | * [验证码响应包有token参数,登录需校验情况如何设置](#8-验证码中有token等校验参数返回登录包中有校验token如何进行设置爆破)
4 | * [验证码中为base64,如何识别验证码](#12-验证码中为base64编码如何识别验证码)
5 | * [验证码响应包有明文验证码,如何配合工具使用](#13-验证码响应包有明文验证码如何配合工具使用)
6 | * [intruder模块都是一个验证码](#14-intruder模块都是一个验证码)
7 | * [一般的数字运算验证码](#20-数字运算验证码)
8 | * [混淆、变形的数字运算验证码](#21-混淆、变形的数字运算验证码)
9 |
10 | # 有问题请在FAQ或者README寻找一下,如果没找到请提issue
11 | # 1-用法
12 |
13 | #### [releases](https://github.com/f0ng/captcha-killer-modified/releases/)下载最新插件与验证码识别端(`captcha-killer-modified.jar`、`codereg.py`)
14 | #### 使用Burp加载`captcha-killer-modified.jar`
15 | #### 安装python依赖,根据[requirement.txt](https://github.com/f0ng/captcha-killer-modified/blob/main/requirement.txt)的内容进行下载依赖(pip install -r requirement.txt )
16 | #### 再使用`python3 codereg.py`开启验证码识别模块,前提安装[ddddocr](https://github.com/sml2h3/ddddocr)
17 | #### 模板
18 | ```
19 | POST /reg HTTP/1.1
20 | Host: 127.0.0.1:8888
21 | Authorization:Basic f0ngauth
22 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0
23 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
24 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
25 | Accept-Encoding: gzip, deflate
26 | Connection: keep-alive
27 | Upgrade-Insecure-Requests: 1
28 | Content-Type: application/x-www-form-urlencoded
29 | Content-Length: 8332
30 |
31 | <@BASE64><@IMG_RAW>@IMG_RAW>@BASE64>
32 | ```
33 | #### 接口地址设置为`http://127.0.0.1:8888`,即可开始识别
34 | #### 其余用法移步[captcha-killer用法](https://gv7.me/articles/2019/burp-captcha-killer-usage/)
35 |
36 | # 2-加载插件错误
37 |
38 | #### 0x01 检查加载的是否是jar文件
39 | #### 0x02 与启动burp的jdk版本有关,可以适当选择与`jdk10`相近的版本启动
40 |
41 | # 3-验证码识别错误
42 |
43 | #### 1.查看模板是否正确,模板
44 | #### 2.检查`python3 codereg.py`是否正常启动,可以本地尝试使用ddddocr进行识别验证码验证
45 | #### 3.重新下载本插件与验证码服识别端进行加载
46 |
47 | # 4.报错 `Typeerror: classification() got an unexpected keyword argument img_base64`或者网页返回`server got ifself in trouble`的500报错
48 |
49 | #### 4 感谢ekko-zhao师傅反馈
50 | #### 修改`codereg.py`源码如下(记得`import base64`)
51 | ```python
52 | async def handle_cb(request) :
53 | img_base64 = await request.text()
54 | img_bytes = base64.b64decode(img_base64)
55 | return web. Response(text=ocr.classification(img_bytes))
56 | ```
57 |
58 |
59 | # 5-无法识别验证码
60 |
61 | #### 1. 一般问题出在新版burp上,由于jdk的原因导致代码运行错误,可以下载相应jdk版本的插件
62 |
63 |
64 |
65 | #### 2. 点击识别无响应,可以将服务识别端`codereg.py`放置在VPS上,使用公网地址访问即可
66 |
67 | # 6-验证码进行二次处理的案例(验证码为gif图,且验证码具体是在gif图的第二帧,无法直接识别)
68 |
69 | 可以自写中转,将处理后的验证码通过接口返回,如下代码:
70 | ```python
71 | # -*- coding: utf-8 -*-
72 | # @Time : 2022/11/29 4:22 下午
73 | # @Software: f0ng
74 | from flask import Flask,Response,render_template
75 |
76 | app = Flask(__name__)
77 |
78 | import requests,time
79 |
80 | proxies = {
81 | 'http':'127.0.0.1:8080',
82 | 'https':'127.0.0.1:8080'
83 | }
84 |
85 | def shibie():
86 | headers = { # 校验用户信息
87 | "Cookie":"ASP.NET_SessionId=fmbv2azmygla0hfkt3v5dupt",
88 | }
89 |
90 | url = 'http://xxxxx.xxxx/CreateCode' # 目标下载链接
91 | r = requests.get(url ,headers=headers ,proxies=proxies) # 发送请求
92 | with open ('1.gif', 'wb+') as f:
93 | f.write(r.content)
94 | f.close
95 |
96 | from PIL import Image, ImageFile
97 | ImageFile.LOAD_TRUNCATED_IMAGES = True
98 |
99 | import os
100 | gifFileName = '1.gif'
101 | # 使用Image模块的open()方法打开gif动态图像时,默认是第一帧
102 | im = Image.open(gifFileName)
103 | pngDir = gifFileName[:-4]
104 | # 创建存放每帧图片的文件夹(文件夹名与图片名称相同)
105 | if os.path.exists(pngDir) == False:
106 | os.mkdir(pngDir)
107 | try:
108 | while True:
109 | # 保存当前帧图片
110 | current = im.tell()
111 | im.save(pngDir + '/' + str(current) + '.png')
112 | # 获取下一帧图片
113 | im.seek(current + 1)
114 | except EOFError:
115 | print("pass")
116 |
117 | @app.route("/1")
118 | def index3():
119 | shibie()
120 |
121 | with open("1/0.png", 'rb') as f:
122 | image = f.read()
123 | resp = Response(image, mimetype="image/jpeg")
124 | return resp
125 |
126 | if __name__ == '__main__':
127 | app.debug = True # 设置调试模式,生产模式的时候要关掉debug
128 | app.run(host="0.0.0.0", port="8888")
129 | ```
130 |
131 | 代码先将gif截取成1.png,再通过接口`/1`返回1.png的图片,故获取验证码的请求可以改为本地的`/1`请求,如下图
132 |
133 |
134 |
135 | 实现成功
136 |
137 |
138 |
139 | # 7-验证码识别顺序错误(验证码识别正确,但是intruder登录接口的验证码识别错误)的调试方法
140 |
141 | 插件里获取验证码->识别->手动在repeater输入->显示验证码是否正确
142 |
143 | 如果验证码不正确,说明请求验证码的cookie等校验用户信息不一致,需要加上该cookie等校验用户信息的参数,如果为二次处理验证码的请求,需在请求验证码的代码中带上用户cookie等校验用户信息的参数。
144 |
145 | # 8-验证码中有token等校验参数返回,登录包中有校验token,如何进行设置爆破?
146 |
147 | 输入响应提取的正则,提取校验的关键字
148 |
149 |
150 |
151 | 在intruder中增加校验的参数`@captcha-killer-modified@`
152 |
153 |
154 |
155 | 在logger中可以看到实际的请求
156 |
157 |
158 |
159 |
160 | # 9-forbideen响应、403响应解决
161 |
162 | 右键模板,选择模板库->ddddocr即可
163 |
164 | ~~由于后续脚本增加了Basic认证,可以自行添加Basic认证头即可~~
165 |
166 | # 10-提取关键字错误
167 |
168 | 1. 可以更换关键字,或者直接输入类似`"iVBO`图片文件头后的base64编码如下:
169 |
170 |
171 |
172 | 感谢微信群师傅@手挥五弦 提供的解决方法
173 |
174 | 2. 如果遇到base64的,以下案例可以使用`data:image/jpeg;base64`,其他情况类似
175 |
176 |
177 |
178 |
179 | # 11-使用@captcha@替代验证码参数,导致爆破错误
180 |
181 | 增加intruder的爆破时间,验证码加载需要时间
182 |
183 | # 12-验证码中为base64编码,如何识别验证码
184 |
185 | json格式中,在关键字中填写关键字;其他格式可以参考[10-提取关键字错误](#10-提取关键字错误)
186 |
187 |
188 |
189 | # 13-验证码响应包有明文验证码,如何配合工具使用?
190 |
191 | 当遇到了验证码在验证码响应包里出现,如下:
192 |
193 |
194 |
195 | 可以直接通过工具提取验证码的值
196 |
197 |
198 |
199 | 在burp模块中使用@captcha-killer-modified@替换验证码参数即可
200 | repeater举例,请求为
201 |
202 |
203 |
204 | 实际请求为
205 |
206 |
207 |
208 | # 14-intruder模块都是一个验证码
209 |
210 | 1. 查看intruder的attack type是否为`Pitchfork`,如果不是,那就选中这个模式,再测试,否则会出现验证码固定的问题
211 |
212 |
213 |
214 | 2. 选中`是否使用该插件`再进行测试
215 |
216 | # 15-获取验证码接口400
217 |
218 | 1. 通过logger查看请求包,打开`\n`视图,查看是不是以`\n`换行,如果是,请用`send to captcha panel`发送验证码,正常情况下,http包是以`\r\n`换行
219 | 2. 排查是否有其他服务占用了8888端口(默认py文件会起服务部署在8888端口)
220 | 3. 一般问题出在新版burp上,由于jdk的原因导致代码运行错误,可以下载相应jdk版本的插件
221 |
222 |
223 |
224 | 4. 点击识别无响应,可以将服务识别端`codereg.py`放置在VPS上,使用公网地址访问即可
225 |
226 | # 16-获取的都是一个验证码
227 |
228 | `0.24.1`临时更新插件按钮,开启按钮才会使用该插件
229 |
230 |
231 |
232 |
233 | # 17-报错(module 'PIL.Image' has no attribute 'ANTIALIAS')
234 |
235 | 降级Pillow的版本,比如使用9.5.0版本
236 |
237 | `pip uninstall -y Pillow`
238 |
239 | `pip install Pillow==9.5.0`
240 |
241 | # 18-无法获取验证码
242 |
243 | ~~更换burp版本进行尝试,目前原因未知~~ 使用0.25.3版本即可
244 |
245 | # 19-The port is required to be int
246 |
247 | 运行脚本报错,The port is required to be int,将脚本中`default="8888"`更改为`default=8888`再次运行即可
248 |
249 | # 20-数字运算验证码
250 |
251 | 使用`reg2`模板【可能不够准确】
252 |
253 |
254 |
255 | # 21-混淆、变形的数字运算验证码
256 |
257 | 使用`reg3`模板【默认不支持该接口,需捐赠,可试用验证码接口,捐赠到一定额度可以有若依验证码的识别接口,以及进内部群,内部群成员捐赠有折扣,后面可能会分享其他类型的技术,不局限于captcha-killer-modified这个插件,后期会酌情涨价】
258 |
259 |
260 |
261 | # 22-验证码多线程、验证码识别错误自动再次识别直到每次请求都识别成功
262 |
263 | ~~在Burp Suite中无法实现,但是yakit中的插件可以实现(笔者开发,未公开)【默认不支持该接口,需捐赠捐赠到一定额度可以有该功能,以及进内部群,内部群成员捐赠有折扣,目前测试一秒两次登录请求、验证码错误会自动纠错】~~
264 |
265 | # 23-如何确认问题?
266 | 1. 不能获取验证码,说明插件配置有问题,也可能是插件不适配该类型的验证码
267 | 2. 能获取到验证码,说明插件本身没问题;识别端返回错误或者不返回,极有可能是python服务运行有问题,或者启动了代理,可以尝试关闭代理、更换jdk等方式尝试解决
268 |
269 | # 24-如何提问?
270 |
271 | 正确做法✅:抛出完整的逻辑,如我遇到了什么问题,在FAQ或者README中按照xxx方式进行了调试,具体问题是识别到了验证码,但是接口返回错误,并给出完整的截图,包括模板、python文件启动截图、识别日志等
272 |
273 | 错误做法❌:《我验证码获取不到》、《这个验证码识别不了》、《怎么识别验证码》、诸如此类等
274 |
275 | # 25-intruder模块无法正常爆破
276 | 1. 检查线程是否为1
277 | 2. 检查延时是否超过1秒,如果1秒不行,则直接加大成5秒,站点的响应速度不同,延时也并非一成不变的
278 |
--------------------------------------------------------------------------------
/src/main/java/utils/Util.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer)
3 | * License: MIT
4 | */
5 | package utils;
6 |
7 | import burp.BurpExtender;
8 | import burp.IResponseInfo;
9 |
10 | import javax.imageio.ImageIO;
11 | import javax.swing.*;
12 | import javax.xml.bind.DatatypeConverter;
13 | import java.awt.*;
14 | import java.awt.image.BufferedImage;
15 | import java.io.ByteArrayInputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.io.UnsupportedEncodingException;
19 | import java.net.URLDecoder;
20 | import java.util.Base64;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | import static burp.BurpExtender.stdout;
25 |
26 | // import sun.misc.BASE64Decoder;
27 | // import sun.misc.BASE64Encoder;
28 | //import java.util.Base64.Decoder;
29 | //import java.util.Base64.Encoder;
30 |
31 | public class Util {
32 | /**
33 | * 插件Banner信息
34 | * @return
35 | */
36 | public static String getBanner(String extName,String version){
37 | String bannerInfo =
38 | "[*] \n"
39 | + "[*] ###################################################\n"
40 | + "[*] " + extName + " v" + version +"\n"
41 | + "[*] author: c0ny1\n"
42 | + "[*] email: root@gv7.me\n"
43 | + "[*] github: http://github.com/c0ny1/captcha-killer\n"
44 | + "[*] ###################################################\n"
45 | + "[*] modifier: f0ng\n"
46 | + "[*] github: http://github.com/f0ng/captcha-killer-modified\n"
47 | + "[!] Install " + extName + " successful!\n"
48 | + "[*] Please enjoy it ^_^\n"
49 | + "[*] use `@captcha@` replace captcha\n"
50 | + "[*] use `@captcha-killer-modified@` replace captcha's token\n"
51 | + "[*] ";
52 | return bannerInfo;
53 | }
54 |
55 | public static boolean isImage(String str_img,String words) throws IOException {
56 | // System.out.println(str_img);
57 | String pattern = "(" + words + ".*?)[,&}/+=\\w]+";
58 | Pattern r = Pattern.compile(pattern);
59 | Matcher m = r.matcher(str_img);
60 | if (m.find( )) {
61 | str_img = m.group(0).replace("\"","").replace("&","").replace("Base64:","").replace("base64:","") ;
62 | }
63 | if (!str_img.contains("data:image")){
64 | str_img = "data:image/jpeg;base64," + str_img;
65 | }
66 |
67 | byte[] img = DatatypeConverter.parseBase64Binary(str_img.substring(str_img.indexOf(",") + 1));
68 | boolean isImg = false;
69 | InputStream buffin = new ByteArrayInputStream(img);
70 | BufferedImage image = ImageIO.read(buffin);
71 | if(image == null){
72 | isImg = false;
73 | }else {
74 | isImg = true;
75 | }
76 | // System.out.println(isImg);
77 | return isImg;
78 | }
79 |
80 | public static boolean isImage(String str_img) throws IOException {
81 | // System.out.println(str_img);
82 | String pattern = "(data:image.*?)[\"|&]|(data%2Aimage.*?)[\"|&]";
83 | str_img = str_img.replace("\\r\\n","").replace("\\","");
84 | // System.out.println(str_img);
85 | str_img = URLDecoder.decode(str_img,"utf-8");
86 | Pattern r = Pattern.compile(pattern);
87 | Matcher m = r.matcher(str_img);
88 | if (m.find( )) {
89 | str_img = m.group(0).replace("\"","").replace("&","").replace("Base64:","").replace("base64:","").replace(".","") ;
90 | }
91 | if (!str_img.contains("data:image")){
92 | str_img = "data:image/jpeg;base64," + str_img;
93 | }
94 | // stdout.println(str_img);
95 | str_img = str_img.replace(" ","+");
96 | byte[] img = DatatypeConverter.parseBase64Binary(str_img.substring(str_img.indexOf(",") + 1));
97 | boolean isImg = false;
98 | InputStream buffin = new ByteArrayInputStream(img);
99 | BufferedImage image = ImageIO.read(buffin);
100 | // System.out.println(image);
101 | if(image == null){
102 | isImg = false;
103 | }else {
104 | isImg = true;
105 | }
106 | System.out.println(image);
107 | System.out.println(isImg);
108 | return isImg;
109 | }
110 |
111 | public static byte[] dataimgToimg(String str_img) throws IOException {
112 | String pattern = "(data:image.*?)[\"|&]|(data%2Aimage.*?)[\"|&]";
113 | str_img = str_img.replace("\\r\\n","");
114 | str_img = str_img.replace("\\n","").replace("\\","");
115 | str_img = str_img.replace("_","/").replace("-","+");
116 | // stdout.println(str_img);
117 | str_img = URLDecoder.decode(str_img,"utf-8");
118 | Pattern r = Pattern.compile(pattern);
119 | Matcher m = r.matcher(str_img);
120 | if (m.find( )) {
121 | str_img = m.group(0).replace("\"","").replace("&","").replace("Base64:","").replace("base64:","").replace(".","") ;
122 | }
123 | if (!str_img.contains("data:image")){
124 | str_img = "data:image/jpeg;base64," + str_img;
125 | }
126 | str_img = str_img.replace(" ","+");
127 | byte[] img = DatatypeConverter.parseBase64Binary(str_img.substring(str_img.indexOf(",") + 1));
128 | // InputStream buffin = new ByteArrayInputStream(img);
129 | return img;
130 | }
131 |
132 | public static String extractToken(String str_img,String token) throws IOException {
133 | String pattern = token ;
134 | str_img = str_img.replace("\\r\\n","").replace("\\","");
135 | str_img = str_img.replace("\\n","");
136 | Pattern r = Pattern.compile(pattern);
137 | Matcher m = r.matcher(str_img);
138 |
139 | if (m.find()) {
140 | str_img = m.group(1).replace("\"", "").replace("&", "").replace("Base64:", "").replace("base64:", "");
141 | }
142 | if( str_img.length() > 300){
143 | str_img = "提取关键字过长,请确认提取是否正确!";
144 | }
145 |
146 | // if( str_img.length() > 100){
147 | // str_img = str_img.substring(0,10) + "……" + str_img.substring(str_img.length()-10 , str_img.length());
148 | // }
149 |
150 | return str_img;
151 | }
152 |
153 |
154 | public static byte[] dataimgToimg(String str_img,String words) throws IOException {
155 | String pattern = "(" + words + ".*?)[,&}/+=\\w]+";
156 | str_img = str_img.replace("\\r\\n","");
157 | str_img = str_img.replace("\\n","").replace("\\","");
158 | str_img = str_img.replace("_","/").replace("-","+");
159 | // stdout.println(str_img);
160 | Pattern r = Pattern.compile(pattern);
161 | Matcher m = r.matcher(str_img);
162 | str_img = URLDecoder.decode(str_img,"utf-8");
163 | if (m.find( )) {
164 | // stdout.println(m.group(0));
165 | str_img = m.group(0).replace("\"","").replace(words,"").replace(":","").replace("&","").replace(",","") ;
166 | }
167 | if (!str_img.contains("data:image")){
168 | str_img = "data:image/jpeg;base64," + str_img;
169 | }
170 | str_img = str_img.replace(" ","+");
171 | // stdout.println(str_img);
172 | //str_img = str_img;
173 | // System.out.println(str_img);
174 | // System.out.println(str_img.indexOf(","));
175 | byte[] img = DatatypeConverter.parseBase64Binary(str_img.substring(str_img.indexOf(",") + 1));
176 | //InputStream buffin = new ByteArrayInputStream(img);
177 | return img;
178 | }
179 |
180 |
181 | public static boolean isImage(byte[] img){
182 | // Reference: https://www.cnblogs.com/shihaiming/p/10404700.html
183 | boolean isImg = false;
184 | InputStream buffin = new ByteArrayInputStream(img);
185 | try {
186 | //两种判断方式只能选中一种
187 | //第一种方式
188 | // ImageInputStream iis = ImageIO.createImageInputStream(buffin);
189 | // Iterator iter = ImageIO.getImageReaders(iis);
190 | // if (!iter.hasNext()) {
191 | // isImg = false;
192 | // }else {
193 | // isImg = true;
194 | // }
195 | //第二方式
196 | BufferedImage image = ImageIO.read(buffin);
197 | if(image == null){
198 | isImg = false;
199 | }else {
200 | isImg = true;
201 | }
202 | } catch (IOException e) {
203 | BurpExtender.stderr.println(e.getMessage());
204 | isImg = false;
205 | }
206 | return isImg;
207 | }
208 |
209 | public static ImageIcon byte2img(byte[] img) {
210 | InputStream buffin = new ByteArrayInputStream(img);
211 | Image image = null;
212 | ImageIcon icon = null;
213 | try {
214 | image = ImageIO.read(buffin);
215 | icon = new ImageIcon(image);
216 | } catch (IOException e) {
217 | // BurpExtender.stderr.println(e.getMessage());
218 | icon = null;
219 | }
220 | return icon;
221 | }
222 |
223 | public static byte[] subBytes(byte[] src, int begin, int count) {
224 | byte[] bs = new byte[count];
225 | for (int i=begin; i 0 && nStart < nEnd){
233 | return "Rules of the error: start should >0 and 0){
395 | count = rCount;
396 | }
397 | if(nCount > 0){
398 | count = nCount;
399 | }
400 | return count;
401 | }
402 |
403 |
404 | public static byte[][] requestImage(String url,String raw) throws IOException {
405 | // if(Util.isURL(url)) {
406 | HttpClient http = new HttpClient(url, raw, null);
407 | byte[] rsp = http.doReust();
408 |
409 | // BurpExtender.stdout.println(new String(rsp).replace("\r\n","\r\n\\r\\n"));
410 | BurpExtender.gui.getTaResponse().setText(new String(rsp).replace("\r\n","\\r\\n\r\n"));
411 | int BodyOffset = BurpExtender.helpers.analyzeResponse(rsp).getBodyOffset();
412 | int body_length = rsp.length - BodyOffset;
413 | byte[] byteImg = Util.subBytes(rsp, BodyOffset, body_length);
414 | return new byte[][]{byteImg, rsp};
415 | // }else{
416 | // BurpExtender.stderr.println("[-] captcha URL format invalid");
417 | // return null;
418 | // }
419 | }
420 |
421 | public static String matchByRegular(String str,String reg){
422 | String res = "";
423 | int start = 0;
424 | int end = 0;
425 | Pattern r = Pattern.compile(reg);
426 | Matcher m = r.matcher(str);
427 | if (m.find()) {
428 | res = m.group(1);//0会获取多余的内容
429 | start = m.start();
430 | int n = str.substring(start,str.length()).indexOf(res);
431 | start += n;
432 | end = start + res.length();
433 | }
434 | return res;
435 | }
436 |
437 | public static String matchByRegular(String str,String reg,int n){
438 | String res = "";
439 | Pattern r = Pattern.compile(reg,Pattern.MULTILINE);
440 | Matcher m = r.matcher(str);
441 | if (m.find()) {
442 | res = m.group(n);//0会获取多余的内容
443 | }
444 | return res;
445 | }
446 |
447 | /**
448 | * 将字符串开头空格去掉
449 | * @param str
450 | * @return
451 | */
452 | public static String trimStart(String str) {
453 | if (str == "" || str == null) {
454 | return str;
455 | }
456 |
457 | final char[] value = str.toCharArray();
458 | int start = 0, last = 0 + str.length();
459 | int end = last;
460 | while ((start <= end) && (value[start] <= ' ')) {
461 | start++;
462 | }
463 | if (start == 0 && end == last) {
464 | return str;
465 | }
466 | if (start >= end) {
467 | return "";
468 | }
469 | return str.substring(start, end);
470 | }
471 | }
472 |
--------------------------------------------------------------------------------
/src/main/java/ui/GUI.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer)
3 | * License: MIT
4 | */
5 | package ui;
6 |
7 | import burp.BurpExtender;
8 | import com.alibaba.fastjson.JSON;
9 | import entity.*;
10 | import matcher.impl.JsonMatcher;
11 | import matcher.impl.XmlMatcher;
12 | import ui.model.TableModel;
13 | import utils.HttpClient;
14 | import utils.RuleMannager;
15 | import utils.Util;
16 |
17 | import javax.swing.*;
18 | import javax.swing.border.EmptyBorder;
19 | import javax.swing.event.ListSelectionEvent;
20 | import javax.swing.event.ListSelectionListener;
21 | import javax.swing.text.Style;
22 | import javax.swing.text.StyleConstants;
23 | import javax.swing.text.StyledDocument;
24 | import java.awt.*;
25 | import java.awt.event.*;
26 | import java.io.IOException;
27 | import java.util.ArrayList;
28 | import java.util.Arrays;
29 | import java.util.List;
30 |
31 | import static burp.BurpExtender.*;
32 | import static utils.Util.dataimgToimg;
33 | import static utils.Util.extractToken;
34 |
35 | public class GUI {
36 | private JPanel MainPanel;
37 | public JSplitPane spImg;
38 | private JSplitPane spInterface;
39 | private JSplitPane spOption;
40 | private JSplitPane spAll;
41 |
42 | //获取验证码面板
43 | private JLabel lbURL;
44 | public JTextField tfURL;
45 | private JButton btnGetCaptcha;
46 | public JTextArea taRequest;
47 | private JLabel lbCaptcha;
48 |
49 | private JLabel lbWords; // 关键字label
50 | public JTextField tfWords; // 关键字textfield
51 |
52 | private JLabel lbToken; // token关键字
53 | public JTextField tfToken; // token关键字 输入
54 |
55 | private JLabel lbTokenex; // token关键字显示
56 | public JLabel tfTokenex; // token关键字显示
57 |
58 |
59 | public String tokenwords ; //token 关键字提取的结果
60 | public String tokenword ; //token 关键字提取的结果,如果太长就会提示太长
61 |
62 | private JLabel lbcapex; // 识别的验证码显示
63 | public JLabel tfcapex; // 识别的验证码显示
64 |
65 | public JRadioButton usebutton; // 识别的验证码显示
66 |
67 | public String cap;
68 |
69 | private JLabel lbImage;
70 | private JToggleButton tlbLock;
71 | private JTextArea taResponse;
72 |
73 | //接口配置编码
74 | private JPanel plInterfaceReq;
75 | private JPopupMenu pmInterfaceMenu;
76 | private JTabbedPane tpInterfaceReq;
77 | public JTextArea taInterfaceTmplReq;
78 | private JTextArea taInterfaceRawReq;
79 | private JLabel lbInterfaceURL;
80 | public JTextField tfInterfaceURL;
81 | private JButton btnIdentify;
82 | private JTabbedPane tpInterfaceRsq;
83 | private JPanel plInterfaceRsq;
84 | //private JTextArea taInterfaceRsq;
85 | private JTextPane InterfaceRsq;
86 | private JLabel lbRuleType = new JLabel("匹配方式:");
87 | private JComboBox cbmRuleType;
88 | private JLabel lbRegular = new JLabel("匹配规则:");
89 | private JTextField tfRegular;
90 | private JButton btnSaveTmpl;
91 | JMenuItem miMarkIdentifyResult = new JMenuItem("标记为识别结果");
92 | JPopupMenu pppInterfaceRsq = new JPopupMenu();
93 |
94 | //识别结果面板
95 | private JPanel plResult;
96 | private JTable table;
97 | private JScrollPane spTable;
98 | private TableModel model;
99 | private JPopupMenu pppMenu = new JPopupMenu();
100 | private JMenuItem miClear = new JMenuItem("清空");
101 | private JMenuItem miShowIntruderResult = new JMenuItem("关闭Intruder识别结果显示");
102 |
103 | //一些公共变量
104 | public byte[] byteImg;
105 | public byte[] byteRes;
106 | public static final List captcha = new ArrayList();
107 |
108 | public JTextField getTfwords(){
109 | return tfWords;
110 | }
111 |
112 |
113 | public JTextField getTfURL(){
114 | return tfURL;
115 | }
116 |
117 | public JTextArea getTaRequest(){
118 | return taRequest;
119 | }
120 |
121 | public JTextArea getTaResponse(){
122 | return taResponse;
123 | }
124 |
125 | public JButton getBtnGetCaptcha(){
126 | return this.btnGetCaptcha;
127 | }
128 |
129 | public JTable getTable(){
130 | return table;
131 | }
132 |
133 | public TableModel getModel(){
134 | return this.model;
135 | }
136 |
137 | public String getCaptchaURL(){
138 | return this.tfURL.getText();
139 | }
140 |
141 | public String getCaptchaReqRaw(){
142 | return this.taRequest.getText();
143 | }
144 |
145 | public JTextField getInterfaceURL(){
146 | return this.tfInterfaceURL;
147 | }
148 |
149 | public JTextArea getTaInterfaceTmplReq(){
150 | return this.taInterfaceTmplReq;
151 | }
152 |
153 | public JTextArea getInterfaceReqRaw(){
154 | return this.taInterfaceRawReq;
155 | }
156 |
157 | public JTextField getRegular(){
158 | return this.tfRegular;
159 | }
160 |
161 | public JComboBox getCbmRuleType(){
162 | return this.cbmRuleType;
163 | }
164 |
165 | public GUI(){
166 | initGUI();
167 | initEvent();
168 | }
169 |
170 | public void initGUI(){
171 | String tokenwords = "";
172 | String cap_shibie = "";
173 | MainPanel = new JPanel();
174 | MainPanel.setBorder(new EmptyBorder(2, 2, 2, 2));
175 | MainPanel.setLayout(new BorderLayout(0, 0));
176 |
177 | //图片获取面板
178 | lbURL = new JLabel("验证码URL:");
179 | tfURL = new JTextField(20);
180 | btnGetCaptcha = new JButton("获取");
181 |
182 | taRequest = new JTextArea();
183 | taRequest.setLineWrap(true);
184 | taRequest.setWrapStyleWord(true);//断行不断字
185 | JScrollPane spRequest = new JScrollPane(taRequest);
186 |
187 | JPanel imgLeftPanel = new JPanel();
188 | imgLeftPanel.setLayout(new GridBagLayout());
189 | GBC gbc_lburl = new GBC(0,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0);
190 | GBC gbc_tfurl = new GBC(1,0,1,1).setFill(GBC.BOTH).setWeight(100,1).setInsets(3,3,0,0);
191 | GBC gbc_btngetcaptcha = new GBC(2,0,11,1).setInsets(3,3,0,3);
192 | GBC gbc_tarequst = new GBC(0,1,100,100).setFill(GBC.BOTH).setWeight(100,100).setInsets(3,3,3,3);
193 | imgLeftPanel.add(lbURL,gbc_lburl);
194 | imgLeftPanel.add(tfURL,gbc_tfurl);
195 | imgLeftPanel.add(btnGetCaptcha,gbc_btngetcaptcha);
196 | imgLeftPanel.add(spRequest,gbc_tarequst);
197 |
198 | JPanel imgRigthPanel = new JPanel();
199 | // GridBagLayout gbc = new GridBagLayout();
200 | // gbc.anchor = GridBagConstraints.EAST;
201 |
202 | imgRigthPanel.setLayout(new GridBagLayout());
203 |
204 | lbImage = new JLabel("");
205 | lbCaptcha = new JLabel("验证码:");
206 |
207 | lbWords = new JLabel("关键字:");
208 | tfWords = new JTextField(10);
209 |
210 | lbToken = new JLabel("响应提取(regex):");
211 | tfToken = new JTextField(10);
212 | GUI.this.tokenwords = tokenwords;
213 |
214 | lbTokenex = new JLabel("提取的关键字:");
215 | tfTokenex = new JLabel("");
216 |
217 |
218 | GUI.this.cap = cap_shibie;
219 | lbcapex = new JLabel("验证码识别为:");
220 | tfcapex = new JLabel("");
221 |
222 | usebutton = new JRadioButton("是否使用该插件");
223 |
224 | // tlbLock = new JToggleButton("锁定");
225 | // tlbLock.setToolTipText("当配置好所有选项后,请锁定防止配置被改动!");
226 |
227 | taResponse = new JTextArea();
228 | taResponse.setLineWrap(true);
229 | taResponse.setWrapStyleWord(true);//断行不断字
230 | taResponse.setEditable(true);
231 | JScrollPane spResponse = new JScrollPane(taResponse);
232 |
233 | GBC gbc_lbcaptcha = new GBC(2,0,1,1).setFill(GBC.BOTH).setInsets(3,3,0,0);
234 | GBC gbc_lbimage = new GBC(3,0,1,1).setFill(GBC.BOTH).setWeight(1,1).setInsets(3,3,0,0);
235 | GBC gbc_lbwords = new GBC(0,0,1,1).setFill(GBC.BOTH).setInsets(3,3,0,1);
236 | GBC gbc_tfwords = new GBC(1,0,1,1).setFill(GBC.HORIZONTAL).setWeight(20,1).setInsets(3,3,0,0);
237 |
238 |
239 | GBC gbc_lbtoken = new GBC(0,1,1,1).setFill(GBC.NONE).setInsets(3,3,0,1);
240 | GBC gbc_tftoken = new GBC(1,1,1,1).setFill(GBC.BOTH).setWeight(20,1).setInsets(3,3,0,0);
241 | GBC gbc_lbtokenex = new GBC(2,1,1,1).setFill(GBC.BOTH).setInsets(3,3,0,1);
242 | GBC gbc_tftokenex = new GBC(3,1,1,1).setFill(GBC.NONE).setWeight(20,1).setInsets(3,3,0,0);
243 |
244 |
245 |
246 | GBC gbc_lbcapex = new GBC(0,2,1,1).setFill(GBC.BOTH).setInsets(3,3,0,1);
247 | GBC gbc_tfcapex = new GBC(1,2,1,1).setFill(GBC.BOTH).setWeight(20,1).setInsets(3,3,0,0);
248 | GBC gbc_usebutton = new GBC(2,2,1,1).setFill(GBC.BOTH).setWeight(20,1).setInsets(3,3,0,0);
249 |
250 | //GBC gbc_tlblock = new GBC(4,0,1,1).setFill(GBC.BOTH).setInsets(3,3,0,3);
251 | GBC gbc_taresponse = new GBC(0,3,100,100).setFill(GBC.BOTH).setWeight(100,100).setInsets(3,3,3,3);
252 |
253 |
254 | imgRigthPanel.add(lbWords,gbc_lbwords);
255 | imgRigthPanel.add(tfWords,gbc_tfwords);
256 |
257 | imgRigthPanel.add(lbCaptcha,gbc_lbcaptcha);
258 | imgRigthPanel.add(lbImage,gbc_lbimage);
259 |
260 | imgRigthPanel.add(lbToken,gbc_lbtoken);
261 | imgRigthPanel.add(tfToken,gbc_tftoken);
262 |
263 | imgRigthPanel.add(lbTokenex,gbc_lbtokenex);
264 | imgRigthPanel.add(tfTokenex,gbc_tftokenex);
265 |
266 | imgRigthPanel.add(lbcapex,gbc_lbcapex);
267 | imgRigthPanel.add(tfcapex,gbc_tfcapex);
268 | imgRigthPanel.add(usebutton,gbc_usebutton);
269 |
270 |
271 |
272 | //imgRigthPanel.add(tlbLock,gbc_tlblock);
273 | imgRigthPanel.add(spResponse,gbc_taresponse);
274 |
275 | spImg = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
276 | spImg.setResizeWeight(0);
277 | spImg.setLeftComponent(imgLeftPanel);
278 | spImg.setRightComponent(imgRigthPanel);
279 |
280 | // 识别接口配置面板
281 | spInterface = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
282 | spInterface.setResizeWeight(0.5);
283 | plInterfaceReq = new JPanel();
284 | plInterfaceReq.setLayout(new GridBagLayout());
285 |
286 | lbInterfaceURL = new JLabel("接口URL:");
287 | tfInterfaceURL = new JTextField(30);
288 | btnIdentify = new JButton("识别");
289 |
290 | tpInterfaceReq = new JTabbedPane();
291 | taInterfaceTmplReq = new JTextArea();
292 | taInterfaceTmplReq.setLineWrap(true);
293 | taInterfaceTmplReq.setWrapStyleWord(true);
294 | JScrollPane spInterfaceReq = new JScrollPane(taInterfaceTmplReq);
295 | taInterfaceRawReq = new JTextArea();
296 | taInterfaceRawReq.setLineWrap(true);
297 | taInterfaceRawReq.setWrapStyleWord(true);
298 | taInterfaceRawReq.setEditable(false);
299 | JScrollPane spInterfaceRawReq = new JScrollPane(taInterfaceRawReq);
300 | tpInterfaceReq.addTab("Requst template",spInterfaceReq);
301 | tpInterfaceReq.addTab("Requst raw",spInterfaceRawReq);
302 |
303 | GBC gbc_lbinterfaceurl = new GBC(1,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0);
304 | GBC gbc_tfinterfaceurl = new GBC(2,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0).setWeight(100,1);
305 | GBC gbc_btnidentify = new GBC(3,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,3);
306 | GBC gbc_tpinterfacereq = new GBC(0,1,100,100).setFill(GBC.BOTH).setWeight(100,100).setInsets(3,3,3,3);
307 | plInterfaceReq.add(lbInterfaceURL,gbc_lbinterfaceurl);
308 | plInterfaceReq.add(tfInterfaceURL,gbc_tfinterfaceurl);
309 | plInterfaceReq.add(btnIdentify,gbc_btnidentify);
310 | plInterfaceReq.add(tpInterfaceReq,gbc_tpinterfacereq);
311 |
312 | plInterfaceRsq = new JPanel();
313 | plInterfaceRsq.setLayout(new GridBagLayout());
314 | String[] str = new String[]{"Response data","Regular expression","Define the start and end positions","Defines the start and end strings","json field match","xml element match"};
315 | cbmRuleType = new JComboBox(str);
316 |
317 | tfRegular = new JTextField(30);
318 | tfRegular.setText("response_data");
319 | tfRegular.setEnabled(false);
320 | btnSaveTmpl = new JButton("匹配");
321 | btnSaveTmpl.setToolTipText("用于测试编写的规则是否正确");
322 | tpInterfaceRsq = new JTabbedPane();
323 | InterfaceRsq = new JTextPane();
324 | InterfaceRsq.setEditable(true);
325 |
326 | pppInterfaceRsq.add(miMarkIdentifyResult);
327 |
328 | //JScrollPane spInterfaceRsq = new JScrollPane(taInterfaceRsq);
329 | JScrollPane spInterfaceRsq = new JScrollPane(InterfaceRsq);
330 | tpInterfaceRsq.addTab("Response raw",spInterfaceRsq);
331 | GBC gbc_lbruletype = new GBC(0,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0);
332 | GBC gbc_cbmruletype = new GBC(1,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0);
333 | GBC gbc_lbregular = new GBC(2,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0);
334 | GBC gbc_tfregular = new GBC(3,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,0).setWeight(100,1);
335 | GBC gbc_btnsavetmpl = new GBC(4,0,1,1).setFill(GBC.HORIZONTAL).setInsets(3,3,0,3);
336 | GBC gbc_tpinterfacersq = new GBC(0,1,100,100).setFill(GBC.BOTH).setWeight(100,100).setInsets(3,3,3,3);
337 | plInterfaceRsq.add(lbRuleType,gbc_lbruletype);
338 | plInterfaceRsq.add(cbmRuleType,gbc_cbmruletype);
339 | plInterfaceRsq.add(lbRegular,gbc_lbregular);
340 | plInterfaceRsq.add(tfRegular,gbc_tfregular);
341 | plInterfaceRsq.add(btnSaveTmpl,gbc_btnsavetmpl);
342 | plInterfaceRsq.add(tpInterfaceRsq,gbc_tpinterfacersq);
343 | spInterface.setLeftComponent(plInterfaceReq);
344 | spInterface.setRightComponent(plInterfaceRsq);
345 |
346 | //识别结果面板
347 | plResult = new JPanel();
348 | pppMenu.add(miClear);
349 | pppMenu.add(miShowIntruderResult);
350 | table = new JTable();
351 | model = new TableModel(table);
352 | table.setModel(model);
353 | table.setAutoCreateRowSorter(true);
354 | spTable = new JScrollPane(table);
355 | plResult.setLayout(new GridBagLayout());
356 | GBC gbc_taresult = new GBC(0,0,100,100).setFill(GBC.BOTH).setWeight(100,100).setInsets(3,3,3,3);
357 | plResult.add(spTable,gbc_taresult);
358 |
359 | //面板合并
360 | spOption = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
361 | spOption.setResizeWeight(0.5);
362 | spOption.setTopComponent(spImg);
363 | spOption.setBottomComponent(spInterface);
364 | spAll = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
365 | spAll.setResizeWeight(0.6);
366 | spAll.setLeftComponent(spOption);
367 | spAll.setRightComponent(plResult);
368 |
369 | MainPanel.add(spAll);
370 | SwingUtilities.invokeLater(new Runnable() {
371 | @Override
372 | public void run() {
373 | BurpExtender.callbacks.customizeUiComponent(spAll);
374 | BurpExtender.callbacks.customizeUiComponent(MainPanel);
375 | }
376 | });
377 | }
378 |
379 | public void initEvent(){
380 | //获取验证码
381 | btnGetCaptcha.addActionListener(new ActionListener() {
382 | @Override
383 | public void actionPerformed(ActionEvent e) {
384 | if(tfURL.getText().equals(null) || tfURL.getText().trim().equals("")){
385 | JOptionPane.showMessageDialog(null,"请设置验证码URL","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
386 | return;
387 | }
388 |
389 | if(taRequest.getText().equals(null) || taRequest.getText().trim().equals("")){
390 | JOptionPane.showMessageDialog(null,"请设置获取验证码的请求数据包","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
391 | return;
392 | }
393 |
394 | // if(!Util.isURL(tfURL.getText())){
395 | // JOptionPane.showMessageDialog(null,"验证码URL不合法!","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
396 | // return;
397 | // }
398 |
399 | GetCaptchaThread thread = new GetCaptchaThread(tfURL.getText(),taRequest.getText());
400 | thread.start();
401 | }
402 | });
403 |
404 | //锁定
405 | // tlbLock.addChangeListener(new ChangeListener(){
406 | // public void stateChanged(ChangeEvent e) {
407 | // boolean isSelected = tlbLock.isSelected();
408 | // if(isSelected){
409 | // tlbLock.setText("解锁");
410 | // tfURL.setEnabled(false);
411 | // taRequest.setEnabled(false);
412 | // btnGetCaptcha.setEnabled(false);
413 | // taResponse.setEnabled(false);
414 | // tfInterfaceURL.setEnabled(false);
415 | // btnIdentify.setEnabled(false);
416 | // taInterfaceTmplReq.setEnabled(false);
417 | // cbmRuleType.setEnabled(false);
418 | // taInterfaceRawReq.setEnabled(false);
419 | // tfRegular.setEnabled(false);
420 | // btnSaveTmpl.setEnabled(false);
421 | // InterfaceRsq.setEnabled(false);
422 | // }else{
423 | // tlbLock.setText("锁定");
424 | // tfURL.setEnabled(true);
425 | // taRequest.setEnabled(true);
426 | // btnGetCaptcha.setEnabled(true);
427 | // taResponse.setEnabled(true);
428 | // tfInterfaceURL.setEnabled(true);
429 | // btnIdentify.setEnabled(true);
430 | // taInterfaceTmplReq.setEnabled(true);
431 | // cbmRuleType.setEnabled(true);
432 | // taInterfaceRawReq.setEnabled(true);
433 | // tfRegular.setEnabled(true);
434 | // btnSaveTmpl.setEnabled(true);
435 | // InterfaceRsq.setEnabled(true);
436 | // }
437 | //// tlbLock.setSelected(isSelected);
438 | // }
439 | // });
440 |
441 | //识别
442 | btnIdentify.addActionListener(new ActionListener() {
443 | @Override
444 | public void actionPerformed(ActionEvent e) {
445 |
446 | // System.out.println(byteImg);
447 |
448 | if(byteImg == null){
449 | JOptionPane.showMessageDialog(null,"请先获取要识别的图片","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
450 | return;
451 | }
452 |
453 | if(!Util.isImage(byteImg)){
454 | JOptionPane.showMessageDialog(null,"要识别的不是图片,请重新获取!","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
455 | System.out.println("被退出");
456 | return;
457 | }
458 |
459 | if(tfInterfaceURL.getText().trim() == null || tfInterfaceURL.getText().trim().equals("")){
460 | JOptionPane.showMessageDialog(null,"请设置好接口URL","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
461 | return;
462 | }
463 |
464 | if(taInterfaceTmplReq.getText().trim() == null|| taInterfaceTmplReq.getText().trim().equals("")){
465 | JOptionPane.showMessageDialog(null,"请设置调用接请求数据包","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
466 | return;
467 | }
468 |
469 | if(tfRegular.getText().trim() == null|| tfRegular.getText().trim().equals("")){
470 | JOptionPane.showMessageDialog(null,"请设置好匹配结果的正则","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
471 | return;
472 | }
473 |
474 | IdentifyCaptchaThread thread = new IdentifyCaptchaThread(tfInterfaceURL.getText(),taInterfaceTmplReq.getText(),byteImg);
475 | thread.start();
476 | }
477 | });
478 | // 接口数据包面板右键
479 | taInterfaceTmplReq.addMouseListener(new MouseAdapter() {
480 | @Override
481 | public void mouseClicked(MouseEvent e) {
482 |
483 | if (e.getButton() == java.awt.event.MouseEvent.BUTTON3) {
484 | synchronized (TmplEntity.tpls){
485 | String str = BurpExtender.callbacks.loadExtensionSetting("tpldb");
486 | if(str != "" && str != null) {
487 | try {
488 | TmplEntity.tpls = JSON.parseArray(str, TmplEntity.class);
489 | }catch (Exception ex){
490 | TmplEntity.tpls = new ArrayList();
491 | }
492 | }
493 | }
494 | pmInterfaceMenu = new JPopupMenu();
495 | JMenu menuTmplManager = new JMenu("模版库");
496 | JMenuItem miGeneralTmpl = new JMenuItem("通用模版");
497 | JMenuItem miTesseract = new JMenuItem("tesseract-ocr-web");
498 | JMenuItem miBaiduOCR = new JMenuItem("ddddocr");
499 | JMenuItem miCNNCaptcha = new JMenuItem("cnn_captcha");
500 | JMenuItem miSaveTpl = new JMenuItem("保存为模版");
501 | JMenuItem miUpdateTpl = new JMenuItem("更新模版");
502 | JMenuItem miDelTpl = new JMenuItem("删除模版");
503 | JMenuItem miImageRaw = new JMenuItem("验证码图片二进制内容标签");
504 | miImageRaw.addActionListener(new ActionListener() {
505 | @Override
506 | public void actionPerformed(ActionEvent e) {
507 | int n = taInterfaceTmplReq.getSelectionStart();
508 | taInterfaceTmplReq.insert("<@IMG_RAW>@IMG_RAW>",n);
509 | }
510 | });
511 | JMenuItem miBase64Encode = new JMenuItem("Base64编码标签");
512 | miBase64Encode.addActionListener(new ActionListener() {
513 | @Override
514 | public void actionPerformed(ActionEvent e) {
515 | int start = taInterfaceTmplReq.getSelectionStart();
516 | int end = taInterfaceTmplReq.getSelectionEnd();
517 | String newStr = String.format("<@BASE64>%s@BASE64>",taInterfaceTmplReq.getSelectedText());
518 | StringBuffer sbRaw = new StringBuffer(taInterfaceTmplReq.getText());
519 | sbRaw.replace(start,end,newStr);
520 | taInterfaceTmplReq.setText(sbRaw.toString());
521 | }
522 | });
523 | JMenuItem miURLEncode = new JMenuItem("URL编码标签");
524 | miURLEncode.addActionListener(new ActionListener() {
525 | @Override
526 | public void actionPerformed(ActionEvent e) {
527 | int start = taInterfaceTmplReq.getSelectionStart();
528 | int end = taInterfaceTmplReq.getSelectionEnd();
529 | String newStr = String.format("<@URLENCODE>%s@URLENCODE>",taInterfaceTmplReq.getSelectedText());
530 | StringBuffer sbRaw = new StringBuffer(taInterfaceTmplReq.getText());
531 | sbRaw.replace(start,end,newStr);
532 | taInterfaceTmplReq.setText(sbRaw.toString());
533 | }
534 | });
535 |
536 | menuTmplManager.add(miGeneralTmpl);
537 | menuTmplManager.add(miTesseract);
538 | menuTmplManager.add(miBaiduOCR);
539 | menuTmplManager.add(miCNNCaptcha);
540 | pmInterfaceMenu.add(menuTmplManager);
541 | pmInterfaceMenu.add(miSaveTpl);
542 | pmInterfaceMenu.add(miUpdateTpl);
543 | pmInterfaceMenu.add(miDelTpl);
544 | pmInterfaceMenu.addSeparator();
545 | pmInterfaceMenu.add(miImageRaw);
546 | pmInterfaceMenu.add(miBase64Encode);
547 | pmInterfaceMenu.add(miURLEncode);
548 |
549 | miGeneralTmpl.addActionListener(new MenuActionManger());
550 | miTesseract.addActionListener(new MenuActionManger());
551 | miBaiduOCR.addActionListener(new ActionListener() {
552 | @Override
553 | public void actionPerformed(ActionEvent e) {
554 | tfInterfaceURL.setText("http://127.0.0.1:8888");
555 | taInterfaceTmplReq.setText("POST /reg HTTP/1.1\n" +
556 | "Host: 127.0.0.1:8888\n" +
557 | "Authorization:Basic f0ngauth\n" +
558 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) " +
559 | "Gecko/20100101 Firefox/97.0\n" +
560 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif," +
561 | "image/webp,*/*;q=0.8\n" +
562 | "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\n" +
563 | "Accept-Encoding: gzip, deflate\n" +
564 | "Connection: keep-alive\n" +
565 | "Upgrade-Insecure-Requests: 1\n" +
566 | "Content-Type: application/x-www-form-urlencoded\n" +
567 | "Content-Length: 8332\n" +
568 | "\n" +
569 | "<@BASE64><@IMG_RAW>@IMG_RAW>@BASE64>");
570 | cbmRuleType.setSelectedIndex(Rule.RULE_TYPE_RESPONSE_DATA);
571 | tfRegular.setText("\"words\"\\: \"(.*?)\"\\}");
572 | }
573 | });
574 | miCNNCaptcha.addActionListener(new MenuActionManger());
575 | miSaveTpl.addActionListener(new ActionListener() {
576 | @Override
577 | public void actionPerformed(ActionEvent e) {
578 | SwingUtilities.invokeLater(new Runnable() {
579 | @Override
580 | public void run() {
581 | if(tfInterfaceURL.getText().trim() == null || tfInterfaceURL.getText().trim().equals("")){
582 | JOptionPane.showMessageDialog(null,"请设置好接口URL","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
583 | return;
584 | }
585 |
586 | if(taInterfaceTmplReq.getText().trim() == null|| taInterfaceTmplReq.getText().trim().equals("")){
587 | JOptionPane.showMessageDialog(null,"请设置调用接请求数据包","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
588 | return;
589 | }
590 |
591 | if(tfRegular.getText().trim() == null|| tfRegular.getText().trim().equals("")){
592 | JOptionPane.showMessageDialog(null,"请设置好匹配结果的正则","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
593 | return;
594 | }
595 |
596 | SwingUtilities.invokeLater(new Runnable() {
597 | @Override
598 | public void run() {
599 | String tplName = JOptionPane.showInputDialog(null,"请输入模版名字","captcha-killer提示",JOptionPane.INFORMATION_MESSAGE);
600 | TmplEntity tpl = new TmplEntity();
601 | tpl.setName(tplName);
602 | tpl.setReqpacke(taInterfaceTmplReq.getText());
603 | tpl.setService(new HttpService(tfInterfaceURL.getText()));
604 | tpl.setRule(new Rule(cbmRuleType.getSelectedIndex(),tfRegular.getText()));
605 | synchronized (TmplEntity.tpls){
606 | TmplEntity.tpls.add(tpl);
607 | }
608 | String tpldb = JSON.toJSONString(TmplEntity.tpls);
609 | BurpExtender.callbacks.saveExtensionSetting("tpldb",tpldb);
610 | }
611 | });
612 |
613 | }
614 | });
615 | }
616 | });
617 | //miUpdateTpl.addActionListener(new MenuActionManger());
618 | miUpdateTpl.addActionListener(new ActionListener() {
619 | @Override
620 | public void actionPerformed(ActionEvent e) {
621 | if(tfInterfaceURL.getText().trim() == null || tfInterfaceURL.getText().trim().equals("")){
622 | JOptionPane.showMessageDialog(null,"请设置好接口URL","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
623 | return;
624 | }
625 |
626 | if(taInterfaceTmplReq.getText().trim() == null|| taInterfaceTmplReq.getText().trim().equals("")){
627 | JOptionPane.showMessageDialog(null,"请设置调用接请求数据包","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
628 | return;
629 | }
630 |
631 | if(tfRegular.getText().trim() == null|| tfRegular.getText().trim().equals("")){
632 | JOptionPane.showMessageDialog(null,"请设置好匹配结果的正则","captcha-killer提示",JOptionPane.WARNING_MESSAGE);
633 | return;
634 | }
635 | SwingUtilities.invokeLater(new Runnable() {
636 | @Override
637 | public void run() {
638 | CustomDlg dlg = new CustomDlg(CustomDlg.TYPE_UPDATE_TPL);
639 | dlg.setVisible(true);
640 | }
641 | });
642 | }
643 | });
644 | //miDelTpl.addActionListener(new MenuActionManger());
645 | miDelTpl.addActionListener(new ActionListener() {
646 | @Override
647 | public void actionPerformed(ActionEvent e) {
648 | SwingUtilities.invokeLater(new Runnable() {
649 | @Override
650 | public void run() {
651 | CustomDlg dlg = new CustomDlg(CustomDlg.TYPE_DEL_TPL);
652 | dlg.setVisible(true);
653 | }
654 | });
655 | }
656 | });
657 | miImageRaw.addActionListener(new MenuActionManger());
658 | miBase64Encode.addActionListener(new MenuActionManger());
659 | miURLEncode.addActionListener(new MenuActionManger());
660 |
661 | if(TmplEntity.tpls.size()>0){
662 | menuTmplManager.addSeparator();
663 | for(TmplEntity tpl:TmplEntity.tpls){
664 | JMenuItem item = new JMenuItem(tpl.getName());
665 | item.addActionListener(new MenuItemAction(tpl));
666 | menuTmplManager.add(item);
667 | }
668 | }
669 | pmInterfaceMenu.show(taInterfaceTmplReq, e.getX(), e.getY());
670 | }
671 | }
672 | });
673 |
674 | // 规则类型
675 | cbmRuleType.addItemListener(new ItemListener() {
676 | @Override
677 | public void itemStateChanged(ItemEvent e) {
678 | switch (e.getStateChange()){
679 | case ItemEvent.SELECTED:
680 | if(e.getItem().equals("Response data")){
681 | tfRegular.setText("response_data");
682 | tfRegular.setEnabled(false);
683 | }else{
684 | tfRegular.setEnabled(true);
685 | }
686 | break;
687 | }
688 | }
689 | });
690 | //匹配
691 | btnSaveTmpl.addActionListener(new ActionListener() {
692 | @Override
693 | public void actionPerformed(ActionEvent e) {
694 | Style keywordStyle = ((StyledDocument) InterfaceRsq.getDocument()).addStyle("Keyword_Style", null);
695 | StyleConstants.setBackground(keywordStyle, Color.YELLOW);
696 | Style normalStyle = ((StyledDocument) InterfaceRsq.getDocument()).addStyle("Keyword_Style", null);
697 | StyleConstants.setForeground(normalStyle, Color.BLACK);
698 | ((StyledDocument) InterfaceRsq.getDocument()).setCharacterAttributes(0,InterfaceRsq.getText().length(),normalStyle,true);
699 |
700 | int type = cbmRuleType.getSelectedIndex();
701 | String rule = tfRegular.getText();
702 | Rule ruleEntity = new Rule(type,rule);
703 | MatchResult result = RuleMannager.match(InterfaceRsq.getText(),ruleEntity);
704 |
705 | //JOptionPane.showMessageDialog(null,Util.getStringCount(InterfaceRsq.getText(),System.lineSeparator())-1,"", JOptionPane.WARNING_MESSAGE);
706 | int offest = result.getStart();
707 | int length = result.getEnd() - result.getStart();
708 | int count = 0;
709 | //如果规则类型不是Posisition类,需要进行地址转换
710 | if(type != Rule.RULE_TYPE_POSISTION){
711 | //IndexOf获取到的字符串的位置和JTextPanel面板中的位置不一致,这是由于换行符号造成的。故需要前者减去换行符的个数就等于后者。
712 | String rspRaw = InterfaceRsq.getText();
713 | rspRaw = rspRaw.substring(0,rspRaw.indexOf(result.getResult()));
714 | //为了兼容win和*nix,故要分别统计\r和\n
715 | int rCount = Util.getStringCount(rspRaw,"\r");
716 | int nCount = Util.getStringCount(rspRaw,"\n");
717 | if(rCount > 0){
718 | count = rCount;
719 | }
720 | if(nCount > 0){
721 | count = nCount;
722 | }
723 | offest -= count;
724 | }
725 |
726 | ((StyledDocument) InterfaceRsq.getDocument()).setCharacterAttributes(offest,length,keywordStyle,true);
727 | }
728 | });
729 | // 标记为结果
730 | miMarkIdentifyResult.addActionListener(new ActionListener() {
731 | @Override
732 | public void actionPerformed(ActionEvent e) {
733 | int start = InterfaceRsq.getSelectionStart();
734 | int end = InterfaceRsq.getSelectionEnd();
735 | int rnCount = 0;
736 | String raw = InterfaceRsq.getText();
737 | String rspData = new String(Util.getRspBody(raw.getBytes()));
738 | String rule = null;
739 | switch (cbmRuleType.getSelectedIndex()){
740 | case Rule.RULE_TYPE_REGULAR:
741 | //JTextPanel.getSelectionStart()和getSelectionEnd()与String.indexOf获取到的位置有区别,需要进行转换
742 | rnCount = Util.getRNCount(raw.substring(0,start));
743 | rule = RuleMannager.generateRegular(raw,start+rnCount,end+rnCount);
744 | tfRegular.setText(rule);
745 | break;
746 | case Rule.RULE_TYPE_POSISTION:
747 | rule = RuleMannager.generatePositionRule(start,end);
748 | tfRegular.setText(rule);
749 | break;
750 | case Rule.RULE_TYPE_START_END_STRING:
751 | //JTextPanel.getSelectionStart()和getSelectionEnd()与String.indexOf获取到的位置有区别,需要进行转换
752 | rnCount = Util.getRNCount(raw.substring(0,start));
753 | rule = RuleMannager.generateStartEndRule(raw,start+rnCount,end+rnCount);
754 | tfRegular.setText(rule);
755 | break;
756 | case Rule.RULE_TYPE_JSON_MATCH:
757 | JsonMatcher jsonMatcher = new JsonMatcher();
758 | rule = jsonMatcher.buildKeyword(rspData,InterfaceRsq.getSelectedText());
759 | tfRegular.setText(rule);
760 | case Rule.RULE_TYPE_XML_MATCH:
761 | XmlMatcher xmlMatcher = new XmlMatcher();
762 | rule = xmlMatcher.buildKeyword(raw,InterfaceRsq.getSelectedText());
763 | tfRegular.setText(rule);
764 | default:
765 | break;
766 | }
767 |
768 | }
769 | });
770 |
771 | //接口返回数据面板右键显示菜单
772 | InterfaceRsq.addMouseListener(new MouseAdapter() {
773 | @Override
774 | public void mouseClicked(MouseEvent e) {
775 | //当选择匹配模式为Response data时,不显示邮件菜单
776 | if(cbmRuleType.getSelectedIndex() == 0){
777 | return;
778 | }
779 | if (e.getButton() == java.awt.event.MouseEvent.BUTTON3) {
780 | pppInterfaceRsq.show(InterfaceRsq, e.getX(), e.getY());
781 | }
782 | }
783 | });
784 |
785 | //结果列表选中事件
786 | table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
787 | @Override
788 | public void valueChanged(ListSelectionEvent e) {
789 | int row = table.getSelectedRow();
790 | if(row != -1) {
791 | taInterfaceRawReq.setText(new String(captcha.get(row).getReqRaw()));
792 | InterfaceRsq.setText(new String(captcha.get(row).getRsqRaw()));
793 | }
794 | }
795 | });
796 |
797 | //结果列表右键事件,显示菜单
798 | table.addMouseListener(new MouseAdapter() {
799 | @Override
800 | public void mouseClicked(MouseEvent e) {
801 | if (e.getButton() == java.awt.event.MouseEvent.BUTTON3) {
802 | pppMenu.show(table, e.getX(), e.getY());
803 | }
804 | }
805 | });
806 |
807 | //清空结果
808 | miClear.addActionListener(new ActionListener() {
809 | @Override
810 | public void actionPerformed(ActionEvent e) {
811 | synchronized (captcha){
812 | captcha.clear();
813 | model.fireTableDataChanged();
814 | }
815 | }
816 | });
817 |
818 | miShowIntruderResult.addActionListener(new ActionListener() {
819 | @Override
820 | public void actionPerformed(ActionEvent e) {
821 | if(BurpExtender.isShowIntruderResult) {
822 | BurpExtender.isShowIntruderResult = false;
823 | miShowIntruderResult.setText("显示Intruder识别结果");
824 | }else{
825 | BurpExtender.isShowIntruderResult = true;
826 | miShowIntruderResult.setText("隐藏Intruder识别结果");
827 | }
828 | }
829 | });
830 | }
831 |
832 |
833 | public static class GetCaptchaThread extends Thread {
834 | private String url;
835 | private String raw;
836 |
837 | public GetCaptchaThread(String url,String raw) {
838 | this.url = url;
839 | this.raw = raw;
840 | }
841 |
842 | public void run() {
843 | gui.btnGetCaptcha.setEnabled(false);
844 | //清洗验证码URL
845 | HttpService service = new HttpService(url);
846 | gui.tfURL.setText(service.toString());
847 |
848 | try {
849 | byte[][] bytesResResp = Util.requestImage(url,raw);
850 | // BurpExtender.stdout.println("820");
851 | // byte[] byteRes = bytesResResp[0]; // 只有响应包
852 | gui.byteRes = bytesResResp[0]; // 只有响应包
853 | byte[] byteResp = bytesResResp[1]; // 响应头+响应包
854 | String words = gui.tfWords.getText().trim();
855 | String token = gui.tfToken.getText().trim();
856 | // BurpExtender.stdout.println("825");
857 |
858 | if ( !token.trim().equals("") ) {
859 | gui.tokenwords = extractToken(new String(byteResp) ,token);
860 | if ( gui.tokenwords.length() > 40 ) {
861 | gui.tokenword = gui.tokenwords.substring(0,10) + "...." + gui.tokenwords.substring(gui.tokenwords.length()-10 , gui.tokenwords.length() );
862 | gui.tfTokenex.setText( gui.tokenword );
863 | } else {
864 | stdout.println(gui.tokenwords.length());
865 | gui.tfTokenex.setText( gui.tokenwords );
866 | }
867 | } else {
868 | gui.tokenwords = "";
869 | }
870 | if(!words.trim().equals("")){
871 |
872 | gui.byteImg = dataimgToimg(new String(gui.byteRes) ,words);
873 |
874 | }else {
875 | if (Util.isImage(gui.byteRes)) {
876 | BurpExtender.gui.byteImg = gui.byteRes;
877 | } else if (Util.isImage(new String(gui.byteRes))) {
878 | BurpExtender.gui.byteImg = dataimgToimg(new String(gui.byteRes));
879 | } else {
880 | gui.lbImage.setIcon(null);
881 | gui.lbImage.setText("获取到的不是图片文件或者未设置关键词!");
882 | gui.lbImage.setForeground(Color.RED);
883 | return;
884 | }
885 | }
886 | stdout.println( "get:" + Arrays.toString(BurpExtender.gui.byteImg).length());
887 | // stdout.println("successsuccesssuccesssuccessend\n");
888 |
889 | ImageIcon icon = Util.byte2img(BurpExtender.gui.byteImg);
890 | BurpExtender.gui.lbImage.setIcon(icon);
891 | BurpExtender.gui.lbImage.setText("");
892 | } catch (Exception e) {
893 | BurpExtender.stderr.println(e.getMessage());
894 | }finally {
895 | gui.getBtnGetCaptcha().setEnabled(true);
896 | }
897 | }
898 | }
899 |
900 |
901 | public static CaptchaEntity identifyCaptcha(String url,String raw,byte[] byteImg,int type,String pattern) throws IOException {
902 | CaptchaEntity cap = new CaptchaEntity();
903 | // BurpExtender.stdout.println(new String(byteImg));
904 | // byteImg = new String(byteImg).replace("data:image/png;base64,","").getBytes();
905 | cap.setImage(byteImg);
906 | HttpClient http = new HttpClient(url, raw, byteImg);
907 | cap.setReqRaw(http.getRaw().getBytes());
908 | byte[] rsp = http.doReust();
909 | cap.setRsqRaw(rsp);
910 | String rspRaw = new String(rsp);
911 |
912 | Rule rule = new Rule(type,pattern);
913 | MatchResult result = RuleMannager.match(rspRaw, rule);
914 | // String result_1 = result.getResult();
915 | // if ( !gui.tokenwords.equals("") ) {
916 | // cap.setResult(result_1 + "|" + gui.tokenwords);
917 | // int row = captcha.size();
918 | // stdout.println(cap.getResult());
919 | // captcha.add(cap);
920 | // BurpExtender.gui.getModel().fireTableRowsInserted(row, row);
921 | // stdout.println("nulllllll");
922 | // }
923 | // else
924 | // cap.setResult(result_1);
925 |
926 | // cap.setResult(result_1);
927 | //排查请求速度过快可能会导致
928 | // BurpExtender.stdout.println("---------------------------------------------");
929 | // BurpExtender.stdout.println(rspRaw);
930 | BurpExtender.stdout.println(gui.tokenwords);
931 | BurpExtender.stdout.println("[+] res = " + result.getResult());
932 | if(BurpExtender.isShowIntruderResult) {
933 | synchronized (gui.captcha) {
934 | int row = gui.captcha.size();
935 | cap.setResult(cap.getResult() + "|"+ gui.tokenwords);
936 | gui.captcha.add(cap);
937 | gui.getModel().fireTableRowsInserted(row, row);
938 | }
939 | }
940 | return cap;
941 | }
942 |
943 | public static String identifyCaptchas(String url,String raw,byte[] byteImg,int type,String pattern) throws IOException {
944 | CaptchaEntity cap = new CaptchaEntity();
945 | cap.setImage(byteImg);
946 | stdout.println( "identify:" + Arrays.toString(byteImg).length());
947 | HttpClient http = new HttpClient(url, raw, byteImg);
948 | cap.setReqRaw(http.getRaw().getBytes());
949 | byte[] rsp = http.doReust();
950 | cap.setRsqRaw(rsp);;
951 | String rspRaw = new String(rsp);
952 |
953 | Rule rule = new Rule(type,pattern);
954 | MatchResult result = RuleMannager.match(rspRaw, rule);
955 | // if ( !gui.tokenwords.equals("") )
956 | // cap.setResult(result.getResult() + "|"+ gui.tokenwords);
957 | // else
958 | cap.setResult(result.getResult());
959 | //排查请求速度过快可能会导致
960 | // BurpExtender.stdout.println("---------------------------------------------");
961 | // BurpExtender.stdout.println("---------------------------------------------");
962 | // BurpExtender.stdout.println(rspRaw);
963 | BurpExtender.stdout.println(gui.tokenwords);
964 | BurpExtender.stdout.println("[+] res = " + result.getResult());
965 | if(BurpExtender.isShowIntruderResult) {
966 | synchronized (gui.captcha) {
967 | int row = gui.captcha.size();
968 | if (gui.tokenwords.equals("")){
969 | cap.setResult( cap.getResult() );
970 | }else
971 | cap.setResult(cap.getResult() + "|"+ gui.tokenwords);
972 |
973 | gui.captcha.add(cap);
974 | gui.getModel().fireTableRowsInserted(row, row);
975 | }
976 | }
977 | return result.getResult();
978 | }
979 | public Boolean getUsebutton(){
980 | return usebutton.isSelected();
981 | }
982 |
983 | public class IdentifyCaptchaThread extends Thread{
984 | private String url;
985 | private String raw;
986 | private byte[] img;
987 | private String pattern;
988 |
989 | public IdentifyCaptchaThread(String url,String raw,byte[] img){
990 | this.url = url;
991 | this.raw = raw;
992 | this.img = img;
993 | this.pattern = tfRegular.getText();
994 | }
995 |
996 | @Override
997 | public void run() {
998 |
999 | int type = cbmRuleType.getSelectedIndex();
1000 | Rule myRule = new Rule(type,tfRegular.getText());
1001 |
1002 | InterfaceRsq.setText("");
1003 | //btnIdentify.setEnabled(false);
1004 | //清洗接口URL
1005 | HttpService service = new HttpService(url);
1006 | tfInterfaceURL.setText(service.toString());
1007 |
1008 | HttpClient http = null;
1009 | try {
1010 | http = new HttpClient(url,raw,byteImg);
1011 | } catch (IOException e) {
1012 | e.printStackTrace();
1013 | }
1014 | taInterfaceRawReq.setText(http.getRaw());
1015 | byte[] rsp = http.doReust();
1016 | String rspRaw = new String(rsp);
1017 | InterfaceRsq.setText(rspRaw);
1018 | btnIdentify.setEnabled(true);
1019 |
1020 | MatchResult result = RuleMannager.match(rspRaw,myRule);
1021 | CaptchaEntity cap = new CaptchaEntity();
1022 | cap.setImage(img);
1023 | cap.setReqRaw(http.getRaw().getBytes());
1024 | cap.setRsqRaw(rsp);
1025 | cap.setResult(result.getResult());
1026 |
1027 |
1028 | gui.cap = result.getResult();
1029 | tfcapex.setText(gui.cap);
1030 |
1031 | synchronized (captcha){
1032 | int row = captcha.size();
1033 | captcha.add(cap);
1034 | model.fireTableRowsInserted(row,row);
1035 | }
1036 | }
1037 | }
1038 |
1039 | public class MenuActionManger implements ActionListener {
1040 | @Override
1041 | public void actionPerformed(ActionEvent e) {
1042 |
1043 | }
1044 | }
1045 |
1046 | public class MenuItemAction implements ActionListener {
1047 | private TmplEntity tpl;
1048 |
1049 | public MenuItemAction(TmplEntity tpl){
1050 | this.tpl = tpl;
1051 | }
1052 |
1053 | @Override
1054 | public void actionPerformed(ActionEvent e) {
1055 | tfInterfaceURL.setText(tpl.getService().toString());
1056 | taInterfaceTmplReq.setText(tpl.getReqpacke());
1057 | cbmRuleType.setSelectedIndex(tpl.getRule().getType());
1058 | tfRegular.setText(tpl.getRule().getRule());
1059 | }
1060 | }
1061 |
1062 | public Component getComponet(){
1063 | return MainPanel;
1064 | }
1065 | }
1066 |
1067 |
--------------------------------------------------------------------------------