系列第二篇:模擬 QQ 網頁版登入,核心流程——抓包分析登入端點、驗證碼獲取、密碼加密演算法(MD5 多輪雜湊)、HTTP POST 取得 Cookie。
輸入帳號後自動請求:
GET https://check.ptlogin2.qq.com/check?
uin={QQ號}&appid=1006102&r={隨機數}
&js_ver=10038&js_type=1
&login_sig=...&u1=http%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone
回傳 JS 片段:
ptui_checkVC('0','!JWE',...) → 不需圖形驗證碼,第二參數為 verifycodeptui_checkVC('1',...) → 需圖形驗證碼,需額外請求圖片端點GET https://captcha.qq.com/getimage?aid=1006102&r={隨機數}&uin={QQ號}
GET https://ptlogin2.qq.com/login?
u={QQ號}&p={加密密碼}&verifycode={驗證碼}
&aid=1006102&u1=http%3A%2F%2Fid.qq.com%2Findex.html
&h=1&ptredirect=1&ptlang=2052&from_ui=1
&fp=loginerroralert&action=...&mibao_css=&t=1&g=1
&js_ver=10038&js_type=1&login_sig=...
成功回傳:ptuiCB('0','0','https://id.qq.com/index.html','1','登錄成功!', 'xxx')
騰訊採三層 MD5:
P = MD5(密碼明文)U = MD5(P + QQ號十六進位)V = MD5(U + verifycode大寫) → 最終提交密碼public static class PasswordHelper {
public static string GetPassword(string qqNum, string pwd, string verifycode) {
// QQ 號轉 16 位十六進位
var uinHex = int.Parse(qqNum).ToString("x").PadLeft(16, '0');
// P = MD5(pwd)
var P = MD5(pwd);
// U = MD5(P + uinHex)
var U = MD5(P + HexToBin(uinHex)).ToUpper();
// V = MD5(U + verifycode)
var V = MD5(U + verifycode.ToUpper()).ToUpper();
return V;
}
static string MD5(string input) {
using (var md5 = System.Security.Cryptography.MD5.Create()) {
var bytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(input);
return BitConverter.ToString(md5.ComputeHash(bytes)).Replace("-", "").ToLower();
}
}
static string HexToBin(string hex) {
var sb = new StringBuilder();
for (int i = 0; i < hex.Length; i += 2)
sb.Append((char)Convert.ToInt32(hex.Substring(i, 2), 16));
return sb.ToString();
}
}
public static class HttpHelper {
const string userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36";
const string accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
const string contentType = "application/x-www-form-urlencoded";
const string referer = "https://ui.ptlogin2.qq.com/";
public static Stream GetStream(string url, CookieContainer cc) {
var req = (HttpWebRequest)WebRequest.Create(url);
req.CookieContainer = cc;
req.UserAgent = userAgent; req.Accept = accept; req.ContentType = contentType; req.Referer = referer;
req.Method = "GET"; req.ServicePoint.ConnectionLimit = int.MaxValue;
return req.GetResponse().GetResponseStream();
}
public static string GetHtml(string url, CookieContainer cc) {
Thread.Sleep(1000); // 禮貌延遲
using (var stream = GetStream(url, cc))
using (var reader = new StreamReader(stream, Encoding.UTF8))
return reader.ReadToEnd();
}
}
// 1. 取得驗證碼
var vcHtml = HttpHelper.GetHtml($"https://check.ptlogin2.qq.com/check?uin={qq}&appid=1006102&r={rnd.NextDouble()}", _cookie);
string verifycode;
if (vcHtml.Contains("ptui_checkVC('0','")) {
verifycode = vcHtml.Replace("ptui_checkVC('0','", "").Replace("')", "").Substring(0, 4);
} else {
// 需圖形驗證碼:顯示圖片讓使用者手動輸入
var imgStream = HttpHelper.GetStream($"https://captcha.qq.com/getimage?aid=1006102&r={rnd.NextDouble()}&uin={qq}", _cookie);
picCaptcha.Image = Image.FromStream(imgStream);
verifycode = txtManualInput.Text; // 使用者輸入
}
// 2. 計算加密密碼
var pwdEncrypted = PasswordHelper.GetPassword(qq, txtPwd.Text, verifycode);
// 3. 提交登入
var loginUrl = $"https://ptlogin2.qq.com/login?u={qq}&p={pwdEncrypted}&verifycode={verifycode}&aid=1006102&u1=http%3A%2F%2Fid.qq.com%2Findex.html&h=1&ptredirect=1&ptlang=2052&from_ui=1&fp=loginerroralert&action=8-29-82478035&mibao_css=&t=1&g=1";
var ret = HttpHelper.GetHtml(loginUrl, _cookie);
// 4. 判斷成功
bool ok = ret.Contains("ptuiCB('0',");
if (ok) MessageBox.Show("登入成功,Cookie 已儲存!");
登入成功後,_cookie 即攜帶 skey 等關鍵憑證,供下一篇群組/成員抓取使用。