编写BurpSuite请求处理(加密、解密)插件

Intro

一直以来抓包都是用Fiddler居多,Python、BurpSuite辅助。以前用不惯BurpSuite的原因主要是界面过于繁杂,Request、Reponse窗口不如Fiddler直观,以及十分不友好的Intercept功能。所以,在之前的工作中,如果遇到协议加密,我的处理方式如下:

  1. 优先考虑是否能通过一些手段Bypass、去掉加密;
  2. 编写Fiddler解密插件,有时需要Xposed、Frida、Substrate、Flex的配合;
  3. 使用Python编写HTTP Server、Proxy,或类似Brida之类的方式,作为Fiddler、BurpSuite的上游代理;
  4. 放弃或安排给其他同事。

所以之前没有写过BurpSuite插件和脚本。这几个月经历多次实战渗透之后,逐渐熟悉了BurpSuite的界面和操作,于是在最近一个需要做协议解密的App分析中,尝试使用Java编写BurpSuite的插件,用于对Request、Response的加密和解密。BurpSuite的插件并没有Fiddler那么直观,搜了一下内部仓库竟然没有相关插件供参考,大惊。进一步抽样Review了报告和近期的入职培训,才意识到自从MonkeyLord编写了XServer并在内部大力推广之后,基本没人再分析加密过程、编写插件。另外一个同事lyxhh也开源过类似的辅助工具,参考这里

XServer大概的原理是,使用Xposed在进程中注入模块,同时开启一个HTTP Server,HTTP Server提供反射机制,所以BurpSuite可以将App的任意函数作为RPC进行调用。在此基础上进一步完善,实现重放、response修改等功能。

插件框架

个人来说,还是喜欢编写Fiddler的插件,尽可能选取通杀的方案,可以避免之后的重复工作。Fiddler的插件接口单一且直观,一般如下:

public void AutoTamperRequestBefore(Session session)
{
  // requestIn
}

public void AutoTamperRequestAfter(Session session)
{
  // requestOut
}

public void AutoTamperResponseBefore(Session session)
{
  // responseIn
}

public void AutoTamperResponseAfter(Session session)
{
  // responseOut
}

上述接口很好的描述了一个代理工具如何处理单个HTTP请求:

  1. 获取客户端发送的request,即requestIn
  2. 代理工具处理request,并将request发送给服务端,即requestOut
  3. 服务端处理完毕,代理工具收到response,即responseIn
  4. 代理工具处理完毕,将response返回到客户端,即responseOut

而BurpSuite插件如果要使用上述功能的话,需要同时实现IHttpListenerIProxyListener接口。IProxyListener处理代理数据,接收客户端发送的请求,并且将响应返回给客户端,IHttpListener负责将请求发送给服务器、接收响应。所以,加密、解密插件的框架代码如下:

public class BurpExtender implements IBurpExtender, IHttpListener, IProxyListener {
  private IExtensionHelpers burpHelpers = null;
  private PrintWriter debugLogger = null;
  private PrintWriter errorLogger = null;

  @Override
  public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
    burpHelpers = callbacks.getHelpers();
    debugLogger = new PrintWriter(callbacks.getStdout(), true);
    errorLogger = new PrintWriter(callbacks.getStderr(), true);

    callbacks.setExtensionName("Cryptor");
    callbacks.registerHttpListener(this);  // 注册HttpListener
    callbacks.registerProxyListener(this); // 注册ProxyListener
  }

  @Override
  public void processHttpMessage(
      int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
    if (messageIsRequest) {
      requestOut(messageInfo);
    } else {
      responseIn(messageInfo);
    }
  }

  @Override
  public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessage message) {
    if (messageIsRequest) {
      requestIn(message);
    } else {
      responseOut(message);
    }
  }

  private void requestIn(IInterceptedProxyMessage message) {
    // 
  }

  private void requestOut(IHttpRequestResponse messageInfo) {
    //
  }

  private void responseIn(IHttpRequestResponse messageInfo) {
    //
  }

  private void responseOut(IInterceptedProxyMessage message) {
    //
  }
}

清楚流程之后,加密、解密的步骤就很好处理,其目的是为了让BurpSuite展示明文,方便分析和测试:

  1. requestIn阶段,收到客户端发送的加密request,进行解密并替换requestBody,使得BurpSuite中显示明文request;
  2. requestOut阶段,即将发送request到服务端,读取明文的request,重新进行加密(包括签名、编码、更新时间戳等),使得服务端正常解析;
  3. responseIn阶段,收到加密response,进行解密并替换responseBody,使得BurpSuite中显示明文response;
  4. responseOut阶段,即将发送response到客户端,读取明文的response,重新进行加密,使得客户端正常解析。

对比Fiddler,BurpSuite的插件有如下优点:

  • 显示原始、修改后的request、response;
  • 动态加载、卸载Jar插件;
  • 独立日志窗口;
  • 实现了IHttpListener接口的插件,在BurpSuite运行Scanner、Spider、Instruder、Repeater均会调用插件,所以抓取到请求后,可以脱离客户端进行测试;

乱码解决

按照上述步骤完成加密、解密插件后,还会遇到两个问题:Interceptor的Reponse断点数据加密,以及解密后的数据显示乱码。乱码问题比较简单,以使用较为广泛的UTF-8编码为例,参考如下方法解决:

  • 调整字体;
  • 启动脚本中,增加jvm参数,设置默认语言及编码:-Duser.language=en -Dfile.encoding=UTF8
  • 需要展示的数据,如解密后的数据,使用burpsuite的提供的函数stringToBytes完成;
  • 对数据重新加密的时候,尽量直接操作bytes。

而Reponse断点则没有太好的方案,分析发现,插件的responseOut回调会在Interceptor的断点之前被调用,返回给客户端的数据也就被加密了,在窗口中看到的也是加密后的数据。好在改Response并不是特别重要,即便是不解密,也可以通过Xposed、甚至手工构造请求的方式解决。

参考

发表评论