最近的工作中,有个需求,需要监控用户使用Windows电脑时候的鼠标键盘事件、cpu内存使用量、以及守护另外一个程序。涉及到的一些知识,个人觉得除非以后要写个外挂、流氓软件、或者是盗号软件,应该以后都不会用到了。
但是个人觉得还是记录下来一些比较好,万一以后要用到还忘记了,就TM尴尬了。难说以后混不下去了,还可以去盗号赚点生活费什么的。
Windows鼠标键盘Hook
这部分本来使用PyHook做的,因为用python写起来比较简单。后来由于项目需要,改成用C++了。但是还是决定把pyHook的方法记录一下,免得以后找起来麻烦。
pyHook
首先,肯定是要安装好pyHook和pywin32。毕竟需要调用的其实还是windows API。之后代码就非常简单了,在import pythoncom 和 import pyHook 之后:
if __name__=='__main__':
hm = pyHook.HookManager()
hm.KeyDown = onKeyboardEvent
hm.HookKeyboard()
hm.MouseAll = onMouseEvent
hm.HookMouse()
pythoncom.PumpMessages()
以上创建了pyHook的句柄hm之后,剩下的其实就是两句话: 1.设置好回调函数onKeyboardEvent()和onMouseEvent();2.挂上钩子HookKeyboard()和HookMouse()。然后在回调函数里就可以查看的需要的各种监听信息了。针对不同的程序功能,可以自己筛选需要的信息处理。
def onMouseEvent(event):
print "MessageName:",event.MessageName
print "Message:", event.Message
print "Time:", event.Time
print "Window:", event.Window
print "WindowName:", event.WindowName
print "Position:", event.Position
print "Wheel:", event.Wheel
print "Injected:", event.Injected
return True
def onKeyboardEvent(event):
print "MessageName:", event.MessageName
print "Message:", event.Message
print "Time:", event.Time
print "Window:", event.Window
print "WindowName:", event.WindowName
print "Ascii:", event.Ascii, chr(event.Ascii)
print "Key:", event.Key
print "KeyID:", event.KeyID
print "ScanCode:", event.ScanCode
print "Extended:", event.Extended
print "Injected:", event.Injected
print "Alt", event.Alt
print "Transition", event.Transition
return True
这里有一个很神奇的地方,鼠标事件中event.WindowName是可以获取到鼠标点击处所对应的窗口名字的,但是有的时候在某一个程序的窗口中点击可能会获取不到,这个应该是因为,该程序有子窗体,子窗体没有设置名字,而我的钩子又没办法钩到父级窗口(至少目前我是不知道怎么钩到父级的。如果有人知道,麻烦告诉我一下),所以获取不到程序的名字。而神奇之处在于键盘事件的event.WindowName是无论如果都能获取主程序窗口名字的。我也是搞不懂为什么。
最后的return True也是非常关键,相当于把消息又从钩子交回去了,这样我们的系统才能继续按原逻辑执行。如果是false,则事件会被钩子全部拦截,鼠标键盘就失去响应了。
C++ Hook
C++的方式,我使用的是Windows.h。其实原理大同小异,只是代码量比python多一点。
int main()
{
MSG Msg;
HHOOK keybordHook;
HHOOK mouseHook;
keybordHook = SetWindowsHookEx(
WH_KEYBOARD_LL, // 监听类型【键盘】
KeyboardProc, // 处理函数
NULL, // 当前实例句柄
NULL // 监听窗口句柄(NULL为全局监听)
);
mouseHook = SetWindowsHookEx(
WH_MOUSE_LL, // 监听类型【鼠标】
MouseProc, // 处理函数
NULL, // 当前实例句柄
NULL // 监听窗口句柄(NULL为全局监听)
);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
首先声明两种钩子,对于每种钩子的安装,都是使用SetWindowsHookEx()来处理,其设置方法也比较简单一共四个参数:
- 钩子监听的消息类型
- 响应钩子监听事件的回调处理函数
- 应用程序的句柄
- 钩子关联的线程标识(NULL就是与所有线程关联,全局监听)
接下来,把消息类型在回调函数中做处理就行了。回调函数的结构为LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam):
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
const char *info = NULL;
char text[50], data[20];
if (nCode >= 0)
{
if (wParam == WM_KEYDOWN) info = "普通按鍵抬起";
else if (wParam == WM_KEYUP) info = "普通按鍵按下";
else if (wParam == WM_SYSKEYDOWN) info = "系統按鍵抬起";
else if (wParam == WM_SYSKEYUP) info = "系統按鍵按下";
ZeroMemory(text, sizeof(text));
ZeroMemory(data, sizeof(data));
wsprintf(text, "%s - 键盘码 [%04d], 扫描码 [%04d] ", info, p->vkCode, p->scanCode);
wsprintf(data, "按鍵目測為: %c ", p->vkCode);
}
return CallNextHookEx(keybordHook, nCode, wParam, lParam);
}
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LPMSLLHOOKSTRUCT p = (LPMSLLHOOKSTRUCT)lParam;
POINT pt = p->pt;
DWORD mouseData = p->mouseData;
const char *info = NULL;
char text[60], pData[50], mData[50];
if (nCode >= 0)
{
if (wParam == WM_MOUSEMOVE) info = "鼠标移动 ";
else if(wParam == WM_LBUTTONDOWN) info = "鼠标【左键】按下";
else if(wParam == WM_LBUTTONUP) info = "鼠标【左键】抬起";
else if(wParam == WM_LBUTTONDBLCLK) info = "鼠标【左键】双击";
else if(wParam == WM_RBUTTONDOWN) info = "鼠标【右键】按下";
else if(wParam == WM_RBUTTONUP) info = "鼠标【右键】抬起";
else if(wParam == WM_RBUTTONDBLCLK) info = "鼠标【右键】双击";
else if(wParam == WM_MBUTTONDOWN) info = "鼠标【滚轮】按下";
else if(wParam == WM_MBUTTONUP) info = "鼠标【滚轮】抬起";
else if(wParam == WM_MBUTTONDBLCLK) info = "鼠标【滚轮】双击";
else if(wParam == WM_MOUSEWHEEL) info = "鼠标【滚轮】滚动";
ZeroMemory(text, sizeof(text));
ZeroMemory(pData, sizeof(pData));
ZeroMemory(mData, sizeof(mData));
wsprintf( text, "当前状态: %10s ", info);
wsprintf(pData, "0x%x - X: [%04d], Y: [%04d] ", wParam, pt.x, pt.y);
wsprintf(mData, "附带数据: %16u ", mouseData);
}
return CallNextHookEx(mouseHook, nCode, wParam, lParam);
}
这里做一个小总结,Windows中的Hook机制如下:
- SetWindowsHookEx() 设置好钩子类型与回调函数。
- 回调函数处理钩子事件,并且返回CallNextHookEx()。
- CallNextHookEx()设置好消息的下一传递目标。
CPU、内存查看
获取内存占用率这个没有什么多的知识点,直接放个代码好了:
DWORD getWin_MemUsage()
{
MEMORYSTATUS ms;
::GlobalMemoryStatus(&ms);
return ms.dwMemoryLoad;
}
计算CPU使用率时候,要先明确一个事情,就是什么是CPU使用率? 即在任务管理器的刷新周期内CPU忙的时间与整个刷新周期的比值。其中任务管理器默认刷新周期为1s。因此我们有如下公式:
sysTime = kernel + user
cpu = (sysTime-idle)*100/sysTime
cpuidle = idle*100/sysTime
其中,sysTime表示该时间段的CPU总时间,其等于用户态user与内核态kernel时间总和。 idle表示cpu空闲状态时间。而如何获取到时间片段呢?我采用的方法是利用连续两次时间,相减得到时间片段。最后代码如下:
__int64 CompareFileTime(FILETIME time1, FILETIME time2)
{
__int64 a = time1.dwHighDateTime << 32 | time1.dwLowDateTime;
__int64 b = time2.dwHighDateTime << 32 | time2.dwLowDateTime;
return (b - a);
}
void getWin_CpuMemUsage(){
HANDLE hEvent;
BOOL res;
FILETIME preidleTime;
FILETIME prekernelTime;
FILETIME preuserTime;
FILETIME idleTime;
FILETIME kernelTime;
FILETIME userTime;
res = GetSystemTimes(&idleTime, &kernelTime, &userTime);
preidleTime = idleTime;
prekernelTime = kernelTime;
preuserTime = userTime;
hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); // 初始值为 nonsignaled ,并且每次触发后自动设置为nonsignaled
WaitForSingleObject(hEvent, 1000);
res = GetSystemTimes(&idleTime, &kernelTime, &userTime);
__int64 idle = CompareFileTime(preidleTime, idleTime);
__int64 kernel = CompareFileTime(prekernelTime, kernelTime);
__int64 user = CompareFileTime(preuserTime, userTime);
__int64 cpu = (kernel + user - idle) * 100 / (kernel + user);
__int64 cpuidle = (idle)* 100 / (kernel + user);
cout << "CPU利用率:" << cpu << "%" << " CPU空闲率:" << cpuidle << "%" << endl;
preidleTime = idleTime;
prekernelTime = kernelTime;
preuserTime = userTime;
}
守护进程
之前在网上找了好多方法,又是要获取进程id,又是要创建子进程的。反正一堆乱七八糟,看得我晕得很,就放弃了。最后决定采取的方案是,对windows的进程列表做快照,发现没有目标程序,就直接调用WinExec()给它启动起来。
#include <Tlhelp32.h>
#include <shellapi.h>
void EnumProc()
{
PROCESSENTRY32 pe32;
pe32.dwSize=sizeof(PROCESSENTRY32);
BOOL bRet;
HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
bRet=Process32First(hProcessSnap,&pe32);
BOOL flag=0;
while(bRet)
{
bRet=Process32Next(hProcessSnap,&pe32);
string task=pe32.szExeFile;
if(task=="notepad.exe")
{
flag=1;
}
}
if (flag==0)
{
WinExec("notepad.exe",SW_SHOWMAXIMIZED);
}
}
C++ 11 线程
这次发现C++ 11的线程机制非常好用,可以自定义线程函数了,所以总结一下。
- 声明线程并指明回调函数
- 以结合(join)或者分离(detach)的方式启用线程
#include <thread>
#include <iostream>
#include <mutex>
using namespace std;
void HookProc()
{
cout<<"hook"<<endl;
std::this_thread::sleep_for (chrono::seconds(10));
}
void SystemUsageProc()
{
cout<<"SystemUse"<<endl;
std::this_thread::sleep_for (chrono::seconds(5));
}
int main()
{
std::thread hookEvent(HookProc);
std::thread systemUsage(SystemUsageProc);
hookEvent.join();
systemUsage.join();
//hookEvent.detach();
//systemUsage.detach();
return 0;
}
另外,包含了mutex之后可以使用锁机制,对共享数据做加锁的操作。其使用方法是对共享数据前后使用:
std::mutex t_lock;
t_lock.lock();
//操作共享数据
t_lock.unlock();
time_t与string互转
最后,记录一下几个用到的工具。
//time_t 转 "%Y/%m/%d-%H:%M:%S"
string time2String(time_t time)
{
char buff[20];
strftime(buff,20, "%Y/%m/%d-%H:%M:%S", localtime(&time));
string timeStr=buff;
return timeStr;
}
// "%Y/%m/%d-%H:%M:%S" 转time_t
time_t string2Time(const std::string & tstring)
{
struct tm tm1;
time_t time1;
string tmp=tstring;
int i = sscanf(tmp.c_str(), "%d/%d/%d-%d:%d:%d" ,
&(tm1.tm_year),
&(tm1.tm_mon),
&(tm1.tm_mday),
&(tm1.tm_hour),
&(tm1.tm_min),
&(tm1.tm_sec),
&(tm1.tm_wday),
&(tm1.tm_yday));
tm1.tm_year -= 1900;
tm1.tm_mon --;
tm1.tm_isdst=-1;
time1 = mktime(&tm1);
return time1;
}
over,先记录这么多。以后用不用得到再说。