Table of Content
  1. Windows鼠标键盘Hook
    1. pyHook
    2. C++ Hook
  2. CPU、内存查看
  3. 守护进程
  4. C++ 11 线程
  5. time_t与string互转

最近的工作中,有个需求,需要监控用户使用Windows电脑时候的鼠标键盘事件、cpu内存使用量、以及守护另外一个程序。涉及到的一些知识,个人觉得除非以后要写个外挂、流氓软件、或者是盗号软件,应该以后都不会用到了。

但是个人觉得还是记录下来一些比较好,万一以后要用到还忘记了,就TM尴尬了。难说以后混不下去了,还可以去盗号赚点生活费什么的。

Windows鼠标键盘Hook

这部分本来使用PyHook做的,因为用python写起来比较简单。后来由于项目需要,改成用C++了。但是还是决定把pyHook的方法记录一下,免得以后找起来麻烦。

pyHook

首先,肯定是要安装好pyHookpywin32。毕竟需要调用的其实还是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,先记录这么多。以后用不用得到再说。