📄 取词内部细节.txt
字号:
if (nCode>=0)
{
// LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lParam;
LPMSLLHOOKSTRUCT pMouseHook=(MSLLHOOKSTRUCT FAR *) lParam;
//if(nCode>=0)
//{
MousePos.x = pMouseHook->pt.x;
MousePos.y = pMouseHook->pt.y;
//}
}
return CallNextHookEx(oldkeyhook,nCode,wParam,lParam);
}
DllExport void EndHook(void)
{
UnhookWindowsHookEx(oldkeyhook);
}
DllExport POINT GetCurMousePoint()
{
return MousePos;
}
调用取词时,取词的钩子是什么时候触发的?
取词库是一个扩展的动态连接库,它开放一些启动取词功能的接口
1、调用主窗口的应用程序中的初始化实例InitInstance(HINSTANCE hInstance, int nCmdShow)中
初始化取词NHD_InitGetWords(hInst, hWnd)——〉
if (!NHD_LoadGetWordLib())
{
NHD_FreeLoadedLib();
return NULL;
}
NHD_LoadGetWordLib()调入取词类库函数——〉
BOOL NHD_LoadGetWordLib(void)
{
/*
//指针函数如下定义——————从取词的函数库中导入取词的函数和开关
typedef DWORD (WINAPI *BL_SETFLAG32)(UINT nFlag, HWND hNotifyWnd, int MouseX, int MouseY);
typedef DWORD (WINAPI *BL_GETTEXT32)(LPSTR lpszCurWord, int nBufferSize, LPRECT lpWordRect);
typedef DWORD (WINAPI *BL_GETTEXT32_EX)(LPSTR lpszCurWords, int nBufferSize, LPRECT lpWordRect);
BL_GETTEXT32 fpBL_GetText32 = NULL;//当前词
BL_SETFLAG32 fpBL_SetFlag32 = NULL;//开关标志
BL_GETTEXT32_EX fpBL_GetText32_Ex = NULL;//当前行
*/
g_hGetWordInst = LoadLibrary("nhw32.dll");
if (!g_hGetWordInst)
{
DbgPrintf("NHD_LoadGetWordLib loading error!\n") ;
return FALSE;
}
fpBL_GetText32 = (BL_GETTEXT32)GetProcAddress(g_hGetWordInst, "BL_GetText32");
if (!fpBL_GetText32)
{
return FALSE;
}
fpBL_GetText32_Ex = (BL_GETTEXT32_EX)GetProcAddress(g_hGetWordInst, "BL_GetText32_Ex");
if (!fpBL_GetText32_Ex)
{
return FALSE;
}
fpBL_SetFlag32 = (BL_SETFLAG32)GetProcAddress(g_hGetWordInst, "BL_SetFlag32");
if (!fpBL_SetFlag32)
{
return FALSE;
}
//only valid in windows NT environment
typedef BOOL (WINAPI *SETNHW32)();//定义函数指针,开始取词入口函数
SETNHW32 SetNHW32 = NULL;
SetNHW32 = GetProcAddress(g_hGetWordInst, "SetNHW32");
if(SetNHW32)//——启动取词功能
{
if(!SetNHW32())
{
DbgPrintf("Unable to Set nhw32!");
return FALSE;
}
}
return TRUE;
}
2、转到取词的动态连接库。
只要动态库中的一个函数或变量被调用,dll将被调用到内存,并执行DllMain入口函数,之后DllMain将不再被启动这个接口,这个函数设置了动态库中创建的所有的钩子的句柄,并且创建了一个异步线程
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason) //judge messages
{
case DLL_PROCESS_ATTACH:
g_hinstDll = hinstDLL;
//设置钩子结构的实例句柄
g_BitBltHook.hInst = hinstDLL;
g_TextOutAHook.hInst = hinstDLL;
g_TextOutWHook.hInst = hinstDLL;
g_ExtTextOutAHook.hInst = hinstDLL;
g_ExtTextOutWHook.hInst = hinstDLL;
//注册消息
//鼠标消息
g_uMsg = RegisterWindowMessage("Noble Hand");
if(!g_uMsg)
{
return FALSE;
}
//是否词标志
//Because forget to add this function before, with the result that it gets word little slowly
BL_HASSTRING = RegisterWindowMessage(MSG_HASSTRINGNAME);
if(!BL_HASSTRING)
{
return FALSE;
}
//创建互斥体
// create mutex
hMutex = CreateMutex(NULL, FALSE, MUTEXNAME);
if (NULL == hMutex)
{
return FALSE;
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
// restore
NHUnHookWin32Api();
// close mutex
if (NULL != hMutex)
{
CloseHandle(hMutex);
}
break;
}
return TRUE;
}
激发SetNHW32()函数,启动取词线程,等待取词鼠标消息
////Exports.c//SetNHW32()——〉//设置global system 底层钩子,(需要在退出取词时关闭钩子)
DLLEXPORT BOOL WINAPI SetNHW32()
{
if(g_hHook == NULL)
{
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hinstDll, 0);
//装载钩子,钩子类型是WH_GETMESSAGE,应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
if (g_hHook == NULL)
{
return FALSE;
}
}
return TRUE;
}
//由于创建的底层钩子,钩子被装载后,系统将不断的监测整个操作系统消息队列的消息,取词dll中注册了
如果一旦有消息发生,它将使用GetMsgProc回调消息处理函数来处理它,
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
ProcessWin32API();
return CallNextHookEx(g_hHook, nCode, wParam, lParam);//调用下一个钩子——处理系统消息,因为win32系统本身使用了很多的钩子
}
//内联,处理win32消息
__inline void ProcessWin32API()
{
if (g_bNewGetWordFlag != g_bOldGetWordFlag)
//察看是否进行取词状态切换
//取词本身已经集成了,如果取到的词 = 已经得到的词,不再进行取词
{
if (g_bNewGetWordFlag)//得到新词
{
WaitForSingleObject(hMutex, INFINITE);
//一个时间只能有一个线程拥有互斥器mutex, 该语句是当前线程拥有互斥器.(该互斥器在dllmain中定义,安全属性为空,不属于当前线程。//)使用realeseMutex()线程释放互斥器。互斥器产生之后,由某一线程完成锁定工作(即调用Wait…函数),此时系统
将该mutex的拥有权交于该线程,然后短暂地将该对象设置为激发态,于是Wait…函数返回,做完相应的工作之后(如:修改链表指针、修改计数器、写文件等),调用ReleaseMutex释放拥有权。
//设置全局重要标志
g_nGetWordStyle = g_nFlag;
g_bAllowGetCurWord = TRUE;
g_CurMousePos.x = g_MouseX;
g_CurMousePos.y = g_MouseY;
g_szCurWord[0] = 0x00;//取词数组置空
g_nCurCaretPlace = -1;
if (!g_bHooked)//还没有设置钩子
{
NHHookWin32Api();//调用取词钩子————————重要函数
g_bHooked = TRUE;//已经取词
}
g_bAllowGetCurWord = TRUE;//允许取词
ReleaseMutex(hMutex);//释放互斥器
}
else
{
WaitForSingleObject(hMutex, INFINITE);//当前线程获得互斥器
g_bAllowGetCurWord = FALSE;
if (g_bHooked)//设置钩子完成
{
//恢复钩子
RestoreWin32Api(&g_BitBltHook, HOOK_ONLY_READ);——————次要函数
RestoreWin32Api(&g_TextOutAHook, HOOK_ONLY_READ);
RestoreWin32Api(&g_TextOutWHook, HOOK_ONLY_READ);
RestoreWin32Api(&g_ExtTextOutAHook, HOOK_ONLY_READ);
RestoreWin32Api(&g_ExtTextOutWHook, HOOK_ONLY_READ);
g_bHooked = FALSE;
}
ReleaseMutex(hMutex);
}
}
g_bOldGetWordFlag = g_bNewGetWordFlag;//设置新的取词状态,
}
先难后易,先分析恢复钩子调用原winapi的地址的函数RestoreWin32Api,
void RestoreWin32Api(LPAPIHOOKSTRUCT lpApiHook, int nSysMemStatus)
{//恢复原始winapi调用
DWORD dwReserved;//保留地址
DWORD dwTemp;//临时地址
if (lpApiHook->lpWinApiProc == NULL)//如果钩子中没有设置替代api的函数,就返回,不用恢复地址
return;
IMAGE_IMPORT_DESCRIPTOR里的Name成员变量是模块名字的指针。如果我们想要挂钩某个函数比如是来自kernel32.dll我们就在导入表里找属于名字kernel32.dll的描述符号。我们先调用ImageDirectoryEntryToData然后找到名字是"kernel32.dll"的描述符号(可能不只一个描述符号是这个名字),最后我们在这个模块的记录里所有函数的列表里找到我们想要的函数(函数地址通过GetProcAddress函数获得)。如果我们找到了就必须用VirtualProtect函数来改变内存页面的保护属性,然后就可以在内存中的这些部分写入代码了。在改写了地址之后我们要把保护属性改回来。在调用VirtualProtect之前我们还要先知道有关页面的信息,这通过VirtualQuery来实现。我们可以加入一些测试以防某些函数会失败(比方说如果第一次调用VirtualProctect就失败了,我们就没办法继续)。
该函数用来把已经分配的页改变成保护页。参数1指定分配页的基地址;参数2指定保护页的长度;参数3指定页的保护属性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE等等;参数4用来返回原来的保护属性。
if (!VirtualProtect(lpApiHook->lpWinApiProc, 16, PAGE_READWRITE,
&dwReserved))//设置虚拟api函数地址页为读写页
{
MessageBox(NULL, "VirtualProtect-READWRITE", NULL, MB_OK);
return;
}
//读出钩子结构体中保存的winapi地址,改写当前地址为winapi函数地址
memcpy(lpApiHook->lpWinApiProc,(LPVOID)lpApiHook->WinApiFiveByte,BUFFERLEN);
if (!VirtualProtect(lpApiHook->lpWinApiProc, 16, dwReserved, &dwTemp))
{//设置虚拟api函数地址页为读写页
MessageBox(NULL, "VirtualProtect-RESTORE", NULL, MB_OK);
return;
}
}
然后分析NHHookWin32Api();//调用取词钩子
DLLEXPORT DWORD WINAPI NHHookWin32Api()
{
//设置全局变量,为取词并分析作准备
g_nCurCaretPlace = -1;
g_szCurWord[0] = 0x00;
g_szTotalWord[0] = 0x00;
g_nCurCaretPlaceInTotalWord = -1;
g_CharType = CHAR_TYPE_OTHER;
g_bMouseInTotalWord = FALSE;
g_bAllowGetCurWord = TRUE;
nWordNum = 0;//当前总词量、
szMemDCWordBuff[0] = 0x00;//设备缓冲中词的初始化
HookAllTextOut();——————————使用钩子取词
return BL_OK;
}
DLLEXPORT DWORD WINAPI NHUnHookWin32Api()
{
g_bAllowGetCurWord = FALSE;
UnHookAllTextOut();
return BL_OK;
}
进一步跟踪到HookAllTextOut();———使用钩子取词,对所有类型的输出函数调用钩子进行输出
void HookAllTextOut()
{
HookWin32Api(&g_BitBltHook, HOOK_CAN_WRITE); //位图钩子
HookWin32Api(&g_TextOutAHook, HOOK_CAN_WRITE); //ascii码字符输出
HookWin32Api(&g_TextOutWHook, HOOK_CAN_WRITE); //unicode码字符输出
HookWin32Api(&g_ExtTextOutAHook, HOOK_CAN_WRITE);
HookWin32Api(&g_ExtTextOutWHook, HOOK_CAN_WRITE);
}
-----进入包装的钩子dll函数库
//钩子函数
void HookWin32Api(LPAPIHOOKSTRUCT lpApiHook, int nSysMemStatus);
//启动钩子事件
void HookWin32Api(LPAPIHOOKSTRUCT lpApiHook, int nSysMemStatus)
{
DWORD dwReserved;//保留双字节地址
DWORD dwTemp;//临时双字节地址
BYTE bWin32Api[5];//保存winapi指令字节数组
bWin32Api[0] = 0x00; //
//1、 取得被截获winapi函数地址﹒
if(lpApiHook->lpWinApiProc == NULL)
//如果从钩子结构提取的winapi函数的指针为空,调用函数进行填充
{
lpApiHook->lpWinApiProc = (LPVOID)NHGetFuncAddress(lpApiHook->hInst,
lpApiHook->lpszApiModuleName,lpApiHook->lpszApiName);————————·1
//如果想要winapi指针地址的偏移,取得该指针的字节表示+指针偏移。
if (lpApiHook->dwApiOffset != 0)
lpApiHook->lpWinApiProc = (LPVOID)((DWORD)lpApiHook->lpWinApiProc + lpApiHook->dwApiOffset);
}
//2、 取得替代函数地址。
if(lpApiHook->lpHookApiProc == NULL)
//如果钩子结构提取的替代函数的指针为空,调用函数进行填充
{
lpApiHook->lpHookApiProc = (LPVOID)NHGetFuncAddress(lpApiHook->hInst,
lpApiHook->lpszHookApiModuleName,lpApiHook->lpszHookApiName);————·2
}
//3、 形成 JMP 指令。
if (lpApiHook->HookApiFiveByte[0] == 0x00)
//如果跳转指令未形成,求得跳转指令
{
MakeJMPCode(lpApiHook->HookApiFiveByte, lpApiHook->lpHookApiProc);——————·3
}
//4、设置虚拟内存中的winapi地址为读写
if (!VirtualProtect(lpApiHook->lpWinApiProc, 16, PAGE_READWRITE,&dwReserved))
//设置虚拟内存中的winapi地址为读写
{
MessageBox(NULL, "VirtualProtect-READWRITE", NULL, MB_OK);
return;
}
5、改写跳转指令
if (nSysMemStatus == HOOK_NEED_CHECK)//——系统内存状态
//hook需要检查,将钩子函数(替代函数)的地址赋值给被截获winapi函数的地址——————地址替换
{
memcpy(lpApiHook->lpWinApiProc, (LPVOID)lpApiHook->HookApiFiveByte,BUFFERLEN);
}
else
{
if (lpApiHook->WinApiFiveByte[0] == 0x00)
//winapi未赋值
{
// 使用API函数指针给数组赋值
memcpy(lpApiHook->WinApiFiveByte,(LPVOID)lpApiHook->lpWinApiProc,BUFFERLEN);
//判断是否重复截获(即判断备份的头5个字节是否为形成的JMP指令)
if (strncmp(lpApiHook->WinApiFiveByte, lpApiHook->HookApiFiveByte, BUFFERLEN) == 0)
//如果读写虚拟内存没有成功,因为系统可能保护这些内存。
{
//恢复备份的字节指令
memcpy(lpApiHook->WinApiFiveByte,(LPVOID)lpApiHook->WinApiBakByte,BUFFERLEN);
}
}
else
//winapi已经赋值,说明系统允许进行读写
{
//填充当前函数中的指令代码数组
memcpy(bWin32Api,(LPVOID)lpApiHook->lpWinApiProc,BUFFERLEN);
}
if (strncmp(bWin32Api, lpApiHook->HookApiFiveByte, BUFFERLEN) != 0)//临时变量为跳转指令
如果当前函数中的指令数组 <> 钩子结构中的钩子函数的指令吗。
{
//將 JMP 指定填入 API 函数的头
//将钩子函数指令填充winapi指令
memcpy(lpApiHook->lpWinApiProc, (LPVOID)lpApiHook->HookApiFiveByte,BUFFERLEN);
}
}
//6、将虚拟内存地址设回原来的状态。
if (!VirtualProtect(lpApiHook->lpWinApiProc, 16, dwReserved, &dwTemp))
{
MessageBox(NULL, "VirtualProtect-RESTORE", NULL, MB_OK);
return;
}
}
函数分析
//远程调用过程,得到函数地址//参数:句柄,模块,方法
FARPROC WINAPI NHGetFuncAddress(HINSTANCE hInst, LPCSTR lpMod, LPCSTR lpFunc);
FARPROC WINAPI NHGetFuncAddress(HINSTANCE hInst, LPCSTR lpMod, LPCSTR lpFunc)
{
HMODULE hMod;//模块句柄
FARPROC procFunc;//远程过程
if (NULL != lpMod)//模块指针不为空
{
hMod=GetModuleHandle(lpMod);//得到模块句柄
procFunc = GetProcAddress(hMod,lpFunc);//得到函数地址
}
else
{
procFunc = GetProcAddress(hInst,lpFunc);
}
return procFunc;//返回函数地址
}
//跳转到lpCodePoint所指的地址,将指令地址+选择符保存到lpJMPCode
1.取得Windows API入口,用GetProcAddress实现
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
3.写入跳转语句 这步最复杂 Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
有一个未公开函数是AllocCsToDsAlias, UINT WINAPI ALLOCCSTODSALIAS(UINT);
你可以取到这个函数的入口,把API的代码段的选择符(保护模式编程)传给他,他会返回一个可写的数据段选择符。
这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。
void MakeJMPCode(LPBYTE lpJMPCode, LPVOID lpCodePoint);
void MakeJMPCode(LPBYTE lpJMPCode, LPVOID lpCodePoint)
{
BYTE temp;
WORD wHiWord = HIWORD(lpCodePoint);//代码入口的高字
WORD wLoWord = LOWORD(lpCodePoint);//代码入口的低字
WORD wCS;
_asm //
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -