侧边栏壁纸
博主头像
一朵云的博客博主等级

拥抱生活,向阳而生。

  • 累计撰写 67 篇文章
  • 累计创建 25 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

Fiddler 学习纪要(二)-- 接口解密插件开发

一朵云
2022-07-09 / 0 评论 / 4 点赞 / 9124 阅读 / 26441 字

前言:

  前一篇文章Fiddler 学习纪要(一)-- FiddlerScript 的使用 中已经简述了怎么在 Fiddler 中使用 FiddlerScript 编写一些简单功能,但实际工作使用中,如对加密接口解密等复杂场景就不适用了,所以我需要引出插件开发的概念,通过开发 Fiddler 插件来达到接口解密的目的。

  在学习fiddler插件开发的过程中,其实资料挺少的,这里我需要特别感谢一位博主分享他自己项目,我在这里贴上他csdn的博客 – Fiddler 插件开发:数据解密

  fiddler插件开发的大致流程:就是使用 c# 进行开发编写 .dll 文件(插件),再将 .dll 文件放在fiddler安装目录/Inspectors 下,重启 fiddler 即可生效。

  额外提一嘴,博主我对Java编程有一定基础,但对C#开发可以说就比小白强一点儿,这还得亏C#的语法与java相似,所以下面的C#上的操作会讲的比较啰嗦,怕自己回过头看不懂了,哈哈。闲话少说,让我们开始吧。



准备:

  • (1)、开发工具:Microsoft Visual Studio(简称:Visual Studio,千万别与前端编程工具 Visual Studio Code 搞混了)
    下载地址:https://visualstudio.microsoft.com,使用社区版本即可。
    image-1670486102659

  • (2)、抓包工具:Fiddler(我使用的fiddler版本为 v5.0.20202.18177 for .NET 4.6.1)

  • (3)、加解密算法(这个需要自己去调试哈,我提供的满足我自己,但不一定满足您)

  • (4)、了解Fiddler对外提供了什么接口fiddler官网插件开发介绍文档,其中包括了接口的使用介绍。

  • IFiddlerExtension,IAutoTamper,IAutoTamper2,IAutoTamper3
    ①、应用于全局(拦截用);
    ②、展示在下图中箭头所指的这行;
    ③、编译出来的.dll文件需放在 Scripts 目录下。
    ④、官网文档:https://docs.telerik.com/fiddler/extend-fiddler/interfaces
    image-1670577590126
 // IFiddlerExtension作用于fiddler启动时。
 public interface IFiddlerExtension
 {
   // Called when Fiddler Classic User Interface is fully available
   void OnLoad();
 
   // Called when Fiddler Classic is shutting down
   void OnBeforeUnload();
 }
 // IAutoTamper作用于接口请求前、请求后、响应前、响应后、以及返回fiddler的http错误
 public interface IAutoTamper : IFiddlerExtension
  {
    // Called before the user can edit a request using the Fiddler Classic Inspectors
    void AutoTamperRequestBefore(Session oSession);

    // Called after the user has had the chance to edit the request using the Fiddler Classic Inspectors, but before the request is sent
    void AutoTamperRequestAfter(Session oSession);

    // Called before the user can edit a response using the Fiddler Classic Inspectors, unless streaming.
    void AutoTamperResponseBefore(Session oSession);

    // Called after the user edited a response using the Fiddler Classic Inspectors.  Not called when streaming.
    void AutoTamperResponseAfter(Session oSession);

    // Called Fiddler returns a self-generated HTTP error (for instance DNS lookup failed, etc)
    void OnBeforeReturningError(Session oSession);
  }
// IAutoTamper2 作用于接口响应存在headers报头时调用,常用于查看响应报头
/// <summary>
/// Interface for AutoTamper extensions that want to "peek" at response headers
/// </summary>
public interface IAutoTamper2 : IAutoTamper
{
/// <summary>
/// Called when the response headers become available
/// </summary>
/// <param name="oSession">The Session object for which the response headers are available</param>
void OnPeekAtResponseHeaders(Session oSession);
}
// IAutoTamper3 作用于接口请求存在headers报头时调用,常用于查看请求报头
/// <summary>
/// Interface for AutoTamper extensions that want to "peek" at request headers
/// </summary>
public interface IAutoTamper3 : IAutoTamper2
{
/// <summary>
/// Called when the request headers become available
/// </summary>
/// <param name="oSession">The Session object for which the request headers are available</param>
void OnPeekAtRequestHeaders(Session oSession);
}
  • Inspector2,IRequestInspector2,IResponseInspector2
    ①、仅应用于单一请求(写插件用);
    ②、IRequestInspector2,IResponseInspector2 分别展示在下图箭头所指的两行;
    ③、编译出来的.dll文件放在 Inspectors 目录下。
    image-1670577977490
  • IHandleExecAction
    ①、可以接收 QuickExec 命令行中的命令;
    ②、编译出来的.dll文件放在 Scripts 目录下。
    image-1670578183522
public interface IHandleExecAction
{
  // return TRUE if handled. 
  bool OnExecAction(string sCommand); 
}
  • ISessionExporter,ISessionImporter
    ①、可以批量地对请求进行导入导出操作;
    ②、出现在右键菜单 Save-Selected Session 中和顶部菜单栏的 File-ImportSession 弹出的菜单中;
    ③、编译出来的.dll文件放在 ImportExport 目录下。
    ④、官网文档:https://docs.telerik.com/fiddler/extend-fiddler/importerexporterinterfaces

  注意:若在同一个.dll文件中引用需在不同目录下才能生效的接口,那么只有接口与目录对应上的,才会生效。

  • (5)、开启Fiddler的调试模式,将错误日志打印出来。
prefs set fiddler.debug.extensions.showerrors  True
prefs set fiddler.debug.extensions.verbose True

image-1670494637721

开始:

1、新建插件项目

因为我使用的是 fiddler 5.0,它的.net版本为 4.6.1,所以新建项目时,版本最好保持一致。

①、安装.net 4.6.1版本。

image-1674984716546

image-1674984879310

②、新建项目并选中对应.net版本。

image-1674984119513

image-1674985471955

2、为项目添加依赖

右键项目 – 添加 – 引用 ,分别添加 System.Windows.Forms 、 Fiddler.exe 和 Standard.dll 文件。

Fiddler.exe 提供了我们需要的 Inspectors2、IRequestInspector2,IResponseInspector2 等接口,Standard.dll 提供了 Fiddler 标准的 RequestTextViewer、ResponseViewer、JSONRequestViewer、JSONResponseViewer 等 UI 组件。

image-1674986601121

image-1675044916313

3、补充该插件的 fiddler 版本要求

编辑项目下的 AssemblyInfo.cs 文件,补充下面这句:

//运行该插件的fiddler最低版本要求
[assembly: Fiddler.RequiredVersion("5.0.0.0")]

image-1674987835988

4、编写fiddler选项卡控件

image-1674988331133

Inspectors 请求一栏的选项卡:

  • Decryption:解密后的请求参数
  • DecryptionJson:解密后、JSON格式的请求参数

Inspectors 响应一栏的选项卡:

  • Decryption:解密后的响应参数
  • DecryptionJson:解密后、JSON格式的响应参数

4-1、新建类 RequestDecryption.cs

用于编写Inspectors 请求一栏的“Decryption”,起到展示解密请求参数的作用。
aesTool.DoDecryption(mBody,AES_KEY);方法是我封装的解密方法,解密逻辑在AesTool.cs类中。(代码中我先注释了,后面用到时再解开。)

using Fiddler;
using System.Windows.Forms;
using Standard;
using System;

namespace MyFirstFiddlerPlug
{
    public class RequestDecryption : Inspector2, IRequestInspector2, IBaseInspector2
    {

        private HTTPRequestHeaders mRequestHeaders;
        private byte[] mBody;
        private bool mBDirty;
        private bool mBReadOnly;
        private RequestTextViewer mRequestTextViewer;
        static private String AES_KEY = "qwertyuiopasdfgh";

        public RequestDecryption()
        {
            mRequestTextViewer = new RequestTextViewer();
        }

        public HTTPRequestHeaders headers {
            get {
                return mRequestHeaders;
            }
            set
            {
                mRequestHeaders = value;
            }
        }
        public byte[] body { 
            get
            {
                return mBody;
            } 
            set
            {
                mBody = value;
				/*
                //先清空原来区域的内容
                mRequestTextViewer.Clear();

                //新增接口解密逻辑
                AesTool aesTool = new AesTool();
                byte[] decodedBody = aesTool.DoDecryption(mBody,AES_KEY);
                if (decodedBody != null)
                {
                    //将解密后的byte[]数据返回给fiddler
                    mRequestTextViewer.body = decodedBody;
                }
                else
                {
                    Clear();
                    mRequestTextViewer.body = value;
                }
				*/
            }
        }

        public bool bDirty{
            get{
                return mBDirty;
            }
        }

        public bool bReadOnly
        {
            get
            {
                return mBReadOnly;
            }
            set
            {
                mBReadOnly = value;
            }
        }

        public override void AddToTab(TabPage o)
        {
            mRequestTextViewer.AddToTab(o);
            o.Text = "Decryption";
        }

        public void Clear()
        {
            mBody = null;
            mRequestTextViewer.Clear();
        }

        public override int GetOrder()
        {
            return 200;
        }
    }
}

4-2、新增类 RequestDecryptionJSON.cs

用于编写Inspectors 请求一栏的“DecryptionJSON”,也起到展示解密请求参数的作用,区别在于该控件使用了Fiddler封装的Json格式化视图 JSONRequestViewer 。

using Fiddler;
using Standard;
using System;
using System.Windows.Forms;

namespace MyFirstFiddlerPlug
{
    public class RequestDecryptionJSON2 : Inspector2, IRequestInspector2, IBaseInspector2
    {
        private HTTPRequestHeaders mRequestHeaders;
        private byte[] mBody;
        private bool mBDirty;
        private bool mBReadOnly;
        private JSONRequestViewer mRequestTextViewer;
        static private String AES_KEY = "qwertyuiopasdfgh";

        public RequestDecryptionJSON2()
        {
            mRequestTextViewer = new JSONRequestViewer();
        }

        public HTTPRequestHeaders headers
        {
            get
            {
                return mRequestHeaders;
            }
            set
            {
                mRequestHeaders = value;
            }
        }
        public byte[] body
        {
            get
            {
                return mBody;
            }
            set
            {
                mBody = value;
				/*
                //先清空原来区域的内容
                mRequestTextViewer.Clear();
                //新增接口解密逻辑
                AesTool aesTool = new AesTool();
                byte[] decodedBody = aesTool.DoDecryption(mBody, AES_KEY);
                if (decodedBody != null)
                {
                    //将解密后的byte[]数据返回给fiddler
                    mRequestTextViewer.body = decodedBody;
                }
                else
                {
                    Clear();
                    mRequestTextViewer.body = value;
                }
				*/
            }
        }

        public bool bDirty
        {
            get
            {
                return mBDirty;
            }
        }

        public bool bReadOnly
        {
            get
            {
                return mBReadOnly;
            }
            set
            {
                mBReadOnly = value;
            }
        }

        public override void AddToTab(TabPage o)
        {
            mRequestTextViewer.AddToTab(o);
            o.Text = "DecryptionJSON";
        }

        public void Clear()
        {
            mBody = null;
            mRequestTextViewer.Clear();
        }

        public override int GetOrder()
        {
            return 201;
        }
    }
}

4-3、新增类 ResponseDecryption

用于编写Inspectors 响应一栏的“Decryption”,起到展示解密响应参数的作用。

using Fiddler;
using System.Windows.Forms;
using Standard;
using System;

namespace MyFirstFiddlerPlug
{
    public class ResponseDecryption : Inspector2, IResponseInspector2, IBaseInspector2
    {

        private HTTPResponseHeaders mResponseHeaders;
        private byte[] mBody;
        private bool mBDirty;
        private bool mBReadOnly;
        private ResponseTextViewer mResponseTextViewer;
        static private String AES_KEY = "qwertyuiopasdfgh";

        public ResponseDecryption()
        {
            mResponseTextViewer = new ResponseTextViewer();
        }

        public HTTPResponseHeaders headers {
            get {
                return mResponseHeaders;
            }
            set
            {
                mResponseHeaders = value;
            }
        }

        public byte[] body { 
            get
            {
                return mBody;
            } 
            set
            {
                mBody = value;
				/*
                //先清空原来区域的内容
                mResponseTextViewer.Clear();
                //新增接口解密逻辑
                AesTool aesTool = new AesTool();
                byte[] decodedBody = aesTool.DoDecryption(mBody, AES_KEY);
                if (decodedBody != null)
                {
                    //将解密后的byte[]数据返回给fiddler
                    mResponseTextViewer.body = decodedBody;
                }
                else
                {
                    Clear();
                    mResponseTextViewer.body = value;
                }
				*/
            } 
        }

        public bool bDirty{
            get{
                return mBDirty;
            }
        }

        public bool bReadOnly { 
            get
            {
                return mBReadOnly;
            }
            set
            {
                mBReadOnly = value;
            }
        }

        public override void AddToTab(TabPage o)
        {
            mResponseTextViewer.AddToTab(o);
            o.Text = "Decryption";
        }

        public void Clear()
        {
            mBody = null;
            mResponseTextViewer.Clear();
        }

        public override int GetOrder()
        {
            return 200;
        }
    }
}

4-4、新增类 ResponseDecryptionJSON

用于编写Inspectors 响应一栏的“DecryptionJSON”,也起到展示解密响应参数的作用,区别在于该控件使用了Fiddler封装的Json格式化视图 JSONRequestViewer 。

using Fiddler;
using System.Windows.Forms;
using Standard;
using System;

namespace MyFirstFiddlerPlug
{
    public class ResponseDecryptionJSON : Inspector2, IResponseInspector2, IBaseInspector2
    {

        private HTTPResponseHeaders mResponseHeaders;
        private byte[] mBody;
        private bool mBDirty;
        private bool mBReadOnly;
        private JSONResponseViewer mResponseTextViewer;
        static private String AES_KEY = "qwertyuiopasdfgh";

        public ResponseDecryptionJSON()
        {
            mResponseTextViewer = new JSONResponseViewer();
        }

        public HTTPResponseHeaders headers 
        {
            get 
            {
                return mResponseHeaders;
            }
            set
            {
                mResponseHeaders = value;
            }
        }

        public byte[] body { 
            get
            {
                return mBody;
            } 
            set
            {
                mBody = value;
				/*
                //先清空原来区域的内容
                mResponseTextViewer.Clear();
                //新增接口解密逻辑
                AesTool aesTool = new AesTool();
                byte[] decodedBody = aesTool.DoDecryption(mBody, AES_KEY);
                if (decodedBody != null)
                {
                    //将解密后的byte[]数据返回给fiddler
                    mResponseTextViewer.body = decodedBody;
                }
                else
                {
                    Clear();
                    mResponseTextViewer.body = value;
                }
                */
            } 
        }

        public bool bDirty{
            get
            {
                return mBDirty;
            }
        }

        public bool bReadOnly 
        { 
            get
            {
                return mBReadOnly;
            }
            set
            {
                mBReadOnly = value;
            }
        }

        public override void AddToTab(TabPage o)
        {
            mResponseTextViewer.AddToTab(o);
            o.Text = "DecryptionJSON";
        }

        public void Clear()
        {
            mBody = null;
            mResponseTextViewer.Clear();
        }

        public override int GetOrder()
        {
            return 201;
        }
    }
}

4-5、生成 .dll 插件并使用

  • ①、点击 开发工具 Visual Studio 顶部的菜单“生成” – “生成解决方案”;
  • ②、右键项目 – 点击“在文件资源管理器中打开文件夹” ;
  • ③、找到目录 bin\Debug\项目名.dll 文件;
  • ④、复制该文件到fiddler安装目录下的 Inspectors 目录下;
  • ⑤、重启fiddler查看刚才的控件是否写入成功。

image-1675066594082

4-6、指定生成事件后命令行(可选)

为了避免每次生成插件都要手动复制,可以进行下列操作。(前提:每次都要关掉fiddler才能去生成解决方案,避免原插件被占用,复制失败。)

image-1675067137951

操作:
右键项目 – 选择“属性” – 选择“生成事件” – 在“生成后事件命令行”文本域中输入:

copy "$(TargetPath)" "D:\Program Files (x86)\Fiddler\Inspectors\$(TargetFilename)"

$(TargetPath):表示当前.dll文件的绝对路径及文件全称。

$(TargetFilename):表示生成.dll文件的文件名。

copy “xxx” “xxx”:表示将前者双引号路径下的文件拷贝到后者双引号路径下。

5、为控件添加解密功能

由于我抓包的JAVA项目,接口是通过AES加密法加密的(通过特定key,进行对称性加解密)。AES/ECB/PKCS5Padding,且未使用偏移量 IV。

①、新增解密工具类 AesTool.cs

using System.Text;
using System;
using Fiddler;
using Standard;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Tab;

namespace MyFirstFiddlerPlug
{
    public class AesTool
    {
        /// <summary>
        ///  AES ECB 加密
        /// </summary>
        /// <param name="str">需加密的字符串</param>
        /// <param name="key">密钥key</param>
        /// <returns></returns>
        public static string AesEncrypt(string str, string key)
        {
            if (string.IsNullOrEmpty(str)) return null;
            Byte[] toEncryptArray = Encoding.UTF8.GetBytes(str);

            System.Security.Cryptography.RijndaelManaged rm = new System.Security.Cryptography.RijndaelManaged
            {
                Key = Encoding.UTF8.GetBytes(key),
                Mode = System.Security.Cryptography.CipherMode.ECB,
                Padding = System.Security.Cryptography.PaddingMode.PKCS7
            };

            System.Security.Cryptography.ICryptoTransform cTransform = rm.CreateEncryptor();
            Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        /// <summary>
        ///  AES ECB 解密
        /// </summary>
        /// <param name="str">需解密的字符串</param>
        /// <param name="key">密钥key</param>
        /// <returns></returns>
        public static string AesDecrypt(string str, string key)
        {
            if (string.IsNullOrEmpty(str)) return null;

            try
            {
                Byte[] toEncryptArray = Convert.FromBase64String(str);

                System.Security.Cryptography.RijndaelManaged rm = new System.Security.Cryptography.RijndaelManaged
                {
                    Key = Encoding.UTF8.GetBytes(key),
                    Mode = System.Security.Cryptography.CipherMode.ECB,
                    Padding = System.Security.Cryptography.PaddingMode.PKCS7
                };

                System.Security.Cryptography.ICryptoTransform cTransform = rm.CreateDecryptor();
                Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

                return Encoding.UTF8.GetString(resultArray);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public byte[] DoDecryption(byte[] mBody,String AES_KEY)
        {
            if(mBody.Length==0 || mBody == null)
            {
                return null;
            }
            
            String strBody = System.Text.UTF8Encoding.UTF8.GetString(mBody);

            String startChar = strBody.Substring(0, 1);
            String endChar = strBody.Substring(strBody.Length - 1, 1);

            //如果strBody开始不等于“{”,结束不等于“}”,则认为该json串被加密了。(按我司习惯,接口一般不会直接返回字符串)
            if (!startChar.Equals("{") && !endChar.Equals("}"))
            {
                // 将 byte[] 转成Base64字符串
                String rawBody = Convert.ToBase64String(mBody);
                String showBody = "";

                try
                {
                    showBody = AesTool.AesDecrypt(rawBody, AES_KEY);
                    

                    byte[] decodeBody = System.Text.Encoding.UTF8.GetBytes(showBody);
                    return decodeBody;
                }
                catch (Exception ex)
                {
                    FiddlerApplication.Log.LogString("解密失败," + ex.Message);
                    return null;
                }
            }else
            {
                //不是json格式就直接返回
                return mBody;
            }
        }
    }
}

②、调用AesTool.cs类的解密方法

  将 RequestDecryption、ResponseDecryption、RequestDecryptionJSON、ResponseDecryptionJSON 中的body方法中注释部分解开,直接调用 AesTool.DoDecryption() 解密方法。

③、如果出现异常,该如何调试?

  当程序出现Exception时,可以通过工具的调试 – 附加到进程 – 选中fiddler进行调试断点排查。

image-1675238313725

6、效果展示:

解密前,无法看出请求参数和相应参数的内容,如下如所示:

image-1675310383337

解密后,可以看出了:

image-1675310448403

image-1675310470484

4

评论区