前言 有一个windows客户端的渗透要做,该客户端是一个视频会议系统。测试过程中发现原来该客户端只是一个http套了一个QT5的UI。但数据交互过程中,请求包和响应包全是加密的字节流,解密字节流后,发现请求包还有签名signature需要绕过,因此记录一下自己对windows客户端的测试和分析过程。
开始工作 抓包 最开始用系统代理发现流量没有转发到burpsuite,然后就换到了proxifer,这样还能指定进程去抓包。
先设置一个代理服务器,代理地址是127.0.0.1,代理端口是8080(burpsuite的默认端口),代理类型是https,如下图所示:
然后再新建一个代理规则
应用程序这里选择可能会发起请求的exe、action这里选择刚刚添加的代理服务器。
我这里是把安装目录下的exe都放进来了。
随便输入一个账号密码,流量到burpsuite后,可以看见请求和响应全是加密的字节流。
不过单看请求url,就可以判断出该系统框架为struts2。
加解密分析 查找接口找算法 先把主程序直接丢到ida进行反编译。
通过快捷键shift+f12查找字符串login.action
双击跟入,按快捷键X查找交叉引用
继续跟进,并按下f5,得到伪代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 char __thiscall sub_5BAE40 (int this , char a2, char a3, char a4, char a5, char a6, int a7, int *a8, int a9, int a10) { ... v147 = 4 ; sub_410D20(v137, &unk_82DCD4, "bussIntfAction!login.action" ); LOBYTE(v147) = 5 ; sub_5B8600(v135); LOBYTE(v147) = 6 ; QJsonObject::QJsonObject((QJsonObject *)v151); ... QJsonValue::QJsonValue((QJsonValue *)v154, (const struct QString *)&a2); v162 = v158("userCode" , 8 ); QJsonObject::insert(v151, v142, &v162, v154); QString::~QString((QString *)&v162); ... QJsonValue::QJsonValue((QJsonValue *)v154, (const struct QJsonObject *)v151); v162 = v11("data" , 4 ); QJsonObject::insert(v135, v142, &v162, v154); QString::~QString((QString *)&v162); ... QJsonDocument::QJsonDocument((QJsonDocument *)v143); QJsonDocument::setObject((QJsonDocument *)v143, (const struct QJsonObject *)v135); QJsonDocument::toJson(v143, v138, 1 ); QString::QString((QString *)v144, (const struct QByteArray *)v138); ... sub_54A9A0(0 ); LOBYTE(this ) = sub_54B040(v123, v124, v125, (char )v126); ... v16 = QMessageLogger::QMessageLogger((QMessageLogger *)v133, 0 , 0 , 0 ); v17 = QMessageLogger::debug(v16); v18 = QDebug::operator <<(v17, "login send data" , v144); QDebug::operator <<(v18); ... v19 = QMessageLogger::QMessageLogger((QMessageLogger *)v133, 0 , 0 , 0 ); v20 = QMessageLogger::debug(v19); v22 = QDebug::operator <<(v20, "login receive data" , v21); ... if ( !(_BYTE)this || (LOBYTE(v147) = 32 , v157 = qstrcmp, v26 = qstrcmp(v13, Directory), LOBYTE(v147) = 29 , !v26) ) { QByteArray::operator =(a9, "0" ); goto LABEL_20; } ... QJsonDocument::fromJson(v149, v13, v131); if ( v132 ) { QByteArray::operator =(a9, "0" ); QJsonDocument::~QJsonDocument((QJsonDocument *)v149); goto LABEL_20; } ... v162 = v158("resultCode" , 10 ); v28 = QJsonObject::take(v136, v154, &v162); v29 = (QVariant *)QJsonValue::toVariant(v28, v141); v153 = (const char *)QVariant::toInt(v29, 0 ); ... if ( v153 ) { ... goto LABEL_20; } ... v156 = ((int (__cdecl *)(const char *, int ))v27)("userInfo" , 8 ); v140 = ((int (__cdecl *)(const char *, int ))v27)("data" , 4 ); ... if ( QJsonObject::isEmpty((QJsonObject *)v160) ) { QByteArray::operator =(a9, "0" ); v25((QJsonObject *)v160); QString::~QString((QString *)v152); v25((QJsonObject *)v136); QJsonDocument::~QJsonDocument((QJsonDocument *)v149); LABEL_20: v103 = 0 ; goto LABEL_21; } ... if ( v159((QString *)&a5, 0 , 10 ) == 3 || QByteArray::toInt((QByteArray *)(v99 + 112 ), 0 , 10 ) ) { v103 = 1 ; } else { ... v103 = 0 ; } ... LABEL_21: sub_54AB10(v127); ... return v103; }
可以看见伪代码里面包含了userCode和userPwd
在触发登录请求的时候,会构造一个json,包含userCode和userPwd,通过某个加密算法加密后,将字节流发送给服务器,服务器解析,并返回加密字节流。
但是整个代码逻辑里面没有发现疑似加密和解密的
只有在253行处,发现了一个封装,将请求参数和请求接口传给了sub_54B040方法。
sub_54B040((char)v116, v117, HIDWORD(v117), v118);
其中v117就是被封装的json对象。
在封装前,可以看见在第242行还有一个用来初始化的函数。但具体初始化了什么,得分析一下。
算法分析 分析sub_54A9A0 该函数的主要作用是用来构建http请求所需的全部核心组件并完成初始化,为后续发送网络请求(如登录请求)。
并还初始化了一个疑似用来加解密的密钥2ebf6b694f4ad1f5b33072df4b602743
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 _DWORD *__thiscall sub_54A9A0 (_DWORD *this , struct QObject *a2) { QNetworkAccessManager *v3; QTimer *v4; QEventLoop *v5; QObject::QObject((QObject *)this , a2); *this = &CHttpHandle::`vftable' ; QUrl::QUrl((QUrl *)(this + 2 )); QByteArray::QByteArray((QByteArray *)(this + 8 )); QString::QString(this + 9 ); v3 = (QNetworkAccessManager *)operator new (8u ); a2 = v3; QNetworkAccessManager::QNetworkAccessManager(v3, (struct QObject *)this ); *(_DWORD *)v3 = &QNetworkAccessManager::`vftable' ; this [3 ] = v3; sub_54C4A0(this ); QObject::connect(&a2, this [3 ], "2finished(QNetworkReply*)" , this , "1OnHttpFinished(QNetworkReply*)" , 0 ); QMetaObject::Connection::~Connection((QMetaObject::Connection *)&a2); v4 = (QTimer *)operator new (0x18 u); a2 = v4; QTimer::QTimer(v4, (struct QObject *)this ); *(_DWORD *)v4 = &QTimer::`vftable' ; this [7 ] = v4; QObject::connect(&a2, v4, "2timeout()" , this , "1handleTimeOut()" , 0 ); QMetaObject::Connection::~Connection((QMetaObject::Connection *)&a2); v5 = (QEventLoop *)operator new (8u ); a2 = v5; QEventLoop::QEventLoop(v5, 0 ); *(_DWORD *)v5 = &QEventLoop::`vftable' ; this [6 ] = v5; this [4 ] = 0 ; this [5 ] = 0 ; QString::operator =(this + 9 , "2ebf6b694f4ad1f5b33072df4b602743" ); return this ; }
分析sub_54B040 双击跟进sub_54B040,得到伪代码如下
分析可知,这是一个基于Qt网络模块实现的带加密和解密的 HTTP POST 请求处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 char __thiscall sub_54B040 (void *this , char a2, char a3, int a4, char a5) { ... v5 = (int )this ; v96 = (int )this ; v6 = 0 ; HIDWORD(v83) = a4; v98 = 0 ; v84 = 4 ; v79 = (double )clock(); sub_40DFF0( "..\\..\\QMeeting_IM_new_5_15_2MSVC\\Src\\Model\\WSThreadModel\\httphandle.cpp" , "CHttpHandle::PostDownLoadUrl" , 68 ); QUrl::QUrl(v87, &a2, 0 ); if ( QUrl::isValid((QUrl *)v87) ) { QByteArray::QByteArray((QByteArray *)&v95); v100 = operator new (0x180330 u); v8 = (void *)sub_526E90(v100); LOBYTE(v84) = 6 ; v100 = v8; Block[0 ] = 1 ; sub_5303C0(); if ( !(unsigned __int8)sub_40DC10("IsEncrypt" , (int )Block) || (v101 = QString::fromAscii_helper("NotEncrypt" , 10 ), v6 = 1 , LOBYTE(v84) = 8 , v98 = 1 , v9 = QString::indexOf(&a2, &v101, 0 , 1 ), Block[0 ] = 0 , v9 != -1 ) ) { Block[0 ] = 1 ; } v84 = 6 ; if ( (v6 & 1 ) != 0 ) { v6 &= ~1u ; QString::~QString(&v101); } if ( Block[0 ] ) { v10 = (QByteArray *)QString::toUtf8(&a3, &v101); v11 = QByteArray::data(v10); QByteArray::operator =(&v95, v11); LOBYTE(v84) = 6 ; QByteArray::~QByteArray((QByteArray *)&v101); } else { ... while ( v15 < *(_DWORD *)(v99 + 4 ) / 2 ) { ... ++v15; } ... while ( v22 != (const struct QString *)v21 ) { ... v22 = (const struct QString *)(v92 + 1 ); v26 = v94 == 1 ; v94 ^= 1u ; ++v92; if ( v26 ) break ; v21 = v93; } ... QString::~QString(&v99); } v101 = (int )operator new (4u ); LOBYTE(v84) = 19 ; v27 = QNetworkRequest::QNetworkRequest((QNetworkRequest *)v101); LOBYTE(v84) = 6 ; v74 = (int )v90; *(_DWORD *)(v5 + 20 ) = v27; QNetworkRequest::sslConfiguration(v27, v74); LOBYTE(v84) = 20 ; QSslConfiguration::setPeerVerifyMode(v90, 0 ); QSslConfiguration::setProtocol(v90, 6 ); QNetworkRequest::setSslConfiguration(*(QNetworkRequest **)(v5 + 20 ), (const struct QSslConfiguration *)v90); QNetworkRequest::setUrl(*(QNetworkRequest **)(v5 + 20 ), (const struct QUrl *)v87); ... sub_410D20(v82, &v101, "c9dee2aa124845cfb35c3b222339f9bb" ); ... v36 = QNetworkAccessManager::post( *(QNetworkAccessManager **)(v96 + 12 ), *(const struct QNetworkRequest **)(v96 + 20 ), (const struct QByteArray *)&v95); ... QObject::connect(&v102, LODWORD(v71), HIDWORD(v71), v72, v73, v74); ... QObject::connect( &v102, *(_DWORD *)(v96 + 16 ), "2downloadProgress(qint64,qint64)" , v96, "1OnDownloadProgress(qint64,qint64)" , 0 ); ... if ( v39 ) v74 = 15000 ; else v74 = 35000 ; QTimer::start(v41, v74); QEventLoop::exec(*(_DWORD *)(v40 + 24 ), 0 ); ... Block[0 ] = 1 ; v74 = (int )Block; v73 = "IsEncrypt" ; v88 = (double )v44; sub_5303C0(); if ( !(unsigned __int8)sub_40DC10(v73, v74) || (v102 = QString::fromAscii_helper("NotEncrypt" , 10 ), LOBYTE(v84) = 31 , v30 |= 2u , v98 = v30, v45 = QString::indexOf(&a2, &v102, 0 , 1 ), Block[0 ] = 0 , v45 != -1 ) ) { Block[0 ] = 1 ; } v84 = 24 ; if ( (v30 & 2 ) != 0 ) { v30 &= ~2u ; QString::~QString(&v102); } if ( Block[0 ] ) { QByteArray::operator =(HIDWORD(v83), v40 + 32 ); v46 = (char *)v100; } else { ... if ( *(int *)(v99 + 4 ) <= 0 ) { ... v7 = 0 ; LABEL_62: QString::~QString(v82); QSslConfiguration::~QSslConfiguration((QSslConfiguration *)v90); QByteArray::~QByteArray((QByteArray *)&v95); goto LABEL_63; } ... QString::~QString(&v99); } if ( v46 ) { sub_526EC0(v46); v74 = 1573680 ; sub_61462E(v46); } v7 = 1 ; goto LABEL_62; } v7 = 0 ; LABEL_63: QUrl::~QUrl((QUrl *)v87); sub_41A220(v80); QString::~QString(&a2); QString::~QString(&a3); QString::~QString(&a5); return v7; }
在第125行开始读取配置文件,判断是否需要加密。如果加密就走到153行sub_529540代码逻辑处。
还在265行这里硬编码了请求头appKey的值
sub_527540是负责响应解密的。
借助豆包,弄了一个表格
方法名
核心作用
关键细节/补充说明
sub_54A9A0
初始化Http对象 + 硬编码初始化密码
① 作为CHttpHandle类构造函数,创建QNetworkAccessManager/QTimer等核心网络组件; ② 硬编码存储DES加解密密钥(2ebf6b694f4ad1f5b33072df4b602743); ③ 绑定“请求完成/超时”回调,搭建请求基础环境
sub_5BAE40
构造HTTP请求对象(请求URL、请求体、请求内容)
① 组装登录请求核心要素:目标接口URL、JSON格式请求体、原始明文参数(如账号/密码); ② 仅做请求对象构造,无加密逻辑; ③ 为后续加密/发送提供原始数据载体
sub_529540
加密请求参数
① DES对称加密算法底层核心实现,仅处理8字节(64位)固定分组数据; ② 执行DES关键步骤:64位位拆分、IP初始置换、16轮轮函数(S盒/P盒); ③ 依赖sub_54A9A0的硬编码密钥,输出密文参数
sub_54B040
带加密和解密的 HTTP POST 请求处理函数
① 接收加密参数,构建QNetworkRequest(配置SSL、请求头如appKey); ② 发送POST请求,设置15s/35s超时并等待响应; ③ 接收服务端密文响应,触发解密流程; ④ 包含内存安全校验,防止数据越界
sub_527540
解密服务端响应数据
① DES解密上层封装:将响应密文按8字节分块、去除加密补位; ② 复用sub_529540(反向执行16轮轮函数)完成单块解密; ③ 拼接解密结果,还原明文业务数据(如用户信息/token); ④ 校验响应内存边界,避免解密越界
数据解密 这里换到wireshark抓包
设置过滤规则如下:
1 ip.addr == 127.0.0.1 &&http
请求解密 复制请求包的请求数据为hex格式的字符串
解密成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "appId" : "c9dee2aa124845cfb35c3b222339f9bb" , "data" : { "clientType" : "3" , "custCode" : "" , "userCode" : "111" , "userPwd" : "3b018a6265b6439dfe80eca21773f5da" , "userType" : "PC" , "versionCode" : "14731" , "versionKey" : "" , "versionType" : "0" }, "method" : "" , "nonce" : "1ce14fb5cb1a49a0a04b6f947d52fbf9" , "signature" : "0021e124a650d3b6596aa94981313a072a338a0477507113eebf5643e6a05137" , "timestamp" : "707741804021548485" , "tokenId" : "" , "userCode" : "" }
响应解密 一样的流程操作一遍
响应也解密成功
1 2 3 4 5 { "data" : null , "resultCode" : "11001" , "resultMsg" : "用户名或者密码错误,请重新输入" }
防重放分析 看到这个json的键值,就知道肯定是有一个防重放了。
通过删改json的键值,发现登录接口似乎不受signature影响,只有appId会产生一点微不足道的影响(不影响一直重放数据包)
注:上面两个截图是借助了mitmproxy框架实现了自动请求响应加解密脚本。
在获取当前用户信息的接口处就很明显是存在了防重放。分别有invalid.nonce、timestamp is not allowed、参数可能被修改。
其实大概也知道这个signature的生成逻辑,就是nonce、timestamp、appId、data、userCode经过一定的组合后,通过sha256之类的hash算法生成的值。
分析方式也很简单,一样的思路,查找交叉引用。
分析sub_5B8600 请求头的appKey是userCode-appId 请求体的appId是固定值c9dee2aa124845cfb35c3b222339f9bb 请求体的nonce是删除-的uuid字符串 请求体的timestamp是时间戳
而signature的生成逻辑是sub_5A80A0 初始化摘要上下文,随后将userCode、 appId、 nonce 和 timestamp 依次复制后传给 sub_5A80B0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 QJsonObject *__thiscall sub_5B8600 (char *this , QJsonObject *a2) { v46 = this ; v37 = a2; QJsonObject::QJsonObject(a2); v2 = (QDateTime *)QDateTime::currentDateTime(v47); v3 = QDateTime::toMSecsSinceEpoch(v2); QString::number(v43, *(_DWORD *)(v4 + 472 ) + v3, (unsigned __int64)(*(_QWORD *)(v4 + 472 ) + v3) >> 32 ); QJsonValue::QJsonValue((QJsonValue *)v41, (const char *)v5); v49 = QString::fromAscii_helper("timestamp" , 9 ); QJsonObject::insert(a2, v40, &v49, v41); v44 = QString::fromAscii_helper("c9dee2aa124845cfb35c3b222339f9bb" , 32 ); QJsonValue::QJsonValue((QJsonValue *)v41, (const char *)v9); v49 = QString::fromAscii_helper("appId" , 5 ); QJsonObject::insert(a2, v40, &v49, v41); sub_5A8810(&v48, v45); QJsonValue::QJsonValue((QJsonValue *)v41, (const char *)v12); v49 = QString::fromAscii_helper("nonce" , 5 ); QJsonObject::insert(a2, v40, &v49, v41); QString::QString((QString *)&v34, (const struct QString *)(v46 + 8 )); QString::QString((QString *)&v33, (const struct QString *)&v44); QString::QString((QString *)&v31, (const struct QString *)v45); QString::QString((QString *)&v31, (const struct QString *)v43); v19 = sub_5A80B0(v47, v32, v33, v34, (char )v35); QJsonValue::QJsonValue((QJsonValue *)v39, (const char *)v20); v49 = QString::fromAscii_helper("signature" , 9 ); QJsonObject::insert(a2, v42, &v49, v39); QJsonValue::QJsonValue((QJsonValue *)v39, Directory); v49 = QString::fromAscii_helper("method" , 6 ); QJsonObject::insert(a2, v42, &v49, v39); v23 = sub_51B5F0(v36); QString::QString((QString *)v47, (const struct QString *)(v23 + 456 )); QJsonValue::QJsonValue((QJsonValue *)v39, (const char *)v24); v49 = QString::fromAscii_helper("tokenId" , 7 ); QJsonObject::insert(a2, v42, &v49, v39); v27 = QString::toStdString(v46 + 8 , Block); QJsonValue::QJsonValue((QJsonValue *)v39, (const char *)v27); v46 = (char *)QString::fromAscii_helper("userCode" , 8 ); QJsonObject::insert(a2, v42, &v46, v39); QString::~QString(v45); QString::~QString(&v44); QString::~QString(v43); nullsub_1(&v48); return a2; }
代码sub_5A80A0 不用分析
1 2 3 4 void *__thiscall sub_5A80A0 (void *this ) { return this ; }
代码sub_5A80B0 在这个代码处,可以看见userCode、 appId、 nonce 和 timestamp 是先放进QMap,然后按字典序(升序)依次取出、append,再做 HMAC-SHA256签名,其中HMAC-SHA256算法的密钥是5fddffdf05aa480ab9e7bb48e658db99。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 int __stdcall sub_5A80B0 (int a1, char a2, char a3, char a4, char a5) { v50 = 0 ; v60 = 1 ; QString::QString((QString *)v41, (const struct QString *)&a2); QString::QString((QString *)v42, (const struct QString *)&a3); QString::QString((QString *)v43, (const struct QString *)&a4); QString::QString((QString *)v44, (const struct QString *)&a5); QString::QString(v45); sub_5A8920(&v51, v41, v41); sub_5A8920(&v51, v42, v42); sub_5A8920(&v51, v43, v43); sub_5A8920(&v51, v44, v44); QString::append((QString *)v45, v15); strcpy (v59, "5fddffdf05aa480ab9e7bb48e658db99" ); v19 = (QByteArray *)QString::toUtf8(v45, v39); v20 = QByteArray::data(v19); v51 = (volatile signed __int32 *)EVP_sha256(); HMAC_CTX_init(v53); HMAC_Init_ex(v53, v59, strlen (v59), v51, 0 ); HMAC_Update(v53, v20, strlen (v20)); HMAC_Final(v53, v21, &v38); HMAC_CTX_cleanup(v53); for ( k = 0 ; k < v22; Src[v33 + 1 ] = v32 ) { v28 = (*((_DWORD *)v57 + k) >> 4 ) & 0xF ; v29 = *((_DWORD *)v57 + k) & 0xF ; v30 = v28 + 87 + (v28 < 0xA ? 0xD9 : 0 ); Src[v48] = v30; ++k; v32 = v29 + 87 + (v29 < 0xA ? 0xD9 : 0 ); v48 = v31 + 2 ; } QByteArray::~QByteArray((QByteArray *)v39); QString::~QString(v45); QString::~QString(v44); QString::~QString(v43); QString::~QString(v42); QString::~QString(v41); return a1; }
以之前解密的数据进行尝试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "appId" : "c9dee2aa124845cfb35c3b222339f9bb" , "data" : { "clientType" : "3" , "custCode" : "" , "userCode" : "111" , "userPwd" : "3b018a6265b6439dfe80eca21773f5da" , "userType" : "PC" , "versionCode" : "14731" , "versionKey" : "" , "versionType" : "0" }, "method" : "" , "nonce" : "1ce14fb5cb1a49a0a04b6f947d52fbf9" , "signature" : "0021e124a650d3b6596aa94981313a072a338a0477507113eebf5643e6a05137" , "timestamp" : "707741804021548485" , "tokenId" : "" , "userCode" : "" }
userCode、 appId、 nonce 和 timestamp经过排序再拼接后得到字符串1ce14fb5cb1a49a0a04b6f947d52fbf9707741804021548485c9dee2aa124845cfb35c3b222339f9bb
经过算法签名后,值和数据包里的signature能完全对应。也就分析成功了
最后效果 实现了一个mitmproxy脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 from mitmproxy import httpfrom Crypto.Cipher import DESfrom Crypto.Util.Padding import pad, unpadimport jsonimport timeimport uuidimport hmacimport hashlibORIGINAL_KEY_STR = "2ebf6b694f4ad1f5b33072df4b602743" DES_KEY = ORIGINAL_KEY_STR[:8 ].encode("ascii" ) BLOCK_SIZE = DES.block_size TARGET_HOSTS = ["127.0.0.1" ] TARGET_PORT = 8001 APP_ID = "c9dee2aa124845cfb35c3b222339f9bb" HMAC_KEY = "5fddffdf05aa480ab9e7bb48e658db99" def build_payload (user_code: str ): """ 生成包含新nonce、timestamp、signature的字典 :param user_code: 用户编码(如safetest10_677) :return: 包含nonce、timestamp、signature的字典 """ timestamp = str (int (time.time() * 1000 )) nonce = uuid.uuid4().hex parts = sorted ([user_code, APP_ID, nonce, timestamp]) payload_str = "" .join(parts) signature = hmac.new( HMAC_KEY.encode(), payload_str.encode(), hashlib.sha256 ).hexdigest() return { "nonce" : nonce, "signature" : signature, "timestamp" : timestamp, } def des_ecb_encrypt_bytes (plain_bytes: bytes ) -> bytes : """ DES ECB加密(输入/输出都是字节流,适配Burp) :param plain_bytes: 明文字节流 :return: 密文字节流 """ try : padded_plain = pad(plain_bytes, BLOCK_SIZE, style="pkcs7" ) cipher = DES.new(DES_KEY, DES.MODE_ECB) cipher_bytes = cipher.encrypt(padded_plain) return cipher_bytes except Exception as e: print (f"加密失败:{str (e)} " ) return plain_bytes def des_ecb_decrypt_bytes (cipher_bytes: bytes ) -> bytes : """ DES ECB解密(输入/输出都是字节流,适配Burp) :param cipher_bytes: 密文字节流 :return: 明文字节流 """ try : cipher = DES.new(DES_KEY, DES.MODE_ECB) decrypted_bytes = cipher.decrypt(cipher_bytes) plain_bytes = unpad(decrypted_bytes, BLOCK_SIZE, style="pkcs7" ) return plain_bytes except Exception as e: print (f"解密失败:{str (e)} " ) return cipher_bytes def is_target_flow (flow: http.HTTPFlow ) -> bool : """判断当前流量是否是目标主机+端口""" return (flow.request.host.lower() in [h.lower() for h in TARGET_HOSTS]) and (flow.request.port == TARGET_PORT) def request (flow: http.HTTPFlow ) -> None : """ 拦截请求: 1. Burp发送的明文 → 替换nonce/timestamp/signature → DES加密 → 发给服务器 """ if is_target_flow(flow): try : request_body = flow.request.content if not request_body: return try : req_json = json.loads(request_body.decode("utf-8" )) user_code = req_json.get("userCode" ) if not user_code and "appKey" in flow.request.headers: app_key = flow.request.headers["appKey" ] user_code = app_key.split("-" )[0 ] if user_code: new_sign_info = build_payload(user_code) req_json["nonce" ] = new_sign_info["nonce" ] req_json["timestamp" ] = new_sign_info["timestamp" ] req_json["signature" ] = new_sign_info["signature" ] request_body = json.dumps(req_json, ensure_ascii=False ).encode("utf-8" ) print (f"✅ 已替换请求体字段 | userCode: {user_code} " ) print (f" 新nonce: {new_sign_info['nonce' ]} " ) print (f" 新timestamp: {new_sign_info['timestamp' ]} " ) print (f" 新signature: {new_sign_info['signature' ]} " ) else : print ("⚠️ 未找到userCode,跳过字段替换" ) except json.JSONDecodeError as e: print (f"❌ JSON解析失败:{str (e)} ,跳过字段替换" ) encrypted_body = des_ecb_encrypt_bytes(request_body) flow.request.content = encrypted_body print (f"✅ 请求加密完成 | 主机: {flow.request.host} :{flow.request.port} " ) print (f" 明文长度:{len (request_body)} 字节 | 密文长度:{len (encrypted_body)} 字节" ) except Exception as e: print (f"❌ 请求处理失败:{str (e)} " ) def response (flow: http.HTTPFlow ) -> None : """ 拦截响应:服务器返回的密文 → mitmproxy解密 → 给Burp显示明文 """ if is_target_flow(flow): try : response_body = flow.response.content if not response_body: return decrypted_body = des_ecb_decrypt_bytes(response_body) flow.response.content = decrypted_body print (f"✅ 响应解密完成 | 主机: {flow.request.host} :{flow.request.port} " ) print (f" 密文长度:{len (response_body)} 字节 | 明文长度:{len (decrypted_body)} 字节" ) except Exception as e: print (f"❌ 响应处理失败:{str (e)} " ) def start (): """mitmproxy启动时执行""" print ("🚀 DES ECB 加解密+签名替换代理已启动" ) print (f" DES密钥(ASCII前8字符):{ORIGINAL_KEY_STR[:8 ]} " ) print (f" DES密钥字节(Hex):{DES_KEY.hex ()} " ) print (f" HMAC APP_ID:{APP_ID} " ) print (f" 目标主机:{TARGET_HOSTS} | 目标端口:{TARGET_PORT} " )