├── requirement.txt ├── src ├── main │ └── java │ │ ├── matcher │ │ ├── IMathcher.java │ │ └── impl │ │ │ ├── XmlMatcher.java │ │ │ └── JsonMatcher.java │ │ ├── entity │ │ ├── MatchResult.java │ │ ├── CaptchaEntity.java │ │ ├── TmplEntity.java │ │ ├── HttpService.java │ │ └── Rule.java │ │ ├── ui │ │ ├── GBC.java │ │ ├── Menu.java │ │ ├── model │ │ │ └── TableModel.java │ │ ├── CustomDlg.java │ │ └── GUI.java │ │ ├── utils │ │ ├── test2.java │ │ ├── test.java │ │ ├── RuleMannager.java │ │ ├── HttpClient.java │ │ ├── LableParser.java │ │ └── Util.java │ │ └── burp │ │ ├── GeneratePayloadSwingWorker.java │ │ ├── BurpExtender.java │ │ └── code.java └── test │ └── java │ ├── Test1.java │ ├── Test.java │ ├── matcher │ └── impl │ │ └── XmlMatcherTest.java │ └── HighlightKeywordsDemo.java ├── pom.xml ├── README.md ├── codereg.py └── FAQ.md /requirement.txt: -------------------------------------------------------------------------------- 1 | Pillow==9.5.0 2 | aiohttp==3.8.3 3 | argparse==1.1 4 | ddddocr==1.5.3 5 | -------------------------------------------------------------------------------- /src/main/java/matcher/IMathcher.java: -------------------------------------------------------------------------------- 1 | package matcher; 2 | 3 | import entity.MatchResult; 4 | 5 | public interface IMathcher { 6 | public MatchResult match(String strResp, String keyword); 7 | public String buildKeyword(String strResp,String value); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/Test1.java: -------------------------------------------------------------------------------- 1 | import utils.Util; 2 | 3 | public class Test1 { 4 | public static void main(String[] args) { 5 | String str = "a\n\rb\n\rc\n\r"; 6 | System.out.println(str.indexOf("b")); 7 | str = "sdfsfsteerwfwfdsvsdfggefdf"; 8 | System.out.println(str.indexOf("sssssssssss")); 9 | 10 | String header = "bbb: sdsdsd:sdsdsdsd"; 11 | System.out.println(header.substring(0, header.indexOf(":"))); 12 | System.out.println(Util.trimStart(header.substring(header.indexOf(":")+1, header.length()))); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/entity/MatchResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package entity; 6 | 7 | public class MatchResult { 8 | private String result = "";//匹配成功的字符串 9 | private int start = 0; //匹配结果开始位置 10 | private int end = 0; //匹配结果结束位置 11 | 12 | public String getResult() { 13 | return result; 14 | } 15 | 16 | public void setResult(String result) { 17 | this.result = result; 18 | } 19 | 20 | public int getStart() { 21 | return start; 22 | } 23 | 24 | public void setStart(int start) { 25 | this.start = start; 26 | } 27 | 28 | public int getEnd() { 29 | return end; 30 | } 31 | 32 | public void setEnd(int end) { 33 | this.end = end; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/Test.java: -------------------------------------------------------------------------------- 1 | import java.util.regex.Matcher; 2 | import java.util.regex.Pattern; 3 | 4 | public class Test { 5 | public static void main(String[] args) { 6 | String str = "title=\"韩国和规范\">yfffgfgiuiuyuytfuigfg"; 7 | Pattern p = Pattern.compile("title=\"(.+?)\""); 8 | Matcher m = p.matcher(str); 9 | //System.out.println(m.start()); 10 | while(m.find()) { 11 | String res = m.group(1); 12 | int start = m.start(); 13 | int n = str.substring(start,str.length()).indexOf(res); 14 | 15 | System.out.println(res); 16 | //System.out.println(m.lookingAt()); 17 | System.out.println(start); 18 | System.out.println(start + n); 19 | } 20 | 21 | System.out.println("ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/entity/CaptchaEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package entity; 6 | 7 | public class CaptchaEntity { 8 | private byte[] image; 9 | private byte[] reqRaw; 10 | private byte[] rsqRaw; 11 | private String result; 12 | 13 | public byte[] getImage() { 14 | return image; 15 | } 16 | 17 | public void setImage(byte[] image) { 18 | this.image = image; 19 | } 20 | 21 | public byte[] getReqRaw() { 22 | return reqRaw; 23 | } 24 | 25 | public void setReqRaw(byte[] reqRaw) { 26 | this.reqRaw = reqRaw; 27 | } 28 | 29 | public byte[] getRsqRaw() { 30 | return rsqRaw; 31 | } 32 | 33 | public void setRsqRaw(byte[] rsqRaw) { 34 | this.rsqRaw = rsqRaw; 35 | } 36 | 37 | public String getResult() { 38 | return result; 39 | } 40 | 41 | public void setResult(String result) { 42 | this.result = result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/entity/TmplEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package entity; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class TmplEntity { 11 | public static List tpls = new ArrayList(); 12 | private String name; 13 | private HttpService service; 14 | private String reqpacke; 15 | private Rule rule; 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public HttpService getService() { 26 | return service; 27 | } 28 | 29 | public void setService(HttpService service) { 30 | this.service = service; 31 | } 32 | 33 | public String getReqpacke() { 34 | return reqpacke; 35 | } 36 | 37 | public void setReqpacke(String reqpacke) { 38 | this.reqpacke = reqpacke; 39 | } 40 | 41 | public Rule getRule() { 42 | return rule; 43 | } 44 | 45 | public void setRule(Rule rule) { 46 | this.rule = rule; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ui/GBC.java: -------------------------------------------------------------------------------- 1 | package ui; 2 | 3 | import java.awt.GridBagConstraints; 4 | import java.awt.Insets; 5 | 6 | public class GBC extends GridBagConstraints 7 | { 8 | public GBC(int gridx,int gridy) { 9 | this.gridx = gridx; 10 | this.gridy = gridy; 11 | } 12 | 13 | 14 | public GBC(int gridx,int gridy,int gridwidth,int gridheight) { 15 | this.gridx = gridx; 16 | this.gridy = gridy; 17 | this.gridwidth = gridwidth; 18 | this.gridheight = gridheight; 19 | } 20 | 21 | 22 | public GBC setFill(int fill) { 23 | this.fill = fill; 24 | return this; 25 | } 26 | 27 | 28 | public GBC setWeight(double weightx,double weighty) { 29 | this.weightx = weightx; 30 | this.weighty = weighty; 31 | return this; 32 | } 33 | 34 | 35 | public GBC setAnchor(int anchor) { 36 | this.anchor = anchor; 37 | return this; 38 | } 39 | 40 | 41 | public GBC setInsets(int top, int left, int bottom, int right) { 42 | this.insets = new Insets(top, left, bottom, right); 43 | return this; 44 | } 45 | 46 | 47 | public GBC setIpad(int ipadx,int ipady) { 48 | this.ipadx = ipadx; 49 | this.ipady = ipady; 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/utils/test2.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * @author f0ng 7 | * @date 2022/3/30 8 | */ 9 | public class test2 { 10 | public static void main(String[] args) throws IOException { 11 | // String url = "https://0.zone:443"; 12 | // 13 | // String raw = "GET /api/captcha/?t=1671786233651 HTTP/1.1\n" + 14 | // "Host: 0.zone\n" + 15 | // "Cookie: csrftoken=FHW6BhBwlh8eFO0iqwm8VDuWKXCZ7bP8XPsu3cR0wbe4FuDhlIm6vW5h4NOHHpAq; sessionid=8ylbc9riyci8nkor68fmiqbu7q0g4tjs\n" + 16 | // "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3\n" + 17 | // "Accept: application/json\n" + 18 | // "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" + 19 | // "Accept-Encoding: gzip, deflate\n" + 20 | // "Cache-Control: no-cache\n" + 21 | // "X-Csrftoken: FHW6BhBwlh8eFO0iqwm8VDuWKXCZ7bP8XPsu3cR0wbe4FuDhlIm6vW5h4NOHHpAq\n" + 22 | // "Sec-Fetch-Dest: empty\n" + 23 | // "Sec-Fetch-Mode: cors\n" + 24 | // "Sec-Fetch-Site: same-origin\n" + 25 | // "Te: trailers\n" + 26 | // "\n"; 27 | // HttpClient http = new HttpClient(url, raw, null); 28 | // byte[] rsp = http.doReust(); 29 | // 30 | // System.out.println(rsp); 31 | 32 | String a = "Businesstypecode: "; 33 | System.out.println(a.indexOf(":")); 34 | System.out.println(Util.trimStart(a.substring(a.indexOf(":")+1))); 35 | 36 | 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/matcher/impl/XmlMatcherTest.java: -------------------------------------------------------------------------------- 1 | package matcher.impl; 2 | 3 | import entity.MatchResult; 4 | import org.dom4j.Document; 5 | import org.dom4j.Element; 6 | import org.dom4j.io.SAXReader; 7 | 8 | import java.io.ByteArrayInputStream; 9 | 10 | public class XmlMatcherTest { 11 | 12 | @org.junit.Test 13 | public void match() { 14 | String strXml = "true0success9e9e18f064e2d3f947dc91e94c98c4461217"; 15 | String keyword = "ResultMessage.data#code"; 16 | XmlMatcher xmlMatcher = new XmlMatcher(); 17 | xmlMatcher.searchXml(getRoot(strXml),keyword); 18 | //System.out.println(String.format("result: %s start:%d end:%d",matchResult.getResult(),matchResult.getStart(),matchResult.getEnd())); 19 | } 20 | 21 | @org.junit.Test 22 | public void buildKeyword() { 23 | String strXml = "true0success9e9e18f064e2d3f947dc91e94c98c4461217"; 24 | 25 | XmlMatcher xmlMatcher = new XmlMatcher(); 26 | xmlMatcher.buildKeyword(getRoot(strXml),"9e9e",null); 27 | //System.out.println(String.format("keyword: %s",keyword)); 28 | } 29 | 30 | public static Element getRoot(String strXml){ 31 | SAXReader sax = new SAXReader(); 32 | Element root = null; 33 | try { 34 | Document document = sax.read(new ByteArrayInputStream(strXml.getBytes())); 35 | root = document.getRootElement();// 获取根节点 36 | }catch (Exception e){ 37 | e.printStackTrace(); 38 | } 39 | return root; 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/entity/HttpService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package entity; 6 | 7 | import burp.BurpExtender; 8 | import burp.IHttpService; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | 13 | public class HttpService implements IHttpService { 14 | private String protocol; 15 | private String host; 16 | private int port; 17 | 18 | public HttpService(String url){ 19 | if(url.startsWith("https://")){ 20 | protocol = "https"; 21 | }else{ 22 | protocol = "http"; 23 | } 24 | 25 | try { 26 | URL u = new URL(url); 27 | this.protocol = u.getProtocol(); 28 | this.host = u.getHost(); 29 | if(u.getPort() == -1){ 30 | if(protocol.equals("https")){ 31 | this.port = 443; 32 | }else{ 33 | this.port = 80; 34 | } 35 | }else{ 36 | this.port = u.getPort(); 37 | } 38 | 39 | } catch (MalformedURLException e) { 40 | BurpExtender.stderr.println("[-] " + e.getMessage()); 41 | } 42 | } 43 | 44 | public HttpService(String protocol, String host, int port){ 45 | this.protocol = protocol; 46 | this.host = host; 47 | this.port = port; 48 | } 49 | 50 | @Override 51 | public String getProtocol() { 52 | return protocol; 53 | } 54 | 55 | @Override 56 | public String getHost() { 57 | return host; 58 | } 59 | 60 | @Override 61 | public int getPort() { 62 | return port; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return String.format("%s://%s:%d",this.protocol,this.host,this.port); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/utils/test.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import javax.swing.*; 4 | import javax.xml.bind.DatatypeConverter; 5 | import java.io.ByteArrayInputStream; 6 | import java.io.InputStream; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * @author f0ng 12 | * @date 2022/3/30 13 | */ 14 | public class test extends JFrame{ 15 | public void GUI() { 16 | String a = "{\"code\":0,\"message\":\"\",\"data\":{\"id\":\"cd2a68265e7ebc6aa7b18032623d9b99\",\"img\":\"data:image\\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAoBAMAAAD6VkJwAAAAG1BMVEXz+\\/6TWyPn5+LPv6vDq5C3\\r\\nl3Wfbz7b08erg1ldU+\\/iAAABQUlEQVQ4je2UsU7DMBCGr3ajMNYkjT0mS9WRAA+QVKJzKxgYG4Si\\r\\njoRIiLEGqfDYXHxJqYRFnIUB9V98cvz5fP\\/ZATjpH4pdXwxF1iJ6GkZ4IaSbNt\\/NrRMy2oF3TmGl\\r\\nipULkmQwlhSWsSddzzYxgx8CUzTzGvclouPwAEAT3GuIjg9IbtYmbz0ED6E7GKSmR3lfkmRHI5ui\\r\\naVkT7Q\\/ffDu8j9sAax+ZskqmlZnkhbWqcdBF2nSp2SR51A9NkL7nGwtSrVhNtwxLoCz5FnhjN3rO\\r\\nAwtSlELQVmg21VJJstHH9Tbk8+7qmSIvrEtTwpkk7\\/wp2nhpYb61Fh9m5Ohd2tRdLF6E+PXesTYd\\r\\n6Iz6O8OHMbcd7admakkLawksckJgqagffHLcWCdxNGI7DGFRPHd9Q53ySAz9m\\/j3i4HESX+qL6nX\\r\\nKovUqUE8AAAAAElFTkSuQmCC\\r\\n\"}}"; 17 | String pattern = "(data:image.*?)[\"|&]|(data%2Aimage.*?)[\"|&]"; 18 | 19 | Pattern r = Pattern.compile(pattern); 20 | Matcher m = r.matcher(a); 21 | if (m.find()) { 22 | a = m.group(0).replace("\"", "").replace("&", "").replace("Base64:", "").replace("base64:", ""); 23 | } 24 | System.out.println(a); 25 | byte[] img = DatatypeConverter.parseBase64Binary(a.substring(a.indexOf(",") + 1)); 26 | InputStream buffin = new ByteArrayInputStream(img); 27 | System.out.println(buffin); 28 | ImageIcon icon = Util.byte2img(img); 29 | //System.out.println(icon.getImage()); 30 | 31 | setTitle("图像测试"); 32 | JPanel panel = new JPanel(); 33 | JLabel label = new JLabel(); 34 | //ImageIcon img = new ImageIcon("images/logo.jpg");// 创建图片对象 35 | label.setIcon(icon); 36 | panel.add(label); 37 | add(panel); 38 | setExtendedState(JFrame.MAXIMIZED_BOTH);// JFrame最大化 39 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 让JFrame的关闭按钮起作用 40 | setVisible(true);// 显示JFrame 41 | 42 | } 43 | 44 | public static void main(String[] args) { 45 | 46 | test d = new test(); 47 | d.GUI(); 48 | 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/ui/Menu.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package ui; 6 | 7 | import burp.*; 8 | import javax.swing.*; 9 | import java.awt.event.ActionEvent; 10 | import java.awt.event.ActionListener; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * 菜单类,负责显示菜单,处理菜单事件 16 | */ 17 | public class Menu implements IContextMenuFactory { 18 | public List createMenuItems(final IContextMenuInvocation invocation) { 19 | List menus = new ArrayList(); 20 | JMenu menu = new JMenu("captcha-killer"); 21 | JMenuItem miSend2Cap = new JMenuItem("Send to captcha panel"); 22 | JMenuItem miSend2Interface = new JMenuItem("Send to interface panel"); 23 | menu.add(miSend2Cap); 24 | menu.add(miSend2Interface); 25 | 26 | final IHttpRequestResponse iReqResp = invocation.getSelectedMessages()[0]; 27 | IRequestInfo reqInfo = BurpExtender.helpers.analyzeRequest(iReqResp.getRequest()); 28 | 29 | miSend2Cap.addActionListener(new ActionListener(){ 30 | 31 | public void actionPerformed(ActionEvent arg0) { 32 | try { 33 | IHttpService httpservice = iReqResp.getHttpService(); 34 | String url = String.format("%s://%s:%d",httpservice.getProtocol(),httpservice.getHost(),httpservice.getPort()); 35 | BurpExtender.gui.getTfURL().setText(url); 36 | BurpExtender.gui.getTaRequest().setText(new String(iReqResp.getRequest())); 37 | }catch (Exception e){ 38 | BurpExtender.stderr.println("[-] " + e.getMessage()); 39 | } 40 | } 41 | }); 42 | 43 | miSend2Interface.addActionListener(new ActionListener() { 44 | @Override 45 | public void actionPerformed(ActionEvent e) { 46 | try { 47 | IHttpService httpservice = iReqResp.getHttpService(); 48 | String url = String.format("%s://%s:%d",httpservice.getProtocol(),httpservice.getHost(),httpservice.getPort()); 49 | BurpExtender.gui.getInterfaceURL().setText(url); 50 | BurpExtender.gui.getTaInterfaceTmplReq().setText(new String(iReqResp.getRequest())); 51 | }catch (Exception ex){ 52 | BurpExtender.stderr.println("[-] " + ex.getMessage()); 53 | } 54 | } 55 | }); 56 | menus.add(menu); 57 | return menus; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/burp/GeneratePayloadSwingWorker.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import ui.GUI; 4 | import utils.Util; 5 | 6 | import javax.swing.*; 7 | 8 | import static burp.BurpExtender.gui; 9 | 10 | public class GeneratePayloadSwingWorker extends SwingWorker { 11 | @Override 12 | protected Object doInBackground() throws Exception { 13 | // BurpExtender.stdout.println("*****"); 14 | if(!Util.isURL(gui.getInterfaceURL().getText())){ 15 | return "Interface URL format invalid".getBytes(); 16 | } 17 | 18 | // CaptchaEntity cap = new CaptchaEntity(); 19 | // String cap = null; 20 | int count = 0; 21 | try { 22 | // byte[] byteImg = Util.requestImage(BurpExtender.gui.getCaptchaURL(),BurpExtender.gui.getCaptchaReqRaw())[0]; 23 | // byte[] byteResp = Util.requestImage(BurpExtender.gui.getCaptchaURL(),BurpExtender.gui.getCaptchaReqRaw())[1]; 24 | 25 | // byte[][] bytesResResp = Util.requestImage(gui.getCaptchaURL(), gui.getCaptchaReqRaw()); 26 | 27 | // byte[] byteImg = bytesResResp[0]; // 只有响应包 28 | 29 | // BurpExtender.stdout.println(new String(gui.byteImg)); 30 | // byte[] byteResp = bytesResResp[1]; // 响应头+响应包 31 | // byteImg = new String(byteImg).replace("data:image/png;base64,","").getBytes(); 32 | // String token = gui.tfToken.getText().trim(); 33 | // if (!token.trim().equals("")) { 34 | // gui.tokenwords = extractToken(new String(byteResp) ,token); 35 | // } 36 | BurpExtender.gui.cap = GUI.identifyCaptchas( BurpExtender.gui.getInterfaceURL().getText(),BurpExtender.gui.getTaInterfaceTmplReq().getText(),BurpExtender.gui.byteImg,BurpExtender.gui.getCbmRuleType().getSelectedIndex(),BurpExtender.gui.getRegular().getText() ); 37 | 38 | //遗留问题:burp自带的发包,无法指定超时。如果访问速度过快,这里可能为空。 39 | while (count < 3 ) { 40 | // BurpExtender.stdout.println("error"); 41 | // BurpExtender.stdout.println(gui.cap); 42 | // BurpExtender.gui.cap = GUI.identifyCaptchas( BurpExtender.gui.getInterfaceURL().getText(),BurpExtender.gui.getTaInterfaceTmplReq().getText(),BurpExtender.gui.byteImg,BurpExtender.gui.getCbmRuleType().getSelectedIndex(),BurpExtender.gui.getRegular().getText() ) ; 43 | // gui.cap = GUI.identifyCaptchas(gui.getInterfaceURL().getText(), gui.getTaInterfaceTmplReq().getText(), gui.byteImg, gui.getCbmRuleType().getSelectedIndex(), gui.getRegular().getText()); 44 | // if(cap.getResult() == null || cap.getResult().trim().equals("")){ 45 | // Thread.sleep(1000); 46 | // count += 1; 47 | // }else{ 48 | // break; 49 | // } 50 | if(gui.cap == null || gui.cap.trim().equals("")){ 51 | Thread.sleep(1500); 52 | count += 1; 53 | }else{ 54 | break; 55 | } 56 | } 57 | 58 | 59 | // if(BurpExtender.isShowIntruderResult) { 60 | // synchronized (BurpExtender.gui.captcha) { 61 | // int row = BurpExtender.gui.captcha.size(); 62 | // BurpExtender.gui.captcha.add(cap); 63 | // BurpExtender.gui.getModel().fireTableRowsInserted(row, row); 64 | // } 65 | // } 66 | } catch (Exception e) { 67 | // cap(e.getMessage()); 68 | } 69 | return gui.cap.getBytes(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.gv7.tools.burpext 8 | captcha-killer-modified 9 | 0.17 10 | 11 | 12 | 13 | 14 | net.portswigger.burp.extender 15 | burp-extender-api 16 | 1.7.22 17 | 18 | 19 | 20 | 21 | com.alibaba 22 | fastjson 23 | 1.2.74 24 | 25 | 26 | 27 | 28 | org.dom4j 29 | dom4j 30 | 2.0.3 31 | 32 | 33 | 34 | 35 | 36 | junit 37 | junit 38 | 4.13 39 | test 40 | 41 | 42 | 43 | 44 | 45 | javax.xml.bind 46 | jaxb-api 47 | 2.3.0 48 | 49 | 50 | com.sun.xml.bind 51 | jaxb-impl 52 | 2.3.0 53 | 54 | 55 | org.glassfish.jaxb 56 | jaxb-runtime 57 | 2.3.0 58 | 59 | 60 | javax.activation 61 | activation 62 | 1.1.1 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-assembly-plugin 72 | 73 | 74 | package 75 | 76 | single 77 | 78 | 79 | 80 | 81 | 82 | jar-with-dependencies 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.1 90 | 91 | 8 92 | 8 93 | utf-8 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/entity/Rule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package entity; 6 | 7 | import com.alibaba.fastjson.JSONObject; 8 | 9 | public class Rule { 10 | public final static int RULE_TYPE_RESPONSE_DATA = 0; 11 | public final static int RULE_TYPE_REGULAR = 1; 12 | public final static int RULE_TYPE_POSISTION = 2; 13 | public final static int RULE_TYPE_START_END_STRING = 3; 14 | public final static int RULE_TYPE_JSON_MATCH = 4; 15 | public final static int RULE_TYPE_XML_MATCH = 5; 16 | private int type; 17 | private String rule; 18 | private int nStart; 19 | private int nEnd; 20 | private String strStart; 21 | private String strEnd; 22 | 23 | public Rule(int type,String rule){ 24 | this.type = type; 25 | if(type == RULE_TYPE_RESPONSE_DATA){ 26 | this.rule = "response_data"; 27 | }else if(type == RULE_TYPE_REGULAR){ 28 | this.rule = rule; 29 | }else if(type == RULE_TYPE_POSISTION){ 30 | int[] position = parsePositionRule(rule); 31 | this.nStart = position[0]; 32 | this.nEnd = position[1]; 33 | }else if(type == RULE_TYPE_START_END_STRING){ 34 | String[] str = parseStartEndString(rule); 35 | this.strStart = str[0]; 36 | this.strEnd = str[1]; 37 | }else if(type == RULE_TYPE_JSON_MATCH){ 38 | this.rule = rule; 39 | }else if(type == RULE_TYPE_XML_MATCH){ 40 | this.rule = rule; 41 | } 42 | } 43 | 44 | public int getType() { 45 | return type; 46 | } 47 | 48 | public void setType(int type) { 49 | this.type = type; 50 | } 51 | 52 | public String getRule() { 53 | if(type == RULE_TYPE_RESPONSE_DATA){ 54 | return "request_data"; 55 | }else if(type == RULE_TYPE_REGULAR){ 56 | return rule; 57 | }else if(type == RULE_TYPE_POSISTION){ 58 | return String.format("{\"start\":%d,\"end\":%d}",nStart,nEnd); 59 | }else if(type == RULE_TYPE_START_END_STRING){ 60 | return String.format("{\"start\":%s,\"end\":%s}",strStart,strEnd); 61 | }else if(type == RULE_TYPE_JSON_MATCH){ 62 | return rule; 63 | }else if(type == RULE_TYPE_XML_MATCH){ 64 | return rule; 65 | }else{ 66 | return rule; 67 | } 68 | 69 | } 70 | 71 | public void setRule(String rule) { 72 | this.rule = rule; 73 | } 74 | 75 | public int getnStart() { 76 | return nStart; 77 | } 78 | 79 | public void setnStart(int nStart) { 80 | this.nStart = nStart; 81 | } 82 | 83 | public int getnEnd() { 84 | return nEnd; 85 | } 86 | 87 | public void setnEnd(int nEnd) { 88 | this.nEnd = nEnd; 89 | } 90 | 91 | public String getStrStart() { 92 | return strStart; 93 | } 94 | 95 | public void setStrStart(String strStart) { 96 | this.strStart = strStart; 97 | } 98 | 99 | public String getStrEnd() { 100 | return strEnd; 101 | } 102 | 103 | public void setStrEnd(String strEnd) { 104 | this.strEnd = strEnd; 105 | } 106 | 107 | public static int[] parsePositionRule(String rule){ 108 | JSONObject json = (JSONObject) JSONObject.parse(rule); 109 | int start = Integer.valueOf(json.get("start").toString()); 110 | int end = Integer.valueOf(json.get("end").toString()); 111 | return new int[]{start,end}; 112 | } 113 | 114 | public static String[] parseStartEndString(String rule){ 115 | JSONObject json = (JSONObject) JSONObject.parse(rule); 116 | String start = json.get("start").toString(); 117 | String end = json.get("end").toString(); 118 | return new String[]{start,end}; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/ui/model/TableModel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 c0ny1 (https://github.com/c0ny1/captcha-killer) 3 | * License: MIT 4 | */ 5 | package ui.model; 6 | 7 | import burp.BurpExtender; 8 | import entity.CaptchaEntity; 9 | import ui.GUI; 10 | 11 | import javax.swing.*; 12 | import javax.swing.table.AbstractTableModel; 13 | import javax.xml.bind.DatatypeConverter; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | import static utils.Util.byte2img; 20 | import static utils.Util.dataimgToimg; 21 | 22 | public class TableModel extends AbstractTableModel { 23 | private List title = new ArrayList(); 24 | private JTable table; 25 | public TableModel(JTable table){ 26 | this.table = table; 27 | title.add(0,"验证码"); 28 | title.add(1,"识别结果"); 29 | } 30 | 31 | public String getColumnName(int column) { 32 | // TODO Auto-generated method stub 33 | return title.get(column); 34 | } 35 | 36 | @Override 37 | public int getRowCount() { 38 | return GUI.captcha.size(); 39 | } 40 | 41 | @Override 42 | public int getColumnCount() { 43 | return title.size(); 44 | } 45 | 46 | @Override 47 | public Class getColumnClass(int columnIndex) 48 | { 49 | if(columnIndex == 0){ 50 | return Icon.class; 51 | } 52 | return String.class; 53 | } 54 | 55 | @Override 56 | public Object getValueAt(int row, int column) { 57 | CaptchaEntity captcha = GUI.captcha.get(row); 58 | //String words = tfWords 59 | try { 60 | switch (column) { 61 | case 0: 62 | String strr = new String(captcha.getImage()); 63 | String words = BurpExtender.gui.getTfwords().getText(); 64 | 65 | if (!words.equals("") && strr.contains(words)){ 66 | byte[] byteImage = dataimgToimg(new String(captcha.getImage()) ,words); 67 | ImageIcon icon = byte2img(byteImage); 68 | table.setRowHeight(row, icon.getIconHeight() + 5);//让行高自动适应图片高 69 | return icon; 70 | }else { 71 | if ((strr.contains("data:image") ) || (strr.contains("data%3Aimage"))) { 72 | // if ((strr.contains("data:image") && !strr.startsWith("data:image")) || (strr.contains("data%3Aimage") && !strr.startsWith("data%3Aimage"))) { 73 | String pattern = "(data:image.*?)[\"|&]|(data%2Aimage.*?)[\"|&]"; 74 | Pattern r = Pattern.compile(pattern); 75 | Matcher m = r.matcher(strr); 76 | 77 | if (m.find()) { 78 | strr = m.group(0).replace("\"", "").replace("&", "").replace("Base64:", "").replace("base64:", ""); 79 | } 80 | if (!strr.contains("data:image")) { 81 | strr = "data:image/jpeg;base64," + strr; 82 | } 83 | System.out.println("*****"); 84 | System.out.println(strr); 85 | 86 | strr = strr.replace("\\r\\n","").replace("\\n",""); 87 | strr = strr.replace("\\",""); 88 | byte[] byteImage = DatatypeConverter.parseBase64Binary(strr.substring(strr.indexOf(",") + 1)); 89 | ImageIcon icon = byte2img(byteImage); 90 | table.setRowHeight(row, icon.getIconHeight() + 5);//让行高自动适应图片高 91 | return icon; 92 | } else { 93 | ImageIcon icon = byte2img(captcha.getImage()); 94 | table.setRowHeight(row, icon.getIconHeight() + 5);//让行高自动适应图片高 95 | return icon; 96 | } 97 | } 98 | 99 | case 1: 100 | return captcha.getResult(); 101 | default: 102 | return ""; 103 | } 104 | }catch (Exception e ){ 105 | 106 | } 107 | return ""; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/ui/CustomDlg.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.HttpService; 10 | import entity.Rule; 11 | import entity.TmplEntity; 12 | import javax.swing.*; 13 | import javax.swing.border.EmptyBorder; 14 | import java.awt.*; 15 | import java.awt.event.ActionEvent; 16 | import java.awt.event.ActionListener; 17 | 18 | /** 19 | * autor: c0ny1 20 | * date: 2019-10-19 21 | * description: 自定对话框类,用于删除和更新模版 22 | */ 23 | public class CustomDlg extends JDialog { 24 | public static final int TYPE_DEL_TPL = 0; 25 | public static final int TYPE_UPDATE_TPL = 1; 26 | int type; 27 | JPanel mainPanel; 28 | JLabel lbTitle; 29 | JComboBox cbmtpl; 30 | JButton btnOk; 31 | JButton btnCanel; 32 | CustomDlg dlg; 33 | 34 | 35 | CustomDlg(final int type){ 36 | this.type = type; 37 | dlg = this; 38 | initGUI(); 39 | InitEvent(); 40 | } 41 | 42 | 43 | private void initGUI(){ 44 | mainPanel = new JPanel(); 45 | mainPanel.setLayout(new GridBagLayout()); 46 | mainPanel.setBorder(new EmptyBorder(2,2,2,2)); 47 | lbTitle = new JLabel("模版"); 48 | cbmtpl = new JComboBox(); 49 | for(TmplEntity tpl:TmplEntity.tpls){ 50 | cbmtpl.addItem(tpl.getName()); 51 | } 52 | if(type == CustomDlg.TYPE_DEL_TPL){ 53 | this.setTitle("删除模版"); 54 | cbmtpl.addItem("所有模版"); 55 | btnOk = new JButton("删除"); 56 | }else{ 57 | this.setTitle("更新模版"); 58 | btnOk = new JButton("更新"); 59 | } 60 | btnCanel = new JButton("取消"); 61 | GBC gbc_lbtitle = new GBC(0,0,1,1).setFill(GBC.HORIZONTAL).setInsets(10,10,0,0); 62 | GBC gbc_cbmtpl = new GBC(1,0,99,1).setFill(GBC.HORIZONTAL).setInsets(10,10,0,10).setWeight(100,1);; 63 | GBC gbc_btnok = new GBC(1,1,1,1).setFill(GBC.HORIZONTAL).setInsets(10,10,0,0);; 64 | GBC gbc_btncanel = new GBC(2,1,1,1).setFill(GBC.HORIZONTAL).setInsets(10,10,0,10);; 65 | mainPanel.add(lbTitle,gbc_lbtitle); 66 | mainPanel.add(cbmtpl,gbc_cbmtpl); 67 | mainPanel.add(btnOk,gbc_btnok); 68 | mainPanel.add(btnCanel,gbc_btncanel); 69 | 70 | this.setModal(true); 71 | this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); 72 | this.setLayout(new BorderLayout(0,0)); 73 | this.add(mainPanel); 74 | this.pack(); 75 | Dimension screensize=Toolkit.getDefaultToolkit().getScreenSize(); 76 | this.setBounds(screensize.width/2-this.getWidth()/2,screensize.height/2-this.getHeight()/2,this.getWidth(),this.getHeight()); 77 | } 78 | 79 | 80 | private void InitEvent(){ 81 | btnOk.addActionListener(new ActionListener() { 82 | @Override 83 | public void actionPerformed(ActionEvent e) { 84 | int n = cbmtpl.getSelectedIndex(); 85 | String msg = null; 86 | if(type == CustomDlg.TYPE_DEL_TPL) { 87 | if(n == -1){ 88 | return; 89 | }else if(n == TmplEntity.tpls.size()){ 90 | TmplEntity.tpls.clear(); 91 | msg = "清空所有自定义模版成功!"; 92 | }else { 93 | TmplEntity entity = TmplEntity.tpls.get(n); 94 | msg = String.format("删除模版[%s]成功!",entity.getName()); 95 | TmplEntity.tpls.remove(n); 96 | } 97 | String tpldb = JSON.toJSONString(TmplEntity.tpls); 98 | BurpExtender.callbacks.saveExtensionSetting("tpldb", tpldb); 99 | JOptionPane.showMessageDialog(CustomDlg.this,msg, "captcha-killer提示", JOptionPane.INFORMATION_MESSAGE); 100 | dlg.setVisible(false); 101 | }else if(type == CustomDlg.TYPE_UPDATE_TPL){ 102 | TmplEntity entity = TmplEntity.tpls.get(n); 103 | TmplEntity tpl = new TmplEntity(); 104 | tpl.setName(entity.getName()); 105 | tpl.setReqpacke(BurpExtender.gui.getTaInterfaceTmplReq().getText()); 106 | tpl.setService(new HttpService(BurpExtender.gui.getInterfaceURL().getText())); 107 | tpl.setRule(new Rule(BurpExtender.gui.getCbmRuleType().getSelectedIndex(),BurpExtender.gui.getRegular().getText())); 108 | synchronized (TmplEntity.tpls){ 109 | TmplEntity.tpls.set(n,tpl); 110 | } 111 | String tpldb = JSON.toJSONString(TmplEntity.tpls); 112 | BurpExtender.callbacks.saveExtensionSetting("tpldb",tpldb); 113 | JOptionPane.showMessageDialog(CustomDlg.this, String.format("更新内容到模版[%s]成功!",entity.getName()), "captcha-killer提示", JOptionPane.INFORMATION_MESSAGE); 114 | dlg.setVisible(false); 115 | } 116 | } 117 | }); 118 | 119 | btnCanel.addActionListener(new ActionListener() { 120 | @Override 121 | public void actionPerformed(ActionEvent e) { 122 | dlg.setVisible(false); 123 | } 124 | }); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/matcher/impl/XmlMatcher.java: -------------------------------------------------------------------------------- 1 | package matcher.impl; 2 | 3 | import entity.MatchResult; 4 | import matcher.IMathcher; 5 | import org.dom4j.Attribute; 6 | import org.dom4j.Document; 7 | import org.dom4j.Element; 8 | import org.dom4j.io.SAXReader; 9 | import java.io.ByteArrayInputStream; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static utils.Util.getRspBody; 14 | 15 | public class XmlMatcher implements IMathcher { 16 | private List result = new ArrayList(); 17 | private List keywords = new ArrayList(); 18 | 19 | @Override 20 | public MatchResult match(String strResponse, String keyword) { 21 | MatchResult matchResult = new MatchResult(); 22 | String rsqData = new String(getRspBody(strResponse.getBytes())); 23 | try { 24 | final SAXReader sax = new SAXReader(); 25 | final Document document = sax.read(new ByteArrayInputStream(rsqData.getBytes())); 26 | final Element root = document.getRootElement();// 获取根节点 27 | this.searchXml(root,keyword); 28 | if(this.result.size() != 0){ 29 | String res = result.get(0); 30 | int start = rsqData.indexOf(res); //存在相同值的化,可能不准确,待优化 31 | int end = start + res.length(); 32 | matchResult.setResult(res); 33 | matchResult.setStart(start); 34 | matchResult.setEnd(end); 35 | }else{ 36 | matchResult.setResult("Alert: No match to"); 37 | } 38 | }catch (Exception e){ 39 | matchResult.setResult(String.format("Error: %s",e.getMessage())); 40 | } 41 | return matchResult; 42 | } 43 | 44 | @Override 45 | public String buildKeyword(String strResponse, String value) { 46 | String keyword = null; 47 | String rsqData = new String(getRspBody(strResponse.getBytes())); 48 | try { 49 | final SAXReader sax = new SAXReader(); 50 | final Document document = sax.read(new ByteArrayInputStream(rsqData.getBytes())); 51 | final Element root = document.getRootElement();// 获取根节点 52 | this.buildKeyword(root,value,null); 53 | if(this.keywords.size() != 0){ 54 | keyword = keywords.get(0); 55 | }else{ 56 | keyword = "build keyword fail!"; 57 | } 58 | }catch (Exception e){ 59 | keyword = String.format("build keyword error: %s",e.getMessage()); 60 | } 61 | return keyword; 62 | } 63 | 64 | 65 | protected void searchXml(final Element node,String keyword) { 66 | String[] fs = keyword.split("\\."); 67 | String current_keyword = fs[0]; 68 | String attr_name = null; 69 | String new_keyword = null; 70 | 71 | if(current_keyword.contains("#")){ 72 | attr_name = current_keyword.split("#")[1]; 73 | current_keyword = current_keyword.split("#")[0]; 74 | } 75 | 76 | if(fs.length != 1) { 77 | new_keyword = keyword.replace(current_keyword + ".", ""); 78 | }else{ 79 | new_keyword = current_keyword; 80 | } 81 | 82 | // 判断是匹配到keyword的头部关键字,否则继续使用原keyword继续递归搜索,直到匹配头部关键字 83 | if(node.getName().equals(current_keyword)){ 84 | if(attr_name != null){ 85 | final List listAttr = node.attributes(); 86 | for (final Attribute attr : listAttr) { 87 | if(attr.getName().equals(attr_name)){ 88 | result.add(attr.getValue()); 89 | } 90 | } 91 | // 属性不存在子树,所以它肯定是keyword末端,无需在继续递归了。 92 | return; 93 | } 94 | 95 | // 如果是keyword末尾关键字,则添加到结果,否则继续使用新的关键字递归搜索 96 | if(fs.length == 1) { 97 | result.add(node.getTextTrim()); 98 | }else{ 99 | final List listElement = node.elements(); 100 | for (final Element e : listElement) { 101 | searchXml(e,new_keyword); 102 | } 103 | } 104 | }else{ 105 | final List listElement = node.elements(); 106 | for (final Element e : listElement) { 107 | searchXml(e,keyword); 108 | } 109 | } 110 | } 111 | 112 | protected void buildKeyword(Element node, String val, String keyword){ 113 | if(keyword == null){ 114 | keyword = ""; 115 | } 116 | String kw = keyword; 117 | kw += keyword.equals("") ? node.getName() : "." + node.getName(); 118 | 119 | //当前节点内容是否是验证码值 120 | if(node.getTextTrim().equals(val)){ 121 | keywords.add(kw); 122 | } 123 | 124 | //当前节点所有属性值中是否有验证码值 125 | final List listAttr = node.attributes(); 126 | for (final Attribute attr : listAttr) { 127 | if(attr.getValue().equals(val)){ 128 | String attrkw = kw; 129 | attrkw += "#" + attr.getName(); 130 | keywords.add(attrkw); 131 | } 132 | } 133 | 134 | final List listElement = node.elements(); 135 | for(final Element e:listElement){ 136 | buildKeyword(e,val,kw); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/matcher/impl/JsonMatcher.java: -------------------------------------------------------------------------------- 1 | package matcher.impl; 2 | 3 | /** 4 | * Autor: c0ny1 5 | * Date: 2020-05-21 6 | * Description: API Json结果匹配模块 7 | */ 8 | 9 | import com.alibaba.fastjson.JSONObject; 10 | import entity.MatchResult; 11 | import matcher.IMathcher; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import static utils.Util.getRspBody; 16 | 17 | public class JsonMatcher implements IMathcher { 18 | private List result = new ArrayList(); 19 | private List keywords = new ArrayList(); 20 | 21 | protected void searchJson(String strJson,String fields){ 22 | String[] fs = fields.split("\\."); 23 | String current_field = fs[0]; 24 | String new_field = null; 25 | if(fs.length != 1) { 26 | new_field = fields.replace(current_field+".", ""); 27 | }else{ 28 | new_field = current_field; 29 | } 30 | 31 | if(strJson.indexOf('{') == 0){ 32 | JSONObject jsonObj = JSONObject.parseObject(strJson); 33 | for (Map.Entry entry : jsonObj.entrySet()) { 34 | String value = jsonObj.getString(entry.getKey()); 35 | if(entry.getKey().equals(current_field)){ 36 | if(fs.length == 1){ // 判断是否是末尾属性 37 | result.add(value); 38 | } 39 | searchJson(value,new_field); 40 | } else { 41 | //若未发现头部属性,继续全关键字搜索 42 | searchJson(value,fields); 43 | } 44 | } 45 | } else if(strJson.indexOf('[') == 0){ 46 | List list = JSONObject.parseArray(strJson, Object.class); 47 | for(Object obj:list){ 48 | if(obj.getClass().getName().equals("com.alibaba.fastjson.JSONObject")){ 49 | JSONObject jsonObj = (JSONObject)obj; 50 | for (Map.Entry entry : jsonObj.entrySet()) { 51 | String value = jsonObj.getString(entry.getKey()); 52 | if(entry.getKey().equals(current_field)){ 53 | if(fs.length == 1){ 54 | result.add(value); 55 | } 56 | searchJson(value,new_field); 57 | } else { 58 | searchJson(value,fields); 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * 遗留问题:当json存在多个属性有相同值的情况,生成的匹配规则不一定准确。临时解决方案是人工微调自动生成的规则。 68 | * @param strJson 69 | * @param val 70 | * @param keyword 71 | */ 72 | protected void makeKeyword(String strJson,String val,String keyword){ 73 | if(keyword == null){ 74 | keyword = ""; 75 | } 76 | if(strJson.indexOf('{') == 0){ 77 | JSONObject jsonObj = JSONObject.parseObject(strJson); 78 | for (Map.Entry entry : jsonObj.entrySet()) { 79 | String value = jsonObj.getString(entry.getKey()); 80 | String kw = keyword; 81 | kw += keyword.equals("") ? entry.getKey() : "." + entry.getKey(); 82 | if(value.equals(val)){ 83 | keywords.add(kw); 84 | } else { 85 | makeKeyword(value,val,kw); 86 | } 87 | } 88 | } else if(strJson.indexOf('[') == 0){ 89 | List list = JSONObject.parseArray(strJson, Object.class); 90 | for(Object obj:list){ 91 | if(obj.getClass().getName().equals("com.alibaba.fastjson.JSONObject")){ 92 | JSONObject jsonObj = (JSONObject)obj; 93 | for (Map.Entry 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 | [![Repo stars](https://img.shields.io/github/stars/f0ng/captcha-killer-modified)](https://github.com/f0ng/captcha-killer-modified/stargazers) 3 | [![Downloads total](https://img.shields.io/github/downloads/f0ng/captcha-killer-modified/total?label=Downloads)](https://github.com/f0ng/captcha-killer-modified/releases) 4 | [![Repo tags](https://img.shields.io/github/v/tag/f0ng/captcha-killer-modified?label=Latest)](https://github.com/f0ng/captcha-killer-modified/tags) 5 | [![Downloads latest total](https://img.shields.io/github/downloads/f0ng/captcha-killer-modified/latest/total?label=Downloads@latest)](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 | image 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 | ![brzif4oz b4w](https://github.com/user-attachments/assets/dd24bae0-d672-40f6-bd04-144671b67187) 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 | image 63 | 64 | 65 | image 66 | 67 | 68 | ## 插件优化的地方 69 | 1. 修改了原项目中`sun.misc.BASE64Encoder`报错的问题 70 | 71 | 2. 优化了验证码`data:image`识别问题 72 | 73 | 3. 添加了ddddocr验证码识别库 74 | 75 | 4. 增加自定义关键词获取验证码 76 | 77 | image 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 | image 98 | 99 | image 100 | 101 | 【2022-3-30】适配`data:image\/png`与base64中出现`\r\n`情况 102 | 103 | image 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 | image 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 | image 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 | image 142 | 143 | - 增加识别结果关键字显示,方便查看关键字是否与验证码对应 144 | image 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 | image 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 | image 178 | 179 | 2. 添加两个接口,添加reg2【识别无混淆的四则运算,项目默认模板】、reg3模板【识别混淆变形的若依四则运算验证码,默认模板不支持,需额外捐赠,捐赠具体费用可以联系作者】 180 | 181 | 182 | ![f](https://starchart.cc/f0ng/captcha-killer-modified.svg) 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("", 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>(.*?)", 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>1b<@BASE64>sd<@IMG_RAW>sds\n\rsdsdd"; 178 | // str = "<@URLENCODE><@1URLENCODE>+11111"; 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> 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 | image 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 | image 134 | 135 | 实现成功 136 | 137 | image 138 | 139 | # 7-验证码识别顺序错误(验证码识别正确,但是intruder登录接口的验证码识别错误)的调试方法 140 | 141 | 插件里获取验证码->识别->手动在repeater输入->显示验证码是否正确 142 | 143 | 如果验证码不正确,说明请求验证码的cookie等校验用户信息不一致,需要加上该cookie等校验用户信息的参数,如果为二次处理验证码的请求,需在请求验证码的代码中带上用户cookie等校验用户信息的参数。 144 | 145 | # 8-验证码中有token等校验参数返回,登录包中有校验token,如何进行设置爆破? 146 | 147 | 输入响应提取的正则,提取校验的关键字 148 | 149 | image 150 | 151 | 在intruder中增加校验的参数`@captcha-killer-modified@` 152 | 153 | image 154 | 155 | 在logger中可以看到实际的请求 156 | 157 | image 158 | 159 | 160 | # 9-forbideen响应、403响应解决 161 | 162 | 右键模板,选择模板库->ddddocr即可 163 | 164 | ~~由于后续脚本增加了Basic认证,可以自行添加Basic认证头即可~~ 165 | 166 | # 10-提取关键字错误 167 | 168 | 1. 可以更换关键字,或者直接输入类似`"iVBO`图片文件头后的base64编码如下: 169 | 170 | image 171 | 172 | 感谢微信群师傅@手挥五弦 提供的解决方法 173 | 174 | 2. 如果遇到base64的,以下案例可以使用`data:image/jpeg;base64`,其他情况类似 175 | 176 | image 177 | 178 | 179 | # 11-使用@captcha@替代验证码参数,导致爆破错误 180 | 181 | 增加intruder的爆破时间,验证码加载需要时间 182 | 183 | # 12-验证码中为base64编码,如何识别验证码 184 | 185 | json格式中,在关键字中填写关键字;其他格式可以参考[10-提取关键字错误](#10-提取关键字错误) 186 | 187 | image 188 | 189 | # 13-验证码响应包有明文验证码,如何配合工具使用? 190 | 191 | 当遇到了验证码在验证码响应包里出现,如下: 192 | 193 | image 194 | 195 | 可以直接通过工具提取验证码的值 196 | 197 | image 198 | 199 | 在burp模块中使用@captcha-killer-modified@替换验证码参数即可 200 | repeater举例,请求为 201 | 202 | image 203 | 204 | 实际请求为 205 | 206 | image 207 | 208 | # 14-intruder模块都是一个验证码 209 | 210 | 1. 查看intruder的attack type是否为`Pitchfork`,如果不是,那就选中这个模式,再测试,否则会出现验证码固定的问题 211 | 212 | image 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 | image 223 | 224 | 4. 点击识别无响应,可以将服务识别端`codereg.py`放置在VPS上,使用公网地址访问即可 225 | 226 | # 16-获取的都是一个验证码 227 | 228 | `0.24.1`临时更新插件按钮,开启按钮才会使用该插件 229 | 230 | image 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 | image 254 | 255 | # 21-混淆、变形的数字运算验证码 256 | 257 | 使用`reg3`模板【默认不支持该接口,需捐赠,可试用验证码接口,捐赠到一定额度可以有若依验证码的识别接口,以及进内部群,内部群成员捐赠有折扣,后面可能会分享其他类型的技术,不局限于captcha-killer-modified这个插件,后期会酌情涨价】 258 | 259 | image 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>",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",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",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>"); 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 | --------------------------------------------------------------------------------