目标:制作一个具备类似Fiddler、Burpsuit、Wireshark的https协议代理抓包功能,但是集成到自己的app内,这样无需修改系统代理设置,使用QWebengineview通过自建的代理服务器,即可实现https包的实时监测、注入等自定义功能。
实现:
一、https代理服务器
1.使用QSslSocket类收发https包;使用多线程,提升代理服务器的性能。
ProxyClientThread.h
cpp
#ifndef PROXYCLIENTTHREAD_H
#define PROXYCLIENTTHREAD_H
#include <QObject>
#include <QTcpSocket>
#include <QNetworkProxy>
#include <QThread>
#include <QDebug>
#include <QSslSocket>
#include <QSslConfiguration>
#include <QFile>
#include <QSslKey>
#include <QByteArray>
#include <QtZlib/zlib.h>
#include <QRegularExpression>
struct HTTPHDR{
QString host;
quint16 port;
bool newReq;
};
struct HTTPHDR2{
quint8 CMD;
QString CMDi;
QString HOST;
quint16 PORT;
bool status;
};
enum ClientConnectionState {
InitialRequest,
TlsHandshake,
DataTransfer
};
class ProxyClientThread : public QThread
{
Q_OBJECT
public:
ProxyClientThread(qintptr sockDesc, QObject *parent = 0);
~ProxyClientThread();
void run();
QByteArray LastResquest;
private:
QSslSocket clientSocket;
QSslSocket serverSocket;
QSslConfiguration sslConfig;
int m_client_state=0;
bool m_serverSocketConnected=false;
QByteArray cNewReqData;
QByteArray clientSockData;
QByteArray serverSockData;
void processClient();
HTTPHDR2 processHeader(QByteArray hdr);
bool loadCertificateAndKey();
//HTTPHDR getHostInfo(QByteArray httpHeaderPartial);
int pid;
bool targetFound=false;//是否找到要注入的目标
bool istargetHeader=true;//是否头部
bool finishInject=false;//已完成注入
QString cachedStr="";//缓存的内容;
private slots:
void clientSockReadyRead();
void serverSockConnected();
void clientSockDisconnected();
void serverSockDisconnected();
void serverSockReadyRead();
void clientTlsHandOk();
void serverSockError(QAbstractSocket::SocketError errorMsg);
void clientSockError(QAbstractSocket::SocketError errorMsg);
signals:
void complete();
};
#endif // PROXYCLIENTTHREAD_H
ProxyClientThread.cpp部分代码
cpp
#include "proxyclientthread.h"
//#define DEBUG 1
QString keyFile="9291.0d30ab5b.js";
QString keyStr="}else e=await V.ImSdk.sendMessage({text:r,textExtra:a,referenceMessage:eQ";
QString injectStr=",window.MySendMsg=e";
ProxyClientThread::ProxyClientThread(qintptr sockDesc, QObject *parent) : QThread(parent)
{
this->pid = sockDesc;
//服务端连接
connect (&this->serverSocket,SIGNAL(disconnected()),this,SLOT(serverSockDisconnected()));
connect (&this->serverSocket,SIGNAL(readyRead()),this,SLOT(serverSockReadyRead()));
connect (&this->serverSocket,SIGNAL(errorOccurred(QAbstractSocket::SocketError)),this,SLOT(serverSockError(QAbstractSocket::SocketError)));
connect (&this->serverSocket,SIGNAL(connected()),this,SLOT(serverSockConnected()));
this->serverSocket.setProxy(QNetworkProxy::NoProxy);
//客户端
m_client_state=InitialRequest;//客户端状态为初始化状态
this->clientSocket.setSocketDescriptor(sockDesc);
connect(&this->clientSocket, SIGNAL(disconnected()),this,SLOT(clientSockDisconnected()));
connect(&this->clientSocket, SIGNAL(readyRead()),this,SLOT(clientSockReadyRead()),Qt::DirectConnection);
connect(&this->clientSocket, SIGNAL(encrypted()), this, SLOT(clientTlsHandOk()));
connect(&this->clientSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(clientSockError(QAbstractSocket::SocketError)));
}
void ProxyClientThread::clientSockReadyRead()
{
this->processClient();
return;
}
void ProxyClientThread::processClient()
{
HTTPHDR2 pHead;
//recieved incoming client packet
this->clientSockData.clear();
this->clientSockData = this->clientSocket.readAll();
#ifdef DEBUG
qDebug()<<this->pid<<"**收到客户端数据"<<this->clientSockData;
#endif
//查找匹配文件请求
QString reqStr=QString(clientSockData);
if(reqStr.contains("GET") and reqStr.contains(keyFile)){
targetFound=true;
qDebug()<<"找到要注入的文件--------------"<<reqStr;
//修改请求头,不压缩
reqStr.replace("Accept-Encoding: gzip, deflate, br","Accept-Encoding: identity");
clientSockData=reqStr.toLocal8Bit();
}
if (this->serverSocket.state() == QAbstractSocket::ConnectedState){
#ifdef DEBUG
qDebug() <<this->pid<<": 4.2.向服务器发送请求:";//<<this->clientSockData;
#endif
serverSocket.write(clientSockData);
return;
}
//处理 header
pHead = this->processHeader(clientSockData.mid(0,100));
if (!pHead.status){
this->LastResquest = this->clientSockData;
return;
}
//process SSL/TLS Connection;
if (pHead.CMD == 3){ //CONNECT类型
if (serverSocket.state() == QAbstractSocket::UnconnectedState){
#ifdef DEBUG
qDebug() <<this->pid<<": 1.收到客户发起CONNECT连接" << pHead.CMD << pHead.HOST << pHead.PORT;
#endif
m_client_state=TlsHandshake;//握手状态
serverSocket.connectToHostEncrypted(pHead.HOST, pHead.PORT);
return;
}
}
if (serverSocket.state() == QAbstractSocket::UnconnectedState){
#ifdef DEBUG
qDebug()<<"***连接服务器";
#endif
LastResquest=clientSockData;
serverSocket.connectToHostEncrypted(pHead.HOST,pHead.PORT);
return;
}
return;
}
void ProxyClientThread::clientTlsHandOk(){
//clientSockData = clientSocket.readAll();//读取客户端请求
#ifdef DEBUG
qDebug()<<this->pid<<": 4.<-- 已经和客户端ssl握手成功:"<<LastResquest;
#endif
serverSocket.write(LastResquest);
}
...
}
/*
* 加载自签名证书
*/
bool ProxyClientThread::loadCertificateAndKey() {
QFile certFile(":/certs/server.crt");
if (!certFile.open(QIODevice::ReadOnly)) {
qWarning() << "Certificate file not found!";
return false;
}
QSslCertificate cert(&certFile);
QFile keyFile(":/certs/server.key");
if (!keyFile.open(QIODevice::ReadOnly)) {
qWarning() << "Private key file not found!";
return false;
}
QSslKey key(&keyFile, QSsl::Rsa);
sslConfig.setLocalCertificate(cert);
sslConfig.setPrivateKey(key);
sslConfig.setProtocol(QSsl::TlsV1_2);
return true;
}
3.proxyserver.h
cpp
#ifndef PROXYSERVER_H
#define PROXYSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QTcpServer>
//#include <proxyclient.h>
#include <proxyclientthread.h>
class proxyServer : public QTcpServer {
Q_OBJECT
public:
explicit proxyServer(QObject* parent = nullptr) : QTcpServer(parent) {}
protected:
void incomingConnection(qintptr socketDescriptor) override {
// 创建子线程并传递 socket 描述符
ProxyClientThread* workerThread = new ProxyClientThread(socketDescriptor, this);
// 启动子线程
workerThread->run();
}
};
#endif // PROXYSERVER_H
代码的逻辑其实不难,按照代理服务器的连接过程补全相关代码就可以了。
二、QWebengineView部分
使用代理服务连接,该设置仅在app内有效,不影响其他应用。
设置QWebengineView的page忽略证书错误(因为是自签名证书),不处理的话无法访问https页面。
cpp
// 配置 QWebEngineView 使用代理
QNetworkProxy proxy(QNetworkProxy::HttpProxy, "127.0.0.1", 8787);
QNetworkProxy::setApplicationProxy(proxy);
//忽略证书错误
connect(webPage,SIGNAL(certificateError(QWebEngineCertificateError)),this,SLOT(on_certerror(QWebEngineCertificateError)));
void xxxx::on_certerror(QWebEngineCertificateError certerror){
auto mutableError = const_cast<QWebEngineCertificateError&>(certerror);
mutableError.acceptCertificate();
qDebug()<<"忽略证书错误。";
if(certerror.type()==QWebEngineCertificateError::CertificateAuthorityInvalid)
{
auto error=const_cast<QWebEngineCertificateError&>(certerror);
qDebug()<<"忽略证书错误。";
error.acceptCertificate();
}
}
经过验证,这个方案可行,可以在代理服务器端修改客户端发起的请求,也可以修改服务器端返回的任何数据(已解密过的)后再返回给客户端,但是前提是要做好对应的处理工作,比如Content-length记得要修改。