前言:
前一篇文章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,使用社区版本即可。
-
(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
// 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 目录下。
- IHandleExecAction
①、可以接收 QuickExec 命令行中的命令;
②、编译出来的.dll文件放在 Scripts 目录下。
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
开始:
1、新建插件项目
因为我使用的是 fiddler 5.0,它的.net版本为 4.6.1,所以新建项目时,版本最好保持一致。
①、安装.net 4.6.1版本。
②、新建项目并选中对应.net版本。
2、为项目添加依赖
右键项目 – 添加 – 引用 ,分别添加 System.Windows.Forms 、 Fiddler.exe 和 Standard.dll 文件。
Fiddler.exe 提供了我们需要的 Inspectors2、IRequestInspector2,IResponseInspector2 等接口,Standard.dll 提供了 Fiddler 标准的 RequestTextViewer、ResponseViewer、JSONRequestViewer、JSONResponseViewer 等 UI 组件。
3、补充该插件的 fiddler 版本要求
编辑项目下的 AssemblyInfo.cs 文件,补充下面这句:
//运行该插件的fiddler最低版本要求
[assembly: Fiddler.RequiredVersion("5.0.0.0")]
4、编写fiddler选项卡控件
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查看刚才的控件是否写入成功。
4-6、指定生成事件后命令行(可选)
为了避免每次生成插件都要手动复制,可以进行下列操作。(前提:每次都要关掉fiddler才能去生成解决方案,避免原插件被占用,复制失败。)
操作:
右键项目 – 选择“属性” – 选择“生成事件” – 在“生成后事件命令行”文本域中输入:
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进行调试断点排查。
6、效果展示:
解密前,无法看出请求参数和相应参数的内容,如下如所示:
解密后,可以看出了:
评论区